2
Подписаться
319
Подписчики

Краткий анализ арбитражных стратегий: как воспользоваться возможностями с низким риском и короткими временными задержками

Создано: 2025-03-07 14:11:55, Обновлено: 2025-03-10 17:38:06
comments   0
hits   1386

Краткий анализ арбитражных стратегий: как воспользоваться возможностями с низким риском и короткими временными задержками

Вдохновение для этой стратегии пришло из публикации автора журнала Zhihu «Dream Dealer» — «Низкорисковая корреляционная арбитражная модель TRUMP и MELANIA». В статье исследуется ценовая корреляция между двумя контрактами, запущенными на BN (TRUMP и MELANIA), и используется небольшая временная задержка между ними, чтобы попытаться уловить краткосрочные рыночные колебания и добиться арбитража с низким риском. Далее мы объясним принципы этой стратегии, логику реализации кода и рассмотрим возможные направления оптимизации.

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


1. Принципы стратегии и рыночная значимость

1.1 Стратегический фон

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

Краткий анализ арбитражных стратегий: как воспользоваться возможностями с низким риском и короткими временными задержками

Краткий анализ арбитражных стратегий: как воспользоваться возможностями с низким риском и короткими временными задержками

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. Методы оптимизации и расширения стратегии

Хотя эта стратегия использует небольшую задержку между двумя тесно связанными контрактами, все еще есть много областей, которые можно улучшить:

3.1 Оптимизация параметров и динамическая регулировка

  • Регулировка порога: Такие параметры, как diffLevel, stopProfitLevel и stopLossLevel, могут потребовать корректировки в различных рыночных условиях. Эти параметры можно автоматически оптимизировать посредством бэктестинга исторических данных или путем динамической корректировки моделей в режиме реального времени (например, алгоритмов машинного обучения).
  • Управление позицией:Текущая стратегия использует фиксированный Trade_Number для открытия позиции. В будущем мы можем рассмотреть возможность внедрения динамического управления позициями или механизма открытия позиций партиями и постепенного извлечения прибыли для снижения риска единичной сделки.

3.2 Фильтрация торговых сигналов

  • Многофакторный сигнал: Расчет коэффициента, основанный только на увеличении и уменьшении цен, может быть подвержен влиянию шума. Вы можете рассмотреть возможность введения объема торгов, глубины книги заявок и технических индикаторов (таких как RSI, MACD и т. д.) для дальнейшей фильтрации ложных сигналов.
  • Компенсация за задержку: Учитывая, что MELANIA имеет задержку в 1-2 секунды, разработка более точного механизма синхронизации времени и прогнозирования сигнала поможет повысить точность определения времени входа.

3.3 Надежность системы и контроль рисков

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

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);
    }
}