Files
IBKRTrader/tests/IBKRTrader.Tests/Modules/CongressTradingStrategyTests.cs
T
RichardandClaude Opus 5 3bccd90d97 Eigentumsregeln 3 und 4: gebundener Ausfuehrungszugang und eine Grenze mit Zaehnen
Regel 3 verlangt, dass die Buchzuordnung serverseitig gesetzt wird und
TradeSignal.SourceModule nicht vertrauenswuerdig ist. Bisher war nur der
unbekannte Name abgefangen; ein Modul konnte weiterhin das Kuerzel eines
ANDEREN registrierten Moduls eintragen und dessen Cap, dessen Konzentration und
dessen GuV belasten.

Jedes Modul bekommt jetzt einen an sein Buch gebundenen IModuleExecution-Zugang
statt des gemeinsamen IExecutionService. Der Zugang ueberschreibt die Herkunft
mit seinem eigenen Buch - was im Signal steht, spielt keine Rolle mehr.

Der Zugang steht ausdruecklich NICHT im Container: was dort steht, kann jedes
Modul aufloesen, auch den Zugang eines anderen. Der Host uebergibt stattdessen
eine Fabrik beim Registrieren (neue Ueberladung von IModule.RegisterServices mit
Vorgabe, damit ein nicht handelndes Modul nichts davon wissen muss), und das
Modul reicht den erzeugten Zugang direkt an seine Strategie.

Regel 4 - Module bekommen den Broker nicht per DI - war eingehalten, aber nicht
erzwungen. Jetzt zwei Tests ueber alle vier Modul-Assemblies: der eine prueft
per Reflection Konstruktorparameter, Felder und Eigenschaften auf IBrokerClient,
IBrokerPortfolioReader und IExecutionService; der andere durchsucht die
Modulquellen nach dem zweiten Weg, dem Griff in den Container. Beide Gegenproben
mit einem absichtlichen Regelbruch gefahren: jeder Test schlaegt bei genau
seinem Weg an.

Getrennte Container je Modul waeren der harte Weg, aendern aber die
Modularchitektur. Der Fall, um den es geht, ist ein NEUES Modul, das den Core
versehentlich umgeht - und den faengt der Test beim Uebersetzen.

404 Tests gruen, auf Windows und auf der Test-VM.

Behebt #474, damit ist auch #302 vollstaendig.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 12:59:44 +02:00

72 lines
2.4 KiB
C#

using FluentAssertions;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Trading;
using IBKRTrader.Modules.CongressTrading.Models;
using IBKRTrader.Modules.CongressTrading.Strategy;
using NSubstitute;
namespace IBKRTrader.Tests.Modules;
[Trait("cat", "unit")]
public class CongressTradingStrategyTests
{
private readonly IModuleExecution _execution = Substitute.For<IModuleExecution>();
private CongressTradingStrategy CreateSut()
{
_execution.ExecuteAsync(Arg.Any<TradeSignal>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(ExecutionResult.Skip("test")));
return new CongressTradingStrategy(_execution, new LoggingService());
}
private static CongressTrade Trade(string type, string ticker = "AAPL") =>
new() { TradeId = "T1", MemberBioId = "W1", IssuerName = "Apple", Ticker = ticker, TradeType = type };
[Theory]
[InlineData("buy", TradeSide.Buy)]
[InlineData("BUY", TradeSide.Buy)]
[InlineData("sell", TradeSide.Sell)]
public async Task NewTrade_EmitsSignal_WithMappedSide(string type, TradeSide expected)
{
var sut = CreateSut();
await sut.HandleNewTradeAsync(Trade(type));
await _execution.Received(1).ExecuteAsync(
Arg.Is<TradeSignal>(s => s.Side == expected && s.Symbol == "AAPL" && s.SourceModule == "CT"),
Arg.Any<CancellationToken>());
}
[Theory]
[InlineData("exchange")]
[InlineData("receive")]
[InlineData("")]
public async Task NonBuySell_TradeType_DoesNotExecute(string type)
{
var sut = CreateSut();
await sut.HandleNewTradeAsync(Trade(type));
await _execution.DidNotReceive().ExecuteAsync(Arg.Any<TradeSignal>(), Arg.Any<CancellationToken>());
}
[Fact]
public async Task EmptyTicker_DoesNotExecute()
{
var sut = CreateSut();
await sut.HandleNewTradeAsync(Trade("buy", ticker: ""));
await _execution.DidNotReceive().ExecuteAsync(Arg.Any<TradeSignal>(), Arg.Any<CancellationToken>());
}
[Fact]
public void MapSide_IsCaseInsensitive_AndIgnoresUnknown()
{
CongressTradingStrategy.MapSide("Buy").Should().Be(TradeSide.Buy);
CongressTradingStrategy.MapSide("SELL").Should().Be(TradeSide.Sell);
CongressTradingStrategy.MapSide("exchange").Should().BeNull();
CongressTradingStrategy.MapSide(null).Should().BeNull();
}
}