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







