Розробка платформи бектестингу торговельних стратегій
Платформа бектестингу — це інфраструктура для перевірки торговельних стратегій на історичних даних. Хороша платформа не просто відтворює стратегію на минулих даних, а робить це максимально реалістично: з урахуванням комісій, проскальзування, ліквідності та захистом від data leakage.
Архітектура платформи
Data Layer — сховище історичних даних: OHLCV свічки, tick data, знімки order book. ClickHouse або Arctic (Python) для ефективного доступу.
Backtest Engine — ядро симуляції. Event-driven або vectorized архітектура.
Strategy Runtime — середовище виконання користувацьких стратегій з ізоляцією.
Results & Reporting — розрахунок метрик, візуалізація P&L, порівняння стратегій.
Optimization Engine — перебір параметрів (grid search, genetic algorithm, Bayesian).
Event-Driven vs Vectorized
Vectorized — вся логіка застосовується до pandas DataFrame відразу. Швидко (NumPy операції), але складно моделювати реальну поведінку ордерів:
# Vectorized підхід
import pandas as pd
import numpy as np
def backtest_ma_crossover(df: pd.DataFrame, fast: int, slow: int) -> pd.Series:
fast_ma = df['close'].rolling(fast).mean()
slow_ma = df['close'].rolling(slow).mean()
# Сигнали
signal = np.where(fast_ma > slow_ma, 1, -1)
signal = pd.Series(signal, index=df.index)
# Returns
returns = df['close'].pct_change()
strategy_returns = signal.shift(1) * returns # shift(1) = немає look-ahead
return strategy_returns.cumsum()
Event-driven — більш реалістична симуляція. Кожна ринкова подія (свіча, тик) обробляється послідовно. Можна моделювати partial fills, order slippage, margin calls:
class EventDrivenBacktester:
def run(self, strategy: Strategy, data_feed: DataFeed) -> BacktestResult:
portfolio = Portfolio(initial_cash=100_000)
broker = SimulatedBroker(portfolio, slippage=0.001, commission=0.0005)
for event in data_feed:
if isinstance(event, MarketEvent):
strategy.on_market_data(event)
elif isinstance(event, SignalEvent):
order = strategy.generate_order(event)
broker.submit_order(order)
elif isinstance(event, FillEvent):
portfolio.update(event)
strategy.on_fill(event)
return BacktestResult(portfolio.equity_curve, portfolio.trades)
Симуляція виконання ордерів
Реалістична симуляція — ключова різниця між добрим та поганим бектестером:
class SimulatedBroker:
def __init__(self, slippage_pct: float = 0.001, commission_pct: float = 0.0005):
self.slippage = slippage_pct
self.commission = commission_pct
self.pending_orders: list[Order] = []
def simulate_fill(self, order: Order, bar: OHLCV) -> FillEvent:
if order.type == "MARKET":
# Market order виконується за наступним open + slippage
fill_price = bar.open * (1 + self.slippage if order.side == "BUY" else 1 - self.slippage)
elif order.type == "LIMIT":
# Limit order виконується якщо ціна досягла рівня
if order.side == "BUY" and bar.low <= order.price:
fill_price = min(order.price, bar.open) # консервативний fill
elif order.side == "SELL" and bar.high >= order.price:
fill_price = max(order.price, bar.open)
else:
return None # не виконаний
commission = fill_price * order.quantity * self.commission
return FillEvent(
order_id=order.id,
fill_price=fill_price,
quantity=order.quantity,
commission=commission,
timestamp=bar.timestamp,
)
Метрики бектесту
def calculate_metrics(equity_curve: pd.Series, trades: list[Trade]) -> BacktestMetrics:
returns = equity_curve.pct_change().dropna()
annual_factor = 252 # торговельних днів
# Базові метрики
total_return = (equity_curve.iloc[-1] / equity_curve.iloc[0]) - 1
annual_return = (1 + total_return) ** (annual_factor / len(returns)) - 1
# Risk-adjusted
sharpe = returns.mean() / returns.std() * np.sqrt(annual_factor) if returns.std() > 0 else 0
sortino = returns.mean() / returns[returns < 0].std() * np.sqrt(annual_factor)
# Drawdown
rolling_max = equity_curve.cummax()
drawdown = (equity_curve - rolling_max) / rolling_max
max_drawdown = drawdown.min()
calmar = annual_return / abs(max_drawdown) if max_drawdown != 0 else 0
# Trade-level метрики
Надійна платформа бектестингу необхідна для професіональної розробки алгоритмів.







