2
フォロー
319
フォロワー

裁定取引戦略の簡単な分析:短い時間差で低リスクの機会を捉える方法

作成日:: 2025-03-07 14:11:55, 更新日:: 2025-03-10 17:38:06
comments   0
hits   1386

裁定取引戦略の簡単な分析:短い時間差で低リスクの機会を捉える方法

この戦略のインスピレーションは、Zhihu の著者「Dream Dealer」による機会投稿「TRUMP と MELANIA の低リスク相関アービトラージ モデル」から得られました。この記事では、BN で開始された 2 つの契約 (TRUMP と MELANIA) 間の価格相関を調査し、2 つの契約間の微妙な時間遅延を利用して短期的な市場変動を捉え、低リスクの裁定取引を実現しようとします。次に、この戦略の原則、コード実装ロジックを説明し、可能な最適化の方向性を探ります。

事前に必要知らせ問題は、この戦略が手動の取引ジョブと同等であることです。 2 つの適切な取引ペアを見つけた後にのみ、一定の利益機会があり、取引ペアの利益寿命は短い可能性があります。 利益機会がないことが判明した場合、利益の減少や損失を防ぐために、戦略を適時に停止する必要があります。


1. 戦略の原則と市場の関連性

1.1 戦略的背景

TRUMP 契約と MELANIA 契約は両方とも同じ発行チームによって発行され、同じ管理ファンドを持っているため、ほとんどの場合、価格動向は高度に同期しています。ただし、契約設計や市場執行などの要因により、MELANIA の価格は TRUMP の価格より 1 ~ 2 秒遅れる傾向があります。このわずかな遅延により、裁定取引業者は価格差を捉え、高頻度のコピー取引を行う機会を得ることができます。簡単に言えば、トランプ氏が急激に変動すると、メラニア氏もすぐにそれに追随する傾向がある。この遅れをうまく利用することで、より低いリスクで取引を完了できる。

裁定取引戦略の簡単な分析:短い時間差で低リスクの機会を捉える方法

裁定取引戦略の簡単な分析:短い時間差で低リスクの機会を捉える方法

1.2 暗号通貨市場における普及

同様の相関現象は暗号通貨市場では珍しいことではありません。

  • 同じプロジェクトの異なる契約または派生商品: 基礎となる資産やチームの背景が同じであるため、異なる製品の価格には強い関連性があることがよくあります。
  • クロス取引所裁定取引:異なる取引所の同じ資産でも、流動性やマッチングの仕組みの違いにより、価格に若干の差が生じる場合があります。
  • ステーブルコインと法定通貨連動商品これらの商品では為替レートの変動が予想されることが多く、裁定取引業者はわずかな変動から利益を得ることができます。

この相関関係により、高頻度取引業者や裁定取引業者は安定した取引シグナルと低リスクの運用機会を得ることができますが、微妙な市場の変化に非常に敏感で、リアルタイムで対応できる取引戦略も必要になります。


2. コードロジックの詳細な説明

コードは主に複数の部分で構成されており、各モジュールはアービトラージ戦略の主要なステップに対応しています。

2.1 補助機能の説明

位置情報を取得する

function GetPosition(pair){
    let pos = exchange.GetPosition(pair)
    if(pos.length == 0){
        return {amount:0, price:0, profit:0}
    }else if(pos.length > 1){
        throw '不支持双向持仓'
    }else if(pos.length == 1){
        return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
    }else{
        Log('未获取仓位数据')
        return null
    }
}
  • 機能: この関数は、指定された取引ペアのポジション情報を一律に取得し、ロングポジションとショートポジションを正負の数に変換するために使用されます。
  • ロジック: ポジションが空の場合は、デフォルトのゼロポジションを返します。複数の注文がある場合は、エラーが報告されます (一方向のポジションを確保するため)。それ以外の場合は、現在のポジションの方向、価格、浮動利益と損失を返します。

アカウントを初期化する

function InitAccount(){
    let account = _C(exchange.GetAccount)
    let total_eq = account.Equity

    let init_eq = 0
    if(!_G('init_eq')){
        init_eq = total_eq
        _G('init_eq', total_eq)
    }else{
        init_eq = _G('init_eq')
    }

    return init_eq
}
  • 機能: この関数は、後続の損益計算の基礎として口座純資産を初期化して記録するために使用されます。

保留中の注文をキャンセルする

function CancelPendingOrders() {
    orders = exchange.GetOrders();  // 获取订单
    for (let order of orders) {
        if (order.Status == ORDER_STATE_PENDING) {  // 只取消未完成的订单
            exchange.CancelOrder(order.Id);  // 取消挂单
        }
    }
}
  • 機能: 注文の競合や重複注文を防ぐために、注文を行う前に、以前に完了していない注文を必ずキャンセルしてください。

