From 7426249194647ef1a36abf0c4aaab81ce7ca7e29 Mon Sep 17 00:00:00 2001 From: Joshua Hoogstraat <34964599+jhoogstraat@users.noreply.github.com> Date: Tue, 12 Apr 2022 21:47:31 +0200 Subject: [PATCH 1/5] ADD basic transfer matching algorithm --- src/book.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.py | 1 + src/transaction.py | 2 +- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/book.py b/src/book.py index 1d42b260..103c710f 100644 --- a/src/book.py +++ b/src/book.py @@ -1212,6 +1212,56 @@ def detect_exchange(self, file_path: Path) -> Optional[str]: return None + def resolve_deposits(self) -> None: + """Matches withdrawals and deposits by referencing the related + withdrawal on the deposit. + + A match is found when: + A. The coin is the same + 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: + # log.debug(op.utc_time) + if isinstance(op, tr.Withdrawal): + if not misc.is_fiat(op.coin): + withdrawal_queue.append(op) + elif isinstance(op, tr.Deposit): + if not misc.is_fiat(op.coin): + try: + match = next(w for w in withdrawal_queue if is_match(w, op)) + op.link = match + withdrawal_queue.remove(match) + log.info( + "Linking transfer: " + f"{match.change} {match.coin} " + f"({match.platform}, {match.utc_time}) " + f"-> {op.change} {op.coin} " + f"({op.platform}, {op.utc_time})" + ) + except StopIteration: + log.warning( + "No matching withdrawal operation found for deposit of " + f"{match.change} {match.coin} " + f"({match.platform}, {match.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..3f56725b 100644 --- a/src/main.py +++ b/src/main.py @@ -44,6 +44,7 @@ def main() -> None: # (as long as there are only one buy/sell pair per time, # might be problematic otherwhise). book.merge_identical_operations() + 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..3bacf348 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -190,7 +190,7 @@ class Commission(Transaction): class Deposit(Transaction): - pass + link: typing.Optional[Withdrawal] = None class Withdrawal(Transaction): From 2b9cabd5df05027bc250f2110931d0d73fa861bb Mon Sep 17 00:00:00 2001 From: Joshua Hoogstraat <34964599+jhoogstraat@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:13:05 +0200 Subject: [PATCH 2/5] Cleanup --- src/book.py | 1 - src/main.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/book.py b/src/book.py index 103c710f..72e011ef 100644 --- a/src/book.py +++ b/src/book.py @@ -1236,7 +1236,6 @@ def is_match(withdrawal: tr.Withdrawal, deposit: tr.Deposit) -> bool: withdrawal_queue: list[tr.Withdrawal] = [] for op in sorted_ops: - # log.debug(op.utc_time) if isinstance(op, tr.Withdrawal): if not misc.is_fiat(op.coin): withdrawal_queue.append(op) diff --git a/src/main.py b/src/main.py index 3f56725b..0efc2c3c 100644 --- a/src/main.py +++ b/src/main.py @@ -44,6 +44,8 @@ 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() From 00acedda72768a5059948366f549d276523c30c7 Mon Sep 17 00:00:00 2001 From: Joshua Hoogstraat <34964599+jhoogstraat@users.noreply.github.com> Date: Wed, 13 Apr 2022 18:03:06 +0200 Subject: [PATCH 3/5] Include foreign fiat currencies --- src/book.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/book.py b/src/book.py index 72e011ef..af58d24b 100644 --- a/src/book.py +++ b/src/book.py @@ -1237,10 +1237,10 @@ def is_match(withdrawal: tr.Withdrawal, deposit: tr.Deposit) -> bool: for op in sorted_ops: if isinstance(op, tr.Withdrawal): - if not misc.is_fiat(op.coin): + if op.coin != config.FIAT: withdrawal_queue.append(op) elif isinstance(op, tr.Deposit): - if not misc.is_fiat(op.coin): + if op.coin != config.FIAT: try: match = next(w for w in withdrawal_queue if is_match(w, op)) op.link = match From e6563c2f6d63189f4343a3ab7083b0bdd9059c4a Mon Sep 17 00:00:00 2001 From: Jeppy Date: Sat, 23 Apr 2022 09:39:43 +0200 Subject: [PATCH 4/5] UPDATE format --- src/book.py | 57 +++++++++++++++++++++++++++------------------- src/transaction.py | 2 +- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/book.py b/src/book.py index af58d24b..508ca3ca 100644 --- a/src/book.py +++ b/src/book.py @@ -1213,11 +1213,10 @@ def detect_exchange(self, file_path: Path) -> Optional[str]: return None def resolve_deposits(self) -> None: - """Matches withdrawals and deposits by referencing the related - withdrawal on the deposit. + """Match withdrawals to deposits. A match is found when: - A. The coin is the same + A. The coin is the same and B. The deposit amount is between 0.99 and 1 times the withdrawal amount. Returns: @@ -1236,28 +1235,40 @@ def is_match(withdrawal: tr.Withdrawal, deposit: tr.Deposit) -> bool: 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): - if op.coin != config.FIAT: - withdrawal_queue.append(op) + # Add new withdrawal to queue. + withdrawal_queue.append(op) + elif isinstance(op, tr.Deposit): - if op.coin != config.FIAT: - try: - match = next(w for w in withdrawal_queue if is_match(w, op)) - op.link = match - withdrawal_queue.remove(match) - log.info( - "Linking transfer: " - f"{match.change} {match.coin} " - f"({match.platform}, {match.utc_time}) " - f"-> {op.change} {op.coin} " - f"({op.platform}, {op.utc_time})" - ) - except StopIteration: - log.warning( - "No matching withdrawal operation found for deposit of " - f"{match.change} {match.coin} " - f"({match.platform}, {match.utc_time})" - ) + 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"{match.change} {match.coin} " + f"({match.platform}, {match.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") diff --git a/src/transaction.py b/src/transaction.py index 3bacf348..ddcc2247 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -190,7 +190,7 @@ class Commission(Transaction): class Deposit(Transaction): - link: typing.Optional[Withdrawal] = None + link: Optional[Withdrawal] = None class Withdrawal(Transaction): From 443b4d786ecd350af052b14f59af268660481d1c Mon Sep 17 00:00:00 2001 From: Jeppy Date: Sat, 23 Apr 2022 09:41:14 +0200 Subject: [PATCH 5/5] FIX Wrong variable used --- src/book.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/book.py b/src/book.py index 508ca3ca..2cb193a5 100644 --- a/src/book.py +++ b/src/book.py @@ -1251,8 +1251,8 @@ def is_match(withdrawal: tr.Withdrawal, deposit: tr.Deposit) -> bool: except StopIteration: log.warning( "No matching withdrawal operation found for deposit of " - f"{match.change} {match.coin} " - f"({match.platform}, {match.utc_time}). " + 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 "