Short introduction to creation of Optimizer plugins

Copyright (C) 2008 Tomasz Janeczko, AmiBroker.com.

All files included in ADK archive are intended only for the use of AmiBroker registered users.
ANY DISSEMINATION, DISTRIBUTION OR COPYING OF THESE FILES WITHOUT PRIOR CONSENT OF AMIBROKER.COM IS PROHIBITED.

If you are unfamiliar with writing AmiBroker plugin DLLs, before reading this document, please read AmiBroker Development Kit (ADK) documentation: points 1.1 (introduction), 1.2 (interface architecture) and 2.2.1 (data types)

The document assumes that you have working knowledge of C/C++ programming language. Examples are prepared using Microsoft Visual C++ 6.

1 Getting Started

Optimizer plugins are very simple to implement. You just need to get skeleton code (MonteCarlo random optimizer sample is good as a starting point) and add your bits to it.

As every AmiBroker DLL plugin, optimizer plugins require 3 core functions: GetPluginInfo, Init(), Release() that are standard part of AmiBroker plugin interface. They are very staightforward (single-liners in most cases). You can just copy / paste the functions below. The only 2 things that you must change:

The plugin ID code MUST BE UNIQUE. Otherwise it will conflict with other plugins. For tests I suggest using PIDCODE( 't', 'e', 's', '1') changing the last digit if you want to have more than one test plugin. For list of already used IDs please see : http://www.amibroker.com/plugins.html . Before releasing your plugin to the public, you must request unique plugin ID from support at amibroker.com. The plugin ID will be later used to specify optimizer in AFL code via OptimizerSetEngine() function.

// These are the only two lines you need to change
#define PLUGIN_NAME
"Monte Carlo Optimizer plug-in"
#define VENDOR_NAME
"Amibroker.com"
#define PLUGIN_VERSION
10001


#define THIS_PLUGIN_TYPE PLUGIN_TYPE_OPTIMIZER

////////////////////////////////////////
// Data section
////////////////////////////////////////
static struct PluginInfo oPluginInfo =
{
       sizeof( struct PluginInfo ),
       THIS_PLUGIN_TYPE,      
       PLUGIN_VERSION,
       PIDCODE( 'm', 'o', 'c', 'a'),
       PLUGIN_NAME,
       VENDOR_NAME,
      
13012679,
      
387000
};


///////////////////////////////////////////////////////////
// Basic plug-in interface functions exported by DLL
///////////////////////////////////////////////////////////

PLUGINAPI
int GetPluginInfo( struct PluginInfo *pInfo )
{
    *pInfo = oPluginInfo;

   
return True;
}


PLUGINAPI
int Init(void)
{
   
   
return 1;
}   

PLUGINAPI
int Release(void)
{

   
return 1;      // default implementation does nothing
}

2 Optimizer Interface

The optimizer interface consists of 4 simple functions:

And two data structures:

struct OptimizeItem
{
    char   *Name
;
    float   Default
;
    float   Min;
    float   Max;
    float   Step;
    double   Current;
    float   Best;
};

#define MAX_OPTIMIZE_ITEMS
100

struct OptimizeParams
{
   
int      Mode;            // 0 - gets defaults, 1 - retrieves settings from formula (setup phase), 2 - optimization phase
   
int      WalkForwardMode;   // 0 - none (regular optimization), 1-in-sample, 2 - out of sample
   
int      Engine;            // optimization engine selected - 0 means - built-in exhaustive search
   
int      Qty;            // number of variables to optimize
   
int      LastQty;
   BOOL   CanContinue;      
// boolean flag 1 - means optimization can continue,
                           //0 - means aborted by pressing "Cancel" in progress dialog or other error

   BOOL   DuplicateCheck;      
// boolean flag 1 - means that AmiBroker will first
                         //
check if same param set wasn't used already
                        
// and if duplicate is found it won't run backtest, instead will return previously stored value
   
int      Reserved;
   char   *InfoText;         
// pointer to info text buffer (providing text display in the progress dialog)
   
int      InfoTextSize;     // the size (in bytes) of info text buffer
   __int64   Step;        
// current optimization step (used for progress indicator) - automatically increased with each iteration
   __int64   NumSteps;         
// total number of optimization steps (used for progress indicator)
   double  TargetCurrent;
   double  TargetBest;
   
int      TargetBestStep;      // optimization step in which best was achieved
   struct   OptimizeItem   Items[ MAX_OPTIMIZE_ITEMS ];
// parameters to optimize
};

