TRADERS’ TIPS
For this month’s Traders’ Tips, the focus is Markos Katsanos’ article in this issue, “Growth Or Value?” Here, we present the December 2023 Traders’ Tips code with possible implementations in various software.
You can right-click on any chart to open it in a new tab or window and view it at it’s originally supplied size, often much larger than the version printed in the magazine.
The Traders’ Tips section is provided to help the reader implement a selected technique from an article in this issue or another recent issue. The entries here are contributed by software developers or programmers for software that is capable of customization.
Since market conditions continually change and may favor either growth or value stocks, investors often find themselves at a crossroads. In response to this dilemma, author Markos Katsanos introduces a momentum trading system designed to rotate between these two strategies. He begins by outlining the distinctions between growth and value investing and subsequently presents a system for switching between the two using Vanguard’s value and growth ETFs. Since the strategy defaults to use a lookback period of 90 bars, the maximum number of bars study will reference (MaxBarsBack) setting must be increased within the general tab of the strategy properties for all window.
Strategy: ETF Rotation // TASC DEC 2023 // Markos Katsanos // "Growth or Value?" // ETF Rotation Strategy // Set up weekly chart with 3 data series: // Data1 = VUG or VTV // Data2 = VTV or VUG // Data3 = SPY inputs: Coef( 0.2 ), VCoef( 2.5 ), VPeriod( 30 ), RSBARS( 90 ), MARSBARS( 20 ), MAXLOSSPERC( 7 ); constants: VUG( "VUG" ), VTV( "VTV" ); variables: MyVol( 0 ), Inter( 0 ), VInter( 0 ), CutOff( 0 ), VAve( 0 ), VMax( 0 ), MF( 0 ), VC( 0 ), DirectionalVolume( 0 ), VFI( 0 ), Ratio1( 0 ), Ratio2( 0 ), Diff1( 0 ), Diff2( 0 ), RSMK1( 0 ), RSMK2( 0 ), BUY1( false ), BUY2( false ), SELL1( false ), SELL2( false ), SELL3( false ); if Symbol = VUG then begin Ratio1 = Close / Close of Data3; Ratio2 = Close of Data2 / Close of Data3; end else if Symbol = VTV then begin Ratio1 = Close of Data2 / Close of Data3; Ratio2 = Close / Close of Data3; end; if Ratio1[RSBARS] > 0 and Ratio2[RSBARS] > 0 then begin MyVol = IFF( BarType >= 2 and BarType < 5, Volume, Ticks ); Inter = Log(TypicalPrice) - Log(TypicalPrice[1]); VInter = StdDev(Inter, 30); CutOff = Coef * VInter * Close; VAve = Average(MyVol, VPeriod)[1]; VMax = VAve * VCoef; VC = MinList(MyVol, VMax); MF = AvgPrice - AvgPrice[1]; if MF > CutOff then DirectionalVolume = VC else if MF < -CutOff then DirectionalVolume = -VC else DirectionalVolume = 0; if VAve <> 0 then VFI = Summation(DirectionalVolume, VPeriod) / VAve; VFI = XAverage(VFI, 2); Diff1 = Log(Ratio1) - Log(Ratio1[RSBARS]); Diff2 = Log(Ratio2) - Log(Ratio2[RSBars]); RSMK1 = XAverage(Diff1, 2) * 100; RSMK2 = XAverage(Diff2, 2) * 100; BUY1 = Symbol = VUG and RSMK1 > RSMK2 and RSMK1 > Average(RSMK1, MARSBARS); BUY2 = Symbol = VTV and RSMK2 > RSMK1 and RSMK2 > Average(RSMK2, MARSBARS); if (BUY1 OR BUY2) and VFI > 0 then Buy this bar at close; SELL1 = Symbol = VUG and RSMK1 < RSMK2 and RSMK1 < Average(RSMK1,MARSBARS) and RSMK1 < RSMK2[1]; SELL2 = Symbol = VTV and RSMK2 < RSMK1 and RSMK2 < Average(RSMK2, MARSBARS) and RSMK2 < RSMK1[1]; SELL3 = Close < Average(Close, 50) and Close < (1 - MAXLOSSPERC / 100) * Close[1]; if SELL1 or SELL2 or SELL3 then Sell this bar at close; end;
A sample chart is shown in Figure 1.
FIGURE 1: TRADESTATION. This TradeStation weekly chart demonstrates the strategy applied to the Vanguard ETF VUG. Data2 is VTV and Data3 is SPY.
This article is for informational purposes. No type of trading or investment recommendation, advice, or strategy is being made, given, or in any manner provided by TradeStation Securities or its affiliates.
To replicate the trades in the article you should run the strategy on both VUG and VTV and eliminate overlapping trades manually by taking trades on VUG only if there is no open VTV position or vice versa.
{******************************************************************* GROWTH OR VALUE? WEEKLY STRATEGY Provided By : MARKOS KATSANOS 2023 ********************************************************************} {For more information see Markos Katsanos's articles in the December 2023 issues of Technical Analysis of Stocks & Commodities magazine. The strategy should be applied on VUG or VTV. To run the strategy set up a weekly chart with 3 data series: Data1 = VUG or VTV Data2 = VTV or VUG Data3 = SPY Max bars study will reference= 60 To replicate the trades in the article you should run the strategy on both VUG and VTV and eliminate overlapping trades manually by taking trades on VUG only if there is no open VTV position or vice versa } inputs: Coef( 0.2 ),VCoef( 2.5 ),VPeriod( 30 ), //VFI VARIABLES RSBARS( 15 ),MARSBARS( 20 ), //RELATIVE STRENGTH PERIOD AND MA MAXLOSSPERC( 7 ); //STOP LOSS % variables: MyVol( 0 ),Inter( 0 ),VInter( 0 ),CutOff( 0 ),VAve( 0 ),VMax( 0 ),MF( 0 ),VC( 0 ),DirectionalVolume( 0 ),VFI1( 0 ), Ratio1( 0 ),Ratio2( 0 ),Diff1( 0 ),Diff2( 0 ), RSMK1( 0 ),RSMK2( 0 ),BUY1( false ),BUY2( false ),SELL1( false ), STP(FALSE), SELL2( false ),SELL3( false ),SYMBOL1( "VUG" ),SYMBOL2( "VTV" ); if Symbol = SYMBOL1 then begin Ratio1 = Close / Close of Data3; Ratio2 = Close of Data2 / Close of Data3;end else if Symbol = SYMBOL2 then begin Ratio1 = Close of Data2 / Close of Data3; Ratio2 = Close / Close of Data3;end; if Ratio1[RSBARS] > 0 and Ratio2[RSBARS] > 0 then begin //VFI MyVol = IFF( BarType >= 2 and BarType < 5, Volume, Ticks ); Inter = Log(TypicalPrice) - Log(TypicalPrice[1]); VInter = StdDev(Inter, 30); CutOff = Coef * VInter * Close; VAve = Average(MyVol, VPeriod)[1]; VMax = VAve * VCoef; VC = MinList(MyVol, VMax); MF = TypicalPrice-TypicalPrice[1]; if MF > CutOff then DirectionalVolume = VC else if MF < -CutOff then DirectionalVolume = -VC else DirectionalVolume = 0; if VAve <> 0 then VFI1 = Summation(DirectionalVolume, VPeriod) / VAve; VFI1 = WAverage(VFI1, 2); // RSMK Indicator Diff1 = Log(Ratio1) - Log(Ratio1[RSBARS]); Diff2 = Log(Ratio2) - Log(Ratio2[RSBars]); RSMK1 = XAverage(Diff1, 2) * 100; RSMK2 = XAverage(Diff2, 2) * 100; STP=Close < Average(Close, 50) and Close <(1-MAXLOSSPERC/100)*Close[1]; // BUY BUY1 = Symbol = SYMBOL1 and RSMK1 > RSMK2 and RSMK1> Average(RSMK1,MARSBARS); BUY2 = Symbol = SYMBOL2 and RSMK2 > RSMK1 and RSMK2 > Average(RSMK2, MARSBARS); if (BUY1 ) and VFI1 > 0 AND STP=FALSE then begin Buy ("BUY1") this bar at close;END; if (BUY2) and VFI1 > 0 AND STP=FALSE then Buy ("BUY2") this bar at close; //SELL IF Symbol = SYMBOL1 and RSMK1 < RSMK2 and RSMK1 < Average(RSMK1,MARSBARS) and RSMK1 < RSMK2[1] then SELL ("SELL1") THIS BAR AT CLOSE; IF Symbol = SYMBOL2 and RSMK2 < RSMK1 and RSMK2 < Average(RSMK2, MARSBARS) and RSMK2 < RSMK1[1] then SELL ("SELL2") THIS BAR AT CLOSE; IF Close < Average(Close, 50) and Close < (1 - MAXLOSSPERC / 100) * Close[1] THEN Sell ("STOP") this bar at close; end;
In his article in this issue, “Growth Or Value?”, author Markos Katsanos once again explores his intermarket edge with a rotation-like system that switches between two growth and value ETFs according to market conditions. To assist his trading decisions, he uses two indicators of his own construction to measure relative strength and detect bear markets.
Fortunately, Wealth-Lab has long included the indicators developed by Markos Katsanos: both the custom relative strength indicator (RSMK) and the volume flow indicator (VFI). As usual, this means that they can be used universally throughout our program features. These include the Building Blocks feature (which offer an easy way to create trading strategies without having to code them), the indicator profiler (which tells how much of an edge an indicator provides), a strategy optimization tool, and many more.
The chart in Figure 2 demonstrates implementing the trading system on a weekly chart of VUG.
FIGURE 2: WEALTH-LAB. This chart shows sample trades signaled by the trading system on a weekly chart of VUG. Data provided by Yahoo! Finance.
Coding in C# for use in Wealth-Lab 8 is shown here:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using WealthLab.TASC; namespace WealthScript1 { public class GrowthOrValue : UserStrategyBase { string symbol1 = "VUG", symbol2 = "VTV", indexSymbol = "SPY"; BarHistory etf1, etf2, index; RSMK rsmk1, rsmk2; SMA ma1, ma2, etfMA1, etfMA2; VFI vfi1, vfi2; double pctMaxLoss; public GrowthOrValue() { AddParameter("VFI Period", ParameterType.Int32, 30, 26, 30, 4); AddParameter("Rel. Strength Bars", ParameterType.Int32, 15, 13, 15, 2); AddParameter("Rel. Strength MA Bars", ParameterType.Int32, 20, 15, 25, 5); AddParameter("MA Period", ParameterType.Int32, 30, 20, 100, 10); AddParameter("Stop Loss", ParameterType.Int32, 7, 5, 9, 2); } public override void Initialize(BarHistory bars) { int vfiPeriod = Parameters[0].AsInt; int rsBars = Parameters[1].AsInt; int maRSBars = Parameters[2].AsInt; int maPeriod = Parameters[3].AsInt; double Coef = 0.2; pctMaxLoss = (1.0 - Parameters[4].AsInt / 100d); etf1 = GetHistory(bars, symbol1); etf2 = GetHistory(bars, symbol2); index = GetHistory(bars, indexSymbol); etfMA1 = SMA.Series(etf1.Close, 50); etfMA2 = SMA.Series(etf2.Close, 50); rsmk1 = RSMK.Series(etf1, indexSymbol, rsBars); rsmk2 = RSMK.Series(etf2, indexSymbol, rsBars); ma1 = SMA.Series(rsmk1, maRSBars); ma2 = SMA.Series(rsmk2, maRSBars); vfi1 = VFI.Series(etf1, vfiPeriod, maPeriod, cutoff: default, curtailCoeff: Coef); vfi2 = VFI.Series(etf2, vfiPeriod, maPeriod, cutoff: default, curtailCoeff: Coef); PlotBarHistory(etf1, "etf1"); PlotBarHistory(etf2, "etf2"); PlotIndicatorLine(rsmk1); PlotIndicatorLine(rsmk2); PlotIndicatorLine(ma1); PlotIndicatorLine(ma2); PlotIndicatorLine(vfi1); PlotIndicatorLine(vfi2); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { var buy1 = (rsmk1[idx] > rsmk2[idx]) && (rsmk1[idx] > ma1[idx]) && (vfi1[idx] > 0); var buy2 = (rsmk2[idx] > rsmk1[idx]) && (rsmk2[idx] > ma2[idx]) && (vfi2[idx] > 0); var Buy = (buy1 || buy2); if(Buy) PlaceTrade(buy1 ? etf1 : buy2 ? etf2 : null, TransactionType.Buy, OrderType.Market); } else { /* first ETF's exits */ if ((rsmk1[idx] < rsmk2[idx]) && (rsmk1[idx] < ma1[idx]) && (rsmk1[idx] < rsmk2[idx - 1])) PlaceTrade(etf1, TransactionType.Sell, OrderType.Market); if(etf1.Close[idx] < etfMA1[idx]) PlaceTrade(etf1, TransactionType.Sell, OrderType.Stop, etf1.Close[idx - 1] * pctMaxLoss); /* second ETF's exits */ if ((rsmk2[idx] < rsmk1[idx]) && (rsmk2[idx] < ma2[idx]) && (rsmk2[idx] < rsmk1[idx - 1])) PlaceTrade(etf2, TransactionType.Sell, OrderType.Market); if(etf2.Close[idx] < etfMA2[idx]) PlaceTrade(etf2, TransactionType.Sell, OrderType.Stop, etf2.Close[idx - 1] * pctMaxLoss); } } } }
The RSMK indicator and ETF switching system presented in the article “Growth Or Value?” in this issue by Markos Katsanos is available for download at the following link for NinjaTrader 8:
Once the file is downloaded, you can import the indicator into NinjaTrader 8 from within the control center by selecting Tools → Import → NinjaScript Add-On and then selecting the downloaded file for NinjaTrader 8.
You can review the indicator source code in NinjaTrader 8 by selecting the menu New → NinjaScript Editor → Indicators folder from within the control center window and selecting the file.
A sample chart displaying the indicators and switching system is shown in Figure 3. Note that the exit logic used affects when entry orders are placed, and therefore affects the trade signals. In this example chart implementation, more trades are signaled compared with the trades shown in the article.
FIGURE 3: NINJATRADER. This sample chart displays the RSMK indicators and switching system. The exit logic used affects when entry orders are placed, and in this example chart implementation, more trades are signaled than in the article.
The ETF rotation strategy described by Markos Katsanos in his article in this issue, titled “Growth Or Value?”can be easily implemented in NeuroShell Trader. Select “new indicator” from the insert menu and use the indicator wizard to create the following indicators:
Relative Strength Indicator: RSMK: Mul2( ExpAvg( Momentum( Ln( Divide( Close, S&P500 Close) ), 90), 3), 100) Volume Flow Indicator: TYPICAL: Avg3 ( High, Low, Close) CUTOFF: Multiply3 ( 0.2, StndDev ( Momentum (Ln (TYPICAL),1), 30 ), Close ) VAVE: LagAvg ( Volume, 1, 130 ) VC: Min2 ( Volume, Multiply2 ( 2.5, VAVE ) ) MF : Momentum (TYPICAL, 1 ) VFI: Divide ( Sum( IfThenIfThenElse ( A>B(MF,CUTOFF), VC, A<B(MF, Negative(CUTOFF)), Negative(VC), 0 ), 130 ), VAVE )
To create a rotation trading system on a chart with a chart page for each of the two ETFs, select “new trading strategy” from the insert menu and enter the following in the appropriate locations of the trading strategy wizard:
BUY LONG CONDITIONS (All of which must be true): RSMK = ChartPageMax ( RSMK ) A>B ( RSMK, MovAvg( RSMK, 20 ) A>B ( VFI, 0 ) SELL LONG CONDITIONS (1 of which must be true): AND4 ( A=B ( RSMK, ChartPageMin ( RSMK ) ) A<B ( RSMK, MovAvg( RSMK, 20 ) A<B ( RSMK, Lag( ChartPageMin ( RSMK ) ), 1 ) A>B ( VFI, 0 ) ) AND2 ( A<B( Close, MovAvg(Close,50 ), A<B ( Close, Multiply( 0.97, Lag(Close,1) )
After entering the system conditions, you can also choose whether or not the parameters should be optimized. After backtesting the trading strategy, use the detailed analysis button to view the backtest and trade-by-trade statistics for the system.
FIGURE 4: NEUROSHELL TRADER. This NeuroShell Trader chart shows the RSMK indicator and trading system.
Users of NeuroShell Trader can go to the Stocks & Commodities section of the NeuroShell Trader free technical support website to download a copy of this or any previous Traders’ Tips.
Here is TradingView Pine Script code implementing the rotation strategy for trading value and growth ETFs, as presented in Markos Katsanos’ article in this issue titled “Growth or Value?”
// TASC Issue: December 2023 - Vol. 41, Issue 13 // Article: Growth or Value? // Article By: Markos Katsanos // Language: TradingView's Pine Script® v5 // Provided By: PineCoders, for tradingview.com //@version=5 string it = 'TASC 2023.12 Growth and Value Switching System' string st = 'GoV' indicator(it, st, true) // --- Constants --- string it0 = 'Growth Equity' string it1 = 'Value Equity' string it2 = 'RS Reference Index' string it3 = 'RS Period' string it4 = 'RS MA Period' string it5 = 'VFI Period' string it6 = 'Stop-loss, %' color ic1 = #C5D7F8 color ic2 = #324B76 color ic3 = #8BB0F2 color ic4 = #D8E5FA color ic5 = #F1F5FD // --- Inputs --- string iSym1 = input.symbol('VUG', it0) string iSym2 = input.symbol('VTV', it1) string iSymI = input.symbol('SPY', it2) int iRSBars = input.int(15, it3) int iMABars = input.int(20, it4) int iPeriod = input.int(30, it5) float iMaxLossP = input.float(7.0, it6) // --- Functions --- // @function Custom Relative Strength Indicator (RSMK) // @param base Input time series // @param im Reference time series // @param bars N bars for RS calculation // @param sk Smoothing constant // @returns Relative Strength RS (float base, float im, int bars, int sk=2) => float r12 = base / im float rs1 = math.log(r12) - math.log(nz(r12[bars], 0.0)) ta.ema(rs1, sk) * 100.0 // @function Custom Volume Flow Indicator (VFI) // @param avg Input time series (hlc3) // @param vol Input time series (volume) // @param period N bars for VFI calculation // @param Coef VFI coefficient // @param VCoef Volume cutoff // @returns Volume Flow Indicator VFI (float avg, float vol, int period, float Coef=0.2, float VCoef=2.5, int smooth=2) => // float inter = math.log(avg) - math.log(nz(avg[1])) float Cuttoff = Coef * ta.stdev(inter, 30) * close float Vave = nz(ta.sma(volume, period)[1], 1.0) float Vc = math.min(volume, Vave * VCoef) float MF = ta.change(avg) float VCP = switch MF > Cuttoff => Vc MF < -Cuttoff => -Vc => 0.0 float vfi1 = math.sum(VCP, period) / Vave ta.wma(vfi1, smooth) // --- Calculations --- float ci = request.security(iSymI, timeframe.period, close) float c1 = request.security(iSym1, timeframe.period, close) float h1 = request.security(iSym1, timeframe.period, hlc3) float v1 = request.security(iSym1, timeframe.period, volume) float c2 = request.security(iSym2, timeframe.period, close) float h2 = request.security(iSym2, timeframe.period, hlc3) float v2 = request.security(iSym2, timeframe.period, volume) float rsmk1 = RS(c1, ci, iRSBars, 2) float rsmk2 = RS(c2, ci, iRSBars, 2) float vfi = VFI(h1, v1, iPeriod, 0.2, 2.5) float mars1 = ta.sma(rsmk1, iMABars) float mars2 = ta.sma(rsmk2, iMABars) float dist = 2.0 * ta.atr(10) // --- Trade Signals --- var string opPos = '' // Long: bool long1 = opPos == '' and rsmk1 > rsmk2 and rsmk1 > mars1 and vfi > 0.0 bool long2 = opPos == '' and rsmk2 > rsmk1 and rsmk2 > mars2 and vfi > 0.0 bool long = long1 or long2 // Exit: bool exit1 = opPos == iSym1 and rsmk1 < rsmk2 and rsmk1 < mars1 and rsmk1 < rsmk2[1] bool exit2 = opPos == iSym2 and rsmk2 < rsmk1 and rsmk2 < mars2 and rsmk2 < rsmk1[1] bool SL1 = opPos == iSym1 and c1 < ta.sma(c1, 50) and c1 < ((1.0 - iMaxLossP/100) * c1[1]) bool SL2 = opPos == iSym2 and c2 < ta.sma(c2, 50) and c2 < ((1.0 - iMaxLossP/100) * c2[1]) bool exit = exit1 or exit2 or SL1 or SL2 // Update open position opPos := switch long1 => iSym1 long2 => iSym2 exit => '' => opPos[1] // Plot plotshape(long1, 'L1', shape.triangleup, location.belowbar, color.purple, text = 'long Growth',size=size.small) plotshape(long2, 'L2', shape.triangleup, location.belowbar, color.green, text = 'long Value', size=size.small) plotshape(exit1 or SL1, 'E1',shape.xcross,location.abovebar, color.purple, text = 'exit Growth',size=size.tiny) plotshape(exit2 or SL2, 'E2',shape.xcross,location.abovebar, color.green, text = 'exit Value', size=size.tiny) // --- Exploration ---- var table T = table.new(position.bottom_right,2,8,ic1) T.cell(0, 0, 'G&V Switching System', text_color = #FFFFFF, bgcolor = ic2) T.merge_cells(0, 0, 1, 0) T.cell(0, 1, 'Created by Markos Katsanos', bgcolor = ic3) T.merge_cells(0, 1, 1, 1) T.cell(0, 2, 'Growth Equity:', bgcolor = ic5) T.cell(1, 2, iSym1, bgcolor = ic5) T.cell(0, 3, 'Value Equity:', bgcolor = ic5) T.cell(1, 3, iSym2, bgcolor = ic5) T.cell(0, 4, 'RS Growth:') T.cell(1, 4, str.format('{0}', rsmk1),bgcolor = ic4) T.cell(0, 5, 'RS Value:') T.cell(1, 5, str.format('{0}', rsmk2),bgcolor = ic4) T.cell(0, 6, 'VFI Growth:') T.cell(1, 6, str.format('{0}', vfi), bgcolor = ic4) T.cell(0, 7, 'Current Position:') T.cell(1, 7, opPos, bgcolor = ic4)
The indicator is available on TradingView from the PineCodersTASC account:
https://www.tradingview.com/u/PineCodersTASC/#published-scripts
FIGURE 5: TRADINGVIEW. This demonstrates the growth and value switching system on a chart of the Vanguard Growth ETF.