From f5914e8c10ab864b45756e8cb5687376581191d5 Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Tue, 30 Oct 2018 16:19:59 +1100 Subject: [PATCH] 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
- + - + - +