├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── Makefile ├── README.md ├── client_sample.py ├── peatio_client ├── __init__.py ├── auth.py ├── client.py ├── error.py └── streaming_client.py ├── requirements.txt ├── setup.py ├── streaming_client_sample.py └── tests ├── __init__.py ├── test_auth.py ├── test_client.py └── test_streaming_client.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swo 3 | dist 4 | *.egg-info 5 | build 6 | .eggs 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.6' 4 | - '2.7' 5 | - '3.2' 6 | - '3.3' 7 | - '3.4' 8 | - nightly 9 | install: pip install -r requirements.txt 10 | script: python setup.py test 11 | deploy: 12 | provider: pypi 13 | user: czheo 14 | password: 15 | secure: RgUoPeublwcdvw54Vk4YanWfwiIeg3I2y+K+VXp7O4o7R97N/gnRNwLfrcSoPKaPpiNCM94i4DssHHp5nIU5PX+FnNFvKDzxoWb9BX2pEG9cCO4ZTjUlinPhEd6NFmaZoekxCcga/pMKVwdsohU8QpNHKYhpgyRC9mEQ2dv0Ct4= 16 | on: 17 | tags: true 18 | repo: czheo/peatio-client-python 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | unit2 discover 3 | 4 | clean: 5 | rm -rfv build dist *.egg-info .eggs 6 | 7 | .PHONY: test clean 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | a client library for the bitcoin exchange Peatio 2 | --- 3 | [![Build Status](https://travis-ci.org/czheo/peatio-client-python.svg?branch=master)](https://travis-ci.org/czheo/peatio-client-python) 4 | 5 | This library is inspired by [peatio-client-ruby](https://github.com/peatio/peatio-client-ruby) 6 | 7 | # Install 8 | ``` 9 | pip install peatio_client 10 | ``` 11 | 12 | # Usage 13 | - Rest API Client: https://github.com/czheo/peatio-client-python/blob/master/client_sample.py 14 | - Streaming API Client: https://github.com/czheo/peatio-client-python/blob/master/streaming_client_sample.py 15 | 16 | # Licence 17 | LGPL 18 | -------------------------------------------------------------------------------- /client_sample.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from peatio_client import Client 3 | 4 | # access public apis 5 | client = Client() 6 | 7 | print(client.get_public("/api/v2/markets.json")) 8 | print(client.get_public("/api/v2/depth.json", params={ 9 | "market": "btccny" 10 | })) 11 | 12 | # access secret apis 13 | client = Client( 14 | access_key="your access key", 15 | secret_key="your secret key" 16 | ) 17 | 18 | print(client.get("/api/v2/members/me.json")) 19 | -------------------------------------------------------------------------------- /peatio_client/__init__.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from .client import Client 3 | from .streaming_client import StreamingClient 4 | from .error import ClientError 5 | -------------------------------------------------------------------------------- /peatio_client/auth.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import hashlib, hmac 3 | try: 4 | from urllib import urlencode 5 | except: 6 | from urllib.parse import urlencode 7 | import time 8 | 9 | def payload(verb, path, params): 10 | # url params should be sorted in alphabet 11 | url_params = urlencode(sorted(params.items())) 12 | return "{verb}|{path}|{url_params}".format( 13 | verb = verb.upper(), 14 | path = path, 15 | url_params = url_params 16 | ) 17 | 18 | class Auth: 19 | def __init__(self, access_key, secret_key): 20 | self.access_key = access_key 21 | self.secret_key = secret_key 22 | 23 | def signed_challenge(self, challenge): 24 | payload = "%s%s" % (self.access_key, challenge) 25 | signature = hmac.new(self.secret_key.encode(), payload.encode(), hashlib.sha256).hexdigest() 26 | return { 27 | "auth": { 28 | "access_key": self.access_key, 29 | "answer": signature 30 | } 31 | } 32 | 33 | def signed_params(self, verb, path, params): 34 | params = params.copy() 35 | params = self._format_params(params) 36 | signature = self.sign(verb, path, params) 37 | params["signature"] = signature 38 | return params 39 | 40 | def sign(self, verb, path, params): 41 | return hmac.new(self.secret_key.encode(), payload(verb, path, params).encode(), hashlib.sha256).hexdigest() 42 | 43 | def _format_params(self, params): 44 | if not params.get("access_key"): 45 | params["access_key"] = self.access_key 46 | if not params.get("tonce"): 47 | params["tonce"] = int(time.time() * 1000) 48 | return params 49 | -------------------------------------------------------------------------------- /peatio_client/client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import requests 3 | from .auth import Auth 4 | from .error import ClientError 5 | 6 | class Client: 7 | def __init__( 8 | self, 9 | endpoint = "https://yunbi.com", 10 | access_key = None, 11 | secret_key = None, 12 | timeout = 60 13 | ): 14 | self.endpoint = endpoint 15 | self.timeout = timeout 16 | 17 | if access_key and secret_key: 18 | self.access_key = access_key 19 | self.secret_key = secret_key 20 | self.auth = Auth(access_key, secret_key) 21 | else: 22 | self.auth = False 23 | 24 | def check_auth(self): 25 | if not self.auth: 26 | raise ClientError("Missing access key and/or secret key") 27 | 28 | def get_public(self, path, params=None): 29 | if params is None: 30 | params = {} 31 | url = "%s%s" % (self.endpoint, path) 32 | 33 | response = requests.get(url, 34 | params = params, 35 | timeout = self.timeout, 36 | verify = self.endpoint.startswith("https://") 37 | ) 38 | 39 | return self.response_to_dict(response) 40 | 41 | def get(self, path, params=None): 42 | if params is None: 43 | params = {} 44 | self.check_auth() 45 | url = "%s%s" % (self.endpoint, path) 46 | params = self.auth.signed_params("get", path, params) 47 | 48 | response = requests.get(url, 49 | params = params, 50 | timeout = self.timeout, 51 | verify = self.endpoint.startswith("https://") 52 | ) 53 | 54 | return self.response_to_dict(response) 55 | 56 | 57 | def post(self, path, params=None): 58 | if params is None: 59 | params = {} 60 | self.check_auth() 61 | url = "%s%s" % (self.endpoint, path) 62 | params = self.auth.signed_params("post", path, params) 63 | 64 | response = requests.post(url, 65 | data = params, 66 | timeout = self.timeout, 67 | verify = self.endpoint.startswith("https://") 68 | ) 69 | 70 | return self.response_to_dict(response) 71 | 72 | def response_to_dict(self, response): 73 | try: 74 | return response.json() 75 | except ValueError: 76 | raise ClientError("Response is in bad json format") 77 | -------------------------------------------------------------------------------- /peatio_client/error.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | class ClientError(Exception): 4 | pass 5 | -------------------------------------------------------------------------------- /peatio_client/streaming_client.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from .auth import Auth 3 | from .error import ClientError 4 | import logging 5 | import websocket 6 | import json 7 | 8 | class StreamingClient: 9 | def __init__(self, 10 | access_key, 11 | secret_key, 12 | endpoint = "wss://yunbi.com:8080", 13 | logger = None 14 | ): 15 | self.endpoint = endpoint 16 | self.access_key = access_key 17 | self.secret_key = secret_key 18 | self.auth = Auth(access_key, secret_key) 19 | if logger is None: 20 | logger = logging.getLogger(__name__) 21 | # initialize the logger 22 | handler = logging.StreamHandler() 23 | handler.setLevel(logging.INFO) 24 | logger.setLevel(logging.INFO) 25 | logger.addHandler(handler) 26 | self.logger = logger 27 | 28 | 29 | def run(self, on_message=None): 30 | if on_message is not None: 31 | self.on_message = on_message 32 | 33 | def on_open(ws): 34 | self.logger.info("Connected!") 35 | 36 | def on_message(ws, message): 37 | msg = json.loads(message) 38 | if "challenge" in msg: 39 | data = msg["challenge"] 40 | ws.send(json.dumps(self.auth.signed_challenge(data))) 41 | else: 42 | try: 43 | self.on_message(msg) 44 | except Exception as e: 45 | self.logger.error(e) 46 | self.logger.error("Failed to process message: %s" % message) 47 | 48 | def on_close(ws): 49 | self.logger.info("Closed!") 50 | 51 | def on_error(ws, error): 52 | self.logger.error(error) 53 | raise ClientError(error) 54 | 55 | ws = websocket.WebSocketApp(self.endpoint) 56 | ws.on_open = on_open 57 | ws.on_message = on_message 58 | ws.on_close = on_close 59 | ws.on_error = on_error 60 | ws.run_forever() 61 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | unittest2 3 | websocket-client 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def readme(): 4 | with open("README.md") as f: 5 | return f.read() 6 | 7 | setup( 8 | name = "peatio_client", 9 | version = "0.1.1", 10 | description = "a client library for the bitcoin exchange Peatio", 11 | url = 'https://github.com/czheo/peatio-client-python', 12 | long_description = readme(), 13 | author = "czheo", 14 | license = "LGPL", 15 | keywords = "bitcoin peatio yunbi.com", 16 | test_suite = "tests", 17 | packages = [ 18 | "peatio_client" 19 | ], 20 | install_requires = [ 21 | "requests" 22 | ], 23 | zip_safe = False 24 | ) 25 | -------------------------------------------------------------------------------- /streaming_client_sample.py: -------------------------------------------------------------------------------- 1 | from peatio_client import StreamingClient 2 | 3 | sc = StreamingClient( 4 | access_key = "your access key", 5 | secret_key = "your secret key" 6 | ) 7 | 8 | def on_message(msg): 9 | print(msg) 10 | 11 | sc.run(on_message) 12 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czheo/peatio-client-python/5e7c9280003e2aeb4da9d01d5aca0fb7ea820cad/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_auth.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import unittest2 3 | from peatio_client import auth 4 | 5 | class AuthTestCase(unittest2.TestCase): 6 | def setUp(self): 7 | self.auth = auth.Auth( 8 | access_key = "accesskey", 9 | secret_key = "secretkey" 10 | ) 11 | 12 | def test_signed_challenge(self): 13 | access_key = "abc" 14 | challenge = "def" 15 | secret_key = "ghi" 16 | a = auth.Auth( 17 | access_key = access_key, 18 | secret_key = secret_key 19 | ) 20 | signature = a.signed_challenge(challenge) 21 | self.assertEqual(signature, { 22 | "auth": { 23 | "access_key": access_key, 24 | "answer": "52ca0e5beab532532c62155e78d81c7dc8ad6d6f744cf3797668cf52dd2f9a41", 25 | } 26 | }) 27 | 28 | def test_signed_params(self): 29 | params = self.auth.signed_params( 30 | "GET", "/api/v2/orders", params={ 31 | "tonce": 1234567 32 | } 33 | ) 34 | self.assertEqual(params, { 35 | "tonce": 1234567, 36 | "access_key": "accesskey", 37 | "signature": "1b89e3a984c25eacb7439ae644be253b55975e35529ee665966e3b9d8e3dcb2f", 38 | }) 39 | 40 | def test_sign(self): 41 | sign = self.auth.sign( 42 | verb = "get", 43 | path = "/api/v2/orders", 44 | params = { 45 | "tonce": 1234567, 46 | "access_key": "accesskey", 47 | } 48 | ) 49 | self.assertEqual(sign, "1b89e3a984c25eacb7439ae644be253b55975e35529ee665966e3b9d8e3dcb2f") 50 | 51 | def test_payload(self): 52 | payload = auth.payload( 53 | verb = "get", 54 | path = "/api/v2/markets", 55 | params = { 56 | "tonce": 123456789, 57 | "access_key": "xxx", 58 | "foo": "bar", 59 | } 60 | ) 61 | self.assertEqual(payload, "GET|/api/v2/markets|access_key=xxx&foo=bar&tonce=123456789") 62 | 63 | if __name__ == "__main__": 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import unittest2 3 | from peatio_client import Client, ClientError 4 | 5 | class ClientTestCase(unittest2.TestCase): 6 | def test_access_private_apis_without_keys(self): 7 | with self.assertRaises(ClientError): 8 | client = Client().post("") 9 | 10 | def test_init_with_options(self): 11 | c = Client(access_key="access", secret_key="secret") 12 | self.assertEqual("access", c.access_key) 13 | self.assertEqual("secret", c.secret_key) 14 | -------------------------------------------------------------------------------- /tests/test_streaming_client.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import unittest2 3 | from peatio_client import StreamingClient, ClientError 4 | 5 | class StreamingClientTestCase(unittest2.TestCase): 6 | def test_init(self): 7 | sc = StreamingClient( 8 | access_key= "access", 9 | secret_key = "secret" 10 | ) 11 | 12 | self.assertEqual("access", sc.access_key) 13 | self.assertEqual("secret", sc.secret_key) 14 | --------------------------------------------------------------------------------