258 lines
8.4 KiB
Python
258 lines
8.4 KiB
Python
from decimal import Decimal
|
|
import time
|
|
|
|
from grid_trading.domain.models import Instrument
|
|
|
|
|
|
def test_formatters_render_money_percent_and_empty_values():
|
|
from grid_trading.ui.formatters import format_money, format_percent, format_price, format_quantity
|
|
|
|
assert format_money(Decimal("1234.5")) == "1,234.50"
|
|
assert format_price(Decimal("3.956")) == "3.96"
|
|
assert format_percent(Decimal("0.1234")) == "12.34%"
|
|
assert format_quantity(1200) == "1,200"
|
|
assert format_money(None) == "-"
|
|
|
|
|
|
def test_main_window_can_be_constructed_offscreen(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication, QLabel, QPushButton
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
|
|
assert window.windowTitle() == "Grid Trading Manager"
|
|
assert window.holdings_table.columnCount() > 0
|
|
assert any(button.text() == "刷新行情" for button in window.findChildren(QPushButton))
|
|
summary_titles = [label.text() for label in window.findChildren(QLabel)]
|
|
assert "账户权益" in summary_titles
|
|
assert "总资产" not in summary_titles
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|
|
|
|
|
|
def test_main_window_contains_grid_level_table(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication, QTabWidget
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
|
|
assert window.grid_levels_table.columnCount() == 7
|
|
tab_titles = _tab_titles(window.details_tabs)
|
|
assert tab_titles == ["网格档位", "待卖网格", "配对明细", "最近成交"]
|
|
assert isinstance(window.details_tabs, QTabWidget)
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|
|
|
|
|
|
def test_main_window_contains_open_grid_lots_table(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
|
|
assert window.open_grid_lots_table.columnCount() == 8
|
|
assert "待卖网格" in _tab_titles(window.details_tabs)
|
|
assert window.grid_matches_table.columnCount() == 9
|
|
assert "配对明细" in _tab_titles(window.details_tabs)
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|
|
|
|
|
|
def test_main_window_applies_light_theme(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
|
|
assert window.centralWidget().objectName() == "appRoot"
|
|
assert window.holdings_table.alternatingRowColors()
|
|
assert "QMainWindow" in window.styleSheet()
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|
|
|
|
|
|
def test_profit_and_loss_cells_use_a_share_colors(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtGui import QColor
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from grid_trading.domain.models import PositionSummary
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
window._positions = [
|
|
PositionSummary(
|
|
instrument_id=1,
|
|
code="510300",
|
|
name="沪深300ETF",
|
|
market="ETF",
|
|
current_price=Decimal("10.50"),
|
|
price_source="quote",
|
|
total_quantity=100,
|
|
available_quantity=100,
|
|
base_quantity=0,
|
|
grid_quantity=100,
|
|
other_quantity=0,
|
|
remaining_cost=Decimal("1000"),
|
|
cost_price=Decimal("10"),
|
|
position_breakeven_price=Decimal("10"),
|
|
account_breakeven_price=Decimal("10"),
|
|
realized_pnl=Decimal("12.34"),
|
|
grid_profit=Decimal("-5.67"),
|
|
floating_pnl=Decimal("50"),
|
|
market_value=Decimal("1050"),
|
|
)
|
|
]
|
|
|
|
window.holdings_table.blockSignals(True)
|
|
window._fill_holdings_table()
|
|
window.holdings_table.blockSignals(False)
|
|
|
|
profit_item = window.holdings_table.item(0, 10)
|
|
loss_item = window.holdings_table.item(0, 11)
|
|
assert profit_item.foreground().color() == QColor("#c62828")
|
|
assert profit_item.font().bold()
|
|
assert loss_item.foreground().color() == QColor("#2e7d32")
|
|
assert loss_item.font().bold()
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|
|
|
|
|
|
def test_main_window_removes_selected_instrument_detail_group(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication, QGroupBox
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db")
|
|
window = MainWindow(service)
|
|
|
|
assert not any(group.title() == "选中标的详情" for group in window.findChildren(QGroupBox))
|
|
|
|
window.close()
|
|
service.close()
|
|
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 _tab_titles(tabs):
|
|
return [tabs.tabText(index) for index in range(tabs.count())]
|
|
|
|
|
|
def test_quote_refresh_does_not_block_main_window(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
from grid_trading.services.trading_service import TradingService
|
|
from grid_trading.ui.main_window import MainWindow
|
|
|
|
class SlowQuoteProvider:
|
|
def fetch_quotes(self, instruments):
|
|
time.sleep(0.3)
|
|
return {}
|
|
|
|
app = QApplication.instance() or QApplication([])
|
|
service = TradingService(tmp_path / "grid.db", quote_provider=SlowQuoteProvider())
|
|
service.ensure_defaults()
|
|
service.add_instrument(Instrument(id=None, code="000001", name="Ping An Bank"))
|
|
window = MainWindow(service)
|
|
|
|
started_at = time.perf_counter()
|
|
window._refresh_quotes()
|
|
elapsed = time.perf_counter() - started_at
|
|
|
|
assert elapsed < 0.15
|
|
|
|
deadline = time.perf_counter() + 2
|
|
while getattr(window, "_quote_thread", None) is not None and time.perf_counter() < deadline:
|
|
app.processEvents()
|
|
time.sleep(0.01)
|
|
|
|
assert getattr(window, "_quote_thread", None) is None
|
|
|
|
window.close()
|
|
service.close()
|
|
app.processEvents()
|