《151 trading strategies》中的 3.13 Strategy: Three Moving Averages
这已经是均线策略在全 A 股中的应用的第三篇文章了,前两篇分别是:【股票策略】《151 trading strategies》中的 simple moving average 和 【股票策略】《151 trading strategies》中的 two moving averages-在 A 股全部股票上测试双均线策略,和单均线双均线策略一样,这个三均线策略依然是择时类的策略,但是策略逻辑相对于单均线和双均线来看,有一定的优化。
- 使用全部的 A 股日数据的后复权价格
- 资金管理方法和前两篇的管理方法一样
- 对于一只股票,当 10 日均线大于 60 日均线,并且 60 日均线大于 250 日均线的时候,并且前一个交易日并不满足这个信号,进场做多;当 10 日均线小于 60 日均线的时候,并且前一个交易日 10 日均线大于 60 日均线,并且持有多头,平仓
- 假设初始资金为 1 个亿
- 假设交易手续费为万分之二
import backtrader as bt
import datetime
import pandas as pd
import numpy as np
import os,sys
import copy
import talib
import math
import warnings
warnings.filterwarnings("ignore")
import pyfolio as pf
# 我们使用的时候,直接用我们新的类读取数据就可以了。
class test_two_ma_strategy(bt.Strategy):
params = (('short_period',10),
("middle_period",60),
("long_period",250),
)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('{}, {}'.format(dt.isoformat(), txt))
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.bar_num=0
# 保存均线数据
self.stock_short_ma_dict={data._name:bt.talib.SMA(data.close,timeperiod=self.p.short_period) for data in self.datas}
self.stock_middle_ma_dict={data._name:bt.talib.SMA(data.close,timeperiod=self.p.middle_period) for data in self.datas}
self.stock_long_ma_dict={data._name:bt.talib.SMA(data.close,timeperiod=self.p.long_period) for data in self.datas}
# 保存现有持仓的股票
self.position_dict={}
# 当前有交易的股票
self.stock_dict={}
def prenext(self):
self.next()
def next(self):
# 假设有 100 万资金,每次成份股调整,每个股票使用 1 万元
self.bar_num+=1
# 前一交易日和当前的交易日
pre_date = self.datas[0].datetime.date(-1).strftime("%Y-%m-%d")
current_date = self.datas[0].datetime.date(0).strftime("%Y-%m-%d")
# 总的价值
total_value = self.broker.get_value()
total_cash = self.broker.get_cash()
# 第一个数据是指数,校正时间使用,不能用于交易
# 循环所有的股票,计算股票的数目
for data in self.datas[1:]:
data_date = data.datetime.date(0).strftime("%Y-%m-%d")
# 如果两个日期相等,说明股票在交易
if current_date == data_date:
stock_name = data._name
if stock_name not in self.stock_dict:
self.stock_dict[stock_name]=1
total_target_stock_num = len(self.stock_dict)
# 现在持仓的股票数目
total_holding_stock_num = len(self.position_dict)
# 计算理论上的手数
if total_holding_stock_num<total_target_stock_num:
now_value = total_cash/(total_target_stock_num-total_holding_stock_num)
stock_value = total_value/total_target_stock_num
now_value =min(now_value,stock_value)
else:
now_value = total_value/total_target_stock_num
# 循环股票,开始交易
for data in self.datas[1:]:
data_date = data.datetime.date(0).strftime("%Y-%m-%d")
# 如果两个日期相等,说明股票在交易
if current_date == data_date:
# 三条均线,保存成 line 的数据结构
short_ma_info = self.stock_short_ma_dict[data._name]
middle_ma_info = self.stock_middle_ma_dict[data._name]
long_ma_info = self.stock_long_ma_dict[data._name]
# 今日向上的条件
up = short_ma_info[0]>middle_ma_info[0] and middle_ma_info[0]>long_ma_info[0]
# 今日向下的条件
down = short_ma_info[0]<=middle_ma_info[0]
# 昨日向上的条件
pre_up = short_ma_info[-1]>middle_ma_info[-1] and middle_ma_info[-1]>long_ma_info[-1]
# 昨日向下的条件
pre_down = short_ma_info[-1]<=middle_ma_info[-1]
# 开多信号
con_buy = up and not pre_up
con_sell = down and not pre_down
# 平多信号
if con_sell:
# 已经下单并且成交了
if self.getposition(data).size>0:
self.close(data)
if data._name in self.position_dict:
self.position_dict.pop(data._name)
# self.buy_list.remove(stock)
# 已经下单,但是订单没有成交
if data._name in self.position_dict and self.getposition(data).size==0:
order = self.position_dict[data._name]
self.cancel(order)
self.position_dict.pop(data._name)
# 开多信号,价格站到均线上方,并且持仓量为 0
if con_buy and self.getposition(data).size==0 :
lots = now_value/data.close[0]
lots = int(lots/100)*100 # 计算能下的手数,取整数
order = self.buy(data,size = lots)
self.position_dict[data._name] = order
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status == order.Rejected:
self.log(f"Rejected : order_ref:{order.ref} data_name:{order.p.data._name}")
if order.status == order.Margin:
self.log(f"Margin : order_ref:{order.ref} data_name:{order.p.data._name}")
if order.status == order.Cancelled:
self.log(f"Concelled : order_ref:{order.ref} data_name:{order.p.data._name}")
if order.status == order.Partial:
self.log(f"Partial : order_ref:{order.ref} data_name:{order.p.data._name}")
if order.status == order.Completed:
if order.isbuy():
self.log(f" BUY : data_name:{order.p.data._name} price : {order.executed.price} , cost : {order.executed.value} , commission : {order.executed.comm}")
else: # Sell
self.log(f" SELL : data_name:{order.p.data._name} price : {order.executed.price} , cost : {order.executed.value} , commission : {order.executed.comm}")
def notify_trade(self, trade):
# 一个 trade 结束的时候输出信息
if trade.isclosed:
self.log('closed symbol is : {} , total_profit : {} , net_profit : {}' .format(
trade.getdataname(),trade.pnl, trade.pnlcomm))
# self.trade_list.append([self.datas[0].datetime.date(0),trade.getdataname(),trade.pnl,trade.pnlcomm])
if trade.isopen:
self.log('open symbol is : {} , price : {} ' .format(
trade.getdataname(),trade.price))
def stop(self):
pass
# 初始化 cerebro,获得一个实例
cerebro = bt.Cerebro()
# cerebro.broker = bt.brokers.BackBroker(shortcash=True) # 0.5%
data_root = "/home/yun/data/stock/day/"
file_list =sorted(os.listdir(data_root))
params=dict(
fromdate = datetime.datetime(2005,1,4),
todate = datetime.datetime(2020,7,31),
timeframe = bt.TimeFrame.Days,
dtformat = ("%Y-%m-%d"),
compression = 1,
datetime = 0,
open = 1,
high = 2,
low =3,
close =4,
volume =5,
openinterest=-1)
# 加载指数数据
feed = bt.feeds.GenericCSVData(dataname = "/home/yun/data/stock/index.csv",**params)
# 添加数据到 cerebro
cerebro.adddata(feed, name = 'index')
# 读取数据
for file in file_list:
#剔除不满一年的股票
if len(pd.read_csv(data_root+file))<252:
continue
feed = bt.feeds.GenericCSVData(dataname = data_root+file,**params)
# 添加数据到 cerebro
cerebro.adddata(feed, name = file[:-4])
print("加载数据完毕")
# 添加手续费,按照万分之二收取
cerebro.broker.setcommission(commission=0.0002,stocklike=True)
# 设置初始资金为 1 亿
cerebro.broker.setcash(1_0000_0000)
# 添加策略
cerebro.addstrategy(test_two_ma_strategy)
cerebro.addanalyzer(bt.analyzers.TotalValue, _name='_TotalValue')
cerebro.addanalyzer(bt.analyzers.PyFolio)
# 运行回测
results = cerebro.run()
# 打印相关信息
pyfoliozer = results[0].analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
pf.create_full_tear_sheet(
returns,
positions=positions,
transactions=transactions,
# gross_lev=gross_lev,
live_start_date='2019-01-01',
) 择时做起来相对比较难,想要用一些简单的指标来获取长期的超额收益率更难。昨天晚上对上证指数日收益率进行了测试,发现,绝大部分的利润来自于前 0.25%的交易日。
A 股策略,最好能够把选股和择时结合到一块做策略。



