Еще одна стратегия исполнения сигналов TradingView

Автор:Лидия., Создан: 2022-12-15 21:23:24, Обновлено: 2023-09-18 20:01:55

img

Еще одна стратегия исполнения сигналов TradingView

Трейдеры, которые часто используют TradingView, знают, что TradingView может пересылать сообщения на другие платформы. В нашей платформе Digest FMZ в библиотеке была опубликована стратегия продвижения сигналов TradingView, где содержание продвинутых сообщений было написано в URL запроса, что было несколько негибким. В этой статье мы перепроектируем стратегию исполнения сигналов TradingView новым способом.

Сценарии и принципы

Некоторые новички могут быть озадачены названием этой статьи и описанием выше, это не имеет значения! Давайте начнем с четкого описания сценариев и принципов спроса.

  1. Сценарии спроса: Итак, какую работу мы хотим, чтобы он делал? Проще говоря, у нас есть много индикаторов, стратегий, кодов и т. Д., Которые мы можем использовать на TradingView, которые можно запустить непосредственно на TradingView, чтобы нарисовать линии, рассчитывать и отображать торговые сигналы. Кроме того, TradingView имеет данные о ценах в режиме реального времени и достаточные данные K-линии, чтобы облегчить расчет различных индикаторов. Эти скриптовые коды на TradingView называются языком PINE. Единственное, что не удобно, это то, что реальный бот торгует на TradingView. Хотя язык PINE поддерживается на FMZ, он также может быть использован для реальной торговли ботами. Однако есть некоторые поклонники TradingView, которые все еще хотят размещать заказы, используя сигналы из графиков на TradingView, поэтому это может быть решено FMZ. Так что в этой статье мы объясним подробности решения.

  2. Принцип:

img

В целом программа включает в себя 4 темы, которые, вкратце, следующие:

img

Поэтому, если вы хотите использовать его таким образом, вам нужны следующие приготовления:

  1. Скрипт, работающий на TradingView, отвечает за отправку запросов сигналов на расширенный интерфейс API FMZ. Учетная запись TradingView должна быть, по крайней мере, членом PRO.
  2. Чтобы развернуть программу докера на FMZ, она должна быть такой, которая может получить доступ к интерфейсу обмена (например, серверам в Сингапуре, Японии, Гонконге и т. Д.).
  3. Конфигурировать API-Ключ биржи для (постановки ордера) операции при отправке сигнала TradingView на FMZ.
  4. Вам нужна стратегия исполнения сигналов TradingView, которая в основном обсуждается в этой статье.

Стратегия исполнения сигнала TradingView

Конструкция TradingView Signal Execution Strategy в предыдущей версии не очень гибкая. Сообщения могут быть написаны только на URL запроса, отправленного TradingView. Если мы хотим, чтобы TradingView писала некоторую переменную информацию в теле при продвижении сообщений, мы не можем ничего делать в это время. Например, такое содержание сообщения на TradingView:

img

Затем 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вqueryURL этого запроса расширенныйAPI KEYПлатформа FMZ, здесь демо установлено наxxxиyyyy. Тогда как создать этот Ключ? На этой странице:https://www.fmz.com/m/account, создавать на нем, держать его должным образом, не разглашать его.

img

Возвращаясь к теме, давайте продолжим говорить о проблеме интерфейса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"}

Для того чтобы Стратегия исполнения сигналов TradingView правильно понимала команду, отправленную TradingView при получении интерактивной команды, следует заранее согласовать следующие форматы сообщений:

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

Параметры стратегии и взаимодействия:

img

Полный адрес стратегии Стратегии исполнения сигналов в режиме Trading View:https://www.fmz.com/strategy/392048

Простой тест

Прежде чем запустить стратегию, объект обмена должен быть настроен, и два параметра AccessKey на платформе FMZ и SecretKey на платформе FMZ должны быть установлены в параметрах стратегии.

img

Он распечатает адрес WebHook, поддерживаемые команды Action и формат сообщения, которые необходимо заполнить в TradingView.

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

Просто скопируйте и вставьте его прямо в соответствующее место в TradingView.

Если вы хотите имитировать сигнал, отправленный TradingView, вы можете нажать кнопку TestSignal на взаимодействии стратегии.

img

Эта стратегия отправляет свой собственный запрос (симулируя TradingView, отправляющий запрос на сигнал), вызывая расширенный интерфейс API FMZ для отправки сообщения самой стратегии:

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

Текущая стратегия получит еще одно интерактивное сообщение и выполнит и разместит заказ на транзакцию.

Тестирование использования TradingView в реальной сцене

Использование теста 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)
  1. Скрипт PINE может прикрепить некоторую информацию, когда скрипт отправляет командные инструкции

Следующие являются заполнителями места.{{strategy.order.contracts}}в поле сообщение оповещения будет отправлено сообщение при запуске заказа (согласно настройкам на оповещении, почтовом push, запросе webhook url, всплывающем окне и т.д.), и сообщение будет содержать количество заказов, выполненных в этот раз.

{{strategy.position_size}}- Верните значение того же ключевого слова в Pine, т.е. размер текущей позиции.{{strategy.order.action}}- Верните строку buy или sell для выполненного ордера.{{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, которое может быть использовано в коде стратегииs Pine при вызове одной из функций, используемых для размещения заказа: strategy.entry,strategy.exit, или strategy.order. Это поддерживается только в Pine v4.{{strategy.market_position}}- Верните текущее положение стратегии в виде строки: long, flat или short.{{strategy.market_position_size}}- возвращает размер текущей позиции в виде абсолютного значения (то есть не отрицательного числа).{{strategy.prev_market_position}}- Верните предыдущее положение стратегии в виде строки: long, flat или short.{{strategy.prev_market_position_size}}- возвращает размер предыдущей позиции в виде абсолютного значения (то есть не отрицательного числа).

  1. Создание сообщений в сочетании со стратегией выполнения сигналов TradingView
{
    "Flag":"{{strategy.order.id}}",
    "Exchange":1,
    "Currency":"BTC_USDT",
    "ContractType":"swap",
    "Price":"-1",
    "Action":"{{strategy.order.comment}}",
    "Amount":"{{strategy.order.contracts}}"
}
  1. Позвольте TradingView отправлять сигнал в соответствии с выполнением сценария PINE. Вам нужно установить предупреждение при загрузке сценария на TradingView

Когда скрипт PINE на TradingView запускает транзакцию, будет отправлен запрос на URL-адрес веб-вхока.

img

Настоящий бот FMZ выполняет этот сигнал.

img

Код в этой статье предназначен только для справки, и его можно корректировать и расширять самостоятельно при фактическом использовании.


Связанные

Больше