avatar of 发明者量化-小小梦 发明者量化-小小梦
Seguir Mensajes Privados
4
Seguir
1271
Seguidores

Aprenda la estrategia básica de "Estrategia de cobertura de contratos OKEX en C++"

Creado el: 2019-08-26 14:30:47, Actualizado el: 2024-12-17 20:43:39
comments   2
hits   3252

Aprenda la estrategia básica de “Estrategia de cobertura de contratos OKEX en C++”

Aprenda la estrategia básica de “Estrategia de cobertura de contratos OKEX en C++”

Cuando se trata de estrategias de cobertura, hay una variedad de estrategias, una variedad de combinaciones y una variedad de ideas en varios mercados. Comenzaremos con la cobertura entre períodos más clásica para explorar las ideas de diseño y los conceptos de las estrategias de cobertura. Hoy en día, el mercado de divisas digitales es mucho más activo que cuando se formó el mercado y han surgido muchos intercambios de contratos que brindan una gran cantidad de oportunidades de arbitraje y cobertura. Existen infinitas estrategias, como el arbitraje entre mercados al contado, el arbitraje de cobertura de futuros al contado, el arbitraje entre períodos de futuros, el arbitraje entre mercados de futuros, etc. A continuación, echemos un vistazo a una estrategia de cobertura de períodos cruzados “hardcore” escrita en lenguaje C++, siendo el mercado de negociación la bolsa de contratos OKEX. La estrategia está escrita en base a la “Plataforma de negociación cuantitativa Inventor”.

  • ### Principio de estrategia

La razón por la que la estrategia es un tanto dura es que está escrita en lenguaje C++, lo que la hace un poco más difícil de leer. Sin embargo, esto no impide que los lectores conozcan la esencia del diseño y las ideas de esta estrategia. La estrategia es relativamente concisa en todo momento y la longitud del código es moderada, sólo más de 500 líneas. En términos de adquisición de datos de mercado, a diferencia de las estrategias anteriores que utilizan la interfaz rest, esta estrategia utiliza la interfaz websocket para recibir datos de mercado de los exchanges. En términos de diseño, la estructura de la estrategia es razonable, el acoplamiento de código es muy bajo y es muy conveniente expandir u optimizar. El pensamiento lógico es claro y este diseño no solo es fácil de usar y ampliar. Como estrategia de enseñanza, el diseño de estrategias de aprendizaje también es un buen ejemplo. El principio de la estrategia es relativamente simple, es decir, utilizar contratos a plazo y contratos a corto plazo para cobertura positiva y negativa, lo que es básicamente lo mismo que la cobertura entre períodos de los futuros de materias primas. Arbitraje positivo, contratos forward cortos y contratos a largo plazo. Cobertura, posiciones largas en contratos a plazo y cortas en contratos a corto plazo. Ahora que los principios básicos están claros, lo que queda es cómo la estrategia activa posiciones de cobertura, cómo cerrar posiciones y cómo aumentar posiciones. Métodos de control de posición y procesamiento de detalles de estrategia. La estrategia de cobertura se centra principalmente en la fluctuación de la diferencia de precio del activo subyacente y realiza operaciones de regresión sobre la diferencia de precio. Sin embargo, el spread puede fluctuar levemente o bruscamente, o en una dirección. Esto genera incertidumbre a la hora de cubrir ganancias y pérdidas, pero el riesgo sigue siendo mucho menor que una tendencia unilateral. Muchas de las diversas optimizaciones de estrategias de período cruzado optan por comenzar desde el nivel de control de posición, a partir de los desencadenantes de apertura y cierre. Por ejemplo, el indicador clásico de Bollinger se utiliza como puntos de apertura y cierre del arbitraje positivo y negativo cuando la diferencia de precios fluctúa. Debido a su diseño razonable y bajo grado de acoplamiento, esta estrategia también se puede modificar fácilmente para convertirla en una estrategia de cobertura entre períodos con indicadores de Bollinger.

  • ### Análisis del código de estrategia

