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

IMPORTANT: Please read first Tutorial: Backtesting your trading ideas article and Portfolio Backtesting

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

Pyramiding / Scaling

Two special constants: sigScaleIn / sigScaleOut added to provide means to tell the backtester when you want to scale-in/out

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

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

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

If you want to see details about scaling you have to run backtest in "DETAIL LOG" mode as only then you will 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 portfolio backtester. Old backtester as well as 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 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 first sigScaleIn as 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 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 first profit target, 50% on next profit target and everything at 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

Mulitple Currency Support


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

"Currencies" page in Preferences - allows to define base currency and exchange rates (fixed or dynamic)
for different currencies. This allows to get correct backtest results when testing securities denominated in different currency than your base portfolio currency.

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

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

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

Let's for example take EURUSD.

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