Sell signal ignored in backtest

In my charts and exploration I am getting correct signals. In backtesting the buy/short signals seem to be correct, but the sell/cover signals seem to be incorrect. Following is an example.

My system generates the following trade.

image

In exploration, there is a buy and sell signal on the correct dates.

image

image

There are no intervening signals.

When I run the backtest, the buy signal is taken correctly. However, the sell signal is not taken. In the backtest, this trade is not exited for another two years. The following image is of the trade list from the backtest.

image

I checked the detailed log. The buy signal is taken correctly.

image

On the date of the sell signal, there is nothing in the detailed log and it still indicates an open position.

image

Then, a second position is opened in this same ticker according to the detailed log, which I thought I had prevented in my trailing stop code (code to follow).

image

However, this is a correct buy signal and shows up in the exploration.

image

Here is the detailed log when the initial position is finally exited roughly two years after the sell signal.

image

There is a sell signal on that date, but it's not the correct one for the initial trade.

image

I am using custom backtester but only to generate custom metrics (mostly to calculate my risk multiple). Having looked at other forum questions, it seems that either something in my backtest or settings is causing this, but I am really struggling to figure out what it is.

Here is my code.

// Variables for entry
highest_high = IIf( BarIndex() > entry_period, Ref( HHV( H, entry_period ), -1 ), Null ); 														// Highest high over the lookback period

lowest_low = IIf( BarIndex() > entry_period, Ref( LLV( L, entry_period ), -1 ), Null ); 														// Lowest low over the lookback period

// Variables for trailing stop
trail_ATRs = abs( trail_stop_multiplier * ATR( 20 ) ); 																							// Multiple of ATR(20) to calculate the trailing stop

trail_stop_buy = TickSize * round( ( Close - trail_ATRs ) / TickSize ); 																		// Trailing stop for a buy rounded to nearest tick

trail_stop_short = TickSize * round( ( Close + trail_ATRs ) / TickSize ); 																		// Trailing stop for a short rounded to nearest tick

//Variables for initial stop
initial_ATRs = abs( 1.5 * ATR( 20 ) );																											// Multiple of ATR(20) to calculate the initial stop

initial_stop_buy = TickSize * round( ( Close - initial_ATRs ) / TickSize );																		// Initial stop for a buy rounded to the nearest tick

risk_points_buy = Ref( C, -1 ) - Ref( initial_stop_buy, -1 );																					// Known risk points for a buy trade from the bar prior to entry

initial_stop_short = TickSize * round( ( Close + initial_ATRs ) / TickSize );																	// Initial stop for a short rounded to the nearest tick

risk_points_short = Ref( initial_stop_short, -1) - Ref( C, -1 );																				// Risk points for a short trade from the bar prior to entry

// Calculate the volatility state of the ticker
true_range = Max( H - L, Max( abs( H - Ref( C,-1 ) ), abs( L - Ref( C,-1 ) ) ) );																// Calculate the true range

vol_ratio = true_range / EMA( true_range, 252 );																								// Volatility ratio is the true range divided by the EMA of the true range

vol_ratio_75 = LastValue( Percentile( vol_ratio, BarIndex(), 75 ) );																			// The 75th percentile of the volatility ratio

vol_ratio_25 = LastValue( Percentile( vol_ratio, BarIndex(), 25 ) );																			// The 25th percentile of the volatility ratio

gt_vol_ratio_75 = Sum( vol_ratio > vol_ratio_75, 63); 																							// Days in the last quarter the volatility ratio was greater than the 75th percentile

lt_vol_ratio_25 = Sum( vol_ratio < vol_ratio_25, 63);																							// Days in the last quarter the volatility ratio was less than the 25th percentile

vol_state = IIf( gt_vol_ratio_75 >= 20, 1, 0) + IIf( lt_vol_ratio_25 >= 20, -1, 0 );															// 1 = volatile, 0 = normal, -1 = quiet

vol_state_entry = Ref( vol_state, -1 );																											// Volatility state one bar before entry

// Calculate the trend state of the ticker
// RAVI = abs( 100 * ( ( MA( C, 7 ) - MA( C, 65 ) ) / MA( C, 65 ) ) );																			// Calculate the range action verification index

// RAVI_state = IIf( RAVI >= 3, 2, IIf( ( RAVI >= 1.5 AND RAVI < 3 ), 1, 0 ) );																	// 2 = trend, 1 = weak trend, 0 = no trend

// Custom backtest procedure
SetOption( "UseCustomBacktestProc", True );
SetCustomBacktestProc( "" );

// Custom backtest metrics for direction and volatility states, risk, risk multiple, and average expectancy
StaticVarSet( "CBT_risk_points_buy" + Name(), risk_points_buy );
StaticVarSet( "CBT_risk_points_short" + Name(), risk_points_short );
StaticVarSet( "CBT_vol_state_entry" + Name(), vol_state_entry );
// StaticVarSet( "CBT_RAVI_state" + Name(), RAVI_state );

