[Amibroker新手日誌] [AFL] (本篇重要)Back-testing your trading ideas

摘要:[Amibroker新手日誌] [AFL] Back-testing your trading ideas

http://www.amibroker.com/guide/h_backtest.html

 

Introduction

One of the most useful things that you can do in the analysis window is to back-test your trading strategy on historical data. This can give you valuable insight into strengths and weak points of your system before investing real money. This single AmiBroker feature is can save lots of money for you.

 

Writing your trading rules

First you need to have objective (or mechanical) rules to enter and exit the market. This step is the base of your strategy and you need to think about it yourself since the system must match your risk tolerance, portfolio size, money management techniques, and many other individual factors.

Once you have your own rules for trading you should write them as buy and sell rules in AmiBroker Formula Lanugage (plus short and cover if you want to test also short trading).

In this chapter we will consider very basic moving average cross over system. The system would buy stocks/contracts when close price rises above 45-day exponential moving average and will sell stocks/contracts when close price falls below 45-day exponential moving average.

The exponential moving average can be calculated in AFL using its built-in function EMA. All you need to do is to specify the input array and averaging period, so the 45-day exponential moving average of closing prices can be obtained by the following statement:

ema( close, 45 );

The close identifier refers to built-in array holding closing prices of currently analysed symbol.

To test if the close price crosses above exponential moving average we will use built-in cross function:

buy = cross( close, ema( close, 45 ) );

The above statement defines a buy trading rule. It gives "1" or "true" when close price crosses above ema( close, 45 ). Then we can write the sell rule which would give "1" when opposite situation happens - close price crosses below ema( close, 45 ):

sell = cross( ema( close, 45 ), close );

Please note that we are using the same cross function but the opposite order of arguments.

So complete formula for long trades will look like this:

buy = cross( close, ema( close, 45 ) );
sell = cross( ema( close, 45 ), close );

NOTE: To create new formula please open Formula Editor using Analysis->Formula Editor menu, type the formula and choose Tools->Send to Analysis menu in Formula editor

 

Back testing

To back-test your system just click on the Back test button in the Automatic analysis window. Make sure you have typed in the formula that contains at least buy and sell trading rules (as shown above). When the formula is correct AmiBroker starts analysing your symbols according to your trading rules and generates a list of simulated trades. The whole process is very fast - you can back test thousands of symbols in a matter of minutes. The progress window will show you estimated completion time. If you want to stop the process you can just click Cancel button in the progress window.

 

Analysing results

When the process is finished the list of simulated trades is shown in the bottom part of Automatic analysis window. (the Results pane). You can examine when the buy and sell signals occurred just by double clicking on the trade in Results pane. This will give you raw or unfiltered signals for every bar when buy and sell conditions are met. If you want to see only single trade arrows (opening and closing currently selected trade) you should double click the line while holding SHIFT key pressed down. Alternatively you can choose the type of display by selecting appropriate item from the context menu that appears when you click on the results pane with a right mouse button.

In addition to the results list you can get very detailed statistics on the performance of your system by clicking on the Report button. To find out more about report statistics please check out report window description.

 

Changing your back testing settings

Back testing engine in AmiBroker uses some predefined values for performing its task including the portfolio size, periodicity (daily/weekly/monthly), amount of commission, interest rate, maximum loss and profit target stops, type of trades, price fields and so on. All these settings could be changed by the user using settings window. After changing settings please remember to run your back testing again if you want the results to be in-sync with the settings.

For example, to back test on weekly bars instead of daily just click on the Settings button select Weekly from Periodicity combo box and click OK, then run your analysis by clicking Back test.

 

Reserved variable names

The following table shows the names of reserved variables used by Automatic Analyser. The meaning and examples on using them are given later in this chapter.

Variable Usage Applies to
buy

defines "buy" (enter long position) trading rule

//多單

Automatic Analysis, Commentary
sell

defines "sell" (close long position) trading rule

//平多單

Automatic Analysis, Commentary
short

defines "short" (enter short position - short sell) trading rule

//空單

Automatic Analysis
cover

defines "cover" (close short position - buy to cover) trading rule

//平空單

Automatic Analysis
buyprice

defines buying price array (this array is filled in with the default values according to the Automatic Analyser settings)

