我如何使用 Google Cloud Platform 开始投资股票
我在参加朋友推荐的一次简短讲座后,对投资产生了兴趣。我决定做一些研究,并开始阅读乔尔·格林布拉特(Joel Greenblatt)的《依然战胜市场的小书》(The Little Book That Still Beats The Market)。从书中,我发现了一些公式,在决定是否投资新加坡公司的股票时,它们或许能帮到我。这篇文章并非为了推广这本书或其投资策略,而是为了展示以下内容以及我是如何做到的:
- 通过 Python 与 Firestore 交互
- 在 Compute Engine 上以特定时间间隔运行 Python 脚本
- 使用 Cloud Functions 从 Firestore 检索数据
起初,我编写了一个 Python 脚本,用于将新加坡交易所网站上上市公司的财务详情和自行计算的比率填充到 Google Sheet中。我发现这很麻烦,因为我必须每天运行这个 Python 脚本来获取股票的最新价格。后来,我决定将这个日常流程迁移到 Google Cloud Platform,这样我就不用再亲自处理这些日常流程了,只需交给云端来帮我处理 :D
下面将解释我如何做我所做的事情,希望能帮助那些可能想要以与我类似的方式使用 Google Cloud Platform 的人。
先决条件
在继续之前,我想提醒大家,为了保持文章简洁,请先完成以下事项。我已附上一些入门链接。
概述
从上图可以看出,我唯一需要做的就是通过 Cloud Functions HTTP API 发出 GET 请求,该请求将返回 Firestore 中存储的所有已计算的公式和值。本质上,步骤 1、2 和 3 涉及我创建的 Python 脚本。步骤 1 和 2 只需使用Requests 库即可完成。
通过 Python 与 Firestore 交互
Firestore 使用集合、文档和字段的概念来存储所需的数据。例如,以图书馆为例,如果您有一架书,那么在 Firestore 看来,这就是一个集合。书籍本身就是文档,书中的每一页都是一个独立的字段。每个文档也可以有自己的集合,但我不会深入探讨这一点。
shelf [collection]
|--book1 [document]
|-- page1 [field]
|-- page2 [field]
|--book2 [document]
|-- page1 [field]
要通过 Python 脚本与 Cloud Firestore 进行交互并更新数据,首先必须通过 安装 Google Cloud Firestore 库pip install google-cloud-firestore
。以下是使用您之前检索到的服务帐户密钥初始化 Firestore 的代码片段。
from google.cloud import firestore
db = firestore.Client.from_service_account_json('/path/to/service/key')
嗯,就是这样!要将数据写入 Firestore,只需执行以下操作:
doc_ref = db.collection(u'name_of_collection').document(u'name_of_document')
doc_ref.set(data_to_update)
data_to_update
是一个 Python 字典,它包含您希望 Firestore 文档保存的键和相应的值。它.set()
允许您更新文档或向文档中插入新字段。就我个人而言,我在这里保存了公司名称、股票价格、财务比率和其他字段。
这里要注意的一点是,即使文档或集合尚不存在,.set()
函数也会自动为您创建集合和文档,并使用前面提到的字段填充文档。
在 Compute Engine 上运行 Python 脚本
有几种方法可以将 Python 脚本推送到虚拟机实例。我的做法是在我的 Google Cloud 项目中创建一个代码库,并将其推送到那里。我创建代码库的原因是我仍然需要某种形式的版本控制,因为我了解自己,喜欢在代码中进行修改并探索不同的实现方式,最终让自己感到困惑。虽然这是一个小项目,但我觉得这对我个人来说是一个很好的做法。然后,我通过 SSH 远程访问了虚拟机实例,并将代码库克隆到实例中。
现在来谈谈 Python 脚本的调度。起初,我认为每 30 分钟调用一次 Python 脚本是个好主意。然而,经过一番考虑,我觉得将脚本调度到下午 6 点(GMT +0800)运行是理想的情况,因为新加坡交易所上午 9 点开市,下午 5 点关市,而我实际上只有下班后才有时间查看股票价格。
要安排 Python 脚本以特定时间间隔或特定时间运行,你可以像我一样使用 Cron 作业。在虚拟机实例的 SSH 会话中,使用以下crontab -e
命令编辑用户的 Crontab。在文件末尾,使用以下格式的计划
# m h dom mon dow command
0 10 * * 1-5 cd /path/to/python/folder && python main.py
上述代码片段会在每个工作日的 UTC 时间上午 10 点(即新加坡时间下午 6 点)运行 Python 脚本,具体时间由时间1-5
段指定。如果您希望脚本在每个时间间隔后运行,可以执行以下操作:
# Runs the command every hour at the 0th minute
0 */1 * * * <some command>
# Runs the command at the 0th minute every day
0 * */1 * * <some command>
注意:我在 VM 实例中最初几次使用 Crontab 时犯了一个错误,如下所示:
# Runs the command every minute after every hour
* */1 * * * <some command>
我本来打算每小时运行一次,但我错过了0
定时任务的分钟标记。所以它每小时过后的每一分钟都会运行脚本。我的脚本每次调用大约需要 3 分钟。我并不介意相对较长的运行时间。但是,由于脚本每分钟运行一次,每次运行都需要 3 分钟……好吧,你可以算一下。我当时傻乎乎的,还试图弄清楚为什么我的虚拟机实例的 CPU 使用率一直在 150-200% 左右,我甚至无法通过 SSH 访问它。这真是一个有趣的教训 :P
使用 Cloud Functions 从 Firestore 检索数据
在这一步,我将 Google Cloud 项目关联到了 Firebase。这样做是为了将来的版本能够在 Firebase Hosting 上托管一个网站,该网站可以利用 Cloud Firestore 的数据,让任何人都可以一目了然地查看财务详情。另一个原因是,我对 Firebase 以及 Cloud Functions 的要求更加熟悉。
我通过 将 Express.js 安装到我的 Cloud Functions 文件夹中npm install --save express
。Express.js 允许我轻松创建 Web API,因为我需要多个端点来从我拥有的 Firestore 中检索各种公司信息。
var db = admin.firestore();
const express = require("express");
const app = express();
app.get('/:nameOfDocument',( req, res)=>{
const nameOfDocument = req.params.nameOfDocument;
var firestoreRef = db.collection("name_of_collection").doc(nameOfDocument);
res.setHeader('Content-Type', 'application/json');
firestoreRef.get().then((snapshot) => {
if (snapshot.exists) {
var returnObj = snapshot.data();
return res.status(200).json(returnObj);
}
else {
return res.status(422).json({error:"Invalid document name"});
}
}).catch(errorObject => {
return res.status(500).json({error:"Internal Server Error"});
});
})
exports.api = functions.https.onRequest(app);
以下是对上述代码片段的逐步解释。首先,通过 初始化对 Firestore 的访问var db = admin.firestore();
。
app.get('/:nameOfDocument',( req, res)=>{
...
}
上述代码告诉 Express,我们希望创建一个包含'/:nameOfDocument'
端点的 GET 请求,其中:nameOfDocument
是 URL 中的一个参数。req
和res
分别是已接收和即将发送的请求对象和响应对象。目前,只res
使用了 ,稍后会详细介绍。
const nameOfDocument = req.params.nameOfDocument;
此行从 URL 中获取参数(:nameOfDocument
在本例中为),并将其存储为名为的变量nameOfDocument
,该变量将在下一行中使用。
var firestoreRef = db.collection("name_of_collection").doc(nameOfDocument);
这行代码实际上创建了对 document 的引用nameOfDocument
。集合名称目前不是变量。您也可以使用 include 集合名称作为参数,如下所示:
app.get('/:nameOfCollection/:nameOfDocument',( req, res)=>{
const nameOfDocument = req.params.nameOfDocument;
const nameOfCollection= req.params.nameOfCollection;
var firestoreRef = db.collection(nameOfCollection).doc(nameOfDocument);
...
}
这样,您可以在 URL 中指定它,而无需更改代码。
firestoreRef.get().then((snapshot) => {
if (snapshot.exists) {
var returnObj = snapshot.data();
return res.status(200).json(returnObj);
}
...
}
上述代码段获取前面提到的引用并检查其是否存在。这至关重要,因为用户可能会意外输入错误的文档或集合名称,而我们希望返回正确的响应。snapshot.data()
检索所有字段键值对,并将其放入名为 的对象中。returnObj
然后,我们将其作为 JSON 对象返回,状态码为 200。
exports.api = functions.https.onRequest(app);
此行告诉 Cloud Functions,当发出请求时,<cloudfunctions.net url>/api
应将其传递给 Express 对象,app
并根据对象本身指定的端点进行相应的调用和处理app
。
就是这样!现在,您可以从 Firebase Cloud Functions 页面上提供的链接调用您的 Cloud Functions,它将从您的 Firestore 中检索您想要处理的相关数据。
附言:这是我的第一篇教程/个人经验帖。请告诉我哪些地方可以改进,以及如何成为一名更好的程序员。欢迎所有建设性的反馈。感谢您阅读我的文章!:D
文章来源:https://dev.to/dansyuqri/how-i-used-google-cloud-platform-to-start-investing-in-stocks-34mb