Пишем библиотеку DLL для Metastock с нуля. Часть 1

DLL для Metastock
Metastock – наверное, самая известная программа для технического анализа рынка. Данная программа может подключать внешние библиотеки DLL, написанные пользователями для создания своих торговых стратегий, используя полную мощь традиционных языков программирования, таких как C или Паскаль.
Занявшись поиском в интернете, с удивлением обнаружил полное отсутствие информации по данной теме. Единственная статья, которая оказалась достойной внимания: “Что такое Metastock Developer's Kit?” (mdk), где описывается коротенький пример на Delphi, там же можно скачать MDK.
В данной статье я постараюсь заполнить этот пробел и описать процесс создания библиотеки внешних функций Metastock (MSX DLL) по шагам на языке C/C++. Все примеры писались в среде Visual Studio 2010 (часть2, часть3).

Немного теории


Функции, которые реализованы в MSX DLL ведут себя точно также как и стандартные встроенные функции Metastock’а. Все функции MSX DLL возвращают массив данных. Каждая внешняя функция имеет уникальное имя.

ExtFml(«DLL Name.Function Name»,arg1,…,argn), где

arg1…argn – аргументы функции.

Каждый аргумент может быть одним из четырех типов:
• Массивы данных (например, Open, High, Low, Close, и т.д., или результаты другой функции)
• Числовые константы (например, 5, -5, 20.55 и т.д.)
• Строковые константы (например, “Hello Woodpecker” и др.)
• Индивидуальные наборы (например, Simple, Triangular, и т.д.)
Как их определять и использовать мы рассмотрим позже, на конкретных примерах.

Функции, определенные в MSX DLL делятся на две категории:
• Функции инициализации
• Функции расчета (или внешние функции).
Функции инициализации вызываются MetaStock’ом во время запуска, чтобы определить, какие внешние функции доступны и какие аргументы они требуют. Функции расчета доступны для пользователей MetaStock’а. Все функции ссылаются на структуры данных определенных в файле MSXStruc.h. Этот файл необходим для компиляции нашей DLL.
Прежде чем писать свои функции необходимо прописать несколько служебных MSX-функций (функции инициализации), для того что бы Mетосток мог общаться с вашей DLL. Их четыре:
• MSXInfo — обязательная функция. Всегда вызывается во время инициализации и проверяет, является ли наша DLL MSX DLL и возвращает основную информацию о ней (авторство, количество наших функций, версия).
• MSXNthFunction — обязательная функция. Вызывается один раз для каждой функции указанной MSXInfo и нумерует, начиная с нуля наши функции. Здесь прописываются имена функций (c учетом регистра), их дескриптор и количество аргументов у каждой.
• MSXNthArg – эта функция обязательна, только если у наших функций есть аргументы. Вызывается во время инициализации для каждого аргумента наших функций.
• MSXNthCustomString – эта функция обязательна, только если у наших функций есть custom-аргументы.

Для начала теории достаточно, приступим к написанию первой DLL. В этом примере будет показано, как вывести массив цен, дату и время в наш индикатор.

Пишем код


Открываем VS 2010. Первым делом создадим пустой проект.
File->New->Project->Other Languages->Visual C++->Win32->Win32 Console Application.
Задаем имя нашей библиотеки (пусть UsePrice) и нажимаем OK.Открывается Win32 Application Wizard.
Next-> и ставим галочки на DLL и Empty project, нажимаем Finish. Пустой проект создан. Добавим в него три файла UsePrice.cpp, UsePrice.def, MSXStruc.h. Правой кнопкой по проекту Add->New Item…, выбираем файл с соответствующим расширением и задаем ему соответствующее имя. Нажимаем Add.
В файле UsePrice.cpp пишем наш код.

/*
Шаг 1 – Заголовки
Комментарии
Директива #include дает указание компилятору читать еще один исходный файл —
в дополнение к тому файлу, в котором находится сама эта директива.
Имя исходного файла должно быть заключено в двойные кавычки или в угловые скобки.

Заголовки для библиотечных функций C

*/
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <tchar.h>

// Обязательно включить - определяет MSX Data Structures (см. файл MSXStruc.h).

