1
0
fin-depo/fin_depo/data.py

218 lines
6.3 KiB
Python
Raw Normal View History

2024-11-28 23:45:31 +00:00
"""Datastructures for `fin-depo`."""
2024-06-02 15:24:53 +00:00
import abc
import dataclassabc
2024-05-29 20:29:15 +00:00
import dataclasses
2024-06-02 14:14:46 +00:00
import datetime
2024-06-02 15:24:53 +00:00
from collections.abc import Iterable, Mapping
2024-06-02 14:14:46 +00:00
from decimal import Decimal
2024-07-18 22:22:29 +00:00
from typing import TypeVar
2024-06-02 14:14:46 +00:00
import enforce_typing
from fin_defs import Asset, AssetAmount
2024-06-02 14:14:46 +00:00
2024-05-29 20:29:15 +00:00
@enforce_typing.enforce_types
2024-07-20 18:21:50 +00:00
@dataclasses.dataclass(frozen=True)
2024-06-02 15:24:53 +00:00
class Depo(abc.ABC):
2024-11-28 23:45:31 +00:00
"""A depository tracking several assets.
2024-06-20 21:43:45 +00:00
Depo can either be DepoSingle, which is the base layer of the depository
structure, or nested in DepoGroup, which allows for a complex hierarcy of
depositories.
The depository structure exposed by each backend depends upon the logical
structure of the relevant service and the API of this service.
"""
2024-05-29 20:29:15 +00:00
name: str
updated_time: datetime.datetime
2024-06-02 15:24:53 +00:00
@abc.abstractmethod
def assets(self) -> Iterable[Asset]:
"""Returns the different assets managed by this depo."""
@abc.abstractmethod
def get_amount_of_asset(self, asset: Asset) -> Decimal:
"""Returns the amount of owned assets for all nested depos.
Must return 0 if depo does not contain the given asset.
"""
2024-06-02 14:14:46 +00:00
2024-05-29 20:29:15 +00:00
@enforce_typing.enforce_types
2024-07-20 18:21:50 +00:00
@dataclasses.dataclass(frozen=True)
2024-05-29 20:29:15 +00:00
class DepoSingle(Depo):
2024-06-20 21:43:45 +00:00
"""Base level of depository."""
2024-06-02 15:24:53 +00:00
_assets: Mapping[Asset, Decimal]
2024-10-20 09:03:31 +00:00
def __post_init__(self):
2024-11-01 10:30:18 +00:00
if None in self._assets:
msg = 'DepoSingle must not containg a None Asset key'
raise ValueError(msg)
2024-10-20 09:03:31 +00:00
2024-06-02 15:24:53 +00:00
def assets(self) -> Iterable[Asset]:
return self._assets
def get_amount_of_asset(self, asset: Asset) -> Decimal:
return self._assets.get(asset, Decimal(0))
2024-05-29 20:29:15 +00:00
2024-06-02 14:14:46 +00:00
2024-05-29 20:29:15 +00:00
@enforce_typing.enforce_types
2024-07-20 18:21:50 +00:00
@dataclasses.dataclass(frozen=True)
2024-05-29 20:29:15 +00:00
class DepoGroup(Depo):
2024-06-20 21:43:45 +00:00
"""Nested depository."""
2024-05-29 20:29:15 +00:00
nested: list[Depo]
2024-06-02 15:24:53 +00:00
2024-10-20 09:03:31 +00:00
def __post_init__(self):
2024-11-01 10:30:18 +00:00
if None in self.nested:
msg = 'DepoGroup must not containg an None depository'
raise ValueError(msg)
2024-10-20 09:03:31 +00:00
2024-06-02 15:24:53 +00:00
def assets(self) -> Iterable[Asset]:
2024-07-18 23:24:59 +00:00
assets: set[Asset] = set()
2024-06-02 15:24:53 +00:00
for nested_depo in self.nested:
2024-07-18 23:24:59 +00:00
assets.update(nested_depo.assets())
2024-06-02 15:24:53 +00:00
return assets
def get_amount_of_asset(self, asset: Asset) -> Decimal:
summed: Decimal = Decimal(0)
for nested_depo in self.nested:
summed += nested_depo.get_amount_of_asset(asset)
del nested_depo
return summed
2024-07-18 22:22:29 +00:00
2024-07-18 22:22:54 +00:00
2024-07-18 22:22:29 +00:00
T = TypeVar('T')
2024-07-18 22:22:54 +00:00
2024-07-18 22:22:29 +00:00
class DepoFetcher(abc.ABC):
"""Base `Depo` fetcher interface.
Used to get depository information for specific websites/backends.
"""
@abc.abstractmethod
def get_depo(self) -> Depo:
"""Fetches the `Depo`s available for the fetcher, possibly as
a `DepoGroup`.
"""
def assert_param(self, param_name: str, param_type: type[T], param_value: T) -> T:
if not isinstance(param_value, param_type):
msg = f'{self} expected {param_name} parameter of type {param_type}, but got: {param_value}'
raise TypeError(msg)
return param_value
@enforce_typing.enforce_types
class DoubleRegister(abc.ABC):
2024-12-02 17:11:44 +00:00
"""Information about some movement of assets."""
@property
@abc.abstractmethod
def input(self) -> AssetAmount | None:
2024-12-02 17:11:44 +00:00
"""How much and what kind of asset have been placed into the depository."""
raise NotImplementedError
@property
@abc.abstractmethod
def output(self) -> AssetAmount | None:
2024-12-02 17:11:44 +00:00
"""How much and what kind of asset have been removed from the depository."""
raise NotImplementedError
@property
@abc.abstractmethod
def fee(self) -> AssetAmount | None:
2024-12-02 17:11:44 +00:00
"""How much and what kind of asset was spent as a service fee."""
raise NotImplementedError
@property
@abc.abstractmethod
def executed_time(self) -> datetime.datetime:
2024-12-02 17:11:44 +00:00
"""The precise point in time when the asset transaction was executed."""
raise NotImplementedError
@enforce_typing.enforce_types
@dataclassabc.dataclassabc(frozen=True)
class TradeOrderDetails(DoubleRegister):
"""Information about an executed trade.
Includes both well-structured data about the order, and unstructured data
from the backend. The unstructured data might be needed for tax purposes.
2024-07-22 14:04:52 +00:00
Fields:
- `input_asset`: Asset that have been sold
- `input_amount`: Amount of asset that has been sold
- `output_asset`: Asset that have been bought
- `output_amount`: Amount of asset that have been bought.
- `fee_asset`: Asset used to pay fee.
- `fee_amount`: Amount of asset that have been used to pay fee.
- `executed_time`: Point in time when order was executed.
- `order_id`: Unique identifier of order. Determined by backend.
- `raw_order_details`: For storing arbitrary unstructured data from backend.
"""
input: AssetAmount
output: AssetAmount
fee: AssetAmount
2024-07-22 14:04:52 +00:00
executed_time: datetime.datetime
order_id: object
raw_order_details: object
2024-11-27 20:56:13 +00:00
2024-11-27 23:45:46 +00:00
2024-11-27 20:56:13 +00:00
@enforce_typing.enforce_types
@dataclassabc.dataclassabc(frozen=True)
class WithdrawalDetails(DoubleRegister):
2024-12-02 17:11:44 +00:00
"""Information about a withdrawal of assets from a specific service.
Withdraw assets may appear in another depository as a `DepositDetails`, but
these cannot be automatically linked by `fin-depo`.
Includes both well-structured data about the order, and unstructured data
from the backend. The unstructured data might be needed for tax purposes.
"""
withdrawn: AssetAmount
fee: AssetAmount
2024-11-27 20:56:13 +00:00
executed_time: datetime.datetime
raw_details: object
@property
def input(self) -> AssetAmount:
return self.withdrawn
@property
def output(self) -> None:
return None
2024-11-27 23:45:46 +00:00
2024-11-27 20:56:13 +00:00
@enforce_typing.enforce_types
@dataclassabc.dataclassabc(frozen=True)
class DepositDetails(DoubleRegister):
2024-12-02 17:11:44 +00:00
"""Information about a deposit of assets to a specific service.
Deposited assets may appear in another depository as a `WithdrawalDetails`, but
these cannot be automatically linked by `fin-depo`.
Includes both well-structured data about the order, and unstructured data
from the backend. The unstructured data might be needed for tax purposes.
"""
deposit: AssetAmount
fee: AssetAmount
2024-11-27 20:56:13 +00:00
executed_time: datetime.datetime
raw_details: object
@property
def input(self) -> None:
return None
2024-11-27 23:45:46 +00:00
@property
def output(self) -> AssetAmount:
return self.deposit