BTC-USD rendered with stop_loss=$79 (real: $80k, LLM dropped the 'k')
and target=entry. The drift check only looked at entry, so those
fields passed through untouched.
Now we also back-fill stop/target if either is more than 20 percent
from entry or within 0.5 percent of entry (the degenerate HOLD case
where the model conflates HOLD with 'no stop, no target').
Re-ground parsed signals when entry drifts from live price; back-fill HOLD N/A
Two remaining failure modes from the live Space:
1. NVDA returned BUY at $10 (with stop $9.70 / target $10.50) because
Qwen3-14B hallucinated small fake numbers that satisfied the BUY
inequality but had nothing to do with the real $215 price. The
parser happily accepted them.
2. BTC-USD HOLD card rendered with 'N/A' for stop and target because
the LLM wrote 'Stop Loss: N/A' literally.
Fix: after the primary parser succeeds, call get_price_change directly
and compare the parsed entry to the live price. If the drift exceeds
30 %, swap the whole signal with the deterministic synthesis. Also
back-fill any missing stop/target (particularly on HOLD) with the
standard ±3–5 % bands so every card shows three concrete prices.
Fallback: if Strategist output lacks a Current Price row, call get_price_change directly
The previous fallback only worked when the live price happened to
leak into the Chief Strategist's final answer. For NVDA the Strategist
emitted a long narrative without echoing the price through from the
Market Scanner, so the fallback returned None and the user saw 'Failed
to parse'. This version calls the get_price_change tool directly as a
backstop whenever the transcript scan fails — yfinance is on the same
machine so there's no real cost.
Deterministic fallback: synthesize TradingSignal from tool outputs
Qwen occasionally loses the output format mid-deliberation even with
the simplest prompt, so crew.run now calls _synthesize_from_tools
whenever the parser returns None. That method scans the raw crew
transcript for the Market Scanner's 'Current Price: $X' and
'Change: +/-$Y (+/-Z%)' lines — which come from real yfinance calls
— and derives action (BUY if +1% today, SELL if -1%, else HOLD),
entry = live price, stop and target at conservative fixed percents
of entry, and confidence scaled by |% change|. Reasoning preserves
the LLM narrative.
Simplify Strategist prompt: concrete example instead of rule-chain
The previous prompt enumerated so many constraints that Qwen3-14B
loaded them into its deliberation loop and never actually produced
the final structured line, causing parse failures. This version shows
one concrete filled example (AAPL BUY 75% $289 $283 $298) and tells
the model to substitute; the literal example also primes it to emit
concrete numbers in the same shape instead of the 'ACTION / XX%'
placeholders it was echoing back.
The sanity-fix in the parser still corrects runaway numbers if they
slip through.
Add deterministic sanity-fix for Strategist's runaway prices
The 14B Strategist occasionally emits wild numbers at synthesis time
(e.g. a $50 000 target on a $290 stock, or stop on the wrong side of
entry). Rather than surface those to the user, the parser now runs
_sanity_fix_prices after extracting the fields: if a stop/target is
more than 20 percent away from the entry or sits on the wrong side of
entry for the BUY/SELL direction, replace it with a conservative
default derived arithmetically from the entry (±3 percent for stop,
±5 percent for target). Valid prices pass through untouched.
Tighten Strategist prompt with explicit BUY/SELL/HOLD price inequalities
Qwen3-14B grounds the entry price in the real tool output but
previously produced a stop_loss ABOVE entry on a BUY (stop 298,
entry 288, target 289) because the original prompt only showed the
format, not the semantics of each field. The tightened prompt
spells out: BUY -> Stop < Entry < Target; SELL -> Target < Entry <
Stop; and ties Entry to the Market Scanner's get_price_change
output.
Bump to Qwen3-14B for better tool-output grounding
Qwen3-8B correctly invoked every tool and saw the real prices from
yfinance but then synthesized the final signal from training-data
memory, producing a $195 entry for AAPL instead of the tool-reported
$293. Qwen3-14B is materially better at grounding final numbers in
the tool outputs it just observed. The MI300X (192 GB) fits 14B with
plenty of headroom.
Swap ChatOpenAI for crewai.LLM so newer crewai versions accept the llm
Newer crewai versions strictly validate Agent(llm=...) and reject a
raw ChatOpenAI instance. Switching to crewai.LLM with the hosted_vllm
provider prefix + explicit base_url routes through litellm cleanly.
Deploy FinAgent: multi-agent trading signals powered by Qwen on AMD MI300X