4
Подписаться
1271
Подписчики

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

Создано: 2022-11-30 10:52:07, Обновлено: 2025-05-16 16:18:13
comments   17
hits   4333

[TOC]

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

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

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

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

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

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

  2. Принцип:

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

Весь план состоит из четырех основных частей, которые вкратце таковы:

серийный номер основной корпус описывать
1 TradingView (Торговый вид на картинке) TradingView запускает скрипт PINE, который может отправлять сигналы и получать доступ к расширенному API-интерфейсу FMZ.
2 Платформа FMZ (на фото платформа (сайт) FMZ) Управляйте реальным рынком, отправляйте интерактивные инструкции на страницу реального рынка, а также используйте расширенный интерфейс API, чтобы платформа FMZ отправляла интерактивные инструкции в программу стратегии реального рынка на кастодиане.
3 Реальная программа на хостинговом программном обеспечении (робот стратегии FMZ на картинке) Фактическая программа, которая реализует стратегию исполнения сигнала TradingView
4 Обмен (обмен на картинке) Биржа, настроенная на реальном рынке, биржа, на которую программа реального рынка на кастодиане напрямую отправляет запрос на размещение ордера

Итак, если вы хотите играть таким образом, вам понадобятся следующие приготовления: 1. Скрипт, работающий на TradingView, отвечает за отправку запросов сигналов в расширенный интерфейс API FMZ. Учетная запись TradingView должна быть как минимум PRO-участником. 2. Разверните кастодиальную программу на FMZ, которая должна иметь доступ к интерфейсу биржи (например, к серверам в Сингапуре, Японии, Гонконге и т. д.). 3. Настройте API KEY биржи на FMZ для работы (размещения ордера) при отправке сигнала TradingView. 4. Вам необходимо иметь «стратегию исполнения сигналов TradingView», о которой в основном и пойдет речь в этой статье.

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

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

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

Затем TradingView можно настроить, как показано на рисунке, записав сообщение в теле запроса и отправив его в расширенный API-интерфейс FMZ. Так как же назвать этот расширенный API-интерфейс FMZ?

Среди ряда расширенных API-интерфейсов FMZ мы будем использоватьCommandRobotЭтот интерфейс обычно называется так:

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

Этот запрос URLqueryВaccess_keyиsecret_keyЭто расширение платформы FMZ.API KEY, поэтому здесь мы устанавливаем его наxxxиyyyy. Как создать этот КЛЮЧ? На этой странице:https://www.fmz.com/m/account, просто создайте его, храните его надлежащим образом и никогда не разглашайте.

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

Возвращаемся к теме, продолжим.CommandRobotПроблема с интерфейсом. Если вам нужен доступCommandRobotИнтерфейс, в запросеmethodПросто установите его на:CommandRobotCommandRobotФункция этого интерфейса заключается в отправке интерактивного сообщения на реальный диск определенного идентификатора через платформу FMZ, поэтому параметрыargsСодержание запроса — это реальный идентификатор и сообщение. Приведенный выше пример URL запроса — это отправка запроса на идентификатор186515Реальная программа, отправить сообщениеok12345

Ранее этот метод использовался для запроса интерфейса CommandRobot API расширения FMZ. Сообщение могло быть только жестко закодировано, как в примере выше.ok12345. Если сообщение находится в теле запроса, необходим другой метод:

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

Таким образом, запрос может быть отправлен через платформу FMZ, а содержимое тела запроса будет отправлено в виде интерактивного сообщения пользователю с идентификатором.130350Реальная сделка. Если сообщение на TradingView установлено следующим образом:{"close": {{close}}, "name": "aaa"}, то идентификатор будет130350Настоящий диск получит интерактивные инструкции:{"close": 39773.75, "name": "aaa"}

Для того чтобы «Стратегия исполнения сигналов TradingView» правильно поняла инструкцию, отправленную 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 на значение spot; для фьючерсов укажите конкретный контракт, например, укажите своп для бессрочных контрактов. Для рыночной цены ордера просто передайте -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

「Стратегия исполнения сигналов TradingView」Полный адрес стратегии: https://www.fmz.com/strategy/392048

Простой тест

Перед запуском стратегии необходимо настроить объект обмена и задать два параметра “AccessKey of FMZ platform” и “SecretKey of FMZ platform” в параметрах стратегии. Убедитесь, что вы не задали их неправильно. Запуск показывает:

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

Последовательно будут распечатаны следующие данные: адрес WebHook, который необходимо заполнить в TradingView, поддерживаемые инструкции по действию и формат сообщения. Важен адрес WebHook:

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

Просто скопируйте и вставьте его в соответствующую позицию на TradingView.

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

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

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

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

Текущая стратегия получит еще одно интерактивное сообщение и выполнит:

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

И сделайте заказ.

Тестирование с использованием 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-уведомления по электронной почте, запроса URL-адреса веб-перехватчика, всплывающего окна и т. д.), и сообщение будет включать количество выполненного ордера в этот раз время.

{{strategy.position_size}} - Возвращает значение того же ключевого слова в Pine, которое представляет собой размер текущей позиции. {{strategy.order.action}} - Возвращает строку «купить» или «продать» для выполненного ордера. {{strategy.order.contracts}} - Возвращает количество контрактов по выполненному заказу. {{strategy.order.price}} - Возвращает цену, по которой был исполнен ордер. {{strategy.order.id}} - Возвращает идентификатор выполненного ордера (строка, используемая в качестве первого аргумента в одном из вызовов функции, сгенерировавшей ордер: strategy.entry, strategy.exit или strategy.order). {{strategy.order.comment}} - Возвращает комментарий выполненного заказа (строка, используемая в параметре комментария в одном из вызовов функций, сгенерировавших заказ: strategy.entry, strategy.exit или strategy.order). Если аннотация не указана, будет использовано значение strategy.order.id. {{strategy.order.alert_message}} - Возвращает значение параметра alert_message, которое может быть использовано в Pine-коде стратегии при вызове одной из функций размещения ордеров: strategy.entry, strategy.exit или strategy.order. Эта функция поддерживается только в Pine v4. {{strategy.market_position}} - Возвращает текущую позицию стратегии в виде строки: «длинная», «флэт» или «короткая». {{strategy.market_position_size}} - Возвращает размер текущей позиции как абсолютное значение (т.е. неотрицательное число). {{strategy.prev_market_position}} - Возвращает последнюю позицию стратегии в виде строки: «длинная», «флэт» или «короткая». {{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

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

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

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

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

Реальная торговля FMZ исполнит этот сигнал.

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

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

URL-адрес видео

Видео Xigua: https://www.ixigua.com/7172134169580372513?utm_source=xiguastudio Станция B: https://www.bilibili.com/video/BV1BY411d7c6/ Чжиху: https://www.zhihu.com/zvideo/1581722694294487040

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