Files
IBKRTrader/tests/IBKRTrader.Tests/Modules/CongressTradingStrategyTests.cs
T
Richard 331623d4f3 @
R5: CongressTrading als Strategie (Signal -> ExecutionService) + Positionen im Modul-Fenster

- CongressTradingStrategy: neuer Scrape-Trade -> TradeSignal (buy/sell-Mapping, sonst ignoriert)
  -> Core-IExecutionService; CongressScrapeWorker ruft sie je neuem Trade (try/catch-isoliert)
- Modul-Fenster: offene Positionen des Moduls via IPortfolioService.GetPositionsAsync("CT")
- Tests: CongressTradingStrategy (Mapping/Ausfuehrung, gemockter ExecutionService) -> 51/51 gruen
- Handel bleibt sicher aus (TradingEnabled=false + NullBrokerClient), bis Broker+Freigabe
- Build + smoke-ui + App-Start ok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-28 19:14:21 +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 IExecutionService _execution = Substitute.For<IExecutionService>();
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();
}
}