fix: validate historical trade chain changes

This commit is contained in:
王鹏
2026-07-08 17:36:37 +08:00
parent 6e73e90f99
commit e6794d6e33
2 changed files with 66 additions and 3 deletions

View File

@@ -143,6 +143,15 @@ class TradingService:
return self.save_trade(trade) return self.save_trade(trade)
def delete_trade(self, trade_id: int) -> None: 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) self.repository.delete_trade(trade_id)
def list_trades(self, *, instrument_id: int | None = None) -> list[Trade]: 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: 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() instruments = self.repository.list_instruments()
as_of = max(item.trade_date for item in trades)
try: try:
calculate_positions( calculate_positions(
instruments, instruments,
[*existing_trades, trade], trades,
as_of=trade.trade_date, as_of=as_of,
) )
except CalculationError as exc: 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]: def _instrument_cashflows(self, account_id: int) -> dict[int, Decimal]:
totals: dict[int, Decimal] = defaultdict(lambda: Decimal("0")) totals: dict[int, Decimal] = defaultdict(lambda: Decimal("0"))

View File

@@ -129,3 +129,51 @@ def test_service_validates_lot_size_and_available_sell_quantity(tmp_path):
) )
assert saved_sell.id is not None 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)