WLP Programming Guide
WLP Programming Guide
eReview #483859
Wealth-Lab Pro WealthScript Programming Guide
Any screenshots, charts or company trading symbols mentioned, are provided for illustrative purposes only and
should not be used or construed as an offer to sell, a solicitation of an offer to buy, or a recommendation for the
security.
Third party trademarks and service marks are the property of their respective owners.
While every precaution has been taken in the preparation of this document, the publisher and the author assume no
responsibility for errors or omissions, or for damages resulting from the use or misuse of information contained in
this document or from the use or misuse of programs and source code that may accompany it. In no event shall the
publisher and the author be liable for any loss of profit or any other commercial damage caused or alleged to have
been caused directly or indirectly by this document.
Table of Contents
Foreword 0
Part I Introduction 1
Part IV DataSeries 11
1 Series...................................................................................................................................
Operators 12
2 Accessing
...................................................................................................................................
a Single Value from a DataSeries 15
3 Filling...................................................................................................................................
a Custom DataSeries 16
4 Accessing
...................................................................................................................................
Secondary Symbols 18
Secondary Series
..........................................................................................................................................................
Synchronization 19
Part VI Indicators 34
1 Series...................................................................................................................................
Method 36
2 Static ...................................................................................................................................
Value Method 37
3 Custom
...................................................................................................................................
Indicators 38
4 Indicators
...................................................................................................................................
of Secondary Symbols 42
5 Stability
...................................................................................................................................
of Indicators 43
4 Peeking
................................................................................................................................... 57
Valid Peeking.......................................................................................................................................................... 59
5 Strategy
...................................................................................................................................
Parameters 62
6 Multi-Position
...................................................................................................................................
Strategies 65
7 Multi-Symbol
...................................................................................................................................
Strategies 67
8 Alerts ................................................................................................................................... 68
9 Options
...................................................................................................................................
Strategies 71
II
III WealthScript Programming Guide, Wealth-Lab Pro
7 OptionExpiryDate
................................................................................................................................... 119
8 NewHighLow
................................................................................................................................... 120
Index 133
1 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.
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.
Tip:
When search for a specific solution, try browsing the "How to" references in
the Index.
Partial: To focus on the essence of the example, most often only the Execute()
method is given. In this case, completely replace the Execute() method, which is
highlighted in the image below.
4. Pay attention to green comment lines in the sample code, which may include special
initialization or setup instructions.
5. Click Compile in the Editor's toolbar and ensure "Strategy compiled successfully!"
in the lower message frame.
6. Click F5 on the keyboard, or click a symbol of your choice in the Data Panel to
execute the example.
The Bars object represents a historical series of OHLC/V data and has the capability of
supporting additional, named data series such as open interest for futures. Conceptually,
you can think of Bars as a summary of a company or trading instrument. While in fact
Bars does hold lists of values for prices and volume with corresponding dates (a
company's price history), Bars also has the properties to access the Symbol under test,
its Security Name, and other information.
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? 3 )
C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
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? 3 )
C#
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));
}
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.
without actually saving the changes. One such application is to make futures 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 non-negative.
Example (How to run Example code? 3 )
C#
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;
}
}
}
The following link is a handy reference for displaying Custom DateTime Format Strings:
http://msdn2.microsoft.com/en-us/library/8kb3ddd4(VS.71).aspx
4 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 34 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.
Instead of creating average price yourself, just use the standard indicator,
AveragePrice instead.
Instead of creating typical price yourself, just use the standard indicator,
AveragePriceC instead.
/* 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);
We'll cover Technical Indicators 34 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 124 for 14-Period RSI values under 30.
Example (How to run Example code? 3 )
C#
protected override void Execute()
{
int bar = Bars.Count - 1;
if (RSI.Series(Close, 14)[bar] < 30)
BuyAtMarket(bar + 1);
}
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? 3 )
C#
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;
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 45 .
Example (How to run Example code? 3 )
C#
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);
BuyAtMarket(bar + 1, Bars.Symbol);
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 19 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.
Default synchronization aligns all external Bars and DataSeries to the Primary Bars by
comparing the date-time of each bar.
Any date times that are outside the range of the Primary Bars are eliminated from the
external series.
Dates that exist in the Primary Series but not in the external series are added to the
external series. The values are copied from the previous bar of the external series.
Dates that exist in the external series but not in the Primary Bars are eliminated from
the external series.
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 1 2 3 5 8 9 10 11 12 15 16
Value 1 2 3 4 5 6 7 8 9 10 11
Fortunately, Wealth-Lab allows you to cope with either scenario. First, here's the moving
average created after using the default synch behavior:
Date 1 2 3 5 8 9 10 11 12 15 16
Value 20 21 22 24 25 25 26 27 28 29 30
SMA5 22.4 23.4 24.4 25.4 26.2 27 28
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.
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 1 2 3 5 8 9 10 11 12 15 16
Value 20 21 22 24 25 25 26 27 28 29 30
SMA5 23 23 24 25 26 27 28
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.
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 23 .
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).
PlotWinRate();
}
public void PlotWinRate()
{
DataSeries winRate = new DataSeries(Bars, "Win Rate");
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);
}
On the other hand, by using the SetPaneMinMax() method, you can expand a pane's y-
axis scale so that it remains constant over a wider range of values. For example, if an
RSI is oscillating between 50 and 90 for a long period of time, it's likely that Wealth-Lab
will automatically rescale the y-axis for the visible chart between those values. But if
you're only concerned with the RSI when it crosses below 30, you could force the y-axis'
minimum scale to 30 or less to gain perspective on the current values.
Example
See SetPaneMinMax() in the QuickRef.
Tip: The plot label is given the same color assigned to the series plot.
Each of the following methods can be used in any ChartPane. See the QuickRef (F11) for
more details and an example for each.
DrawCircle() Drawing small circles are useful for highlighting key points or targets
in a chart.
DrawEllipse() For an ellipse, you need only to specify opposing corners of a the
imaginary rectangle that bounds the ellipse.
DrawLine() 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().
DrawHorzLine() A convenient method for drawing support and resistance lines at a
fixed value across the entire chart.
DrawPolygon() Provides a method to draw any n-sided polygon.
namespace WealthLab.Strategies
{
public class SpeedResistanceDemo : WealthScript
{
public void SpeedResistance(int bar1, double price1, int bar2, double
price2 )
{
// You can extend the lines drawn with this procedure using LineExtendY or
LineExtendYLog for semi-log charts
double delta = price1 - price2;
DrawLine(PricePane, bar1, price1, bar2, price2, Color.Red,
WealthLab.LineStyle.Solid, 2);
DrawLine(PricePane, bar1, price1, bar2, price2 + delta / 3, Color.Blue,
WealthLab.LineStyle.Dots, 1);
DrawLine(PricePane, bar1, price1, bar2, price2 + delta * 2 / 3,
Color.Blue, WealthLab.LineStyle.Dots, 1);
}
protected override void Execute()
{
// Find the latest 8% peak and trough and draw the S-R lines between them
int bar = Bars.Count - 1;
int b1 = (int)PeakBar.Value(bar, Close, 8.0,
WealthLab.Indicators.PeakTroughMode.Percent);
int b2 = (int)TroughBar.Value(bar, Close, 8.0,
WealthLab.Indicators.PeakTroughMode.Percent);
if (b2 > b1)
SpeedResistance( b1, Close[b1], b2, Close[b2] );
else
SpeedResistance( b2, Close[b2], b1, Close[b1] );
}
}
}
6 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.
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
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.
Method 1: Use the new operator to return an instance of the indicator derived from
DataSeries. In addition to being more "professional", Method 1 allows you
to provide a friendly string Description in the same call.
Method 2: Pre-.NET legacy Wealth-Lab customers may find comfort in the static
Series notation method to return a DataSeries type.
// 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);
}
If supported by the indicator, the Value method will accept the same parameters as the
indicator's Series method with the addition of an int parameter to specify a bar number.
The result is a type double value.
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.
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.
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
// create the same series "on the fly", starting with a DataSeries of
zeroes to fill
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];
Note the use of the offset operator 12 (>>) to shift the Close one bar to the right,
allowing the creation of a DataSeries that's the difference between today's and
yesterday's close. The result is multiplied by Volume - another 1-line indicator!
Example (How to run Example code? 3 )
C#
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);
}
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.
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.
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.
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.
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.
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
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
}
}
}
}
}
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.
namespace WealthLab.Strategies
{
public class StrategyTest : WealthScript
{
private int _scriptRunCountSinceCompile = 0;
Since Wealth-Lab always executes a Strategy's trades in Raw Profit mode, the first trade
in a series of entry triggers will always result in creating a Position. If that Position is later
rejected due to sizing rules or insufficient cash, when using single-Position logic it's not
possible for any of the other potential trade triggers to have occurred until the
programmed exit of the first trade because the entry logic would not be executed until
that time.
As a generic example, imagine a strategy whose single-Position entry logic buys a Position
on any Tuesday or Wednesday and whose exit logic sells on Friday. Raw profit processing
will always result in picking up the Position on Tuesday, which causes only the exit logic to
be processed until the Position is sold on Friday. The test to enter a Position on
Wednesday will never be executed - even if the simulated Position from Tuesday is
rejected.
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.
pos 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.
limitPrice or You must specify a limit/stop price for AtLimit/AtStop orders just as
stopPrice you would for live trading. Wealth-Lab AtStop orders function as
"stop market" orders. In other words, the stopPrice is the activation
price.
signalName Optional for all signals and is displayed in the Trades list's Signal
Name column, making it a useful for sorting trades.
ClearDebug();
PrintDebug("Positions.Count = " + Positions.Count);
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 43 .
You can actually use the Strategy Builder to create a simple screen. First, select the
Convert to Code-Based Strategy action in the Rules view. By default, the Strategy Builder
includes a for loop as shown above. To turn it into a Screen, you need only to replace
the "for" looping statement by assigning the variable bar to the last chart bar as shown:
Example (illustrative)
/* Strategy with for loop */
protected override void Execute()
{
for(int bar = 50; bar < Bars.Count; bar++) /* CHANGE THIS */
{
// Trading 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.
{
const int Period = 14;
PlotSeries(PricePane, SMA.Series(Close, Period), Color.Blue, LineStyle.Solid,
1);
The process is precisely the same when programming Strategies in Wealth-Lab: access
data as of the current bar, calculate stop or limit prices, and place the order to execute
on the next bar, bar + 1.
Going back to the previous example for Testing for an Open Position 51 , the entry logic
uses a BuyAtLimit order, which is placed on the next bar (bar + 1), but only if the Close
crosses over its moving average. The Strategy could have just as easily used a
BuyAtMarket order to guarantee taking on a Position after a cross over, but the limit
order specifies a maximum price that you're willing to pay. That price happens to be the
The next example is a simplified version of the Channel Breakout VT Strategy. Instead of
plotting the breakout series, using the PlotStops() method we can visually identify the
bars and prices where orders are placed, replaced, or canceled.
Example (How to run Example code? 3 )
C#
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();
the stop price. When you run the code, you'll notice that the stops are not plotted
exactly on the SMA line. That's because PlotStops() shows you the bar and price at
which a stop (or limit) order is active. The SMA is calculated and plotted on the current
bar, but the resulting value is used as the stop price for the next bar, i.e., bar + 1.
Example (How to run Example code? 3 )
C#
protected override void Execute()
{
const int Period = 14;
PlotSeries(PricePane, SMA.Series(Close, Period), Color.Blue, LineStyle.Solid,
1);
PlotStops();
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? 3 )
C#
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
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 57 ).
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.
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 59 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.
More often, subtle mistakes are made prior to the main loop by extracting some statistic
or indicator based on the most-recent value(s) and then using that information later in
the main loop 50 . In the example, we obtain the standard deviation of the entire Close
DataSeries, but then [incorrectly] use its value in the trading Strategy.
Example (Illustrating what not to do)
protected override void Execute()
{
double sd = StdDev.Value(Bars.Count - 1, Close, Bars.Count,
StdDevCalculation.Sample);
for(int bar = 20; bar < Bars.Count; bar++)
{
// Trading logic here CANNOT use sd
}
}
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 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.
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? 3 )
C#
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.
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? 3 )
C#
protected override void Execute()
{
bool hasSplit = false;
Method 1 (automatic)
From a Chart or Strategy window, drag and drop one or more indicators. To automatically
generate the plot code and sliders, simply click the button in the Function Toolbar.
Method 2 (semi-automatic)
From any Strategy window, click Optimize link in the lower left status bar. Use the
Optimizer's interface to Add New Parameters, adjust their default, start, end, and step
values, and finally Apply Changes to Code. In the code, access the value of
strategyParameterN using the Value or ValueInt property for floating point or integer
parameters, respectively.
Method 3 (manual)
Assume that you're working with a simple moving average (SMA) crossover Strategy and
want an easy way to change the SMA periods. Below we start with the basic (Strapless)
crossover Strategy.
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
protected override void Execute()
{
int fastPer = 20;
int slowPer = 55;
else
{
if (CrossOver(bar, smaFast, smaSlow))
BuyAtMarket(bar + 1);
}
}
}
}
}
namespace WealthLabCompile
Step 3 {
class MovingAverageCrossover :
Finally, convert the StrategyParameter WealthScript
types to their corresponding integer or {
private StrategyParameter slow;
double parameter inside the execute
private StrategyParameter fast;
method using the ValueInt or Value
properties, respectively. public MovingAverageCrossover()
{
For the final result of this process,
When working with multiple Positions, you typically have a secondary loop within your main
loop that processes each active Position to determine if it should be closed out. See
How to Use ActivePositions 66 . However, if the Strategy logic is such that all active
Positions are always closed simultaneously, use the Position.AllPositions shortcut 66
instead.
Programming notes:
Place exit logic above entry trading rules to prevent the exit logic from being applied
to Positions that are opened on the very same bar.
It's usually a mistake to use LastPosition logic (especially IsLastPositionActive)
in MP Strategies. You can unwittingly close the last Position, leaving Positions that
were opened earlier unprocessed.
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 51 . For this reason you
must be extra-careful to ensure that you don't write code that peeks 57 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? 3 )
C#
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];
}
}
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.
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.
7.8 Alerts
Trading signals are used in two ways:
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 48 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 69 ).
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 57 error and would result in a script runtime when
processing the chart's last bar.
{
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";
However, if you were able to access partial bar data (Wealth-Lab User Guide: Yahoo! Data
Provider), it would be possible to update a Daily bar just prior to the market close to get
an estimate of the final closing price with which to execute a trade. The trick, however,
is to use an AtMarket order in place of the AtClose order when processing the last bar as
demonstrated below.
Example (How to run Example code? 3 )
C#
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);
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.
!symbol_strike_YYMMDD_optionType
where,
//contract 1
int b = Bars.Count - 120;
Bars oc = CreateSyntheticOption(b, 30, 30, true);
PrintDebug(oc.Count);
Color c = Color.Black;
PlotSymbol(pane, oc, c, c);
//contract 2
b = Bars.Count - 80;
Bars oc2 = CreateSyntheticOption(b, 30, 30, true);
c = Color.Purple;
PlotSymbol(pane, oc2, c, c);
//contract 3
b = Bars.Count - 40;
Bars oc3 = CreateSyntheticOption(b, 30, 30, true);
c = Color.Blue;
PlotSymbol(pane, oc3, c, c);
}
The following code uses a “4x2” approach. When the underlying is down 4 days in a row,
the strategy buys a synthetic call with an expiry date of at least 30 days out. When the
underlying is up 2 days in a row, the call is sold.
Example (How to run Example code? 3 )
C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
private Color[] PlotColors = { Color.FromArgb(255, 128, 128),
Color.FromArgb(128, 255, 128),
Color.FromArgb(128, 128, 255) };
private int PlotIndex;
private List<string> PlottedSymbols = new List<string>();
{
ChartPane optionsPane = CreatePane(75, true, true);
SetBarColors(Color.Gray, Color.Gray);
for(int bar = 30; bar < Bars.Count; bar++)
{
if (IsLastPositionActive)
{
if (CumUp.Value(bar, Close, 1) >= 2)
{
//sell the call
SetBarColor(bar, Color.Red);
Bars contract = LastPosition.Bars;
SetContext(contract);
SellAtMarket(bar + 1, LastPosition, "Sell Call");
RestoreContext();
}
}
else
{
if (CumDown.Value(bar, Close, 1) == 4)
{
//buy a call
SetBarColor(bar, Color.Blue);
Bars contract = CreateSyntheticOption(bar, 30, 30, true);
SetContext(contract);
BuyAtMarket(bar + 1, "Buy Call");
RestoreContext();
8 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.
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"
Sentiment
Analyst Ratings
"all analyst ratings", "analyst upgrade", or "analyst downgrade".
See Analyst Upgrades Downgrades Data in the Fundamental Data and Economic
Indicator Definitions Guide (Help menu) for more information.
Security Sentiment
"net insider transactions", "insider buy", or "insider sell"
"shares short"
"days to cover"
"short interest as a % of shares outstanding"
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.
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.
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):
//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.
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.
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);
Equity Summary Score has three main factors in its calculation. The first is normalization
of data where scarce ratings are given more importance by adding weight to them.
StarMine adjusts their recommendation values by overweighting the scarce values and
underweighting the plentiful ratings. The second factor is weighted historical accuracy.
StarMine uses the previous 24 month relative firm/sector ratings accuracy to determine
which firms' ratings have the most weight in the aggregated ESS. The final factor used in
ESS is forced ranking between the 1,500 largest market capitalization stocks. Equity
Summary Score is on a scale of 0.1 to 10.0, and by ranking the largest stocks. This
ensures there will be uniform distribution of scores of the larger stocks, and then the
smaller cap stocks are slotted into the distribution without ranking.
For more information see Research Firm Details for StarMine at fidelity.com.
The following table summarizes the Equity Summary Score, chart icons, and associated
sentiment ratings (up/downgrade) by StarMine.
Downgrade
Icon Sentiment Rating Upgrade To
Score To
0.1 to 1.0 Very Bearish
1.1 to 3.0 Bearish
3.1 to 7.0 Neutral
7.1 to 9.0 Bullish
9.1 to 10.0 Very Bullish
You access fundamental DataSeries of Equity Summary Scores/Category in the same way
as other fundamental data: by passing the strings "equity summary score" or "equity
The Strategy Builder provides the fastest way to start a strategy that employs Equity
Summary Score. When you add a condition using Equity Summary Score, the wizard
creates a pane with colored ranges representing the Equity Summary categories. The
result provides a fine visual reference for a company's ESS history.
Your Wealth-Lab Pro® installation includes Equity Summary Score-based Strategies in the
Equity Summary Score Strategy folder for you to reference as additional programming
examples.
Multi-Time Frame Analysis allows you to access data and indicators based
on higher timeframes, but create trades in the chart's base scale.
For example, let's say that you'd like your trade setup to be based on an underlying trend
measured by a moving average of weekly bars. In Wealth-Lab Version 6 you can identify
that trend and trade it on a daily (or even intraday) basis.
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.
}
}
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? 3 )
C#
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, hR2, Color.Maroon, LineStyle.Dotted, 1);
PlotSeries( PricePane, hR1, Color.Olive, LineStyle.Solid, 1 );
PlotSeries( PricePane, hPivot, Color.Blue, LineStyle.Solid, 1 );
PlotSeries( PricePane, hS1, Color.Fuchsia, LineStyle.Dotted, 1 );
PlotSeries( PricePane, hS2, Color.Red, LineStyle.Solid, 1 );
{
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;
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);
}
}
}
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.
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);
}
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? 3 )
C#
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";
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 );
}
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? 3 )
C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
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;
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:
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.
Each bar number in a traditional OHLC chart has an associated column object. In this
way, it's possible to access the column properties as of a specific bar. Note that another
characteristic of all Trending Chart Styles is that price movement may or may not
contribute to a column's high or low price value. Consequently, it's possible to advance
from bar to bar without any corresponding movement in a Trending Chart Style, each of
which is dependent on the Chart Style's settings.
For Kagi and PnF charts, a single plotted column represents an entire bullish or
bearish move. For Renko and Line Break charts, several consecutive columns
can represent bullish or bearish periods, which are plotted using "bricks" or bars
in a stair-step fashion.
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.
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).
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.
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).
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).
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.
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.
Members of WealthLab.ChartStyles.Trending.TKagi
public TKagi(WealthLab.Bars b ars, KagiReverseType kag iRe ve rse T yp e , double re ve rsalA m o u n t , int
kag iA T RP e rio d)
The TKagi contructor. Pass a Bars object, a KagiReverseType enumeration, the re ve rsalA m o u n t , and an
integer for kag iA T RP e rio d , the ATR period, which is used only when kag iRe ve rse T yp e =
KagiReverseType.ATR.
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.
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
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
The following members are unique to the Point and Figure Chart Style. They complete the
properties for a PnF Column.
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 lo g Me t h o d = true
b o xS ize is interpreted as a percentage (otherwise as points), and,
b o xS ize cannot be smaller than 0.01, otherwise 0.01% is used, i.e., Ln(1 + 0.01/100)
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 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.
For example use, see the Point and Figure Basic Strategy found in your
Wealth-Lab Pro installation's Trend Following* Strategy folder.
Members of WealthLab.ChartStyles.Trending.TrendLine
public TrendLine(Bars bars, int bar1, double price1, int bar2, double price2, int idBar)
TrendLine constructor.
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.
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.
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 79 .
The plot functions plot the Arms Index DataSeries, automatically creating a ChartPane, if required.
public void plotArmsIndexSeries()
public void plotArmsIndexSeries(WealthLab.ChartPane p an e S T I)
public void plotArmsIndexSeries(int h e ig h t , bool ab o ve P ric e , bool sh o w Grid)
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
protected override void Execute()
{
ArmsIndex ad = new ArmsIndex(this, "NYSE", 5 );
DataSeries dsSTI = ad.ArmsIndexSeries;
ad.plotArmsIndexSeries();
}
}
}
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:
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.
namespace WealthLab.Strategies
{
public class CandlePatternStrategy : WealthScript
{
protected override void Execute()
{
bool[] bullishThreeWhiteSoldiers;
CandlePattern.BullishThreeWhiteSoldiers(this, "+3White Soldiers", true,
out bullishThreeWhiteSoldiers);
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);
{
Position p = LastPosition;
if (bearishThreeBlackCrows[bar] || bearishTriStar[bar])
SellAtMarket(bar + 1, p);
}
else
{
if (bullishThreeWhiteSoldiers[bar] || bullishInvertedHammer[bar])
BuyAtMarket(bar + 1);
}
}
}
}
}
Clarification for terminology used in the Candlestick definitions in the following topics:
White Line/ A candle whose close price is greater than the open price.
Candle
Black Line/ A candle whose closing price is less than the open price.
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 104
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.
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 104
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 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.
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.
11.3 DateRules
Members of WealthLab.Rules.DateRules
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);
}
}
}
}
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)'.
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
protected override void Execute()
{
DataSeries reverseAdjustment;
DataSeriesOp.SplitReverseFactor( this, "Split (Yahoo! Finance)", out
reverseAdjustment);
11.5 FundamentalsDateRules
Members of WealthLab.Rules.FundamentalsDateRules
public FundamentalDateRules(WealthLab.WealthScript w s, WealthLab.Bars b ars, string sym b o l,
string it e m Nam e )
Constructor.
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");
11.6 FundamentalsRatio
Members of WealthLab.Rules.FundamentalsRatio
Applies to: Fidelity Wealth-Lab Pro fundamental data items, except where specified
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.
Abbreviations:
ttm = Trailing 12 Months
mrq = Most-Recent Quarter
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);
}
}
}
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:");
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
protected override void Execute()
{
DataSeries dsCPG = FundamentalsRatio.ConsecutiveGrowthSeries( this, "sales
turnover", 1, 12, true, true );
dsCPG.Description = "Consecutive years of Sales Growth above 12%";
ChartPane paneCPG = CreatePane(50, true, false);
PlotSeries(paneCPG, dsCPG, Color.Purple, LineStyle.Histogram, 50);
}
}
}
Returns the growth rate expressed as percentage growth (e.g. 10 = 10%) with respect to the specified
number ( p e rio d s) of years ago ( an n u al= t ru e ) or quarters ago ( an n u al= false ). Recommended values for
it e m Nam e : "cash" (Cash Flow), "net income", and "sales turnover".
Example (How to run Example code? 3 )
C#
using System;
using System.Drawing;
using WealthLab;
using WealthLab.Rules;
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);
}
}
}
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
Enterprise Value is not calculated correctly due to the use of Cash Flow c ash instead of Balance
Sheet c ash, the latter of which is currently not an available fundamental data item.
public static WealthLab.DataSeries FundamentalSeries(WealthLab.WealthScript w s, string it e m Nam e ,
bool an n u al)
Returns the Fundamental DataSeries for the it e m Nam e synchronized with the chart. Pass false to an n u al
for mrq (raw) data values. Pass t ru e to an n u al 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.
Example (How to run Example code? 3 )
C#
using System;
using System.Drawing;
using WealthLab;
using WealthLab.Rules;
namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{
protected override void Execute()
{
// A Balance Sheet item
DataSeries assets_mrq = FundamentalsRatio.FundamentalSeries(this,
"assets", false);
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);
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);
}
}
}
using WealthLab;
using WealthLab.Rules;
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);
}
}
}
11.7 OptionExpiryDate
Members of WealthLab.Rules.OptionExpiryDate
The OptionExpiryDate function names and syntax are self-descriptive.
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());
}
}
}
}
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 79 .
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 WealthLab.DataSeries MomentumIndexSeries { get; }
public WealthLab.DataSeries MomentumRatioSeries { get; }
public WealthLab.DataSeries MomentumSMAIndexSeries { get; }
public WealthLab.DataSeries MomentumSMARatioSeries { get; }
public WealthLab.DataSeries 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 bool createMomentumIndexSeries()
public bool createMomentumRatioSeries()
public bool createMomentumSMAIndexSeries()
public bool createMomentumSMARatioSeries()
The plot functions plot the indicated DataSeries, automatically creating a ChartPane, if required.
public void plotIndex()
public void plotIndex(WealthLab.ChartPane p an e In d e x)
public void plotIndex(int h e ig h t , bool ab o ve P ric e , bool sh o w Grid)
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? 3 )
C#
using System;
using WealthLab;
using WealthLab.Indicators;
using WealthLab.Rules;
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.
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();
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.
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);
}
The example above uses familiar technical indicators, but you can apply
fundamental criteria 74 as well.
scan, will run much more quickly. Also, for the fastest screens on large DataSets, it's best
to use the Data Manager to update data first and to disable File > Update Data on 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.
// WAS:
BuyAtMarket(bar + 1);
// CHANGE TO:
BuyAtMarket(bar + 1, RSI.Series(Close, 10)[bar].ToString("0.000"));
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.
See the "RSI Rotation" and "Dogs of the Dow" Strategies included with the installation.
This example illustrates how, using the power of .NET classes, you could access remote
sources of information on-the-fly in your trading strategy. After the raw data for the total
Put/Call Ratio is downloaded from the CBOE website and becomes available as a
DataSeries, the strategy is able to make its trading decisions on Bollinger Bands derived
from the Put/Call Ratio.
The code also demonstrates how to cache the resulting Bars object in the global memory
(GOP) to speed up Multi-Symbol Backtests. Without caching, the strategy would have to
generate redundant data requests to the server on each symbol in a DataSet. As soon the
data is in place, access to the Internet is no longer required.
namespace WealthLab.Strategies
{
public class Detail
{
public string sym;
public DateTime lastdt;
public DateTime firstdt;
public int nbars;
}
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
Internet 129
Abs 13
add, subtract, multiply, divide 12
-1- delay 12
offset 12
100% of Equity Sizing 126
shift 12
DateTime 8
-A- DayOfWeek 8
divide (DataSeries) 12
add (DataSeries) 12 division-by-zero (DataSeries) 12
AllPositions 66
Arms Index 103
Average Price 12 -E-
AveragePriceC 12
Example Code
*instructions* 3
-B- External Symbols 18
bar 4
bar + n, n > 1 54 -F-
Black-Scholes 71
Filter 124
Floor Trader Pivots 87
-C- Fundamental Data
Deletion 74
Candlesticks Functions 74
Bearish 107 Refresh 74
Bullish 105 Retrieval 74
Neutral and Reversal 108
syntax 104
Chart Style -G-
Kagi 96
Good Til Canceled 53
Line Break 98
GTC 53
Point and Figure 98
Renko 97
Chart Styles
Trending 94
-H-
ChartPane Heikin-Ashi 24
create 23 Hour 8
class-scope variables 45 How to
click to topic 76 Run Example Code 3
Compile Strategies 45 How to (Bars)
Custom Indicators 38 access OHLC/V values 6
access scale and interval 8
access symbol, company name 10
-D- access tick, margin, point 10
change OHLC/V series 6
Data
Count total ~ in chart 4
© 2015 FMR LLC All rights reserved.
134 WealthScript Programming Guide, Wealth-Lab Pro
Market Sentiment 79
New Highs, Lows 120
Market Sentiment Data 79 -S-
Minute 8
scale 8
MP Strategies 65
Screen 124
multiply (DataSeries) 12
seed data 43
Series method 36
-N- shift operator (DataSeries)
SP Strategies 51
12
-V-
Value method 37
Variables
class-scope 45
Volume
hide 23
-W-
Wealth-Lab.com Knowledge Base 132
-Y-
y-axis
scale control 27