AFL Code Examples

These code examples are mainly taken from the book “Quantitative Trading Systems” (2nd Edition) by Howard B Bandy. Some of them are also taken from various AFL tutorials on Youtube.

Relevant Tutorials

Code Snippets

Entry and Exit at Fixed Intervals

SetTradeDelays(0, 0, 0, 0);
EntryInterval = Param("Entry Interval", 6, 4, 100, 1);
HoldDays = Param("Hold Days", 3, 1, 100, 1);

BuyPrice = C;

Buy = (BarIndex() % EntryInterval) == 0;  //Enter every EntryInterval days
Sell = BarsSince(Buy) >= HoldDays;  //Exit after HoldDays days

Sell = ExRem(Sell, Buy);  //No more sell signal until you reach a buy signal

Scaling In

/** Method 1: 10% of equity per trade **/

SetPositionSize(10, spsPercentOfEquity);

/** Method 2: First position 50%, subsequent 10% **/

PosSize = IIf(Buy==1, 50, IIf(Buy == sigScaleIn, 10, 0));
SetPositionSize(PosSize, spsPercentOfEquity);

/** Method 3: First position is 50% of equity, subsequent scale ins at (50/number of scales)*percent of current position **/


//Determine how many times we scale in (excluding the first purchase)
ScalesSinceEntry = SumSince(Buy==1, Buy==sigScaleIn);	

//Assign Position Sizes
ScalePosSize= Nz(50/ScalesSinceEntry, 0);

EntryPosSize = 50;

//Set position size for first position
SetPositionSize(EntryPosSize, spsPercentOfEquity);

//Update the position size when buy==sigscalein
SetPositionSize(ScalePosSize, IIf(Buy==sigScaleIn, spsPercentOfPosition, spsNoChange));	

Scaling Out

Scaling in and out are both coded in the Buy array.

/** Buy when price breaks out of a 5 days high, scale out 10% every 5 days. Sell when price breaks below 20 days low **/
bullCond = H>Ref(HHV(H, 5), -1);
Sell = L<Ref(LLV(L, 20), -1);

bullCond = ExRem(bullCond, Sell);   //Remove excess bullCond signals

Buy = IIf(bullCond, 1, IIf(BarsSince(Buy==1)%5==0, sigScaleOut, 0));

Sell = ExRem(Sell, Buy);   //Remove excess sell signals

//Buy 50% of equity, scale out 10% each time
PosSize = IIf(Buy==1, 50, IIf(Buy==sigScaleOut, 10, 0));  
SetPositionSize(PosSize, spsPercentOfEquity);

Profit Target

/*Using a 10% profit target. No exit otherwise.
 This example shows how we can use a scalar in our code */

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

Buy = Cross(MA(C, 5), MA(C, 25));

Sell = 0;

ProfitTargetPercent = 10;

EntryPrice = 0; //This is a scalar

// Loop through all the bars
// There are BarCount bars, indexed from 0 to BarCount-1

for(i=0; i<BarCount; i++)
{
	if(EntryPrice == 0 AND Buy[i] == 1)	//Check for being flat and entering a new trade
	{
		EntryPrice = BuyPrice[i];
	}else if (EntryPrice > 0 AND H[i] > EntryPrice*(1.0 + 0.01*ProfitTargetPercent))
	{
		Sell[i] = 1;
		SellPrice[i] = EntryPrice*(1.0 + 0.01*ProfitTargetPercent);
		EntryPrice = 0; 	//Reset entry price
	}
}

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

Multiple Exits

/*
Three Exits:
1. If first profit target is hit, scale out 50% of position
2. If second profit target is hit, exit completely
3. If trailing stop is hit, exit completely

This demonstrates how to code a trailing stop exit and how to do a scale out

*/


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

Buy = Cross(MA(C, 5), MA(C, 25));
Sell = 0;

//Targets (in percentages)
FirstProfitTarget = 10;
SecondProfitTarget = 20;
TrailingStop = 10;

