summaryrefslogtreecommitdiff
path: root/trade.py
blob: 7c2ecc7d422cbf7f740136395ead117a5df40402 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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})"