Files
ccxt_with_mt5/ccxt/pro/gemini.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

886 lines
36 KiB
Python

# -*- coding: utf-8 -*-
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
import ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp
import hashlib
from ccxt.base.types import Any, Int, Order, OrderBook, Str, Strings, Tickers, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import NotSupported
from ccxt.base.precise import Precise
class gemini(ccxt.async_support.gemini):
def describe(self) -> Any:
return self.deep_extend(super(gemini, self).describe(), {
'has': {
'ws': True,
'watchBalance': False,
'watchTicker': False,
'watchTickers': False,
'watchBidsAsks': True,
'watchTrades': True,
'watchTradesForSymbols': True,
'watchMyTrades': False,
'watchOrders': True,
'watchOrderBook': True,
'watchOrderBookForSymbols': True,
'watchOHLCV': True,
},
'hostname': 'api.gemini.com',
'urls': {
'api': {
'ws': 'wss://api.gemini.com',
},
'test': {
'ws': 'wss://api.sandbox.gemini.com',
},
},
})
async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
watch the list of most recent trades for a particular symbol
https://docs.gemini.com/websocket-api/#market-data-version-2
:param str symbol: unified symbol of the market to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
"""
await self.load_markets()
market = self.market(symbol)
messageHash = 'trades:' + market['symbol']
marketId = market['id']
request: dict = {
'type': 'subscribe',
'subscriptions': [
{
'name': 'l2',
'symbols': [
marketId.upper(),
],
},
],
}
subscribeHash = 'l2:' + market['symbol']
url = self.urls['api']['ws'] + '/v2/marketdata'
trades = await self.watch(url, messageHash, request, subscribeHash)
if self.newUpdates:
limit = trades.getLimit(market['symbol'], limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
"""
https://docs.gemini.com/websocket-api/#multi-market-data
get the list of most recent trades for a list of symbols
:param str[] symbols: unified symbol of the market to fetch trades for
:param int [since]: timestamp in ms of the earliest trade to fetch
:param int [limit]: the maximum amount of trades to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
"""
trades = await self.helper_for_watch_multiple_construct('trades', symbols, params)
if self.newUpdates:
first = self.safe_list(trades, 0)
tradeSymbol = self.safe_string(first, 'symbol')
limit = trades.getLimit(tradeSymbol, limit)
return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)
def parse_ws_trade(self, trade, market=None) -> Trade:
#
# regular v2 trade
#
# {
# "type": "trade",
# "symbol": "BTCUSD",
# "event_id": 122258166738,
# "timestamp": 1655330221424,
# "price": "22269.14",
# "quantity": "0.00004473",
# "side": "buy"
# }
#
# multi data trade
#
# {
# "type": "trade",
# "symbol": "ETHUSD",
# "tid": "1683002242170204", # self is not TS, but somewhat ID
# "price": "2299.24",
# "amount": "0.002662",
# "makerSide": "bid"
# }
#
timestamp = self.safe_integer(trade, 'timestamp')
id = self.safe_string_2(trade, 'event_id', 'tid')
priceString = self.safe_string(trade, 'price')
amountString = self.safe_string_2(trade, 'quantity', 'amount')
side = self.safe_string_lower(trade, 'side')
if side is None:
marketSide = self.safe_string_lower(trade, 'makerSide')
if marketSide == 'bid':
side = 'sell'
elif marketSide == 'ask':
side = 'buy'
marketId = self.safe_string_lower(trade, 'symbol')
symbol = self.safe_symbol(marketId, market)
return self.safe_trade({
'id': id,
'order': None,
'info': trade,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'symbol': symbol,
'type': None,
'side': side,
'takerOrMaker': None,
'price': priceString,
'cost': None,
'amount': amountString,
'fee': None,
}, market)
def handle_trade(self, client: Client, message):
#
# {
# "type": "trade",
# "symbol": "BTCUSD",
# "event_id": 122278173770,
# "timestamp": 1655335880981,
# "price": "22530.80",
# "quantity": "0.04",
# "side": "buy"
# }
#
trade = self.parse_ws_trade(message)
symbol = trade['symbol']
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
stored = self.safe_value(self.trades, symbol)
if stored is None:
stored = ArrayCache(tradesLimit)
self.trades[symbol] = stored
stored.append(trade)
messageHash = 'trades:' + symbol
client.resolve(stored, messageHash)
def handle_trades(self, client: Client, message):
#
# {
# "type": "l2_updates",
# "symbol": "BTCUSD",
# "changes": [
# ["buy", '22252.37', "0.02"],
# ["buy", '22251.61', "0.04"],
# ["buy", '22251.60', "0.04"],
# # some asks
# ],
# "trades": [
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258166738, timestamp: 1655330221424, price: '22269.14', quantity: "0.00004473", side: "buy"},
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258141090, timestamp: 1655330213216, price: '22250.00', quantity: "0.00704098", side: "buy"},
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258118291, timestamp: 1655330206753, price: '22250.00', quantity: "0.03", side: "buy"},
# ],
# "auction_events": [
# {
# "type": "auction_result",
# "symbol": "BTCUSD",
# "time_ms": 1655323200000,
# "result": "failure",
# "highest_bid_price": "21590.88",
# "lowest_ask_price": "21602.30",
# "collar_price": "21634.73"
# },
# {
# "type": "auction_indicative",
# "symbol": "BTCUSD",
# "time_ms": 1655323185000,
# "result": "failure",
# "highest_bid_price": "21661.90",
# "lowest_ask_price": "21663.78",
# "collar_price": "21662.845"
# },
# ]
# }
#
marketId = self.safe_string_lower(message, 'symbol')
market = self.safe_market(marketId)
trades = self.safe_value(message, 'trades')
if trades is not None:
symbol = market['symbol']
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
stored = self.safe_value(self.trades, symbol)
if stored is None:
stored = ArrayCache(tradesLimit)
self.trades[symbol] = stored
for i in range(0, len(trades)):
trade = self.parse_ws_trade(trades[i], market)
stored.append(trade)
messageHash = 'trades:' + symbol
client.resolve(stored, messageHash)
def handle_trades_for_multidata(self, client: Client, trades, timestamp: Int):
if trades is not None:
tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
storesForSymbols: dict = {}
for i in range(0, len(trades)):
marketId = trades[i]['symbol']
market = self.safe_market(marketId.lower())
symbol = market['symbol']
trade = self.parse_ws_trade(trades[i], market)
trade['timestamp'] = timestamp
trade['datetime'] = self.iso8601(timestamp)
stored = self.safe_value(self.trades, symbol)
if stored is None:
stored = ArrayCache(tradesLimit)
self.trades[symbol] = stored
stored.append(trade)
storesForSymbols[symbol] = stored
symbols = list(storesForSymbols.keys())
for i in range(0, len(symbols)):
symbol = symbols[i]
stored = storesForSymbols[symbol]
messageHash = 'trades:' + symbol
client.resolve(stored, messageHash)
async def watch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
"""
watches historical candlestick data containing the open, high, low, and close price, and the volume of a market
https://docs.gemini.com/websocket-api/#candles-data-feed
:param str symbol: unified symbol of the market to fetch OHLCV data for
:param str timeframe: the length of time each candle represents
:param int [since]: timestamp in ms of the earliest candle to fetch
:param int [limit]: the maximum amount of candles to fetch
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns int[][]: A list of candles ordered, open, high, low, close, volume
"""
await self.load_markets()
market = self.market(symbol)
timeframeId = self.safe_string(self.timeframes, timeframe, timeframe)
request: dict = {
'type': 'subscribe',
'subscriptions': [
{
'name': 'candles_' + timeframeId,
'symbols': [
market['id'].upper(),
],
},
],
}
messageHash = 'ohlcv:' + market['symbol'] + ':' + timeframeId
url = self.urls['api']['ws'] + '/v2/marketdata'
ohlcv = await self.watch(url, messageHash, request, messageHash)
if self.newUpdates:
limit = ohlcv.getLimit(symbol, limit)
return self.filter_by_since_limit(ohlcv, since, limit, 0, True)
def handle_ohlcv(self, client: Client, message):
#
# {
# "type": "candles_15m_updates",
# "symbol": "BTCUSD",
# "changes": [
# [
# 1561054500000,
# 9350.18,
# 9358.35,
# 9350.18,
# 9355.51,
# 2.07
# ],
# [
# 1561053600000,
# 9357.33,
# 9357.33,
# 9350.18,
# 9350.18,
# 1.5900161
# ]
# ...
# ]
# }
#
type = self.safe_string(message, 'type', '')
timeframeId = type[8:]
timeframeEndIndex = timeframeId.find('_')
timeframeId = timeframeId[0:timeframeEndIndex]
marketId = self.safe_string(message, 'symbol', '').lower()
market = self.safe_market(marketId)
symbol = self.safe_symbol(marketId, market)
changes = self.safe_value(message, 'changes', [])
timeframe = self.find_timeframe(timeframeId)
ohlcvsBySymbol = self.safe_value(self.ohlcvs, symbol)
if ohlcvsBySymbol is None:
self.ohlcvs[symbol] = {}
stored = self.safe_value(self.ohlcvs[symbol], timeframe)
if stored is None:
limit = self.safe_integer(self.options, 'OHLCVLimit', 1000)
stored = ArrayCacheByTimestamp(limit)
self.ohlcvs[symbol][timeframe] = stored
changesLength = len(changes)
# reverse order of array to store candles in ascending order
for i in range(0, changesLength):
index = changesLength - i - 1
parsed = self.parse_ohlcv(changes[index], market)
stored.append(parsed)
messageHash = 'ohlcv:' + symbol + ':' + timeframeId
client.resolve(stored, messageHash)
return message
async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.gemini.com/websocket-api/#market-data-version-2
:param str symbol: unified symbol of the market to fetch the order book for
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
await self.load_markets()
market = self.market(symbol)
messageHash = 'orderbook:' + market['symbol']
marketId = market['id']
request: dict = {
'type': 'subscribe',
'subscriptions': [
{
'name': 'l2',
'symbols': [
marketId.upper(),
],
},
],
}
subscribeHash = 'l2:' + market['symbol']
url = self.urls['api']['ws'] + '/v2/marketdata'
orderbook = await self.watch(url, messageHash, request, subscribeHash)
return orderbook.limit()
def handle_order_book(self, client: Client, message):
changes = self.safe_value(message, 'changes', [])
marketId = self.safe_string_lower(message, 'symbol')
market = self.safe_market(marketId)
symbol = market['symbol']
messageHash = 'orderbook:' + symbol
# orderbook = self.safe_value(self.orderbooks, symbol)
if not (symbol in self.orderbooks):
self.orderbooks[symbol] = self.order_book()
orderbook = self.orderbooks[symbol]
for i in range(0, len(changes)):
delta = changes[i]
price = self.safe_number(delta, 1)
size = self.safe_number(delta, 2)
side = 'bids' if (delta[0] == 'buy') else 'asks'
bookside = orderbook[side]
bookside.store(price, size)
orderbook[side] = bookside
orderbook['symbol'] = symbol
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
"""
watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
https://docs.gemini.com/websocket-api/#multi-market-data
:param str[] symbols: unified array of symbols
:param int [limit]: the maximum amount of order book entries to return
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
"""
orderbook = await self.helper_for_watch_multiple_construct('orderbook', symbols, params)
return orderbook.limit()
async def watch_bids_asks(self, symbols: Strings = None, params={}) -> Tickers:
"""
watches best bid & ask for symbols
https://docs.gemini.com/websocket-api/#multi-market-data
:param str[] symbols: unified symbol of the market to fetch the ticker for
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
"""
return await self.helper_for_watch_multiple_construct('bidsasks', symbols, params)
def handle_bids_asks_for_multidata(self, client: Client, rawBidAskChanges, timestamp: Int, nonce: Int):
#
# {
# eventId: '1683002916916153',
# events: [
# {
# price: '50945.37',
# reason: 'top-of-book',
# remaining: '0.0',
# side: 'bid',
# symbol: 'BTCUSDT',
# type: 'change'
# },
# {
# price: '50947.75',
# reason: 'top-of-book',
# remaining: '0.11725',
# side: 'bid',
# symbol: 'BTCUSDT',
# type: 'change'
# }
# ],
# socket_sequence: 322,
# timestamp: 1708674495,
# timestampms: 1708674495174,
# type: 'update'
# }
#
marketId = rawBidAskChanges[0]['symbol']
market = self.safe_market(marketId.lower())
symbol = market['symbol']
if not (symbol in self.bidsasks):
self.bidsasks[symbol] = self.parse_ticker({})
self.bidsasks[symbol]['symbol'] = symbol
currentBidAsk = self.bidsasks[symbol]
messageHash = 'bidsasks:' + symbol
# last update always overwrites the previous state and is the latest state
for i in range(0, len(rawBidAskChanges)):
entry = rawBidAskChanges[i]
rawSide = self.safe_string(entry, 'side')
price = self.safe_number(entry, 'price')
sizeString = self.safe_string(entry, 'remaining')
if Precise.string_eq(sizeString, '0'):
continue
size = self.parse_number(sizeString)
if rawSide == 'bid':
currentBidAsk['bid'] = price
currentBidAsk['bidVolume'] = size
else:
currentBidAsk['ask'] = price
currentBidAsk['askVolume'] = size
currentBidAsk['timestamp'] = timestamp
currentBidAsk['datetime'] = self.iso8601(timestamp)
currentBidAsk['info'] = rawBidAskChanges
bidsAsksDict = {}
bidsAsksDict[symbol] = currentBidAsk
self.bidsasks[symbol] = currentBidAsk
client.resolve(bidsAsksDict, messageHash)
async def helper_for_watch_multiple_construct(self, itemHashName: str, symbols: List[str] = None, params={}):
await self.load_markets()
if symbols is None:
raise NotSupported(self.id + ' watchMultiple requires at least one symbol')
symbols = self.market_symbols(symbols, None, False, True, True)
firstMarket = self.market(symbols[0])
if not firstMarket['spot'] and not firstMarket['linear']:
raise NotSupported(self.id + ' watchMultiple supports only spot or linear-swap symbols')
messageHashes = []
marketIds = []
for i in range(0, len(symbols)):
symbol = symbols[i]
messageHash = itemHashName + ':' + symbol
messageHashes.append(messageHash)
market = self.market(symbol)
marketIds.append(market['id'])
queryStr = ','.join(marketIds)
url = self.urls['api']['ws'] + '/v1/multimarketdata?symbols=' + queryStr + '&heartbeat=true&'
if itemHashName == 'orderbook':
url += 'trades=false&bids=true&offers=true'
elif itemHashName == 'bidsasks':
url += 'trades=false&bids=true&offers=true&top_of_book=true'
elif itemHashName == 'trades':
url += 'trades=true&bids=false&offers=false'
return await self.watch_multiple(url, messageHashes, None)
def handle_order_book_for_multidata(self, client: Client, rawOrderBookChanges, timestamp: Int, nonce: Int):
#
# rawOrderBookChanges
#
# [
# {
# delta: "4105123935484.817624",
# price: "0.000000001",
# reason: "initial", # initial|cancel|place
# remaining: "4105123935484.817624",
# side: "bid", # bid|ask
# symbol: "SHIBUSD",
# type: "change", # seems always change
# },
# ...
#
marketId = rawOrderBookChanges[0]['symbol']
market = self.safe_market(marketId.lower())
symbol = market['symbol']
messageHash = 'orderbook:' + symbol
if not (symbol in self.orderbooks):
ob = self.order_book()
self.orderbooks[symbol] = ob
orderbook = self.orderbooks[symbol]
bids = orderbook['bids']
asks = orderbook['asks']
for i in range(0, len(rawOrderBookChanges)):
entry = rawOrderBookChanges[i]
price = self.safe_number(entry, 'price')
size = self.safe_number(entry, 'remaining')
rawSide = self.safe_string(entry, 'side')
if rawSide == 'bid':
bids.store(price, size)
else:
asks.store(price, size)
orderbook['bids'] = bids
orderbook['asks'] = asks
orderbook['symbol'] = symbol
orderbook['nonce'] = nonce
orderbook['timestamp'] = timestamp
orderbook['datetime'] = self.iso8601(timestamp)
self.orderbooks[symbol] = orderbook
client.resolve(orderbook, messageHash)
def handle_l2_updates(self, client: Client, message):
#
# {
# "type": "l2_updates",
# "symbol": "BTCUSD",
# "changes": [
# ["buy", '22252.37', "0.02"],
# ["buy", '22251.61', "0.04"],
# ["buy", '22251.60', "0.04"],
# # some asks
# ],
# "trades": [
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258166738, timestamp: 1655330221424, price: '22269.14', quantity: "0.00004473", side: "buy"},
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258141090, timestamp: 1655330213216, price: '22250.00', quantity: "0.00704098", side: "buy"},
# {type: 'trade', symbol: 'BTCUSD', event_id: 122258118291, timestamp: 1655330206753, price: '22250.00', quantity: "0.03", side: "buy"},
# ],
# "auction_events": [
# {
# "type": "auction_result",
# "symbol": "BTCUSD",
# "time_ms": 1655323200000,
# "result": "failure",
# "highest_bid_price": "21590.88",
# "lowest_ask_price": "21602.30",
# "collar_price": "21634.73"
# },
# {
# "type": "auction_indicative",
# "symbol": "BTCUSD",
# "time_ms": 1655323185000,
# "result": "failure",
# "highest_bid_price": "21661.90",
# "lowest_ask_price": "21663.79",
# "collar_price": "21662.845"
# },
# ]
# }
#
self.handle_order_book(client, message)
self.handle_trades(client, message)
async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
"""
watches information on multiple orders made by the user
https://docs.gemini.com/websocket-api/#order-events
:param str symbol: unified market symbol of the market orders were made in
:param int [since]: the earliest time in ms to fetch orders for
:param int [limit]: the maximum number of order structures to retrieve
:param dict [params]: extra parameters specific to the exchange API endpoint
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
"""
url = self.urls['api']['ws'] + '/v1/order/events?eventTypeFilter=initial&eventTypeFilter=accepted&eventTypeFilter=rejected&eventTypeFilter=fill&eventTypeFilter=cancelled&eventTypeFilter=booked'
await self.load_markets()
authParams: dict = {
'url': url,
}
await self.authenticate(authParams)
if symbol is not None:
market = self.market(symbol)
symbol = market['symbol']
messageHash = 'orders'
orders = await self.watch(url, messageHash, None, messageHash)
if self.newUpdates:
limit = orders.getLimit(symbol, limit)
return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)
def handle_heartbeat(self, client: Client, message):
#
# {
# "type": "heartbeat",
# "timestampms": 1659740268958,
# "sequence": 7,
# "trace_id": "25b3d92476dd3a9a5c03c9bd9e0a0dba",
# "socket_sequence": 7
# }
#
client.lastPong = self.milliseconds()
return message
def handle_subscription(self, client: Client, message):
#
# {
# "type": "subscription_ack",
# "accountId": 19433282,
# "subscriptionId": "orderevents-websocket-25b3d92476dd3a9a5c03c9bd9e0a0dba",
# "symbolFilter": [],
# "apiSessionFilter": [],
# "eventTypeFilter": []
# }
#
return message
def handle_order(self, client: Client, message):
#
# [
# {
# "type": "accepted",
# "order_id": "134150423884",
# "event_id": "134150423886",
# "account_name": "primary",
# "client_order_id": "1659739406916",
# "api_session": "account-pnBFSS0XKGvDamX4uEIt",
# "symbol": "batbtc",
# "side": "sell",
# "order_type": "exchange limit",
# "timestamp": "1659739407",
# "timestampms": 1659739407576,
# "is_live": True,
# "is_cancelled": False,
# "is_hidden": False,
# "original_amount": "1",
# "price": "1",
# "socket_sequence": 139
# }
# ]
#
messageHash = 'orders'
if self.orders is None:
limit = self.safe_integer(self.options, 'ordersLimit', 1000)
self.orders = ArrayCacheBySymbolById(limit)
orders = self.orders
for i in range(0, len(message)):
order = self.parse_ws_order(message[i])
orders.append(order)
client.resolve(self.orders, messageHash)
def parse_ws_order(self, order, market=None):
#
# {
# "type": "accepted",
# "order_id": "134150423884",
# "event_id": "134150423886",
# "account_name": "primary",
# "client_order_id": "1659739406916",
# "api_session": "account-pnBFSS0XKGvDamX4uEIt",
# "symbol": "batbtc",
# "side": "sell",
# "order_type": "exchange limit",
# "timestamp": "1659739407",
# "timestampms": 1659739407576,
# "is_live": True,
# "is_cancelled": False,
# "is_hidden": False,
# "original_amount": "1",
# "price": "1",
# "socket_sequence": 139
# }
#
timestamp = self.safe_integer(order, 'timestampms')
status = self.safe_string(order, 'type')
marketId = self.safe_string(order, 'symbol')
typeId = self.safe_string(order, 'order_type')
behavior = self.safe_string(order, 'behavior')
timeInForce = 'GTC'
postOnly = False
if behavior == 'immediate-or-cancel':
timeInForce = 'IOC'
elif behavior == 'fill-or-kill':
timeInForce = 'FOK'
elif behavior == 'maker-or-cancel':
timeInForce = 'PO'
postOnly = True
return self.safe_order({
'id': self.safe_string(order, 'order_id'),
'clientOrderId': self.safe_string(order, 'client_order_id'),
'info': order,
'timestamp': timestamp,
'datetime': self.iso8601(timestamp),
'lastTradeTimestamp': None,
'status': self.parse_ws_order_status(status),
'symbol': self.safe_symbol(marketId, market),
'type': self.parse_ws_order_type(typeId),
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': self.safe_string(order, 'side'),
'price': self.safe_number(order, 'price'),
'stopPrice': None,
'average': self.safe_number(order, 'avg_execution_price'),
'cost': None,
'amount': self.safe_number(order, 'original_amount'),
'filled': self.safe_number(order, 'executed_amount'),
'remaining': self.safe_number(order, 'remaining_amount'),
'fee': None,
'trades': None,
}, market)
def parse_ws_order_status(self, status):
statuses: dict = {
'accepted': 'open',
'booked': 'open',
'fill': 'closed',
'cancelled': 'canceled',
'cancel_rejected': 'rejected',
'rejected': 'rejected',
}
return self.safe_string(statuses, status, status)
def parse_ws_order_type(self, type):
types: dict = {
'exchange limit': 'limit',
'market buy': 'market',
'market sell': 'market',
}
return self.safe_string(types, type, type)
def handle_error(self, client: Client, message):
#
# {
# "reason": "NoValidTradingPairs",
# "result": "error"
# }
#
raise ExchangeError(self.json(message))
def handle_message(self, client: Client, message):
#
# public
# {
# "type": "trade",
# "symbol": "BTCUSD",
# "event_id": 122278173770,
# "timestamp": 1655335880981,
# "price": "22530.80",
# "quantity": "0.04",
# "side": "buy"
# }
#
# private
# [
# {
# "type": "accepted",
# "order_id": "134150423884",
# "event_id": "134150423886",
# "account_name": "primary",
# "client_order_id": "1659739406916",
# "api_session": "account-pnBFSS0XKGvDamX4uEIt",
# "symbol": "batbtc",
# "side": "sell",
# "order_type": "exchange limit",
# "timestamp": "1659739407",
# "timestampms": 1659739407576,
# "is_live": True,
# "is_cancelled": False,
# "is_hidden": False,
# "original_amount": "1",
# "price": "1",
# "socket_sequence": 139
# }
# ]
#
isArray = isinstance(message, list)
if isArray:
self.handle_order(client, message)
return
reason = self.safe_string(message, 'reason')
if reason == 'error':
self.handle_error(client, message)
methods: dict = {
'l2_updates': self.handle_l2_updates,
'trade': self.handle_trade,
'subscription_ack': self.handle_subscription,
'heartbeat': self.handle_heartbeat,
}
type = self.safe_string(message, 'type', '')
if type.find('candles') >= 0:
self.handle_ohlcv(client, message)
return
method = self.safe_value(methods, type)
if method is not None:
method(client, message)
# handle multimarketdata
if type == 'update':
ts = self.safe_integer(message, 'timestampms', self.milliseconds())
eventId = self.safe_integer(message, 'eventId')
events = self.safe_list(message, 'events')
orderBookItems = []
bidaskItems = []
collectedEventsOfTrades = []
eventsLength = len(events)
for i in range(0, len(events)):
event = events[i]
eventType = self.safe_string(event, 'type')
isOrderBook = (eventType == 'change') and ('side' in event) and self.in_array(event['side'], ['ask', 'bid'])
eventReason = self.safe_string(event, 'reason')
isBidAsk = (eventReason == 'top-of-book') or (isOrderBook and (eventReason == 'initial') and eventsLength == 2)
if isBidAsk:
bidaskItems.append(event)
elif isOrderBook:
orderBookItems.append(event)
elif eventType == 'trade':
collectedEventsOfTrades.append(events[i])
lengthBa = len(bidaskItems)
if lengthBa > 0:
self.handle_bids_asks_for_multidata(client, bidaskItems, ts, eventId)
lengthOb = len(orderBookItems)
if lengthOb > 0:
self.handle_order_book_for_multidata(client, orderBookItems, ts, eventId)
lengthTrades = len(collectedEventsOfTrades)
if lengthTrades > 0:
self.handle_trades_for_multidata(client, collectedEventsOfTrades, ts)
async def authenticate(self, params={}):
url = self.safe_string(params, 'url')
if (self.clients is not None) and (url in self.clients):
return
self.check_required_credentials()
startIndex = len(self.urls['api']['ws'])
urlParamsIndex = url.find('?')
urlLength = len(url)
endIndex = urlParamsIndex if (urlParamsIndex >= 0) else urlLength
request = url[startIndex:endIndex]
payload: dict = {
'request': request,
'nonce': self.nonce(),
}
b64 = self.string_to_base64(self.json(payload))
signature = self.hmac(self.encode(b64), self.encode(self.secret), hashlib.sha384, 'hex')
defaultOptions: dict = {
'ws': {
'options': {
'headers': {},
},
},
}
# self.options = self.extend(defaultOptions, self.options)
self.extend_exchange_options(defaultOptions)
originalHeaders = self.options['ws']['options']['headers']
headers: dict = {
'X-GEMINI-APIKEY': self.apiKey,
'X-GEMINI-PAYLOAD': b64,
'X-GEMINI-SIGNATURE': signature,
}
self.options['ws']['options']['headers'] = headers
self.client(url)
self.options['ws']['options']['headers'] = originalHeaders