Блог им. RomellaAkumov
Финансовые рынки редко движутся изолированно. Криптовалюты реагируют на фондовые индексы, золото реагирует на макроэкономику, а внутри крипторынка движение биткоина задаёт направление для альткоинов.
Гипотеза проекта:
Если агрегировать данные по разным классам активов (крипто, акции, золото), измерить их волатильность, тренд и взаимную корреляцию, можно получить осмысленную вероятностную оценку того, каким будет рынок в ближайшие 24 часа: рост, падение или консолидация.
Цель скрипта — не предсказать точную цену, а оценить состояние рынка в целом, получить вероятностный прогноз и использовать его как основу для торговых стратегий и автоматизированной торговли.
На логическом уровне скрипт состоит из пяти ключевых блоков:
Данные → Индикаторы → Агрегация → Корреляции → Вероятностный прогноз
Код выложен на github.
Используются разные рынки:
Все данные приводятся к единому таймфрейму (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>Что здесь происходит:
Зачем это нужно: Корреляции, волатильность и агрегаты работают корректно только при одинаковом таймфрейме.
На выходе:DataFrame с чистыми часовыми свечами.
<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>roc = (price_now — price_24h_ago) / price_24h_ago
Интерпретация:
> 0 — рынок растёт
< 0 — рынок падает
Используется как базовый индикатор направления.
atr_pct = ATR / price
Почему в процентах: Абсолютный ATR не сопоставим между BTC и, например, ADA. Процентная форма решает эту проблему.
trend = (EMA12 — EMA26) / EMA26
Простая и устойчивая оценка:
Также мы можем использовать модели через прочие скользящие средние. Это может быть использование tema, линий macd и прочих.
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>Здесь нам необходимо получить данные о корреляциях.
Что здесь происходит:
Зачем это нужно:
Ключевая особенность проекта — отказ от бинарных сигналов вида buy / sell. Вместо этого используется вероятностная модель, оценивающая три сценария поведения рынка на ближайшие 24 часа:
upward);downward);consolidation).Такой подход лучше отражает реальность рынка: в каждый момент времени существует несколько возможных сценариев, а не один “правильный”.
up = down = cons = 1 / 3
Модель стартует из нейтрального состояния, в котором рынок с равной вероятности может падать, расти или флетиться. Ни один сценарий не имеет преимущества, но почти сразу после запуска расчитываются уже реальные вероятности, основываясь на представленной ранее модели.
Это принципиально важно — модель не имеет смещения и не “верит” в рост или падение заранее.
<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>Экономический смысл:
Что делает модель:
<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>Логика:
Золото используется как макро-фильтр:
<code>total = up + down + cons up /= total down /= total cons /= total </code>
После всех корректировок вероятности:
Нормализация решает эту проблему и возвращает корректное распределение.
Факторы в модели имеют иерархию, а не равные веса:
Это отражает реальную структуру рынка cначала появляется движение, затем волатильность и объём, и только потом рынок синхронизируется глобально.
Модель различает три принципиально разных состояния рынка:
Каждому состоянию соответствует разное распределение вероятностей, а не одинаковый сигнал.
Использование вероятностей позволяет грамотно фильтровать входы, выбирать размер позиции, адаптировать стратегию под режимы рынка и избегать торговли в неопределенности. Поэтому такой софт полезен даже для ручной торговли.
Модель может указать нам на состояние рынка в текущей момент — что при наличии определенного нарратива может помочь нам принять грамотное решение.
<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 анализом.
В рамках проекта была реализована вероятностная модель оценки рыночного состояния, которая опирается не на один индикатор или источник данных, а на совокупность факторов: импульс, волатильность, тренд, объём и межрыночные корреляции.
Ключевая особенность подхода заключается в том, что модель не пытается предсказать цену и не генерирует жёсткие торговые сигналы. Вместо этого она формирует контекст рынка, описывая его через распределение вероятностей между тремя базовыми сценариями: рост, падение и консолидация.