用 LangGraph 构建 AI 交易代理
我想分享一个我特别兴奋的项目:一个自动化的金融交易代理。它是一个有状态的、自我指导的代理,能够推理目标、执行一系列金融分析步骤,并决定何时完成工作。
我最近深入研究了人工智能代理的世界,我想分享一个我特别兴奋的项目:一个自动化的金融交易代理。这不仅仅是一个简单的脚本;它是一个有状态的、自我指导的代理,能够推理目标、执行一系列金融分析步骤,并决定何时完成工作。
这个神奇之处在于 LangGraph,这是一个用于构建强大、循环代理工作流的库。让我带你了解一下我是如何构建它的。
你可以在这个 GitHub 仓库 中找到完整的代码。
1、整体思路:一切皆是状态机
在这个代理的核心,是一个状态机。它在两个关键状态之间循环:思考和行动。它思考下一步该做什么,使用工具执行动作,然后根据结果决定是否再次思考或完成。这个循环会持续下去,直到代理的目标达成。
这是此代码创建的工作流程的可视化表示:

系统分为两个主要文件:main.py(大脑)和 tools.py(力量)。
2、main.py ~ 代理的大脑
这个文件定义了图、状态和决策逻辑。
2.1 状态:代理的记忆
首先,我定义了一个 TypedDict 来结构化代理的记忆。这个 AgentState 是在节点之间传递的单一真相来源。
class AgentState(TypedDict):
goal: str
past_steps: List[str]
action_command: str
result: str
goal: 总体任务(例如,“获取 TSLA,计算 SMA,然后回测”)。
past_steps: 所有先前操作和其结果的列表。这对于上下文至关重要。
action_command: 要执行的下一个命令(例如,FETCH “TSLA”)。
result: 最新工具执行的输出。
2.2 节点:思考和行动
图表有两个节点:
1. executor_node: 这是“思考者”。它使用 LLM(GPT-4o-mini)基于目标和过去步骤来决定下一个命令。我提示 LLM 只输出原始命令,这使得解析变得容易。
def executor_node(state: AgentState) -> dict:
# … (setup and prompt)
prompt = f"""
You are a financial trading agent.
Your goal: {state['goal']}
Past Steps:
{past_steps}
Available Commands:
1. FETCH "TICKER"
2. INDICATOR "TICKER" "INDICATOR_NAME"
# … etc …
"""
response = llm.invoke(prompt)
return {"action_command": response.content.strip()}
2. tool_node: 这是“执行者”。它从状态中获取 action_command 并通过 execute_command 函数运行它。然后将动作及其结果添加到 past_steps 中,这样执行器就知道发生了什么。
这里的一个关键设计选择是处理 BACKTEST 命令。由于回测通常是分析的最后一步,工具节点会自动将下一步设置为 FINISH,迫使图表在下一次循环中结束。
if command.startswith("BACKTEST"):
return {
"past_steps": state["past_steps"] + [f"Action: {command}\nResult: {result}"],
"action_command": f'FINISH "{result}"', # Force a finish
"result": result
}
2.3 条件边:决策点
循环的真正智能在于 should_continue 函数。在工具节点运行后,这个函数检查状态以确定下一步应该发生什么。
def should_continue(state: AgentState) -> str:
if state["action_command"].startswith("FINISH"):
return "end"
return "continue"
如果最后一个命令是 FINISH,则图表结束。否则,它会路由回 executor_node 以选择下一个动作。
2.4 组装图
最后,我们用 LangGraph 非常声明式地将所有内容连接在一起。
workflow = StateGraph(AgentState)
workflow.add_node("executor", executor_node)
workflow.add_node("tools", tool_node)
workflow.set_entry_point("executor")
workflow.add_edge("executor", "tools")
workflow.add_conditional_edges("tools", should_continue, {"continue": "executor", "end": END})
app = workflow.compile()
3、tools.py ~ 代理的力量
这个文件包含与外部世界交互的实际函数。我使用了 yfinance 来获取实时市场数据。
fetch_data(ticker): 获取最新的股票价格数据。
calculate_indicator(ticker, indicator): 计算技术指标如 SMA 或 RSI。
backtest_strategy(ticker, strategy): 这是最复杂的函数。它运行一个简单的回测(目前是 SMA 交叉策略),计算累计回报,并…这是我最喜欢的部分~生成一个可视化图表 :)
在回测期间调用 plot_chart 函数。它创建一个双面板图表:
顶部面板: 价格序列、SMA 和交易进入(绿色向上三角形表示盈利,红色表示亏损)和退出(向下三角形)的视觉标记。
底部面板: 资产曲线,将策略的表现与简单的“买入并持有”方法进行比较。
这些图表会自动保存到 charts/ 目录中,为代理的最终答案提供清晰的可视化输出。
4、运行代理
当你执行 main.py 时,它会流式传输事件,让你实时观看代理的思维过程。
if __name__ == "__main__":
GOAL = "Fetch TSLA, calculate SMA, then backtest SMA strategy"
inputs = {"goal": GOAL, "past_steps": [], "result": ""}
for event in app.stream(inputs):
for k, v in event.items():
if k != "__end__":
print(f"\n - - Event: {k} - -\n{v}")
你会看到类似这样的输出:
--- EXECUTOR ---
--- Event: executor ---
{'action_command': 'FETCH "TSLA"'}
--- TOOLS ---
/home/rejith/Sites/Agent/tools.py:8: FutureWarning: YF.download() has changed argument auto_adjust default to True
df = yf.download(ticker, period="1y", interval="1d")
[*********************100%***********************] 1 of 1 completed
--- Event: tools ---
{'past_steps': ['Action: FETCH "TSLA"\nResult: Price Close High Low Open Volume\nTicker TSLA TSLA TSLA TSLA TSLA\nDate \n2025-10-20 447.429993 449.799988 440.609985 443.869995 63719000\n2025-10-21 442.600006 449.299988 442.049988 445.760010 54412200\n2025-10-22 438.970001 445.540009 429.000000 443.450012 84023500\n2025-10-23 448.980011 449.399994 413.899994 420.000000 126709800\n2025-10-24 433.720001 451.679993 430.170013 446.829987 94408400'], 'result': 'Price Close High Low Open Volume\nTicker TSLA TSLA TSLA TSLA TSLA\nDate \n2025-10-20 447.429993 449.799988 440.609985 443.869995 63719000\n2025-10-21 442.600006 449.299988 442.049988 445.760010 54412200\n2025-10-22 438.970001 445.540009 429.000000 443.450012 84023500\n2025-10-23 448.980011 449.399994 413.899994 420.000000 126709800\n2025-10-24 433.720001 451.679993 430.170013 446.829987 94408400'}
--- EXECUTOR ---
--- Event: executor ---
{'action_command': 'INDICATOR "TSLA" "SMA"'}
--- TOOLS ---
/home/rejith/Sites/Agent/tools.py:12: FutureWarning: YF.download() has changed argument auto_adjust default to True
df = yf.download(ticker, period="6mo", interval="1d")
[*********************100%***********************] 1 of 1 completed
--- Event: tools ---
{'past_steps': ['Action: FETCH "TSLA"\nResult: Price Close High Low Open Volume\nTicker TSLA TSLA TSLA TSLA TSLA\nDate \n2025-10-20 447.429993 449.799988 440.609985 443.869995 63719000\n2025-10-21 442.600006 449.299988 442.049988 445.760010 54412200\n2025-10-22 438.970001 445.540009 429.000000 443.450012 84023500\n2025-10-23 448.980011 449.399994 413.899994 420.000000 126709800\n2025-10-24 433.720001 451.679993 430.170013 446.829987 94408400', 'Action: INDICATOR "TSLA" "SMA"\nResult: Price Close SMA20\nTicker TSLA \nDate \n2025-10-20 447.429993 436.774498\n2025-10-21 442.600006 437.611998\n2025-10-22 438.970001 437.420998\n2025-10-23 448.980011 438.700497\n2025-10-24 433.720001 438.366498'], 'result': 'Price Close SMA20\nTicker TSLA \nDate \n2025-10-20 447.429993 436.774498\n2025-10-21 442.600006 437.611998\n2025-10-22 438.970001 437.420998\n2025-10-23 448.980011 438.700497\n2025-10-24 433.720001 438.366498'}
--- EXECUTOR ---
--- Event: executor ---
{'action_command': 'BACKTEST "TSLA" "SMA"'}
--- TOOLS ---
/home/rejith/Sites/Agent/tools.py:27: FutureWarning: YF.download() has changed argument auto_adjust default to True
df = yf.download(ticker, period="1y", interval="1d")
[*********************100%***********************] 1 of 1 completed
--- Event: tools ---
{'past_steps': ['Action: FETCH "TSLA"\nResult: Price Close High Low Open Volume\nTicker TSLA TSLA TSLA TSLA TSLA\nDate \n2025-10-20 447.429993 449.799988 440.609985 443.869995 63719000\n2025-10-21 442.600006 449.299988 442.049988 445.760010 54412200\n2025-10-22 438.970001 445.540009 429.000000 443.450012 84023500\n2025-10-23 448.980011 449.399994 413.899994 420.000000 126709800\n2025-10-24 433.720001 451.679993 430.170013 446.829987 94408400', 'Action: INDICATOR "TSLA" "SMA"\nResult: Price Close SMA20\nTicker TSLA \nDate \n2025-10-20 447.429993 436.774498\n2025-10-21 442.600006 437.611998\n2025-10-22 438.970001 437.420998\n2025-10-23 448.980011 438.700497\n2025-10-24 433.720001 438.366498', 'Action: BACKTEST "TSLA" "SMA"\nResult: Backtest completed for TSLA (SMA).\nChart saved: charts/chart_TSLA_e97bc4.png\n\n Returns Strategy\nDate \n2025-10-20 0.473997 0.226798\n2025-10-21 0.463202 0.216003\n2025-10-22 0.455001 0.207801\n2025-10-23 0.477804 0.230605\n2025-10-24 0.443816 0.196616'], 'action_command': 'FINISH "Backtest completed for TSLA (SMA).\nChart saved: charts/chart_TSLA_e97bc4.png\n\n Returns Strategy\nDate \n2025-10-20 0.473997 0.226798\n2025-10-21 0.463202 0.216003\n2025-10-22 0.455001 0.207801\n2025-10-23 0.477804 0.230605\n2025-10-24 0.443816 0.196616"', 'result': 'Backtest completed for TSLA (SMA).\nChart saved: charts/chart_TSLA_e97bc4.png\n\n Returns Strategy\nDate \n2025-10-20 0.473997 0.226798\n2025-10-21 0.463202 0.216003\n2025-10-22 0.455001 0.207801\n2025-10-23 0.477804 0.230605\n2025-10-24 0.443816 0.196616'}
在你的 charts 文件夹中,你会找到一个生成的 PNG 文件,显示完整的回测结果!