if( Status( "action" ) == actionPortfolio )
{
	bo = GetBacktesterObject();
	
	bo.Backtest( 1 );
	
	sum_profit_per_risk = 0;
	total_trades = 0;
	
	for( trade=bo.GetFirstTrade(); trade; trade=bo.GetNextTrade() )
	{
		if ( trade.IsLong )
			risk = Lookup( StaticVarGet( "CBT_risk_points_buy" + trade.Symbol ), trade.EntryDateTime ) * trade.PointValue() * trade.EntryFxRate;	// Initial dollar risk for each buy trade
		else
			risk = Lookup( StaticVarGet( "CBT_risk_points_short" + trade.Symbol ), trade.EntryDateTime ) * trade.PointValue() * trade.EntryFxRate;	// Initial dollar risk for each short trade
			
		r_multiple = trade.GetProfit() / risk;																									// R multiple for each trade
		
		vol_state_backtest = Lookup( StaticVarGet( "CBT_vol_state_entry" + trade.Symbol ), trade.EntryDateTime );								// Volatility state for each trade
		
		// RAVI_state_backtest = Lookup( StaticVarGet( "CBT_RAVI_state" + trade.Symbol ), trade.EntryDateTime );								// RAVI state for each trade
				
		trade.AddCustomMetric( "Initial Risk", risk );
		trade.AddCustomMetric( "R-Multiple", r_multiple );
		trade.AddCustomMetric( "Volatility State", vol_state_backtest );
		// trade.AddCustomMetric( "RAVI State", RAVI_state_backtest );
		
		sum_profit_per_risk = sum_profit_per_risk + r_multiple;																					// Sum of R multiples
		total_trades++;
	}	
	
	expectancy = sum_profit_per_risk / total_trades;																							// Expectancy of all trades
	
	bo.AddCustomMetric( "Avg. Expectancy", expectancy );
		
	bo.ListTrades();
}

SetChartOptions(0,chartShowArrows|chartShowDates);

_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%0.1%%) {{VALUES}}",O,H,L,C,SelectedValue(ROC(C,1))));

// Buy-Short Rules
Buy = Cross( C, highest_high ) AND vol_state == -1;																								// Buy when the high crosses the highest high of the lookback window and market filter applies
Short = Cross( lowest_low, C ) AND vol_state == -1;																								// Short when the low crosses the lowest low of the lookback window and market filter applies

// Sell-Cover Rules
Sell = 0;																																		
trail_array_buy = Null;																															
trailing_stop_buy = 0;																																
risk_stop_buy = 0;																																

for( i = 1; i < BarCount; i++ )																													// For loop to calculate the trailing stop for a buy
{
	if( trailing_stop_buy == 0 AND Buy[i] )																										// Condition when the buy trailing stop is 0 and a buy signal
	{
		trailing_stop_buy = trail_stop_buy[i];																									// Set the buy trailing stop equal to the buy stop
		risk_stop_buy = initial_stop_buy[i];																									// Set the initial buy risk stop equal to the initial risk stop	
	}
	else Buy[i] = 0; 																															// Remove excess buy signals
		
	if(trailing_stop_buy != 0 AND risk_stop_buy != 0 AND ( C[i] < trailing_stop_buy OR L[i] < lowest_low[i] OR C[i] < risk_stop_buy) )			// Condition when the buy trailing and risk stops don't equal 0 and a sell signal
	{
		Sell[i] = 1;																															// Generate a sell signal 
		trailing_stop_buy = 0;																													// Return the trailing stop to 0
		risk_stop_buy = 0;																														// Return the initial risk stop to 0
	}
	
	if( trailing_stop_buy != 0 AND risk_stop_buy != 0 )																							// Condition when the buy trailing and risk stops don't equal 0 and no sell signal
	{
		trailing_stop_buy = Max( trail_stop_buy[i], trailing_stop_buy );																		// The trailing stop is the max of the initial value or the value at the current bar
		trail_array_buy[i] = Max( trailing_stop_buy, risk_stop_buy );																			// The buy trail array is the max of the trailing and initial risk stops
	}
}

Cover = 0;																																																												
trail_array_short = Null;																															
trailing_stop_short = 0;																																
risk_stop_short = 0;																														

