রিসোর্স লোড হচ্ছে... লোডিং...

এফএমজেড প্ল্যাটফর্মের বাহ্যিক সংকেত গ্রহণ নিয়ে আলোচনাঃ কৌশলগতভাবে অন্তর্নির্মিত এইচটিটিপি পরিষেবা সহ সংকেত গ্রহণের জন্য একটি সম্পূর্ণ সমাধান

লেখক:এফএমজেড-লিডিয়া, তৈরিঃ ২০২৪-১২-১৮ ০৯ঃ২২ঃ৩৩, আপডেটঃ ২০২৪-১২-১৮ ০৯ঃ২৪ঃ৪৯

প্রিফেস

পূর্ববর্তী নিবন্ধেএফএমজেড প্ল্যাটফর্মের বাহ্যিক সংকেত গ্রহণের বিষয়ে আলোচনাঃ এক্সটেন্ডেড এপিআই বনাম কৌশল অন্তর্নির্মিত এইচটিটিপি পরিষেবা, আমরা প্রোগ্রাম্যাটিক ট্রেডিংয়ের জন্য বাহ্যিক সংকেত গ্রহণের দুটি ভিন্ন উপায়ের তুলনা করেছি এবং বিশ্লেষণ করেছি। বাহ্যিক সংকেত গ্রহণের জন্য FMZ প্ল্যাটফর্ম বর্ধিত API ব্যবহার করার সমাধানটি প্ল্যাটফর্ম কৌশল লাইব্রেরিতে একটি সম্পূর্ণ কৌশল রয়েছে। এই নিবন্ধে, আসুন সংকেত গ্রহণের জন্য কৌশল বিল্ট-ইন Http পরিষেবা ব্যবহার করার একটি সম্পূর্ণ সমাধান বাস্তবায়ন করি।

কৌশল বাস্তবায়ন

ট্রেডিং ভিউ সংকেত অ্যাক্সেস করার জন্য FMZ এক্সটেনশন API ব্যবহার করার পূর্ববর্তী কৌশল অনুসরণ করে, আমরা পূর্ববর্তী বার্তা বিন্যাস, বার্তা প্রক্রিয়াকরণ পদ্ধতি, ইত্যাদি ব্যবহার করি এবং কৌশলটিতে সহজ পরিবর্তনগুলি করি।

যেহেতু কৌশলটির অন্তর্নির্মিত পরিষেবাগুলি Http বা HTTPS ব্যবহার করতে পারে, একটি সহজ বিক্ষোভের জন্য, আমরা Http প্রোটোকল ব্যবহার করি, আইপি হোয়াইটলিস্ট যাচাইকরণ যুক্ত করি এবং পাসওয়ার্ড যাচাইকরণ যুক্ত করি। যদি সুরক্ষা আরও বাড়ানোর প্রয়োজন হয় তবে কৌশলটির অন্তর্নির্মিত পরিষেবাটি একটি Https পরিষেবা হিসাবে ডিজাইন করা যেতে পারে।

//Signal structure
var Template = {
    Flag: "45M103Buy",     // Logo, can be specified at will
    Exchange: 1,           // Designated exchange trading pairs
    Currency: "BTC_USDT",  // Trading pairs
    ContractType: "spot",  // Contract type, swap, quarter, next_quarter, spot fill in spot
    Price: "{{close}}",    // Opening or closing price, -1 is the market price
    Action: "buy",         // Transaction type [buy: spot buy, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long]
    Amount: "1",           // Trading volume
}

var Success = "#5cb85c"    // Success color
var Danger = "#ff0000"     // Danger color
var Warning = "#f0ad4e"    // Warning color
var buffSignal = []

// Http service
function serverFunc(ctx, ipWhiteList, passPhrase) {
    var path = ctx.path()
    if (path == "/CommandRobot") {
        // Verify IP address
        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 
            }
        }

        // Verify password
        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)
    }
}

// Check signal message format
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("The minimum exchange number is 1 and is an integer.", Danger)
        return
    }
    if (Signal.Amount <= 0 || typeof(Signal.Amount) != "number") {
        Log("The trading volume cannot be less than 0 and must be a numeric type.", typeof(Signal.Amount), Danger)
        return
    }
    if (typeof(Signal.Price) != "number") {
        Log("Price must be a numeric value", Danger)
        return
    }
    if (Signal.ContractType == "spot" && Signal.Action != "buy" && Signal.Action != "sell") {
        Log("The instruction is to operate spot goods, and the Action is wrong, Action:", Signal.Action, Danger)
        return 
    }
    if (Signal.ContractType != "spot" && Signal.Action != "long" && Signal.Action != "short" && Signal.Action != "closesell" && Signal.Action != "closebuy") {
        Log("The instruction is to operate futures, and the Action is wrong, Action:", Signal.Action, Danger)
        return 
    }
    return true
}

