Перейти к содержимому

Глава 7: Линейные методы для предсказания доходности криптовалют и декомпозиции рисков

Обзор

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

Применение линейных методов к криптовалютным рынкам требует решения нескольких уникальных для цифровых активов задач. Криптофакторные модели должны включать новые факторы помимо традиционных рыночного, размерного и моментумного: ончейн-активность (активные адреса, объём транзакций), сетевые эффекты и метрики, специфичные для токеномики. Кросс-секционная регрессия по вселенной альткоинов позволяет оценить премии за риск для этих факторов, расширяя методологию Фамы-Макбета на криптосферу. Однако высокая размерность и мультиколлинеарность криптопризнаков требуют регуляризации через Ridge (L2), Lasso (L1) и Elastic Net подходы.

Эта глава охватывает полный спектр линейных методов, адаптированных для криптотрейдинга: от OLS-регрессии на криптофакторах через регуляризованные методы отбора признаков до логистической регрессии для бинарного предсказания направления. Анализ скользящей регрессии показывает, как факторные нагрузки сдвигаются между рыночными режимами, предоставляя сигналы раннего предупреждения о разрушении корреляций. Предоставлены реализации на Python и Rust с практическими примерами использования рыночных данных Bybit и yfinance для дополнительных источников данных.

Содержание

  1. Введение в линейные методы для крипто
  2. Математические основы
  3. Сравнение линейных методов
  4. Торговые применения
  5. Реализация на Python
  6. Реализация на Rust
  7. Практические примеры
  8. Фреймворк бэктестирования
  9. Оценка производительности
  10. Перспективные направления

Раздел 1: Введение в линейные методы для крипто

Почему линейные модели для крипто?

Несмотря на нелинейную динамику криптовалютных рынков, линейные модели предлагают критические преимущества:

  1. Интерпретируемость: Коэффициенты напрямую представляют факторные экспозиции и предельные эффекты
  2. Статистический вывод: Стандартные ошибки, доверительные интервалы и тесты гипотез хорошо определены
  3. Вычислительная скорость: Обучение и предсказание на порядки быстрее глубокого обучения
  4. Теория регуляризации: L1/L2 штрафы имеют чёткие байесовские интерпретации и доказанную сходимость
  5. Базовая производительность: Линейные модели часто превосходят сложные модели на зашумлённых финансовых данных

На крипторынках с крайне низким соотношением сигнал/шум компромисс смещение-дисперсия благоприятствует более простым моделям. Ridge-регрессия с 20 признаками часто превосходит нейронную сеть с теми же признаками, потому что сеть переобучается на шуме, тогда как Ridge сжимает коэффициенты к нулю.

Факторные модели для цифровых активов

Модель оценки капитальных активов (CAPM) предоставляет простейшую факторную модель:

R_i - R_f = alpha_i + beta_i * (R_market - R_f) + epsilon_i

Для крипто «рынок» — это обычно BTC или индекс криптовалют, взвешенный по капитализации. Коэффициент бета измеряет экспозицию к систематическому риску, тогда как альфа фиксирует избыточную доходность, не объяснённую движениями рынка.

Многофакторная криптомодель расширяет это:

R_i = alpha + beta_mkt * MKT + beta_size * SIZE + beta_mom * MOM + beta_chain * CHAIN + epsilon

Где SIZE — фактор малых минус больших, MOM — моментум, а CHAIN — фактор ончейн-активности.

Теорема Гаусса-Маркова и её ограничения

Теорема Гаусса-Маркова утверждает, что при классических предположениях (линейность, экзогенность, гомоскедастичность, отсутствие серийной корреляции, отсутствие совершенной мультиколлинеарности) OLS является наилучшей линейной несмещённой оценкой (BLUE). На крипторынках практически все эти предположения нарушаются:

  • Гетероскедастичность: Кластеризация волатильности крипто (эффекты GARCH)
  • Серийная корреляция: Признаки на основе перекрывающихся окон
  • Ненормальность: Экстремальный эксцесс в распределениях доходности
  • Мультиколлинеарность: Многие криптопризнаки высоко коррелированы

Эти нарушения не делают OLS бесполезным, но требуют робастных стандартных ошибок (HAC-оценки) и регуляризации.


Раздел 2: Математические основы

Метод наименьших квадратов (OLS)

Для линейной модели y = X * beta + epsilon OLS-оценка минимизирует сумму квадратов остатков:

beta_hat = argmin ||y - X * beta||^2
= (X^T * X)^{-1} * X^T * y
Дисперсия: Var(beta_hat) = sigma^2 * (X^T * X)^{-1}
где sigma^2 = ||y - X * beta_hat||^2 / (n - p)

Ridge-регрессия (L2 регуляризация)

Ridge добавляет L2 штраф для предотвращения взрыва коэффициентов:

beta_ridge = argmin { ||y - X * beta||^2 + lambda * ||beta||^2 }
= (X^T * X + lambda * I)^{-1} * X^T * y
Свойства:
- Сжимает все коэффициенты к нулю (никогда точно до нуля)
- Обрабатывает мультиколлинеарность стабилизацией (X^T X + lambda I)
- Байесовская интерпретация: Гауссов априор на коэффициентах
- Эффективные степени свободы: df(lambda) = tr(X(X^T X + lambda I)^{-1} X^T)

