1. 程式人生 > 其它 >利用SQLite構建發明者量化資料庫

利用SQLite構建發明者量化資料庫

目錄

摘要

資料是量化交易的源頭,如何高效地管理大量資料是非常關鍵的環節,資料庫是最佳的解決方案之一,現如今資料庫的應用已經是各類日內交易、高頻交易等策略的量化標準配置。本篇我們來研究一下發明者量化(FMZ.COM)內建的資料庫,內容包括:如何建立資料表、儲存資料、修改資料、刪除資料、引用資料以及如何應用於實戰。

如何選擇資料庫

熟悉發明者量化平臺的應該知道,在這之前要想把資料儲存到本地複用,只能用_G()函式,每次停止策略時,_G()函式會自動儲存需要的資訊。但如果想要儲存更多更復雜的格式化資料,_G()函式顯然就不太適用了,於是很多人想到了自建資料庫來解決這個問題。

提到自建資料庫,想必大家能想到Oracle、MySQL、KDB、OneTick、NoSQL...這些都是非常優秀的企業級別的應用,不管是功能還是效能都非常強大。但也面臨幾個問題:上手難度大,配置繁瑣維護起來麻煩,這對於量化交易散戶來說,有點用大炮打蒼蠅的感覺,即使上手也只是用到很少一部分功能。

發明者量化內建資料庫

接下來讓我們認識一下發明者量化內建的輕型資料庫,DBExec是發明者量化內建的一個關係型資料管理系統介面,基於SQLite開發,其本身是用C寫的,不但體積小巧,佔用資源低,而且處理速度快,非常適合用於金融量化分析愛好者在本地實現資料管理,因為可以將不同的“物件”(例如交易所,資料來源,價格)分成不同的表,並在表之間定義關係。此外使用者無需單獨安裝和配置,只要呼叫DBExec()函式就可以直接使用!

另外,SQLite語言的學習成本很低,在資料庫上執行的大部分工作都由SQLite語句完成。熟悉基本語法就能滿足大部分需求,以下是SQLite的基礎語法。

基礎語法

SQLite的語法是不區分大小寫的,不過有一些命令對大小寫敏感,如GLOB和glob代表不同的含義。SQLite語句可以以任何關鍵字開始,如SELECT、INSERT、UPDATE、DELETE、ALTER、DROP等,它們分別表示:提取資料、插入資料、更新資料、刪除資料、修改資料庫、刪除資料表。所有的語句以英文分號結束。下面是一個簡單的資料庫建立、增、刪、改、查等操作:

function main() {
    // 建立:如果“users”表不存在就建立一個,“id”是整數且自動增加,“name”是文字形式且不為空
    Log(DBExec('CREATE TABLE IF NOT EXISTS "users" (id INTEGER PRIMARY KEY AUTOINCREMENT, name text not NULL);'));
    
    // 增加:
    Log(DBExec("INSERT INTO users(name) values('張三')"));
    Log(DBExec("INSERT INTO users(name) values('李四')"));
    
    // 刪除:
    Log(DBExec("DELETE FROM users WHERE id=1;"));
    
    // 修改
    Log(DBExec("UPDATE users SET name='王五' WHERE id=2"));
    
    // 查詢
    Log(DBExec('select 2, ?, ?, ?, ?', 'ok', true,9.8,null));
    Log(DBExec('select * from kvdb'));
    Log(DBExec('select * from cfg'));
    Log(DBExec('select * from log'));
    Log(DBExec('select * from profit'));
    Log(DBExec('select * from chart'));
    Log(DBExec("selEct * from users"));
}

一個數據庫通常包含一個或多個表,每個表都有一個名字標識,需要注意的是系統保留表分別: kvdb, cfg, log, profit, chart。也就是說在建立表時,應該避開系統保留的名字。讓我們執行上面的程式碼,輸出以下內容:

策略例項

瞭解了SQLite的基礎語法,我們趁熱打鐵使用發明者量化內建的資料庫,建立一個收集和使用Tick資料的例項。

第一步:更新託管者
首先確保您使用的是最新版本的託管者,如果之前下載使用過託管者,需要先刪除,並在 https://www.fmz.com/m/add-node 頁面重新下載和部署。

第二步:建立策略

