# Alpha Documentation

## Current honest result (2026-04-12)

**MAKER_OPT: +$147.97 on $500 (29.6%)**
- **Trades:** 40
- **Win rate:** 67.5%
- **Max drawdown:** 2.49%
- **Ret/DD:** 59.4x
- **Data:** ~18 unique hours across 2 calendar days (Apr 5-7, 2026)
- **Fill model:** OB-based maker chase limit emulation
- **Fee model:** entry fee + exit fee both included in per-trade net (matches Bybit rpnl exactly)

### Progression of honest backtesting

| Stage | Return | DD | Ret/DD | What changed |
|---|---:|---:|---:|---|
| Bar hi/lo fills | +$199 | 1.56% | 128 | Original optimistic |
| OB chase fills | +$110 | 1.88% | 58 | Realistic entry fill emulation |
| Taker SL + slippage | +$98 | 2.17% | 45 | Realistic exit costs |
| + trend filter | +$128 | 3.82% | 33 | Secondary entry rv 3-5 |
| Trail 30% (from 45%) | +$161 | 2.23% | 72 | Tighter trail captures more |
| Exit optimization | +$161 | 2.23% | 72 | Time skip >10bps, force 180s |
| Entry fee in net | +$142 | 2.96% | 48 | Honest per-trade PnL |
| Stop 20bps (from 15) | +$148 | 2.49% | 59 | Survive stop-hunt bounces |

---

## Entry model

### Primary entry: high-vol regime (rv 5-7)
- **Gate:** realized vol (bar-close, multi-venue) in [5, 7] bps
- **Signal:** absorption OR momentum from `shouldEnter()`
- **Rationale:** rv 5-7 is the cleanest edge zone (t=4.56, n=104)

### Secondary entry: trending moderate-vol (rv 3-5)
- **Gate:** rv in [3, 5) AND 30-min absolute trend > 20bps AND MOM only
- **Rationale:** rv 3-5 with trend: mean=+11.41bps, t=3.16 (n=58); without trend: +1.55, t=0.99

### Signal: `shouldEnter()`

**Absorption:** delta opposes price, |delta|>0.30, surge>2.5, fastVol>$80k, topShare≤65%

**Momentum:** |score|>0.55, |delta|>0.40, fastVol>$100k, surge>3, delta aligned with price, topShare≤65%

### Signal computation: `computeSignal()`

10-second bars from 7 venues. Fast window 50s, slow window 10min. Coinbase weighted 1.2x.
Score = 0.45 × delta_score + 0.35 × price_score.

---

## Exit model

### Trail stop
- Activation: bestBps > 5bps
- Distance: 30% of best (captures 70% of peak)
- Fires when: uBps < trail_level AND uBps > 0
- Fee: maker (2bps)

### Time exit
- At maxHold (120s): exit if 0 < uBps < 10bps
- Skips trades winning >10bps (lets trail handle big winners)
- Fee: maker

### Force close
- At 1.5× maxHold (180s): exit regardless
- Fee: taker if market fallback, maker if chase fills

### Stop loss
- Minimum: 20bps (`Math.max(20, rangeBps * 0.4)`)
- 60s hunt window: no SL during first 60s (survive stop-hunt)
- After 60s: catastrophic backstop only
- Fee: depends on actual fill (wasTaker boolean)

### Take profit (TP1)
- Range-based adaptive target
- Fee: maker

---

## Risk model

### Position sizing
- Base risk: 1.5% of equity × conviction (0.25x-3.0x)
- Fee budget: assumes taker exit (conservative)
- Max notional: 50% × equity × leverage

### Conviction multiplier
- Score: 0.75x-1.5x based on |score|
- Volatile regime: 1.25x
- Absorption: 1.2x
- Win streak ≥2: 1.15x
- Loss streak ≥1: 0.75x; ≥2: 0.5x
- Capped [0.25, 3.0]

### Cooldowns
- Entry: 300s after any entry
- Loss: 600s after any SL
- Direction: 300s after 2 consecutive same-direction losses

