from datetime import datetime from decimal import Decimal from enum import Enum class PriceAdaption(Enum): KeepTotalCost = 1 KeepPricePerCoin = 2 class Trade: """ Represents a cryptocurrency trade, including the amount traded, total cost, and the date of trade. Provides methods to modify the trade and access various attributes. """ def __init__( self, amount: Decimal, total_cost: Decimal, timestamp: str, refid: str | None = None ) -> None: """ Initialize a new Trade instance. Args: amount (Decimal): The amount of cryptocurrency traded. total_cost (Decimal): The total cost of the trade. timestamp (str): The date or timestamp of the trade, formatted as a string. """ if amount < 0 and total_cost < 0: pass elif amount > 0 and total_cost > 0: pass else: raise ValueError("Amount and tota> cost must be same sign") # force timestamp try: datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") except ValueError: datetime.strptime(timestamp, "%Y-%m-%d") timestamp = timestamp + " 00:00:00" self.__amount: Decimal = Decimal(amount) self.__total_cost: Decimal = Decimal(total_cost) self.__timestamp: str = timestamp self.__refid: str | None = refid def remove_coins( self, amount: float | Decimal, adapt: PriceAdaption = PriceAdaption.KeepPricePerCoin, ) -> None: """ Reduce the amount of cryptocurrency in the trade by a specified amount. This effectively "loses" coins. Args: amount (Decimal): The amount of cryptocurrency to remove. Raises: ValueError: If the amount to remove exceeds the current amount in the trade. """ if amount > self.__amount: raise ValueError(f"Can't remove more than {self.__amount}") if adapt == PriceAdaption.KeepPricePerCoin: amount = Decimal(amount) self.__total_cost -= amount * self.price_per_coin self.__amount -= amount elif adapt == PriceAdaption.KeepTotalCost: amount = Decimal(amount) self.__amount -= amount else: raise ValueError("Unknown adaptation strategy") @property def amount(self) -> Decimal: """ Get the current amount of cryptocurrency in the trade. Returns: Decimal: The amount of cryptocurrency. """ return self.__amount @property def total_cost(self) -> Decimal: """ Get the total cost of the trade. Returns: Decimal: The total cost of the trade. """ return self.__total_cost @property def date(self) -> str: """ Get the date of the trade. Returns: str: The trade date as a string. """ return self.__timestamp.split(" ")[0] @property def timestamp(self) -> str: """ Get the timestamp of the trade. Returns: str: The trade timestamp as a string. """ return self.__timestamp @property def price_per_coin(self) -> Decimal: """ Calculate the price per coin based on the total cost and current amount. Returns: Decimal: The price per coin. Raises: ZeroDivisionError: If the current amount is zero. """ if self.amount == 0: raise ZeroDivisionError( "Price per coin cannot be calculated when the amount is zero" ) return self.total_cost / self.amount def __repr__(self) -> str: """ Get a string representation of the Trade instance. Returns: str: A formatted string displaying the trade details. """ rid = self.__refid or "" return f"Trade(amount={self.amount:.2f}, price_per_coin={self.price_per_coin:.2f}, total_cost={self.total_cost:.2f}, date={self.date}, refid={rid})"