Sample code required for Options Backtesting

HI,
I have a similar issue mentioned in the below forum, this was closed so i am unable to continue it. I have used the exact approach as the solution provided but it doesn't solve the problem.
The Strick selection happens for the last day of the testing date range and for all other days, it continue to keep the same strike and doesn't change for each day
For example, if i select day 1 till day 5 in the back test, it selects the strike perfectly for day 5, and then for all the other days, it keeps selecting the same strike.
Has any one successfully did any back testing in Amibroker with dynamic strike selection in the coding?
I want to know whether Amibroker is capable of handling multiple Options strike?

Please consider augmenting your question with your own coding effort. Please follow this advice: How to ask a good question

The thing that was explained in the quoted thread is that tradeable security is current one. SetForeign should not be used to select option symbol to be traded but to access underlying stock price to find out when to trade current symbol. So it is the reverse from what the poster assumed. AmiBroker is multithreaded application and processes multiple symbols in parallel. For each of those simultaneously processed symbols you decide when to trade them. It is different from “sequential” thinking presented by the poster.

Thanks for the reply, I am trying to back test with the below logic

  1. Capture ATM CE and PE at 9:30 AM and set 30% as SL on its current value.
  2. If SL is hit at one LEG, initiate trade at the other LEG with same predefined SL.
  3. You can again take oppsite side trade if same condition occurs (SL Hit)
  4. Exit trade at 3:15 PM if position is open.

Code is as below

currentTime= TimeNum()/100;
TradeTime= currentTime>=930 AND currentTime<=1400;
squareOffTime= currentTime>=1500;
captureDateTime= StrToDateTime(StrExtract( Date(),0,' ')+" 09:30:00");

SLPercentage = 0.30; //Stop Loss % for each Leg
CurrentSymbol = Name();
OptionPrefix = "BANKNIFTY";
UnderlyingIndex = "NIFTYBANK-INDEX";
SetPositionSize( 25, spsShares );	

//Getting Underlying Price and setting ATM strike
SetForeign( UnderlyingIndex );
IndexCurrentPrice= Lookup(Close, captureDateTime, -1);
RestorePriceArrays();

ATMStrikePrice= NumToStr(round( IndexCurrentPrice / 100 ) * 100, 1.0, False);
CEStrike= OptionPrefix + "LW" + ATMStrikePrice + "CE";
PEStrike= OptionPrefix + "LW" + ATMStrikePrice + "PE";

//Fetching CE Latest Price and setting SL
SetForeign( CEStrike );
CELatestPrice= Close;
CE_SL= Lookup(Close, captureDateTime, -1)*(1+SLPercentage);
CE_SL_Hit=CELatestPrice>CE_SL;
RestorePriceArrays();

//Fetching PE Latest Price and setting SL
SetForeign( PEStrike );
PELatestPrice= Close;
PE_SL= Lookup(Close, captureDateTime, -1)*(1+SLPercentage);
PE_SL_Hit=PELatestPrice>PE_SL;
RestorePriceArrays();

//Trade execution
if (StrFind(CurrentSymbol, ATMStrikePrice + "CE"))
{
	Short= TradeTime AND PE_SL_Hit;
	Cover= squareOffTime;
	Short= ExRem( Short, Cover );
	Cover= ExRem( Cover, Short );

}

if (StrFind(CurrentSymbol, ATMStrikePrice + "PE"))
{
	Short= TradeTime AND CE_SL_Hit;
	Cover= squareOffTime;
	Short= ExRem( Short, Cover );
	Cover= ExRem( Cover, Short );
}

But when I run this code against a range of dates, it selects correct ATM only for the last date of the range. And then uses same ATM strike for the back testing of the rest of the days which gives wrong result
Report screenshot is as below, as you see, the last date of the back test range is 04-08-2022 and the strike selected for that day is correct, but same strike is used by the back tester for all other days.
OptionsAFL

You possibly have multiple issues with your code, and I suggest using an Exploration to view your intermediate values so you better understand what's going on. Regarding your question about the strike only being accurate for the last day, it's because of this line:

ATMStrikePrice= NumToStr(round( IndexCurrentPrice / 100 ) * 100, 1.0, False);

AmiBroker does not support arrays of strings, so ATMStrikePrice is a single string. It is created from a single numeric value (i.e. a Scalar), which is the Last Value of IndexCurrentPrice.

As @Tomasz already suggested, you may want to consider a different approach altogether. Specifically, instead of trying to determine which symbol(s) to trade each day, you should instead decidefor each symbol in your watchlist (which presumably includes all options contracts that you would consider trading) whether that symbol should be traded at any particular day/time.

