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