for( i=1; i < BarCount; i++ )																													// For loop to calculate the short trailing stop
{
	if( trailing_stop_short == 0 AND Short[i] )																									// Condition when the short trailing stop is 0 and a short signal 
	{
		trailing_stop_short = trail_stop_short[i];																								// Set the short trailing stop equal to the short stop
		risk_stop_short = initial_stop_short[i];																								// Set the initial short stop equal to the initial short risk stop
	}
	else Short[i] = 0; 																															// Remove excess short signals
		
	if( trailing_stop_short != 0 AND risk_stop_short != 0 AND 
	( C[i] > trailing_stop_short OR H[i] > highest_high[i] OR C[i] > risk_stop_short ) )														// Condition when the short trailing and risk stops don't equal 0 and a sell signal	
	{
		Cover[i] = 1;																															// Generate a cover signal
		trailing_stop_short = 0;																												// Return the short trailing stop to 0
		risk_stop_short = 0;																													// Return the initial short risk stop to 0
	}
	
	if( trailing_stop_short != 0 AND risk_stop_short != 0 )																						// Condition when the short trailing and risk stops don't equal 0 and no cover signal
	{
		trailing_stop_short = Min( trail_stop_short[i] ,trailing_stop_short );																	// The trailing stop is the min of the initial value and the value at the current bar
		trail_array_short[i] = Min( trailing_stop_short, risk_stop_short );																		// The short trail array is the min of the trailing and initial risk stops
	}
}
1 Like

I tried commenting out the custom backtest code and I still got the same trades in the trade list when running the backtest.

I suggest you use the interactive debugger or _TRACE() statements to better understand what your code is doing, especially inside your trailing stop loop.

Also, I don't see any Exploration code in the AFL that you posted, and as far as I know AmiBroker will only take multiple trades in a single symbol if you have used SetBacktestMode() with one of the "multi" options. Therefore, it would seem that the code that you posted is not the code that you are running, which will make it very difficult indeed for anyone to help you.

I will try the debugger and _TRACE() statement. I read about the _TRACE() statement this morning but wasn't quite sure I understand how to apply it or what to look for.

I am not using SetBacktestMode() in my code. Below is a screenshot of my general settings when running a backtest.

image

Below is my exploration code.

// Exploration
Filter = 1;

AddColumn( Open, "Open", 1.7 );
AddColumn( High, "High", 1.7 );
AddColumn( Low, "Low", 1.7 );
AddColumn( Close, "Close", 1.7 );
AddColumn( gt_vol_ratio_75, "Days Volatility Ratio Above IQR" );
AddColumn( lt_vol_ratio_25, "Days Volatility Ratio Below IQR" );
AddColumn( vol_state, "Volatility State" );
AddColumn( vol_state_entry, "Volatility State - Entry" );
AddColumn( Buy, "Buy" );
AddColumn( Sell, "Sell" );
AddColumn( Short, "Short" );
AddColumn( Short, "Short" );

I'm definitely running the code I posted above to generate the results I showed.

Exploration is not backtest, if you use exploration you need to carefully read this:

I understand that exploration is not the same as backtest. I was trying to use both to help me understand what is happening and why I am seeing something that is different than what I expected. I clearly have something off in my code.

Did you carefully read the whole post I pointed? (I guess not because clicks are counted by the forum, yet no click was made).

I think so. I did click on the link and read it. I've read it before too. So, maybe I don't understand it.

My understanding is that the exploration just shows the array of Buy/Sell/Short/Cover but that doesn't necessarily mean a trade is executed in the backtest because there are a number of other factors that come into play to execute a trade.

I was trying to use the exploration to even see if the Sell Array had a 1 in it where my chart was showing the signal. It did. But, the backtest isn't reslting in a sell on that date. I can understand why a buy/short would not get executed because of filters, equity, etc., but I'm struggling to figure out why the sell/cover is not getting executed.

I copied the code from your post and the syntax check generates multiple errors:

image

So again I would assert that the code you're running is not the code you've posted. If you want people to help you, you should make it as easy as possible for them to do so. Nobody wants to guess what you might have done in your working version of the code. If you're worried that someone will steal your "secret sauce", then replace your entry and exit rules with something simple like a moving average crossover and then verify that you can still reproduce the problem you're seeing.

1 Like

I'm pretty sure I don't have any secret sauce. I posted almost my entire code previously. I will post it all below.

I'm really not trying to make it difficult. But, I'm pretty new to coding.

I always run the AFL check - especially when I make any change to the code. I am not getting any syntax errors.

_SECTION_BEGIN( "Breakout System-Test" );
// Breakout/breakdown system using daily data with a fixed initial risk stop and a trailing stop (whichever is closer to the close).

// Turn off Quick AFL to use all bars
SetBarsRequired( sbrAll, sbrAll ); 

// System Parameters
SetOption( "InitialEquity", 1000000 );
SetOption( "AllowSameBarExit", False );
SetOption( "FuturesMode", True );
SetTradeDelays( 1, 1, 1, 1 );

// PositionSize=MarginDeposit=1;
SetPositionSize( 1, spsShares );

// Set trade prices
slippage = TickSize * round( ( Open * 0.0025 ) / TickSize ); 																					// Average slippage from my current trading rounded to nearest tick

BuyPrice = Open + slippage;
SellPrice = Open - slippage;
ShortPrice = Open - slippage;
CoverPrice = Open + slippage;

// Parameters
entry_period = Param( "Entry Period", 100, 40, 260 ); 																							// Lookback window size for breakouts and breakdowns