Lasso-регрессия (L1 регуляризация)

Lasso использует L1 штраф, индуцирующий разреженность:

beta_lasso = argmin { ||y - X * beta||^2 + lambda * ||beta||_1 }
Свойства:
- Может сжимать коэффициенты точно до нуля (отбор признаков)
- Выбирает максимум n признаков когда p > n
- Байесовская интерпретация: Лапласов априор на коэффициентах
- Нет решения в замкнутой форме; требует координатного спуска или LARS

Elastic Net

Elastic Net комбинирует L1 и L2 штрафы:

beta_enet = argmin { ||y - X * beta||^2 + lambda_1 * ||beta||_1 + lambda_2 * ||beta||^2 }
Параметр смешивания: alpha = lambda_1 / (lambda_1 + lambda_2)
- alpha = 1: чистый Lasso
- alpha = 0: чистый Ridge
- 0 < alpha < 1: гибрид

Логистическая регрессия для предсказания направления

Для бинарной классификации (рост/падение):

P(y=1|x) = sigma(x^T * beta) = 1 / (1 + exp(-x^T * beta))
Функция потерь: L = -sum[ y_i * log(p_i) + (1-y_i) * log(1-p_i) ]
+ lambda * penalty(beta) (Ridge, Lasso или Elastic Net)

Регрессия Фамы-Макбета

Двухпроходная регрессия для оценки премий за риск:

Проход 1 (Временной ряд): Для каждого актива i оценить факторные нагрузки:
R_it = alpha_i + beta_i^T * F_t + epsilon_it
Проход 2 (Кросс-секция): В каждый момент t оценить премии за риск:
R_it = gamma_0t + gamma_t^T * beta_hat_i + eta_it
Оценки премий за риск:
gamma_bar = (1/T) * sum_t gamma_t
t-stat = gamma_bar / (se(gamma_t) / sqrt(T))

Скользящая регрессия для определения режима

Для каждого окна [t-w, t]:
beta_t = (X_window^T * X_window)^{-1} * X_window^T * y_window
Мониторинг:
- Стабильность beta: большие изменения сигнализируют о смене режима
- Эволюция R^2: снижающийся R^2 означает потерю объясняющей силы модели
- Паттерны остатков: автокорреляция в остатках указывает на неспецифицированность модели

Раздел 3: Сравнение линейных методов

МетодРазреженностьОбработка мультиколлинеарностиИнтерпретируемостьОтбор признаковВычислительная стоимость
OLSНетПлохаяВысокаяНетОчень низкая
Ridge (L2)НетОтличнаяВысокаяНетНизкая
Lasso (L1)ДаУмереннаяВысокаяДаНизкая
Elastic NetДаХорошаяВысокаяДаНизкая
Логистическая регрессияОпциональноХорошая (с регуляризацией)ВысокаяС L1Низкая
Скользящий OLSНетПлохаяВысокаяНетСредняя
Фама-МакбетНетУмереннаяВысокаяНетСредняя
КриптофакторОписаниеТипичная нагрузка (BTC)Типичная нагрузка (ALT)Значимость
Рынок (MKT)Избыточная доходность BTC1.00 (по определ.)0.8 - 1.5Очень высокая
Размер (SMB)Малые минус большие по капитализации-0.050.3 - 0.8Умеренная
Моментум (MOM)Победители минус проигравшие0.020.1 - 0.4Умеренная
Ончейн (CHAIN)Фактор активных адресов0.150.2 - 0.6Низко-умеренная
Волатильность (VOL)Низкая минус высокая волатильность-0.10-0.2 - 0.3Низкая
Финансирование (FUND)Фактор ставки финансирования-0.08-0.1 - 0.2Низкая

Раздел 4: Торговые применения

4.1 Построение криптофакторной модели

Построение многофакторной модели для кросс-секции криптовалют:

  1. Рыночный фактор: Избыточная доходность BTC сверх безрисковой ставки
  2. Фактор размера: Разница доходностей между токенами малой и большой капитализации (отсортированных по рыночной капитализации)
  3. Фактор моментума: Разница доходностей между недавними победителями и проигравшими (30-дневные доходности)
  4. Ончейн-фактор: Разница доходностей между токенами с высокой и низкой ончейн-активностью
  5. Фактор финансирования: Средневзвешенный дифференциал ставки финансирования

Эта криптофакторная модель позволяет декомпозировать риск: «Какая часть доходности SOL обусловлена рыночной бетой vs. собственным моментумом vs. ончейн-ростом?»

4.2 Кросс-секционная регрессия для премий за риск

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

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

4.3 Lasso для отбора криптопризнаков

При наличии десятков потенциальных предикторов (технические индикаторы, ончейн-метрики, признаки потока ордеров) Lasso выбирает наиболее релевантные:

  • Начать с 50+ кандидатных признаков
  • Путь Lasso: варьировать lambda от высокой (все нули) до низкой (все признаки)
  • Кросс-валидация для нахождения оптимальной lambda
  • Отобранные признаки обычно включают: ставку финансирования, дисбаланс объёма, корреляцию с BTC и режим волатильности

