# OTB-264: Alert Streamer Reconnect Issue

## Problem
Der OptraBot erhält keine Order-Status-Updates von Tastytrade, nachdem der Alert Streamer die Verbindung verliert.

## Analyse

### Symptome aus dem Log
1. **09:32:02**: Order erfolgreich platziert (Order ID: 416648875)
2. **01:30:16**: Tastytrade Alert Streamer disconnected
3. **04:17:51**: Tastytrade Alert Streamer disconnected  
4. **06:14:32**: Tastytrade Alert Streamer disconnected
5. **09:35:14**: Tastytrade Alert Streamer disconnected (während Order-Tracking!)

### Beobachtungen
- Der **DXLink Streamer** (für Marktdaten) reconnected automatisch:
  - Bei Disconnect wird `_on_streamer_disconnect()` aufgerufen
  - Dies triggert `_disconnect_internal()` und setzt `_is_disconnecting = True`
  - Der Reconnect-Mechanismus im Haupt-Code stellt die Verbindung wieder her
  
- Der **Alert Streamer** (für Account Updates) reconnected NICHT:
  - Bei Disconnect wird `_on_alert_streamer_disconnect()` aufgerufen
  - Diese Methode macht **NICHTS** außer Logging!
  - Der Task `task_listen_accounts` läuft weiter, aber der Alert Streamer ist disconnected
  - Keine Order-Status-Updates werden mehr empfangen

### Root Cause
Die Methode `_on_alert_streamer_disconnect()` implementiert keine Reconnect-Logik:

```python
async def _on_alert_streamer_disconnect(self, streamer: AlertStreamer):
    logger.debug("Tastytrade Alert Streamer disconnected.")
    # KEINE WEITERE AKTION!
```

Im Gegensatz dazu triggert `_on_streamer_disconnect()` einen vollständigen Disconnect/Reconnect:

```python
async def _on_streamer_disconnect(self, streamer: DXLinkStreamer):
    logger.debug("Tastytrade Streamer disconnected. Performing disconnect operations")
    if self._is_disconnecting == False:
        await self._disconnect_internal()
```

### Warum der Bug nicht sofort auffällt
- Alert Streamer Disconnects passieren relativ selten (ca. alle 1-4 Stunden im Log)
- Wenn keine Orders aktiv sind, wird der fehlende Alert Streamer nicht bemerkt
- Nur wenn eine Order platziert wird WÄHREND der Alert Streamer disconnected ist, werden Status-Updates nicht empfangen

## Lösung

### Implementierte Lösung: Selektiver Alert Streamer Reconnect

Die `_on_alert_streamer_disconnect()` Methode wurde erweitert, um nur den Alert Streamer neu zu verbinden, ohne den gesamten Connector zu disconnecten:

```python
async def _on_alert_streamer_disconnect(self, streamer: AlertStreamer):
    """
    Callback method which is called when the Tastytrade alert streamer disconnects.
    Reconnects only the alert streamer without affecting the DXLink streamer.
    """
    logger.debug("Tastytrade Alert Streamer disconnected.")
    
    if self._is_disconnecting == False:
        logger.warning("Alert Streamer disconnected unexpectedly. Reconnecting Alert Streamer...")
        
        # Cancel current account listening task if it exists
        if self.task_listen_accounts and not self.task_listen_accounts.done():
            try:
                self.task_listen_accounts.cancel()
                await asyncio.wait_for(self.task_listen_accounts, timeout=1.0)
            except (asyncio.TimeoutError, asyncio.CancelledError):
                pass
        
        # Close the old alert streamer
        if self._alert_streamer:
            try:
                await asyncio.wait_for(self._alert_streamer.close(), timeout=2.0)
            except:
                pass
            self._alert_streamer = None
        
        # Restart the account updates task (creates new alert streamer)
        self.task_listen_accounts = asyncio.create_task(self._request_account_updates())
        logger.info("Alert Streamer reconnected successfully.")
```

**Vorteile:**
- Minimaler Impact - nur Alert Streamer wird neu verbunden
- DXLink Streamer (Marktdaten) läuft unterbrechungsfrei weiter
- Keine unnötigen vollständigen Reconnects wenn nur der Alert Streamer betroffen ist

**Warum diese Lösung:**
Das Log zeigt, dass der Alert Streamer unabhängig vom DXLink Streamer disconnecten kann. Ein vollständiger Connector-Reconnect wäre in diesen Fällen übertrieben und würde auch die Marktdaten-Verbindung unnötig unterbrechen.

## Testing
Nach dem Fix sollte getestet werden:
1. Alert Streamer Disconnect während einer aktiven Order
2. Mehrfache Alert Streamer Disconnects über einen längeren Zeitraum
3. Gleichzeitiger Disconnect von Alert Streamer und DXLink Streamer

## Follow-up Fix: Shutdown Issues
Nach der initialen Implementierung wurden beim Shutdown (Ctrl+C) folgende Probleme festgestellt:
- `RecursionError: maximum recursion depth exceeded` beim Canceln von Tasks
- `RuntimeError: no running event loop` beim Versuch, neue Tasks zu erstellen

### Root Cause
Die `_is_disconnecting` Flag wurde zu spät gesetzt (erst innerhalb `_disconnect_internal()`), was dazu führte dass:
1. Disconnect-Callbacks ausgelöst wurden während der Shutdown läuft
2. Diese Callbacks versuchten, neue Tasks zu erstellen oder Reconnects zu triggern
3. Dies führte zu rekursiven Cancel-Aufrufen und Event-Loop-Fehlern

### Implementierte Fixes
1. **Frühere Flag-Setzung**: `_is_disconnecting` wird jetzt **VOR** `_disconnect_internal()` gesetzt
2. **Event-Loop-Check**: Alert Streamer Reconnect prüft ob Event-Loop noch läuft vor Task-Erstellung
3. **Defensive Fehlerbehandlung**: RuntimeError wird beim Reconnect-Versuch abgefangen

```python
async def disconnect(self):
    await super().disconnect()
    # Set flag BEFORE calling _disconnect_internal to prevent reconnect attempts
    self._is_disconnecting = True
    await self._disconnect_internal()

async def _on_alert_streamer_disconnect(self, streamer: AlertStreamer):
    if self._is_disconnecting == False:
        # ... cleanup code ...
        try:
            # Check if event loop is still running before creating new task
            loop = asyncio.get_event_loop()
            if loop.is_running():
                self.task_listen_accounts = asyncio.create_task(self._request_account_updates())
            else:
                logger.debug("Event loop not running, skipping Alert Streamer reconnect")
        except RuntimeError as runtime_err:
            logger.debug(f'Cannot reconnect Alert Streamer: {runtime_err}')
```