2.2 メイントランザクションロジック

メイン関数は無限ループを使用して、以下のステップを継続的に実行します。

  1. データ収集と市場計算
    各サイクルはexchange.GetRecords Pair_A と Pair_B の市場データをそれぞれ取得します。

    • 計算式: [ ratio = \frac{Close{A} - Open{A}}{Open{A}} - \frac{Close{B} - Open{B}}{Open{B}} ] 両者の上昇と下降を比較することで、異常な価格差があるかどうかを判断できます。価格差が事前に設定された diffLevel を超えると、開始条件がトリガーされます。
  2. 開業条件を決定し、注文する
    現在のポジションがない場合 (position_B.amount == 0) かつ取引が許可されている場合 (afterTrade==1):

    • 比率が diffLevel より大きい場合、市場は上昇しつつあると考えられるため、Pair_B に対して買い注文が発行されます (買いロングポジション)。
    • 比率が -diffLevel より小さい場合、市場が下落しようとしているとみなされ、売り注文が発行されます (ショートポジションが開かれます)。 注文を行う前に、注文キャンセル機能が呼び出され、現在の注文ステータスがクリアされていることを確認します。
  3. ストッププロフィットとストップロスのロジック
    ポジションが確立されると、戦略はポジションの方向に応じて対応する利益確定注文と損切り注文を設定します。

    • ロングポジション(買い): テイクプロフィット価格をポジション価格に(1 + stopProfitLevel)を掛けた値に設定し、ストップロス価格をポジション価格に(1 - stopLossLevel)を掛けた値に設定します。
    • ショートポジション(売り): テイクプロフィット価格はポジション価格に(1 - stopProfitLevel)を掛けた値に設定され、ストップロス価格はポジション価格に(1 + stopLossLevel)を掛けた値に設定されます。 システムはリアルタイムの市場価格を監視します。利益確定または損切り条件が発動されると、元の保留中の注文はキャンセルされ、ポジションをクローズする注文が出されます。
  4. ポジションをクローズした後の利益統計とログ記録
    各ポジションがクローズされると、システムは口座残高の変化を取得し、利益数、損失数、累積損益額をカウントします。
    同時に、表やグラフを使用して、現在の位置情報、トランザクション統計、サイクル遅延をリアルタイムで表示し、その後の戦略効果分析に便利です。


3. 戦略の最適化と拡張方法

この戦略は、相関性の高い 2 つの契約間の微妙な遅延を利用しますが、改善できる領域はまだ数多くあります。

3.1 パラメータの最適化と動的調整

  • しきい値調整: diffLevel、stopProfitLevel、stopLossLevel などのパラメータは、さまざまな市場環境で調整する必要がある場合があります。これらのパラメータは、履歴データのバックテストを通じて、またはモデルをリアルタイムで動的に調整することによって(機械学習アルゴリズムなど)、自動的に最適化できます。
  • ポジション管理:現在の戦略では、固定の Trade_Number を使用してポジションを開きます。将来的には、動的なポジション管理や、バッチでポジションを開いて徐々に利益を獲得するメカニズムを導入して、単一の取引のリスクを軽減することを検討できます。

3.2 取引シグナルのフィルタリング

  • 多因子シグナル: 価格の増減のみに基づいて比率を計算すると、ノイズの影響を受ける可能性があります。誤ったシグナルをさらに除外するために、取引量、注文書の深さ、テクニカル指標(RSI、MACD など)を導入することを検討できます。
  • 遅延補償: MELANIA には 1 ~ 2 秒の遅延があることを考慮すると、より正確な時間同期と信号予測メカニズムを開発することで、進入タイミングの精度を向上させることができます。

3.3 システムの堅牢性とリスク管理

  • エラー処理: 例外処理とログ記録を追加して、ネットワークの遅延や交換インターフェースの異常が発生した場合にタイムリーな対応を確保し、システム障害による予期しない損失を防ぎます。
  • リスク管理戦略: 資本管理と最大ドローダウン制御を組み合わせて、1 日または 1 回の取引の損失限度を設定し、極端な市場環境での連続損失を防止します。

3.4 コードとアーキテクチャの最適化

  • 非同期処理: 現在、戦略ループは 100 ミリ秒ごとに実行されます。非同期処理とマルチスレッド最適化により、遅延と実行ブロックのリスクを軽減できます。
  • 戦略のバックテストとシミュレーション:完全なバックテストシステムとリアルタイムのシミュレーション取引環境を導入し、さまざまな市場状況下での戦略のパフォーマンスを検証し、実際の取引で戦略がより安定して実行されるようにします。