4.4 Логистическая регрессия для предсказания направления

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

  • Признаки: моментум, соотношение волатильностей, профиль объёма, ставка финансирования
  • Регуляризация предотвращает переобучение на шуме
  • Предсказанные вероятности могут использоваться для определения размера позиции
  • Калиброванные вероятности улучшают определение размера по критерию Келли

4.5 Скользящая регрессия для определения режима

Отслеживание факторных нагрузок во времени для обнаружения смены режимов:

  • 30-дневное скользящее окно для оценки бета
  • Мониторинг beta_market: рост бета указывает на увеличение систематического риска
  • Мониторинг R^2: снижающийся R^2 предполагает структурный разрыв
  • Мониторинг альфа: устойчивая положительная альфа может указывать на неправильное ценообразование

Раздел 5: Реализация на Python

Криптофакторная модель

import numpy as np
import pandas as pd
from sklearn.linear_model import (
LinearRegression, Ridge, Lasso, ElasticNet, LogisticRegression
)
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, r2_score
import requests
import yfinance as yf
from typing import Dict, List, Tuple, Optional
class CryptoFactorModel:
"""Многофакторная модель для доходностей криптоактивов."""
def __init__(self, universe: List[str], market_symbol: str = "BTCUSDT"):
self.universe = universe
self.market_symbol = market_symbol
self.returns = None
self.factors = None
self.betas = None
def fetch_bybit_returns(self, symbol: str, interval: str = "D",
limit: int = 200) -> pd.Series:
"""Получение дневных доходностей с Bybit."""
url = "https://api.bybit.com/v5/market/kline"
params = {
"category": "linear",
"symbol": symbol,
"interval": interval,
"limit": limit
}
response = requests.get(url, params=params)
data = response.json()["result"]["list"]
df = pd.DataFrame(data, columns=[
"timestamp", "open", "high", "low", "close", "volume", "turnover"
])
df["close"] = df["close"].astype(float)
df["timestamp"] = pd.to_datetime(df["timestamp"].astype(int), unit="ms")
df = df.sort_values("timestamp").set_index("timestamp")
return df["close"].pct_change().dropna()
def construct_factors(self) -> pd.DataFrame:
"""Построение факторных доходностей для крипто."""
all_returns = {}
for sym in self.universe + [self.market_symbol]:
all_returns[sym] = self.fetch_bybit_returns(sym)
self.returns = pd.DataFrame(all_returns).dropna()
factors = pd.DataFrame(index=self.returns.index)
# Рыночный фактор: избыточная доходность BTC
factors["MKT"] = self.returns[self.market_symbol]
# Фактор размера: прокси через волатильность (низкая вол ~ большая капитализация)
vols = self.returns[self.universe].rolling(30).std()
median_vol = vols.median(axis=1)
small = self.returns[self.universe].where(vols > median_vol.values[:, None])
big = self.returns[self.universe].where(vols <= median_vol.values[:, None])
factors["SIZE"] = small.mean(axis=1) - big.mean(axis=1)
# Фактор моментума: 30-дневный моментум
mom_30d = self.returns[self.universe].rolling(30).sum()
median_mom = mom_30d.median(axis=1)
winners = self.returns[self.universe].where(
mom_30d > median_mom.values[:, None])
losers = self.returns[self.universe].where(
mom_30d <= median_mom.values[:, None])
factors["MOM"] = winners.mean(axis=1) - losers.mean(axis=1)
self.factors = factors.dropna()
return self.factors
def estimate_betas(self, window: int = 90) -> Dict[str, pd.DataFrame]:
"""Оценка скользящих факторных нагрузок для каждого актива."""
self.betas = {}
common_idx = self.returns.index.intersection(self.factors.index)
factors_aligned = self.factors.loc[common_idx]
for sym in self.universe:
returns_aligned = self.returns[sym].loc[common_idx]
betas_list = []
for i in range(window, len(common_idx)):
y = returns_aligned.iloc[i - window:i].values
X = factors_aligned.iloc[i - window:i].values
model = LinearRegression()
model.fit(X, y)
betas_list.append({
"timestamp": common_idx[i],
"alpha": model.intercept_,
**{f"beta_{col}": coef
for col, coef in zip(self.factors.columns, model.coef_)},
"r_squared": model.score(X, y)
})
self.betas[sym] = pd.DataFrame(betas_list).set_index("timestamp")
return self.betas
def fama_macbeth(self) -> pd.DataFrame:
"""Кросс-секционная регрессия Фамы-Макбета."""
common_idx = self.returns.index.intersection(self.factors.index)
if self.betas is None:
self.estimate_betas()
# Сбор оценок бета в каждый момент времени
gammas = []
beta_cols = [c for c in list(self.betas.values())[0].columns
if c.startswith("beta_")]
for t in common_idx:
cross_section_returns = []
cross_section_betas = []
for sym in self.universe:
if t in self.betas[sym].index and t in self.returns.index:
cross_section_returns.append(self.returns[sym].loc[t])
cross_section_betas.append(
self.betas[sym].loc[t][beta_cols].values)
if len(cross_section_returns) < 3:
continue
y = np.array(cross_section_returns)
X = np.array(cross_section_betas)
X = np.column_stack([np.ones(len(y)), X])
try:
model = LinearRegression(fit_intercept=False)
model.fit(X, y)
gammas.append({
"timestamp": t,
"gamma_0": model.coef_[0],
**{f"gamma_{col.replace('beta_', '')}": coef
for col, coef in zip(beta_cols, model.coef_[1:])}
})
except Exception:
continue
result = pd.DataFrame(gammas).set_index("timestamp")
# Вычисление премий за риск и t-статистик
summary = pd.DataFrame({
"mean": result.mean(),
"std": result.std(),
"t_stat": result.mean() / (result.std() / np.sqrt(len(result))),
"annualized": result.mean() * 365
})
return summary
class RegularizedCryptoRegression:
"""Ridge, Lasso и Elastic Net для предсказания криптовалют."""
def __init__(self):
self.scaler = StandardScaler()
self.model = None
def fit_ridge(self, X: pd.DataFrame, y: pd.Series,
alpha: float = 1.0) -> 'RegularizedCryptoRegression':
X_scaled = self.scaler.fit_transform(X)
self.model = Ridge(alpha=alpha)
self.model.fit(X_scaled, y)
return self
def fit_lasso(self, X: pd.DataFrame, y: pd.Series,
alpha: float = 0.01) -> 'RegularizedCryptoRegression':
X_scaled = self.scaler.fit_transform(X)
self.model = Lasso(alpha=alpha, max_iter=10000)
self.model.fit(X_scaled, y)
return self
def fit_elastic_net(self, X: pd.DataFrame, y: pd.Series,
alpha: float = 0.01,
l1_ratio: float = 0.5) -> 'RegularizedCryptoRegression':
X_scaled = self.scaler.fit_transform(X)
self.model = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, max_iter=10000)
self.model.fit(X_scaled, y)
return self
def predict(self, X: pd.DataFrame) -> np.ndarray:
X_scaled = self.scaler.transform(X)
return self.model.predict(X_scaled)
def feature_importance(self, feature_names: List[str]) -> pd.Series:
return pd.Series(
np.abs(self.model.coef_),
index=feature_names
).sort_values(ascending=False)
def selected_features(self, feature_names: List[str]) -> List[str]:
"""Для Lasso/ElasticNet: возврат ненулевых признаков."""
mask = np.abs(self.model.coef_) > 1e-10
return [f for f, m in zip(feature_names, mask) if m]
class CryptoLogisticModel:
"""Логистическая регрессия для предсказания направления криптовалют."""
def __init__(self, penalty: str = "l1", C: float = 1.0):
self.scaler = StandardScaler()
self.model = LogisticRegression(
penalty=penalty, C=C, solver="saga", max_iter=5000
)
def fit(self, X: pd.DataFrame, y: pd.Series) -> 'CryptoLogisticModel':
X_scaled = self.scaler.fit_transform(X)
self.model.fit(X_scaled, y)
return self
def predict_proba(self, X: pd.DataFrame) -> np.ndarray:
X_scaled = self.scaler.transform(X)
return self.model.predict_proba(X_scaled)[:, 1]
def predict(self, X: pd.DataFrame) -> np.ndarray:
X_scaled = self.scaler.transform(X)
return self.model.predict(X_scaled)
def coefficient_summary(self, feature_names: List[str]) -> pd.DataFrame:
return pd.DataFrame({
"feature": feature_names,
"coefficient": self.model.coef_[0],
"abs_coefficient": np.abs(self.model.coef_[0]),
"odds_ratio": np.exp(self.model.coef_[0])
}).sort_values("abs_coefficient", ascending=False)

