using System; using System.Collections.Generic; using System.Text; using System.Drawing; using WealthLab; using WealthLab.Indicators; using System.Linq; //Click "References...", check "System.Core", click OK using System.IO; using System.Globalization; using System.Windows.Forms.DataVisualization.Charting; //1. Click "References...", then "Other Assemblies..." > "Add a reference" //2. In "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\" (or "Framework" on 32-bit systems), //choose and okay "System.Windows.Forms.DataVisualization.dll"namespace WealthLab.Strategies { public class SC_2019_09_KaufmanSeasonality : WealthScript { private StrategyParameter paramYears; private StrategyParameter paramPrice; private StrategyParameter paramThresholdHigh; private StrategyParameter paramThresholdLow; public SC_2019_09_KaufmanSeasonality() { paramYears = CreateParameter("Average, Years", 4, 2, 100, 1); paramPrice = CreateParameter("Use (H+L)/2 or Close", 0, 0, 1, 1); paramThresholdHigh = CreateParameter("High freq >", 75, 50, 90, 5); paramThresholdLow = CreateParameter("Low freq <", 25, 5, 50, 5); } protected override void Execute() { if (Bars.Scale != BarScale.Monthly) { DrawLabel( PricePane, "Please switch to Monthly scale"); Abort(); } var howManyYearsToAverage = paramYears.ValueInt; var firstYearWithValidData = Date[0].Year + howManyYearsToAverage; var startBar = DateTimeToBar(new DateTime( firstYearWithValidData, 12, 31), false); //3. Only trade if the high frequency is 75 % or greater and the low frequency is 25 % or lower. var thresholdHigh = paramThresholdHigh.ValueInt / 100d; var thresholdLow = paramThresholdLow.ValueInt / 100d; //Average annual price DataSeries avgYearlyPrice = AveragePrice.Series(BarScaleConverter.ToYearly(Bars)); //Average monthly prices (take AveragePrice or simply Close) SetScaleMonthly(); DataSeries avgMonthlyPrice = paramPrice.ValueInt == 0 ? AveragePrice.Series(Bars) : Close; RestoreScale(); avgMonthlyPrice = Synchronize( avgMonthlyPrice); avgYearlyPrice = Synchronize( avgYearlyPrice); HideVolume(); //Collect monthly average price var lstMonths = new List<MonthData>(); for (int bar = 1; bar < Bars.Count; bar++) { if (Date[bar].Month != Date[bar - 1].Month) //New month { lstMonths.Add(new MonthData(Date[bar].Month, Date[bar].Year, avgMonthlyPrice[bar])); } } //Calculations for (int bar = GetTradingLoopStartBar( startBar); bar < Bars.Count; bar++) { if (bar <= 0) continue; int yearTo = Date[bar].Year; int yearFrom = yearTo - 1 - howManyYearsToAverage; //Average price by year var yearlyAverages = lstMonths.GroupBy(i => i.Year) .Where(i => i.Key < yearTo & i.Key >= yearFrom) .Select(g => new { Year = g.Key, Average = g.Average(a => a.AvgPrice) }); var lstFreqUp = new Dictionary<int, double>(); var lstBreakdown = new Dictionary<int, Tuple<double, double>>(); //Calculate Monthly Adjusted Returns, Up Months and Frequency of Positive Returns for (int month = 1; month <= 12; month++) { int monthCount = 0, upMonths = 0; double freqUp = 0d, monthlyAdjReturn = 0d; foreach (var _m in lstMonths) { //Ensure this year's data is excluded from processing and trading if (_m.Month == month && _m.Year < yearTo && _m.Year >= yearFrom) { monthCount++; var givenYearAverage = yearlyAverages.GroupBy(i => i.Year). Where(i => i.Key == _m.Year).First().ToList(); var _adjReturn = _m.AvgPrice / givenYearAverage[0].Average - 1; if (_adjReturn > 0) upMonths++; monthlyAdjReturn += _adjReturn; } } if (monthCount > 0) { freqUp = upMonths / (double)monthCount; monthlyAdjReturn /= monthCount; } //1. Average the monthly frequency of the past N years. lstFreqUp.Add(month, freqUp); lstBreakdown.Add(month, new Tuple<double, double>(freqUp, monthlyAdjReturn)); } //Plot actual chart of Frequency of Positive Returns (for last N years) if(bar == Bars.Count-1) { Chart chart = new Chart(); //Histogram chart chart.BackColor = Color.Transparent; chart.Width = 500; string name = "Bins"; chart.ChartAreas.Add(name); chart.ChartAreas[0].AxisX.MajorGrid.Enabled = chart.ChartAreas[0].AxisY.MajorGrid.Enabled = false; chart.ChartAreas[0].AxisX.Title = "Month"; //Custom axis titles chart.ChartAreas[0].AxisY.Title = "Frequency of Positive Returns"; chart.ChartAreas[0].BackColor = Color.Transparent; chart.ChartAreas[0].AxisY.Minimum = 0; chart.ChartAreas[0].AxisY.Maximum = 100; chart.Series.Add(name); chart.Series[name].ChartType = SeriesChartType.Column; foreach (var f in lstFreqUp) { chart.Series[name].Points.AddXY( CultureInfo.GetCultureInfo("en-US").DateTimeFormat.GetMonthName(f.Key), f.Value * 100d); } using (MemoryStream memStream = new MemoryStream()) { chart.SaveImage(memStream, ChartImageFormat.Png); Image img = Image.FromStream(memStream); DrawImage( PricePane, img, Bars.Count-50, Close[Bars.Count-50], false ); } } //Trading if( IsLastPositionActive) { int monthToExit = (int)LastPosition.Tag; if (Date[bar].Month == monthToExit) ExitAtClose(bar, LastPosition, monthToExit.ToString()); } else { //Month numbers with frequency higher (lower) than a threshold var highFreqMonths = lstFreqUp.Where(p => p.Value > thresholdHigh); var lowFreqMonths = lstFreqUp.Where(p => p.Value < thresholdLow); var resultsAreValid = (highFreqMonths.Count() > 0 && lowFreqMonths.Count() > 0); if (resultsAreValid) { //2. Find the last occurrences of the highest (lowest) frequency int lastHighestFrequencyMonth = 0, lastLowestFrequencyMonth = 0; lastHighestFrequencyMonth = highFreqMonths.LastOrDefault().Key; lastLowestFrequencyMonth = lowFreqMonths.LastOrDefault().Key; //4.If the high frequency comes first, sell short at the end of the month with the high frequency. if (lastHighestFrequencyMonth < lastLowestFrequencyMonth) { if (Date[bar].Month == lastHighestFrequencyMonth) if( ShortAtClose(bar, lastHighestFrequencyMonth.ToString()) != null) //Cover the short at the end of the month with the low frequency. LastPosition.Tag = (object)lastLowestFrequencyMonth; } //5. If the low frequency comes first, buy at the end of the month with the low frequency. else { if (Date[bar].Month == lastLowestFrequencyMonth) if( BuyAtClose(bar, lastLowestFrequencyMonth.ToString()) != null) //Sell to exit at the end of the month with the high frequency. LastPosition.Tag = (object)lastHighestFrequencyMonth; } } } } } } public class MonthData { public MonthData() { } public MonthData(int month, int year, double avgPrice) { Month = month; Year = year; AvgPrice = avgPrice; } private int _month; public int Month { get { return _month; } set { _month = value; } } private int _year; public int Year { get { return _year; } set { _year = value; } } private double _avgPrice; public double AvgPrice { get { return _avgPrice; } set { _avgPrice = value; } } } }