
简介基于 Python 的开源量化交易项目定位为面向股票、期权、期货、比特币和机器学习策略的完整源码架构包适合想系统学习量化交易开发的学生、个人投资者以及需要课程设计、毕业设计或初期项目立项演示的开发者。压缩包包含 382 个文件整体约 55.87MB其中 248 个 py 文件为可运行的策略与回测核心51 个 ipynb 文件便于按单元格阅读探索数据和模型7 个 csv 文件提供股票、期货、港股、美股等样本数据另配有 png、gif 可视化过程、md 文档和 prototxt 模型配置目录层次较规范。该项目由作者 baidu_1234567 整理发布已有 1320 人浏览学习。除了完整可运行的回测框架、数据抓取与预处理脚本、多市场样本数据和本地数据库文件还附带事件驱动、组合管理、机器学习预测等模块说明可直接对照源码理解从数据、策略到执行的完整链路内置动图展示了部分策略效果能帮助快速验证结果也方便移植改造和二次创新。1. 先拆这套 python 量化交易架构别急着从零写回测框架做了一年多单策略回测我最大的教训是收益曲线好看不是本事把订单、资金、数据、风控串起来才是。这份基于 python 的开源量化交易架构把股票、期权、期货、比特币统一进一个事件驱动框架机器学习预测模块、回测、模拟撮合、风控和实盘接口都齐了。特别适合两类人一是想把自己的研究策略工程化但没有系统设计经验的人二是在成熟平台上跑策略、想迁到本地做二次开发的人。它能直接回答数据从哪来、信号在哪生成、订单怎么被撮合、仓位怎么管理。源码包本身带说明文档模块边界清楚改起来比从空文件开始快太多。2. 源码包整体设计事件驱动、数据流与多资产适配打开这套源码第一件事不是急着跑策略而是先把主循环和几个核心模块认清楚。我见过不少人把策略代码直接塞进一个 for 循环里加日志、加风控、加多资产之后就改不动了。这套架构用了事件驱动的思路把行情、信号、订单、成交都当成事件在模块间传递模块之间不互相调用而是通过一个统一的事件总线通信。这样做的好处是回测和实盘可以复用同一套逻辑只是把数据源和撮合器换掉。事件驱动听起来玄学实际上就是一个队列加一张事件类型到处理函数的映射表。行情到了发一个 MarketEvent策略算完发一个 SignalEvent下单模块收到信号后发 OrderEvent最后交易所或模拟撮合器返回 FillEvent。每个模块只关心自己注册的事件类型不关心事件是谁发的。2.1 事件驱动主循环为什么需要一个统一的总线# 事件驱动主循环简化版 import queue class EventEngine: def __init__(self): self.events queue.Queue() self.handlers {} def register(self, event_type, handler): # 同一个事件类型可以注册多个处理函数 self.handlers.setdefault(event_type, []).append(handler) def put(self, event): self.events.put(event) def run(self): while True: event self.events.get() for handler in self.handlers.get(event.type, []): handler(event) def stop(self): self.events.put(None)这里queue.Queue是线程安全的实盘接 websocket 行情时可以把行情推送放到一个线程里put主循环在另一个线程里消费不用加锁。register的作用是把事件类型和对应的处理函数绑定比如MarketEvent绑到strategy.on_barFillEvent绑到portfolio.update_position。这样的设计让新增模块变得很便宜我后来加了一个基于机器学习的信号模块只注册一个事件类型旧策略一行没改。如果不用事件驱动最常见的翻车方式是在策略里直接调用券商接口。回测时还可以用模拟接口上了实盘之后一个网络超时就会卡住整个策略线程。事件驱动的另一个隐藏好处是你可以把行情处理、策略计算、下单执行分别放到不同的进程里中间用消息队列传递事件回测和实盘只是把最后的执行器换掉。2.2 核心数据格式K线、订单与成交的结构定义多资产量化最容易被忽略的是数据结构的统一。股票、期货、期权、比特币各有各的字段如果每个策略各写各的读取逻辑后面做因子研究时会非常痛苦。这套源码里把行情统一成一个Bar对象资产类型用asset_type字段区分扩展字段用默认值兜底。dataclass class Bar: symbol: str asset_type: str # stock / futures / option / crypto datetime: datetime open: float high: float low: float close: float volume: float # 扩展字段 open_interest: float 0.0 # 期货持仓量看合约活跃度 multiplier: float 1.0 # 合约乘数股票是1IF股指期货通常是300 settle: float 0.0 # 期权/期货结算价盯市用这里有几个字段需要特别说明。multiplier是最容易踩坑的字段很多人在股票上跑通的逻辑直接搬到期货上发现盈亏数字放大了几十倍就是因为没把合约乘数算进保证金和盈亏。settle字段在回测里看似没用但做期货逐日盯市时每日盈亏是按结算价计算的不是按收盘价。订单和成交也类似Order至少包含方向、数量、价格类型、有效时间Fill则记录实际成交价、数量和手续费。实盘接口里这些字段会被翻译成交易所要求的格式。数据格式统一之后策略层和风控层写一遍就可以适配所有资产类型这是这套架构能同时处理股票、期权、期货、比特币的关键。2.3 策略层与执行层如何解耦策略只负责产生信号不直接下单。这是量化框架里最常见的分层策略层、风控层、执行层。策略拿到行情后只生成一个目标持仓或信号风控层检查资金、持仓限额、涨跌停过滤执行层才真正发单。class Strategy: def __init__(self, name): self.name name def on_bar(self, bar, portfolio): # 只输出信号不直接调用交易所接口 raise NotImplementedError class RiskManager: def check_order(self, order, portfolio) - tuple: # 返回 (是否通过, 拒绝原因) if order.quantity 0: return False, quantity must be positive if portfolio.cash order.quantity * order.price * 0.1: return False, insufficient margin return True, ok这样分离之后策略研究员不需要理解柜台接口实盘运维也不需要看懂策略逻辑只需要保证风控规则足够保守。常见做法是在信号事件里带上target_weight而不是quantity由组合模块把目标权重换算成具体股数。这套源码里也是这么做的因为多资产组合的权重管理远比单一标的复杂比如期货保证金占用、期权组合保证金、比特币资金费率这些都要在组合层统一计算。2.4 资产类型在撮合和资金规则上的差异资产交易时段涨跌停资金占用回测里需要特别注意股票有午休T1通常有全额资金买入当日不能卖信号不能隔日反手期货有夜盘T0有涨跌停保证金合约乘数和逐日盯市期权有集合竞价波动剧烈权利金/组合保证金行权到期、价值非线性衰减比特币7x24无全额或杠杆无休市注意隔夜跳空和资金费率这一张表基本决定了策略在回测和实盘中的行为差异。比如股票策略里如果信号在收盘后触发实盘只能明天买如果回测里用当天收盘价成交就高估了收益期货策略里如果不处理换月临近交割月的价格波动和流动性问题会让净值曲线失真。源码包里对这四个资产类型分别有适配器我建议拿到包之后先跑一遍各资产类型的示例再看具体适配器代码。我刚拿到这套架构的时候为了省事直接把股票配置复制到期货结果回测出来的资金曲线全是锯齿。后来发现是没有处理保证金占用和合约乘数资金曲线才会在每次开仓时出现大坑。多资产回测不能只换行情数据撮合规则、资金模型、交易时段都要跟着资产类型走。3. 从零跑通第一个策略环境、配置与回测结果解读把架构看懂之后接下来就是落地。我第一次跑这套源码时踩了两个坑一个是直接pip install装错了包版本另一个是配置文件里的日期格式写成了2020/01/01程序不认。下面按顺序讲一遍。3.1 环境准备与依赖安装建议用 Python 3.8 到 3.10太新的版本容易出现某些依赖还没有预编译 wheel 的问题。先建虚拟环境再装requirements.txt。python -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txtrequirements.txt里主要锁定了 numpy、pandas、matplotlib、pyyaml、scikit-learn以及一个事件循环库。如果你只做回测不接实盘可以先把实盘相关的依赖注释掉减少编译时间。Windows 用户如果装某些库报错优先用 Anaconda 建环境而不是在系统 Python 里硬装。装完依赖后用python -c import pandas; print(pandas.__version__)验证一遍确认版本跟说明文档里的版本对得上。我这里遇到过一次 pandas 2.x 和旧代码不兼容的问题特征是append被移除或者fill_method报错回滚到 1.5.3 就正常了。3.2 配置文件里的关键参数这套源码用 YAML 做配置文件行情数据源、回测区间、初始资金、手续费、滑点全在配置里。先抄作业再改参数比在代码里到处找常量靠谱。data: start_date: 2020-01-01 end_date: 2023-12-31 source: csv # csv / sqlite / 交易所接口 symbols: - {symbol: 600519.SS, asset_type: stock} - {symbol: BTCUSDT, asset_type: crypto} backtest: initial_cash: 1000000 commission: 0.0003 slippage: 0.001 fill_on: next_open lot_size: 100commission是按成交金额的比例股票一般万三期货按手数收费的话要额外配一个按合约乘数计算的字段。slippage我一般设为 0.001实盘手续费加滑点加起来差不多这个量级。fill_on是成交价假设next_open表示信号出现后用下一根K线的开盘价成交current_close表示用当前K线收盘价成交。对日线策略来说前者更接近真实后者会高估收益。lot_size是下单最小单位股票 A 股是 100 股一手比特币这里可以配成 0.001期货则是 1 手。不要小看这个字段如果不做手数取整回测里会出现买入 137 股这种无法成交的订单盈亏曲线会和实盘差很远。3.3 跑通内置趋势跟踪策略配置写好后直接跑内置示例。源码包里带了一个run_backtest.py入口通过--strategy指定策略名称。python run_backtest.py --config config/demo.yaml --strategy trend_follow趋势跟踪在这个包里是最简单的策略实现适合用来验证链路是否通。核心逻辑只有两行均线判断class TrendFollow(Strategy): def __init__(self, fast10, slow30): self.fast fast self.slow slow def on_bar(self, bar, portfolio): fast_ma portfolio.history(bar.symbol, self.fast).close.mean() slow_ma portfolio.history(bar.symbol, self.slow).close.mean() if fast_ma slow_ma and not portfolio.has_position(bar.symbol): portfolio.target_weight(bar.symbol, 0.2) elif fast_ma slow_ma and portfolio.has_position(bar.symbol): portfolio.target_weight(bar.symbol, 0)portfolio.history取最近self.fast根K线target_weight是目标仓位权重由组合模块再去换算成手数。这里的参数fast10和slow30是示例值用来跑通流程没问题别当成可以稳定盈利的参数。如果你运行后没有输出任何交易先检查配置里的symbols是否和 CSV 文件名一致再检查start_date是否能覆盖到足够的K线至少要比slow参数大。3.4 看懂回测报告跑完会输出一个回测报告关键指标如下指标含义参考关注点total_return累计收益率和策略基准比annual_return年化收益率排除回测区间长短影响max_drawdown最大回撤比收益率重要sharpe_ratio风险调整收益小于1通常很难上实盘turnover换手率换手高说明手续费吃收益win_rate胜率趋势策略胜率不高但盈亏比大我一般不会只看最后一条净值曲线。先看最大回撤发生在哪一段时间再结合当时的市场环境判断是策略失效还是正常的回撤。如果回撤超过 20%即使年化 30% 我也很难拿住。再对比一下换手率如果换手率很高但收益没上去多半是手续费和滑点把利润吃掉了。至此链路已经通了可以开始改自己的策略。但改之前先把下一章的机器学习模块看一遍因为这里最容易出数据穿越问题。4. 机器学习模块实战与常见问题排查从特征工程到滚动回测源码包里机器学习部分不是简单地调用几次sklearn而是把因子、标签、训练、预测、组合转换串起来。这一章最常见的错误不是模型选得不好而是数据处理阶段就把未来信息塞进了特征。4.1 特征怎么构造才不偷看未来金融数据的特征一定要保证在t时刻只能使用t时刻及之前的信息。我见过最典型的错误是直接用全样本的均值和标准差做标准化然后训练测试一起做这等于把未来的分布信息提前偷看了一遍。正确做法是每个时间点只用过去窗口内的统计量。def build_features(df, lookback20): df df.copy() df[ret] df[close].pct_change() df[ma5] df[close].rolling(5).mean() df[ma20] df[close].rolling(20).mean() df[vol_ratio] df[volume] / df[volume].rolling(20).mean() df[target] df[close].shift(-5) / df[close] - 1 return df.dropna()shift(-5)表示用未来5天的收益作为预测目标这是监督学习的标签不是特征。建模时X只能取ma5, ma20, vol_ratio, ret这些列target只出现在y里。如果你不小心把target也当成特征模型在训练集上会表现得极其好样本外直接崩掉。一个更隐蔽的问题在标准化。常见的做法是用sklearn.preprocessing.StandardScaler.fit() 在训练集上算均值和标准差然后把同样的 scaler 变换验证集和测试集。如果对整个df做fit再切分验证集会在训练之前就影响标准化参数回测结果会略微偏乐观实盘时却拿不到这种未来信息。4.2 时间序列切分为什么不能随机打乱普通的机器学习任务可以把样本随机分成训练集和测试集但金融数据不行。相邻两天的特征高度相关随机打乱会让模型“看到”未来样本的统计信息。时间序列必须按时间顺序切分。from sklearn.ensemble import GradientBoostingRegressor n_train int(len(df) * 0.6) n_val int(len(df) * 0.2) train df.iloc[:n_train] val df.iloc[n_train:n_train n_val] test df.iloc[n_train n_val:] features [ret, ma5, ma20, vol_ratio] model GradientBoostingRegressor(max_depth3, n_estimators100) model.fit(train[features], train[target]) val_pred model.predict(val[features])常见的做法是留出最后一段做样本外验证前一段训练中间一段调参。你还可以用TimeSeriesSplit做交叉验证但注意它的每一折训练集必须在验证集之前。max_depth3和n_estimators100是起点参数金融数据信噪比低树太深很容易过拟合。4.3 样本外滚动回测单次切分只能得到一个静态结果实盘里模型会过一段时间重新训练。源码包里写了滚动训练的逻辑每次只学习最近一段时间的数据然后预测未来一段再把窗口往前推。这样做能比较真实地反映模型在时间上的稳定性。import pandas as pd retrain_window 1200 test_window 200 step 200 for start in range(0, len(df) - retrain_window - test_window, step): train df.iloc[start:start retrain_window] test df.iloc[start retrain_window:start retrain_window test_window] model.fit(train[features], train[target]) test_pred model.predict(test[features]) ic test[target].corr(pd.Series(test_pred, indextest.index)) print(fwindow {start}: IC{ic:.4f})这里的IC是预测值和真实收益的相关系数量化里常用它衡量因子预测能力。滚动窗口输出的多个 IC 应该相对稳定如果第一段 IC0.1、第二段 IC-0.05说明模型抓到的规律不稳定不适合实盘。4.4 常见问题排查清单现象训练集收益很高验证集收益很差。原因要么特征里含有未来信息要么模型太复杂把噪声也拟合了。解决先检查shift方向再做特征重要性排序最后把max_depth降到 2 或 3 试试。现象不同滚动窗口的 IC 忽正忽负。原因特征本身和未来收益的关系不稳定或者市场状态切换。解决换更稳健的特征比如横截面排名因子而不是原始价格类因子。现象回测净值稳步上涨但持仓全是同一行业。原因没有做行业或品种暴露控制模型找到了一个行业 beta。解决在特征里加入行业收益率做中性化或者在组合层限制单行业最大权重。现象把机器学习预测结果换成股票池后交易次数太多。原因预测值每天都在变换仓频率很高。解决加一个持仓置信度阈值比如预测收益要大于某个分位数才换仓否则继续持有。现象标准化时用了全样本统计量样本外表现正常但回测异常爆炸。原因数据穿越。解决标准化参数必须从训练集计算验证集和测试集只调用变换。这五条里数据穿越是最隐蔽的。我拆过的不少源码包里都有这个问题表面上回测收益率高得吓人实际上是一行StandardScaler().fit(df)的位置写错了。排查的时候先看数据预处理代码段再去看模型训练代码大多数坑都能在这两层里定位。5. 上线前的边界值检查滑点、撮合模式与可复现验证策略在回测里有效不代表实盘有效。上线前我必做的三件事换成交价假设、测滑点敏感性、做一次严格的不可见数据复现。这套源码把回测撮合接口独立出来了所以做这些验证不需要改策略代码。5.1 用参数敏感性测试找出“刚好能赚”的策略把滑点从 0 逐步加到 0.005观察年化收益和回撤的变化。如果滑点从万分之一加到千分之五收益就变成负的说明策略赚的是手续费差不是市场规律。slippageannual_returnmax_drawdown028.6%-12.4%0.000521.3%-13.8%0.00115.1%-16.2%0.0026.2%-22.9%看到这个表你就明白趋势策略在参数变差时开始亏损说明它赚的是趋势收益但利润很薄。实盘至少要按 0.001 以上的滑点预留安全垫。5.2 把回测撮合改为下一根开盘成交这是最容易修的一个配置fill_on从current_close改成next_open。信号产生在收盘前几秒用收盘价成交是理想化假设。改成下一根开盘价之后回测收益率通常会降低一个量级但这才是正常的。不要舍不得这个差值它是你实盘能拿到的真实表现。5.3 从一次实盘事故里养成的习惯我印象最深的一次是一个机器学习策略在回测里年化 45%实盘三个月亏了 12%。后来定位到两个原因一是回测里用收盘价撮合二是特征标准化用了全样本。把这两处改掉后回测年化从 45% 掉到 16%实盘随即转正。从那以后我每次拿到新的策略源码都会强制把最后 20% 数据单独锁起来调参时只碰前面 80%等所有参数定稿后再用最后 20% 盲测一次。如果你下载了这套源码建议第一件事先跑一遍自带的示例配置确认它可以复现出说明文档上的数值再开始改策略。希望你拿到这份资源后能少走我那些偏路把时间花在值得研究的地方。本文还有配套的精品资源点击获取