Capping Amount of Limit Orders Placed

Hi all, I'm trying to overcome the capital allocation issue that arises when using limit orders. I had found this snippet below in another post (can no longer find it) but it doesn't appear to work as expected and I'm not sure why. The gist of the function is to ensure only x of the signals will be kept because it's impossible to know which limit orders will be filled.

Below are the results of my exploration which yielded 5 signals. Of those 5 signals only the top 3 should be 'placed in the market' and the others discarded.

image

This image shows the backtest results where it can be seen that an order was executed for DXI.au. Because ORI.au did not hit the limit, I am expecting only KAR.au and QAN.au to have positions opened.

image

I understand the difficulties of using limit orders in these scenarios but this is how I will be executing the strategy when live so it's crucial I can replicate it in the backtest. I seemed to understand that 'sig.Price = -1' would negate the signal but I may have been mistaken. Can anyone point me in the right direction?

if( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    bo.PreProcess();

    for( i = 0; i < BarCount; i++ )
    {
        PosQty = bo.GetOpenPosQty();
        count = 0;

        for( sig = bo.Getfirstsignal( i ) ; sig ; sig = bo.GetNextSignal( i ) )
        {
            LowArray = Foreign( sig.Symbol, "L" );

            if( sig.IsEntry() )
            {   
				if( bo.Equity < 50000) // 
				{
					psize = (20/100) * bo.Equity;
					
					if( count > 2 - PosQty OR LowArray[i] > sig.Price )
						sig.Price = -1;
						
				}

				if( bo.Equity >= 50000)
				{
					psize = 10000;
					
					if( count > 15 - PosQty OR LowArray[i] > sig.Price )
						sig.Price = -1;
				}
            }
			
			sig.PosSize = psize;
            count++;
        }

        bo.ProcessTradeSignals( i );

    }

    bo.PostProcess();
}

Much love,
larata

There are a number of issues with the CBT that you posted, but the most relevant one is that it appears to assume that the maximum number of open positions should either be 2 (when Equity < 50000) or 15 (when Equity >= 50000). Your message implies, but does not actually state, that the max number of positions should be 5.

I suggest searching this forum for "limit order" and reviewing the results, as this question has been addressed multiple times.

1 Like

You're somewhat correct. In my strategy, yes, it is max 5 positions (20% eq) but I have lowered it to 3 to illustrate my issue.

I believe position indexing starts at 0 so 2 here means 3 open positions (which can be seen in expl/bt output).

if( count > 2 - PosQty OR LowArray[i] > sig.Price )
						sig.Price = -1;

I have also read many limit order related posts, that is where this snippet was pulled from prior to customizing it.

Are you able to highlight the other issues with the CBT?

EDIT: This is the original post by hokchun

It would be nice to know things like that in advance. You should also post your entire AFL file, not just snippets. Here is a simplified example that actually works.

maxPos = 4;
SetPositionSize(100/maxPos, spsPercentOfEquity);

SetOption("usecustombacktestproc", True);
SetCustomBacktestProc("");
SetBacktestMode(backtestRegularRaw);

SetOption("MaxOpenPositions",maxPos);                
SetOption("AllowPositionShrinking", True);   
SetTradeDelays(0, 0, 0, 0);                             
SetOption("PriceBoundChecking", False); 

Buy=Sell=Short=Cover=0;
BuyPrice = SellPrice = ShortPrice = CoverPrice = C;

limitPrice = Ref(C,-1) * 0.99;
BuyPrice = limitPrice;
PositionScore = Ref(RSI(5), -1);

Buy = True;	// Your previous day Setup conditions here
Sell = DayOfWeek() == 5;

if (Status("action") == actionExplore)
{
	Filter = True;
	AddColumn(PositionScore, "PosScore");
	AddColumn(L < limitPrice, "Limit Breach?");
	SetSortColumns(2,-3);
}

if( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    bo.PreProcess();
	
    for( i = 0; i < BarCount; i++ )
    {
        PosQty = bo.GetOpenPosQty();

        for( sig = bo.Getfirstsignal( i ) ; sig ; sig = bo.GetNextSignal( i ) )
        {
            if( sig.IsEntry() )
            {   
				PosQty++;
				LowArray = Foreign( sig.Symbol, "L" );
				
				if( PosQty > maxPos OR LowArray[i] > sig.Price )
					sig.Price = -1;
            }
        }

        bo.ProcessTradeSignals( i );
    }

    bo.PostProcess();
}
1 Like

Thanks mradtke, I used your snippet and tried to change it for my 'dynamic' position sizing but I can't figure this one out. I have posted the entire formula below in hopes I'll get a bit more insight into my shortcomings.

Looping through the signals works but what I am left with is just the signals where the limit price has been hit, which doesn't translate to live trading. If I have 2 available positions (and sufficient cash), I want only the top 2 ranked signals in the market whether they hit the limit or not.

For example, this are the signals that make it through. 4 open positions, 1 available.

afl_dl

With the results below from an exploration, I would expect CXL.au to be the only 'considered' signal even though it did not hit the limit price. The 3 signals that have been generated are the 3 from the total list that did hit the limit price.

