Back to Home

Writing a DLL for Metastock from scratch. Part 2

Metastock · dll · libraries · Metastock Developer's Kit

Writing a DLL for Metastock from scratch. Part 2

  • Tutorial
DLL for Metastock

This article will examine in detail our function ( part1 , part3 ), the rules for obtaining data from Metastock, processing them and returning the result back to Metastock. This information will help to avoid errors in the MSX DLL.

Our function has the form:
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)

, where
* a_psBasic is a pointer to the MSXDataRec structure (all available data on securities);
* a_psArray - pointer to the MSXDataInfoRecArgsArray structure (array arguments);
* a_psNumeric - pointer to the MSXNumericArgsArray structure (numeric arguments);
* a_psString - pointer to the MSXStringArgsArray structure (string arguments);
* a_psCustom - pointer to the MSXCustomArgsArray structure (Custom arguments);
* a_psResult - pointer to the MSXResultRec structure (the result of data processing returned to Metastock);
* a_psValue - pointer to the MSXDataInfoRec structure (all numerical data used in the calculations).
All structures are described in the MSXStruc.h file .

Data storage and calculations


All numerical data used in the calculation of indicators are stored in structures known as data arrays. Data arrays are used to store price data (for example, Open, High, Low, etc.), numerical constants and indicator calculation results. When MetaStock supplies data about the securities and arguments of the MSX DLL functions, this data is used as arrays. When the function computed by the MSX DLL returns the indicator results to MetaStock, the result is returned as a data array.
Data arrays are implemented in the MSXDataInfoRec structure and have three main components:
• Data elements - data elements.
• First valid index - the starting point of reference (Fvi).
• Last valid index - the end point of reference (Lvi).
Data items are the actual numerical values ​​associated with the data array. These values ​​are stored in the array as float values. The first and last valid index are used to determine which data items contain valid values. All data elements between the first and last valid index (inclusive) contain valid data. All elements outside this range have undefined values ​​and should be ignored for all calculations.
The data array is considered " empty " if Fvi Or, if the result of a 100 - period moving average is applied to an array of securities prices that contains less than 100 elements, then we get an empty data array.
The first and last valid indices are very important during indicator calculation. Calculations should always be limited to data items contained between them. Two important points should be understood in order to correctly set the Fvi and Lvi indexes for the returned data array:
• Always limit calculations to the range of allowable ranges of all used input data arrays.
• Fvi and Lvi as a result of the calculation should retain their positions with respect to the values ​​of all input data arrays.

The MSXDataRec structure contains seven data arrays:
sOpen, sHigh , sLow , sClose , sVol , sOI , sInd ,
storing their values ​​in the MSXDataInfoRec structure. These data sets store all the necessary price data of the Central Bank.
Some of these arrays may be empty if the Central Bank does not have data for this price field (for example, Open Interest).
• In addition, the MSXDataRec structure contains a pointer to an array from the MSXDateTime structure .
This array contains date and time information for each data point. If the calculation function needs access to the date and time for the N-th bar of the instrument, you must refer to the N-th element of the arraypsDate . Note that this array is different from a data array such as sHigh, sLow, etc.
• The sInd data array contains data for the indicator selected by the user. In the case of a custom indicator, this data array will contain the value for the chart object to which the indicator was attached. If the sInd section is not selected, the data array will be empty.
• Note that the location of the data in these arrays is synchronized with the Nth element of each element of the array corresponding to the time period.
• The sClose data array always contains the maximum number of data elements. All other data arrays contain the number of elements <= sClose.iLastValid.
• The iFirstValid and iLastValid settings in the sClose dataset are very important.
Usually the number of elements in this array determines the maximum number of data elements stored in other price arrays. This is important for determining the number of valid elements contained in the psDate array. For example, if the sClose.iFirstValid = 100 field and the sClose.iLastValid = 200 field, you can be sure that the psDate array contains valid data between psDate [100] and psDate [200].
• After the calculation is completed, the iLastValid value in a_psResult -> psResultArray should never be greater than the iLastValid value of the sClose data array.
iFirstValid and iLastValidfrom sClose should be used to determine how many values ​​are available to store all the data in arrays. Enough memory is allocated to store all arrays, only to the sClose.iLastValid data point. The data returned in a_psResult -> psResultArray from the MSX DLL should fit into the same storage restrictions.
• The beginning of the a_psResult-> psResultArray dataset returned from the MSX DLL should never be less than sClose.iFirstValid. End of data array a_psResult -> psResultArray should never be greater than sClose.iLastValid.

What you need to remember


• Calculation functions should never change any incoming arguments, except for the resulting record (a_psResult). Incoming arguments are defined as ' const ', where possible, in the provided templates to ensure that no illegal changes occur.
• Be sure to set a_psResult -> psResultArray -> iFirstValid and a_psResult -> psResultArray -> iLastValid before returning from your function.
• If your function returns MSX_ERROR , which indicates an internal error, be sure to copy the error describing the error a_psResult -> pszExtendedError into the extended string.
• Never set a_psResult -> psResultArray -> iFirstValid less than sClose.iFirstValid.
• Never set a_psResult -> psResultArray -> iLastValid greater than sClose.iLastValid. Writing to a_psResult -> psResultArray -> pfValue for the value sClose.iLastValid will cause the memory to be mapped in MetaStock and the program to crash.
• Be sure to check iFirstValid and iLastValid for any MSXDataInfoRec or a_psBasic arguments that you intend to use. Never think that data will be available in any data array. If the data is not available for your function to process, set
a_psResult -> psResultArray -> iFirstValid = 0 and
a_psResult -> psResultArray -> iLastValid = -1 ,
to indicate that there is no valid data to return the array. This method avoids the abnormal termination of the Metastock program.

