"Estrategias de cobertura contractual de OKEX en C++"

El autor:Un sueño pequeño., Creado: 2019-08-26 14:30:47, Actualizado: 2023-10-19 21:09:01

img

"Estrategias de cobertura contractual de OKEX en C++"

Hablando de estrategias de cobertura, hay una gran variedad de estrategias, combinaciones y ideas en los diferentes mercados. Empezamos con los clásicos de cobertura a largo plazo. Hoy en día, el mercado de divisas digitales es mucho más activo que cuando se formó, y hay muchos intercambios de contratos que ofrecen una gran cantidad de oportunidades de cobertura.

  • Principios estratégicos

    La razón por la que se dice que la estrategia es un poco dura es que está escrita en lenguaje C++, y es un poco difícil de leer. Pero no impide que el lector aprenda lo esencial sobre el diseño y la idea de la estrategia. La estrategia es más concisa, con una longitud de código moderada de solo 500 líneas. En cuanto al diseño, la estructura de la estrategia es razonable, la congruencia del código es baja, es fácil de ampliar u optimizar. El pensamiento lógico es claro, 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. En la actualidad, la mayoría de las empresas que ofrecen servicios de venta de ropa de lujo están trabajando en el sector de la ropa. En este sentido, el ministro de Asuntos Exteriores de la República de China, Li Keqiang, dijo que el gobierno de la República de China no ha firmado un acuerdo sobre el tema. Los principios básicos están claros, lo que queda es la estrategia de cómo activar el arriesgamiento, cómo liquidar, cómo aumentar la posición. La estrategia de hedge se centra principalmente en la variación de los precios de los productos indicados, para negociar retroactivamente los diferenciales. Sin embargo, los diferenciales pueden ser pequeños, grandes o unilaterales. Esto conlleva la incertidumbre de las ganancias y pérdidas de cobertura, pero el riesgo es mucho menor que la tendencia unilateral. Muchas de las optimizaciones de las estrategias a largo plazo se optan por comenzar desde el nivel de control de posiciones y comenzar desde el desencadenamiento de posiciones abiertas. Por ejemplo, el clásico uso del indicador de Brin como una estrategia de cobertura a largo plazo de los indicadores de Brin en caso de fluctuaciones de los precios de los diferencias.

  • Desarrollo de la estrategia de código

    En general, el código se divide en cuatro partes principales.

    • 1. La definición de valores de lista, la definición de algunos valores de estado, para marcar el estado. Algunas funciones funcionales que no están relacionadas con la idea de la política, como las funciones de codificación de url, las funciones de conversión de tiempo, etc., no están relacionadas con la idea de la política y solo se utilizan para procesar datos.
    • 2.K线数据生成器类:策略由该生成器类对象生成的K线数据驱动。
    • 3.对冲类:该类的对象可以执行具体的交易逻辑,对冲操作、策略细节的处理机制等。
    • 4.策略主函数,也就是 mainLa función.mainLa función es la función de entrada de la política, y el ciclo principal se ejecuta dentro de esta función, además de una operación importante que se realiza dentro de esta función: acceder a la interfaz websocket de la bolsa, obtener los datos de tick movimiento impulsados, como los datos de la materia prima del generador de datos de línea K.

    Con un conocimiento global del código de la estrategia, podemos aprender el diseño, las ideas y las técnicas de la estrategia a través de un análisis gradual de cada uno de los componentes.

    • Definición de valores de lista, otras funciones funcionales

      1 Lista de tiposStateDeclaración

      enum State {                    // 枚举类型  定义一些 状态
          STATE_NA,                   // 非正常状态
          STATE_IDLE,                 // 空闲
          STATE_HOLD_LONG,            // 持多仓
          STATE_HOLD_SHORT,           // 持空仓
      };
      

      Como hay funciones en el código que devuelven un estado, todos estos estados están definidos en el tipo de lista.StateEn el centro. Se ve en el código.STATE_NAEl estado de abnormalidad es el estado en el que se encuentra la persona.STATE_IDLEPara el estado de vacío, es decir, el estado en el que se puede operar con cobertura.STATE_HOLD_LONGPara mantener una posición de cobertura adecuada.STATE_HOLD_SHORTEl estado de las posiciones de cobertura contrapartida.

      2, sustitución de strings, no se llama en esta política, es una función de herramientas de respaldo, que se ocupa principalmente de las strings.

      string replace(string s, const string from, const string& to)   
      

      Función 3 es una función que se convierte en un sistema de 16 caracteres.toHex

      inline unsigned char toHex(unsigned char x)
      

      4 Función para procesar el código de url

      std::string urlencode(const std::string& str)
      

      5, función de conversión de tiempo, que convierte el tiempo del formato de la cuerda en el tiempo de la barra.

      uint64_t _Time(string &s)
      
    • Generación de datos en 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 tick obtenidos en K-lines de diferencia para impulsar la lógica de cobertura estratégica. Algunos lectores pueden tener dudas, ¿por qué usar datos de tick? ¿Por qué construir un generador de datos de línea K como este? ¿No es bueno usar datos de línea K directamente? Los datos de la línea K de la diferencia de precios de los dos contratos son estadísticas de cambios en los precios de la diferencia en un período determinado, por lo que no se puede simplemente tomar los datos de la línea K de los dos contratos respectivos para hacer una operación de deducción y calcular el diferencial de los datos en cada línea K Bar, como el diferencial. El error más obvio es, por ejemplo, que el precio más alto, el precio más bajo de los dos contratos, no siempre es el mismo momento. Por lo tanto, el número de valores deducidos no tiene mucho significado. Por lo tanto, necesitamos usar datos de ticks en tiempo real, calcular diferencias en tiempo real, estadísticas en tiempo real de los cambios de precios dentro de un ciclo determinado (es decir, los altos y bajos en el pilar de K). Así que necesitamos un generador de datos de K-line, separado como una clase, que sea bueno para procesar la separación lógica.

    • Clase de 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("");                               // 图表对象,并初始化
      };
      
      
      

      Debido a que el código es relativamente largo, se omite una parte, que muestra principalmente la estructura de esta clase de cobertura, la función de construcción Hedge no se menciona, principalmente la inicialización de objetos; lo que queda es que hay dos funciones principales.

      • - ¿ Qué pasa?

        Esta función se ocupa principalmente de la detección de pedidos, la cancelación de pedidos, la detección de posiciones, el equilibrio de posiciones, etc. Porque en el proceso de negociación de cobertura, no se puede evitar la situación de un solo pie (es decir, un contrato está firmado, un contrato no está firmado), si se realiza la detección en la lógica de la orden, y luego se procesa la orden de seguimiento o el equilibrio, la lógica estratégica será más desordenada. Por lo tanto, se adoptó otra forma de pensar al diseñar esta parte.

      • El bucle

        La lógica de transacción de la estrategia está envuelta en esta función, donde se llamagetStateSe utilizan objetos generadores de datos K-line para generar datos K-line de diferencias, para realizar juicios de apertura, liquidación y lógica de acoplamiento. También hay algunas operaciones para actualizar los datos de los 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 política se ejecuta desde la función main, la cual se inicializa en la función main, y se suscribe al tick market de la interfaz websocket. La función main tiene como principal tarea construir un ciclo principal, recibir continuamente los tick markets enviados por la interfaz websocket de la bolsa, y luego llamar a las funciones miembro de los objetos de tipo de cobertura: Loop functions. La lógica de transacción en la función Loop es impulsada por los datos de transacción. Un punto a tener en cuenta es que el mercado de ticks mencionado en el artículo anterior, en realidad es una interfaz de datos de profundidad fina de la orden de suscripción, que obtiene datos finos de la orden de cada grupo. Pero la estrategia solo utiliza datos del primer grupo, que en realidad son similares a los datos del mercado de ticks, la estrategia no utiliza datos de otros grupos, ni el número de pedidos del primer grupo. Para más detalles, vea la política de cómo suscribir los datos de la interfaz websocket y cómo configurarlos.

      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");
      

      Primero, se debe codificar la URL de los mensajes de suscripción enviados por la interfaz de suscripción con el parámetro json, es decir,payloadEl valor de los parámetros. Luego, un paso más importante es llamar a la función de la interfaz API de la plataforma de intercambio cuantitativa de los inventores.DialLa función.DialLa función se utiliza para acceder a la interfaz websocket de la bolsa. Aquí hacemos algunas configuraciones para que el objeto de control de conexión de websocket que se va a crear ws tenga una conexión automática de corte (los mensajes de suscripción todavía utilizan la cadena qs del valor del parámetro payload).DialAñadir opciones de configuración en la cadena de parámetros de la función.

      DialLa primera parte de los parámetros de las funciones es la siguiente:

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

      La dirección de la interfaz de websocket que se necesita para acceder a ella y luego usarla para obtener información.|Separado.compress=gzip_raw&mode=recv&reconnect=true&payload="+qsTodos son parámetros de configuración.

      Nombre del parámetro Descripción
      comprimir compress es el modo de compresión, la interfaz del websocket de OKEX usa gzip_raw Este modo, por lo tanto, está configurado como gzip_raw
      el modo modo es el modo, opcional dual, send, recv Tres tipos; dual es bidireccional, envía datos de compresión y recibe datos de compresión; send para enviar datos de compresión; recv para recibir datos de compresión, descomprimir localmente.
      vuelve a conectar reconnect indica si se ha establecido una reconexión, reconnect=true indica si se ha activado una conexión, sin establecer una no conexión por defecto.
      Carga útil Los mensajes de suscripción que se necesitan para enviar cuando el payload se vuelve a conectar a ws.

      Con esta configuración, incluso si la conexión del websocket se desconecta, el inventor puede volver a conectar automáticamente a la plataforma de transacción cuantitativa. El sistema de base del administrador también puede volver a conectar automáticamente, obteniendo los datos más recientes del mercado en el momento adecuado. Los inversores de los mercados de divisas están tratando de capturar cada movimiento de los precios para captar rápidamente el mercado de cobertura adecuado.

  • Control de posiciones

    El control de posiciones se realiza con una proporción de posiciones de cobertura similar a las columnas de números de "Boffinach".

    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 ver que cada vez que se aumenta el número de posiciones alzado es la suma de las dos posiciones anteriores más recientes. Este tipo de control de posiciones permite lograr un aumento relativo de la cantidad de cobertura de apalancamiento y una diversificación de las posiciones, lo que permite capturar posiciones pequeñas con fluctuaciones de diferencias y posiciones grandes con fluctuaciones de diferencias.

  • Pago de liquidación: pérdidas y pérdidas

    El precio fijo de la suspensión, el precio fijo de la suspensión, el precio fijo de la suspensión. El precio de la diferencia de tenencia se detiene cuando se alcanza el punto de ruptura, el punto de parada se detiene cuando se detiene la ruptura.

  • Ingreso, salida, diseño de ciclos

    El parámetro NPeriod controla el ciclo para controlar el equilibrio de la estrategia.

  • Diagrama de estrategias

    La estrategia genera automáticamente un gráfico de K-line de diferencias, marcando la información relacionada con las transacciones.

    La estrategia de C++ para crear gráficos personalizados es muy sencilla, como se puede ver en la función de construcción de las clases de cobertura, donde usamos la configuración de gráficos bien escrita con la cadena de configuración_cfgStr configurada para el objeto de gráfico_c, _c es el miembro privado de la clase de cobertura.ChartObjetos gráficos construidos por funciones.

    _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();                         // 重置图表数据。
    
    • Llamado_c.update(_cfgStr);Configurar objetos de gráficos con el _cfgStr.
    • Llamado_c.reset();Reemplazar los datos del gráfico.

    Cuando el código estratégico requiere insertar datos en un gráfico, también se puede realizar una operación de inserción de datos mediante la actualización de datos del gráfico, llamando directamente a la función miembro del objeto _c, o pasando una referencia de _c como parámetro, y luego llamando a la función miembro del objeto _c. Por ejemplo:

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

    Después de la transacción, se escribe un registro de etiquetas en el gráfico de la línea K.

    A continuación, el dibujo de la línea K es una función miembro de la clase BarFeeder mediante la invocaciónfeedCuando el objeto del gráfico _c es un parámetro, se introduce el parámetro.

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

    Es decir,feedEl parámetro de la función c.

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

    El objeto de gráfico _c es llamadoaddFunción miembro para insertar nuevos datos de KlineBar en el gráfico. El código es:c->add(chartIdx, point);

  • Las pruebas

    img

    img

    img

Esta estrategia se utiliza sólo para el aprendizaje de intercambio, cuando se utiliza el disco real, por favor, modifique y optimice usted mismo según la situación real del disco.

La dirección de la estrategia:https://www.fmz.com/strategy/163447

Más estrategias interesantes pueden encontrarse en "Inventors Quantify Trading Platforms":https://www.fmz.com


Relacionados

Más.