Simple Mean-Reversion strategy - how correct is my AFL?

Hello guys!
Nice to join your community.

I will be very thankful if you can help me and advise if the following AFL codes are correct. I am trying a very simple strategy just to test my skills in AFL. The strategy is very simple and it is price based. Rules as follows:

If the price drops a certain percentage BUY.
Close at the end of the day.

I made two versions of this AFL.
One of the codes uses the DAILY timeframe.
The second code uses a 5m timeframe (and actually can be modified to fit any timeframe easily).
The final results from the two AFL codes show some difference but this is expected.

AFL_1:

SetPositionSize(5, spsPercentOfEquity );
SetOption("MaxOpenPositions", 20);
SetOption("AllowSameBarExit", True);
SetTradeDelays(0,0,0,0);

Parameter = Optimize("Parameter", 0.95, 0.9, 0.93, 0.01);

BuyPrice = Ref(C,-1)*Parameter; 
SellPrice = Close;

Buy = IIf((L <=  Ref(C,-1)*Parameter), 1, 0);
Sell = C > 0;

Filter = 1;

AddColumn(Close, "Close");
AddColumn(Ref(C,-1)*Parameter, "Ref(C,-1)*Parameter)");
AddColumn(L, "LOW");

AFL_2:

SetPositionSize(100, spsPercentOfEquity );
SetOption("MaxOpenPositions", 5);
SetTradeDelays(1,0,0,0);
Parameter = Optimize("Parameter", 0.95, 0.9, 0.98, 0.01);

tn = TimeNum();
PriceAtTime = 0;
Counter = 0;
Start = 0;

for (i = 1; i < BarCount; i++)
{

	if(tn[i] == 0 AND Counter < 2016)  // Price at 00:00 h -> 5m chart
	{
	PriceAtTime[i] = Open[i];          // assigning the price to variable
	Counter = 0;					   
	Start = 1;						   // this is used to signal that the counter can start counting
	} 	
	
	if(Counter > 0)
	{
	PriceAtTime[i] = PriceAtTime[i-Counter]; 
	}
	
	if(Start == 1)
	{
	Counter++;
	}
	
	if( Counter >= 2016)
	{
	Counter = 0;
	}
}

Buy = C <= PriceAtTime*Parameter;
Sell = tn == 000000;

BuyPrice = O;
SellPrice = C;

Filter = 1;

AddColumn(PriceAtTime, "PriceAtTime");
AddColumn(Close, "Close");
AddColumn(tn, "tn");

Thank you for your attention :slight_smile:

Can not help you much but what I do see is that your default optimization value in the first script is outside of the min max range. Not a problem, just "unusual".

Second script, you are trading 100% setpositionsize so if 5 positions are taken, you will have 5 times leverage.

I can not imagine wanting to trade at 00:00 but that might depend on your timezone and time settings I guess... I am just guessing here, I have honestly no idea how AB handles all that...

Thank you Henri! Thanks a lot for your feedback!

Yes, my value is out of range in the first AFL code (it is probably some mistake due to manipulation with this data). About the position size, I do agree is again another mistake. It should be spsPercentOfEquity = 20% and MaxOpenPositions = 5. I am sorry for those mistakes...
Also, yes, I want to trade at 00:00 :slight_smile:

Indeed those mistakes will cause a significant difference in the final results but what is important for me is to understand if someone seeing any logic errors in my AFL.

Depending on how you plan to execute live trades and how big your trading universe is, your backtest may be producing better performance than you can achieve in live trading. For example, if you have 1000 instruments in your universe, can you place limit orders for all 1000 of them at once? If not, how do you intend to enter the first 5 that breach the limit price? What if 10 of the stocks breach the limit price at the open? You have not used PositionScore to rank your entries, so are you intending to randomly select which 5 trades to enter?

1 Like

Hi mradtke! Thanks a lot for your time answering my questions!
It really helps me understand if my code has correct logic!