// Signal processing object
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("Create a task:", 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) {
            // If it is not spot, set up a contract
            e.SetContractType(task.ContractType)
        } else if (task.ContractType == "spot" && name.indexOf("Futures_") == -1) {
            isFutures = false 
        } else {
            task.error = "The ContractType in the instruction does not match the configured exchange object type"
            return 
        }
        
        var depth = e.GetDepth()
        if (!depth || !depth.Bids || !depth.Asks) {
            task.error = "Abnormal order book data"
            return 
        }
        
        if (depth.Bids.length == 0 && depth.Asks.length == 0) {
            task.error = "No orders on the market"
            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 = "Failed to get precision"
            return 
        }
        
        e.SetPrecision(self.pricePrecision, self.amountPrecision)
        
        // buy: spot purchase, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long
        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 = "Wrong transaction direction:" + 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 = "Wrong transaction direction:" + task.Action
                return 
            }
        }
        var id = tradeFunc(task.Price, task.Amount)
        if (!id) {
            task.error = "Order failed"
        }
        
        task.finished = true
    }
    
    return self
}

function main() {
    // Reset log information
    if (isResetLog) {
        LogReset(1)
    }

    Log("Transaction type [buy: spot buy, sell: spot sell, long: futures long, short: futures short, closesell: futures buy to close short, closebuy: futures sell to close long]", Danger)
    Log("Instruction templates:", 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}`)
    }

    // Creating an Http built-in service
    __Serve("http://0.0.0.0:" + port, serverFunc, ipWhiteList, passPhrase)

    // Initialize the code to execute
    if (initCode && initCode.length > 0) {
        try {
            Log("Execute the initialization code:", initCode)
            eval(initCode)
        } catch(error) {
            Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
        }
    }    

    // Create a signal management object
    var manager = createManager()
    
    while (true) {
        try {
            // Detect interactive controls for testing
            var cmd = GetCommand()
            if (cmd) {
                // Send Http request, simulate test
                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": JSON.stringify(signal)})
                        Log("Test request response:", ret)
                    } else {
                        var ret = HttpQuery(`http://${serverIP}:${port}/CommandRobot?passPhrase=${passPhrase}`, {"method": "POST", "body": JSON.stringify(signal)})
                        Log("Test request response:", ret)
                    }                    
                }
            }

            // Detect the message that the built-in Http service notifies the main thread after receiving the request, and writes it to the task queue of the manager object
            var msg = threading.mainThread().peekMessage(-1)
            if (msg) {
                Log("Receive message msg:", msg)
                var objSignal = JSON.parse(msg)
                if (DiffObject(Template, objSignal)) {
                    Log("Receive trading signal instructions:", objSignal)
                    buffSignal.push(objSignal)
                    
                    // Check trading volume, exchange ID
                    if (!CheckSignal(objSignal)) {
                        continue
                    }
                    
                    // Create a task
                    if (objSignal["Flag"] == "TestSignal") {
                        Log("Received test message:", JSON.stringify(objSignal))
                    } else {
                        manager.newTask(objSignal)
                    }                    
                } else {
                    Log("Command not recognized", signal)
                }
            } else {
                Sleep(1000 * SleepInterval)
            }

            // Processing tasks
            manager.process()
            
            // Status bar displays signal
            if (buffSignal.length > maxBuffSignalRowDisplay) {
                buffSignal.shift()
            }
            var buffSignalTbl = {
                "type" : "table",
                "title" : "Signal recording",
                "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

  • পোর্ট প্যারামিটারঃ যদি আপনি Http প্রোটোকল ব্যবহার করেন, আপনি শুধুমাত্র ট্রেডিং ভিউতে পোর্ট 80 সেট করতে পারেন।
  • serverIP প্যারামিটারঃ সার্ভারের পাবলিক আইপি ঠিকানা লিখুন।
  • initCode প্যারামিটারঃ এটি এক্সচেঞ্জ টেস্ট পরিবেশে পরীক্ষার জন্য বেস ঠিকানা পরিবর্তন করতে ব্যবহার করা যেতে পারে।

বহিরাগত সংকেত অ্যাক্সেস করার জন্য বর্ধিত এপিআই ব্যবহার করার কৌশল তুলনায়, কৌশল অনেক পরিবর্তন হয় না। এটি শুধুমাত্র একটিserverFuncএইচটিটিপি সার্ভিস প্রসেসিং ফাংশন এবং মাল্টি থ্রেডেড বার্তা পাস পদ্ধতি ব্যবহার করে যা সম্প্রতি এফএমজেড প্ল্যাটফর্ম দ্বারা যুক্ত হয়েছেঃpostMessage / peekMessageবাকি কোডগুলো প্রায় অপরিবর্তিত।

আইপি হোয়াইটলিস্ট

যেহেতু ট্রেডিং ভিউ এর ওয়েবহুক থেকে অনুরোধগুলি শুধুমাত্র নিম্নলিখিত আইপি ঠিকানা থেকে পাঠানো হয়:

52.89.214.238
34.212.75.30
54.218.53.128
52.32.178.7

অতএব, আমরা একটি পরামিতি যোগipWhiteListআইপি অ্যাড্রেস হোয়াইটলিস্ট সেট করার কৌশল। আইপি অ্যাড্রেস হোয়াইটলিস্টে নেই এমন সমস্ত অনুরোধ উপেক্ষা করা হবে।

        // Verify IP address
        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যাচাইকরণ পাসওয়ার্ড সেট করার কৌশল. এই পাসওয়ার্ডটি ট্রেডিং ভিউতে ওয়েবহুক url সেটিংসে কনফিগার করা হয়. যাচাইকরণ পাসওয়ার্ডের সাথে মেলে না এমন অনুরোধগুলি উপেক্ষা করা হবে.

উদাহরণস্বরূপ, আমরা সেট করিঃtest123456.

        // Verify password
        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 
            }
        }