//Scalars to keep track of prices while in trade
PriceAtBuy = 0;
HighSinceBuy = 0;
Exit = 0;

for (i=0; i< BarCount; i++)
{

	//Check for being flat and entering a new trade
	
	if (PriceAtBuy ==0 AND Buy[i] == 1)
	{
		PriceAtBuy = BuyPrice[i];
	}else if (PriceAtBuy > 0)	//If there is an existing trade
	{
		//Update HighSinceBuy if current high is higher than HighSinceBuy
		HighSinceBuy = Max(High[i], HighSinceBuy);	
		
		if (Exit == 0 AND High[i] >= (1+ FirstProfitTarget*0.01)*PriceAtBuy)												       
		//First Profit Target Hit, scale out
		{
			Exit = 1;
			Buy[i] = sigScaleOut;
			BuyPrice[i] = (1+ FirstProfitTarget*0.01)*PriceAtBuy;
		}
		
		if (Exit == 1 AND High[i] >= (1+SecondProfitTarget*0.01)*PriceAtBuy)	
		//Second Profit Target Hit, exit completely
		{
			Exit = 2;
			SellPrice = Max(Open[i],(1+SecondProfitTarget*0.01)*PriceAtBuy);
		}
		
		if (Low[i] <= (1-TrailingStop*0.01)*HighSinceBuy)			
		//Trailing stop is hit, exit completely
		{
			Exit = 3;
			SellPrice[i] = Min(Open[i], (1-TrailingStop*0.01)*HighSinceBuy);
		}
		

		if (Exit >= 2)
		{
			Buy[i] = 0;

			//Assign the correct sell code. 2 = Max Loss, 3 = Profit, 4 = Trail
			Sell[i] = Exit + 1; 
			

			//Reset scalars
			Exit = 0;
			PriceAtBuy = 0;
			HighSinceBuy = 0;
		
		}
	}

}

SetPositionSize(50, spsPercentOfEquity);

//Set position size to 50% of position when scaling out (i.e. Exit = 1 or Buy == sigScaleOut)
SetPositionSize(50, spsPercentOfPosition*(Buy == sigScaleOut));

Ideal Entries

This code allows us to set the desired profit, maximum acceptable risk and holding period, and finds all the candles that satisfy such parameters.

  • Gain is computed from Close of entry day to Close of exit day.
  • Risk is computed from Close of entry day to lowest Low while in trade.

Once you identify the ideal entry and exit candles, you can working on designing a system that generates signals corresponding to the ideal entry and exit candles.

The code plots hollow Up and Down triangles for the ideal entry and exit candles, respectively. It also includes a simple moving average crossover system that plots a solid Up arrow when a Buy signal occurs.

If all the solid Up arrows correspond perfectly with the hollow Up triangles, we have a perfect system that matches our criteria.

Unfortunately, this is not the case. The objective is to find a system that matches the hollow Up and Down triangles as closely as possible.

One possible use for this code is to use Amibroker’s AddColumn() statements to generate tabular results that can be analysed with a spreadsheet or fed into a machine learning model to train the model. The code below added a AddColumn() statement for the “IdealEntry” array. You can view this array using the “New Analysis window > Explore” feature.

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

/*This code looks into the future to compute potential gain and potential loss from the present.*/

// Defining and plotting the hollow triangles for the ideal system

DaysAhead = Param("Days Ahead", 5, 1, 20, 1);
GainAhead = 100.0 * (Ref(C, DaysAhead) - C) / C;
RiskAhead = 100.0 * (C- LLV(Ref(L, DaysAhead), DaysAhead)) / C;

DesiredProfit = Param("Desired Profit", 5, 0, 5, 10, 0.5);
MaximumRisk = Param("Maximum Risk", 3, 0.5, 10, 0.5);

IdealEntry = (GainAhead >= DesiredProfit) AND (RiskAhead <= MaximumRisk);

