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


Подобные явления корреляции не редкость на рынке криптовалют:
Эта корреляция обеспечивает высокочастотных трейдеров и арбитражеров стабильными торговыми сигналами и возможностями для работы с меньшим риском, но также требует, чтобы торговые стратегии были высокочувствительны к едва заметным изменениям рынка и могли реагировать в режиме реального времени.
Код в основном состоит из нескольких частей, каждый модуль соответствует ключевым шагам арбитражной стратегии.
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); // 取消挂单
}
}
}
Основная функция использует бесконечный цикл для непрерывного выполнения следующих шагов:
Сбор данных и расчет рынка
Каждый цикл начинается сexchange.GetRecords Получите рыночные данные Pair_A и Pair_B соответственно.
Определите условия открытия и оформите заказ
Когда текущей позиции нет (position_B.amount == 0) и торговля разрешена (afterTrade==1):
Логика стоп-профита и стоп-лосса
После открытия позиции стратегия установит соответствующие ордера тейк-профит и стоп-лосс в соответствии с направлением позиции:
Статистика прибыли и записи журнала после закрытия позиции
После закрытия каждой позиции система получит данные об изменениях в капитале счета и подсчитает количество прибылей, количество убытков и совокупную сумму прибылей/убытков.
При этом таблицы и графики используются для отображения информации о текущих позициях, статистики транзакций и задержек циклов в режиме реального времени, что удобно для последующего анализа эффекта стратегии.
Хотя эта стратегия использует небольшую задержку между двумя тесно связанными контрактами, все еще есть много областей, которые можно улучшить:
В данной статье подробно излагаются основные принципы и код реализации стратегии арбитража на основе корреляции контрактов с кратковременным отставанием. От использования разницы в росте и падении цен до использования возможностей входа и установки стоп-профита и стоп-лосса для управления позициями — эта стратегия в полной мере использует преимущества высокой корреляции между активами на рынке криптовалют. В то же время мы также выдвинули ряд предложений по оптимизации, включая динамическую настройку параметров, фильтрацию сигналов, надежность системы и оптимизацию кода, с целью дальнейшего повышения стабильности и прибыльности стратегии в приложениях реального времени.
Несмотря на то, что стратегия уникальна и проста в реализации, к любым арбитражным операциям следует относиться с осторожностью на высокочастотном и нестабильном рынке криптовалют. Надеюсь, эта статья станет ценным источником вдохновения и источником информации для друзей, интересующихся количественной торговлей и арбитражными стратегиями.
Примечание: Средой тестирования стратегии является имитационная торговля 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);
}
}