2
focar em
319
Seguidores

Uma breve análise das estratégias de arbitragem: como capturar oportunidades de baixo risco com curtos intervalos de tempo

Criado em: 2025-03-07 14:11:55, atualizado em: 2025-03-10 17:38:06
comments   0
hits   1386

Uma breve análise das estratégias de arbitragem: como capturar oportunidades de baixo risco com curtos intervalos de tempo

A inspiração para essa estratégia vem do post de oportunidade do autor de Zhihu, “Dream Dealer” - “Modelo de arbitragem de correlação de baixo risco TRUMP e MELANIA”. O artigo explora a correlação de preços entre dois contratos lançados na BN (TRUMP e MELANIA) e usa o sutil atraso de tempo entre os dois para tentar capturar flutuações de mercado de curto prazo e obter arbitragem de baixo risco. A seguir, explicaremos os princípios dessa estratégia, a lógica de implementação do código e exploraremos possíveis direções de otimização.

Precisa estar adiantadoPerceberO problema é que essa estratégia é equivalente a um trabalho de negociação manual. Ela tem certas oportunidades de lucro somente após encontrar dois pares de negociação adequados, e a vida útil do lucro do par de negociação pode ser curta. Quando se descobre que não há oportunidade de lucro, é necessário interromper a estratégia a tempo para evitar redução de lucro ou até mesmo perda.


1. Princípios de estratégia e relevância de mercado

1.1 Contexto estratégico

Os contratos TRUMP e MELANIA são emitidos pela mesma equipe emissora e têm os mesmos fundos controladores, então suas tendências de preços são altamente sincronizadas na maior parte do tempo. No entanto, devido a fatores como design de contrato ou execução de mercado, o preço da MELANIA tende a ficar atrás do TRUMP em 1-2 segundos. Esse pequeno atraso oferece aos arbitradores a oportunidade de capturar diferenças de preços e conduzir negociações de cópias de alta frequência. Simplificando, quando TRUMP flutua rapidamente, MELANIA tende a seguir o exemplo logo depois. Tirando vantagem desse atraso, as transações podem ser concluídas com menor risco.

Uma breve análise das estratégias de arbitragem: como capturar oportunidades de baixo risco com curtos intervalos de tempo

Uma breve análise das estratégias de arbitragem: como capturar oportunidades de baixo risco com curtos intervalos de tempo

1.2 Prevalência no Mercado de Criptomoedas

Fenômenos de correlação semelhantes não são incomuns no mercado de criptomoedas:

  • Diferentes contratos ou derivativos do mesmo projeto: Devido aos mesmos ativos subjacentes ou histórico de equipe, os preços de diferentes produtos geralmente têm fortes ligações.
  • Arbitragem de câmbio cruzado:O mesmo ativo em diferentes bolsas pode ter pequenas diferenças de preço devido a diferenças na liquidez e nos mecanismos de correspondência.
  • Stablecoins e produtos atrelados a moedas fiduciárias:Esses produtos geralmente apresentam desvios esperados na taxa de câmbio, e os arbitradores podem lucrar com pequenas flutuações.

Essa correlação fornece aos traders de alta frequência e arbitradores sinais de negociação estáveis ​​e oportunidades operacionais de menor risco, mas também exige que as estratégias de negociação sejam altamente sensíveis a mudanças sutis de mercado e sejam capazes de responder em tempo real.


2. Explicação detalhada da lógica do código

O código consiste principalmente em várias partes, cada módulo corresponde às principais etapas da estratégia de arbitragem.

2.1 Descrição da função auxiliar

Obter informações de posição

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
    }
}
  • Funções: Esta função é usada para obter uniformemente as informações de posição do par de negociação especificado e converter as posições longas e curtas em números positivos e negativos.
  • Lógica: Se a posição estiver vazia, retorna a posição zero padrão; se houver mais de uma ordem, um erro é relatado (para garantir uma posição unidirecional); caso contrário, retorna a direção, o preço e o lucro e a perda flutuantes da posição atual.

Inicializar conta

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
}
  • Funções: Esta função é usada para inicializar e registrar o patrimônio da conta como base para cálculos subsequentes de lucros e perdas.

Cancelar pedido pendente

function CancelPendingOrders() {
    orders = exchange.GetOrders();  // 获取订单
    for (let order of orders) {
        if (order.Status == ORDER_STATE_PENDING) {  // 只取消未完成的订单
            exchange.CancelOrder(order.Id);  // 取消挂单
        }
    }
}
  • Funções: Antes de fazer um pedido, cancele todos os pedidos não concluídos para evitar conflitos de pedidos ou pedidos duplicados.

2.2 Lógica de transação principal

