├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── examples
├── cli.py
├── ddeclient.py
├── ddefeeder.py
├── externaldeal.py
└── server.py
├── pymt5
├── __init__.py
└── pymt5.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /dist/
3 | /pymt5.egg-info/
4 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2020 DevCartel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | # Include the license file
2 | include LICENSE.txt
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyMT5
2 | [](https://pypi.org/project/pymt5)
3 | [](#)
4 | [](#platform-availability)
5 | [](https://github.com/devcartel/pymt5/blob/master/LICENSE.txt)
6 | [](https://pypi.org/project/pymt5)
7 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CKHUBNTFNDCB8)
8 |
9 | Provides simplified, multithreaded, socket-based Python interfaces to MT5 gateways. PyMT5 requires a [DevCartel MT5 gateway](https://github.com/devcartel/devcartelgateway64) installed on the MT5 platform to work with PyMT5.
10 |
11 |
12 |
13 |
14 |
15 | ## Installation
16 | PyMT5 supports both Python 2 and 3. Simply install from [PyPI](https://pypi.org/project/pymt5) using `pip`:
17 |
18 | pip install pymt5
19 |
20 | ## Example
21 | ```python
22 | import pymt5
23 |
24 | def onData(data):
25 | client = data.get('client_id')
26 | # Login
27 | if data.get('type') == '1':
28 | # Send heartbeat
29 | m.send(client, {'ver':'3','type':'6'})
30 | # Send login OK response
31 | m.send(client, {'ver':'3',
32 | 'type':'1',
33 | 'login':data.get('login'),
34 | 'password':data.get('password'),
35 | 'res':'0'})
36 |
37 | m = pymt5.PyMT5()
38 | m.onConnected = onConnected
39 | m.onDisconnected = onDisconnected
40 | m.onData = onData
41 |
42 | ```
43 |
44 | Checkout more message [examples](https://github.com/devcartel/pymt5/tree/master/examples).
45 |
46 | ## API
47 | __pymt5.PyMT5([_host=''_], [_port=16838_])__
48 | _host: str_
49 | _port: int_
50 | _➥return: object_
51 | Starts a PyMT5 server and listening on a port defined by _port_.
52 |
53 | >> m = pymt5.PyMT5()
54 |
55 | Upon incoming connection from a gateway, PyMT5 stores client information in `pymt5.requests` in dict format as
56 |
57 | __pymt5.stop()__
58 | Disconnects all MT5 gateway connections and stop the server.
59 |
60 | >> m.stop()
61 |
62 | __pymt5.broadcast(_data_)__
63 | _data: dict_
64 | Sends a message to all connected gateways. Consider using this when sending market data.
65 |
66 | >> #send a tick
67 | >> m.broadcast({'ver':'3','type':'4','symbol':'EURUSD.TEST','bank':'dc','bid':'1.2661','ask':'1.2665','last':'1.2665','volume':'1','datetime':'0'})
68 |
69 | __pymt5.send(client_id, _data_)__
70 | client_id: int
71 | _data: dict_
72 | Sends a message to a connected gateway.
73 |
74 | >> #send heartbeat
75 | >> m.send(123145536110592, {'ver':'3','type':'6'})
76 |
77 | __pymt5.disconnect(client_id)__
78 | client_id: int
79 | Terminates a connection.
80 |
81 | >> m.disconnect(123145536110592)
82 |
83 | __pymt5.onConnected(client_info)__
84 | client_info: dict
85 | A callback `onConnected`, if assigned, is called upon a successful connection from a client. Client information can be accessed from `client_info`'s values as `client_id`, `client_address` and `client_port`.
86 |
87 | >> def onConnected(client_info):
88 | >> print(str(client_info))
89 | >> # print {'client_port': 64941, 'client_address': '127.0.0.1', 'client_id': 123145536110592}
90 | >>
91 | >> m = pymt5.PyMT5()
92 | >> m.onConnected = onConnected
93 |
94 | __pymt5.onDisconnected(client_info)__
95 | client_info: dict
96 | A callback `onDisconnected`, if assigned, is called upon a disconnection from a client. Client information can be accessed from `client_info`'s values as `client_id`, `client_address` and `client_port`.
97 |
98 | >> def onDisonnected(client_info):
99 | >> print(str(client_info))
100 | >>
101 | >> m = pymt5.PyMT5()
102 | >> m.onDisconnected = onDisconnected
103 |
104 | __pymt5.onData(_data_)__
105 | _data: dict_
106 | A callback `onData`, if assigned, is called upon receiving messages from gateways. See [Data Format](#data-format) for more information.
107 |
108 | >> def onData(data):
109 | >> print(json.dumps(data))
110 | >>
111 | >> m = pymt5.PyMT5()
112 | >> m.onData = onData
113 |
114 | ## Data Format
115 | Data is to be composed as a dict with key/value defined below to be sent and received from a gateway.
116 |
117 | | Data type | Header | Tags |
118 | | ------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
119 | | Login | `'ver':'3','type':1'` | `'login'`,`'password'`,`'res'` |
120 | | Logout | `'ver':'3','type':2'` | _None_ |
121 | | Symbol | `'ver':'3','type':3'` | `'index'`,`'symbol'`,`'path'`,`'description'`,`'page'`,
`'currency_base'`,`'currency_profit'`,`'currency_margin'`,
`'digits'`,`'tick_flags'`,`'calc_mode'`,`'exec_mode'`,
`'chart_mode'`,`'fill_flags'`,`'expir_flags'`,`'tick_value'`,
`'tick_size'`,`'contract_size'`,`'volume_min'`,`'volume_max'`,
`'volume_step'`,`'market_depth'`,`'margin_flags'`,
`'margin_initial'`,`'margin_maintenance'`,`'margin_long'`,
`'margin_short'`,`'margin_limit'`,
`'margin_stop'`,`'margin_stop_limit'`,`'settlement_price'`,
`'price_limit_max'`,`'price_limit_min'`,`'time_start'`,
`'time_expiration'`,`'trade_mode'` |
122 | | Tick | `'ver':'3','type':4'` | `'symbol'`,`'bank'`,`'bid'`,`'ask'`,`'last'`,`'volume'`,`'datetime'` |
123 | | Order | `'ver':'3','type':5'` | `'symbol'`,`'bank'`,`'bid'`,`'ask'`,`'last'`,`'volume'`,`'datetime'`,
`'order_action'`,`'state'`,`'order'`,`'exchange_id'`,
`'custom_data'`,`'request_id'`,`'symbol'`,`'login'`,
`'type_order'`,`'type_time'`,`'type_fill'`,`'action'`,`'price_order'`,
`'price_sl'`,`'price_tp'`,`'price_tick_bid'`,`'price_tick_ask'`,
`'volume'`,`'expiration_time'`,`'result'` |
124 | | Heartbeat | `'ver':'3','type':6'` | _None_ |
125 | | Deal | `'ver':'3','type':7'` | `'exchange_id'`,`'order'`,`'symbol'`,`'login'`,`'type_deal'`,
`'volume'`,`'volume_rem'`,`'price'`,`'position'` |
126 | | External Deal | `'ver':'3','type':50'` | `'exchange_id'`,`'order'`,`'symbol'`,`'login'`,`'type_deal'`,
`'volume'`,`'volume_rem'`,`'price'`,`'datetime'`,`'comment'` |
127 |
128 | ## Support
129 | * Get a [DevCartel MT5 Gateway](http://devcartel.com/devcartelgateway64) in order to work with PyMT5
130 | * Report an issue in [issue tracker](https://github.com/devcartel/pymt5/issues)
131 |
132 | ## Changelog
133 | 1.4.0
134 | * 30 October 2022
135 | * Fix potential data loss due to data fragmentation
136 |
137 | 1.3.0
138 | * 8 August 2021
139 | * Fix parsing data buffer
140 |
141 | 1.2.0
142 | * 8 July 2019
143 | * Support for Python 3.7
144 | * Update support links
145 | * Add examples
146 |
147 | 1.1.0
148 | * 21 April 2018
149 | * Released on PyPI
150 | * Added README
151 |
152 | 1.0.0
153 | * 13 April 2018
154 | * Initial release
155 |
--------------------------------------------------------------------------------
/examples/cli.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('../')
3 | import pymt5
4 | import time
5 | import signal
6 | from collections import OrderedDict
7 |
8 | client = ''
9 | order = deal = OrderedDict()
10 | m = None
11 |
12 | def onConnected(client_info):
13 | global client
14 | client = client_info.get('client_id')
15 | print('[onConnected] client_address: ' + str(client_info))
16 |
17 | def onDisconnected(client_info):
18 | global client
19 | client = ''
20 | print('[onDisconnected] client_address: ' + str(client_info))
21 |
22 | def sendOrderConfirmed(client, order):
23 | order.update({'exchange_id':str(int(time.time()))})
24 | if order['type_order'] == '0':
25 | order.update({'price_order':'1.2665'})
26 | else:
27 | order.update({'price_order':'1.2661'})
28 | order.update({'state':'1'})
29 | order.update({'result':'10009'})
30 | m.send(client, order)
31 |
32 | def sendOrderPlaced(client, order):
33 | order.update({'state':'2'})
34 | order.update({'result':'10008'})
35 | m.send(client, order)
36 |
37 | def sendOrderNew(client, order):
38 | order.update({'state':'3'})
39 | order.update({'result':'1'})
40 | m.send(client, order)
41 |
42 | def sendOrderDeal(client, order):
43 | order.update({'state':'5'})
44 | order.update({'result':'1'})
45 | m.send(client, order)
46 |
47 | def sendOrderRejectNew(client, order):
48 | order.update({'state':'4'})
49 | order.update({'result':'10006'})
50 | m.send(client, order)
51 |
52 | def sendOrderCancel(client, order):
53 | order.update({'state':'10'})
54 | order.update({'result':'10007'})
55 | m.send(client, order)
56 |
57 | def sendOrderComplete(client, order):
58 | order.update({'state':'20'})
59 | order.update({'result':'10009'})
60 | m.send(client, order)
61 |
62 | def sendDeal(client, order, price):
63 | global deal
64 | deal = OrderedDict([('ver','3'),
65 | ('type','7'),
66 | ('exchange_id',str(int(time.time()))),
67 | ('order',order['order']),
68 | ('symbol',order['symbol']),
69 | ('login',order['login']),
70 | ('type_deal',order['type_order']),
71 | ('volume',order['volume']),
72 | ('volume_rem','0'),
73 | ('price',price)])
74 | m.send(client, deal)
75 |
76 | def sendDealExternal(client):
77 | m.send(client, OrderedDict([('ver','3'),('type','50'),('exchange_id',str(int(time.time()))),('order','0'),('symbol','XAUUSD.G9'),('login','1000014'),('type_deal','0'),('volume','1'),('volume_rem','0'),('price','1344.80')]))
78 |
79 | def sendTick():
80 | m.broadcast(OrderedDict([('ver','3'),('type','4'),('symbol','EURUSD.TEST'),('bank','dc'),('bid','1.2661'),('ask','1.2665'),('last','1.2665'),('volume','1'),('datetime','0')]))
81 |
82 | def onData(data):
83 | client = data.get('client_id')
84 | # Login
85 | if data.get('type') == '1':
86 | print('[onData] data: ' + str(data))
87 | # Send heartbeat
88 | m.send(client, OrderedDict([('ver','3'),
89 | ('type','6')]))
90 | # Send login OK response
91 | m.send(client, OrderedDict([('ver','3'),
92 | ('type','1'),
93 | ('login',data.get('login')),
94 | ('password',data.get('password')),
95 | ('res','0')]))
96 | # Sync a symbol
97 | time.sleep(0.5)
98 | m.send(client, OrderedDict([('ver','3'),
99 | ('type','3'),
100 | ('index','0'),
101 | ('symbol','EURUSD.TEST'),
102 | ('path','TestExchange'),
103 | ('description','test'),
104 | ('page','http://www.google.co/finance?q=EURUSD.TEST'),
105 | ('currency_base','EUR'),
106 | ('currency_profit','USD'),
107 | ('currency_margin','USD'),
108 | ('digits','4'),
109 | ('tick_flags','7'),
110 | ('calc_mode','0'),
111 | ('exec_mode','2'),
112 | ('chart_mode','0'),
113 | ('fill_flags','3'),
114 | ('expir_flags','15'),
115 | ('tick_value','0.00000'),
116 | ('tick_size','0.00000'),
117 | ('contract_size','100000.00000'),
118 | ('volume_min','0.00000'),
119 | ('volume_max','10000.00000'),
120 | ('volume_step','0.01000'),
121 | ('market_depth','0'),
122 | ('margin_flags','0'),
123 | ('margin_initial','0.00000'),
124 | ('margin_maintenance','0.00000'),
125 | ('margin_long','1.00000'),
126 | ('margin_short','1.00000'),
127 | ('margin_limit','0.00000'),
128 | ('margin_stop','0.00000'),
129 | ('margin_stop_limit','0.00000'),
130 | ('settlement_price','1.22804'),
131 | ('price_limit_max','0.00000'),
132 | ('price_limit_min','0.00000'),
133 | ('time_start','0'),
134 | ('time_expiration','0'),
135 | ('trade_mode','4')]))
136 | m.send(client, OrderedDict([('ver','3'),
137 | ('type','3'),
138 | ('index','0'),
139 | ('symbol','XAUUSD.G9'),
140 | ('path','TestExchange'),
141 | ('description','test'),
142 | ('page','http://www.google.co/finance?q=XAUUSD.TEST'),
143 | ('currency_base','USD'),
144 | ('currency_profit','USD'),
145 | ('currency_margin','USD'),
146 | ('digits','2'),
147 | ('tick_flags','7'),
148 | ('calc_mode','0'),
149 | ('exec_mode','2'),
150 | ('chart_mode','0'),
151 | ('fill_flags','3'),
152 | ('expir_flags','15'),
153 | ('tick_value','0.00000'),
154 | ('tick_size','0.00000'),
155 | ('contract_size','100'),
156 | ('volume_min','0.10'),
157 | ('volume_max','100.00'),
158 | ('volume_step','0.01'),
159 | ('market_depth','0'),
160 | ('margin_flags','0'),
161 | ('margin_initial','0.00'),
162 | ('margin_maintenance','0.00'),
163 | ('margin_long','1.00'),
164 | ('margin_short','1.00'),
165 | ('margin_limit','0.00'),
166 | ('margin_stop','0.00'),
167 | ('margin_stop_limit','0.00'),
168 | ('settlement_price','0.00'),
169 | ('price_limit_max','0.00'),
170 | ('price_limit_min','0.00'),
171 | ('time_start','0'),
172 | ('time_expiration','0'),
173 | ('trade_mode','4')]))
174 | # Send a tick
175 | sendTick()
176 | # New order
177 | elif data.get('type') == '5' and data.get('state') == '0':
178 | print('[onData] data: ' + str(data))
179 | global deal
180 | global order
181 | order = data
182 | del order['client_id']
183 | # sendOrderConfirmed(client, order)
184 | # sendOrderPlaced(client, order)
185 | # sendOrderNew(client, order)
186 | # time.sleep(0.1)
187 |
188 | m = pymt5.PyMT5()
189 | m.onConnected = onConnected
190 | m.onDisconnected = onDisconnected
191 | m.onData = onData
192 |
193 |
194 |
195 | # Flow on new order
196 | # sendOrderPlaced -> sendOrderNew -> sendDeal
197 |
198 | #m.send(client, OrderedDict([('ver','3'),('type','7'),('exchange_id','3'),('order','4056'),('symbol','EURUSD.TEST'),('login','1000014'),('type_deal','0'),('volume','1'),('volume_rem','1'),('price','1.2665')]))
199 | #
200 | #OrderedDict([('ver', '3'), ('type', '5'), ('order_action', '1'), ('state', '0'), ('order', '4059'), ('exchange_id', '0'), ('custom_data', '0'), ('request_id', '12995002'), ('symbol', 'EURUSD.TEST'), ('login', '1000014'), ('type_order', '0'), ('type_time', '0'), ('action', '3'), ('price_order', '0.00000'), ('price_sl', '0.00000'), ('price_tp', '0.00000'), ('price_tick_bid', '0.00000'), ('price_tick_ask', '0.00000'), ('volume', '0'), ('expiration_time', '0'), ('result', '0'), ('client_id', 140558617073408)])
201 | #
202 | #m.send(client, OrderedDict([('ver', '3'), ('type', '5'), ('order_action', '1'), ('state', '2'), ('order', '4059'), ('exchange_id', '2'), ('custom_data', '0'), ('request_id', '12995002'), ('symbol', 'EURUSD.TEST'), ('login', '1000014'), ('type_order', '0'), ('type_time', '0'), ('action', '3'), ('price_order', '0.00000'), ('price_sl', '0.00000'), ('price_tp', '0.00000'), ('price_tick_bid', '0.00000'), ('price_tick_ask', '0.00000'), ('volume', '0'), ('expiration_time', '0'), ('result', '0')]))
203 |
204 | # ORDER_STATE_UNKNOWN =0, // undefined state
205 | # ORDER_STATE_CONFIRMED =1, // order confirmed
206 | # ORDER_STATE_REQUEST_PLACED =2, // order generation request placed
207 | # ORDER_STATE_NEW =3, // create a new order
208 | # ORDER_STATE_REJECT_NEW =4, // order rejected
209 | # ORDER_STATE_DEAL =5, // a deal by order sent
210 | # ORDER_STATE_REQUEST_MODIFY =6, // order modification request received
211 | # ORDER_STATE_MODIFY =7, // order modified
212 | # ORDER_STATE_REJECT_MODIFY =8, // order modification rejected
213 | # ORDER_STATE_REQUEST_CANCEL =9, // order cancelation request received
214 | # ORDER_STATE_CANCEL =10, // order canceled
215 | # ORDER_STATE_REJECT_CANCEL =11, // order cancelation rejected
216 | # ORDER_STATE_COMPLETED =20 // operation on the order complete, can be removed from the queue
217 |
218 | #define MSG_TAG_ORDER_ACTION_TYPE "order_action=" // order action
219 | #define MSG_TAG_ORDER_STATE "state=" // order state
220 | #define MSG_TAG_ORDER_MT_ID "order=" // order ticket
221 | #define MSG_TAG_ORDER_EXCHANGE_ID "exchange_id=" // order id in external system
222 | #define MSG_TAG_ORDER_CUSTOM_DATA "custom_data=" // custom data
223 | #define MSG_TAG_ORDER_REQUEST_ID "request_id=" // request id
224 | #define MSG_TAG_ORDER_SYMBOL "symbol=" // symbol
225 | #define MSG_TAG_ORDER_LOGIN "login=" // client's login
226 | #define MSG_TAG_ORDER_TYPE_ORDER "type_order=" // order type
227 | #define MSG_TAG_ORDER_TYPE_TIME "type_time=" // order expiration type
228 | #define MSG_TAG_ORDER_ACTION "action=" // action
229 | #define MSG_TAG_ORDER_PRICE_ORDER "price_order=" // order price
230 | #define MSG_TAG_ORDER_PRICE_SL "price_sl=" // Stop Loss level
231 | #define MSG_TAG_ORDER_PRICE_TP "price_tp=" // Take Profit level
232 | #define MSG_TAG_ORDER_PRICE_TICK_BID "price_tick_bid=" // symbol bid price in external trading system
233 | #define MSG_TAG_ORDER_PRICE_TICK_ASK "price_tick_ask=" // symbol ask price in external trading system
234 | #define MSG_TAG_ORDER_VOLUME "volume=" // order volume
235 | #define MSG_TAG_ORDER_EXPIRATION_TIME "expiration_time=" // expiration time
236 | #define MSG_TAG_ORDER_RESULT "result=" // result of message processing
237 |
238 | #//+------------------------------------------------------------------+
239 | #//| Deal message tags |
240 | #//+------------------------------------------------------------------+
241 | #define MSG_TAG_DEAL_EXCHANGE_ID "exchange_id=" // deal id in external system
242 | #define MSG_TAG_DEAL_ORDER "order=" // order ticket
243 | #define MSG_TAG_DEAL_SYMBOL "symbol=" // symbol
244 | #define MSG_TAG_DEAL_LOGIN "login=" // login
245 | #define MSG_TAG_DEAL_TYPE "type_deal=" // action
246 | #define MSG_TAG_DEAL_VOLUME "volume=" // volume
247 | #define MSG_TAG_DEAL_VOLUME_REM "volume_rem=" // volume remaind
248 | #define MSG_TAG_DEAL_PRICE "price=" // price
249 |
250 |
251 | #//--- form the string value of order action type
252 | # switch(order.action)
253 | # {
254 | # case IMTRequest::TA_PRICE : res.Assign(L"prices for"); break;
255 | # case IMTRequest::TA_REQUEST : res.Assign(L"request"); break;
256 | # case IMTRequest::TA_INSTANT : res.Assign(L"instant"); break;
257 | # case IMTRequest::TA_MARKET : res.Assign(L"market"); break;
258 | # case IMTRequest::TA_EXCHANGE : res.Assign(L"exchange"); break;
259 | # case IMTRequest::TA_PENDING : res.Assign(L"pending"); break;
260 | # case IMTRequest::TA_SLTP : res.Assign(L"modify"); break;
261 | # case IMTRequest::TA_MODIFY : res.Assign(L"modify"); break;
262 | # case IMTRequest::TA_REMOVE : res.Assign(L"cancel"); break;
263 | # case IMTRequest::TA_ACTIVATE : res.Assign(L"activate"); break;
264 | # case IMTRequest::TA_ACTIVATE_SL : res.Assign(L"activate stop loss"); break;
265 | # case IMTRequest::TA_ACTIVATE_TP : res.Assign(L"activate take profit"); break;
266 | # case IMTRequest::TA_ACTIVATE_STOPLIMIT : res.Assign(L"activate stop-limit order"); break;
267 | # case IMTRequest::TA_STOPOUT_ORDER : res.Assign(L"delete stop-out order"); break;
268 | # case IMTRequest::TA_STOPOUT_POSITION : res.Assign(L"close stop-out position"); break;
269 | # case IMTRequest::TA_EXPIRATION : res.Assign(L"expire"); break;
270 | # default : res.Assign(L"order"); break;
271 | # }
272 |
273 | #//+------------------------------------------------------------------+
274 | #//| Position message tags |
275 | #//+------------------------------------------------------------------+
276 | #define MSG_TAG_POSITION_SYMBOL "pos_symbol=" // symbol
277 | #define MSG_TAG_POSITION_LOGIN "pos_login=" // client's login
278 | #define MSG_TAG_POSITION_PRICE "pos_price=" // position opening price
279 | #define MSG_TAG_POSITION_VOLUME "pos_volume=" // position volume
280 | #define MSG_TAG_POSITION_DIGITS "pos_digits=" // price digits
281 |
--------------------------------------------------------------------------------
/examples/ddeclient.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import print_function
3 | import sys
4 | from ctypes import c_int, c_double, c_char_p, c_void_p, c_ulong, c_char, pointer, cast
5 | from ctypes import windll, byref, create_string_buffer, Structure, sizeof
6 | from ctypes import POINTER, WINFUNCTYPE
7 | from ctypes.wintypes import BOOL, HWND, MSG, DWORD, BYTE, INT, LPCWSTR, UINT, ULONG, LPCSTR
8 |
9 | # DECLARE_HANDLE(name) typedef void *name;
10 | HCONV = c_void_p # = DECLARE_HANDLE(HCONV)
11 | HDDEDATA = c_void_p # = DECLARE_HANDLE(HDDEDATA)
12 | HSZ = c_void_p # = DECLARE_HANDLE(HSZ)
13 | LPBYTE = c_char_p # POINTER(BYTE)
14 | LPDWORD = POINTER(DWORD)
15 | LPSTR = c_char_p
16 | ULONG_PTR = c_ulong
17 |
18 | # See windows/ddeml.h for declaration of struct CONVCONTEXT
19 | PCONVCONTEXT = c_void_p
20 |
21 | # DDEML errors
22 | DMLERR_NO_ERROR = 0x0000 # No error
23 | DMLERR_ADVACKTIMEOUT = 0x4000 # request for synchronous advise transaction timed out
24 | DMLERR_DATAACKTIMEOUT = 0x4002 # request for synchronous data transaction timed out
25 | DMLERR_DLL_NOT_INITIALIZED = 0x4003 # DDEML functions called without iniatializing
26 | DMLERR_EXECACKTIMEOUT = 0x4006 # request for synchronous execute transaction timed out
27 | DMLERR_NO_CONV_ESTABLISHED = 0x400a # client's attempt to establish a conversation has failed (can happen during DdeConnect)
28 | DMLERR_POKEACKTIMEOUT = 0x400b # A request for a synchronous poke transaction has timed out.
29 | DMLERR_POSTMSG_FAILED = 0x400c # An internal call to the PostMessage function has failed.
30 | DMLERR_SERVER_DIED = 0x400e
31 |
32 | # Predefined Clipboard Formats
33 | CF_TEXT = 1
34 | CF_BITMAP = 2
35 | CF_METAFILEPICT = 3
36 | CF_SYLK = 4
37 | CF_DIF = 5
38 | CF_TIFF = 6
39 | CF_OEMTEXT = 7
40 | CF_DIB = 8
41 | CF_PALETTE = 9
42 | CF_PENDATA = 10
43 | CF_RIFF = 11
44 | CF_WAVE = 12
45 | CF_UNICODETEXT = 13
46 | CF_ENHMETAFILE = 14
47 | CF_HDROP = 15
48 | CF_LOCALE = 16
49 | CF_DIBV5 = 17
50 | CF_MAX = 18
51 |
52 | # DDE constants for wStatus field
53 | DDE_FACK = 0x8000
54 | DDE_FBUSY = 0x4000
55 | DDE_FDEFERUPD = 0x4000
56 | DDE_FACKREQ = 0x8000
57 | DDE_FRELEASE = 0x2000
58 | DDE_FREQUESTED = 0x1000
59 | DDE_FAPPSTATUS = 0x00FF
60 | DDE_FNOTPROCESSED = 0x0000
61 |
62 | DDE_FACKRESERVED = (~(DDE_FACK | DDE_FBUSY | DDE_FAPPSTATUS))
63 | DDE_FADVRESERVED = (~(DDE_FACKREQ | DDE_FDEFERUPD))
64 | DDE_FDATRESERVED = (~(DDE_FACKREQ | DDE_FRELEASE | DDE_FREQUESTED))
65 | DDE_FPOKRESERVED = (~(DDE_FRELEASE))
66 |
67 | # DDEML Transaction class flags
68 | XTYPF_NOBLOCK = 0x0002
69 | XTYPF_NODATA = 0x0004
70 | XTYPF_ACKREQ = 0x0008
71 |
72 | XCLASS_MASK = 0xFC00
73 | XCLASS_BOOL = 0x1000
74 | XCLASS_DATA = 0x2000
75 | XCLASS_FLAGS = 0x4000
76 | XCLASS_NOTIFICATION = 0x8000
77 |
78 | XTYP_ERROR = (0x0000 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK)
79 | XTYP_ADVDATA = (0x0010 | XCLASS_FLAGS)
80 | XTYP_ADVREQ = (0x0020 | XCLASS_DATA | XTYPF_NOBLOCK)
81 | XTYP_ADVSTART = (0x0030 | XCLASS_BOOL)
82 | XTYP_ADVSTOP = (0x0040 | XCLASS_NOTIFICATION)
83 | XTYP_EXECUTE = (0x0050 | XCLASS_FLAGS)
84 | XTYP_CONNECT = (0x0060 | XCLASS_BOOL | XTYPF_NOBLOCK)
85 | XTYP_CONNECT_CONFIRM = (0x0070 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK)
86 | XTYP_XACT_COMPLETE = (0x0080 | XCLASS_NOTIFICATION )
87 | XTYP_POKE = (0x0090 | XCLASS_FLAGS)
88 | XTYP_REGISTER = (0x00A0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK )
89 | XTYP_REQUEST = (0x00B0 | XCLASS_DATA )
90 | XTYP_DISCONNECT = (0x00C0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK )
91 | XTYP_UNREGISTER = (0x00D0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK )
92 | XTYP_WILDCONNECT = (0x00E0 | XCLASS_DATA | XTYPF_NOBLOCK)
93 | XTYP_MONITOR = (0x00F0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK)
94 |
95 | XTYP_MASK = 0x00F0
96 | XTYP_SHIFT = 4
97 |
98 | # DDE Timeout constants
99 | TIMEOUT_ASYNC = 0xFFFFFFFF
100 |
101 | # DDE Application command flags / Initialization flag (afCmd)
102 | APPCMD_CLIENTONLY = 0x00000010
103 |
104 | # Code page for rendering string.
105 | CP_WINANSI = 1004 # default codepage for windows & old DDE convs.
106 | CP_WINUNICODE = 1200
107 |
108 | # Declaration
109 | DDECALLBACK = WINFUNCTYPE(HDDEDATA, UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, ULONG_PTR, ULONG_PTR)
110 |
111 | # PyZDDE specific globals
112 | number_of_apps_communicating = 0 # to keep an account of the number of zemax
113 | # server objects --'ZEMAX', 'ZEMAX1' etc
114 |
115 | class CreateServer(object):
116 | """This is really just an interface class so that PyZDDE can use either the
117 | current dde code or the pywin32 transparently. This object is created only
118 | once. The class name cannot be anything else if compatibility has to be maintained
119 | between pywin32 and this dde code.
120 | """
121 | def __init__(self):
122 | self.serverName = 'None'
123 |
124 | def Create(self, client):
125 | """Set a DDE client that will communicate with the DDE server
126 |
127 | Parameters
128 | ----------
129 | client : string
130 | Name of the DDE client, most likely this will be 'ZCLIENT'
131 | """
132 | self.clientName = client # shall be used in `CreateConversation`
133 |
134 | def Shutdown(self, createConvObj):
135 | """The shutdown should ideally be requested only once per CreateConversation
136 | object by the PyZDDE module, but for ALL CreateConversation objects, if there
137 | are more than one. If multiple CreateConversation objects were created and
138 | then not cleared, there will be memory leak, and eventually the program will
139 | error out when run multiple times
140 |
141 | Parameters
142 | ----------
143 | createConvObj : CreateConversation object
144 |
145 | Exceptions
146 | ----------
147 | An exception occurs if a Shutdown is attempted with a CreateConvObj that
148 | doesn't have a conversation object (connection with ZEMAX established)
149 | """
150 | global number_of_apps_communicating
151 | #print("Shutdown requested by {}".format(repr(createConvObj))) # for debugging
152 | if number_of_apps_communicating > 0:
153 | #print("Deleting object ...") # for debugging
154 | createConvObj.ddec.__del__()
155 | number_of_apps_communicating -=1
156 |
157 |
158 | class CreateConversation(object):
159 | """This is really just an interface class so that PyZDDE can use either the
160 | current dde code or the pywin32 transparently.
161 |
162 | Multiple objects of this type may be instantiated depending upon the
163 | number of simultaneous channels of communication with Zemax that the user
164 | program wants to establish using `ln = pyz.PyZDDE()` followed by `ln.zDDEInit()`
165 | calls.
166 | """
167 | def __init__(self, ddeServer):
168 | """
169 | Parameters
170 | ----------
171 | ddeServer :
172 | d
173 | """
174 | self.ddeClientName = ddeServer.clientName
175 | self.ddeServerName = 'None'
176 | self.ddetimeout = 50 # default dde timeout = 50 seconds
177 |
178 | def ConnectTo(self, appName, data=None):
179 | """Exceptional error is handled in zdde Init() method, so the exception
180 | must be re-raised"""
181 | global number_of_apps_communicating
182 | self.ddeServerName = appName
183 | try:
184 | self.ddec = DDEClient(self.ddeServerName, self.ddeClientName) # establish conversation
185 | except DDEError:
186 | raise
187 | else:
188 | number_of_apps_communicating +=1
189 | #print("Number of apps communicating: ", number_of_apps_communicating) # for debugging
190 |
191 | def Request(self, item, timeout=None):
192 | """Request DDE client
193 | timeout in seconds
194 | Note ... handle the exception within this function.
195 | """
196 | if not timeout:
197 | timeout = self.ddetimeout
198 | try:
199 | reply = self.ddec.request(item, int(timeout*1000)) # convert timeout into milliseconds
200 | except DDEError:
201 | err_str = str(sys.exc_info()[1])
202 | error = err_str[err_str.find('err=')+4:err_str.find('err=')+10]
203 | if error == hex(DMLERR_DATAACKTIMEOUT):
204 | print("TIMEOUT REACHED. Please use a higher timeout.\n")
205 | if (sys.version_info > (3, 0)): #this is only evaluated in case of an error
206 | reply = b'-998' #Timeout error value
207 | else:
208 | reply = '-998' #Timeout error value
209 | return reply
210 |
211 | def RequestArrayTrace(self, ddeRayData, timeout=None):
212 | """Request bulk ray tracing
213 |
214 | Parameters
215 | ----------
216 | ddeRayData : the ray data for array trace
217 | """
218 | pass
219 | # TO DO!!!
220 | # 1. Assign proper timeout as in Request() function
221 | # 2. Create the rayData structure conforming to ctypes structure
222 | # 3. Process the reply and return ray trace data
223 | # 4. Handle errors
224 | #reply = self.ddec.poke("RayArrayData", rayData, timeout)
225 |
226 | def SetDDETimeout(self, timeout):
227 | """Set DDE timeout
228 | timeout : timeout in seconds
229 | """
230 | self.ddetimeout = timeout
231 |
232 | def GetDDETimeout(self):
233 | """Returns the current timeout value in seconds
234 | """
235 | return self.ddetimeout
236 |
237 |
238 | def get_winfunc(libname, funcname, restype=None, argtypes=(), _libcache={}):
239 | """Retrieve a function from a library/DLL, and set the data types."""
240 | if libname not in _libcache:
241 | _libcache[libname] = windll.LoadLibrary(libname)
242 | func = getattr(_libcache[libname], funcname)
243 | func.argtypes = argtypes
244 | func.restype = restype
245 | return func
246 |
247 | class DDE(object):
248 | """Object containing all the DDEML functions"""
249 | AccessData = get_winfunc("user32", "DdeAccessData", LPBYTE, (HDDEDATA, LPDWORD))
250 | ClientTransaction = get_winfunc("user32", "DdeClientTransaction", HDDEDATA, (LPBYTE, DWORD, HCONV, HSZ, UINT, UINT, DWORD, LPDWORD))
251 | Connect = get_winfunc("user32", "DdeConnect", HCONV, (DWORD, HSZ, HSZ, PCONVCONTEXT))
252 | CreateDataHandle = get_winfunc("user32", "DdeCreateDataHandle", HDDEDATA, (DWORD, LPBYTE, DWORD, DWORD, HSZ, UINT, UINT))
253 | CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleW", HSZ, (DWORD, LPCWSTR, UINT)) # Unicode version
254 | #CreateStringHandle = get_winfunc("user32", "DdeCreateStringHandleA", HSZ, (DWORD, LPCSTR, UINT)) # ANSI version
255 | Disconnect = get_winfunc("user32", "DdeDisconnect", BOOL, (HCONV,))
256 | GetLastError = get_winfunc("user32", "DdeGetLastError", UINT, (DWORD,))
257 | Initialize = get_winfunc("user32", "DdeInitializeW", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # Unicode version of DDE initialize
258 | #Initialize = get_winfunc("user32", "DdeInitializeA", UINT, (LPDWORD, DDECALLBACK, DWORD, DWORD)) # ANSI version of DDE initialize
259 | FreeDataHandle = get_winfunc("user32", "DdeFreeDataHandle", BOOL, (HDDEDATA,))
260 | FreeStringHandle = get_winfunc("user32", "DdeFreeStringHandle", BOOL, (DWORD, HSZ))
261 | QueryString = get_winfunc("user32", "DdeQueryStringA", DWORD, (DWORD, HSZ, LPSTR, DWORD, c_int)) # ANSI version of QueryString
262 | UnaccessData = get_winfunc("user32", "DdeUnaccessData", BOOL, (HDDEDATA,))
263 | Uninitialize = get_winfunc("user32", "DdeUninitialize", BOOL, (DWORD,))
264 |
265 | class DDEError(RuntimeError):
266 | """Exception raise when a DDE error occures."""
267 | def __init__(self, msg, idInst=None):
268 | if idInst is None:
269 | RuntimeError.__init__(self, msg)
270 | else:
271 | RuntimeError.__init__(self, "%s (err=%s)" % (msg, hex(DDE.GetLastError(idInst))))
272 |
273 | class DDEClient(object):
274 | """The DDEClient class.
275 |
276 | Use this class to create and manage a connection to a service/topic. To get
277 | classbacks subclass DDEClient and overwrite callback."""
278 |
279 | def __init__(self, service, topic):
280 | """Create a connection to a service/topic."""
281 | self._idInst = DWORD(0) # application instance identifier.
282 | self._hConv = HCONV()
283 |
284 | self._callback = DDECALLBACK(self._callback)
285 | # Initialize and register application with DDEML
286 | res = DDE.Initialize(byref(self._idInst), self._callback, APPCMD_CLIENTONLY, 0)
287 | if res != DMLERR_NO_ERROR:
288 | raise DDEError("Unable to register with DDEML (err=%s)" % hex(res))
289 |
290 | hszServName = DDE.CreateStringHandle(self._idInst, service, CP_WINUNICODE)
291 | hszTopic = DDE.CreateStringHandle(self._idInst, topic, CP_WINUNICODE)
292 | # Try to establish conversation with the Zemax server
293 | self._hConv = DDE.Connect(self._idInst, hszServName, hszTopic, PCONVCONTEXT())
294 | DDE.FreeStringHandle(self._idInst, hszTopic)
295 | DDE.FreeStringHandle(self._idInst, hszServName)
296 | if not self._hConv:
297 | raise DDEError("Unable to establish a conversation with server", self._idInst)
298 |
299 | def __del__(self):
300 | """Cleanup any active connections and free all DDEML resources."""
301 | if self._hConv:
302 | DDE.Disconnect(self._hConv)
303 | if self._idInst:
304 | DDE.Uninitialize(self._idInst)
305 |
306 | def advise(self, item, stop=False):
307 | """Request updates when DDE data changes."""
308 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE)
309 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_ADVSTOP if stop else XTYP_ADVSTART, TIMEOUT_ASYNC, LPDWORD())
310 | DDE.FreeStringHandle(self._idInst, hszItem)
311 | if not hDdeData:
312 | raise DDEError("Unable to %s advise" % ("stop" if stop else "start"), self._idInst)
313 | DDE.FreeDataHandle(hDdeData)
314 |
315 | def execute(self, command):
316 | """Execute a DDE command."""
317 | pData = c_char_p(command)
318 | cbData = DWORD(len(command) + 1)
319 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, HSZ(), CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, LPDWORD())
320 | if not hDdeData:
321 | raise DDEError("Unable to send command", self._idInst)
322 | DDE.FreeDataHandle(hDdeData)
323 |
324 | def request(self, item, timeout=5000):
325 | """Request data from DDE service."""
326 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE)
327 | #hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, LPDWORD())
328 | pdwResult = DWORD(0)
329 | hDdeData = DDE.ClientTransaction(LPBYTE(), 0, self._hConv, hszItem, CF_TEXT, XTYP_REQUEST, timeout, byref(pdwResult))
330 | DDE.FreeStringHandle(self._idInst, hszItem)
331 | if not hDdeData:
332 | raise DDEError("Unable to request item", self._idInst)
333 |
334 | if timeout != TIMEOUT_ASYNC:
335 | pdwSize = DWORD(0)
336 | pData = DDE.AccessData(hDdeData, byref(pdwSize))
337 | if not pData:
338 | DDE.FreeDataHandle(hDdeData)
339 | raise DDEError("Unable to access data in request function", self._idInst)
340 | DDE.UnaccessData(hDdeData)
341 | else:
342 | pData = None
343 | DDE.FreeDataHandle(hDdeData)
344 | return pData
345 |
346 | def poke(self, item, data, timeout=5000):
347 | """Poke (unsolicited) data to DDE server"""
348 | hszItem = DDE.CreateStringHandle(self._idInst, item, CP_WINUNICODE)
349 | pData = c_char_p(data)
350 | cbData = DWORD(len(data) + 1)
351 | pdwResult = DWORD(0)
352 | #hData = DDE.CreateDataHandle(self._idInst, data, cbData, 0, hszItem, CP_WINUNICODE, 0)
353 | #hDdeData = DDE.ClientTransaction(hData, -1, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, LPDWORD())
354 | hDdeData = DDE.ClientTransaction(pData, cbData, self._hConv, hszItem, CF_TEXT, XTYP_POKE, timeout, byref(pdwResult))
355 | DDE.FreeStringHandle(self._idInst, hszItem)
356 | #DDE.FreeDataHandle(dData)
357 | if not hDdeData:
358 | print("Value of pdwResult: ", pdwResult)
359 | raise DDEError("Unable to poke to server", self._idInst)
360 |
361 | if timeout != TIMEOUT_ASYNC:
362 | pdwSize = DWORD(0)
363 | pData = DDE.AccessData(hDdeData, byref(pdwSize))
364 | if not pData:
365 | DDE.FreeDataHandle(hDdeData)
366 | raise DDEError("Unable to access data in poke function", self._idInst)
367 | # TODO: use pdwSize
368 | DDE.UnaccessData(hDdeData)
369 | else:
370 | pData = None
371 | DDE.FreeDataHandle(hDdeData)
372 | return pData
373 |
374 | def callback(self, value, topic=None, item=None):
375 | """Callback function for advice."""
376 | print("callback: %s: %s=%s" % (item.decode('euc-kr'), topic.decode('euc-kr'), value.decode('euc-kr')))
377 |
378 | def _callback(self, wType, uFmt, hConv, hsz1, hsz2, hDdeData, dwData1, dwData2):
379 | """DdeCallback callback function for processing Dynamic Data Exchange (DDE)
380 | transactions sent by DDEML in response to DDE events
381 |
382 | Parameters
383 | ----------
384 | wType : transaction type (UINT)
385 | uFmt : clipboard data format (UINT)
386 | hConv : handle to conversation (HCONV)
387 | hsz1 : handle to string (HSZ)
388 | hsz2 : handle to string (HSZ)
389 | hDDedata : handle to global memory object (HDDEDATA)
390 | dwData1 : transaction-specific data (DWORD)
391 | dwData2 : transaction-specific data (DWORD)
392 |
393 | Returns
394 | -------
395 | ret : specific to the type of transaction (HDDEDATA)
396 | """
397 | if wType == XTYP_ADVDATA: # value of the data item has changed [hsz1 = topic; hsz2 = item; hDdeData = data]
398 | dwSize = DWORD(0)
399 | pData = DDE.AccessData(hDdeData, byref(dwSize))
400 | if pData:
401 | topic = create_string_buffer(b'\000' * 128)
402 | item = create_string_buffer(b'\000' * 128)
403 | DDE.QueryString(self._idInst, hsz1, topic, 128, CP_WINANSI)
404 | DDE.QueryString(self._idInst, hsz2, item, 128, CP_WINANSI)
405 | self.callback(pData, topic.value, item.value)
406 | DDE.UnaccessData(hDdeData)
407 | return DDE_FACK
408 | else:
409 | print("Error: AccessData returned NULL! (err = %s)"% (hex(DDE.GetLastError(self._idInst))))
410 | if wType == XTYP_DISCONNECT:
411 | print("Disconnect notification received from server")
412 |
413 | return 0
414 |
415 | def WinMSGLoop():
416 | """Run the main windows message loop."""
417 | LPMSG = POINTER(MSG)
418 | LRESULT = c_ulong
419 | GetMessage = get_winfunc("user32", "GetMessageW", BOOL, (LPMSG, HWND, UINT, UINT))
420 | TranslateMessage = get_winfunc("user32", "TranslateMessage", BOOL, (LPMSG,))
421 | # restype = LRESULT
422 | DispatchMessage = get_winfunc("user32", "DispatchMessageW", LRESULT, (LPMSG,))
423 |
424 | msg = MSG()
425 | lpmsg = byref(msg)
426 | while GetMessage(lpmsg, HWND(), 0, 0) > 0:
427 | TranslateMessage(lpmsg)
428 | DispatchMessage(lpmsg)
429 |
430 | if __name__ == "__main__":
431 | quotes = {}
432 | bid = DDEClient("WINROS", "bid")
433 | ask = DDEClient("WINROS", "ask")
434 | last = DDEClient("WINROS", "last")
435 | totalvol = DDEClient("WINROS", "totalvol")
436 | bid.advise("GC V19")
437 | ask.advise("GC V19")
438 | last.advise("GC V19")
439 | totalvol.advise("GC V19")
440 | WinMSGLoop()
441 |
--------------------------------------------------------------------------------
/examples/ddefeeder.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pymt5
3 | import time
4 | import signal
5 | from collections import OrderedDict
6 | import json
7 | exec(open('./ddeclient.py').read())
8 |
9 | symbols = [
10 |
11 | 'CRUDEOIL V19-MCX',
12 | 'GOLD Z19-MCX',
13 | 'BANKNIFTY U19-NSF',
14 |
15 | ]
16 | maps = {
17 |
18 | 'CRUDEOIL V19-MCX' : 'CRUDEOIL-1',
19 | 'GOLD Z19-MCX' : 'GOLD-1',
20 | 'BANKNIFTY U19-NSF' : 'BANKNIFTY-1',
21 |
22 | }
23 | clients = {}
24 | indices = {}
25 | quotes = {}
26 |
27 | ##################
28 | # PyMT5 handlers
29 | ##################
30 | def onConnected(client_info):
31 | client = client_info.get('client_id')
32 | clients[client] = client_info
33 | clients[client]['logged_in'] = False
34 | print("MT5 - onConnected - client_info:{}".format(str(client_info)))
35 | print("MT5 - onConnected - clients:{}".format(str(list(clients))))
36 |
37 | def onDisconnected(client_info):
38 | client = client_info.get('client_id')
39 | del clients[client]
40 | print("MT5 - onDisconnected - client_info:{}".format(str(client_info)))
41 | print("MT5 - onDisconnected - clients:{}".format(str(list(clients))))
42 |
43 | def sendSymbol(client, symbol):
44 | data = OrderedDict([('ver','3'),
45 | ('type','3'),
46 | ('index',str(indices[client])),
47 | ('symbol',symbol),
48 | ('path','DDE'),
49 | ('description','DDE'),
50 | ('page',''),
51 | ('currency_base',''),
52 | ('currency_profit',''),
53 | ('currency_margin',''),
54 | ('digits','4'),
55 | ('tick_flags','7'),
56 | ('calc_mode','0'),
57 | ('exec_mode','2'),
58 | ('chart_mode','0'),
59 | ('fill_flags','3'),
60 | ('expir_flags','15'),
61 | ('tick_value','0.00000'),
62 | ('tick_size','0.00000'),
63 | ('contract_size','100000.00000'),
64 | ('volume_min','0.01000'),
65 | ('volume_max','10000.00000'),
66 | ('volume_step','0.01000'),
67 | ('market_depth','0'),
68 | ('margin_flags','0'),
69 | ('margin_initial','0.00000'),
70 | ('margin_maintenance','0.00000'),
71 | ('margin_long','1.00000'),
72 | ('margin_short','1.00000'),
73 | ('margin_limit','0.00000'),
74 | ('margin_stop','0.00000'),
75 | ('margin_stop_limit','0.00000'),
76 | ('settlement_price','1.22804'),
77 | ('price_limit_max','0.00000'),
78 | ('price_limit_min','0.00000'),
79 | ('time_start','0'),
80 | ('time_expiration','0'),
81 | ('trade_mode','4')])
82 | m.send(client, data)
83 | indices[client] += 1
84 |
85 | def onData(data):
86 | if data.get('type') == '1':
87 | onLogin(data)
88 | elif data.get('type') == '2':
89 | onLogout(data)
90 | elif data.get('type') == '6':
91 | onHeartbeat(data)
92 | else:
93 | print("MT5 - onData - data:{}".format(json.dumps(data)))
94 |
95 | def onSIGINT(signum, frame):
96 | global end
97 | end = True
98 |
99 | def onHeartbeat(data):
100 | print("MT5 - onHeartbeat - data:{}".format(json.dumps(data)))
101 |
102 | def onLogin(data):
103 | print("MT5 - onLogin - data:{}".format(json.dumps(data)))
104 | client = data.get('client_id')
105 | login = data.get('login')
106 | password = data.get('password')
107 | # Send login OK response
108 | if login and password:
109 | clients[client]['logged_in'] = True
110 | m.send(client, OrderedDict([('ver','3'),
111 | ('type','1'),
112 | ('login',login),
113 | ('password',password),
114 | ('res','0')]))
115 | # Send a test symbol to make gateway status online
116 | indices[client] = 0
117 | for symbol in symbols:
118 | sendSymbol(client, maps[symbol])
119 | else:
120 | m.disconnect(client)
121 | del clients[client]
122 | print("MT5 - onLogin - not authorized - client_id:{}".format(client))
123 |
124 | def onLogout(data):
125 | print("MT5 - onLogout - data:{}".format(json.dumps(data)))
126 | client = data.get('client_id')
127 | del clients[client]
128 | m.disconnect(client)
129 | print("MT5 - onLogout - data:{}".format(json.dumps(data)))
130 | print("MT5 - onLogout - clients:{}".format(str(list(clients))))
131 |
132 | ######################
133 | # DDE client callback
134 | ######################
135 | class ESignal(DDEClient):
136 | def callback(self, value, topic=None, item=None):
137 | print("eSignal - %s - %s=%s" % (item.decode('euc-kr'), topic.decode('euc-kr'), value.decode('euc-kr')))
138 | symbol = item.decode('euc-kr')
139 | #quotes[maps[symbol]]['datetime'] = str(int(round(time.time() * 1000))).replace('L','')[:-3]
140 | # Best bid/ask
141 | if topic.decode('euc-kr') == 'bid':
142 | quotes[maps[symbol]]['bid'] = value.decode('euc-kr')
143 | if topic.decode('euc-kr') == 'ask':
144 | quotes[maps[symbol]]['ask'] = value.decode('euc-kr')
145 | quotes[maps[symbol]]['last'] = '0'
146 | quotes[maps[symbol]]['volume'] = '0'
147 | print("MT5 - sendTick - data:{}".format(str(dict(quotes[maps[symbol]]))))
148 | m.broadcast(quotes[maps[symbol]])
149 | # Last and volume
150 | if topic.decode('euc-kr') == 'last':
151 | quotes[maps[symbol]]['last'] = value.decode('euc-kr')
152 | if topic.decode('euc-kr') == 'tradesize':
153 | quotes[maps[symbol]]['bid'] = ''
154 | quotes[maps[symbol]]['ask'] = ''
155 | quotes[maps[symbol]]['volume'] = value.decode('euc-kr')
156 | print("MT5 - sendTick - data:{}".format(str(dict(quotes[maps[symbol]]))))
157 | m.broadcast(quotes[maps[symbol]])
158 |
159 | ##################
160 | # Main
161 | ##################
162 | signal.signal(signal.SIGINT, onSIGINT)
163 |
164 | # MT5 gateway
165 | m = pymt5.PyMT5()
166 | m.onConnected = onConnected
167 | m.onDisconnected = onDisconnected
168 | m.onData = onData
169 |
170 | time.sleep(10)
171 |
172 | # eSignal DDE client
173 | bid = ESignal("WINROS", "bid")
174 | ask = ESignal("WINROS", "ask")
175 | last = ESignal("WINROS", "last")
176 | tradesize = ESignal("WINROS", "tradesize")
177 | for symbol in symbols:
178 | quotes[maps[symbol]] = OrderedDict([('ver','3'),
179 | ('type','4'),
180 | ('symbol',maps[symbol]),
181 | ('bank','esignal'),
182 | ('bid','0'),
183 | ('ask','0'),
184 | ('last','0'),
185 | ('volume','0'),
186 | ('datetime','0')])
187 | bid.advise(symbol)
188 | ask.advise(symbol)
189 | last.advise(symbol)
190 | tradesize.advise(symbol)
191 |
192 | # Event loop
193 | end = False
194 | LPMSG = POINTER(MSG)
195 | LRESULT = c_ulong
196 | GetMessage = get_winfunc("user32", "GetMessageW", BOOL, (LPMSG, HWND, UINT, UINT))
197 | TranslateMessage = get_winfunc("user32", "TranslateMessage", BOOL, (LPMSG,))
198 | DispatchMessage = get_winfunc("user32", "DispatchMessageW", LRESULT, (LPMSG,))
199 | msg = MSG()
200 | lpmsg = byref(msg)
201 | while GetMessage(lpmsg, HWND(), 0, 0) > 0 and not end:
202 | TranslateMessage(lpmsg)
203 | DispatchMessage(lpmsg)
204 |
205 | m.stop()
206 |
--------------------------------------------------------------------------------
/examples/externaldeal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | import sys
4 | sys.path.append('../')
5 | import pymt5
6 | import time
7 | import signal
8 | from collections import OrderedDict
9 |
10 | client = ''
11 | order = deal = OrderedDict()
12 | logged_in = False
13 | m = None
14 |
15 | def onConnected(client_info):
16 | global client
17 | client = client_info.get('client_id')
18 | print('[onConnected] client_address: ' + str(client_info))
19 |
20 | def onDisconnected(client_info):
21 | global client
22 | client = ''
23 | print('[onDisconnected] client_address: ' + str(client_info))
24 |
25 | def onData(data):
26 | global logged_in
27 | print('[onData] data: ' + str(data))
28 | client = data.get('client_id')
29 | # Login
30 | if data.get('type') == '1':
31 | # Send heartbeat
32 | m.send(client, OrderedDict([('ver','3'),
33 | ('type','6')]))
34 | # Send login OK response
35 | m.send(client, OrderedDict([('ver','3'),
36 | ('type','1'),
37 | ('login',data.get('login')),
38 | ('password',data.get('password')),
39 | ('res','0')]))
40 | # Sync a symbol
41 | time.sleep(0.5)
42 | m.send(client, OrderedDict([('ver','3'),
43 | ('type','3'),
44 | ('index','0'),
45 | ('symbol','XAUUSD.a'),
46 | ('path','TestExchange'),
47 | ('description','test'),
48 | ('page','http://www.google.co/finance?q=XAUUSD.a'),
49 | ('currency_base','XAU'),
50 | ('currency_profit','USD'),
51 | ('currency_margin','USD'),
52 | ('digits','4'),
53 | ('tick_flags','7'),
54 | ('calc_mode','0'),
55 | ('exec_mode','2'),
56 | ('chart_mode','0'),
57 | ('fill_flags','3'),
58 | ('expir_flags','15'),
59 | ('tick_value','0.00000'),
60 | ('tick_size','0.00000'),
61 | ('contract_size','100000.00000'),
62 | ('volume_min','0.00000'),
63 | ('volume_max','10000.00000'),
64 | ('volume_step','0.01000'),
65 | ('market_depth','0'),
66 | ('margin_flags','0'),
67 | ('margin_initial','0.00000'),
68 | ('margin_maintenance','0.00000'),
69 | ('margin_long','1.00000'),
70 | ('margin_short','1.00000'),
71 | ('margin_limit','0.00000'),
72 | ('margin_stop','0.00000'),
73 | ('margin_stop_limit','0.00000'),
74 | ('settlement_price','1.22804'),
75 | ('price_limit_max','0.00000'),
76 | ('price_limit_min','0.00000'),
77 | ('time_start','0'),
78 | ('time_expiration','0'),
79 | ('trade_mode','4')]))
80 | logged_in = True
81 |
82 | def onSIGINT(signum, frame):
83 | global end
84 | end = True
85 |
86 | ##################
87 | # Main
88 | ##################
89 | signal.signal(signal.SIGINT, onSIGINT)
90 | m = pymt5.PyMT5(port=5533)
91 | m.onConnected = onConnected
92 | m.onDisconnected = onDisconnected
93 | m.onData = onData
94 | while not logged_in:
95 | time.sleep(1)
96 | deal = OrderedDict([('ver','3'),
97 | ('type','50'),
98 | ('exchange_id', str(int(time.time()))),
99 | ('order','1596687043'),
100 | ('symbol','XAUUSD.a'),
101 | ('login','999011'),
102 | ('type_deal','0'),
103 | ('volume','2'),
104 | ('volume_rem','0'),
105 | ('price','1355.80'),
106 | ('datetime', '1596535624555'),
107 | ('comment','TEST')])
108 | m.send(client, deal)
109 | print('[sendExternalDeal] deal: ' + str(deal))
110 | end = False
111 | while not end:
112 | time.sleep(1)
113 | m.stop()
114 |
--------------------------------------------------------------------------------
/examples/server.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import pymt5
3 | import time
4 | import signal
5 | from collections import OrderedDict
6 | import json
7 | import random
8 | import string
9 |
10 | clients = []
11 | bid = float(random.randrange(12660, 12680))/10000
12 | spread = 0.0004
13 | ask = bid+spread
14 | last = 0
15 | vol = 0
16 |
17 | def onConnected(client_info):
18 | clients.append(client_info.get('client_id'))
19 | print('[onConnected] client_address: ' + str(client_info))
20 | print('[clients] ' + str(clients))
21 |
22 | def onDisconnected(client_info):
23 | clients.remove(client_info.get('client_id'))
24 | print('[onDisconnected] client_address: ' + str(client_info))
25 | print('[clients] ' + str(clients))
26 |
27 | def sendOrderConfirmed(client, order):
28 | global last
29 | global vol
30 | order.update({'exchange_id':str(int(time.time()))})
31 | if order['type_order'] == '0':
32 | order.update({'price_order':str(ask)})
33 | last = ask
34 | else:
35 | order.update({'price_order':str(bid)})
36 | last = bid
37 | vol = float(order['volume'])
38 | order.update({'state':'1'})
39 | order.update({'result':'10009'})
40 | m.send(client, order)
41 | m.broadcast(OrderedDict([('ver','3'),('type','4'),('symbol','EURUSD.TEST'),('bank','dc'),('bid',str(bid)),('ask',str(ask)),('last',str(last)),('volume',str(vol)),('datetime','0')]))
42 |
43 | def sendOrderPlaced(client, order):
44 | order.update({'state':'2'})
45 | order.update({'result':'10008'})
46 | m.send(client, order)
47 |
48 | def sendOrderNew(client, order):
49 | order.update({'state':'3'})
50 | order.update({'result':'1'})
51 | m.send(client, order)
52 |
53 | def sendOrderDeal(client, order):
54 | order.update({'state':'5'})
55 | order.update({'result':'1'})
56 | m.send(client, order)
57 |
58 | def sendOrderRejectNew(client, order):
59 | order.update({'state':'4'})
60 | order.update({'result':'10006'})
61 | m.send(client, order)
62 |
63 | def sendOrderCancel(client, order):
64 | order.update({'state':'10'})
65 | order.update({'result':'10007'})
66 | m.send(client, order)
67 |
68 | def sendOrderComplete(client, order):
69 | order.update({'state':'20'})
70 | order.update({'result':'10009'})
71 | m.send(client, order)
72 |
73 | def sendDeal(client, order):
74 | exchange_id = '0'
75 | while exchange_id.startswith('0'):
76 | exchange_id = ''.join(random.choice(string.digits) for i in range(8))
77 | # type_deal: 0=buy, 1=sell
78 | deal = OrderedDict([('ver','3'),
79 | ('type','7'),
80 | ('exchange_id',exchange_id),
81 | ('order',order['order']),
82 | ('symbol',order['symbol']),
83 | ('login',order['login']),
84 | ('type_deal','0'),
85 | ('volume',order['volume']),
86 | ('volume_rem','0'),
87 | ('price','1.2679')])
88 | m.send(client, deal)
89 |
90 | def sendDealExternal(client, exchange_id, symbol, login, type_deal, volume, price, datetime='1611339821000'):
91 | deal = OrderedDict([('ver','3'),
92 | ('type','50'),
93 | ('exchange_id',exchange_id),
94 | ('order','0'),
95 | ('symbol',symbol),
96 | ('login',login),
97 | ('type_deal',type_deal),
98 | ('volume',volume),
99 | ('volume_rem','0'),
100 | ('price',price),
101 | ('datetime',datetime)])
102 | m.send(client, deal)
103 |
104 | def sendTick():
105 | global bid
106 | global ask
107 | bid = float(random.randrange(12660, 12680))/10000
108 | ask = bid+spread
109 | m.broadcast(OrderedDict([('ver','3'),('type','4'),('symbol','EURUSD.TEST'),('bank','dc'),('bid',str(bid)),('ask',str(ask)),('last',str(last)),('volume',str(vol)),('datetime','0')]))
110 |
111 | def onData(data):
112 | print('[onData] data: ' + json.dumps(data))
113 | client = data.get('client_id')
114 | # Login
115 | if data.get('type') == '1':
116 | # Send heartbeat
117 | m.send(client, OrderedDict([('ver','3'),
118 | ('type','6')]))
119 | # Send login OK response (no auth)
120 | m.send(client, OrderedDict([('ver','3'),
121 | ('type','1'),
122 | ('login',data.get('login')),
123 | ('password',data.get('password')),
124 | ('res','0')]))
125 | # Send symbol with index starting at 0
126 | time.sleep(0.5)
127 | m.send(client, OrderedDict([('ver','3'),
128 | ('type','3'),
129 | ('index','0'),
130 | ('symbol','EURUSD.TEST'),
131 | ('path','TestExchange'),
132 | ('description','test'),
133 | ('page','http://www.google.co/finance?q=EURUSD.TEST'),
134 | ('currency_base','EUR'),
135 | ('currency_profit','USD'),
136 | ('currency_margin','USD'),
137 | ('digits','4'),
138 | ('tick_flags','7'),
139 | ('calc_mode','0'),
140 | ('exec_mode','2'),
141 | ('chart_mode','0'),
142 | ('fill_flags','3'),
143 | ('expir_flags','15'),
144 | ('tick_value','0.00000'),
145 | ('tick_size','0.00000'),
146 | ('contract_size','100000.00000'),
147 | ('volume_min','0.01000'),
148 | ('volume_max','10000.00000'),
149 | ('volume_step','0.01000'),
150 | ('market_depth','0'),
151 | ('margin_flags','0'),
152 | ('margin_initial','0.00000'),
153 | ('margin_maintenance','0.00000'),
154 | ('margin_long','1.00000'),
155 | ('margin_short','1.00000'),
156 | ('margin_limit','0.00000'),
157 | ('margin_stop','0.00000'),
158 | ('margin_stop_limit','0.00000'),
159 | ('settlement_price','1.22804'),
160 | ('price_limit_max','0.00000'),
161 | ('price_limit_min','0.00000'),
162 | ('time_start','0'),
163 | ('time_expiration','0'),
164 | ('trade_mode','4')]))
165 | # Logout
166 | elif data.get('type') == '2':
167 | m.disconnect(client)
168 | # New order
169 | elif data.get('type') == '5' and data.get('state') == '0':
170 | order = data
171 | del order['client_id']
172 | # Market
173 | if order['action'] == '2':
174 | sendOrderConfirmed(client, order)
175 | elif order['action'] == '3':
176 | sendOrderConfirmed(client, order)
177 | # Limit
178 | elif order['action'] in ('4','5'):
179 | sendOrderPlaced(client, order)
180 | sendOrderNew(client, order)
181 | sendDeal(client, order)
182 | else:
183 | sendOrderRejectNew(client, order)
184 |
185 | def onSIGINT(signum, frame):
186 | global end
187 | end = True
188 |
189 | ##################
190 | # Main
191 | ##################
192 | signal.signal(signal.SIGINT, onSIGINT)
193 | m = pymt5.PyMT5()
194 | m.onConnected = onConnected
195 | m.onDisconnected = onDisconnected
196 | m.onData = onData
197 |
198 | end = False
199 | while not end:
200 | time.sleep(1)
201 | sendTick()
202 | m.stop()
203 |
--------------------------------------------------------------------------------
/pymt5/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Copyright (C) DevCartel Co.,Ltd.
4 | # Bangkok, Thailand
5 | #
6 | import sys
7 | __version__ = '1.4.0'
8 |
9 | if sys.version_info >= (3, 0):
10 | from pymt5.pymt5 import *
11 | else:
12 | from pymt5 import *
13 |
--------------------------------------------------------------------------------
/pymt5/pymt5.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Copyright (C) DevCartel Co.,Ltd.
4 | # Bangkok, Thailand
5 | #
6 | import socket
7 | import threading
8 | from six.moves import socketserver
9 | import re
10 | import time
11 | from collections import OrderedDict
12 |
13 | MSG_SEPARATOR = '\n'
14 | MSG_SEPARATOR_TAG = '\x01'
15 | MSG_SEPARATOR_TAGVALUE = '='
16 | MSG_MAX_SIZE = 4096
17 |
18 | socketserver.TCPServer.allow_reuse_address = True
19 |
20 | __all__ = ['PyMT5']
21 |
22 | class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
23 | def handle(self):
24 | cur_thread = threading.current_thread()
25 | requests = self.server.requests
26 | if self.request not in requests:
27 | requests[self.request] = {'client_id':cur_thread.ident,
28 | 'client_address':self.client_address[0],
29 | 'client_port':self.client_address[1]}
30 | if callable(self.server.onConnected):
31 | self.server.onConnected(requests[self.request])
32 | buffer = b''
33 | while True:
34 | try:
35 | part = self.request.recv(MSG_MAX_SIZE)
36 | if not part: break
37 | buffer += part
38 | if len(part) < MSG_MAX_SIZE:
39 | for decoded in buffer.decode('utf-8').split(MSG_SEPARATOR):
40 | data = OrderedDict(re.findall("(.*?)"+MSG_SEPARATOR_TAGVALUE+"(.*?)"+MSG_SEPARATOR_TAG, decoded))
41 | if data and callable(self.server.onData):
42 | data.update({'client_id':cur_thread.ident})
43 | self.server.onData(data)
44 | buffer = b''
45 | except socket.error:
46 | break
47 | if callable(self.server.onDisconnected) and (self.request in requests):
48 | self.server.onDisconnected(requests[self.request])
49 | self.request.close()
50 |
51 | class PyMT5(socketserver.ThreadingTCPServer):
52 | def __init__(self, host='', port=16838, *args, **kwargs):
53 | socketserver.ThreadingTCPServer.__init__(self, (host, port), ThreadedTCPRequestHandler)
54 | self.requests = {}
55 | self.server_thread = threading.Thread(target=self.serve_forever)
56 | self.server_thread.setDaemon(True)
57 | self.server_thread.start()
58 | self.onConnected = None
59 | self.onDisconnected = None
60 | self.onData = None
61 |
62 | def stop(self):
63 | for request in list(self.requests):
64 | self.shutdown_request(request)
65 | self.shutdown()
66 | self.server_close()
67 |
68 | def broadcast(self, data):
69 | msg = ''
70 | for k,v in data.items():
71 | msg += k+MSG_SEPARATOR_TAGVALUE+v+MSG_SEPARATOR_TAG
72 | for request in list(self.requests):
73 | try:
74 | request.sendall((msg+MSG_SEPARATOR).encode('utf-8'))
75 | except socket.error:
76 | del self.requests[request]
77 |
78 | def send(self, client_id, data):
79 | msg = ''
80 | for k,v in data.items():
81 | msg += k+MSG_SEPARATOR_TAGVALUE+v+MSG_SEPARATOR_TAG
82 | for request in list(self.requests):
83 | if client_id == self.requests[request]['client_id']:
84 | try:
85 | request.sendall((msg+MSG_SEPARATOR).encode('utf-8'))
86 | except socket.error:
87 | del self.requests[request]
88 |
89 | def sendRaw(self, client_id, data):
90 | for request in list(self.requests):
91 | if client_id == self.requests[request]['client_id']:
92 | try:
93 | request.sendall(data.encode('utf-8'))
94 | except socket.error:
95 | del self.requests[request]
96 |
97 | def disconnect(self, client_id):
98 | for request in list(self.requests):
99 | if client_id == self.requests[request]['client_id']:
100 | self.shutdown_request(request)
101 | del self.requests[request]
102 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license_file = LICENSE.txt
3 |
4 | [bdist_wheel]
5 | universal=1
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from setuptools import setup, find_packages
3 | from setuptools.dist import Distribution
4 | # To use a consistent encoding
5 | from codecs import open
6 | from os import path
7 | import platform
8 |
9 | here = path.abspath(path.dirname(__file__))
10 |
11 | with open(path.join(here, 'README.md'), encoding='utf-8') as f:
12 | long_description = f.read()
13 |
14 | setup(
15 | name='pymt5',
16 | version='1.4.0',
17 | description='Python API for MetaTrader 5 gateways',
18 | long_description=long_description,
19 | long_description_content_type='text/markdown',
20 | url='https://github.com/devcartel/pymt5',
21 | author='DevCartel',
22 | author_email='support@devcartel.com',
23 | license='MIT',
24 | classifiers=[
25 | 'Development Status :: 5 - Production/Stable',
26 | 'Intended Audience :: Developers',
27 | 'Topic :: Software Development :: Build Tools',
28 | 'License :: OSI Approved :: MIT License',
29 | 'Programming Language :: Python :: 2',
30 | 'Programming Language :: Python :: 2.7',
31 | 'Programming Language :: Python :: 3',
32 | 'Programming Language :: Python :: 3.4',
33 | 'Programming Language :: Python :: 3.5',
34 | 'Programming Language :: Python :: 3.6',
35 | 'Programming Language :: Python :: 3.7',
36 | ],
37 | keywords='metatrader, metaquotes, mt5, gateway, api, python',
38 | packages=['pymt5']
39 | )
40 |
--------------------------------------------------------------------------------