原 薦 Node.js股票模擬交易後臺
我曾經花了一週時間開發了一個股票模擬交易後臺程式,使用Node.js。程式碼量很少,能完成基本功能。下面給大家介紹一下其實現步驟。
基本功能
- 開戶
- 搜尋股票
- 掛單(多單、空單)
- 撤單(主動、被動)
- 成交(非撮合)
- 除權、除息
- 查詢
- 訂單狀態
- 持倉
- 今日委託
- 今日成交
- 歷史委託
- 歷史成交
- 掛單列表
- 賬戶詳情(總收益,收益率,總資產)
其中模擬交易和真實交易最大的不同是,真實交易採用撮合制,邏輯較為複雜。模擬交易採用更簡單的即時成交機制,只要符合條件,訂單立即成交。
這個後臺程式一共就兩個js檔案,一個用於處理成交,即判斷成交條件,寫資料庫。另一個處理其他邏輯。當然這裡面沒有提到獲取股票實時價格的問題,這是另一個系統完成,我們通過訊息佇列實時獲取我們所關心的股票的價格,這是另一個話題了。
這個後臺程式以一個node.js程序的方式執行,一個10秒一次的定時器執行成交判斷。(真實交易所的撮合器也是10秒鐘一次)
此外有一個WebAPI Server接受來自客戶端的請求。所以總體架構,可以看成是一個微服務組成的系統。
資料庫設計
賬戶表
Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '模擬賬戶', `MemberCode` varchar(20) DEFAULT '' COMMENT '使用者編號', `AccountNo` varchar(255) DEFAULT NULL COMMENT '賬號', `TranAmount` int(11) DEFAULT NULL COMMENT '模擬賬戶入資金額', `CommissionLimit` decimal(20,4) DEFAULT '2.9900' COMMENT '最低佣金', `CommissionRate` decimal(20,4) DEFAULT '0.0125' COMMENT '佣金比例', `Cash` decimal(20,4) DEFAULT '0.0000' COMMENT '現金', `UsableCash` decimal(20,4) DEFAULT '0.0000' COMMENT '可用資金', `Status` tinyint(4) DEFAULT '1' COMMENT '賬號狀態:1正常', `AccountType` tinyint(4) DEFAULT '1' COMMENT '賬號型別:1現金賬號,2保證金賬號', `CreateTime` datetime DEFAULT NULL COMMENT '建立時間', PRIMARY KEY (`Id`)
其中一個使用者可以對應多個賬戶,所以有一個AccountNo作為區分。 TranAmount為初始資金,用於重置賬戶。佣金欄位用於模擬交易的手續費和稅費。可用資金欄位是,當用戶掛單的時候有一部分資金處於凍結狀態,可用資金就是去除凍結資金的金額。
訂單表
Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '模擬交易訂單表', `MemberCode` varchar(20) DEFAULT '' COMMENT '使用者編號', `AccountNo` varchar(20) DEFAULT '' COMMENT '模擬賬號', `SecuritiesType` varchar(10) DEFAULT '' COMMENT '股票型別:us,hk,sh,sz', `SecuritiesNo` varchar(20) DEFAULT '' COMMENT '股票編號', `CPrice` decimal(20,4) DEFAULT '0.0000' COMMENT '委託價', `Price` decimal(20,4) DEFAULT '0.0000' COMMENT '價格', `OrderQty` decimal(20,4) DEFAULT '0.0000' COMMENT '股票資料量', `Side` char(1) DEFAULT '' COMMENT '交易型別:B買、S賣', `OrdType` tinyint(4) DEFAULT '1' COMMENT '訂單型別:1市場訂單、2限價訂單、3止損訂單、4做空市場訂單、5做空限價訂單、6做空止損訂單', `execType` tinyint(4) DEFAULT '1' COMMENT '執行型別:0新的,1成交、2取消、3拒絕', `Commission` decimal(20,4) DEFAULT '2.9900' COMMENT '佣金', `Reason` tinyint(4) DEFAULT '0' COMMENT '訂單拒絕理由:0正常、1資金不足、2倉位不足、3超時失效', `Amount` decimal(20,4) DEFAULT '0.0000' COMMENT '金額', `EndTime` datetime DEFAULT NULL COMMENT '訂單截止時間', `CreateTime` datetime DEFAULT NULL COMMENT '訂單時間', `TurnoverTime` datetime DEFAULT NULL COMMENT '成交時間', PRIMARY KEY (`Id`)
這是最重要的兩張表,其他幾張表就不羅列詳細的內容,只做簡單說明
- 資產表(記錄浮動盈虧,持倉金額,各種時間範圍的收益率)
- 額外津貼記錄表(記錄除權,除息)
- 資金記錄表(記錄特殊資金變動)
- 倉位表
- 倉位記錄表(記錄倉位變化)
- 做空倉位記錄表
- 排行榜
掛單
掛單的核心就是向資料庫插入一條記錄,不過即便是簡潔的js程式碼,也差不多寫了80行程式碼。 首先就是一系列的判斷,是否可以建立訂單。
- 引數是否在取值範圍內。
- 市價單型別,判斷是否開市,未開盤時間段不能建立訂單。
- 賬戶異常狀態不能建立訂單。
- 如果是賣多單,或者買空單,則要把倉位資料取出來判斷,是否倉位夠扣。
- 如果是買多單,或者賣空單,則要計算扣除佣金(手續費)後可用資金夠不夠。
- 如果是限價單或者是止損單,則判斷價格設定是否在有效範圍內。 然後執行一個數據庫事務,插入一條訂單記錄,同時修改可交易倉位或者可用資金。
撤單
撤單比掛單簡單許多。主要步驟就是先判斷訂單是否存在,然後修改訂單狀態,同時修改可交易倉位或者可用資金。
模擬交易主程序
系統每隔10秒執行一次邏輯。
所有訂單快取策略
如果每隔10秒鐘從資料庫讀取所有訂單的話,效率會很低,而且過多佔用資料庫IO資源。所以訂單資料都快取在成交判斷的程序記憶體中。將來也可以升級為使用redis等記憶體資料庫來儲存。 當有訂單建立的時候,通過訊息佇列通知程序。當程序重啟的時候,從資料庫讀取資料進行初始化。
超時訂單處理
有些訂單一直沒有滿足成交條件,但已經超過交易時間,所以要進行處理。(訂單狀態設定為拒絕)
成交判斷
未開盤則跳過。 根據訂單型別判斷是否達到成交條件
'訂單型別:1市場訂單、2限價訂單、3止損訂單、4做空市場訂單、5做空限價訂單、6做空止損訂單' Price:訂單設定的價格 price:當前股價 B:買入 S:賣出
let trigge = false switch (OrdType) { case 1: trigge = true; break; case 2: case 3: trigge = Side == "BS" [OrdType - 2] ? (Price >= price) : (Price <= price) break; case 4: trigge = true; break; case 5: case 6: trigge = Side == "BS" [6 - OrdType] ? (Price >= price) : (Price <= price) break; }
執行成交
最初是用程式執行的,後來為了執行效率和資料一致性,採用儲存過程。 首先,我們需要查詢出賬戶的現金和可用資金,以及倉位資訊。 如果是賣多或者買空(減少持倉,增加現金),我們計算出此時需要增加的金額,當然這個時候可能出現倉位不夠的情況,就拒絕訂單。 如果是買多或者賣空(增加持倉,減少現金),我們就需要計算此時需要扣除的金額,如果出現可用金額不足,就拒絕訂單。 最後,我們修改賬戶的實際金額和可用金額,寫入持倉記錄和現金變化記錄,修改訂單狀態為已成交狀態。
資訊查詢
普通資料庫查詢,這裡不多贅述了。
除權、除息
由於模擬交易系統無法第一時間自動得到除權和除息的訊息,所以當需要進行除權和除息的操作的時候,可能使用者已經發生成交的訂單。這時候需要根據持倉記錄變更表進行一些計算,恢復正確的持倉,如果是除息就是根據現金記錄變更表,進行資金重新計算。最後我們把這次操作的日誌記錄下來。