-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
con2mtapi.py
executable file
·465 lines (420 loc) · 19.8 KB
/
con2mtapi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
#!/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")
#sys.path.append(r"C:\Program Files (x86)\MtApi")
sys.path.append(r".\libs")
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)