Otra estrategia de ejecución de señales de TradingView

El autor:Un sueño pequeño., Creado: 2022-11-30 10:52:07, Actualizado: 2023-09-18 20:01:09

[TOC] ¿Qué quieres decir?

img

Otra estrategia de ejecución de señales de TradingView

Los traders que usan TradingView con frecuencia saben que TradingView puede enviar mensajes a otras plataformas. Anteriormente, también se publicó una política de envío de señales de TradingView en la biblioteca, el contenido del mensaje enviado está escrito en la url de la solicitud, algo inflexible.

Escenarios y principios

Puede que algunos de los estudiantes nuevos vean que el título y la descripción de este artículo son un poco extraños, no importa. Primero explicamos el escenario de las necesidades, los principios.

El primer escenario de demanda: En pocas palabras, tenemos muchos indicadores, estrategias, códigos, etc. que podemos elegir en TradingView, que se pueden ejecutar directamente en TradingView, dibujar líneas, calcular, mostrar señales de negociación, etc. Y TradingView tiene datos de precios en tiempo real, una gran cantidad de datos de líneas K para calcular una variedad de indicadores.

2o Principio:

img

El programa se centra en cuatro temas, que en pocas palabras son:

Número El sujeto Descripción
1 TradingView (Vista de operaciones en el gráfico) El script PINE se ejecuta en TradingView para enviar señales y acceder a la API de extensión de FMZ
2 Plataforma FMZ (en la imagen, la plataforma FMZ en la página web) Administrar el disco, puede enviar instrucciones de interacción en la página del disco, también puede enviar instrucciones de interacción a los administradores de la plataforma FMZ mediante una interfaz API extendida
3 Programas de disco real en el software del administrador (en la imagen, el robot de estrategia de FMZ) TradingView, el programa en el que se ejecuta la estrategia de ejecución de señales
4 El intercambio (exchange en el gráfico) Intercambio configurado en vivo, el programa de intercambio en vivo del administrador envía directamente la solicitud al intercambio de la orden

Así que si quieres jugar, tienes que hacer estas preparaciones: 1, Un script que se ejecuta en TradingView y que es responsable de enviar las solicitudes de señal a la interfaz API de extensión de FMZ, requiere que la cuenta de TradingView sea al menos un miembro PRO. 2, Para implementar un programa de administrador en FMZ, se requiere que sea el que pueda acceder a la interfaz de la bolsa (por ejemplo, servidores en Singapur, Japón, Hong Kong, etc.). 3. Configurar en FMZ la API KEY del exchange para realizar el pedido cuando se envíe la señal de TradingView. 4. Necesitas tener una "Estrategia de ejecución de señales TradingView", que es lo que este artículo trata.

Política de ejecución de señales de TradingView

En la versión anterior, la política de ejecución de señales de TradingView no era tan flexible, y los mensajes solo podían escribirse en la url de la solicitud enviada por TradingView.

img

Entonces, en TradingView se puede configurar como en el gráfico, escribir un mensaje en el cuerpo de la solicitud y enviarlo a la interfaz API de extensión de FMZ. ¿Cómo se llama esta interfaz API de extensión de FMZ?

En FMZ, una serie de interfaces de API de extensión, lo que vamos a usar esCommandRobotLa interfaz, que normalmente se llama así:

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

Este es el URL solicitado.queryEn el interioraccess_keyysecret_keyEs una extensión de la plataforma FMZ.API KEYAsí que aquí la demostración está configurada comoxxxyyyyy¿Cómo se creó este KEY?https://www.fmz.com/m/accountEn la página web de Facebook de la organización, se puede crear una, guardarlas bien y no filtrarlas.

img

Volviendo al tema, continúa:CommandRobotEl problema de la interfaz.CommandRobotInterfaz, en la solicitudmethodEl mensaje de Twitter se ha convertido en el siguiente:CommandRobotCommandRobotLa función de esta interfaz es enviar un mensaje interactivo a un disco de un ID a través de la plataforma FMZ, por lo que los parámetros son:argsEn este caso, el URL de la solicitud es el ID de la aplicación.186515El programa de disco real envía mensajesok12345

