Suppress New Buy Signal if Positions are still open

Hi,
I am trying to develop an option strategy for an underlying asset such as WTI.
The idea is to enter two option trades (one Long and one Short) simultaneously based on a condition of the underlying asset. Both positions have a trailing stop. Once both positions have been sold, another buy order can take place once the condition of the underlying asset becomes true again.

I have done a scan to create the data for Long and Short options using ATC. I also did an exploration where I was able to simulate all trades and suppress the invalid ones. Everything works as expected. But when I run the backtest, the trades are not correct since I am not able to use information from one symbol to cancel the trade signal for another symbol.

I have spent a lot of time looking into the custom backtester programming. This is what I came up with:

SetPositionSize( 1000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption("UseCustomBacktestProc", True ); // Enable Custom Backtester

Period = 100;
TSAmount = 0.50;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop(stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, reentrydelay=1);

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop
OpenPosQty = 0;

// Custom Backtester
// Suppress Buy Signals if either LONG or SHORT Position is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();

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

		for( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
		{
			OpenPosQty = bo.GetOpenPosQty();
		}

		for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
		{			
			_TRACE( "Signal: "  + sig.type + " | " + DateTimeToStr( dt[bar] ) + " | " + sig.Symbol + " | " + OpenPosQty );

			if (sig.IsEntry() and OpenPosQty != 0 ) // Entry Signal and Open Position(s)
			{
				sig.Price = -1;// cancel Entry Signal
			}
		}
	
	bo.ProcessTradeSignals( bar );
	}
bo.PostProcess();
}

For some reason, my code only creates the first two buy trades and no more after the positions are sold. The log window shows buy trades even if there is still an open position. Without the custom backtester all trades are executed but in a wrong fashion since buy orders will execute while positions are still open.
I am using periodicity of 1 minute bars.

Log Window:
Log%20Window

Your help is very much appreciated.
Thank you

Oh, I forgot to mention that I have assigned the Long and Short option to watchlist 1, that I use when filtering for the backtest. So there are only two symbols in play. The correct sequence of the trades would be as follows:

  1. Buy signal for Long and Short
  2. Trailing Stop for either Long or Short
  3. Trailing Stop for either Short or Long
  4. Another Buy signal for Long and Short
    ...

@Bernd,

Only users with License verified badge are allowed to post on this forum.

Your problem is quite clear from the log output that you get: you have ONE position open and your custom backtester code DENIES opening new positions if ANY open position is present. Since this single trade never exits (because stop is not hit), you don't open new positions. Exactly and precisely as your own code says.

The topic is duplicate of many others in this forum. Search for pair trading https://forum.amibroker.com/search?q=pair%20trading

The techniques that you should use for pair trading are covered in KB:
https://www.amibroker.com/kb/2014/09/20/broad-market-timing-in-system-formulas/

@Tomasz,

I have identified the problem. At 08:00 the system triggers the ApplyStop for the Long Symbol while at the same bar the original Buy condition is met (both symbols are Long entries for AFL) is met. Since there is no time information within that bar, PosQty remains 1 although I need it to be 0 after the Exit to prevent the Buy.

For the same symbol I can enforce trade sequence with

SetOption("AllowSameBarExit", True );
SetOption("HoldMinBars", 1 ); type or paste code here

but I am not sure how to implement this when using two symbols.
Would something along these lines work:

// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	cntsig = 0; // Counter for Signal Balance (Buy = 1, Sell = -1)
	for( bar = 0; bar < BarCount; bar++ )
		{
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					if (sig.IsEntry() )
						{
							cntsig++;
						}
										
					if (sig.IsExit() )
						{
							cntsig--;
						}
						
					if (sig.IsEntry() and ( cntsig != 0 ) ) // Entry Signal and Open Position(s)
						{
							sig.Price = -1; // Cancel Entry Signal
						}

				}
		}

}

Thank you very much in advance.

Please find below the full code. But I am not able to get it to work. I am missing a way how to detect the sequence of Buy and Sell on one bar. I was hoping that either Entry or Exit signal would come first by default, but it seems that in the Backtester is processing both at the same time, so my code does not work.

