用 LangGraph 构建 AI 交易代理

我想分享一个我特别兴奋的项目:一个自动化的金融交易代理。它是一个有状态的、自我指导的代理,能够推理目标、执行一系列金融分析步骤,并决定何时完成工作。

用 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 文件,显示完整的回测结果!

带有 26.67% 交易准确率的 TSLA :’)

4、我学到的东西和未来步骤

构建这个项目是我理解有状态 AI 代理的一种绝佳方式。推理(图表)和执行(工具)之间的清晰分离使系统非常灵活且易于扩展。

接下来呢?

  • 更多工具: 添加新闻情绪分析、投资组合优化甚至模拟交易的命令。
  • 更智能的状态管理: past_steps 列表可能会变得很长。我可能会使用向量存储来进行长期记忆,以避免上下文窗口限制。
  • 人机协作: 添加一个可以暂停执行并请求人类批准或澄清的节点,将是迈向稳健性的巨大进步。

5、必要的警告

构建这个代理是一种极好的方式来理解有状态的 AI 工作流程。LangGraph 提供的框架确实感觉像是构建复杂、可靠代理的正确抽象,我很期待看到在此基础上还能构建出什么。

然而,必须用现实来平衡这种兴奋。我刚刚展示的系统只是一个技术演示。它是一个原型,旨在说明 AI 代理的架构,而不是一个强大的金融分析引擎。

回测逻辑是有意简化,整个管道缺乏进行实盘交易所需的严谨性。金融市场是复杂的,没有经过充分验证、风险管理以及对底层假设的深入了解就部署自动化策略,会导致严重的财务损失。

请将此代码作为 AI 开发的学习工具,而不是投资决策的基础。在考虑真实资金之前,请始终进行专业级别的回测和验证。


原文链接:Building a Stateful AI Trading Agent with LangGraph

汇智网翻译整理,转载请标明出处