Python 机器学习算法交易实用指南(一)(4)https://developer.aliyun.com/article/1523719
数据的质量
数据集的质量是另一个重要的标准,因为它影响分析和货币化所需的工作量,以及它包含的预测信号的可靠性。质量方面包括数据频率和其可用历史长度、其所含信息的可靠性或准确性、它是否符合当前或潜在未来的法规,以及其使用的独家性。
法律和声誉风险
使用替代数据可能带有法律或声誉风险,特别是当它们包括以下项目时:
- 重要非公开信息(MNPI),因为它意味着侵犯了内幕交易法规
- 个人身份信息(PII),主要因为欧盟已经颁布了通用数据保护条例(GDPR)
因此,法律和合规要求需要彻底审查。当数据提供者也是积极基于数据集进行交易的市场参与者时,也可能存在利益冲突。
排他性
替代数据集包含的信号是否足够预测,能够在一段有意义的时间内以高夏普比率单独驱动策略的可能性与其可用性和处理的便利性成反比。换句话说,数据越独占,处理难度越大,数据越有可能具有阿尔法内容,而不会遭受快速信号衰减的影响。
提供标准财务比率的公共基本数据包含很少的阿尔法,不适合独立策略,但可能有助于多样化风险因素的投资组合。大型复杂数据集需要更长的时间被市场吸收,并且新数据集继续频繁出现。因此,评估其他投资者对数据集的熟悉程度以及提供商是否是此类信息的最佳来源至关重要。
当企业开始销售其为其他目的生成的废弃数据时,排他性或成为新数据集的早期采用者可能会带来额外的好处,因为可能可以影响数据的收集或策划方式,或者协商限制竞争对手访问的条件,至少在一定时间段内。
时间范围
对于在不同情景下测试数据集的预测能力而言,更广泛的历史记录非常理想。可用性在几个月到几十年之间变化很大,并且对基于数据构建和测试的交易策略的范围有重要影响。我们在介绍主要来源的主要类型时提到了一些不同数据集的时间范围。
频率
数据的频率决定了新信息多久可用一次以及在给定时期内预测信号可以多么差异化。它还影响投资策略的时间范围,范围从每日,到每周,甚至更低的频率。
可靠性
当然,数据准确反映其意图的程度以及这可以得到多好的验证是非常重要的关注点,并且应通过彻底的审计进行验证。这适用于原始数据和处理过的数据,其中提取或聚合信息的方法需要进行分析,考虑到提议收购的成本效益比。
技术方面
技术方面关注报告的延迟或延迟以及数据提供的格式。
延迟
数据提供商通常提供批量资源,延迟可能来自数据的收集方式、后续处理和传输,以及法规或法律约束。
格式
数据的可用格式范围广泛,取决于来源。处理后的数据将以用户友好的格式提供,并可通过强大的 API 轻松集成到现有系统或查询中。另一方面,体积庞大的数据源,如视频、音频或图像数据,或专有格式,需要更多的技能来准备分析,但也为潜在竞争者提供了更高的准入壁垒。
替代数据市场
投资行业预计在 2018 年将花费约 2,000,000,000-3,000,000,000 美元用于数据服务,预计这一数字将与其他行业保持两位数的增长。这些支出包括替代数据的获取、相关技术的投资以及合格人才的聘用。
安永的一项调查显示,2017 年替代数据的使用得到了广泛的应用;例如,有 43%的基金使用了网络抓取的数据,几乎 30%的基金正在尝试卫星数据。根据迄今为止的经验,基金经理认为网络抓取的数据和信用卡数据最具洞察力,而地理定位和卫星数据约 25%的人认为不够信息丰富:
反映这个新兴行业的快速增长,替代数据提供商市场相当分散。根据摩根大通的数据,有超过 500 家专业数据公司,而AlternativeData.org列出了 300 多家。供应商扮演着多种角色,包括咨询公司、数据聚合商和技术解决方案;卖方支持以各种格式提供数据,从原始数据到半加工数据或从一个或多个来源提取的信号形式。
我们将重点介绍主要类别的规模,并概述一些突出的例子,以说明它们的多样性。
数据提供商和使用案例
AlternativeData.org(由供应商 Yipit 支持)列出了几个类别,可以作为各种数据提供商领域活动的大致代理。社交情绪分析是迄今为止最大的类别,而卫星和地理定位数据近年来增长迅速:
产品类别 | 供应商数量 | 目标 |
社交情绪 | 48 | 原始或加工后的社交媒体数据;短期趋势 |
卫星 | 26 | 中期经济活动的航拍监测 |
地理定位 | 22 | 追踪零售、商业地产或活动人流量 |
网络数据和流量 | 22 | 监控搜索兴趣、品牌流行度和事件 |
信用卡和借记卡使用情况 | 14 | 追踪短期消费者支出和企业收入 |
应用使用情况 | 7 | 监控应用销售或收集二手数据 |
电子邮件和消费者收据 | 6 | 通过连锁店、品牌、行业或地理位置跟踪消费者支出 |
天气 | 4 | 与作物和商品相关的长期趋势 |
其他 | 87 |
以下简要示例旨在说明服务提供商的广泛范围和潜在用例。
社会情感数据
社会情感分析与 Twitter 数据最密切相关。Gnip 是一个早期的社交媒体聚合器,通过 API 从许多网站提供数据,并于 2014 年以 1.34 亿美元的价格被 Twitter 收购。搜索引擎是另一个来源,当研究人员发表在《自然》杂志上时,基于 Google Trends 的投资策略(例如债务)可以用于一个较长时期的有利交易策略时,它变得显著(参见 GitHub repo github.com/PacktPublishing/Hands-On-Machine-Learning-for-Algorithmic-Trading
的参考资料)。
Dataminr
Dataminr 成立于 2009 年,根据与 Twitter 的独家协议提供社会情感和新闻分析。该公司是较大的替代性提供商之一,并于 2018 年 6 月由富达领投筹集了额外的 3.92 亿美元,估值达到 16 亿美元,使其总融资达到 569 亿美元。它强调使用机器学习从社交媒体提取的实时信号,并为广泛的客户提供服务,包括不仅仅是买卖双方的投资公司,还有新闻组织和公共部门。
StockTwits
StockTwits 是一个社交网络和微博平台,几十万投资专业人士在其中分享信息和交易想法,这些信息以 StockTwits 的形式被广泛的金融网络和社交媒体平台的大量观众所关注。这些数据可以被利用,因为它可能反映了投资者的情绪或本身驱动了交易,反过来又影响了价格。GitHub 上的参考资料包含了一篇建立在选定特征上的交易策略的链接。
RavenPack
RavenPack 分析大量不同的非结构化基于文本的数据,生成包含对投资者相关信息的结构化指标,包括情感评分。底层数据来源包括高级新闻线和监管信息,以及新闻稿和超过 19,000 个网上出版物。摩根大通测试了基于情感评分的多头主权债券和股票策略,并取得了与传统风险溢价低相关的积极结果(见参考资料)。
卫星数据
RS Metrics 成立于 2010 年,通过卫星、无人机和飞机三角测量地理空间数据,重点关注金属和商品、房地产和工业应用。该公司基于自己的高分辨率卫星提供信号、预测分析、警报和终端用户应用。使用案例包括估算针对特定连锁店或商业地产的零售流量,以及某些常见金属的生产和储存或相关生产地点的就业情况。
地理位置数据
Advan 成立于 2015 年,为对冲基金客户提供源自手机流量数据的信号,目标是美国和欧洲各个领域的 1600 个股票。该公司使用应用程序收集数据,在明确获得用户同意的情况下,在智能手机上安装地理位置代码,并使用多个通道(如 WiFi、蓝牙和蜂窝信号)跟踪位置以提高准确性。使用案例包括估算实体店位置的客流量,进而作为预测交易公司收入的模型的输入。
电子邮件收据数据
Eagle Alpha 提供了一系列服务,其中包括利用电子邮件收据的大量在线交易数据,涵盖了 5000 多个零售商,包括在 53 个产品组中分类的项目和 SKU 级交易数据。摩根大通分析了一个时间序列数据集,从 2013 年开始,涵盖了整个样本期间内始终活跃的用户群。数据集包含每个时期的总体花费、订单数量和独立买家数量。
处理替代数据
我们将通过网络爬虫说明替代数据的获取,首先针对 OpenTable 餐厅数据,然后转移到 Seeking Alpha 托管的盈利电话转录。
爬取 OpenTable 数据
替代数据的典型来源是评价网站,如 Glassdoor 或 Yelp,通过员工评论或客户评论传达内部见解。这些数据为旨在预测企业前景或直接其市值以获取交易信号的 ML 模型提供了宝贵的输入。
数据需要从 HTML 源中提取,除非有法律障碍。为了说明 Python 提供的网络爬虫工具,我们将从 OpenTable 检索有关餐厅预订的信息。此类数据可用于预测地理位置的经济活动、房地产价格或餐厅连锁收入。
使用 requests 和 BeautifulSoup 从 HTML 中提取数据
在本节中,我们将请求和解析 HTML 源代码。我们将使用 requests
库进行 超文本传输协议 (HTTP) 请求和检索 HTML 源代码,使用 BeautifulSoup
解析和提取文本内容。
然而,我们将遇到一个常见的障碍:网站可能只在初始页面加载后使用 JavaScript 请求某些信息。因此,直接的 HTTP 请求将不会成功。为了规避这种类型的保护,我们将使用一个无界面浏览器,以浏览器的方式检索网站内容:
from bs4 import BeautifulSoup import requests # set and request url; extract source code url = "https://www.opentable.com/new-york-restaurant-listings" html = requests.get(url) html.text[:500] ' <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE"/> <title>Restaurant Reservation Availability</title> <meta name="robots" content="noindex" > </meta> <link rel="shortcut icon" href="//components.otstatic.com/components/favicon/1.0.4/favicon/favicon.ico" type="image/x-icon"/><link rel="icon" href="//components.otstatic.com/components/favicon/1.0.4/favicon/favicon-16.png" sizes="16x16"/><link rel='
现在我们可以使用 BeautifulSoup
解析 HTML 内容,然后查找我们通过检查源代码获得的所有与餐厅名称相关的span
标签,即 rest-row-name-text
(请参阅 GitHub 仓库中链接的指令以检查网站源代码):
# parse raw html => soup object soup = BeautifulSoup(html.text, 'html.parser') # for each span tag, print out text => restaurant name for entry in soup.find_all(name='span', attrs={'class':'rest-row-name- text'}): print(entry.text) Wade Coves Alley Dolorem Maggio Islands ...
一旦你识别出感兴趣的页面元素,BeautifulSoup
将很容易地检索其中包含的文本。如果你想要获取每个餐厅的价格类别,你可以使用:
# get the number of dollars signs for each restaurant for entry in soup.find_all('div', {'class':'rest-row-pricing'}): price = entry.find('i').text
当你尝试获取预订数量时,然而,你只会得到一个空列表,因为该网站使用 JavaScript 代码在初始加载完成后请求此信息:
soup.find_all('div', {'class':'booking'}) []
介绍 Selenium - 使用浏览器自动化
我们将使用浏览器自动化工具 Selenium 操作一个无头 FireFox 浏览器,它将为我们解析 HTML 内容。
以下代码打开了 FireFox 浏览器:
from selenium import webdriver # create a driver called Firefox driver = webdriver.Firefox()
让我们关闭浏览器:
# close it driver.close()
要使用 selenium 和 Firefox 检索 HTML 源代码,请执行以下操作:
import time, re # visit the opentable listing page driver = webdriver.Firefox() driver.get(url) time.sleep(1) # wait 1 second # retrieve the html source html = driver.page_source html = BeautifulSoup(html, "lxml") for booking in html.find_all('div', {'class': 'booking'}): match = re.search(r'\d+', booking.text) if match: print(match.group())
建立一个餐厅预订数据集
现在,你只需要将网站中所有有趣的元素结合起来,创建一个特性,你可以将其用于模型中,以预测地理区域的经济活动或特定社区的人流量。
使用 Selenium,你可以跟随链接到下一页,并快速构建纽约市超过 10,000 家餐厅的数据集,然后定期更新以跟踪时间序列。首先,我们设置一个函数来解析我们计划爬取的页面的内容:
def parse_html(html): data, item = pd.DataFrame(), {} soup = BeautifulSoup(html, 'lxml') for i, resto in enumerate(soup.find_all('div', class_='rest-row- info')): item['name'] = resto.find('span', class_='rest-row-name- text').text booking = resto.find('div', class_='booking') item['bookings'] = re.search('\d+', booking.text).group() if booking else 'NA' rating = resto.select('div.all-stars.filled') item['rating'] = int(re.search('\d+', rating[0].get('style')).group()) if rating else 'NA' reviews = resto.find('span', class_='star-rating-text--review- text') item['reviews'] = int(re.search('\d+', reviews.text).group()) if reviews else 'NA' item['price'] = int(resto.find('div', class_='rest-row- pricing').find('i').text.count('$')) item['cuisine'] = resto.find('span', class_='rest-row-meta-- cuisine').text item['location'] = resto.find('span', class_='rest-row-meta-- location').text data[i] = pd.Series(item) return data.T
然后,我们启动一个无界面浏览器,它将继续为我们点击“下一页”按钮,并捕获每个页面显示的结果:
restaurants = pd.DataFrame() driver = webdriver.Firefox() url = "https://www.opentable.com/new-york-restaurant-listings" driver.get(url) while True: sleep(1) new_data = parse_html(driver.page_source) if new_data.empty: break restaurants = pd.concat([restaurants, new_data], ignore_index=True) print(len(restaurants)) driver.find_element_by_link_text('Next').click() driver.close()
网站仍在不断变化,因此这段代码可能在某些时候停止工作,并需要更新以跟随最新的站点导航和机器人检测。
进一步的一步 - Scrapy 和 splash
Scrapy 是一个强大的库,用于构建跟随链接、检索内容并以结构化方式存储解析结果的机器人。结合无头浏览器 splash 使用,它还可以解释 JavaScript,并成为 Selenium 的高效替代方案。你可以在 01_opentable
目录中使用 scrapy crawl opentable
命令运行蜘蛛,结果将记录在 spider.log
中:
from opentable.items import OpentableItem from scrapy import Spider from scrapy_splash import SplashRequest class OpenTableSpider(Spider): name = 'opentable' start_urls = ['https://www.opentable.com/new-york-restaurant- listings'] def start_requests(self): for url in self.start_urls: yield SplashRequest(url=url, callback=self.parse, endpoint='render.html', args={'wait': 1}, ) def parse(self, response): item = OpentableItem() for resto in response.css('div.rest-row-info'): item['name'] = resto.css('span.rest-row-name- text::text').extract() item['bookings'] = resto.css('div.booking::text').re(r'\d+') item['rating'] = resto.css('div.all- stars::attr(style)').re_first('\d+') item['reviews'] = resto.css('span.star-rating-text--review- text::text').re_first(r'\d+') item['price'] = len(resto.css('div.rest-row-pricing > i::text').re('\$')) item['cuisine'] = resto.css('span.rest-row-meta-- cuisine::text').extract() item['location'] = resto.css('span.rest-row-meta-- location::text').extract() yield item
从这些数据中提取信息的方法有很多,超出了个别餐厅或连锁店的评论和预订。
我们还可以进一步收集并对餐馆的地址进行地理编码,以将餐馆的物理位置与其他感兴趣的区域(如热门零售店或街区)联系起来,以获取有关经济活动特定方面的见解。如前所述,这样的数据结合其他信息将最有价值。
收益电话转录
文本数据是一个重要的替代数据来源。一个例子是收益电话的转录,执行人员不仅展示最新的财务结果,还回答金融分析师的问题。投资者利用这些转录来评估情绪变化、特定主题的强调或沟通风格。
我们将说明如何从流行的交易网站www.seekingalpha.com爬取和解析收益电话的转录:
import re from pathlib import Path from time import sleep from urllib.parse import urljoin from bs4 import BeautifulSoup from furl import furl from selenium import webdriver transcript_path = Path('transcripts') SA_URL = 'https://seekingalpha.com/' TRANSCRIPT = re.compile('Earnings Call Transcript') next_page = True page = 1 driver = webdriver.Firefox() while next_page: url = f'{SA_URL}/earnings/earnings-call-transcripts/{page}' driver.get(urljoin(SA_URL, url)) response = driver.page_source page += 1 soup = BeautifulSoup(response, 'lxml') links = soup.find_all(name='a', string=TRANSCRIPT) if len(links) == 0: next_page = False else: for link in links: transcript_url = link.attrs.get('href') article_url = furl(urljoin(SA_URL, transcript_url)).add({'part': 'single'}) driver.get(article_url.url) html = driver.page_source meta, participants, content = parse_html(html) meta['link'] = link driver.close()
使用正则表达式解析 HTML
为了从非结构化的转录中收集结构化数据,我们可以使用正则表达式以及 BeautifulSoup
。
它们让我们不仅可以收集有关收益电话公司和时间的详细信息,还可以记录谁在场,并将声明归因于分析师和公司代表:
def parse_html(html): date_pattern = re.compile(r'(\d{2})-(\d{2})-(\d{2})') quarter_pattern = re.compile(r'(\bQ\d\b)') soup = BeautifulSoup(html, 'lxml') meta, participants, content = {}, [], [] h1 = soup.find('h1', itemprop='headline').text meta['company'] = h1[:h1.find('(')].strip() meta['symbol'] = h1[h1.find('(') + 1:h1.find(')')] title = soup.find('div', class_='title').text match = date_pattern.search(title) if match: m, d, y = match.groups() meta['month'] = int(m) meta['day'] = int(d) meta['year'] = int(y) match = quarter_pattern.search(title) if match: meta['quarter'] = match.group(0) qa = 0 speaker_types = ['Executives', 'Analysts'] for header in [p.parent for p in soup.find_all('strong')]: text = header.text.strip() if text.lower().startswith('copyright'): continue elif text.lower().startswith('question-and'): qa = 1 continue elif any([type in text for type in speaker_types]): for participant in header.find_next_siblings('p'): if participant.find('strong'): break else: participants.append([text, participant.text]) else: p = [] for participant in header.find_next_siblings('p'): if participant.find('strong'): break else: p.append(participant.text) content.append([header.text, qa, '\n'.join(p)]) return meta, participants, content
我们将结果存储在几个 .csv
文件中,以便在使用 ML 处理自然语言时轻松访问:
def store_result(meta, participants, content): path = transcript_path / 'parsed' / meta['symbol'] if not path.exists(): path.mkdir(parents=True, exist_ok=True) pd.DataFrame(content, columns=['speaker', 'q&a', 'content']).to_csv(path / 'content.csv', index=False) pd.DataFrame(participants, columns=['type', 'name']).to_csv(path / 'participants.csv', index=False) pd.Series(meta).to_csv(path / 'earnings.csv'
在 GitHub 仓库中的 README
中查看其他详细信息和引用,以获取进一步开发网络爬虫应用程序的资源。
总结
在本章中,我们介绍了作为大数据革命结果而可用的新型替代数据来源,包括个人、业务流程和传感器,例如卫星或 GPS 位置设备。我们提出了一个框架来从投资的角度评估替代数据集,并列出了帮助您导航这个提供关键输入的庞大且迅速扩张的领域的关键类别和提供者,这些输入用于使用 ML 的算法交易策略。
我们探索了强大的 Python 工具,以大规模收集您自己的数据集,这样您就有可能通过网络爬取来获得您的私人信息优势,成为一个算法交易员。
我们现在将在接下来的章节中进行设计和评估产生交易信号的 alpha 因子,并研究如何在投资组合背景下将它们结合起来。