fix: separate trade price from realtime price
This commit is contained in:
@@ -37,7 +37,7 @@ def make_trade(
|
||||
|
||||
def test_buy_sell_grid_profit_and_breakeven():
|
||||
today = date(2026, 7, 8)
|
||||
instrument = Instrument(id=1, code="510300", name="沪深300ETF", manual_price=Decimal("9.50"))
|
||||
instrument = Instrument(id=1, code="510300", name="沪深300ETF")
|
||||
trades = [
|
||||
make_trade(
|
||||
trade_id=1,
|
||||
@@ -68,7 +68,17 @@ def test_buy_sell_grid_profit_and_breakeven():
|
||||
),
|
||||
]
|
||||
|
||||
[summary] = calculate_positions([instrument], trades, as_of=today)
|
||||
quotes = {
|
||||
1: QuoteSnapshot(
|
||||
symbol="sh510300",
|
||||
code="510300",
|
||||
name="沪深300ETF",
|
||||
price=Decimal("9.50"),
|
||||
source="tencent",
|
||||
)
|
||||
}
|
||||
|
||||
[summary] = calculate_positions([instrument], trades, as_of=today, quote_snapshots=quotes)
|
||||
|
||||
assert summary.total_quantity == 100
|
||||
assert summary.base_quantity == 100
|
||||
@@ -79,6 +89,8 @@ def test_buy_sell_grid_profit_and_breakeven():
|
||||
assert summary.cost_price == Decimal("9.01")
|
||||
assert summary.position_breakeven_price == Decimal("8.03")
|
||||
assert summary.account_breakeven_price == Decimal("8.03")
|
||||
assert summary.current_price == Decimal("9.50")
|
||||
assert summary.price_source == "tencent"
|
||||
assert summary.market_value == Decimal("950.00")
|
||||
assert summary.floating_pnl == Decimal("49.00")
|
||||
|
||||
@@ -111,7 +123,7 @@ def test_t_plus_one_available_quantity_excludes_today_buys():
|
||||
assert summary.available_quantity == 200
|
||||
|
||||
|
||||
def test_quote_snapshot_overrides_manual_price_for_position_value():
|
||||
def test_quote_snapshot_supplies_current_price_for_position_value():
|
||||
today = date(2026, 7, 8)
|
||||
instrument = Instrument(id=1, code="510300", name="沪深300ETF", manual_price=Decimal("3.90"))
|
||||
trades = [
|
||||
@@ -141,6 +153,28 @@ def test_quote_snapshot_overrides_manual_price_for_position_value():
|
||||
assert summary.market_value == Decimal("4120.00")
|
||||
|
||||
|
||||
def test_missing_quote_does_not_use_manual_or_last_trade_price_for_current_price():
|
||||
today = date(2026, 7, 8)
|
||||
instrument = Instrument(id=1, code="510300", name="沪深300ETF", manual_price=Decimal("3.90"))
|
||||
trades = [
|
||||
make_trade(
|
||||
trade_id=1,
|
||||
trade_date=today - timedelta(days=1),
|
||||
side=TradeSide.BUY,
|
||||
price="4.00",
|
||||
quantity=1000,
|
||||
trade_group=TradeGroup.BASE,
|
||||
)
|
||||
]
|
||||
|
||||
[summary] = calculate_positions([instrument], trades, as_of=today)
|
||||
|
||||
assert summary.current_price is None
|
||||
assert summary.price_source == "missing"
|
||||
assert summary.market_value is None
|
||||
assert summary.floating_pnl is None
|
||||
|
||||
|
||||
def test_sell_more_than_group_position_raises():
|
||||
today = date(2026, 7, 8)
|
||||
instrument = Instrument(id=1, code="159915", name="创业板ETF")
|
||||
|
||||
@@ -64,10 +64,12 @@ def test_service_creates_default_account_and_computes_summary(tmp_path):
|
||||
|
||||
assert len(positions) == 1
|
||||
assert positions[0].total_quantity == 500
|
||||
assert positions[0].current_price is None
|
||||
assert positions[0].price_source == "missing"
|
||||
assert positions[0].grid_profit == Decimal("91.47")
|
||||
assert summary.cash == Decimal("98138.97")
|
||||
assert summary.market_value == Decimal("2000.00")
|
||||
assert summary.total_assets == Decimal("100138.97")
|
||||
assert summary.market_value == Decimal("0.00")
|
||||
assert summary.total_assets == Decimal("98138.97")
|
||||
|
||||
|
||||
def test_service_validates_lot_size_and_available_sell_quantity(tmp_path):
|
||||
|
||||
@@ -35,6 +35,47 @@ def test_main_window_can_be_constructed_offscreen(tmp_path, monkeypatch):
|
||||
app.processEvents()
|
||||
|
||||
|
||||
def test_instrument_dialog_does_not_collect_manual_current_price(monkeypatch):
|
||||
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QLabel
|
||||
|
||||
from grid_trading.ui.dialogs import InstrumentDialog
|
||||
|
||||
app = QApplication.instance() or QApplication([])
|
||||
dialog = InstrumentDialog()
|
||||
|
||||
labels = [label.text() for label in dialog.findChildren(QLabel)]
|
||||
assert "手动价格" not in labels
|
||||
assert not hasattr(dialog, "manual_price_edit")
|
||||
assert dialog.to_instrument().manual_price is None
|
||||
|
||||
dialog.close()
|
||||
app.processEvents()
|
||||
|
||||
|
||||
def test_trade_dialog_labels_price_as_trade_price(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QLabel
|
||||
|
||||
from grid_trading.services.trading_service import TradingService
|
||||
from grid_trading.ui.dialogs import TradeDialog
|
||||
|
||||
app = QApplication.instance() or QApplication([])
|
||||
service = TradingService(tmp_path / "grid.db")
|
||||
service.ensure_defaults()
|
||||
account = service.get_active_account()
|
||||
dialog = TradeDialog(service, account.id, [Instrument(id=1, code="510300", name="沪深300ETF")])
|
||||
|
||||
labels = [label.text() for label in dialog.findChildren(QLabel)]
|
||||
assert "成交价" in labels
|
||||
|
||||
dialog.close()
|
||||
service.close()
|
||||
app.processEvents()
|
||||
|
||||
|
||||
def test_quote_refresh_does_not_block_main_window(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user