Golden SkyGolden SkyGuía Técnica

Referencia técnica · Paso 10 · Walk-Forward Optimization

Re-descubrimiento y eficiencia

La prueba más exigente del descubrimiento: en vez de validar un combo fijo, el WFO re-corre todo el discovery dentro de cada fold y pregunta si el mismo concepto reaparece en varias ventanas. Un edge real se re-descubre solo; uno espurio, no.

§0Notación

signatureconcepto = bases de indicador, sin umbrales
n_discn_folds_discovered (folds distintos)
pooled PFΣgp / Σgl sobre los folds
efficiencypooled_test_pf / pooled_train_pf
embargogap train→test = max_holding
ISin-sample (development), sin OOS

§1WFO no valida un combo — re-descubre un concepto

La diferencia con el walk-forward (§9): allí un combo fijo se testea por folds. Acá, dentro del train de cada fold, se vuelve a correr el discovery completo (lift + combinaciones) desde cero.

por cada fold: discovery( solo el train del fold ) → combos re-descubiertos
¿el mismo concepto aparece en varios folds? → n_folds_discovered
// corre sobre el IS (development) — NUNCA toca la ventana OOS reservada

La pregunta cambia de "¿este combo aguanta?" a "¿el proceso vuelve a encontrar esta idea cuando le doy otra ventana?". Reproducibilidad temporal del descubrimiento, no de un resultado puntual.

§2Qué es un "concepto"

Dos combos con las mismas familias de indicador pero distintos umbrales son la misma idea. La signature semántica captura eso.

semantic_signature signature = sorted( base(regla) por cada regla del combo )  // bases, sin umbrales
rsi<30 AND sma_slope>0.1  →  { rsi, sma_slope }
rsi<25 AND sma_slope>0.3  →  { rsi, sma_slope }  // mismo concepto

Se reusa semantic_signature del dedup de combinaciones — misma definición de "regla" que produjo el pool IS. n_folds_discovered cuenta folds distintos: un fold que halla el concepto dos veces cuenta 1.

§3Aislamiento leakage-safe

Re-correr el discovery por fold es peligroso: si una sola fila de test se cuela en el train, el edge se fabrica. Cuatro guardas lo impiden.

GuardaCómo
Discovery restringido al trainSe slicea df_indicators / df_binary a [train_start, train_end) antes de llamar al discovery. La restricción es estructural — físicamente no recibe filas de test.
EmbargoEl fold se arma con gap = max_holding: los targets forward-looking de las últimas barras de train caen en el gap, nunca en la test-window.
Params idénticosSe reusan los mismos params de lift/combinations del pipeline. WFO solo fuerza discovery_train_ratio=None (el fold YA es el train — no aplica otro split interno).
df_binary conservadorLa frecuencia se calculó sobre el IS completo; el filtro técnico per-fold (min_signals sobre train) descarta las reglas raras. Residual conservador: a lo sumo omite alguna, nunca fabrica edge.

§4Selección por train, reporte por test

El corazón anti-snooping del WFO: qué decide que un concepto pase, y qué se limita a informar.

La selección usa solo el train. Un concepto "pasa" por su n_folds_discovered — cuántas veces el discovery lo re-encontró en las ventanas de train. La performance en las test-windows se calcula y se REPORTA, pero nunca entra a la selección. Si el test decidiera qué pasa, estaríamos snoopeando el test — exactamente lo que WFO existe para evitar.

Por eso match_signatures devuelve el set completo de conceptos evaluados (sin filtrar por min_folds) — el gate lo aplica el caller. Reportar todo lo evaluado, no solo los ganadores, es la defensa contra el selection bias.

§5Scoring en las test-windows

Para cada concepto, se backtestean sus reglas concretas en la test-window de cada fold donde se re-descubrió, con el mismo motor que todo el resto (misma exit policy, costos, triple-swap, filtro de régimen).

score_concepts pnls de test de todos los folds → pooled: pooled_test_pf = Σ gptestΣ gltest // no promedio de PFs
wfo_efficiency = pooled_test_pfpooled_train_pf  // cuánto degrada de train a test

El pooling suma las ganancias y pérdidas reales antes de dividir (mismo criterio que el walk-forward, audit #1 — promediar razones puede invertir el signo). wfo_efficiency cerca de 1 = el edge se sostiene fuera del train; muy por debajo = degradó.

§6Gate y concentración

Un concepto pasa si se re-descubrió lo suficiente Y su performance test pooleada supera el umbral. min_folds_discovered y min_profit_factor son parámetros de entrada — abajo se muestran sus valores por defecto (config completa en §7).

passed ⟺ n_folds_discovered ≥ min_folds_discovered (default 3)
  Y pooled_test_pf ≥ min_profit_factor (default 1.15)
  Y test_total_trades > 0
El pooling agrega tamaño, no revela concentración. Por eso se reportan (sin ser gate): test_pf_per_fold, test_pf_std, test_pf_min y n_losing_folds (folds con trades donde PF<1). Un pooled bueno con pf_min ≪ 1 o muchos losing_folds = un fold outlier lo sostiene → edge no robusto. Es lectura del trader, no un veredicto automático.

§7El cuello de botella es el train

Un WFO que "no re-descubre" puede estar diciendo dos cosas muy distintas: no hay edge, o no hay datos suficientes para encontrarlo.

min( train_size por fold ) < TRAIN_MIN_VIABLE (800 barras)warning

Debajo de ese piso, el discovery (lift + combinations con FDR) pierde poder estadístico: n_folds_discovered baja por falta de datos, no por ausencia de edge. El binding constraint del WFO es el train por fold — la salida es bajar n_folds o ampliar el IS, no aflojar el gate.

Configuración

n_foldsdefault 5 (ge 2)
methoddefault rolling (o expanding)
train_ratiodefault 0.70 (el resto es test + embargo)
min_folds
_discovered
default 3
min_profit
_factor
default 1.15 (≥ rigor del WFV)
top_n
_concepts
default None (opt) — limita el scoring

Viene de 9 (walk-forward / WFV). Sigue en 11: el test out-of-sample.

Fuentes:
· domain/walk_forward_optimization/rediscovery.py — match_signatures, RediscoveredConcept
· domain/combinations/dedup.py — semantic_signature (bases sin umbrales)
· services/walk_forward_optimization_service.py — discover_on_train, score_concepts, execute_wfo
· domain/validation_common/pooled_metrics.py — pool_fold_metrics
· webapp/docs/STEPS_10_12_MIGRATION_PLAN.md §2.1bis — diseño y puntos de aislamiento