mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 19:25:24 +03:00
fix(agents): require absolute price levels from the Trader
- asking the Trader for concrete entry/stop levels invited a percentage (stop_loss: '15%'), which is not a price and failed the whole structured parse, dropping the run to a free-text retry - state the requirement in the prompt and in both field descriptions - a percentage now nulls that field instead of failing the proposal; it is never salvaged, since 15% must not become a 15 stop. Human-formatted prices with a currency symbol or thousands separator parse #1288
This commit is contained in:
@@ -31,9 +31,23 @@ _NULLISH_FLOAT = {"", "none", "n/a", "na", "null", "nil", "-", "tbd", "unknown"}
|
||||
|
||||
|
||||
def _coerce_optional_float(value):
|
||||
if isinstance(value, str) and value.strip().lower() in _NULLISH_FLOAT:
|
||||
"""Normalise an LLM-written optional numeric field before validation.
|
||||
|
||||
Three shapes show up in practice: a placeholder string ("None", "N/A") in
|
||||
place of an omitted value (#1058); a percentage where a price was asked for
|
||||
("15%", #1288); and a human-formatted price ("$1,234.50"). A percentage
|
||||
cannot be salvaged into an absolute level -- reading "15%" as 15 would put a
|
||||
stop at $15 on a $600 stock -- so it is dropped like a placeholder, leaving
|
||||
one bad field to null out instead of failing the whole proposal. A formatted
|
||||
price is reduced to its number. Anything else passes through to pydantic.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
text = value.strip()
|
||||
if text.lower() in _NULLISH_FLOAT or text.endswith("%"):
|
||||
return None
|
||||
return value
|
||||
cleaned = text.replace(",", "").lstrip("$€£¥").strip()
|
||||
return cleaned or None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -140,11 +154,19 @@ class TraderProposal(BaseModel):
|
||||
)
|
||||
entry_price: float | None = Field(
|
||||
default=None,
|
||||
description="Optional entry price target in the instrument's quote currency.",
|
||||
description=(
|
||||
"Optional entry price target as an absolute number in the instrument's "
|
||||
"quote currency (e.g. 189.5), never a percentage or a range. Omit it "
|
||||
"if you cannot state a specific level."
|
||||
),
|
||||
)
|
||||
stop_loss: float | None = Field(
|
||||
default=None,
|
||||
description="Optional stop-loss price in the instrument's quote currency.",
|
||||
description=(
|
||||
"Optional stop-loss as an absolute price in the instrument's quote "
|
||||
"currency (e.g. 172.0), never a percentage. Convert a percentage "
|
||||
"distance to the price level it implies, or omit it."
|
||||
),
|
||||
)
|
||||
position_sizing: str | None = Field(
|
||||
default=None,
|
||||
|
||||
@@ -50,6 +50,13 @@ def create_trader(llm):
|
||||
"You are a trading agent analyzing market data to make investment decisions. "
|
||||
"Based on your analysis, provide a specific recommendation to buy, sell, or hold. "
|
||||
+ grounding
|
||||
# Entry/stop are numeric price fields. Asking for concrete
|
||||
# levels invites a percentage ("15%"), which is not a price
|
||||
# and fails the structured parse (#1288).
|
||||
+ "State entry price and stop-loss as absolute price levels in the "
|
||||
"instrument's quote currency (for example 189.5), never a percentage "
|
||||
"or a range; convert a percentage distance to the price level it "
|
||||
"implies, or omit the field if you cannot state a number. "
|
||||
+ NO_EXTERNAL_TOOLS
|
||||
+ get_language_instruction()
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user