HoldDays = Param("Hold Days", 3, 1, 60, 1);

IdealExit = BarsSince(IdealEntry) >= HoldDays;
IdealExit = ExRem(IdealExit, IdealEntry); // Remove extra exits, but show all entries

IdealShape = IdealEntry * shapeHollowUpTriangle + IdealExit * shapeHollowDownTriangle;

IdealColor = IIf(IdealEntry, colorPaleGreen, colorPink);
IdealPosition = IIf(IdealEntry, Low, High);
PlotShapes(IdealShape, IdealColor, 0, IdealPosition);

/** Testing a simple moving average crossover system. Alter the code below to test your own system. **/

// Start of system
MALength1 = Param("MA Length 1", 5, 1, 50, 1);
MALength2 = Param("MA Length 2", 20, 1, 50, 1);
MA1 = MA(C, MALength1);
MA2 = MA(C, MALength2);
Buy = Cross(MA1, MA2);
// End of system

//Plot system (up arrows and moving averages)
IndicatorShape = Buy * shapeUpArrow;
IndicatorColor = colorGreen;
IndicatorPosition = IIf(Buy, Low, High);
PlotShapes(IndicatorShape, IndicatorColor, 0, IndicatorPosition);
Plot(MA1, "MA1", colorGreen, styleLine);
Plot(MA2, "MA2", colorBlue, styleLine);
GraphXSpace = 5;

/** Exploration Code **/

Filter = 1;
AddColumn(IdealEntry, "Ideal Entry", 1);

Plotting the Equity Curve

e = Equity();
Maxe = LastValue(Highest(e));

Plot(e, "Equity Curve", colorBlue, styleLine|styleOwnScale, 0, Maxe);

Generic Binary Search

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_13_Anticipating_Signals.

//GenericBinarySearch.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 192

Length1 = 1;
Length2 = 36;
AccuracyTarget = 0.0001;

/* 
A cross occurs when A-B goes from positive to zero to negative, or vice versa.
Therefore, if we can calculate the value of A (or B) when A-B goes to zero, we can anticipate the price of A that is needed for a crossover.
For instance, if we know that when A = 12, A-B=0, we can infer that when A = 12.1, A-B>0 (indicating a positive crossover).
*/

function ZeroToFind(P)
{
	
	FTZ = MA(P, Length1) - MA(P, Length2);	//Modify this statement for other crossovers (e.g. Cross between RSI and the value 20)
	return FTZ;
}


//To determine the value of A when A-B=0, we can do a binary search

BC = LastValue(BarIndex());		//BC is the index of the final bar in the existing array

TF = Ref(C, 1);		//TF is the temp array used for the calculations. It stores the value of the close one bar in the future. The code assumes that the variable being searched is the closing price. 

TF[BC] = HGuess = TF[BC-1] * 10;	//TF for the final bar is empty because there is no future bar available. Set TF for the final bar to be 10 times TF for the second last bar. This serves as the high guess for the binary search.

HSign = IIf(LastValue(ZeroToFind(TF))>0, 1, -1);	//Get the sign associated with HGuess.

TF[BC] = LGuess = TF[BC-1] * 0.1;	//Set TF for the final bar to be 0.1 times TF for the second last bar. This serves as the lower guess for the binary search.

LSign = IIf(LastValue(ZeroToFind(TF))>0, 1, -1);	//Get the sign associated with LGuess

/* If the signs of HGuess and LGuess are the same, there is no zero in between them.
Set the return value to zero and return.
Otherwise, loop through the binary search.
*/

if (HSign == LSign)
{
	HGuess = 0.0;
}else
{
	while(abs(HGuess - LGuess) > AccuracyTarget)
	{
		MGuess = (HGuess + LGuess)/2;
		TF[BC] = MGuess;
		
		MSign = IIf(LastValue(ZeroToFind(TF))>0, 1, -1);
		HGuess = IIf(HSign == MSign, MGuess, HGuess);
		LGuess = IIf(LSign == MSign, MGuess, LGuess);		
	}
}


