Record monotone fill-timing gradient, live [1.57,1.77] bracket, recover-flag interpretation, gap-cap as third tail-trim, and pre-scheduler ops checklist.
147 lines
5.9 KiB
Markdown
147 lines
5.9 KiB
Markdown
# Execution recovery (2026-07-18) — when you fill is the edge you leave on the table
|
||
|
||
Report: `reports/execution_recovery_matrix.json` / `.md`
|
||
Follows Phase A A4 ([phase-a-matrix.md](phase-a-matrix.md)).
|
||
Arms: `close_control` · `next_open` · `stale_close` · `next_open_gap2`.
|
||
|
||
Mechanics verified before sign-off: `stale_close` re-anchors stop to
|
||
fill − 1.5×ATR(signal day); no same-day stop after a close fill; each t−1
|
||
candidate keeps its own gate/rank cross-section (no lookahead). Numbers trusted.
|
||
|
||
---
|
||
|
||
## Results
|
||
|
||
| Arm | Full Sharpe | Full CAGR | Full DD | Train Sharpe | Val Sharpe |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| **close_control** | **1.77** | **48.3%** | **21.6%** | 1.75 | 1.68 |
|
||
| **stale_close** | **1.57** | **40.6%** | **21.8%** | 1.36 | **1.74** |
|
||
| next_open | 1.20 | 30.0% | 28.2% | 0.94 | 1.44 |
|
||
| next_open_gap2 | 1.06 | 25.2% | 27.8% | 0.72 | 1.41 |
|
||
|
||
### Monotonicity (the strongest evidence)
|
||
|
||
In every window, Sharpe recovers as the fill moves **toward** the signal:
|
||
|
||
| Window | next_open → stale_close → close |
|
||
|---|---|
|
||
| Train | 0.94 → 1.36 → 1.75 |
|
||
| Validation | 1.44 → 1.74 → 1.68* |
|
||
| Full | 1.20 → 1.57 → 1.77 |
|
||
|
||
\*Val close 1.68 is within SE of stale 1.74 — not a break of the story.
|
||
|
||
A dead edge does **not** produce a monotone gradient in fill timing. A live edge
|
||
that is progressively surrendered to execution delay does. Combined with full-period
|
||
**DD recovery** (28.2% → 21.8% ≈ close’s 21.6%), this confirms the diagnosis:
|
||
**when you fill**, not decaying alpha.
|
||
|
||
### Auto-flag `recover: False` is not a null
|
||
|
||
Pre-registered recovery applied the “≥ close − 0.5 SE” bar to **`stale_close`**,
|
||
the **lower-bound** arm (one full session of lag). Flags:
|
||
|
||
| Flag | Result |
|
||
|---|---|
|
||
| near_close_control (val) | **True** (Δ +0.06) |
|
||
| beats_next_open | **True** (val Δ +0.30) |
|
||
| train_ok | **False** (1.36 vs need ≥ ~1.39 — miss by ~0.03) |
|
||
|
||
The floor missed “≥ close − 0.5 SE” by 0.03 while the live design is expected to
|
||
sit **above** the floor. The flag worked correctly on the wrong object.
|
||
|
||
**Log sentence:** *Partial recovery proven; full recovery needs same-day fill.*
|
||
|
||
### Live outcome bracket
|
||
|
||
| Bound | Arm | Meaning |
|
||
|---|---|---|
|
||
| Floor | stale_close ~1.57 full | One-session-stale close fill (conservative) |
|
||
| Ceiling | close_control ~1.77 full | Same-day close; optimistic only by final ~15 min of signal info |
|
||
|
||
Real near-close execution (scan ~15:30–15:40 ET on a ~99% complete bar, MOC by
|
||
15:50/15:55) is signal-at-partial-bar filled at the same close the control uses.
|
||
**Live truth is bracketed [1.57, 1.77]** with residual uncertainty of ~15 minutes
|
||
of staleness, not 24 hours — expect near the **top** of the bracket.
|
||
|
||
`stale_close` alone already justifies the schedule change. **No further fill-timing
|
||
simulation on this snapshot** — the bracket is the result. You cannot simulate
|
||
15:45 partial bars from daily data, and you do not need to.
|
||
|
||
### Gap-cap — dead (third tail-trim instance)
|
||
|
||
`next_open_gap2` worse than plain next_open on every window (full Sharpe 1.06 vs
|
||
1.20). **262** full-period gap-ups skipped — they were continuations.
|
||
|
||
This is the **third independent instance** of the same lesson:
|
||
|
||
1. Take-profit exits (gate target as TP)
|
||
2. FIP as an in-book filter
|
||
3. **Gap-up entry caps**
|
||
|
||
Any rule that trims the right tail improves cosmetic quality metrics and destroys
|
||
P&L. **Standing method rule:** momentum filters must be presumed guilty of
|
||
tail-trimming until shown otherwise.
|
||
|
||
---
|
||
|
||
## Decisions locked
|
||
|
||
1. **Biggest lever is execution scheduling**, not a strategy rewrite.
|
||
2. **Until near-close execution is live:** grade strategy promotion under
|
||
`fill_mode=next_open`; keep close-fill as historical control.
|
||
3. **After near-close ships:** grade under a close-like / near-close fill mode
|
||
(actual live path).
|
||
4. **No more sim arms** on fill timing for this snapshot.
|
||
5. **Gap-cap:** do not ship.
|
||
6. **Strategy work** (nasdaq_all, fip_id, sector) waits until the execution path
|
||
is decided — those experiments must be graded under the fill mode you will trade.
|
||
|
||
---
|
||
|
||
## Ops design — settle before touching the scheduler
|
||
|
||
### 1. One canonical qualifying scan per day (non-negotiable)
|
||
|
||
Post-stop gate-reset was validated on **one full-universe observation per day**.
|
||
Adding a near-close scan *alongside* the 07:00 scan would let fail→qualify
|
||
transitions complete twice as fast and **silently change** the validated re-entry
|
||
policy.
|
||
|
||
**Move** the R:R scan to near-close. **Leave** sentiment / fundamentals /
|
||
OHLCV-backfill at 07:00 (or existing early slots). Preserve **scan before
|
||
Outcome Eval** (scan late session, eval after close) so the documented
|
||
strict-gate-reset live analogue is unchanged.
|
||
|
||
### 2. Cron in `America/New_York`, not `Europe/Berlin`
|
||
|
||
DST offsets shift on different dates. A Berlin-fixed wall time drifts ~1 hour
|
||
off the US close for a week or two twice a year.
|
||
|
||
### 3. Fill mechanics and feed honesty
|
||
|
||
- NYSE MOC cutoff **15:50 ET**; Nasdaq **15:55 ET**.
|
||
- Scan ~**15:30–15:40 ET** on a latest-price snapshot; place entries by 15:50.
|
||
- For now: paper-trade entries marked at the **actual close**.
|
||
- Document Alpaca entitlement: if only 15-minute-delayed SIP, a 15:35 scan sees
|
||
~15:20 prices — immaterial for a 12-month signal, but write it down so nobody
|
||
treats it as a bug.
|
||
|
||
### 4. Partial-bar plumbing
|
||
|
||
- Scan synthesizes day-t’s in-progress bar from the snapshot.
|
||
- Nightly OHLCV job overwrites with the final bar via existing idempotent upsert.
|
||
- Forward paper record marks entries at the actual near-close fill so the live
|
||
track measures the new execution honestly.
|
||
|
||
---
|
||
|
||
## Implementation status
|
||
|
||
| Item | Status |
|
||
|---|---|
|
||
| Research evidence | **Closed** — this doc + matrix report |
|
||
| Scheduler move (R:R scan → NY near-close) | **Not started** — blocked on ops design above |
|
||
| Partial-bar scan path | **Not started** |
|
||
| Paper fill-at-close marking | **Not started** (may already mark at close; verify when shipping) |
|