[TOC]

TradingView를 자주 사용하는 트레이더라면 TradingView에서 다른 플랫폼으로 메시지를 푸시할 수 있다는 사실을 알고 있습니다. 이전에는 TradingView 신호 푸시 전략이 문서 라이브러리에 게시되었습니다. 푸시된 메시지의 내용은 요청 URL에 하드 코딩되어 있어 다소 유연하지 않았습니다. 이 글에서는 TradingView 신호 실행 전략을 다른 방식으로 재설계해보겠습니다.
일부 초보 학생들은 이 기사의 제목과 위의 설명을 보고 혼란스러워할 수 있습니다. 상관없습니다! 먼저 수요 시나리오와 원칙을 명확히 해보겠습니다. 내가 무슨 말을 하는지 알려드리겠습니다. 좋습니다. 바로 요점으로 들어가겠습니다.
수요 시나리오: 이렇게 많은 이야기가 오갔는데, 이게 무슨 역할을 하는 거지? 간단히 말해서, TradingView에서 사용할 수 있는 많은 지표, 전략, 코드 등이 있습니다. 이는 TradingView에서 직접 실행할 수 있으며, 선을 그리거나, 계산하거나, 거래 신호를 표시하는 등의 작업을 할 수 있습니다. 또한, TradingView는 실시간 가격 데이터와 충분한 K-라인 데이터를 제공하여 다양한 지표를 계산하는 데 도움이 됩니다. TradingView의 이러한 스크립트 코드는 PINE 언어라고 합니다. 유일한 불편함은 TradingView에서 실제 거래하는 것입니다. PINE 언어는 이미 FMZ에서 지원되지만 실시간으로 실행할 수도 있습니다. 그러나 TradingView의 차트에서 전송된 신호에 따라 주문을 하기를 여전히 바라는 TradingView의 열렬한 팬도 있습니다. 이 수요는 FMZ에서도 해결할 수 있습니다. 따라서 이 문서에서는 이 솔루션의 구체적인 세부 사항을 설명합니다.
원리:

전체 계획에는 4개의 주요 기관이 포함되며, 간략히 설명하면 다음과 같습니다.
| 일련번호 | 본체 | 설명하다 |
|---|---|---|
| 1 | TradingView (그림 속의 Trading View) | TradingView는 PINE 스크립트를 실행하는데, 이 스크립트는 신호를 보내고 FMZ의 확장된 API 인터페이스에 접근할 수 있습니다. |
| 2 | FMZ 플랫폼(사진 속 FMZ 플랫폼(웹사이트)) | 실제 시장을 관리하고 실제 시장 페이지에서 대화형 지침을 보내고 확장된 API 인터페이스를 사용하여 FMZ 플랫폼이 관리자의 실제 시장 전략 프로그램에 대화형 지침을 보낼 수 있도록 합니다. |
| 3 | 호스팅 소프트웨어의 실제 프로그램(사진 속 FMZ 전략 로봇) | TradingView 신호 실행 전략을 실행하는 실제 프로그램 |
| 4 | 교환(그림 속 교환) | 실제 시장에서 구성된 거래소, 보관 기관의 실제 시장 프로그램이 직접 주문을 하도록 요청을 보내는 거래소 |
따라서 이런 방식으로 플레이하려면 다음과 같은 준비가 필요합니다. 1. TradingView에서 실행되는 스크립트는 FMZ의 확장 API 인터페이스로 신호 요청을 보내는 역할을 합니다. TradingView 계정은 최소한 PRO 멤버여야 합니다. 2. FMZ에 보관 프로그램을 배포합니다. 이 프로그램은 거래소 인터페이스(예: 싱가포르, 일본, 홍콩 등의 서버)에 액세스할 수 있어야 합니다. 3. TradingView 신호가 전송될 때 작동(주문 실행)할 FMZ 거래소의 API KEY를 구성합니다. 4. 이 글에서 주로 언급하는 “TradingView 신호 실행 전략”이 필요합니다.
“TradingView Signal Execution Strategy”의 이전 버전은 유연하게 설계되지 않았으며, 메시지는 TradingView에서 보낸 요청의 URL에만 하드 코딩될 수 있었습니다. 메시지를 푸시할 때 TradingView에서 Body에 일부 변수 정보를 쓰게 하려는 경우, 지금으로서는 할 수 있는 일이 없습니다. 예를 들어, TradingView의 메시지 내용은 다음과 같습니다.

