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.
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
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").