feat: Implement EMA crossover signal generation for Phase 2 gates optimization
- Added CalculateEMA() method to ReplayEngine for 12/26-day exponential moving average - Updated GenerateSignalsAsync() to emit Buy/Sell signals when EMA12 crosses EMA26 - Added 0.1% threshold to avoid noise and excessive trading - Signal confidence set to 0.75m with clear rationale for traceability - New SignalGenerationTests to verify signal generation on trending data - Fixes: signals were empty (0 signals/orders/returns), now generates trade signals - Result: Phase 2 metrics should now be non-zero (orders, returns, metrics) - AGENTS.md v16.0: Necessity-driven (unblocks Phase 3), Simple logic, Reliability tested Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,10 +160,44 @@ public sealed class ReplayEngine(
|
||||
IReadOnlyList<DataBackfiller.OhlcvBar> bars,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Simplified: stub model prediction
|
||||
// In production: call model.predict() with features
|
||||
await Task.Delay(10, cancellationToken);
|
||||
return new List<Signal>();
|
||||
await Task.Delay(5, cancellationToken);
|
||||
|
||||
var signals = new List<Signal>();
|
||||
|
||||
// EMA12/EMA26 Crossover Strategy
|
||||
var barsByDate = bars.OrderBy(b => b.Date).ToList();
|
||||
var currentIdx = barsByDate.FindIndex(b => b.Date == date);
|
||||
if (currentIdx < 26)
|
||||
return signals; // Not enough data
|
||||
|
||||
var prices = barsByDate.Take(currentIdx + 1).GroupBy(b => b.Ticker)
|
||||
.ToDictionary(g => g.Key, g => g.Select(b => b.Close).ToList());
|
||||
|
||||
foreach (var (ticker, closes) in prices)
|
||||
{
|
||||
var ema12 = CalculateEMA(closes, 12);
|
||||
var ema26 = CalculateEMA(closes, 26);
|
||||
|
||||
if (ema12 > ema26 * 1.001m) // 0.1% threshold to avoid noise
|
||||
signals.Add(new Signal(Guid.NewGuid(), date, ticker, SignalAction.Buy, 0.75m, "EMA12 > EMA26"));
|
||||
else if (ema12 < ema26 * 0.999m)
|
||||
signals.Add(new Signal(Guid.NewGuid(), date, ticker, SignalAction.Sell, 0.75m, "EMA12 < EMA26"));
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
private static decimal CalculateEMA(List<decimal> prices, int period)
|
||||
{
|
||||
if (prices.Count < period) return prices.Last();
|
||||
|
||||
var multiplier = 2m / (period + 1);
|
||||
var ema = prices.Take(period).Average();
|
||||
|
||||
foreach (var price in prices.Skip(period))
|
||||
ema = (price * multiplier) + (ema * (1 - multiplier));
|
||||
|
||||
return ema;
|
||||
}
|
||||
|
||||
private static decimal GetClosePrice(
|
||||
|
||||
Reference in New Issue
Block a user