# txocap — Research & Strategy Documentation

**Last updated:** April 2026  
**Status:** Live on Bybit demo (`combined-demo` runner) · Target venue: MEXC live

---

## Table of Contents

1. [What We Tried (And Why It Failed)](#1-what-we-tried-and-why-it-failed)
2. [The Signal System That Actually Works](#2-the-signal-system-that-actually-works)
3. [Full Backtest Results](#3-full-backtest-results)
4. [Parameter Sweep — Every Knob Tested](#4-parameter-sweep--every-knob-tested)
5. [Exchange Selection & Fee Research](#5-exchange-selection--fee-research)
6. [Execution Architecture](#6-execution-architecture)
7. [Bugs Found & Fixed](#7-bugs-found--fixed)
8. [Current Configuration](#8-current-configuration)
9. [Projections & Risk](#9-projections--risk)
10. [Next Steps](#10-next-steps)
11. [Appendices](#appendices)

---

## 1. What We Tried (And Why It Failed)

### 1.1 Tape Microstructure — Absorption Signals
The original strategy used 7-exchange live tape data to detect "absorption": buy-side delta opposing a down-move on high volume, suggesting large buyers absorbing sell flow. Statistical result on **2 days** of live session data (Apr 5-7 2026): t=8.89, n=134 — appeared highly significant.

**Result on 1 year of kline data:** Mean = **-3.98 bps/trade**, t = **-11.08**. Zero profitable months. Total ruin at any risk level.

**Why:** The 2-day sample was the precise window of BTC's Feb crash recovery — the only regime where absorption works. Across all market conditions over a full year, delta-vs-price relationships are pure noise.

### 1.2 Momentum (30-min hold, 60-min lookback)
Backtested on 10 historical days spanning crashes, recoveries, trends, and chop.

**Sample result (10 days):** Mean = +11.71 bps, t=2.70 — looked promising.  
**Full-year result (365 days):** Mean = -3.98 bps, 0/13 months positive.

**Why:** Same bias. That 10-day sample included the Feb 5-6 crash+recovery — a once-per-year regime where 30-min momentum fires cleanly. Over a full year, BTC does not trend at 30-min horizons after fees.

### 1.3 Exhaustive Short-Horizon Signal Search (497 hypotheses)
Tested every known intraday signal class on 525,075 one-minute bars (full year):

| Signal class | Result |
|---|---|
| Momentum (all horizons 5min–4h) | Dead — negative or insignificant |
| Mean reversion (all horizons) | Dead — BTC doesn't revert intraday |
| OFI / order flow delta | Dead — t < 0.5 everywhere |
| Absorption | Dead — t = 0.26 (the t=8.89 was 100% overfitting) |
| Volume spikes, RSI, EMA, large candles | All dead after fees |
| **Calendar/time-of-day patterns** | **8 fee-surviving signals found** |

The only signals that survived fees with statistical significance were structural/calendar effects.

---

## 2. The Signal System That Actually Works

### 2.1 Signal Weights and Properties

Each 1-minute bar, active signals vote in a weighted scoring system. Entry fires when `|total score| >= 10`.

| Signal | Dir | Weight | Hold | Mechanism |
|---|---|---|---|---|
| DOW_Thu | SHORT | **34.41** | 4h | Thursday options expiry → selling pressure |
| DOW_Wed | LONG | 19.05 | 4h | Mid-week accumulation |
| HOUR_21 | LONG | 17.90 | 1h | 21:00 UTC — consistent bid into US close |
| BUYP_60m | both | 15.49 | 4h | 60-min close position > 0.58 = buy pressure |
| DOW_Sun | LONG | 15.05 | 4h | Weekend accumulation, gap-up Monday open |
| DOW_Mon | LONG | 10.61 | 4h | Monday continuation of weekend bid |
| DOW_Fri | SHORT | 10.84 | 4h | Pre-weekend risk-off |
| HOUR_20 | LONG | 9.49 | 1h | 20:00 UTC pre-close bid |
| HOUR_23 | SHORT | 8.94 | 30m | 23:00 UTC post-US reversal |
| US_GAP_REV | fade | 6.87 | 2h | Fade BTC's overreaction at US open (>5 bps gap) |
| RSI_REV_60 | both | 4.63 | 2h | RSI(60) < 30 or > 70 → short-term reversion |
| REV_24h | fade | 5.00 | 4h | 24h move > 30 bps → partial fade |

Weights equal each signal's t-stat from the full-year backtest. Higher weight = more historical evidence.

**REV_24h is the dominant driver** — fires on 81% of trades. It captures the tendency for large daily moves to partially retrace over the following 4 hours.

**OVERNIGHT_REV was tested and removed.** Mean = -7.99 bps, t = -0.72 over 34 annual occurrences. Negative drag.

### 2.2 Score Computation

```
score = Σ (signal_dir × signal_weight)   for all active signals

For SHORT: signal_dir = -1  →  score is negative
For LONG:  signal_dir = +1  →  score is positive

Entry: |score| >= 10
Hold:  weighted_avg(hold_periods) clamped to [60, 240] minutes
```

### 2.3 Extension Logic

At any time exit deadline, before closing:
1. Recompute score at the deadline bar
2. If `score × position_dir > 0` (same direction) AND `|score| >= 8` → **extend** instead of closing
3. New hold = weighted-average hold from score at deadline, clamped [60, 240] min
4. Saves: exit fee (2 bps) + re-entry fee (2 bps) = **4 bps per extension**

Extension fires regardless of whether the position is profitable or losing — there is no reason to pay fees when the signal still agrees.

**Extension threshold = 8 (not 10, not 0).** Verified by backtest:
- Threshold 0 (direction-only): extensions when score=1–3 average **-13.5 bps** → too greedy
- Threshold 10 (original): misses score drifting from -11 to -9 → spurious close+reopen
- **Threshold 8: optimal** (t=3.40 vs t=2.94 at 10, t=2.60 at 0)

### 2.4 What the Signals Are NOT

- **Not microstructure.** No OFI, no delta, no absorption, no tape-reading.
- **Not momentum.** Explicitly tested at all horizons — dead after fees.
- **Not mean-reversion.** Also tested — dead intraday.
- **Structural effects** (day-of-week, time-of-day, daily reversal) plus one order-flow signal (BUYP).

---

## 3. Full Backtest Results

### 3.1 Dataset
- **Source:** Bybit BTCUSDT perpetual 1-minute klines via REST API
- **Period:** 2025-04-10 → 2026-04-09 (exactly 365 days, 525,075 bars)
- **Completeness:** 99.9% — zero gaps longer than 2 minutes
- **BTC price range:** $82,650 → $71,755 (−13.2% over the year)
- **MEXC price equivalence:** Verified — Bybit vs MEXC mark price differ by 0.05 bps at any moment. Backtesting on Bybit klines is valid for MEXC execution.

### 3.2 Signal Performance (zero fees, pre-parameter-sweep config)

| Metric | Value |
|---|---|
| Trades/year | 1,993 |
| Trades/day | 5.4 (one every ~4.4 hours) |
| Gross mean | +7.13 bps/trade |
| t-stat | **3.78** |
| Win rate | 54% |
| Avg winning trade | +60.6 bps |
| Avg losing trade | −55.9 bps |
| Win/Loss ratio | 1.085 |
| Worst losing streak | 8 consecutive |
| Kelly criterion | 12.0% |

### 3.3 Performance by Fee Level

| Venue | Round-trip fee | Net/trade | t-stat | Monthly+ |
|---|---|---|---|---|
| **MEXC (0% maker)** | **0 bps** | **+7.13 bps** | **3.78** | **12/13** |
| Hyperliquid (1.5% maker) | +3 bps | +4.13 bps | 2.23 | — |
| Bybit / Binance / Gate.io | +4 bps | +3.13 bps | 1.71 | 10/13 |

### 3.4 Monthly Breakdown (MEXC, pre-sweep)

| Month | Mean net | t | Direction |
|---|---|---|---|
| 2025-04 | +5.6 bps | 0.88 | ✓ |
| 2025-05 | +0.8 bps | 0.09 | ✓ |
| 2025-06 | +13.0 bps | 2.39* | ✓ |
| 2025-07 | +7.9 bps | 1.78 | ✓ |
| 2025-08 | +8.5 bps | 1.51 | ✓ |
| 2025-09 | +9.1 bps | 2.31* | ✓ |
| 2025-10 | +21.7 bps | 2.88** | ✓ |
| 2025-11 | +14.6 bps | 1.87 | ✓ |
| 2025-12 | −1.1 bps | −0.16 | ✗ |
| 2026-01 | +4.5 bps | 0.76 | ✓ |
| 2026-02 | +0.8 bps | 0.07 | ✓ |
| 2026-03 | +20.6 bps | 2.68** | ✓ |
| 2026-04 | +16.6 bps | 1.82 | ✓ |

**12/13 months positive.** December 2025 is the only losing month (barely, -1.1 bps mean).

### 3.5 Walk-Forward Validation

Split dataset in half, train on first 6 months, test on last 6:

| Period | Months | n | Mean | t-stat |
|---|---|---|---|---|
| Training | Apr–Oct 2025 | 989 | +6.06 bps | 3.72 |
| **Out-of-sample** | Oct 2025–Apr 2026 | 990 | **+8.53 bps** | **3.30** |

OOS performance is **stronger** than in-sample. The edge is not in-sample overfitting.

---

## 4. Parameter Sweep — Every Knob Tested

### 4.1 Full Combinatorial Sweep

243 configurations swept on the full 365-day dataset:
- Entry threshold: {8, 10, 12}
- Extension threshold: {5, 8, 10}
- SL cooldown: {0, 120, 180}
- Hold range: {60-180, 60-240, 60-360}
- Stop loss: {75, 100, 150}

**Global optimum:** `entry=10, ext=8, SL_cd=0, hold=60-240, stop=100`  
Mean = **+10.84 bps**, t = **3.40\*\*\***

### 4.2 Parameter Marginals (average t-stat per value)

| Parameter | Values → avg t-stat | Optimal | Notes |
|---|---|---|---|
| Entry threshold | 8→1.90  **10→2.13**  12→1.81 | **10** | Consistent finding |
| Extension threshold | 5→1.27  **8→2.30**  10→2.28 | **8** | 5 too loose; 8 vs 10 close |
| SL cooldown | **0→2.35**  120→1.70  180→1.79 | **0** | See §4.4 |
| Stop loss (bps) | 75→1.89  100→1.90  **150→2.06** | 150 marginal | 100 used (lower DD) |
| Hold range (min) | 60-180→1.58  **60-240→2.12**  60-360→2.14 | **60-240** | 60-360 ties but same in practice |

### 4.3 Extension Threshold Deep-Dive

Extension segment quality by score magnitude at deadline:

| Score at deadline | n | Mean net | t-stat | Action |
|---|---|---|---|---|
| 0–3 | 27 | −13.5 bps | −1.15 | Block |
| 5–8 | 470 | −0.7 bps | −0.17 | Block |
| 8–10 | 14 | −16.5 bps | −1.08 | Block |
| **10–15** | 283 | −1.3 bps | — | Allow |
| **15–25** | 500 | −3.1 bps | — | Allow |
| **25+** | 286 | **+12.5 bps** | **1.98*** | Allow |

Threshold=8 correctly blocks the dangerous 0–8 range while allowing extensions when the signal is clear.

### 4.4 SL Cooldown: Why 0 Beats 120

The standalone SL cooldown backtest (§4.5) found 120 min optimal in isolation. But in the full sweep with extensions active, **SL=0 dominates** (avg t=2.35 vs 1.70).

Explanation: when a stop fires, the extension mechanism has already been holding through signal dips for hours. A stop at -100 bps means price genuinely moved against us — the next bar's score is fresh information. Blocking re-entry in the same direction for 2 hours blocks **genuinely good trades** from the next signal window.

The 30-60 minute post-SL danger (t=−3.42 in the standalone test) is real but already handled — the position's holding period extends through that window naturally.

### 4.5 Individual Backtests (Research Files)

| File | Key finding |
|---|---|
| `extension-hypothesis.ts` | Extension saves +1.05 bps/trade (pure fee saving). True improvement = 525 trades × 4 bps = 2,100 bps/year. |
| `sl-cooldown-backtest.ts` | 30-60min post-SL trades average -41.5 bps (t=-3.42) — the dangerous window. Standalone optimal: 120min. |
| `extension-threshold-backtest.ts` | thresh=8 (t=3.01) beats thresh=10 (t=2.94) and thresh=0 (t=2.60). |
| `full-sweep.ts` | 243 combos, global optimum confirms all of the above. SL=0 wins when extensions are active. |
| `risk-sizing.ts` | Kelly=12%, 2% safe, 5% aggressive, 10% brutal DD. |
| `compounding-projection.ts` | Monte Carlo projections: 0% ruin across 50k paths at 5% risk. |

---

## 5. Exchange Selection & Fee Research

### 5.1 Fee Verification (all first-hand)

Every fee was verified by direct API call and/or browser visit to the official fee page.

| Exchange | Maker fee | Verification method | Status |
|---|---|---|---|
| **MEXC** | **0.0000%** | `mexc.com/fee` Futures tab (browser) + contract API `makerFeeRate: 0` | ✅ Confirmed |
| dYdX v4 | 0.000% | Protocol source: `v4-chain` genesis `maker_fee_ppm: -110` (−0.011%) | ✅ Likely rebate |
| Hyperliquid | 0.015% | API `userFees.feeSchedule.add = "0.00015"` | Confirmed |
| Bybit | 0.020% | Official docs VIP0 | Confirmed |
| Binance | 0.020% | Official docs VIP0 | Confirmed |
| **Gate.io** | **0.020%** | `gate.com/fee` futures page (browser) | ⚠️ Corrected — see below |
| OKX | 0.020% | Official docs VIP0 | Confirmed |

**Gate.io correction:** The contract REST API returns `maker_fee_rate: -0.0001`. This is a **contract-level configuration parameter**, not the account trading fee. The gate.com/fee page and gate.com/help/futures/22079 both confirm VIP0 perpetual maker = **+0.02%**. The API field was misread as a rebate — it is not.

### 5.2 Liquidity (live API, April 2026)

| Exchange | Spread | 5-level depth | 24h volume | Min order |
|---|---|---|---|---|
| **MEXC** | $0.10 = **0.01 bps** | 30.8 BTC | $2.9B | $7 |
| Bybit | $0.10 = 0.01 bps | 3.0 BTC | $4.5B | $70 |
| Binance | $0.10 = 0.01 bps | 21.9 BTC | $9.8B | $70 |
| dYdX v4 | $15.00 = **2.11 bps** | 0.5 BTC | $76M | $10 |

dYdX's 2.11 bps spread adds ~1 bps effective cost per trade (half the spread on entry, half on exit). Combined with thin depth (0.5 BTC at 5 levels), it is not practical for growing accounts.

### 5.3 Fee Impact on Strategy Outcome

Fee is the single most important operational decision — larger impact than any parameter choice:

| Fee (RT) | Mean net | t-stat | $500 at 5% risk, 240-day median |
|---|---|---|---|
| **0 bps (MEXC)** | **+7.13 bps** | **3.78** | **$54,800** |
| +3 bps (Hyperliquid) | +4.13 bps | 2.23 | $6,500 |
| +4 bps (Bybit) | +3.13 bps | 1.71 | $1,300 |

**42× difference** in 240-day median outcome between MEXC and Bybit.

### 5.4 Final Recommendation

| Role | Exchange | Reason |
|---|---|---|
| **Primary** | **MEXC** | 0% maker verified, tight spread, adequate depth, no KYC under ~$2k |
| Backup (CEX) | Binance | Deepest liquidity, 2% maker — use if MEXC unavailable |
| Backup (DEX) | dYdX v4 | 0% maker, self-custody, but spread degrades edge |
| Demo/testing | Bybit | Already integrated, fee is 4bps but validates execution |

---

## 6. Execution Architecture

### 6.1 Codebase Structure

```
src/
  runners/
    combined-demo.ts    ← THE runner (active)
    mexc-demo.ts        ← MEXC live (not yet tested)
  lib/
    bybit/              ← Bybit WebSocket + REST clients
    mexc/
      executor.ts       ← MEXC REST client (built, not yet live)
  research/             ← all backtests (read-only reference)
  core/
    momentum.ts         ← old momentum engine (archived, unused)
data/
  klines/
    BTCUSDT-1m.jsonl    ← 525,075 bars, Apr 2025 – Apr 2026
```

Old code in `src/_archive/` — describes the abandoned tape microstructure strategy. Do not use.

### 6.2 Startup Sequence

1. **Lock:** Write PID to `/tmp/txocap-combined/runner.lock`. Abort if another instance alive.
2. **Resume:** Read `/tmp/txocap-combined/state.json` if it exists. Check Bybit for open position.
3. **Reconcile:** Match engine state vs Bybit position. Resolve mismatches (see §6.6).
4. **Preload bars:**
   - Load last 2,000 bars from `data/klines/BTCUSDT-1m.jsonl`
   - Fetch gap via Bybit REST klines (250ms between requests to avoid rate limits)
   - Result: ~10,000–13,000 bars, full 24h lookback available immediately
5. **Stream:** Connect to Bybit BTCUSDT linear public trade stream
6. **Trade:** Strategy fires from the first bar after stream connects

### 6.3 Signal Loop (every 1-minute bar seal)

```
New bar sealed
  ├─ Has position?
  │   ├─ Stop check: pnlBps <= -100 → executeExit('stop')
  │   ├─ Deadline check: i >= exitDeadlineBar
  │   │   ├─ Score same dir AND |score| >= 8 → EXTEND (update deadline, save state)
  │   │   └─ Otherwise → executeExit('time')
  │   └─ (nothing — position alive, monitoring continues)
  └─ Flat + not in cooldown?
      ├─ |score| >= 10 AND direction not in cooldown → executeEntry()
      └─ (wait)
```

### 6.4 Entry Execution

1. Compute position size: `notional = min(equity × riskPct / stopDist, equity × leverage)`
2. Place maker limit at live bar close price
3. Chase: 6 attempts × 4 seconds each, repricing to current bar close each time
4. Miss: 1-bar cooldown, retry next bar if score still ≥ 10
5. After fill: verify Bybit size matches expected. Close excess with market order if mismatch.

### 6.5 Exit Execution

**Time/extension exit (maker):**
- Place at `mid − $0.10` (close long) or `mid + $0.10` (close short) — 1 tick from mid
- This places us at the front of the bid/ask queue
- Retry every ~5 seconds, repricing to live mid
- After **10 minutes**: market fallback (taker, logged as `MKTFB`)

**Stop exit (market):**
- 2 maker attempts, then immediate market order
- Correct behaviour: at −100 bps, price is running; maker won't fill

### 6.6 Exchange Reconciliation (every minute)

| Engine state | Bybit state | Action |
|---|---|---|
| Has position | No position | Position closed externally → clear engine state, log |
| Has position | Position (mismatch size/side) | Close Bybit, reset engine, log RECON |
| Flat | Has position | Orphan position → market close, log RECON |
| Flat | Flat | OK |

### 6.7 State Persistence

`state.json` written after every: fill, exit, status update.

```json
{
  "equity": 507.34,
  "peak": 512.00,
  "maxDdPct": 0.009,
  "totalFees": 4.20,
  "closedTrades": [...],
  "position": {
    "side": "short",
    "entryPrice": 74730,
    "sizeBtc": 0.020,
    "entryBar": 11847,
    "entryTs": 1776234000000,
    "exitDeadlineBar": 12087,
    "stopPrice": 75477,
    "peakFavBps": 12.3
  },
  "slCooldownDir": null,
  "slCooldownUntil": -1,
  "savedAt": 1776235000000
}
```

On restart: position is verified against Bybit before the engine re-adopts it.

### 6.8 Lock File

```
/tmp/txocap-combined/runner.lock
Contents: <PID>\n<ISO timestamp>\n

New process: if lock exists AND pid is alive → ABORT
             if lock exists AND pid is dead → stale lock, take over
```

Released on: `process.exit()`, SIGINT, SIGTERM, uncaughtException. **Never delete manually** — always `kill -SIGINT <pid>`.

### 6.9 Log Format

```
[HH:MM:SS] [STATUS] 2h15m | BTC $74883 | $507 (+$7) | SHORT @74730 since 21:04 | pnl +21bps peak 30bps | exit ~58min stop $75477 | 3 trades DD 1.2%
[HH:MM:SS] [SIGNAL] SHORT score=-39 [Thu(-34) Rev24h(-5)] 0.020btc hold=240m
[HH:MM:SS] [FILL]   SHORT 0.020btc @74730 stop=75477 (3att 10.6s)
[HH:MM:SS] [EXTEND] SHORT +21.0bps + score=-11 same dir — extending 240min (saves 4bps fees)
[HH:MM:SS] [EXIT]   TIME SHORT 21:04→04:10 (426m) @74730→74622 net=+64.6bps $9.89 maker bal=$517
[HH:MM:SS] [RECON]  ⚠ MISMATCH: engine=short 0.02btc  bybit=Buy 0.04btc — closing and resetting
```

### 6.10 Restart Procedure

```bash
# Clean shutdown (saves state for resume)
kill -SIGINT $(cat /tmp/txocap-combined/pid)

# Wait for lock to clear (~4 seconds)
sleep 4

# Restart (resumes from state.json automatically)
cd /home/mnm/workspaces/txocap
nohup node dist/runners/combined-demo.js \
  > /tmp/txocap-combined/demo.log 2>&1 &
echo $! > /tmp/txocap-combined/pid
```

---

## 7. Bugs Found & Fixed

### 7.1 Double-Fill on Entry
**Root cause:** On a maker entry MISS, `busy` was set back to `false` but `lastCooldownUntil` was not updated. The signal fired again at the very next bar. If multiple attempts happened to partially fill on Bybit before cancellation, the total position doubled (e.g., 0.028 BTC instead of 0.014 BTC).

**Fix:**
- On MISS: set `lastCooldownUntil = barIdx + 1` (only skip 1 bar, not the full hold period)
- After fill: fetch Bybit position, compare to expected size, close excess immediately with market order
- The `cancelAll()` inside `chaseLimitOrder` prevents double-fills across attempts

### 7.2 Taker Exits (All Exits Were Market Orders)
**Root cause:** Time exits placed the limit at `refPrice` — the bar close when the signal fired, not the current price. Over 6 attempts × 3 seconds = 18 seconds, price had moved. Order sat unmatched, fell back to market.

**Fix:** Use live `barAgg.get()[last].c` as the limit price, repriced each attempt. Place 1 tick ($0.10) inside the spread for Sell/Buy — this puts the order at the front of the bid/ask queue and gets hit within 1-2 attempts.

**Before:** All exits were taker (market fallback, paid 5.5 bps each)  
**After:** Most exits are maker (2 bps), taker fallback is rare (10+ min of price going away)

### 7.3 Extension Threshold Bug
**Root cause:** Extension required `|score| >= 10`. For a SHORT position on a Friday (Fri signal = -10.84), if BUYP was modestly above 0.5 at the deadline, score drifted to -8. The extension silently failed, the trade closed, and 8 minutes later when BUYP normalized the score recovered to -11 → new entry fired. Net result: 4 bps fees paid for no reason.

**First (wrong) fix:** Dropped to direction-only (threshold = 0). Backtested and found this was worse — very weak signals (score 1-3) average -13.5 bps at extension.

**Correct fix:** Threshold = 8. Blocks score 0-8 (too weak or dangerous), allows score 8+ (structural signal still active).

### 7.4 Zombie Processes
**Root cause:** Restart scripts killed the `bash -c` shell wrapper but not the actual `node` child process. Two node processes then both managed the same Bybit position, with interleaved log lines and potential double-exits.

**Fix:** Exclusive PID lock file. New process reads the lock, checks `process.kill(pid, 0)` (signal 0 = existence check only), and aborts if the process is alive. Released on every possible exit path: `process.on('exit')`, `process.on('SIGINT')`, `process.on('SIGTERM')`, `process.on('uncaughtException')`.

### 7.5 Infinity setTimeout Crash
**Root cause:** `setTimeout(fn, Infinity)` is treated by Node.js as `setTimeout(fn, 1)` (fires immediately, with a warning). The default duration was changed to `Infinity` to make the runner persistent, but this caused immediate shutdown.

**Fix:** `isFinite(DURATION_MS)` guard — only create the timer when a finite duration is specified via `TXOCAP_BENCH_MS` env var.

### 7.6 SL Directional Cooldown (Was: 120 min, Now: 0)
**Root cause:** Standalone backtest suggested 120-min same-direction cooldown after a stop was optimal (t=2.40 vs baseline t=1.98). But the full 243-combo sweep showed SL=0 consistently outperforms SL>0 when extensions are active (avg t=2.35 vs 1.70).

**Explanation:** Extensions already keep the position alive when the structural signal is active. By the time a stop fires at -100 bps, price has made a genuine 1% move against us. The next bar's score reflects current conditions — blocking it based on the old position's direction discards valid information.

**Fix:** `SL_COOLDOWN_BARS = 0` — no directional restriction after stops.

### 7.7 Preload Rate Limiting
**Root cause:** Bridging 5,000-8,000+ bars via Bybit REST with 120ms between requests triggered HTTP 429 (rate limit: 120 requests/min).

**Fix:** Increased delay to 250ms between requests (~240 req/min, well under the limit).

---

## 8. Current Configuration

```typescript
// src/runners/combined-demo.ts — as of April 2026

const RISK_PCT   = 0.03   // 3% of equity per trade
const STOP_BPS   = 100    // catastrophe stop: 1% adverse price move
const FEE_BPS    = 2      // Bybit demo maker fee (0% on MEXC live)
const LEVERAGE   = 100    // exchange leverage multiplier

// Signal parameters — optimised via 243-combo sweep (best: t=3.40)
ENTRY_THRESH     = 10     // min |score| to open a position
EXT_THRESH       = 8      // min |score| to extend at deadline
SL_COOLDOWN_BARS = 0      // no directional block after SL
MIN_HOLD         = 60     // min hold period (minutes = bars at 1-min)
MAX_HOLD         = 240    // max hold period (minutes)
```

### 8.1 Position Sizing

```
notional = min(equity × RISK_PCT / (STOP_BPS/10000), equity × LEVERAGE)
         = min(equity × 3%, equity × 100)   [stopDist = 1%]
         = equity × 3   (the 100× cap never binds at 3% risk with 100bps stop)

At $500 equity:   notional = $1,500  →  0.020 BTC @ $75,000
At $5,000 equity: notional = $15,000 →  0.200 BTC
At $50,000 equity: notional = $150,000 → 2.00 BTC  ← max practical for MEXC depth
```

### 8.2 Expected Performance by Venue

| Venue | Fee RT | Net/trade | t-stat | Trades/day | Daily PnL on $500 |
|---|---|---|---|---|---|
| **MEXC live** | **0 bps** | **+7.13 bps** | **3.78** | **5.4** | **+$5.78** |
| Bybit demo (current) | +4 bps | +3.13 bps | 1.71 | 5.4 | +$2.54 |

Note: the sweep-optimised config (SL=0) has t=3.40, mean=+10.84 bps on MEXC.

---

## 9. Projections & Risk

### 9.1 Monte Carlo Parameters
- **50,000 simulated paths** via bootstrap resampling of 1,993 historical trade outcomes
- **Starting capital:** $500
- **Risk:** 5% per trade (used for projections; current runner uses 3%)
- **Fees:** 0 bps (MEXC)

### 9.2 Equity Projections (5% risk, $500 start, MEXC)

| Horizon | p10 bad luck | p25 below avg | **Median** | p75 above avg | p90 good luck |
|---|---|---|---|---|---|
| 30 days | $449 | $616 | **$917** | $1,300 | $1,800 |
| 60 days | $621 | $950 | **$1,600** | $2,800 | $4,300 |
| 120 days | $1,300 | $2,400 | **$5,200** | $10,700 | $20,700 |
| 240 days | **$7,600** | $19,500 | **$54,800** | $154,500 | $377,900 |

Max DD (p90 over 240 days): **67% from peak**  
Ruin probability (`<$100`): **0.00%**  
Probability of losing money after 240 days: **0.1%**

### 9.3 Probability of Each Outcome at 1 Year (5% risk)

| Outcome | Dollar range | Probability |
|---|---|---|
| Lose money | < $500 | **0.00%** |
| Modest gain | $500 – $2,500 | 0.8% |
| 5× – 20× | $2,500 – $10,000 | 0.8% |
| 20× – 100× | $10,000 – $50,000 | 5.7% |
| 100× – 500× | $50,000 – $250,000 | 19.2% |
| 500×+ | > $250,000 | **74.4%** |

### 9.4 Honest Caveats

1. **One year of data.** t=3.78 is strong. Not conclusive. Edge could decay as BTC market structure evolves.
2. **Fees determine viability.** At Bybit (t=1.71), the edge is marginal and may not persist. MEXC (t=3.78) is required for confidence.
3. **Drawdowns are large.** At 5% risk, p90 worst DD = 67%. This means watching $200k peak equity fall to $66k before recovery. Psychologically hard. At 3% risk, p90 DD ≈ 50%.
4. **Walk-forward holds.** OOS t=3.30 > IS t=3.72 is unusual and encouraging. But one OOS period is still one data point.
5. **Calendar signals can change.** Thursday bearishness exists because of options expiry cycles. If the options market structure changes (e.g., CME vs Deribit dominance shifts), the signal may weaken.
6. **No position sizing cap.** As equity grows, position sizes approach MEXC's depth limits (~$150k notional = 2 BTC). Above that, scaling requires multiple exchanges or size reduction.

---

## 10. Next Steps

### Immediate (before going live on MEXC)
- [ ] Update `data/klines/BTCUSDT-1m.jsonl` to current date (last update: Apr 9 2026)
- [ ] Test MEXC API authentication: `src/lib/mexc/executor.ts` exists but untested against live endpoints
- [ ] Set leverage to 100x on MEXC futures account
- [ ] Verify MEXC futures account has USDT balance
- [ ] Run 1 test trade on MEXC to confirm fills, fees, and reconciliation work

### Short-term
- [ ] Monitor actual vs backtest signal frequency on MEXC (should be ~5.4/day)
- [ ] Track fill rates: entries should fill in <3 attempts, exits in <4 attempts
- [ ] Confirm 0% maker fee appears on actual trade confirmations (not just fee page)
- [ ] After 50+ live trades: compare actual t-stat to backtest t-stat

### Research
- [ ] Test stop=150 bps — sweep shows marginal t improvement; worth validating with monthly detail
- [ ] Weekend signal decomposition (Sun/Sat are both LONG — are they independent?)
- [ ] Time-varying signal weights — do DOW patterns degrade during high-volatility regimes?
- [ ] Auto-update klines at startup instead of requiring manual refresh

### Infrastructure
- [ ] Add Telegram/email alert: on SL hit, position open/close
- [ ] Compress log rotation (current log grows unbounded)
- [ ] Build MEXC version of runner using `src/lib/mexc/executor.ts`

---

## Appendices

### Appendix A: Research Files

| File | What it tests | Key result |
|---|---|---|
| `fullyear-backtest.ts` | Full year backtest, all signals | −3.98 bps (momentum dead), 0/13 months |
| `fullyear-edge-search.ts` | 497 hypothesis test on 1 year | 8 fee-surviving calendar signals |
| `edge-deepdive.ts` | Deep dive: DOW, BUYP, US gap, overnight | Overnight removed; rest confirmed |
| `combined-strategy.ts` | Combined scoring system | t=2.14 (Bybit), t=4.75 (MEXC) |
| `bitmex-analysis.ts` | Fee comparison + walk-forward | Gate.io trap; MEXC confirmed best |
| `extension-hypothesis.ts` | Extension saves fees | +1.05 bps/trade = 4bps × 26% of trades |
| `sl-cooldown-backtest.ts` | Post-SL same-direction trades | 30-60min bucket: t=−3.42. 120min solo optimal |
| `extension-threshold-backtest.ts` | What |score| threshold for extension | 8 optimal (t=3.01 vs 2.94 at 10, 2.60 at 0) |
| `full-sweep.ts` | 243-combo parameter sweep | SL=0 best when extensions active; t=3.40 |
| `risk-sizing.ts` | Kelly, drawdown, ruin analysis | Kelly=12%, 3% conservative, 5% aggressive |
| `compounding-projection.ts` | Long-range Monte Carlo | 0% ruin at 5% risk, 240-day median $54.8k |
| `mexc-backtest.ts` | MEXC-specific parameter sweep | Confirms MEXC 0% makes strategy viable |
| `momentum-optimize.ts` | Momentum strategy parameters | Momentum dead on full year |
| `momentum-rr-optimize.ts` | Risk:reward on momentum | All R:R variants worse than time exit |

### Appendix B: Signal Score Reference

```
Signal active conditions:
  DOW_Thu:   getUTCDay() === 4 (Thursday)     weight −34.41
  DOW_Wed:   getUTCDay() === 3 (Wednesday)    weight +19.05
  HOUR_21:   getUTCHours() === 21             weight +17.90
  BUYP_60m:  avgClosePos(60) > 0.58           weight +15.49
             avgClosePos(60) < 0.42           weight −15.49
  DOW_Sun:   getUTCDay() === 0 (Sunday)       weight +15.05
  DOW_Mon:   getUTCDay() === 1 (Monday)       weight +10.61
  DOW_Fri:   getUTCDay() === 5 (Friday)       weight −10.84
  HOUR_20:   getUTCHours() === 20             weight +9.49
  HOUR_23:   getUTCHours() === 23             weight −8.94
  US_GAP:    (h===13 || h===14 && m<=15)
             && lbRet(60) > 5 bps             weight −6.87 (fade up-gap)
             && lbRet(60) < -5 bps            weight +6.87 (fade down-gap)
  RSI_REV:   rsi(60) < 30                     weight +4.63
             rsi(60) > 70                     weight −4.63
  REV_24h:   lbRet(1440) > 30 bps            weight −5.00 (fade up-move)
             lbRet(1440) < -30 bps           weight +5.00 (fade down-move)

Score examples:

  Thursday 21:00 UTC, BTC up 80bps today:
    Thu(−34.41) + H21(+17.90) + Rev24h(−5.00) = −21.5 → SHORT

  Wednesday 21:00 UTC, BUYP=0.61:
    Wed(+19.05) + H21(+17.90) + BUYP(+15.49) = +52.4 → LONG (strong)

  Monday 14:00 UTC, US gap faded by BUYP:
    Mon(+10.61) + USgap(−6.87) + Rev24h(−5.0) = −1.3 → FLAT

  Friday 12:00 UTC, RSI=28:
    Fri(−10.84) + RSI(+4.63) = −6.2 → FLAT (below threshold)
```

### Appendix C: Key Numbers Quick Reference

```
SIGNAL EDGE
  Gross (0% fees):     +7.13 bps/trade
  Net on MEXC:         +7.13 bps/trade  (0 maker fee)
  Net on Bybit demo:   +3.13 bps/trade  (2 bps × 2 = 4 bps RT)
  Sweep optimum:       +10.84 bps (with SL=0 and extension improvements)

TRADING FREQUENCY
  Trades per year:     ~1,993 (pre-sweep) / ~1,200 (post-sweep with more extensions)
  Trades per day:      ~5.4
  Avg hold:            ~2.5 hours (with extensions)

RISK PARAMETERS
  Risk per trade:      3% (current) — Kelly is 12%, half-Kelly is 6%
  Stop loss:           100 bps = 1% adverse price move
  Leverage:            100×
  Stop in dollars:     equity × 3% = e.g. $15 at $500

POSITION SIZE
  At $500:  notional = $1,500  = 0.020 BTC @ $75k
  At $5k:   notional = $15,000 = 0.200 BTC
  At $50k:  notional = $150,000 = 2.00 BTC  (MEXC depth limit ≈ here)

DAILY EXPECTED PNL (at $500, 3% risk)
  MEXC:  $1,500 × 7.13bps × 5.4 trades = +$5.78/day
  Bybit: $1,500 × 3.13bps × 5.4 trades = +$2.54/day

WALK-FORWARD
  Train (Apr–Oct 2025):     t = 3.72
  Test  (Oct 2025–Apr 2026): t = 3.30  (stronger OOS than IS)

MONTE CARLO (5% risk, 50k paths, MEXC)
  240-day p10:  $7,600   p50: $54,800   p90: $377,900
  Max DD p90:   67% from peak
  Ruin:         0.00%
```

### Appendix D: Operational Files

```
/tmp/txocap-combined/
  demo.log       ← live log (append-only, never truncated)
  runner.lock    ← PID + timestamp, self-cleans on SIGINT
  state.json     ← persisted engine state for resume

data/klines/
  BTCUSDT-1m.jsonl  ← 525,075 bars, needs periodic refresh

./mexc             ← MEXC API credentials (AccessKey + SecretKey)
.env               ← Bybit demo credentials
```
