UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,187 lines (1,185 loc) 85.8 kB
// ---------------------------------------------------------------------------- // 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 // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import Exchange from './abstract/gemini.js'; import { ExchangeError, ArgumentsRequired, BadRequest, OrderNotFound, InvalidOrder, InvalidNonce, InsufficientFunds, AuthenticationError, PermissionDenied, NotSupported, OnMaintenance, RateLimitExceeded, ExchangeNotAvailable } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { TICK_SIZE } from './base/functions/number.js'; import { sha384 } from './static_dependencies/noble-hashes/sha512.js'; // --------------------------------------------------------------------------- /** * @class gemini * @augments Exchange */ export default class gemini extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'gemini', 'name': 'Gemini', 'countries': ['US'], // 600 requests a minute = 10 requests per second => 1000ms / 10 = 100ms between requests (private endpoints) // 120 requests a minute = 2 requests per second => ( 1000ms / rateLimit ) / 2 = 5 (public endpoints) 'rateLimit': 100, 'version': 'v1', 'pro': true, 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': true, 'future': false, 'option': false, 'addMargin': false, 'cancelOrder': true, 'closeAllPositions': false, 'closePosition': false, 'createDepositAddress': true, 'createMarketOrder': false, 'createOrder': true, 'createReduceOnlyOrder': false, 'fetchBalance': true, 'fetchBidsAsks': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchClosedOrders': false, 'fetchCrossBorrowRate': false, 'fetchCrossBorrowRates': false, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': true, 'fetchDepositsWithdrawals': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchIsolatedBorrowRate': false, 'fetchIsolatedBorrowRates': false, 'fetchLeverage': false, 'fetchLeverageTiers': false, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterestHistory': false, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': false, 'fetchPosition': false, 'fetchPositionMode': false, 'fetchPositions': false, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactions': 'emulated', 'postOnly': true, 'reduceMargin': false, 'sandbox': true, 'setLeverage': false, 'setMarginMode': false, 'setPositionMode': false, 'withdraw': true, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg', 'api': { 'public': 'https://api.gemini.com', 'private': 'https://api.gemini.com', 'web': 'https://docs.gemini.com', 'webExchange': 'https://exchange.gemini.com', }, 'www': 'https://gemini.com/', 'doc': [ 'https://docs.gemini.com/rest-api', 'https://docs.sandbox.gemini.com', ], 'test': { 'public': 'https://api.sandbox.gemini.com', 'private': 'https://api.sandbox.gemini.com', // use the true doc instead of the sandbox doc // since they differ in parsing // https://github.com/ccxt/ccxt/issues/7874 // https://github.com/ccxt/ccxt/issues/7894 'web': 'https://docs.gemini.com', 'webExchange': 'https://exchange.gemini.com', }, 'fees': [ 'https://gemini.com/api-fee-schedule', 'https://gemini.com/trading-fees', 'https://gemini.com/transfer-fees', ], }, 'api': { 'webExchange': { 'get': [ '', ], }, 'web': { 'get': [ 'rest-api', ], }, 'public': { 'get': { 'v1/symbols': 5, 'v1/symbols/details/{symbol}': 5, 'v1/staking/rates': 5, 'v1/pubticker/{symbol}': 5, 'v2/ticker/{symbol}': 5, 'v2/candles/{symbol}/{timeframe}': 5, 'v1/trades/{symbol}': 5, 'v1/auction/{symbol}': 5, 'v1/auction/{symbol}/history': 5, 'v1/pricefeed': 5, 'v1/book/{symbol}': 5, 'v1/earn/rates': 5, }, }, 'private': { 'post': { 'v1/staking/unstake': 1, 'v1/staking/stake': 1, 'v1/staking/rewards': 1, 'v1/staking/history': 1, 'v1/order/new': 1, 'v1/order/cancel': 1, 'v1/wrap/{symbol}': 1, 'v1/order/cancel/session': 1, 'v1/order/cancel/all': 1, 'v1/order/status': 1, 'v1/orders': 1, 'v1/mytrades': 1, 'v1/notionalvolume': 1, 'v1/tradevolume': 1, 'v1/clearing/new': 1, 'v1/clearing/status': 1, 'v1/clearing/cancel': 1, 'v1/clearing/confirm': 1, 'v1/balances': 1, 'v1/balances/staking': 1, 'v1/notionalbalances/{currency}': 1, 'v1/transfers': 1, 'v1/addresses/{network}': 1, 'v1/deposit/{network}/newAddress': 1, 'v1/deposit/{currency}/newAddress': 1, 'v1/withdraw/{currency}': 1, 'v1/account/transfer/{currency}': 1, 'v1/payments/addbank': 1, 'v1/payments/methods': 1, 'v1/payments/sen/withdraw': 1, 'v1/balances/earn': 1, 'v1/earn/interest': 1, 'v1/earn/history': 1, 'v1/approvedAddresses/{network}/request': 1, 'v1/approvedAddresses/account/{network}': 1, 'v1/approvedAddresses/{network}/remove': 1, 'v1/account': 1, 'v1/account/create': 1, 'v1/account/list': 1, 'v1/heartbeat': 1, 'v1/roles': 1, }, }, }, 'precisionMode': TICK_SIZE, 'fees': { 'trading': { 'taker': 0.004, 'maker': 0.002, }, }, 'httpExceptions': { '400': BadRequest, '403': PermissionDenied, '404': OrderNotFound, '406': InsufficientFunds, '429': RateLimitExceeded, '500': ExchangeError, '502': ExchangeNotAvailable, '503': OnMaintenance, // The exchange is down for maintenance }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1hr', '6h': '6hr', '1d': '1day', }, 'exceptions': { 'exact': { 'AuctionNotOpen': BadRequest, 'ClientOrderIdTooLong': BadRequest, 'ClientOrderIdMustBeString': BadRequest, 'ConflictingOptions': BadRequest, 'EndpointMismatch': BadRequest, 'EndpointNotFound': BadRequest, 'IneligibleTiming': BadRequest, 'InsufficientFunds': InsufficientFunds, 'InvalidJson': BadRequest, 'InvalidNonce': InvalidNonce, 'InvalidApiKey': AuthenticationError, 'InvalidOrderType': InvalidOrder, 'InvalidPrice': InvalidOrder, 'InvalidQuantity': InvalidOrder, 'InvalidSide': InvalidOrder, 'InvalidSignature': AuthenticationError, 'InvalidSymbol': BadRequest, 'InvalidTimestampInPayload': BadRequest, 'Maintenance': OnMaintenance, 'MarketNotOpen': InvalidOrder, 'MissingApikeyHeader': AuthenticationError, 'MissingOrderField': InvalidOrder, 'MissingRole': AuthenticationError, 'MissingPayloadHeader': AuthenticationError, 'MissingSignatureHeader': AuthenticationError, 'NoSSL': AuthenticationError, 'OptionsMustBeArray': BadRequest, 'OrderNotFound': OrderNotFound, 'RateLimit': RateLimitExceeded, 'System': ExchangeError, 'UnsupportedOption': BadRequest, // This order execution option is not supported. }, 'broad': { 'The Gemini Exchange is currently undergoing maintenance.': OnMaintenance, 'We are investigating technical issues with the Gemini Exchange.': ExchangeNotAvailable, 'Internal Server Error': ExchangeNotAvailable, }, }, 'options': { 'fetchMarketsMethod': 'fetch_markets_from_api', 'fetchMarketFromWebRetries': 10, 'fetchMarketsFromAPI': { 'fetchDetailsForAllSymbols': false, 'quoteCurrencies': ['USDT', 'GUSD', 'USD', 'DAI', 'EUR', 'GBP', 'SGD', 'BTC', 'ETH', 'LTC', 'BCH', 'SOL'], }, 'fetchMarkets': { 'webApiEnable': true, 'webApiRetries': 10, }, 'fetchUsdtMarkets': ['btcusdt', 'ethusdt'], 'fetchCurrencies': { 'webApiEnable': true, 'webApiRetries': 5, 'webApiMuteFailure': true, }, 'fetchTickerMethod': 'fetchTickerV1', 'networks': { 'BTC': 'bitcoin', 'ERC20': 'ethereum', 'BCH': 'bitcoincash', 'LTC': 'litecoin', 'ZEC': 'zcash', 'FIL': 'filecoin', 'DOGE': 'dogecoin', 'XTZ': 'tezos', 'AVAXX': 'avalanche', 'SOL': 'solana', 'ATOM': 'cosmos', 'DOT': 'polkadot', }, 'nonce': 'milliseconds', 'conflictingMarkets': { 'paxgusd': { 'base': 'PAXG', 'quote': 'USD', }, }, }, 'features': { 'default': { 'sandbox': true, 'createOrder': { 'marginMode': false, 'triggerPrice': true, 'triggerPriceType': undefined, 'triggerDirection': false, 'stopLossPrice': false, 'takeProfitPrice': false, 'attachedStopLossTakeProfit': undefined, 'timeInForce': { 'IOC': true, 'FOK': true, 'PO': true, 'GTD': false, }, 'hedged': false, 'trailing': false, 'leverage': false, 'marketBuyByCost': true, 'marketBuyRequiresPrice': false, 'selfTradePrevention': false, 'iceberg': false, }, 'createOrders': undefined, 'fetchMyTrades': { 'marginMode': false, 'limit': 500, 'daysBack': undefined, 'untilDays': undefined, 'symbolRequired': true, }, 'fetchOrder': { 'marginMode': false, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOpenOrders': { 'marginMode': false, 'limit': undefined, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOrders': undefined, 'fetchClosedOrders': undefined, 'fetchOHLCV': { 'limit': undefined, }, }, 'spot': { 'extends': 'default', }, 'swap': { 'linear': { 'extends': 'default', }, 'inverse': undefined, }, 'future': { 'linear': undefined, 'inverse': undefined, }, }, }); } /** * @method * @name gemini#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} [params] extra parameters specific to the endpoint * @returns {object} an associative dictionary of currencies */ async fetchCurrencies(params = {}) { return await this.fetchCurrenciesFromWeb(params); } /** * @method * @name gemini#fetchCurrenciesFromWeb * @ignore * @description fetches all available currencies on an exchange * @param {object} [params] extra parameters specific to the endpoint * @returns {object} an associative dictionary of currencies */ async fetchCurrenciesFromWeb(params = {}) { const data = await this.fetchWebEndpoint('fetchCurrencies', 'webExchangeGet', true, '="currencyData">', '</script>'); if (data === undefined) { return undefined; } // // { // "tradingPairs": [ [ 'BTCUSD', 2, 8, '0.00001', 10, true ], ... ], // "currencies": [ // [ "ORCA", "Orca", 204, 6, 0, 6, 8, false, null, "solana" ], // as confirmed, precisions seem to be the 5th index // [ "ATOM", "Cosmos", 44, 6, 0, 6, 8, false, null, "cosmos" ], // [ "ETH", "Ether", 2, 6, 0, 18, 8, false, null, "ethereum" ], // [ "GBP", "Pound Sterling", 22, 2, 2, 2, 2, true, "£", null ], // ... // ], // "networks": [ // [ "solana", "SOL", "Solana" ], // [ "zcash", "ZEC", "Zcash" ], // [ "tezos", "XTZ", "Tezos" ], // [ "cosmos", "ATOM", "Cosmos" ], // [ "ethereum", "ETH", "Ethereum" ], // ... // ] // } // const result = {}; this.options['tradingPairs'] = this.safeList(data, 'tradingPairs'); const currenciesArray = this.safeValue(data, 'currencies', []); for (let i = 0; i < currenciesArray.length; i++) { const currency = currenciesArray[i]; const id = this.safeString(currency, 0); const code = this.safeCurrencyCode(id); const type = this.safeString(currency, 7) ? 'fiat' : 'crypto'; const precision = this.parseNumber(this.parsePrecision(this.safeString(currency, 5))); const networks = {}; const networkId = this.safeString(currency, 9); let networkCode = undefined; if (networkId !== undefined) { networkCode = this.networkIdToCode(networkId); networks[networkCode] = { 'info': currency, 'id': networkId, 'network': networkCode, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': precision, 'limits': { 'deposit': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, }; } result[code] = this.safeCurrencyStructure({ 'info': currency, 'id': id, 'code': code, 'name': this.safeString(currency, 1), 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'type': type, 'precision': precision, 'limits': { 'deposit': { 'min': undefined, 'max': undefined, }, 'withdraw': { 'min': undefined, 'max': undefined, }, }, 'networks': networks, }); } return result; } /** * @method * @name gemini#fetchMarkets * @description retrieves data on all markets for gemini * @see https://docs.gemini.com/rest-api/#symbols * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} an array of objects representing market data */ async fetchMarkets(params = {}) { const method = this.safeValue(this.options, 'fetchMarketsMethod', 'fetch_markets_from_api'); if (method === 'fetch_markets_from_web') { const promises = []; promises.push(this.fetchMarketsFromWeb(params)); // get usd markets promises.push(this.fetchUSDTMarkets(params)); // get usdt markets const promisesResult = await Promise.all(promises); return this.arrayConcat(promisesResult[0], promisesResult[1]); } return await this.fetchMarketsFromAPI(params); } async fetchMarketsFromWeb(params = {}) { const data = await this.fetchWebEndpoint('fetchMarkets', 'webGetRestApi', false, '<h1 id="symbols-and-minimums">Symbols and minimums</h1>'); const error = this.id + ' fetchMarketsFromWeb() the API doc HTML markup has changed, breaking the parser of order limits and precision info for markets.'; const tables = data.split('tbody>'); const numTables = tables.length; if (numTables < 2) { throw new NotSupported(error); } const rows = tables[1].split("\n<tr>\n"); // eslint-disable-line quotes const numRows = rows.length; if (numRows < 2) { throw new NotSupported(error); } const result = []; // skip the first element (empty string) for (let i = 1; i < numRows; i++) { const row = rows[i]; const cells = row.split("</td>\n"); // eslint-disable-line quotes const numCells = cells.length; if (numCells < 5) { throw new NotSupported(error); } // [ // '<td>btcusd', // currency // '<td>0.00001 BTC (1e-5)', // min order size // '<td>0.00000001 BTC (1e-8)', // tick size // '<td>0.01 USD', // quote currency price increment // '</tr>' // ] let marketId = cells[0].replace('<td>', ''); marketId = marketId.replace('*', ''); // const base = this.safeCurrencyCode (baseId); const minAmountString = cells[1].replace('<td>', ''); const minAmountParts = minAmountString.split(' '); const minAmount = this.safeNumber(minAmountParts, 0); const amountPrecisionString = cells[2].replace('<td>', ''); const amountPrecisionParts = amountPrecisionString.split(' '); const idLength = marketId.length - 0; const startingIndex = idLength - 3; const pricePrecisionString = cells[3].replace('<td>', ''); const pricePrecisionParts = pricePrecisionString.split(' '); const quoteId = this.safeStringLower(pricePrecisionParts, 1, marketId.slice(startingIndex, idLength)); const baseId = this.safeStringLower(amountPrecisionParts, 1, marketId.replace(quoteId, '')); const base = this.safeCurrencyCode(baseId); const quote = this.safeCurrencyCode(quoteId); result.push({ 'id': marketId, 'symbol': base + '/' + quote, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'active': undefined, 'contract': false, 'linear': undefined, 'inverse': undefined, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.safeNumber(amountPrecisionParts, 0), 'price': this.safeNumber(pricePrecisionParts, 0), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': minAmount, 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'created': undefined, 'info': row, }); } return result; } parseMarketActive(status) { const statuses = { 'open': true, 'closed': false, 'cancel_only': true, 'post_only': true, 'limit_only': true, }; if (status === undefined) { return true; // as defaulted below } return this.safeBool(statuses, status, true); } async fetchUSDTMarkets(params = {}) { // these markets can't be scrapped and fetchMarketsFrom api does an extra call // to load market ids which we don't need here if ('test' in this.urls) { return []; // sandbox does not have usdt markets } const fetchUsdtMarkets = this.safeValue(this.options, 'fetchUsdtMarkets', []); const result = []; for (let i = 0; i < fetchUsdtMarkets.length; i++) { const marketId = fetchUsdtMarkets[i]; const request = { 'symbol': marketId, }; // don't use Promise.all here, for some reason the exchange can't handle it and crashes const rawResponse = await this.publicGetV1SymbolsDetailsSymbol(this.extend(request, params)); result.push(this.parseMarket(rawResponse)); } return result; } async fetchMarketsFromAPI(params = {}) { const marketIdsRaw = await this.publicGetV1Symbols(params); // // [ // "btcusd", // "linkusd", // ... // ] // const result = []; const options = this.safeDict(this.options, 'fetchMarketsFromAPI', {}); const bugSymbol = 'efilfil'; // we skip this inexistent test symbol, which bugs other functions const marketIds = []; for (let i = 0; i < marketIdsRaw.length; i++) { if (marketIdsRaw[i] !== bugSymbol) { marketIds.push(marketIdsRaw[i]); } } if (this.safeBool(options, 'fetchDetailsForAllSymbols', false)) { const promises = []; for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const request = { 'symbol': marketId, }; promises.push(this.publicGetV1SymbolsDetailsSymbol(this.extend(request, params))); // // { // "symbol": "BTCUSD", // "base_currency": "BTC", // "quote_currency": "USD", // "tick_size": 1E-8, // "quote_increment": 0.01, // "min_order_size": "0.00001", // "status": "open", // "wrap_enabled": false // } // } const responses = await Promise.all(promises); for (let i = 0; i < responses.length; i++) { result.push(this.parseMarket(responses[i])); } } else { // use trading-pairs info, if it was fetched const tradingPairs = this.safeList(this.options, 'tradingPairs'); if (tradingPairs !== undefined) { const indexedTradingPairs = this.indexBy(tradingPairs, 0); for (let i = 0; i < marketIds.length; i++) { const marketId = marketIds[i]; const tradingPair = this.safeList(indexedTradingPairs, marketId.toUpperCase()); if (tradingPair !== undefined) { result.push(this.parseMarket(tradingPair)); } } } else { for (let i = 0; i < marketIds.length; i++) { result.push(this.parseMarket(marketIds[i])); } } } return result; } parseMarket(response) { // // response might be: // // btcusd // // or // // [ // 'BTCUSD', // symbol // 2, // priceTickDecimalPlaces // 8, // quantityTickDecimalPlaces // '0.00001', // quantityMinimum // 10, // quantityRoundDecimalPlaces // true // minimumsAreInclusive // ], // // or // // { // "symbol": "BTCUSD", // perpetuals have 'PERP' suffix, i.e. DOGEUSDPERP // "base_currency": "BTC", // "quote_currency": "USD", // "tick_size": 1E-8, // "quote_increment": 0.01, // "min_order_size": "0.00001", // "status": "open", // "wrap_enabled": false // "product_type": "swap", // only in perps // "contract_type": "linear", // only in perps // "contract_price_currency": "GUSD" // only in perps // } // let marketId = undefined; let baseId = undefined; let quoteId = undefined; let settleId = undefined; let tickSize = undefined; let amountPrecision = undefined; let minSize = undefined; let status = undefined; let swap = false; let contractSize = undefined; let linear = undefined; let inverse = undefined; const isString = (typeof response === 'string'); const isArray = (Array.isArray(response)); if (!isString && !isArray) { marketId = this.safeStringLower(response, 'symbol'); amountPrecision = this.safeNumber(response, 'tick_size'); // right, exchange has an imperfect naming and this turns out to be an amount-precision tickSize = this.safeNumber(response, 'quote_increment'); // this is tick-size actually minSize = this.safeNumber(response, 'min_order_size'); status = this.parseMarketActive(this.safeString(response, 'status')); baseId = this.safeString(response, 'base_currency'); quoteId = this.safeString(response, 'quote_currency'); settleId = this.safeString(response, 'contract_price_currency'); } else { // if no detailed API was called, then parse either string or array if (isString) { marketId = response; } else { marketId = this.safeStringLower(response, 0); tickSize = this.parseNumber(this.parsePrecision(this.safeString(response, 1))); // priceTickDecimalPlaces amountPrecision = this.parseNumber(this.parsePrecision(this.safeString(response, 2))); // quantityTickDecimalPlaces minSize = this.safeNumber(response, 3); // quantityMinimum } const marketIdUpper = marketId.toUpperCase(); const isPerp = (marketIdUpper.indexOf('PERP') >= 0); const marketIdWithoutPerp = marketIdUpper.replace('PERP', ''); const conflictingMarkets = this.safeDict(this.options, 'conflictingMarkets', {}); const lowerCaseId = marketIdWithoutPerp.toLowerCase(); if (lowerCaseId in conflictingMarkets) { const conflictingMarket = conflictingMarkets[lowerCaseId]; baseId = conflictingMarket['base']; quoteId = conflictingMarket['quote']; if (isPerp) { settleId = conflictingMarket['quote']; } } else { const quoteCurrencies = this.handleOption('fetchMarketsFromAPI', 'quoteCurrencies', []); for (let i = 0; i < quoteCurrencies.length; i++) { const quoteCurrency = quoteCurrencies[i]; if (marketIdWithoutPerp.endsWith(quoteCurrency)) { const quoteLength = this.parseToInt(-1 * quoteCurrency.length); baseId = marketIdWithoutPerp.slice(0, quoteLength); quoteId = quoteCurrency; if (isPerp) { settleId = quoteCurrency; // always same } break; } } } } const base = this.safeCurrencyCode(baseId); const quote = this.safeCurrencyCode(quoteId); const settle = this.safeCurrencyCode(settleId); let symbol = base + '/' + quote; if (settleId !== undefined) { symbol = symbol + ':' + settle; swap = true; contractSize = tickSize; // always same linear = true; // always linear inverse = false; } const type = swap ? 'swap' : 'spot'; return { 'id': marketId, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, 'type': type, 'spot': !swap, 'margin': false, 'swap': swap, 'future': false, 'option': false, 'active': status, 'contract': swap, 'linear': linear, 'inverse': inverse, 'contractSize': contractSize, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'price': tickSize, 'amount': amountPrecision, }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': minSize, 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'created': undefined, 'info': response, }; } /** * @method * @name gemini#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.gemini.com/rest-api/#current-order-book * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async fetchOrderBook(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit_bids'] = limit; request['limit_asks'] = limit; } const response = await this.publicGetV1BookSymbol(this.extend(request, params)); return this.parseOrderBook(response, market['symbol'], undefined, 'bids', 'asks', 'price', 'amount'); } async fetchTickerV1(symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetV1PubtickerSymbol(this.extend(request, params)); // // { // "bid":"9117.95", // "ask":"9117.96", // "volume":{ // "BTC":"1615.46861748", // "USD":"14727307.57545006088", // "timestamp":1594982700000 // }, // "last":"9115.23" // } // return this.parseTicker(response, market); } async fetchTickerV2(symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetV2TickerSymbol(this.extend(request, params)); // // { // "symbol":"BTCUSD", // "open":"9080.58", // "high":"9184.53", // "low":"9063.56", // "close":"9116.08", // // Hourly prices descending for past 24 hours // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], // "bid":"9115.86", // "ask":"9115.87" // } // return this.parseTicker(response, market); } async fetchTickerV1AndV2(symbol, params = {}) { const tickerPromiseA = this.fetchTickerV1(symbol, params); const tickerPromiseB = this.fetchTickerV2(symbol, params); const [tickerA, tickerB] = await Promise.all([tickerPromiseA, tickerPromiseB]); return this.deepExtend(tickerA, { 'open': tickerB['open'], 'high': tickerB['high'], 'low': tickerB['low'], 'change': tickerB['change'], 'percentage': tickerB['percentage'], 'average': tickerB['average'], 'info': tickerB['info'], }); } /** * @method * @name gemini#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://docs.gemini.com/rest-api/#ticker * @see https://docs.gemini.com/rest-api/#ticker-v2 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {object} [params.fetchTickerMethod] 'fetchTickerV2', 'fetchTickerV1' or 'fetchTickerV1AndV2' - 'fetchTickerV1' for original ccxt.gemini.fetchTicker - 'fetchTickerV1AndV2' for 2 api calls to get the result of both fetchTicker methods - default = 'fetchTickerV1' * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async fetchTicker(symbol, params = {}) { const method = this.safeValue(this.options, 'fetchTickerMethod', 'fetchTickerV1'); if (method === 'fetchTickerV1') { return await this.fetchTickerV1(symbol, params); } if (method === 'fetchTickerV2') { return await this.fetchTickerV2(symbol, params); } return await this.fetchTickerV1AndV2(symbol, params); } parseTicker(ticker, market = undefined) { // // fetchTickers // // { // "pair": "BATUSD", // "price": "0.20687", // "percentChange24h": "0.0146" // } // // fetchTickerV1 // // { // "bid":"9117.95", // "ask":"9117.96", // "volume":{ // "BTC":"1615.46861748", // "USD":"14727307.57545006088", // "timestamp":1594982700000 // }, // "last":"9115.23" // } // // fetchTickerV2 // // { // "symbol":"BTCUSD", // "open":"9080.58", // "high":"9184.53", // "low":"9063.56", // "close":"9116.08", // // Hourly prices descending for past 24 hours // "changes":["9117.33","9105.69","9106.23","9120.35","9098.57","9114.53","9113.55","9128.01","9113.63","9133.49","9133.49","9137.75","9126.73","9103.91","9119.33","9123.04","9124.44","9117.57","9114.22","9102.33","9076.67","9074.72","9074.97","9092.05"], // "bid":"9115.86", // "ask":"9115.87" // } // const volume = this.safeValue(ticker, 'volume', {}); const timestamp = this.safeInteger(volume, 'timestamp'); let symbol = undefined; const marketId = this.safeStringLower(ticker, 'pair'); market = this.safeMarket(marketId, market); let baseId = undefined; let quoteId = undefined; let base = undefined; let quote = undefined; if ((marketId !== undefined) && (market === undefined)) { const idLength = marketId.length - 0; if (idLength === 7) { baseId = marketId.slice(0, 4); quoteId = marketId.slice(4, 7); } else { baseId = marketId.slice(0, 3); quoteId = marketId.slice(3, 6); } base = this.safeCurrencyCode(baseId); quote = this.safeCurrencyCode(quoteId); symbol = base + '/' + quote; } if ((symbol === undefined) && (market !== undefined)) { symbol = market['symbol']; baseId = this.safeStringUpper(market, 'baseId'); quoteId = this.safeStringUpper(market, 'quoteId'); } const price = this.safeString(ticker, 'price'); const last = this.safeString2(ticker, 'last', 'close', price); const percentage = this.safeString(ticker, 'percentChange24h'); const open = this.safeString(ticker, 'open'); const baseVolume = this.safeString(volume, baseId); const quoteVolume = this.safeString(volume, quoteId); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'high': this.safeString(ticker, 'high'), 'low': this.safeString(ticker, 'low'), 'bid': this.safeString(ticker, 'bid'), 'bidVolume': undefined, 'ask': this.safeString(ticker, 'ask'), 'askVolume': undefined, 'vwap': undefined, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': percentage, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }, market); } /** * @method * @name gemini#fetchTickers * @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market * @see https://docs.gemini.com/rest-api/#price-feed * @param {string[]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async fetchTickers(symbols = undefined, params = {}) { await this.loadMarkets(); const response = await this.publicGetV1Pricefeed(params); // // [ // { // "pair": "BATUSD", // "price": "0.20687", // "percentChange24h": "0.0146" // }, // { // "pair": "LINKETH", // "price": "0.018", // "percentChange24h": "0.0000" // }, // ] // return this.parseTickers(response, symbols); } parseTrade(trade, market = undefined) { // // public fetchTrades // // { // "timestamp":1601617445, // "timestampms":1601617445144, // "tid":14122489752, // "price":"0.46476", // "amount":"28.407209", // "exchange":"gemini", // "type":"buy" // } // // private fetchTrades // // { // "price":"3900.00", // "amount":"0.00996", // "timestamp":1638891173, // "timestampms":1638891173518, // "type":"Sell", // "aggressor":false, // "fee_currency":"EUR", // "fee_amount":"0.00", // "tid":73621746145, // "order_id":"73621746059", // "exchange":"gemini", // "is_auction_fill":false, // "is_clearing_fill":false, // "symbol":"ETHEUR", // "client_order_id":"1638891171610" // } // const timestamp = this.safeInteger(trade, 'timestampms'); const id = this.safeString(trade, 'tid'); const orderId = this.safeString(trade, 'order_id'); const feeCurrencyId = this.safeString(trade, 'fee_currency'); const feeCurrencyCode = this.safeCurrencyCode(feeCurrencyId); const fee = { 'cost': this.safeString(trade, 'fee_amount'), 'currency': feeCurrencyCode, }; const priceString = this.safeString(trade, 'price'); const amountString = this.safeString(trade, 'amount'); const side = this.safeStringLower(trade, 'type'); const symbol = this.safeSymbol(undefined, market); return this.safeTrade({ 'id': id, 'order': orderId, 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'symbol': symbol, 'type': undefined, 'side': side, 'takerOrMaker': undefined, 'price': priceString, 'cost': undefined, 'amount': amountString, 'fee': fee, }, market); } /** * @method * @name gemini#fetchTrades * @description get the list of most recent trades for a particular symbol * @see https://docs.gemini.com/rest-api/#trade-history * @param {string} 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 {object} [params] extra parameters specific to the exchange API endpoint * @returns {Trade[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades} */ async fetchTrades(symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'symbol': market['id'], }; if (limit !== undefined) { request['limit_trades'] = Math.min(limit, 500); } if (since !== undefined) { request['timestamp'] = since; } const response = await this.publicGetV1TradesSymbol(this.extend(request, params)); // // [ // { // "timestamp":1601617445, // "timestampms":1601617445144, // "tid":14122489752, // "price":"0.46476", // "amount":"28.407209", // "exchange":"gemini", // "type":"buy" // }, // ] // return this.parseTrades(response, market, since, limit); } parseBalance(response) { const result = { 'info': response }; for (let i = 0; i < response.length; i++) { const balance = response[i]; const currencyId = this.safeString(balance, 'currency'); const code = this.safeCurrencyCode(currencyId); const account = this.account(); account['free'] = this.safeString(balance, 'available'); account['total'] = this.safeString(balance, 'amount'); result[code] = account; } return this.safeBalance(result); } /** * @method * @name gemini#fetchTradingFees * @description fetch the trading