//When the loop finishes, HGuess and LGuess will be very close togethr. Either one is an acceptable value for our purpose.

Filter = BarIndex() == BC;
AddColumn(HGuess, "Zero If Close is: ", 1.9);

Export Data Series

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_14_Sector_Analysis.

//ExportDataSeries.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 196

/* 
To use this file, open the file in Automatics Analysis. 
Next, open the file in AFL Formula Editor and click the Verify Syntax icon.
A file named Exported.csv will be written to the directory specified as argument to the fopen() function. 
*/

//Change the path below to the path you want the file to be exported to
fh = fopen("C:\\Users\\<username>\\Desktop\\exported.csv", "w");


if(fh)
{
	fputs("Date,Open,High,Low,Close,Volume\n", fh);
	y = Year();
	m = Month();
	d = Day();
	
	for (i=0; i<BarCount; i++)
	{
		ds = StrFormat("%04.0f-%02.0f-%02.0f,",y[i],m[i],d[i]);
		
		fputs(ds, fh);
		
		qs = StrFormat("%.4f,%.4f,%.4f,%.4f,%.0f\n", O[i],H[i],L[i],C[i],V[i]);
		
		fputs(qs, fh);
		
	}

	fclose(fh);
}

Trading Systems

These trading systems should only be used as inspiration; they are not complete trading systems.

They use very simple entry and exit strategies and do not take into consideration money management, position sizing etc.

Mean Reversion: Center of Gravity Oscillator

This example demonstrates how you can use the smoothed version of an oscillator to trigger a signal.

//Center of Gravity.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 143

//Trading system based on John Ehlers' Center of Gravity Oscillator
//Cybernetic Analysis for Stocks and Futures
//Wiley 2004

SetTradeDelays(0,0,0,0);
BuyPrice = C;
SellPrice = C;
SetBarsRequired(200, 0);

function CGOscillator(Price, Length)
{
	Result = 0;
	
	for(i=length; i<BarCount; i++)
	{
		Num = 0;
		Denom = 0;
		
		for(j=0; j<Length;j++)
		{
			Num = Num + (1+j)* Price[i-j];
			Denom = Denom + Price[i-j];
		}
		
		if (Denom !=0)
			Result[i] = 100.0 * ((-Num/Denom) + (Length + 1)/2);			
	}
	
	return Result;
	
}


CGOLength = Param("CGOLength", 13, 1, 250, 10);
SmLength = Param("SmLength", 2, 1, 20, 2);
HoldDays = Param("HoldDays", 6, 1, 10, 1);

Price = (H+L)/2;
CGO = CGOscillator(Price, CGOLength);


CGOSmoothed = DEMA(CGO, SmLength);

Buy = Cross(CGO, CGOSmoothed);
Sell = Cross(CGOSmoothed, CGO) OR (BarsSince(Buy) >= HoldDays);
Sell = ExRem(Sell, Buy);


Plot(CGO, "CG Oscillator", colorRed, styleLine|styleLeftAxisScale);
Plot(CGOSmoothed, "CG Smoothed", colorBlue, styleLine|styleLeftAxisScale);

shape = Buy * shapeUpArrow + Sell * shapeDownArrow;
Plot(Close, "Price", colorBlack, styleCandle);
PlotShapes(shape, IIf(Buy, colorGreen, colorRed), 0, IIf(Buy, Low, High));

Mean Reversion: Stochastic Of the RSI

This example demonstrates how you can compute a position-in-range (PIR) statistic and use that to form a series. It also demonstrates how we can use the PIR series and a smoothed version of the series to get buy and sell signals.

I had to make some changes to Line 15 because HHV(r, PIRLookback)-LLV(r, PIRLookback) is sometimes zero, which leads to a “Divide by zero” error. The solution I used is to ensure that HHV(r, PIRLookback)-LLV(r, PIRLookback) is at least 0.01 using the Max() function.