trail_stop_multiplier = Param( "Trail Stop Multiplier", 6, 2, 8 ); 																				// ATR multiplier for trailing stop

// Variables for entry
highest_high = IIf( BarIndex() > entry_period, Ref( HHV( H, entry_period ), -1 ), Null ); 														// Highest high over the lookback period

lowest_low = IIf( BarIndex() > entry_period, Ref( LLV( L, entry_period ), -1 ), Null ); 														// Lowest low over the lookback period

// Variables for trailing stop
trail_ATRs = abs( trail_stop_multiplier * ATR( 20 ) ); 																							// Multiple of ATR(20) to calculate the trailing stop

trail_stop_buy = TickSize * round( ( Close - trail_ATRs ) / TickSize ); 																		// Trailing stop for a buy rounded to nearest tick

trail_stop_short = TickSize * round( ( Close + trail_ATRs ) / TickSize ); 																		// Trailing stop for a short rounded to nearest tick

//Variables for initial stop
initial_ATRs = abs( 1.5 * ATR( 20 ) );																											// Multiple of ATR(20) to calculate the initial stop

initial_stop_buy = TickSize * round( ( Close - initial_ATRs ) / TickSize );																		// Initial stop for a buy rounded to the nearest tick

risk_points_buy = Ref( C, -1 ) - Ref( initial_stop_buy, -1 );																					// Known risk points for a buy trade from the bar prior to entry

initial_stop_short = TickSize * round( ( Close + initial_ATRs ) / TickSize );																	// Initial stop for a short rounded to the nearest tick

risk_points_short = Ref( initial_stop_short, -1) - Ref( C, -1 );																				// Risk points for a short trade from the bar prior to entry

// Calculate the volatility state of the ticker
true_range = Max( H - L, Max( abs( H - Ref( C,-1 ) ), abs( L - Ref( C,-1 ) ) ) );																// Calculate the true range

vol_ratio = true_range / EMA( true_range, 252 );																								// Volatility ratio is the true range divided by the EMA of the true range

vol_ratio_75 = LastValue( Percentile( vol_ratio, BarIndex(), 75 ) );																			// The 75th percentile of the volatility ratio

vol_ratio_25 = LastValue( Percentile( vol_ratio, BarIndex(), 25 ) );																			// The 25th percentile of the volatility ratio

gt_vol_ratio_75 = Sum( vol_ratio > vol_ratio_75, 63); 																							// Days in the last quarter the volatility ratio was greater than the 75th percentile

lt_vol_ratio_25 = Sum( vol_ratio < vol_ratio_25, 63);																							// Days in the last quarter the volatility ratio was less than the 25th percentile

vol_state = IIf( gt_vol_ratio_75 >= 20, 1, 0) + IIf( lt_vol_ratio_25 >= 20, -1, 0 );															// 1 = volatile, 0 = normal, -1 = quiet

vol_state_entry = Ref( vol_state, -1 );																											// Volatility state one bar before entry

// Calculate the trend state of the ticker
// RAVI = abs( 100 * ( ( MA( C, 7 ) - MA( C, 65 ) ) / MA( C, 65 ) ) );																			// Calculate the range action verification index

// RAVI_state = IIf( RAVI >= 3, 2, IIf( ( RAVI >= 1.5 AND RAVI < 3 ), 1, 0 ) );																	// 2 = trend, 1 = weak trend, 0 = no trend

// Custom backtest procedure
SetOption( "UseCustomBacktestProc", True );
SetCustomBacktestProc( "" );

// Custom backtest metrics for direction and volatility states, risk, risk multiple, and average expectancy
StaticVarSet( "CBT_risk_points_buy" + Name(), risk_points_buy );
StaticVarSet( "CBT_risk_points_short" + Name(), risk_points_short );
StaticVarSet( "CBT_vol_state_entry" + Name(), vol_state_entry );
// StaticVarSet( "CBT_RAVI_state" + Name(), RAVI_state );

if( Status( "action" ) == actionPortfolio )
{
	bo = GetBacktesterObject();
	
	bo.Backtest( 1 );
	
	sum_profit_per_risk = 0;
	total_trades = 0;
	
	for( trade=bo.GetFirstTrade(); trade; trade=bo.GetNextTrade() )
	{
		if ( trade.IsLong )
			risk = Lookup( StaticVarGet( "CBT_risk_points_buy" + trade.Symbol ), trade.EntryDateTime ) * trade.PointValue() * trade.EntryFxRate;	// Initial dollar risk for each buy trade
		else
			risk = Lookup( StaticVarGet( "CBT_risk_points_short" + trade.Symbol ), trade.EntryDateTime ) * trade.PointValue() * trade.EntryFxRate;	// Initial dollar risk for each short trade
			
		r_multiple = trade.GetProfit() / risk;																									// R multiple for each trade
		
		vol_state_backtest = Lookup( StaticVarGet( "CBT_vol_state_entry" + trade.Symbol ), trade.EntryDateTime );								// Volatility state for each trade
		
		// RAVI_state_backtest = Lookup( StaticVarGet( "CBT_RAVI_state" + trade.Symbol ), trade.EntryDateTime );								// RAVI state for each trade
				
		trade.AddCustomMetric( "Initial Risk", risk );
		trade.AddCustomMetric( "R-Multiple", r_multiple );
		trade.AddCustomMetric( "Volatility State", vol_state_backtest );
		// trade.AddCustomMetric( "RAVI State", RAVI_state_backtest );
		
		sum_profit_per_risk = sum_profit_per_risk + r_multiple;																					// Sum of R multiples
		total_trades++;
	}	
	
	expectancy = sum_profit_per_risk / total_trades;																							// Expectancy of all trades
	
	bo.AddCustomMetric( "Avg. Expectancy", expectancy );
		
	bo.ListTrades();
}

