Un autre schéma de stratégie d'exécution des signaux TradingView

Auteur:Je ne sais pas., Créé: 2022-12-15 21:23:24, Mis à jour: 2023-09-18 20:01:55

img

Un autre schéma de stratégie d'exécution des signaux TradingView

Les traders qui utilisent souvent TradingView savent que TradingView peut pousser des messages vers d'autres plateformes. Dans notre Digest de la plateforme FMZ, il y avait une stratégie de poussée de signal TradingView publiée dans la bibliothèque, où le contenu des messages poussés était écrit dans l'url de la demande, ce qui était quelque peu inflexible.

Scénarios et principes

Certains débutants peuvent être confus par le titre de cet article et la description ci-dessus, peu importe! Commençons par une description claire des scénarios et des principes de la demande.

  1. Scénarios de la demande: Donc, quel type de travail nous voulons qu'il fasse? Pour le dire simplement, nous avons beaucoup d'indicateurs, de stratégies, de codes, etc. que nous pouvons choisir d'utiliser sur TradingView, qui peuvent être exécutés directement sur TradingView pour dessiner des lignes, calculer et afficher des signaux de trading. En outre, TradingView dispose de données de prix en temps réel et de données de ligne K suffisantes pour faciliter le calcul de divers indicateurs. Ces codes de script sur TradingView sont appelés langage PINE. La seule chose qui n'est pas pratique est que le vrai bot négocie sur TradingView. Bien que le langage PINE soit pris en charge sur FMZ, il peut également être utilisé pour le vrai trading bot. Cependant, il y a quelques fans de TradingView qui veulent toujours passer des ordres en utilisant les signaux des graphiques sur TradingView, ce qui peut être résolu par FMZ. Dans cet article, nous allons donc expliquer les détails de la solution.

  2. Principe:

img

L'ensemble du programme comporte quatre sujets, qui sont, en résumé:

img

Donc si vous voulez l'utiliser de cette façon, vous avez besoin de ces préparations:

  1. Le script exécuté sur TradingView est responsable de l'envoi de requêtes de signal à l'interface API étendue de FMZ. Le compte TradingView doit être au moins un membre PRO.
  2. Pour déployer un programme docker sur FMZ, il doit être du type qui peut accéder à l'interface d'échange (comme les serveurs de Singapour, du Japon, de Hong Kong, etc.).
  3. Configurer la CLAVE API de l'échange pour effectuer une opération (d'ordre) lorsque le signal TradingView est envoyé sur FMZ.
  4. Vous devez avoir une TradingView Signal Execution Strategy, qui est principalement discutée dans cet article.

Stratégie d'exécution du signal TradingView

La conception de la stratégie d'exécution de signal de TradingView dans la version précédente n'est pas très flexible. Les messages ne peuvent être écrits qu'à l'url de la demande envoyée par TradingView. Si nous voulons que TradingView écrive des informations variables dans le corps lors de l'envoi de messages, nous ne pouvons rien faire à ce moment-là. Par exemple, le contenu de ce message sur TradingView:

img

Ensuite, le TradingView peut être configuré comme indiqué sur la figure pour écrire le message dans le corps de la demande et l'envoyer à l'interface API étendue de FMZ.

Dans une série d'interfaces API étendues de FMZ, nous devons utiliser leCommandRobotinterface, qui est généralement appelée comme suit:

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

Leaccess_keyetsecret_keydans lequeryde cette demande url est l'extensionAPI KEYde la plate-forme FMZ, ici la démo mise àxxxetyyyyAlors comment créer cette CLAVE?https://www.fmz.com/m/account, créez dessus, gardez-le correctement, ne le divulguez pas.

img

Retour au sujet, continuons à parler du problème de l'interface deCommandRobotSi vous avez besoin d' accéder auCommandRobotl'interface, lemethoddans la demande sera réglée sur:CommandRobot. La fonction duCommandRobotl'interface est d'envoyer un message interactif à un vrai bot avec un ID à travers la plate-forme FMZ, donc le paramètreargsL'exemple de l'url de demande ci-dessus est pour envoyer le messageok12345à un vrai programme bot avec un ID de 186515.

Auparavant, cette méthode était utilisée pour demander l'interface CommandRobot de l'API étendue FMZ. Les messages ne peuvent être écrits que dans l'exemple ci-dessus, comme leok12345Si le message est dans l'organisme demandé, vous devez utiliser une autre méthode:

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

De cette façon, la demande peut envoyer le contenu du corps dans la demande comme un message interactif au vrai bot avec ID130350Si le message sur TradingView est réglé sur:{"close": {{close}}, "name": "aaa"}, alors le vrai robot avec l' ID de130350recevra des instructions interactives:{"close": 39773.75, "name": "aaa"}

Afin que la stratégie d'exécution du signal de TradingView puisse comprendre correctement la commande envoyée par TradingView lors de la réception de la commande interactive, les formats de message suivants doivent être convenus à l'avance:

{
    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
}

