refactor(dotnet): simplify factor calculator guards
This commit is contained in:
@@ -26,31 +26,16 @@ namespace QuantEngine.Core.Domain
|
|||||||
return new FactorOutputs(0, 0, 0, 0, 0, 1.0, 0);
|
return new FactorOutputs(0, 0, 0, 0, 0, 1.0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure sorted chronologically (oldest to newest)
|
|
||||||
var sortedStock = stockBars.OrderBy(b => b.TradeDate).ToList();
|
var sortedStock = stockBars.OrderBy(b => b.TradeDate).ToList();
|
||||||
var sortedIndex = indexBars?.OrderBy(b => b.TradeDate).ToList() ?? new List<PriceHistoryDailyRecord>();
|
var sortedIndex = indexBars?.OrderBy(b => b.TradeDate).ToList() ?? new List<PriceHistoryDailyRecord>();
|
||||||
|
return new FactorOutputs(
|
||||||
int count = sortedStock.Count;
|
CalculateMomentum(sortedStock, 20),
|
||||||
double closeToday = (double)sortedStock[^1].Close;
|
CalculateMomentum(sortedStock, 60),
|
||||||
|
CalculateMomentum(sortedStock, 120),
|
||||||
// 1. Momentum
|
CalculateAtr20Pct(sortedStock),
|
||||||
double mom20 = CalculateMomentum(sortedStock, 20);
|
CalculatePriceStDev20D(sortedStock),
|
||||||
double mom60 = CalculateMomentum(sortedStock, 60);
|
CalculateBeta60D(sortedStock, sortedIndex),
|
||||||
double mom120 = CalculateMomentum(sortedStock, 120);
|
CalculateRs20D(sortedStock, sortedIndex));
|
||||||
|
|
||||||
// 2. ATR 20D Percentage
|
|
||||||
double atrPct = CalculateAtr20Pct(sortedStock);
|
|
||||||
|
|
||||||
// 3. Price Standard Deviation 20D
|
|
||||||
double stdev = CalculatePriceStDev20D(sortedStock);
|
|
||||||
|
|
||||||
// 4. Beta 60D
|
|
||||||
double beta = CalculateBeta60D(sortedStock, sortedIndex);
|
|
||||||
|
|
||||||
// 5. Relative Strength (RS) 20D (vs Index)
|
|
||||||
double rs = CalculateRs20D(sortedStock, sortedIndex);
|
|
||||||
|
|
||||||
return new FactorOutputs(mom20, mom60, mom120, atrPct, stdev, beta, rs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double CalculateMomentum(List<PriceHistoryDailyRecord> bars, int period)
|
private static double CalculateMomentum(List<PriceHistoryDailyRecord> bars, int period)
|
||||||
@@ -58,7 +43,7 @@ namespace QuantEngine.Core.Domain
|
|||||||
if (bars.Count <= period) return 0.0;
|
if (bars.Count <= period) return 0.0;
|
||||||
double current = (double)bars[^1].Close;
|
double current = (double)bars[^1].Close;
|
||||||
double prev = (double)bars[^(period + 1)].Close;
|
double prev = (double)bars[^(period + 1)].Close;
|
||||||
if (prev <= 0.0) return 0.0;
|
if (prev <= 0.0 || double.IsNaN(prev) || double.IsInfinity(prev)) return 0.0;
|
||||||
return ((current - prev) / prev) * 100.0;
|
return ((current - prev) / prev) * 100.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +64,7 @@ namespace QuantEngine.Core.Domain
|
|||||||
|
|
||||||
double atr = trList.Average();
|
double atr = trList.Average();
|
||||||
double closeToday = (double)bars[^1].Close;
|
double closeToday = (double)bars[^1].Close;
|
||||||
if (closeToday <= 0.0) return 0.0;
|
if (closeToday <= 0.0 || double.IsNaN(closeToday) || double.IsInfinity(closeToday)) return 0.0;
|
||||||
return (atr / closeToday) * 100.0;
|
return (atr / closeToday) * 100.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +75,6 @@ namespace QuantEngine.Core.Domain
|
|||||||
var subset = bars.Skip(bars.Count - 20).Select(b => (double)b.Close).ToList();
|
var subset = bars.Skip(bars.Count - 20).Select(b => (double)b.Close).ToList();
|
||||||
double avg = subset.Average();
|
double avg = subset.Average();
|
||||||
double sumOfSquares = subset.Sum(val => Math.Pow(val - avg, 2));
|
double sumOfSquares = subset.Sum(val => Math.Pow(val - avg, 2));
|
||||||
|
|
||||||
// Sample standard deviation (N-1)
|
|
||||||
return Math.Sqrt(sumOfSquares / (subset.Count - 1));
|
return Math.Sqrt(sumOfSquares / (subset.Count - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,11 +82,9 @@ namespace QuantEngine.Core.Domain
|
|||||||
{
|
{
|
||||||
if (stock.Count < 61 || index.Count < 61) return 1.0;
|
if (stock.Count < 61 || index.Count < 61) return 1.0;
|
||||||
|
|
||||||
// Align daily returns
|
|
||||||
var stockMap = stock.ToDictionary(b => b.TradeDate);
|
var stockMap = stock.ToDictionary(b => b.TradeDate);
|
||||||
var indexMap = index.ToDictionary(b => b.TradeDate);
|
var indexMap = index.ToDictionary(b => b.TradeDate);
|
||||||
|
|
||||||
// Compute returns for overlapping dates
|
|
||||||
var overlappingDates = stockMap.Keys.Intersect(indexMap.Keys).OrderBy(d => d).ToList();
|
var overlappingDates = stockMap.Keys.Intersect(indexMap.Keys).OrderBy(d => d).ToList();
|
||||||
if (overlappingDates.Count < 61) return 1.0;
|
if (overlappingDates.Count < 61) return 1.0;
|
||||||
|
|
||||||
@@ -113,7 +94,6 @@ namespace QuantEngine.Core.Domain
|
|||||||
var stockReturns = new List<double>();
|
var stockReturns = new List<double>();
|
||||||
var indexReturns = new List<double>();
|
var indexReturns = new List<double>();
|
||||||
|
|
||||||
// Calculate returns starting from last 60 days
|
|
||||||
int startIdx = Math.Max(1, alignedStock.Count - 60);
|
int startIdx = Math.Max(1, alignedStock.Count - 60);
|
||||||
for (int i = startIdx; i < alignedStock.Count; i++)
|
for (int i = startIdx; i < alignedStock.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -154,7 +134,6 @@ namespace QuantEngine.Core.Domain
|
|||||||
{
|
{
|
||||||
if (stock.Count < 21 || index.Count < 21) return 0.0;
|
if (stock.Count < 21 || index.Count < 21) return 0.0;
|
||||||
|
|
||||||
// Align dates
|
|
||||||
var stockMap = stock.ToDictionary(b => b.TradeDate);
|
var stockMap = stock.ToDictionary(b => b.TradeDate);
|
||||||
var indexMap = index.ToDictionary(b => b.TradeDate);
|
var indexMap = index.ToDictionary(b => b.TradeDate);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user