{"id":1716,"date":"2008-03-16T11:52:08","date_gmt":"2008-03-16T11:52:08","guid":{"rendered":"http:\/\/www.amibroker.org\/userkb\/2008\/03\/16\/amibroker-custom-backtester-interface-2\/"},"modified":"2015-09-30T14:28:03","modified_gmt":"2015-09-30T14:28:03","slug":"amibroker-custom-backtester-interface-2","status":"publish","type":"post","link":"http:\/\/www.amibroker.org\/editable_userkb\/2008\/03\/16\/amibroker-custom-backtester-interface-2\/","title":{"rendered":"AmiBroker Custom Backtester Interface"},"content":{"rendered":"
by Wayne (GP)<\/em><\/p>\n Introduction<\/strong>\t<\/p>\n From version 4.67.0, AmiBroker provides a custom backtester interface to allow customising the operation of the backtester’s second phase which processes the trading signals. This allows a range of special situations to be achieved that aren’t natively supported by the backtester. AmiBroker tends to refer to this as the Advanced Portfolio Backtester Interface, but as it seems to be more widely referred to as the Custom Backtester Interface, I will use this latter terminology.<\/p>\n Due to the object model used by the backtester interface, a higher level of programming knowledge is required than for simple AFL or looping. This document starts by discussing that model, so is aimed at AFL programmers who are already proficient and comfortable with basic AFL use, array indexing, and looping. If you don’t understanding looping, then you almost certainly won’t understand the custom backtester interface.<\/p>\n The Object Model<\/strong><\/p>\n The modern programming paradigm is called object-oriented programming, with the system being developed modelled as a set of objects that interact. The custom backtester interface follows that model.<\/p>\n An object can be thought of as a self-contained black-box that has certain properties and can perform certain functions. Internally it’s a combination of code and variables, where both can be made either private to the internals of the object only or accessible from outside for the benefit of users of the object. The private code and variables are totally hidden from the outside world and are of no interest to users of the object. Only developers working on the object itself care about them. Users of the object are only interested in the code and variables made accessible for their use.<\/p>\n Any variable made accessible to an object’s user is called a property of the object. For example, the Backtester object has a property (variable) called “Equity”, which is the current value of the portfolio equity during a backtest. Properties can be read and written much the same as any other variable, just by using them in expressions and assigning values to them (although some properties may be read-only). However, the syntax is a little different due to the fact they’re properties of an object, not ordinary variables.<\/p>\n An object’s code is made accessible to its users by providing a set of functions that can be called in relation to the object. These functions are called methods of the object. They are essentially identical to ordinary functions, but perform operations that are relevant to the purpose of the object. For example, the Backtester object has methods (functions) that perform operations related to backtesting. Methods are called in much the same way as other functions, but again the syntax is a little different due to them being methods of an object rather than ordinary functions.<\/p>\n The aim of the object model is to view the application as a set of self-contained and reusable objects that can manage their own functionality and provide interfaces for other objects and code to use. Imagine it as being similar to a home entertainment system, where you buy a number of components (objects) like a TV, DVD player, surround-sound system, and karaoke unit (if you’re that way inclined!). Each of those components manages its own functionality and provides you with a set of connectors and cables to join them all together to create the final application: the home entertainment system. The beauty of that arrangement is that each component provides a standard interface (if you’re lucky) that will allow any brands of the other components to be connected, without those components having to know the details of how all the other components work internally, and considerable choice in the structure of the final entertainment system constructed. Similarly, software objects have standard interfaces in the form of methods and properties that allow them to be used and reused in any software.<\/p>\n Accessing Oject Properties And Methods<\/strong><\/p>\n To access the properties and methods of an object, you need to know not only the name of the property or method, but also the name of the object. In AmiBroker AFL, you cannot define or create your own objects, only use objects already provided by AmiBroker. AmiBroker help details all its objects, methods, and properties in the section “Advanced portfolio backtester interface”. The variable “bo” is your own variable, and you can call it whatever you like within the naming rules of AFL. However, to avoid a lot of verbose statements, it’s good to keep it nice and short. Previously you’ve only dealt with variables that are either single numbers, arrays of numbers, or strings. The variable “bo” is none of those, instead being a new type of variable called an object variable. In this case it holds the Backtester object (or really a reference to the Backtester object, but I don’t want to get into the complicated topic of references here). Now that you have the Backtester object in your own variable, you can access its properties and methods.<\/p>\n The syntax for referencing an object’s property is objectName.objectProperty<\/font>, for example bo.InitialEquity<\/font>,. That can then be used the same as any other variable (assuming it’s not a read-only property, which InitialEquity is not):<\/p>\n From this you can see the advantage of keeping object variable names short. If you called the variable “myBacktesterObject”, then for the last example above you’d end up with:<\/p>\n =============== but:<\/p>\n The same syntax is used to access the methods of an object. The method name is preceded by the object name with a decimal point: objectName.objectMethod(). Any parameters are passed to the method in the same manner as to ordinary functions: <\/p>\n For example, to call the Backtester object’s AddCustomMetric method and pass the two compulsory parameters Title and Value, a statement like this would be used:<\/p>\n AmiBroker help indicates that this method returns a value of type “bool”, which means boolean and thus can only take the values True and False. However, it doesn’t detail what this return value means. A good guess would be that it returns True if the custom metric was successfully added and False if for some reason it failed to be added. However, that’s only a guess, but a common reason for returning boolean values. For some of the other methods that return values of type “long”, it’s more difficult to guess what they might contain. Here the variable “sig” is another object variable, but this time of type Signal rather than Backtester. In other words, it holds a Signal object rather than a Backtester object. Unlike the single Backtester object, AmiBroker can have many different Signal objects created at the same time (one for each trading signal). As a Signal object holds the signal data for a particular symbol at a particular bar, the method needs to know the bar number, which would typically be specified using a loop index variable (‘i’ above) inside a loop:<\/p>\n Once a Signal object has been obtained, its properties and methods can be referenced:<\/p>\n Note that the property sig.PosScore is a single number, not an array. While the AFL variable PositionScore is an array, the “sig” object only holds data for a single bar, so the property sig.PosScore is the position score value for that bar only, thus a single number.<\/p>\n Also note that AmiBroker help is not very clear on some topics. For example, the Signal object only has a few methods that indicate whether the current bar contains an entry, exit, long, or short signal, or has a scale in or out signal. However, it doesn’t indicate how you combine these to get the exact details. For example, how do you tell the difference between a scale-in and a scale-out? Is scaling in to a long position a combination of IsScale, IsEntry, and IsLong, or perhaps just IsScale and IsLong, or neither of those? In some cases you need to use trial and error and see what actually works (learn how to use the DebugView program with _TRACE statements: see Appendix B). Fortunately for this specific example, the Signal object also has a property called Type that indicates exactly what type the signal is.<\/p>\n Using The Custom Backtester Interface<\/strong><\/p>\n To use your own custom backtest procedure, you first need to tell AmiBroker that you will be doing so. There are a few ways of doing this:<\/p>\n The next thing that’s required in all backtest procedures is to ensure the procedure only runs during the second phase of the backtest. That’s achieved with the following conditional statement:<\/p>\n And finally, before anything else can be done, a copy of the Backtester object is needed:<\/p>\n So all custom backtest procedures, where they’re in the same file as the other AFL code, will have a template like this:<\/p>\n If the backtests were using a procedure in the file:<\/p>\n c:\\AmiBroker\\Formulas\\Custom\\Backtests\\MyBacktest.afl<\/p>\n then the first line above in your system AFL code would be replaced with:<\/p>\n and the rest of the procedure would be in the specified file. Or, if the same values were specified in the Automatic Analysis settings, the two lines above would not be needed in your AFL code at all, and the procedure would be in the specified file.<\/p>\n Custom Backtester Levels<\/strong><\/p>\n The AmiBroker custom backtester interface provides three levels of user customisation, simply called high-level, mid-level, and low-level. The high-level approach requires the least programming knowledge, and the low-level approach the most. These levels are just a convenient way of grouping together methods that can and need to be called for a customisation to work, and conversely indicate which methods cannot be called in the same customisation because their functionality conflicts. Some methods can be called at all levels, others only at higher levels, and others only at lower levels. AmiBroker help details which levels each method can be used with. Naturally, the higher the level and the simpler the programming, the less flexibility that’s available.<\/p>\n This document will not detail every single method and property available, so the rest of this document should be read in conjunction with the AmiBroker help sections “Advanced portfolio backtester interface” and “Adding custom backtest metrics”.<\/p>\n High-Level Interface<\/strong><\/p>\n The high-level interface doesn’t allow any customising of the backtest procedure itself. It simply allows custom metrics to be defined for the backtester results display, and trade statistics and metrics to be calculated and examined. A single method call runs the whole backtest in one hit, the same as when the custom backtester interface isn’t used at all.<\/p>\n AmiBroker help has an example of using the high level interface to add a custom metric. See the section called “Adding custom backtest metrics”. In essence, the steps are:<\/p>\n That would look something like this:<\/p>\n As well as just using the built-in statistics and metrics, obtained from the Stats object after the backtest has been run, it’s also possible to calculate your metric by examining all the trades using the Trade object. As some positions may still be open at the end of the backtest, you may need to iterate through both the closed trade and open position lists:<\/p>\n
\nTo use real AFL examples, the first object detailed in the help is the Backtester object. AmiBroker provides a single Backtester object to perform backtests. To use the Backtester object, you first have to get a copy of it and assign that to your own variable:<\/p>\nbo <\/span>= <\/span>GetBacktesterObject<\/span>();<\/span><\/pre>\n
bo<\/span>.<\/span>InitialEquity <\/span>= <\/span>10000<\/span>;\r<\/span>capital <\/span>= <\/span>bo<\/span>.<\/span>InitialEquity<\/span>;\r<\/span>gain <\/span>= (<\/span>capital <\/span>- <\/span>bo<\/span>.<\/span>InitialEquity<\/span>) \/ <\/span>bo<\/span>.<\/span>InitialEquity <\/span>* <\/span>100<\/span>;<\/span><\/pre>\n
gain <\/span>= (<\/span>capital <\/span>- <\/span>myBacktesterObject<\/span>.<\/span>InitialEquity<\/span>) \/ <\/span>myBacktesterObject<\/span>.<\/span>InitialEquity <\/span>* <\/span>100<\/span>;<\/span><\/pre>\n
\nHere I’ve had to reduce the font size just to fit it all on a single line.
\nIf a property is read-only, then you cannot perform any operation that would change its value. So, using the Equity property which is read-only:<\/p>\ncurrentEquity <\/span>= <\/span>bo<\/span>.<\/span>Equity<\/span>; <\/span>\/\/ This is fine<\/span><\/pre>\n
bo<\/span>.<\/span>Equity <\/span>= <\/span>50000<\/span>; <\/span>\/\/ This is an error!<\/span><\/pre>\n
objectName<\/span>.<\/span>objectMethod<\/span>(<\/span>parm1<\/span>, <\/span>parm2<\/span>, <\/span>parm3<\/span>).<\/span><\/pre>\n
bo<\/span>.<\/span>AddCustomMetric<\/span>(<\/span>"myMetric"<\/span>, <\/span>1000<\/span>);<\/span><\/pre>\n
\nAnother example with a return parameter:<\/p>\nsig <\/span>= <\/span>bo<\/span>.<\/span>GetFirstSignal<\/span>(<\/span>i<\/span>);<\/span><\/pre>\n
<\/span>for (<\/span>i <\/span>= <\/span>0<\/span>; <\/span>i <\/span>< <\/span>BarCount<\/span>; <\/span>i<\/span>++)\r{\r . . . .\r <\/span>sig <\/span>= <\/span>bo<\/span>.<\/span>GetFirstSignal<\/span>(<\/span>i<\/span>);\r . . . .\r}<\/span><\/pre>\n
sig<\/span>.<\/span>PosScore <\/span>= <\/span>0<\/span>; <\/span>\/\/ Set position score to zero for this bar\r<\/span>if (<\/span>sig<\/span>.<\/span>IsEntry<\/span>()) <\/span>\/\/ If this bar's signal is entry (buy\/short)\r<\/span>{\r . . . .\r}<\/span><\/pre>\n
\n
<\/span>if (<\/span>Status<\/span>(<\/span>"action"<\/span>) == <\/span>actionPortfolio<\/span>)\r{\r . . . . \r}<\/span><\/pre>\n
bo <\/span>= <\/span>GetBacktesterObject<\/span>();<\/span><\/pre>\n
SetCustomBacktestProc<\/span>(<\/span>""<\/span>);\rif (<\/span>Status<\/span>(<\/span>"action"<\/span>) == <\/span>actionPortfolio<\/span>)\r{\r <\/span>bo <\/span>= <\/span>GetBacktesterObject<\/span>();\r\r <\/span>\/\/ Rest of procedure goes here\r\r<\/span>}<\/span><\/pre>\n
SetOption<\/span>(<\/span>"UseCustomBacktestProc"<\/span>, <\/span>True<\/span>);\r<\/span>SetCustomBacktestProc<\/span>(<\/span>"c:\\\\AmiBroker\\\\Formulas\\\\Custom\\\\Backtests\\\\MyBacktest.afl"<\/span>);<\/span><\/pre>\n
\n
SetCustomBacktestProc<\/span>(<\/span>""<\/span>);\rif (<\/span>Status<\/span>(<\/span>"action"<\/span>) == <\/span>actionPortfolio<\/span>)\r{\r <\/span>bo <\/span>= <\/span>GetBacktesterObject<\/span>(); <\/span>\/\/ Get backtester object\r <\/span>bo<\/span>.<\/span>Backtest<\/span>(); <\/span>\/\/ Run backtests\r <\/span>stats <\/span>= <\/span>bo<\/span>.<\/span>GetPerformanceStats<\/span>(<\/span>0<\/span>); <\/span>\/\/ Get Stats object for all trades\r <\/span>myMetric <\/span>= <<\/span>calculation using stats<\/span>>; <\/span>\/\/ Calculate new metric\r <\/span>bo<\/span>.<\/span>AddCustomMetric<\/span>(<\/span>"MyMetric"<\/span>, <\/span>myMetric<\/span>); <\/span>\/\/ Add metric to display\r<\/span>}<\/span><\/pre>\n
<\/span>for (<\/span>trade <\/span>= <\/span>bo<\/span>.<\/span>GetFirstTrade<\/span>(); <\/span>trade<\/span>; <\/span>trade <\/span>= <\/span>bo<\/span>.<\/span>GetNextTrade<\/span>())\r{\r . . . .\r}\rfor (<\/span>