# TUTOR REPORT — REGOLE STRATEGIA RETTANGOLO (CRITICO)

> **Data**: 2026-07-18 17:05 Europe/Rome
> **Autore**: Mavis (tutore REGOLE)
> **Destinatari**: Verifier (audit) → Coder (fix) → disciplined-coder (controllo) → Mavis (riverifica)
> **Status**: 🔴 **CRITICO** — caso serio, Mattia ha dichiarato "c'è una posizione BTC aperta da strategia rettangolo totalmente sbagliata"
> **Regola Mattia 18/07 17:00**: Mavis NON deve toccare codice, deve SOLO identificare le regole sbagliate e passare gli input al Coder via Verifier.

---

## 🔴 CONTESTO

Mattia segnala che c'è una **posizione BTC aperta su Bybit** che è arrivata dalla strategia Rettangolo, e che le regole della strategia sono **scritte in modo errato**. La strategia sta producendo **segnali sbagliati** che il runner esegue fedelmente.

**Cosa NON faccio io (tutore)**:
- ❌ NON chiudo la posizione BTC (azione)
- ❌ NON modifico `rettangolo_strategy.py`, `sltp_engine.py`, `rettangolo_runner.py` (azione Coder)
- ❌ NON killo `rettangolo_runner.py` (azione Supervisor)

**Cosa FACcio io (tutore)**:
- ✅ Analizzo il codice delle 3 strategie
- ✅ Identifico le regole sbagliate con evidenza
- ✅ Produco report dettagliato per Verifier con **input precisi per Coder**

---

## 🔴 CRIT-RETT-[1] — Pattern 2 candele TROPPO PERMISSIVO (ROOT CAUSE del bug BTC)

**File**: `G:\AI TRADING ENGINE\live_deploy\rettangolo_strategy.py`
**Righe**: 36-55 (`_is_two_candle_pattern`)
**Regola originale del video "Diari di Trading"** (citata in `STRATEGY_RULES.md`):
> "LONG: prima candela ribassista (rossa) + seconda verde/Doji/Hammer rialzista"
> "SHORT: prima candela rialzista (verde) + seconda rossa/Doji/Hammer ribassista"

**Cosa dice il codice**:
```python
def _is_two_candle_pattern(cur: Dict, prev: Dict, side: str) -> bool:
    if side == "SHORT":
        prev_bullish = prev["close"] >= prev["open"]
        cur_bearish = cur["close"] < cur["open"]
        cur_pattern = is_doji(cur) or has_hammer_bearish(cur) or cur_bearish  # ← BUG
        return prev_bullish and cur_pattern
    else:  # LONG
        prev_bearish = prev["close"] < prev["open"]
        cur_bullish = cur["close"] >= cur["open"]
        cur_pattern = is_doji(cur) or has_hammer_bullish(cur) or cur_bullish  # ← BUG
        return prev_bearish and cur_pattern
```

**Il bug**: `cur_pattern = is_doji OR has_hammer_bearish OR cur_bearish` matcha **qualsiasi candela rossa** come pattern valido SHORT, anche candele bearish generiche SENZA corpo piccolo né ombre lunghe.

**Effetto pratico BTC**: il pattern "prima verde + seconda rossa" matcha **TUTTE** le coppie di candele consecutive dove la prima chiude ≥ open e la seconda chiude < open. In mercato laterale/intraday rumoroso, BTCUSDT a 30m produce dozzine di questi pattern al giorno → **pioggia di segnali falsi**.

**Cosa dovrebbe essere (per Coder)**:
```python
# Pattern 2 candele CORRETTO dal video Diari di Trading
# La SECONDA candela DEVE essere Doji o Hammer (conferma di indecisione/inversione)
# NON basta una qualsiasi candela rossa
def _is_two_candle_pattern(cur: Dict, prev: Dict, side: str) -> bool:
    if side == "SHORT":
        prev_bullish = prev["close"] >= prev["open"]
        # SOLO Doji o Hammer ribassista, NO candele rosse generiche
        cur_pattern = is_doji(cur) or has_hammer_bearish(cur)
        return prev_bullish and cur_pattern
    else:  # LONG
        prev_bearish = prev["close"] < prev["open"]
        # SOLO Doji o Hammer rialzista, NO candele verdi generiche
        cur_pattern = is_doji(cur) or has_hammer_bullish(cur)
        return prev_bearish and cur_pattern
```

**Violazione**: P005-equivalente (regola di strategia non rispettata). Non c'è un paletto Charter per il pattern matching del Rettangolo, ma il pattern è dichiarato in `STRATEGY_RULES.md` e il codice NON lo rispetta.

---

## 🔴 CRIT-RETT-[2] — Nessun filtro di QUALITÀ del breakout