Antes, se solicitaba de esta manera a la interfaz CommandRobot de la API de extensión de FMZ, y el mensaje solo se podía escribir como en el ejemplo anterior.ok12345Si el mensaje está en el cuerpo solicitado, se necesita otra forma:

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

Esta solicitud puede ser enviada a través de la plataforma FMZ como un mensaje interactivo al ID para el contenido del cuerpo de la solicitud.130350Si el mensaje en TradingView está configurado para:{"close": {{close}}, "name": "aaa"}Entonces, el ID es130350En el caso de los dispositivos móviles, el disco físico recibirá instrucciones de interacción:{"close": 39773.75, "name": "aaa"}

Para que la política de ejecución de señales de TradingView pueda entender correctamente la instrucción enviada por TradingView al recibir una instrucción de interacción, debe acordarse el formato del mensaje con antelación:

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

La política está diseñada para ser una arquitectura multi-intercambio, por lo que se pueden configurar varios objetos de intercambio en esta política, es decir, se pueden controlar operaciones de intercambio de varias cuentas diferentes. Sólo con Exchange que especifica el intercambio para operar en la estructura de señales, la configuración 1 es que esta señal opera la cuenta de intercambio correspondiente al primer objeto de intercambio que se agrega.

Ahora podemos diseñar el código de la estrategia, el código completo de la estrategia:

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

Parámetros estratégicos y interacción:

img

La política completa de ejecución de señales de TradingView se encuentra en:https://www.fmz.com/strategy/392048

Pruebas simples

Para configurar el objeto de la bolsa antes de ejecutar la política, en los parámetros de la política, configure "AccessKey de la plataforma FMZ" y "SecretKey de la plataforma FMZ", los dos parámetros, no configure mal.

img

Imprimirá: la dirección de WebHook que se requiere en TradingView, las instrucciones de acción compatibles, el formato de mensaje; lo importante es la dirección de WebHook:

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

Se puede copiar y pegar directamente la ubicación correspondiente en TradingView.

Si desea simular el envío de señales de TradingView, puede hacer clic en el botón TestSignal en la interacción de la política:

img

La política envía una solicitud (como una solicitud de señal de envío de TradingView) a sí misma, llama a la interfaz API de extensión de FMZ y envía un mensaje a la política:

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

La política actual recibe otro mensaje de interacción y ejecuta:

img

Y luego pedimos una transacción.

Pruebas con TradingView en escenarios reales

Para usar TradingView, se requiere una cuenta de TradingView de nivel Pro. Hay algunos conocimientos previos que deben explicarse de manera simple antes de probar.

