Wealth Script
Wealth Script
Wealth Script
eReview #483859.4.0
Special thanks to: Wealth-Lab's great on-line community whose comments have helped make this manual more useful for veteran and new users alike. EC Software, whose product HELP & MANUAL printed this document.
Table of Contents
Foreword 0
Part I Introduction Part II How to Run Example Code Part III The Bars Object
1 3 4
1 OHLC/V Series ................................................................................................................................... 6 2 Scale, Interval, and Date ................................................................................................................................... 8 3 Symbol Info ................................................................................................................................... 10
Part IV DataSeries
11
1 Series Operators ................................................................................................................................... 12 2 Accessing a ................................................................................................................................... 14 Single Value from a DataSeries 3 Filling a Custom DataSeries ................................................................................................................................... 15 4 Accessing Secondary Symbols ................................................................................................................................... 17
Secondary Series Synchronization .......................................................................................................................................................... 18
21
1 Chart Panes ................................................................................................................................... 22 2 Charting Multiple Symbols ................................................................................................................................... 23 3 Plotting DataSeries ................................................................................................................................... 25
Controlling the y-scale .......................................................................................................................................................... 26
4 Writing Text ................................................................................................................................... 27 5 Drawing Objects ................................................................................................................................... 29 6 Plotting Fundamental Items ................................................................................................................................... 31
Part VI Indicators
32
1 Series Method ................................................................................................................................... 34 2 Static Value ................................................................................................................................... 35 Method 3 Custom Indicators ................................................................................................................................... 36 4 Indicators of................................................................................................................................... 40 Secondary Symbols 5 Stability of Indicators ................................................................................................................................... 41
43
Contents 4 Peeking
II
................................................................................................................................... 53
55
5 Strategy Parameters ................................................................................................................................... 58 6 Multi-Position Strategies ................................................................................................................................... 61 7 Multi-Symbol Strategies ................................................................................................................................... 63 8 Alerts ................................................................................................................................... 64
67
1 Corporate Fundamental Data ................................................................................................................................... 69 2 Economic Indicator Data ................................................................................................................................... 71 3 Market Sentiment Data ................................................................................................................................... 72 4 GICS Data ................................................................................................................................... 73
75
83
85 85 86 86 87 87 87 88 89
91
3 DateRules
................................................................................................................................... 98
4 DataSeriesOp ................................................................................................................................... 99 5 FundamentalsDateRules ................................................................................................................................... 100 6 FundamentalsRatio ................................................................................................................................... 101 7 OptionExpiryDate ................................................................................................................................... 106 8 NewHighLow ................................................................................................................................... 107
2011 FMR LLC All rights reserved.
II
III
110
1 Creating a Screener ................................................................................................................................... 111 2 Sizing with ................................................................................................................................... 113 100% of Equity 3 Symbol Rotation ................................................................................................................................... 114 4 Most Probable AtStop ................................................................................................................................... 115 5 Access ad-hoc Data from the Internet ................................................................................................................................... 116 6 Utility Scripts ................................................................................................................................... 117
Data Checking .......................................................................................................................................................... DataSet Symbol Details .......................................................................................................................................................... 117 117
118 119
Introduction
Introduction
Welcome to the WealthScript Programming Guide
The purpose of the WealthScript Programming Guide is to provide you with the basic (and some not-so-basic) concepts to express your trading strategies using Wealth-Lab Pro's WealthScript language. WealthScript is a class library of charting and trading functions in the {}WealthLab .NET namespace. You'll be amazed with what you can accomplish by coding trading systems with WealthScript! While this guide assumes that you're familiar with .NET programming, even if you're not, the explanation-by-example method should be easy to follow. We encourage you to run these ready-made solutions for yourself by following the instructions: How to Run Example Code 3 . In many cases, you'll be able to find precisely the solution that you're searching for. Though many of the most-essential WealthScript functions are used in this guide to demonstrate programming and trading system development concepts, it is not within the scope of the WealthScript Programming Guide to highlight every single WealthScript function. A complete list of functions available for use in Trading Strategies with syntax, descriptions, and examples can be found in the WealthScript QuickRef (Tools menu, or F11). Functions that exist in the {}WealthLab namespace but that are not documented in the QuickRef have no guarantee or warranty for use in Strategy code.
Programming in .NET
Wealth-Lab Pro Version 6 is a technical analysis and trading platform in which you can express and test trading strategies in any .NET language. That's right. In the .NET Framework, all .NET languages compile to Intermediate Language (IL), so you can choose from more than 20 languages to work with. Wealth-Lab's Strategy Editor, however, comes with a C# compiler, and consequently the Strategy Builder generates code with C# syntax, so naturally we provide coding examples for C#. Our preference, however, does not preclude the possibility of testing and employing Strategies compiled in other .NET languages. Deciding on a .NET language? We recommend C# for newbies that want to take a course in programming. C# is a truly modern, developed-for-.NET language that borrows some of the best features from other languages. If you've programmed before, the learning curve for C# is a snap.
symbol rotation strategies, and the like. Once you come across a strategy that the Wizard isn't able to fully program, you'll need to dig in to the world of programming using WealthScript.
PrintDebug("The chart has " + Bars.Count + " bars."); PrintDebug("The index number of the first bar is 0, and the number of the last bar is " + (Bars.Count -
How to: Find the first intraday bar number of the most-recent day
While bar numbers in the chart are always from 0 to Bars.Count - 1, it's often useful to know the number of an intraday bar, where 0 is the first intraday bar of the day. To find the intraday bar number of a particular bar, use the Bars.IntradayBarNumber method. The example uses IntradayBarNumber in reverse to find the first bar number of a specified day. Example (How to run Example code? C#
using using using using using using System; System.Collections.Generic; System.Text; System.Drawing; WealthLab; WealthLab.Indicators;
3
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { //Returns the first bar of DaysAgo for an intraday chart public int FirstBarXDaysAgo(int DaysAgo) { int startBar = Bars.Count; for (int day = 1; day <= DaysAgo; day++) startBar = startBar - 1 - Bars.IntradayBarNumber(startBar - 1); return startBar; } protected override void Execute() { ClearDebug();
PrintDebug("First bar number of current day = " + FirstBarXDaysAgo(1)); PrintDebug("First bar number of yesterday = " + FirstBarXDaysAgo(2)); } } }
How to: Find the bar number that corresponds to a specific date
Use the ConvertDateToBar method of Bars. Unless you're certain that the date will be found in the chart, it's convenient to specify the exactMatch parameter as false. For example, to find the first intraday bar with a particular date, with exactMatch = false you need only to specify the date, not the time. Example (How to run Example code? C#
3
protected override void Execute() { ClearDebug(); int year = 2007; int month = 11; int day = 16; DateTime dt = new DateTime(year, month, day); PrintDebug("The bar number for 11/16/2007 is " + Bars.ConvertDateToBar(dt, false)); }
3.1
OHLC/V Series
The Open, High, Low, Close, and Volume (OHLC/V) series that make up the primary chart are accessed by their same-name property in Bars. It's difficult to talk about the OHLC/ V series in Bars without prematurely introducing the DataSeries type, which is covered in the next topic 11 . For now, suffice to say that values in DataSeries are double-precision floating point numbers, which means that they're accurate to at least 15 significant digits. Significant digits of a floating point number include the numbers before and after the decimal.
protected override void Execute() { const int period = 5; double sum = 0d; for(int bar = Bars.Count - 1; bar >= Bars.Count - period; bar--) sum = sum + Bars.Close[bar]; double avg = sum / period; PrintDebug("The " + period + "-Period average is: " + avg); }
Since the DataSeries Open, High, Low, Close, and Volume also exist in the WealthScript base class, When referencing any of the five OHLC/V DataSeries, you don't have to qualify them with Bars. In other words, Close is equivalent to Bars.Close, High is Bars.High, etc.
protected override void Execute() { PlotSeries(PricePane, High, Color.Gray, WealthLab.LineStyle.Solid, 2); PlotSeries(PricePane, Low, Color.Gray, WealthLab.LineStyle.Solid, 2); // Envelope the Volume histogram PlotSeries(VolumePane, Volume, Color.Fuchsia, WealthLab.LineStyle.Solid, 2); }
21
continuous-contract data non-negative. The example checks the value of the lowest-low, and if it's negative adds an integer value that will make all OHLC/V DataSeries nonnegative. Example (How to run Example code? C#
3
protected override void Execute() { // If the lowest value is negative, round it up, and add to the OHLC DataSeries double low = Lowest.Value(Bars.Count - 1,Low, Bars.Count); if (low < 0) { low = Math.Ceiling(Math.Abs(low)); for (int bar = 0; bar < Bars.Count; bar++) { Open[bar] = Open[bar] + low; High[bar] = High[bar] + low; Low[bar] = Low[bar] + low; Close[bar] = Close[bar] + low; } } }
3.2
protected override void Execute() { if (Bars.Scale != BarScale.Minute) { DrawLabel(PricePane, "A minute scale is not selected", Color.Red); return; } // Assumes a 60-Minute interval or less for simplicity int exitTime = 1500 + 60 - Bars.BarInterval; // Find the Period equivalent to the last 2 days int Period = 390 * 2 / Bars.BarInterval; DataSeries breakoutHigh = Highest.Series(High, Period); PlotStops(); for(int bar = Period; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; int currentTime = Date[bar].Hour * 100 + Date[bar].Minute; if (currentTime >= exitTime) SellAtMarket(bar + 1, p, "Market Close"); else SellAtLimit(bar + 1, p, p.EntryPrice * 1.02, "Profit Target"); } else { // Don't enter on the first bar of the day if (!Bars.IsLastBarOfDay(bar)) BuyAtStop(bar + 1, breakoutHigh[bar]); } } }
ClearDebug(); for(int bar = 20; bar < Bars.Count; bar++) { PrintDebug(String.Format( "{0:yyyyMMdd HHmm }", Date[bar] ) + Close[bar]); } }
The following link is a handy reference for displaying Custom DateTime Format Strings: http://msdn2.microsoft.com/en-us/library/8kb3ddd4(VS.71).aspx
10
3.3
Symbol Info
How to: Access the current Symbol or Company Name
Symbol and SecurityName are string properties of Bars. Example (How to run Example code? C#
3
protected override void Execute() { DrawLabel(PricePane, Bars.Symbol + ": " + Bars.SecurityName, Color.Black); }
protected override void Execute() { SymbolInfo si = Bars.SymbolInfo; DrawLabel(PricePane, si.Symbol + " Tick = " + si.Tick, Color.Blue); int bar = Bars.Count - 1; // Create an Alert 1 tick over the last close for Futures, otherwise 5 ticks if ( si.SecurityType == WealthLab.SecurityType.Future ) BuyAtLimit(bar + 1, Close[bar] + si.Tick); else BuyAtLimit(bar + 1, Close[bar] + si.Tick * 5); }
DataSeries
11
DataSeries
A DataSeries is the basic data structure that represents a historical series. It's represented internally as a List<double> with an associated List<DateTime>, which can be managed in the instance or linked to another DataSeries or Bars object. Bars OHLC/V Series 6 and all indicators 32 are instances of DataSeries. Characteristics of DataSeries 1. A DataSeries is a series of floating point data values of type double. Consequently, each value in the series is double precision (14 to 15 significant digits). 2. All DataSeries contain the same number of values as bars in the chart. Exception: If you specify false for the synchronize parameter when accessing secondary symbol data via SetContext or GetExternalSymbol, the returned series can have a different number of bars than the chart.
12
4.1
Series Operators
How to: Multiply, Divide, Add, or Subtract a DataSeries by another
Performing math operation with DataSeries are very intuitive in Wealth-Lab Pro Version 6. Simply perform the operation as if a DataSeries were any plain-vanilla double type. You can multiply, divide, add, or subtract a DataSeries with another DataSeries to return a result DataSeries. In reality, when performing math operations on two DataSeries, the operation occurs with the values from the same bar (DateTime) in each series. When the operation is between a DataSeries and a constant value, then that value operates on each element of the DataSeries. When dividing two DataSeries, Wealth-Lab suppresses division-by-zero errors and inserts the a zero value (0d) for those bars. Example (How to run Example code? C#
3
// Create an "Average Price" DataSeries protected override void Execute() { DataSeries avgPrice = (High + Low) / 2; avgPrice.Description = "Average Price"; PlotSeries(PricePane, avgPrice, Color.Blue, WealthLab.LineStyle.Solid, 1); }
Instead of creating average price yourself, just use the standard indicator, AveragePrice instead.
protected override void Execute() { DataSeries typPrice = (High + Low + Close) / 3; typPrice.Description = "Typical Price"; PlotSeries(PricePane, typPrice, Color.Black, WealthLab.LineStyle.Solid, 1); }
Instead of creating typical price yourself, just use the standard indicator, AveragePriceC instead.
DataSeries
13
protected override void Execute() { // Plot the percent change over the last 5 bar DataSeries delayedClose = Close >> 5; DataSeries pctChange = 100 * ( Close / delayedClose - 1); pctChange.Description = "5-day Pct Change"; ChartPane changePane = CreatePane(40, true, true); PlotSeries(changePane, pctChange, Color.Red, WealthLab.LineStyle.Solid, 1); /* We'll cover indicators later, but notice that the same result is obtained from the ROC indicator which we'll plot as a histogram */ DataSeries roc = ROC.Series(Close, 5); PlotSeries(changePane, roc, Color.Black, WealthLab.LineStyle.Histogram, 1); }
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { //Returns a DataSeries with the sum of the count that ds1 < ds2 in the specified Period public DataSeries LessThanCountSeries( DataSeries ds1, DataSeries ds2, int Period ) { DataSeries LTCSeries = ds2 - ds1; LTCSeries = LTCSeries + DataSeries.Abs(LTCSeries); LTCSeries = LTCSeries / LTCSeries; return Sum.Series(LTCSeries, Period); } protected override void Execute() { // Count how many closes are below its 20-Period moving averages in the last 20 Periods DataSeries sma = SMA.Series(Close, 20); DataSeries ltc = LessThanCountSeries(Close, sma, 20); ChartPane ltcPane = CreatePane(40, true, false); PlotSeries(ltcPane, ltc, Color.Blue, WealthLab.LineStyle.Histogram, 1); PlotSeries(PricePane, sma, Color.Blue, WealthLab.LineStyle.Solid, 2); } } }
14
4.2
protected override void Execute() { DataSeries avgPrice = (High + Low) / 2; avgPrice.Description = "Average Price"; PlotSeries(PricePane, avgPrice, Color.Blue, WealthLab.LineStyle.Solid, 1); int lastBarNumber = Bars.Count - 1; double lastAvg = avgPrice[lastBarNumber]; DrawLabel(PricePane, "The value of avgPrice on the final bar is " + lastAvg, Color.Black); }
We'll cover Technical Indicators 32 later, but we'll just point out here that it's often convenient to access an indicator's value at a specified bar directly from the Series method, as shown in this simple screen 111 for 14-Period RSI values under 30. Example (How to run Example code? C#
protected override void Execute() { int bar = Bars.Count - 1; if (RSI.Series(Close, 14)[bar] < 30) BuyAtMarket(bar + 1); }
3
DataSeries
15
4.3
Preferrably, create a new DataSeries object by passing the Bars object of the primary series to the DataSeries function. This method provides the ability to give a friendly description to the new series as well. C#
protected override void Execute() { // Create, fill, and, plot a random DataSeries with values between 50 and 100, otherwise zero. DataSeries random = new DataSeries(Bars, "Initially zeroes, filled by random numbers"); Random r = new Random(); for (int bar = 0; bar < Bars.Count; bar++) { double rVal = 100 * r.NextDouble(); if (rVal > 50) random[bar] = rVal; } ChartPane rPane = CreatePane( 40, true, true ); PlotSeries(rPane, random, Color.Blue, WealthLab.LineStyle.Histogram, 1); }
protected override void Execute() { const double Sensitivity = 2.5; // less sensitive with bigger numbers DataSeries atr = ATR.Series( Bars, 5 ); DataSeries hi = Highest.Series(High, 3); DataSeries lo = Lowest.Series(Low, 3); for (int bar = 10; bar < Bars.Count; bar++){ if (High[bar] > High[bar-1] + Sensitivity * atr[bar]) High[bar] = Math.Max( hi[bar-1], Math.Max(Open[bar], Close[bar]) ); if (Low[bar] < Low[bar-1] - Sensitivity * atr[bar]) Low[bar] = Math.Min( lo[bar-1], Math.Min(Open[bar], Close[bar]) ); } }
16
DataSeries
17
4.4
In the example, we access Bars of a benchmark symbol and use its Close series to calculate a relative index using a Most Anchored Momentum technique. Conveniently, using the same Bars reference for the benchmark, we can plot the benchmark symbol as well. Example (How to run Example code? C#
3
protected override void Execute() { // Create a Most-Anchored Momentum Index with a benchmark symbol Bars bmBars = GetExternalSymbol(".SPX", true); DataSeries mam = Close / bmBars.Close; mam = EMA.Series(mam, 10, EMACalculation.Modern) / SMA.Series(mam, 20); mam.Description = "Most Anchored Momentum: " + Bars.Symbol + " - " + bmBars.Symbol; ChartPane mamPane = CreatePane(40, true, true); PlotSeries(mamPane, mam, Color.Blue, LineStyle.Solid, 2); ChartPane bmPane = CreatePane(40, true, true); PlotSymbol(bmPane, bmBars, Color.LightGreen, Color.Black); }
Method 2:
SetContext() changes the context of the script to a symbol other than the primary or "clicked" symbol such that the Bars reference is [temporarily] re-assigned to the new symbol. After a call to SetContext() you can create trades with the new symbol in the normal manner. See Programming Trading Strategies 43 .
3
protected override void Execute() { SetContext("MSFT", true); Bars msft = Bars; // Save the Bars reference to MSFT RestoreContext(); // Go back to the primary chart symbol // Plot the Close series from MSFT in the main Price Pane PlotSeries(PricePane, msft.Close, Color.Blue, LineStyle.Solid, 2); // Create an alert for the current symbol int bar = Bars.Count - 1; BuyAtMarket(bar + 1, Bars.Symbol); // Create an alert for MSFT SetContext("MSFT", true); BuyAtMarket(bar + 1, Bars.Symbol); RestoreContext(); } 2011 FMR LLC All rights reserved.
18
Normally and in the examples above, you'll set the boolean synchronize parameter in GetExternalSymbol() and SetContext() to true. See the next topic on Secondary Series Synchronization 18 to learn about a case in which you may prefer to disable synchronization in order to create indicators using the raw Bars or DataSeries of the external symbol.
4.4.1
The end result is that all external series are synchronized bar by bar with the Primary Bars. This ensures that the Bars.Count for the external series matches perfectly. Also, you can create and plot indicators based on the external series without worry. The following illustration shows how the synchronization takes place between a Primary and an external DataSeries. Primary DataSeries Date Value 1 1 2 2 3 3 5 4 8 5 9 6 10 7 11 8 12 9 15 10 16 11
DataSeries
19
The External Series was affected in the following ways: The bar for date "4" was eliminated because it didn't exist in the Primary Series. Bar "9" was inserted, and the price value was adopted from Bar "8". Bar "17" was eliminated, because it was outside the bounds of the Primary Series.
Changing the Default Behavior WealthScript provides a way to disable the default behavior and perform the synchronization after calculating indicators on the raw external series. This involves disabling secondary symbol synchronization when accessing the a symbol's Bars or DataSeries and later synchronizing with the Synchronize() function. 1. To disable the default synchronization behavior, pass false to the synchronize parameter in GetExternalSymbol() and SetContext(). 2. Create the indicators on the unsynchronized (raw) external Bars or DataSeries reference from Step 1. 3. Call Synchronize() to synchronize the external series to create a synchronized Bars or DataSeries reference. Synchronize() synchronizes the external series just like the default behavior, but it also synchronizes the indicators based on the external series. In this way you can be sure that the indicators are accurate if the external series has different trading days than the Primary Bars.
20
Here's how the moving average would have changed if we calculate it using the raw external series data, and then synchronize it. Note that the moving average value for the inserted bar 9 is copied from the previous moving average value. Date Value SMA5 1 20 2 21 3 22 5 24 8 25 23 9 25 23 10 26 24 11 27 25 12 28 26 15 29 27 16 30 28
protected override void Execute() { // Access the secondary symbol, but don't synch Bars naz = GetExternalSymbol(".NDX", false); // Calculate it's moving average series DataSeries nazSMA = SMA.Series(naz.Close, 50); // Now synchronize the Bars and average DataSeries naz = Synchronize(naz); nazSMA = Synchronize(nazSMA); ChartPane nazPane = CreatePane(75, true, true); PlotSeries(nazPane, nazSMA, Color.Blue, WealthLab.LineStyle.Solid, 1); PlotSymbol(nazPane, naz, Color.LightBlue, Color.Black); }
21
22
5.1
Chart Panes
Three types of chart panes exist, but all are ChartPane objects. Price Pane Bars OHLC series are displayed in the Price Pane according to the selected Chart Style. By default, all Strategy windows have a Price pane, which is referenced in Strategy code as PricePane. Volume Pane Bars.Volume appears as a histogram in the Volume Pane. By default, all Strategy windows have a Volume pane, which is referenced in Strategy code as VolumePane. Custom Panes All other panes are custom panes created in Strategy code by calling CreatePane, which returns a ChartPane object.
protected override void Execute() { bool abovePricePane = true; bool displayGrid = true; ChartPane myCustomPane1 = CreatePane(40, abovePricePane, displayGrid); DrawLabel(myCustomPane1, "myCustomPane1", Color.Black); // Create a second pane displayGrid = false; ChartPane myCustomPane2 = CreatePane(40, abovePricePane, displayGrid); DrawLabel(myCustomPane2, "myCustomPane2", Color.Black); // Create a third pane abovePricePane = false; ChartPane myCustomPane3 = CreatePane(40, abovePricePane, displayGrid); DrawLabel(myCustomPane3, "myCustomPane3", Color.Black); }
23
5.2
protected override void Execute() { //Plot Microsoft data in a new pane Bars msft = GetExternalSymbol("MSFT", true); ChartPane msftPane = CreatePane( 100, false, true); PlotSymbol( msftPane, msft, Color.DodgerBlue, Color.Red); }
/* Plot a Heikin-Ashi chart above the main PricePane */ protected override void Execute() { DataSeries HO = Open + 0; // Add 0 to create a copy of a DataSeries based on a core series DataSeries HH = High + 0; DataSeries HL = Low + 0; DataSeries HC = (Open + High + Low + Close) / 4; for (int bar = 1; bar < Bars.Count; bar++) { double o1 = HO[ bar - 1 ]; double c1 = HC[ bar - 1 ]; HO[bar] = ( o1 + c1 ) / 2; HH[bar] = Math.Max( HO[bar], High[bar] ); HL[bar] = Math.Min( HO[bar], Low[bar] ); } ChartPane haPane = CreatePane(100, true, true); PlotSyntheticSymbol(haPane, "Heikin-Ashi", HO, HH, HL, HC, Volume, Color.DodgerBlue, Color.Red); }
public void PlotBenchMark(string symbol, DateTime fromDate) { if (Bars.Symbol != symbol) { int bar = Bars.ConvertDateToBar(fromDate, false); double refPrice = Close[bar]; Bars bmBars = GetExternalSymbol(symbol, true); double factor = refPrice / bmBars.Close[bar];
24
DataSeries dsO = bmBars.Open * factor; DataSeries dsH = bmBars.High * factor; DataSeries dsL = bmBars.Low * factor; DataSeries dsC = bmBars.Close * factor; PlotSyntheticSymbol(PricePane, symbol + " (relative)", dsO, dsH, dsL, dsC, Volume, Color.Blue, Co } } protected override void Execute() { // Compare to a benchmark symbol from 1/1/2006 PlotBenchMark("MSFT", new DateTime(2006, 1, 1)); }
25
5.3
Plotting DataSeries
How to Plot a DataSeries
To plot a DataSeries: 1. Create the DataSeries, and if required, finish assigning values to all bars before proceeding to Step 3. 2. Optional: If the DataSeries is not to be plotted in the Price or Volume panes, create a custom ChartPane 22 . 3. Call one of the following PlotSeries methods: PlotSeries(), PlotSeriesFillBand(), PlotSeriesDualFillBand(), and PlotSeriesOscillator(). PlotSeries* statements should be called only once for each plot. They never belong in a Bars.Count loop. In previous topics, we've already seen various examples of plotting a DataSeries using the PlotSeries(). See the QuickRef for more examples of each type of PlotSeries* method. (Category: Cosmetic Chart).
protected override void Execute() { HideVolume(); ChartPane myVolumePane = CreatePane(40, true, true); PlotSeries(myVolumePane, Bars.Volume, Color.Black, WealthLab.LineStyle.Histogram, 5); for (int bar = 0; bar < Bars.Count; bar++) if (Close[bar] > Open[bar]) SetSeriesBarColor(bar, Bars.Volume, Color.Green); else SetSeriesBarColor(bar, Bars.Volume, Color.Red); }
26
protected override void Execute() { /* Your Trading Strategy here */ PlotWinRate(); } public void PlotWinRate() { DataSeries winRate = Close - Close; // Creates a DataSeries filled with zeroes DataSeries tradeTotal = winRate + 0; foreach (Position p in Positions) { if (p.ExitBar > -1){ tradeTotal[p.ExitBar] += 1; if (p.NetProfitPercent > 0) winRate[p.ExitBar] += 1; } } for (int bar = 1; bar < Bars.Count; bar++){ tradeTotal[bar] = tradeTotal[bar] + tradeTotal[bar-1]; winRate[bar] = winRate[bar] + winRate[bar - 1]; } winRate = 100 * winRate / tradeTotal; winRate.Description = "Win Rate (%)"; ChartPane winPane = CreatePane(40, true, true); PlotSeries(winPane, winRate, Color.Green, WealthLab.LineStyle.Solid, 1); }
5.3.1
27
5.4
Writing Text
For programmatically writing text on a chart, the following methods are available: AnnotateBar() Provides a method to write a string above/below the high/low of a bar in the PricePane. Multiple calls on the same bar result in "stacked text".
AnnotateChart() Similar to AnnotateBar() but more flexible since you can specify the ChartPane, bar, alignment relative to bar, and price to place text. DrawLabel() Multiple labels are stacked in the upper left corner of the Price pane, below the symbol. Labels are automatically drawn for plotted DataSeries using their Description property. DrawText() Provides a method to write a string to a position defined by x, y coordinates in pixels, where 0, 0 is the upper left corner of the chart.
// A vertical labeling routine public void VerticalText(int bar, string str, bool abovePrices, Color color) { if (abovePrices) for (int n = str.Length - 1; n >= 0; n--) AnnotateBar(str.Substring(n, 1), bar, abovePrices, color); else for (int n = 0; n < str.Length; n++) AnnotateBar(str.Substring(n, 1), bar, abovePrices, color); } protected override void Execute() { // Make extra room for peak label HidePaneLines(); CreatePane(3, true, false); // identify 5% peak and trough bars int bar = Bars.Count -1; while (bar > 0) { bar = (int)PeakBar.Series(Close, 5, WealthLab.Indicators.PeakTroughMode.Percent)[bar]; if (bar > -1) VerticalText(bar, "PEAK", true, Color.Red); } bar = Bars.Count -1; while (bar > 0) { bar = (int)TroughBar.Series(Close, 5, WealthLab.Indicators.PeakTroughMode.Percent)[bar]; if (bar > -1) VerticalText(bar, "TROUGH", false, Color.Green); } }
28
protected override void Execute() { // Create an Average Price DataSeries, delayed by 1 bar DataSeries avgPrice = (High + Low) / 2 >> 1; string defaultDescription = avgPrice.Description; // assign a friendly Description avgPrice.Description = "Average Price (1 bar delay)"; PlotSeries(PricePane, avgPrice, Color.Blue, WealthLab.LineStyle.Solid, 1); DrawLabel(PricePane, "Default Description: " + defaultDescription, Color.Red); }
29
5.5
Drawing Objects
When drawing objects, the "hard part" is simply to find the points to connect. The PeakBar() and TroughBar() functions are convenient for this purpose, but any method can be used to find interesting coordinates on a chart for drawing flag formations, triangles, rectangles, etc. Each of the following methods can be used in any ChartPane. See the QuickRef (F11) for more details and an example for each. DrawCircle() DrawEllipse() DrawLine() Drawing small circles are useful for highlighting key points or targets in a chart. For an ellipse, you need only to specify opposing corners of a the imaginary rectangle that bounds the ellipse. Draws a line that connects two points. The line can be extended with the same slope by finding the y value on a subsequent bar via the LineExtendY() or LineExtendYLog() methods and passing the result to another call to DrawLine(). A convenient method for drawing support and resistance lines at a fixed value across the entire chart. Provides a method to draw any n-sided polygon.
DrawHorzLine() DrawPolygon()
protected override void Execute() { // Find the last two 5% peaks and draw a line to the end of the chart int bar = Bars.Count - 1; int pb2 = (int)PeakBar.Value(bar, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); int pb1 = pb2; if (pb2 > -1) pb1 = (int)PeakBar.Value(pb2, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); if ((pb2 == -1) || (pb1 == -1)) DrawLabel(PricePane, "Could not find two 5% peaks", Color.Red); else DrawExtendedTrendline(PricePane, pb1, High[pb1], pb2, High[pb2], bar, Color.Blue); } public void DrawExtendedTrendline(ChartPane pane, int bar1, double val1, int bar2, double val2, int ba { // draw the line for the 2 known points DrawLine(pane, bar1, val1, bar2, val2, Color.Blue, WealthLab.LineStyle.Solid, 2); // extend the line to the specified bar3 double val3 = LineExtendY(bar1, val1, bar2, val2, bar3); if (pane.LogScale) val3 = LineExtendYLog(bar1, val1, bar2, val2, bar3); DrawLine(pane, bar2, val2, bar3, val3, color, WealthLab.LineStyle.Solid, 1); }
30
protected override void Execute() { // Find the last two 5% peaks and draw a line to the end of the chart int bar = Bars.Count - 1; int pb2 = (int)PeakBar.Value(bar, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); int pb1 = pb2; if (pb2 > -1) pb1 = (int)PeakBar.Value(pb2, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); if ((pb2 == -1) || (pb1 == -1)) DrawLabel(PricePane, "Could not find two 5% peaks", Color.Red); else DrawExtendedTrendline(PricePane, pb1, High[pb1], pb2, High[pb2], bar, Color.Blue); } public void DrawExtendedTrendline(ChartPane pane, int bar1, double val1, int bar2, double val2, int ba { // draw the line for the 2 known points DrawLine(pane, bar1, val1, bar2, val2, Color.Blue, WealthLab.LineStyle.Solid, 2); // extend the line to the specified bar3 double val3 = LineExtendY(bar1, val1, bar2, val2, bar3); if (pane.LogScale) val3 = LineExtendYLog(bar1, val1, bar2, val2, bar3); DrawLine(pane, bar2, val2, bar3, val3, color, WealthLab.LineStyle.Solid, 1); }
protected override void Execute() { // Find the last two 5% peaks and draw a line to the end of the chart int bar = Bars.Count - 1; int pb2 = (int)PeakBar.Value(bar, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); int pb1 = pb2; if (pb2 > -1) pb1 = (int)PeakBar.Value(pb2, High, 5, WealthLab.Indicators.PeakTroughMode.Percent); if ((pb2 == -1) || (pb1 == -1)) { DrawLabel(PricePane, "Could not find two 5% peaks", Color.Red); return; } int tb2 = (int)TroughBar.Value(bar, Low, 5, WealthLab.Indicators.PeakTroughMode.Percent); int tb1 = tb2; if (tb2 > -1) tb1 = (int)TroughBar.Value(pb2, Low, 5, WealthLab.Indicators.PeakTroughMode.Percent); if ((tb2 == -1) || (tb1 == -1)){ DrawLabel(PricePane, "Could not find two 5% troughs", Color.Red); return; } DrawPolygon(PricePane, Color.LightBlue, Color.LightBlue, WealthLab.LineStyle.Invisible, 1, true, pb1, High[pb1], pb2, High[pb2], tb2, Low[tb2], tb1, Low[tb1]); }
31
5.6
protected override void Execute() { ChartPane fundPane = CreatePane(40, true, true); PlotFundamentalItems(fundPane, "assets", Color.Black, WealthLab.LineStyle.Solid, 1); }
67
32
Indicators
All Wealth-Lab technical indicators that are visible in the Technical Indicators dialog (right) are integrated as Indicator Libraries, which are .NET components. Upon startup, Wealth-Lab Pro detects indicator libraries that exist in the main installation directory and displays them as folders in the Technical Indicators dialog. See APIs
118
Indicator Syntax
As with other classes, selecting an indicator and viewing its syntax is integrated with the Strategy Editor. Just strike Ctrl + Space to bring up the code completion pop up. Type a few letters to narrow down the search as shown. After locating the item, pressing the Tab or Enter key completes the item.
Code Completion
As you'd expect, typing the period delimiter automatically lists class properties and methods. As you'll soon learn, all indicators have a Series method, and some also have a Value
Indicators
33
method. If the list becomes hidden, place the cursor after the period and strike Ctrl + Space to show the list again. Upon arriving at the parameter list, typing the opening parenthesis reveals the full indicator syntax. If the tooltip syntax disappears (due to moving the cursor out of the parameter list, for example), place the cursor back in the parameter list and strike Ctrl + Shift + Space to view the tooltip again. The bold type shows indicates the current parameter at the cursor's position.
Tip: Make it a habit to type a white space after each comma in a parameter list. This causes enumerations to appear automatically for enumerated parameters as shown below.
34
6.1
Series Method
Indicator classes implement a static method called Series that returns an instance of the indicator, which is derived from the DataSeries class. This will either be a new instance, or an instance that was found in the Cache. The caching mechanism saves computer resources by ensuring that multiple copies of the same indicator are not created.
Method 2:
// Method 1 SMA sma = new SMA(Close, 50, "50-day SMA"); PlotSeries(PricePane, sma, Color.Blue, WealthLab.LineStyle.Solid, 2); // Method 2 DataSeries sma2 = SMA.Series(Close, 20); sma2.Description = "20-day SMA"; PlotSeries(PricePane, sma2, Color.Red, WealthLab.LineStyle.Solid, 2); }
Indicators
35
6.2
protected override void Execute() { int bar = Bars.Count - 1; DataSeries sma = SMA.Series(Close, 50); if (Close[bar] > sma[bar]) DrawLabel(PricePane, "Above 50-day SMA", Color.Blue); else DrawLabel(PricePane, "Below 50-day SMA", Color.Red); }
protected override void Execute() { int bar = Bars.Count - 1; double smaVal = SMA.Value(bar, Close, 50); if (Close[bar] > smaVal) DrawLabel(PricePane, "Above 50-day SMA", Color.Blue); else DrawLabel(PricePane, "Below 50-day SMA", Color.Red); }
While both methods produce equivalent results, recall that the Series method returns a DataSeries with indicator values calculated for every bar. Since we only need one value in the example, there's no reason to commit to the processing and memory overhead of the Series method when only a single SMA value is required.
36
6.3
Custom Indicators
In Wealth-Lab Pro Version 6 only formal, compiled indicator libraries exist that conform to Indicator Library 118 specifications. You can, of course, create your own conforming custom library namespace for indicators that you frequently use. In many cases, however, it's simply not worth the effort to formalize an indicator that can be created by a few simple, easy-to-remember math operations. For this reason, we refer to custom indicators as DataSeries that are created "on the fly" rather than those that are formalized in a compiled library. The "typical price" series 12 is one such example that we've already seen.
/* Replicating the Sum.Series indicator */ using System; using System.Collections.Generic; using System.Text; using System.Drawing; using WealthLab; using WealthLab.Indicators; namespace WealthLab.Strategies { public class SumTest : DataSeries { public SumTest ( DataSeries ds, int period ) : base(ds, "My Sum.Series") { double sum = 0; for (int bar = 0; bar < period; bar++) sum += ds[bar]; this[period - 1] = sum; // sum for initial period for (int bar = period; bar < ds.Count; bar++) this[bar] = this[bar - 1] - ds[bar - period] + ds[bar]; } public static SumTest Series( DataSeries ds, int period ) { SumTest _SumTest = new SumTest( ds, period ); return _SumTest ; } } public class MyStrategy : WealthScript { protected override void Execute() { int period = 20; // create and plot the semi-formal Sum.Series DataSeries sum1 = SumTest.Series(Close, period); ChartPane cp = CreatePane(40, true, true); PlotSeries(cp, sum1, Color.Blue, LineStyle.Solid, 1); // create the same series "on the fly", starting with a DataSeries of zeroes to fill
Indicators
37
DataSeries sum2 = Close - Close; sum2.Description = "Sum (on-the-fly)"; double sum = 0d; for (int bar = 0; bar < period; bar++) sum += Close[bar]; sum2[period - 1] = sum; // sum for initial period for (int bar = period; bar < Bars.Count; bar++) sum2[bar] = sum2[bar - 1] - Close[bar - period] + Close[bar]; // plot as a histogram in the same pane for comparison PlotSeries(cp, sum2, Color.Black, LineStyle.Histogram, 1); } } }
// Note: The modern calculation for the EMA exponent is 2/(1 + Period) protected override void Execute() { // Create a custom MACD indicator DataSeries macdX = EMA.Series(Close, 12, EMACalculation.Modern) - EMA.Series(Close, 26, EMACalculati // Plot the custom MACD and compare to the standard one ChartPane macdPane = CreatePane(40, true, true); MACD standardMACD = new MACD(Close, "Standard MACD"); PlotSeries(macdPane, macdX, Color.Blue, LineStyle.Solid, 1); PlotSeries(macdPane, standardMACD, Color.Red, LineStyle.Histogram, 1); }
protected override void Execute() { DataSeries weightedClose = ( (2 * Close) + High + Low ) / 4; PlotSeries(PricePane, weightedClose, Color.Blue, LineStyle.Solid, 1); }
protected override void Execute() { DataSeries spread = Close - GetExternalSymbol("MSFT", true).Close; ChartPane sPane = CreatePane(40, true, true); PlotSeries(sPane, spread, Color.Blue, LineStyle.Solid, 2);
38
protected override void Execute() { DataSeries efo = (Close - (Close >> 1)) * Volume; efo.Description = "Elder Force"; DataSeries efEma = EMA.Series(efo, 2, EMACalculation.Modern); ChartPane efPane = CreatePane(40, true, true); PlotSeries(efPane, efo, Color.Black, LineStyle.Histogram, 1); PlotSeries(efPane, efEma, Color.Green, LineStyle.Solid, 2); }
for(int bar = K.FirstValidValue; bar < Bars.Count; bar++) { // Calculate NRTR_WATR Series if( Trend >= 0 ) { HPrice[bar] = Math.Max( Bars.Close[bar], HPrice[bar] ); Reverse = HPrice[bar] - K[bar]; if( Bars.Close[bar] <= Reverse ) { Trend = -1; LPrice[bar] = Bars.Close[bar]; Reverse = LPrice[bar] + K[bar]; } } if( Trend <= 0 ) { LPrice[bar] = Math.Min( Bars.Close[bar], LPrice[bar] ); Reverse = LPrice[bar] + K[bar]; if( Bars.Close[bar] >= Reverse )
Indicators
39
{ Trend = 1; HPrice[bar] = Bars.Close[bar]; Reverse = HPrice[bar] - K[bar]; } } NRTR_WATR[bar] = Reverse; } // Display the resulting NRTR_WATR data series PlotSeries( PricePane, NRTR_WATR, Color.Teal, WealthLab.LineStyle.Dotted, 3 ); for(int bar = NRTR_WATR.FirstValidValue+1; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if( CrossUnder( bar, Close, NRTR_WATR ) ) SellAtMarket( bar+1, LastPosition, "NRTR Exit" ); } else if( CrossOver( bar, Close, NRTR_WATR ) ) BuyAtMarket( bar+1, "NRTR Entry" ); } }
40
6.4
protected override void Execute() { Bars msftBars = GetExternalSymbol("MSFT", true); // Plot MSFT and it's 20-period SMA in a new pane ChartPane msftPane = CreatePane(60, true, true); DataSeries smaMSFT = SMA.Series(msftBars.Close, 20); PlotSymbol(msftPane, msftBars, Color.LightGreen, Color.Black); PlotSeries(msftPane, smaMSFT, Color.Blue, LineStyle.Solid, 2); // Plot the 10-period ATR of MSFT in another pane ChartPane atrPane = CreatePane(40, true, true); DataSeries atrMSFT = ATR.Series(msftBars, 10); PlotSeries(atrPane, atrMSFT, Color.Red, LineStyle.Solid, 2); }
Indicators
41
6.5
Stability of Indicators
Traders new to technical analysis (and some not so new) often assume that they can apply a technical indicator as soon as it begins churning out "valid" values. For many indicators such as SMA, WMA, StochK, etc., the assumption is fine. For example, the value of a 20-period Simple Moving Average (SMA) will always be the same at the end of the same 20 bars. Values of these indicators at the end of the Period are not affected by data prior to the Period. Another large group of indicators such as EMA, WilderMA, RSI, MACD, Kalman (more below) are in fact, calculated using the indicator's own previous value(s). You're likely to find that progressively-calculated indicators like these generally have reduced lag, but that the initial short-term values that they produce are unstable. What does that mean? It's a good question for which we've prepared an example using four indicators with nominal 20-period lengths. To run the example, start by setting the Data Range control to 21 fixed bars and click any symbol. Progressively change the Data Range, increasing the number of bars to 30, 40, 60, 100, and 120. With each change the script will execute and print a new set of values for the final bar in the debug window. You'll notice along the way that only one of the indicators (SMA) maintains a single precise value, while the others fluctuate until they stabilize on a more precise value. Example (How to run Example code? C#
protected override { HideVolume(); ChartPane pane1 ChartPane pane2 ChartPane pane3 ChartPane pane4 PrintDebug(""); PrintDebug("Bar DataSeries DataSeries DataSeries DataSeries void Execute() = = = = CreatePane( CreatePane( CreatePane( CreatePane( 25, 25, 25, 25, true, true, true, true, true true true true ); ); ); );
3
ema = EMA.Series(Close, 20, WealthLab.Indicators.EMACalculation.Modern); macd = MACD.Series(Close); rsi = RSI.Series(Close, 20); sma = SMA.Series(Close, 20);
// Print the last value of each indicator to the debug window int bar = Bars.Count - 1; PrintDebug("ema = " + ema[bar]); PrintDebug("macd = " + macd[bar]); PrintDebug("rsi = " + rsi[bar]); PrintDebug("sma = " + sma[bar]); PlotSeries(pane1, PlotSeries(pane2, PlotSeries(pane3, PlotSeries(pane4, } ema, Color.Red, WealthLab.LineStyle.Solid, 1); macd, Color.Blue, WealthLab.LineStyle.Solid, 1); rsi, Color.Green, WealthLab.LineStyle.Solid, 1); sma, Color.Fuchsia, WealthLab.LineStyle.Solid, 1);
Avoid Using Unstable Indicator Values As we demonstrated, some indicators can produce wildly different results in the short term. The trick is to understand the indicators that you're using, and to ignore their values until they're stable. For meaningful and reproducible results from a trading strategy, you must use indicators when they're stable.
42
The best way to ignore an indicator is to start the trading loop on a bar after which the longest indicator is estimated to be stable. For the four 20-period indicators in the example, you may have noticed that the RSI requires a significant amount of "seed data" to stabilize. Consequently, we would choose to start our trading loop no earlier than bar 60 and preferably bar 120 if sufficient test data is available. A reasonable rule of thumb is to ignore progressively-calculated indicators for 3 to 4 times their period.
protected override void Execute() { /* begin trading loop */ for(int bar = 60; bar < Bars.Count; bar++) { /* trading strategy code or rules */ } }
Which Indicators? The following list from the Wealth-Lab Standard Indicator Library are calculated using previous values and consequently have the potential to produce unstable values at the beginning of the series. ADX, ADXR, ATR, ATRP, CADO, DIMinus, DIPlus, DSS, EMA, FAMA, Kalman, KAMA, KST, MACD, MAMA, OBV, Parabolic, Parabolic2, RSI, StochRSI, TRIX, UltimateOsc, Vidya, Volatility, WilderMA AccumDist is also unstable in the sense that its calculation "accumulates" from a specific starting point. When that starting point changes, the calculation also changes for all bars that follow.
43
Compiling Strategies
A script's Strategy class is compiled and instantiated when you: click Run the Strategy, Compile, or on demand when you open a non-compiled Strategy from the Strategy Explorer or when a Strategy Window is restored with a Workspace. Consequently, class-scope variables are initialized only when the class is instantiated. Run the example script by first clicking Run the Strategy, and thereafter by clicking , striking F5, or by clicking another symbol. You should notice that the variable _scriptRunCountSinceCompile is initialized to 0 only upon compilation and thereafter it retains the value assigned inside the Execute
2011 FMR LLC All rights reserved.
44
45
7.1
Trading Signals
At the heart of backtesting is the ability to create theoretical trades according to specified criteria. WealthScript trading signals are what you would think of as orders that you place with a broker. Refer to the following table. Buy (open long Position) Sell (exit long Position) Short (open short Position) Cover (exit short Position) AtMarket (execute at opening price) AtLimit (execute at Limit price or better) AtStop (execute at Stop price or worse) AtClose (execute at closing price)
Exit (can be used in place of Sell or Cover) All combinations of the first and second columns are possible in Wealth-Lab for a total of 16 order types (20 counting Exit); e.g., BuyAtMarket or SellAtLimit are WealthScript functions used to create and exit a long Position.
Signal Parameters
bar All trading signals have a bar parameter to specify the bar on which the trade is placed. In general, AtMarket and AtClose orders are executed on the bar placed. Exceptions deal with insufficient Position size or trading equity. For all exit signals (Sell, Cover, or Exit) you must specify a Position . For most [single-Position] scripts, you'll most often use LastPosition. To indiscriminately exit all Positions, pass Position.AllPositions for the pos parameter. You must specify a limit/stop price for AtLimit/AtStop orders just as you would for live trading. Wealth-Lab AtStop orders function as "stop market" orders. In other words, the stopPrice is the activation price. Optional for all signals and is displayed in the Trades list's Signal Name column, making it a useful for sorting trades.
pos
limitPrice or stopPrice
signalName
46
PrintDebug("Positions.Count = " + Positions.Count); // assign a variable 'bar' to the 10th to last bar in the chart int bar = Bars.Count - 10; BuyAtMarket(bar + 1); PrintDebug("Positions.Count = " + Positions.Count); PrintDebug("LastPosition.Active = " + LastPosition.Active); // increment the bar and exit the Position AtClose bar++; SellAtClose(bar + 1, LastPosition); PrintDebug("Positions.Count = " + Positions.Count); PrintDebug("LastPosition.Active = " + LastPosition.Active); }
In the example, note that Positions.Count is incremented immediately upon the creation of a Position, which initially is "active". After selling the Position, it remains in the list, but its active status changes to false. The example subtlety illustrates that Wealth-Lab is Position-based. Notice that the details of Position size (shares) are not specified in the Strategy code. Instead, the size is determined by the Position Size control in the Data Panel (User Guide).
protected override void Execute() { // Create a trade on or as close to 12/18/2007 as possible DateTime dt = new DateTime(2007, 12, 18); int bar = DateTimeToBar(dt, false); if (bar > -1) BuyAtMarket(bar, "Trade on " + dt.ToString()); else DrawLabel(PricePane, "No bar on or after " + dt.ToString(), Color.Red); }
47
7.2
Trading Loop
When screening, we're interested only in identifying symbols that currently meet a specified criteria. That's why we're [generally] only interested in values at the last bar, or Bars.Count - 1, for screens. Conversely, when backtesting, we want to test criteria on every bar after all our indicators become valid. Every trading Strategy should have a main loop that cycles through the data in your chart. This is accomplished with a typical for statement and allows you to "look" the data one bar at a time, just as you would in real trading. We often refer to the main loop as the trading loop. Example (illustrative)
protected override void Execute() { for(int bar = 50; bar < Bars.Count; bar++) { // Trading rules here } }
Here, the for loop starts at the 50th bar of data, but you should set your main loop's starting point based on the technical indicators that you're using. For example, if you use a 50-bar SMA and a 14-bar RSI in your rules, choose the longer of the two indicators and set your starting for loop value to 50. The main loop should end by processing the last bar at Bars.Count - 1. For more information about when to start the main loop, see Stability of Indicators 41 .
/* CHANGE THIS */
*/
48
7.3
Single-Position Strategies
Strategies that trade one Position at a time are Single-Position (SP) Strategies. Generally, this means that the Strategy can either hold one active Position (long or short) or no active Positions for any symbol. When you run a SP Strategy in a Multi-Symbol Backtest (see User Guide), the "total simulation" can hold multiple Positions simultaneously but only one active Position per symbol, at most.
// SP Strategy Template protected override void Execute() { for(int bar = 20; bar < Bars.Count; bar++) { if (IsLastPositionActive) { //code your exit rules here } else { //code your entry rules here } } }
Positions
A key concept for programming Strategies and processing results is that of the Positions list. As a script creates trades, they are added to the Positions list. You can access Position objects from the list at any time during a Strategy's execution, and it's often required to do so to determine which Position to pass as the pos parameter in an exit signal. The LastPosition property conveniently accesses the most-recent Position added to Positions, and therefore is most useful in SP Strategies. Refer to the Position Management functions in the QuickRef (F11) for more details.
protected override void Execute() { const int Period = 14; PlotSeries(PricePane, SMA.Series(Close, Period), Color.Blue, LineStyle.Solid, 1);
49
for(int bar = Period; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if( CrossUnder(bar, Close, SMA.Series(Close, Period)) ) SellAtMarket(bar + 1, LastPosition); } else { if( CrossOver(bar, Close, SMA.Series(Close, Period)) ) BuyAtLimit(bar + 1, Close[bar]); } } }
Closing out a Position You can see from the example above that the SellAtMarket function is one way to close out an open long Position. The function takes two parameters. The first parameter is the bar in which to close out the Position, and the second is a reference to the Position that we want to sell. Since WealthScript can support trading systems that manage multiple positions 61 we need a way to tell the exit rule which Position we want to sell. For systems that manage a single Position at a time we can use the LastPosition for this purpose. See also: Multiple-Position Strategies
61
7.3.1
50
protected override void Execute() { int longPer = 55; int shortPer = 21; DataSeries H55 = Highest.Series(High, longPer); DataSeries L55 = Lowest.Series(Low, longPer); DataSeries H21 = Highest.Series(High, shortPer); DataSeries L21 = Lowest.Series(Low, shortPer); PlotStops(); for (int bar = longPer; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position Pos = LastPosition; if (Pos.PositionType == PositionType.Long) SellAtStop(bar + 1, LastPosition, L21[bar]); else CoverAtStop(bar + 1, LastPosition, H21[bar]); } else { RiskStopLevel = L21[bar]; if (BuyAtStop(bar + 1, H55[bar]) == null) { RiskStopLevel = H21[bar]; ShortAtStop(bar + 1, L55[bar]); } } } }
protected override void Execute() { const int Period = 14; PlotSeries(PricePane, SMA.Series(Close, Period), Color.Blue, LineStyle.Solid, 1); PlotStops(); for(int bar = Period; bar < Bars.Count; bar++)
51
{ if (IsLastPositionActive) { SellAtStop(bar + 1, LastPosition, SMA.Series(Close, Period)[bar]); } else { if( CrossOver(bar, Close, SMA.Series(Close, Period)) ) BuyAtLimit(bar + 1, Close[bar]); } } }
The small colored dots, automatically plotted by one call to PlotStops(), represent the bar and price on which a stop is active.
7.3.2
Time-Based Exits
How to: Perform a Time-Based Exit
Generally speaking, a time-based exit strategy is a market order that is triggered after a specified number of bars. In a SP Strategy, the general pattern for a time-based exit is as follows:
int numberOfBars = 5; // specify max number of Bars to hold Position Position p = LastPosition; if( bar + 1 - p.EntryBar >= numberOfBars ) ExitAtMarket(bar + 1, p, "Time-based");
Here's a look at a time-based exit in a Strategy that buys at a limit price 3% below the most-recent close and sells after 5 bars. Example (How to run Example code? C#
3
protected override void Execute() { int numberOfBars = 5; for(int bar = 1; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; if( bar + 1 - p.EntryBar >= numberOfBars ) ExitAtMarket(bar + 1, p); } else
52
protected override void Execute() { int numberOfBars = 5; for(int bar = 1; bar < Bars.Count; bar++) { if (IsLastPositionActive) // THE WRONG WAY TO CODE A TIME-BASED EXIT (OR ANY TRADING SIGNAL) ExitAtMarket(bar + numberOfBars, LastPosition); else BuyAtLimit(bar + 1, Close[bar]* 0.97); } }
Specifying a trading signal to occur on bar + n, where n > 1 is never correct for backtesting or trading because: 1. Wealth-Lab Pro executes trading signals immediately in backtests. Exiting a Position on bar + n, where n > 1 causes a Position's status to change to 'not active' prematurely, which has the effect of disrupting a Strategy's intended logic (see Peeking 53 ).
2. It's impossible to trade a Strategy that generates Alerts on bar + n, where n > 1. Depending on the Strategy, the result will be either getting an Alert n - 1 days too early, or, getting an Alert for each of n days to exit the same Position.
53
7.4
Peeking
Being a programming environment, Wealth-Lab doesn't artificially limit what you can do in Strategy code, such as peeking - intentionally or unintentionally. (In Trade Your Way to Financial Freedom, Van Tharp called peeking "postdictive" errors.) While some valid uses for peeking 55 exist, generally it's something that you want to avoid. Unintentional peeking is usually not a concern with logically-designed code, but reviewing the following subtopics should help you avoid some common pitfalls.
54
4. AtLimit signals (profit targets) Stop losses should be tested before profit targets in order to give the most pessimistic result in case both signals would have triggered in the same bar since it's usually not possible to precisely determine which signal would have triggered first. Example (Illustrative) C#
protected override void Execute() { for(int bar = 50; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; if( CumUp.Series(Close, 5)[bar] >= 1 ) ExitAtClose(bar, p); else if( p.MAEAsOfBarPercent(bar) < -8) ExitAtMarket(bar + 1, p); else if( !ExitAtTrailingStop(bar + 1, p, SMA.Series(Close, 50)[bar]) ) ExitAtLimit(bar + 1, p, p.EntryPrice * 1.25); } else { //code your entry rules here } } }
Survivorship Bias
Survivorship bias refers to backtesting with data only from companies that survived severe market downturns. Excluding data from companies that no longer exist or trade publicly can make Strategies appear to perform better (or in some cases, worse) in a
55
simulation than they would have had they been actually traded. Survivorship bias may or may not have a significant effect on the performance of your Strategy, but it's good to be aware that it exists. Noting that it can be difficult to obtain price data from companies that are no longer in business, it's definitely a worthwhile exercise to test with such data if you can find it. Conversely, it's worth mentioning that a good number of companies are purchased by others at large premiums, which create price shocks in a positive direction.
7.4.1
Valid Peeking
It may be surprising to you to find code that appears peek does not actually qualify as peeking. Some common cases follow, but more may apply. Valid Peeking #1 Checking if a known event (options expiry, last trading day of the month, split, etc.) will to occur on a "future bar". In the same way that you can look at a calendar and determine if tomorrow is a new month, the example's NextBarIsNewMonth function checks the date of the next bar. If it's a new month, a new Position is created. By running the script on you'll see that indeed Positions are entered on the first trading day of each month. Note, however, that the script can check the next bar's date only if that next bar actually exists in the chart. For that reason (and simplicity) when processing the final bar of the chart, the function assumes that the next bar is a new month. Consequently, when a Position is not active, the Strategy will always generate an Alert to enter a new trade, and you must look at a calendar to find out if you should actually place the order. Example (How to run Example code? C#
3
public bool NextBarIsNewMonth(int bar) { if (bar < Bars.Count - 1) return Date[bar + 1].Day < Date[bar].Day; else /* Assume that next bar is a new month. Look at a calendar and just ignore any entry Alerts if it is not! */ return true; } protected override void Execute() { for(int bar = 1; bar < Bars.Count; bar++) { if (IsLastPositionActive) { // exit after 3 trading days Position p = LastPosition; if (bar + 1 - p.EntryBar >= 3) SellAtMarket(bar + 1, p, "Time-based"); } else if (NextBarIsNewMonth(bar)) // enter on the first trading day of a new month BuyAtMarket(bar + 1); } }
Valid Peeking #2 Determine if an AtLimit/AtStop order occurred "on the open" so as to give the Position a higher priority. Generally speaking, Position.Priority is difficult to apply when backtesting end-of-day
2011 FMR LLC All rights reserved.
56
stop and limit Strategies. That's because the actual trade sequence should be prioritized by the time of day at which the actual price attains the order price. Nonetheless, it's easy to determine if a stop or limit order would have occurred at market on the opening of a session. In this case, you can assign a greater-than-zero priority so that Wealth-Lab processes those trades first. Example (How to run Example code? C#
3
protected override void Execute() { for(int bar = 1; bar < Bars.Count; bar++) { if (IsLastPositionActive) { // exit logic } else { double limitPrice = Close[bar] * 0.98; // 2% drop if ( BuyAtLimit(bar + 1, limitPrice, "") != null ) if ( bar < Bars.Count - 1 && Open[bar+1] <= limitPrice ) LastActivePosition.Priority = 1.0; } } }
Valid Peeking #3 Determine when an exit condition occurs to Split and exit a portion of a Position When trading in real life, you don't have to think in terms of "splitting Positions" - to exit half of a 1000-share Postion, you'd simply enter an order for 500 shares. When backtesting in Wealth-Lab, the same operation required that you split the initial Position into two individual Positions and then exit when required. But what if you want to split the Position to lock in profits on the way up, but exit the full Position at a stop loss? If you always split the Position and sell both halves for a loss, you incur the penalty of an additional commission charge. Instead, you can wait for the exit condition to occur (just as in real trading) and decide at that moment if a split is required. In the example, we look ahead to the High of the next bar to lock in a 5% profit on half of the initial Position. Example (How to run Example code? C#
3
protected override void Execute() { bool hasSplit = false; for(int bar = 1; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; // test stop loss first if (!SellAtStop(bar + 1, p, p.EntryPrice * 0.975, "Stop Loss")) if (!hasSplit) { // exit half of the Position at a 5% profit target double target = p.EntryPrice * 1.05; if( bar < Bars.Count - 1 && target <= High[bar+1] ) { Position s = SplitPosition( p, 49.99 ); // s is now the LastPosition, so sell off p to continue to use LastPosition logic hasSplit = SellAtLimit(bar + 1, p, target, "5% Profit"); } } else
57
// Sell the rest at 20% profit target SellAtLimit(bar + 1, p, p.EntryPrice * 1.2, "20% Profit"); } else { BuyAtLimit(bar + 1, Close[bar] * 0.97); hasSplit = false; } } }
58
7.5
Strategy Parameters
Strategy Parameters (Straps for short) provide the ability to perform quick, on-demand optimizations to Strategies by adjusting visual sliders at the bottom of the Data Panel. See the User Guide for more details about their application.
59
} }
Step 1 Focusing on the MyStrategy class, change its name to something more meaningful, like SMACrossover, and declare any number of Straps as StrategyParameter types as private class variables. Since we want to adjust the fast and slow periods, we identify a variable for each one. Step 2 The next step is to add a public class constructor. A constructor's method name is the same as its class: SMACrossover(). Within the constructor assign the CreateParameter() method for each one of the variables that you declared in Step 1. Refer to the syntax for CreateParameter():
namespace WealthLab.Strategies { public class SMACrossver : WealthScript { /* Declare parameters */ private StrategyParameter slowPeriod; private StrategyParameter fastPeriod; protected override void Execute() { int fastPer = 20; int slowPer = 55;
namespace WealthLab.Strategies { public class SMACrossver : WealthScript { private StrategyParameter slow; private StrategyParameter fast; /* Add a public constructor (same name as the class) */ public SMACrossver() { fast = CreateParameter("Fast Per", 20, 4, 100, 1); slow = CreateParameter("Slow Per", 55, 5, 300, 5); } protected override void Execute() { int fastPer = 20; int slowPer = 55;
CreateParameter(string name, double value, double start, double stop, double step); The specified name appears next to the slider in the Data Panel to identify the parameter, value is the initial default value for the Strategy Parameter, and step controls the increments between the start and stop minimum and maximum bounds of the parameter.
Step 3 Finally, convert the StrategyParameter types to their corresponding integer or double parameter inside
namespace WealthLabCompile { class MovingAverageCrossover : WealthScript { private StrategyParameter slow; private StrategyParameter fast; public MovingAverageCrossover() { fast = CreateParameter("Fast Per", 20, 4, 100, 1);
60
the execute method using the ValueInt or Value properties, respectively. For the final result of this process, please see the pre-built "Moving Average Crossover" Strategy.
slow = CreateParameter("Slow Per", 55, 5, 300, 5); } protected override void Execute() { /* Obtain periods from parameters */ int fastPer = fast.ValueInt; int slowPer = slow.ValueInt;
61
7.6
Multi-Position Strategies
All examples up to this point have used a design pattern that ensure that Strategies can enter and hold only one Position at a time. Multiple-Position (MP) Strategies are design to manage one or more Positions simultaneously. For example, a Strategy that pyramids Positions (adds more shares) of the same stock has to manage each new Position separately. While you can use SplitPosition() to split a Position into two parts, it's not currently possible to "merge" multiple Positions into one.
MP Strategy Template
Multiple Positions are typically opened on different bars for the same symbol, so the entry and exit logic cannot be mutually-exclusive as with SP Strategies 48 . For this reason you must be extra-careful to ensure that you don't write code that peeks 53 by testing Position-active status following the exit logic. In other words, make sure that entry logic is independent of exits that occur on the next bar. Example (How to run Example code? C#
3
protected override void Execute() { // main loop for(int bar = 20; bar < Bars.Count; bar++) { //exit logic - test each active position for(int pos = ActivePositions.Count - 1; pos >= 0; pos--) { // assign the Position using the list index Position p = ActivePositions[pos];
62
// Example: Process the currently-assigned Position, p if (bar - p.EntryBar > 10) ExitAtMarket(bar + 1, p, "Time-Based"); } /* entry rules here */ } }
Don't use the foreach statement with ActivePositions in the exit logic. It's an error in C# to remove items from a collection like ActivePositions, which occurs by closing a Position. The proper method is to work backwards in the collection, as shown above.
/* This Trading System buys whenever RSI crosses above 30, and closes all open positions when it crosse protected override void Execute() { RSI rsi = RSI.Series(Close, 14); for(int bar = 42; bar < Bars.Count; bar++) { if( ActivePositions.Count > 0 ) if( CrossUnder(bar, rsi, 70) ) SellAtMarket(bar + 1, Position.AllPositions); if( CrossOver(bar, rsi, 30 ) ) BuyAtMarket(bar + 1); } }
48
63
7.7
Multi-Symbol Strategies
With Wealth-Lab's Multi-Symbol Backtest feature, it's generally not required to explicitly create trades on specific symbols within a script. In other words, by default a script trades the primary symbol - the symbol under test. However, when a Strategy involves trading rules that depend on price action of other symbols and vice-versa, then you must take full control of the simulation by explicitly creating trades on secondary symbols after calling SetContext(). Examples of these more-complex Strategies are Pairs Trading and Symbol Rotation Strategies. For examples, see the pre-built "RSI Rotation" or "Dogs of the Dow" scripts.
64
7.8
Alerts
Trading signals are used in two ways: 1. To create theoretical trades for backtests, and, 2. To generate Alerts for orders that should be place on the bar following the last bar of the chart. Alerts are what allow you to trade your strategy "live". They're the orders that you place on the next bar (intraday interval, day, week, etc.) For example, when trading daily bars you'll run the Strategy each evening after updating data following the market's close. If the script generates an Alert, you would place a corresponding order the next day. On the other hand, when trading intraday, you must place the order immediately after receiving an Alert. When coding a trading system in WealthScript the only requirement to generate an Alert is to pass bar + 1 to one of the trading signals 45 such that the signal occurs on a bar beyond the last bar in the chart. During the final iteration of the trading loop, when bar is assigned the value Bars.Count - 1, a trading signal such as BuyAtMarket(bar + 1); will create an alert since the bar number passed to the signal is greater than Bars.Count - 1.
Warning! Never pass bar + n, where n > 1 to a trading signal. Never. Always pass bar + 1 to trading signals, with the single exception being that trades can be created on the current bar for AtClose orders (See Alerts for AtClose Orders 65 ). We've already made use of Alerts for Creating a Screener. For more examples, review some of the pre-existing Strategies that come with the Wealth-Lab installation. While you should use bar + 1 in trading signals to create a trade condition for the next bar, you should not attempt to access data on the next bar. That would be an obvious peeking 53 error and would result in a script runtime when processing the chart's last bar.
/* Must be used in Raw Profit mode for proper Alert Shares (size) */ public void SaveAlerts(string path) { string dateFormat = "yyyyMMdd"; if (Bars.IsIntraday) dateFormat = "yyyyMMdd HHmm"; if( Alerts.Count > 0 ) {
65
for( int i = 0; i < Alerts.Count; i++ ) { Alert a = Alerts[i]; string s = a.AlertDate.ToString(dateFormat); s = s + ";" + a.AlertType.ToString(); s = s + ";" + a.Shares.ToString(); // always 1 in Portfolio Simulation Mode s = s + ";" + a.Symbol; s = s + ";" + a.OrderType.ToString(); if (a.OrderType == OrderType.Limit || a.OrderType == OrderType.Stop) s = s + ";" + a.Price.ToString(); else s = s + ";" + a.BasisPrice.ToString(); s = s + ";" + a.SignalName + "\n"; System.IO.TextWriter tw = new System.IO.StreamWriter(path, true); tw.Write(s); tw.Close(); } } } protected override void Execute() { // Generate 5 Alerts, each 1% lower than the previous int bar = Bars.Count - 1; int pct = 99; for(int numAlerts = 1; numAlerts <= 5; numAlerts++) BuyAtLimit(bar + 1, Close[bar] * (pct - numAlerts)/100, numAlerts.ToString()); // Call the SaveAlerts method after the main trading loop SaveAlerts(@"C:\Alerts.txt"); }
protected override void Execute() { SMA smaFast = new SMA(Close, 8, "Fast"); SMA smaSlow = new SMA(Close, 16, "Slow"); PlotSeries(PricePane, smaFast, Color.Green, LineStyle.Solid, 1); PlotSeries(PricePane, smaSlow, Color.Red, LineStyle.Solid, 1); for(int bar = 16; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; if (CrossUnder(bar, smaFast, smaSlow)) { /* Exit condition is true. If last bar is current bar, then Alert AtMarket */ if (bar == Bars.Count - 1) SellAtMarket(bar+1, p, "Alert"); else SellAtClose(bar, p, "AtClose");
66
On intraday scales, the only difference between AtClose and AtMarket orders is 1 trade. Since intraday AtClose signals cannot be realized by any practical means, they should rarely - if ever - be used in intraday Strategies; AtMarket signals are preferred. An exception would be to close intraday Positions at the end of the trading day in the same manner as described above.
Fundamental Analysis
67
Fundamental Analysis
You may have used company fundamentals to whittle down a list of trading candidates, but how about using time-series fundamental and economic data in a trading strategy combined with technical analysis? Fundamental Data Analysis is fully integrated in WealthScript using a set of functions to access fundamental data collections for Fidelity DataSets.
Tip: Click the More Info.. link at the bottom of the Fundamental Data dialog to launch the Fundamental Data and Economic Indicator Definitions Guide.
68
The availability of corporate fundamental data has been observed to lag quarterly reports by several weeks. Since it's possible that an update will include only a partial report (e.g., missing Balance Sheet items), Wealth-Lab refreshes corporate fundamental data for up to 6 weeks after the report date to ensure the entire most-recent report is gathered when it becomes available. See also: Plotting Fundamental Items
31
Fundamental Analysis
69
8.1
Balance Sheet items provide "as is" current totals. Cash Flow items
"operating activities" "cash"
Corporate items
"adjustment factor" /* updates as required */ "common shares used to calculate eps diluted" /* updates annually */ "common shares outstanding" "cash dividends" "dividend" /* per share, split-adjusted */ "employee" "fiscal quarter" "fiscal year" "split" /* updates as required */
Estimated Earnings
"estimated earnings"
See Analyst Upgrades Downgrades Data in the Fundamental Data and Economic Indicator Definitions Guide (Help menu) for more information.
70
Security Sentiment
"net insider transactions", "insider buy", or "insider sell" "shares short" "days to cover" "short interest as a % of shares outstanding"
Short interest data points are updated twice a month (semi-monthly) in accordance with SEC requirements. Create Strategies with the Insider and Short Interest sentiment items by employing rules
from the General Fundamental folder in the Strategy Builder. You can also drag, drop, and push Short Interest items into a Chart or Strategy Window.
Fundamental Analysis
71
8.2
Tip: If you don't need to access its data in a script, the quickest way to plot an item is to drag it from the Fundamental Data dialog (Ctrl+U) and drop it in a chart window.
72
8.3
Market sentiment symbols are not tradeable, but you can access their data in your trading strategies in the normal way using GetExternalSymbol or SetContext. Note that advancing, declining, and unchanged issues have an associated Volume series representing the corresponding share volume. The following example will clarify (run on any symbol in the Dow 30, for example): Example (How to run Example code? C#
3
protected override void Execute() { //Access the NYSE advancing issues and their volume for a trading system Bars advN = GetExternalSymbol(".MB_ADV.N", true );
//Now you can use advN.Close and advN.Volume as references to series containing advancing issues/volume ChartPane cp = CreatePane(40, true, true); PlotSeries(cp, advN.Close, Color.Blue, LineStyle.Histogram, 2); PlotSeries(VolumePane, advN.Volume, Color.Green, LineStyle.Solid, 2); }
More examples of how to access and use these data can be found in the Strategy Builder as "Market Sentiment Conditions". Note, however, that the Strategy Builder use methods from the WealthLab.Rules component, which hides the complexity of specifying the exact market sentiment symbol as in the example above. Instead, the WealthLab.Rules methods require you, for example, to specify the exchange for the Arms Index - the Strategy Builder are programmed to use the corresponding symbol.
Fundamental Analysis
73
8.4
GICS Data
Download and keep Global Industry Classification Standard (GICS) Industry data updated using the DataSource Manager (Ctrl+M) to Create a New [Fidelity] DataSet; it's not required to complete creating a new DataSet to update GICS data. Choose the option for Industry Classification groups and finally click the Update Industry Data button. Industry data is stored in the Gics.xml data in your Wealth-Lab Data folder. The GICS database provides an 8-digit code that classifies every security traded, and the code is divided into four sections (2 digits each): Sector Industry Group Industry Sub-Industry
This structure provides a hierarchy that can be used as a basis for analyzing securities in various industries. The Gics static class in the {}WealthLab.DataProviders namespace provides the necessary functions to easily extract GICS data for any symbol in the database. For a complete map of the GICS structure, refer to the Standard & Poor's website.
Members of WealthLab.DataProviders.Gics
public static string DataPath { set; get; } Wealth-Lab sets the DataPath to the Gics.xml file and you can retrieve this property in your scripts. The DataPath is used internally for the other members in this group, so there's no need for you to use this property in your scripts. public static string GetIndustry(string symbol) Returns the 6-digit GICS industry code of the specified symbol as a string. Pass the result of GetIndustry to GetDesc to return a textual description of a GICS Industry. public static string GetIndustryGroup(string symbol) Returns the 4-digit GICS (Global Industry Classification Standard) industry group code of the specified Symbol as a string. Pass the result of GetIndustryGroup to GetDesc to return a textual description of a GICS Industry Group. public static string GetSubIndustry(string symbol) Returns the 8-digit GICS (Global Industry Classification Standard) sub-industry code of the specified Symbol as a string. Pass the result of GetSubIndustry to GetDesc to return a textual description of a GICS Sub-Industry. public static string GetSector(string symbol) Returns the 2-digit GICS (Global Industry Classification Standard) sector code of the specified Symbol as a string. Pass the result of GetSector to GetDesc to return a textual description of a GICS Sector. public static string GetDesc(string gicsCode) Returns a textual description of the specified gicsCode. Use the result of any of the functions above as the gicsCode parameter: GetSector, GetIndustryGroup, GetIndustry, or GetSubIndustry.
74
If the symbol or gicsCode parameter does not correspond to a valid description, a runtime exception occurs with the message: "The given key was not present in the dictionary". Example (How to run Example code? C#
using System; using WealthLab; using WealthLab.DataProviders;
3
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { ClearDebug(); string sym = Bars.Symbol; try { string sector = Gics.GetSector(sym); string industryGroup = Gics.GetIndustryGroup(sym); string industry = Gics.GetIndustry(sym); string subIndustry = Gics.GetSubIndustry(sym); // Print out the GICS codes PrintDebug( sym ); PrintDebug( Gics.DataPath ); PrintDebug( String.Format("{0} PrintDebug( String.Format("{0} PrintDebug( String.Format("{0} PrintDebug( String.Format("{0} } catch (Exception e) { PrintDebug( e.Message ); } } } }
= = = =
sector, Gics.GetDesc( sector ) ) ); industryGroup, Gics.GetDesc( industryGroup ) ) ); industry, Gics.GetDesc( industry ) ) ); subIndustry, Gics.GetDesc( subIndustry ) ) );
75
protected override void Execute() { // Returns the chart bar interval and data scale BarDataScale ds = Bars.DataScale; if( ds.BarInterval == 0 ) PrintDebug( ds.Scale ); else PrintDebug( ds.BarInterval + "-" + ds.Scale ); }
76
9.1
Intraday/Intraday
To access data from a higher time frame in an intraday chart, use SetScaleCompressed() method with the required target period. After you're finished operating in the higher time frame, call the RestoreScale() method. The next step is to return a new synchronized DataSeries or Bars object which is implemented by calling the Synchronize() method.
77
protected override void Execute() { // Strategy requires intraday data if( Bars.BarInterval == 0 ){ DrawLabel(PricePane, "Strategy requires intraday data", Color.Red); return; } // Enough data to create a 60-bar 120-minute indicator? int barsRequired = 120 * (60 + 1) / Bars.BarInterval; if (Bars.Count < barsRequired){ DrawLabel(PricePane, "Strategy requires " + barsRequired.ToString() + " bars, minimum", Color.Red return; } // Get 20- and 60-bar SMAs in original scale DataSeries SMA20 = SMA.Series( Close, 20 ); DataSeries SMA60 = SMA.Series( Close, 60 ); //Switch to 120-minute scale SetScaleCompressed( 120 ); //Obtain 20-and 60-bar 120-minute SMAs DataSeries SMA20_120 = SMA.Series( Close, 20 ); DataSeries SMA60_120 = SMA.Series( Close, 60 ); //Switch back to the original time frame RestoreScale(); SMA20_120 = Synchronize( SMA20_120 ); SMA60_120 = Synchronize( SMA60_120 ); //Plot the SMAs PlotSeries( PricePane, PlotSeries( PricePane, PlotSeries( PricePane, PlotSeries( PricePane, SMA20, Color.DarkRed, WealthLab.LineStyle.Solid, 2 SMA60, Color.DarkGreen, WealthLab.LineStyle.Solid, SMA20_120, Color.Red, WealthLab.LineStyle.Solid, 2 SMA60_120, Color.Green, WealthLab.LineStyle.Solid, ); 2 ); ); 2 );
// Highlight crossovers and crossunders for(int bar = barsRequired; bar < Bars.Count; bar++) { if( CrossOver( bar, SMA20, SMA60 ) ) AnnotateChart( PricePane, Bars.DataScale + " Crossover", bar, SMA20[bar], Color.Red ); if( CrossOver( bar, SMA20_120, SMA60_120 ) ) AnnotateChart( PricePane, "120 Min Crossover", bar, SMA20_120[bar], Color.Green); if( CrossUnder( bar, SMA20, SMA60 ) ) AnnotateChart( PricePane, Bars.DataScale + " Crossunder", bar, SMA20[bar], Color.Red ); if( CrossUnder( bar, SMA20_120, SMA60_120 ) ) AnnotateChart( PricePane, "120 Min Crossunder", bar, SMA20_120[bar], Color.Green); } }
78
9.2
Intraday/Daily
How to: Access Daily Data from an Intraday Chart
The example demonstrates how to calculate the so called "Floor Trader Pivots", also known as daily support and resistance lines. These series use intraday data compressed to the Daily scale. As always, when working with data in multiple timeframes, plotting and trading must be done on the chart's base scale. Delayed Synchronization In this example, the Daily data are delayed 12 by one bar in the intraday timeframe so that the current intraday bar, especially the final bar for each day, always synchronizes with the previous day's data. Otherwise new pivots for the current day would be displayed on the last bar in a Streaming chart. Wealth-Lab Version 4 customers will recognize this as "Option 3" synchronization. Example (How to run Example code? C#
3
protected override void Execute() { const int NumTicks = 2; // Number of ticks past the pivot SetScaleDaily(); DataSeries hPivot = AveragePriceC.Series(Bars); DataSeries hDayHigh = High; DataSeries hDayClose = Close; DataSeries hDayLow = Low; RestoreScale(); hPivot = Synchronize( hPivot ) >> 1; hDayHigh = Synchronize( hDayHigh ) >> 1; hDayClose = Synchronize( hDayClose ) >> 1; hDayLow = Synchronize( hDayLow ) >> 1; hPivot.Description = "Pivot"; DataSeries hR1 = ( 2 * hPivot - hDayLow ); hR1.Description = "R1"; DataSeries hS1 = ( 2 * hPivot - hDayHigh ); hS1.Description = "S1"; DataSeries hR2 = ( hPivot - ( hS1 - hR1 ) ); hR2.Description = "R2"; DataSeries hS2 = ( hPivot - ( hR1 - hS1 ) ); hS2.Description = "S2"; HideVolume(); PlotSeries( PricePane, PlotSeries( PricePane, PlotSeries( PricePane, PlotSeries( PricePane, PlotSeries( PricePane, hR2, Color.Maroon, LineStyle.Dotted, 1); hR1, Color.Olive, LineStyle.Solid, 1 ); hPivot, Color.Blue, LineStyle.Solid, 1 ); hS1, Color.Fuchsia, LineStyle.Dotted, 1 ); hS2, Color.Red, LineStyle.Solid, 1 );
// Breakout value to be added/subtracted from resistance/support double boVal = Bars.SymbolInfo.Tick * NumTicks; // Find the StartBar of the 2nd day int StartBar = 0; int cnt = 0; for(int bar = 0; bar < Bars.Count; bar++) { if( Bars.IntradayBarNumber(bar) == 0 ) cnt++; if( cnt == 2 ) { StartBar = bar; break; } } for(int bar = StartBar; bar < Bars.Count; bar++) { // Don't trade on the first 2 bars of the day, 0 and 1 if( Bars.IntradayBarNumber(bar) < 2 ) continue;
79
if (IsLastPositionActive) { Position p = LastPosition; if( p.PositionType == PositionType.Long ) { if( !SellAtStop(bar + 1, p, hS1[bar], "StopLoss" ) ) SellAtLimit(bar + 1, p, hR2[bar], "PftTgt" ); } else if( !CoverAtStop(bar + 1, p, hR1[bar], "StopLoss" ) ) CoverAtLimit(bar + 1, p, hS2[bar], "PftTgt" ); } else if( Bars.IsLastBarOfDay( bar ) == false ) { bool NewPositionEntered = false; if( Close[bar] < hR1[bar] ) NewPositionEntered = BuyAtStop(bar + 1, hR1[bar] + boVal) != null; if( !NewPositionEntered ) if( Close[bar] > hS1[bar] ) ShortAtStop(bar + 1, hS1[bar] - boVal); } } }
for(int bar = 1; bar < Bars.Count; bar++) { if ( Bars.IsLastBarOfDay(bar) || (bar == Bars.Count-1) ) { int o = bar - Bars.IntradayBarNumber( bar ); // first bar of the day double p1 = Open[o]; double p2 = Close[bar]; double p3 = iDay.High[bar]; double p4 = iDay.Low[bar]; double[] rectangle = { o, p1, o, p2, bar, p2, bar, p1 }; // counter-clockwise
int avgBar = bar - Bars.IntradayBarNumber(bar)/2; if( p2 > p1 ){ DrawPolygon( PricePane, Color.Silver, Color.Empty, WealthLab.LineStyle.Invisible, 2, true, DrawLine( PricePane, avgBar, p4, avgBar, p1, Color.Silver, WealthLab.LineStyle.Solid, 3 ); DrawLine( PricePane, avgBar, p3, avgBar, p2, Color.Silver, WealthLab.LineStyle.Solid, 3 ); } else { DrawPolygon( PricePane, Color.Silver, Color.Silver, WealthLab.LineStyle.Invisible, 2, true, DrawLine( PricePane, avgBar, p3, avgBar, p4, Color.Silver, WealthLab.LineStyle.Solid, 3 ); } } } }
80
9.3
Intraday/Weekly,Monthly
Intraday can be scaled to Daily or even Weekly and Monthly scales - provided that a sufficient amount of data exists in the chart's intraday base time frame. For example, you need over 8,000 1-Minute bars for a single Monthly bar.
protected override void Execute() { // Search for weekly support/resistance levels on intraday charts // Support/resistance is the moving average of lows/highs if( Bars.Scale != BarScale.Minute ) return; SetScaleWeekly(); DataSeries WeeklySup = SMA.Series( Bars.Low, 5 ); DataSeries WeeklyRes = SMA.Series( Bars.High, 5 ); RestoreScale(); WeeklySup = Synchronize( WeeklySup ); WeeklySup.Description = "Weekly Support"; WeeklyRes = Synchronize( WeeklyRes ); WeeklyRes.Description = "Weekly Resistance"; HideVolume(); for(int bar = 0; bar < Bars.Count; bar++) SetBackgroundColor( bar, Color.LightSkyBlue ); PlotSeriesFillBand(PricePane, WeeklySup, WeeklyRes, Color.Black, Color.White, LineStyle.Solid, 2); }
81
9.4
Daily/Weekly
How to: Access Weekly Indicator Series from a Daily Chart
To illustrate the concept, the following strategy buys when Daily MACD crosses over zero, and sells when Weekly MACD crosses under zero. Example (How to run Example code? C#
3
protected override void Execute() { /* Long position is opened when Daily MACD crosses over zero The position is closed when Weekly MACD crosses below zero */ if( Bars.Scale != BarScale.Daily ) return; SetScaleWeekly(); DataSeries WeeklyMACD = MACD.Series( Bars.Close ); RestoreScale(); WeeklyMACD = Synchronize( WeeklyMACD ); WeeklyMACD.Description = "Weekly MACD"; for(int bar = 60; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if( CrossUnder( bar, WeeklyMACD, 0 ) ) SellAtMarket( bar+1, LastPosition, "Weekly MACD xu 0" ); } else { if( CrossOver( bar, MACD.Series( Bars.Close ), 0 ) ) BuyAtMarket( bar+1, "Daily MACD xo 0" ); } } HideVolume(); ChartPane WeeklyMACDPane = CreatePane( 30, false, true ); DrawHorzLine( WeeklyMACDPane, 0, Color.Red, WealthLab.LineStyle.Solid, 2 ); PlotSeries( WeeklyMACDPane, WeeklyMACD, Color.Blue, WealthLab.LineStyle.Histogram, 2 ); }
82
9.5
Daily/Monthly
How to: Access Monthly Indicator Series from a Daily Chart
To access Weekly and Monthly data from a strategy working on Daily data, call the SetScaleWeekly() and SetScaleMonthly() methods respectively. After you're finished operating in the higher time frame, call the RestoreScale() method. The next step is to return a new synchronized DataSeries or Bars object which is achieved by calling the Synchronize() method. Here is a sample strategy that utilizes the three time frames: Daily, Weekly and Monthly in order to arrive at trading decision. If both the Weekly and Monthly CCI indicators are greater than 100, and the Daily CCI crosses over 100, an order is generated to enter next bar at market. This long position is exited when Daily CCI crosses under -200. Example (How to run Example code? C#
using using using using using using System; System.Collections.Generic; System.Text; System.Drawing; WealthLab; WealthLab.Indicators;
3
namespace WealthLab.Strategies { public class CCItriplex : WealthScript { private StrategyParameter cciPeriod; // parameter for period public CCItriplex() { cciPeriod = CreateParameter("CCI Period", 10, 10, 60, 5 ); } protected override void Execute() { int Period = cciPeriod.ValueInt; if( Bars.Scale != BarScale.Daily ) return; DataSeries cciD = CCI.Series( Bars, Period ); SetScaleWeekly(); DataSeries cciW = CCI.Series( Bars, Period ); RestoreScale(); SetScaleMonthly(); DataSeries cciM = CCI.Series( Bars, Period ); RestoreScale(); cciW = Synchronize( cciW ); cciW.Description = "Weekly CCI"; cciM = Synchronize( cciM ); cciM.Description = "Monthly CCI"; ChartPane cciPane = CreatePane( 50, true, true ); PlotSeries( cciPane, cciD, Color.Green, WealthLab.LineStyle.Solid, 1 ); PlotSeries( cciPane, cciW, Color.Blue, WealthLab.LineStyle.Solid, 1 ); PlotSeries( cciPane, cciM, Color.Red, WealthLab.LineStyle.Solid, 1 ); for(int bar = Period; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if( CrossUnder( bar, cciD, -200 ) ) SellAtMarket( bar+1, LastPosition ); } else if( ( cciW[bar] >= 100 ) && ( cciM[bar] >= 100 ) && CrossOver( bar, cciD, 100 ) ) BuyAtMarket( bar+1 ); } } } }
83
10
In WealthScript, you can gain access to the current Chart Style utilizing the ChartStyle property and casting it as the underlying Chart Style in use. For example, the Kagi Basic Strategy accesses the instance of a KagiChartStyle with the following line of code:
KagiChartStyle kagiCS = (KagiChartStyle)ChartStyle;
In the current version of Wealth-Lab Pro (6.2 and up), however, the Trending Chart Styles and scripts now both have access to and can use the following classes:
TKagi TRenko TLineBreak TPnF
In other words, when you view a Kagi Chart, the KagiChartStyle creates a TKagi object. Likewise, when you want to create a script that uses Kagi methods, you will create its own TKagi object in your WealthScript code. The object the you create in a script can be completely different based on the Bars object (which can be from any symbol) and properties that you use to instantiate TKagi. This arrangement gives you maximum flexibility since you do not even need to employ the visual Chart Style whose objects your script references! Strategies create (instantiate) their own Chart Style objects and are therefore disconnected from the properties that you set for a Chart Style. For example, if you use a 1% reversal in your Kagi chart, your code that uses Kagi methods should also use 1% so that the signals are coordinated with the chart.
84
10.1
Members of WealthLab.ChartStyles.Trending.Column
public int Bar { get; } For Kagi, Line Break, and Renko, new Column objects are added for each bar in the chart, which is reflected in this value. However, for Point and Figure, new PnF Columns are created only when one of its property values change. For backtesting, since the bar number is always known, you'll normally pass the current bar to a Columns collection object to access the current state of a column; consequently it's usually never necessary to access this property. public int Col { get; } Col is the column number of the object. Just as bars are numbered from 0 to Bars.Count - 1, Trending ChartStyle columns are numbered from 0 to Columns.Count - 1. public bool DirectionUp { get; } The current Chart Style trend direction as of the specified bar. If true, the trend is bullish, and if false the trend is bearish. public double High { get; } The high of the column as of the specified Bar. Note that the most-recent closing price can always be accessed directly from the Bars object. For Point & Figure log method, this is the natural log value of price. Convert to the the arithmetic price using Math.Pow(value, Math.E). public double Low { get; } The low of the column as of the specified Bar. Note that the most-recent closing price can always be accessed directly from the Bars object. For Renko and Line Break charts the low/high of a column refers to the low and high of 1 or more bricks/lines for a particular bar. The Low and High Column properties continue to report the low/high of the last bricks/lines drawn. If no new brick/line for a particular bar, then Low and High are the most-recent value that the last brick/line drawn.
85
For Point & Figure log method, this is the natural log value of price. Convert to the the arithmetic price using Math.Pow(value, Math.E). public bool Plotted { set; get; } Indicates true if the Chart Style column object should be plotted on the specified bar and must be reset for the following bar (new object required). The plotted property is read/write for Kagi and PnF styles which need to set plotted retroactively after a Column object has been added to the collection. public bool Reversed { get; } Indicates true if the Chart Style trend direction reversed on the specified bar. Note that even if the next Bar does not change the chart after a reverse, the reversed property will toggle to false since a reverse will occur only for one bar in a column. public double ReverseLevel { get; } The price at which the column will reverse (in the future). Can be used as a stop for the next bar, for example. For Point & Figure log method, this is the natural log value of price. Convert to the the arithmetic price using Math.Pow(value, Math.E). public double Volume { get; } The cumulative volume of the column as of the specified Bar. For Renko charts, the volume is cumulative for the current trend.
10.1.1 Kagi
Members of WealthLab.ChartStyles.Trending.Kagi
The following members are unique to the Kagi Chart Style. They complete the properties for a Kagi Column.
public bool IsBullish { get; } IsBullish is not the same as DirectionUp. A Kagi chart IsBullish when the thick yang line is being drawn. This condition can and does continue when DirectionUp is false for some columns. public double YangLevel { get; } The level at which the Kagi chart turns bullish. This is the level of the most-recent "shoulder" (peak). public double YinLevel { get; } The level at which the Kagi chart turns bearish. This is the level of the most-recent "waist" (trough).
10.1.1.1 TKagi The TKagi class serves up the object used by KagiChartStyle, and, you can create your own script-based TKagi objects without the requirement to actually use the Kagi Chart Style. Furthermore, you can create multiple instances of TKagi using not only different settings, but also for external symbols by passing their Bars object.
86
Members of WealthLab.ChartStyles.Trending.TKagi
public enum KagiReverseType { Points, Percent, ATR }
The Kagi reversal method that determines how to interpret the reversalAmount parameter in the TKagi constructor (below). public TKagi(WealthLab.Bars bars, KagiReverseType kagiReverseType, double reversalAmount, int kagiATRPeriod) The TKagi contructor. Pass a Bars object, a KagiReverseType enumeration, the reversalAmount, and an integer for kagiATRPeriod, the ATR period, which is used only when kagiReverseType = KagiReverseType.ATR. public IDictionary<int, PnF> Columns; Columns holds the collection of Kagi Column objects, indexed by bar number.
For an example, use the ChartScript Explorer's Download feature to download the Kagi Basic [Rev A] Strategy to your Trend Following Strategy folder.
10.1.2 Renko
Members of WealthLab.ChartStyles.Trending.Renko
The following members are unique to the Renko Chart Style. They complete the properties for a Renko Column.
public int BricksInTrend { get; } The number of total bricks in the current trend, which can be thought of as a horizontally-extended column.
public double NextBrick { get; } The price level at which the next brick will be drawn in the current trend.
10.1.2.1 TRenko The TRenko class serves up the object used by RenkoChartStyle, and, you can create your own script-based TRenko objects without the requirement to actually use the Renko Chart Style. Furthermore, you can create multiple instances of TRenko using not only different settings, but also for external symbols by passing their Bars object.
Members of WealthLab.ChartStyles.Trending.TRenko
public TRenko(WealthLab.Bars bars, double rkoAmount) The TRenko contructor. Pass a Bars object and the rkoAmount. public IDictionary<int, PnF> Columns; Columns holds the collection of Renko Column objects, indexed by bar number.
For an example, use the ChartScript Explorer's Download feature to download the Renko Basic [Rev A] Strategy to your Trend Following
87
Strategy folder.
For an example, use the ChartScript Explorer's Download feature to download the Line Break Basic [Rev A] Strategy to your Trend Following Strategy folder.
10.1.3.1 TLineBreak The TLineBreak class serves up the object used by LineBreakChartStyle, and, you can create your own script-based TLineBreak objects without the requirement to actually use the LineBreak Chart Style. Furthermore, you can create multiple instances of TLineBreak using not only different settings, but also for external symbols by passing their Bars object.
Members of WealthLab.ChartStyles.Trending.TLineBreak
public TLineBreak(WealthLab.Bars bars, int lines) The TLineBreak contructor. Pass a Bars object and the number of lines to break, 2 or greater. public IDictionary<int, PnF> Columns; Columns holds the collection of LineBreak Column objects, indexed by bar number.
For an example, use the ChartScript Explorer's Download feature to download the Line Break Basic [Rev A] Strategy to your Trend Following Strategy folder.
88
if, say, if price is trending in the X direction, reverses for one O only, and then resumes the X trend. This field remains true from the time the condition is confirmed until the next reversal. public bool OneStepBackProbationary { get; } Returns true if the current state of the column at Bar is a probationary one-step-back condition (1-box charts only). The condition is probationary while only one box is drawn on a reversal.
88
, TrendLine Class
89
10.1.4.1 TPnF Class The TPnF class serves up objects used by PnFChartStyle, and, you can create your own script-based TPnF objects without the requirement to actually use the Point and Figure Chart Style. Furthermore, you can create multiple instances of TPnF using not only different settings, but also for external symbols.
Members of WealthLab.ChartStyles.Trending.TPnF
public TPnF(Bars bars, ControlPrice priceField, double boxSize, int reversalBoxes, bool logMethod) The TPnF contructor. Pass a Bars object, a ControlPrice enum (below), the box size, the number of reversal boxes, and true to use the logarithmic method. When logMethod = true boxSize is interpreted as a percentage (otherwise as points), and, boxSize cannot be smaller than 0.01, otherwise 0.01% is used, i.e., Ln(1 + 0.01/100) public enum ControlPrice { Close, HighLow } The price field use to generate the Point and Figure chart. public ControlPrice PriceField public int ReversalBoxes public bool LogMethod These properties are loaded with the values that you pass to the TPnF constructor, and consequently it is unlikely that you'll need to access them during script execution. Note that after creating TPnF, explicitly writing new values will have no effect on the Column objects. public double BoxSize Like the properties above, you pass the BoxSize to the TPnF constructor. However in the case of the log method, the BoxSize property returns the "actual" log value box size. For example, if you pass 2.0 as boxSize, the BoxSize property will return Ln(1 + 2.0/100) = 0.0198026272961797. public IDictionary<int, PnF> Columns; Columns holds the collection of PnF Column objects, indexed by bar number. public IDictionary<int, int> ReversalBars; The ReversalBars collection allows you to obtain the bar number at which the reversal occurred for a specified column number. (key, value = columnNumber, bar) public List<PnFTrendLine> TrendLines; The TrendLines list contains all Trendline information. Currently, TPnF finds 45 lines only. Note that 45 trendlines can be projected immediately following each reversal. See: TrendLine Class 89 public int[] DblTopDblBottom Returns a value > 0 if the last Point and Figure signal was a double-top buy (value < 0 double-bottom sell). The value is 2 on the bar that triggers a double-top buy and -2 for a double-bottom sell trigger. Thereafter, the value is 1 or -1 indicating the direction of the last signal. The signal is not dependent on the prevailing
89
trend. A double-top (bottom) signal occurs when there is one more box of X's (O's) in the current column than O's (X's) in the previous column. This indicator facilitates the creation of the Bullish Percent Index (BPI), but is not valid until the first buy or sell signal. public void PaintContributorBars(WealthScript ws, Color XBarColor, Color OBarColor, Color NullBarColor ) Sets the colors of the charted bars that contribute to creating new Point and Figure boxes in a regular OHLC/Candlestick chart. Pass the X, O, and Null (non-contributor) colors. This method is not valid for secondary symbols. public int Boxes(double high, double low) Returns the number of boxes between the high and low values [of a column]. public double RoundToBox(double x) Returns the box price corresponding to the box closest the price passed as the parameter. This is used for fine-rounding after a calculation near a box price. public double CeilingToBox(double x) Returns the box price corresponding to the box above the price passed as the parameter. public double FloorToBox(double x) Returns the box price corresponding to the box below the price passed as the parameter.
For example use, see the Point and Figure Basic Strategy found in your Wealth-Lab Pro installation's Trend Following* Strategy folder.
* The Strategy may be located in the Newly Installed Strategies folder
10.1.4.2 TrendLine Class The TrendLine Class is used to store the properties of trendlines in any chart. TPnF builds PnFTrendLines, which are optionally displayed by the Point and Figure ChartStyle. You can use the trendline information in your PnF Strategies. See the TrendLines collection in the TPnF class 88 .
Members of WealthLab.ChartStyles.Trending.TrendLine
public TrendLine(Bars bars, int bar1, double price1, int bar2, double price2, int idBar) TrendLine constructor. public int Bar1 = -1; public double Price1 = 0d; Bar1 and Price1 are bar and price that defines the start of the trend. public int Bar2 = -1; public double Price2 = 0d; Bar2 and Price2 are the second defining bar of a general trendline. For 45 deg PnF trendlines, IdBar and Bar2 will equal the StartBar. public int EndBar = -1; EndBar is the bar at which trendline ends (used for drawing). If EndBar = Bars.Count - 1, then the trend defined by the trendline is currently intact. public int IdBar = -1; IdBar is the bar at which a trendline is confirmed/detected. For a PnFTrendLine, IdBar equals Bar1 since
90
these special type of trendlines can be drawn immediately following a PnF reversa. See PnFTrendLine below.
90
public int Accel = 0; Accel is the number of "accelerated" trendlines that are redrawn after a primary/underlying trend is identified. For PnF, the Accel represents the trendline number in the same direction as an underlying trend; e.g., if a second +45 trendline is drawn at a higher level, its Accel value is 1. public bool IsRising = true; When a trendline is instantiated, IsRising is set to true if Price2 > Price1.
Members of WealthLab.ChartStyles.Trending.PnFTrendLine
PnFTrendLine inherits from TrendLine; some of the
public PnFTrendLine(Bars bars, int bar1, double price1, int bar2, double price2, int idBar, int startCol, PnFAngle ang, bool risingTrendLine) PnFTrendLine constructor. public enum PnFAngle { a27, a34, a45, a56, a63 } Enumerations for predefined trendline angles. Currently, only 45 PnFTrendLines are found by TPnF. public PnFAngle Angle = PnFAngle.a45; The angle of the PnFTrendLine. public int StartColumn = -1; public int EndColumn = -1; The starting and ending columns of a PnFTrendLine in a Point and Figure Chart. EndColumn = -1 if the trend defined by a PnFTrendLine is still intact. For simplicity (at least initially) TPnF considers the end of a trend when the line "touches" a Point and Figure box. Another, perhaps more popular, interpretation of PnF trendlines is that the trend does not end until a double top/bottom signal occurs near the crossing. public int Reversals = 0; This field increments if a reversal occurs precisely at the value of the existing trend, and, in this case a new line is not added to the TrendLines collection.
WealthLab.Rules Classes
91
11
WealthLab.Rules Classes
A number of classes found in the WealthLab.Rules{} and WealthLab.Rules. CandleSticks{} namespaces support the implementation of fundamental-based and other rules. The classes are not actually necessary to create the rules, but their use provides indirection, which hides the implementation details helping to keep your strategy code saner. Of course, programmers can access any of the public methods without resorting to the Strategy Builder.
92
11.1
ArmsIndex
Members of WealthLab.Rules.ArmsIndex
Applies to: Fidelity Wealth-Lab Pro The ArmsIndex class simplifies accessing and plotting a market's Arms Index (TRIN). The Market Sentiment data for NYSE, Nasdaq, and AMEX are found in the Market Sentiment DataSet 72 .
public ArmsIndex(WealthLab.WealthScript ws, string exchange, double level) Constructor.
ws
An instance of the WealthScript class, i.e., this exchang NYSE, Nasdaq, or AMEX e level Specifies the level at which to draw a horizontal line in the plotted pane
public WealthLab.DataSeries ArmsIndexSeries { get; } Arms Index DataSeries property. The plot functions plot the Arms Index DataSeries, automatically creating a ChartPane, if required. public void plotArmsIndexSeries() public void plotArmsIndexSeries(WealthLab.ChartPane paneSTI) public void plotArmsIndexSeries(int height, bool abovePrice, bool showGrid)
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { ArmsIndex ad = new ArmsIndex(this, "NYSE", 5 ); DataSeries dsSTI = ad.ArmsIndexSeries; ad.plotArmsIndexSeries(); } } }
WealthLab.Rules Classes
93
11.2
Candlesticks
Member of WealthLab.Rules.Candlesticks.CandlePattern
The CandlePattern methods, found in the WealthLab.Rules.Candlesticks{} namespace, greatly simplify identifying candlestick patterns for charting and for strategy rules. All methods have the same parameter list as shown in the following typical syntax:
public static void ReversalLongLeggedDoji(WealthLab.WealthScript w, string annotateText, bool highlight, out bool[] arr)
w An instance of the WealthScript class, i.e., this annotateT Text to annotate the candlestick pattern. For no annotation pass a blank string, i.e., "". ext highlight Pass true for a Fuchsia highlight around the last bar of the pattern and false for no highlight. arr Declare a bool array to pass to the method. The array returned is sized to Bars.Count and indicates true for bars on which the candle pattern is detected. (See example.) Being a static class, you must qualify methods by using the type name CandlePattern instead of using an instance reference (see example). Note that many patterns use a 10-period ATR indicator in their detection logic, and for this reason you should ignore patterns detected in approximately the first 25 bars of a chart. The following is an example of a complete trading strategy based on two bullish and two bearish candlestick patterns. The strategy buys when either bullish pattern is detected and sells when a bearish pattern is found. Example (How to run Example code? C#
using System; using WealthLab; using WealthLab.Rules.Candlesticks;
3
namespace WealthLab.Strategies { public class CandlePatternStrategy : WealthScript { protected override void Execute() { bool[] bullishThreeWhiteSoldiers; CandlePattern.BullishThreeWhiteSoldiers(this, "+3White Soldiers", true, out bullishThreeWhiteS bool[] bullishInvertedHammer; CandlePattern.BullishInvertedHammer(this, "+Inverted Hammer", true, out bullishInvertedHammer) bool[] bearishThreeBlackCrows; CandlePattern.BearishThreeBlackCrows(this, "-3Black Crows", true, out bearishThreeBlackCrows); bool[] bearishTriStar; CandlePattern.BearishTriStar(this, "-Tri Star", true, out bearishTriStar); for(int bar = 25; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; if (bearishThreeBlackCrows[bar] || bearishTriStar[bar]) SellAtMarket(bar + 1, p); } else { if (bullishThreeWhiteSoldiers[bar] || bullishInvertedHammer[bar])
94
BuyAtMarket(bar + 1); } } } } }
Clarification for terminology used in the Candlestick definitions in the following topics: White A candle whose close price is greater than the open price. Line/Candle Black A candle whose closing price is less than the open price. Line/Candle stable at bar 25 A 10-period Average True Range (ATR) is used to determine relative candle length for many of the patterns. ATR indicator values are unstable until approximately 2.5 times the period, or 25 bars.
11.2.1 Bearish
Bearish Candlestick Method Names
Typical syntax and example: Candlesticks
93
BearishBeltHold (2-bar pattern, stable at bar 25) A significant gap up occurs but prices are heading downwards only. After a significant gap up causes large profit-taking. Expect lower prices. BearishDarkCloudCover (2-bar pattern, stable at bar 25) The first day is a long white day, the second day is a black day which opens above the first day's high and closes within the first day but below the midpoint. Although a gap-up indicates a uptrend, prices are heading downwards and probabilities are high that prices continue to fall. BearishDojiStar (2-bar pattern, stable at bar 25) The first day is a long white day. The second day is a doji star that gaps above the first day. A star indicates a reversal and a doji indicates indecision. This pattern usually indicates a reversal following an indecisive period. You should wait for a confirmation before trading a doji star. BearishEngulfingLines (2-bar pattern, stable at bar 25) A small white line (first day) is engulfed by a large black line (second day). This pattern is strongly bearish if it occurs after a significant up-trend (i.e., it acts as a reversal pattern). BearishEveningStar (3-bar pattern, stable at bar 25) The first day is a long white day, the second day gaps above the first day's close and the third day is a long black day. The "star" indicates a possible reversal and the black line confirms this. The star can be empty or filled-in. This is a bearish pattern signifying a potential top. BearishHangingMan (2-bar pattern, stable at bar 25) A small real body (i.e., a small range between the open and closing prices) and a long lower shadow (i.e., the low was significantly lower than the open, high, and close). This pattern is bearish if it occurs after a significant uptrend, but detection requires only a minor uptrend measured by a 5-period SMA. BearishHarami (2-bar pattern, stable at bar 25) A line with a small black body (second day) falls within the area of a larger white body (first day). This pattern indicates a decrease in momentum.
2011 FMR LLC All rights reserved.
WealthLab.Rules Classes
95
BearishHaramiCross (2-bar pattern, stable at bar 25) First day is a long white day and the second day is a Doji day that is engulfed by the first day's body. It is similar to a Harami, except the second line is a Doji (signifying indecision). This pattern indicates a decrease in momentum. BearishKicking (2-bar pattern, stable at bar 25) A white marubozu followed by a black marubozu where the black marubozu gaps below the 1st day's open. The gap between the 2nd and 3rd day is a resistance area, a downtrend is expected. BearishLongBlackLine (1-bar pattern, stable at bar 25) Prices open near the high and close significantly lower near the period's low. This is a bearish line. BearishSeparatingLines (2-bar pattern) The 1st day is a long white day followed by a black day that opens at the opening price of the 1st day. Although the 1st day prices are heading upwards, the second day shows significant downturn with an insignificant upper shadow. The downtrend should continue. BearishShootingStar (2-bar pattern, stable at bar 25) Price gap up to the upside with a small real body. The star's body must appear near the low price and the line should have a long upper shadow. This pattern suggests a minor reversal when it appears after a uptrend. BearishSideBySideWhiteLines (3-bar pattern) A black day followed by a gap down of two almost identical white days. No trend-reversal occurs on the 2nd and 3rd day, thus the downtrend is still intact. BearishThreeBlackCrows (3-bar pattern, stable at bar 25) Three black days with lower closes each day where each day opens lower but within the body of the previous day. Strong down-trend indicates a continuation of falling prices. BearishThreeInsideDown (3-bar pattern, stable at bar 25) The first two days are a Bearish Harami pattern followed by a black day that closes below the second day. The 3rd day is the confirmation of the Bearish Harami pattern. BearishThreeOutsideDown (3-bar pattern, stable at bar 25) The first two days are a Bearish Engulfing pattern followed by a black day that closes below the second day. The third day is the confirmation of the Bearish Engulfing pattern. BearishTriStar (3-bar pattern, stable at bar 25) Three Dojis where the second Doji gaps above the 1st and the third. High indecision during three days suggests that the trend is about to change. This pattern appears very seldom on securities with high volume.
11.2.2 Bullish
Bullish Candlestick Method Names
Typical syntax and example: Candlesticks
93
BullishBeltHold (2-bar pattern) A significant gap down occurs but prices are heading upwards only. After a significant down gap causes a short-covering and strong buying. Expect higher prices. BullishDojiStar (2-bar pattern, stable at bar 25) First day is a long black day, the second day is a Doji day that gaps below
2011 FMR LLC All rights reserved.
96
the first day. A "star" indicates a reversal and a Doji indicates indecision. This pattern usually indicates a reversal following an indecisive period. You should wait for a confirmation before trading a Doji star. BullishEngulfingLines (2-bar pattern, stable at bar 25) A small black line (first day) is engulfed by a large white line (second day). This pattern is strongly bullish if it occurs after a significant downtrend (i.e., it acts as a reversal pattern). BullishHammer (1-bar pattern, stable at bar 25) A small real body (i.e., a small range between the open and closing prices) and a long lower shadow (i.e., the low was significantly lower than the open, high, and close). Detection requires at least a minor downtrend measured by a 5-period SMA. BullishHarami (2-bar pattern, stable at bar 25) A line with a small white body (second day) falls within the area of a larger black body (first day). This pattern indicates a decrease in momentum. BullishHaramiCross (2-bar pattern, stable at bar 25) First day is a long black day and the second day is a Doji day that is engulfed by the first day's body. It is similar to a Harami, except the second line is a Doji (signifying indecision). This pattern indicates a decrease in momentum. BullishInvertedHammer (2-bar pattern, stable at bar 25) Price gap down to the downside with a small real body. The star's body must appear near the low price and the line should have a long upper shadow. This pattern suggests a minor reversal when it appears after a downtrend. BullishKicking (2-bar pattern, stable at bar 25) A black marubozu followed by a white marubozu where the white marubozu gaps up the 1st day's open. The gap between the 2nd and 3rd day is a support area, an uptrend is expected. BullishLongWhiteLine (1-bar pattern, stable at bar 25) The prices open near the low and close significantly lower near the period's high. This is a bullish line. BullishMorningStar (3-bar pattern, stable at bar 25) The first day is a long black day, the second day gaps below the first day's close and the third day is a long white day. The "star" indicates a possible reversal and the white line confirms this. This is a bullish pattern signifying a potential bottom. BullishPiercingLine (2-bar pattern, stable at bar 25) The first day is a long black day, the second day is a white day which opens below the first day's low and closes within the first day but above the midpoint. This is a bullish pattern and the opposite of a Dark Cloud Cover. BullishSeparatingLines (2-bar pattern) The 1st day is a long black day followed by a white day that opens at the opening price of the 1st day. Although the 1st day prices are heading downwards, the second day shows significant upturn with an insignificant lower shadow. The uptrend should continue. BullishSideBySideWhiteLines (3-bar pattern) A white day followed by a gap up of two almost identical white days. No trend-reversal occurs on the 2nd and 3rd day, thus the uptrend is still intact. BullishThreeInsideUp (3-bar pattern, stable at bar 25) The first two days are a Bullish Harami pattern followed by a white day that closes above the second day. This is usually the confirmation of the Bullish Harami pattern.
WealthLab.Rules Classes
97
BullishThreeOutsideUp (3-bar pattern, stable at bar 25) The first two days are a Bullish Engulfing pattern followed by a white day that closes above the second day. The third day is the confirmation of the Bullish Engulfing pattern. BullishThreeWhiteSoldiers (3-bar pattern, stable at bar 25) Three white days with higher closes each day and where each day opens above but within the body of the previous day. This pattern represents strong uptrend. BullishTriStar (3-bar pattern, stable at bar 25) Three Dojis where the second Doji gaps below the 1st and the third. High indecision during three days suggests that the trend is about to change. This pattern appears very seldom on securities with high volume.
NeutralDoji (1-bar pattern, stable at bar 25) The price opens and closes at the same price. This pattern implies indecision. Double doji lines (two adjacent doji lines) imply that a forceful move will follow a breakout from the current indecision. NeutralMarubozu (1-bar pattern, stable at bar 25) A candlestick that has no upper and lower shadows. NeutralSpinningTop (1-bar pattern) The distance between the high and low, and the distance between the open and close, are relatively small.
ReversalDragonflyDoji (1-bar pattern) The open and close are the same, and the low is significantly lower than the open, high, and closing prices. This pattern signifies a turning point. ReversalGravestoneDoji (1-bar pattern) The open, close, and low are the same, and the high is significantly higher than the open, low, and closing prices. This pattern signifies a turning point. ReversalLongLeggedDoji (1-bar pattern, stable at bar 25) The open and close are the same, and the range between the high and low is relatively large. This pattern often signifies a turning point.
98
11.3
DateRules
Members of WealthLab.Rules.DateRules
DateRules functions are static members whose syntax is self-descriptive.
public static bool IsLastTradingDayOfMonth(System.DateTime dt) public static bool IsLastTradingDayOfQuarter(System.DateTime dt)
// Highlight the last trading day of the month or quarter using System; using System.Drawing; using WealthLab; using WealthLab.Rules; namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DateRules dr = new DateRules(); for(int bar = 1; bar < Bars.Count; bar++) { if( DateRules.IsLastTradingDayOfQuarter(Date[bar]) ) SetBackgroundColor(bar, Color.LightSalmon); else if( DateRules.IsLastTradingDayOfMonth(Date[bar]) ) SetBackgroundColor(bar, Color.LightCyan); } } } }
WealthLab.Rules Classes
99
11.4
DataSeriesOp
Members of WealthLab.Rules.DataSeriesOp
DataSeriesOp contains a single function, SplitReverseFactor, that populates a DataSeries ds with the reverse split adjustment factor. Multiply prices or divide volume by the DataSeries returned to obtain a DataSeries that indicates price (or volume) at the actual levels traded historically, i.e., not split-adjusted. This method is used in the Price (or Volume) Action rules specified for '(backtest)'.
public static void SplitReverseFactor(WealthLab.WealthScript ws, string splitItem, out WealthLab.DataSeries ds)
ws An instance of the WealthScript class, i.e., this splitItem Source of split data: Use "split" for Fidelity, "Split (Yahoo! Finance)", or "Split (MSN)" ds The reverse split factor DataSeries Example (How to run Example code? C#
using using using using System; System.Drawing; WealthLab; WealthLab.Rules;
3
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries reverseAdjustment; DataSeriesOp.SplitReverseFactor( this, "Split (Yahoo! Finance)", out reverseAdjustment); // Reverse adjust the Close and Volume DataSeries DataSeries closeAdjusted = Close * reverseAdjustment; DataSeries volAdjusted = Volume / reverseAdjustment; PlotSeries(PricePane, closeAdjusted, Color.Blue, WealthLab.LineStyle.Solid, 1); PlotSeries(VolumePane, volAdjusted, Color.Blue, WealthLab.LineStyle.Solid, 1); } } }
100
11.5
FundamentalsDateRules
Members of WealthLab.Rules.FundamentalsDateRules
public FundamentalDateRules(WealthLab.WealthScript ws, WealthLab.Bars bars, string symbol, string itemName) Constructor. public bool DaysSinceFundamentalItem(int bar, out int days) public bool DaysToFundamentalItem(int bar, out int days) DaysSince/DaysTo functions indicate calendar days item (not number of bars) since/to the previous/next fundamental item specified. If the item is not found, the functions return false and days is assigned 0.
// Annotate every 5th bar with the days since/to the previous/next item specified. using System; using System.Drawing; using WealthLab; using WealthLab.Rules; namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { int daysTo = 0; int daysSince = 0; Font font = new Font("Arial", 8, FontStyle.Regular); FundamentalDateRules fdr = new FundamentalDateRules(this, Bars, Bars.Symbol, "dividend"); for(int bar = 1; bar < Bars.Count; bar++) { if( bar % 5 == 0 ) { if( fdr.DaysToFundamentalItem(bar, out daysTo) ) AnnotateBar(daysTo.ToString(), bar, true, Color.Black, Color.Transparent, font); if( fdr.DaysSinceFundamentalItem(bar, out daysSince) ) AnnotateBar(daysSince.ToString(), bar, false, Color.Black, Color.Transparent, font); } } } } }
WealthLab.Rules Classes
101
11.6
FundamentalsRatio
Members of WealthLab.Rules.FundamentalsRatio
Applies to: Fidelity Wealth-Lab Pro The following methods are available in the FundamentalsRatio() static class. Being a static class, you must qualify the methods using the type name FundamentalsRatio instead of using an instance reference (see examples). Each method takes a WealthScript instance, which you pass as this in Strategy code. For external (secondary) symbols, call SetContext() prior to using one of these functions, and RestoreContext() after. ttm and mrq are Trailing 12 Months and Most-Recent Quarter, respectively.
public static System.DateTime BarFundamentalItemDate(WealthLab.WealthScript _ws, string _name, int _bar) Returns the date of the most-recent fundamental item (_name parameter) with respect to the specified _bar.
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { for(int bar = 0; bar < Bars.Count; bar++) if( FundamentalsRatio.BarFundamentalItemDate(this, "assets", bar) == Date[bar] ) SetBackgroundColor(bar, Color.LightSalmon); } } }
public static System.DateTime FundamentalItemDate(WealthLab.WealthScript _ws, string _name) public static System.DateTime FundamentalItemDate(WealthLab.WealthScript _ws, string _name, int _cnt) Returns the date of the first fundamental item specified by the _name parameter or the date of the item identified by _cnt in the FundamentalDataItems list.
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { const string item = "assets"; ClearDebug(); PrintDebug("First date: " + FundamentalsRatio.FundamentalItemDate(this, item)); PrintDebug("All dates:");
102
IList<FundamentalItem> fList = FundamentalDataItems(item); for(int j = 0; j < fList.Count; j++) PrintDebug(FundamentalsRatio.FundamentalItemDate(this, item, j)); } } }
public static WealthLab.DataSeries ConsecutiveGrowthSeries(WealthLab.WealthScript ws, string _name, int _periods, double _target, bool _above, bool _annual) Returns a series with the number of consecutive quarters whose growth is above the specified _target growth rate percentage, which can be a positive or negative number. Growth is relative to the the _periods parameter, which represents quarter over quarter growth (annual = false) or fiscal year over fiscal year ( annual = true). Recommended Fundamentals items for the_name parameter are "cash" (Cash Flow), "net income", and "sales turnover".
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries dsCPG = FundamentalsRatio.ConsecutiveGrowthSeries( this, "sales turnover", 1, 12, t dsCPG.Description = "Consecutive years of Sales Growth above 12%"; ChartPane paneCPG = CreatePane(50, true, false); PlotSeries(paneCPG, dsCPG, Color.Purple, LineStyle.Histogram, 50); } } }
public static WealthLab.DataSeries GrowthRateSeries(WealthLab.WealthScript ws, string itemName, int periods, bool annual) Returns the growth rate expressed as percentage growth (e.g. 10 = 10%) with respect to the specified number (periods) of years ago (annual=true) or quarters ago (annual=false). Recommended values for itemName: "cash" (Cash Flow), "net income", and "sales turnover".
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries dsRatio = FundamentalsRatio.GrowthRateSeries( this, "sales turnover", 1, true ); dsRatio.Description = "Sales Growth Rate % over 1 year"; ChartPane paneRatio = CreatePane(50, true, false); PlotSeries(paneRatio, dsRatio, Color.Purple, LineStyle.Histogram, 50); } } }
public static string ConvertName(string name) Internallly this is used to convert itemName parameters to a corresponding Fundamental item string. If not found, the name passed is returned. public static WealthLab.DataSeries DilutedSharesSeries(WealthLab.WealthScript ws)
2011 FMR LLC All rights reserved.
WealthLab.Rules Classes
103
Number of shares (millions) used to calculate diluted eps on an annual basis, adjusted for splits. public static WealthLab.DataSeries ShareSeries(WealthLab.WealthScript ws) Common shares outstanding (millions) on a ttm basis, adjusted for splits. public static WealthLab.DataSeries MarketCapSeries(WealthLab.WealthScript ws) Market Capitalization (millions of dollars) for the trailing 12 months (ttm) is calculated by multiplying the company's stock price by the number of common shares outstanding. For example: 12,345.67 = 12 billion, 345 million, 67 thousand dollars public static WealthLab.DataSeries ReturnOnCapitalSeries(WealthLab.WealthScript ws) Return on Capital (percentage) for the trailing 12 months (ttm). Inventories, Receivables, Liabilities and Assets are aggregated and averaged for the previous 4 quarters. public static WealthLab.DataSeries SalesPerEmployeeSeries(WealthLab.WealthScript ws) Sales per employee is an aggregate measure (ttm) in thousands of dollars.
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries spe = FundamentalsRatio.SalesPerEmployeeSeries( this ); spe.Description = "Sales per Employee (ttm)"; ChartPane spePane = CreatePane(50, true, true); PlotSeries(spePane, spe, Color.Purple, LineStyle.Histogram, 50); } } }
public static WealthLab.DataSeries EnterpriseValueSeries(WealthLab.WealthScript ws) Enterprise Value is not calculated correctly due to the use of Cash Flow cash instead of Balance Sheet cash, the latter of which is currently not an available fundamental data item. public static WealthLab.DataSeries FundamentalSeries(WealthLab.WealthScript ws, string itemName, bool annual) Returns the Fundamental DataSeries for the itemName synchronized with the chart. Pass false to annual for mrq (raw) data values. Pass true to annual for annualized values. For balance sheet items like "assets", "liabilities", "long term debt", etc. the annualized value is the average of the reports for the trailing twelve months (ttm). Income statement items like "net income", "interest expense", "sales turnover", etc. are summed for the ttm value.
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { // A Balance Sheet item DataSeries assets_mrq = FundamentalsRatio.FundamentalSeries(this, "assets", false);
104
DataSeries assets_ttm = FundamentalsRatio.FundamentalSeries(this, "assets", true); assets_mrq.Description = "Assets (mrq)"; assets_ttm.Description = "Assets (ttm)"; ChartPane assetsPane = CreatePane(50, true, true); PlotSeries(assetsPane, assets_ttm, Color.Green, LineStyle.Solid, 2); PlotSeries(assetsPane, assets_mrq, Color.Purple, LineStyle.Histogram, 50); // An Income Statement item DataSeries sales_mrq = FundamentalsRatio.FundamentalSeries(this, "net income", false); DataSeries sales_ttm = FundamentalsRatio.FundamentalSeries(this, "net income", true); sales_mrq.Description = "Sales (mrq)"; sales_ttm.Description = "Sales (ttm)"; ChartPane salesPane = CreatePane(50, true, true); PlotSeries(salesPane, sales_ttm, Color.Green, LineStyle.Solid, 2); PlotSeries(salesPane, sales_mrq, Color.Purple, LineStyle.Histogram, 50); } } }
public static WealthLab.DataSeries PerEmployeeSeries(WealthLab.WealthScript ws, string itemName) Returns a ttm itemName/Employee ratio series. public static WealthLab.DataSeries PerShareSeries(WealthLab.WealthScript ws, string itemName, bool annual) A Fundamental Item per Share for the trailing twelve months (annual = true) or most-recent quarter (annual = false). Possible itemName values: "net income", "assets", "sales turnover", "stockholder equity", "total inventories", "total receivables". Enter the value in dollars per share; e.g. 20 = $20/share public static WealthLab.DataSeries PriceToRatioSeries(WealthLab.WealthScript ws, string itemName) Price/Fundamental ratio annualized for the trailing 12 months (ttm). Valid fundamentals: "cash" (Cash Flow), "net income", and "sales turnover". public static WealthLab.DataSeries RatioSeries(WealthLab.WealthScript ws, string itemName1, string itemName2, bool annual) Returns a the ttm (annual = true) or mrq (annual = false) ratio series between itemName1 and itemName2. public static WealthLab.DataSeries ReturnOnSeries(WealthLab.WealthScript ws, string itemName, bool annual) itemName: "stockholder equity" or "assets" returns a Return on equity or assets series, respectively. Pass true to the annual parameter for a trailing twelve months (ttm) or false for most-recent quarter (mrq) return-on data.
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries dsRatio = FundamentalsRatio.ReturnOnSeries( this, "stockholder equity", false ); dsRatio.Description = "Return on Equity (mrq)"; ChartPane paneRatio = CreatePane(50, true, false); PlotSeries(paneRatio, dsRatio, Color.Purple, LineStyle.Histogram, 50); } } }
public static WealthLab.DataSeries YieldSeries(WealthLab.WealthScript ws, string itemName) itemName: "cash dividends" or "net income" returns a dividend or earnings yield series, respectively.
WealthLab.Rules Classes
105
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { DataSeries divYield = FundamentalsRatio.YieldSeries(this, "cash dividends"); divYield.Description = "Dividend Yield (ttm)"; ChartPane paneRatio = CreatePane(50, true, false); PlotSeries(paneRatio, divYield, Color.Green, LineStyle.Histogram, 50); } } }
106
11.7
OptionExpiryDate
Members of WealthLab.Rules.OptionExpiryDate
The OptionExpiryDate function names and syntax are self-descriptive.
public OptionExpiryDate(WealthLab.Bars bars) OptionExpiryDate Class constructor. public int DaysSinceOptionExpiryDate(int bar) public int DaysSinceTripleWitchingDate(int bar) public int DaysToOptionExpiryDate(int bar) public int DaysToTripleWitchingDate(int bar) DaysSince and DaysTo functions indicate calendar days since/to expiry dates (not number of bars). public System.DateTime NextOptionExpiryDate(int year, int month) public System.DateTime NextTripleWitchingDate(int year, int month)
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { OptionExpiryDate oed = new OptionExpiryDate(Bars); for (int y = 2008; y <= 2010; y++) for (int m = 1; m <= 12; m++) { DateTime dt = oed.NextOptionExpiryDate(y, m); PrintDebug(dt.ToShortDateString()); } } } }
WealthLab.Rules Classes
107
11.8
NewHighLow
Members of WealthLab.Rules.NewHighLow
Applies to: Fidelity Wealth-Lab Pro The NewHighLow class simplifies accessing a market's new highs-to-lows ratio as well as the convergence and divergence set of Market Sentiment rules. The Market Sentiment data for NYSE, Nasdaq, and AMEX are found in the Market Sentiment DataSet 72 .
public NewHighLow(WealthLab.WealthScript ws, string exchange, string index, int period, int smaPeriod)
ws
exchang NYSE, Nasdaq, or AMEX e index period .NYA (NYSE Composite), .IXIC (Nasdaq Composite), or .XAX (AMEX Composite) Momentum lookback period
smaPeri Moving Average period. The average of the Momentum series is used to detect od convergence/divergence.
public string Exchange { set; } The Exchange ("NYSE", "Nasdaq", or "AMEX") determines which New High/Low data are used from the Market Sentiment DataSet 72 . public string Index { set; } The Composite Index symbol (".NYA", ".IXIC", or ".XAX") used for the convergence/divergence detection. public int Period { set; } The momentum lookback period. public int SMAPeriod { set; } The simple moving average period used to average the momentum series. public double Threshold { set; } The plotNewHighLowRatio method plots a dotted horizontal line at the Threshold value. The value is not used in any calculations. The following group of functions return the series indicated by the function name. The source of the ratio and index are specified when instantiating the class or after changing one of the properties above (Exchange, Index, etc.). public public public public public WealthLab.DataSeries WealthLab.DataSeries WealthLab.DataSeries WealthLab.DataSeries WealthLab.DataSeries MomentumIndexSeries { get; } MomentumRatioSeries { get; } MomentumSMAIndexSeries { get; } MomentumSMARatioSeries { get; } NewHighLowRatioSeries { get; }
The following group of functions create the series indicated by the function name. It's not required to explicitly call these functions. Instead, they are implicitly created by the previous set of functions that return a Wealth-Lab DataSeries. public public public public public public bool bool bool bool bool bool createMomentumIndexSeries() createMomentumRatioSeries() createMomentumSMAIndexSeries() createMomentumSMARatioSeries() createNewHighLowRatioSeries() createNewHighSeries()
108
public bool createNewLowSeries() The plot functions plot the indicated DataSeries, automatically creating a ChartPane, if required. public void plotIndex() public void plotIndex(WealthLab.ChartPane paneIndex) public void plotIndex(int height, bool abovePrice, bool showGrid) public void plotMomentumRatioToIndex() public void plotMomentumRatioToIndex(WealthLab.ChartPane paneMomentum) public void plotMomentumRatioToIndex(int height, bool abovePrice, bool showGrid) public void plotMomentumSMA() public void plotMomentumSMA(WealthLab.ChartPane paneMomentumSMA) public void plotMomentumSMA(int height, bool abovePrice, bool showGrid) public void plotNewHighLowRatio() public void plotNewHighLowRatio(WealthLab.ChartPane paneRatio) public void plotNewHighLowRatio(int height, bool abovePrice, bool showGrid) public void plotNewHighs() public void plotNewHighs(WealthLab.ChartPane paneNH) public void plotNewHighs(int height, bool abovePrice, bool showGrid) public void plotNewLows() public void plotNewLows(WealthLab.ChartPane paneNL) public void plotNewLows(int height, bool abovePrice, bool showGrid)
Simply specify the exchange of the desired Market Sentiment data to easily plot the Highs, Lows, and ratio series as in the following example. Example (How to run Example code? C#
using using using using System; WealthLab; WealthLab.Indicators; WealthLab.Rules;
3
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { NewHighLow nhl = new NewHighLow(this, "Nasdaq", "", 0, 0 ); nhl.Threshold = 5; DataSeries dsNewHighLowRatio = nhl.NewHighLowRatioSeries; nhl.plotNewLows(); nhl.plotNewHighs(); nhl.plotNewHighLowRatio(); } } }
In the following example, the Strategy buys when the NYSE High/Low ratio >= 10 and sells on bearish convergence with the NYSE Composite Index. Example (How to run Example code? C#
using System; using WealthLab; using WealthLab.Rules;
3
WealthLab.Rules Classes
109
namespace WealthLab.Strategies { public class MyStrategy : WealthScript { protected override void Execute() { NewHighLow nhl = new NewHighLow(this, "NYSE", ".NYA", 5, 10 ); nhl.Threshold = 10; DataSeries dsNewHighLowRatio = nhl.NewHighLowRatioSeries; DataSeries dsMomentumSMAIndex = nhl.MomentumSMAIndexSeries; DataSeries dsMomentumSMARatio = nhl.MomentumSMARatioSeries; nhl.plotNewLows(); nhl.plotNewHighs(); nhl.plotNewHighLowRatio(); nhl.plotMomentumSMA(); nhl.plotIndex(); for(int bar = 11; bar < Bars.Count; bar++) { if (IsLastPositionActive) { Position p = LastPosition; if(( dsMomentumSMAIndex[bar] < 0 )&&( dsMomentumSMARatio[bar] < 0 )) SellAtMarket(bar + 1, p); } else { if( dsNewHighLowRatio[bar] >= 10 ) BuyAtMarket(bar + 1); } } } } }
110
12
Techniques
This chapter will be dedicated to widely-used testing techniques and how to accomplish them with WealthScript. Check back for more content with each successive Wealth-Lab Pro software upgrade.
Techniques
111
12.1
Creating a Screener
A Screen, or Filter, is the simplest form of a Strategy that identifies one or more symbols that currently meet a specified criteria; that is, using the most recently-available completed bar. You can do some extremely sophisticated screening with Wealth-Lab, much more elaborate than most other software packages. The key is writing a "Screening Script" and then running it in a Strategy Window in Multi-Symbol Backtest mode. Generally, we think of screens as an end-of-day activity, but there's no reason you can't use Wealth-Lab's Strategy Monitor to generate Alerts for DataSets screening using intraday data.
The bottom line is that if you want the symbol to appear in the screen's results, issue a BuyAtMarket at bar number Bars.Count (How to count bars in a chart 4 ).
An Example Screen
The following example will implement a screen based on the following concepts: Prices are above the 200 day simple moving average Avoid price shocks, largest 5 day loss within 200 days < 20% 20 day average Volume must be above 100,000 Avoid stocks that are short-term overbought, 10 day RSI must be below 50 Example (How to run Example code? 3 ) Save this as a new Strategy called "Example Screen" in a new folder called "Screens", where you can store all of your future screening scripts.
protected override void Execute() { int bar = Bars.Count - 1; // the last chart bar number if (bar < 199) return; // chart must have at least 200 bars to continue if ( Close[bar] > SMA.Value(bar, Close, 200) && Lowest.Value(bar, ROC.Series(Close, 5), 200) > -20 && SMA.Value(bar, Volume, 20) > 100000 && RSI.Series(Close, 10)[bar] < 50 ) BuyAtMarket(bar + 1); // Note: bar + 1 = Bars.Count - 1 + 1 = Bars.Count }
The example above uses familiar technical indicators, but you can apply fundamental criteria 67 as well.
112
Demand. Click the "Go" or the "Backtest" buttons to start the screening process. The results of the scan (if any) will appear in the Alerts view. You can also examine the individual charts by double clicking on a symbol. For a more automated daily scanning operation, add screening Strategies to the Strategy Monitor. See the User Guide for details.
The signalName shows us the 10-period RSI of all of the Alerts generated. By clicking the "Signal Name" column header, it's easy to order the Alerts so that you can pick the trades with the highest or lowest value. Related: How to Convert Strategy to a Screen
47
Techniques
113
12.2
114
12.3
Symbol Rotation
Symbol Rotation strategies are those that seek to remain highly exposed to the market (fully or near-fully invested) by choosing the best candidates from a given DataSet of symbols. Rotation strategies contain all code necessary to create indicators and trades for each symbol in the current DataSet. Run these strategies on a single symbol only not in Multi-Backtest mode. See the "RSI Rotation" and "Dogs of the Dow" Strategies included with the installation. Note for Wealth-Lab Pro Version 4 legacy customers: Multi-Symbol Backtest mode does not kick out of the implied symbol loop even if trades are created after a call to SetContext().
Techniques
115
12.4
public Position MostProbableAtStop(int TradeBar, double BuyStopPrice, double ShortStopPrice) { if (TradeBar > Bars.Count - 1) // Alert both, return null { ShortAtStop(TradeBar, ShortStopPrice); BuyAtStop(TradeBar, BuyStopPrice); return null; } if (Open[TradeBar] - Low[TradeBar] < High[TradeBar] - Open[TradeBar]) { if (ShortAtStop(TradeBar, ShortStopPrice) != null) return LastPosition; else return BuyAtStop( TradeBar, BuyStopPrice); } else { if (BuyAtStop(TradeBar, BuyStopPrice) != null) return LastPosition; else return ShortAtStop(TradeBar, ShortStopPrice); } }
116
12.5
Techniques
117
12.6
Utility Scripts
Utility scripts are small programs that usually perform some supportive function for Strategy testing or trading - like checking reasonableness of historic data or perhaps exporting data to ASCII format for off-line analysis.
namespace WealthLab.Strategies { public class Detail { public string sym; public DateTime lastdt; public DateTime firstdt; public int nbars; } public class SymbolDetails : WealthScript { protected override void Execute() { ClearDebug(); List<Detail> details = new List<Detail>(); for(int n = 0; n < DataSetSymbols.Count; n++) { Detail detail = new Detail(); detail.sym = DataSetSymbols[n]; SetContext( detail.sym, false ); detail.lastdt = Bars.Date[Bars.Count - 1]; detail.firstdt = Bars.Date[0]; detail.nbars = Bars.Count; details.Add(detail); } RestoreContext();
// Sort by date (earliest dates printed first) details.Sort(delegate(Detail d1, Detail d2) { return d1.lastdt.CompareTo(d2.lastdt); }); details.ForEach(delegate(Detail d) { PrintDebug(d.sym + "\t" + d.nbars + "\t" + d.firstdt + "\ } } }
118
13
APIs
Wealth-Lab Pro Version 6 makes extensive use of components. In fact, all installed data adapters, chart styles, performance visualizers, etc. are component-based. Likewise, developers can integrate their own components seamlessly. Documentation and examples for the APIs are available at Fidelity.com: http://personal.fidelity.com/ products/trading/Trading_Platforms_Tools/wlp-under-the-hood.shtml
Index
119
Index
-1100% of Equity Sizing 113
DataSeries Abs 13 add, subtract, multiply, divide 12 delay 12 offset 12 shift 12 DateTime 8 DayOfWeek 8 divide (DataSeries) 12 division-by-zero (DataSeries) 12
-FFilter 111 Floor Trader Pivots Fundamental Data Deletion 67 Functions 67 Refresh 67 Retrieval 67 78
-CCandlesticks Bearish 95 Bullish 94 Neutral and Reversal 97 syntax 93 Chart Style Kagi 85 Line Break 87 Point and Figure 87 Renko 86 Chart Styles Trending 83 ChartPane create 22 class-scope variables 43 Compile Strategies 43 Custom Indicators 36
-HHeikin-Ashi 23 Hour 8 How to Run Example Code 3 How to (Bars) access OHLC/V values 6 access scale and interval 8 access symbol, company name access tick, margin, point 10 change OHLC/V series 6 Count total ~ in chart 4 date and time 8 Find bar at a specified date 5
10
120
WealthScript Programming Guide, Wealth-Lab Pro Add Strategy Parameters 58 AtClose Alerts 65 Convert to Screen 47 Create a trade 45 Create a trade on specified date 46 Create Screen 111 Good Til Canceled 50 Save Alerts to File 64 Test for Open Position 48 Time-based exit 51 Trigger Limit/Stop Orders 49 Use Position.AllPositions 62 How to (Time Frame) Access Higher Intraday Scale 76 Access Monthly from Daily 82 Access Weekly from Daily 81 Daily from Intraday 78 Determine Chart Scale 75 Overlay Daily on Intraday 79 How to Use ActivePositions 61 http://www2.wealth-lab.com/WL5Wiki/kbAccessIntern et.ashx 116
How to (Bars) Find first intraday bar number 4 plot OHLC/V series 6 How to (ChartPane) create a custom pane 22 hide Volume 22 How to (DataSeries) access secondary symbol data 17 add, subtract DataSeries 12 average price 12 change data 15 create series filled with zeroes 15 delay a DataSeries 12 multiply, divide DataSeries 12 offset a DataSeries 12 Return an absolute value ~ 13 typical price 12 value of data on a specific bar 14 How to (Draw Objects) draw Speed-Resistance Lines 30 draw/extend a trendline 29 highlight an area with a polygon 30 How to (Indicators) access an Indicator Series 34 calculate a single value 35 Create indicator from a secondary symbols' raw DataSeries 20 Elder Force 38 MACD with custom periods 37 NRTR_WATR Indicator 38 secondary symbol indicators 40 Semi-formal custom indicator 36 Spread 37 Weighted Close 37 How to (Plot Text) control a DataSeries plot label 27 write vertical text 27 How to (Plot) compare with Benchmark Symbol 23 External Symbol 23 Plot a DataSeries 25 Plot Stops 50 Secondary Symbol 23 Strategy metrics 25 Synthetic Symbol 23 volume above price 25 How to (Strategy) Access ActivePositions 62
-IIndicators Custom 36 Series method 34 Stability 41 Syntax 32 Value method 35 Internet 116 interval 8 Intraday AtClose 66
Index
121
-PParameters Strategy 58 Peeking Don't Access Future Data 53 Order of Trading Signals 53 Position Active Status 54 Trade on bar + 1 53 Valid ~ 55 What is ~? 53 PlotSeries 25 PlotSeriesDualFillBand 25 PlotSeriesFillBand 25 PlotSeriesOscillator. 25 PlotSymbol 23 PlotSyntheticSymbol 23 PnF 87 PnFTrendLine 90 Point and Figure 87 TPnF Class 88 TrendLine Class 89 point value 10 Positions List 48 Postdictive errors 53 Programming Trading Strategies 43
Series method 34 shift operator (DataSeries) 12 SP Strategies 48 spike detection 15 Stability of Indicators 41 Straps 58 Strategies Multiple-Position (MP) 61 Single-Position (SP) 48 Symbol Rotation 114 Strategy Parameters 58 Strategy Class 43 subtract (DataSeries) 12 Survivorship bias 54 Symbol External 17 Plot a secondary symbol 23 Secondary 17 Symbol Rotation Strategies 114 Synchronization Option 3 78 Secondary Symbols 18
-TTemplate Multiple-Position (MP) Strategy 61 Multi-Position (MP) Strategy 61 Single-Position (SP) Strategy 48 tick 10 trading loop 47 Trading Signals 45 TrendLine PnF 90 TrendLine Class 89 Typical Price 12
122