The potential trading universe is highly correlated so getting into positions in real trading will be OK in random order or first signals first serve until reaching some MaxOpenPositions (let's say 5).
Also, I will not use limit but market orders so the first signals will be executed first. However, here it comes a problem that I didn't take into account in the code... When I am using market orders there will be a slippage. Can I add some coefficient for slippage as follows or it is a stupid idea:

BuyPrice = Ref(C,-1)*Parameter*SlipCoefficient  // this will negatively affect the final results and will punish every trade with a certain amount

BTW, how does the backtester choose which trades to open when there is no PositionScore and when we use a daily timeframe so we have multiple signals at the same time? Is this problem described here? I just can't find it explained. If it is explained there sorry for the question.

Thank you again for your time :slight_smile:

Default ranking of entry signals is described here: What is the default ranking without positionscore - #2 by fxshrat

Regarding slippage, you can either change your entry/exit prices or you can just set a commission amount that is higher than the actual commissions you will pay, which has the effect of penalizing all trades. However, if you're waiting for the limit price to be breached and then placing a market order, it's likely that half the time you will enter at a worse price and half the time at a better price, so in live trading the net slippage may be close to 0.

1 Like

Thank you for the explanation!
It was very helpful for me and save me time and possible misunderstanding of how AFL works.
I wish you a nice and lovely evening :slight_smile:

if you plan to take the strategy live, I suggest to backtest using the 5 minute timeframe chart. When trying to backtest your idea on a daily timeframe, you may set postionscore to random() to completely randomize the picking of 5 stocks that hit your criteria for a given day. In reality, what you will get when trading live is the first five stocks of the day that hit your criteria. From personal experience this can be quite different results.
How I took such a strategy live is by constantly rebalancing the portfolio throughout the day (sell some positions from each stock to accomodate for the new stock that hit the criteria). You may want to to backtest on those lines if you find results are not good enough from the initial idea (constant position size)

1 Like

Hello there! Nice to hear your feedback!
Could you please advise why you recommend using a 5-minute timeframe as more relevant compared to a daily timeframe?

About PositionScore set at random() - your idea is really-really nice because I thought about how to solve this dilemma after understanding (Thanks to Matt :slight_smile: ) that the backtester would choose stocks in alphabetical order (when PositionSize is fixed).

In reality, what you will get when trading live is the first five stocks of the day that hit your criteria - you are right here the reality is not random but consequential so my results would not be very representative of reality (but random seems better than alphabetical). However, I see it as a possible solution to decrease the trading stock universe and increase MaximumOpenPositions... Anyway, I will take into consideration also your suggestion for portfolio rebalancing.

Again thanks for your help!

The reason is exactly what I mentioned. This way you will nullify the impact of random selections. if you can test it on tick data , even better (5 minute also will have the constraint that more than 1 instrument hits your criteria in a 5 minute interval). Your backtest and live trading will be more or less similar if you consider the transaction costs as well.

To make it clearer, imagine a really bad day for the market and lets say your universe is nasdaq 100. your strategy in real life will take 5 positions in the first few minutes and market further tanks and lets say 50 of the 100 stocks hit the criteria on that day. it is very likely you got into the worst stocks of the day (assuming overall market took the already holding stocks further down).

Why rebalancing is preferred is because this way you can fully utilize the fund. Imagine days when market is strong and only one stock hit your criteria. you will allocate only 20% of the portfolio for the day. As opposed to that, you will be 100% allocated on bad days. intuitively this should affect the strategy negatively. (less allocation on good days, full allocation on bad days)

you chose a simple afl to test your skills, but trading it live is a bit more complicated :slight_smile:

1 Like

Super! I really appreciate your feedback!
I understand now your point why using shorter timeframes is better.

You are also indeed correct that I have no experience in live trading but only backtesting. :slight_smile:
That's why I am so thankful to all of you for sharing your experience so I can at least avoid some of the possible mistakes which are invisible to me!

1 Like

I don't have a ton of time to look at this, but my gut is afl2 can be done without a for loop. Always strive to use AFL functions (speed), loops are last resort.

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