fix: separate trade price from realtime price

This commit is contained in:
王鹏
2026-07-09 10:45:27 +08:00
parent 5bbc4e607a
commit 15636fb910
9 changed files with 105 additions and 55 deletions

View File

@@ -75,7 +75,6 @@ def calculate_positions(
lambda: {group: _GroupState() for group in TradeGroup}
)
net_invested: dict[int, Decimal] = defaultdict(lambda: Decimal("0"))
last_trade_price: dict[int, Decimal] = {}
today_buys: dict[int, int] = defaultdict(int)
for trade in sorted(trades, key=lambda item: (item.trade_date, item.id or 0)):
@@ -89,8 +88,6 @@ def calculate_positions(
group_state = states[trade.instrument_id][trade.trade_group]
gross = money(trade.gross_amount)
fees = money(trade.total_fee)
last_trade_price[trade.instrument_id] = trade.price
if trade.side is TradeSide.BUY:
group_state.quantity += trade.quantity
group_state.cost = money(group_state.cost + gross + fees)
@@ -123,11 +120,7 @@ def calculate_positions(
remaining_cost = money(sum((state.cost for state in group_states.values()), Decimal("0")))
realized_pnl = money(sum((state.realized_pnl for state in group_states.values()), Decimal("0")))
grid_profit = money(group_states[TradeGroup.GRID].realized_pnl)
current_price, price_source = _resolve_current_price(
instrument,
last_trade_price.get(instrument.id),
quotes.get(instrument.id),
)
current_price, price_source = _resolve_current_price(quotes.get(instrument.id))
market_value = money(current_price * Decimal(total_quantity)) if current_price is not None else None
floating_pnl = money(market_value - remaining_cost) if market_value is not None else None
available_quantity = max(0, total_quantity - today_buys[instrument.id])
@@ -207,17 +200,9 @@ def _validate_trade(trade: Trade) -> None:
raise CalculationError("Trade fees cannot be negative")
def _resolve_current_price(
instrument: Instrument,
fallback_trade_price: Decimal | None,
quote_snapshot: QuoteSnapshot | None,
) -> tuple[Decimal | None, str]:
def _resolve_current_price(quote_snapshot: QuoteSnapshot | None) -> tuple[Decimal | None, str]:
if quote_snapshot is not None:
return quote_snapshot.price, quote_snapshot.source
if instrument.manual_price is not None:
return instrument.manual_price, "manual"
if fallback_trade_price is not None:
return fallback_trade_price, "last_trade"
return None, "missing"

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from datetime import date
from decimal import Decimal, InvalidOperation
from decimal import Decimal
from PySide6.QtCore import QDate
from PySide6.QtWidgets import (
@@ -81,7 +81,6 @@ class InstrumentDialog(QDialog):
self.lot_size_edit = QSpinBox()
self.lot_size_edit.setRange(1, 1_000_000)
self.lot_size_edit.setValue(instrument.lot_size if instrument else 100)
self.manual_price_edit = QLineEdit(str(instrument.manual_price or "") if instrument else "")
self.allow_odd_lot_edit = QCheckBox("允许非整手")
self.allow_odd_lot_edit.setChecked(instrument.allow_odd_lot if instrument else False)
@@ -90,7 +89,6 @@ class InstrumentDialog(QDialog):
form.addRow("名称", self.name_edit)
form.addRow("市场", self.market_edit)
form.addRow("交易单位", self.lot_size_edit)
form.addRow("手动价格", self.manual_price_edit)
form.addRow("", self.allow_odd_lot_edit)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
@@ -102,14 +100,13 @@ class InstrumentDialog(QDialog):
layout.addWidget(buttons)
def to_instrument(self) -> Instrument:
manual_price = _optional_decimal(self.manual_price_edit.text().strip(), "手动价格")
return Instrument(
id=self._instrument.id if self._instrument else None,
code=self.code_edit.text().strip(),
name=self.name_edit.text().strip(),
market=self.market_edit.currentText(),
lot_size=self.lot_size_edit.value(),
manual_price=manual_price,
manual_price=None,
allow_odd_lot=self.allow_odd_lot_edit.isChecked(),
active=True,
)
@@ -227,7 +224,7 @@ class TradeDialog(QDialog):
form.addRow("日期", self.date_edit)
form.addRow("方向", self.side_edit)
form.addRow("分组", self.group_edit)
form.addRow("", self.price_edit)
form.addRow("成交", self.price_edit)
form.addRow("数量", self.quantity_edit)
form.addRow(fee_row)
form.addRow("备注", self.notes_edit)
@@ -291,10 +288,3 @@ def _percent_spinbox(value: Decimal) -> QDoubleSpinBox:
return spinbox
def _optional_decimal(raw: str, label: str) -> Decimal | None:
if not raw:
return None
try:
return Decimal(raw)
except InvalidOperation as exc:
raise ValueError(f"{label} 不是有效数字") from exc