وسائل لوڈ ہو رہے ہیں... لوڈنگ...

ایف ایم زیڈ پلیٹ فارم کے بیرونی سگنل وصول کرنے کا جائزہ: حکمت عملی بلٹ میں HTTP سروس سگنل وصول کرنے کا مکمل نظام

مصنف:ایجاد کاروں کی مقدار - خواب, تخلیق: 2024-12-17 11:44:07, تازہ کاری: 2024-12-17 16:08:11

[TOC]

img

پیش گوئی

پچھلے مضمون میں"ایف ایم زیڈ پلیٹ فارمز کے لئے بیرونی سگنل وصولی کا جائزہ: API کی توسیع بمقابلہ بلٹ ان HTTP سروسز کی حکمت عملی"اس بحث میں ہم نے دو مختلف طریقوں کا موازنہ کیا جو بیرونی سگنل وصول کرتے ہیں اور ان کی تفصیلات کا تجزیہ کیا ہے۔ ایف ایم زیڈ پلیٹ فارم کا استعمال کرتے ہوئے بیرونی سگنل وصول کرنے کے لئے API کو بڑھانے کے لئے پلیٹ فارم کی پالیسی لائبریری میں پہلے سے ہی ایک مکمل حکمت عملی موجود ہے ، اس پوسٹ میں ہم نے ایک مکمل حکمت عملی استعمال کرنے کے لئے مل کر کام کیا ہے۔

حکمت عملی کا نفاذ

FMZ توسیع API کا استعمال کرتے ہوئے ٹریڈنگ ویو سگنل تک رسائی کے لئے پہلے کی پالیسی کی نقل کرتے ہوئے ، ہم نے پہلے کے پیغام کی شکل ، پیغام پروسیسنگ وغیرہ پر عمل کرتے ہوئے اس پالیسی میں آسان تبدیلیاں کیں۔

چونکہ پالیسی میں بلٹ ان سروسز HTTP یا HTTPS استعمال کر سکتی ہیں، اس لیے ہم نے ایک سادہ مظاہرے کے لیے HTTP پروٹوکول کا استعمال کرتے ہوئے آئی پی وائٹ لسٹ کی توثیق، پاس ورڈ کی توثیق شامل کی ہے۔ اگر سیکیورٹی کو مزید بڑھانے کی ضرورت ہو تو پالیسی میں بلٹ ان سروسز کو HTTP سروسز کے طور پر ڈیزائن کیا جا سکتا ہے۔

//信号结构
var Template = {
    Flag: "45M103Buy",     // 标识,可随意指定
    Exchange: 1,           // 指定交易所交易对
    Currency: "BTC_USDT",  // 交易对
    ContractType: "spot",  // 合约类型,swap,quarter,next_quarter,现货填写spot
    Price: "{{close}}",    // 开仓或者平仓价格,-1为市价
    Action: "buy",         // 交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]
    Amount: "1",           // 交易量
}

var Success = "#5cb85c"    // 成功颜色
var Danger = "#ff0000"     // 危险颜色
var Warning = "#f0ad4e"    // 警告颜色
var buffSignal = []

// Http服务
function serverFunc(ctx, ipWhiteList, passPhrase) {
    var path = ctx.path()
    if (path == "/CommandRobot") {
        // 校验IP地址
        var fromIP = ctx.remoteAddr().split(":")[0]        
        if (ipWhiteList && ipWhiteList.length > 0) {
            var ipList = ipWhiteList.split(",")
            if (!ipList.includes(fromIP)) {
                ctx.setStatus(500)
                ctx.write("IP address not in white list")
                Log("500 Error: IP address not in white list", "#FF0000")
                return 
            }
        }

        // 校验口令
        var pass = ctx.rawQuery().length > 0 ? ctx.query("passPhrase") : ""
        if (passPhrase && passPhrase.length > 0) {
            if (pass != passPhrase) {
                ctx.setStatus(500)
                ctx.write("Authentication failed")
                Log("500 Error: Authentication failed", "#FF0000")
                return 
            }
        }

        var body = JSON.parse(ctx.body())
        threading.mainThread().postMessage(JSON.stringify(body))
        ctx.write("OK")
        // 200
    } else {
        ctx.setStatus(404)
    }
}

// 校验信号消息格式
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 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
}

