简介:这份量化策略源码资源面向具备一定Python与机器学习基础的量化研究者、金融数据爱好者及策略开发初学者,围绕个股日线数据的采集、预处理、建模、评估与仓位管理,提供一条可落地的完整流程参考,帮助读者理解从原始行情到组合配置的实现思路。资源包共18个文件,以10个Python脚本和7个SQL建表/数据脚本为主,另附1份Markdown说明文档,压缩包约56KB,体积轻量便于本地部署与二次修改。其中Python脚本覆盖数据采集、训练集整合、SVM建模、回测评估与马科维茨组合等环节,SQL文件则对应行情、股票池与资金等数据表结构。目前已有3570人学习下载,读者可据此搭建本地量化实验环境,掌握查准率、查全率与F1分值的评估方法,并借鉴推进式建模与仓位管理思路,形成可复用的策略研究框架。
1. 量化策略源码:从一份策略包到能跑起来的回测骨架
第一次拿到一份量化策略源码,很多人的第一反应是打开主文件找买卖信号,结果翻了三层目录还在看数据加载。这份资源解决的不是“教你炒股”,而是把一套策略从信号生成、仓位管理到回测统计的完整链路摊开给你看。它适合两类人:一类是刚接触量化、想照着跑通第一个回测的新手;另一类是手里有策略想法、缺一套可复用工程骨架的开发者。源码里最值得看的不是某条均线参数,而是策略与执行、数据、风控之间的解耦方式——这决定了你后面能不能把新想法快速塞进去验证,而不是每次推倒重来。
2. 工程结构与技术选型:策略、数据、回测三层怎么拆
2.1 目录骨架与模块职责
一份能二次开发的量化策略源码,目录通常不会把所有逻辑塞进一个文件。按常见工程做法,结构大致如下:
quant_strategy/ ├── config/ │ └── settings.yaml # 标的池、时间区间、手续费、滑点 ├── data/ │ ├── loader.py # 行情读取与字段标准化 │ └── cache/ # 本地缓存的历史数据 ├── strategy/ │ ├── base.py # 策略基类,定义 on_bar / on_init │ └── dual_ma.py # 具体策略实现 ├── engine/ │ ├── backtest.py # 回测主循环 │ └── broker.py # 撮合、手续费、滑点模拟 ├── risk/ │ └── position.py # 仓位计算与最大回撤约束 ├── report/ │ └── metrics.py # 收益、夏普、回撤统计 └── run.py # 入口,串起数据→策略→回测→报告这个拆法的核心是把“策略逻辑”和“执行逻辑”分开。strategy/里只关心什么时候买、什么时候卖;engine/负责按时间推进、撮合成交;risk/决定每次下多少;report/只做统计。新手最容易犯的错是把仓位计算写进策略信号里,后面想换风控规则就得改策略文件,改着改着就乱了。
2.2 技术栈与依赖选择
这类项目通常用 Python,依赖集中在几个库:pandas做时间序列和表格处理,numpy做向量化计算,matplotlib出净值曲线,配置用PyYAML读 YAML。回测本身不依赖重型框架,主循环手写,好处是每一步都能打断点看中间变量,坏处是性能一般,适合日线或分钟级、标的数量不多的场景。
pip install pandas numpy matplotlib pyyaml如果源码里带了requirements.txt,直接pip install -r requirements.txt更稳。注意版本,pandas2.x 对append这类老写法不兼容,源码里如果出现df.append(...),要么降版本,要么改成pd.concat。这是跑老项目最常见的翻车点之一。
2.3 配置驱动的设计意图
把标的、区间、费率放进config/settings.yaml,而不是硬编码在代码里,是为了让同一套策略能快速换标的做对比。常见配置项:
| 配置项 | 含义 | 常见取值 |
|---|---|---|
| symbols | 标的列表 | 单标的或多标的 |
| start_date / end_date | 回测区间 | 至少覆盖一轮完整行情 |
| commission | 单边手续费率 | 万分之几量级 |
| slippage | 滑点 | 按跳数或比例 |
| initial_cash | 初始资金 | 影响仓位取整 |
参数说明里最容易被忽略的是滑点。很多人回测收益漂亮,实盘一上就缩水,原因就是没算滑点。源码里如果broker.py有滑点字段,别把它设成 0 图省事。
3. 核心模块拆解:一个 bar 从进入到成交的完整链路
3.1 策略基类与信号生成
策略基类定义统一接口,具体策略只实现信号逻辑。典型写法:
# strategy/base.py class BaseStrategy: def __init__(self, params): self.params = params self.position = 0 # 当前持仓数量 self.signals = [] # 记录每根 bar 的信号 def on_init(self, context): """回测开始前初始化,可预计算指标""" pass def on_bar(self, bar): """每根 bar 调用一次,返回目标仓位或信号""" raise NotImplementedErroron_bar收到的是单根 bar 的数据(时间、开高低收、成交量),返回目标仓位。这里有个设计选择:返回“目标仓位”比返回“买/卖”更好做仓位管理,因为目标仓位是绝对值,引擎可以直接算差额下单,避免重复加仓。
具体策略以双均线为例:
# strategy/dual_ma.py import pandas as pd from strategy.base import BaseStrategy class DualMAStrategy(BaseStrategy): def on_init(self, context): self.fast = self.params["fast"] self.slow = self.params["slow"] self.close_history = [] def on_bar(self, bar): self.close_history.append(bar["close"]) if len(self.close_history) < self.slow: return 0 # 数据不够,空仓 series = pd.Series(self.close_history) fast_ma = series.iloc[-self.fast:].mean() slow_ma = series.iloc[-self.slow:].mean() if fast_ma > slow_ma: return 1 # 目标满仓 return 0 # 目标空仓逻辑说明:每来一根 bar 就把收盘价追加进历史,长度不够慢线周期时返回 0 不交易;够长度后算快慢均线,快线在上返回 1,否则返回 0。参数fast和slow从配置传入,改参数不用动代码。注意这里用的是“滚动窗口重算均值”,数据量大时效率低,熟手会改成增量维护均线,但新手先跑通再说。
3.2 回测主循环与撮合模拟
引擎负责按时间推进,把 bar 喂给策略,再把目标仓位翻译成成交:
# engine/backtest.py def run_backtest(data, strategy, broker, initial_cash): cash = initial_cash position = 0 equity_curve = [] strategy.on_init({}) for bar in data.itertuples(): target = strategy.on_bar({ "datetime": bar.Index, "close": bar.close, }) target_shares = int(cash * target / bar.close) if target else 0 delta = target_shares - position if delta != 0: fill_price = broker.fill_price(bar.close, delta) cost = abs(delta) * fill_price fee = cost * broker.commission cash -= delta * fill_price + fee position += delta equity = cash + position * bar.close equity_curve.append(equity) return equity_curve逻辑说明:target是策略给的目标仓位比例(0 或 1),乘以现金再除以价格得到目标股数;delta是本次需要买卖的差额;broker.fill_price在收盘价基础上加滑点;手续费按成交金额乘费率扣。参数initial_cash决定初始规模,broker.commission和滑点共同决定回测的真实度。这里用int取整股,A 股场景合理,如果是加密货币或基金可以改成浮点。
3.3 绩效统计与净值曲线
回测跑完得到净值序列,统计模块负责把曲线翻译成可比较的指标:
# report/metrics.py import numpy as np def calc_metrics(equity_curve): equity = np.array(equity_curve, dtype=float) returns = np.diff(equity) / equity[:-1] total_return = equity[-1] / equity[0] - 1 annual_return = (1 + total_return) ** (252 / len(returns)) - 1 sharpe = returns.mean() / (returns.std() + 1e-9) * np.sqrt(252) peak = np.maximum.accumulate(equity) max_drawdown = ((equity - peak) / peak).min() return { "total_return": total_return, "annual_return": annual_return, "sharpe": sharpe, "max_drawdown": max_drawdown, }逻辑说明:returns是逐期收益率,annual_return按 252 个交易日年化,sharpe用日收益均值除标准差再乘根号 252,max_drawdown用累计最大值算回撤。参数里 252 是交易日假设,换成其他市场要改。注意1e-9是防止标准差为 0 时除零,属于工程上的小保险。
4. 跑起来:从环境到第一张净值曲线的操作步骤
4.1 环境准备与数据落位
先把依赖装好,再把数据放到data/cache/下。源码通常约定数据文件为 CSV,字段至少包含datetime, open, high, low, close, volume。如果拿到的是其他格式,在data/loader.py里加一层转换即可:
# data/loader.py import pandas as pd def load_bars(path): df = pd.read_csv(path, parse_dates=["datetime"]) df = df.sort_values("datetime").set_index("datetime") required = ["open", "high", "low", "close", "volume"] missing = [c for c in required if c not in df.columns] if missing: raise ValueError(f"缺少字段: {missing}") return df逻辑说明:读 CSV 后把时间列解析成日期并设为索引,排序保证时间递增,然后校验必需字段。参数path是数据文件路径。这一步的报错信息要写清楚缺哪个字段,不然新手看到KeyError会懵。
4.2 入口脚本与参数覆盖
run.py把各模块串起来,通常支持命令行覆盖配置:
# run.py import argparse, yaml from data.loader import load_bars from strategy.dual_ma import DualMAStrategy from engine.backtest import run_backtest from engine.broker import Broker from report.metrics import calc_metrics parser = argparse.ArgumentParser() parser.add_argument("--config", default="config/settings.yaml") parser.add_argument("--fast", type=int) parser.add_argument("--slow", type=int) args = parser.parse_args() cfg = yaml.safe_load(open(args.config, encoding="utf-8")) if args.fast: cfg["strategy"]["fast"] = args.fast if args.slow: cfg["strategy"]["slow"] = args.slow data = load_bars(cfg["data_path"]) strategy = DualMAStrategy(cfg["strategy"]) broker = Broker(cfg["commission"], cfg["slippage"]) curve = run_backtest(data, strategy, broker, cfg["initial_cash"]) print(calc_metrics(curve))逻辑说明:--config指定配置文件,--fast和--slow允许命令行临时改均线周期,方便做参数扫描。参数覆盖的顺序是“配置文件打底,命令行优先”。跑的时候先小改参数验证链路通不通,再上完整区间。
4.3 第一次运行的验证清单
跑通的标准不是“没报错”,而是结果能对上常识。建议按这个顺序检查:净值曲线起点是否等于初始资金;交易次数是否合理(双均线在震荡市会频繁交易,次数过少可能是信号没触发);最大回撤是否在可接受范围;把滑点调大后收益是否明显下降(如果不降,说明滑点没生效)。这几步走完,基本能确认引擎没有大问题。
提示:第一次跑先用短区间和单标的,确认链路通了再扩到多标的和长区间,否则报错定位会很痛苦。
5. 避坑与排查:回测结果不对劲时先看这几处
5.1 未来函数:信号用了当天收盘却按当天开盘成交
现象是回测收益异常漂亮,实盘完全复现不了。原因通常是on_bar里用了当前 bar 的收盘价算信号,但成交价也用了同一个收盘价,等于提前知道结果。解决是把成交价改成下一根 bar 的开盘价,或者在信号里只用上一根 bar 的数据。检查方法:在on_bar里打印信号时间和成交时间,看是否同一时刻。
5.2 数据缺口与停牌处理
现象是某段时间净值横盘或跳变。原因是数据里有缺失交易日,或者停牌期间价格被前值填充。解决是在loader.py里做交易日对齐,缺失值明确标记而不是静默填充;回测循环里遇到无效 bar 跳过并记录。参数上要区分“真停牌”和“数据缺失”,前者不该交易,后者该补数据。
5.3 手续费与滑点方向搞反
现象是加了滑点后收益反而变高。原因是滑点加在了错误方向:买入应该加价、卖出应该减价,如果统一加价,卖出时反而多赚。解决是在broker.fill_price里按买卖方向分别处理:
def fill_price(self, price, delta): direction = 1 if delta > 0 else -1 return price * (1 + direction * self.slippage)参数delta为正表示买入,滑点向上;为负表示卖出,滑点向下。
5.4 仓位取整导致小资金失真
现象是初始资金设小了以后收益曲线完全变形。原因是int(cash * target / price)在小资金下经常取整到 0,策略等于没交易。解决是资金规模设到能买至少一手,或者改成按金额下单、允许小数份额。这是回测参数设置里很隐蔽的一个坑。
5.5 参数过拟合:调出来的最优解不可信
现象是某组均线参数回测收益极高,换区间就崩。原因是参数在单一区间上被反复优化。解决是留出一段样本外区间,或者做滚动窗口验证。血泪经验是:回测里最漂亮的参数,往往是最不该直接用的那组。
6. 二次开发:把新策略塞进骨架的改造思路
拿到这套源码后,最有价值的改造不是调均线参数,而是换策略逻辑时不动引擎。具体做法是新建一个继承BaseStrategy的类,只实现on_init和on_bar,然后在run.py里换类名。比如把双均线换成布林带突破,信号部分重写,仓位和统计完全复用。
改造时建议加一层参数校验,避免配置写错导致静默失败:
def validate_params(params): assert params["fast"] < params["slow"], "快线周期必须小于慢线" assert params["slow"] > 0, "周期必须为正" return params验证方法上,我习惯做三组对照:原策略原参数、新策略原参数、新策略调参后,三组跑同一区间同一费率,看差异是否来自策略本身而不是环境变化。另外每次改完引擎相关代码,都强制跑一遍已知结果的小样本用例,确认统计数字没漂移。
| 改造点 | 改动文件 | 验证方式 |
|---|---|---|
| 换信号逻辑 | strategy/ 下新增类 | 同区间对比原策略 |
| 换风控规则 | risk/position.py | 检查最大回撤变化 |
| 换数据源 | data/loader.py | 字段校验 + 抽样对账 |
| 加指标 | report/metrics.py | 手算小样本核对 |
从那以后我每次动回测引擎,哪怕只改一行滑点计算,都强制走一遍小样本对账,因为引擎的错不会报错,只会安静地把收益算漂亮。希望这份拆解能帮你少走几步弯路,把源码跑起来之后,真正的乐趣在往里塞自己的策略。
本文还有配套的精品资源,点击获取