Разработка системы out-of-sample тестирования

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы out-of-sample тестирования
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1062
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка системы out-of-sample тестирования

Out-of-sample (OOS) тестирование — финальная проверка стратегии на данных, которые никогда не использовались ни для разработки, ни для оптимизации. Это самый близкий к реальному trading тест, показывающий насколько стратегия обобщается на неизвестных данных.

Принцип разделения данных

|-------- In-Sample (IS) --------|--- Out-of-Sample (OOS) ---|

IS используется для:          OOS используется для:
  - Разработки стратегии         - Финальной оценки
  - Отбора параметров            - Проверки robustness
  - Walk-forward оптимизации     - Принятия решения о деплое
  - Итеративных улучшений

Золотое правило: OOS данные нельзя смотреть до того, как стратегия окончательно зафиксирована. Как только вы посмотрели на OOS и что-то изменили — это данные больше не являются out-of-sample.

Типичное разбиение для крипто

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

OOS Validation Framework

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 результаты всегда хуже IS — это нормально. Важно что OOS положителен и деградация разумна (< 50–70%). Если OOS показывает значительно лучше IS — это тоже плохой знак: нужно проверить на look-ahead bias.