그런 다음 그림과 같이 TradingView를 설정하고 요청 본문에 메시지를 작성하여 FMZ의 확장 API 인터페이스로 전송할 수 있습니다. 그러면 FMZ의 확장된 API 인터페이스를 어떻게 호출하나요?
FMZ의 확장된 API 인터페이스 시리즈 중에서 우리는 다음을 사용할 것입니다.CommandRobot이 인터페이스는 일반적으로 다음과 같이 호출됩니다.
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
이 요청 URLquery~ 안에access_key그리고secret_keyFMZ 플랫폼의 확장입니다API KEY, 그래서 여기서 우리는 그것을 설정합니다xxx그리고yyyy. 이 KEY를 어떻게 생성하나요? 이 페이지에서:https://www.fmz.com/m/account그냥 하나만 만들어서 제대로 보관하고 절대로 공개하지 마세요.

본론으로 돌아와서 계속해 보겠습니다.CommandRobot인터페이스 문제. 접근해야 하는 경우CommandRobot요청에서 인터페이스method다음과 같이 설정하세요:CommandRobot。CommandRobot이 인터페이스의 기능은 FMZ 플랫폼을 통해 특정 ID의 실제 디스크에 대화형 메시지를 보내는 것입니다.args요청의 내용은 실제 ID와 메시지입니다. 위의 요청 URL 예는 ID에 요청을 보내는 것입니다.186515실제 프로그램, 메시지 보내기ok12345。
이전에는 이 메서드가 FMZ 확장 API의 CommandRobot 인터페이스를 요청하는 데 사용되었습니다. 메시지는 위의 예와 같이 하드코딩만 가능합니다.ok12345. 메시지가 요청 본문에 있는 경우 다른 방법이 필요합니다.
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]
이런 방식으로 FMZ 플랫폼을 통해 요청을 보낼 수 있으며, 요청 본문의 내용은 ID가 있는 사용자에게 대화형 메시지로 전송됩니다.130350진짜입니다. TradingView의 메시지가 다음과 같이 설정된 경우:{"close": {{close}}, "name": "aaa"}, 그러면 ID는130350실제 디스크는 다음과 같은 대화형 지침을 받게 됩니다.{"close": 39773.75, "name": "aaa"}
“TradingView Signal Execution Strategy”가 대화형 지시를 받을 때 TradingView에서 보낸 지시를 올바르게 이해하려면 메시지 형식이 사전에 합의되어야 합니다.
{
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", // 交易量
}
이 전략은 다중 거래소 아키텍처로 설계되었으므로 이 전략에 여러 거래소 객체를 구성할 수 있으며, 즉 여러 계정의 주문 작업을 제어할 수 있습니다. 신호 구조에서 Exchange를 사용하여 작동할 거래소를 지정하기만 하면 됩니다. 1로 설정하면 이 신호가 첫 번째로 추가된 거래소 객체에 해당하는 거래소 계정을 작동시킨다는 의미입니다. 현물 계약을 운영하려면 ContractType을 현물로 설정하고, 선물 계약의 경우 특정 계약을 작성합니다. 예를 들어, 영구 계약의 경우 스왑을 작성합니다. 시장가 주문 가격의 경우 -1만 전달하면 됩니다. 액션 설정은 선물, 현물, 개시 및 마감 포지션마다 다르며, 잘못 설정할 수 없습니다.
다음으로, 전략 코드를 설계할 수 있습니다. 전체 전략 코드는 다음과 같습니다.
//信号结构
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)
}
}
}
전략 매개변수 및 상호 작용:

