python-股票量化-双均线策略(如何读取数据?,股票涨跌幅/涨跌停/复权计算,绘制股票资金曲线k线如何标记双均线策略信号,持仓分组标记及涨跌幅分析,绩效指标计算及调参方法,用循环调整周期参数)

炒股的人通常都喜欢各种技术指标,来指定买卖策略,然而我们却经常看到股民们亏钱,难道是这些指标有问题吗?还是专家在忽悠股民朋友们?将以非常简单的均线指标为例,来挖掘其在一只股票上的绩效潜力。

实现流程的简单梳理

1.读取数据,了解数据的结构
2.对读取的数据进行清洗,使数据符合进一步分析
3.计算相应的股票日涨跌幅度
4.通过画图来展示:
a.基于回测资金曲线
b.股票k线图
c.参数优化的热力图

读取数据

注意事项:

pd.read_excel()有两个设置参数,分别是:
1.parse_dates:是指将指定列转化为时间类型格式,注意是列表形式指定列[“交易日期”]
2.index_cel:是指将指定列变为行索引index

# 导包
import pandas as pd
df = pd.read_excel("sz002115",parse_dates=["加油日期"],index_cel="交易日期")

在这里插入图片描述

为什么要设置"交易日期"列为datetime数据类型?

主要是后面的作图,直接传入时间数据为y轴,用str数据类型,有可能不会被识别.

最后我们查看一下数据的基本构造,看数据是否有空值,是否需要填充

df.info()

在这里插入图片描述

股票涨跌幅/涨跌停/复权计算

概念解析

1.股票涨跌幅:股票每天相对的昨日收盘价的涨跌幅
2.股票涨跌停价格:股票每一天的涨停价,跌停价
3.复权:把复权简单理解为就是排除送股,配股,分红等影响因素。

计算股票涨跌幅

df["涨跌幅"] = df["收盘价"]/df["前收盘价"]-1

注意事项

使用前收盘价,而非用开盘价,是为了避免因为送股,配股,分红等影响因素的股价不合理变化.

计算股票涨跌停

df["涨停价"] = df["前收盘价"]*1.1
df["跌停价"] = df["前收盘价"]*0.8

计算股票后复权价格

概念解析

前复权处理:股票最近一天价格不变,之前数据按照真实日涨跌幅做调整;

后复权处理:股票上市第一天价格不变,之后数据按照真实日涨跌幅做调整;(后复权类似于基金净值的统计)

计算股票资金曲线

所谓资金曲线,即把股票上市第一天的净值设为1,之后每天净值随真实日涨跌幅波动的曲线
1+涨跌幅 我们的到的是涨跌比例,我们把每一天的涨跌比例累程起来.我们的到一个1+涨跌幅的累乘数据,在做归一化
在这里插入图片描述
这个是比较难理解的,看图你应该就明白了!!!

