fix复盘交易记录盈亏与数据统计同口径,并展示期权手续费。
列表盈亏改为币盈亏×卖回价;手续费仅计期权开/平,不含现货。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-12
@@ -31,35 +31,36 @@
|
||||
|
||||
## 2. 双口径定义
|
||||
|
||||
### 2.1 口径 A — 期权复盘(交易账户)
|
||||
### 2.1 口径 A — 交易账户对账(内部冻结)
|
||||
|
||||
```
|
||||
本单已实现盈亏(U) = trading_usdt_after − trading_usdt_before
|
||||
```
|
||||
|
||||
- **消费者**:期权复盘「交易记录」「复盘记录」、复盘 KPI
|
||||
- **刻意含多买缓冲 / 滑点 / 手续费**:便于交易账户对账
|
||||
- **用途**:交易账户对账、卖回后冻结(`pnl_settled`)
|
||||
- **刻意含多买缓冲 / 滑点 / 现货侧成本**
|
||||
- **门禁**:全部期权仓位已平 **且** 标的币无可卖量(粉尘可忽略)后才拍 `after` 并冻结
|
||||
- **不**作为复盘「期权交易记录」列表盈亏展示
|
||||
|
||||
### 2.2 口径 B — 期权数据统计(历史 × 卖回价)
|
||||
### 2.2 口径 B — 数据统计 / 复盘交易记录(历史 × 卖回价)
|
||||
|
||||
```
|
||||
统计已实现盈亏(U) = 期权历史.realized_pnl(币) × 平仓卖回价
|
||||
展示已实现盈亏(U) = 期权历史.realized_pnl(币) × 平仓卖回价
|
||||
```
|
||||
|
||||
- **平仓卖回价**:优先卖回成交均价 / 冻结的 `close_index_px`;缺失时再用当时指数一次性冻结
|
||||
- **消费者**:期权面板「数据统计」合计/胜率/盈亏比、数据统计页 KPI、顶栏期权窗口统计
|
||||
- **不含**账户多买缓冲差分;与复盘 A **独立**,禁止混用
|
||||
- **消费者**:期权面板「数据统计」、顶栏窗口统计、**期权复盘「期权交易记录」盈亏列**(与数据统计同数)
|
||||
- **不含**账户多买缓冲差分
|
||||
|
||||
### 2.3 初始权利金与盈利率
|
||||
### 2.3 初始权利金、盈利率、期权手续费
|
||||
|
||||
| 字段 | 定义 |
|
||||
|------|------|
|
||||
| 初始权利金(U) | `open_index_px × premium_paid`(`premium_paid` 为开仓权利金币量;**不含** `budget_usdt` 多买) |
|
||||
| 盈利率(复盘) | `realized_pnl_usdt(A) / initial_premium_usdt` |
|
||||
| 盈利率(统计) | `统计盈亏(B) / initial_premium_usdt`(展示层可算,不强制落库) |
|
||||
| 初始权利金(U) | `open_index_px × premium_paid`(不含 `budget_usdt` 多买) |
|
||||
| 盈利率 | `口径B盈亏 / initial_premium_usdt` |
|
||||
| 期权手续费(U) | 仅期权开/平手续费:`open_fee×开仓指数 + close_fee×卖回价`(**不含**现货买币/卖回手续费) |
|
||||
|
||||
复盘列表与复盘详情须展示:初始权利金(U)、盈利率。
|
||||
复盘「期权交易记录」须展示:初始权利金(U)、盈亏(B)、盈利率、期权手续费(U)。
|
||||
|
||||
---
|
||||
|
||||
@@ -261,3 +262,4 @@ IDLE
|
||||
|------|------|
|
||||
| 2026-10-09 | 初版:交易账户快照差分 + 一次一仓 + 持仓禁划转 + 卖回后冻结 |
|
||||
| 2026-10-13 | 修订:双口径 A/B;卖光再结算;初始权利金/盈利率;状态机;历史修复脚本 |
|
||||
| 2026-10-13 | 复盘「期权交易记录」盈亏与数据统计同口径 B;增加仅期权手续费展示 |
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
return n.toFixed(2) + "U";
|
||||
}
|
||||
|
||||
function fmtOptionFee(v) {
|
||||
if (v == null || v === "") return "—";
|
||||
var n = Number(v);
|
||||
if (Number.isNaN(n)) return "—";
|
||||
return n.toFixed(2) + "U";
|
||||
}
|
||||
|
||||
function fmtProfitRate(v) {
|
||||
if (v == null || v === "") return "—";
|
||||
var n = Number(v);
|
||||
@@ -398,7 +405,7 @@
|
||||
if (!tbody) return;
|
||||
var wrap = beginListLoad("or-trades-wrap", soft);
|
||||
if (!soft) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="muted">加载中…</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="10" class="muted">加载中…</td></tr>';
|
||||
}
|
||||
var p = baseQs();
|
||||
// 交易记录保留已复盘条目,不再只显示待复盘
|
||||
@@ -412,7 +419,7 @@
|
||||
.then(function (data) {
|
||||
if (doSync) setSyncStatus("本地记录已加载");
|
||||
if (!data.ok) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="muted">加载失败</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="10" class="muted">加载失败</td></tr>';
|
||||
endListLoad(wrap);
|
||||
return;
|
||||
}
|
||||
@@ -424,7 +431,7 @@
|
||||
tradesCache = {};
|
||||
if (!rows.length) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="9" class="muted">暂无交易记录</td></tr>';
|
||||
'<tr><td colspan="10" class="muted">暂无交易记录</td></tr>';
|
||||
endListLoad(wrap);
|
||||
return;
|
||||
}
|
||||
@@ -471,6 +478,9 @@
|
||||
'">' +
|
||||
fmtProfitRate(pr) +
|
||||
"</td>" +
|
||||
"<td>" +
|
||||
fmtOptionFee(t.option_fee_usdt) +
|
||||
"</td>" +
|
||||
'<td class="muted" style="font-size:12px;white-space:nowrap">' +
|
||||
escapeHtml(t.opened_at || "—") +
|
||||
"</td>" +
|
||||
@@ -508,7 +518,7 @@
|
||||
})
|
||||
.catch(function () {
|
||||
if (doSync) setSyncStatus("加载失败");
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="muted">加载失败</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="10" class="muted">加载失败</td></tr>';
|
||||
endListLoad(wrap);
|
||||
});
|
||||
}
|
||||
@@ -885,6 +895,7 @@
|
||||
["初始权利金", fmtPremiumU(t.initial_premium_usdt)],
|
||||
["盈亏", fmtPnl(t.realized_pnl_total)],
|
||||
["盈利率", fmtProfitRate(resolveProfitRate(t))],
|
||||
["期权手续费", fmtOptionFee(t.option_fee_usdt)],
|
||||
["持有", fmtHold(t.hold_seconds)],
|
||||
["开仓时间", t.opened_at || "—"],
|
||||
["平仓时间", t.closed_at || "—"],
|
||||
@@ -1226,6 +1237,7 @@
|
||||
($("or-f-pnl") || {}).value = fmtPnl(t.realized_pnl_total);
|
||||
($("or-f-initial-prem") || {}).value = fmtPremiumU(t.initial_premium_usdt);
|
||||
($("or-f-profit-rate") || {}).value = fmtProfitRate(resolveProfitRate(t));
|
||||
($("or-f-option-fee") || {}).value = fmtOptionFee(t.option_fee_usdt);
|
||||
($("or-f-hold") || {}).value = fmtHold(t.hold_seconds);
|
||||
setSelectValue($("or-f-strategy"), e.strategy_tag || "");
|
||||
setSelectValue($("or-f-direction"), e.direction_view || autoDirection(t));
|
||||
|
||||
@@ -133,6 +133,8 @@ def init_options_review_tables(conn: sqlite3.Connection) -> None:
|
||||
_ensure_column(conn, "options_review_trades", "profit_rr", "REAL")
|
||||
_ensure_column(conn, "options_review_trades", "initial_premium_usdt", "REAL")
|
||||
_ensure_column(conn, "options_review_trades", "profit_rate", "REAL")
|
||||
# 仅期权开/平手续费折 U(不含现货买币/卖回)
|
||||
_ensure_column(conn, "options_review_trades", "option_fee_usdt", "REAL")
|
||||
_ensure_column(conn, "options_review_entries", "prev_entry_tf", "TEXT")
|
||||
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ def upsert_option_history_row(conn: sqlite3.Connection, row: dict[str, Any]) ->
|
||||
"realized_pnl": pnl,
|
||||
"initial_premium_usdt": _safe_float(row.get("initial_premium_usdt")),
|
||||
"profit_rate": _safe_float(row.get("profit_rate")),
|
||||
"option_fee_usdt": _safe_float(row.get("option_fee_usdt")),
|
||||
}
|
||||
cols = list(fields.keys())
|
||||
if existing:
|
||||
@@ -319,7 +320,8 @@ def sync_options_from_local_trades(
|
||||
open_quote, close_quote, premium_paid, premium_received, realized_pnl,
|
||||
realized_pnl_usdt, close_index_px, open_index_px, budget_usdt,
|
||||
premium_ccy, created_at, closed_at, signal_note, status,
|
||||
trading_usdt_before, pnl_settled, initial_premium_usdt
|
||||
trading_usdt_before, pnl_settled, initial_premium_usdt,
|
||||
open_fee, close_fee
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
ORDER BY id DESC
|
||||
@@ -411,16 +413,9 @@ def sync_options_from_local_trades(
|
||||
ccy = str(r["premium_ccy"] or "").strip().upper()
|
||||
if not ccy:
|
||||
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(inst), underly)
|
||||
frozen_usdt = _safe_float(r["realized_pnl_usdt"])
|
||||
try:
|
||||
settled = int(r["pnl_settled"] or 0) == 1
|
||||
except Exception:
|
||||
settled = False
|
||||
# 账户快照路径:未结算不写 U,避免指数假数进复盘
|
||||
acct_pending = _safe_float(r["trading_usdt_before"]) is not None and not settled
|
||||
close_idx = _safe_float(r["close_index_px"])
|
||||
open_idx = _safe_float(r["open_index_px"])
|
||||
# 折算指数:平仓冻结 > 开仓 > 实时(仅无冻结时兜底)
|
||||
# 折算指数:平仓卖回价 > 开仓 > 实时(仅无冻结时兜底)
|
||||
idx = None
|
||||
if close_idx is not None and close_idx > 0:
|
||||
idx = close_idx
|
||||
@@ -435,22 +430,36 @@ def sync_options_from_local_trades(
|
||||
if paid0 is not None and recv0 is not None:
|
||||
pnl = recv0 - paid0
|
||||
prem = _safe_float(r["premium_paid"])
|
||||
if acct_pending:
|
||||
pnl_u = None
|
||||
elif frozen_usdt is not None and (settled or _safe_float(r["trading_usdt_before"]) is None):
|
||||
pnl_u = round(float(frozen_usdt), 4)
|
||||
else:
|
||||
pnl_u = _to_usdt(pnl, ccy=ccy, idx=idx)
|
||||
# 期权交易记录盈亏与数据统计同口径 B:币盈亏 × 平仓卖回价(不用账户差分 A)
|
||||
pnl_u = _to_usdt(pnl, ccy=ccy, idx=idx)
|
||||
# 初始权利金(U)=开仓指数×权利金币量;不含多买
|
||||
stored_initial = _safe_float(r["initial_premium_usdt"])
|
||||
open_idx_for_prem = open_idx if (open_idx is not None and open_idx > 0) else idx
|
||||
prem_u = stored_initial
|
||||
if prem_u is None:
|
||||
prem_u = _to_usdt(prem, ccy=ccy, idx=open_idx_for_prem)
|
||||
if ccy in ("ETH", "BTC") and pnl_u is None and not acct_pending:
|
||||
pnl_u = pnl
|
||||
if ccy in ("ETH", "BTC") and pnl_u is None:
|
||||
pnl_u = None
|
||||
if ccy in ("ETH", "BTC") and prem_u is None:
|
||||
prem_u = prem
|
||||
# 仅期权开/平手续费折 U(不含现货)
|
||||
ofee = abs(_safe_float(r["open_fee"]) or 0.0)
|
||||
cfee = abs(_safe_float(r["close_fee"]) or 0.0)
|
||||
fee_u = None
|
||||
if ccy in ("ETH", "BTC"):
|
||||
fee_parts = 0.0
|
||||
has_fee = False
|
||||
if ofee > 0 and open_idx_for_prem is not None and open_idx_for_prem > 0:
|
||||
fee_parts += ofee * float(open_idx_for_prem)
|
||||
has_fee = True
|
||||
sell_idx = close_idx if (close_idx is not None and close_idx > 0) else idx
|
||||
if cfee > 0 and sell_idx is not None and sell_idx > 0:
|
||||
fee_parts += cfee * float(sell_idx)
|
||||
has_fee = True
|
||||
if has_fee:
|
||||
fee_u = round(fee_parts, 4)
|
||||
elif ofee > 0 or cfee > 0:
|
||||
fee_u = round(ofee + cfee, 4)
|
||||
profit_rate = None
|
||||
if pnl_u is not None and prem_u is not None and abs(float(prem_u)) > 1e-12:
|
||||
profit_rate = round(float(pnl_u) / float(prem_u), 6)
|
||||
@@ -470,12 +479,13 @@ def sync_options_from_local_trades(
|
||||
"open_avg_px": r["open_quote"],
|
||||
"close_avg_px": r["close_quote"],
|
||||
"premium_paid": prem_u if prem_u is not None else prem,
|
||||
"realized_pnl": pnl_u if pnl_u is not None else (None if acct_pending else pnl),
|
||||
"realized_pnl": pnl_u,
|
||||
"initial_premium_usdt": prem_u,
|
||||
"profit_rate": profit_rate,
|
||||
"option_fee_usdt": fee_u,
|
||||
"created_at": opened_at,
|
||||
"closed_at": closed_at,
|
||||
"status_label": "待结算" if acct_pending else "已平",
|
||||
"status_label": "已平",
|
||||
},
|
||||
)
|
||||
if action == "inserted":
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
.or-list-loading{opacity:.55;pointer-events:none;transition:opacity .12s ease}
|
||||
.or-trades-table-wrap,.or-reviewed-table-wrap{min-height:9.5rem;overflow-x:auto}
|
||||
.or-reviewed-table{min-width:1180px}
|
||||
.or-trades-table{min-width:960px}
|
||||
.or-trades-table{min-width:1080px}
|
||||
.or-kpi-row{
|
||||
display:grid;grid-template-columns:repeat(6,minmax(0,1fr));
|
||||
gap:8px;margin-bottom:12px;
|
||||
@@ -241,6 +241,7 @@
|
||||
<th>初始权利金</th>
|
||||
<th>盈亏</th>
|
||||
<th>盈利率</th>
|
||||
<th>手续费</th>
|
||||
<th>开仓时间</th>
|
||||
<th>平仓时间</th>
|
||||
<th>持有</th>
|
||||
@@ -248,7 +249,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="or-trades-tbody">
|
||||
<tr><td colspan="9" class="muted">加载中…</td></tr>
|
||||
<tr><td colspan="10" class="muted">加载中…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -285,6 +286,7 @@
|
||||
autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other">
|
||||
<input type="text" id="or-f-initial-prem" name="or_f_initial_prem" placeholder="初始权利金(U)" readonly autocomplete="off">
|
||||
<input type="text" id="or-f-profit-rate" name="or_f_profit_rate" placeholder="盈利率" readonly autocomplete="off">
|
||||
<input type="text" id="or-f-option-fee" name="or_f_option_fee" placeholder="期权手续费(U)" readonly autocomplete="off" title="仅期权开/平手续费,不含现货">
|
||||
<input type="text" id="or-f-hold" name="or_f_hold" placeholder="持有时长" readonly autocomplete="off">
|
||||
</div>
|
||||
<div class="or-form-grid2">
|
||||
|
||||
Reference in New Issue
Block a user