# V16 CHANGELOG

---

## 2026-06-07 — Sesión de debugging y optimización completa

### ESTADO INICIAL AL ABRIR SESIÓN
- 6 servicios live (systemd) + 6 paper (tmux) corriendo desde 22:44 UTC del 06/06
- Balance: **$453.20** pUSD en wallet
- Problema reportado: "bot reporta 2 órdenes abiertas pero no las veo en Polymarket"

---

### BUG 1 — Código viejo en memoria (servicios no reiniciados tras cambio)
**Síntoma:** logs mostraban `[TAKE LIVE]` pero executor.py tenía `[TAKE FILLED]`  
**Causa:** executor.py fue modificado a las 22:56 pero servicios arrancaron a las 22:44. Python cargó el `.pyc` viejo (market orders FOK) y lo mantuvo en memoria.  
**Efecto:** BTC/SOL/XRP pagaron precios de 0.67-0.79 (market orders barriendo el libro) vs paper 0.44-0.54.  
**Fix:** Reinicio completo + borrar `__pycache__` antes de cada cambio de código.  
**Lección:** Siempre `rm -rf __pycache__` antes de reiniciar servicios.

---

### BUG 2 — FOK market orders barren el libro
**Síntoma:** Live pagó BTC @ 0.78 cuando paper pagó 0.54 (mismo mercado, misma ventana)  
**Causa:** `MarketOrderArgsV2(amount=2.5_USD)` con FOK consume todos los ask levels hasta gastar $2.5, sin control de precio.  
**Fix:** Vuelta a `OrderArgsV2(price=ask+bump)` con GTC (como V12).  
**Archivo:** `v16/executor.py` — reemplazado `create_and_post_market_order` por `create_and_post_order`

---

### BUG 3 — GTC instant-cancel perdía el 80% de ventanas
**Síntoma:** Live operaba solo 18-23% de las ventanas que paper capturaba. BNB: 0 trades.  
**Causa:** Al recibir `status=live`, se cancelaba inmediatamente. Polymarket tarda 1-3s en matchear.  
**Fix:** Esperar `LIVE_ORDER_WAIT = 4` segundos antes de intentar cancelar.  
**Archivo:** `v16/executor.py` — nuevo branch `status=live → sleep(4) → cancel_check()`

---

### BUG 4 — Order spam (una orden por tick)
**Síntoma:** En una ventana de 5min se llegaron a colocar 8+ órdenes GTC distintas.  
**Causa:** Tras cada TIMEOUT, `_entered=False`, el siguiente tick disparaba nueva señal → nueva orden.  
**Fix:** Flag `_entry_attempted` en bot.py — una sola orden por ventana, independientemente del resultado.  
**Archivo:** `v16/bot.py` — `self._entry_attempted = False` en `__init__` y `_reset_window_state`, check en `tick()`

---

### BUG 5 — Fill no detectado (orden llenada reportada como TIMEOUT)
**Síntoma:** Orden ETH llenada a 0.44, 6.1 shares, ganó +$6.10 en Polymarket → bot lo registró como TIMEOUT.  
**Causa:** El CLOB Polymarket devuelve órdenes ya-filled en la lista `canceled`, no en `not_canceled`. `_cancel_check` interpretaba "cancelada" como "no llenada".  
**Fix:** Usar comparación de balance antes/después como fuente de verdad. Si `bal_before - bal_after >= cost * 0.5` → fill confirmado aunque el cancel diga otra cosa.  
**Archivo:** `v16/executor.py` — `_cancel_check()` reemplazado por balance-delta verification  
**Nota:** Trade ETH +$6.10 no quedó registrado en trades.csv de esa sesión (logs reseteados).

---