//StochasticOfTheRSI.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 146

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

RSILength = Optimize("RSILength", 10, 2, 20, 1);
PIRLookback = Optimize("PIRLookback", 20, 4, 40, 2);
TriggerSmoother = Optimize("TriggerSmoother", 3, 2, 10, 1);

r = RSIa(C, RSILength);

//pir = Position in range
pir = (r-LLV(r, PIRLookback))/Max(0.01, (HHV(r, PIRLookback)-LLV(r, PIRLookback)));
	
//A smoothed copy of the pir series
trigger = DEMA(pir, TriggerSmoother);

Buy = Cross(pir, trigger);
Sell = Cross(trigger, pir);

PlotShapes(Buy*shapeUpArrow, colorBrightGreen);
PlotShapes(Sell*shapeDownArrow, colorRed);

Plot(pir, "PIR", colorRed, styleLine|styleLeftAxisScale);
Plot(trigger, "Trigger", colorBlue, styleLine|styleLeftAxisScale);
 

Mean Reversion: Adaptive Price Zone

//AdaptivePriceZone.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 148

//Trading system based on Lee Leibfarth's Trading with An Adaptive Price Zone
//TASC September 2006 Issue

function DblSmoothEMA(price, length)
{
	period = IIf(Length < 0, 1, sqrt(Length));
	smooth = 2 / (period + 1);
	
	return AMA( AMA(price, smooth), smooth);
}

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

price = C;
period = 50; 
BandPct = 2;

DsEMA = DblSmoothEMA(price, period);
RangeDsEMA = DblSmoothEMA(H-L, period);

UpBand = BandPct * RangeDsEMA + DsEMA;
DnBand = DsEMA - BandPct * RangeDsEMA;

ADXThshold = 30;
ADXLength = 14;
ADXValue = ADX(ADXLength);

Buy = ADXValue <= ADXThshold AND Low <= DnBand;
Sell = ADXValue > ADXThshold;

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

Plot(UpBand, "UpBand", colorLightGrey);
Plot(DnBand, "DownBand", colorLightGrey);

PlotShapes(Buy*shapeUpArrow, colorGreen, 0, DnBand, -24);
PlotShapes(Sell*shapeDownArrow, colorRed, 0, UpBand, -24);

Mean Reversion: DeTrended Price Oscillator

This example shows how you can convert any series into an oscillator by subtracting a moving average from it.

//DeTrendedPriceOscillator.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 149

/* Using any price series as input, compute an oscillator by subtracting
a moving average of the series from the series itself.
Then use that series to compute Buy and Sell signals */

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

//The series to be detrended
Price = C;

//The length of the moving average to remove
MALength = Param("MA Length", 30, 2, 200, 1);

//Subtract the moving average, leaving an oscillator
MovingAverage = MA(Price, MALength);
Oscillator = Price - MovingAverage;

// Smooth the oscillator once
OscSmoothLength = Param("OscSmLen", 10, 1, 50, 1);
SmOsc = DEMA(Oscillator, OscSmoothLength);

// Smooth again to create a trigger line
Trigger = DEMA(SmOsc, 3);

Buy = Cross(SmOsc, Trigger);
Sell = Cross(Trigger, SmOsc);

//Plot(MovingAverage, "MA", colorRed, styleLine);
//Plot(Oscillator, "Osc", colorBlue, styleLine|styleLeftAxisScale);

Plot(SmOsc, "SmOsc", colorGreen, styleLine|styleLeftAxisScale);
Plot(Trigger, "Trigger", colorRed, styleLine|styleLeftAxisScale);

PlotShapes(Buy*shapeUpArrow+Sell*shapeDownArrow, IIf(Buy, colorGreen, colorRed));

Testing Seasonality Patterns

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_11_Seasonality_Systems.

//TestingSeasonality.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 155 to Pg 161

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

model = Param("Model", 0, 1, 2);

