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 = {};