**File**: `G:\AI TRADING ENGINE\live_deploy\rettangolo_strategy.py`
**Righe**: 70-79 (`compute_signal`, loop di ricerca breakout)

**Cosa dice il codice**:
```python
for j in range(idx - lookback, idx):
    bar = intraday_history[j]
    if bar["close"] > rng_top:
        breakout_dir = "up"
        breakout_idx = j
        break
    if bar["close"] < rng_bot:
        breakout_dir = "down"
        breakout_idx = j
        break
```

**Il bug**: un breakout di 0.01% (1 centesimo) viene trattato come breakout valido. Non c'è verifica:
- che la candela abbia chiuso **chiaramente** oltre il range
- che il corpo della candela sia sufficientemente grande
- che il volume sia confermato

**Cosa dovrebbe essere (per Coder)**:
```python
# Breakout di qualità: la candela deve chiudere ALMENO 0.1% oltre il range
# (configurabile: BREAKOUT_MIN_PCT = 0.001)
BREAKOUT_MIN_PCT = 0.001  # 0.1% minimo oltre il range

for j in range(idx - lookback, idx):
    bar = intraday_history[j]
    if bar["close"] > rng_top:
        breakout_pct = (bar["close"] - rng_top) / rng_top
        if breakout_pct < BREAKOUT_MIN_PCT:
            continue  # breakout troppo debole, skip
        breakout_dir = "up"
        breakout_idx = j
        break
    if bar["close"] < rng_bot:
        breakout_pct = (rng_bot - bar["close"]) / rng_bot
        if breakout_pct < BREAKOUT_MIN_PCT:
            continue
        breakout_dir = "down"
        breakout_idx = j
        break
```

**Effetto pratico BTC**: su BTCUSDT 30m in mercato laterale, candele con close 0.05% oltre il range vengono trattate come breakout → falso segnale.

---

## 🔴 CRIT-RETT-[3] — Nessun filtro sulla DIMENSIONE del range

**File**: `G:\AI TRADING ENGINE\live_deploy\rettangolo_strategy.py`
**Righe**: 67-69

**Cosa dice il codice**:
```python
rng_size = rng_top - rng_bot
if rng_size <= 0:
    return None
```

**Il bug**: accetta range di 0.01 USDT come range valido. Su BTCUSDT a 64000, un range daily di 0.01 USDT (0.00002%) viene trattato come rettangolo valido.

**Cosa dovrebbe essere (per Coder)**:
```python
# Range minimo: almeno 0.3% del prezzo medio (evita range piatti)
RANGE_MIN_PCT = 0.003  # 0.3% minimo

rng_size = rng_top - rng_bot
rng_mid_price = (rng_top + rng_bot) / 2
if rng_size <= 0 or (rng_size / rng_mid_price) < RANGE_MIN_PCT:
    return None  # range troppo stretto, no signal
```

**Effetto pratico BTC**: su BTCUSDT 64000, daily range deve essere almeno ~192 USDT (0.3%). Se daily è piatto (50-100 USDT), nessun segnale viene generato.

---

## 🟡 CRIT-RETT-[4] — Lookback del breakout troppo ampio (20 ore su 2H)

**File**: `G:\AI TRADING ENGINE\live_deploy\rettangolo_strategy.py`
**Riga**: 72
**Regola**: `lookback = min(10, idx)` → 10 candele indietro

**Problema**: con TF 2H (rettangolo_runner.py:24 hardcoded `TIMEFRAME = "120"`, ma CSV dice 30m → coerenza rotta, vedi CRIT-RETT-[6]), lookback=10 = 20 ore di "memoria del breakout". Se il mercato si muove e poi torna, un breakout vecchio viene ripreso.

**Cosa dovrebbe essere (per Coder)**: ridurre lookback a 3-5 candele (6-15 ore su 2H, o 1.5-2.5 ore su 30m). Il retest deve avvenire **vicino** al breakout, non a distanza di un giorno.

---

## 🟡 CRIT-RETT-[5] — TIMEFRAME hardcoded = 120 (2H) in rettangolo_runner.py ma CSV dice 30m

**File**: `G:\AI TRADING ENGINE\live_deploy\rettangolo_runner.py`
**Riga**: 24
```python
TIMEFRAME = "120"  # 2H (hardcoded, matches rettangolo_assets.csv)
```

**Problema**: il commento dice "matches rettangolo_assets.csv" ma il CSV ha `30m` per tutti gli asset rettangolo. La costante `TIMEFRAME` non viene effettivamente usata (viene sovrascritta da `tf_int` letto dal CSV), MA è **codice morto fuorviante**.

**Cosa dovrebbe essere (per Coder)**: rimuovere la costante `TIMEFRAME` o commentarla esplicitamente come "DEPRECATED, letto da CSV".

