Buy today at Close or Next day at Open depending on ATC condition

Dear all,

I am currently trading a system which Buyprice equal to the Close of the Buy signal candle. However, when trading real-time, I encounter this situation in which my ATC (At the Close) order couldn't get filled because there was no one selling, as price has reached the maximum increase for the day.
As the result, I must wait til next day to buy that stock At the Open (ATO).
(This phenomenon is knowned as "Ceiling", a very common phenomenon happens in the Vietnamese stock market which I am trading. The Ceiling is placed at 7% above yesterday closed.)

With this reality, I want the following example sequence to happen:
Day 1: ATC of the day, I got 3 buy signal, the signal with the highest Position score is also at the Ceiling, so I must wait til next day to buy that signal ATO.
Day 2: I had bought the stock I intended to buy yesterday at ATO. And at ATC, I got 5 new signals. I bought the signal with the highest Position score ATC because that stock has not reach the ceiling.

So now I need to set up the backtest so that BuyPrice either equal Open of next day or Close of today, depend on whether the stock with the highest Position score today has reached the ceiling or not.
Preferably, I still want to be able to buy at the Close, so the "ceiling" is very much an undesirable situation, yet I still need to cope with this reality.

My attemp to solve the problem is the following code.

TickSize = 0.05;
market = MarketID(1);
exchange_limit = IIf(market == "HSX", 7, IIf(market == "HNX", 10, 15));
overlimit = abs((C+TickSize)/Ref(C,-1) - 1) > exchange_limit;
delay = IIf(overlimit,1,0);
buyvalue = IIf(delay == 1, Open, Close);

SetTradeDelays(delay, 0, 0, 0);
BuyPrice = buyvalue;
SellPrice = Close; //Realistic result would be Buyprice = Open

However, the above code has the problem with the SetTradeDelays as the function doesn't allow me to use array.
Any help would be greatly appreaciate!

As I further explore this topic, I encounter the following situation that I need to solve:
I want to enter only 1 signal per day either at the close OR enter 2 signal per day: 1 signal at ATO, which is the one with the highest Position score yesterday and the one with the highest Position score today at ATC.

The issue here is that using a normal Position score function, the signal that buy at ATO will surely look into the future, as I use the Close array to calculate that score.

How do I solve this?

I have been working on this but haven't found a correct way to achieve the goal mentioned.
I am thinking maybe I need a custom backtest procedure. I have read the following link and it gives me some ideas

I have tried to integrated the code at that link to my CBT, but havent get to anywhere yet. Can anyone please help? @mradtke would you please take a look at my problem? I have always regard you as the CBT master!

lim_entry = 1; //Allow max 1 position enter per day
limit_daily_entries = lim_entry;
SetOption("UseCustomBacktestProc", True );
SetCustomBacktestProc( "" );
if ( Status( "action" ) == actionPortfolio )
{
    // retrieving portfolio backtester interface
    bo = GetBacktesterObject();
    bo.PreProcess();

    for ( i = 0; i < BarCount; i++ ) 
    {        
        // iterating through all trade signals and adjust pos size
        PosQty = bo.GetOpenPosQty();
		_TRACE("Day ------ " + i);
		_TRACE("Open Position: " + PosQty);        
        count = 0;
        eq = bo.Equity;
        
        for ( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) 
		{	
			LowArray=Foreign(sig.Symbol,"Low"); //grab the low to check limit order fill
			//CloseArray=Foreign(sig.Symbol,"Close"); //grab the close to check limit order fill
			if( sig.IsEntry() ) 
            {	
				if ( count < limit_daily_entries AND LowArray[i] <= sig.Price AND count <= MaxOpenPos - PosQty ) //limit 1 position per day and check if low array <= entry price
				{	
					count ++;  
					Max_buy1 = StaticVarGet( "max_buy" + sig.Symbol );
					ew_pos = StaticVarGet( "Equal_weight" + sig.Symbol );
					//Van_Tharp = StaticVarGet( "Van_Tharp" + sig.Symbol );
					Max_buy_portion[i] = Max_buy1[i]/eq[i]*100;    
					sig.PosSize = -Min(ew_pos[i],Max_buy_portion[i]);
					//sig.PosSize = -Min(Van_Tharp[i],Max_buy_portion[i]); 
				}
				else
					sig.Price = -1; //ignore entry signal
			}
        }
        bo.ProcessTradeSignals( i );
        
        StaticVarSet( Name() + "Equity", eq[i] );
    }	//  End of for loop over bars
    bo.PostProcess();
} 

