Вступление: идея, цель, гипотеза
Финансовые рынки редко движутся изолированно. Криптовалюты реагируют на фондовые индексы, золото реагирует на макроэкономику, а внутри крипторынка движение биткоина задаёт направление для альткоинов.
Гипотеза проекта:
Если агрегировать данные по разным классам активов (крипто, акции, золото), измерить их волатильность, тренд и взаимную корреляцию, можно получить осмысленную вероятностную оценку того, каким будет рынок в ближайшие 24 часа: рост, падение или консолидация.
Цель скрипта — не предсказать точную цену, а оценить состояние рынка в целом, получить вероятностный прогноз и использовать его как основу для торговых стратегий и автоматизированной торговли.
Общая архитектура проекта
На логическом уровне скрипт состоит из пяти ключевых блоков:
Данные → Индикаторы → Агрегация → Корреляции → Вероятностный прогноз
Код выложен на github.
Источники данных
Используются разные рынки:
криптовалюты (Binance, через ccxt),
фондовые индексы (S&P 500, NASDAQ, Dow Jones),
золото (через yfinance).
Все данные приводятся к единому таймфрейму (1час), единой временной зоне UTC, ну и само собой равномерно распределяю исходя из времени свечи, чтобы не было сдвигов данных на разных активов.
Это критично: без этого любые корреляции и агрегации будут искажены.
Конфигурация и управляемость
Все ключевые параметры вынесены в один CONFIG:
CONFIG = { "timeframe": "1h", "lookback_days": 30, "vol_low": 0.01, "vol_high": 0.05, "trend_thr": 0.02, "corr_weight": 0.30, }
Это позволить нам гибко использовать прогнозную модель в будущем, а также тестировать гипотезы и подгонять параметры для определенных торговых стратегий.
Получение данных
Получаем данные по криптовалютам
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
Что здесь происходит:
Загружаем OHLCV-данные с Binance через
ccxt.Переводим временные метки в UTC.
Приводим данные к равномерной часовой сетке.
Зачем это нужно:
Корреляции, волатильность и агрегаты работают корректно только при одинаковом таймфрейме.
На выходе:DataFrame с чистыми часовыми свечами.
Получаем данные фондового рынка и золота (yFinance)
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
Что происходит:
Загружаем индексы и золото.
Приводим названия колонок к единому формату.
Ресемплируем данные.
Почему не Binance:
Индексы и золото удобнее и стабильнее получать через yfinance.
Расчёт индикаторов для одного актива
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 }
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
Объём позволяет выявить всплески интереса, подтвердить тренд и выявить фразу распределение накопления.
Агрегация индикаторов по рынку
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
Что делаем:
рассчитываем индикаторы для каждого актива;
усредняем значения.
Почему усреднение работает:
Мы оцениваем не конкретный актив, а состояние рынка в целом.
Корреляционный анализ
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
Здесь нам необходимо получить данные о корреляциях.
Что здесь происходит:
берём BTC как якорный актив;
считаем корреляции с остальными рынками;
вычисляем среднюю корреляцию.
Зачем это нужно:
высокая корреляция → рынок движется синхронно;
низкая → повышенная неопределённость и флет.
Вероятностная модель прогноза
Ключевая особенность проекта — отказ от бинарных сигналов вида buy / sell.
Вместо этого используется вероятностная модель, оценивающая три сценария поведения рынка на ближайшие 24 часа:
рост (
upward);падение (
downward);консолидация (
consolidation).
Такой подход лучше отражает реальность рынка: в каждый момент времени существует несколько возможных сценариев, а не один “правильный”.
Базовая инициализация вероятностей
up = down = cons = 1 / 3
Модель стартует из нейтрального состояния, в котором рынок с равной вероятности может падать, расти или флетиться. Ни один сценарий не имеет преимущества, но почти сразу после запуска расчитываются уже реальные вероятности, основываясь на представленной ранее модели.
Это принципиально важно — модель не имеет смещения и не “верит” в рост или падение заранее.
Учет импульса (Momentum / ROC)
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
Экономический смысл:
сильное движение за последние 24 часа редко полностью исчезает на следующий день;
импульс увеличивает вероятность продолжения движения, но не гарантирует его.
Что делает модель:
усиливает сценарий по направлению импульса;
снижает альтернативные сценарии, но не обнуляет их.
Учет волатильности (ATR %)
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
Интерпретация:
низкая волатильность → рынок не готов к сильному движению;
высокая волатильность → рынок уже “раскачан” и движение вероятнее.
При этом направление усиливается только при наличии тренда, что снижает шум.
Учет объёма
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
Почему объём важен:
объём — это подтверждение участия капитала;
движение без объёма статистически менее устойчиво.
Модель использует объём не как триггер, а как усилитель уже существующего направления.
Учет межрыночных корреляций
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
Логика:
низкая корреляция между рынками → высокая неопределённость;
высокая корреляция → рынок следует глобальному макро-направлению.
Золото используется как макро-фильтр:
рост золота при высокой корреляции часто указывает на risk-off;
падение — на risk-on.
Нормализация вероятностей
total = up + down + cons up /= total down /= total cons /= total
После всех корректировок вероятности:
могут выходить за диапазон;
могут не суммироваться в 1.
Нормализация решает эту проблему и возвращает корректное распределение.
Математика модели: приоритеты и характеристики движения рынка
Приоритетность факторов
Факторы в модели имеют иерархию, а не равные веса:
Импульс (Momentum) — определяет базовое направление.
Волатильность — определяет возможность движения.
Тренд — подтверждает устойчивость.
Объём — подтверждает участие капитала.
Корреляции — корректируют сценарий на уровне всего рынка.
Это отражает реальную структуру рынка cначала появляется движение, затем волатильность и объём, и только потом рынок синхронизируется глобально.
Характеристики движения рынка
Модель различает три принципиально разных состояния рынка:
1. Трендовое движение
положительный ROC;
высокая волатильность;
подтверждённый EMA-тренд;
объём выше среднего.
2. Импульсное движение
высокий ROC;
скачок волатильности;
объём растёт, но тренд ещё нестабилен.
3. Консолидация
низкий ROC;
низкая ATR;
слабые корреляции;
отсутствие объёма.
Каждому состоянию соответствует разное распределение вероятностей, а не одинаковый сигнал.
Почему вероятности, а не сигналы
Использование вероятностей позволяет грамотно фильтровать входы, выбирать размер позиции, адаптировать стратегию под режимы рынка и избегать торговли в неопределенности. Поэтому такой софт полезен даже для ручной торговли.
Модель может указать нам на состояние рынка в текущей момент - что при наличии определенного нарратива может помочь нам принять грамотное решение.
Выход в консоль
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 } }
Тут скрипт нам выводит основные коэффициенты корреляций, индикаторы и самое главное сверху - рыночные вероятности. В этом примере вероятность роста 13%, падения 43% и консолидации 43%. Исходя из этих данных - сегодняшний день неплохой для открытия шортовых позиций, что сочетается и с моим ict анализом.
Вывод: возможности модели и практическое применение
В рамках проекта была реализована вероятностная модель оценки рыночного состояния, которая опирается не на один индикатор или источник данных, а на совокупность факторов: импульс, волатильность, тренд, объём и межрыночные корреляции.
Ключевая особенность подхода заключается в том, что модель не пытается предсказать цену и не генерирует жёсткие торговые сигналы. Вместо этого она формирует контекст рынка, описывая его через распределение вероятностей между тремя базовыми сценариями: рост, падение и консолидация.
Что даёт такая модель на практике
Фильтрация торговых стратегий
Модель может использоваться как верхнеуровневый фильтр: отключать трендовые стратегии в фазе консолидации, снижать риск при неопределённостиАдаптивное управление риском
Вероятностный выход позволяет управлять размером позиции: регулировать плечо и маржу, а также изменять тейки и стопы под состояние рынка.Контекст для алгоритмической торговли
Модель хорошо ложится в архитектуру торговых ботов как внешний аналитический сервис.Кросс-рыночный анализ
За счёт использования индексов и золота модель позволяет учитывать макроэкономические сдвиги, корреляции рынков и включать "режим risk on/risk off".Это особенно важно для крипторынка, который всё чаще реагирует на внешние макрофакторы.
