From 75188f8d900b8f33bd9c5018cc875caf2974c422 Mon Sep 17 00:00:00 2001 From: uvok Date: Sat, 19 Apr 2025 16:17:10 +0200 Subject: Add match method --- trade_queue.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/trade_queue.py b/trade_queue.py index c3a4b9d..87bbdfc 100644 --- a/trade_queue.py +++ b/trade_queue.py @@ -3,7 +3,7 @@ import bisect from collections import deque from copy import deepcopy from decimal import Decimal -from typing import Callable, Deque, List +from typing import Callable, Deque, List, Tuple from exceptions import TradeNotFound from trade import Trade @@ -63,6 +63,63 @@ class FIFOQueue: self._cache_valid = False logger.info(f"Added trade: {trade}.") + def match_trades(self) -> List[Tuple[Trade, Trade]]: + """ + Match sell trades with buy trades in a FIFO manner, ensuring matched amounts are equal. + If multiple buy trades are needed for one sell trade, split the sell trade accordingly. + + Returns: + List[Tuple[Trade, Trade]]: List of tuples with (buy_trade, sell_trade) of equal amounts. + """ + matched_pairs: List[Tuple[Trade, Trade]] = [] + + # Locate the next sell trade + sell_trade = next((t for t in self.__queue if t.amount < 0), None) + + while sell_trade: + matched_pairs.extend(self.__match_trades_impl(sell_trade)) + self.__queue.remove(sell_trade) + self._cache_valid = False + sell_trade = next((t for t in self.__queue if t.amount < 0), None) + + if not sell_trade: + logger.info("No more sell trades available for matching.") + + return matched_pairs + + def __match_trades_impl(self, sell_trade: Trade) -> List[Tuple[Trade, Trade]]: + matched_pairs: List[Tuple[Trade, Trade]] = [] + + # Convert to positive for easier calculations + remaining_sell_amount = -sell_trade.amount + + # Use `self.remove_coins` to fetch corresponding buy trades + try: + buy_trades = self.remove_coins(remaining_sell_amount) + except ValueError as e: + logger.error(f"Failed to match trade: {e}") + raise + + # Process the buy trades and split the sell trade if necessary + for buy_trade in buy_trades: + # remove_coins should take care this doesn't happen + assert buy_trade.amount <= remaining_sell_amount + + matched_pairs.append( + ( + buy_trade, + Trade( + buy_trade.amount, + buy_trade.amount * sell_trade.price_per_coin, + sell_trade.timestamp, + ), + ) + ) + remaining_sell_amount -= buy_trade.amount + + logger.info(f"Matched sell trade {sell_trade} with buy trades: {matched_pairs}") + return matched_pairs + # The remove_coins method needs updating, it currently operates on the first trade in the queue, disregarding whether it's buy or sell def remove_coins( self, amount: float | Decimal, before_ts: str | None = None -- cgit v1.2.3