Pyramiding (scaling in/out) and multiple currencies in the portfolio backtester

IMPORTANT: Please first read the Tutorial: Backtesting Your Trading Ideas and Portfolio Backtesting

Starting from version 4.70, the portfolio backtester allows position scaling and supports multiple currencies. Note that these advanced features are supported by the PORTFOLIO backtester only. The old single-security backtester and single-security equity() function do NOT support these features.

Pyramiding / Scaling

Two special constants, `sigScaleIn` and `sigScaleOut`, have been added to provide a means to tell the backtester when you want to scale in or out.

All you have to do to implement pyramiding is to:
- Assign `sigScaleIn` to the BUY/SHORT variable if you want to scale in (increase the size of) a LONG/SHORT position
- Assign `sigScaleOut` to the BUY/SHORT variable if you want to scale out (decrease the size of) a LONG/SHORT position

Scaling size is defined by the `PositionSize` variable, which in the case of scaling defines not the absolute position size, but the dollar increase or decrease.

IMPORTANT: Please note that the backtester treats a trade that you scale in/out as a SINGLE trade (i.e., it will show a single row in the trade list). The only difference versus a plain trade is that it will calculate the average entry price (and average entry FX rate) based on all partial entries and the average exit price (and average exit FX rate) based on all partial exits, and it will show average prices in the entry/exit price field. The commission is, of course, applied correctly to each (partial) entry/exit depending on the partial buy/sell size.

If you want to see details about scaling, you have to run the backtest in "DETAIL LOG" mode, as only then will you see how scaling in/out works and how average prices are calculated.

Note also that scaling in/out and multiple-currency support is available only in the portfolio backtester. The old backtester, as well as the `Equity()` function, do NOT handle scaling in/out nor multiple currencies (they simply ignore scaling commands).

Easy Examples:

Example 1: Dollar-Cost Averaging (each month you buy stocks for a fixed dollar amount)


FixedDollarAmount = 500;
MonthBegin =
Month() != Ref( Month(), -1 );

FirstPurchase =
Cum( MonthBegin ) == 1;

Buy = IIf( FirstPurchase, 1, // True (or 1) represents regular buy signal
      IIf( MonthBegin, sigScaleIn, // each month increase position
           0 ) ); // otherwise no signal

Sell = 0; // we do not sell
PositionSize = FixedDollarAmount;


Example 2: Dollar-Cost Averaging
(simplified formula because AB treats the first `sigScaleIn` as a buy anyway)


FixedDollarAmount = 500;
MonthBegin =
Month() != Ref( Month(), -1 );

FirstPurchase =
Cum( MonthBegin ) == 1;

Buy = IIf( MonthBegin, sigScaleIn, 0 ); // each month increase position

Sell = 0; // we do not sell

PositionSize = FixedDollarAmount;



Example 3: Increasing Position When Profit Generated by a Trade Without Pyramiding
becomes greater than 5% and Decreasing Position When Loss Is Greater Than -5%


// percent equity change threshold when pyramiding is performed
PyramidThreshold =
5;

// regular trading rules (no pyramiding)
Buy = Cross( MACD(), Signal() );
Sell = Cross( Signal(), MACD() );

e =
Equity(1); // generate equity without pyramiding effect

PcntProfit =
100 * ( e - ValueWhen( Buy, e ) )/ValueWhen( Buy, e );

InTrade =
Flip( Buy, Sell );

// ExRem is used here to ensure that scaling in/out occurs
// only once since trade entry
DoScaleIn =
ExRem( InTrade AND PcntProfit > PyramidThreshold, Sell );
DoScaleOut =
ExRem( InTrade AND PcntProfit < -PyramidThreshold, Sell );

// modify rules to handle pyramiding
Buy = Buy + sigScaleIn * DoScaleIn + sigScaleOut * DoScaleOut;

PositionSize = IIf( DoScaleOut, 500, 1000 ); // enter and scale-in size $1000, scale-out size: $500

 

Example 4: Partial Exit (Scaling Out) on Profit Target Stops

Example of code that exits 50% on the first profit target, 50% on the next profit target, and everything at the trailing stop:

Buy = Cross( MA( C, 10 ), MA( C, 50 ) );
Sell = 0;

// the system will exit
// 50% of position if FIRST PROFIT TARGET stop is hit
// 50% of position is SECOND PROFIT TARGET stop is hit
// 100% of position if TRAILING STOP is hit

FirstProfitTarget =
10; // profit
SecondProfitTarget =
20; // in percent
TrailingStop =
10; // also in percent

priceatbuy=
0;
highsincebuy =
0;

exit =
0;

for( i = 0; i < BarCount; i++ )
{
   
if( priceatbuy == 0 AND Buy[ i ] )
    {
       priceatbuy =
BuyPrice[ i ];
    }

   
if( priceatbuy > 0 )
    {
       highsincebuy =
Max( High[ i ], highsincebuy );

      
if( exit == 0 AND
         
High[ i ] >= ( 1 + FirstProfitTarget * 0.01 ) * priceatbuy )
       {
         
// first profit target hit - scale-out
         exit =
1;
         
Buy[ i ] = sigScaleOut;
       }

      
if( exit == 1 AND
         
High[ i ] >= ( 1 + SecondProfitTarget * 0.01 ) * priceatbuy )
       {
         
// second profit target hit - exit
         exit =
2;
         
SellPrice[ i ] = Max( Open[ i ], ( 1 + SecondProfitTarget * 0.01 ) * priceatbuy );
       }

      
if( Low[ i ] <= ( 1 - TrailingStop * 0.01 ) * highsincebuy )
       {
         
// trailing stop hit - exit
         exit =
3;   
         
SellPrice[ i ] = Min( Open[ i ], ( 1 - TrailingStop * 0.01 ) * highsincebuy );
       }

      
if( exit >= 2 )
       {
         
Buy[ i ] = 0;
         
Sell[ i ] = exit + 1; // mark appropriate exit code
         exit =
0;
         priceatbuy =
0; // reset price
         highsincebuy =
0;
       }
    }
}

SetPositionSize( 50, spsPercentOfEquity );
SetPositionSize( 50, spsPercentOfPosition * ( Buy == sigScaleOut ) ); // scale out 50% of position

Multiple Currency Support


The portfolio backtester allows backtesting systems on securities denominated in different currencies. It includes the ability to use historical (variable) currency rates. Currency rates are definable in the "Currencies" page in the preferences. The currency in which a given symbol is denominated can be entered in the Symbol->Information page.

The "Currencies" page in Preferences allows defining base currency and exchange rates (fixed or dynamic) for different currencies. This allows getting correct backtest results when testing securities denominated in a different currency than your base portfolio currency.

How does AB know whether I want the fixed or dynamic quote?

There are the following requirements to use currency adjustments:
a) The Symbol->Information "Currency" field shows a currency different than the BASE currency
b) The appropriate currency (defined in Symbol) has a matching entry in the Preferences->Currencies page
c) The dynamic rate "FX SYMBOL" defined in the preferences EXISTS in your database and HAS QUOTES for each day within the analysis range.

What is the "INVERSE" check box for in the preferences?

Let's take EURUSD as an example.

When "USD" is your BASE currency, then the EUR exchange rate would be a "straight" EURUSD FX (i.e., 1.3).
But when "EUR" is your BASE currency, then the USD exchange rate would be the INVERSE of EURUSD (i.e., 1/1.3).
The opposite would be true with FX rates like USDJPY (which are already "inverse").