# TITAN V3 — Production Hotfix Pack
**Date:** 2026-02-19
**Scope:** Issues #2-5 from live log audit
**Files changed:** 4 files, 6 bugs fixed

---

## Issue #1: Database Schema Mismatch ✅ ALREADY FIXED
- `roll_spread` column added to Supabase via ALTER TABLE

---

## Issue #2: Directional Lock (SHORT-only bias)
**Root cause:** Two compounding problems prevented long trades from ever firing.

### Fix 2a: `signals/confluence_scorer.py`
**Problem:** MTF alignment scoring was binary — all 4 timeframes must agree or score = 0 (out of 2 points). At market inflection points (where the best entries occur), lower timeframes flip first while higher timeframes lag. BTC was stuck at 5-6 confluence because it lost 2 points every time a single timeframe disagreed.

**Change:** Graduated MTF scoring:
- 4/4 aligned = +2.0 (unchanged)
- 3/4 aligned = +1.0 (NEW — catches inflection setups)
- 2/4 aligned + 4H confirms = +0.5 (NEW — early trend entries)
- <2 aligned = +0.0 (unchanged)

This means a BTC setup with OB + sweep + CVD + session + toxicity (5 points) plus 3/4 MTF alignment (+1) now scores 6 → still needs one more factor but is no longer structurally impossible.

### Fix 2b: `signals/signal_generator.py` line 198-206
**Problem:** When MTF direction = "bearish", the generator ONLY evaluated SHORT trades. Long setups were never even scored — they were filtered before reaching confluence. This double-punished counter-trend setups.

**Change:** Always evaluate BOTH sides. The confluence scorer already penalizes going against MTF direction (loses up to 2 points). Let the scoring system decide, not a pre-filter.

---

## Issue #3: Max Positions Bottleneck
**Root cause:** Three compounding problems.

### Fix 3a: `signals/signal_filter.py` line 39
**Problem:** `MAX_OPEN_POSITIONS = 3` with 5 active symbols. System was 3/3 full and blocking every passing signal for 9+ minutes.

**Change:** `MAX_OPEN_POSITIONS = 5` — one slot per active symbol. With proper risk management (Kelly sizing + drawdown adjustment), 5 concurrent positions is safe.

### Fix 3b: `signals/signal_filter.py` — per-symbol duplicate prevention
**Problem:** No guard against opening 2 positions on the same symbol. With max raised to 5, this becomes important.

**Change:** Added `_active_symbols` set with `register_position()` / `unregister_position()` methods. Hard filter rejects signals for symbols that already have an open position.

### Fix 3c: `signals/signal_generator.py` line 121-122
**Problem:** Signal generator subscribed to `position.opened` / `position.closed` events, but execution manager publishes `trade.opened` / `trade.closed`. Event name mismatch meant position count NEVER updated from the default (0). The filter was seeing stale counts from some other path.

**Change:** Subscribe to `trade.opened` / `trade.closed` and properly increment/decrement `_open_positions`. Also wire per-symbol tracking into these handlers.

---

## Issue #4: AI Confidence Gate Rubber-Stamping
**Root cause:** Two problems made the gate non-selective.

### Fix 4a: `ai/confidence_gate.py` line 41
**Problem:** `DEFAULT_THRESHOLD = 0.65`. Since signals only reach the AI gate AFTER passing confluence ≥ 7, they already have strong MTF, OB, CVD etc. The AI gate was scoring pre-filtered signals in the 0.68-0.79 range — everything passed.

**Change:** `DEFAULT_THRESHOLD = 0.72`. This creates real discrimination. Signals with weak regime, off-hours session, or no win history will now fail.

### Fix 4b: `ai/confidence_gate.py` line 241
**Problem:** `win_momentum` component gave 0.08 free points when there was no trade history. Combined with other "neutral" defaults across components, the floor was ~0.55+ even for mediocre setups.

**Change:** No trade history = 0.0 points. Must be earned through actual winning trades.

### Fix 4c: `signals/signal_filter.py` line 38
**Problem:** `MIN_AI_CONFIDENCE = 0.65` in the hard filter didn't match the gate threshold.

**Change:** `MIN_AI_CONFIDENCE = 0.72` — synchronized with gate.

---

## Issue #5: Cooldown Dominance / Redundant Computation
**Root cause:** Expensive pipeline running for signals that will be blocked.

### Fix 5a: `signals/signal_generator.py` — early exit in `_evaluate_setup()`
**Problem:** Every 15 seconds, the generator ran full confluence scoring, feature extraction, and AI prediction for every symbol+side combo — then the signal filter rejected 95%+ due to cooldown or max_positions. Wasted ~19,000+ cycles in 9 minutes of logs.

**Change:** Check cooldown and max_positions FIRST before any scoring. If either would block, return None immediately. This eliminates ~95% of wasted computation and reduces CPU load.

---

## Summary of Constants Changed

| Setting | Old | New | Rationale |
|---------|-----|-----|-----------|
| MAX_OPEN_POSITIONS | 3 | 5 | 1 per active symbol |
| MIN_AI_CONFIDENCE (filter) | 0.65 | 0.72 | Match gate threshold |
| DEFAULT_THRESHOLD (AI gate) | 0.65 | 0.72 | Actual discrimination |
| Win momentum (no history) | 0.08 | 0.00 | No free points |
| MTF alignment | binary 0/2 | graduated 0/0.5/1/2 | Partial credit |
| Side evaluation | MTF-locked | Both sides always | Let scorer decide |

---

## Installation

Copy these 4 files to your `titan/` directory, replacing the originals:

```
signals/confluence_scorer.py
signals/signal_generator.py
signals/signal_filter.py
ai/confidence_gate.py
```

Then restart: `python -m titan.main`
