Стандартные среды из Gymnasium (CartPole, LunarLander) не годятся для трейдинга. Они игнорируют проскальзывание, комиссии и рыночное влияние. Результат: Sharpe 2 на бэктесте, а в реальности — слив депозита. Представьте: вы обучили агента на стандартной среде, получили Sharpe 2.5, запускаете на реальном счете — и через месяц просадка 40%. Знакомая ситуация? Проблема в том, что стандартные среды не учитывают микроструктуру рынка: проскальзывание при исполнении, комиссии брокера (0.05–0.2% за сделку), рыночное влияние крупных ордеров (до 5% от объема). Агент учится торговать на идеальных данных, а в реальности все иначе. Мы решаем эту проблему, создавая кастомные Gym-среды, которые включают все ключевые факторы: спреды, комиссии, глубину стакана для HFT, и даже риск-менеджмент. Это позволяет агенту обучаться в условиях, максимально приближенных к реальным, и показывать состоятельные результаты на live-рынке. Оценим ваш проект за 1 день — свяжитесь с нами.
Почему кастомная Gym-среда критична для торговой стратегии?
Стандартные среды дают завышенные результаты на 20–40%. Например, стратегия с лимитными ордерами на нескольких биржах в обычной среде покажет отличный результат, а в реальности проскальзывание в 0.2% и комиссия 0.1% съедают 30% прибыли. Агент учится торговать неправильно, принимая шум за сигнал. В денежном выражении для портфеля в $100,000 это означает потерю $30,000 в год — только на издержках. Кастомная среда моделирует эти эффекты, и агент учится их избегать.
Как мы разрабатываем кастомную среду?
Мы строим среду на базе Gymnasium с точным моделированием всех аспектов торговли.
Структура класса CustomTradingEnv
import gymnasium as gym
from gymnasium import spaces
import numpy as np
class CustomTradingEnv(gym.Env):
metadata = {'render_modes': ['human', 'rgb_array']}
def __init__(self, df, config):
super().__init__()
self.df = df
self.config = config
# observation space: OHLCV + indicators + portfolio state
n_features = config['n_features']
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf,
shape=(n_features,), dtype=np.float32
)
# action space: position size [-1, 1] per asset
n_assets = config['n_assets']
self.action_space = spaces.Box(
low=-1.0, high=1.0,
shape=(n_assets,), dtype=np.float32
)
self._reset_portfolio()
def reset(self, seed=None, options=None):
super().reset(seed=seed)
self._reset_portfolio()
self.current_step = self.config['window_size']
obs = self._get_observation()
return obs, {}
def step(self, action):
# 1. Исполнение действия (с реалистичным моделированием)
executed_action = self._execute_order(action)
# 2. Переход к следующему шагу
self.current_step += 1
# 3. Обновление портфеля по новым ценам
self._update_portfolio()
# 4. Расчёт reward
reward = self._compute_reward()
# 5. Наблюдение
obs = self._get_observation()
# 6. Условие завершения
terminated = self.current_step >= len(self.df) - 1
truncated = self.portfolio_value < self.config['min_capital']
info = {
'portfolio_value': self.portfolio_value,
'positions': self.positions.copy(),
'total_trades': self.total_trades
}
return obs, reward, terminated, truncated, info
Реалистичное исполнение ордеров
Крупные ордера двигают рынок — игнорирование этого даёт ложные сигналы. Мы моделируем проскальзывание и комиссии:
def _execute_order(self, target_weights):
current_prices = self.df.iloc[self.current_step][['open', 'high', 'low', 'close']]
# проскальзывание: зависит от размера ордера и spread
order_size = np.abs(target_weights - self.current_weights)
slippage = order_size * self.config['slippage_factor']
execution_price = current_prices['open'] * (1 + slippage)
# комиссия
trade_value = np.abs(order_size) * execution_price * self.portfolio_value
commission = trade_value * self.config['commission_rate']
self.portfolio_value -= commission.sum()
self.current_weights = target_weights.copy()
self.total_trades += (order_size > 0.01).sum()
return target_weights
Для HFT-стратегий добавляем симуляцию стакана:
class LOBSimulator:
"""Level-2 order book simulation"""
def __init__(self, spread_bps=5, depth_levels=10):
self.spread_bps = spread_bps
self.depth_levels = depth_levels
def get_fill_price(self, mid_price, order_size_usd):
# fill price зависит от глубины стакана
spread = mid_price * self.spread_bps / 10000
market_impact = np.sqrt(order_size_usd / 1e6) * spread
return mid_price + spread/2 + market_impact
Как выбрать reward функцию для торговой стратегии?
Выбор reward — самая важная часть кастомной среды. Мы предлагаем несколько вариантов:
| Функция | Преимущества | Недостатки | Когда использовать |
|---|---|---|---|
Простой return (daily_return) |
Прозрачность, простота | Не учитывает риск | Для стратегий с низкой толерантностью к риску? Нет. Лучше для тестов |
| Sharpe-adjusted | Учитывает риск через rolling window | Чувствителен к окну | Для долгосрочных стратегий, где важна стабильность |
| Penalized drawdown | Штраф за просадку | Сложнее настройка penalty | Для стратегий с защитой капитала |
def _compute_reward(self):
daily_return = (self.portfolio_value / self.prev_portfolio_value) - 1
# вариант 1: простой return
reward = daily_return
# вариант 2: Sharpe-adjusted (rolling window)
self.returns_history.append(daily_return)
if len(self.returns_history) >= 20:
sharpe = np.mean(self.returns_history[-20:]) / (np.std(self.returns_history[-20:]) + 1e-8)
reward = daily_return * (1 + sharpe)
# вариант 3: penalized drawdown
current_dd = (self.peak_value - self.portfolio_value) / self.peak_value
reward = daily_return - self.config['dd_penalty'] * current_dd
# штраф за чрезмерную торговлю
reward -= self.config['turnover_penalty'] * self.daily_turnover
return float(reward)
Штраф за turnover особенно важен для HFT — он учит агента не торговать, когда выгоды нет.
Observation Engineering
def _get_observation(self):
window = self.df.iloc[self.current_step - self.window_size:self.current_step]
features = []
# ценовые returns (нормализованы)
returns = window['close'].pct_change().fillna(0).values
features.extend(returns[-self.window_size:])
# технические индикаторы
features.extend([
window['rsi'].iloc[-1] / 100,
window['macd_norm'].iloc[-1],
window['bb_position'].iloc[-1] # (price - lower) / (upper - lower)
])
# portfolio state
features.extend(self.current_weights)
features.append(self.portfolio_value / self.initial_capital - 1)
return np.array(features, dtype=np.float32)
Мы добавляем нормализованные признаки — это ускоряет сходимость агента.
Сравнение стандартной и кастомной сред
| Параметр | Стандартная среда | Кастомная среда |
|---|---|---|
| Проскальзывание | Не учитывается | Моделируется на основе объема и спреда (0.1–0.5 бп) |
| Комиссии | Нет | Брокерские + биржевые (0.05–0.2%) |
| Рыночное влияние | Нет | Учитывается через LOB (impact до 5%) |
| Поддержка нескольких активов | Только один | Любое количество (до 20+ в проектах) |
| Reward | Обычный return | Sharpe, drawdown, turnover штраф |
Процесс работы и сроки
| Этап | Длительность |
|---|---|
| Анализ стратегии | 1 день |
| Проектирование среды | 1–2 дня |
| Реализация | 2–7 дней |
| Тестирование (validation) | 1–2 дня |
| Деплой и документация | 1 день |
Общий срок — от 3–5 дней для одного актива до 3–4 недель для multi-asset с order book. Стоимость рассчитывается индивидуально — оставьте заявку на консультацию, и мы оценим проект.
Что вы получите в результате
- Исходный код кастомной среды на Python (Gymnasium) с поддержкой ваших данных.
- Конфигурационные файлы для single-asset, multi-asset и HFT режимов.
- Документацию по API, reward функциям и моделированию исполнения.
- Jupyter-ноутбук с обучением агента и визуализацией результатов.
- Поддержку при интеграции в пайплайн (MLflow, Weights & Biases).
- Гарантию прохождения проверок gymnasium.utils.env_checker.
Свяжитесь с нами для обсуждения вашей стратегии — и получите консультацию в течение дня.
Типичные проблемы при разработке среды
- Look-ahead bias: нормализация на всем датасете. Мы проверяем временное разделение и не допускаем утечки будущих данных.
- Игнорирование комиссий в reward: агент учится часто торговать, что снижает доходность на 20–30%.
- Слишком простой reward (только return): ведет к высокой волатильности и просадкам. Мы используем комбинированные reward с штрафами.
Для верификации среды используем gymnasium.utils.env_checker и sanity checks:
- Random policy теряет капитал из-за комиссий.
- Buy & Hold воспроизводится при постоянных весах.
- Нет look-ahead: observation не содержит будущих цен.
Наши инженеры с сертификатами по машинному обучению и трейдингу гарантируют качество. Получите консультацию по вашей стратегии — свяжитесь с нами.







