avatar of 发明者量化-小小梦 发明者量化-小小梦
フォロー ダイレクトメッセージ
4
フォロー
1271
フォロワー

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

作成日:: 2019-08-26 14:30:47, 更新日:: 2024-12-17 20:43:39
comments   2
hits   3252

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

ヘッジ戦略に関しては、さまざまな市場において、さまざまな戦略、さまざまな組み合わせ、さまざまなアイデアが存在します。まず、最も古典的なクロス期間ヘッジから始めて、ヘッジ戦略の設計アイデアと概念を探ります。現在、デジタル通貨市場は市場が最初に形成されたときよりもはるかに活発になっており、多くの契約取引所が出現し、多数の裁定取引とヘッジの機会を提供しています。スポットクロスマーケット裁定取引、スポット先物ヘッジ裁定取引、先物クロス期間裁定取引、先物クロスマーケット裁定取引など、戦略は無限にあります。次に、取引市場が OKEX 契約取引所である、C++ 言語で記述された「ハードコア」な期間間ヘッジ戦略を見てみましょう。この戦略は、「Inventor Quantitative Trading Platform」に基づいて記述されています。

  • ### 戦略原則

この戦略がややハードコアなのは、戦略が C++ 言語で書かれており、読みにくくなっているためです。しかし、読者がこの戦略設計とアイデアの本質を学ぶことを妨げるものではありません。戦略は全体的に比較的簡潔で、コードの長さも 500 行強と中程度です。市場データの取得に関しては、REST インターフェイスを使用する以前の戦略とは異なり、この戦略では Websocket インターフェイスを使用して取引所から市場データのプッシュを受信します。 設計面では、戦略構造が合理的で、コードの結合度が非常に低く、拡張や最適化が非常に便利です。論理的思考が明確で、使いやすく拡張しやすいデザインです。教育戦略としては、学習戦略設計も良い例です。この戦略の原理は比較的単純で、先物契約と短期契約をポジティブとネガティブのヘッジに使用するというもので、基本的には商品先物の期間間ヘッジと同じです。 プラスの裁定取引、ショート先物契約、ロング短期契約。 ヘッジ、先物契約の買いと短期契約の売り。 基本的な原則が明確になったので、残っているのは、戦略がヘッジポジションをトリガーする方法、ポジションをクローズする方法、およびポジションを増やす方法です。ポジション制御方法と戦略の詳細処理。 ヘッジ戦略は、主に原資産の価格差の変動に焦点を当て、価格差に対して回帰取引を実行します。ただし、スプレッドはわずかに変動したり、急激に変動したり、一方向に変動したりする可能性があります。 これにより、損益ヘッジに不確実性が生じますが、リスクは依然として一方的な傾向よりもはるかに小さくなります。クロス期間戦略のさまざまな最適化の多くは、開始トリガーと終了トリガーから始めて、ポジション制御レベルから開始することを選択します。たとえば、古典的なボリンジャー指標は、価格差が変動する場合の正と負の裁定取引の開始点と終了点として使用されます。合理的な設計と低い結合度により、この戦略はボリンジャー インジケーターの期間間ヘッジ戦略に簡単に変更することもできます。

  • ### 戦略コード分析

#### コードを全体的に見ると、コードは主に 4 つの部分に分かれていることがわかります。

  • 1. 列挙値の定義。ステータスをマークするために使用されるいくつかのステータス値を定義します。 URL エンコード機能、時間変換機能など、戦略に関係のない一部の機能は、戦略とは関係がなく、データ処理にのみ使用されます。
  • 2. K ライン データ ジェネレーター クラス: この戦略は、ジェネレーター クラス オブジェクトによって生成された K ライン データによって駆動されます。
  • 3. ヘッジ クラス: このクラスのオブジェクトは、特定のトランザクション ロジック、ヘッジ操作、戦略詳細処理メカニズムなどを実行できます。
  • 4. 戦略の主な機能、つまりmain 関数。main この関数は戦略のエントリー関数です。メインループはこの関数で実行されます。さらに、この関数は重要な操作も実行します。つまり、取引所のウェブソケットインターフェースにアクセスして、プッシュされたティック市場データを生データとして取得します。 K ライン データ ジェネレーターの材料。データ。

