
La inspiración para esta estrategia proviene de la publicación de oportunidad del autor Zhihu “Dream Dealer” - “Modelo de arbitraje de correlación de bajo riesgo de TRUMP y MELANIA”. El artículo explora la correlación de precios entre dos contratos lanzados en BN (TRUMP y MELANIA) y utiliza el sutil retraso temporal entre ambos para intentar capturar las fluctuaciones del mercado a corto plazo y lograr un arbitraje de bajo riesgo. A continuación, explicaremos los principios de esta estrategia, la lógica de implementación del código y exploraremos posibles direcciones de optimización.
Es necesario estar con antelaciónAvisoEl problema es que esta estrategia es equivalente a un trabajo de trading manual. Tiene una cierta oportunidad de obtener ganancias solo después de encontrar dos pares de trading adecuados, y la vida útil de la ganancia del par de trading puede ser corta. Cuando se descubre que no hay oportunidad de obtener ganancias, es necesario detener la estrategia a tiempo para evitar una reducción de las ganancias o incluso una pérdida.
Los contratos TRUMP y MELANIA son emitidos por el mismo equipo emisor y tienen los mismos fondos de control, por lo que sus tendencias de precios están altamente sincronizadas la mayor parte del tiempo. Sin embargo, debido a factores como el diseño del contrato o la ejecución del mercado, el precio de MELANIA tiende a estar entre 1 y 2 segundos por detrás del de TRUMP. Este pequeño retraso proporciona a los arbitrajistas la oportunidad de capturar diferencias de precios y realizar operaciones de copia de alta frecuencia. En pocas palabras, cuando TRUMP fluctúa rápidamente, MELANIA tiende a seguir su ejemplo muy pronto. Al aprovechar esta demora, las transacciones se pueden completar con un menor riesgo.


Fenómenos de correlación similares no son infrecuentes en el mercado de criptomonedas:
Esta correlación proporciona a los operadores de alta frecuencia y a los arbitrajistas señales comerciales estables y oportunidades operativas de menor riesgo, pero también requiere que las estrategias comerciales sean muy sensibles a los cambios sutiles del mercado y puedan responder en tiempo real.
El código consta principalmente de varias partes, cada módulo corresponde a los pasos claves en la estrategia de arbitraje.
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); // 取消挂单
}
}
}
La función principal utiliza un bucle infinito para ejecutar continuamente los siguientes pasos:
Adquisición de datos y cálculo de mercado
Cada ciclo comienza porexchange.GetRecords Obtenga los datos de mercado de Pair_A y Pair_B respectivamente.
Determinar las condiciones de apertura y realizar un pedido
Cuando no hay una posición actual (position_B.amount == 0) y se permite la negociación (afterTrade==1):
Lógica de stop-profit y stop-loss
Una vez establecida una posición, la estrategia establecerá las órdenes de toma de ganancias y de stop loss correspondientes según la dirección de la posición:
Estadísticas de ganancias y registros de operaciones después de cerrar una posición
Después de cerrar cada posición, el sistema obtendrá los cambios en el capital de la cuenta y contará el número de ganancias, el número de pérdidas y el monto acumulado de ganancias/pérdidas.
Al mismo tiempo, se utilizan tablas y gráficos para mostrar información de la posición actual, estadísticas de transacciones y retrasos del ciclo en tiempo real, lo que resulta conveniente para el posterior análisis del efecto de la estrategia.
Si bien esta estrategia aprovecha el retraso sutil entre dos contratos altamente correlacionados, todavía hay muchas áreas que se pueden mejorar:
Este artículo presenta en detalle los principios básicos y el código de implementación de una estrategia de arbitraje de correlación de contratos rezagados de corto plazo. Desde explotar la diferencia entre los aumentos y disminuciones de precios hasta capturar oportunidades de entrada y establecer stop-profit y stop-loss para la gestión de posiciones, esta estrategia aprovecha al máximo la alta correlación entre los activos en el mercado de criptomonedas. Al mismo tiempo, también hemos presentado una serie de sugerencias de optimización, incluido el ajuste dinámico de parámetros, el filtrado de señales, la robustez del sistema y la optimización del código, para mejorar aún más la estabilidad y la rentabilidad de la estrategia en aplicaciones en tiempo real.
Si bien la estrategia está inspirada de manera única y es fácil de implementar, cualquier operación de arbitraje debe tratarse con precaución en el mercado de criptomonedas volátil y de alta frecuencia. Espero que este artículo pueda proporcionar una valiosa referencia e inspiración para los amigos que estén interesados en el trading cuantitativo y las estrategias de arbitraje.
Nota: El entorno de prueba de estrategia es el trading de simulación OKX, y los detalles específicos se pueden modificar para diferentes intercambios.
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);
}
}