Files
mtapi/Examples/Python/MT4/con2mtapi.py
T
eabase 8369788133 Added con2mtapi python example for MT4
Added the latest con2mtapi python example for MT4, as is maintained and part of the `mt4pycon`, found here:  
https://github.com/eabase/mt4pycon
2020-11-12 17:21:01 +02:00

464 lines
20 KiB
Python

#!/usr/bin/env python3
# con2mtapi.py - An example for connecting to the mtapi EA on MT4 and get some data
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Author: EAML
# Date: 2020-11-10
# Version: 1.0.2
# License: MIT
# URL: https://github.com/eabase/mt4pycon/
# ---------------------------------------------------------------------
#
# Description:
#
# This is an example client using the MT4 API (mtapi) to connect
# to the MT4 EA, using either an TCP/IP or a pipe port, to get some
# historical candle OHLCV data for a specified symbol and timeframe.
#
# ToDo:
#
# [ ] Make a native pip installer package (PyPi)
# [ ] Automatic detection of MT5
# [ ] Fix bug when requested chart does not (yet) have any candle data... double run!
# [x] Allow TF specification as MT4/5 text ('4H') and not in minutes ('240')
# [ ] Add CLI options:
# ----------------------------------------------------------
# - [ ] '-5' : Use script for MT5 installs (this will use MT5 DLL's)
# - [ ] '-c' : Enable ANSI color markup of output
# - [x] '-d' : Enable extra debug info
# - [ ] '-e' : Show candle data as minimal CSV list (ignores -l) ???
# - [x] '-h' : Show THIS help list
# - [x] '-n <n>' : Show <n> number of candles back ['0' is last complete candle]
# - [x] '-p' : Use a named pipe connection (instead of a TCP/IP host:port)
# - [x] '-l' : Show candle data as a list (one line per OHLC item)
# - [x] '-s <symbol>' : Set symbol name to <symbol> ['EURUSD']
# - [x] '-t <timeframe>' : Set timeframe to <timeframe>
# - [x] '-v' : Show THIS program version
# ----------------------------------------------------------
#
# Installation:
#
# <TBA>
#
# Usage:
#
# <TBA>
#
# Requirements:
#
# pip install pythonnet
# pip install
#
# NOTES
#
# 1. Spread for candles are meaningless, unless we talk about an "average",
# which is not available without also getting all the ticks inside.
#
# 2. Some Default Settings:
# For MT5:
# MTPATH = C:\Program Files\MtApi5 # Default Path to installed MtApi library files
# MTSERV = 127.0.0.1 # Default IP address of your localhost where MtApi terminal is running
# MTPORT = 8228 # Default Port setting of your MtApi EA
#
# 3. Using TCP vs. a "Named Pipe" connection:
# TCP: client.BeginConnect(ip, port)
# Pipe: client.BeginConnect(port)
# (Note that a Windows "named pipe" is using the "localhost" interface.)
#
# 4. What is the deal with: 0.0.0.0 & 127.0.0.1 ?
# See [4]
#
# 5. To measure performance, use:
# (Measure-Command { python.exe .\con2mtapi.py -n10000 -s "CADCHF" -t 15 -p }).TotalSeconds
#
# 6. How to get Tick data streamed?
#
# struct MqlTick {
# datetime time; // Time of the last prices update
# double bid; // Current Bid price
# double ask; // Current Ask price
# double last; // Price of the last deal (Last)
# ulong volume; // Volume for the current Last price
#
# REFERENCES
#
# [1] https://www.mql5.com/en/docs/constants/structures/mqlrates
# [2] https://docs.mql4.com/constants/structures/mqltick
# [3] https://stackoverflow.com/questions/48542644/python-and-windows-named-pipes
# [4] https://www.howtogeek.com/225487/what-is-the-difference-between-127.0.0.1-and-0.0.0.0/
# [5] https://docs.mql4.com/constants/chartconstants/enum_timeframes
#
# ---------------------------------------------------------------------
import os, sys
import clr
import time
import getopt
#--------------------------------------
# For MT5 installs
#--------------------------------------
# C:\Program Files\MtApi5
#sys.path.append(r"C:\Program Files\MtApi5")
#asm = clr.AddReference("MtApi5")
#import MtApi5 as mt
#--------------------------------------
# For MT4 installs
#--------------------------------------
# C:\Program Files (x86)\MtApi
sys.path.append(r"C:\Program Files\MtApi")
asm = clr.AddReference('MtApi')
import MtApi as mt
#--------------------------------------
# Import/Use colorama
#--------------------------------------
##from colorama import Fore, Style
#from colorama import init, AnsiToWin32
#init(wrap=False)
#stream = AnsiToWin32(sys.stderr).stream
#--------------------------------------
#----------------------------------------------------------
# Global Variables
#----------------------------------------------------------
__author__ = 'EAML (EABASE)'
__copyright__ = 'MIT (2020)'
__version__ = '1.0.2'
debug = 0 # Enable debug printing
#----------------------------------------------------------
# Default Program Constants
#----------------------------------------------------------
#MTSERV = '192.168.1.101' # IP address of "your local machine" where MtApi terminal is running
MTSERV = '127.0.0.1' # IP address of "your localhost" where MtApi terminal is running
#MTSERV = '0.0.0.0' # IP address of "ALL on local net" where MtApi terminal is running
MTPORT = 8222 # Port setting of your MT4 EA [Default:8222]
#MTPORT = 8228 # Port setting of your MT5 EA [Default:8228]
MTSYMB = 'EURUSD.e' # <select your default symbol>
#MTTIME = 'M15' # <select your default timefrme>
#------------------------------------------------
# mt.ENUM_TIMEFRAMES.PERIOD_M15
#------------------------------------------------
t1 = '[M1,M5,M15,M30,H1,H4,D1,W1,MN1]' # Standard Periods
t2 = '[M2,M3,M4,M6,M10,M12,M20,H2,H3,H6,H8,H12]' # Extended Periods (MQL/API only)
p1 = [1,5,15,30,60,240,1440,10080,43200] # [min]
p2 = [2,3,4,6,10,12,20,120,180,360,480,720] # [min]
z1 = t1.translate(str.maketrans('','','[]')).split(',') # Remove "[" and "]"
z2 = t2.translate(str.maketrans('','','[]')).split(',') #
d1 = dict(zip(z1,p1)) # convert to dict
d2 = dict(zip(z2,p2)) # convert to dict
d3 = {**d1,**d2} # Add the 2 dicts (in Py3.9+ use: d3=d1|d2)
d4 = {k: v for k, v in sorted(d3.items(), key=lambda item: item[1])} # sort dict
# "reverse" the dict key:value pairs, so we can use
# get('value') to obtain the key of the original
#d4r = dict(zip(d4.values(),d4.keys()))
#TF = d4.get('M15')
# Get the key, given a value
#TFk = [k for k in d4 if (d4[k] == TF)][0]
#TFk = next((k for k in d4 if d4[k] == TF), None)
#------------------------------------------------
# Text Coloring
#------------------------------------------------
# Usage: print(yellow("This is yellow"))
def color(text, color_code):
#if self.nposix:
#if not is_posix():
# return text
# for brighter colors, use "1;" in front of "color_code"
bright = '' # '1;'
return '\x1b[%s%sm%s\x1b[0m' % (bright, color_code, text)
def red(text): return color(text, 31) # # noqa
def green(text): return color(text, 32) # '1;49;32' # noqa
def bgreen(text): return color(text, '1;49;32') # bright green # noqa
def orange(text): return color(text, '0;49;91') # 31 - looks bad! # noqa
def yellow(text): return color(text, 33) # # noqa
def blue(text): return color(text, '1;49;34') # bright blue # noqa
def purple(text): return color(text, 35) # aka. magenta # noqa
def cyan(text): return color(text, '0;49;96') # 36 # noqa
def white(text): return color(text, '0;49;97') # bright white # noqa
#------------------------------------------------
# Print Usage
#------------------------------------------------
def usage():
myName = os.path.basename(__file__)
print('\n Usage: ./{}\n'.format(myName))
print(' This connects to an MT4 EA via a TCP port (or pipe) to receive OHLCV data.\n')
# [:cdhprvn:s:t:]
print(' ','-'*80)
print(' Command Line Options:')
# print(' -5 : Use script for MT5 installs (this will use MT5 DLL\'s)')
# print(' -c : Enable ANSI color markup of output')
print(' -d : Enable extra debug info')
#print(' -e : Show candle data as minimal CSV list (ignores -l)') # ???
print(' -n <n> : Show <n> number of candles back')
print(' -p : Use a "Named Pipe" connection (instead of a TCP IP host:port) ')
print(' -l : Show candle data as a list (one line per OHLC item)')
print(' -s <symbol> : Set symbol name to <symbol> ["EURUSD"]')
print(' -t <timeframe> : Set timeframe to <timeframe> [M1,M5,M15*,M30,H1,H4,D1,W1,MN1]**')
print(' -h, --help : Show THIS help list')
print(' -v, --version : Show THIS program version')
print(' ','-'*80)
print(' * = a default setting')
print(' ** = The standard MT4 TF\'s are:')
print(' [M1,M5,M15,M30,H1,H4,D1,W1,MN1]')
print(' [1,5,15,30,60,240,1440,10080,43200]\n')
print(' The non-standard TF\'s are: (not yet available)')
print(' [M2,M3,M4,M6,M10,M12,M20,H2,H3,H6,H8,H12]')
print(' [2,3,4,6,10,12,20,120,180,360,480,720] ')
print(' ','-'*80)
print('\n Example for Windows:')
#print(' python.exe .\\{} -n4 -s "CADCHF.x" -t 240 -p -d\n'.format(myName))
print(' python.exe .\\{} -n4 -s "CADCHF.x" -t H4 -p -d\n'.format(myName))
print(' Please file any bug reports at:')
print(' https://github.com/eabase/mt4pycon/\n')
print(' For support of the MT4/5 API, see:')
print(' https://github.com/vdemydiuk/mtapi/\n')
print(' Version: {}'.format(__version__))
print(' License: {}\n'.format(__copyright__))
sys.exit(2)
#----------------------------------------------------------
# Helper Functions
#----------------------------------------------------------
# Test if a given TF argument is a integer
def isArgInt(a) :
try:
int(a)
return True
except ValueError:
return False
def printTFerr(a,b) :
print(" ERROR: Bad TF argument: {} reverting to default: {} min.".format(a,b))
#----------------------------------------------------------
# Description:
# Get history data of MqlRates structure of a specified symbol-period
# in specified quantity into the ratesArray array. The elements ordering
# of the copied data is from present to the past, i.e., starting
# position of 0 means the current bar.
#
# NOTE:
#
# The CopyRates() implementations are different between MT4 and MT5.
#
# [1] https://docs.mql4.com/series/copyrates
# [2] https://www.mql5.com/en/docs/series/copyrates
#
# MT5: int CopyRates(string symbolName, ENUM_TIMEFRAMES timeframe, int startPos, int count, out MqlRates[] ratesArray)
# MT4: List<MqlRates> CopyRates(string symbolName, ENUM_TIMEFRAMES timeframe, int startPos, int count)
#----------------------------------------------------------
def getCopyRates(count,SYM,TF,useList,useCSV):
startPos = 1 # Starting position of candle (backward counting up!)
#count = 3 # Number of candles requested (to be received into Rate Array)
rA = [] # The "Rate Array" for Receiving results
ccnt = 0 # Number of candles actually recived (into array)
errn = 0 # The GetLastError "code" (supposedly?)
try:
# Check how many candles are available to download:
#cc = c.iBars(SYM,TF)
# if cc < count : print(' INFO: CopyRates: Not all candles are available!');
#ccnt = client.CopyRates(SYM, TF, startPos, count, rA) # MT5: int
rA = client.CopyRates(SYM, TF, startPos, count) # MT4: List<MqlRates>
ccnt = len(rA) # Rate Array length
except Exception as e:
print('\n ERROR: in CopyRates(): \n {}\n'.format(e))
if debug :
print(' DBG: CopyRates:ccnt = ', ccnt)
errn = client.GetLastError()
errd = client.ErrorDescription(errn) if errn else '<n/a>';
print(' DBG: CopyRates: ErrorCode : {:d}'.format(errn))
print(' DBG: CopyRates: ErrorDescription :\n {}'.format(errd))
exit()
errn = client.GetLastError();
#TFk = next((k for k in d4 if d4[k] == TF), None)
TFk = [k for k in d1 if (d1[k] == TF)][0]
#if debug :
print()
print(' ','-'*60)
print(' INFO: CopyRates: Symbol : {}'.format(SYM))
print(' INFO: CopyRates: TimeFrame : {}'.format(TFk))
print(' INFO: CopyRates: Requested : {:d} candles'.format(count))
print(' INFO: CopyRates: Received : {:d} candles'.format(ccnt))
print(' INFO: CopyRates: ErrorCode : {:d}'.format(errn)) # rA[0].ErrorCode))
print(' ','-'*60)
print(' INFO: CopyRates: Rate Array (rA):\n')
#--------------------------------------------
# Response = {"ErrorCode" : 0, "Rates" :
# [{ "Close" : 1.17094,
# "Open" : 1.17206,
# "MtTime" : 1604432700,
# "Low" : 1.17079,
# "High" : 1.17207,
# "TickVolume" : 1251,
# "RealVolume" : 0,
# "Spread" : 0}, ...
#--------------------------------------------
# We skip: rA[i].Spread (see notes)
#--------------------------------------------
if useCSV :
csv_th = 'Time,Open,High,Low,Close,Volume'
csv_tf = '{},{:.5f},{:.5f},{:.5f},{:.5f},{:d}'
print(csv_th)
for i in range(ccnt):
print(csv_tf.format(rA[i].Time, rA[i].Open, rA[i].High, rA[i].Low, rA[i].Close, rA[i].TickVolume))
sys.exit(2)
if useList :
myLformat = '[{}]: {}\nO: {}\nH: {}\nL: {}\nC: {}\nV: {:d}\n' # S: {:.2f}
for i in range(ccnt):
print(myLformat.format(i, rA[i].Time, rA[i].Open, rA[i].High, rA[i].Low, rA[i].Close, rA[i].TickVolume))
else :
tableHeader = ' {:<4} {:<19} {:<7} {:<7} {:<7} {:<7} {:<6}'
header_str = tableHeader.format('#','Time','Open', 'High', 'Low', 'Close', 'Volume')
# 0: 2020-11-04 01:45:00 1.17531 1.17616 1.17500 1.17613 590
hlen = len(header_str)
print(header_str)
print(' ','-'*hlen)
myRformat = ' {:<3}: {} {:.5f} {:.5f} {:.5f} {:.5f} {:d}'
for i in range(ccnt):
print(myRformat.format(i, rA[i].Time, rA[i].Open, rA[i].High, rA[i].Low, rA[i].Close, rA[i].TickVolume))
#----------------------------------------------------------
# ToDo
#----------------------------------------------------------
#def getLiveTicks():
# Here be dragons
# SymbolInfoTick
# MqlTick tick;
# if (!SymbolInfoTick(symbol, tick)) {
# response = CreateErrorResponse(GetLastError(), "SymbolInfoDouble failed");
# return;
# }
#SymbolInfoTick
#----------------------------------------------------------
# MAIN
#----------------------------------------------------------
if __name__ == '__main__':
#def main(self):
#----------------------------------------------------------
# CLI arguments
#----------------------------------------------------------
dTF = d1.get('M15')
nmax = 3 # Default number of candles
mtVer = 4 # Default to MT4 API version [4,5]
aSym = str(MTSYMB) # Default Symbol()
aTF = dTF # Default TimeFrame [Default: M15]
apipe = 0 # Use a Named Pipe port for connection (instead of TCP host:port)
alist = 0 #
useCSV = 0 # ... CSV
narg = len(sys.argv) - 1
try:
opts, args = getopt.getopt(sys.argv[1:], ":cdehpvln:s:t:", ["help", "version"])
except getopt.GetoptError :
usage(); sys.exit();
if not opts :
if not args :
usage(); sys.exit();
else :
for opt, arg in opts:
if (debug) :
print ("opt: ", opt)
print ("arg: ", arg)
if opt in ("-h", "--help"): usage(); sys.exit();
elif opt in ("-v", "--version"): print ("Version: ", __version__); sys.exit();
#elif opt == "-c": useColors=1; # Use colored markup on: (Time, Volume, OHCL)
elif opt == "-d": debug = 1; #
elif opt == "-e": useCSV = 1; # print OHLCV data as CSV
elif opt == "-l": alist = 1; # print each OHLCV on new line
elif opt == "-p": apipe = 1; # use named pipe connection
elif opt == "-n": nmax = int(arg); # Number of received candles
elif opt == "-s": aSym = str(arg); # Instrument Symbol
elif opt == "-t": # <timeframe>
if (isArgInt(arg)) : #
if int(arg) in d1.values() : # check if '30' in d1
aTF = int(arg) # convert '30' to 30
else :
printTFerr(arg,dTF)
continue
else:
if arg in d1.keys() : # check if 'M30' in d1
aTF = d1.get(arg) # get 30 from 'M30'
else :
printTFerr(arg,dTF)
continue
#----------------------------------------------------------
#client = mt.MtApi5Client()
client = mt.MtApiClient()
ip = str(MTSERV) # Host IP
port = MTPORT # EA port
sleeps = 10 # Sleep this many times
cnt = 1 #
print('\n INFO: Attmpting to Connect to: {}:{}'.format(ip,port))
if (debug): print(' DBG : using {}'.format('a name pipe' if apipe else 'TCP IP'))
try:
while sleeps > 0:
print(' [{}] Connecting '.format(cnt), end='')
if (apipe == 0) :
client.BeginConnect(ip, port)
else :
client.BeginConnect(port)
time.sleep(1)
sleeps = sleeps - 1
cnt += 1
#------------------------------------
# Check Connection state:
#------------------------------------
# 0 = Disconnected
# 1 = Connecting
# 2 = Connected
# 3 = Failed
#------------------------------------
if client.ConnectionState == 0: print('Disconnected'); continue;
if client.ConnectionState == 1: print('.', end=''); continue;
if client.ConnectionState == 2: print('OK'); getCopyRates(nmax,aSym,aTF,alist,useCSV); break
if client.ConnectionState == 3: print('FAILED'); break
if (debug): print(' DBG: Unknown ConnectionState: ', client.ConnectionState)
except Exception as e:
print(e)
client.BeginDisconnect()
if (debug): print('\n DBG: Disconnected!')
print('\n Done.')
#----------------------------------------------------------
# END
#----------------------------------------------------------
#if __name__ == "__main__":
# main()
# sys.exit(0)