Communicating between Python and MT5

Metatrader 5 (MT5) – one of the most popular platforms for trading on financial markets with its own programming language for developing automating trading strategies (mql5).

Python – Includes incredibly powerful tools for machine learning and predictive analytics.

It seems like a link between these two would be a match made in heaven, enabling the use of machine learning in trading strategies. However, this link isn’t as straight forward as I hoped. A common solution is to export data from Metatrader manually and then analyse it python before setting up trading strategies. Unfortunately, this approach prevents the use of metatrader’s best features: automation.

I decided to create a DLL written in C++ to link Metatrader 5 and Python. Although I believe the DLL could be used by a number of programs to link with Python.

Initially I attempted to create the DLL using Boost.python. This strategy worked well, but unfortunately I had some issues compiling the library for 64 bit systems, to which my Metatrader was limited. Therefore, I decided to rather use Python’s C API. An article by arnavguddu on embedding Python in C++ was particularly useful (https://www.codeproject.com/Articles/820116/Embedding-Python-program-in-a-C-Cplusplus-code). I made use of his code for pyhelper.hpp as it made the initialization and finalization of the Python environment much easier.

Next I created my RunPythonCode below to call a python function in a saved file by the name of the file and function (both stored as a character array). Two variables are passed to the python function, namely a 2D Numpy array of up to 20 fields (mql5_arr), and an array of arguments (args).

#include <Python.h>
#include "pyhelper.h"
#include "arrayobject.h"

CPyInstance hInstance;
CPyObject pName, pModule, pFunc, pValue, pInputs, pargs, pArray;

long RunPythonCode(double *mql5_arr[][20], int array_rows, int array_cols, char FileName[], char FuncName[], int *args[]) {
	
	if (PyArray_API == NULL)
	{
		import_array();
	}
	pName = PyUnicode_FromString(FileName);
	pModule = PyImport_Import(pName);

	if (pModule)
	{
		pFunc = PyObject_GetAttrString(pModule, FuncName);

		if (pFunc && PyCallable_Check(pFunc))
		{
			// Build the 2D array in C++
			int SIZE = array_rows;
			npy_intp dims[2]{ array_rows, array_cols };
			const int ND = 2;

			// Convert it to a NumPy array.
			pArray = PyArray_SimpleNewFromData(ND, dims, NPY_DOUBLE, reinterpret_cast<void*>(mql5_arr));

			if (!pArray) {
				printf("Error converting to python array.\n");
				return -2;
			}

			//Add python array to tuple
			pInputs = PyTuple_New(2); //Number of inputs to function
			PyTuple_SetItem(pInputs, 0, pArray); //Add value to inputs

			//Add args to Inputs
			int foo = sizeof(args) / sizeof(int);
			npy_intp argsdims[1]{ foo };
			pargs = PyArray_SimpleNewFromData(1, argsdims, NPY_INT, reinterpret_cast<void*>(args));
			PyTuple_SetItem(pInputs, 1, pargs); //Add value to inputs

			// Execute function pFunc with variables pInputs
			pValue = PyObject_CallObject(pFunc, pInputs);

			long pyResult = PyLong_AsLong(pValue);
			return pyResult;
		}
		else
		{
			printf("ERROR: Python function in module\n");
		}

	}
	else
	{
		printf_s("ERROR: Module not imported\n");
		return -3;
	}

	return 1;
}

The next bit of code is the only function of the Dll. The strings for the python file and function name are received as wchar_t* and then converted to char arrays using wcstombs_s.

#include <wchar.h>
_DLLAPI long __stdcall CallPython(double *mql5_arr[][20], int array_rows, int array_cols, wchar_t* FileName, wchar_t* FuncName, int *args[]) //First column should be the targets
{
	int n_file = wcslen(FileName) + 1;
	int n_func = wcslen(FuncName)+1;
	size_t i_file,i_func;
	char *CharFileName = new char[n_file];
	char *CharFuncName = new char[n_func];
	wcstombs_s(&i_file, CharFileName, n_file, FileName, n_file);
	wcstombs_s(&i_func, CharFuncName, n_func, FuncName, n_func);

	long Result = RunPythonCode(mql5_arr, array_rows, array_cols, CharFileName, CharFuncName, args);
	delete(CharFileName);
	delete(CharFuncName);
	return Result;
}

The Dll can  be imported into mql5 using:

#import "D:\\Documents\\My_Projects\\Markets\\PythonDll\\x64\\Release\\PythonDll.dll"
   long CallPython(double &a[][11], int array_rows, int array_cols, string &FileName, string &FuncName, int &args[]);
#import

An example of the use of the Dll in which a function called WriteData is called from a python script called PythonCode.py is:

void CallPython()
 {
   Print("Writing data to file...");
   double AllInputs[][11];
   GetMarketInputs(AllInputs,NumRows); // Add data to matrix
   int args[] = {1,1}; // Additional args to python if needed
   string FileString = "PythonCode";
   string FuncString = "WriteData";
   Print("Result = " ,CallPython(AllInputs,NumRows,NumCols,FileString,FuncString,args)); // NumCols = 11 for this example
   
   return(INIT_SUCCEEDED);
 }

The python script associated with this example is:

 
def WriteData(a, args):
 DataFileName = 'D:\Documents\My_Projects\Markets\AllData.csv'
 print('Python write function called!')
 file = open(DataFileName, 'w')
 writer = csv.writer(file,lineterminator = '\n')
 for row in a:
 writer.writerow(row)
 
 file.close()
 return 1;

For additional information or to download the files, please see the repository at:
https://github.com/sdswart/pythondll