Estrategia vigente: sma_cross
Cruce de medias móviles con filtro de tendencia, sobre barras diarias, solo posiciones largas, una posición por símbolo. Elegida por ser explicable y tener pocos parámetros. No se espera que sea rentable por sí sola: su función es validar toda la tubería (datos → señal → riesgo → orden).
Los valores concretos de cada parámetro se leen de .env / config.py; el panel web los muestra
a la derecha de esta página. Los nombres son los de las variables de entorno.
Entrada (BUY)
Se cumple todo a la vez, evaluado al cierre de la barra diaria:
SMA_FASTcruza por encima deSMA_SLOW, o lo hizo dentro de las últimasENTRY_WINDOWbarras y sigue por encima.- El cierre está por encima de
SMA_TREND(filtro de tendencia de largo plazo). - El volumen del día supera
VOLUME_FILTER_RATIO× su media de 20 días.
La orden se ejecuta en el primer ciclo con mercado abierto tras la señal, como orden de mercado
con bracket: stop-loss a STOP_ATR_MULT × ATR(ATR_PERIOD) bajo el precio y take-profit a
TAKE_PROFIT_ATR_MULT × ATR por encima.
Salida (SELL)
Cualquiera de estas:
SMA_FASTcruza por debajo deSMA_SLOW.- El precio toca el stop-loss (bracket en Alpaca; en backtest se simula con el mínimo del día).
- El precio toca el take-profit (bracket; en backtest con el máximo del día).
- Han pasado
MAX_HOLD_DAYSdías hábiles desde la entrada.
EXIT_MODE elige entre take_profit (bracket con stop y take-profit fijos, el actual) y
trailing (solo stop, que sube con el máximo alcanzado menos STOP_ATR_MULT × ATR; en vivo se
actualiza la pata stop del bracket una vez al día). El backtest de cartera compara ambos; el modo
vigente se cambia solo con ese reporte.
Regla de empate en backtest: si en la misma barra el mínimo toca el stop y el máximo toca el take-profit, se asume que el stop se ejecutó primero (lo conservador).
Ejecución
La señal se calcula sobre el cierre, pero la orden se envía a las 9:35 ET del día siguiente. En
ese momento se vuelve a leer el último precio y se recalculan stop, take-profit y cantidad con él.
Si el precio se ha alejado más de MAX_GAP_PCT del cierre de la señal, la entrada se descarta.
Las salidas por cruce o por tiempo cancelan primero las patas del bracket y luego venden la
cantidad real que Alpaca reporta en la posición.
Tamaño de posición
cantidad = min( equity × RISK_PER_TRADE_PCT / (precio − stop) , equity × MAX_POSITION_PCT / precio )
Es decir, se arriesga como máximo un porcentaje fijo del equity por operación, medido como distancia al stop, y ninguna posición supera un porcentaje fijo del equity.
Reglas de riesgo que pueden vetar una señal
MAX_OPEN_POSITIONS, DAILY_LOSS_LIMIT_PCT, MAX_DRAWDOWN_PCT, MAX_DAY_TRADES,
mercado abierto según el reloj de Alpaca y kill switch. Cada regla es una función pura con su test
en tests/test_risk_rules.py.
Versiones de la estrategia
| Versión | Fecha | Cambio | Backtest |
|---|---|---|---|
| 1.0 | 2026-09-10 | Cruce exacto en la barra del cruce | (sin reporte guardado) |
| 1.1 | 2026-09-10 | Entrada permitida hasta ENTRY_WINDOW barras tras el cruce |
PORTFOLIO-IS_2020-07-27_2022-12-30.md, PORTFOLIO-OOS_2021-11-29_2026-09-10.md |
Regla: cada fila nueva exige un reporte en docs/backtests/ y una entrada en CHANGELOG.md.
Nota sobre datos: el plan gratuito de Alpaca (feed IEX) entrega barras diarias desde el 27-07-2020, no desde 2019. Los backtests "desde 2019" empiezan en realidad en esa fecha.
Lectura del primer reporte (v1.1, take_profit): fuera de muestra +14.6 % con drawdown máximo
−2.9 % y 93 operaciones, frente a +63.1 % de SPY. Protege capital pero no bate al índice; el modo
trailing fue mejor en muestra y peor fuera de muestra, así que se mantiene take_profit.
Lo que esta estrategia no hace
- No opera en corto ni con apalancamiento.
- No usa datos intradía para decidir, solo para ejecutar.
- No ajusta parámetros por símbolo. Cualquier optimización se hace en backtest con
--splity se documenta antes de desplegarse.