Розробка системи out-of-sample тестування криптостратегій

Ви написали торгову стратегію: на історичних даних Ethereum вона показує Sharpe 2.8, максимальну просадку 12%, win rate 65%. Запускаєте на демо-рахунку — за тиждень мінус 40%. Причина: переоптимізація під історію. **Out-of-sample (OOS) тестування** — єдиний спосіб відсіяти такі стратегії до того, як

Напрямки блокчейн-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1451
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1309
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1005
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1270
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    719
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1011

Ви написали торгову стратегію: на історичних даних 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-тестування

  1. Зберіть історичні дані за 3+ роки.
  2. Розділіть на in-sample (80%) та out-of-sample (20%).
  3. Налаштуйте параметри стратегії на IS.
  4. Запустіть бектест на OOS.
  5. Оцініть метрики та прийміть рішення.

Фреймворк перевірки 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 дні.