天翼云电脑上实践纯Python通过LMDB加速股票行情读写速度
前言
**量化交易,行情先行!**对于量化交易,行情数据很重要,可以说很关键。策略再优秀,行情有问题,跑出的结果那就千差万别了。如果误触发,亏了钱连说理的地方都找不到。
**行情要准,但也要快!**获取行情数据,因为网络时延、本地IO开销、电脑速度等原因,会大大增加开销。就算你1000兆光纤入户,但这个只是下行网速,不代表响应时延就能比交易所附近的10M专线快小。本地IO开销,M.2 NVMe固态盘目前是个人配置的最优方案。电脑速度那就因人而异了,但获取网络上的行情数据,CPU再好,对提速帮助不是很大。
那么,如何实现对行情数据的极速、稳定的访问呢?
笔者之前一直使用Mysql,后来也试过MongoDB,还曾甩开数据库,直接用csv文件读写,但速度都还不够快!直到后来学习到可以将数据缓存到内存里,那速度可真的快得不要不要滴!redis内存数据库的访问速度比mysql快了不止一个等量级,速度胜过各种数据库(没有了IO的开销,直接在内存里读写)。但是本地电脑受网速的影响,甚至偶尔停电的影响,总归不是最佳解决方案。
直到我发现了天翼云电脑,4核8G,80G硬盘,一年才558。关键是网络稳定,24小时在线,更不用担心停电的问题。但这个配置再装redis就比较坑了,那么有没有一种既可以享受内存数据库的快速,又不用太消耗资源的办法呢?
答案是:有!
废话不多说,上方案。
一、LMDB 是什么?
LMDB 全称为 Lightning Memory-Mapped Database,就是非常快的内存映射型数据库,是一个key-value数据库(键值型数据库,同redis)。
redis之所以快首先就是得益于其内存读取,免去了磁盘的IO消耗。
LMDB效率高的一个关键原因是它是基于内存映射的,这意味着它返回指向键和值的内存地址的指针,而不需要像大多数其他数据库那样复制内存中的任何内容。
二、LMDB 怎么用?
1. 准备工作
读写行情,离不开pandas包,这个怎么安装自己百度,下面才是我们的主角。使用如下语句安装lmdb包,通过一个包加载一个文件就可以将本地数据转移到内存中读写,免去了redis安装的繁琐。
pip install lmdb
2. 基本常识
LMDB属于key-value数据库,而不是关系型数据库( 比如 MySQL ),LMDB提供 key-value 存储,其中每个键值对都可以存一只股票行情。LMDB的主要作用是提供数据管理,可以将各种各样的原始数据转换为统一的key-value存储。
2.1 LMDB 的基本函数
env = lmdb.open() # 创建 lmdb 环境
txn = env.begin() # 建立事务
txn.put(key, value) # 进行插入和修改
txn.delete(key) # 进行删除
txn.get(key) # 通过查询value
txn.cursor() # 整个db进行遍历
txn.commit() # 提交更改
env.close() # 关闭环境
2.2 重点解释
env = lmdb.open(lmdb_path, map_size=1099511627776)
lmdb_path 指定存放生成的lmdb数据库的文件夹路径,如果没有该文件夹则自动创建。map_size 指定创建的新数据库所需磁盘空间的最小值,1099511627776B=1T。可以在这里进行 存储单位换算。
创建环境会在指定路径下创建 data.mdb 和 lock.mdb 两个文件,一是个数据文件,一个是锁文件。
先创建一个事务(transaction) 对象 txn,所有的操作都必须经过这个事务对象。因为我们要对数据库进行写入操作,所以将 write 参数置为 True,默认其为 False。
使用 .put(key, value) 对数据库进行插入和修改操作,传入的参数为键值对。
值得注意的是,需要在键值字符串后加 .encode() 改变其编码格式,将 str 转换为 bytes 格式,否则会报该错误:TypeError: Won’t implicitly convert Unicode to bytes; use .encode()。在后面使用 .decode() 对其进行解码得到原数据。
使用 .delete(key) 删除指定键值对。
对LMDB的读写操作在事务中执行,需要使用 commit 方法提交待处理的事务,如果不提交存在数据保存不成功的情况。
每次 commit() 之后都要用 env.begin() 更新 txn(得到最新的lmdb数据库)。
3. 获取行情并写入lmdb(代码展示)
# -*- coding: utf-8 -*-
import lmdb # 安装:pip install lmdb
import pickle
def lmdb_put(key,value):
# 添加数据和键值
txn.put(key = key.encode(), value = value)
def lmdb_get(key):
# get函数通过键值查询数据
res = txn.get(key.encode())
return res
if __name__ == '__main__':
# 下载Ashare.py、KTstock放到和这个文件相同目录下
from Ashare import get_price
# 获取股票代码
from KTstock import get_stocklist_dfcfw # KTstock详见文章http://t.csdn.cn/59WUP
res = get_stocklist_dfcfw()
if res['success']:
df = res['df_data']
# 剔除交易量为0的股票
df = df[df['volume']>0]
# # 只筛选特定代码的数据
# lists = ['000001','300750','600259']
# if len(lists)>0:
# df = df[df['code'].isin(lists)]
code_list = df['code'].tolist()
print(code_list)
import time
# 计时开始
time1 = time.time()
if 1:
# 建立lmdb环境
env = lmdb.open("./stock", map_size=int(1e9))
# 参数write设置为True才可以写入
txn = env.begin(write=True)
# 遍历并lmdb数据库
count = 0
for key, value in txn.cursor():
count+=1
print(count,'lmdb现有键',key)
if 1:
# 读取键值
df_bytes_from_lmdb = txn.get(key)
if not df_bytes_from_lmdb is None:
df = pickle.loads(df_bytes_from_lmdb)
print('提取lmdb现有键值到df',df.tail(5))
if 1:
# 删除键值
txn.delete(key)
print('lmdb现有键',key,'已删除')
txn.commit()
time2 = time.time()
print("Python遍历lmdb耗时:",time2-time1,'秒')
if 1:
# 建立lmdb环境
env = lmdb.open("./stock", map_size=int(1e9))
# 参数write设置为True才可以写入
txn = env.begin(write=True)
# 提取行情并写入lmdb
for code in code_list:
if code[0] == "6" or code[0] == "9": #上证股票
code = "sh"+code
if code[0] == "0" or code[0] == "3" or code[0] == "2": #深证股票
code = "sz"+code
if code[0] == "4" or code[0] == "8": #北证股票
code = "bj"+code
print('通过Ashare获取股票('+code+')行情...')
try:
df_data=get_price(code,frequency='1d',count=1000) #支持'1d'日, '1w'周, '1M'月
except Exception as e:
print('获取股票('+code+')行情失败!')
else:
df_bytes = pickle.dumps(df)
lmdb_put(code, df_bytes)
time3 = time.time()
print("Python写入lmdb耗时:",time3-time2,'秒')
# 关闭lmdb环境
env.close()
# 计时结束
time_end = time.time()
print("Python读取lmdb总耗时:",time_end-time1,'秒')
这里为方便演示使用单文件的Ashare,但经常获取数据会失败,所有增加了try语句保护运行完整。获取行情数据更多方法请参考:
【数据知多少】一文学懂通过Tushare、AKshare、baostock、Ashare、Pytdx获取股票行情数据(含代码)https://blog.csdn.net/popboy29/article/details/125815775
总结
笔者使用自制采集方法,并写入和便利lmdb,结果如下:
Python采集所有股票(D)并写入到lmdb耗时: 480.66940474510193 秒
Python遍历读取lmdb共耗时: 4.877945423126221 秒
注:写入因为包含采集时间所以长一些,但读取非常快,不进行任何计算,不到5秒就可以遍历5000多支股票。
至此,使用LMDB存取股票行情已展示完毕,更多玩法各位自行探索。有好消息或疑问请评论区留言,我看到会回复大家。
附:常见错误及解决办法
-
错误提示:lmdb.MapFullError: mdb_put: MDB_MAP_FULL: Environment mapsize limit reached
解决方法: lmdb.open(“./stock”, map_size=int(1e9) -
错误提示:TypeError: Won’t implicitly convert Unicode to bytes; use .encode()
解决方法: TypeError:不会隐式地将Unicode转换为字节,对字符串部分,进行.encode()