Lifecycle Manager

The slow clock: scheduled events and watchdogs

Trading systems need to handle events that don’t fit the tick loop:

  • Options expire on specific dates
  • Prediction markets resolve at fixed times
  • Positions need rolling before expiry
  • Stop losses can fire between ticks
  • Margin watchdog runs continuously
  • Portfolios need scheduled rebalancing

These all live in the Lifecycle Manager: the slow clock alongside the fast tick loop.

Two clocks

Fast clock (every tick)           Slow clock (event-driven + schedule)
─────────────────────            ──────────────────────────────────
Data → Features → Signal  ←→     Lifecycle Manager
→ Portfolio → Execution           - Scheduled: expiry, resolution, rebalance
→ Risk → Venue → Fills            - Watchdog: stops, drawdown, margin
                                  - Triggered: kill switch, stop events

The two clocks share state (the ledger, the signal store) but run on their own timelines. The tick loop doesn’t wait for the watchdog; the watchdog doesn’t wait for the tick.

LifecycleEvent

Events are discriminated-union-style dataclasses:

python
from horizon.lifecycle import LifecycleEvent, LifecycleEventKind

event = LifecycleEvent(
    kind=LifecycleEventKind.StopLoss,
    market_id="AAPL",
    fires_at=now,
    payload={"position_qty": 10, "last_price": 95.0},
    reason="stop loss breached",
)

Event kinds

KindFired byHandled by
StopLossWatchdogEmit forced-close market order
DrawdownGuardFireWatchdogSet active drawdown action
MarginWarning / MarginReduce / MarginEmergencyWatchdogAlert / reduce / flatten
KillSwitchFireWatchdogFlatten + halt
OptionExpirySchedulerSettle contract (Phase 6 follow-up)
PredictionResolutionSchedulerCredit cash on 0/1 payout (Phase 6 follow-up)
RebalanceSchedulerRe-run portfolio optimizer
OptionRollScheduler + watchdogClose current, open new
DividendAccrualSchedulerCredit cash
FundingSettlementSchedulerPerp funding P&L
CompoundingSchedulerEquity-relative sizing update

LifecycleConfig

User-facing configuration for the manager:

python
from datetime import timedelta
from horizon.lifecycle import LifecycleConfig

lifecycle = LifecycleConfig(
    rebalance_schedule="weekly",                  # daily | weekly | monthly | never
    option_roll_dte=7,                             # roll options with < 7 DTE
    option_roll_delta=0.5,                         # roll when delta crosses 0.5
    option_max_abs_delta=0.8,                      # force close if |delta| > 0.8
    auto_close_before_resolution=timedelta(hours=4),
    compound_profits=True,
)

Watchdog integration

The watchdog is the part of the lifecycle manager that runs on every tick (not on a schedule). The backtest loop calls it after portfolio optimization but before execution:

python
# run.py:
watchdog_events = risk_engine.watchdog(risk_state)
for event in watchdog_events:
    if event.kind.value == "stop_loss":
        # Forced close
        pos = ledger.position(event.market_id)
        forced_close_actions.append(
            OrderAction.place(
                market_id=event.market_id,
                side="sell" if pos.quantity > 0 else "buy",
                quantity=abs(pos.quantity),
                order_type="market",
                urgency=Urgency.Immediate,
            )
        )

Stop-loss events become market orders added to the planned-actions queue. They go through the normal pre-order risk pipeline (which always passes them because the kill switch isn’t engaged and they’re reducing, not opening).

Drawdown guards as lifecycle events

A drawdown breach doesn’t directly place orders; it sets a mode on the engine that the pre-order checker reads:

python
# Risk engine:
if dd_pct >= threshold and not is_active:
    self._active_guards[guard_id] = state.now
    events.append(LifecycleEvent(
        kind=LifecycleEventKind.DrawdownGuardFire,
        payload={"action": guard.action.value, ...},
    ))

Then in check_order:

python
if state.drawdown_halt_active == "halt_new":
    if self._is_opening(action, state):
        return Decision.reject("drawdown halt_new active")
elif state.drawdown_halt_active == "flatten":
    return Decision.reject("drawdown flatten active")

New opens get blocked; reduces are allowed.

Scheduled events

The manager holds a priority queue of future events:

python
from horizon.lifecycle import LifecycleManager

lcm = LifecycleManager(config=lifecycle_config)

# Schedule a specific event
lcm.schedule_option_expiry(
    market_id="AAPL250117C00180000",
    fires_at=datetime(2025, 1, 17, 16, 0),
)

# Per tick, advance the clock
events = lcm.advance_clock_to(now, state)
for event in events:
    lcm.handle(event, state)

When the loop advances past the scheduled time, the event is popped and its handler runs.

Handler registration

Custom handlers:

python
def my_handler(event: LifecycleEvent, state) -> list:
    # Do something, return follow-up OrderActions
    return []

lcm.register_handler(LifecycleEventKind.OptionRoll, my_handler)

The built-in handlers for stop losses and drawdown guards live in the run loop itself; custom handlers can override them or add new behavior.

Backtest parity

A critical promise: the lifecycle manager runs in backtest mode exactly as it does in live.

  • Scheduled events in backtest fire when simulated time passes them (priority queue)
  • Watchdogs run on every tick in both modes
  • Stop losses fire at the tick price that breached the threshold, not at some averaged price

This means: if your backtest shows the strategy got stopped out at 12:45 on day 37, that’s when it would have been stopped out in live trading with the same data.

What’s real today

ComponentStatus
Scheduler + priority queue✅ Real
Stop-loss watchdog✅ Real, fires in backtest
Drawdown-guard watchdog✅ Real, sets halt mode
Margin watchdog✅ Real (requires buying-power snapshot)
Kill switch auto-trigger✅ Real
Option expiry handler⏸️ Scaffold. awaits options module integration
Prediction resolution handler⏸️ Scaffold. awaits prediction module integration
Rebalance scheduler⏸️ Scaffold. infrastructure ready, handler placeholder
Dividend / funding / compounding⏸️ Scaffolds

The infrastructure is stable; the remaining pieces are fill-in work following the phase plan.

Next