#### Mirando el código en general, podemos concluir que el código se divide principalmente en cuatro partes.

  • 1. Definición de valor de enumeración, define algunos valores de estado, utilizados para marcar el estado. Algunas funciones funcionales que no están relacionadas con la estrategia, como la función de codificación de URL, la función de conversión de tiempo, etc., no tienen nada que ver con la estrategia y solo se utilizan para el procesamiento de datos.
  • 2. Clase generadora de datos de la línea K: la estrategia está impulsada por los datos de la línea K generados por el objeto de clase generador.
  • 3. Clase de cobertura: Los objetos de esta clase pueden ejecutar lógica de transacción específica, operaciones de cobertura, mecanismo de procesamiento de detalles de estrategia, etc.
  • 4. La función principal de la estrategia, es decirmain función.main La función es la función de entrada de la estrategia. El bucle principal se ejecuta en esta función. Además, esta función también realiza una operación importante, es decir, acceder a la interfaz websocket del exchange para obtener los datos del mercado de ticks enviados como datos brutos. Material del generador de datos de la línea K. datos.

#### Al comprender el código de estrategia como un todo, ahora podemos analizar gradualmente cada vínculo para aprender completamente el diseño, las ideas y las técnicas de la estrategia.

  • Definición de valor de enumeración, otras funciones

    1. Tipo de enumeraciónState declaración
    enum State {                    // 枚举类型  定义一些 状态
        STATE_NA,                   // 非正常状态
        STATE_IDLE,                 // 空闲
        STATE_HOLD_LONG,            // 持多仓
        STATE_HOLD_SHORT,           // 持空仓
    };
    

    Debido a que algunas funciones en el código devuelven un estado determinado, estos estados se definen en el tipo de enumeración.Statemedio. Ver aparecer el códigoSTATE_NA Es decir, es un estado anormal.STATE_IDLE Se encuentra en un estado inactivo, es decir, un estado en el que se pueden realizar operaciones de cobertura.STATE_HOLD_LONG El estado de mantener una posición de cobertura positiva.STATE_HOLD_SHORT El estado de mantener una posición de cobertura.

    1. Reemplazo de cadenas: esta función no se llama en esta estrategia. Es una función de herramienta de repuesto que procesa principalmente cadenas.
    string replace(string s, const string from, const string& to)   
    
    1. Función para convertir a caracteres hexadecimalestoHex
    inline unsigned char toHex(unsigned char x)
    
    1. Función para procesar la codificación de URL
    std::string urlencode(const std::string& str)
    
    1. Función de conversión de hora, convierte la hora en formato de cadena en una marca de tiempo.
    uint64_t _Time(string &s)
    
  • Clase generadora de datos de la línea K

    class BarFeeder {                                                                       // K线 数据生成器类
        public:
            BarFeeder(int period) : _period(period) {                                       // 构造函数,参数为 period 周期, 初始化列表中初始化
                _rs.Valid = true;                                                           // 构造函数体中初始化 K线数据的 Valid属性。
            }    
    
    
            void feed(double price, Chart *c=nullptr, int chartIdx=0) {                     // 输入数据,nullptr 空指针类型,chartIdx 索引默认参数为 0
                uint64_t epoch = uint64_t(Unix() / _period) * _period * 1000;               // 秒级时间戳祛除不完整时间周期(不完整的_period 秒数),转为 毫秒级时间戳。
                bool newBar = false;                                                        // 标记 新K线Bar 的标记变量
                if (_rs.size() == 0 || _rs[_rs.size()-1].Time < epoch) {                    // 如果 K线数据 长度为 0 。 或者 最后一bar 的时间戳小于 epoch(K线最后一bar 比当前最近的周期时间戳还要靠前)
                    Record r;                                                               // 声明一个 K线bar 结构
                    r.Time = epoch;                                                         // 构造当前周期的K线bar 
                    r.Open = r.High = r.Low = r.Close = price;                              // 初始化 属性
                    _rs.push_back(r);                                                       // K线bar 压入 K线数据结构
                    if (_rs.size() > 2000) {                                                // 如果K线数据结构长度超过 2000 , 就剔除最早的数据。
                        _rs.erase(_rs.begin());
                    }
                    newBar = true;                                                          // 标记
                } else {                                                                    // 其它情况,不是出现新bar 的情况下的处理。
                    Record &r = _rs[_rs.size() - 1];                                        // 引用 数据中最后一bar 的数据。
                    r.High = max(r.High, price);                                            // 对引用数据的最高价更新操作。
                    r.Low = min(r.Low, price);                                              // 对引用数据的最低价更新操作。
                    r.Close = price;                                                        // 对引用数据的收盘价更新操作。
                }
    
    
                auto bar = _rs[_rs.size()-1];                                               // 取最后一柱数据 ,赋值给 bar 变量
                json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close};            // 构造一个 json 类型数据
                if (c != nullptr) {                                                         // 图表对象指针不等于 空指针,执行以下。
                   if (newBar) {                                                            // 根据标记判断,如果出现新Bar 
                        c->add(chartIdx, point);                                            // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
                        c->reset(1000);                                                     // 只保留1000 bar的数据
                    } else {
                        c->add(chartIdx, point, -1);                                        // 否则就更新(不是新bar),这个点(更新这个bar)。
                    } 
                }
            }
            Records & get() {                                                               // 成员函数,获取K线数据的方法。
                return _rs;                                                                 // 返回对象的私有变量 _rs 。(即 生成的K线数据)
            }
        private:
            int _period;
            Records _rs;
    };
    

    Esta clase es principalmente responsable de procesar los datos de ticks adquiridos en la línea K de diferencia de precios para impulsar la lógica de cobertura de la estrategia. Algunos lectores quizás se pregunten: ¿por qué necesitamos utilizar datos de ticks? ¿Por qué necesitamos construir un generador de datos de línea K? ¿No es mejor utilizar directamente los datos de la línea K? Estas tres preguntas surgieron en mi mente mientras escribía algunas estrategias de cobertura. Encontré la respuesta cuando escribí la estrategia de cobertura de spread de Bollinger. Porque los datos de la línea K de un solo contrato son las estadísticas de cambio de precio de este contrato dentro de un período determinado. Los datos de la línea K de la diferencia de precio entre dos contratos son las estadísticas de la diferencia de precio dentro de un período determinado. Por lo tanto, no podemos simplemente tomar los datos de la línea K de los dos contratos para restarlos y calcular la diferencia de cada dato en cada uno de ellos. Barra K-line. Valor, según la diferencia de precio. El error más obvio es que el precio más alto y el precio más bajo de dos contratos no necesariamente coinciden. Por lo tanto, el valor restado no tiene mucho sentido. Por lo tanto, necesitamos utilizar datos de ticks en tiempo real, calcular la diferencia de precios en tiempo real y generar estadísticas sobre los cambios de precios dentro de un período determinado en tiempo real (es decir, la apertura máxima y el cierre mínimo en la columna de la línea K). De esta manera, necesitamos un generador de datos de K-line como una clase separada para separar la lógica de procesamiento.

  • Cobertura

    class Hedge {                                                                           // 对冲类,策略主要逻辑。
      public:
        Hedge() {                                                                           // 构造函数
            ...
        };
    
    
        State getState(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB) {        // 获取状态,参数: 合约A名称 、合约A深度数据, 合约B名称、 合约B深度数据
    
    
            ...
        }
        bool Loop(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB, string extra="") {       // 开平仓 策略主要逻辑
    
    
            ...
        }    
    
    
      private:
        vector<double> _addArr;                                     // 对冲加仓列表
        string _state_desc[4] = {"NA", "IDLE", "LONG", "SHORT"};    // 状态值 描述信息
        int _countOpen = 0;                                 // 开仓次数
        int _countCover = 0;                                // 平仓次数
        int _lastCache = 0;                                 // 
        int _hedgeCount = 0;                                // 对冲次数
        int _loopCount = 0;                                 // 循环计数(循环累计次数)
        double _holdPrice = 0;                              // 持仓价格
        BarFeeder _feederA = BarFeeder(DPeriod);            // A合约 行情 K线生成器
        BarFeeder _feederB = BarFeeder(DPeriod);            // B合约 行情 K线生成器
        State _st = STATE_NA;                               // 对冲类型 对象的 对冲持仓状态
        string _cfgStr;                                     // 图表配置 字符串
        double _holdAmount = 0;                             // 持仓量
        bool _isCover = false;                              // 是否平仓 标记
        bool _needCheckOrder = true;                        // 设置是否 检查订单
        Chart _c = Chart("");                               // 图表对象,并初始化
    };
    
    
    

    Como el código es bastante largo, se omite una parte. Se muestra principalmente la estructura de la clase Hedge. No se menciona el constructor Hedge, ya que se utiliza principalmente para la inicialización de objetos. Quedan principalmente dos funciones.

    • getState

    Esta función se encarga principalmente de la detección de órdenes, cancelación de órdenes, detección de posiciones, equilibrio de posiciones, etc. Porque en el proceso de operaciones de cobertura, la situación de una sola pierna (es decir, se negocia un contrato y el otro no) es inevitable. Si se detecta en la lógica de la orden y luego se procesa la orden de seguimiento o el cierre de la posición , la lógica de la estrategia será caótica. Así que adoptamos otro enfoque al diseñar esta pieza. Si se activa la operación de cobertura, se coloca una orden una vez. Independientemente de si se produce una cobertura de una sola pierna, la cobertura se considera exitosa de forma predeterminada. Luego, se verifica el saldo de la posición en la función getState y se ejecuta la lógica para verificar y procesar la El equilibrio se separa.

    • Loop

    La lógica comercial de la estrategia está encapsulada en esta función, donde la llamadagetState , utilice el objeto generador de datos de la línea K para generar los datos de la línea K de la diferencia de precio y emitir juicios sobre la lógica de apertura, cierre y adición de posiciones de cobertura. También hay algunas operaciones de actualización de datos para gráficos.

  • Función principal de la estrategia

    void main() {  
    
    
        ...
    
    
        string realSymbolA = exchange.SetContractType(symbolA)["instrument"];    // 获取设置的A合约(this_week / next_week / quarter ) ,在 OKEX 合约 当周、次周、季度 对应的真实合约ID 。
        string realSymbolB = exchange.SetContractType(symbolB)["instrument"];    // ...
    
    
        string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump());    // 对 ws 接口的要传的参数进行 json 编码、 url 编码
        Log("try connect to websocket");                                                                                                            // 打印连接 WS接口的信息。
        auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs);     // 调用FMZ API Dial 函数 访问  OKEX 期货的 WS 接口
        Log("connect to websocket success");
    
    
        Depth depthA, depthB;                               // 声明两个 深度数据结构的变量 用于储存A合约和B合约 的深度数据
        auto fillDepth = [](json &data, Depth &d) {         // 用接口返回的json 数据,构造 Depth 数据的代码。
            d.Valid = true;
            d.Asks.clear();
            d.Asks.push_back({atof(string(data["asks"][0][0]).c_str()), atof(string(data["asks"][0][1]).c_str())});
            d.Bids.clear();
            d.Bids.push_back({atof(string(data["bids"][0][0]).c_str()), atof(string(data["bids"][0][1]).c_str())});
        };
        string timeA;   // 时间 字符串 A 
        string timeB;   // 时间 字符串 B 
        while (true) {
            auto buf = ws.read();                           // 读取 WS接口 推送来的数据
    
    
            ...
    
    
    }
    

    Una vez iniciada la estrategia, esta comienza a ejecutarse desde la función principal. Durante la inicialización de la función principal, la estrategia se suscribe al mercado de ticks de la interfaz websocket. La tarea principal de la función principal es construir un bucle principal, recibir continuamente información de ticks enviada por la interfaz websocket del exchange y luego llamar a la función miembro del objeto de clase de cobertura: la función Loop. La lógica comercial en la función Loop está impulsada por datos del mercado. Una cosa que debe explicarse es que el mercado de ticks mencionado anteriormente es en realidad la interfaz de datos de profundidad del libro de órdenes suscrito, que obtiene los datos del libro de órdenes de cada nivel. Sin embargo, la estrategia solo utiliza los datos del primer nivel, que en realidad son similares a los datos del mercado de ticks. La estrategia no utiliza datos de otros niveles ni tampoco utiliza los valores del volumen de órdenes del primer nivel. Veamos con más detalle cómo la estrategia se suscribe a los datos de la interfaz websocket y cómo se configura.

    string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump());    
    Log("try connect to websocket");                                                                                                            
    auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs);     
    Log("connect to websocket success");
    

    En primer lugar, debe codificar la URL del parámetro json del mensaje de suscripción transmitido por la interfaz de suscripción, es decir,payload El valor del parámetro. Luego, el paso más importante es llamar a la función de interfaz API de la plataforma de comercio cuantitativo de Inventor.Dial función.Dial La función se puede utilizar para acceder a la interfaz websocket de intercambio. Aquí realizamos algunas configuraciones para permitir que el objeto de control de conexión de WebSocket ws se cree para volver a conectarse automáticamente después de la desconexión (el mensaje de suscripción aún usa la cadena de valor qs del parámetro de carga útil). Para lograr esta función, debeDial Agregue opciones de configuración a la cadena de parámetros de la función.

    DialLos parámetros de la función comienzan de la siguiente manera:

    wss://real.okex.com:10442/ws/v3
    

    ¿Es la dirección de la interfaz websocket a la que se debe acceder y luego utilizar?| Separación. compress=gzip_raw&mode=recv&reconnect=true&payload="+qs Todos estos son parámetros de configuración.

    Nombre del parámetro Descripción
    compress compress es el método de compresión. La interfaz websocket de OKEX utiliza gzip_raw, por lo que se configura en gzip_raw
    mode mode es el modo, y las opciones son dual, enviar y recibir. dual significa bidireccional, enviando datos comprimidos y recibiendo datos comprimidos. enviar es enviar datos comprimidos. recv recibe datos comprimidos y los descomprime localmente.
    reconnect reconnect indica si se debe establecer la reconexión. reconnect=true habilita la reconexión. Si no se establece, la reconexión se deshabilita de manera predeterminada.
    payload La carga útil es el mensaje de suscripción que debe enviarse cuando ws se vuelve a conectar.

    Después de esta configuración, incluso si se desconecta la conexión websocket, el sistema subyacente del custodio de Inventor Quantitative Trading Platform se volverá a conectar automáticamente y obtendrá los últimos datos del mercado de manera oportuna. Aproveche cada fluctuación de la diferencia de precios y capture rápidamente el mercado de cobertura adecuado.

  • Control de posición

