UserModelBase represents a trading Model in Quantacula. When you work with C# Code-Based Models, you are actually coding a custom class that is derived from UserModelBase. The class provides properties and methods that let you control the logic of the trading system, including placing orders and examining the current state of the system.
You hook into the trading system logic by overriding several virtual methods in UserModelBase. These methods pass as a parameter the instance of the BarHistory object that contains the historical data to backtest. The following are the most important methods:
Draws the specified text above or below the specified bar on the chart.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //annotate bullish and bearish "outside bars" public override void Execute(BarHistory bars, int idx) { if (idx == 0) return; if (bars.High[idx] > bars.High[idx - 1] && bars.Low[idx] < bars.Low[idx - 1]) { bool isBullish = bars.Close[idx] > bars.Open[idx]; string annotation = isBullish ? "Bullish" : "bearish"; Color c = isBullish ? Color.Green : Color.Red; DrawBarAnnotation(annotation, idx, isBullish, c, 6); } } } }
Draw a dot on that chart at the specified bar number and price value. The color parameter determines the color of the dot, and the radius parameter determines its size.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel14 : UserModelBase { private BBLower lowerBollingerBand; private BBUpper upperBollingerBand; private ATR atr; private double stopLevel; double atrStop; public override void Initialize(BarHistory bars) { lowerBollingerBand = new BBLower(bars.Close, 20, 2.0); upperBollingerBand = new BBUpper(bars.Close, 20, 2.0); PlotIndicator(lowerBollingerBand, Color.Navy, PlotStyles.Bands); atr = new ATR(bars, 14); PlotIndicator(atr); } public override void Execute(BarHistory bars, int idx) { if (idx < 20) return; if (HasOpenPosition(bars, PositionType.Long) == false) { if (bars.Low[idx] <= lowerBollingerBand[idx] && bars.Close[idx] > lowerBollingerBand[idx]) { SetBackgroundColor(idx, Color.LightGreen, "Price"); PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]); atrStop = bars.Close[idx] - atr[idx] * 2; DrawDot(idx, atrStop, Color.Red, 2); PlaceTrade(bars, TransactionType.Sell, OrderType.Stop, atrStop); } } else { //uncomment this line to use a fixed stop level calculated at entry time //atrStop = bars.Close[idx] - atr[idx] * 2; DrawDot(idx, atrStop, Color.Red, 2); PlaceTrade(bars, TransactionType.Sell, OrderType.Stop, atrStop); SetBackgroundColor(idx, Color.LightPink, "Price"); PlaceTrade(bars, TransactionType.Sell, OrderType.Limit, upperBollingerBand[idx]); } } } }
Draws an ellipse on the chart using the specified parameters, color, and style.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //draw an ellipse arounf the last 100 bar range public override void Initialize(BarHistory bars) { if (bars.Count < 100) return; double highest = bars.High.GetHighest(bars.Count - 1, 100); double lowest = bars.Low.GetLowest(bars.Count - 1, 100); DrawEllipse(bars.Count - 99, highest, bars.Count - 1, lowest, Color.Navy, LineStyles.Thick); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } //declare private variables below } }
Draws the specified text on the chart, under the upper left hand corner box that displays the symbol and price values.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //display some information on the chart public override void Initialize(BarHistory bars) { double smaVal = SMA.Calculate(bars.Count - 1, bars.Close, 20); double closingVal = bars.Close[bars.Count - 1]; double diff = closingVal - smaVal; double pctDiff = (diff * 100.0) / smaVal; if (diff < 0) DrawHeaderText("Price is " + (-pctDiff).ToString("N2") + "% below SMA(20)", Color.Red); else DrawHeaderText("Price is " + pctDiff.ToString("N2") + "% above SMA(20)", Color.Green); } public override void Execute(BarHistory bars, int idx) { } } }
Draws the specified Image on the chart, at the specified location.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { cmo = new CMO(bars.Close, 20); Plot(cmo); img = (Bitmap)Bitmap.FromFile(@"C:\Images\BuyArrow.bmp"); img.MakeTransparent(Color.White); } //draw arrows on the chart where CMO oscillator turns up from oversold area public override void Execute(BarHistory bars, int idx) { if (cmo[idx] < cmo.OversoldLevel) if (cmo.TurnsUp(idx)) { DrawImage(img, idx, bars.Low[idx], "Price", 5); SetBackgroundColor(idx, c); } } //declare private variables below private CMO cmo; private Bitmap img; Color c = Color.FromArgb(32, 255, 0, 0); } }
Draws a line on the chart using the specified parameters, color, and style.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //draw extended trendlines between two most recent peaks/troughs public override void Initialize(BarHistory bars) { PeakTroughCalculator pt = new PeakTroughCalculator(bars, 5.0); if (pt.PeakTroughs.Count > 4) { int ptc = pt.PeakTroughs.Count - 1; //render peak trendline PeakTrough peak1 = pt.PeakTroughs[ptc].Type == PeakTroughTypes.Peak ? pt.PeakTroughs[ptc] : pt.PeakTroughs[ptc - 1]; PeakTrough peak2 = pt.GetPeak(peak1.DetectedAtIndex - 1); if (peak1 != null && peak2 != null) { DrawLine(peak2.PeakTroughIndex, peak2.Value, peak1.PeakTroughIndex, peak1.Value, Color.Red, LineStyles.Dotted, "Price", false, true); } //render trough trendline PeakTrough trough1 = pt.PeakTroughs[ptc].Type == PeakTroughTypes.Peak ? pt.PeakTroughs[ptc - 1] : pt.PeakTroughs[ptc]; PeakTrough trough2 = pt.GetTrough(trough1.DetectedAtIndex - 1); if (trough1 != null && trough2 != null) { DrawLine(trough2.PeakTroughIndex, trough2.Value, trough1.PeakTroughIndex, trough1.Value, Color.Green, LineStyles.Dotted, "Price", false, true); } } } public override void Execute(BarHistory bars, int idx) { } //declare private variables below } }
Draws a polygon based on the list of points in the pts parameter, using the specific color and style. The ChartPoint class represents a point on the chart. It contains 2 properties; an int Index and a double Value.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //draw a triangle around most recent 3 peak/troughs public override void Initialize(BarHistory bars) { PeakTroughCalculator pt = new PeakTroughCalculator(bars, 7.0); if (pt.PeakTroughs.Count < 3) return; PeakTrough pt1 = pt.PeakTroughs[pt.PeakTroughs.Count - 1]; PeakTrough pt2 = pt.PeakTroughs[pt.PeakTroughs.Count - 2]; PeakTrough pt3 = pt.PeakTroughs[pt.PeakTroughs.Count - 3]; List<IChartPoint> points = new List<IChartPoint>(); points.Add(pt1); points.Add(pt2); points.Add(pt3); DrawPolygon(points, Color.DarkCyan, LineStyles.Thick); } public override void Execute(BarHistory bars, int idx) { } } }
Draws a rectangle on the chart using the specified parameters, color, and style.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //draw rectangle around recent peak/troughs public override void Initialize(BarHistory bars) { PeakTroughCalculator pt = new PeakTroughCalculator(bars, 10.0); if (pt.PeakTroughs.Count == 0) return; PeakTrough pt1 = pt.PeakTroughs[pt.PeakTroughs.Count - 1]; if (pt1 != null) { PeakTrough pt2 = pt.GetPeakTrough(pt1.DetectedAtIndex - 1); if (pt2 != null) { DrawRectangle(pt2.PeakTroughIndex, pt2.Value, pt1.PeakTroughIndex, pt1.Value, Color.Blue, LineStyles.Thick); } } } public override void Execute(BarHistory bars, int idx) { } } }
Draws the specified text on the chart, at the specified location.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create moving averages public override void Initialize(BarHistory bars) { sma50 = new SMA(bars.Close, 50); sma200 = new SMA(bars.Close, 200); Plot(sma50); Plot(sma200, Color.Red); } //label where moving average crossover occur public override void Execute(BarHistory bars, int idx) { if (sma50.CrossesOver(sma200, idx)) DrawText("Cross Over", idx, sma200[idx], Color.Black, 8); if (sma50.CrossesUnder(sma200, idx)) DrawText("Cross Under", idx, sma200[idx], Color.Black, 8); } //declare private variables below SMA sma50; SMA sma200; } }
Does not actually plot, but rather calculates the extension of a line specified by two points (x1,x2 and y1,y2), and returns the value of y extened to the point along the line where the x parameter occurs.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create an indicator that projects price 5 bars into the future public override void Initialize(BarHistory bars) { TimeSeries projected = new TimeSeries(bars.DateTimes); for (int n = 5; n < bars.Count; n++) { double projectedValue = ExtendLine(n - 5, bars.Close[n - 5], n, bars.Close[n], n + 5); projected[n] = projectedValue; } Plot(projected, "Projected Price"); } public override void Execute(BarHistory bars, int idx) { } } }
Fills an ellipse on the chart using the specified parameters, color, and style.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //fill 3 ellipses around descending high/low ranges public override void Initialize(BarHistory bars) { if (bars.Count < 100) return; DrawMyEllipse(bars, 100); DrawMyEllipse(bars, 70); DrawMyEllipse(bars, 40); } private void DrawMyEllipse(BarHistory bars, int barRange) { Color c = Color.FromArgb(32, 0, 0, 128); double highest = bars.High.GetHighest(bars.Count - 1, barRange); double lowest = bars.Low.GetLowest(bars.Count - 1, barRange); FillEllipse(bars.Count - barRange + 1, highest, bars.Count - 1, lowest, c); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } //declare private variables below } }
Fills a rectangle on the chart using the specified parameters, color, and style.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //for intraday charts - draw a rectangle around the zone of yesterday's high/low range public override void Initialize(BarHistory bars) { BarHistory daily = BarHistoryCompressor.ToDaily(bars); if (daily == null || daily.Count < 2) return; for (int n = bars.Count - 1; n > 0; n--) { if (bars.DateTimes[n].Day != bars.DateTimes[n - 1].Day) { int dailyIdx = daily.Count - 1; double dailyLow = daily.Low[dailyIdx]; double dailyHigh = daily.High[dailyIdx]; Color c = Color.FromArgb(32, 0, 255, 255); FillRectangle(n, dailyHigh, bars.Count - 1, dailyLow, c); return; } } } public override void Execute(BarHistory bars, int idx) { } } }
The Plot method has been deprecated as of QStudio Q164. For cleaner and more understandable code, use the following methods instead:
There are several overloads of the Plot method, two for plotting indicators, two for plotting vanilla TimeSeries instances, and two for plotting BarHistory instances.
The Plot methods contain a plotStyle parameter that specified how to plot the data. It is a member of the PlotStyles enum.
Some methods include a paneTag parameter, a string which specifies which chart pane to plot the data. You can pass "Price" for the price pane, "Volume" for the volume pane, or a unique string to plot the data in its own pane.
The overloads the plot a TimeSeries require a name parameter. Provide a descriptive label for the data that will be displayed at the top of the chart pane where the data is plotted.
There are two overloads for plotting a secondary BarHistory instance, for example, one returned via a call to GetHistory. In the first overload you specify a paneTag. The vertical axis of the pane will increase to accommodate the BarHistory instance. In the second overload, the history is plotted in the price pane, and is overlaid atop the existing vertical axis scale. The history values are transformed behind the scenes to plot in the existing scale of the price pane without disrupting it.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //plot two moving averages, and their difference as a histogram in a new pane public override void Initialize(BarHistory bars) { EMA ema20 = new EMA(bars.Close, 20); EMA ema50 = new EMA(bars.Close, 50); TimeSeries difference = ema20 - ema50; Plot(ema20, Color.Black); Plot(ema50, Color.Red); Plot(difference, "EMA Difference", Color.Maroon, PlotStyles.Histogram, "diff"); } //example of seasonal entries/exits public override void Execute(BarHistory bars, int idx) { } //declare private variables below } }
Plots a BarHistory instance on the chart. BarHistory instances are typically obtained as a result of the GetHistory method. Provide the BarHistory to be plotted in the bh parameter.
The paneTag parameter should specify what pane to plot the data. You can specify Price for the price pane, Volume for the volume pane, or some other unique string for a custom pane.
The color parameter is optional. If not specified, Quantacula will plot the data in black.
The fitInAxis parameter is also optional, with a default of false. If set to true, it will cause the BarHistory data to fit within the existing scale of the pane, so as not to distort it. If false, the pane's scale will adjust to accommodate the BarHistory data.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using System.Drawing; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //Plot QQQ in its own pane below price BarHistory qqq = GetHistory(bars, "QQQ"); PlotBarHistory(qqq, "QQQ"); //Plot SPY in the same pane, using the dual scale BarHistory spy = GetHistory(bars, "SPY"); PlotBarHistory(spy, "QQQ", Color.Red, true); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Plots an indicator on the chart. The ib parameter is an indicator is an instance of the IndicatorBase class, the class which all indicators in Quantacula are derived from. Any time you create an indicator in code, you're returned an instance of the indicator's specialized class, which is a descendant of IndicatorBase.
The color parameter is optional. If not specified Quantacula will use the indicator's default color.
The plotStyle parameter is optional, and is a member of the PlotStyles enum. If not specified, Quantacula uses the indicator's default plot style.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using Quantacula.Indicators; using System.Drawing; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //plot a MACD and signal line IndicatorBase macd = new MACD(bars.Close); PlotIndicator(macd, Color.Firebrick, PlotStyles.ThickHistogram); IndicatorBase signalLine = new SMA(macd, 9); PlotIndicator(signalLine); //plot an SMA, semi-transparent IndicatorBase sma = new SMA(bars.AveragePriceOHLC, 9); PlotIndicator(sma, Color.FromArgb(64, Color.CadetBlue), PlotStyles.ThickLine); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Plots a TimeSeries on the chart. The ts parameter is a TimeSeries instance. PlotTimeSeries is useful when plotting the results of mathematical operations on indicators and other TimeSeries. These operations always return instances of the TimeSeries class.
The name parameter should be a descriptive name of the data being plotted. This appears in the pane label.
The paneTag specifies which chart pane to plot the data in. You can specify Price for the price pane, Volume for the volume pane, or some other string value for a custom pane.
The color parameter is optional. If not provided, Quantacula will use a default color.
The plotStyle parameter is also optional, and is a member of the PlotStyles enum. If not specified, Quantacula will use PlotStyles.Line.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using Quantacula.Indicators; using System.Drawing; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //plot a ratio of QLD and QID momentum BarHistory qld = GetHistory(bars, "QLD"); BarHistory qid = GetHistory(bars, "QID"); TimeSeries ratio = Momentum.Series(qld.Close, 20) / Momentum.Series(qid.Close, 20); PlotTimeSeries(ratio, "QLD/QID Momentum Ratio", "Ratio", Color.Maroon, PlotStyles.ThickHistogram); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } } }
Sets the background of the specified chart pane, at the specified index, to the color you specify.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { roc = new ROC(bars.Close, 10); Plot(roc, Color.Black, PlotStyles.ThickHistogram); } //set color of ROC indicator based on its value public override void Execute(BarHistory bars, int idx) { //transform RSI value into 0-100 range after correcting tails double value = roc[idx]; if (value < -20) value = -20; if (value > 20) value = 20; value += 20; value *= 2.5; Color c = ColorExtensions.InterpolateColors(Color.Red, Color.LimeGreen, (int)value); SetBackgroundColor(idx, c, "ROC"); } //declare private variables below ROC roc; } }
Sets the chart bar color at the specified index to the color you specify.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { rsi = new RSI(bars.Close, 20); Plot(rsi); } //set bar color depending on position of RSI public override void Execute(BarHistory bars, int idx) { //transform RSI value into 0-100 range after correcting tails double value = (int)rsi[idx]; if (value < 30) value = 30; if (value > 70) value = 70; value -= 30; value *= 2.5; Color c = ColorExtensions.InterpolateColors(Color.Red, Color.LimeGreen, (int)value); SetBarColor(idx, c); } //declare private variables below RSI rsi; } }
Lets you control how text is rendered to the chart by any of the text drawing methods. You can specify a background color (backgroundColor parameter), and/or a border color (borderColor parameter) and width (borderWidth parameter) to use when text is rendered. If you don't want a background color and/or border, specify Color.Transparent in these parameters. The settings affect all subsequent calls to text drawing methods.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { bbLower = new BBLower(bars.Close, 20, 2.00); bbUpper = new BBUpper(bars.Close, 20, 2.00); Plot(bbLower); Plot(bbUpper); SetTextDrawingOptions(Color.FromArgb(32, 255, 0, 0), Color.DarkRed, 2); } //annotate bars when prices cross lower bollinger band public override void Execute(BarHistory bars, int idx) { if (bars.Close.CrossesUnder(bbLower, idx)) DrawBarAnnotation("Watch out!", idx, false, Color.Red, 6, true); } //declare private variables below BBLower bbLower; BBUpper bbUpper; } }
Returns a List of BarHistory objects that contain the historical data being backtested.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class ExampleModel11 : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //how correlated is this symbol to all of the symbols in the universe? TimeSeries tsSumCorr = new TimeSeries(bars.DateTimes, 0.0); int n = 0; foreach (BarHistory bh in BacktestData) { Corr c = new Corr(bars, bh, PriceComponents.Close, 20); if (c != null && c.Count > 0) { tsSumCorr += c; n++; } } if (n > 0) tsSumCorr /= n; PlotTimeSeries(tsSumCorr, "Avg Corr", "Corr", Color.DarkOrange); } //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 } else { //code your sell conditions here } } //declare private variables below } }
Returns the current cash level available in the simulation. The cash level decreases as your model opens new positions, and increases as it exits positions.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { SetTextDrawingOptions(Color.Wheat, Color.Black, 2); sma200 = new SMA(bars.Close, 200); Plot(sma200); } //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)) { if (bars.Close.CrossesOver(sma200, idx)) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market); string s = "Portfolio cash at entry bar = $" + CurrentCash.ToString("N2"); DrawText(s, idx, bars.Low[idx], Color.Navy, 8, "Price", true); } } else { if (bars.Close.CrossesUnder(sma200, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //declare private variables below SMA sma200; } }
Returns the current equity level in the simulation. This is initially determined by the Starting Equity that you established in Model Settings. As your model executes bar by bar, and enters and exits positions, the equity level will rise and fall.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { sma200 = new SMA(bars.Close, 200); equity = new TimeSeries(bars.DateTimes); Plot(sma200); Plot(equity, "Equity", Color.DarkGreen, PlotStyles.ThickHistogram, "Equity"); } //populate equity as model executes bar by bar public override void Execute(BarHistory bars, int idx) { equity[idx] = CurrentEquity; if (HasOpenPosition(bars, PositionType.Long) && bars.Close.CrossesUnder(sma200, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); else if (bars.Close.CrossesOver(sma200, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } //declare private variables below SMA sma200; TimeSeries equity; } }
This method is intended to be called in either the PreExecute or PostExecute methods. These two methods provide you a list of BarHistory objects that are being processed during the current Execute cycle. GetCurrentIndex takes a BarHistory parameter, which should be one of the instances in the list mentioned above. It returns the int index into that BarHistory that represents the bar currently being processed by Execute.
Lets you access data for another symbol from within your model. Returns a BarHistory object for the specified symbol. The synchWith parameter specifies a BarHistory object to synch the historical data with. Generally, this will be the bars parameter from the Initialize or Execute method.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //plot some market benchmarks along with the symbol being charted public override void Initialize(BarHistory bars) { BarHistory spy = GetHistory(bars, "SPY"); BarHistory qqq = GetHistory(bars, "QQQ"); Plot(spy, Color.Silver); Plot(qqq, Color.LightCoral); } public override void Execute(BarHistory bars, int idx) { } } }
Returns an instance of the BarHistory class the represents historical data for the symbol and scale you specify. If Quantacula could not locate historical data for the symbol/scale, the method returns null. The resulting data is not synched to the BarHistory currently being processed on the chart. Therefore, you should not plot indicators or TimeSeries derived from this data, unless you first synchronize these series using the TimeSeriesSynchronizer helper class.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using Quantacula.Indicators; using System.Drawing; namespace Quantacula { public class MyModel : UserModelBase { //create indictors based on an external data series //synchronize them AFTER they are created to preserve consistency despite possible irregularities //between the primary and external symbol histories public override void Initialize(BarHistory bars) { //create the unsynchronized indicator (RSI of SPY) BarHistory spy = GetHistoryUnsynched("SPY", HistoryScale.Daily); TimeSeries spyRSI = new RSI(spy.Close, 14); //sychrnize it with the data being processed spyRSI = TimeSeriesSynhronizer.Synchronize(spyRSI, bars); //Plot it PlotTimeSeries(spyRSI, "RSI(SPY,14)", "RSI", Color.Red); //Plot RSI of symbol being processed RSI rsi = new RSI(bars.Close, 14); PlotIndicator(rsi); } public override void Execute(BarHistory bars, int idx) { } } }
Writes a line of output to the Debug Log tab. Quantacula Studio shows the Debug Log tab automatically after your Model runs, if you've called this method.
The backtester calls this method for the first symbol (sorted alphabetically) in the universe prior to the backtest beginning its processing.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; namespace Quantacula { public class MyModel : UserModelBase { //intialize the sum variable to zero, this only gets called once per backtest public override void BacktestBegin() { sumClose = 0; sumObs = 0; } //we add the closing price to the sum and increment the number of observations public override void Execute(BarHistory bars, int idx) { sumObs++; sumClose += bars.Close[idx]; } //display average close of entire universe on chart public override void Cleanup(BarHistory bars) { double avg = sumClose / sumObs; DrawHeaderText(avg.ToString("N2")); } //declare private variables below private static double sumClose; private static int sumObs; } }
The backtester calls this method for the last symbol (sorted alphabetically) in the universe after the backtest processing is completed for all symbols.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using System.IO; namespace Quantacula { public class MyModel : UserModelBase { //intialize the sum variable to zero, this only gets called once per backtest public override void BacktestBegin() { sumClose = 0; sumObs = 0; } //we add the closing price to the sum and increment the number of observations public override void Execute(BarHistory bars, int idx) { sumObs++; sumClose += bars.Close[idx]; } //save the information that we gathered to a file public override void BacktestComplete() { double avg = sumClose / sumObs; string output = "Average Closing Price of Universe: " + avg.ToString("N2"); File.WriteAllText(@"C:\My Folder\My File.txt", output); } //declare private variables below private static double sumClose; private static int sumObs; } }
The backtester calls the model's Cleanup method after all processing is complete for a symbol. Override the Cleanup method to dispose of any necessary objects or data that are not handled by normal .NET garbage collection.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; namespace Quantacula { public class MyModel : UserModelBase { //this example illustrates the Execute method by adding the closing price to a //summation variable each time it is called, then displaying the average close public override void Execute(BarHistory bars, int idx) { sumClose += bars.Close[idx]; } //display average close on chart public override void Cleanup(BarHistory bars) { double avg = sumClose / bars.Count; DrawHeaderText(avg.ToString("N2")); } //declare private variables below private double sumClose = 0; } }
The backtester calls the model's Execute method for each bar of data being processed. The BarHistory being processed is passed in the bars parameter. The numeric index being processed is passed in the idx parameter. Override the Execute method to implement the model's primary logic.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; namespace Quantacula { public class MyModel : UserModelBase { //this example illustrates the Execute method by adding the closing price to a //summation variable each time it is called, then displaying the average close public override void Execute(BarHistory bars, int idx) { sumClose += bars.Close[idx]; } //display average close on chart public override void Cleanup(BarHistory bars) { double avg = sumClose / bars.Count; DrawHeaderText(avg.ToString("N2")); } //declare private variables below private double sumClose = 0; } }
The backtester calls the model's Initialize method prior to beginning the main trading logic loop. Initialize is called once for each symbol being backtested, and the symbol's BarHistory is passed in the bars parameter. Override this method to create any instances of indicators and other objects your model will need.
Example Codeusing Quantacula.Backtest; using Quantacula.Core; using Quantacula.Indicators; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { myRSI = new RSI(bars.Close, 14); Plot(myRSI); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } //declare private variables below RSI myRSI; } }
Executes immediately after the main backtesting loop that processed each symbol via the Execute method. PostExecute gives you a chance to operate on the list of symbols that have been processed during this backtesting loop iteration. You are provided the date/time being processed via the dt parameter, and a list of BarHistory instances containing the data being processed this iteration in the participants parameter. Use the GetCurrentIndex method to determine the index to use for a particular BarHistory instance.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { } //report on symbols that were processed public override void PostExecute(DateTime dt, List<BarHistory> participants) { string s = dt.ToShortDateString() + ": Symbols=" + participants.Count + " "; foreach (BarHistory bh in participants) s += bh.Symbol + ","; WriteToDebugLog(s); } } }
Executes immediately prior to the main backtesting loop that processed each symbol via the Execute method. PreExecute gives you a chance to operate on the list of symbols that are being processed during this backtesting loop iteration. You are provided the date/time being processed via the dt parameter, and a list of BarHistory instances containing the data being processed this iteration in the participants parameter. Use the GetCurrentIndex method to determine the index to use for a particular BarHistory instance.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //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); 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; } }
This property controls the starting point of your backtest, specified as a bar number index. By default, StartIndex return 0, which means that your model's Execute method will first get called with a bar index of zero. If you assign a higher value to StartIndex, the Execute method will first get called at the index you specified.
This can be useful to avoid unnecessary calls to Execute. For example, if your model uses a 200 bar moving average, you can set StartIndex to 199 to prevent the Execute method from being called 199 times before the moving average is available.
The backtester also uses StartIndex to determine the entry bar for the benchmark buy & hold comparison. In the example above, the buy & hold comparison would have also entered its position at bar 199, making the benchmark a more fair comparison.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //first 200 bars not needed public override void Initialize(BarHistory bars) { sma200 = new SMA(bars.Close, 200); StartIndex = 199; } //a price/SMA crossover public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(sma200, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (bars.Close.CrossesUnder(sma200, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //declare private variables below private SMA sma200; } }
Call this from within the model's constructor to add a parameter to the model. The method returns an instance of the Parameter class that represents the parameter you added.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //constructor public MyModel() : base() { AddParameter("Short Period", ParameterTypes.Int32, 50, 30, 70, 10); AddParameter("Long Period", ParameterTypes.Int32, 200, 150, 300, 50); } //create indicators, note obtaining period from parameters public override void Initialize(BarHistory bars) { smaShort = new SMA(bars.Close, Parameters[0].AsInt); smaLong = new SMA(bars.Close, Parameters[1].AsInt); Plot(smaShort); Plot(smaLong, Color.Red); } //a parameter-driven SMA crossover model public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (smaShort.CrossesOver(smaLong, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (smaShort.CrossesUnder(smaLong, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //declare private variables below SMA smaShort; SMA smaLong; } }
Returns a List<Parameter> containing the model's parameters, all instances of the Parameter class. Model parameters should be added in the constructor, by calling the AddParameter method.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //constructor public MyModel() : base() { AddParameter("Short Period", ParameterTypes.Int32, 50, 30, 70, 10); AddParameter("Long Period", ParameterTypes.Int32, 200, 150, 300, 50); } //create indicators, note obtaining period from parameters public override void Initialize(BarHistory bars) { smaShort = new SMA(bars.Close, Parameters[0].AsInt); smaLong = new SMA(bars.Close, Parameters[1].AsInt); Plot(smaShort); Plot(smaLong, Color.Red); } //a parameter-driven SMA crossover model public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (smaShort.CrossesOver(smaLong, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (smaShort.CrossesUnder(smaLong, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //declare private variables below SMA smaShort; SMA smaLong; } }
Looks for an open Position with the specified numeric positionTag. Positions that were created as a result of passing a value in the PlaceTrade method's positionTag parameter can be found in this way.
The second version of FindOpenPosition allows you to look for positions by type, where the pt parameter can contain PositionType.Long or PositionType.Short.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { rsi = new RSI(bars.Close, 20); } //example of seasonal entries/exits public override void Execute(BarHistory bars, int idx) { //open positions in February and March Position febPosition = FindOpenPosition(2); Position marchPosition = FindOpenPosition(3); if (rsi[idx] < 30) { if (bars.DateTimes[idx].Month == 2) { if (febPosition == null) PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 2); } else if (bars.DateTimes[idx].Month == 3) { if (marchPosition == null) PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 3); } } //sell February position in December, sell March position in November if (febPosition != null) if (bars.DateTimes[idx].Month == 12) ClosePosition(febPosition, OrderType.Market); if (marchPosition != null) if (bars.DateTimes[idx].Month == 11) ClosePosition(marchPosition, OrderType.Market); } //declare private variables below RSI rsi; } }
Returns the open position with the specified positionTag among the entire set of currently open postions, regardless of symbol. This method was created to support a model that stops normal trading during a specific technical event, and instead swaps the entire portfolio into a bond ETF. When the technical condition is over, and normal trading can begin again, the model calls FindOpenPositionAllSymbols to locate the position in this bond ETF that it had opened previously, and closes it.
Returns the list of positions (instances of the Position class) for the current symbol being processed. Use the includeNSF parameter to determine if NSF Positions should be included.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { downDays = new ConsecDown(bars.Close, 4); upDays = new ConsecUp(bars.Close, 4); } //buy when 9 consecutive days where close is < close of 4 days ago, sell reverse public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (downDays[idx] == 9) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (upDays[idx] == 9) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //report how many trades were filled and not filled public override void Cleanup(BarHistory bars) { int filled = GetPositions().Count; int all = GetPositions(true).Count; int notFilled = all - filled; DrawHeaderText("Filled Positions in " + bars.Symbol + " = " + filled); DrawHeaderText("Unfilled Positions in " + bars.Symbol + "= " + notFilled); } //declare private variables below private ConsecDown downDays; private ConsecUp upDays; } }
Returns the list of positions (instances of the Position class) for all symbols in the backtest. Use the includeNSF parameter to determine if NSF Positions should be included.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { downDays = new ConsecDown(bars.Close, 4); upDays = new ConsecUp(bars.Close, 4); } //buy when 9 consecutive days where close is < close of 4 days ago, sell reverse public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (downDays[idx] == 9) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (upDays[idx] == 9) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //report how many trades were filled and not filled public override void Cleanup(BarHistory bars) { int filled = GetPositionsAllSymbols().Count; int all = GetPositionsAllSymbols(true).Count; int notFilled = all - filled; DrawHeaderText("Filled Positions = " + filled); DrawHeaderText("Unfilled Positions in = " + notFilled); } //declare private variables below private ConsecDown downDays; private ConsecUp upDays; } }
Lets you determine if there is currently an open Position of the current type (PositionType.Long or PositionType.Short). For the bars parameter, pass the value of the bars parameter that you received in the Execute method.
Return the most recently created open Position object, or null if there are no open Positions currently.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { cmo = new CMO(bars.Close, 14); PlotIndicator(cmo); } //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 (LastPosition == null) { if (cmo.CrossesOver(cmo.OversoldLevel, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } else { if (cmo.CrossesUnder(cmo.OverboughtLevel, idx)) ClosePosition(LastPosition, OrderType.Market); } } //declare private variables below private CMO cmo; } }
Returns a list of the open positions (instances of the Position class) for the BarHistory currently being processed. This includes positions that are marked NSF.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { rsi = new RSI(bars.Close, 20); Plot(rsi); } //keep buying positions when the RSI is oversold public override void Execute(BarHistory bars, int idx) { //see if we need to sell positions if (rsi.CrossesOver(50.0, idx)) { foreach(Position pos in OpenPositions) ClosePosition(pos, OrderType.Market); } //time to buy? if (rsi[idx] < 30) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } //declare private variables below private RSI rsi; } }
Returns a list of open positions (instances of the Position class) for all symbols being backtested. You generally won't need to access other symbol positions during normal model processing, but this can be handy for certain summary processing you might perform during the BacktestComplete method.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators public override void Initialize(BarHistory bars) { roc = new ROC(bars.Close, 20); } //fade the momentum public override void Execute(BarHistory bars, int idx) { //close all positions when momentum reaches zero if (roc[idx] >= 2) foreach(Position pos in OpenPositions) ClosePosition(pos, OrderType.Market); //scale in when momentum descreases if (roc[idx] < -2) { //no more than 20 total open positions in portfolio if (OpenPositionsAllSymbols.Count < 20) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } } //declare private variables below ROC roc; } }
Contains the current value of the trailing stop for the position. Trailing stops are managed by the UserModelBase CloseAtTrailingStop method.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //create a TimeSeries to plot trailing stop stops = new TimeSeries(bars.DateTimes); PlotTimeSeries(stops, "Trailing Stop", "Price", Color.Navy, PlotStyles.Dots); } //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)) { CloseAtTrailingStop(LastPosition, TrailingStopTypes.PercentHL, 10); stops[idx] = LastPosition.TrailingStopPrice; } else if (idx == bars.Count - 50) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } } //declare private variables below private TimeSeries stops; } }
Use this method to close the specified Position object (pos) at a trailing stop. Specify the type of trailing stop in the tst parameter. You can specify percentage or point based trailing stops, and specify whether the trailing stop is based off closing prices or off high/lows. The amount parameter specifies the size of the stop, either in percentage or point value.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { //create a TimeSeries to plot trailing stop stops = new TimeSeries(bars.DateTimes); PlotTimeSeries(stops, "Trailing Stop", "Price", Color.Navy, PlotStyles.Dots); } //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)) { CloseAtTrailingStop(LastPosition, TrailingStopTypes.PercentHL, 10); stops[idx] = LastPosition.TrailingStopPrice; } else if (idx == bars.Count - 50) { PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } } //declare private variables below private TimeSeries stops; } }
Use this method to explicitly close a Position object (pos). Specify the orderType (OrderType.Market, OrderType.Limit or OrderType.Stop) to use to close the position. If you use a limit or stop order, specify the order price in the price parameter.
Example Codeusing Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { rsi = new RSI(bars.Close, 14); Plot(rsi); } //buy multiple positions when RSI oversold public override void Execute(BarHistory bars, int idx) { if (rsi[idx] < 30) PlaceTrade(bars, TransactionType.Buy, OrderType.Market); if (rsi[idx] > 60) { //sell open positions one by one, sell the least profitable position first Position pos = null; double lowestProfit = Double.MaxValue; foreach (Position openPos in OpenPositions) if (openPos.ProfitAsOf(idx) < lowestProfit) { lowestProfit = openPos.ProfitAsOf(idx); pos = openPos; } if (pos != null) ClosePosition(pos, OrderType.Market); } } //declare private variables below private RSI rsi; } }
Places a simulated order, and returns an instance of the Transaction class the represents it. The Quantacula backtester will determine the number of shares based on the position size settings you established in the Model Settings. The backtester will attempt to fill the trade at the start of the following bar, and the simulation must have sufficient simulated capital to do so. The method returns an instance of the Transaction class that represents the transaction.
For the bars parameter, pass the same bars parameter value that you received in the call to the Execute method.
For the transType parameter, specify TransactionType.Buy, TransactionType.Sell, TransactionType.Short, or TransactionType.Cover.
For the orderType parameter, specify OrderType.Market, OrderType.Limit or OrderType.Stop.
For limit and stop orders, supply an order price in the price parameter.
The positionTag parameter allows you to optionally maintain groups of positions, using integer codes that you specify. You can locate an open position with a matching positionTag by calling the FindOpenPosition method.
using Quantacula.Backtest; using System; using Quantacula.Core; using Quantacula.Indicators; using Quantacula.ChartWPF; using System.Drawing; using System.Collections.Generic; namespace Quantacula { public class MyModel : UserModelBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { hh = new Highest(bars.High, 33); ll = new Lowest(bars.Low, 33); Plot(hh, Color.Green); Plot(ll, Color.Red); } //a basic stop and reverse system public override void Execute(BarHistory bars, int idx) { PlaceTrade(bars, TransactionType.Cover, OrderType.Stop, hh[idx]); PlaceTrade(bars, TransactionType.Sell, OrderType.Stop, ll[idx]); if (!HasOpenPosition(bars, PositionType.Long)) PlaceTrade(bars, TransactionType.Buy, OrderType.Stop, hh[idx]); if (!HasOpenPosition(bars, PositionType.Short)) PlaceTrade(bars, TransactionType.Short, OrderType.Stop, ll[idx]); } //declare private variables below IndicatorBase hh; IndicatorBase ll; } }