When implementing the quantitative strategy, concurrent execution can reduce latency and improve efficiency in many cases. Taking the hedging robot as an example, we need to obtain the depth of two coins. The code executed in sequence is as follows:
var depthA = exchanges[0].GetDepth()
var depthB = exchanges[1].GetDepth()
There is a delay in requesting a rest API. Assuming that it is 100ms, the time for obtaining the depth is actually different. If more access is required, the delay problem will become more prominent and affect the implementation of the strategy.
JavaScript has no multithreading, so the Go function is encapsulated at the bottom to solve this problem. However, due to the design mechanism, the implementation is relatively cumbersome.
var a = exchanges[0].Go("GetDepth")
var b = exchanges[1].Go("GetDepth")
var depthA = a.wait() // Call the wait method to wait for the return of the depth result asynchronously
var depthB = b.wait()
In most simple cases, there is nothing wrong with writing the strategy this way. However, it is noted that this process should be repeated for each strategy loop. The intermediate variables a and b are only temporary aids. If we have a lot of concurrent tasks, we need to record the corresponding relationship between a and depthA, and b and depthB. When our concurrent tasks are uncertain, the situation becomes more complex. Therefore, we want to implement a function: when writing Go concurrently, bind a variable at the same time, and when the result of concurrent operation returns, the result will be automatically assigned to the variable, thus eliminating the intermediate variable and making the program more concise. The specific implementation is as follows:
function G(t, ctx, f) {
return {run:function(){
f(t.wait(1000), ctx)
}}
}
We define a G function, where the parameter t is the Go function to be executed, ctx is the recording program context, and f is the specific assignment function. You will see the function later.
At this time, the overall program framework can be written as similar to the “producer-consumer” model (with some differences). The producer keeps sending out tasks, and the consumer executes them concurrently. The following code is only for demonstration, not involving the program execution logic.
var Info = [{depth:null, account:null}, {depth:null, account:null}] // If we need to obtain the depth and account of the two exchanges, more information can also be put in, such as order ID, status, etc.
var tasks = [ ] // Global list of tasks
function produce(){ // Issue various concurrent tasks
// The logic of task generation is omitted here, for demonstration purposes only.
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) // Delete executed tasks
jobs.push(G(exchanges[task.exchange].Go.apply(this, task.param), task, function(v, task) {
Info[task.exchange][task.ret] = v // The v here is the return value of the concurrent Go function wait(), which can be experienced carefully.
}))
}
_.each(jobs, function(t){
t.run() // Execute all tasks concurrently here
})
}
function main() {
while(true){
produce() // Send trade orders
worker() // Concurrent execution
Sleep(1000)
}
}
It seems that only one simple function has been implemented after going around in circles. In fact, the complexity of the code has been simplified greatly. We only need to care about what tasks the program needs to generate. The worker() program will automatically execute them concurrently and return the corresponding results. The flexibility has improved a lot.