El control de posición utiliza un ratio de posición de cobertura similar a la serie “Porfinacci” para controlar.

  for (int i = 0; i < AddMax + 1; i++) {                                          // 构造 控制加仓数量的数据结构,类似 波菲纳契数列 对冲数量 比例。
      if (_addArr.size() < 2) {                                                   // 前两次加仓量变化为: 加一倍对冲数量 递增
          _addArr.push_back((i+1)*OpenAmount);
      }
      _addArr.push_back(_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]);   // 最后 两个加仓数量相加,算出当前的加仓数量储存到 _addArr数据结构中。
  }

Se puede observar que el número de posiciones agregadas cada vez es la suma de las dos posiciones más recientes. Este control de posición puede lograr que cuanto mayor sea la diferencia de precio, mayor sea relativamente la cantidad de cobertura de arbitraje, y las posiciones se pueden dispersar, de modo de captar las pequeñas fluctuaciones de la diferencia de precio con posiciones pequeñas y aumentar las posiciones de manera apropiada con una gran diferencia de precio. fluctuaciones.

  • ### Cerrar una posición: Stop loss, take profit

Spread de toma de ganancias fijo, spread de stop loss. Cuando la diferencia de precio de la posición alcanza la posición de toma de ganancias o la posición de stop loss, se ejecutará la toma de ganancias o el stop loss.

  • ### Diseño de ciclos de entrada y salida del mercado

