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:
@@ -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
@@ -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("""
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user