Automatic Analysis
sellprice defines selling price array (this array is filled in with the default values according to the Automatic Analyser settings) Automatic Analysis
shortprice defines short selling price array (this array is filled in with the default values according to the Automatic Analyser settings) Automatic Analysis
coverprice defines buy to cover price array (this array is filled in with the default values according to the Automatic Analyser settings) Automatic Analysis
exclude

If defined, a true (or 1) value of this variable excludes current symbol from scan/exploration/back test. They are also not considered in buy and hold calculations. Useful when you want to narrow your analysis to certain set of symbols.

//將目前標的中排除測試

Automatic Analysis
roundlotsize

defines round lot sizes used by backtester (see explanations below)

//成批交易(看不懂,是否如台股一張1000股?)

Automatic Analysis (new in 4.10)
ticksize defines tick size used to align prices generated by built-in stops (see explanations below) (note: it does not affect entry/exit prices specified by buyprice/sellprice/shortprice/coverprice) Automatic Analysis (new in 4.10)
pointvalue

allows to read and modify future contract point value (see backtesting futures
CAVEAT: this AFL variable is by default set to 1 (one) regardless of contents of Information window UNLESS you turn ON futures mode (SetOption("FuturesMode", True ))

//設定一點值多少

Automatic Analysis (new in 4.10)
margindeposit

allows to read and modify future contract margin (see backtesting futures)

//期貨契約保證金

 

Automatic Analysis (new in 4.10)
positionsize

Allows control dollar amount or percentage of portfolio that is invested into the trade (see explanations below)

//在投組共用的Equity中,保留給該檔股票的資金有多少(可以用金額設定,也可用百分比設定)

Automatic Analysis (new in 3.9)

 

Advanced concepts

Until now we discussed fairly simple use of the back tester. AmiBroker, however supports much more sophisticated methods and concepts that will be discussed later on in this chapter. Please note that the beginner user should first play a little bit with the easier topics described above before proceeding.

So, when you are ready, please take a look at the following recently introduced features of the back-tester:

a) AFL scripting host for advanced formula writers 
b) enhanced support for short trades
c) the way to control order execution price from the script
d) various kinds of stops in back tester
e) position sizing
f) round lot size and tick size
g) margin account
h) backtesting futures

