
1. 为什么我劝新手用双均线策略作为第一个量化项目1.1 从一次真实的亏损经历说起三年前我刚接触量化交易的时候满脑子想的都是怎么搞一个年化收益翻倍的策略。那时候我连K线的基本逻辑都没吃透就急着去写什么机器学习预测模型结果实盘跑了两周账户回撤了将近百分之二十。后来我复盘发现问题根本不在于模型不够复杂而在于我连最基础的趋势跟踪逻辑都没有验证过。这个教训让我明白一个道理新手入门量化交易最忌讳的就是一上来就追求复杂策略。你应该先找一个逻辑简单、参数少、容易验证的策略把从数据获取到回测再到实盘的完整链路跑通。而双均线策略就是那个最适合练手的标的。所谓双均线策略核心逻辑非常朴素用两条不同周期的移动平均线来判断趋势方向。短周期均线代表近期价格走势长周期均线代表中长期趋势。当短均线上穿长均线时说明近期买盘力量增强可能形成上涨趋势这时候开仓做多当短均线下穿长均线时说明卖压增大趋势可能反转这时候平仓离场。你可能会觉得这也太简单了能赚钱吗说实话单靠双均线策略确实很难在市场上持续盈利但它是一个完美的教学工具。它的每一个环节都是透明的你能清楚地知道每一笔交易为什么开仓、为什么平仓这对于理解量化交易的底层逻辑至关重要。1.2 双均线策略到底解决了什么问题很多新手会问既然双均线策略盈利能力有限为什么还要学它我的回答是它解决的不是赚钱问题而是认知问题。第一它帮你建立规则化交易的思维。以前你看盘是凭感觉觉得要涨了就买觉得要跌了就卖。双均线策略强迫你把模糊的盘感转化成明确的数学规则什么条件下开仓什么条件下平仓每次交易多少仓位止损设在什么位置。这些规则一旦确定就不允许盘中临时更改。第二它让你理解回测的真正意义。回测不是用来证明你的策略能赚多少钱的而是用来验证你的策略逻辑是否成立、在历史行情中表现如何、最大回撤你能不能承受。双均线策略的回测结果通常不会太惊艳但正是这种平庸让你能冷静地分析策略的优缺点。第三它为你后续学习更复杂的策略打下基础。你现在学的数据获取、指标计算、信号生成、回测框架使用、绩效评估这些技能在后续学习多因子模型、机器学习策略时都会用到。双均线策略就像学编程时的Hello World简单但不可或缺。1.3 适合什么样的人来学这篇文章面向的是完全没有量化交易经验、但有一定Python基础的朋友。如果你已经能熟练使用pandas处理数据、用matplotlib画图那读起来会很顺畅。如果你Python刚入门也没关系我会把每一步的代码都写清楚你照着敲一遍就能跑通。需要提前说明的是量化交易不是稳赚不赔的买卖。双均线策略在震荡行情中会频繁止损在趋势行情中才能吃到利润。我见过太多人拿着回测曲线漂亮的策略冲进实盘结果被市场反复打脸。所以我的建议是先用小资金或者模拟盘跑至少三个月把策略的脾气摸透了再考虑加大投入。2. 动手之前环境搭建与工具选型2.1 Python环境安装的避坑指南工欲善其事必先利其器。量化交易的第一步不是写策略而是把开发环境搭好。我见过不少新手卡在环境配置这一步就放弃了所以这里我尽量说得细一点。首先去Python官网下载安装包。版本选择上我建议用Python 3.9到3.11之间的版本太老的版本很多库不支持太新的版本比如3.13有些量化库还没适配。下载的时候注意勾选Add Python to PATH这个选项否则后面在命令行里调用python命令会提示找不到。安装完成后打开命令行输入python --version如果能看到版本号就说明安装成功了。如果提示python was not found那多半是PATH没配好重新安装一遍记得勾选那个选项就行。接下来是代码编辑器。我强烈推荐VS Code免费、轻量、插件丰富。安装好VS Code后在扩展商店里搜索Python插件装上它会自动帮你配置代码补全、调试、格式化等功能。如果你习惯用PyCharm也可以社区版免费功能也够用。注意不要用Anaconda来管理量化环境。Anaconda虽然方便但它自带的库版本往往比较旧而且体积庞大。我建议直接用pip安装需要的库干净利落。2.2 必装的几个核心库环境搭好后打开命令行依次安装下面这几个库pip install pandas numpy matplotlib backtrader akshare逐个说一下它们的作用。pandas是数据处理的核心K线数据、指标计算全靠它numpy提供数值计算支持pandas底层也依赖它matplotlib用来画回测曲线和K线图backtrader是我们这次要用的回测框架akshare是一个免费的数据接口库可以获取A股、期货、基金等历史行情数据。这里重点说一下数据源的选择。网上经常有人问量化交易用的数据API最好的是什么我的经验是新手阶段用免费的akshare就够了。它数据全、更新及时、调用简单虽然偶尔会有接口不稳定的情况但对于学习来说完全够用。等你真正要跑实盘了再考虑付费的商用数据接口那时候你对数据质量的要求会更高。安装过程中如果遇到网络慢的问题可以加个国内镜像源pip install pandas numpy matplotlib backtrader akshare -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 Backtrader框架为什么值得学回测框架有很多选择国内比较流行的有Backtrader、vnpy、聚宽等。我选Backtrader作为教学框架原因有三个。第一它的文档和社区非常成熟。你遇到的绝大多数问题网上都能搜到答案。而且它的API设计很直观策略类的结构清晰新手容易理解。第二它支持多股回测和多种数据频率。你以后想扩展到多标的轮动策略Backtrader也能胜任。它内置了丰富的技术指标库双均线、MACD、RSI这些常用指标直接调用就行不用自己从头写。第三它的事件驱动架构和实盘逻辑一致。你在回测阶段写的策略代码稍作修改就能接入实盘接口。这种一致性对于后续从回测过渡到实盘非常重要。当然Backtrader也有缺点比如运行速度偏慢、中文资料相对少。但对于学习阶段来说这些都不是大问题。等你把双均线策略跑通了再去了解其他框架也不迟。3. 双均线策略的核心逻辑拆解3.1 移动平均线到底在算什么在写代码之前我们先把移动平均线的数学含义搞清楚。移动平均线Moving Average简称MA就是把最近N天的收盘价加起来除以N得到一个平均值。随着时间推移这个平均值会不断更新连起来就成了一条平滑的曲线。举个例子假设某只股票最近5天的收盘价分别是10元、10.5元、11元、10.8元、11.2元那么5日移动平均线今天的值就是1010.51110.811.2除以5等于10.7元。明天如果收盘价是11.5元那新的5日均线值就变成10.51110.811.211.5除以5等于11元。移动平均线的本质是对价格进行平滑处理过滤掉短期波动的噪音。周期越短均线越贴近价格反应越灵敏但也越容易发出假信号周期越长均线越平滑反应越迟钝但信号更可靠。双均线策略就是同时用两条不同周期的均线。常见组合有5日配20日、10日配30日、20日配60日。短周期均线叫快线长周期均线叫慢线。快线上穿慢线叫金叉是买入信号快线下穿慢线叫死叉是卖出信号。3.2 金叉死叉的信号逻辑金叉和死叉的判断本质上是在比较两条均线的相对位置。当快线从下方穿越慢线时说明短期价格走势开始强于中长期走势市场情绪可能转多。反过来快线从上方跌破慢线时说明短期走势转弱可能要进入下跌通道。这里有个细节需要注意金叉死叉的判断要基于当前K线和上一根K线的均线关系。也就是说不能只看当前快线是否大于慢线还要看上一根K线快线是否小于慢线。只有发生了穿越这个动作才是有效的信号。如果快线一直在慢线上方那只是持续的多头状态不是新的买入信号。用代码表达就是if self.fast_ma[0] self.slow_ma[0] and self.fast_ma[-1] self.slow_ma[-1]: # 金叉买入 elif self.fast_ma[0] self.slow_ma[0] and self.fast_ma[-1] self.slow_ma[-1]: # 死叉卖出其中[0]表示当前值[-1]表示上一根K线的值。这个判断逻辑看起来简单但很多新手会写成只比较当前值结果导致在多头趋势中反复发出买入信号这是常见的错误。3.3 参数选择背后的权衡双均线策略的参数就是两条均线的周期。选5和20还是10和30还是20和60这背后其实是在做一道权衡题。周期越短信号越多交易越频繁。5日配20日的组合在一年里可能产生几十次交易信号。交易频繁意味着手续费成本高而且容易在震荡行情中被反复打脸。但好处是能更快地捕捉到趋势反转在趋势初期就介入。周期越长信号越少交易越稀疏。20日配60日的组合一年可能只有几次交易。交易少意味着手续费低而且信号更可靠不容易被短期波动干扰。但缺点是反应慢等信号出来的时候趋势可能已经走了一半了。我的建议是新手先用10日配30日这个组合。它介于灵敏和迟钝之间交易频率适中适合用来理解策略的运行节奏。等你对策略的脾气有感觉了再去尝试其他参数组合看看回测结果有什么变化。另外提醒一句不要过度优化参数。有些人会拿历史数据反复测试找出一个回测收益最高的参数组合然后拿去做实盘。这种做法叫过拟合历史上表现最好的参数未来往往表现最差。参数选择要基于逻辑而不是基于历史收益。4. 完整代码实现与逐行解析4.1 数据获取与预处理我们先解决数据问题。用akshare获取一只股票的历史日线数据以贵州茅台为例import akshare as ak import pandas as pd # 获取贵州茅台日线数据 df ak.stock_zh_a_hist(symbol600519, perioddaily, start_date20200101, end_date20241231, adjustqfq) # 重命名列方便后续使用 df df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume }) # 转换日期格式并设为索引 df[date] pd.to_datetime(df[date]) df df.set_index(date) df df[[open, high, low, close, volume]] print(df.head()) print(f数据条数{len(df)})这段代码做了几件事调用akshare接口获取数据、把中文列名改成英文、把日期列转成datetime类型并设为索引、只保留OHLCV五列。adjustqfq表示前复权这个参数很重要做回测一定要用复权数据否则除权除息那天会出现价格跳空导致均线信号失真。数据拿到后先检查一下有没有缺失值print(df.isnull().sum())如果某列有缺失可以用前值填充df df.fillna(methodffill)4.2 Backtrader策略类的编写接下来是核心部分用Backtrader写双均线策略。先看完整代码import backtrader as bt class DualMovingAverageStrategy(bt.Strategy): params ( (fast_period, 10), # 快线周期 (slow_period, 30), # 慢线周期 (stake, 100), # 每次交易股数 ) def __init__(self): # 初始化两条均线 self.fast_ma bt.indicators.SMA( self.data.close, periodself.params.fast_period ) self.slow_ma bt.indicators.SMA( self.data.close, periodself.params.slow_period ) # 交叉信号 self.crossover bt.indicators.CrossOver(self.fast_ma, self.slow_ma) # 记录交易日志 self.trade_log [] def next(self): # 如果还没持仓 if not self.position: # 金叉买入 if self.crossover 0: self.buy(sizeself.params.stake) self.trade_log.append({ date: self.data.datetime.date(0), action: BUY, price: self.data.close[0] }) # 如果已经持仓 else: # 死叉卖出 if self.crossover 0: self.sell(sizeself.params.stake) self.trade_log.append({ date: self.data.datetime.date(0), action: SELL, price: self.data.close[0] })逐行拆解一下。params定义了策略的可调参数快线周期、慢线周期、每次交易股数。把这些参数抽出来方便后续做参数优化。__init__方法里初始化了两条SMA均线以及一个CrossOver指标。CrossOver是Backtrader内置的交叉判断指标当快线上穿慢线时返回1下穿时返回-1没有交叉时返回0。用它比手动比较均线值要简洁得多而且不容易出错。next方法是策略的核心逻辑每根K线都会调用一次。逻辑很直白没持仓的时候等金叉买入有持仓的时候等死叉卖出。self.position是Backtrader自动维护的持仓状态不需要自己管理。注意next方法里不要写print语句来调试因为回测几千根K线会打印几千行把终端刷爆。要记录交易信息像我上面那样存到一个列表里回测结束后统一输出。4.3 回测引擎的配置与运行策略写好了接下来配置回测引擎import backtrader as bt # 创建回测引擎 cerebro bt.Cerebro() # 添加策略 cerebro.addstrategy(DualMovingAverageStrategy) # 准备数据源 data bt.feeds.PandasData(datanamedf) cerebro.adddata(data) # 设置初始资金 cerebro.broker.setcash(100000.0) # 设置手续费万分之三 cerebro.broker.setcommission(commission0.0003) # 设置每次交易比例可选 # cerebro.addsizer(bt.sizers.PercentSizer, percents95) # 添加分析器 cerebro.addanalyzer(bt.analyzers.SharpeRatio, _namesharpe) cerebro.addanalyzer(bt.analyzers.DrawDown, _namedrawdown) cerebro.addanalyzer(bt.analyzers.Returns, _namereturns) cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _nametrades) # 打印初始资金 print(f初始资金{cerebro.broker.getvalue():.2f}) # 运行回测 results cerebro.run() strat results[0] # 打印最终资金 print(f最终资金{cerebro.broker.getvalue():.2f}) # 打印绩效指标 print(f夏普比率{strat.analyzers.sharpe.get_analysis()}) print(f最大回撤{strat.analyzers.drawdown.get_analysis()}) print(f收益率{strat.analyzers.returns.get_analysis()})这段代码里几个关键点。setcash设置初始资金我习惯用10万方便计算收益率。setcommission设置手续费A股实际手续费包括佣金和印花税卖出时还有千分之一的印花税这里简化处理只设了万分之三的佣金。如果你想更精确可以分开设置。分析器是Backtrader的一大亮点。SharpeRatio算夏普比率衡量单位风险下的超额收益DrawDown算最大回撤看策略在最差情况下会亏多少Returns算总收益率TradeAnalyzer统计每笔交易的盈亏情况。这些指标比单纯看最终资金有意义得多。4.4 回测结果的可视化回测跑完后用一行代码就能画出资金曲线和交易信号cerebro.plot(stylecandlestick, volumeFalse)出来的图里上半部分是K线图叠加了两条均线买卖点会用箭头标出来下半部分是资金曲线。通过这张图你能直观地看到策略在哪些位置开仓、哪些位置平仓、资金是怎么变化的。如果你想保存图片而不是弹窗显示可以这样import matplotlib matplotlib.use(Agg) # 无界面模式 fig cerebro.plot(stylecandlestick, volumeFalse) fig[0][0].savefig(backtest_result.png, dpi150, bbox_inchestight)提示Backtrader画图时如果数据量太大超过2000根K线图会非常密集看不清。建议先用最近一两年的数据跑等策略逻辑验证没问题了再用全量数据测试。5. 回测结果分析与策略调优5.1 怎么读懂回测报告回测跑完后终端会输出一堆数字。新手最容易犯的错误就是只看最终收益率其他指标一概不看。我来说说每个指标该怎么理解。总收益率是最直观的但它不能单独看。一个策略今年赚了30%听起来不错但如果同期大盘涨了50%那这个策略其实是跑输大盘的。所以看收益率的时候一定要有个参照系。最大回撤是我最看重的指标。它告诉你如果在最差的时间点买入账户最多会亏多少。比如最大回撤是25%意味着你的10万本金可能一度亏到7.5万。如果你承受不了这种浮亏那这个策略就不适合你哪怕它最终收益很高。夏普比率衡量的是性价比。一般来说夏普比率大于1算及格大于2算优秀。但要注意夏普比率是基于历史数据算的未来不一定能维持。胜率和盈亏比是一对搭档。双均线策略的胜率通常不高可能只有40%左右但它靠的是截断亏损、让利润奔跑——亏损的时候亏得少盈利的时候赚得多。所以不要看到胜率低就否定策略关键看盈亏比。指标含义参考标准总收益率回测期间的总盈亏比例需与基准对比最大回撤从最高点到最低点的最大亏损一般不超过30%夏普比率单位风险获得的超额收益大于1及格大于2优秀胜率盈利交易占总交易的比例双均线通常40%左右盈亏比平均盈利除以平均亏损大于1.5比较健康交易次数回测期间的总交易笔数太少说明信号稀疏5.2 参数优化的正确姿势很多人跑完第一遍回测后第一反应就是我要调参数让收益更高。这个想法没错但方法要对。错误的方法是拿历史数据反复测试找出收益最高的参数组合。比如测试了100组参数发现快线7、慢线23的组合收益最高就用这个。这种做法的问题在于你找到的是历史最优而不是未来最优。历史行情不会简单重复过拟合的参数在实盘中往往表现很差。正确的方法是先确定参数的大致范围再看参数变化对结果的影响趋势。比如你测试快线周期从5到20慢线周期从20到60观察收益率和最大回撤的变化。如果发现某个参数区间内策略表现都比较稳定那说明这个策略是稳健的如果只有某一组参数表现特别好其他都很差那大概率是过拟合。我一般会这样做参数敏感性分析import itertools fast_range [5, 10, 15, 20] slow_range [20, 30, 40, 60] results [] for fast, slow in itertools.product(fast_range, slow_range): if fast slow: continue cerebro bt.Cerebro() cerebro.addstrategy(DualMovingAverageStrategy, fast_periodfast, slow_periodslow) data bt.feeds.PandasData(datanamedf) cerebro.adddata(data) cerebro.broker.setcash(100000.0) cerebro.broker.setcommission(commission0.0003) cerebro.addanalyzer(bt.analyzers.DrawDown, _namedd) cerebro.addanalyzer(bt.analyzers.Returns, _nameret) strat cerebro.run()[0] final_value cerebro.broker.getvalue() ret (final_value - 100000) / 100000 * 100 dd strat.analyzers.dd.get_analysis().max.drawdown results.append({ fast: fast, slow: slow, return: round(ret, 2), max_drawdown: round(dd, 2) }) result_df pd.DataFrame(results) print(result_df.sort_values(return, ascendingFalse))跑完你会得到一张表能看到不同参数组合下的收益和回撤。我的经验是选那个收益和回撤都比较均衡的参数而不是收益最高的那个。因为收益最高的往往伴随着高回撤实盘的时候你未必扛得住。5.3 加入止损止盈的改进方案基础版的双均线策略有个明显缺陷它只在死叉时才平仓如果死叉来得太晚浮亏可能已经很大了。解决办法是加入止损止盈机制。在Backtrader里加止损很简单用self.sell的时候指定exectype和price就行def next(self): if not self.position: if self.crossover 0: # 买入时同时设置止损单 self.buy(sizeself.params.stake) # 记录买入价格 self.buy_price self.data.close[0] else: # 死叉卖出 if self.crossover 0: self.sell(sizeself.params.stake) # 止损亏损超过8%就卖出 elif self.data.close[0] self.buy_price * 0.92: self.sell(sizeself.params.stake) self.trade_log.append({ date: self.data.datetime.date(0), action: STOP_LOSS, price: self.data.close[0] })止损比例设多少合适这取决于你的风险承受能力和策略特性。一般建议设在5%到10%之间。设得太小容易被正常波动扫出去设得太大止损就失去意义了。我个人的习惯是8%你可以根据自己的情况调整。止盈也是类似的思路当盈利达到某个比例时主动平仓。但双均线策略本身是趋势跟踪策略过早止盈会截断利润所以我一般只设止损不设止盈让利润跟着趋势跑。6. 从回测到实盘新手最容易踩的坑6.1 回测漂亮实盘亏钱的真相我见过太多人拿着回测年化50%的策略冲进实盘结果三个月亏了20%。这不是策略的问题而是回测和实盘之间存在巨大的鸿沟。第一个鸿沟是滑点。回测的时候你假设自己能在收盘价精确成交。但实盘中你看到信号再下单价格可能已经变了。尤其是流动性差的股票买卖价差可能就有1%到2%。解决办法是在回测时就加入滑点设置cerebro.broker.set_slippage_perc(0.001) # 千分之一滑点第二个鸿沟是未来函数。有些策略在计算指标时不小心用到了未来的数据导致回测收益虚高。比如你用当天的最高价来判断是否触发买入但实际交易中你不可能提前知道当天的最高价。写策略时要反复检查确保每个决策只依赖于当前和过去的数据。第三个鸿沟是市场环境变化。回测用的是历史数据但未来的市场可能完全不同。双均线策略在趋势行情中表现好在震荡行情中会反复止损。如果你回测的区间恰好是一段大牛市那收益自然好看但未来未必还有这样的行情。6.2 模拟盘验证的必要性我的建议是回测跑通后至少跑三个月模拟盘再考虑实盘。模拟盘用的是实时行情但不动真金白银。它能帮你验证几件事策略在实时数据下是否正常运行、信号触发和实际成交之间有多大偏差、你能否严格执行策略信号而不手动干预。模拟盘期间每天记录策略的信号和你的操作。三个月后对比一下如果模拟盘的表现和回测差距在合理范围内比如收益率差10%以内那说明策略是靠谱的。如果差距很大就要找原因是滑点太大还是信号延迟还是你自己没管住手。6.3 资金管理与心态控制最后说一个容易被忽视但极其重要的问题资金管理。不管你多看好一个策略都不要把全部资金押上去。我的原则是单个策略的投入不超过总资金的20%。这样即使策略失效也不会伤筋动骨。剩下的资金可以分散到其他策略或者低风险资产上。心态控制同样关键。双均线策略在震荡市中会连续止损可能连亏五六次。这时候你很容易怀疑策略失效了想手动干预。但根据我的经验连续亏损往往是趋势行情来临的前兆这时候放弃可能正好错过后面的大行情。所以实盘之前一定要对策略的最大连续亏损次数有个预期做好心理准备。常见问题原因解决办法回测收益高实盘亏损滑点、未来函数、市场变化加滑点设置、检查代码、降低预期信号频繁但赚不到钱震荡行情反复止损加趋势过滤条件、放大均线周期连续亏损后不敢下单心态崩溃提前设好最大亏损额度、减小仓位策略突然失效市场风格切换多策略组合、定期评估策略有效性数据获取失败接口不稳定多数据源备份、本地缓存数据7. 后续可以怎么扩展这个策略双均线策略跑通之后你可以从几个方向继续深入。方向一加入过滤条件。比如只在均线向上倾斜时才开仓或者用ADX指标判断趋势强度只在趋势明显时才交易。这样可以过滤掉震荡市中的假信号。方向二多标的轮动。不要只盯着一只股票选一篮子股票每天买入信号最强的几只。Backtrader支持多数据源回测你可以把多个股票的数据同时喂进去。方向三结合基本面因子。纯技术面策略有局限性可以加入市盈率、ROE等基本面指标作为辅助判断。比如只在低估值区间才执行买入信号。方向四尝试其他框架。Backtrader跑通后你可以试试vnpy或者自己用pandas写一个简易回测引擎。自己写一遍对回测的理解会更深。我个人的体会是量化交易的学习曲线是前陡后缓。刚开始要学的东西很多环境配置、数据获取、策略编写、回测分析每一步都有坑。但把双均线策略完整跑通一遍之后后面再学其他策略就会快很多因为底层的东西都是相通的。最后分享一个小技巧把你写的每个策略都保存下来记录下参数、回测区间、绩效指标。过几个月回头看你会发现自己进步了多少也能看出哪些策略是真的有效哪些只是运气好。这个习惯我坚持了三年受益匪浅。