AmiBroker Development Kit

ADK Revision 1.10. 20 November, 2002

Copyright (C)2001, 2002 Tomasz Janeczko, AmiBroker.com.

All files included in ADK archive are intended only for the use of AmiBroker registered users. The files may contain information that is confidential and privileged.
ANY DISSEMINATION, DISTRIBUTION OR COPYING OF THESE FILES WITHOUT PRIOR CONSENT OF AMIBROKER.COM IS PROHIBITED.

1 BASICS

1.1 INTRODUCTION

AmiBroker plug-in DLLs are regular Win32 dynamic link libraries. Currently two types of plugins are supported by AmiBroker plugin interface:

The AFL plugins can expose unlimited number of functions to the AFL engine. The functions provided by the plugin are integrated tightly with AFL engine so there is no difference in performance or functionality between built-in functions and the ones provided by the plug-in.

Data plugins allow to link any external data source. The interface allows to use fast, local data sources like file-based databases (Metastock files, ASCII files, FastTrack, Quotes Plus) as well as internet-based feeds (eSignal, myTrack, QuoteTracker). Both end-of-day and intraday modes are supported. Data plugins also support various notifications so plugin can notify AmiBroker that new data just arrived and AmiBroker can notify the plugin about settings change and/or user action.

The plug-ins can be created in any language that meets the following requirements:

