Почему стратегия на истории кажется умной почти всегда
Backtesting очень привлекателен, потому что дает ощущение быстрого ответа: берем стратегию, прогоняем по прошлым периодам и смотрим, как она бы себя вела. Это полезно и в торговых системах, и в продуктовых правилах, и в decision policy задачах. Но именно здесь рождается одна из самых опасных иллюзий аналитики: если стратегия красиво выглядела на истории, значит она хороша и впереди. На практике история легко переобучается так же, как и модель.
Поэтому backtesting полезен только тогда, когда его воспринимают как честную симуляцию прошлого решения, а не как машину для самоуспокоения.
Что именно делает backtesting
$$ Performance = \sum_{t=1}^{T} outcome_t(strategy_t) $$Backtesting оценивает стратегию как последовательность решений на прошлых периодах и их фактических исходов. Смысл в том, чтобы шаг за шагом проиграть, что произошло бы, если бы в каждый момент применялась именно эта стратегия.
Performance— накопленный результат стратегии на историческом горизонтеoutcome_t— фактический исход решения в период tstrategy_t— решение стратегии в момент времени tT— длина исторического окна проверки
Эта формула полезна потому, что возвращает проверку стратегии к последовательности действий во времени. Мы не просто смотрим среднюю метрику. Мы разыгрываем сценарий принятия решений на исторической траектории.
Почему backtesting легко сделать нечестным
Во-первых, через look-ahead bias: когда стратегия использует информацию, которая в тот момент времени еще не была доступна. Во-вторых, через подгонку правил под уже известную историю. В-третьих, через игнорирование издержек: задержек, комиссий, операционных ограничений, нагрузки команды, latency в системе. Исторический тест без этих ограничений часто выглядит лучше реальности.
Именно поэтому сильный backtest — это не просто прогон на CSV, а аккуратная реконструкция того, какие данные, сигналы и ограничения были доступны в каждый момент времени.
Как показать идею в Python
import pandas as pd # создаем простой пример исторической стратегии по порогу сигнала
frame = pd.DataFrame({ # задаем временные наблюдения и будущий результат действия
'day': [1, 2, 3, 4, 5], # временная ось проверки
'signal': [0.2, 0.7, 0.4, 0.9, 0.6], # сигнал, доступный в момент решения
'future_gain': [0.0, 1.2, -0.3, 1.8, 0.4], # фактический результат действия после решения
})
frame['action'] = (frame['signal'] >= 0.6).astype(int) # стратегия действует только при сильном сигнале
frame['realized_gain'] = frame['action'] * frame['future_gain'] # считаем фактический результат стратегии по периодам
print(frame) # смотрим, как историческая проверка раскладывает стратегию по времени
print(round(frame['realized_gain'].sum(), 3)) # суммируем общий результат backtest на историиBacktesting становится по-настоящему полезным тогда, когда сохраняет уважение к времени, ограничениям и честности данных. Иначе он быстро превращается в красивую, но опасную ретроспективную иллюзию.