TimeSeriesBase is an abstract class that contains the plumbing for working with a time series keyed off a list of DateTimes. It exposes this list through the DateTimes property, which is a List<DateTime>
. Two important concrete classes derive from TimeSeriesBase:
- TimeSeries - which manages a series of double values
- BarHistory - which represents historical open, high, low, close and volume data
A generic cache Dictionary that can be used to store objects during a backtest run. Certain indicators such as ADX, ADXR, DIPlus and DIMinus use the Cache to store the collection of indicators that are all calculated together as a group.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //the list of symbols that we should buy each bar private static List<BarHistory> buys = new List<BarHistory>(); //create the weight indicator and stash it into the BarHistory object for reference in PreExecute public override void Initialize(BarHistory bars) { rsi = new RSI(bars.Close, 14); //store the RSI indicator in the cache, so it can be accessed when PreExecute is called bars.Cache["RSI"] = rsi; } //this is called prior to the Execute loop, determine which symbols have the lowest RSI public override void PreExecute(DateTime dt, List<BarHistory> participants) { //store the symbols' RSI value in their BarHistory instances foreach (BarHistory bh in participants) { RSI rsi = (RSI)bh.Cache["RSI"]; int idx = GetCurrentIndex(bh); //this returns the index of the BarHistory for the bar currently being processed double rsiVal = rsi[idx]; bh.UserData = rsiVal; //save the current RSI value along with the BarHistory instance } //sort the participants by RSI value (lowest to highest) participants.Sort((a, b) => a.UserDataAsDouble.CompareTo(b.UserDataAsDouble)); //keep the top 3 symbols buys.Clear(); for (int n = 0; n < 3; n++) { if (n >= participants.Count) break; buys.Add(participants[n]); } } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { bool inBuyList = buys.Contains(bars); if (!HasOpenPosition(bars, PositionType.Long)) { //buy logic - buy if it's in the buys list if (inBuyList) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { //sell logic, sell if it's not in the buys list if (!inBuyList) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //declare private variables below private RSI rsi; } }
Returns the number of items contained in the time series. The DateTimes property list contains this many DateTimes. For TimeSeries, the Values property contains this many values. And for BarHistory, the Open, High, Low, Close and Volume properties (all instances of the TimeSeries class) contain this many values.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //do some analysis of 10, 20, 50, 100 bars ago public override void Initialize(BarHistory bars) { SetTextDrawingOptions(WLColor.Beige, WLColor.Black, 1); ReportGainAfterNBars(bars, 10); ReportGainAfterNBars(bars, 20); ReportGainAfterNBars(bars, 50); ReportGainAfterNBars(bars, 100); } private void ReportGainAfterNBars(BarHistory bars, int numBars) { int idx = bars.Count - numBars; if (idx >= 0) { double gain = bars.Close[bars.Count - 1] - bars.Close[idx]; gain = gain * 100.0 / bars.Close[idx]; DrawBarAnnotation(numBars + " bar gain: " + gain.ToString("N2") + "%", idx, false, WLColor.Black, 7); DrawLine(idx, bars.Close[idx], bars.Count - 1, bars.Close[bars.Count - 1], WLColor.Gray, 2, LineStyle.Dotted); } } public override void Execute(BarHistory bars, int idx) { } } }
A list of DateTime objects that represents the date/time of each item in the time series. The derived classes maintain other lists that are synchronized with DateTimes. TimeSeries maintains a list of floating point double Values. BarHistory has TimeSeries properties for Open, High, Low, Close, and Volume. All of these other lists will have exactly the same number of elements as the DateTImes list, allowing you to use the same index to access their elements interchangably.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //annotate the 100 bar high and low public override void Initialize(BarHistory bars) { double lowVal = Double.MaxValue; double highVal = Double.MinValue; int lowIdx = -1; int highIdx = -1; for (int n = bars.Count - 1; n > bars.Count - 100; n--) { if (n >= 0) { if (bars.Low[n] < lowVal) { lowVal = bars.Low[n]; lowIdx = n; } if (bars.High[n] > highVal) { highVal = bars.High[n]; highIdx = n; } } } if (lowIdx >= 0) { string txt = "100 bar low occurred on " + bars.DateTimes[lowIdx].ToLongDateString(); DrawBarAnnotation(txt, lowIdx, false, WLColor.Red, 8); } if (highIdx >= 0) { string txt = "100 bar high occurred on " + bars.DateTimes[highIdx].ToLongDateString(); DrawBarAnnotation(txt, highIdx, true, WLColor.Green, 8); } } public override void Execute(BarHistory bars, int idx) { } } }
Returns the last DateTime in the DateTimes property list. If the list is empty, returns DateTime.MaxValue.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { SetTextDrawingOptions(WLColor.AliceBlue, WLColor.Black, 1); string s = "Chart end date is " + bars.EndDate.ToLongDateString(); DrawHeaderText(s); } public override void Execute(BarHistory bars, int idx) { } } }
Returns the index into the DateTimes property list that contains the DateTime specified in the dt parameter. If exactMatchOnly is false, and an exact match does not exist, the method returns the index following the closest matching DateTime, unless the requested DateTime falls completely outside the range of the list, in which case it returns -1. If exactMatchOnly is true and no exact match exists, the method returns -1.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //whoever figures out the significance of this date will receive a one month free Premium membership credit public override void Initialize(BarHistory bars) { WLColor green = WLColor.FromArgb(128, 0, 255, 0); if (bars.Count == 0) return; for (int yr = bars.StartDate.Year; yr <= bars.EndDate.Year; yr++) { DateTime dt = new DateTime(yr, 8, 25); int idx = bars.IndexOf(dt); if (idx >= 0) SetBackgroundColor(bars, idx, green); } } public override void Execute(BarHistory bars, int idx) { } } }
Returns whether the historical data appears up to date, as of the DateTime specified in the endDate parameter. The method accounts for weekends, market holidays, and market open and closing times, when called from a BarHistory object.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //report on whether data needs updating public override void Initialize(BarHistory bars) { string txt; WLColor c; if (bars.IsUpToDate(DateTime.Now)) { txt = "Data appears to be up to date"; c = WLColor.Green; } else { txt = "Data appears to need updating!"; c = WLColor.Red; } DrawHeaderText(txt, c); } public override void Execute(BarHistory bars, int idx) { } } }
Returns the first DateTime in the DateTimes property list. If the list is empty, returns DateTime.MinValue.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { SetTextDrawingOptions(WLColor.AliceBlue, WLColor.Black, 1); string s = "Chart start date is " + bars.StartDate.ToLongDateString(); DrawHeaderText(s); } public override void Execute(BarHistory bars, int idx) { } } }
Returns a TimeSpan that encompasses the range of the DateTimes in the DateTimes property list. If there are fewer than two DateTimes in the list, returns a zero TimeSpan.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using WealthLab.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace WealthLab { public class MyStrategy : UserStrategyBase { //display total amount of time covered in chart public override void Initialize(BarHistory bars) { string txt = "Chart data comprised of " + bars.TimeSpan.TotalHours.ToString("N0") + " total hours"; DrawHeaderText(txt); } public override void Execute(BarHistory bars, int idx) { } } }
Allows you to store ad-hoc data in a property of a BarHistory or TimeSeries instance. You can store object instances, or primitive values like ints or doubles.
Allows you to access a value you stored in a BarHistory or TimeSeries UserData property as a double.
Allows you to access a value you stored in a BarHistory or TimeSeries UserData property as an int.