📜 ⬆️ ⬇️

Writing a DLL for Metastock from scratch. Part2

DLL  Metastock

In this article, our function ( part 1 , part 3 ), the rules for getting data from Metastock, processing it and returning the result back to Metastock will be discussed in detail. This information will help to avoid errors in the MSX DLL.

Our function is:
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 - 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 numeric data used in calculations).
All structures are described in the MSXStruc.h file.

Data Storage and Calculations


All numerical data used in the calculations 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 delivers data about the securities and arguments of the MSX DLL functions, this data is used as an array. When the function calculated by the MSX DLL returns the indicator results in MetaStock, the result is returned as an array of data.
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 final point of reference (Lvi).
The data elements are actual numeric 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 elements contain valid values. All data elements between the first and last valid index (inclusive) contain valid data. All items outside this range are null and should be ignored for all calculations.
A data array is considered " empty " if Fvi <Lvi. Empty data arrays are not uncommon and should be processed properly. As a rule, an empty data array will have Fvi = 0 and Lvi = -1, although any combination under the condition Fvi <Lvi should be considered empty. An empty data array appears when data is not available. For example, the 'Open Interest' dataset is used for a security (CB) that does not have open interest.
Or, if the result of the 100 - period moving average is applied to the price data set of the Central Bank, which contains less than 100 elements, then we get an empty data set.
The first and last valid indices are very important during the calculation of the indicator. Calculations should always be limited to the data elements contained between them. Two important points should be understood in order to properly set the Fvi and Lvi indexes for the returned data array:
• Always restrict calculations in the range of allowable ranges of all input data sets used.
• Fvi and Lvi as a result of the calculation must retain their positions in relation to the values ​​of all arrays of input data.
')
The MSXDataRec structure contains seven data arrays:
• sOpen , sHigh , sLow , sClose , sVol , sOI , sInd ,
storing their values ​​in the MSXDataInfoRec structure. These data files 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 requires access to the date and time for the Nth bar of the instrument, refer to the Nth element of the psDate array. Note that this array is different from a data array like 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 area is not selected sInd 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.
• An array of data sClose 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 from psDate [100] to 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 iLastValid from sClose should be used to determine how many values ​​are available to store all of the data arrays. To store all arrays, enough memory is allocated, only up to the data point sClose.iLastValid. The data returned in a_psResult -> psResultArray from the MSX DLL must fit into the same storage restrictions.
• The beginning of the a_psResult-> psResultArray data array returned from an MSX DLL should never be less than sClose.iFirstValid. The end of the a_psResult -> psResultArray dataset should never be greater than sClose.iLastValid.

What you need to remember


• The calculation functions should never change any input arguments, with the exception of the resulting record (a_psResult). Inputs are defined as ' const ', where possible, in the templates provided, to ensure that illegal changes do not 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, make sure that you have copied into the extended error line the reason for describing the a_psResult -> pszExtendedError error.
• Never set a_psResult -> psResultArray -> iFirstValid less than sClose.iFirstValid.
• Never set a_psResult -> psResultArray -> iLastValid to be greater than sClose.iLastValid. Writing to a_psResult -> psResultArray -> pfValue for the sClose.iLastValid value will cause memory overtaking in MetaStock and crash the program.
• Be sure to check iFirstValid and iLastValid for any MSXDataInfoRec or a_psBasic arguments you intend to use. Never assume that the data will be available in any data array. If data is not available for your function to process, install
a_psResult -> psResultArray -> iFirstValid = 0 and
a_psResult -> psResultArray -> iLastValid = -1 ,
to indicate that there is no reliable data to return an array. This method avoids the crash of the Metastock program.

Change the code


In accordance with the above material, we slightly change the code of our function. Add Fvi and Lvi, as well as an exception in the 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; } 

File output


To display our data, be sure to include the header file stdio.h, in which a special data type is declared - the FILE structure.
• #include <stdio.h>
Comments
To organize work with files, the program must use pointers to files. To create a file pointer variable, use an operator of the type: FILE * file (stream declaration). 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 the stream and has the following form:
• file = fopen (“file path”, “file operation mode”).
Mode " w " is used to write to the 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)
Calling fclose () frees the file control block associated with the stream and makes it available for reuse.

Let's display the following data: the name of the Central Bank, the period ('D'aily,' W'eekly, 'M'onthly,' Q'uarterly, 'I'ntraday), the 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, in my one-minute RTS Index futures chart, the following happened:
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 understood. In the next article, we will look at creating functions with different arguments.

Source: https://habr.com/ru/post/219953/


All Articles