if (model == 0)
{
	//Testing which day of the month produces the most profit
	DayToBuy = Optimize("DTB", 1, 1, 31, 1);
	Buy = DayToBuy == Day();
} else if (model == 1)
{
	//Testing which day surrounding the first day of the month produces the most profit
	DOI = Month()!=Ref(Month(), -1);
	Minus8 = Ref(DOI, 8);
	Daynumber = -8 + BarsSince(Minus8);

	DayToBuy = Optimize("day To Buy", 0, -8, 8, 1);
	Buy = Daynumber == DayToBuy;
}else 
{
	//Testing which day surrounding options expiration day produces the most profit
	DOI = (DayOfWeek() == 5) AND ((Day() >=15) AND (Day() <= 21));
	Minus8 = Ref(DOI, 8);
	Daynumber = -8 + BarsSince(Minus8);

	DayToBuy = Optimize("Day To Buy", 0, -8, 8, 1);
	Buy = Daynumber == DayToBuy;
}


//Sell on the close of the next trading day
Sell = BarsSince(Buy) >= 1;

Pattern Systems

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_12_Pattern_Systems.

//PatternDailyClose.afl
//Taken from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 174

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

PctC = (C - Ref(C, -1))/C; 	//Calculate the daily percentage returns

Cutoff = 15;

TopGroup = PctC >= Percentile(PctC, 60, 100-Cutoff);		//Define criteria for a day to be classified in the top group (percentage return is in the top percentile of the past 60 days' returns)
BottomGroup = PctC <= Percentile(PctC, 60, Cutoff);			//Define criteria for a day to be classified in the bottom group (percentage return is in the bottom percentile of the past 60 days' returns)
MiddleGroup = (NOT TopGroup) AND (NOT BottomGroup);

Position = IIf(TopGroup, 3, IIf(MiddleGroup, 2, 1));		//Label the position for each day (1 = BottomGroup, 2 = MiddleGroup, 3 = TopGroup)

//For a single identifier for each bar for its three day sequence 
//For instance, if the daily return is in the top group for today, yesterday and the day before, sequence = 333

Sequence = 100 * Ref(Position, -2) + 10 * Ref(Position, -1) + Position;

/* These AddColumn statements display the categories each day is assigned to, 
and help verify that the program works as it should.
Run as an Exploration with Current Symbol and Last Days set to 100.
*/

Filter = 1;
AddColumn(C, "C", 1.9);
AddColumn(PctC, "PctC", 1.9);
AddColumn(TopGroup, "TopGroup", 1.0);
AddColumn(MiddleGroup, "MiddleGroup", 1.0);
AddColumn(BottomGroup, "BottomGroup", 1.0);
AddColumn(Position, "Position", 1.0);
AddColumn(Sequence, "Sequence", 1.0);

// Create a set of optimizations that will cycle through all the possible sequences
TwoDaysAgo = Optimize("TwoDaysAgo", 1, 1, 3, 1);
OneDayAgo = Optimize("OneDayAgo", 1, 1, 3, 1);
ThisDay = Optimize("ThisDay", 1, 1, 3, 1);

Selected = 100 * TwoDaysAgo + 10 * OneDayAgo + ThisDay;

Buy = Selected == Sequence;
Sell = BarsSince(Buy) >= 1;

Using Foreign

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_14_Sector_Analysis.

//UsingForeign.afl
//Adapted from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 197

/*
This program assumes that the file named "signals" has been imported into the Amibroker database.

Any close greater than or equal to 14.0 will be taken as a Buy signal and any close less than or equal 
to 6.0 will be taken as a Sell signal.
*/

sigs = Foreign("signals", "C"); //Load the closing price of signals into variable sigs

RestorePriceArrays();	//Use data from the current symbol from this point on

Buy = sigs >= 14.0;
Sell = sigs <= 6.0;

Plot(C, "C", colorBlack, styleCandle);
Plot(sigs, "Signals", colorRed, styleLine|styleOwnScale);