বাহ্যিক সংকেত

ট্রেডিং ভিউ প্ল্যাটফর্মের PINE স্ক্রিপ্টটি বহিরাগত সংকেত ট্রিগার উত্স হিসাবে ব্যবহার করুন এবং ট্রেডিং ভিউ কর্তৃক আনুষ্ঠানিকভাবে এলোমেলোভাবে প্রকাশিত PINE স্ক্রিপ্টগুলির মধ্যে একটি নির্বাচন করুনঃ

//@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")

অবশ্যই, আপনি সরাসরি FMZ প্ল্যাটফর্মে লাইভ ট্রেডিং চালানোর জন্য PINE স্ক্রিপ্ট চালাতে পারেন, কিন্তু আপনি যদি ট্রেডিং ভিউ প্ল্যাটফর্মটি সিগন্যাল পাঠানোর জন্য PINE স্ক্রিপ্ট চালাতে চান তবে আপনি কেবলমাত্র আমরা আলোচনা করেছি এমন সমাধানগুলি ব্যবহার করতে পারেন।

আমরা এই স্ক্রিপ্টের অর্ডার স্থাপন ফাংশন উপর ফোকাস করতে হবে. আমাদের webhook অনুরোধ বার্তা এই পাইন স্ক্রিপ্ট মানিয়ে নিতে, আমরা ট্রেডিং ফাংশন পরিবর্তন করতে হবেcomment, যা আমরা পরবর্তী প্রবন্ধে উল্লেখ করব।

WebhookUrl এবং Request শরীরের সেটিংস

WebhookUrl এবং request body এর সেটিংস মূলত বহিরাগত সংকেত অ্যাক্সেস করার পূর্ববর্তী বর্ধিত API পদ্ধতির মতোই। একই অংশগুলি এই নিবন্ধে পুনরাবৃত্তি করা হবে না। আপনি পূর্ববর্তী নিবন্ধটি উল্লেখ করতে পারেন।

ওয়েবহুক URL

img

ট্রেডিং ভিউতে আমরা এই পাইন স্ক্রিপ্টটি একটি মার্কেটের চার্টে যুক্ত করার পরে (আমরা পরীক্ষার জন্য বিন্যান্সের ETH_USDT চিরস্থায়ী চুক্তি বাজারটি বেছে নিই), আমরা দেখতে পাচ্ছি যে স্ক্রিপ্টটি কাজ শুরু করেছে। তারপর আমরা স্ক্রিপ্টটিতে একটি সতর্কতা যুক্ত করি যেমন স্ক্রিনশটে দেখানো হয়েছে।