2.1 Data structures

The OptimizeParams structure holds all information needed to perform optimization. The most important part is Items array of OptimizeItem structures. It holds the array of all parameters specified for optimization using AFL's Optimize() function. The number of valid parameters is stored in Qty member of OptimizeParams structure.

2.2 OptimizerInit function

PLUGINAPI int OptimizerInit( struct OptimizeParams *pParams )

This function gets called when AmiBroker collected all information about parameters that should be optimized. This information is available in OptimizeParams structure. The optimization engine DLL should use this point to initialize internal data structures. Also the optimizer should set the value of pParams->NumSteps variable to the expected TOTAL NUMBER OF BACKTESTS that are supposed to be done during optimization.

This value is used for two purposes:
1. progress indicator (total progress is expressed as backtest number divided by NumSteps)
2. flow control (by default AmiBroker will continue calling OptimizerRun until number of backtests reaches the NumSteps) - it is possible however to
override that (see below)

Note that sometimes you may not know exact number of steps (backtests) in advance, in that case provide estimate. Later, inside OptimizerRun you will be able to adjust it, as tests go by.

Return values:
1 - initialization complete and OK
0 - init failed

2.3 OptimizerSetOption function

PLUGINAPI int OptimizerSetOption( const char *pszParam, AmiVar newValue )

This function is intended to be used to allow setting additional options / parameters of optimizer from the AFL level.

It gets called in two situations:
1. When SetOptimizerEngine() AFL function is called for particular optimizer - then it calls OptimizerSetOption once with pszParam set to NULL
and it means that optimizer should reset parameter values to default values
2. When OptimizerSetOption( "paramname", value ) AFL function is called

Return codes:
1 - OK (set successful)
0 - option does not exist
-1 - wrong type, number expected
-2 - wrong type, string expected

2.4 OptimizerRun function

PLUGINAPI int OptimizerRun( struct OptimizeParams *pParams, double (*pfEvaluateFunc)( void * ), void *pContext )


This function is called multiple times during main optimization loop

There are two basic modes of operations
1. Simple Mode
2. Advanced Mode

In simple optimization mode, AmiBroker calls OptimizerRun before running backtest internally. Inside OptimizationRun the plugin should simply set current values of parameters and return 1 as long as backtest using given parameter set should be performed. AmiBroker internally will
do the remaining job. By default the OptimizerRun will be called pParams->NumSteps times.
In this mode you don't use pfEvaluateFunc argument.

See Monte Carlo (MOCASample) sample optimizer for coding example using simple mode.


In advanced optimization mode, you can trigger multiple "objective function" evaluations during single OptimizerRun call.
There are many algorithms (mostly "evolutionary" ones) that perform optimization by doing multiple runs, with each run consisting of multiple "objective function"/"fitness" evaluations. To allow interfacing such algorithms with AmiBroker's optimizer infrastructure the advanced mode provides access to pfEvaluateFunc pointer that call evaluation function.

In order to properly evaluate objective function you need to call it the following way:

pfEvaluateFunc( pContext );

Passing the pContext pointer is absolutely necessary as it holds internal state of AmiBroker optimizer. The function will crash if you fail to pass the context.

The following things happen inside AmiBroker when you call evaluation function:
a) the backtest with current parameter set (stored in pParams) is performed
b) step counter gets incremented (pParams->Step)
c) progress window gets updated
d) selected optimization target value is calculated and stored in pParams->TargetCurrent and returned as a result of pfEvaluateFunc

Once you call pfEvaluateFunc() from your plugin, AmiBroker will know that you are using advanced mode, and will NOT perform extra backtest after returning from OptimizerRun

By default AmiBroker will continue to call OptimizerRun as long as pParams->Step reaches pParams->NumSteps. You can overwrite this behaviour by returning value other than 1. See Standard Particle Swarm Optimizer (PSOSample) for coding example using advanced mode.

Return values:
0 - terminate optimization
1 (default value) - optimization should continue until reaching defined number of steps
2 - continue optimization loop regardless of step counter

2.5 OptimizerFinalize function

PLUGINAPI int OptimizerFinalize( struct OptimizeParams *pParams )

This function gets called when AmiBroker has completed the optimization. The optimization engine should use this point to release internal
data structures.

Return values:
1 - finalization complete and OK
0 - finalization failed