refactor!: 删除可执行价位能力,不再以配置开关形式保留

BREAKING CHANGE: 上一提交引入的 enable_execution_levels 开关一并移除。

改为直接删除而非默认关闭:荐股软件的认定看软件是否「具备」该功能,
留一个开关在代码里、README 还写着怎么打开,那软件依然具备该功能。
删掉才是真的不具备,同时少一个开关、少两个 schema 变体、少两处分支。

- 删除 TraderProposalWithLevels / PortfolioDecisionWithTarget 两个变体
  与 trader_proposal_model() / portfolio_decision_model() 选择器
- 删除 entry_price / stop_loss / position_sizing / price_target 字段
- 渲染函数不再输出对应四节,getattr 兼容层一并移除
- create_trader / create_portfolio_manager / GraphSetup 去掉开关参数
- default_config 去掉 enable_execution_levels
- 提示词保持收紧(仅删字段挡不住模型写进散文字段)
- 测试 TestExecutionLevelsFlag → TestNoExecutionLevels:
  锁定「schema 无价位字段 / 提示词禁止 / 渲染永不输出」

需要该能力的使用者可自行 fork 添加(Apache-2.0 允许)。

测试:161 passed + 48 subtests passed。
This commit is contained in:
Simon Lin
2026-07-24 19:54:24 +12:00
parent 35a297c3ab
commit d55820c08d
10 changed files with 55 additions and 188 deletions
+8 -7
View File
@@ -8,14 +8,15 @@ Breaking changes within the 0.x line are called out explicitly.
## [0.3.0] — 2026-07-24 ## [0.3.0] — 2026-07-24
明确项目定位为「框架的工程实现与研究复现」,并可执行价位改为默认关闭。**有破坏性变更**(见下)。 明确项目定位为「框架的工程实现与研究复现」,并**移除可执行价位相关能力**。**有破坏性变更**(见下)。
### 变更(破坏性) ### 移除(破坏性)
- **`enable_execution_levels` 新增,默认 `False`**:默认情况下 Trader 与 Portfolio Manager 只输出方向 / 评级与理由,**不再产出建仓价、止损位、仓位、目标价** - **可执行价位能力整体删除**Trader 与 Portfolio Manager 现在只输出方向 / 评级与理由,框架内**不再存在**建仓价、止损位、仓位、目标价这类输出
- Schema 拆分`TraderProposal`(默认,无价位)/ `TraderProposalWithLevels`opt-in);`PortfolioDecision`(默认,无目标价)/ `PortfolioDecisionWithTarget`opt-in)。用 `trader_proposal_model()` / `portfolio_decision_model()` 按开关取用 - 删除字段`TraderProposal.entry_price` / `.stop_loss` / `.position_sizing``PortfolioDecision.price_target`
- 提示词同步收紧:删字段挡不住模型把价位写进散文字段,因此系统提示与 `executive_summary` / `reasoning` 的字段描述都显式要求不给价位。 - 提示词同步收紧:删字段挡不住模型把价位写进散文字段,因此系统提示与 `executive_summary` / `reasoning` 的字段描述都显式要求不给价位。
- 渲染函数改用 `getattr`,两种变体都能渲染,下游 markdown 格式不变。 - 渲染函数不再输出 `**Entry Price**` / `**Stop Loss**` / `**Position Sizing**` / `**Price Target**` 四节;其余 markdown 格式不变。
- **升级影响**:依赖 `TraderProposal.entry_price` 等字段的下游代码,需改用 `*WithLevels` 变体,或在 config 里设 `enable_execution_levels: True` - **这是删除而不是开关**——不提供 opt-in 配置项。需要这类能力的使用者可自行 fork 添加(Apache-2.0 允许),并自行承担相应责任
- **升级影响**:依赖 `TraderProposal.entry_price` 等字段的下游代码需自行调整。
- **`ResearchPlan.strategic_actions`** 的字段描述去掉「including position sizing guidance」。 - **`ResearchPlan.strategic_actions`** 的字段描述去掉「including position sizing guidance」。
### 移除 ### 移除
+1 -2
View File
@@ -288,7 +288,6 @@ streamlit run web/app.py
| `backend_url` | `None` | 自定义 API 端点 / 第三方中转网关。可在 Web UI 侧边栏填写,或用 `.env``BACKEND_URL`;方便国内通过代理访问 Claude / OpenAI | | `backend_url` | `None` | 自定义 API 端点 / 第三方中转网关。可在 Web UI 侧边栏填写,或用 `.env``BACKEND_URL`;方便国内通过代理访问 Claude / OpenAI |
| `output_language` | `"Chinese"` | 报告输出语言(内部辩论始终英文) | | `output_language` | `"Chinese"` | 报告输出语言(内部辩论始终英文) |
| `market_lookback_days` | `None` | 技术分析回溯天数(分析区间 = 起始日期 → 分析日期)。Web/CLI 由「数据起始日期」自动算出;`None` = 模型自选(约 30 天)。#16 | | `market_lookback_days` | `None` | 技术分析回溯天数(分析区间 = 起始日期 → 分析日期)。Web/CLI 由「数据起始日期」自动算出;`None` = 模型自选(约 30 天)。#16 |
| `enable_execution_levels` | `False` | **默认关闭。** 关闭时 Trader / Portfolio Manager 只给方向与理由,不产出建仓价 / 止损位 / 仓位 / 目标价。开启后才输出这些可执行价位——是否开启由使用者自行决定并自担责任,详见[项目定位](#项目定位)。 |
| `max_debate_rounds` | `1` | Bull vs Bear 辩论轮数 | | `max_debate_rounds` | `1` | Bull vs Bear 辩论轮数 |
| `max_risk_discuss_rounds` | `1` | 风险三方辩论轮数 | | `max_risk_discuss_rounds` | `1` | 风险三方辩论轮数 |
| `data_vendors` | 全部 `"a_stock"` | 数据供应商路由 | | `data_vendors` | 全部 `"a_stock"` | 数据供应商路由 |
@@ -411,7 +410,7 @@ TradingAgents-Astock/
- **它是什么**[TradingAgents 论文](https://arxiv.org/abs/2412.20138)TauricResearch)多 Agent 架构的 A 股工程实现,用于研究与教学——研究多 Agent 辩论在金融文本上的行为、A 股数据源如何接入、结构化输出如何落地。 - **它是什么**[TradingAgents 论文](https://arxiv.org/abs/2412.20138)TauricResearch)多 Agent 架构的 A 股工程实现,用于研究与教学——研究多 Agent 辩论在金融文本上的行为、A 股数据源如何接入、结构化输出如何落地。
- **它不是什么**:不是投资顾问、不是荐股软件、不提供任何投资服务。本仓库不发布针对具体证券的分析报告、评级或买卖建议;`examples/` 下只有可自行运行的脚本,没有任何预生成的个股结论。 - **它不是什么**:不是投资顾问、不是荐股软件、不提供任何投资服务。本仓库不发布针对具体证券的分析报告、评级或买卖建议;`examples/` 下只有可自行运行的脚本,没有任何预生成的个股结论。
- **模型和数据都是你自己的**:你配置自己的 LLM API key,在自己的机器上运行,产出的内容归你所有、由你判断、由你负责。项目本身不托管服务、不代为分析、不接触你的运行结果。 - **模型和数据都是你自己的**:你配置自己的 LLM API key,在自己的机器上运行,产出的内容归你所有、由你判断、由你负责。项目本身不托管服务、不代为分析、不接触你的运行结果。
- **默认不产出可执行价位**`enable_execution_levels` 默认为 `False`Trader 与 Portfolio Manager 只给方向与理由,不给建仓价 / 止损位 / 仓位 / 目标价。要打开是使用者的决定,使用者需自行承担相应责任、自行确认所在司法辖区的资质要求。 - **不产出可执行价位**框架内**没有**建仓价 / 止损位 / 仓位 / 目标价这类输出——不是默认关闭,是代码里就没有。Trader 与 Portfolio Manager 只给方向、评级与理由。需要这类能力的使用者可以自行 fork 添加(Apache-2.0 允许),并自行承担相应责任、自行确认所在司法辖区的资质要求。
> **⚠️ 免责声明** > **⚠️ 免责声明**
> >
+5 -10
View File
@@ -5,12 +5,7 @@ import pandas as pd
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from tradingagents.agents.utils.memory import TradingMemoryLog from tradingagents.agents.utils.memory import TradingMemoryLog
from tradingagents.agents.schemas import ( from tradingagents.agents.schemas import PortfolioDecision, PortfolioRating
PortfolioDecision,
PortfolioDecisionWithTarget,
PortfolioRating,
portfolio_decision_model,
)
from tradingagents.graph.reflection import Reflector from tradingagents.graph.reflection import Reflector
from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.graph.propagation import Propagator from tradingagents.graph.propagation import Propagator
@@ -617,21 +612,21 @@ class TestPortfolioManagerInjection:
downstream consumers (memory log, signal processor, CLI display) downstream consumers (memory log, signal processor, CLI display)
can parse without any extra LLM call.""" can parse without any extra LLM call."""
captured = {} captured = {}
decision = PortfolioDecisionWithTarget( decision = PortfolioDecision(
rating=PortfolioRating.OVERWEIGHT, rating=PortfolioRating.OVERWEIGHT,
executive_summary="Build position gradually over the next two weeks.", executive_summary="Build position gradually over the next two weeks.",
investment_thesis="AI capex cycle remains intact; institutional flows constructive.", investment_thesis="AI capex cycle remains intact; institutional flows constructive.",
price_target=215.0,
time_horizon="3-6 months", time_horizon="3-6 months",
) )
llm = _structured_pm_llm(captured, decision) llm = _structured_pm_llm(captured, decision)
pm_node = create_portfolio_manager(llm, enable_execution_levels=True) pm_node = create_portfolio_manager(llm)
result = pm_node(_make_pm_state()) result = pm_node(_make_pm_state())
md = result["final_trade_decision"] md = result["final_trade_decision"]
assert "**Rating**: Overweight" in md assert "**Rating**: Overweight" in md
assert "**Executive Summary**: Build position gradually" in md assert "**Executive Summary**: Build position gradually" in md
assert "**Investment Thesis**: AI capex cycle" in md assert "**Investment Thesis**: AI capex cycle" in md
assert "**Price Target**: 215.0" in md # 框架不产出目标价——渲染里永远不该出现这一节。
assert "Price Target" not in md
assert "**Time Horizon**: 3-6 months" in md assert "**Time Horizon**: 3-6 months" in md
def test_pm_falls_back_to_freetext_when_structured_unavailable(self): def test_pm_falls_back_to_freetext_when_structured_unavailable(self):
+10 -42
View File
@@ -17,10 +17,8 @@ from tradingagents.agents.schemas import (
ResearchPlan, ResearchPlan,
TraderAction, TraderAction,
TraderProposal, TraderProposal,
TraderProposalWithLevels,
render_research_plan, render_research_plan,
render_trader_proposal, render_trader_proposal,
trader_proposal_model,
) )
from tradingagents.agents.trader.trader import create_trader from tradingagents.agents.trader.trader import create_trader
@@ -41,21 +39,6 @@ class TestRenderTraderProposal:
# analyst stop-signal text and any external code that greps for it. # analyst stop-signal text and any external code that greps for it.
assert "FINAL TRANSACTION PROPOSAL: **HOLD**" in md assert "FINAL TRANSACTION PROPOSAL: **HOLD**" in md
def test_optional_fields_included_when_present(self):
p = TraderProposalWithLevels(
action=TraderAction.BUY,
reasoning="Strong technicals + fundamentals.",
entry_price=189.5,
stop_loss=178.0,
position_sizing="6% of portfolio",
)
md = render_trader_proposal(p)
assert "**Action**: Buy" in md
assert "**Entry Price**: 189.5" in md
assert "**Stop Loss**: 178.0" in md
assert "**Position Sizing**: 6% of portfolio" in md
assert "FINAL TRANSACTION PROPOSAL: **BUY**" in md
def test_optional_fields_omitted_when_absent(self): def test_optional_fields_omitted_when_absent(self):
p = TraderProposal(action=TraderAction.SELL, reasoning="Guidance cut.") p = TraderProposal(action=TraderAction.SELL, reasoning="Guidance cut.")
md = render_trader_proposal(p) md = render_trader_proposal(p)
@@ -120,36 +103,24 @@ def _structured_trader_llm(captured: dict, proposal: TraderProposal | None = Non
@pytest.mark.unit @pytest.mark.unit
class TestExecutionLevelsFlag: class TestNoExecutionLevels:
"""Execution levels (entry / stop-loss / sizing) are opt-in, off by default.""" """The framework ships no executable price levels at all — not a default,
not a flag. See TraderProposal's docstring for why."""
def test_default_schema_has_no_level_fields(self): def test_schema_has_no_level_fields(self):
fields = trader_proposal_model().model_fields fields = TraderProposal.model_fields
assert "action" in fields and "reasoning" in fields assert "action" in fields and "reasoning" in fields
for f in ("entry_price", "stop_loss", "position_sizing"): for f in ("entry_price", "stop_loss", "position_sizing"):
assert f not in fields assert f not in fields
def test_enabled_schema_has_level_fields(self): def test_prompt_forbids_levels(self):
fields = trader_proposal_model(True).model_fields
for f in ("entry_price", "stop_loss", "position_sizing"):
assert f in fields
def test_default_prompt_forbids_levels(self):
captured = {} captured = {}
trader = create_trader(_structured_trader_llm(captured)) trader = create_trader(_structured_trader_llm(captured))
trader(_make_trader_state()) trader(_make_trader_state())
system = next(m["content"] for m in captured["prompt"] if m["role"] == "system") system = next(m["content"] for m in captured["prompt"] if m["role"] == "system")
assert "Do NOT state entry prices" in system assert "Do NOT state entry prices" in system
assert "Be specific about entry price" not in system
def test_enabled_prompt_asks_for_levels(self): def test_render_never_emits_levels(self):
captured = {}
trader = create_trader(_structured_trader_llm(captured), enable_execution_levels=True)
trader(_make_trader_state())
system = next(m["content"] for m in captured["prompt"] if m["role"] == "system")
assert "Be specific about entry price" in system
def test_render_omits_levels_for_default_proposal(self):
p = TraderProposal(action=TraderAction.BUY, reasoning="Trend intact.") p = TraderProposal(action=TraderAction.BUY, reasoning="Trend intact.")
md = render_trader_proposal(p) md = render_trader_proposal(p)
for label in ("Entry Price", "Stop Loss", "Position Sizing"): for label in ("Entry Price", "Stop Loss", "Position Sizing"):
@@ -160,19 +131,16 @@ class TestExecutionLevelsFlag:
class TestTraderAgent: class TestTraderAgent:
def test_structured_path_produces_rendered_markdown(self): def test_structured_path_produces_rendered_markdown(self):
captured = {} captured = {}
proposal = TraderProposalWithLevels( proposal = TraderProposal(
action=TraderAction.BUY, action=TraderAction.BUY,
reasoning="AI capex cycle intact; institutional flows constructive.", reasoning="AI capex cycle intact; institutional flows constructive.",
entry_price=189.5,
stop_loss=178.0,
position_sizing="6% of portfolio",
) )
llm = _structured_trader_llm(captured, proposal) llm = _structured_trader_llm(captured, proposal)
trader = create_trader(llm, enable_execution_levels=True) trader = create_trader(llm)
result = trader(_make_trader_state()) result = trader(_make_trader_state())
plan = result["trader_investment_plan"] plan = result["trader_investment_plan"]
assert "**Action**: Buy" in plan assert "**Action**: Buy" in plan
assert "**Entry Price**: 189.5" in plan assert "**Reasoning**: AI capex cycle intact" in plan
assert "FINAL TRANSACTION PROPOSAL: **BUY**" in plan assert "FINAL TRANSACTION PROPOSAL: **BUY**" in plan
# The same rendered markdown is also added to messages for downstream agents. # The same rendered markdown is also added to messages for downstream agents.
assert plan in result["messages"][0].content assert plan in result["messages"][0].content
@@ -10,7 +10,7 @@ back gracefully to free-text generation.
from __future__ import annotations from __future__ import annotations
from tradingagents.agents.schemas import portfolio_decision_model, render_pm_decision from tradingagents.agents.schemas import PortfolioDecision, render_pm_decision
from tradingagents.agents.utils.agent_utils import ( from tradingagents.agents.utils.agent_utils import (
build_instrument_context, build_instrument_context,
get_language_instruction, get_language_instruction,
@@ -21,20 +21,16 @@ from tradingagents.agents.utils.structured import (
) )
def create_portfolio_manager(llm, enable_execution_levels: bool = False): # Mirrors the Trader: the schema alone cannot stop the model from putting
structured_llm = bind_structured( # price levels into the prose fields, so the prompt says it explicitly too.
llm, portfolio_decision_model(enable_execution_levels), "Portfolio Manager" _NO_LEVELS_RULE = (
) "\n- Do NOT state entry prices, stop-loss levels, target prices or "
# Mirrors the Trader: the schema alone cannot stop the model from putting "position sizes for this security; give the rating and the reasoning."
# levels into the prose fields, so say it in the prompt too. )
levels_rule = (
""
if enable_execution_levels def create_portfolio_manager(llm):
else ( structured_llm = bind_structured(llm, PortfolioDecision, "Portfolio Manager")
"\n- Do NOT state entry prices, stop-loss levels, target prices or "
"position sizes for this security; give the rating and the reasoning."
)
)
def portfolio_manager_node(state) -> dict: def portfolio_manager_node(state) -> dict:
instrument_context = build_instrument_context(state["company_of_interest"]) instrument_context = build_instrument_context(state["company_of_interest"])
@@ -83,7 +79,7 @@ def create_portfolio_manager(llm, enable_execution_levels: bool = False):
--- ---
Be decisive and ground every conclusion in specific evidence from the analysts.{levels_rule}{get_language_instruction()}""" Be decisive and ground every conclusion in specific evidence from the analysts.{_NO_LEVELS_RULE}{get_language_instruction()}"""
final_trade_decision = invoke_structured_or_freetext( final_trade_decision = invoke_structured_or_freetext(
structured_llm, structured_llm,
+10 -80
View File
@@ -112,10 +112,12 @@ class TraderProposal(BaseModel):
The trader reads the Research Manager's investment plan and the analyst The trader reads the Research Manager's investment plan and the analyst
reports, then states a direction and the reasoning behind it. reports, then states a direction and the reasoning behind it.
This default variant deliberately carries **no executable price levels** It deliberately carries **no executable price levels** no entry price,
(entry / stop-loss / sizing). Those live in no stop-loss, no position size. This project is a research and education
:class:`TraderProposalWithLevels`, which is opt-in via the implementation of the upstream TradingAgents framework, and concrete trade
``enable_execution_levels`` config flag. See that class for why. levels for a named security are what turn a research tool into an
investment-advisory product. The capability is not shipped here; a
downstream fork that wants it can add it under its own responsibility.
""" """
action: TraderAction = Field( action: TraderAction = Field(
@@ -130,37 +132,6 @@ class TraderProposal(BaseModel):
) )
class TraderProposalWithLevels(TraderProposal):
"""Opt-in variant that additionally asks for executable price levels.
Only used when ``enable_execution_levels`` is True in the config. It is
off by default: this project is a research/education implementation of
the upstream TradingAgents framework, and concrete entry / stop-loss /
position-size levels for a named security are exactly the kind of output
that turns a research tool into an investment-advisory product. Operators
who turn this on are responsible for how the output is used and for any
licensing their jurisdiction requires.
"""
entry_price: Optional[float] = Field(
default=None,
description="Optional entry price target in the instrument's quote currency.",
)
stop_loss: Optional[float] = Field(
default=None,
description="Optional stop-loss price in the instrument's quote currency.",
)
position_sizing: Optional[str] = Field(
default=None,
description="Optional sizing guidance, e.g. '5% of portfolio'.",
)
def trader_proposal_model(enable_execution_levels: bool = False) -> type[TraderProposal]:
"""Pick the Trader schema matching the ``enable_execution_levels`` flag."""
return TraderProposalWithLevels if enable_execution_levels else TraderProposal
def render_trader_proposal(proposal: TraderProposal) -> str: def render_trader_proposal(proposal: TraderProposal) -> str:
"""Render a TraderProposal to markdown. """Render a TraderProposal to markdown.
@@ -168,26 +139,13 @@ def render_trader_proposal(proposal: TraderProposal) -> str:
preserved for backward compatibility with the analyst stop-signal text preserved for backward compatibility with the analyst stop-signal text
and any external code that greps for it. and any external code that greps for it.
""" """
parts = [ return "\n".join([
f"**Action**: {proposal.action.value}", f"**Action**: {proposal.action.value}",
"", "",
f"**Reasoning**: {proposal.reasoning}", f"**Reasoning**: {proposal.reasoning}",
]
# getattr: the default TraderProposal has no level fields at all.
entry_price = getattr(proposal, "entry_price", None)
stop_loss = getattr(proposal, "stop_loss", None)
position_sizing = getattr(proposal, "position_sizing", None)
if entry_price is not None:
parts.extend(["", f"**Entry Price**: {entry_price}"])
if stop_loss is not None:
parts.extend(["", f"**Stop Loss**: {stop_loss}"])
if position_sizing:
parts.extend(["", f"**Position Sizing**: {position_sizing}"])
parts.extend([
"", "",
f"FINAL TRANSACTION PROPOSAL: **{proposal.action.value.upper()}**", f"FINAL TRANSACTION PROPOSAL: **{proposal.action.value.upper()}**",
]) ])
return "\n".join(parts)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -202,6 +160,9 @@ class PortfolioDecision(BaseModel):
extraction pass is required. Field descriptions double as the model's extraction pass is required. Field descriptions double as the model's
output instructions, so the prompt body only needs to convey context and output instructions, so the prompt body only needs to convey context and
the rating-scale guidance. the rating-scale guidance.
Like :class:`TraderProposal`, this carries no price target and no other
executable level see that class for why.
""" """
rating: PortfolioRating = Field( rating: PortfolioRating = Field(
@@ -230,33 +191,6 @@ class PortfolioDecision(BaseModel):
) )
class PortfolioDecisionWithTarget(PortfolioDecision):
"""Opt-in variant that additionally asks for a price target.
Mirrors :class:`TraderProposalWithLevels` gated behind the same
``enable_execution_levels`` config flag, off by default. See that class
for the rationale.
"""
executive_summary: str = Field(
description=(
"A concise action plan covering entry strategy, position sizing, "
"key risk levels, and time horizon. Two to four sentences."
),
)
price_target: Optional[float] = Field(
default=None,
description="Optional target price in the instrument's quote currency.",
)
def portfolio_decision_model(
enable_execution_levels: bool = False,
) -> type[PortfolioDecision]:
"""Pick the Portfolio Manager schema matching the ``enable_execution_levels`` flag."""
return PortfolioDecisionWithTarget if enable_execution_levels else PortfolioDecision
def render_pm_decision(decision: PortfolioDecision) -> str: def render_pm_decision(decision: PortfolioDecision) -> str:
"""Render a PortfolioDecision back to the markdown shape the rest of the system expects. """Render a PortfolioDecision back to the markdown shape the rest of the system expects.
@@ -272,10 +206,6 @@ def render_pm_decision(decision: PortfolioDecision) -> str:
"", "",
f"**Investment Thesis**: {decision.investment_thesis}", f"**Investment Thesis**: {decision.investment_thesis}",
] ]
# getattr: the default PortfolioDecision has no price_target field at all.
price_target = getattr(decision, "price_target", None)
if price_target is not None:
parts.extend(["", f"**Price Target**: {price_target}"])
if decision.time_horizon: if decision.time_horizon:
parts.extend(["", f"**Time Horizon**: {decision.time_horizon}"]) parts.extend(["", f"**Time Horizon**: {decision.time_horizon}"])
return "\n".join(parts) return "\n".join(parts)
+6 -13
View File
@@ -6,30 +6,23 @@ import functools
from langchain_core.messages import AIMessage from langchain_core.messages import AIMessage
from tradingagents.agents.schemas import render_trader_proposal, trader_proposal_model from tradingagents.agents.schemas import TraderProposal, render_trader_proposal
from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction from tradingagents.agents.utils.agent_utils import build_instrument_context, get_language_instruction
from tradingagents.agents.utils.structured import ( from tradingagents.agents.utils.structured import (
bind_structured, bind_structured,
invoke_structured_or_freetext, invoke_structured_or_freetext,
) )
# Instruction appended when execution levels are off (the default). Keeps the # The schema alone cannot stop the model from putting price levels into the
# model from re-introducing entry/stop/size levels in the free-text reasoning, # free-text reasoning field, so the prompt says it explicitly too.
# which the schema alone cannot prevent.
_NO_LEVELS_INSTRUCTION = ( _NO_LEVELS_INSTRUCTION = (
"Explain the reasoning behind the direction. Do NOT state entry prices, " "Explain the reasoning behind the direction. Do NOT state entry prices, "
"stop-loss levels, target prices or position sizes for this security." "stop-loss levels, target prices or position sizes for this security."
) )
_LEVELS_INSTRUCTION = "Be specific about entry price, stop loss, and position sizing."
def create_trader(llm, enable_execution_levels: bool = False): def create_trader(llm):
structured_llm = bind_structured( structured_llm = bind_structured(llm, TraderProposal, "Trader")
llm, trader_proposal_model(enable_execution_levels), "Trader"
)
levels_instruction = (
_LEVELS_INSTRUCTION if enable_execution_levels else _NO_LEVELS_INSTRUCTION
)
def trader_node(state, name): def trader_node(state, name):
company_name = state["company_of_interest"] company_name = state["company_of_interest"]
@@ -63,7 +56,7 @@ def create_trader(llm, enable_execution_levels: bool = False):
"- Minimum lot: 100 shares (main board) or 200 shares (STAR/ChiNext)\n" "- Minimum lot: 100 shares (main board) or 200 shares (STAR/ChiNext)\n"
"- Trading hours: 09:30-11:30, 13:00-15:00 Beijing time\n" "- Trading hours: 09:30-11:30, 13:00-15:00 Beijing time\n"
"Anchor your reasoning in the analysts' reports and the research plan. " "Anchor your reasoning in the analysts' reports and the research plan. "
f"{levels_instruction} " f"{_NO_LEVELS_INSTRUCTION} "
"(以上参数仅供技术研究参考,不构成投资建议)" "(以上参数仅供技术研究参考,不构成投资建议)"
), ),
}, },
-7
View File
@@ -38,13 +38,6 @@ DEFAULT_CONFIG = {
# start date (default: first day of the current month → "monthly" view); # start date (default: first day of the current month → "monthly" view);
# None keeps the previous behaviour (the model's own default, ~30). (#16) # None keeps the previous behaviour (the model's own default, ~30). (#16)
"market_lookback_days": None, "market_lookback_days": None,
# Executable price levels (entry / stop-loss / position size / price target).
# OFF by default: this project is a research and education implementation of
# the upstream TradingAgents framework, and concrete trade levels for a named
# security are what turn a research tool into an investment-advisory product.
# Turning this on is the operator's decision, and the operator is responsible
# for how the output is used and for any licensing their jurisdiction requires.
"enable_execution_levels": False,
# Debate and discussion settings # Debate and discussion settings
"max_debate_rounds": 1, "max_debate_rounds": 1,
"max_risk_discuss_rounds": 1, "max_risk_discuss_rounds": 1,
+3 -10
View File
@@ -19,17 +19,12 @@ class GraphSetup:
deep_thinking_llm: Any, deep_thinking_llm: Any,
tool_nodes: Dict[str, ToolNode], tool_nodes: Dict[str, ToolNode],
conditional_logic: ConditionalLogic, conditional_logic: ConditionalLogic,
enable_execution_levels: bool = False,
): ):
"""Initialize with required components. """Initialize with required components."""
``enable_execution_levels`` is off by default; see ``default_config.py``.
"""
self.quick_thinking_llm = quick_thinking_llm self.quick_thinking_llm = quick_thinking_llm
self.deep_thinking_llm = deep_thinking_llm self.deep_thinking_llm = deep_thinking_llm
self.tool_nodes = tool_nodes self.tool_nodes = tool_nodes
self.conditional_logic = conditional_logic self.conditional_logic = conditional_logic
self.enable_execution_levels = enable_execution_levels
def setup_graph( def setup_graph(
self, selected_analysts=["market", "social", "news", "fundamentals", "policy", "hot_money", "lockup"] self, selected_analysts=["market", "social", "news", "fundamentals", "policy", "hot_money", "lockup"]
@@ -110,15 +105,13 @@ class GraphSetup:
bull_researcher_node = create_bull_researcher(self.quick_thinking_llm) bull_researcher_node = create_bull_researcher(self.quick_thinking_llm)
bear_researcher_node = create_bear_researcher(self.quick_thinking_llm) bear_researcher_node = create_bear_researcher(self.quick_thinking_llm)
research_manager_node = create_research_manager(self.deep_thinking_llm) research_manager_node = create_research_manager(self.deep_thinking_llm)
trader_node = create_trader(self.quick_thinking_llm, self.enable_execution_levels) trader_node = create_trader(self.quick_thinking_llm)
# Create risk analysis nodes # Create risk analysis nodes
aggressive_analyst = create_aggressive_debator(self.quick_thinking_llm) aggressive_analyst = create_aggressive_debator(self.quick_thinking_llm)
neutral_analyst = create_neutral_debator(self.quick_thinking_llm) neutral_analyst = create_neutral_debator(self.quick_thinking_llm)
conservative_analyst = create_conservative_debator(self.quick_thinking_llm) conservative_analyst = create_conservative_debator(self.quick_thinking_llm)
portfolio_manager_node = create_portfolio_manager( portfolio_manager_node = create_portfolio_manager(self.deep_thinking_llm)
self.deep_thinking_llm, self.enable_execution_levels
)
# Create workflow # Create workflow
workflow = StateGraph(AgentState) workflow = StateGraph(AgentState)
-1
View File
@@ -122,7 +122,6 @@ class TradingAgentsGraph:
self.deep_thinking_llm, self.deep_thinking_llm,
self.tool_nodes, self.tool_nodes,
self.conditional_logic, self.conditional_logic,
self.config.get("enable_execution_levels", False),
) )
self.propagator = Propagator() self.propagator = Propagator()