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

59 lines
2.3 KiB
C#

using FluentAssertions;
using IBKRTrader.Modules.Accounting.Logic;
using IBKRTrader.Modules.Accounting.Models;
namespace IBKRTrader.Tests.Modules.Accounting;
[Trait("cat", "unit")]
public class AccountingClassifierTests
{
[Fact]
public void ClassifyExecution_Buy_CostsGrossPlusFee()
{
var e = new RawExecution { AccountId = "U1", TradeId = "T1", Side = "BUY", GrossBase = 1000m, FeeBase = 1m, Quantity = 10, Currency = "USD" };
var entry = AccountingClassifier.ClassifyExecution(e, 5);
entry.EventType.Should().Be(LedgerEventType.TradeBuy);
entry.NetBase.Should().Be(-1001m);
entry.IdempotencyKey.Should().Be("TRD|TradeBuy|T1");
entry.IngestBatchId.Should().Be(5);
}
[Fact]
public void ClassifyExecution_Sell_BringsGrossMinusFee()
{
var e = new RawExecution { AccountId = "U1", TradeId = "T2", Side = "SELL", GrossBase = 1300m, FeeBase = 1m };
var entry = AccountingClassifier.ClassifyExecution(e, 1);
entry.EventType.Should().Be(LedgerEventType.TradeSell);
entry.NetBase.Should().Be(1299m);
}
[Theory]
[InlineData("Dividends", LedgerEventType.Dividend)]
[InlineData("Withholding Tax", LedgerEventType.TaxWithholding)]
[InlineData("Broker Interest Received", LedgerEventType.Interest)]
[InlineData("Deposit", LedgerEventType.Deposit)]
[InlineData("Withdrawal", LedgerEventType.Withdrawal)]
public void MapCashType_MapsKnownTypes(string ibkrType, LedgerEventType expected)
{
AccountingClassifier.MapCashType(ibkrType).Should().Be(expected);
}
[Fact]
public void ClassifyCashTransaction_KeepsReportedSign()
{
var div = new RawCashTransaction { AccountId = "U1", TransactionId = "C1", Type = "Dividends", AmountBase = 50m };
var tax = new RawCashTransaction { AccountId = "U1", TransactionId = "C2", Type = "Withholding Tax", AmountBase = -7.5m };
AccountingClassifier.ClassifyCashTransaction(div, 1).NetBase.Should().Be(50m);
var t = AccountingClassifier.ClassifyCashTransaction(tax, 1);
t.EventType.Should().Be(LedgerEventType.TaxWithholding);
t.NetBase.Should().Be(-7.5m);
t.GrossBase.Should().Be(7.5m);
t.IdempotencyKey.Should().Be("CASH|TaxWithholding|C2");
}
}