[TOC]

Os traders que usam frequentemente o TradingView sabem que o TradingView pode enviar mensagens para outras plataformas. Anteriormente, uma estratégia de push de sinal do TradingView era publicada na biblioteca de documentos. O conteúdo da mensagem push era codificado na URL de solicitação, o que era um tanto inflexível. Neste artigo, redesenharemos uma estratégia de execução de sinal do TradingView de uma maneira diferente.
Alguns alunos novatos podem ficar confusos ao ver o título deste artigo e a descrição acima, não importa! Vamos primeiro esclarecer os cenários e princípios de demanda. Vou deixar você saber do que estou falando. Ok, vamos direto ao ponto.
Cenário de demanda: Depois de toda essa conversa, o que essa coisa vai fazer? Simplificando, temos muitos indicadores, estratégias, códigos, etc. que podemos escolher usar no TradingView. Eles podem ser executados diretamente no TradingView, e podem desenhar linhas, calcular, exibir sinais de negociação, etc. Além disso, o TradingView tem dados de preços em tempo real e dados K-line suficientes para facilitar o cálculo de vários indicadores. Esses códigos de script no TradingView são chamados de linguagem PINE. O único inconveniente é a negociação real no TradingView. Embora a linguagem PINE já seja suportada no FMZ, ela também pode ser executada em tempo real. No entanto, também há fãs obstinados do TradingView que ainda esperam fazer ordens com base nos sinais enviados pelos gráficos no TradingView. Essa demanda também pode ser resolvida pela FMZ. Então este artigo explica os detalhes específicos desta solução.
Princípio:

Todo o plano envolve quatro corpos principais, que são resumidamente:
| número de série | corpo principal | descrever |
|---|---|---|
| 1 | TradingView (Visualização de negociação na imagem) | O TradingView executa o script PINE, que pode enviar sinais e acessar a interface de API estendida da FMZ. |
| 2 | Plataforma FMZ (plataforma FMZ (site) na imagem) | Gerencie o mercado real, envie instruções interativas na página do mercado real e também use a interface API estendida para permitir que a plataforma FMZ envie instruções interativas para o programa de estratégia de mercado real no custodiante |
| 3 | O programa real no software de hospedagem (robô de estratégia FMZ na imagem) | O programa real que executa a estratégia de execução do sinal TradingView |
| 4 | Troca (troca na imagem) | A bolsa configurada no mercado real, a bolsa para a qual o programa do mercado real no custodiante envia diretamente a solicitação para colocar uma ordem |
Então, se você quiser tocar assim, você precisa dos seguintes preparativos: 1. O script em execução no TradingView é responsável por enviar solicitações de sinal para a interface API estendida do FMZ. A conta TradingView precisa ser pelo menos um membro PRO. 2. Implante um programa de custódia na FMZ, que precisa ter acesso à interface de câmbio (como servidores em Cingapura, Japão, Hong Kong, etc.). 3. Configure a CHAVE API da bolsa na FMZ para operar (colocar uma ordem) quando o sinal TradingView for enviado. 4. Você precisa ter uma “estratégia de execução de sinal do TradingView”, que é sobre o que este artigo fala principalmente.
A versão anterior da “Estratégia de execução de sinal do TradingView” não foi projetada para ser flexível, e a mensagem só podia ser codificada na URL da solicitação enviada pelo TradingView. Se quisermos que o TradingView escreva alguma informação variável no Corpo ao enviar uma mensagem, não há nada que possamos fazer neste momento. Por exemplo, o conteúdo da mensagem no TradingView se parece com isto:

Em seguida, o TradingView pode ser configurado conforme mostrado na figura, escrevendo a mensagem no corpo da solicitação e enviando-a para a interface de API estendida da FMZ. Então, como você chama essa interface de API estendida do FMZ?
Dentre as séries de interfaces API estendidas do FMZ, vamos utilizarCommandRobotEssa interface geralmente é chamada assim:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
Esta URL de solicitaçãoqueryEmaccess_keyesecret_keyÉ uma extensão da plataforma FMZAPI KEY, aqui demonstramos como configurá-lo paraxxxeyyyy. Como criar esta CHAVE? Nesta página:https://www.fmz.com/m/account, basta criar um, guardá-lo adequadamente e nunca divulgá-lo.

