Position Sizing based on Equity Value using Custom Backtester

Hi,

I am trying to code the position sizing using the current equity value. I have written the following code in the Mid-Level Interface using Custom Backtesting.

SetCustomBacktestProc("");
if(Status("action") == actionPortfolio)
{
   bo = GetBacktesterObject();
   bo.PreProcess();
   for( i = 0; i<BarCount; i++)
   {  
     CurrentPortfolioEquity = bo.Equity;
     for(sig = bo.GetFirstSignal(i) ; sig ; sig = bo.GetNextSignal(i) )
     {
         if (sig.IsEntry())
        {
          NiftyClose = Foreign("$NIFTY-NSE"+sig.Symbol , "C" ) ;
		  LotSize = IIf(Datenum()>=1110101 AND DateNum()<=1141031, 50 , IIf( DateNum()>=1141031 AND DateNum()<=1151030 , 25 , 75)) ;  
          MarginPerLot = (NiftyClose[i]*LotSize/15);
          TotalLots = round(CurrentPortfolioEquity / MarginPerLot );
          TotalUnits = TotalLots*LotSize;
          sig.PosSize = -(TotalUnits*sig.Price/CurrentPortfolioEquity)*100;
        }
         
	  }	
	  bo.ProcessTradeSignals(i);
	}
   bo.PostProcess();
} 

While backtesting, I get the following error

13

Not sure where am I going wrong.

sig.PosSize = -(TotalUnits*sig.Price/CurrentPortfolioEquity)*100;

This line in the code isn't the same as in the error message.

Yes. Could that mean there is an Error in the AFL?

Why don't use you post the correct code with relevant error message.

If you are testing, two or more different lines of code, paste each one properly.

Also, CurrentPortfolioEquity is not defined in your partial code, you need to be clear of what you are doing.

1 Like

So this is the code

_SECTION_BEGIN("");
SetChartOptions(0,chartShowArrows|chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ));
Plot( C, "Close", ParamColor("Color", colorBlack ), styleNoTitle | ParamStyle("Style") | GetPriceStyle() ); 

