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

Практическое исследование стратегий арбитража с мгновенной поставкой: подводные камни на пути от идеала к реальности.

Создано: 2025-11-28 14:22:49, Обновлено: 2025-12-16 11:31:06
comments   0
hits   345

Практическое исследование стратегий арбитража с мгновенной поставкой: подводные камни на пути от идеала к реальности.

Предисловие

Недавно друг попросил меня разработать арбитражную стратегию, которой, как оказалось, не было на платформе Inventor. Я думал, это несложно, но потребовался примерно месяц, чтобы едва-едва наладить базовую логику. Оглядываясь назад, я понимаю, что на пути от первоначальной идеи до её реализации я столкнулся с гораздо большим количеством трудностей, чем предполагал.

В данной статье описаны практические проблемы, возникшие в ходе разработки этой арбитражной стратегии, и найденные решения.Предназначено исключительно для учебных и справочных целей; не имеет практической инвестиционной ценности.

Основная логика стратегии

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

Первоначальная идея была проста:

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

Звучит здорово, правда? Но на практике вы обнаружите, что существует бесчисленное множество деталей, связанных только с этапом «открытия позиции».

Первая ловушка: стабильность ценовых спредов.

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

Особенно по мере приближения даты поставки поведение спреда может меняться. Поэтому был добавлен тест ADF для определения того, является ли ряд спреда стационарным.

function adfTest(series, maxLag=null){
  const n = series.length;
  if(n<10) throw new Error('series too short');
  if(maxLag===null) maxLag = Math.floor(12*Math.pow(n/100, 1/4));
  
  // ... ADF检验的核心计算逻辑
  
  const res = ols(rows, Ys);
  const tstat = tStat(res.beta, res.cov, 1);
  return { tStat: tstat, pValue: pval, usedLag: p };
}

Первоначально мы добавили ряд тестов, включая тест отношения дисперсий, тест периода полураспада и тест Колмогорова-Смирнова, но обнаружили, что слишком большое количество тестов значительно сокращает число возможностей для открытия позиций. В итоге мы упростили тест, оставив только тест ADF, и установили пороговое значение p-значения равным 0,1.

Что еще более важно, был добавлен еще один.Постоянный счетчик отказов

if (!adfPass) {
  stationarityFailCount[deliverySymbol] = (stationarityFailCount[deliverySymbol] || 0) + 1;
} else {
  stationarityFailCount[deliverySymbol] = 0;
}

let consecutiveFails = stationarityFailCount[deliverySymbol];
let canTrade = consecutiveFails < CONFIG.consecutiveFailThreshold;

Торговля запрещена, если тест проваливается три раза подряд. Это может предотвратить открытие позиций вслепую, когда рынок находится в ненормальном состоянии.

Вторая ловушка: путаница, связанная со статистикой прибылей и убытков в режиме реального времени.

Рассчитать прибыль и убытки по фьючерсному счету просто: достаточно посмотреть на изменения курса USDT. Но спотовые счета отличаются; они содержат как USDT, так и криптовалюту. Как это рассчитать?

Вот деталь, которую легко упустить из виду:Стоимость хранения товаров на спотовом рынке будет меняться в зависимости от торговых операций.Например, если вы покупаете одну монету за 100 USDT, продаете ее за 90 USDT, а затем выкупаете обратно за 85 USDT, ваша себестоимость уже не будет равна первоначальным 100 USDT. Простое использование цены, зафиксированной на момент открытия позиции, для расчета стоимости монеты не будет отражать реальную ситуацию с прибылью и убытками.

Правильный подход заключается в следующем:Извлеките фактическую среднюю цену сделки из объекта заказа.

let openSpotPrice = (openSpotOrder && openSpotOrder.AvgPrice) ? 
  openSpotOrder.AvgPrice : record.openSpotPrice;
let closeSpotPrice = closeSpotOrder.AvgPrice || currentPair.spotPrice;

Затем, исходя из фактической цены сделки, рассчитывается норма доходности:

