上篇文章记录了因子扩展的过程,这篇文章利用这些因子,来演示如何实现特定选股规则准确率的判断。本文代码主要实现以下功能:
- 通过遍历历史数据,实现某个选股规则下,N日内收益M%的准确率判断(例如10日内收益超过6%)。
- 记录符合选股规则的个股及日期,并分为正负样本分别保存为文件,便于后续分析。正样本是指,在当前规则下,N日内能收益M%的某次选股;负样本是指,在当前规则下,N日内未能收益M%的某次选股。
- 对所有选股的最大收益进行图形化显示。
预期收益
持股2周(10个交易日),收益6%
# 持股天数
g_days = 10
# 预期盈利
g_profit = 0.06
选股规则
- 2日前倍量暴涨9%以上。
- 随后两日缩量调整。
- 收盘价在20均线上方。
- 20、30、60、120、250均线多头排列。
- 以下一日开盘价买进
# 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以上的位置有一根短柱,即有一部分选股可以实现两周接近翻倍的行情。
直方图可以直观地显示最大收益的分布,为后续指定预期收益的标准提供参考。
小结
- 通过本文程序可以实现选股规则准确率的判断。
- 通过输出正负样本,可以通过K线图观察选股准确与错误的情况,以便进一步优化选股规则。
- 通过直方图可以直观显示最大收益分布情况,便于合理指定收益目标。
完整代码:
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()
博客内容只用于交流学习,不构成投资建议,盈亏自负!
个人博客:http://coderx.com.cn/(优先更新)
项目最新代码:https://gitee.com/sl/quant_from_scratch
欢迎大家转发、留言。有微信群用于学习交流,感兴趣的读者请扫码加微信!
如果认为博客对您有帮助,可以扫码进行捐赠,感谢!
微信二维码 | 微信捐赠二维码 |
---|---|
![]() | ![]() |