Einige Gedanken zur Logik des digitalen Währungsterminhandels

Erstellt in: 2020-06-01 09:52:45, aktualisiert am: 2023-10-08 19:41:25
comments   6
hits   2069

Einige Gedanken zur Logik des digitalen Währungsterminhandels

Einige Gedanken zur Logik des digitalen Währungsterminhandels

Problemszenario

Das Problem der Datenverzögerung der API-Schnittstelle für digitale Währungsumtauschpositionen hat mich lange Zeit gestört. Da ich noch keine passende Lösung gefunden habe, werde ich das Problem reproduzieren. Normalerweise entspricht die von der Vertragsbörse bereitgestellte Marktorder tatsächlich dem Preis der Gegenpartei. Daher ist die Verwendung dieser sogenannten „Marktorder“ manchmal unzuverlässig. Daher verwenden die meisten von uns beim Schreiben von Handelsstrategien für digitale Währungs-Futures Limit-Orders. Nach der Platzierung jeder Bestellung müssen wir die Position überprüfen, um festzustellen, ob die Bestellung ausgeführt wurde und ob die entsprechende Position gehalten wird. Das Problem liegt in diesen Positionsinformationen. Wenn der Auftrag ausgeführt wird, sollten die von der Börsenschnittstelle für Positionsinformationen zurückgegebenen Daten (d. h. die Börsenschnittstelle, auf die tatsächlich zugegriffen wird, wenn wir exchange.GetPosition aufrufen) die neu eröffneten Positionsinformationen enthalten. Wenn jedoch Wenn es sich bei den von der Börse zurückgegebenen Daten um alte Daten handelt, also um die Positionsinformationen vor der Ausführung der gerade erteilten Bestellung, liegt ein Problem vor. Die Handelslogik könnte davon ausgehen, dass die Order nicht ausgeführt wurde und weiterhin mit der Platzierung der Order fortfahren. An der Schnittstelle zur Auftragserteilung der Börse kommt es jedoch zu keinen Verzögerungen. Stattdessen werden Transaktionen sehr schnell abgeschlossen und Aufträge ausgeführt, sobald sie erteilt werden. Dies wird die schwerwiegende Konsequenz haben, dass die Strategie wiederholt Aufträge erteilt, wenn eine Eröffnungsoperation ausgelöst wird.

Tatsächliche Erfahrung

Aufgrund dieses Problems habe ich eine Strategie gesehen, die eine verrückte volle Long-Position eröffnete. Glücklicherweise boomte der Markt zu dieser Zeit und der schwebende Gewinn überstieg einmal 10 BTC. Glücklicherweise boomt der Markt. Wäre er eingebrochen, wäre das Ergebnis vorhersehbar gewesen.

Versuchen Sie zu lösen

  • Lösung 1 Die Auftragslogik der Strategie kann so gestaltet werden, dass nur eine Bestellung aufgegeben wird und der Auftragspreis dem aktuellen Preis des Gegners zuzüglich eines größeren Slippages entspricht, um Aufträge des Gegners mit einer bestimmten Tiefe anzunehmen. Der Vorteil hierbei ist, dass Sie nur eine Bestellung aufgeben und diese nicht auf Positionsdaten basiert. Dadurch kann das Problem doppelter Aufträge vermieden werden. Manchmal kann jedoch die Platzierung eines Auftrags bei starken Preisschwankungen den Preislimitmechanismus der Börse auslösen, und es ist möglich, dass der Auftrag selbst bei einem großen Slippage nicht ausgeführt wird und somit die Gelegenheit verpasst wird. .

  • Lösung 2 Verwenden Sie die Market-Order-Funktion der Börse und geben Sie -1 an FMZ als Preis weiter, was eine Market-Order ist. Derzeit wurde die OKEX-Futures-Schnittstelle aktualisiert, um echte Market-Orders zu unterstützen.

  • Lösung 3 Wir verwenden noch immer die vorherige Handelslogik und platzieren Aufträge mithilfe von Limit-Orders, aber wir fügen der Handelslogik einige Erkennungen hinzu, um zu versuchen, das durch die Verzögerung der Positionsdaten verursachte Problem zu lösen. Überprüfen Sie, ob die Bestellung aus der Liste der ausstehenden Bestellungen verschwindet, ohne dass sie nach der Bestellung storniert wird (es gibt zwei Möglichkeiten für das Verschwinden aus der Liste der ausstehenden Bestellungen: 1 Stornierung und 2 Erfüllung). Wenn eine solche Situation erkannt wird und die Bestellmenge und die Bestellung Das Volumen ist das gleiche wie beim letzten Mal. Zu diesem Zeitpunkt sollten Sie darauf achten, ob die Positionsdaten verzögert sind. Lassen Sie das Programm in die Wartelogik wechseln und die Positionsinformationen erneut abrufen. Sie können die Anzahl sogar weiter optimieren und erhöhen von Auslösewartezeiten. Wenn sie eine bestimmte Zahl überschreitet, bedeutet dies, dass die Positionsschnittstellendaten verzögert sind. Das Problem ist schwerwiegend und die Logik dieser Transaktion wird beendet.

Entwurf nach Schema 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")
}

Vorlagenadresse: https://www.fmz.com/strategy/203258

Die Vorgehensweise zum Aufrufen der Vorlagenschnittstelle ist die gleiche wie in der Hauptfunktion oben.$.OpenLong$.CoverLong。 Die Vorlage ist eine Beta-Version. Vorschläge und Kommentare sind willkommen. Wir werden sie weiter optimieren, um das Problem der Positionsdatenverzögerung zu lösen.