1
0

Compare commits

..

2 Commits

Author SHA1 Message Date
39cee8c601
Ruff
Some checks failed
Python Ruff Code Quality / ruff (push) Failing after 23s
Run Python tests (through Pytest) / Test (push) Successful in 28s
Verify Python project can be installed, loaded and have version checked / Test (push) Successful in 24s
2024-11-28 00:45:46 +01:00
c1ae958458
Laying groundwork for both tax accounting and for repository history 2024-11-28 00:15:57 +01:00
4 changed files with 127 additions and 67 deletions

View File

@ -6,7 +6,7 @@ from decimal import Decimal
from typing import TypeVar
import enforce_typing
from fin_defs import Asset
from fin_defs import Asset, AssetAmount
@enforce_typing.enforce_types
@ -124,40 +124,36 @@ class TradeOrderDetails:
- `raw_order_details`: For storing arbitrary unstructured data from backend.
"""
input_asset: Asset
input_amount: Decimal
output_asset: Asset
output_amount: Decimal
fee_asset: Asset
fee_amount: Decimal
input: AssetAmount
output: AssetAmount
fee: AssetAmount
executed_time: datetime.datetime
order_id: object
raw_order_details: object
@enforce_typing.enforce_types
@dataclasses.dataclass(frozen=True)
class WithdrawalDetails:
withdrawn_asset: Asset
withdrawn_amount: Decimal
fee_asset: Asset
fee_amount: Decimal
withdrawn: AssetAmount
fee: AssetAmount
executed_time: datetime.datetime
raw_details: object
@enforce_typing.enforce_types
@dataclasses.dataclass(frozen=True)
class DepositDetails:
deposit_asset: Asset
deposit_amount: Decimal
fee_asset: Asset
fee_amount: Decimal
deposit: AssetAmount
fee: AssetAmount
executed_time: datetime.datetime
raw_details: object
@enforce_typing.enforce_types
@dataclasses.dataclass(frozen=True)
class DoubleRegister:
input: AssetAmount | None
output: AssetAmount | None
executed_time: datetime.datetime

View File

@ -3,13 +3,22 @@
import datetime
import logging
import time
from collections.abc import Iterator
from decimal import Decimal
import fin_defs
import kucoin.client
from fin_defs import AssetAmount
from .data import (DepoFetcher, DepoGroup, DepoSingle, TradeOrderDetails,
WithdrawalDetails, DepositDetails)
from .data import (
DepoFetcher,
DepoGroup,
DepoSingle,
DepositDetails,
DoubleRegister,
TradeOrderDetails,
WithdrawalDetails,
)
logger = logging.getLogger(__name__)
@ -20,6 +29,36 @@ def parse_asset_from_ticker(ticker: str) -> fin_defs.Asset:
return fin_defs.WELL_KNOWN_SYMBOLS[ticker]
def order_from_json(order_details: dict):
(asset, base_asset) = [
parse_asset_from_ticker(a) for a in order_details['symbol'].split('-')
]
# Convert from kucoin results
if order_details['side'] == 'buy':
input_asset, output_asset = base_asset, asset
input_amount_final = Decimal(order_details['dealFunds'])
output_amount_final = Decimal(order_details['dealSize'])
else:
input_asset, output_asset = asset, base_asset
output_amount_final = Decimal(order_details['dealFunds'])
input_amount_final = Decimal(order_details['dealSize'])
fee_asset = parse_asset_from_ticker(order_details['feeCurrency'])
return TradeOrderDetails(
input=AssetAmount(input_asset, input_amount_final),
output=AssetAmount(output_asset, output_amount_final),
fee=AssetAmount(fee_asset, Decimal(order_details['fee'])),
executed_time=datetime.datetime.fromtimestamp(
order_details['createdAt'] / 1000,
tz=datetime.UTC,
),
order_id=order_details['id'],
raw_order_details=order_details,
)
class KucoinDepoFetcher(DepoFetcher):
"""`Depo` fetcher for [Kucoin](https://www.kucoin.com), the online crypto currency exchange.
@ -145,7 +184,7 @@ class KucoinDepoFetcher(DepoFetcher):
del symbol, side, size, funds, input_amount
# Determine order details
return self._get_order_details(response['orderId'], input_asset, output_asset)
return self._get_order_details(response['orderId'])
def _get_withdrawals(self) -> list[WithdrawalDetails]:
raw_details = self.kucoin_client.get_withdrawals()
@ -154,48 +193,82 @@ class KucoinDepoFetcher(DepoFetcher):
for item in raw_details['items']:
withdrawn_asset = parse_asset_from_ticker(item['currency'])
withdrawn_amount = Decimal(item['amount'])
fee_asset = withdrawn_asset # Assumed
fee_asset = withdrawn_asset # Assumed
fee_amount = Decimal(item['fee'])
executed_time=datetime.datetime.fromtimestamp(
executed_time = datetime.datetime.fromtimestamp(
item['createdAt'] / 1000,
tz=datetime.UTC,
)
withdrawals.append(WithdrawalDetails(withdrawn_asset,
withdrawn_amount, fee_asset,
fee_amount, executed_time,
item))
withdrawals.append(
WithdrawalDetails(
AssetAmount(withdrawn_asset, withdrawn_amount),
AssetAmount(fee_asset, fee_amount),
executed_time,
item,
),
)
del item
return withdrawals
def _get_deposits(self) -> list[WithdrawalDetails]:
def _get_deposits(self) -> list[DepositDetails]:
raw_details = self.kucoin_client.get_deposits()
deposits = []
for item in raw_details['items']:
deposit_asset = parse_asset_from_ticker(item['currency'])
deposit_amount = Decimal(item['amount'])
fee_asset = deposit_asset # Assumed
fee_asset = deposit_asset # Assumed
fee_amount = Decimal(item['fee'])
executed_time=datetime.datetime.fromtimestamp(
executed_time = datetime.datetime.fromtimestamp(
item['createdAt'] / 1000,
tz=datetime.UTC,
)
deposits.append(DepositDetails(deposit_asset,
deposit_amount, fee_asset,
fee_amount, executed_time,
item))
deposits.append(
DepositDetails(
AssetAmount(deposit_asset, deposit_amount),
AssetAmount(fee_asset, fee_amount),
executed_time,
item,
),
)
del item
return deposits
def _get_historic_spot_orders(self) -> Iterator[TradeOrderDetails]:
end_time = datetime.datetime.now(tz=datetime.UTC)
for _weeks_back in range(20):
end_time = end_time - datetime.timedelta(days=7)
timestamp = int(end_time.timestamp() * 1000)
print(timestamp)
raw_details = self.kucoin_client.get_orders(end=timestamp)
yield from (order_from_json(item) for item in raw_details['items'])
del _weeks_back, raw_details
def _get_double_registers(self) -> list[DoubleRegister]:
deposits = self._get_deposits()
withdrawals = self._get_withdrawals()
spot_trades = self._get_historic_spot_orders()
double_registers = []
double_registers += [
DoubleRegister(d.deposit, None, d.executed_time) for d in deposits
]
double_registers += [
DoubleRegister(None, d.withdrawn, d.executed_time) for d in withdrawals
]
double_registers += [
DoubleRegister(d.input, d.output, d.executed_time) for d in spot_trades
]
double_registers.sort(key=lambda x: x.executed_time)
return double_registers
def _get_order_details(
self,
order_id: str,
input_asset: fin_defs.Asset,
output_asset: fin_defs.Asset,
) -> TradeOrderDetails:
"""Determine the order details for the order with the given id.
@ -203,29 +276,7 @@ class KucoinDepoFetcher(DepoFetcher):
order through their systems.
"""
order_details = self._get_order_with_retries(order_id, num_retries=10)
# Convert from kucoin results
if input_asset == fin_defs.USDT:
input_amount_final = Decimal(order_details['dealFunds'])
output_amount_final = Decimal(order_details['dealSize'])
else:
output_amount_final = Decimal(order_details['dealFunds'])
input_amount_final = Decimal(order_details['dealSize'])
return TradeOrderDetails(
input_asset=input_asset,
input_amount=input_amount_final,
output_asset=output_asset,
output_amount=output_amount_final,
fee_asset=parse_asset_from_ticker(order_details['feeCurrency']),
fee_amount=Decimal(order_details['fee']),
executed_time=datetime.datetime.fromtimestamp(
order_details['createdAt'] / 1000,
tz=datetime.UTC,
),
order_id=order_id,
raw_order_details=order_details,
)
return order_from_json(order_details)
def _get_order_with_retries(
self,

View File

@ -1,7 +1,5 @@
import datetime
from decimal import Decimal
import fin_defs
import pytest
import fin_depo
@ -19,6 +17,7 @@ fin_depo.defi_kucoin.logger.setLevel('INFO')
NOW = datetime.datetime.now(tz=datetime.UTC)
def get_fetcher() -> fin_depo.defi_kucoin.KucoinDepoFetcher:
return fin_depo.defi_kucoin.KucoinDepoFetcher(
secrets.KUCOIN_KEY,
@ -26,6 +25,7 @@ def get_fetcher() -> fin_depo.defi_kucoin.KucoinDepoFetcher:
secrets.KUCOIN_PASS,
)
@needs_secrets
def test_get_depo():
"""Can inspect kucoin depository."""
@ -35,14 +35,28 @@ def test_get_depo():
for nested_depo in depo.nested:
assert isinstance(nested_depo, fin_depo.data.DepoSingle)
@needs_secrets
def test_get_withdrawals():
withdrawals = get_fetcher()._get_withdrawals()
print(withdrawals)
assert len(withdrawals) > 0
@needs_secrets
def test_get_deposits():
deposits = get_fetcher()._get_deposits()
print(deposits)
assert len(deposits) > 0
@needs_secrets
def test_get_historic_spot_orders():
orders = get_fetcher()._get_historic_spot_orders()
assert next(orders)
@needs_secrets
def test_get_double_registers():
double_registers = get_fetcher()._get_double_registers()
print(double_registers)
assert len(double_registers) > 0
assert False

View File

@ -106,4 +106,3 @@ def test_place_sell_side_order():
assert order_details.fee_amount is not None
assert NOW <= order_details.executed_time <= NOW + datetime.timedelta(minutes=10)