mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
websocket/gateio: Support multi connection management and integrate with GateIO (#1580)
* gateio: Add multi asset websocket support WIP. * meow * Add tests and shenanigans * integrate flushing and for enabling/disabling pairs from rpc shenanigans * some changes * linter: fixes strikes again. * Change name ConnectionAssociation -> ConnectionCandidate for better clarity on purpose. Change connections map to point to candidate to track subscriptions for future dynamic connections holder and drop struct ConnectionDetails. * Add subscription tests (state functional) * glorious:nits + proxy handling * Spelling * linter: fixerino * instead of nil, dont do nil. * clean up nils * cya nils * don't need to set URL or check if its running * stop ping handler routine leak * * Fix bug where reader routine on error that is not a disconnection error but websocket frame error or anything really makes the reader routine return and then connection never cycles and the buffer gets filled. * Handle reconnection via an errors.Is check which is simpler and in that scope allow for quick disconnect reconnect without waiting for connection cycle. * Dial now uses code from DialContext but just calls context.Background() * Don't allow reader to return on parse binary response error. Just output error and return a non nil response * Allow rollback on connect on any error across all connections * fix shadow jutsu * glorious/gk: nitters - adds in ws mock server * linter: fix * fix deadlock on connection as the previous channel had no reader and would hang connection reader for eternity. * gk: nits * Leak issue and edge case * gk: nits * gk: drain brain * glorious: nits * Update exchanges/stream/websocket.go Co-authored-by: Scott <gloriousCode@users.noreply.github.com> * glorious: nits * add tests * linter: fix * After merge * Add error connection info * Fix edge case where it does not reconnect made by an already closed connection * stream coverage * glorious: nits * glorious: nits removed asset error handling in stream package * linter: fix * rm block * Add basic readme * fix asset enabled flush cycle for multi connection * spella: fix * linter: fix * Add glorious suggestions, fix some race thing * reinstate name before any routine gets spawned * stop on error in mock tests * glorious: nits * glorious: nits found in CI build * Add test for drain, bumped wait times as there seems to be something happening on macos CI builds, used context.WithTimeout because its instant. * mutex across shutdown and connect for protection * lint: fix * test time withoffset, reinstate stop * fix whoops * const trafficCheckInterval; rm testmain * y * fix lint * bump time check window * stream: fix intermittant test failures while testing routines and remove code that is not needed. * spells * cant do what I did * protect race due to routine. * update testURL * use mock websocket connection instead of test URL's * linter: fix * remove url because its throwing errors on CI builds * connections drop all the time, don't need to worry about not being able to echo back ws data as it can be easily reviewed _test file side. * remove another superfluous url thats not really set up for this * spawn overwatch routine when there is no errors, inline checker instead of waiting for a time period, add sleep inline with echo handler as this is really quick and wanted to ensure that latency is handing correctly * linter: fixerino uperino * glorious: panix * linter: things * whoops * defer lock and use functions that don't require locking in SetProxyAddress * lint: fix * thrasher: nits --------- Co-authored-by: shazbert <ryan.oharareid@thrasher.io> Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,9 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/futures"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
|
||||
testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions"
|
||||
@@ -2540,7 +2542,7 @@ const wsTickerPushDataJSON = `{"time": 1606291803, "channel": "spot.tickers", "e
|
||||
|
||||
func TestWsTickerPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsTickerPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsTickerPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket ticker push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2549,7 +2551,7 @@ const wsTradePushDataJSON = `{ "time": 1606292218, "channel": "spot.trades", "ev
|
||||
|
||||
func TestWsTradePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsTradePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsTradePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket trade push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2558,7 +2560,7 @@ const wsCandlestickPushDataJSON = `{"time": 1606292600, "channel": "spot.candles
|
||||
|
||||
func TestWsCandlestickPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsCandlestickPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsCandlestickPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket candlestick push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2567,7 +2569,7 @@ const wsOrderbookTickerJSON = `{"time": 1606293275, "channel": "spot.book_ticker
|
||||
|
||||
func TestWsOrderbookTickerPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsOrderbookTickerJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsOrderbookTickerJSON)); err != nil {
|
||||
t.Errorf("%s websocket orderbook push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2579,11 +2581,11 @@ const (
|
||||
|
||||
func TestWsOrderbookSnapshotPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := g.wsHandleData([]byte(wsOrderbookSnapshotPushDataJSON))
|
||||
err := g.WsHandleSpotData(context.Background(), []byte(wsOrderbookSnapshotPushDataJSON))
|
||||
if err != nil {
|
||||
t.Errorf("%s websocket orderbook snapshot push data error: %v", g.Name, err)
|
||||
}
|
||||
if err = g.wsHandleData([]byte(wsOrderbookUpdatePushDataJSON)); err != nil {
|
||||
if err = g.WsHandleSpotData(context.Background(), []byte(wsOrderbookUpdatePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket orderbook update push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2592,7 +2594,7 @@ const wsSpotOrderPushDataJSON = `{"time": 1605175506, "channel": "spot.orders",
|
||||
|
||||
func TestWsPushOrders(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsSpotOrderPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsSpotOrderPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket orders push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2601,7 +2603,7 @@ const wsUserTradePushDataJSON = `{"time": 1605176741, "channel": "spot.usertrade
|
||||
|
||||
func TestWsUserTradesPushDataJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsUserTradePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsUserTradePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket users trade push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2610,7 +2612,7 @@ const wsBalancesPushDataJSON = `{"time": 1605248616, "channel": "spot.balances",
|
||||
|
||||
func TestBalancesPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsBalancesPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsBalancesPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket balances push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2619,7 +2621,7 @@ const wsMarginBalancePushDataJSON = `{"time": 1605248616, "channel": "spot.fundi
|
||||
|
||||
func TestMarginBalancePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsMarginBalancePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsMarginBalancePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket margin balance push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2628,7 +2630,7 @@ const wsCrossMarginBalancePushDataJSON = `{"time": 1605248616,"channel": "spot.c
|
||||
|
||||
func TestCrossMarginBalancePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsCrossMarginBalancePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsCrossMarginBalancePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket cross margin balance push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2637,7 +2639,7 @@ const wsCrossMarginBalanceLoan = `{ "time":1658289372, "channel":"spot.cross_loa
|
||||
|
||||
func TestCrossMarginBalanceLoan(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleData([]byte(wsCrossMarginBalanceLoan)); err != nil {
|
||||
if err := g.WsHandleSpotData(context.Background(), []byte(wsCrossMarginBalanceLoan)); err != nil {
|
||||
t.Errorf("%s websocket cross margin loan push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2646,7 +2648,7 @@ const wsFuturesTickerPushDataJSON = `{"time": 1541659086, "channel": "futures.ti
|
||||
|
||||
func TestFuturesTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesTickerPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesTickerPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2655,7 +2657,7 @@ const wsFuturesTradesPushDataJSON = `{"channel": "futures.trades","event": "upda
|
||||
|
||||
func TestFuturesTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesTradesPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesTradesPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2666,7 +2668,7 @@ const (
|
||||
|
||||
func TestOrderbookData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesOrderbookTickerJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesOrderbookTickerJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket orderbook ticker push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2675,7 +2677,7 @@ const wsFuturesOrderPushDataJSON = `{ "channel": "futures.orders", "event": "upd
|
||||
|
||||
func TestFuturesOrderPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesOrderPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesOrderPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures order push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2684,7 +2686,7 @@ const wsFuturesUsertradesPushDataJSON = `{"time": 1543205083, "channel": "future
|
||||
|
||||
func TestFuturesUserTrades(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesUsertradesPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesUsertradesPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures user trades push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2693,7 +2695,7 @@ const wsFuturesLiquidationPushDataJSON = `{"channel": "futures.liquidates", "eve
|
||||
|
||||
func TestFuturesLiquidationPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesLiquidationPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesLiquidationPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures liquidation push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2702,7 +2704,7 @@ const wsFuturesAutoDelevergesNotification = `{"channel": "futures.auto_deleverag
|
||||
|
||||
func TestFuturesAutoDeleverges(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesAutoDelevergesNotification), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesAutoDelevergesNotification), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures auto deleverge push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2711,7 +2713,7 @@ const wsFuturesPositionClosePushDataJSON = ` {"channel": "futures.position_close
|
||||
|
||||
func TestPositionClosePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesPositionClosePushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesPositionClosePushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures position close push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2720,7 +2722,7 @@ const wsFuturesBalanceNotificationPushDataJSON = `{"channel": "futures.balances"
|
||||
|
||||
func TestFuturesBalanceNotification(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesBalanceNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesBalanceNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures balance notification push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2729,7 +2731,7 @@ const wsFuturesReduceRiskLimitNotificationPushDataJSON = `{"time": 1551858330, "
|
||||
|
||||
func TestFuturesReduceRiskLimitPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesReduceRiskLimitNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesReduceRiskLimitNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures reduce risk limit notification push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2738,7 +2740,7 @@ const wsFuturesPositionsNotificationPushDataJSON = `{"time": 1588212926,"channel
|
||||
|
||||
func TestFuturesPositionsNotification(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesPositionsNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesPositionsNotificationPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures positions change notification push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2747,7 +2749,7 @@ const wsFuturesAutoOrdersPushDataJSON = `{"time": 1596798126,"channel": "futures
|
||||
|
||||
func TestFuturesAutoOrderPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleFuturesData([]byte(wsFuturesAutoOrdersPushDataJSON), asset.Futures); err != nil {
|
||||
if err := g.WsHandleFuturesData(context.Background(), []byte(wsFuturesAutoOrdersPushDataJSON), asset.Futures); err != nil {
|
||||
t.Errorf("%s websocket futures auto orders push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2758,7 +2760,7 @@ const optionsContractTickerPushDataJSON = `{"time": 1630576352, "channel": "opti
|
||||
|
||||
func TestOptionsContractTickerPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsContractTickerPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsContractTickerPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options contract ticker push data failed with error %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2767,7 +2769,7 @@ const optionsUnderlyingTickerPushDataJSON = `{"time": 1630576352, "channel": "op
|
||||
|
||||
func TestOptionsUnderlyingTickerPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsUnderlyingTickerPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsUnderlyingTickerPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options underlying ticker push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2776,7 +2778,7 @@ const optionsContractTradesPushDataJSON = `{"time": 1630576356, "channel": "opti
|
||||
|
||||
func TestOptionsContractTradesPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsContractTradesPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsContractTradesPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket contract trades push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2785,7 +2787,7 @@ const optionsUnderlyingTradesPushDataJSON = `{"time": 1630576356, "channel": "op
|
||||
|
||||
func TestOptionsUnderlyingTradesPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsUnderlyingTradesPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsUnderlyingTradesPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket underlying trades push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2794,7 +2796,7 @@ const optionsUnderlyingPricePushDataJSON = `{ "time": 1630576356, "channel": "op
|
||||
|
||||
func TestOptionsUnderlyingPricePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsUnderlyingPricePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsUnderlyingPricePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket underlying price push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2803,7 +2805,7 @@ const optionsMarkPricePushDataJSON = `{ "time": 1630576356, "channel": "options.
|
||||
|
||||
func TestOptionsMarkPricePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsMarkPricePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsMarkPricePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket mark price push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2812,7 +2814,7 @@ const optionsSettlementsPushDataJSON = `{ "time": 1630576356, "channel": "option
|
||||
|
||||
func TestSettlementsPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsSettlementsPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsSettlementsPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options settlements push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2821,7 +2823,7 @@ const optionsContractPushDataJSON = `{"time": 1630576356, "channel": "options.co
|
||||
|
||||
func TestOptionsContractPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsContractPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsContractPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options contracts push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2833,10 +2835,10 @@ const (
|
||||
|
||||
func TestOptionsCandlesticksPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsContractCandlesticksPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsContractCandlesticksPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options contracts candlestick push data error: %v", g.Name, err)
|
||||
}
|
||||
if err := g.wsHandleOptionsData([]byte(optionsUnderlyingCandlesticksPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsUnderlyingCandlesticksPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options underlying candlestick push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2850,17 +2852,17 @@ const (
|
||||
|
||||
func TestOptionsOrderbookPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := g.wsHandleOptionsData([]byte(optionsOrderbookTickerPushDataJSON))
|
||||
err := g.WsHandleOptionsData(context.Background(), []byte(optionsOrderbookTickerPushDataJSON))
|
||||
if err != nil {
|
||||
t.Errorf("%s websocket options orderbook ticker push data error: %v", g.Name, err)
|
||||
}
|
||||
if err = g.wsHandleOptionsData([]byte(optionsOrderbookSnapshotPushDataJSON)); err != nil {
|
||||
if err = g.WsHandleOptionsData(context.Background(), []byte(optionsOrderbookSnapshotPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options orderbook snapshot push data error: %v", g.Name, err)
|
||||
}
|
||||
if err = g.wsHandleOptionsData([]byte(optionsOrderbookUpdatePushDataJSON)); err != nil {
|
||||
if err = g.WsHandleOptionsData(context.Background(), []byte(optionsOrderbookUpdatePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options orderbook update push data error: %v", g.Name, err)
|
||||
}
|
||||
if err = g.wsHandleOptionsData([]byte(optionsOrderbookSnapshotUpdateEventPushDataJSON)); err != nil {
|
||||
if err = g.WsHandleOptionsData(context.Background(), []byte(optionsOrderbookSnapshotUpdateEventPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options orderbook snapshot update event push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2869,7 +2871,7 @@ const optionsOrderPushDataJSON = `{"time": 1630654851,"channel": "options.orders
|
||||
|
||||
func TestOptionsOrderPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsOrderPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsOrderPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options orders push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2878,7 +2880,7 @@ const optionsUsersTradesPushDataJSON = `{ "time": 1639144214, "channel": "option
|
||||
|
||||
func TestOptionUserTradesPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsUsersTradesPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsUsersTradesPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options orders push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2887,7 +2889,7 @@ const optionsLiquidatesPushDataJSON = `{ "channel": "options.liquidates", "event
|
||||
|
||||
func TestOptionsLiquidatesPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsLiquidatesPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsLiquidatesPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options liquidates push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2896,7 +2898,7 @@ const optionsSettlementPushDataJSON = `{ "channel": "options.user_settlements",
|
||||
|
||||
func TestOptionsSettlementPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsSettlementPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsSettlementPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options settlement push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2905,7 +2907,7 @@ const optionsPositionClosePushDataJSON = `{"channel": "options.position_closes",
|
||||
|
||||
func TestOptionsPositionClosePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsPositionClosePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsPositionClosePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options position close push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2914,7 +2916,7 @@ const optionsBalancePushDataJSON = `{ "channel": "options.balances", "event": "u
|
||||
|
||||
func TestOptionsBalancePushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsBalancePushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsBalancePushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options balance push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2923,7 +2925,7 @@ const optionsPositionPushDataJSON = `{"time": 1630654851, "channel": "options.po
|
||||
|
||||
func TestOptionsPositionPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
if err := g.wsHandleOptionsData([]byte(optionsPositionPushDataJSON)); err != nil {
|
||||
if err := g.WsHandleOptionsData(context.Background(), []byte(optionsPositionPushDataJSON)); err != nil {
|
||||
t.Errorf("%s websocket options position push data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -2935,11 +2937,11 @@ const (
|
||||
|
||||
func TestFuturesOrderbookPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := g.wsHandleFuturesData([]byte(futuresOrderbookPushData), asset.Futures)
|
||||
err := g.WsHandleFuturesData(context.Background(), []byte(futuresOrderbookPushData), asset.Futures)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = g.wsHandleFuturesData([]byte(futuresOrderbookUpdatePushData), asset.Futures)
|
||||
err = g.WsHandleFuturesData(context.Background(), []byte(futuresOrderbookUpdatePushData), asset.Futures)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -2949,14 +2951,13 @@ const futuresCandlesticksPushData = `{"time": 1678469467, "time_ms": 16784694679
|
||||
|
||||
func TestFuturesCandlestickPushData(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := g.wsHandleFuturesData([]byte(futuresCandlesticksPushData), asset.Futures)
|
||||
err := g.WsHandleFuturesData(context.Background(), []byte(futuresCandlesticksPushData), asset.Futures)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateSubscriptions exercises generateSubscriptions
|
||||
func TestGenerateSubscriptions(t *testing.T) {
|
||||
func TestGenerateSubscriptionsSpot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
g := new(Gateio) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
@@ -2966,7 +2967,7 @@ func TestGenerateSubscriptions(t *testing.T) {
|
||||
g.Features.Subscriptions = append(g.Features.Subscriptions, &subscription.Subscription{
|
||||
Enabled: true, Channel: spotOrderbookChannel, Asset: asset.Spot, Interval: kline.ThousandMilliseconds, Levels: 5,
|
||||
})
|
||||
subs, err := g.generateSubscriptions()
|
||||
subs, err := g.generateSubscriptionsSpot()
|
||||
require.NoError(t, err, "generateSubscriptions must not error")
|
||||
exp := subscription.List{}
|
||||
for _, s := range g.Features.Subscriptions {
|
||||
@@ -3005,13 +3006,10 @@ func TestGenerateSubscriptions(t *testing.T) {
|
||||
|
||||
func TestSubscribe(t *testing.T) {
|
||||
t.Parallel()
|
||||
g := new(Gateio) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
|
||||
require.NoError(t, testexch.Setup(g), "Test instance Setup must not error")
|
||||
subs, err := g.Features.Subscriptions.ExpandTemplates(g)
|
||||
require.NoError(t, err, "ExpandTemplates must not error")
|
||||
g.Features.Subscriptions = subscription.List{}
|
||||
testexch.SetupWs(t, g)
|
||||
err = g.Subscribe(subs)
|
||||
err = g.Subscribe(context.Background(), &DummyConnection{}, subs)
|
||||
require.NoError(t, err, "Subscribe must not error")
|
||||
}
|
||||
|
||||
@@ -3023,7 +3021,11 @@ func TestGenerateDeliveryFuturesDefaultSubscriptions(t *testing.T) {
|
||||
}
|
||||
func TestGenerateFuturesDefaultSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
if _, err := g.GenerateFuturesDefaultSubscriptions(); err != nil {
|
||||
if _, err := g.GenerateFuturesDefaultSubscriptions(currency.USDT); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := g.GenerateFuturesDefaultSubscriptions(currency.BTC); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -3657,3 +3659,26 @@ func TestGenerateWebsocketMessageID(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.NotEmpty(t, g.GenerateWebsocketMessageID(false))
|
||||
}
|
||||
|
||||
type DummyConnection struct{ stream.Connection }
|
||||
|
||||
func (d *DummyConnection) GenerateMessageID(bool) int64 { return 1337 }
|
||||
func (d *DummyConnection) SendMessageReturnResponse(context.Context, request.EndpointLimit, any, any) ([]byte, error) {
|
||||
return []byte(`{"time":1726121320,"time_ms":1726121320745,"id":1,"conn_id":"f903779a148987ca","trace_id":"d8ee37cd14347e4ed298d44e69aedaa7","channel":"spot.tickers","event":"subscribe","payload":["BRETT_USDT"],"result":{"status":"success"},"requestId":"d8ee37cd14347e4ed298d44e69aedaa7"}`), nil
|
||||
}
|
||||
|
||||
func TestHandleSubscriptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
subs := subscription.List{{Channel: subscription.OrderbookChannel}}
|
||||
|
||||
err := g.handleSubscription(context.Background(), &DummyConnection{}, subscribeEvent, subs, func(context.Context, stream.Connection, string, subscription.List) ([]WsInput, error) {
|
||||
return []WsInput{{}}, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = g.handleSubscription(context.Background(), &DummyConnection{}, unsubscribeEvent, subs, func(context.Context, stream.Connection, string, subscription.List) ([]WsInput, error) {
|
||||
return []WsInput{{}}, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ const (
|
||||
spotFundingBalanceChannel = "spot.funding_balances"
|
||||
crossMarginBalanceChannel = "spot.cross_balances"
|
||||
crossMarginLoanChannel = "spot.cross_loan"
|
||||
|
||||
subscribeEvent = "subscribe"
|
||||
unsubscribeEvent = "unsubscribe"
|
||||
)
|
||||
|
||||
var defaultSubscriptions = subscription.List{
|
||||
@@ -71,16 +74,13 @@ var subscriptionNames = map[string]string{
|
||||
subscription.AllTradesChannel: spotTradesChannel,
|
||||
}
|
||||
|
||||
// WsConnect initiates a websocket connection
|
||||
func (g *Gateio) WsConnect() error {
|
||||
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
|
||||
return stream.ErrWebsocketNotEnabled
|
||||
}
|
||||
// WsConnectSpot initiates a websocket connection
|
||||
func (g *Gateio) WsConnectSpot(ctx context.Context, conn stream.Connection) error {
|
||||
err := g.CurrencyPairs.IsAssetEnabled(asset.Spot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.Conn.Dial(&websocket.Dialer{}, http.Header{})
|
||||
err = conn.DialContext(ctx, &websocket.Dialer{}, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,14 +88,12 @@ func (g *Gateio) WsConnect() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
Websocket: true,
|
||||
Delay: time.Second * 15,
|
||||
Message: pingMessage,
|
||||
MessageType: websocket.TextMessage,
|
||||
})
|
||||
g.Websocket.Wg.Add(1)
|
||||
go g.wsReadConnData()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -108,29 +106,15 @@ func (g *Gateio) generateWsSignature(secret, event, channel string, t int64) (st
|
||||
return hex.EncodeToString(mac.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// wsReadConnData receives and passes on websocket messages for processing
|
||||
func (g *Gateio) wsReadConnData() {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
resp := g.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
err := g.wsHandleData(resp.Raw)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gateio) wsHandleData(respRaw []byte) error {
|
||||
// WsHandleSpotData handles spot data
|
||||
func (g *Gateio) WsHandleSpotData(_ context.Context, respRaw []byte) error {
|
||||
var push WsResponse
|
||||
err := json.Unmarshal(respRaw, &push)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if push.Event == "subscribe" || push.Event == "unsubscribe" {
|
||||
if push.Event == subscribeEvent || push.Event == unsubscribeEvent {
|
||||
if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) {
|
||||
return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID)
|
||||
}
|
||||
@@ -641,22 +625,25 @@ func (g *Gateio) processCrossMarginLoans(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateSubscriptions returns configured subscriptions
|
||||
func (g *Gateio) generateSubscriptions() (subscription.List, error) {
|
||||
// generateSubscriptionsSpot returns configured subscriptions
|
||||
func (g *Gateio) generateSubscriptionsSpot() (subscription.List, error) {
|
||||
return g.Features.Subscriptions.ExpandTemplates(g)
|
||||
}
|
||||
|
||||
// GetSubscriptionTemplate returns a subscription channel template
|
||||
func (g *Gateio) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) {
|
||||
return template.New("master.tmpl").Funcs(sprig.FuncMap()).Funcs(template.FuncMap{
|
||||
"channelName": channelName,
|
||||
"singleSymbolChannel": singleSymbolChannel,
|
||||
"interval": g.GetIntervalString,
|
||||
}).Parse(subTplText)
|
||||
return template.New("master.tmpl").
|
||||
Funcs(sprig.FuncMap()).
|
||||
Funcs(template.FuncMap{
|
||||
"channelName": channelName,
|
||||
"singleSymbolChannel": singleSymbolChannel,
|
||||
"interval": g.GetIntervalString,
|
||||
}).
|
||||
Parse(subTplText)
|
||||
}
|
||||
|
||||
// manageSubs sends a websocket message to subscribe or unsubscribe from a list of channel
|
||||
func (g *Gateio) manageSubs(event string, subs subscription.List) error {
|
||||
func (g *Gateio) manageSubs(ctx context.Context, event string, conn stream.Connection, subs subscription.List) error {
|
||||
var errs error
|
||||
subs, errs = subs.ExpandTemplates(g)
|
||||
if errs != nil {
|
||||
@@ -665,11 +652,11 @@ func (g *Gateio) manageSubs(event string, subs subscription.List) error {
|
||||
|
||||
for _, s := range subs {
|
||||
if err := func() error {
|
||||
msg, err := g.manageSubReq(event, s)
|
||||
msg, err := g.manageSubReq(ctx, event, conn, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := g.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, msg.ID, msg)
|
||||
result, err := conn.SendMessageReturnResponse(ctx, request.Unset, msg.ID, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -681,9 +668,9 @@ func (g *Gateio) manageSubs(event string, subs subscription.List) error {
|
||||
return fmt.Errorf("(%d) %s", resp.Error.Code, resp.Error.Message)
|
||||
}
|
||||
if event == "unsubscribe" {
|
||||
return g.Websocket.RemoveSubscriptions(s)
|
||||
return g.Websocket.RemoveSubscriptions(conn, s)
|
||||
}
|
||||
return g.Websocket.AddSuccessfulSubscriptions(s)
|
||||
return g.Websocket.AddSuccessfulSubscriptions(conn, s)
|
||||
}(); err != nil {
|
||||
errs = common.AppendError(errs, fmt.Errorf("%s %s %s: %w", s.Channel, s.Asset, s.Pairs, err))
|
||||
}
|
||||
@@ -692,16 +679,16 @@ func (g *Gateio) manageSubs(event string, subs subscription.List) error {
|
||||
}
|
||||
|
||||
// manageSubReq constructs the subscription management message for a subscription
|
||||
func (g *Gateio) manageSubReq(event string, s *subscription.Subscription) (*WsInput, error) {
|
||||
func (g *Gateio) manageSubReq(ctx context.Context, event string, conn stream.Connection, s *subscription.Subscription) (*WsInput, error) {
|
||||
req := &WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelName(s),
|
||||
Time: time.Now().Unix(),
|
||||
Payload: strings.Split(s.QualifiedChannel, ","),
|
||||
}
|
||||
if s.Authenticated {
|
||||
creds, err := g.GetCredentials(context.TODO())
|
||||
creds, err := g.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -719,13 +706,13 @@ func (g *Gateio) manageSubReq(event string, s *subscription.Subscription) (*WsIn
|
||||
}
|
||||
|
||||
// Subscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) Subscribe(subs subscription.List) error {
|
||||
return g.manageSubs("subscribe", subs)
|
||||
func (g *Gateio) Subscribe(ctx context.Context, conn stream.Connection, subs subscription.List) error {
|
||||
return g.manageSubs(ctx, subscribeEvent, conn, subs)
|
||||
}
|
||||
|
||||
// Unsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) Unsubscribe(subs subscription.List) error {
|
||||
return g.manageSubs("unsubscribe", subs)
|
||||
func (g *Gateio) Unsubscribe(ctx context.Context, conn stream.Connection, subs subscription.List) error {
|
||||
return g.manageSubs(ctx, unsubscribeEvent, conn, subs)
|
||||
}
|
||||
|
||||
func (g *Gateio) listOfAssetsCurrencyPairEnabledFor(cp currency.Pair) map[asset.Item]bool {
|
||||
@@ -782,3 +769,37 @@ const subTplText = `
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
// GeneratePayload returns the payload for a websocket message
|
||||
type GeneratePayload func(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List) ([]WsInput, error)
|
||||
|
||||
// handleSubscription sends a websocket message to receive data from the channel
|
||||
func (g *Gateio) handleSubscription(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List, generatePayload GeneratePayload) error {
|
||||
payloads, err := generatePayload(ctx, conn, event, channelsToSubscribe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
for k := range payloads {
|
||||
result, err := conn.SendMessageReturnResponse(ctx, request.Unset, payloads[k].ID, payloads[k])
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
}
|
||||
var resp WsEventResponse
|
||||
if err = json.Unmarshal(result, &resp); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else {
|
||||
if resp.Error != nil && resp.Error.Code != 0 {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s error code: %d message: %s", payloads[k].Event, payloads[k].Channel, resp.Error.Code, resp.Error.Message))
|
||||
continue
|
||||
}
|
||||
if event == subscribeEvent {
|
||||
errs = common.AppendError(errs, g.Websocket.AddSuccessfulSubscriptions(conn, channelsToSubscribe[k]))
|
||||
} else {
|
||||
errs = common.AppendError(errs, g.Websocket.RemoveSubscriptions(conn, channelsToSubscribe[k]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
@@ -93,12 +94,12 @@ func (g *Gateio) SetDefaults() {
|
||||
OrderbookFetching: true,
|
||||
TradeFetching: true,
|
||||
KlineFetching: true,
|
||||
FullPayloadSubscribe: true,
|
||||
AuthenticatedEndpoints: true,
|
||||
MessageCorrelation: true,
|
||||
GetOrder: true,
|
||||
AccountBalance: true,
|
||||
Subscribe: true,
|
||||
Unsubscribe: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCrypto |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -155,26 +156,16 @@ func (g *Gateio) SetDefaults() {
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
// TODO: Majority of margin REST endpoints are labelled as deprecated on the API docs. These will need to be removed.
|
||||
err = g.DisableAssetWebsocketSupport(asset.Margin)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
// TODO: Add websocket cross margin support.
|
||||
err = g.DisableAssetWebsocketSupport(asset.CrossMargin)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = g.DisableAssetWebsocketSupport(asset.Futures)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = g.DisableAssetWebsocketSupport(asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
err = g.DisableAssetWebsocketSupport(asset.Options)
|
||||
if err != nil {
|
||||
log.Errorln(log.ExchangeSys, err)
|
||||
}
|
||||
g.API.Endpoints = g.NewEndpoints()
|
||||
err = g.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
|
||||
exchange.RestSpot: gateioTradeURL,
|
||||
@@ -206,31 +197,101 @@ func (g *Gateio) Setup(exch *config.Exchange) error {
|
||||
return err
|
||||
}
|
||||
|
||||
wsRunningURL, err := g.API.Endpoints.GetURL(exchange.WebsocketSpot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.Websocket.Setup(&stream.WebsocketSetup{
|
||||
ExchangeConfig: exch,
|
||||
DefaultURL: gateioWebsocketEndpoint,
|
||||
RunningURL: wsRunningURL,
|
||||
Connector: g.WsConnect,
|
||||
Subscriber: g.Subscribe,
|
||||
Unsubscriber: g.Unsubscribe,
|
||||
GenerateSubscriptions: g.generateSubscriptions,
|
||||
Features: &g.Features.Supports.WebsocketCapabilities,
|
||||
FillsFeed: g.Features.Enabled.FillsFeed,
|
||||
TradeFeed: g.Features.Enabled.TradeFeed,
|
||||
ExchangeConfig: exch,
|
||||
Features: &g.Features.Supports.WebsocketCapabilities,
|
||||
FillsFeed: g.Features.Enabled.FillsFeed,
|
||||
TradeFeed: g.Features.Enabled.TradeFeed,
|
||||
UseMultiConnectionManagement: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
// Spot connection
|
||||
err = g.Websocket.SetupNewConnection(&stream.ConnectionSetup{
|
||||
URL: gateioWebsocketEndpoint,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
Handler: g.WsHandleSpotData,
|
||||
Subscriber: g.Subscribe,
|
||||
Unsubscriber: g.Unsubscribe,
|
||||
GenerateSubscriptions: g.generateSubscriptionsSpot,
|
||||
Connector: g.WsConnectSpot,
|
||||
BespokeGenerateMessageID: g.GenerateWebsocketMessageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Futures connection - USDT margined
|
||||
err = g.Websocket.SetupNewConnection(&stream.ConnectionSetup{
|
||||
URL: futuresWebsocketUsdtURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
Handler: func(ctx context.Context, incoming []byte) error {
|
||||
return g.WsHandleFuturesData(ctx, incoming, asset.Futures)
|
||||
},
|
||||
Subscriber: g.FuturesSubscribe,
|
||||
Unsubscriber: g.FuturesUnsubscribe,
|
||||
GenerateSubscriptions: func() (subscription.List, error) { return g.GenerateFuturesDefaultSubscriptions(currency.USDT) },
|
||||
Connector: g.WsFuturesConnect,
|
||||
BespokeGenerateMessageID: g.GenerateWebsocketMessageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Futures connection - BTC margined
|
||||
err = g.Websocket.SetupNewConnection(&stream.ConnectionSetup{
|
||||
URL: futuresWebsocketBtcURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
Handler: func(ctx context.Context, incoming []byte) error {
|
||||
return g.WsHandleFuturesData(ctx, incoming, asset.Futures)
|
||||
},
|
||||
Subscriber: g.FuturesSubscribe,
|
||||
Unsubscriber: g.FuturesUnsubscribe,
|
||||
GenerateSubscriptions: func() (subscription.List, error) { return g.GenerateFuturesDefaultSubscriptions(currency.BTC) },
|
||||
Connector: g.WsFuturesConnect,
|
||||
BespokeGenerateMessageID: g.GenerateWebsocketMessageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Add BTC margined delivery futures.
|
||||
// Futures connection - Delivery - USDT margined
|
||||
err = g.Websocket.SetupNewConnection(&stream.ConnectionSetup{
|
||||
URL: deliveryRealUSDTTradingURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
Handler: func(ctx context.Context, incoming []byte) error {
|
||||
return g.WsHandleFuturesData(ctx, incoming, asset.DeliveryFutures)
|
||||
},
|
||||
Subscriber: g.DeliveryFuturesSubscribe,
|
||||
Unsubscriber: g.DeliveryFuturesUnsubscribe,
|
||||
GenerateSubscriptions: g.GenerateDeliveryFuturesDefaultSubscriptions,
|
||||
Connector: g.WsDeliveryFuturesConnect,
|
||||
BespokeGenerateMessageID: g.GenerateWebsocketMessageID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Futures connection - Options
|
||||
return g.Websocket.SetupNewConnection(&stream.ConnectionSetup{
|
||||
URL: optionsWebsocketURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
|
||||
Handler: g.WsHandleOptionsData,
|
||||
Subscriber: g.OptionsSubscribe,
|
||||
Unsubscriber: g.OptionsUnsubscribe,
|
||||
GenerateSubscriptions: g.GenerateOptionsDefaultSubscriptions,
|
||||
Connector: g.WsOptionsConnect,
|
||||
BespokeGenerateMessageID: g.GenerateWebsocketMessageID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,14 +4,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
@@ -19,7 +16,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,60 +35,27 @@ var defaultDeliveryFuturesSubscriptions = []string{
|
||||
futuresCandlesticksChannel,
|
||||
}
|
||||
|
||||
// responseDeliveryFuturesStream a channel thought which the data coming from the two websocket connection will go through.
|
||||
var responseDeliveryFuturesStream = make(chan stream.Response)
|
||||
|
||||
var fetchedFuturesCurrencyPairSnapshotOrderbook = make(map[string]bool)
|
||||
|
||||
// WsDeliveryFuturesConnect initiates a websocket connection for delivery futures account
|
||||
func (g *Gateio) WsDeliveryFuturesConnect() error {
|
||||
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
|
||||
return stream.ErrWebsocketNotEnabled
|
||||
}
|
||||
func (g *Gateio) WsDeliveryFuturesConnect(ctx context.Context, conn stream.Connection) error {
|
||||
err := g.CurrencyPairs.IsAssetEnabled(asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err = g.Websocket.SetWebsocketURL(deliveryRealUSDTTradingURL, false, true)
|
||||
err = conn.DialContext(ctx, &websocket.Dialer{}, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
URL: deliveryRealBTCTradingURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: g.Config.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: g.Config.WebsocketResponseMaxLimit,
|
||||
Authenticated: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.AuthConn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Wg.Add(3)
|
||||
go g.wsReadDeliveryFuturesData()
|
||||
go g.wsFunnelDeliveryFuturesConnectionData(g.Websocket.Conn)
|
||||
go g.wsFunnelDeliveryFuturesConnectionData(g.Websocket.AuthConn)
|
||||
if g.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "successful connection to %v\n",
|
||||
g.Websocket.GetWebsocketURL())
|
||||
}
|
||||
pingMessage, err := json.Marshal(WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Time: time.Now().Unix(),
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Time: time.Now().Unix(), // TODO: Func for dynamic time as this will be the same time for every ping message.
|
||||
Channel: futuresPingChannel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
Websocket: true,
|
||||
Delay: time.Second * 5,
|
||||
MessageType: websocket.PingMessage,
|
||||
@@ -101,47 +64,6 @@ func (g *Gateio) WsDeliveryFuturesConnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// wsReadDeliveryFuturesData read coming messages thought the websocket connection and pass the data to wsHandleFuturesData for further process.
|
||||
func (g *Gateio) wsReadDeliveryFuturesData() {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-g.Websocket.ShutdownC:
|
||||
select {
|
||||
case resp := <-responseDeliveryFuturesStream:
|
||||
err := g.wsHandleFuturesData(resp.Raw, asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
select {
|
||||
case g.Websocket.DataHandler <- err:
|
||||
default:
|
||||
log.Errorf(log.WebsocketMgr, "%s websocket handle data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
return
|
||||
case resp := <-responseDeliveryFuturesStream:
|
||||
err := g.wsHandleFuturesData(resp.Raw, asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsFunnelDeliveryFuturesConnectionData receives data from multiple connection and pass the data
|
||||
// to wsRead through a channel responseStream
|
||||
func (g *Gateio) wsFunnelDeliveryFuturesConnectionData(ws stream.Connection) {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
resp := ws.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
responseDeliveryFuturesStream <- stream.Response{Raw: resp.Raw}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateDeliveryFuturesDefaultSubscriptions returns delivery futures default subscriptions params.
|
||||
func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.List, error) {
|
||||
_, err := g.GetCredentials(context.Background())
|
||||
@@ -150,21 +72,21 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.Lis
|
||||
}
|
||||
channelsToSubscribe := defaultDeliveryFuturesSubscriptions
|
||||
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
channelsToSubscribe = append(
|
||||
channelsToSubscribe,
|
||||
futuresOrdersChannel,
|
||||
futuresUserTradesChannel,
|
||||
futuresBalancesChannel,
|
||||
)
|
||||
channelsToSubscribe = append(channelsToSubscribe, futuresOrdersChannel, futuresUserTradesChannel, futuresBalancesChannel)
|
||||
}
|
||||
pairs, err := g.GetAvailablePairs(asset.DeliveryFutures)
|
||||
|
||||
pairs, err := g.GetEnabledPairs(asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
if errors.Is(err, asset.ErrNotEnabled) {
|
||||
return nil, nil // no enabled pairs, subscriptions require an associated pair.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subscriptions subscription.List
|
||||
for i := range channelsToSubscribe {
|
||||
for j := range pairs {
|
||||
params := make(map[string]interface{})
|
||||
params := make(map[string]any)
|
||||
switch channelsToSubscribe[i] {
|
||||
case futuresOrderbookChannel:
|
||||
params["limit"] = 20
|
||||
@@ -172,13 +94,13 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.Lis
|
||||
case futuresCandlesticksChannel:
|
||||
params["interval"] = kline.FiveMin
|
||||
}
|
||||
fpair, err := g.FormatExchangeCurrency(pairs[j], asset.DeliveryFutures)
|
||||
fPair, err := g.FormatExchangeCurrency(pairs[j], asset.DeliveryFutures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptions = append(subscriptions, &subscription.Subscription{
|
||||
Channel: channelsToSubscribe[i],
|
||||
Pairs: currency.Pairs{fpair.Upper()},
|
||||
Pairs: currency.Pairs{fPair.Upper()},
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
@@ -187,68 +109,31 @@ func (g *Gateio) GenerateDeliveryFuturesDefaultSubscriptions() (subscription.Lis
|
||||
}
|
||||
|
||||
// DeliveryFuturesSubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) DeliveryFuturesSubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleDeliveryFuturesSubscription("subscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) DeliveryFuturesSubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, subscribeEvent, channelsToUnsubscribe, g.generateDeliveryFuturesPayload)
|
||||
}
|
||||
|
||||
// DeliveryFuturesUnsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) DeliveryFuturesUnsubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleDeliveryFuturesSubscription("unsubscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) DeliveryFuturesUnsubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, g.generateDeliveryFuturesPayload)
|
||||
}
|
||||
|
||||
// handleDeliveryFuturesSubscription sends a websocket message to receive data from the channel
|
||||
func (g *Gateio) handleDeliveryFuturesSubscription(event string, channelsToSubscribe subscription.List) error {
|
||||
payloads, err := g.generateDeliveryFuturesPayload(event, channelsToSubscribe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
var respByte []byte
|
||||
// con represents the websocket connection. 0 - for usdt settle and 1 - for btc settle connections.
|
||||
for con, val := range payloads {
|
||||
for k := range val {
|
||||
if con == 0 {
|
||||
respByte, err = g.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, val[k].ID, val[k])
|
||||
} else {
|
||||
respByte, err = g.Websocket.AuthConn.SendMessageReturnResponse(context.TODO(), request.Unset, val[k].ID, val[k])
|
||||
}
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
}
|
||||
var resp WsEventResponse
|
||||
if err = json.Unmarshal(respByte, &resp); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else {
|
||||
if resp.Error != nil && resp.Error.Code != 0 {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s error code: %d message: %s", val[k].Event, val[k].Channel, resp.Error.Code, resp.Error.Message))
|
||||
continue
|
||||
}
|
||||
if err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscribe subscription.List) ([2][]WsInput, error) {
|
||||
payloads := [2][]WsInput{}
|
||||
func (g *Gateio) generateDeliveryFuturesPayload(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
||||
if len(channelsToSubscribe) == 0 {
|
||||
return payloads, errors.New("cannot generate payload, no channels supplied")
|
||||
return nil, errors.New("cannot generate payload, no channels supplied")
|
||||
}
|
||||
var creds *account.Credentials
|
||||
var err error
|
||||
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
creds, err = g.GetCredentials(context.TODO())
|
||||
creds, err = g.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
outbound := make([]WsInput, 0, len(channelsToSubscribe))
|
||||
for i := range channelsToSubscribe {
|
||||
if len(channelsToSubscribe[i].Pairs) != 1 {
|
||||
return payloads, subscription.ErrNotSinglePair
|
||||
return nil, subscription.ErrNotSinglePair
|
||||
}
|
||||
var auth *WsAuthInput
|
||||
timestamp := time.Now()
|
||||
@@ -268,7 +153,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
|
||||
var sigTemp string
|
||||
sigTemp, err = g.generateWsSignature(creds.Secret, event, channelsToSubscribe[i].Channel, timestamp.Unix())
|
||||
if err != nil {
|
||||
return [2][]WsInput{}, err
|
||||
return nil, err
|
||||
}
|
||||
auth = &WsAuthInput{
|
||||
Method: "api_key",
|
||||
@@ -282,7 +167,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
|
||||
var frequencyString string
|
||||
frequencyString, err = g.GetIntervalString(frequency)
|
||||
if err != nil {
|
||||
return payloads, err
|
||||
return nil, err
|
||||
}
|
||||
params = append(params, frequencyString)
|
||||
}
|
||||
@@ -305,7 +190,7 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
|
||||
var intervalString string
|
||||
intervalString, err = g.GetIntervalString(interval)
|
||||
if err != nil {
|
||||
return payloads, err
|
||||
return nil, err
|
||||
}
|
||||
params = append([]string{intervalString}, params...)
|
||||
}
|
||||
@@ -315,25 +200,14 @@ func (g *Gateio) generateDeliveryFuturesPayload(event string, channelsToSubscrib
|
||||
params = append(params, intervalString)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(channelsToSubscribe[i].Pairs[0].Quote.Upper().String(), "USDT") {
|
||||
payloads[0] = append(payloads[0], WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
} else {
|
||||
payloads[1] = append(payloads[1], WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
}
|
||||
outbound = append(outbound, WsInput{
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
}
|
||||
return payloads, nil
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
@@ -24,7 +23,6 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
"github.com/thrasher-corp/gocryptotrader/log"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,61 +57,25 @@ var defaultFuturesSubscriptions = []string{
|
||||
futuresCandlesticksChannel,
|
||||
}
|
||||
|
||||
// responseFuturesStream a channel thought which the data coming from the two websocket connection will go through.
|
||||
var responseFuturesStream = make(chan stream.Response)
|
||||
|
||||
// WsFuturesConnect initiates a websocket connection for futures account
|
||||
func (g *Gateio) WsFuturesConnect() error {
|
||||
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
|
||||
return stream.ErrWebsocketNotEnabled
|
||||
}
|
||||
func (g *Gateio) WsFuturesConnect(ctx context.Context, conn stream.Connection) error {
|
||||
err := g.CurrencyPairs.IsAssetEnabled(asset.Futures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err = g.Websocket.SetWebsocketURL(futuresWebsocketUsdtURL, false, true)
|
||||
err = conn.DialContext(ctx, &websocket.Dialer{}, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.Websocket.SetupNewConnection(stream.ConnectionSetup{
|
||||
URL: futuresWebsocketBtcURL,
|
||||
RateLimit: request.NewWeightedRateLimitByDuration(gateioWebsocketRateLimit),
|
||||
ResponseCheckTimeout: g.Config.WebsocketResponseCheckTimeout,
|
||||
ResponseMaxLimit: g.Config.WebsocketResponseMaxLimit,
|
||||
Authenticated: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.AuthConn.Dial(&dialer, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Wg.Add(3)
|
||||
go g.wsReadFuturesData()
|
||||
go g.wsFunnelFuturesConnectionData(g.Websocket.Conn)
|
||||
go g.wsFunnelFuturesConnectionData(g.Websocket.AuthConn)
|
||||
if g.Verbose {
|
||||
log.Debugf(log.ExchangeSys, "Successful connection to %v\n",
|
||||
g.Websocket.GetWebsocketURL())
|
||||
}
|
||||
pingMessage, err := json.Marshal(WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Time: func() int64 {
|
||||
return time.Now().Unix()
|
||||
}(),
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Time: time.Now().Unix(), // TODO: Func for dynamic time as this will be the same time for every ping message.
|
||||
Channel: futuresPingChannel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
Websocket: true,
|
||||
MessageType: websocket.PingMessage,
|
||||
Delay: time.Second * 15,
|
||||
@@ -123,7 +85,7 @@ func (g *Gateio) WsFuturesConnect() error {
|
||||
}
|
||||
|
||||
// GenerateFuturesDefaultSubscriptions returns default subscriptions information.
|
||||
func (g *Gateio) GenerateFuturesDefaultSubscriptions() (subscription.List, error) {
|
||||
func (g *Gateio) GenerateFuturesDefaultSubscriptions(settlement currency.Code) (subscription.List, error) {
|
||||
channelsToSubscribe := defaultFuturesSubscriptions
|
||||
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
channelsToSubscribe = append(channelsToSubscribe,
|
||||
@@ -132,15 +94,39 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() (subscription.List, error
|
||||
futuresBalancesChannel,
|
||||
)
|
||||
}
|
||||
|
||||
pairs, err := g.GetEnabledPairs(asset.Futures)
|
||||
if err != nil {
|
||||
if errors.Is(err, asset.ErrNotEnabled) {
|
||||
return nil, nil // no enabled pairs, subscriptions require an associated pair.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
subscriptions := make(subscription.List, len(channelsToSubscribe)*len(pairs))
|
||||
count := 0
|
||||
|
||||
var subscriptions subscription.List
|
||||
for i := range channelsToSubscribe {
|
||||
switch {
|
||||
case settlement.Equal(currency.USDT):
|
||||
pairs, err = pairs.GetPairsByQuote(currency.USDT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case settlement.Equal(currency.BTC):
|
||||
offset := 0
|
||||
for x := range pairs {
|
||||
if pairs[x].Quote.Equal(currency.USDT) {
|
||||
continue // skip USDT pairs
|
||||
}
|
||||
pairs[offset] = pairs[x]
|
||||
offset++
|
||||
}
|
||||
pairs = pairs[:offset]
|
||||
default:
|
||||
return nil, fmt.Errorf("settlement currency %s not supported", settlement)
|
||||
}
|
||||
|
||||
for j := range pairs {
|
||||
params := make(map[string]interface{})
|
||||
params := make(map[string]any)
|
||||
switch channelsToSubscribe[i] {
|
||||
case futuresOrderbookChannel:
|
||||
params["limit"] = 100
|
||||
@@ -151,80 +137,39 @@ func (g *Gateio) GenerateFuturesDefaultSubscriptions() (subscription.List, error
|
||||
params["frequency"] = kline.ThousandMilliseconds
|
||||
params["level"] = "100"
|
||||
}
|
||||
fpair, err := g.FormatExchangeCurrency(pairs[j], asset.Futures)
|
||||
fPair, err := g.FormatExchangeCurrency(pairs[j], asset.Futures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptions[count] = &subscription.Subscription{
|
||||
subscriptions = append(subscriptions, &subscription.Subscription{
|
||||
Channel: channelsToSubscribe[i],
|
||||
Pairs: currency.Pairs{fpair.Upper()},
|
||||
Pairs: currency.Pairs{fPair.Upper()},
|
||||
Params: params,
|
||||
}
|
||||
count++
|
||||
})
|
||||
}
|
||||
}
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// FuturesSubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) FuturesSubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleFuturesSubscription("subscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) FuturesSubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, subscribeEvent, channelsToUnsubscribe, g.generateFuturesPayload)
|
||||
}
|
||||
|
||||
// FuturesUnsubscribe sends a websocket message to stop receiving data from the channel
|
||||
func (g *Gateio) FuturesUnsubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleFuturesSubscription("unsubscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) FuturesUnsubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, g.generateFuturesPayload)
|
||||
}
|
||||
|
||||
// wsReadFuturesData read coming messages thought the websocket connection and pass the data to wsHandleData for further process.
|
||||
func (g *Gateio) wsReadFuturesData() {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-g.Websocket.ShutdownC:
|
||||
select {
|
||||
case resp := <-responseFuturesStream:
|
||||
err := g.wsHandleFuturesData(resp.Raw, asset.Futures)
|
||||
if err != nil {
|
||||
select {
|
||||
case g.Websocket.DataHandler <- err:
|
||||
default:
|
||||
log.Errorf(log.WebsocketMgr, "%s websocket handle data error: %v", g.Name, err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
return
|
||||
case resp := <-responseFuturesStream:
|
||||
err := g.wsHandleFuturesData(resp.Raw, asset.Futures)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsFunnelFuturesConnectionData receives data from multiple connection and pass the data
|
||||
// to wsRead through a channel responseStream
|
||||
func (g *Gateio) wsFunnelFuturesConnectionData(ws stream.Connection) {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
resp := ws.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
responseFuturesStream <- stream.Response{Raw: resp.Raw}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error {
|
||||
// WsHandleFuturesData handles futures websocket data
|
||||
func (g *Gateio) WsHandleFuturesData(_ context.Context, respRaw []byte, a asset.Item) error {
|
||||
var push WsResponse
|
||||
err := json.Unmarshal(respRaw, &push)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if push.Event == "subscribe" || push.Event == "unsubscribe" {
|
||||
if push.Event == subscribeEvent || push.Event == unsubscribeEvent {
|
||||
if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) {
|
||||
return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID)
|
||||
}
|
||||
@@ -233,27 +178,27 @@ func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error
|
||||
|
||||
switch push.Channel {
|
||||
case futuresTickersChannel:
|
||||
return g.processFuturesTickers(respRaw, assetType)
|
||||
return g.processFuturesTickers(respRaw, a)
|
||||
case futuresTradesChannel:
|
||||
return g.processFuturesTrades(respRaw, assetType)
|
||||
return g.processFuturesTrades(respRaw, a)
|
||||
case futuresOrderbookChannel:
|
||||
return g.processFuturesOrderbookSnapshot(push.Event, push.Result, assetType, push.Time.Time())
|
||||
return g.processFuturesOrderbookSnapshot(push.Event, push.Result, a, push.Time.Time())
|
||||
case futuresOrderbookTickerChannel:
|
||||
return g.processFuturesOrderbookTicker(push.Result)
|
||||
case futuresOrderbookUpdateChannel:
|
||||
return g.processFuturesAndOptionsOrderbookUpdate(push.Result, assetType)
|
||||
return g.processFuturesAndOptionsOrderbookUpdate(push.Result, a)
|
||||
case futuresCandlesticksChannel:
|
||||
return g.processFuturesCandlesticks(respRaw, assetType)
|
||||
return g.processFuturesCandlesticks(respRaw, a)
|
||||
case futuresOrdersChannel:
|
||||
var processed []order.Detail
|
||||
processed, err = g.processFuturesOrdersPushData(respRaw, assetType)
|
||||
processed, err = g.processFuturesOrdersPushData(respRaw, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.DataHandler <- processed
|
||||
return nil
|
||||
case futuresUserTradesChannel:
|
||||
return g.procesFuturesUserTrades(respRaw, assetType)
|
||||
return g.procesFuturesUserTrades(respRaw, a)
|
||||
case futuresLiquidatesChannel:
|
||||
return g.processFuturesLiquidatesNotification(respRaw)
|
||||
case futuresAutoDeleveragesChannel:
|
||||
@@ -261,7 +206,7 @@ func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error
|
||||
case futuresAutoPositionCloseChannel:
|
||||
return g.processPositionCloseData(respRaw)
|
||||
case futuresBalancesChannel:
|
||||
return g.processBalancePushData(respRaw, assetType)
|
||||
return g.processBalancePushData(respRaw, a)
|
||||
case futuresReduceRiskLimitsChannel:
|
||||
return g.processFuturesReduceRiskLimitNotification(respRaw)
|
||||
case futuresPositionsChannel:
|
||||
@@ -276,62 +221,23 @@ func (g *Gateio) wsHandleFuturesData(respRaw []byte, assetType asset.Item) error
|
||||
}
|
||||
}
|
||||
|
||||
// handleFuturesSubscription sends a websocket message to receive data from the channel
|
||||
func (g *Gateio) handleFuturesSubscription(event string, channelsToSubscribe subscription.List) error {
|
||||
payloads, err := g.generateFuturesPayload(event, channelsToSubscribe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
var respByte []byte
|
||||
// con represents the websocket connection. 0 - for usdt settle and 1 - for btc settle connections.
|
||||
for con, val := range payloads {
|
||||
for k := range val {
|
||||
if con == 0 {
|
||||
respByte, err = g.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, val[k].ID, val[k])
|
||||
} else {
|
||||
respByte, err = g.Websocket.AuthConn.SendMessageReturnResponse(context.TODO(), request.Unset, val[k].ID, val[k])
|
||||
}
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
}
|
||||
var resp WsEventResponse
|
||||
if err = json.Unmarshal(respByte, &resp); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else {
|
||||
if resp.Error != nil && resp.Error.Code != 0 {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s error code: %d message: %s", val[k].Event, val[k].Channel, resp.Error.Code, resp.Error.Message))
|
||||
continue
|
||||
}
|
||||
if err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k]); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscription.List) ([2][]WsInput, error) {
|
||||
payloads := [2][]WsInput{}
|
||||
func (g *Gateio) generateFuturesPayload(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
||||
if len(channelsToSubscribe) == 0 {
|
||||
return payloads, errors.New("cannot generate payload, no channels supplied")
|
||||
return nil, errors.New("cannot generate payload, no channels supplied")
|
||||
}
|
||||
var creds *account.Credentials
|
||||
var err error
|
||||
if g.Websocket.CanUseAuthenticatedEndpoints() {
|
||||
creds, err = g.GetCredentials(context.TODO())
|
||||
creds, err = g.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
g.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
||||
}
|
||||
}
|
||||
|
||||
outbound := make([]WsInput, 0, len(channelsToSubscribe))
|
||||
for i := range channelsToSubscribe {
|
||||
if len(channelsToSubscribe[i].Pairs) != 1 {
|
||||
return payloads, subscription.ErrNotSinglePair
|
||||
return nil, subscription.ErrNotSinglePair
|
||||
}
|
||||
var auth *WsAuthInput
|
||||
timestamp := time.Now()
|
||||
@@ -353,7 +259,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscr
|
||||
var sigTemp string
|
||||
sigTemp, err = g.generateWsSignature(creds.Secret, event, channelsToSubscribe[i].Channel, timestamp.Unix())
|
||||
if err != nil {
|
||||
return [2][]WsInput{}, err
|
||||
return nil, err
|
||||
}
|
||||
auth = &WsAuthInput{
|
||||
Method: "api_key",
|
||||
@@ -367,7 +273,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscr
|
||||
var frequencyString string
|
||||
frequencyString, err = g.GetIntervalString(frequency)
|
||||
if err != nil {
|
||||
return payloads, err
|
||||
return nil, err
|
||||
}
|
||||
params = append(params, frequencyString)
|
||||
}
|
||||
@@ -390,7 +296,7 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscr
|
||||
var intervalString string
|
||||
intervalString, err = g.GetIntervalString(interval)
|
||||
if err != nil {
|
||||
return payloads, err
|
||||
return nil, err
|
||||
}
|
||||
params = append([]string{intervalString}, params...)
|
||||
}
|
||||
@@ -400,27 +306,16 @@ func (g *Gateio) generateFuturesPayload(event string, channelsToSubscribe subscr
|
||||
params = append(params, intervalString)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(channelsToSubscribe[i].Pairs[0].Quote.Upper().String(), "USDT") {
|
||||
payloads[0] = append(payloads[0], WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
} else {
|
||||
payloads[1] = append(payloads[1], WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
}
|
||||
outbound = append(outbound, WsInput{
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
Auth: auth,
|
||||
Time: timestamp.Unix(),
|
||||
})
|
||||
}
|
||||
return payloads, nil
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (g *Gateio) processFuturesTickers(data []byte, assetType asset.Item) error {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
||||
@@ -69,34 +68,24 @@ var defaultOptionsSubscriptions = []string{
|
||||
var fetchedOptionsCurrencyPairSnapshotOrderbook = make(map[string]bool)
|
||||
|
||||
// WsOptionsConnect initiates a websocket connection to options websocket endpoints.
|
||||
func (g *Gateio) WsOptionsConnect() error {
|
||||
if !g.Websocket.IsEnabled() || !g.IsEnabled() {
|
||||
return stream.ErrWebsocketNotEnabled
|
||||
}
|
||||
func (g *Gateio) WsOptionsConnect(ctx context.Context, conn stream.Connection) error {
|
||||
err := g.CurrencyPairs.IsAssetEnabled(asset.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dialer websocket.Dialer
|
||||
err = g.Websocket.SetWebsocketURL(optionsWebsocketURL, false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = g.Websocket.Conn.Dial(&dialer, http.Header{})
|
||||
err = conn.DialContext(ctx, &websocket.Dialer{}, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pingMessage, err := json.Marshal(WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
Time: time.Now().Unix(),
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Time: time.Now().Unix(), // TODO: Func for dynamic time as this will be the same time for every ping message.
|
||||
Channel: optionsPingChannel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Websocket.Wg.Add(1)
|
||||
go g.wsReadOptionsConnData()
|
||||
g.Websocket.Conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
conn.SetupPingHandler(request.Unset, stream.PingHandler{
|
||||
Websocket: true,
|
||||
Delay: time.Second * 5,
|
||||
MessageType: websocket.PingMessage,
|
||||
@@ -130,15 +119,21 @@ func (g *Gateio) GenerateOptionsDefaultSubscriptions() (subscription.List, error
|
||||
log.Errorf(log.ExchangeSys, "no subaccount found for authenticated options channel subscriptions")
|
||||
}
|
||||
}
|
||||
|
||||
getEnabledPairs:
|
||||
var subscriptions subscription.List
|
||||
|
||||
pairs, err := g.GetEnabledPairs(asset.Options)
|
||||
if err != nil {
|
||||
if errors.Is(err, asset.ErrNotEnabled) {
|
||||
return nil, nil // no enabled pairs, subscriptions require an associated pair.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subscriptions subscription.List
|
||||
for i := range channelsToSubscribe {
|
||||
for j := range pairs {
|
||||
params := make(map[string]interface{})
|
||||
params := make(map[string]any)
|
||||
switch channelsToSubscribe[i] {
|
||||
case optionsOrderbookChannel:
|
||||
params["accuracy"] = "0"
|
||||
@@ -160,13 +155,13 @@ getEnabledPairs:
|
||||
}
|
||||
params["user_id"] = userID
|
||||
}
|
||||
fpair, err := g.FormatExchangeCurrency(pairs[j], asset.Options)
|
||||
fPair, err := g.FormatExchangeCurrency(pairs[j], asset.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptions = append(subscriptions, &subscription.Subscription{
|
||||
Channel: channelsToSubscribe[i],
|
||||
Pairs: currency.Pairs{fpair.Upper()},
|
||||
Pairs: currency.Pairs{fPair.Upper()},
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
@@ -174,7 +169,7 @@ getEnabledPairs:
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
||||
func (g *Gateio) generateOptionsPayload(ctx context.Context, conn stream.Connection, event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
||||
if len(channelsToSubscribe) == 0 {
|
||||
return nil, errors.New("cannot generate payload, no channels supplied")
|
||||
}
|
||||
@@ -233,7 +228,7 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe subscr
|
||||
}
|
||||
params = append([]string{strconv.FormatInt(userID, 10)}, params...)
|
||||
var creds *account.Credentials
|
||||
creds, err = g.GetCredentials(context.Background())
|
||||
creds, err = g.GetCredentials(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -276,7 +271,7 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe subscr
|
||||
params...)
|
||||
}
|
||||
payloads[i] = WsInput{
|
||||
ID: g.Websocket.Conn.GenerateMessageID(false),
|
||||
ID: conn.GenerateMessageID(false),
|
||||
Event: event,
|
||||
Channel: channelsToSubscribe[i].Channel,
|
||||
Payload: params,
|
||||
@@ -287,73 +282,25 @@ func (g *Gateio) generateOptionsPayload(event string, channelsToSubscribe subscr
|
||||
return payloads, nil
|
||||
}
|
||||
|
||||
// wsReadOptionsConnData receives and passes on websocket messages for processing
|
||||
func (g *Gateio) wsReadOptionsConnData() {
|
||||
defer g.Websocket.Wg.Done()
|
||||
for {
|
||||
resp := g.Websocket.Conn.ReadMessage()
|
||||
if resp.Raw == nil {
|
||||
return
|
||||
}
|
||||
err := g.wsHandleOptionsData(resp.Raw)
|
||||
if err != nil {
|
||||
g.Websocket.DataHandler <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OptionsSubscribe sends a websocket message to stop receiving data for asset type options
|
||||
func (g *Gateio) OptionsSubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleOptionsSubscription("subscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) OptionsSubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, subscribeEvent, channelsToUnsubscribe, g.generateOptionsPayload)
|
||||
}
|
||||
|
||||
// OptionsUnsubscribe sends a websocket message to stop receiving data for asset type options
|
||||
func (g *Gateio) OptionsUnsubscribe(channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleOptionsSubscription("unsubscribe", channelsToUnsubscribe)
|
||||
func (g *Gateio) OptionsUnsubscribe(ctx context.Context, conn stream.Connection, channelsToUnsubscribe subscription.List) error {
|
||||
return g.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, g.generateOptionsPayload)
|
||||
}
|
||||
|
||||
// handleOptionsSubscription sends a websocket message to receive data from the channel
|
||||
func (g *Gateio) handleOptionsSubscription(event string, channelsToSubscribe subscription.List) error {
|
||||
payloads, err := g.generateOptionsPayload(event, channelsToSubscribe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs error
|
||||
for k := range payloads {
|
||||
result, err := g.Websocket.Conn.SendMessageReturnResponse(context.TODO(), request.Unset, payloads[k].ID, payloads[k])
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
continue
|
||||
}
|
||||
var resp WsEventResponse
|
||||
if err = json.Unmarshal(result, &resp); err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
} else {
|
||||
if resp.Error != nil && resp.Error.Code != 0 {
|
||||
errs = common.AppendError(errs, fmt.Errorf("error while %s to channel %s asset type: options error code: %d message: %s", payloads[k].Event, payloads[k].Channel, resp.Error.Code, resp.Error.Message))
|
||||
continue
|
||||
}
|
||||
if payloads[k].Event == "subscribe" {
|
||||
err = g.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe[k])
|
||||
} else {
|
||||
err = g.Websocket.RemoveSubscriptions(channelsToSubscribe[k])
|
||||
}
|
||||
if err != nil {
|
||||
errs = common.AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (g *Gateio) wsHandleOptionsData(respRaw []byte) error {
|
||||
// WsHandleOptionsData handles options websocket data
|
||||
func (g *Gateio) WsHandleOptionsData(_ context.Context, respRaw []byte) error {
|
||||
var push WsResponse
|
||||
err := json.Unmarshal(respRaw, &push)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if push.Event == "subscribe" || push.Event == "unsubscribe" {
|
||||
if push.Event == subscribeEvent || push.Event == unsubscribeEvent {
|
||||
if !g.Websocket.Match.IncomingWithData(push.ID, respRaw) {
|
||||
return fmt.Errorf("couldn't match subscription message with ID: %d", push.ID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user