fix(action): 12维操作建议写入action+技术路径action权限保护+RR区间显示修复

- batch parse_response提取【操作建议】→ save_result写action(根治: action字段只有技术路径写,
  旧'盈亏比不足不建议买入'与12维买入分析同框矛盾)
- write_holding_strategy: 技术路径不得覆盖新鲜(<20h)12维 action(与信号权威同级)
- 前端: rr_high>0即显示低~高区间(rr_low=0是合法信息: 区下沿=止损贴底即损)
- server: SEND_FILE_MAX_AGE_DEFAULT=0 禁静态缓存
This commit is contained in:
hmo
2026-07-22 23:16:43 +08:00
parent f7f7fe3ca0
commit 0f2c6555df
4 changed files with 27 additions and 3 deletions
+10 -1
View File
@@ -341,7 +341,7 @@ def parse_response(text):
绝不用"包含关键词的第一行"——修改点段落会引用旧脏值(如"原买入区间95.0~99.0"),
曾导致脏数据被反复写回(17只股票背着95~99区间,LLM新区间形同虚设)。"""
result = {"signal": "", "entry_low": 0, "entry_high": 0, "stop_loss": 0, "take_profit": 0, "position": "",
"zone_cleared": False}
"zone_cleared": False, "action_advice": ""}
def _section_line(name):
"""匹配节标题行:行首(可含空白)【名称】,返回该行内容"""
@@ -387,6 +387,11 @@ def parse_response(text):
result["take_profit"] = float(nums[0])
break
# 操作建议(只认【操作建议】节行)→ action 字段,前端"当前操作策略"列的唯一新鲜来源
al = _section_line("操作建议")
if al:
result["action_advice"] = re.sub(r'^\s*【操作建议】\s*', '', al).strip()[:200]
# 仓位:只有买入信号才需要,提取百分比数字(只认【建议仓位】节行)
result["position"] = ""
if result["signal"] == "买入":
@@ -460,6 +465,10 @@ def save_result(code, full_text, parsed):
if parsed["position"]:
updates.append("position_advice=?")
params.append(parsed["position"])
if parsed.get("action_advice"):
# 12维操作建议 → action(前端"当前操作策略"列;防技术路径旧值与分析矛盾)
updates.append("action=?")
params.append(parsed["action_advice"])
params.append(code)
sql = f"UPDATE holding_strategies SET {', '.join(updates)} WHERE code=? AND status='active'"
+15 -1
View File
@@ -1487,9 +1487,10 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
_old_tag = ''
_old_sig = ''
_old_ra = ''
_old_action = ''
if True:
try:
_old = conn.execute("SELECT full_analysis, reassessed_at, tag, timing_signal FROM holding_strategies WHERE code=? ORDER BY id DESC LIMIT 1", (code,)).fetchone()
_old = conn.execute("SELECT full_analysis, reassessed_at, tag, timing_signal, action FROM holding_strategies WHERE code=? ORDER BY id DESC LIMIT 1", (code,)).fetchone()
if _old:
if not _existing_fa:
if _old[0]: _existing_fa = _old[0]
@@ -1497,6 +1498,7 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
_old_tag = _old[2] or ''
_old_sig = _old[3] or ''
_old_ra = _old[1] or ''
_old_action = _old[4] or ''
except:
pass
# ── 信号权威层级(2026-07-22):新鲜(<20h)12维动作级信号,
@@ -1515,6 +1517,16 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
data['timing_signal'] = _old_sig
except Exception:
pass
# ── action 权限保护(与信号同一权威层级,2026-07-22)──
# 技术路径不得覆盖新鲜(<20h)12维 action。
# 根治:技术路径写的"盈亏比不足1:1.5不建议买入"旧 action 与12维买入分析同框矛盾。
if source_trigger not in ('batch_12d', 'per_stock_12d') and _old_action and _old_ra:
try:
from datetime import datetime as _ddt2, timedelta as _dtd2
if (_ddt2.now() - _ddt2.fromisoformat(str(_old_ra)[:19])) < _dtd2(hours=20):
data['action'] = _old_action
except Exception:
pass
if _old_tag == 'active_manual':
_existing_tag = 'active_manual' # 人工标记不可动
elif _explicit_tag is not None:
@@ -1532,6 +1544,8 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
print(f" [TYPE GUARD] {code} shares类型异常({type(_shares).__name__}={_shares!r}),重置为0", flush=True)
_shares = 0
# ── action 权限保护已在上方信号权威块中统一处理 ──
# DELETE + INSERT
conn.execute("DELETE FROM holding_strategies WHERE code=?", (code,))
conn.execute("""
+1
View File
@@ -76,6 +76,7 @@ from mo_data import read_portfolio, read_decisions, read_watchlist
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_watchlist_stock, write_holding_strategy
app = Flask(__name__, static_folder="static", static_url_path="")
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # 禁静态缓存:前端迭代频繁,防浏览器旧版残留
DATA_DIR = Path(__file__).parent / "data"
UPLOAD_DIR = Path(__file__).parent / "uploads"
+1 -1
View File
@@ -453,7 +453,7 @@ function renderWatchlist() {
<td class="p-3 text-right font-mono text-sm">${priceDisplay}</td>
<td class="p-3 text-right font-mono text-sm ${chg>=0?'text-green-400':'text-red-400'}">${chg>=0?'+':''}${chg.toFixed(2)}</td>
<td class="p-3 text-right font-mono text-xs text-slate-300">${buyZone}<br><span class="text-slate-500">损${sl}${tp}</span></td>
<td class="p-3 text-right font-mono text-sm ${rr>=1.5?'text-green-400':rr>0?'text-yellow-400':'text-slate-500'}">${rr>0?rr.toFixed(2):'—'}${(s.rr_low&&s.rr_high&&s.rr_low!==s.rr_high)?'<br><span class="text-[10px] text-slate-500">'+s.rr_low.toFixed(2)+'~'+s.rr_high.toFixed(2)+'</span>':''}</td>
<td class="p-3 text-right font-mono text-sm ${rr>=1.5?'text-green-400':rr>0?'text-yellow-400':'text-slate-500'}">${rr>0?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-3 text-right"><span class="text-xs px-2 py-1 rounded ${signal.includes('买入')?'bg-green-900/50 text-green-300':signal.includes('关注')?'bg-yellow-900/50 text-yellow-300':'bg-slate-800 text-slate-400'}">${signal||'—'}</span></td>
<td class="p-3 text-right text-xs text-slate-400">${s.position_advice || '—'}</td>
</tr>