Engine-Testabdeckung (Guards) + K3-Korrektur am echten Bug-Ort

Testabdeckung fuer den geldkritischen Engine-Pfad (bisher 0 Tests), da die
Fable-Guards zwischen Services entstehen und Unit-Tests sie nicht fangen:
- Engine._clob -> IClobClient (verhaltensneutral, DI registriert IClobClient bereits);
  ProcessAccountOrderAsync internal. FakeMarketRepository/FakeAccountRepository ergaenzt.
- 7 Integrationstests (CopyTradingEngineTests) ueber gemockten CLOB: H3 BUY-Skip bei
  ExitPending, Doppel-SELL-Guard, K3 System-Close (TraderId==0) schliesst Fremd-Position,
  Fremd-Trader-SELL bleibt abgewiesen, H2 Cleanup schont Leiter (+ Kontrast ohne Leiter).

DABEI ECHTEN BUG GEFANGEN: Der K3-Fix aus Slice 3 sass an der falschen Stelle
(IsAuthorizedSell nach dem Position-Remove, Zeile ~643) – der eigentliche Ownership-Check
ist der fruehe inPortfolio-Lookup (Zeile 437, p.SourceTraderId == signal.TraderId), der
System-Signale schon vorher mit early return abwies. Fix jetzt am richtigen Ort; der
downstream-Check bleibt als Defense-in-depth. Ohne den Engine-Test waere das unentdeckt geblieben.

Build 0 Fehler, 242 Tests gruen, --smoke-ui ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-09 19:38:11 +02:00
co-authored by Claude Opus 4.8
parent 782c08a860
commit f4045f08e1
3 changed files with 204 additions and 5 deletions
@@ -19,7 +19,7 @@ namespace PolyTraderSharp.Services
private readonly ChannelReader<CopySignal> _signalReader;
private readonly ChannelWriter<ClosedTrade> _closedTradeWriter;
private readonly TerminalLogger _logger;
private readonly PolymarketClobClient _clob;
private readonly IClobClient _clob;
private readonly PolymarketApiService _api;
private readonly IPositionRepository _positionRepo;
private readonly IMarketRepository _marketRepo;
@@ -34,7 +34,7 @@ namespace PolyTraderSharp.Services
ChannelReader<CopySignal> signalReader,
ChannelWriter<ClosedTrade> closedTradeWriter,
TerminalLogger logger,
PolymarketClobClient clob,
IClobClient clob,
PolymarketApiService api,
IPositionRepository positionRepo,
IMarketRepository marketRepo,
@@ -219,7 +219,7 @@ namespace PolyTraderSharp.Services
await Task.WhenAll(accountTasks);
}
private async Task ProcessAccountOrderAsync(AccountState account, TrackedTrader? trader, CopySignal signal, bool isNegRisk)
internal async Task ProcessAccountOrderAsync(AccountState account, TrackedTrader? trader, CopySignal signal, bool isNegRisk)
{
// Copytrading-Detail-Einstellungen (Limits) dieses Accounts.
var settings = _copyState.GetAccountSettings(account.AccountId);
@@ -434,9 +434,12 @@ namespace PolyTraderSharp.Services
{
// PRE-FLIGHT SELL Check: Exists in portfolio AND opened by the SAME master trader?
// CRITICAL: We must NOT sell a position opened by Trader A based on a SELL signal from Trader B.
var inPortfolio = account.OpenPositions.Values.FirstOrDefault(p =>
// K3: System-Signale (TraderId == 0, Demo-Auto-Close bei Marktauflösung) sind vom
// Ownership-Check ausgenommen (CopyTradingRisk.IsAuthorizedSell) sonst wird die Position
// hier als "gehört anderem Trader" abgewiesen und schließt bei Resolution nie.
var inPortfolio = account.OpenPositions.Values.FirstOrDefault(p =>
(p.TokenId == signal.TokenId || (p.MarketSlug == signal.MarketSlug && p.Outcome == signal.Outcome))
&& p.SourceTraderId == signal.TraderId);
&& CopyTradingRisk.IsAuthorizedSell(signal.TraderId, p.SourceTraderId));
if (inPortfolio == null)
{
// Check if position exists but belongs to a different trader (for clearer logging)