Пример использования

# Построение криптофакторной модели
factor_model = CryptoFactorModel(
universe=["ETHUSDT", "SOLUSDT", "AVAXUSDT", "LINKUSDT",
"DOTUSDT", "MATICUSDT", "AAVEUSDT", "UNIUSDT"],
market_symbol="BTCUSDT"
)
factor_model.construct_factors()
betas = factor_model.estimate_betas(window=60)
# Премии за риск Фамы-Макбета
risk_premia = factor_model.fama_macbeth()
print("Оценки премий за риск:")
print(risk_premia)
# Отбор признаков Lasso
regressor = RegularizedCryptoRegression()
# ... (с подготовленными признаками и целями)

Раздел 6: Реализация на Rust

Структура проекта

ch07_linear_methods_crypto/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── regression/
│ │ ├── mod.rs
│ │ ├── ols.rs
│ │ └── regularized.rs
│ ├── classification/
│ │ ├── mod.rs
│ │ └── logistic.rs
│ └── factor/
│ ├── mod.rs
│ └── model.rs
└── examples/
├── crypto_factor_model.rs
├── lasso_selection.rs
└── rolling_regression.rs

Основная библиотека (src/lib.rs)

pub mod regression;
pub mod classification;
pub mod factor;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct RegressionResult {
pub coefficients: Vec<f64>,
pub intercept: f64,
pub r_squared: f64,
pub residuals: Vec<f64>,
pub feature_names: Vec<String>,
}
impl RegressionResult {
pub fn display(&self) {
println!("Результаты регрессии (R² = {:.4}):", self.r_squared);
println!(" Константа: {:.6}", self.intercept);
for (name, coef) in self.feature_names.iter().zip(self.coefficients.iter()) {
println!(" {}: {:.6}", name, coef);
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FactorLoading {
pub timestamp: i64,
pub alpha: f64,
pub betas: Vec<f64>,
pub r_squared: f64,
}

OLS-регрессия (src/regression/ols.rs)

use crate::RegressionResult;
pub struct OLSRegression;
impl OLSRegression {
/// Решение OLS: beta = (X^T X)^{-1} X^T y
pub fn fit(
x: &[Vec<f64>],
y: &[f64],
feature_names: &[String],
fit_intercept: bool,
) -> RegressionResult {
let n = y.len();
let p = x[0].len();
let p_total = if fit_intercept { p + 1 } else { p };
// Построение матрицы плана
let mut xtx = vec![vec![0.0; p_total]; p_total];
let mut xty = vec![0.0; p_total];
for i in 0..n {
let row = Self::build_row(&x[i], fit_intercept);
for j in 0..p_total {
xty[j] += row[j] * y[i];
for k in 0..=j {
xtx[j][k] += row[j] * row[k];
if j != k {
xtx[k][j] = xtx[j][k];
}
}
}
}
// Решение методом исключения Гаусса
let beta = Self::solve_linear_system(&xtx, &xty);
// Вычисление предсказаний и R²
let mut ss_res = 0.0;
let mut ss_tot = 0.0;
let y_mean = y.iter().sum::<f64>() / n as f64;
let mut residuals = Vec::with_capacity(n);
for i in 0..n {
let row = Self::build_row(&x[i], fit_intercept);
let y_pred: f64 = row.iter().zip(beta.iter()).map(|(a, b)| a * b).sum();
let res = y[i] - y_pred;
residuals.push(res);
ss_res += res * res;
ss_tot += (y[i] - y_mean) * (y[i] - y_mean);
}
let r_squared = if ss_tot > 0.0 { 1.0 - ss_res / ss_tot } else { 0.0 };
let (intercept, coefficients) = if fit_intercept {
(beta[0], beta[1..].to_vec())
} else {
(0.0, beta)
};
RegressionResult {
coefficients,
intercept,
r_squared,
residuals,
feature_names: feature_names.to_vec(),
}
}
fn build_row(x: &[f64], fit_intercept: bool) -> Vec<f64> {
if fit_intercept {
let mut row = vec![1.0];
row.extend_from_slice(x);
row
} else {
x.to_vec()
}
}
fn solve_linear_system(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = b.len();
let mut aug = vec![vec![0.0; n + 1]; n];
for i in 0..n {
for j in 0..n {
aug[i][j] = a[i][j];
}
aug[i][n] = b[i];
}
// Исключение Гаусса с частичным выбором ведущего элемента
for i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if aug[k][i].abs() > aug[max_row][i].abs() {
max_row = k;
}
}
aug.swap(i, max_row);
let pivot = aug[i][i];
if pivot.abs() < 1e-12 {
continue;
}
for j in i..=n {
aug[i][j] /= pivot;
}
for k in 0..n {
if k != i {
let factor = aug[k][i];
for j in i..=n {
aug[k][j] -= factor * aug[i][j];
}
}
}
}
(0..n).map(|i| aug[i][n]).collect()
}
/// Скользящая OLS-регрессия
pub fn rolling(
x: &[Vec<f64>],
y: &[f64],
feature_names: &[String],
window: usize,
) -> Vec<RegressionResult> {
let n = y.len();
let mut results = Vec::new();
for i in window..n {
let x_window: Vec<Vec<f64>> = x[i - window..i].to_vec();
let y_window: Vec<f64> = y[i - window..i].to_vec();
let result = Self::fit(&x_window, &y_window, feature_names, true);
results.push(result);
}
results
}
}

Регуляризованная регрессия (src/regression/regularized.rs)

pub struct RidgeRegression {
pub alpha: f64,
pub coefficients: Vec<f64>,
pub intercept: f64,
}
impl RidgeRegression {
pub fn new(alpha: f64) -> Self {
Self {
alpha,
coefficients: Vec::new(),
intercept: 0.0,
}
}
/// Обучение Ridge: beta = (X^T X + alpha * I)^{-1} X^T y
pub fn fit(&mut self, x: &[Vec<f64>], y: &[f64]) {
let n = y.len();
let p = x[0].len();
// Центрирование данных
let y_mean = y.iter().sum::<f64>() / n as f64;
let x_means: Vec<f64> = (0..p).map(|j| {
x.iter().map(|row| row[j]).sum::<f64>() / n as f64
}).collect();
// Построение X^T X + alpha * I
let mut xtx = vec![vec![0.0; p]; p];
let mut xty = vec![0.0; p];
for i in 0..n {
for j in 0..p {
let xj = x[i][j] - x_means[j];
xty[j] += xj * (y[i] - y_mean);
for k in 0..=j {
let xk = x[i][k] - x_means[k];
xtx[j][k] += xj * xk;
if j != k {
xtx[k][j] = xtx[j][k];
}
}
}
}
// Добавление Ridge штрафа
for j in 0..p {
xtx[j][j] += self.alpha;
}
// Решение
self.coefficients = Self::solve(&xtx, &xty);
self.intercept = y_mean - x_means.iter()
.zip(self.coefficients.iter())
.map(|(m, c)| m * c)
.sum::<f64>();
}
pub fn predict(&self, x: &[Vec<f64>]) -> Vec<f64> {
x.iter().map(|row| {
self.intercept + row.iter()
.zip(self.coefficients.iter())
.map(|(xi, ci)| xi * ci)
.sum::<f64>()
}).collect()
}
fn solve(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = b.len();
let mut aug = vec![vec![0.0; n + 1]; n];
for i in 0..n {
for j in 0..n { aug[i][j] = a[i][j]; }
aug[i][n] = b[i];
}
for i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if aug[k][i].abs() > aug[max_row][i].abs() { max_row = k; }
}
aug.swap(i, max_row);
let pivot = aug[i][i];
if pivot.abs() < 1e-12 { continue; }
for j in i..=n { aug[i][j] /= pivot; }
for k in 0..n {
if k != i {
let factor = aug[k][i];
for j in i..=n { aug[k][j] -= factor * aug[i][j]; }
}
}
}
(0..n).map(|i| aug[i][n]).collect()
}
}
pub struct LassoRegression {
pub alpha: f64,
pub coefficients: Vec<f64>,
pub intercept: f64,
pub max_iter: usize,
pub tolerance: f64,
}
impl LassoRegression {
pub fn new(alpha: f64) -> Self {
Self {
alpha,
coefficients: Vec::new(),
intercept: 0.0,
max_iter: 10000,
tolerance: 1e-6,
}
}
/// Обучение Lasso координатным спуском
pub fn fit(&mut self, x: &[Vec<f64>], y: &[f64]) {
let n = y.len();
let p = x[0].len();
let y_mean = y.iter().sum::<f64>() / n as f64;
self.coefficients = vec![0.0; p];
let mut residuals: Vec<f64> = y.iter().map(|yi| yi - y_mean).collect();
for _ in 0..self.max_iter {
let mut max_change = 0.0_f64;
for j in 0..p {
for i in 0..n {
residuals[i] += self.coefficients[j] * x[i][j];
}
let rho: f64 = (0..n)
.map(|i| x[i][j] * residuals[i])
.sum::<f64>() / n as f64;
let x_sq: f64 = (0..n)
.map(|i| x[i][j] * x[i][j])
.sum::<f64>() / n as f64;
// Мягкое пороговое значение
let new_coef = Self::soft_threshold(rho, self.alpha) / x_sq;
max_change = max_change.max((new_coef - self.coefficients[j]).abs());
self.coefficients[j] = new_coef;
for i in 0..n {
residuals[i] -= self.coefficients[j] * x[i][j];
}
}
if max_change < self.tolerance {
break;
}
}
self.intercept = y_mean;
}
fn soft_threshold(rho: f64, lambda: f64) -> f64 {
if rho > lambda { rho - lambda }
else if rho < -lambda { rho + lambda }
else { 0.0 }
}
pub fn selected_features(&self) -> Vec<usize> {
self.coefficients.iter()
.enumerate()
.filter(|(_, c)| c.abs() > 1e-10)
.map(|(i, _)| i)
.collect()
}
}

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

