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

デジタル通貨先物取引のロジックに関する考察

作成日:: 2020-06-01 09:52:45, 更新日:: 2023-10-08 19:41:25
comments   6
hits   2406

デジタル通貨先物取引のロジックに関する考察

デジタル通貨先物取引のロジックに関する考察

問題のシナリオ

長い間、デジタル通貨交換ポジションAPIインターフェースのデータ遅延問題が常に私を悩ませてきました。まだ適切な解決策が見つかっていないので、この問題を再現してみます。通常、契約取引所が提供する成行注文は実際には相手方の価格であるため、このいわゆる「成行注文」を使用すると信頼できない場合があります。したがって、デジタル通貨先物取引戦略を作成する場合、ほとんどの人は指値注文を使用します。各注文を行った後、注文が実行されたかどうか、対応するポジションが保持されているかどうかを確認する必要があります。問題はこのポジション情報にあります。注文が実行されると、取引所のポジション情報インターフェース(つまり、exchange.GetPositionを呼び出すときに実際にアクセスされる取引所インターフェース)によって返されるデータには、新しく開かれたポジション情報が含まれているはずです。しかし、取引所から返されるデータが古いデータ、つまり、発注したばかりの注文が実行される前のポジション情報である場合、問題が発生します。取引ロジックは注文が実行されていないと判断し、注文を続行する場合があります。ただし、取引所の注文配置インターフェースでは遅延は発生しません。代わりに、取引は非常に迅速に完了し、注文は配置されるとすぐに実行されます。これにより、オープニング操作がトリガーされたときに戦略が繰り返し注文を出すという深刻な結果が生じます。

実際の経験

この問題のために、狂ったようにフルロングポジションをオープンする戦略を見たことがあります。幸いなことに、当時は市場が活況を呈しており、浮動利益は10BTCを超えたことがありました。幸いなことに、市場は活況を呈しており、もし急落していたら、結果は予想できただろう。

解決してみる

  • 解決策1 この戦略の注文ロジックは、1 つの注文のみを配置するように設計することができ、注文価格は、その時点の相手側の価格にさらに大きなスリッページを加えたものとなり、一定の深さの相手側の注文を受け付けます。これを行う利点は、注文が一度だけ行われ、位置情報に基づいていないことです。これにより、重複注文の問題を回避できますが、価格が大きく変動したときに注文を出すと、取引所の価格制限メカニズムがトリガーされ、大きなスリッページがあっても注文が執行されず、機会を逃す可能性があります。 。

  • 解決策2 取引所の成行注文機能を使用し、FMZに-1を価格として渡します。これは成行注文です。現在、OKEX先物インターフェースはアップグレードされ、実際の成行注文をサポートしています。

  • 解決策3 以前の取引ロジックを引き続き使用し、指値注文を使用して注文を配置しますが、ポジション データの遅延によって発生する問題を解決するために、取引ロジックにいくつかの検出を追加します。注文後、キャンセルされずに保留中の注文リストから注文が消えるかどうかを確認します(保留中の注文リストから注文が消えるには、1キャンセルと2履行の2つの可能性があります)。このような状況が検出され、注文数量と注文ボリュームは前回と同じです。このとき、位置データが遅れているかどうかに注意する必要があります。プログラムに待機ロジックを入力して位置情報を再取得させます。最適化を続けて数を増やすこともできます。トリガー待機回数。一定数を超えると、ポジションインターフェースデータが遅延していることを意味します。問題は深刻であるため、このトランザクションのロジックは終了します。

スキーム3に基づく設計

// 参数
/*
var MinAmount = 1
var SlidePrice = 5
var Interval = 500
*/

function GetPosition(e, contractType, direction) {
    e.SetContractType(contractType)
    var positions = _C(e.GetPosition);
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType == contractType && positions[i].Type == direction) {
            return positions[i]
        }
    }

    return null
}