I would paraphase my question here.

If a BUY signal with the highest position score meet the Overlimit condition, I cannot Buy that signal today, and I want to wait til next day to buy at the Open.
If the BUY signal with the highest position score does NOT meet the Overlimit contion, I want to BUY that signal at the CLOSE today.
However I only want to buy 1 signal per day, max 2 in which I buy 1 signal ATO and 1 signal ATC.

How do I write a custom backtest procedure to do this?

I think you will need to use a low-level CBT so that you can call bo.EnterTrade on a bar where there is no actual signal. Once you have converted your existing mid-level CBT to a low-level CBT, then it should be pretty easy to use a couple of static variables to remember which trade(s) need to be handled on the next bar's open instead of the current bar's close. I don't believe you should do something like this (calling EnterTrade using the next bar number), although it would be the simplest approach:

for (bar = 0; bar < BarCount; ++bar)
{
   ...
   if (isOverlimit)
   {
      bo.EnterTrade(bar+1, sig.Symbol, sig.IsLong, openPrice, sig.PosSize);
   }
   else
   {
      bo.EnterTrade(bar, sig.Symbol, sig.IsLong, sig.Price, sig.PosSize);
   }
   ...
}

Perhaps @Tomasz can confirm that's a bad approach.

1 Like

I dont think there is any CBT required for that.

Use Ref() function ( -> Ref(Buy,-delay) ) and set SetTradeDelays to zero.

TickSize = 0.05;
market = MarketID(1);
exchange_limit = IIf(market == "HSX", 7, IIf(market == "HNX", 10, 15));
overlimit = abs((C+TickSize)/Ref(C,-1) - 1) > exchange_limit;

delay = IIf(Ref(overlimit,-1),1,0);
buyvalue = IIf(delay > 0, Open, Close);

SetTradeDelays(0, 0, 0, 0);

BuyPrice = buyvalue;
SellPrice = Close; //Realistic result would be Buyprice = Open

Buy = Cross(C, MA(C,10));
Buy = Ref(Buy, -delay) AND NOT (overlimit AND delay == 0); 

Sell = Cross(MA(C,10), C);

Short = Cover = 0;

Rather use

delay = IIf(Ref(overlimit,-1),1,0);

as above

2 Likes

Dear @fxshrat,

Your answer is simple yet effective, and almost be the solution. Except for the fact that you have forgotten the issue with position score.
When I run the backtest, the backtest indeed bought the correct ATO price of the signal that has overlimit condition the previous day. However, the reason that the backtest choose to buy that one signal was because at the end of the day, that stock end up to be the one with the highest position score compare to other signal generate during the day.
And that's something I am not possible to replicate in real time trading. I need to buy the signal with the highest score YESTERDAY that was overlimit right away ATO today, and then if any other signal pop up today with the highest score, I would buy that stock too, preferably ATC, unless that highest score signal is overlimit again.
I want to buy that ATC stock even when my ATO-bought stock has a higher score.

Since the complication of this procedure, I am thinking about using CBT. Currently, I have a CBT written that only allow me to buy a maximum of 1 signal per day, and it has to be the one with the highest score. But since now I will need to buy 1 stock at ATO and 1 stock at ATC, I will have to allow 2 signals to be bought per day max. But then if I change the CBT to allow 2 entry per day, then there will be times when instead of buy 1 ATO stock and 1 ATC stock, the backtester buy 2 ATC stocks.

