"OKEX Contract Hedging Strategy en C++", qui vous aide à apprendre la stratégie du noyau.

Auteur:Le petit rêve, Créé: 2019-08-26 14:30:47, Mis à jour: 2023-10-19 21:09:01

img

"OKEX Contract Hedging Strategy en C++", qui vous aide à apprendre la stratégie du noyau.

En ce qui concerne les stratégies de couverture, il existe une grande variété de stratégies, de combinaisons et d'idées dans les différents marchés. Nous avons commencé à explorer les idées de conception et les concepts de la stratégie de couverture à partir de la couverture à long terme la plus classique. Aujourd'hui, l'activité du marché de la monnaie numérique est beaucoup plus élevée que lors de la création du marché, et de nombreux marchés de contrats ont vu le jour, offrant de nombreuses opportunités de couverture avantageuse.

  • Les principes stratégiques

    La raison pour laquelle les stratégies sont un peu dures est que les stratégies sont écrites en C++ et sont un peu difficiles à lire. Mais cela n'empêche pas le lecteur d'apprendre l'essence de la conception et de l'idée de la stratégie. Les stratégies sont plus concises, la longueur du code est modérée, seulement plus de 500 lignes. En termes de conception, la structure stratégique est raisonnable, la densité du code est faible, facile à étendre ou à optimiser. Une pensée logique claire, une telle conception n'est pas seulement facile à utiliser et à étendre. En tant que stratégie pédagogique, la conception stratégique d'apprentissage est également un bon exemple. Il y a des gens qui ont des vêtements, qui font des contrats à long terme, qui font des contrats à court terme. Il y a aussi des contrats à long terme et des contrats à court terme. Les principes de base sont clairs, il ne reste plus qu'à déterminer la stratégie pour déclencher l'ouverture d'une position de couverture, la façon de l'équilibrer et de la surcharger. Les stratégies de contrepartie se concentrent principalement sur la fluctuation des prix des produits indiqués, en faisant des échanges de retour sur les écarts. Cependant, il est possible que les écarts soient légers, importants ou unilatéraux. Cela entraîne l'incertitude des gains et pertes de la couverture, mais le risque est encore bien inférieur à la tendance unilatérale. Pour les stratégies à plus long terme, de nombreuses optimisations sont choisies à partir du niveau de contrôle des positions et du déclenchement de la position ouverte. Par exemple, l'indicateur Brin classique est utilisé pour les fluctuations de prix, les positions ouvertes, les positions ouvertes et les points d'équilibre.

  • Le code de la stratégie est analysé

    En regardant un peu le code, on peut conclure que le code est principalement divisé en quatre parties.

    • 1. définition de la valeur de l'énumération, définition de certaines valeurs d'état, utilisées pour marquer l'état. Certaines fonctions fonctionnelles non liées à l'idée stratégique, telles que les fonctions d'encodage d'url, les fonctions de conversion de temps, etc., ne sont pas liées à l'idée stratégique et sont uniquement utilisées pour traiter des données.
    • 2.K线数据生成器类:策略由该生成器类对象生成的K线数据驱动。
    • 3.对冲类:该类的对象可以执行具体的交易逻辑,对冲操作、策略细节的处理机制等。
    • 4.策略主函数,也就是 mainLes fonctions.mainLes fonctions sont des fonctions d'entrée de la stratégie, où le cycle principal est exécuté, et où une opération importante est exécutée, qui est d'accéder à l'interface websocket de l'échange et d'obtenir les données de marché de tick poussées, qui servent de données de base pour le générateur de données K-line.

    Grâce à une compréhension globale du code de stratégie, nous pouvons apprendre la conception, les idées et les techniques de cette stratégie en analysant les différents éléments progressivement.

    • Définition des valeurs énumérées, autres fonctions fonctionnelles

      1, type de listeStateDéclaration

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

      Parce que certaines fonctions dans le code renvoient à un état, ces états sont définis dans le type de l'énumération.StateJe suis désolé. Voir le code apparaîtSTATE_NAIl y a aussi des gens qui ont des problèmes de santé.STATE_IDLEL'état d'impasse, c'est-à-dire l'état où l'opération peut être contre-attaquée.STATE_HOLD_LONGLe taux d'intérêt de l'épargne-investissement est le plus élevé au cours de l'année écoulée.STATE_HOLD_SHORTPour les positions détenues en contre-courant.

      2, substitution de chaîne, qui n'est pas appelée dans cette politique, est une fonction d'outil de sauvegarde qui traite principalement des chaînes.

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

      3, fonction qui est convertie en 16 caractères.toHex

      inline unsigned char toHex(unsigned char x)
      

      4, fonction pour traiter les codes url

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

      5, la fonction de conversion du temps, qui convertit le temps du format de la chaîne en une barre de temps.

      uint64_t _Time(string &s)
      
    • Générateur de données en ligne 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;
      };
      

      Cette classe est principalement chargée de traiter les données de tick obtenues en lignes de différence K, qui sont utilisées pour piloter la logique de couverture stratégique. Certains lecteurs pourraient se demander pourquoi utiliser des données tick? Pourquoi construire un générateur de données K-line comme celui-ci? Est-ce que les données K-line ne sont pas bonnes? Les données K de la différence de prix des deux contrats sont des statistiques de variation de prix de la différence dans un certain cycle, et ne peuvent donc pas simplement prendre les données K des deux contrats respectifs pour effectuer des opérations de déduction et calculer la différence des données sur chaque K de la ligne Bar, comme le décalage. Nous avons donc besoin d'utiliser des données de tick en temps réel, de calculer les différences en temps réel, de statistiquer en temps réel les variations de prix sur une certaine période (c'est-à-dire les hauts et bas de la colonne K). Nous avons donc besoin d'un générateur de données de ligne K, séparé en tant que classe, qui est bon pour traiter la séparation logique.

    • Classes de contrepartie

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

      En raison de la longueur du code, une partie a été omise, principalement pour montrer la structure de cette classe de couverture, la fonction de construction Hedge n'est pas mentionnée, principalement pour l'initialisation d'objets.

      • - Je suis désolé.

        Cette fonction traite principalement de la détection d'ordres, de la révocation d'ordres, de la détection de positions, des soldes de positions, etc.; car, dans le processus de négociation de hedge, il est impossible d'éviter une situation à un pied unique (c'est-à-dire un contrat conclu, un contrat non conclu), si une détection est effectuée dans la logique de sous-commande, puis traite l'ordre ou le solde, la logique stratégique sera plus désordonnée.

      • Boucle

        La logique de transaction de la stratégie est enveloppée dans cette fonction, où l'appelgetStateLes objets du générateur de données K utilisent les données K de la différence pour effectuer des jugements sur la logique des positions ouvertes, de l'équilibrage et de l'augmentation des positions.

    • Fonction principale de stratégie

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

      Une fois que la stratégie est lancée, elle est exécutée à partir de la fonction main, qui s'engage dans l'initialisation de la fonction main en souscrivant à la fonction tick de l'interface websocket. La fonction main a pour tâche principale de construire une boucle principale, de recevoir en permanence les ticks envoyés par l'interface websocket de l'échange, puis d'appeler les fonctions membres de l'objet de type couverture: la fonction Loop. Il est important de préciser que le marché des ticks mentionné ci-dessus est en fait une interface de données profondes de l'ordre de l'abonnement, qui obtient des données fines d'ordre pour chaque classe. Mais la stratégie utilise uniquement les données du premier groupe, qui sont en fait à peu près les mêmes que les données du marché des ticks. Pour plus de détails, voir la politique sur la façon de souscrire aux données de l'interface websocket et comment les configurer.

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

      La première chose à faire est d'encoder l'URL du message d'abonnement transmis par l'interface de l'abonnement avec le paramètre json, qui est:payloadLa valeur des paramètres. Ensuite, une étape plus importante est d'appeler les fonctions d'interface API de la plateforme de négociation quantitative des inventeurs.DialLes fonctions.DialLes fonctions peuvent être utilisées pour accéder à l'interface websocket de l'échange. Nous faisons ici quelques réglages pour que l'objet de contrôle de connexion ws du websocket à créer ait une liaison automatique d'interruption (le message de souscription utilise toujours la chaîne qs de la valeur du paramètre payload).DialAjouter des options de configuration dans la chaîne de paramètres d'une fonction.

      DialLa première partie du paramètre de la fonction est la suivante:

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

      C'est l'adresse de l'interface websocket à laquelle vous devez accéder, puis utilisez|Je ne peux pas vous aider.compress=gzip_raw&mode=recv&reconnect=true&payload="+qsLes paramètres sont des paramètres de configuration.

      Nom du paramètre Décrire
      compresser compress est le mode de compression utilisé par l'interface du websocket OKEX pour gzip_raw.
      le mode mode est le mode, en option dual, send, recv; dual est le mode bidirectionnel, envoyer des données de compression et recevoir des données de compression; send est le mode d'envoi des données de compression; recv est le mode d'accueil des données de compression et de décompression locale.
      reconnecter reconnect indique si la reconnexion est définie, reconnect=true indique si la reconnexion est activée, sans définir la non-réconnexion par défaut.
      charge utile Les messages d'abonnement doivent être envoyés lorsque le payload est rechargé vers ws.

      Une fois configuré de cette façon, même si la connexion du websocket est coupée, le système de base de la plateforme de trading quantifiée par l'inventeur et l'hôte se reconnecte automatiquement pour obtenir les dernières données du marché en temps opportun. Il est important de se concentrer sur les prix des produits et des services, et de se concentrer sur les prix des produits et des services.

  • Contrôle de position

    Le contrôle des positions est effectué en utilisant des proportions de positions de couverture similaires à celles de la colonne de Po Finnach.

    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数据结构中。
    }
    

    On peut voir que chaque fois que le nombre de positions augmentent, c'est la somme des deux positions précédentes. Un tel contrôle de position permet d'obtenir un effet de levier relativement plus élevé, une diversification des positions afin de capturer les petits écarts et les grands écarts.

  • Plafond: les pertes et les pertes ont diminué

    Le prix fixe de l'arrêt, le prix fixe de l'arrêt-perte. La différence de prix de l'acquisition de titres est maintenue jusqu'à ce qu'elle atteigne la position de l'acquisition, la position de l'acquisition ou la position de l'acquisition.

  • Commercialisation, commercialisation, conception de cycles

    Le paramètre NPeriod contrôle le cycle de la stratégie et exerce une certaine maîtrise dynamique sur les positions ouvertes.

  • Graphique stratégique

    La stratégie génère automatiquement un graphique K-line de la différence, marquant les informations de transaction concernées.

    La stratégie C++ pour créer des graphiques personnalisés est également très simple, comme on peut le voir dans la fonction de construction de la classe de contrebande, nous avons utilisé la configuration de la chaîne de configuration de graphique_cfgStr bien écrite pour configurer l'objet du graphique _c, _c est le membre privé de la classe de contrebande.ChartLes objets graphiques construits par des fonctions.

    _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();                         // 重置图表数据。
    
    • Appelée_c.update(_cfgStr);Utilisez _cfgStr pour configurer des objets graphiques.
    • Appelée_c.reset();Réservez les données du graphique.

    Lorsque le code de stratégie nécessite l'insertion de données dans un graphique, il est également possible de mettre à jour les données du graphique en appelant directement la fonction membre de l'objet _c, ou en passant une référence à _c comme paramètre, puis en appelant la fonction membre de l'objet _c. Par exemple:

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

    Une fois la transaction passée, les enregistrements sont étiquetés sur le graphique K-line.

    Ci-dessous, le dessin de la ligne K est une fonction membre de la classe BarFeeder en appelantfeedLorsque vous passez une référence à l'objet du graphique _c comme paramètre, passez une référence à l'objet du graphique _c comme paramètre.

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

    Je veux dire,feedLe paramètre c de la fonction.

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

    En appelant l'objet du graphique _caddUne fonction membre pour insérer de nouvelles données K-LineBar dans le graphique. Le code:c->add(chartIdx, point);

  • Réécriture

    img

    img

    img

Cette stratégie est uniquement utilisée pour apprendre à communiquer.

L'adresse de la stratégie:https://www.fmz.com/strategy/163447

Pour plus d'informations sur les stratégies intéressantes, voir "Inventors Quantitative Trading Platform":https://www.fmz.com


Relationnée

Plus de