feat(evaluation): restore versioned strategy tracking tab
This commit is contained in:
@@ -773,6 +773,15 @@ def run_once(round_label=""):
|
||||
# 取消超时定时器(正常完成)
|
||||
signal.alarm(0)
|
||||
|
||||
# ── 策略追踪评估(2026-07-27 老爸:检查推荐是否触发止盈/止损)──
|
||||
try:
|
||||
import mofin_db
|
||||
tracked = mofin_db.check_strategy_outcomes(conn)
|
||||
if tracked:
|
||||
print(f" [TRACK] {tracked}条推荐触发止盈/止损", flush=True)
|
||||
except Exception as e:
|
||||
print(f" [TRACK] 检查失败: {e}", flush=True)
|
||||
|
||||
# 清理进程锁
|
||||
try:
|
||||
os.remove("/tmp/price_monitor.lock")
|
||||
|
||||
+161
-4
@@ -285,6 +285,46 @@ def init_all_tables(conn: sqlite3.Connection):
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_strategy_history_code ON strategy_history(code, snapshotted_at);
|
||||
|
||||
-- 策略追踪评估(2026-07-27 老爸:每条推荐操作的完整生命周期跟踪)
|
||||
-- 每个版本一条记录,策略变更时自动追加新版本
|
||||
CREATE TABLE IF NOT EXISTS strategy_tracking (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT NOT NULL,
|
||||
name TEXT,
|
||||
version_seq INTEGER DEFAULT 1, -- 该股票的第几个策略版本
|
||||
-- 推荐时的快照
|
||||
tracked_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
||||
timing_signal TEXT,
|
||||
rec_score INTEGER DEFAULT 0,
|
||||
rr_ratio REAL,
|
||||
entry_low REAL,
|
||||
entry_high REAL,
|
||||
entry_mid REAL,
|
||||
stop_loss REAL,
|
||||
take_profit REAL,
|
||||
position_advice TEXT,
|
||||
price_at_track REAL, -- 记录时的市价
|
||||
-- 区别于前一版本的变化摘要
|
||||
change_summary TEXT,
|
||||
-- 结果跟踪
|
||||
status TEXT DEFAULT 'active' CHECK(status IN ('active','hit_tp','hit_sl','expired','manual_close')),
|
||||
closed_at TEXT,
|
||||
close_price REAL,
|
||||
close_reason TEXT,
|
||||
theoretical_pnl REAL, -- 理论盈亏%(基于中值买入价)
|
||||
-- 实操数据(由用户或导入脚本填入)
|
||||
actual_action TEXT, -- "买入600股@148.86"
|
||||
actual_entry REAL,
|
||||
actual_shares INTEGER,
|
||||
actual_exit REAL,
|
||||
actual_pnl REAL,
|
||||
actual_exit_reason TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_track_code ON strategy_tracking(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_track_status ON strategy_tracking(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_track_date ON strategy_tracking(tracked_at);
|
||||
|
||||
-- 自选股
|
||||
CREATE TABLE IF NOT EXISTS watchlist_stocks (
|
||||
code TEXT PRIMARY KEY REFERENCES stocks(code),
|
||||
@@ -1373,13 +1413,14 @@ def sync_recommend_tag(conn, code: str, timing_signal: str):
|
||||
_pct = max(5, min(20, _pct))
|
||||
_pos_auto = f"{int(_pct)}%(系统按RR{_rr_pos:.1f}自动计算,见原建议仓位)"
|
||||
# ── 股数换算(2026-07-27 老爸:方便快速操作)──
|
||||
# 2026-07-27 修正:按总资产算仓位,不用现金(现金波动剧烈)
|
||||
try:
|
||||
_lp = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
|
||||
_cash = conn.execute("SELECT cash FROM portfolio_summary WHERE id=1").fetchone()
|
||||
if _lp and _lp[0] and _cash and _cash[0]:
|
||||
_ta = conn.execute("SELECT total_assets FROM portfolio_summary WHERE id=1").fetchone()
|
||||
if _lp and _lp[0] and _ta and _ta[0]:
|
||||
_price = float(_lp[0])
|
||||
_cash_val = float(_cash[0])
|
||||
_shares_raw = _cash_val * _pct / 100.0 / _price
|
||||
_total = float(_ta[0])
|
||||
_shares_raw = _total * _pct / 100.0 / _price
|
||||
if _shares_raw >= 100:
|
||||
_shares = int(_shares_raw / 100) * 100 # A股整手
|
||||
else:
|
||||
@@ -1426,6 +1467,8 @@ def sync_recommend_tag(conn, code: str, timing_signal: str):
|
||||
"WHERE code=? AND status='active' AND tag='current_recommend'",
|
||||
(code,))
|
||||
conn.commit()
|
||||
# ── 策略版本追踪(2026-07-27 老爸:每次tag变更都记录到评估表)──
|
||||
track_strategy_version(conn, code)
|
||||
except Exception as e:
|
||||
print(f" [TAG SYNC] {code} 失败: {e}", flush=True)
|
||||
|
||||
@@ -1495,6 +1538,117 @@ def enqueue_recommend(conn, code: str):
|
||||
return False
|
||||
|
||||
|
||||
def track_strategy_version(conn, code: str):
|
||||
"""版本化策略追踪:每次策略变更(sync_recommend_tag 后)自动记录新版本。
|
||||
只在 tag='current_recommend' 时记录;与上一条相比有实质变更才追加新版本。"""
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT name, timing_signal, rec_score, rr_ratio, entry_low, entry_high, "
|
||||
"stop_loss, take_profit, position_advice, tag FROM holding_strategies "
|
||||
"WHERE code=? AND status='active'", (code,)).fetchone()
|
||||
if not row:
|
||||
return
|
||||
name, sig, score, rr, el, eh, sl, tp, pos, tag = row
|
||||
if tag != 'current_recommend':
|
||||
return # 非推荐状态不追踪
|
||||
|
||||
lp = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
|
||||
price = lp[0] if lp and lp[0] else 0
|
||||
mid = round((el + eh) / 2, 2) if el > 0 and eh > el else 0
|
||||
|
||||
# 查上一个版本
|
||||
prev = conn.execute(
|
||||
"SELECT entry_low, entry_high, stop_loss, take_profit, rr_ratio, rec_score, "
|
||||
"status FROM strategy_tracking WHERE code=? ORDER BY id DESC LIMIT 1",
|
||||
(code,)).fetchone()
|
||||
|
||||
# 计算版本号
|
||||
last_ver = conn.execute(
|
||||
"SELECT MAX(version_seq) FROM strategy_tracking WHERE code=?", (code,)).fetchone()
|
||||
ver = (last_ver[0] or 0) + 1 if last_ver else 1
|
||||
|
||||
if prev and prev[6] == 'active': # 上一版本还在进行中
|
||||
if (abs((prev[0] or 0) - (el or 0)) < 0.01 and
|
||||
abs((prev[1] or 0) - (eh or 0)) < 0.01 and
|
||||
abs((prev[2] or 0) - (sl or 0)) < 0.01 and
|
||||
abs((prev[3] or 0) - (tp or 0)) < 0.01):
|
||||
# 参数没变,只更新评分和RR
|
||||
conn.execute(
|
||||
"UPDATE strategy_tracking SET rec_score=?, rr_ratio=?, price_at_track=?, "
|
||||
"timing_signal=?, position_advice=? WHERE id=("
|
||||
"SELECT id FROM strategy_tracking WHERE code=? ORDER BY id DESC LIMIT 1)",
|
||||
(score, rr, price, sig, pos, code))
|
||||
conn.commit()
|
||||
return
|
||||
|
||||
# 有变更 → 追加新版本
|
||||
change = ""
|
||||
if prev:
|
||||
parts = []
|
||||
if abs((prev[0] or 0) - (el or 0)) > 0.5: parts.append(f"区{prev[0]}→{el}")
|
||||
if abs((prev[2] or 0) - (sl or 0)) > 0.5: parts.append(f"损{prev[2]}→{sl}")
|
||||
if abs((prev[3] or 0) - (tp or 0)) > 0.5: parts.append(f"盈{prev[3]}→{tp}")
|
||||
if abs((prev[5] or 0) - (score or 0)) >= 5: parts.append(f"评分{prev[5]}→{score}")
|
||||
change = "; ".join(parts) if parts else ""
|
||||
|
||||
conn.execute("""
|
||||
INSERT INTO strategy_tracking
|
||||
(code, name, version_seq, tracked_at, timing_signal, rec_score, rr_ratio,
|
||||
entry_low, entry_high, entry_mid, stop_loss, take_profit,
|
||||
position_advice, price_at_track, change_summary)
|
||||
VALUES (?,?,?,datetime('now','localtime'),?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", (code, name, ver, sig, score, rr, el, eh, mid, sl, tp, pos, price, change))
|
||||
conn.commit()
|
||||
if change:
|
||||
print(f" [TRACK] {code} v{ver}: {change}", flush=True)
|
||||
except Exception as e:
|
||||
print(f" [TRACK] {code} 版本记录失败: {e}", flush=True)
|
||||
|
||||
|
||||
def check_strategy_outcomes(conn):
|
||||
"""检查所有 active 追踪版本是否触发 SL/TP,自动关闭并记录理论盈亏"""
|
||||
active = conn.execute("""
|
||||
SELECT id, code, entry_mid, stop_loss, take_profit, rr_ratio
|
||||
FROM strategy_tracking WHERE status='active'
|
||||
""").fetchall()
|
||||
|
||||
updated = 0
|
||||
for r in active:
|
||||
tid, code, mid, sl, tp, rr = r
|
||||
lp = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
|
||||
if not lp or not lp[0]:
|
||||
continue
|
||||
price = float(lp[0])
|
||||
mid = mid or price # fallback
|
||||
|
||||
closed = False
|
||||
if tp and tp > 0 and price >= tp:
|
||||
pnl_pct = round((tp - mid) / mid * 100, 1) if mid > 0 else 0
|
||||
conn.execute("""
|
||||
UPDATE strategy_tracking SET status='hit_tp', closed_at=datetime('now','localtime'),
|
||||
close_price=?, close_reason='止盈触发', theoretical_pnl=?
|
||||
WHERE id=?
|
||||
""", (price, pnl_pct, tid))
|
||||
print(f" [TRACK] {code} v{tid} 止盈! {price}≥{tp} +{pnl_pct}%", flush=True)
|
||||
closed = True
|
||||
elif sl and sl > 0 and price <= sl:
|
||||
pnl_pct = round((sl - mid) / mid * 100, 1) if mid > 0 else -5
|
||||
conn.execute("""
|
||||
UPDATE strategy_tracking SET status='hit_sl', closed_at=datetime('now','localtime'),
|
||||
close_price=?, close_reason='止损触发', theoretical_pnl=?
|
||||
WHERE id=?
|
||||
""", (price, pnl_pct, tid))
|
||||
print(f" [TRACK] {code} v{tid} 止损! {price}≤{sl} {pnl_pct}%", flush=True)
|
||||
closed = True
|
||||
|
||||
if closed:
|
||||
updated += 1
|
||||
|
||||
if updated:
|
||||
conn.commit()
|
||||
return updated
|
||||
|
||||
|
||||
def flush_rec_digest(max_items=5):
|
||||
"""把队列里的新推荐聚成一条 XMPP 摘要(RR 降序,最多 max_items 只)。
|
||||
头部 1-2 只附带策略依据摘要+按现金的操盘建议。"""
|
||||
@@ -1967,6 +2121,9 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
|
||||
_existing_tag,
|
||||
))
|
||||
conn.commit()
|
||||
# ── 策略版本追踪(每次策略写入后自动记录,2026-07-27)──
|
||||
if _existing_tag == 'current_recommend':
|
||||
track_strategy_version(conn, code)
|
||||
# ── 推荐转场:LLM路径新转为 current_recommend → 记入摘要队列(不逐只推送)──
|
||||
if _existing_tag == 'current_recommend' and _old_tag != 'current_recommend' \
|
||||
and source_trigger in ('batch_12d', 'per_stock_12d'):
|
||||
|
||||
@@ -488,6 +488,43 @@ def api_watchlist():
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/tracking")
|
||||
def get_tracking():
|
||||
"""策略追踪评估:所有推荐的历史记录和结果"""
|
||||
import sqlite3
|
||||
conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db")
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute("""
|
||||
SELECT st.*, lp.price as current_price
|
||||
FROM strategy_tracking st
|
||||
LEFT JOIN live_prices lp ON st.code = lp.code
|
||||
ORDER BY
|
||||
CASE st.status WHEN 'active' THEN 0 WHEN 'hit_tp' THEN 1 WHEN 'hit_sl' THEN 2 ELSE 3 END,
|
||||
st.tracked_at DESC
|
||||
""").fetchall()
|
||||
tracks = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
# 理论盈亏:按买入区中值买入,当前价 vs 中值
|
||||
if d.get('entry_mid') and d.get('current_price') and d.get('status') == 'active':
|
||||
mid = float(d['entry_mid'])
|
||||
price = float(d['current_price'])
|
||||
d['theoretical_pnl'] = round((price - mid) / mid * 100, 2)
|
||||
tracks.append(d)
|
||||
conn.close()
|
||||
return jsonify({
|
||||
"tracks": tracks,
|
||||
"stats": {
|
||||
"total": len(tracks),
|
||||
"active": sum(1 for r in tracks if r["status"] == "active"),
|
||||
"hit_tp": sum(1 for r in tracks if r["status"] == "hit_tp"),
|
||||
"hit_sl": sum(1 for r in tracks if r["status"] == "hit_sl"),
|
||||
"expired": sum(1 for r in tracks if r["status"] == "expired"),
|
||||
"manual": sum(1 for r in tracks if r["status"] == "manual_close"),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@app.route("/api/overview")
|
||||
def api_overview():
|
||||
"""概览数据"""
|
||||
@@ -1692,4 +1729,4 @@ register_routes(app)
|
||||
if __name__ == "__main__":
|
||||
port = int(os.environ.get("PORT", 8899))
|
||||
print(f"🚀 MoFin Dashboard → http://0.0.0.0:{port}")
|
||||
app.run(host="0.0.0.0", port=port, debug=False)
|
||||
app.run(host="0.0.0.0", port=port, debug=False)
|
||||
|
||||
+54
-79
@@ -612,89 +612,64 @@ function renderMarket() {
|
||||
}
|
||||
|
||||
// ── Evaluation Tab ──
|
||||
let evalTimer = null;
|
||||
function renderEvaluation() {
|
||||
const el = document.getElementById('tab-evaluation');
|
||||
Promise.all([
|
||||
fetchJSON('/api/evaluation'),
|
||||
fetchJSON('/api/stats/accuracy'),
|
||||
]).then(([evals, stats]) => {
|
||||
const p1 = stats.phase1 || {};
|
||||
const p2 = stats.phase2 || {};
|
||||
if (evalTimer) clearInterval(evalTimer);
|
||||
fetchJSON('/api/tracking').then(data => {
|
||||
const tracks = data.tracks || [];
|
||||
const stats = data.stats || {};
|
||||
|
||||
const statusBadge = s => {
|
||||
const m = {active:'🟡进行中',hit_tp:'🟢止盈',hit_sl:'🔴止损',expired:'⚫已过期',manual_close:'🔵手动平仓'};
|
||||
return m[s] || s;
|
||||
};
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="text-sm font-semibold">策略双维度评估 <span class="spec-btns"><span class="spec-btn help" onclick="showModuleHelp('evaluation','human')">?</span><span class="spec-btn ai" onclick="showModuleHelp('evaluation','ai')">§</span></span></span>
|
||||
<span class="text-xs bg-blue-900/50 text-blue-300 px-2 py-0.5 rounded-full">${evals.length} 只已评估</span>
|
||||
<button class="text-xs px-2 py-1 rounded bg-slate-700 hover:bg-slate-600 ml-auto" onclick="triggerEval()">🔄 重新评估</button>
|
||||
<span class="text-sm font-semibold">📊 策略追踪评估</span>
|
||||
<span class="text-xs bg-blue-900/50 text-blue-300 px-2 py-0.5 rounded-full">${stats.total||0} 条记录</span>
|
||||
<span class="text-xs text-green-400">止盈 ${stats.hit_tp||0}</span>
|
||||
<span class="text-xs text-red-400">止损 ${stats.hit_sl||0}</span>
|
||||
<span class="text-xs text-yellow-400">进行中 ${stats.active||0}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-4">
|
||||
<div class="card p-3 text-center">
|
||||
<div class="text-lg font-bold text-green-400">${p1.correct||0}</div>
|
||||
<div class="text-xs text-slate-500">阶段一 达标</div>
|
||||
</div>
|
||||
<div class="card p-3 text-center">
|
||||
<div class="text-lg font-bold text-red-400">${p1.wrong||0}</div>
|
||||
<div class="text-xs text-slate-500">阶段一 止损</div>
|
||||
</div>
|
||||
<div class="card p-3 text-center">
|
||||
<div class="text-lg font-bold text-yellow-400">${p1.pending||0}</div>
|
||||
<div class="text-xs text-slate-500">待验证</div>
|
||||
</div>
|
||||
<div class="card p-3 text-center">
|
||||
<div class="text-lg font-bold text-white">${p1.accuracy_pct||0}%</div>
|
||||
<div class="text-xs text-slate-500">准确率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead><tr class="text-slate-500 border-b border-slate-800/50">
|
||||
<th class="text-left p-3">股票</th><th class="text-left p-3">策略</th>
|
||||
<th class="text-left p-3">理论盈亏</th><th class="text-left p-3">实际盈亏</th>
|
||||
<th class="text-left p-3">状态</th>
|
||||
</tr></thead>
|
||||
<tbody>${evals.map(x => {
|
||||
const adv = (x.advice_evaluation && x.advice_evaluation[0]) || {};
|
||||
const t = x.theoretical || {};
|
||||
const a = x.actual || {};
|
||||
const status = t.status || 'safe';
|
||||
const icon = status === 'take_profit_hit' ? '🟢' : status === 'stop_loss_hit' ? '🔴' : status === 'in_entry_zone' ? '📥' : '💤';
|
||||
return `<tr class="stock-row border-b border-slate-800/30">
|
||||
<td class="p-3"><div class="font-medium text-white">${x.name}</div><div class="text-slate-500">${x.code}</div></td>
|
||||
<td class="p-3 text-slate-300">${x.current || (x.advice_evaluation && x.advice_evaluation[0] && x.advice_evaluation[0].current_advice || '') || '—'}</td>
|
||||
<td class="p-3"><span class="${(t.theoretical_pnl_pct||0)>=0?'badge-up':'badge-down'}">${(t.theoretical_pnl_pct||0).toFixed(1)}%</span></td>
|
||||
<td class="p-3"><span class="${(a.actual_pnl_pct||0)>=0?'badge-up':'badge-down'}">${(a.actual_pnl_pct||0).toFixed(1)}%</span></td>
|
||||
<td class="p-3">${icon} ${status.replace(/_/g,' ')}</td>
|
||||
</tr>`;
|
||||
}).join('')}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-slate-600 mt-2">更新时间: ${stats.updated_at?.slice(0,16)||'—'}</div>
|
||||
`;
|
||||
// Load feedback
|
||||
fetchJSON('/api/feedback').then(fb => {
|
||||
const trend = fb.accuracy_trend || {};
|
||||
const completed = fb.phase1_completed_count || 0;
|
||||
const reassess = fb.reassess_needed_count || 0;
|
||||
if (!completed && !reassess) return;
|
||||
const fbEl = document.createElement('div');
|
||||
fbEl.className = 'card p-4 mt-4';
|
||||
fbEl.innerHTML = `
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-sm font-semibold">🔄 反馈闭环</span>
|
||||
<span class="text-xs text-slate-500">趋势: ${trend.trend||'stable'}</span>
|
||||
<span class="text-xs ${completed>0?'text-green-400':'text-slate-500'}">完成 ${completed}</span>
|
||||
<span class="text-xs ${reassess>0?'text-yellow-400':'text-slate-500'}">重评 ${reassess}</span>
|
||||
</div>
|
||||
${(fb.feedback||[]).filter(f => f.adjustments?.length).slice(0,10).map(f => f.adjustments.map(a => `
|
||||
<div class="flex items-start gap-2 p-2 bg-slate-800/30 rounded-lg mb-1 text-xs">
|
||||
<span class="${a.type==='phase1_success'?'text-green-400':a.type==='phase1_failure'?'text-red-400':'text-yellow-400'}">${a.type==='phase1_success'?'✅':a.type==='phase1_failure'?'❌':'🔄'}</span>
|
||||
<span class="text-slate-300">${a.message}</span>
|
||||
</div>
|
||||
`).join('')).join('')}
|
||||
`;
|
||||
el.appendChild(fbEl);
|
||||
});
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead><tr class="text-slate-500 border-b border-slate-800/50">
|
||||
<th class="text-left p-2">股票</th>
|
||||
<th class="text-left p-2">推荐时间</th>
|
||||
<th class="text-right p-2">评分</th>
|
||||
<th class="text-right p-2">RR</th>
|
||||
<th class="text-right p-2">买入区(中值)</th>
|
||||
<th class="text-right p-2">止损/止盈</th>
|
||||
<th class="text-right p-2">推荐价</th>
|
||||
<th class="text-right p-2">推荐仓位</th>
|
||||
<th class="text-right p-2">理论盈亏</th>
|
||||
<th class="text-right p-2">实际盈亏</th>
|
||||
<th class="text-left p-2">状态</th>
|
||||
</tr></thead>
|
||||
<tbody>${tracks.length === 0 ? '<tr><td colspan="12" class="p-4 text-center text-slate-500">暂无追踪记录</td></tr>' : tracks.map(t => {
|
||||
const mid = t.entry_mid ? Number(t.entry_mid).toFixed(2) : '—';
|
||||
const tPnl = t.theoretical_pnl != null ? ((t.theoretical_pnl >= 0 ? '+' : '') + t.theoretical_pnl + '%') : '—';
|
||||
const actualInfo = t.actual_action ? '<span class="text-blue-400">' + t.actual_action + '</span>' : (t.actual_pnl != null ? ((t.actual_pnl >= 0 ? '+' : '') + t.actual_pnl + '%') : '—');
|
||||
const rowBg = t.status === 'hit_tp' ? 'bg-green-900/10' : t.status === 'hit_sl' ? 'bg-red-900/10' : '';
|
||||
return '<tr class="border-b border-slate-800/30 ' + rowBg + '">' +
|
||||
'<td class="p-2"><span class="font-medium text-white">' + (t.name||'') + '</span><br><span class="text-slate-500">' + t.code + '</span></td>' +
|
||||
'<td class="p-2 text-slate-400">' + (t.tracked_at||'').slice(0,16) + '</td>' +
|
||||
'<td class="p-2 text-right font-mono ' + (t.rec_score>=70?'text-green-400':'text-amber-300') + '">' + (t.rec_score||'—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono">' + (t.rr_ratio ? t.rr_ratio.toFixed(2) : '—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono text-xs">' + (t.entry_low||'—') + ' → <b class="text-amber-300">' + mid + '</b> ← ' + (t.entry_high||'—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono text-xs"><span class="text-red-400">损' + (t.stop_loss||'—') + '</span><br><span class="text-green-400">盈' + (t.take_profit||'—') + '</span></td>' +
|
||||
'<td class="p-2 text-right font-mono text-slate-300">' + (t.price_at_track||'—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono text-slate-300">' + (t.position_advice||'—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono">' + tPnl + '</td>' +
|
||||
'<td class="p-2 text-right font-mono">' + actualInfo + '</td>' +
|
||||
'<td class="p-2">' + statusBadge(t.status) + (t.close_reason ? '<br><span class="text-slate-500">' + t.close_reason + '</span>' : '') + '</td>' +
|
||||
'</tr>';
|
||||
}).join('')}</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
evalTimer = setInterval(renderEvaluation, 30000);
|
||||
});
|
||||
}
|
||||
function triggerEval() {
|
||||
@@ -1902,4 +1877,4 @@ function inMd(t) {
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user