Трейдеры, которые часто используют TradingView, знают, что TradingView может пересылать сообщения на другие платформы. В нашей платформе
Некоторые новички могут быть озадачены названием этой статьи и описанием выше, это не имеет значения! Давайте начнем с четкого описания сценариев и принципов спроса.
Сценарии спроса: Итак, какую работу мы хотим, чтобы он делал? Проще говоря, у нас есть много индикаторов, стратегий, кодов и т. Д., Которые мы можем использовать на TradingView, которые можно запустить непосредственно на TradingView, чтобы нарисовать линии, рассчитывать и отображать торговые сигналы. Кроме того, TradingView имеет данные о ценах в режиме реального времени и достаточные данные K-линии, чтобы облегчить расчет различных индикаторов. Эти скриптовые коды на TradingView называются языком PINE. Единственное, что не удобно, это то, что реальный бот торгует на TradingView. Хотя язык PINE поддерживается на FMZ, он также может быть использован для реальной торговли ботами. Однако есть некоторые поклонники TradingView, которые все еще хотят размещать заказы, используя сигналы из графиков на TradingView, поэтому это может быть решено FMZ. Так что в этой статье мы объясним подробности решения.
Принцип:
В целом программа включает в себя 4 темы, которые, вкратце, следующие:
Поэтому, если вы хотите использовать его таким образом, вам нужны следующие приготовления:
Конструкция
Затем TradingView можно настроить, как показано на рисунке, чтобы написать сообщение в request Body и отправить его на расширенный интерфейс API FMZ. Как вызвать расширенный интерфейс API FMZ?
В серии расширенных API интерфейсов FMZ, мы должны использоватьCommandRobot
интерфейс, который обычно называется следующим образом:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
Вaccess_key
иsecret_key
вquery
URL этого запроса расширенныйAPI KEY
Платформа FMZ, здесь демо установлено наxxx
иyyyy
. Тогда как создать этот Ключ? На этой странице:https://www.fmz.com/m/account
, создавать на нем, держать его должным образом, не разглашать его.
Возвращаясь к теме, давайте продолжим говорить о проблеме интерфейсаCommandRobot
Если вам нужен доступ кCommandRobot
интерфейс,method
в запросе будет установлено:CommandRobot
ФункцияCommandRobot
Интерфейс - это отправка интерактивного сообщения реального бота с идентификатором через платформу FMZ, поэтому параметрargs
Пример запроса url выше предназначен для отправки сообщенияok12345
к настоящей ботовой программе с идентификатором 186515.
Ранее этот метод использовался для запроса интерфейса CommandRobot расширенного API FMZ. Сообщения могут быть написаны только в приведенном выше примере, например,ok12345
Если сообщение находится в запрошенном органе, вам нужно использовать другой метод:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]
Таким образом, запрос может отправить содержимое тела в запросе в виде интерактивного сообщения к реальному боту с ID130350
Если сообщение на TradingView настроено на:{"close": {{close}}, "name": "aaa"}
, тогда настоящий бот с идентификатором130350
будет получать интерактивные инструкции:{"close": 39773.75, "name": "aaa"}
Для того чтобы
{
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
}
Стратегия разработана как многообменная архитектура, поэтому на этой стратегии можно настроить несколько обменных объектов, то есть можно контролировать операцию размещения заказов нескольких разных счетов. Только биржа в структуре сигнала указывает обмен, который будет эксплуатироваться. Настройка 1 позволяет этому сигналу управлять обменным счетом, соответствующим первому добавленному обменному объекту. Если спот ContractType установлен на спот, фьючерсы будут писать конкретные контракты, такие как своп на вечные контракты. Рыночный ценовой список может передаваться в -1. Настройки действия различны для фьючерсов, спота, открытия и закрытия позиций, и его нельзя установить неправильно.
Далее, вы можете разработать код стратегии.
//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)
}
}
}
Параметры стратегии и взаимодействия:
Полный адрес стратегии
Прежде чем запустить стратегию, объект обмена должен быть настроен, и два параметра
Он распечатает адрес WebHook, поддерживаемые команды Action и формат сообщения, которые необходимо заполнить в TradingView.
https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]
Просто скопируйте и вставьте его прямо в соответствующее место в TradingView.
Если вы хотите имитировать сигнал, отправленный TradingView, вы можете нажать кнопку TestSignal на взаимодействии стратегии.
Эта стратегия отправляет свой собственный запрос (симулируя TradingView, отправляющий запрос на сигнал), вызывая расширенный интерфейс API FMZ
{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}
Текущая стратегия получит еще одно интерактивное сообщение и выполнит и разместит заказ на транзакцию.
Использование теста TradingView требует, чтобы учетная запись TradingView была на уровне Pro.
Взять простой скрипт PINE (случайно найден и изменен на TradingView) в качестве примера
//@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)
Следующие являются заполнителями места.{{strategy.order.contracts}}
в поле
{{strategy.position_size}}
- Верните значение того же ключевого слова в Pine, т.е. размер текущей позиции.{{strategy.order.action}}
- Верните строку {{strategy.order.contracts}}
- Укажите количество контрактов, по которым были выполнены заказы.{{strategy.order.price}}
- Верните цену исполненного ордера.{{strategy.order.id}}
- возвращает идентификатор выполненного ордера (строка, используемая в качестве первого параметра в одном из вызовов функций, которые генерируют ордер: strategy.entry,strategy.exitили стратегии.{{strategy.order.comment}}
- возвращает комментарий выполненного ордера (строка, используемая в параметре комментариев в одном из вызовов функций, которые генерируют ордер: strategy.entry,strategy.exitЕсли нет комментариев, значениеstrategy.order.idбудет использоваться.{{strategy.order.alert_message}}
- возвращает значение параметра alert_message, которое может быть использовано в коде стратегии{{strategy.market_position}}
- Верните текущее положение стратегии в виде строки: {{strategy.market_position_size}}
- возвращает размер текущей позиции в виде абсолютного значения (то есть не отрицательного числа).{{strategy.prev_market_position}}
- Верните предыдущее положение стратегии в виде строки: {{strategy.prev_market_position_size}}
- возвращает размер предыдущей позиции в виде абсолютного значения (то есть не отрицательного числа).
{
"Flag":"{{strategy.order.id}}",
"Exchange":1,
"Currency":"BTC_USDT",
"ContractType":"swap",
"Price":"-1",
"Action":"{{strategy.order.comment}}",
"Amount":"{{strategy.order.contracts}}"
}
Когда скрипт PINE на TradingView запускает транзакцию, будет отправлен запрос на URL-адрес веб-вхока.
Настоящий бот FMZ выполняет этот сигнал.
Код в этой статье предназначен только для справки, и его можно корректировать и расширять самостоятельно при фактическом использовании.