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;
}