A função principal usa um loop infinito para executar continuamente as seguintes etapas:

  1. Aquisição de dados e cálculo de mercado
    Cada ciclo começa porexchange.GetRecords Obtenha os dados de mercado do Par_A e do Par_B, respectivamente.

    • Fórmula de cálculo: [ ratio = \frac{Close{A} - Open{A}}{Open{A}} - \frac{Close{B} - Open{B}}{Open{B}} ] Ao comparar a ascensão e a queda dos dois, podemos determinar se há uma diferença anormal de preço. Quando a diferença de preço excede o diffLevel predefinido, a condição de abertura é acionada.
  2. Determine as condições de abertura e faça um pedido
    Quando não há posição atual (position_B.amount == 0) e a negociação é permitida (afterTrade==1):

    • Se a proporção for maior que o nível de diferença, acredita-se que o mercado está prestes a subir, então uma ordem de compra é emitida para o Par_B (comprar posição longa).
    • Se a proporção for menor que -diffLevel, considera-se que o mercado está prestes a cair e uma ordem de venda é emitida (uma posição curta é aberta). Antes de fazer um pedido, a função de cancelamento do pedido será chamada para garantir que o status atual do pedido seja limpo.
  3. Lógica de stop-profit e stop-loss
    Uma vez que uma posição é estabelecida, a estratégia definirá ordens de take-profit e stop-loss correspondentes de acordo com a direção da posição:

    • Posição longa (comprar): Defina o preço de take profit como o preço da posição multiplicado por (1 + stopProfitLevel) e o preço de stop loss como o preço da posição multiplicado por (1 - stopLossLevel).
    • Posição curta (venda): O preço de take profit é definido como o preço da posição multiplicado por (1 - stopProfitLevel), e o preço de stop loss é definido como o preço da posição multiplicado por (1 + stopLossLevel). O sistema monitorará o preço de mercado em tempo real. Uma vez que as condições de take-profit ou stop-loss forem acionadas, a ordem pendente original será cancelada e uma ordem será colocada para fechar a posição.
  4. Estatísticas de lucro e registros de log após o fechamento de uma posição
    Após o fechamento de cada posição, o sistema obterá as alterações no patrimônio da conta e contará o número de lucros, o número de perdas e o valor acumulado de lucro/perda.
    Ao mesmo tempo, tabelas e gráficos são usados ​​para exibir informações de posição atual, estatísticas de transações e atrasos de ciclo em tempo real, o que é conveniente para análise subsequente do efeito da estratégia.


3. Otimização de estratégias e métodos de expansão

Embora essa estratégia explore o sutil atraso entre dois contratos altamente correlacionados, ainda há muitas áreas que podem ser melhoradas:

3.1 Otimização de parâmetros e ajuste dinâmico

  • Ajuste de Limiar: Parâmetros como diffLevel, stopProfitLevel e stopLossLevel podem precisar ser ajustados em diferentes ambientes de mercado. Esses parâmetros podem ser otimizados automaticamente por meio de testes retrospectivos de dados históricos ou pelo ajuste dinâmico de modelos em tempo real (por exemplo, algoritmos de aprendizado de máquina).
  • Gestão de posições:A estratégia atual usa um Trade_Number fixo para abrir uma posição. No futuro, podemos considerar introduzir uma gestão dinâmica de posição ou um mecanismo de abertura de posições em lotes e obtenção gradual de lucros para reduzir o risco de uma única transação.

3.2 Filtragem de sinais de negociação

  • Sinal multifatorial: O cálculo da proporção com base apenas em aumentos e diminuições de preços pode ser afetado pelo ruído. Você pode considerar introduzir volume de negociação, profundidade do livro de ordens e indicadores técnicos (como RSI, MACD, etc.) para filtrar ainda mais sinais falsos.
  • Compensação por atraso: Considerando que MELANIA tem um atraso de 1-2 segundos, desenvolver um mecanismo de sincronização de tempo e previsão de sinal mais preciso ajudará a melhorar a precisão do tempo de entrada.

3.3 Robustez do sistema e controle de risco

  • Tratamento de erros: Adicione tratamento de exceções e registro para garantir resposta oportuna ao encontrar atrasos de rede ou anomalias na interface de troca, para evitar perdas inesperadas causadas por falhas do sistema.
  • Estratégia de Controle de Risco: Combine a gestão de capital e o controle máximo de redução para definir um limite de perda diária ou de transação única para evitar perdas consecutivas em ambientes de mercado extremos.

3.4 Otimização de código e arquitetura

  • Processamento Assíncrono: Atualmente, o loop de estratégia é executado a cada 100 milissegundos. Por meio de processamento assíncrono e otimização multithread, o atraso e o risco de bloqueio de execução podem ser reduzidos.
  • Backtesting e simulação de estratégia:Introduzir um sistema completo de backtesting e um ambiente de negociação simulado em tempo real para verificar o desempenho das estratégias sob diferentes condições de mercado e ajudar as estratégias a serem executadas de forma mais estável em negociações reais.

IV. Conclusão

Este artigo apresenta em detalhes os princípios básicos e o código de implementação de uma estratégia de arbitragem de correlação de contratos de curto prazo. Desde explorar a diferença nos aumentos e diminuições de preços até capturar oportunidades de entrada, até definir stop-profit e stop-loss para gerenciamento de posição, essa estratégia aproveita ao máximo a alta correlação entre ativos no mercado de criptomoedas. Ao mesmo tempo, também apresentamos uma série de sugestões de otimização, incluindo ajuste dinâmico de parâmetros, filtragem de sinal, robustez do sistema e otimização de código, a fim de melhorar ainda mais a estabilidade e a lucratividade da estratégia em aplicações em tempo real.

Embora a estratégia seja exclusivamente inspirada e simples de implementar, qualquer operação de arbitragem deve ser tratada com cautela no mercado de criptomoedas de alta frequência e volatilidade. Espero que este artigo possa fornecer referência e inspiração valiosas para amigos interessados ​​em estratégias de negociação quantitativa e arbitragem.


Nota: O ambiente de teste de estratégia é uma simulação de negociação OKX, e os detalhes específicos podem ser modificados para diferentes bolsas

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