Skip to content

Commit

Permalink
Merge branch 'feature/tox_travis_coverall' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sammchardy committed Aug 25, 2017
2 parents ab15627 + 0343cda commit b15e7a2
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 106 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.tox
.cache/v/cache
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
language: python

python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"

install:
- pip install -r requirements.txt
- pip install -r test-requirements.txt
- pip install tox-travis

script:
- tox

after_success:
- coveralls
6 changes: 6 additions & 0 deletions PYPIREADME.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ Binance API
.. image:: https://img.shields.io/pypi/l/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance

.. image:: https://img.shields.io/travis/sammchardy/python-binance/master.svg
:target: https://travis-ci.org/sammchardy/python-binance

.. image::https://img.shields.io/coveralls/sammchardy/python-binance.svg
:target: https://coveralls.io/github/sammchardy/python-binance

.. image:: https://img.shields.io/pypi/wheel/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance

Expand Down
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ Binance API
.. image:: https://img.shields.io/pypi/l/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance

.. image:: https://img.shields.io/travis/sammchardy/python-binancer.svg
:target: https://travis-ci.org/sammchardy/python-binance

.. image::https://img.shields.io/coveralls/sammchardy/python-binance.svg
:target: https://coveralls.io/github/sammchardy/python-binance

.. image:: https://img.shields.io/pypi/wheel/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance

