量化現貨合約跟單交易所軟體開發|原始碼搭建模式二開

nice1022發表於2023-03-08

策略設計成多交易所架構,開發I34-案例I633-演示53I9所以可以在這個策略上配置多個交易所物件,也就是可以控制多個不同賬戶的下單操作。只用在訊號結構中Exchange指定要操作的交易所即可,設定1就是要讓這個訊號操作第一個新增的交易所物件對應的交易所賬戶。如果要操作的是現貨ContractType設定為spot,期貨就寫具體合約,例如永續合約寫swap。市價單價格傳-1就可以了。Action設定對於期貨、現貨、開倉、平倉都是有區別的,不能設定錯。


接下來就可以設計策略程式碼了,完整的策略程式碼: 

//訊號結構
var Template = {
    Flag: "45M103Buy",     // 標識,可隨意指定
    Exchange: 1,           // 指定交易所交易對
    Currency: "BTC_USDT",  // 交易對
    ContractType: "swap",  // 合約型別,swap,quarter,next_quarter,現貨填寫spot
    Price: "{{close}}",    // 開倉或者平倉價格,-1為市價
    Action: "buy",         // 交易型別[ buy:現貨買入 , sell:現貨賣出 , long:期貨做多 , short:期貨做空 , closesell:期貨買入平空 , closebuy:期貨賣出平多]
    Amount: "0",           // 交易量
}
var BaseUrl = "api/v1"   // 擴充套件API介面地址
var RobotId = _G()                           // 當前實盤ID
var Success = "#5cb85c"    // 成功顏色
var Danger = "#ff0000"     // 危險顏色
var Warning = "#f0ad4e"    // 警告顏色
var buffSignal = []
// 校驗訊號訊息格式
function DiffObject(object1, object2) {
    const keys1 = Object.keys(object1)
    const keys2 = Object.keys(object2)
    if (keys1.length !== keys2.length) {
        return false
    }
    for (let i = 0; i < keys1.length; i++) {
        if (keys1[i] !== keys2[i]) {
            return false
        }
    }
    return true
}
function CheckSignal(Signal) {
    Signal.Price = parseFloat(Signal.Price)
    Signal.Amount = parseFloat(Signal.Amount)
    if (Signal.Exchange <= 0 || !Number.isInteger(Signal.Exchange)) {
        Log("交易所最小編號為1,並且為整數", Danger)
        return
    }
    if (Signal.Amount <= 0 || typeof(Signal.Amount) != "number") {
        Log("交易量不能小於0,並且為數值型別", typeof(Signal.Amount), Danger)
        return
    }
    if (typeof(Signal.Price) != "number") {
        Log("價格必須是數值", Danger)
        return
    }
    if (Signal.ContractType == "spot" && Signal.Action != "buy" && Signal.Action != "sell") {
        Log("指令為操作現貨,Action錯誤,Action:", Signal.Action, Danger)
        return
    }
    if (Signal.ContractType != "spot" && Signal.Action != "long" && Signal.Action != "short" && Signal.Action != "closesell" && Signal.Action != "closebuy") {
        Log("指令為操作期貨,Action錯誤,Action:", Signal.Action, Danger)
        return
    }
    return true
}
function commandRobot(url, accessKey, secretKey, robotId, cmd) {
    // api/v1?access_key=xxx&secret_key=xxx&method=CommandRobot&args=[xxx,+""]
    url = url + '?access_key=' + accessKey + '&secret_key=' + secretKey + '&method=CommandRobot&args=[' + robotId + ',+""]'
    var postData = {
        method:'POST',
        data:cmd
    }
    var headers = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36\nContent-Type: application/json"
    var ret = HttpQuery(url, postData, "", headers)
    Log("模擬TradingView的webhook請求,傳送用於測試的POST請求:", url, "body:", cmd, "應答:", ret)
}
function createManager() {
    var self = {}
    self.tasks = []
    self.process = function() {
        var processed = 0
        if (self.tasks.length > 0) {
            _.each(self.tasks, function(task) {
                if (!task.finished) {
                    processed++
                    self.pollTask(task)
                }
            })
            if (processed == 0) {
                self.tasks = []
            }
        }
    }
    self.newTask = function(signal) {
        // {"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
        var task = {}
        task.Flag = signal["Flag"]
        task.Exchange = signal["Exchange"]
        task.Currency = signal["Currency"]
        task.ContractType = signal["ContractType"]
        task.Price = signal["Price"]
        task.Action = signal["Action"]
        task.Amount = signal["Amount"]
        task.exchangeIdx = signal["Exchange"] - 1
        task.pricePrecision = null
        task.amountPrecision = null
        task.error = null
        task.exchangeLabel = exchanges[task.exchangeIdx].GetLabel()
        task.finished = false
        Log("建立任務:", task)
        self.tasks.push(task)
    }
    self.getPrecision = function(n) {
        var precision = null
        var arr = n.toString().split(".")
        if (arr.length == 1) {
            precision = 0
        } else if (arr.length == 2) {
            precision = arr[1].length
        }
        return precision
    }
    self.pollTask = function(task) {
        var e = exchanges[task.exchangeIdx]
        var name = e.GetName()
        var isFutures = true
        e.SetCurrency(task.Currency)
        if (task.ContractType != "spot" && name.indexOf("Futures_") != -1) {
            // 非現貨,則設定合約
            e.SetContractType(task.ContractType)
        } else if (task.ContractType == "spot" && name.indexOf("Futures_") == -1) {
            isFutures = false
        } else {
            task.error = "指令中的ContractType與配置的交易所物件型別不匹配"
            return
        }
        var depth = e.GetDepth()
        if (!depth || !depth.Bids || !depth.Asks) {
            task.error = "訂單薄資料異常"
            return
        }
        if (depth.Bids.length == 0 && depth.Asks.length == 0) {
            task.error = "盤口無訂單"
            return
        }
        _.each([depth.Bids, depth.Asks], function(arr) {
            _.each(arr, function(order) {
                var pricePrecision = self.getPrecision(order.Price)
                var amountPrecision = self.getPrecision(order.Amount)
                if (Number.isInteger(pricePrecision) && !Number.isInteger(self.pricePrecision)) {
                    self.pricePrecision = pricePrecision
                } else if (Number.isInteger(self.pricePrecision) && Number.isInteger(pricePrecision) && pricePrecision > self.pricePrecision) {
                    self.pricePrecision = pricePrecision
                }
                if (Number.isInteger(amountPrecision) && !Number.isInteger(self.amountPrecision)) {
                    self.amountPrecision = amountPrecision
                } else if (Number.isInteger(self.amountPrecision) && Number.isInteger(amountPrecision) && amountPrecision > self.amountPrecision) {
                    self.amountPrecision = amountPrecision
                }
            })
        })
        if (!Number.isInteger(self.pricePrecision) || !Number.isInteger(self.amountPrecision)) {
            task.err = "獲取精度失敗"
            return
        }
        e.SetPrecision(self.pricePrecision, self.amountPrecision)
        // buy:現貨買入 , sell:現貨賣出 , long:期貨做多 , short:期貨做空 , closesell:期貨買入平空 , closebuy:期貨賣出平多
        var direction = null
        var tradeFunc = null
        if (isFutures) {
            switch (task.Action) {
                case "long":
                    direction = "buy"
                    tradeFunc = e.Buy
                    break
                case "short":
                    direction = "sell"
                    tradeFunc = e.Sell
                    break
                case "closesell":
                    direction = "closesell"
                    tradeFunc = e.Buy
                    break
                case "closebuy":
                    direction = "closebuy"
                    tradeFunc = e.Sell
                    break
            }
            if (!direction || !tradeFunc) {
                task.error = "交易方向錯誤:" + task.Action
                return
            }
            e.SetDirection(direction)
        } else {
            if (task.Action == "buy") {
                tradeFunc = e.Buy
            } else if (task.Action == "sell") {
                tradeFunc = e.Sell
            } else {
                task.error = "交易方向錯誤:" + task.Action
                return
            }
        }
        var id = tradeFunc(task.Price, task.Amount)
        if (!id) {
            task.error = "下單失敗"
        }
        task.finished = true
    }
    return self
}
var manager = createManager()
function HandleCommand(signal) {
    // 檢測是否收到互動指令
    if (signal) {
        Log("收到互動指令:", signal)     // 收到互動指令,列印互動指令
    } else {
        return                            // 沒有收到時直接返回,不做處理
    }
    // 檢測互動指令是否是測試指令,測試指令可以由當前策略互動控制元件發出來進行測試
    if (signal.indexOf("TestSignal") != -1) {
        signal = signal.replace("TestSignal:", "")
        // 呼叫擴充套件API介面,模擬Trading View的webhook,互動按鈕TestSignal傳送的訊息:{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
        commandRobot(BaseUrl, _AccessKey, _SecretKey, RobotId, signal)
    } else if (signal.indexOf("evalCode") != -1) {
        var js = signal.split(':', 2)[1]
        Log("執行除錯程式碼:", js)
        eval(js)
    } else {
        // 處理訊號指令
        objSignal = JSON.parse(signal)
        if (DiffObject(Template, objSignal)) {
            Log("接收到交易訊號指令:", objSignal)
            buffSignal.push(objSignal)
            // 檢查交易量、交易所編號
            if (!CheckSignal(objSignal)) {
                return
            }
            // 建立任務
            manager.newTask(objSignal)
        } else {
            Log("指令無法識別", signal)
        }
    }
}
function main() {
    Log("WebHook地址:", "api/v1?access_key=" + _AccessKey + "&secret_key=" + _SecretKey + "&method=CommandRobot&args=[" + RobotId + ',+""]', Danger)
    Log("交易型別[ buy:現貨買入 , sell:現貨賣出 , long:期貨做多 , short:期貨做空 , closesell:期貨買入平空 , closebuy:期貨賣出平多]", Danger)
    Log("指令模板:", JSON.stringify(Template), Danger)
    while (true) {
        try {
            // 處理互動
            HandleCommand(GetCommand())
            // 處理任務
            manager.process()
            if (buffSignal.length > maxBuffSignalRowDisplay) {
                buffSignal.shift()
            }
            var buffSignalTbl = {
                "type" : "table",
                "title" : "訊號記錄",
                "cols" : ["Flag", "Exchange", "Currency", "ContractType", "Price", "Action", "Amount"],
                "rows" : []
            }
            for (var i = buffSignal.length - 1 ; i >= 0 ; i--) {
                buffSignalTbl.rows.push([buffSignal[i].Flag, buffSignal[i].Exchange, buffSignal[i].Currency, buffSignal[i].ContractType, buffSignal[i].Price, buffSignal[i].Action, buffSignal[i].Amount])
            }
            LogStatus(_D(), "\n", "`" + JSON.stringify(buffSignalTbl) + "`")
            Sleep(1000 * SleepInterval)
        } catch (error) {
            Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
            Sleep(1000 * 10)
        }
    }
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011332/viewspace-2938679/,如需轉載,請註明出處,否則將追究法律責任。

相關文章