img

ওয়েবহুক URL সেটিংসঃ স্ট্র্যাটেজি কোডটি স্বয়ংক্রিয়ভাবে ওয়েবহুক ইউআরএল তৈরি করার জন্য ডিজাইন করা হয়েছে। আমাদের কেবল কৌশল অপারেশনের শুরুতে লগ থেকে এটি অনুলিপি করতে হবে।

img

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

ট্রেডিং ভিউতে উল্লেখ করা হয়েছে যে ওয়েবহুক ইউআরএল শুধুমাত্র HTTP অনুরোধের জন্য পোর্ট 80 ব্যবহার করতে পারে, তাই আমরা কৌশলটিতে পোর্ট প্যারামিটারটি 80 এ সেট করি, যাতে আমরা দেখতে পারি যে কৌশল দ্বারা উত্পন্ন ওয়েবহুক ইউআরএলের লিঙ্ক পোর্টটিও 80।

শরীরের বার্তা

img

তারপর আমরা Settings ট্যাবে request body বার্তা সেট করি যেমন স্ক্রিনশটে দেখানো হয়েছে।

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

আপনি কি PINE স্ক্রিপ্টের অর্ডার স্থাপন কোডটি মনে রাখবেন যা আমরা সবেমাত্র আলোচনা করেছি? উদাহরণস্বরূপ লং পজিশন খোলার কোডটি নেওয়া যাকঃ

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

MACrossLE হল {{ দিয়ে পূরণ করা বিষয়বস্তুstrategy.order.id}} যখন ভবিষ্যতে সতর্কতা সক্রিয় করা হয়।

long হল {{কৌশল.অর্ডার.কমেন্ট}} এ পূরণ করা বিষয়বস্তু যখন ভবিষ্যতে সতর্কতা ট্রিগার করা হয়। কৌশলটিতে চিহ্নিত সংকেতটি হল (নীচের স্ক্রিনশট):

img

সুতরাং সেটিংস ধারাবাহিক হতে হবে। এখানে আমরা অর্ডার ফাংশনের জন্য long এবং short সেট করি, যা দীর্ঘ এবং সংক্ষিপ্ত খোলার সংকেত নির্দেশ করে।

PINE স্ক্রিপ্ট প্রতিটি অর্ডারের জন্য অর্ডার পরিমাণ নির্দিষ্ট করে না, তাই যখন ট্রেডিং ভিউ একটি সতর্কতা বার্তা প্রেরণ করে, এটি {{কৌশল.অর্ডার.চুক্তি}} অংশটি পূরণ করতে ডিফল্ট অর্ডার পরিমাণ ব্যবহার করে।

লাইভ ট্রেডিং টেস্ট

img

img

যখন ট্রেডিং ভিউতে চলমান পাইন স্ক্রিপ্ট ট্রেডিং ফাংশনটি সম্পাদন করে, কারণ আমরা ওয়েবহুক ইউআরএল সতর্কতা সেট করেছি, ট্রেডিং ভিউ প্ল্যাটফর্মটি আমাদের কৌশলটির অন্তর্নির্মিত এইচটিটিপি পরিষেবাতে একটি পিওএসটি অনুরোধ প্রেরণ করবে। এই অনুরোধ অনুসন্ধানে একটি পাসওয়ার্ড প্যারামিটার রয়েছেpassPhraseযাচাইয়ের জন্য। প্রকৃত অনুরোধকারী সংস্থাটি এইরকমঃ

img

তারপর আমাদের কৌশল এই শরীরের বার্তা উপর ভিত্তি করে সংশ্লিষ্ট ট্রেডিং অপারেশন বাস্তবায়ন।

এটি দেখা যায় যে কৌশলটি ট্রেডিং ভিউতে পাইন স্ক্রিপ্ট অনুযায়ী OKX সিমুলেশন পরিবেশে সিঙ্ক্রোনাইজড সিগন্যাল ট্রেডিং সম্পাদন করে।

কৌশল ঠিকানা

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

এফএমজেড কোয়ান্টের প্রতি আপনার মনোযোগের জন্য ধন্যবাদ, এবং পড়ার জন্য ধন্যবাদ।


আরো