简单地说,如果我们买入/持有近段时间跑赢大盘指数的股票,那么收益曲线也会跑赢大盘。 如下图是一只近期跑赢大盘的股票,无论是在A、B、C、D点买入,无论买入点是基于基本面、技术面、消息面、X面,无论是根据股评大师们口中的金叉、死叉、OOXX、X叉,无论是打算做长线、短线、X线,在个股跑赢大盘期间,收益曲线都是跑赢大盘指数的🙂。

相反,如果我们买入/持有近段时间跑输大盘指数的股票。。。基本面、技术面、消息面、X面。。。金叉、死叉、OOXX、X叉。。。长线、短线、X线,在个股跑输大盘期间,收益曲线都是跑输大盘指数的🙂。

总的来说 买入/持有近段时间跑赢大盘指数的股票,那么收益曲线也会跑赢大盘; 买入/持有近段时间跑输大盘指数的股票,那么收益曲线也会跑输大盘指数 <- 天呐,这不是21世纪最废的废话了吗??!!

没错,这篇文章就是研究这个问题的: 假如我们要求即将买入(无论是基于什么原因买入)的股票在近期是跑赢大盘的,并且在股票不再跑赢大盘指数了的时候卖出(即便后面还有上涨空间,但只要它跑输大盘就要卖出了,避免它拖累收益曲线跑输大盘指数) 会是一副什么样的情景呢?

下面开始做实验。

等等,上面的 近期是跑赢大盘 的“近期”是多久呢? 1天,10天,or 100天?“跑赢大盘”跑赢多少算数呢? 0.01%,1%,or 10%+? 用全市场5天内最能跑赢大盘的前5个股票行不行呢?

有人做过实验,结果显示不行!

那怎么办呢? 这个神圣的任务就交给机器学习吧。它的工作原理是这样的:
step 1. 计算全部交割单买入点前n*1, n*2, ...n*x天至买入当天的涨速(可以使用收盘价的线性拟合角度,或者均线的线性拟合角度,或者干脆统计价格涨幅百分比)。
step 2. 将它们分组,比如说买入点前n*1天 跌5%-10%, n*2天跌2%-5%的为一组; n*1天 涨0%-5%, n*2涨2%-5%的为一组 等等。
step 3. 假如它发现 买入点前n*1天 跌5%-10%, n*2天跌2%-5% 这一组的交易90+%都是亏钱的,那么下次它在下次发现发出特征类似这一组的交易时就拦截。我们(人类)如果有兴趣可以去深扒成功/错误的命中率都集中在哪里,如果没有兴趣也可以不管了,反正裁判默认只会拦截失败可能性>成功可能性的交易。

但是裁判是基于机器学习的,如果我们不告诉它有“买入/并持有近段时间跑赢大盘指数的股票,那么收益曲线也会跑赢大盘”这么一回事,它是不懂得这么观察和分析交易的。

if I say Audi and BMW, Boeing, Airbus, and Titanic belongs to 2 different groups, It would predict Audi and BMW belongs to group 1 and Boeing, Airbus, and Titanic belongs to group 2. But it cannot say what does group 1 and group 2 actually refers to, some human need to look at them and say — hey these are cars. Machine learning obviously can’t gure it out. If I say those objects belong to 3 di erent groups it can say Titanic belongs to group 3 because it’s features are drastically different from others, but can never say it is a ship.
如果我(人类)告诉机器人奥迪和宝马、波音、空客和泰坦尼克号属于两个个不同的组,那么奥迪和宝马属于第一组,波音、空客和泰坦尼克号属于第二组。但是机器学习并不知道第一组和第二组实际上指的是什么,人类一看就明白 ——嘿,这些是汽车。机器学习显然没有这个能力。如果我说这些物体属于三个不同的组,它会把泰坦尼克号归类到第三组,因为它的功能与其他物体截然不同,但它不知道是泰坦尼克号一艘船。
-- https://medium.com/@sapy/get-over-the-machine-learning-hype-79abcbe37272

下面是1个我们只需要提供一些idea,然后由机器学习完成精细的定量分析,并指导交易的例子。
* 这一拨新裁判是刚刚完成“代码工程”和训练的,由于笔者迫不及待看到它们的效果,所以让它们一起上阵了,没有深扒其中单独的一个(例如上面提到的观察个股近期是否跑赢大盘的那一个)对整个收益曲线的影响。但是它们十几个裁判一起把本来亏损的策略改善到年化30%左右,盈亏比3%+ 显然已经很了不起了。


接上一篇《弱人工智能否帮助无知小散从市场获利?答案是:甚至能帮助小狗获利》

本次有以下不同:

1.更改卖出因子的配置参数,只为了使交易更频繁(结果没有裁判参与交易的情况下当然是亏损更严重),制造更多交割单给裁判们学习。 <- 这不是重点。
2.更改了仓位管理策略。 <- 这也不是重点。

3.新增了好几个自定义的机器人裁判扭转局势:

# 机器人裁判,通过对比个股买入点前n*1,n*2,n*x天至买入当天的均线角度 和 对应大盘
# 的均线角度 得知该股是否跑赢大盘,以及跑赢百分比
ump.manager.append_user_ump(WzUmpEdgeMaSpeed)

# 机器人裁判,观察买入点前n*1,n*2,n*x天至买入当天的价格rank(相当于压力位/支撑位)和
# 均线角度
ump.manager.append_user_ump(WzUmpEdgePriceWithMaDeg)

# 机器人裁判,观察买入点前n*1,n*2,n*x天至买入当天的个股对应
# 大盘指数(上证50,上证,深证,创业)的均线角度
ump.manager.append_user_ump(WzUmpEdgeIndexMaDeg)

