TASC 2014-12 | Detecting Flags In Intraday Charts (Katsanos)

Modified on 2014/10/29 09:11 by Eugene — Categorized as: TASC Traders Tips

Traders' Tip text

Detecting chart patterns is always a special joy to code if provided rules are well thought-out and fully mechanical. Such is the clear definition of flag pattern given by author Markos Katsanos in December 2014 issue of Stocks & Commodities.

We've used this simplified set of rules to detect flags:


Image

Figure 1. A Wealth-Lab 6 chart illustrating the detection of the flag pattern on a 5-minute chart chart of SPY (S&P 500 SPDR).

With minimal tweaks to the system's parameters it can be applied to charts of different time frames (e.g. EOD):

Image

Figure 2. A failed flag on the Daily chart of AXP (American Express) in Wealth-Lab 6.

There's room for improvement: consider adding a filter against erratic price movement, phase out the less probable trades against the medium-term (daily) trend, playing with the exits (to our taste, the initial stop at the flag bottom may result in premature exits; subtracting an ATR from that level could be a more robust approach etc).

Also, there's a closely similar system looking for tight consolidation ranges, available to Wealth-Lab users for download called «Rectangle Trading System (Acme R)». Along with other related systems mechanically identifying chart patterns, look for it in the «Chart Patterns» folder after having downloaded all publicly available strategies (click “Download” in the “Open Strategy” dialog).

To sum up, the proposed technique is useful to recognize brief retreats in steep trends.


using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace WealthLab.Strategies { public class Katsanos201412 : WealthScript { private StrategyParameter paramPoleTimeout; private StrategyParameter paramFlagTimeout; private StrategyParameter paramUptrendBeforePole; private StrategyParameter paramMinFlagDuration; private StrategyParameter paramMaxFlagDuration; private StrategyParameter paramSMAPeriod; private StrategyParameter paramInactivityStop; private StrategyParameter paramTimeout; private StrategyParameter paramPoleHeight; private StrategyParameter paramFlagHeight; private StrategyParameter paramProfitTarget;

void DrawRectangle(int b1, int b2, double p1, double p2, Color c) { double[] rect = { b1, p1, b1, p2, b2, p2, b2, p1 }; DrawPolygon( PricePane, Color.Blue, c, LineStyle.Solid, 1, true, rect ); } public Katsanos201412() { paramPoleTimeout = CreateParameter("Pole Timeout", 23, 10, 50, 1); paramPoleHeight = CreateParameter("Pole Height", 5.5, 1.0, 10, 0.5); paramUptrendBeforePole = CreateParameter("Uptrend Before Pole", 70, 10, 100, 10); paramSMAPeriod = CreateParameter("SMA Period", 50, 10, 100, 5); paramFlagHeight = CreateParameter("Flag Height", 2.5, 0.5, 5.0, 0.5); paramFlagTimeout = CreateParameter("Flag Timeout", 15, 3, 30, 1); paramMinFlagDuration = CreateParameter("Min Flag Duration", 3, 3, 30, 1);

paramInactivityStop = CreateParameter("Inactivity Stop", 70, 10, 100, 10); paramTimeout = CreateParameter("Timeout", 100, 10, 100, 10); paramProfitTarget = CreateParameter("Profit Target ATR", 1.2, 0.2, 3.0, 0.2); } protected override void Execute() { int PoleTimeout = paramPoleTimeout.ValueInt, FlagTimeout = paramFlagTimeout.ValueInt, UptrendLeadingToPole = paramUptrendBeforePole.ValueInt, MinFlagDuration = paramMinFlagDuration.ValueInt, smaPeriod = paramSMAPeriod.ValueInt, inactivityStop = paramInactivityStop.ValueInt, timeout = paramTimeout.ValueInt, PoleBar = 0, FlagBar = 0, ba = 0; double poleHeight = paramPoleHeight.Value, flagHeight = paramFlagHeight.Value, currPoleHeight = 0, ProfitTarget = paramProfitTarget.Value, InitialStop = 0, ws = 0.5, flagTop = 0, flagBottom = 0; bool PoleValid = false, FlagValid = false; SMA sma = SMA.Series( Close,smaPeriod ); LinearRegSlope lrs = LinearRegSlope.Series( Close, FlagTimeout ); HideVolume();

for(int bar = GetTradingLoopStartBar(100); bar < Bars.Count; bar++) { if (IsLastPositionActive) { // Exits Position p = LastPosition; double atr = ATR.Series( Bars, 40 )[bar]; double high = p.HighestHighAsOfBar(bar); double chandelier = high - atr * 3; double inactivity = atr * 4;

if( ( bar+1 - p.EntryBar >= inactivityStop ) && ( p.MFEAsOfBar( bar ) < inactivity ) ) SellAtMarket( bar+1, p, "Inactivity+MFE" ); else if( bar+1 - p.EntryBar >= timeout ) SellAtMarket( bar+1, p, "Time exit" ); else if( !SellAtStop( bar+1, p, p.RiskStopLevel, "Stop loss" ) ) if( !SellAtStop( bar+1, p, chandelier, "Trailing (Chandelier)" ) ) SellAtLimit( bar+1, p, p.AutoProfitLevel, "Profit Target" ); } else { if( !PoleValid ) { //Uptrend during the last 70 bars leading to the pole. if(Lowest.Value( bar, Close, PoleTimeout ) > Lowest.Value( bar, Close, UptrendLeadingToPole )) { //A steep pole of 5.5 times the average true range (ATR) or more, in 23 bars or less. currPoleHeight = Close[PoleBar] - Close[bar - PoleTimeout]; double atr = ATR.Value(bar, Bars, 40); PoleBar = bar; PoleValid = currPoleHeight >= atr * poleHeight ? true: false; } } if( PoleValid ) { if( !FlagValid ) { //A flag breaking out in 15 bars or less from the pole top and sloping horizontally or slightly down. if( bar <= PoleBar + FlagTimeout && bar >= PoleBar + MinFlagDuration ) // To avoid premature triggering { flagTop = Highest.Value( bar, Close, FlagTimeout ); flagBottom = Lowest.Value( bar, Close, FlagTimeout ); InitialStop = flagBottom; double flagRange = flagTop - flagBottom; double atr = ATR.Value(bar, Bars, 40); double slope = lrs[bar]; bool isSlopeOK = slope > -0.04 && slope <= 0.01;

//Flag depth not more than 2.5 times the ATR measured from the highest to the lowest point in the flag.

if( flagRange <= atr * flagHeight && isSlopeOK ) { FlagValid = true; FlagBar = bar; } } else PoleValid = bar + 1 - PoleBar < PoleTimeout; // reset if Setup has timed out }

if( FlagValid ) { if( BuyAtStop( bar + 1, Highest.Value(bar, High, FlagTimeout) ) != null ) { // Draw flag and pole DrawRectangle( FlagBar, FlagBar-FlagTimeout, flagTop, flagBottom, Color.LightSteelBlue ); DrawRectangle( PoleBar, PoleBar-PoleTimeout, Close[PoleBar], Close[PoleBar-PoleTimeout], Color.Transparent ); // Assign initial stop and profit target levels LastPosition.AutoProfitLevel = LastPosition.EntryPrice + currPoleHeight * ProfitTarget; LastPosition.RiskStopLevel = InitialStop; PoleValid = false; FlagValid = false; // reset Setup variables } else // reset if Setup has timed out { PoleValid = bar + 1 - PoleBar < PoleTimeout; FlagValid = false; flagTop = 0; flagBottom = 0; } } } } } } } }

Eugene
Wealth-Lab team
www.wealth-lab.com