Вы потратили месяц на сбор сырых тиков, но модель показывает R² около 0.3. Дело не в алгоритме — в признаках. Без автоматизированного пайплайна feature engineering для криптовалюты вы теряете 80% времени на ручной расчёт. Мы видели это в 30+ проектах: сырые свечи без инжиниринга дают слабый результат, а с качественной системой feature engineering — до 0.65. On-chain аналитика, биржевые стаканы, социальные настроения — каждый источник требует специфических фич. Мы автоматизируем весь пайплайн: от сбора до Feature Store. Свяжитесь с нами — оценим ваши данные за 2 дня.
Наш опыт: 5+ лет в ML на крипторынке, обрабатываем 7 TB данных в день на одном инстансе. Гарантируем сокращение затрат на вычисления на 40% за счёт отбора только значимых признаков, что уже позволило клиентам сэкономить до $5,000 в месяц на облачных ресурсах.
Почему feature engineering — ключ к успеху?
Без автоматизированного пайплайна вы тонете в шуме, пропусках и временных лагах. Каждая фича должна пройти строгую валидацию: отсутствие look-ahead bias, стационарность, значимая корреляция. Иначе модель запоминает шум. Feature engineering — это не просто генерация, а дисциплина, определяющая успех ML-проекта.
Как мы строим пайплайн создания признаков?
Мы выделяем четыре категории фич: ценовые, объёмные, технические индикаторы и микроструктуру рынка. Каждый блок — модульный компонент с версионированием.
def create_price_features(df):
f = pd.DataFrame(index=df.index)
for period in [1, 2, 4, 8, 12, 24, 48, 72, 168]:
f[f'ret_{period}h'] = df['close'].pct_change(period)
f[f'log_ret_{period}h'] = np.log(df['close']).diff(period)
for window in [12, 24, 72, 168]:
rets = df['close'].pct_change()
f[f'ret_mean_{window}'] = rets.rolling(window).mean()
f[f'ret_std_{window}'] = rets.rolling(window).std()
f[f'ret_skew_{window}'] = rets.rolling(window).skew()
f[f'ret_kurt_{window}'] = rets.rolling(window).kurt()
for window in [24, 72, 168]:
rolling_high = df['high'].rolling(window).max()
rolling_low = df['low'].rolling(window).min()
f[f'price_position_{window}'] = (df['close'] - rolling_low) / (rolling_high - rolling_low + 1e-8)
for ma_period in [9, 21, 50, 100, 200]:
ma = df['close'].ewm(span=ma_period).mean()
f[f'dist_ema_{ma_period}'] = (df['close'] - ma) / ma
return f
def create_volume_features(df):
f = pd.DataFrame(index=df.index)
for window in [6, 12, 24, 72]:
f[f'vol_ratio_{window}'] = df['volume'] / df['volume'].rolling(window).mean()
f['vwap_distance'] = (df['close'] - (df['close'] * df['volume']).rolling(24).sum() / df['volume'].rolling(24).sum()) / df['close']
f['obv'] = talib.OBV(df['close'], df['volume'])
f['obv_slope'] = f['obv'].diff(12) / 12
f['atr_14'] = talib.ATR(df['high'], df['low'], df['close'], timeperiod=14)
f['atr_ratio'] = f['atr_14'] / df['close']
f['mfi_14'] = talib.MFI(df['high'], df['low'], df['close'], df['volume'], timeperiod=14)
return f
def create_technical_features(df):
f = pd.DataFrame(index=df.index)
for period in [9, 14, 21]:
f[f'rsi_{period}'] = talib.RSI(df['close'], timeperiod=period) / 100
for fast, slow, signal in [(12, 26, 9), (5, 13, 5), (24, 52, 18)]:
macd, sig, hist = talib.MACD(df['close'], fast, slow, signal)
f[f'macd_hist_{fast}_{slow}'] = hist / df['close']
for window, std in [(20, 2), (20, 1), (50, 2)]:
upper, mid, lower = talib.BBANDS(df['close'], window, std, std)
f[f'bb_width_{window}_{std}'] = (upper - lower) / mid
f[f'bb_pos_{window}_{std}'] = (df['close'] - lower) / (upper - lower + 1e-8)
f['adx_14'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=14) / 100
f['adx_trend'] = (f['adx_14'] > 0.25).astype(float)
slowk, slowd = talib.STOCH(df['high'], df['low'], df['close'])
f['stoch_k'] = slowk / 100
f['stoch_d'] = slowd / 100
return f
def create_microstructure_features(df_ticks):
f = pd.DataFrame(index=df_ticks.index)
f['spread'] = (df_ticks['ask'] - df_ticks['bid']) / df_ticks['mid']
f['spread_ma'] = f['spread'].rolling(100).mean()
f['spread_relative'] = f['spread'] / f['spread_ma']
f['buy_volume'] = df_ticks['buy_volume']
f['sell_volume'] = df_ticks['sell_volume']
f['ofi'] = (f['buy_volume'] - f['sell_volume']) / (f['buy_volume'] + f['sell_volume'] + 1e-8)
f['ofi_ma'] = f['ofi'].rolling(20).mean()
return f
Каждый модуль покрыт unit-тестами и версионируется через Git LFS. Это позволяет быстро откатывать изменения и воспроизводить эксперименты.
Почему валидация признаков критична?
Типичные ошибки: look-ahead bias, нестационарность, мультиколлинеарность. Без отсева модель запоминает шум. Наш валидатор автоматически проверяет каждый признак по пяти метрикам. Согласно Wikipedia, feature engineering — критический этап ML, а исследования показывают, что до 70% улучшения модели достигается за счёт качественных признаков.
class FeatureValidator:
def validate(self, features_df, target_series):
report = {}
for col in features_df.columns:
series = features_df[col]
missing_pct = series.isna().mean()
valid_mask = series.notna() & target_series.notna()
if valid_mask.sum() > 100:
corr = series[valid_mask].corr(target_series[valid_mask])
else:
corr = 0
from statsmodels.tsa.stattools import adfuller
try:
adf_stat, adf_p = adfuller(series.dropna())[:2]
stationary = adf_p < 0.05
except:
stationary = None
variance = series.var()
report[col] = {
'missing_pct': missing_pct,
'correlation_with_target': corr,
'stationary': stationary,
'variance': variance,
'recommended': (missing_pct < 0.05 and abs(corr) > 0.01 and variance > 1e-8)
}
return pd.DataFrame(report).T
В отличие от ручной проверки, наш валидатор обрабатывает 200+ признаков за секунды. Результат — чистый набор фич, готовых к обучению.
Подробнее о пяти метриках валидации
- Missing percentage: не более 5%.
- Correlation with target: модуль > 0.01.
- Stationarity: p-value ADF теста < 0.05.
- Variance: > 1e-8.
- Recommended: все условия выполнены.
Отбор признаков: количество не равно качество
После генерации получаем до 200 признаков. 95% из них — шум. Отбираем самые значимые тремя методами:
- Mutual Information — учитывает нелинейные зависимости.
- SHAP importance — показывает реальный вклад в предсказание на baseline модели.
- Корреляционный фильтр — удаляем пары с корреляцией > 0.95.
В результате остаётся 30–50 фич, которые идут в обучение. Для временных рядов дополнительно проверяем стационарность и Granger causality.
| Метод отбора | Сильные стороны | Слабые стороны | Скорость |
|---|---|---|---|
| Mutual Information | Улавливает нелинейные зависимости | Чувствителен к шуму при малом количестве данных | Средняя |
| SHAP | Интерпретируемость, учитывает взаимодействия | Вычислительно затратен (O(n²)) | Низкая |
| Correlation filter | Простота, быстрота | Только линейные связи, не учитывает взаимодействия | Высокая |
from sklearn.feature_selection import mutual_info_classif
import shap
mi_scores = mutual_info_classif(X_train, y_train, random_state=42)
mi_df = pd.DataFrame({'feature': X_train.columns, 'mi_score': mi_scores})
top_features = mi_df.nlargest(50, 'mi_score')['feature'].tolist()
explainer = shap.TreeExplainer(lgb_model)
shap_values = explainer.shap_values(X_val)
feature_importance = pd.Series(np.abs(shap_values).mean(0), index=X_val.columns).sort_values(ascending=False)
Feature Store — центральное хранилище признаков
Production-архитектура требует разделения на online и offline. Online (Redis) — для real-time инференса, offline (Parquet/S3) — для батчевого обучения. Версионирование позволяет откатить изменения, если новый признак ухудшил модель. Feature Store снижает latency инференса в 3 раза по сравнению с запросами к сырым данным.
| Компонент | Назначение | Технологии |
|---|---|---|
| Feature computation | Расчёт признаков по расписанию | Python, Airflow, Ray |
| Feature Store | Версионированное хранение | Feast, Hopsworks, PostgreSQL |
| Online store | Быстрая выдача для инференса | Redis, DynamoDB |
| Offline store | Данные для обучения | Parquet, S3, HDFS |
Типичные ошибки при создании признаков
- Look-ahead bias: использование будущих данных при расчёте фич (например, VWAP за полный день на середине дня).
- Нестационарность: цена имеет тренд — используем логарифмические доходности вместо абсолютных цен.
- Мультиколлинеарность: оставляем только одну фичу из группы сильно коррелирующих.
- Пропуски >5%: признак бесполезен, удаляем или интерполируем.
Что входит в работу
- Аудит источников данных — определяем доступные API, форматы, частоту обновления.
- Разработка пайплайна — создание модулей для каждой категории признаков с тестами на look-ahead.
- Feature selection — отбор 30–50 фич через MI, SHAP и корреляционный фильтр.
- Feature Store — развёртывание хранилища с версионированием и two-store (online/offline).
- Документация и обучение — описание каждого признака, API Feature Store, воркшоп для команды.
- Поддержка — 3 месяца после сдачи, включая корректировки под меняющиеся рыночные условия.
| Этап | Длительность | Результат |
|---|---|---|
| Аудит источников | 2–3 дня | Отчёт по доступным данным, форматам, частоте |
| Разработка пайплайна | 2–3 недели | Модули генерации признаков с тестами |
| Feature selection | 1 неделя | 30–50 фич с обоснованием |
| Развёртывание Feature Store | 1–2 недели | Online/offline хранилище с версионированием |
| Документация и обучение | 3 дня | Описание фич, API, воркшоп для команды |
Снижение затрат на вычисления на 40% — реальный эффект после внедрения нашей системы. Средняя экономия на инфраструктуре составляет $7,500 в месяц, а проект окупается в среднем за 2 месяца (экономия $10,000). Получите консультацию — наши инженеры за 2 дня подготовят архитектуру под ваш стэк.







