本文代碼和文章發在FMZ發明者比特幣量化交易平臺上:javascript
使用JavaScript實現量化策略併發執行--封裝Go函數 - 發明者量化 https://www.fmz.com/digest-topic/3981java
在實現量化策略時,不少狀況下,併發執行能夠下降延時提高效率。以對衝機器人爲例,須要獲取兩個幣的深度,順序執行的代碼以下:多線程
var depthA = exchanges[0].GetDepth() var depthB = exchanges[1].GetDepth()
請求一次rest API存在延時,假設是100ms,那麼兩次獲取深度的時間實際上不同,若是須要更多的訪問,延時問題將會更突出,影響策略的執行。併發
JavaScript因爲沒有多線程,所以底層封裝了Go函數解決這個問題,但因爲設計機制,實現起來較爲繁瑣。app
var a = exchanges[0].Go("GetDepth") var b = exchanges[1].Go("GetDepth") var depthA = a.wait() //調用wait方法等待返回異步獲取depth結果 var depthB = b.wait()
在大多數簡單狀況下,這樣寫策略並沒有問題。但注意到每次策略循環都要重複這個過程,中間變量a,b實際上只是臨時輔助。若是咱們的併發任務很是多,就要另外紀錄a和depthA,b和depthB之間的對應關係,當咱們的併發任務不肯定時,狀況就更加複雜。所以,咱們但願實現一個函數:當寫Go併發時,同時綁定一個變量,當併發運行結果返回時,結果自動賦值給變量,這樣就省去了中間變量,使程序更加簡潔。具體實現以下:框架
function G(t, ctx, f) { return {run:function(){ f(t.wait(1000), ctx) }} }
咱們定義了一個G函數,其中參數t是將要執行的Go函數,ctx是記錄程序上下文,f爲具體賦值的函數。等會就會看到這個函數的做用。異步
這時,總體的程序框架能夠寫爲相似於「生產者-消費者」模型(有一些區別),生產者不斷髮出任務,消費者將它們併發執行,一下代碼僅爲演示,不涉及到程序的執行邏輯。函數
var Info = [{depth:null, account:null}, {depth:null, account:null}] //加入咱們須要獲取兩個交易所的深度和帳戶,跟多的信息也能夠放入,如訂單Id,狀態等。 var tasks = [ ] //全局的任務列表 function produce(){ //下發各類併發任務 //這裏省略了任務產生的邏輯,僅爲演示 tasks.push({exchange:0, ret:'depth', param:['GetDepth']}) tasks.push({exchange:1, ret:'depth', param:['GetDepth']}) tasks.push({exchange:0, ret:'sellID', param:['Buy', Info[0].depth.Asks[0].Price, 10]}) tasks.push({exchange:1, ret:'buyID', param:['Sell', Info[1].depth.Bids[0].Price, 10]}) } function worker(){ var jobs = [] for(var i=0;i<tasks.length;i++){ var task = tasks[i] tasks.splice(i,1) //刪掉已執行的任務 jobs.push(G(exchanges[task.exchange].Go.apply(this, task.param), task, function(v, task) { Info[task.exchange][task.ret] = v //這裏的v就是併發Go函數wait()的返回值,能夠仔細體會下 })) } _.each(jobs, function(t){ t.run() //在這裏併發執行全部任務 }) } function main() { while(true){ produce() // 發出交易指令 worker() // 併發執行 Sleep(1000) } }
看上去兜了一圈只實現了一個簡單功能,實際上大大簡化了代碼複雜程度,咱們只需關心程序須要產生什麼任務,由worker()程序自動將他們併發執行,並返回相應的結果。靈活性提高了不少。this