「TradingView Signal Execution Strategy」전체 전략 주소: https://www.fmz.com/strategy/392048
전략을 실행하기 전에 교환 객체를 구성하고 전략 매개변수에서 두 매개변수 “FMZ 플랫폼의 AccessKey”와 “FMZ 플랫폼의 SecretKey”를 설정해야 합니다. 이를 잘못 설정하지 않도록 주의하세요. 실행하면 다음이 표시됩니다.

다음 내용이 순서대로 출력됩니다: TradingView에 입력해야 하는 WebHook 주소, 지원되는 작업 지침 및 메시지 형식입니다. 중요한 것은 WebHook 주소입니다:
https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]
복사하여 붙여넣은 다음 TradingView의 해당 위치에 입력하세요.
TradingView가 신호를 보내는 것을 시뮬레이션하려면 전략 상호 작용에서 TestSignal 버튼을 클릭하면 됩니다.

이 전략은 자체적으로 요청을 보내고(TradingView가 신호 요청을 보내는 것을 시뮬레이션함), FMZ 확장 API 인터페이스를 호출하고, 전략 자체에 메시지를 보냅니다.
{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}
현재 전략은 또 다른 대화형 메시지를 받고 실행합니다.

그리고 주문을 하세요.
TradingView 테스트를 사용하려면 Pro 레벨의 TradingView 계정이 필요합니다. 테스트하기 전에 간단히 설명해야 할 몇 가지 전제 지식이 있습니다.
간단한 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}}그런 다음 주문이 트리거되면 메시지가 전송됩니다(알람, 이메일 푸시, 웹훅 URL 요청, 팝업 창 등의 설정에 따라 다름). 메시지에는 이번 주문에 실행된 주문 수량이 포함됩니다. 시간.
{{strategy.position_size}} - Pine에서 동일한 키워드의 값을 반환합니다. 즉, 현재 위치의 크기입니다.
{{strategy.order.action}} - 실행된 주문에 대해 문자열 “구매” 또는 “판매”를 반환합니다.
{{strategy.order.contracts}} - 실행된 주문에 대한 계약 수를 반환합니다.
{{strategy.order.price}} - 주문이 실행된 가격을 반환합니다.
{{strategy.order.id}} - 실행된 주문의 ID를 반환합니다(주문을 생성한 함수 호출 중 하나에서 첫 번째 인수로 사용되는 문자열: strategy.entry, strategy.exit 또는 strategy.order).
{{strategy.order.comment}} - 실행된 주문의 주석을 반환합니다(주문을 생성한 함수 호출 중 하나인 strategy.entry, strategy.exit 또는 strategy.order의 주석 매개변수에 사용된 문자열). 주석이 지정되지 않으면 strategy.order.id의 값이 사용됩니다.
{{strategy.order.alert_message}} - strategy.entry, strategy.exit 또는 strategy.order와 같은 주문을 위한 함수 중 하나를 호출할 때 전략의 Pine 코드에서 사용할 수 있는 alert_message 매개변수 값을 반환합니다. 이 기능은 Pine v4에서만 지원됩니다.
{{strategy.market_position}} - 전략의 현재 위치를 문자열로 반환합니다: “long”, “flat”, 또는 “short”.
{{strategy.market_position_size}} - 현재 위치의 크기를 절대값(음수가 아닌 숫자)으로 반환합니다.
{{strategy.prev_market_position}} - 전략의 마지막 포지션을 문자열로 반환합니다: “long”, “flat”, 또는 “short”.
{{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}}"
}

TradingView의 PINE 스크립트가 거래 작업을 트리거하면 웹후크 URL 요청이 전송됩니다.


FMZ의 실제 거래는 이 신호를 실행합니다.


시과 비디오: https://www.ixigua.com/7172134169580372513?utm_source=xiguastudio B 스테이션: https://www.bilibili.com/video/BV1BY411d7c6/ 지후: https://www.zhihu.com/zvideo/1581722694294487040
기사의 코드는 참조용일 뿐입니다. 실제 사용을 위해 조정하고 확장할 수 있습니다.