92c222584e
Co-authored-by: Cursor <cursoragent@cursor.com>
559 lines
34 KiB
HTML
559 lines
34 KiB
HTML
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
|
||
{% extends "base.html" %}
|
||
{% block title %}交易记录与复盘 - 国内期货 · 交易复盘系统{% endblock %}
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/records.css') }}?v={{ asset_v }}">
|
||
{% endblock %}
|
||
{% block content %}
|
||
{% macro trade_detail_json(t) -%}
|
||
{{ {
|
||
"symbol": t.symbol_name or t.symbol,
|
||
"symbol_code": t.symbol,
|
||
"monitor_type": t.monitor_type,
|
||
"source": "柜台" if t.source == "ctp" else "本地",
|
||
"direction": "做多" if t.direction == "long" else "做空",
|
||
"entry_price": t.entry_price,
|
||
"close_price": t.close_price,
|
||
"stop_loss": t.stop_loss,
|
||
"take_profit": t.take_profit,
|
||
"lots": t.lots,
|
||
"margin": t.margin,
|
||
"margin_pct": t.margin_pct,
|
||
"holding_minutes": t.holding_minutes or 0,
|
||
"open_time": t.open_time,
|
||
"close_time": t.close_time,
|
||
"pnl": t.pnl,
|
||
"fee": t.fee,
|
||
"pnl_net": t.pnl_net,
|
||
"equity_after": t.equity_after,
|
||
"result": t.result,
|
||
"verified": t.verified,
|
||
"fill_review_url": url_for("fill_review_from_trade", tid=t.id),
|
||
"del_url": url_for("del_trade", tid=t.id)
|
||
} | tojson }}
|
||
{%- endmacro %}
|
||
{% macro trade_symbol_cell(t) %}
|
||
<div class="dash-symbol-cell">
|
||
<div class="dash-symbol-title">
|
||
{{ t.symbol_name or t.symbol }}
|
||
{% if t.symbol_exchange %}<span class="dash-symbol-ex text-muted">{{ t.symbol_exchange }}</span>{% endif %}
|
||
{% if t.symbol_is_main %}<span class="badge planned dash-main-badge">主力</span>{% endif %}
|
||
{% if t.symbol and (t.symbol_name or '')|lower != (t.symbol or '')|lower %}
|
||
<span class="text-accent">{{ t.symbol }}</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endmacro %}
|
||
{% macro trade_pnl_cell(v) %}
|
||
{% if v is not none %}{% set n = v|float %}<span class="{% if n > 0 %}pnl-pos{% elif n < 0 %}pnl-neg{% endif %}">{{ ('+' if n > 0 else '') ~ ('%.2f'|format(n)) }} 元</span>{% else %}—{% endif %}
|
||
{% endmacro %}
|
||
{% macro trade_verify_form(t) %}
|
||
<form method="post" action="{{ url_for('update_trade', tid=t.id) }}" class="records-trade-verify-form">
|
||
<input type="hidden" name="symbol_name" value="{{ t.symbol_name or t.symbol }}">
|
||
<input type="hidden" name="monitor_type" value="{{ t.monitor_type }}">
|
||
<input type="hidden" name="direction" value="{{ t.direction }}">
|
||
<input type="hidden" name="entry_price" value="{{ t.entry_price }}">
|
||
<input type="hidden" name="stop_loss" value="{{ t.stop_loss }}">
|
||
<input type="hidden" name="take_profit" value="{{ t.take_profit }}">
|
||
<input type="hidden" name="close_price" value="{{ t.close_price or '' }}">
|
||
<input type="hidden" name="lots" value="{{ t.lots }}">
|
||
<input type="hidden" name="margin" value="{{ t.margin or '' }}">
|
||
<input type="hidden" name="holding_minutes" value="{{ t.holding_minutes or 0 }}">
|
||
<input type="hidden" name="open_time" value="{{ t.open_time or '' }}">
|
||
<input type="hidden" name="close_time" value="{{ t.close_time or '' }}">
|
||
<input type="hidden" name="pnl" value="{{ t.pnl or '' }}">
|
||
<input type="hidden" name="result" value="{{ t.result }}">
|
||
<button type="submit" class="btn-verify" {% if t.verified %}disabled{% endif %}>核对修改</button>
|
||
</form>
|
||
{% endmacro %}
|
||
{% macro trade_row_actions(t, detail_class) %}
|
||
<div class="trade-actions records-trade-row-actions">
|
||
<button type="button" class="btn-link {{ detail_class }}">详情</button>
|
||
<a href="{{ url_for('fill_review_from_trade', tid=t.id) }}" class="btn-fill">填入复盘</a>
|
||
{{ trade_verify_form(t) }}
|
||
<a href="{{ url_for('del_trade', tid=t.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
||
</div>
|
||
{% endmacro %}
|
||
<div class="records-page">
|
||
<div class="card records-equity-card" style="margin-bottom:1.25rem">
|
||
<h2>资金曲线</h2>
|
||
<div class="card-body">
|
||
<div id="equity-curve-chart" style="width:100%;min-height:220px;overflow:hidden"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card records-trade-card" style="margin-bottom:1.25rem">
|
||
<h2>交易记录</h2>
|
||
<div class="card-body">
|
||
{% if ctp_sync_info and ctp_sync_info.connected %}
|
||
<p class="hint" style="margin-top:0">
|
||
已连接 CTP,本页已自动同步柜台成交(新增 {{ ctp_sync_info.synced or 0 }} 条,更新 {{ ctp_sync_info.updated or 0 }} 条)。
|
||
带来源「柜台」的记录以交易所成交为准;「本地」为程序写入,可手动删除错误项。
|
||
</p>
|
||
{% else %}
|
||
<p class="hint" style="margin-top:0">CTP 未连接时仅显示本地数据库记录;连接后打开本页会自动同步柜台成交。</p>
|
||
{% endif %}
|
||
<label class="trade-switch-label records-desktop-only">
|
||
<input type="checkbox" id="trade-edit-switch">
|
||
<span>修改/核对开关(开启后可编辑关键字段)</span>
|
||
</label>
|
||
<div class="records-mobile-list records-phone-only" id="records-trade-mobile">
|
||
{% for t in trades %}
|
||
{% set trade_pnl = t.pnl_net if t.pnl_net is not none else t.pnl %}
|
||
{% set trade_px = t.close_price if t.close_price else t.entry_price %}
|
||
{% set pnl_cls = 'is-profit' if trade_pnl and trade_pnl > 0 else ('is-loss' if trade_pnl and trade_pnl < 0 else 'is-flat') %}
|
||
<div class="records-trade-row" data-trade-id="{{ t.id }}" data-trade='{{ trade_detail_json(t)|trim }}'>
|
||
<div class="records-trade-main">
|
||
<div class="records-trade-head">
|
||
<span class="records-trade-title">
|
||
<span class="records-mobile-symbol">{{ t.symbol_name or t.symbol }}</span>
|
||
{% if t.symbol and (t.symbol_name or '')|lower != (t.symbol or '')|lower %}
|
||
<span class="text-accent records-trade-code">{{ t.symbol }}</span>
|
||
{% endif %}
|
||
<span class="badge dir">{{ '做多' if t.direction == 'long' else '做空' }}</span>
|
||
{% if t.verified %}<span class="badge active records-verified-badge">已核对</span>{% endif %}
|
||
</span>
|
||
</div>
|
||
<div class="records-trade-summary">
|
||
{{ (t.lots or 0)|int }}手 平仓 {{ trade_px if trade_px is not none else '-' }}
|
||
盈亏 <span class="records-mobile-pnl {{ pnl_cls }}">{{ trade_pnl if trade_pnl is not none else '-' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="records-trade-phone-foot">
|
||
<span class="records-mobile-chevron">详情 ›</span>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<p class="records-mobile-empty">暂无交易记录</p>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="card-scroll records-trade-table-wrap records-tablet-only">
|
||
<table class="dashboard-table records-trade-table">
|
||
<thead>
|
||
<tr>
|
||
<th>品种</th>
|
||
<th>方向</th>
|
||
<th>手数</th>
|
||
<th>开仓</th>
|
||
<th>平仓</th>
|
||
<th>盈亏</th>
|
||
<th>净盈亏</th>
|
||
<th>平仓时间</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for t in trades %}
|
||
<tr data-trade-id="{{ t.id }}" data-trade='{{ trade_detail_json(t)|trim }}'>
|
||
<td>{{ trade_symbol_cell(t) }}</td>
|
||
<td>
|
||
<span class="badge dir {% if t.direction == 'long' %}dir-long{% else %}dir-short{% endif %}">
|
||
{{ '做多' if t.direction == 'long' else '做空' }}
|
||
</span>
|
||
</td>
|
||
<td>{% if t.lots is not none %}{{ '%.2f'|format(t.lots|float) }}{% else %}—{% endif %}</td>
|
||
<td>{% if t.entry_price is not none %}{{ '%.2f'|format(t.entry_price|float) }}{% else %}—{% endif %}</td>
|
||
<td>{% if t.close_price is not none %}{{ '%.2f'|format(t.close_price|float) }}{% elif t.entry_price is not none %}{{ '%.2f'|format(t.entry_price|float) }}{% else %}—{% endif %}</td>
|
||
<td>{{ trade_pnl_cell(t.pnl) }}</td>
|
||
<td>{{ trade_pnl_cell(t.pnl_net) }}</td>
|
||
<td>{{ (t.close_time or '')[:16].replace('T', ' ') or '—' }}</td>
|
||
<td>{{ trade_row_actions(t, 'records-tablet-detail-btn') }}</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="9" class="text-muted">暂无交易记录</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="trade-table-wrap records-desktop-only">
|
||
<table class="trade-table">
|
||
<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>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for t in trades %}
|
||
<tr data-trade-id="{{ t.id }}">
|
||
<td><span class="cell-readonly">{{ t.symbol_name or t.symbol }}</span></td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.monitor_type }}</span>
|
||
{% if t.source == 'ctp' %}
|
||
<span class="badge" style="margin-left:.25rem;font-size:.65rem">柜台</span>
|
||
{% else %}
|
||
<span class="text-muted" style="margin-left:.25rem;font-size:.65rem">本地</span>
|
||
{% endif %}
|
||
<input class="cell-edit-show" type="hidden" name="monitor_type" value="{{ t.monitor_type }}">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">
|
||
<span class="badge dir">{{ '做多' if t.direction == 'long' else '做空' }}</span>
|
||
</span>
|
||
<select class="cell-edit-show" name="direction" style="display:none">
|
||
<option value="long" {% if t.direction=='long' %}selected{% endif %}>做多</option>
|
||
<option value="short" {% if t.direction=='short' %}selected{% endif %}>做空</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.entry_price }}</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.stop_loss }}</span>
|
||
<input class="cell-edit-show" type="number" step="0.0001" name="stop_loss" value="{{ t.stop_loss }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.take_profit }}</span>
|
||
<input class="cell-edit-show" type="number" step="0.0001" name="take_profit" value="{{ t.take_profit }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.lots }}</span>
|
||
<input class="cell-edit-show" type="number" step="0.01" name="lots" value="{{ t.lots }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.margin if t.margin is not none else '-' }}</span>
|
||
<input class="cell-edit-show" type="number" step="0.01" name="margin" value="{{ t.margin or '' }}" placeholder="保证金" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">
|
||
{% if t.margin_pct is not none %}{{ t.margin_pct }}%{% else %}-{% endif %}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ t.holding_minutes or 0 }}</span>
|
||
<input class="cell-edit-show" type="number" name="holding_minutes" value="{{ t.holding_minutes or 0 }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ (t.open_time or '')[:16].replace('T',' ') }}</span>
|
||
<input class="cell-edit-show" type="text" name="open_time" value="{{ t.open_time or '' }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">{{ (t.close_time or '')[:16].replace('T',' ') }}</span>
|
||
<input class="cell-edit-show" type="text" name="close_time" value="{{ t.close_time or '' }}" style="display:none">
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide {% if t.pnl and t.pnl > 0 %}text-profit{% elif t.pnl and t.pnl < 0 %}text-loss{% endif %}">
|
||
{{ 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>
|
||
<td>
|
||
<span class="cell-readonly {% if t.pnl_net and t.pnl_net > 0 %}text-profit{% elif t.pnl_net and t.pnl_net < 0 %}text-loss{% endif %}">
|
||
{{ t.pnl_net if t.pnl_net is not none else '-' }}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly">{{ t.equity_after if t.equity_after is not none else '-' }}</span>
|
||
</td>
|
||
<td>
|
||
<span class="cell-readonly cell-edit-hide">
|
||
{% if t.result == '止盈' %}<span class="badge profit">{{ t.result }}</span>
|
||
{% elif t.result == '止损' %}<span class="badge loss">{{ t.result }}</span>
|
||
{% elif t.result == '移动止盈' %}<span class="badge profit">{{ t.result }}</span>
|
||
{% elif t.result == '保本止盈' %}<span class="badge profit">{{ t.result }}</span>
|
||
{% elif t.result == '手动平仓' %}<span class="badge result-manual">{{ t.result }}</span>
|
||
{% else %}<span class="badge result-external">{{ t.result }}</span>{% endif %}
|
||
{% if t.verified %}<span class="badge active" style="margin-left:.25rem">已核对</span>{% endif %}
|
||
</span>
|
||
<select class="cell-edit-show" name="result" style="display:none">
|
||
<option value="手动平仓" {% if t.result=='手动平仓' %}selected{% endif %}>手动平仓</option>
|
||
<option value="止盈" {% if t.result=='止盈' %}selected{% endif %}>止盈</option>
|
||
<option value="止损" {% if t.result=='止损' %}selected{% endif %}>止损</option>
|
||
<option value="移动止盈" {% if t.result=='移动止盈' %}selected{% endif %}>移动止盈</option>
|
||
<option value="保本止盈" {% if t.result=='保本止盈' %}selected{% endif %}>保本止盈</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<div class="trade-actions">
|
||
<a href="{{ url_for('fill_review_from_trade', tid=t.id) }}" class="btn-fill">填入复盘</a>
|
||
<button type="button" class="btn-verify trade-save-btn" disabled>核对修改</button>
|
||
<a href="{{ url_for('del_trade', tid=t.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="18" class="text-muted">暂无交易记录</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="split-grid records-split">
|
||
<div class="card" id="review-panel">
|
||
<h2>复盘上传</h2>
|
||
<div class="card-body">
|
||
<form id="review-form" action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data" class="form-compact form-compact-review line-tight">
|
||
<div class="form-line line-4">
|
||
<div class="symbol-wrap">
|
||
<input type="text" class="symbol-input" placeholder="品种" autocomplete="off" required>
|
||
<input type="hidden" name="symbol" required>
|
||
<input type="hidden" name="symbol_name">
|
||
<input type="hidden" name="market_code" required>
|
||
<input type="hidden" name="sina_code">
|
||
<div class="symbol-dropdown"></div>
|
||
<div class="symbol-selected"></div>
|
||
</div>
|
||
<select name="direction" required>
|
||
<option value="">方向</option>
|
||
<option value="long">做多</option>
|
||
<option value="short">做空</option>
|
||
</select>
|
||
<input name="lots" type="number" step="1" min="1" value="1" placeholder="张数" required>
|
||
<input name="timeframe" value="5m" placeholder="周期">
|
||
</div>
|
||
<div class="form-line line-4">
|
||
<input name="entry_price" type="number" step="0.0001" placeholder="成交价" required>
|
||
<input name="stop_loss" type="number" step="0.0001" placeholder="止损" required>
|
||
<input name="take_profit" type="number" step="0.0001" placeholder="止盈" required>
|
||
<input name="close_price" type="number" step="0.0001" placeholder="平仓价" required>
|
||
</div>
|
||
<div class="form-line line-4">
|
||
<div class="mini-field"><span>开仓时间</span><input type="datetime-local" name="open_time" required></div>
|
||
<div class="mini-field"><span>平仓时间</span><input type="datetime-local" name="close_time" required></div>
|
||
<input id="holding_duration" type="text" readonly class="calc-readonly" placeholder="持仓时长(自动)">
|
||
<input name="pnl" type="number" step="0.01" placeholder="盈亏金额(手动)">
|
||
</div>
|
||
<div class="form-line line-2">
|
||
<input id="initial_rr" type="text" readonly class="calc-readonly" placeholder="初始盈亏比(自动)">
|
||
<input id="actual_rr" type="text" readonly class="calc-readonly" placeholder="实际盈亏比(自动)">
|
||
</div>
|
||
<div class="form-line line-4">
|
||
<select name="open_type" required>
|
||
<option value="">开仓类型</option>
|
||
{% for t in open_types %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
|
||
</select>
|
||
<select name="exit_trigger" required>
|
||
<option value="">离场触发</option>
|
||
{% for t in exit_triggers %}<option value="{{ t }}">{{ t }}</option>{% endfor %}
|
||
</select>
|
||
<input name="exit_supplement" placeholder="离场补充">
|
||
<select name="watch_after_breakeven" title="保本后盯盘">
|
||
<option value="否">保本盯盘:否</option>
|
||
<option value="是">保本盯盘:是</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-line line-4">
|
||
<select name="new_position_while_occupied" title="占用时新开仓">
|
||
<option value="否">占用新开:否</option>
|
||
<option value="是">占用新开:是</option>
|
||
</select>
|
||
<input type="file" name="screenshot" accept="image/*" title="截图上传">
|
||
<textarea name="notes" placeholder="备注" rows="1"></textarea>
|
||
<button type="submit" class="btn-primary">保存</button>
|
||
</div>
|
||
<div class="kline-row">
|
||
<label><input type="checkbox" name="auto_kline" value="1" checked> 自动K线</label>
|
||
<select name="kline_period1" title="周期1">{% for p in kline_periods %}<option value="{{ p }}" {% if p=='15m' %}selected{% endif %}>{{ p }}</option>{% endfor %}</select>
|
||
<select name="kline_period2" title="周期2">{% for p in kline_periods %}<option value="{{ p }}" {% if p=='1h' %}selected{% endif %}>{{ p }}</option>{% endfor %}</select>
|
||
<input name="kline_count" type="number" value="300" placeholder="K线数" title="K线数">
|
||
<select name="kline_cutoff" title="K线截止">{% for c in kline_cutoffs %}<option value="{{ c }}">{{ c }}</option>{% endfor %}</select>
|
||
</div>
|
||
<p class="section-hint">勾选行为标签即为情绪单</p>
|
||
<div class="tag-grid">
|
||
{% for tag in behavior_tags %}
|
||
<label><input type="checkbox" name="tag_{{ tag }}" value="1"> {{ tag }}</label>
|
||
{% endfor %}
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>复盘历史</h2>
|
||
<div class="card-body">
|
||
<div class="preset-tabs">
|
||
<a href="{{ url_for('records', preset='today') }}" class="{% if preset=='today' %}active{% endif %}">本日</a>
|
||
<a href="{{ url_for('records', preset='week') }}" class="{% if preset=='week' %}active{% endif %}">本周</a>
|
||
<a href="{{ url_for('records', preset='month') }}" class="{% if preset=='month' %}active{% endif %}">本月</a>
|
||
<a href="{{ url_for('records', preset='custom') }}" class="{% if preset=='custom' %}active{% endif %}">自定义</a>
|
||
</div>
|
||
{% if preset == 'custom' or start or end %}
|
||
<form method="get" class="filter-row">
|
||
<input type="hidden" name="preset" value="custom">
|
||
<div class="field"><label>开始</label><input type="date" name="start" value="{{ start }}"></div>
|
||
<div class="field"><label>结束</label><input type="date" name="end" value="{{ end }}"></div>
|
||
<button type="submit" class="btn-primary">筛选</button>
|
||
</form>
|
||
{% endif %}
|
||
<div class="records-mobile-list records-review-mobile">
|
||
{% for r in reviews %}
|
||
<button type="button" class="records-mobile-item review-view-btn{% if r.is_emotion %} is-emotion{% endif %}" data-review='{{ {
|
||
"symbol": r.symbol_name or r.symbol, "symbol_code": r.symbol,
|
||
"direction": "做多" if r.direction=="long" else "做空",
|
||
"lots": r.lots, "timeframe": r.timeframe,
|
||
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
|
||
"close_price": r.close_price,
|
||
"open_time": r.open_time, "close_time": r.close_time,
|
||
"holding_duration": r.holding_duration,
|
||
"initial_pnl": r.initial_pnl, "actual_pnl": r.actual_pnl, "pnl": r.pnl,
|
||
"fee": r.fee, "pnl_net": r.pnl_net,
|
||
"open_type": r.open_type,
|
||
"exit_trigger": r.exit_trigger, "exit_supplement": r.exit_supplement,
|
||
"watch_after_breakeven": r.watch_after_breakeven,
|
||
"new_position_while_occupied": r.new_position_while_occupied,
|
||
"is_emotion": r.is_emotion, "behavior_tags": r.behavior_tags,
|
||
"notes": r.notes, "screenshot": r.screenshot
|
||
} | tojson }}'>
|
||
<div class="records-mobile-item-head">
|
||
<span class="records-mobile-symbol">{{ r.symbol_name or r.symbol }}</span>
|
||
<span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span>
|
||
</div>
|
||
<div class="records-mobile-item-meta">
|
||
<span>{{ r.close_time[:16].replace('T', ' ') if r.close_time else '' }}</span>
|
||
{% if r.is_emotion %}<span class="badge emotion">情绪单</span>{% endif %}
|
||
</div>
|
||
<div class="records-mobile-item-foot">
|
||
<span class="records-mobile-pnl {{ 'is-profit' if r.pnl_net and r.pnl_net > 0 else ('is-loss' if r.pnl_net and r.pnl_net < 0 else 'is-flat') }}">
|
||
<span class="records-mobile-pnl-lbl">净盈亏 </span>{{ r.pnl_net if r.pnl_net is not none else '-' }}
|
||
</span>
|
||
<span class="records-mobile-chevron">详情 ›</span>
|
||
</div>
|
||
</button>
|
||
{% else %}
|
||
<p class="records-mobile-empty">暂无复盘记录</p>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="card-scroll records-desktop-only">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>平仓</th><th>品种</th><th>方向</th><th>盈亏</th><th>手续费</th><th>净盈亏</th><th>情绪单</th><th>详情</th><th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for r in reviews %}
|
||
<tr class="{% if r.is_emotion %}row-emotion{% endif %}">
|
||
<td>{{ r.close_time[:16] if r.close_time else '' }}</td>
|
||
<td>{{ r.symbol_name or r.symbol }}</td>
|
||
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
||
<td>
|
||
{% if r.pnl and r.pnl > 0 %}<span class="badge profit">{{ r.pnl }}</span>
|
||
{% elif r.pnl and r.pnl < 0 %}<span class="badge loss">{{ r.pnl }}</span>
|
||
{% else %}{{ r.actual_pnl or '-' }}{% endif %}
|
||
</td>
|
||
<td class="text-muted">{{ r.fee if r.fee is not none else '-' }}</td>
|
||
<td>
|
||
{% if r.pnl_net and r.pnl_net > 0 %}<span class="badge profit">{{ r.pnl_net }}</span>
|
||
{% elif r.pnl_net and r.pnl_net < 0 %}<span class="badge loss">{{ r.pnl_net }}</span>
|
||
{% else %}-{% endif %}
|
||
</td>
|
||
<td>{% if r.is_emotion %}<span class="badge emotion">情绪单</span>{% else %}-{% endif %}</td>
|
||
<td>
|
||
<button type="button" class="btn-link review-view-btn" data-review='{{ {
|
||
"symbol": r.symbol_name or r.symbol, "symbol_code": r.symbol,
|
||
"direction": "做多" if r.direction=="long" else "做空",
|
||
"lots": r.lots, "timeframe": r.timeframe,
|
||
"entry_price": r.entry_price, "stop_loss": r.stop_loss, "take_profit": r.take_profit,
|
||
"close_price": r.close_price,
|
||
"open_time": r.open_time, "close_time": r.close_time,
|
||
"holding_duration": r.holding_duration,
|
||
"initial_pnl": r.initial_pnl, "actual_pnl": r.actual_pnl, "pnl": r.pnl,
|
||
"fee": r.fee, "pnl_net": r.pnl_net,
|
||
"open_type": r.open_type,
|
||
"exit_trigger": r.exit_trigger, "exit_supplement": r.exit_supplement,
|
||
"watch_after_breakeven": r.watch_after_breakeven,
|
||
"new_position_while_occupied": r.new_position_while_occupied,
|
||
"is_emotion": r.is_emotion, "behavior_tags": r.behavior_tags,
|
||
"notes": r.notes, "screenshot": r.screenshot
|
||
} | tojson }}'>放大查看</button>
|
||
</td>
|
||
<td><a href="{{ url_for('del_review', rid=r.id) }}" class="btn-del" onclick="return confirm('删除?')">删</a></td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="9" class="text-muted">暂无复盘记录</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="review-modal" class="modal-mask">
|
||
<div class="modal-box review-modal-fullscreen">
|
||
<span class="modal-close">✕</span>
|
||
<h3>复盘详情</h3>
|
||
<div id="review-modal-body" class="review-modal-body"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="trade-detail-modal" class="modal-mask">
|
||
<div class="modal-box review-modal-fullscreen">
|
||
<span class="modal-close">✕</span>
|
||
<h3>交易详情</h3>
|
||
<div id="trade-detail-modal-body" class="review-modal-body"></div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if auto_records %}
|
||
<div class="card" style="margin-top:1rem">
|
||
<h2>系统自动记录(止盈/止损)</h2>
|
||
<table>
|
||
<thead><tr><th>品种</th><th>类型</th><th>方向</th><th>触发价</th><th>结果</th><th>时间</th></tr></thead>
|
||
<tbody>
|
||
{% for r in auto_records %}
|
||
<tr>
|
||
<td>{{ r.symbol_name or r.symbol }}</td>
|
||
<td>{{ r.monitor_type }}</td>
|
||
<td><span class="badge dir">{{ '多' if r.direction == 'long' else '空' }}</span></td>
|
||
<td>{{ r.trigger_price }}</td>
|
||
<td>{% if r.result == '止盈' %}<span class="badge profit">止盈</span>{% else %}<span class="badge loss">止损</span>{% endif %}</td>
|
||
<td>{{ r.created_at[:16] if r.created_at else '' }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|
||
{% block extra_js %}
|
||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||
<script>window.__EQUITY_CURVE__ = {{ equity_curve | default([]) | tojson }};</script>
|
||
<script src="{{ url_for('static', filename='js/equity_curve.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/review.js') }}?v={{ asset_v }}"></script>
|
||
<script src="{{ url_for('static', filename='js/records.js') }}?v={{ asset_v }}"></script>
|
||
<script src="{{ url_for('static', filename='js/trades.js') }}?v={{ asset_v }}"></script>
|
||
{% if prefill %}
|
||
<script>
|
||
function bootReviewPrefill() {
|
||
var form = document.getElementById('review-form');
|
||
var panel = document.getElementById('review-panel');
|
||
if (!form) return;
|
||
var map = {{ prefill | tojson }};
|
||
Object.keys(map).forEach(function (k) {
|
||
var el = form.querySelector('[name="' + k + '"]');
|
||
if (el && map[k] != null && map[k] !== '') el.value = map[k];
|
||
});
|
||
var symInput = form.querySelector('.symbol-input');
|
||
if (symInput && map.symbol_name) symInput.value = map.symbol_name;
|
||
if (typeof recalc === 'function') recalc();
|
||
if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootReviewPrefill);
|
||
else document.addEventListener('DOMContentLoaded', bootReviewPrefill);
|
||
</script>
|
||
{% endif %}
|
||
<script>
|
||
(function () {
|
||
function scrollReviewPanel() {
|
||
if (window.location.hash === '#review-panel') {
|
||
var panel = document.getElementById('review-panel');
|
||
if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
}
|
||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(scrollReviewPanel);
|
||
else document.addEventListener('DOMContentLoaded', scrollReviewPanel);
|
||
})();
|
||
</script>
|
||
{% endblock %}
|