S&P 500 Reversal Coding System

I’m just getting started with AFL and I coded the simple S&P 500 Reversal system below.

If you have a moment to read it, I welcome feedback on errors and suggested improvements.

// Simple S&P 500 Reversal System -- comments at bottom after the code

SetTradeDelays( 0, 0, 0, 0 );             // no trade delays

SetOption("MaxOpenPositions", 1);
SetOption("ExtraColumnsLocation", 1 ); 

SetCustomBacktestProc( "" );

if ( Status( "action" ) == actionPortfolio )
    bo = GetBacktesterObject();
    bo.Backtest(); // run default backtest procedure

    // read Net Profit, Winners and Losers profits from the report
    st = bo.GetPerformanceStats( 0 );
    netProfit = st.GetValue( "NetProfit" );
    tradeProfits = st.GetValue("WinnersTotalProfit") + st.GetValue("LosersTotalLoss");

    bo.AddCustomMetric( "Trading profits", tradeProfits ); 
    bo.AddCustomMetric( "Interest earnings", netProfit - tradeProfits );


BuyPrice  = C;
SellPrice = C;

dnPct = Optimize("DN %", 7.2, 5.0, 10.0, 0.05);
upPct = Optimize("UP %", 8.4, 5.0, 10.0, 0.05);

// prime bar 0
s105exp[0]  = 1;                  // set initial exposure: 1 is long, 0 is out
s105stop[0] = C[0] * (1-dnPct);   // set initial stop
s105Flag[0] = 1;                  // set initial trade flag: 1 is buy, -1 is sell, 9 is end of data and long

for( i = 1; i < BarCount; i++ )
	if (s105exp[i-1] == 1)				  // if Long
		if (C[i] >= s105stop[i-1])		// stay in -- must close under
			s105exp[i]  = 1;			// maintain long
			s105stop[i] = Max(s105stop[i-1],(C[i]*(1-(dnPct/100))));		// update sell stop
		else							            // sell and go flat 
			s105Flag[i] = -1;
			s105exp[i]  = 0;
			s105stop[i] = C[i]*(1+(upPct/100));		// set buy stop
	else								            // else if out	
		if (C[i] <= s105stop[i-1])		// stay out -- must close over
			s105exp[i]  = 0;
			s105stop[i] = Min(s105stop[i-1],(C[i]*(1+(upPct/100))));		// update buy stop
		else							            // buy and go long
			s105Flag[i] = 1;
			s105exp[i]  = 1;
			s105stop[i] = C[i]*(1-(dnPct/100));		// set sell stop

if (s105Flag[BarCount-1] == 0) { s105Flag[BarCount-1] = 9;}

AddColumn(s105exp,  "S105 Exp", 1.0);
AddColumn(s105stop, "S105 Stop", 1.3);
AddColumn(s105Flag, "S105 Flag", 1.0 );

Buy  = (s105Flag == 1);
Sell = (s105Flag ==-1);

Filter = (s105Flag != 0);

A simple weekly reversal system for the S&P 500 Index (SPX) as an AFL coding exercise.

Backtester Periodicity set to Weekly.

Initial state set to Long.

Sell when the S&P falls -7.2% using a weekly close trailing stop.

Buy when the SPX rises +8.4% using a similar stop. 

Reversal systems, by definition, won't miss a major move up or hold a major move down. 

But their numerous "whipsaws" make them hard to stick with.

This concept is courtesy of Ned Davis Research and NDR Chart S105.

A similar system for the NASDAQ 100 Index is discussed on p.51 of Ned's 2014 book "Being Right or Making Money".

This code is for educational purposes only and is not intended as investment advice.


Welcome @stevo713 It has been a while since I read the Ned Davis book (love all his stuff) but I also think that model was based upon Marty Zweig’s 4% model published around 1986 in “Winning On Wall Street” (long before Davis’ books).

Your code may be nice but you are in AmiBroker world now. Array’s can help you avoid many of the loops you seem to want to write.

Here is a brief example that only took a few minutes to code (that’s why it may be completely wrong!) but take a look and experiment with it.

DownThreshold = Optimize( "DN %", -4, -10, -2, 0.05 );
UpThreshold = Optimize( "UP %", 4, 2, 10.0, 0.05 );

PercentChange = ROC( C, 1 ); // if running on weekly data

BuySignal = PercentChange > UpThreshold;
SellSignal = PercentChange < DownThreshold;

Buy = BuySignal;
Sell = SellSignal;

Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );

I’ll try to flush out the code in a few hours.


Thanks, Larry – I forgot about Marty Z’s book (it’s “only” been ~30 years).

As I was reading in the User’s Guide about EXREM I found this link: 4% System

Very similar to your code.

I think the only systematic difference is how I “hold” my stops with




1 Like

@stevo713 We should have known, with a system that was 30 years old there must be some AmiBroker code out there somewhere! Marty Z's book was already about 6 or 7 years old when I read it and it was one of the two or three reasons I became interested in quantifiable investing/trading. Too bad it took me until 2015 to buy AmiBroker. If you like Ned Davis and Marty Zweig you must be a good guy, and if we ever get together I'll buy the first round.

My code somewhat flushed out is below, not proof read but seems to work as I went to test it on the old Value Line Geometric index (but I am not sure if Marty Z meant the Arithmetic, and I don't seem to have my old well worn copy of his book anywhere).

// Constants

Float = 100000;
MaxPositions = 1;

// Backtester Options

SetOption( "CommissionAmount", 0.0 );
SetOption( "MaxOpenPositions", MaxPositions );
SetOption( "InitialEquity", Float );
SetOption( "AllowSameBarExit", True );
SetOption( "AllowPositionShrinking", True );
SetPositionSize( 100 / MaxPositions, spsPercentOfEquity );
SetTradeDelays( 0, 0, 0, 0 ); // trading on the Close, if you want to trade on Next Weekly Open, adjust these numbers

DownThreshold = Optimize( "DN %", -4, -10, -2, 0.05 );
UpThreshold = Optimize( "UP %", 4, 2, 10.0, 0.05 );

PercentChange = ROC( C, 1 ); // if running on weekly data

BuySignal = PercentChange > UpThreshold;
SellSignal = PercentChange < DownThreshold;

Buy = BuySignal;
Sell = SellSignal;

Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );

Short = Cover = 0;
InLong = Flip(Buy, Sell);
AddColumn(C, "Close");
AddColumn(PercentChange, "Weekly % Change");
AddColumn(Buy, "Buy", 1.0,colorBlack, iif(Buy==1, colorLime, colorDefault));
AddColumn(InLong, "InLong", 1.0, colorDefault, IIf(InLong, colorGreen, colorDefault));
AddColumn(Sell, "Sell", 1.0,colorBlack, iif(Sell==1, colorRed, colorDefault));

I use Explore for debuggin. Exploration result looks like this.

And surprisingly the risk adjusted results are substantially better than buy-and-hold.


But before you go trade this with your baby's milk money, I have not had time to proof read it or really debug it!


Nice code & I like the color-coded columns – helpful – thank you.

I’m not surprised about the good risk-adjusted numbers – simple reversal systems avoid prolonged downtrends so exposure is often low and risk-adjusted performance is high(er).

These numbers on the backtest look way too high. When I tried to replicate your test, it looked like the last week’s returns for the long signals are being cut off. In other words, the sell signal week of -4% is not being included in the long returns for the prior long signal. I suspect there is a problem with the Trade Delay in your AFL script conflicting with the Backtester settings in the Trades tab. You can see it clearly when you drop the weekly returns in a spreadsheet and go week by week and compare the returns for each buy signal.

@paulgraham I agree there is a “future leak” in that it trades on the Open of the week that gets its signal data at the end of the weekly Close. It was just a code example I whipped up and shouldn’t have posted the backtest results (but I did say that I had not debugged anything).

So more properly the backtester settings should be adjusted to either trade on the Close, or set trade delays and trade on the next weekly Open.

I think it can be hard-coded,

SetTradeDelays( 0, 0, 0, 0 ); 
BuyPrice = Close;
SellPrice = Close;

or the other choice,

BuyPrice = Open;
SellPrice = Open;

Either way, it still beats buy-and-hold both on an absolute basis and substantially on a risk-adjusted basis.


Thanks for the clarification. I appreciate your work on this subject.