
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.
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.


Fenômenos de correlação semelhantes não são incomuns no mercado de criptomoedas:
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.
O código consiste principalmente em várias partes, cada módulo corresponde às principais etapas da estratégia de arbitragem.
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); // 取消挂单
}
}
}
A função principal usa um loop infinito para executar continuamente as seguintes etapas:
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.
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):
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:
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.
Embora essa estratégia explore o sutil atraso entre dois contratos altamente correlacionados, ainda há muitas áreas que podem ser melhoradas:
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);
}
}