前文介绍了股票日线数据下载,从本文起开始记录一些股票因子的计算方法,这些因子将用于后续策略的编写。
我们将实现双神穿多线策略,策略的选股条件是,股票当日形成双神(间隔的2个涨停),K线同时上穿5、10、20、30日均线,30日线在60日线上方,把当日涨停收盘价格定义为买点buy_point。买入价格为后续日期开盘价与buy_point的较小值,卖出止盈价格为buy_point*(1+6.18%),止损价格为buy_point*(1-16.18%)。
在写本系列文章之前,尚未对该策略进行参数调整、胜率统计、回测分析,就目前观察的几只个股来看,都有不错的涨幅。读者可以参与一起实现和优化,共同打造成一个实盘策略。
该策略在2018年至2020年3年周期回测后,年化收益为41.7%,最大回撤为25.8%。
本文首先计算涨停因子。
主要代码分析
新建源文件,命名为data_center_v3.py,全部内容见文末,v3主要涉及3个方面的改动:
新增计算涨停因子函数
def zt(df):
该函数用于计算涨停因子,其中:
- 参数df为待计算扩展因子的DataFrame
- 返回值为包含扩展因子的DataFrame
这里以当日收盘价较前一日收盘价上涨9.8%及以上作为涨停判断标准。若涨停,则因子为True,否则为False。
df['zt'] = np.where((df['close'].values >= 1.098 * df['preclose'].values), True, False)
计算涨停因子,考虑到复权以及创业板和科创板涨停为涨幅20%,这里把当日收盘价较前一日收盘价上涨9.8%及以上都作为涨停。
return df
返回包含扩展因子的DataFrame
新增计算扩展因子
def extend_factor(df):
该函数用于计算扩展因子,其中:
- 参数df为待计算扩展因子的DataFrame
- 返回值为包含扩展因子的DataFrame
在v3中只计算涨停因子,后续会添加双神等因子的计算。
df = df.pipe(zt)
使用pipe调用函数zt,计算涨停因子
return df
返回包含扩展因子的DataFrame。
修改创建数据函数
def create_data(stock_codes, from_date='1990-12-19', to_date=datetime.date.today().strftime('%Y-%m-%d'), adjustflag='2'):
"""
下载指定日期内,指定股票的日线数据,计算扩展因子
:param stock_codes: 待下载数据的股票代码
:param from_date: 日线开始日期
:param to_date: 日线结束日期
:param adjustflag: 复权选项 1:后复权 2:前复权 3:不复权 默认为前复权
:return: None
"""
# 下载股票循环
for code in stock_codes:
print('正在下载{}...'.format(code))
# 登录BaoStock
bs.login()
# 下载日线数据
out_df = bs.query_history_k_data_plus(code, g_baostock_data_fields, start_date=from_date, end_date=to_date,
frequency='d', adjustflag=adjustflag).get_data()
# 注销登录
bs.logout()
# 剔除停盘数据
if out_df.shape[0]:
out_df = out_df[(out_df['volume'] != '0') & (out_df['volume'] != '')]
以上内容与v2相同,可参考v2分析内容。
if not out_df.shape[0]:
continue
如果数据为空,则不进行后续过滤及扩展因子计算。
out_df.drop_duplicates(['date'], inplace=True)
曾遇到过出现重复数据的情况,加一道去重过滤。
if out_df.shape[0] < g_available_days_limit:
continue
这里设置只处理已有多于等于g_available_days_limit根日线数据的股票,如果可用的日线数据少于g_available_days_limit根,则不创建数据,来确保后续可以有效计算扩展因子。
在文件开头我们设置了全局变量:
g_available_days_limit = 250
convert_list = ['open', 'high', 'low', 'close', 'preclose', 'volume', 'amount', 'turn', 'pctChg']
out_df[convert_list] = out_df[convert_list].astype(float)
把相关字段类型转化为float类型,BaoStock下载得到的这些字段默认为str类型,需要转化为float类型,才能用于后续指标计算。
out_df.reset_index(drop=True, inplace=True)
重置索引,由于我们进行了去重、剔除停盘数据等操作,需要对索引进行重置,来保持索引的连续性。
out_df = extend_factor(out_df)
调用函数extend_factor计算扩展因子。
print(out_df)
打印数据创建结果,我们来看一下三美股份sh.603379的打印结果:
date open high ... pcfNcfTTM isST zt
0 2019-04-02 32.652593 32.652593 ... 76.815822 0 True
1 2019-04-03 35.917853 35.917853 ... 84.497404 0 True
2 2019-04-04 39.511735 39.511735 ... 92.952079 0 True
3 2019-04-08 43.462210 43.462210 ... 102.245642 0 True
4 2019-04-09 47.755292 47.755292 ... 97.278109 0 False
.. ... ... ... ... ... ... ...
598 2021-09-13 31.580000 33.380000 ... -70.841885 0 False
599 2021-09-14 32.520000 35.000000 ... -71.822773 0 False
600 2021-09-15 33.020000 36.250000 ... -79.015949 0 True
601 2021-09-16 37.900000 38.000000 ... -77.381136 0 False
602 2021-09-17 35.510000 39.050000 ... -85.119250 0 True
[603 rows x 18 columns]
倒数第三行和最后一行显示2021年9月15日和17日为涨停,对应看一下K线图:
也可以看到2021年9月15日和17日均为涨停,计算结果正确。
小结
本文主要介绍了要实现的策略的思路,完成了涨停因子的计算,后续继续介绍策略所需的其他因子的实现方式。
到目前为止,创建的数据只是用于打印,未实现存储。因此只要确保程序能正常运行即可,不需要等待程序运行结束。等后续文章所有因子都介绍完成后,我们会进行多线程计算,并将结果保存到MySQL中。
data_center_v3.py的全部代码如下:
import baostock as bs
import datetime
import sys
import numpy as np
# 可用日线数量约束
g_available_days_limit = 250
# BaoStock日线数据字段
g_baostock_data_fields = 'date,open,high,low,close,preclose,volume,amount,adjustflag,turn,tradestatus,pctChg,peTTM,pbMRQ, psTTM,pcfNcfTTM,isST'
def get_stock_codes(date=None):
"""
获取指定日期的A股代码列表
若参数date为空,则返回最近1个交易日的A股代码列表
若参数date不为空,且为交易日,则返回date当日的A股代码列表
若参数date不为空,但不为交易日,则打印提示非交易日信息,程序退出
:param date: 日期
:return: A股代码的列表
"""
# 登录baostock
bs.login()
# 从BaoStock查询股票数据
stock_df = bs.query_all_stock(date).get_data()
# 如果获取数据长度为0,表示日期date非交易日
if 0 == len(stock_df):
# 如果设置了参数date,则打印信息提示date为非交易日
if date is not None:
print('当前选择日期为非交易日或尚无交易数据,请设置date为历史某交易日日期')
sys.exit(0)
# 未设置参数date,则向历史查找最近的交易日,当获取股票数据长度非0时,即找到最近交易日
delta = 1
while 0 == len(stock_df):
stock_df = bs.query_all_stock(datetime.date.today() - datetime.timedelta(days=delta)).get_data()
delta += 1
# 注销登录
bs.logout()
# 筛选股票数据,上证和深证股票代码在sh.600000与sz.39900之间
stock_df = stock_df[(stock_df['code'] >= 'sh.600000') & (stock_df['code'] < 'sz.399000')]
# 返回股票列表
return stock_df['code'].tolist()
def create_data(stock_codes, from_date='1990-12-19', to_date=datetime.date.today().strftime('%Y-%m-%d'),
adjustflag='2'):
"""
下载指定日期内,指定股票的日线数据,计算扩展因子
:param stock_codes: 待下载数据的股票代码
:param from_date: 日线开始日期
:param to_date: 日线结束日期
:param adjustflag: 复权选项 1:后复权 2:前复权 3:不复权 默认为前复权
:return: None
"""
# 下载股票循环
for code in stock_codes:
print('正在下载{}...'.format(code))
# 登录BaoStock
bs.login()
# 下载日线数据
out_df = bs.query_history_k_data_plus(code, g_baostock_data_fields, start_date=from_date, end_date=to_date,
frequency='d', adjustflag=adjustflag).get_data()
# 注销登录
bs.logout()
# 剔除停盘数据
if out_df.shape[0]:
out_df = out_df[(out_df['volume'] != '0') & (out_df['volume'] != '')]
# 如果数据为空,则不创建
if not out_df.shape[0]:
continue
# 删除重复数据
out_df.drop_duplicates(['date'], inplace=True)
# 日线数据少于g_available_days_limit,则不创建
if out_df.shape[0] < g_available_days_limit:
continue
# 将数值数据转为float型,便于后续处理
convert_list = ['open', 'high', 'low', 'close', 'preclose', 'volume', 'amount', 'turn', 'pctChg']
out_df[convert_list] = out_df[convert_list].astype(float)
# 重置索引
out_df.reset_index(drop=True, inplace=True)
# 计算扩展因子
out_df = extend_factor(out_df)
print(out_df)
def extend_factor(df):
"""
计算扩展因子
:param df: 待计算扩展因子的DataFrame
:return: 包含扩展因子的DataFrame
"""
# 使用pipe计算涨停因子
df = df.pipe(zt)
return df
def zt(df):
"""
计算涨停因子
若涨停,则因子为True,否则为False
以当日收盘价较前一日收盘价上涨9.8%及以上作为涨停判断标准
:param df: 待计算扩展因子的DataFrame
:return: 包含扩展因子的DataFrame
"""
df['zt'] = np.where((df['close'].values >= 1.098 * df['preclose'].values), True, False)
return df
if __name__ == '__main__':
stock_codes = get_stock_codes()
create_data(stock_codes)
博客内容只用于交流学习,不构成投资建议,盈亏自负!
个人博客:http://coderx.com.cn/(优先更新)
项目最新代码:https://gitee.com/sl/quant_from_scratch
欢迎大家转发、留言。有微信群用于学习交流,感兴趣的读者请扫码加微信!
如果认为博客对您有帮助,可以扫码进行捐赠,感谢!
微信二维码 | 微信捐赠二维码 |
---|---|
![]() | ![]() |