├── .gitignore ├── LICENSE ├── README.md ├── examples └── basic.py ├── okex ├── __init__.py └── client.py ├── requirements.txt ├── scripts └── okex-poll-orderbook ├── setup.cfg ├── setup.py └── test └── okex_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 haobtc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Okex Python Client 2 | 3 | A Python client for the Okex API. 4 | 5 | Most of the unauthenticated calls have been implemented. It is planned to 6 | implement the remainder of the API. 7 | 8 | ## Installation 9 | 10 | pip install okex 11 | 12 | 13 | ## Poll The Order Book 14 | 15 | Run the ```okex-poll-orderbook``` script in a terminal. 16 | 17 | Press ```Ctrl-c``` to exit. 18 | 19 | okex-poll-orderbook 20 | 21 | ## Setup 22 | 23 | Install the libs 24 | 25 | pip install -r ./requirements.txt 26 | 27 | 28 | ## Usage 29 | 30 | See the examples directory for samples. 31 | 32 | e.g. 33 | 34 | PYTHONPATH=.:$PYTHONPATH python examples/basic.py 35 | 36 | 37 | ## Compatibility 38 | 39 | This code has been tested on 40 | 41 | - Python 2.7.10 42 | 43 | 44 | ## TODO 45 | 46 | - Implement all API calls that Okex make available. 47 | 48 | ## Contributing 49 | 50 | 1. Create an issue and discuss. 51 | 1. Fork it. 52 | 1. Create a feature branch containing only your fix or feature. 53 | 1. Add tests!!!! Features or fixes that don't have good tests won't be accepted. 54 | 1. Create a pull request. 55 | 56 | ## References 57 | 58 | - [https://www.okex.com/rest_api.html](https://www.okex.com/rest_api.html) 59 | 60 | ## Licence 61 | 62 | The MIT License (MIT) 63 | 64 | See [LICENSE.md](LICENSE.md) 65 | -------------------------------------------------------------------------------- /examples/basic.py: -------------------------------------------------------------------------------- 1 | from okex.client import OkexClient, OkexTradeClient 2 | 3 | proxies = { 4 | 'http': 'socks5h://127.0.0.1:1080', 5 | 'https': 'socks5h://127.0.0.1:1080' 6 | } 7 | 8 | client = OkexClient(None, None, proxies) 9 | 10 | symbol = 'ltc_btc' 11 | 12 | print client.ticker(symbol) 13 | print client.trades(symbol) 14 | print client.depth(symbol) 15 | 16 | authClient = OkexTradeClient('', '', proxies=proxies) 17 | print authClient.balances() 18 | 19 | print authClient.history('bcc_btc', 1, 500) -------------------------------------------------------------------------------- /okex/__init__.py: -------------------------------------------------------------------------------- 1 | from okex.client import OkexClient, OkexTradeClient, OkexClientError 2 | -------------------------------------------------------------------------------- /okex/client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from __future__ import absolute_import 4 | import requests 5 | import json 6 | import base64 7 | import hmac 8 | import hashlib 9 | import time 10 | import logging 11 | import urllib 12 | 13 | PROTOCOL = "https" 14 | HOST = "www.okex.com/api" 15 | VERSION = "v1" 16 | 17 | PATH_SYMBOLS = "symbols" #获取支持的币币交易类型 18 | PATH_TICKER = "ticker.do" #获取币币交易行情 19 | PATH_TRADES = "trades.do" #获取币币交易信息 20 | PATH_DEPTH = "depth.do" #获取币币市场深度 21 | PATH_ORDER_INFO = "order_info.do" #获取订单信息 22 | PATH_ORDERS_INFO = "orders_info.do" #批量获取订单信息 23 | PATH_ORDER_HISTORY = "order_history.do" #获取历史订单信息,只返回最近两天的信息 24 | PATH_CANCEL_ORDER = "cancel_order.do" #撤销订单 25 | PATH_BALANCES_USERINFO = "userinfo.do" #个人资产情况 26 | PATH_TRADE = "trade.do" #获取币币交易信息 27 | 28 | # HTTP request timeout in seconds 29 | TIMEOUT = 10.0 30 | 31 | 32 | class OkexClientError(Exception): 33 | pass 34 | 35 | 36 | class OkexBaseClient(object): 37 | def __init__(self, key, secret, proxies=None): 38 | self.URL = "{0:s}://{1:s}/{2:s}".format(PROTOCOL, HOST, VERSION) 39 | self.KEY = key 40 | self.SECRET = secret 41 | self.PROXIES = proxies 42 | 43 | @property 44 | def _nonce(self): 45 | """ 46 | Returns a nonce 47 | Used in authentication 48 | """ 49 | return str(int(time.time() * 1000)) 50 | 51 | def _build_parameters(self, parameters): 52 | # sort the keys so we can test easily in Python 3.3 (dicts are not 53 | # ordered) 54 | keys = list(parameters.keys()) 55 | keys.sort() 56 | return '&'.join(["%s=%s" % (k, parameters[k]) for k in keys]) 57 | 58 | def url_for(self, path, path_arg=None, parameters=None): 59 | url = "%s/%s" % (self.URL, path) 60 | # If there is a path_arh, interpolate it into the URL. 61 | # In this case the path that was provided will need to have string 62 | # interpolation characters in it 63 | if path_arg: 64 | url = url % (path_arg) 65 | # Append any parameters to the URL. 66 | if parameters: 67 | url = "%s?%s" % (url, self._build_parameters(parameters)) 68 | return url 69 | 70 | def _sign_payload(self, payload): 71 | sign = '' 72 | for key in sorted(payload.keys()): 73 | sign += key + '=' + str(payload[key]) +'&' 74 | data = sign+'secret_key='+self.SECRET 75 | return hashlib.md5(data.encode("utf8")).hexdigest().upper() 76 | 77 | def _convert_to_floats(self, data): 78 | """ 79 | Convert all values in a dict to floats at first level 80 | """ 81 | for key, value in data.items(): 82 | data[key] = float(value) 83 | return data 84 | 85 | def _get(self, url, timeout=TIMEOUT): 86 | req = requests.get(url, timeout=timeout, proxies=self.PROXIES) 87 | if req.status_code/100 != 2: 88 | logging.error(u"Failed to request:%s %d headers:%s", url, req.status_code, req.headers) 89 | try: 90 | return req.json() 91 | except Exception as e: 92 | logging.exception('Failed to GET:%s result:%s', url, req.text) 93 | raise e 94 | 95 | def _post(self, url, params=None, needsign=True, headers=None, timeout=TIMEOUT): 96 | req_params = {'api_key' : self.KEY} 97 | if params and needsign: 98 | req_params.update(params) 99 | req_params['sign'] = self._sign_payload(req_params) 100 | 101 | req_headers = { 102 | "Content-type" : "application/x-www-form-urlencoded", 103 | } 104 | if headers: 105 | req_headers.update(headers) 106 | logging.info("%s %s", req_headers, req_params) 107 | 108 | req = requests.post(url, headers=req_headers, data=urllib.urlencode(req_params), timeout=TIMEOUT, proxies=self.PROXIES) 109 | if req.status_code/100 != 2: 110 | logging.error(u"Failed to request:%s %d headers:%s", url, req.status_code, req.headers) 111 | try: 112 | return req.json() 113 | except Exception as e: 114 | logging.exception('Failed to POST:%s result:%s', url, req.text) 115 | raise e 116 | 117 | 118 | class OkexTradeClient(OkexBaseClient): 119 | """ 120 | Authenticated client for trading through Bitfinex API 121 | """ 122 | 123 | def place_order(self, amount, price, ord_type, symbol='btcusd'): 124 | """ 125 | # Request 126 | POST https://www.okex.com/api/v1/trade.do 127 | # Response 128 | {"result":true,"order_id":123456} 129 | """ 130 | assert(isinstance(amount, str) and isinstance(price, str)) 131 | 132 | if ord_type not in ('buy', 'sell', 'buy_market', 'sell_market'): 133 | #买卖类型: 限价单(buy/sell) 市价单(buy_market/sell_market) 134 | raise OkexClientError("Invaild order type") 135 | 136 | payload = { 137 | "symbol": symbol, "amount": amount, "price": price, "type": ord_type 138 | } 139 | result = self._post(self.url_for(PATH_TRADE), params=payload) 140 | if 'error_code' not in result and result['result'] and result['order_id']: 141 | return result 142 | raise OkexClientError('Failed to place order:'+str(result)) 143 | 144 | def status_order(self, symbol, order_id): 145 | """ 146 | # Request 147 | order_id -1:未完成订单,否则查询相应订单号的订单 148 | POST https://www.okex.com/api/v1/order_info.do 149 | # Response 150 | { 151 | "result": true, 152 | "orders": [ 153 | { 154 | "amount": 0.1, 155 | "avg_price": 0, 156 | "create_date": 1418008467000, 157 | "deal_amount": 0, 158 | "order_id": 10000591, 159 | "orders_id": 10000591, 160 | "price": 500, 161 | "status": 0, 162 | "symbol": "btc_usd", 163 | "type": "sell" 164 | } 165 | ] 166 | } 167 | amount:委托数量 168 | create_date: 委托时间 169 | avg_price:平均成交价 170 | deal_amount:成交数量 171 | order_id:订单ID 172 | orders_id:订单ID(不建议使用) 173 | price:委托价格 174 | status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 175 | type:buy_market:市价买入 / sell_market:市价卖出 176 | """ 177 | payload = { 178 | "symbol": symbol, "order_id": order_id 179 | } 180 | result = self._post(self.url_for(PATH_ORDER_INFO), params=payload) 181 | if result['result']: 182 | return result 183 | raise OkexClientError('Failed to get order status:'+str(result)) 184 | 185 | def cancel_order(self, symbol, order_id): 186 | ''' 187 | # Request 188 | POST https://www.okex.com/api/v1/cancel_order.do 189 | order_id: 订单ID(多个订单ID中间以","分隔,一次最多允许撤消3个订单) 190 | # Response 191 | #多笔订单返回结果(成功订单ID,失败订单ID) 192 | {"success":"123456,123457","error":"123458,123459"} 193 | ''' 194 | payload = { 195 | "symbol": symbol, "order_id": order_id 196 | } 197 | result = self._post(self.url_for(PATH_CANCEL_ORDER), params=payload) 198 | if result['result']: 199 | return result 200 | raise OkexClientError('Failed to cancal order:%s %s' % (symbol, order_id)) 201 | 202 | def cancel_orders(self, symbol, order_ids): 203 | final_result = {'result':True, 'success':[], 'error':[]} 204 | for i in range(0, len(order_ids), 3): 205 | three_order_ids = ",".join(order_ids[i:i+3]) 206 | tmp = self.cancel_order(symbol, three_order_ids) 207 | final_result['result'] &= tmp['result'] 208 | final_result['success'].extend(tmp['success'].split(',')) 209 | final_result['error'].extend(tmp['error'].split(',')) 210 | return final_result 211 | 212 | def active_orders(self, symbol): 213 | """ 214 | Fetch active orders 215 | """ 216 | return self.status_order(symbol, -1) 217 | 218 | def history(self, symbol, status, limit=500): 219 | """ 220 | # Request 221 | POST https://www.okex.com/api/v1/order_history.do 222 | status: 查询状态 0:未完成的订单 1:已经完成的订单 (最近两天的数据) 223 | # Response 224 | { 225 | "current_page": 1, 226 | "orders": [ 227 | { 228 | "amount": 0, 229 | "avg_price": 0, 230 | "create_date": 1405562100000, 231 | "deal_amount": 0, 232 | "order_id": 0, 233 | "price": 0, 234 | "status": 2, 235 | "symbol": "btc_usd", 236 | "type": "sell” 237 | } 238 | ], 239 | "page_length": 1, 240 | "result": true, 241 | "total": 3 242 | } 243 | status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 244 | type:buy_market:市价买入 / sell_market:市价卖出 245 | """ 246 | PAGE_LENGTH = 200 # Okex限制 每页数据条数,最多不超过200 247 | final_result = [] 248 | for page_index in range(int(limit/PAGE_LENGTH)+1): 249 | payload = { 250 | "symbol": symbol, 251 | "status": status, 252 | "current_page": page_index, 253 | "page_length": PAGE_LENGTH, 254 | } 255 | result = self._post(self.url_for(PATH_ORDER_HISTORY), params=payload) 256 | if not result['result']: 257 | raise OkexClientError('Failed to get history order:%s %s' % (symbol, result)) 258 | if len(result['orders'])>0: 259 | final_result.extend(result['orders']) 260 | else: 261 | break 262 | return final_result 263 | 264 | def balances(self): 265 | ''' 266 | # Request 267 | POST https://www.okex.com/api/v1/userinfo.do 268 | # Response 269 | { 270 | "info": { 271 | "funds": { 272 | "free": { 273 | "btc": "0", 274 | "usd": "0", 275 | "ltc": "0", 276 | "eth": "0" 277 | }, 278 | "freezed": { 279 | "btc": "0", 280 | "usd": "0", 281 | "ltc": "0", 282 | "eth": "0" 283 | } 284 | } 285 | }, 286 | "result": true 287 | } 288 | ''' 289 | payload = { 290 | } 291 | result = self._post(self.url_for(PATH_BALANCES_USERINFO), params=payload) 292 | if result['result']: 293 | return result 294 | raise OkexClientError('Failed to get balances userinfo order:%s' % result) 295 | 296 | 297 | class OkexClient(OkexBaseClient): 298 | """ 299 | Client for the Okex.com API. 300 | See https://www.okex.com/rest_api.html for API documentation. 301 | """ 302 | 303 | def ticker(self, symbol): 304 | """ 305 | GET /api/v1/ticker.do?symbol=ltc_btc 306 | 307 | GET https://www.okex.com/api/v1/ticker.do?symbol=ltc_btc 308 | { 309 | "date":"1410431279", 310 | "ticker":{ 311 | "buy":"33.15", 312 | "high":"34.15", 313 | "last":"33.15", 314 | "low":"32.05", 315 | "sell":"33.16", 316 | "vol":"10532696.39199642" 317 | } 318 | } 319 | """ 320 | return self._get(self.url_for(PATH_TICKER, parameters={'symbol' : symbol})) 321 | 322 | def trades(self, symbol, since_tid=None): 323 | """ 324 | GET /api/v1/trades.do 325 | GET https://www.okex.com/api/v1/trades.do?symbol=ltc_btc&since=7622718804 326 | [ 327 | { 328 | "date": "1367130137", 329 | "date_ms": "1367130137000", 330 | "price": 787.71, 331 | "amount": 0.003, 332 | "tid": "230433", 333 | "type": "sell" 334 | } 335 | ] 336 | """ 337 | params = {'symbol':symbol} 338 | if since_tid: 339 | params['since'] = since_tid 340 | return self._get(self.url_for(PATH_TRADES, parameters=params)) 341 | 342 | def depth(self, symbol, size=200): 343 | ''' 344 | # Request 345 | GET https://www.okex.com/api/v1/depth.do?symbol=ltc_btc 346 | # Response 347 | { 348 | "asks": [ 349 | [792, 5], 350 | [789.68, 0.018], 351 | [788.99, 0.042], 352 | [788.43, 0.036], 353 | [787.27, 0.02] 354 | ], 355 | "bids": [ 356 | [787.1, 0.35], 357 | [787, 12.071], 358 | [786.5, 0.014], 359 | [786.2, 0.38], 360 | [786, 3.217], 361 | [785.3, 5.322], 362 | [785.04, 5.04] 363 | ] 364 | } 365 | ''' 366 | params = {'symbol':symbol} 367 | if size>0: 368 | params['size'] = size 369 | return self._get(self.url_for(PATH_DEPTH, parameters=params)) 370 | 371 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /scripts/okex-poll-orderbook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Poll the Bitfinex order book and print to console. 4 | # 5 | # Author : Scott Barr 6 | # Date : 29 Mar 2014 7 | # 8 | 9 | import os, sys 10 | from datetime import datetime 11 | 12 | import okex 13 | 14 | # symbol to query the order book 15 | symbol = 'ltc_btc' 16 | 17 | # set the parameters to limit the number of bids or asks 18 | parameters = {'limit_asks': 5, 'limit_bids': 5} 19 | 20 | # create the client 21 | client = okex.OkexClient(None, None) 22 | 23 | while True: 24 | 25 | # get latest ticker 26 | ticker = client.ticker(symbol) 27 | orders = client.trades(symbol) 28 | 29 | # clear the display, and update values 30 | os.system('clear') 31 | 32 | print "# Okex (Last Update : %s)" % (datetime.now()) 33 | print "## Last Ticker" 34 | print ticker 35 | 36 | for order in orders: 37 | print order 38 | 39 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | # Runtime dependencies. See requirements.txt for development dependencies. 4 | dependencies = [ 5 | 'requests', 6 | ] 7 | 8 | version = '1.0.2' 9 | 10 | setup(name='okex', 11 | version=version, 12 | description = 'Python client for the okex API', 13 | author = 'Winlin', 14 | author_email = 'pcliuguangtao@163.com', 15 | url = 'https://github.com/haobtc/okex', 16 | license = 'MIT', 17 | packages=['okex'], 18 | scripts = ['scripts/okex-poll-orderbook'], 19 | install_requires = dependencies, 20 | keywords = ['bitcoin', 'btc'], 21 | classifiers = [], 22 | zip_safe=True) 23 | -------------------------------------------------------------------------------- /test/okex_test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | OKEX_CONF = ('', '') 4 | 5 | from okex.client import OkexClient, OkexTradeClient 6 | 7 | class TestOkexTradeClient(): 8 | def setUp(self): 9 | self.tc = OkexTradeClient(*OKEX_CONF) 10 | self.tcGet = OkexClient(*OKEX_CONF) 11 | 12 | def test_instantiate_tradeclient(self): 13 | self.assertIsInstance(self.tc, OkexTradeClient) 14 | 15 | def test_get_active_orders_returns_json(self): 16 | ao = self.tc.active_orders() 17 | self.assertIsInstance(ao, list) 18 | 19 | def test_get_active_positions_returns_json(self): 20 | ap = self.tc.active_positions() 21 | self.assertIsInstance(ap, list) 22 | 23 | def test_get_full_history(self): 24 | ap = self.tc.active_positions() 25 | self.assertIsInstance(ap, list) 26 | 27 | def test_balances(self): 28 | print self.tc.balances() 29 | 30 | def test_history(self): 31 | print 'call test_history' 32 | print self.tc.history('bch_btc', 0) 33 | 34 | def test_ticker(self): 35 | print 'call test_ticker' 36 | print self.tcGet.ticker('bch_btc') 37 | 38 | def test_sell(self): 39 | print 'call place_order' 40 | print self.tc.place_order('0.2', '0.1', 'buy', symbol='bch_btc') 41 | 42 | aa = TestOkexTradeClient() 43 | aa.setUp() 44 | aa.test_history() 45 | aa.test_balances() 46 | aa.test_ticker() 47 | aa.test_sell() 48 | 49 | --------------------------------------------------------------------------------