// 正套:买现货+卖期货
if (record.direction === 'positive') {
  spotReturnRate = (closeSpotPrice - openSpotPrice) / openSpotPrice;
  deliveryReturnRate = (openDeliveryPrice - closeDeliveryPrice) / openDeliveryPrice;
} else {
  // 反套:卖现货+买期货
  spotReturnRate = (openSpotPrice - closeSpotPrice) / openSpotPrice;
  deliveryReturnRate = (closeDeliveryPrice - openDeliveryPrice) / openDeliveryPrice;
}

let totalReturnRate = spotReturnRate + deliveryReturnRate;
let requiredUSD = openSpotPrice * record.spotAmount;
let actualTotalPnl = totalReturnRate * requiredUSD;

Обратите внимание на логику расчета прибыли и убытков:

  • Обычный наборВ то время на спотовом рынке были открыты длинные позиции, а на фьючерсном — короткие, поэтому прибыль/убыток на спотовом рынке составляли: (цена закрытия - цена открытия) / цена открытия, а прибыль/убыток на фьючерсном рынке — (цена открытия - цена закрытия) / цена открытия.
  • обеспечить регрессВ то время спотовая торговля подразумевала короткие продажи, а фьючерсная торговля — длинные продажи, но в противоположных направлениях.

Наконец, фактическая прибыль или убыток по каждой сделке суммируются, образуя общую прибыль или убыток:

accumulatedProfit += actualTotalPnl;
_G('accumulatedProfit', accumulatedProfit);

Третья серьезная ловушка: ловушка ликвидности.

Вот в чем настоящая головная боль. Почему после 10 лет работы платформа Inventor Quantitative Platform предлагает так мало арбитражных стратегий для фьючерсных контрактов? Ответ прост:Недостаточная ликвидность на рынке договоров поставки.

Вопрос 1: Одноэтапная транзакция

Арбитраж — это не сказка, где вы открываете позиции одновременно. Реальность такова:

  • Спотовый ордер исполнен, но фьючерсный ордер всё ещё находится в обработке.
  • Или же фьючерсный ордер был исполнен, но спотовый ордер был отменен.

Это классический пример «одностороннего риска». Одна сторона находится внутри, а другая — снаружи. В этот момент любое колебание цены перестает быть арбитражем и превращается в одностороннюю позицию.

Решение заключается в том, чтобы присоединиться.механизм отката

if (!deliveryOrder) {
  Log('❌ 期货卖单失败,回滚现货');
  exchanges[0].CreateOrder(pair.spotSymbol, 'sell', -1, spotAmount);
  addCooldown(pair.deliverySymbol, pair.coin, '期货卖单失败,已回滚现货');
  return false;
}

В случае неудачи по какой-либо из сторон сделки, немедленно разместите рыночный ордер для закрытия уже исполненной стороны, снизив тем самым риск, связанный с этой отдельной стороной.

Вопрос 2: Рыночные ордера также могут быть отклонены.

Ещё более абсурдно то, что иногда рыночные ордера также терпят неудачу. Это может быть связано с механизмами контроля валютных рисков или недостаточной глубиной рынка; короче говоря, ордер просто не будет исполнен.

И я это сделал.Двойной механизм: рыночный ордер + лимитный ордер

function createOrderWithFallback(exchange, symbol, direction, amount, limitPrice, orderType, maxRetry = 3) {
  let useMarketOrder = (limitPrice === -1);
  
  // 先尝试限价单
  if (!useMarketOrder) {
    orderId = exchange.CreateOrder(symbol, direction, limitPrice, amount);
    if (!orderId) {
      Log(`❌ 限价单提交失败,改用市价单`);
      useMarketOrder = true;
    }
  }
  
  // 限价单失败则用市价单
  if (useMarketOrder && !orderId) {
    orderId = exchange.CreateOrder(symbol, direction, -1, marketAmount);
  }
  
  // ... 检查订单状态,失败则重试
}

Система может повторить попытку максимум 3 раза, при этом каждая попытка использует рыночный ордер в качестве страховки.

Вопрос 3: Ловушка объема в спотовых заказах на покупку

Эта ловушка особенно хорошо скрыта и требует особой осторожности. Количество ордера на фьючерсном рынке составляет…Количество монетОднако количество, запрашиваемое при покупке на спотовом рынке, составляет…Сумма в USDT