function Open(e, contractType, direction, opAmount) {
    var initPosition = GetPosition(e, contractType, direction);
    var isFirst = true;
    var initAmount = initPosition ? initPosition.Amount : 0;
    var nowPosition = initPosition;
    var directBreak = false 
    var preNeedOpen = 0
    var timeoutCount = 0
    while (true) {
        var ticker = _C(e.GetTicker)
        var needOpen = opAmount;
        if (isFirst) {
            isFirst = false;
        } else {
            nowPosition = GetPosition(e, contractType, direction);
            if (nowPosition) {
                needOpen = opAmount - (nowPosition.Amount - initAmount);
            }
            // 检测directBreak 并且持仓未变的情况
            if (preNeedOpen == needOpen && directBreak) {
                Log("疑似仓位数据延迟,等待30秒", "#FF0000")
                Sleep(30000)
                nowPosition = GetPosition(e, contractType, direction);
                if (nowPosition) {
                    needOpen = opAmount - (nowPosition.Amount - initAmount);
                }
                /*
                timeoutCount++
                if (timeoutCount > 10) {
                    Log("连续10次疑似仓位延迟,下单失败!", "#FF0000")
                    break
                }
                */
            } else {
                timeoutCount = 0
            }
        }
        if (needOpen < MinAmount) {
            break;
        }
        
        var amount = needOpen;
        preNeedOpen = needOpen
        e.SetDirection(direction == PD_LONG ? "buy" : "sell");
        var orderId;
        if (direction == PD_LONG) {
            orderId = e.Buy(ticker.Sell + SlidePrice, amount, "开多仓", contractType, ticker);
        } else {
            orderId = e.Sell(ticker.Buy - SlidePrice, amount, "开空仓", contractType, ticker);
        }

        directBreak = false
        var n = 0
        while (true) {
            Sleep(Interval);
            var orders = _C(e.GetOrders);
            if (orders.length == 0) {
                if (n == 0) {
                    directBreak = true
                }
                break;
            }
            for (var j = 0; j < orders.length; j++) {
                e.CancelOrder(orders[j].Id);
                if (j < (orders.length - 1)) {
                    Sleep(Interval);
                }
            }
            n++
        }
    }

    var ret = {
        price: 0,
        amount: 0,
        position: nowPosition
    };
    if (!nowPosition) {
        return ret;
    }
    if (!initPosition) {
        ret.price = nowPosition.Price;
        ret.amount = nowPosition.Amount;
    } else {
        ret.amount = nowPosition.Amount - initPosition.Amount;
        ret.price = _N(((nowPosition.Price * nowPosition.Amount) - (initPosition.Price * initPosition.Amount)) / ret.amount);
    }
    return ret;
}

function Cover(e, contractType, opAmount, direction) {
    var initPosition = null;
    var position = null;
    var isFirst = true;

    while (true) {
        while (true) {
            Sleep(Interval);
            var orders = _C(e.GetOrders);
            if (orders.length == 0) {
                break;
            }
            for (var j = 0; j < orders.length; j++) {
                e.CancelOrder(orders[j].Id);
                if (j < (orders.length - 1)) {
                    Sleep(Interval);
                }
            }
        }

        position = GetPosition(e, contractType, direction)
        if (!position) {
            break
        }
        if (isFirst == true) {
            initPosition = position;
            opAmount = Math.min(opAmount, initPosition.Amount)
            isFirst = false;
        }

        var amount = opAmount - (initPosition.Amount - position.Amount)
        if (amount <= 0) {
            break
        }

        var ticker = _C(exchange.GetTicker)
        if (position.Type == PD_LONG) {
            e.SetDirection("closebuy");
            e.Sell(ticker.Buy - SlidePrice, amount, "平多仓", contractType, ticker);
        } else if (position.Type == PD_SHORT) {
            e.SetDirection("closesell");
            e.Buy(ticker.Sell + SlidePrice, amount, "平空仓", contractType, ticker);
        }

        Sleep(Interval)
    }

    return position
}

$.OpenLong = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Open(e, contractType, PD_LONG, amount);
}

$.OpenShort = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Open(e, contractType, PD_SHORT, amount);
};

$.CoverLong = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Cover(e, contractType, amount, PD_LONG);
};

$.CoverShort = function(e, contractType, amount) {
    if (typeof(e) == "string") {
        amount = contractType
        contractType = e
        e = exchange
    }

    return Cover(e, contractType, amount, PD_SHORT);
};


function main() {
    Log(exchange.GetPosition())
    var info = $.OpenLong(exchange, "quarter", 100)
    Log(info, "#FF0000")

    Log(exchange.GetPosition())
    info = $.CoverLong(exchange, "quarter", 30)
    Log(exchange.GetPosition())
    Log(info, "#FF0000")

    info = $.CoverLong(exchange, "quarter", 80)
    Log(exchange.GetPosition())
    Log(info, "#FF0000")
}

テンプレートアドレス: https://www.fmz.com/strategy/203258

テンプレートインターフェースを呼び出す方法は、上記のメイン関数と同じです。$.OpenLong$.CoverLong。 テンプレートはベータ版です。ご提案やご意見をお待ちしております。位置データの遅延の問題を解決するために、引き続き最適化を進めていきます。