#### 戦略コード全体を理解することで、各リンクを徐々に分析し、戦略の設計、アイデア、テクニックを完全に理解できるようになります。

  • 列挙値の定義、その他の機能

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

    コード内のいくつかの関数は特定のステータスを返すため、これらの状態は列挙型で定義されます。State真ん中。 コードが表示されるSTATE_NA つまり異常な状態です。STATE_IDLE アイドル状態、つまりヘッジ操作が実行できる状態です。STATE_HOLD_LONG プラスのヘッジポジションを保持している状態。STATE_HOLD_SHORT ヘッジポジションを保持している状態。

    1. 文字列の置換: この関数はこの戦略では呼び出されません。主に文字列を処理する予備ツール関数です。
    string replace(string s, const string from, const string& to)   
    
    1. 16進数文字に変換する関数toHex
    inline unsigned char toHex(unsigned char x)
    
    1. URLエンコード処理機能
    std::string urlencode(const std::string& str)
    
    1. 時間変換関数は、文字列形式の時間をタイムスタンプに変換します。
    uint64_t _Time(string &s)
    
  • 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;
    };
    

    このクラスは主に、取得したティック データを価格差 K ラインに処理して、戦略ヘッジ ロジックを実行する役割を担います。 なぜティックデータを使用する必要があるのか​​疑問に思う読者もいるかもしれません。なぜこのような K ライン データ ジェネレーターを構築する必要があるのでしょうか? Kラインデータを直接使用した方が良いのではないでしょうか?いくつかのヘッジ戦略を書いていたときに、これらの 3 つの疑問が頭に浮かびました。スプレッドボリンジャーヘッジ戦略を書いたときに答えを見つけました。単一契約の K ライン データは、一定期間内のこの契約の価格変動統計であるためです。 2つの契約間の価格差のKラインデータは、一定期間内の価格差の統計です。したがって、2つの契約のKラインデータを単純に減算して、各契約のKラインデータの差を計算することはできません。 K ライン バー。価格差としての値。最も明らかな間違いは、2 つの契約の最高価格と最低価格が必ずしも同時になるわけではないということです。したがって、減算された値はあまり意味がありません。 したがって、リアルタイムのティック データを使用し、価格差をリアルタイムで計算し、一定期間内の価格変動(K ライン列の高値の開始と安値の終了)に関する統計をリアルタイムで作成する必要があります。このように、処理ロジックを分離するには、別のクラスとして K ライン データ ジェネレーターが必要です。

  • ヘッジ

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

    コードはかなり長いため、一部は省略されています。主に hedge クラスの構造を示しています。コンストラクタ Hedge は主にオブジェクトの初期化に使用されるため、ここでは説明しません。残っている機能は主に2つです。

    • getState

    この機能は主に注文検出、注文キャンセル、ポジション検出、ポジションバランスなどを処理します。ヘッジ取引のプロセスでは、片足の状況(つまり、一方の契約は取引され、もう一方の契約は取引されない)は避けられません。注文ロジックでそれが検出され、その後にフォローアップ注文またはポジションのクローズが処理される場合戦略ロジックは混乱するでしょう。そこで、この部分を設計する際には別のアプローチを採用しました。ヘッジ操作がトリガーされると、注文が1回出されます。シングルレッグヘッジ状況が発生するかどうかに関係なく、ヘッジはデフォルトで成功と見なされます。次に、getState関数でポジション残高がチェックされ、チェックと処理のロジックが実行されます。残高が分離されます。

    • Loop

    戦略の取引ロジックはこの関数にカプセル化されており、呼び出しはgetState K ライン データ ジェネレーター オブジェクトを使用して価格差の K ライン データを生成し、ヘッジの開始、終了、およびポジション追加のロジックを判断します。チャートのデータ更新操作もいくつかあります。

  • 戦略の主な機能

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

    戦略が開始されると、メイン関数から実行が開始されます。メイン関数の初期化中に、戦略は Websocket インターフェイスのティック マーケットをサブスクライブします。 main 関数の主なタスクは、メイン ループを構築し、取引所の Websocket インターフェイスによってプッシュされたティック情報を継続的に受信し、ヘッジ クラス オブジェクトのメンバー関数である Loop 関数を呼び出すことです。ループ機能の取引ロジックは市場データによって駆動されます。 説明する必要があることの 1 つは、上記のティック マーケットは実際にはサブスクライブされた注文書の深さデータ インターフェイスであり、各レベルの注文書データを取得することです。ただし、この戦略では、実際にはティック市場データに類似した第 1 レベルのデータのみを使用します。この戦略では、他のレベルのデータは使用せず、第 1 レベルの注文量値も使用しません。 戦略が Websocket インターフェースのデータにどのようにサブスクライブし、それがどのように設定されるかを詳しく見てみましょう。

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

    まず、サブスクリプションインターフェースによって送信されるサブスクリプションメッセージのJSONパラメータをURLエンコードする必要があります。つまり、payload パラメータの値。次に、より重要なステップは、Inventor Quantitative Trading PlatformのAPIインターフェース関数を呼び出すことです。Dial 関数。Dial 関数を使用して、Exchange Websocket インターフェイスにアクセスできます。ここでは、切断後に自動的に再接続するためのWebSocket接続制御オブジェクトwsを作成できるようにするための設定をいくつか行います(サブスクリプションメッセージは、ペイロードパラメータの値qs文字列を引き続き使用します)。この機能を実現するには、Dial 関数のパラメータ文字列に構成オプションを追加します。

    Dial関数のパラメータは次のように始まります。

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

    アクセスして使用する必要のあるWebSocketインターフェースアドレスです| 分離。 compress=gzip_raw&mode=recv&reconnect=true&payload="+qs これらはすべて構成パラメータです。

    パラメータ名 説明
    compress compress は圧縮方法です。OKEX Websocket インターフェースは gzip_raw を使用するため、gzip_raw に設定されています
    mode mode はモードで、オプションは、dual、send、recv です。デュアルは双方向を意味し、圧縮されたデータを送信し、圧縮されたデータを受信します。 send は圧縮されたデータを送信します。 recv は圧縮されたデータを受信し、ローカルで解凍します。
    reconnect reconnect は再接続を設定するかどうかです。reconnect=true は再接続を有効にします。設定されていない場合は、デフォルトで再接続は無効になります。
    payload ペイロードは、ws が再接続するときに送信する必要があるサブスクリプション メッセージです。

    この設定を行うと、Websocket 接続が切断された場合でも、Inventor Quantitative Trading Platform カストディアンの基盤システムが自動的に再接続し、最新の市場データをタイムリーに取得します。 あらゆる価格差の変動を捉え、適切なヘッジ市場を迅速に獲得します。

  • 位置制御