function main() {
    // 重置日志信息
    if (isResetLog) {
        LogReset(1)
    }

    Log("交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]", Danger)
    Log("指令模板:", JSON.stringify(Template), Danger)    
    if (!passPhrase || passPhrase.length == 0) {
        Log("webhook url:", `http://${serverIP}:${port}/CommandRobot`)
    } else {
        Log("webhook url:", `http://${serverIP}:${port}/CommandRobot?passPhrase=${passPhrase}`)
    }

    // 创建Http内置服务
    __Serve("http://0.0.0.0:" + port, serverFunc, ipWhiteList, passPhrase)

    // 初始化执行的代码
    if (initCode && initCode.length > 0) {
        try {
            Log("执行初始化代码:", initCode)
            eval(initCode)
        } catch(error) {
            Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
        }
    }    

    // 创建信号管理对象
    var manager = createManager()
    
    while (true) {
        try {
            // 检测交互控件,用于测试
            var cmd = GetCommand()
            if (cmd) {
                // 发送Http请求,模拟测试
                var arrCmd = cmd.split(":", 2)
                if (arrCmd[0] == "TestSignal") {
                    // {"Flag":"TestSignal","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"long","Amount":"1"}
                    var signal = cmd.replace("TestSignal:", "")
                    if (!passPhrase || passPhrase.length == 0) {
                        var ret = HttpQuery(`http://${serverIP}:${port}/CommandRobot`, {"method": "POST", "body": signal})
                        Log("测试请求的应答:", ret)
                    } else {
                        var ret = HttpQuery(`http://${serverIP}:${port}/CommandRobot?passPhrase=${passPhrase}`, {"method": "POST", "body": signal})
                        Log("测试请求的应答:", ret)
                    }                    
                }
            }

            // 检测内置Http服务收到请求之后通知主线程的消息,写入manager对象的任务队列
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                Log("收到消息 msg:", msg)
                var objSignal = JSON.parse(msg)
                if (DiffObject(Template, objSignal)) {
                    Log("接收到交易信号指令:", objSignal)
                    buffSignal.push(objSignal)
                    
                    // 检查交易量、交易所编号
                    if (!CheckSignal(objSignal)) {
                        continue
                    }
                    
                    // 创建任务
                    if (objSignal["Flag"] == "TestSignal") {
                        Log("收到测试消息:", JSON.stringify(objSignal))
                    } else {
                        manager.newTask(objSignal)
                    }                    
                } else {
                    Log("指令无法识别", signal)
                }
            } else {
                Sleep(1000 * SleepInterval)
            }

            // 处理任务
            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) + "`")            
        } catch (error) {
            Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
        }        
    }
}

img

  • port پیرامیٹر: اگر HTTP پروٹوکول استعمال کیا جاتا ہے تو ، ٹریڈنگ ویو میں صرف 80 بندرگاہیں مقرر کی جاسکتی ہیں۔
  • سرور آئی پی پیرامیٹرز: سرور کے عوامی نیٹ ورک آئی پی ایڈریس کو پُر کریں۔
  • initCode پیرامیٹرز: بیس ایڈریس کو تبدیل کرنے کے لئے استعمال کیا جا سکتا ہے، جو ایکسچینج ٹیسٹنگ ماحول کی جانچ کے لئے استعمال کیا جاتا ہے۔

اس حکمت عملی میں کوئی بڑی تبدیلی نہیں ہوئی ہے ، اس کے مقابلے میں بیرونی سگنل تک رسائی حاصل کرنے کے لئے API کو بڑھانے کے طریقہ کار کا استعمال کرتے ہوئے ، اس میں صرف ایک اضافہ ہوا ہے۔serverFuncایچ ٹی ٹی پی سروس پروسیسنگ فنکشن ، ایف ایم زیڈ پلیٹ فارم میں نئے ملٹی تھریڈ میسجنگ طریقوں کا استعمال کرتے ہوئے:postMessage/peekMessageاس کے علاوہ ، دیگر کوڈ میں بہت کم تبدیلی آئی ہے۔

آئی پی وائٹ لسٹ

ٹریڈنگ ویو کے ویب ہوک کی وجہ سے درخواست صرف مندرجہ ذیل آئی پی ایڈریس سے کی گئی ہے:

52.89.214.238
34.212.75.30
54.218.53.128
52.32.178.7