Здесь специально добавлена ​​логика преобразования:

function getActualAmount(useMarket) {
  if (isSpotBuy && useMarket) {
    // 现货市价买单:需要用USDT金额
    let currentPrice = getDepthMidPrice(exchange, symbol);
    let usdtAmount = amount * currentPrice;
    Log(`  💡 现货买单转换: ${amount.toFixed(6)} 币 → ${usdtAmount.toFixed(4)} USDT`);
    return usdtAmount;
  }
  return amount;
}

Количество криптовалюты, используемой в лимитном ордере, автоматически конвертируется в USDT для рыночного ордера.

Четвертая ловушка: иллюзия арбитражных возможностей.

Ещё одна неприятная проблема заключается в том, что обнаруживается арбитражный сигнал, и вы готовы открыть позицию, но возможность уже исчезла к моменту размещения ордера.

Цены колеблются в режиме реального времени, и рыночные условия могли измениться между обнаружением сигнала и фактическим исполнением позиции. Спреды цен могли сузиться, а арбитражные возможности могли исчезнуть. Если вы все же по глупости откроете позицию на этом этапе, вы просто выбросите комиссионные на ветер.

Поэтому это было добавлено.Вторичный механизм подтверждения

// 开仓前重新获取实时价格并验证套利机会
Log('🔄 重新获取实时Depth盘口价格并验证套利机会...');

let realtimeSpotPrice = getDepthMidPrice(exchanges[0], pair.spotSymbol, true);
let realtimeDeliveryPrice = getDepthMidPrice(exchanges[1], pair.deliverySymbol, true);

let realtimeSpread = realtimeDeliveryPrice - realtimeSpotPrice;
let realtimeSpreadRate = realtimeSpread / realtimeSpotPrice;

// 重新计算实时Z-Score
let realtimeZScore = (realtimeSpreadRate - mu) / (sigma || 1e-6);

// 验证:实时Z-Score是否仍然满足开仓条件
if (absRealtimeZ < CONFIG.zScoreEntry) {
  Log('❌ 套利机会已消失!');
  Log('  取消开仓,避免亏损');
  return false;
}

Перед фактическим размещением ордера повторно получите цену в реальном времени, пересчитайте Z-показатель и исполняйте ордер только в том случае, если вы уверены, что возможность для сделки все еще существует.

Пятая ловушка: проблемы, связанные с устаревшими фьючерсными позициями.

Иногда, при перезапуске стратегии или закрытии предыдущей позиции, на фьючерсном счете могут оставаться остаточные позиции. Если их не закрыть, новые позиции будут перекрывать старые, что приведет к неконтролируемому увеличению размера позиции.

Поэтому это было добавлено.Принудительная ликвидация перед открытием позиции.Логика:

// 检查期货现有仓位并平仓
let existingPosition = getPositionBySymbol(pair.deliverySymbol);

if (existingPosition && Math.abs(existingPosition.Amount) > 0) {
  Log('⚠️ 检测到该合约的现有仓位,执行平仓操作...');
  
  let closeDirection = existingPosition.Type === PD_LONG ? 'closebuy' : 'closesell';
  let closeAmount = Math.abs(existingPosition.Amount);
  
  let closeOrder = createOrderWithFallback(
    exchanges[1],
    pair.deliverySymbol,
    closeDirection,
    closeAmount,
    -1,
    '期货'
  );
  
  if (!closeOrder) {
    Log('❌ 平仓现有持仓失败,终止开仓');
    addCooldown(pair.deliverySymbol, pair.coin, '平仓现有持仓失败');
    return false;
  }
}

Перед открытием позиции проверьте её. Если остались какие-либо позиции, закройте их, чтобы убедиться в чистоте своего счёта.

Шестая главная ловушка: ловушка запаздывания в тикеровских данных.

Это весь процесс разработки стратегии.Наиболее скрытное и наиболее смертоносноеОдна из проблем.

