Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just as well be a debug log.


def get_price_from_csv(self) -> None:
"""Calculate coin prices from buy/sell operations in CSV files.

Expand Down
3 changes: 3 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion src/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class Commission(Transaction):


class Deposit(Transaction):
pass
link: Optional[Withdrawal] = None


class Withdrawal(Transaction):
Expand Down