function main() {
    // 訂閱合約
    _C(exchange.SetContractType, 'swap');
    
    // 建立資料表
    DBExec('CREATE TABLE IF NOT EXISTS "tick" (id INTEGER PRIMARY KEY AUTOINCREMENT,'.concat(
        'High FLOAT not NULL,', 
        'Low FLOAT not NULL,', 
        'Sell FLOAT not NULL,', 
        'Buy FLOAT not NULL,', 
        'Last FLOAT not NULL,', 
        'Volume INTEGER not NULL,', 
        'Time INTEGER not NULL);'
    ));
    
    // 獲取10個tick資料
    while (true) {
        let tick = exchange.GetTicker();
        // 在tick表中增加資料
        DBExec(`INSERT INTO tick(High, Low, Sell, Buy, Last, Volume, Time) values(${tick.High}, ${tick.Low}, ${tick.Sell}, ${tick.Buy}, ${tick.Last}, ${tick.Volume}, ${tick.Time})`);
        // 查詢所有資料
        let allDate = DBExec('select * from tick');
        if (allDate.values.length > 10) {
            break;
        }
        Sleep(1000);
    }
    
    // 查詢所有資料
    Log(DBExec('select * from tick'));
    
    // 查詢第一個資料
    Log(DBExec('select * from tick limit 1'));
    
    // 查詢前兩個資料
    Log(DBExec('select * from tick limit 0,2'));
    
    // 刪除第一個資料
    Log(DBExec('DELETE FROM tick WHERE id=1;'));
    
    // 修改第二個資料
    Log(DBExec('UPDATE tick SET High=10000 WHERE id=2'));
    
    // 查詢所有資料
    let allDate = DBExec('select * from tick')
    Log(allDate);
}

第三步:執行策略
以Windows為例,執行策略之後,會在託管者目錄的“\logs\storage”目錄中生成一個以機器人編號命名的資料夾,開啟該資料夾,裡面有一個以“.db3”作為字尾的檔案,這個檔案就是發明者量化內建資料庫的檔案。以下圖所示:

上面的程式碼首先建立了一個以“tick”命名的資料表,然後給該表新增tick資料欄位,接著在迴圈中從交易所獲取tick資料,並把這些資料插入到“tick”資料表中,同時判斷該資料表中的資料量超過10個就跳出迴圈。最後分別用5個SQLite命令查詢、刪除、修改資料表中的資料。並在日誌中打印出來,以下圖所示:

第四步:建立狀態列
最後我們增加一些程式碼,通過獲取發明者量化資料庫中的資料,給策略建立一個狀態列,把資料更直觀的展示出來,新增程式碼如下:

    // 建立狀態列
    let table = {
        type: 'table',
        title: '幣安Tick資料',
        cols: allDate.columns,
        rows: allDate.values
    }
    LogStatus('`' + JSON.stringify(table) + '`');

上面的程式碼通過資料庫中的資料,建立了一個“幣安Tick資料”表。其中資料庫中的“columns”欄位代表狀態列中的“行”,“values”欄位代表狀態列中的“列”。如下圖所示:

完整策略程式碼

/*backtest
start: 2020-07-19 00:00:00
end: 2020-08-17 23:59:00
period: 15m
basePeriod: 15m
exchanges: [{"eid":"Binance","currency":"LTC_USDT"}]
*/

function main() {
    Log(DBExec('DROP TABLE tick;'));
    // 訂閱合約
    _C(exchange.SetContractType, 'swap');

    // 建立資料表
    DBExec('CREATE TABLE IF NOT EXISTS "tick" (id INTEGER PRIMARY KEY AUTOINCREMENT,'.concat(
        'High FLOAT not NULL,',
        'Low FLOAT not NULL,',
        'Sell FLOAT not NULL,',
        'Buy FLOAT not NULL,',
        'Last FLOAT not NULL,',
        'Volume INTEGER not NULL,',
        'Time INTEGER not NULL);'
    ));

    // 獲取10個tick資料
    while (true) {
        let tick = exchange.GetTicker();
        // 在tick表中增加資料
        DBExec(`INSERT INTO tick(High, Low, Sell, Buy, Last, Volume, Time) values(${tick.High}, ${tick.Low}, ${tick.Sell}, ${tick.Buy}, ${tick.Last}, ${tick.Volume}, ${tick.Time})`);
        // 查詢所有資料
        let allDate = DBExec('select * from tick');
        if (allDate.values.length > 10) {
            break;
        }
        Sleep(1000);
    }

    // 查詢所有資料
    Log(DBExec('select * from tick'));

    // 查詢第一個資料
    Log(DBExec('select * from tick limit 1'));

    // 查詢前兩個資料
    Log(DBExec('select * from tick limit 0,2'));

    // 刪除第一個資料
    Log(DBExec('DELETE FROM tick WHERE id=1;'));

    // 修改第二個資料
    Log(DBExec('UPDATE tick SET High=10000 WHERE id=2'));

    // 查詢所有資料
    let allDate = DBExec('select * from tick')
    Log(allDate);

    // 建立狀態列
    let table = {
        type: 'table',
        title: '幣安Tick資料',
        cols: allDate.columns,
        rows: allDate.values
    }
    LogStatus('`' + JSON.stringify(table) + '`');
}

點選該連結 https://www.fmz.com/strategy/265906 即可複製完整策略程式碼。

記憶體資料庫

如果操作的資料不希望永久儲存到磁碟,可以在SQL語句前加上:符號就可以在記憶體資料庫裡操作, 機器人重啟後資料重置

DBExec(":select 1,2,3");

總結

資料庫不僅可以承載海量資料,更能承載眾多量化交易愛好者的寬客夢想。對於資料庫的使用絕非僅限於本文的例子,更多使用方法可以參考SQLite教程,以及發明者量化後續推出的系列文章。