تو ہم نے اس کی حکمت عملی میں ایک پیرامیٹر شامل کیا ہے.ipWhiteListیہ آئی پی ایڈریس کو سفید فہرست میں شامل نہیں کیا جاتا ہے، اور اس کی درخواستوں کو نظر انداز نہیں کیا جاتا ہے۔

        // 校验IP地址
        var fromIP = ctx.remoteAddr().split(":")[0]        
        if (ipWhiteList && ipWhiteList.length > 0) {
            var ipList = ipWhiteList.split(",")
            if (!ipList.includes(fromIP)) {
                ctx.setStatus(500)
                ctx.write("IP address not in white list")
                Log("500 Error: IP address not in white list", "#FF0000")
                return 
            }
        }

پاس ورڈ کی تصدیق

پالیسی میں ایک پیرامیٹر شامل کریںpassPhraseیہ پاس ورڈ ٹریڈنگ ویو پر ویب ہک یو آر ایل کی ترتیبات میں تشکیل دیا گیا ہے ، جس میں تصدیق کے پاس ورڈ سے متصادم درخواستوں کو نظرانداز کیا جاتا ہے۔

مثال کے طور پر، ہم نے قائم کیا:test123456

        // 校验口令
        var pass = ctx.rawQuery().length > 0 ? ctx.query("passPhrase") : ""
        if (passPhrase && passPhrase.length > 0) {
            if (pass != passPhrase) {
                ctx.setStatus(500)
                ctx.write("Authentication failed")
                Log("500 Error: Authentication failed", "#FF0000")
                return 
            }
        }

بیرونی سگنل

ٹریڈنگ ویو کے پلیٹ فارم کے پائن اسکرپٹ کو بیرونی سگنل ٹرگر کے ذریعہ استعمال کرتے ہوئے ، ٹریڈنگ ویو کے سرکاری طور پر دستیاب پائن اسکرپٹ میں سے کسی ایک کا انتخاب کریں:

//@version=6
strategy("MovingAvg Cross", overlay=true)
length = input(9)
confirmBars = input(1)
price = close
ma = ta.sma(price, length)
bcond = price > ma
bcount = 0
bcount := bcond ? nz(bcount[1]) + 1 : 0
if (bcount == confirmBars)
	strategy.entry("MACrossLE", strategy.long, comment="long")
scond = price < ma
scount = 0
scount := scond ? nz(scount[1]) + 1 : 0
if (scount == confirmBars)
	strategy.entry("MACrossSE", strategy.short, comment="short")

یقیناً ایف ایم زیڈ پلیٹ فارم پر براہ راست پائن اسکرپٹ چلاتے ہوئے فلیش ٹریڈنگ کی جاسکتی ہے ، لیکن اگر آپ ٹریڈنگ ویو پلیٹ فارم کو پائن اسکرپٹ چلاتے ہوئے سگنل بھیجنا چاہتے ہیں تو ، صرف ان طریقوں کا استعمال کریں جن کے بارے میں ہم بات کر رہے ہیں۔

ہم اسکرپٹ کے سبسکرپٹ پر توجہ دینے کی ضرورت ہے، اور اسکرپٹ کو ہمارے webhook کی درخواست میں پیغامات کو ایڈجسٹ کرنے کے لئے، ہمیں ٹرانزیکشن فنکشن میں موجود پیغامات کو تبدیل کرنے کی ضرورت ہےcommentاس مضمون میں ہم نے کچھ تبدیلیاں کیں جن کا ذکر بعد میں کیا جائے گا۔

Webhook Url اور request body کی ترتیبات

ویب ہک یو آر ایل اور درخواست کے جسم کی ترتیبات کے لئے ، بیرونی سگنل تک رسائی حاصل کرنے کے لئے پچھلے توسیع API کے طریقوں کے ساتھ بنیادی طور پر مماثل ہے ، اسی جگہ پر اس مضمون میں مزید بحث نہیں کی گئی ہے۔

ویب ہک یو آر ایل

img

جب ہم اس پائن اسکرپٹ کو ٹریڈنگ ویو پر کسی مارکیٹ میں شامل کرتے ہیں (ہم نے بِی این اے این کے انتخاب کے لئے ETH_USDT مستقل معاہدے کی مارکیٹ کا تجربہ کیا ہے) ، تو اسکرپٹ کام کرنا شروع ہو جاتا ہے۔ پھر ہم اسکرپٹ میں الارم شامل کرتے ہیں ، جیسا کہ اسکرین شاٹ میں دکھایا گیا ہے۔

