Add close price column to trade records for overnight position PnL review.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1280,34 +1280,83 @@ def trades():
|
||||
def update_trade(tid):
|
||||
d = request.form
|
||||
conn = get_db()
|
||||
row = conn.execute("SELECT * FROM trade_logs WHERE id=?", (tid,)).fetchone()
|
||||
if not row:
|
||||
conn.close()
|
||||
flash("记录不存在")
|
||||
return redirect(url_for("records"))
|
||||
row = dict(row)
|
||||
entry = float(d.get("entry_price") or 0)
|
||||
close_px = float(d.get("close_price") or 0)
|
||||
lots = float(d.get("lots") or 0)
|
||||
sl_raw = d.get("stop_loss")
|
||||
tp_raw = d.get("take_profit")
|
||||
stop_loss = float(sl_raw) if sl_raw not in (None, "") else None
|
||||
take_profit = float(tp_raw) if tp_raw not in (None, "") else None
|
||||
open_time = (d.get("open_time") or row.get("open_time") or "").strip()
|
||||
close_time = (d.get("close_time") or row.get("close_time") or "").strip()
|
||||
direction = (d.get("direction") or row.get("direction") or "long").strip()
|
||||
|
||||
from trade_log_lib import recalc_trade_log_pnl, refresh_trade_log_equity_chain, _read_initial_capital
|
||||
from trading_context import get_trading_mode
|
||||
|
||||
pnl = float(row.get("pnl") or 0)
|
||||
fee = float(row.get("fee") or 0)
|
||||
pnl_net = float(row.get("pnl_net") or 0)
|
||||
old_entry = float(row.get("entry_price") or 0)
|
||||
old_close = float(row.get("close_price") or 0)
|
||||
old_lots = float(row.get("lots") or 0)
|
||||
prices_changed = (
|
||||
abs(entry - old_entry) > 0.0001
|
||||
or abs(close_px - old_close) > 0.0001
|
||||
or abs(lots - old_lots) > 0.0001
|
||||
)
|
||||
if prices_changed and close_px > 0 and entry > 0 and lots > 0:
|
||||
calc = recalc_trade_log_pnl(
|
||||
symbol=row.get("symbol") or "",
|
||||
direction=direction,
|
||||
entry_price=entry,
|
||||
close_price=close_px,
|
||||
lots=lots,
|
||||
stop_loss=stop_loss,
|
||||
take_profit=take_profit,
|
||||
open_time=open_time,
|
||||
close_time=close_time,
|
||||
trading_mode=get_trading_mode(get_setting),
|
||||
)
|
||||
pnl = calc["pnl"]
|
||||
fee = calc["fee"]
|
||||
pnl_net = calc["pnl_net"]
|
||||
|
||||
conn.execute(
|
||||
"""UPDATE trade_logs SET
|
||||
symbol_name=?, monitor_type=?, direction=?,
|
||||
entry_price=?, stop_loss=?, take_profit=?, close_price=?,
|
||||
lots=?, margin=?, holding_minutes=?, open_time=?, close_time=?,
|
||||
pnl=?, result=?, verified=1
|
||||
pnl=?, fee=?, pnl_net=?, result=?, verified=1
|
||||
WHERE id=?""",
|
||||
(
|
||||
d.get("symbol_name", "").strip(),
|
||||
d.get("monitor_type", "").strip(),
|
||||
d.get("direction", "").strip(),
|
||||
float(d.get("entry_price") or 0),
|
||||
float(d.get("stop_loss") or 0),
|
||||
float(d.get("take_profit") or 0),
|
||||
float(d.get("close_price") or 0),
|
||||
float(d.get("lots") or 0),
|
||||
direction,
|
||||
entry,
|
||||
stop_loss,
|
||||
take_profit,
|
||||
close_px,
|
||||
lots,
|
||||
float(d.get("margin") or 0),
|
||||
int(d.get("holding_minutes") or 0),
|
||||
d.get("open_time", "").strip(),
|
||||
d.get("close_time", "").strip(),
|
||||
float(d.get("pnl") or 0),
|
||||
open_time,
|
||||
close_time,
|
||||
pnl,
|
||||
fee,
|
||||
pnl_net,
|
||||
d.get("result", "").strip(),
|
||||
tid,
|
||||
),
|
||||
)
|
||||
try:
|
||||
cap = float(get_setting("live_capital", "0") or 0)
|
||||
refresh_trade_log_equity_chain(conn, cap if cap > 0 else None)
|
||||
refresh_trade_log_equity_chain(conn, _read_initial_capital(conn))
|
||||
except Exception as exc:
|
||||
app.logger.debug("equity chain refresh after trade edit: %s", exc)
|
||||
conn.commit()
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{ label: '合约', value: esc(data.symbol_code), wide: false },
|
||||
{ label: '类型', value: esc(data.monitor_type) + ' · ' + esc(data.source), wide: false },
|
||||
{ label: '方向', value: esc(data.direction), wide: false },
|
||||
{ label: '成交价', value: esc(data.entry_price), wide: false },
|
||||
{ label: '开仓价', value: esc(data.entry_price), wide: false },
|
||||
{ label: '平仓价', value: esc(data.close_price), wide: false },
|
||||
{ label: '手数', value: esc(data.lots), wide: false },
|
||||
{ label: '止损', value: esc(data.stop_loss), wide: false },
|
||||
|
||||
+10
-4
@@ -93,6 +93,9 @@
|
||||
{% else %}
|
||||
<p class="hint" style="margin-top:0">CTP 未连接时仅显示本地数据库记录;连接后打开本页会自动同步柜台成交。</p>
|
||||
{% endif %}
|
||||
<p class="hint" style="margin-top:0;margin-bottom:.75rem">
|
||||
跨日持仓的盈亏以<strong>平仓价</strong>与柜台结算为准;表格中「开仓价」为程序记录,「平仓价」为成交回报,二者不一致时请以平仓价核对净盈亏。
|
||||
</p>
|
||||
<label class="trade-switch-label records-desktop-only">
|
||||
<input type="checkbox" id="trade-edit-switch">
|
||||
<span>修改/核对开关(开启后可编辑关键字段)</span>
|
||||
@@ -170,7 +173,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>品种</th><th>类型</th><th>方向</th>
|
||||
<th>成交</th><th>止损(开仓)</th><th>止盈</th>
|
||||
<th>开仓价</th><th>平仓价</th><th>止损(开仓)</th><th>止盈</th>
|
||||
<th>手数</th><th>保证金</th><th>保证金占比</th><th>持仓分钟</th>
|
||||
<th>开仓时间</th><th>平仓时间</th>
|
||||
<th>盈亏(元)</th><th>手续费</th><th>净盈亏</th><th>最新资金</th><th>结果</th><th>操作</th>
|
||||
@@ -199,9 +202,13 @@
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-readonly cell-edit-hide">{{ t.entry_price }}</span>
|
||||
<span class="cell-readonly cell-edit-hide">{{ t.entry_price if t.entry_price is not none else '-' }}</span>
|
||||
<input class="cell-edit-show" type="number" step="0.0001" name="entry_price" value="{{ t.entry_price }}" style="display:none">
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-readonly cell-edit-hide">{{ t.close_price if t.close_price is not none else '-' }}</span>
|
||||
<input class="cell-edit-show" type="number" step="0.0001" name="close_price" value="{{ t.close_price or '' }}" style="display:none">
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-readonly cell-edit-hide">{{ t.stop_loss }}</span>
|
||||
<input class="cell-edit-show" type="number" step="0.0001" name="stop_loss" value="{{ t.stop_loss }}" style="display:none">
|
||||
@@ -240,7 +247,6 @@
|
||||
{{ t.pnl if t.pnl is not none else '-' }}
|
||||
</span>
|
||||
<input class="cell-edit-show" type="number" step="0.01" name="pnl" value="{{ t.pnl or '' }}" style="display:none">
|
||||
<input type="hidden" name="close_price" value="{{ t.close_price or '' }}">
|
||||
<input type="hidden" name="symbol_name" value="{{ t.symbol_name or t.symbol }}">
|
||||
</td>
|
||||
<td><span class="cell-readonly text-muted">{{ t.fee if t.fee is not none else '-' }}</span></td>
|
||||
@@ -279,7 +285,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="18" class="text-muted">暂无交易记录</td></tr>
|
||||
<tr><td colspan="19" class="text-muted">暂无交易记录</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -32,6 +32,42 @@ def calc_equity_after(capital: float, pnl_net: float) -> float | None:
|
||||
return round(cap + float(pnl_net or 0), 2)
|
||||
|
||||
|
||||
def recalc_trade_log_pnl(
|
||||
*,
|
||||
symbol: str,
|
||||
direction: str,
|
||||
entry_price: float,
|
||||
close_price: float,
|
||||
lots: float,
|
||||
stop_loss: float | None = None,
|
||||
take_profit: float | None = None,
|
||||
open_time: str = "",
|
||||
close_time: str = "",
|
||||
trading_mode: str = "simulation",
|
||||
capital: float = 0.0,
|
||||
) -> dict[str, float]:
|
||||
"""按开/平仓价重算盈亏与手续费(跨日持仓可手动改价后核对)。"""
|
||||
from contract_specs import calc_position_metrics
|
||||
from fee_specs import calc_round_trip_fee
|
||||
|
||||
sym = (symbol or "").strip()
|
||||
direction = (direction or "long").strip().lower()
|
||||
entry = float(entry_price or close_price or 0)
|
||||
close_px = float(close_price or 0)
|
||||
lots_f = float(lots or 0)
|
||||
sl = float(stop_loss) if stop_loss is not None else entry
|
||||
tp = float(take_profit) if take_profit is not None else entry
|
||||
metrics = calc_position_metrics(
|
||||
direction, entry, sl, tp, lots_f, close_px, capital, sym,
|
||||
)
|
||||
pnl = round(float(metrics.get("float_pnl") or 0), 2)
|
||||
fee = calc_round_trip_fee(
|
||||
sym, entry, close_px, lots_f, open_time, close_time, trading_mode=trading_mode,
|
||||
)
|
||||
pnl_net = round(pnl - fee, 2)
|
||||
return {"pnl": pnl, "fee": round(fee, 2), "pnl_net": pnl_net}
|
||||
|
||||
|
||||
def _read_initial_capital(conn, initial_capital: float | None = None) -> float:
|
||||
if initial_capital is not None and initial_capital > 0:
|
||||
return float(initial_capital)
|
||||
|
||||
Reference in New Issue
Block a user