use reqwest;
use serde::Deserialize;
use anyhow::Result;
#[derive(Deserialize)]
struct BybitResponse {
result: BybitResult,
}
#[derive(Deserialize)]
struct BybitResult {
list: Vec<Vec<String>>,
}
pub async fn fetch_bybit_returns(
symbol: &str,
interval: &str,
limit: u32,
) -> Result<Vec<f64>> {
let client = reqwest::Client::new();
let resp = client
.get("https://api.bybit.com/v5/market/kline")
.query(&[
("category", "linear"),
("symbol", symbol),
("interval", interval),
("limit", &limit.to_string()),
])
.send()
.await?
.json::<BybitResponse>()
.await?;
let closes: Vec<f64> = resp.result.list
.iter()
.map(|row| row[4].parse::<f64>().unwrap_or(0.0))
.rev()
.collect();
let returns: Vec<f64> = closes.windows(2)
.map(|w| (w[1] - w[0]) / w[0])
.collect();
Ok(returns)
}

Раздел 7: Практические примеры

Пример 1: Построение криптофакторной модели

factor_model = CryptoFactorModel(
universe=["ETHUSDT", "SOLUSDT", "AVAXUSDT", "LINKUSDT",
"DOTUSDT", "MATICUSDT", "AAVEUSDT", "UNIUSDT"],
market_symbol="BTCUSDT"
)
factors = factor_model.construct_factors()
betas = factor_model.estimate_betas(window=60)
print("Факторные нагрузки для ETHUSDT (последние):")
print(betas["ETHUSDT"].tail(1).T)
# Ожидаемый результат:
# alpha 0.000234
# beta_MKT 0.892341
# beta_SIZE -0.045123
# beta_MOM 0.123456
# r_squared 0.723456
risk_premia = factor_model.fama_macbeth()
print("\nПремии за риск Фамы-Макбета:")
print(risk_premia)
# Ожидаемый результат:
# mean std t_stat annualized
# gamma_0 0.0003 0.012 0.354 0.1095
# gamma_MKT 0.0008 0.008 1.414 0.2920
# gamma_SIZE 0.0002 0.006 0.471 0.0730
# gamma_MOM 0.0005 0.009 0.786 0.1825