AFL scripting host is an advanced topic that is covered in a separate document available here (//還蠻屌的,可以用VB或是JS)and I won't discuss it in this document. Remaining features are much more easy to understand.

 

Short trade support //支援放空訊號

In the previous versions of AmiBroker, if you wanted to back-test system using both long and short trades, you could only simulate stop-and-reverse strategy. When long position was closed a new short position was opened immediatelly. It was because buy and sell reserved variables were used for both types of trades.

Now (with version 3.59 or higher) there are separate reserved variables for opening and closing long and short trades:

buy - "true" or 1 value opens long trade
sell - "true" or 1 value closes long trade
short - "true" or 1 value opens short trade
cover - "true" or 1 value closes short trade

 

Som in order to back-test short trades you need to assign short and cover variables.
If you use stop-and-reverse system (always on the market) simply assign sell to short and buy to cover

short = sell;
cover = buy;

This simulates the way pre-3.59 versions worked.

But now AmiBroker enables you to have separate trading rules for going long and for going short as shown in this simple example:

// long trades entry and exit rules:
buy = cross( cci(), 100 );
sell = cross( 100, cci() );

// short trades entry and exit rules:
short = cross( -100, cci() );
cover = cross( cci(), -100 );

Note that in this example if CCI is between -100 and 100 you are out of the market.

//CCI買賣超指標
//捕捉大盤短線底的靈敏度更高、更明顯,安全性也相對說來更好
//CCI>100短多; CCI<-100短空

 

Controlling trade price //控制交易價格

AmiBroker now provides 4 new reserved variables for specifying the price at which buy, sell, short and cover orders are executed. These arrays have the following names: buyprice, sellprice, shortprice and coverprice.

The main application of these variables is controlling trade price:

BuyPrice = IIF( dayofweek() == 1, HIGH, CLOSE ); 
// on monday buy at high, otherwise buy on close

So you can write the following to simulate real stop-orders: 

BuyStop = ... the formula for buy stop level;   //1. BuyStop不是關鍵字。2. BuyStop是追多概念
SellStop = ... the formula for sell stop level; //追空

// if anytime during the day prices rise above buystop level (high>buystop) 
// the buy order takes place (at buystop or low whichever is higher)
Buy = Cross( High, BuyStop );  //如果價位超過了BuyStop所定的價位,就追多買進

// if anytime during the day prices fall below sellprice level ( low < sellstop )
// the sell order takes place (at sellstop or high whichever is lower)
Sell = Cross( SellPrice, SellStop);
//如果價位低於了SellStop所定的價位,就追空放空

BuyPrice = max( BuyStop, Low ); // make sure buy price not less than Low 
SellPrice = min( SellStop, High ); // make sure sell price not greater than High

 

Please note that AmiBroker presets buyprice, sellprice, shortprice and coverprice array variables with the values defined in system test settings window (shown below), so you can but don't need to define them in your formula. If you don't define them AmiBroker works as in the old versions.

During back-testing AmiBroker will check if the values you assigned to buyprice, sellprice, shortprice, coverprice fit into high-low range of given bar. If not, AmiBroker will adjust it to high price (if price array value is higher than high) or to the low price (if price array value is lower than low)

Profit target stops //停利

As you can see in the picture above, new settings for profit target stops are available in the system test settings window. Profit target stops are executed when the high price for a given day exceedes the stop level that can be given as a percentage or point increase from the buying price. By default stops are executed at price that you define as sell price array (for long trades) or cover price array (for short trades). This behaviour can be changed by using "Exit at stop" feature.

 

"Exit at stop" feature //停損

If you mark "Exit at stop" box in the settings the stops will be executed at exact stop level, i.e. if you define profit target stop at +10% your stop and the buy price was 50 stop order will be executed at 55 even if your sell price array contains different value (for example closing price of 56).

Maximum loss stops work in a similar manner - they are executed when the low price for a given day drops below the stop level that can be given as a percentage or point increase from the buying price

Trailing stops //追踨止損 (停損點會隨著獲利增加而升高,這樣就可以保護一定的獲利)

This kind of stop is used to protect profits as it tracks your trade so each time a position value reaches a new high, the trailing stop is placed at a higher level. When the profit drops below the trailing stop level the position is closed. This mechanism is illustrated in the picture below (10% trailing stop is shown):

<

The trailing stop, as well as two other kind of stops could be enabled from user interface (Automatic analysis' Settings window) or from the formula level - using ApplyStop function:

To reproduce the example above you would need to add the following code to your automatic analysis formula:

ApplyStop( 2, 1, 10, 1 ); // 10% trailing stop, percent mode, exit at stop ON

or you can write it using predefined constants that are more descriptive

ApplyStop( stopTypeTrail, stopModePercent, 10, True );

Trailing stops could be also defined in points (dollars) and percent of profit (risk). In the latter case the amount parameter defines the percentage of profits that could be lost without activating the stop. So 20% percent of profit (risk) stop will exit your trade that has maximum profit of $100 when the profit decreases below $80.

 

Dynamic stops //動態停損

The ApplyStop() function allows now to change the stop level from trade to trade. This enables you to implement for example volatility-based stops very easily.

For example to apply maximum loss stop that will adapt the maximum acceptable loss based on 10 day average true range you would need to write:

ApplyStop( 0, 2, 2 * ATR( 10 ), 1 );

or you can write it using predefined constants that are more descriptive

ApplyStop( stopTypeLoss, stopModePoint, 2 * ATR( 10 ), True );

The function above will place the stop 2 times 10 day ATR below entry price.

As ATR changes from trade to trade - this will result in dynamic, volatility based stop level. Please note that 3rd parameter of ApplyStop function (the amount) is sampled at the trade entry and held troughout the trade. So in the example above it uses ATR(10) value from the date of the entry. Further changes of ATR do not affect the stop level.

See complete APPLYSTOP function documentation for more details.

 

Coding your own custom stop types//客製停損單

ApplyStop function is intended to cover most "popular" kinds of stops. You can however code your own kind of stops and exits using looping code. For example the following re-implements profit target stop and shows how to refer to the trade entry price in your formulas:

/* a sample low-level implementation of Profit-target stop in AFL: */ 

//示範停利

Buy = CrossMACD(), Signal() ); 

priceatbuy=
0

for( i = 0; i < BarCount; i++ ) 

     
if( priceatbuy == 0 && Buy[ i ] ) 
     priceatbuy = 
BuyPrice[ i ];  //先找出買價是多少 ex: 用50元買到一張gg

     if( priceatbuy > 0 && SellPrice[ i ] > 1.1 * priceatbuy )  //如果賣的價錢高於買價1.1倍,ex:如果價格>55元,就停利出掉
     { 
       
Sell[ i ] = 1
       
SellPrice[ i ] = 1.1 * priceatbuy; 
       priceatbuy = 
0
     } 
     
else 
       
Sell[ i ] = 0

 

Position sizing//可投資資金

This is a new feature in version 3.9. Position sizing in backtester is implemented by means of new reserved variable

PositionSize = <size array>

Now you can control dollar amount or percentage of portfolio that is invested into the trade 

  • positive number define (dollar) amount that is invested into the trade for example:  

    PositionSize = 1000; // invest $1000 in every trade
     
  • negative numbers -100..-1 define percentage: 
    -100 gives 100% of current portfolio size, 
    -33 gives 33% of available equity for example:

    PositionSize = -50; /* always invest only half of the current equity */ //只允許投資目前資金的一半 
     
  • dynamic sizing example:

    PositionSize = - 100 + RSI();

    as RSI varies from 0..100 this will result in position depending on RSI values -> low values of RSI will result in higher percentage invested

 

 

If less than 100% of available cash is invested then the remaining amount earns interest rate as defined in the settings.

//如果錢都投資了,怎麼辦

There is also a new checkbox in the AA settings window: "Allow position size shrinking" - this controls how backtester handles the situation when requested position size (via PositionSize variable) exceeds available cash: when this flag is checked the position is entered with size shinked(縮小) to available cash if it is unchecked the position is not entered.

//Allow position size shrinking 當此次要投入的金額>口袋的餘額,如果設定這個選項,就用餘額買進等值的部位;如果沒設定,錢不夠就不買了

To see actual position sizes please use a new report mode in AA settings window: "Trade list with prices and pos. size"

For the end, here is an example of Tharp's ATR-based position sizing technique coded in AFL:

Buy = <your buy formula here>
Sell = 0; // selling only by stop

TrailStopAmount = 2 * ATR( 20 ); //追縱停損設成 =20天平均價差的2倍  //ps:ATR Average True Range , excel試算表說明
Capital = 100000; /* IMPORTANT: Set it also in the Settings: Initial Equity */  //設定期初資本10萬

Risk = 0.01*Capital; //風險1000元
PositionSize = (Risk/TrailStopAmount)*BuyPrice; 
ApplyStop( 2, 2, TrailStopAmount, 1 );

The technique could be summarized as follows:

The total equity per symbol is $100,000, we set the risk level at 1% of total equity. Risk level is defined as follows: if a trailing stop on a $50 stock is at, say, $45 (the value of two ATR's against the position), the $5 loss is divided into the $1000 risk to give 200 shares to buy. So, the loss risk is $1000 but the allocation risk is 200 shares x $50/share or $10,000. So, we are
allocating 10% of the equity to the purchase but only risking $1000. (Edited excerpt from the AmiBroker mailing list)

//上述程式說明
//設定每檔股票可投資資金10萬元,設定風險等級為可投資金額的1%,就等於1000元。 如果以50元買進的股票,追縱停損在45元(2倍平均價差),那就有5元的損失。這5元的損失以1000元的風險等級就可以攤到200股。所以損失風險是1000,但是分攤風險是200股*每股50元 = 1萬元,等於分攤了可投資金額的10%
//@@看不懂

 

Round lot size and tick size

Round lot size

Various instruments are traded with various "trading units" or "blocks". For example you can purchase fractional number of units of mutual fund, but you can not purchase fractional number of shares. Sometimes you have to buy in 10s or 100s lots. AmiBroker now allows you to specify the block size on global and per-symbol level.

You can define per-symbol round lot size in the Symbol->Information page (pic. 3). The value of zero means that the symbol has no special round lot size and will use "Default round lot size" (global setting) from the Automatic Analysis settings page (pic. 1). If default size is set also to zero it means that fractional number of shares/contracts are allowed.

You can also control round lot size directly from your AFL formula using RoundLotSize reserved variable, for example:

RoundLotSize = 10;

//可以在Symbol->Information設定RoundLotSize,一次交易幾股

 

 

Tick size

This setting controls the minimum price move of given symbol. You can define it on global and per-symbol level. As with round lot size, you can define per-symbol tick size in the Symbol->Information page (pic. 3). The value of zero instructs AmiBroker to use "default tick size" defined in the Settings page (pic. 1) of Automatic Analysis window. If default tick size is also set to zero it means that there is no minimum price move.

You can set and retrieve the tick size also from AFL formula using TickSize reserved variable, for example:

TickSize = 0.01;

Note that the tick size setting affects ONLY trades exited by built-in stops and/or ApplyStop(). The backtester assumes that price data follow tick size requirements and it does not change price arrays supplied by the user.

So specifying tick size makes sense only if you are using built-in stops so exit points are generated at "allowed" price levels instead of calculated ones. For example in Japan - you can not have fractional parts of yen so you should define global ticksize to 1, so built-in stops exit trades at integer levels.

 

Margin account

Account margin setting defines percentage margin requirement for entire account. The default value of Account margin is 100. This means that you have to provide 100% funds to enter the trade, and this is the way how backtester worked in previous versions. But now you can simulate a margin account. When you buy on margin you are simply borrowing money from your broker to buy stock. With current regulations you can put up 50% of the purchase price of the stock you wish to buy and borrow the other half from your broker. To simulate this just enter 50 in the Account margin field (see pic. 1) . If your intial equity is set to 10000 your buying power will be then 20000 and you will be able to enter bigger positions. Please note that this settings sets the margin for entire account and it is NOT related to futures trading at all. In other words you can trade stocks on margin account.

 

Additional settings

  • "Reverse entry signal forces exit" check box to the Backtester settings.
    When it is ON (the default setting) - backtester works as in previous versions and closes already open positon if new entry signal in reverse direction is encountered. If this switch is OFF - even if reverse signal occurs backtester maintains currently open trade and does not close positon until regular exit (sell or cover) signal is generated. 
    In other words when this switch is OFF backtester ignores Short signals during long trades and ignores Buy signals during short trades.
     
  • "Allow same bar exit (single bar trade)" option to the Settings
    When it is ON (the default settings) - entry and exit at the very same bar is allowed (as in previous versions)
    if it is OFF - exit can happen starting from next bar only (this applies to regular signals,there is a separate setting for ApplyStop-generated exits). Switching it to OFF allows to reproduce the behaviour of MS backtester that is not able to handle same day exits.
  • "Activate stops immediately"

    This setting solves the problem of testing systems that enter trades on market open. In versions prior to 4.09 backtester assumed that you were entering trades on market close so built-in stops were activated from the next day. The problem was when you in fact defined open price as the trade entry price - then same day price fluctuations did not trigger the stops. There were some published workarounds based on AFL code but now you don't need to use them. Simply if you trade on open you should mark "Activate stops immediately" (pic. 1). 

    You may ask why do not simply check the buyprice or shortprice array if it is equal to open price. Unfortunatelly this won't work. Why? Simply because there are doji days when open price equals close and then backtester will never know if trade was entered at market open or close. So we really need a separate setting. 
  • "Use QuickAFL"

    QuickAFL(tm) is a feature that allows faster AFL calculation under certain conditions. Initially (since 2003) it was available for indicators only, as of version 5.14+ it is available in Automatic Analysis too.

    Initially the idea was to allow faster chart redraws through calculating AFL formula only for that part which is visible on the chart. In a similar manner, automatic analysis window can use subset of available quotations to calculate AFL, if selected “range” parameter is less than “All quotations". 

    Detailed explanation on how QuickAFL works and how to control it, is provided in this Knowledge Base article: http://www.amibroker.com/kb/2008/07/03/quickafl/

    Note that this option works not only in the backtester, but also in optimizations, explorations and scans.

 

See Also:

Portfolio-level backtesting article.

Backtesting systems for futures contracts article.

APPLYSTOP function description

Using AFL editor section of the guide.

Insider guide to backtester (newsletter 1/2002)

http://www.plurk.com/SophieQ