SetChartOptions(0,chartShowArrows|chartShowDates);

_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%0.1%%) {{VALUES}}",O,H,L,C,SelectedValue(ROC(C,1))));

// Buy-Short Rules
Buy = Cross( C, highest_high ) AND vol_state == -1;																								// Buy when the high crosses the highest high of the lookback window and market filter applies
Short = Cross( lowest_low, C ) AND vol_state == -1;																								// Short when the low crosses the lowest low of the lookback window and market filter applies

// Sell-Cover Rules
Sell = 0;																																		
trail_array_buy = Null;																															
trailing_stop_buy = 0;																																
risk_stop_buy = 0;																																

for( i = 1; i < BarCount; i++ )																													// For loop to calculate the trailing stop for a buy
{
	if( trailing_stop_buy == 0 AND Buy[i] )																										// Condition when the buy trailing stop is 0 and a buy signal
	{
		trailing_stop_buy = trail_stop_buy[i];																									// Set the buy trailing stop equal to the buy stop
		risk_stop_buy = initial_stop_buy[i];																									// Set the initial buy risk stop equal to the initial risk stop	
	}
	else Buy[i] = 0; 																															// Remove excess buy signals
		
	if(trailing_stop_buy != 0 AND risk_stop_buy != 0 AND ( C[i] < trailing_stop_buy OR L[i] < lowest_low[i] OR C[i] < risk_stop_buy) )			// Condition when the buy trailing and risk stops don't equal 0 and a sell signal
	{
		Sell[i] = 1;																															// Generate a sell signal 
		trailing_stop_buy = 0;																													// Return the trailing stop to 0
		risk_stop_buy = 0;																														// Return the initial risk stop to 0
	}
	
	if( trailing_stop_buy != 0 AND risk_stop_buy != 0 )																							// Condition when the buy trailing and risk stops don't equal 0 and no sell signal
	{
		trailing_stop_buy = Max( trail_stop_buy[i], trailing_stop_buy );																		// The trailing stop is the max of the initial value or the value at the current bar
		trail_array_buy[i] = Max( trailing_stop_buy, risk_stop_buy );																			// The buy trail array is the max of the trailing and initial risk stops
	}
}

Cover = 0;																																																												
trail_array_short = Null;																															
trailing_stop_short = 0;																																
risk_stop_short = 0;																														

for( i=1; i < BarCount; i++ )																													// For loop to calculate the short trailing stop
{
	if( trailing_stop_short == 0 AND Short[i] )																									// Condition when the short trailing stop is 0 and a short signal 
	{
		trailing_stop_short = trail_stop_short[i];																								// Set the short trailing stop equal to the short stop
		risk_stop_short = initial_stop_short[i];																								// Set the initial short stop equal to the initial short risk stop
	}
	else Short[i] = 0; 																															// Remove excess short signals
		
	if( trailing_stop_short != 0 AND risk_stop_short != 0 AND 
	( C[i] > trailing_stop_short OR H[i] > highest_high[i] OR C[i] > risk_stop_short ) )														// Condition when the short trailing and risk stops don't equal 0 and a sell signal	
	{
		Cover[i] = 1;																															// Generate a cover signal
		trailing_stop_short = 0;																												// Return the short trailing stop to 0
		risk_stop_short = 0;																													// Return the initial short risk stop to 0
	}
	
	if( trailing_stop_short != 0 AND risk_stop_short != 0 )																						// Condition when the short trailing and risk stops don't equal 0 and no cover signal
	{
		trailing_stop_short = Min( trail_stop_short[i] ,trailing_stop_short );																	// The trailing stop is the min of the initial value and the value at the current bar
		trail_array_short[i] = Min( trailing_stop_short, risk_stop_short );																		// The short trail array is the min of the trailing and initial risk stops
	}
}

// Plot chart, indicators, stops, arrows
Plot( Close, "Price", colorDefault, styleCandle );

Plot( highest_high, "Buy", colorLime, styleStaircase|styleThick|styleDashed );