#include "MSXStruc.h"

/*
Шаг 2 – Экспорт
Комментарии
Директива #define определяет идентификатор и последовательность символов,
которой будет замещаться данный идентификатор при его обнаружении в тексте
программы. Идентификатор также называется именем макроса, а процесс
замещения называется подстановкой макроса. Стандартный вид директивы
следующий:

#define имя_макроса последовательность_символов

Обратим внимание, что в данном операторе отсутствует точка с запятой. Между
идентификатором и последовательностью символов может быть любое число
пробелов. Макрос завершается только переходом на новую строку.

Например, если необходимо использовать TRUE для значения 1, a FALSE для 0,
то можно объявить следующие два макроса:

#define TRUE 1
#define FALSE 0

В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то
он заменит их на 1 и 0 соответственно.
В нашем случае если в коде встречается DLL_EXPORT, то выполняется макрос

extern «C» __declspec(dllexport)

Чтобы нормально спрягать код на C++ с C, где name mangling отсутствует, введено
extern «C», которое отключает этот механизм у экспортируемых имен переменных/функций.
extern «C» обозначает использование простой генерации сигнатуры функции
(в стиле языка С) при получении объектных файлов. В частности, это запрещает
компилятору C++ производить «декорацию» имени функции дополнительными
символами при экспорте в DLL.
Атрибут класса хранения dllexport — это специфическое для Microsoft расширение
языков C и C++. Его можно использовать для экспорта функций, данных и объектов
в библиотеку DLL.
__declspec( dllexport ) declarator
Этот атрибут явно определяют интерфейс DLL для ее клиента, который может быть
исполняемым файлом или другой библиотекой DLL. Объявление функций как
dllexport позволяет обходиться без файла определения модуля (DEF), по крайней
мере, в отношении спецификации экспортированных функций.

*/
#define DLL_EXPORT extern "C" __declspec(dllexport)

/*
Шаг 3 – Функции инициализации
Комментарии
Прежде чем писать свои функции необходимо прописать несколько служебных
MSX-функций, для того, чтобы Mетосток мог общаться с нашей DLL.
В первом примере мы будем писать одну функцию без аргументов, поэтому
MSXNthArg и MSXNthCustomString нам не понадобятся.

MSXInfo
Замените поле <VS 2010 C++ MSX DLL, Copyright Pretzel, 2014> вашей
информацией об авторстве, а также установите количество функций = 1.
strncpy (strDest,strSource,count) — копирует символы одной строки в другую.
• strDest — строка назначения.
• strSource — иссходная строка.
• count — число символов для копирования.
strncpy требует обязательный заголовок <string.h>.
На strncpy компилятор VS C++ выдает предупреждения, можно использовать
strncpy_s (проверял — все работает, но не могу сказать насколько это корректно).

*/
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
Значение, копируемое в a_sFuncDef->szFunctionName должно точно соответствовать
нашей функции, которую мы будете экспортировать, с учетом регистра.
Изменяемые значения:
• Name — имя нашей функции.
• Description — то, как она будет читаться в Metastock'е (в окне 'Paste Functions').
• Arguments — число аргументов нашей функции.
На strcpy компилятор VS C++ выдает предупреждения, можно использовать strcpy_s
(проверял — все работает, но не могу сказать насколько это корректно).

*/
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;
}


/*
Шаг 4 – Наша функция

*/
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;
}


Далее в файле UsePrice.def введем следующий код:

LIBRARY UsePrice
EXPORTS
  MSXInfo
  MSXNthFunction
  Price
 

Заполняем MSXStruc.h кодом.
MSXStruc.h

#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



В Visual Studio нажимаем Build -> Build Solution (F6) и получаем нашу DLL. Отправляем ее в папку ‘External Function DLLs’ в Metastock’e и можно пользоваться. Наш индикатор будет иметь следующий вид:

ExtFml(«UsePrice.Price»)

В следующей части мы рассмотрим нашу функцию, подключим аргументы, добавим исключения и выведем данные во внешнюю среду.
Share post

Comments 0

Only users with full accounts can post comments. Log in, please.