'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 === 'M') { takerOrMaker = 'maker'; } else if (takerOrMaker === 'T') { takerOrMaker = 'taker'; } const side = this.safeString (trade, 'side'); let cost = undefined; if (amount !== undefined) { if (price !== undefined) { cost = amount * price; } } const feeCost = this.safeFloat (trade, 'fee'); let fee = undefined; if (feeCost !== undefined) { let feeCurrency = undefined; if (market !== undefined) { feeCurrency = (side === 'buy') ? market['base'] : market['quote']; } fee = { // fee is either a positive number (invitation rebate) // or a negative number (transaction fee deduction) // therefore we need to invert the fee // more about it https://github.com/ccxt/ccxt/issues/5909 'cost': -feeCost, 'currency': feeCurrency, }; } const orderId = this.safeString (trade, 'order_id'); return { 'info': trade, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'id': this.safeString2 (trade, 'trade_id', 'ledger_id'), 'order': orderId, 'type': undefined, 'takerOrMaker': takerOrMaker, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const method = market['type'] + 'GetInstrumentsInstrumentIdTrades'; if ((limit === undefined) || (limit > 100)) { limit = 100; // maximum = default = 100 } const request = { 'instrument_id': market['id'], 'limit': limit, // from: 'id', // to: 'id', }; const response = await this[method] (this.extend (request, params)); // // spot markets // // [ // { // 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 markets, swap markets // // [ // { // 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" // } // ] // return this.parseTrades (response, market, since, limit); } parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { // // spot markets // // { // close: "0.02684545", // high: "0.02685084", // low: "0.02683312", // open: "0.02683894", // time: "2018-12-17T20:28:00.000Z", // volume: "101.457222" // } // // futures markets // // [ // 1545072720000, // 0.3159, // 0.3161, // 0.3144, // 0.3149, // 22886, // 725179.26172331, // ] // if (Array.isArray (ohlcv)) { const numElements = ohlcv.length; const volumeIndex = (numElements > 6) ? 6 : 5; let timestamp = ohlcv[0]; if (typeof timestamp === 'string') { timestamp = this.parse8601 (timestamp); } return [ timestamp, // timestamp parseFloat (ohlcv[1]), // Open parseFloat (ohlcv[2]), // High parseFloat (ohlcv[3]), // Low parseFloat (ohlcv[4]), // Close // parseFloat (ohlcv[5]), // Quote Volume // parseFloat (ohlcv[6]), // Base Volume parseFloat (ohlcv[volumeIndex]), // Volume, okex will return base volume in the 7th element for future markets ]; } else { return [ this.parse8601 (this.safeString (ohlcv, 'time')), this.safeFloat (ohlcv, 'open'), // Open this.safeFloat (ohlcv, 'high'), // High this.safeFloat (ohlcv, 'low'), // Low this.safeFloat (ohlcv, 'close'), // Close this.safeFloat (ohlcv, 'volume'), // Base Volume ]; } } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const method = market['type'] + 'GetInstrumentsInstrumentIdCandles'; const request = { 'instrument_id': market['id'], 'granularity': this.timeframes[timeframe], }; if (since !== undefined) { request['start'] = this.iso8601 (since); } const response = await this[method] (this.extend (request, params)); // // spot markets // // [ { close: "0.02683401", // high: "0.02683401", // low: "0.02683401", // open: "0.02683401", // time: "2018-12-17T23:47:00.000Z", // volume: "0" }, // ... // { close: "0.02684545", // high: "0.02685084", // low: "0.02683312", // open: "0.02683894", // time: "2018-12-17T20:28:00.000Z", // volume: "101.457222" } ] // // futures // // [ [ 1545090660000, // 0.3171, // 0.3174, // 0.3171, // 0.3173, // 1648, // 51930.38579450868 ], // ... // [ 1545072720000, // 0.3159, // 0.3161, // 0.3144, // 0.3149, // 22886, // 725179.26172331 ] ] // return this.parseOHLCVs (response, market, timeframe, since, limit); } parseAccountBalance (response) { // // account // // [ // { // balance: 0, // available: 0, // currency: "BTC", // hold: 0 // }, // { // balance: 0, // available: 0, // currency: "ETH", // hold: 0 // } // ] // // spot // // [ // { // frozen: "0", // hold: "0", // id: "2149632", // currency: "BTC", // balance: "0.0000000497717339", // available: "0.0000000497717339", // holds: "0" // }, // { // frozen: "0", // hold: "0", // id: "2149632", // currency: "ICN", // balance: "0.00000000925", // available: "0.00000000925", // holds: "0" // } // ] // 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['total'] = this.safeFloat (balance, 'balance'); account['used'] = this.safeFloat (balance, 'hold'); account['free'] = this.safeFloat (balance, 'available'); result[code] = account; } return this.parseBalance (result); } parseMarginBalance (response) { // // [ // { // "currency:BTC": { // "available":"0", // "balance":"0", // "borrowed":"0", // "can_withdraw":"0", // "frozen":"0", // "hold":"0", // "holds":"0", // "lending_fee":"0" // }, // "currency:USDT": { // "available":"100", // "balance":"100", // "borrowed":"0", // "can_withdraw":"100", // "frozen":"0", // "hold":"0", // "holds":"0", // "lending_fee":"0" // }, // "instrument_id":"BTC-USDT", // "liquidation_price":"0", // "product_id":"BTC-USDT", // "risk_rate":"" // }, // ] // const result = { 'info': response }; for (let i = 0; i < response.length; i++) { const balance = response[i]; const marketId = this.safeString (balance, 'instrument_id'); const market = this.safeValue (this.markets_by_id, marketId); let symbol = undefined; if (market === undefined) { const [ baseId, quoteId ] = marketId.split ('-'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } else { symbol = market['symbol']; } const omittedBalance = this.omit (balance, [ 'instrument_id', 'liquidation_price', 'product_id', 'risk_rate', 'margin_ratio', 'maint_margin_ratio', 'tiers', ]); const keys = Object.keys (omittedBalance); const accounts = {}; for (let k = 0; k < keys.length; k++) { const key = keys[k]; const marketBalance = balance[key]; if (key.indexOf (':') >= 0) { const parts = key.split (':'); const currencyId = parts[1]; const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['total'] = this.safeFloat (marketBalance, 'balance'); account['used'] = this.safeFloat (marketBalance, 'hold'); account['free'] = this.safeFloat (marketBalance, 'available'); accounts[code] = account; } else { throw new NotSupported (this.id + ' margin balance response format has changed!'); } } result[symbol] = this.parseBalance (accounts); } return result; } parseFuturesBalance (response) { // // { // "info":{ // "eos":{ // "auto_margin":"0", // "contracts": [ // { // "available_qty":"40.37069445", // "fixed_balance":"0", // "instrument_id":"EOS-USD-190329", // "margin_for_unfilled":"0", // "margin_frozen":"0", // "realized_pnl":"0", // "unrealized_pnl":"0" // }, // { // "available_qty":"40.37069445", // "fixed_balance":"14.54895721", // "instrument_id":"EOS-USD-190628", // "margin_for_unfilled":"0", // "margin_frozen":"10.64042157", // "realized_pnl":"-3.90853564", // "unrealized_pnl":"-0.259" // }, // ], // "equity":"50.75220665", // "margin_mode":"fixed", // "total_avail_balance":"40.37069445" // }, // } // } // // their root field name is "info", so our info will contain their info const result = { 'info': response }; const info = this.safeValue (response, 'info', {}); const ids = Object.keys (info); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const code = this.safeCurrencyCode (id); const balance = this.safeValue (info, id, {}); const account = this.account (); // it may be incorrect to use total, free and used for swap accounts account['total'] = this.safeFloat (balance, 'equity'); account['free'] = this.safeFloat (balance, 'total_avail_balance'); result[code] = account; } return this.parseBalance (result); } parseSwapBalance (response) { // // { // "info": [ // { // "equity":"3.0139", // "fixed_balance":"0.0000", // "instrument_id":"EOS-USD-SWAP", // "margin":"0.5523", // "margin_frozen":"0.0000", // "margin_mode":"crossed", // "margin_ratio":"1.0913", // "realized_pnl":"-0.0006", // "timestamp":"2019-03-25T03:46:10.336Z", // "total_avail_balance":"3.0000", // "unrealized_pnl":"0.0145" // } // ] // } // // their root field name is "info", so our info will contain their info const result = { 'info': response }; const info = this.safeValue (response, 'info', []); for (let i = 0; i < info.length; i++) { const balance = info[i]; const marketId = this.safeString (balance, 'instrument_id'); let symbol = marketId; if (marketId in this.markets_by_id) { symbol = this.markets_by_id[marketId]['symbol']; } const account = this.account (); // it may be incorrect to use total, free and used for swap accounts account['total'] = this.safeFloat (balance, 'equity'); account['free'] = this.safeFloat (balance, 'total_avail_balance'); result[symbol] = account; } return this.parseBalance (result); } async fetchBalance (params = {}) { await this.loadMarkets (); const defaultType = this.safeString2 (this.options, 'fetchBalance', 'defaultType'); const type = this.safeString (params, 'type', defaultType); if (type === undefined) { throw new ArgumentsRequired (this.id + " fetchBalance requires a type parameter (one of 'account', 'spot', 'margin', 'futures', 'swap')"); } const suffix = (type === 'account') ? 'Wallet' : 'Accounts'; const method = type + 'Get' + suffix; const query = this.omit (params, 'type'); const response = await this[method] (query); // // account // // [ // { // balance: 0, // available: 0, // currency: "BTC", // hold: 0 // }, // { // balance: 0, // available: 0, // currency: "ETH", // hold: 0 // } // ] // // spot // // [ // { // frozen: "0", // hold: "0", // id: "2149632", // currency: "BTC", // balance: "0.0000000497717339", // available: "0.0000000497717339", // holds: "0" // }, // { // frozen: "0", // hold: "0", // id: "2149632", // currency: "ICN", // balance: "0.00000000925", // available: "0.00000000925", // holds: "0" // } // ] // // margin // // [ // { // "currency:BTC": { // "available":"0", // "balance":"0", // "borrowed":"0", // "can_withdraw":"0", // "frozen":"0", // "hold":"0", // "holds":"0", // "lending_fee":"0" // }, // "currency:USDT": { // "available":"100", // "balance":"100", // "borrowed":"0", // "can_withdraw":"100", // "frozen":"0", // "hold":"0", // "holds":"0", // "lending_fee":"0" // }, // "instrument_id":"BTC-USDT", // "liquidation_price":"0", // "product_id":"BTC-USDT", // "risk_rate":"" // }, // ] // // futures // // { // "info":{ // "eos":{ // "auto_margin":"0", // "contracts": [ // { // "available_qty":"40.37069445", // "fixed_balance":"0", // "instrument_id":"EOS-USD-190329", // "margin_for_unfilled":"0", // "margin_frozen":"0", // "realized_pnl":"0", // "unrealized_pnl":"0" // }, // { // "available_qty":"40.37069445", // "fixed_balance":"14.54895721", // "instrument_id":"EOS-USD-190628", // "margin_for_unfilled":"0", // "margin_frozen":"10.64042157", // "realized_pnl":"-3.90853564", // "unrealized_pnl":"-0.259" // }, // ], // "equity":"50.75220665", // "margin_mode":"fixed", // "total_avail_balance":"40.37069445" // }, // } // } // // swap // // { // "info": [ // { // "equity":"3.0139", // "fixed_balance":"0.0000", // "instrument_id":"EOS-USD-SWAP", // "margin":"0.5523", // "margin_frozen":"0.0000", // "margin_mode":"crossed", // "margin_ratio":"1.0913", // "realized_pnl":"-0.0006", // "timestamp":"2019-03-25T03:46:10.336Z", // "total_avail_balance":"3.0000", // "unrealized_pnl":"0.0145" // } // ] // } // if ((type === 'account') || (type === 'spot')) { return this.parseAccountBalance (response); } else if (type === 'margin') { return this.parseMarginBalance (response); } else if (type === 'futures') { return this.parseFuturesBalance (response); } else if (type === 'swap') { return this.parseSwapBalance (response); } throw new NotSupported (this.id + " fetchBalance does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); let request = { 'instrument_id': market['id'], // 'client_oid': 'abcdef1234567890', // [a-z0-9]{1,32} // 'order_type': '0', // 0: Normal limit order (Unfilled and 0 represent normal limit order) 1: Post only 2: Fill Or Kill 3: Immediatel Or Cancel }; let method = undefined; if (market['futures'] || market['swap']) { const size = market['futures'] ? this.numberToString (amount) : this.amountToPrecision (symbol, amount); request = this.extend (request, { 'type': type, // 1:open long 2:open short 3:close long 4:close short for futures 'size': size, 'price': this.priceToPrecision (symbol, price), // 'match_price': '0', // Order at best counter party price? (0:no 1:yes). The default is 0. If it is set as 1, the price parameter will be ignored. When posting orders at best bid price, order_type can only be 0 (regular order). }); if (market['futures']) { request['leverage'] = '10'; // or '20' } method = market['type'] + 'PostOrder'; } else { const marginTrading = this.safeString (params, 'margin_trading', '1'); // 1 = spot, 2 = margin request = this.extend (request, { 'side': side, 'type': type, // limit/market 'margin_trading': marginTrading, // 1 = spot, 2 = margin }); if (type === 'limit') { request['price'] = this.priceToPrecision (symbol, price); request['size'] = this.amountToPrecision (symbol, amount); } else if (type === 'market') { // for market buy it requires the amount of quote currency to spend if (side === 'buy') { let notional = this.safeFloat (params, 'notional'); const createMarketBuyOrderRequiresPrice = this.safeValue (this.options, 'createMarketBuyOrderRequiresPrice', true); if (createMarketBuyOrderRequiresPrice) { if (price !== undefined) { if (notional === undefined) { notional = amount * price; } } else if (notional === undefined) { throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false and supply the total cost value in the 'notional' extra parameter (the exchange-specific behaviour)"); } } request['notional'] = this.costToPrecision (symbol, notional); } else { request['size'] = this.amountToPrecision (symbol, amount); } } method = (marginTrading === '2') ? 'marginPostOrders' : 'spotPostOrders'; } const response = await this[method] (this.extend (request, params)); // // { // "client_oid":"oktspot79", // "error_code":"", // "error_message":"", // "order_id":"2510789768709120", // "result":true // } // const timestamp = this.milliseconds (); const id = this.safeString (response, 'order_id'); return { 'info': response, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'status': undefined, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'amount': amount, 'filled': undefined, 'remaining': undefined, 'cost': undefined, 'trades': undefined, 'fee': undefined, }; } async cancelOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const type = market['type']; let method = type + 'PostCancelOrder'; const request = { 'instrument_id': market['id'], }; if (market['futures'] || market['swap']) { method += 'InstrumentId'; } else { method += 's'; } const clientOid = this.safeString (params, 'client_oid'); if (clientOid !== undefined) { method += 'ClientOid'; request['client_oid'] = clientOid; } else { method += 'OrderId'; request['order_id'] = id; } const query = this.omit (params, 'type'); const response = await this[method] (this.extend (request, query)); const result = ('result' in response) ? response : this.safeValue (response, market['id'], {}); // // spot, margin // // { // "btc-usdt": [ // { // "result":true, // "client_oid":"a123", // "order_id": "2510832677225473" // } // ] // } // // futures, swap // // { // "result": true, // "client_oid": "oktfuture10", // missing if requested by order_id // "order_id": "2517535534836736", // "instrument_id": "EOS-USD-190628" // } // return this.parseOrder (result, market); } parseOrderStatus (status) { const statuses = { '-2': 'failed', '-1': 'canceled', '0': 'open', '1': 'open', '2': 'closed', '3': 'open', '4': 'canceled', }; return this.safeString (statuses, status, status); } parseOrderSide (side) { const sides = { '1': 'buy', // open long '2': 'sell', // open short '3': 'sell', // close long '4': 'buy', // close short }; return this.safeString (sides, side, side); } parseOrder (order, market = undefined) { // // createOrder // // { // "client_oid":"oktspot79", // "error_code":"", // "error_message":"", // "order_id":"2510789768709120", // "result":true // } // // cancelOrder // // { // "result": true, // "client_oid": "oktfuture10", // missing if requested by order_id // "order_id": "2517535534836736", // // instrument_id is missing for spot/margin orders // // available in futures and swap orders only // "instrument_id": "EOS-USD-190628", // } // // fetchOrder, fetchOrdersByState, fetchOpenOrders, fetchClosedOrders // // // spot and margin orders // // { // "client_oid":"oktspot76", // "created_at":"2019-03-18T07:26:49.000Z", // "filled_notional":"3.9734", // "filled_size":"0.001", // filled_qty in futures and swap orders // "funds":"", // this is most likely the same as notional // "instrument_id":"BTC-USDT", // "notional":"", // "order_id":"2500723297813504", // "order_type":"0", // "price":"4013", // "product_id":"BTC-USDT", // missing in futures and swap orders // "side":"buy", // "size":"0.001", // "status":"filled", // "state": "2", // "timestamp":"2019-03-18T07:26:49.000Z", // "type":"limit" // } // // // futures and swap orders // // { // "instrument_id":"EOS-USD-190628", // "size":"10", // "timestamp":"2019-03-20T10:04:55.000Z", // "filled_qty":"10", // filled_size in spot and margin orders // "fee":"-0.00841043", // "order_id":"2512669605501952", // "price":"3.668", // "price_avg":"3.567", // missing in spot and margin orders // "status":"2", // "state": "2", // "type":"4", // "contract_val":"10", // "leverage":"10", // missing in swap, spot and margin orders // "client_oid":"", // "pnl":"1.09510794", // missing in swap, spo and margin orders // "order_type":"0" // } // const id = this.safeString (order, 'order_id'); const timestamp = this.parse8601 (this.safeString (order, 'timestamp')); let side = this.safeString (order, 'side'); let type = this.safeString (order, 'type'); if ((side !== 'buy') && (side !== 'sell')) { side = this.parseOrderSide (type); } if ((type !== 'limit') && (type !== 'market')) { if ('pnl' in order) { type = 'futures'; } else { type = 'swap'; } } let symbol = undefined; const marketId = this.safeString (order, 'instrument_id'); if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; } else { symbol = marketId; } if (market !== undefined) { if (symbol === undefined) { symbol = market['symbol']; } } let amount = this.safeFloat (order, 'size'); const filled = this.safeFloat2 (order, 'filled_size', 'filled_qty'); let remaining = undefined; if (amount !== undefined) { if (filled !== undefined) { amount = Math.max (amount, filled); remaining = Math.max (0, amount - filled); } } if (type === 'market') { remaining = 0; } let cost = this.safeFloat2 (order, 'filled_notional', 'funds'); const price = this.safeFloat (order, 'price'); let average = this.safeFloat (order, 'price_avg'); if (cost === undefined) { if (filled !== undefined && average !== undefined) { cost = average * filled; } } else { if ((average === undefined) && (filled !== undefined) && (filled > 0)) { average = cost / filled; } } const status = this.parseOrderStatus (this.safeString (order, 'state')); const feeCost = this.safeFloat (order, 'fee'); let fee = undefined; if (feeCost !== undefined) { const feeCurrency = undefined; fee = { 'cost': feeCost, 'currency': feeCurrency, }; } return { 'info': order, 'id': id, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'average': average, 'cost': cost, 'amount': amount, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': fee, }; } async fetchOrder (id, symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrder requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const defaultType = this.safeString2 (this.options, 'fetchOrder', 'defaultType', market['type']); const type = this.safeString (params, 'type', defaultType); if (type === undefined) { throw new ArgumentsRequired (this.id + " fetchOrder requires a type parameter (one of 'spot', 'margin', 'futures', 'swap')."); } const instrumentId = (market['futures'] || market['swap']) ? 'InstrumentId' : ''; let method = type + 'GetOrders' + instrumentId; const request = { 'instrument_id': market['id'], // 'client_oid': 'abcdef12345', // optional, [a-z0-9]{1,32} // 'order_id': id, }; const clientOid = this.safeString (params, 'client_oid'); if (clientOid !== undefined) { method += 'ClientOid'; request['client_oid'] = clientOid; } else { method += 'OrderId'; request['order_id'] = id; } const query = this.omit (params, 'type'); const response = await this[method] (this.extend (request, query)); // // spot, margin // // { // "client_oid":"oktspot70", // "created_at":"2019-03-15T02:52:56.000Z", // "filled_notional":"3.8886", // "filled_size":"0.001", // "funds":"", // "instrument_id":"BTC-USDT", // "notional":"", // "order_id":"2482659399697408", // "order_type":"0", // "price":"3927.3", // "product_id":"BTC-USDT", // "side":"buy", // "size":"0.001", // "status":"filled", // "state": "2", // "timestamp":"2019-03-15T02:52:56.000Z", // "type":"limit" // } // // futures, swap // // { // "instrument_id":"EOS-USD-190628", // "size":"10", // "timestamp":"2019-03-20T02:46:38.000Z", // "filled_qty":"10", // "fee":"-0.0080819", // "order_id":"2510946213248000", // "price":"3.712", // "price_avg":"3.712", // "status":"2", // "state": "2", // "type":"2", // "contract_val":"10", // "leverage":"10", // "client_oid":"", // missing in swap orders // "pnl":"0", // missing in swap orders // "order_type":"0" // } // return this.parseOrder (response); } async fetchOrdersByState (state, symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrdersByState requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); const type = market['type']; const request = { 'instrument_id': market['id'], // '-2': failed, // '-1': cancelled, // '0': open , // '1': partially filled, // '2': fully filled, // '3': submitting, // '4': cancelling, // '6': incomplete(open+partially filled), // '7': complete(cancelled+fully filled), 'state': state, }; let method = type + 'GetOrders'; if (market['futures'] || market['swap']) { method += 'InstrumentId'; } const query = this.omit (params, 'type'); const response = await this[method] (this.extend (request, query)); // // spot, margin // // [ // // in fact, this documented API response does not correspond // // to their actual API response for spot markets // // OKEX v3 API returns a plain array of orders (see below) // [ // { // "client_oid":"oktspot76", // "created_at":"2019-03-18T07:26:49.000Z", // "filled_notional":"3.9734", // "filled_size":"0.001", // "funds":"", // "instrument_id":"BTC-USDT", // "notional":"", // "order_id":"2500723297813504", // "order_type":"0", // "price":"4013", // "product_id":"BTC-USDT", // "side":"buy", // "size":"0.001", // "status":"filled", // "state": "2", // "timestamp":"2019-03-18T07:26:49.000Z", // "type":"limit" // }, // ], // { // "before":"2500723297813504", // "after":"2500650881647616" // } // ] // // futures, swap // // { // "result":true, // missing in swap orders // "order_info": [ // { // "instrument_id":"EOS-USD-190628", // "size":"10", // "timestamp":"2019-03-20T10:04:55.000Z", // "filled_qty":"10", // "fee":"-0.00841043", // "order_id":"2512669605501952", // "price":"3.668", // "price_avg":"3.567", // "status":"2", // "state": "2", // "type":"4", // "contract_val":"10", // "leverage":"10", // missing in swap orders // "client_oid":"", // "pnl":"1.09510794", // missing in swap orders // "order_type":"0" // }, // ] // } // let orders = undefined; if (market['type'] === 'swap' || market['type'] === 'futures') { orders = this.safeValue (response, 'order_info', []); } else { orders = response; const responseLength = response.length; if (responseLength < 1) { return []; } // in fact, this documented API response does not correspond // to their actual API response for spot markets // OKEX v3 API returns a plain array of orders if (responseLength > 1) { const before = this.safeValue (response[1], 'before'); if (before !== undefined) { orders = response[0]; } } } return this.parseOrders (orders, market, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { // '-2': failed, // '-1': cancelled, // '0': open , // '1': partially filled, // '2': fully filled, // '3': submitting, // '4': cancelling, // '6': incomplete(open+partially filled), // '7': complete(cancelled+fully filled), return await this.fetchOrdersByState ('6', symbol, since, limit, params); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { // '-2': failed, // '-1': cancelled, // '0': open , // '1': partially filled, // '2': fully filled, // '3': submitting, // '4': cancelling, // '6': incomplete(open+partially filled), // '7': complete(cancelled+fully filled), return await this.fetchOrdersByState ('7', symbol, since, limit, params); } parseDepositAddresses (addresses) { const result = []; for (let i = 0; i < addresses.length; i++) { result.push (this.parseDepositAddress (addresses[i])); } return result; } parseDepositAddress (depositAddress, currency = undefined) { // // { // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', // currency: 'eth' // tag: 'abcde12345', // will be missing if the token does not require a deposit tag // payment_id: 'abcde12345', // will not be returned if the token does not require a payment_id // // can_deposit: 1, // 0 or 1, documented but missing // // can_withdraw: 1, // 0 or 1, documented but missing // } // const address = this.safeString (depositAddress, 'address'); let tag = this.safeString2 (depositAddress, 'tag', 'payment_id'); tag = this.safeString (depositAddress, 'memo', tag); const currencyId = this.safeString (depositAddress, 'currency'); const code = this.safeCurrencyCode (currencyId); this.checkAddress (address); return { 'currency': code, 'address': address, 'tag': tag, 'info': depositAddress, }; } async fetchDepositAddress (code, params = {}) { await this.loadMarkets (); const currency = this.currency (code); const request = { 'currency': currency['id'], }; const response = await this.accountGetDepositAddress (this.extend (request, params)); // // [ // { // address: '0x696abb81974a8793352cbd33aadcf78eda3cfdfa', // currency: 'eth' // } // ] // const addresses = this.parseDepositAddresses (response); const numAddresses = addresses.length; if (numAddresses < 1) { throw new InvalidAddress (this.id + ' fetchDepositAddress cannot return nonexistent addresses, you should create withdrawal addresses with the exchange website first'); } return addresses[0]; } async withdraw (code, amount, address, tag = undefined, params = {}) { this.checkAddress (address); await this.loadMarkets (); const currency = this.currency (code); if (tag) { address = address + ':' + tag; } const fee = this.safeString (params, 'fee'); if (fee === undefined) { throw new ExchangeError (this.id + " withdraw() requires a `fee` string parameter, network transaction fee must be ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set '0'. Withdrawing to external digital asset address requires network transaction fee."); } const request = { 'currency': currency['id'], 'to_address': address, 'destination': '4', // 2 = OKCoin International, 3 = OKEx 4 = others 'amount': this.numberToString (amount), 'fee': fee, // String. Network transaction fee ≥ 0. Withdrawals to OKCoin or OKEx are fee-free, please set as 0. Withdrawal to external digital asset address requires network transaction fee. }; if ('password' in params) { request['trade_pwd'] = params['password']; } else if ('trade_pwd' in params) { request['trade_pwd'] = params['trade_pwd']; } else if (this.password) { request['trade_pwd'] = this.password; } const query = this.omit (params, [ 'fee', 'password', 'trade_pwd' ]); if (!('trade_pwd' in request)) { throw new ExchangeError (this.id + ' withdraw() requires this.password set on the exchange instance or a password / trade_pwd parameter'); } const response = await this.accountPostWithdrawal (this.extend (request, query)); // // { // "amount":"0.1", // "withdrawal_id":"67485", // "currency":"btc", // "result":true // } // return { 'info': response, 'id': this.safeString (response, 'withdrawal_id'), }; } async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = {}; let method = 'accountGetDepositHistory'; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['code'] = currency['code']; method += 'Currency'; } const response = await this[method] (this.extend (request, params)); return this.parseTransactions (response, currency, since, limit, params); } async fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = {}; let method = 'accountGetWithdrawalHistory'; let currency = undefined; if (code !== undefined) { currency = this.currency (code); request['code'] = currency['code']; method += 'Currency'; } const response = await this[method] (this.extend (request, params)); return this.parseTransactions (response, currency, since, limit, params); } parseTransactionStatus (status) { // // deposit statuses // // { // '0': 'waiting for confirmation', // '1': 'confirmation account', // '2': 'recharge success' // } // // withdrawal statues // // { // '-3': 'pending cancel', // '-2': 'cancelled', // '-1': 'failed', // '0': 'pending', // '1': 'sending', // '2': 'sent', // '3': 'email confirmation', // '4': 'manual confirmation', // '5': 'awaiting identity confirmation' // } // const statuses = { '-3': 'pending', '-2': 'pending', '-1': 'failed', '0': 'pending', '1': 'pending', '2': 'ok', '3': 'pending', '4': 'pending', '5': 'pending', }; return this.safeString (statuses, status, status); } parseTransaction (transaction, currency = undefined) { // // withdraw // // { // "amount":"0.1", // "withdrawal_id":"67485", // "currency":"btc", // "result":true // } // // fetchWithdrawals // // { // amount: "4.72100000", // withdrawal_id: "1729116", // fee: "0.01000000eth", // txid: "0xf653125bbf090bcfe4b5e8e7b8f586a9d87aa7de94598702758c0802b…", // currency: "ETH", // from: "7147338839", // to: "0x26a3CB49578F07000575405a57888681249c35Fd", // timestamp: "2018-08-17T07:03:42.000Z", // status: "2" // } // // fetchDeposits // // { // amount: "0.47847546", // txid: "1723573_3_0_0_WALLET", // currency: "BTC", // to: "", // timestamp: "2018-08-16T03:41:10.000Z", // status: "2" // } // let type = undefined; let id = undefined; let address = undefined; const withdrawalId = this.safeString (transaction, 'withdrawal_id'); const addressFrom = this.safeString (transaction, 'from'); const addressTo = this.safeString (transaction, 'to'); if (withdrawalId !== undefined) { type = 'withdrawal'; id = withdrawalId; address = addressTo; } else { // the payment_id will appear on new deposits but appears to be removed from the response after 2 months id = this.safeString (transaction, 'payment_id'); type = 'deposit'; address = addressTo; } const currencyId = this.safeString (transaction, 'currency'); const code = this.safeCurrencyCode (currencyId); const amount = this.safeFloat (transaction, 'amount'); const status = this.parseTransactionStatus (this.safeString (transaction, 'status')); const txid = this.safeString (transaction, 'txid'); const timestamp = this.parse8601 (this.safeString (transaction, 'timestamp')); let feeCost = undefined; if (type === 'deposit') { feeCost = 0; } else { if (currencyId !== undefined) { const feeWithCurrencyId = this.safeString (transaction, 'fee'); if (feeWithCurrencyId !== undefined) { // https://github.com/ccxt/ccxt/pull/5748 const lowercaseCurrencyId = currencyId.toLowerCase (); const feeWithoutCurrencyId = feeWithCurrencyId.replace (lowercaseCurrencyId, ''); feeCost = parseFloat (feeWithoutCurrencyId); } } } // todo parse tags return { 'info': transaction, 'id': id, 'currency': code, 'amount': amount, 'addressFrom': addressFrom, 'addressTo': addressTo, 'address': address, 'tagFrom': undefined, 'tagTo': undefined, 'tag': undefined, 'status': status, 'type': type, 'updated': undefined, 'txid': txid, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'fee': { 'currency': code, 'cost': feeCost, }, }; } async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { // okex actually returns ledger entries instead of fills here, so each fill in the order // is represented by two trades with opposite buy/sell sides, not one :\ // this aspect renders the 'fills' endpoint unusable for fetchOrderTrades // until either OKEX fixes the API or we workaround this on our side somehow if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrderTrades requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); if ((limit === undefined) || (limit > 100)) { limit = 100; } const request = { 'instrument_id': market['id'], 'order_id': id, // from: '1', // return the page after the specified page number // to: '1', // return the page before the specified page number 'limit': limit, // optional, number of results per request, default = maximum = 100 }; const defaultType = this.safeString2 (this.options, 'fetchMyTrades', 'defaultType'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); const method = type + 'GetFills'; const response = await this[method] (this.extend (request, query)); // // spot trades, margin trades // // [ // { // "created_at":"2019-09-20T07:15:24.000Z", // "exec_type":"T", // "fee":"0", // "instrument_id":"ETH-USDT", // "ledger_id":"7173486113", // "liquidity":"T", // "order_id":"3553868136523776", // "price":"217.59", // "product_id":"ETH-USDT", // "side":"sell", // "size":"0.04619899", // "timestamp":"2019-09-20T07:15:24.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 // } // ] // return this.parseTrades (response, market, since, limit); } async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const defaultType = this.safeString2 (this.options, 'fetchLedger', 'defaultType'); const type = this.safeString (params, 'type', defaultType); const query = this.omit (params, 'type'); const suffix = (type === 'account') ? '' : 'Accounts'; let argument = ''; const request = { // 'from': 'id', // 'to': 'id', }; if (limit !== undefined) { request['limit'] = limit; } let currency = undefined; if ((type === 'spot') || (type === 'futures')) { if (code === undefined) { throw new ArgumentsRequired (this.id + " fetchLedger requires a currency code argument for '" + type + "' markets"); } argument = 'Currency'; currency = this.currency (code); request['currency'] = currency['id']; } else if ((type === 'margin') || (type === 'swap')) { if (code === undefined) { throw new ArgumentsRequired (this.id + " fetchLedger requires a code argument (a market symbol) for '" + type + "' markets"); } argument = 'InstrumentId'; const market = this.market (code); // we intentionally put a market inside here for the margin and swap ledgers currency = this.currency (market['base']); request['instrument_id'] = market['id']; // // if (type === 'margin') { // // // // 3. Borrow // // 4. Repayment // // 5. Interest // // 7. Buy // // 8. Sell // // 9. From capital account // // 10. From C2C // // 11. From Futures // // 12. From Spot // // 13. From ETT // // 14. To capital account // // 15. To C2C // // 16. To Spot // // 17. To Futures // // 18. To ETT // // 19. Mandatory Repayment // // 20. From Piggybank // // 21. To Piggybank // // 22. From Perpetual // // 23. To Perpetual // // 24. Liquidation Fee // // 54. Clawback // // 59. Airdrop Return. // // // request['type'] = 'number'; // All types will be returned if this filed is left blank // } // } else if (type === 'account') { if (code !== undefined) { currency = this.currency (code); request['currency'] = currency['id']; } // // // // // 1. deposit // // 2. withdrawal // // 13. cancel withdrawal // // 18. into futures account // // 19. out of futures account // // 20. into sub account // // 21. out of sub account // // 28. claim // // 29. into ETT account // // 30. out of ETT account // // 31. into C2C account // // 32. out of C2C account // // 33. into margin account // // 34. out of margin account // // 37. into spot account // // 38. out of spot account // // // request['type'] = 'number'; // } else { throw new NotSupported (this.id + " fetchLedger does not support the '" + type + "' type (the type must be one of 'account', 'spot', 'margin', 'futures', 'swap')"); } const method = type + 'Get' + suffix + argument + 'Ledger'; const response = await this[method] (this.extend (request, query)); // // transfer funds transfer in/out // trade funds moved as a result of a trade, spot and margin accounts only // rebate fee rebate as per fee schedule, spot and margin accounts only // match open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) // fee fee, futures only // settlement settlement/clawback/settle long/settle short // liquidation force close long/force close short/deliver close long/deliver close short // funding funding fee, swap only // margin a change in the amount after adjusting margin, swap only // // account // // [ // { // "amount":0.00051843, // "balance":0.00100941, // "currency":"BTC", // "fee":0, // "ledger_id":8987285, // "timestamp":"2018-10-12T11:01:14.000Z", // "typename":"Get from activity" // } // ] // // spot // // [ // { // "timestamp":"2019-03-18T07:08:25.000Z", // "ledger_id":"3995334780", // "created_at":"2019-03-18T07:08:25.000Z", // "currency":"BTC", // "amount":"0.0009985", // "balance":"0.0029955", // "type":"trade", // "details":{ // "instrument_id":"BTC-USDT", // "order_id":"2500650881647616", // "product_id":"BTC-USDT" // } // } // ] // // margin // // [ // [ // { // "created_at":"2019-03-20T03:45:05.000Z", // "ledger_id":"78918186", // "timestamp":"2019-03-20T03:45:05.000Z", // "currency":"EOS", // "amount":"0", // ? // "balance":"0.59957711", // "type":"transfer", // "details":{ // "instrument_id":"EOS-USDT", // "order_id":"787057", // "product_id":"EOS-USDT" // } // } // ], // { // "before":"78965766", // "after":"78918186" // } // ] // // futures // // [ // { // "ledger_id":"2508090544914461", // "timestamp":"2019-03-19T14:40:24.000Z", // "amount":"-0.00529521", // "balance":"0", // "currency":"EOS", // "type":"fee", // "details":{ // "order_id":"2506982456445952", // "instrument_id":"EOS-USD-190628" // } // } // ] // // swap // // [ // { // "amount":"0.004742", // "fee":"-0.000551", // "type":"match", // "instrument_id":"EOS-USD-SWAP", // "ledger_id":"197429674941902848", // "timestamp":"2019-03-25T05:56:31.286Z" // }, // ] // const isArray = Array.isArray (response[0]); const isMargin = (type === 'margin'); const entries = (isMargin && isArray) ? response[0] : response; return this.parseLedger (entries, currency, since, limit); } parseLedgerEntryType (type) { const types = { 'transfer': 'transfer', // // funds transfer in/out 'trade': 'trade', // funds moved as a result of a trade, spot and margin accounts only 'rebate': 'rebate', // fee rebate as per fee schedule, spot and margin accounts only 'match': 'trade', // open long/open short/close long/close short (futures) or a change in the amount because of trades (swap) 'fee': 'fee', // fee, futures only 'settlement': 'trade', // settlement/clawback/settle long/settle short 'liquidation': 'trade', // force close long/force close short/deliver close long/deliver close short 'funding': 'fee', // funding fee, swap only 'margin': 'margin', // a change in the amount after adjusting margin, swap only }; return this.safeString (types, type, type); } parseLedgerEntry (item, currency = undefined) { // // // account // // { // "amount":0.00051843, // "balance":0.00100941, // "currency":"BTC", // "fee":0, // "ledger_id":8987285, // "timestamp":"2018-10-12T11:01:14.000Z", // "typename":"Get from activity" // } // // spot // // { // "timestamp":"2019-03-18T07:08:25.000Z", // "ledger_id":"3995334780", // "created_at":"2019-03-18T07:08:25.000Z", // "currency":"BTC", // "amount":"0.0009985", // "balance":"0.0029955", // "type":"trade", // "details":{ // "instrument_id":"BTC-USDT", // "order_id":"2500650881647616", // "product_id":"BTC-USDT" // } // } // // margin // // { // "created_at":"2019-03-20T03:45:05.000Z", // "ledger_id":"78918186", // "timestamp":"2019-03-20T03:45:05.000Z", // "currency":"EOS", // "amount":"0", // ? // "balance":"0.59957711", // "type":"transfer", // "details":{ // "instrument_id":"EOS-USDT", // "order_id":"787057", // "product_id":"EOS-USDT" // } // } // // futures // // { // "ledger_id":"2508090544914461", // "timestamp":"2019-03-19T14:40:24.000Z", // "amount":"-0.00529521", // "balance":"0", // "currency":"EOS", // "type":"fee", // "details":{ // "order_id":"2506982456445952", // "instrument_id":"EOS-USD-190628" // } // } // // swap // // { // "amount":"0.004742", // "fee":"-0.000551", // "type":"match", // "instrument_id":"EOS-USD-SWAP", // "ledger_id":"197429674941902848", // "timestamp":"2019-03-25T05:56:31.286Z" // }, // const id = this.safeString (item, 'ledger_id'); const account = undefined; const details = this.safeValue (item, 'details', {}); const referenceId = this.safeString (details, 'order_id'); const referenceAccount = undefined; const type = this.parseLedgerEntryType (this.safeString (item, 'type')); const code = this.safeCurrencyCode (this.safeString (item, 'currency'), currency); const amount = this.safeFloat (item, 'amount'); const timestamp = this.parse8601 (this.safeString (item, 'timestamp')); const fee = { 'cost': this.safeFloat (item, 'fee'), 'currency': code, }; const before = undefined; const after = this.safeFloat (item, 'balance'); const status = 'ok'; return { 'info': item, 'id': id, 'account': account, 'referenceId': referenceId, 'referenceAccount': referenceAccount, 'type': type, 'currency': code, 'amount': amount, 'before': before, // balance before 'after': after, // balance after 'status': status, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'fee': fee, }; } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { const isArray = Array.isArray (params); let request = '/api/' + api + '/' + this.version + '/'; request += isArray ? path : this.implodeParams (path, params); const query = isArray ? params : this.omit (params, this.extractParams (path)); let url = this.urls['api'] + request; const type = this.getPathAuthenticationType (path); if (type === 'public') { if (Object.keys (query).length) { url += '?' + this.urlencode (query); } } else if (type === 'private') { this.checkRequiredCredentials (); const timestamp = this.iso8601 (this.milliseconds ()); headers = { 'OK-ACCESS-KEY': this.apiKey, 'OK-ACCESS-PASSPHRASE': this.password, 'OK-ACCESS-TIMESTAMP': timestamp, // 'OK-FROM': '', // 'OK-TO': '', // 'OK-LIMIT': '', }; let auth = timestamp + method + request; if (method === 'GET') { if (Object.keys (query).length) { const urlencodedQuery = '?' + this.urlencode (query); url += urlencodedQuery; auth += urlencodedQuery; } } else { if (isArray || Object.keys (query).length) { body = this.json (query); auth += body; } headers['Content-Type'] = 'application/json'; } const signature = this.hmac (this.encode (auth), this.encode (this.secret), 'sha256', 'base64'); headers['OK-ACCESS-SIGN'] = this.decode (signature); } return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } getPathAuthenticationType (path) { const auth = this.safeValue (this.options, 'auth', {}); const key = this.findBroadlyMatchedKey (auth, path); return this.safeString (auth, key, 'private'); } handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { const feedback = this.id + ' ' + body; if (code === 503) { throw new ExchangeError (feedback); } if (!response) { return; // fallback to default error handler } const message = this.safeString (response, 'message'); const errorCode = this.safeString2 (response, 'code', 'error_code'); if (message !== undefined) { this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); } this.throwExactlyMatchedException (this.exceptions['exact'], errorCode, feedback); if (message !== undefined) { throw new ExchangeError (feedback); // unknown message } } };