Using SetForeign

//UsingSetForeign.afl
//Adapted from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 198

/* This code reads one data series that will be used to compute the trading signals for a group of issues */

SetForeign("PLTR");	//SetForiegn loads the OHLCV data of OEX into the O,H,L,C,V variables

Buy = Cross(MA(C, 7), MA(C, 3));	//At this point, all calculations are done with the OHLCV of OEX
Sell = BarsSince(Buy) >=3;

RestorePriceArrays();	//From the point forward, use thed ata in the selected data series

/* Use the Charts menu and insert this program. Place the cursor on a trade and select other symbols in the Symbols menu.
You'll see that trades always take place at the same date. */

PlotShapes(Buy*shapeSmallCircle,colorGreen,0, L*0.9); 
PlotShapes(Sell*shapeSmallCircle,colorPink,0, H*1.1); 

Rotation

Refer to https://smarttradingstrategies.com/quantitative-trading-systems-2nd-edition-by-howard-b-bandy/#Chapter_15_Rotation.

//Rotation.afl
//Adapted from Quantitative Trading System (2nd Edition) by Howard Bandy Pg 209

EnableRotationalTrading();		//This function is now marked as obsolete. Use SetBacktestMode( backtestRotational ) in new formulas.

NumberHeld = 2; //The number of issues to hold at a time

PositionSize = -100/NumberHeld;		//Allocate funds equally among all the issues

NumberExtras = 3;
WorstRank = NumberHeld + NumberExtras;
SetOption("WorstRankHeld", WorstRank);   //Set WorstRankHeld to be some number greater than the number of positions held

LookBack = 5;		//The lookback period for the Rate of Change indicator
UpDown = 2; 	 	//UpDown allows the ROC to be inverted (treat a rising ROC as a "Sell" signal
AllowShort = 2; 	//1 = Allow, 2 = Do not allow

//Compute a score based on the recent Rate of Change of the closing price
Multiplier = IIf(UpDown==1, 1, -1);
Score = Multiplier*ROC(C, LookBack);
Score = IIf(AllowShort == 1, Score, Max(Score, 0));
PositionScore = Score;

Comparing Arrays and Getting the Maximum Value

The example below calculates the strengths of the S&P 500 index, XLE, XLK, and XLV and uses the function VarGetMax() to compare the four arrays.

The code for VarGetMax() is taken from https://forum.amibroker.com/t/getting-maximum-of-6-arrays/10642/5, credit to fxshrat.

StrengthLookBack = 20;

SetForeign("SP500");
Filters = Ref(MA(C, 50), -1) > Ref(MA(C, 200), -1);
StrengthOneMth = Ref(C, -1)/Ref(C, -1*StrengthLookBack);
StrengthThreeMth = Ref(C, -1)/Ref(C, -3*StrengthLookBack);

SetForeign("XLE");
XLEStrength1 = Ref(C, -1)/Ref(C, -1*StrengthLookBack);

SetForeign("XLK");
XLKStrength1 = Ref(C, -1)/Ref(C, -1*StrengthLookBack);

SetForeign("XLV");
XLVStrength1 = Ref(C, -1)/Ref(C, -1*StrengthLookBack);

RestorePriceArrays();

function VarGetMax( varname, num ) {
    local n, maxall;
    maxall = -1e9;
    for ( n = 1; n <= num; n++ )
        maxall = Max( maxall, VarGet( varname + n ) );
    return maxall;
}

// Assign the arrays for comparing to the variables var1, var2, var3 etc (variables must be named as var*, where * is a running sequence of integers)
var1 = XLKStrength1;
var2 = XLEStrength1;
var3 = XLVStrength1;
var4 = StrengthOneMth;

// Overall maximum n variables
getmax = VarGetMax( "var", n = 4 );

// If XLVStrength1 is maximum, Strong = 1, else Strong = 0
Strong = IIf(XLVStrength1 == getmax, 1, 0);