fix(ui): 盯盘推荐区RR三值+个股弹窗DB化
- /api/stock/<code>: 从holding_strategies读策略/三值RR/action/full_analysis
(原先只读data/stocks/{code}.json, 与DB脱节, 弹窗打开=空白)
- openStock弹窗: 新增当前策略卡(信号/区间/损盈/三值RR/仓位/操作建议)+12维分析折叠区
- 盯盘推荐区RR列: 中值下方小字显示下沿~上沿(上次改错表了, renderWatchlist≠盯盘推荐区)
This commit is contained in:
@@ -503,8 +503,37 @@ def api_report(report_id):
|
||||
|
||||
@app.route("/api/stock/<code>")
|
||||
def api_stock(code):
|
||||
"""个股详情 + 操作建议历史"""
|
||||
"""个股详情:DB策略(12维分析/action/三值RR)为主,JSON历史为辅。
|
||||
根治:弹窗只读 data/stocks/{code}.json,与DB脱节,打开=空白。"""
|
||||
stock_data = _load_json(DATA_DIR / "stocks" / f"{code}.json", {})
|
||||
try:
|
||||
conn = get_conn()
|
||||
conn.row_factory = sqlite3.Row
|
||||
r = conn.execute("""
|
||||
SELECT hs.code, hs.name, hs.timing_signal, hs.action, hs.position_advice,
|
||||
hs.entry_low, hs.entry_high, hs.stop_loss, hs.take_profit,
|
||||
hs.rr_ratio, hs.rr_low, hs.rr_high, hs.full_analysis, hs.reassessed_at,
|
||||
hs.tag, hs.decision_type,
|
||||
lp.price AS live_price, lp.change_pct
|
||||
FROM holding_strategies hs
|
||||
LEFT JOIN live_prices lp ON hs.code = lp.code
|
||||
WHERE hs.code=? AND hs.status='active'
|
||||
""", (code,)).fetchone()
|
||||
conn.close()
|
||||
if r:
|
||||
stock_data.update({
|
||||
"code": r["code"], "name": r["name"],
|
||||
"timing_signal": r["timing_signal"], "action": r["action"],
|
||||
"position_advice": r["position_advice"],
|
||||
"entry_low": r["entry_low"], "entry_high": r["entry_high"],
|
||||
"stop_loss": r["stop_loss"], "take_profit": r["take_profit"],
|
||||
"rr_ratio": r["rr_ratio"], "rr_low": r["rr_low"], "rr_high": r["rr_high"],
|
||||
"full_analysis": r["full_analysis"], "reassessed_at": r["reassessed_at"],
|
||||
"tag": r["tag"], "decision_type": r["decision_type"],
|
||||
"price": r["live_price"], "change_pct": r["change_pct"],
|
||||
})
|
||||
except Exception as _e:
|
||||
stock_data.setdefault("db_error", str(_e))
|
||||
return jsonify(stock_data)
|
||||
|
||||
|
||||
|
||||
+37
-1
@@ -159,7 +159,9 @@ body { background: #0f0f13; color: #e2e8f0; }
|
||||
<button onclick="closeStock()" class="text-slate-500 hover:text-white text-xl">×</button>
|
||||
</div>
|
||||
<div id="modalInfo" class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4"></div>
|
||||
<div id="modalStrategy" class="mb-4"></div>
|
||||
<div id="modalChart" class="h-48 mb-4 bg-slate-900 rounded-xl p-2"></div>
|
||||
<div id="modalAnalysis" class="mb-4"></div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-300 mb-2">📜 操作建议历史</h3>
|
||||
<div id="modalHistory" class="space-y-2"></div>
|
||||
@@ -696,6 +698,40 @@ async function openStock(code) {
|
||||
</div></div>
|
||||
` : `<div class="col-span-full text-xs text-slate-500">暂无持仓数据</div>`;
|
||||
|
||||
// ── 当前策略卡(12维参数 + 三值RR + 操作建议)──
|
||||
const sd = stockData || {};
|
||||
const hasStrategy = sd.action || sd.entry_low || sd.timing_signal;
|
||||
if (hasStrategy) {
|
||||
const rrMid = sd.rr_ratio || 0, rrLo = sd.rr_low || 0, rrHi = sd.rr_high || 0;
|
||||
const rrTxt = rrMid ? `${rrMid.toFixed(2)}${(rrHi>0)?` <span class="text-slate-500 text-[11px]">(下沿${rrLo.toFixed(2)} / 上沿${rrHi.toFixed(2)})</span>`:''}` : '—';
|
||||
document.getElementById('modalStrategy').innerHTML = `
|
||||
<h3 class="text-sm font-semibold text-slate-300 mb-2">🎯 当前策略 <span class="text-xs text-slate-500 font-normal">${(sd.reassessed_at||'').slice(0,16)}</span></h3>
|
||||
<div class="bg-slate-800/40 rounded-lg p-3 space-y-1.5 text-xs">
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 font-mono">
|
||||
${sd.timing_signal?`<span>信号 <b class="${sd.timing_signal.includes('买入')?'text-green-400':sd.timing_signal.includes('卖出')?'text-red-400':'text-yellow-400'}">${sd.timing_signal}</b></span>`:''}
|
||||
${sd.entry_low?`<span class="text-slate-300">区间 ${sd.entry_low}~${sd.entry_high}</span>`:''}
|
||||
${sd.stop_loss?`<span class="text-red-400">损 ${sd.stop_loss}</span>`:''}
|
||||
${sd.take_profit?`<span class="text-green-400">盈 ${sd.take_profit}</span>`:''}
|
||||
<span class="${rrMid>=1.5?'text-green-400':'text-yellow-400'}">RR ${rrTxt}</span>
|
||||
${sd.position_advice?`<span class="text-blue-400">仓位 ${sd.position_advice}</span>`:''}
|
||||
</div>
|
||||
${sd.action?`<div class="text-slate-200 leading-relaxed border-t border-slate-700/50 pt-1.5">${sd.action}</div>`:''}
|
||||
</div>`;
|
||||
} else {
|
||||
document.getElementById('modalStrategy').innerHTML = '';
|
||||
}
|
||||
|
||||
// ── 12维完整分析(折叠)──
|
||||
if (sd.full_analysis) {
|
||||
document.getElementById('modalAnalysis').innerHTML = `
|
||||
<details class="bg-slate-800/30 rounded-lg">
|
||||
<summary class="text-sm font-semibold text-slate-300 p-3 cursor-pointer select-none">📋 12维完整分析(点击展开)</summary>
|
||||
<pre class="text-xs text-slate-300 leading-relaxed whitespace-pre-wrap p-3 pt-0 font-sans">${sd.full_analysis}</pre>
|
||||
</details>`;
|
||||
} else {
|
||||
document.getElementById('modalAnalysis').innerHTML = '';
|
||||
}
|
||||
|
||||
// Render recommendation history
|
||||
const history = stockData.history || [];
|
||||
document.getElementById('modalHistory').innerHTML = history.length ? history.map(h => `
|
||||
@@ -1542,7 +1578,7 @@ async function renderWatch() {
|
||||
'<td class="p-2 text-left"><span class="text-xs px-2 py-1 rounded ' + sigCls + '">' + (sig || '—') + '</span></td>' +
|
||||
'<td class="p-2 text-right font-mono text-slate-300">' + buyZone + '</td>' +
|
||||
'<td class="p-2 text-right font-mono text-slate-300">' + sltp + '</td>' +
|
||||
'<td class="p-2 text-right font-mono ' + (rr >= 1.5 ? 'text-green-400' : rr > 0 ? 'text-yellow-400' : 'text-slate-500') + '">' + (rr ? rr.toFixed(2) : '—') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono ' + (rr >= 1.5 ? 'text-green-400' : rr > 0 ? 'text-yellow-400' : 'text-slate-500') + '">' + (rr ? rr.toFixed(2) : '—') + ((s.rr_high>0)?'<br><span class="text-[10px] text-slate-500">'+(s.rr_low||0).toFixed(2)+'~'+s.rr_high.toFixed(2)+'</span>':'') + '</td>' +
|
||||
'<td class="p-2 text-right font-mono text-slate-300">' + posDisplay + '</td>' +
|
||||
'<td class="p-2 ' + stratCls + ' max-w-[200px] break-all line-clamp-2">' + strat + '</td>' +
|
||||
'<td class="p-2 text-center"><button class="text-xs px-2 py-1 rounded bg-blue-900/50 text-blue-400 hover:bg-blue-800/50 transition" onclick="event.stopPropagation();openStrategyHistory(\'' + s.code + '\')">📋 历史</button></td>' +
|
||||
|
||||
Reference in New Issue
Block a user