from __future__ import annotations from dataclasses import replace from datetime import date from decimal import Decimal from grid_trading.domain.calculations import money, price from grid_trading.domain.models import OpenGridLot, Trade, TradeGroup, TradeSide def calculate_open_grid_lots( trades: list[Trade], *, spacing: Decimal, current_price: Decimal | None, as_of: date, ) -> list[OpenGridLot]: if spacing <= 0 or spacing >= 1: raise ValueError("网格间距必须大于 0 且小于 100%") lots: list[OpenGridLot] = [] for trade in sorted(trades, key=lambda item: (item.trade_date, item.id or 0)): if trade.trade_date > as_of or trade.trade_group is not TradeGroup.GRID: continue if trade.side is TradeSide.BUY: buy_price = price(trade.price) suggested_sell_price = price(buy_price * (Decimal("1") + spacing)) lots.append( OpenGridLot( buy_trade_id=trade.id, buy_date=trade.trade_date, buy_price=buy_price, remaining_quantity=trade.quantity, actual_investment=money(buy_price * Decimal(trade.quantity)), suggested_sell_price=suggested_sell_price, estimated_gross_profit=money( (suggested_sell_price - buy_price) * Decimal(trade.quantity) ), current_price=current_price, status=_status(current_price, suggested_sell_price), ) ) else: lots = _match_sell_against_lots(lots, trade.quantity) return lots def _match_sell_against_lots(lots: list[OpenGridLot], quantity: int) -> list[OpenGridLot]: remaining_sell_quantity = quantity updated_lots: list[OpenGridLot] = [] for lot in lots: if remaining_sell_quantity <= 0: updated_lots.append(lot) continue matched_quantity = min(lot.remaining_quantity, remaining_sell_quantity) remaining_sell_quantity -= matched_quantity remaining_quantity = lot.remaining_quantity - matched_quantity if remaining_quantity > 0: updated_lots.append(_with_remaining_quantity(lot, remaining_quantity)) return updated_lots def _with_remaining_quantity(lot: OpenGridLot, quantity: int) -> OpenGridLot: return replace( lot, remaining_quantity=quantity, actual_investment=money(lot.buy_price * Decimal(quantity)), estimated_gross_profit=money((lot.suggested_sell_price - lot.buy_price) * Decimal(quantity)), ) def _status(current_price: Decimal | None, suggested_sell_price: Decimal) -> str: if current_price is None: return "未刷新行情" if current_price >= suggested_sell_price: return "可卖" return "未到价"