How to sell all titles after a defined period?

Hello everybody

I make pretty good process in transferring my trading models to AmiBroker and learn a lot about mistakes I made in the past because I wasn't able to backtest then.

Now I have a question for which I can't find any answer in the manual nor in the community forum. Could you help me, please?

First, I created the following rotating model (My Formula 17):

SetBacktestMode( backtestRotational );
SetOption("MaxOpenPositions",10);
// SetOption("WorstRankHeld",10);
SetPositionSize( 10, spsPercentOfEquity );

// Rebalancing, in trading days ;
SetOption("HoldMinBars", 127);

Rise = C > MA (Close, 200);
TotalRise = sum (Rise, 1280);
GDSum = (TotalRise*100/1280);
// Filter = GDSum > 78 ; // This filter seems not to work
PositionScore = ROC(CLOSE,1280);

Running through the S&P 500 from 1.1.2015 to 20.12.2020 to have two difficult periods in the test gives an annual profit of 21,7 %.

I tried to further refine the title list with a function only trading the equities with Close > MA200 during at least 78 % of the time in the past five years. But the "filter" function seems not to work in rotational backtesting.

To force the system to work with daily data but to rotate only twice a year I inserted "HoldMinBars", 127. This is not perfect, but it works and it shows the direction what this model could achieve.

Question: Is there a way to work with daily data in rotational backtest and to define an option when to rotate? Monthly, quarterly, halfyear etc as in the model further below?

In the next step I made a similar model but in "classical" AFL Code (my formula 21). Instead of ROC I use RSL.

// Is trading this month allowed?
// Switch to S&P symbol to calculate broad-market timing
SetForeign( "A0AET0" );
// now we can calculate, based on S&P 500
MarketIsUp = C > MA( C, 200 ); // here C represents closing price of S&P 500
//
// now go back to origiginal data (current symbol)
RestorePriceArrays();
//
NumPos = 10;
SetOption( "MaxOpenPositions", NumPos );
PositionSize = -100 / NumPos;
// Close during at least 78 % of time > MA 200?
Rise = C > MA (Close, 200);
TotalRise = sum (Rise, 1280);
GDSum = (TotalRise*100/1280);
// Filter = GDSum >= 78; // This filter function seems not to work.
// Ranking with highest RSL during past 5 years
PositionScore = Close / MA (Close, 1280);
mth = Month();
dow = DayOfWeek();
mth_mod3 = mth % 3;
hy = mth % 6;
NewWeek = dow < Ref(dow,-1);
NewMonth = mth != Ref(mth,-1);
NewQuarter = mth_mod3 * !Ref(mth_mod3,-1);
NewHalfyear = hy * !Ref(hy,-1);
NewYear = Year() != Ref(Year(), -1);
// PeriodStart = NewWeek;
// PeriodStart = NewMonth;
// PeriodStart = NewQuarter;
PeriodStart = NewHalfyear;
// PeriodStart = NewYear;
TradingDay = BarsSince(PeriodStart) == 1; // Second day in period

Bcond7 = TradingDay;
Bcond8 = GDSum >= 78;
// Buy = Bcond7 AND Bcond8;
Buy = Bcond7 ;

Scond3 = TradingDay;
Scond4 = C < MA(C,200);
Sell = Scond3 AND Scond4 ;
// Sell = TradingDay ; 

The results of this model are encouraging too (16,5 % yearly, same time period as above), but I would like to further refine them.

Here I have the following question: Is there an elegant solution to sell all shares every trading day (in the model above every half year) and to build up the portfolio new with the most promising shares as defined in the "Buy" rules?
When I use the expression Sell = Close < 0 as a workaround, the system sells the shares the same day when bought.

I would appreciate to get some input what solutions might exist to solve these challenges.

Filter variable is for Exploration only, not for backtest. It has been mentioned many times on this forum.

https://forum.amibroker.com/search?q=filter%20backtest

As to your question:

Is there a way to work with daily data in rotational backtest and to define an option when to rotate?

In rotational trading you stop rotation the way described in the manual. It is important to every sentence of manual describing given function, as every sentence is important.

From manual: http://www.amibroker.com/f?enablerotationaltrading

The score has the following meaning:
[...points 1 and 2 skipped ....]
3. the score equal to scoreNoRotate constant means that already open trades should be kept and no new trades entered
4. the score equal to scoreExitAll constant causes rotational mode backtester to exit all positions regardless of HoldMinBars. Note that this is global flag and it is enough to set it for just any single symbol to exit all currently open positions, no matter on which symbol you use scoreExitAll (it may be even on symbol that is not currently held). By setting PositionScore to scoreExitAll you exit all positions immediatelly regardless of HoldMinBars setting

Therefore it is pretty easy to rotate say once a month():

NewMonth = Month() != Ref( Month(), -1 );
PositionScore = IIF( NewMonth, ....your score here...., scoreNoRotate );

Your second system is NOT rotational one. Rotational is the one that uses EnableRotationalTrading. Rotational system does not use buy and sell variables. What you have is plain system with position score (ranking).

In such system it is very easy to add broad market timing and it is explained in the Knowledge Base:
http://www.amibroker.com/kb/2014/09/20/broad-market-timing-in-system-formulas/

Generally it is good to read all Knowledge Base articles as they contain heaps of knowledge

http://www.amibroker.com/kb/toc/

3 Likes

Thank you very much for these extensive explanations! The rotational model works perfect already.

@RetiTrader, it is nice to know that @Tomasz provided you the suggestions to correct your rotational model

Anyway, to experiment a little, in the "classical" model, I tried setting the "Sell" lines to:

Scond = C < MA(C,200);
Sell = Scond OR (Ref(TradingDay, 1) == 1); // REFERENCE TO FUTURE DATE (not using quotes)

As it is evident by the comment, this code has a reference in the FUTURE.
We should be careful to avoid this in our systems since you'll get unrealistic results "peeking" at future prices.

But, let's assume that I decide to manually trade this long-term system: in such a case, I'm indeed able to calculate the exiting dates - every 6 months - independently of the application. (Let's also ignore that some of the trades that I will close manually may need to be reopened the next day, wasting some commissions money and the overnight price gap).

If the above holds, I think that the future leak does not matter for this specific backtest since I will close all the open trades unconditionally without using any future quotation.
(Some other trades will already be closed earlier when the price goes below the 200 MA line).

I would like to hear from other experienced AmiBroker users if the above assumptions are wrong. In such a scenario, what is the best way to fix the "classical" model formula?

1 Like

It seems that my question was indirectly answered [here] by @Tomasz(First Monday every month, was: Not Understanding Flip()).

Quoting his example and comment:

FirstMondayEveryMonth = ExRem( DayOfWeek() == 1, Month() != Ref( Month(), 1 ) );

Note: that the code references one bar ahead but only to detect last day of the month so it is harmless

Good morning @beppe - Thank you for the two code elements you provided which are very helpful. And thanks for the link where @Tomasz explaines how ExRem works.

Playing around with the code, I used your code

Scond = C < MA(C,200);
Sell = Scond OR (Ref(TradingDay, 1) == 1); // REFERENCE TO FUTURE DATE (not using quotes)

When I test in the "Sell" line with "OR" or "AND", the result differs quite much. To my surprise delivers the rotational model the highest yield. I always thought it is necessary to use a kind of stop-loss mechanism to prevent losses but the backtesting leads to the conclusion that buy and hold with halfyearly checks and rebalancing seems to be more successful.

1 Like

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