Voltando ao assunto, vamos continuarCommandRobotProblema de interface. Se você precisar acessarCommandRobotInterface, na solicitaçãomethodBasta configurá-lo para:CommandRobot。CommandRobotA função desta interface é enviar uma mensagem interativa para um disco real de um determinado ID através da plataforma FMZ, portanto os parâmetrosargsO conteúdo da solicitação é o ID real e a mensagem. O exemplo de URL de solicitação acima é para enviar uma solicitação ao ID186515Programa real, envie mensagemok12345。
Anteriormente, esse método era usado para solicitar a interface CommandRobot da API de extensão FMZ. A mensagem só podia ser codificada, como no exemplo acima.ok12345. Se a mensagem estiver no corpo da solicitação, outro método será necessário:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]
Desta forma, a solicitação pode ser enviada através da plataforma FMZ, e o conteúdo do corpo da solicitação é enviado como uma mensagem interativa ao usuário com ID130350O verdadeiro negócio. Se a mensagem no TradingView estiver definida como:{"close": {{close}}, "name": "aaa"}, então o ID é130350O disco real receberá instruções interativas:{"close": 39773.75, "name": "aaa"}
Para que a “Estratégia de Execução de Sinal do TradingView” entenda corretamente a instrução enviada pelo TradingView ao receber a instrução interativa, o formato da mensagem deve ser previamente acordado:
{
Flag: "45M103Buy", // 标识,可随意指定
Exchange: 1, // 指定交易所交易对
Currency: "BTC_USDT", // 交易对
ContractType: "swap", // 合约类型,swap,quarter,next_quarter,现货填写spot
Price: "{{close}}", // 开仓或者平仓价格,-1为市价
Action: "buy", // 交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]
Amount: "0", // 交易量
}
A estratégia é projetada como uma arquitetura de múltiplas bolsas, de modo que vários objetos de bolsa podem ser configurados nessa estratégia, o que significa que as operações de ordens de várias contas diferentes podem ser controladas. Basta usar Exchange na estrutura do sinal para especificar a exchange a ser operada. Definir como 1 significa que esse sinal operará a conta de exchange correspondente ao primeiro objeto de exchange adicionado. Se o contrato à vista for operado, defina ContractType como à vista; para futuros, escreva o contrato específico, por exemplo, escreva swap para contratos perpétuos. Para preço de ordem de mercado, basta passar -1. As configurações de ação são diferentes para posições futuras, à vista, de abertura e de fechamento e não podem ser definidas incorretamente.
Em seguida, você pode projetar o código de estratégia. O código de estratégia completo é:
//信号结构
var Template = {
Flag: "45M103Buy", // 标识,可随意指定
Exchange: 1, // 指定交易所交易对
Currency: "BTC_USDT", // 交易对
ContractType: "swap", // 合约类型,swap,quarter,next_quarter,现货填写spot
Price: "{{close}}", // 开仓或者平仓价格,-1为市价
Action: "buy", // 交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]
Amount: "0", // 交易量
}
var BaseUrl = "https://www.fmz.com/api/v1" // FMZ扩展API接口地址
var RobotId = _G() // 当前实盘ID
var Success = "#5cb85c" // 成功颜色
var Danger = "#ff0000" // 危险颜色
var Warning = "#f0ad4e" // 警告颜色
var buffSignal = []
// 校验信号消息格式
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 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("模拟TradingView的webhook请求,发送用于测试的POST请求:", url, "body:", cmd, "应答:", 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("创建任务:", 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
}
var manager = createManager()
function HandleCommand(signal) {
// 检测是否收到交互指令
if (signal) {
Log("收到交互指令:", signal) // 收到交互指令,打印交互指令
} else {
return // 没有收到时直接返回,不做处理
}
// 检测交互指令是否是测试指令,测试指令可以由当前策略交互控件发出来进行测试
if (signal.indexOf("TestSignal") != -1) {
signal = signal.replace("TestSignal:", "")
// 调用FMZ扩展API接口,模拟Trading View的webhook,交互按钮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("执行调试代码:", js)
eval(js)
} else {
// 处理信号指令
objSignal = JSON.parse(signal)
if (DiffObject(Template, objSignal)) {
Log("接收到交易信号指令:", objSignal)
buffSignal.push(objSignal)
// 检查交易量、交易所编号
if (!CheckSignal(objSignal)) {
return
}
// 创建任务
manager.newTask(objSignal)
} else {
Log("指令无法识别", signal)
}
}
}
function main() {
Log("WebHook地址:", "https://www.fmz.com/api/v1?access_key=" + FMZ_AccessKey + "&secret_key=" + FMZ_SecretKey + "&method=CommandRobot&args=[" + RobotId + ',+""]', Danger)
Log("交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]", Danger)
Log("指令模板:", JSON.stringify(Template), Danger)
while (true) {
try {
// 处理交互
HandleCommand(GetCommand())
// 处理任务
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) + "`")
Sleep(1000 * SleepInterval)
} catch (error) {
Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
Sleep(1000 * 10)
}
}
}
Parâmetros de estratégia e interações:

