from __future__ import annotations
import logging
from dataclasses import dataclass
from decimal import Decimal
from typing import TYPE_CHECKING
import pandas as pd
# TODO: use explicit imports until mypyc fixes attribute lookups in dataclass
# (https://github.com/mypyc/mypyc/issues/1000)
from pandas import Timestamp # type: ignore
if TYPE_CHECKING:
from .asset import AssetName
logger = logging.getLogger(__name__)
__all__ = ["CashTransaction"]
[docs]@dataclass(frozen=True, kw_only=True)
class Transaction:
"""A frozen record of a transaction."""
ts: pd.Timestamp
"""Transaction time."""
total: Decimal = Decimal(0)
"""Total transaction value.
Negative values are costs and positive values are benefits.
"""
desc: str = ""
"""Description."""
def __post_init__(self):
# since frozen we need to use obj method to cast values
if not isinstance(self.total, Decimal):
object.__setattr__(self, "total", Decimal(self.total))
[docs]@dataclass(frozen=True, kw_only=True)
class CashTransaction(Transaction):
"""A frozen record of a cash transaction."""
[docs]@dataclass(frozen=True, kw_only=True)
class Trade(Transaction):
"""A frozen record of a trade transaction.
A negative `quantity` represents a sell trade and a positive
`quantity` represents a buy trade.
"""
quantity: Decimal
"""Traded quantity."""
price: Decimal
"""Traded price."""
asset_name: AssetName
"""Traded asset."""
order_label: str | None
"""Associated Order label."""
def __post_init__(self):
super().__post_init__()
if self.quantity == 0:
raise ValueError("trade quantity cannot be zero")
# since frozen we need to use obj method to cast values
if not isinstance(self.price, Decimal):
object.__setattr__(self, "price", Decimal(self.price))
if not isinstance(self.quantity, Decimal):
object.__setattr__(self, "quantity", Decimal(self.quantity))
# total represents cost/benefit when buy/sell
object.__setattr__(self, "total", -self.quantity * self.price)
object.__setattr__(
self,
"desc",
f"{'sell' if self.quantity < 0 else 'buy'} {self.asset_name}",
)