Change the code


In accordance with the above material, we will slightly modify the code of our function. Add Fvi and Lvi, as well as an exception in case of receiving a damaged array at the output of 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)
{
   BOOL l_bRtrn = MSX_SUCCESS;
    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->iFirstValid = a_psBasic->sClose.iFirstValid;
    a_psResult->psResultArray->iLastValid = a_psBasic->sClose.iLastValid;
	    // проверяем выходной массив		
	if (a_psResult->psResultArray->iFirstValid < 0 || a_psResult->psResultArray->iLastValid < 0 
		|| a_psResult->psResultArray->iLastValid < a_psResult->psResultArray->iFirstValid || 
		a_psResult->psResultArray->iFirstValid < a_psBasic->sClose.iFirstValid || 
		a_psResult->psResultArray->iLastValid > a_psBasic->sClose.iLastValid)
	l_bRtrn = MSX_ERROR;
      // Если данные не доступны, возвращаем пустой массив.
	if (l_bRtrn != MSX_SUCCESS)
      {
		strncpy (a_psResult->szExtendedError, "Error: Corrupted Result Array.",
			     sizeof(a_psResult->szExtendedError)-1);
		a_psResult->psResultArray->iFirstValid = 0;
		a_psResult->psResultArray->iLastValid = -1;
       }
  return l_bRtrn;
}

Output to file


To display our data, be sure to include the stdio.h header file, in which a special data type is declared - the FILE structure.
#include
Comments
To organize work with files, the program needs to use pointers to files. To create a file pointer variable, an operator of the type: FILE * file (stream declaration) is used. To be able to access the file, you must open it. The fopen () function opens a stream for use, associates a file with this stream, returns a FILE pointer to this stream and has the following form:
• file = fopen (“path to file”, “file operation mode”).
The w mode is used to write to a file. The fprintf () function writes to the file:
• fprintf (file, [format string], [list of variables, constants]).
The fclose () function is used to close a stream previously opened with fopen ().
• fclose (file)
A call to fclose () releases the file control block associated with the stream and makes it reusable.

Let's get the following data: the name of the Central Bank, the period ('D'aily,' W'eekly, 'M'onthly,' Q'uarterly, 'I'ntraday), index, our indicator, time and date in the file.

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)
{
   BOOL l_bRtrn = MSX_SUCCESS;
   FILE *file;
   file = fopen("D:\\example.txt", "w");
    for (int i= a_psBasic ->sClose.iFirstValid; i<= a_psBasic ->sClose.iLastValid; i++)
	{
        a_psResult->psResultArray->pfValue[ i ] = a_psBasic ->sClose.pfValue[ i ];
	  if (file)
		fprintf(file, "%-10s %2c %5u %12.4f %5u %10u\n", a_psBasic ->pszSecurityName, 
			   a_psBasic ->iPeriod, i, double (a_psResult->psResultArray->pfValue[i]), 
			   a_psBasic ->psDate[i].lTime/1000, a_psBasic ->psDate[i].lDate);
	}
		fclose(file);
      // Задаем начало и конец массива.
	a_psResult->psResultArray->iFirstValid = a_psBasic->sClose.iFirstValid;
    a_psResult->psResultArray->iLastValid = a_psBasic->sClose.iLastValid;
	    // проверяем выходной массив		
	if (a_psResult->psResultArray->iFirstValid < 0 || a_psResult->psResultArray->iLastValid < 0 
		|| a_psResult->psResultArray->iLastValid < a_psResult->psResultArray->iFirstValid || 
		a_psResult->psResultArray->iFirstValid < a_psBasic->sClose.iFirstValid || 
		a_psResult->psResultArray->iLastValid > a_psBasic->sClose.iLastValid)
	l_bRtrn = MSX_ERROR;
      // Если данные не доступны, возвращаем пустой массив.
	if (l_bRtrn != MSX_SUCCESS)
      {
		strncpy (a_psResult->szExtendedError, "Error: Corrupted Result Array.",
			     sizeof(a_psResult->szExtendedError)-1);
		a_psResult->psResultArray->iFirstValid = 0;
		a_psResult->psResultArray->iLastValid = -1;
       }
  return l_bRtrn;
}

As a result, I got the following on a one-minute chart of futures on the RTS index:
example.txt
SP_RTS_1m   I     1  112310.0000  1725   20140417
SP_RTS_1m   I     2  112320.0000  1726   20140417
SP_RTS_1m   I     3  112130.0000  1727   20140417
...
SP_RTS_1m   I   497  117150.0000  1336   20140418
SP_RTS_1m   I   498  117170.0000  1337   20140418
SP_RTS_1m   I   499  117200.0000  1338   20140418
SP_RTS_1m   I   500  117190.0000  1339   20140418


With functions without arguments, I hope we figured it out. In the next article, we will look at creating functions with various arguments.

Read Next