「Estratégia de execução de sinal TradingView」Endereço da estratégia completa: https://www.fmz.com/strategy/392048
Antes de executar a estratégia, você deve configurar o objeto de troca e definir os dois parâmetros “AccessKey of FMZ platform” e “SecretKey of FMZ platform” nos parâmetros da estratégia. Certifique-se de não defini-los incorretamente. Executando-o mostra:

O seguinte será impresso em sequência: o endereço do WebHook que precisa ser preenchido no TradingView, as instruções de ação suportadas e o formato da mensagem. O importante é o endereço do WebHook:
https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]
Basta copiar e colar e escrever na posição correspondente no TradingView.
Se você quiser simular o TradingView enviando um sinal, você pode clicar no botão TestSignal na interação da estratégia:

Esta estratégia enviará uma solicitação por si só (simulando o envio de uma solicitação de sinal pelo TradingView), chamará a interface da API estendida do FMZ e enviará uma mensagem para a própria estratégia:
{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}
A estratégia atual receberá outra mensagem interativa e será executada:

E faça um pedido.
Para usar o teste TradingView, você precisa de uma conta TradingView nível Pro. Antes do teste, há alguns conhecimentos pré-requisitos que precisam ser brevemente explicados.
Tome como exemplo um script PINE simples (eu o encontrei no TradingView e o modifiquei um pouco)
//@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)
Os seguintes são marcadores de posição. Por exemplo, escrevi na caixa “Message” no alarme:{{strategy.order.contracts}}, então quando uma ordem for acionada, uma mensagem será enviada (com base nas configurações do alarme, envio de e-mail, solicitação de URL do webhook, janela pop-up, etc.), e a mensagem incluirá a quantidade da ordem executada neste momento. tempo.
{{strategy.position_size}} - Retorna o valor da mesma palavra-chave no Pine, que é o tamanho da posição atual.
{{strategy.order.action}} - Retorna a string “comprar” ou “vender” para uma ordem executada.
{{strategy.order.contracts}} - Retorna o número de contratos para a ordem executada.
{{strategy.order.price}} - Retorna o preço pelo qual a ordem foi executada.
{{strategy.order.id}} - Retorna o ID da ordem executada (uma string usada como o primeiro argumento em uma das chamadas de função que geraram uma ordem: strategy.entry, strategy.exit ou strategy.order).
{{strategy.order.comment}} - Retorna o comentário da ordem executada (a string usada no parâmetro comment em uma das chamadas de função que geraram a ordem: strategy.entry, strategy.exit ou strategy.order). Se nenhuma anotação for especificada, o valor de strategy.order.id será usado.
{{strategy.order.alert_message}} - Retorna o valor do parâmetro alert_message, que pode ser usado no código Pine da estratégia ao chamar uma das funções para colocar ordens: strategy.entry, strategy.exit ou strategy.order. Este recurso só é suportado no Pine v4.
{{strategy.market_position}} - Retorna a posição atual da estratégia como uma string: “longa”, “plana” ou “curta”.
{{strategy.market_position_size}} - Retorna o tamanho da posição atual como um valor absoluto (ou seja, um número não negativo).
{{strategy.prev_market_position}} - Retorna a última posição da estratégia como uma string: “longa”, “plana” ou “curta”.
{{strategy.prev_market_position_size}} - Retorna o tamanho da posição anterior como um valor absoluto (ou seja, um número não negativo).
{
"Flag":"{{strategy.order.id}}",
"Exchange":1,
"Currency":"BTC_USDT",
"ContractType":"swap",
"Price":"-1",
"Action":"{{strategy.order.comment}}",
"Amount":"{{strategy.order.contracts}}"
}

Quando o script PINE no TradingView aciona uma ação de negociação, uma solicitação de URL de webhook é enviada.


A negociação real da FMZ executará este sinal.


Vídeo Xigua: https://www.ixigua.com/7172134169580372513?utm_source=xiguastudio Estação B: https://www.bilibili.com/video/BV1BY411d7c6/ Zhihu: https://www.zhihu.com/zvideo/1581722694294487040
Os códigos no artigo são apenas para referência. Você pode ajustá-los e expandi-los para uso real.