### BUG 6 — CLOBFeed: precios fantasma (causa raíz de divergencia live vs paper)
**Síntoma:** SOL live veía `ask_yes=0.39` pero paper veía `ask_yes=0.48`. Live intentaba comprar a 0.40, el mercado real tenía ask en 0.48 → TIMEOUT garantizado. Pattern sistemático: paper siempre veía precios "mejores".  
**Causa:** `CLOBFeed._on_msg` tomaba `min(all_prices_in_message)` sin filtrar `size=0`. Los mensajes `price_change` con cancelaciones (size=0) corrompían el best_ask con precios de niveles ya eliminados del libro.  
```python
# CÓDIGO ROTO:
if a:
    self.best_ask = min(float(x["price"]) for x in a)  # ignora size=0 = cancelaciones
```
**Fix:** CLOBFeed ahora mantiene un libro local `dict[price → size]`:
- Snapshot inicial (`event_type=book`): reconstruye libro completo, filtra size=0
- Updates incrementales (`event_type=price_change`): aplica deltas, elimina niveles con size=0
- Al reconectar: resetea libro para forzar snapshot fresco
**Archivo:** `v16/feeds.py` — reescrito completamente `_on_msg` y añadidos `_bids`, `_asks` dicts

---

### CAMBIO — Bump 0.01 → 0.05
**Motivo:** Incluso con CLOBFeed corregido, ask+0.01 era insuficiente para cruzar el spread real en mercados con pocos market makers.  
**Tradeoff:** Pagamos hasta 5 cents sobre ask (controlado) vs market orders que podían pagar +0.27.  
**Archivo:** `v16/config.py` — `AGGRESSIVE_BUMP = 0.05`

---

### CAMBIO — Confirm ticks 1 → 3
**Motivo:** WR de 33-43% con confirm=1. Intento de filtrar spikes de 1-2 segundos que generaban señales falsas.  
**Resultado:** No mejoró significativamente el WR. Sí redujo frecuencia de trades (especialmente BTC y ETH que volvieron a tener TIMEOUTS consistentes).  
**Estado actual:** `CONFIRM_TICKS = 3` activo.

---

### FEATURE — Botón de emergencia en dashboard
**Funcionalidad:** Botón rojo pulsante "⛔ STOP ALL" en el topbar del dashboard.  
**Mecanismo:**
1. Click → confirm dialog → PHP escribe `/logs/EMERGENCY_STOP`
2. Cada bot detecta el archivo en el siguiente tick (≤1 segundo)
3. Llama a `self.shutdown()` → cancela órdenes abiertas, cierra WebSockets limpiamente
4. Botón cambia a gris "BOTS DETENIDOS — click para limpiar"
5. Click de nuevo → elimina el archivo (permite reinicio manual posterior)
**Archivos:**
- `v16_multi_api.php` — acciones `emergency_stop` y `clear_stop`
- `v16_multi_dashboard.html` — `.btn-emergency` CSS + `emergencyStop()` JS
- `v16/bot.py` — check de `EMERGENCY_STOP_FILE` al inicio de cada `tick()`

---

### CAMBIO — Paper bots desactivados
**Motivo:** Paper y live corrían como procesos independientes con conexiones WebSocket separadas al CLOB. Posible interferencia en latencia + comparación con paper era engañosa (paper asume fill infinito a ask+0.01, nunca tiene TIMEOUT).  
**Estado:** Solo 6 servicios live activos (systemd). Paper eliminado.  
**Para restaurar paper:** `cd /var/www/html/Poly/V16 && for asset in btc eth sol xrp doge bnb; do tmux new-session -d -s "v16-${asset}-paper" "bash v16_run.sh ${asset} 5 2>&1 | tee logs/${asset}/run.log"; done`

---

### ANÁLISIS — WR y EV de la estrategia
**Datos (29 trades settleados):**
- Win rate: 45% momentum / 55% si se invirtiera todo
- Win medio: +$2.32 | Loss medio: -$2.79 | Ratio: 0.83x
- Break-even necesario: **55% WR** (por asimetría de precios 0.50-0.58)
- EV por trade con 45% WR: **-$0.47**

**Correlación |move| en entrada vs WR:** ninguna. Movimientos de 0.08-0.25% producen similar WR (~44%).

**Hipótesis:** El bot entra cuando el momentum lleva 3 ticks confirmados → entra en extremos locales que tienden a revertir → WR < 50%.

---

### CAMBIO — Inversión de señal por activo
**Análisis (datos históricos de la sesión):**