После запуска стратегии было замечено странное явление:

  • Обнаружен арбитражный сигнал, и пора открывать позицию.
  • Оптимальная цена лимитного ордера рассчитывается путем сложения и вычитания разницы в цене из цены билета.
  • В результате заказ оставался в статусе “в ожидании выполнения”.
  • После длительного ожидания без завершения какой-либо транзакции заказ был отменен.

Попробуйте использовать рыночные ордера? Результат оказался ещё хуже:

  • Рыночный ордер был успешно исполнен.
  • Однако цена сделки оказалась совершенно иной, чем ожидаемый арбитражный спред.
  • Запланированная арбитражная сделка после ее завершения оказалась убыточной.

Что именно произошло? После многократного сравнения данных о текущих торгах на бирже проблема наконец-то была обнаружена:

Тикер против глубины: принципиальное различие между данными.

Тикерные данные отражают самую последнюю фактическую цену сделки.Звучит неплохо, но на рынках с низкой ликвидностью, таких как рынки контрактов на поставку, возникают проблемы:

时间轴:
10:00:00 - 有人以50000成交了1张合约 → Ticker价格更新为50000
10:00:05 - 盘口挂单:买49800 / 卖50200(但没有成交)
10:00:10 - 盘口挂单:买49850 / 卖50150(但没有成交)
...
10:05:00 - Ticker价格仍然是50000(因为5分钟内没有新的成交)

Вы заметили проблему?Цена тикера в 50 000 уже стала историей 5 минут назад.Однако текущая фактическая рыночная цена (цена в портфеле заказов) могла составить 4985050150.

Если вы используете тикер 50 000 для расчета арбитражных возможностей и размещения лимитных ордеров, то:

  1. Ошибка в сужденииАрбитражные сигналы рассчитываются на основе устаревших цен.
  2. Заказ не выполненЦена по лимитному ордеру слишком сильно отличается от фактической рыночной цены, что делает завершение сделки невозможным.
  3. Рыночные потериДля совершения сделки был использован рыночный ордер, но фактическая цена оказалась совершенно иной, чем ожидалось.

Почему котировки контрактов на поставку настолько ненадежны?

Ликвидность контрактов на поставку значительно хуже, чем у спотовых контрактов:

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

Для контрактов с низкой ликвидностью отклонение между ценой тикера и фактической ценой в книге заявок может достигать следующих значений:

  • Нормальный диапазон: 0,1% - 0,5%
  • Период колебаний: 1% - 3% или даже больше.

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

Решение: Заменить Ticker данными о глубине диска.

Поскольку Ticker — ненадежный сервис, давайте воспользуемся…Данные о глубине (глубине книги заказов)

function getDepthMidPrice(exchange, symbol, logDetail = false) {
    let depth = exchange.GetDepth(symbol);
    if (!depth || !depth.Bids || depth.Bids.length === 0 || 
        !depth.Asks || depth.Asks.length === 0) {
        Log(`❌ 获取${symbol}盘口失败`);
        return null;
    }
    
    let bestBid = depth.Bids[0].Price;  // 最优买价
    let bestAsk = depth.Asks[0].Price;  // 最优卖价
    let midPrice = (bestBid + bestAsk) / 2;  // 中间价
    
    if (logDetail) {
        let spread = bestAsk - bestBid;
        let spreadRate = spread / midPrice * 100;
        Log(`📊 ${symbol} 盘口: Bid=${bestBid.toFixed(2)}, Ask=${bestAsk.toFixed(2)}, Mid=${midPrice.toFixed(2)}, Spread=${spread.toFixed(2)} (${spreadRate.toFixed(3)}%)`);
    }
    
    return midPrice;
}

Преимущества данных о глубине:

  • В режиме реального времениОтражает текущее фактическое состояние портфеля заказов без каких-либо задержек.
  • точностьВаш заказ будет сопоставлен с данными в книгах заявок, и это реальная рыночная цена.
  • РаботоспособностьЛимитные ордера, рассчитанные на основе цен в стакане ордеров, имеют более высокую вероятность исполнения.

Стратегия двойной системы ценообразования

В конечном итоге был принятГибридное решение, сочетающее в себе функции Ticker и Depth.

1. Используйте Ticker для поддержания последовательности исторических данных.

