R8: Accounting- + Supervisor-Modul + Core-Datenfundament (S-0)

Portierung der beiden fehlenden Grundbausteine aus PolytraderSharp (voller Ausbau).

Core S-0 (Datenfundament fuer Analyse/Forensik):
- core_decision_journal + core_order_events (+ ReasonCode/Decision/OrderEvent-Enums),
  IDecisionJournal/IOrderEventLog mit fehlertoleranten EF-Impls (Handel bricht nie).
- SignalId-Durchreichung TradeSignal -> ExecutionService -> core_trade_history;
  ExecutionService schreibt an jeder Verzweigung Journal/Order-Events.
- JSONL-Log-Sink (LogJson + Dual-Sink), pure Analytik: RealizedPnlEngine (FIFO),
  TradeAnalytics, DossierBuilder. Migration AddAnalysisFoundation.

Accounting-Modul (acc_): unabhaengiger IBKR-Kontoauszug (Activity Flex Query) hinter
Interfaces mit Offline-Null-Stubs -> append-only Ledger + Periodenabrechnung/BWA + FX
(USD/EUR) + CSV/PDF (PDFsharp/MigraDoc). Steuerschicht bewusst offen (Platzhalter-Tab).
Kein Handel. Migration InitialAccounting.

Supervisor-Modul (sup_): read-only OpenRouter-Agent (Function-Calling-Loop) + read-only
Tool-Registry (8 Tools) + Profile + Dossier-Browser + Counterfactual-Job (Stub) +
Tagesbericht/MCP-Light (opt-in). Migration InitialSupervisor.

Verdrahtung: Program.cs (beide Module + Icons), slnx/App/Tests-Referenzen,
provision-db.ps1, AppSettings-Sektionen, docs/konzepte, README.

Tests: 79 -> 117 gruen (FIFO/KPIs/Dossier/JSONL, Classifier/Engine/FX/Idempotenz,
OpenRouter/Registry/Agent/MCP, STA-Konstruktion beider neuen Fenster).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-31 09:25:18 +02:00
co-authored by Claude Opus 4.8
parent cbbedb2e0e
commit 2a312ca035
86 changed files with 6880 additions and 22 deletions
@@ -1,5 +1,6 @@
using FluentAssertions;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Persistence;
using IBKRTrader.Core.Settings;
using IBKRTrader.Core.Trading;
using NSubstitute;
@@ -13,9 +14,11 @@ public class ExecutionServiceTests
private readonly IRiskService _risk = Substitute.For<IRiskService>();
private readonly IPortfolioService _portfolio = Substitute.For<IPortfolioService>();
private readonly SettingsService _settings = new();
private readonly IDecisionJournal _journal = Substitute.For<IDecisionJournal>();
private readonly IOrderEventLog _orderLog = Substitute.For<IOrderEventLog>();
private ExecutionService CreateSut() =>
new(_broker, _risk, _portfolio, _settings, new LoggingService());
new(_broker, _risk, _portfolio, _settings, new LoggingService(), _journal, _orderLog);
private static readonly TradeSignal BuySignal = new()
{
@@ -92,7 +95,7 @@ public class ExecutionServiceTests
result.Executed.Should().BeTrue();
result.Order!.OrderId.Should().Be("O1");
await _portfolio.Received(1).RecordFillAsync(
"CT", "AAPL", TradeSide.Buy, 5, 100m, "O1", Arg.Any<CancellationToken>());
"CT", "AAPL", TradeSide.Buy, 5, 100m, "O1", Arg.Any<string?>(), Arg.Any<CancellationToken>());
}
[Fact]
@@ -107,6 +110,34 @@ public class ExecutionServiceTests
Arg.Any<CancellationToken>());
}
[Fact]
public async Task TradingDisabled_WritesSkippedDecision()
{
await CreateSut().ExecuteAsync(BuySignal);
_journal.Received().Write(Arg.Is<IBKRTrader.Core.Persistence.Entities.CoreDecisionRecord>(
d => d.Decision == IBKRTrader.Core.Persistence.Entities.TradeDecision.Skipped &&
d.Reason == IBKRTrader.Core.Persistence.Entities.DecisionReason.TradingDisabled));
}
[Fact]
public async Task HappyPath_PropagatesSignalId_AndJournalsExecuted()
{
ArrangeHappyPath();
var signal = new TradeSignal { Symbol = "AAPL", Side = TradeSide.Buy, SourceModule = "CT", SignalId = "sig-abc" };
await CreateSut().ExecuteAsync(signal);
await _portfolio.Received(1).RecordFillAsync(
"CT", "AAPL", TradeSide.Buy, 5, 100m, "O1", "sig-abc", Arg.Any<CancellationToken>());
_journal.Received().Write(Arg.Is<IBKRTrader.Core.Persistence.Entities.CoreDecisionRecord>(
d => d.SignalId == "sig-abc" &&
d.Decision == IBKRTrader.Core.Persistence.Entities.TradeDecision.Executed));
_orderLog.Received().Write(Arg.Is<IBKRTrader.Core.Persistence.Entities.CoreOrderEvent>(
e => e.SignalId == "sig-abc" &&
e.EventType == IBKRTrader.Core.Persistence.Entities.OrderEventType.Filled));
}
[Fact]
public async Task OrderFails_ReturnsError_AndDoesNotBook()
{
@@ -120,6 +151,6 @@ public class ExecutionServiceTests
result.Reason.Should().Contain("Broker abgelehnt");
await _portfolio.DidNotReceive().RecordFillAsync(
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<TradeSide>(),
Arg.Any<int>(), Arg.Any<decimal>(), Arg.Any<string>(), Arg.Any<CancellationToken>());
Arg.Any<int>(), Arg.Any<decimal>(), Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<CancellationToken>());
}
}