Another TradingView signal execution strategy

Author: The Little Dream, Created: 2022-11-30 10:52:07, Updated: 2023-09-18 20:01:09

[TOC]

img

Another TradingView signal execution strategy

Traders who regularly use TradingView know that TradingView can push messages to other platforms. A previously published TradingView signal push policy was also published in the library. The message push content is written dead in the request url, which is somewhat inflexible.

Scenes and Principles

Some new students may find the title of this article and the description above a bit strange, but that's okay! Let's explain the need scenario and the principles first. Let's get to the point.

The first is the demand scenario: What does this thing do for half a day? Simply put, we have a lot of indicators, strategies, codes, etc. to choose from in TradingView, which can be run directly in TradingView, can draw lines, calculate, display trading signals, etc. And TradingView has real-time price data, plenty of K-line data to compute various indicators. The script code on TradingView is called the PINE language, the only thing that is not so convenient is to trade in real time in TradingView. Although FMZ already supports the PINE language, it can also run in real time.

The second principle:

img

The whole programme involves four subjects, which are simply:

Numbered Subject Describe
1 TradingView (Trading View in the diagram) Running the PINE script on TradingView to send signals and access the FMZ's extended API interface
2 FMZ platform (pictured on the FMZ platform website) Manage the disk, can send interactive instructions on the disk page, or can use an extended API interface to allow the FMZ platform to send interactive instructions to the administrator.
3 Real-time programs on the host software (FMZ strategy robot in the figure) TradingView Signal Execution Strategies The process by which the signal is actually run
4 Exchange (exchange in the picture) Exchange configured on-premises, the on-premises process on the custodian sends the request directly to the exchange to place the order

So if you want to play this game, you need to prepare yourself: 1, a script running on TradingView that is responsible for sending signal requests to FMZ's extension API interface, requiring a TradingView account that is at least a PRO member. 2, Deploying a custodian program on FMZ requires one that can access the exchange interface (e.g. servers in Singapore, Japan, Hong Kong, etc.). 3. Configure on FMZ the API KEY of the exchange to be operated when the TradingView signal is sent in. 4, you need to have a "TradingView signal execution strategy", which is the main topic of this article.

TradingView signal execution policy

The previous version of the "TradingView signal execution policy" was designed to be less flexible, and the message could only be written dead in the url of the request that was sent by TradingView. If we wanted to have TradingView write some variable information in the body when pushing the message, this would not work. For example, in TradingView, the message content:

img

In TradingView, the message can be written in the request body and sent to the FMZ extension API interface.

In the FMZ series of extension API interfaces, what we're going to use isCommandRobotThis interface is usually called this:

https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]

This requested urlqueryThe insideaccess_keyandsecret_keyIt is an extension of the FMZ platform.API KEYThis is a demonstration, so it's set toxxxandyyyy│How is this KEY created?https://www.fmz.com/m/accountIn the meantime, I'm going to start a blog about how to create one, keep it safe, and never leak it.

img

Back to basics and continue.CommandRobotInterface issues. If you need access toCommandRobotInterface, in the requestmethodIt is set to:CommandRobotCommandRobotThe function of this interface is to send an interactive message to the disk of an ID through the FMZ platform, so the parametersargsThis request url example above is for the ID to be set to186515It's a real-time program that sends messages.ok12345

Previously this was the way to request FMZ extension API from the CommandRobot interface, the message could only be written dead, as in the example above.ok12345If the message is in the requested body, you need to use another way:

https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]

This allows the request to be sent through the FMZ platform as an interactive message to the ID as the content of the body in the request.130350If the message on TradingView is set to:{"close": {{close}}, "name": "aaa"}So the ID is130350In this case, the user receives an interactive instruction:{"close": 39773.75, "name": "aaa"}

In order for the TradingView signal execution policy to correctly understand the instruction that TradingView sends when it receives the interaction instruction, the message format must be agreed in advance:

{
    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",           // 交易量
}

The policy is designed to be a multi-exchange architecture, so multiple exchange objects can be configured on this policy, i.e. it can control the ordering operation of multiple different accounts. Set 1 is to have this signal operate on the corresponding exchange account of the first exchange object added to the signal structure. If you want to operate on the spot ContractType setting, futures write specific spot contracts, such as permanent contract swaps.

The next thing you can do is design the strategy code, the complete strategy code:

//信号结构
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)
        }
    }
}

Strategy parameters and interaction:

img

"TradingView Signal Execution Policy" full policy address:https://www.fmz.com/strategy/392048

Simple tests

Before running the policy, configure the exchange object, set the parameters "AccessKey for the FMZ platform" and "SecretKey for the FMZ platform" in the policy parameters, and do not set them incorrectly.

img

It will print the following: WebHook addresses to be filled in in TradingView, supported Action instructions, message format.

https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]

You can directly copy and paste the corresponding location in TradingView.

If you want to simulate the sending of signals in TradingView, you can click on the TestSignal button on the Policy Interaction:

img