# 机器人裁判,观察买入点前n*1,n*2,n*x天至买入当天的个股对应大盘指数的处于
# 趋势or震荡(采用多项式拟合技术)
ump.manager.append_user_ump(WzUmpEdgeIndexPoly)

# 下面就不一样介绍了
ump.manager.append_user_ump(WzUmpEdgeMaDeg)
ump.manager.append_user_ump(WzUmpEdgePoly)

ump.manager.append_user_ump(WzUmpEdgeMaSpeedWithIndMaDegAndSmlr)
ump.manager.append_user_ump(WzUmpEdgeIndexPolyAndMaDeg)
ump.manager.append_user_ump(WzUmpEdgePolyWithMaDeg)

新增裁判较多,这里挑选WzUmpEdgeMaSpeed 这个出来讲解。这个裁判通过对比个股买入点前n*1,n*2,n*x天至买入当天的均线角度 和 对应大盘的均线角度, 从而得知该股是否跑赢大盘,以及跑赢百分比

由于abupy内置的裁判都没有通过这种视觉观察交易的, 我们需要编辑代码(相当于特征工程)来指导裁判从这个视觉观察和分析交易:

def calc_feature(self, kl_pd, combine_kl_pd, day_ind, buy_feature):
    """
    根据买入或者卖出时的金融时间序列,以及交易日信息构造拟合角度特征
    :param kl_pd: 择时阶段金融时间序列
    :param combine_kl_pd: 合并择时阶段之前1年的金融时间序列
    :param day_ind: 交易发生的时间索引,即对应self.kl_pd.key
    :param buy_feature: 是否是买入特征构造(bool)
    :return: 构造角度特征的键值对字典
    """
    # 返回的角度特征键值对字典
    deg_dict = {}
    for dk in self.keys:
        xd,ma_timeperiod = dk.split('/')
        xd = int(xd)
        ma_timeperiod = int(ma_timeperiod)
        # 迭代预设角度周期,计算构建特征
        ma_deg_key = xd + ma_timeperiod
        if day_ind - ma_deg_key >= 0:
            # 如果择时时间序列够提取特征,使用kl_pd截取特征交易周期收盘价格
            # deg_close = kl_pd[day_ind - ma_deg_key + 1:day_ind + 1].close
            kl_local = kl_pd[day_ind - ma_deg_key + 1:day_ind + 1]
        else:
            # 如果择时时间序列不够提取特征,使用combine_kl_pd截取特征交易周期,首先截取直到day_ind的时间序列
            combine_kl_pd = combine_kl_pd.loc[:kl_pd.index[day_ind]]
            # 如combine_kl_pd长度大于特征周期长度-> 截取combine_kl_pd[-dk:].close,否则取combine_kl_pd所有交易收盘价格
            # deg_close = combine_kl_pd[-ma_deg_key:].close if combine_kl_pd.shape[0] > ma_deg_key else combine_kl_pd.close
            kl_local= combine_kl_pd[-ma_deg_key:] if combine_kl_pd.shape[0] > ma_deg_key else combine_kl_pd
        # 准备获取大盘指数k线图数据
        start = kl_local.head(1).date[0]
        end= kl_local.tail(1).date[0]
        start_date = moment.date(str(start), "%Y%m%d").format("YYYY-MM-DD")
        end_date = moment.date(str(end), "%Y%m%d").format("YYYY-MM-DD")
        index_symbol = self.detect_index_symbol(kl_pd.name) # <- 返回大盘指数symbol
        # 开始获取大盘指数k线图数据
        index_kl = ABuSymbolPd.make_kl_df(index_symbol, start=start_date, end=end_date)
        # 计算近期大盘均线
        index_ma = MaService.calc_ma(index_symbol, index_kl, ma_timeperiod, MA_Type.EMA)
        # 计算近期大盘均线角度
        index_ang = ABuRegUtil.calc_regress_deg(index_ma.tail(xd), show=False)
        # 标准化拟合角度值
        index_ang = 0 if np.isnan(index_ang) else round(index_ang, 3)            

        # 计算个股均线
        ma = MaService.calc_ma(kl_pd.name, kl_local, ma_timeperiod, MA_Type.EMA)
        # print('index_ma:', kl_pd.name, ma_timeperiod, index_ma)
        # print('ma', kl_pd.name, ma_timeperiod , ma)
        # 计算均线拟合角度
        ang = ABuRegUtil.calc_regress_deg(ma.tail(xd), show=False)
        # 标准化拟合角度值
        ang = 0 if np.isnan(ang) else round(ang, 3)

        diff = ang - index_ang
        diff = 0 if np.isnan(diff) else round(diff, 3)
        # print(dk , diff)
        # 角度特征键值对字典添加拟合角度周期key和对应的拟合角度值
        deg_dict['{}speed_diff_deg{}'.format(self.feature_prefix(buy_feature=buy_feature), dk)] = diff

    return deg_dict

以上是特征class的关键代码。还有裁判class的代码也只是几十行,这里就不贴上来了。

到此,人类要做的事情就差不多完成了,剩下的交给机器。

以下为jupyter notebook预览,:

在新的窗口中打开 | 下载jupyter notebook在本地浏览

本策略实盘演示:

在新的窗口中打开 | 下载jupyter notebook在本地浏览

ps: 由于整个研究比较沉长,可以点击上面链接下载jupyter notebook在本地浏览。 the easiest way to 搭建和配置jupyter研究环境是使用一行command。 在jupyter中可以借助左边的toc(table of content)导航能很方便浏览整个research:

总结: