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