前言
**量化交易,行情先行!**對于量化交易,行情數(shù)據(jù)很重要,可以說很關(guān)鍵。策略再優(yōu)秀,行情有問題,跑出的結(jié)果那就千差萬別了。如果誤觸發(fā),虧了錢連說理的地方都找不到。
**行情要準,但也要快!**獲取行情數(shù)據(jù),因為網(wǎng)絡(luò)時延、本地IO開銷、電腦速度等原因,會大大增加開銷。就算你1000兆光纖入戶,但這個只是下行網(wǎng)速,不代表響應(yīng)時延就能比交易所附近的10M專線快小。本地IO開銷,M.2 NVMe固態(tài)盤目前是個人配置的最優(yōu)方案。電腦速度那就因人而異了,但獲取網(wǎng)絡(luò)上的行情數(shù)據(jù),CPU再好,對提速幫助不是很大。
那么,如何實現(xiàn)對行情數(shù)據(jù)的極速、穩(wěn)定的訪問呢?
筆者之前一直使用Mysql,后來也試過MongoDB,還曾甩開數(shù)據(jù)庫,直接用csv文件讀寫,但速度都還不夠快!直到后來學(xué)習(xí)到可以將數(shù)據(jù)緩存到內(nèi)存里,那速度可真的快得不要不要滴!redis內(nèi)存數(shù)據(jù)庫的訪問速度比mysql快了不止一個等量級,速度勝過各種數(shù)據(jù)庫(沒有了IO的開銷,直接在內(nèi)存里讀寫)。但是本地電腦受網(wǎng)速的影響,甚至偶爾停電的影響,總歸不是最佳解決方案。
直到我發(fā)現(xiàn)了天翼云電腦,4核8G,80G硬盤,一年才558。關(guān)鍵是網(wǎng)絡(luò)穩(wěn)定,24小時在線,更不用擔(dān)心停電的問題。但這個配置再裝redis就比較坑了,那么有沒有一種既可以享受內(nèi)存數(shù)據(jù)庫的快速,又不用太消耗資源的辦法呢?
答案是:有!
廢話不多說,上方案。
一、LMDB 是什么?
LMDB 全稱為 Lightning Memory-Mapped Database,就是非??斓膬?nèi)存映射型數(shù)據(jù)庫,是一個key-value數(shù)據(jù)庫(鍵值型數(shù)據(jù)庫,同redis)。
redis之所以快首先就是得益于其內(nèi)存讀取,免去了磁盤的IO消耗。
LMDB效率高的一個關(guān)鍵原因是它是基于內(nèi)存映射的,這意味著它返回指向鍵和值的內(nèi)存地址的指針,而不需要像大多數(shù)其他數(shù)據(jù)庫那樣復(fù)制內(nèi)存中的任何內(nèi)容。
二、LMDB 怎么用?
1. 準備工作
讀寫行情,離不開pandas包,這個怎么安裝自己百度,下面才是我們的主角。使用如下語句安裝lmdb包,通過一個包加載一個文件就可以將本地數(shù)據(jù)轉(zhuǎn)移到內(nèi)存中讀寫,免去了redis安裝的繁瑣。
pip install lmdb
2. 基本常識
LMDB屬于key-value數(shù)據(jù)庫,而不是關(guān)系型數(shù)據(jù)庫( 比如 MySQL ),LMDB提供 key-value 存儲,其中每個鍵值對都可以存一只股票行情。LMDB的主要作用是提供數(shù)據(jù)管理,可以將各種各樣的原始數(shù)據(jù)轉(zhuǎn)換為統(tǒng)一的key-value存儲。
2.1 LMDB 的基本函數(shù)
env = lmdb.open() # 創(chuàng)建 lmdb 環(huán)境
txn = env.begin() # 建立事務(wù)
txn.put(key, value) # 進行插入和修改
txn.delete(key) # 進行刪除
txn.get(key) # 通過查詢value
txn.cursor() # 整個db進行遍歷
txn.commit() # 提交更改
env.close() # 關(guān)閉環(huán)境
2.2 重點解釋
env = lmdb.open(lmdb_path, map_size=1099511627776) lmdb_path 指定存放生成的lmdb數(shù)據(jù)庫的文件夾路徑,如果沒有該文件夾則自動創(chuàng)建。map_size 指定創(chuàng)建的新數(shù)據(jù)庫所需磁盤空間的最小值,1099511627776B=1T??梢栽谶@里進行 存儲單位換算。 創(chuàng)建環(huán)境會在指定路徑下創(chuàng)建 data.mdb 和 lock.mdb 兩個文件,一是個數(shù)據(jù)文件,一個是鎖文件。
先創(chuàng)建一個事務(wù)(transaction) 對象 txn,所有的操作都必須經(jīng)過這個事務(wù)對象。因為我們要對數(shù)據(jù)庫進行寫入操作,所以將 write 參數(shù)置為 True,默認其為 False。
使用 .put(key, value) 對數(shù)據(jù)庫進行插入和修改操作,傳入的參數(shù)為鍵值對。
值得注意的是,需要在鍵值字符串后加 .encode() 改變其編碼格式,將 str 轉(zhuǎn)換為 bytes 格式,否則會報該錯誤:TypeError: Won’t implicitly convert Unicode to bytes; use .encode()。在后面使用 .decode() 對其進行解碼得到原數(shù)據(jù)。
使用 .delete(key) 刪除指定鍵值對。
對LMDB的讀寫操作在事務(wù)中執(zhí)行,需要使用 commit 方法提交待處理的事務(wù),如果不提交存在數(shù)據(jù)保存不成功的情況。
每次 commit() 之后都要用 env.begin() 更新 txn(得到最新的lmdb數(shù)據(jù)庫)。
3. 獲取行情并寫入lmdb(代碼展示)
# -*- coding: utf-8 -*-
import lmdb # 安裝:pip install lmdb
import pickle
def lmdb_put(key,value):
# 添加數(shù)據(jù)和鍵值
txn.put(key = key.encode(), value = value)
def lmdb_get(key):
# get函數(shù)通過鍵值查詢數(shù)據(jù)
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./59WUP
res = get_stocklist_dfcfw()
if res['success']:
df = res['df_data']
# 剔除交易量為0的股票
df = df[df['volume']>0]
# # 只篩選特定代碼的數(shù)據(jù)
# 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環(huán)境
env = lmdb.open('./stock', map_size=int(1e9))
# 參數(shù)write設(shè)置為True才可以寫入
txn = env.begin(write=True)
# 遍歷并lmdb數(shù)據(jù)庫
count = 0
for key, value in txn.cursor():
count+=1
print(count,'lmdb現(xiàn)有鍵',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現(xiàn)有鍵值到df',df.tail(5))
if 1:
# 刪除鍵值
txn.delete(key)
print('lmdb現(xiàn)有鍵',key,'已刪除')
txn.commit()
time2 = time.time()
print('Python遍歷lmdb耗時:',time2-time1,'秒')
if 1:
# 建立lmdb環(huán)境
env = lmdb.open('./stock', map_size=int(1e9))
# 參數(shù)write設(shè)置為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,'秒')
# 關(guān)閉lmdb環(huán)境
env.close()
# 計時結(jié)束
time_end = time.time()
print('Python讀取lmdb總耗時:',time_end-time1,'秒')
這里為方便演示使用單文件的Ashare,但經(jīng)常獲取數(shù)據(jù)會失敗,所有增加了try語句保護運行完整。獲取行情數(shù)據(jù)更多方法請參考: 【數(shù)據(jù)知多少】一文學(xué)懂通過Tushare、AKshare、baostock、Ashare、Pytdx獲取股票行情數(shù)據(jù)(含代碼)https://blog.csdn.net/popboy29/article/details/125815775
總結(jié)
筆者使用自制采集方法,并寫入和便利lmdb,結(jié)果如下: Python采集所有股票(D)并寫入到lmdb耗時: 480.66940474510193 秒 Python遍歷讀取lmdb共耗時: 4.877945423126221 秒
注:寫入因為包含采集時間所以長一些,但讀取非???,不進行任何計算,不到5秒就可以遍歷5000多支股票。
至此,使用LMDB存取股票行情已展示完畢,更多玩法各位自行探索。有好消息或疑問請評論區(qū)留言,我看到會回復(fù)大家。
附:常見錯誤及解決辦法
-
錯誤提示: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轉(zhuǎn)換為字節(jié),對字符串部分,進行.encode()
|