feat: add grid level suggestions
This commit is contained in:
69
tests/test_grid_levels.py
Normal file
69
tests/test_grid_levels.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from grid_trading.domain.grid_levels import generate_grid_levels
|
||||
|
||||
|
||||
def test_generate_grid_levels_compounds_percentage_spacing_and_rounds_values():
|
||||
levels = generate_grid_levels(
|
||||
current_price=Decimal("10.00"),
|
||||
spacing=Decimal("0.03"),
|
||||
amount_per_grid=Decimal("10000"),
|
||||
lot_size=100,
|
||||
levels=3,
|
||||
)
|
||||
|
||||
assert [item.level for item in levels] == [1, 2, 3]
|
||||
assert [item.buy_price for item in levels] == [Decimal("9.70"), Decimal("9.41"), Decimal("9.13")]
|
||||
assert [item.buy_amount for item in levels] == [Decimal("10000.00")] * 3
|
||||
assert [item.suggested_quantity for item in levels] == [1000, 1000, 1000]
|
||||
assert [item.actual_investment for item in levels] == [
|
||||
Decimal("9700.00"),
|
||||
Decimal("9410.00"),
|
||||
Decimal("9130.00"),
|
||||
]
|
||||
assert [item.sell_price for item in levels] == [Decimal("9.99"), Decimal("9.69"), Decimal("9.40")]
|
||||
assert [item.estimated_gross_profit for item in levels] == [
|
||||
Decimal("290.00"),
|
||||
Decimal("280.00"),
|
||||
Decimal("270.00"),
|
||||
]
|
||||
|
||||
|
||||
def test_generate_grid_levels_uses_zero_quantity_when_amount_cannot_buy_one_lot():
|
||||
[level] = generate_grid_levels(
|
||||
current_price=Decimal("10.00"),
|
||||
spacing=Decimal("0.03"),
|
||||
amount_per_grid=Decimal("500"),
|
||||
lot_size=100,
|
||||
levels=1,
|
||||
)
|
||||
|
||||
assert level.buy_price == Decimal("9.70")
|
||||
assert level.suggested_quantity == 0
|
||||
assert level.actual_investment == Decimal("0.00")
|
||||
assert level.estimated_gross_profit == Decimal("0.00")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("current_price", "spacing", "amount_per_grid", "lot_size", "levels"),
|
||||
[
|
||||
(Decimal("0"), Decimal("0.03"), Decimal("10000"), 100, 10),
|
||||
(Decimal("10"), Decimal("0"), Decimal("10000"), 100, 10),
|
||||
(Decimal("10"), Decimal("1"), Decimal("10000"), 100, 10),
|
||||
(Decimal("10"), Decimal("0.03"), Decimal("0"), 100, 10),
|
||||
(Decimal("10"), Decimal("0.03"), Decimal("10000"), 0, 10),
|
||||
(Decimal("10"), Decimal("0.03"), Decimal("10000"), 100, 0),
|
||||
(Decimal("10"), Decimal("0.03"), Decimal("10000"), 100, 101),
|
||||
],
|
||||
)
|
||||
def test_generate_grid_levels_validates_inputs(current_price, spacing, amount_per_grid, lot_size, levels):
|
||||
with pytest.raises(ValueError):
|
||||
generate_grid_levels(
|
||||
current_price=current_price,
|
||||
spacing=spacing,
|
||||
amount_per_grid=amount_per_grid,
|
||||
lot_size=lot_size,
|
||||
levels=levels,
|
||||
)
|
||||
@@ -223,3 +223,38 @@ def test_service_refresh_quotes_uses_realtime_price_in_summaries(tmp_path):
|
||||
assert position.current_price == Decimal("4.12")
|
||||
assert position.price_source == "tencent"
|
||||
assert summary.market_value == Decimal("4120.00")
|
||||
|
||||
|
||||
def test_service_generates_grid_level_suggestions_from_realtime_price(tmp_path):
|
||||
service = TradingService(tmp_path / "grid.db", quote_provider=FakeQuoteProvider())
|
||||
service.ensure_defaults()
|
||||
account = service.get_active_account()
|
||||
instrument = service.add_instrument(Instrument(id=None, code="510300", name="沪深300ETF", market="ETF"))
|
||||
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("4.00"),
|
||||
quantity=1000,
|
||||
trade_group=TradeGroup.BASE,
|
||||
)
|
||||
)
|
||||
service.refresh_quotes()
|
||||
|
||||
levels = service.get_grid_level_suggestions(instrument.id, levels=2)
|
||||
|
||||
assert [item.buy_price for item in levels] == [Decimal("4.00"), Decimal("3.88")]
|
||||
assert [item.sell_price for item in levels] == [Decimal("4.12"), Decimal("4.00")]
|
||||
assert [item.buy_amount for item in levels] == [Decimal("5000.00"), Decimal("5000.00")]
|
||||
assert [item.suggested_quantity for item in levels] == [1200, 1200]
|
||||
|
||||
|
||||
def test_service_returns_empty_grid_levels_without_realtime_price(tmp_path):
|
||||
service = TradingService(tmp_path / "grid.db")
|
||||
service.ensure_defaults()
|
||||
instrument = service.add_instrument(Instrument(id=None, code="510300", name="沪深300ETF", market="ETF"))
|
||||
|
||||
assert service.get_grid_level_suggestions(instrument.id, levels=10) == []
|
||||
|
||||
@@ -35,6 +35,26 @@ def test_main_window_can_be_constructed_offscreen(tmp_path, monkeypatch):
|
||||
app.processEvents()
|
||||
|
||||
|
||||
def test_main_window_contains_grid_level_table(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 window.grid_levels_table.columnCount() == 7
|
||||
assert 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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user