From 0f2c6555df83e52cdf6ebcebcff2a26bf6bff820 Mon Sep 17 00:00:00 2001 From: hmo Date: Wed, 22 Jul 2026 23:16:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(action):=2012=E7=BB=B4=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=86=99=E5=85=A5action+=E6=8A=80=E6=9C=AF?= =?UTF-8?q?=E8=B7=AF=E5=BE=84action=E6=9D=83=E9=99=90=E4=BF=9D=E6=8A=A4+RR?= =?UTF-8?q?=E5=8C=BA=E9=97=B4=E6=98=BE=E7=A4=BA=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 禁静态缓存 --- deploy/profile-scripts/batch_reassess.py | 11 ++++++++++- mofin_db.py | 16 +++++++++++++++- server.py | 1 + static/index.html | 2 +- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/deploy/profile-scripts/batch_reassess.py b/deploy/profile-scripts/batch_reassess.py index 11490344..6e688718 100644 --- a/deploy/profile-scripts/batch_reassess.py +++ b/deploy/profile-scripts/batch_reassess.py @@ -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'" diff --git a/mofin_db.py b/mofin_db.py index dadc9d44..66e4c0f7 100644 --- a/mofin_db.py +++ b/mofin_db.py @@ -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(""" diff --git a/server.py b/server.py index 0f17c981..c0f34e6b 100644 --- a/server.py +++ b/server.py @@ -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" diff --git a/static/index.html b/static/index.html index 107feb3c..def1cba3 100644 --- a/static/index.html +++ b/static/index.html @@ -453,7 +453,7 @@ function renderWatchlist() { ${priceDisplay} ${chg>=0?'+':''}${chg.toFixed(2)} ${buyZone}
损${sl} 盈${tp} - ${rr>0?rr.toFixed(2):'—'}${(s.rr_low&&s.rr_high&&s.rr_low!==s.rr_high)?'
'+s.rr_low.toFixed(2)+'~'+s.rr_high.toFixed(2)+'':''} + ${rr>0?rr.toFixed(2):'—'}${(s.rr_high>0)?'
'+(s.rr_low||0).toFixed(2)+'~'+s.rr_high.toFixed(2)+'':''} ${signal||'—'} ${s.position_advice || '—'}