背景
最近在用 QMT 写量化策略,肯定需要获取一些行情数据。其中 AKShare(免费)和 TuShare 是最常用的 Python 库,但是在使用的时候碰到了一个比较棘手的问题。
QMT 的默认 Python 版本是 v3.6.8,但是 AKShare 要求要 v3.8 以上,这就有点难办了。
思考了一阵,最终选择用云函数(阿里云的 FC)封装一个 HTTP API 来解决这个问题。
实现 AKShare Server
1. 创建云函数
如下图,创建一个 Python 3.10 环境,「处理 HTTP 请求」模板的云函数 akshare_server。这里没什么好说的,直接下一步。
2. 服务端实现
- 先安装依赖,
ctrl/cmd + ~打开控制台,输入pip install akshare -t .; - 实现服务端代码,部署,测试。具体代码见后文;
- 修改「HTTP 触发器」只留
POST方法; - 给 FC 加上自定义域名,比如
<your_domain>/akshare。这个就不展开了,自定义域名是使用云服务必备的;
# -*- coding: utf-8 -*-
import json
import base64
import akshare as ak
import pandas as pd
import io
def handler(event, context):
try:
event_json = json.loads(event)
except:
return "The request did not come from an HTTP Trigger because the event is not a json string, event: {}".format(event)
payload = get_payload(event_json)
print(payload)
api = payload['api']
try:
api_function = getattr(ak, api)
except AttributeError:
return f"API '{api}' not found in AkShare."
args = payload['args']
if args:
args_dict = args
else:
args_dict = {}
try:
df = api_function(**args_dict)
csv_buffer = io.StringIO()
df.to_csv(csv_buffer, index=False)
csv_buffer.seek(0)
return {
'statusCode': 200,
'headers': {'Content-Type': 'text/plain'},
'isBase64Encoded': False,
'body': csv_buffer.getvalue()
}
except Exception as e:
return f"Error calling API '{api}': {str(e)}"
def get_payload(event_obj):
# http
if 'requestContext' in event_obj:
if 'body' in event_obj:
obj_str = event_obj['body']
if event_obj.get('isBase64Encoded', False):
obj_str = base64.b64decode(obj_str).decode('utf-8')
return json.loads(obj_str)
# normal
return event_obj
下面对这个 API 做几点说明:
- 入参为
api: str和args: json,分别对应 AKShare 的方法名和所需参数,比如获取「沪深 300」的市盈率:
{
"api": "stock_index_pe_lg",
"args": { "symbol": "沪深300" }
}
- 因为大多数接口的返回数据都是 pandas 的 DataFrame 数据(不确定),所以统一处理成了 csv 字符串,使用的时候需要自行处理一下,下文有客户端的代码。
日期 指数 等权静态市盈率 静态市盈率 静态市盈率中位数 等权滚动市盈率 滚动市盈率 滚动市盈率中位数
0 2005-04-08 1003.45 33.19 15.68 21.17 30.39 14.98 20.65
1 2005-04-11 995.42 33.43 15.76 21.40 29.77 15.12 20.65
3 2005-04-13 1000.90 32.99 15.59 21.45 29.53 15.12 20.78
4 2005-04-14 986.98 32.23 15.42 21.14 28.95 14.99 20.56
... ... ... ... ... ... ... ... ...
4742 2024-10-16 3831.59 31.44 12.40 19.91 27.90 12.21 20.28
4743 2024-10-17 3788.22 31.23 12.27 19.75 27.77 12.07 19.84
4744 2024-10-18 3925.23 31.90 12.61 20.43 29.19 12.40 20.50
4745 2024-10-21 3935.20 32.16 12.59 20.21 29.46 12.36 20.49
4746 2024-10-22 3957.78 32.25 12.63 20.44 29.49 12.32 21.07
3. 客户端实现
上文说了,返回值都是 csv 字符串,所以我们用的时候再用 pandas 把它还原就行了。服务端转成 csv,客户端又转回来,好像有点折腾,不过这也是封装成 HTTP Server 增加适应性的代价。
另外,要注意 utf8 的编码,否则中文可能会出乱码。
def fetch_ak_api(api, args=None, url=AK_URL):
try:
response = requests.post(url, json={ "api": api, "args": args })
response_content = response.content.decode('utf-8')
csv_data = io.StringIO(response_content)
return pd.read_csv(csv_data)
except Exception:
return pd.DataFrame()
# USEAGE:
# df = fetch_ak_api("stock_index_pe_lg", {"symbol": "沪深300"})
# pe = df.loc[df['日期'] == '2024-10-22', '静态市盈率'].values[0]
实现 TuShare Server
步骤跟 AKShare Server 完全一样,只是服务端代码略有不同,改动如下:
api = payload['api']
ts.set_token(os.environ.get('TOKEN'))
pro = ts.pro_api()
try:
api_function = getattr(pro, api)
except AttributeError:
return f"API '{api}' not found in TuShare."
注意,TuShare 的调用是需要 TOKEN 的,有些接口还得充钱升级才能调用,所以如果接口调用不成功,那么很有可能是你的 TOKEN 权限不够。
另外,为了安全起见,还是建议把 TOKEN 放到环境变量里,具体如何做这里就不展开了。测试用的参数可以用下面的:
{
"api": "index_dailybasic",
"args": {
"ts_code": "000300.SH",
"trade_date": "20241022"
}
}
总结
把 AKShare/TuShare 封装成云函数有几个好处:
- 可以解决跟 QMT Python 版本冲突的问题;
- 在 QMT 环境下不需要安装
akshare或tushare的依赖; - 封装成 HTTP API 后,所有语言都可以调用;
- 可以降低 TOKEN 等敏感数据的泄露风险。
当然,缺点就是调用可能会慢一点。针对这个问题,我的解决方法是:
- 实盘的时候,尽量在
init或after_init的时候只获取一次数据,然后存到全局变量里,之后都用全局变量访问; - 回测的时候,先提前下载好数据,在本地存成 csv 文件,这样速度就能快不少。
至于云函数调用产生的费用,几乎可以忽略不计。
总之,个人认为这个方法还是挺好用的,供大家参考。