I hope that make sense. Much appreciate your help @fxshrat and @mradtke :wink:

1 Like

Dear @mradtke and @fxshrat

Here is my CBT code full for my system. I am trying to implement Matt's suggestion to use low level CBT, but this code still got me no where.
Can you guys please take a look?
I also include other things to add custom metric to my backtest result. I wonder if they affect the low level CBT that I am trying to create to achieve my aforementioned goal?

//==================================== LIMIT SIZE + ADD CUSTOM METRIC CBT ======================================
//===================================== PRE-CUSTOM BACKTEST PROCEDURE (LIMIT LIQUIDITY) =========================

return1 = (C*0.5+O*0.1+H*0.2+L*0.2);
thanhkhoan = return1*Volume;
thanhkhoan1=Ref(thanhkhoan ,-1);
thanhkhoanbq10=MA(thanhkhoan1 ,10);
thanhkhoanbq20=MA(thanhkhoan1 ,20); //Gia tri giao dich TB 20 phien
Max_buy = thanhkhoanbq20 * lim_liquid / 100; //5% of GTGD 20 phien
precision = 0;
ew_pos = -25;
lim_entry = 1;

StaticVarSet( "mkt_vol" + Name(), thanhkhoanbq20);

days_held = BarsSince(Buy);
tkbq20 = Prec(thanhkhoanbq20, precision );
mb20 = Prec(Max_buy, precision );
volprec = Prec(V, precision );
 
StaticVarSet( "mkt_vol" + Name(), tkbq20 );
StaticVarSet( "max_buy" + Name(), mb20 );
StaticVarSet( Name() + "liquidity", tkbq20 ); //Display
StaticVarSet( Name() + "max_entry", mb20 ); //Display
StaticVarSet( Name() + "10% Volume", volprec/10); //Display
StaticVarSet( "Equal_weight" + Name(), ew_pos);
StaticVarSet( Name() + "Days Held", days_held ); //Display


