Логика торговли криптовалютными фьючерсами

Автор:Доброта, Создано: 2020-07-18 13:31:34, Обновлено: 2023-10-26 20:05:28

img

Проблематическая сцена

Долгое время меня всегда беспокоила проблема задержки передачи данных в интерфейсе API криптовалютной биржи. Я не нашел подходящего способа справиться с ней. Я воспроизнесу сцену этой проблемы.

Обычно рыночный заказ, предоставляемый биржей контрактов, на самом деле является ценой контрагента, поэтому иногда так называемый рыночный заказ является несколько ненадежным. Поэтому, когда мы пишем стратегии торговли фьючерсами криптовалют, большинство из них используют лимитные ордера. После каждого размещения ордера нам нужно проверить позицию, чтобы увидеть, выполнен ли заказ и удерживается ли соответствующая позиция.

Проблема заключается в этой информации о позиции. Если ордер закрыт, данные, возвращенные обменной информацией об интерфейсе позиции (то есть обменный интерфейс, который нижний слой фактически получает доступ, когда мы называемexchange.GetPositionЕсли данные, возвращаемые биржей, являются старыми данными, то есть информацией о позиции ордера, размещенного до завершения сделки, это вызовет проблему.

Логика торговли может считать, что ордер не был выполнен и продолжать размещать ордер. Однако интерфейс размещения ордеров биржи не задерживается, но транзакция быстрая, и ордер выполняется. Это приведет к серьезному последствию того, что стратегия будет неоднократно размещать заказы при запуске операции открытия позиции.

Опыт

Из-за этой проблемы я видел стратегию заполнения длинной позиции безумно, к счастью, рынок поднялся в то время, и плавающая прибыль однажды превысила 10BTC. К счастью, рынок взлетел вверх. Если это падение, конец можно представить.

Попробуйте решить

  • План 1

Можно разработать логику размещения ордеров для стратегии размещения только одного ордера. Цена размещения ордера является большим скольжением для ценового разрыва цены оппонента в то время, и определенная глубина ордеров оппонента может быть выполнена. Преимущество этого заключается в том, что размещается только один ордер, и он не оценивается на основе информации о позиции. Это может избежать проблемы повторного размещения ордеров, но иногда, когда цена меняется относительно сильно, ордер запускает механизм ценового лимита биржи, и это может привести к тому, что большой ордер скольжения все еще не завершен, и упустил торговую возможность.

  • План 2

Используя функцию рыночной цены биржи, цена пропуска -1 на FMZ является рыночной ценой. В настоящее время интерфейс фьючерсов OKEX был обновлен для поддержки реальной рыночной цены.

  • План 3

Мы по-прежнему используем предыдущую логику торговли и размещаем лимитный ордер, но добавляем некоторое обнаружение в логику торговли, чтобы попытаться решить проблему, вызванную задержкой данных о позиции. После размещения ордера, если ордер не отменен, он исчезает непосредственно в списке ожидаемых ордеров (список ожидаемых ордеров исчезает двумя возможными способами: 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функция выше.

Шаблон является бета-версией, любые предложения приветствуются, я буду продолжать оптимизировать для решения проблемы задержек в данных о положении.


Связанные

Больше