Por ejemplo, un sencillo guión de PINE (algo modificado que encontré en 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, El guión PINE puede incluir información cuando el guión emite la instrucción siguiente

Estos son los símbolos que escribo en el cuadro de "mensajes" de una alarma, por ejemplo:{{strategy.order.contracts}}Si el cliente no está en contacto con el cliente, entonces se envía un mensaje (de acuerdo con la configuración de la alarma, el envío de correo electrónico, la solicitud de url de webhook, el popup, etc.) que incluye el número de pedidos ejecutados.

{{strategy.position_size}}- Devuelve el valor de la misma palabra clave en Pine, es decir, el tamaño de la posición actual.{{strategy.order.action}}- Para la orden ejecutada, devuelva la cadena buy button o sell button.{{strategy.order.contracts}}- Regresar el número de contratos que se han ejecutado.{{strategy.order.price}}- Volver el precio de ejecución del pedido.{{strategy.order.id}}- Devuelve el ID de la orden ejecutada ((se utiliza como la primera de las strings de los parámetros en una de las llamadas de la función que genera la orden: estrategy.entry, strategy.exit o strategy.order)).{{strategy.order.comment}}- Devuelve la nota de la orden ejecutada (string utilizada en el parámetro de comentarios en una de las llamadas de la función que genera la orden:strategy.entry,strategy.exitSi no se especifica una nota, se usará el valor de estrategy.order.id.{{strategy.order.alert_message}}- Devuelve el valor del parámetro alert_message, que se puede usar en el código Pine de la política cuando se llama una de las funciones que se usan para ordenar:strategy.exitTambién se puede usar el código de instrucción de la página web de la empresa, o el código de instrucción de la página web de la empresa.{{strategy.market_position}}- Retorno de la estrategia de la posición actual en forma de una cadena: "long", "flat" o "short".{{strategy.market_position_size}}- Devuelve el tamaño de la posición actual en forma de valor absoluto (es decir, no negativo).{{strategy.prev_market_position}}- Retorno de la estrategia en forma de una cadena a la posición anterior: "long", "flat" o "short".{{strategy.prev_market_position_size}}- Devuelve el tamaño de la posición anterior en forma de valor absoluto (es decir, no negativo).

2. Construir mensajes con "Estrategia de ejecución de señales de TradingView"

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

3. Para que TradingView emita una señal cuando se ejecuta este guión PINE, se requiere que se establezca una alarma cuando se cargue este guión en TradingView

img

Cuando el script PINE en TradingView inicia una acción de negociación, se envía una solicitud de url webhook.

img

img

El disco de FMZ ejecutará la señal.

img

img

Dirección de vídeo

El video de la calabaza:https://www.ixigua.com/7172134169580372513?utm_source=xiguastudioEstación B:https://www.bilibili.com/video/BV1BY411d7c6/¿Qué es lo que está pasando?https://www.zhihu.com/zvideo/1581722694294487040

El código en el artículo es solo para referencia y puede ser modificado o ampliado por el usuario.


Relacionados

Más.

wbe3- pequeñas papas fritasMungo, ¿cómo se ejecuta la operación del entorno del disco analógico?

el guohwaPor favor, ¿puede el mensaje de alerta de Tradingview contener el mensaje de la última orden? Quiero saber si la última orden fue ganadora o perdedora, y si la última orden fue perdedora, el robot no ejecutará la operación hasta que la última orden obtenida sea ganadora. ¿Pueden hacerlo, por favor?

13811047519/upload/asset/2a5a9fa2b97561c42c027.jpg Por favor, Dios, ¿qué significa este error y cómo eliminarlo?

Lo mejorDream Big, he añadido 6 a 7 cuentas para hacer transacciones con esta señal, pero por el momento es bastante grande, una cuenta de intercambio de señales completadas para la siguiente cuenta de transacción de señales, es una ejecución en serie, ¿hay una manera de que simultáneamente ejecutar señales de transacción?

wbe3- pequeñas papas fritasEn la política de recepción de señales, parece que no se imprimen los ingresos, y la publicación parece que no se genera, así que por favor, agregue un modelo de formulario de información de cuenta relacionado.

Un sueño pequeño.La página se ha añadido automáticamente.

wbe3- pequeñas papas fritasGracias, amigo, ya lo probé, pero no hay una estrategia de puntuación después de la transacción.

Un sueño pequeño.Interfaz OKX, que puede ser cambiado a un entorno de prueba de disco de simulación de OKX, utilizando el intercambio. IO (("simulate", true), que puede ser cambiado a un entorno de disco de simulación.

el guohwaGracias por la respuesta, tengo dos preguntas: 1, lo que no entiendo es que fmz puede escribir el script pine por sí mismo, ¿por qué este artículo también debe enviar alertas a fmz a través de TradingView y luego procesar y luego negociar? 2, ahora he encontrado una buena estrategia en sí misma, pero sin acceso al código fuente, quiero evitar errores con el método que mencioné anteriormente, lo que usted dice en el mensaje de envío añadido, pero este envío parece ser el precio al momento de realizar el pedido, después en el fmz, ¿cómo a través de este precio para juzgar si es una ganancia o un perjuicio, no tengo un poco de entendimiento.

Un sueño pequeño.Debe ser posible que pujas el contenido de {{strategy.order.price}} en el momento de enviar el mensaje, y luego la estrategia en FMZ procesará la información para decidir si pones un pedido o no en función de la comparación de precios actual.

Un sueño pequeño.¿Está todo bien ahora?

Lo mejorMuy bien, gracias al jefe.

Un sueño pequeño.FMZ añade nuevas funciones de concurrencia, que deberían ser convertibles a la concurrencia, aunque el código de la estrategia puede cambiar mucho más.