# OTB-257: Fix für Duplicate Order ID Race Condition

## Problem

Wenn eine Entry Order adjustiert wird und TWS den Error 103 (Duplicate Order ID) zurückgibt, kann es zu einer Race Condition kommen, bei der:

1. Die alte Order (z.B. 201254) wird mit Error 103 gecancelt
2. Eine neue Replacement-Order (z.B. 201255) wird erstellt
3. Die neue Order erhält kurzzeitig den Status `ApiCancelled`
4. Der Trade Manager interpretiert dies als "Entry Order cancelled" und löscht den Trade
5. Die Replacement-Order wird danach erfolgreich ausgeführt
6. **Resultat**: Unmanaged Trade läuft im Broker, aber OptraBot kennt ihn nicht!

## Ablauf des Problems (Trade 680)

```
10:00:00.069 - Order 201254 placed (Trade 680)
10:00:05.094 - Entry order adjustment (-0.6 → -0.55)
10:00:05.096 - Order 201254 cancelled (Error 103: Duplicate Order ID)
10:00:05.097 - New order 201255 created
10:00:05.100 - Order 201255 receives ApiCancelled status
10:00:05.100 - ❌ Trade Manager deletes Trade 680 (because entry cancelled)
10:00:10.387 - Order 201255 becomes PreSubmitted (but Trade 680 is gone!)
10:00:14.596 - Order 201255 fills successfully → UNMANAGED TRADE!
```

## Root Cause

Die Duplicate-Order-ID-Prüfung fügte nur die alte Order-ID zur `_duplicate_order_ids` Liste hinzu, aber nicht die neue Replacement-Order-ID. Dadurch wurde die neue Order beim `ApiCancelled` Status als reguläre Stornierung behandelt.

## Lösung

1. **Replacement-Order zur Duplicate-Liste hinzufügen**: Wenn eine neue Order-ID als Ersatz für eine Duplicate-Order erstellt wird, wird sie ebenfalls zur `_duplicate_order_ids` Liste hinzugefügt.

2. **Cleanup bei Filled**: Wenn eine Order erfolgreich gefüllt wird und sie in der Duplicate-Liste ist, wird sie daraus entfernt.

## Code-Änderungen

**File**: `optrabot/broker/ibtwsconnector.py`

### Änderung 1: Neue Order-ID zur Duplicate-Liste hinzufügen (Zeile ~707-720)

```python
elif log_entry.errorCode == 103:
    # Error 103, reqId 10292: Doppelt vorhandene Order-ID
    logger.warning('Adjustment of entry order has been rejected, because Duplicate Order-ID.')
    duplicate_order_id = trade.order.orderId
    self._duplicate_order_ids.append(duplicate_order_id)
    logger.debug(f'Added Order ID {duplicate_order_id} to duplicate order IDs')
    
    # In diesem Fall muss die Order sicherheitshalber neu platziert werden
    try:
        self._ib.cancelOrder(trade.order)
    except Exception as excp:
        logger.debug(f'Error cancelling order: {excp}')
    
    new_order_id = self._ib.client.getReqId()
    trade.order.orderId = new_order_id
    # Add the replacement order ID to duplicate list to prevent it from being treated as cancelled
    self._duplicate_order_ids.append(new_order_id)  # ✅ NEU
    logger.debug(f'Replacing order with new Order Id {trade.order.orderId}')
    relevantOrder.brokerSpecific['trade'] = self._ib.placeOrder(trade.contract, trade.order)
    logger.info(f'Order {duplicate_order_id} has been replaced with new Order Id {trade.order.orderId}')
    return
```

### Änderung 2: Cleanup bei gefüllter Order (Zeile ~736-746)

```python
if trade.orderStatus.status == OrderStatus.Filled:
    # Wenn die gefüllte Order eine verursachende Order für conflicting Order(s) ist und sie vollständig ausgeführt wurde,
    # dann müssen die stornierten Orders wieder in den Markt gelegt werden
    await self._placeConflictingOrdersAgain(relevantOrder)

    filledAmount = relevantOrder.quantity - trade.remaining()
    relevantOrder.averageFillPrice = trade.orderStatus.avgFillPrice
    
    # Remove filled order from duplicate order IDs list (if it's a replacement order)
    if trade.order.orderId in self._duplicate_order_ids:  # ✅ NEU
        self._duplicate_order_ids.remove(trade.order.orderId)
        logger.debug(f'Removed filled order {trade.order.orderId} from duplicate order IDs list')
```

## Verhalten nach dem Fix

```
10:00:00.069 - Order 201254 placed (Trade 680)
10:00:05.094 - Entry order adjustment (-0.6 → -0.55)
10:00:05.096 - Order 201254 cancelled (Error 103: Duplicate Order ID)
                → 201254 added to duplicate_order_ids
10:00:05.097 - New order 201255 created
                → 201255 added to duplicate_order_ids  ✅
10:00:05.100 - Order 201255 receives ApiCancelled status
                → ✅ IGNORED (is in duplicate_order_ids list)
10:00:10.387 - Order 201255 becomes PreSubmitted (Trade 680 still exists!)
10:00:14.596 - Order 201255 fills successfully
                → ✅ Trade 680 managed normally
                → 201255 removed from duplicate_order_ids
```

## Test-Szenario

1. Trade mit Entry Order platzieren
2. Entry Order nach 5 Sekunden adjustieren (z.B. Preis ändern)
3. TWS meldet Error 103 (Duplicate Order ID)
4. Neue Order wird erstellt und ausgeführt
5. Trade bleibt im System und wird normal gemanaged

## Betroffene Versionen

- Bis Version: 0.18.0a3
- Fix in Version: 0.18.0a4

## Risiko-Bewertung

**Kritikalität**: HOCH
- Unmanaged Trades sind ein kritisches Risiko
- Kann zu Verlusten führen, wenn Trades nicht geschlossen werden

**Häufigkeit**: MITTEL
- Tritt bei Entry Order Adjustments mit gleichzeitigem Duplicate Order Error auf
- Timing-abhängig (Race Condition)

## Related Issues

- Ähnliches Problem wie bei SQLAlchemy Session Management (OTB-252, OTB-256)
- Zeigt die Notwendigkeit von robusten State-Management bei asynchronen Broker-Events