---

## Stop-loss analysis

### MAE recovery data (104 signals, rv 5-7)

99% of trades stopped at 8bps saw profit first. The stop was getting hunted.

| Stop (bps) | Survive % | Stopped MFE | Recovery rate |
|---:|---:|---:|---:|
| 8 | 20% | 39.8 bps | — |
| 15 | 54% | 36.3 bps | 86% recover |
| 20 | 73% | 33.5 bps | 58% recover |
| 25 | 83% | 20.0 bps | 39% recover |

20bps is optimal: survives the stop-hunt bounce (86% of -15bps hits recover)
without holding through genuine adverse moves (-25bps: only 39% recover).

### Hunt window analysis

Almost all -15bps adverse excursions happen within the first 60 seconds.
Extending the window to 90s or 120s has no measurable effect.

### Time-based stop structure

1. First 60s: no fixed SL (survive the hunt)
2. Trail active always: 5bps activation, 30% distance
3. Time exit at 120s: if 0 < uBps < 10bps
4. Force close at 180s: regardless
5. Catastrophic SL after 60s: 20bps minimum

---

## Chase execution

### Entry chase
- Maker PostOnly at best bid/ask
- 3 attempts × 3s wait = 9s total
- Adverse-drift gate: abort if reprice moves against signal direction
- Fill rate: ~79% across all demo runs
- 1-attempt: 51%, 2-attempt: 40%, 3-attempt: 9%

### Exit chase (non-SL)
- Trail: 4 attempts × 1.5s = 6s (fast, protect profit)
- Time/force: 9 attempts × 2s = 18s (more patience)
- If maker chase exhausts: market order fallback

### Exit (SL)
- 3 attempts × 3s maker chase
- If fails: market order fallback (taker fee)

### Adverse-drift gate
Before each reprice attempt, check if price moved against signal:
- LONG (Buy): new bid < previous bid → abort
- SHORT (Sell): new ask > previous ask → abort

---

## Engine-Bybit reconciliation

### Root causes found and fixed

1. **Entry fee missing from net:** engine `t.net` was `gross - exitFee`. Now `fullNet = gross - entryFee - exitFee - slippage`. Matches Bybit `rpnl` exactly.

2. **Assumed taker for all SL exits:** some SLs fill as maker (PostOnly limit hit when price crosses through). Now `wasTaker` boolean passed from actual execution mode.

3. **Non-SL maker-exhaust not placing orders:** force_close/time exits that exhausted maker chase were confirming in engine without sending Bybit order. Now falls back to market order.

### Current reconciliation
Latest demo session: **engine_pnl = actual_pnl exactly** ($0.04/trade gap).

---

## Fill model (backtest)

### Entry
`emulateChaseEntry()` looks ahead in recorded OB snapshots:
1. Post at bid/ask
2. Wait 3s, check if opposing side crossed our level
3. Cancel, reprice, retry up to 3 times

### Exit
Fill at target price. SL exits use taker fee + 1bps slippage estimate.
TP/trail/time use maker fee. `wasTaker` passed based on exit reason.

---

## Known limitations

### Data coverage
- ~18 unique hours across 2 calendar days
- 5 overlapping sessions inflate apparent trade count
- Uptrend-biased (66.7k→69.9k): shorts are net negative in backtest
- No downtrend, consolidation, news-event, or extreme-funding data

### Demo performance
7+ demo runs, all in choppy/ranging markets. Total demo PnL: ~-$200.
The strategy requires trending volatile sessions (100+ bps moves) which
haven't occurred during the demo testing period (Apr 7-12).

### Engine vs live execution
- Backtest uses 250ms OB snapshots; live has tick-level granularity
- Market-fallback exits cost ~$0.50-2 more than engine estimates
- Adverse-drift gate and faster trail chase mitigate but don't eliminate

---

## What was tested and rejected (portfolio level)

