组合管理 Agent:LLM 如何在硬约束内拍板 难度:入门 组合管理 Agent( 中的 )是虚拟投委会的主持人兼拍板人。它阅读 中全部研究结论(压缩为 ),读取风控给出的 与 ,经 算出每种动作的最大股数,再调用 LLM 在合法动作集合内选出最终指令。 本文你将学到 与 的 Pydantic 决策 schema 与五种动作 的确定性算法 如何结合现金、保证金与持仓 的预填充 hold 与 LLM 最小 Prompt 策略 读输出时的推荐顺序 在流程中的位置 提醒:输出仍是研究模拟建议,不是已成交的真实订单。 决策 Schema(源码级) 最终 为 , 解析 得到 字典。
难度:入门
组合管理 Agent(src/agents/portfolio_manager.py 中的 portfolio_management_agent)是虚拟投委会的主持人兼拍板人。它阅读 analyst_signals 中全部研究结论(压缩为 {sig, conf}),读取风控给出的 remaining_position_limit 与 current_price,经 compute_allowed_actions 算出每种动作的最大股数,再调用 LLM 在合法动作集合内选出最终指令。
PortfolioDecision 与 PortfolioManagerOutput 的 Pydantic 决策 schemamax_shares 与五种动作 buy/sell/short/cover/hold 的确定性算法compute_allowed_actions 如何结合现金、保证金与持仓generate_trading_decision 的预填充 hold 与 LLM 最小 Prompt 策略提醒:输出仍是研究模拟建议,不是已成交的真实订单。
class PortfolioDecision(BaseModel): action: Literal["buy", "sell", "short", "cover", "hold"] quantity: int # 股数,hold 时为 0 confidence: int # 0–100 reasoning: str # 简短理由,Prompt 要求 ≤100 字符 class PortfolioManagerOutput(BaseModel): decisions: dict[str, PortfolioDecision] # ticker → 决策
最终 HumanMessage.content 为 json.dumps({ticker: decision.model_dump()}),run_hedge_fund 解析 messages[-1] 得到 decisions 字典。
对每个 ticker:
risk_data = analyst_signals["risk_management_agent"][ticker] position_limits[ticker] = risk_data["remaining_position_limit"] current_prices[ticker] = risk_data["current_price"] max_shares[ticker] = int(position_limits[ticker] // current_price) # price>0 时
max_shares 是 buy 与 short 新增暴露的共同天花板(股数维度)。
遍历 analyst_signals,跳过 risk_management_agent 键,提取:
ticker_signals[agent] = {"sig": signal, "conf": confidence}
风控条目不参与方向投票,只提供价格与限额。_compact_signals 会剔除空 Agent,减小 LLM Token。
对每只股票,先读取 long_shares、short_shares、cash、margin_requirement(默认 0.5)、margin_used、equity(缺省等于 cash)。
| 动作 | 计算逻辑 | 为 0 时 |
|---|---|---|
| buy | min(max_shares, int(cash // price)) |
无现金或无限额 |
| sell | long_shares(全平上限) |
无多头 |
| short | min(max_shares, int(available_margin // price)),其中 available_margin = max(0, equity/margin_requirement - margin_used);若 margin_requirement≤0 则仅受 max_shares 约束 |
无保证金余量 |
| cover | short_shares |
无空头 |
| hold | 恒合法,quantity=0 | 永远可用 |
修剪规则:容量为 0 的动作从字典删除,只保留 hold 与正容量动作,降低 Prompt 噪音。
| action | 直观含义 | quantity 含义 |
|---|---|---|
| buy | 增加多头 | 买入股数 ≤ allowed buy |
| sell | 减少多头 | 卖出股数 ≤ 持仓多头 |
| short | 增加空头(模拟) | 做空股数 ≤ allowed short |
| cover | 减少空头 | 买回股数 ≤ 持仓空头 |
| hold | 维持现状 | 固定为 0 |
若某 ticker 的 allowed_actions 仅有 {"hold": 0}(无任何可交易容量),不送 LLM,直接:
PortfolioDecision(action="hold", quantity=0, confidence=100, reasoning="No valid trade available")
System:你是 portfolio manager;输入为每票分析师信号与已校验的 allowed max qty;每票选一个 allowed action,quantity ≤ max;理由极简;只返回 JSON。
Human 注入:
signals:紧凑 JSON {TICKER: {agent: {sig, conf}}}allowed:{TICKER: {buy: N, sell: M, ...}}call_llm 带 default_factory:解析失败时,未预填充的 ticker 全部 hold, confidence=0, reasoning="Default decision: hold",再与预填充 hold 合并。
max_shares=0,只能 hold。decisions[TICKER].action 与 quantityanalyst_signals 找分歧最大的 Agent 及其 reasoningrisk_management_agent[TICKER].reasoning 中的 combined_position_limit_pct 与 remaining_limitallowed_actions 逻辑:是现金、保证金还是波动率卡住了交易分析师并行 → analyst_signals(方向) ↓ 风控串行 → remaining_position_limit, current_price ↓ 组合管理 → max_shares → allowed_actions → LLM → PortfolioDecision ↓ messages[-1].content → run_hedge_fund 返回 decisions
| 函数名 | 作用 |
|---|---|
portfolio_management_agent |
主入口,汇总信号、算 max_shares、调用 LLM |
compute_allowed_actions |
确定性计算五种动作的最大合法股数 |
generate_trading_decision |
预填充 hold、构造 Prompt、合并 LLM 输出 |
_compact_signals |
剥离冗余字段,仅保留 sig 与 conf |
若 agent_id 以 portfolio_manager_ 为前缀(可视化编辑器场景),风控键名自动映射为 risk_management_agent_{suffix},保证每个组合经理读取配对的风控输出。CLI 默认使用无前缀的 portfolio_manager 与 risk_management_agent。
对比 analyst_signals 中某 ticker 的各 Agent signal 字段与最终 decisions[TICKER].action:若 action 为 hold 而多数分析师 bullish,优先检查 allowed_actions 是否只剩 hold——这通常意味着风控限额或现金已耗尽,而非 LLM「不听话」。
Agent 体系三层结构至此完整:人格与专题负责观点,风控负责界线(波动率 × 相关性 → 美元上限),组合负责拍板(五种动作 + 股数 + 置信度 + 理由)。风控算的是「还能花多少钱」,组合算的是「这笔钱买还是卖、买多少」。LLM 只在合法动作集合内行使自由裁量,工程约束始终优先。
请打开上手实践支柱《AI 对冲基金上手实践》,目录名 03-hands-on,把本章结构跑成终端里的一次真实输出。