mirror of
https://github.com/simonlin1212/TradingAgents-astock.git
synced 2026-08-31 01:23:38 +00:00
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:
+8
-7
@@ -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」。
|
||||||
|
|
||||||
### 移除
|
### 移除
|
||||||
|
|||||||
@@ -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,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):
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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} "
|
||||||
"(以上参数仅供技术研究参考,不构成投资建议)"
|
"(以上参数仅供技术研究参考,不构成投资建议)"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user