Plot( lowest_low, "Short", colorLime, styleStaircase|styleThick|styleDashed );

Plot( trail_array_buy, "Buy Trailing Stop", colorWhite, styleStaircase|styleThick );

Plot( trail_array_short, "Short Trailing Stop", colorWhite, styleStaircase|styleThick );

PlotShapes( IIf( Buy, shapeUpArrow, shapeNone ),colorWhite, 0, H, offset=15 );

PlotShapes( IIf( Sell, shapeDownArrow, shapeNone ), colorWhite, 0, L, offset=15 );

PlotShapes( IIf( Short, shapeDownArrow, shapeNone ), colorWhite, 0, L, offset=15 );

PlotShapes( IIf( Cover, shapeUpArrow, shapeNone ), colorWhite, 0, H, offset=15 );

// Exploration
Filter = 1;

AddColumn( Open, "Open", 1.7 );
AddColumn( High, "High", 1.7 );
AddColumn( Low, "Low", 1.7 );
AddColumn( Close, "Close", 1.7 );
AddColumn( gt_vol_ratio_75, "Days Volatility Ratio Above IQR" );
AddColumn( lt_vol_ratio_25, "Days Volatility Ratio Below IQR" );
AddColumn( vol_state, "Volatility State" );
AddColumn( vol_state_entry, "Volatility State - Entry" );
AddColumn( Buy, "Buy" );
AddColumn( Sell, "Sell" );
AddColumn( Short, "Short" );
AddColumn( Short, "Short" );
// AddColumn( RAVI, "RAVI" );
// AddColumn( RAVI_state, "RAVI State" );

_SECTION_END();

Without running code some oberservations....

Please do yourself a favour and output the value of your Ticksize.
It is unknown what value it is really is.
Is it zero?
In your Analysis settings it is zero but what about Information window?
So output it via trace.

If it is zero then all your trail_stop* and initial_stop* variables will be zero too.


These lines are future leaks in backtest.

vol_ratio_75 = LastValue( Percentile( vol_ratio, BarIndex(), 75 ) );   
vol_ratio_25 = LastValue( Percentile( vol_ratio, BarIndex(), 25 ) );

These lines are things done twice.

SetOption( "UseCustomBacktestProc", True );
SetCustomBacktestProc( "" );

Either use one or the other. E.g.

SetCustomBacktestProc( "", True );

or

SetOption( "UseCustomBacktestProc", True );


This one

vol_state = IIf( gt_vol_ratio_75 >= 20, 1, 0) + IIf( lt_vol_ratio_25 >= 20, -1, 0 );

can be written

vol_state = (gt_vol_ratio_75 >= 20) - (lt_vol_ratio_25 >= 20);


etc.


Run each exit separately -> so remove/comment all others. In addition run long only first and comment Short entirely. Use Trace or Debugger.

BTW, your exit loops are basically a modifications of this one from KB.

So replace your modification with that one (for testing) and see whether you get trail exit.
Or remove all loops and Sell and Cover lines and add simple Sell lines:

Sell = 1;

Or

Sell = Cross(Ma(C,20), C);

Do you get exits?

1 Like

Your post is helpful and gives me a lot to look into.

I checked the information for every symbol. They all have a tick size.

I now (pretty obviously) see that those are future leaks. What I was trying to achieve was calculating the 75th and 25th percentile of the volatility ratio for all the bars up to the current bar. The volatility ratio doesn't start until bar 253 since it involves a 252 period EMA. So, I changed the code to the following, which I think should fix the future leak problem. Please let me know if you don't think it does.

vol_ratio_75 = Percentile ( vol_ratio, BarIndex() - 252, 75 );																						// The 75th percentile of the volatility ratio

vol_ratio_25 = Percentile ( vol_ratio, BarIndex() - 252, 25 );																						// The 25th percentile of the volatility ratio

I condensed the code for the vol_state as you stated.

I reran the backtest and still have the same issue with the same two trades.

Now, I will work on the rest of your suggestions regarding running each exit separately, running long only first, etc. I will report back as I work through your other suggestions.

Again, thank you for the helpful suggestions.

Before running anything separately, I checked all the trades for one symbol, comparing the entry and exit dates from the trade list from the backtest to the entry and exit dates from the chart.

All of the short trades matched exactly.

None of the long trades matched. For most of the long trades, the entry date was correct. Although, three entries were skipped. For the long exits, they all were associated with a long trade that was one trade too early. So, in the Excel image below, if the long exit dates in red were all moved down one trade, then they would match exactly.

So, I'm guessing something is wrong in my for loop code for the long exit.

image

Did you look at the "Detailed log" (see How do I debug my formula? ) if there were signals generated by your code you will see the reason for not entering.

image

The image above is from the trade list. The backtest didn't show entries for the rows that say "skipped trade" in bold.

However, in the detailed log, the backtest shows that all three of those entries were taken. Below is one example.

image

