Sample code required for Options Backtesting

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

@bappe I am not debugging anymore. Debugging and Trace result is perfect. The logic is working flowless now.
Back testing is the issue (Not using Exploration at all).
I understand that "Short" command behaves differently an there is not much help available to tell me its behavior. It picks symbol randomly.
If you see my code. Trace and Short are under the same condition (If) But trace shows me correct symbol while Short shorts other symbol. However i will try single thread, but i am not sure how that will help me in my coding. I need to know why Trace is different than Short while executing code.
They atleast should work with the same symbol right?
Why it doest follow any logic and work absolutely randomly? I need a simple reference on how "Short" picks a symbol, which is not in line with trace. Short should always pick the current symbol "name()". Why it is not doing so in my code?

Single thread or multi thread, Backtest result doesn't change.

What is the value of TradeTime ? I'm asking since you are assigning it to Short array.

Usually signals are assigned using logical conditions that result in 0 (false i.e no signal) or 1 (true).

it it

currentTime= TimeNum()/100;
TradeTime= currentTime>=924 AND currentTime<=1400;

@santanusengupta, without seeing all the code (and having the same data to work with), unfortunately it is a guessing game...

Anyway...

Short= TradeTime;

With this code you are setting a LOT of short signals since you are assigning 1 to all the values where currentTime(i.e. TimeNum()) is in the interval (approx. 4.5 hours - at 15m is 18 Short signals).

Maybe your intention was:

Short[i] = TradeTime[i]; 

but as said... just a guess!

@beppe I am ok with multiple short signals, I can any time run a dedupe like ExRem to fix that. But my question is, why will it short a different strike price than what is captured by _Trace?
Why it doesn't short CurrentSymbol = Name();

Anyway, you have already helped a lot with capturing Value at a particular time. I don't think you can fix the behavour of AMIBROKER. There is absolutely no error in the code, the trace result is the proof of this. As there is no manual or Book available which can teach me how to use AFL code and How "Short" and "Cover" behaves, I think I will be left to do Trial and Error on my own to develope the first code ever in AFL for Options trading.
This thread can be closed. I dont think I will come back here hoping for a fix.