上篇文章记录了因子扩展的过程,这篇文章利用这些因子,来演示如何实现特定选股规则准确率的判断。本文代码主要实现以下功能:
持股2周(10个交易日),收益6%
# 持股天数
g_days = 10
# 预期盈利
g_profit = 0.06
# 2日前倍量暴涨,连续缩量调整两天
condition = df['value_boom_2a'].iloc[i] and \
df['volume_2a'].iloc[i] >= 2 * df['volume_3a'].iloc[i] and \
df['volume_2a'].iloc[i] > df['volume_1a'].iloc[i] and \
df['volume_2a'].iloc[i] > df['volume'].iloc[i] and \
df['close'].iloc[i] < df['close_2a'].iloc[i] and \
df['close_1a'].iloc[i] < df['close_2a'].iloc[i] and \
df['close'].iloc[i] > df['ma_20'].iloc[i] and df['ma_30'].iloc[i] > df['ma_60'].iloc[i] \
> df['ma_120'].iloc[i] > df['ma_250'].iloc[i]
这里用到了上一篇文章的扩展指标,close_2a表示2日前的收盘价,依次类推。
实验对2018年1月1日以来的历史数据进行测试,所用选股规则的准确率为57.5%,即有57.5%的买入在两周内能至少盈利6%。
程序会将正负样本输出到csv文件中,以pos结尾的为正样本,以neg结尾的为负样本,如下图所示。
正样本以sh600057_pos.csv为例,打开文件显示如下。
表格中只有一行日期(如果有多次符合选股规则,会显示多行日期),即表示在2020年8月3日买入600057,两周内会有至少6%的收益。来看下2020年8月3日附近的K线图。
2020年7月30日倍量暴涨,然后缩量调整两天到8月3日,均线多头排列,那么在8月4日开盘价买入(红色向上箭头),后面有一波涨幅。
负样本以sh600063_pos.csv为例,打开文件显示如下。
表格中只有一行日期(如果有多次符合选股规则,会显示多行日期),即表示在2019年10月14日买入600063,两周内没有实现6%的收益。来看下2019年10月14日附近的K线图。
2019年10月10日倍量暴涨,然后缩量调整两天到10月14日,均线多头排列,那么在10月15日开盘价买入(红色向上箭头),在后面有一个多月的调整,没有实现6%的收益。
通过正负样本K线的观察,可以叠加选股规则,实现准确率的进一步提升。
程序输出的收益直方图如下。
通过直方图可以看到,最高柱处在0的位置,也就是有很多买入即为高点的情况。也可以看到在0.8以上的位置有一根短柱,即有一部分选股可以实现两周接近翻倍的行情。
直方图可以直观地显示最大收益的分布,为后续指定预期收益的标准提供参考。
完整代码:
import os
import sys
import pandas as pd
import datetime
from matplotlib import pyplot as plt
import matplotlib as mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 设置简黑字体
mpl.rcParams['axes.unicode_minus'] = False # 解决‘-’bug
# 获取当前目录
proj_path = os.path.dirname(os.path.abspath(sys.argv[0])) + '/../'
# 持股天数
g_days = 10
# 预期盈利
g_profit = 0.06
# 最小周期数,需要根据选股因子的进行不同设置
g_min_period = 280
if __name__ == '__main__':
# 读入股票代码
stk_code_file = proj_path + 'data/tdx/all_stock_codes.csv'
stk_codes = pd.read_csv(stk_code_file, encoding='unicode_escape')
# 记录正确样本次数
pos_count = 0
# 记录错误样本次数
neg_count = 0
# 记录最大收益的列表
profit_list = []
# 输出路径,用于保存正负样本的代码及日期
output_path = proj_path + 'data/temp/' + datetime.datetime.strftime(
datetime.datetime.now(), '%Y-%m-%d-%H-%M-%S') + '/'
if not os.path.exists(output_path):
os.makedirs(output_path)
# 循环处理每只股票
for code in stk_codes['code']:
print('processing {} ...'.format(code))
# 读入股票因子
input_file = proj_path + 'data/extension/d/hard_rules/' + code + '.csv'
df = pd.read_csv(input_file)
# 如果日线数据不足,则跳过
if df.shape[0] < g_min_period:
continue
# 只分析2018年以后的行情
df = df[df['date'] > '2018-01-01']
# 存储正、负样本的列表
out_pos_list = []
out_neg_list = []
for i in range(g_min_period, df.shape[0] - g_days):
# 2日前倍量暴涨,连续缩量调整两天
condition = df['value_boom_2a'].iloc[i] and \
df['volume_2a'].iloc[i] >= 2 * df['volume_3a'].iloc[i] and \
df['volume_2a'].iloc[i] > df['volume_1a'].iloc[i] and \
df['volume_2a'].iloc[i] > df['volume'].iloc[i] and \
df['close'].iloc[i] < df['close_2a'].iloc[i] and \
df['close_1a'].iloc[i] < df['close_2a'].iloc[i] and \
df['close'].iloc[i] > df['ma_20'].iloc[i] and df['ma_30'].iloc[i] > df['ma_60'].iloc[i] \
> df['ma_120'].iloc[i] > df['ma_250'].iloc[i]
if condition:
# 记录满足条件时的收盘价
open_price = df['open'].iloc[i + 1]
max_price = 0
# 计算g_days日内的最大收益
for j in range(g_days):
max_price = max(max_price, df['high'].iloc[i + j + 1])
# 将g_days日内的最大收益保存在列表中
profit_list.append((max_price - open_price) / open_price)
# 如果最大收益大于g_profit则为正样本,否则为负样本
if (max_price - open_price) / open_price > g_profit:
pos_count += 1
out_pos_list.append(df['date'].iloc[i])
else:
neg_count += 1
out_neg_list.append(df['date'].iloc[i])
if pos_count + neg_count:
print('pos_count:{}\tneg_count:{}\taccuracy:{}'.format(
pos_count, neg_count, pos_count / (pos_count + neg_count)))
# 将正、负样本股票及日期保存在文件中,便于后续分析
if len(out_pos_list):
pd.DataFrame(columns=['date'], data=out_pos_list).to_csv(
'{}{}_pos.csv'.format(output_path, code), index=False)
if len(out_neg_list):
pd.DataFrame(columns=['date'], data=out_neg_list).to_csv(
'{}{}_neg.csv'.format(output_path, code), index=False)
print('strategy accuracy:{}'.format(pos_count / (pos_count + neg_count)))
# 绘制最大收益直方图
plt.hist(profit_list, 100)
plt.xlabel('收益')
plt.ylabel('分布')
plt.title('收益直方图')
plt.show()
欢迎大家关注、点赞、转发、留言,感谢支持!
微信群用于学习交流,感兴趣的读者请扫码加微信!
QQ群(676186743)用于资料共享,欢迎加入!