img

ویب ہک یو آر ایل کی ترتیبات: پالیسی کوڈ میں خود کار طریقے سے ویب ہک یو آر ایل پیدا کرنے کے لئے ڈیزائن کیا گیا ہے ، جسے ہم صرف اس دن کاپی کرسکتے ہیں جب پالیسی شروع ہوتی ہے۔

img

http://xxx.xxx.xxx.xxx:80/CommandRobot?passPhrase=test123456

ٹریڈنگ ویو کا اصول یہ ہے کہ ویب ہک یو آر ایل HTTP درخواستوں کے لئے صرف 80 پورٹ استعمال کرسکتا ہے ، لہذا حکمت عملی میں ہم نے پورٹ پیرامیٹر کو بھی 80 پر مقرر کیا ہے ، لہذا آپ دیکھ سکتے ہیں کہ حکمت عملی کے ذریعہ تیار کردہ ویب ہک یو آر ایل کا لنک پورٹ بھی 80 ہے۔

body خبریں

img

اس کے بعد اسکرین شاٹ میں "ترتیبات" ٹیب میں درخواست کا جسم پیغام مقرر کریں۔

{
    "Flag":"{{strategy.order.id}}",
    "Exchange":1,
    "Currency":"ETH_USDT",
    "ContractType":"swap",
    "Price":"-1",
    "Action":"{{strategy.order.comment}}",
    "Amount":"{{strategy.order.contracts}}"
}

کیا آپ کو یاد ہے کہ ہم نے ابھی پیین اسکرپٹ میں مندرجہ ذیل کوڈ کو دیکھا تھا؟ ہم نے اس کوڈ میں متعدد پوزیشنوں کا استعمال کیا ہے:

strategy.entry("MACrossLE", strategy.long, comment="long")

اس کے علاوہ، "MACrossLE" مستقبل میں الارم کو چلانے کے لئے "#MACrossLE" کا استعمال کرتا ہے، جس کا مطلب ہے "#MACrossLE" کے لئے "#MACrossLE".strategy.order.id}}" کا مواد بھریں۔

اس میں "long" کا مطلب یہ ہے کہ مستقبل میں الارم چلانے پر ، "strategy.order.comment" کے لئے بھرنے کا مواد۔ پالیسی میں شناخت کردہ سگنل یہ ہے:

img

لہذا ہم آہنگی قائم کرنا ضروری ہے۔ یہاں ہم نے مندرجہ ذیل واحد افعال کے لئے "طویل" اور "مختصر" مقرر کیا ہے ، جس سے زیادہ ، کھلی جگہ کا اشارہ ہوتا ہے۔

PINE اسکرپٹ میں ہر بار کتنے معاہدوں کا تعین نہیں کیا گیا ہے ، لہذا ٹریڈنگ ویو نے الارم پیغام بھیجنے کے لئے پہلے سے طے شدہ معاہدے کو بھرنے کا حصہ استعمال کیا ہے۔

ریئل ڈسک ٹیسٹ

img

img

جب PINE اسکرپٹ ٹریڈنگ ویو پر چلتا ہے ، تو جب ہم ویب ہوک یو آر ایل الارم قائم کرتے ہیں تو ، ٹریڈنگ ویو پلیٹ فارم ہماری پالیسی میں بلٹ ان HTTP سروس کو ایک POST درخواست بھیجتا ہے ، جس میں درخواست کے استفسار میں ایک پاس ورڈ پیرامیٹر شامل ہوتا ہے جس کی تصدیق کے لئے استعمال ہوتا ہے۔passPhrase◄ اصل میں موصول ہونے والی درخواستوں کا جسم کچھ اس طرح ہے:

img

اس کے بعد ہماری حکمت عملی اس جسم میں موجود پیغام کے مطابق ٹرانزیکشنز پر عملدرآمد کرتی ہے۔

جیسا کہ آپ دیکھ سکتے ہیں، حکمت عملی OKX مشابہت کے ماحول میں، ٹریڈنگ ویو پر PINE اسکرپٹ کے مطابق مطابقت پذیر سگنل ٹریڈنگ کی جاتی ہے.

حکمت عملی کا پتہ

https://www.fmz.com/strategy/475235

ایف ایم زیڈ کی مقدار پر توجہ دینے اور پڑھنے کا شکریہ۔


مزید