// 用Ticker更新历史价差序列(保持连续性)
let spotTicker = exchanges[0].GetTicker(pair.spotSymbol);
let deliveryTicker = exchanges[1].GetTicker(pair.deliverySymbol);

pair.spotPrice = spotTicker.Last;
pair.deliveryPrice = deliveryTicker.Last;
pair.spread = pair.deliveryPrice - pair.spotPrice;

// 历史序列用于ADF检验、Z-Score计算
priceHistory[pair.deliverySymbol].push({
    time: Date.now(),
    spreadRate: pair.spread / pair.spotPrice,
    spread: pair.spread,
    spotPrice: pair.spotPrice,
    deliveryPrice: pair.deliveryPrice
});

Почему Ticker до сих пор используется для исторических данных? Потому что это необходимо.непрерывность данныхЕсли исторические данные также представлены с использованием показателя глубины, колебания цен в портфеле заявок вызовут разрывы в исторической последовательности, что повлияет на точность статистического анализа.

2. Используйте Depth для принятия решений в режиме реального времени и проверки открытия позиций.

// 开仓前用Depth重新验证套利机会
let realtimeSpotPrice = getDepthMidPrice(exchanges[0], pair.spotSymbol, true);
let realtimeDeliveryPrice = getDepthMidPrice(exchanges[1], pair.deliverySymbol, true);

// 基于Depth价格重新计算Z-Score
let realtimeSpread = realtimeDeliveryPrice - realtimeSpotPrice;
let realtimeSpreadRate = realtimeSpread / realtimeSpotPrice;
let realtimeZScore = (realtimeSpreadRate - mu) / (sigma || 1e-6);

// 二次验证:套利机会是否仍然存在
if (Math.abs(realtimeZScore) < CONFIG.zScoreEntry) {
    Log('❌ 套利机会已消失(基于Depth实时价格)');
    return false;
}

3. Рассчитайте цену лимитного ордера, используя глубину.

// 基于Depth价格和平均价差计算限价单价格
let spreadDeviation = realtimeSpread - avgSpread;
let adjustmentRatio = Math.min(
    Math.abs(spreadDeviation) * CONFIG.limitOrderSpreadRatio,
    spreadStd * 0.5
);

if (direction === 'positive') {
    spotLimitPrice = realtimeSpotPrice + adjustmentRatio;
    deliveryLimitPrice = realtimeDeliveryPrice - adjustmentRatio;
} else {
    spotLimitPrice = realtimeSpotPrice - adjustmentRatio;
    deliveryLimitPrice = realtimeDeliveryPrice + adjustmentRatio;
}

Цена лимитного ордера, рассчитанная таким образом, основана на фактическом стакане заявок, что значительно повышает вероятность исполнения.

4. Рассчитайте прибыль и убытки в режиме реального времени, используя глубину.

function calculateUnrealizedPnL(record, currentPair) {
    // 优先用Depth价格计算实时盈亏
    let currentSpotPrice = getDepthMidPrice(exchanges[0], currentPair.spotSymbol);
    let currentDeliveryPrice = getDepthMidPrice(exchanges[1], currentPair.deliverySymbol);
    
    // Depth获取失败才回退到Ticker
    if (!currentSpotPrice || !currentDeliveryPrice) {
        currentSpotPrice = currentPair.spotPrice;
        currentDeliveryPrice = currentPair.deliveryPrice;
    }
    
    // 计算盈亏...
}

Сравнение реальных боевых последствий

Проблемы с использованием Ticker:

检测到套利信号(基于Ticker)
→ 计算限价单价格
→ 下单等待
→ 长时间不成交(价格已经不对了)
→ 改用市价单
→ 成交价格和预期差很多
→ 套利失败或微利

Улучшения после использования параметра «Глубина»:

检测到套利信号(基于Ticker历史)
→ 用Depth重新验证(机会仍在)
→ 基于Depth计算限价单价格
→ 下单,价格贴近盘口
→ 较快成交
→ 成交价格符合预期
→ 套利成功

Оптимизация ценообразования лимитных ордеров

Если мы собираемся использовать лимитные ордера, как установить цену? Если мы установим её слишком агрессивно, сделка не состоится; если мы установим её слишком консервативно, мы не получим выгодную цену.

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

