Fixed tests, updated transaction structs

api-breakage
Morty Space 5 years ago
parent 42ffe96ae4
commit fe5e088d0a
  1. 2
      azure-pipelines.yml
  2. 2
      docs/source/conf.py
  3. 6
      src/cryptocom/exchange/__init__.py
  4. 2
      src/cryptocom/exchange/api.py
  5. 7
      src/cryptocom/exchange/market.py
  6. 50
      src/cryptocom/exchange/private.py
  7. 97
      src/cryptocom/exchange/structs.py
  8. 8
      tests/test_api.py
  9. 6
      tests/test_market.py
  10. 30
      tests/test_private.py

@ -18,7 +18,7 @@ variables:
value: 16bcfb0958d99f11456f8d80aeb5800d567724471e151fe6e74a4b329b45dcb6 value: 16bcfb0958d99f11456f8d80aeb5800d567724471e151fe6e74a4b329b45dcb6
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'debian-latest'
strategy: strategy:
maxParallel: 1 maxParallel: 1

@ -18,7 +18,7 @@ from cryptocom.exchange import __version__
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'cryptocom-exchange' project = 'cryptocom-exchange'
copyright = '2020, goincrypto.com' copyright = '2020-2021'
author = 'Yaroslav Rudenok [MortySpace]' author = 'Yaroslav Rudenok [MortySpace]'
# The short X.Y version # The short X.Y version

@ -1,6 +1,7 @@
from .structs import ( from .structs import (
Order, OrderSide, OrderStatus, OrderType, Pair, Period, Candle, Order, OrderSide, OrderStatus, OrderType, Pair, Period, Candle,
MarketTrade, Coin, PrivateTrade MarketTrade, Coin, PrivateTrade, Timeframe, DepositStatus, Deposit,
WithdrawalStatus, Withdrawal
) )
from .market import Exchange from .market import Exchange
from .private import Account from .private import Account
@ -11,8 +12,9 @@ __all__ = [
'Order', 'OrderSide', 'OrderStatus', 'OrderType', 'Order', 'OrderSide', 'OrderStatus', 'OrderType',
'pairs', 'Pair', 'coins', 'Coin', 'pairs', 'Pair', 'coins', 'Coin',
'Period', 'Candle', 'MarketTrade', 'PrivateTrade', 'Period', 'Candle', 'MarketTrade', 'PrivateTrade',
'Timeframe', 'Deposit', 'Withdrawal', 'DepositStatus', 'WithdrawalStatus',
'Exchange', 'Account', 'Exchange', 'Account',
'ApiError', 'ApiProvider' 'ApiError', 'ApiProvider'
] ]
__version__ = '0.8.1' __version__ = '0.9'

@ -33,7 +33,7 @@ class ApiProvider:
self.retries = retries self.retries = retries
# NOTE: do not change this, due to crypto.com rate-limits # NOTE: do not change this, due to crypto.com rate-limits
self.semaphore = asyncio.Semaphore(40) self.semaphore = asyncio.Semaphore(20)
if not auth_required: if not auth_required:
return return