4、我学到的东西和未来步骤
构建这个项目是我理解有状态 AI 代理的一种绝佳方式。推理(图表)和执行(工具)之间的清晰分离使系统非常灵活且易于扩展。
接下来呢?
- 更多工具: 添加新闻情绪分析、投资组合优化甚至模拟交易的命令。
- 更智能的状态管理: past_steps 列表可能会变得很长。我可能会使用向量存储来进行长期记忆,以避免上下文窗口限制。
- 人机协作: 添加一个可以暂停执行并请求人类批准或澄清的节点,将是迈向稳健性的巨大进步。
5、必要的警告
构建这个代理是一种极好的方式来理解有状态的 AI 工作流程。LangGraph 提供的框架确实感觉像是构建复杂、可靠代理的正确抽象,我很期待看到在此基础上还能构建出什么。
然而,必须用现实来平衡这种兴奋。我刚刚展示的系统只是一个技术演示。它是一个原型,旨在说明 AI 代理的架构,而不是一个强大的金融分析引擎。
回测逻辑是有意简化,整个管道缺乏进行实盘交易所需的严谨性。金融市场是复杂的,没有经过充分验证、风险管理以及对底层假设的深入了解就部署自动化策略,会导致严重的财务损失。
请将此代码作为 AI 开发的学习工具,而不是投资决策的基础。在考虑真实资金之前,请始终进行专业级别的回测和验证。
原文链接:Building a Stateful AI Trading Agent with LangGraph
汇智网翻译整理,转载请标明出处