Блог им. RomellaAkumov

Как я написал скрипт для 24-часового прогноза рынка: корреляции, волатильность и вероятностная модель

Вступление: идея, цель, гипотеза

Финансовые рынки редко движутся изолированно. Криптовалюты реагируют на фондовые индексы, золото реагирует на макроэкономику, а внутри крипторынка движение биткоина задаёт направление для альткоинов.

Гипотеза проекта:

Если агрегировать данные по разным классам активов (крипто, акции, золото), измерить их волатильность, тренд и взаимную корреляцию, можно получить осмысленную вероятностную оценку того, каким будет рынок в ближайшие 24 часа: рост, падение или консолидация.

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

Общая архитектура проекта

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

Данные → Индикаторы → Агрегация → Корреляции → Вероятностный прогноз

Код выложен на github.

Источники данных

Используются разные рынки:

  • криптовалюты (Binance, через ccxt),
  • фондовые индексы (S&P 500, NASDAQ, Dow Jones),
  • золото (через yfinance).

Все данные приводятся к единому таймфрейму (1час), единой временной зоне UTC, ну и само собой равномерно распределяю исходя из времени свечи, чтобы не было сдвигов данных на разных активов.

Это критично: без этого любые корреляции и агрегации будут искажены.

Конфигурация и управляемость

Все ключевые параметры вынесены в один конфиг:

<code>CONFIG = {
    "timeframe": "1h",
    "lookback_days": 30,
    "vol_low": 0.01,
    "vol_high": 0.05,
    "trend_thr": 0.02,
    "corr_weight": 0.30,
}</code>

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

Получение данных

Получаем данные по криптовалютам

<code>def _fetch_crypto(self, symbol: str, days: int) -> pd.DataFrame:
    since = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
    raw = self.exchange.fetch_ohlcv(symbol, "1h", since=since)

    df = pd.DataFrame(raw, columns=[
        'ts', 'open', 'high', 'low', 'close', 'volume'
    ])
    df['ts'] = pd.to_datetime(df['ts'], unit='ms', utc=True)
    df.set_index('ts', inplace=True)

    df = df.resample('1H').ffill()
    return df</code>

Что здесь происходит:

  • Загружаем OHLCV-данные с Binance через ccxt.
  • Переводим временные метки в UTC.
  • Приводим данные к равномерной часовой сетке.

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

На выходе:DataFrame с чистыми часовыми свечами.

Получаем данные фондового рынка и золота (yFinance)

<code>def _fetch_yf(self, symbol: str, days: int) -> pd.DataFrame:
    end = datetime.now(timezone.utc)
    start = end - timedelta(days=days)

    df = yf.download(symbol, start=start, end=end, interval="1h")
    df = df[['Open', 'High', 'Low', 'Close', 'Volume']]
    df.columns = ['open', 'high', 'low', 'close', 'volume']

    df.index = pd.to_datetime(df.index, utc=True)
    df = df.resample('1H').ffill()
    return df</code>

Что происходит:

  • Загружаем индексы и золото.
  • Приводим названия колонок к единому формату.
  • Ресемплируем данные.

Почему не Binance: Индексы и золото удобнее и стабильнее получать через yfinance.

Расчёт индикаторов для одного актива

<code>def _asset_indicators(self, df: pd.DataFrame) -> dict:
    close = df['close']

    # Momentum (24h ROC)
    roc = (close.iloc[-1] - close.iloc[-25]) / close.iloc[-25]

    # ATR %
    atr = self._atr(df)
    atr_pct = atr.iloc[-1] / close.iloc[-1]

    # EMA trend
    ema12 = close.ewm(span=12).mean()
    ema26 = close.ewm(span=26).mean()
    trend = (ema12.iloc[-1] - ema26.iloc[-1]) / ema26.iloc[-1]

    # Volume ratio
    vol_ratio = df['volume'].iloc[-1] / df['volume'].rolling(24).mean().iloc[-1]

    return {
        "roc_24h": roc,
        "atr_pct": atr_pct,
        "trend_strength": trend,
        "volume_ratio": vol_ratio
    }
</code>

1. Momentum (ROC 24 часа)

roc = (price_now — price_24h_ago) / price_24h_ago

Интерпретация:

> 0 — рынок растёт

< 0 — рынок падает

Используется как базовый индикатор направления.

2. Волатильность (ATR в процентах)

atr_pct = ATR / price

Почему в процентах: Абсолютный ATR не сопоставим между BTC и, например, ADA. Процентная форма решает эту проблему.

3. Тренд через EMA

trend = (EMA12 — EMA26) / EMA26