IV. 結論

この記事では、短期遅れ契約相関アービトラージ戦略の基本原理と実装コードを詳しく紹介します。価格の上昇と下落の差を利用してエントリーの機会を捉えることから、ポジション管理のためのストッププロフィットとストップロスの設定まで、この戦略は暗号市場における資産間の高い相関関係を最大限に活用します。同時に、リアルタイム アプリケーションにおける戦略の安定性と収益性をさらに向上させるために、動的パラメータ調整、信号フィルタリング、システムの堅牢性、コードの最適化など、いくつかの最適化提案も行いました。

この戦略は独創的で実装も簡単ですが、高頻度かつ変動の激しい暗号通貨市場では、裁定取引は慎重に行う必要があります。この記事が、定量取引や裁定取引戦略に熱心な友人にとって貴重な参考資料やインスピレーションとなることを願っています。


注: 戦略テスト環境はOKXシミュレーション取引であり、具体的な詳細は取引所ごとに変更可能です。

function GetPosition(pair){
    let pos = exchange.GetPosition(pair)
    if(pos.length == 0){
        return {amount:0, price:0, profit:0}
    }else if(pos.length > 1){
        throw '不支持双向持仓'
    }else if(pos.length == 1){
        return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
    }else{
        Log('未获取仓位数据')
        return null
    }
}

function InitAccount(){
    let account = _C(exchange.GetAccount)
    let total_eq = account.Equity

    let init_eq = 0
    if(!_G('init_eq')){
        init_eq = total_eq
        _G('init_eq', total_eq)
    }else{
        init_eq = _G('init_eq')
    }

    return init_eq
}

function CancelPendingOrders() {
    orders = exchange.GetOrders();  // 获取订单
    for (let order of orders) {
        if (order.Status == ORDER_STATE_PENDING) {  // 只取消未完成的订单
            exchange.CancelOrder(order.Id);  // 取消挂单
        }
    }
}

var pair_a = Pair_A + "_USDT.swap";
var pair_b = Pair_B + "_USDT.swap";


