# Bybit Execution Leg (Sub-project C2) — Design

**Date:** 2026-07-10
**Status:** Approved (brainstorming) — ready for implementation plan
**Sub-project:** C2 — the SECOND venue of the execution plane (layer 5) of the "unified data-driven pipeline"
north-star, after C1 (Alpaca/multistrat, merged). C3 (real-money arming / multi-venue aggregate) comes after.
**Builds on:** A (deterministic forward state + `run_track` observed machinery) + B1 (gate/promotion policy) +
B2a (allocation → `strategy_allocation` target dollars) + B3 (funding revocation → the allocation is
funding-filtered) + C1 (the execution pattern: envelope bridge + `record_exec_track` observed track) — all
merged. Reuses the EXISTING Bybit-testnet execution leg (`bybit_testnet_reconcile`/`bybit_testnet_record`
Dagster assets, `plan_orders`, `slippage.py`, `BybitTestnetRepo`).

## North-star context (execution plane, second venue)

The execution engine is a data-driven RECONCILIATION LOOP: given the desired state (B2a's funding-filtered
target dollars) 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 closed that loop for Alpaca/multistrat. C2 closes it for the
CRYPTO book on Bybit — the venue the fund's real edges actually live on (`bybit_4edge` is the `tier==deploy`
edge):

```
B2a strategy_allocation[bybit_4edge] (target $, ALREADY B3-funding-filtered)
  → BRIDGE:   envelope = min(target_$, testnet_equity); size the raw 4-sleeve weights to the envelope
  → RECONCILE: target vs Bybit-testnet positions → minimal orders   [reuse the existing plan_orders + leg]
  → PLACE:    market orders on Bybit TESTNET (kill-switch; per-fill decomposed slippage)   [existing]
  → FILLS:    envelope-basis, funding/fee/slippage-NET executed return → observed `bybit_4edge_exec` track
              (A's run_track), reconciling executed-vs-modeled against `bybit_4edge`
```

Guiding principle (mirror A/B1/B2a/B3/C1): the engine reconciles; it does NOT authorize capital. Capital stays
human-armed (Bybit keys; real-money is a separate deliberate step = C3) + ceiling-bounded (B2a) +
funding-gated (B3). The reconciliation is deterministic given (funding-filtered targets + account state).

**The problem C2 solves.** The Bybit-testnet execution leg exists and is sophisticated (per-fill decomposed
slippage, kill-switch, idempotent NAV marker), but it (1) sizes the book against the FULL testnet equity via
`plan_orders(weights, current, equity=adapter.equity(), ...)`, IGNORING how much capital B2a allocated to
`bybit_4edge` and never de-risking when B3 de-funds it or B2a's killswitch zeroes it; and (2) records the
executed book only to its own `bybit_testnet_nav` (full-testnet-equity basis), NOT as an A-style observed
forward track — so the real executed crypto book is not comparable to the modeled `bybit_4edge` through the
gate/cockpit.

## Goal

Make the Bybit-testnet leg B2a/B3-driven — deploy exactly `strategy_allocation[bybit_4edge]` dollars (min with
the testnet equity; flatten to cash when B3 de-funds it, B2a killswitches, or there is no allocation) — and
record the executed book's daily return as an envelope-basis, capital-flow-excluded, **funding/fee/slippage-net**
observed `bybit_4edge_exec` track via A's `run_track`, reconciling executed-vs-modeled against `bybit_4edge`.
Reuse the existing slippage decomposition unchanged. Testnet only; real capital stays human-armed (C3).

## Components

### 1. The envelope bridge — B2a/B3 dollars → sized crypto book

