mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-05-01 14:33:10 +03:00
Extends the canonical structured-output pattern from the Portfolio Manager to the other two decision-making agents. Each of the three agents now returns a typed Pydantic instance via llm.with_structured_output() in a single primary call, and a render helper turns the result into the same markdown shape downstream agents and saved reports already consume. - ResearchPlan: 5-tier recommendation, conversational rationale, concrete strategic actions for the trader. - TraderProposal: 3-tier action (transaction direction is naturally Buy / Hold / Sell — position sizing happens later at the Portfolio Manager), reasoning, and optional entry_price / stop_loss / position_sizing. Rendered output preserves the trailing "FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**" line for backward compatibility with the analyst stop-signal text. - PortfolioDecision: 5-tier rating, executive summary, investment thesis, optional price_target / time_horizon (unchanged). The shared try-structured-then-fallback pattern is extracted into tradingagents/agents/utils/structured.py (bind_structured + invoke_structured_or_freetext) so all three agents go through the same code path and log the same warning when a provider lacks structured output and the agent falls back to free-text generation. Net effect for users: every saved markdown report (research/manager.md, trading/trader.md, portfolio/decision.md) now has consistent section headers across runs and providers, easier to scan. Net effect for the runtime: the rating extraction round-trip is gone — the rating comes from the structured response itself, not a second LLM call. SignalProcessor was already simplified to a heuristic adapter in the previous commit. 11 new tests in tests/test_structured_agents.py cover the Trader and Research Manager render functions, structured-output happy paths, and free-text fallback. Full suite: 88 tests pass in ~2s without API keys.
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""Portfolio Manager: synthesises the risk-analyst debate into the final decision.
|
|
|
|
Uses LangChain's ``with_structured_output`` so the LLM produces a typed
|
|
``PortfolioDecision`` directly, in a single call. The result is rendered
|
|
back to markdown for storage in ``final_trade_decision`` so memory log,
|
|
CLI display, and saved reports continue to consume the same shape they do
|
|
today. When a provider does not expose structured output, the agent falls
|
|
back gracefully to free-text generation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tradingagents.agents.schemas import PortfolioDecision, render_pm_decision
|
|
from tradingagents.agents.utils.agent_utils import (
|
|
build_instrument_context,
|
|
get_language_instruction,
|
|
)
|
|
from tradingagents.agents.utils.structured import (
|
|
bind_structured,
|
|
invoke_structured_or_freetext,
|
|
)
|
|
|
|
|
|
def create_portfolio_manager(llm):
|
|
structured_llm = bind_structured(llm, PortfolioDecision, "Portfolio Manager")
|
|
|
|
def portfolio_manager_node(state) -> dict:
|
|
instrument_context = build_instrument_context(state["company_of_interest"])
|
|
|
|
history = state["risk_debate_state"]["history"]
|
|
risk_debate_state = state["risk_debate_state"]
|
|
research_plan = state["investment_plan"]
|
|
trader_plan = state["trader_investment_plan"]
|
|
|
|
past_context = state.get("past_context", "")
|
|
lessons_line = (
|
|
f"- Lessons from prior decisions and outcomes:\n{past_context}\n"
|
|
if past_context
|
|
else ""
|
|
)
|
|
|
|
prompt = f"""As the Portfolio Manager, synthesize the risk analysts' debate and deliver the final trading decision.
|
|
|
|
{instrument_context}
|
|
|
|
---
|
|
|
|
**Rating Scale** (use exactly one):
|
|
- **Buy**: Strong conviction to enter or add to position
|
|
- **Overweight**: Favorable outlook, gradually increase exposure
|
|
- **Hold**: Maintain current position, no action needed
|
|
- **Underweight**: Reduce exposure, take partial profits
|
|
- **Sell**: Exit position or avoid entry
|
|
|
|
**Context:**
|
|
- Research Manager's investment plan: **{research_plan}**
|
|
- Trader's transaction proposal: **{trader_plan}**
|
|
{lessons_line}
|
|
**Risk Analysts Debate History:**
|
|
{history}
|
|
|
|
---
|
|
|
|
Be decisive and ground every conclusion in specific evidence from the analysts.{get_language_instruction()}"""
|
|
|
|
final_trade_decision = invoke_structured_or_freetext(
|
|
structured_llm,
|
|
llm,
|
|
prompt,
|
|
render_pm_decision,
|
|
"Portfolio Manager",
|
|
)
|
|
|
|
new_risk_debate_state = {
|
|
"judge_decision": final_trade_decision,
|
|
"history": risk_debate_state["history"],
|
|
"aggressive_history": risk_debate_state["aggressive_history"],
|
|
"conservative_history": risk_debate_state["conservative_history"],
|
|
"neutral_history": risk_debate_state["neutral_history"],
|
|
"latest_speaker": "Judge",
|
|
"current_aggressive_response": risk_debate_state["current_aggressive_response"],
|
|
"current_conservative_response": risk_debate_state["current_conservative_response"],
|
|
"current_neutral_response": risk_debate_state["current_neutral_response"],
|
|
"count": risk_debate_state["count"],
|
|
}
|
|
|
|
return {
|
|
"risk_debate_state": new_risk_debate_state,
|
|
"final_trade_decision": final_trade_decision,
|
|
}
|
|
|
|
return portfolio_manager_node
|