if(Name()=="NIFTY11JAN6200CE"){Short = (DateNum()==1110107 AND TimeNum() == 131500) ; Cover = (DateNum () == 1110127  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11FEB5900CE"){Short = (DateNum()==1110127 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110217  AND TimeNum()==111500 ) ; }
if(Name()=="NIFTY11MAR5100PE"){Short = (DateNum()==1110217 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110224  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11MAR5300PE"){Short = (DateNum()==1110224 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110224  AND TimeNum()==131500 ) ; }
if(Name()=="NIFTY11MAR5600CE"){Short = (DateNum()==1110224 AND TimeNum() == 131500) ; Cover = (DateNum () == 1110301  AND TimeNum()==141500 ) ; }
if(Name()=="NIFTY11MAR5200PE"){Short = (DateNum()==1110301 AND TimeNum() == 141500) ; Cover = (DateNum () == 1110315  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11MAR5700CE"){Short = (DateNum()==1110315 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110324  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11APR5300PE"){Short = (DateNum()==1110324 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110331  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11APR5400PE"){Short = (DateNum()==1110331 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110419  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11APR6000CE"){Short = (DateNum()==1110419 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110421  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11MAY5600PE"){Short = (DateNum()==1110421 AND TimeNum() == 101500) ; Cover = (DateNum () == 1110428  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11MAY5700PE"){Short = (DateNum()==1110428 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110429  AND TimeNum()==131500 ) ; }
if(Name()=="NIFTY11MAY6000CE"){Short = (DateNum()==1110429 AND TimeNum() == 131500) ; Cover = (DateNum () == 1110526  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11JUN5600CE"){Short = (DateNum()==1110526 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110531  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11JUN5300PE"){Short = (DateNum()==1110531 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110613  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11JUN5700CE"){Short = (DateNum()==1110613 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110624  AND TimeNum()==141500 ) ; }
if(Name()=="NIFTY11JUL5100PE"){Short = (DateNum()==1110624 AND TimeNum() == 141500) ; Cover = (DateNum () == 1110630  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11JUL5200PE"){Short = (DateNum()==1110630 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110720  AND TimeNum()==141500 ) ; }
if(Name()=="NIFTY11JUL5800CE"){Short = (DateNum()==1110720 AND TimeNum() == 141500) ; Cover = (DateNum () == 1110726  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11AUG5500PE"){Short = (DateNum()==1110726 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110727  AND TimeNum()==111500 ) ; }
if(Name()=="NIFTY11AUG5800CE"){Short = (DateNum()==1110727 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110728  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11AUG5700CE"){Short = (DateNum()==1110728 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110825  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11SEP5200CE"){Short = (DateNum()==1110825 AND TimeNum() == 111500) ; Cover = (DateNum () == 1110902  AND TimeNum()==91500 ) ; }
if(Name()=="NIFTY11SEP4700PE"){Short = (DateNum()==1110902 AND TimeNum() == 91500) ; Cover = (DateNum () == 1110922  AND TimeNum()==121500 ) ; }
if(Name()=="NIFTY11OCT5200CE"){Short = (DateNum()==1110922 AND TimeNum() == 121500) ; Cover = (DateNum () == 1110929  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11OCT5100CE"){Short = (DateNum()==1110929 AND TimeNum() == 111500) ; Cover = (DateNum () == 1111010  AND TimeNum()==111500 ) ; }
if(Name()=="NIFTY11OCT4700PE"){Short = (DateNum()==1111010 AND TimeNum() == 111500) ; Cover = (DateNum () == 1111025  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11NOV4900PE"){Short = (DateNum()==1111025 AND TimeNum() == 111500) ; Cover = (DateNum () == 1111111  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11NOV5400CE"){Short = (DateNum()==1111111 AND TimeNum() == 101500) ; Cover = (DateNum () == 1111124  AND TimeNum()==101500 ) ; }
if(Name()=="NIFTY11DEC5200CE"){Short = (DateNum()==1111124 AND TimeNum() == 111500) ; Cover = (DateNum () == 1111202  AND TimeNum()==121500 ) ; }
if(Name()=="NIFTY11DEC4600PE"){Short = (DateNum()==1111202 AND TimeNum() == 121500) ; Cover = (DateNum () == 1111212  AND TimeNum()==141500 ) ; }
if(Name()=="NIFTY11DEC5100CE"){Short = (DateNum()==1111212 AND TimeNum() == 141500) ; Cover = (DateNum () == 1111229  AND TimeNum()==101500 ) ; }

SetOption("InitialEquity" , 10000000 ) ;
SetOption("DisableRuinStop" , True ) ;

SetCustomBacktestProc("");
if(Status("action") == actionPortfolio)
{
   bo = GetBacktesterObject();
   bo.PreProcess();
   for( i = 0; i<BarCount; i++)
   {
     CurrentPortfolioEquity= bo.Equity;
     for(sig = bo.GetFirstSignal(i) ; sig ; sig = bo.GetNextSignal(i) )
     {
         if (sig.IsEntry())
        {
          NiftyClose = Foreign("$NIFTY-NSE"+sig.Symbol , "C" ) ;
		  LotSize = IIf(Datenum()>=1110101 AND DateNum()<=1141031, 50 , IIf( DateNum()>=1141031 AND DateNum()<=1151030 , 25 , 75)) ;  
          Deposit = (NiftyClose[i]*LotSize/15);
          TotalLots = round(CurrentPortfolioEquity / Deposit );
          TotalUnits = TotalLots*LotSize;
          sig.PosSize = -(TotalUnits*sig.Price/CurrentPortfolioEquity);
        }
         
	  }	
	  bo.ProcessTradeSignals(i);
	}
   bo.PostProcess();
} 
  

_SECTION_END();

After running the code, the only error I get is this:

13

Are you sure you're the AA window is running the same file that is being edited ?
Because even now the two lines are different.

There have been such cases that file name is same but path is different, or the APX is loaded with old code and the AFL being updated isn't the one actually running.
Just like in chart, you insert an indicator and then subsequent changes to file don't reflect because the copy is running.

Try changing this line sig.PosSize = … to something else and see if the New code is being run.
sig.PosSize = 2; for example.

Its fairly simple. The line below is yielding an array because the first arg to IIf() is an array. Run as Exploration to see. Any calculations based on it go downhill from there. So, you can't assign an array to sig.PosSize.

LotSize = IIf(Datenum()>=1110101 AND DateNum()<=1141031, 50 , IIf( DateNum()>=1141031 AND DateNum()<=1151030 , 25 , 75)) ;  

Filter = Status( "lastbarinrange" );
AddTextColumn( typeof( LotSize ), "typeof", 1.0 );

I found this code in Amibroker User's Knowledge Base

SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
    bo = GetBacktesterObject();    //  Get backtester object
    bo.PreProcess();    //  Do pre-processing
    for (i = 0; i < BarCount; i++)    //  Loop through all bars
    {
        for (sig = bo.GetFirstSignal(i); sig; sig = bo.GetNextSignal(i))
        {    //  Loop through all signals at this bar
            if (sig.IsEntry() && sig.IsLong())    //  If this signal is a long entry (ie. buy)
            {
                evol = Foreign("~evol_"+sig.Symbol, "V");   //  Get stock's composite volume array
                psize = sig.PosSize;    //  Get position size specified in AFL code
                if (psize < 0)    //  If it's negative (a percentage of equity)
                    psize = (-psize/100) * bo.Equity;  //  Convert to dollar value using current equity
                scnt = psize / sig.Price;    //  Calculate number of shares for position size
                if (scnt > evol[i] / 10)    //  If number of shares is > 10% of volume EMA
                {
                    scnt = evol[i] / 10;    //  Limit number of shares to 10% of EMA value
                    psize = scnt * sig.Price;    //  Calculate new position size
                }
                if (psize < 5000)    //  If position size is less than $5,000
                    psize = 0;    //  Set to zero so buy signal will be ignored
                else
               {
                    if (psize > 50000)    //  If position size is greater than $50,000
                        psize = 50000;    //  Limit to $50,000
                }
                sig.PosSize = psize;    //  Set modified position size back into object
            }
        }    //  End of for loop over signals at this bar
        bo.ProcessTradeSignals(i);    //  Process trades at this bar
    }    //  End of for loop over bars
    bo.PostProcess();    //  Do post-processing
}

There is an array which is assigned to sig.PosSize

It works when I keep sig.PosSize = -2. Is there a way to access arrays used in the custom backtester in the main AFL?

sig.PosSize = -100 * ( LastValue( TotalUnits) * LastValue( sig.Price) / CurrentPortfolioEquity );

Then most likely you have a Type issue, try and force Array to scalar with LastVaue() as shown.
Permute through each, you'll know which variable is causing it.

I'll get involved but probably shouldn't. Anyway, I'll detail the problem and solutions.

Karan - first, note that the code fragment that you got from the Knowledge Base used "if" statements, not "IIf" functions. If statements are used there to produce scalars. That is why in the example, evol[i] is used because if references the array element in "evol" that the bar counter "i" indexes.

The bottom line is that you have to do one of the following -

  1. Get rid of the "IIf()" statement in the "LotSize = " statement and replace with an "If" statement construct.

  2. Or do the following, which is simple but slightly less efficient with minimal change. Change the following statement -

LotSize = IIf(Datenum()&gt;=1110101 AND DateNum()&lt;=1141031, 50 , IIf( DateNum()&gt;=1141031 AND DateNum()&lt;=1151030 , 25 , 75)) ;

Change it to -

LotSizeArray = IIf(Datenum()&gt;=1110101 AND DateNum()&lt;=1141031, 50 , IIf( DateNum()&gt;=1141031 AND DateNum()&lt;=1151030 , 25 , 75)) ;
LotSize = LotSizeArray[i];

Lastly, an unsolicited suggestion.

If you are going to write anything more than simple code, it is important to learn debugging techniques. Learn to sprinkle your code with _TRACE() statements. Specifically, after seeing the "Type mismatch" error, you would put a _TRACE() statement before it to output the variable types. This would have led you back to the problem source.

3 Likes

So this is the final code.

SetCustomBacktestProc("");
if(Status("action") == actionPortfolio)
{
   bo = GetBacktesterObject();
   bo.PreProcess();
   for( i = 0; i<BarCount; i++)
   {  
     CurrentPortfolioEquity = bo.Equity;
     NiftyClose = Foreign("$NIFTY-NSE", "C" ) ;
     for(sig = bo.GetFirstSignal(i) ; sig ; sig = bo.GetNextSignal(i) )
     {
         if (sig.IsEntry())
        {
          LotSizeArray = IIf(Datenum()>=1110101 AND DateNum()<=1141031, 50 , IIf( DateNum()>=1141031 AND DateNum()<=1151030 , 25 , 75)) ;  
          LotSize = LotSizeArray[i] ;
          Deposit = (NiftyClose[i]*LotSize/15);
          TotalLots = round(CurrentPortfolioEquity / Deposit );
          TotalUnits = TotalLots*LotSize;
          sig.PosSize = -100*(LastValue(TotalUnits)*LastValue(sig.Price)/CurrentPortfolioEquity);
           
        }
         
	  }	
	  bo.ProcessTradeSignals(i);
	}
   bo.PostProcess();
} 

So this code works properly now. Its calculating the position Size perfectly.
@bruce1r,@travick,@abbruiser Thank you for your help.

Really final code? First the code posted is incomplete.

Secondly you can achieve the same without custom backtester at all.

Just use built-in
SetPositionSize() function with spsPercentOfEquity, RoundLotSize variable and MarginDeposit variable
Read:
http://www.amibroker.com/guide/h_portfolio.html
and
http://www.amibroker.com/guide/h_futbacktest.html

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