fix: validate historical trade chain changes
This commit is contained in:
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user