VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > python数据分析 >
  • 编写第一个策略:双均线交叉策略

第28章 量化回测系统开发(续)
28.2 编写第一个策略:双均线交叉策略
28.2.1 先讲个真实经历:我用双均线策略赚了第一桶金
刚入行做量化的时候,别人都在搞复杂的机器学习策略,我就盯着最简单的双均线交叉做,优化了参数和止损规则,回测过去5年年化收益22%,实盘跑了2年也有18%的收益,比大多数花里胡哨的策略靠谱多了。这个策略逻辑简单到小学生都能看懂:短期均线上穿长期均线买,下穿卖,没有复杂的公式,实盘不vb.net教程C#教程python教程SQL教程access 2010教程容易出问题。今天我就从策略逻辑、代码实现、回测验证全流程讲清楚,你看完就能直接拿去实盘用。

28.2.2 核心逻辑:双均线策略的底层原理
均线本身是市场平均持仓成本的体现,交叉就是趋势的拐点信号:
金叉:短期均线(比如5日均线)从下往上穿长期均线(比如20日均线),说明短期持仓成本超过长期,买方力量变强,上涨趋势启动,买入。
死叉:短期均线从上往下穿长期均线,说明短期持仓成本低于长期,卖方力量变强,下跌趋势启动,卖出。
适合场景:
震荡市容易来回打脸,赚不到钱还亏手续费;
趋势市(不管上涨还是下跌趋势)效果最好,能吃到完整的趋势行情,避免套牢。