@mradtke Thanks for identifying the issue with ATMStrikePrice string. Any suggestion on how to overcome this? I tried to store the value as number (removing the string conversion) but the result is still the same. No mater which approach i take , i eventually have to see what is the underlying price to determine the strike price at 9:30. And I am unable to fetch that details.
If I come to know how to get a foreign value at a particular time of the day, each day, i am fine with the rest of the code.

Perhaps something like this is what you're looking for:

IndexCurrentPrice= Foreign(UnderlyingIndex, "C");
IndexPriceAt930 = ValueWhen(TimeNum() == 93000, IndexCurrentPrice);
ATMStrike= round(IndexPriceAt930 / 100 ) * 100;

First and foremost thing is that one really has to read the manual, before doing anything else

NumToStr is synonym for WriteVal. The docs for
http://www.amibroker.com/guide/afl/writeval.html
say this:

WriteVal always returns one value of the array (not arrays of values). In almost all cases this is LastValue of the array but in indicators it is "selected value" -
the one that is selected by the vertical line.

NumToStr and WriteVal clearly present the return value:

image

A STRING is

It represents SINGLE text. Therefore the code works EXACTLY AS YOU CODED IT - your CEStrike variable holds a SINGLE TEXT (symbol) representing LAST VALUE of selected analysis period.

If you used debugger
https://www.amibroker.com/guide/h_debugger.html

you would clearly see what the variable holds

image

So you had all tools at your fingertips to find out what is happening.

Now, to the solution - you don't need arrays of strings. I wrote about it in my response in the other thread..

If you wanted to use multiple option symbols on bar by bar basis you have to write a for loop that goes thru bars, something along these lines

Short = 0;
Cover = 0;

for( i = 0; i < BarCount; i++ )
{
    SymbolName = StrFormat("SymbolForBar%g", i ); //... Format symbol here...

    if( Name() == SymbolName )
    {
      // combine array with bar by bar signals
       Short = Short OR ...your desired signal
      // ditto with cover
    }  
}

It is your code and you should finish the writing yourself.

The debugger will be helpfull, but I still don't understand how to get price of a underlying at a partucular time of the day by going bar by bar. Forget about short cover siglan, let us not get distracted by that
If i go bar by bar, i assume amibroker will create an array of prices for each bar, it will also tell me that my programming is very inefficient as i will call foreign from within such a for loop. But even if i do that, how can i get the price at 9:30AM bar? I am not asking for code here, but asking a logic on how to get price price at 9:30AM?
Any one ever has written any code to get price at a certain time of the day, please tell me how to do it.
Tell me atleast the logic how AMIbroker can fetch the data?

You use array = Foreign ( symbol, “c” ) and array indexing operator array[ index ]

Please consult the manual
https://www.AmiBroker.com/guide/h_understandafl.html

Your code doesn't give me any reference to time, neither the link you provided have any reference to time. I went through that link already.
I dont know how amibroker will understand that i am looking for data at 9:30AM.
If you read my problem carefully enough, you would know that I am struggling with time. When we update the database, we not only enter OHLC and V, but we also update two specific fields, one is date and the other is time. But i don't see a way to tell amibroker that , you get me C when Time==myTime and date==myDate.

@santanusengupta, please, refer to the "Date/Time" section for the AFL Function Reference - Categorized list of functions. There are quite a lot of functions to access and manipulate date and time arrays and variables.

When you are using a loop you can access a specific value of the array variables using the bracket indicator [].

From the AFL Reference Manual:

Accessing array elements: [ ] - subscript operator

An array identifier followed by an expression in square brackets ([ ]) is a subscripted representation of an element of an array object.

arrayidentifier [ expression ]

It represents the value of expression-th element of array.

Try the following example:

// use the 
dn = DateNum(); 
tn = TimeNum(); 
dt = DateTime(); 
myTime = 93000;
myDate = 1200715; // as dateNum - Unambiguous
// or
// myDateStr = "15/07/2020"; // if converting from string be sure to use a format as displayed per your locale
// myDate = DateTimeConvert(0, StrToDateTime(myDateStr)); // converts DateTime format to DateNum format
_TRACEF("myTime: %1.0f - myDate: %1.0f - As date: %s", mytime, myDate, DateTimeFormat("%#x", DateTimeConvert(2, myDate, mytime)));
for( i = 0; i < BarCount; i++ )
{
	if (( dn[i] == myDate) AND (tn[i] == myTime ))
    {
		price = C[i]; // get the price at a specific date and time
		_TRACEF("DATE AND TIME -> Close: %1.4f at %s - Bar index: %1.f", price, DateTimeToStr(dt[i]), i);
    }
    else if( tn[i] == myTime )
    {
		price = C[i]; // get the price at a specific time
		_TRACEF("Close: %1.4f at %s - Bar index: %1.f", price, DateTimeToStr(dt[i]), i);
    }
}