df["资金曲线"] = (1+df["涨跌幅"].cumprod()  
df.loc[:,"资金曲线"] = df["资金曲线"]/df["资金曲线"].iat[o]  #进行数据归一化

iat只能访问单个元素

df[“xxx”].iat[x]
当前"xxx"这一列的第x值

loc与iloc

loc是标签名,不带i的是名字
iloc是位置号,带i的就是index位置
取数据都遵循:左行右列loc[左行,有列] iloc[左行,右列]

计算后复权价格

格式:
收盘后复权 = 第一天的当日收盘价(固定值) * 资金曲线的值 (一直在变动反应股票涨跌变化情况的值)
其他后复权,求出对于收盘价的涨跌幅后乘上收盘价后复权
涨跌幅 * 收盘价后复权

df["收盘价_后复权"] = df["资金曲线"] * df["收盘价"].iat[0] # 股票第一天的价格不变作为基准价

df["开盘价_后复权"] = df["开盘价"]/df["收盘价"]*df["收盘价_后复权"]
df["最高价_后复权"] = df["最高价"]/df["收盘价"]*df["收盘_后复权"]
df["最低价_后复权"] = df["最低价"]/df["收盘价"]*df["收盘_后复权"]

# 生成新的4个字段
df[["开盘价_后复权","最高价_后复权","最低价_后复权","收盘价_后复权"]]

在这里插入图片描述

绘制股票历史价格变动图、k线图

绘制股票历史价格变动图

df["资金曲线"].plot(figsize=(20,8),
					grid=True,
					alpha=0.8,
					rot=45,
					title="股票历史价格变动图")

在这里插入图片描述

绘制不复权k线图

# 导入pyecharts工具包,用pe作为简写
import pyecharts as pe

# 提取x轴数据,即交易日期,同时转换为字符串形式
x = df.index.astype('str')

# 提取y轴数据,即绘制股票k线所必须的4个价格字段
y = df[['开盘价','收盘价','最低价','最高价']].values

# 创建k线图对象,并设置大标题
kline = pe.Kline('不复权K线图_三维通信')

# 绘制图表
kline.add('日K', x, y,                          #图例名称,x轴数据,y轴数据
          is_datazoom_show=True,                #使用区域缩放组件
          datazoom_range=[97,100],              #设置区域缩放的范围,这里设置为末尾3%的k线数据
          mark_point=['max','min'],             #标记可视范围内开盘价最大值,最小值
          is_xaxislabel_align = True,           #设置x轴刻度与标签对齐
          tooltip_trigger ='axis',              #坐标轴触发弹窗提示
          tooltip_axispointer_type ='shadow')   #设置tooltip指示器

在这里插入图片描述

标记双均线策略信号

计算均线

df["MA50"] = df["收盘价_后复权"].rolling(50).mean()
df["MA200"] = df["收盘价_后复权"].rolling(200).mean()

注意事项

pandasSeries.rolling(n)是表示对一列的n个数据做滚动处理,rolling(3).mean()表示
对滚动的数据做均值计算,并且记录在最后一行
在这里插入图片描述

双均线策略信号标记

在这里插入图片描述
在这里插入图片描述

总结一句话就是:

对于短周期均线前天小后天大是上传买入信号,前天大后天小是下穿卖出信号

## 1位买入信号
df.loc[(df["MA50"]<df["MA200")&
		(df["MA50"].shift(1)>df["MA200"].shift(1)),
		"交易信号"]=1
# 0卖出信号
df.loc[(df["MA50"]>df["MA200"])&
		(df["MA50"].shift(1)<df["MA200"].shift(1)),
		"交易信号"]=0
# 去除掉空值
df["交易信号"].dropna()

注意事项:

df.loc[(条件1)&(条件2), ‘交易信号’] = 1找到了同时满足2个条件的所有行,并且索引到交易信号这一列,当这一列最开始不存在的时候,pandas会帮我们自动新建一列空值的交易信号,然后对这一列满足2个条件的所有行赋值为1,这就完成了买入信号的标记,卖出信号标记则同理

涨跌停信号屏蔽

当股票涨停是无法买入的,跌停也是无法卖出的,如果我们策略中有这样的情况,则需要把交易信号屏幕掉(即设置为空值)

df.loc[(df["收盘价"]>=df["涨停价"])&
		(df["交易信号"]==1),
		"交易信号"]=None
df.loc[(df["收盘价"]<=df["跌停价"])&
		(df["交易信号"]==0),
		"交易信号"]=None
df["交易信号"].dropna()

持仓信号标记

我们需要根据 交易信号 来标记出,在股票历史中,我们买入持仓的期间,以及卖出空仓状态的期间。这是为了之后能回测双均线策略的优劣所必须的步骤

df["持仓信息"] = df["持仓状态"].fillna(method="ffill").shift().fillna(0)

df[‘交易信号’].fillna(method=‘ffill’) 函数会填充 “交易信号” 字段的空值,这里用到的参数 method=‘ffill’ 代表向前填充。假设交易信号只有4行 [NaN , 1 , NaN , NaN],每一行空值都会按之前最近的一个非空值来填充自己,ffill填充结果为 [NaN , 1 , 1 , 1],首行找不到之前的非空数据就不会被填充
pandas里的方法是可以连续调用的,在填充空值后,连续调用 .shift() 移位,沿用上面例子的假设,[NaN , 1 , 1 , 1] 将变为 [NaN , NaN , 1 , 1, 1],最后调用 .fillna(0) 将剩余空值填充为 0 [0 , 0 , 1 , 1]
在这里插入图片描述
这样子做的目的是为了实现,当日建仓因为是T+1的,当日是不可以买卖的持仓数量应为0,次日才应该有持仓
在这里插入图片描述

构建函数分析

持仓的计算,都是基于50日均线,200日均线来标记生成的。如果我想测试其他的均线组合呢?我要一行行去改代码中出现MA50,MA200的位置嘛?–这里我们总结上述信息封装一个函数

def Ma_mark(dfr,n,m):
""
dfr股票原始数据
n短周期
m长周期
""
assert n<m,"短周期数值不能大于长周期数组"
assert m<dfr.shape[0],"长周期数值不能大数提供的股票数据的长度"
# 制作数据备份
df = dfr.copy()
# 计算双均线
df["{}日均线".format(n)] = df["收盘价_后复权"].rolling(n).mean()
df["{}日均线".format(m)] = df["收盘价_后复权"].rolling(n).mean()

# 计算交易信号
#买入
df.loc[df["{}日均线".format(n)]<df["{}日均线".format(m)]&
		df["{}日均线".format(n)].shift()>df["{}日均线".format(m)].shift(),
		"交易信号"]=1
# 卖出
df.loc[df["{}日均线".format(n)]>df["{}日均线".format(m)]&
		df["{}日均线".format(n)].shift()<df["{}日均线".format(m)].shift(),
		"交易信号"]=0
# 涨停无法买入标记
df.loc[df["收盘价"]>=df["涨停价"]&
		df["交易信号"]==1,
		"交易信号"]=None
# 跌停无法卖出标记
df.loc[df["收盘价"<=df["跌停价"]]&
		df["交易信号"]==0,
		"交易信号"]=None
# 计算持仓状态信号
# ffill向上填充
df["持仓状态"] = df["交易信号"].fillna(method="ffill").shift().fillna(0)

# 调用函数
df_ma= Ma_mark(df,50,20)

# 画图展示
df_ma[["收盘价_后复权","50日均线","200日均线"]]

在这里插入图片描述

注意事项

1.DataFrame.shape可以拿到数据的(行数,列数),索引取0即取数据的行数
2.assert语句包含两部分,assert 条件,不满足条件时报错并弹出对应提示 ,用来限制大家对函数参数的传入

持仓分组标记及涨跌幅分析

我们持仓状态要么是1,要么是0,类似[0 0 0 1 1 1 1 1 1 0 0 …]这样循环往复,我们希望对这些持仓状态都标记为不同的组,比如[0 0 1 1 0 0] 我们希望标记为 [组1 组1 组2 组2 组3 组3]

我么怎么实现这种持仓分组标记?

1.直接赋值index(交易日期)为持仓分组
2.我们看当日持仓状态是否等于前一日持仓状态.如果等于我们就把相等的数据对应的持仓分组行赋值为空值(None)
3.我们对持仓分组进行向上填充,就可以达到 [组1 组1 组2 组2 组3 组3]
method=‘ffill’ 代表向前填充。假设交易信号只有4行 [NaN , 1 , NaN , NaN],每一行空值都会按之前最近的一个非空值来填充自己,ffill填充结果为 [NaN , 1 , 1 , 1]
在这里插入图片描述

策略涨跌幅及手续费计算

策略持仓股票时,策略的涨跌和股票同步,策略空仓时,策略不涨不跌

  1. 开仓买入手续费计算
    在持仓状态为1的每一组,首行即开仓买入,持仓状态刚刚从0到1,此时策略就要开始产生涨跌幅了。假设我们开仓支付了0.1%的手续费,那么实际持仓的价值就只有99.9%,也就是只有99.9%的仓位发生了涨跌, 首行策略实际涨跌幅 = (1 + 股票涨跌幅)[1是股票本身(基准)+涨幅]*(1 - 开仓手续费)[股票买入成本是100%-手续费0.1%]-1
  2. 平仓卖出手续费计算
    在持仓状态为1的每一组,最后一行收盘时平仓卖出,再下一行持仓状态就要从1变为0了,此时策略涨跌幅就要被屏蔽掉了。在此之前,假设平仓也是0.1%手续费,那么最终变现的价值也就只有99.9%,公式同样是 最后一行策略实际涨跌幅 = (1 + 股票涨跌幅)*(1 - 平仓手续费)- 1
# 如果持仓状态为0,乘以涨跌幅,就是0.而如果为1,乘以涨跌幅,就涨跌幅本身
df["涨跌幅_策略"] = df_ma["涨跌幅"] * df_ma["持仓状态"]
# 找到第一次开仓点(持仓状态为1,上一行是0),扣除手续费.上一行=本行的shift()平移
df_ma.loc[df["涨跌幅_策略"]!=0 &
		df["涨跌幅_策略"]!=df["涨跌幅_策略"].shift()
		,"涨跌幅_策略"]=(1+"涨跌幅_策略")*(1-0.0008)-1
# 找出开仓组的最后一行(最后一行持仓状态为1,下一行是0),扣除手续费
df_ma.loc[df["涨跌幅_策略"]!=0 &
		df["涨跌幅_策略"]!=df["涨跌幅_策略"].shift(-1)
		,"涨跌幅_策略"]=(1+"涨跌幅_策略")*(1-0.0008)-1

注意事项:

当出现df[“涨跌幅_策略”]!=df[“涨跌幅_策略”].shift(-1)
df[“涨跌幅_策略”]!=df[“涨跌幅_策略”].shift()说明到了组与组的分界点了
在这里插入图片描述
在这里插入图片描述

绩效指标计算及调参方法

策略资金曲线,绩效指标计算

# 计算并更新策略资金曲线
df_ma.loc[:,"资金曲线_策略"] = (1+ df_ma["涨跌幅_策略"]).cumprod()
# 归一化
df_ma.loc[:,"资金曲线_策略"] = df_ma["资金曲线_策略"]/df_ma["资金曲线_策略"].iat[0]

 
# 计算最大回撤(策略在每个交易日,相对之前最高点跌幅是多大)
df_ma["最大回撤"]= df_ma["资金曲线_策略"].cummax()
df_ma.loc[:,"最大回撤"]= 1-df_ma["资金曲线_策略"]/df_ma["最大回撤"]


# 新建一个字典来存储各绩效指标
# 股票资金曲线终值:即股票资金曲线在最近一个交易日的数值,展示了开盘以来累计涨跌的收益情况
# 策略资金曲线终值:即策略资金曲线在最近一个交易日的数值,展示了开盘以来累计涨跌的收益情况
# 相对收益:最近一个交易日,策略资金曲线与股票资金曲线的比值
# 总交易次数:所有的买入卖出次数总计
# 平均持仓时间:持仓一次平均的时间长度
# 历史最大回撤:策略历史上,从最高点到最低点的跌幅
# 股票夏普率:股票平均涨跌幅/涨跌幅的波动,课程里我们简化夏普率计算,假设无风险收益率为0
# 策略夏普率:策略平均涨跌幅/涨跌幅的波动
dic={"股票资金曲线终值":df_ma["资金曲线"].iat[-1],
    "策略资金曲线终值":df_ma["资金曲线_策略"].iat[-1],
    "相对收益":df_ma["资金曲线_策略"].iat[-1]/df_ma["资金曲线"].iat[-1],
    "总交易次数":df_ma["持仓分组"].unique().shape[0],
    "平均持仓时间":(df_ma.index[-1]-df_ma.index[0])/(df_ma["持仓分组"].unique().shape[0]),
    "历史最大回撤":df_ma["最大回撤"].max(),
    "股票夏普率":df_ma["涨跌幅"].mean()/df_ma["涨跌幅"].std(),
    "策略夏普率":df_ma["涨跌幅_策略"].mean()/df_ma["涨跌幅_策略"].std()
    
}
print(dic)
df_ma[["资金曲线","资金曲线_策略"]].plot(figsize=(20,8))

在这里插入图片描述
在这里插入图片描述

注意事项:

1.对策略资金曲线运用cummax函数,它能帮助我们计算出,资金曲线每一行,累计的最大值。比如我对一个列表 [1,3,5,4,2] 计算 cummax 结果为 [1,3,5,5,5]
2.df.unqiue() 去重获取到唯一值
3.shape():读取矩阵长度,如shape[0]是读取矩阵第一维的长度。

上面做了一堆很是凌乱,不方便我们后期复用,所以我们在这里封装一个函数,在实现上面的内容,方便后的的策略优化调用

# 重置一下df_ma,看看其他周期的效果
df_ma = MA_mark(df,100,300)

def fund_curve(dfr,tr=.0008):
    '''
   【回测:资金曲线及绩效指标计算】
    dfr:标记交易信号及持仓状态后的股票数据
    tr:单次交易综合手续费,默认万分之8
    '''
    
    # 拷贝一份数据
    df = dfr.copy()
    
    # 标记多空持仓组别
    df['持仓分组'] = df.index
    df.loc[df['持仓状态']==df['持仓状态'].shift(),'持仓分组'] = None
    df['持仓分组'].fillna(method='ffill',inplace=True)    
    
    # 计算策略基本涨跌幅,持仓为0则当日策略基本收益率也为0
    df['涨跌幅_策略'] = df['涨跌幅'] * df['持仓状态']
    
    # 筛选所有非空持仓并扣除开平仓手续费
    df.loc[(df['持仓状态'] != 0) &
           (df['持仓状态'] != df['持仓状态'].shift()),
           '涨跌幅_策略'] = (1 + df['涨跌幅_策略']) * (1 - tr) - 1
 
    df.loc[(df['持仓状态'] != 0) &
           (df['持仓状态'] != df['持仓状态'].shift(-1)),
           '涨跌幅_策略'] = (1 + df['涨跌幅_策略']) * (1 - tr) - 1
    
    # 计算并更新策略资金曲线    
    df.loc[:,'资金曲线_策略'] = (1 + df['涨跌幅_策略']).cumprod()
    df.loc[:,'资金曲线_策略'] = df['资金曲线_策略'] / df['资金曲线_策略'].iat[0]
    
	# 计算最大回撤
    df['最大回撤'] = df['资金曲线_策略'].cummax()
    df.loc[:,'最大回撤'] = 1 - df['资金曲线_策略'] / df['最大回撤']
    
    return df,{'股票资金曲线终值':df['资金曲线'].iat[-1],
               '策略资金曲线终值':df['资金曲线_策略'].iat[-1],
               '相对收益':df['资金曲线_策略'].iat[-1]/df['资金曲线'].iat[-1],
               '总交易次数':df['持仓分组'].unique().shape[0],
               '平均持仓时间':(df.index[-1]-df.index[0])/df['持仓分组'].unique().shape[0],
               '历史最大回撤':df['最大回撤'].max(),
               '股票夏普率':df['涨跌幅'].mean()/df['涨跌幅'].std(ddof=0),
               '策略夏普率':df['涨跌幅_策略'].mean()/df['涨跌幅_策略'].std(ddof=0)}

# 调用函数,把原始的df_ma数据传入资金曲线计算函数
result = fund_curve(df_ma)

# 查看绩效指标,并绘制资金曲线对比可视化图表
print(result[1]) 
result[0][['资金曲线','资金曲线_策略']].plot(figsize=(20,8),grid=True)

在这里插入图片描述
在这里插入图片描述

用循环调整周期参数

绘制各绩效指标参数热力图

最优参数绩效及资金曲线

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值