Ви написали торгову стратегію: на історичних даних Ethereum вона показує Sharpe 2.8, максимальну просадку 12%, win rate 65%. Запускаєте на демо-рахунку — за тиждень мінус 40%. Причина: переоптимізація під історію. Out-of-sample (OOS) тестування — єдиний спосіб відсіяти такі стратегії до того, як ви ризикуєте реальним капіталом. Ми розробляємо кастомні системи OOS-тестування, які вбудовуються у ваш алгоритмічний pipeline.
Наш досвід у цій галузі: 6+ років розробки торгових систем для криптовалют, понад 70 завершених проєктів, включаючи 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-системи починається від $5 000. Така перевірка варта вкладень. Загалом, OOS-тестування в 4 рази точніше виявляє переоптимізовані стратегії порівняно зі звичайним backtesting. Наш фреймворк в 3 рази надійніший за стандартні підходи.
Покрокова інструкція впровадження OOS-тестування
- Зберіть історичні дані за 3+ роки.
- Розділіть на in-sample (80%) та out-of-sample (20%).
- Налаштуйте параметри стратегії на IS.
- Запустіть бектест на OOS.
- Оцініть метрики та прийміть рішення.
Фреймворк перевірки 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 bias) | Звіт валідації |
| Деплой | Інтегруємо в 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 дні.







