diff options
author | uvok | 2025-04-17 11:20:01 +0200 |
---|---|---|
committer | uvok | 2025-04-17 11:20:01 +0200 |
commit | ecea606533550d8d53a65daa6eeef583788448f4 (patch) | |
tree | 6ad17761e9ddede12da40e638d6f0d917ed3e4c4 | |
parent | 5ed67c88b608a3ee10381635fcff799eebbfc201 (diff) |
Enforce use of Decimal, formatting
-rw-r--r-- | bla.py | 5 | ||||
-rw-r--r-- | kraken.py | 2 | ||||
-rw-r--r-- | ledger_action.py | 3 | ||||
-rw-r--r-- | test_trade.py | 5 | ||||
-rw-r--r-- | test_trade_queue.py | 36 | ||||
-rw-r--r-- | trade.py | 16 | ||||
-rw-r--r-- | trade_queue.py | 18 |
7 files changed, 57 insertions, 28 deletions
@@ -11,15 +11,14 @@ from trade import Trade from trade_queue import FIFOQueue logging.basicConfig( - level=logging.WARNING, - format='%(asctime)s - %(levelname)s - %(name)s - %(message)s' + level=logging.WARNING, format="%(asctime)s - %(levelname)s - %(name)s - %(message)s" ) # Set up a dedicated logger for FIFOQueue logger = logging.getLogger("pnlcalc") -def generate_report(sale_entries, proceeds: float | Decimal, crypto_asset, date_sold): +def generate_report(sale_entries, proceeds: Decimal, crypto_asset, date_sold): report = [] sell_date = datetime.strptime(date_sold, "%Y-%m-%d").strftime("%d.%m.%Y") proceeds = Decimal(proceeds) @@ -4,7 +4,7 @@ from typing import List from ledger_action import LedgerAction -def parse_row(row: dict[str,str]) -> LedgerAction: +def parse_row(row: dict[str, str]) -> LedgerAction: date = row["time"].split(" ")[0] return LedgerAction( type=row["type"], diff --git a/ledger_action.py b/ledger_action.py index 952692b..cc0eb83 100644 --- a/ledger_action.py +++ b/ledger_action.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from decimal import Decimal + @dataclass class LedgerAction: """ @@ -8,7 +9,7 @@ class LedgerAction: The `LedgerAction` class encapsulates details of each action recorded in the ledger, whether it's a deposit, trade, or other types of transactions. - + Attributes: type (str): The type of action, e.g., "trade" or "deposit". asset (str): The cryptocurrency or fiat asset associated with the action. diff --git a/test_trade.py b/test_trade.py index c3d7764..ab853d1 100644 --- a/test_trade.py +++ b/test_trade.py @@ -3,12 +3,13 @@ from decimal import Decimal from trade import PriceAdaption, Trade + class TestTrade(unittest.TestCase): def setUp(self) -> None: """ Set up a Trade instance for testing. """ - self.trade = Trade(amount=10.0, total_cost=100.0, date="2025-04-14") + self.trade = Trade(amount=Decimal(10.0), total_cost=Decimal(100.0), date="2025-04-14") def test_initialization(self): """ @@ -45,7 +46,7 @@ class TestTrade(unittest.TestCase): self.assertEqual(self.trade.amount, 6.0) self.assertEqual(self.trade.total_cost, 100.0) - self.assertEqual(self.trade.price_per_coin, Decimal("100.0")/Decimal("6.0")) + self.assertEqual(self.trade.price_per_coin, Decimal("100.0") / Decimal("6.0")) def test_remove_coins_exceeds_amount(self): """ diff --git a/test_trade_queue.py b/test_trade_queue.py index 89c75e8..70a795a 100644 --- a/test_trade_queue.py +++ b/test_trade_queue.py @@ -1,17 +1,19 @@ +from decimal import Decimal import unittest from datetime import datetime from trade_queue import FIFOQueue + class TestFIFOQueue(unittest.TestCase): def setUp(self): """ Set up a FIFOQueue instance and some test trades. """ self.queue = FIFOQueue() - self.queue.add(10.0, 100.0, "2025-04-14") - self.queue.add(20.0, 200.0, "2025-04-15") - self.queue.add(30.0, 300.0, "2025-04-16") + self.queue.add(Decimal(10.0), Decimal(100.0), "2025-04-14") + self.queue.add(Decimal(20.0), Decimal(200.0), "2025-04-15") + self.queue.add(Decimal(30.0), Decimal(300.0), "2025-04-16") def test_add(self): """ @@ -48,8 +50,10 @@ class TestFIFOQueue(unittest.TestCase): """ trades = self.queue.remove_coins(25.0) self.assertEqual(len(trades), 2) # Two trades should be returned - self.assertEqual(trades[0].amount, 10.0) # The first trade should be fully consumed - self.assertEqual(trades[1].amount, 15.0) # The second trade should be partially consumed + # The first trade should be fully consumed + self.assertEqual(trades[0].amount, 10.0) + # The second trade should be partially consumed + self.assertEqual(trades[1].amount, 15.0) tq = self.queue.get_copy() self.assertEqual(tq[0].amount, 5.0) # Remaining trade in queue should update @@ -71,14 +75,16 @@ class TestFIFOQueue(unittest.TestCase): """ Test the remaining amount in the queue after adding trades. """ - self.assertEqual(self.queue.get_remaining_amount(), 60.0) # Total of all amounts: 10 + 20 + 30 + # Total of all amounts: 10 + 20 + 30 + self.assertEqual(self.queue.get_remaining_amount(), 60.0) def test_get_remaining_amount_after_removal(self): """ Test the remaining amount after removing some assets. """ self.queue.remove_coins(15.0) # Remove 15 assets - self.assertEqual(self.queue.get_remaining_amount(), 45.0) # Remaining: 60 - 15 + # Remaining: 60 - 15 + self.assertEqual(self.queue.get_remaining_amount(), 45.0) def test_get_remaining_amount_empty_queue(self): """ @@ -108,14 +114,20 @@ class TestFIFOQueue(unittest.TestCase): trades = self.queue.remove_coins(4.0) # Remove 4 COIN from the first trade self.assertEqual(len(trades), 1) # Only one trade should be returned - self.assertEqual(trades[0].price_per_coin, 10) # Coin-cost needs to stay constant + # Coin-cost needs to stay constant + self.assertEqual(trades[0].price_per_coin, 10) self.assertEqual(trades[0].amount, 4.0) # Check the removed amount - self.assertEqual(trades[0].total_cost, 40.0) # Total cost should be proportional: (100 * 5 / 10) + # Total cost should be proportional: (100 * 5 / 10) + self.assertEqual(trades[0].total_cost, 40.0) tq = self.queue.get_copy() - self.assertEqual(tq[0].price_per_coin, 10) # Original total cost remains unchanged - self.assertEqual(tq[0].amount, 6.0) # Remaining amount in the first trade should be updated - self.assertEqual(tq[0].total_cost, 60.0) # Original total cost remains unchanged + # Original total cost remains unchanged + self.assertEqual(tq[0].price_per_coin, 10) + # Remaining amount in the first trade should be updated + self.assertEqual(tq[0].amount, 6.0) + # Original total cost remains unchanged + self.assertEqual(tq[0].total_cost, 60.0) + if __name__ == "__main__": unittest.main() @@ -1,6 +1,7 @@ from decimal import Decimal from enum import Enum + class PriceAdaption(Enum): KeepTotalCost = 1 KeepPricePerCoin = 2 @@ -11,7 +12,8 @@ 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: float|Decimal, total_cost: float|Decimal, date: str) -> None: + + def __init__(self, amount: Decimal, total_cost: Decimal, date: str) -> None: """ Initialize a new Trade instance. @@ -28,7 +30,11 @@ class Trade: self.__total_cost: Decimal = Decimal(total_cost) self.__date: str = date - def remove_coins(self, amount: float|Decimal, adapt: PriceAdaption = PriceAdaption.KeepPricePerCoin) -> None: + def remove_coins( + self, + amount: float | Decimal, + adapt: PriceAdaption = PriceAdaption.KeepPricePerCoin, + ) -> None: """ Reduce the amount of cryptocurrency in the trade by a specified amount. @@ -42,7 +48,7 @@ class 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 @@ -95,7 +101,9 @@ class Trade: ZeroDivisionError: If the current amount is zero. """ if self.amount == 0: - raise ZeroDivisionError("Price per coin cannot be calculated when the amount is zero") + raise ZeroDivisionError( + "Price per coin cannot be calculated when the amount is zero" + ) return self.total_cost / self.amount diff --git a/trade_queue.py b/trade_queue.py index a0dfc47..dcc9725 100644 --- a/trade_queue.py +++ b/trade_queue.py @@ -7,18 +7,20 @@ from trade import Trade # Set up a dedicated logger for FIFOQueue logger = logging.getLogger(__name__) + class FIFOQueue: """ Crypto trading FIFO queue. Will track trades. """ + def __init__(self) -> None: self.__queue: Deque[Trade] = deque() self._cached_total: Decimal = Decimal(0) self._cache_valid: bool = True logger.info("FIFOQueue initialized with empty queue.") - + def __len__(self) -> int: return len(self.__queue) @@ -28,7 +30,7 @@ class FIFOQueue: """ return [t for t in self.__queue] - def add(self, amount: float | Decimal, total_cost: float | Decimal, date: str) -> None: + def add(self, amount: Decimal, total_cost: Decimal, date: str) -> None: """ Add a trade to the queue. """ @@ -50,7 +52,9 @@ class FIFOQueue: if amount > self.get_remaining_amount(): logger.error(f"Insufficient assets to process sale of {amount}.") - raise ValueError(f"Insufficient assets in queue to process sale of {amount}.") + raise ValueError( + f"Insufficient assets in queue to process sale of {amount}." + ) logger.debug(f"Removing {amount:.2f} coins from the queue.") logger.info("Cache invalidated before removing coins.") @@ -73,7 +77,9 @@ class FIFOQueue: remaining -= trade.amount entries.append(trade) self.__queue.popleft() - logger.info(f"Removed full trade: {trade}. Remaining coins to remove: {remaining}") + logger.info( + f"Removed full trade: {trade}. Remaining coins to remove: {remaining}" + ) return entries @@ -83,7 +89,9 @@ class FIFOQueue: """ if not self._cache_valid: logger.debug("Cache invalid, recalculating remaining amount.") - self._cached_total = sum((trade.amount for trade in self.__queue), Decimal(0)) + self._cached_total = sum( + (trade.amount for trade in self.__queue), Decimal(0) + ) self._cache_valid = True logger.info(f"Cache recalculated: {self._cached_total:.2f}") |