diff --git a/server.py b/server.py index 462a4922..8226015b 100644 --- a/server.py +++ b/server.py @@ -1158,42 +1158,32 @@ def api_stock(code): @app.route("/api/market") def api_market(): - """市场观察""" + """市场观察(2026-08-22 重构:板块快照 + A股/港股温区 regime)""" try: from mofin_db import get_conn, query_latest_market conn = get_conn() data = query_latest_market(conn) + # 当前市场温区(每市场取最新一条 market_regime) + try: + rows = conn.execute(""" + SELECT mr.market, mr.date, mr.regime, mr.close, + mr.above_ma20, mr.ma20_slope, mr.roc, mr.adx + FROM market_regime mr + JOIN (SELECT market, MAX(date) md FROM market_regime GROUP BY market) t + ON mr.market = t.market AND mr.date = t.md + ORDER BY mr.market + """).fetchall() + data["regimes"] = [dict(r) for r in rows] + except Exception: + data["regimes"] = [] conn.close() - if data and data.get("sectors"): + if data and (data.get("sectors") or data.get("regimes")): return jsonify(data) except Exception: pass return jsonify(_load_json(DATA_DIR / "market.json", {})) -# ── 信号API(新增) ───────────────────────────────────── - - -@app.route("/api/signals") -def api_signals(): - """最近信号 + 小果分析""" - try: - from mofin_db import get_conn - conn = get_conn() - signals = conn.execute(""" - SELECT sn.id, sn.sector, sn.overall_sentiment, - sn.summary, sn.source, sn.created_at, - ss.signal_type, ss.severity - FROM signal_news sn - LEFT JOIN sector_signals ss ON sn.signal_id = ss.id - ORDER BY sn.id DESC LIMIT 20 - """).fetchall() - conn.close() - return jsonify([dict(r) for r in signals]) - except Exception as e: - return jsonify({"error": str(e)}), 500 - - @app.route("/api/xiaoguo-scan") def api_xiaoguo_scan(): """小果扫描统计(2026-08-19 小果已废弃收尸,返回空)""" diff --git a/static/index.html b/static/index.html index b0bb4a83..7062c495 100644 --- a/static/index.html +++ b/static/index.html @@ -111,7 +111,6 @@ body { background: #0f0f13; color: #e2e8f0; } - @@ -126,7 +125,6 @@ body { background: #0f0f13; color: #e2e8f0; } - @@ -313,7 +311,6 @@ function renderTab(name) { else if (name === 'evaluation') renderEvaluation(); else if (name === 'prompts') renderPrompts(); else if (name === 'market') renderMarket(); - else if (name === 'signals') renderSignals(); else if (name === 'health') renderHealth(); else if (name === 'principles') renderPrinciples(); else if (name === 'watch') renderWatch(); @@ -563,6 +560,7 @@ function renderWatchlist() { window._fullAnalysisMap = {}; stocks.forEach(s => { window._fullAnalysisMap[s.code] = s.full_analysis || ''; }); el.innerHTML = ` +
自选 · ${stocks.length} 只 @@ -619,59 +617,19 @@ function renderWatchlist() {
`; + loadWatchlistCandidates(); } -// ── Market Tab ── -let marketRefreshInterval = null; - -function refreshMarketData() { - const el = document.getElementById('tab-market'); - if (!el || el.classList.contains('hidden')) return; - Promise.all([ - fetchJSON('/api/market'), - fetchJSON('/api/signals'), - fetchJSON('/api/candidates'), - ]).then(([mkt, signals, candidates]) => { - const sectors = mkt.sectors || []; - const sigs = signals || []; - const cands = candidates || []; - const trend = sigs.filter(s => s.source && s.source !== 'xiaoguo').concat(sigs.filter(s => s.source === 'market_scanner')); - el.innerHTML = ` -
- 🌐 市场全景 ?§?§ -
-
-
-

🏭 行业热点

- ${sectors.length ? sectors.slice(0,8).map(s => ` -
- ${s.name} - ${(s.change||0)>=0?'+':''}${(s.change||0).toFixed(1)}% -
- `).join('') : '
暂无
'} -
-
-

📡 趋势信号 (${trend.length})

- ${trend.length ? trend.slice(0,6).map(s => ` -
- ${s.overall_sentiment||'中性'} - ${s.sector} ${(s.summary||'').slice(0,60)} -
- `).join('') : '
暂无
'} -
-
-

📊 信号汇总 (${sigs.length})

- ${sigs.length ? sigs.slice(0,6).map(s => ` -
- ${s.overall_sentiment||'中性'} - ${s.sector} ${(s.summary||'').slice(0,60)} -
- `).join('') : '
暂无
'} -
-
-
+// ── 候选池(2026-08-22 从市场Tab迁入:候选→提拔→自选是同一管道)── +async function loadWatchlistCandidates() { + const box = document.getElementById('watchlist-candidates'); + if (!box) return; + try { + const cands = await fetchJSON('/api/candidates') || []; + if (!cands.length) { box.innerHTML = ''; return; } + box.innerHTML = ` +

🎯 主力建仓候选 (${cands.length})

- ${cands.length ? `
@@ -704,19 +662,74 @@ function refreshMarketData() { }).join('')}
- ` : '
暂无候选(开盘后自动扫描)
'} +
`; + } catch(e) { box.innerHTML = ''; } +} + +// ── Market Tab ── +let marketRefreshInterval = null; + +function refreshMarketData() { + const el = document.getElementById('tab-market'); + if (!el || el.classList.contains('hidden')) return; + Promise.all([ + fetchJSON('/api/market'), + ]).then(([mkt]) => { + const sectors = mkt.sectors || []; + const regimes = mkt.regimes || []; + const REG_ICON = {trend_up:'📈', choppy:'〰️', trend_down:'📉'}; + const REG_NAME = {trend_up:'趋势市', choppy:'震荡市', trend_down:'下跌市'}; + const regColor = s => s==='trend_up' ? 'text-emerald-400' : s==='choppy' ? 'text-amber-400' : 'text-red-400'; + const MKT_NAME = {a:'A股', hk:'港股'}; + const MKT_ORDER = {a:0, hk:1}; + regimes.sort((x,y) => (MKT_ORDER[x.market] ?? 9) - (MKT_ORDER[y.market] ?? 9)); + const moodName = {bull:'偏多', bear:'偏空', neutral:'中性'}[mkt.mood] || ''; + el.innerHTML = ` +
+ 🌐 市场全景 ?§ + ${mkt.timestamp||''}${moodName?' · '+moodName:''}${mkt.up_ratio!=null?' · 上涨占比 '+mkt.up_ratio+'%':''}
+
+ ${regimes.length ? regimes.map(r => ` +
+
+ ${REG_ICON[r.regime]||'🌐'} + ${MKT_NAME[r.market]||r.market} + ${REG_NAME[r.regime]||r.regime} + ${r.date||''} +
+
+
指数收盘${r.close!=null?r.close:'—'}
+
站上MA20${r.above_ma20?'是':'否'}
+
MA20斜率${r.ma20_slope!=null?r.ma20_slope.toFixed(3):'—'}
+
ROC${r.roc!=null?r.roc.toFixed(2):'—'}
+
ADX${r.adx!=null?r.adx.toFixed(1):'—'}
+
+
`).join('') : '
暂无温区数据(market_thermometer_daily 每日生成)
'} +
+
+

🏭 行业热点

+ ${sectors.length ? sectors.slice(0,8).map(s => ` +
+ ${s.name}${s.lead_stock||''} + ${(s.change_pct||0)>=0?'+':''}${(s.change_pct||0).toFixed(1)}% +
+ `).join('') : '
暂无
'} +
+

📋 全部板块排行(${sectors.length})

-
+
${sectors.map(s => `
${s.name} - ${(s.change||0)>=0?'+':''}${(s.change||0).toFixed(1)}% + ${(s.change_pct||0)>=0?'+':''}${(s.change_pct||0).toFixed(1)}%
`).join('')}
`; + }).catch(e => { + el.innerHTML = `
加载失败: ${e.message}
`; }); } @@ -1136,69 +1149,6 @@ function closePromptModal() { document.getElementById('promptModal').classList.remove('flex'); } -// ── 信号页 ── -async function renderSignals() { - const el = document.getElementById('tab-signals'); - el.innerHTML = '
加载中…
'; - try { - const [signals, candidates] = await Promise.all([ - fetchJSON('/api/signals'), - fetchJSON('/api/candidates') - ]); - let html = ` -
- 🔍 信号 ?§ -
-
-
-
今日信号
-
- 📡 趋势信号: ${signals.length || 0} 条 - 🎯 候选股: ${candidates.length || 0} 只 -
-
-
-
全市场扫描
-
- 📋 候选池: ${candidates.length || 0} 只 - 🏆 已入自选: ${candidates.filter(c => c.promoted).length || 0} 只 -
-
-
`; - - if (!signals || signals.length === 0) { - html += '
暂无信号
'; - el.innerHTML = html; - return; - } - - html += '
'; - for (const s of signals) { - const sev = s.severity || '-'; - const sevColor = sev === 'high' ? 'text-red-400' : sev === 'medium' ? 'text-yellow-400' : 'text-green-400'; - const sent = s.overall_sentiment || '-'; - const sentColor = sent === '利好' ? 'text-green-400' : sent === '利空' ? 'text-red-400' : 'text-slate-400'; - const source = s.source || 'trend'; - const sourceIcon = source === 'market_scanner' ? '🎯' : source === 'macro' ? '🌐' : '📡'; - html += ` -
-
- ${sev.toUpperCase()} - ${sent} - ${sourceIcon} ${source} - ${s.created_at ? s.created_at.slice(5,16) : ''} -
-
${s.sector || '-'}
-
${(s.summary || '').slice(0,120)}
-
`; - } - html += '
'; - el.innerHTML = html; - } catch(e) { - el.innerHTML = `
加载失败: ${e.message}
`; - } -} - // ── Spec System ── let _specCache = {};