using FluentAssertions; using IBKRTrader.Modules.Accounting.Logic; using IBKRTrader.Modules.Accounting.Models; namespace IBKRTrader.Tests.Modules.Accounting; [Trait("cat", "unit")] public class AccountingEngineTests { private static LedgerEntry E(LedgerEventType type, decimal net, decimal gross, int day, decimal fee = 0m) => new() { AccountId = "U1", EventType = type, NetBase = net, GrossBase = gross, FeeBase = fee, Timestamp = new DateTime(2026, 3, day, 12, 0, 0, DateTimeKind.Utc) }; [Fact] public void Statement_SatisfiesBalanceInvariant() { var entries = new[] { E(LedgerEventType.Deposit, 1000m, 1000m, 1), E(LedgerEventType.TradeBuy, -500m, 499m, 2, fee: 1m), E(LedgerEventType.TradeSell, 650m, 651m, 3, fee: 1m), E(LedgerEventType.Dividend, 20m, 20m, 4), E(LedgerEventType.Withdrawal, -200m, 200m, 5) }; var s = AccountingEngine.BuildStatement(entries, new DateTime(2026, 3, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 3, 31, 23, 59, 59, DateTimeKind.Utc), "U1"); // Invariante: Endsaldo − Anfang = Ergebnis + Einzahlungen − Auszahlungen s.BalanceChange.Should().Be(s.NetTradingResult + s.Deposits - s.Withdrawals); s.Deposits.Should().Be(1000m); s.Withdrawals.Should().Be(200m); s.Dividends.Should().Be(20m); s.Fees.Should().Be(2m); s.TradeCount.Should().Be(2); s.ClosingBalance.Should().Be(970m); // 1000 -500 +650 +20 -200 } [Fact] public void OpeningBalance_AccumulatesEntriesBeforeFrom() { var entries = new[] { E(LedgerEventType.Deposit, 500m, 500m, 1), // vor dem Zeitraum E(LedgerEventType.Dividend, 30m, 30m, 20) // im Zeitraum }; var s = AccountingEngine.BuildStatement(entries, new DateTime(2026, 3, 10, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 3, 31, 0, 0, 0, DateTimeKind.Utc), "U1"); s.OpeningBalance.Should().Be(500m); s.ClosingBalance.Should().Be(530m); } [Fact] public void MonthlyBreakdown_ChainsOpeningBalances() { var entries = new[] { E(LedgerEventType.Deposit, 100m, 100m, 1), // März E(LedgerEventType.Dividend, 10m, 10m, 5) }; var monthly = AccountingEngine.BuildMonthlyBreakdown(entries, new DateTime(2026, 3, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(2026, 4, 30, 0, 0, 0, DateTimeKind.Utc), "U1"); monthly.Should().HaveCount(2); monthly[0].From.Month.Should().Be(3); monthly[1].OpeningBalance.Should().Be(monthly[0].ClosingBalance); // April startet mit März-Endsaldo } }