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>
59 lines
2.3 KiB
C#
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");
|
|
}
|
|
}
|