hola, tengo este codigo y me gustaria ampliar los metodos de calculo de estacionalidad. Me gustaria implementar uno que usase SSA y otro FFT usando Accord.NET pero no tengo claro como se aplicarian esos metodos ya que lo que quiero es que se usen sobre el array de precios por anyo indexados por dia del anyo de forma que se analice la estacionalidad en base a todos los anyos separados.     public enum EnumSeasonalityModel
    {
        None,
        Additive,
        Multiplicative,
        Logarithmic
    };

    public enum EnumSeasonalityCalculationMethod
    {
        Mean,
        Median
    };

    public interface ISeasonalityCalculator
    {
        SeasonalityResult CalculateSeasonality(IReadOnlyList<DateTime> dates, IReadOnlyList<double> prices);
        double[] GenerateSeasonalitySeries(IReadOnlyList<DateTime> dates);
    }

    public record SeasonalityResult(double[] SeasonalIndices);

    public class AdvancedSeasonalityCalculator : ISeasonalityCalculator
    {
        private readonly EnumSeasonalityModel _model;
        private readonly ISeasonalityMethodStrategy _calculationMethod;
        private readonly IDataFiller _dataFiller;
        private readonly int _lookbackYears;
        private Dictionary<int, double[]> _seasonalIndicesByYear;

        public AdvancedSeasonalityCalculator(
            EnumSeasonalityModel model,
            ISeasonalityMethodStrategy calculationMethod,
            IDataFiller dataFiller,
            int lookbackYears)
        {
            if (lookbackYears < 0)
                throw new ArgumentException("Lookback years must be non-negative");

            _model = model;
            _calculationMethod = calculationMethod ?? throw new ArgumentNullException(nameof(calculationMethod));
            _dataFiller = dataFiller ?? throw new ArgumentNullException(nameof(dataFiller));
            _lookbackYears = lookbackYears;

            _seasonalIndicesByYear = new Dictionary<int, double[]>();
        }

        public SeasonalityResult CalculateSeasonality(IReadOnlyList<DateTime> dates, IReadOnlyList<double> prices)
        {
            ValidateInput(dates, prices);

            var sortedData = CreateSortedData(dates, prices);
            var dataByYear = GroupDataByYear(sortedData);

            ValidateYearlyData(dataByYear);

            _seasonalIndicesByYear = PrecalculateSeasonalIndicesByYear(dataByYear);

            // Opcional: puedes consolidar todos los índices en una sola matriz si es necesario
            var consolidatedIndices = dataByYear.SelectMany(g => _seasonalIndicesByYear[g.Key]).ToArray();

            return new SeasonalityResult(consolidatedIndices);
        }

        private Dictionary<int, double[]> PrecalculateSeasonalIndicesByYear(List<IGrouping<int, DatePrice>> dataByYear)
        {
            var seasonalIndicesByYear = new ConcurrentDictionary<int, double[]>();
            var exceptions = new ConcurrentQueue<Exception>();

            try
            {
                // Paso 1: Preparar todos los datos históricos por año
                var historicalDataByYear = new ConcurrentDictionary<int, List<DatePrice>>();
                // Guardamos las claves ordenadas para reutilizarlas
                var orderedYears = dataByYear.OrderBy(g => g.Key).Select(g => g.Key).ToList();

                foreach (var year in orderedYears)
                {
                    historicalDataByYear[year] = dataByYear.First(g => g.Key == year).ToList();
                }

                var lookbackDataByYear = new ConcurrentDictionary<int, List<DatePrice>>();
                var options = new ParallelOptions
                {
                    MaxDegreeOfParallelism = Environment.ProcessorCount
                };

                // Usamos orderedYears en lugar de volver a ordenar
                Parallel.ForEach(orderedYears, options, year =>
                {
                    try
                    {
                        var historicalData = historicalDataByYear.Where(kvp => kvp.Key < year).SelectMany(kvp => kvp.Value).ToList();

                        lookbackDataByYear[year] = (_lookbackYears == 0) ? historicalData : historicalData.Where(dp => dp.Date.Year >= year - _lookbackYears).ToList();
                    }
                    catch (Exception ex)
                    {
                        exceptions.Enqueue(ex);
                    }
                });

                if (!exceptions.IsEmpty)
                    throw new AggregateException(exceptions);

                // Usamos orderedYears nuevamente
                Parallel.ForEach(orderedYears, options, year =>
                {
                    try
                    {
                        var lookbackData = lookbackDataByYear[year];

                        seasonalIndicesByYear[year] = (lookbackData.Count == 0) ? DefaultSeasonalIndices() : CalculateSeasonalIndicesForYear(lookbackData);
                    }
                    catch (Exception ex)
                    {
                        exceptions.Enqueue(ex);
                    }
                });

                if (!exceptions.IsEmpty)
                    throw new AggregateException(exceptions);

                return new Dictionary<int, double[]>(seasonalIndicesByYear);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Error calculating seasonal indices", ex);
            }
        }

        private double[] CalculateSeasonalIndicesForYear(List<DatePrice> lookbackData)
        {
            var filledData = _dataFiller.FillMissingDays(lookbackData);
            var dayOfYearGroups = GroupByDayOfYear(filledData);
            var indices = CalculateIndicesForEachDay(dayOfYearGroups);
            return NormalizeIndices(indices);
        }

        public double[] GenerateSeasonalitySeries(IReadOnlyList<DateTime> dates)
        {
            if (dates == null)
                throw new ArgumentNullException(nameof(dates));

            var series = new double[dates.Count];

            double defaultValue = DefaultSeasonalValue();

            for (int i = 0; i < dates.Count; i++)
            {
                var date = dates[i];
                int currentYear = date.Year;
                int dayOfYear = AdjustDayForLeapYear(date);

                if (_seasonalIndicesByYear.TryGetValue(currentYear, out var indices))
                {
                    if (dayOfYear >= 1 && dayOfYear < indices.Length)
                    {
                        // Aquí es donde aplicamos la des-transformación
                        series[i] = _model == EnumSeasonalityModel.Logarithmic ? Math.Exp(indices[dayOfYear]) : indices[dayOfYear];
                    }
                    else
                    {
                        series[i] = defaultValue;
                    }
                }
                else
                {
                    series[i] = defaultValue;
                }
            }

            return series;
        }

        private double DefaultSeasonalValue()
        {
            return _model == EnumSeasonalityModel.Additive ? 0.0 : 1.0;
        }

        private void ValidateInput(IReadOnlyList<DateTime> dates, IReadOnlyList<double> prices)
        {
            if (dates == null) throw new ArgumentNullException(nameof(dates));
            if (prices == null) throw new ArgumentNullException(nameof(prices));
            if (dates.Count != prices.Count) throw new ArgumentException("Lists must have the same length");

            // Requisito actualizado: al menos un año completo de datos
            int years = dates.Select(d => d.Year).Distinct().Count();
            if (years < 1) throw new ArgumentException("At least one full year of data required");
        }

        private List<DatePrice> CreateSortedData(IReadOnlyList<DateTime> dates, IReadOnlyList<double> prices) => dates.Zip(prices, (date, price) => new DatePrice(date, price)).OrderBy(dp => dp.Date).ToList();

        private List<IGrouping<int, DatePrice>> GroupDataByYear(List<DatePrice> sortedData) => sortedData.GroupBy(dp => dp.Date.Year).OrderBy(g => g.Key).ToList();

        private void ValidateYearlyData(List<IGrouping<int, DatePrice>> dataByYear)
        {
            if (dataByYear.Count < 1)
                throw new ArgumentException("At least one year of data required");
        }

        private Dictionary<int, List<double>> GroupByDayOfYear(List<DatePrice> data)
        {
            var groups = new Dictionary<int, List<double>>();

            foreach (var point in data)
            {
                int day = AdjustDayForLeapYear(point.Date);

                if (!groups.ContainsKey(day))
                    groups[day] = new List<double>();

                groups[day].Add(point.Price);
            }

            return groups;
        }

        private double[] CalculateIndicesForEachDay(Dictionary<int, List<double>> dayGroups)
        {
            int maxDay = DayOfYearMax();
            var indices = new double[maxDay + 1];

            for (int day = 1; day <= maxDay; day++)
            {
                if (dayGroups.TryGetValue(day, out var values) && values.Count > 0)
                {
                    indices[day] = _calculationMethod.Calculate(values);
                }
                else
                {
                    indices[day] = InterpolateIndex(day, indices);
                }
            }

            return indices;
        }

        private int DayOfYearMax()
        {
            // Máximo día del año considerando años bisiestos
            return 366;
        }

        private double[] NormalizeIndices(double[] indices)
        {
            if (_model == EnumSeasonalityModel.Multiplicative)
            {
                double average = indices.Where(x => x > 0).Average();
                return indices.Select(x => x > 0 ? x / average : 1.0).ToArray();
            }
            else if (_model == EnumSeasonalityModel.Logarithmic)
            {
                var logIndices = indices.Select(x => x > 0 ? Math.Log(x) : 0.0).ToArray();
                double logAverage = logIndices.Average();
                return logIndices.Select(x => x - logAverage).ToArray();
            }
            else // Additive
            {
                double average = indices.Average();
                return indices.Select(x => x - average).ToArray();
            }
        }

        private double InterpolateIndex(int day, double[] indices)
        {
            var (prevDay, nextDay) = FindNearestValidDays(day, indices);
            return CalculateInterpolatedValue(prevDay, nextDay, indices);
        }

        private (int prev, int next) FindNearestValidDays(int day, double[] indices)
        {
            // Buscar hacia atrás incluyendo el final del año
            int prevDay = day;
            do
            {
                prevDay = prevDay > 1 ? prevDay - 1 : DayOfYearMax();
            } while (prevDay != day && indices[prevDay] == 0);

            // Buscar hacia adelante incluyendo el inicio del año
            int nextDay = day;
            do
            {
                nextDay = nextDay < DayOfYearMax() ? nextDay + 1 : 1;
            } while (nextDay != day && indices[nextDay] == 0);

            return (prevDay, nextDay);
        }

        private double CalculateInterpolatedValue(int prevDay, int nextDay, double[] indices)
        {
            if (prevDay >= 1 && nextDay <= DayOfYearMax())
                return (indices[prevDay] + indices[nextDay]) / 2.0;

            if (prevDay >= 1)
                return indices[prevDay];

            if (nextDay <= DayOfYearMax())
                return indices[nextDay];

            return _model == EnumSeasonalityModel.Multiplicative ? 1.0 : 0.0;
        }

        private int AdjustDayForLeapYear(DateTime date)
        {
            if (DateTime.IsLeapYear(date.Year) && date.DayOfYear > 59)
                return date.DayOfYear - 1;
            return date.DayOfYear;
        }

        private double[] DefaultSeasonalIndices()
        {
            return Enumerable.Repeat(_model == EnumSeasonalityModel.Multiplicative ? 1.0 : 0.0, DayOfYearMax() + 1).ToArray();
        }
    };

    // Interfaces y clases de apoyo
    public interface ISeasonalityMethodStrategy
    {
        double Calculate(IEnumerable<double> values);
    }

    public class MeanStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.Mean();
    }

    public class MedianStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.Median();
    }

    public interface IDataFiller
    {
        List<DatePrice> FillMissingDays(List<DatePrice> data);
    };

    public class LinearDataFiller : IDataFiller
    {
        public List<DatePrice> FillMissingDays(List<DatePrice> data)
        {
            if (data == null)
                throw new ArgumentNullException(nameof(data));
            if (!data.Any())
                return new List<DatePrice>();
            if (data.Count == 1)
                return new List<DatePrice>(data);

            var filledData = new List<DatePrice>();
            var firstDate = data.Min(d => d.Date);
            var lastDate = data.Max(d => d.Date);
            var currentDate = firstDate;
            var dataDict = data.ToDictionary(d => d.Date, d => d.Price);

            while (currentDate <= lastDate)
            {
                if (dataDict.TryGetValue(currentDate, out var price))
                {
                    filledData.Add(new DatePrice(currentDate, price));
                }
                else
                {
                    double interpolatedPrice = InterpolatePrice(currentDate, dataDict);
                    filledData.Add(new DatePrice(currentDate, interpolatedPrice));
                }

                currentDate = currentDate.AddDays(1);
            }

            return filledData;
        }

        private static double InterpolatePrice(DateTime date, Dictionary<DateTime, double> dataDict)
        {
            var keysMin = dataDict.Keys.Min();
            var keysMax = dataDict.Keys.Max();

            if (date < keysMin)
                return dataDict[keysMin];
            if (date > keysMax)
                return dataDict[keysMax];

            var prevDate = date.AddDays(-1);
            var nextDate = date.AddDays(1);

            while (!dataDict.ContainsKey(prevDate) && prevDate >= keysMin)
                prevDate = prevDate.AddDays(-1);
            while (!dataDict.ContainsKey(nextDate) && nextDate <= keysMax)
                nextDate = nextDate.AddDays(1);

            if (dataDict.ContainsKey(prevDate) && dataDict.ContainsKey(nextDate))
            {
                var totalDays = (nextDate - prevDate).TotalDays;
                var daysFromPrev = (date - prevDate).TotalDays;
                var weight = daysFromPrev / totalDays;

                return dataDict[prevDate] * (1 - weight) + dataDict[nextDate] * weight;
            }

            return dataDict.ContainsKey(prevDate) ? dataDict[prevDate] : dataDict[nextDate];
        }
    };

    // Factory para crear la calculadora
    public class SeasonalityCalculatorFactory
    {
        public static ISeasonalityCalculator Create(EnumSeasonalityModel model, EnumSeasonalityCalculationMethod method, int lookBackYears)
        {
            ISeasonalityMethodStrategy strategy = method switch
            {
                EnumSeasonalityCalculationMethod.Mean => new MeanStrategy(),
                EnumSeasonalityCalculationMethod.Median => new MedianStrategy(),
                _ => throw new ArgumentException("Invalid calculation method")
            };

            return new AdvancedSeasonalityCalculator(model, strategy, new LinearDataFiller(), lookBackYears);
        }
    };