using Xunit; using KArtSell.BuildingBlocks.Time; using KArtSell.Modules.ModelOperations.ShadowRun; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace KArtSell.Integration.Tests; public sealed class SignalGenerationTests { private readonly ILogger _logger = new NoOpLogger(); [Fact] public async Task GenerateSignals_EMA_ProducesTradeSignals() { // Arrange var replay = new ReplayEngine(_logger); // Create OHLCV bars with clear trend var bars = new List(); // Generate uptrend (days 1-30): price goes from 2500 to 2600 for (int i = 1; i <= 30; i++) { var price = 2500m + (i * 3.33m); // Linear uptrend bars.Add(new DataBackfiller.OhlcvBar( new DateOnly(2026, 1, i), "KOSPI", price - 10, // open price + 10, // high price - 15, // low price, // close 1_000_000L)); } // Downtrend (days 31-45): price goes from 2600 down to 2500 for (int i = 31; i <= 45; i++) { var price = 2600m - ((i - 30) * 6.67m); // Linear downtrend bars.Add(new DataBackfiller.OhlcvBar( new DateOnly(2026, 2, i - 30), "KOSPI", price - 10, price + 10, price - 15, price, 1_000_000L)); } var fees = new List { new(new DateOnly(2026, 1, 1), 0.001m, 0.0005m), }; var sessions = bars.Select(b => b.Date).Distinct().OrderBy(d => d).ToList(); // Act var result = await replay.ReplayAsync( Guid.NewGuid(), bars, fees, 10_000_000m, sessions, CancellationToken.None); // Assert Assert.NotNull(result); Assert.NotEmpty(result.Signals); // Should have signals from trend // Verify signal generation happened var buySignals = result.Signals.Where(s => s.Action == ReplayEngine.SignalAction.Buy).ToList(); var sellSignals = result.Signals.Where(s => s.Action == ReplayEngine.SignalAction.Sell).ToList(); Assert.NotEmpty(buySignals); // Should have BUY signals during uptrend Assert.NotEmpty(sellSignals); // Should have SELL signals during downtrend } private sealed class NoOpLogger : ILogger { public IDisposable? BeginScope(TState state) where TState : notnull => null; public bool IsEnabled(LogLevel logLevel) => false; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { } } }