Files
PolyTraderSharp/tests/PolyTrader.Tests/TraderMonitorServiceTests.cs
T
RichardandClaude Opus 4.8 c75a958e36 Supervisor S-1: Dossier-Generator + Modul-Skelett mit Dossier-Browser + Journal-Nachverdrahtung
Neues Modul PolyTrader.Modules.Supervisor (IPolyTraderModule, Name=Supervisor, DbPrefix=sup_,
nur Core-Referenz, strikt read-only):
- DossierBuilder (Core/Analytics, pur+getestet): TradeDossier aus Entscheidungen + Order-Events +
  Trades + Log-Zeilen, chronologisch, mit Markdown-Rendering (Tabellen, Pipe-Escaping).
- DossierService (Modul): beschafft Journal/Events/Trade-Log per SignalId + JSONL-Zeilen per CID
  (nur Tagesdateien im Ereignis-Zeitfenster +-1 Tag); RecentSignals-Uebersicht (Journal gruppiert).
- SupervisorMainForm: Dossier-Browser - links juengste Signale, rechts Markdown-Dossier;
  SignalId-Suche; Analyse-Chat (OpenRouter) folgt in S-2. In Launcher/Smoke registriert.

Journal-Nachverdrahtung (S-0-Vervollstaendigung):
- TraderMonitor: Profit-Target erzeugt eigene SignalId -> Leiter + Journal (ProfitTargetTriggered);
  Stale-Cleanup-Cancels als OrderEvents (StaleCleanupCancel).
- StartupOrderReconciliation: K2-Cancels als OrderEvents (StartupReconcileCancel).
- RF: Demo-Einstiege (DemoFilled, eigene SignalId) + Resolution-Closes (SystemResolutionClose)
  im Journal - damit sind ALLE Module im Entscheidungsjournal vertreten.

Tests: +3 DossierBuilder; 4 Service-Builder auf neue Ctors. Build 0 Fehler, 344 Tests gruen,
--smoke-ui: [OK] supervisor.main (alle 5 Views gruen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:09:02 +02:00

72 lines
3.1 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;
using System.Net.Http;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using PolyTrader.Tests.Fakes;
using PolyTraderSharp;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>
/// Verifiziert K1a (Fable-Fix): Der Stale-Order-Cleanup darf die ruhende Order einer aktiven
/// SELL-Eskalationsleiter NICHT canceln sonst steckt die Position am Floor fest (Floor-Deadlock).
/// </summary>
public class TraderMonitorServiceTests
{
private const string Tok = "tok-mon";
private static (TraderMonitorService svc, TradingState state, CopyTradingState copy, FakeClobClient clob) Build()
{
var state = new TradingState();
var copy = new CopyTradingState();
var clob = new FakeClobClient();
var logger = new TerminalLogger();
var api = new PolymarketApiService(logger, new HttpClient());
var posRepo = new FakePositionRepository();
var marketRepo = new FakeMarketRepository();
var tradeLog = new FakeCopyTradeLogRepository();
var threema = new ThreemaService(logger, new JobManager());
var ladder = new SellLadderService(copy, state, clob, logger, threema, posRepo, new FakeOrderEventLog());
var signalCh = Channel.CreateUnbounded<CopySignal>();
var closedCh = Channel.CreateUnbounded<ClosedTrade>();
var svc = new TraderMonitorService(state, copy, api, clob, signalCh.Writer, closedCh.Writer,
logger, posRepo, marketRepo, tradeLog, ladder, new FakeDecisionJournal(), new FakeOrderEventLog());
state.Accounts[1] = new AccountState { AccountId = 1, Name = "Live", IsDemo = false };
return (svc, state, copy, clob);
}
[Fact]
public async Task Cleanup_does_not_cancel_orders_of_active_ladder()
{
var (svc, state, copy, clob) = Build();
// Stale (1h alt) getrackte Order + aktive Leiter für denselben Key.
copy.PendingOrderTimestamps["1_" + Tok] = (DateTime.UtcNow.AddHours(-1), 7, "SELL");
copy.ExitLadders["1_" + Tok] = new ExitLadderState { AccountId = 1, TokenId = Tok, Floor = 0.40m, CurrentLimit = 0.40m };
clob.OpenOrdersByAsset[Tok] = new() { ("o1", "SELL", 0.40m) };
await svc.CleanupStaleOpenOrdersAsync(CancellationToken.None);
Assert.Empty(clob.CanceledOrderIds); // Leiter-Order NICHT gecancelt
}
[Fact]
public async Task Cleanup_cancels_stale_orders_without_ladder()
{
// Kontrast: ohne aktive Leiter wird die veraltete Order storniert.
var (svc, state, copy, clob) = Build();
copy.PendingOrderTimestamps["1_" + Tok] = (DateTime.UtcNow.AddHours(-1), 7, "SELL");
clob.OpenOrdersByAsset[Tok] = new() { ("o1", "SELL", 0.40m) };
await svc.CleanupStaleOpenOrdersAsync(CancellationToken.None);
Assert.Contains("o1", clob.CanceledOrderIds);
}
}
}