let spreadDeviation = realtimeSpread - avgSpread;
let adjustmentRatio = Math.min(
  Math.abs(spreadDeviation) * CONFIG.limitOrderSpreadRatio,
  spreadStd * 0.5
);

// 限制调整幅度在合理区间
let minAdjustment = realtimeSpotPrice * 0.0005;
let maxAdjustment = realtimeSpotPrice * 0.005;
adjustmentRatio = Math.max(minAdjustment, Math.min(maxAdjustment, adjustmentRatio));

Если это полный комплект (с большой разницей в цене):

  • Цена покупки на спотовом рынке = Средняя глубина + диапазон корректировки
  • Цена продажи фьючерсов = Средняя глубина - Диапазон корректировки

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

Механизм охлаждения

Любая неудачная попытка открыть позицию указывает на проблему на рынке, которая может быть вызвана недостаточной ликвидностью или чрезмерной волатильностью. В таких случаях не следует сразу же пытаться снова; вместо этого следует сохранять спокойствие.

Таким образом, к каждой неудачной торговой паре добавлялся штраф.10-минутная задержка

function addCooldown(deliverySymbol, coin, reason) {
  pairCooldowns[deliverySymbol] = Date.now() + CONFIG.cooldownDuration;
  Log(`⏸️ ${deliverySymbol} 进入10分钟冷却期`);
  Log(`   原因: ${reason}`);
  _G('pairCooldowns', pairCooldowns);
}

В течение переходного периода позиции по данной торговой паре открываться не будут во избежание повторных неудач и неэффективных комиссий за транзакции.

Текущие недостатки и области для улучшения

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

1. Проблема задержки В настоящее время цены получаются методом опроса, что приводит к значительной задержке. Переход на WebSocket для обновления цен в реальном времени значительно повысит скорость отклика.

2. Оптимизация контроля рисков Существующий метод установки стоп-лосса относительно прост и понятен, и вы можете рассмотреть следующие варианты:

  • Динамический стоп-лосс (корректируется в зависимости от волатильности)
  • Стоп-лосс, основанный на времени (принудительная ликвидация из-за чрезмерного времени удержания).
  • Максимальный контроль отмены

3. Управление проскальзыванием Стратегию ценообразования для лимитных ордеров можно сделать более интеллектуальной, например, динамически корректируя ее в зависимости от таких факторов, как глубина книги ордеров и объем последних транзакций.

4. Дальнейшие области применения данных о глубине Она может анализировать дисбаланс в книге заявок, прогнозировать ценовые тенденции и повышать вероятность успеха арбитража.

Подвести итог

Арбитражные стратегии звучат заманчиво, но на практике очевидно, что между идеалом и реальностью таится множество подводных камней.

  • Недостатки проверки стационарности
  • Подводные камни в статистике прибылей и убытков.
  • Недостаток ликвидности — ловушка.
  • Недостатки односторонних транзакций
  • Подводные камни несостоятельности рыночного порядка
  • Подводные камни при определении суммы покупки на спотовом рынке
  • Ловушка исчезающих арбитражных возможностей
  • Яма остаточных позиций
  • Наиболее существенным недостатком является задержка в работе механизма отображения тикеров.

В частности, проблема запаздывания тикеров представляет собой проблему на всем этапе разработки стратегии.Наиболее легко упустить из виду, но при этом иметь наибольшее влияние.Подводные камни. Для рынков контрактов на поставку с низкой ликвидностью:

Основной принцип: используйте тикеры для поддержания исторической преемственности, а глубину — для использования возможностей в реальном времени.

  • Ticker подходит для анализа исторических данных (тест ADF, расчет Z-показателя).
  • Глубина подходит для принятия решений в режиме реального времени и исполнения сделок (проверка открытия позиций, определение цены лимитных ордеров, расчет прибыли и убытков).

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

Если вы используете аналогичную стратегию, не стесняйтесь обсудить это со мной. Рынок сложен, и именно эта сложность делает количественную торговлю такой сложной задачей.

Исходный код стратегии: https://www.fmz.com/strategy/519280