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