fix(parser): 节标题锚定解析+脏区间清洗+三值RR

- parse_response: 只认行首【买入区间】【综合结论】等节行(原先取首个关键词行,
  命中修改点段落引用的旧脏值→95~99区间反复写回,17只股票受害);
  信号锚定】后首词,防'观望(不建议买入)'误判; 【买入区间】无→显式清空
- save_result: 区间-现价距离门禁(eh<0.5px或el>1.5px→拒写并清空,不再保留原值养脏)
- recompute_rr: 三值RR(rr_low/rr_ratio中/rr_high,分别对应区下沿/中值/上沿入场)
- 迁移: holding_strategies+rr_low/rr_high列; digest显示RR中(低~高); watch API+前端展示
This commit is contained in:
hmo
2026-07-22 22:59:18 +08:00
parent 56e1326569
commit f7f7fe3ca0
4 changed files with 120 additions and 65 deletions
+81 -45
View File
@@ -336,50 +336,69 @@ PE={data.get('pe','?')}(最新财报) 市值={data.get('mcap','?')}亿
2. 禁止输出 <structured_data> 或任何 XML/JSON/代码块
3. 所有【】节标题一个都不能少"""
def parse_response(text):
"""从LLM回复中提取策略参数"""
result = {"signal": "", "entry_low": 0, "entry_high": 0, "stop_loss": 0, "take_profit": 0, "position": ""}
# 信号
sl = [l for l in text.split("\n") if "综合结论" in l]
"""从LLM回复中提取策略参数
⚠️ 节标题精确匹配:只认行首【买入区间】【综合结论】等节行。
绝不用"包含关键词的第一行"——修改点段落会引用旧脏值(如"原买入区间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}
def _section_line(name):
"""匹配节标题行:行首(可含空白)【名称】,返回该行内容"""
for l in text.split("\n"):
if re.match(r'^\s*【' + name + r'', l):
return l
return ""
# 信号(只认【综合结论】节行,且锚定】后的首个词,防"观望(不建议买入)"误判为买入)
sl = _section_line("综合结论")
if sl:
for kw in ["买入","关注","观望","卖出"]:
if kw in sl[0]:
result["signal"] = kw
break
# 买入区间
zl = [l for l in text.split("\n") if "买入区间" in l]
m = re.search(r'综合结论】\s*[(]?\s*(弱势持有|可加仓|可买入|买入|卖出|止盈|关注|观望|持有)', sl)
if m:
result["signal"] = m.group(1)
# 买入区间(只认【买入区间】节行;"无"→显式清空,不保留旧值)
zl = _section_line("买入区间")
if zl:
nums = re.findall(r'[\d.]+', zl[0])
if len(nums) >= 2:
result["entry_low"] = float(nums[0])
result["entry_high"] = float(nums[1])
# 止损
for l in text.split("\n"):
if "建议止损" in l:
nums = re.findall(r'[\d.]+', l)
if nums: result["stop_loss"] = float(nums[0])
# 止盈
for l in text.split("\n"):
if "建议止盈" in l:
nums = re.findall(r'[\d.]+', l)
if nums: result["take_profit"] = float(nums[0])
# 仓位:只有买入信号才需要,提取百分比数字
if re.search(r'\s*(无|不设|不参与|空仓)', zl):
result["zone_cleared"] = True
else:
nums = re.findall(r'\d+\.?\d*', zl)
if len(nums) >= 2:
a, b = float(nums[0]), float(nums[1])
result["entry_low"] = min(a, b)
result["entry_high"] = max(a, b)
# 止损(只认【建议止损】节行)
for name in ("建议止损", "止损"):
l = _section_line(name)
if l:
nums = re.findall(r'\d+\.?\d*', l)
if nums:
result["stop_loss"] = float(nums[0])
break
# 止盈(只认【建议止盈】节行)
for name in ("建议止盈", "止盈"):
l = _section_line(name)
if l:
nums = re.findall(r'\d+\.?\d*', l)
if nums:
result["take_profit"] = float(nums[0])
break
# 仓位:只有买入信号才需要,提取百分比数字(只认【建议仓位】节行)
result["position"] = ""
if result["signal"] == "买入":
for l in text.split("\n"):
if "建议仓位" in l:
nums = re.findall(r'[\d.]+', l)
for n in nums:
f = float(n)
if 1 <= f <= 30: # 合理的仓位范围
result["position"] = f"{f:.0f}%"
break
break
l = _section_line("建议仓位")
if l:
nums = re.findall(r'\d+\.?\d*', l)
for n in nums:
f = float(n)
if 1 <= f <= 30: # 合理的仓位范围
result["position"] = f"{f:.0f}%"
break
return result
def save_result(code, full_text, parsed):
@@ -402,11 +421,28 @@ def save_result(code, full_text, parsed):
# 区间写入门禁:上下沿都必须为正且 下沿<上沿<下沿x3,否则视为解析错误整体跳过
# (防 214.68~2.52 类解析污染,与 GATE_ZONE_SANITY 同级防护)
_el, _eh = parsed["entry_low"], parsed["entry_high"]
if _el > 0 and _eh > _el and _eh < _el * 3:
updates.append("entry_low=?")
params.append(_el)
updates.append("entry_high=?")
params.append(_eh)
if parsed.get("zone_cleared"):
# LLM 显式输出【买入区间】无 → 清空区间(不再保留可能脏的旧值)
updates.append("entry_low=0")
updates.append("entry_high=0")
elif _el > 0 and _eh > _el and _eh < _el * 3:
# 区间-现价距离门禁:整体偏离现价过远(区上沿<现价0.5x 或 区下沿>现价1.5x
# → 判定脏数据/解析错误,拒写并清空(不再"保留原值"养脏,如95~99 vs 现价60
_px = 0.0
try:
_pr = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
_px = float(_pr[0]) if _pr and _pr[0] else 0.0
except Exception:
pass
if _px > 0 and (_eh < _px * 0.5 or _el > _px * 1.5):
print(f" ⚠️ 买入区{_el}~{_eh}偏离现价{_px}过远,拒写并清空(防脏数据残留)", flush=True)
updates.append("entry_low=0")
updates.append("entry_high=0")
else:
updates.append("entry_low=?")
params.append(_el)
updates.append("entry_high=?")
params.append(_eh)
elif _el > 0 or _eh > 0:
print(f" ⚠️ 买入区解析异常({_el}~{_eh}),跳过区间写入(保留原值)", flush=True)
# 止损/止盈一致性门禁:损>0 时必须在区间下沿之下(0.5x~1.0x),盈>0 时必须在区间上沿之上