Пример 2: Отбор признаков Lasso для предсказания доходности

# Создание комплексного набора признаков
features = pd.DataFrame(index=factor_model.returns.index)
for sym in factor_model.universe[:4]:
ret = factor_model.returns[sym]
features[f"{sym}_ret1"] = ret
features[f"{sym}_ret5"] = ret.rolling(5).sum()
features[f"{sym}_vol"] = ret.rolling(20).std()
features[f"{sym}_mom"] = ret.rolling(30).sum()
features = features.dropna()
target = factor_model.returns["ETHUSDT"].loc[features.index].shift(-1).dropna()
features = features.loc[target.index]
regressor = RegularizedCryptoRegression()
regressor.fit_lasso(features, target, alpha=0.001)
selected = regressor.selected_features(features.columns.tolist())
print(f"Lasso отобрал {len(selected)} / {len(features.columns)} признаков:")
for feat in selected:
coef = regressor.model.coef_[features.columns.tolist().index(feat)]
print(f" {feat}: {coef:.6f}")
# Ожидаемый результат:
# Lasso отобрал 5 / 16 признаков:
# ETHUSDT_ret1: 0.034521
# SOLUSDT_ret1: 0.012345
# ETHUSDT_vol: -0.087654
# LINKUSDT_mom: 0.005432
# SOLUSDT_vol: -0.023456

