ccxt-bybit
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
926 lines (910 loc) • 122 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ExchangeNotAvailable, ArgumentsRequired, BadRequest, AccountSuspended, InvalidAddress, PermissionDenied, DDoSProtection, InsufficientFunds, InvalidNonce, CancelPending, InvalidOrder, OrderNotFound, AuthenticationError, RequestTimeout, NotSupported, BadSymbol } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class okex3 extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'okex3',
'name': 'OKEX',
'countries': [ 'CN', 'US' ],
'version': 'v3',
'rateLimit': 1000, // up to 3000 requests per 5 minutes ≈ 600 requests per minute ≈ 10 requests per second ≈ 100 ms
'has': {
'CORS': false,
'fetchOHLCV': true,
'fetchOrder': true,
'fetchOrders': false,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'fetchCurrencies': false, // see below
'fetchDeposits': true,
'fetchWithdrawals': true,
'fetchTime': true,
'fetchTransactions': false,
'fetchMyTrades': false, // they don't have it
'fetchDepositAddress': true,
'fetchOrderTrades': true,
'fetchTickers': true,
'fetchLedger': true,
'withdraw': true,
'futures': true,
},
'timeframes': {
'1m': '60',
'3m': '180',
'5m': '300',
'15m': '900',
'30m': '1800',
'1h': '3600',
'2h': '7200',
'4h': '14400',
'6h': '21600',
'12h': '43200',
'1d': '86400',
'1w': '604800',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/32552768-0d6dd3c6-c4a6-11e7-90f8-c043b64756a7.jpg',
'api': 'https://www.okex.com',
'www': 'https://www.okex.com',
'doc': 'https://www.okex.com/docs/en/',
'fees': 'https://www.okex.com/pages/products/fees.html',
},
'api': {
'general': {
'get': [
'time',
],
},
'account': {
'get': [
'currencies',
'wallet',
'wallet/{currency}',
'withdrawal/fee',
'withdrawal/history',
'withdrawal/history/{currency}',
'ledger',
'deposit/address',
'deposit/history',
'deposit/history/{currency}',
],
'post': [
'transfer',
'withdrawal',
],
},
'spot': {
'get': [
'accounts',
'accounts/{currency}',
'accounts/{currency}/ledger',
'orders',
'orders_pending',
'orders/{order_id}',
'orders/{client_oid}',
'fills',
'algo',
// public
'instruments',
'instruments/{instrument_id}/book',
'instruments/ticker',
'instruments/{instrument_id}/ticker',
'instruments/{instrument_id}/trades',
'instruments/{instrument_id}/candles',
],
'post': [
'order_algo',
'orders',
'batch_orders',
'cancel_orders/{order_id}',
'cancel_orders/{client_oid}',
'cancel_batch_algos',
'cancel_batch_orders',
],
},
'margin': {
'get': [
'accounts',
'accounts/{instrument_id}',
'accounts/{instrument_id}/ledger',
'accounts/availability',
'accounts/{instrument_id}/availability',
'accounts/borrowed',
'accounts/{instrument_id}/borrowed',
'orders',
'orders/{order_id}',
'orders/{client_oid}',
'orders_pending',
'fills',
],
'post': [
'accounts/borrow',
'accounts/repayment',
'orders',
'batch_orders',
'cancel_orders',
'cancel_orders/{order_id}',
'cancel_orders/{client_oid}',
'cancel_batch_orders',
],
},
'futures': {
'get': [
'position',
'{instrument_id}/position',
'accounts',
'accounts/{currency}',
'accounts/{currency}/leverage',
'accounts/{currency}/ledger',
'order_algo/{instrument_id}',
'orders/{instrument_id}',
'orders/{instrument_id}/{order_id}',
'orders/{instrument_id}/{client_oid}',
'fills',
// public
'instruments',
'instruments/{instrument_id}/book',
'instruments/ticker',
'instruments/{instrument_id}/ticker',
'instruments/{instrument_id}/trades',
'instruments/{instrument_id}/candles',
'accounts/{instrument_id}/holds',
'instruments/{instrument_id}/index',
'rate',
'instruments/{instrument_id}/estimated_price',
'instruments/{instrument_id}/open_interest',
'instruments/{instrument_id}/price_limit',
'instruments/{instrument_id}/liquidation',
'instruments/{instrument_id}/mark_price',
],
'post': [
'accounts/{currency}/leverage',
'accounts/margin_mode',
'order',
'orders',
'order_algo',
'cancel_algos',
'cancel_order/{instrument_id}/{order_id}',
'cancel_order/{instrument_id}/{client_oid}',
'cancel_batch_orders/{instrument_id}',
'close_position',
'cancel_all',
],
},
'swap': {
'get': [
'position',
'{instrument_id}/position',
'accounts',
'{instrument_id}/accounts',
'accounts/{instrument_id}/settings',
'accounts/{instrument_id}/ledger',
'accounts/{instrument_id}/holds',
'order_algo/{instrument_id}',
'orders/{instrument_id}',
'orders/{instrument_id}/{order_id}',
'orders/{instrument_id}/{client_oid}',
'fills',
// public
'instruments',
'instruments/{instrument_id}/depth',
'instruments/ticker',
'instruments/{instrument_id}/ticker',
'instruments/{instrument_id}/trades',
'instruments/{instrument_id}/candles',
'instruments/{instrument_id}/index',
'rate',
'instruments/{instrument_id}/open_interest',
'instruments/{instrument_id}/price_limit',
'instruments/{instrument_id}/liquidation',
'instruments/{instrument_id}/funding_time',
'instruments/{instrument_id}/mark_price',
'instruments/{instrument_id}/historical_funding_rate',
],
'post': [
'accounts/{instrument_id}/leverage',
'order',
'order_algo',
'orders',
'cancel_algos',
'cancel_order/{instrument_id}/{order_id}',
'cancel_order/{instrument_id}/{client_oid}',
'cancel_batch_orders/{instrument_id}',
],
},
// they have removed this part from public
'ett': {
'get': [
'accounts',
'accounts/{currency}',
'accounts/{currency}/ledger',
'orders', // fetchOrder, fetchOrders
// public
'constituents/{ett}',
'define-price/{ett}',
],
'post': [
'orders',
'orders/{order_id}',
],
},
},
'fees': {
'trading': {
'taker': 0.0015,
'maker': 0.0010,
},
'spot': {
'taker': 0.0015,
'maker': 0.0010,
},
'futures': {
'taker': 0.0005,
'maker': 0.0002,
},
'swap': {
'taker': 0.00075,
'maker': 0.00020,
},
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'password': true,
},
'exceptions': {
// http error codes
// 400 Bad Request — Invalid request format
// 401 Unauthorized — Invalid API Key
// 403 Forbidden — You do not have access to the requested resource
// 404 Not Found
// 500 Internal Server Error — We had a problem with our server
'exact': {
'1': ExchangeError, // { "code": 1, "message": "System error" }
// undocumented
'failure to get a peer from the ring-balancer': ExchangeNotAvailable, // { "message": "failure to get a peer from the ring-balancer" }
'4010': PermissionDenied, // { "code": 4010, "message": "For the security of your funds, withdrawals are not permitted within 24 hours after changing fund password / mobile number / Google Authenticator settings " }
// common
'30001': AuthenticationError, // { "code": 30001, "message": 'request header "OK_ACCESS_KEY" cannot be blank'}
'30002': AuthenticationError, // { "code": 30002, "message": 'request header "OK_ACCESS_SIGN" cannot be blank'}
'30003': AuthenticationError, // { "code": 30003, "message": 'request header "OK_ACCESS_TIMESTAMP" cannot be blank'}
'30004': AuthenticationError, // { "code": 30004, "message": 'request header "OK_ACCESS_PASSPHRASE" cannot be blank'}
'30005': InvalidNonce, // { "code": 30005, "message": "invalid OK_ACCESS_TIMESTAMP" }
'30006': AuthenticationError, // { "code": 30006, "message": "invalid OK_ACCESS_KEY" }
'30007': BadRequest, // { "code": 30007, "message": 'invalid Content_Type, please use "application/json" format'}
'30008': RequestTimeout, // { "code": 30008, "message": "timestamp request expired" }
'30009': ExchangeError, // { "code": 30009, "message": "system error" }
'30010': AuthenticationError, // { "code": 30010, "message": "API validation failed" }
'30011': PermissionDenied, // { "code": 30011, "message": "invalid IP" }
'30012': AuthenticationError, // { "code": 30012, "message": "invalid authorization" }
'30013': AuthenticationError, // { "code": 30013, "message": "invalid sign" }
'30014': DDoSProtection, // { "code": 30014, "message": "request too frequent" }
'30015': AuthenticationError, // { "code": 30015, "message": 'request header "OK_ACCESS_PASSPHRASE" incorrect'}
'30016': ExchangeError, // { "code": 30015, "message": "you are using v1 apiKey, please use v1 endpoint. If you would like to use v3 endpoint, please subscribe to v3 apiKey" }
'30017': ExchangeError, // { "code": 30017, "message": "apikey's broker id does not match" }
'30018': ExchangeError, // { "code": 30018, "message": "apikey's domain does not match" }
'30019': ExchangeNotAvailable, // { "code": 30019, "message": "Api is offline or unavailable" }
'30020': BadRequest, // { "code": 30020, "message": "body cannot be blank" }
'30021': BadRequest, // { "code": 30021, "message": "Json data format error" }, { "code": 30021, "message": "json data format error" }
'30022': PermissionDenied, // { "code": 30022, "message": "Api has been frozen" }
'30023': BadRequest, // { "code": 30023, "message": "{0} parameter cannot be blank" }
'30024': BadSymbol, // {"code":30024,"message":"\"instrument_id\" is an invalid parameter"}
'30025': BadRequest, // { "code": 30025, "message": "{0} parameter category error" }
'30026': DDoSProtection, // { "code": 30026, "message": "requested too frequent" }
'30027': AuthenticationError, // { "code": 30027, "message": "login failure" }
'30028': PermissionDenied, // { "code": 30028, "message": "unauthorized execution" }
'30029': AccountSuspended, // { "code": 30029, "message": "account suspended" }
'30030': ExchangeError, // { "code": 30030, "message": "endpoint request failed. Please try again" }
'30031': BadRequest, // { "code": 30031, "message": "token does not exist" }
'30032': BadSymbol, // { "code": 30032, "message": "pair does not exist" }
'30033': BadRequest, // { "code": 30033, "message": "exchange domain does not exist" }
'30034': ExchangeError, // { "code": 30034, "message": "exchange ID does not exist" }
'30035': ExchangeError, // { "code": 30035, "message": "trading is not supported in this website" }
'30036': ExchangeError, // { "code": 30036, "message": "no relevant data" }
'30038': AuthenticationError, // { "code": 30038, "message": "user does not exist" }
'30037': ExchangeNotAvailable, // { "code": 30037, "message": "endpoint is offline or unavailable" }
// futures
'32001': AccountSuspended, // { "code": 32001, "message": "futures account suspended" }
'32002': PermissionDenied, // { "code": 32002, "message": "futures account does not exist" }
'32003': CancelPending, // { "code": 32003, "message": "canceling, please wait" }
'32004': ExchangeError, // { "code": 32004, "message": "you have no unfilled orders" }
'32005': InvalidOrder, // { "code": 32005, "message": "max order quantity" }
'32006': InvalidOrder, // { "code": 32006, "message": "the order price or trigger price exceeds USD 1 million" }
'32007': InvalidOrder, // { "code": 32007, "message": "leverage level must be the same for orders on the same side of the contract" }
'32008': InvalidOrder, // { "code": 32008, "message": "Max. positions to open (cross margin)" }
'32009': InvalidOrder, // { "code": 32009, "message": "Max. positions to open (fixed margin)" }
'32010': ExchangeError, // { "code": 32010, "message": "leverage cannot be changed with open positions" }
'32011': ExchangeError, // { "code": 32011, "message": "futures status error" }
'32012': ExchangeError, // { "code": 32012, "message": "futures order update error" }
'32013': ExchangeError, // { "code": 32013, "message": "token type is blank" }
'32014': ExchangeError, // { "code": 32014, "message": "your number of contracts closing is larger than the number of contracts available" }
'32015': ExchangeError, // { "code": 32015, "message": "margin ratio is lower than 100% before opening positions" }
'32016': ExchangeError, // { "code": 32016, "message": "margin ratio is lower than 100% after opening position" }
'32017': ExchangeError, // { "code": 32017, "message": "no BBO" }
'32018': ExchangeError, // { "code": 32018, "message": "the order quantity is less than 1, please try again" }
'32019': ExchangeError, // { "code": 32019, "message": "the order price deviates from the price of the previous minute by more than 3%" }
'32020': ExchangeError, // { "code": 32020, "message": "the price is not in the range of the price limit" }
'32021': ExchangeError, // { "code": 32021, "message": "leverage error" }
'32022': ExchangeError, // { "code": 32022, "message": "this function is not supported in your country or region according to the regulations" }
'32023': ExchangeError, // { "code": 32023, "message": "this account has outstanding loan" }
'32024': ExchangeError, // { "code": 32024, "message": "order cannot be placed during delivery" }
'32025': ExchangeError, // { "code": 32025, "message": "order cannot be placed during settlement" }
'32026': ExchangeError, // { "code": 32026, "message": "your account is restricted from opening positions" }
'32029': ExchangeError, // { "code": 32029, "message": "order info does not exist" }
'32028': ExchangeError, // { "code": 32028, "message": "account is suspended and liquidated" }
'32027': ExchangeError, // { "code": 32027, "message": "cancelled over 20 orders" }
'32044': ExchangeError, // { "code": 32044, "message": "The margin ratio after submitting this order is lower than the minimum requirement ({0}) for your tier." }
// token and margin trading
'33001': PermissionDenied, // { "code": 33001, "message": "margin account for this pair is not enabled yet" }
'33002': AccountSuspended, // { "code": 33002, "message": "margin account for this pair is suspended" }
'33003': InsufficientFunds, // { "code": 33003, "message": "no loan balance" }
'33004': ExchangeError, // { "code": 33004, "message": "loan amount cannot be smaller than the minimum limit" }
'33005': ExchangeError, // { "code": 33005, "message": "repayment amount must exceed 0" }
'33006': ExchangeError, // { "code": 33006, "message": "loan order not found" }
'33007': ExchangeError, // { "code": 33007, "message": "status not found" }
'33008': ExchangeError, // { "code": 33008, "message": "loan amount cannot exceed the maximum limit" }
'33009': ExchangeError, // { "code": 33009, "message": "user ID is blank" }
'33010': ExchangeError, // { "code": 33010, "message": "you cannot cancel an order during session 2 of call auction" }
'33011': ExchangeError, // { "code": 33011, "message": "no new market data" }
'33012': ExchangeError, // { "code": 33012, "message": "order cancellation failed" }
'33013': InvalidOrder, // { "code": 33013, "message": "order placement failed" }
'33014': OrderNotFound, // { "code": 33014, "message": "order does not exist" }
'33015': InvalidOrder, // { "code": 33015, "message": "exceeded maximum limit" }
'33016': ExchangeError, // { "code": 33016, "message": "margin trading is not open for this token" }
'33017': InsufficientFunds, // { "code": 33017, "message": "insufficient balance" }
'33018': ExchangeError, // { "code": 33018, "message": "this parameter must be smaller than 1" }
'33020': ExchangeError, // { "code": 33020, "message": "request not supported" }
'33021': BadRequest, // { "code": 33021, "message": "token and the pair do not match" }
'33022': InvalidOrder, // { "code": 33022, "message": "pair and the order do not match" }
'33023': ExchangeError, // { "code": 33023, "message": "you can only place market orders during call auction" }
'33024': InvalidOrder, // { "code": 33024, "message": "trading amount too small" }
'33025': InvalidOrder, // { "code": 33025, "message": "base token amount is blank" }
'33026': ExchangeError, // { "code": 33026, "message": "transaction completed" }
'33027': InvalidOrder, // { "code": 33027, "message": "cancelled order or order cancelling" }
'33028': InvalidOrder, // { "code": 33028, "message": "the decimal places of the trading price exceeded the limit" }
'33029': InvalidOrder, // { "code": 33029, "message": "the decimal places of the trading size exceeded the limit" }
'33034': ExchangeError, // { "code": 33034, "message": "You can only place limit order after Call Auction has started" }
'33059': BadRequest, // { "code": 33059, "message": "client_oid or order_id is required" }
'33060': BadRequest, // { "code": 33060, "message": "Only fill in either parameter client_oid or order_id" }
// account
'34001': PermissionDenied, // { "code": 34001, "message": "withdrawal suspended" }
'34002': InvalidAddress, // { "code": 34002, "message": "please add a withdrawal address" }
'34003': ExchangeError, // { "code": 34003, "message": "sorry, this token cannot be withdrawn to xx at the moment" }
'34004': ExchangeError, // { "code": 34004, "message": "withdrawal fee is smaller than minimum limit" }
'34005': ExchangeError, // { "code": 34005, "message": "withdrawal fee exceeds the maximum limit" }
'34006': ExchangeError, // { "code": 34006, "message": "withdrawal amount is lower than the minimum limit" }
'34007': ExchangeError, // { "code": 34007, "message": "withdrawal amount exceeds the maximum limit" }
'34008': InsufficientFunds, // { "code": 34008, "message": "insufficient balance" }
'34009': ExchangeError, // { "code": 34009, "message": "your withdrawal amount exceeds the daily limit" }
'34010': ExchangeError, // { "code": 34010, "message": "transfer amount must be larger than 0" }
'34011': ExchangeError, // { "code": 34011, "message": "conditions not met" }
'34012': ExchangeError, // { "code": 34012, "message": "the minimum withdrawal amount for NEO is 1, and the amount must be an integer" }
'34013': ExchangeError, // { "code": 34013, "message": "please transfer" }
'34014': ExchangeError, // { "code": 34014, "message": "transfer limited" }
'34015': ExchangeError, // { "code": 34015, "message": "subaccount does not exist" }
'34016': PermissionDenied, // { "code": 34016, "message": "transfer suspended" }
'34017': AccountSuspended, // { "code": 34017, "message": "account suspended" }
'34018': AuthenticationError, // { "code": 34018, "message": "incorrect trades password" }
'34019': PermissionDenied, // { "code": 34019, "message": "please bind your email before withdrawal" }
'34020': PermissionDenied, // { "code": 34020, "message": "please bind your funds password before withdrawal" }
'34021': InvalidAddress, // { "code": 34021, "message": "Not verified address" }
'34022': ExchangeError, // { "code": 34022, "message": "Withdrawals are not available for sub accounts" }
'34023': PermissionDenied, // { "code": 34023, "message": "Please enable futures trading before transferring your funds" }
// swap
'35001': ExchangeError, // { "code": 35001, "message": "Contract does not exist" }
'35002': ExchangeError, // { "code": 35002, "message": "Contract settling" }
'35003': ExchangeError, // { "code": 35003, "message": "Contract paused" }
'35004': ExchangeError, // { "code": 35004, "message": "Contract pending settlement" }
'35005': AuthenticationError, // { "code": 35005, "message": "User does not exist" }
'35008': InvalidOrder, // { "code": 35008, "message": "Risk ratio too high" }
'35010': InvalidOrder, // { "code": 35010, "message": "Position closing too large" }
'35012': InvalidOrder, // { "code": 35012, "message": "Incorrect order size" }
'35014': InvalidOrder, // { "code": 35014, "message": "Order price is not within limit" }
'35015': InvalidOrder, // { "code": 35015, "message": "Invalid leverage level" }
'35017': ExchangeError, // { "code": 35017, "message": "Open orders exist" }
'35019': InvalidOrder, // { "code": 35019, "message": "Order size too large" }
'35020': InvalidOrder, // { "code": 35020, "message": "Order price too high" }
'35021': InvalidOrder, // { "code": 35021, "message": "Order size exceeded current tier limit" }
'35022': ExchangeError, // { "code": 35022, "message": "Contract status error" }
'35024': ExchangeError, // { "code": 35024, "message": "Contract not initialized" }
'35025': InsufficientFunds, // { "code": 35025, "message": "No account balance" }
'35026': ExchangeError, // { "code": 35026, "message": "Contract settings not initialized" }
'35029': OrderNotFound, // { "code": 35029, "message": "Order does not exist" }
'35030': InvalidOrder, // { "code": 35030, "message": "Order size too large" }
'35031': InvalidOrder, // { "code": 35031, "message": "Cancel order size too large" }
'35032': ExchangeError, // { "code": 35032, "message": "Invalid user status" }
'35039': ExchangeError, // { "code": 35039, "message": "Open order quantity exceeds limit" }
'35040': InvalidOrder, // {"error_message":"Invalid order type","result":"true","error_code":"35040","order_id":"-1"}
'35044': ExchangeError, // { "code": 35044, "message": "Invalid order status" }
'35046': InsufficientFunds, // { "code": 35046, "message": "Negative account balance" }
'35047': InsufficientFunds, // { "code": 35047, "message": "Insufficient account balance" }
'35048': ExchangeError, // { "code": 35048, "message": "User contract is frozen and liquidating" }
'35049': InvalidOrder, // { "code": 35049, "message": "Invalid order type" }
'35050': InvalidOrder, // { "code": 35050, "message": "Position settings are blank" }
'35052': InsufficientFunds, // { "code": 35052, "message": "Insufficient cross margin" }
'35053': ExchangeError, // { "code": 35053, "message": "Account risk too high" }
'35055': InsufficientFunds, // { "code": 35055, "message": "Insufficient account balance" }
'35057': ExchangeError, // { "code": 35057, "message": "No last traded price" }
'35058': ExchangeError, // { "code": 35058, "message": "No limit" }
'35059': BadRequest, // { "code": 35059, "message": "client_oid or order_id is required" }
'35060': BadRequest, // { "code": 35060, "message": "Only fill in either parameter client_oid or order_id" }
'35061': BadRequest, // { "code": 35061, "message": "Invalid instrument_id" }
'35062': InvalidOrder, // { "code": 35062, "message": "Invalid match_price" }
'35063': InvalidOrder, // { "code": 35063, "message": "Invalid order_size" }
'35064': InvalidOrder, // { "code": 35064, "message": "Invalid client_oid" }
},
'broad': {
},
},
'options': {
'createMarketBuyOrderRequiresPrice': true,
'fetchMarkets': [ 'spot', 'futures', 'swap' ],
'defaultType': 'spot', // 'account', 'spot', 'margin', 'futures', 'swap'
'auth': {
'time': 'public',
'currencies': 'private',
'instruments': 'public',
'rate': 'public',
'constituents/{ett}': 'public',
'define-price/{ett}': 'public',
},
},
'commonCurrencies': {
// OKEX refers to ERC20 version of Aeternity (AEToken)
'AE': 'AET', // https://github.com/ccxt/ccxt/issues/4981
'HOT': 'Hydro Protocol',
'HSR': 'HC',
'MAG': 'Maggie',
'YOYO': 'YOYOW',
'WIN': 'WinToken', // https://github.com/ccxt/ccxt/issues/5701
},
});
}
async fetchTime (params = {}) {
const response = await this.generalGetTime (params);
//
// {
// "iso": "2015-01-07T23:47:25.201Z",
// "epoch": 1420674445.201
// }
//
return this.parse8601 (this.safeString (response, 'iso'));
}
async fetchMarkets (params = {}) {
const types = this.safeValue (this.options, 'fetchMarkets');
let result = [];
for (let i = 0; i < types.length; i++) {
const markets = await this.fetchMarketsByType (types[i], params);
result = this.arrayConcat (result, markets);
}
return result;
}
parseMarkets (markets) {
const result = [];
for (let i = 0; i < markets.length; i++) {
result.push (this.parseMarket (markets[i]));
}
return result;
}
parseMarket (market) {
//
// spot markets
//
// [ { base_currency: "EOS",
// instrument_id: "EOS-OKB",
// min_size: "0.01",
// product_id: "EOS-OKB",
// quote_currency: "OKB",
// size_increment: "0.000001",
// tick_size: "0.0001" },
//
// ..., // the spot endpoint also returns ETT instruments
//
// { base_currency: "OK06ETT",
// base_increment: "0.00000001",
// base_min_size: "0.01",
// instrument_id: "OK06ETT-USDT",
// min_size: "0.01",
// product_id: "OK06ETT-USDT",
// quote_currency: "USDT",
// quote_increment: "0.0001",
// size_increment: "0.00000001",
// tick_size: "0.0001" } ]
//
// futures markets
//
// [ { instrument_id: "BTG-USD-190329",
// underlying_index: "BTG",
// quote_currency: "USD",
// tick_size: "0.01",
// contract_val: "10",
// listing: "2018-12-14",
// delivery: "2019-03-29",
// trade_increment: "1" } ]
//
// swap markets
//
// [ { instrument_id: "BTC-USD-SWAP",
// underlying_index: "BTC",
// quote_currency: "USD",
// coin: "BTC",
// contract_val: "100",
// listing: "2018-10-23T20:11:00.443Z",
// delivery: "2018-10-24T20:11:00.443Z",
// size_increment: "4",
// tick_size: "4" } ]
//
const id = this.safeString (market, 'instrument_id');
let marketType = 'spot';
let spot = true;
let future = false;
let swap = false;
let baseId = this.safeString (market, 'base_currency');
const contractVal = this.safeFloat (market, 'contract_val');
if (contractVal !== undefined) {
marketType = 'swap';
spot = false;
swap = true;
baseId = this.safeString (market, 'coin');
const futuresAlias = this.safeString (market, 'alias');
if (futuresAlias !== undefined) {
swap = false;
future = true;
marketType = 'futures';
baseId = this.safeString (market, 'underlying_index');
}
}
const quoteId = this.safeString (market, 'quote_currency');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const symbol = spot ? (base + '/' + quote) : id;
let amountPrecision = this.safeString (market, 'size_increment');
if (amountPrecision !== undefined) {
amountPrecision = this.precisionFromString (amountPrecision);
}
let pricePrecision = this.safeString (market, 'tick_size');
if (pricePrecision !== undefined) {
pricePrecision = this.precisionFromString (pricePrecision);
}
const precision = {
'amount': amountPrecision,
'price': pricePrecision,
};
const minAmount = this.safeFloat2 (market, 'min_size', 'base_min_size');
let minPrice = this.safeFloat (market, 'tick_size');
if (precision['price'] !== undefined) {
minPrice = Math.pow (10, -precision['price']);
}
let minCost = undefined;
if (minAmount !== undefined && minPrice !== undefined) {
minCost = minAmount * minPrice;
}
const active = true;
const fees = this.safeValue2 (this.fees, marketType, 'trading', {});
return this.extend (fees, {
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'info': market,
'type': marketType,
'spot': spot,
'futures': future,
'swap': swap,
'active': active,
'precision': precision,
'limits': {
'amount': {
'min': minAmount,
'max': undefined,
},
'price': {
'min': minPrice,
'max': undefined,
},
'cost': {
'min': minCost,
'max': undefined,
},
},
});
}
async fetchMarketsByType (type, params = {}) {
const method = type + 'GetInstruments';
const response = await this[method] (params);
//
// spot markets
//
// [ { base_currency: "EOS",
// base_increment: "0.000001",
// base_min_size: "0.01",
// instrument_id: "EOS-OKB",
// min_size: "0.01",
// product_id: "EOS-OKB",
// quote_currency: "OKB",
// quote_increment: "0.0001",
// size_increment: "0.000001",
// tick_size: "0.0001" } ]
//
// futures markets
//
// [ { instrument_id: "BTG-USD-190329",
// underlying_index: "BTG",
// quote_currency: "USD",
// tick_size: "0.01",
// contract_val: "10",
// listing: "2018-12-14",
// delivery: "2019-03-29",
// trade_increment: "1" } ]
//
// swap markets
//
// [ { instrument_id: "BTC-USD-SWAP",
// underlying_index: "BTC",
// quote_currency: "USD",
// coin: "BTC",
// contract_val: "100",
// listing: "2018-10-23T20:11:00.443Z",
// delivery: "2018-10-24T20:11:00.443Z",
// size_increment: "4",
// tick_size: "4" } ]
//
return this.parseMarkets (response);
}
async fetchCurrencies (params = {}) {
// has['fetchCurrencies'] is currently set to false
// despite that their docs say these endpoints are public:
// https://www.okex.com/api/account/v3/withdrawal/fee
// https://www.okex.com/api/account/v3/currencies
// it will still reply with { "code":30001, "message": "OK-ACCESS-KEY header is required" }
// if you attempt to access it without authentication
const response = await this.accountGetCurrencies (params);
//
// [
// {
// name: '',
// currency: 'BTC',
// can_withdraw: '1',
// can_deposit: '1',
// min_withdrawal: '0.0100000000000000'
// },
// ]
//
const result = {};
for (let i = 0; i < response.length; i++) {
const currency = response[i];
const id = this.safeString (currency, 'currency');
const code = this.safeCurrencyCode (id);
const precision = 8; // default precision, todo: fix "magic constants"
const name = this.safeString (currency, 'name');
const canDeposit = this.safeInteger (currency, 'can_deposit');
const canWithdraw = this.safeInteger (currency, 'can_withdraw');
const active = canDeposit && canWithdraw;
result[code] = {
'id': id,
'code': code,
'info': currency,
'type': undefined,
'name': name,
'active': active,
'fee': undefined, // todo: redesign
'precision': precision,
'limits': {
'amount': { 'min': undefined, 'max': undefined },
'price': { 'min': undefined, 'max': undefined },
'cost': { 'min': undefined, 'max': undefined },
'withdraw': {
'min': this.safeFloat (currency, 'min_withdrawal'),
'max': undefined,
},
},
};
}
return result;
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
let method = market['type'] + 'GetInstrumentsInstrumentId';
method += (market['type'] === 'swap') ? 'Depth' : 'Book';
const request = {
'instrument_id': market['id'],
};
if (limit !== undefined) {
request['size'] = limit; // max 200
}
const response = await this[method] (this.extend (request, params));
//
// { asks: [ ["0.02685268", "0.242571", "1"],
// ["0.02685493", "0.164085", "1"],
// ...
// ["0.02779", "1.039", "1"],
// ["0.027813", "0.0876", "1"] ],
// bids: [ ["0.02684052", "10.371849", "1"],
// ["0.02684051", "3.707", "4"],
// ...
// ["0.02634963", "0.132934", "1"],
// ["0.02634962", "0.264838", "2"] ],
// timestamp: "2018-12-17T20:24:16.159Z" }
//
const timestamp = this.parse8601 (this.safeString (response, 'timestamp'));
return this.parseOrderBook (response, timestamp);
}
parseTicker (ticker, market = undefined) {
//
// { best_ask: "0.02665472",
// best_bid: "0.02665221",
// instrument_id: "ETH-BTC",
// product_id: "ETH-BTC",
// last: "0.02665472",
// ask: "0.02665472", // missing in the docs
// bid: "0.02665221", // not mentioned in the docs
// open_24h: "0.02645482",
// high_24h: "0.02714633",
// low_24h: "0.02614109",
// base_volume_24h: "572298.901923",
// timestamp: "2018-12-17T21:20:07.856Z",
// quote_volume_24h: "15094.86831261" }
//
const timestamp = this.parse8601 (this.safeString (ticker, 'timestamp'));
let symbol = undefined;
const marketId = this.safeString (ticker, 'instrument_id');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else if (marketId !== undefined) {
const parts = marketId.split ('-');
const numParts = parts.length;
if (numParts === 2) {
const [ baseId, quoteId ] = parts;
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
} else {
symbol = marketId;
}
}
if (market !== undefined) {
symbol = market['symbol'];
}
const last = this.safeFloat (ticker, 'last');
const open = this.safeFloat (ticker, 'open_24h');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeFloat (ticker, 'high_24h'),
'low': this.safeFloat (ticker, 'low_24h'),
'bid': this.safeFloat (ticker, 'best_bid'),
'bidVolume': undefined,
'ask': this.safeFloat (ticker, 'best_ask'),
'askVolume': undefined,
'vwap': undefined,
'open': open,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': this.safeFloat (ticker, 'base_volume_24h'),
'quoteVolume': this.safeFloat (ticker, 'quote_volume_24h'),
'info': ticker,
};
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const method = market['type'] + 'GetInstrumentsInstrumentIdTicker';
const request = {
'instrument_id': market['id'],
};
const response = await this[method] (this.extend (request, params));
//
// { best_ask: "0.02665472",
// best_bid: "0.02665221",
// instrument_id: "ETH-BTC",
// product_id: "ETH-BTC",
// last: "0.02665472",
// ask: "0.02665472",
// bid: "0.02665221",
// open_24h: "0.02645482",
// high_24h: "0.02714633",
// low_24h: "0.02614109",
// base_volume_24h: "572298.901923",
// timestamp: "2018-12-17T21:20:07.856Z",
// quote_volume_24h: "15094.86831261" }
//
return this.parseTicker (response);
}
async fetchTickersByType (type, symbols = undefined, params = {}) {
await this.loadMarkets ();
const method = type + 'GetInstrumentsTicker';
const response = await this[method] (params);
const result = {};
for (let i = 0; i < response.length; i++) {
const ticker = this.parseTicker (response[i]);
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return result;
}
async fetchTickers (symbols = undefined, params = {}) {
const defaultType = this.safeString2 (this.options, 'fetchTickers', 'defaultType');
const type = this.safeString (params, 'type', defaultType);
return await this.fetchTickersByType (type, symbols, this.omit (params, 'type'));
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// spot trades
//
// {
// time: "2018-12-17T23:31:08.268Z",
// timestamp: "2018-12-17T23:31:08.268Z",
// trade_id: "409687906",
// price: "0.02677805",
// size: "0.923467",
// side: "sell"
// }
//
// futures trades, swap trades
//
// {
// trade_id: "1989230840021013",
// side: "buy",
// price: "92.42",
// qty: "184", // missing in swap markets
// size: "5", // missing in futures markets
// timestamp: "2018-12-17T23:26:04.613Z"
// }
//
// fetchOrderTrades (private)
//
// spot trades, margin trades
//
// {
// "created_at":"2019-03-15T02:52:56.000Z",
// "exec_type":"T", // whether the order is taker or maker
// "fee":"0.00000082",
// "instrument_id":"BTC-USDT",
// "ledger_id":"3963052721",
// "liquidity":"T", // whether the order is taker or maker
// "order_id":"2482659399697408",
// "price":"3888.6",
// "product_id":"BTC-USDT",
// "side":"buy",
// "size":"0.00055306",
// "timestamp":"2019-03-15T02:52:56.000Z"
// },
//
// futures trades, swap trades
//
// {
// "trade_id":"197429674631450625",
// "instrument_id":"EOS-USD-SWAP",
// "order_id":"6a-7-54d663a28-0",
// "price":"3.633",
// "order_qty":"1.0000",
// "fee":"-0.000551",
// "created_at":"2019-03-21T04:41:58.0Z", // missing in swap trades
// "timestamp":"2019-03-25T05:56:31.287Z", // missing in futures trades
// "exec_type":"M", // whether the order is taker or maker
// "side":"short", // "buy" in futures trades
// }
//
let symbol = undefined;
if (market !== undefined) {
symbol = market['symbol'];
}
const timestamp = this.parse8601 (this.safeString2 (trade, 'timestamp', 'created_at'));
const price = this.safeFloat (trade, 'price');
let amount = this.safeFloat2 (trade, 'size', 'qty');
amount = this.safeFloat (trade, 'order_qty', amount);
let takerOrMaker = this.safeString2 (trade, 'exec_type', 'liquidity');
if (takerOrMaker ===