La stratégie est conçue comme une architecture multi-échange, de sorte que plusieurs objets d'échange peuvent être configurés sur cette stratégie, c'est-à-dire que l'opération de placement d'ordres de plusieurs comptes différents peut être contrôlée. Seul l'échange dans la structure du signal spécifie l'échange à exploiter. Le réglage 1 est de permettre à ce signal d'opérer le compte d'échange correspondant au premier objet d'échange ajouté. Si le spot ContractType est réglé sur spot, les contrats à terme écriront des contrats spécifiques, tels que le swap pour les contrats perpétuels. La liste de prix du marché peut passer en -1.

Ensuite, vous pouvez concevoir le code de stratégie.

//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)
        }
    }
}

Paramètres et interactions de la stratégie:

img

L'adresse complète de la stratégie de la stratégie d'exécution des signaux de la vue de négociation:https://www.fmz.com/strategy/392048

Épreuve simple

Avant d'exécuter la stratégie, l'objet d'échange doit être configuré et les deux paramètres AccessKey sur la plateforme FMZ et SecretKey sur la plateforme FMZ doivent être définis dans les paramètres de stratégie.

img

Il imprimera l'adresse WebHook, les commandes d'action prises en charge et le format du message qui doivent être remplis sur le TradingView.

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

Il suffit de copier et coller directement à l'emplacement correspondant sur le TradingView.

Si vous voulez simuler un signal envoyé par TradingView, vous pouvez cliquer sur le bouton TestSignal sur l'interaction de stratégie.

img

Cette stratégie envoie sa propre demande (simulant une demande de signal envoyée par TradingView), appelant l'interface API étendue de FMZ pour envoyer un message à la stratégie elle-même:

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

La stratégie actuelle recevra un autre message interactif et exécutera et placera un ordre de transaction.

Le test de l'utilisation de TradingView dans la scène réelle

L'utilisation du test TradingView nécessite que le compte TradingView soit au niveau Pro. Avant le test, vous devez connaître certaines connaissances préalables.

Prenez un script PINE simple (trouvé et modifié au hasard sur TradingView) comme exemple

//@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. Le script PINE peut joindre des informations lorsque le script envoie des instructions de commande

Voici les marqueurs de place.{{strategy.order.contracts}}dans la zone message de l'alerte, un message sera envoyé lorsque l'ordre est déclenché (selon les paramètres de l'alerte, mail push, webhook url request, pop-up, etc.), et le message contiendra le nombre d'ordres exécutés cette fois.

{{strategy.position_size}}- Renvoie la valeur du même mot clé dans Pine, c'est-à-dire la taille de la position actuelle.{{strategy.order.action}}- Retournez la chaîne buy ou sell pour l'ordre exécuté.{{strategy.order.contracts}}- Indiquer le nombre de contrats pour lesquels des ordres ont été exécutés.{{strategy.order.price}}- Retournez le prix de l'ordre exécuté.{{strategy.order.id}}- Renvoie l'ID de l'ordre exécuté (la chaîne utilisée comme premier paramètre dans l'une des appels de fonction qui génèrent l'ordre: strategy.entry,strategy.exitou stratégie.ordre).{{strategy.order.comment}}- Renvoie le commentaire de l'ordre exécuté (la chaîne utilisée dans le paramètre commentaire dans l'une des appels de fonction qui génèrent l'ordre: strategy.entry,strategy.exitSi aucun commentaire n'est spécifié, la valeur destrategy.order.idseront utilisés.{{strategy.order.alert_message}}- Renvoie la valeur du paramètre alert_message qui peut être utilisé dans le code Pine de la stratégie lors de l'appel d'une des fonctions utilisées pour passer une commande: strategy.entry,strategy.exit, ou strategy.order. Ceci n'est pris en charge que dans Pine v4.{{strategy.market_position}}- Renvoie la position actuelle de la stratégie sous forme de chaîne: long, flat ou short.{{strategy.market_position_size}}- Renvoie la taille de la position en cours sous forme de valeur absolue (c'est-à-dire un nombre non négatif).{{strategy.prev_market_position}}- Renvoie la position précédente de la stratégie sous forme de chaîne: long, flat, ou short.{{strategy.prev_market_position_size}}- Renvoie la taille de la position précédente sous forme de valeur absolue (c'est-à-dire un nombre non négatif).

  1. Construire des messages en combinaison avec la stratégie d'exécution des signaux de TradingView
{
    "Flag":"{{strategy.order.id}}",
    "Exchange":1,
    "Currency":"BTC_USDT",
    "ContractType":"swap",
    "Price":"-1",
    "Action":"{{strategy.order.comment}}",
    "Amount":"{{strategy.order.contracts}}"
}
  1. Laissez TradingView envoyer un signal selon l'exécution du script PINE. Vous devez définir une alerte lors du chargement du script sur le TradingView

Lorsque le script PINE sur TradingView déclenche une transaction, une demande d'url webhook est envoyée.

img

Le vrai robot FMZ exécutera ce signal.

img

Le code de cet article est à titre indicatif et vous pouvez le modifier et l'agrandir vous-même.


Relationnée

Plus de