limit_daily_entries = lim_entry;
SetCustomBacktestProc( "" );
if ( Status( "action" ) == actionPortfolio )
{
    // retrieving portfolio backtester interface
    bo = GetBacktesterObject();
    bo.PreProcess();

    for ( i = 0; i < BarCount; i++ ) 
    {        
        // iterating through all trade signals and adjust pos size      
        count = 0;
        eq = bo.Equity;
        
        for ( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) 
		{	
			if( sig.IsEntry() && sig.IsLong() ) 
            {	
				//if ( count < limit_daily_entries[i] ) //limit = 1
				if ( overlimit[i] )
				{	
					//sig.Price = Open[i+1];
					bo.EnterTrade(i+1, sig.Symbol, sig.IsLong, sig.Price, sig.PosSize);
				}
				else
				{
					if ( count < limit_daily_entries) //limit = 1
					{	
						count ++;  
						Max_buy1 = StaticVarGet( "max_buy" + sig.Symbol );
						ew_pos = StaticVarGet( "Equal_weight" + sig.Symbol );
						Max_buy_portion[i] = Max_buy1[i]/eq[i]*100;    
						sig.PosSize = -Min(ew_pos[i],Max_buy_portion[i]);
						bo.EnterTrade(i, sig.Symbol, sig.IsLong, sig.Price, sig.PosSize);
					}
					else
						sig.Price = -1; //ignore entry signal
				}
			}
			else
			{
				if(sig.IsExit() && sig.IsLong())
					bo.ExitTrade(i,sig.Symbol,sig.Price);
			}
        }
        bo.HandleStops(i);
		bo.UpdateStats(i,1);
		bo.UpdateStats(i,2);   
        StaticVarSet( Name() + "Equity", eq[i] );
    }	//  End of for loop over bars
    
	for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
	{
		// read Vol values and display as custom metric
        symbol_liquidity = StaticVarGet( trade.Symbol + "liquidity" );
        symbol_maxentry = StaticVarGet( trade.Symbol + "max_entry" );
        symbol_vol = StaticVarGet( trade.Symbol + "10% Volume" );
        symbol_ew = StaticVarGet( "Equal_weight" + trade.Symbol );
        symbol_dayhelds = StaticVarGet( trade.Symbol + "Days Held" );
        equityvalue = Lookup( bo.EquityArray, trade.EntryDateTime );
        
        trade.AddCustomMetric( "PosSize", Lookup( symbol_ew, trade.EntryDateTime ) );
        //trade.AddCustomMetric( "PosSize", Lookup( symbol_VanTharp, trade.EntryDateTime ) );
        trade.AddCustomMetric( "GTGD (trieu)", Lookup( symbol_liquidity, trade.EntryDateTime ) );
        trade.AddCustomMetric( "Max Buy (trieu)", Lookup( symbol_maxentry, trade.EntryDateTime) );
        trade.AddCustomMetric( "Max Buy (shares)", Lookup( symbol_vol, trade.EntryDateTime) );
        trade.AddCustomMetric( "Days Held", Lookup( symbol_dayhelds, trade.ExitDateTime) );
		trade.AddCustomMetric( "Equity", equityvalue );
		trade.AddCustomMetric( "Entry", 100*trade.GetEntryValue/equityvalue);
	}	//Close loop for closed trades
	
	for (trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ))
    {
        // read ATR values and display as custom metric
        symbol_liquidity = StaticVarGet( trade.Symbol + "liquidity" );
        symbol_maxentry = StaticVarGet( trade.Symbol + "max_entry" );
        symbol_vol = StaticVarGet( trade.Symbol + "10% Volume" );
        symbol_ew = StaticVarGet( "Equal_weight" + trade.Symbol );
        equityvalue = Lookup( bo.EquityArray, trade.EntryDateTime );
        days_held = BarsSince(Buy);
        
        trade.AddCustomMetric( "PosSize", Lookup( symbol_ew, trade.EntryDateTime ) );
        trade.AddCustomMetric( "GTGD (trieu)", Lookup( symbol_liquidity, trade.EntryDateTime ) );
        trade.AddCustomMetric( "Max Buy (trieu)", Lookup( symbol_maxentry, trade.EntryDateTime) );
        trade.AddCustomMetric( "Max Buy (shares)", Lookup( symbol_vol, trade.EntryDateTime) ); 
        trade.AddCustomMetric( "Days Held", days_held );
        trade.AddCustomMetric( "Equity", equityvalue );
        trade.AddCustomMetric( "Entry", 100*trade.GetEntryValue/equityvalue);
    }	//Close loop for open trades
    
    bo.PostProcess();
} 	//Close status portfolio action 

A couple of things:

  • You are using the approach that I specifically advised NOT to use unless @Tomasz confirms that it's OK to enter a trade for the next bar.
  • You need to create a symbol-specific static variable for overlimit. The way your code is written now, it's just picking up the overlimit value from Phase 1 when it was run with the ~~~EQUITY symbol.

For example, in phase 1 you could do something like this:

overlimit = abs((C+TickSize)/Ref(C,-1) - 1) > exchange_limit;
StaticVarSet(Name()+"overlimit", overlimit);

Then in your CBT, you need to retrieve the overlimit value for the signal you are processing:

overlimit = StaticVarGet(sig.Symbol+"overlimit");
if (overlimit[i])
// Delay entry until the next bar

Please make sure you understand AmiBroker's two-phase approach to running a backtest: Phase 1 is executed once for each symbol in your watchlist to generate signals and other associated data (price, position size, position score, etc.). When all symbols have been processed, then AB runs both Phase 1 and Phase 2 (CBT) using the ~~~EQUITY symbol. That means the CBT is only executed ONCE, not once per symbol.

1 Like

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.