├── .gitignore ├── README.md ├── cfRestApiV3.py └── cfRestApiV3Examples.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Crypto Facilities REST API v3 2 | ================================== 3 | This is a sample RESTful application for [Crypto Facilities Ltd](https://www.cryptofacilities.com/), to demonstrate 4 | the REST v3 API. 5 | 6 | 7 | Getting Started 8 | --------------- 9 | 10 | 1. Amend the `cfRestApiV3Examples.py` script to enter your api keys and other details 11 | 1. Run the `cfRestApiV3Examples.py` script after you check the example script actions 12 | 13 | Functionality Overview 14 | ---------------------- 15 | 16 | * This application makes a REST call to every Crypto Facilities endpoint 17 | 18 | Application Sample Output 19 | ------------------------- 20 | 21 | The following is some of what you can expect when running this application: 22 | 23 | ``` 24 | get_instruments: 25 | {'result': 'success', 'instruments': [{'symbol': 'fi_xbtusd_180606', 'type': 'futures_inverse', 'underlying': 'rr_xbtusd', 'lastTradingTime': '2018-06-06T16:00:00.000Z', 'tickSize': 1, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fi_xrpusd_180606', 'type': 'futures_inverse', 'underlying': 'rr_xrpusd', 'lastTradingTime': '2018-06-06T16:00:00.000Z', 'tickSize': 0.0001, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fv_xrpxbt_180606', 'type': 'futures_vanilla', 'underlying': 'rr_xrpxbt', 'lastTradingTime': '2018-06-06T16:00:00.000Z', 'tickSize': 1e-07, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fi_ethusd_180606', 'type': 'futures_inverse', 'underlying': 'rr_ethusd', 'lastTradingTime': '2018-06-06T16:00:00.000Z', 'tickSize': 0.01, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fi_xbtusd_180519', 'type': 'futures_inverse', 'underlying': 'rr_xbtusd', 'lastTradingTime': '2018-05-19T16:00:00.000Z', 'tickSize': 1, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fi_xrpusd_180519', 'type': 'futures_inverse', 'underlying': 'rr_xrpusd', 'lastTradingTime': '2018-05-19T16:00:00.000Z', 'tickSize': 0.0001, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fv_xrpxbt_180519', 'type': 'futures_vanilla', 'underlying': 'rr_xrpxbt', 'lastTradingTime': '2018-05-19T16:00:00.000Z', 'tickSize': 1e-07, 'contractSize': 1, 'tradeable': True}, {'symbol': 'fi_ethusd_180519', 'type': 'futures_inverse', 'underlying': 'rr_ethusd', 'lastTradingTime': '2018-05-19T16:00:00.000Z', 'tickSize': 0.01, 'contractSize': 1, 'tradeable': True}, {'symbol': 'rr_xbtusd', 'type': 'spot index', 'tradeable': False}, {'symbol': 'in_xbtusd', 'type': 'spot index', 'tradeable': False}, {'symbol': 'rr_xrpusd', 'type': 'spot index', 'tradeable': False}, {'symbol': 'in_xrpusd', 'type': 'spot index', 'tradeable': False}, {'symbol': 'rr_xrpxbt', 'type': 'spot index', 'tradeable': False}, {'symbol': 'in_xrpxbt', 'type': 'spot index', 'tradeable': False}, {'symbol': 'rr_ethusd', 'type': 'spot index', 'tradeable': False}, {'symbol': 'in_ethusd', 'type': 'spot index', 'tradeable': False}], 'serverTime': '2018-05-09T15:50:45.543Z'} 26 | get_tickers: 27 | {'result': 'success', 'tickers': [{'symbol': 'fi_xrpusd_180519', 'markPrice': 0.8017, 'vol24h': 0, 'suspended': False}, {'symbol': 'fi_ethusd_180606', 'markPrice': 749.33, 'vol24h': 0, 'suspended': False}, {'symbol': 'fi_ethusd_180519', 'markPrice': 749.33, 'vol24h': 0, 'suspended': False}, {'symbol': 'fi_xbtusd_180606', 'markPrice': 9292, 'vol24h': 0, 'suspended': False}, {'symbol': 'fv_xrpxbt_180519', 'markPrice': 8.62e-05, 'vol24h': 0, 'suspended': False}, {'symbol': 'fi_xrpusd_180606', 'markPrice': 0.8017, 'vol24h': 0, 'suspended': False}, {'symbol': 'fv_xrpxbt_180606', 'markPrice': 8.62e-05, 'vol24h': 0, 'suspended': False}, {'symbol': 'fi_xbtusd_180519', 'markPrice': 9292, 'bid': 5640, 'bidSize': 100, 'vol24h': 600, 'open24h': 6800, 'last': 5640, 'lastTime': '2018-05-09T14:28:15.275Z', 'lastSize': 100, 'suspended': False}, {'symbol': 'in_xbtusd', 'last': 9288, 'lastTime': '2018-05-09T15:50:41.000Z'}, {'symbol': 'in_xrpusd', 'last': 0.8013, 'lastTime': '2018-05-09T15:50:41.000Z'}, {'symbol': 'in_ethusd', 'last': 748.96, 'lastTime': '2018-05-09T15:50:41.000Z'}, {'symbol': 'in_ltcusd', 'last': 158.29, 'lastTime': '2018-05-09T15:50:41.000Z'}, {'symbol': 'in_xrpxbt', 'last': 8.613e-05, 'lastTime': '2018-05-09T15:50:41.000Z'}, {'symbol': 'rr_xbtusd', 'last': 9269, 'lastTime': '2018-05-09T15:00:00.000Z'}, {'symbol': 'rr_xrpusd', 'last': 0.7937, 'lastTime': '2018-05-09T15:00:00.000Z'}, {'symbol': 'rr_ethusd', 'last': 744.53, 'lastTime': '2018-05-09T15:00:00.000Z'}, {'symbol': 'rr_ltcusd', 'last': 156.57, 'lastTime': '2018-05-09T15:00:00.000Z'}, {'symbol': 'rr_xrpxbt', 'last': 8.563e-05, 'lastTime': '2018-05-09T15:00:00.000Z'}], 'serverTime': '2018-05-09T15:50:45.548Z'} 28 | get_orderbook: 29 | {'result': 'success', 'orderBook': {'bids': [[5640, 100], [5620, 1190], [1, 3]], 'asks': []}, 'serverTime': '2018-05-09T15:50:45.561Z'} 30 | get_history: 31 | {'result': 'success', 'history': [], 'serverTime': '2018-05-09T15:50:45.570Z'} 32 | get_accounts: 33 | {'result': 'success', 'accounts': {'fi_xbtusd': {'type': 'marginAccount', 'auxiliary': {'usd': 0, 'pv': 0.40321572134, 'pnl': -0.00480628374, 'af': 0.39955674003}, 'marginRequirements': {'im': 0.00365898131, 'mm': 0.001829490655, 'lt': 0.001829490655, 'tt': 0.001829490655}, 'triggerEstimates': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'balances': {'xbt': 0.40802200508, 'xrp': 0, 'eth': 0, 'fi_xbtusd_180519': -1700}, 'currency': 'xbt'}, 'cash': {'type': 'cashAccount', 'balances': {'xbt': 0, 'xrp': 0, 'eth': 0}}, 'fv_xrpxbt': {'type': 'marginAccount', 'auxiliary': {'usd': 0, 'pv': 0.3200027937, 'pnl': 0, 'af': 0.3200027937}, 'marginRequirements': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'triggerEstimates': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'balances': {'xbt': 0.3200027937, 'xrp': 0, 'eth': 0}, 'currency': 'xbt'}, 'fi_xrpusd': {'type': 'marginAccount', 'auxiliary': {'usd': 0, 'pv': 3512.22578455212, 'pnl': 0, 'af': 3512.22578455212}, 'marginRequirements': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'triggerEstimates': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'balances': {'xrp': 3512.22578455212, 'xbt': 0, 'eth': 0}, 'currency': 'xrp'}, 'fi_ethusd': {'type': 'marginAccount', 'auxiliary': {'usd': 0, 'pv': 4.36367445959, 'pnl': 0, 'af': 4.36367445959}, 'marginRequirements': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'triggerEstimates': {'im': 0, 'mm': 0, 'lt': 0, 'tt': 0}, 'balances': {'eth': 4.36367445959, 'xrp': 0, 'xbt': 0}, 'currency': 'eth'}}, 'serverTime': '2018-05-09T15:50:45.583Z'} 34 | send_order (limit): 35 | {'result': 'success', 'serverTime': '2018-05-09T15:50:45.598Z', 'sendStatus': {'status': 'tooManySmallOrders'}} 36 | send_order (limit) with client id: 37 | {'result': 'success', 'serverTime': '2018-05-09T15:50:45.607Z', 'sendStatus': {'status': 'tooManySmallOrders'}} 38 | send_order (stop): 39 | {'result': 'success', 'serverTime': '2018-05-09T15:50:45.621Z', 'sendStatus': {'status': 'tooManySmallOrders'}} 40 | cancel_order: 41 | {'result': 'success', 'cancelStatus': {'status': 'notFound', 'receivedTime': '2018-05-09T15:50:45.637Z'}, 'serverTime': '2018-05-09T15:50:45.637Z'} 42 | send_batchorder: 43 | {'result': 'success', 'batchStatus': [{'order_tag': '1', 'status': 'clientOrderIdAlreadyExist'}, {'order_tag': '2', 'status': 'tooManySmallOrders'}, {'order_id': 'e35d61dd-8a30-4d5f-a574-b5593ef0c050', 'status': 'notFound'}, {'status': 'notFound'}], 'serverTime': '2018-05-09T15:50:45.653Z'} 44 | get_openorders: 45 | {'result': 'success', 'openOrders': [{'order_id': '635cd703-db52-4ae9-8c19-858dbb1b0578', 'symbol': 'fi_xbtusd_180519', 'side': 'buy', 'orderType': 'lmt', 'limitPrice': 1, 'unfilledSize': 1, 'receivedTime': '2018-05-09T15:36:13.962Z', 'status': 'untouched', 'filledSize': 0}, {'order_id': 'e05ad9d1-10c7-4e26-a6bc-e0d96c174068', 'cliOrdId': 'my_another_client_id', 'symbol': 'fi_xbtusd_180519', 'side': 'buy', 'orderType': 'lmt', 'limitPrice': 1, 'unfilledSize': 1, 'receivedTime': '2018-05-09T15:32:19.516Z', 'status': 'untouched', 'filledSize': 0}, {'order_id': 'd427f920-ec55-4c18-ba95-5fe241513b30', 'cliOrdId': 'hello id', 'symbol': 'fi_xbtusd_180519', 'side': 'buy', 'orderType': 'lmt', 'limitPrice': 5640, 'unfilledSize': 100, 'receivedTime': '2018-05-09T14:28:12.287Z', 'status': 'partiallyFilled', 'filledSize': 100}, {'order_id': '1313a0af-f2ae-4db3-ac27-ee9ae109fcd3', 'cliOrdId': '36825274-4c2f-4fb4-acd9-583f897d2b3f', 'symbol': 'fi_xbtusd_180519', 'side': 'buy', 'orderType': 'lmt', 'limitPrice': 5620, 'unfilledSize': 200, 'receivedTime': '2018-05-09T14:25:38.064Z', 'status': 'untouched', 'filledSize': 0}], 'serverTime': '2018-05-09T15:50:45.668Z'} 46 | get_fills: 47 | {'result': 'success', 'fills': [], 'serverTime': '2018-05-09T15:50:45.682Z'} 48 | get_openpositions: 49 | {'result': 'success', 'openPositions': [{'side': 'short', 'symbol': 'fi_xbtusd_180519', 'price': 9054.335894621296, 'fillTime': '2018-05-09T15:50:45.698Z', 'size': 1700}], 'serverTime': '2018-05-09T15:50:45.698Z'} 50 | get_transfers: 51 | {'result': 'success', 'transfers': [], 'serverTime': '2018-05-09T15:50:45.723Z'} 52 | ``` -------------------------------------------------------------------------------- /cfRestApiV3.py: -------------------------------------------------------------------------------- 1 | # Crypto Facilities Ltd REST API v3 2 | 3 | # Copyright (c) 2018 Crypto Facilities 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining 6 | # a copy of this software and associated documentation files (the "Software"), 7 | # to deal in the Software without restriction, including without limitation 8 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | # and/or sell copies of the Software, and to permit persons to whom the 10 | # Software is furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included 13 | # in all copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 20 | # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | import time 23 | import base64 24 | import hashlib 25 | import hmac 26 | import json 27 | import urllib.request as urllib2 28 | import urllib.parse as urllib 29 | import ssl 30 | 31 | 32 | class cfApiMethods(object): 33 | def __init__(self, apiPath, apiPublicKey="", apiPrivateKey="", timeout=10, checkCertificate=True, useNonce=False): 34 | self.apiPath = apiPath 35 | self.apiPublicKey = apiPublicKey 36 | self.apiPrivateKey = apiPrivateKey 37 | self.timeout = timeout 38 | self.nonce = 0 39 | self.checkCertificate = checkCertificate 40 | self.useNonce = useNonce 41 | 42 | ##### public endpoints ##### 43 | 44 | # returns all instruments with specifications 45 | def get_instruments(self): 46 | endpoint = "/derivatives/api/v3/instruments" 47 | return self.make_request("GET", endpoint) 48 | 49 | # returns market data for all instruments 50 | def get_tickers(self): 51 | endpoint = "/derivatives/api/v3/tickers" 52 | return self.make_request("GET", endpoint) 53 | 54 | # returns the entire order book of a futures 55 | def get_orderbook(self, symbol): 56 | endpoint = "/derivatives/api/v3/orderbook" 57 | postUrl = "symbol=%s" % symbol 58 | return self.make_request("GET", endpoint, postUrl=postUrl) 59 | 60 | # returns historical data for futures and indices 61 | def get_history(self, symbol, lastTime=""): 62 | endpoint = "/derivatives/api/v3/history" 63 | if lastTime != "": 64 | postUrl = "symbol=%s&lastTime=%s" % (symbol, lastTime) 65 | else: 66 | postUrl = "symbol=%s" % symbol 67 | return self.make_request("GET", endpoint, postUrl=postUrl) 68 | 69 | ##### private endpoints ##### 70 | 71 | # returns key account information 72 | # Deprecated because it returns info about the Futures margin account 73 | # Use get_accounts instead 74 | def get_account(self): 75 | endpoint = "/derivatives/api/v3/account" 76 | return self.make_request("GET", endpoint) 77 | 78 | # returns key account information 79 | def get_accounts(self): 80 | endpoint = "/derivatives/api/v3/accounts" 81 | return self.make_request("GET", endpoint) 82 | 83 | # places an order 84 | def send_order(self, orderType, symbol, side, size, limitPrice, stopPrice=None, clientOrderId=None): 85 | endpoint = "/derivatives/api/v3/sendorder" 86 | postBody = "orderType=%s&symbol=%s&side=%s&size=%s&limitPrice=%s" % ( 87 | orderType, symbol, side, size, limitPrice) 88 | 89 | if orderType == "stp" and stopPrice is not None: 90 | postBody += "&stopPrice=%s" % stopPrice 91 | 92 | if clientOrderId is not None: 93 | postBody += "&cliOrdId=%s" % clientOrderId 94 | 95 | return self.make_request("POST", endpoint, postBody=postBody) 96 | 97 | # places an order 98 | def send_order_1(self, order): 99 | endpoint = "/derivatives/api/v3/sendorder" 100 | postBody = urllib.urlencode(order) 101 | return self.make_request("POST", endpoint, postBody=postBody) 102 | 103 | # edit an order 104 | def edit_order(self, edit): 105 | endpoint = "/derivatives/api/v3/editorder" 106 | postBody = urllib.urlencode(edit) 107 | return self.make_request("POST", endpoint, postBody=postBody) 108 | 109 | # cancels an order 110 | def cancel_order(self, order_id=None, cli_ord_id=None): 111 | endpoint = "/derivatives/api/v3/cancelorder" 112 | 113 | if order_id is None: 114 | postBody = "cliOrdId=%s" % cli_ord_id 115 | else: 116 | postBody = "order_id=%s" % order_id 117 | 118 | return self.make_request("POST", endpoint, postBody=postBody) 119 | 120 | # cancel all orders 121 | def cancel_all_orders(self, symbol=None): 122 | endpoint = "/derivatives/api/v3/cancelallorders" 123 | if symbol is not None: 124 | postbody = "symbol=%s" % symbol 125 | else: 126 | postbody = "" 127 | 128 | return self.make_request("POST", endpoint, postBody=postbody) 129 | 130 | # cancel all orders after 131 | def cancel_all_orders_after(self, timeoutInSeconds=60): 132 | endpoint = "/derivatives/api/v3/cancelallordersafter" 133 | postbody = "timeout=%s" % timeoutInSeconds 134 | 135 | return self.make_request("POST", endpoint, postBody=postbody) 136 | 137 | # places or cancels orders in batch 138 | def send_batchorder(self, jsonElement): 139 | endpoint = "/derivatives/api/v3/batchorder" 140 | postBody = "json=%s" % jsonElement 141 | return self.make_request("POST", endpoint, postBody=postBody) 142 | 143 | # returns all open orders 144 | def get_openorders(self): 145 | endpoint = "/derivatives/api/v3/openorders" 146 | return self.make_request("GET", endpoint) 147 | 148 | # returns filled orders 149 | def get_fills(self, lastFillTime=""): 150 | endpoint = "/derivatives/api/v3/fills" 151 | if lastFillTime != "": 152 | postUrl = "lastFillTime=%s" % lastFillTime 153 | else: 154 | postUrl = "" 155 | return self.make_request("GET", endpoint, postUrl=postUrl) 156 | 157 | # returns all open positions 158 | def get_openpositions(self): 159 | endpoint = "/derivatives/api/v3/openpositions" 160 | return self.make_request("GET", endpoint) 161 | 162 | # sends an xbt withdrawal request 163 | def send_withdrawal(self, targetAddress, currency, amount): 164 | endpoint = "/derivatives/api/v3/withdrawal" 165 | postBody = "targetAddress=%s¤cy=%s&amount=%s" % ( 166 | targetAddress, currency, amount) 167 | return self.make_request("POST", endpoint, postBody=postBody) 168 | 169 | # returns xbt transfers 170 | def get_transfers(self, lastTransferTime=""): 171 | endpoint = "/derivatives/api/v3/transfers" 172 | if lastTransferTime != "": 173 | postUrl = "lastTransferTime=%s" % lastTransferTime 174 | else: 175 | postUrl = "" 176 | return self.make_request("GET", endpoint, postUrl=postUrl) 177 | 178 | # returns all notifications 179 | def get_notifications(self): 180 | endpoint = "/derivatives/api/v3/notifications" 181 | return self.make_request("GET", endpoint) 182 | 183 | # makes an internal transfer 184 | def transfer(self, fromAccount, toAccount, unit, amount): 185 | endpoint = "/derivatives/api/v3/transfer" 186 | postBody = "fromAccount=%s&toAccount=%s&unit=%s&amount=%s" % ( 187 | fromAccount, toAccount, unit, amount) 188 | return self.make_request("POST", endpoint, postBody=postBody) 189 | 190 | # accountlog csv 191 | def get_accountlog(self): 192 | endpoint = "/api/history/v2/accountlogcsv" 193 | return self.make_request("GET", endpoint) 194 | 195 | def _get_partial_historical_elements(self, elementType, **params): 196 | endpoint = "/api/history/v2/%s" % elementType 197 | 198 | params = {k: v for k, v in params.items() if v is not None} 199 | postUrl = urllib.urlencode(params) 200 | 201 | return self.make_request_raw("GET", endpoint, postUrl) 202 | 203 | def _get_historical_elements(self, elementType, since=None, before=None, sort=None, limit=1000): 204 | elements = [] 205 | 206 | continuationToken = None 207 | 208 | while True: 209 | res = self._get_partial_historical_elements(elementType, since = since, before = before, sort = sort, continuationToken = continuationToken) 210 | body = json.loads(res.read().decode('utf-8')) 211 | elements = elements + body['elements'] 212 | 213 | if res.headers['is-truncated'] is None or res.headers['is-truncated'] == "false": 214 | continuationToken = None 215 | break 216 | else: 217 | continuationToken = res.headers['next-continuation-token'] 218 | 219 | if len(elements) >= limit: 220 | elements = elements[:limit] 221 | break 222 | 223 | return elements 224 | 225 | def get_orders(self, since=None, before=None, sort=None, limit=1000): 226 | """ 227 | Retrieves orders of your account. With default parameters it gets the 1000 newest orders. 228 | 229 | :param since: Timestamp in milliseconds. Retrieves orders starting at this time rather than the newest/latest. 230 | :param before: Timestamp in milliseconds. Retrieves orders before this time. 231 | :param sort: String "asc" or "desc". The sorting of orders. 232 | :param limit: Amount of orders to be retrieved. 233 | :return: List of orders 234 | """ 235 | 236 | return self._get_historical_elements('orders', since, before, sort, limit) 237 | 238 | def get_executions(self, since=None, before=None, sort=None, limit=1000): 239 | """ 240 | Retrieves executions of your account. With default parameters it gets the 1000 newest executions. 241 | 242 | :param since: Timestamp in milliseconds. Retrieves executions starting at this time rather than the newest/latest. 243 | :param before: Timestamp in milliseconds. Retrieves executions before this time. 244 | :param sort: String "asc" or "desc". The sorting of executions. 245 | :param limit: Amount of executions to be retrieved. 246 | :return: List of executions 247 | """ 248 | 249 | return self._get_historical_elements('executions', since, before, sort, limit) 250 | 251 | def get_market_price(self, symbol, since=None, before=None, sort=None, limit=1000): 252 | """ 253 | Retrieves prices of given symbol. With default parameters it gets the 1000 newest prices. 254 | 255 | :param symbol: Name of a symbol. For example "PI_XBTUSD". 256 | :param since: Timestamp in milliseconds. Retrieves prices starting at this time rather than the newest/latest. 257 | :param before: Timestamp in milliseconds. Retrieves prices before this time. 258 | :param sort: String "asc" or "desc". The sorting of prices. 259 | :param limit: Amount of prices to be retrieved. 260 | :return: List of prices 261 | """ 262 | 263 | return self._get_historical_elements('market/' + symbol + '/price', since, before, sort, limit) 264 | 265 | def get_market_orders(self, symbol, since=None, before=None, sort=None, limit=1000): 266 | """ 267 | Retrieves orders of given symbol. With default parameters it gets the 1000 newest orders. 268 | 269 | :param symbol: Name of a symbol. For example "PI_XBTUSD". 270 | :param since: Timestamp in milliseconds. Retrieves orders starting at this time rather than the newest/latest. 271 | :param before: Timestamp in milliseconds. Retrieves orders before this time. 272 | :param sort: String "asc" or "desc". The sorting of orders. 273 | :param limit: Amount of orders to be retrieved. 274 | :return: List of orders 275 | """ 276 | 277 | return self._get_historical_elements('market/' + symbol + '/orders', since, before, sort, limit) 278 | 279 | def get_market_executions(self, symbol, since=None, before=None, sort=None, limit=1000): 280 | """ 281 | Retrieves executions of given symbol. With default parameters it gets the 1000 newest executions. 282 | 283 | :param symbol: Name of a symbol. For example "PI_XBTUSD". 284 | :param since: Timestamp in milliseconds. Retrieves executions starting at this time rather than the newest/latest. 285 | :param before: Timestamp in milliseconds. Retrieves executions before this time. 286 | :param sort: String "asc" or "desc". The sorting of executions. 287 | :param limit: Amount of executions to be retrieved. 288 | :return: List of executions 289 | """ 290 | 291 | return self._get_historical_elements('market/' + symbol + '/executions', since, before, sort, limit) 292 | 293 | # signs a message 294 | def sign_message(self, endpoint, postData, nonce=""): 295 | if endpoint.startswith('/derivatives'): 296 | endpoint = endpoint[len('/derivatives'):] 297 | 298 | # step 1: concatenate postData, nonce + endpoint 299 | message = postData + nonce + endpoint 300 | 301 | # step 2: hash the result of step 1 with SHA256 302 | sha256_hash = hashlib.sha256() 303 | sha256_hash.update(message.encode('utf8')) 304 | hash_digest = sha256_hash.digest() 305 | 306 | # step 3: base64 decode apiPrivateKey 307 | secretDecoded = base64.b64decode(self.apiPrivateKey) 308 | 309 | # step 4: use result of step 3 to has the result of step 2 with HMAC-SHA512 310 | hmac_digest = hmac.new(secretDecoded, hash_digest, 311 | hashlib.sha512).digest() 312 | 313 | # step 5: base64 encode the result of step 4 and return 314 | return base64.b64encode(hmac_digest) 315 | 316 | # creates a unique nonce 317 | def get_nonce(self): 318 | # https://en.wikipedia.org/wiki/Modulo_operation 319 | self.nonce = (self.nonce + 1) & 8191 320 | return str(int(time.time() * 1000)) + str(self.nonce).zfill(4) 321 | 322 | # sends an HTTP request 323 | def make_request_raw(self, requestType, endpoint, postUrl="", postBody=""): 324 | # create authentication headers 325 | postData = postUrl + postBody 326 | 327 | if self.useNonce: 328 | nonce = self.get_nonce() 329 | signature = self.sign_message(endpoint, postData, nonce=nonce) 330 | authentHeaders = {"APIKey": self.apiPublicKey, 331 | "Nonce": nonce, "Authent": signature} 332 | else: 333 | signature = self.sign_message(endpoint, postData) 334 | authentHeaders = { 335 | "APIKey": self.apiPublicKey, "Authent": signature} 336 | 337 | authentHeaders["User-Agent"] = "cf-api-python/1.0" 338 | 339 | # create request 340 | if postUrl != "": 341 | url = self.apiPath + endpoint + "?" + postUrl 342 | else: 343 | url = self.apiPath + endpoint 344 | 345 | request = urllib2.Request(url, str.encode(postBody), authentHeaders) 346 | request.get_method = lambda: requestType 347 | 348 | # read response 349 | if self.checkCertificate: 350 | response = urllib2.urlopen(request, timeout=self.timeout) 351 | else: 352 | ctx = ssl.create_default_context() 353 | ctx.check_hostname = False 354 | ctx.verify_mode = ssl.CERT_NONE 355 | response = urllib2.urlopen( 356 | request, context=ctx, timeout=self.timeout) 357 | 358 | # return 359 | return response 360 | 361 | # sends an HTTP request and read response body 362 | def make_request(self, requestType, endpoint, postUrl="", postBody=""): 363 | return self.make_request_raw(requestType, endpoint, postUrl, postBody).read().decode("utf-8") 364 | -------------------------------------------------------------------------------- /cfRestApiV3Examples.py: -------------------------------------------------------------------------------- 1 | # Crypto Facilities Ltd REST API V3 2 | 3 | # Copyright (c) 2018 Crypto Facilities 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining 6 | # a copy of this software and associated documentation files (the "Software"), 7 | # to deal in the Software without restriction, including without limitation 8 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | # and/or sell copies of the Software, and to permit persons to whom the 10 | # Software is furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included 13 | # in all copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 20 | # IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | import cfRestApiV3 as cfApi 23 | import datetime 24 | 25 | # accessible on your Account page under Settings -> API Keys 26 | apiPublicKey = "..." 27 | # accessible on your Account page under Settings -> API Keys 28 | apiPrivateKey = "..." 29 | 30 | # use "api.cryptofacilities.com" if your IP is whitelisted (Settings -> API Keys -> IP Whitelist) 31 | apiPath = "https://www.cryptofacilities.com" 32 | timeout = 20 33 | checkCertificate = True # when using the test environment, this must be set to "False" 34 | useNonce = False # nonce is optional 35 | 36 | cfPublic = cfApi.cfApiMethods( 37 | apiPath, timeout=timeout, checkCertificate=checkCertificate) 38 | cfPrivate = cfApi.cfApiMethods(apiPath, timeout=timeout, apiPublicKey=apiPublicKey, 39 | apiPrivateKey=apiPrivateKey, checkCertificate=checkCertificate, useNonce=useNonce) 40 | 41 | 42 | def APITester(): 43 | ##### public endpoints ##### 44 | 45 | # get instruments 46 | result = cfPublic.get_instruments() 47 | print("get_instruments:\n", result) 48 | 49 | # get tickers 50 | result = cfPublic.get_tickers() 51 | print("get_tickers:\n", result) 52 | 53 | # get order book 54 | symbol = "PI_XBTUSD" 55 | result = cfPublic.get_orderbook(symbol) 56 | print("get_orderbook:\n", result) 57 | 58 | # get history 59 | symbol = "PI_XBTUSD" # "PI_XBTUSD", "cf-bpi", "cf-hbpi" 60 | lastTime = datetime.datetime.strptime( 61 | "2016-01-20", "%Y-%m-%d").isoformat() + ".000Z" 62 | result = cfPublic.get_history(symbol, lastTime=lastTime) 63 | print("get_history:\n", result) 64 | 65 | # get prices 66 | result = cfPublic.get_market_price(symbol) 67 | print("get_market_price:\n", "%s elements" % len(result)) 68 | 69 | ##### private endpoints ##### 70 | 71 | # get account 72 | result = cfPrivate.get_accounts() 73 | print("get_accounts:\n", result) 74 | 75 | # send limit order 76 | limit_order = { 77 | "orderType": "lmt", 78 | "symbol": "PI_XBTUSD", 79 | "side": "buy", 80 | "size": 1, 81 | "limitPrice": 1.00, 82 | "reduceOnly": "true" 83 | } 84 | result = cfPrivate.send_order_1(limit_order) 85 | print("send_order (limit):\n", result) 86 | 87 | # send stop reduce-only order 88 | stop_order = { 89 | "orderType": "stp", 90 | "symbol": "PI_XBTUSD", 91 | "side": "buy", 92 | "size": 1, 93 | "limitPrice": 1.00, 94 | "stopPrice": 2.00, 95 | "cliOrdId": "my_stop_client_id" 96 | } 97 | result = cfPrivate.send_order_1(stop_order) 98 | print("send_order (stop):\n", result) 99 | 100 | edit = { 101 | "cliOrdId": "my_stop_client_id", 102 | "size": 2, 103 | "limitPrice": 1.50, 104 | "stopPrice": 2.50, 105 | } 106 | result = cfPrivate.edit_order(edit) 107 | print("edit_order (stop):\n", result) 108 | 109 | # cancel order 110 | order_id = "e35d61dd-8a30-4d5f-a574-b5593ef0c050" 111 | result = cfPrivate.cancel_order(order_id) 112 | print("cancel_order:\n", result) 113 | 114 | # cancel all orders of a margin account 115 | result = cfPrivate.cancel_all_orders(symbol="fi_xbtusd") 116 | print("cancel_all_orders:\n", result) 117 | 118 | # cancel all orders after a minute 119 | timeout_in_seconds = 60 120 | result = cfPrivate.cancel_all_orders_after(timeout_in_seconds) 121 | print("cancel_all_orders_after:\n", result) 122 | 123 | # batch order 124 | jsonElement = { 125 | "batchOrder": 126 | [ 127 | { 128 | "order": "send", 129 | "order_tag": "1", 130 | "orderType": "lmt", 131 | "symbol": "PI_XBTUSD", 132 | "side": "buy", 133 | "size": 1, 134 | "limitPrice": 1.00, 135 | "cliOrdId": "my_another_client_id" 136 | }, 137 | { 138 | "order": "send", 139 | "order_tag": "2", 140 | "orderType": "stp", 141 | "symbol": "PI_XBTUSD", 142 | "side": "buy", 143 | "size": 1, 144 | "limitPrice": 2.00, 145 | "stopPrice": 3.00, 146 | }, 147 | { 148 | "order": "cancel", 149 | "order_id": "e35d61dd-8a30-4d5f-a574-b5593ef0c050", 150 | }, 151 | { 152 | "order": "cancel", 153 | "cliOrdId": "my_client_id", 154 | }, 155 | ], 156 | } 157 | result = cfPrivate.send_batchorder(jsonElement) 158 | print("send_batchorder:\n", result) 159 | 160 | # get open orders 161 | result = cfPrivate.get_openorders() 162 | print("get_openorders:\n", result) 163 | 164 | # get fills 165 | lastFillTime = datetime.datetime.strptime( 166 | "2016-02-01", "%Y-%m-%d").isoformat() + ".000Z" 167 | result = cfPrivate.get_fills(lastFillTime=lastFillTime) 168 | print("get_fills:\n", result) 169 | 170 | # get open positions 171 | result = cfPrivate.get_openpositions() 172 | print("get_openpositions:\n", result) 173 | 174 | # get historical orders since start of the year 175 | since = datetime.datetime(2021, 1, 1).timestamp() 176 | since = int(since) * 1000 177 | result = cfPrivate.get_orders(since=since, sort="asc", limit=10000) 178 | print("get_orders(since=%d, sort=\"asc\", limit=10000):\n" % since, "%s elements" % len(result)) 179 | 180 | # get recent orders 181 | result = cfPrivate.get_orders() 182 | print("get_orders:\n", "%s elements" % len(result)) 183 | 184 | # get historical executions since start of the year 185 | since = datetime.datetime(2021, 1, 1).timestamp() 186 | since = int(since) * 1000 187 | result = cfPrivate.get_executions(since=since, sort="asc", limit=10000) 188 | print("get_executions(since=%d, sort=\"asc\", limit=10000):\n" % since, "%s elements" % len(result)) 189 | 190 | # get recent executions 191 | result = cfPrivate.get_executions() 192 | print("get_executions:\n", "%s elements" % len(result)) 193 | 194 | # get historical executions since start of the year 195 | since = datetime.datetime(2021, 1, 1).timestamp() 196 | since = int(since) * 1000 197 | result = cfPrivate.get_historical_executions(since=since) 198 | print("get_historical_executions:\n", "%s elements" % len(result)) 199 | 200 | # get recent executions 201 | result = cfPrivate.get_recent_executions() 202 | print("get_recent_executions:\n", "%s elements" % len(result)) 203 | 204 | # send xbt withdrawal request 205 | targetAddress = "xxxxxxxxxx" 206 | currency = "xbt" 207 | amount = 0.12345678 208 | result = cfPrivate.send_withdrawal(targetAddress, currency, amount) 209 | print("send_withdrawal:\n", result) 210 | 211 | # get xbt transfers 212 | lastTransferTime = datetime.datetime.strptime( 213 | "2016-02-01", "%Y-%m-%d").isoformat() + ".000Z" 214 | result = cfPrivate.get_transfers(lastTransferTime=lastTransferTime) 215 | print("get_transfers:\n", result) 216 | 217 | # transfer 218 | fromAccount = "fi_ethusd" 219 | toAccount = "cash" 220 | unit = "eth" 221 | amount = 0.1 222 | result = cfPrivate.transfer(fromAccount, toAccount, unit, amount) 223 | print("transfer:\n", result) 224 | 225 | 226 | APITester() 227 | --------------------------------------------------------------------------------