diff --git a/src/grid_trading/services/trading_service.py b/src/grid_trading/services/trading_service.py index bb70a0d..8f73254 100644 --- a/src/grid_trading/services/trading_service.py +++ b/src/grid_trading/services/trading_service.py @@ -143,6 +143,15 @@ class TradingService: return self.save_trade(trade) def delete_trade(self, trade_id: int) -> None: + trade = self.repository.get_trade(trade_id) + if trade is None: + return + remaining_trades = [ + existing + for existing in self.repository.list_trades(account_id=trade.account_id) + if existing.id != trade_id + ] + self._validate_trade_chain(remaining_trades) self.repository.delete_trade(trade_id) def list_trades(self, *, instrument_id: int | None = None) -> list[Trade]: @@ -216,15 +225,21 @@ class TradingService: ) def _validate_resulting_position(self, trade: Trade, existing_trades: list[Trade]) -> None: + self._validate_trade_chain([*existing_trades, trade]) + + def _validate_trade_chain(self, trades: list[Trade]) -> None: + if not trades: + return instruments = self.repository.list_instruments() + as_of = max(item.trade_date for item in trades) try: calculate_positions( instruments, - [*existing_trades, trade], - as_of=trade.trade_date, + trades, + as_of=as_of, ) except CalculationError as exc: - raise ValueError(str(exc)) from exc + raise ValueError(f"后续成交重算失败:{exc}") from exc def _instrument_cashflows(self, account_id: int) -> dict[int, Decimal]: totals: dict[int, Decimal] = defaultdict(lambda: Decimal("0")) diff --git a/tests/test_services.py b/tests/test_services.py index 0d74e0c..2bf4aab 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -129,3 +129,51 @@ def test_service_validates_lot_size_and_available_sell_quantity(tmp_path): ) assert saved_sell.id is not None + + +def test_service_rejects_historical_changes_that_break_future_sells(tmp_path): + service = TradingService(tmp_path / "grid.db") + service.ensure_defaults() + account = service.get_active_account() + instrument = service.add_instrument(Instrument(id=None, code="159915", name="创业板ETF")) + buy = service.save_trade( + Trade( + id=None, + account_id=account.id, + instrument_id=instrument.id, + trade_date=date(2026, 7, 7), + side=TradeSide.BUY, + price=Decimal("2.00"), + quantity=200, + trade_group=TradeGroup.GRID, + ) + ) + service.save_trade( + Trade( + id=None, + account_id=account.id, + instrument_id=instrument.id, + trade_date=date(2026, 7, 8), + side=TradeSide.SELL, + price=Decimal("2.10"), + quantity=200, + trade_group=TradeGroup.GRID, + ) + ) + + with pytest.raises(ValueError, match="后续成交"): + service.update_trade( + Trade( + id=buy.id, + account_id=account.id, + instrument_id=instrument.id, + trade_date=date(2026, 7, 7), + side=TradeSide.BUY, + price=Decimal("2.00"), + quantity=100, + trade_group=TradeGroup.GRID, + ) + ) + + with pytest.raises(ValueError, match="后续成交"): + service.delete_trade(buy.id)