emmanuelakbi commited on
Commit
2ac24b1
·
1 Parent(s): 98060b8

Fallback: if Strategist output lacks a Current Price row, call get_price_change directly

Browse files

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.

Files changed (1) hide show
  1. crew/crew.py +65 -26
crew/crew.py CHANGED
@@ -219,45 +219,84 @@ class FinAgentCrew:
219
  ) -> Optional[TradingSignal]:
220
  """Build a TradingSignal directly from tool outputs when LLM parsing fails.
221
 
222
- The agents have already called ``get_price_change``, returning a
223
- formatted block that contains the live price and the recent percent
224
- change. Those lines also leak into the crew's full raw_output, so we
225
- scan there for the canonical ``Current Price: $XXX.XX`` and
226
- ``Change: +/-$X.XX (+/-YY.YY%)`` rows emitted by ``tools.market_scanner``.
 
 
 
 
 
227
 
228
- From those we derive:
229
  * **entry** = live price
230
  * **action** = BUY if today's change is ≥ +1 %, SELL if ≤ −1 %,
231
  otherwise HOLD
232
- * **stop / target** = ± 3 % / ± 5 % of entry (per the signal parser's
233
- sanity fix)
234
  * **confidence** = 50 baseline, + up to 25 scaled by |% change|
235
  * **reasoning** = preserve the LLM's narrative (first ~800 chars)
236
 
237
- Returns ``None`` only if no live price was ever retrieved.
238
  """
 
 
 
 
239
  price_match = re.search(
240
  r"Current Price:\s*\$\s*([\d,]+\.?\d*)", raw_output
241
  )
242
- if not price_match:
243
- return None
244
-
245
- try:
246
- entry = float(price_match.group(1).replace(",", ""))
247
- except ValueError:
248
- return None
249
-
250
- # Percent-change row, e.g. "Change: +$5.90 (+2.05%)"
251
- pct_match = re.search(
252
- r"Change:[^()]*\(([+-]?)([\d.]+)%\)", raw_output
253
- )
254
- pct_change = 0.0
255
- if pct_match:
256
- sign = -1.0 if pct_match.group(1) == "-" else 1.0
257
  try:
258
- pct_change = sign * float(pct_match.group(2))
259
  except ValueError:
260
- pct_change = 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  # Choose action from today's trend.
263
  if pct_change >= 1.0:
 
219
  ) -> Optional[TradingSignal]:
220
  """Build a TradingSignal directly from tool outputs when LLM parsing fails.
221
 
222
+ First we try to scan ``raw_output`` for the canonical
223
+ ``Current Price: $X.XX`` row emitted by ``tools.market_scanner``
224
+ that row leaks into the transcript when the agents call
225
+ ``get_price_change``. If the Strategist's final answer doesn't
226
+ include it (it often doesn't the Strategist is the last agent
227
+ and only sees the prior agents' *summaries*), we call
228
+ ``get_price_change`` directly as a backstop so we still have a
229
+ live price to ground the card on.
230
+
231
+ From the live price we derive:
232
 
 
233
  * **entry** = live price
234
  * **action** = BUY if today's change is ≥ +1 %, SELL if ≤ −1 %,
235
  otherwise HOLD
236
+ * **stop / target** = ± 3 % / ± 5 % of entry
 
237
  * **confidence** = 50 baseline, + up to 25 scaled by |% change|
238
  * **reasoning** = preserve the LLM's narrative (first ~800 chars)
239
 
240
+ Returns ``None`` only if no live price can be retrieved at all.
241
  """
242
+ entry: Optional[float] = None
243
+ pct_change = 0.0
244
+
245
+ # Try the transcript first (cheap — no extra network call).
246
  price_match = re.search(
247
  r"Current Price:\s*\$\s*([\d,]+\.?\d*)", raw_output
248
  )
249
+ if price_match:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  try:
251
+ entry = float(price_match.group(1).replace(",", ""))
252
  except ValueError:
253
+ entry = None
254
+
255
+ pct_match = re.search(
256
+ r"Change:[^()]*\(([+-]?)([\d.]+)%\)", raw_output
257
+ )
258
+ if pct_match:
259
+ sign = -1.0 if pct_match.group(1) == "-" else 1.0
260
+ try:
261
+ pct_change = sign * float(pct_match.group(2))
262
+ except ValueError:
263
+ pct_change = 0.0
264
+
265
+ # Backstop: call get_price_change directly if the Strategist's
266
+ # response did not carry the price row through from upstream.
267
+ if entry is None:
268
+ try:
269
+ market_tools = self._tools.get("market_scanner", [])
270
+ price_tool = next(
271
+ (t for t in market_tools
272
+ if getattr(t, "name", "") == "Get Price Change"),
273
+ None,
274
+ )
275
+ if price_tool is None:
276
+ return None
277
+ # crewai wraps tools; the underlying function is .func.
278
+ price_fn = getattr(price_tool, "func", price_tool)
279
+ result = price_fn(ticker)
280
+ p = re.search(
281
+ r"Current Price:\s*\$\s*([\d,]+\.?\d*)", str(result)
282
+ )
283
+ if not p:
284
+ return None
285
+ entry = float(p.group(1).replace(",", ""))
286
+ pct = re.search(
287
+ r"Change:[^()]*\(([+-]?)([\d.]+)%\)", str(result)
288
+ )
289
+ if pct:
290
+ sign = -1.0 if pct.group(1) == "-" else 1.0
291
+ try:
292
+ pct_change = sign * float(pct.group(2))
293
+ except ValueError:
294
+ pct_change = 0.0
295
+ except Exception:
296
+ return None
297
+
298
+ if entry is None or entry <= 0:
299
+ return None
300
 
301
  # Choose action from today's trend.
302
  if pct_change >= 1.0: