Schaff Trend Indicator - STC

Hi all,

Just wondering if anybody have coded the Schaff Trend Indicator (STC) on AFL and is willing to share with the community - just feel that doing a WET approach (Write Everything Twice) on a popular indicator.

If anybody has it, kindly share it on this post-would be highly appreciated! I also want to thank Tom and the other people on the forum, I've been using Amibroker for the past few months and develop and deep interest in trading and been picking up Python (my university background is in Philosophy and German).

Hi @Bob,

Good idea! Did not know it has a term (WET) too. Well, at times I write a piece 4-5 times (WEF may be :slight_smile: ) until able to write on my own - helps to breakdown any complicated concept into small sections.

STC is Stochastic of MACD, very much similar to Stochastic RSI. Here is a beautiful piece of code by Larry (portfoliobuilder) which shows how Stochastic of RSI can be formulated using AFL:

Please study the code to learn how to find Stochastics of an array. Then, you remove the RSI and replace that with MACD to get a basic version of STC. But the creator of STC - Doug Schaff, implements some other techniques to further smooth the indicator.

Also read this article, there is a EasyLanguage code too, that, can be ported to AFL very easily:

The Schaff Trend Cycle

Simply uploading the code won't help in the long run. And this Forum believes in helping not in spoon-feeding, so, would request you to share your efforts first.

8 Likes

@Bob I wish Schaff had kept this simple as mentioned above by @Cougar and we just took a stochastic of the MACD. But I'm a bit of an indicator junkie so I looked up Schaff's original publication (easy to find on the internet).

I can't believe I just spent a couple of hours on this and I am not at all familiar with EasyLanguage so I preface almost every line of my following interpretation with "this is my best guess". The following was provided by Doug Schaff to the general public in 2008 (despite his copyright of 1999).

image
image

// Schaff Trend Cycle afl v1.0
// attempt by portfoliobuilder
///@link https://forum.amibroker.com/t/schaff-trend-indicator-stc/13355/3
 
MA1 				= Param( "MACD Short MA Periods", 23, 5, 36 );
MA2 				= Param( "MACD Long MA Periods", 50, 10, 100 );
TrendCycleLength 	= Param( "Trend Cycle Length", 10, 5, 20 ); // LB periods for "Stochastic" calculations
Factor 				= 0.5;
Frac1 				= 0; // Fraction 1
Frac2 				= 0; // Fraction 2
PF = PFF			= 0; // steps Schaff uses to calculate indicator

// Doug Schaff calls this step Calculating "a MACD Line"
// I found that confusing as most traders would just use the term "MACD"
// to not get mixed up with the well known "Signal LINE" of the MACD
xMACD = MACD( MA1, MA2 ); // AmiBroker's built in MACD is using exponential moving averages, as per Schaff's intent

// Schaff writes "1st Stochastic: Calculate Stochastic of a MACD"
Value1 = LLV( xMACD, TrendCycleLength );
Value2 = HHV( xMACD, TrendCycleLength ) - Value1; // this line generates the denominator of a stochastic

// Schaff calls this next step the %FastK of MACD
// I know pretty much nothing about EasyLanguage so
// here is where my AmiBroker skills may not match the supplied EasyLanguage code
Frac1 = IIf( Value2 > 0, 100 * ( xMACD - Value1 ) / ( Value2 + 1e-9 ), Ref( Frac1, -1 ) );

// Schaff calls this step the "Smoothed calculation for %FastD of MACD"
PF = AMA( Frac1, Factor ); 

//2nd Stochastic: Calculate Stochastic of Smoothed Percent FastD, "PF" above.
Value3 = LLV( PF, TrendCycleLength );
Value4 = HHV( PF, TrendCycleLength ) - Value3;

// Schaff's %FastK of PF
Frac2 = 100 * ( PF - Value3 ) / ( Value4 + 1e-9 );

// his Smoothed calculation for %FastD of PF
PFF = AMA( Frac2, Factor );


Plot( PFF, "Schaff Trend Cycle (" + MA1 + ", " + MA2 + ", " + TrendCycleLength + ")", colorAqua, styleLine | styleThick );
Plot( 75, "", colorGreen, styleLine | styleDashed );
Plot( 25, "", colorRed, styleLine | styleDashed );

Produces a chart like this,
image

Perhaps someone familiar with either the indicator or EasyLanguage can make any necessary corrections.
And on a personal pet peeve of mine, I always found Tradestation codes that use uninformative variable names like "value1" and "value2" kind of stupid.

7 Likes

@PortfolioBuilder,

Frac1 It is not correct.
Ref() is not for self referencing.
Just use

Frac1 = Nz(((xMACD- Value1) / Value2)) * 100;

Besides LLV and HHV are not required.
All that is needed is Stochastic function

Here is Schaff