function main() {
    exchange.IO('simulate', true);
    LogReset(0);
    Log('策略开始运行')

    var precision = exchange.GetMarkets();
    var ratio = 0

    var takeProfitOrderId = null;
    var stopLossOrderId = null;
    var successCount = 0;
    var lossCount = 0;
    var winMoney = 0;
    var failMoney = 0;
    var afterTrade = 1;

    var initEq = InitAccount();

    var curEq = initEq

    var pricePrecision = precision[pair_b].PricePrecision;

    while (true) {
        try{
            let startLoopTime = Date.now();
            let position_B = GetPosition(pair_b);
            let new_r_pairB = exchange.GetRecords(pair_b, 1).slice(-1)[0];

            if (!new_r_pairB || !position_B) {
                Log('跳过当前循环');
                continue;
            }
            
            // 合并交易条件:检查是否可以开仓并进行交易
            if (afterTrade == 1 && position_B.amount == 0) {
                
                let new_r_pairA = exchange.GetRecords(pair_a, 1).slice(-1)[0];
                if (!new_r_pairA ) {
                    Log('跳过当前循环');
                    continue;
                }
                
                ratio = (new_r_pairA.Close - new_r_pairA.Open) / new_r_pairA.Open - (new_r_pairB.Close - new_r_pairB.Open) / new_r_pairB.Open;

                if (ratio > diffLevel) {
                    CancelPendingOrders();
                    Log('实时ratio:', ratio, '买入:', pair_b, position_B.amount);
                    exchange.CreateOrder(pair_b, "buy", -1, Trade_Number);
                    afterTrade = 0;
                } else if (ratio < -diffLevel) {
                    CancelPendingOrders();
                    Log('实时ratio:', ratio, '卖出:', pair_b, position_B.amount);
                    exchange.CreateOrder(pair_b, "sell", -1, Trade_Number);
                    afterTrade = 0;
                }            
            }

            

            // 判断止盈止损
            if (position_B.amount > 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
                Log('多仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 + stopProfitLevel), '止损价格:', position_B.price * (1 - stopLossLevel));
                takeProfitOrderId = exchange.CreateOrder(pair_b, "closebuy", position_B.price * (1 + stopProfitLevel), position_B.amount);
                Log('止盈订单:', takeProfitOrderId);
            }

            if (position_B.amount > 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close < position_B.price * (1 - stopLossLevel) && afterTrade == 0) {
                CancelPendingOrders();
                takeProfitOrderId = null
                Log('多仓止损');
                stopLossOrderId = exchange.CreateOrder(pair_b, "closebuy", -1, position_B.amount);
                Log('多仓止损订单:', stopLossOrderId);
            }

            if (position_B.amount < 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
                Log('空仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 - stopProfitLevel), '止损价格:', position_B.price * (1 + stopLossLevel));
                takeProfitOrderId = exchange.CreateOrder(pair_b, "closesell", position_B.price * (1 - stopProfitLevel), -position_B.amount);
                Log('止盈订单:', takeProfitOrderId, '当前价格:', new_r_pairB.Close );
            }

            if (position_B.amount < 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close > position_B.price * (1 + stopLossLevel) && afterTrade == 0) {
                CancelPendingOrders();
                takeProfitOrderId = null
                Log('空仓止损');
                stopLossOrderId = exchange.CreateOrder(pair_b, "closesell", -1, -position_B.amount);
                Log('空仓止损订单:', stopLossOrderId);
            }


            // 平市价单未完成
            if (takeProfitOrderId == null && stopLossOrderId != null && afterTrade == 0) {
                
                let stoplosspos = GetPosition(pair_b)
                if(stoplosspos.amount > 0){
                    Log('平多仓市价单未完成')
                    exchange.CreateOrder(pair_b, 'closebuy', -1, stoplosspos.amount)
                }
                if(stoplosspos.amount < 0){
                    Log('平空仓市价单未完成')
                    exchange.CreateOrder(pair_b, 'closesell', -1, -stoplosspos.amount)
                }
            }

            // 未平仓完毕
            if (Math.abs(position_B.amount) < Trade_Number && Math.abs(position_B.amount) > 0 && afterTrade == 0){
                Log('未平仓完毕')
                if(position_B.amount > 0){
                    exchange.CreateOrder(pair_b, 'closebuy', -1, position_B.amount)
                }else{
                    exchange.CreateOrder(pair_b, 'closesell', -1, -position_B.amount)
                }
            }

            // 计算盈亏
            if (position_B.amount == 0 && afterTrade == 0) {
                if (stopLossOrderId != null || takeProfitOrderId != null) {
                    stopLossOrderId = null;
                    takeProfitOrderId = null;

                    let afterEquity = exchange.GetAccount().Equity;
                    let curAmount = afterEquity - curEq;

                    curEq = afterEquity

                    if (curAmount > 0) {
                        successCount += 1;
                        winMoney += curAmount;
                        Log('盈利金额:', curAmount);
                    } else {
                        lossCount += 1;
                        failMoney += curAmount;
                        Log('亏损金额:', curAmount);
                    }
                    afterTrade = 1;
                }
            }

            if (startLoopTime % 10 == 0) {  // 每 10 次循环记录一次
                let curEquity = exchange.GetAccount().Equity

                // 输出交易信息表
                let table = {
                    type: "table",
                    title: "交易信息",
                    cols: [
                        "初始权益", "当前权益", Pair_B + "仓位", Pair_B + "持仓价", Pair_B + "收益", Pair_B + "价格", 
                        "盈利次数", "盈利金额", "亏损次数", "亏损金额", "胜率", "盈亏比"
                    ],
                    rows: [
                        [
                            _N(_G('init_eq'), 2),  // 初始权益
                            _N(curEquity, 2),  // 当前权益
                            _N(position_B.amount, 1),  // Pair B 仓位
                            _N(position_B.price, pricePrecision),  // Pair B 持仓价
                            _N(position_B.profit, 1),  // Pair B 收益
                            _N(new_r_pairB.Close, pricePrecision),  // Pair B 价格
                            _N(successCount, 0),  // 盈利次数
                            _N(winMoney, 2),  // 盈利金额
                            _N(lossCount, 0),  // 亏损次数
                            _N(failMoney, 2),  // 亏损金额
                            _N(successCount + lossCount === 0 ? 0 : successCount / (successCount + lossCount), 2),  // 胜率
                            _N(failMoney === 0 ? 0 : winMoney / failMoney * -1, 2)  // 盈亏比
                        ]
                    ]
                };

                $.PlotMultLine("ratio plot", "幅度变化差值", ratio, startLoopTime);
                $.PlotMultHLine("ratio plot", diffLevel, "差价上限", "red", "ShortDot");
                $.PlotMultHLine("ratio plot", -diffLevel, "差价下限", "blue", "ShortDot");

                LogStatus("`" + JSON.stringify(table) + "`");
                LogProfit(curEquity - initEq, '&')
            }
        }catch(e){
            Log('策略出现错误:', e)
        }

        
        Sleep(200);
    }
}