Files
PolyTraderSharp/tests/PolyTrader.Tests/TraderMonitorServiceTests.cs
T
RichardandClaude Opus 4.8 bf3e012f8b Supervisor S-0a/b: Entscheidungsjournal, Order-Events, SignalId, JSONL-Logs
Datenfundament fuer die KI-Analyse (docs/konzepte/KONZEPT-Modul-Supervisor.md, Phase S-0).
Sofortnutzen auch ohne KI: abfragbare Rejects, rekonstruierbare Order-Ketten.

- core_decision_journal (DecisionRecord): JEDE Handelsentscheidung strukturiert -
  Decision (Executed/Rejected/Skipped/Failed) + ReasonCode-Enum (als String, erweiterbar)
  + Kontext-JSON (kulturinvariant) + MarketSlug/EndDate (counterfactual-ready).
- core_order_events (OrderEventRecord): Order-Lifecycle als Daten (Placed/PlaceFailed/
  LadderStart/LadderStep/FloorReplaced/DustAbort ...) inkl. CLOB-Response.
- SignalId (GUID) auf CopySignal, durchgereicht bis ClosedTrade + Core-TradeRecord
  (Dual-Write) und in die Leiter (ExitLadderState.SignalId) -> Dossier-Korrelation.
- CopyTradingEngine: ~20 Entscheidungs-Sites journalisieren zusaetzlich zum Freitext-Log
  (Mode/SellOnly/H3/MaxBuyPrice/PerMaster/TimeLimit/Budget/Balance/Minimum/SpamBlock/
  Leiter/Ownership/NotFound/Teilverkauf/Inkonsistenz/Grace/Demo-Fill/-Close/Live-BUY).
- SellLadderService: Order-Events fuer Start/Step/Floor-Replace/Dust; IOrderEventLog injiziert.
- TerminalLogger: JSONL-Sink (eine Datei/Tag, LogJson pur) + CorrelationId-Support. Dual-Sink.
- Journal-Writes fehlertolerant (brechen den Geld-Pfad nie).
- Migrationen offline generiert UND angewendet (rein additiv): Core (Widen F1 + Journal),
  CopyTrading (SignalId), RF (Initial) - DB war bis AddMarketTakerFeeBps migriert, verifiziert.
- Launcher-Designer: Account-Spalten-Instanziierung wiederhergestellt (war bei Designer-
  Reserialisierung verloren gegangen -> NullReference im Smoke).

Tests: +5 (LogJson, Journal-Roundtrips, SignalId) + Journal-Assertions in Engine-Tests.
Build 0 Fehler, 336 Tests gruen, --smoke-ui komplett gruen (alle Views inkl. RF gegen echte Tabellen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 18:48:25 +02:00

72 lines
3.0 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);
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);
}
}
}