In each case, a second position was opened.

I checked the trade list with my chart for two more symbols this morning. For both symbols, all entries and exits in the trade list matched the entries and exits on the chart.

I read through the link you provided about debugging as well as the documentation on _TRACE and some posts in the forum where _TRACE is used. I did download the debug viewer. However, I'm still very confused on how to use _TRACE.

What am I supposed to look for with it to find errors? What do I pass through _TRACE? Where does it go in my code?

I apologize for these very basic questions. Obviously, I am new to coding. The examples I saw in the forum were too complicated for me to follow. Or, they didn't help me understand how it would help me solve my problem.

The problem is that you are sending only tiny pieces of information. And we have to guess & guess & guess. You need to look at detailed log, but NOT only at the entry but also at the supposed EXIT date. Trade list shows trade only if both entry and exit exists.
If you only have ENTRY but not exit, it will show such trade at the very end of trade list as "OPEN LONG".
You don't have open long for that trade in the screenshot that you sent, but you do have long trade that is open on 1/10/1985 and exit for that trade is on 12/23/1988. That is probably the trade that you assumed is missing. It is not missing. It is there, but simply exit date is different than what you think it is.

When I initially posted the screenshot, I wrote that the exits for the longs are all one trade earlier than I think they should be.

For example, on the second row, there is a long entry on 2/5/1982 and a long exit on 6/19/1984. The actual exit on my chart, which is where I want it, occurs on 4/21/1982. For some reason that sell signal gets skipped. The long exit on 6/19/1984 should go with the trade on the third row with an entry date of 9/16/1983.

So, all the exit dates in red should be pushed down the spreadsheet to the next long trade. Then, all of the long trades would match what is on my chart and my exploration.

I have checked five other symbols and this particular symbol, only for the long trades, is the only time this is happening.

So, I don't understand why the code is working except for this one symbol so far.

I'm trying to provide everything that would help people help me. I'll gladly provide more.

Again: we don't see your computer screen, therefore we only can guess based on limited and truncated information provided.

I was referring to trade that you posted on truncated detailed log (showing only the entry, not the exit).
image

You only shown first entry in the Detailed Log and complained that this trade is missing

You did not provide entire Detailed Log, so again, another set of wild guesses needs to be done and solely looking at truncated trade list, I can actually see the trade that begun on that day (1/10/1985).

That trade is marked as missing in your screenshot from trade list. But it is not really missing. On one particular bar (1/10/1985) you are opening ONE trade. One BUY signal->One trade. You opened ONE trade on 1/10/1985. And it was later closed on 12/23/1988. That is how it works. And that is what is happening. You got that trade present on your trade list, just lower in the list.

image

So, my system has two exits:

  1. A fixed, or max, loss stop of 1.5 * ATR
  2. A trailing stop of 6 * ATR

So, the stop should only move with the symbol once the trailing stop is closer to the price than the fixed stop. I realize I could do this with ApplyStop, but I want to be able to plot the stops.

I tested each of these exits separately per your suggestion. I changed only the code for the stops (sell/cover signals).

Let's look at the fixed stop first. Following is the code for the fixed stop.

// Sell-Cover Rules
Sell = 0;																																		
trail_array_buy = Null;																															
risk_stop_buy = 0;																																																												

for( i = 1; i < BarCount; i++ )																													// For loop to calculate the trailing stop for a buy
{
	if( risk_stop_buy == 0 AND Buy[i] )																											// Condition when the buy trailing stop is 0 and a buy signal
	{
		risk_stop_buy = initial_stop_buy[i];																									// Set the buy trailing stop equal to the buy stop
	}
	else Buy[i] = 0; 																															// Remove excess buy signals
		
	if(risk_stop_buy != 0 AND C[i] < risk_stop_buy )																							// Condition when the buy trailing and risk stops don't equal 0 and a sell signal
	{
		Sell[i] = 1;																															// Generate a sell signal 
		risk_stop_buy = 0;																														// Return the trailing stop to 0
	}
	
	if( risk_stop_buy != 0 )																													// Condition when the buy trailing and risk stops don't equal 0 and no sell signal
	{
		trail_array_buy[i] = risk_stop_buy;																										// The buy trail array is the max of the trailing and initial risk stops
	}
}

Cover = 0;																																																												
trail_array_short = Null;																															
risk_stop_short = 0;																																																														

for( i=1; i < BarCount; i++ )																													// For loop to calculate the short trailing stop
{
	if( risk_stop_short == 0 AND Short[i] )																										// Condition when the short trailing stop is 0 and a short signal 
	{
		risk_stop_short = initial_stop_short[i];																								// Set the short trailing stop equal to the short stop
	}
	else Short[i] = 0; 																															// Remove excess short signals
		
	if( risk_stop_short != 0 AND C[i] > risk_stop_short )																						// Condition when the short trailing and risk stops don't equal 0 and a sell signal	
	{
		Cover[i] = 1;																															// Generate a cover signal
		risk_stop_short = 0;																													// Return the short trailing stop to 0
	}
	
	if( risk_stop_short != 0 )																													// Condition when the short trailing and risk stops don't equal 0 and no cover signal
	{
		trail_array_short[i] = risk_stop_short;																									// The short trail array is the min of the trailing and initial risk stops
	}
}

