Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6d7554dba | |||
| aa656a0efa | |||
| bc594e97e7 |
Binary file not shown.
@@ -1,206 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| OnTick(string symbol).mqh |
|
||||
//| Copyright 2010, Lizar |
|
||||
//| https://login.mql5.com/ru/users/Lizar |
|
||||
//| Revision 2011.01.30 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2010, Lizar"
|
||||
#property link "https://login.mql5.com/ru/users/Lizar"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| The events enumeration is implemented as flags |
|
||||
//| the events can be combined using the OR ("|") logical operation |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
|
||||
enum ENUM_CHART_EVENT_SYMBOL
|
||||
{
|
||||
CHARTEVENT_NO =0, // Events disabled
|
||||
CHARTEVENT_INIT =0, // "Initialization" event
|
||||
|
||||
CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart
|
||||
CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart
|
||||
CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart
|
||||
CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart
|
||||
CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart
|
||||
CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart
|
||||
CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart
|
||||
CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart
|
||||
CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart
|
||||
CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
|
||||
CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
|
||||
CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
|
||||
CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
|
||||
CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
|
||||
CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
|
||||
CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
|
||||
CHARTEVENT_TICK =0x00200000, // "New tick" event
|
||||
|
||||
CHARTEVENT_ALL =0xFFFFFFFF, // All events enabled
|
||||
};
|
||||
|
||||
//---
|
||||
#define CHART_EVENT_SYMBOL CHARTEVENT_TICK // frequency of calling OnTick()
|
||||
|
||||
int _handle_[];
|
||||
|
||||
int _symbols_total_ = 0; // total symbols
|
||||
int _symbols_market_ = 0; // number of symbols in Market Watch
|
||||
bool _market_watch_ = false; // use symbols from Market Watch
|
||||
bool _testing_ = false; // In testing mode
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Custom indicator initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int LoadSymbol(string symbol)
|
||||
|
||||
// TODO only load when not already exists
|
||||
{
|
||||
//--- check if we work in Strategy Tester:
|
||||
_testing_=((bool)MQL5InfoInteger(MQL5_TESTING) ||
|
||||
(bool)MQL5InfoInteger(MQL5_OPTIMIZATION) ||
|
||||
(bool)MQL5InfoInteger(MQL5_VISUAL_MODE));
|
||||
|
||||
//--- check settings
|
||||
if(_testing_ )
|
||||
{
|
||||
Print("Error: Strategy Tester is not working. ");
|
||||
return(1);
|
||||
}
|
||||
|
||||
//--- Initialization of variables and arrays:
|
||||
_symbols_total_=SymbolsTotal(false); // total symbols
|
||||
ArrayResize(_handle_,_symbols_total_); // resize array for handles of "spys"
|
||||
ArrayInitialize(_handle_,INVALID_HANDLE); // initalizae array for handles of "spys"
|
||||
|
||||
_symbols_total_=ArraySize(tickSymbols);
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
if(!LoadAgent(i, symbol)) return(1);
|
||||
|
||||
//--- Execute OnInit function of Expert Advisor
|
||||
//_OnInit();
|
||||
return(0);
|
||||
}
|
||||
|
||||
int UnloadAllSymbols()
|
||||
{
|
||||
//--- check if we work in Strategy Tester:
|
||||
_testing_=((bool)MQL5InfoInteger(MQL5_TESTING) ||
|
||||
(bool)MQL5InfoInteger(MQL5_OPTIMIZATION) ||
|
||||
(bool)MQL5InfoInteger(MQL5_VISUAL_MODE));
|
||||
|
||||
//--- check settings
|
||||
if(_testing_ )
|
||||
{
|
||||
Print("Error: Strategy Tester is not working. ");
|
||||
return(1);
|
||||
}
|
||||
|
||||
_symbols_total_=ArraySize(tickSymbols);
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
if(!DeLoadAgent(i, tickSymbols[i])) return(false);
|
||||
|
||||
|
||||
//--- Execute OnInit function of Expert Advisor
|
||||
//_OnInit();
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//| Used only in Strategy Tester |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
if(_testing_)
|
||||
{
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
{
|
||||
string __symbol__=tickSymbols[i];
|
||||
if(MathAbs(GlobalVariableGet(__symbol__+"_flag")-2)<0.1)
|
||||
{
|
||||
GlobalVariableSet(__symbol__+"_flag",1);
|
||||
OnTick(__symbol__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
//| ChartEvent function |
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
|
||||
|
||||
{
|
||||
//--- Call of OnTick(string symbol) or OnChartEvent event handler:
|
||||
if(id==CHARTEVENT_CUSTOM_LAST)
|
||||
{
|
||||
OnTick(sparam);
|
||||
//--- synchronize "agents" with Market Watch if necessary:
|
||||
}
|
||||
else _OnChartEvent(id,lparam,dparam,sparam);
|
||||
}
|
||||
|
||||
#define OnChartEvent _OnChartEvent // rendefine of OnChartEvent function
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Function for loading of "spys" |
|
||||
//| INPUT: __id__ - id, corresponds to the symbol index in |
|
||||
//| the list of symbols |
|
||||
//| __symbol__ - symbol name |
|
||||
//| OUTPUT: true - if successful |
|
||||
//| false - if error |
|
||||
//| REMARK: no. |
|
||||
//+------------------------------------------------------------------+
|
||||
bool LoadAgent(int __id__, string __symbol__)
|
||||
{
|
||||
_handle_[__id__]=iCustom(__symbol__,_Period,"Spy Control panel MCM",ChartID(),65534,CHART_EVENT_SYMBOL);
|
||||
if(_handle_[__id__]==INVALID_HANDLE)
|
||||
{
|
||||
Print("Error in setting of agent for ",__symbol__);
|
||||
return(false);
|
||||
}
|
||||
Print("The agent for ",__symbol__," is set.");
|
||||
return(true);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Function for release of the "spys" |
|
||||
//| INPUT: __id__ - id, corresponds to the symbol index in |
|
||||
//| the list of symbols |
|
||||
//| __symbol__ - symbol name |
|
||||
//| OUTPUT: true - if successful |
|
||||
//| false - if error |
|
||||
//| REMARK: no. |
|
||||
//+------------------------------------------------------------------+
|
||||
bool DeLoadAgent(int __id__, string __symbol__)
|
||||
{
|
||||
if(_handle_[__id__]!=INVALID_HANDLE)
|
||||
{
|
||||
if(!IndicatorRelease(_handle_[__id__]))
|
||||
{
|
||||
Print("Error deletion of agent for ",__symbol__);
|
||||
return(false);
|
||||
}
|
||||
Print("The agent for ",__symbol__," is deleted.");
|
||||
_handle_[__id__]=INVALID_HANDLE;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
//+------------------------------ end -------------------------------+
|
||||
Binary file not shown.
@@ -1,376 +1,385 @@
|
||||
# Metaquotes MQL5 - JSON - API
|
||||
|
||||
### Development state: first stable release
|
||||
|
||||
Tested on macOS Mojave / Windows 10 in Parallels Desktop container.
|
||||
|
||||
Working in production on Debian 10 / Wine 4.
|
||||
|
||||
An issue was found because of REP/REQ socket. Architecture changes are in development.
|
||||
|
||||
## Table of Contents
|
||||
* [About the Project](#about-the-project)
|
||||
* [Installation](#installation)
|
||||
* [Documentation](#documentation)
|
||||
* [Usage](#usage)
|
||||
* [Live data and streaming events](#live-data-and-streaming-events)
|
||||
* [Error handling](#error-handling)
|
||||
* [License](#license)
|
||||
|
||||
## About the Project
|
||||
|
||||
This project was developed to work as a server for the Backtrader Python trading framework. It is based on ZeroMQ sockets and uses JSON format to communicate. But now it has grown to the independent project. You can use it with any programming language that has [ZeroMQ binding](http://zeromq.org/bindings:_start).
|
||||
|
||||
|
||||
Backtrader Python client is located here: [Python Backtrader - Metaquotes MQL5 ](https://github.com/khramkov/Backtrader-MQL5-API)
|
||||
|
||||
In development:
|
||||
* Devitation
|
||||
* Stop limit orders
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install ZeroMQ for MQL5 [https://github.com/dingmaotu/mql-zmq](https://github.com/dingmaotu/mql-zmq)
|
||||
2. Put `include/Json.mqh` from this repo to your MetaEditor `include` directoty.
|
||||
3. Download and compile `experts/JsonAPI.mq5` script.
|
||||
4. Check if Metatrader 5 automatic trading is allowed.
|
||||
5. Attach the script to a chart in Metatrader 5.
|
||||
6. Allow DLL import in dialog window.
|
||||
7. Check if the ports are free to use. (default:`15555`,`15556`, `15557`,`15558`)
|
||||
|
||||
## Documentation
|
||||
|
||||
The script uses four ZeroMQ sockets:
|
||||
|
||||
1. `System socket` - recives requests from client and replies 'OK'
|
||||
2. `Data socket` - pushes data to client depending on the request via System socket.
|
||||
3. `Live socket` - automatically pushes last candle when it closes.
|
||||
4. `Streaming socket` - automatically pushes last transaction info every time it happens.
|
||||
|
||||
The idea is to send requests via `System socket` and recieve results/errors via `Data socket`. Event handlers should be created for `Live socket` and `Streaming socket` because the server sends data to theese sockets automatically. See examples in [Live data and streaming events](#live-data-and-streaming-events) section.
|
||||
|
||||
`System socket` request uses default JSON dictionary:
|
||||
|
||||
```
|
||||
{
|
||||
"action": null,
|
||||
"actionType": null,
|
||||
"symbol": null,
|
||||
"chartTF": null,
|
||||
"fromDate": null,
|
||||
"toDate": null,
|
||||
"id": null,
|
||||
"magic": null,
|
||||
"volume": null,
|
||||
"price": null,
|
||||
"stoploss": null,
|
||||
"takeprofit": null,
|
||||
"expiration": null,
|
||||
"deviation": null,
|
||||
"comment": null
|
||||
}
|
||||
```
|
||||
Check out the available combinations of `action` and `actionType`:
|
||||
|
||||
action | actionType | Description |
|
||||
-----------|----------------------|----------------------------|
|
||||
CONFIG | null | Set script configuration |
|
||||
ACCOUNT | null | Get account settings |
|
||||
BALANCE | null | Get current balance |
|
||||
POSITIONS | null | Get current open positions |
|
||||
ORDERS | null | Get current open orders |
|
||||
HISTORY | DATA | Get data history |
|
||||
HISTORY | TRADES | Get trades history |
|
||||
TRADE | ORDER_TYPE_BUY | Buy market |
|
||||
TRADE | ORDER_TYPE_SELL | Sell market |
|
||||
TRADE | ORDER_TYPE_BUY_LIMIT | Buy limit |
|
||||
TRADE | ORDER_TYPE_SELL_LIMIT| Sell limit |
|
||||
TRADE | ORDER_TYPE_BUY_STOP | Buy stop |
|
||||
TRADE | ORDER_TYPE_SELL_STOP | Sell stop |
|
||||
TRADE | POSITION_MODIFY | Position modify |
|
||||
TRADE | POSITION_PARTIAL | Position close partial |
|
||||
TRADE | POSITION_CLOSE_ID | Position close by id |
|
||||
TRADE | POSITION_CLOSE_SYMBOL| Positions close by symbol |
|
||||
TRADE | ORDER_MODIFY | Order modify |
|
||||
TRADE | ORDER_CANCEL | Order cancel |
|
||||
|
||||
Python 3 API class example:
|
||||
|
||||
``` python
|
||||
import zmq
|
||||
|
||||
class MTraderAPI:
|
||||
def __init__(self, host=None):
|
||||
self.HOST = host or 'localhost'
|
||||
self.SYS_PORT = 15555 # REP/REQ port
|
||||
self.DATA_PORT = 15556 # PUSH/PULL port
|
||||
self.LIVE_PORT = 15557 # PUSH/PULL port
|
||||
self.EVENTS_PORT = 15558 # PUSH/PULL port
|
||||
|
||||
# ZeroMQ timeout in seconds
|
||||
sys_timeout = 1
|
||||
data_timeout = 10
|
||||
|
||||
# initialise ZMQ context
|
||||
context = zmq.Context()
|
||||
|
||||
# connect to server sockets
|
||||
try:
|
||||
self.sys_socket = context.socket(zmq.REQ)
|
||||
# set port timeout
|
||||
self.sys_socket.RCVTIMEO = sys_timeout * 1000
|
||||
self.sys_socket.connect('tcp://{}:{}'.format(self.HOST, self.SYS_PORT))
|
||||
|
||||
self.data_socket = context.socket(zmq.PULL)
|
||||
# set port timeout
|
||||
self.data_socket.RCVTIMEO = data_timeout * 1000
|
||||
self.data_socket.connect('tcp://{}:{}'.format(self.HOST, self.DATA_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Binding ports ERROR")
|
||||
|
||||
def _send_request(self, data: dict) -> None:
|
||||
"""Send request to server via ZeroMQ System socket"""
|
||||
try:
|
||||
self.sys_socket.send_json(data)
|
||||
msg = self.sys_socket.recv_string()
|
||||
# terminal received the request
|
||||
assert msg == 'OK', 'Something wrong on server side'
|
||||
except AssertionError as err:
|
||||
raise zmq.NotDone(err)
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Sending request ERROR")
|
||||
|
||||
def _pull_reply(self):
|
||||
"""Get reply from server via Data socket with timeout"""
|
||||
try:
|
||||
msg = self.data_socket.recv_json()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone('Data socket timeout ERROR')
|
||||
return msg
|
||||
|
||||
def live_socket(self, context=None):
|
||||
"""Connect to socket in a ZMQ context"""
|
||||
try:
|
||||
context = context or zmq.Context.instance()
|
||||
socket = context.socket(zmq.PULL)
|
||||
socket.connect('tcp://{}:{}'.format(self.HOST, self.LIVE_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Live port connection ERROR")
|
||||
return socket
|
||||
|
||||
def streaming_socket(self, context=None):
|
||||
"""Connect to socket in a ZMQ context"""
|
||||
try:
|
||||
context = context or zmq.Context.instance()
|
||||
socket = context.socket(zmq.PULL)
|
||||
socket.connect('tcp://{}:{}'.format(self.HOST, self.EVENTS_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Data port connection ERROR")
|
||||
return socket
|
||||
|
||||
def construct_and_send(self, **kwargs) -> dict:
|
||||
"""Construct a request dictionary from default and send it to server"""
|
||||
|
||||
# default dictionary
|
||||
request = {
|
||||
"action": None,
|
||||
"actionType": None,
|
||||
"symbol": None,
|
||||
"chartTF": None,
|
||||
"fromDate": None,
|
||||
"toDate": None,
|
||||
"id": None,
|
||||
"magic": None,
|
||||
"volume": None,
|
||||
"price": None,
|
||||
"stoploss": None,
|
||||
"takeprofit": None,
|
||||
"expiration": None,
|
||||
"deviation": None,
|
||||
"comment": None
|
||||
}
|
||||
|
||||
# update dict values if exist
|
||||
for key, value in kwargs.items():
|
||||
if key in request:
|
||||
request[key] = value
|
||||
else:
|
||||
raise KeyError('Unknown key in **kwargs ERROR')
|
||||
|
||||
# send dict to server
|
||||
self._send_request(request)
|
||||
|
||||
# return server reply
|
||||
return self._pull_reply()
|
||||
```
|
||||
## Usage
|
||||
All examples will be on Python 3. Lets create an instance of MetaTrader API class:
|
||||
|
||||
``` python
|
||||
api = MTraderAPI()
|
||||
```
|
||||
|
||||
First of all we should configure the script `symbol` and `timeframe`. Live data stream will be configured to the seme params.
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="CONFIG", symbol="EURUSD", chartTF="M5")
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Get information about the trading account.
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="ACCOUNT")
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Get historical data. `fromDate` should be in timestamp format. The data will be loaded to the last candle if `toDate` is `None`. Notice, that the script sends the last unclosed candle too. You should delete it manually.
|
||||
|
||||
There are some issues:
|
||||
|
||||
- MetaTrader keeps historical data in cache. But when you make a request for the first time, MetaTrader downloads the data from a broker. This operation can exceed `Data socket` timeout. It depends on your broker. Second request will be handeled quickly.
|
||||
- It takes 6-7 seconds to process `50000` M1 candles. It was tested on Windows 10 in Parallels Desktop container with 4 cores and 4GB RAM. So if you need more data there are three ways to handle it. 1) Increase `Data socket` timeout. 2) You can load data partially using `fromDate` and `toDate`. 3) You can use more powerfull hardware.
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="HISTORY", actionType="DATA", symbol="EURUSD", chartTF="M5", fromDate=1555555555)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
History data reply example:
|
||||
|
||||
```
|
||||
{'data': [[1560782340, 1.12271, 1.12288, 1.12269, 1.12277, 46.0],[1560782400, 1.12278, 1.12299, 1.12276, 1.12297, 43.0],[1560782460, 1.12296, 1.12302, 1.12293, 1.123, 23.0]]}
|
||||
```
|
||||
|
||||
Buy market order.
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_BUY", symbol="EURUSD", "volume"=0.1, "stoploss"=1.1, "takeprofit"=1.3)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Sell limit order. Remember to switch SL/TP depending on BUY/SELL, or you will get `invalid stops` error.
|
||||
|
||||
- BUY: SL < price < TP
|
||||
- SELL: SL > price > TP
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_SELL_LIMIT", symbol="EURUSD", "volume"=0.1, "price"=1.2, "stoploss"=1.3, "takeprofit"=1.1)
|
||||
print(rep)
|
||||
```
|
||||
All pending orders are set to `Good till cancel` by default. If you want to set an expiration date, pass the date in timestamp format to `expiration` param.
|
||||
|
||||
``` python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_SELL_LIMIT", symbol="EURUSD", "volume"=0.1, "price"=1.2, "expiration"=1560782460)
|
||||
print(rep)
|
||||
```
|
||||
## Live data and streaming events
|
||||
|
||||
Event handler example for `Live socket` and `Data socket`.
|
||||
|
||||
``` python
|
||||
import zmq
|
||||
import threading
|
||||
|
||||
api = MTraderAPI()
|
||||
|
||||
|
||||
def _t_livedata():
|
||||
socket = api.live_socket()
|
||||
while True:
|
||||
try:
|
||||
last_candle = socket.recv_json()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Live data ERROR")
|
||||
print(last_candle)
|
||||
|
||||
|
||||
def _t_streaming_events():
|
||||
socket = api.streaming_socket()
|
||||
while True:
|
||||
try:
|
||||
trans = socket.recv_json()
|
||||
request, reply = trans.values()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Streaming data ERROR")
|
||||
print(request)
|
||||
print(reply)
|
||||
|
||||
|
||||
|
||||
t = threading.Thread(target=_t_livedata, daemon=True)
|
||||
t.start()
|
||||
|
||||
t = threading.Thread(target=_t_streaming_events, daemon=True)
|
||||
t.start()
|
||||
|
||||
while True:
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
There are only two variants of `Live socket` data. When everything is ok, the script sends data on candle close:
|
||||
|
||||
```
|
||||
{"status":"CONNECTED","data":[1560780120,1.12186,1.12194,1.12186,1.12191,15.00000]}
|
||||
```
|
||||
|
||||
If the terminal has lost connection to the market:
|
||||
|
||||
```
|
||||
{"status":"DISCONNECTED"}
|
||||
```
|
||||
|
||||
When the terminal reconnects to the market, it sends the last closed candle again. So you should update your historical data. Make the `action="HISTORY"` request with `fromDate` equal to your last candle timestamp before disconnect.
|
||||
|
||||
`OnTradeTransaction` function is called when a trade transaction event occurs. `Streaming socket` sends `TRADE_TRANSACTION_REQUEST` data every time it happens. Try to create and modify orders in the MQL5 terminal manually and check the expert logging tab for better understanding. Also see [MQL5 docs](https://www.mql5.com/en/docs/event_handlers/ontradetransaction).
|
||||
|
||||
`TRADE_TRANSACTION_REQUEST` request data:
|
||||
|
||||
```
|
||||
{
|
||||
'action': 'TRADE_ACTION_DEAL',
|
||||
'order': 501700843,
|
||||
'symbol': 'EURUSD',
|
||||
'volume': 0.1,
|
||||
'price': 1.12181,
|
||||
'stoplimit': 0.0,
|
||||
'sl': 1.1,
|
||||
'tp': 1.13,
|
||||
'deviation': 10,
|
||||
'type': 'ORDER_TYPE_BUY',
|
||||
'type_filling': 'ORDER_FILLING_FOK',
|
||||
'type_time': 'ORDER_TIME_GTC',
|
||||
'expiration': 0,
|
||||
'comment': None,
|
||||
'position': 0,
|
||||
'position_by': 0
|
||||
}
|
||||
```
|
||||
`TRADE_TRANSACTION_REQUEST` result data:
|
||||
|
||||
```
|
||||
{
|
||||
'retcode': 10009,
|
||||
'result': 'TRADE_RETCODE_DONE',
|
||||
'deal': 501700843,
|
||||
'order': 501700843,
|
||||
'volume': 0.1,
|
||||
'price': 1.12181,
|
||||
'comment': None,
|
||||
'request_id': 8,
|
||||
'retcode_external': 0
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
First of all, when you send a command via `System socket`, you should always receive back `"OK"` message via `System socket`. It means that your command was received and deserialized.
|
||||
|
||||
All data that come through `Data socket` have an `error` param. This param will have `true` key if somethng goes wrong. Also, there will be `description` and `function` params. They will hold information about error and the name of the function with error.
|
||||
|
||||
This information also applies to the trade commannds. See [MQL5 docs](https://www.mql5.com/en/docs/constants/errorswarnings/enum_trade_return_codes) for possible server answers.
|
||||
|
||||
## License
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See `LICENSE` for more information.
|
||||
# Metaquotes MQL5 - JSON - API
|
||||
|
||||
### Development state: first stable release
|
||||
|
||||
Tested on macOS Mojave / Windows 10 in Parallels Desktop container.
|
||||
|
||||
Working in production on Debian 10 / Wine 4.
|
||||
|
||||
An issue was found because of REP/REQ socket. Architecture changes are in development.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [About the Project](#about-the-project)
|
||||
- [Installation](#installation)
|
||||
- [Documentation](#documentation)
|
||||
- [Usage](#usage)
|
||||
- [Live data and streaming events](#live-data-and-streaming-events)
|
||||
- [Error handling](#error-handling)
|
||||
- [License](#license)
|
||||
|
||||
## About the Project
|
||||
|
||||
This project was developed to work as a server for the Backtrader Python trading framework. It is based on ZeroMQ sockets and uses JSON format to communicate. But now it has grown to the independent project. You can use it with any programming language that has [ZeroMQ binding](http://zeromq.org/bindings:_start).
|
||||
|
||||
Backtrader Python client is located here: [Python Backtrader - Metaquotes MQL5 ](https://github.com/khramkov/Backtrader-MQL5-API)
|
||||
|
||||
In development:
|
||||
|
||||
- Devitation
|
||||
- Stop limit orders
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install ZeroMQ for MQL5 [https://github.com/dingmaotu/mql-zmq](https://github.com/dingmaotu/mql-zmq)
|
||||
2. Put `include/Json.mqh` from this repo to your MetaEditor `include` directoty.
|
||||
3. Download and compile `experts/JsonAPI.mq5` script.
|
||||
4. Check if Metatrader 5 automatic trading is allowed.
|
||||
5. Attach the script to a chart in Metatrader 5.
|
||||
6. Allow DLL import in dialog window.
|
||||
7. Check if the ports are free to use. (default:`15555`,`15556`, `15557`,`15558`)
|
||||
|
||||
## Documentation
|
||||
|
||||
The script uses four ZeroMQ sockets:
|
||||
|
||||
1. `System socket` - recives requests from client and replies 'OK'
|
||||
2. `Data socket` - pushes data to client depending on the request via System socket.
|
||||
3. `Live socket` - automatically pushes last candle when it closes.
|
||||
4. `Streaming socket` - automatically pushes last transaction info every time it happens.
|
||||
|
||||
The idea is to send requests via `System socket` and recieve results/errors via `Data socket`. Event handlers should be created for `Live socket` and `Streaming socket` because the server sends data to theese sockets automatically. See examples in [Live data and streaming events](#live-data-and-streaming-events) section.
|
||||
|
||||
`System socket` request uses default JSON dictionary:
|
||||
|
||||
```
|
||||
{
|
||||
"action": null,
|
||||
"actionType": null,
|
||||
"symbol": null,
|
||||
"chartTF": null,
|
||||
"fromDate": null,
|
||||
"toDate": null,
|
||||
"id": null,
|
||||
"magic": null,
|
||||
"volume": null,
|
||||
"price": null,
|
||||
"stoploss": null,
|
||||
"takeprofit": null,
|
||||
"expiration": null,
|
||||
"deviation": null,
|
||||
"comment": null
|
||||
}
|
||||
```
|
||||
|
||||
Check out the available combinations of `action` and `actionType`:
|
||||
|
||||
| action | actionType | Description |
|
||||
| --------- | --------------------- | ---------------------------- |
|
||||
| CONFIG | null | Set script configuration |
|
||||
| ACCOUNT | null | Get account settings |
|
||||
| BALANCE | null | Get current balance |
|
||||
| POSITIONS | null | Get current open positions |
|
||||
| ORDERS | null | Get current open orders |
|
||||
| HISTORY | DATA | Get data history |
|
||||
| HISTORY | TRADES | Get trades history |
|
||||
| HISTORY | WRITE | Downlaod history data as CSV |
|
||||
| TRADE | ORDER_TYPE_BUY | Buy market |
|
||||
| TRADE | ORDER_TYPE_SELL | Sell market |
|
||||
| TRADE | ORDER_TYPE_BUY_LIMIT | Buy limit |
|
||||
| TRADE | ORDER_TYPE_SELL_LIMIT | Sell limit |
|
||||
| TRADE | ORDER_TYPE_BUY_STOP | Buy stop |
|
||||
| TRADE | ORDER_TYPE_SELL_STOP | Sell stop |
|
||||
| TRADE | POSITION_MODIFY | Position modify |
|
||||
| TRADE | POSITION_PARTIAL | Position close partial |
|
||||
| TRADE | POSITION_CLOSE_ID | Position close by id |
|
||||
| TRADE | POSITION_CLOSE_SYMBOL | Positions close by symbol |
|
||||
| TRADE | ORDER_MODIFY | Order modify |
|
||||
| TRADE | ORDER_CANCEL | Order cancel |
|
||||
|
||||
Python 3 API class example:
|
||||
|
||||
```python
|
||||
import zmq
|
||||
|
||||
class MTraderAPI:
|
||||
def __init__(self, host=None):
|
||||
self.HOST = host or 'localhost'
|
||||
self.SYS_PORT = 15555 # REP/REQ port
|
||||
self.DATA_PORT = 15556 # PUSH/PULL port
|
||||
self.LIVE_PORT = 15557 # PUSH/PULL port
|
||||
self.EVENTS_PORT = 15558 # PUSH/PULL port
|
||||
|
||||
# ZeroMQ timeout in seconds
|
||||
sys_timeout = 1
|
||||
data_timeout = 10
|
||||
|
||||
# initialise ZMQ context
|
||||
context = zmq.Context()
|
||||
|
||||
# connect to server sockets
|
||||
try:
|
||||
self.sys_socket = context.socket(zmq.REQ)
|
||||
# set port timeout
|
||||
self.sys_socket.RCVTIMEO = sys_timeout * 1000
|
||||
self.sys_socket.connect('tcp://{}:{}'.format(self.HOST, self.SYS_PORT))
|
||||
|
||||
self.data_socket = context.socket(zmq.PULL)
|
||||
# set port timeout
|
||||
self.data_socket.RCVTIMEO = data_timeout * 1000
|
||||
self.data_socket.connect('tcp://{}:{}'.format(self.HOST, self.DATA_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Binding ports ERROR")
|
||||
|
||||
def _send_request(self, data: dict) -> None:
|
||||
"""Send request to server via ZeroMQ System socket"""
|
||||
try:
|
||||
self.sys_socket.send_json(data)
|
||||
msg = self.sys_socket.recv_string()
|
||||
# terminal received the request
|
||||
assert msg == 'OK', 'Something wrong on server side'
|
||||
except AssertionError as err:
|
||||
raise zmq.NotDone(err)
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Sending request ERROR")
|
||||
|
||||
def _pull_reply(self):
|
||||
"""Get reply from server via Data socket with timeout"""
|
||||
try:
|
||||
msg = self.data_socket.recv_json()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone('Data socket timeout ERROR')
|
||||
return msg
|
||||
|
||||
def live_socket(self, context=None):
|
||||
"""Connect to socket in a ZMQ context"""
|
||||
try:
|
||||
context = context or zmq.Context.instance()
|
||||
socket = context.socket(zmq.PULL)
|
||||
socket.connect('tcp://{}:{}'.format(self.HOST, self.LIVE_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Live port connection ERROR")
|
||||
return socket
|
||||
|
||||
def streaming_socket(self, context=None):
|
||||
"""Connect to socket in a ZMQ context"""
|
||||
try:
|
||||
context = context or zmq.Context.instance()
|
||||
socket = context.socket(zmq.PULL)
|
||||
socket.connect('tcp://{}:{}'.format(self.HOST, self.EVENTS_PORT))
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Data port connection ERROR")
|
||||
return socket
|
||||
|
||||
def construct_and_send(self, **kwargs) -> dict:
|
||||
"""Construct a request dictionary from default and send it to server"""
|
||||
|
||||
# default dictionary
|
||||
request = {
|
||||
"action": None,
|
||||
"actionType": None,
|
||||
"symbol": None,
|
||||
"chartTF": None,
|
||||
"fromDate": None,
|
||||
"toDate": None,
|
||||
"id": None,
|
||||
"magic": None,
|
||||
"volume": None,
|
||||
"price": None,
|
||||
"stoploss": None,
|
||||
"takeprofit": None,
|
||||
"expiration": None,
|
||||
"deviation": None,
|
||||
"comment": None
|
||||
}
|
||||
|
||||
# update dict values if exist
|
||||
for key, value in kwargs.items():
|
||||
if key in request:
|
||||
request[key] = value
|
||||
else:
|
||||
raise KeyError('Unknown key in **kwargs ERROR')
|
||||
|
||||
# send dict to server
|
||||
self._send_request(request)
|
||||
|
||||
# return server reply
|
||||
return self._pull_reply()
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
All examples will be on Python 3. Lets create an instance of MetaTrader API class:
|
||||
|
||||
```python
|
||||
api = MTraderAPI()
|
||||
```
|
||||
|
||||
First of all we should configure the script `symbol` and `timeframe`. Live data stream will be configured to the same params.
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="CONFIG", symbol="EURUSD", chartTF="M5")
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Get information about the trading account.
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="ACCOUNT")
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Get historical data. `fromDate` should be in timestamp format. The data will be loaded to the last candle if `toDate` is `None`. Notice, that the script sends the last unclosed candle too. You should delete it manually.
|
||||
|
||||
There are some issues:
|
||||
|
||||
- MetaTrader keeps historical data in cache. But when you make a request for the first time, MetaTrader downloads the data from a broker. This operation can exceed `Data socket` timeout. It depends on your broker. Second request will be handeled quickly.
|
||||
- It takes 6-7 seconds to process `50000` M1 candles. It was tested on Windows 10 in Parallels Desktop container with 4 cores and 4GB RAM. So if you need more data there are three ways to handle it. 1) Increase `Data socket` timeout. 2) You can load data partially using `fromDate` and `toDate`. 3) You can use more powerfull hardware.
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="HISTORY", actionType="DATA", symbol="EURUSD", chartTF="M5", fromDate=1555555555)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
History data reply example:
|
||||
|
||||
```
|
||||
{'data': [[1560782340, 1.12271, 1.12288, 1.12269, 1.12277, 46.0],[1560782400, 1.12278, 1.12299, 1.12276, 1.12297, 43.0],[1560782460, 1.12296, 1.12302, 1.12293, 1.123, 23.0]]}
|
||||
```
|
||||
|
||||
Buy market order.
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_BUY", symbol="EURUSD", "volume"=0.1, "stoploss"=1.1, "takeprofit"=1.3)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
Sell limit order. Remember to switch SL/TP depending on BUY/SELL, or you will get `invalid stops` error.
|
||||
|
||||
- BUY: SL < price < TP
|
||||
- SELL: SL > price > TP
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_SELL_LIMIT", symbol="EURUSD", "volume"=0.1, "price"=1.2, "stoploss"=1.3, "takeprofit"=1.1)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
All pending orders are set to `Good till cancel` by default. If you want to set an expiration date, pass the date in timestamp format to `expiration` param.
|
||||
|
||||
```python
|
||||
rep = api.construct_and_send(action="TRADE", actionType="ORDER_TYPE_SELL_LIMIT", symbol="EURUSD", "volume"=0.1, "price"=1.2, "expiration"=1560782460)
|
||||
print(rep)
|
||||
```
|
||||
|
||||
## Live data and streaming events
|
||||
|
||||
Event handler example for `Live socket` and `Data socket`.
|
||||
|
||||
```python
|
||||
import zmq
|
||||
import threading
|
||||
|
||||
api = MTraderAPI()
|
||||
|
||||
|
||||
def _t_livedata():
|
||||
socket = api.live_socket()
|
||||
while True:
|
||||
try:
|
||||
last_candle = socket.recv_json()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Live data ERROR")
|
||||
print(last_candle)
|
||||
|
||||
|
||||
def _t_streaming_events():
|
||||
socket = api.streaming_socket()
|
||||
while True:
|
||||
try:
|
||||
trans = socket.recv_json()
|
||||
request, reply = trans.values()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Streaming data ERROR")
|
||||
print(request)
|
||||
print(reply)
|
||||
|
||||
|
||||
|
||||
t = threading.Thread(target=_t_livedata, daemon=True)
|
||||
t.start()
|
||||
|
||||
t = threading.Thread(target=_t_streaming_events, daemon=True)
|
||||
t.start()
|
||||
|
||||
while True:
|
||||
pass
|
||||
```
|
||||
|
||||
There are only two variants of `Live socket` data. When everything is ok, the script sends data on candle close:
|
||||
|
||||
```
|
||||
{"status":"CONNECTED","data":[1560780120,1.12186,1.12194,1.12186,1.12191,15.00000]}
|
||||
```
|
||||
|
||||
If the terminal has lost connection to the market:
|
||||
|
||||
```
|
||||
{"status":"DISCONNECTED"}
|
||||
```
|
||||
|
||||
When the terminal reconnects to the market, it sends the last closed candle again. So you should update your historical data. Make the `action="HISTORY"` request with `fromDate` equal to your last candle timestamp before disconnect.
|
||||
|
||||
`OnTradeTransaction` function is called when a trade transaction event occurs. `Streaming socket` sends `TRADE_TRANSACTION_REQUEST` data every time it happens. Try to create and modify orders in the MQL5 terminal manually and check the expert logging tab for better understanding. Also see [MQL5 docs](https://www.mql5.com/en/docs/event_handlers/ontradetransaction).
|
||||
|
||||
`TRADE_TRANSACTION_REQUEST` request data:
|
||||
|
||||
```
|
||||
{
|
||||
'action': 'TRADE_ACTION_DEAL',
|
||||
'order': 501700843,
|
||||
'symbol': 'EURUSD',
|
||||
'volume': 0.1,
|
||||
'price': 1.12181,
|
||||
'stoplimit': 0.0,
|
||||
'sl': 1.1,
|
||||
'tp': 1.13,
|
||||
'deviation': 10,
|
||||
'type': 'ORDER_TYPE_BUY',
|
||||
'type_filling': 'ORDER_FILLING_FOK',
|
||||
'type_time': 'ORDER_TIME_GTC',
|
||||
'expiration': 0,
|
||||
'comment': None,
|
||||
'position': 0,
|
||||
'position_by': 0
|
||||
}
|
||||
```
|
||||
|
||||
`TRADE_TRANSACTION_REQUEST` result data:
|
||||
|
||||
```
|
||||
{
|
||||
'retcode': 10009,
|
||||
'result': 'TRADE_RETCODE_DONE',
|
||||
'deal': 501700843,
|
||||
'order': 501700843,
|
||||
'volume': 0.1,
|
||||
'price': 1.12181,
|
||||
'comment': None,
|
||||
'request_id': 8,
|
||||
'retcode_external': 0
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
First of all, when you send a command via `System socket`, you should always receive back `"OK"` message via `System socket`. It means that your command was received and deserialized.
|
||||
|
||||
All data that come through `Data socket` have an `error` param. This param will have `true` key if somethng goes wrong. Also, there will be `description` and `function` params. They will hold information about error and the name of the function with error.
|
||||
|
||||
This information also applies to the trade commannds. See [MQL5 docs](https://www.mql5.com/en/docs/constants/errorswarnings/enum_trade_return_codes) for possible server answers.
|
||||
|
||||
## License
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See `LICENSE` for more information.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
### 11th January 2020
|
||||
|
||||
- add support for multiple datastreams in parallel for any combination of symbols and timeframes independently of the timeframe and symbol of the attached chart
|
||||
- add support for tick data
|
||||
- add support for direct download as CSV files
|
||||
- add one automatic retry binding to sockets. When running under Wine in Linux, sockets will be blocked for 60 seconds if closed uncleanly. This can happen if the client is still connected while the EA gets reloaded.
|
||||
- skip re-initialization on chart timeframe change
|
||||
- add testing mode with fake tick data
|
||||
@@ -1,73 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| OnTick(string symbol).mq5 |
|
||||
//| Copyright 2010, Lizar |
|
||||
//| https://login.mql5.com/ru/users/Lizar |
|
||||
//+------------------------------------------------------------------+
|
||||
#define VERSION "1.00 Build 1 (01 Fab 2011)"
|
||||
|
||||
#property copyright "Copyright 2010, Lizar"
|
||||
#property link "https://login.mql5.com/ru/users/Lizar"
|
||||
#property version VERSION
|
||||
#property description "Template of the Expert Advisor"
|
||||
#property description "with multicurrency OnTick(string symbol) event handler"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| MULTICURRENCY MODE SETTINGS |
|
||||
//| of OnTick(string symbol) event handler |
|
||||
//| |
|
||||
//| 1.1 List of symbols needed to proceed in the events: |
|
||||
#define SYMBOLS_TRADING "EURUSD","GBPUSD","USDJPY","USDCHF"
|
||||
//| 1.2 If you want all symbols from Market Watch, use this: |
|
||||
//#define SYMBOLS_TRADING "MARKET_WATCH"
|
||||
//| Note: Select only one way from 1.1 or 1.2. |
|
||||
//| |
|
||||
//| 2. Event type for OnTick(string symbol): |
|
||||
#define CHART_EVENT_SYMBOL CHARTEVENT_TICK
|
||||
//| Note: the event type must corresponds to the |
|
||||
//| ENUM_CHART_EVENT_SYMBOL enumeration. |
|
||||
//| |
|
||||
//| 3. Include file: |
|
||||
#include <OnTick(string symbol).mqh>
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//| This function must be declared, even if it empty. |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
//--- Add your code here...
|
||||
return(0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert multi tick function |
|
||||
//| Use this function instead of the standard OnTick() function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick(string symbol)
|
||||
{
|
||||
//--- Add your code here...
|
||||
Print("New event on symbol: ",symbol);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ChartEvent function |
|
||||
//| This function must be declared, even if it empty. |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnChartEvent(const int id, // event id
|
||||
const long& lparam, // event param of long type
|
||||
const double& dparam, // event param of double type
|
||||
const string& sparam) // event param of string type
|
||||
{
|
||||
//--- Add your code here...
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
//--- Add your code here...
|
||||
}
|
||||
|
||||
//+------------------------------ end -------------------------------+
|
||||
@@ -1,214 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| OnTick(string symbol).mqh |
|
||||
//| Copyright 2010, Lizar |
|
||||
//| https://login.mql5.com/ru/users/Lizar |
|
||||
//| Revision 2011.01.30 |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2010, Lizar"
|
||||
#property link "https://login.mql5.com/ru/users/Lizar"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| The events enumeration is implemented as flags |
|
||||
//| the events can be combined using the OR ("|") logical operation |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
|
||||
enum ENUM_CHART_EVENT_SYMBOL
|
||||
{
|
||||
CHARTEVENT_NO =0, // Events disabled
|
||||
CHARTEVENT_INIT =0, // "Initialization" event
|
||||
|
||||
CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart
|
||||
CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart
|
||||
CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart
|
||||
CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart
|
||||
CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart
|
||||
CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart
|
||||
CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart
|
||||
CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart
|
||||
CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart
|
||||
CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
|
||||
CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
|
||||
CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
|
||||
CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
|
||||
CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
|
||||
CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
|
||||
CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
|
||||
CHARTEVENT_TICK =0x00200000, // "New tick" event
|
||||
|
||||
CHARTEVENT_ALL =0xFFFFFFFF, // All events enabled
|
||||
};
|
||||
|
||||
//---
|
||||
string _symbol_[]={SYMBOLS_TRADING};
|
||||
int _handle_[];
|
||||
|
||||
int _symbols_total_ = 0; // total symbols
|
||||
int _symbols_market_ = 0; // number of symbols in Market Watch
|
||||
bool _market_watch_ = false; // use symbols from Market Watch
|
||||
bool _testing_ = false; // In testing mode
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Custom indicator initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
//--- check if we work in Strategy Tester:
|
||||
_testing_=((bool)MQL5InfoInteger(MQL5_TESTING) ||
|
||||
(bool)MQL5InfoInteger(MQL5_OPTIMIZATION) ||
|
||||
(bool)MQL5InfoInteger(MQL5_VISUAL_MODE));
|
||||
|
||||
//--- Check do we need to use the symbols from Market Watch
|
||||
if(_symbol_[0]=="MARKET_WATCH") _market_watch_=true;
|
||||
|
||||
//--- check settings
|
||||
if(_testing_ && _market_watch_)
|
||||
{
|
||||
Print("Error: Please specify list of symbols in SYMBOLS_TRADING for use in Strategy Tester ");
|
||||
return(1);
|
||||
}
|
||||
|
||||
//--- Initialization of variables and arrays:
|
||||
_symbols_total_=SymbolsTotal(false); // total symbols
|
||||
ArrayResize(_handle_,_symbols_total_); // resize array for handles of "spys"
|
||||
ArrayInitialize(_handle_,INVALID_HANDLE); // initalizae array for handles of "spys"
|
||||
|
||||
//--- Load "spys":
|
||||
if(_market_watch_) SynchronizationTradingTools();
|
||||
else
|
||||
{
|
||||
_symbols_total_=ArraySize(_symbol_);
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
if(!LoadAgent(i, _symbol_[i])) return(1);
|
||||
}
|
||||
|
||||
//--- Execute OnInit function of Expert Advisor
|
||||
_OnInit();
|
||||
return(0);
|
||||
}
|
||||
#define OnInit _OnInit // rendefine of OnInit function
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//| Used only in Strategy Tester |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
if(_testing_)
|
||||
{
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
{
|
||||
string __symbol__=_symbol_[i];
|
||||
if(MathAbs(GlobalVariableGet(__symbol__+"_flag")-2)<0.1)
|
||||
{
|
||||
GlobalVariableSet(__symbol__+"_flag",1);
|
||||
OnTick(__symbol__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| ChartEvent function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
|
||||
{
|
||||
//--- Call of OnTick(string symbol) or OnChartEvent event handler:
|
||||
if(id==CHARTEVENT_CUSTOM_LAST)
|
||||
{
|
||||
OnTick(sparam);
|
||||
//--- synchronize "agents" with Market Watch if necessary:
|
||||
if(_market_watch_) SynchronizationTradingTools();
|
||||
}
|
||||
else _OnChartEvent(id,lparam,dparam,sparam);
|
||||
}
|
||||
#define OnChartEvent _OnChartEvent // rendefine of OnChartEvent function
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Function for synchronization of "spys" with symbols |
|
||||
//| from "Market Watch" |
|
||||
//| INPUT: no. |
|
||||
//| OUTPUT: no. |
|
||||
//| REMARK: no. |
|
||||
//+------------------------------------------------------------------+
|
||||
void SynchronizationTradingTools()
|
||||
{
|
||||
if(_symbols_market_!=SymbolsTotal(true))
|
||||
{
|
||||
_symbols_market_=0;
|
||||
|
||||
for(int i=0;i<_symbols_total_;i++)
|
||||
{
|
||||
long __symbol_select__;
|
||||
string __symbol__=SymbolName(i,false);
|
||||
if(!SymbolInfoInteger(__symbol__,SYMBOL_SELECT,__symbol_select__)) return;
|
||||
|
||||
if(_handle_[i]==INVALID_HANDLE && __symbol_select__)
|
||||
{
|
||||
if(!LoadAgent(i,__symbol__)) return;
|
||||
}
|
||||
else if(_handle_[i]!=INVALID_HANDLE && !__symbol_select__)
|
||||
{
|
||||
if(!DeLoadAgent(i,__symbol__)) return;
|
||||
}
|
||||
}
|
||||
_symbols_market_=SymbolsTotal(true);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Function for loading of "spys" |
|
||||
//| INPUT: __id__ - id, corresponds to the symbol index in |
|
||||
//| the list of symbols |
|
||||
//| __symbol__ - symbol name |
|
||||
//| OUTPUT: true - if successful |
|
||||
//| false - if error |
|
||||
//| REMARK: no. |
|
||||
//+------------------------------------------------------------------+
|
||||
bool LoadAgent(int __id__, string __symbol__)
|
||||
{
|
||||
_handle_[__id__]=iCustom(__symbol__,_Period,"Spy Control panel MCM",ChartID(),65534,CHART_EVENT_SYMBOL);
|
||||
if(_handle_[__id__]==INVALID_HANDLE)
|
||||
{
|
||||
Print("Error in setting of agent for ",__symbol__);
|
||||
return(false);
|
||||
}
|
||||
Print("The agent for ",__symbol__," is set.");
|
||||
return(true);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Function for release of the "spys" |
|
||||
//| INPUT: __id__ - id, corresponds to the symbol index in |
|
||||
//| the list of symbols |
|
||||
//| __symbol__ - symbol name |
|
||||
//| OUTPUT: true - if successful |
|
||||
//| false - if error |
|
||||
//| REMARK: no. |
|
||||
//+------------------------------------------------------------------+
|
||||
bool DeLoadAgent(int __id__, string __symbol__)
|
||||
{
|
||||
if(_handle_[__id__]!=INVALID_HANDLE)
|
||||
{
|
||||
if(!IndicatorRelease(_handle_[__id__]))
|
||||
{
|
||||
Print("Error deletion of agent for ",__symbol__);
|
||||
return(false);
|
||||
}
|
||||
Print("The agent for ",__symbol__," is deleted.");
|
||||
_handle_[__id__]=INVALID_HANDLE;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
//+------------------------------ end -------------------------------+
|
||||
@@ -1,160 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Spy Control panel MCM.mq5 |
|
||||
//| Copyright 2010, Lizar |
|
||||
//| https://login.mql5.com/ru/users/Lizar |
|
||||
//+------------------------------------------------------------------+
|
||||
#define VERSION "1.00 Build 4 (30 Jan 2011)"
|
||||
|
||||
#property copyright "Copyright 2010, Lizar"
|
||||
#property link "https://login.mql5.com/ru/users/Lizar"
|
||||
#property version VERSION
|
||||
#property description "MCM Control panel agent."
|
||||
#property description "It can be attached to the chart (any timeframe) of the symbol needed"
|
||||
#property description "and generate the NewBar and/or NewTick custom events for the chart"
|
||||
|
||||
#property indicator_chart_window
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| The events enumeration is implemented as flags |
|
||||
//| the events can be combined using the OR ("|") logical operation |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
enum ENUM_CHART_EVENT_SYMBOL
|
||||
{
|
||||
CHARTEVENT_INIT =0, // "Initialization" event
|
||||
CHARTEVENT_NO =0, // Events disabled
|
||||
|
||||
CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on 1-m chart
|
||||
CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on 2-m chart
|
||||
CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on 3-m chart
|
||||
CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on 4-m chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on 5-m chart
|
||||
CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on 6-m chart
|
||||
CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on 10-m chart
|
||||
CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on 12-m chart
|
||||
|
||||
CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on 15-m chart
|
||||
CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on 20-m chart
|
||||
CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on 30-m chart
|
||||
CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on hourly chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
|
||||
CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
|
||||
CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
|
||||
CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
|
||||
CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
|
||||
CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
|
||||
CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
|
||||
|
||||
CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
|
||||
CHARTEVENT_TICK =0x00200000, // "New tick" event
|
||||
|
||||
CHARTEVENT_ALL =0xFFFFFFFF, // All events enabled
|
||||
};
|
||||
|
||||
input long chart_id; // chart id
|
||||
input ushort custom_event_id; // event id
|
||||
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO; // event flag.
|
||||
|
||||
MqlDateTime time, prev_time;
|
||||
|
||||
bool testing=false;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Custom indicator initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
testing=((bool)MQL5InfoInteger(MQL5_TESTING) ||
|
||||
(bool)MQL5InfoInteger(MQL5_OPTIMIZATION) ||
|
||||
(bool) MQL5InfoInteger(MQL5_VISUAL_MODE));
|
||||
|
||||
if(testing)
|
||||
{
|
||||
GlobalVariableTemp(_Symbol+"_flag");
|
||||
GlobalVariableTemp(_Symbol+"_custom_id");
|
||||
GlobalVariableTemp(_Symbol+"_event");
|
||||
GlobalVariableTemp(_Symbol+"_price");
|
||||
}
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Custom indicator iteration function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnCalculate (const int rates_total, // size of price[] array
|
||||
const int prev_calculated, // bars calculated at previous call
|
||||
const int begin, // begin
|
||||
const double& price[] // array for calculation
|
||||
)
|
||||
{
|
||||
double price_current=price[rates_total-1];
|
||||
|
||||
TimeCurrent(time);
|
||||
|
||||
if(prev_calculated==0)
|
||||
{
|
||||
EventCustom(CHARTEVENT_INIT,price_current);
|
||||
prev_time=time;
|
||||
return(rates_total);
|
||||
}
|
||||
|
||||
//--- new tick
|
||||
if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);
|
||||
|
||||
//--- check change time
|
||||
if(time.min==prev_time.min &&
|
||||
time.hour==prev_time.hour &&
|
||||
time.day==prev_time.day &&
|
||||
time.mon==prev_time.mon) return(rates_total);
|
||||
|
||||
//--- new minute
|
||||
if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);
|
||||
if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0) EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
|
||||
if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0) EventCustom(CHARTEVENT_NEWBAR_M3,price_current);
|
||||
if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0) EventCustom(CHARTEVENT_NEWBAR_M4,price_current);
|
||||
if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0) EventCustom(CHARTEVENT_NEWBAR_M5,price_current);
|
||||
if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0) EventCustom(CHARTEVENT_NEWBAR_M6,price_current);
|
||||
if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);
|
||||
if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);
|
||||
if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);
|
||||
if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);
|
||||
if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);
|
||||
if(time.min!=0) {prev_time=time; return(rates_total);}
|
||||
//--- new hour
|
||||
if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
|
||||
if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0) EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
|
||||
if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0) EventCustom(CHARTEVENT_NEWBAR_H3,price_current);
|
||||
if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0) EventCustom(CHARTEVENT_NEWBAR_H4,price_current);
|
||||
if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0) EventCustom(CHARTEVENT_NEWBAR_H6,price_current);
|
||||
if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0) EventCustom(CHARTEVENT_NEWBAR_H8,price_current);
|
||||
if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);
|
||||
if(time.hour!=0) {prev_time=time; return(rates_total);}
|
||||
//--- new day
|
||||
if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);
|
||||
//--- new week
|
||||
if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);
|
||||
//--- new month
|
||||
if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current);
|
||||
prev_time=time;
|
||||
//--- return value of prev_calculated for next call
|
||||
return(rates_total);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price)
|
||||
{
|
||||
if(!testing) EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol);
|
||||
else
|
||||
{
|
||||
if(GlobalVariableSet(_Symbol+"_custom_id",custom_event_id)==0) return;
|
||||
if(GlobalVariableSet(_Symbol+"_event",event)==0) return;
|
||||
if(GlobalVariableSet(_Symbol+"_price",price)==0) return;
|
||||
GlobalVariableSet(_Symbol+"_flag",2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user