Skip to content

Commit

Permalink
add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
reddec committed Mar 5, 2019
1 parent e476a5a commit 64dceb0
Show file tree
Hide file tree
Showing 9 changed files with 476 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ venv
test.py
build
dist
*.egg-info
*.egg-info
docs/_build
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Documentation requirements

* sphinx
* sphinx-autodoc-typehints
82 changes: 74 additions & 8 deletions crix/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@


class APIError(RuntimeError):
"""
General exception for API calls
"""

operation: str #: operation name
code: int #: HTTP response code
text: str #: error description

def __init__(self, operation: str, code: int, text: str) -> None:
self.code = code
Expand All @@ -17,11 +24,25 @@ def __init__(self, operation: str, code: int, text: str) -> None:

@staticmethod
def ensure(operation: str, req: requests.Response):
"""
Ensure status code of HTTP request and raise exception if needed
:param operation: logical operation name
:param req: request's response object
"""
if req.status_code not in (200, 204):
raise APIError(operation, req.status_code, req.text)


class Client:
"""
HTTP client to the exchange for non-authorized requests.
Supported environments:
- 'mvp' - testnet sandbox with full-wipe each 2nd week (usually)
- 'prod' - mainnet, production environment with real currency
"""

def __init__(self, *, env: str = 'mvp'):
self.environment = env
Expand All @@ -34,6 +55,7 @@ def __init__(self, *, env: str = 'mvp'):
def fetch_currency_codes(self) -> List[str]:
"""
Get list of currencies codes in quote_base format (ex. btc_bch)
:return: list of formatted currencies codes
"""
return [(sym.base + "_" + sym.quote).lower() for sym in self.fetch_markets()]
Expand All @@ -42,6 +64,7 @@ def fetch_markets(self) -> List[Symbol]:
"""
Get list of all symbols on the exchange. Also includes symbol details like precision, quote, base and e.t.c.
It's a good idea to cache result of this function after first invoke
:return: list of supported symbols
"""
symbols = []
Expand All @@ -55,6 +78,7 @@ def fetch_markets(self) -> List[Symbol]:
def fetch_order_book(self, symbol: str, level_aggregation: Optional[int] = None) -> Depth:
"""
Get order book for specific symbol and level aggregation
:param symbol: interesting symbol name
:param level_aggregation: aggregate by rounding numbers (if not defined - no aggregation)
:return: order depth book
Expand All @@ -73,6 +97,7 @@ def fetch_order_book(self, symbol: str, level_aggregation: Optional[int] = None)
def fetch_ticker(self) -> List[Ticker24]:
"""
Get tickers for all symbols for the last 24 hours
:return: list of tickers
"""
tickers = []
Expand All @@ -88,6 +113,7 @@ def fetch_ohlcv(self, symbol: str, utc_start_time: datetime, utc_end_time: datet
limit: int = 10) -> List[Ticker]:
"""
Get K-Lines for specific symbol in a time frame
:param symbol: K-Line symbol name
:param utc_start_time: earliest interesting time
:param utc_end_time: latest interesting time
Expand All @@ -113,6 +139,17 @@ def fetch_ohlcv(self, symbol: str, utc_start_time: datetime, utc_end_time: datet


class AuthorizedClient(Client):
"""
HTTP client to the exchange for non-authorized and authorized requests.
Supported environments:
- 'mvp' - testnet sandbox with full-wipe each 2nd week (usually)
- 'prod' - mainnet, production environment with real currency
Expects API token and API secret provided by CRIX.IO exchange as
part of bot API.
"""

def __init__(self, token: str, secret: str, *, env: str = 'mvp'):
super().__init__(env=env)
Expand All @@ -121,8 +158,15 @@ def __init__(self, token: str, secret: str, *, env: str = 'mvp'):

def fetch_open_orders(self, *symbols: str, limit: int = 1000) -> Iterator[Order]:
"""
Get all open orders for the user
:param symbols: filter orders by symbols. if not specified - used all symbols
Get all open orders for the user.
.. note::
One request per each symbol will be made plus additional
request to query all supported symbols if symbols parameter
not specified.
:param symbols: filter orders by symbols. if not specified - all symbols queried and used
:param limit: maximum number of orders for each symbol
:return: iterator of orders definitions
"""
Expand All @@ -141,7 +185,13 @@ def fetch_open_orders(self, *symbols: str, limit: int = 1000) -> Iterator[Order]
def fetch_closed_orders(self, *symbols: str, limit: int = 1000) -> Iterator[Order]:
"""
Get complete (filled, canceled) orders for user
:param symbols: filter orders by symbols. if not specified - used all symbols
.. note::
One request per each symbol will be made plus additional
request to query all supported symbols if symbols parameter
not specified.
:param symbols: filter orders by symbols. if not specified - all symbols queried and used
:param limit: maximum number of orders for each symbol
:return: iterator of orders definitions
"""
Expand All @@ -162,6 +212,11 @@ def fetch_orders(self, *symbols: str, limit: int = 1000) -> Iterator[Order]:
Get opened and closed orders filtered by symbols. If no symbols specified - all symbols are used.
Basically the function acts as union of fetch_open_orders and fetch_closed_orders.
.. note::
Two requests per each symbol will be made plus additional
request to query all supported symbols if symbols parameter
not specified.
:param symbols: symbols: filter orders by symbols. if not specified - used all symbols
:param limit: maximum number of orders for each symbol for each state (open, close)
:return: iterator of orders definitions sorted from open to close
Expand All @@ -178,6 +233,12 @@ def fetch_my_trades(self, *symbols: str, limit: int = 1000) -> Iterator[Trade]:
"""
Get all trades for the user. There is some gap (a few ms) between time when trade is actually created and time
when it becomes visible for the user.
.. note::
One request per each symbol will be made plus additional
request to query all supported symbols if symbols parameter
not specified.
:param symbols: filter trades by symbols. if not specified - used all symbols
:param limit: maximum number of trades for each symbol
:return: iterator of trade definition
Expand All @@ -197,6 +258,7 @@ def fetch_my_trades(self, *symbols: str, limit: int = 1000) -> Iterator[Trade]:
def fetch_balance(self) -> List[Account]:
"""
Get all balances for the user
:return: list of all accounts
"""
response = self.__signed_request('fetch-balance', self._base_url + '/user/accounts', {})
Expand All @@ -205,6 +267,7 @@ def fetch_balance(self) -> List[Account]:
def cancel_order(self, order_id: int, symbol: str) -> Order:
"""
Cancel placed order
:param order_id: order id generated by the exchange
:param symbol: symbol names same as in placed order
:return: order definition with filled field (also includes filled quantity)
Expand All @@ -220,6 +283,7 @@ def cancel_order(self, order_id: int, symbol: str) -> Order:
def create_order(self, new_order: NewOrder) -> Order:
"""
Create and place order to the exchange
:param new_order: order parameters
:return: order definition with filled fields from the exchange
"""
Expand All @@ -231,6 +295,7 @@ def create_order(self, new_order: NewOrder) -> Order:
def fetch_order(self, order_id: int, symbol_name: str) -> Optional[Order]:
"""
Fetch single open order info
:param order_id: order id generated by server during 'create_order' phase
:param symbol_name: symbol name same as in order
:return: order definition or None if nothing found
Expand All @@ -253,11 +318,12 @@ def fetch_history(self, begin: datetime, end: datetime, currency: str) -> Iterat
"""
Get historical minute tickers for specified time range and currency
There are several caveats:
* it requires additional permission
* end param should be not more then server time, otherwise error returned
* maximum difference between earliest and latest date should be no more then 366 days
* it could be slow for a long time range
* mostly all points have 1 minute tick however in a very few cases gap can be a bit bigger
- it requires additional permission
- end param should be not more then server time, otherwise error returned
- maximum difference between earliest and latest date should be no more then 366 days
- it could be slow for a long time range
- mostly all points have 1 minute tick however in a very few cases gap can be a bit bigger
:param begin: earliest interesting time
:param end: latest interesting time
Expand Down
58 changes: 54 additions & 4 deletions crix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Symbol(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Symbol':
"""
Construct object from dictionary
"""
return Symbol(
name=info['symbolName'],
base=info['base'],
Expand All @@ -49,7 +52,7 @@ def from_json(info: dict) -> 'Symbol':
class Ticker(NamedTuple):
symbol_name: str
open_time: datetime
open: Decimal
open: Decimal # type: Decimal
close: Decimal
high: Decimal
low: Decimal
Expand All @@ -58,6 +61,9 @@ class Ticker(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Ticker':
"""
Construct object from dictionary
"""
return Ticker(
symbol_name=info['symbolName'],
open_time=datetime.fromtimestamp(info['openTime'] / 1000.0),
Expand All @@ -71,6 +77,9 @@ def from_json(info: dict) -> 'Ticker':

@staticmethod
def from_json_history(info: dict) -> 'Ticker':
"""
Construct object from dictionary (for a fixed resolution)
"""
return Ticker(
symbol_name=info['currency'],
open_time=datetime.fromtimestamp(info['timestamp']),
Expand Down Expand Up @@ -101,6 +110,9 @@ class Ticker24(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Ticker24':
"""
Construct object from dictionary
"""
return Ticker24(
symbol_name=info['symbolName'],
open_time=datetime.fromtimestamp(info['openTime'] / 1000.0),
Expand All @@ -125,6 +137,9 @@ class Offer(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Offer':
"""
Construct object from dictionary
"""
return Offer(
count=info['c'],
price=Decimal(info['p']),
Expand All @@ -142,6 +157,9 @@ class Depth(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Depth':
"""
Construct object from dictionary
"""
return Depth(
symbol_name=info['symbolName'],
level_aggregation=info['levelAggregation'],
Expand Down Expand Up @@ -192,6 +210,9 @@ class Order(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Order':
"""
Construct object from dictionary
"""
return Order(
id=info['orderId'],
user_id=info['userId'],
Expand All @@ -218,6 +239,9 @@ class NewOrder(NamedTuple):
expire_time: Optional[datetime] = None

def to_json(self) -> dict:
"""
Build JSON package ready to send to the API endpoint
"""
req = {
"isBuy": self.is_buy,
"price": str(self.price),
Expand All @@ -232,15 +256,35 @@ def to_json(self) -> dict:
return req

@staticmethod
def limit(symbol: str, is_buy: bool, price: Decimal, quantity: Decimal, **args) -> 'NewOrder':
def limit(symbol: str, is_buy: bool, price: Union[Decimal, float, str], quantity: Union[Decimal, float, str],
**args) -> 'NewOrder':
"""
Helper to create basic limit order
:param symbol: symbol name as defined by the exchange
:param is_buy: order direction
:param price: order price
:param quantity: number of items in the order
:param args: additional parameters proxied to the NewOrder constructor
:return: new order
"""
return NewOrder(symbol=symbol,
price=price,
quantity=quantity,
price=Decimal(price),
quantity=Decimal(quantity),
is_buy=is_buy,
**args)

@staticmethod
def market(symbol: str, is_buy: bool, quantity: Union[Decimal, float, str], **args) -> 'NewOrder':
"""
Helper to create basic market order
:param symbol: symbol name as defined by the exchange
:param is_buy: order direction
:param quantity: number of items
:param args: additional parameters proxied to the NewOrder constructor
:return: new order
"""
return NewOrder(symbol=symbol,
price=Decimal('0'),
quantity=Decimal(quantity),
Expand Down Expand Up @@ -269,6 +313,9 @@ class Trade(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Trade':
"""
Construct object from dictionary
"""
return Trade(
id=info['id'],
created_at=datetime.fromtimestamp(info['createdAt'] / 1000),
Expand Down Expand Up @@ -298,6 +345,9 @@ class Account(NamedTuple):

@staticmethod
def from_json(info: dict) -> 'Account':
"""
Construct object from dictionary
"""
return Account(
id=info['id'],
user_id=info['userId'],
Expand Down
19 changes: 19 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
8 changes: 8 additions & 0 deletions docs/clients.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. _clients:

API clients
================================

.. automodule:: crix.client
:members:
:undoc-members:

0 comments on commit 64dceb0

Please sign in to comment.