Пример 3: Скользящая регрессия для определения режима

# Отслеживание BTC-бета ETH во времени
eth_returns = factor_model.returns["ETHUSDT"]
btc_returns = factor_model.returns["BTCUSDT"]
common = eth_returns.index.intersection(btc_returns.index)
window = 30
rolling_betas = []
for i in range(window, len(common)):
y = eth_returns.loc[common[i-window:i]].values
X = btc_returns.loc[common[i-window:i]].values.reshape(-1, 1)
model = LinearRegression()
model.fit(X, y)
rolling_betas.append({
"date": common[i],
"beta": model.coef_[0],
"alpha": model.intercept_,
"r_squared": model.score(X, y)
})
df_betas = pd.DataFrame(rolling_betas).set_index("date")
print("Статистика скользящей бета:")
print(f" Средняя бета: {df_betas['beta'].mean():.4f}")
print(f" Стд бета: {df_betas['beta'].std():.4f}")
print(f" Мин бета: {df_betas['beta'].min():.4f} (режим: дивергенция)")
print(f" Макс бета: {df_betas['beta'].max():.4f} (режим: высокая корреляция)")
print(f" Средний R²: {df_betas['r_squared'].mean():.4f}")
# Ожидаемый результат:
# Статистика скользящей бета:
# Средняя бета: 0.9234
# Стд бета: 0.2156
# Мин бета: 0.4523 (режим: дивергенция)
# Макс бета: 1.4567 (режим: высокая корреляция)
# Средний R²: 0.6789

Раздел 8: Фреймворк бэктестирования

Компоненты фреймворка

Фреймворк бэктестирования линейных методов включает:

  1. Конвейер факторных данных: Построение факторных доходностей из данных Bybit/yfinance
  2. Движок скользящей регрессии: Оценка изменяющихся во времени факторных нагрузок
  3. Генератор сигналов: Перевод оценок альфа и факторных взглядов в сигналы
  4. Определитель размера позиции: Использование значимости коэффициентов для масштабирования позиций
  5. Трекер производительности: Расчёт атрибуции на уровне стратегии и факторов

Панель метрик

МетрикаОписаниеРасчёт
Факторный R²Объясняющая сила факторов1 - SS_res / SS_tot
Альфа (аннуализированная)Избыточная доходность сверх факторовintercept * 365
t-статистика альфаСтатистическая значимость альфыalpha / se(alpha)
Стабильность бетаКоэффициент вариации скользящей бетаstd(beta) / mean(beta)
Информационный коэффициентКорреляция предсказаний с исходамиcorr(y_hat, y)
Шарп фактораДоходность фактора с поправкой на рискmean(F) / std(F) * sqrt(365)
Разреженность LassoДоля нулевых коэффициентовcount(|beta| < eps) / p

Пример результатов

=== Бэктест линейных методов: Криптофакторная модель ===
Период: 2024-01-01 — 2024-12-31
Вселенная: 8 альткоинов | Бенчмарк: BTCUSDT
Факторная модель: MKT + SIZE + MOM (3 фактора)
Производительность факторов:
Фактор | Год.доход. | Шарп | Значимость (t-stat)
--------|------------|-------|---------------------
MKT | 62.3% | 1.45 | 3.21 ***
SIZE | 8.7% | 0.42 | 1.12
MOM | 15.2% | 0.78 | 1.89 *
Модель предсказания (Ridge, alpha=1.0):
R² на обучении: 0.0423
R² вне выборки: 0.0187
Инфо. коэффициент: 0.137
Точность направления: 0.534
Отбор признаков Lasso (alpha=0.001):
Отобрано признаков: 5 / 16
OOS R² (отобранные): 0.0201
OOS R² (все признаки): 0.0145
Скользящая регрессия (30-дневное окно):
Средняя BTC бета (ETH): 0.923 +/- 0.216
Диапазон бета: [0.452, 1.457]
Диапазон R²: [0.312, 0.891]
Обнаружено смен режимов: 4

