We write the DLL for Metastock from scratch. Part 1

Metastock is probably the most famous program for the technical analysis of the market. This program can connect external DLLs written by users to create their own trading strategies, using the full power of traditional programming languages such as C or Pascal.
Having undertaken a search on the Internet, I was surprised to find a complete lack of information on this topic. The only article that turned out to be worthy of attention: “What is the Metastock Developer's Kit?” ( mdk ), which describes a short example on Delphi, you can also download MDK there.
In this article, I will try to fill this gap and describe the process of creating a Metastock library of external functions (MSX DLL) in steps in C / C ++. All examples were written in Visual Studio 2010 ( part 2, part 3 ).
Bit of theory
Functions that are implemented in the MSX DLL behave exactly like the standard Metastock built-in functions. All MSX DLL functions return an array of data. Each external function has a unique name.
ExtFml ("DLL Name.Function Name", arg1, ..., argn) , where
arg1 ... argn are the arguments to the function.
Each argument can be one of four types:
• Arrays of data (for example, Open, High, Low, Close, etc., or the results of another function)
• Numeric constants (for example, 5, -5, 20.55, etc. .)
• String constants (for example, “Hello Woodpecker”, etc.)
• Individual sets (for example, Simple, Triangular, etc.)
We will consider how to define and use them later, with specific examples.
The functions defined in the MSX DLL are divided into two categories:
• Initialization
functions • Calculation functions (or external functions).
Initialization functions are called by MetaStock during startup to determine which external functions are available and what arguments they require. Calculation functions are available for MetaStock users. All functions reference data structures defined in the MSXStruc.h file. This file is required to compile our DLL.
Before writing your own functions, you need to register several MSX service functions (initialization functions) so that Metostok can communicate with your DLL. There are four of them:
• MSXInfo - required function. It is always called during initialization and checks whether our DLL is an MSX DLL and returns basic information about it (authorship, number of our functions, version).
• MSXNthFunction - required function. It is called once for each function specified by MSXInfo and numbers our functions starting from zero. Here are the names of the functions (case sensitive), their descriptor and the number of arguments for each.
• MSXNthArg - This function is required only if our functions have arguments. Called during initialization for each argument of our functions.
• MSXNthCustomString - this function is required only if our functions have custom arguments.
To start the theory, let's start writing the first DLL. This example will show how to display an array of prices, date and time in our indicator.
Writing a code
Open VS 2010. First, create an empty project.
File-> New-> Project-> Other Languages-> Visual C ++ -> Win32-> Win32 Console Application.
Set the name of our library (let UsePrice) and click OK. The Win32 Application Wizard opens.
Next-> and check the DLL and Empty project, click Finish. An empty project has been created. Add three files UsePrice.cpp, UsePrice.def, MSXStruc.h to it . Right-click on the project Add-> New Item ..., select the file with the appropriate extension and give it the corresponding name. Click Add.
In the file UsePrice.cpp we write our code.
/ *
- Step 1 - Headers
in addition to the file in which this directive itself is located.
The name of the source file must be enclosed in double quotation marks or in angle brackets.
Headers for C Library Functions
* /
#include
#include
#include
#include
#include
// Обязательно включить - определяет MSX Data Structures (см. файл MSXStruc.h).
#include "MSXStruc.h"
/ *
- Step 2 - Export
which this identifier will be replaced when it is found in the
program text . The identifier is also called the macro name, and the
substitution process is called macro substitution. The standard form of the directive is
as follows:
#define macro_name sequence of
characters Note that there is no semicolon in this statement. There
can be any number of
spaces between an identifier and a sequence of characters . The macro ends only with a newline.
For example, if you want to use TRUE for a value of 1, and FALSE for 0,
then you can declare the following two macros:
#define TRUE 1
#define FALSE 0
As a result, if the compiler detects TRUE or FALSE in the program text,
it will replace them with 1 and 0, respectively.
In our case, if DLL_EXPORT is found in the code, then the
extern “C” __declspec (dllexport) macro is executed.
To normally conjugate C ++ code with C, where name mangling is absent,
extern “C” is introduced , which disables this mechanism for exported variable / function names .
extern “C” means the use of a simple generation of a function signature
(in C language style) when retrieving object files. In particular, this prohibits the
C ++ compiler to “decorate” the function name with additional
characters when exporting to a DLL.
The dllexport storage class attribute is a Microsoft-specific extension to the
C and C ++ languages. It can be used to export functions, data, and objects
to a DLL.
__declspec (dllexport) declarator
This attribute explicitly defines the DLL interface for its client, which may be an
executable file or another DLL. Declaring functions as
dllexport eliminates the need for a module definition file (DEF), at
least with respect to the specification of exported functions.
* /
#define DLL_EXPORT extern "C" __declspec(dllexport)
/ *
- Step 3 - Initialization Functions
MSX service functions in order for Metostok to communicate with our DLL.
In the first example, we will write one function without arguments, so
we will not need MSXNthArg and MSXNthCustomString.
MSXInfo
Replace Field
authorship information, and also set the number of functions = 1.
strncpy (strDest, strSource, count) - copies the characters of one line to another.
• strDest is the destination string.
• strSource is the source string.
• count - the number of characters to copy.
strncpy requires a required header
On strncpy, the VS C ++ compiler gives warnings, you can use
strncpy_s (I checked that everything works, but I can’t say how correct this is).
* /
DLL_EXPORT BOOL __stdcall MSXInfo (MSXDLLDef *a_psDLLDef)
{
strncpy (a_psDLLDef->szCopyright, "VS 2010 C++ MSX DLL, Copyright (c) Pretzel, 2014", sizeof(a_psDLLDef->szCopyright)-1);
a_psDLLDef->iNFuncs = 1; // число функций.
a_psDLLDef->iVersion = MSX_VERSION; // версия
return MSX_SUCCESS;
}
/ *
MSXNthFunction
The value copied to a_sFuncDef-> szFunctionName must exactly match
our function, which we will export, case sensitive.
Variable values:
• Name - the name of our function.
• Description - how it will be read in Metastock (in the 'Paste Functions' window).
• Arguments - the number of arguments to our function.
On strcpy, the VS C ++ compiler gives warnings, you can use strcpy_s
(I checked that everything works, but I can’t say how correct this is).
* /
DLL_EXPORT BOOL __stdcall MSXNthFunction (int a_iNthFunc, MSXFuncDef *a_psFuncDef)
{
BOOL l_bRtrn = MSX_SUCCESS;
switch (a_iNthFunc)
{
case 0:
strcpy (a_psFuncDef->szFunctionName, "Price");
strcpy (a_psFuncDef->szFunctionDescription, "FirstFunction");
a_psFuncDef->iNArguments = 0; // число аргументов
break;
default:
l_bRtrn = MSX_ERROR;
break;
}
return l_bRtrn;
}
/ *
- Step 4 - Our function
* /
DLL_EXPORT BOOL __stdcall Price(const MSXDataRec *a_psBasic,
const MSXDataInfoRecArgsArray *a_psArrayArgs,
const MSXNumericArgsArray *a_psNumericArgs,
const MSXStringArgsArray *a_psStringArgs,
const MSXCustomArgsArray *a_psCustomArgs,
MSXResultRec *a_psResult)
{
// Выводим в наш индикатор Close
for (int i= a_psBasic ->sClose.iFirstValid; i<= a_psBasic ->sClose.iLastValid; i++)
a_psResult->psResultArray->pfValue[ i ] = a_psBasic ->sClose.pfValue[ i ];
// Заменив предыдущую строку на:
// a_psResult->psResultArray->pfValue[ i ] = float (a_psBasic ->psDate[i].lDate);
// или
// a_psResult->psResultArray->pfValue[ i ] = float (a_psBasic ->psDate[i].lTime);
// соответственно получим дату или время.
return MSX_SUCCESS;
}
Next, in the UsePrice.def file , enter the following code:
LIBRARY UsePrice
EXPORTS
MSXInfo
MSXNthFunction
Price
Fill MSXStruc.h with code.
#ifndef MSX_Structures_h
#define MSX_Structures_h
/*
Structures required for MetaStock External Function DLL interface
*/
#ifndef BOOL
typedef int BOOL;
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
// --------------------------------------------------------------------------
// Return this DLL version constant
// --------------------------------------------------------------------------
const int MSX_VERSION = 1;
// --------------------------------------------------------------------------
// Maximum number of aguments
// --------------------------------------------------------------------------
const int MSX_MAXARGS = 9;
// --------------------------------------------------------------------------
// Maximum string size (does not include MSXString arguments passed in to
// external functions).
// --------------------------------------------------------------------------
const int MSX_MAXSTRING = 100;
// --------------------------------------------------------------------------
// The following two BOOL return values are returned from MSX functions
// --------------------------------------------------------------------------
const BOOL MSX_SUCCESS = FALSE;
const BOOL MSX_ERROR = TRUE;
// ----------------------------------------------------------------------------------------
// There are four potential argument types
// ----------------------------------------------------------------------------------------
const int MSXDataArray = 0;
const int MSXNumeric = 1;
const int MSXString = 2;
const int MSXCustom = 3;
// ----------------------------------------------------------------------------------------
// The following structure is used by the exported function MSXInfo
// ----------------------------------------------------------------------------------------
typedef struct
{
char szCopyright[MSX_MAXSTRING];
int iNFuncs;
int iVersion;
} MSXDLLDef;
// ----------------------------------------------------------------------------------------
// The following structure is used by the exported function MSXNthFunction
// ----------------------------------------------------------------------------------------
typedef struct
{
char szFunctionName[MSX_MAXSTRING];
char szFunctionDescription[MSX_MAXSTRING];
int iNArguments;
} MSXFuncDef;
// ----------------------------------------------------------------------------------------
// The following structure is used by the exported function MSXNthArg
// ----------------------------------------------------------------------------------------
typedef struct
{
int iArgType;
// argtype constants:
// 0 DataArray
// 1 Numeric
// 2 String
// 3 CustomType
char szArgName[MSX_MAXSTRING];
int iNCustomStrings;
} MSXFuncArgDef;
// ----------------------------------------------------------------------------------------
// The following structure is used by the exported function MSXNthCustomString
// ----------------------------------------------------------------------------------------
typedef struct
{
char szString[MSX_MAXSTRING];
int iID;
} MSXFuncCustomString;
// ----------------------------------------------------------------------------------------
// the following datastructures are passed into and out of the user-written external
// calculation functions.
// ----------------------------------------------------------------------------------------
typedef struct
{
long lDate;
long lTime;
} MSXDateTime;
typedef struct
{
float *pfValue;
int iFirstValid;
int iLastValid;
} MSXDataInfoRec;
typedef struct
{
MSXDateTime *psDate;
MSXDataInfoRec sOpen;
MSXDataInfoRec sHigh;
MSXDataInfoRec sLow;
MSXDataInfoRec sClose;
MSXDataInfoRec sVol;
MSXDataInfoRec sOI;
MSXDataInfoRec sInd;
char *pszSecurityName; // Security Name
char *pszSymbol; // Security Symbol
char *pszSecurityPath; // Path where security is stored (may be in UNC format)
char *pszOnlineSource; // Unused - reserved for future use...
int iPeriod; // 'D'aily, 'W'eekly, 'M'onthly, 'Q'uarterly, 'I'ntraday
int iInterval; // For period='I'ntraday only. 0=tick, other value = minutes compression.
int iStartTime; // HHMM format. Undefined for non-intraday period.
int iEndTime; // HHMM format. Undefined for non-intraday period.
int iSymbolType; // Unused - reserved for future use
} MSXDataRec;
typedef struct
{ // possible for MSX_MAXARGS data arrays
MSXDataInfoRec *psDataInfoRecs[MSX_MAXARGS]; // pointers to the data arrays
int iNRecs; // number of arrays present (just a sanity check)
} MSXDataInfoRecArgsArray;
typedef struct
{
float fNumerics[MSX_MAXARGS]; // possible for MSX_MAXARGS numerics
int iNRecs; // also a sanity check - func knows how many there should be.
} MSXNumericArgsArray;
typedef struct
{
char *pszStrings[MSX_MAXARGS]; // possible for MSX_MAXARGS strings
int iNRecs; // ditto the above
} MSXStringArgsArray;
typedef struct
{
int iCustomIDs[MSX_MAXARGS]; // numeric ID associated with a custom arg
int iNRecs; // ditto the above
} MSXCustomArgsArray;
typedef struct
{
MSXDataInfoRec *psResultArray; // Pointer to result array
char szExtendedError[MSX_MAXSTRING]; // Extended Error string
} MSXResultRec;
#endif
In Visual Studio, click Build -> Build Solution (F6) and get our DLL. We send it to the folder 'External Function DLLs' in Metastock'e and you can use it. Our indicator will look like this:
ExtFml (“UsePrice.Price”)
In the next part, we will consider our function, connect the arguments, add exceptions and output the data to the external environment.