El período controlado por el parámetro NPeriod proporciona un cierto grado de control dinámico sobre la apertura y el cierre de posiciones de la estrategia.

  • ### Cuadro de estrategia

La estrategia genera automáticamente un gráfico de velas de diferencia de precios y marca la información de transacción relevante.

La operación de dibujo de gráficos personalizados de la estrategia C++ también es muy sencilla. Puede ver que en el constructor de la clase hedge, usamos la cadena de configuración de gráficos escrita _cfgStr para configurar el objeto de gráfico _c._c es la clase de cobertura. Cuando se inicializa el miembro privado, se llama a la función de interfaz API de gráfico personalizado cuantitativo del inventor.Chart El objeto de gráfico construido por la función.

  _cfgStr = R"EOF(
  [{
  "extension": { "layout": "single", "col": 6, "height": "500px"},
  "rangeSelector": {"enabled": false},
  "tooltip": {"xDateFormat": "%Y-%m-%d %H:%M:%S, %A"},
  "plotOptions": {"candlestick": {"color": "#d75442", "upColor": "#6ba583"}},
  "chart":{"type":"line"},
  "title":{"text":"Spread Long"},
  "xAxis":{"title":{"text":"Date"}},
  "series":[
      {"type":"candlestick", "name":"Long Spread","data":[], "id":"dataseriesA"},
      {"type":"flags","data":[], "onSeries": "dataseriesA"}
      ]
  },
  {
  "extension": { "layout": "single", "col": 6, "height": "500px"},
  "rangeSelector": {"enabled": false},
  "tooltip": {"xDateFormat": "%Y-%m-%d %H:%M:%S, %A"},
  "plotOptions": {"candlestick": {"color": "#d75442", "upColor": "#6ba583"}},
  "chart":{"type":"line"},
  "title":{"text":"Spread Short"},
  "xAxis":{"title":{"text":"Date"}},
  "series":[
      {"type":"candlestick", "name":"Long Spread","data":[], "id":"dataseriesA"},
      {"type":"flags","data":[], "onSeries": "dataseriesA"}
      ]
  }
  ]
  )EOF";
  _c.update(_cfgStr);                 // 用图表配置 更新图表对象
  _c.reset();                         // 重置图表数据。
  • Llamar_c.update(_cfgStr); usar _cfgStr Configuración del objeto gráfico.
  • Llamar_c.reset(); Restablecer datos del gráfico.

