Возьмём informed prior: Sharpe distribution retail intraday strategies.
Prior (log-normal based on hedge fund survey + academic lit): P(true Sharpe > 1) = 30% (decent strategy) P(true Sharpe > 2) = 8% (good) P(true Sharpe > 4) = 0.5% (excellent, Medallion tier) P(true Sharpe > 6) = 0.01% (physically implausible without HFT) P(true Sharpe > 8) = 10^-6 (lottery ticket) Likelihood: sharpe_hat=8.48, N=736 days SE(sharpe_hat) ≈ sqrt((1 + 8.48^2/2) / 736) ≈ 0.24 Posterior (Bayesian update): P(true Sharpe > 4 | data) ≈ 65% P(true Sharpe > 6 | data) ≈ 15% P(true Sharpe > 7 | data) ≈ 3% Expected posterior Sharpe ≈ 3.8 - 4.5
Наблюдаемый 8.48 настолько экстремален, что prior тянет posterior ВНИЗ сильно. Это называется Bayesian shrinkage.
Вывод: С вероятностью ~65% реальный Sharpe > 4 (ещё отлично). С вероятностью < 3% он > 7. Планируй жить с Sharpe 3-5, не 8.
Kurtosis normal = 3. Наблюдаемый = 6.71 → tail risk в 2.2x выше normal.
Sharpe ratio предполагает нормальное распределение. При k=6.71:
| Event | Normal (Sharpe 8.48) | Adjusted (k=6.71) |
|---|---|---|
| Day loss > 2σ | 2.3% дней | ~4.5% дней |
| Day loss > 4σ | 0.003% дней | ~0.8% дней (1 в 125) |
| Year max DD estimate | -$3K | -$8K to -$15K |
Наблюдаемый max_losing_streak = 6 дней. При true Sharpe 8.48 вероятность такой полосы ≈ 0.5^6 × days ≈ 1 шанс из 1000. Наблюдаемый = 1 за 736 дней → consistent с true Sharpe ~2-3, не 8.
В файле моделей: M1, M2, M3, M4, M5, M6, M7, M8, M9, M9d. Каждая "улучшала" предыдущую.
Family-wise error rate (FWER): 10 моделей × тестируем на тех же данных P(at least one false positive) = 1 - (1-0.05)^10 = 40% Даже с Bonferroni: p_adjusted = 0.05 / 10 = 0.005 Но при Sharpe 8.48 с SE 0.24 → z-score = 35 → still significant
Статистически M9d переживает коррекцию Бонферрони. Но selection bias реален: вы выбрали M9d потому что она победила. Из 10 вариантов хотя бы один случайно выиграет.
Тест: сгенерируй 10 синтетических "моделей" из random rules, запусти на тех же данных. Если best-of-10 Sharpe > 5 → selection bias = real.
В scoring_v4_oos.py PEAD и ticker biases computed ONCE на весь TRAIN (до 2024-12-31), потом применены как constants на TEST.
Это НЕ true expanding window. Реальная expanding window:
for date in sorted(test_dates):
train_subset = all_data[data.date < date]
biases = compute(train_subset) # re-fit на каждой дате
score_today = apply(biases, today_row)
Текущий подход = "frozen biases". Это OK если distribution stable, но каждый месяц без retrain = risk of stale bias.
Posterior distribution for true Sharpe (Bayesian with informed prior):
5% — median — 95% credible interval: 2.1 — 4.2 — 6.8
Best estimate true Sharpe = 4.2. Plan для worst-case: Sharpe 2.1 → $180/day становится $45/day.
41 trade/day, ~55% Tech names (NVDA, AMD, TSLA, SMCI, MSTR, COIN, PLTR и т.д.). Корреляция Tech names внутри дня ≈ 0.6-0.8.
| Setup | Independent (assumption) | Реальность (ρ=0.7) |
|---|---|---|
| 41 trades × $1000 × ret std 1% | Daily std = $64 | Daily std = $540 |
| 99% VaR day | -$150 | -$1,250 |
| 3-sigma event | -$192 | -$1,620 |
Sharpe 8.48 в бэктесте возможен только потому что Tech rally с 2023 был продолжительным. В Tech-drawdown (Q3 2022-style) 41 correlated LONG = single concentrated bet.
Fix: max 8 positions per sector/day, max 25% daily BP в одном секторе.
Фактические метрики: WR 61.2%, avg win ≈ +0.42%, avg loss ≈ -0.30% (rough estimate из PnL/N).
Kelly fraction per trade: p = 0.612, q = 0.388 b = avg_win / avg_loss ≈ 1.40 f* = (p*b - q) / b = (0.612*1.40 - 0.388) / 1.40 = 33.3% per trade Но 41 trades/day × 33% = 1,365% BP → НЕВОЗМОЖНО Реально: Full Kelly distributed: Per trade f = 33% / sqrt(41 trades) = 5.2% Per trade $ = 5.2% × $100K = $5,200 Текущее sizing A+ = $1,500 (1.5% BP) → Half-Kelly х 3 = Quarter-Kelly
Вывод: Вы недосайжены в 3-4 раза ПО KELLY. НО — Kelly на переоценённом Sharpe переоценивает sizing. С истинным Sharpe 4 правильный sizing близок к текущему. Не увеличивай sizing пока live Sharpe не подтверждён.
Positive autocorr → winning days группируются, losing тоже. Max streak 6 дней loss = реальная проблема для психики трейдера и для margin.
| Scenario | Linear DD | 10x size DD | Compound 3x DD |
|---|---|---|---|
| Max streak × avg loss | -$2.4K | -$24K | -$72K |
| Psychological limit (retail) | OK | Hard | Ruined |
Sharpe 8.48 не защищает от 6-day streak. При scaling до $1M BP (4x sizes) → $24K DD нормально. При compound 3x → blow-up risk.
LONG WR 67.1%, SHORT WR 54.3%. Разница 12.8pp.
SHORT P&L = $39,874 / 15,248 trades = $2.61 per short trade After 15bps slippage + locate fee $0.02/sh: avg SHORT trade cost ≈ $2-3 Net SHORT edge ≈ $0 после realistic costs LONG P&L = $132,514 / 16,979 = $7.80 per long trade After 15bps: avg LONG cost ≈ $1.50 Net LONG edge = $6.30
Action: рассмотри LONG-only вариант (как старый M6). Потеряешь diversification, но SHORT может быть value-destructive после realistic costs. Сделай A/B test: M9d-LongOnly vs M9d-Both.
Текущий sizing:
| Grade | Size | Implied WR | Actual WR |
|---|---|---|---|
| A+ | $1,500 (1.5%) | ≥70% | 74% |
| A | $1,000 (1.0%) | 65-70% | 65% |
| B+ | $500 (0.5%) | 55-60% | 56% |
Ratio 3:2:1 (A+:A:B+). Kelly predicts оптимально ~4:2:1 (A+ больше, B+ меньше). Текущий B+ $500 почти не приносит edge после costs.
Альтернатива: A+ = $2,000, A = $1,000, B+ = $0 (skip). Или risk-parity: все equal по $1K риска.
Модель учится на TRAIN parquet с 222 columns, pre-computed факторами. В 9:28 live нужно вычислить те же 222 features за секунды.
Риски:
| Feature | Train | Serving | Skew риск |
|---|---|---|---|
| SPY_gap | End-of-day Datum snapshot | Real-time 9:28 feed | Низкий (простой) |
| rel_auction | Computed from 9:28-9:30 imbalance | Need NASDAQ NOII feed | ВЫСОКИЙ |
| market_breadth | Post-market count | Мало кто считает в 9:28 | ВЫСОКИЙ |
| stretch_925_pct | PM aggregation | Нужен точно тот же алгоритм | Средний |
| TTN tags (good_news etc) | Enriched parquet | TTN live API + composite logic | ВЫСОКИЙ |
Если live feature value ≠ train value даже на 5% → scoring ошибается → grade drift → wrong trades.
Fix: Сохранить snapshot features в 9:28 live, через неделю сравнить с back-filled TRAIN version тех же дат. Mean diff > 0.1 σ → skew confirmed.
В 01_data_builder.py:
# Line 432-434: gap_pct uses entry_930 gap_pct = (entry_930 - prev_close) / prev_close * 100 # Line 440: direction assigned from gap_pct direction = "LONG" if gap_pct > 0 else "SHORT" # Line 443: ret_dir uses direction ret_dir = np.where(direction=="LONG", ret_955, -ret_955)
Если entry_930 = close of 9:30 bar → direction определяется по данным из 9:30:59. Но "decision time" в модели = 9:28. Вы не можете знать direction в 9:28 если direction = f(9:30 data).
Это — training leak. Training учится предсказывать ret_dir, но direction сам коррелирует с ret_955 (одни и те же данные). WR искусственно завышен.
Verify:
u = pd.read_parquet('universe_scored_v4.parquet')
# pm-based direction (legit)
u['dir_pm'] = np.where(u.pm_close > u.prev_close, 'LONG', 'SHORT')
mismatch = (u.direction != u.dir_pm).mean()
print(f'Direction mismatch PM vs entry: {mismatch:.1%}')
# Если > 3% → real impact on live performance
Модель frozen 2026-04-19. Market regime меняется. PEAD weights, factor weights, ticker biases — всё устаревает.
Типичные signs of decay:
Proposal:
Monthly retrain: - recompute factor weights on expanding TRAIN - recompute ticker biases - A/B test: new vs frozen на следующий месяц - deploy new IF: OOS Sharpe(new) > Sharpe(frozen) × 0.9 - fallback: keep frozen, alert human
Нет production observability. Нужны dashboards:
| Metric | Alert Threshold | Action |
|---|---|---|
| Daily P&L vs expected | < -3σ for 3 days | Pause trading |
| Win Rate rolling 20d | < 55% (drops 6pp) | Retrain trigger |
| Slippage (actual vs model) | > 20bps | Review execution |
| Feature NaN rate | > 5% | Data pipeline alert |
| Scoring latency | > 30 sec | Infra alert |
| Grade distribution drift | KS test p<0.01 | Factor distribution changed |
Что делать если:
Каждый из этих сценариев случается 1-2 раза в месяц. Без runbook каждый раз = ad-hoc decisions → inconsistent execution → sharpe decay.
universe_scored_v4.parquet имеет 224 columns. Какая column из какого source script? Нет lineage docs. При debug проблемы невозможно быстро найти источник.
Fix: Column provenance metadata в parquet (pandas meta).
| # | Finding | Аудитор | Action |
|---|---|---|---|
| 1 | Posterior Sharpe 3-5 (shrinkage) | Bayesian | Plan для Sharpe 4, не 8 |
| 2 | Kurtosis 6.71 → fat tails не в Sharpe | Bayesian | Sortino + CVaR гораздо важнее |
| 3 | Sector concentration = hidden risk | PM | Max 8 positions/sector/day |
| 4 | SHORT edge ≈ 0 после costs | PM | A/B test LONG-only |
| 5 | Training-serving skew риск | ML | Live feature snapshot + weekly compare |
| 6 | Feature leakage через direction | ML | Recompute direction из pm_close |
| 7 | Нет model decay monitoring | ML | Monthly retrain + A/B gate |
| 8 | Failure modes не документированы | ML | Runbook для 6 сценариев |
| Backtest M9d | Round 1 corrections | Round 2 corrections | |
|---|---|---|---|
| Sharpe raw | 8.48 | 3.0-5.0 | 3.0-4.5 |
| Sortino | n/a | n/a | 2.0-3.0 (fat tails) |
| Avg daily ($100K BP) | $234 | $120-180 | $100-150 |
| Annual return | 56% | 30-45% | 25-40% |
| Max 6-day DD realistic | -$2.4K | -$4K | -$6-10K (fat tail) |
| Probability live Sharpe > 4 | Unknown | 50% | 35-50% |
M9d = viable production strategy, но с оговорками:
Даже на worst-case (Sharpe 2, 25% annual) — эта стратегия всё ещё лучше 95% institutional funds. Не упусти возможность из-за перфекционизма, но и не переразмерься на иллюзии Sharpe 8.