| Asset | WR momentum | WR invertido | N trades | Acción |
|-------|------------|--------------|---------|--------|
| SOL   | 75%        | 25%          | 4       | MANTENER momentum |
| DOGE  | 57%        | 43%          | 7       | MANTENER momentum |
| ETH   | 40%        | 60%          | 5       | **INVERTIR** |
| XRP   | 25%        | 75%          | 4       | **INVERTIR** |
| BNB   | 0%         | 100%         | 3       | **INVERTIR** |
| BTC   | 33%        | 67%          | 3       | Sin cambio (muestra insuficiente) |

**Implementación:**
- `v16/config.py` — flag `invert_signal: bool = False` en Config + `"invert_signal": True` en ETH, XRP, BNB
- `v16/strategy.py` — 3 líneas: si `cfg.invert_signal`, flippear YES↔NO antes de construir el Signal
- BTC/SOL/DOGE: lógica momentum sin cambios

**Advertencia:** Muestras de 3-7 trades son estadísticamente débiles (necesitan ~30+ para 95% confianza). Revisar tras acumular 20+ trades por activo invertido.

---

## ESTADO FINAL DE LA SESIÓN

### Configuración activa
```
Confirm ticks:  3
Bump:           +$0.05
Lógica ETH:     INVERTIDA (apuesta contra momentum)
Lógica XRP:     INVERTIDA
Lógica BNB:     INVERTIDA
Lógica BTC:     momentum normal
Lógica SOL:     momentum normal
Lógica DOGE:    momentum normal
Paper bots:     DESACTIVADOS
```

### Balance
- Inicio sesión: $453.20
- Fin sesión: ~$435 (pérdidas durante debugging y experimentos)

### Servicios activos
```bash
systemctl status v16-btc.service v16-eth.service v16-sol.service \
                 v16-xrp.service v16-doge.service v16-bnb.service
```

### Archivos clave modificados
| Archivo | Cambio |
|---------|--------|
| `v16/executor.py` | GTC limit, 4s wait, balance-delta fill detection |
| `v16/feeds.py` | CLOBFeed con libro local, filtra size=0 |
| `v16/bot.py` | `_entry_attempted`, emergency stop check |
| `v16/strategy.py` | `invert_signal` per-asset |
| `v16/config.py` | bump=0.05, confirm=3, invert_signal por activo |
| `v16_multi_api.php` | Acciones emergency_stop / clear_stop |
| `v16_multi_dashboard.html` | Botón ⛔ STOP ALL |

### Comandos útiles de diagnóstico
```bash
# Ver trades actuales
for a in btc eth sol xrp doge bnb; do echo "==$a=="; cat logs/${a}_live/trades.csv; done

# Ver estado instantáneo
for a in btc eth sol xrp doge bnb; do
  python3 -c "import json; d=json.load(open('logs/${a}_live/state.json')); \
  print('$a:', d.get('phase'), 'entered='+str(d.get('entered')), \
  'ask_y='+str(d.get('ask_yes',0)), 'move='+str(d.get('move_pct',0))+'%')" 2>/dev/null
done

# Ver señales y fills recientes (cualquier asset)
grep -E "SIGNAL|TAKE FILLED|TAKE DELAYED|TAKE TIMEOUT|EMERGENCY" logs/eth_live/run.log | tail -20

# Reinicio limpio completo (borra logs)
sudo systemctl stop v16-{btc,eth,sol,xrp,doge,bnb}.service
for d in btc_live eth_live sol_live xrp_live doge_live bnb_live; do
  rm -f logs/$d/{run.log,state.json,trades.csv,windows.csv}
done
rm -rf v16/__pycache__
sudo systemctl start v16-{btc,eth,sol,xrp,doge,bnb}.service

# Emergency stop manual (sin dashboard)
touch logs/EMERGENCY_STOP

# Limpiar emergency stop
rm -f logs/EMERGENCY_STOP
```

### Próximos pasos recomendados
1. Dejar correr 4-6 horas y acumular 20+ trades por activo
2. Comparar WR de activos invertidos (ETH/XRP/BNB) vs no invertidos (BTC/SOL/DOGE)
3. Si BTC sigue con WR < 40% tras 10+ trades → considerar invertir también
4. Si WR global sigue < 50% con estos cambios → revisar la estrategia a nivel fundamental (el problema podría ser que el momentum de 3-5 ticks en crypto no predice binarios de 5min)
5. Considerar analizar datos de V11b (otro servidor) para comparar parámetros
