Các nhà giao dịch thường sử dụng TradingView biết rằng TradingView có thể đẩy tin nhắn đến các nền tảng khác. Trong nền tảng
Một số người mới có thể bị nhầm lẫn bởi tiêu đề của bài viết này và mô tả ở trên, nó không quan trọng! Hãy bắt đầu với một mô tả rõ ràng về các kịch bản và nguyên tắc nhu cầu.
Các kịch bản nhu cầu: Vì vậy, chúng ta muốn nó làm công việc gì? Nói đơn giản, chúng ta có rất nhiều chỉ số, chiến lược, mã, v.v. mà chúng ta có thể chọn sử dụng trên TradingView, có thể chạy trực tiếp trên TradingView để vẽ đường, tính toán và hiển thị tín hiệu giao dịch. Ngoài ra, TradingView có dữ liệu giá thời gian thực và đủ dữ liệu đường K để tạo điều kiện cho việc tính toán các chỉ số khác nhau. Các mã kịch bản trên TradingView được gọi là ngôn ngữ PINE. Điều duy nhất không thuận tiện là giao dịch bot thực trên TradingView. Mặc dù ngôn ngữ PINE được hỗ trợ trên FMZ, nó cũng có thể được sử dụng cho giao dịch bot thực. Tuy nhiên, vẫn có một số người hâm mộ TradingView vẫn muốn đặt lệnh bằng cách sử dụng các tín hiệu từ các biểu đồ trên TradingView, vì vậy điều này có thể được giải quyết bởi FMZ. Vì vậy, trong bài viết này, chúng tôi sẽ giải thích chi tiết về giải pháp.
Nguyên tắc:
Có 4 chủ đề liên quan đến toàn bộ chương trình này, ngắn gọn là như sau:
Vì vậy, nếu bạn muốn sử dụng nó theo những cách này, bạn cần những chuẩn bị này:
Thiết kế của
Sau đó, TradingView có thể được thiết lập như trong hình để viết thông điệp trong request Body và gửi nó đến giao diện API mở rộng của FMZ.
Trong một loạt các giao diện API mở rộng của FMZ, chúng ta cần sử dụng cácCommandRobot
giao diện, thường được gọi như sau:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
Cácaccess_key
vàsecret_key
trongquery
của URL yêu cầu này là mở rộngAPI KEY
của nền tảng FMZ, ở đây các bản demo thiết lập đểxxx
vàyyyy
Vậy làm thế nào để tạo khóa này?https://www.fmz.com/m/account
, tạo ra trên nó, giữ nó đúng cách, không tiết lộ nó.
Trở lại vấn đề, hãy tiếp tục nói về vấn đề giao diện củaCommandRobot
Nếu bạn cần truy cậpCommandRobot
giao diện,method
trong yêu cầu sẽ được thiết lập là:CommandRobot
. Chức năng củaCommandRobot
giao diện là để gửi một thông điệp tương tác đến một bot thực sự với một ID thông qua nền tảng FMZ, do đó, các tham sốargs
có chứa ID bot thực sự và tin nhắn. ví dụ url yêu cầu ở trên là để gửi tin nhắnok12345
đến một chương trình robot thực sự với ID 186515.
Trước đây, phương pháp này được sử dụng để yêu cầu giao diện CommandRobot của FMZ mở rộng API. Thông điệp chỉ có thể được viết trong ví dụ trên, chẳng hạn nhưok12345
. Nếu thông điệp là trong cơ quan yêu cầu, bạn cần phải sử dụng một phương pháp khác:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]
Bằng cách này, yêu cầu có thể gửi nội dung của cơ thể trong yêu cầu như một thông điệp tương tác đến bot thực sự với ID130350
thông qua nền tảng FMZ. Nếu thông báo trên TradingView được thiết lập là:{"close": {{close}}, "name": "aaa"}
, thì robot thực sự với ID của130350
sẽ nhận được hướng dẫn tương tác:{"close": 39773.75, "name": "aaa"}
Để chiến lược thực thi tín hiệu TradingView hiểu chính xác lệnh được gửi bởi TradingView khi nhận lệnh tương tác, các định dạng tin nhắn sau đây nên được thỏa thuận trước:
{
Flag: "45M103Buy", // Marker, which can be specified at will
Exchange: 1, // Specify exchange trading pairs
Currency: "BTC_USDT", // Trading pair
ContractType: "swap", // Contract type, swap, quarter, next_quarter, fill in spot for spot
Price: "{{close}}", // Opening position or closing position price, -1 is the market price
Action: "buy", // Transaction type [buy: spot buying, sell: spot selling, long: go long futures, short: go short futures, closesell: buy futures and close short positions, close buy: sell futures and close long positions]
Amount: "0", // Transaction amount
}
Chiến lược được thiết kế như một kiến trúc đa giao dịch, vì vậy nhiều đối tượng giao dịch có thể được cấu hình trên chiến lược này, tức là, hoạt động đặt lệnh của nhiều tài khoản khác nhau có thể được kiểm soát. Chỉ có trao đổi trong cấu trúc tín hiệu chỉ định giao dịch sẽ được vận hành. Cài đặt 1 là để cho phép tín hiệu này vận hành tài khoản giao dịch tương ứng với đối tượng giao dịch đầu tiên được thêm vào. Nếu Spot ContractType được thiết lập là Spot, các hợp đồng tương lai sẽ viết các hợp đồng cụ thể, chẳng hạn như trao đổi cho các hợp đồng vĩnh viễn. Danh sách giá thị trường có thể được truyền vào -1. Cài đặt hành động khác nhau cho các vị trí tương lai, Spot, mở và đóng, và nó không thể được đặt sai.
Tiếp theo, bạn có thể thiết kế mã chiến lược.
//Signal structure
var Template = {
Flag: "45M103Buy", // Marker, which can be specified at will
Exchange: 1, // Specify exchange trading pairs
Currency: "BTC_USDT", // Trading pair
ContractType: "swap", // Contract type, swap, quarter, next_quarter, fill in spot for spot
Price: "{{close}}", // Opening position or closing position price, -1 is the market price
Action: "buy", // Transaction type [buy: spot buying, sell: spot selling, long: go long futures, short: go short futures, closesell: buy futures and close short positions, close buy: sell futures and close long positions]
Amount: "0", // Transaction amount
}
var BaseUrl = "https://www.fmz.com/api/v1" // FMZ extended API interface address
var RobotId = _G() // Current real bot ID
var Success = "#5cb85c" // Color for success
var Danger = "#ff0000" // Color for danger
var Warning = "#f0ad4e" // Color for alert
var buffSignal = []
// 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 number of the exchange is 1 and it is an integer", Danger)
return
}
if (Signal.Amount <= 0 || typeof(Signal.Amount) != "number") {
Log("The transaction amount cannot be less than 0 and it is numerical type", typeof(Signal.Amount), Danger)
return
}
if (typeof(Signal.Price) != "number") {
Log("Price must be a value", Danger)
return
}
if (Signal.ContractType == "spot" && Signal.Action != "buy" && Signal.Action != "sell") {
Log("The command is to operate spot, Action error, Action:", Signal.Action, Danger)
return
}
if (Signal.ContractType != "spot" && Signal.Action != "long" && Signal.Action != "short" && Signal.Action != "closesell" && Signal.Action != "closebuy") {
Log("The command is to operate future, Action error, Action:", Signal.Action, Danger)
return
}
return true
}
function commandRobot(url, accessKey, secretKey, robotId, cmd) {
// https://www.fmz.com/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("Simulate a webhook request from TradingView, sending a POST request for testing purposes:", url, "body:", cmd, "response:", 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("Create 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) {
// Non-spot, then set the contract
e.SetContractType(task.ContractType)
} else if (task.ContractType == "spot" && name.indexOf("Futures_") == -1) {
isFutures = false
} else {
task.error = "The ContractType in the command does not match the configured exchange object type"
return
}
var depth = e.GetDepth()
if (!depth || !depth.Bids || !depth.Asks) {
task.error = "Order book data exception"
return
}
if (depth.Bids.length == 0 && depth.Asks.length == 0) {
task.error = "No orders on the market entry position"
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 obtain precision"
return
}
e.SetPrecision(self.pricePrecision, self.amountPrecision)
// buy: spot buying, sell: spot selling, long: go long futures, short: go short futures, closesell: buy futures and close short positions, close buy: sell futures and close long positions
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 = "Failed to place an order"
}
task.finished = true
}
return self
}
var manager = createManager()
function HandleCommand(signal) {
// Detect whether interactive command is received
if (signal) {
Log("Receive interactive command:", signal) // Receive the interactive command, print the interactive command
} else {
return // If it is not received, it will be returned directly without processing
}
// Check whether the interactive command is a test instruction. The test instruction can be sent out by the current strategy interaction control for testing
if (signal.indexOf("TestSignal") != -1) {
signal = signal.replace("TestSignal:", "")
// Call the FMZ extended API interface to simulate the webhook of the TradingView, and the message sent by the interactive button TestSignal: {"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
commandRobot(BaseUrl, FMZ_AccessKey, FMZ_SecretKey, RobotId, signal)
} else if (signal.indexOf("evalCode") != -1) {
var js = signal.split(':', 2)[1]
Log("Execute debug code:", js)
eval(js)
} else {
// Process signal command
objSignal = JSON.parse(signal)
if (DiffObject(Template, objSignal)) {
Log("Received transaction signal command:", objSignal)
buffSignal.push(objSignal)
// Check the trading volume and exchange number
if (!CheckSignal(objSignal)) {
return
}
// Create task
manager.newTask(objSignal)
} else {
Log("Command cannot be recognized", signal)
}
}
}
function main() {
Log("WebHook address:", "https://www.fmz.com/api/v1?access_key=" + FMZ_AccessKey + "&secret_key=" + FMZ_SecretKey + "&method=CommandRobot&args=[" + RobotId + ',+""]', Danger)
Log("Transaction type [buy: spot buying, sell: spot selling, long: go long futures, short: go short futures, closesell: buy futures and close short positions, close buy: sell futures and close long positions]", Danger)
Log("Command template:", JSON.stringify(Template), Danger)
while (true) {
try {
// Process interactions
HandleCommand(GetCommand())
// Process tasks
manager.process()
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) + "`")
Sleep(1000 * SleepInterval)
} catch (error) {
Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
Sleep(1000 * 10)
}
}
}
Các thông số chiến lược và tương tác:
Địa chỉ chiến lược đầy đủ của Chiến lược thực hiện tín hiệu Trading View:https://www.fmz.com/strategy/392048
Trước khi chạy chiến lược, đối tượng trao đổi nên được cấu hình và hai tham số
Nó sẽ in ra địa chỉ WebHook, hỗ trợ lệnh hành động, và định dạng tin nhắn cần phải điền vào trên TradingView.
https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]
Chỉ cần sao chép và dán nó trực tiếp vào vị trí tương ứng trên TradingView.
Nếu bạn muốn mô phỏng một tín hiệu được gửi bởi TradingView, bạn có thể nhấp vào nút TestSignal trên tương tác chiến lược.
Chiến lược này gửi một yêu cầu của riêng nó (giống như một TradingView gửi một yêu cầu tín hiệu), gọi giao diện API mở rộng FMZ
{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}
Chiến lược hiện tại sẽ nhận được một thông điệp tương tác khác và thực hiện, và đặt một lệnh cho giao dịch.
Sử dụng thử nghiệm TradingView đòi hỏi rằng tài khoản TradingView ở cấp Pro. Trước khi thử nghiệm, bạn cần biết một số kiến thức trước.
Hãy lấy một tập lệnh PINE đơn giản (bất định vị được tìm thấy và sửa đổi trên TradingView) làm ví dụ
//@version=5
strategy("Consecutive Up/Down Strategy", overlay=true)
consecutiveBarsUp = input(3)
consecutiveBarsDown = input(3)
price = close
ups = 0.0
ups := price > price[1] ? nz(ups[1]) + 1 : 0
dns = 0.0
dns := price < price[1] ? nz(dns[1]) + 1 : 0
if (not barstate.ishistory and ups >= consecutiveBarsUp and strategy.position_size <= 0)
action = strategy.position_size < 0 ? "closesell" : "long"
strategy.order("ConsUpLE", strategy.long, 1, comment=action)
if (not barstate.ishistory and dns >= consecutiveBarsDown and strategy.position_size >= 0)
action = strategy.position_size > 0 ? "closebuy" : "short"
strategy.order("ConsDnSE", strategy.short, 1, comment=action)
Sau đây là các vị trí giữ. Ví dụ, nếu tôi viết{{strategy.order.contracts}}
trong hộp
{{strategy.position_size}}
- Trả về giá trị của cùng một từ khóa trong Pine, tức là kích thước của vị trí hiện tại.{{strategy.order.action}}
- Trả lại chuỗi {{strategy.order.contracts}}
- Trả về số lượng hợp đồng mà lệnh đã được thực hiện.{{strategy.order.price}}
- Trả lại giá của lệnh thực hiện.{{strategy.order.id}}
- Trả về ID của lệnh được thực thi (dòng chuỗi được sử dụng như là tham số đầu tiên trong một trong các cuộc gọi hàm tạo ra lệnh: strategy.entry,strategy.exithoặc chiến lược.{{strategy.order.comment}}
- Trả lại bình luận của lệnh được thực thi (dòng chuỗi được sử dụng trong tham số bình luận trong một trong các cuộc gọi hàm tạo ra lệnh: strategy.entry,strategy.exitNếu không có bình luận nào được chỉ định, giá trị củastrategy.order.idsẽ được sử dụng.{{strategy.order.alert_message}}
- Trả về giá trị của tham số alert_message có thể được sử dụng trong mã Pine của chiến lược khi gọi một trong những hàm được sử dụng để đặt lệnh: strategy.entry,strategy.exit, hoặc strategy.order. Điều này chỉ được hỗ trợ trong Pine v4.{{strategy.market_position}}
- Trả lại vị trí hiện tại của chiến lược dưới dạng chuỗi: {{strategy.market_position_size}}
- Trả về kích thước của vị trí hiện tại dưới dạng giá trị tuyệt đối (tức là một số không âm).{{strategy.prev_market_position}}
- Trả lại vị trí trước của chiến lược dưới dạng một chuỗi: {{strategy.prev_market_position_size}}
- Trả về kích thước của vị trí trước đó dưới dạng giá trị tuyệt đối (tức là một số không âm).
{
"Flag":"{{strategy.order.id}}",
"Exchange":1,
"Currency":"BTC_USDT",
"ContractType":"swap",
"Price":"-1",
"Action":"{{strategy.order.comment}}",
"Amount":"{{strategy.order.contracts}}"
}
Khi kịch bản PINE trên TradingView kích hoạt giao dịch, một yêu cầu url webhook sẽ được gửi.
Robot FMZ sẽ thực hiện tín hiệu này.
Mã trong bài viết này chỉ để tham khảo, và bạn có thể tự điều chỉnh và mở rộng nó khi sử dụng thực tế.