SetPositionSize( 1000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption("UseCustomBacktestProc", True ); // Enable Custom Backtester

Period = 100;
TSAmount = 0.20;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop(stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, reentrydelay=1);

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop



// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	cntsig = 0; // Counter for Signal Balance (Buy = 1, Sell = -1)
	
	for( bar = 0; bar < BarCount; bar++ )
		{
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					if (sig.IsEntry() )
					{ 
						cntsig++;
						_TRACE( "EntrySig: "  + DateTimeToStr( dt[bar] ) + " | " + cntsig );
					}				
					if (sig.IsExit() ) 
					{
						cntsig--;
						_TRACE( "ExitSig: "  + DateTimeToStr( dt[bar] ) + " | " + cntsig );
					}
					
					_TRACE( "PosCount: "  + DateTimeToStr( dt[bar] ) + " | " + cntsig );	
					
					if (sig.IsEntry() and cntsig != 0 ) // Entry Signal and Open Position(s)
						{
							sig.Price = -1; // Cancel Entry Signal
						}
				}
			bo.ProcessTradeSignals( bar );    //  Process trades at bar (always required)
		}
		bo.PostProcess();    //  Do post-processing (always required)
}

Any assistance highly appreciated. Thank you.

My code so far:

SetPositionSize( 3000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption("UseCustomBacktestProc", True ); // Enable Custom Backtester

Period = 100;
TSAmount = 0.20;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop(stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, reentrydelay=1);

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop

// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	PosQty = 0;
	SigQty = 0;

	for( bar = 0; bar < BarCount; bar++ )	
		{
		
			PosQty = bo.GetOpenPosQty();
			
			_TRACE( "PosQty: " + DateTimeToStr( dt[bar] ) + " | " + PosQty );	
			
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					SigQty = bo.GetSignalQty(bar, 0);
					_TRACE( "PosQty and SigQty @ Signal: " + DateTimeToStr( dt[bar] ) + " | " + PosQty + " | " + SigQty );
				
					_TRACE( "Signals on Bar: " + DateTimeToStr( dt[bar] ) + " | " + sig.Type + " | " + sig.Symbol + " | " + ( sig.PosSize + 2000 ) );
					
					if (sig.IsEntry() ) // Entry Signal 
						{
							if ( PosQty != 0 ) // Open Position(s) or previous Entry
								{
									sig.Price = -1; // Cancel Entry Signal
									_TRACE( "BUY Signal canceled." );
								}
						}
				}
			bo.ProcessTradeSignals( bar );    //  Process trades at bar (always required)
		}
		bo.PostProcess();    //  Do post-processing (always required)
}type or paste code here

It seems that if I cancel a Buy trade, I also need to cancel the corresponding Sell trade. Can anyone confirm this, please? Thank you.

@Tomasz

The strategy behind this code is to buy 2 options (1 Long and 1 Short) for an underlying asset (e.g. WTI) at the same time. The data for the options has been created with ATC. Both options are Buy trades as the short values are inverted in the option data. Both options have a trailing stop and once both options are sold, they can be bought again together for the next trade setup. For the backtest, I have put both options into watchlist 1. Max. open positions = 2. In this sense, it is different from pair trading where you buy one position and sell another one and vice versa.

My AFL code creates Buy signals independent of when the stop limits hit. Therefore, I need to cancel those Buy trades that occur before the second option has been sold.

In my CBT code I cancel a Buy signal (1) if PosQty != 0.. But the corresponding Sell signal (1) still shows when tracing and it prevents my next Buy signal (2) for the second option from triggering. Although bo.ProcessTradeSignals(bar) removes the Buy trade (2), for some reason the next Buy signal (2) is being ignored until the obsolete Sell signal (1) has passed. I only trade two positions and PosQty prior to Buy signal (2) shows 0 for that bar. What am I doing wrong?

SetPositionSize( 3000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 0, 0, 0, 0 );
SetOption("UseCustomBacktestProc", True ); // Enable Custom Backtester

Period = 100;
TSAmount = 0.20;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop(stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, reentrydelay=1);

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop

// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	PosQty = 0;
	SigQty = 0;
	OPL = 0;
	OPS = 0;

	for ( bar = 0; bar < BarCount; bar++ )	
		{
		
			PosQty = bo.GetOpenPosQty();
			
			_TRACE( "PosQty: " + DateTimeToStr( dt[bar] ) + " | " + PosQty + " | " + OPL + " | " + OPS );	
			
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					SigQty = bo.GetSignalQty(bar, 0);
					_TRACE( "PosQty and SigQty @ Signal: " + DateTimeToStr( dt[bar] ) + " | " + PosQty + " | " + SigQty );
					
					if  ( sig.Symbol == "~KO_Long(old)" ) OPL = sig.PosSize + 2000;
					if  ( sig.Symbol == "~KO_Short(old)" ) OPS = sig.PosSize + 2000;
				
					_TRACE( "Signals on Bar: " + DateTimeToStr( dt[bar] ) + " | " + sig.Type + " | " + sig.Symbol + " | " + ( sig.PosSize + 2000 ) );
					
					if (sig.IsEntry() ) // Entry Signal 
						{
							if ( PosQty != 0 ) // Open Position(s) or previous Entry
								{
									sig.Price = -1; // Cancel Entry Signal
									Sell = True;
									_TRACE( "BUY Signal canceled." + sig.symbol );
								}
						}

				}
			bo.ProcessTradeSignals( bar );    //  Process trades at bar (always required)
		}
		bo.PostProcess();    //  Do post-processing (always required)
}

Trade%20Results

In the above trade list, both options are bought at 07:30 but only one option is bought at 08:06.
Subsequent trades where no Buy signal is canceled work well.

Below are the first two trades that execute correctly in the log window.

Log%20Window%201

And here are the follow up trades that won't work.

Log%20Window%202

The Long option is supposed to be bought at 08:06 together with the Short option (2) . But it can't be bought before the Sell signal (1) has passed.

I have identified one of the problem areas. It is multiple signals on a single bar. In order to reduce the problem, I have now changed my trade delays to SetTradeDelays( 1, 0, 0, 0 ). That has solved one issue.

I have also expanded my code in order to eliminate any ambiguities on what happens during code execution. Unfortunately, the custom backtester does not behave in the way I expect.

As per my logic - please tell me if I am wrong here - any BUY signal that has been manually canceled should result in the following:

  1. No execution of trade
  2. PosQty to remain at zero
  3. Based on the above the corresponding SELL signal shall no longer be triggered

My observations were that although a BUY signal was canceled and hence there was no Position, the system still triggered a trailing stop and send my PosQty into negative territory. And then a few bars later a double BUY signal became a single BUY signal instead.

Can you please explain what is happening inside that custom backtester - or at least - provide me with a code that works. I have spent countless hours on this and I am getting increasingly frustrated.

Code is attached hereto:

SetPositionSize( 3000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 1, 0, 0, 0 );
SetOption("UseCustomBacktestProc", true ); // Enable Custom Backtester
SetBacktestMode( backtestRegular );

Period = 100;
TSAmount = 0.20;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop( stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, ReEntryDelay=1 );

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop

// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	PosQty = 0;
	SigQty = 0;
	OPL = 0;
	OPS = 0;
	Poslong = 0;
	PosShort=0;
	BuyLong = 0;
	BuyShort = 0;
	
	for ( bar = 0; bar < 1500; bar++ )	
	//for ( bar = 0; bar < BarCount; bar++ )	
		{
			PosQty = bo.GetOpenPosQty();
			
			_TRACE( "PosQty | LONG | SHORT: " + DateTimeToStr( dt[bar] ) + " | " + PosQty + " | " + PosLong + " | " + PosShort );	
			
			for( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() ) 
				{
					_TRACE( "OpenPos | PosQty: " + DateTimeToStr( dt[bar] ) + " | " + openpos.Symbol + " | " + PosQty );
				}
			
			// Check Exit Signals first to verify Open Positions
			// If PosLong == 0 and PosShort == 0 after this Loop, clear to buy
			// even on same Bar
			// This Signal Loop has to be independent of Entry Signal Loop
			// If not Entry Signal may come first and Information from Exit
			// is not applied
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					if ( sig.IsExit() AND sig.Symbol == "~KO_Long(old)" ) PosLong--;
					if ( sig.IsExit() AND sig.Symbol == "~KO_Short(old)" ) PosShort--;
				}
					//_TRACE( "Before Check | Bar | PosLong | PosShort: " + bar + " | " + PosLong + " | " + PosShort );
					// Verify if a wrong Exit Signal has caused negative Open Positions
					if ( PosLong < 0 ) PosLong = 0;
					if (PosShort < 0 ) PosShort = 0;
				
					_TRACE( "After Check | Bar | PosLong | PosShort: " + bar + " | " + PosLong + " | " + PosShort );
					
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					SigQty = bo.GetSignalQty(bar, 0);
					_TRACE( "PosQty and SigQty @ Signal: " + DateTimeToStr( dt[bar] ) + " | " + PosQty + " | " + SigQty );
					
					if  ( sig.Symbol == "~KO_Long(old)" ) OPL = sig.PosSize + 2000;
					if  ( sig.Symbol == "~KO_Short(old)" ) OPS = sig.PosSize + 2000;
				
					_TRACE( "Signals on Bar: " + DateTimeToStr( dt[bar] ) + " | " + sig.Type + " | " + sig.Symbol + " | " + ( sig.PosSize + 2000 ) );			
					
					if (sig.IsEntry() ) // Entry Signal 
						{
							if ( ( PosLong == 0 AND PosShort == 0 ) ) // Open Position(s) or previous Entry
								{
									//Proceed with Buy
									if (sig.Symbol == "~KO_Long(old)" ) 
										{
											BuyLong = 1;
											_TRACE(  "Buy Long | PosLong | PosShort " + PosLong + " | " + PosShort );
										}
									if (sig.Symbol == "~KO_Short(old)" ) 
										{
											BuyShort = 1;
											_TRACE(  "Buy Short | PosLong | PosShort " + PosLong + " | " + PosShort );
										}
								}
							else
								{
									// Cancel BUY Signal
									sig.Price = -1; // Cancel Entry Signal									
									_TRACE( "BUY Signal canceled. | BuyLong | BuyShort " + sig.Symbol + " | " + BuyLong + " | " + BuyShort );
								}
						}

					}
			
			// Adjust LONG and SHORT Positions for next Bar
			if ( BuyLong == 1 AND BuyShort == 1 ) 
				{
					PosLong++;
					PosShort++;
					BuyLong = 0;
					BuyShort = 0;
				}	
			
			bo.ProcessTradeSignals( bar );    //  Process trades at bar (always required)
		} // end of bar loop
		bo.PostProcess();    //  Do post-processing (always required)
} // end of status=action loop

At one instance in the program you will see the following lines:

				// Verify if a wrong Exit Signal has caused negative Open Positions
				if ( PosLong < 0 ) PosLong = 0;
				if (PosShort < 0 ) PosShort = 0;

Is this really necessary?

Another thing that I would expect after running the custom backtester would be that the arrows showing the actual trades would adapt to the new rules. However, all canceled signals are still being displayed. Is there a way to change the arrows to display the real trades?

Thank you very much for your assistance.

You should not count long/short positions that way (via manual addition/subtraction on signals). Instead iterate thru actual positions

for( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() ) 
{
   if( openpos.IsLong() ) PosLong++
   else PosShort++;
}

