Monday, May 12, 2014

Code to Export C# DLL to Metatrader Build 600+

This is a quick followup to the article Code to Export C# DLL to Metatrader to update the code for Metatrader build 600+ due to the many comments I have received.

The good news: most things stay the same, particularly with regards to the dll call from MQL4. The changes in C# are relatively straightforward.

With build 600+, strings must now be explicitly specified in C# with the following format:
[return: MarshalAs(UnmanagedType.LPWStr)]

I added in this update two array examples. The first shows how to pass a double array as a parameter to C# and do something with those values (calculate the mean of 5 sample values) in C#. The second example shows how to pass an array by reference to C#. The MT4 end of things is identical. In C# you simply specify [In, Out...] to allow the array to be bi-directional.

Here is the C# (updated) code:
using System;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace testUMD600
{
   class Test
   {
 
        [DllExport("AddInteger", CallingConvention = CallingConvention.StdCall)]
        public static int AddInteger(int Value1, int Value2) {
            MessageBox.Show("Add Integers: " + Value1.ToString() + " " + Value2.ToString(), "AddInteger");
            return (Value1 + Value2);
        }

        [DllExport("AddDouble", CallingConvention = CallingConvention.StdCall)]
        public static double AddDouble(double Value1, double Value2) {
            MessageBox.Show("AddDouble: " + Value1.ToString() + " " + Value2.ToString(), "AddDouble");
            double Value3 = Value1 + Value2;
            return (Value3);
        }

        [DllExport("AddDoubleString", CallingConvention = CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.LPWStr)]  // note this change for build 600+
        public static string AddDoubleString(double  Value1, double Value2) {
            MessageBox.Show("AddDoubleString: " + Value1.ToString() + " " + Value2.ToString(), "AddDoubleString");
            double Value3 = Value1 + Value2;
            return (Value3.ToString() );
        }

        [DllExport("returnString", CallingConvention = CallingConvention.StdCall)]
        [return: MarshalAs(UnmanagedType.LPWStr)] // note this change for build 600+ as well as MarshalAs statement on the next line...
        public static string returnString ([MarshalAs(UnmanagedType.LPWStr)] string Input) {
            MessageBox.Show("Received: " + Input, "returnString");
            return ("SEND to MT4");
        }

        // many thanks to anonymous for the code sample below!
        [DllExport("ReturnDouble2", CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
        static double ReturnDouble2() {
            return 4.5;
        }

       // a double array is passed in as a parameter from MT4, for the purposes of this demo, 
       // only the first five (or fewer) items in array data are processed.
       // An average is created from the first 5 items in data and returned to MT4, 
       // and those first 5 items items are shown in a message box for validation purposes.
        [DllExport("PassDoubleArray", CallingConvention = CallingConvention.StdCall)]
        public static double PassDoubleArray ([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] data,
            int datasize)  
        {
            string ff = "first five items (or less):";
            int end = 5;
            if (end > data.Length) end = data.Length;
            double ave = 0.0;
            for (int i = 0; i < end; i++) {
                ff = ff + " " + data[i].ToString();
                ave += data[i];
            }
            if (end == 0) end = 1;
            ave /= end;
            MessageBox.Show("Received " + ff, "PassDoubleArray");
            return (ave);
        }

       /*
       NOTE:

        The key with array passing (as a parameter to C#) is the SizeParamIndex field (above).
        When SizeParamIndex is equal to 1 (shown above) the size of the array data must be contained
        In the parameter in slot 1 (the 2nd parameter). 
        This is fulfilled by the variable datasize. If datasize were the first parameter, then SizeParamIndex = 0 would
        be appropriate.

       This is inferred from this page:
       www.mql5.com/en/articles/249
       (See 4.3 example 3 as well as some others) 
       Note how both arrays reference SizeParamIndex = 1 which is the "len" parameter in slot 1 (2nd parameter).
       
       Also note the use of [In Out MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] when passing an array by reference in that example,
       and below in PassDoubleArrayByref example.
       */

        [DllExport("PassDoubleArrayByref", CallingConvention = CallingConvention.StdCall)]
        public static double PassDoubleArrayByref ([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] double[] data,
            int datasize) {
            string ff = "first five items (or less):";
            int end = 5;
            if (end > data.Length) end = data.Length;
            double mean = 0.0;
            for (int i = 0; i < end; i++) {
                ff = ff + " " + data[i].ToString();
                mean += data[i];
            }
            
            if (end == 0) end = 1;
            mean /= end;
            // subtract the mean from each of the first values in data
            ff = ff + "\r\n" + "Data passed back to MT4: " + "\r\n" ;
            for (int i = 0; i < end; i++) {
                data[i] -= mean; // subtract the mean
                ff = ff + " " + data[i].ToString();
            }
            MessageBox.Show("Received " + ff, "PassDoubleArrayByref");
            return (mean);
        }

        [DllExport("PassStringArrayByref", CallingConvention = CallingConvention.StdCall)]
        public static void PassStringArrayByref ([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] IntPtr[] stringPointer, int datasize) {
            try {
                // I don't know why we need to pass a fake size 3x larger than the array size but this seems to work this way.
                // Use at your own risk!!!
                // Not every index contains string values so you must go through and select the items with a valid pointer
                string[] data = new string[stringPointer.Length/3];
                int datacount = -1; // used to properly index the data array
                for (int i = 0; i < stringPointer.Length; i++) {
                    if (stringPointer[i] != IntPtr.Zero) {
                        datacount++;
                        data[datacount] = Marshal.PtrToStringAuto(stringPointer[i]);
                        if (datacount == 0) {
                            // attempt to alter the incoming string going back to MT4
                            stringPointer[i] = Marshal.StringToBSTR("String returned byref from C# DLL!!!");
                        } else {
                            // show how it is possible to append data to an existing string (symbol names) and send back to MT4
                            System.Security.SecureString alteredData = new System.Security.SecureString();
                            foreach (char c in (data[datacount] + "_modified by C#")) {
                                alteredData.AppendChar(c);
                            }
                            stringPointer[i] = Marshal.SecureStringToBSTR(alteredData);
                        }
                        MessageBox.Show(data[datacount], "PassStringArrayByref");
                    } else {
                        MessageBox.Show("Pointer " + i.ToString() + " is zero!", "PassStringArrayByref");
                    }
                }
            } catch (Exception ex) {
                MessageBox.Show("Exception at PassStringArrayByref: " + ex.ToString(), "PassStringArrayByref");
            }
        }


   }
}



Here is the MT4 (updated) code:
//+------------------------------------------------------------------+
//|                                                   testDLL600.mq4 |
//|                               Copyright © 2014, Patrick M. White |
//|                     https://sites.google.com/site/marketformula/ |
//|                                    updated 5/12/2014             |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2014, Patrick M. White"
#property link      "https://sites.google.com/site/marketformula/"
#property version   "1.00"
#property strict

#import "testUMD600.dll"
   // nothing changed on the MT4 side. String handling changed in C#
   int AddInteger(int Value1, int Value2);
   double AddDouble(double Value1, double Value2);
   string AddDoubleString(double Value1, double Value2);
   string returnString(string Input);
   double ReturnDouble2(); 
   
   // note that the two calls below are identical, the key is in the C# dll adding 
   // [In, Out... to PassDoubleArrayByref
   double PassDoubleArray(double &data[], int datasize);
   double PassDoubleArrayByref(double &data[], int datasize); 
   void PassStringArrayByref(string &data[], int datasize); 
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Print("AddInteger: " + DoubleToStr(AddInteger(250, 750),0));
   double a = AddDouble(250,750);
   Print("AddDouble: " + DoubleToStr(a,4));

   double d = StrToDouble(AddDoubleString(250, 750));
   Print("AddDoubleString: " + DoubleToStr(d,4));
   string temp = "Send to DLL";
   string recv = returnString(temp);
   Print(recv);
   double dd = ReturnDouble2();
   Print("Returning Double from C#: " + DoubleToStr(dd, 4));
   
   double data[];
   ArrayResize(data, 5);
   string s = "Sending: ";
   for (int i = 0; i < ArraySize(data); i++) {
      data[i] = i + 1.0;
      s = s + " " + DoubleToStr(data[i],1);
   }
   Print(s);
   double ret = PassDoubleArray(data, ArraySize(data));
   Print("Mean of 1+2+3+4+5: " + DoubleToStr(ret, 4));
   
   ret = PassDoubleArrayByref(data, ArraySize(data));
   s = "Receiving sent values less their mean: ";
   for (int i = 0; i < ArraySize(data); i++) {
      s = s + " " + DoubleToStr(data[i],2);
   }
   Print(s);
   
   // string array demo:
   string sa[];
   ArrayResize(sa, 4);
   sa[0] = "Test from MT4 to DLL";
   sa[1] = "EURUSD";
   sa[2] = "USDJPY";
   sa[3] = "AUDUSD";
   PassStringArrayByref(sa, ArraySize(sa)*3); // pass fake size 3x larger - why? it works! 
   for (int i = 0; i < ArraySize(sa); i++) {
      Print("PassStringArrayByref index: " + i + " size: " + ArraySize(sa) + " text: " +  sa[i]);
   }

  }
//+------------------------------------------------------------------+



I just tested the above script in MT4 build 646 and it worked correctly.

As a reminder, compile the dll in C# with Platform / Platform Target for x86 (Project / Properties / Build ). 

The build 600+ project can be downloaded at Code to Export CSharp DLL to Metatrader 600+.

Instructions to get the Unmanaged Exports (DllExport for .NET) for C# via nuget can be found here.

Edit and VERY important note: you MUST handle all errors produced by your DLL, inside your DLL or those errors will feed back to the calling application, causing MT4 to shut down. While the code samples above notably don't show this implemented, you should include a default try{} catch (Exception ex) {} block in each of your C# methods to prevent this from happening. There have been MANY comments from those attempting to implement this code that has prompted this edit, so take note!

6/8/2015 Edit: Added code to allow byref passing of string arrays to/from C# and a demo on how one might use this to pass symbol names to the DLL, modify a string in the DLL and pass back to MT4.  Note only the code above in this blog post has been changed. The uploaded code has not been updated, so copy/paste here to get the updated code. Also note that this was done by trial and error and so the 3x array size hack may not work in all cases. So do your own testing and report back what you learn. If you have a better way to define or make the method call  for passing a string array from MT4 to C#, please report your findings here so we can all learn.

Saturday, May 18, 2013

Triangular Arbitrage with Bid Ask Prices

Is is possible to identify triangular arbitrage opportunities using bid and ask prices for a theoretical risk free trade? Using simple rules and examples it is possible to determine the proper formula for computing triangular arbitrage relationships. The three examples show how to calculate the triangular arbitrage formula for different currency pairs due to the way pairs are converted to base currency and traded via currency pairs. The results can be intuitively interpreted to determine if a real arbitrage opportunity exists, or if an opportunity exists to improve execution price by using the synthetic pair instead of the underlying pair for trade execution even when no real arbitrage opportunity exists.

Triangular Arbitrage with bid ask prices 1000 bid vs synthetic ask quotes.png (654×446)

Triangular Arbitrage with Bid Ask Quotes


Thursday, April 4, 2013

VB6 VBA Read Large Binary Flat Files Past 2GB Limitation Using Windows API ReadFile WriteFile

If you deal with really large flat files in VB6 or VBA, how do you read past the 2 GB limitation? Visual Basic 6 / VBA has a very easy file read/write mechanism for binary files. It works great for user defined types (UDTs) and arrays of UDTs. Unfortunately, this interface was written before terabyte hard drives became standard. The actual limit is the size of a long integer (2,147,483,647). When reading using the binary Get function, I had the unpleasant experience of rolling the record number, which resulted in the value becoming negative, triggering a read error. I was fortunate to find a well-written demo that gives a solution using the Windows API file read / write methods to ReadFile and WriteFile. You can find the demo and more information at the link below:

VB6 HugeFixedFile

The code contains a class that encapsulates the basic methods you will need to deal with large files in VBA and VB6. See the code and forum posts for more details. I have since ditched the UDT interface as I found that LSet didn't do a great job converting my byte array to UDT. So I manually convert the byte arrays into the proper types. I had to find / build functions to convert a byte array to an integer and a long. The code is posted below in case some of you might find it useful to convert a byte array to an integer or a byte array to a long in VB6 or VBA:

Code:
Private Function ConvertByteToInteger(ByVal start As Long, ByVal lend As Long, ByRef byt() As Byte) As Integer
    ConvertByteToInteger = byt(start) + CLng(byt(lend)) * 256
End Function

Private Function ConvertByteToLong(ByVal start As Long, ByVal lend As Long, ByRef byt() As Byte) As Long
    ConvertByteToLong = byt(start) + CLng(byt(start + 1)) * 256& + CLng(byt(start + 2)) * 2& ^ 16 + CLng(byt(lend)) * 2 ^ 32
End Function

I also ditched the UDTs though the code at the link above shows how to convert a byte array to UDT using LSet. I found that by using the HugeFixedFile class and loading a byte array of 1000 records of 16 byte records, my processing time was cut by almost 2/3 when reading a large file. As a result, I edited the Read function (found in the demo) to allow for reading a larger byte array than the record count as follows: (assumes a zero-based byte array)

Code:
Public Function ReadRec(ByRef Record() As Byte) As Long
    If ReadFile(hFile, VarPtr(Record(0)), UBound(Record) + 1, ReadRec, 0) Then
        If ReadRec = 0 Then
            fEOF = True
        End If
    Else
        RaiseError HFF_READ_FAILURE
    End If
End Function



Wednesday, January 23, 2013

Calculating Triangular Arbitrage Lot Size


Have you ever wondered how to correctly size positions between the underlying pair and its synthetics to eliminate or hedge directional risk? This article describes  how to calculate triangular arbitrage lot size to fully hedge all exposure when initiating a triangular arbitrage trade. The arbitrage trade is at the heart of all good strategies that take advantage of inefficiency. In the forex market this means triangular arbitrage, so understanding how to correctly size positions to eliminate or minimize individual currency risk is very important.


Triangular Arbitrage Lot Size


What triangular arbitrage lot size should be traded to capture this 6 pip inefficiency?

Tuesday, January 1, 2013

Trading Search Engine

Get better search results for your trading search queries by using the:

Trading Search Engine

Perfect for stock, forex, bond, foreign exchange, and commodity traders and trading.

Make sure to bookmark the link for future access!

Thursday, October 25, 2012

Harness the Power of Machine Learning in MT4 to Create a Metatrader Indicator with Encog

This step by step guide shows how to create and test an Encog machine learning method using inputs from MT4 indicators to create an indicator that can be used in Metatrader. By using machine learning methods such as neural networks, genetic algorithms, support vector machines and other machine learning methods Encog allows an ensemble approach to design of machine learning indicators that may be used in MT4.

This looks very interesting!

Creating a MQL4 Indicator with Encog

Monday, September 17, 2012

C# ZeroMQ Install Step by Step for Visual Studio.

ZeroMQ is a socket library that has many uses and claims to be faster than TCP for clustered products and supercomputing. Sounds interesting! In attempting to install ZeroMQ for C# in Visual Studio, I found that the documentation was not very clear, and nowhere could I find a step by step guide for installing C# ZeroMQ.

This post is made so I can recall the ZeroMQ C# installation steps for future reference, and in hopes that someone else might have an easier path to ZeroMQ installation in Visual Studio.

The easiest way to get ZeroMQ working for C# is by using a Visual Studio Extension called NuGet. To download NuGet from within Visual Studio, use the Tools / Extension Manager menus.

From within Visual Studio's Extension Manager, click on Updates and wait for the list to populate. Select NuGet Package Manager, and wait for the package to download and install.

To display the NuGet Package Manager select View / Other Windows / Package Manager Console from the menus. Make sure your Visual Studio project is set to either x64 or x86 from the Project / Properties menu in Visual Studio, then install the appropriate package from the link above

The installation package for ZeroMQ is clrzmq 2.2.5 or clrzmq-x64 for x86 or x64 projects respectively.

In Visual Studio's Project Manager Console type one of the following in:

PM> Install-Package clrzmq -Version 2.2.5
PM> Install-Package clrzmq-x64

This command will install the appropriate version in your package. I created an x64 project with the command directly above. This created a new directory under my project.

Open up Windows Explorer and find your project's folder. You should see a new subfolder called packages\clrzmq-x64.2.2.5\content that contains the libzmq.dll library if you are doing an x64 project.

Copy libzmq.dll to your \bin\Debug or \bin\Release directory so that your project can find the dll.

Prepare one of the sample codes that are available on github for C#.

For your next project you will need to once again use the Package Manager Console to build the libzmq.dll and create the clrzmq and clrzmq-ext References in C#, so make a note of the installation command and file copying procedure for future reference:


PM> Install-Package clrzmq -Version 2.2.5
PM> Install-Package clrzmq-x64


As a post script, the ZeroMQ Windows installation doesn't seem to be necessary for C# users. In fact, I uninstalled it and was still able to run my demo project. The installation contains some executables and dlls, as well as a number of help documents that may be of interest to developers.