Финансовые рынки становятся ареной алгоритмов. Те, кто умеет эффективно анализировать данные — зарабатывает огромные деньги. Эта статья — о том, как шаг за шагом построить на Python торговые стратегии на основе глубокого машинного обучения.
Софиен Каабар в книге «Deep Learning for Finance» демонстрирует, как связать машинное обучение, глубокие нейросети и технический анализ. Перед вами не академический учебник, а практическое руководство — от простых индикаторов до продвинутых методов обучения с подкреплением. В книге представлены:
Первый шаг любого алгоритмического трейдера — получение чистых, корректных данных и базовая предобработка. В примерах ниже используется yfinance для загрузки котировок и расчёта простейшей доходности.
import yfinance as yf
import pandas as pd
import numpy as np
df = yf.download("AAPL", start="2017-01-01", end="2024-01-01")
df = df.sort_index()
df["ret"] = df["Close"].pct_change().fillna(0)
Машинное обучение любит признаки. В трейдинге это индикаторы: RSI, EMA, Bollinger Bands.
Пример RSI с корректной обработкой:
def rsi(series: pd.Series, period: int = 14) -> pd.Series:
delta = series.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.ewm(alpha=1/period, adjust=False).mean()
avg_loss = loss.ewm(alpha=1/period, adjust=False).mean()
rs = avg_gain / (avg_loss + 1e-12)
return 100 - (100 / (1 + rs))
df["RSI"] = rsi(df["Close"])
df["EMA20"] = df["Close"].ewm(span=20, adjust=False).mean()
Теперь у нас есть «фичи», которые можно подать в модель.
Начнём с классики: логистическая регрессия предсказывает, вырастет ли цена завтра.
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
X = df[["RSI", "EMA20"]].dropna()
y = (df["Close"].pct_change().shift(-1) > 0).astype(int).reindex(X.index).fillna(0).astype(int)
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=1000))
])
tscv = TimeSeriesSplit(n_splits=5)
scores = []
for train_idx, test_idx in tscv.split(X):
pipe.fit(X.iloc[train_idx], y.iloc[train_idx])
scores.append(pipe.score(X.iloc[test_idx], y.iloc[test_idx]))
print(f"Средняя точность: {np.mean(scores):.3f}")
Используем TimeSeriesSplit, чтобы не «подсматривать в будущее».
Глубокое обучение позволяет ловить нелинейные зависимости. LSTM‑сеть учится предсказывать цену на основе последних 60 дней.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from sklearn.preprocessing import MinMaxScaler
import numpy as np
prices = df["Close"].dropna().values.reshape(-1, 1)
scaler = MinMaxScaler()
scaled = scaler.fit_transform(prices)
window = 60
X, y = [], []
for i in range(len(scaled) - window):
X.append(scaled[i:i+window])
y.append(scaled[i+window])
X, y = np.array(X), np.array(y)
split = int(len(X) * 0.8)
X_train, y_train = X[:split], y[:split]
X_test, y_test = X[split:], y[split:]
model = Sequential([LSTM(64, input_shape=(window, 1)), Dense(1)])
model.compile(optimizer="adam", loss="mse")
model.fit(X_train, y_train, epochs=10, batch_size=64, verbose=1, validation_data=(X_test, y_test))
После обучения можно сравнить прогнозы с реальными ценами и посчитать RMSE.
Модель — это хорошо, но важно понять, как она работает в торговле. Мы строим сигналы и считаем доходность.
Пример простого бэктеста стратегии:
import numpy as np
def sharpe(returns, periods_per_year=252):
excess_ret = returns - 0 # при отсутствии безрисковой ставки
return np.sqrt(periods_per_year) * excess_ret.mean() / (excess_ret.std() + 1e-12)
def max_drawdown(equity_curve):
peak = np.maximum.accumulate(equity_curve)
drawdown = (equity_curve - peak) / (peak + 1e-12)
return drawdown.min()
def cagr(equity_curve, periods_per_year=252):
n_periods = len(equity_curve)
total_return = equity_curve[-1] / equity_curve[0]
years = n_periods / periods_per_year
return total_return ** (1 / years) - 1
signal = (df["RSI"] < 30).astype(int) # покупаем при перепроданности
strat_ret = signal.shift(1).fillna(0) * df["ret"]
equity = (1 + strat_ret).cumprod().fillna(method="ffill")
print("Sharpe:", sharpe(strat_ret.dropna()))
print("MaxDD:", max_drawdown(equity.values))
print("CAGR:", cagr(equity.values))
Теперь видно, насколько стратегия устойчива.
Подходы RL позволяют напрямую оптимизировать экономический результат, но требуют аккуратного оформления среды и стабильного обучения. Важно понимать, что классический Q‑learning подойдёт только для сильно упрощённой среды с дискретным и небольшим пространством состояний. Для реальных задач чаще используют Deep RL (DQN, PPO), сложные среды и симуляции комиссий/ликвидности.
Простейший пример Q‑learning на дискретном состоянии (RSI‑бинов):
states = pd.cut(df["RSI"].dropna(), bins=[0,30,50,70,100], labels=False)
n_states = states.max() + 1
n_actions = 2 # 0 = нет позиции, 1 = держать/купить
Q = np.zeros((n_states, n_actions))
alpha, gamma, epsilon = 0.1, 0.9, 0.1
rewards = []
for t in range(1, len(states)):
s = states.iloc[t-1]
if np.random.rand() > epsilon:
a = np.argmax(Q[s])
else:
a = np.random.randint(n_actions)
r = df["ret"].iloc[t] * (1 if a == 1 else 0)
rewards.append(r)
s_next = states.iloc[t]
Q[s, a] += alpha * (r + gamma * np.max(Q[s_next]) - Q[s, a])
Такой агент постепенно учится, какие состояния выгоднее для входа в позицию.
Дисклеймер: материалы носят информационный характер. Торговля на финансовых рынках связана с высоким уровнем риска. Автор статьи не даёт инвестиционных рекомендаций и не призывает к совершению операций с финансовыми инструментами.