If you are unfamiliar with the arrays used, please, review the documentation for DateNum(), TimeNum() and DateTime().

If you apply this formula or perform an exploration (on intraday data) nothing is displayed: it only writes debug messages in the system debug viewer or in the internal Log window (Window-> Log).
I used _TRACEF() to check the values during the loop. The tracing functions are a must tool to debug your code.

P.S. To learn more about loops in Amibroker, please, check this post (to download the PDF you need to register to the linked site/forum).

@beppe Thanks for the post, your code has come very close to what I want but i still have one more problem

myDate = 1200715 <- in this line you are putting a static date. But for me this date has to be the date of the current bar in the loop.
My requirement is, i need to fetch the price at 9:30AM each day and trade accordingly. SO, for each day the time remains same(static) but date should be current bar date. This is what i am unable to figure out. If say, I am doing backtesting with 1 week of data, this date will change 5 times in the process to give price at 9:30AM of each day. If you please guide me how i can replace "myDate" with the date of the current bar, I think i will have a solution here.
Or should I create another parent for loop and make sure that it runs from the first day of back testing to the last day of back testing, incrementing day by day? This will really look very ugly and too many nested loops will make the script very inefficient. Particularly, when in python, all these could be achieved in a simgle line of code. Unfortunately i have not developed any backtesting engine in pythin. I was hopping that Amibroker will make my life easy :slight_smile:

@santanusengupta, in the loop you can access the data of the arrays as needed (the date is available in the datenum() array )

Test this snippet doing an exploration over a date range of a week (with intraday data):

dn = DateNum();
tn = TimeNum();
dt = DateTime();
myTime = 93000;

current_daynum = Null;  // use to detect day changes after "myTime"

// see: https://forum.amibroker.com/t/status-barinrange/16720
bir = Status( "barinrange" );

for( i = 0; i < BarCount; i++ )
{
    // skip the bars outside of exploration/backtest range
    if( bir[i] )
    {
       
        datenum_of_bar = dn[i];   // this is the datenum of the bar processed
        datetime_of_bar = dt[i];  // this is the datetime of the bar processed
        price = C[i];             // get the price at a specific time

        if( IsNull( current_daynum ) AND ( tn[i] == myTime ) )
        {
            current_daynum = datenum_of_bar;
            _TRACEF( "AT \"MY TIME\" - Close: %1.4f at %s - DateNum of bar: %1.0f - Bar index: %1.f", price,  DateTimeToStr( datetime_of_bar ), datenum_of_bar, i );
        }
        else
        {
            if( IsTrue( current_daynum ) ) 
            {
                if( datenum_of_bar == current_daynum )
                    _TRACEF( "SAME DAY %1.f - Close: %1.4f at %s - DateNum of bar: %1.0f - Bar index: %1.f", current_daynum, price,  DateTimeToStr( datetime_of_bar ), datenum_of_bar, i );
                else
                {
                    _TRACEF( "ANOTHER DAY - Close: %1.4f at %s - DateNum of bar: %1.0f - Bar index: %1.f", price,  DateTimeToStr( datetime_of_bar ), datenum_of_bar, i );
                    current_daynum = Null;
                }
            }
        }
    }
}

I'm afraid I don't understand exactly what you mean by:

"I need to fetch the price at 9:30AM each day and trade accordingly. SO, for each day the time remains same(static) but date should be current bar date."
The day for the following intraday bars (up to midnight - if you have after-
hours data) will obviously be the same as the bar of 9:30....

"Should I create another parent for loop and make sure that it runs from the first day of back testing to the last day of back testing, incrementing day by day?
The backtest, when you select a date range, does exactly this: it runs automatically over each bar in the selected interval.

Maybe you try to post some code using the information provided by all the previous posts/contributions to make it more clear.

P.S. Even using nested loops, AmiBroker's backtests are probably much faster than many written in Python!

1 Like

Hello @beppe thanks again for your time, really appreciate it.

The logic of my script is as below, this is an intraday options writing strategy

  1. AT 9:30 am, which is 15 mins after the market opentime (9:15AM), i see the current value of the underlying index and calculate the ATM strike prices of CE and PE.
  2. Once I fix the ATM strikes, i fetch the present values(prices) of those strikes and set a 30% stoplose on the prices.
  3. Then i keep tracking those prices (no trade taken yet only monitoring)
  4. If SL is Hit at one leg, i take trade on the other leg. Means if we hit SL on PE, that is the entry condition for CE and vise versa.

