为了机器学习量化策略,我标注了两万条数据

news/2024/9/25 23:19:38/

在这里插入图片描述

题图:芝加哥大学海德公园。芝大是经济学重镇,其学者开创了著名的芝加哥经济学派,共产生了 100 位诺奖、10 位菲尔兹奖、4 位图灵奖。今天量化人追逐的 Alpha, 最早就来自于 Michael Jessen 在芝大时的博士论文。


很多人对基于机器学习的量化策略很好奇,常常问什么时候有机器学习的课。其实,对很多人(我自己就是)来说,没有能力改进机器学习的算法和框架,机器学习都是作为黑盒子来学习,难度主要是卡在训练数据上。

这篇文章,将介绍一种数据标注方法和工具。

有监督的机器学习需要标注数据。标注数据一般是一个二维矩阵,其中一列是标签(一般记为 y),其它列是特征(一般记为 X)。训练的过程就是:

$$

fit(x) = WX -> y’ \approx y

$$

训练就是通过反向传播来调整权重矩阵 W W W,使之得到的 y ′ y' y最接近于 y y y

特征矩阵并不困难。它可以是因子在某个时间点上的取值。但如何标注是一个难题。它实际上反应的是,你如何理解因子与标签之间的逻辑关系:因子究竟是能预测标的未来的价格呢,还是可以预测它未来价格的走势?

应该如何标注数据

前几年有一篇比较火的论文,使用 LSTM 来预测股价。我了解到的一些人工智能与金融结合的硕士专业,还把类似的题目布置给学生练习。


作为练习题无可厚非,但也应该讲清楚,使用 LSTM 来预测股价的荒谬之处:你无法利用充满噪声的时序金融数据,从价格直接推导出下一个价格。

坊间还流传另一个方法,既然数据与标签之间不是逻辑回归的关系,那么我们把标签离散化,使之转换成为一个分类问题。比如,按第二天的涨跌,大于 3%的,归类为大幅上涨;涨跌在 1%到 3%的,归类为小幅上涨。在-1%到 1%的,归类为方向不明。

其实这种方法背后的逻辑仍然是逻辑回归。而且,为什么上涨 2.99%是小幅上涨,上涨 3%就是大幅上涨呢?有人就提出改进方法,在每个类之间加上 gap,即 [-0.5%, 0.5%] 为方向不明,[1%,3%] 为小幅上涨,而处在 [0.5%, 1%] 之间的数据就丢掉,不进行训练。这些技巧在其它领域有时候是有效的,但在量化领域,我认为它仍然不够好。因为原理不对。

我们应该回归问题的本质。要判断每一天的涨跌,其实是有难度的。但如果要判断一段趋势是否结束,则相对来讲,特征会多一点,偶然性会低一点。用数学语言来讲,我们可以把一段 k 线中的顶点标注为 1,底部标注为-1,中间的部分都标注为 0。每一个峰都会有一个谷对应,但中间的点会显著多一些,数据分类不够平衡。在训练时,要做到数据分类平衡,把标签为 0 的部分少取一点即可。


顶底数据的标注

鉴于上面的思考,我做了一个小工具,用来标注行情数据的顶和底。

这个工具要实现的任务是:

  1. 加载一段行情数据,绘制 k 线图
  2. 自动识别这段 k 线中的的顶和底,并在图上标记出来
  3. 把这些顶和底的时间提取出来,放到峰和谷两个编辑框中,供人工纠错
  4. 数据校准后,点击“记录 > 下一组"来标注下一段数据

我们使用 zigzag 库来自动寻找 k 线中的顶和底。相比 scipy.signals 包中的 argrelextrema 和 find_peaks 等方法,zigzag 库中的 peaks_valleys_pivot 方法更适合股价数据 – 像 find_peaks 这样的方法,要求的数据质量太高了,金融数据的噪声远远超过它的期待。

peaks_valleys_pivot 会自动把首尾的部分也标记成为峰或者谷 – 这在很多时候会是错误的 – 因为行情还没走完,尾部的标记还没有固定下来。因此,我们需要手动移除这部分标记。此外,偶尔会发现峰谷标记太密的情况 – 一般是由于股价波动太厉害,但如果很快得到修复,我们也可以不标记这一部分。这也需要我们手动移除。

最终,我们将行情数据的 OHLC、成交量等数据与顶底标记一起保存起来。最终,我们将得到类似下面的数据:

当然,它只能作为我们训练数据的一个底稿。我们说过,不能直接使用价格数据作为训练数据。我们必须从中提取特征。显然,像 RSI 这样的反转类指标是比较好的特征。


另外,冲高回落、均线切线斜率变化(由正转负意味着见顶,反之意味着见底)、两次冲击高点不过、k 线 pattern 中的早晨之星、黄昏之星(如果你将它们的 k 线进行 resample, 实际上它是一个冲高回落过程,或者说长上影、长下影)等等都是有一定指示性的特征。

标注工具构建方法

!!! tip
这里我们介绍的是 jupyter 的 ipywidgets 来构建界面的方法。此外,Plotly Dash, streamlit, H2O wave 也是主要为此目标设计的工具。

为了在 notebook 中使用界面元素,我们需要先导入相关的控件:

from ipywidgets import Button, HBox, VBox, Textarea, Layout,Output, Boxfrom IPython.display import display

在一个单元格中,如果最后的输出是一个对象,那么 notebook 将会直接显示这个对象。如果我们要在一个单元格中显示多个对象,或者,在中间的代码中要显示一些对象,就需要用到 display 这个方法。这是我们上面代码引入 display 的原因。

