docs: design open grid lots
This commit is contained in:
464
docs/superpowers/plans/2026-07-09-open-grid-lots.md
Normal file
464
docs/superpowers/plans/2026-07-09-open-grid-lots.md
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
# Open Grid Lots Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Show unsold grid-buy lots with buy price, remaining quantity, suggested sell price, expected gross profit, current price, and sell readiness.
|
||||||
|
|
||||||
|
**Architecture:** Add a pure domain calculator that FIFO-matches grid sells against grid buys, expose it through `TradingService`, then render the result in a new PySide6 “待卖网格” table in the selected-instrument details area. The feature reuses existing trades, the default strategy template, and in-memory Tencent quote snapshots.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.11, Decimal, PySide6, SQLite repository/service pattern, pytest.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- Create `src/grid_trading/domain/open_grid_lots.py`: pure FIFO calculation for unsold grid lots.
|
||||||
|
- Modify `src/grid_trading/domain/models.py`: add `OpenGridLot`.
|
||||||
|
- Modify `src/grid_trading/services/trading_service.py`: add `get_open_grid_lots(instrument_id, as_of=None)`.
|
||||||
|
- Modify `src/grid_trading/ui/main_window.py`: add the “待卖网格” table and refresh it with selected-instrument details.
|
||||||
|
- Create `tests/test_open_grid_lots.py`: domain FIFO and status tests.
|
||||||
|
- Modify `tests/test_services.py`: service integration test.
|
||||||
|
- Modify `tests/test_ui.py`: GUI smoke test.
|
||||||
|
- Modify `README.md`: document the new table.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### Task 1: Domain FIFO Calculator
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/grid_trading/domain/open_grid_lots.py`
|
||||||
|
- Modify: `src/grid_trading/domain/models.py`
|
||||||
|
- Test: `tests/test_open_grid_lots.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing domain tests**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from grid_trading.domain.models import Trade, TradeGroup, TradeSide
|
||||||
|
from grid_trading.domain.open_grid_lots import calculate_open_grid_lots
|
||||||
|
|
||||||
|
|
||||||
|
def make_trade(*, trade_id, trade_date, side, price, quantity, trade_group=TradeGroup.GRID):
|
||||||
|
return Trade(
|
||||||
|
id=trade_id,
|
||||||
|
account_id=1,
|
||||||
|
instrument_id=1,
|
||||||
|
trade_date=trade_date,
|
||||||
|
side=side,
|
||||||
|
price=Decimal(price),
|
||||||
|
quantity=quantity,
|
||||||
|
trade_group=trade_group,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_calculate_open_grid_lots_fifo_matches_grid_sells_against_grid_buys():
|
||||||
|
today = date(2026, 7, 9)
|
||||||
|
trades = [
|
||||||
|
make_trade(trade_id=1, trade_date=today - timedelta(days=3), side=TradeSide.BUY, price="10.00", quantity=300),
|
||||||
|
make_trade(trade_id=2, trade_date=today - timedelta(days=2), side=TradeSide.BUY, price="9.50", quantity=200),
|
||||||
|
make_trade(trade_id=3, trade_date=today - timedelta(days=1), side=TradeSide.SELL, price="10.30", quantity=350),
|
||||||
|
make_trade(
|
||||||
|
trade_id=4,
|
||||||
|
trade_date=today,
|
||||||
|
side=TradeSide.BUY,
|
||||||
|
price="8.00",
|
||||||
|
quantity=100,
|
||||||
|
trade_group=TradeGroup.BASE,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
lots = calculate_open_grid_lots(
|
||||||
|
trades,
|
||||||
|
spacing=Decimal("0.03"),
|
||||||
|
current_price=Decimal("9.80"),
|
||||||
|
as_of=today,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(lots) == 1
|
||||||
|
[lot] = lots
|
||||||
|
assert lot.buy_trade_id == 2
|
||||||
|
assert lot.buy_date == today - timedelta(days=2)
|
||||||
|
assert lot.buy_price == Decimal("9.50")
|
||||||
|
assert lot.remaining_quantity == 150
|
||||||
|
assert lot.actual_investment == Decimal("1425.00")
|
||||||
|
assert lot.suggested_sell_price == Decimal("9.79")
|
||||||
|
assert lot.estimated_gross_profit == Decimal("43.50")
|
||||||
|
assert lot.current_price == Decimal("9.80")
|
||||||
|
assert lot.status == "可卖"
|
||||||
|
|
||||||
|
|
||||||
|
def test_calculate_open_grid_lots_reports_not_reached_and_missing_quote_statuses():
|
||||||
|
today = date(2026, 7, 9)
|
||||||
|
trades = [
|
||||||
|
make_trade(trade_id=1, trade_date=today, side=TradeSide.BUY, price="10.00", quantity=100),
|
||||||
|
]
|
||||||
|
|
||||||
|
[not_reached] = calculate_open_grid_lots(
|
||||||
|
trades,
|
||||||
|
spacing=Decimal("0.03"),
|
||||||
|
current_price=Decimal("10.20"),
|
||||||
|
as_of=today,
|
||||||
|
)
|
||||||
|
[missing_quote] = calculate_open_grid_lots(
|
||||||
|
trades,
|
||||||
|
spacing=Decimal("0.03"),
|
||||||
|
current_price=None,
|
||||||
|
as_of=today,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not_reached.suggested_sell_price == Decimal("10.30")
|
||||||
|
assert not_reached.status == "未到价"
|
||||||
|
assert missing_quote.current_price is None
|
||||||
|
assert missing_quote.status == "未刷新行情"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run domain tests and verify red**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_open_grid_lots.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL because `grid_trading.domain.open_grid_lots` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add model and implementation**
|
||||||
|
|
||||||
|
Add `OpenGridLot` to `models.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class OpenGridLot:
|
||||||
|
buy_trade_id: int | None
|
||||||
|
buy_date: date
|
||||||
|
buy_price: Decimal
|
||||||
|
remaining_quantity: int
|
||||||
|
actual_investment: Decimal
|
||||||
|
suggested_sell_price: Decimal
|
||||||
|
estimated_gross_profit: Decimal
|
||||||
|
current_price: Decimal | None
|
||||||
|
status: str
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `open_grid_lots.py` with FIFO matching:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import replace
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from grid_trading.domain.calculations import money, price
|
||||||
|
from grid_trading.domain.models import OpenGridLot, Trade, TradeGroup, TradeSide
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_open_grid_lots(
|
||||||
|
trades: list[Trade],
|
||||||
|
*,
|
||||||
|
spacing: Decimal,
|
||||||
|
current_price: Decimal | None,
|
||||||
|
as_of: date,
|
||||||
|
) -> list[OpenGridLot]:
|
||||||
|
if spacing <= 0 or spacing >= 1:
|
||||||
|
raise ValueError("网格间距必须大于 0 且小于 100%")
|
||||||
|
|
||||||
|
lots: list[OpenGridLot] = []
|
||||||
|
for trade in sorted(trades, key=lambda item: (item.trade_date, item.id or 0)):
|
||||||
|
if trade.trade_date > as_of or trade.trade_group is not TradeGroup.GRID:
|
||||||
|
continue
|
||||||
|
if trade.side is TradeSide.BUY:
|
||||||
|
suggested_sell_price = price(trade.price * (Decimal("1") + spacing))
|
||||||
|
lots.append(
|
||||||
|
OpenGridLot(
|
||||||
|
buy_trade_id=trade.id,
|
||||||
|
buy_date=trade.trade_date,
|
||||||
|
buy_price=price(trade.price),
|
||||||
|
remaining_quantity=trade.quantity,
|
||||||
|
actual_investment=money(trade.price * Decimal(trade.quantity)),
|
||||||
|
suggested_sell_price=suggested_sell_price,
|
||||||
|
estimated_gross_profit=money((suggested_sell_price - price(trade.price)) * Decimal(trade.quantity)),
|
||||||
|
current_price=current_price,
|
||||||
|
status=_status(current_price, suggested_sell_price),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
remaining_sell_quantity = trade.quantity
|
||||||
|
updated_lots: list[OpenGridLot] = []
|
||||||
|
for lot in lots:
|
||||||
|
if remaining_sell_quantity <= 0:
|
||||||
|
updated_lots.append(lot)
|
||||||
|
continue
|
||||||
|
matched_quantity = min(lot.remaining_quantity, remaining_sell_quantity)
|
||||||
|
remaining_sell_quantity -= matched_quantity
|
||||||
|
remaining_quantity = lot.remaining_quantity - matched_quantity
|
||||||
|
if remaining_quantity > 0:
|
||||||
|
updated_lots.append(_with_remaining_quantity(lot, remaining_quantity))
|
||||||
|
lots = updated_lots
|
||||||
|
return lots
|
||||||
|
|
||||||
|
|
||||||
|
def _with_remaining_quantity(lot: OpenGridLot, quantity: int) -> OpenGridLot:
|
||||||
|
return replace(
|
||||||
|
lot,
|
||||||
|
remaining_quantity=quantity,
|
||||||
|
actual_investment=money(lot.buy_price * Decimal(quantity)),
|
||||||
|
estimated_gross_profit=money((lot.suggested_sell_price - lot.buy_price) * Decimal(quantity)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _status(current_price: Decimal | None, suggested_sell_price: Decimal) -> str:
|
||||||
|
if current_price is None:
|
||||||
|
return "未刷新行情"
|
||||||
|
if current_price >= suggested_sell_price:
|
||||||
|
return "可卖"
|
||||||
|
return "未到价"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run domain tests and verify green**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_open_grid_lots.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
### Task 2: Service API
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/grid_trading/services/trading_service.py`
|
||||||
|
- Test: `tests/test_services.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing service test**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_service_returns_open_grid_lots_with_suggested_sell_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.GRID,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
service.save_trade(
|
||||||
|
Trade(
|
||||||
|
id=None,
|
||||||
|
account_id=account.id,
|
||||||
|
instrument_id=instrument.id,
|
||||||
|
trade_date=date(2026, 7, 8),
|
||||||
|
side=TradeSide.SELL,
|
||||||
|
price=Decimal("4.12"),
|
||||||
|
quantity=400,
|
||||||
|
trade_group=TradeGroup.GRID,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
service.refresh_quotes()
|
||||||
|
|
||||||
|
[lot] = service.get_open_grid_lots(instrument.id, as_of=date(2026, 7, 9))
|
||||||
|
|
||||||
|
assert lot.buy_price == Decimal("4.00")
|
||||||
|
assert lot.remaining_quantity == 600
|
||||||
|
assert lot.suggested_sell_price == Decimal("4.12")
|
||||||
|
assert lot.current_price == Decimal("4.12")
|
||||||
|
assert lot.status == "可卖"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run service test and verify red**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_services.py::test_service_returns_open_grid_lots_with_suggested_sell_price -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL because `get_open_grid_lots` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement service method**
|
||||||
|
|
||||||
|
Import `calculate_open_grid_lots` and `OpenGridLot`, then add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_open_grid_lots(
|
||||||
|
self,
|
||||||
|
instrument_id: int,
|
||||||
|
*,
|
||||||
|
as_of: date | None = None,
|
||||||
|
) -> list[OpenGridLot]:
|
||||||
|
as_of_date = as_of or date.today()
|
||||||
|
self._require_instrument(instrument_id)
|
||||||
|
account = self.get_active_account()
|
||||||
|
trades = self.repository.list_trades(account_id=account.id, instrument_id=instrument_id)
|
||||||
|
position = next(
|
||||||
|
(
|
||||||
|
item
|
||||||
|
for item in self.get_position_summaries(as_of=as_of_date)
|
||||||
|
if item.instrument_id == instrument_id
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
template = self.get_default_strategy_template()
|
||||||
|
return calculate_open_grid_lots(
|
||||||
|
trades,
|
||||||
|
spacing=template.grid_spacing_pct,
|
||||||
|
current_price=position.current_price if position is not None else None,
|
||||||
|
as_of=as_of_date,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run service tests and verify green**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_services.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
### Task 3: GUI Table
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/grid_trading/ui/main_window.py`
|
||||||
|
- Test: `tests/test_ui.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing GUI smoke test**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_main_window_contains_open_grid_lots_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.open_grid_lots_table.columnCount() == 8
|
||||||
|
assert any(group.title() == "待卖网格" for group in window.findChildren(QGroupBox))
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
service.close()
|
||||||
|
app.processEvents()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run GUI test and verify red**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_ui.py::test_main_window_contains_open_grid_lots_table -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL because `open_grid_lots_table` does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement GUI table**
|
||||||
|
|
||||||
|
Add `OPEN_GRID_LOT_COLUMNS`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
OPEN_GRID_LOT_COLUMNS = [
|
||||||
|
"买入日期",
|
||||||
|
"买入价",
|
||||||
|
"剩余股数",
|
||||||
|
"实际投入",
|
||||||
|
"建议卖出价",
|
||||||
|
"预计毛利润",
|
||||||
|
"当前价",
|
||||||
|
"状态",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Create `self.open_grid_lots_table` in `_build_ui`, add a `QGroupBox("待卖网格")`, and refresh it from `_refresh_details`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _refresh_open_grid_lots(self, position: PositionSummary | None) -> None:
|
||||||
|
if position is None:
|
||||||
|
self._fill_open_grid_lots_table([])
|
||||||
|
return
|
||||||
|
lots = self.service.get_open_grid_lots(position.instrument_id)
|
||||||
|
self._fill_open_grid_lots_table(lots)
|
||||||
|
|
||||||
|
|
||||||
|
def _fill_open_grid_lots_table(self, lots) -> None:
|
||||||
|
self.open_grid_lots_table.setRowCount(len(lots))
|
||||||
|
for row, lot in enumerate(lots):
|
||||||
|
values = [
|
||||||
|
lot.buy_date.isoformat(),
|
||||||
|
format_price(lot.buy_price),
|
||||||
|
format_quantity(lot.remaining_quantity),
|
||||||
|
format_money(lot.actual_investment),
|
||||||
|
format_price(lot.suggested_sell_price),
|
||||||
|
format_money(lot.estimated_gross_profit),
|
||||||
|
format_price(lot.current_price),
|
||||||
|
lot.status,
|
||||||
|
]
|
||||||
|
for column, value in enumerate(values):
|
||||||
|
self.open_grid_lots_table.setItem(row, column, QTableWidgetItem(value))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run GUI tests and verify green**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest tests/test_ui.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
### Task 4: Docs And Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `README.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update README**
|
||||||
|
|
||||||
|
Document that the selected-instrument area contains a “待卖网格” table showing unsold grid buys and suggested sell prices.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run all tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pytest -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run CLI smoke**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m grid_trading.app --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: help text prints normally.
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
- Spec coverage: FIFO matching, suggested sell price, expected profit, current-price status, service API, GUI table, and README are covered.
|
||||||
|
- Placeholder scan: no unresolved placeholder text is intentionally left.
|
||||||
|
- Type consistency: `OpenGridLot`, `calculate_open_grid_lots`, and `get_open_grid_lots` names match across tasks.
|
||||||
46
docs/superpowers/specs/2026-07-09-open-grid-lots-design.md
Normal file
46
docs/superpowers/specs/2026-07-09-open-grid-lots-design.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# 待卖网格明细设计
|
||||||
|
|
||||||
|
日期:2026-07-09
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
在选中标的详情区展示“待卖网格”明细,让用户看清当前网格仓由哪些尚未卖出的网格买入组成、每笔买入价是多少、按策略应该挂到什么卖出价。
|
||||||
|
|
||||||
|
该功能只做计算和展示,不自动下单,不自动生成成交。
|
||||||
|
|
||||||
|
## 已确认口径
|
||||||
|
|
||||||
|
- 只统计成交分组为 `网格` 的成交。
|
||||||
|
- 网格买入形成一笔待卖批次。
|
||||||
|
- 网格卖出按时间顺序 FIFO 抵消最早的待卖批次。
|
||||||
|
- 仍有剩余数量的买入批次显示在“待卖网格”表中。
|
||||||
|
- 建议卖出价 = 买入价 × (1 + 默认策略模板的网格间距)。
|
||||||
|
- 预计毛利润 = (建议卖出价 - 买入价) × 剩余股数,不扣手续费。
|
||||||
|
- 如果已经刷新行情,则显示当前价,并用当前价判断状态:
|
||||||
|
- 当前价 >= 建议卖出价:`可卖`
|
||||||
|
- 当前价 < 建议卖出价:`未到价`
|
||||||
|
- 没有现价:`未刷新行情`
|
||||||
|
|
||||||
|
## 展示列
|
||||||
|
|
||||||
|
新增表格标题:`待卖网格`
|
||||||
|
|
||||||
|
列:
|
||||||
|
|
||||||
|
```text
|
||||||
|
买入日期 / 买入价 / 剩余股数 / 实际投入 / 建议卖出价 / 预计毛利润 / 当前价 / 状态
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码结构
|
||||||
|
|
||||||
|
- 新增领域模型 `OpenGridLot`,表示一笔尚未卖完的网格买入。
|
||||||
|
- 新增领域计算模块 `open_grid_lots.py`,根据成交记录、网格间距和现价计算待卖批次。
|
||||||
|
- 新增服务方法 `TradingService.get_open_grid_lots(instrument_id)`,供 GUI 调用。
|
||||||
|
- 主窗口下方详情区域新增“待卖网格”表,选中标的变化、刷新行情、录入/编辑/删除成交后自动刷新。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不改变现有持仓成本和网格利润算法。
|
||||||
|
- 不自动匹配券商委托。
|
||||||
|
- 不新增数据库表。
|
||||||
|
- 不扣除手续费估算净利润。
|
||||||
Reference in New Issue
Block a user