Простая и устойчивая оценка:

  • положительное значение — восходящий тренд;
  • отрицательное — нисходящий.

Также мы можем использовать модели через прочие скользящие средние. Это может быть использование tema, линий macd и прочих.

4. Объём

volume_ratio = current_volume / avg_24h_volume

Объём позволяет выявить всплески интереса, подтвердить тренд и выявить фразу распределение накопления.

Агрегация индикаторов по рынку

<code>def aggregate(self) -> dict:
    rows = []

    for df in self.data.values():
        ind = self._asset_indicators(df)
        rows.append(ind)

    agg = pd.DataFrame(rows).mean().to_dict()
    return agg</code>

Что делаем:

  • рассчитываем индикаторы для каждого актива;
  • усредняем значения.

Почему усреднение работает: Мы оцениваем не конкретный актив, а состояние рынка в целом.

Корреляционный анализ

<code>def correlations(self) -> dict:
    btc = self.data["BTC/USDT"]['close'].tail(24)

    result = {}
    for symbol, df in self.data.items():
        if symbol == "BTC/USDT":
            continue

        series = df['close'].tail(24)
        corr, _ = pearsonr(btc, series)
        result[f"{symbol}_vs_BTC"] = corr

    result["avg_corr"] = np.mean(list(result.values()))
    return result</code>

Здесь нам необходимо получить данные о корреляциях.

Что здесь происходит:

  • берём BTC как якорный актив;
  • считаем корреляции с остальными рынками;
  • вычисляем среднюю корреляцию.

Зачем это нужно:

  • высокая корреляция → рынок движется синхронно;
  • низкая → повышенная неопределённость и флет.

Вероятностная модель прогноза

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

  • рост (upward);
  • падение (downward);
  • консолидация (consolidation).

Такой подход лучше отражает реальность рынка: в каждый момент времени существует несколько возможных сценариев, а не один “правильный”.

Базовая инициализация вероятностей

up = down = cons = 1 / 3

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

Это принципиально важно — модель не имеет смещения и не “верит” в рост или падение заранее.

Учет импульса (Momentum / ROC)

<code>roc = ind.get('roc_24h', 0.0)

if roc > CONFIG["trend_thr"]:
    up += 0.20
    down -= 0.10
    cons -= 0.10
elif roc < -CONFIG["trend_thr"]:
    down += 0.20
    up -= 0.10
    cons -= 0.10</code>

Экономический смысл:

  • сильное движение за последние 24 часа редко полностью исчезает на следующий день;
  • импульс увеличивает вероятность продолжения движения, но не гарантирует его.

Что делает модель:

  • усиливает сценарий по направлению импульса;
  • снижает альтернативные сценарии, но не обнуляет их.

Учет волатильности (ATR %)

<code>vol = ind.get('atr_pct', 0.0)

if vol < CONFIG["vol_low"]:
    cons += 0.20
    up -= 0.10
    down -= 0.10
elif vol > CONFIG["vol_high"]:
    if ind.get('trend_strength', 0.0) > 0:
        up += 0.15
    else:
        down += 0.15
    cons -= 0.15</code>

Интерпретация:

  • низкая волатильность → рынок не готов к сильному движению;
  • высокая волатильность → рынок уже “раскачан” и движение вероятнее.

При этом направление усиливается только при наличии тренда, что снижает шум.

Учет объёма

<code>vratio = ind.get('volume_ratio', 1.0)

if vratio > 1.5:
    if ind.get('trend_strength', 0.0) > 0:
        up += 0.10
    else:
        down += 0.10
    cons -= 0.10</code>

Почему объём важен:

  • объём — это подтверждение участия капитала;
  • движение без объёма статистически менее устойчиво.

Модель использует объём не как триггер, а как усилитель уже существующего направления.

Учет межрыночных корреляций

<code>avg_c = corr.get('avg_corr', 0.0)
c_adj = CONFIG["corr_weight"] * abs(avg_c)

if abs(avg_c) < 0.5:
    cons += c_adj
    up -= c_adj / 2
    down -= c_adj / 2
else:
    gold_roc = ind.get('gold_roc', 0.0)
    if gold_roc > 0 and avg_c > 0:
        up += c_adj / 2
    elif gold_roc < 0 and avg_c > 0:
        down += c_adj / 2</code>

Логика:

  • низкая корреляция между рынками → высокая неопределённость;
  • высокая корреляция → рынок следует глобальному макро-направлению.

Золото используется как макро-фильтр:

  • рост золота при высокой корреляции часто указывает на risk-off;
  • падение — на risk-on.

Нормализация вероятностей

<code>total = up + down + cons

up /= total
down /= total
cons /= total


