diff --git a/static/app.html b/static/app.html index f53fb78..e4420b8 100644 --- a/static/app.html +++ b/static/app.html @@ -106,7 +106,7 @@ .level-badge.short_swing { color: #187574; background: rgba(15,188,176,.12); border-color: rgba(15,188,176,.20); } .level-badge.structure_watch { color: var(--yellow-dark); background: var(--yellow-light); border-color: rgba(252,185,0,.22); } .level-badge.theme_trend { color: var(--blue); background: rgba(66,98,255,.07); border-color: rgba(66,98,255,.12); } -.signal-level-strip { margin: 0 18px 10px; border: 1px solid var(--hairline-soft); background: linear-gradient(180deg, rgba(248,250,252,.96), rgba(255,255,255,.98)); border-radius: var(--radius-lg); padding: 10px 12px; display: grid; grid-template-columns: minmax(118px,.78fr) minmax(150px,1fr) minmax(150px,1fr); gap: 10px; align-items: center; } +.signal-level-strip { margin: 0 18px 10px; border: 1px solid var(--hairline-soft); background: linear-gradient(180deg, rgba(248,250,252,.96), rgba(255,255,255,.98)); border-radius: var(--radius-lg); padding: 10px 12px; display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 10px; align-items: center; } .signal-level-title { display:flex; align-items:center; gap:7px; min-width:0; } .signal-level-dot { width:8px; height:8px; border-radius:50%; background: var(--blue); box-shadow: 0 0 0 4px rgba(66,98,255,.08); flex-shrink:0; } .signal-level-strip.intraday_breakout .signal-level-dot { background: var(--green); box-shadow:0 0 0 4px rgba(0,180,115,.10); } @@ -658,14 +658,6 @@ function renderRecCard(r) { var chgSinceRec = r.current_price && r.entry_price && r.entry_price > 0 ? ((r.current_price - r.entry_price) / r.entry_price * 100) : 0; var isOld = ageHours > 12; var hasSurged = chgSinceRec > 3; - var actionBadge = ''; - if (isBuy && isOld && hasSurged) actionBadge = '追高风险(+'+chgSinceRec.toFixed(1)+'%)'; - else if (isBuy && isOld) actionBadge = '信号偏弱'; - else if (!isOld && hasSurged && isBuy) actionBadge = '追高风险(+'+chgSinceRec.toFixed(1)+'%)'; - else if (!isOld && (r.rec_score||0) < 50 && isBuy) actionBadge = '信号偏弱'; - else if (isBuy) actionBadge = '入场窗口'; - else actionBadge = ''+phase.label+''; - if (isWeakObserve) actionBadge = '弱观察'; var ePrice = r.entry_price||''; var sl = (r.stop_loss&&r.stop_loss>0) ? r.stop_loss : ''; var tp = (r.tp1&&r.tp1>0) ? r.tp1 : ''; @@ -717,10 +709,10 @@ function renderRecCard(r) { } var weakNoteHtml = isWeakObserve ? '
'+cleanDisplayText(r.observe_reason || '信号强度不足,仅保留为低优先级观察,不构成实时机会。')+'
' : ''; var decisionCls = isBuy ? 'buy' : (isWait ? 'wait' : (isWeakObserve ? 'weak' : 'observe')); - var decisionTitle = cleanDisplayText(r.execution_label || phase.label); - var decisionFocus = isBuy ? ('现价 '+fmtP(price)) : (isWait ? ('等 '+fmtP(entryRef)) : (isWeakObserve ? '低优先级观察' : '等待确认')); - var decisionReason = cleanDisplayText(isBuy ? (entryWindowSummary() || r.execution_reason || '入场窗口有效') : (r.execution_reason || (isWait ? '现价不追,等回踩价附近再评估' : (r.observe_reason || r.state_reason || '未形成入场窗口')))); - var decisionHtml = '
最终建议'+decisionTitle+'
'+decisionFocus+''+decisionReason+'
'; + var decisionTitle = isBuy ? '现在可买' : (isWait ? '等回踩,不追高' : (isWeakObserve ? '弱观察' : '观察')); + var decisionFocus = isBuy ? ('现价 '+fmtP(price)) : (isWait ? ('参考 '+fmtP(entryRef)) : (isWeakObserve ? '低优先级观察' : '等待确认')); + var decisionReason = cleanDisplayText(isBuy ? (entryWindowSummary() || r.execution_reason || '入场窗口有效') : (r.execution_reason || (isWait ? '当前不追,等待回踩价附近再评估' : (r.observe_reason || r.state_reason || '未形成入场窗口')))); + var decisionHtml = '
当前结论'+decisionTitle+'
'+decisionFocus+''+decisionReason+'
'; var aiInsightHtml = ''; var aiInsight = r.llm_insight && r.llm_insight.content ? r.llm_insight.content : null; function hasAiText(v) { @@ -751,14 +743,14 @@ function renderRecCard(r) { return '多周期'; } var levelBasis = Array.isArray(ep.plan_basis) ? ep.plan_basis.slice(0,2).join(' · ') : ''; - var signalLevelHtml = '
信号级别'+cleanDisplayText(levelLabel)+'
捕捉周期'+cleanDisplayText(horizon || levelFrameText(levelKey))+''+cleanDisplayText(levelFrameText(levelKey))+'
确认方式'+cleanDisplayText(entryModel || '等待当前触发')+''+cleanDisplayText(levelBasis || phase.label)+'
'; + var signalLevelHtml = '
机会级别'+cleanDisplayText(levelLabel)+''+cleanDisplayText(horizon || levelFrameText(levelKey))+'
触发门槛'+cleanDisplayText(entryModel || '等待当前触发')+''+cleanDisplayText(levelBasis || phase.short || '当前触发 + 风险边界')+'
'; var entryPlanHtml = ''; if (isTradePlan) { - entryPlanHtml = '
机会级别'+cleanDisplayText(levelLabel)+''+cleanDisplayText(horizon || phase.short)+'
'+entryLabel+''+fmtP(entryRef)+''+cleanDisplayText(entryModel || '触发/计划价')+'
风险边界'+fmtP(riskLine)+''+cleanDisplayText(stopModel)+'
上方空间'+(upsidePct?('+'+upsidePct.toFixed(1)+'%'):'--')+''+cleanDisplayText(tpModel)+' · '+fmtP(spaceRef)+'
'; + entryPlanHtml = '
入场参考'+fmtP(entryRef)+''+cleanDisplayText(entryLabel+' · '+(entryModel || '触发/计划价'))+'
风险边界'+fmtP(riskLine)+''+cleanDisplayText(stopModel)+'
上方空间'+(upsidePct?('+'+upsidePct.toFixed(1)+'%'):'--')+''+cleanDisplayText(tpModel)+' · '+fmtP(spaceRef)+'
持有阶段'+cleanDisplayText(horizon || phase.short)+''+cleanDisplayText(levelLabel)+'
'; } else { - entryPlanHtml = '
机会级别'+cleanDisplayText(levelLabel)+''+cleanDisplayText(horizon || '观察池候选')+'
当前参考'+fmtP(price)+'不是入场价
确认条件待触发'+cleanDisplayText(entryModel || '需15m/1H当前信号')+'
绩效口径不计入未成交易推荐
'; + entryPlanHtml = '
当前参考'+fmtP(price)+'不是入场价
观察重点待触发'+cleanDisplayText(entryModel || '需15m/1H当前信号')+'
绩效口径不计入未成交易推荐
观察阶段'+cleanDisplayText(horizon || '观察池候选')+''+cleanDisplayText(levelLabel)+'
'; } - return '
'+base.slice(0,2).toUpperCase()+'
'+base+'
'+cleanDisplayText(levelLabel)+''+actionBadge+''+score+''+st.label+'
$'+priceFmt+''+changeHtml+'
'+decisionHtml+signalLevelHtml+onchainHtml+aiInsightHtml+'
'+(isWeakObserve ? weakNoteHtml : entryPlanHtml)+(sigHtml?'
'+sigHtml+'
':'')+'
'; + return '
'+base.slice(0,2).toUpperCase()+'
'+base+'
'+score+''+st.label+'
$'+priceFmt+''+changeHtml+'
'+decisionHtml+signalLevelHtml+onchainHtml+aiInsightHtml+'
'+(isWeakObserve ? weakNoteHtml : entryPlanHtml)+(sigHtml?'
'+sigHtml+'
':'')+'
'; } catch (e) { console.error('renderRecCard hard fail', r && r.symbol, e); return renderLiveFallbackCard(r); @@ -1002,12 +994,14 @@ async function loadHistoryRecommendations(reset) { var sigs = Array.isArray(r.signals)?r.signals:[]; var sigHtml = sigs.slice(0,4).map(function(s){ return ''+cleanDisplayText(s).replace(/^(\\d+H|\\d+m|日线|周线)\\s*/,'').slice(0,12)+''; }).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 '
'+ - '
'+base.slice(0,2).toUpperCase()+'
'+base+'
'+outcome.label+'
'+ + '
'+base.slice(0,2).toUpperCase()+'
'+base+'
'+outcomeText+'
'+ '
'+(hasPaper ? '$'+fmtN(entryP)+''+(paper.status === 'closed' ? '$'+fmtN(exitP) : '持有中')+'' : '未执行失效/归档')+'评分 '+score+' · '+st.label+''+duration+'
'+ - '
归档状态'+signalStateText+'
执行状态'+execText+'
归档原因'+exitMode+'
'+ + '
交易阶段'+(hasPaper ? (paper.status === 'closed' ? '已完成模拟交易' : '模拟交易持有中') : '未执行归档')+'
结果说明'+outcomeDetail+'
执行状态'+execText+'
'+ '
'+ (sigHtml?'
'+sigHtml+'
':'')+ '
';