在实际的桌面与后端技术方案中,用 Delphi 快速构建一个 AI 交易 agent 并不是天方夜谭。这个项目标题里的核心关键词很明确:Delphi、AI、agent。目标是在秒级完成一个可运行的交易决策工作流:拉取行情、交给大模型分析、生成结构化交易信号、再转成订单指令并记录日志。本篇文章会从原理、环境、最小案例、关键代码、验证方式、排错链路和生产化建议几个角度展开,帮助你用 Object Pascal 快速搭出一个能跑、能查、能扩展的 AI trading agent 框架。
需要注意,文中所有示例都是技术研究用途,不构成任何投资建议。真实交易涉及资金安全、合规审查、市场风险和系统稳定性,示例只适合在模拟行情、回测环境和自己掌控的测试账号中运行。
1. 先理解 AI 交易 Agent 的组成,再谈 Delphi 为什么合适
1.1 AI 交易 Agent 是什么
AI 交易 agent 本质上是把“数据采集、策略决策、订单执行、结果记录”四个环节连成一条自动化链路。传统程序化交易靠人写死规则,例如“价格突破 20 日均线就买入”;AI 交易 agent 则把决策环节替换成大模型或者机器学习模型,让模型根据行情上下文输出动作。
这个动作不能是一段随意自然语言,必须是结构化的,例如:
{ "action": "buy", "symbol": "BTCUSDT", "quantity": 0.01, "confidence": 0.78, "reason": "short_term_momentum_and_volume_increase" }agent 要做的事就是把上面的 JSON 从模型输出中解析出来,校验 legality、做风控,最后转成交易所或券商接口能识别的订单结构。所以它并不是“一个模型”,而是一个围绕模型构建的工程系统。
1.2 Delphi 在这个场景里的定位
Delphi 的强项是 RADE(快速应用开发),编译生成原生程序,不依赖虚拟机,启动速度快,适合做需要长时间运行的桌面行情监控程序、本地服务程序。相比 Python 这类解释型语言,Delphi 在内存占用、并发线程稳定性、GUI 与后台任务结合方面都有优势。
AI 部分并不需要在 Delphi 里训练模型。Delphi 通过 HTTP 调用大模型 API,例如 OpenAI 兼容接口、本地 Ollama 服务、或者国内模型厂商提供的接口,拿到返回结果后使用System.JSON解析。这样分工很清晰:
- Delphi 负责工程:调度、网络、解析、风控、日志、界面。
- AI 模型负责语义理解:根据行情文本输出交易动作。
这也是“Build your own AI trading agent in seconds”这类项目能成立的原因,因为不需要从零写模型推理,只要把工程链路串好就可以。
1.3 整体工作链路
一条最小可运行链路大概是这样:
定时器触发 -> 拉取最新行情 -> 组装行情上下文文本 -> 发送给 AI 模型 -> 获取模型返回 JSON -> 解析交易信号 -> 风控校验 -> 模拟下单 -> 写日志在工程实现上,这个链路可以拆成下面几个模块:
| 模块 | 职责 | 关键技术点 |
|---|---|---|
| 行情客户端 | 拉取最新价格、K 线、成交量 | REST/WebSocket、JSON 解析、缓存 |
| 提示词构造器 | 把行情转成模型可读文本 | 文本模板、上下文长度控制、字段白名单 |
| AI 客户端 | 调用大模型接口 | HTTP 客户端、超时、重试、API Key 管理 |
| 信号解析器 | 解析模型返回的 JSON | 容错解析、枚举校验、数值范围校验 |
| 风控模块 | 限制单笔金额、每日次数、symbol 范围 | 硬编码规则、配置文件、禁止真实资金 |
| 调度器 | 按频率触发任务,处理并发 | TThread、TTimer、队列、互斥锁 |
| 日志模块 | 记录输入输出和异常 | 本地文件、结构化 JSON、滚动日志 |
理解这条链路之后,才能知道哪些代码可以先写简化版,哪些代码从一开始就不能偷懒。
2. 环境准备与项目骨架
2.1 Delphi 版本与依赖
本文示例基于 Delphi 11 Alexandria 或 Delphi 12 Athens 编写,使用 Windows 平台下的控制台程序模式,方便专注于逻辑而非界面。实际项目如果要做行情监控界面,可以使用 FMX 或 VCL 窗体工程,后台逻辑不变。
核心依赖全部来自 Delphi 自带的 RTL 和网络库:
| 功能 | 单元 | 说明 |
|---|---|---|
| HTTP 客户端 | System.Net.HttpClient | 提供TNetHTTPClient,支持同步和异步请求 |
| JSON 解析 | System.JSON | 提供TJSONObject、TJSONArray等类型 |
| 多线程 | System.Classes | 使用TThread做后台轮询 |
| 定时触发 | System.Threading | 使用TTimer或TTask |
| 环境变量读取 | System.SysUtils | 通过GetEnvironmentVariable读取配置 |
不需要安装第三方组件。如果要用数据库保存历史记录,可以再引入 FireDAC 连接 SQLite;如果要做更复杂的行情推送,可以引入 WebSocket 客户端库,但最小案例中先用 HTTP 轮询。
2.2 创建项目
打开 Delphi 后,选择File -> New -> Other,在 Delphi Projects 下选择 Console Application。生成的项目默认代码结构如下:
program AIAgentDemo; {$APPTYPE CONSOLE} uses System.SysUtils; begin try // 程序入口 except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.为了跑通一个最小 demo,不需要急着建窗体和界面。把逻辑写在一个模块里反而更容易排查问题。
2.3 目录结构建议
虽然是最小案例,也建议按模块划分文件,方便后续扩展:
AIAgentDemo/ ├── AIAgentDemo.dpr ├── MarketDataSource.pas ├── AIClient.pas ├── SignalParser.pas ├── RiskManager.pas ├── AgentWorker.pas └── config.iniconfig.ini不是必须的,Demo 阶段可以用常量,但一旦准备接入真实 API,就必须把 API Key、模型名称、超时时间、交易标的等配置外置。不要把密钥写死在代码里。
注意:先确认你的 Delphi 版本中
System.Net.HttpClient支持 TLS 1.2 或 1.3。部分老旧 Delphi 版本默认 TLS 版本较低,调用现代大模型 API 时会直接握手失败。
3. 实现一个可运行的 AI 交易 Agent
这一章会给出一个最小闭环实现。代码示例用于说明思路,真实项目要结合自己的包名、路径和模型接口调整。
3.1 封装行情数据源
先定义行情数据接口,这样后续可以方便替换成真实数据源:
unit MarketDataSource; interface type TMarketData = record Symbol: string; Price: Double; Volume: Double; Timestamp: TDateTime; end; IMarketDataSource = interface function GetLatestData(const ASymbol: string): TMarketData; end; TSimulatedMarketDataSource = class(TInterfacedObject, IMarketDataSource) private FBasePrice: Double; public constructor Create; function GetLatestData(const ASymbol: string): TMarketData; end; implementation constructor TSimulatedMarketDataSource.Create; begin FBasePrice := 100.0; end; function TSimulatedMarketDataSource.GetLatestData(const ASymbol: string): TMarketData; begin // 模拟行情:在基础价格上随机波动 Result.Symbol := ASymbol; Result.Price := FBasePrice + Random * 10 - 5; Result.Volume := 1000 + Random * 500; Result.Timestamp := Now; end; end.模拟数据源在开发和测试阶段非常重要。它不依赖外部网络,可以稳定复现问题。真实环境下,你只需要再实现一个类,内部通过 HTTP 调用行情服务商接口,并返回TMarketData即可。
3.2 调用 AI 模型生成交易信号
AI 客户端统一负责 HTTP 调用。下面是一个通用结构,假设接口兼容 OpenAI Chat Completions 格式:
unit AIClient; interface uses System.Net.HttpClient, System.JSON; type TAIClient = class private FHttpClient: TNetHTTPClient; FBaseUrl: string; FApiKey: string; FModel: string; public constructor Create(const ABaseUrl, AApiKey, AModel: string); destructor Destroy; override; function ChatCompletion(const APrompt: string): string; end; implementation constructor TAIClient.Create(const ABaseUrl, AApiKey, AModel: string); begin FHttpClient := TNetHTTPClient.Create(nil); FBaseUrl := ABaseUrl; FApiKey := AApiKey; FModel := AModel; end; destructor TAIClient.Destroy; begin FHttpClient.Free; inherited; end; function TAIClient.ChatCompletion(const APrompt: string): string; var LRequestBody: TJSONObject; LMessages: TJSONArray; LUserMessage: TJSONObject; LResponse: string; LJson: TJSONObject; LChoices: TJSONArray; begin LRequestBody := TJSONObject.Create; try LRequestBody.AddPair('model', FModel); LMessages := TJSONArray.Create; LUserMessage := TJSONObject.Create; LUserMessage.AddPair('role', 'user'); LUserMessage.AddPair('content', APrompt); LMessages.AddElement(LUserMessage); LRequestBody.AddPair('messages', LMessages); // 建议限制输出格式 LRequestBody.AddPair('temperature', TJSONNumber.Create(0.2)); LRequestBody.AddPair('max_tokens', TJSONNumber.Create(300)); LResponse := FHttpClient.Post( FBaseUrl + '/chat/completions', LRequestBody.ToJSON, nil, procedure(ARequest: TNetHTTPRequest) begin ARequest.CustomHeaders['Authorization'] := 'Bearer ' + FApiKey; ARequest.CustomHeaders['Content-Type'] := 'application/json'; end ).ContentAsString(); // 解析 choices[0].message.content LJson := TJSONObject.ParseJSONValue(LResponse) as TJSONObject; try LChoices := LJson.GetValue('choices') as TJSONArray; Result := ((LChoices.Items[0] as TJSONObject).GetValue('message') as TJSONObject) .GetValue<string>('content'); finally LJson.Free; end; finally LRequestBody.Free; end; end; end.这段代码里有几个关键点先说明:
TNetHTTPClient.Post支持传入请求体字符串,并能通过匿名过程改写请求头。Authorization头是常见的大模型 API 鉴权方式,但不同厂商可能用api-key或x-api-key,接入前必须查看对方文档。- 将
temperature设置得较低,例如0.2,可以减少模型输出随机性,让交易信号更稳定。 max_tokens用于限制返回长度,防止模型一次性输出很长的自然语言导致解析困难。
3.3 解析 AI 返回的交易信号
模型返回的内容应当是 JSON 字符串,但实际输出可能会包含多余的解释文字,例如“好的,根据当前行情,我建议买入:{...}”。因此解析器要做两层处理。
unit SignalParser; interface uses System.JSON; type TActionType = (actBuy, actSell, actHold); TSignal = record Action: TActionType; Symbol: string; Quantity: Double; Confidence: Double; Reason: string; end; TSignalParser = class public class function Parse(const AContent: string): TSignal; end; implementation class function TSignalParser.Parse(const AContent: string): TSignal; var LJson: TJSONObject; LActionStr: string; LActionValue: TJSONValue; LStartPos, LEndPos: Integer; LCleanContent: string; begin // 如果模型输出包含额外文字,先提取最外层花括号片段 LStartPos := Pos('{', AContent); LEndPos := LastDelimiter('}', AContent); if (LStartPos > 0) and (LEndPos > LStartPos) then LCleanContent := Copy(AContent, LStartPos, LEndPos - LStartPos + 1) else LCleanContent := AContent; LJson := TJSONObject.ParseJSONValue(LCleanContent) as TJSONObject; if LJson = nil then raise Exception.Create('AI 返回内容不是有效 JSON'); try LActionValue := LJson.GetValue('action'); if Assigned(LActionValue) then LActionStr := LActionValue.Value else LActionStr := 'hold'; // 动作白名单校验 if LActionStr = 'buy' then Result.Action := actBuy else if LActionStr = 'sell' then Result.Action := actSell else Result.Action := actHold; Result.Symbol := LJson.GetValue<string>('symbol'); Result.Quantity := LJson.GetValue<double>('quantity'); Result.Confidence := LJson.GetValue<double>('confidence'); Result.Reason := LJson.GetValue<string>('reason'); finally LJson.Free; end; end; end.这里要注意,ParseJSONValue是无法保证模型永远输出合法 JSON 的。当模型返回内容为空、包含 Markdown 代码块、或者花括号嵌套不完整时,解析就会失败。所以生产代码中还要加入异常捕获,并考虑对模型输出做预处理,例如去掉可能的 ```json 标记。
3.4 组合调度线程与风控校验
把 AI 客户端和行情数据源、信号解析器组合进一个后台线程,实现周期轮询:
unit AgentWorker; interface uses System.Classes, System.SysUtils, MarketDataSource, AIClient, SignalParser, RiskManager; type TAgentWorker = class(TThread) private FDataSource: IMarketDataSource; FAIClient: TAIClient; FRiskManager: TRiskManager; FSymbol: string; FIntervalSec: Integer; protected procedure Execute; override; public constructor Create(const ASymbol: string; AIntervalSec: Integer); destructor Destroy; override; end; implementation constructor TAgentWorker.Create(const ASymbol: string; AIntervalSec: Integer); begin inherited Create(True); FSymbol := ASymbol; FIntervalSec := AIntervalSec; FDataSource := TSimulatedMarketDataSource.Create; FAIClient := TAIClient.Create('https://api.example.com/v1', 'YOUR_API_KEY', 'gpt-4o-mini'); FRiskManager := TRiskManager.Create; FreeOnTerminate := False; end; destructor TAgentWorker.Destroy; begin FAIClient.Free; FRiskManager.Free; inherited; end; procedure TAgentWorker.Execute; var LData: TMarketData; LPrompt: string; LContent: string; LSignal: TSignal; begin while not Terminated do begin try LData := FDataSource.GetLatestData(FSymbol); LPrompt := Format( '当前时间: %s, 标的价格: %.2f, 成交量: %.0f。' + '请判断交易动作,只返回 JSON: {"action":"buy/sell/hold", "symbol":"%s", ' + '"quantity": 数量, "confidence": 0到1之间, "reason": "简短原因"}', [DateTimeToStr(LData.Timestamp), LData.Price, LData.Volume, FSymbol] ); LContent := FAIClient.ChatCompletion(LPrompt); LSignal := TSignalParser.Parse(LContent); if FRiskManager.Validate(LSignal) then // 这里接入模拟交易所接口,Demo 阶段只打印 Writeln(Format('下单: %s %s 数量 %.4f, 置信度 %.2f, 原因 %s', [FSymbol, LSignal.Action.ToString, LSignal.Quantity, LSignal.Confidence, LSignal.Reason])) else Writeln('风控拦截: ' + FSymbol + ' 的信号未通过校验'); except on E: Exception do Writeln('任务异常: ', E.Message); end; Sleep(FIntervalSec * 1000); end; end; end.风控模块的职责在 Demo 中可以先简化为检查confidence范围和quantity是否超过上限:
unit RiskManager; interface uses SignalParser; type TRiskManager = class public function Validate(const ASignal: TSignal): Boolean; end; implementation function TRiskManager.Validate(const ASignal: TSignal): Boolean; begin Result := True; // 超出置信度范围 if (ASignal.Confidence < 0) or (ASignal.Confidence > 1) then Exit(False); // 单次数量过大 if ASignal.Quantity > 100 then Exit(False); // 不允许做空类高频买卖 if ASignal.Action = actSell then Exit(False); end; end.Demo 中的风控很简单,但“必须存在”这一点很重要。因为没有校验的 agent 一旦接入真实接口,模型输出的一个小错误就可能变成不可控订单。
4. 关键参数与执行流程详解
4.1 提示词设计决定了 agent 的可靠性上限
AI 交易 agent 的决策质量并不完全取决于模型,提示词在很大程度上决定了模型输出是否稳定。同一份行情数据,如果提示词写得模糊,模型可能返回一大段分析文字,而不是结构化 JSON;如果提示词约束明确,模型会更容易遵循输出格式。
建议提示词包含以下信息:
- 当前时间和行情数据。
- 要求返回的动作枚举:
buy、sell、hold。 - 要求返回 JSON 结构。
- 要求
confidence在 0 到 1 之间。 - 要求
reason简短,且不能出现非交易原因。
示例提示词:
你现在是一个只负责输出 JSON 的交易决策程序。 当前时间:2025-01-01 10:00:00 标的价格:102.35 成交量:1200 请根据短期趋势判断买入、卖出还是持有。 只返回 JSON,不要包含其他解释,格式如下: {"action":"buy","symbol":"BTCUSDT","quantity":0.01,"confidence":0.8,"reason":"...}temperature参数也建议调低。在 OpenAI 兼容接口里,temperature越高,输出越随机。对交易决策,随机性过大会导致同一行情反复出现 buy/sell 抖动,因此建议设置在0.1到0.3之间。
max_tokens设置 200 到 500 就够用。交易信号字段不多,太长输出反而容易在 JSON 之后夹带多余文本。
4.2 网络参数:超时、重试与流量控制
调用外部 AI 接口时,最常见的错误不是逻辑错误,而是网络超时、限流、连接重置。相关参数必须在一开始就设计好:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| ConnectTimeout | 10 秒 | 建立连接的最长等待时间 |
| ReadTimeout | 30 秒 | 等待响应体的最长等待时间 |
| 重试次数 | 2 到 3 次 | 只有在连接超时和 429/5xx 错误时才重试 |
| 重试退避 | 1 秒、2 秒、4 秒 | 指数退避,避免连续打满接口 |
| 单标的轮询间隔 | 5 到 60 秒 | 根据策略频率和数据源限制调整 |
| 同时并发标的数 | 建议不超过 5 个 | 每增加一个标的就是多一份 API 成本和风险 |
在 Delphi 中,TNetHTTPClient可以设置超时参数:
FHttpClient.ConnectionTimeout := 10000; // 毫秒 FHttpClient.ResponseTimeout := 30000; // 毫秒重试逻辑不要放在 HTTP 客户端内部,建议封装成一个服务方法,捕获异常后按次数和退避策略重试。要特别注意:当前代码里TAIClient.ChatCompletion没有重试,生产版本需要加一层循环,同时对429 Too Many Requests和5xx做不同处理。
4.3 JSON 解析的容错策略
大模型输出的 JSON 不像普通程序那样永远合法。常见问题有:
- 输出内容被 Markdown 代码块包裹,例如
```json ... ```。 - 输出中包含解释性文字。
- JSON 字段名被模型改写成
Action而不是action。 - 数值字段返回字符串
"0.5"。 - 字段缺失。
所以在解析前先做以下预处理:
- 移除首尾空白。
- 如果包含代码块标记,去掉
```json和```。 - 提取最外层
{到最后一个}的内容。 - 解析失败时记录原始内容,方便后续调整提示词或增加 few-shot 示例。
一个更稳妥的做法是不仅解析TSignalParser.Parse,还要在解析失败时把原始返回写入日志文件。这样即使模型输出异常,你也知道是哪个环节出了问题。
5. 运行验证与结果分析
5.1 控制台运行流程
把主程序入口补全,创建后台线程并启动:
program AIAgentDemo; {$APPTYPE CONSOLE} uses System.SysUtils, AgentWorker in 'AgentWorker.pas', MarketDataSource in 'MarketDataSource.pas', AIClient in 'AIClient.pas', SignalParser in 'SignalParser.pas', RiskManager in 'RiskManager.pas'; var LWorker: TAgentWorker; begin try Randomize; Writeln('AI Trading Agent Demo 启动'); LWorker := TAgentWorker.Create('BTCUSDT', 10); LWorker.Start; Writeln('按回车键退出'); Readln; LWorker.Terminate; LWorker.WaitFor; LWorker.Free; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.运行后预期输出类似:
AI Trading Agent Demo 启动 按回车键退出 下单: BTCUSDT buy 数量 0.0100, 置信度 0.78, 原因 short_term_momentum 下单: BTCUSDT hold 数量 0.0000, 置信度 0.55, 原因 no_clear_signalDemo 阶段没有真正连接交易所,所以收到类似输出说明链路已经跑通。
5.2 验证点不只是“能启动”
很多项目卡在最容易出错的地方:程序能启动,但每次调用都失败,或者偶尔成功偶尔失败。对 AI 交易 agent,建议至少检查以下验证点:
- 行情数据是否成功拉取,字段是否正确。
- AI 请求是否成功返回,返回状态码是 200 还是 429/500。
- AI 返回内容是否可被解析成
TSignal。 - 风控逻辑是否拦截了异常信号。
- 日志文件中是否有完整的请求输入和输出。
一个快速的验证方法是写一个单独的测试入口,不启动线程,直接执行一次完整链路:
LData := FDataSource.GetLatestData('BTCUSDT'); LPrompt := BuildPrompt(LData); LContent := FAIClient.ChatCompletion(LPrompt); LSignal := TSignalParser.Parse(LContent); Writeln(Format('action=%s, symbol=%s, quantity=%.4f, confidence=%.2f, reason=%s', [LSignal.Action.ToString, LSignal.Symbol, LSignal.Quantity, LSignal.Confidence, LSignal.Reason]));这样可以在不依赖界面和定时任务的情况下,快速定位问题在哪个模块。
5.3 日志与回放
交易 agent 的日志必须做到“可回放”。也就是说,一段时间后你能根据之前记录的行情和模型输出,重演当时的决策过程。推荐每次都记录以下信息:
- 时间戳(精确到毫秒)。
- 行情快照(价格、成交量、标的)。
- 发送给模型的完整提示词。
- 模型原始返回。
- 解析后的信号。
- 风控结果。
- 是否下单。
- 异常信息(如果有)。
日志可以使用 JSON Lines 格式,每行一个事件,方便用脚本分析。
{"ts":"2025-01-01 10:00:00.123","symbol":"BTCUSDT","price":102.35,"action":"buy","confidence":0.78,"accepted":true}6. 常见问题排查
6.1 典型问题速查表
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 调用 AI 接口报 TLS 错误 | Delphi 版本默认 TLS 版本过低 | 检查 HTTP 客户端日志,确认握手失败位置 | 升级 Delphi 版本,或使用 WinHTTP 组件并启用 TLS 1.2 |
| AI 返回内容解析失败 | 模型输出包含 Markdown 或解释文字 | 打印模型原始返回内容 | 增加代码块过滤、花括号提取、失败日志 |
| 请求返回 401 或 403 | API Key 错误,或者鉴权头格式不对 | 检查请求头是否完整发送 | 对照接口文档确认Authorization或api-key格式 |
| 程序运行一段时间不再输出 | 线程阻塞或异常被反复吞掉 | 查看日志文件或添加异常详情 | 增加日志记录,避免空 except |
| 风控一直拦截信号 | 模型输出的类型与预期不符 | 打印解析后信号字段 | 调整提示词,或者在解析层增加字段类型转换 |
| 请求被限流 | 轮询频率过高,超过 API 限制 | 查看 HTTP 状态码是否为 429 | 增加间隔,使用指数退避重试 |
| 模拟行情波动太大,策略不稳定 | 随机数波动范围不合理 | 查看日志中的价格历史 | 使用固定种子或更平滑的随机模型 |
6.2 一条实用的排查链路
如果运行出错,不要盯着异常栈看,而是按以下顺序排查:
- 先确认输入:行情源是否真的返回了数据,字段是否完整。
- 再确认提示词:发送给模型的内容是否包含合适上下文,是否有格式约束。
- 确认 AI 接口:请求是否成功,返回状态码,原始返回内容。
- 确认解析器:是否把原始内容转成了
TSignal。 - 确认风控:为什么拦截,是置信度不合格还是数量越界。
- 最后确认执行端:是否真的按
TSignal生成了订单指令。
这个顺序的本质是从输入到输出逐层推进。每一层都留下日志,问题就会很快浮出水面。
注意:不要只为 Success 情况写日志,异常分支同样需要日志。很多时候,agent 不触发交易并不代表它正常工作,也可能是解析器一直失败,信号被静默吞掉。
7. 生产化前必须补齐的能力
7.1 学习环境与生产环境的差异
最小 Demo 能跑通,和生产可用之间还有很长的距离。下表列出主要差异:
| 维度 | 学习/Demo 环境 | 生产环境 |
|---|---|---|
| 行情源 | 随机数模拟 | 交易所官方 API、行情服务商、合规数据源 |
| AI 模型 | 任意兼容接口 | 根据时延、成本、稳定性选择模型,考虑本地部署 |
| 订单执行 | 打印日志 | 交易所/券商接口,需要签名、私钥、订单确认 |
| 资金安全 | 无真实资金 | 需要止损、最大回撤、单日亏损限制 |
| 风控 | 简单校验 | 多层级风控、人工审批、交易熔断 |
| 日志 | 控制台输出 | 结构化日志、集中采集、审计 |
| 监控 | 无 | 指标监控、告警、看板 |
| 版本管理 | 单机源码 | CI/CD、回滚、灰度发布 |
| 合规 | 不做要求 | 遵守当地法律法规和交易所规则 |
Demo 阶段可以直接把 API Key 写在常量里,但一旦进入生产,不仅要把 Key 外置,还要考虑密钥泄露后的吊销机制。建议至少使用环境变量或加密配置文件。
7.2 风控是第一优先级
对于交易 agent,风控的价值高于所有策略优化。没有风控的 agent 就像没有刹车的车。生产环境至少要加入这些风控:
- 单笔订单数量上限。
- 单日订单次数上限。
- 单日亏损熔断。
- 最大持仓数量限制。
- 只允许操作白名单内的 symbol。
- 模拟盘和真实盘配置严格隔离。
- 关键订单走人工二次确认。
风控参数不要写死在代码里,建议放在配置文件中,并限制只有特定角色可以修改。
7.3 上线前检查清单
下面是一份可复用的检查清单,适合在接入真实交易前逐项确认:
| 检查项 | 是否完成 | 备注 |
|---|---|---|
| 行情源接入是否通过官方接口验证 | 是/否 | 不要用抓包非正规接口 |
| AI 调用是否加入超时与重试 | 是/否 | 避免长时间阻塞 |
| AI 输出是否做了容错解析 | 是/否 | 记录原始内容 |
| 风控是否覆盖异常信号 | 是/否 | 包括数量、置信度、频次 |
| 模拟盘是否运行满 7 天 | 是/否 | 观察长周期稳定性 |
| 是否配置结构化日志 | 是/否 | 可回放决策过程 |
| 是否配置告警 | 是/否 | 进程崩溃、接口连续失败 |
| 是否确认符合平台规则 | 是/否 | 不同平台对自动交易有不同要求 |
| 是否有手动停止和应急回滚方案 | 是/否 | 一键断开下单接口 |
| API Key 是否安全存储 | 是/否 | 不提交到版本库 |
7.4 下一步扩展方向
最小 agent 跑通后,可以从以下方向继续扩展:
- 增加真实行情源:先用 REST 轮询,再逐步过渡到 WebSocket 推送,降低延迟。
- 增加回测引擎:把历史行情和 AI 决策组合,观察策略在不同市场环境下的表现。
- 多策略并行:让多个模型或者多个提示词策略同时分析,再通过投票机制决定最终动作。
- 本地模型接入:使用 Ollama 等本地推理方案,可以减少外部接口的时延和网络依赖,也避免行情数据外传。
- 订单管理模块:增加未成交订单查询、撤单、持仓管理,形成真正可用的交易闭环。
从工程角度看,AI 交易 agent 的核心难点并不在于“让 AI 判断涨跌”,而在于如何把模型输出放到一个稳定、可审计、有风控的工程系统里。Delphi 的价值恰好体现在这里:它能让开发者快速搭建后端调度、网络、解析和日志链路,同时保持原生程序的性能和稳定性。对于想研究 agent 架构的开发者来说,用 Delphi 做一个最小版本,是一条值得走完的实践路径。