As you can see, it is very first step for me to determine the price of the underlying (Foreign) at 9:30AM. Only then i can calculate what is ATM strike.
I will work on the script you have provided and see how can i use it. I work on my scripts only on weekends as I am a fultime working professional. Not yet a full time trader :slight_smile:
I am using Amibroker for last 1 year and never had issues backtesting indicator based strategies. But here i am really puzzled.

Hello @beppe I have used the code you have given and update my strategy around it and getting good response in debugger window. But when I use backtesting, the execution never enters if( tn[i] == myTime ) . What could be the reason for backtester doing something different than debugger?
I have also used SetBacktestMode(backtestRegular ); , but it didn't change the behiviour

The sample code is here`Code button (use to enter/paste AFL formula)

dn = DateNum();
tn = TimeNum();
dt = DateTime();
myTime = 93000;

current_daynum = Null;  // use to detect day changes after "myTime"

bir = Status( "barinrange" );

for( i = 0; i < BarCount; i++ )
{	
    if( bir[i] ) // skip the bars outside of exploration/backtest range
	{
		datenum_of_bar = dn[i];   // this is the datenum of the bar processed
		datetime_of_bar = dt[i];  // this is the datetime of the bar processed                
			
		if( tn[i] == myTime )  // <--This doesnt work in BackTester but works perfectly in Debugger window
		{
			current_daynum = datenum_of_bar;
			UnderlyingPriceArray= Foreign( Underlying, "Close" );
			UnderlyingPrice= UnderlyingPriceArray[i];
			_TRACEF( "AT \"MY TIME\" - Close: %1.4f at %s - DateNum of bar: %1.0f - Bar index: %1.f", UnderlyingPrice,  
				DateTimeToStr( datetime_of_bar ), datenum_of_bar, i );
			
			//My codes to capture other values at 9:30 each day
		}	
		// My codes to keep tracking values for the rest of the day and take Short/Cover decision
	}
}

@santanusengupta, as a first step be sure for your exploration to set a range from/to that includes your 'mytime' and the periodicity of the exploration is set so one of the bars will be at that specific time.

I suggest to _TRACEF the values of the arrays before the if( tn[i] == myTime ) to see what are the actual timestamps of your bars. In case there is no trace output, do the same (moving the 2 assignements (datenum_of_bar and datetime_of_bar before the if( bir[i] ) line to see why the condition is not met and the rest of code is skipped.

When the problem is solved, if not absolutely needed (I mean in case the 'underlying' instrument name may change at each bar) you should retrieve via Foreign() its "close" array values only once OUTSIDE of the loop.

Hi @beppe I have come very close to what I want but unable to trigger "Short" correctly

This is the portion of the code

if (newDay)
		{
			noSLTrigger=True;
		}
if (tn[i] > myTime)
		{
			CESymbol= iif (StrMatch(CurrentSymbol, CEStrike), True, False);
			PESymbol= iif (StrMatch(CurrentSymbol, PEStrike), True, False);
			if (CESymbol)
			{
				PEStrikeLPArray= Foreign( PEStrike, "Close" );
				PELP= PEStrikeLPArray[i];
				PESL= StaticVarGet(PESLVar);
				PESLHit=IIf(PELP>PESL, True, False);
				
				if (PESLHit AND noSLTrigger)
				{
					Short= TradeTime;
					noSLTrigger= False;
					_TRACE( "Time:   "+DateTimeToStr(dt[i])+" "+CurrentSymbol+"   PE SL Hit:   "+PESLHit+"   PELP:   "+PELP+"   PESL:   "+PESL);
				}
				Cover= squareOffTime OR CESLHit;
			}
			

and the trace result is showing me correct entry Symbol and entry point as per my strategy
TracePic

But the Backtesting is all over the place, not even taking correct Symbol sometimes
BackTestPic

Please help me in getting the backtest in line with trace result.

@Tomasz Please point me to a book which helps me understand how AFL code works. What is the flow of the programming. I think the main problem is, i am unable to understand how it executes lines one by one.
which line will be picked up after which line? Why it is defying all the logic i am putting and jumping .

While Trace is working perfectly, why Short will have to take a different path? And short a wrong symbol?

@santanusengupta when to debug your code using _TRACE() in combination with explorations it is useful to add the top of your formula the following directive:

#pragma maxthreads 1

This will disable multithreading and the exploration code will execute in a single thread producing logs that will be a lot easier to follow.

1 Like