# Execution Engine — Alpaca/multistrat Loop (Sub-project C1) — Design

**Date:** 2026-07-09
**Status:** Approved (brainstorming) — ready for implementation plan
**Sub-project:** C1 — the first slice of the execution plane (layer 5) of the "unified data-driven pipeline"
north-star. C2 (Bybit venue) + C3 (real-money arming / multi-venue aggregate) come after.
**Builds on:** A (deterministic forward state + `forward_record` observed machinery, merged) + B1 (gate/
promotion policy, merged) + B2a (allocation policy → `strategy_allocation` target dollars, merged).

## North-star context (layer 5, first slice)

The execution engine is a data-driven RECONCILIATION LOOP: given the desired state (B2a's target positions)
and the actual state (the venue account), compute the minimal orders to close the gap, place them, and record
the fills as observed data. C1 closes that loop for ONE venue/strategy — Alpaca / multistrat, on paper:

```
B2a strategy_allocation[multistrat] (target $)
  → BRIDGE:   envelope = min(target_$, account_NLV); per-instrument target $ = within_weight_i · envelope
  → RECONCILE: target vs Alpaca account → minimal orders   [reuse ExecutionService._plan / rebalance_weights]
  → PLACE:    fractional orders via AlpacaBroker (PAPER; --execute; safety gates)
  → FILLS:    executed-book equity delta → observed record for "multistrat_exec" (A's forward_record)
```

Guiding principle (mirror A/B1/B2a): the engine reconciles; it does NOT authorize capital. Capital stays
human-armed (Alpaca keys; live is a separate deliberate step) + ceiling-bounded (B2a). The reconciliation is
deterministic given (targets + account state).

**The problem C1 solves.** The execution legs exist (`execute-multistrat` CLI, `ExecutionService`,
`AlpacaBroker`, the safety gates hard-won from live paper trading) but do NOT consume B2a's per-strategy
dollar allocation — `execute-multistrat` sizes `multistrat.target_weights` against the full Alpaca account
NLV, ignoring how much capital B2a allocated to multistrat and never de-risking when B2a's killswitch/prune
zeroes the allocation. And the executed reality is never recorded back as an observed track, so the forward
gate can only ever rule on the MODELED book, not the real executed one.

## Goal

Make `execute-multistrat` B2a-driven: deploy exactly `strategy_allocation[multistrat]` dollars into the
multistrat book on Alpaca (flatten to cash when B2a allocates $0 or has no row), reconcile + place via the
existing `ExecutionService` (dry-run default), and record the executed book's daily return as an observed
`forward_record` for a new `multistrat_exec` track — so the real executed track is trackable and comparable
to the modeled one. Paper only; real capital stays human-armed.

## Components

### 1. The bridge — B2a dollars → target positions

Read `ForwardNavRepo.current_allocation()["multistrat"]` (B2a's target dollars; from `strategy_allocation`).
The **envelope** = `min(target_dollars, account_NLV)` (never over-deploy the account, never exceed B2a's
allocation). The within-book instrument weights come from `multistrat.target_weights(closes,
FUND_INSTRUMENTS)` (the book's own weights, unchanged). Per-instrument target dollars = `within_weight_i ·
envelope`.

- **B2a row absent, or `target_dollars == 0`** (killswitch flattened / marginal-gate pruned / not deploy-tier)
  → envelope = 0 → all target positions 0 → the book FLATTENS to cash. The engine de-allocates exactly when
  B2a de-allocates — the killswitch/prune propagates to real positions.

### 2. Reconcile + place — reuse `ExecutionService`

Feed the bridge output to the EXISTING `ExecutionService.rebalance_weights(name, weights, prices, execute)` by
passing `weights_i = target_dollars_i / account_NLV` (so the engine's `weight · NLV` sizing lands exactly on
`target_dollars_i`). `ExecutionService._plan` reconciles vs the Alpaca account, sizes FRACTIONAL (Alpaca
`supports_fractional = True`), and applies the hard-won safety gates (NLV-vs-cash consistency / `data_gap`,
`allow_live`, `max_gross`) before placing. **Dry-run is the default; `--execute` places.** C1 changes only
the DRIVER (the B2a envelope instead of full NLV) + the flatten-on-zero + the recording — the reconciliation
engine, adapter, and gates are unchanged.

### 3. Fills → the executed observed track (the loop closure)

After a real (`--execute`) run, compute the executed multistrat book equity **on B2a's envelope, NOT the full
account NLV**: `exec_equity = Σ(multistrat_position_qty · price) + reserve_cash`, where `reserve_cash =
envelope − deployed_notional` (deployed = Σ of the position notionals just targeted). Store the prior
`exec_equity` in A's observed book-state (`ForwardNavRepo.set_book_state("multistrat_exec", ...)`). The daily
executed return = `exec_equity_today / exec_equity_prior − 1`, appended via
`ForwardNavRepo.append_record("multistrat_exec", today, exec_return, at)` (A's append-only observed
`forward_record`). On the FIRST run there is no prior equity → record no return (inception, exactly like A).

**Load-bearing correctness invariant:** the executed return is measured on the B2a-allocated **envelope**,
not the full account NLV. Otherwise idle account cash (e.g. a $90k idle balance behind a $10k envelope)
dilutes the return ~10× and the modeled-vs-executed comparison is meaningless. A test pins this: a large
account with a small envelope yields an executed return reflecting the envelope's P&L, undiluted.

**Capital flows are not return (time-weighted):** the daily return is the P&L of the HELD positions since the
last rebalance (`marked_value_of_prior_positions_at_today's_prices / value_at_prior_rebalance − 1`), NOT the
raw equity delta. A B2a re-allocation (e.g. multistrat $10k → $15k) is a capital FLOW, not a gain — it enters
at the rebalance, not in the return. The plan specifies the exact computation; a test pins that a pure
re-allocation with flat prices records a ~0% return, not a jump.

### 4. Registry — the executed track

Add `multistrat_exec` to `STRATEGY_REGISTRY` as an OBSERVED track (`record_mode: "observed"`, venue
`alpaca-paper`, a `definition` per B1's shape, tier research). Its reconciliation backtest reference is the
MODELED multistrat's series (so B1's gate reconciles executed-vs-modeled — the executed track PASSes only if
it tracks the modeled book, catching execution decay). It flows through A's engine (observed mode) + B1's
gate + the cockpit like any other track, appearing beside the modeled `multistrat`.

### 5. Shape + safety

Keep the existing `execute-multistrat` CLI (run by the SUSPENDED `fxhnt-alpaca-rebalancer` CronJob when
armed) — deliberately SEPARATE from the nightly `cockpit_forward` (a broker hiccup must not taint the forward
track, mirroring why the Bybit testnet leg is its own job). C1 extends the CLI: read B2a's allocation as the
envelope, flatten-on-zero, and on `--execute` record the `multistrat_exec` observed track. Safety = the
existing `ExecutionService` gates + dry-run default. Real capital = `--live` + `allow_live` + live Alpaca keys
— a separate, deliberate human step (unchanged).

### Where the human stays in the loop

The human arms Alpaca keys (paper first; live separately) and sets the B2a capital ceiling. The engine
deploys exactly B2a's multistrat envelope, reconciles, and records — it never authorizes capital. Moving to
live is `--live` + `allow_live` + live keys, deliberately human.

## Error handling

- **No B2a row / `$0` allocation** → envelope 0 → flatten to cash (deploy nothing). Safe.
- **Account data inconsistent** (`data_gap`) → `ExecutionService` blocks (existing gate).
- **Live account without `allow_live`** → blocked (existing gate).
- **First `--execute` run** → no prior exec equity → record no return (inception), like A.
- **Envelope > account NLV** → envelope capped at NLV (never over-deploy the account).
- The executed return is on the ENVELOPE, never the full NLV (the correctness invariant).

## Testing

- **Bridge:** B2a $10k × within-weights → per-instrument target dollars sum to ≤ $10k; B2a $0 / absent row →
  all-zero targets (flatten).
- **Envelope basis (load-bearing):** a $100k account with a $10k B2a envelope → the executed return reflects
  the $10k book's P&L (undiluted by the idle $90k), NOT the full-account NLV return.
- **Capital flows excluded (load-bearing):** a pure B2a re-allocation ($10k → $15k) with FLAT prices records a
  ~0% executed return (the $5k is a capital flow, not a gain), not a jump.
- **Reconcile driver:** the bridge produces `weights_i = target_dollars_i / NLV` such that
  `ExecutionService` sizes to the intended target dollars (reuses the tested engine + gates; C1's diff is the
  driver).
- **Observed record:** after `--execute`, `multistrat_exec` gets an observed `forward_record`; the first run
  is inception (no return); a flatten run sells the book to cash and the exec equity reflects it.
- **Dry-run default:** without `--execute`, no orders are placed and no record is written.
- **Registry:** `multistrat_exec` is an observed track with the modeled multistrat as its backtest ref.

## Out of scope (YAGNI)

No Bybit venue (C2 — where per-fill slippage decomposition belongs, since crypto slippage matters and the
`bybit_testnet` leg already has it). No per-fill slippage decomposition for Alpaca (commission-free fractional
ETFs → net equity-delta suffices). No real-money live path beyond the existing `allow_live` gate. No
multi-venue aggregate reconciliation (C3). Reuses `ExecutionService`, `AlpacaBroker`, and A's observed
`forward_record`/book-state machinery.

## Affected code (initial map — the plan refines this)

- Modify: `src/fxhnt/cli.py` `execute_multistrat` — read `current_allocation()["multistrat"]` as the envelope
  (min with NLV), size the within-book weights to it, flatten-on-zero, and on `--execute` record the
  `multistrat_exec` observed return.
- New: an `application/` helper (e.g. `multistrat_exec_record.py`) — compute the envelope-basis exec equity +
  daily return from the account positions + prior book-state, and `append_record` it. Pure where possible
  (the equity math), with the repo write at the edge.
- Modify: `src/fxhnt/registry.py` — add the `multistrat_exec` observed track (definition + backtest-ref
  mapping to the modeled multistrat).
- Reuse: `ExecutionService.rebalance_weights`, `AlpacaBroker`, `ForwardNavRepo.current_allocation` (B2a) +
  `append_record` / `get_book_state` / `set_book_state` (A observed machinery).
```
