【给量化行情插上翅膀】天翼云电脑上实践纯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存取股票行情已展示完毕,更多玩法各位自行探索。有好消息或疑问请评论区留言,我看到会回复大家。

附:常见错误及解决办法

  1. 错误提示:lmdb.MapFullError: mdb_put: MDB_MAP_FULL: Environment mapsize limit reached
    解决方法: lmdb.open(“./stock”, map_size=int(1e9)

  2. 错误提示:TypeError: Won’t implicitly convert Unicode to bytes; use .encode()
    解决方法: TypeError:不会隐式地将Unicode转换为字节,对字符串部分,进行.encode()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT里的交易员

分享是一种快乐,打赏是一种肯定

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值