| Change | Signal evidence | Portfolio result |
|---|---|---|
| Delta cap 0.65 | t=0.93 | -35% return |
| Range 20-60 only | t=5.23 | -26% return |
| maxHold 300s | peak t at 300s | -9% return |
| AC1 conviction | t=7.14 | -66% return |
| Signed trend | t=3.19 | -66% return |
| CB non-alignment | t=6.28 | -4% to -47% |
| Mean-reversion fade | 59% reversion | -$271 swing |
| Hybrid maker-taker | — | -16% to -42% |
| Early breakeven exit | — | kills 2 winners |
| Partial take-profit | — | -23% to -50% capture |
| SL inversion | — | -18bps avg (t=-14.70) |
| Conviction cap marginal range | — | -41% return |
| Hunt window 90s/120s | — | no effect |

### Pattern: signal-level findings fail at portfolio level

The strategy's profits are concentrated in specific trending sessions.
Any filter, gate, or sizing adjustment that reduces exposure during those
sessions hurts more than it helps during bad sessions. The conviction
multiplier already optimally allocates via score magnitude and regime.

---

## What survived and is active

| Change | Impact |
|---|---|
| rv cap at 7 | +6% return |
| Secondary entry (rv 3-5, MOM + trend) | +30% return |
| Stop 20bps (from 8) | +4% return, -16% DD |
| 60s hunt window | live safety |
| Trail 30% (from 45%) | +33% return, -12% DD |
| Time exit skip >10bps | +20% risk-adjusted |
| Force close 180s (from 240s) | +20% risk-adjusted |
| Taker SL fees + slippage | honest model |
| Entry fee in net (fullNet) | matches Bybit exactly |
| wasTaker exit fee | matches Bybit exactly |
| Adverse-drift chase gate | execution safety |
| Non-SL market fallback | execution correctness |
| Faster trail chase (4×1.5s) | protect trail profits |

---

## Architecture

Single execution path: `StrategyEngine` in `src/core/strategy.ts`.

- **Replay:** `txocap-replay <session-dir>` — recorded data + emulated chase fills
- **Demo:** `txocap-demo-benchmark` — live data + real demo API fills
- **Dashboard:** `txocap-dashboard` — pure IPC consumer of engine_state

All strategy decisions, sizing, exits, and PnL tracked in one engine class.
Only the fill provider differs by mode.

---

## Demo trading performance (2026-04-07 to 2026-04-12)

### 0 profitable sessions out of 11 runs

Total: ~57 hours, ~93 trades, ~-$322 actual PnL

**The strategy has not produced a single profitable demo session.**

### Why the demos keep losing

All demo sessions occurred during BTC's choppy descent from $73k to $70.7k.
The market pattern was: high bar-to-bar rv (gate opens) + tight 5-min range (3-15bps)
+ immediate bounce after every directional spike.

This produces:
1. Signal fires (momentum spike detected)
2. Entry fills
3. Price reverses 8-15bps (stop-hunt bounce)
4. SL or force_close triggers
5. Cooldown blocks re-entry
6. Repeat

**Zero TP1 exits in 57+ hours.** TP1 requires 20-40bps directional moves which haven't
occurred during the demo testing window.

### Backtest vs demo gap

| | Backtest | Demo |
|---|---:|---:|
| Return | +$148 | -$322 |
| Win rate | 67.5% | ~30% |
| TP1 exits | 7 | 0 |
| Avg win | +$5.50 | +$1.20 |
| Avg loss | -$5.18 | -$7.40 |

The gap is NOT execution quality (engine-Bybit gap: $0.04/trade).
The gap is regime: the backtest captured one trending session (Apr 5 evening, +$84)
that hasn't occurred once in 57 hours of live trading.

### Honest risk assessment

Three possibilities:
1. **Market regime mismatch (60%):** the strategy is correct but we haven't hit the right regime yet
2. **Moderate overfitting (30%):** 40 backtest trades on 2 days of data is insufficient sample
3. **Execution edge decay (10%):** the 10-second signal edge decays before orders fill

For full discussion see `docs/demo-runs.md`.