---

## 🔴 CRIT-RETT-[6] — 3 implementazioni DIVERSE di `prev_daily` (drift critico)

**File 1**: `rettangolo_monitor.py:130-145` — usa custom box aggregato London 9-9 (24h su Europe/London)
**File 2**: `rettangolo_runner.py:69` — usa Bybit daily K-line (0-24h UTC) → `prev_daily = daily[-2]`
**File 3**: `sltp_engine.py:120` circa — usa Bybit daily K-line (0-24h UTC) → `prev_daily = daily[-2]`

**Il bug**: il **segnale** viene generato da `rettangolo_runner.py` usando il range Bybit daily (UTC 0-24). Il **SL/TP** viene applicato da `sltp_engine.py` usando lo stesso range. Quindi sono coerenti.

**MA**: il **monitor visuale** (HTML che Mattia vede) usa un range DIVERSO (custom box London 9-9). Questo significa che Mattia vede un rettangolo sul grafico che **non corrisponde** al rettangolo effettivamente usato per generare i trade.

**Effetto**: confusione diagnostica. Mattia vede un breakout a un livello, ma il sistema ha usato un altro livello per aprire il trade.

**Cosa dovrebbe essere (per Coder)**: UNA SOLA definizione di `prev_daily` in un modulo comune (es. `rettangolo_range.py`):
```python
# rettangolo_range.py (NUOVO)
def compute_prev_daily_range(client, symbol):
    """Calcola range daily precedente in modo COERENTE in tutto il sistema.
    Default: Bybit daily K-line (0-24h UTC), che è la fonte usata da runner + sltp.
    Opzionale: custom box London 9-9 se Mattia conferma che è la regola vera.
    """
    daily = client.fetch_ohlcv(symbol, "D", 5)
    if len(daily) < 2:
        return None
    return {
        "high": daily[-2][2],  # high
        "low": daily[-2][3],   # low
        "ts": daily[-2][0],
    }
```

E `rettangolo_monitor.py` dovrebbe importare questa stessa funzione invece di calcolare il custom box.

---

## 🟡 CRIT-RETT-[7] — Manca safety check su size in compute_sltp_rettangolo

**File**: `G:\AI TRADING ENGINE\live_deploy\sltp_engine.py:compute_sltp_rettangolo`
**Righe**: ~150-180

**Contesto**: `rettangolo_runner.py` apre la posizione con qty = `ORDER_VALUE_USD / price` (nozionale 1500 = margin 500 × leva 3, OK con P015). MA `sltp_engine.py` non ha un check analogo — si limita a calcolare SL/TP.

**Cosa manca (per Coder)**: aggiungere in `sltp_engine.py` un log periodico che verifica che la size della posizione aperta sia coerente con P015:
```python
# Esempio: log ogni 5 cicli se size != 1500 USDT nozionale ± 5%
if pos:
    nozionale = size * avg_price
    if not (1425 <= nozionale <= 1575):
        log(f"⚠️ {symbol} size {size} * price {avg_price} = {nozionale} USDT nozionale, FUORI range P015 [1425-1575]")
```

Non è un fix critico (il sizing è già corretto in rettangolo_runner), ma è un check di vigilanza.

---

## 🔴 CRIT-RETT-[8] — orders.log BTC: ordini recenti NON da Rettangolo

**Cosa ho verificato**:
- `rettangolo_runner.log` 18/07 16:31 → 17:02: 27 entry "processo 5 asset" consecutive, MA **NESSUNA riga "ORDER APERTA"** → il runner NON ha aperto BTCUSDT di recente
- `orders.log` ha ordini BTCUSDT 18/07 13:50 e 15:50 con `strategy=vptr3` (qty 0.001, nozionale 64 USDT = TEST)
- 17/07 18:54: 6 ordini BTCUSDT buy qty=0.001 strategy=audit_test

**Cosa significa**:
- La posizione BTC attualmente aperta su Bybit **NON** risulta aperta da Rettangolo nel log recente
- Potrebbe essere:
  1. Posizione aperta MOLTO tempo fa (settimane) da Rettangolo con logica precedente
  2. Posizione aperta manualmente da Mattia
  3. Posizione aperta da VPTR3 Pine (alert Pine "Opposite signal" interpretato male)
  4. Posizione "fantasma" rimasta da un fix di `bybit_demo_client.py`

**Cosa chiedo a Mattia** (per Coder/Verifier):
- Quando è stata aperta la posizione BTC?
- Ha un `orderId` o un timestamp di apertura?
- È un trade Rettangolo VERO o un errore di sistema?

---

## 📊 STATO REGOLE RETTANGOLO (snapshot)