Read `ForwardNavRepo.current_allocation()["bybit_4edge"]` (B2a's target dollars; ALREADY B3-funding-filtered —
a de-funded/killswitched `bybit_4edge` is absent → `.get(..., 0.0)` = 0). The **envelope** =
`min(target_dollars, testnet_equity)` (never over-deploy the account, never exceed B2a's allocation). The
within-book weights are the existing raw 4-sleeve weights (`latest_raw_sleeve_weights` — unchanged; the 4-edge
book has no overlay, so the raw weights are the effective weights). Size them to the envelope by passing the
**envelope as the sizing equity** to `plan_orders` (targets = `weight · envelope`), instead of
`adapter.equity()`.

- **`bybit_4edge` de-funded / killswitched / no allocation → envelope = 0** → all target positions 0 → the
  book FLATTENS to cash (reduce-only orders close the positions). The engine de-allocates exactly when
  B3/B2a de-allocate — funding revocation and the killswitch propagate to real (testnet) positions.
- `max_gross` now binds against the envelope (deploy ≤ `envelope · max_gross`), tighter and correct.

Everything else in `bybit_testnet_reconcile` is unchanged: the kill-switch (log intent, place nothing), the
idempotent per-day NAV marker (no-op re-run), the resting-open-orders abort, the per-order `BrokerError` →
structured error row, and the per-fill decomposed-slippage capture.

### 2. The executed observed track — `bybit_4edge_exec` (loop closure)

Register `bybit_4edge_exec` in `STRATEGY_REGISTRY` as an OBSERVED track (`record_mode: "observed"`, venue
`bybit-testnet`, tier `research`, `gate_type reconciliation`), mirroring C1's `multistrat_exec`. Its
reconciliation reference is the MODELED `bybit_4edge` via a ref-provider alias `bybit_4edge_exec →
bybit_4edge` (extending C1's `CockpitBacktestRefProvider._REF_ALIAS`), so B1's gate reconciles
executed-vs-modeled — the executed crypto track PASSes only if it tracks the modeled book (execution decay =
divergence), and it flows through A's engine + B1's gate + the cockpit + B3's funding beside `bybit_4edge`.

After a real (non-killed, non-noop) reconcile, compute the executed daily return (§3) and book it via A's
`run_track(repo, "bybit_4edge_exec", ...)` — the same observed-booking path C1 uses (`record_exec_track`).
The next run's position/cash basis is carried in A's book-state under a key kept SEPARATE from run_track's own
(e.g. `bybit_4edge_exec_pos`), exactly as C1 does with `multistrat_exec_pos`.

### 3. The executed return — envelope-basis, flow-excluded, funding/fee/slippage-net (the C2 crux)

C1 (Alpaca, commission-free equities) computed the executed return by marking the held positions at prices +
idle reserve. Crypto perps add two real components C1 did not have: **funding** (a periodic cash flow that IS
edge return — several `bybit_4edge` sleeves harvest it) and **fees/slippage** (non-zero, unlike commission-free
ETFs). The executed return MUST be measured on the SAME basis as the modeled `bybit_4edge` it reconciles
against (which is funding-net and cost-modeled), or the gate diverges spuriously.

Computation (extends C1's `compute_exec_return`):

```
exec_equity_today = Σ(held_qty_s · mark_today_s) + reserve_cash
```
where `reserve_cash` carries forward `envelope − deployed_notional` from the last rebalance AND accrues the
realized **funding − fees** since the last mark. The daily executed return is:
```
exec_return = exec_equity_today / envelope_prior − 1
```
On the FIRST run there is no prior book → record no return (inception, exactly like A/C1).

Three points make the attribution exact and unambiguous:
- **Dedicated account:** the Bybit-testnet account holds ONLY the `bybit_4edge` book (all positions are the
  book's), so ALL realized funding/fees are the book's — there is no cross-strategy attribution to untangle.
  The idle balance (`testnet_equity − envelope`) holds no positions, hence accrues no funding.
- **Per-period delta:** the funding/fees folded into `reserve_cash` must be the realized amount SINCE THE LAST
  RECORD (a per-run delta), not a cumulative account figure. If the existing leg exposes `realized_pnl` as a
  cumulative, the plan derives the per-period delta (this-run cumulative − last-run cumulative, carried in the
  book-state).
- **Re-allocation resets the basis:** a B2a re-allocation (e.g. `bybit_4edge` $5k → $8k) is a capital FLOW,
  not a gain. The return for the period is computed against `envelope_prior` FIRST; then the stored basis
  (`envelope`, post-rebalance `positions`, `reserve_cash = envelope − deployed`) resets to the NEW envelope —
  so the injected/withdrawn capital enters at the rebalance, never in the return (mirrors C1's "next basis is
  the target book" rule).

**Load-bearing correctness invariant:** the executed return is measured on the B2a-allocated **envelope**
(not the full testnet equity), is **capital-flow-excluded** (a pure re-allocation with flat marks + zero
funding records ~0%), and is **funding/fee/slippage-net** (funding and fees flow through `reserve_cash`; fills
embed slippage). A test pins each clause: a large testnet balance behind a small envelope yields the
envelope's undiluted P&L; a flat-mark re-allocation records ~0%; and a funding credit with flat marks records
a POSITIVE return (funding is edge return, not a flow).

### 4. Slippage — reuse the decomposition unchanged

The existing per-fill decomposition (`execution_slippage_bps` = fill vs live mid; `timing_drift_bps` =
mid-at-order vs book mark; `slippage_vs_assumed_bps` = validation delta vs the A8 assumed cost tier) is
first-class and already recorded to `bybit_testnet_fills` by `bybit_testnet_record`. C2 does NOT change it —
the executed-track return in §3 is net of the REAL fills (which embed the actual slippage), and the per-fill
rows remain the A8 cost-validation output. This is the concrete reason C2 differs from C1: crypto slippage is
material, and it is measured, not assumed.

### 5. Where the human stays in the loop

The human arms Bybit TESTNET keys (real-money keys are a separate C3 step) and sets the B2a capital ceiling +
the `GatePolicy` (whose B3 windows gate funding). C2 deploys exactly B2a's `bybit_4edge` envelope (funding-
filtered), reconciles, and records — it never authorizes real capital. Moving to real money is deliberate
Bybit-mainnet keys + an explicit arming step (C3). The kill-switch remains an always-available circuit-breaker.

## Error handling

- **No `bybit_4edge` allocation / de-funded / `$0`** → envelope 0 → flatten to cash (reduce-only). Safe.
- **Kill-switch on** → log intended orders, place NOTHING (existing), record NO return (a killed run is a
  visible gap, not a silent pass — existing `bybit_testnet_record` writes nothing).
- **Idempotent re-run** (NAV marker for today) → no-op, no double-execution (existing).
- **Resting open orders** → abort (existing); **per-order `BrokerError`** → structured error row, no crash
  (existing).
- **First run** → no prior exec equity → record no return (inception), like A/C1.
- **Envelope > testnet equity** → capped at equity (never over-deploy).
- **Low-confidence mid** (missing book) → excluded from the slippage verdict (existing); the return marks use
  the best available mark.
- The executed return is on the ENVELOPE, funding/fee/slippage-net (the correctness invariant).

## Testing

- **Envelope bridge:** B2a `bybit_4edge` $X (funded) × raw sleeve weights → per-symbol targets sum to ≤ $X;
  B2a `$0` / de-funded / absent row → all-zero targets (flatten, reduce-only).
- **Envelope basis (load-bearing):** a large testnet equity with a small `bybit_4edge` envelope → the executed
  return reflects the envelope's P&L (undiluted by the idle balance), NOT the full-testnet-equity return.
- **Funding is return, not flow (load-bearing):** a funding CREDIT with flat marks records a POSITIVE executed
  return (funding accrues into `reserve_cash`); a pure re-allocation with flat marks + zero funding records
  ~0% (capital flow excluded).
- **Fee/slippage net (load-bearing):** fees reduce `reserve_cash` → a flat-mark period with a fee records a
  slightly NEGATIVE return; the per-fill slippage rows are unchanged (decomposition reused).
- **Observed record:** after a real reconcile, `bybit_4edge_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.
- **Kill-switch / no-op:** a killed or idempotent-no-op reconcile records NO order and NO return.
- **Registry + gate:** `bybit_4edge_exec` is an observed track whose reconciliation reference resolves (via
  the alias) to the modeled `bybit_4edge`.

## Out of scope (YAGNI)

- **No real-money Bybit** (C3 — mainnet keys + an explicit arming step). Testnet only.
- **No multi-venue aggregate reconciliation** (C3).
- **No change to the edges / the 4-sleeve book / the slippage model.** C2 reuses `latest_raw_sleeve_weights`,
  `plan_orders`, `slippage.py`, the `bybit_testnet` leg, and A's observed machinery.
- **`bybit_testnet_nav` is kept** (per the brainstorm decision) as the per-run full-equity + mean-slippage
  detail; the new observed track is the envelope-basis forward record. Cockpit VISUAL surfacing of the exec
  track follows the same deferral as C1/B3 (surfaced later alongside allocation).

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

- Modify: `src/fxhnt/adapters/orchestration/assets.py` `bybit_testnet_reconcile` — read
  `current_allocation()["bybit_4edge"]` as the envelope (min with testnet equity), size the sleeve weights to
  it via `plan_orders(..., equity=envelope, ...)`, flatten-on-zero; carry the realized-funding/fee delta +
  post-rebalance positions into the return computation. `bybit_testnet_record` — after recording the fills +
  `bybit_testnet_nav` (unchanged), compute the envelope-basis exec return (§3) and book the
  `bybit_4edge_exec` observed track via A's `run_track` + book-state.
- New: a small `application/` helper (e.g. extend/parallel `multistrat_exec_record.py`) — the envelope-basis,
  funding/fee/slippage-net exec-equity + return + `record_exec_track` for the crypto book. Pure where possible
  (the equity math), repo/`run_track` at the edge. Reuse C1's `compute_exec_return`/`record_exec_track` shape.
- Modify: `src/fxhnt/registry.py` — add the `bybit_4edge_exec` observed track (venue `bybit-testnet`, tier
  research, gate_type reconciliation, record_mode observed, definition per B1's shape).
- Modify: `src/fxhnt/application/forward_ingest.py` `CockpitBacktestRefProvider._REF_ALIAS` — add
  `bybit_4edge_exec → bybit_4edge` (so the gate reconciles executed-vs-modeled).
- Reuse: `ForwardNavRepo.current_allocation` (B2a+B3), `plan_orders`, `slippage.py`, `BybitTestnetRepo`,
  `run_track` / `append_record` / `get_book_state` / `set_book_state` (A), C1's `record_exec_track` pattern.
```
