Files
RichardandClaude Opus 5 fd6d62618f Kultur-Bug behoben + Threema entfernt (INotificationSink)
- TraderMonitorService las API-Preise kulturabhaengig: unter de-DE wurde aus
  "0.53" der Wert 53 (Faktor-100-Fehler im Einstandspreis). Nutzt jetzt den
  bereits vorhandenen invarianten Helper ParseDecimal.
- Gleiche Fehlerklasse in PolymarketClobClient (6x) und MasterTraderAnalyticsJob
  vorsorglich auf InvariantCulture gestellt.
- Neuer Regressionstest ApiNumberParsingTests (10 Faelle unter erzwungener de-DE-Kultur).
- Threema komplett entfernt (Entscheidung Richard): ThreemaService, vendorte
  Bibliothek libs/Threema-MsgApi-Net-Core, ServerSettings-Block, DI-Verdrahtung.
- Ersetzt durch neutrale INotificationSink (No-Throw-Vertrag) + LogNotificationSink
  als Uebergang; RocketChat/Telegram folgen spaeter.
- Entfernt nebenbei libsodium 1.0.16, die einzige Registry-Nutzung im Build,
  den HttpListener-Webhook und System.Web.HttpUtility (alles Linux-Hindernisse).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:01:13 +02:00

72 lines
3.1 KiB
C#
Raw Permalink 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 notify = new FakeNotificationSink();
var ladder = new SellLadderService(copy, state, clob, logger, notify, 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);
}
}
}