公司动态
从回测到实盘:基于大语言模型的量化交易AI智能体架构与实战
1. 项目概述当“龙虾”遇上真实行情最近在量化圈子里“龙虾”这个词的热度有点高。不是餐桌上的那个而是一个代号指的是一类新兴的、试图将大语言模型LLM能力与量化交易逻辑结合的开源项目或工具集。我最初也是抱着“这玩意儿到底是不是噱头”的心态从GitHub上拉了个类似“OpenClaw”的代码仓库想看看所谓的“AI交易员”到底有几斤几两。在本地用历史数据回测时它的表现中规中矩能生成一些基于新闻情绪或技术指标的逻辑描述但总感觉隔靴搔痒像是一个在纸上谈兵的参谋。直到我下决心把它接入了一个实时的、带Tick数据的模拟交易API整个项目的画风才彻底变了。当虚拟账户的盈亏数字随着真实市场的每一次跳动而闪烁当“龙虾”需要实时解析行情、计算指标、并立刻做出“买”、“卖”或“等待”的决策时我才真正体会到一个量化系统从“玩具”到“工具”的蜕变关键就在于这“最后一公里”的接入。这个项目就是记录我如何将一个基于Python的、代号“龙虾”的量化AI智能体从离线回测环境一步步接入真实行情与模拟交易接口并观察其行为模式发生质变的全过程。它不再只是分析过去而是开始尝试“干活”了——虽然这“活”干得可能还很笨拙甚至会闯祸但这个过程本身对于理解AI在金融时序决策中的应用边界和挑战价值巨大。2. 核心设计从回测沙箱到实时战场的架构演进2.1 原有“龙虾”系统的局限性分析我手头这个“龙虾”原型其核心是一个基于Transformer架构微调过的中型语言模型配合一套传统的量化分析库比如TA-Lib,pandas。它的工作流是典型的离线模式数据输入加载CSV格式的日K线或分钟K线历史数据。信息处理模型接收当前时间点及之前一段窗口期的数据价格、成交量、指标并结合可能爬取的文本新闻需额外模块生成一段自然语言描述例如“当前价格突破20日均线但RSI处于超买区域市场情绪偏多但需警惕短期回调。”信号生成一个固定的、硬编码的规则解析器rule parser会尝试从这段描述中提取关键词如“突破”、“超买”、“警惕”映射成预定义的信号“强多”、“弱多”、“中性”、“弱空”、“强空”。策略回测根据这些信号在历史数据上模拟交易计算夏普比率、最大回撤等绩效指标。问题立刻暴露了延迟幻觉模型处理的是已经静止的历史切片它“知道”所有后续数据但必须假装不知道。这种训练方式容易让模型产生“后见之明”的偏差。信号粗糙从自然语言到交易指令的映射损失了大量信息。“警惕回调”到底该不该平仓何时平仓规则解析器无法处理这种灰度。无状态、无成本它没有“持仓”概念没有“交易成本”佣金、滑点更不考虑订单能否以理想价格成交。这就像在玩一个无限金币的单机游戏。2.2 接入实时行情的核心架构设计要让“龙虾”干活必须将其嵌入一个实时事件驱动的系统中。我的设计目标很明确低延迟、可观测、风控优先。整体架构演变为下图所示的一个异步工作流整个系统围绕一个主事件循环运行由行情API推送驱动。我选择了asyncio来构建这个异步框架因为行情和交易指令都是高IO密集型的操作异步能有效避免阻塞。核心模块拆解行情网关这是系统的感官。我接入了提供模拟交易的券商API如一些券商提供的量化仿真接口或开源项目vn.py支持的接口。它的职责是订阅标的如000001.SZ平安银行的实时Tick逐笔成交或快照3秒/笔数据并以统一格式发布到内部事件总线。数据总线和缓存我使用redis作为高速缓存和轻量级消息队列。每一个新的行情Tick到来都会更新redis中该标的的最新价、买卖盘、成交量等字段。同时也会触发一个行情更新事件。策略引擎“龙虾”核心这是系统的大脑。它监听行情更新事件。一旦触发引擎会获取上下文从redis中读取最近N个周期的K线由原始Tick实时合成计算技术指标。组装Prompt将“时间、价格、指标、当前持仓状态、账户余额”等信息结构化地填充到一个预设的Prompt模板中。Prompt的设计至关重要例如“当前时间{time}标的{symbol}最新价{price}持仓{position}股可用资金{cash}元。近10分钟K线数据如下[...]。技术指标RSI{rsi}, MACD{macd}...。请分析当前市场状况并给出具体的交易操作建议。必须严格按照以下格式输出操作买入/卖出/持有理由...数量...限价...可选”调用模型将组装好的Prompt发送给本地部署的“龙虾”大模型我用了FastChat部署的Vicuna-7B量化版对16G内存的机器比较友好。这里的关键是响应速度模型推理必须在几百毫秒内完成否则信号就过时了。因此模型量化如使用bitsandbytes库进行INT8量化和硬件加速CUDA是必须的。解析与验证对模型输出的文本进行严格的格式和逻辑解析。不仅要用正则表达式提取“操作、数量、价格”等字段还要进行业务逻辑校验买入数量是否超过资金允许卖出数量是否超过持仓价格是否偏离市价过远避免异常指令订单管理与风控这是系统的手和刹车。通过校验的指令会被转化为具体的订单请求订单类型、价格、数量发送给交易API。同时这里部署了硬风控单笔最大下单量、日内累计亏损限额、最大持仓比例等。任何订单执行前必须通过风控检查。监控与日志这是系统的黑匣子。所有事件——行情数据、模型Prompt、模型输出、解析结果、订单状态、账户变动——都以高密度写入日志文件如structlog和数据库如InfluxDB用于时间序列数据。一个简单的Flask面板用来实时展示账户权益曲线、持仓、以及最新的模型决策理由。注意在Prompt中强制规定输出格式是保证程序可解析的关键。直接让模型生成自由文本再试图用另一个模型去理解在实时系统中是灾难性的。结构化输出JSON或固定格式文本是工业级应用的前提。2.3 技术栈选型与考量Python 3.9量化生态最成熟的语言。异步框架asyncioaiohttp处理高并发行情和网络IO。缓存与消息Redis性能极高用作数据缓存和简易消息队列足够。模型服务FastChat或vLLM用于高效部署和推理开源LLM。考虑到资源我选择了7B参数的模型并使用bitsandbytes进行INT8量化在消费级显卡如RTX 4060 Ti 16G上也能获得不错的推理速度~100-300ms。量化分析pandas数据处理、TA-Lib技术指标、numpy数值计算。交易接口根据选择的券商或模拟平台使用其Python SDK或使用vn.py这类开源框架进行封装。监控Loguru或Structlog用于日志InfluxDBGrafana用于可视化监控。选择这个技术栈的核心考量是平衡性能、开发效率与资源消耗。全量LLM如70B参数实时推理对个人开发者不现实量化后的7B-13B模型是可行的起点。Redis的引入避免了每次计算都从数据库读取大量历史数据极大降低了延迟。3. 关键实现打通数据流与决策流的魔鬼细节3.1 实时行情接入与K线合成接入行情API后拿到的是源源不断的Tick数据。但大多数技术指标是基于K线OHLC开、高、低、收、量计算的。因此第一个关键环节是实时K线合成。我创建了一个BarGenerator类它维护一个字典以标的和周期如1分钟、5分钟为键缓存最新的Tick数据流。import asyncio from collections import defaultdict from datetime import datetime, timedelta from typing import Dict, List import pandas as pd class BarGenerator: def __init__(self): # 存储每个symbol-period对应的未完结的Tick列表和当前K线 self.ticks_buffer: Dict[str, Dict[str, List]] defaultdict(lambda: defaultdict(list)) self.current_bar: Dict[str, Dict] defaultdict(dict) async def on_tick(self, symbol: str, tick: dict): 处理新的Tick数据 # tick 结构: {price: float, volume: int, datetime: datetime, ...} current_time tick[datetime] for period in [1min, 5min]: # 支持多周期 period_seconds int(period.replace(min, )) * 60 # 计算该Tick所属的K线起始时间 bar_start self._align_time(current_time, period_seconds) bar_key f{symbol}_{period} if bar_key not in self.current_bar or self.current_bar[bar_key].get(datetime) ! bar_start: # 新K线开始推送旧K线如果存在并初始化新K线 if bar_key in self.current_bar: await self._push_bar(bar_key, self.current_bar[bar_key]) self.current_bar[bar_key] { symbol: symbol, period: period, datetime: bar_start, open: tick[price], high: tick[price], low: tick[price], close: tick[price], volume: tick[volume], ticks: 1 } else: # 更新当前K线 bar self.current_bar[bar_key] bar[high] max(bar[high], tick[price]) bar[low] min(bar[low], tick[price]) bar[close] tick[price] bar[volume] tick[volume] bar[ticks] 1 def _align_time(self, dt: datetime, period_seconds: int) - datetime: 将时间对齐到K线周期起始点 epoch_seconds int(dt.timestamp()) aligned_seconds (epoch_seconds // period_seconds) * period_seconds return datetime.fromtimestamp(aligned_seconds) async def _push_bar(self, bar_key: str, bar: dict): 将完整的K线数据发布出去例如写入Redis或触发事件 # 这里可以计算技术指标 bar_df pd.DataFrame([bar]) # 计算RSI, MACD等 (这里简化) # ... 计算指标逻辑 ... # 将带指标的K线数据存入Redis供策略引擎读取 # await redis_client.set(fbar:{bar_key}, json.dumps(bar)) print(fBar Generated: {bar})这个类确保了无论Tick多么频繁策略引擎总是能获取到最新、已计算好指标的固定周期K线数据这是后续模型分析的基石。3.2 模型Prompt工程与结构化输出解析这是“龙虾”能否产出有效指令的核心。糟糕的Prompt会让模型胡言乱语好的Prompt则能引导它进行专业思考。我的Prompt模板经历了数次迭代V1失败“请分析一下平安银行的股票。” 结果模型输出了一篇关于银行股基本面的短文毫无操作性。V2改进“给定最新价格xx20日均线yy请给出交易建议。” 结果模型可能会说“建议关注”、“可以考虑买入”依然无法解析。V3最终版你是一个专业的量化交易员。请严格根据以下信息做出交易决策。 【交易上下文】 - 时间{current_time} - 标的代码{symbol} - 当前持仓{position}股成本价{avg_cost} - 账户可用资金{cash}元 - 最新价{last_price}元 【近期市场数据最近10根1分钟K线】 {df.to_string(indexFalse)} # 这里插入一个格式化的DataFrame字符串 【计算出的技术指标】 - RSI(14): {rsi_value:.2f} - MACD(12,26,9): DIF{dif:.4f}, DEA{dea:.4f}, MACD柱{macd_bar:.4f} - 当前价格相对于20周期均线的位置{price_vs_ma}% 【指令】 请综合以上信息判断当前应该执行何种操作。你的输出必须且只能包含以下三个部分用换行分隔 操作[买入|卖出|持有] 数量[正整数若为持有则填0] 理由[简要说明你的决策逻辑不超过50字] 示例 操作买入 数量100 理由价格回调至20日均线附近获得支撑RSI脱离超卖区MACD金叉在即短期有反弹动能。这个Prompt的成功之处在于明确角色让模型进入“交易员”角色。提供结构化上下文持仓、资金、价格、数据、指标所有必要信息一目了然。强制结构化输出严格限定输出格式极大降低了后续解析的复杂度。解析代码示例import re def parse_model_output(output_text: str) - dict: 解析模型输出的结构化文本 pattern r操作(\w)\s*数量(\d)\s*理由(.*) match re.search(pattern, output_text, re.DOTALL) if not match: raise ValueError(f无法解析模型输出: {output_text}) action, amount_str, reason match.groups() action action.strip() amount int(amount_str.strip()) reason reason.strip() # 验证操作类型 if action not in [买入, 卖出, 持有]: raise ValueError(f非法操作类型: {action}) # 验证数量持有时为0 if action 持有 and amount ! 0: raise ValueError(f持有操作时数量必须为0得到: {amount}) if action ! 持有 and amount 0: raise ValueError(f买卖操作数量必须为正整数得到: {amount}) return { action: action, amount: amount, reason: reason }3.3 风险控制模块的实现没有风控的交易系统是自杀系统。我实现了三层风控指令级风控在解析模型指令后立即执行。def pre_trade_risk_check(signal: dict, portfolio: dict) - bool: 订单执行前风控 symbol signal.get(symbol) action signal.get(action) amount signal.get(amount) price signal.get(price, portfolio[last_price]) # 默认用最新价估算 # 1. 单笔最大下单量 if amount MAX_ORDER_PER_TRADE: log.warning(f风控拦截单笔下单数量{amount}超过限制{MAX_ORDER_PER_TRADE}) return False # 2. 买入资金检查 if action 买入: needed_cash amount * price * (1 COMMISSION_RATE) if needed_cash portfolio[available_cash]: log.warning(f风控拦截所需资金{needed_cash:.2f}大于可用资金{portfolio[available_cash]:.2f}) return False # 3. 卖出持仓检查 elif action 卖出: if amount portfolio[positions].get(symbol, 0): log.warning(f风控拦截卖出数量{amount}超过持仓{portfolio[positions].get(symbol, 0)}) return False # 4. 价格偏离检查避免异常价格订单 if price portfolio[last_price] * 0.9 or price portfolio[last_price] * 1.1: log.warning(f风控拦截订单价格{price}偏离市价{portfolio[last_price]}超过10%) return False return True账户级风控独立进程定时检查。日内最大亏损如果当日浮动亏损超过总资产的2%则暂停所有新开仓。最大持仓比例单标的持仓市值不超过总资产的20%。连续止损如果连续3笔交易亏损强制进入“冷却期”1小时。系统级风控心跳监测如果行情中断超过10秒或模型响应超时如5秒系统自动暂停交易转为待机状态。异常指令熔断如果单位时间内如1分钟收到超过N次如5次被风控拒绝的指令可能模型已“发疯”触发熔断暂停该策略引擎一段时间。4. 实战观察接入真实行情后的行为质变系统跑起来后我让它在模拟账户上运行了整整一周观察它与之前回测版本的巨大差异。4.1 从“分析者”到“决策者”的思维转变在回测中“龙虾”更像一个评论员它的输出是开放式的分析。而在实时系统中它被逼成了一个必须下注的决策者。最明显的变化是输出确定性增强在Prompt的压力下它很少再输出“可能”、“或许”、“建议关注”这类模糊词汇。它被迫在“买入”、“卖出”、“持有”中三选一并给出一个明确的理由。这暴露了模型在不确定性下做决策的“性格”有的版本偏向激进频繁交易有的则偏向保守长期持有。开始考虑“状态”因为它能接收到当前的持仓和资金信息它的决策出现了连贯性。例如在一次盈利买入后当价格小幅回落时它给出的理由是“属于正常技术性回调多头趋势未改继续持有”而不是像回测中每个时间点独立判断那样可能给出“卖出”信号。它开始有了“交易记忆”的雏形。对市场噪音的反应Tick级别的数据充满了噪音。我观察到模型在行情剧烈波动时例如快速拉升又砸下有时会在几分钟内给出相反的信号。这促使我改进了Prompt加入了“请过滤短期噪音关注主要趋势”的指令并增大了数据观察窗口从10根K线增加到30根有效减少了“追涨杀跌”的无效交易。4.2 暴露出的新问题与挑战延迟与滑点的真实伤害回测中假设订单立即以当前价成交。现实中从模型推理~200ms到指令解析、风控、发单再到交易所撮合可能有500ms-1秒的延迟。在快速变动的市场中成交价可能与预期价相差甚远滑点。我亲眼看到一次模型发出“限价买入”指令但因价格快速上涨订单一直未能成交错过了整个波段。教训必须对模型进行“延迟与滑点”的感知训练或者在Prompt中强调“仅在流动性充裕、趋势明确时操作”或者直接使用市价单并接受更高的成本。模型的不稳定性同样的市场情况模型有时会给出略有不同的决策。这源于LLM本身的概率生成特性。虽然通过设置较低的temperature参数如0.1可以增加确定性但无法完全根除。对策引入“投票机制”或“多轮思考链Chain-of-Thought”。例如让模型连续推理三次取多数票作为最终决策或者在Prompt中要求它先逐步推理再给出结论提高决策过程的可靠性。对极端行情的误判在一次市场突然暴跌时模型基于“RSI超卖”和“价格偏离均线过远”的指标给出了“买入”信号。这从纯技术分析上看似乎合理但它完全忽略了引发暴跌的潜在宏观消息系统当时未接入实时新闻。结果买入后价格继续阴跌造成了较大回撤。启示纯技术分析的AI在黑天鹅事件面前是脆弱的。一个更健壮的系统必须融合多模态信息包括新闻舆情、板块资金流等。4.3 性能优化与迭代为了让系统更“可用”我做了以下关键优化模型推理加速量化将FP16模型转换为INT8推理速度提升近一倍精度损失在可接受范围内。批处理如果需要监控多个标的将多个标的的Prompt组成一个Batch一次性送入模型能极大提升GPU利用率。缓存对于相似的市场状态如价格、指标变化不大可以缓存上一次的模型输出避免重复计算直到市场状态发生显著变化。事件驱动架构优化将不同的策略逻辑如趋势跟踪、均值回归拆分成独立的“策略工人”通过消息队列接收行情事件并行处理提高系统吞吐量。引入简单的强化学习反馈环我设计了一个简单的奖励函数每笔交易平仓后根据盈亏计算一个奖励值。将这个奖励值连同当时的市场状态State和模型采取的动作Action一起存储下来。定期用这些数据对模型进行微调P-tuning或LoRA目标是让模型学会向盈利动作靠拢。这是一个初步的尝试但为系统从“基于规则模仿”向“基于结果学习”演进提供了可能。5. 常见问题与排查实录在部署和运行过程中我踩过不少坑这里记录下最典型的几个问题和解决方法。5.1 模型响应超时或崩溃现象策略引擎长时间收不到模型回复或模型服务进程突然退出。排查检查GPU内存是否溢出。使用nvidia-smi命令监控。LLM推理很吃显存特别是处理长序列时。检查Prompt长度。过长的Prompt如包含大量历史K线数据会导致推理时间指数级增长。需要优化数据表示例如只传递指标值而非原始K线。查看模型服务日志。可能是遇到了模型无法处理的特殊字符或格式导致内部错误。解决为模型服务设置显存监控和自动重启机制如使用supervisor。精简Prompt只传递核心信息。将历史数据摘要为几个关键特征值。在代码中设置严格的超时如asyncio.wait_for超时后触发降级策略如使用简单规则库生成信号。5.2 解析模型输出失败现象parse_model_output函数频繁抛出ValueError提示“无法解析模型输出”。排查查看日志中模型输出的原始文本。常见问题有模型没有严格遵守格式在“操作”前加了一些废话。模型生成了中文标点或全角字符导致正则匹配失败。模型输出了“建议买入”而不是“买入”。解决强化Prompt在Prompt开头和结尾反复强调“必须严格按照格式输出”。后处理清洗在解析前对输出文本进行清洗移除多余的空行、首尾空白并将全角字符转换为半角。使用更鲁棒的解析器从正则表达式升级到基于大语言模型的“格式校验与修正”小模型或者使用langchain的OutputParser组件。对于简单格式可以尝试用多行正则或分步提取。5.3 交易指令的“毛刺”与过度交易现象账户频繁发出小额买卖指令产生大量手续费侵蚀利润。排查分析模型日志发现当价格在某个关键点位如均线附近小幅震荡时模型会随着价格上下穿均线而频繁改变观点。解决在策略层增加过滤器引入“信号确认”机制。例如只有当买入信号连续出现2个周期以上才真正发单。或者在发出反向信号前必须满足一定幅度的价格变动或时间间隔。修改Prompt加入明确的交易频率限制指令如“避免在窄幅震荡区间内进行交易”、“除非趋势发生明确反转否则应保持现有持仓”。在风控层增加频率限制设置同一标的的最小交易时间间隔例如至少间隔5分钟才能发出新指令。5.4 模拟环境与实盘的心理差异现象在模拟盘表现稳定的策略一旦想到要投入真金白银就会对模型的每一个决策产生怀疑总想手动干预。反思这其实不是技术问题而是人机协作和心理问题。量化交易的核心纪律之一就是排除情绪干扰严格执行系统信号。如果无法信任自己的系统说明系统本身可能存在未发现的缺陷或者你对它的理解不够深入。建议充分回测与模拟在模拟盘运行的时间要足够长经历各种市场行情趋势、震荡、暴跌、暴涨。小额实盘试水用你完全能承受损失的金额开始实盘目的是验证整个流程开户、API、风控、结算是否通畅并感受真实盈亏带来的心理压力。建立干预规则明确界定在什么情况下可以手动干预如系统出现明显bug、市场出现极端流动性危机什么情况下绝对不可以。将规则写下来避免临时起意。把“龙虾”接入真实行情就像给一个聪明的学生安排了毕业实习。在学校的模拟考试回测中它可能成绩优异但面对真实职场市场的复杂、动态和压力它的不足和潜力才被真正激发出来。这个过程让我深刻认识到AI在量化领域的应用绝不是用一个模型替代所有而是构建一个以AI为核心决策组件、同时被严密的风险管理、高效的数据工程和稳健的系统架构所包裹的复杂系统。这个项目远未结束它只是一个起点。下一步我计划引入更多维度的数据如订单簿、新闻情感尝试多模型集成投票并深化强化学习的反馈循环。这条路很长但看着自己搭建的系统开始自主地“呼吸”市场空气并做出反应这种成就感远超任何一次离线回测的漂亮曲线。