ポジション制御は、「ポルフィナッチ」シリーズに類似したヘッジポジション比率を使用して制御します。

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

毎回追加されるポジションの数は、最新の 2 つのポジションの合計であることがわかります。 このようなポジション制御により、価格差が大きいほど裁定ヘッジの量が相対的に大きくなり、ポジションを分散させることができるため、小さなポジションで小さな価格差の変動を把握し、大きな価格差でポジションを適切に増やすことができます。変動。

  • ### ポジションのクローズ: 損切り、利益確定

利益確定スプレッド、損失確定スプレッドを固定しました。 ポジションの価格差が利益確定ポジションまたは損切りポジションに達した場合、利益確定または損切りが実行されます。

  • ### 市場参入と市場退出サイクル設計

パラメータ NPeriod によって制御される期間は、戦略のポジションの開始と終了に対してある程度の動的な制御を提供します。

  • ### 戦略チャート

この戦略は、価格差のローソク足チャートを自動的に生成し、関連する取引情報をマークします。

C++ 戦略カスタム チャート描画操作も非常に簡単です。ヘッジ クラスのコンストラクターで、記述されたチャート構成文字列 _cfgStr を使用してチャート オブジェクト _c を構成していることがわかります。_c はヘッジクラスです。プライベートメンバーが初期化されると、発明者の定量カスタムチャート API インターフェイス関数が呼び出されます。Chart 関数によって構築されたチャート オブジェクト。

  _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();                         // 重置图表数据。
  • 電話_c.update(_cfgStr); 使用 _チャート オブジェクトへの cfgStr 構成。
  • 電話_c.reset(); チャートデータをリセットします。

戦略コードがチャートにデータを挿入する必要がある場合は、直接呼び出されます。_c オブジェクトのメンバー関数、または_c の参照がパラメータとして渡され、_c のオブジェクト メンバー関数 (メソッド) が呼び出されて、チャート データの更新および挿入操作が実行されます。 例えば:

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

注文をしたら、ローソク足チャートにマークを付けます。

以下に示すように、Kラインの描画はBarFeederクラスのメンバー関数を呼び出すことによって行われます。feed チャートオブジェクト_c への参照がパラメータとして渡されます。

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

今すぐfeed関数のパラメータ 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)。
      } 
  }

チャートオブジェクトを呼び出すことによって_caddメンバー関数は、新しい K ライン バー データをチャートに挿入します。 コード:c->add(chartIdx, point);

  • ### バックテスト

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

「C++ での OKEX 契約ヘッジ戦略」のハードコア戦略を学ぶ

この戦略は学習とコミュニケーションのみを目的としています。実際の取引で使用する場合は、取引の実際の状況に応じて修正および最適化してください。

戦略アドレス: https://www.fmz.com/strategy/163447

さらに興味深い戦略については、「Inventor Quantitative Trading Platform」をご覧ください: https://www.fmz.com