Cuando el código de estrategia necesita insertar datos en el gráfico, también se llama directamente._función miembro del objeto c, o_La referencia de c se pasa como parámetro y luego se llama a la función miembro del objeto (método) de _c para realizar operaciones de inserción y actualización de datos del gráfico. Por ejemplo:

  _c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action},  {"text", format("diff: %f", opPrice)}, {"color", color}});

Después de realizar un pedido, márquelo en el gráfico de velas.

Como se muestra a continuación, el dibujo de la línea K se realiza llamando a la función miembro de la clase BarFeederfeed Cuando el objeto gráfico_Se pasa una referencia a c como parámetro.

  void feed(double price, Chart *c=nullptr, int chartIdx=0)

Ahora mismofeedEl parámetro c de la función.

  json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close};            // 构造一个 json 类型数据
  if (c != nullptr) {                                                         // 图表对象指针不等于 空指针,执行以下。
     if (newBar) {                                                            // 根据标记判断,如果出现新Bar 
          c->add(chartIdx, point);                                            // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
          c->reset(1000);                                                     // 只保留1000 bar个数据
      } else {
          c->add(chartIdx, point, -1);                                        // 否则就更新(不是新bar),这个点(更新这个bar)。
      } 
  }

Al llamar al objeto gráfico_doaddFunción miembro, inserta nuevos datos de la barra de la línea K en el gráfico. Código:c->add(chartIdx, point);

  • ### Prueba retrospectiva

Aprenda la estrategia básica de “Estrategia de cobertura de contratos OKEX en C++”

Aprenda la estrategia básica de “Estrategia de cobertura de contratos OKEX en C++”

Aprenda la estrategia básica de “Estrategia de cobertura de contratos OKEX en C++”

Esta estrategia es solo para aprendizaje y comunicación. Cuando la utilice en operaciones reales, modifíquela y optimícela según la situación real de la operación.

Dirección de estrategia: https://www.fmz.com/strategy/163447

Para conocer estrategias más interesantes, visite la “Plataforma de negociación cuantitativa Inventor”: https://www.fmz.com