fix: separate trade price from realtime price
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user