The policy itself sends a request (analogous to TradingView sending a signal request) that calls FMZ's extension API interface and sends a message to the policy itself:

{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}

The current policy receives another interaction message and executes:

img

And then I ordered a deal.

Testing with TradingView in real-world scenarios

TradingView is a Pro-level account, and some preliminary knowledge is required before the test.

An example of this is a simple PINE script (a bit modified from a random search in 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)

1, a PINE script can include some information when the script is issuing a command

The following are the placeholders, for example, that I type in the "message" box in the alarm.{{strategy.order.contracts}}When an order is triggered, a message is sent (depending on the settings on the alert, email push, webhook url request, popup window, etc.), which contains the number of times the order has been executed.

{{strategy.position_size}}- Returns the value of the same keyword in Pine, i.e. the current position size.{{strategy.order.action}}- Returns the string buy button or sell button for the order to be executed.{{strategy.order.contracts}}- Returns the number of contracts executed.{{strategy.order.price}}- Returns the price of the order.{{strategy.order.id}}- Returns the ID of an order that has been executed (string used as the first parameter in one of the function calls that generates the order: strategy.entry, strategy.exit or strategy.order).{{strategy.order.comment}}- Returns the annotation of an order that has been executed (string used in the comment parameter in one of the function calls that generated the order:strategy.entry,strategy.exit、 or strategy.order) 、 If no annotation is specified, the value of strategy.order.id is used.{{strategy.order.alert_message}}- Returns the value of the parameter alert_message, which can be used in the Pine code of the policy when calling one of the functions used for the next order: strategy.entry,strategy.exit、 or strategy.order。 This feature is only supported in Pine v4。{{strategy.market_position}}- Returns the current holdings of the strategy in the form of a string: long holdings, flat holdings, or short holdings.{{strategy.market_position_size}}- Returns the size of the current position in the form of an absolute value (i.e. non-negative).{{strategy.prev_market_position}}- Returns the previous holding of the strategy in the form of a string: long, flat or short.{{strategy.prev_market_position_size}}- Returns the size of the previous position in the form of an absolute value (i.e. a non-negative number).

2 Construction of messages with "TradingView signal execution policy"

{
    "Flag":"{{strategy.order.id}}",
    "Exchange":1,
    "Currency":"BTC_USDT",
    "ContractType":"swap",
    "Price":"-1",
    "Action":"{{strategy.order.comment}}",
    "Amount":"{{strategy.order.contracts}}"
}

3 ̊ For TradingView to signal when running this PINE script, you need to set an alarm when this script is loaded in TradingView

img

When the PINE script in TradingView triggers a transaction, a webhook url request is sent.

img

img

The FMZ's hard drive will execute this signal.

img

img

Video address

The video of the squid:https://www.ixigua.com/7172134169580372513?utm_source=xiguastudioB station:https://www.bilibili.com/video/BV1BY411d7c6/I know:https://www.zhihu.com/zvideo/1581722694294487040

The code in the article is for reference only and can be customized and extended for actual use.


Related

More

wbe3- small potato chipsMongo, how do you run the analogue disk environment operation?

guohwaCan I ask you a question, can the alert message in Tradingview contain the message of the last order? I want to get the last order to be a profit or a loss, and if the last order is a loss, the robot will not execute the order until the last order is a profit. Can you do that, please? Thank you!

13811047519/upload/asset/2a5a9fa2b97561c42c027.jpg Please God, what does this error mean and how to fix it?

The Good PlaceI've added 6 and 7 accounts to do signal trading with this, but for now it's pretty big, one exchange account signal is completed and then the next trading account signal is executed, is it a serial execution, is there a way to simultaneously execute the trading signal?

wbe3- small potato chipsIn the policy of receiving the signal, it seems that there is no printing revenue, and the public seems that it will not be generated, so would you please add a related account information form template to view the policy performance?

The Little DreamThat's the public policy outline, which is automatically added to the page.

wbe3- small potato chipsThank you Monk, I've tested it well, but there's no strategy rating review after the trade, is it necessary to add it yourself?

The Little DreamOKX interface, which can be switched to OKX's analogue disk test environment using exchange.IO (("simulate", true), which can be switched to analogue disk environment.

guohwaThank you for your reply, I have two questions: 1, what I'm a little confused about is that fmz can write its own pine script, so why would this article send an alert to fmz via TradingView and then process it and then trade? 2, I have now found a very good strategy in itself, but without the source code having the right to use, I want to avoid mistakes by using the method I mentioned above, you said in the push message to add {{strategy.order.price}} I also added, but this push seems to be the price at the time of ordering, how to judge by this price in the back of the fmz whether it is a profit or a loss, I do not understand.

The Little DreamIt should be possible to push the {{strategy.order.price}} content when pushing the message, and then the policy on FMZ processes this information and decides whether to place an order based on the current price comparison.

The Little DreamI'm here to test normal.

The Good PlaceGood. Thank you, boss.

The Little DreamFMZ adds concurrency functionality and should be able to be made concurrent, although the policy code may change significantly. Upgrade a concurrent example recently if you have time.