Files
PolyTraderSharp/tests/PolyTrader.Tests/StartupOrderReconciliationTests.cs
T
RichardandClaude Opus 4.8 98109b78f6 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>
2026-07-09 13:20:03 +02:00

84 lines
2.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Threading;
using System.Threading.Tasks;
using PolyTrader.Tests.Fakes;
using PolyTraderSharp;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>
/// Slice 2 (Fable-Fixes) K2 Neustart-Reconciliation: Beim Start werden alle offenen CLOB-Orders
/// je Live-Account pauschal storniert, damit die Engine gegen keine Waisen-Orders anläuft.
/// </summary>
public class StartupOrderReconciliationTests
{
private static AccountState LiveAccount(int id, string name)
{
return new AccountState
{
AccountId = id,
Name = name,
IsDemo = false,
ApiKey = "k",
ApiSecret = "s",
ApiPassphrase = "p",
PrivateKey = "0xabc"
};
}
[Fact]
public async Task Cancels_all_open_orders_for_live_accounts()
{
var state = new TradingState();
var acc = LiveAccount(1, "Live");
state.Accounts[acc.AccountId] = acc;
var clob = new FakeClobClient();
clob.AllOpenOrders.Add(("o1", "SELL", 0.40m));
clob.AllOpenOrders.Add(("o2", "BUY", 0.55m));
var svc = new StartupOrderReconciliationService(state, clob, new TerminalLogger());
await svc.StartAsync(CancellationToken.None);
Assert.Equal(new[] { "o1", "o2" }, clob.CanceledOrderIds);
}
[Fact]
public async Task Skips_demo_accounts_and_accounts_without_credentials()
{
var state = new TradingState();
var demo = LiveAccount(1, "Demo");
demo.IsDemo = true;
var noCreds = new AccountState { AccountId = 2, Name = "NoCreds", IsDemo = false };
state.Accounts[demo.AccountId] = demo;
state.Accounts[noCreds.AccountId] = noCreds;
var clob = new FakeClobClient();
clob.AllOpenOrders.Add(("x", "SELL", 0.40m));
var svc = new StartupOrderReconciliationService(state, clob, new TerminalLogger());
await svc.StartAsync(CancellationToken.None);
Assert.Empty(clob.CanceledOrderIds); // weder Demo noch credential-lose Accounts angefasst
}
[Fact]
public async Task No_open_orders_cancels_nothing()
{
var state = new TradingState();
var acc = LiveAccount(1, "Live");
state.Accounts[acc.AccountId] = acc;
var clob = new FakeClobClient(); // AllOpenOrders leer
var svc = new StartupOrderReconciliationService(state, clob, new TerminalLogger());
await svc.StartAsync(CancellationToken.None);
Assert.Empty(clob.CanceledOrderIds);
}
}
}