update
This commit is contained in:
parent
cc4a0c7eb7
commit
4fa4dcb965
@ -197,7 +197,7 @@ def _decision_archive_where(archive_filter):
|
||||
)
|
||||
"""
|
||||
if archive_filter == "executed":
|
||||
# 已执行口径以 paper trading 账本为准。正在持仓中的模拟交易,
|
||||
# 已执行口径以策略交易账本为准。正在持仓中的策略交易,
|
||||
# 其 recommendation 仍可能是 active/watch_pool,不能被归档条件挡掉。
|
||||
return executed_where
|
||||
if archive_filter == "invalid":
|
||||
|
||||
@ -162,7 +162,7 @@ def _paper_review(conn, since, days):
|
||||
exit_reasons = _bucket_count([t for t in trades if t.get("status") == "closed"], "exit_reason", "unknown")
|
||||
event_types = _bucket_count(events, "event_type", "unknown")
|
||||
return {
|
||||
"definition": "模拟交易复盘是唯一收益口径,基于 paper_trades 的开仓、平仓、移动止盈事件。",
|
||||
"definition": "策略交易复盘是唯一收益口径,基于交易账本的开仓、平仓、移动止盈事件。",
|
||||
"summary": summary,
|
||||
"exit_reasons": exit_reasons,
|
||||
"event_types": event_types,
|
||||
@ -306,7 +306,7 @@ def get_review_center_dashboard(days=30):
|
||||
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"principles": [
|
||||
"机会归档不计算交易收益,只记录发现、确认、失效和漏选。",
|
||||
"真实收益口径只来自模拟交易或未来真实交易账本。",
|
||||
"真实收益口径只来自策略交易或未来真实交易账本。",
|
||||
"链上、舆情、LLM 属于证据层,只做发现和解释,不直接改变推荐状态。",
|
||||
"策略迭代只发布经过样本约束和灰度闸门验证的规则。",
|
||||
],
|
||||
|
||||
@ -45,7 +45,7 @@ DEFAULT_JOBS = [
|
||||
"every_seconds": 180,
|
||||
"initial_delay": 30,
|
||||
"lock_group": "paper_trading_write",
|
||||
"description": "模拟交易账本同步",
|
||||
"description": "策略交易账本同步",
|
||||
"sort_order": 25,
|
||||
},
|
||||
{
|
||||
@ -386,7 +386,7 @@ def _display_job_name(job_name):
|
||||
"sentiment": "舆情",
|
||||
"onchain": "链上",
|
||||
"llm-sentiment": "AI舆情",
|
||||
"paper-trader": "模拟交易",
|
||||
"paper-trader": "策略交易",
|
||||
"review": "复盘",
|
||||
}.get(job_name, job_name)
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ def get_strategy_insights():
|
||||
"paper_realized_pnl_usdt": paper_realized_usdt,
|
||||
"actionable_conversion_pct": round(len(actionable) / total * 100, 1) if total else 0,
|
||||
"paper_conversion_pct": round(len(paper_items) / len(buy_now) * 100, 1) if buy_now else 0,
|
||||
"definition": "策略归因只看机会转化和模拟交易转化;收益只来自 paper_trades,不读取 recommendation.pnl_pct。",
|
||||
"definition": "策略归因只看机会转化和策略交易转化;收益只来自交易账本,不读取 recommendation.pnl_pct。",
|
||||
}
|
||||
|
||||
def add_bucket(bucket_map, key, item):
|
||||
@ -135,8 +135,8 @@ def get_strategy_insights():
|
||||
"metric_definition": {
|
||||
"opportunity_count": "进入 opportunity/recommendation 表的机会样本数,不代表交易。",
|
||||
"actionable_count": "确认层输出 buy_now 或 wait_pullback 的样本数。",
|
||||
"paper_trade_count": "已经被模拟交易账本执行的样本数。",
|
||||
"paper_realized_pnl_usdt": "仅来自 paper_trades 的已平仓模拟收益。",
|
||||
"paper_trade_count": "已经被策略交易账本执行的样本数。",
|
||||
"paper_realized_pnl_usdt": "仅来自交易账本的已平仓策略收益。",
|
||||
},
|
||||
"factor_attribution": serialize_buckets("factor", factor_map)[:30],
|
||||
"market_environment": serialize_buckets("environment", env_map)[:20],
|
||||
|
||||
@ -1299,7 +1299,7 @@ def confirm_burst(symbol, cand):
|
||||
|
||||
|
||||
def _watch_candidate_plan(symbol, result, cand_detail):
|
||||
"""把强势但未形成交易买点的样本写成机会观察,不触发模拟交易。"""
|
||||
"""把强势但未形成交易买点的样本写成机会观察,不触发策略交易。"""
|
||||
market_context = result.get("market_context") or {}
|
||||
signals = list(result.get("signals") or [])
|
||||
price = float(result.get("price") or 0)
|
||||
|
||||
@ -123,7 +123,7 @@ def extract_symbol(message: str, session=None, preferences=None) -> str:
|
||||
|
||||
def detect_intent(message: str, symbol: str = "") -> str:
|
||||
text = str(message or "").lower()
|
||||
if any(k in text for k in ("模拟交易", "纸面交易", "paper trading", "paper-trading", "paper")):
|
||||
if any(k in text for k in ("策略交易", "模拟交易", "纸面交易", "paper trading", "paper-trading", "paper")):
|
||||
return "restricted"
|
||||
if not _is_crypto_question(text, symbol):
|
||||
return "unsupported"
|
||||
@ -479,8 +479,8 @@ def build_context(intent: str, message: str, symbol: str, preferences=None) -> d
|
||||
def _fallback_answer(intent: str, message: str, context: dict) -> dict:
|
||||
if intent == "restricted":
|
||||
return {
|
||||
"summary": "内部模拟交易数据不可在智能问答中直接访问。",
|
||||
"answer": "我不能读取或解释内部模拟交易数据。你可以继续问公开行情、单币技术面、推荐解释、链上异动、舆情影响或复盘结果(不含纸面交易明细)。",
|
||||
"summary": "内部策略交易数据不可在智能问答中直接访问。",
|
||||
"answer": "我不能读取或解释内部策略交易数据。你可以继续问公开行情、单币技术面、推荐解释、链上异动、舆情影响或复盘结果(不含交易账本明细)。",
|
||||
"evidence": [],
|
||||
"related_records": [],
|
||||
"followups": ["分析 BTC/USDT 的技术面", "解释最新推荐为什么不是可买"],
|
||||
@ -614,7 +614,7 @@ def _call_chat_llm(message: str, context: dict, history=None) -> dict:
|
||||
"context": context,
|
||||
"recent_history": (history or [])[-8:],
|
||||
"rules": [
|
||||
"只回答加密货币、AlphaX 当前数据、技术面、链上、舆情、复盘相关问题,不要访问内部模拟交易数据。",
|
||||
"只回答加密货币、AlphaX 当前数据、技术面、链上、舆情、复盘相关问题,不要访问内部策略交易数据。",
|
||||
"不要给真实下单指令,不要修改推荐状态,不要承诺收益。",
|
||||
"回答使用中文,采用两段式:先结论,再证据。",
|
||||
"根据 intent 选择 answer_style:technical/decision/market/news/onchain/review/notice/help/default。",
|
||||
|
||||
@ -52,7 +52,7 @@ def main(limit: int = 100):
|
||||
except Exception as exc:
|
||||
finished_at = datetime.now()
|
||||
log_cron_run(
|
||||
job_name="模拟交易",
|
||||
job_name="策略交易",
|
||||
script_name="paper_trader.py",
|
||||
run_status="error",
|
||||
result_status="exception",
|
||||
@ -65,7 +65,7 @@ def main(limit: int = 100):
|
||||
raise
|
||||
finished_at = datetime.now()
|
||||
log_cron_run(
|
||||
job_name="模拟交易",
|
||||
job_name="策略交易",
|
||||
script_name="paper_trader.py",
|
||||
run_status="success",
|
||||
result_status=output.get("status", "completed"),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""
|
||||
山寨币爆发监控系统 — 推荐信号跟踪 + paper trading 执行账本
|
||||
山寨币爆发监控系统 — 推荐信号跟踪 + 策略交易执行账本
|
||||
趋势反转检测:1H连续阴动K、量价背离、空头加速(替代MACD/RSI)
|
||||
推荐层只管理信号状态;模拟成交、TP/SL、移动止盈由 paper_trading 独立负责。
|
||||
推荐层只管理信号状态;策略交易成交、TP/SL、移动止盈由 paper_trading 独立负责。
|
||||
"""
|
||||
|
||||
import sys, os, shutil
|
||||
@ -140,14 +140,14 @@ def analyze_tracking_signals(symbol, rec, current_price):
|
||||
# ---- 止盈信号检测 ----
|
||||
pnl_pct = ((current_price / entry_price) - 1) * 100 if entry_price > 0 else 0
|
||||
|
||||
# TP/SL 是模拟交易生命周期,不再写成推荐信号动作。
|
||||
# TP/SL 是策略交易生命周期,不再写成推荐信号动作。
|
||||
if tp1 > 0 and current_price >= tp1:
|
||||
sell_signals.append(f"模拟交易目标价已到达(${tp1:.4f}),执行结果以 paper trading 为准")
|
||||
sell_signals.append(f"策略交易目标价已到达(${tp1:.4f}),执行结果以交易账本为准")
|
||||
|
||||
rules = load_rules()
|
||||
|
||||
if tp1 == 0 and pnl_pct >= 15:
|
||||
sell_signals.append(f"无TP保护但浮盈已达+{pnl_pct:.1f}%,仅作为信号风险提醒,是否平仓由 paper trading/人工处理")
|
||||
sell_signals.append(f"无TP保护但浮盈已达+{pnl_pct:.1f}%,仅作为信号风险提醒,是否平仓由策略交易/人工处理")
|
||||
|
||||
# ---- 止损接近警告 ----
|
||||
if stop_loss > 0:
|
||||
@ -155,7 +155,7 @@ def analyze_tracking_signals(symbol, rec, current_price):
|
||||
if loss_pct < 3: # 当前价离止损不到3%
|
||||
sell_signals.append(f"⚠️ 接近止损!当前${current_price:.4f}离止损${stop_loss:.4f}仅{loss_pct:.1f}%")
|
||||
if current_price <= stop_loss:
|
||||
sell_signals.append(f"🔴 模拟交易止损价已触达!${current_price:.4f}≤${stop_loss:.4f},执行结果以 paper trading 为准")
|
||||
sell_signals.append(f"🔴 策略交易止损价已触达!${current_price:.4f}≤${stop_loss:.4f},执行结果以交易账本为准")
|
||||
|
||||
# ---- 趋势反转信号(PA行为检测,替代MACD) ----
|
||||
if h1_df is not None and len(h1_df) >= 30 and atr_1h > 0:
|
||||
|
||||
@ -904,12 +904,12 @@ function historyOutcome(r) {
|
||||
if (paper && paper.status === 'closed') {
|
||||
var exitReason = String(paper.exit_reason || '').toLowerCase();
|
||||
if (exitReason === 'stop_loss' || exitReason === 'sl' || exitReason === 'stopped_out') {
|
||||
return { resolved: true, type: 'executed_failed', label: '模拟交易止损', detail: '已进入模拟交易并触发止损' };
|
||||
return { resolved: true, type: 'executed_failed', label: '策略交易止损', detail: '已进入策略交易并触发止损' };
|
||||
}
|
||||
return { resolved: true, type: 'executed_success', label: '模拟交易兑现', detail: '已进入模拟交易并完成退出' };
|
||||
return { resolved: true, type: 'executed_success', label: '策略交易兑现', detail: '已进入策略交易并完成退出' };
|
||||
}
|
||||
if (paper && paper.status === 'open') {
|
||||
return { resolved: true, type: 'executed_open', label: '模拟交易持有', detail: '已进入模拟交易,仍在持仓中' };
|
||||
return { resolved: true, type: 'executed_open', label: '策略交易持有', detail: '已进入策略交易,仍在持仓中' };
|
||||
}
|
||||
if (status === 'hit_tp1' || status === 'hit_tp2' || execution === 'completed') {
|
||||
return { resolved: true, type: 'executed_success', label: '执行后兑现', detail: '已进入模拟/持仓口径验证' };
|
||||
@ -949,7 +949,7 @@ async function loadHistoryRecommendations(reset) {
|
||||
$('histCount').textContent = totalCount ? ' ' + totalCount : '';
|
||||
$('historyStats').innerHTML =
|
||||
'<div class="hstat"><div class="num" style="color:var(--blue)">'+totalCount+'</div><div class="lbl"><svg width="14" height="14" color="var(--blue)"><use href="#svg-target"/></svg> 归档信号</div><div class="sub">机会/观察历史</div></div>'+
|
||||
'<div class="hstat"><div class="num" style="color:var(--green)">'+executedCount+'</div><div class="lbl"><svg width="14" height="14" color="var(--green)"><use href="#svg-trendup"/></svg> 进入执行</div><div class="sub">收益见模拟交易</div></div>'+
|
||||
'<div class="hstat"><div class="num" style="color:var(--green)">'+executedCount+'</div><div class="lbl"><svg width="14" height="14" color="var(--green)"><use href="#svg-trendup"/></svg> 进入执行</div><div class="sub">收益见策略交易</div></div>'+
|
||||
'<div class="hstat"><div class="num" style="color:var(--yellow-dark)">'+notExecutedCount+'</div><div class="lbl"><svg width="14" height="14" color="var(--yellow-dark)"><use href="#svg-star"/></svg> 未执行归档</div><div class="sub">观察/等回踩失效</div></div>'+
|
||||
'<div class="hstat"><div class="num" style="color:var(--red)">'+invalidCount+'</div><div class="lbl"><svg width="14" height="14" color="var(--red)"><use href="#svg-shield"/></svg> 信号失效</div><div class="sub">含过期/风控失效</div></div>';
|
||||
var items = Array.isArray(page.items) ? page.items : [];
|
||||
@ -994,14 +994,14 @@ async function loadHistoryRecommendations(reset) {
|
||||
var sigs = Array.isArray(r.signals)?r.signals:[];
|
||||
var sigHtml = sigs.slice(0,4).map(function(s){ return '<span class=\"sig info\">'+cleanDisplayText(s).replace(/^(\\d+H|\\d+m|日线|周线)\\s*/,'').slice(0,12)+'</span>'; }).join('');
|
||||
var duration = daysBetween(r.rec_time, r.last_track_time||r.hit_tp1_time||r.stopped_out_time);
|
||||
var execText = hasPaper ? (paper.status === 'closed' ? '模拟交易已完成' : '模拟交易持有中') : (Number(r.entry_triggered || 0) ? '已触发执行' : '未执行');
|
||||
var execText = hasPaper ? (paper.status === 'closed' ? '策略交易已完成' : '策略交易持有中') : (Number(r.entry_triggered || 0) ? '已触发执行' : '未执行');
|
||||
var signalStateText = hasPaper ? '已执行归档' : (historyArchiveFilter === 'invalid' ? '失效归档' : '未执行归档');
|
||||
var outcomeText = outcome.label;
|
||||
var outcomeDetail = outcome.detail;
|
||||
return '<div class=\"card\">'+
|
||||
'<div class=\"card-bar\"><div class=\"coin-left\"><div class=\"coin-icon\">'+base.slice(0,2).toUpperCase()+'</div><div><span class=\"coin-symbol\">'+base+'</span></div></div><span class=\"hist-result-badge '+resultCls+'\">'+outcomeText+'</span></div>'+
|
||||
'<div class=\"h-pnl-row\">'+(hasPaper ? '<span class=\"price h-entry-price\">$'+fmtN(entryP)+'</span><span class=\"h-arrow neutral\">→</span><span class=\"price h-exit-price '+(paper.status === 'closed' ? resultCls : 'muted')+'\">'+(paper.status === 'closed' ? '$'+fmtN(exitP) : '持有中')+'</span>' : '<span class=\"price h-entry-price muted\">未执行</span><span class=\"h-arrow neutral\">→</span><span class=\"price h-exit-price muted\">失效/归档</span>')+'<span class=\"hist-score-pill '+scoreCls+'\">机会分 '+score+'</span><span class=\"h-duration\">'+duration+'</span></div>'+
|
||||
'<div class=\"hist-metric-row\"><div class=\"hist-metric\"><span class=\"hm-label\">交易阶段</span><span class=\"hm-val '+(hasPaper ? 'win' : 'blue')+'\">'+(hasPaper ? (paper.status === 'closed' ? '已完成模拟交易' : '模拟交易持有中') : '未执行归档')+'</span></div><div class=\"hist-metric\"><span class=\"hm-label\">结果说明</span><span class=\"hm-val blue\">'+outcomeDetail+'</span></div><div class=\"hist-metric\"><span class=\"hm-label\">执行状态</span><span class=\"hm-val '+(Number(r.entry_triggered||0)?'win':'blue')+'\">'+execText+'</span></div></div>'+
|
||||
'<div class=\"hist-metric-row\"><div class=\"hist-metric\"><span class=\"hm-label\">交易阶段</span><span class=\"hm-val '+(hasPaper ? 'win' : 'blue')+'\">'+(hasPaper ? (paper.status === 'closed' ? '已完成策略交易' : '策略交易持有中') : '未执行归档')+'</span></div><div class=\"hist-metric\"><span class=\"hm-label\">结果说明</span><span class=\"hm-val blue\">'+outcomeDetail+'</span></div><div class=\"hist-metric\"><span class=\"hm-label\">执行状态</span><span class=\"hm-val '+(Number(r.entry_triggered||0)?'win':'blue')+'\">'+execText+'</span></div></div>'+
|
||||
'<div class=\"kline-wrap\" id=\"wrap_'+hid+'\"><div class=\"kline-int-bar\"><button class=\"kline-int-btn\" data-int=\"15m\" onclick=\"switchKlineInterval(this);event.stopPropagation()\">15m</button><button class=\"kline-int-btn active\" data-int=\"1h\" onclick=\"switchKlineInterval(this);event.stopPropagation()\">1H</button><button class=\"kline-int-btn\" data-int=\"4h\" onclick=\"switchKlineInterval(this);event.stopPropagation()\">4H</button><button class=\"kline-int-btn\" data-int=\"1d\" onclick=\"switchKlineInterval(this);event.stopPropagation()\">1D</button></div><div class=\"kline-container loading\" id=\"'+hid+'\" data-symbol=\"'+(r.symbol||'')+'\" data-entry-price=\"'+hEntryPrice+'\" data-stop-loss=\"'+hSl+'\" data-tp1=\"'+hTp+'\" data-rec-time=\"'+hEntryTime+'\" data-tp1-time=\"'+hTpTime+'\" data-sl-time=\"'+hSlTime+'\" data-ref-price=\"'+(r.current_price||hEntryPrice||hTp||hSl||0)+'\" data-status=\"'+(r.status||'')+'\" ><div class=\"chart-loading\"><svg class=\"spin\" width=\"16\" height=\"16\" color=\"#8e91a0\"><use href=\"#svg-spinner\"/></svg></div></div></div>'+
|
||||
(sigHtml?'<div class=\"signals-row\">'+sigHtml+'</div>':'')+
|
||||
'<div class=\"card-footer hist-footer\"><span>'+fmtTime(r.rec_time)+'</span><span class=\"card-ver\">'+(r.strategy_version||'')+'</span></div></div>';
|
||||
|
||||
@ -183,7 +183,7 @@ a { color: inherit; text-decoration: none; }
|
||||
<a class="sidebar-link {% if active_nav == 'subscription' %}active{% endif %}" href="/subscription"><svg class="link-icon"><use href="#svg-subscribe"/></svg>订阅</a>
|
||||
<a class="sidebar-link {% if active_nav == 'referral' %}active{% endif %}" href="/referral"><svg class="link-icon"><use href="#svg-referral"/></svg>邀请</a>
|
||||
<div class="sidebar-section-label admin-link" style="display:none">管理员菜单</div>
|
||||
<a class="sidebar-link admin-link {% if active_nav == 'paper_trading' %}active{% endif %}" href="/paper-trading" style="display:none"><svg class="link-icon"><use href="#svg-paper"/></svg>模拟交易</a>
|
||||
<a class="sidebar-link admin-link {% if active_nav == 'paper_trading' %}active{% endif %}" href="/paper-trading" style="display:none"><svg class="link-icon"><use href="#svg-paper"/></svg>策略交易</a>
|
||||
<a class="sidebar-link admin-link {% if active_nav == 'review_center' %}active{% endif %}" href="/review-center" style="display:none"><svg class="link-icon"><use href="#svg-iterate"/></svg>复盘中心</a>
|
||||
<a class="sidebar-link admin-link {% if active_nav == 'pipeline' %}active{% endif %}" href="/pipeline" style="display:none"><svg class="link-icon"><use href="#svg-pipeline"/></svg>链路日志</a>
|
||||
<a class="sidebar-link admin-link {% if active_nav == 'llm_insights' %}active{% endif %}" href="/llm-insights" style="display:none"><svg class="link-icon"><use href="#svg-ai"/></svg>AI 记录</a>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
<button class="btn" onclick="loadConfigs()">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">建议:新闻源、LLM、链上、调度、模拟交易属于系统配置;复盘 meta、learned_rules、策略覆盖属于策略运行时配置。</div>
|
||||
<div class="hint">建议:新闻源、LLM、链上、调度、策略交易属于系统配置;复盘 meta、learned_rules、策略覆盖属于策略运行时配置。</div>
|
||||
<div class="layout">
|
||||
<section class="panel">
|
||||
<div class="panel-head"><div class="panel-title">配置列表</div><div class="panel-note" id="countNote">--</div></div>
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="panel-title">导出内容</div>
|
||||
<div class="list">
|
||||
<div class="item"><b>推荐链路</b><span>recommendation / screening_log / cron_run_log</span></div>
|
||||
<div class="item"><b>交易账本</b><span>paper_orders / paper_trades / events</span></div>
|
||||
<div class="item"><b>策略交易</b><span>挂单 / 持仓 / 交易事件</span></div>
|
||||
<div class="item"><b>复盘迭代</b><span>review / missed / strategy rules</span></div>
|
||||
<div class="item"><b>证据源</b><span>舆情 / 链上 / AI 记录</span></div>
|
||||
<div class="item"><b>运行配置</b><span>策略与系统配置快照</span></div>
|
||||
|
||||
@ -118,7 +118,7 @@ h2 { font-size:26px; font-weight:900; margin:0 0 8px; color:var(--ink); }
|
||||
</div>
|
||||
|
||||
<div class="panel active" id="panel-timeline"><div class="timeline" id="timeline"><div class="loading">加载中…</div></div></div>
|
||||
<div class="panel" id="panel-candidates"><div class="card"><div class="card-title">发现的规律 <span class="badge hold">未达标不发布</span></div><div class="user-tabs-note">这些是系统复盘后发现的可能规律。只有样本、机会表现和稳定性都达标,才会进入线上策略;交易收益以模拟交易页为准。</div><div id="candidates"></div></div></div>
|
||||
<div class="panel" id="panel-candidates"><div class="card"><div class="card-title">发现的规律 <span class="badge hold">未达标不发布</span></div><div class="user-tabs-note">这些是系统复盘后发现的可能规律。只有样本、机会表现和稳定性都达标,才会进入线上策略;交易收益以策略交易页为准。</div><div id="candidates"></div></div></div>
|
||||
<div class="panel" id="panel-dryrun"><div class="card"><div class="card-title">发布预演 <span class="badge hold">只读评估,不改线上策略</span></div><div id="dryrun"></div></div></div>
|
||||
<div class="panel" id="panel-failures"><div class="board"><div class="card"><div class="card-title">主要失败原因</div><div id="failureSummary"></div></div><div class="card"><div class="card-title">失败样本</div><div id="failures"></div></div></div></div>
|
||||
<div class="panel" id="panel-versions"><div class="card"><div class="card-title">版本机会表现</div><div id="versions"></div></div></div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -10,7 +10,7 @@
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>复盘中心</h1>
|
||||
<p>把机会发现、模拟交易收益、多源证据和策略迭代拆开看。收益只看模拟交易;机会归档只看发现和确认质量。</p>
|
||||
<p>把机会发现、策略交易收益、多源证据和策略迭代拆开看。收益只看策略交易;机会归档只看发现和确认质量。</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<select class="select" id="daysSel" onchange="loadAll()">
|
||||
@ -29,7 +29,7 @@
|
||||
<div class="panel-body" id="opportunityPanel"><div class="loading">加载中...</div></div>
|
||||
</section>
|
||||
<section class="panel">
|
||||
<div class="panel-head"><div><div class="panel-title">模拟交易复盘</div><div class="panel-desc" id="paperDef">--</div></div><span class="badge ok">唯一收益口径</span></div>
|
||||
<div class="panel-head"><div><div class="panel-title">策略交易复盘</div><div class="panel-desc" id="paperDef">--</div></div><span class="badge ok">唯一收益口径</span></div>
|
||||
<div class="panel-body" id="paperPanel"><div class="loading">加载中...</div></div>
|
||||
</section>
|
||||
<section class="panel">
|
||||
@ -52,11 +52,11 @@
|
||||
var API='';function $(id){return document.getElementById(id)}function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]})}function num(v,d){return Number(v||0).toFixed(d==null?0:d)}function pct(v){return (Number(v||0)>=0?'+':'')+Number(v||0).toFixed(2)+'%'}function usd(v){v=Number(v||0);return (v>=0?'+':'-')+'$'+Math.abs(v).toFixed(2)}function time(t){if(!t)return '--';var d=new Date(t);if(isNaN(d.getTime()))return t;return (d.getMonth()+1)+'/'+d.getDate()+' '+('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2)}
|
||||
function kpis(items){return '<div class="kpis">'+items.map(function(x){return '<div class="kpi"><span>'+esc(x[0])+'</span><b class="'+(x[2]||'')+'">'+esc(x[1])+'</b></div>'}).join('')+'</div>'}
|
||||
function rows(items,label,value,sub){items=items||[];if(!items.length)return '<div class="empty">暂无数据</div>';return '<div class="list">'+items.map(function(x){return '<div class="row"><div class="row-main"><div class="row-title">'+esc(label(x))+'</div><div class="row-sub">'+esc(sub?sub(x):'')+'</div></div><div class="value">'+esc(value?value(x):'')+'</div></div>'}).join('')+'</div>'}
|
||||
function renderOpportunity(o){var s=o.summary||{};$('oppDef').textContent=o.definition||'';$('opportunityPanel').innerHTML=kpis([['机会样本',s.total_opportunities||0,'blue'],['可买/等回踩',(s.buy_now_count||0)+' / '+(s.wait_pullback_count||0),''],['模拟执行',s.paper_executed_count||0,'green'],['漏选爆发',s.missed_explosion_count||0,'red'],['有效复盘',s.effective_review_count||0,''],['机会命中',num(s.opportunity_hit_rate,1)+'%','green'],['观察样本',s.observe_count||0,''],['失效样本',s.invalid_count||0,'red']])+'<div class="split"><div><div class="mini-title">状态分布</div>'+rows(o.status_distribution,function(x){return x.name||'--'},function(x){return x.count})+'</div><div><div class="mini-title">复盘结果</div>'+rows(o.outcome_distribution,function(x){return x.name||'--'},function(x){return x.count})+'</div></div>'}
|
||||
function renderOpportunity(o){var s=o.summary||{};$('oppDef').textContent=o.definition||'';$('opportunityPanel').innerHTML=kpis([['机会样本',s.total_opportunities||0,'blue'],['可买/等回踩',(s.buy_now_count||0)+' / '+(s.wait_pullback_count||0),''],['策略执行',s.paper_executed_count||0,'green'],['漏选爆发',s.missed_explosion_count||0,'red'],['有效复盘',s.effective_review_count||0,''],['机会命中',num(s.opportunity_hit_rate,1)+'%','green'],['观察样本',s.observe_count||0,''],['失效样本',s.invalid_count||0,'red']])+'<div class="split"><div><div class="mini-title">状态分布</div>'+rows(o.status_distribution,function(x){return x.name||'--'},function(x){return x.count})+'</div><div><div class="mini-title">复盘结果</div>'+rows(o.outcome_distribution,function(x){return x.name||'--'},function(x){return x.count})+'</div></div>'}
|
||||
function renderPaper(p){var s=p.summary||{};$('paperDef').textContent=p.definition||'';$('paperPanel').innerHTML=kpis([['当前余额','$'+num(s.current_balance_usdt,2),'blue'],['总收益',usd(s.total_pnl_usdt),Number(s.total_pnl_usdt||0)>=0?'green':'red'],['账户收益率',pct(s.account_total_return_pct),Number(s.account_total_return_pct||0)>=0?'green':'red'],['胜率',num(s.win_rate,1)+'%',''],['开仓/平仓',(s.open_count||0)+' / '+(s.closed_count||0),''],['已实现',usd(s.realized_pnl_usdt),Number(s.realized_pnl_usdt||0)>=0?'green':'red'],['未实现',usd(s.open_unrealized_pnl_usdt),Number(s.open_unrealized_pnl_usdt||0)>=0?'green':'red'],['累计杠杆',num(s.cumulative_leverage,2)+'x','']])+'<div class="split"><div><div class="mini-title">退出原因</div>'+rows(p.exit_reasons,function(x){return x.name||'--'},function(x){return x.count})+'</div><div><div class="mini-title">执行事件</div>'+rows(p.event_types,function(x){return x.name||'--'},function(x){return x.count})+'</div></div>'}
|
||||
function renderEvidence(e){var s=e.summary||{};$('evidenceDef').textContent=e.definition||'';$('evidencePanel').innerHTML=kpis([['新闻事件',s.news_count||0,'blue'],['有效舆情',s.actionable_news_count||0,''],['链上信号',s.onchain_signal_count||0,'blue'],['高置信链上',s.high_confidence_onchain_count||0,'green'],['原始链上',s.raw_onchain_count||0,''],['已映射原始',s.mapped_raw_onchain_count||0,'green'],['LLM 调用',s.llm_runs||0,''],['LLM 成功',s.llm_success_count||0,'green']])+'<div class="split"><div><div class="mini-title">链上信号</div>'+rows(e.onchain_signals,function(x){return x.name||'--'},function(x){return x.count})+'</div><div><div class="mini-title">舆情决策</div>'+rows(e.news_decisions,function(x){return x.name||'未处理'},function(x){return x.count})+'</div></div>'}
|
||||
function renderIteration(i){var s=i.summary||{};$('iterationDef').textContent=i.definition||'';$('iterationPanel').innerHTML=kpis([['迭代记录',s.iteration_count||0,'blue'],['候选规则',s.candidate_count||0,''],['灰度规则',s.gray_count||0,'green'],['生效规则',s.active_count||0,'green']])+'<div class="row" style="margin-bottom:10px"><div class="row-main"><div class="row-title">最新发布结论:'+esc(s.latest_release_decision||'hold')+'</div><div class="row-sub">'+esc(s.latest_release_reason||'暂无发布说明')+'</div></div><div class="value">闸门</div></div><div class="split"><div><div class="mini-title">发布决策</div>'+rows(i.release_decisions,function(x){return x.name||'--'},function(x){return x.count})+'</div><div><div class="mini-title">候选状态</div>'+rows(i.candidate_status,function(x){return x.name||'--'},function(x){return x.count})+'</div></div>'}
|
||||
function renderRecent(d){var opp=(d.opportunity&&d.opportunity.missed_explosions)||[], trades=(d.paper_trading&&d.paper_trading.recent_trades)||[], news=(d.evidence&&d.evidence.recent_news)||[], chain=(d.evidence&&d.evidence.recent_onchain)||[];$('recentPanel').innerHTML='<div class="split"><div><div class="mini-title">最近模拟交易</div>'+rows(trades.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.status||'--')},function(x){return x.status==='closed'?pct(x.realized_pnl_pct):pct(x.pnl_pct)},function(x){return time(x.opened_at)+' · '+(x.exit_reason||x.source_status||'')})+'</div><div><div class="mini-title">漏选爆发</div>'+rows(opp.slice(0,8),function(x){return x.symbol||'--'},function(x){return pct(x.gain_pct)},function(x){return time(x.detect_time)+' · '+(x.reason_missed||'')})+'</div><div><div class="mini-title">舆情事件</div>'+rows(news.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.title||'--')},function(x){return x.importance||'--'},function(x){return time(x.detected_at)+' · '+(x.source||'')+' · '+(x.decision||'未处理')})+'</div><div><div class="mini-title">链上信号</div>'+rows(chain.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.signal_label||x.signal_code||'--')},function(x){return x.severity||x.confidence||'--'},function(x){return time(x.detected_at)+' · '+(x.source||'')+' · '+(x.direction||'')})+'</div></div>'}
|
||||
function renderRecent(d){var opp=(d.opportunity&&d.opportunity.missed_explosions)||[], trades=(d.paper_trading&&d.paper_trading.recent_trades)||[], news=(d.evidence&&d.evidence.recent_news)||[], chain=(d.evidence&&d.evidence.recent_onchain)||[];$('recentPanel').innerHTML='<div class="split"><div><div class="mini-title">最近策略交易</div>'+rows(trades.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.status||'--')},function(x){return x.status==='closed'?pct(x.realized_pnl_pct):pct(x.pnl_pct)},function(x){return time(x.opened_at)+' · '+(x.exit_reason||x.source_status||'')})+'</div><div><div class="mini-title">漏选爆发</div>'+rows(opp.slice(0,8),function(x){return x.symbol||'--'},function(x){return pct(x.gain_pct)},function(x){return time(x.detect_time)+' · '+(x.reason_missed||'')})+'</div><div><div class="mini-title">舆情事件</div>'+rows(news.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.title||'--')},function(x){return x.importance||'--'},function(x){return time(x.detected_at)+' · '+(x.source||'')+' · '+(x.decision||'未处理')})+'</div><div><div class="mini-title">链上信号</div>'+rows(chain.slice(0,8),function(x){return (x.symbol||'--')+' · '+(x.signal_label||x.signal_code||'--')},function(x){return x.severity||x.confidence||'--'},function(x){return time(x.detected_at)+' · '+(x.source||'')+' · '+(x.direction||'')})+'</div></div>'}
|
||||
function render(d){$('principles').innerHTML=(d.principles||[]).map(function(x){return '<div class="principle">'+esc(x)+'</div>'}).join('');renderOpportunity(d.opportunity||{});renderPaper(d.paper_trading||{});renderEvidence(d.evidence||{});renderIteration(d.iteration||{});renderRecent(d)}
|
||||
async function loadAll(){try{var days=$('daysSel').value;var d=await (await fetch(API+'/api/review-center/dashboard?days='+days+'&_ts='+Date.now(),{cache:'no-store'})).json();render(d)}catch(e){['principles','opportunityPanel','paperPanel','evidencePanel','iterationPanel','recentPanel'].forEach(function(id){$(id).innerHTML='<div class="empty">加载失败</div>'})}}
|
||||
loadAll();
|
||||
|
||||
@ -10,16 +10,16 @@
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>策略归因</h1>
|
||||
<p>看清哪些因子、环境和证据源更容易把机会推进到可执行,以及是否真的进入模拟交易。这里不把机会价格波动当作交易收益。</p>
|
||||
<p>看清哪些因子、环境和证据源更容易把机会推进到可执行,以及是否真的进入策略交易。这里不把机会价格波动当作交易收益。</p>
|
||||
</div>
|
||||
<span class="pill">收益只来自模拟交易</span>
|
||||
<span class="pill">收益只来自策略交易</span>
|
||||
</div>
|
||||
<div class="kpis" id="metrics"><div class="loading">加载中...</div></div>
|
||||
<div class="note" id="definition">策略归因只看机会转化和模拟交易转化;收益只来自 paper_trades。</div>
|
||||
<div class="note" id="definition">策略归因只看机会转化和策略交易转化;收益只来自交易账本。</div>
|
||||
<div class="flow">
|
||||
<div class="flow-step"><b>机会样本</b><span>系统发现并入库的机会,不等于交易。</span></div>
|
||||
<div class="flow-step"><b>可执行转化</b><span>确认层输出现在可买或等回踩。</span></div>
|
||||
<div class="flow-step"><b>模拟执行</b><span>只有 buy_now 被 paper trading 开仓才进入收益账本。</span></div>
|
||||
<div class="flow-step"><b>策略执行</b><span>只有 buy_now 被策略交易开仓才进入收益账本。</span></div>
|
||||
<div class="flow-step"><b>证据归因</b><span>链上、舆情、技术因子只做贡献分析。</span></div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
@ -34,7 +34,7 @@
|
||||
<script>
|
||||
function esc(v){return String(v==null?'':v).replace(/[&<>"']/g,function(c){return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]})}
|
||||
function pct(x){return Number(x||0).toFixed(1)+'%'}function usd(x){x=Number(x||0);return (x>=0?'+':'-')+'$'+Math.abs(x).toFixed(2)}
|
||||
function table(rows,key){if(!rows||!rows.length)return'<div class="empty">暂无数据</div>';return '<div class="table-wrap"><table class="table"><thead><tr><th>归因项</th><th>机会</th><th>可执行</th><th>现买</th><th>模拟执行</th><th>执行转化</th><th>模拟胜率</th><th>模拟已实现</th></tr></thead><tbody>'+rows.map(function(r){return '<tr><td><span class="tag">'+esc(r[key]||'--')+'</span></td><td class="num">'+esc(r.opportunity_count||0)+'</td><td>'+esc(r.actionable_count||0)+'</td><td>'+esc(r.buy_now_count||0)+'</td><td>'+esc(r.paper_trade_count||0)+'</td><td class="num">'+pct(r.actionable_conversion_pct)+'</td><td class="num">'+pct(r.paper_win_rate_pct)+'</td><td class="'+(Number(r.paper_realized_pnl_usdt||0)>=0?'green':'red')+'">'+usd(r.paper_realized_pnl_usdt)+'</td></tr>'}).join('')+'</tbody></table></div>'}
|
||||
async function load(){try{var d=await (await fetch('/api/strategy/insights?_ts='+Date.now(),{cache:'no-store'})).json();var o=d.overview||{};definition.textContent=o.definition||'策略归因只看机会转化和模拟交易转化。';metrics.innerHTML=[['机会样本',o.total_opportunities||0,'blue'],['可执行机会',o.actionable_count||0,''],['现在可买',o.buy_now_count||0,'green'],['模拟执行',o.paper_trade_count||0,'green'],['模拟胜率',pct(o.paper_win_rate_pct),''],['已实现收益',usd(o.paper_realized_pnl_usdt),Number(o.paper_realized_pnl_usdt||0)>=0?'green':'red']].map(function(x){return '<div class="kpi"><span>'+esc(x[0])+'</span><b class="'+(x[2]||'')+'">'+esc(x[1])+'</b></div>'}).join('');factorPerf.innerHTML=table(d.factor_attribution,'factor');envPerf.innerHTML=table(d.market_environment,'environment');evidencePerf.innerHTML=table(d.evidence_attribution,'evidence');versionPerf.innerHTML=table(d.version_performance,'strategy_version')}catch(e){metrics.innerHTML='<div class="empty">加载失败</div>'}}load();
|
||||
function table(rows,key){if(!rows||!rows.length)return'<div class="empty">暂无数据</div>';return '<div class="table-wrap"><table class="table"><thead><tr><th>归因项</th><th>机会</th><th>可执行</th><th>现买</th><th>策略执行</th><th>执行转化</th><th>策略胜率</th><th>策略已实现</th></tr></thead><tbody>'+rows.map(function(r){return '<tr><td><span class="tag">'+esc(r[key]||'--')+'</span></td><td class="num">'+esc(r.opportunity_count||0)+'</td><td>'+esc(r.actionable_count||0)+'</td><td>'+esc(r.buy_now_count||0)+'</td><td>'+esc(r.paper_trade_count||0)+'</td><td class="num">'+pct(r.actionable_conversion_pct)+'</td><td class="num">'+pct(r.paper_win_rate_pct)+'</td><td class="'+(Number(r.paper_realized_pnl_usdt||0)>=0?'green':'red')+'">'+usd(r.paper_realized_pnl_usdt)+'</td></tr>'}).join('')+'</tbody></table></div>'}
|
||||
async function load(){try{var d=await (await fetch('/api/strategy/insights?_ts='+Date.now(),{cache:'no-store'})).json();var o=d.overview||{};definition.textContent=o.definition||'策略归因只看机会转化和策略交易转化。';metrics.innerHTML=[['机会样本',o.total_opportunities||0,'blue'],['可执行机会',o.actionable_count||0,''],['现在可买',o.buy_now_count||0,'green'],['策略执行',o.paper_trade_count||0,'green'],['策略胜率',pct(o.paper_win_rate_pct),''],['已实现收益',usd(o.paper_realized_pnl_usdt),Number(o.paper_realized_pnl_usdt||0)>=0?'green':'red']].map(function(x){return '<div class="kpi"><span>'+esc(x[0])+'</span><b class="'+(x[2]||'')+'">'+esc(x[1])+'</b></div>'}).join('');factorPerf.innerHTML=table(d.factor_attribution,'factor');envPerf.innerHTML=table(d.market_environment,'environment');evidencePerf.innerHTML=table(d.evidence_attribution,'evidence');versionPerf.innerHTML=table(d.version_performance,'strategy_version')}catch(e){metrics.innerHTML='<div class="empty">加载失败</div>'}}load();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -94,12 +94,12 @@ def test_chat_rejects_paper_trading_questions(monkeypatch):
|
||||
client = TestClient(web_server.app)
|
||||
client.cookies.set("altcoin_session", token)
|
||||
|
||||
resp = client.post("/api/chat/send", json={"message": "帮我看一下模拟交易开仓和收益"})
|
||||
resp = client.post("/api/chat/send", json={"message": "帮我看一下策略交易开仓和收益"})
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["intent"] == "restricted"
|
||||
assert "内部模拟交易数据不可在智能问答中直接访问" in data["answer"]["summary"]
|
||||
assert "内部策略交易数据不可在智能问答中直接访问" in data["answer"]["summary"]
|
||||
assert data["answer"]["evidence"] == []
|
||||
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ def test_paper_trading_admin_can_access_page_and_api():
|
||||
summary = client.get("/api/paper-trading/summary")
|
||||
|
||||
assert page.status_code == 200
|
||||
assert "模拟交易" in page.text
|
||||
assert "策略交易" in page.text
|
||||
assert summary.status_code == 200
|
||||
assert "account_equity_usdt" in summary.json()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user