我曾經花了一週時間開發了一個股票模擬交易後臺程序,使用Node.js。代碼量不多,能完成基本功能。下面給你們介紹一下其實現步驟。node
其中模擬交易和真實交易最大的不一樣是,真實交易採用撮合制,邏輯較爲複雜。模擬交易採用更簡單的即時成交機制,只要符合條件,訂單當即成交。redis
這個後臺程序一共就兩個js文件,一個用於處理成交,即判斷成交條件,寫數據庫。另外一個處理其餘邏輯。固然這裏面沒有提到獲取股票實時價格的問題,這是另外一個系統完成,咱們經過消息隊列實時獲取咱們所關心的股票的價格,這是另外一個話題了。sql
這個後臺程序以一個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行代碼。 首先就是一系列的判斷,是否能夠建立訂單。ide
撤單比掛單簡單許多。主要步驟就是先判斷訂單是否存在,而後修改訂單狀態,同時修改可交易倉位或者可用資金。微服務
系統每隔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; }
最初是用程序執行的,後來爲了執行效率和數據一致性,採用存儲過程。 首先,咱們須要查詢出帳戶的現金和可用資金,以及倉位信息。 若是是賣多或者買空(減小持倉,增長現金),咱們計算出此時須要增長的金額,固然這個時候可能出現倉位不夠的狀況,就拒絕訂單。 若是是買多或者賣空(增長持倉,減小現金),咱們就須要計算此時須要扣除的金額,若是出現可用金額不足,就拒絕訂單。 最後,咱們修改帳戶的實際金額和可用金額,寫入持倉記錄和現金變化記錄,修改訂單狀態爲已成交狀態。
普通數據庫查詢,這裏很少贅述了。
因爲模擬交易系統沒法第一時間自動獲得除權和除息的消息,因此當須要進行除權和除息的操做的時候,可能用戶已經發生成交的訂單。這時候須要根據持倉記錄變動表進行一些計算,恢復正確的持倉,若是是除息就是根據現金記錄變動表,進行資金從新計算。最後咱們把此次操做的日誌記錄下來。