"OKEX Contract Hedging Strategy em C++"

Autora:Sonhos pequenos, Criado: 2019-08-26 14:30:47, Atualizado: 2023-10-19 21:09:01

img

"OKEX Contract Hedging Strategy em C++"

Falando de estratégias de hedge, há uma grande diversidade de estratégias, de combinações e de ideias em cada mercado. Nós começamos com o mais clássico hedge de longo prazo. Hoje, a atividade do mercado de moeda digital é muito maior do que quando o mercado começou, e muitas bolsas de contratos surgiram, oferecendo uma grande quantidade de oportunidades de hedge.

  • Princípios estratégicos

    Por que dizer que a estratégia é um pouco dura, é porque a estratégia é escrita em C++ e é um pouco mais difícil de ler. Mas não impede o leitor de aprender a essência do design e da ideia da estratégia. A estratégia é mais concisa, o código é de comprimento moderado, apenas mais de 500 linhas. Em termos de design, a estrutura estratégica é razoável, a densidade do código é baixa, é fácil de expandir ou otimizar. O pensamento lógico é claro, esse design não é apenas fácil de usar e expandir. Como uma estratégia de ensino, o design de estratégias de aprendizagem também é um bom exemplo. O princípio estratégico é simples, ou seja, os contratos de longo prazo e os contratos de curto prazo são corretos, a cobertura de contrapartida é, basicamente, consistente com a cobertura de longo prazo dos futuros de commodities. O que é que ele está a fazer? A partir daí, a empresa começou a trabalhar com a empresa para a criação de um novo projeto de negócios. Os princípios básicos são claros, o que resta é a estratégia de como desencadear o hedge, como equilibrar, como aumentar o posicionamento. A estratégia de hedge é principalmente focada na flutuação do preço do item indicado, negociando o diferencial em retorno. No entanto, o diferencial pode ser um pequeno ou grande ou unilateral. Isso traz incerteza de ganhos e prejuízos de hedge, mas o risco é muito menor do que a tendência unilateral. Muitas das melhorias para estratégias de longo prazo são escolhidas a partir do nível de controle de posições e do gatilho do posicionamento aberto.

  • Análise de código estratégico

    O código é dividido em quatro partes principais. O código pode ser dividido em quatro partes principais.

    • 1. Definição de valores de listagem, definição de alguns valores de estado, usados para marcar o estado. Algumas funções funcionais não relacionadas ao pensamento estratégico, como funções de codificação de url, funções de conversão de tempo, etc., não têm relação com o pensamento estratégico e são usadas apenas para processar dados.
    • 2.K线数据生成器类:策略由该生成器类对象生成的K线数据驱动。
    • 3.对冲类:该类的对象可以执行具体的交易逻辑,对冲操作、策略细节的处理机制等。
    • 4.策略主函数,也就是 mainFunção.mainA função é a função de entrada da política, e o principal ciclo é executado dentro dela. Além disso, a função executa uma operação importante: acessar a interface do websocket da bolsa, obter dados do mercado de ticks impulsionados, como dados de matéria-prima do gerador de dados da linha K.

    Com uma compreensão geral do código da estratégia, podemos aprender a conceber, conceber e conceber a estratégia através de uma análise gradual de cada um dos seus elementos.

    • Definição de valores enumerados, outras funções funcionais

      Tipo de listagemStateDeclaração

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

      Como algumas funções no código retornam a um estado, esses estados são definidos no tipo de listagem.StateO que você está fazendo? Veja o que aparece no código.STATE_NAO que é um estado anormal?STATE_IDLEPara o estado de vazio, ou seja, o estado em que as operações podem ser cobertas.STATE_HOLD_LONGA posição de um grupo de investidores é a posição de um grupo de investidores.STATE_HOLD_SHORTO estado de posições de contra-hedge.

      2, Substituição de strings, que não é chamada nesta política, é uma função de ferramenta de backup, que lida principalmente com strings.

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

      Função 3 é uma função que é convertida em 16 caracteres.toHex

      inline unsigned char toHex(unsigned char x)
      

      Funções para processar URLs

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

      5, função de conversão de tempo, que converte o tempo do formato de string em um fuso de tempo.

      uint64_t _Time(string &s)
      
    • Classe de geradores de dados de linha 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 classe é principalmente responsável por processar os dados de tick obtidos em linhas de diferença K, para impulsionar a lógica de hedge estratégico. Alguns leitores podem ter dúvidas, por que usar dados de tick? Por que construir um gerador de dados de linha K assim? Não é bom usar dados de linha K diretamente? Os dados da linha K do diferencial de preços de dois contratos são estatísticas de mudanças de preços de diferença em um determinado período, portanto, não é possível simplesmente tirar os dados da linha K de cada contrato para fazer uma operação de subtração, calcular o diferencial de cada dado em cada linha K Bar, como diferencial. O erro mais óbvio é, por exemplo, que o preço mais alto, o preço mais baixo de dois contratos, não é necessariamente o mesmo momento. Portanto, o valor subtraído não é muito significativo. Portanto, precisamos de usar dados de ticks em tempo real, calcular o diferencial em tempo real, estudar as variações de preços em tempo real dentro de um determinado ciclo (ou seja, os altos e baixos recebimentos nos colunas de linha K); assim, precisamos de um gerador de dados de linha K, separado como uma classe, que seja bom para processar a separação lógica.

    • Classe de contrapartida

      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 o código é relativamente longo, omite uma parte, mostrando principalmente a estrutura desta classe de hedge, não dizendo a construção da função Hedge, principalmente inicialização de objetos; restando, principalmente, duas funções funcionais.

      • getState

        Esta função lida principalmente com a detecção de pedidos, a revogação de pedidos, a detecção de posições, o equilíbrio de posições e outros trabalhos. Como, no processo de negociação de hedge, não é possível evitar uma situação de uma só perna (isto é, um contrato é transacionado, um contrato não é transacionado), se a detecção for feita na lógica de sub-ordem, e depois processar o pedido ou o equilíbrio, a lógica estratégica será mais desordenada.

      • Loop

        A lógica de transação da estratégia é encapsulada nesta função, onde a chamadagetStateO Kline Data Generator utiliza objetos para gerar dados Kline de diferença, para fazer o julgamento da lógica de negociação, liquidação e acréscimo de posições.

    • Funções estratégicas

      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接口 推送来的数据
              
              ...
              
      }
      

      Após a inicialização, a política é executada a partir da função main, que é executada no trabalho de inicialização da função main, assinando o mercado de ticks da interface websocket. O principal trabalho da função main é construir um ciclo principal, receber o mercado de ticks enviado pela interface websocket de um exchange sem parar e, em seguida, chamar a função membro do objeto do tipo de hedge: Loop. É importante ressaltar que o mercado de ticks mencionado no artigo anterior é, na verdade, uma interface de dados de profundidade fina de pedidos de assinatura, obtendo dados finos de pedidos de cada arquivo. Mas a estratégia usa apenas os dados do primeiro arquivo, na verdade, é quase igual aos dados do mercado de ticks. Para mais detalhes, veja a política de como subscrever os dados da interface do websocket e como configurá-los.

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

      A primeira coisa a fazer é codificar a url da mensagem de assinatura enviada pela interface da assinatura com o parâmetro json, ou seja,payloadO valor dos parâmetros. Em seguida, o passo mais importante é chamar a função de interface API da plataforma de negociação quantitativa do inventor.DialFunção.DialA função pode ser usada para acessar a interface do websocket do exchange. Aqui, fazemos algumas configurações para que o websocket que está prestes a ser criado tenha o controle de conexão do objeto ws com reconexão automática de interrupção (a mensagem de assinatura ainda usa a string qs do parâmetro de valor do payload).DialAdicionar opções de configuração na string de parâmetros da função.

      DialA primeira parte do parâmetro da função é a seguinte:

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

      O endereço do interface do websocket é o endereço que precisa ser acessado e depois usado.|A partir de agora, não.compress=gzip_raw&mode=recv&reconnect=true&payload="+qsTodos são parâmetros de configuração.

      Nome do parâmetro Descrição
      compressão compress é o modo de compressão, OKEX websocket interface usa gzip_raw Este modo, portanto, é definido como gzip_raw
      Modo Modo como modo, opcional dual, send, recv Três modos; dual como bidirecional, enviar dados de compressão, receber dados de compressão; send para enviar dados de compressão; recv para receber dados de compressão, descomprimir localmente.
      Conectar de novo reconnect é para saber se a reconexão está definida, reconnect=true é para permitir a reconexão, sem definir a reconexão por defeito.
      carga útil mensagens de subscrição que devem ser enviadas quando o payload é religado para ws.

      Esta configuração permite que, mesmo que o websocket seja desligado, o inventor quantifique a plataforma de negociação e o sistema de base do administrador para religar automaticamente, obtendo dados de mercado atualizados em tempo real. O mercado de hedge financeiro é um mercado de investimento que se baseia em uma estratégia de captação de cada variação de preços e captura rapidamente o mercado de hedge financeiro apropriado.

  • Controle de posição

    O controle de posições é feito usando uma proporção de posições de hedge semelhante a uma coluna 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数据结构中。
    }
    

    Pode-se ver que cada vez que o número de posições aumentadas é a soma das duas posições anteriores mais recentes. Esse controle de posições pode ser realizado com o maior diferencial, o aumento relativo do número de hedges de juros, a diversificação de posições, assim, capturando pequenas posições com pequenas variações de diferencial, as posições com grandes variações de diferencial aumentam apropriadamente.

  • Equilíbrio: Stop-loss e stop-loss

    A diferença de preço fixo para a paralisação, a diferença de paralisação. A diferença de posicionamento é realizada quando o preço chega à posição de crescimento, e a posição de stop loss é a posição de stop loss.

  • Introdução ao mercado, saída do mercado, design do ciclo

    O parâmetro NPeriod controla o ciclo e exerce um certo controle dinâmico sobre a estratégia de posicionamento aberto.

  • Gráfico de estratégias

    A estratégia gera automaticamente um gráfico de linha K de diferença, marcando as informações de transação relevantes.

    A estratégia de C++ para criar gráficos personalizados também é muito simples, como pode ser visto na construção de uma classe de hedge, onde usamos uma configuração de grafos bem escrita com a string_cfgStr configurada para o objeto de gráfico _c, _c é o membro privado da classe de hedge.ChartObjetos gráficos construídos por funções.

    _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();                         // 重置图表数据。
    
    • Chamada_c.update(_cfgStr);Configure os objetos de gráficos com o _cfgStr.
    • Chamada_c.reset();Reinicie os dados do gráfico.

    Quando o código da estratégia precisa inserir dados para o gráfico, também é possível fazer a atualização de dados do gráfico, inserindo a operação, chamando diretamente a função membro do objeto _c, ou passando a referência do objeto _c como um parâmetro, e depois chamando a função membro do objeto _c (método). Por exemplo:

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

    Após a transação, a marcação é registrada no gráfico da linha K.

    A seguir, a linha K é desenhada como uma função membro da classe BarFeeder por meio de uma chamada.feedQuando você faz isso, você pode usar a referência do objeto do gráfico _c como um parâmetro para passar.

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

    Ou seja,feedO parâmetro c da função.

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

    Por exemplo, você pode fazer um gráfico com o objeto _c.addFunção membro para inserir novos dados de linha KBar no gráfico. Código:c->add(chartIdx, point);

  • Revisão

    img

    img

    img

Esta estratégia é apenas para uso de aprendizado de interação. Quando você estiver usando o disco físico, modifique e otimize-o de acordo com a situação real do disco físico.

A estratégia é endereçada:https://www.fmz.com/strategy/163447

Mais estratégias interessantes podem ser encontradas em Inventors Quantify Trading Platforms:https://www.fmz.com


Relacionados

Mais.