这里我们引入了 HBox, VBox 和 Box 三个容器类控件,Button, TextArea 这样的功能性控件。


Layout 用来指定控件的样式,比如要指定一个峰值时刻输入框的宽度和高度:

peaks_box = Textarea(value='',placeholder='请输入峰值时间,每行一个',description='峰值时间',layout=Layout(width='40%',height='100px')
)

按钮类控件一般需要指定点击时执行的动作,我们通过 on_click 方法,将点击事件和一个事件处理方法相绑定:

save_button = Button(description='存盘'
)
save_button.on_click(save)def save(c):# SAVE DATA TO DISKpass

这里要注意的是,事件响应函数(比如这里的 save),在函数签名上一定要带一个参数。否则,当按钮被点击时,事件就无法传导到这个函数中来,并且不会有任何错误提示。

HBox, VBox 用来将子控件按行、列进行排列。比如:

# K 线图的父容器
figbox = Box(layout=Layout(width="100%"))
inputs = HBox((peaks_box, valleys_box))
buttons = HBox((backward_button, keep_button, save_button, info))
display(VBox((buttons, inputs, figbox)))

Output 控件是比较特殊的一个控件。如果我们在事件响应函数中进行了打印,这些打印是无法像其它单元格中的打印那样,直接输出在单元格下方的。我们必须定义一个 Output 控制,打印的消息将会捕获,并显示在 Output 控件的显示区域中。

info = Output(layout=Layout(width="40%"))def save(c):global info# DO THE SAVE JOBwith info:print("数据已保存到磁盘!")

与此类似,plotly 绘制的 k 线图,也不能直接显示。我们要通过 go.FigureWidget 来显示 k 线图。

import plotly.graph_objects as gofigure = ... # draw the candlestick with bars
fig = go.FigureWidget(figure)
figbox.children = (fig, )

我们特别给出这段代码,是要展示更换 k 线图的方法。在初始化时,我们就必须把 figbox 与其它控件一起安排好,但如何更新 figbox 的内容呢?

答案是,让 figbox 成为一个容器,而 go.FigureWidget 成为它的一个子控件。每次要更新 k 线图时,我们生成一个新的 fig 对象,通过figbox.children = (fig, )来替换它。


最后,谈一点 troubleshooting 的方法。所有通过 on_click 方法绑定的事件函数,即使在运行中出了错,也不会有任何提示。因此,我们需要自己捕获错误,再通过 Output 控件来显示错误堆栈:

def log(msg):global infoinfo.clear_output()with info:if isinstance(msg, Exception):traceback.print_exc(msg)else:print(msg)def on_save(b):try:# DO SOMETHING MAY CAUSE CHAOSraise ValueError()except Exception as e:log(e)info = Output(layout = Layout(...))
save_button = Button()
save_button.on_click(on_save)

利用这款工具,大概花了两小时,最终我得到了2万条数据,其中顶底标签约1600个。


http://www.ppmy.cn/news/1426765.html

相关文章

Sy-linux下常用的网络命令linux network commands

linux下的网络命令非常强大,这里根据教材需要,列出来常用的网络命令和场景实例,供参考。 一、命令列表: Command Description ip Manipulating routing to assigning and configuring network parameters traceroute Identi…

47.基于SpringBoot + Vue实现的前后端分离-校园外卖服务系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统,采用SpringBoot Vue框架,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得基于SpringBoot Vue技术的校园外卖服务系统设计与实现管理工作…

【Excel2LaTeX】复杂表格制作的解决方案

刚开始用LaTeX写论文,遇到的第一道坎就是绘制表格,较小的普通表格可以通过简单的语法实现,但是较大的复杂的表格却让我无从下手。 Excel2LaTeX插件 这里介绍一种我用到非常顺手的工具:Excel2LaTeX插件,下载地址&#x…

symbol 类型学习

探索 JavaScript 中的独特标识符:Symbol 类型 JavaScript 作为一门灵活且功能丰富的编程语言,提供了多种数据类型供开发者构建复杂的应用程序。其中,一种相对新颖且独特的类型——Symbol,为对象属性的管理带来了更高的安全性和定…

python调用Microsoft Word把文件夹下所有docx或doc批量转化为PDF

python调用Microsoft Word把文件夹下所有docx或doc批量转化为PDF 首先,确保你的系统上安装了Microsoft Word。然后,你需要安装comtypes库,如果你还没有安装,可以通过以下命令进行安装: pip install comtypes以下是一个…

明道云HAP合作伙伴计划全解析:开辟业务增长新路径

什么是明道云HAP合作伙伴计划? 明道云采纳的是增值伙伴商业模式。在这个模式下,合作伙伴通过平台型产品为终端客户提供定制应用、行业解决方案、赋能培训等增值活动,从而在大幅降低交付成本的同时获得多来源的收入,提高经营绩效水…

学习JavaEE的日子 Day39 注解,反射

Day39 注解 1.什么是注解 理解:给程序员和程序解释代码信息 2.注解与注释的区别 注释:对程序员解释代码信息 注解:对程序和程序员解释代码信息 3.注解的所用 可以作用于包、类、方法、属性上,给他们添加额外的信息,可…

[Qt网络编程]之获取基本网络信息

目录 前言 获取主机IP地址 获取主机 IP 地址和子网掩码 获取主机 MAC 地址 前言 获取主机的网络地址和接口信息是进行网络编程的第一步,也是网络编程的基础。Qt提供了网络接口类 QNetworkInterface、网络地址人口类 QNetworkAddressEntry 和主机地址类 QHostAd…