পূর্ববর্তী নিবন্ধেএফএমজেড প্ল্যাটফর্মের বাহ্যিক সংকেত গ্রহণের বিষয়ে আলোচনাঃ এক্সটেন্ডেড এপিআই বনাম কৌশল অন্তর্নির্মিত এইচটিটিপি পরিষেবা, আমরা প্রোগ্রাম্যাটিক ট্রেডিংয়ের জন্য বাহ্যিক সংকেত গ্রহণের দুটি ভিন্ন উপায়ের তুলনা করেছি এবং বিশ্লেষণ করেছি। বাহ্যিক সংকেত গ্রহণের জন্য 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)
}
}
}
বহিরাগত সংকেত অ্যাক্সেস করার জন্য বর্ধিত এপিআই ব্যবহার করার কৌশল তুলনায়, কৌশল অনেক পরিবর্তন হয় না। এটি শুধুমাত্র একটি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 body এর সেটিংস মূলত বহিরাগত সংকেত অ্যাক্সেস করার পূর্ববর্তী বর্ধিত API পদ্ধতির মতোই। একই অংশগুলি এই নিবন্ধে পুনরাবৃত্তি করা হবে না। আপনি পূর্ববর্তী নিবন্ধটি উল্লেখ করতে পারেন।
ট্রেডিং ভিউতে আমরা এই পাইন স্ক্রিপ্টটি একটি মার্কেটের চার্টে যুক্ত করার পরে (আমরা পরীক্ষার জন্য বিন্যান্সের ETH_USDT চিরস্থায়ী চুক্তি বাজারটি বেছে নিই), আমরা দেখতে পাচ্ছি যে স্ক্রিপ্টটি কাজ শুরু করেছে। তারপর আমরা স্ক্রিপ্টটিতে একটি সতর্কতা যুক্ত করি যেমন স্ক্রিনশটে দেখানো হয়েছে।
ওয়েবহুক URL সেটিংসঃ স্ট্র্যাটেজি কোডটি স্বয়ংক্রিয়ভাবে ওয়েবহুক ইউআরএল তৈরি করার জন্য ডিজাইন করা হয়েছে। আমাদের কেবল কৌশল অপারেশনের শুরুতে লগ থেকে এটি অনুলিপি করতে হবে।
http://xxx.xxx.xxx.xxx:80/CommandRobot?passPhrase=test123456
ট্রেডিং ভিউতে উল্লেখ করা হয়েছে যে ওয়েবহুক ইউআরএল শুধুমাত্র HTTP অনুরোধের জন্য পোর্ট 80 ব্যবহার করতে পারে, তাই আমরা কৌশলটিতে পোর্ট প্যারামিটারটি 80 এ সেট করি, যাতে আমরা দেখতে পারি যে কৌশল দ্বারা উত্পন্ন ওয়েবহুক ইউআরএলের লিঙ্ক পোর্টটিও 80।
তারপর আমরা
{
"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")
সুতরাং সেটিংস ধারাবাহিক হতে হবে। এখানে আমরা অর্ডার ফাংশনের জন্য
PINE স্ক্রিপ্ট প্রতিটি অর্ডারের জন্য অর্ডার পরিমাণ নির্দিষ্ট করে না, তাই যখন ট্রেডিং ভিউ একটি সতর্কতা বার্তা প্রেরণ করে, এটি
যখন ট্রেডিং ভিউতে চলমান পাইন স্ক্রিপ্ট ট্রেডিং ফাংশনটি সম্পাদন করে, কারণ আমরা ওয়েবহুক ইউআরএল সতর্কতা সেট করেছি, ট্রেডিং ভিউ প্ল্যাটফর্মটি আমাদের কৌশলটির অন্তর্নির্মিত এইচটিটিপি পরিষেবাতে একটি পিওএসটি অনুরোধ প্রেরণ করবে। এই অনুরোধ অনুসন্ধানে একটি পাসওয়ার্ড প্যারামিটার রয়েছেpassPhrase
যাচাইয়ের জন্য। প্রকৃত অনুরোধকারী সংস্থাটি এইরকমঃ
তারপর আমাদের কৌশল এই শরীরের বার্তা উপর ভিত্তি করে সংশ্লিষ্ট ট্রেডিং অপারেশন বাস্তবায়ন।
এটি দেখা যায় যে কৌশলটি ট্রেডিং ভিউতে পাইন স্ক্রিপ্ট অনুযায়ী OKX সিমুলেশন পরিবেশে সিঙ্ক্রোনাইজড সিগন্যাল ট্রেডিং সম্পাদন করে।
এফএমজেড কোয়ান্টের প্রতি আপনার মনোযোগের জন্য ধন্যবাদ, এবং পড়ার জন্য ধন্যবাদ।