Also I strongly advice AGAINST using custom backtester in your case (especially if you don't understand what your own code is doing).. Instead you should set the limit of open positions to 1. This way backtester will not new open position if position is already open. All without custom backtester at all.

1 Like

@ Tomasz
Thanks a lot, this is certainly more elegant. I did not know that I can use IsLong property with openpos.
The problem is, the code is still doing the same thing as before.
The refined code as per below:

SetPositionSize( 3000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 1, 0, 0, 0 );
SetOption("UseCustomBacktestProc", true ); // Enable Custom Backtester
SetBacktestMode( backtestRegular );

Period = 100;
TSAmount = 0.20;

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

Buy = Match2 == 1;
Sell = false;

ApplyStop( stopTypeTrailing, stopModePoint, TSAmount, ExitAtStop = 1, volatile=false, ReEntryDelay = 1 );

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop

// Custom Backtester
// Suppress Buy Signals if either Option A or Option B is open LONG
if( Status("action") == actionPortfolio )
{
	bo = GetBacktesterObject();
	bo.PreProcess();
	SigQty = 0;
	OpenPositions = 0;
	
	for ( bar = 0; bar < 1500; bar++ )	
	//for ( bar = 0; bar < BarCount; bar++ )	
		{
			for( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() ) 
				{
					if( openpos.IsLong() ) OpenPositions++;
				}
			
			_TRACE( "OpenPos: " + DateTimeToStr( dt[bar] ) + " | " + OpenPositions );
				
			for( sig = bo.GetFirstSignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
				{	
					if (sig.IsEntry() ) // Entry Signal 
						{
							if ( ( OpenPositions == 0 ) ) // Open Position(s) or previous Entry
								{
									//Proceed with Buy
									_TRACE( "@ BUY - OpenPos: " + DateTimeToStr( dt[bar] ) + " | " + OpenPositions );
								}
							else
								{
									// Cancel BUY Signal
									sig.Price = -1; // Cancel Entry Signal									
								}
						}
					}

			bo.ProcessTradeSignals( bar );    //  Process trades at bar (always required)
			
			// Reset OpenPositions for next Bar
			OpenPositions = 0;
			
		} // end of bar loop
		bo.PostProcess();    //  Do post-processing (always required)
} // end of status=action loop

As mentioned before I am using two LONG positions to simulate buying one Call Option and one Put Option. As such I will only monitor total open positions.

Output Trades (with sig.Price = -1 enabled):

Canceled%20Buy%20Signals

Buy always happen in pairs. The signal comes from underlying asset, hence is independent of the option data. Exits applies through respective trailing stop for both options.

At 10:12 a single entry happens instead of a double entry.

This is the corresponding log window tracing OpenPositions:

Log%20Window%20Canceled%20Buy%20Signals

OpenPositions prior to Buy is always zero. Still at 10:12 something prevents execution of double entry.

Below are the trades that are triggered with sig.Price = -1 commented out:

All%20Buy%20Signals

I believe the marked trade is causing the problem. It starts after the initial double entry at 09:25 but only concludes at 10:19 after both entries have closed at 09:45 and 10:08 respectively.

This is a very simple application of a trading rule that should not cause an error. There must still be a mistake in my approach, but I can't see it.

Thank you.

1 Like

Log Window not very clear, so I split it into two:

Log%20Window%20Canceled%20Buy%20Signals%201

Log%20Window%20Canceled%20Buy%20Signals%202

Hi,
For those who follow this thread, please find below an update. The fact is that the custom backtester is not suitable to solve this issue.

Buy and Sell signals in this strategy are highly interrelated and canceling one Buy signal may result in a new Buy or Sell signal at a different time and price from the original Buy or Sell signal. Signals at the custom backtester level may therefore be incorrect and can't be referred to.

The solution is instead to create a loop to calculate Buy and Sell signals for both options using the Foreign function. Once the overlapping Buy signals have been removed, a static array is created that contains the remaining Buy signals. Then the portfolio backtest is executed based on those signals.

I will put the code here at a later time.

Finally, i got it to work. Probably not the finest coding out here but it does the job.

// Trade 2 Options (1 LONG and 1 SHORT) for Underlying Asset (UA), e.g. WTI
// BUY Condition based on UA, Sell by Trailing Stop
// Version 21 - Loop Version
// Make sure to assign both Options to Watchlist 1
// Backtest - Filter - Watchlist 1
// Works on 1 Minute Data
// Use SetForeign to define valid BUY points for LONG and SHORT
// Remove BUY Signals during LONG or SHORT Trades 
// Store Resulting BUY Signals in Static Array BuyCondVar

SetPositionSize( 3000, spsShares );
SetOption( "MaxOpenPositions", 2 );
SetTradeDelays( 0, 0, 0, 0 );
SetBacktestMode( backtestRegular );

Period = 100;    // MA Period
r = 1;           // Ratio
EURUSD = 1;      // Exchange Rate, Alternative: Use  Foreign Values of EURUSD Array
TSAmount = 0.20; // Trailing Stop Amount
InLong = 0;
InShort = 0;

// Names of Options
KO_Long = "KO_Long";
KO_Short = "KO_Short";

UA_O = Foreign( "WTI", "O" ); // Underlying Asset OHLC Prices
UA_C = Foreign( "WTI", "C" );
UA_H = Foreign( "WTI", "H" );
UA_L = Foreign( "WTI", "L" );

// BUY Condition for Underlying Asset
MAV = MA( UA_C, Period);
Match = MAV <= UA_H AND MAV >= UA_L; // MA within Bar
Match2 = IIf ( Match AND Ref( Match, -1 ) == 0, 1, 0 ); // MA within Bar for First Time

dt = DateTime(); // Record Date and Time for use of _TRACE in Loop

if (Status("stocknum") == 0) // Perform only when the very first symbol in the watchlist is being processed
{

// Assign OHLC to LONG and SHORT Position
Long_O = Foreign( KO_Long, "O" );
Long_H = Foreign( KO_Long, "H" );
Long_L = Foreign( KO_Long, "L" );
Short_O = Foreign( KO_Short, "O" );
Short_H = Foreign( KO_Short, "H" );
Short_L = Foreign( KO_Short, "L" );

BuyCond = 0;
Long_HHV = Short_HHV = 0;
InLong = InShort = 0;
// Loop to remove BUY Signals with overlapping SELL Signals (LONG/SHORT)
//for( i = 1; i < 1000; i++ )
for( i = 1; i < BarCount; i++ )
{
	if ( Match2[i] AND NOT InLong AND NOT InShort ) // BUY Condition True and No Open Positions in LONG or SHORT
	{
		BuyCond[i] = True;
		InLong = InShort = True;  // Restart Positions
		Long_HHV = Long_O[i];
		Short_HHV = Short_O[i];
		TrailingStopLong = Long_HHV - TSAmount;   // Trailing Stops on 1st Bar based on OPEN Price
		TrailingStopShort = Short_HHV - TSAmount;
	}
		//_TRACE( "Buy | LONG | SHORT :" + DateTimeToStr( dt[i] ) + " | " + BuyCond[i] + " | " + InLong + " | " + InShort );
	// Trailing Stops
	// Trailing Stop at Open of Bar takes Precedence over Stop intra-Bar
	// Trailing Stop at Open >>> Sell Price == Open
	// Trailing Stop intraBar >>> Sell Price == TrailingStopLimit
	// Earliest Sell 1 Bar after Buy
	if ( InLong ) // Check Trade Long
	{
		if ( Long_O[i] < TrailingStopLong )
		{
			InLong = False; // Stop if Bar opens below Trailing Stop
		}
		else 
		{	Long_HHV = Max ( Long_H[i], Long_HHV ); // Adjust Trailing Stop
			TrailingStopLong = Long_HHV - TSAmount;
		}	
		if ( Long_L[i] < TrailingStopLong )
		{
			InLong = False; // Stop if Price falls below Trailing Stop
		}
	}
	
	if ( InShort ) // Check Trade Short
	{
		if ( Short_O[i] < TrailingStopShort )
		{
			InShort = False; // Stop if Bar opens below Trailing Stop
		}
		else
		{
			Short_HHV = Max ( Short_H[i], Short_HHV ); // Adjust Trailing Stop
			TrailingStopShort = Short_HHV - TSAmount;
		}
		 if ( Short_L[i] < TrailingStopShort )
		{
			InShort = False; // Stop if Price falls below Trailing Stop
		}
	}
}
	StaticVarSet( "BuyCondVar", BuyCond ); // Set Static Variable Array
}

// Execute Trades for New Signals based on BuyCond for LONG and SHORT
TrailingStopLimit = 0;
BuyCond = StaticVarGet( "BuyCondVar" ); // Get Static Variable Array
Buy = BuyCond;

TrailingStopLimit = HighestSince( Buy, High, 0 ) - TSAmount; // based on current Bar

StopCondition1 = IIf( Buy, 0, Open < Ref( TrailingStopLimit, -1 ) ); // Stop if Bar opens below previous Trailing Stop, no Sell on Buy Bar
StopCondition2 = Low < TrailingStopLimit;  // Stop if Price falls below Trailing Stop
StopCondition2 = IIf ( StopCondition1 AND StopCondition2, 0, StopCondition2 );

// SELL at Close if Open Position on last Bar
SellLastBar = BarIndex() == BarCount-1;

Sell = StopCondition1 OR StopCondition2 OR SellLastBar;
SellPrice = IIf ( StopCondition1, Open, 
			IIf ( StopCondition2, TrailingStopLimit,
			IIf ( SellLastBar, Close , 0 ) ) );
3 Likes