仮想通貨先物取引の論理

作者: リン・ハーン優しさ作成日:2020年7月18日 13:31:34 更新日:2023年10月26日 20:05:28

img

問題現場

長い間,暗号通貨取引所の API インターフェイスのデータ遅延問題は常に私を悩ませてきた.私はそれに対処する適切な方法を見つけることができませんでした.この問題のシーンを再現します.

通常,コントラクト取引所が提供するマーケットオーダーは実際には対価であるため,いわゆる"マーケットオーダー"は,ある程度信頼性が低い場合もあります.したがって,暗号通貨先物取引戦略を書いているとき,そのほとんどはリミットオーダーを使用します.各オーダーが配達された後,オーダーが満たされ,対応するポジションが保持されているかどうかを確認する必要があります.

この位置情報に問題があります. オーダーが閉ざされた場合,交換位置情報インターフェース (つまり,下層が実際にアクセスする交換インターフェース) によって返されるデータは,exchange.GetPosition取引が完了する直前に下された注文の位置情報です. しかし,取引所が返したデータは古いデータである場合,これは問題になります.

取引論理は,オーダーが満たされていないと判断し,オーダーを継続する可能性があります.しかし,取引所のオーダー配置インターフェースは遅延しませんが,取引は迅速で,オーダーが実行されます.これは,戦略がポジションを開設する操作をトリガーする際に繰り返しオーダーを置く深刻な結果を引き起こすでしょう.

実際 の 経験

この問題のために,私はロングポジションを満たす戦略を見ました 狂った,幸運なことに,その時点で市場は上昇し,浮動利益は一度10BTCを超えました. 幸いなことに,市場は急上昇しました.それが下落した場合,終わりは想像できます.

解決 を 試みる

  • プラン1

注文の配置の論理を設計することで,戦略は1つの注文のみを行うことができる.注文の配置価格は,当時の相手価格の価格ギャップに対する大きなスライプであり,特定の深さの相手注文が実行できる.この利点は1つの注文のみが配置され,ポジション情報に基づいて判断されないことである.これは,繰り返し注文の配置の問題を回避できるが,時には価格が比較的大きく変化すると,注文は取引所の価格制限メカニズムを起動し,大きなスライプオーダーがまだ完了せず,取引機会を逃してしまう可能性があります.

  • プラン2

取引所の"市場価格"関数を用いると,FMZの価格パス−"は"市場価格"である.現在,OKEXの先物インターフェースは"実際の市場価格"をサポートするためにアップグレードされている.

  • プラン3

我々は,以前の取引ロジックを使い,制限オーダーを置くが,ポジションデータの遅延によって引き起こされた問題を解決しようと,取引ロジックに検出を加える.オーダーが付いた後,注文がキャンセルされない場合は,直ぐに待機中のオーダーリストに消える (待機中のオーダーリストは,2つの可能な方法で消える: 1の引き取りオーダー,2の実行),そのような状況を検出し,再びオーダー金額を置く.最後のオーダーの金額は同じである.この時点で,ポジションデータが遅れているかどうかを注意する必要があります. ポジション情報を再取得するためにプログラムを待機論理に入力してください. 起動待ち数を最適化し,増加することもできます. 特定の回数を超えた場合は,ポジションインターフェースデータが遅れます. 問題が深刻であれば,取引ロジックを終了させてください.

プラン3に基づく設計

// Parameter
/*
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);
            }
            // Detect directBreak and the position has not changed
            if (preNeedOpen == needOpen && directBreak) {
                Log("Suspected position data is delayed, wait 30 seconds", "#FF0000")
                Sleep(30000)
                nowPosition = GetPosition(e, contractType, direction);
                if (nowPosition) {
                    needOpen = opAmount - (nowPosition.Amount - initAmount);
                }
                /*
                timeoutCount++
                if (timeoutCount > 10) {
                    Log("Suspected position delay for 10 consecutive times, placing order fails!", "#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, "Open long position", contractType, ticker);
        } else {
            orderId = e.Sell(ticker.Buy - SlidePrice, amount, "Open short position", 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, "Close long position", contractType, ticker);
        } else if (position.Type == PD_SHORT) {
            e.SetDirection("closesell");
            e.Buy(ticker.Sell + SlidePrice, amount, "Close short position", 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についてmain上の関数です

テンプレートはベータ版です. どんな提案も歓迎します. 位置データの遅延の問題に対処するために最適化を続けます.


関連性

もっと