Just wanted to share this code of a seasonal trading system which does some nice plotting with ScottPlot:
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using System.Drawing; using System.Collections.Generic; using System.Linq; using ScottPlot; using System.Globalization; namespace WealthScript1 { public class MonthData { public MonthData() { } public MonthData(int month, int year, double avgPrice) { Month = month; Year = year; AvgPrice = avgPrice; } public int Month; public int Year; public double AvgPrice; } public class SC_2019_09_KaufmanSeasonality : UserStrategyBase { int paramYears, howManyYearsToAverage, firstYearWithValidData, startBar, paramThresholdHigh, paramThresholdLow; double thresholdHigh, thresholdLow; Dictionary<double, double> lstFreqUp = new Dictionary<double, double>(); Dictionary<int, Tuple<double, double>> lstBreakdown = new Dictionary<int, Tuple<double, double>>(); public SC_2019_09_KaufmanSeasonality() { AddParameter("Average, Years", ParameterTypes.Int32, 4, 2, 100, 1); AddParameter("Use (H+L)/2 or Close", ParameterTypes.Int32, 0, 0, 1, 1); AddParameter("High freq >", ParameterTypes.Int32, 75, 50, 90, 5); AddParameter("Low freq <", ParameterTypes.Int32, 25, 5, 50, 5); } //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { if (bars.Scale != HistoryScale.Monthly) { DrawHeaderText("Please switch to Monthly scale"); return; } #region Declarations howManyYearsToAverage = Parameters[0].AsInt; firstYearWithValidData = bars.DateTimes[0].Year + howManyYearsToAverage; startBar = bars.IndexOf(new DateTime(firstYearWithValidData, 12, 31), false); StartIndex = startBar; int paramPrice = Parameters[1].AsInt; //3. Only trade if the high frequency is 75 % or greater and the low frequency is 25 % or lower. paramThresholdHigh = Parameters[2].AsInt; paramThresholdLow = Parameters[3].AsInt; thresholdHigh = paramThresholdHigh / 100d; thresholdLow = paramThresholdLow / 100d; #endregion Declarations #region Series //Average annual price TimeSeries avgYearlyPrice = TimeSeriesCompressor.ToYearly(bars.AveragePriceHL); //Average monthly prices (take AveragePrice or simply Close) TimeSeries avgMonthlyPrice = paramPrice == 0 ? bars.AveragePriceHL : bars.Close; avgMonthlyPrice = TimeSeriesCompressor.ToMonthly(avgMonthlyPrice); avgMonthlyPrice = TimeSeriesSynchronizer.Synchronize(avgMonthlyPrice, bars); avgYearlyPrice = TimeSeriesSynchronizer.Synchronize(avgYearlyPrice, bars); PlotTimeSeriesLine(avgMonthlyPrice, "Avg Monthly Price", "avg", Color.DarkBlue, 2); PlotTimeSeriesLine(avgYearlyPrice, "Avg Yearly Price", "avg", Color.DarkRed, 2); #endregion #region Collect monthly average price var lstMonths = new List<MonthData>(); for (int bar = 1; bar < bars.Count; bar++) { if (bars.DateTimes[bar].Month != bars.DateTimes[bar - 1].Month) //New month { lstMonths.Add(new MonthData(bars.DateTimes[bar].Month, bars.DateTimes[bar].Year, avgMonthlyPrice[bar])); } } #endregion for (int bar = startBar; bar < bars.Count; bar++) { if (bar <= 0) continue; int yearTo = bars.DateTimes[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) }); //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/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. if (!lstFreqUp.ContainsKey(month)) lstFreqUp.Add(month, freqUp * 100); if (!lstBreakdown.ContainsKey(month)) lstBreakdown.Add(month, new Tuple<double, double>(freqUp, monthlyAdjReturn)); } } //Plot actual chart of Frequency of Positive Returns (for last N years) Bitmap bmp = null; Plot plt = new ScottPlot.Plot(600, 400); plt.Title(string.Format("A Simple Way To Trade Seasonality of {0}", bars.Symbol)); plt.YLabel("Frequency of Positive Returns"); plt.XLabel("Month"); // make the bar plot plt.PlotBar(lstFreqUp.Keys.ToArray(), lstFreqUp.Values.ToArray(), showValues: true); // customize the plot to make it look nicer plt.Grid(enable: false, lineStyle: LineStyle.Dot); // apply custom axis tick labels string[] lstTicks = new string[13]{"","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; plt.XTicks(lstTicks); bmp = plt.GetBitmap(); DrawImage(bmp, bars.Count - 50, bars.Close[bars.Count - 10], "Price"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { //code your buy conditions here //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 = (int)highFreqMonths.LastOrDefault().Key; lastLowestFrequencyMonth = (int)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 (bars.DateTimes[idx].Month == lastHighestFrequencyMonth) { Transaction t = PlaceTrade(bars, TransactionType.Short, OrderType.MarketClose, 0, lastHighestFrequencyMonth.ToString()); //Cover the short at the end of the month with the low frequency. t.Tag = (object)lastHighestFrequencyMonth; } } //5. If the low frequency comes first, buy at the end of the month with the low frequency. else { if (bars.DateTimes[idx].Month == lastLowestFrequencyMonth) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.MarketClose, 0, lastLowestFrequencyMonth.ToString()); //Sell to exit at the end of the month with the high frequency. t.Tag = (object)lastHighestFrequencyMonth; } } } } else { //code your sell conditions here int monthToExit = (int)LastPosition.Tag; if (bars.DateTimes[idx].Month == monthToExit) ClosePosition(LastPosition, OrderType.MarketClose, 0, monthToExit.ToString()); } } //declare private variables below } }
Rename
Hi,
I am trying to adapt this script for WL8, but am having some issues and I am afraid that sorting out the errors that I am getting is beyond my current C# skills.
Any chance of posting the code for WL8, please?
Thank you!
I am trying to adapt this script for WL8, but am having some issues and I am afraid that sorting out the errors that I am getting is beyond my current C# skills.
Any chance of posting the code for WL8, please?
Thank you!
Here you go ...
CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using System.Drawing; using System.Collections.Generic; using System.Linq; using ScottPlot; using System.Globalization; namespace WealthScript1 { public class MonthData { public MonthData() { } public MonthData(int month, int year, double avgPrice) { Month = month; Year = year; AvgPrice = avgPrice; } public int Month; public int Year; public double AvgPrice; } public class SC_2019_09_KaufmanSeasonality : UserStrategyBase { int paramYears, howManyYearsToAverage, firstYearWithValidData, startBar, paramThresholdHigh, paramThresholdLow; double thresholdHigh, thresholdLow; Dictionary<double, double> lstFreqUp = new Dictionary<double, double>(); Dictionary<int, Tuple<double, double>> lstBreakdown = new Dictionary<int, Tuple<double, double>>(); public SC_2019_09_KaufmanSeasonality() { AddParameter("Average, Years", ParameterType.Int32, 4, 2, 100, 1); AddParameter("Use (H+L)/2 or Close", ParameterType.Int32, 0, 0, 1, 1); AddParameter("High freq >", ParameterType.Int32, 75, 50, 90, 5); AddParameter("Low freq <", ParameterType.Int32, 25, 5, 50, 5); } //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { if (bars.Scale != HistoryScale.Monthly) { DrawHeaderText("Please switch to Monthly scale"); return; } #region Declarations howManyYearsToAverage = Parameters[0].AsInt; firstYearWithValidData = bars.DateTimes[0].Year + howManyYearsToAverage; startBar = bars.IndexOf(new DateTime(firstYearWithValidData, 12, 31), false); StartIndex = startBar; int paramPrice = Parameters[1].AsInt; //3. Only trade if the high frequency is 75 % or greater and the low frequency is 25 % or lower. paramThresholdHigh = Parameters[2].AsInt; paramThresholdLow = Parameters[3].AsInt; thresholdHigh = paramThresholdHigh / 100d; thresholdLow = paramThresholdLow / 100d; #endregion Declarations #region Series //Average annual price TimeSeries avgYearlyPrice = TimeSeriesCompressor.ToYearly(bars.AveragePriceHL); //Average monthly prices (take AveragePrice or simply Close) TimeSeries avgMonthlyPrice = paramPrice == 0 ? bars.AveragePriceHL : bars.Close; avgMonthlyPrice = TimeSeriesCompressor.ToMonthly(avgMonthlyPrice); avgMonthlyPrice = TimeSeriesSynchronizer.Synchronize(avgMonthlyPrice, bars); avgYearlyPrice = TimeSeriesSynchronizer.Synchronize(avgYearlyPrice, bars); PlotTimeSeriesLine(avgMonthlyPrice, "Avg Monthly Price", "avg", WLColor.DarkBlue, 2); PlotTimeSeriesLine(avgYearlyPrice, "Avg Yearly Price", "avg", WLColor.DarkRed, 2); #endregion #region Collect monthly average price var lstMonths = new List<MonthData>(); for (int bar = 1; bar < bars.Count; bar++) { if (bars.DateTimes[bar].Month != bars.DateTimes[bar - 1].Month) //New month { lstMonths.Add(new MonthData(bars.DateTimes[bar].Month, bars.DateTimes[bar].Year, avgMonthlyPrice[bar])); } } #endregion for (int bar = startBar; bar < bars.Count; bar++) { if (bar <= 0) continue; int yearTo = bars.DateTimes[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) }); //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/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. if (!lstFreqUp.ContainsKey(month)) lstFreqUp.Add(month, freqUp * 100); if (!lstBreakdown.ContainsKey(month)) lstBreakdown.Add(month, new Tuple<double, double>(freqUp, monthlyAdjReturn)); } } //Plot actual chart of Frequency of Positive Returns (for last N years) Bitmap bmp = null; Plot plt = new ScottPlot.Plot(600, 400); plt.Title(string.Format("A Simple Way To Trade Seasonality of {0}", bars.Symbol)); plt.YLabel("Frequency of Positive Returns"); plt.XLabel("Month"); // make the bar plot plt.PlotBar(lstFreqUp.Keys.ToArray(), lstFreqUp.Values.ToArray(), showValues: true); // customize the plot to make it look nicer plt.Grid(enable: false, lineStyle: ScottPlot.LineStyle.Dot); // apply custom axis tick labels string[] lstTicks = new string[13] { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; plt.XTicks(lstTicks); bmp = plt.GetBitmap(); DrawImage(bmp, bars.Count - 50, bars.Close[bars.Count - 10], "Price"); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { //code your buy conditions here //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 = (int)highFreqMonths.LastOrDefault().Key; lastLowestFrequencyMonth = (int)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 (bars.DateTimes[idx].Month == lastHighestFrequencyMonth) { Transaction t = PlaceTrade(bars, TransactionType.Short, OrderType.MarketClose, 0, lastHighestFrequencyMonth.ToString()); //Cover the short at the end of the month with the low frequency. t.Tag = (object)lastHighestFrequencyMonth; } } //5. If the low frequency comes first, buy at the end of the month with the low frequency. else { if (bars.DateTimes[idx].Month == lastLowestFrequencyMonth) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.MarketClose, 0, lastLowestFrequencyMonth.ToString()); //Sell to exit at the end of the month with the high frequency. t.Tag = (object)lastHighestFrequencyMonth; } } } } else { //code your sell conditions here int monthToExit = (int)LastPosition.Tag; if (bars.DateTimes[idx].Month == monthToExit) ClosePosition(LastPosition, OrderType.MarketClose, 0, monthToExit.ToString()); } } //declare private variables below } }
Thank you very much!
Your Response
Post
Edit Post
Login is required