Arquitectura · A3 · Dominio y servicio
La regla que mantiene el cálculo reutilizable: cada función de dominio es una unidad aislada y pura —recibe datos, devuelve datos— sin saber de la API, el disco ni el run. El servicio es el que coordina, lee y escribe.
Nunca se mezclan. Si algo se puede sacar a una función pura, va al dominio; si coordina o toca el mundo exterior, es del servicio.
El servicio traduce entre los schemas Pydantic del contrato y las entradas primitivas (arrays, DataFrames) que el dominio espera. La concurrencia (un filelock por (run_id, paso)) y el marcador is_stale —que avisa, sin borrar, cuando re-ejecutás un paso anterior— son responsabilidad del servicio, nunca del dominio.
Toda función en domain/ las cumple. Juntas garantizan que se pueda invocar desde cualquier lado sin arrastrar el resto del sistema.
np.ndarray, DataFrame, int, dict — no objetos Pydantic del schema, no run_id, no requests.dict, dataclass, DataFrame o primitivo — no una respuesta HTTP.seed parametrizable.domain/{modulo}/{funcion}.py en vez de un utils.py gigante.La tentación es meter el cálculo en el servicio "porque es más rápido escribir todo junto". El resultado es lógica imposible de reusar o testear sin la API.
Cada compute_* vive en su propio archivo del dominio, con su test. El servicio solo lee, arma la entrada y compone la salida.
La pureza del dominio no es purismo: compra cuatro cosas concretas.
| Beneficio | En la práctica |
|---|---|
| Reusabilidad cross-step | La misma función (ej. losers_ratio) sirve al refinamiento y al transfer sin duplicar. |
| Análisis ad-hoc | El trader abre un notebook, importa la función y la corre sobre datos exploratorios sin levantar la API. |
| Tests rápidos | Validar un cálculo no necesita FastAPI ni disco: genera arrays, corre, chequea. |
| Refactor local | Cambiar cómo se calcula algo toca solo su archivo del dominio y su test — no el servicio ni las rutas. |
El webapp es la capa visible de un pipeline de descubrimiento cuantitativo. Si introdujera uno de los cinco antipatrones clásicos, invalidaría todo lo que está arriba. La arquitectura los frena activamente, en el código.
| Antipatrón | Dónde lo frena la arquitectura |
|---|---|
| Data Leakage | Un checker escanea filtraciones; un CRITICAL fuerza REJECT — el endpoint devuelve 422 y NO persiste el resultado. |
| Look-Ahead | shift(1) en las señales rolling; el HTF se desplaza para estar disponible solo desde su cierre. |
| Data Snooping | Walk-forward: test de permutación por combo + corrección FDR-BH sobre los p-values. |
| Overfitting | Splits IS/OOS estrictamente temporales; el webapp no permite optimizar hiperparámetros sobre OOS. |
| Selection Bias | Se reporta el conjunto evaluado completo — el grid entero de calibración, la lista completa de instrumentos (incluidos los sin datos). |
Que estos guards vivan en el dominio (puro y testeable) es lo que permite probar cada uno de forma aislada: un test verifica que un CRITICAL fuerza REJECT sin levantar el servidor. La integridad no es una promesa — es un test.
Viene de A2 (fuente de verdad). Sigue en A4: el walkthrough de 9 pasos para agregar un módulo nuevo de punta a punta.
Fuente: webapp/ARCHITECTURE.md §1.5 (antipatrones), §5.5 (dominio vs servicio).