Стандартные среды из 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 не содержит будущих цен.
Наши инженеры с сертификатами по машинному обучению и трейдингу гарантируют качество. Получите консультацию по вашей стратегии — свяжитесь с нами.