function SchaffTC(period, s_len, l_len) {
	/// Schaff Trend Cycle
	/// AFL version by fxshrat@gmail.com
	/// @link https://forum.amibroker.com/t/schaff-trend-indicator-stc/13355/4
	local MCD, Frac1, Frac2, PF, PFF;
	MCD = MACD(s_len, l_len);

	//StochK of MACD
	C = H = L = MCD;
	Frac1 = StochK(period, 1);
	RestorePriceArrays();

	//Smoothed calculation
	PF = AMA(frac1, factor = 0.5);

	//StochK of PF
	C = H = L = PF;
	Frac2 = StochK(period, 1);
	RestorePriceArrays();

	//Smoothed calculation
	PFF = AMA(frac2, factor);
	return PFF;
}

period = Param("Schaff Period", 10, 1, 100, 1);
short_period = Param("Short Period", 23, 1, 100, 1);
long_period = Param("Long Period", 50, 1, 100, 1);

Schaff = SchaffTC(period, short_period, long_period);

Plot(Schaff,"Schaff Trend Cycle", colorRed, styleLine);

5 Likes

@fxshrat thanks for the tidy code. I recommend everyone go through the exercise of trying to code something they are totally unfamiliar with. It was educational, specifically I needed to learn and understand AmiBroker's AMA function. And then your code with the assignment of price arrays to your calculated variable followed by RestorePriceArrays(). Very nice (and educational)!

Perhaps you can clarify for me about the self referencing with REF(),

I was trying to avoid looping (while just reconstructing the EasyLanguage code line by line) and the calculations do seem to work (your code and mine appear to produce the same results). Will it result in incorrect calculations or other execution problems? Thanks in advance.

EasyLanguage code such as this:

Variables: ..., Frac1(0), ....;

....

....

Frac1 = IFF( Value2 > 0, 100 * ( xMACD - Value1 ) / Value2, Frac1[1]);

Can be translated to AFL via...

Loop version:

Frac1 = 0;
for ( i = 1; i < BarCount; i++ ) {
    prev = Frac1[ i - 1 ];// previous bar's Frac1
    Frac1[ i ] = IIf(Value2[ i ] > 0, 100 * ( xMACD[ i ] - Value1[ i ] ) / Value2[ i ], prev );
}

Non-Loop version (-> AFL array processing):

Frac1 = ValueWhen(Value2 > 0, 100 * ( xMACD - Value1 ) / Value2);

Or just preventing infinite bar result or division by zero via:

Frac1 = 100 * Nz(( xMACD - Value1 ) / Value2, 0);

or

Frac1 = 100 * ( xMACD - Value1 ) / (Value2+1e-9);

As for RestorePriceArrays()... actually it is need to be called just once within that upper posted function (to restore original price arrays again if needed in further code).

So...

function SchaffTC(period, s_len, l_len) {
	/// Schaff Trend Cycle
	/// AFL version by fxshrat@gmail.com
	/// @link https://forum.amibroker.com/t/schaff-trend-indicator-stc/13355/6
	local MCD, Frac1, Frac2, PF, PFF;
	MCD = MACD(s_len, l_len);

	//StochK of MACD
	C = H = L = MCD;
	Frac1 = StochK(period, 1);

	//Smoothed calculation
	PF = AMA(frac1, factor = 0.5);

	//StochK of PF
	C = H = L = PF;
	Frac2 = StochK(period, 1);
	RestorePriceArrays();

	//Smoothed calculation
	PFF = AMA(frac2, factor);
	return PFF;
}

period = Param("Schaff Period", 10, 1, 100, 1);
short_period = Param("Short Period", 23, 1, 100, 1);
long_period = Param("Long Period", 50, 1, 100, 1);

Schaff = SchaffTC(period, short_period, long_period);

Plot(Schaff,"Schaff Trend Cycle", colorRed, styleLine);


13 Likes

@fxshrat @portfoliobuilder

Good learning! Thank you for sharing...... This is how AFL is unique. To be honest the one I had coded was simple translation of the EasyLanguage code.

EasyLanguage is very easy to translate from to AFL. There are few "shortcuts" that you can learn and use later when translating EasyLanguage.

Many functions translate directly. And there is one programming pattern in EasyLanguage that pretty much always translates either to AMA or IIR function. I am talking about recurrent formulation like this:

{ EasyLanguage }
Output = IIF( CurrentBar <= 1, Input, Factor * Input - ( 1 - Factor ) * Output[ 1 ] );

{ or more convoluted version of the same }
Output = IIF( CurrentBar <= 1, Input, Output[ 1 ] + (Factor * ( Input - Output[ 1 ] ) ) );

{ or yet another variation }
Output = IIF( CurrentBar <= 1, Input, ForwardFactor * Input - BackFactor * Output[ 1 ] );

As you can see the formula is recursive as it uses "previous" bar value of the output (Output[ 1 ]) to produce current bar value.

Such statements always directly translate to AmiBroker's AMA or AMA2.

// AFL version
Output = AMA( Input, Factor );

// or for the last variation
Output = AMA2( Input, ForwardFactor, BackwardFactor );

As you can see AFL version is much cleaner. Literally thousands of Tradestation codes can be efficiently translated using this pattern. You just need to learn to spot these typical programming block.

For more details:
http://www.amibroker.com/f?ama

For other self-referencing / recursive patterns see: Doubt when using self referencing

15 Likes

Great post Tomasz! Many thanks again.....