Traders' Tip text
In September issue, author Charlotte Hudgin presents a formalized approach to identifying dips in stocks with established trends. Despite its clarity, the abundance of included system parameters (which includes various timeouts, periods, and percentages) makes it quite picky. You might want to reduce them if you notice that it catches too few setups, or to apply it to a large watchlist (think hundreds or even thousands of stocks) if you’re an active trader.
Author’s idea of using “white space” between the price and its moving average is a clever visual technique to help find consistent trends that don’t retrace too often and with higher momentum. We quantify the white space as percentage of bars with their Low price above a moving average. A potential enhancement might be in evaluation of
how much does the Low price deviate from the SMA.
Unfortunately, as it frequently happens the article does not focus on getting
out of the trade. We will leave development of a complete exit strategy to motivated traders, though a simple trailing exit that you can find in the code will do pretty good.
Figure 1. A Wealth-Lab 6 chart illustrating the application of the system's rules on a Daily chart of UA (Under Armor).To execute the enclosed trading system, Wealth-Lab users may copy/paste the enclosed strategy’s C# code or simply let Wealth-Lab do the job: in the “Open Strategy” dialog, click “Download” to get the strategy code.
Spotting trend pullbacks is a very popular tactics. If you want more, Wealth-Lab has to offer different variations of the same tune. In particular we’d like to highlight another downloadable Strategy “Big Move, Pullback and Continuation” system which can be found under the “Pullbacks” folder in the “Open Strategy” dialog (Ctrl-O). It also features great flexibility in configuring various parameters interactively through “sliders”.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
namespace WealthLab.Strategies
{
public class GoldenTriangleStrategy : WealthScript
{
private StrategyParameter paramRiseBars;
private StrategyParameter paramWhiteSpace;
private StrategyParameter paramMinRise;
private StrategyParameter paramSMA;
private StrategyParameter paramMom;
private StrategyParameter paramHi;
private StrategyParameter paramProximity;
private StrategyParameter paramPullback;
private StrategyParameter paramRecovery;
private StrategyParameter paramTimeout;
private StrategyParameter paramVolConf;
private StrategyParameter paramMaxBuy;
private StrategyParameter paramTrail;
public GoldenTriangleStrategy()
{
paramRiseBars = CreateParameter("Rise: X bars", 50, 10, 100, 10);
paramWhiteSpace = CreateParameter("White space %", 50, 10, 100, 10);
paramMinRise = CreateParameter("Min. rise %", 10, 5, 200, 5);
paramSMA = CreateParameter("SMA period", 50, 10, 200, 10);
paramMom = CreateParameter("Momentum period", 10, 2, 30, 2);
paramHi = CreateParameter("Highest period", 20, 10, 100, 10);
paramProximity = CreateParameter("Within SMA %", 2, 1, 5, 1);
paramPullback = CreateParameter("Pullback %", 2, 2, 18, 2);
paramRecovery = CreateParameter("Approaching %", 2, 2, 6, 2);
paramTimeout = CreateParameter("Expires after Y bars", 20, 5, 40, 5);
paramVolConf = CreateParameter("Volume confirmation?", 0, 0, 1, 1);
paramMaxBuy = CreateParameter("Max buy price", 5, 1, 10, 1);
paramTrail = CreateParameter("Trailing low exit", 40, 10, 80, 10);
}
protected override void Execute()
{
bool pivot = false; int pivotBar = -1;
bool pullback = false; int pullbackBar = -1;
bool recovery = false; int recoveryBar = -1;
bool volConfirm = paramVolConf.ValueInt == 1;
double ws = paramWhiteSpace.Value / 100d;
double within = paramProximity.Value;
double minRise = paramMinRise.Value;
double risePct = 0.0, pivotPrice = 0.0, dipPrice = 0.0;
int riseBars = paramRiseBars.ValueInt, ba = 0;
SMA sma = SMA.Series(Close,paramSMA.ValueInt);
MomentumPct mom = MomentumPct.Series(Close,paramMom.ValueInt);
Highest hi = Highest.Series(High,paramHi.ValueInt);
DataSeries whiteSpace = new DataSeries(Bars,"WhiteSpace");
Color blue = Color.FromArgb(50,Color.Blue);
LineStyle ls = LineStyle.Solid;
PlotSeries(PricePane,sma,Color.Red,ls,1);
for(int bar = Math.Max(riseBars, GetTradingLoopStartBar(paramSMA.ValueInt)); bar < Bars.Count; bar++)
{
// "White space": percentage of bars above 50-day SMA
for (int i = bar - riseBars; i <= bar; i++)
{
ba = (Low[i] > sma[i]) ? ba += 1 : 0;
whiteSpace[bar] = ba / (double)riseBars;
}
if (IsLastPositionActive)
{
SellAtStop( bar+1, LastPosition, Lowest.Series(Low,paramTrail.ValueInt)[bar] );
}
else
{
// 1. Detecting pivot
if( !pivot )
{
// Uptrend: price > SMA, momentum % > 100, "white space" at or exceeds 50%, hit new 50-day high
if( mom[bar] >= 100 && whiteSpace[bar] > ws && High[bar] >= hi[bar] )
{
// Rise over X bars (default)
risePct = (High[bar] - High[bar - riseBars]) / High[bar] * 100.0;
// Pivot detected: price rise exceeds predefined % threshold
if( risePct > minRise )
{
pivot = true; pivotBar = bar; pivotPrice = Close[pivotBar];
SetBackgroundColor( pivotBar, blue );
}
}
}
// 2. Looking for pullback
if( pivot )
{
// Pullback is valid until it times out
if( bar <= pivotBar + paramTimeout.ValueInt )
{
if( !pullback )
{
// Pullback detected: price dove within N% of SMA
bool priceNearSMA = Close[bar] > (sma[bar] * 1 - (within / 100d)) &&
Close[bar] < (sma[bar] * 1 + (within / 100d));
if( priceNearSMA )
{
pullback = true; pullbackBar = bar; dipPrice = Close[pullbackBar];
SetBackgroundColor( pullbackBar, Color.FromArgb(30, Color.Red) );
}
}
// 3. Looking for recovery
if( pullback )
{
// Rebound is valid until it times out
if( bar <= pullbackBar + paramTimeout.ValueInt )
{
if( !recovery )
{
// Recovery started: current price is above both the 50-day SMA and Pullback price
// but current high is still below the Pivot price
if( (Close[bar] > sma[bar]) && (Close[bar] > dipPrice) && (High[bar] <= High[pivotBar]) )
{
recovery = true; recoveryBar = bar;
SetBackgroundColor( recoveryBar, Color.FromArgb(50, Color.Orange) );
}
}
// 4. Looking to enter
if( recovery )
{
// 4.a Price confirmation
if( Close[bar] > sma[bar] )
{
// 4.b Volume confirmation (if enabled)
if( !volConfirm || (volConfirm && Volume[bar] > SMA.Series(Volume,50)[bar]) )
{
// Enter: price below Max Buy price
double maxBuyPrice = (1 - paramMaxBuy.Value / 100d) * High[pivotBar];
if( Close[bar] < maxBuyPrice )
{
// Eugene: buy at stop half-way between the Pivot and Pullback prices (or higher)
if( BuyAtStop( bar + 1, (dipPrice + (pivotPrice - dipPrice) / 2), Bars.FormatValue(risePct) ) != null )
// Alternative:
//if( BuyAtMarket( bar + 1, Bars.FormatValue(risePct) ) != null )
{
DrawLine( PricePane, pivotBar, pivotPrice, pullbackBar, dipPrice, blue, ls, 2 );
DrawLine( PricePane, pullbackBar, dipPrice, bar, High[pivotBar], blue, ls, 2 );
DrawLine( PricePane, pivotBar, High[pivotBar], bar, High[pivotBar], blue, LineStyle.Dashed, 2 );
pivot = false; pullback = false; recovery = false;
LastPosition.Priority = -Close[bar];
}
else
// reset if setup has timed out
recovery = bar + 1 - recoveryBar < paramTimeout.ValueInt;
}
}
}
}
}
else
{
pullback = false;
SetBackgroundColor( pullbackBar, Color.Transparent );
}
}
}
else
{
pivot = false;
SetBackgroundColor( pivotBar, Color.Transparent );
}
}
}
}
}
}
}
Eugene
Wealth-Lab team
www.wealth-lab.com