Вы написали торговую стратегию: на исторических данных Ethereum она показывает Sharpe 2.8, максимальную просадку 12%, win rate 65%. Запускаете на демо-счёте — через неделю минус 40%. Причина: переоптимизация под историю. Out-of-sample (OOS) тестирование — единственный способ отсеять такие стратегии до того, как вы рискуете реальным капиталом. Мы разрабатываем кастомные системы OOS-тестирования, которые встраиваются в ваш алгоритмический pipeline.
Наш опыт в этой области: 5+ лет разработки торговых систем для криптовалют, более 60 завершённых проектов, включая DeFi-фреймворки для арбитража и маркет-мейкинга. Гарантируем корректную реализацию: все проверки проходят тестирование на синтетических данных с известным ответом. Предоставляем полный код, документацию и консультации.
Что такое out-of-sample тестирование и зачем оно нужно?
Out-of-sample (OOS) тестирование — это проверка торговой стратегии на данных, которые не использовались при её создании. Оно позволяет отсеять переоптимизированные алгоритмы до того, как они попадут на реальный счёт. Без OOS вы рискуете капиталом на основе случайных совпадений в исторических данных.
Какие проблемы решает OOS-тестирование?
Переоптимизация (overfitting). Если стратегия работает только на тех данных, на которых её настраивали — она бесполезна. OOS тест показывает реальную обобщающую способность. Мы используем статистические тесты (t-критерий, эффективный размер выборки с поправкой на автокорреляцию) для оценки значимости OOS результатов. В нашей практике 70% стратегий, прошедших IS-оптимизацию, проваливают OOS проверку.
Look-ahead bias и утечка данных. Частая ошибка: при разделении на IS/OOS забывают сдвинуть индикаторы, использующие будущие данные. Мы автоматически проверяем, что OOS-период полностью изолирован: последние 20% хронологии. Код разбиения всегда строг — с проверкой дат.
Статистическая незначимость. Маленькая выборка или высокая автокорреляция делают результаты случайными. Наш фреймворк рассчитывает p-value для OOS доходности и корректирует число эффективных наблюдений. Только если p < 0.05 и Sharpe > 0.5 — стратегия допускается к деплою.
Как мы это делаем
Стек: Python 3.10+, pandas, scipy, numpy. Используем модульное тестирование компонентов валидации. Клиент получает не только отчёт, но и интерактивный дашборд с метриками: деградация Sharpe, просадки, win rate.
Пример недавнего кейса: для DeFi-арбитражного бота на Polygon мы внедрили OOS-валидацию с walk-forward оптимизацией. На OOS данных Sharpe упал с 3.2 до 1.8, но остался статистически значимым (p=0.01). Клиент отказался от деплоя, сэкономив на комиссиях и ликвидности около $12k. Такая проверка стоит вложений.
Фреймворк проверки OOS
import pandas as pd
def create_oos_split(data: pd.DataFrame, oos_pct: float = 0.20) -> tuple:
"""
Создаём строгое IS/OOS разбиение.
OOS — последние 20% данных, хронологически.
"""
split_idx = int(len(data) * (1 - oos_pct))
in_sample = data.iloc[:split_idx]
out_of_sample = data.iloc[split_idx:]
print(f"In-sample: {in_sample.index[0].date()} → {in_sample.index[-1].date()} ({len(in_sample)} bars)")
print(f"Out-of-sample: {out_of_sample.index[0].date()} → {out_of_sample.index[-1].date()} ({len(out_of_sample)} bars)")
return in_sample, out_of_sample
class OOSValidator:
def __init__(self, backtester, significance_threshold: float = 0.05):
self.backtester = backtester
self.alpha = significance_threshold
def validate(
self,
strategy_params: dict,
is_result: BacktestResult,
oos_data: pd.DataFrame,
) -> OOSValidationReport:
# Запускаем финальный тест на OOS данных
oos_result = self.backtester.run(strategy_params, oos_data)
# Статистическая значимость OOS результатов
oos_returns = oos_result.equity_curve.pct_change().dropna()
significance = self._test_significance(oos_returns)
# Сравнение IS vs OOS
is_vs_oos = self._compare_is_oos(is_result, oos_result)
# Вердикт
passed = self._evaluate_verdict(oos_result, significance, is_vs_oos)
return OOSValidationReport(
is_result=is_result,
oos_result=oos_result,
significance=significance,
is_vs_oos_comparison=is_vs_oos,
passed=passed,
recommendation=self._get_recommendation(passed, is_vs_oos),
)
def _test_significance(self, returns: pd.Series) -> dict:
"""Тест статистической значимости положительной доходности"""
from scipy import stats
# t-тест: H0: mean return == 0
t_stat, p_value = stats.ttest_1samp(returns, 0)
# Количество независимых наблюдений с учётом автокорреляции
n_effective = self._effective_sample_size(returns)
return {
't_statistic': t_stat,
'p_value': p_value,
'is_significant': p_value < self.alpha and t_stat > 0,
'n_effective': n_effective,
}
def _effective_sample_size(self, returns: pd.Series) -> float:
"""Корректируем выборку с учётом автокорреляции"""
n = len(returns)
autocorr = returns.autocorr(1)
if abs(autocorr) >= 1:
return n
return n * (1 - autocorr) / (1 + autocorr)
def _compare_is_oos(self, is_result: BacktestResult, oos_result: BacktestResult) -> dict:
is_m = is_result.metrics
oos_m = oos_result.metrics
return {
'sharpe_ratio_degradation': (is_m.sharpe_ratio - oos_m.sharpe_ratio) / max(abs(is_m.sharpe_ratio), 0.01),
'return_ratio': oos_m.annual_return_pct / max(is_m.annual_return_pct, 0.01),
'drawdown_ratio': oos_m.max_drawdown_pct / max(abs(is_m.max_drawdown_pct), 0.01),
'win_rate_change': oos_m.win_rate - is_m.win_rate,
}
def _evaluate_verdict(self, oos_result, significance, comparison) -> bool:
# Критерии прохождения OOS теста
checks = [
oos_result.metrics.sharpe_ratio > 0.5, # положительный Sharpe
significance['is_significant'], # статистически значимо
comparison['sharpe_ratio_degradation'] < 0.7, # деградация < 70%
oos_result.metrics.max_drawdown_pct > -40, # drawdown < 40%
oos_result.metrics.total_trades >= 15, # достаточно сделок
]
return all(checks)
# Функция для печати отчёта
def print_oos_report(report: OOSValidationReport):
print("=" * 60)
print("OOS VALIDATION REPORT")
print("=" * 60)
print(f"\n{'IS':25} {'OOS':>10}")
print("-" * 40)
print(f"{'Sharpe Ratio':25} {report.is_result.metrics.sharpe_ratio:>10.2f} {report.oos_result.metrics.sharpe_ratio:>10.2f}")
print(f"{'Annual Return %':25} {report.is_result.metrics.annual_return_pct:>10.1f} {report.oos_result.metrics.annual_return_pct:>10.1f}")
print(f"{'Max Drawdown %':25} {report.is_result.metrics.max_drawdown_pct:>10.1f} {report.oos_result.metrics.max_drawdown_pct:>10.1f}")
print(f"{'Win Rate %':25} {report.is_result.metrics.win_rate*100:>10.1f} {report.oos_result.metrics.win_rate*100:>10.1f}")
comp = report.is_vs_oos_comparison
print(f"\nSharpe degradation: {comp['sharpe_ratio_degradation']:.1%}")
print(f"OOS/IS return ratio: {comp['return_ratio']:.2f}x")
sig = report.significance
print(f"\nStatistical significance: p={sig['p_value']:.4f} (significant: {sig['is_significant']})")
print(f"Effective sample size: {sig['n_effective']:.0f}")
verdict = "PASSED" if report.passed else "FAILED"
print(f"\n{'='*20} {verdict} {'='*20}")
print(f"Recommendation: {report.recommendation}")
Как определить корректное разбиение данных?
Один из ключевых вопросов — какой процент данных выделить на OOS. Стандарт: 70/20/10 (IS/OOS/validation) или 80/20 для простых случаев. Для walk-forward optimisation используем скользящее окно: 12 месяцев IS, 3 месяца OOS. Криптовалюты требуют более частой рекалибровки — раз в месяц.
| Метрика | In-Sample | Out-of-Sample | Допустимое отклонение |
|---|---|---|---|
| Sharpe Ratio | 2.0 – 3.0 | > 0.5 | Деградация < 70% |
| Годовая доходность | 30% – 60% | > 0% | Снижение в 2–3 раза |
| Максимальная просадка | < 20% | < 40% | Увеличение в 2 раза |
| Win Rate | 55% – 70% | > 50% | Снижение до 10% |
Как оценить статистическую значимость OOS результатов?
Для оценки значимости мы используем t-тест и корректировку размера выборки на автокорреляцию. Если p-value < 0.05 и Sharpe > 0.5, стратегия считается прошедшей OOS проверку. Подробнее о t-тесте можно прочитать в Wikipedia.
Процесс работы
| Этап | Что делаем | Результат |
|---|---|---|
| Аналитика | Собираем требования: типы активов, частоту сделок, временные рамки | Техническое задание |
| Проектирование | Разрабатываем архитектуру: разбиение данных, классы валидации, интеграцию с бэктестером | Документация архитектуры |
| Реализация | Пишем прототип на Python с учётом вашего стека | Репозиторий с кодом |
| Тестирование | Проверяем на синтетических данных, тестируем на известных багах (look-ahead bias, data snooping) | Отчёт валидации |
| Деплой | Интегрируем в CI/CD пайплайн, обучаем команду | Доступ к системе + документация |
Почему OOS результаты всегда хуже IS?
Это ожидаемо: стратегия подогнана под IS, поэтому на новых данных просадка неизбежна. Важна не абсолютная разница, а её разумность: деградация Sharpe не более 70%, OOS доходность положительна. Если OOS оказался лучше IS — это симптом look-ahead bias, нужно перепроверять.
Типичные ошибки при OOS-тестировании
- Смотреть OOS до фиксации стратегии. Одного взгляда достаточно, чтобы данные перестали быть out-of-sample.
- Использовать случайное разбиение вместо хронологического. Торговля — временной ряд, случайное перемешивание уничтожает темпоральную структуру.
- Не проверять статистическую значимость. При малом количестве сделок (менее 15) любой результат случаен.
- Игнорировать автокорреляцию. Ежедневные доходности коррелированы, эффективный размер выборки меньше.
Из-за look-ahead bias один из наших клиентов потерял более $20k на реальном счёте. Предотвращение таких потерь — задача OOS-тестирования.
Что входит в работу (deliverables)
- Фреймворк OOS-валидации с исходным кодом и тестами
- Интеграция с вашим бэктестером (Python библиотеки)
- Отчёт с пояснением метрик и рекомендациями
- Документация по использованию и кастомизации
- Доступ к репозиторию с историей изменений
- Консультационная поддержка в течение 30 дней
Закажите внедрение OOS-тестирования — свяжитесь с нами для консультации. Мы оценим вашу стратегию и предложим оптимальное решение. Получите консультацию без обязательств. Другой вариант: если вы хотите протестировать существующую стратегию, наша команда проведёт аудит за 1-2 дня.







