update README and changelog
minor update to JsonAPIIndicator
This commit is contained in:
+1
-1
@@ -443,7 +443,7 @@ void IndicatorControl(CJAVal &dataObject){
|
||||
if(actionType=="REQUEST") {
|
||||
GetIndicatorResult(dataObject);
|
||||
}
|
||||
else if(actionType=="START") {
|
||||
else if(actionType=="ATTACH") {
|
||||
StartIndicator(dataObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ int OnCalculate(const int rates_total,
|
||||
const long &volume[],
|
||||
const int &spread[])
|
||||
{
|
||||
// While a new candle is forming, set the current value to the previous value
|
||||
// While a new candle is forming, set the current value to be empty
|
||||
if(rates_total>prev_calculated){
|
||||
Buffer[0] = EMPTY_VALUE;
|
||||
}
|
||||
@@ -109,7 +109,11 @@ void SubscriptionHandler(ZmqMsg &chartMsg){
|
||||
Alert("Deserialization Error");
|
||||
ExpertRemove();
|
||||
}
|
||||
if(message["indicatorChartId"]==IndicatorId) WriteToBuffer(message);
|
||||
if(message["indicatorChartId"]==IndicatorId) {
|
||||
if(message["action"]=="PLOT") {
|
||||
WriteToBuffer(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -131,6 +135,7 @@ void WriteToBuffer(CJAVal &message) {
|
||||
Buffer[i+1] = message["data"][messageDataSize-1-i].ToDbl();
|
||||
}
|
||||
}
|
||||
// Set the most recent plotted value to nothing, as we do not have any data for yet unformed candles
|
||||
Buffer[0] = EMPTY_VALUE;
|
||||
}
|
||||
|
||||
@@ -139,14 +144,14 @@ void WriteToBuffer(CJAVal &message) {
|
||||
//| Check for new indicator data function |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckMessages(){
|
||||
// This is a workaround for Timer(). It is needed, because Timer() works, when the indicator is manually added to a chart, but not with ChartIndicatorAdd()
|
||||
// This is a workaround for Timer(). It is needed, because OnTimer() works if the indicator is manually added to a chart, but not with ChartIndicatorAdd()
|
||||
|
||||
ZmqMsg chartMsg;
|
||||
|
||||
// Recieve chart instructions stream from client via live Chart socket.
|
||||
chartSubscriptionSocket.recv(chartMsg,true);
|
||||
|
||||
// Request recived
|
||||
// Request recieved
|
||||
if(chartMsg.size()>0){
|
||||
// Handle subscription SubscriptionHandler()
|
||||
SubscriptionHandler(chartMsg);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Tested on macOS Mojave / Windows 10 in Parallels Desktop container.
|
||||
|
||||
Tested on Manjaro Linux / Windows 10 in VirtualBox
|
||||
|
||||
Working in production on Debian 10 / Wine 4.
|
||||
|
||||
## Table of Contents
|
||||
@@ -13,6 +15,9 @@ Working in production on Debian 10 / Wine 4.
|
||||
- [Documentation](#documentation)
|
||||
- [Usage](#usage)
|
||||
- [Live data and streaming events](#live-data-and-streaming-events)
|
||||
- [Streaming MT5 indicator data](#streaming-mt5-indicator-data)
|
||||
- [Plot values to MT5 charts](#plot-values-to-mt5-charts)
|
||||
- [The JsonAPIIndicator](#the-jsonapiindicator)
|
||||
- [Error handling](#error-handling)
|
||||
- [License](#license)
|
||||
|
||||
@@ -22,7 +27,7 @@ This project was developed to work as a server for the Backtrader Python trading
|
||||
|
||||
Backtrader Python client is located here: [Python Backtrader - Metaquotes MQL5 ](https://github.com/khramkov/Backtrader-MQL5-API)
|
||||
|
||||
Thanks to the participation of [freedumb2000](https://github.com/freedumb2000), the project moved to a new level.
|
||||
Thanks to the participation of [Gunther Schulz](https://github.com/Gunther-Schulz), the project moved to a new level.
|
||||
|
||||
New features:
|
||||
|
||||
@@ -31,30 +36,41 @@ New features:
|
||||
- Support for direct download as CSV files
|
||||
- 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
|
||||
- Support for spread data (ask/bid)
|
||||
- Support for plotting to charts in MT5 by streaming values from the client
|
||||
- Support for processing client data with MT5 indicators
|
||||
|
||||
In development:
|
||||
|
||||
- Devitation
|
||||
- Stop limit orders
|
||||
- Drawing of chart objects
|
||||
|
||||
## 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`)
|
||||
2. Put the following files from this repo to your MetaEditor Iinclude` directory
|
||||
- `Include/Json.mqh`
|
||||
- `Include/controlerrors.mqh`
|
||||
- `Include/StringToEnumInt.mqh`
|
||||
3. Put the `Indicators/JsonAPIIndicator.mq5` file from this repo to your MetaEditor `Indicators` directory
|
||||
4. Download and compile `experts/JsonAPI.mq5` script.
|
||||
5. Check if Metatrader 5 automatic trading is allowed.
|
||||
6. Attach the `JsonAPI.mq5` script to a chart in Metatrader 5.
|
||||
7. Allow DLL import in dialog window.
|
||||
8. Check if the ports are free to use. (default:`15555`,`15556`, `15557`,`15558`, `15559`, `15560`,`15562`)
|
||||
|
||||
## Documentation
|
||||
|
||||
The script uses four ZeroMQ sockets:
|
||||
The script uses seven 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.
|
||||
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.
|
||||
5. `Indicator data socket` - automatically pushes indicator result values to the client.
|
||||
6. `Chart Data Socket` - Recieves values to be plotted to a specific chart.
|
||||
7. `Chart Indicator Socket` - Only for internal communication. Passes values to be plotted TO the supplied JsonAPIIndicator indicator
|
||||
|
||||
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.
|
||||
|
||||
@@ -76,35 +92,43 @@ The idea is to send requests via `System socket` and recieve results/errors via
|
||||
"takeprofit": null,
|
||||
"expiration": null,
|
||||
"deviation": null,
|
||||
"comment": null
|
||||
"comment": null,
|
||||
"chartId": None,
|
||||
"indicatorChartId": None,
|
||||
"chartIndicatorSubWindow": None,
|
||||
"style": None,
|
||||
}
|
||||
```
|
||||
|
||||
Check out the available combinations of `action` and `actionType`:
|
||||
|
||||
| action | actionType | Description |
|
||||
| --------- | --------------------- | ---------------------------- |
|
||||
| CONFIG | null | Set script configuration |
|
||||
| RESET | null | Reset subscribed symbols |
|
||||
| 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 | Download 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 |
|
||||
| action | actionType | Description |
|
||||
| --------- | --------------------- | --------------------------------- |
|
||||
| CONFIG | null | Set script configuration |
|
||||
| RESET | null | Reset subscribed symbols |
|
||||
| ACCOUNT | null | Get account settings |
|
||||
| BALANCE | null | Get current balance |
|
||||
| POSITIONS | null | Get current open positions |
|
||||
| ORDERS | null | Get current open orders |
|
||||
| INDICATOR | ATTACH | Attach an indicator and return ID |
|
||||
| INDICATOR | REQUEST | Get indicator data |
|
||||
| CHART | OPEN | Open a new chart window |
|
||||
| CHART | ADDINDICATOR | Attach JsonAPIIndicator indicator |
|
||||
| HISTORY | DATA | Get data history |
|
||||
| HISTORY | TRADES | Get trades history |
|
||||
| HISTORY | WRITE | Download 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:
|
||||
|
||||
@@ -118,6 +142,8 @@ class MTraderAPI:
|
||||
self.DATA_PORT = 15556 # PUSH/PULL port
|
||||
self.LIVE_PORT = 15557 # PUSH/PULL port
|
||||
self.EVENTS_PORT = 15558 # PUSH/PULL port
|
||||
self.INDICATOR_DATA_PORT = 15559 # REP/REQ port
|
||||
self.CHART_DATA_PORT = 15560 # PUSH port
|
||||
|
||||
# ZeroMQ timeout in seconds
|
||||
sys_timeout = 1
|
||||
@@ -137,6 +163,20 @@ class MTraderAPI:
|
||||
# set port timeout
|
||||
self.data_socket.RCVTIMEO = data_timeout * 1000
|
||||
self.data_socket.connect('tcp://{}:{}'.format(self.HOST, self.DATA_PORT))
|
||||
|
||||
self.indicator_data_socket = context.socket(zmq.PULL)
|
||||
# set port timeout
|
||||
self.indicator_data_socket.RCVTIMEO = data_timeout * 1000
|
||||
self.indicator_data_socket.connect(
|
||||
"tcp://{}:{}".format(self.HOST, self.INDICATOR_DATA_PORT)
|
||||
)
|
||||
self.chart_data_socket = context.socket(zmq.PUSH)
|
||||
# set port timeout
|
||||
# TODO check if port is listening and error handling
|
||||
self.chart_data_socket.connect(
|
||||
"tcp://{}:{}".format(self.HOST, self.CHART_DATA_PORT)
|
||||
)
|
||||
|
||||
except zmq.ZMQError:
|
||||
raise zmq.ZMQBindError("Binding ports ERROR")
|
||||
|
||||
@@ -160,6 +200,16 @@ class MTraderAPI:
|
||||
raise zmq.NotDone('Data socket timeout ERROR')
|
||||
return msg
|
||||
|
||||
def _indicator_pull_reply(self):
|
||||
"""Get reply from server via Data socket with timeout"""
|
||||
try:
|
||||
msg = self.indicator_data_socket.recv_json()
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Indicator Data socket timeout ERROR")
|
||||
if self.debug:
|
||||
print("ZMQ INDICATOR DATA REPLY: ", msg)
|
||||
return msg
|
||||
|
||||
def live_socket(self, context=None):
|
||||
"""Connect to socket in a ZMQ context"""
|
||||
try:
|
||||
@@ -180,6 +230,15 @@ class MTraderAPI:
|
||||
raise zmq.ZMQBindError("Data port connection ERROR")
|
||||
return socket
|
||||
|
||||
def _push_chart_data(self, data: dict) -> None:
|
||||
"""Send message for chart control to server via ZeroMQ chart data socket"""
|
||||
try:
|
||||
if self.debug:
|
||||
print("ZMQ PUSH CHART DATA: ", data, " -> ", data)
|
||||
self.chart_data_socket.send_json(data)
|
||||
except zmq.ZMQError:
|
||||
raise zmq.NotDone("Sending request ERROR")
|
||||
|
||||
def construct_and_send(self, **kwargs) -> dict:
|
||||
"""Construct a request dictionary from default and send it to server"""
|
||||
|
||||
@@ -199,7 +258,11 @@ class MTraderAPI:
|
||||
"takeprofit": None,
|
||||
"expiration": None,
|
||||
"deviation": None,
|
||||
"comment": None
|
||||
"comment": None,
|
||||
"chartId": None,
|
||||
"indicatorChartId": None,
|
||||
"chartIndicatorSubWindow": None,
|
||||
"style": None,
|
||||
}
|
||||
|
||||
# update dict values if exist
|
||||
@@ -214,6 +277,58 @@ class MTraderAPI:
|
||||
|
||||
# return server reply
|
||||
return self._pull_reply()
|
||||
|
||||
def indicator_construct_and_send(self, **kwargs) -> dict:
|
||||
"""Construct a request dictionary from default and send it to server"""
|
||||
|
||||
# default dictionary
|
||||
request = {
|
||||
"action": None,
|
||||
"actionType": None,
|
||||
"id": None,
|
||||
"symbol": None,
|
||||
"chartTF": None,
|
||||
"fromDate": None,
|
||||
"toDate": None,
|
||||
"name": None,
|
||||
"params": None,
|
||||
"linecount": 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._indicator_pull_reply()
|
||||
|
||||
def chart_data_construct_and_send(self, **kwargs) -> dict:
|
||||
"""Construct a request dictionary from default and send it to server"""
|
||||
|
||||
# default dictionary
|
||||
message = {
|
||||
"action": None,
|
||||
"actionType": None,
|
||||
"chartId": None,
|
||||
"indicatorChartId": None,
|
||||
"data": None,
|
||||
}
|
||||
|
||||
# update dict values if exist
|
||||
for key, value in kwargs.items():
|
||||
if key in message:
|
||||
message[key] = value
|
||||
else:
|
||||
raise KeyError("Unknown key in **kwargs ERROR")
|
||||
|
||||
# send dict to server
|
||||
self._push_chart_data(message)
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -395,6 +510,99 @@ When the terminal reconnects to the market, it sends the last closed candle agai
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming MT5 indicator data
|
||||
|
||||
Open a chart window and attach a MT5 indicator.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` - a unique id string.
|
||||
- `symbol` - chart symbol to open and atatch the indicator to.
|
||||
- `chartTF` - timeframe to set the chart at.
|
||||
- `name` - the name of the MT5 indicator to attach.
|
||||
- `params` - the initialisation paramaters that the specified indicator expects.
|
||||
- `linecount` - the number of buffers the indicator returns. In the example below MACD is used and it return the values for "macd" and "signal".
|
||||
|
||||
```python
|
||||
print(api.indicator_construct_and_send(action='INDICATOR', actionType='ATTACH', id='4df306ea-e8e6-439b-8004-b86ba4bcc8c3', symbol='EURUSD', chartTF='M1', name='Examples/MACD', 'params'=['12', '26', '9', 'PRICE_CLOSE'], 'linecount'=2))
|
||||
```
|
||||
|
||||
Stream the calculated result values of a previously attached indicator.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `id` - id string of a previously attached indicator.
|
||||
- `fromDate` - timestamp for which a result value is requested.
|
||||
|
||||
```python
|
||||
print(api.indicator_construct_and_send(action='INDICATOR', actionType='REQUEST', id='4df306ea-e8e6-439b-8004-b86ba4bcc8c3', 'fromDate'=1591993860))
|
||||
```
|
||||
|
||||
Example of the result:
|
||||
|
||||
```python
|
||||
{'error': False, 'id': '4df306ea-e8e6-439b-8004-b86ba4bcc8c3', 'data': ['0.00008204', '0.00001132']}
|
||||
```
|
||||
|
||||
The data field holds a list with results of the calculated indicator buffers.
|
||||
|
||||
## Plot values to MT5 charts
|
||||
|
||||
Open a new chart window to plot values to.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `chartId` - a unique id string to reference the new chart window.
|
||||
- `fromDate` - timestamp for which a result value is requested.
|
||||
- `symbol` - chart symbol to open and atatch the indicator to.
|
||||
- `chartTF` - timeframe to set the chart at.
|
||||
|
||||
```python
|
||||
print(api.construct_and_send(action='CHART', actionType='OPEN', symbol='EURUSD', chartTF='M1', chartId='cbb82988-3193-4dda-9cea-c27faaf7835b'))
|
||||
```
|
||||
|
||||
A common scenario would be to stream vlaues calculated by the client indictor to be plotted in MT5. This is done by attaching the supplied MT5 indicator `JsonAPIIndicator` and passing values to be plotted to it.
|
||||
|
||||
Initialize a plot line object by attaching a new instance of `JsonAPIIndicator`, ready to recieve values to be plotted.
|
||||
|
||||
Parameters:
|
||||
|
||||
- `chartId` - id string of a previously opened chart.
|
||||
- `indicatorChartId`: a unique id string to reference the new plot line object.
|
||||
- `chartIndicatorSubWindow`: chart sub window to plot to (https://www.mql5.c.om/en/docs/chart_operations/chartindicatoradd)
|
||||
- `style`: style settings for the plot. `shortname` and `linelabel` can be any string value. `linewidth` expects an int. All other paramters require constants supported by MQL5.
|
||||
Supported are the following style paramers (with the corresponding MQL5 constants in braces): `color` (PLOT_LINE_COLOR), `linetype` (PLOT_DRAW_TYPE), `linestyle` (PLOT_LINE_STYLE).
|
||||
|
||||
```python
|
||||
print(api.construct_and_send(action='CHART', actionType='ADDINDICATOR', chartId='cbb82988-3193-4dda-9cea-c27faaf7835b', indicatorChartId='5f2c1ab5-6b36-498f-96ac-3982a4a3551a', chartIndicatorSubWindow=1, style={shortname='BT-BollingerBands', linelabel='Middle', color='clrYellow', linetype='DRAW_LINE', linestyle='STYLE_SOLID', linewidth=1))
|
||||
```
|
||||
|
||||
Stream values to a plot line object (draw a line).
|
||||
|
||||
Parameters:
|
||||
|
||||
- `chartId` - id string of a previously opened chart.
|
||||
- `indicatorChartId`: id string of a previously initialized plot line object.
|
||||
- `data`: list of values to plot. The last value in a list (`values[-1]`) corresponds to the most recent candle. If the size of the list of values passsed is >= 1, and the number of historic candles to plot is `n` then `values[n-1]` is the most recent candle and `values[0]` is the oldest candle.
|
||||
|
||||
```python
|
||||
# Plot line with historic data
|
||||
values=[1.1225948211353751, 1.1226243406054506, 1.1226266123404378]
|
||||
print(api.chart_data_construct_and_send(action='PLOT', chartId='cbb82988-3193-4dda-9cea-c27faaf7835b', indicatorChartId='5f2c1ab5-6b36-498f-96ac-3982a4a3551a', chartIndicatorSubWindow=1, data=values))
|
||||
|
||||
n=len(values)
|
||||
print(f'The value for the oldest candle: {values[0]} - The value for the most recent candle: {values[n-1]}')
|
||||
|
||||
# Extend the plotted line with the most recent values as new candles are created
|
||||
print(api.chart_data_construct_and_send(action='PLOT', chartId='cbb82988-3193-4dda-9cea-c27faaf7835b', indicatorChartId='5f2c1ab5-6b36-498f-96ac-3982a4a3551a', chartIndicatorSubWindow=1, data=[1.122618120966847]))
|
||||
print(api.chart_data_construct_and_send(action='PLOT', chartId='cbb82988-3193-4dda-9cea-c27faaf7835b', indicatorChartId='5f2c1ab5-6b36-498f-96ac-3982a4a3551a', chartIndicatorSubWindow=1, data=[1.1226254106923093]))
|
||||
```
|
||||
|
||||
## The JsonAPIIndicator
|
||||
|
||||
The supplied indicator `JsonAPIIndicator` does not do any calculations by itself. It simply plots
|
||||
incoming data to a chart which can be passed by via JSON interface to the `Chart Data Socket`. The indicator is controlled by the expert script `JsonAPI.mq5` locally via port `15562`.
|
||||
|
||||
## 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.
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
### 30th April 2020
|
||||
|
||||
- add support for spreads
|
||||
- add support for drawing custom indicator data to charts
|
||||
- add support for indicator data output
|
||||
- add support for plotting custom indicator data to charts
|
||||
- add support for streaming MT5 indicator data
|
||||
- new error reporting
|
||||
|
||||
### 16th February 2020
|
||||
|
||||
Reference in New Issue
Block a user