Visual C++ 6.0 was used to create all source code examples in the AmiBroker Development Kit. However you can use other development platform and/or language. To demonstrate this AFL plugin examples include also project (.dev) files for Bloodshed DevC++ package (http://www.bloodshed.net). DevC++ is a FREE development package that can be used to build plugins for AmiBroker. It produces slightly larger binaries than VisualC++ but other than that it is fully compatible. To compile AFL samples just install DevC++ package and then double click on supplied .dev file, then choose Execute->Compile from DevC++ IDE.

Note to Delphi users: Delphi does not support returning 8 byte structures by register so this is an obstacle in developing plugins in Delphi. This could be solved by little stub assembly code. Please contact me if you are interested adk@amibroker.com

1.2 INTERFACE ARCHITECTURE

Plugin interface is designed to be as simple as possible. AFL plugins need to export just 5 functions to be fully functional. The simplest data plugin needs only 4 functions. The full definition of interface is included in Plugin.h header file and below you will find all necessary information to get going with the development.

1.2.1 Background

To provide maximum flexibility AmiBrokers plug-in interface must provide the methods for two-way communications between AmiBroker and plugin. AmiBroker as the process that loaded the DLL can call the functions exported by the DLL but also the DLL has to have a way to call back AmiBroker functions. For this purpose AmiBroker provides a "site interface" which is a structure containing function pointers that can be used to call back internal AmiBroker code. Data plugins have also ability to send messages to AmiBroker main window notifying about the updates.

Each plugin DLL must export at least one function called GetPluginInfo(). DLLs without this function are ignored by AmiBroker. GetPlugin info function provides basic information about the plugin needed by AmiBroker to access all remaining functions.

When AmiBroker starts (or when a "Load" button is pressed in the Plugins window) the following things happen:

After this step DLL is loaded but waits for the second initialization phase.

For AFL plugins this second initialization phase happens when AFL engine starts for a very first time initializing its function table. Then AmiBroker performs the following operations:

Note that this is done only once after loading DLLs in the newer versions of AmiBroker (4.10 and up).

For Data plugins the second initialization phase happens when given data source is selected for the very first time in current AmiBroker session. Then AmiBroker just calls Init() function from the plugin that should be used for initializing working variables/allocating extra memory if necessary. No other function is called for data plugins at this time.

After such initialization process the plugin is ready to be used. Next actions depend on type of the plugin.

For AFL plugins if any external function call is included in the formula being parsed by AFL engine, AmiBroker finds appropriate pointer to the function in its dispatch table and calls either internal code or the code found in one of the plug-in DLLs.

For Data plugins AmiBroker may call different functions descibed in Data plugin section of this document.

The final stage is performed when AmiBroker exits:

2 AFL PLUGINS

2.1 FEATURES

AFL plug-in DLLs have the following features:

2.2 INTERFACE DEFINITION

2.2.1 Data types

One of the most important structures is AmiVar structure. It is used for holding different types of values. This single structure can hold floating point number, the array of floating point numbers, a string or IDispatch interface pointer. Each AFL function receives its arguments as an array of AmiVar values. The AmiVar structure looks like this:

	typedef struct AmiVar
	{
   		int type;
   		union 
   		{
   			float val;
   			float *array;
   			char *string;
   			void *disp;
   		};
	} AmiVar;

The first member of the structure - type - holds the information about data type which can be one of the following:

	// the list of AmiVar types
   	enum { VAR_NONE, VAR_FLOAT, VAR_ARRAY, VAR_STRING, VAR_DISPATCH };
   

VAR_NONE represents the AmiVar that does not have any value. If type is VAR_FLOAT it means that val member is valid and hold the floating point number. If type equals VAR_ARRAY it means that array member is valid and points to the array of floating point values (the size of the array is the same for all arrays during single function call and can be obtained from site interface - which will be described later). If type equals to VAR_STRING it means that string member is valid and points to null-terminated C-style character string. The VAR_DISPATCH type is provided for calling COM objects and will not be covered here.

Proper usage of AmiVar structure looks like this:

	AmiVar myvar;
	myvar.type = VAR_FLOAT; // set the type
	myvar.val = 10.5f; // assign floating point number

Please note that assigning arrays and strings usually require allocating memory. These allocations must be performed using special allocator functions provided by the site interface in order to enable AmiBroker to track these allocations and free the memory when it is no longer needed.

The next important structure is the SiteInterface:

struct SiteInterface 
{
int nStructSize;
int (*GetArraySize) (void);
float * (*GetStockArray)( int nType );
AmiVar (*GetVariable) ( const char *pszName );
void (*SetVariable) ( const char *pszName, AmiVar newValue );
AmiVar (*CallFunction) ( const char *szName, int nNumArgs, AmiVar *ArgsTable );
AmiVar (*AllocArrayResult) (void);
void * (*Alloc) (unsigned int nSize);
void (*Free) (void *pMemory);
};

The site interface is provided to the DLL to enable calling back AmiBroker's internal routines from the plug in side. The site interface provides a set of function pointers that

The pointer to the site interface is set during SetSiteInterface() function call during second stage of the initialization process (described above). It happens before call to Init() so you can use all these pointers even in the Init() function.

A detailed description on how to use the functions provided by the site interface is given later on in this document.

Also important is a function descriptor structure - FunDesc:

// FunDesc structure
// holds the pointer to actual user-defined function
// that can be called by AmiBroker.
// It holds also the number of array, string, float and default arguments
// for the function and the default values
//
typedef struct FunDesc
{
AmiVar (*Function)( int NumArgs, AmiVar *ArgsTable );
UBYTE ArrayQty; // number of Array arguments required
UBYTE StringQty; // number of String arguments required
SBYTE FloatQty; // number of float args
UBYTE DefaultQty; // number of default float args
float *DefaultValues; // the pointer to defaults table
} FunDesc;

This structure is used to create a function table that is retrieved by GetFunctionTable. Each entry of this table contains a function name (visible in AFL) and the function descriptor structure shown above. The function descriptor contains a function pointer (Function member) which is used to call actual function when it is referenced from AFL formula. It works in the following way:

In addition to the function pointer, the numbers of arguments of different types are also stored in FunDesc structure for checking argument count and types at run time. Note that AFL allows unlimited number of arguments but the order is fixed: first come the array arguments, then string arguments, then numeric arguments with no defaults and at the end - numeric arguments with default values. The number of arguments of each kind is defined in ArrayQty, StringQty, FloatQty, DefaultQty members respectively. The pointer to the array of default values is stored in DefaultValues member (this can be NULL if DefaultQty is zero

2.2.2 Interface functions

A valid AmiBroker AFL plug-in DLL must export the following functions:

PLUGINAPI int GetPluginInfo( struct PluginInfo *pInfo );

PLUGINAPI int Init(void);
PLUGINAPI int Release(void);

PLUGINAPI int GetFunctionTable( FunctionTag **ppFunctionTable );
PLUGINAPI int SetSiteInterface( struct SiteInterface *pInterface );

The GetPluginInfo() function is used for obtaining the information about the plugin (the name, vendor name, type, min allowed AmiBroker version) - you should provide accurate information in your DLL for easy identification of your plugin in the "Plugins" window in AmiBroker.

Init() and Release() functions are provided to allow extra memory allocation/other resource initialization in the DLL.

SetSiteInterface() function is called by AmiBroker to set the pointer to the SiteInterface structure in your DLL. Using this pointer you can call back various internal AmiBroker functions.

GetFunctionTable() function is called by AmiBroker to retrieve the table of function names/descriptors describing the AFL functions exposed by your DLL.

2.3 CREATING YOUR OWN AFL PLUGIN DLL

Creating your own plug-in DLL is quite simple. If you are using Visual C++ 6 you should do the following:

  1. Choose File->New from the menu.
  2. From the list of available projects choose "Win32 Dynamic-Link Library" and type the project name, for example "MyPlugin", then click "OK"
  3. In the page "Win32 Dynamic-Link Library - Step 1 of 1" choose "A simple DLL project" - this will create a project file and three source code files - MyPlugin.cpp, StdAfx.h, StdAfx.cpp
  4. Now copy "Plugin.cpp", "Plugin.h" and "Functions.cpp" files from the Sample plugin DLL source code folder to your project folder
  5. Choose Project->Add to project->Files... menu. From the file dialog please choose "Plugin.cpp", "Plugin.h" and "Functions.cpp" files and click OK. Now these files are added to the project and you can build it.

After these steps you have functional copy of a Sample project with your own name (MyPlugin). From now you can modify project files.

The only file you really need to modify is "Functions.cpp" file that actually implements the functions that your plug in will expose to AFL. You should leave "Plugin.cpp" and "Plugin.h" files untouched (with one exception). The exception is that you should modify your plugin name, vendor and version information defined in lines 23-25 of Plugin.cpp:

#define PLUGIN_NAME "MyPlugin - enter here real name of the plugin"
#define VENDOR_NAME "Your name"
#define PLUGIN_VERSION 010000

The information defined here is displayed by the AmiBroker in the Plugins window so it is important to give the user correct information. Please do not forget to do that.

2.4 IMPLEMENTING YOUR OWN AFL FUNCTIONS

Now the only work which is left to do is to implement your functions in "Functions.cpp" file. It is quite good idea to use already written code supplied with a Sample DLL as a starting point for your modifications.

2.4.1 Defining function table

Function exposed by your plug-in must be listed in the functions table that is retrieved during plug-in intialization using GetFunctionTable call. The function table looks like this:

// Each entry of the table must contain:
// "Function name", { FunctionPtr, <no. of array args>, <no. of string args>, <no. of float args>, <no. of default args>, <pointer to default values table float *>
FunctionTag gFunctionTable[] = { "ExampleMACD", { VExampleMACD, 0, 0, 0, 0, NULL }, "ExampleMA", { VExampleMA, 1, 0, 1, 0, NULL }, "ExampleEMA", { VExampleMA, 1, 0, 1, 0, NULL } };

Each entry in this table contains a string that defines the name of the function as seen by AFL engine and the FunDesc structure that defines pointer to the function itself and the arguments required by the function:

"NameOfYourFunction", { ptrToYourFunction, num_of_array_args, num_of_string_args, num_of_float_args, num_of_default_args, ptr_to_default_values },

AFL engine uses this information during parsing of your AFL formula to check if the function with given name exists, to check, parse and cast arguments to appropriate types and finally to call the function.

If you want to create a function that accepts 1 array and 1 float argument with no default value the function table entry will look like this:

FunctionTag gFunctionTable[] = { 
"MyFunction", { MyFunction, 1, 0, 1, 0, NULL },
...

On the other hand if you want to have a default value of 15 for float argument you would need to write:

float myfunction_defaults[] = { 15.0f };
FunctionTag gFunctionTable[] = { 
   "MyFunction", { MyFunction, 1, 0, 0, 1, myfunction_defaults },
   ...

2.4.2 Defining functions

Every function exposed by your plugin must have the following prototype:

AmiVar MyFunction( int NumArgs, AmiVar *ArgsTable )

It means that it gets the pointer to the arguments table (*ArgsTable), the number of elements in this array (NumArgs) and returns the AmiVar value.

In case of functions that don't need any argument NumArgs is zero and ArgsTable has no allocated elements.

In our example MyFunction will multiply elements of the array (first argument) by the numeric value given in second argument.

First we will write the beginning of our function:

AmiVar MyFunction( int NumArgs, AmiVar *ArgsTable )
{
  	int i;
   	AmiVar result;
   	result = gSite.AllocArrayResult();
int nSize = gSite.GetArraySize();

As you can see after standard prototype we define the counter variable (i) and the variable that will hold the result of our function (of AmiVar type). Since our function returns an array we need to allocate the memory for its elements (for float return values it is of course not needed, but it *is* needed if you want to return strings). Allocation of the array is easy by calling AllocArrayResult from site interface. You may also use simply Alloc() function from site interface, but this function requires byte size of memory to be allocated so it is more useful to allocate strings ( for example: gSite.Alloc( strlen( string ) + 1 ) ). At the end of this block we retrieve the size of the arrays used by AFL engine using GetArraySize function of site interface.

Please note that we could write also:

	int nSize = gSite.GetArraySize;
result.type = VAR_ARRAY;
result.array = gSite.Alloc( sizeof( float ) * nSize );

but it is longer than using AllocArrayResult().

Now it is the time for main loop in which we will calculate the values of the resulting array

 for( i = 0; i < nSize; i++ )
 {
    result.array[ i ] = ArgsTable[ 0 ].array[ i ] * ArgsTable[ 1 ].val;
 }
   

In this loop we simply multiply each element of the array stored in the first argument by the numeric value stored in the second argument. In function implementation we don't need to check argument types - once we defined them in the function table - AFL engine takes care about type checking and implict conversions, so we can be sure that ArgsTable[ 0 ] holds the array (therefore array member of the union is valid) and ArgsTable[ 1 ] holds floating point value (therefore val member of the union is valid).

Now the only thing left is to return the result from the function:

	return result;
}

2.4.3 Calling internal AmiBroker functions

You can call internal AmiBroker function using CallFunction() method of site interface. To do so you should prepare argument table first. Argument table should define all parameters needed by the function you are calling (even the default ones).

	AmiVar args[ 2 ];
	args[ 0 ].type = VAR_FLOAT;
	args[ 0 ].val = 12;
	args[ 1 ].type = VAR_FLOAT;
	args[ 1 ].val = 26;
	gSite.CallFunction("macd", 2, args );

2.4.4 Reading and writing AFL variables

You can read (get) and write (set) the contents of any AFL variable using GetVariable/SetVariable methods of site interface.

To read variable just call:

	AmiVar value = gSite.GetVariable( "buyprice" );
	// value.array holds buy price (array of floats)

To store numeric value into variable use:

	AmiVar myvar;
	myvar.type = VAR_FLOAT;

	myvar.val = 7;

	gSite.SetVariable("myownvariable", myvar );

The following example illustrates how to set string variable (we get current time and format it to string then call SetVariable):

	time_t ltime;
	time( &ltime ); // we get current time
AmiVar myvar; myvar.type = VAR_STRING; myvar.string = gSite.Alloc( 100 ); // allocate memory for string sprintf( myvar.string, "The time is %s", ctime( &ltime ) ); // print to allocated buffer gSite.SetVariable("currenttime", myvar );


Third example shows how to set array variable:

	AmiVar myvar = gSite.AllocArrayResult();

	int nSize = gSite.GetArraySize();
	for( int i = 0; i < nSize; i++ )
	{
		myvar.array[ i ] = sin( 0.1 * i );
	}


	gSite.SetVariable("sinetable", myvar );

Please note that ability to set AFL variables from the plugin level allows you to return ANY number of results from your function. Simply call SetVariable as many times you wish inside your function and you will be able to get the values of all those variables from AFL side.

3 DATA PLUGINS

3.1 FEATURES

Data plug-in DLLs have the following features:

3.2 INTERFACE DEFINITION

3.2.1 Data types

In addition to already descibed AmiVar structure, data plugin may use the following data types:

struct PackDate {
                        unsigned int Tick : 4;      // 0..11 - Tick is 5 seconds   15 is reserved as EOD marker
                        unsigned int Minute : 6;    // 0..59                       63 is reserved as EOD marker
                        unsigned int Hour : 5;      // 0..23                       31 is reserved as EOD marker
                        unsigned int Day : 5;       // 1..31
                        unsigned int Month : 4;     // 1..12
                        unsigned int Year : 8;  // 0..255
                                                // year is stored with as number ( real_year - 1900 )
                                                // so effective range is 1900...2155
                    };


union AmiDate
{
						unsigned int	Date;
						struct PackDate PackDate;
};


AmiDate is a structure that AmiBroker uses to store date and time of quotation. To conserve memory, its date/time fields are bit-packed so they span only 32 bits (the same as long integer). Supported date range is Jan 1st, 1900 upto and including 31 Dec, 2155. Time is stored in 5-second resolution which is sufficient except for tick charts. In tick mode AmiBroker uses multiple records having the same AmiDate value. Additional time / tick information is then stored in the Flags and PrecentReduc fields of Quotation structure. End-of-day records are marked with 15 in Tick field, 63 in Minute field and 31 in hour field. This is equivalent to 0x7FFF bitmask. Years are stored with -1900 offset so 0 in Year field represents 1900 and 255 represents year 2155.

struct Quotation {
						union	AmiDate DateTime;
                        float   Price;
                        float   Open;
                        float   High;
                        float   Low;
                        int     Volume;
                        int     OpenInterest;
                        UBYTE   Flags;				// 
                        UBYTE   PercentReduc;		// in tick mode this field stores tick ID within 5 sec period
                 };


Quotation structure holds single bar data. DateTime field holds bar date and time and is an AmiDate structure equivalent to unsigned int (32 bit). Price field is actualy Close price. The Open, High, Low fields are self-explanatory, these are single precision floating point numbers. Please note that Volume and OpenInterest fields are currently integers. Flags and PercentReduc fields are used for storing extra information. You should set both fields to zero unless in tick mode, where you should store increasing tick number modulo 255 in PrecentReduc field.


struct StockInfo {
                        char    Reserved3[14];    
                        char    FullName[64];
                        char    Address[128];
						char	Country[40];
						char	Currency[8];
						char	AliasName[16];
                        char    ShortName[16];
                        char    Reserved5[10];		/* in the future will be used to store longer ticker symbols */
                        int     Flags;          /* MarketID etc.*/      
                        int     Code;
                        int     StockQty;
                        int     NomValue;
                        int     BookValue;
                        int     Inventories;         /* UNUSED YET */
                        int     TotalCurrentAssets;  /* UNUSED YET */
                        int     TotalGrossAssets;    /* UNUSED YET */
                        int     CurrentLiabilities;  /* UNUSED YET */
                        int     LongTermDebt;        /* UNUSED YET */
                        int		ReservedB;
						int		IndustryID;
                        int     MoreFlags;          /* 0x80000000 - values in thousands */
                        int     Year[4];
                        int     SalesIncome[4];     /* per quarter */
                        int     EBT[4];             /* earnings before tax per quarter */
                        int     EAT[4];             /* earnings after tax per quarter */
						char	Reserved4[14];
						int		DataSource;			/* the ID of the data plug-in, 0 - accept workspace settings */
                        int     DataLocalMode;		/* local mode of operation - 0 - accept workspace settings, 1 - store locally, 2 - don't store locally */
                        int     Risk;
                        int     Yield;
                        int     Dividend;
						int		WatchListBits;		/* 32 watch lists */
						int		MoreWatchListBits;	/* the place for additional 32 watch lists */
			};


StockInfo structure holds basic symbol information like full name (FullName field), ticker symbol (ShortName field) and a number of others. This structure is returned by AddStock method of InfoSite structure and allows you to set-up initial values for symbol that was added using AddStock call. See Quotes Plus plugin source code for example usage.

struct InfoSite
			{
				int			nStructSize;
				int			(*GetStockQty)( void );
				struct StockInfo * (*AddStock)( const char *pszTicker );
				int			(*SetCategoryName)( int nCategory, int nItem, const char *pszName );
				const char *(*GetCategoryName)( int nCategory, int nItem );
			    int			(*SetIndustrySector) ( int nIndustry, int nSector );
				int			(*GetIndustrySector) ( int nIndustry );
			};

InfoSite structure is a similar concept to SiteInterface found in AFL plugins. It provides function pointers that allow to call-back AmiBroker internal functions and perform various operations on AmiBroker stock database. See Quotes Plus plugin source code for example usage.

struct RecentInfo
{
	int		nStructSize;
	char	Name[ 64 ];
	char	Exchange[8];
	int		nStatus;
	int		nBitmap;	// describes which fields are valid
	float	fOpen;
	float	fHigh;
	float	fLow;
	float	fLast;
	int		iTradeVol;
	int		iTotalVol;
	float	fOpenInt;
	float	fChange;
	float	fPrev;
	float	fBid;
	int		iBidSize;
	float	fAsk;
	int		iAskSize;
	float	fEPS;
	float	fDividend;
	float	fDivYield;
	int		nShares; // shares outstanding
	float	f52WeekHigh;
	int		n52WeekHighDate;
	float	f52WeekLow;
	int		n52WeekLowDate;
	int		nDateChange; // format YYYYMMDD
	int		nTimeChange; // format HHMMSS
	int		nDateUpdate; // format YYYYMMDD
	int		nTimeUpdate; // format HHMMSS
};

RecentInfo structure is used by real-time plugins to store information about most recent streaming update (last bid/ask/trade and a number of other data). Name is a full name of symbol. There is no ticker symbol in this structure because it is specified by argument passed separately. Please note that if given data source does not provide all these data then not all those fields have to be valid. To mark which fields are valid you should store combination of RI_*** bit flags defined in Plugin.h into nBitmap field. For example storing RI_LAST | RI_OPEN into nBitmap field will mean that fLast and fOpen fields contain valid values. nDateChange and nTimeChange fields provide the information about date/time of price fields. For example you may start AmiBroker on Sunday and these fields will show Friday's date/time because last price change was on Friday. nDateUpdate and nTimeUpdate fields store the date/time of last update of any field. In most cases this is current minute/second. So in previous example it would be the Sunday date/time. It is very important to update nDateUpdate and nTimeUpdate fields each time you update any value in this structure otherwise real-time quote window will not work properly.

RecentInfo structure is used in two cases: (1) as a return value of GetRecentInfo() function which is called by AmiBroker's real-time quote window on each display refresh, and (2) passed in LPARAM in WM_USER_STREAMING_UPDATE message sent by plugin to AmiBroker. See QuoteTracker plugin source code for example usage.

struct PluginStatus
{
	int			nStructSize;
	int			nStatusCode;
	COLORREF	clrStatusColor;
	char		szLongMessage[ 256 ];
	char		szShortMessage[ 32 ];
};

Plugin status structure is returned by GetStatus function and provides both numeric and text status information. Text information is provided in long and short version. Short version is displayed in the plugin status area of AmiBroker status bar, long version is displayed in the tooltip. The highest nibble (4-bit part) of nStatus code represents type of status: 0 - OK, 1 - WARNING, 2 - MINOR ERROR, 3 - SEVERE ERROR that translate to color of status area: 0 - green, 1 - yellow, 2 - red, 3 - violet. See QuoteTracker plugin source for sample usage.


struct _Workspace {
	int		DataSource;	 /* 0 - use preferences, 1 - local, ID of plugin */
	int		DataLocalMode;	/* 0 - use preferences, 1 - store locally, 2 - don't */
	int		NumBars;
	int		TimeBase;
	int		TimeShift; // in hours
	BOOL	FilterAfterHours; //
	int		SessionStart; // bit encoding HHHHH.MMMMMM.0000	 hours << 10 | ( minutes << 4 ) 
	int		SessionEnd;	  // 			  //
	BOOL	FilterWeekends;
};


struct PluginNotification
{
	int		nStructSize;
	int		nReason;
	LPCTSTR pszDatabasePath;
	HWND	hMainWnd;
	struct  StockInfo	*pCurrentSI;
	struct  _Workspace	*pWorkspace;
};


PluginNotification structure is filled up by AmiBroker and passed to plugin as an argument of Notify() call. nReason field describes the "reason" of notification, that could be the fact that database is loaded, unloaded, settings are changed or user has clicked with right mouse button over plugin status area of AmiBroker status bar. This last value (REASON_STATUS_RMBCLICK) is used to display context menu that can offer some choices to the user like "Connect", "Diconnect", etc. Possible values are defined in Plugin.h file. See QuoteTracker plugin source for sample usage.

 

3.2.2 Interface functions

A valid AmiBroker data plug-in DLL must export the following functions:

PLUGINAPI int GetPluginInfo( struct PluginInfo *pInfo );
PLUGINAPI int Init(void);
PLUGINAPI int Release(void);
PLUGINAPI int GetQuotes( LPCTSTR pszTicker, int nPeriodicity, int nLastValid, int nSize, struct Quotation *pQuotes );
   

The GetPluginInfo() function is used for obtaining the information about the plugin (the name, vendor name, type, plugin ID, min allowed AmiBroker version) - you should provide accurate information in your DLL for easy identification of your plugin in the "Plugins" window in AmiBroker.

Init() and Release() functions are provided to allow extra memory allocation/other resource initialization in the DLL.

GetQuotes() function is a basic function that all data plugins must export and it is called each time AmiBroker wants to get new quotes. The main idea behind GetQuotes function is very simple: ticker symbol, bar interval and pre-allocated quotation array of nSize elements are passed as arguments to this function. GetQuotes() should simply fill the array with the quotes of given symbol and given interval. It is that simple. Detailed implementations vary depending on underlying data source (detailed description later in this document).

Functions listed below are optional for the data plugin and they may be exported/defined only when additional functionality is needed:

PLUGINAPI AmiVar GetExtraData( LPCTSTR pszTicker, LPCTSTR pszName, int nArraySize, int nPeriodicity, void* (*pfAlloc)(unsigned int nSize) );
PLUGINAPI int Configure( LPCTSTR pszPath, struct InfoSite *pSite );
PLUGINAPI int SetTimeBase( int nTimeBase );
PLUGINAPI int Notify( struct PluginNotification *pNotifyData );
PLUGINAPI int GetPluginStatus( struct PluginStatus *status );

GetExtraData() is used for retrieving non-quotation data. It is called when the GetExtraData() AFL function is used in the formula. pfAlloc parameter is a pointer to AFL memory allocator that you should use to allocate memory so it can be later freed by AmiBroker itself. Source code for QuotesPlus plugin shows sample implementation of GetExtraData() that retrieves fundamental data.

Configure() function is called when user presses "Configure" button in AmiBroker's File->Database Settings window. The path to the database and InfoSite structure are passed as arguments. This allows the plugin to display its own dialog box that provides plugin-specific settings and allows to store the settings either in registry (global settings) or in the file stored in database path (local per-database settings). InfoSite allows the plugin to automatically setup AmiBroker's database out of information retrieved from external data source. The source code for QuotesPlus plugin shows real-life example (getting symbols and setting up entire sector/industry tree). If Configure() function is not exported by the plugin AmiBroker displays message that "plugin does not require further configuration" when "Configure" button is clicked.

SetTimeBase() function is called when user is changing base time interval in File->Database Settings window. It is not required for plugins that handle only daily (EOD) interval. It has to be defined for all intraday plugins. The function takes bar interval in seconds (60 for 1-minute bars, 86400 for daily bars) and should return 1 if given interval is supported by the plugin and 0 if it is not supported.

Notify() function is called when database is loaded, unloaded, settings are changed, or right mouse button in the plugin status area is clicked. It supersedes SetDatabasePath (which is now obsolete) that was called only when database was loaded. Notify() function is optional however it is implemented in almost all plugins because it is a good place to initialize/deinitialize plugin state. For example implementation of Notify() function please check QuoteTracker plugin source.

GetPluginStatus() function is optional and used mostly by real-time plugins to provide visual feedback on current plugin status. It provides a way to display status information in the AmiBroker's status bar. For example implementation of GetPluginStatus() function please check QuoteTracker plugin source.

The following two functions are implemented only by real-time plugins:

PLUGINAPI struct RecentInfo * GetRecentInfo( LPCTSTR pszTicker ); // RT plugins    only 
PLUGINAPI int GetSymbolLimit( void ); // RT plugins only

GetRecentInfo() function is exported only by real-time plugins and provides the information about the most recent trade, bid/ask, days high/low, etc (updated by streaming data). It is called by AmiBroker's real-time quote window on each window refresh (occurs several times a second). The function takes ticker symbol and returns the pointer to RecentInfo structure described above.

GetSymbolLimit() function is exported only by real-time plugins. It returns the maximum number of streaming symbols that plugin and/or external data source can handle. The result of this function is used to limit the number of symbols that are displayed in real-time quote window.

3.3 CREATING YOUR OWN DATA PLUGIN DLL

Creating your own plug-in DLL is quite simple. If you are using Visual C++ 6 you should do the following:

  1. Choose File->New from the menu.
  2. From the list of available projects choose "Win32 Dynamic-Link Library" and type the project name, for example "MyPlugin", then click "OK"
  3. In the page "Win32 Dynamic-Link Library - Step 1 of 1" choose "A simple DLL project" - this will create a project file and three source code files - MyPlugin.cpp, StdAfx.h, StdAfx.cpp
  4. Now copy "Plugin.cpp", "Plugin.h" files from the Data_Template plugin DLL source code folder to your project folder
  5. Choose Project->Add to project->Files... menu. From the file dialog please choose "Plugin.cpp", "Plugin.h" files and click OK. Now these files are added to the project and you can build it.

After these steps you have functional copy of a Sample project with your own name (MyPlugin). From now you can modify project files.

The only file you really need to modify is "Plugin.cpp" file that actually implements the functions that your plug in will expose to AFL.

You have to modify your plugin name, vendor and version information and plugin ID code defined in lines 23-26 of Plugin.cpp:

#define PLUGIN_NAME "MyPlugin - enter here real name of the plugin"
#define VENDOR_NAME "Your name"
#define PLUGIN_VERSION 010000
#define PLUGIN_ID PIDCODE( 'T', 'E', 'S', 'T')

The information defined here is displayed by the AmiBroker in the Plugins window so it is important to give the user correct information. Please do not forget to do that.

It is EXTREMELY IMPORTANT to use PLUGIN_ID to uniquely identifies your data source. AmiBroker uses the plugin ID to distinguish between data sources. For testing purposes you may use PIDCODE( 'T', 'E', 'S', 'T'), but for release to the public you should contack us at adk@amibroker.com to receive unique plugin identifier for your data plugin. Already reserved plugin IDs are: QTRK, MSTK, eSIG, myTK, TC2K, FTRK, CSI, QCOM, DTNI.

Right after than you should add (if it does not already exist) the following line:

// IMPORTANT: Define plugin type !!!
#define THIS_PLUGIN_TYPE PLUGIN_TYPE_DATA

This defines that the plugin you are writing is data plugin.

3.4 IMPLEMENTING DATA PLUGIN

A very basic data plugin requires just modification of GetQuotes() function supplied with the template. Before we will dig into details a little background is needed.

Each time AmiBroker needs quotes for particular symbol it calls GetQuotes() function. Please note that AmiBroker caches response received from GetQuotes() and will not ask for quotes again for the symbol until: (a) user chooses "Refresh" from View menu, (b) plugin notifies AmiBroker that new data arrived using WM_USER_STREAMING_UPDATE message, (c) old data were removed from the cache. AmiBroker cache maintains the list of most recently accessed symbols and may remove the data of the least recently accessed symbols. The size of AmiBroker's cache is controlled by "in-memory cache" setting in Tools->Preferences->Data.

External data sources could be divided into two categories (a) local databases (b) remote databases. Local databases have all data stored on computer hard disk or CD-ROM and available for immediate retrieval. Remote databases (also known as on-demand data sources) do not store all data locally, instead they retrieve the data on-demand from remote computer (usually via Internet). These two kinds of data sources have to be handled differently.

In the first case (local sources) quotes can be retrieved in synchronous way: we ask for data and block the calling application until data are collected. This is acceptable because data are available locally and can be delivered within few milliseconds. This is the case for all file-based sources like: Metastock, Quotes Plus, TC2000, FastTrack. During GetQuotes() call you should simply read requested number of bars from the data source and fill provided Quotation array.

In the second case (remote sources) quotes have to be retrieved in asynchronous way. When GetQuotes() function is called for the first time, request for new data has to be send to the data source. As data from remote source arrive usually after a few seconds (or more) we can not block calling application (AmiBroker). Instead control should be returned to AmiBroker. Depending on architecture of your data source you should either setup a window or another thread that will wait for the message sent back by the data source when data is ready. When such message is received the plugin should send WM_USER_STREAMING_UPDATE message that will nofity AmiBroker that it should ask for quotes. In the response to this message AmiBroker will call GetQuotes() function again. This time you should fill Quotation array with the data you received from remote source. To avoid repeated requests for historical intraday data, once it is retrieved, real-time plugins begin to collect streaming time/sales data and build-up intraday bars. Each successive GetQuotes() call receives bars that were build-up inside plugin from streaming upates. This mode of operation is used by eSignal and myTrack real-time plugins.

Now we will show the simplest form of GetQuotes() function that will read the quotes from the local ASCII file. We will be reading 1-minute intraday ASCII files.

We assume that data files are stored in 'ASCII' subfolder of AmiBroker directory and they have name of <SYMBOL>.AQI and the format of Date (YYMMDD), Time (HHMM), Open, High, Low, Close, Volume (actually these are AQI files produced by AmiQuote) and quotes inside file are sorted in ascending rder (the oldest quote is on the top)

PLUGINAPI int GetQuotes( LPCTSTR pszTicker, int nPeriodicity, int nLastValid, int nSize, struct Quotation *pQuotes  )
{

	// we assume that intraday data files are stored in ASCII subfolder
	// of AmiBroker directory and they have name of .AQI
	// and the format of Date(YYMMDD),Time(HHMM),Open,High,Low,Close,Volume
	// and quotes are sorted in ascending order - oldest quote is on the top

	char filename[ 256 ];
	FILE *fh;
	int  iLines = 0;
	
	// format path to the file (we are using relative path)
	sprintf( filename, "ASCII\\%s.AQI", pszTicker );

	// open file for reading
	fh = fopen( filename, "r" );

	// if file is successfully opened read it and fill quotation array
	if( fh )
	{
		char line[ 256 ];

		// read the line of text until the end of text
		// but not more than array size provided by AmiBroker
		while( fgets( line, sizeof( line ), fh ) && iLines < nSize )
		{
			// get array entry
			struct Quotation *qt = &pQuotes[ iLines ];
			
			// parse line contents: divide tokens separated by comma (strtok) and interpret values
			
			// date	and time first
			int datenum = atoi( strtok( line, "," ) );	// YYMMDD
			int timenum = atoi( strtok( NULL, "," ) );	// HHMM

			// unpack datenum and timenum and store date/time 
			qt->DateTime.PackDate.Tick = 0;
			qt->DateTime.PackDate.Minute = timenum % 100;
			qt->DateTime.PackDate.Hour = timenum / 100;
			qt->DateTime.PackDate.Year = 100 + datenum / 10000;
			qt->DateTime.PackDate.Month = ( datenum / 100 ) % 100;
			qt->DateTime.PackDate.Day = datenum % 100;

			// now OHLC price fields
			qt->Open = (float) atof( strtok( NULL, "," ) );
			qt->High = (float) atof( strtok( NULL, "," ) );
			qt->Low  = (float) atof( strtok( NULL, "," ) );
			qt->Price = (float) atof( strtok( NULL, "," ) ); // close price

			// ... and Volume
			qt->Volume = atoi( strtok( NULL, ",\n" ) );

			iLines++;
		}

		// close the file once we are done
		fclose( fh );

	}

	// return number of lines read which is equal to
	// number of quotes
	return iLines;	 
}

For simplicity the example does not do any serious error checking.

A few comments about arguments of GetQuotes() function. First argument pszTicker is a null-terminated ticker symbol, nPeriodicity is bar interval (in seconds). Third parameter nLastValid requires some more description. When AmiBroker calls GetQuotes() function it may already have some data for given symbol (stored for example in its own files when local data storage is ON). However, before GetQuotes() is called AmiBroker always allocates quotation array of size defined in File->Database Settings: default number of bars. This size is passed to the plugin as nSize argument. If AmiBroker has already some data for given symbol it fills initial elements of quotation array passed to GetQuotes() function. The index of last filled element of quotation array is passed as nLastValid argument. This allows to update just a few new bars without the need to fill entire array inside the plugin. This technique is used for example in QuotesPlus plugin that does not update entire array if it finds that last valid quote is the same as last quote available from Quotes Plus database. The last argument is pQuotes array which is array of Quotation structures. The array is allocated by AmiBroker itself and it has the size of nSize elements.

4 SUPPLIED EXAMPLES

In the ADK archive you will find the following examples of plug in DLLs:

Sample AFL plugins:

Sample Data plugins:

Note that pre-build ready to use DLLs are located in Plugins subfolder of this ADK archive.