Slice 2 (Fable-Fixes): K2 – Neustart-Reconciliation

Ruhende GTC-Leiter-/MakerEntry-Orders ueberleben Neustarts auf dem CLOB, der
Verwaltungszustand (ExitLadders/ExitPending/PendingOrderTimestamps) ist transient.
Ohne Bereinigung liefe die Engine gegen Waisen-Orders (Doppel-Leiter, Kaskaden).

- StartupOrderReconciliationService (IHostedService): storniert beim Start je
  Live-Account alle offenen CLOB-Orders; danach entscheidet die Engine sauber neu.
  Registriert als ERSTER Modul-HostedService (nach Hydration, vor Monitor/Engine),
  pro Account fehlertolerant.
- GetOpenOrdersAsync: assetId jetzt optional (default "") -> ohne Filter ALLE
  offenen Orders des Accounts. Signaturneutral (HMAC geht ueber Pfad ohne Query),
  rueckwaertskompatibel fuer die bestehenden per-Asset-Aufrufer. IClobClient +
  FakeClobClient nachgezogen.

Hinweis: "/data/orders ohne asset_id = alle Orders" ist API-gated und im Zielland
live zu verifizieren (wie M6).

Tests: 3 neue (cancelt alle Orders je Live-Account, ueberspringt Demo/credential-los,
no-op ohne offene Orders). Build 0 Fehler, 218 Tests gruen, --smoke-ui ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-09 13:20:03 +02:00
co-authored by Claude Opus 4.8
parent 0200a726e7
commit 98109b78f6
6 changed files with 195 additions and 10 deletions
@@ -25,9 +25,12 @@ namespace PolyTrader.Tests.Fakes
public Queue<string> PlaceResults { get; } = new();
public bool CancelResult { get; set; } = true;
/// <summary>Offene Orders je Asset, die GetOpenOrdersAsync zurückgibt.</summary>
/// <summary>Offene Orders je Asset, die GetOpenOrdersAsync(assetId) zurückgibt.</summary>
public Dictionary<string, List<(string Id, string Side, decimal Price)>> OpenOrdersByAsset { get; } = new();
/// <summary>Alle offenen Orders des Accounts, die GetOpenOrdersAsync() ohne assetId zurückgibt (K2).</summary>
public List<(string Id, string Side, decimal Price)> AllOpenOrders { get; } = new();
public Task<string> PlaceOrderAsync(
AccountState account, string tokenId, string sideStr, decimal investAmountUsd, decimal limitPrice,
string orderType = "FOK", bool debugPayloadLog = false, bool isNegRisk = false, int actualFeeBps = 0,
@@ -44,8 +47,10 @@ namespace PolyTrader.Tests.Fakes
return Task.CompletedTask;
}
public Task<List<(string Id, string Side, decimal Price)>> GetOpenOrdersAsync(AccountState acc, string assetId)
public Task<List<(string Id, string Side, decimal Price)>> GetOpenOrdersAsync(AccountState acc, string assetId = "")
{
if (string.IsNullOrEmpty(assetId))
return Task.FromResult(new List<(string, string, decimal)>(AllOpenOrders));
if (OpenOrdersByAsset.TryGetValue(assetId, out var list))
return Task.FromResult(new List<(string, string, decimal)>(list));
return Task.FromResult(new List<(string, string, decimal)>());