Expand Down Expand Up @@ -97,6 +103,17 @@ Some methods have a `recvWindow` parameter for `timing security, see Binance doc

API Endpoints are rate limited by Binance at 20 requests per second.

Order Validation
^^^^^^^^^^^^^^^^

Binance has a number of rules around symbol pair orders with validation on minimum price, quantity and total order value.

These rules are fetched when the client is initialised.

The rules can be refreshed by calling the `get_products` API endpoint.

We can then validate if pairs are being actively traded on Binance as well.

ENUMs
^^^^^

Expand Down
58 changes: 51 additions & 7 deletions binance/client.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
#!/usr/bin/env python
# coding=utf-8

import hashlib
import requests
import six
import time
from .exceptions import BinanceAPIException
from .validation import validate_order

if six.PY2:
from urllib import urlencode
elif six.PY3:
from urllib.parse import urlencode

from .exceptions import BinanceAPIException
from .validation import validate_order


class Client(object):

API_URL = 'https://www.binance.com/api'
WEBSITE_URL = 'https://www.binance.com'
API_VERSION = 'v1'

_products = None

def __init__(self, api_key, api_secret):

self.API_KEY = api_key
Expand All @@ -24,6 +30,7 @@ def __init__(self, api_key, api_secret):

# init DNS and SSL cert
self.ping()
self.get_products()

def _init_session(self):

Expand All @@ -36,6 +43,9 @@ def _init_session(self):
def _create_api_uri(self, path):
return self.API_URL + '/' + self.API_VERSION + '/' + path

def _create_website_uri(self, path):
return self.WEBSITE_URL + '/' + path

def _generate_signature(self, data):

query_string = urlencode(data)
Expand All @@ -52,7 +62,7 @@ def _request(self, method, path, signed, **kwargs):
kwargs['data'] = data
if signed:
# generate signature
kwargs['data']['timestamp'] =int(time.time() * 1000)
kwargs['data']['timestamp'] = int(time.time() * 1000)
kwargs['data']['signature'] = self._generate_signature(kwargs['data'])

if data and method == 'get':
Expand All @@ -62,6 +72,21 @@ def _request(self, method, path, signed, **kwargs):
response = getattr(self.session, method)(uri, **kwargs)
return self._handle_response(response)

def _request_website(self, method, path, **kwargs):

uri = self._create_website_uri(path)

data = kwargs.get('data', None)
if data and isinstance(data, dict):
kwargs['data'] = data

if data and method == 'get':
kwargs['params'] = kwargs['data']
del(kwargs['data'])

response = getattr(self.session, method)(uri, **kwargs)
return self._handle_response(response)

def _handle_response(self, response):
"""Internal helper for handling API responses from the Coinbase server.
Raises the appropriate exceptions when necessary; otherwise, returns the
Expand All @@ -83,6 +108,25 @@ def _put(self, path, signed=False, **kwargs):
def _delete(self, path, signed=False, **kwargs):
return self._request('delete', path, signed, **kwargs)

def _parse_products(self, products):
"""
:param products:
:return:
"""
self._products = {}
if 'data' in products:
products = products['data']
for p in products:
self._products[p['symbol']] = p

# Website Endpoints

def get_products(self):
products = self._request_website('get', 'exchange/public/product')
self._parse_products(products)
return products

# General Endpoints

def ping(self):
Expand Down Expand Up @@ -171,7 +215,7 @@ def create_order(self, **params):
icebergQty - Used with iceberg orders
:return:
"""
validate_order(params)
validate_order(params, self._products)
return self._post('order', True, data=params)

def create_test_order(self, **params):
Expand All @@ -191,7 +235,7 @@ def create_test_order(self, **params):
recvWindow - the number of milliseconds the request is valid for
:return:
"""
validate_order(params)
validate_order(params, self._products)
return self._post('order/test', True, data=params)

def get_order(self, **params):
Expand Down Expand Up @@ -280,4 +324,4 @@ def stream_keepalive(self, **params):
return self._put('userDataStream', False, data=params)

def stream_close(self, **params):
return self._delete('userDataStream', False, data=params)
return self._delete('userDataStream', False, data=params)
2 changes: 2 additions & 0 deletions binance/enums.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python
# coding=utf-8

SYMBOL_TYPE_SPOT = 'SPOT'

Expand Down
19 changes: 18 additions & 1 deletion binance/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env python
# coding=utf-8


class BinanceAPIException(Exception):
def __init__(self, response):
Expand Down Expand Up @@ -40,4 +43,18 @@ class BinanceOrderMinTotalException(BinanceOrderException):

def __init__(self, value):
message = "Total must be at least %s" % value
super(BinanceOrderMinTotalException, self).__init__(-1013, message)
super(BinanceOrderMinTotalException, self).__init__(-1013, message)


class BinanceOrderUnknownSymbolException(BinanceOrderException):

def __init__(self, value):
message = "Unknown symbol %s" % value
super(BinanceOrderUnknownSymbolException, self).__init__(-1013, message)


class BinanceOrderInactiveSymbolException(BinanceOrderException):

def __init__(self, value):
message = "Attempting to trade an inactive symbol %s" % value
super(BinanceOrderInactiveSymbolException, self).__init__(-1013, message)
134 changes: 42 additions & 92 deletions binance/validation.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,54 @@
from .enums import *
from .exceptions import *
#!/usr/bin/env python
# coding=utf-8

# https://binance.zendesk.com/hc/en-us/articles/115000594711
TRADE_LIMITS = {
'ETHBTC': {
'min_amount': 0.001,
'min_price': 0.000001,
'min_order_value': 0.001
},
'LTCBTC': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.001
},
'BNBBTC': {
'min_amount': 1,
'min_price': 0.00000001,
'min_order_value': 0.001
},
'NEOBTC': {
'min_amount': 0.001,
'min_price': 0.000001,
'min_order_value': 0.001
},
'GASBTC': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.001
},
'BCCBTC': {
'min_amount': 0.001,
'min_price': 0.000001,
'min_order_value': 0.001
},
'HCCBTC': {
'min_amount': 1,
'min_price': 0.00000001,
'min_order_value': 0.001
},
'HSRBTC': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.001
},
'BNBETH': {
'min_amount': 1,
'min_price': 0.00000001,
'min_order_value': 0.01
},
'QTUMETH': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.01
},
'SNTETH': {
'min_amount': 1,
'min_price': 0.00000001,
'min_order_value': 0.01
},
'BNTETH': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.01
},
'EOSETH': {
'min_amount': 0.01,
'min_price': 0.000001,
'min_order_value': 0.01
},
'BTMETH': {
'min_amount': 1,
'min_price': 0.00000001,
'min_order_value': 0.01
},
'BTCUSDT': {
'min_amount': 0.000001,
'min_price': 0.01,
'min_order_value': 1
},
'ETHUSDT': {
'min_amount': 0.00001,
'min_price': 0.01,
'min_order_value': 1
},
from .exceptions import BinanceOrderUnknownSymbolException, \
BinanceOrderInactiveSymbolException, \
BinanceOrderMinPriceException, \
BinanceOrderMinAmountException, \
BinanceOrderMinTotalException

"""
Use details from
https://www.binance.com/exchange/public/product
minTrade means min Amount
ticksize means min price
Notional limits from https://binance.zendesk.com/hc/en-us/articles/115000594711
BTC - 0.001
ETH - 0.01
USDT - 1
"""

NOTIONAL_LIMITS = {
'BTC': 0.001,
'ETH': 0.01,
'USDT': 1
}


def validate_order(params):
limits = TRADE_LIMITS[params['symbol']]
def validate_order(params, products):
print(params)
if params['symbol'] not in products:
raise BinanceOrderUnknownSymbolException(params['symbol'])

limits = products[params['symbol']]

if not limits['active']:
raise BinanceOrderInactiveSymbolException(params['symbol'])

price = float(params['price'])
quantity = float(params['quantity'])
# check price
if price < limits['min_price'] != 0:
raise BinanceOrderMinPriceException(limits['min_price'])
if price < float(limits['tickSize']) != 0:
raise BinanceOrderMinPriceException(limits['tickSize'])

# check order amount
if quantity % limits['min_amount'] != 0.0:
raise BinanceOrderMinAmountException(limits['min_amount'])
min_trade = float(limits['minTrade'])
if quantity / min_trade - int(quantity / min_trade) > 0.0:
raise BinanceOrderMinAmountException(limits['minTrade'])

# check order total
total = float(params['price']) * float(params['quantity'])
if total < limits['min_order_value']:
raise BinanceOrderMinTotalException(limits['min_order_value'])
notional_total = NOTIONAL_LIMITS[limits['quoteAsset']]
if total < notional_total:
raise BinanceOrderMinTotalException(notional_total)
3 changes: 3 additions & 0 deletions binance/websockets.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env python
# coding=utf-8

import json
import threading

Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[bdist_wheel]
universal = 1

[pep8]
ignore = E501

0 comments on commit b15e7a2

Please sign in to comment.