afl_ex

I don't expect the complete answer from anyone but I would like to know how this works because I'm lost. Not sure if I also need to be using some static ranks function for what I want to do.

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"

/* CBT & More */

if( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    bo.PreProcess();

    for( i = 0; i < BarCount; i++ )
    {
		posQty = bo.GetOpenPosQty();
		pSize = IIf( bo.Equity <= 50000, 0.20 * bo.Equity, 10000);
		maxPos = IIf( bo.Equity <= 50000, 5, 15);
		
        for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
        {
            if( sig.IsEntry() )
            {
                posQty++;
				lowArray = Foreign( sig.Symbol, "L" );
				
				if( posQty > maxPos OR lowArray[i] > sig.Price )
				{
					sig.Price = -1;
				}
				
            }
            
            sig.PosSize = pSize;
        }

        bo.ProcessTradeSignals( i );
    }

    bo.PostProcess();
}

function ConnorsRSI( lenRSI, lenUD, lenROC )
{
    upDays = BarsSince( C <= Ref( C, -1 ) );
    downDays = BarsSince( C >= Ref( C, -1 ) );
    updownDays = IIf( upDays > 0, upDays, IIf( downDays > 0, -downDays, 0 ) );
    crsiT = ( PercentRank( ROC( C, 1 ), lenROC ) + RSIa( updownDays, lenUD ) + RSI( lenRSI ) ) / 3;

    return crsiT;
}


/* Set Options */

SetOption( "EveryBarNullCheck", True );
SetOption( "InitialEquity", 10000 );
SetOption( "AllowSameBarExit", False );
//SetOption( "MaxOpenPositions", 15 );
SetOption( "CommissionMode", 2 );
SetOption( "CommissionAmount", 5 );
SetOption( "UsePrevBarEquityForPosSizing", True );
//SetOption( "PortfolioReportMode", 0 );
SetTradeDelays( 0, 0, 0, 0 );
SetBacktestMode( backtestRegular );
SetOption( "ActivateStopsImmediately", True );
SetOption("usecustombacktestproc", True);
SetOption("PriceBoundChecking", False); 
SetOption("AllowPositionShrinking", True); 
SetCustomBacktestProc( "" );
RoundLotSize = 1;


/* Ranking/Score */

PositionScore = Ref((1000 + ROC( Close, 150 )), -1);
positionScoreE= 1000 + ROC( Close, 150 );

/* Parameters */

_SECTION_BEGIN( "ConnersRSI" );

p_crsiLength = Param( "cRSI Length", 3, 2, 100, 1 );
P_crsiUd = Param( "cRSI UpClose Length", 2, 2, 100, 1 );
p_crsiROC = Param( "PerecentRank Length", 100, 10, 200, 1 );
p_rsiObLev = Param( "cRSI Overbought", 65, 1, 100, 1 );
p_rsiOsLev = Param( "cRSI Oversold", 15, 1, 100, 1 );

crsiInd = ConnorsRSI( p_crsiLength, P_crsiUd, p_crsiROC );

_SECTION_END();

_SECTION_BEGIN( "Misc." );

p_pctBelow = Param( "Entry Target x% Below", 2, 0.5, 100.00, 0.5 );

_SECTION_END();


/* Buy Conditions */

crsiHitOs = Cross( p_rsiOsLev, crsiInd );

volAbLimit = MA( Volume * Close, 10 ) > 300000;

enterLong = (  MA( Close, 50 ) > MA( Close, 300 )  AND volAbLimit AND Close < 100 AND crsiHitOs AND NorgateIndexConstituentTimeSeries( "$XAO.au" ) );


/* Buy */

entryTarget = Ref(Low, -1) - ((p_pctBelow/100) * Ref(Low, -1));
entryTargetEX = Low - ((p_pctBelow/100) * Low);
BuyPrice = IIf( Open < entryTarget, Open, entryTarget );
Buy = ( Ref( enterLong, -1 ) ) AND( Low < entryTarget );


/* Sell */

exitLong = Cross( crsiInd, p_rsiObLev );
SellPrice = Open;
Sell = Ref( exitLong, -1 );

ApplyStop( stopTypeNBar, stopModeBars, 15 );


/* Exploration / Scan */

Filter = enterLong; // OR Sell;
AddColumn( entryTargetEX, "Entry Price" );
AddColumn( Open, "Open" );
AddColumn( High, "High" );
AddColumn( Low, "Low" );
AddColumn( Close, "Close" );
AddColumn( positionScoreE, "Pos Score");
AddColumn(MA( Volume * Close, 10 ), "Vol Avg $");


That's because you didn't follow the working example that I posted. You need to remove the limit price check from your Buy condition so that AmiBroker creates signals for all your Setups. So change this:

Buy = ( Ref( enterLong, -1 ) ) AND( Low < entryTarget );

to this:

Buy = Ref( enterLong, -1 );
3 Likes

Much appreciated, it appears to be functioning as intended. I now acknowledge the importance of posting the full formula too.

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