hola tengo este codigo que calcula la estacionalidad de un activo financiero pero el problema que veo es que puede que un numero del dia del anyo en ciertos anyos recaiga en dominog y en otros en lunes u otro dia de la semana por lo que puede que en ciertos dias el mercado este cerrado, no haya datos, o simplemente que en cierto dia de la semana la volatilidad no sea la misma por lo que no se hasta que punto calcular la estacionalidad teniendo en cuenta unicament ele dia del anyo es lo mas robusto o preciso.     public class AdvancedSeasonalityCalculator : ISeasonalityCalculator
    {
        private readonly EnumSeasonalityModel _model;
        private readonly ISeasonalityMethodStrategy _calculationMethod;
        private readonly IDataFiller _dataFiller;
        private readonly int _lookbackYears;
        private readonly ITimeSeriesFilter _timeSeriesFilter;
        private Dictionary<int, double[]> _seasonalIndicesByYear;

        public AdvancedSeasonalityCalculator(
            EnumSeasonalityModel model,
            ISeasonalityMethodStrategy calculationMethod,
            IDataFiller dataFiller,
            ITimeSeriesFilter timeSeriesFilter,
            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));
            _timeSeriesFilter = timeSeriesFilter;
            _lookbackYears = lookbackYears;

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

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

            var sortedData = CreateSortedData(dates, prices);
            sortedData = _dataFiller.FillMissingDays(sortedData);
            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
            {
                // Guardamos las claves ordenadas para reutilizarlas
                var orderedYears = dataByYear.OrderBy(g => g.Key).Select(g => g.Key).ToList();

                var historicalDataByYear = dataByYear.ToDictionary(g => g.Key, g => g.ToList());

                var lookbackDataByYear = new ConcurrentDictionary<int, List<DatePrice>>();

                var options = new ParallelOptions
                {
                    MaxDegreeOfParallelism = Environment.ProcessorCount
                };

                int firstYear = orderedYears.FirstOrDefault();

                // Usamos orderedYears en lugar de volver a ordenar
                Parallel.ForEach(orderedYears, options, year =>
                {
                    try
                    {
                        int y0 = _lookbackYears == 0 ? firstYear : Math.Max(year - _lookbackYears, firstYear);

                        // Filtering
                        if (_timeSeriesFilter != null)
                        {
                            var targetSeries = historicalDataByYear[year].PriceData().ToArray();

                            lookbackDataByYear[year] = historicalDataByYear.Where(kvp => (kvp.Key < year) && (kvp.Key >= y0) && _timeSeriesFilter.IsValid(targetSeries, kvp.Value.PriceData().ToArray())).SelectMany(kvp => kvp.Value).ToList();
                        }
                        else
                        {
                            lookbackDataByYear[year] = historicalDataByYear.Where(kvp => (kvp.Key < year) && (kvp.Key >= y0)).SelectMany(kvp => kvp.Value).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 dayOfYearGroups = GroupByDayOfYear(lookbackData);
            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()
        {
            double defaultValue = DefaultSeasonalValue();

            return Enumerable.Repeat(defaultValue, 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 class WeightedMeanStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.WeightedArithmeticMean(0.001);
    }

    public class InterPercentile10MeanStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.ToArray().InterquantileValues(0.1, 0.9, TimeSeriesExtensions.InputSortOption.SortInPlace).Mean();
    }
    public class InterPercentile25MeanStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.ToArray().InterquantileValues(0.25, 0.75, TimeSeriesExtensions.InputSortOption.SortInPlace).Mean();
    }

    public class InterPercentile10MedianStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.ToArray().InterquantileValues(0.1, 0.9, TimeSeriesExtensions.InputSortOption.SortInPlace).Median();
    }

    public class InterPercentile25MedianStrategy : ISeasonalityMethodStrategy
    {
        public double Calculate(IEnumerable<double> values) => values.ToArray().InterquantileValues(0.25, 0.75, TimeSeriesExtensions.InputSortOption.SortInPlace).Median();
    }

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

    public class LinearDataFiller : IDataFiller
    {
#if false
        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;
        }
#else
        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);

            // Get the first and last available dates from the input data
            var firstAvailableDate = data.Min(d => d.Date);
            var lastAvailableDate = data.Max(d => d.Date);

            // Ensure the range is from January 1st to December 31st of the same year as the data
            var firstDate = new DateTime(firstAvailableDate.Year, 1, 1);
            var lastDate = new DateTime(lastAvailableDate.Year, 12, 31);

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

            // Fill the data from January 1st to December 31st
            while (currentDate <= lastDate)
            {
                if (!dataDict.TryGetValue(currentDate, out double price))
                {
                    price = (currentDate < firstAvailableDate) ? dataDict[firstAvailableDate] : (currentDate > lastAvailableDate) ? dataDict[lastAvailableDate] : InterpolatePrice(currentDate, dataDict);
                }

                filledData.Add(new DatePrice(currentDate, price));

                currentDate = currentDate.AddDays(1);
            }

            return filledData;
        }
#endif
        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, EnumTimeSeriesFilter timeSeriesFilterType, double filterThreshold, int lookBackYears)
        {
            ISeasonalityMethodStrategy strategy = method switch
            {
                EnumSeasonalityCalculationMethod.Mean => new MeanStrategy(),
                EnumSeasonalityCalculationMethod.Median => new MedianStrategy(),
                EnumSeasonalityCalculationMethod.WeightedMean => new WeightedMeanStrategy(),
                EnumSeasonalityCalculationMethod.InterPercentile10Mean => new InterPercentile10MeanStrategy(),
                EnumSeasonalityCalculationMethod.InterPercentile25Mean => new InterPercentile25MeanStrategy(),
                EnumSeasonalityCalculationMethod.InterPercentile10Median => new InterPercentile10MedianStrategy(),
                EnumSeasonalityCalculationMethod.InterPercentile25Median => new InterPercentile25MedianStrategy(),
                _ => throw new ArgumentException("Invalid calculation method")
            };

            ITimeSeriesFilter timeSeriesFilter = timeSeriesFilterType switch
            {
                EnumTimeSeriesFilter.None => null,
                EnumTimeSeriesFilter.PearsonCorrelation => new PearsonCorrelationFilter(filterThreshold),
                EnumTimeSeriesFilter.NegativePearsonCorrelation => new NegativePearsonCorrelationFilter(filterThreshold),
                EnumTimeSeriesFilter.AbsolutePearsonCorrelation => new AbsolutePearsonCorrelationFilter(filterThreshold),
                EnumTimeSeriesFilter.WeightedPearsonCorrelation => new WeightedPearsonCorrelationFilter(filterThreshold),
                EnumTimeSeriesFilter.WeightedNegativePearsonCorrelation => new WeightedNegativePearsonCorrelationFilter(filterThreshold),
                EnumTimeSeriesFilter.WeightedAbsolutePearsonCorrelation => new WeightedAbsolutePearsonCorrelationFilter(filterThreshold),
                _ => throw new ArgumentException("Invalid time series filter type")
            };

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