最近我用AI辅助编程的方式,从零搭完了一套个人量化研究框架。前后花了三周业余时间,核心功能包括历史行情回测、单因子分析、多策略对比和绩效报告生成。这个项目的价值在于,它把常见量化框架里最重的几块——数据清洗、回测撮合、指标计算——全部用对话式开发搞定,省去了大量手写样板代码的时间。
如果你是想入门量化但代码基础一般的人,或者已经有策略想法但一直被工程实现卡住的老手,这篇内容都适合你。我会把整个框架的设计思路、模块拆解、AI辅助开发的完整流程,以及我在这个过程中踩过的坑,一次性讲清楚。
1. 整体框架设计:先画清楚边界,再动手写代码
1.1 量化框架的标准分层
任何一套量化交易框架,无论机构级还是个人级,核心都逃不开五个模块:行情数据获取、数据存储与管理、策略逻辑定义、回测撮合引擎、绩效分析报告。个人框架没必要做到微服务化,但分层的思想必须保留,否则后续每加一个新策略都要改动底层,维护成本会迅速失控。
我给自己定的架构是四层。最底层是数据接入层,负责从公开行情接口拉取K线数据并落地存储;第二层是数据管理层,统一提供切片、对齐、复权等接口;第三层是回测引擎,负责按时间顺序逐bar撮合,计算成交和持仓变动;最上层是策略层和绩效层,策略层写具体的买卖信号,绩效层输出收益曲线、回撤、夏普等指标。
AI生成代码时,如果你不先拆清层次,它很容易把所有功能揉在一个文件里。模块边界越清楚,AI越容易按你的要求生成针对性代码,后面调试定位问题也越省力。
1.2 技术选型:为什么是Python加SQLite
技术选型上,我直接用了Python加SQLite的组合。Python不用多说,量化生态最成熟的语种,pandas、numpy、matplotlib这套组合拳打下来,数据处理和可视化都能覆盖。SQLite则是考虑到个人回测场景的数据量根本不需要上MySQL,单机文件数据库足够,而且AI对SQLite的SQL语法生成质量非常稳定。
我踩过的第一个坑是过于追求“正规化”,一开始想用MySQL加Redis做数据层,后来发现这套组合对个人研究来说完全是给自己上刑。回测数据是典型的写少读多场景,SQLite的读写性能完全够用,备份也方便,直接复制文件就行。
1.3 框架设计的核心原则
整个框架我坚持了三个原则。第一,回测逻辑必须确定性,同一份数据跑同一个策略,结果必须完全一致;第二,策略与引擎解耦,策略只负责输出信号,不直接操作账户和仓位,这样换策略时不会破坏引擎;第三,数据与代码分离,所有外部参数放在配置文件里,改参数不用改代码。
这三点看起来基础,但很多个人框架做不到。尤其是第一点,如果回测里混入随机因素或者数据对齐问题,结果不可复现,后续一切分析都是空中楼阁。AI帮我写代码时,我特意在提示词里反复强调“确定性”和“无未来函数”,这直接决定了生成代码的质量。
2. 数据从哪来:行情接口、清洗流程与本地存储
2.1 数据源的选取与权衡
数据源是整个框架的地基。我调研了一圈公开行情接口,最终选择了一个免费且无需额外认证的数据源,支持日线级别的历史K线拉取,覆盖A股、指数和ETF。为什么选它而不是付费数据?因为个人回测研究阶段,免费数据源的准确度虽然偶有小问题,但通过对齐和清洗可以解决,性价比最高。
需要提醒的是,不同数据源在复权方式、停牌处理、涨跌停标识这些细节上有差异,这会导致同一策略在不同数据源下回测结果存在偏差。我的建议是,框架设计时预留一个数据接口抽象层,后续要换数据源时只改一个适配器,不需要动策略代码。AI在生成这个适配器时非常拿手,因为它的IO边界清晰,描述清楚格式转换规则就能输出可用代码。
2.2 日线数据清洗的具体操作
数据清洗是整个流程里最无趣但最重要的一环。日线数据常见的脏问题包括:缺失交易日、重复K线、停牌日无成交记录、异常价格跳动等。我设计了清洗管线专门处理这几类问题。
import pandas as pd def clean_daily_data(df): # 去重 df = df.drop_duplicates(subset=['trade_date'], keep='last') # 日期排序 df['trade_date'] = pd.to_datetime(df['trade_date']) df = df.sort_values('trade_date').reset_index(drop=True) # 剔除空值 df = df.dropna(subset=['open', 'high', 'low', 'close']) # 异常价过滤 df = df[(df['close'] > 0) & (df['high'] >= df['low'])] return df清洗时要特别注意复权因子。我用的是后复权方式,这样在回测历史收益时不会因为分红除权产生假信号。复权处理必须在数据进入数据库前完成,否则每一轮回测都要重复计算,AI生成的代码在这一点上容易漏掉,需要单独检查。
2.3 本地文件存储与增量更新策略
存储方案我采用了“SQLite存元数据+本地缓存文件存K线”的组合。为什么不让所有数据都进数据库?因为回测引擎高频读取的是K线序列,直接从SQLite读会有IO开销,而pickle或parquet格式的本地缓存读取速度更快。
增量更新策略其实很简单,核心逻辑是记录每只标的最新数据日期,拉取时只请求该日期之后的数据,然后追加进本地缓存。AI生成这个增量更新逻辑时,我踩过“重复追加”的坑,解决办法是每次更新后都做一次完整去重。实际开发时我把这个逻辑写成了一个独立的update函数,每次执行完都输出更新前后行数对比,方便肉眼检查异常。
3. 回测引擎:让历史数据替我们验证策略
3.1 向量化回测与逐bar循环的选择
回测引擎我采用了向量化为主、事件循环为辅的混合方案。向量化的意思是,整个时间序列上的信号计算一次性完成,而不是逐行循环;事件循环部分只处理成交、持仓更新这些需要逐bar推进的逻辑。为什么混合?纯向量化虽然快,但在处理持仓变化、分批加减仓这类逻辑时非常别扭;纯事件循环又太慢,几千只股票跑三年日线会慢到怀疑人生。
混合方案下,策略先生成一个含有信号列的DataFrame,引擎再按时间顺序逐bar读取信号并更新持仓。这个设计让策略逻辑和撮合逻辑保持独立,AI生成时也更容易理解。
3.2 撮合逻辑与交易成本模型
撮合逻辑决定回测结果是否贴近实盘。我的框架默认采用次日开盘价成交,而不是当日收盘价,因为实盘中信号产生到下单存在延迟,用次日开盘价更接近真实情况。这个细节被称为“未来函数规避”,是回测可信度的关键。
交易成本模型包含两部分:佣金和滑点。佣金按成交金额万分之三收取,最低五元;滑点我按百分比估算,默认设千分之一。具体计算方式如下:
def apply_trade_cost(price, shares, commission_rate=0.0003, slippage_rate=0.001): gross = price * shares commission = max(gross * commission_rate, 5) slippage = gross * slippage_rate net = gross - commission - slippage return net滑点参数是回测与实盘差异的重要来源之一。小市值股票和流动性差的标的,实际滑点可能远高于千分之一,我在框架里把这个参数做成了全局配置项,而不是硬编码,方便不同策略做敏感性分析。
3.3 资金管理与持仓记录的数据结构
持仓记录我用DataFrame维护,核心字段包括:交易日期、标的代码、持仓数量、持仓市值、可用资金、总资产。每一根bar结束前都会更新这批数据,最终汇总成资金曲线。
用AI生成这部分代码时,我一开始描述得比较模糊,结果它生成的持仓逻辑存在严重bug——卖出时没有先检查是否有足够持仓,导致出现负持仓。后来我在提示词里明确加入了“所有交易操作必须校验持仓充足性”这条规则,生成代码的质量立刻提高了很多。这说明AI编程的关键不是让它自由发挥,而是给它清晰编码过的约束条件。
4. AI写代码实录:从提示词到可运行模块的完整过程
4.1 高质量提示词的三要素
这三周里我总结了高质量提示词的三要素:明确的输入输出定义、清晰的业务规则、可验证的边界条件。举个例子,让AI生成数据清洗函数时,我会这样描述:“写一个Python函数,输入是包含trade_date、open、high、low、close、volume字段的DataFrame,输出是清洗后的DataFrame,要求去除重复日期、按日期升序排列、剔除close为0的数据。”
和一开始用自然语言聊几句就让AI写代码相比,这样写提示词生成结果的可用率至少翻了一倍。AI不是万能的,它像一个理解力超强但缺乏业务常识的实习生,你不说清它会自由发挥,而自由发挥常常偏离金融场景的真实需求。
4.2 从需求到代码的完整对话过程
我举一个真实例子,生成双均线策略时,我的第一轮提示词是:“实现一个双均线策略,金叉买入死叉卖出。”AI返回的结果能运行,但存在两个问题:一是信号在当天收盘价就成交,引入了未来函数;二是没有处理空仓状态下的卖出信号。
我第二轮补充了具体规则:“信号计算基于当天的收盘价均线判断,但信号在次日开盘执行;卖出时需检查当前持仓量,若持仓为0则跳过。”AI修正后,代码逻辑明显合理了。这个过程很能说明问题:AI在你给出模糊需求时,输出的是“正确但不符合场景”的代码,你需要在多轮对话中逐步注入金融业务知识。
4.3 AI生成代码的审查方法论
AI生成的代码不能盲信,必须有一套审查方法论。我总结为三步:读结构、查边界、跑数据。读结构是看函数是否按模块划分、是否有全局变量污染;查边界是看数据为空、持仓不足、除数为零这些场景是否都有处理;跑数据是用真实行情数据跑一遍,输出中间结果做人工核对。
有一个典型案例:AI生成的指标计算函数,在股票上市不满60天时计算60日均线会产生NaN值,它直接填充为0,这会导致策略在次新股上产生错误信号。我用边界数据测试时发现了这个问题,后来在代码里特意加了一段处理逻辑,新股上市初期不生成任何信号,从而避开了无数据区间的干扰。
5. 一个能跑的策略:双均线加动量过滤的搭建与回测
5.1 策略逻辑与参数设定
为了验证框架可用性,我实现了一个经典策略:双均线交叉加动量过滤。均线部分用5日均线和20日均线,金叉考虑买入,死叉考虑卖出;动量过滤部分要求过去60个交易日收益率大于0时才允许做多。逻辑上,加动量过滤是为了过滤掉均线在下跌趋势中的假金叉。
参数设定上,5和20是双均线最常用的组合,60日动量则是中期趋势的常用判断窗口。这些参数本身不是核心,框架的价值在于用户可以随意替换参数组合,快速做敏感性测试。
5.2 策略代码与框架交互方式
策略代码通过一个统一的接口与回测引擎交互。这个接口包含两个方法:init用于初始化参数和持仓状态,on_bar用于接收每日K线数据并返回目标仓位或交易信号。
class DoubleMAStrategy: def __init__(self, fast=5, slow=20, momentum_window=60): self.fast = fast self.slow = slow self.momentum_window = momentum_window def on_bar(self, df): if len(df) < self.slow: return 0 fast_ma = df['close'].rolling(self.fast).mean().iloc[-1] slow_ma = df['close'].rolling(self.slow).mean().iloc[-1] momentum = df['close'].iloc[-1] / df['close'].iloc[-self.momentum_window] - 1 if fast_ma > slow_ma and momentum > 0: return 1 elif fast_ma < slow_ma: return 0 return None返回1代表满仓做多,0代表空仓,None代表持仓不变。这样设计的好处是,策略只需要输出目标仓位,具体买卖数量和交易成本由引擎计算,两者互不干扰。
5.3 回测结果解读与框架验证
我用某只指数ETF的三年日线数据跑了一次回测,结果让我很意外:策略累计收益约18%,但最大回撤达到了14%。这说明双均线策略在震荡市里确实会被来回打脸,也说明我的回测引擎具备了基本的可信度,因为策略表现和理论预期一致。
为了让结果更可读,我在绩效模块里输出了年化收益、夏普比率、最大回撤、胜率和盈亏比五个核心指标。这些指标,尤其是最大回撤,对判断策略真实水平至关重要。光看收益不看回撤,很容易被表面数字误导。
6. 关键绩效指标:如何判断策略真能赚钱
6.1 核心指标体系与公式
绩效分析模块是整个框架价值最直观的出口。我实现了以下指标体系,并以表格形式输出:
| 指标 | 公式/含义 | 用途 |
|---|---|---|
| 年化收益率 | 累计收益率按时间年化 | 衡量整体赚钱能力 |
| 夏普比率 | (策略年化收益 - 无风险利率) / 年化波动率 | 衡量风险调整后收益 |
| 最大回撤 | 资金曲线峰值到谷底的最大跌幅 | 衡量策略极端风险 |
| 卡玛比率 | 年化收益 / 最大回撤 | 衡量收益回撤性价比 |
| 胜率 | 盈利交易次数 / 总交易次数 | 衡量信号准确度 |
夏普比率是很多人最看重的指标,但单独看夏普也不够。卡玛比率可能对个人投资者更直观,它会告诉你每承受一个单位的回撤,能换回多少年化收益。我通常要求策略的卡玛比率大于0.5才算基本合格。
6.2 实际回测中的指标波动原因
实际回测中,这些指标的稳定性比数值本身更重要。同一个策略在同一段数据上,参数微调就会导致夏普比率大起大落,这说明策略参数过于敏感,存在过拟合风险。
我的框架里加入了参数敏感性分析功能:自动遍历多组参数组合,输出每个组合的绩效指标,然后观察指标变化是否平滑。如果某些参数只在一个很窄的区间内有效,参数稍偏就绩效崩溃,这样的策略我不会纳入实盘考虑。AI生成这个遍历逻辑时非常高效,我只需要给出参数范围和步长,它就能生成循环计算并汇总结果的代码。
6.3 过拟合的识别与规避
过拟合是量化策略开发中最隐蔽的陷阱。识别方法除了参数敏感性分析,还有一个简单有效的办法:样本外测试。我习惯把数据切成前70%做参数调优,后30%做样本外验证。如果样本外表现显著劣于样本内,基本可以断定策略过拟合了。这个切分逻辑写在框架的配置里,默认开启。
实际上,AI在过拟合这个问题上既可能是帮凶也可能是助手。帮凶是因为它生成代码太快,让你有条件在大量参数组合里反复拟合直到找到“完美”的一组参数;助手是因为它可以快速帮你完成样本内外切分对比,让你更快意识到策略不稳健。关键在于你是否有意识地在流程中加入验证环节,而不是一味追求回测曲线好看。
7. 常见问题与避坑清单
7.1 数据类问题与解决
数据问题是我整个开发过程中遇到最多的一类。最典型的包括:数据源返回的停牌日K线缺失导致复权计算错误、不同股票交易日不同步导致策略在计算滚动均值时数据对齐偏位、后复权价格出现负数导致指标异常。
我建议在数据层输出一张“数据质量报告”,每天更新后自动检查每个标的的K线条数、日期连续性、价格正负性。AI非常适合生成这类检查脚本,因为它本质是一个有明确输入输出和判断规则的逻辑,不需要太多领域知识。
7.2 代码逻辑类问题与解决
代码逻辑问题主要集中在前视偏差和边界处理。前视偏差的典型症状是回测收益异常高,胜率高得离谱,资金曲线完美得不像真钱。排查方法是随机挑选几个交易日,手动验证信号产生时对应的K线是否已经收盘。
边界问题则更隐蔽,比如新上市股票数据长度不足导致均线为NaN、停牌股票复牌后跳空导致滑点成本低估、除权除息日价格跳变导致收益计算虚高等。这些都需要在审查方法论部分提到的“跑数据”环节逐一发现并修复。
7.3 AI生成代码时的提示词避坑
最后把我的提示词避坑经验整理出来。第一,不要问“帮我写一个量化交易系统”,太宽泛的指令只能得到玩具级输出;第二,要告诉AI“输出完整可运行的代码,而不是示例片段”,否则它经常给你缩略版;第三,把金融场景的特殊约束写清楚,比如不要用当日收盘价成交、交易成本必须包含佣金和滑点、必须处理持仓不足等边界条件。
我用的一个固定提示词模板是:“实现一个Python函数,完成[具体功能],输入是[详细字段],输出是[具体结构],要求[具体约束1],[具体约束2],并附上注释说明关键逻辑。”这样限定后,AI生成代码的可用率大幅提升,我只需要做审查和边界补充,而不是大改。
7.4 回测表现与实盘预期的差距认知
最后想说的是,回测表现好不代表实盘能赚钱。回测只是在历史数据上做了一次完美的后视镜检验,实盘要面对的是滑点扩大、流动性不足、错过最佳成交价、开盘涨停买不进等各种现实约束。我的框架里已经把交易成本模型放到最大范围,但在实盘前,仍然建议先用模拟盘跑一段时间,验证框架的实时数据对接和交易执行逻辑是否正常。
我个人的体会是,AI编程确实能把量化框架的开发效率提升一个量级,但它的价值上限取决于你对业务的理解深度。AI能帮你把脑子里的想法快速变成可运行的代码,但它不会替你想清楚策略逻辑是否合理、参数是否过拟合、风控是否到位。框架可以交给AI来搭,但判断力必须自己来练。