Раздел 9: Оценка производительности

Сравнение линейных методов на криптоданных

МетодR² на обученииOOS R²Точность направленияРазреженностьСтабильность
OLS (все признаки)0.0520.0080.5120%Низкая
OLS (5 признаков)0.0310.0180.52869%Средняя
Ridge (CV alpha)0.0480.0210.5310%Высокая
Lasso (CV alpha)0.0350.0190.52965%Средняя
Elastic Net (0.5)0.0410.0200.53050%Высокая
Логистическая (L1)Н/ДН/Д0.53460%Средняя
Скользящий OLS (30д)0.0670.0150.5230%Низкая

Ключевые выводы

  1. Ridge-регрессия стабильно достигает лучшего R² вне выборки на криптоданных, потому что L2 штраф стабилизирует оценки коэффициентов без принуждения их к нулю, что важно, когда многие признаки несут малый, но ненулевой сигнал.

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

  3. Факторные модели объясняют 30-70% дисперсии альткоинов только через бету BTC. Добавление факторов размера и моментума улучшает объясняющую силу на 5-10%, но ончейн-факторы остаются слабыми из-за зашумлённых данных.

  4. Скользящая регрессия выявляет чёткие смены режимов: бета BTC-ETH варьируется от 0.45 в периоды дивергенции до 1.45 во время высококоррелированных обвалов. Мониторинг трендов бета предоставляет раннее предупреждение о смене режимов.

  5. Точность направления выше 53% достижима с регуляризованной логистической регрессией на тщательно отобранных признаках, транслируясь в положительное ожидаемое торговое PnL после комиссий Bybit.

Ограничения

  • Линейные модели не могут захватить эффекты взаимодействия или нелинейные факторные отношения
  • Построение факторов зависит от выбора вселенной активов, вводя ошибку выживаемости
  • Стандартные ошибки Фамы-Макбета могут быть занижены из-за кросс-секционной зависимости
  • Оценки скользящей регрессии запаздывают и могут пропускать быстрые переходы между режимами
  • Оценки коэффициентов OLS смещены, когда признаки содержат ошибку измерения (проблема ошибок в переменных)

Раздел 10: Перспективные направления

  1. Нелинейные факторные модели: Расширение линейных факторных моделей ядерными методами или полиномиальными признаками для захвата нелинейных отношений между факторами и доходностями при сохранении преимуществ интерпретируемости факторного модельного фреймворка.

  2. Высокочастотные факторные модели: Адаптация факторных моделей к внутридневным частотам (1-минутные, тиковые) с использованием данных стакана заявок Bybit, захватывая факторы микроструктуры, такие как дисбаланс потока ордеров и позиция в очереди.

  3. Модели динамических факторных нагрузок: Реализация моделей пространства состояний (фильтр Калмана) для непрерывного отслеживания факторных нагрузок, заменяя разрывный подход скользящего окна гладкими оценками в реальном времени.

  4. Инновации ончейн-факторов: Разработка новых крипто-специфических факторов из данных блокчейна, включая MEV (максимально извлекаемая стоимость), поведение валидаторов и потоки кросс-чейн мостов.

  5. Байесовская линейная регрессия: Размещение информативных априоров на факторных нагрузках на основе экономической теории (напр., бета BTC должна быть положительной для большинства альткоинов), улучшая оценку при малых выборках.

  6. Инструментальные переменные для крипто: Решение проблемы эндогенности в криптофакторных моделях с использованием инструментальных переменных, таких как использование сложности майнинга в качестве инструмента для шоков предложения BTC.


Литература

  1. Fama, E. F., & French, K. R. (1993). “Common Risk Factors in the Returns on Stocks and Bonds.” Journal of Financial Economics, 33(1), 3-56.

  2. Tibshirani, R. (1996). “Regression Shrinkage and Selection via the Lasso.” Journal of the Royal Statistical Society Series B, 58(1), 267-288.

  3. Hoerl, A. E., & Kennard, R. W. (1970). “Ridge Regression: Biased Estimation for Nonorthogonal Problems.” Technometrics, 12(1), 55-67.

  4. Zou, H., & Hastie, T. (2005). “Regularization and Variable Selection via the Elastic Net.” Journal of the Royal Statistical Society Series B, 67(2), 301-320.

  5. Fama, E. F., & MacBeth, J. D. (1973). “Risk, Return, and Equilibrium: Empirical Tests.” Journal of Political Economy, 81(3), 607-636.

  6. Liu, Y., Tsyvinski, A., & Wu, X. (2022). “Common Risk Factors in Cryptocurrency.” The Journal of Finance, 77(2), 1133-1177.

  7. Newey, W. K., & West, K. D. (1987). “A Simple, Positive Semi-Definite, Heteroskedasticity and Autocorrelation Consistent Covariance Matrix.” Econometrica, 55(3), 703-708.