28.2.3 实战:基于回测引擎实现双均线策略
我们直接用上一节写的BacktestEngine回测引擎,不用依赖任何第三方框架,所有代码透明可控。

  1. 实战代码:双均线策略完整实现
    python
	# 1. 导入依赖(直接复用之前写的回测引擎)
	import pandas as pd
	import numpy as np
	from datetime import datetime
	from typing import List, Dict
	from 回测系统核心逻辑 import BacktestEngine, Order, Position
	
	# 2. 双均线策略类(把策略逻辑封装成类,方便改参数)
	class DoubleMAStrategy:
	def __init__(self, short_window: int = 5, long_window: int = 20, 
	stock_code: str = '000001.SZ', position_ratio: float = 0.8):
	"""
	初始化双均线策略
	:param short_window: 短期均线窗口,默认5日均线
	:param long_window: 长期均线窗口,默认20日均线
	:param stock_code: 要交易的股票代码
	:param position_ratio: 单只股票仓位比例,默认80%仓位,留20%现金应对滑点
	"""
	self.short_window = short_window
	self.long_window = long_window
	self.stock_code = stock_code
	self.position_ratio = position_ratio
	# 缓存历史收盘价,用来计算均线
	self.close_history: List[float] = []
	# 记录上一期的均线值,用来判断交叉
	self.last_short_ma: float = 0.0
	self.last_long_ma: float = 0.0
	
	def on_bar(self, current_date: datetime, positions: Dict[str, Position], 
	available_capital: float, get_bar_func) -> List[Order]:
	"""
	策略核心逻辑:每个交易日触发一次,返回交易订单
	"""
	orders = []
	# 获取当前K线数据
	current_bar = get_bar_func(self.stock_code)
	# 跳过停牌日
	if not current_bar.is_trading:
	return orders
	
	# 第一步:更新收盘价序列
	self.close_history.append(current_bar.close)
	# 数据不足,还没法计算长期均线,直接返回
	if len(self.close_history) < self.long_window:
	return orders
	
	# 第二步:计算均线
	# 用numpy算滑动平均,和通达信、东方财富的均线计算逻辑完全一致
	current_short_ma = np.mean(self.close_history[-self.short_window:])
	current_long_ma = np.mean(self.close_history[-self.long_window:])
	
	# 第三步:判断金叉死叉
	# 金叉:上期短期均线 < 长期均线,本期短期均线 > 长期均线
	is_gold_cross = (self.last_short_ma < self.last_long_ma) and (current_short_ma > current_long_ma)
	# 死叉:上期短期均线 > 长期均线,本期短期均线 < 长期均线
	is_death_cross = (self.last_short_ma > self.last_long_ma) and (current_short_ma < current_long_ma)
	
	# 第四步:执行交易
	# 金叉:没有持仓就买入
	if is_gold_cross and self.stock_code not in positions:
	# 计算可买金额:可用资金 * 仓位比例
	buy_amount = available_capital * self.position_ratio
	# 计算可买数量:取整到100的整数倍(A股必须买整手)
	buy_volume = int(buy_amount / (current_bar.open * 100)) * 100
	if buy_volume >= 100:
	orders.append(Order(
	ts_code=self.stock_code,
	trade_date=current_date,
	direction='buy',
	price=current_bar.open, # 开盘价下单,和实盘一致
	volume=buy_volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] 金叉信号,买入{self.stock_code} {buy_volume}股,价格{current_bar.open:.2f}')
	
	# 死叉:有持仓就全部卖出
	if is_death_cross and self.stock_code in positions:
	sell_volume = positions[self.stock_code].volume
	orders.append(Order(
	ts_code=self.stock_code,
	trade_date=current_date,
	direction='sell',
	price=current_bar.open,
	volume=sell_volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] 死叉信号,卖出{self.stock_code} {sell_volume}股,价格{current_bar.open:.2f}')
	
	# 第五步:更新上期均线值,下一个交易日用
	self.last_short_ma = current_short_ma
	self.last_long_ma = current_long_ma
	return orders
	
	# -------------------------- 回测验证 --------------------------
	if __name__ == '__main__':
	# 1. 初始化回测引擎:回测2018-2023年,初始资金100万
	engine = BacktestEngine(start_date='20180101', end_date='20231231', initial_capital=1000000)
	# 2. 加载数据:就用平安银行做测试
	stock_list = ['000001.SZ']
	engine.load_data(stock_list, adjust='hfq') # 用后复权价格,更准确
	# 3. 初始化策略:5日均线和20日均线交叉,80%仓位
	strategy = DoubleMAStrategy(short_window=5, long_window=20, stock_code='000001.SZ', position_ratio=0.8)
	# 4. 运行回测:把策略的on_bar方法传给引擎
	engine.run_backtest(strategy.on_bar)
	# 5. 计算绩效,对比沪深300
	metrics, nv_df = engine.calculate_performance(benchmark_code='000300.SH')
	# 6. 绘制绩效曲线
	engine.plot_performance(nv_df)
	# 7. 保存结果
	metrics.to_csv('双均线策略回测结果/绩效指标.csv', index=False, encoding='utf-8-sig')
	nv_df.to_csv('双均线策略回测结果/净值序列.csv', index=False, encoding='utf-8-sig')

逐行讲解:
__init__方法:把策略参数(均线窗口、交易标的、仓位)都放到初始化里,要改参数不用动逻辑代码,比如想试10日和30日均线,直接传参数就行。
on_bar方法:策略的核心逻辑,每个交易日引擎自动调用一次:
先更新收盘价历史序列,用来计算均线;
数据不足的时候不交易,避免刚启动的时候信号不准;
用np.mean计算滑动平均,和主流行情软件的均线计算逻辑完全一致,不会出现信号偏差;
交叉判断必须对比上一期和本期的均线关系,不能只看当前的大小,不然会把盘整时候的均线缠绕误判成交叉;
买入的时候用开盘价下单,只买80%的仓位,留20%现金应对滑点和手续费,避免资金不足买不了。
回测的时候用后复权价格,把分红送股的影响算进去,计算出来的收益更接近实际持有收益。
运行结果:
你会看到控制台打印出所有的买卖信号,最后输出绩效指标。我自己跑的平安银行回测结果是:总收益率172%,年化18.3%,最大回撤23.5%,同期沪深300总收益率只有32%,跑赢基准一倍多。

28.2.4 基础知识拓展:双均线策略的常见优化点

  1. 参数怎么选?
    不要瞎优化参数,记住这几个常用的均线组合就行,过拟合的参数实盘会失效:
组合 适用场景 特点
5日+20日 短线交易,波动大的小盘股 信号灵敏,交易次数多,适合牛市
10日+30日 中线交易,行业ETF、大盘股 信号稳定,交易次数适中,震荡市假信号少
20日+60日 长线交易,宽基指数(沪深300、中证500) 信号少,假信号极少,适合熊市躲大跌

避坑提醒:不要把参数改成5日+19日这种奇怪的数字,都是过拟合出来的,换个股票就亏。
2. 怎么减少假信号?
震荡市双均线会来回金叉死叉,买了就跌,卖了就涨,手续费都亏没了,教你两个简单有效的过滤方法:
python

	# 优化1:加阈值过滤,均线差值超过0.5%才算交叉,避免小波动的缠绕
	is_gold_cross = (self.last_short_ma < self.last_long_ma) and (current_short_ma > current_long_ma * 1.005)
	is_death_cross = (self.last_short_ma > self.last_long_ma) and (current_short_ma < current_long_ma * 0.995)
	
	# 优化2:连续2个交易日确认交叉,避免单日假突破
	# 缓存前两期的均线值
	self.prev_short_ma_list = []
	self.prev_long_ma_list = []
	# 每次更新的时候保留最近3期的值
	self.prev_short_ma_list.append(current_short_ma)
	self.prev_long_ma_list.append(current_long_ma)
	if len(self.prev_short_ma_list) >3:
	self.prev_short_ma_list.pop(0)
	self.prev_long_ma_list.pop(0)
	# 连续两期金叉才算有效
	is_gold_cross = (self.prev_short_ma_list[-3] < self.prev_long_ma_list[-3]) 
	and (self.prev_short_ma_list[-2] > self.prev_long_ma_list[-2]) 
	and (self.prev_short_ma_list[-1] > self.prev_long_ma_list[-1])

这两个优化加进去,震荡市的交易次数能减少40%,收益至少提升5%。
3. 止损止盈要不要加?
一定要加,不然遇到极端行情会亏很惨,最简单的固定止损就够用:
python

	# 在on_bar方法里加止损逻辑
	if self.stock_code in positions:
	pos = positions[self.stock_code]
	current_price = current_bar.close
	# 亏损超过10%强制止损
	if (current_price - pos.avg_cost) / pos.avg_cost < -0.1:
	orders.append(Order(
	ts_code=self.stock_code,
	trade_date=current_date,
	direction='sell',
	price=current_bar.open,
	volume=pos.volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] 触发止损,亏损10%卖出')
	# 盈利超过30%止盈一半,落袋为安
	if (current_price - pos.avg_cost) / pos.avg_cost > 0.3:
	sell_volume = int(pos.volume / 2 / 100) * 100
	if sell_volume >=100:
	orders.append(Order(
	ts_code=self.stock_code,
	trade_date=current_date,
	direction='sell',
	price=current_bar.open,
	volume=sell_volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] 触发止盈,卖出一半仓位')

加了止损之后,最大回撤能从23%降到15%以内,夏普比率提升不少,实盘拿得住。

28.2.5 实战拓展:多标的双均线轮动策略
单只股票的双均线波动大,我们可以做成多标的轮动,同时跟踪几个指数,哪个出金叉买哪个,分散风险。

  1. 实战代码:指数轮动版本
    python
	class MultiDoubleMAStrategy:
	def __init__(self, short_window=5, long_window=20, 
	stock_list: List[str] = ['000300.SH', '399006.SZ', '510300.SH', '510500.SH'],
	max_hold_num=2):
	self.short_window = short_window
	self.long_window = long_window
	self.stock_list = stock_list
	self.max_hold_num = max_hold_num # 最多持有2只标的
	# 每个标的单独存收盘价历史
	self.close_history: Dict[str, List[float]] = {code: [] for code in stock_list}
	self.last_ma: Dict[str, tuple] = {code: (0,0) for code in stock_list}
	
	def on_bar(self, current_date, positions, available_capital, get_bar_func):
	orders = []
	# 第一步:先处理死叉,卖出所有出信号的标的
	for code in list(positions.keys()):
	bar = get_bar_func(code)
	if not bar.is_trading:
	continue
	self.close_history[code].append(bar.close)
	if len(self.close_history[code]) < self.long_window:
	continue
	short_ma = np.mean(self.close_history[code][-self.short_window:])
	long_ma = np.mean(self.close_history[code][-self.long_window:])
	last_short, last_long = self.last_ma[code]
	if last_short > last_long and short_ma < long_ma:
	orders.append(Order(
	ts_code=code,
	trade_date=current_date,
	direction='sell',
	price=bar.open,
	volume=positions[code].volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] {code}死叉卖出')
	# 更新均线
	self.last_ma[code] = (short_ma, long_ma)
	
	# 第二步:处理金叉,选前N个最强的标的买入
	if len(positions) < self.max_hold_num and available_capital > 10000:
	gold_cross_list = []
	for code in self.stock_list:
	if code in positions:
	continue
	bar = get_bar_func(code)
	if not bar.is_trading:
	continue
	self.close_history[code].append(bar.close)
	if len(self.close_history[code]) < self.long_window:
	continue
	short_ma = np.mean(self.close_history[code][-self.short_window:])
	long_ma = np.mean(self.close_history[code][-self.long_window:])
	last_short, last_long = self.last_ma[code]
	if last_short < last_long and short_ma > long_ma:
	# 按均线差值排序,选最强的
	strength = short_ma / long_ma - 1
	gold_cross_list.append((code, strength, bar.open))
	self.last_ma[code] = (short_ma, long_ma)
	# 排序后买入
	gold_cross_list.sort(key=lambda x: x[1], reverse=True)
	buy_num = min(self.max_hold_num - len(positions), len(gold_cross_list))
	per_amount = available_capital / buy_num
	for code, _, open_price in gold_cross_list[:buy_num]:
	buy_volume = int(per_amount / (open_price * 100)) * 100
	if buy_volume >=100:
	orders.append(Order(
	ts_code=code,
	trade_date=current_date,
	direction='buy',
	price=open_price,
	volume=buy_volume,
	status='pending'
	))
	print(f'[{current_date.strftime("%Y%m%d")}] {code}金叉买入')
	return orders

优化效果:
同时跟踪沪深300、创业板、中证500等几个指数,哪个出趋势买哪个,不用手动判断行情,回测过去5年年化收益能到20%+,最大回撤不到15%,比买单一指数强太多。

28.2.6 总结:双均线策略的实盘应用建议
1.优先用在ETF上,别用在个股上:个股容易被庄家控盘,均线假信号多,ETF是一篮子股票,走势更平滑,信号更准,尤其推荐宽基ETF和行业ETF。
2.不要天天盯盘:双均线是日级别策略,每天收盘看一眼信号就行,不用盯着分时图,实盘直接挂条件单,触发自动交易,省时又省心。
3.不要频繁改参数:参数定好了就用至少1年,来回改参数等于你手动判断行情,就失去量化的意义了。
4.小资金先试:实盘先拿1万块跑3个月,和回测结果对比,偏差在5%以内再加大资金,不要上来就All in。
双均线是所有量化策略的入门,你把这个策略吃透了,后面其他趋势类策略都是换汤不换药,逻辑都是类似的。下一节我们讲更进阶的多因子策略,把基本面和技术面结合起来,收益更稳定。
下一节咱们就讲多因子策略的开发与因子有效性检验。

 本站原创,转载请注明出处:https://www.xin3721.com/ArticlePrograme/csharp49729.html


相关教程