持仓监控独立导航页,交易记录与复盘合并为同一页
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -728,11 +728,19 @@ def keys():
|
|||||||
history = conn.execute(
|
history = conn.execute(
|
||||||
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
"SELECT * FROM key_monitors WHERE status='archived' ORDER BY archived_at DESC LIMIT 100"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
positions = conn.execute(
|
conn.close()
|
||||||
|
return render_template("keys.html", keys=key_list, history=history)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/positions")
|
||||||
|
@login_required
|
||||||
|
def positions():
|
||||||
|
conn = get_db()
|
||||||
|
pos_list = conn.execute(
|
||||||
"SELECT * FROM position_monitors WHERE status='active' ORDER BY id DESC"
|
"SELECT * FROM position_monitors WHERE status='active' ORDER BY id DESC"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return render_template("keys.html", keys=key_list, history=history, positions=positions)
|
return render_template("positions.html", positions=pos_list)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/add_key", methods=["POST"])
|
@app.route("/add_key", methods=["POST"])
|
||||||
@@ -773,7 +781,7 @@ def add_position():
|
|||||||
sina_code = d.get("sina_code", "").strip()
|
sina_code = d.get("sina_code", "").strip()
|
||||||
if not symbol or not market_code:
|
if not symbol or not market_code:
|
||||||
flash("请从下拉列表选择品种")
|
flash("请从下拉列表选择品种")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("positions"))
|
||||||
entry = float(d["entry_price"])
|
entry = float(d["entry_price"])
|
||||||
sl = float(d["stop_loss"])
|
sl = float(d["stop_loss"])
|
||||||
tp = float(d["take_profit"])
|
tp = float(d["take_profit"])
|
||||||
@@ -796,7 +804,7 @@ def add_position():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("持仓已添加")
|
flash("持仓已添加")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("positions"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/del_position/<int:pid>")
|
@app.route("/del_position/<int:pid>")
|
||||||
@@ -813,7 +821,7 @@ def close_position(pid):
|
|||||||
if not row:
|
if not row:
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("持仓不存在")
|
flash("持仓不存在")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("positions"))
|
||||||
sym = row["symbol"]
|
sym = row["symbol"]
|
||||||
market = row["market_code"] or ""
|
market = row["market_code"] or ""
|
||||||
sina = row["sina_code"] or ""
|
sina = row["sina_code"] or ""
|
||||||
@@ -828,7 +836,7 @@ def close_position(pid):
|
|||||||
if close_price is None:
|
if close_price is None:
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("无法获取现价,平仓失败")
|
flash("无法获取现价,平仓失败")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("positions"))
|
||||||
capital = float(get_setting("live_capital", "0") or 0)
|
capital = float(get_setting("live_capital", "0") or 0)
|
||||||
metrics = calc_position_metrics(direction, entry, sl, tp, lots, close_price, capital, sym)
|
metrics = calc_position_metrics(direction, entry, sl, tp, lots, close_price, capital, sym)
|
||||||
pnl = metrics.get("float_pnl") or 0.0
|
pnl = metrics.get("float_pnl") or 0.0
|
||||||
@@ -850,16 +858,13 @@ def close_position(pid):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash(f"已平仓,盈亏 {pnl:.2f} 元,已记入交易记录")
|
flash(f"已平仓,盈亏 {pnl:.2f} 元,已记入交易记录")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("positions"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trades")
|
@app.route("/trades")
|
||||||
@login_required
|
@login_required
|
||||||
def trades():
|
def trades():
|
||||||
conn = get_db()
|
return redirect(url_for("records"))
|
||||||
rows = conn.execute("SELECT * FROM trade_logs ORDER BY id DESC LIMIT 500").fetchall()
|
|
||||||
conn.close()
|
|
||||||
return render_template("trades.html", trades=rows)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/update_trade/<int:tid>", methods=["POST"])
|
@app.route("/update_trade/<int:tid>", methods=["POST"])
|
||||||
@@ -895,7 +900,7 @@ def update_trade(tid):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("交易记录已核对保存")
|
flash("交易记录已核对保存")
|
||||||
return redirect(url_for("trades"))
|
return redirect(url_for("records"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/del_trade/<int:tid>")
|
@app.route("/del_trade/<int:tid>")
|
||||||
@@ -906,7 +911,7 @@ def del_trade(tid):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
flash("已删除")
|
flash("已删除")
|
||||||
return redirect(url_for("trades"))
|
return redirect(url_for("records"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/fill_review/<int:tid>")
|
@app.route("/fill_review/<int:tid>")
|
||||||
@@ -917,7 +922,7 @@ def fill_review_from_trade(tid):
|
|||||||
conn.close()
|
conn.close()
|
||||||
if not row:
|
if not row:
|
||||||
flash("记录不存在")
|
flash("记录不存在")
|
||||||
return redirect(url_for("trades"))
|
return redirect(url_for("records"))
|
||||||
q = {
|
q = {
|
||||||
"symbol": row["symbol"],
|
"symbol": row["symbol"],
|
||||||
"symbol_name": row["symbol_name"] or row["symbol"],
|
"symbol_name": row["symbol_name"] or row["symbol"],
|
||||||
@@ -933,7 +938,8 @@ def fill_review_from_trade(tid):
|
|||||||
"close_time": row["close_time"],
|
"close_time": row["close_time"],
|
||||||
"pnl": row["pnl"],
|
"pnl": row["pnl"],
|
||||||
}
|
}
|
||||||
return redirect(url_for("records", **{k: v for k, v in q.items() if v is not None}))
|
params = {k: v for k, v in q.items() if v is not None}
|
||||||
|
return redirect(url_for("records", **params) + "#review-panel")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/del_key/<int:pid>")
|
@app.route("/del_key/<int:pid>")
|
||||||
@@ -974,6 +980,9 @@ def records():
|
|||||||
auto_list = conn.execute(
|
auto_list = conn.execute(
|
||||||
"SELECT * FROM trade_records ORDER BY id DESC LIMIT 30"
|
"SELECT * FROM trade_records ORDER BY id DESC LIMIT 30"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
trade_list = conn.execute(
|
||||||
|
"SELECT * FROM trade_logs ORDER BY id DESC LIMIT 500"
|
||||||
|
).fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
trade_prefill_keys = (
|
trade_prefill_keys = (
|
||||||
@@ -986,6 +995,7 @@ def records():
|
|||||||
return render_template(
|
return render_template(
|
||||||
"records.html",
|
"records.html",
|
||||||
reviews=review_list,
|
reviews=review_list,
|
||||||
|
trades=trade_list,
|
||||||
auto_records=auto_list,
|
auto_records=auto_list,
|
||||||
preset=preset,
|
preset=preset,
|
||||||
start=start,
|
start=start,
|
||||||
|
|||||||
+2
-72
@@ -1,17 +1,11 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var keyTimer = null;
|
var keyTimer = null;
|
||||||
var posTimer = null;
|
|
||||||
|
|
||||||
function fmtDist(v) {
|
function fmtDist(v) {
|
||||||
if (v === null || v === undefined) return '--';
|
if (v === null || v === undefined) return '--';
|
||||||
return Number(v).toFixed(2);
|
return Number(v).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtNum(v, digits) {
|
|
||||||
if (v === null || v === undefined) return '--';
|
|
||||||
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pollKeyPrices() {
|
function pollKeyPrices() {
|
||||||
var list = document.getElementById('key-monitor-list');
|
var list = document.getElementById('key-monitor-list');
|
||||||
if (!list || !list.querySelector('.key-item')) return;
|
if (!list || !list.querySelector('.key-item')) return;
|
||||||
@@ -33,72 +27,8 @@
|
|||||||
.catch(function () { /* ignore */ });
|
.catch(function () { /* ignore */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPosCard(row) {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var pnlClass = '';
|
|
||||||
if (row.float_pnl > 0) pnlClass = 'pnl-pos';
|
|
||||||
if (row.float_pnl < 0) pnlClass = 'pnl-neg';
|
|
||||||
var pnlText = '--';
|
|
||||||
if (row.float_pnl != null) {
|
|
||||||
var sign = row.float_pnl >= 0 ? '+' : '';
|
|
||||||
pnlText = sign + fmtNum(row.float_pnl) + '元';
|
|
||||||
if (row.float_pct != null) {
|
|
||||||
pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--';
|
|
||||||
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
|
||||||
|
|
||||||
return (
|
|
||||||
'<div class="pos-card" data-pos-id="' + row.id + '">' +
|
|
||||||
'<div class="pos-card-head">' +
|
|
||||||
'<div><div class="title">' + row.symbol + ' <span class="badge dir">' + row.direction + '</span></div></div>' +
|
|
||||||
'<form method="post" action="/close_position/' + row.id + '" style="display:inline" onsubmit="return confirm(\'确认平仓?\')">' +
|
|
||||||
'<button type="submit" class="btn-del pos-del">平仓</button></form>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="pos-card-meta">来源 <strong>手动输入</strong> · 风险 <strong>' +
|
|
||||||
fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元</strong></div>' +
|
|
||||||
'<div class="pos-metrics">' +
|
|
||||||
'<div class="cell"><label>成交价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
|
||||||
'<div class="cell"><label>止损</label><div>' + fmtNum(row.stop_loss) + '</div></div>' +
|
|
||||||
'<div class="cell"><label>止盈</label><div>' + fmtNum(row.take_profit) + '</div></div>' +
|
|
||||||
'<div class="cell"><label>盈亏比</label><div>' + rr + '</div></div>' +
|
|
||||||
'<div class="cell"><label>标记价</label><div>' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '</div></div>' +
|
|
||||||
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="pos-footer">' +
|
|
||||||
'<span>保证金 ' + fmtNum(row.margin) + '元</span>' +
|
|
||||||
'<span>仓位占比 ' + fmtNum(row.position_pct) + '%</span>' +
|
|
||||||
'<span>开仓 ' + (openT || '--') + '</span>' +
|
|
||||||
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
|
|
||||||
'<span>张数 ' + row.lots + '</span>' +
|
|
||||||
'</div></div>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pollPositions() {
|
|
||||||
var list = document.getElementById('position-live-list');
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
fetch('/api/position_live')
|
|
||||||
.then(function (r) { return r.json(); })
|
|
||||||
.then(function (rows) {
|
|
||||||
if (!rows.length) {
|
|
||||||
list.innerHTML = '<div class="empty-hint">暂无持仓,左侧录入后显示</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
list.innerHTML = rows.map(buildPosCard).join('');
|
|
||||||
})
|
|
||||||
.catch(function () { /* ignore */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPolling() {
|
|
||||||
if (keyTimer) clearInterval(keyTimer);
|
|
||||||
if (posTimer) clearInterval(posTimer);
|
|
||||||
pollKeyPrices();
|
pollKeyPrices();
|
||||||
pollPositions();
|
|
||||||
keyTimer = setInterval(pollKeyPrices, 1000);
|
keyTimer = setInterval(pollKeyPrices, 1000);
|
||||||
posTimer = setInterval(pollPositions, 1000);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', startPolling);
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
(function () {
|
||||||
|
var posTimer = null;
|
||||||
|
|
||||||
|
function fmtNum(v, digits) {
|
||||||
|
if (v === null || v === undefined) return '--';
|
||||||
|
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPosCard(row) {
|
||||||
|
var pnlClass = '';
|
||||||
|
if (row.float_pnl > 0) pnlClass = 'pnl-pos';
|
||||||
|
if (row.float_pnl < 0) pnlClass = 'pnl-neg';
|
||||||
|
var pnlText = '--';
|
||||||
|
if (row.float_pnl != null) {
|
||||||
|
var sign = row.float_pnl >= 0 ? '+' : '';
|
||||||
|
pnlText = sign + fmtNum(row.float_pnl) + '元';
|
||||||
|
if (row.float_pct != null) {
|
||||||
|
pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--';
|
||||||
|
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
||||||
|
|
||||||
|
return (
|
||||||
|
'<div class="pos-card" data-pos-id="' + row.id + '">' +
|
||||||
|
'<div class="pos-card-head">' +
|
||||||
|
'<div><div class="title">' + row.symbol + ' <span class="badge dir">' + row.direction + '</span></div></div>' +
|
||||||
|
'<form method="post" action="/close_position/' + row.id + '" style="display:inline" onsubmit="return confirm(\'确认平仓?\')">' +
|
||||||
|
'<button type="submit" class="btn-del pos-del">平仓</button></form>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="pos-card-meta">来源 <strong>手动输入</strong> · 风险 <strong>' +
|
||||||
|
fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元</strong></div>' +
|
||||||
|
'<div class="pos-metrics">' +
|
||||||
|
'<div class="cell"><label>成交价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
||||||
|
'<div class="cell"><label>止损</label><div>' + fmtNum(row.stop_loss) + '</div></div>' +
|
||||||
|
'<div class="cell"><label>止盈</label><div>' + fmtNum(row.take_profit) + '</div></div>' +
|
||||||
|
'<div class="cell"><label>盈亏比</label><div>' + rr + '</div></div>' +
|
||||||
|
'<div class="cell"><label>标记价</label><div>' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '</div></div>' +
|
||||||
|
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="pos-footer">' +
|
||||||
|
'<span>保证金 ' + fmtNum(row.margin) + '元</span>' +
|
||||||
|
'<span>仓位占比 ' + fmtNum(row.position_pct) + '%</span>' +
|
||||||
|
'<span>开仓 ' + (openT || '--') + '</span>' +
|
||||||
|
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
|
||||||
|
'<span>张数 ' + row.lots + '</span>' +
|
||||||
|
'</div></div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pollPositions() {
|
||||||
|
var list = document.getElementById('position-live-list');
|
||||||
|
if (!list) return;
|
||||||
|
|
||||||
|
fetch('/api/position_live')
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (rows) {
|
||||||
|
if (!rows.length) {
|
||||||
|
list.innerHTML = '<div class="empty-hint">暂无持仓,左侧录入后显示</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.innerHTML = rows.map(buildPosCard).join('');
|
||||||
|
})
|
||||||
|
.catch(function () { /* ignore */ });
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
pollPositions();
|
||||||
|
posTimer = setInterval(pollPositions, 1000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
+9
-3
@@ -350,7 +350,13 @@
|
|||||||
.pos-footer span{color:var(--text-primary)}
|
.pos-footer span{color:var(--text-primary)}
|
||||||
.pos-del{font-size:.75rem;padding:.35rem .65rem}
|
.pos-del{font-size:.75rem;padding:.35rem .65rem}
|
||||||
.trade-toolbar{display:flex;align-items:center;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
|
.trade-toolbar{display:flex;align-items:center;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
|
||||||
.trade-toolbar label{display:flex;align-items:center;gap:.4rem;font-size:.85rem;cursor:pointer;color:var(--text-muted)}
|
.trade-switch-label{
|
||||||
|
display:flex;align-items:center;gap:.35rem;
|
||||||
|
font-size:.68rem;color:var(--text-muted);
|
||||||
|
white-space:nowrap;margin-bottom:.65rem;cursor:pointer;
|
||||||
|
}
|
||||||
|
.trade-switch-label span{line-height:1}
|
||||||
|
.trade-switch-label input{flex-shrink:0}
|
||||||
.trade-table-wrap{overflow-x:auto}
|
.trade-table-wrap{overflow-x:auto}
|
||||||
.trade-table{font-size:.8rem}
|
.trade-table{font-size:.8rem}
|
||||||
.trade-table th{font-size:.75rem;padding:.55rem .45rem}
|
.trade-table th{font-size:.75rem;padding:.55rem .45rem}
|
||||||
@@ -396,8 +402,8 @@
|
|||||||
<nav class="site-nav">
|
<nav class="site-nav">
|
||||||
<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>
|
<a href="{{ url_for('plans') }}" class="{% if request.endpoint == 'plans' %}active{% endif %}">开单计划</a>
|
||||||
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
<a href="{{ url_for('keys') }}" class="{% if request.endpoint == 'keys' %}active{% endif %}">关键位监控</a>
|
||||||
<a href="{{ url_for('trades') }}" class="{% if request.endpoint == 'trades' %}active{% endif %}">交易记录</a>
|
<a href="{{ url_for('positions') }}" class="{% if request.endpoint == 'positions' %}active{% endif %}">持仓监控</a>
|
||||||
<a href="{{ url_for('records') }}" class="{% if request.endpoint == 'records' %}active{% endif %}">复盘</a>
|
<a href="{{ url_for('records') }}" class="{% if request.endpoint in ('records', 'trades') %}active{% endif %}">交易记录与复盘</a>
|
||||||
<a href="{{ url_for('stats') }}" class="{% if request.endpoint == 'stats' %}active{% endif %}">统计分析</a>
|
<a href="{{ url_for('stats') }}" class="{% if request.endpoint == 'stats' %}active{% endif %}">统计分析</a>
|
||||||
<a href="{{ url_for('settings') }}" class="{% if request.endpoint == 'settings' %}active{% endif %}">系统设置</a>
|
<a href="{{ url_for('settings') }}" class="{% if request.endpoint == 'settings' %}active{% endif %}">系统设置</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -79,47 +79,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="split-grid" style="margin-top:1.5rem">
|
|
||||||
<div class="card">
|
|
||||||
<h2>持仓录入</h2>
|
|
||||||
<div class="card-body">
|
|
||||||
<form action="{{ url_for('add_position') }}" method="post" class="form-compact">
|
|
||||||
<div class="form-line line-3">
|
|
||||||
<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>
|
|
||||||
<div class="mini-field"><span>开仓时间</span><input type="datetime-local" name="open_time" required></div>
|
|
||||||
<input name="lots" type="number" step="1" min="1" value="1" placeholder="张数" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-line line-3">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div class="form-line line-btn">
|
|
||||||
<button type="submit" class="btn-primary">添加持仓</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<p class="hint" style="margin-top:.5rem">方向根据止损与成交价自动判断;风险比例依赖系统设置中的实盘资金。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>实时持仓</h2>
|
|
||||||
<div class="card-body card-scroll" id="position-live-list">
|
|
||||||
{% if not positions %}
|
|
||||||
<div class="empty-hint">暂无持仓,左侧录入后显示</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script src="{{ url_for('static', filename='js/keys.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/keys.js') }}"></script>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}持仓监控 - 国内期货监控系统{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="split-grid">
|
||||||
|
<div class="card">
|
||||||
|
<h2>持仓录入</h2>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="{{ url_for('add_position') }}" method="post" class="form-compact">
|
||||||
|
<div class="form-line line-3">
|
||||||
|
<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>
|
||||||
|
<input type="datetime-local" name="open_time" required title="开仓时间">
|
||||||
|
<input name="lots" type="number" step="1" min="1" value="1" placeholder="张数" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-line line-3">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="form-line line-btn">
|
||||||
|
<button type="submit" class="btn-primary">添加持仓</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<p class="hint" style="margin-top:.5rem">方向根据止损与成交价自动判断;风险比例依赖系统设置中的实盘资金。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>实时持仓</h2>
|
||||||
|
<div class="card-body card-scroll" id="position-live-list">
|
||||||
|
{% if not positions %}
|
||||||
|
<div class="empty-hint">暂无持仓,左侧录入后显示</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block extra_js %}
|
||||||
|
<script src="{{ url_for('static', filename='js/positions.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
+119
-2
@@ -1,8 +1,114 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}复盘 - 国内期货监控系统{% endblock %}
|
{% block title %}交易记录与复盘 - 国内期货监控系统{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="card" style="margin-bottom:1.25rem">
|
||||||
|
<h2>交易记录</h2>
|
||||||
|
<div class="card-body">
|
||||||
|
<label class="trade-switch-label">
|
||||||
|
<input type="checkbox" id="trade-edit-switch">
|
||||||
|
<span>修改/核对开关(开启后可编辑关键字段)</span>
|
||||||
|
</label>
|
||||||
|
<div class="trade-table-wrap card-scroll">
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
<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 }}手 / {{ t.margin or '-' }}</span>
|
||||||
|
<input class="cell-edit-show" type="number" step="0.01" name="margin" value="{{ t.margin or '' }}" placeholder="保证金" style="display:none">
|
||||||
|
<input type="hidden" name="lots" value="{{ t.lots }}">
|
||||||
|
</td>
|
||||||
|
<td><span class="cell-readonly">—</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 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 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="14" class="text-muted">暂无交易记录</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="split-grid records-split">
|
<div class="split-grid records-split">
|
||||||
<div class="card">
|
<div class="card" id="review-panel">
|
||||||
<h2>复盘上传</h2>
|
<h2>复盘上传</h2>
|
||||||
<div class="card-body">
|
<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">
|
<form id="review-form" action="{{ url_for('add_review') }}" method="post" enctype="multipart/form-data" class="form-compact form-compact-review line-tight">
|
||||||
@@ -178,10 +284,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script src="{{ url_for('static', filename='js/review.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/review.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/trades.js') }}"></script>
|
||||||
{% if prefill %}
|
{% if prefill %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var form = document.getElementById('review-form');
|
var form = document.getElementById('review-form');
|
||||||
|
var panel = document.getElementById('review-panel');
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
var map = {{ prefill | tojson }};
|
var map = {{ prefill | tojson }};
|
||||||
Object.keys(map).forEach(function (k) {
|
Object.keys(map).forEach(function (k) {
|
||||||
@@ -191,7 +299,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
var symInput = form.querySelector('.symbol-input');
|
var symInput = form.querySelector('.symbol-input');
|
||||||
if (symInput && map.symbol_name) symInput.value = map.symbol_name;
|
if (symInput && map.symbol_name) symInput.value = map.symbol_name;
|
||||||
if (typeof recalc === 'function') recalc();
|
if (typeof recalc === 'function') recalc();
|
||||||
|
if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
if (window.location.hash === '#review-panel') {
|
||||||
|
var panel = document.getElementById('review-panel');
|
||||||
|
if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}交易记录 - 国内期货监控系统{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<div class="card">
|
|
||||||
<h2>交易记录</h2>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="trade-toolbar">
|
|
||||||
<label><input type="checkbox" id="trade-edit-switch"> 修改/核对开关(开启后可编辑关键字段)</label>
|
|
||||||
</div>
|
|
||||||
<div class="trade-table-wrap card-scroll">
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
<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 }}手 / {{ t.margin or '-' }}</span>
|
|
||||||
<input class="cell-edit-show" type="number" step="0.01" name="margin" value="{{ t.margin or '' }}" placeholder="保证金" style="display:none">
|
|
||||||
<input type="hidden" name="lots" value="{{ t.lots }}">
|
|
||||||
</td>
|
|
||||||
<td><span class="cell-readonly">—</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 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 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="14" class="text-muted">暂无交易记录</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% block extra_js %}
|
|
||||||
<script src="{{ url_for('static', filename='js/trades.js') }}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
Reference in New Issue
Block a user