Обучение Reinforcement Learning агента для торговли
Reinforcement Learning (RL) — принципиально другой подход к алгоритмической торговле. Вместо предсказания цены и построения правил, агент учится сам, взаимодействуя со средой (рынком) и получая награды/штрафы за свои действия. RL агент может открывать позиции, закрывать их, изменять размер — и учится делать это оптимально через trial and error.
Постановка задачи как Markov Decision Process (MDP)
State (наблюдение): то, что агент видит в каждый момент времени.
- OHLCV за последние N свечей (нормализованные)
- Технические индикаторы (RSI, MACD, BB)
- Текущая позиция (нет позиции / лонг / шорт)
- Unrealized PnL текущей позиции
- Баланс счёта (нормализованный)
Action (действие):
- Дискретное: 0=hold, 1=buy, 2=sell (3 действия)
- Или непрерывное: [-1, 1] где -1=полный шорт, 0=нет позиции, 1=полный лонг
Reward (награда): это самая критичная часть. Неправильно выбранный reward ломает обучение.
def calculate_reward(prev_portfolio, curr_portfolio, action, transaction_cost=0.001):
# Базовый reward: изменение стоимости портфеля
portfolio_return = (curr_portfolio - prev_portfolio) / prev_portfolio
# Штраф за транзакционные издержки
trade_penalty = transaction_cost if action != 0 else 0
# Штраф за удержание большой позиции при высоком риске
risk_penalty = 0 # можно добавить при желании
return portfolio_return - trade_penalty
Episodic structure: один эпизод = торговля на определённом временном окне (например, 3 месяца исторических данных). После завершения эпизода — сброс в начало следующего случайного периода.
Торговая среда (Trading Environment)
import gymnasium as gym
import numpy as np
class CryptoTradingEnv(gym.Env):
def __init__(self, data, initial_capital=100000,
transaction_cost=0.001, window_size=60):
super().__init__()
self.data = data
self.capital = initial_capital
self.initial_capital = initial_capital
self.window_size = window_size
self.transaction_cost = transaction_cost
# Observation space
n_features = data.shape[1]
obs_dim = window_size * n_features + 3 # +3: position, pnl, balance
self.observation_space = gym.spaces.Box(
low=-np.inf, high=np.inf, shape=(obs_dim,), dtype=np.float32
)
# Action space: 0=hold, 1=buy_full, 2=sell_full
self.action_space = gym.spaces.Discrete(3)
def reset(self, seed=None):
self.current_step = self.window_size
self.capital = self.initial_capital
self.position = 0 # 0=none, 1=long, -1=short
self.position_price = 0
return self._get_observation(), {}
def step(self, action):
current_price = self.data.iloc[self.current_step]['close']
# Исполняем действие
reward, done = self._execute_action(action, current_price)
self.current_step += 1
terminated = self.current_step >= len(self.data) - 1
truncated = self.capital <= self.initial_capital * 0.5 # stop loss
obs = self._get_observation()
info = {'portfolio_value': self.capital}
return obs, reward, terminated or truncated, False, info
def _get_observation(self):
window = self.data.iloc[self.current_step-self.window_size:self.current_step]
features = window.values.flatten()
# Нормализуем features
position_info = np.array([
self.position,
(self.capital - self.initial_capital) / self.initial_capital,
self.capital / self.initial_capital
])
return np.concatenate([features, position_info]).astype(np.float32)
Алгоритмы RL для торговли
PPO (Proximal Policy Optimization) — наиболее популярный выбор для финансовых задач. Стабильный, хорошо работает с непрерывными и дискретными action space.
SAC (Soft Actor-Critic) — лучший для непрерывных action space (размер позиции как непрерывная переменная). Максимизирует reward + энтропию политики (exploration).
DQN (Deep Q-Network) — только для дискретных действий. Проще в реализации. Double DQN, Dueling DQN — улучшения базовой версии.
Обучение с библиотекой Stable-Baselines3
from stable_baselines3 import PPO, SAC
from stable_baselines3.common.vec_env import DummyVecEnv
from stable_baselines3.common.callbacks import EvalCallback
# Создаём параллельные среды для ускорения обучения
n_envs = 8
env = DummyVecEnv([lambda: CryptoTradingEnv(train_data)] * n_envs)
eval_env = DummyVecEnv([lambda: CryptoTradingEnv(val_data)])
model = PPO(
policy='MlpPolicy',
env=env,
learning_rate=3e-4,
n_steps=2048,
batch_size=256,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2,
verbose=1,
tensorboard_log='./tb_logs/'
)
eval_callback = EvalCallback(
eval_env,
eval_freq=10000,
best_model_save_path='./best_model/',
deterministic=True
)
model.learn(
total_timesteps=2_000_000,
callback=eval_callback
)
Reward shaping
Базовый portfolio return как reward приводит к агентам, которые берут огромный риск ради большой награды. Улучшения:
Sharpe Ratio как reward: максимизируем доходность с поправкой на риск.
def sharpe_reward(returns_history, current_return, rf=0.0):
returns_history.append(current_return)
if len(returns_history) < 20:
return current_return
recent_returns = returns_history[-252:]
mean = np.mean(recent_returns)
std = np.std(recent_returns) + 1e-8
return mean / std
Penalize drawdown: штраф пропорционален текущему drawdown.
Max position duration: штраф за удержание позиции дольше N шагов без движения.
Curriculum Learning
Начинаем обучение на «лёгких» периодах (низкая волатильность, выраженный тренд) и постепенно добавляем сложные (высокая волатильность, боковик).
Backtesting RL агента
def backtest_agent(model, test_env):
obs, _ = test_env.reset()
portfolio_values = [test_env.capital]
actions = []
while True:
action, _ = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, info = test_env.step(action)
portfolio_values.append(info['portfolio_value'])
actions.append(action)
if terminated or truncated:
break
return portfolio_values, actions
Разрабатываем RL trading agent с PPO/SAC, кастомной trading environment, reward shaping (Sharpe-based), walk-forward validation на нескольких тестовых периодах и production deployment.







