Seis subagentes de Claude Code que imponen disciplina anti-overfitting en la investigación de estrategias cuantitativas.
La idea no es que los agentes sean más listos. Es que el proceso no permita autoengañarse: la hipótesis se pre-registra antes de tocar datos, cada fase deja un artefacto en disco, y un auditor independiente con poder de veto decide si el resultado avanza o vuelve atrás.
Pensado para cripto perpetuos, pero la mecánica es general.
git clone https://github.com/glender222/quant-agent-team.git
cd quant-agent-team
./install.sh --dry-run # ver qué haría, sin escribir nada
./install.sh # instalar
./tests/run.sh # 58 comprobaciones sobre los hooks- El pipeline
- Los agentes
- Por qué funciona
- Los hooks
- Alcance real de los hooks de protección
- Instalación
- Estructura del proyecto destino
- Tests
- Ajustes
flowchart LR
R["<b>research-quant</b><br/>hipótesis pre-registrada"]
D["<b>data-backtest-quant</b><br/>data card + backtest"]
M["<b>modeling-quant</b><br/>model card"]
C{"<b>critic-quant</b><br/>GATE"}
K["<b>risk-portfolio-quant</b><br/>sizing + runbook"]
R --> D
D --> M
M --> C
D -. "estrategia de reglas<br/>(salta el modelado)" .-> C
C -- "PASS" --> K
C -- "FAIL" --> R
classDef gate fill:#7f1d1d,stroke:#ef4444,stroke-width:2px,color:#fff
classDef stage fill:#1e293b,stroke:#64748b,color:#e2e8f0
class C gate
class R,D,M,K stage
quant-manager orquesta las cinco fases y no ejecuta trabajo técnico: delega,
lee artefactos para decidir el siguiente paso y hace cumplir los gates.
Una estrategia de reglas puede saltarse el modelado. Lo que no se salta nunca
es el gate del crítico: con FAIL, la hipótesis vuelve a la fase que corresponda
o se archiva. Nunca se empuja hacia adelante.
| Agente | Rol | Modelo | Effort | Artefactos |
|---|---|---|---|---|
quant-manager |
Orquesta el pipeline, delega y hace cumplir los gates. | fable |
max |
— |
research-quant |
Hipótesis pre-registrada con racional económico y criterio de falsación. | opus |
max |
hypotheses/ |
data-backtest-quant |
Datasets point-in-time, features y backtests con costes reales. | sonnet |
max |
data/ · backtests/ |
modeling-quant |
Modelos y señales sobre el dataset ya preparado. | opus |
max |
models/ |
critic-quant |
Auditor independiente. Veredicto PASS / FAIL con veto. |
opus |
max |
verdicts/ |
risk-portfolio-quant |
Sizing desde el riesgo, stress testing y runbook de despliegue. | opus |
max |
risk/ · runbooks/ |
Herramientas de cada agente
| Agente | tools |
|---|---|
quant-manager |
Agent(…), Read, Grep, Glob, TodoWrite |
research-quant |
Read, Grep, Glob, WebSearch, WebFetch, Write |
data-backtest-quant |
Read, Grep, Glob, Bash, Write, Edit, NotebookEdit |
modeling-quant |
Read, Grep, Glob, Bash, Write, Edit, NotebookEdit |
critic-quant |
Read, Grep, Glob, Bash, Write — con disallowedTools: Edit |
risk-portfolio-quant |
Read, Grep, Glob, Bash, Write, Edit |
Los cinco especialistas llevan memory: user: su memoria persiste entre
proyectos, así que el catálogo de trampas de cada fuente de datos, de patrones de
leakage y de hipótesis ya falsadas se acumula en vez de perderse.
| Skill | Contenido |
|---|---|
quant-rules |
Las diez reglas invariantes del equipo, precargadas en cada agente vía el campo skills del frontmatter. |
quant-validation |
Leakage temporal (purged K-Fold, embargo, CPCV), deflación por multiple testing (DSR, PBO, t-stat de Harvey-Liu-Zhu), mecánica de perpetuos y cobertura de régimen. |
El crítico es independiente y no puede tocar lo que audita.
Lleva disallowedTools: Edit y un hook que solo le deja escribir en verdicts/
(y en su propia memoria persistente). No puede "arreglar" el código para que
pase: solo puede dictaminar.
El veredicto es vinculante.
Sin un VERDICT-*.md con PASS, el agente de riesgo devuelve la tarea. Con
FAIL se retrocede.
El orquestador no ejecuta. Quien decide no es quien produce el número. Eso quita el incentivo de que el resultado salga bonito.
Cada fase deja un artefacto en disco. Los subagentes no comparten contexto: el handoff es por archivos. Todo queda escrito, fechado y auditable — y el pre-registro es real, porque el documento de hipótesis existe antes de que nadie mire los datos.
N se cuenta.
Cada corrida añade una línea a experiments/LOG.md. El crítico cuenta N desde
ahí y deflacta con ese número. Sin log, N es desconocido y eso ya es un FAIL.
Tres hooks PreToolUse en hooks/quant/, declarados en el frontmatter de cada
agente.
| Hook | Matcher | Declarado en | Qué hace |
|---|---|---|---|
protect-data.sh |
Write|Edit|MultiEdit|NotebookEdit |
data-backtest-quant, modeling-quant, risk-portfolio-quant |
Bloquea escrituras sobre datos protegidos |
write-only-in.sh <dir> |
Write |
research-quant → hypotheses, critic-quant → verdicts |
Allowlist: un solo directorio de escritura |
block-live-endpoints.sh |
Bash |
los cuatro agentes con Bash |
Bloquea mainnet y acceso a secretos |
Bloquea escrituras bajo data/raw/ (inmutable) y data/holdout/ (sellado) en
cualquier proyecto, y extiende ese perímetro con la regex ERE de la primera línea
de <proyecto>/.claude/quant-protected.regex, si existe. La raíz se resuelve con
${CLAUDE_PROJECT_DIR:-$PWD}, para que la política del proyecto se aplique
aunque el cwd del proceso del hook no sea la raíz.
Como compara cadenas, normaliza la ruta antes de decidir: colapsa //, ./ y
componente/.., de modo que data/processed/../raw/x.csv no se cuela por no
contener literalmente data/raw/. Compara la forma cruda y la normalizada y
bloquea si cualquiera hace match.
Note
El efecto es que una ruta que atraviesa una carpeta protegida para acabar
fuera de ella también se bloquea. Es un falso positivo asumido a propósito: el
mensaje muestra la ruta normalizada y corregirlo es una línea, mientras que el
error inverso sería una escritura silenciosa dentro de data/raw/.
Si la regex del proyecto no es una ERE válida, el hook bloquea y lo dice, en vez de dejar pasar la escritura en silencio.
Allowlist de escritura: dice qué se permite, no qué se prohíbe. Lleva una
excepción explícita para ${CLAUDE_DIR:-$HOME/.claude}/agent-memory/, porque los
agentes con memory: user escriben ahí y sin la excepción el hook los empujaría
a escribir su memoria desde Bash — justo el vector que este diseño quiere evitar.
Por ser un allowlist compara solo la ruta normalizada: si comparase también
la cruda, verdicts/../../etc/passwd pasaría por contener la subcadena
permitida. Y si se invoca sin directorio permitido —un typo en el frontmatter—
bloquea en vez de abrirse entero.
Bloquea endpoints de trading real (mainnet) y accesos a secretos (.env,
secrets/, API_KEY…). Testnet queda permitido a propósito. Inspecciona el JSON
crudo del payload, lo que es intencionalmente sobre-inclusivo: prefiere un falso
positivo a dejar pasar una orden con dinero real.
Important
Lee esta sección antes de confiar en el diseño para un proyecto con dinero
real. La pared de verdad la impone el kernel, no un grep.
protect-data.sh intercepta únicamente las herramientas de escritura de archivos
(Write, Edit, MultiEdit, NotebookEdit): bloquea rutas bajo data/raw/ y
data/holdout/ (más las que añada el .claude/quant-protected.regex del
proyecto) cuando el agente las escribe con esas herramientas. No inspecciona los
comandos de Bash, así que una redirección >, un cp/mv/rm/tee/sed -i
o un python -c "open(..., 'w')" escriben en esas rutas sin que el hook
intervenga; y tampoco cubre la lectura del holdout sellado, porque leer no es
escribir.
La barrera contra el uso del holdout es, por tanto, de intención: vive en las
instrucciones de los agentes y en la skill quant-rules, no en el sistema de
permisos.
Deliberadamente no intentamos parchearlo inspeccionando el texto de los comandos:
cualquier lista de patrones sobre shell arbitrario es evadible (variables, eval,
base64, subshells, intérpretes que escriben desde dentro de un script) y el precio
en falsos positivos sobre el trabajo legítimo de un ingeniero de datos es alto.
Si necesitas una garantía y no un guardarraíl, séllalo en el sistema de
archivos antes de arrancar la sesión y ábrelo tú, a mano, una sola vez, después
de un veredicto PASS:
chmod -R a-w data/raw # inmutabilidad, impuesta por el kernel
chmod -R a-rwx data/holdout # el sello: ni leerdata/holdout/ debe quedar sin permiso de lectura para el usuario que corre los
agentes — o montado en solo lectura desde fuera. Cualquiera de las dos cubre de
forma uniforme redirecciones, sed -i, intérpretes, heredocs y también las
lecturas, porque no depende de reconocer texto.
Dos precisiones sobre el borde del matcher
- El matcher se declara explícito:
"Write|Edit|MultiEdit|NotebookEdit". Así el hook cubre también los cuadernos quedata-backtest-quantymodeling-quanttienen entre sus tools, sin depender de si tu motor de hooks hace match por subcadena o anclado. Las herramientas que un agente no declara entools:simplemente nunca disparan ese matcher. - El
settings.jsonde tu proyecto puede añadir una capa depermissions.denypor encima de los hooks (verexamples/settings.example.json). Esa capa es del entorno, no de este repo, y no viaja con los archivos que instalas aquí.
./install.sh --dry-run # muestra qué haría, sin escribir nada
./install.sh # instalaCopia agents/, hooks/quant/ y skills/ a ${CLAUDE_DIR:-$HOME/.claude}. Es
idempotente: si un archivo de destino ya existe y difiere, guarda un
.bak.<timestamp> antes de sobrescribir y te lo dice.
Warning
Nunca toca tu settings.json. Si quieres la capa de permissions.deny,
cópiala tú desde examples/settings.example.json.
Con --claude-dir RUTA (o la variable CLAUDE_DIR) puedes instalar en otro
sitio. En ese caso reescribe las rutas de hooks del frontmatter de los agentes,
que por defecto apuntan a ~/.claude/hooks/quant/: sin esa reescritura los hooks
no existirían en el destino, y un hook que no arranca sale 127 — que no
bloquea, así que la instalación quedaría desprotegida sin avisar.
| Archivo de ejemplo | Copiar a | Para qué |
|---|---|---|
examples/quant-protected.regex |
<proyecto>/.claude/quant-protected.regex |
Ampliar el perímetro de datos protegidos |
examples/settings.example.json |
<proyecto>/.claude/settings.json |
Capa de permissions.deny sobre los hooks |
Los agentes crean lo que necesiten al escribir su primer archivo.
tu-proyecto/
├── .claude/
│ ├── settings.json # opcional
│ └── quant-protected.regex # opcional
├── hypotheses/ HYP-NNN-slug.md ← research-quant
├── data/
│ ├── raw/ INMUTABLE — nadie escribe aquí
│ ├── holdout/ SELLADO — nadie lee, hasta el PASS
│ ├── processed/ toda transformación va aquí
│ └── DATA_CARD-*.md ← data-backtest-quant
├── backtests/ BT-NNN.md ← data-backtest-quant
├── models/ MODEL_CARD-*.md ← modeling-quant
├── verdicts/ VERDICT-NNN.md ← critic-quant
├── risk/ RISK_CARD-*.md ← risk-portfolio-quant
├── runbooks/ RUNBOOK-*.md ← risk-portfolio-quant
└── experiments/
└── LOG.md una línea por corrida — de aquí sale N
./tests/run.shEjercita los tres hooks con payloads JSON sintéticos: perímetro genérico,
travesía de rutas, política por proyecto con regex válida e inválida, falsos
positivos que deben pasar, allowlist mal configurado y la excepción de memoria.
Corre la suite entera dos veces, con jq y con la detección de jq desactivada,
para cubrir también el camino de respaldo.
Es hermético: solo escribe en un directorio temporal propio y no toca tu $HOME.
Si cambias un hook, esta suite es la que decide si el README sigue siendo cierto.
Los model: y effort: del frontmatter son una elección, no un requisito.
data-backtest-quant va en sonnet porque su trabajo es mecánico y de volumen;
el resto en opus porque el juicio importa más que el throughput. Cámbialos según
tu presupuesto y tu latencia — nada más del diseño depende de esa elección.
MIT. Ver LICENSE.