Files
RichardandClaude Opus 4.8 2a312ca035 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>
2026-07-31 09:25:18 +02:00

73 lines
2.6 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 FluentAssertions;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Persistence.Ef;
using IBKRTrader.Core.Persistence.Entities;
using Microsoft.EntityFrameworkCore;
namespace IBKRTrader.Tests.Persistence;
/// <summary>Entscheidungsjournal + Order-Event-Log gegen EF-InMemory, inkl. Robustheits-Garantie.</summary>
[Trait("cat", "unit")]
public class AnalysisJournalsTests
{
private sealed class Factory<T>(DbContextOptions<T> options) : IDbContextFactory<T> where T : DbContext
{
public T CreateDbContext() => (T)Activator.CreateInstance(typeof(T), options)!;
}
/// <summary>Factory, die immer wirft simuliert einen DB-Ausfall.</summary>
private sealed class ThrowingFactory : IDbContextFactory<CoreDbContext>
{
public CoreDbContext CreateDbContext() => throw new InvalidOperationException("DB weg");
}
private static Factory<CoreDbContext> InMemory() =>
new(new DbContextOptionsBuilder<CoreDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString()).Options);
[Fact]
public void DecisionJournal_WritesAndQueriesBack()
{
var journal = new EfDecisionJournal(InMemory(), new LoggingService());
journal.Write(new CoreDecisionRecord
{
SignalId = "sig-1", Module = "CT", Symbol = "AAPL", Side = "BUY",
Decision = TradeDecision.Rejected, Reason = DecisionReason.RiskRejected, Message = "Limit"
});
var rows = journal.Query(d => d.SignalId == "sig-1");
rows.Should().HaveCount(1);
rows[0].Reason.Should().Be(DecisionReason.RiskRejected);
}
[Fact]
public void OrderEventLog_WritesAndQueriesBack()
{
var log = new EfOrderEventLog(InMemory(), new LoggingService());
log.Write(new CoreOrderEvent
{
SignalId = "sig-2", Module = "CT", Symbol = "AAPL",
EventType = OrderEventType.Filled, Side = "BUY", Quantity = 5, Price = 100m, Response = "OK"
});
var rows = log.Query(e => e.SignalId == "sig-2");
rows.Should().HaveCount(1);
rows[0].EventType.Should().Be(OrderEventType.Filled);
}
[Fact]
public void Write_NeverThrows_OnDbFailure()
{
var journal = new EfDecisionJournal(new ThrowingFactory(), new LoggingService());
var log = new EfOrderEventLog(new ThrowingFactory(), new LoggingService());
var writeJournal = () => journal.Write(new CoreDecisionRecord { SignalId = "x" });
var writeEvent = () => log.Write(new CoreOrderEvent { SignalId = "x" });
writeJournal.Should().NotThrow();
writeEvent.Should().NotThrow();
}
}