diff --git a/src/book.py b/src/book.py index 1d42b260..2cb193a5 100644 --- a/src/book.py +++ b/src/book.py @@ -1212,6 +1212,66 @@ def detect_exchange(self, file_path: Path) -> Optional[str]: return None + def resolve_deposits(self) -> None: + """Match withdrawals to deposits. + + A match is found when: + A. The coin is the same and + B. The deposit amount is between 0.99 and 1 times the withdrawal amount. + + Returns: + None + """ + sorted_ops = tr.sort_operations(self.operations, ["utc_time"]) + + def is_match(withdrawal: tr.Withdrawal, deposit: tr.Deposit) -> bool: + return ( + withdrawal.coin == deposit.coin + and withdrawal.change * decimal.Decimal(0.99) + <= deposit.change + <= withdrawal.change + ) + + withdrawal_queue: list[tr.Withdrawal] = [] + + for op in sorted_ops: + if op.coin == config.FIAT: + # Do not match home fiat deposit/withdrawals. + continue + + if isinstance(op, tr.Withdrawal): + # Add new withdrawal to queue. + withdrawal_queue.append(op) + + elif isinstance(op, tr.Deposit): + try: + # Find a matching withdrawal for this deposit. + # If multiple are found, take the first (regarding utc_time). + match = next(w for w in withdrawal_queue if is_match(w, op)) + except StopIteration: + log.warning( + "No matching withdrawal operation found for deposit of " + f"{op.change} {op.coin} " + f"({op.platform}, {op.utc_time}). " + "The tax evaluation might be wrong. " + "Have you added all account statements? " + "For tax evaluation, it might be importend when " + "and for which price these coins were bought." + ) + else: + # Match the found withdrawal and remove it from queue. + op.link = match + withdrawal_queue.remove(match) + log.debug( + "Linking withdrawal with deposit: " + f"{match.change} {match.coin} " + f"({match.platform}, {match.utc_time}) " + f"-> {op.change} {op.coin} " + f"({op.platform}, {op.utc_time})" + ) + + log.info("Finished matching") + def get_price_from_csv(self) -> None: """Calculate coin prices from buy/sell operations in CSV files. diff --git a/src/main.py b/src/main.py index 49c1603d..0efc2c3c 100644 --- a/src/main.py +++ b/src/main.py @@ -44,6 +44,9 @@ def main() -> None: # (as long as there are only one buy/sell pair per time, # might be problematic otherwhise). book.merge_identical_operations() + # Resolve dependencies between withdrawals and deposits, which is + # necessary to correctly fetch prices and to calculate p/l. + book.resolve_deposits() book.get_price_from_csv() book.match_fees_with_operations() diff --git a/src/transaction.py b/src/transaction.py index ceb6b32a..ddcc2247 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -190,7 +190,7 @@ class Commission(Transaction): class Deposit(Transaction): - pass + link: Optional[Withdrawal] = None class Withdrawal(Transaction):