mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 07:26:48 +00:00
* Exchanges: Remove example BespokeGenerateMessageID * Okx: Replace conn.RequestIDGenerator with MesssageID Continued overall direction to remove the closed-loop of e => conn => e roundtrip for message ids * Exchanges: Add MessageSequence This method removes the either/or nature of message id generation. We don't tie the message ids to connections, or to anything. Consumers just call whichever they want, or even combine them as they want. Anything more complicated will need a separate installation anyway * GateIO: Split usage of MessageID and MessageSequence * Binance: Switch to UUID message IDs * Kraken: Switch to e.MessageSequence * Kucoin: Switch to MessageID * HitBTC: Switch to UUIDv7 for ws message ID * Bybit: Switch to UUIDv7 for ws message ID * Bitfinex: Switch to UUIDv7 and MessageSequence Tested CID - It accepts 53 bits only for an int, so MessageSequence makes sense. Can't use MessageID * Websocket: Remove now unused MessageID function Moved all MessageID usage into funcs and onto base methods, to remove the closed loop of message IDs * Docs: Update guidance for message signatures
711 lines
22 KiB
Go
711 lines
22 KiB
Go
package gateio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
gws "github.com/gorilla/websocket"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/encoding/json"
|
|
"github.com/thrasher-corp/gocryptotrader/exchange/websocket"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/fill"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
|
|
"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/types"
|
|
)
|
|
|
|
const (
|
|
btcFuturesWebsocketURL = "wss://fx-ws.gateio.ws/v4/ws/btc"
|
|
usdtFuturesWebsocketURL = "wss://fx-ws.gateio.ws/v4/ws/usdt"
|
|
|
|
futuresPingChannel = "futures.ping"
|
|
futuresTickersChannel = "futures.tickers"
|
|
futuresTradesChannel = "futures.trades"
|
|
futuresOrderbookChannel = "futures.order_book"
|
|
futuresOrderbookTickerChannel = "futures.book_ticker"
|
|
futuresOrderbookUpdateChannel = "futures.order_book_update"
|
|
futuresCandlesticksChannel = "futures.candlesticks"
|
|
futuresOrdersChannel = "futures.orders"
|
|
|
|
// authenticated channels
|
|
futuresUserTradesChannel = "futures.usertrades"
|
|
futuresLiquidatesChannel = "futures.liquidates"
|
|
futuresAutoDeleveragesChannel = "futures.auto_deleverages"
|
|
futuresAutoPositionCloseChannel = "futures.position_closes"
|
|
futuresBalancesChannel = "futures.balances"
|
|
futuresReduceRiskLimitsChannel = "futures.reduce_risk_limits"
|
|
futuresPositionsChannel = "futures.positions"
|
|
futuresAutoOrdersChannel = "futures.autoorders"
|
|
|
|
futuresOrderbookUpdateLimit uint64 = 20
|
|
)
|
|
|
|
var defaultFuturesSubscriptions = []string{
|
|
futuresTickersChannel,
|
|
futuresTradesChannel,
|
|
futuresOrderbookUpdateChannel,
|
|
futuresCandlesticksChannel,
|
|
}
|
|
|
|
// WsFuturesConnect initiates a websocket connection for futures account
|
|
func (e *Exchange) WsFuturesConnect(ctx context.Context, conn websocket.Connection) error {
|
|
a := asset.USDTMarginedFutures
|
|
if conn.GetURL() == btcFuturesWebsocketURL {
|
|
a = asset.CoinMarginedFutures
|
|
}
|
|
if err := e.CurrencyPairs.IsAssetEnabled(a); err != nil {
|
|
return err
|
|
}
|
|
if err := conn.Dial(ctx, &gws.Dialer{}, http.Header{}); err != nil {
|
|
return err
|
|
}
|
|
pingHandler, err := getWSPingHandler(futuresPingChannel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn.SetupPingHandler(websocketRateLimitNotNeededEPL, pingHandler)
|
|
return nil
|
|
}
|
|
|
|
// GenerateFuturesDefaultSubscriptions returns default subscriptions information.
|
|
// TODO: Update to use the new subscription template system
|
|
func (e *Exchange) GenerateFuturesDefaultSubscriptions(a asset.Item) (subscription.List, error) {
|
|
channelsToSubscribe := defaultFuturesSubscriptions
|
|
if e.Websocket.CanUseAuthenticatedEndpoints() {
|
|
channelsToSubscribe = append(channelsToSubscribe, futuresOrdersChannel, futuresUserTradesChannel, futuresBalancesChannel)
|
|
}
|
|
|
|
pairs, err := e.GetEnabledPairs(a)
|
|
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]any)
|
|
switch channelsToSubscribe[i] {
|
|
case futuresOrderbookChannel:
|
|
params["limit"] = 100
|
|
params["interval"] = "0"
|
|
case futuresCandlesticksChannel:
|
|
params["interval"] = kline.FiveMin
|
|
case futuresOrderbookUpdateChannel:
|
|
// This is the fastest frequency available for futures orderbook updates 20 levels every 20ms
|
|
params["frequency"] = kline.TwentyMilliseconds
|
|
params["level"] = strconv.FormatUint(futuresOrderbookUpdateLimit, 10)
|
|
}
|
|
fPair, err := e.FormatExchangeCurrency(pairs[j], a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subscriptions = append(subscriptions, &subscription.Subscription{
|
|
Channel: channelsToSubscribe[i],
|
|
Pairs: currency.Pairs{fPair.Upper()},
|
|
Params: params,
|
|
Asset: a,
|
|
})
|
|
}
|
|
}
|
|
return subscriptions, nil
|
|
}
|
|
|
|
// FuturesSubscribe sends a websocket message to stop receiving data from the channel
|
|
func (e *Exchange) FuturesSubscribe(ctx context.Context, conn websocket.Connection, channelsToUnsubscribe subscription.List) error {
|
|
return e.handleSubscription(ctx, conn, subscribeEvent, channelsToUnsubscribe, e.generateFuturesPayload)
|
|
}
|
|
|
|
// FuturesUnsubscribe sends a websocket message to stop receiving data from the channel
|
|
func (e *Exchange) FuturesUnsubscribe(ctx context.Context, conn websocket.Connection, channelsToUnsubscribe subscription.List) error {
|
|
return e.handleSubscription(ctx, conn, unsubscribeEvent, channelsToUnsubscribe, e.generateFuturesPayload)
|
|
}
|
|
|
|
// WsHandleFuturesData handles futures websocket data
|
|
func (e *Exchange) WsHandleFuturesData(ctx context.Context, conn websocket.Connection, respRaw []byte, a asset.Item) error {
|
|
push, err := parseWSHeader(respRaw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if push.RequestID != "" {
|
|
return conn.RequireMatchWithData(push.RequestID, respRaw)
|
|
}
|
|
|
|
if push.Event == subscribeEvent || push.Event == unsubscribeEvent {
|
|
return conn.RequireMatchWithData(push.ID, respRaw)
|
|
}
|
|
|
|
switch push.Channel {
|
|
case futuresTickersChannel:
|
|
return e.processFuturesTickers(respRaw, a)
|
|
case futuresTradesChannel:
|
|
return e.processFuturesTrades(respRaw, a)
|
|
case futuresOrderbookChannel:
|
|
return e.processFuturesOrderbookSnapshot(push.Event, push.Result, a, push.Time)
|
|
case futuresOrderbookTickerChannel:
|
|
return e.processFuturesOrderbookTicker(push.Result)
|
|
case futuresOrderbookUpdateChannel:
|
|
return e.processFuturesOrderbookUpdate(ctx, push.Result, a, push.Time)
|
|
case futuresCandlesticksChannel:
|
|
return e.processFuturesCandlesticks(respRaw, a)
|
|
case futuresOrdersChannel:
|
|
processed, err := e.processFuturesOrdersPushData(respRaw, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- processed
|
|
return nil
|
|
case futuresUserTradesChannel:
|
|
return e.procesFuturesUserTrades(respRaw, a)
|
|
case futuresLiquidatesChannel:
|
|
return e.processFuturesLiquidatesNotification(respRaw)
|
|
case futuresAutoDeleveragesChannel:
|
|
return e.processFuturesAutoDeleveragesNotification(respRaw)
|
|
case futuresAutoPositionCloseChannel:
|
|
return e.processPositionCloseData(respRaw)
|
|
case futuresBalancesChannel:
|
|
return e.processBalancePushData(ctx, push.Result, a)
|
|
case futuresReduceRiskLimitsChannel:
|
|
return e.processFuturesReduceRiskLimitNotification(respRaw)
|
|
case futuresPositionsChannel:
|
|
return e.processFuturesPositionsNotification(respRaw)
|
|
case futuresAutoOrdersChannel:
|
|
return e.processFuturesAutoOrderPushData(respRaw)
|
|
case "futures.pong":
|
|
return nil
|
|
default:
|
|
e.Websocket.DataHandler <- websocket.UnhandledMessageWarning{
|
|
Message: e.Name + websocket.UnhandledMessage + string(respRaw),
|
|
}
|
|
return errors.New(websocket.UnhandledMessage)
|
|
}
|
|
}
|
|
|
|
func (e *Exchange) generateFuturesPayload(ctx context.Context, event string, channelsToSubscribe subscription.List) ([]WsInput, error) {
|
|
if len(channelsToSubscribe) == 0 {
|
|
return nil, errors.New("cannot generate payload, no channels supplied")
|
|
}
|
|
var creds *account.Credentials
|
|
var err error
|
|
if e.Websocket.CanUseAuthenticatedEndpoints() {
|
|
creds, err = e.GetCredentials(ctx)
|
|
if err != nil {
|
|
e.Websocket.SetCanUseAuthenticatedEndpoints(false)
|
|
}
|
|
}
|
|
|
|
outbound := make([]WsInput, 0, len(channelsToSubscribe))
|
|
for i := range channelsToSubscribe {
|
|
if len(channelsToSubscribe[i].Pairs) != 1 {
|
|
return nil, subscription.ErrNotSinglePair
|
|
}
|
|
var auth *WsAuthInput
|
|
timestamp := time.Now()
|
|
var params []string
|
|
params = []string{channelsToSubscribe[i].Pairs[0].String()}
|
|
if e.Websocket.CanUseAuthenticatedEndpoints() {
|
|
switch channelsToSubscribe[i].Channel {
|
|
case futuresOrdersChannel, futuresUserTradesChannel,
|
|
futuresLiquidatesChannel, futuresAutoDeleveragesChannel,
|
|
futuresAutoPositionCloseChannel, futuresBalancesChannel,
|
|
futuresReduceRiskLimitsChannel, futuresPositionsChannel,
|
|
futuresAutoOrdersChannel:
|
|
value, ok := channelsToSubscribe[i].Params["user"].(string)
|
|
if ok {
|
|
params = append(
|
|
[]string{value},
|
|
params...)
|
|
}
|
|
var sigTemp string
|
|
sigTemp, err = e.generateWsSignature(creds.Secret, event, channelsToSubscribe[i].Channel, timestamp.Unix())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth = &WsAuthInput{
|
|
Method: "api_key",
|
|
Key: creds.Key,
|
|
Sign: sigTemp,
|
|
}
|
|
}
|
|
}
|
|
frequency, okay := channelsToSubscribe[i].Params["frequency"].(kline.Interval)
|
|
if okay {
|
|
var frequencyString string
|
|
frequencyString, err = getIntervalString(frequency)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params = append(params, frequencyString)
|
|
}
|
|
levelString, okay := channelsToSubscribe[i].Params["level"].(string)
|
|
if okay {
|
|
params = append(params, levelString)
|
|
}
|
|
limit, okay := channelsToSubscribe[i].Params["limit"].(int)
|
|
if okay {
|
|
params = append(params, strconv.Itoa(limit))
|
|
}
|
|
accuracy, okay := channelsToSubscribe[i].Params["accuracy"].(string)
|
|
if okay {
|
|
params = append(params, accuracy)
|
|
}
|
|
switch channelsToSubscribe[i].Channel {
|
|
case futuresCandlesticksChannel:
|
|
interval, okay := channelsToSubscribe[i].Params["interval"].(kline.Interval)
|
|
if okay {
|
|
var intervalString string
|
|
intervalString, err = getIntervalString(interval)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params = append([]string{intervalString}, params...)
|
|
}
|
|
case futuresOrderbookChannel:
|
|
intervalString, okay := channelsToSubscribe[i].Params["interval"].(string)
|
|
if okay {
|
|
params = append(params, intervalString)
|
|
}
|
|
}
|
|
outbound = append(outbound, WsInput{
|
|
ID: e.MessageSequence(),
|
|
Event: event,
|
|
Channel: channelsToSubscribe[i].Channel,
|
|
Payload: params,
|
|
Auth: auth,
|
|
Time: timestamp.Unix(),
|
|
})
|
|
}
|
|
return outbound, nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesTickers(data []byte, assetType asset.Item) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFutureTicker `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tickerPriceDatas := make([]ticker.Price, len(resp.Result))
|
|
for x := range resp.Result {
|
|
tickerPriceDatas[x] = ticker.Price{
|
|
ExchangeName: e.Name,
|
|
Volume: resp.Result[x].Volume24HBase.Float64(),
|
|
QuoteVolume: resp.Result[x].Volume24HQuote.Float64(),
|
|
High: resp.Result[x].High24H.Float64(),
|
|
Low: resp.Result[x].Low24H.Float64(),
|
|
Last: resp.Result[x].Last.Float64(),
|
|
AssetType: assetType,
|
|
Pair: resp.Result[x].Contract,
|
|
LastUpdated: resp.Time.Time(),
|
|
}
|
|
}
|
|
e.Websocket.DataHandler <- tickerPriceDatas
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesTrades(data []byte, assetType asset.Item) error {
|
|
saveTradeData := e.IsSaveTradeDataEnabled()
|
|
if !saveTradeData && !e.IsTradeFeedEnabled() {
|
|
return nil
|
|
}
|
|
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesTrades `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
trades := make([]trade.Data, len(resp.Result))
|
|
for x := range resp.Result {
|
|
trades[x] = trade.Data{
|
|
Timestamp: resp.Result[x].CreateTime.Time(),
|
|
CurrencyPair: resp.Result[x].Contract,
|
|
AssetType: assetType,
|
|
Exchange: e.Name,
|
|
Price: resp.Result[x].Price.Float64(),
|
|
Amount: resp.Result[x].Size,
|
|
TID: strconv.FormatInt(resp.Result[x].ID, 10),
|
|
}
|
|
}
|
|
return e.Websocket.Trade.Update(saveTradeData, trades...)
|
|
}
|
|
|
|
func (e *Exchange) processFuturesCandlesticks(data []byte, assetType asset.Item) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []FuturesCandlestick `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
klineDatas := make([]websocket.KlineData, len(resp.Result))
|
|
for x := range resp.Result {
|
|
icp := strings.Split(resp.Result[x].Name, currency.UnderscoreDelimiter)
|
|
if len(icp) < 3 {
|
|
return errors.New("malformed futures candlestick websocket push data")
|
|
}
|
|
currencyPair, err := currency.NewPairFromString(strings.Join(icp[1:], currency.UnderscoreDelimiter))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
klineDatas[x] = websocket.KlineData{
|
|
Pair: currencyPair,
|
|
AssetType: assetType,
|
|
Exchange: e.Name,
|
|
StartTime: resp.Result[x].Timestamp.Time(),
|
|
Interval: icp[0],
|
|
OpenPrice: resp.Result[x].OpenPrice.Float64(),
|
|
ClosePrice: resp.Result[x].ClosePrice.Float64(),
|
|
HighPrice: resp.Result[x].HighestPrice.Float64(),
|
|
LowPrice: resp.Result[x].LowestPrice.Float64(),
|
|
Volume: resp.Result[x].Volume,
|
|
}
|
|
}
|
|
e.Websocket.DataHandler <- klineDatas
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesOrderbookTicker(incoming []byte) error {
|
|
var data WsFuturesOrderbookTicker
|
|
err := json.Unmarshal(incoming, &data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- data
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesOrderbookUpdate(ctx context.Context, incoming []byte, a asset.Item, pushTime time.Time) error {
|
|
var data WsFuturesAndOptionsOrderbookUpdate
|
|
if err := json.Unmarshal(incoming, &data); err != nil {
|
|
return err
|
|
}
|
|
asks := make([]orderbook.Level, len(data.Asks))
|
|
for x := range data.Asks {
|
|
asks[x].Price = data.Asks[x].Price.Float64()
|
|
asks[x].Amount = data.Asks[x].Size
|
|
}
|
|
bids := make([]orderbook.Level, len(data.Bids))
|
|
for x := range data.Bids {
|
|
bids[x].Price = data.Bids[x].Price.Float64()
|
|
bids[x].Amount = data.Bids[x].Size
|
|
}
|
|
|
|
return e.wsOBUpdateMgr.ProcessOrderbookUpdate(ctx, e, data.FirstUpdatedID, &orderbook.Update{
|
|
UpdateID: data.LastUpdatedID,
|
|
UpdateTime: data.Timestamp.Time(),
|
|
LastPushed: pushTime,
|
|
Pair: data.ContractName,
|
|
Asset: a,
|
|
Asks: asks,
|
|
Bids: bids,
|
|
AllowEmpty: true,
|
|
})
|
|
}
|
|
|
|
func (e *Exchange) processFuturesOrderbookSnapshot(event string, incoming []byte, assetType asset.Item, lastPushed time.Time) error {
|
|
if event == "all" {
|
|
var data WsFuturesOrderbookSnapshot
|
|
err := json.Unmarshal(incoming, &data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
base := orderbook.Book{
|
|
Asset: assetType,
|
|
Exchange: e.Name,
|
|
Pair: data.Contract,
|
|
LastUpdated: data.Timestamp.Time(),
|
|
LastPushed: lastPushed,
|
|
ValidateOrderbook: e.ValidateOrderbook,
|
|
}
|
|
base.Asks = make([]orderbook.Level, len(data.Asks))
|
|
for x := range data.Asks {
|
|
base.Asks[x].Amount = data.Asks[x].Size
|
|
base.Asks[x].Price = data.Asks[x].Price.Float64()
|
|
}
|
|
base.Bids = make([]orderbook.Level, len(data.Bids))
|
|
for x := range data.Bids {
|
|
base.Bids[x].Amount = data.Bids[x].Size
|
|
base.Bids[x].Price = data.Bids[x].Price.Float64()
|
|
}
|
|
return e.Websocket.Orderbook.LoadSnapshot(&base)
|
|
}
|
|
var data []WsFuturesOrderbookUpdateEvent
|
|
err := json.Unmarshal(incoming, &data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dataMap := map[string][2][]orderbook.Level{}
|
|
for x := range data {
|
|
ab, ok := dataMap[data[x].CurrencyPair]
|
|
if !ok {
|
|
ab = [2][]orderbook.Level{}
|
|
}
|
|
if data[x].Amount > 0 {
|
|
ab[1] = append(ab[1], orderbook.Level{
|
|
Price: data[x].Price.Float64(),
|
|
Amount: data[x].Amount,
|
|
})
|
|
} else {
|
|
ab[0] = append(ab[0], orderbook.Level{
|
|
Price: data[x].Price.Float64(),
|
|
Amount: -data[x].Amount,
|
|
})
|
|
}
|
|
if !ok {
|
|
dataMap[data[x].CurrencyPair] = ab
|
|
}
|
|
}
|
|
if len(dataMap) == 0 {
|
|
return errors.New("missing orderbook ask and bid data")
|
|
}
|
|
for key, ab := range dataMap {
|
|
currencyPair, err := currency.NewPairFromString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{
|
|
Asks: ab[0],
|
|
Bids: ab[1],
|
|
Asset: assetType,
|
|
Exchange: e.Name,
|
|
Pair: currencyPair,
|
|
LastUpdated: lastPushed,
|
|
LastPushed: lastPushed,
|
|
ValidateOrderbook: e.ValidateOrderbook,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesOrdersPushData(data []byte, assetType asset.Item) ([]order.Detail, error) {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesOrder `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
orderDetails := make([]order.Detail, len(resp.Result))
|
|
for x := range resp.Result {
|
|
var status order.Status
|
|
if resp.Result[x].Status == "finished" {
|
|
if resp.Result[x].FinishAs == "ioc" || resp.Result[x].FinishAs == "reduce_only" {
|
|
status = order.Cancelled
|
|
} else {
|
|
status, err = order.StringToOrderStatus(resp.Result[x].FinishAs)
|
|
}
|
|
} else {
|
|
status, err = order.StringToOrderStatus(resp.Result[x].Status)
|
|
}
|
|
if err != nil {
|
|
e.Websocket.DataHandler <- order.ClassificationError{
|
|
Exchange: e.Name,
|
|
OrderID: strconv.FormatInt(resp.Result[x].ID, 10),
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
orderDetails[x] = order.Detail{
|
|
Amount: resp.Result[x].Size,
|
|
Exchange: e.Name,
|
|
OrderID: strconv.FormatInt(resp.Result[x].ID, 10),
|
|
Status: status,
|
|
Pair: resp.Result[x].Contract,
|
|
LastUpdated: resp.Result[x].FinishTime.Time(),
|
|
Date: resp.Result[x].CreateTime.Time(),
|
|
ExecutedAmount: resp.Result[x].Size - resp.Result[x].Left,
|
|
Price: resp.Result[x].Price,
|
|
AssetType: assetType,
|
|
AccountID: resp.Result[x].User,
|
|
CloseTime: resp.Result[x].FinishTime.Time(),
|
|
}
|
|
}
|
|
return orderDetails, nil
|
|
}
|
|
|
|
func (e *Exchange) procesFuturesUserTrades(data []byte, assetType asset.Item) error {
|
|
if !e.IsFillsFeedEnabled() {
|
|
return nil
|
|
}
|
|
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesUserTrade `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fills := make([]fill.Data, len(resp.Result))
|
|
for x := range resp.Result {
|
|
fills[x] = fill.Data{
|
|
Timestamp: resp.Result[x].CreateTime.Time(),
|
|
Exchange: e.Name,
|
|
CurrencyPair: resp.Result[x].Contract,
|
|
OrderID: resp.Result[x].OrderID,
|
|
TradeID: resp.Result[x].ID,
|
|
Price: resp.Result[x].Price.Float64(),
|
|
Amount: resp.Result[x].Size,
|
|
AssetType: assetType,
|
|
}
|
|
}
|
|
return e.Websocket.Fills.Update(fills...)
|
|
}
|
|
|
|
func (e *Exchange) processFuturesLiquidatesNotification(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesLiquidationNotification `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesAutoDeleveragesNotification(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesAutoDeleveragesNotification `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processPositionCloseData(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsPositionClose `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processBalancePushData(ctx context.Context, data []byte, assetType asset.Item) error {
|
|
var resp []WsBalance
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
creds, err := e.GetCredentials(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
changes := make([]account.Change, len(resp))
|
|
for x, bal := range resp {
|
|
c := bal.Currency
|
|
if assetType == asset.Options && c.IsEmpty() {
|
|
c = currency.USDT // Settlement currency is USDT
|
|
}
|
|
changes[x] = account.Change{
|
|
AssetType: assetType,
|
|
Account: bal.User,
|
|
Balance: &account.Balance{
|
|
Currency: c,
|
|
Total: bal.Balance,
|
|
Free: bal.Balance,
|
|
AvailableWithoutBorrow: bal.Balance,
|
|
UpdatedAt: bal.Time.Time(),
|
|
},
|
|
}
|
|
}
|
|
e.Websocket.DataHandler <- changes
|
|
return account.ProcessChange(e.Name, changes, creds)
|
|
}
|
|
|
|
func (e *Exchange) processFuturesReduceRiskLimitNotification(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesReduceRiskLimitNotification `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesPositionsNotification(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesPosition `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|
|
|
|
func (e *Exchange) processFuturesAutoOrderPushData(data []byte) error {
|
|
resp := struct {
|
|
Time types.Time `json:"time"`
|
|
Channel string `json:"channel"`
|
|
Event string `json:"event"`
|
|
Result []WsFuturesAutoOrder `json:"result"`
|
|
}{}
|
|
err := json.Unmarshal(data, &resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e.Websocket.DataHandler <- &resp
|
|
return nil
|
|
}
|