@ -58,10 +58,10 @@ class Exchange:
'public/get-trades', {'instrument_name': pair.name}) 'public/get-trades', {'instrument_name': pair.name})
return [MarketTrade.from_api(pair, trade) for trade in reversed(data)] return [MarketTrade.from_api(pair, trade) for trade in reversed(data)]
async def get_orderbook(self, pair: Pair, depth: int = 150) -> OrderBook: async def get_orderbook(self, pair: Pair) -> OrderBook:
"""Get the order book for a particular market.""" """Get the order book for a particular market, depth always 150."""
data = await self.api.get('public/get-book', { data = await self.api.get('public/get-book', {
'instrument_name': pair.name, 'depth': depth}) 'instrument_name': pair.name})
buys = [ buys = [
OrderInBook(*order, pair, OrderSide.BUY) OrderInBook(*order, pair, OrderSide.BUY)
for order in data[0]['bids'] for order in data[0]['bids']
@ -75,7 +75,6 @@ class Exchange:
async def get_candles(self, pair: Pair, period: Period) -> List[Candle]: async def get_candles(self, pair: Pair, period: Period) -> List[Candle]:
data = await self.api.get('public/get-candlestick', { data = await self.api.get('public/get-candlestick', {
'instrument_name': pair.name, 'timeframe': period.value}) 'instrument_name': pair.name, 'timeframe': period.value})
return [Candle.from_api(pair, candle) for candle in data] return [Candle.from_api(pair, candle) for candle in data]
async def listen_candles( async def listen_candles(

@ -5,9 +5,9 @@ from typing import List, Dict
from .api import ApiProvider, ApiError from .api import ApiProvider, ApiError
from .market import Exchange from .market import Exchange
from .structs import ( from .structs import (
Pair, OrderSide, OrderStatus, OrderType, Order, Coin, Balance, Deposit, DepositStatus, Pair, OrderSide, OrderStatus, OrderType, Order,
OrderExecType, OrderForceType, PrivateTrade, Interest, Withdrawal, Coin, Balance, OrderExecType, OrderForceType, PrivateTrade, Interest,
Deposit Withdrawal, WithdrawalStatus
) )
@ -38,57 +38,60 @@ class Account:
} }
async def get_deposit_history( async def get_deposit_history(
self, coin: Coin, start_ts: int, end_ts: int, status: int, page: int = 0, page_size: int = 20)\ self, coin: Coin, start_ts: int = None, end_ts: int = None,
-> List[Deposit]: status: DepositStatus = None, page: int = 0, page_size: int = 20
) -> List[Deposit]:
"""Return all history withdrawals.""" """Return all history withdrawals."""
params = {'page_size': page_size, 'page': page} params = {'page_size': page_size, 'page': page}
if coin: if coin:
params['currency'] = coin.name params['currency'] = coin.name
if start_ts: if start_ts:
params['start_ts'] = start_ts params['start_ts'] = int(start_ts) * 1000
if end_ts: if end_ts:
params['end_ts'] = end_ts params['end_ts'] = int(end_ts) * 1000
if status: if status:
params['status'] = str(status) params['status'] = status
data = await self.api.post( data = await self.api.post(
'private/get-deposit-history', {'params': params}) or {} 'private/get-deposit-history', {'params': params}) or {}
return [ return [
Deposit.create_from_api(deposit) Deposit.create_from_api(trx)
for deposit in data.get('deposit_list') or [] for trx in data.get('deposit_list') or []
] ]
async def get_withdrawal_history( async def get_withdrawal_history(
self, coin: Coin, start_ts: int, end_ts: int, status: int, page: int = 0, page_size: int = 20)\ self, coin: Coin, start_ts: int = None, end_ts: int = None,
-> List[Withdrawal]: status: WithdrawalStatus = None, page: int = 0, page_size: int = 20
"""Return all history withdrawals.""" ) -> List[Withdrawal]:
"""Return all history for withdrawal transactions."""
params = {'page_size': page_size, 'page': page} params = {'page_size': page_size, 'page': page}
if coin: if coin:
params['currency'] = coin.name params['currency'] = coin.name
if start_ts: if start_ts:
params['start_ts'] = start_ts params['start_ts'] = int(start_ts) * 1000
if end_ts: if end_ts:
params['end_ts'] = end_ts params['end_ts'] = int(end_ts) * 1000
if status: if status:
params['status'] = str(status) params['status'] = status
data = await self.api.post( data = await self.api.post(
'private/get-withdrawal-history', {'params': params}) or {} 'private/get-withdrawal-history', {'params': params}) or {}
return [ return [
Withdrawal.create_from_api(withdrawal) Withdrawal.create_from_api(trx)
for withdrawal in data.get('withdrawal_list') or [] for trx in data.get('withdrawal_list') or []
] ]
async def get_interest_history( async def get_interest_history(
self, coin: Coin, start_ts: int, end_ts: int, page: int = 0, page_size: int = 20) -> List[Interest]: self, coin: Coin, start_ts: int = None, end_ts: int = None,
page: int = 0, page_size: int = 20) -> List[Interest]:
"""Return all history interest.""" """Return all history interest."""
params = {'page_size': page_size, 'page': page} params = {'page_size': page_size, 'page': page}
if coin: if coin:
params['currency'] = coin.name params['currency'] = coin.name
if start_ts: if start_ts:
params['start_ts'] = start_ts params['start_ts'] = int(start_ts) * 1000
if end_ts: if end_ts:
params['end_ts'] = end_ts params['end_ts'] = int(end_ts) * 1000
data = await self.api.post( data = await self.api.post(
'private/margin/get-order-history', {'params': params}) or {} 'private/margin/get-order-history', {'params': params}) or {}
@ -105,9 +108,10 @@ class Account:
if pair: if pair:
params['instrument_name'] = pair.name params['instrument_name'] = pair.name
if start_ts: if start_ts:
params['start_ts'] = start_ts params['start_ts'] = int(start_ts) * 1000
if end_ts: if end_ts:
params['end_ts'] = end_ts params['end_ts'] = int(end_ts) * 1000
data = await self.api.post( data = await self.api.post(
'private/get-order-history', {'params': params}) or {} 'private/get-order-history', {'params': params}) or {}
return [ return [

@ -1,8 +1,9 @@
import time import time
from enum import Enum from enum import Enum, IntEnum
from typing import List, Dict from typing import List, Dict
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from cached_property import cached_property from cached_property import cached_property
@ -368,53 +369,87 @@ class Interest:
) )
class WithdrawalStatus(str, Enum):
PENDING = '0'
PROCESSING = '1'
REJECTED = '2'
PAYMENT_IN_PROGRESS = '3'
PAYMENT_FAILED = '4'
COMPLETED = '5'
CANCELLED = '6'
class DepositStatus(str, Enum):
NOT_ARRIVED = '0'
ARRIVED = '1'
FAILED = '2'
PENDING = '3'
class TransactionType(IntEnum):
WITHDRAWAL = 0
DEPOSIT = 1
@dataclass @dataclass
class Withdrawal: class Transaction:
coin: Coin coin: Coin
client_wid: str
fee: float fee: float
create_time: int create_time: int
id: str id: str
update_time: int update_time: int
amount: float amount: float
address: str address: str
status: str
@classmethod @staticmethod
def create_from_api(cls, data: Dict) -> 'Withdrawal': def _prepare(data):
return cls( return dict(
id=data['id'],
coin=Coin(data['currency']), coin=Coin(data['currency']),
client_wid=data['client_wid'],
fee=float(data['fee']), fee=float(data['fee']),
create_time=data['create_time'], create_time=datetime.fromtimestamp(
id=data['id'], int(data['create_time']) / 1000),
update_time=data['update_time'], update_time=datetime.fromtimestamp(
int(data['update_time']) / 1000),
amount=float(data['amount']), amount=float(data['amount']),
address=data['address'], address=data['address'],
status=data['status']
) )
@dataclass @dataclass
class Deposit: class Deposit(Transaction):
coin: Coin status: DepositStatus
fee: float
create_time: int
id: str
update_time: int
amount: float
address: str
status: str
@classmethod @classmethod
def create_from_api(cls, data: Dict) -> 'Deposit': def create_from_api(cls, data: Dict) -> 'Deposit':
return cls( params = cls._prepare(data)
coin=Coin(data['currency']), params['status'] = DepositStatus(data['status'])
fee=float(data['fee']), return cls(**params)
create_time=data['create_time'],
id=data['id'],
update_time=data['update_time'], @dataclass
amount=float(data['amount']), class Withdrawal(Transaction):
address=data['address'], client_wid: str
status=data['status'] status: WithdrawalStatus
) txid: str
@classmethod
def create_from_api(cls, data: Dict) -> 'Withdrawal':
params = cls._prepare(data)
params['client_wid'] = data.get('client_wid', '')
params['status'] = WithdrawalStatus(data['status'])
params['txid'] = data['txid']
return cls(**params)
class Timeframe(IntEnum):
NOW = 0
MINUTES = 60
HOURS = 60 * MINUTES
DAYS = 24 * HOURS
WEEKS = 7 * DAYS
MONTHS = 30 * DAYS
@classmethod
def resolve(cls, seconds: int) -> int:
return seconds + int(time.time())

@ -1,9 +1,17 @@
import os import os
import time
import pytest import pytest
import cryptocom.exchange as cro import cryptocom.exchange as cro
def test_timeframe():
days_5 = cro.Timeframe.DAYS * 5
result = cro.Timeframe.resolve(days_5)
assert result - int(time.time()) == days_5
assert cro.Timeframe.resolve(cro.Timeframe.NOW) == int(time.time())
def test_api_args(monkeypatch): def test_api_args(monkeypatch):
with pytest.raises(ValueError): with pytest.raises(ValueError):
cro.Account() cro.Account()

@ -41,8 +41,8 @@ async def test_get_price(exchange: cro.Exchange):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_orderbook(exchange: cro.Exchange): async def test_get_orderbook(exchange: cro.Exchange):
depth = 50 depth = 150
book = await exchange.get_orderbook(cro.pairs.CRO_USDT, depth=depth) book = await exchange.get_orderbook(cro.pairs.CRO_USDT)
assert book.buys and book.sells assert book.buys and book.sells
assert book.sells[0].price > book.buys[0].price assert book.sells[0].price > book.buys[0].price
assert book.spread > 0 assert book.spread > 0
@ -62,7 +62,7 @@ async def test_listen_candles(exchange: cro.Exchange):
candles = [] candles = []
pairs = (cro.pairs.CRO_USDC, cro.pairs.USDC_USDT, cro.pairs.BTC_USDT) pairs = (cro.pairs.CRO_USDC, cro.pairs.USDC_USDT, cro.pairs.BTC_USDT)
count = 0 count = 0
default_count = 600 default_count = 2
async for candle in exchange.listen_candles(cro.Period.MINS, *pairs): async for candle in exchange.listen_candles(cro.Period.MINS, *pairs):
candles.append(candle) candles.append(candle)

@ -1,8 +1,12 @@
import time
import asyncio import asyncio
import pytest import pytest
import cryptocom.exchange as cro import cryptocom.exchange as cro
from cryptocom.exchange.structs import (
DepositStatus, Timeframe, WithdrawalStatus
)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -23,6 +27,32 @@ async def test_account_get_balance(account: cro.Account):
assert coin in local_coins assert coin in local_coins
@pytest.mark.asyncio
async def test_deposit_withdrawal_history(
exchange: cro.Exchange, account: cro.Account):
transactions = await account.get_withdrawal_history(cro.coins.CRO)
assert transactions
assert transactions[0].status == cro.WithdrawalStatus.COMPLETED
transactions = await account.get_deposit_history(cro.coins.CRO)
assert transactions
assert transactions[0].status == cro.DepositStatus.ARRIVED
transactions = await account.get_deposit_history(
cro.coins.CRO, status=DepositStatus.NOT_ARRIVED)
assert not transactions
transactions = await account.get_withdrawal_history(
cro.coins.CRO, status=WithdrawalStatus.CANCELLED)
assert not transactions
transactions = await account.get_deposit_history(
cro.coins.CRO, start_ts=time.time() - Timeframe.DAYS * 5,
end_ts=Timeframe.resolve(Timeframe.NOW)
)
assert not transactions
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_no_duplicate_mass_limit_orders( async def test_no_duplicate_mass_limit_orders(
exchange: cro.Exchange, account: cro.Account): exchange: cro.Exchange, account: cro.Account):

Loading…
Cancel
Save