</code>

После всех корректировок вероятности:

  • могут выходить за диапазон;
  • могут не суммироваться в 1.

Нормализация решает эту проблему и возвращает корректное распределение.

Математика модели: приоритеты и характеристики движения рынка

Приоритетность факторов

Факторы в модели имеют иерархию, а не равные веса:

  1. Импульс (Momentum) — определяет базовое направление.
  2. Волатильность — определяет возможность движения.
  3. Тренд — подтверждает устойчивость.
  4. Объём — подтверждает участие капитала.
  5. Корреляции — корректируют сценарий на уровне всего рынка.

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

Характеристики движения рынка

Модель различает три принципиально разных состояния рынка:

1. Трендовое движение

  • положительный ROC;
  • высокая волатильность;
  • подтверждённый EMA-тренд;
  • объём выше среднего.

2. Импульсное движение

  • высокий ROC;
  • скачок волатильности;
  • объём растёт, но тренд ещё нестабилен.

3. Консолидация

  • низкий ROC;
  • низкая ATR;
  • слабые корреляции;
  • отсутствие объёма.

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

Почему вероятности, а не сигналы

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

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

Выход в консоль

<code>Market forecast: {
  "timestamp_utc": "2025-12-22T09:30:23.229039+00:00",
  "probabilities": {
    "upward": 0.1333,
    "downward": 0.4333,
    "consolidation": 0.4333
  },
  "indicators": {
    "roc_24h": -0.02796597685998984,
    "atr_pct": 0.006690744224154546,
    "trend_strength": -0.005271170297204962,
    "volume_ratio": 0.394397730504669,
    "crypto_cnt": 10,
    "stock_cnt": 0,
    "gold_roc": 0.0
  },
  "correlations": {
    "ETH/USDT_vs_BTC": 0.9975288228710706,
    "SOL/USDT_vs_BTC": 0.9811768939562674,
    "ADA/USDT_vs_BTC": 0.9907696697577224,
    "DOT/USDT_vs_BTC": 0.823343645765213,
    "LINK/USDT_vs_BTC": 0.9543745729318979,
    "LTC/USDT_vs_BTC": 0.9508485363364672,
    "TRX/USDT_vs_BTC": 0.9222384210578161,
    "AVAX/USDT_vs_BTC": 0.9603413942455601,
    "DOGE/USDT_vs_BTC": 0.9765121574347357,
    "avg_corr": 0.9507926793729722
  }
}</code>

Тут скрипт нам выводит основные коэффициенты корреляций, индикаторы и самое главное сверху — рыночные вероятности. В этом примере вероятность роста 13%, падения 43% и консолидации 43%. Исходя из этих данных — сегодняшний день неплохой для открытия шортовых позиций, что сочетается и с моим ict анализом.

Вывод: возможности модели и практическое применение

В рамках проекта была реализована вероятностная модель оценки рыночного состояния, которая опирается не на один индикатор или источник данных, а на совокупность факторов: импульс, волатильность, тренд, объём и межрыночные корреляции.

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

Что даёт такая модель на практике

  1. Фильтрация торговых стратегий Модель может использоваться как верхнеуровневый фильтр: отключать трендовые стратегии в фазе консолидации, снижать риск при неопределённости
  2. Адаптивное управление риском Вероятностный выход позволяет управлять размером позиции: регулировать плечо и маржу, а также изменять тейки и стопы под состояние рынка.
  3. Контекст для алгоритмической торговли Модель хорошо ложится в архитектуру торговых ботов как внешний аналитический сервис.
  4. Кросс-рыночный анализ За счёт использования индексов и золота модель позволяет учитывать макроэкономические сдвиги, корреляции рынков и включать «режим risk on/risk off».Это особенно важно для крипторынка, который всё чаще реагирует на внешние макрофакторы.
274 | ★2

Читайте на SMART-LAB:
Фото
EUR/USD: пара прорвалась выше ключевого сопротивления и закрепляет успех
Евро продолжает восстанавливаться и вышел на очередные локальные максимумы, после чего начал корректироваться в поисках новой поддержки. Фактором...
Фото
RENI рекомендует: книга «Как устроена экономика» от Ха-Джун Чанга
Ха-Джун Чанг — известный экономист, бывший профессор Кембриджского университета и один из наиболее влиятельных критиков мейнстримной...
Фото
📘 Настольная книга эмитента: Московская биржа презентовала IR-гид
Руководство «Как говорить с инвесторами» создано в помощь IR-специалистам публичных компаний и тем, кто только планирует выход на биржу. Вы...

теги блога Roman crypto_maniac

....все тэги



UPDONW
Новый дизайн