從頭開始建構金融數據時間序列數據庫
我的公司正在啟動一項旨在從頭開始建構財務數據庫的新計劃。
我們將通過以下方式使用它:
- 時間序列分析:公司的財務數據(例如:IBM 的固定資產總額隨時間變化)、聚合(例如:材料部門的固定資產總額隨時間變化)等。
- 單個公司快照:單個公司的各種數據點
- 在單個時間範圍內(通常是當天)跨多個數據欄位分析多家公司。
- 想法和自定義因素的回測、排名分析、數據分析等。
數據的大致範圍:
- 3000家公司
- 3500 個數據欄位(例如:固定資產總額、收益等)
- 500 個聚合級別
- 週期:每天、每月、每季度、每年
- 20 年回顧,隨著時間的推移而增長
問題:
- **我們應該選擇什麼數據庫?**我們目前僅限於免費選項,我們更喜歡開源(原則上)。目前我們使用 PostgreSQL。
- **我應該如何建構這個架構?**我正在考慮將欄位類型分為幾類(資產負債表、描述性、損益表、自定義計算等),這樣每家公司都會有一個用於資產負債表、描述性、損益表、自定義計算等的表格,每行代表列/欄位的表類別的一天和適當的欄位。那將是我完全規範化的數據庫。使用完全規範化的數據庫,然後我將建構一個未完全規範化的數據倉庫、臨時表、視圖等,以便對前面描述的各種案例進行快速查詢。這種方法的一個問題是表的數量。例如,如果我有 5 類公司數據和 3000 家公司,我將在完全規範化的數據庫中擁有 15,000 個表,僅用於儲存公司數據。但是,在我看來,
- **索引和建構時間序列部分的最佳策略是什麼?**我和幾個人談過,我對時間序列數據庫索引/結構做了一些研究,但是幫助/參考/提示/等等。在這方面,即使他們複製了我的發現,也會有所幫助。我意識到這取決於上面#1 的答案,所以也許假設我將繼續使用 PostgreSQL,我將自己建構“時間序列”功能特定的花里胡哨。
筆記:
- 深入的技術答案和參考/連結更受歡迎。
- 這是針對小型買方金融投資公司的。
- 如果您以前曾走過這條路,歡迎提出超出我最初問題範圍的建議。
- 我們不能在數據量上妥協,所以減少數據量不是我們的選擇;但是,我提供的數字只是估計值。
- 如果有更好的地方問這個問題,請告訴我。
- 我們想做的事情還有很多,但從資料結構的角度來看,這代表了我們想做的事情的核心。
我將推荐一些我毫不懷疑會讓人們完全振作起來並可能讓人們攻擊我的東西。它發生在過去,當人們對我的回答投反對票時,我在 StackOverflow 上丟了很多分。我當然希望人們在量化論壇上更加開放。
注意 - 這個建議似乎再次引起了一些強烈的分歧。在您閱讀本文之前,我想指出,此建議適用於“小型買方公司”,而不是大型多使用者系統。
我花了 7 年時間管理高頻交易操作,我們的主要重點是建構這樣的系統。我們花費了大量時間試圖找出最有效的方法來儲存、檢索和分析來自 NYSE、納斯達克和各種 ECN 的訂單級別數據。我給你的是那項工作的結果。
我們的答案是不要使用數據庫。序列化數據塊的基本結構化文件系統工作得更好。市場時間序列數據在許多方面都是獨一無二的,無論是使用方式還是儲存方式。數據庫是為完全不同的需求而開發的,實際上會損害您正在嘗試做的事情的性能。
這是在中小型交易操作的背景下,該操作專注於與交易策略或風險分析相關的數據分析。如果您正在為大型經紀公司、銀行創建解決方案,或者必須同時滿足大量客戶的需求,那麼我想您的解決方案會與我的不同。
我碰巧喜歡數據庫。我現在在一個新項目中使用 MongoDB 來分析期權交易,但我的市場時間序列數據,包括 16 年的期權數據,都內置在結構化文件儲存中。
讓我解釋一下這背後的原因以及為什麼它更高效。
首先,讓我們看一下儲存數據。數據庫旨在允許系統對數據進行各種處理。基本的 CRUD 功能;創建、讀取、更新和刪除。為了有效和安全地做這些事情,必須實施許多檢查和安全機制。在您讀取數據之前,數據庫需要確保數據沒有被修改,它正在檢查是否存在衝突等。當您讀取數據庫中的數據時,伺服器會花費大量精力來記憶體該數據並確定是否以後可以更快地提供它。有索引操作和複製數據以準備以不同方式查看。數據庫設計人員投入了大量精力來設計這些功能以使其變得快速,但它們都需要處理時間,如果不使用它們只是一個障礙。
市場時間序列數據以完全不同的方式儲存。事實上,我會說它是準備好的而不是儲存的。每個數據項只需要寫入一次,之後就不需要修改或更改。數據項可以順序寫入,中間不需要插入任何東西。它根本不需要 ACID 功能。他們幾乎沒有引用任何其他數據。時間序列實際上是它自己的東西。
作為一個數據庫,它具有使數據庫變得美妙的所有魔力,它也包含在字節上。數據可以佔用的最小空間是它自己的原始大小。他們也許能夠通過規範化數據和壓縮來玩一些技巧,但這些技巧只能做到這一點並且減慢速度。索引、記憶體和引用數據最終會打包字節並佔用儲存空間。
閱讀也非常簡單。查找數據就像時間和符號一樣簡單。複雜的索引沒有好處。由於時間序列數據通常以線性方式和順序塊一次讀取,因此記憶體策略實際上會減慢訪問速度而不是幫助。它需要處理器週期來記憶體您不會很快再次讀取的數據。
這是對我們有用的基本結構。我們創建了用於序列化數據的基本資料結構。如果您主要關心的是速度和數據大小,您可以使用簡單的自定義二進制儲存。在另一個答案中,omencat 建議使用TeaFiles並且看起來它也有一些承諾。我們最近需要更大的靈活性,因此我們選擇使用相當密集但靈活的 JSON 格式。
我們將數據分解成相當明顯的塊。EOD 庫存數據是一個非常簡單的例子,但這個概念也適用於我們更大的數據集。
我們使用這些數據在相當傳統的時間序列場景中進行分析。它可以作為一個引用引用,也可以作為一次包含多年數據的系列引用。將數據分解為一口大小的塊進行儲存非常重要,因此我們選擇將我們的數據中的一個“塊”設置為一年的 EOD 庫存時間序列數據。每個塊都是一個文件,其中包含序列化為 JSON 的一年 OHLC EOD 數據。文件名是帶有下劃線前綴的股票符號。注意 - 當股票程式碼與 DOS 命令(如 COM 或 PRN)衝突時,下劃線可防止出現問題。
請注意,請確保您了解文件系統的限制。當我們將太多文件放在一個地方時,我們遇到了麻煩。這導致了一個目錄結構,它實際上是它自己的索引。它按數據年份細分,然後按股票程式碼的第一個字母排序。這為每個目錄提供了大約 20 到幾百個符號文件。它看起來大致是這樣的;
\StockEOD{YYYY}{初始}_symbol.json
2015 年的 AAPL 數據將是
\StockEOD\2015\A_AAPL.json
它的一小部分數據文件看起來像這樣;
[{"dt":"2007-01-03T00:00:00","o":86.28,"h":86.58,"l":81.9,"c":83.8,"v":43674760}, {"dt":"2007-01-04T00:00:00","o":84.17,"h":85.95,"l":83.82,"c":85.66,"v":29854074}, {"dt":"2007-01-05T00:00:00","o":85.84,"h":86.2,"l":84.4,"c":85.05,"v":29631186}, {"dt":"2007-01-08T00:00:00","o":85.98,"h":86.53,"l":85.28,"c":85.47,"v":28269652}
我們有一個路由器對象,它可以在幾行中為我們提供任何數據請求的文件名列表。每個文件都使用非同步文件流讀取並反序列化。每個報價都會變成一個對象並添加到系統中的排序列表中。那時,我們可以做一個非常快速的查詢來修剪掉不需要的數據。數據現在在記憶體中,幾乎可以以任何需要的方式使用。
如果查詢大小變得太大而電腦無法處理,則將過程分塊並不困難。到達那裡需要一個巨大的要求。
我曾讓我描述過的程序員幾乎要大發雷霆,告訴我我做錯了什麼。這是“滾動我自己的數據庫”,完全是浪費時間。事實上,我們是從一個相當複雜的數據庫切換而來的。當我們做我們的程式碼庫來處理這個問題時,只剩下少數幾個類,不到我們用來管理數據庫解決方案的程式碼的 1/4。我們的速度也提高了近 100 倍。我可以在幾毫秒內檢索 20 個品種的 7 年股票收盤數據。
我們舊的 HF 交易系統使用了類似的概念,但在高度優化的 Linux 環境中執行並在納秒範圍內執行。
上面的所有答案(不幸的是,在這一點上得到了高度評價)都沒有抓住重點。您不應該根據一般性能基準來選擇 DBMS 或儲存解決方案,而應該根據案例來選擇。如果有人說他們獲得了“x ms 讀取”、“y 每秒插入次數”、“k 倍加速”、“儲存 n TB 數據”或“有 m 年經驗”,並以此證明向您提出的建議是合理的,請不要不要相信那個人。
我可以為上面提出的每一個解決方案描述一個常見的斷點:
- 平面文件:當您開始擁有許多客戶端應用程序、您有一個小團隊和/或您需要實時訪問這些數據。即使是一個小團隊也可以在異構伺服器上同時處理數百個程序,因此如果您將數據儲存在平面文件中並依靠您自己的應用程序來管理並發訪問,您就會開始將其作為硬體或文件系統問題。即使是 10 到 20 年曆史的 RDBMS 在管理這方面也做得相當不錯,而您自己的開發人員需要花費大量時間來複製。編寫低級並發軟體來處理網路 I/O 限製或文件系統限制幾乎總是比弄清楚如何實現集群數據庫或使用開源工具分片更昂貴,如果你有一個小團隊,你的開發時間就是比單個文件查詢速度更有價值。您說您計劃對數據儲存進行回測 - 是的,當然,
- 面向列的時間序列數據庫:大多數人將現代數據庫優化誤認為是面向列的優勢。更現代的 DBMS 具有巧妙的並行基數雜湊連接、基於 SIMD 的聚合操作等,這些都解釋了它們的加速。比如說,理論上,面向行的 DBMS 在寫入速度上應該總是優於面向列的 DBMS,但在許多基準測試中你會看到相反的情況,因為許多面向列的 DBMS 有更現代的方法來延遲元數據生成或維護索引。最後,面向列是
一世。當您幾乎總是提前**完全實現您的記錄時,這是一個壞主意。例如,假設您只是儲存 {time, best_bid, best_ask} 並且您只是選擇時間間隔中的所有列 $ [a,b] $ 因為你在做探索性分析,還不知道什麼功能 $ f(best\ bid, best\ ask) $ 你想一起工作。一、記憶體順序遍歷(連續儲存)的優勢 $ \mathbb{O}(n) $ 失去了通過索引 B-tree 追逐指針的算法優勢 $ \mathbb{O}(\log n) $ . 大多數面向列的 DBMS 架構師都意識到了這一點,並實現了他們的查詢優化器以在查詢遵循這種模式時回退到索引上,因此面向行和麵向列的 DBMS 在這裡是均勻匹配的。但是在物化步驟,面向列的 DBMS 仍然需要將單獨的列反序列化回面向行的記錄,而面向行的 DBMS 只是按儲存順序寫出數據,理論上應該更快。
ii. 如果您的查詢限定集通常很小,則可以忽略不計。最大的瓶頸是將磁碟扇區載入到記憶體中。如果您的限定集很小,那麼無論是面向列還是面向行的佈局,它們都位於相同(少數)扇區上,因此在面向列的佈局中沒有一階速度優勢。
iii. 如果您需要第三方支持,則價格昂貴。只有少數具有生產實力的面向列的 DBMS,在這裡找一個承包商來幫助你比在 MySQL 上獲得免費的質量幫助要貴得多。如果您希望實時應用程序訂閱您的數據庫,那麼您可能需要一個昂貴的商業解決方案,因為所有開源選項都對此類功能的支持很弱。 3. NoSQL:假設您有 2 家公司決定合併,這種情況經常發生,因為您正在查看每日粒度。處理此問題有不同的約定,但現在您如何更新與任何一家公司相關的收益?MongoDB 中沒有級聯式數據模型,因此現在您將工作從數據庫級別轉移到應用程序級別。這可能很糟糕,有幾個原因,(1) 很有可能,您將這項任務交給分析師來為您完成,與讓架構確保您的完整性相比,他更容易搞砸它,(2) 您需要編寫特定於案例的程式碼來更新 JSON 文件中的特定欄位,這很難維護,(3) MongoDB,上面提出的,
我建議您這樣做:堅持使用 PostgreSQL,因為您已經熟悉它,以面向未來的方式設計架構,以便您輕鬆將數據遷移到任何未來的解決方案,確定您的案例的性能瓶頸在哪裡尋求更具體的解決方案。
您必須詢問最終使用者這些瓶頸是什麼。是嗎:
- 回測
- 執行時間範圍查詢(“獲取我這個時間範圍內的所有數據列。”)
- 執行聚合操作(“我想找到最高交易價格,計算總交易量等”)
- 並發訪問(“我想在伺服器 B 將結果廣播到伺服器 B 和 C 時將我的回測結果寫入伺服器 A。”)
- 維持複雜的關係(“我需要知道所有的股息日期修訂,我必須經常更新它們。”)
- 維持非結構化關係(“該資產類別有執行價格,但其他資產類別沒有。”)
理想的解決方案因您的案例而異。