Phase 0.1: SELL-Eskalationsleiter statt Market-Dump (CLOB-kritisch)

Behebt die April-Verlustquelle: SELLs wurden als Market-Order mit 0.01-Limit ins
oft leergeraeumte Orderbuch geworfen -> wir wurden zur Exit-Liquidity. Jetzt:
GTC-Limit nahe am Master-Exit, stufenweises Nachpreisen bis zum Floor.

- Position.ExitPending (runtime-only, EF-ignoriert): Position wird bei SELL NICHT
  mehr optimistisch entfernt, sondern als ExitPending zurueckgestellt (kein
  Doppel-SELL, Limits rechnen korrekt; Sync schliesst nach bestaetigtem Fill).
- CopyTradingState.ExitLadders + ExitLadderState (transienter Leiter-Zustand).
- SellLogic (pure, getestet): FirstLimit (HF-fest/prozentual), Floor (SellFloorPct),
  NextPrice (relative Stufe, auf Floor geclamped), IsAtFloor, LadderStepPct (3%),
  LadderIntervalSeconds (HF 20s / sonst 120s).
- SellLadderService (BackgroundService): senkt offene Exit-Limits stufenweise
  (cancel via CancelConflictingOrdersAsync -> tiefer neu platzieren), am Floor ohne
  Fill Position halten + Threema-Benachrichtigung. Fills erkennt der bestehende Sync.
- CopyTradingEngine SELL-Live-Pfad ruft die Leiter; Demo-Pfad unveraendert.
  Doppel-SELL-Guard ueber ExitPending. Umfangreiches Logging (kein Live-Test moeglich).

163 Tests gruen. Build/Smoke gruen. Backup-Rollback: Commit 1dffc9e.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-07 17:40:40 +02:00
co-authored by Claude Opus 4.8
parent 1dffc9e253
commit e22a6e3091
8 changed files with 266 additions and 25 deletions
+7
View File
@@ -23,5 +23,12 @@ namespace PolyTraderSharp.Models
public decimal CurrentValueUsd { get; set; }
public System.DateTime? ExpiryDate { get; set; }
public System.DateTime OpenedAt { get; set; } = System.DateTime.UtcNow;
/// <summary>
/// Runtime-only (nicht persistiert): Für diese Position läuft gerade eine SELL-Eskalations-
/// leiter (Phase 0.1). Verhindert Doppel-SELL und hält die Limits-Berechnung korrekt, bis der
/// Fill über den Sync bestätigt ist. Nach App-Neustart bewusst false (Ladder-Zustand ist transient).
/// </summary>
public bool ExitPending { get; set; }
}
}
@@ -41,6 +41,7 @@ namespace PolyTrader.Core.Persistence.Ef
{
e.ToTable("core_positions");
e.HasKey(x => new { x.AccountId, x.IsDemo, x.TokenId });
e.Ignore(x => x.ExitPending); // Runtime-only (SELL-Eskalationsleiter)
e.Property(x => x.TokenId).HasMaxLength(120);
e.Property(x => x.MarketSlug).HasMaxLength(300);
e.Property(x => x.ConditionId).HasMaxLength(120);