Porfolio Backtester Interface Reference Guide
(Updated February 20th, 2010 to cover enhancements and additions introduced
in AmiBroker 5.30.0)
Basics
AmiBroker version 4.67.0 exposes new object-oriented interface to porfolio
backtester allowing to control 2nd phase of the backtest. This allows multitude
of applications including, but not limited to:
- position sizing based on portfolio-level equity
- implementing advanced
rotational systems (you have now access to ranking arrays and can
decide what trades to take after knowing which symbols
scores best on bar-by-bar basis)
- adding your custom metrics to backtest
and optimization statistics
- implementing custom formulas for slippage
control
- advanced scaling-in/-out based on portfolio equity and other
run-time stats
- advanded trading systems that use portfolio-level statistics
evaluated on bar-by-bar basis to decide which trades
to take
This document describes all objects, methods and properties exposed by portfolio
interface.
Requirements
To use new interface the user needs AmiBroker 4.67.0 or higher
and needs to have AFL coding skills including understanding the terms: an
object, method and property.
Various approaches for various applications
The porfolio backtester interface supports various approaches to customization
of backtest process that suit different applications.
- high-level approach (the easiest)
- using Backtest() method and it runs
default backtest
procedure (as in old versions) - allows simple implementation of custom metrics
- mid-level approach
-
using PreProcess()/ProcessTradeSignal()/PostProcess()
methods - allows to modify signals, query open positions (good for advanced
position sizing)
- low-level approach (the most complex)
- using PreProcess()/EnterTrade()/ExitTrade()/ScaleTrade()/UpdateStats()/HandleStops()/PostProcess()
methods - provides full control over entire backtest process for hard-code
programmers only
Getting access to the interface
To access new portfolio backtester interface you need to:
- enable custom backtesting procedure by calling:
SetOption("UseCustomBacktestProc", True );
or calling
SetCustomBacktestProc( "C:\\MyPath\\MyCustomBacktest.afl" );
in your formula
or by enabling it in Automatic Analysis->Settings window, "Portfolio" tab
and specifying external custom procedure file.
- get access to backtester object by calling GetBacktesterObject() method.
Note that GetBacktester method should only be called when Status("action")
returns actionPortfolio:
if( Status("action")==
actionPortfolio )
{
// retrieve the interface
to portfolio backtester
bo = GetBacktesterObject();
...here is your custom backtest formula.
}
When using external custom procedure file you don't need to check for actionPortfolio,
because external backtest procedures are called exclusively in actionPortfolio
mode.
Typing Conventions
- bool - italic represents the type of parameter/variable/return
value (Trade, Signal, Stats - represent the type of object returned)
- AddSymbols - bold represents function / method / property
name
- SymbolList - underline type face represents formal parameter
- [optional] - denotes optional parameter (that does not need to
be supplied)
- variant - represent variable type that can be either string
or a number
Despite the fact that interface handles integer data type such as long,
short, bool and two different floating point types: float and double, the
AFL itself converts all those data types to float because AFL treats all numbers
as floats (32-bit IEEE floating point numbers).
Objects
The interface exposes the following objects:
- Backtester object
- Signal object
- Trade object
- Stats object
The only object directly accessible from AFL is Backtester object, all other
objects are accessible by calling Backtester object methods as shown in the
picture below.
Backtester object
Backtester object allows to control backtest process (process signals, enter/exit/scale
trades) and get access to signal list, open position and trade list and to
performance statistics
object.
Methods:
- bool AddCustomMetric( string Title,
variant Value, [optional] variant LongOnlyValue,
[optional] variant ShortOnlyValue , [optional] variant DecPlaces =
2, [optional] variant CombineMethod = 2 )
This method adds custom metric to the backtest report, backtest "summary"
and optimization result list. Title is a name of the metric to be displayed
in the report, Value is the value of the metric, optional arguments LongOnlyValue,
ShortOnlyValue allow to provide values for additional long/short-only
columns in the backtest report. DecPlaces argument controls how many
decimal places should be used to display the value. The last CombineMethod
argument defines how custom metrics are combined for walk-forward out-of-sample
summary report
Supported CombineMethod values are:
1 first step value, - summary report will show the value of custom metric from
very first out-of-sample step
2 last step value (default), - summary report will show the value of custom metric
from the last out-of-sample step
3 sum, - summary report will show the sum of the values of custom metric from
all out of sample steps
4 average, - summary report will show the average of the values of custom metric
from all out of sample steps
5 minimum, - summary report will show the smallest value of custom metric from
all out of sample steps
6 maximum.- summary report will show the largest value of custom metric from
all out of sample steps
Note that certain metrics calculation methods are complex and for example
averaging them would not lead to mathematically correct representation of all
out of sample test.
Summaries of all built-in metrics are mathematically correct out-of-the-box
(i.e. they are *not* averages, but properly calculated metrics using method
that is appropriate for given value). This contrasts with custom metrics, because
they are user-definable and it is up to the user to select 'combining' method,
and still it may happen that none of the available methods is appropriate.
For that reason the report includes the note that explains what user-definable
method was used to combine custom metrics.
- bool Backtest( [optional] bool NoTradeList )
This high-level method performs default portfolio backtest procedure in single
call. It should be used if you just need to obtain custom metrics and do
not want
to change
the way
backtest is performed. (Version 4.68.0 and above): If optional parameter NoTradeList is
set to True, then trade list is not generated automatically. This is useful
if
you
want to add some per-trade metrics. Once you add them, you can generate trade
list with your metrics using ListTrades method.
- long EnterTrade( long Bar, string Symbol, bool bLong, float Price, float PosSize, [optional]
variant PosScore, [optional]
variant RoundLotSize, [optional] variant MarginDeposit,
[optional] variant TickSize, [optional] variant PointValue )
Low-level method that enters trade on any symbol. This allows to take trades
even on symbols that have no corresponding signals. If values for optional
parameters are not provided then AmiBroker uses values defined in Symbol->Information
window
- long ExitTrade( long Bar, string Symbol, float Price, [optional]
variant ExitType )
Low-level method that exits trade on any symbol. This method searches open
trade list and if there is no open trade on given symbol it does nothing. Optional
ExitType parameter specifies the reason for exit (1 - regular exit, 2 - max.
loss, 3 - profit, 4 - trail, 5 - N-bar, 6 - ruin)
- Trade FindOpenPos( string Symbol )
This method looks for the Symbol in open position list and returns
matching trade object if it finds one or returns null object otherwise.
- Signal FindSignal( long Bar, string Symbol,
long Type ) - new in v5.10
where bar is a bar number,
type represents type of signal to find: 0 - both entries and exits, 1 - only
entries, 2 - only exits
The method finds for first matching signal that has fPrice != -1 (different
than -1). If 0 is used as type, and entry and exit is on the same bar then
entry signal will be returned. Note: fPrice = -1 is a special marker meaning
that given signal should be ignored.
-
long GetSignalQty( long Bar,long Type ) -
new in v5.30
where bar is a bar number, symbol is ticker symbol, type represents type of signal
to find: 0 - both entries and exits, 1 - only entries, 2 - only exits
The method retrieves the number of signals occuring on given bar. Note
that AmiBroker to conserve memory keeps track only of 2 * MaxNumberOfPositions
entry signals on any single bar, however it keeps track of ALL exit signals
(because they consume much less space and at the time of signal collection
it is not known which positions are already open, so all exits must be
tracked to prevent missing an exit)
- Trade GetFirstOpenPos()
This method returns first Trade object from open position list
- Signal GetFirstSignal( long Bar )
This method returns first trading Signal object for given Bar
- Trade GetFirstTrade()
This method returns first Trade object from closed trade
list
- Trade GetNextOpenPos()
This method returns next Trade object from open
positions list. You should call GetFirstOpenPos before calling this method
for the first time. Returns null object when no more open positions are found.
- Signal GetNextSignal( long Bar )
This method returns next Signal object from closed signal list of
given Bar. You should call GetFirstSignal before calling this method
for the first time.
Returns null object
when no more signals are found.
- Trade GetNextTrade()
This method returns next Trade object from closed trade
list. You should call GetFirstTrade before calling this method
for the first time. Returns
null object when no more trades are found.
- long GetOpenPosQty()
This method returns number of currently open positions
- Stats GetPerformanceStats( long Type )
Calculates built-in statistics and metrics and returns Stats object.
Type parameter specifies what trades should be counted in. Type
= 0 means all trades, Type = 1 means long-only, Type = 2 means short-only.
- HandleStops( long Bar )
This low-level method handles automatic stops (applystops). This
method MUST NOT be used in high-level and mid-level approaches. In low-level mode you
should call this method once for each bar inside trading loop.
- ListTrades()
(Version 4.68.0 and above) This outputs trades to the result list of Automatic
Analysis window. Usually this function does NOT need to be called because Backtest()
method by default lists trades already. This function should only be used
when you disabled trade listing in Backtest method to add some custom per-trade
metrics.
- PostProcess()
This mid-level and low-level method performs final processing required to
complete backtest correctly. Among other things it frees price cache, closes
out any open trades and outputs trade list to the Automatic Analysis window.
It should
NOT be
used when you call Backtest() method because Backtest() already performs
necessary processing.
- PreProcess()
This mid-level and low-level method performs initial processing required
to perform backtest correctly. Among other things it initializes price cache
and sets up initial variables. It should NOT be
used when you call Backtest() method because Backtest() already performs
necessary processing.
- bool ProcessTradeSignals( long Bar )
This mid-level method processes all trading signals for given bar. It should
be called once per every bar in your custom backtesting loop.
- RawTextOutput( string Text )
(Version 4.68.0 and above) This method outputs any string to the Automatic Analysis result list at
the time of the call. The user can output text formatted in multiple columns
using
\t
(tab)
character.
- long ScaleTrade(long Bar, string Symbol, bool bIncrease,
float Price, float PosSize, [optional] variant Deposit)
Low-level method that scales trade on any symbol. This method searches open
trade list and if there is no open trade on given symbol it does nothing. Optional
Deposit parameter specifies margin deposit for futures, if not given
then Price parameter
is used.
- UpdateStats( long Bar, long TimeInsideBar )
Low-level method that updates equity, exposure, trade excursions (for MAE/MFE
calculations) and other internal variables required for correct calculation
of statistics. You must NOT use
this function in high-level and mid-level approaches. TimeInsideBar parameter
specifies intraday time position. TimeInsideBar = 0 means opening of the
bar, TimeInsideBar = 1 means middle
of the bar, TimeInsideBar = 2 means end of bar. As certain internal calculations
depend on end-of-bar calculations, this method must be called once and only
once with TimeInsideBar parameter
set to 2 at the end of processing of every bar inside in your custom backtesting
loop. May be called zero or more times for every bar inside backtesting loop
with TimeInsideBar parameter set to 0 (zero) or 1.
- GetMonteCarloSim()
get the instance of MonteCarloSim object to access MC distributions
Properties:
- double Cash
available funds (cash) in your portfolio
- double Equity
current portfolio-level Equity (read-only property)
- array EquityArray
portfolio-level Equity array (read-only property)
Note: Since version 5.50 backtester object now has EquityArray property that
returns entire equity array (not just current value).
Please note that values are filled during backtest and only after backtest is
complete, all values are valid. If you call it in the middle, it will contain
only "upto given point" data. Avoid abusing this function and it is
costly in terms of RAM/CPU.e).
You may use
bo.EquityArray instead of Foreign("~~~Equity", "C" )
in custom backtester code.
- double InitialEquity
funds that are available at the beginning of the backtest
- double MarginLoan
loan amount (only if you are using margin account)
(read-only property)
Signal object
Signal object represents trading signal (buy/sell/short/cover) or ranking
array element generated by AmiBroker during first phase of backtest when your
formula is executed on every symbol under test. During this first phase scan
AmiBroker collects data from buy/sell/short/cover signal, price, position size
and score arrays, performs sorting of signals and put top-ranked entry signals
and all scale and exit signals into the list. Separate list of trading
signals is maintaned for every bar. Signal list is sorted so first entry signals
appear
(top ranked first) and after that scaling and exit signals follow. To conserve
memory AmiBroker stores only (2*MaxOpenPositons) top-ranked entry signals per
bar. It keeps however all exit and scaling signals. Once first phase is completed
and backtester enters 2nd phase (real backtest) it
iterates
through
bars and
through
all signals
within given bar and executes trades based on this signals.
To iterate through signal list you should use GetFirstSignal() / GetNextSignal()
methods of Backtester object, as shown below:
// retrieve the interface to portfolio backtester
bo = GetBacktesterObject();
for( i = 0;
i < BarCount; i++ )
{
for( sig = bo.GetFirstSignal( i ); sig; sig
= bo.GetNextSignal( i ) )
{
if( sig.IsEntry() )
{
// handle entry signal
....
}
}
bo.ProcessTradeSignals( i );
}
Methods:
- bool IsEntry()
True if this is entry signal, False otherwise
- bool IsExit()
True if this is exit signal, False otherwise
- bool IsLong()
True if this is long entry (buy) or long exit (sell) or scale-in signal,
False otherwise
- bool IsScale()
True if this is scale-in or scale-out signal, False otherwise
Properties:
- float MarginDeposit
margin deposit (for futures)
- float PointValue
point value (for futures, currencies)
- float PosScore
position score
- float PosSize
requested position size (positive numbers mean dollar value, negative values
mean percent of portfolio equity)
- float Price
entry/exit/scale price
- short int Reason
this specifies reason of exit ( 1 - regular exit, 2 - max.
loss, 3 - profit, 4 - trail, 5 - N-bar, 6 - ruin )
- float RoundLotSize
round lot size
- string Symbol
symbol of security
- float TickSize
tick size (minimum price change)
- short int Type
this specifies signal type ( 0 - rank (rotational systems only), 1
- buy, 2 - sell, 3 - short, 4 - cover, 5 - scale-in, 6 - scale-out )
Trade object
Trade object represents either currently open position (open trade) or closed
trade. AmiBroker maintains 2 lists of trades: open position list (accessible
using GetFirstOpenPos/GetNextOpenPos methods of backtester object) and closed
trade lists (accessible using GetFirstTrade/GetNextTrade methods of the backtester
objects). Once open position is closed by the backtester it is automatically
moved from open position list to trade list. When backtest is completed (after
PostProcess call) AmiBroker closes out all open positions, so trade list includes
all trades. You can access both lists any time during backtest, you can also
access trade list after completion to generate trade-related stats.
To iterate through open position list you should use GetFirstOpenPos()
/ GetNextOpenPos()
methods of Backtester object, as shown below:
//
'bo' variable holds Backtester object retrieved earlier
for(
openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() )
{
//
openpos variable now holds Trade object
}
To iterate through closed trade list you should use GetFirstTrade()
/ GetNextTrade()
methods of Backtester object, as shown below:
for(
trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade()
)
{
//
trade variable now holds Trade object
}
Methods:
- long AddCustomMetric( string Title,
variant Value, [optional] decplaces = 2)
(Version 4.68.0 BETA and above) This method adds PER-TRADE custom
metric to the trade list only. Title is a name of the metric to be displayed
in
the
report,
Value
is the
value
of the metric. When using this function you have to ensure that
you the same metrics in the same order to every trade. Otherwise output
may be messed up. Note that in contrast to Backtester.AddCustomMetric method
that is usually called after PostProcess, the Trade.AddCustomMetric should
be called before PostProcess call because PostProcess lists
trades. Using
Trade.AddCustomMetric after PostProcess gives
no result, because trades are already listed.
Also if you are using Backtester.Backtest() method
you should call it with NoTradeList parameter set to True, add your per-trade
metrics and then call ListTrades
to allow your custom metrics to be included in the output.
- float GetCommission( [optional] bool InclExit )
retrieves commission paid for that trade (includes all scale in/out commissions).
Depending on InclExit parameter the function returns commission including (True,
default) or exluding (False) exit commission.
- double GetEntryValue()
retrieves dollar entry value of the trade
- float GetMAE()
retrieves trade's Maximum Adverse Excursion in percent
- float GetMFE()
retrieves trade's Maximum Favorable Excursion in percent
- double GetPercentProfit()
retrieves current percent profit of the trade
- double GetPositionValue( )
retrieves current dollar value of the position.
- float GetPrice( long Bar, string Field )
(Version 4.68.0 BETA and above) provides quick access to price arrays of
open positions. Bar parameter
represents the data bar to query price for, Field
parameter specifies which price field you want to get, allowable values are:
"O" (Open)
"H" (High)
"L" (Low)
"C" (Close)
"F" (Fx currency rate)
NOTES:
1. GetPrice method is available for OPEN POSITIONS only, when called on
closed trade returns Null value
2. Open Interest field is NOT available via GetPrice
3. Bar must be between
0..BarCount-1, otherwise exception will occur
- double GetProfit()
retrieves current dollar (point) profit of the trade
Properties:
- long BarsInTrade
bars spent in trade (counting starts from 0)
Note however that the value of zero is available only when trade
is just opened in "low-level" approach, so normally you would see numbers >=
1 (all other reporting in AB remains as it was, so enter today and exit tommorrow
counts as 2-bar trade)
- float EntryDateTime
entry date/time in internal AmiBroker format (the same as used by AFL function
DateTime())
- float EntryFxRate
entry foreign exchange currency rate, if any scaling-in occurred this holds
average entry fx rate
- float EntryPrice
entry price, if any scaling-in occurred this holds average entry price
- float ExitDateTime
exit date/time in internal AmiBroker format (the same as used by AFL function
DateTime())
- float ExitFxRate
exit foreign exchange currency rate, if any scaling-out occurred this holds average
exit fx rate
- float ExitPrice
exit price, if any scaling-out occurred this holds average exit
price
- double Handle
internal handle value that allows to uniquely identify and manage (for example
exit or scale in/out) multiple trades open
on the same symbol at the same time. It can be passed to ExitTrade / ScaleTrade
instead of the symbol.
- bool IsLong
True if trade is long, False otherwise
- bool IsOpen
True if trade is open, False otherwise
- float MarginDeposit
initial margin deposit
- double MarginLoan
loan amount used for this trade
- float PointValue
point value (for futures / currencies)
- float RoundLotSize
round lot size
- float Score
entry score
- float Shares
number of shares / contracts
- string Symbol
symbol of the security
- string FullName
full name of the instrument (added in 5.69)
- float TickSize
tick size (minimum price change)
Stats object
Stats object provides the access to built-in backtester statistics and metrics.
Metrics are usually calculated once backtest is completed but it is also possible
to calculate metrics during backtest. To calculate current metrics and get
the access to them simply call GetPerformanceStats method of Backtester object.
Please note that if you calculate statistics in the middle of the backtest
they will include only closed trades.
To calculate and access stats use the following code:
// 'bo' variable holds
Backtester object retrieved earlier
stats = bo.GetPerformanceStats( 0 );
Methods:
- double GetValue( string MetricName )
retrieves the value of a metric, MetricName can be one of the following:
"InitialCapital" ,
"EndingCapital"
"NetProfit"
"NetProfitPercent"
"ExposurePercent"
"NetRAR"
"CAR"
"RAR"
"AllQty"
"AllPercent"
"AllAvgProfitLoss"
"AllAvgProfitLossPercent"
"AllAvgBarsHeld"
"WinnersQty"
"WinnersPercent"
"WinnersTotalProfit"
"WinnersAvgProfit"
"WinnersAvgProfitPercent"
"WinnersAvgBarsHeld"
"WinnersMaxConsecutive"
"WinnersLargestWin"
"WinnersLargestWinBars"
"LosersQty"
"LosersPercent"
"LosersTotalLoss"
"LosersAvgLoss"
"LosersAvgLossPercent"
"LosersAvgBarsHeld" ,
"LosersMaxConsecutive"
"LosersLargestLoss"
"LosersLargestLossBars"
"MaxTradeDrawdown"
"MaxTradeDrawdownPercent"
"MaxSystemDrawdown"
"MaxSystemDrawdownPercent"
"RecoveryFactor"
"CAR/MDD"
"RAR/MDD"
"ProfitFactor"
"PayoffRatio"
"StandardError"
"RRR"
"UlcerIndex"
"UlcerPerformanceIndex"
"SharpeRatio"
"KRatio"
Properties:
-none-
MonteCarloSim object
The object allows to access MonteCarlo simulation results and has only one
method
Methods:
- GetValue( string "field", float percentile
)
retrieves "field" value at specified percentile level. Available fields: "FinalEquity", "CAR",
"LowestEquity", "MaxDrawdown", "MaxPercDrawdown"
Further information
Examples and more documentation
can be found in this
Houston
presentation covering custom backtester interface (300 KB PDF format) and the
Knowledge
Base: http://www.amibroker.com/kb/category/afl/custom-backtest/