Add robust Ollama output normalizer with fallback parse path for decision reliability

This commit is contained in:
2026-02-28 18:57:34 -08:00
parent 9956f421b0
commit ef6e2f160c

View File

@@ -89,11 +89,13 @@ def extra_research(symbol: str, weak_points: list, limit: int = 6):
def summarize_news_with_ollama(symbol: str, context_items: list):
prompt = {"task": "Summarize market-moving info into a concise brief", "symbol": symbol, "news": context_items}
fallback = " | ".join([(x.get("title") or "")[:90] for x in context_items[:3] if x.get("title")]) or f"No strong headlines for {symbol}"
try:
parsed = _ollama_generate(settings.ollama_curator_model, prompt)
return parsed.get("summary", "no-summary")
s = parsed.get("summary")
return s if s else fallback
except Exception:
return "summary-unavailable"
return fallback
def strategy_signals(symbol: str, context_items: list):
@@ -106,6 +108,30 @@ def strategy_signals(symbol: str, context_items: list):
return {"strategy": "event-momentum-v1", "score": score, "action": action, "confidence": conf, "signals": {"bullish": bullish, "bearish": bearish}}
def _normalize_decision(d: dict, strategy: dict):
action = str(d.get("action", strategy.get("action", "hold"))).lower()
if action not in {"buy", "sell", "hold"}:
action = strategy.get("action", "hold")
try:
confidence = max(0.0, min(1.0, float(d.get("confidence", strategy.get("confidence", 0.5)))))
except Exception:
confidence = min(strategy.get("confidence", 0.5), 0.55)
try:
order_usd = float(d.get("order_usd", settings.max_order_usd))
except Exception:
order_usd = settings.max_order_usd
order_usd = max(1.0, min(order_usd, settings.max_order_usd))
reason = str(d.get("reason", "normalized-decision"))[:500]
return {
"action": action,
"confidence": confidence,
"order_usd": order_usd,
"reason": reason,
"needs_more_research": bool(d.get("needs_more_research", False)),
"research_topics": d.get("research_topics", []) or [],
}
def llm_final_decision(symbol: str, context_items: list, strategy: dict, memory_hits: list):
prompt = {
"task": "Final trading decision. Return strict JSON.",
@@ -116,21 +142,39 @@ def llm_final_decision(symbol: str, context_items: list, strategy: dict, memory_
"news": context_items,
"output_schema": {"action": "buy|sell|hold", "confidence": "0-1", "order_usd": f"<= {settings.max_order_usd}", "reason": "short rationale", "needs_more_research": True, "research_topics": ["..."]},
}
# pass 1: strict json mode
try:
d = _ollama_generate(settings.ollama_decision_model, prompt, timeout=60)
action = str(d.get("action", "hold")).lower()
if action not in {"buy", "sell", "hold"}:
action = "hold"
return {
"action": action,
"confidence": max(0.0, min(1.0, float(d.get("confidence", 0.5)))),
"order_usd": min(float(d.get("order_usd", settings.max_order_usd)), settings.max_order_usd),
"reason": d.get("reason", "fallback"),
"needs_more_research": bool(d.get("needs_more_research", False)),
"research_topics": d.get("research_topics", []) or [],
}
return _normalize_decision(d, strategy)
except Exception:
return {"action": strategy.get("action", "hold"), "confidence": min(strategy.get("confidence", 0.5), 0.55), "order_usd": min(1.0, settings.max_order_usd), "reason": "decision-fallback-strategy", "needs_more_research": False, "research_topics": []}
pass
# pass 2: non-json constrained output, then parse heuristically
try:
text_prompt = (
f"Symbol: {symbol}\n"
f"Strategy prior: {strategy}\n"
f"Return 4 lines only:\n"
f"action: buy|sell|hold\nconfidence: 0-1\norder_usd: <= {settings.max_order_usd}\nreason: <short>\n"
)
r = requests.post(f"{settings.ollama_url}/api/generate", json={"model": settings.ollama_decision_model, "prompt": text_prompt, "stream": False}, timeout=45)
if r.ok:
raw = (r.json().get("response", "") or "").lower()
action = "buy" if "buy" in raw else ("sell" if "sell" in raw else "hold")
conf = 0.6 if "confidence" not in raw else strategy.get("confidence", 0.55)
parsed = {"action": action, "confidence": conf, "order_usd": settings.max_order_usd, "reason": raw[:300]}
return _normalize_decision(parsed, strategy)
except Exception:
pass
return {
"action": strategy.get("action", "hold"),
"confidence": min(strategy.get("confidence", 0.5), 0.55),
"order_usd": min(5.0, settings.max_order_usd),
"reason": "decision-fallback-strategy",
"needs_more_research": False,
"research_topics": [],
}
def alpaca_headers():