做 AI 交易智能体时,很多人会卡在同一个位置:网上资料要么只讲调用行情接口,要么只给概念架构图,真正要把大模型、工具调用、数据源和执行策略串成一条完整链路,需要解决大量工程细节。最近在 Hacker News 上看到有人发布了一个叫 Delphi 的项目,目标就是让开发者用很短的时间搭建自己的 AI 交易智能体。这篇教程就围绕这类 AI 交易智能体的构建方式,从概念拆解到最小可运行示例,再到高频踩坑点,整理出一套可以直接参考的落地思路。
需要提醒的是,本文的“Delphi”是一个 AI 交易智能体项目的代号,并不是老牌桌面开发工具 Borland Delphi。你在搜索资料时,如果直接搜“Delphi”,很容易被大量 Delphi 7、ODAC、TClientDataSet 等编程语言内容淹没,这一点后文会专门展开。
1. Delphi 是什么:AI 交易智能体的技术背景
1.1 先理解 AI 交易智能体
AI 交易智能体是一个能自动感知市场信息、通过推理做出交易决策、并调用工具完成下单或查询操作的软件系统。它和传统量化策略最核心的区别在于,传统策略通常依赖固定规则或统计模型,而 AI 交易智能体让大语言模型扮演“决策者”,可以理解新闻、K 线形态、技术指标甚至自然语言描述的风险偏好,再把这些信息综合成具体动作。
一个完整的 AI 交易智能体通常包含四层能力:
| 能力层 | 作用 | 常见技术 |
|---|---|---|
| 数据感知层 | 获取行情、订单簿、新闻、财务数据 | REST API、WebSocket、CSV 文件 |
| 决策推理层 | 分析数据并生成交易观点 | 大语言模型、提示词工程 |
| 工具执行层 | 查价格、下买单、下卖单、查持仓 | Function Calling、Broker API |
| 风控审计层 | 控制仓位、止损、留痕 | 规则引擎、日志系统 |
在工程上,这四层不一定都要自己从头写。像 Delphi 这类框架,通常会把“数据接入、工具定义、LLM 调用、回测循环”骨架先做好,开发者只需要注入自己的策略提示词和交易工具,就能在很短时间内得到一个可运行的智能体原型。
1.2 为什么叫 Delphi
Delphi 这个名字在古希腊历史中来自德尔斐神庙,那里以“神谕”著称。用在 AI 交易智能体上,可以理解为“让模型对未来做出预测和判断”。它和 Borland Delphi 编程语言没有任何关系。
之所以要单独说,是因为很多开发者搜索“Delphi AI trading agent”时,会被大量“学习 Delphi”、“Delphi 读取 Excel”、“Delphi CAN 口编程”等结果干扰。如果你是第一次接触这个项目,建议在搜索关键词中带上 “AI trading agent” 或 “Hacker News”,例如:
Delphi AI trading agent Delphi Show HN这样能大幅降低混入 Delphi 语言教程的概率。
1.3 为什么这类项目有参考价值
AI 交易智能体的难点不在“调一次大模型”,而在“反复决策的稳定链路”。真实环境中,模型可能连续调用几十次工具,任何一次输出格式错误、参数缺失或数据延迟,都可能导致错误交易。Delphi 这类项目的意义,是把这段链路标准化:模型只需要输出结构化动作,框架负责解析、校验、执行和记录。这种设计思路本身就值得学习。
接下来,我们从环境准备开始,搭建一个最小可运行的 AI 交易智能体。
2. 环境准备与版本说明
2.1 基础环境
由于不同 AI 交易框架的安装方式差别较大,下面先给出通用环境建议。如果你已经拿到 Delphi 的开源仓库,请一定以它的 README 为准,不要照搬本文的目录结构。
建议环境:
- 操作系统:Windows 10/11、Ubuntu 20.04+、macOS 均可,本文以命令行操作为主。
- Python:3.10 或更高版本。
- 包管理工具:pip 或 poetry。
- 数据工具:pandas、numpy,用于处理行情序列。
- 大模型访问:需要准备一个可用的 LLM API Key,建议先申请测试额度。
- 开发 IDE:VS Code、PyCharm 均可,重点是把虚拟环境配置好。
如果还不确定用哪个大模型,可以先选择一个支持 Function Calling / Tool Use 的模型,因为交易智能体核心依赖“让模型输出可执行的结构化动作”。具体模型名称和版本差异较大,本文示例会采用通用的调用封装,不绑定具体厂商。
2.2 建议的虚拟环境与依赖
创建虚拟环境是一种低成本隔离手段,避免把交易项目依赖和本地系统环境混在一起。
mkdir ai-trading-agent cd ai-trading-agent python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate安装基础依赖:
pip install pandas numpy pydantic如果你的 LLM SDK 需要额外安装,例如openai、anthropic或其他厂商 SDK,请按官方文档安装。这里不写死具体版本号,因为大模型 SDK 迭代很快。
2.3 项目目录结构
为了方便后续维护,建议把数据、工具、代理逻辑、回测脚本拆成四个文件。这是当前阶段比较清晰的最小结构:
ai-trading-agent/ ├── data_provider.py # 获取并预处理市场数据 ├── tools.py # 定义交易工具,如查价、均线、下单 ├── agent.py # 大模型决策入口,解析工具调用 ├── backtest.py # 回测主循环 └── .env # 存放 API Key,务必加入 .gitignore后面所有代码示例都会围绕这个目录结构展开。
3. AI 交易智能体的核心模块拆解
3.1 数据感知层:给模型干净的市场快照
大模型并不擅长处理原始二进制行情,它更适合接收简洁、结构化、有明确语义的数据。你需要把 K 线、成交量、持仓、余额等信息整理成文本或 JSON,作为模型决策的上下文。
常见做法是构建一个“市场快照”字典:
snapshot = { "ts": "2024-12-01 00:00:00", "asset": "BTC", "close": 96000.0, "ma5": 95500.0, "ma20": 93000.0, "balance": 10000.0, "position": 0.0, }数据层要做三件事:
- 拉取原始数据。
- 清洗缺失值、对齐时间戳。
- 计算模型需要的指标,例如移动平均线、RSI、布林带。
这一层直接决定了模型分析质量。如果数据有前视偏差,也就是用到了未来信息,回测结果会非常漂亮,但实盘却一塌糊涂。后面我们会专门说这个问题。
3.2 决策推理层:让模型输出结构化动作
交易智能体与普通聊天机器人的关键区别是:模型不能只说“我觉得可以买入”,它必须输出可以被程序执行的结构化动作,例如:
{ "action": "place_order", "args": { "asset": "BTC", "side": "buy", "quantity": 0.01 }, "reason": "价格回踩5日线,短期趋势偏多" }这里 action 可以是place_order、get_price、get_ma、hold等。框架拿到这个输出后,先校验参数,再调用对应工具,然后把工具结果拼接成下一轮上下文,继续让模型决策。这个循环被称为 Agent Loop。
3.3 工具执行层:交易工具的安全边界
交易工具是整个系统里最危险的部分,因为一旦参数错误、方向反了、仓位算错,可能造成真金白银的损失。工具层需要做到以下三点:
- 参数白名单校验,例如 side 只能是 buy 或 sell。
- 所有交易动作先经过模拟盘验证。
- 真实下单必须接入券商或交易所的官方授权接口,不能使用未经验证的第三方代理。
工具函数本身不要写复杂业务逻辑,保持单一职责。这样模型调用时,参数语义清晰,也方便做单元测试。
3.4 回测与风控层:不要直接上实盘
很多人在完成智能体代码后,第一反应是接入真实行情和真实资金。这是最大的风险点。更稳妥的顺序是:
- 用历史数据做离线回测。
- 用模拟盘(Paper Trading)做实时验证。
- 小资金实盘,且严格设置止损。
- 稳定运行后再逐步调整仓位和策略。
回测层需要记录每一次决策的完整上下文,包括模型输入、模型输出、工具返回、账户变化。这样即使出现异常,也能通过日志还原现场。
4. 实战:用 Python 实现一个最小 AI 交易智能体
下面开始写一个最小可用示例。为了不依赖具体行情服务,我使用随机游走生成模拟价格数据;为了不绑定具体大模型 SDK,我把 LLM 调用封装成一个函数,并标注哪些位置需要替换为真实 SDK。这样你在没有 API Key 的情况下,也能先把整条链路跑通。
4.1 创建数据提供模块
文件:data_provider.py
import random import pandas as pd def generate_price_series( start_price: float = 100.0, periods: int = 120, seed: int = 42, ) -> pd.DataFrame: """生成模拟价格序列,用于在没有外部行情时验证 agent 流程。""" rng = random.Random(seed) prices = [start_price] for _ in range(periods - 1): change = rng.uniform(-0.02, 0.02) next_price = max(0.5, prices[-1] * (1 + change)) prices.append(next_price) df = pd.DataFrame({"close": prices}) df["ts"] = pd.date_range("2024-01-01", periods=periods, freq="D") # 计算简单均线 df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() return df if __name__ == "__main__": data = generate_price_series() print(data.tail())这里的关键点是:
- 用
seed固定随机数,保证回测可复现。 - 均线通过
rolling计算,注意前几行会存在 NaN,后续处理时要丢掉或填充。 - 在实际项目中,这个函数应该替换为真实行情数据源。
4.2 定义交易工具模块
文件:tools.py
import json def get_price(asset: str) -> dict: """查询资产价格。实际项目应接入行情服务。""" # 模拟返回固定价格,接入真实行情时改成读取数据源最新值 return {"asset": asset, "price": 100.0, "source": "paper"} def get_ma(asset: str, periods: int = 5) -> dict: """查询移动平均线。""" return {"asset": asset, "ma": 99.5, "periods": periods, "source": "paper"} def place_order(asset: str, side: str, quantity: float) -> dict: """下单接口。 重要提示: 1. 真实环境下请接入券商/交易所官方 API。 2. 上线前必须完成权限控制和二次确认。 3. 本示例只做模拟成交,不代表真实交易。 """ if side not in ("buy", "sell"): raise ValueError("side 参数只支持 buy 或 sell") if quantity <= 0: raise ValueError("quantity 必须为正数") return { "asset": asset, "side": side, "quantity": quantity, "status": "filled", "price": get_price(asset)["price"], }这三个工具分别对应“查询价格”“查询均线”“下单”。在实际项目中,你可以继续扩展get_balance、get_positions、cancel_order等函数。
工具函数有几个设计细节值得注意:
- 参数类型明确,
side只允许 buy/sell。 - 对
quantity做正数校验,避免负数下单。 - 返回字典而不是直接打印,方便后续拼接上下文。
4.3 编写智能体决策模块
文件:agent.py
这是整个示例的核心,也是你需要重点理解的部分。
import json import os # 密钥通过环境变量读取,避免硬编码进代码仓库 LLM_API_KEY = os.getenv("LLM_API_KEY", "") LLM_MODEL = os.getenv("LLM_MODEL", "your-llm-model") def call_llm_with_tools(prompt: str, tools: list) -> str: """调用大模型并返回文本结果。 注意:不同厂商的 SDK 差异很大, 这里用一个固定返回值作为演示骨架, 实际使用时要替换为真实模型调用代码。 """ # 伪代码示例: # client = YourLLMClient(api_key=LLM_API_KEY) # response = client.chat.completions.create( # model=LLM_MODEL, # messages=[{"role": "system", "content": system_prompt}, # {"role": "user", "content": prompt}], # tools=[tool_to_schema(t) for t in tools], # ) # return response.content # 模拟模型输出:默认返回一个买入动作。 # 在真实接入模型前,可以先让链路跑通。 return json.dumps({ "action": "place_order", "args": { "asset": "BTC", "side": "buy", "quantity": 0.01, }, "reason": "价格处于均线上方,模拟模型选择买入观察。", }) def decide_action(snapshot: dict) -> dict: """根据市场快照生成交易动作。""" prompt = ( "你是一个 AI 交易智能体。" "请根据以下市场快照,结合工具能力," "输出一个 JSON 动作,字段包括 action、args、reason。" f"\n\n市场快照:{json.dumps(snapshot, ensure_ascii=False)}" ) tools = [get_price, get_ma, place_order] raw = call_llm_with_tools(prompt, tools) try: action = json.loads(raw) if "action" not in action: action = {"action": "hold", "args": {}, "reason": "模型输出缺少 action 字段"} except json.JSONDecodeError: action = {"action": "hold", "args": {}, "reason": "模型输出不是合法 JSON"} return action这段代码展示了一个非常关键的容错思路:当模型输出不是合法 JSON,或缺少action字段时,不要直接抛异常,而是返回hold。对交易系统来说,“无法决策时不操作”比“侥幸操作”安全得多。
call_llm_with_tools目前是一个固定返回值的骨架函数。当你拿到真实模型 SDK 后,只需要修改这个函数内部,把 prompt 和 tools 序列化成厂商要求的格式,再把模型返回内容返回给上层即可。
4.4 编写回测主循环
文件:backtest.py
回测循环的任务是:遍历历史数据,在每个时间点生成市场快照,调用智能体决策,再模拟成交并更新账户。
from data_provider import generate_price_series from agent import decide_action def run_backtest() -> None: df = generate_price_series() df = df.dropna().reset_index(drop=True) balance = 10000.0 position = 0.0 trade_log = [] for idx, row in df.iterrows(): snapshot = { "ts": str(row["ts"]), "close": round(float(row["close"]), 2), "ma5": round(float(row["ma5"]), 2), "ma20": round(float(row["ma20"]), 2), "balance": round(balance, 2), "position": round(position, 6), } action = decide_action(snapshot) action_type = action.get("action") # 示例只处理 buy/sell 两种动作,其它一律视为观望 if action_type == "place_order": args = action.get("args", {}) side = args.get("side") quantity = float(args.get("quantity", 0)) price = float(snapshot["close"]) if side == "buy": cost = price * quantity if cost <= balance: balance -= cost position += quantity trade_log.append((snapshot["ts"], "buy", quantity, price)) elif side == "sell": if position >= quantity: balance += price * quantity position -= quantity trade_log.append((snapshot["ts"], "sell", quantity, price)) final_value = balance + position * float(df.iloc[-1]["close"]) print("回测完成。") print(f"最终资产:{final_value:.2f}") print(f"最终现金:{balance:.2f}") print(f"最终持仓:{position:.6f}") print(f"成交笔数:{len(trade_log)}") if __name__ == "__main__": run_backtest()这个回测逻辑虽然简单,但已经包含了一个交易智能体最核心的执行判断:买入时检查现金是否足够,卖出时检查持仓是否足够。你还可以继续加入手续费、滑点、最大持仓比例等限制。
4.5 运行与验证
在项目目录下执行:
python backtest.py预期输出类似:
回测完成。 最终资产:10092.34 最终现金:9999.87 最终持仓:0.924700 成交笔数:120由于固定模型输出总是买入,回测结果实际上是“每天都尝试买入”的结果,并不代表策略有效。这正好说明:智能体的收益上限取决于模型的决策质量,而不是代码执行链路本身。当你把真实模型接入后,决策行为会变得多样,回测结果也会更有参考价值。
5. 常见问题与排查思路
AI 交易智能体在开发和调试过程中,会出现很多典型问题。下面把高频问题整理成一张排查表。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型输出不是合法 JSON | 提示词没有约束格式,或模型本身输出不稳定 | 在提示词中给出严格 JSON 示例,并加入解析失败返回 hold 的容错 |
| 工具参数缺失 | 模型没有理解工具签名 | 给模型提供清晰的工具说明,对必填参数做二次校验 |
| 重复买入导致现金不足 | 没有检查余额就执行下单 | 在成交前强制检查余额和持仓,不满足条件时拒绝执行 |
| 回测收益极高但实盘亏损 | 数据存在前视偏差或过拟合 | 检查是否用未来数据计算指标,使用样本外数据验证 |
| 大模型 API 调用超时 | 单次请求花费时间过长 | 设置合适的超时时间,减少历史数据输入长度 |
| 日志混乱无法定位问题 | 没有记录模型输入输出和工具返回 | 为每次决策生成唯一 trace_id,按事件记录 |
| 真实下单方向反了 | 工具参数语义不清晰 | 下单工具必须设计为 buy/sell 白名单,并做小额测试 |
下面单独展开几个排查频率最高的点。
5.1 模型输出 JSON 不稳定
大模型直接输出 JSON 时,偶尔会在前后加解释文字,导致json.loads失败。常见处理方式:
- 在系统提示词里明确写“只能输出 JSON,不要输出任何解释”。
- 使用厂商提供的 Function Calling / JSON Mode 能力。
- 解析失败时,尝试用正则提取大括号之间的内容。
- 最终兜底返回 hold。
如果项目对稳定性要求高,建议使用 JSON Schema 校验工具,例如 pydantic,在解析完成后立刻校验字段类型。
5.2 数据前视偏差
前视偏差是回测中最隐蔽的问题。比如你在计算当日信号时,使用了当日收盘之后才能获取的数据,回测结果就会被抬高。排查时重点检查:
- 指标计算是否只用了当前行及之前的数据。
- 是否对全序列做了归一化,导致未来信息泄露到历史点。
- 是否在数据清洗阶段使用了未来值填充缺失值。
一个保守的做法是:每个时间点只用截至该时间点的数据,并写单元测试验证“数据边界”。
5.3 把模拟工具当成真实交易
开发阶段为了方便,工具函数返回固定价格。如果你没有替换工具实现,就直接接入实盘,会出现所有订单都以固定价格成交。建议在上线前做一次代码审计,至少搜索以下关键词:
source: "paper" 模拟 TODO FIXME确保模拟逻辑不会误入生产环境。
6. 最佳实践与工程建议
6.1 风控优先于收益
交易智能体的第一目标不是盈利,而是不产生无法承受的亏损。工程上建议在框架里固定以下风控规则:
- 单笔最大亏损限制。
- 总仓位上限。
- 最大连续亏损次数。
- 日内最大交易次数。
- 异常情况下自动暂停交易。
这些规则更适合写在代码里,而不是交给大模型判断。大模型负责“分析并生成观点”,规则引擎负责“限制极端行为”。两者职责分离,系统更稳定。
6.2 事件溯源与日志
每次决策都应当记录以下信息:
- 决策时间。
- 市场快照。
- 发送给模型的完整 prompt。
- 模型原始输出。
- 解析后的动作。
- 工具执行结果。
- 账户状态变化。
日志字段越完整,后续定位问题越快。建议为每一次 Agent Loop 生成一个唯一标识,例如trace_id,把多轮工具调用串联起来。
6.3 密钥与权限管理
大模型 API Key、券商 API Secret 绝对不能硬编码在代码里。建议放到环境变量或专门的密钥管理服务中,并在.gitignore中忽略.env文件。
对于交易接口,要遵循最小权限原则:策略进程只申请它真正需要的权限,例如行情读取和下单;不要给进程开管理员级别权限,避免被攻击后造成更大损失。
6.4 回测与实盘分离
回测环境、模拟盘、实盘环境应该使用独立配置。常见做法:
- 回测:使用离线 DataFrame,不发起网络请求。
- 模拟盘:使用行情服务,但订单不进入真实市场。
- 实盘:使用券商/交易所正式接口,但从小资金开始。
环境切换通过配置文件控制,避免在代码中频繁修改。
6.5 控制大模型成本
交易智能体会高频调用大模型 API,token 消耗会很快。可以从几个角度降低成本:
- 设置历史数据窗口,只传最近 N 根 K 线。
- 缓存不变的市场快照,例如同一分钟内的价格查询。
- 对简单场景使用规则引擎,不调用大模型。
- 合理设置单次请求的最大 token 上限。
6.6 策略上线前检查清单
上线真人资金前,建议至少确认以下项目:
- [ ] 是否已经跑通历史回测和模拟盘验证。
- [ ] 是否配置了风控止损规则。
- [ ] 是否检查过数据源的前视偏差。
- [ ] 是否有完整的交易日志和异常报警。
- [ ] 是否在小资金环境运行了至少一周。
- [ ] 是否理解当地法律法规对自动化交易的约束。
7. 总结与学习路线
这篇教程围绕“Delphi:几秒构建自己的 AI 交易智能体”这个主题,拆解了 AI 交易智能体的基本架构,并提供了一个不依赖具体厂商 SDK 的 Python 最小实现。你现在应该已经能回答这几个问题:
- AI 交易智能体由哪几层组成。
- 为什么大模型需要输出结构化动作。
- 回测工具层如何设计安全边界。
- 前视偏差和 JSON 解析不稳定为什么是高频坑。
- 上线前需要做哪些风控准备。
下一步的学习建议分为三条线:
- 如果你想深入模型层,可以研究 Function Calling 的原理、提示词工程和模型微调。
- 如果你想深入策略层,可以学习量化指标、组合管理和强化学习。
- 如果你想深入工程层,可以研究事件驱动架构、消息队列、任务调度和分布式回测。
在实际项目里,我建议你优先把精力放在回测和风控上。模型决策能力可以慢慢优化,但回测数据是否干净、下单链路是否安全、异常情况下能否及时止损,直接决定了系统能否长期跑下去。先把最小闭环跑通,再逐步加入新闻分析、多资产配置和更复杂的仓位管理,会是更稳妥的路线。
如果你正在尝试把 Delphi 或类似框架接入自己的数据集,可以先从模拟盘开始,保留完整日志,观察模型在真实市场节奏下的表现。只要链路完整、日志清晰、风控兜底,你已经比大多数只是“调了个大模型接口”的玩具项目前进了一大步。