Skip to content

Commit

Permalink
[feat] handle conversions between qty and total by default
Browse files Browse the repository at this point in the history
  • Loading branch information
aarjaneiro committed Mar 21, 2024
1 parent 30b8ea4 commit a6215b8
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 8 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

setup(
name='vcx_py',
version='1.0.6',
version='1.1.0',
packages=['vcx_py'],
license='MIT',
author='Aaron Janeiro Stone',
Expand Down
81 changes: 74 additions & 7 deletions vcx_py/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@
# SOFTWARE.

from typing import Optional
from threading import Lock
from warnings import warn

import requests as rq

from .constants import ROOT_ADDRESS, VERIFICATION, KLineType, OrderStatus, \
OrderType, OrderDirection
from .utils import vcx_sign, result_formatter
from .utils import VirgoCXWarning, VirgoCXException, vcx_sign, result_formatter


class VirgoCXClient:
"""
A simple python client for the VirgoCX API.
"""
FMT_DATA = None # created by first instance
STATIC_LOCK = Lock() # in case of multithreading, locks the formatting cache

def __init__(self, api_key: str = None, api_secret: str = None):
# Prevents the api key and secret from being visible as class attributes
def _api_key():
Expand All @@ -28,6 +36,13 @@ def signer(dct: dict):
self.signer = signer
self._api_key = _api_key

with VirgoCXClient.STATIC_LOCK:
if VirgoCXClient.FMT_DATA is None:
VirgoCXClient.FMT_DATA = {v["symbol"]: {"price_decimals": v["priceDecimals"],
"qty_decimals": v["qtyDecimals"],
"min_total": v["minTotal"]} for v in
self.tickers()}

@result_formatter()
def kline(self, symbol: str, period: KLineType):
"""
Expand Down Expand Up @@ -96,7 +111,8 @@ def query_trades(self, symbol: str):
@result_formatter()
def place_order(self, symbol: str, category: OrderType, direction: OrderDirection,
price: Optional[float] = None, qty: Optional[float] = None,
total: Optional[float] = None):
total: Optional[float] = None, handle_conversions: bool = True,
**kwargs):
"""
Places an order.
Expand All @@ -106,35 +122,86 @@ def place_order(self, symbol: str, category: OrderType, direction: OrderDirectio
:param price: The price of the order (optional).
:param qty: The quantity of the order in terms of the cryptocurrency (optional).
:param total: The total value of the order in terms of the fiat currency (optional).
:param handle_conversions: Whether to handle conversions between `qty`, and `total` (optional, default True).
Might require additional API calls in order to determine the market price.
Note that `price` is required for limit orders and `total` is required for non-limit buy orders
(otherwise `qty` is required).
Note that without `handle_conversions`, `price` is required for limit orders and `total` is required for
non-limit buy orders (otherwise `qty` is required).
"""
if isinstance(category, OrderType):
category = category.value
if isinstance(direction, OrderDirection):
direction = direction.value

# Handle conversions
if category == OrderType.LIMIT:
if price is None:
raise ValueError("Price is required for limit orders")
if qty is None:
raise ValueError("Quantity is required for limit orders")
else:
if not handle_conversions:
raise ValueError("Quantity is required for limit orders")
else:
qty = total / price
total = None
else: # i.e., quick trade or market order
if total is None and direction == OrderDirection.BUY:
raise ValueError("Total is required for non-limit buy orders")
if not handle_conversions:
raise ValueError("Total is required for non-limit buy orders")
else:
market_price = kwargs.get("market_price", None)
if market_price is None:
market_price = self.__extract_market_price__(direction, symbol)
total = qty * market_price
qty = None

payload = {"apiKey": self._api_key(), "symbol": symbol, "category": category, "type": direction,
"country": 1}

with VirgoCXClient.STATIC_LOCK:
if symbol in VirgoCXClient.FMT_DATA:
fmt_data = VirgoCXClient.FMT_DATA[symbol]
else:
raise VirgoCXException(f"Symbol {symbol} not found in formatting cache")

# Format and check values
if price is not None:
price = float(price) # can be int which breaks decimal places check
if len(str(price).split(".")[1]) > fmt_data["price_decimals"]:
warn(f"Price {price} has more than {fmt_data['price_decimals']} decimal places. Correcting...",
VirgoCXWarning)
price = round(price, fmt_data["price_decimals"])
payload["price"] = price

if qty is not None:
qty = float(qty)
if len(str(qty).split(".")[1]) > fmt_data["qty_decimals"]:
warn(f"Quantity {qty} has more than {fmt_data['qty_decimals']} decimal places. Correcting...",
VirgoCXWarning)
qty = round(qty, fmt_data["qty_decimals"])
payload["qty"] = qty

if total is not None:
total = float(total)
if total < fmt_data["min_total"]:
raise ValueError(f"Total {total} is below the minimum allowed {fmt_data['min_total']}")
if len(str(total).split(".")[1]) > fmt_data["price_decimals"]:
warn(f"Total {total} has more than {fmt_data['price_decimals']} decimal places. Correcting...",
VirgoCXWarning)
total = round(total, fmt_data["price_decimals"])
payload["total"] = total

# Sign and send request
payload["sign"] = self.signer(payload)
return rq.post(f"{ROOT_ADDRESS}/member/addOrder", data=payload, verify=VERIFICATION)

def __extract_market_price__(self, direction, symbol):
market_price = self.get_discount(symbol=symbol)[0]
if direction == OrderDirection.BUY:
market_price = float(market_price["Ask"])
else:
market_price = float(market_price["Bid"])
return market_price

@result_formatter()
def cancel_order(self, order_id: str):
"""
Expand Down
7 changes: 7 additions & 0 deletions vcx_py/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
from vcx_py.constants import TYPICAL_KEY_TO_ENUM, ATYPICAL_KEY_TO_ENUM


class VirgoCXWarning(Warning):
"""
Base warning for the VirgoCX API.
"""
pass # overrides allow for optional fine-grained control


class VirgoCXException(Exception):
"""
Base exception for the VirgoCX API.
Expand Down

0 comments on commit a6215b8

Please sign in to comment.