public void HWTTest2WeekSystemPriceOptim() { double[] y = { 31.0500, 30.4700, 28.9200, 27.8800, 26.9600, 27.8400, 28.7900, 28.6300, 28.4400, 28.3000, 30.6500, 31.5500, 32.1600, 32.4500, 32.6300, 33.6500, 34.9000, 36.2200, 36.6500, 36.3700, 35.4900, 34.4100, 34.6600, 32.5500, 33.1500, 32.6600, 31.8300, 31.4700, 32.5600, 34.3600, 36.2800, 38.3900, 39.0900, 38.3300, 38.4200, 38.2500, 37.9600, 37.8900, 37.8800, 38.7800, 39.8300, 39.9100, 39.3200, 38.4900, 37.4600, 36.9400, 36.3700, 34.5900, 33.1100, 32.2200, 31.4600, 31.6700, 32.0500, 33.6700, 34.9300, 35.8200, 36.3800, 36.5200, 36.7100, 36.6000, 36.5100, 36.4000, 36.4200, 36.5800, 36.9400, 36.9400, 36.8100, 36.4300, 35.9100, 35.4500, 34.7700, 32.0900, 31.7100, 30.8600, 30.2100, 30.3600, 30.8900, 32.2100, 34.3300, 35.9800, 36.7200, 36.6800, 36.8000, 36.6400, 36.4400, 36.3100, 35.8300, 36.1200, 36.8000, 37.2900, 37.1500, 36.4700, 36.0300, 35.4300, 35.1700, 32.9500, 33.6900, 32.7100, 32.0900, 32.1700, 32.7300, 33.7700, 34.0300, 33.9800, 34.4500, 36.1200, 36.6900, 36.7400, 36.5200, 36.1200, 36.1400, 36.7800, 37.7100, 38.3700, 37.9900, 36.9400, 36.2400, 35.8900, 35.7000, 35.4400, 34.8700, 33.0500, 32.2500, 32.4100, 32.4500, 32.8000, 33.2700, 32.9700, 33.6500, 34.2500, 35.0100, 35.9400, 35.7900, 35.0200, 34.8500, 35.1800, 35.9000, 37.0900, 37.6500, 37.3900, 36.9900, 36.1000, 36.0000, 34.9300, 34.9700, 33.7100, 33.1300, 33.2300, 34.1000, 35.6800, 38.5100, 40.6600, 42.4800, 42.0800, 41.9700, 41.3700, 40.3000, 39.9600, 40.1700, 41.6000, 42.3700, 41.9300, 39.9700, 38.9000, 37.9200, 37.4700, 36.4900, 35.5500 }; int period = 24; int m = 3 * 24; double alpha = 0.5; double beta = 0.4; double gamma = 0.6; var abg = HoltWinters.OptimizeTripleHWT(y, period, m); double[] prediction = HoltWinters.TripleHWT(y, period, m, abg.alpha, abg.beta, abg.gamma); var tst = string.Join(" ", y.Select(x => x.ToString())); var tstpred = string.Join(" ", prediction.Select(x => x.ToString())); //Assert.AreEqual("Forecast does not match", expected, // prediction, 0.0000000000001); }
public JsonResult Forecast(DateTime?date, string forecastMethod, string spikesPreprocessMethod, double spikesThreshold, string forecastModel, string timeHorizon, double confidence, string MathModel, string exogenousVariables) { var dt = date.HasValue ? date.Value : DateTime.Today; var ths = GetUnionCaseNames <Types.TimeHorizon>(); var forecastHorizon = Array.IndexOf(ths, timeHorizon); var forecastingMethod = GetUnionCaseFromName <ForecastMethod>(forecastMethod); if (forecastHorizon < 0) { throw new ArgumentException("Wrong argument"); } //naive hardcoded for now... //data to estimate on is not an input parameter... var d = AppData.GetHistoricalSeries(_timeSeries); var data = d.Where(x => x.DateTime < dt) .Select(x => x.Value != null ? (double)x.Value : 0) //0 for now, when interpolation is done... etc.. .ToArray(); var spikesPreprocess = GetUnionCaseFromName <SpikePreprocess>(spikesPreprocessMethod); if (spikesPreprocess.IsSimilarDay) { //First deseasonalize data at larger lags, all >= weekly, a stable series is need since spikes are at lengths <= day var desezonalizedData = Desezonalize(data, 168); //Then perform the spike estimation var spikeIndices = EstimateSpikesOnMovSDs(desezonalizedData, 24, 2, spikesThreshold); //go ahead and pre process the d series... yeah... data = ReplaceSingularSpikes(data, spikeIndices, spikesPreprocess, 0.95); } var horizon = GetTimeHorizonValue(forecastHorizon); var rlzd = d.Where(x => x.DateTime >= dt) .Take(horizon) .Select(x => x.Value != null ? (double)x.Value : 0) //0 for now, when interpolation is done... etc.. .ToArray(); ForecastResult forecast; double[] forecasted; object model; var oneYear = (int)Math.Floor((dt - dt.AddMonths(-12)).TotalHours); var halfYear = (int)Math.Floor((dt - dt.AddMonths(-6)).TotalHours); var seasons = new int[] { oneYear, halfYear, 24 * 7 * 4 * 3, 24 * 7 * 4, 24 * 7, 24 }; //we need always seasonalities >= forecast horizon var relevantSeasons = seasons.Where(s => s >= horizon).ToArray(); double[] estimationData = null; //add naive with exogenous variables next... if (forecastingMethod == ForecastMethod.Naive) { forecast = Naive(data, relevantSeasons, horizon, confidence); forecasted = forecast.Forecast.Take(rlzd.Length).ToArray(); model = new object(); } else if (forecastingMethod == ForecastMethod.HoltWinters) { estimationData = data.Reverse().Take(24 * 7 * 2).Reverse().ToArray(); var hwparams = JsonConvert.DeserializeObject <HoltWinters.HoltWintersParams>(MathModel); //optimize if all seem to be wrong if (hwparams.alpha == 0 || hwparams.beta == 0 || hwparams.gamma == 0) { hwparams = HoltWinters.OptimizeTripleHWT(estimationData, 24, horizon); } forecast = HoltWinters.TripleHWTWithPIs(estimationData, 24, horizon, hwparams, confidence); forecasted = forecast.Forecast.Take(rlzd.Length).ToArray(); model = hwparams; } else if (forecastingMethod == ForecastMethod.ARMA) { var exogenousVariablesJs = JObject.Parse(exogenousVariables); //alternative...? //always take next 2 highest seasons... var maSes = seasons.Where(s => s >= horizon).OrderBy(x => x).Take(2).ToArray(); var arSes = new int[] { 1, 2, 24, 25 }; //twice as the highest season estimation data var estimationDataLength = maSes.Last() * 2; estimationData = data.Reverse().Take(estimationDataLength).Reverse().ToArray(); var isUnivariate = exogenousVariablesJs.Properties().Where(p => p.Value.ToString() == "True").Count() == 0; if (isUnivariate) { var arma = JsonConvert.DeserializeObject <ARMAResult>(MathModel); //problem: when changing param for ARMA, provide forecast, when changed date or horizon, re-estimate...yes... //fix it later if (arma.AR == null || arma.AR.Coefficients == null) { } arma = ARMASimple2(estimationData, arSes, maSes); var inSampleRes = Infer(arma, estimationData); forecast = MarketModels.TimeSeries.Forecast(estimationData, inSampleRes, arma, horizon, confidence); //when done out of sample, matches the realized and forecasted lengths to compute fit... //if no available data, no fit can be done... forecasted = forecast.Forecast.Take(rlzd.Length).ToArray(); //allow only AR and MA coefficients to be changed //model = new { AR = arma.AR, MA = arma.MA }; model = null; } else { var arxma = JsonConvert.DeserializeObject <ARXMAModel>(MathModel); var vars = exogenousVariablesJs.Properties() .Where(p => p.Value.ToString() == "True") .Select(p => p.Name) .ToList(); var specialDaysSelected = vars.Remove("SpecialWeekDays"); var exData = vars .Select(p => AppData.GetHistoricalSeries(_exVarsPre + p + "_Hourly_All.json") .Where(x => x.DateTime < dt) .Select(x => x.Value != null ? (double)x.Value : 0) //0 for now, when interpolation is done... etc.. .Reverse().Take(estimationDataLength + horizon).Reverse().ToArray() .ToList() ).ToList().ColumnsToRectangularArray(); //append indicators for special data... if (specialDaysSelected) { //Friday, Saturday and Sunday var ivars = MathFunctions.IndicatorVariablesMatrix(estimationDataLength + horizon, 168, 24, new int[] { 0, 5, 6 }); //concatenate matrices to columns if (exData.GetLength(1) > 0) { exData = MathFunctions.concat2D(exData, ivars, false); } else { exData = ivars; } } var exDataEst = MathFunctions.firstRows2D(exData, estimationDataLength); arxma = ARXMASimple2(estimationData, exDataEst, arSes, maSes); var inSampleRes = arxma.Infer(estimationData, exDataEst); forecast = arxma.Forecast(estimationData, inSampleRes, exData, horizon, confidence); //when done out of sample, matches the realized and forecasted lengths to compute fit... //if no available data, no fit can be done... forecasted = forecast.Forecast.Take(rlzd.Length).ToArray(); //allow only AR and MA coefficients to be changed //model = new { AR = arma.AR, MA = arma.MA }; model = null; } } else { throw new ArgumentException("Unsupported argument passed"); } var log = ""; if (forecast.Forecast.Any(x => x > 500)) { log += "Some data is wrong..."; } if (forecast.Forecast.HasInvalidData() || forecast.Confidence.HasInvalidData()) { throw new Exception("Abnormal results generated"); } /* Post processing */ //cap all values above a reasonable limit, e.g. 500 CapSeries(forecast.Forecast, 500, -100); CapMultipleSeries(forecast.Confidence, 500, -100); FitStatistics eFit = null, eBFit = null, ePFit = null, fit = null, bfit = null, pfit = null; if (rlzd.Length > 0) { fit = ForecastFit(forecasted, rlzd); pfit = ForecastFit( GetSubPeriodsFrom(forecasted, 24, DAY_PEAK_HOURS), GetSubPeriodsFrom(rlzd, 24, DAY_PEAK_HOURS)); bfit = ForecastFit( GetSubPeriodsFrom(forecasted, 24, DAY_BASE_HOURS), GetSubPeriodsFrom(rlzd, 24, DAY_BASE_HOURS)); } if (estimationData != null && forecast.Backcast.Length > 0) { //model with estimation, that is: the model is different than naive eFit = ForecastFit(forecast.Backcast, estimationData); ePFit = ForecastFit( GetSubPeriodsFrom(forecast.Backcast, 24, DAY_PEAK_HOURS), GetSubPeriodsFrom(estimationData, 24, DAY_PEAK_HOURS)); eBFit = ForecastFit( GetSubPeriodsFrom(forecast.Backcast, 24, DAY_BASE_HOURS), GetSubPeriodsFrom(estimationData, 24, DAY_BASE_HOURS)); } var obj = new { Result = forecast, MathModel = model, DaysAhead = horizon / 24, //in sample fit for all hours EstimationFit = eFit, BaseEstimationFit = eBFit, PeakEstimationFit = ePFit, Fit = fit, BaseFit = bfit, //refine later... PeakFit = pfit, //additional remarks about the resulting data... Log = log }; return(Json(obj, JsonRequestBehavior.AllowGet)); }