| Componente | Regola | Rispettata? | Note |
|---|---|---|---|
| `_is_two_candle_pattern` SHORT | "prima verde + seconda Doji/Hammer rosso" | ❌ VIOLA | Anche candele rosse generiche matchano |
| `_is_two_candle_pattern` LONG | "prima rossa + seconda Doji/Hammer verde" | ❌ VIOLA | Anche candele verdi generiche matchano |
| Breakout quality filter | Forza minima del breakout | ❌ MANCA | 0.01% vale come breakout |
| Range size filter | Dimensione minima del range | ❌ MANCA | Range di 0.01 USDT accettato |
| Lookback breakout | Numero candele indietro | 🟡 DEBOLE | 10 candele = 20h su 2H è troppo |
| Timeframe consistency | Hardcoded vs CSV | 🟡 CODICE MORTO | TIMEFRAME="120" non usato |
| prev_daily definition | Sorgente range daily | ❌ DIVERGENZA | 3 implementazioni diverse |
| Size P015 compliance | Nozionale 1500 USDT | ✅ OK | Solo se leva=3 |
| P001 No media-up | No doppia entry su stesso symbol | ✅ OK | check in runner riga 96-103 |
| P011 set_leverage prima | set_leverage prima di market | ✅ OK | runner riga 89-94, 116-121 |

**Score regole rispettate**: 3 su 10.

---

## 📋 INPUT SPECIFICI PER CODER (passando per Verifier)

Il Coder deve:

### Fix 1 (CRIT-RETT-[1]) — Pattern 2 candele
- Modificare `_is_two_candle_pattern` in `rettangolo_strategy.py` righe 36-55
- Rimuovere `or cur_bearish` / `or cur_bullish` dalle rispettive righe
- Aggiungere test unitario con candele non-pattern che NON devono matchare

### Fix 2 (CRIT-RETT-[2]) — Breakout quality
- Aggiungere costante `BREAKOUT_MIN_PCT = 0.001` (0.1% minimo oltre range)
- Modificare loop breakout in `compute_signal` righe 73-83
- Aggiungere test: breakout 0.05% NON matcha, breakout 0.2% matcha

### Fix 3 (CRIT-RETT-[3]) — Range size
- Aggiungere costante `RANGE_MIN_PCT = 0.003` (0.3% minimo)
- Modificare check `rng_size <= 0` in riga 68
- Aggiungere test: range 0.01 USDT NON matcha, range 1% matcha

### Fix 4 (CRIT-RETT-[4]) — Lookback
- Cambiare `lookback = min(10, idx)` a `lookback = min(5, idx)` (configurabile)
- Aggiungere commento esplicito

### Fix 5 (CRIT-RETT-[5]) — TIMEFRAME dead code
- Rimuovere o commentare `TIMEFRAME = "120"` in `rettangolo_runner.py:24`

### Fix 6 (CRIT-RETT-[6]) — prev_daily unificato
- Creare `rettangolo_range.py` con funzione `compute_prev_daily_range(client, symbol)`
- Modificare `rettangolo_runner.py`, `sltp_engine.py`, `rettangolo_monitor.py` per usare questa funzione
- Decisione: usare Bybit daily K-line (semplice) o custom box London 9-9 (complesso ma coerente con intento Mattia)

### Fix 7 (CRIT-RETT-[7]) — Size check in sltp_engine
- Aggiungere log periodico se nozionale posizione FUORI range P015

---

## ❓ DOMANDE PER MATTIA (per Verifier)

1. **La posizione BTC è ancora aperta su Bybit?** Se sì, qty, side, entry price, quando aperta.
2. **Vuoi che il Coder faccia TUTTI i 7 fix, o solo i 3 CRITICI (1, 2, 3)?**
3. **Per CRIT-RETT-[6] (prev_daily unificato)**: usare Bybit daily K-line (0-24h UTC, semplice) o custom box London 9-9 (complesso, coerente con intento iniziale Mattia)?
4. **Vuoi che la posizione BTC attuale venga chiusa?** Se sì, è azione del Coder (via Supervisor) o lasci al Supervisor/Coder di gestirla?
5. **Il backup `webhook_receiver.py.bak_20260718-120900` copre anche Rettangolo o solo webhook?** Se copre solo webhook, c'è un rischio che i fix al Rettangolo rompano la stabilità del webhook.

---

## 🚨 STATO

- **Nessuna azione eseguita** da Mavis (tutore)
- **Nessun file modificato**
- **Nessun processo killato**
- **Nessuna posizione chiusa**
- Report pronto per **Verifier** → audit indipendente → **Coder** → fix

**Output destinato a**: Verifier per audit. Se Verifier dà pass, passa a Coder con questo report come specifica.