Here is a plot of those stops.

image

The very first long sell signal in the lowest left is still ignored in the back test. And the next long buy signal is taken even though the first long trade is still open.

Here is the trade list with both long/short and long only. I highlighted on the long/short list how a new long position is being opened before the previous long position is closed. Also, the long exits are still happening one trade too early. In other words, the first long position is taking the second long position's exit, and the second long position is taking the third long position's exit, and so on.

The first long position should have exited on 4/20/1982 as the close on 4/19/1982 was below the fixed stop of 1.5 * ATR of the long entered on 2/5/1982. Looking at the detailed log, there is no statement about the sell signal on 4/20/1982.

image

I then tried testing the trailing stop only. Here is the code for the trailing stop only.

// Sell-Cover Rules
Sell = 0;																																		
trail_array_buy = Null;																															
trailing_stop_buy = 0;																																																												

for( i = 1; i < BarCount; i++ )																													// For loop to calculate the trailing stop for a buy
{
	if( trailing_stop_buy == 0 AND Buy[i] )																										// Condition when the buy trailing stop is 0 and a buy signal
	{
		trailing_stop_buy = trail_stop_buy[i];																									// Set the buy trailing stop equal to the buy stop
	}
	else Buy[i] = 0; 																															// Remove excess buy signals
		
	if(trailing_stop_buy != 0 AND C[i] < trailing_stop_buy )																					// Condition when the buy trailing and risk stops don't equal 0 and a sell signal
	{
		Sell[i] = 1;																															// Generate a sell signal 
		trailing_stop_buy = 0;																													// Return the trailing stop to 0
	}
	
	if( trailing_stop_buy != 0 )																												// Condition when the buy trailing and risk stops don't equal 0 and no sell signal
	{
		trailing_stop_buy = Max( trail_stop_buy[i], trailing_stop_buy );																		// The buy trail array is the max of the trailing and initial risk stops
		trail_array_buy[i] = trailing_stop_buy;
	}
}

Cover = 0;																																																												
trail_array_short = Null;																															
trailing_stop_short = 0;																																																														

for( i=1; i < BarCount; i++ )																													// For loop to calculate the short trailing stop
{
	if( trailing_stop_short == 0 AND Short[i] )																									// Condition when the short trailing stop is 0 and a short signal 
	{
		trailing_stop_short = trail_stop_short[i];																								// Set the short trailing stop equal to the short stop
	}
	else Short[i] = 0; 																															// Remove excess short signals
		
	if( trailing_stop_short != 0 AND C[i] > trailing_stop_short )																				// Condition when the short trailing and risk stops don't equal 0 and a sell signal	
	{
		Cover[i] = 1;																															// Generate a cover signal
		trailing_stop_short = 0;																												// Return the short trailing stop to 0
	}
	
	if( trailing_stop_short != 0 )																												// Condition when the short trailing and risk stops don't equal 0 and no cover signal
	{
		trailing_stop_short = Min( trail_stop_short[i], trailing_stop_short );
		trail_array_short[i] = trailing_stop_short;																								// The short trail array is the min of the trailing and initial risk stops
	}
}

Following is the trade list generated using the trailing stop.

image

First, a second long position is opened on 9/16/83 before the first one closes on 6/19/1984. But, let's look at the plot of the first two trades.

image

The sell signal on 4/16/1982 for the first long trade is ignored. And, the detailed log records nothing on the date.

image

However, looking at the trade list, the exit for the second long signal is assigned to the first long position. So, once again, all of the long exits are being assigned to the previous long entry.

So, I'm getting trail exits but one trade too late. The short trail exits on this symbol seem to work fine. And, I have gone through at least five other symbols by hand and all of the signals and trades from the trade list and detailed log match my plot. So, far the problem seems isolated to this one symbol.

I have read lots of posts on the _TRACE function but I still can't figure out how to use it to help me solve this problem. As a very new programmer, the uses seem confusing to me.

I tried passing the trail_array_buy through the _TRACE function when it was placed at the end of my for loop for the exit. I got a bunch of lines with a bunch of numbers, but I couldn't figure out how it tied back to my trades or my plot and how it was supposed to help me solve the problem.

What array or other information do I pass through the _TRACE function to help me solve this problem?

Where should the _TRACE function go in my code?

Read the manual carefully please
http://www.amibroker.com/guide/h_portfolio.html

It explains how signals work (there is a picture that you should study) and how redundant signals are removed.

In short: in regular backtest mode (the default), there is ONE open position PER SYMBOL at a time. And this is precisely what Detailed Log shows.