Traders' Tip text
The Nomalized Volatility Indicator can be found in Wealth-Lab’s Standard Indicators set as ATRP. Using some artistic license with the strategy, I used an EMA-smoothed version of ATRP to avoid whipsaw trades around the trigger level. By detecting the next market structure peak (short) or trough (long) and then comparing current price to the previous Position’s entry price before adding a new Position, the WealthScript Strategy adds Positions (a fixed-dollar value) only when the market continues to move in your favor.
The strategy should work well for a small set of stocks or ETFs, however, it’s necessary to find an appropriate trigger level for each issue. For this task, perform a raw-profit optimization on the DataSet. When finished, right-click the Results set and choose the option to “Apply Preferred Values (PVs) based on the highest metric per symbol.” In this way, you can easily recall a symbol’s PV trigger level for testing and trading.
Figure 1. In order to scale into multiple Positions only when you’re right, long Positions are added on higher troughs, whereas short Positions are added after detecting lower peaks.
WealthScript Code (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
{
StrategyParameter _nviLevel;
StrategyParameter _atrPeriod;
StrategyParameter _maxPositions;
public MyStrategy()
{
_nviLevel = CreateParameter("Trigger Level", 3.7, 1, 5, 0.1);
_atrPeriod = CreateParameter("ATR Period", 64, 30, 90, 2);
_maxPositions = CreateParameter("Max Positions", 5, 1, 10, 1);
}
protected override void Execute()
{
double nviTrigger = _nviLevel.Value;
int maxPositions = _maxPositions.ValueInt;
int per = _atrPeriod.ValueInt;
// Smoothed "nvi"
DataSeries nvi = EMA.Series( ATRP.Series(Bars, per), 21, EMACalculation.Modern);
ChartPane nviPane = CreatePane(40, true, true);
PlotSeries(nviPane, nvi, Color.Blue, LineStyle.Solid, 2);
DrawHorzLine(nviPane, nviTrigger, Color.Fuchsia, LineStyle.Dashed, 2);
for(int bar = Math.Min(3 * per, 64); bar < Bars.Count; bar++)
{
if (nvi[bar] > nviTrigger )
{
if ( CrossOver(bar, nvi, nviTrigger) )
{
ExitAtMarket(bar + 1, Position.AllPositions);
ShortAtMarket( bar + 1, "Crossover" );
}
else if ( CrossUnder(bar, Momentum.Series(Low, 4), 0) )
{
int ap = ActivePositions.Count;
if ( ap > 0 && ap < maxPositions)
{
Position p = LastPosition;
if ( bar - p.EntryBar > 5 && Close[bar] < p.EntryPrice )
ShortAtMarket( bar + 1, "Add" );
}
}
}
if (nvi[bar] < nviTrigger )
{
if ( CrossUnder(bar, nvi, nviTrigger) )
{
ExitAtMarket(bar + 1, Position.AllPositions);
BuyAtMarket( bar + 1, "Crossover" );
}
else if ( CrossOver(bar, Momentum.Series(High, 4), 0) )
{
int ap = ActivePositions.Count;
if ( ap > 0 && ap < maxPositions)
{
Position p = LastPosition;
if ( bar - p.EntryBar > 5 && Close[bar] > p.EntryPrice )
BuyAtMarket( bar + 1, "Add" );
}
}
}
}
}
}
}