namespace QuantEngine.Core.QuantEngine; using System.Linq; /// /// 데이터 기반 팩터 분석 엔진 /// 원칙: 데이터 기반 퀀트, 게임이론, 패턴화, 고도화 /// public class FactorEngine { /// /// 가격 모멘텀 팩터 계산 /// public double CalculateMomentum(List prices, int period = 20) { if (prices.Count < period) return 0; var recent = prices.TakeLast(period).ToList(); var oldest = prices[prices.Count - period]; var newest = prices.Last(); // 가격 변화율 return (double)(newest - oldest) / (double)oldest; } /// /// 변동성 팩터 (표준편차) /// public double CalculateVolatility(List prices) { if (prices.Count < 2) return 0; var mean = (double)prices.Average(); var variance = prices .Select(p => Math.Pow((double)p - mean, 2)) .Average(); return Math.Sqrt(variance); } /// /// RSI (Relative Strength Index) 팩터 /// public double CalculateRSI(List prices, int period = 14) { if (prices.Count < period + 1) return 50; var changes = prices .Zip(prices.Skip(1), (a, b) => b - a) .ToList(); var gains = changes.Where(c => c > 0).Sum(); var losses = Math.Abs(changes.Where(c => c < 0).Sum()); if (losses == 0) return 100; if (gains == 0) return 0; var rs = (double)gains / (double)losses; return 100 - (100 / (1 + rs)); } /// /// 거래량 팩터 (Volume Strength) /// public double CalculateVolumeStrength(List prices, List volumes) { if (prices.Count < 20 || volumes.Count < 20) return 0.5; var recent20 = volumes.TakeLast(20).ToList(); var avg20 = recent20.Average(); var latest = recent20.Last(); // 최근 거래량이 20일 평균보다 얼마나 높은가 return latest / avg20; } /// /// 복합 팩터 점수 (0-100) /// public FactorScore CalculateCompositeScore( double momentum, double volatility, double rsi, double volumeStrength) { // 정규화 var momentumScore = Normalize(momentum, -0.5, 0.5) * 25; var rsiScore = Math.Max(0, Math.Min(100, rsi)) * 0.25; var volumeScore = Math.Max(0, Math.Min(2.0, volumeStrength)) * 50; // 변동성은 높을수록 감점 (리스크) var volatilityPenalty = Math.Min(25, volatility * 50); var compositeScore = momentumScore + rsiScore + volumeScore - volatilityPenalty; return new FactorScore { Momentum = momentumScore, RSI = rsiScore, Volume = volumeScore, VolatilityPenalty = volatilityPenalty, CompositeScore = Math.Max(0, Math.Min(100, compositeScore)), Signal = GenerateSignal(compositeScore), }; } private string GenerateSignal(double score) { return score switch { >= 75 => "Strong Buy", >= 60 => "Buy", >= 40 => "Hold", >= 25 => "Sell", _ => "Strong Sell", }; } private double Normalize(double value, double min, double max) { var range = max - min; if (range == 0) return 0.5; return (value - min) / range; } } public record FactorScore { public double Momentum { get; init; } public double RSI { get; init; } public double Volume { get; init; } public double VolatilityPenalty { get; init; } public double CompositeScore { get; init; } public string Signal { get; init; } = string.Empty; }