From 34af70c043f80298c2292aa31a9c268a7f649766 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Sun, 16 Feb 2020 11:51:25 +0100 Subject: [PATCH 01/17] add script to remove binary characters and add BOM --- clean.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 clean.sh diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..4db1e7f --- /dev/null +++ b/clean.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# https://www.commandlinefu.com/commands/view/11560/delete-all-non-printing-characters-from-a-file +# http://www.skybert.net/bash/adding-utf-8-bom-from-the-command-line/ + +# Remove non-printable ASCII characters +tr -cd '[:print:]\n\r' < ./Experts/JsonAPI.mq5 > ./Experts/JsonAPI.clean.mq5 +# Add UTF-8 BOM +sed -i '1s/^/\xef\xbb\xbf/' ./Experts/JsonAPI.clean.mq5 +mv ./Experts/JsonAPI.clean.mq5 ./Experts/JsonAPI.mq5 + + From dc60a0a0c955e67ada126a9c9869a2171edbcdbd Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Sun, 16 Feb 2020 12:22:02 +0100 Subject: [PATCH 02/17] update .gitattributes for Metatrader 5 file types --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitattributes b/.gitattributes index dfe0770..20f1b94 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ # Auto detect text files and perform LF normalization * text=auto + +text *.mq5 eol=CRLF diff=c +text *.mqh eol=CRLF diff=c \ No newline at end of file From c1e136d64ee8214d09712df0439dd45c29601725 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Sun, 16 Feb 2020 12:49:51 +0100 Subject: [PATCH 03/17] minor README.md formating cleanup --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6ea1f0d..bdf5c24 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ Tested on macOS Mojave / Windows 10 in Parallels Desktop container. Working in production on Debian 10 / Wine 4. - - ## Table of Contents - [About the Project](#about-the-project) @@ -28,11 +26,11 @@ Thanks to the participation of [freedumb2000](https://github.com/freedumb2000), New features: -- support for multiple datastreams in parallel for any combination of symbols and timeframes independently of the timeframe and symbol of the attached chart -- support for tick data -- 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 multiple datastreams in parallel for any combination of symbols and timeframes independently of the timeframe and symbol of the attached chart +- Support for tick data +- 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 In development: @@ -233,6 +231,7 @@ print(api.construct_and_send(action="CONFIG", symbol="EURUSD", chartTF="M5")) print(api.construct_and_send(action="CONFIG", symbol="AUDUSD", chartTF="M1")) ... ``` + There is also `tick` data. You can subscribe for `tick` and `candle` data at the same `symbol`. ```python @@ -240,12 +239,12 @@ print(api.construct_and_send(action="CONFIG", symbol="EURUSD", chartTF="TICK")) print(api.construct_and_send(action="CONFIG", symbol="EURUSD", chartTF="M1")) ``` -If you want to stop `Live data`, you should reset server subscriptions. +If you want to stop `Live data`, you should reset server subscriptions. ```python rep = api.construct_and_send(action="RESET") print(rep) -``` +``` Get information about the trading account. From f27bc3d1d965f7f3d24b262186879a9c246a8910 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Sun, 16 Feb 2020 12:23:28 +0100 Subject: [PATCH 04/17] add indicator control any mt5 indicator --- Experts/JsonAPI.mq5 | 182 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 164 insertions(+), 18 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 24a5592..74342dc 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -36,6 +36,7 @@ int SYS_PORT=15555; int DATA_PORT=15556; int LIVE_PORT=15557; int STR_PORT=15558; +int IND_DATA_PORT=15559; // ZeroMQ Cnnections Context context("MQL5 JSON API"); @@ -43,6 +44,7 @@ Socket sysSocket(context,ZMQ_REP); Socket dataSocket(context,ZMQ_PUSH); Socket liveSocket(context,ZMQ_PUSH); Socket streamSocket(context,ZMQ_PUSH); +Socket indicatorDataSocket(context,ZMQ_PUSH); // Global variables bool debug = false; @@ -53,6 +55,10 @@ string chartSymbols[]; int chartSymbolCount = 0; string chartSymbolSettings[][3]; +int indicatorCount = 0; +double indicators[]; +string indicatorIds[]; + //+------------------------------------------------------------------+ //| Bind ZMQ sockets to ports | //+------------------------------------------------------------------+ @@ -66,22 +72,27 @@ bool BindSockets(){ if (result == false) return result; result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); if (result == false) return result; + result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_PORT)); + if (result == false) return result; Print("Bound 'System' socket on port ", SYS_PORT); Print("Bound 'Data' socket on port ", DATA_PORT); Print("Bound 'Live' socket on port ", LIVE_PORT); Print("Bound 'Streaming' socket on port ", STR_PORT); + Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT); sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); + indicatorDataSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); dataSocket.setSendHighWaterMark(5); liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); + indicatorDataSocket.setSendHighWaterMark(5); return result; } @@ -159,17 +170,31 @@ void OnTick(){ //+------------------------------------------------------------------+ //| Check if subscribed to symbol and timeframe combination | //+------------------------------------------------------------------+ -bool hasChartSymbol(string symbol, string chartTF) +bool HasChartSymbol(string symbol, string chartTF) { for(int i=0;i= 0) { + if(CopyBuffer(indicators[idx], i, fromDate, 1, values) < 0) values[0] = 0.0; + results[i] = DoubleToString(values[0]); + } + else { + // TODO error handling + } + } + + if (liveStream) { + CJAVal data; + data["id"] = (string) id; + data["data"].Set(results); + + string t=data.Serialize(); + if(debug) Print(t); + InformClientSocket(indicatorDataSocket,t); + } +} + //+------------------------------------------------------------------+ //| Account information | //+------------------------------------------------------------------+ @@ -709,18 +847,26 @@ void GetOrders(CJAVal &dataObject){ } //+------------------------------------------------------------------+ -//| Clear symbol subscriptions | +//| Clear symbol subscriptions and indicators | //+------------------------------------------------------------------+ -void ResetSubscriptions(CJAVal &dataObject){ +void ResetSubscriptionsAndIndicators(){ ArrayFree(chartSymbols); - chartSymbolCount=0; ArrayFree(chartSymbolSettings); + chartSymbolCount=0; - if(ArraySize(chartSymbols)!=0 ||ArraySize(chartSymbolSettings)!=0){ + bool error = false; + for(int i=0;i Date: Sun, 16 Feb 2020 21:43:06 +0100 Subject: [PATCH 05/17] add spread support for price candle data --- Experts/JsonAPI.mq5 | 14 +++++++++++--- changelog.md | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 74342dc..6f65ffa 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -215,6 +215,7 @@ void StreamPriceData(){ datetime thisBar = 0; MqlTick tick; MqlRates rates[1]; + int spread[1]; if( chartTF == "TICK"){ if(SymbolInfoTick(symbol,tick) !=true) { /*error processing */ }; @@ -222,6 +223,7 @@ void StreamPriceData(){ } else { if(CopyRates(symbol,period,1,1,rates)!=1) { /*error processing */ }; + if(CopySpread(symbol,period,1,1,spread)!=1) { /*error processing */ }; thisBar=(datetime)rates[0].time; } if(lastBar!=thisBar){ @@ -238,6 +240,7 @@ void StreamPriceData(){ Data[3] = (double) rates[0].low; Data[4] = (double) rates[0].close; Data[5] = (double) rates[0].tick_volume; + Data[6] = (int) spread[0]; } last["status"] = (string) "CONNECTED"; @@ -612,6 +615,7 @@ void HistoryInfo(CJAVal &dataObject){ CJAVal c, d; MqlRates r[]; + int spread[]; string fileName=symbol + "-" + chartTF + ".csv"; // file name string directoryName="Data"; // directory name string outputFile=directoryName+"//"+fileName; @@ -629,6 +633,7 @@ void HistoryInfo(CJAVal &dataObject){ if(dataObject["toDate"].ToInt()!=NULL)Print("4) Date to:"+TimeToString(toDate)); barCount=CopyRates(symbol,period,fromDate,toDate,r); + if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*error processing */ } if(barCount){ ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); @@ -642,7 +647,7 @@ void HistoryInfo(CJAVal &dataObject){ PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file for(int i=0;i Date: Mon, 17 Feb 2020 14:55:30 +0100 Subject: [PATCH 06/17] add indicator control --- Experts/JsonAPI.mq5 | 259 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 204 insertions(+), 55 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 6f65ffa..36f8f44 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -1,4 +1,4 @@ -//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ // // Copyright (C) 2019 Nikolai Khramkov // @@ -30,7 +30,9 @@ #include #include #include +#include +// Set ports and host for ZeroMQ string HOST="*"; int SYS_PORT=15555; int DATA_PORT=15556; @@ -51,13 +53,18 @@ bool debug = false; bool liveStream = true; bool connectedFlag= true; int deInitReason = -1; + +// Variables for handling price data stream string chartSymbols[]; int chartSymbolCount = 0; string chartSymbolSettings[][3]; +// Variables for controlling indicators int indicatorCount = 0; double indicators[]; string indicatorIds[]; +int indicatorParamCount[]; +int indicatorBufferCount[]; //+------------------------------------------------------------------+ //| Bind ZMQ sockets to ports | @@ -102,7 +109,7 @@ bool BindSockets(){ //+------------------------------------------------------------------+ int OnInit(){ /* Bindinig ZMQ ports on init */ - +Print(GetIndicatorConstantValue("VOLUME_TICK"), " ",VOLUME_TICK); // Skip reloading of the EA script when the reason to reload is a chart timeframe change if (deInitReason != REASON_CHARTCHANGE){ @@ -125,9 +132,54 @@ int OnInit(){ return(INIT_FAILED); } + //testDraw(); return(INIT_SUCCEEDED); } +void testDraw() { +// ChartSetSymbolPeriod(ChartID(), Symbol(),PERIOD_M1); + + // long chart_id; + // chart_id = ChartOpen("EURUSD",PERIOD_M1); + // Print(chart_id); + +long chart_id = 0; + int window = 0; + + datetime time1 = 1581891300; + double price1 = 1.08346; + datetime time2 = 1581891600; + double price2 = 1.08354; + chart_id = ChartOpen("EURUSD",PERIOD_M5); + Print(chart_id); + /* + bool CChartObjectTrend::Create(long chart_id,string name,int window, + datetime time1,double price1,datetime time2,double price2) + { + bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2); + if(result) result&=Attach(chart_id,name,window,2); +//--- + return(result); + } + */ + CChartObject object; + //ObjectCreate(chart_id,"ellipse",OBJ_ELLIPSE,window,time1,price1,time2,price2,time2,price2+0.00005); + //ObjectCreate(chart_id,"ellipse",OBJ_ELLIPSE,window,time1-100,price1,time1,price1+0.00010,time1+200,price1); + //ObjectCreate(chart_id,"trend",OBJ_TREND,window,time1,price1,time2,price2); + ObjectCreate(chart_id,"reactangle",OBJ_TREND,window,time1,price1+0.00010,time2,price2+0.00020); + + + //--- attach chart object + /* + if(!object.Attach(ChartID(),"MyObject",0,2)) + { + printf("Object attach error"); + + } + */ + //ChartSetSymbolPeriod(ChartID(), Symbol(),PERIOD_M5); + ChartRedraw(chart_id); +} //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ @@ -342,7 +394,7 @@ void RequestHandler(ZmqMsg &request){ else if(action=="POSITIONS") GetPositions(message); else if(action=="ORDERS") GetOrders(message); else if(action=="RESET") ResetSubscriptionsAndIndicators(); - else if(action=="INDICATOR") StartIndicator(message); + else if(action=="INDICATOR") IndicatorControl(message); // Action command error processing else ActionDoneOrError(65538, __FUNCTION__); @@ -352,7 +404,7 @@ void RequestHandler(ZmqMsg &request){ //| Reconfigure the script params | //+------------------------------------------------------------------+ void ScriptConfiguration(CJAVal &dataObject){ - + //testDraw(); string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); string actionType=dataObject["actionType"].ToStr(); @@ -377,114 +429,152 @@ void ScriptConfiguration(CJAVal &dataObject){ } +//+------------------------------------------------------------------+ +//| Start new indicator or request indicator data | +//+------------------------------------------------------------------+ +void IndicatorControl(CJAVal &dataObject){ + + string actionType=dataObject["actionType"].ToStr(); + + if(actionType=="REQUEST") { + GetIndicatorResult(dataObject); + } + else if(actionType=="START") { + StartIndicator(dataObject); + } + +} + //+------------------------------------------------------------------+ //| Start new indicator instance | //+------------------------------------------------------------------+ void StartIndicator(CJAVal &dataObject){ - + string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); string id=dataObject["id"].ToStr(); - string indicatorName=dataObject["indicatorName"].ToStr(); + string indicatorName=dataObject["name"].ToStr(); - indicatorCount++; + indicatorCount++; ArrayResize(indicators,indicatorCount); ArrayResize(indicatorIds,indicatorCount); + ArrayResize(indicatorParamCount,indicatorCount); + ArrayResize(indicatorBufferCount,indicatorCount); + int idx = indicatorCount-1; + indicatorIds[idx] = id; + indicatorBufferCount[idx] = dataObject["linecount"].ToInt(); double params[]; - int paramsCount = dataObject["indicatorParams"].Size(); - for(int i=0;i= 0) { - if(CopyBuffer(indicators[idx], i, fromDate, 1, values) < 0) values[0] = 0.0; - results[i] = DoubleToString(values[0]); - } - else { - // TODO error handling - } + double values[2]; + ArrayResize(values, indicatorBufferCount[idx]); + CJAVal results; + // Cycle through all avaliable buffer positions + for(int i=0;i= 0) { + if(CopyBuffer(indicators[idx], i, fromDate, 1, values) < 0) {/* Error handling */} + results[i] = DoubleToString(values[0]); + } + else { + // TODO error handling } - - if (liveStream) { - CJAVal data; - data["id"] = (string) id; - data["data"].Set(results); - - string t=data.Serialize(); - if(debug) Print(t); - InformClientSocket(indicatorDataSocket,t); } + + + CJAVal message; + message["id"] = (string) id; + message["data"].Set(results); + + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(indicatorDataSocket,t); + + // TODO more error handling + //if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ + // ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); + //} + //else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } //+------------------------------------------------------------------+ @@ -1077,7 +1167,66 @@ ENUM_TIMEFRAMES GetTimeframe(string chartTF){ else tf=NULL; return(tf); } - + +//+------------------------------------------------------------------+ +//| Convert indicator constants from string to int | +//+------------------------------------------------------------------+ +int GetIndicatorConstantValue(string indicatorConstantString){ + + int r = -1; + ENUM_APPLIED_PRICE ap; + r = StringToEnum(indicatorConstantString,ap); + if(r>=0)return r; + + ENUM_APPLIED_VOLUME av; + r = StringToEnum(indicatorConstantString,av); + if(r>=0)return r; + + ENUM_STO_PRICE sp; + r = StringToEnum(indicatorConstantString,sp); + if(r>=0)return r; + + ENUM_MA_METHOD mm; + r = StringToEnum(indicatorConstantString,mm); + if(r>=0)return r; + + return(-1); +} + +#define MIN_ENUM_VALUES 0 +#define MAX_ENUM_VALUES 255 +//+------------------------------------------------------------------+ +//| StringToEnum : Convert a string to an ENUM value, | +//| it loop between min(0) and max(255), adjustable if needed. | +//| Non existing enum value defined as -1. If -1 is used as an | +//| enum value, code need to be adjusted to an other default. | +//| Parameters : | +//| in - string to convert | +//| out - ENUM value | +//| @return - int if conversion succeed, false otherwise | +//| | +//| Based on: | +//| https://www.mql5.com/en/forum/61741/page3#comment_5491344 | +//+------------------------------------------------------------------+ +template +int StringToEnum(string in,ENUM &out) + { + out=-1; +//--- + for(int i=MIN_ENUM_VALUES;i<=MAX_ENUM_VALUES;i++) + { + ENUM enumValue=(ENUM)i; + if(in==EnumToString(enumValue)) + { + out=enumValue; + break; + } + } +//--- + return(out); + } + + //+------------------------------------------------------------------+ //| Trade confirmation | //+------------------------------------------------------------------+ From 7099ac1cacec6a376fb3046564784f6efcb02b02 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Sun, 1 Mar 2020 18:06:24 +0100 Subject: [PATCH 07/17] add chart control open and draw indicator lines on chart --- Experts/JsonAPI.mq5 | 591 ++++++++++++++++++++++---------- Include/EnumStringToInt.mqh | 382 +++++++++++++++++++++ Indicators/JsonAPIIndicator.mq5 | 200 +++++++++++ clean.sh | 11 + 4 files changed, 1003 insertions(+), 181 deletions(-) create mode 100644 Include/EnumStringToInt.mqh create mode 100644 Indicators/JsonAPIIndicator.mq5 diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 36f8f44..2aae593 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -1,4 +1,4 @@ -//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ // // Copyright (C) 2019 Nikolai Khramkov // @@ -30,7 +30,12 @@ #include #include #include -#include +#include +//#include +//#include +//#include +// Starts an Expert Advisor with specified parameters +//#include // Set ports and host for ZeroMQ string HOST="*"; @@ -39,6 +44,8 @@ int DATA_PORT=15556; int LIVE_PORT=15557; int STR_PORT=15558; int IND_DATA_PORT=15559; +int CHART_LIVE_PORT=15560; +int PUB_CHART_LIVE_PORT=15562; // ZeroMQ Cnnections Context context("MQL5 JSON API"); @@ -47,24 +54,65 @@ Socket dataSocket(context,ZMQ_PUSH); Socket liveSocket(context,ZMQ_PUSH); Socket streamSocket(context,ZMQ_PUSH); Socket indicatorDataSocket(context,ZMQ_PUSH); +Socket chartLiveSocket(context,ZMQ_PULL); +Socket pubChartLiveSocket(context,ZMQ_PUB); // Global variables bool debug = false; bool liveStream = true; bool connectedFlag= true; int deInitReason = -1; +double chartAttached = ChartID(); // Variables for handling price data stream -string chartSymbols[]; -int chartSymbolCount = 0; -string chartSymbolSettings[][3]; +struct SymbolSubscription { + string symbol; + string chartTf; + datetime lastBar; +}; + +SymbolSubscription symbolSubscriptions[]; +//string chartSymbols[]; +int symbolSubscriptionCount = 0; +//string chartSymbolSettings[][3]; // Variables for controlling indicators + +struct Indicator { + long id; + string indicatorId; + int indicatorHandle; + int indicatorParamCount; + int indicatorBufferCount; +}; +Indicator indicators[]; int indicatorCount = 0; +/* double indicators[]; string indicatorIds[]; int indicatorParamCount[]; int indicatorBufferCount[]; +*/ + +// Variables for controlling chart +struct ChartWindow { + long id; + string chartId; + string indicatorId; + int indicatorHandle; +}; +ChartWindow chartWindows[]; +int chartWindowCount = 0; + +// Refresh chart windows interval. OnTimer function of indicatore JsonAPIIndicator gets triggered on each interval +int chartWindowTimerInterval = 100; +int chartWindowTimerCounter = 0; + + +//string chartWindowObjects[]; + +//double chartCurvePositions[][3]; + //+------------------------------------------------------------------+ //| Bind ZMQ sockets to ports | @@ -82,17 +130,26 @@ bool BindSockets(){ result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_PORT)); if (result == false) return result; + result = chartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); + if (result == false) { return result; } else {Print("Bound 'Chart Live' socket on port ", CHART_LIVE_PORT);} + + result = pubChartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); + if (result == false) { return result; } else {Print("Bound 'PUB Chart Live' socket on port ", PUB_CHART_LIVE_PORT);} + Print("Bound 'System' socket on port ", SYS_PORT); Print("Bound 'Data' socket on port ", DATA_PORT); Print("Bound 'Live' socket on port ", LIVE_PORT); Print("Bound 'Streaming' socket on port ", STR_PORT); Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT); + sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); + chartLiveSocket.setLinger(1000); + //pubChartLiveSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); @@ -100,6 +157,8 @@ bool BindSockets(){ liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); + chartLiveSocket.setReceiveHighWaterMark(1); // TODO confirm settings + //pubChartLiveSocket.setReceiveHighWaterMark(1); return result; } @@ -109,10 +168,10 @@ bool BindSockets(){ //+------------------------------------------------------------------+ int OnInit(){ /* Bindinig ZMQ ports on init */ -Print(GetIndicatorConstantValue("VOLUME_TICK"), " ",VOLUME_TICK); + // Skip reloading of the EA script when the reason to reload is a chart timeframe change if (deInitReason != REASON_CHARTCHANGE){ - + EventSetMillisecondTimer(1); int bindSocketsDelay = 65; // Seconds to wait if binding of sockets fails. @@ -136,50 +195,6 @@ Print(GetIndicatorConstantValue("VOLUME_TICK"), " ",VOLUME_TICK); return(INIT_SUCCEEDED); } -void testDraw() { -// ChartSetSymbolPeriod(ChartID(), Symbol(),PERIOD_M1); - - // long chart_id; - // chart_id = ChartOpen("EURUSD",PERIOD_M1); - // Print(chart_id); - -long chart_id = 0; - int window = 0; - - datetime time1 = 1581891300; - double price1 = 1.08346; - datetime time2 = 1581891600; - double price2 = 1.08354; - chart_id = ChartOpen("EURUSD",PERIOD_M5); - Print(chart_id); - /* - bool CChartObjectTrend::Create(long chart_id,string name,int window, - datetime time1,double price1,datetime time2,double price2) - { - bool result=ObjectCreate(chart_id,name,OBJ_TREND,window,time1,price1,time2,price2); - if(result) result&=Attach(chart_id,name,window,2); -//--- - return(result); - } - */ - CChartObject object; - //ObjectCreate(chart_id,"ellipse",OBJ_ELLIPSE,window,time1,price1,time2,price2,time2,price2+0.00005); - //ObjectCreate(chart_id,"ellipse",OBJ_ELLIPSE,window,time1-100,price1,time1,price1+0.00010,time1+200,price1); - //ObjectCreate(chart_id,"trend",OBJ_TREND,window,time1,price1,time2,price2); - ObjectCreate(chart_id,"reactangle",OBJ_TREND,window,time1,price1+0.00010,time2,price2+0.00020); - - - //--- attach chart object - /* - if(!object.Attach(ChartID(),"MyObject",0,2)) - { - printf("Object attach error"); - - } - */ - //ChartSetSymbolPeriod(ChartID(), Symbol(),PERIOD_M5); - ChartRedraw(chart_id); -} //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ @@ -204,12 +219,22 @@ void OnDeinit(const int reason){ liveSocket.unbind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); Print("Unbinding 'Streaming' socket on port ", STR_PORT, ".."); streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); + Print("Unbinding 'CHart' socket on port ", STR_PORT, ".."); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); + Print("Unbinding 'pub chart' socket on port ", STR_PORT, ".."); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); // Shutdown ZeroMQ Context context.shutdown(); context.destroy(0); + // Reset + ResetSubscriptionsAndIndicators(); + EventKillTimer(); + for(int i=0;i0){ + Print(chartMsg.getData()); + pubChartLiveSocket.send(chartMsg,true); + ResetLastError(); } -*/ + + // Trigger the indicator JsonAPIIndicator to check for new Messages + if(chartWindowTimerCounter >= chartWindowTimerInterval) { + for(int i=0;i= 0) { - if(CopyBuffer(indicators[idx], i, fromDate, 1, values) < 0) {/* Error handling */} + if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) {/* Error handling */} results[i] = DoubleToString(values[0]); } else { @@ -577,6 +628,237 @@ void GetIndicatorResult(CJAVal &dataObject) { //else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } +//+------------------------------------------------------------------+ +//| Open new chart or add indicator to chart | +//+------------------------------------------------------------------+ + +void ChartControl(CJAVal &dataObject){ + + string actionType=dataObject["actionType"].ToStr(); + + if(actionType=="ADDINDICATOR") { + AddIndicatorChartToChart(dataObject); + } + else if(actionType=="OPEN") { + OpenChart(dataObject); + } +} + +//+------------------------------------------------------------------+ +//| Open new chart | +//+------------------------------------------------------------------+ +void OpenChart(CJAVal &dataObject){ + + string chartId=dataObject["chartId"].ToStr(); + string symbol=dataObject["symbol"].ToStr(); + string chartTF=dataObject["chartTF"].ToStr(); + + chartWindowCount++; + ArrayResize(chartWindows,chartWindowCount); + //ArrayResize(chartWindowIds,chartWindowCount); + //ArrayResize(chartWindowPeriods,chartWindowCount); + // ArrayResize(chartWindowObjects,chartWindowCount); + + int idx = chartWindowCount-1; + + chartWindows[idx].chartId = chartId; + + ENUM_TIMEFRAMES period = GetTimeframe(chartTF); + chartWindows[idx].id = ChartOpen(symbol, period); + //chartWindowPeriods[idx] = period; + + CJAVal message; + message["chartId"] = (string) chartId; + + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(dataSocket,t); +} + +//+------------------------------------------------------------------+ +//| Add JsonAPIIndicator indicator chart to chart | +//+------------------------------------------------------------------+ +void AddIndicatorChartToChart(CJAVal &dataObject){ + + string chartIdStr=dataObject["chartId"].ToStr(); + string chartIndicatorId=dataObject["indicatorChartId"].ToStr(); + int chartIndicatorSubWindow=dataObject["chartIndicatorSubWindow"].ToInt(); + string shortname = dataObject["style"]["shortname"].ToStr(); + string colorstyle = dataObject["style"]["color"].ToStr(); + string linetype = dataObject["style"]["linetype"].ToStr(); + string linestyle = dataObject["style"]["linestyle"].ToStr(); + int linewidth = dataObject["style"]["linewidth"].ToInt(); + + int idx = GetChartWindowIdxByChartWindowId(chartIdStr); + long ChartId = chartWindows[idx].id; + + double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortname,colorstyle,linetype,linestyle,linewidth); + + ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle); + + chartWindows[idx].indicatorId = chartIndicatorId; + chartWindows[idx].indicatorHandle = chartIndicatorHandle; + + CJAVal message; + message["chartId"] = (string) chartIdStr; + + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(dataSocket,t); +} + +//+------------------------------------------------------------------+ +//| Draw on chart | +//+------------------------------------------------------------------+ +void ChartDraw(CJAVal &dataObject){ +/* + Print("drawchart"); + + string id=dataObject["id"].ToStr(); + //int windowIdx = GetChartWindowIdxByChartWindowId(id); + + int datesSize = dataObject["data"][0].Size(); + int x[]; + int y[]; + double x_dbl[]; + double y_dbl[]; + ArrayResize(chartCurvePositions, datesSize); + ArrayResize(x,datesSize); + ArrayResize(y,datesSize); + ArrayResize(x_dbl,datesSize); + ArrayResize(y_dbl,datesSize); + for(int i=0;i=0)return r; - - ENUM_APPLIED_VOLUME av; - r = StringToEnum(indicatorConstantString,av); - if(r>=0)return r; - - ENUM_STO_PRICE sp; - r = StringToEnum(indicatorConstantString,sp); - if(r>=0)return r; - - ENUM_MA_METHOD mm; - r = StringToEnum(indicatorConstantString,mm); - if(r>=0)return r; - - return(-1); -} - -#define MIN_ENUM_VALUES 0 -#define MAX_ENUM_VALUES 255 -//+------------------------------------------------------------------+ -//| StringToEnum : Convert a string to an ENUM value, | -//| it loop between min(0) and max(255), adjustable if needed. | -//| Non existing enum value defined as -1. If -1 is used as an | -//| enum value, code need to be adjusted to an other default. | -//| Parameters : | -//| in - string to convert | -//| out - ENUM value | -//| @return - int if conversion succeed, false otherwise | -//| | -//| Based on: | -//| https://www.mql5.com/en/forum/61741/page3#comment_5491344 | -//+------------------------------------------------------------------+ -template -int StringToEnum(string in,ENUM &out) - { - out=-1; -//--- - for(int i=MIN_ENUM_VALUES;i<=MAX_ENUM_VALUES;i++) - { - ENUM enumValue=(ENUM)i; - if(in==EnumToString(enumValue)) - { - out=enumValue; - break; - } - } -//--- - return(out); - } - - //+------------------------------------------------------------------+ //| Trade confirmation | //+------------------------------------------------------------------+ @@ -1386,4 +1614,5 @@ string getUninitReasonText(int reasonCode) } //--- return text; - } \ No newline at end of file + } + \ No newline at end of file diff --git a/Include/EnumStringToInt.mqh b/Include/EnumStringToInt.mqh new file mode 100644 index 0000000..f26d640 --- /dev/null +++ b/Include/EnumStringToInt.mqh @@ -0,0 +1,382 @@ + +#define MIN_ENUM_VALUES 0 +#define MAX_ENUM_VALUES 255 +//+------------------------------------------------------------------+ +//| StringToEnum : Convert a string to an ENUM value, | +//| it loops between min(0) and max(255), adjustable if needed. | +//| Non existing enum value defined as -1. If -1 is used as an | +//| enum value, code need to be adjusted to an other default. | +//| Parameters : | +//| in - string to convert | +//| out - ENUM value | +//| @return - int if conversion succeeded, false otherwise | +//| | +//| Based on: | +//| https://www.mql5.com/en/forum/61741/page3#comment_5491344 | +//+------------------------------------------------------------------+ +template +int StringToEnum(string in,ENUM &out) + { + out=-1; +//--- + for(int i=MIN_ENUM_VALUES;i<=MAX_ENUM_VALUES;i++) + { + ENUM enumValue=(ENUM)i; + if(in==EnumToString(enumValue)) + { + out=enumValue; + break; + } + } +//--- + return(out); + } + + +int StringToEnumInt(string indicatorConstantString, bool debug=1){ + + int r = -1; + + ENUM_ACCOUNT_INFO_DOUBLE a1; + r = StringToEnum(indicatorConstantString,a1); + //if(debug) Print("ENUM type: ENUM_ACCOUNT_INFO_DOUBLE"); + if(r>=0)return r; + ENUM_ACCOUNT_INFO_INTEGER a2; + r = StringToEnum(indicatorConstantString,a2); + if(r>=0)return r; + ENUM_ACCOUNT_INFO_STRING a3; + r = StringToEnum(indicatorConstantString,a3); + if(r>=0)return r; + ENUM_ACCOUNT_MARGIN_MODE a4; + r = StringToEnum(indicatorConstantString,a4); + if(r>=0)return r; + ENUM_ACCOUNT_STOPOUT_MODE a5; + r = StringToEnum(indicatorConstantString,a5); + if(r>=0)return r; + ENUM_ACCOUNT_TRADE_MODE a6; + r = StringToEnum(indicatorConstantString,a6); + if(r>=0)return r; + ENUM_ALIGN_MODE a7; + r = StringToEnum(indicatorConstantString,a7); + if(r>=0)return r; + ENUM_ANCHOR_POINT a8; + r = StringToEnum(indicatorConstantString,a8); + if(r>=0)return r; + ENUM_APPLIED_PRICE a9; + r = StringToEnum(indicatorConstantString,a9); + if(r>=0)return r; + ENUM_APPLIED_PRICE a10; + r = StringToEnum(indicatorConstantString,a10); + if(r>=0)return r; + ENUM_APPLIED_VOLUME a11; + r = StringToEnum(indicatorConstantString,a11); + if(r>=0)return r; + ENUM_ARROW_ANCHOR a12; + r = StringToEnum(indicatorConstantString,a12); + if(r>=0)return r; + ENUM_BASE_CORNER a13; + r = StringToEnum(indicatorConstantString,a13); + if(r>=0)return r; + ENUM_BOOK_TYPE a14; + r = StringToEnum(indicatorConstantString,a14); + if(r>=0)return r; + ENUM_BORDER_TYPE a15; + r = StringToEnum(indicatorConstantString,a15); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_FREQUENCY a16; + r = StringToEnum(indicatorConstantString,a16); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_IMPACT a17; + r = StringToEnum(indicatorConstantString,a17); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_IMPORTANCE a18; + r = StringToEnum(indicatorConstantString,a18); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_MULTIPLIER a19; + r = StringToEnum(indicatorConstantString,a19); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_SECTOR a20; + r = StringToEnum(indicatorConstantString,a20); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_TIMEMODE a21; + r = StringToEnum(indicatorConstantString,a21); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_TYPE a22; + r = StringToEnum(indicatorConstantString,a22); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_UNIT a23; + r = StringToEnum(indicatorConstantString,a23); + if(r>=0)return r; + ENUM_CHART_EVENT a24; + r = StringToEnum(indicatorConstantString,a24); + if(r>=0)return r; + ENUM_CHART_MODE a25; + r = StringToEnum(indicatorConstantString,a25); + if(r>=0)return r; + ENUM_CHART_POSITION a26; + r = StringToEnum(indicatorConstantString,a26); + if(r>=0)return r; + ENUM_CHART_PROPERTY_DOUBLE a27; + r = StringToEnum(indicatorConstantString,a27); + if(r>=0)return r; + ENUM_CHART_PROPERTY_INTEGER a28; + r = StringToEnum(indicatorConstantString,a28); + if(r>=0)return r; + ENUM_CHART_PROPERTY_STRING a29; + r = StringToEnum(indicatorConstantString,a29); + if(r>=0)return r; + ENUM_CHART_VOLUME_MODE a30; + r = StringToEnum(indicatorConstantString,a30); + if(r>=0)return r; + ENUM_CL_DEVICE_TYPE a31; + r = StringToEnum(indicatorConstantString,a31); + if(r>=0)return r; + ENUM_COLOR_FORMAT a32; + r = StringToEnum(indicatorConstantString,a32); + if(r>=0)return r; + ENUM_CRYPT_METHOD a33; + r = StringToEnum(indicatorConstantString,a33); + if(r>=0)return r; + ENUM_CUSTOMIND_PROPERTY_DOUBLE a34; + r = StringToEnum(indicatorConstantString,a34); + if(r>=0)return r; + ENUM_CHART_PROPERTY_INTEGER a35; + r = StringToEnum(indicatorConstantString,a35); + if(r>=0)return r; + ENUM_CUSTOMIND_PROPERTY_STRING a36; + r = StringToEnum(indicatorConstantString,a36); + if(r>=0)return r; + ENUM_DATABASE_EXPORT_FLAGS a37; + r = StringToEnum(indicatorConstantString,a37); + if(r>=0)return r; + ENUM_DATABASE_FIELD_TYPE a38; + r = StringToEnum(indicatorConstantString,a38); + if(r>=0)return r; + ENUM_DATABASE_OPEN_FLAGS a39; + r = StringToEnum(indicatorConstantString,a39); + if(r>=0)return r; + ENUM_DATABASE_PRINT_FLAGS a40; + r = StringToEnum(indicatorConstantString,a40); + if(r>=0)return r; + ENUM_DATATYPE a41; + r = StringToEnum(indicatorConstantString,a41); + if(r>=0)return r; + ENUM_DAY_OF_WEEK a42; + r = StringToEnum(indicatorConstantString,a42); + if(r>=0)return r; + ENUM_DEAL_ENTRY a43; + r = StringToEnum(indicatorConstantString,a43); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_DOUBLE a44; + r = StringToEnum(indicatorConstantString,a44); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_INTEGER a45; + r = StringToEnum(indicatorConstantString,a45); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_STRING a46; + r = StringToEnum(indicatorConstantString,a46); + if(r>=0)return r; + ENUM_DEAL_REASON a47; + r = StringToEnum(indicatorConstantString,a47); + if(r>=0)return r; + ENUM_DEAL_TYPE a48; + r = StringToEnum(indicatorConstantString,a48); + if(r>=0)return r; + ENUM_DRAW_TYPE a49; + r = StringToEnum(indicatorConstantString,a49); + if(r>=0)return r; + ENUM_DX_BUFFER_TYPE a50; + r = StringToEnum(indicatorConstantString,a50); + if(r>=0)return r; + ENUM_DX_FORMAT a51; + r = StringToEnum(indicatorConstantString,a51); + if(r>=0)return r; + ENUM_DX_HANDLE_TYPE a52; + r = StringToEnum(indicatorConstantString,a52); + if(r>=0)return r; + ENUM_DX_PRIMITIVE_TOPOLOGY a53; + r = StringToEnum(indicatorConstantString,a53); + if(r>=0)return r; + ENUM_DX_SHADER_TYPE a54; + r = StringToEnum(indicatorConstantString,a54); + if(r>=0)return r; + ENUM_ELLIOT_WAVE_DEGREE a55; + r = StringToEnum(indicatorConstantString,a55); + if(r>=0)return r; + ENUM_FILESELECT_FLAGS a56; + r = StringToEnum(indicatorConstantString,a56); + if(r>=0)return r; + ENUM_FILE_POSITION a57; + r = StringToEnum(indicatorConstantString,a57); + if(r>=0)return r; + ENUM_FILE_PROPERTY_INTEGER a58; + r = StringToEnum(indicatorConstantString,a58); + if(r>=0)return r; + ENUM_GANN_DIRECTION a59; + r = StringToEnum(indicatorConstantString,a59); + if(r>=0)return r; + ENUM_INDEXBUFFER_TYPE a60; + r = StringToEnum(indicatorConstantString,a60); + if(r>=0)return r; + ENUM_INDICATOR a61; + r = StringToEnum(indicatorConstantString,a61); + if(r>=0)return r; + ENUM_INIT_RETCODE a62; + r = StringToEnum(indicatorConstantString,a62); + if(r>=0)return r; + ENUM_LICENSE_TYPE a63; + r = StringToEnum(indicatorConstantString,a63); + if(r>=0)return r; + ENUM_LINE_STYLE a64; + r = StringToEnum(indicatorConstantString,a64); + if(r>=0)return r; + ENUM_MA_METHOD a65; + r = StringToEnum(indicatorConstantString,a65); + if(r>=0)return r; + ENUM_MQL_INFO_INTEGER a66; + r = StringToEnum(indicatorConstantString,a66); + if(r>=0)return r; + ENUM_MQL_INFO_STRING a67; + r = StringToEnum(indicatorConstantString,a67); + if(r>=0)return r; + ENUM_OBJECT a68; + r = StringToEnum(indicatorConstantString,a68); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_DOUBLE a69; + r = StringToEnum(indicatorConstantString,a69); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_INTEGER a70; + r = StringToEnum(indicatorConstantString,a70); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_STRING a71; + r = StringToEnum(indicatorConstantString,a71); + if(r>=0)return r; + ENUM_OPENCL_HANDLE_TYPE a72; + r = StringToEnum(indicatorConstantString,a72); + if(r>=0)return r; + ENUM_OPENCL_PROPERTY_INTEGER a73; + r = StringToEnum(indicatorConstantString,a73); + if(r>=0)return r; + ENUM_PLOT_PROPERTY_STRING a74; + r = StringToEnum(indicatorConstantString,a74); + if(r>=0)return r; + ENUM_POINTER_TYPE a75; + r = StringToEnum(indicatorConstantString,a75); + if(r>=0)return r; + ENUM_POSITION_PROPERTY_DOUBLE a76; + r = StringToEnum(indicatorConstantString,a76); + if(r>=0)return r; + ENUM_ORDER_PROPERTY_INTEGER a77; + r = StringToEnum(indicatorConstantString,a77); + if(r>=0)return r; + ENUM_PLOT_PROPERTY_STRING a78; + r = StringToEnum(indicatorConstantString,a78); + if(r>=0)return r; + ENUM_PROGRAM_TYPE a79; + r = StringToEnum(indicatorConstantString,a79); + if(r>=0)return r; + ENUM_SERIESMODE a80; + r = StringToEnum(indicatorConstantString,a80); + if(r>=0)return r; + ENUM_SERIES_INFO_INTEGER a81; + r = StringToEnum(indicatorConstantString,a81); + if(r>=0)return r; + ENUM_SIGNAL_BASE_DOUBLE a82; + r = StringToEnum(indicatorConstantString,a82); + if(r>=0)return r; + ENUM_SIGNAL_BASE_INTEGER a83; + r = StringToEnum(indicatorConstantString,a83); + if(r>=0)return r; + ENUM_SIGNAL_BASE_STRING a84; + r = StringToEnum(indicatorConstantString,a84); + if(r>=0)return r; + ENUM_SIGNAL_INFO_DOUBLE a85; + r = StringToEnum(indicatorConstantString,a85); + if(r>=0)return r; + ENUM_SIGNAL_INFO_INTEGER a86; + r = StringToEnum(indicatorConstantString,a86); + if(r>=0)return r; + ENUM_SIGNAL_INFO_STRING a87; + r = StringToEnum(indicatorConstantString,a87); + if(r>=0)return r; + ENUM_STATISTICS a88; + r = StringToEnum(indicatorConstantString,a88); + if(r>=0)return r; + ENUM_STO_PRICE a89; + r = StringToEnum(indicatorConstantString,a89); + if(r>=0)return r; + ENUM_SYMBOL_CALC_MODE a90; + r = StringToEnum(indicatorConstantString,a90); + if(r>=0)return r; + ENUM_SYMBOL_CHART_MODE a91; + r = StringToEnum(indicatorConstantString,a91); + if(r>=0)return r; + ENUM_SYMBOL_INFO_DOUBLE a92; + r = StringToEnum(indicatorConstantString,a92); + if(r>=0)return r; + ENUM_SYMBOL_INFO_INTEGER a93; + r = StringToEnum(indicatorConstantString,a93); + if(r>=0)return r; + ENUM_SYMBOL_INFO_STRING a94; + r = StringToEnum(indicatorConstantString,a94); + if(r>=0)return r; + ENUM_STATISTICS a95; + r = StringToEnum(indicatorConstantString,a95); + if(r>=0)return r; + ENUM_STO_PRICE a96; + r = StringToEnum(indicatorConstantString,a96); + if(r>=0)return r; + ENUM_SYMBOL_CALC_MODE a97; + r = StringToEnum(indicatorConstantString,a97); + if(r>=0)return r; + ENUM_SYMBOL_CHART_MODE a98; + r = StringToEnum(indicatorConstantString,a98); + if(r>=0)return r; + ENUM_SYMBOL_INFO_DOUBLE a99; + r = StringToEnum(indicatorConstantString,a99); + if(r>=0)return r; + ENUM_SYMBOL_INFO_INTEGER a100; + r = StringToEnum(indicatorConstantString,a100); + if(r>=0)return r; + ENUM_SYMBOL_INFO_STRING a101; + r = StringToEnum(indicatorConstantString,a101); + if(r>=0)return r; + ENUM_SYMBOL_OPTION_MODE a102; + r = StringToEnum(indicatorConstantString,a102); + if(r>=0)return r; + ENUM_SYMBOL_OPTION_RIGHT a103; + r = StringToEnum(indicatorConstantString,a103); + if(r>=0)return r; + ENUM_SYMBOL_ORDER_GTC_MODE a104; + r = StringToEnum(indicatorConstantString,a104); + if(r>=0)return r; + ENUM_SYMBOL_SWAP_MODE a105; + r = StringToEnum(indicatorConstantString,a105); + if(r>=0)return r; + ENUM_SYMBOL_TRADE_EXECUTION a106; + r = StringToEnum(indicatorConstantString,a106); + if(r>=0)return r; + ENUM_SYMBOL_TRADE_MODE a107; + r = StringToEnum(indicatorConstantString,a107); + if(r>=0)return r; + ENUM_TERMINAL_INFO_DOUBLE a108; + r = StringToEnum(indicatorConstantString,a108); + if(r>=0)return r; + ENUM_TERMINAL_INFO_INTEGER a109; + r = StringToEnum(indicatorConstantString,a109); + if(r>=0)return r; + ENUM_TERMINAL_INFO_STRING a110; + r = StringToEnum(indicatorConstantString,a110); + if(r>=0)return r; + ENUM_TIMEFRAMES a111; + r = StringToEnum(indicatorConstantString,a111); + if(r>=0)return r; + ENUM_TRADE_REQUEST_ACTIONS a112; + r = StringToEnum(indicatorConstantString,a112); + if(r>=0)return r; + ENUM_TRADE_TRANSACTION_TYPE a113; + r = StringToEnum(indicatorConstantString,a113); + if(r>=0)return r; + + return(-1); +} diff --git a/Indicators/JsonAPIIndicator.mq5 b/Indicators/JsonAPIIndicator.mq5 new file mode 100644 index 0000000..12d06b7 --- /dev/null +++ b/Indicators/JsonAPIIndicator.mq5 @@ -0,0 +1,200 @@ +//+------------------------------------------------------------------+ +//| JsonAPIIndicator.mq5 | +//| Copyright 2020, Gunther Schulz | +//| https://www.guntherschulz.de | +//+------------------------------------------------------------------+ + +#property copyright "2020 Gunther Schulz" +#property link "https://www.guntherschulz.de" +#property version "1.00" + +#include +#include +#include + +// Set ports and host for ZeroMQ +string HOST="localhost"; +int CHART_SUB_PORT=15562; + +// ZeroMQ Cnnections +Context context("MQL5 JSON API"); +Socket chartLiveSocket(context,ZMQ_SUB); + +//#property indicator_separate_window +//#property indicator_buffers 1 +//#property indicator_plots 1 +//---- plot MA +//#property indicator_label1 "MA" +//#property indicator_type1 DRAW_LINE +//#property indicator_color1 clrRed +//#property indicator_style1 STYLE_SOLID +//#property indicator_width1 1 + +//--- input parameters +input string IndicatorId=""; +input string ShortName="JsonAPI"; +input string ColorSyle = "clrRed"; +input string LineType = "DRAW_LINE"; +input string LineStyle = "STYLE_SOLID"; +input int LineWidth = 1; + +//--- indicator settings +double Buffer[]; +bool debug = false; + +bool first = false; + +//double Values[]; +//ENUM_TIMEFRAMES originalTF = ChartPeriod(); + +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { + bool result = chartLiveSocket.connect(StringFormat("tcp://%s:%d", HOST, CHART_SUB_PORT)); + if (result == false) {Print("Failed to subscrbe on port ", CHART_SUB_PORT);} + else { + Print("Accepting Chart Indicator data on port ", CHART_SUB_PORT); + chartLiveSocket.setSubscribe(""); + chartLiveSocket.setLinger(1000); + // Number of messages to buffer in RAM. + chartLiveSocket.setReceiveHighWaterMark(5); // TODO confirm settings + } + +//--- indicator buffers mapping; + ArraySetAsSeries(Buffer,true); + SetIndexBuffer(0,Buffer,INDICATOR_DATA); + + color colorstyle = StringToColor(ColorSyle); + int linetype = StringToEnumInt(LineType); + int linestyle = StringToEnumInt(LineStyle); + SetStyle(ShortName, colorstyle, linetype, linestyle, LineWidth); +//--- + return(INIT_SUCCEEDED); + } + + +void SetStyle(string shortname, color colorstyle, int linetype, int linestyle, int linewidth) { + IndicatorSetString(INDICATOR_SHORTNAME,shortname); + PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,colorstyle); + PlotIndexSetInteger(0,PLOT_DRAW_TYPE,linetype); + PlotIndexSetInteger(0,PLOT_LINE_STYLE,linestyle); + PlotIndexSetInteger(0,PLOT_LINE_WIDTH,linewidth); +} + +//+------------------------------------------------------------------+ +//| Custom indicator iteration function | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const datetime &time[], + const double &open[], + const double &high[], + const double &low[], + const double &close[], + const long &tick_volume[], + const long &volume[], + const int &spread[]) + { + // While a new candle is forming, set the current value to the previous value + if(rates_total>prev_calculated){ + Buffer[0] = EMPTY_VALUE; + } + +/* +https://www.mql5.com/en/docs/constants/structures/mqlparam + +*/ + + +//--- return value of prev_calculated for next call + return(rates_total); + } + +void SubscriptionHandler(ZmqMsg &chartMsg){ + CJAVal message; + + //ResetLastError(); + + // Get data from reguest + string msg=chartMsg.getData(); + if(debug) Print("Processing:"+msg); + + // Deserialize msg to CJAVal array + if(!message.Deserialize(msg)){ + //ActionDoneOrError(65537, __FUNCTION__); + Alert("Deserialization Error"); + ExpertRemove(); + } + if(message["indicatorChartId"]==IndicatorId) WriteToBuffer(message); +} + +void WriteToBuffer(CJAVal &message) { + int bufferSize = ArraySize(Buffer); + int messageDataSize = message["data"].Size(); + if(first==false) { + //ArrayFill(Buffer, 0, ArraySize(Buffer), EMPTY_VALUE); + PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,bufferSize-messageDataSize); + first = true; + } + + for(int i=0;i0){ + // Handle subscription SubscriptionHandler(). + Print(chartMsg.getData()); + SubscriptionHandler(chartMsg); + ChartRedraw(ChartID()); + } +} + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + //if(id==CHARTEVENT_CUSTOM+222 && sparam==IndicatorId) CheckMessages(); + if(id==CHARTEVENT_CUSTOM+222) CheckMessages(); + } +//+---------------------------------------------------- diff --git a/clean.sh b/clean.sh index 4db1e7f..48c0908 100755 --- a/clean.sh +++ b/clean.sh @@ -10,3 +10,14 @@ sed -i '1s/^/\xef\xbb\xbf/' ./Experts/JsonAPI.clean.mq5 mv ./Experts/JsonAPI.clean.mq5 ./Experts/JsonAPI.mq5 +# Remove non-printable ASCII characters +tr -cd '[:print:]\n\r' < ./Indicators/JsonAPIIndicator.mq5 > ./Indicators/JsonAPIIndicator.clean.mq5 +# Add UTF-8 BOM +sed -i '1s/^/\xef\xbb\xbf/' ./Indicators/JsonAPIIndicator.clean.mq5 +mv ./Indicators/JsonAPIIndicator.clean.mq5 ./Indicators/JsonAPIIndicator.mq5 + +# Remove non-printable ASCII characters +tr -cd '[:print:]\n\r' < ./Include/EnumStringToInt.mqh > ./Include/EnumStringToInt.clean.mqh +# Add UTF-8 BOM +sed -i '1s/^/\xef\xbb\xbf/' ./Include/EnumStringToInt.clean.mqh +mv ./Include/EnumStringToInt.clean.mqh ./Include/EnumStringToInt.mqh \ No newline at end of file From cc0f9b75750fb222c0fe74bc6b1c23c3f0c6970b Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Mon, 2 Mar 2020 09:07:28 +0100 Subject: [PATCH 08/17] change error handling to utilize MQL5 standard error handling --- Experts/JsonAPI.mq5 | 412 ++++++++++++++++++++++---------------- Include/controlerrors.mqh | Bin 0 -> 40742 bytes 2 files changed, 243 insertions(+), 169 deletions(-) create mode 100644 Include/controlerrors.mqh diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 2aae593..2f0b465 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -31,6 +31,7 @@ #include #include #include +#include //#include //#include //#include @@ -70,14 +71,10 @@ struct SymbolSubscription { string chartTf; datetime lastBar; }; - SymbolSubscription symbolSubscriptions[]; -//string chartSymbols[]; int symbolSubscriptionCount = 0; -//string chartSymbolSettings[][3]; // Variables for controlling indicators - struct Indicator { long id; string indicatorId; @@ -87,12 +84,6 @@ struct Indicator { }; Indicator indicators[]; int indicatorCount = 0; -/* -double indicators[]; -string indicatorIds[]; -int indicatorParamCount[]; -int indicatorBufferCount[]; -*/ // Variables for controlling chart struct ChartWindow { @@ -108,6 +99,7 @@ int chartWindowCount = 0; int chartWindowTimerInterval = 100; int chartWindowTimerCounter = 0; +ControlErrors mControl; //string chartWindowObjects[]; @@ -120,28 +112,19 @@ int chartWindowTimerCounter = 0; bool BindSockets(){ bool result = false; result = sysSocket.bind(StringFormat("tcp://%s:%d", HOST,SYS_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'System' socket on port ", SYS_PORT);} result = dataSocket.bind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Data' socket on port ", DATA_PORT);} result = liveSocket.bind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Live' socket on port ", LIVE_PORT);} result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Streaming' socket on port ", STR_PORT);} result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_PORT)); - if (result == false) return result; - + if (result == false) { return result; } else {Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT);} result = chartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); if (result == false) { return result; } else {Print("Bound 'Chart Live' socket on port ", CHART_LIVE_PORT);} - result = pubChartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); if (result == false) { return result; } else {Print("Bound 'PUB Chart Live' socket on port ", PUB_CHART_LIVE_PORT);} - - Print("Bound 'System' socket on port ", SYS_PORT); - Print("Bound 'Data' socket on port ", DATA_PORT); - Print("Bound 'Live' socket on port ", LIVE_PORT); - Print("Bound 'Streaming' socket on port ", STR_PORT); - Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT); - sysSocket.setLinger(1000); dataSocket.setLinger(1000); @@ -149,7 +132,7 @@ bool BindSockets(){ streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); chartLiveSocket.setLinger(1000); - //pubChartLiveSocket.setLinger(1000); + pubChartLiveSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); @@ -158,7 +141,7 @@ bool BindSockets(){ streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); chartLiveSocket.setReceiveHighWaterMark(1); // TODO confirm settings - //pubChartLiveSocket.setReceiveHighWaterMark(1); + pubChartLiveSocket.setReceiveHighWaterMark(1); return result; } @@ -167,8 +150,15 @@ bool BindSockets(){ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ - /* Bindinig ZMQ ports on init */ + // Settimg up error reporting + mControl.SetAlert(true); + //mControl.SetPrint(true); + mControl.SetSound(false); + mControl.SetWriteFlag(false); + //mControl.SetPlaySoundFile("news.wav"); + + /* Bindinig ZMQ ports on init */ // Skip reloading of the EA script when the reason to reload is a chart timeframe change if (deInitReason != REASON_CHARTCHANGE){ @@ -232,9 +222,6 @@ void OnDeinit(const int reason){ ResetSubscriptionsAndIndicators(); EventKillTimer(); - for(int i=0;i= 0) { - if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) {/* Error handling */} + if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) { + if(mControl.mGetLastError()) + { + CJAVal message; + string desc = mControl.mGetDesc(); + int lastError = mControl.mGetLastError(); + mControl.Check(); + + message["error"]=(bool) true; + message["lastError"]=(string) lastError; + message["description"]=desc; + message["function"]=(string) __FUNCTION__; + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(indicatorDataSocket,t); + } + } results[i] = DoubleToString(values[0]); } - else { - // TODO error handling - } } - CJAVal message; + message["error"]=(bool) false; message["id"] = (string) id; message["data"].Set(results); @@ -621,11 +636,6 @@ void GetIndicatorResult(CJAVal &dataObject) { if(debug) Print(t); InformClientSocket(indicatorDataSocket,t); - // TODO more error handling - //if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ - // ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); - //} - //else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } //+------------------------------------------------------------------+ @@ -655,9 +665,6 @@ void OpenChart(CJAVal &dataObject){ chartWindowCount++; ArrayResize(chartWindows,chartWindowCount); - //ArrayResize(chartWindowIds,chartWindowCount); - //ArrayResize(chartWindowPeriods,chartWindowCount); - // ArrayResize(chartWindowObjects,chartWindowCount); int idx = chartWindowCount-1; @@ -665,7 +672,6 @@ void OpenChart(CJAVal &dataObject){ ENUM_TIMEFRAMES period = GetTimeframe(chartTF); chartWindows[idx].id = ChartOpen(symbol, period); - //chartWindowPeriods[idx] = period; CJAVal message; message["chartId"] = (string) chartId; @@ -694,17 +700,18 @@ void AddIndicatorChartToChart(CJAVal &dataObject){ double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortname,colorstyle,linetype,linestyle,linewidth); - ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle); - - chartWindows[idx].indicatorId = chartIndicatorId; - chartWindows[idx].indicatorHandle = chartIndicatorHandle; - - CJAVal message; - message["chartId"] = (string) chartIdStr; - - string t=message.Serialize(); - if(debug) Print(t); - InformClientSocket(dataSocket,t); + if(ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle)){ + chartWindows[idx].indicatorId = chartIndicatorId; + chartWindows[idx].indicatorHandle = chartIndicatorHandle; + } + if(!CheckError(__FUNCTION__)) { + CJAVal message; + message["chartId"] = (string) chartIdStr; + + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(dataSocket,t); + } } //+------------------------------------------------------------------+ @@ -943,19 +950,22 @@ void HistoryInfo(CJAVal &dataObject){ ulong toDateM = StringToTime(toDate); tickCount=CopyTicksRange(symbol,tickArray,COPY_TICKS_ALL,1000*(ulong)fromDateM,1000*(ulong)toDateM); - if(tickCount){ - ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); + if(tickCount < 0) { + mControl.mSetUserError(65541, GetErrorID(65541)); } - else ActionDoneOrError(65541 , __FUNCTION__); + CheckError(__FUNCTION__); Print("Preparing data of ", tickCount, " ticks for ", symbol); int file_handle=FileOpen(outputFile, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE){ - msg["status"] = (string) "CONNECTED"; - msg["type"] = (string) "NORMAL"; - msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFile); - if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); - ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); + msg["status"] = (string) "CONNECTED"; + msg["type"] = (string) "NORMAL"; + msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFile); + if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); + ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, "ERR_SUCCESS"); + //ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, dataSocket); + // Inform client that file is avalable for writing + PrintFormat("%s file is available for writing",fileName); PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file @@ -975,9 +985,12 @@ void HistoryInfo(CJAVal &dataObject){ if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); } - else{ - PrintFormat("Failed to open %s file, Error code = %d",fileName,GetLastError()); - ActionDoneOrError(65542 , __FUNCTION__); + else{ + // File is not available for writing + mControl.mSetUserError(65542, GetErrorID(65542)); + CheckError(__FUNCTION__); + //PrintFormat("Failed to open %s file, Error code = %d",fileName,GetLastError()); + //ActionDoneOrError(65542 , __FUNCTION__); } connectedFlag=false; } @@ -1005,16 +1018,16 @@ void HistoryInfo(CJAVal &dataObject){ if(dataObject["toDate"].ToInt()!=NULL)Print("4) Date to:"+TimeToString(toDate)); barCount=CopyRates(symbol,period,fromDate,toDate,r); - if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*error processing */ } - - if(barCount){ - ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); + if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { + mControl.mSetUserError(65541, GetErrorID(65541)); + //CheckError(__FUNCTION__); } - else ActionDoneOrError(65541, __FUNCTION__); Print("Preparing tick data of ", barCount, " ticks for ", symbol); int file_handle=FileOpen(outputFile, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE){ + ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, "ERR_SUCCESS"); + //ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, dataSocket); PrintFormat("%s file is available for writing",outputFile); PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file @@ -1025,8 +1038,10 @@ void HistoryInfo(CJAVal &dataObject){ PrintFormat("Data is written, %s file is closed", outputFile); } else{ - PrintFormat("Failed to open %s file, Error code = %d",outputFile,GetLastError()); - ActionDoneOrError(65542 , __FUNCTION__); + //PrintFormat("Failed to open %s file, Error code = %d",outputFile,GetLastError()); + //ActionDoneOrError(65542 , __FUNCTION__); + mControl.mSetUserError(65542, GetErrorID(65542)); + CheckError(__FUNCTION__); } } @@ -1091,7 +1106,7 @@ void HistoryInfo(CJAVal &dataObject){ } barCount=CopyRates(symbol, period, fromDate, toDate, r); - if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*error processing */ } + if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*mControl.Check();*/ } if(barCount){ for(int i=0;iHW0H~yTcje6qyUCNa2Zmx#KoMEX+`#f<>iu` zUw`-T)N-o(-XUc;!C+?Y?XIr+&Z)Ckb@%`L_lMJm)4S9A{{TPlPX9dplcD)=`s=j! z6aQSBo=?xF@WN~HjaSr(60J$x-@Jv1dJsIH z9@$^C;Dvog0yz52zDrZk4A&y$c7Lh>u_4$5geH|Bk2waloeQSASEneD;Vwss&u4c}V7tZmCQ6Ag(nqs)a zsE6~IPwhW*uDNr)_};$LBOGI9vhe~{iA5TVRK$5{smnP3MgQK+HQI)xaaL5`FbZ6L z;cnfhdeg{GCoPVwPw!3rT;CjpA1{oeC)O7;BE5D=EqTye#?{h5Y)vjt@&SsnU^=V;BdnY-EOa2-uX{iF8A*|)1C3

5SAlg+;^8U@?&#EQw?c=_%zaYWmjG4CKg}zg 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 cGZ(ONbsyQ4vNl3jey*)ljaYProkEKL7v@~ literal 0 HcmV?d00001 From 37cb0fe738d0b951532a9783f753c3043ede45b8 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Tue, 3 Mar 2020 12:52:46 +0100 Subject: [PATCH 09/17] cleanup --- Experts/JsonAPI.mq5 | 173 +++++--- Include/EnumStringToInt.mqh | 692 ++++++++++++++++---------------- Indicators/JsonAPIIndicator.mq5 | 28 +- 3 files changed, 494 insertions(+), 399 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 2f0b465..550dadb 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -44,9 +44,9 @@ int SYS_PORT=15555; int DATA_PORT=15556; int LIVE_PORT=15557; int STR_PORT=15558; -int IND_DATA_PORT=15559; -int CHART_LIVE_PORT=15560; -int PUB_CHART_LIVE_PORT=15562; +int INDICATOR_DATA_PORT=15559; +int CHART_DATA_PORT=15560; +int CHART_INDICATOR_PORT=15562; // ZeroMQ Cnnections Context context("MQL5 JSON API"); @@ -55,15 +55,15 @@ Socket dataSocket(context,ZMQ_PUSH); Socket liveSocket(context,ZMQ_PUSH); Socket streamSocket(context,ZMQ_PUSH); Socket indicatorDataSocket(context,ZMQ_PUSH); -Socket chartLiveSocket(context,ZMQ_PULL); -Socket pubChartLiveSocket(context,ZMQ_PUB); +Socket chartDataSocket(context,ZMQ_PULL); +Socket chartIndicatorSocket(context,ZMQ_PUB); -// Global variables +// Global variables \\ bool debug = false; bool liveStream = true; -bool connectedFlag= true; +bool connectedFlag = true; int deInitReason = -1; -double chartAttached = ChartID(); +double chartAttached = ChartID(); // Chart id where the expert is attached to // Variables for handling price data stream struct SymbolSubscription { @@ -76,29 +76,30 @@ int symbolSubscriptionCount = 0; // Variables for controlling indicators struct Indicator { - long id; - string indicatorId; - int indicatorHandle; - int indicatorParamCount; - int indicatorBufferCount; + long id; // Internal id + string indicatorId; // UUID + int indicatorHandle; // Internal id/handle + int indicatorParamCount; // Number of parameters to be passed to the indicator + int indicatorBufferCount; // Numnber of buffers to be returned bythe indicator }; Indicator indicators[]; int indicatorCount = 0; // Variables for controlling chart struct ChartWindow { - long id; - string chartId; - string indicatorId; - int indicatorHandle; + long id; // Internal id + string chartId; // UUID + string indicatorId; // UUID + int indicatorHandle; // Internal id/handle }; ChartWindow chartWindows[]; int chartWindowCount = 0; -// Refresh chart windows interval. OnTimer function of indicatore JsonAPIIndicator gets triggered on each interval -int chartWindowTimerInterval = 100; -int chartWindowTimerCounter = 0; +// Refresh chart window interval for JsonAPIIndicator +int chartWindowTimerInterval = 100; // Cycles of the globally set EventSetMillisecondTimer interval +int chartWindowTimerCounter = 0; // Keeps track of the current cycle +// Error handling ControlErrors mControl; //string chartWindowObjects[]; @@ -119,20 +120,20 @@ bool BindSockets(){ if (result == false) { return result; } else {Print("Bound 'Live' socket on port ", LIVE_PORT);} result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); if (result == false) { return result; } else {Print("Bound 'Streaming' socket on port ", STR_PORT);} - result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_PORT)); - if (result == false) { return result; } else {Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT);} - result = chartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); - if (result == false) { return result; } else {Print("Bound 'Chart Live' socket on port ", CHART_LIVE_PORT);} - result = pubChartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); - if (result == false) { return result; } else {Print("Bound 'PUB Chart Live' socket on port ", PUB_CHART_LIVE_PORT);} + result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,INDICATOR_DATA_PORT)); + if (result == false) { return result; } else {Print("Bound 'Indicator Data' socket on port ", INDICATOR_DATA_PORT);} + result = chartDataSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_DATA_PORT)); + if (result == false) { return result; } else {Print("Bound 'Chart Data' socket on port ", CHART_DATA_PORT);} + result = chartIndicatorSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_INDICATOR_PORT)); + if (result == false) { return result; } else {Print("Bound 'JsonAPIIndicator Data' socket on port ", CHART_INDICATOR_PORT);} sysSocket.setLinger(1000); dataSocket.setLinger(1000); liveSocket.setLinger(1000); streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); - chartLiveSocket.setLinger(1000); - pubChartLiveSocket.setLinger(1000); + chartDataSocket.setLinger(1000); + chartIndicatorSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); @@ -140,8 +141,8 @@ bool BindSockets(){ liveSocket.setSendHighWaterMark(1); streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); - chartLiveSocket.setReceiveHighWaterMark(1); // TODO confirm settings - pubChartLiveSocket.setReceiveHighWaterMark(1); + chartDataSocket.setReceiveHighWaterMark(1); // TODO confirm settings + chartIndicatorSocket.setReceiveHighWaterMark(1); return result; } @@ -201,18 +202,18 @@ void OnDeinit(const int reason){ if (reason != REASON_CHARTCHANGE){ Print(__FUNCTION__," Deinitialization reason: ", getUninitReasonText(reason)); - Print("Unbinding 'System' socket on port ", SYS_PORT,".."); - sysSocket.unbind(StringFormat("tcp://%s:%d", HOST,SYS_PORT)); - Print("Unbinding 'Data' socket on port ", DATA_PORT,".."); - dataSocket.unbind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); + Print("Unbinding 'System' socket on port ", SYS_PORT, ".."); + sysSocket.unbind(StringFormat("tcp://%s:%d", HOST, SYS_PORT)); + Print("Unbinding 'Data' socket on port ", DATA_PORT, ".."); + dataSocket.unbind(StringFormat("tcp://%s:%d", HOST, DATA_PORT)); Print("Unbinding 'Live' socket on port ", LIVE_PORT, ".."); - liveSocket.unbind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); + liveSocket.unbind(StringFormat("tcp://%s:%d", HOST, LIVE_PORT)); Print("Unbinding 'Streaming' socket on port ", STR_PORT, ".."); - streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); - Print("Unbinding 'CHart' socket on port ", STR_PORT, ".."); - streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); - Print("Unbinding 'pub chart' socket on port ", STR_PORT, ".."); - streamSocket.unbind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, STR_PORT)); + Print("Unbinding 'Chart Data' socket on port ", STR_PORT, ".."); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, CHART_DATA_PORT)); + Print("Unbinding 'JsonAPIIndicator Data' socket on port ", STR_PORT, ".."); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, CHART_INDICATOR_PORT)); // Shutdown ZeroMQ Context context.shutdown(); @@ -374,11 +375,11 @@ void OnTimer(){ // Publish indicator values for the JsonAPIIndicator indicator ZmqMsg chartMsg; - chartLiveSocket.recv(chartMsg, true); + chartDataSocket.recv(chartMsg, true); if(chartMsg.size()>0){ Print(chartMsg.getData()); - pubChartLiveSocket.send(chartMsg,true); - ResetLastError(); + chartIndicatorSocket.send(chartMsg,true); + //ResetLastError(); } // Trigger the indicator JsonAPIIndicator to check for new Messages @@ -674,6 +675,7 @@ void OpenChart(CJAVal &dataObject){ chartWindows[idx].id = ChartOpen(symbol, period); CJAVal message; + message["error"]=(bool) false; message["chartId"] = (string) chartId; string t=message.Serialize(); @@ -682,7 +684,7 @@ void OpenChart(CJAVal &dataObject){ } //+------------------------------------------------------------------+ -//| Add JsonAPIIndicator indicator chart to chart | +//| Add JsonAPIIndicator indicator to chart | //+------------------------------------------------------------------+ void AddIndicatorChartToChart(CJAVal &dataObject){ @@ -706,6 +708,7 @@ void AddIndicatorChartToChart(CJAVal &dataObject){ } if(!CheckError(__FUNCTION__)) { CJAVal message; + message["error"]=(bool) false; message["chartId"] = (string) chartIdStr; string t=message.Serialize(); @@ -717,7 +720,7 @@ void AddIndicatorChartToChart(CJAVal &dataObject){ //+------------------------------------------------------------------+ //| Draw on chart | //+------------------------------------------------------------------+ -void ChartDraw(CJAVal &dataObject){ +void ChartDrawY(CJAVal &dataObject){ /* Print("drawchart"); @@ -866,6 +869,86 @@ void ChartDrawX(CJAVal &dataObject){ //ChartRedraw(chart_id); } +//+------------------------------------------------------------------+ +//| Draw on chart | +//+------------------------------------------------------------------+ +void ChartDraw(CJAVal &dataObject){ + + // DONT DRAW, if chartid noes not exist any more + + string id=dataObject["id"].ToStr(); + + string graphicsType=dataObject["data"]["graphicstype"].ToStr(); + + if (graphicsType=="line") { + string objectId=dataObject["data"]["objectId"].ToStr(); + datetime fromDate=dataObject["data"]["fromDate"].ToInt(); + datetime toDate=dataObject["data"]["toDate"].ToInt(); + double fromPrice=dataObject["data"]["fromPrice"].ToDbl(); + double toPrice=dataObject["data"]["toPrice"].ToDbl(); + + + /* + double params[]; + for(int i=0;i=0)return r; - ENUM_ACCOUNT_INFO_INTEGER a2; - r = StringToEnum(indicatorConstantString,a2); - if(r>=0)return r; - ENUM_ACCOUNT_INFO_STRING a3; - r = StringToEnum(indicatorConstantString,a3); - if(r>=0)return r; - ENUM_ACCOUNT_MARGIN_MODE a4; - r = StringToEnum(indicatorConstantString,a4); - if(r>=0)return r; - ENUM_ACCOUNT_STOPOUT_MODE a5; - r = StringToEnum(indicatorConstantString,a5); - if(r>=0)return r; - ENUM_ACCOUNT_TRADE_MODE a6; - r = StringToEnum(indicatorConstantString,a6); - if(r>=0)return r; - ENUM_ALIGN_MODE a7; - r = StringToEnum(indicatorConstantString,a7); - if(r>=0)return r; - ENUM_ANCHOR_POINT a8; - r = StringToEnum(indicatorConstantString,a8); - if(r>=0)return r; - ENUM_APPLIED_PRICE a9; - r = StringToEnum(indicatorConstantString,a9); - if(r>=0)return r; - ENUM_APPLIED_PRICE a10; - r = StringToEnum(indicatorConstantString,a10); - if(r>=0)return r; - ENUM_APPLIED_VOLUME a11; - r = StringToEnum(indicatorConstantString,a11); - if(r>=0)return r; - ENUM_ARROW_ANCHOR a12; - r = StringToEnum(indicatorConstantString,a12); - if(r>=0)return r; - ENUM_BASE_CORNER a13; - r = StringToEnum(indicatorConstantString,a13); - if(r>=0)return r; - ENUM_BOOK_TYPE a14; - r = StringToEnum(indicatorConstantString,a14); - if(r>=0)return r; - ENUM_BORDER_TYPE a15; - r = StringToEnum(indicatorConstantString,a15); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_FREQUENCY a16; - r = StringToEnum(indicatorConstantString,a16); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_IMPACT a17; - r = StringToEnum(indicatorConstantString,a17); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_IMPORTANCE a18; - r = StringToEnum(indicatorConstantString,a18); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_MULTIPLIER a19; - r = StringToEnum(indicatorConstantString,a19); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_SECTOR a20; - r = StringToEnum(indicatorConstantString,a20); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_TIMEMODE a21; - r = StringToEnum(indicatorConstantString,a21); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_TYPE a22; - r = StringToEnum(indicatorConstantString,a22); - if(r>=0)return r; - ENUM_CALENDAR_EVENT_UNIT a23; - r = StringToEnum(indicatorConstantString,a23); - if(r>=0)return r; - ENUM_CHART_EVENT a24; - r = StringToEnum(indicatorConstantString,a24); - if(r>=0)return r; - ENUM_CHART_MODE a25; - r = StringToEnum(indicatorConstantString,a25); - if(r>=0)return r; - ENUM_CHART_POSITION a26; - r = StringToEnum(indicatorConstantString,a26); - if(r>=0)return r; - ENUM_CHART_PROPERTY_DOUBLE a27; - r = StringToEnum(indicatorConstantString,a27); - if(r>=0)return r; - ENUM_CHART_PROPERTY_INTEGER a28; - r = StringToEnum(indicatorConstantString,a28); - if(r>=0)return r; - ENUM_CHART_PROPERTY_STRING a29; - r = StringToEnum(indicatorConstantString,a29); - if(r>=0)return r; - ENUM_CHART_VOLUME_MODE a30; - r = StringToEnum(indicatorConstantString,a30); - if(r>=0)return r; - ENUM_CL_DEVICE_TYPE a31; - r = StringToEnum(indicatorConstantString,a31); - if(r>=0)return r; - ENUM_COLOR_FORMAT a32; - r = StringToEnum(indicatorConstantString,a32); - if(r>=0)return r; - ENUM_CRYPT_METHOD a33; - r = StringToEnum(indicatorConstantString,a33); - if(r>=0)return r; - ENUM_CUSTOMIND_PROPERTY_DOUBLE a34; - r = StringToEnum(indicatorConstantString,a34); - if(r>=0)return r; - ENUM_CHART_PROPERTY_INTEGER a35; - r = StringToEnum(indicatorConstantString,a35); - if(r>=0)return r; - ENUM_CUSTOMIND_PROPERTY_STRING a36; - r = StringToEnum(indicatorConstantString,a36); - if(r>=0)return r; - ENUM_DATABASE_EXPORT_FLAGS a37; - r = StringToEnum(indicatorConstantString,a37); - if(r>=0)return r; - ENUM_DATABASE_FIELD_TYPE a38; - r = StringToEnum(indicatorConstantString,a38); - if(r>=0)return r; - ENUM_DATABASE_OPEN_FLAGS a39; - r = StringToEnum(indicatorConstantString,a39); - if(r>=0)return r; - ENUM_DATABASE_PRINT_FLAGS a40; - r = StringToEnum(indicatorConstantString,a40); - if(r>=0)return r; - ENUM_DATATYPE a41; - r = StringToEnum(indicatorConstantString,a41); - if(r>=0)return r; - ENUM_DAY_OF_WEEK a42; - r = StringToEnum(indicatorConstantString,a42); - if(r>=0)return r; - ENUM_DEAL_ENTRY a43; - r = StringToEnum(indicatorConstantString,a43); - if(r>=0)return r; - ENUM_DEAL_PROPERTY_DOUBLE a44; - r = StringToEnum(indicatorConstantString,a44); - if(r>=0)return r; - ENUM_DEAL_PROPERTY_INTEGER a45; - r = StringToEnum(indicatorConstantString,a45); - if(r>=0)return r; - ENUM_DEAL_PROPERTY_STRING a46; - r = StringToEnum(indicatorConstantString,a46); - if(r>=0)return r; - ENUM_DEAL_REASON a47; - r = StringToEnum(indicatorConstantString,a47); - if(r>=0)return r; - ENUM_DEAL_TYPE a48; - r = StringToEnum(indicatorConstantString,a48); - if(r>=0)return r; - ENUM_DRAW_TYPE a49; - r = StringToEnum(indicatorConstantString,a49); - if(r>=0)return r; - ENUM_DX_BUFFER_TYPE a50; - r = StringToEnum(indicatorConstantString,a50); - if(r>=0)return r; - ENUM_DX_FORMAT a51; - r = StringToEnum(indicatorConstantString,a51); - if(r>=0)return r; - ENUM_DX_HANDLE_TYPE a52; - r = StringToEnum(indicatorConstantString,a52); - if(r>=0)return r; - ENUM_DX_PRIMITIVE_TOPOLOGY a53; - r = StringToEnum(indicatorConstantString,a53); - if(r>=0)return r; - ENUM_DX_SHADER_TYPE a54; - r = StringToEnum(indicatorConstantString,a54); - if(r>=0)return r; - ENUM_ELLIOT_WAVE_DEGREE a55; - r = StringToEnum(indicatorConstantString,a55); - if(r>=0)return r; - ENUM_FILESELECT_FLAGS a56; - r = StringToEnum(indicatorConstantString,a56); - if(r>=0)return r; - ENUM_FILE_POSITION a57; - r = StringToEnum(indicatorConstantString,a57); - if(r>=0)return r; - ENUM_FILE_PROPERTY_INTEGER a58; - r = StringToEnum(indicatorConstantString,a58); - if(r>=0)return r; - ENUM_GANN_DIRECTION a59; - r = StringToEnum(indicatorConstantString,a59); - if(r>=0)return r; - ENUM_INDEXBUFFER_TYPE a60; - r = StringToEnum(indicatorConstantString,a60); - if(r>=0)return r; - ENUM_INDICATOR a61; - r = StringToEnum(indicatorConstantString,a61); - if(r>=0)return r; - ENUM_INIT_RETCODE a62; - r = StringToEnum(indicatorConstantString,a62); - if(r>=0)return r; - ENUM_LICENSE_TYPE a63; - r = StringToEnum(indicatorConstantString,a63); - if(r>=0)return r; - ENUM_LINE_STYLE a64; - r = StringToEnum(indicatorConstantString,a64); - if(r>=0)return r; - ENUM_MA_METHOD a65; - r = StringToEnum(indicatorConstantString,a65); - if(r>=0)return r; - ENUM_MQL_INFO_INTEGER a66; - r = StringToEnum(indicatorConstantString,a66); - if(r>=0)return r; - ENUM_MQL_INFO_STRING a67; - r = StringToEnum(indicatorConstantString,a67); - if(r>=0)return r; - ENUM_OBJECT a68; - r = StringToEnum(indicatorConstantString,a68); - if(r>=0)return r; - ENUM_OBJECT_PROPERTY_DOUBLE a69; - r = StringToEnum(indicatorConstantString,a69); - if(r>=0)return r; - ENUM_OBJECT_PROPERTY_INTEGER a70; - r = StringToEnum(indicatorConstantString,a70); - if(r>=0)return r; - ENUM_OBJECT_PROPERTY_STRING a71; - r = StringToEnum(indicatorConstantString,a71); - if(r>=0)return r; - ENUM_OPENCL_HANDLE_TYPE a72; - r = StringToEnum(indicatorConstantString,a72); - if(r>=0)return r; - ENUM_OPENCL_PROPERTY_INTEGER a73; - r = StringToEnum(indicatorConstantString,a73); - if(r>=0)return r; - ENUM_PLOT_PROPERTY_STRING a74; - r = StringToEnum(indicatorConstantString,a74); - if(r>=0)return r; - ENUM_POINTER_TYPE a75; - r = StringToEnum(indicatorConstantString,a75); - if(r>=0)return r; - ENUM_POSITION_PROPERTY_DOUBLE a76; - r = StringToEnum(indicatorConstantString,a76); - if(r>=0)return r; - ENUM_ORDER_PROPERTY_INTEGER a77; - r = StringToEnum(indicatorConstantString,a77); - if(r>=0)return r; - ENUM_PLOT_PROPERTY_STRING a78; - r = StringToEnum(indicatorConstantString,a78); - if(r>=0)return r; - ENUM_PROGRAM_TYPE a79; - r = StringToEnum(indicatorConstantString,a79); - if(r>=0)return r; - ENUM_SERIESMODE a80; - r = StringToEnum(indicatorConstantString,a80); - if(r>=0)return r; - ENUM_SERIES_INFO_INTEGER a81; - r = StringToEnum(indicatorConstantString,a81); - if(r>=0)return r; - ENUM_SIGNAL_BASE_DOUBLE a82; - r = StringToEnum(indicatorConstantString,a82); - if(r>=0)return r; - ENUM_SIGNAL_BASE_INTEGER a83; - r = StringToEnum(indicatorConstantString,a83); - if(r>=0)return r; - ENUM_SIGNAL_BASE_STRING a84; - r = StringToEnum(indicatorConstantString,a84); - if(r>=0)return r; - ENUM_SIGNAL_INFO_DOUBLE a85; - r = StringToEnum(indicatorConstantString,a85); - if(r>=0)return r; - ENUM_SIGNAL_INFO_INTEGER a86; - r = StringToEnum(indicatorConstantString,a86); - if(r>=0)return r; - ENUM_SIGNAL_INFO_STRING a87; - r = StringToEnum(indicatorConstantString,a87); - if(r>=0)return r; - ENUM_STATISTICS a88; - r = StringToEnum(indicatorConstantString,a88); - if(r>=0)return r; - ENUM_STO_PRICE a89; - r = StringToEnum(indicatorConstantString,a89); - if(r>=0)return r; - ENUM_SYMBOL_CALC_MODE a90; - r = StringToEnum(indicatorConstantString,a90); - if(r>=0)return r; - ENUM_SYMBOL_CHART_MODE a91; - r = StringToEnum(indicatorConstantString,a91); - if(r>=0)return r; - ENUM_SYMBOL_INFO_DOUBLE a92; - r = StringToEnum(indicatorConstantString,a92); - if(r>=0)return r; - ENUM_SYMBOL_INFO_INTEGER a93; - r = StringToEnum(indicatorConstantString,a93); - if(r>=0)return r; - ENUM_SYMBOL_INFO_STRING a94; - r = StringToEnum(indicatorConstantString,a94); - if(r>=0)return r; - ENUM_STATISTICS a95; - r = StringToEnum(indicatorConstantString,a95); - if(r>=0)return r; - ENUM_STO_PRICE a96; - r = StringToEnum(indicatorConstantString,a96); - if(r>=0)return r; - ENUM_SYMBOL_CALC_MODE a97; - r = StringToEnum(indicatorConstantString,a97); - if(r>=0)return r; - ENUM_SYMBOL_CHART_MODE a98; - r = StringToEnum(indicatorConstantString,a98); - if(r>=0)return r; - ENUM_SYMBOL_INFO_DOUBLE a99; - r = StringToEnum(indicatorConstantString,a99); - if(r>=0)return r; - ENUM_SYMBOL_INFO_INTEGER a100; - r = StringToEnum(indicatorConstantString,a100); - if(r>=0)return r; - ENUM_SYMBOL_INFO_STRING a101; - r = StringToEnum(indicatorConstantString,a101); - if(r>=0)return r; - ENUM_SYMBOL_OPTION_MODE a102; - r = StringToEnum(indicatorConstantString,a102); - if(r>=0)return r; - ENUM_SYMBOL_OPTION_RIGHT a103; - r = StringToEnum(indicatorConstantString,a103); - if(r>=0)return r; - ENUM_SYMBOL_ORDER_GTC_MODE a104; - r = StringToEnum(indicatorConstantString,a104); - if(r>=0)return r; - ENUM_SYMBOL_SWAP_MODE a105; - r = StringToEnum(indicatorConstantString,a105); - if(r>=0)return r; - ENUM_SYMBOL_TRADE_EXECUTION a106; - r = StringToEnum(indicatorConstantString,a106); - if(r>=0)return r; - ENUM_SYMBOL_TRADE_MODE a107; - r = StringToEnum(indicatorConstantString,a107); - if(r>=0)return r; - ENUM_TERMINAL_INFO_DOUBLE a108; - r = StringToEnum(indicatorConstantString,a108); - if(r>=0)return r; - ENUM_TERMINAL_INFO_INTEGER a109; - r = StringToEnum(indicatorConstantString,a109); - if(r>=0)return r; - ENUM_TERMINAL_INFO_STRING a110; - r = StringToEnum(indicatorConstantString,a110); - if(r>=0)return r; - ENUM_TIMEFRAMES a111; - r = StringToEnum(indicatorConstantString,a111); - if(r>=0)return r; - ENUM_TRADE_REQUEST_ACTIONS a112; - r = StringToEnum(indicatorConstantString,a112); - if(r>=0)return r; - ENUM_TRADE_TRANSACTION_TYPE a113; - r = StringToEnum(indicatorConstantString,a113); - if(r>=0)return r; +int StringToEnumInt(string indicatorConstantString) + { + int r = -1; - return(-1); -} + ENUM_ACCOUNT_INFO_DOUBLE a1; + r = StringToEnum(indicatorConstantString,a1); + //if(debug) Print("ENUM type: ENUM_ACCOUNT_INFO_DOUBLE"); + if(r>=0)return r; + ENUM_ACCOUNT_INFO_INTEGER a2; + r = StringToEnum(indicatorConstantString,a2); + if(r>=0)return r; + ENUM_ACCOUNT_INFO_STRING a3; + r = StringToEnum(indicatorConstantString,a3); + if(r>=0)return r; + ENUM_ACCOUNT_MARGIN_MODE a4; + r = StringToEnum(indicatorConstantString,a4); + if(r>=0)return r; + ENUM_ACCOUNT_STOPOUT_MODE a5; + r = StringToEnum(indicatorConstantString,a5); + if(r>=0)return r; + ENUM_ACCOUNT_TRADE_MODE a6; + r = StringToEnum(indicatorConstantString,a6); + if(r>=0)return r; + ENUM_ALIGN_MODE a7; + r = StringToEnum(indicatorConstantString,a7); + if(r>=0)return r; + ENUM_ANCHOR_POINT a8; + r = StringToEnum(indicatorConstantString,a8); + if(r>=0)return r; + ENUM_APPLIED_PRICE a9; + r = StringToEnum(indicatorConstantString,a9); + if(r>=0)return r; + ENUM_APPLIED_PRICE a10; + r = StringToEnum(indicatorConstantString,a10); + if(r>=0)return r; + ENUM_APPLIED_VOLUME a11; + r = StringToEnum(indicatorConstantString,a11); + if(r>=0)return r; + ENUM_ARROW_ANCHOR a12; + r = StringToEnum(indicatorConstantString,a12); + if(r>=0)return r; + ENUM_BASE_CORNER a13; + r = StringToEnum(indicatorConstantString,a13); + if(r>=0)return r; + ENUM_BOOK_TYPE a14; + r = StringToEnum(indicatorConstantString,a14); + if(r>=0)return r; + ENUM_BORDER_TYPE a15; + r = StringToEnum(indicatorConstantString,a15); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_FREQUENCY a16; + r = StringToEnum(indicatorConstantString,a16); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_IMPACT a17; + r = StringToEnum(indicatorConstantString,a17); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_IMPORTANCE a18; + r = StringToEnum(indicatorConstantString,a18); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_MULTIPLIER a19; + r = StringToEnum(indicatorConstantString,a19); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_SECTOR a20; + r = StringToEnum(indicatorConstantString,a20); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_TIMEMODE a21; + r = StringToEnum(indicatorConstantString,a21); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_TYPE a22; + r = StringToEnum(indicatorConstantString,a22); + if(r>=0)return r; + ENUM_CALENDAR_EVENT_UNIT a23; + r = StringToEnum(indicatorConstantString,a23); + if(r>=0)return r; + ENUM_CHART_EVENT a24; + r = StringToEnum(indicatorConstantString,a24); + if(r>=0)return r; + ENUM_CHART_MODE a25; + r = StringToEnum(indicatorConstantString,a25); + if(r>=0)return r; + ENUM_CHART_POSITION a26; + r = StringToEnum(indicatorConstantString,a26); + if(r>=0)return r; + ENUM_CHART_PROPERTY_DOUBLE a27; + r = StringToEnum(indicatorConstantString,a27); + if(r>=0)return r; + ENUM_CHART_PROPERTY_INTEGER a28; + r = StringToEnum(indicatorConstantString,a28); + if(r>=0)return r; + ENUM_CHART_PROPERTY_STRING a29; + r = StringToEnum(indicatorConstantString,a29); + if(r>=0)return r; + ENUM_CHART_VOLUME_MODE a30; + r = StringToEnum(indicatorConstantString,a30); + if(r>=0)return r; + ENUM_CL_DEVICE_TYPE a31; + r = StringToEnum(indicatorConstantString,a31); + if(r>=0)return r; + ENUM_COLOR_FORMAT a32; + r = StringToEnum(indicatorConstantString,a32); + if(r>=0)return r; + ENUM_CRYPT_METHOD a33; + r = StringToEnum(indicatorConstantString,a33); + if(r>=0)return r; + ENUM_CUSTOMIND_PROPERTY_DOUBLE a34; + r = StringToEnum(indicatorConstantString,a34); + if(r>=0)return r; + ENUM_CHART_PROPERTY_INTEGER a35; + r = StringToEnum(indicatorConstantString,a35); + if(r>=0)return r; + ENUM_CUSTOMIND_PROPERTY_STRING a36; + r = StringToEnum(indicatorConstantString,a36); + if(r>=0)return r; + ENUM_DATABASE_EXPORT_FLAGS a37; + r = StringToEnum(indicatorConstantString,a37); + if(r>=0)return r; + ENUM_DATABASE_FIELD_TYPE a38; + r = StringToEnum(indicatorConstantString,a38); + if(r>=0)return r; + ENUM_DATABASE_OPEN_FLAGS a39; + r = StringToEnum(indicatorConstantString,a39); + if(r>=0)return r; + ENUM_DATABASE_PRINT_FLAGS a40; + r = StringToEnum(indicatorConstantString,a40); + if(r>=0)return r; + ENUM_DATATYPE a41; + r = StringToEnum(indicatorConstantString,a41); + if(r>=0)return r; + ENUM_DAY_OF_WEEK a42; + r = StringToEnum(indicatorConstantString,a42); + if(r>=0)return r; + ENUM_DEAL_ENTRY a43; + r = StringToEnum(indicatorConstantString,a43); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_DOUBLE a44; + r = StringToEnum(indicatorConstantString,a44); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_INTEGER a45; + r = StringToEnum(indicatorConstantString,a45); + if(r>=0)return r; + ENUM_DEAL_PROPERTY_STRING a46; + r = StringToEnum(indicatorConstantString,a46); + if(r>=0)return r; + ENUM_DEAL_REASON a47; + r = StringToEnum(indicatorConstantString,a47); + if(r>=0)return r; + ENUM_DEAL_TYPE a48; + r = StringToEnum(indicatorConstantString,a48); + if(r>=0)return r; + ENUM_DRAW_TYPE a49; + r = StringToEnum(indicatorConstantString,a49); + if(r>=0)return r; + ENUM_DX_BUFFER_TYPE a50; + r = StringToEnum(indicatorConstantString,a50); + if(r>=0)return r; + ENUM_DX_FORMAT a51; + r = StringToEnum(indicatorConstantString,a51); + if(r>=0)return r; + ENUM_DX_HANDLE_TYPE a52; + r = StringToEnum(indicatorConstantString,a52); + if(r>=0)return r; + ENUM_DX_PRIMITIVE_TOPOLOGY a53; + r = StringToEnum(indicatorConstantString,a53); + if(r>=0)return r; + ENUM_DX_SHADER_TYPE a54; + r = StringToEnum(indicatorConstantString,a54); + if(r>=0)return r; + ENUM_ELLIOT_WAVE_DEGREE a55; + r = StringToEnum(indicatorConstantString,a55); + if(r>=0)return r; + ENUM_FILESELECT_FLAGS a56; + r = StringToEnum(indicatorConstantString,a56); + if(r>=0)return r; + ENUM_FILE_POSITION a57; + r = StringToEnum(indicatorConstantString,a57); + if(r>=0)return r; + ENUM_FILE_PROPERTY_INTEGER a58; + r = StringToEnum(indicatorConstantString,a58); + if(r>=0)return r; + ENUM_GANN_DIRECTION a59; + r = StringToEnum(indicatorConstantString,a59); + if(r>=0)return r; + ENUM_INDEXBUFFER_TYPE a60; + r = StringToEnum(indicatorConstantString,a60); + if(r>=0)return r; + ENUM_INDICATOR a61; + r = StringToEnum(indicatorConstantString,a61); + if(r>=0)return r; + ENUM_INIT_RETCODE a62; + r = StringToEnum(indicatorConstantString,a62); + if(r>=0)return r; + ENUM_LICENSE_TYPE a63; + r = StringToEnum(indicatorConstantString,a63); + if(r>=0)return r; + ENUM_LINE_STYLE a64; + r = StringToEnum(indicatorConstantString,a64); + if(r>=0)return r; + ENUM_MA_METHOD a65; + r = StringToEnum(indicatorConstantString,a65); + if(r>=0)return r; + ENUM_MQL_INFO_INTEGER a66; + r = StringToEnum(indicatorConstantString,a66); + if(r>=0)return r; + ENUM_MQL_INFO_STRING a67; + r = StringToEnum(indicatorConstantString,a67); + if(r>=0)return r; + ENUM_OBJECT a68; + r = StringToEnum(indicatorConstantString,a68); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_DOUBLE a69; + r = StringToEnum(indicatorConstantString,a69); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_INTEGER a70; + r = StringToEnum(indicatorConstantString,a70); + if(r>=0)return r; + ENUM_OBJECT_PROPERTY_STRING a71; + r = StringToEnum(indicatorConstantString,a71); + if(r>=0)return r; + ENUM_OPENCL_HANDLE_TYPE a72; + r = StringToEnum(indicatorConstantString,a72); + if(r>=0)return r; + ENUM_OPENCL_PROPERTY_INTEGER a73; + r = StringToEnum(indicatorConstantString,a73); + if(r>=0)return r; + ENUM_PLOT_PROPERTY_STRING a74; + r = StringToEnum(indicatorConstantString,a74); + if(r>=0)return r; + ENUM_POINTER_TYPE a75; + r = StringToEnum(indicatorConstantString,a75); + if(r>=0)return r; + ENUM_POSITION_PROPERTY_DOUBLE a76; + r = StringToEnum(indicatorConstantString,a76); + if(r>=0)return r; + ENUM_ORDER_PROPERTY_INTEGER a77; + r = StringToEnum(indicatorConstantString,a77); + if(r>=0)return r; + ENUM_PLOT_PROPERTY_STRING a78; + r = StringToEnum(indicatorConstantString,a78); + if(r>=0)return r; + ENUM_PROGRAM_TYPE a79; + r = StringToEnum(indicatorConstantString,a79); + if(r>=0)return r; + ENUM_SERIESMODE a80; + r = StringToEnum(indicatorConstantString,a80); + if(r>=0)return r; + ENUM_SERIES_INFO_INTEGER a81; + r = StringToEnum(indicatorConstantString,a81); + if(r>=0)return r; + ENUM_SIGNAL_BASE_DOUBLE a82; + r = StringToEnum(indicatorConstantString,a82); + if(r>=0)return r; + ENUM_SIGNAL_BASE_INTEGER a83; + r = StringToEnum(indicatorConstantString,a83); + if(r>=0)return r; + ENUM_SIGNAL_BASE_STRING a84; + r = StringToEnum(indicatorConstantString,a84); + if(r>=0)return r; + ENUM_SIGNAL_INFO_DOUBLE a85; + r = StringToEnum(indicatorConstantString,a85); + if(r>=0)return r; + ENUM_SIGNAL_INFO_INTEGER a86; + r = StringToEnum(indicatorConstantString,a86); + if(r>=0)return r; + ENUM_SIGNAL_INFO_STRING a87; + r = StringToEnum(indicatorConstantString,a87); + if(r>=0)return r; + ENUM_STATISTICS a88; + r = StringToEnum(indicatorConstantString,a88); + if(r>=0)return r; + ENUM_STO_PRICE a89; + r = StringToEnum(indicatorConstantString,a89); + if(r>=0)return r; + ENUM_SYMBOL_CALC_MODE a90; + r = StringToEnum(indicatorConstantString,a90); + if(r>=0)return r; + ENUM_SYMBOL_CHART_MODE a91; + r = StringToEnum(indicatorConstantString,a91); + if(r>=0)return r; + ENUM_SYMBOL_INFO_DOUBLE a92; + r = StringToEnum(indicatorConstantString,a92); + if(r>=0)return r; + ENUM_SYMBOL_INFO_INTEGER a93; + r = StringToEnum(indicatorConstantString,a93); + if(r>=0)return r; + ENUM_SYMBOL_INFO_STRING a94; + r = StringToEnum(indicatorConstantString,a94); + if(r>=0)return r; + ENUM_STATISTICS a95; + r = StringToEnum(indicatorConstantString,a95); + if(r>=0)return r; + ENUM_STO_PRICE a96; + r = StringToEnum(indicatorConstantString,a96); + if(r>=0)return r; + ENUM_SYMBOL_CALC_MODE a97; + r = StringToEnum(indicatorConstantString,a97); + if(r>=0)return r; + ENUM_SYMBOL_CHART_MODE a98; + r = StringToEnum(indicatorConstantString,a98); + if(r>=0)return r; + ENUM_SYMBOL_INFO_DOUBLE a99; + r = StringToEnum(indicatorConstantString,a99); + if(r>=0)return r; + ENUM_SYMBOL_INFO_INTEGER a100; + r = StringToEnum(indicatorConstantString,a100); + if(r>=0)return r; + ENUM_SYMBOL_INFO_STRING a101; + r = StringToEnum(indicatorConstantString,a101); + if(r>=0)return r; + ENUM_SYMBOL_OPTION_MODE a102; + r = StringToEnum(indicatorConstantString,a102); + if(r>=0)return r; + ENUM_SYMBOL_OPTION_RIGHT a103; + r = StringToEnum(indicatorConstantString,a103); + if(r>=0)return r; + ENUM_SYMBOL_ORDER_GTC_MODE a104; + r = StringToEnum(indicatorConstantString,a104); + if(r>=0)return r; + ENUM_SYMBOL_SWAP_MODE a105; + r = StringToEnum(indicatorConstantString,a105); + if(r>=0)return r; + ENUM_SYMBOL_TRADE_EXECUTION a106; + r = StringToEnum(indicatorConstantString,a106); + if(r>=0)return r; + ENUM_SYMBOL_TRADE_MODE a107; + r = StringToEnum(indicatorConstantString,a107); + if(r>=0)return r; + ENUM_TERMINAL_INFO_DOUBLE a108; + r = StringToEnum(indicatorConstantString,a108); + if(r>=0)return r; + ENUM_TERMINAL_INFO_INTEGER a109; + r = StringToEnum(indicatorConstantString,a109); + if(r>=0)return r; + ENUM_TERMINAL_INFO_STRING a110; + r = StringToEnum(indicatorConstantString,a110); + if(r>=0)return r; + ENUM_TIMEFRAMES a111; + r = StringToEnum(indicatorConstantString,a111); + if(r>=0)return r; + ENUM_TRADE_REQUEST_ACTIONS a112; + r = StringToEnum(indicatorConstantString,a112); + if(r>=0)return r; + ENUM_TRADE_TRANSACTION_TYPE a113; + r = StringToEnum(indicatorConstantString,a113); + if(r>=0)return r; + + return(-1); + } diff --git a/Indicators/JsonAPIIndicator.mq5 b/Indicators/JsonAPIIndicator.mq5 index 12d06b7..a5dd2e3 100644 --- a/Indicators/JsonAPIIndicator.mq5 +++ b/Indicators/JsonAPIIndicator.mq5 @@ -8,7 +8,7 @@ #property link "https://www.guntherschulz.de" #property version "1.00" -#include +#include #include #include @@ -18,7 +18,7 @@ int CHART_SUB_PORT=15562; // ZeroMQ Cnnections Context context("MQL5 JSON API"); -Socket chartLiveSocket(context,ZMQ_SUB); +Socket chartSubscriptionSocket(context,ZMQ_SUB); //#property indicator_separate_window //#property indicator_buffers 1 @@ -52,14 +52,16 @@ bool first = false; //+------------------------------------------------------------------+ int OnInit() { - bool result = chartLiveSocket.connect(StringFormat("tcp://%s:%d", HOST, CHART_SUB_PORT)); + bool result = chartSubscriptionSocket.connect(StringFormat("tcp://%s:%d", HOST, CHART_SUB_PORT)); if (result == false) {Print("Failed to subscrbe on port ", CHART_SUB_PORT);} else { Print("Accepting Chart Indicator data on port ", CHART_SUB_PORT); - chartLiveSocket.setSubscribe(""); - chartLiveSocket.setLinger(1000); + // TODO subscribe only to own IndicatorId topic + // Subscribe to all topics + chartSubscriptionSocket.setSubscribe(""); + chartSubscriptionSocket.setLinger(1000); // Number of messages to buffer in RAM. - chartLiveSocket.setReceiveHighWaterMark(5); // TODO confirm settings + chartSubscriptionSocket.setReceiveHighWaterMark(5); // TODO confirm settings } //--- indicator buffers mapping; @@ -130,6 +132,9 @@ void SubscriptionHandler(ZmqMsg &chartMsg){ if(message["indicatorChartId"]==IndicatorId) WriteToBuffer(message); } +//+------------------------------------------------------------------+ +//| Update indicator buffer function | +//+------------------------------------------------------------------+ void WriteToBuffer(CJAVal &message) { int bufferSize = ArraySize(Buffer); int messageDataSize = message["data"].Size(); @@ -172,13 +177,16 @@ dependable redraw after calling timer */ +//+------------------------------------------------------------------+ +//| Check for new indicator data function | +//+------------------------------------------------------------------+ void CheckMessages(){ - // 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 Timer() works, when the indicator is manually added to a chart, but not with ChartIndicatorAdd() ZmqMsg chartMsg; // Recieve chart instructions stream from client via live Chart socket. - chartLiveSocket.recv(chartMsg,true); + chartSubscriptionSocket.recv(chartMsg,true); // Request recived if(chartMsg.size()>0){ @@ -189,6 +197,10 @@ void CheckMessages(){ } } +//+------------------------------------------------------------------+ +//| OnTimer() workaround function | +//+------------------------------------------------------------------+ +// Gets triggered by the OnTimer() function of the JsonAPI Expert script void OnChartEvent(const int id, const long &lparam, const double &dparam, From 4b2297fbfb873213786d0fb3f50c8fbb06b4a3b8 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Tue, 3 Mar 2020 13:21:24 +0100 Subject: [PATCH 10/17] allow ENUM as string as parameters for indicators minor code optimization --- Experts/JsonAPI.mq5 | 70 +++++++++++++------ ...numStringToInt.mqh => StringToEnumInt.mqh} | 0 clean.sh | 6 +- 3 files changed, 51 insertions(+), 25 deletions(-) rename Include/{EnumStringToInt.mqh => StringToEnumInt.mqh} (100%) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 550dadb..72ea6a9 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include //#include //#include @@ -377,7 +377,7 @@ void OnTimer(){ ZmqMsg chartMsg; chartDataSocket.recv(chartMsg, true); if(chartMsg.size()>0){ - Print(chartMsg.getData()); + if(debug) Print(chartMsg.getData()); chartIndicatorSocket.send(chartMsg,true); //ResetLastError(); } @@ -489,12 +489,27 @@ void IndicatorControl(CJAVal &dataObject){ StartIndicator(dataObject); } } - + +//+------------------------------------------------------------------+ +//| Check if string is a representation of a number | +//+------------------------------------------------------------------+ +bool IsNumberAsString(string str) { + // MQL5 seems to return true if the values are the same, no matter the data type, in this case comparing str and dbl/int. + // (str "2.1" == double 2.1) will return true. + double dbl = StringToDouble(str); + int integer = StringToInteger(str); + // Compaing to both int and double to cover both cases + if(str==dbl || str==integer) return true; + else return false; +} + //+------------------------------------------------------------------+ //| Start new indicator instance | //+------------------------------------------------------------------+ void StartIndicator(CJAVal &dataObject){ + // TODO map Indicators Constants https://www.mql5.com/en/docs/constants/indicatorconstants + string symbol=dataObject["symbol"].ToStr(); string chartTF=dataObject["chartTF"].ToStr(); string id=dataObject["id"].ToStr(); @@ -511,10 +526,16 @@ void StartIndicator(CJAVal &dataObject){ double params[]; indicators[idx].indicatorParamCount = dataObject["params"].Size(); for(int i=0;i ./Include/EnumStringToInt.clean.mqh +tr -cd '[:print:]\n\r' < ./Include/StringToEnumInt.mqh > ./Include/StringToEnumInt.clean.mqh # Add UTF-8 BOM -sed -i '1s/^/\xef\xbb\xbf/' ./Include/EnumStringToInt.clean.mqh -mv ./Include/EnumStringToInt.clean.mqh ./Include/EnumStringToInt.mqh \ No newline at end of file +sed -i '1s/^/\xef\xbb\xbf/' ./Include/StringToEnumInt.clean.mqh +mv ./Include/StringToEnumInt.clean.mqh ./Include/StringToEnumInt.mqh \ No newline at end of file From 99ea77ebfe64552a878887f35537a702baf29a63 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Thu, 30 Apr 2020 10:34:45 +0200 Subject: [PATCH 11/17] cleanup --- Experts/JsonAPI.mq5 | 340 ++------------------------------ Indicators/JsonAPIIndicator.mq5 | 46 ----- clean.sh | 1 - 3 files changed, 14 insertions(+), 373 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 72ea6a9..aca5741 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -102,11 +102,6 @@ int chartWindowTimerCounter = 0; // Keeps track of the current cycle // Error handling ControlErrors mControl; -//string chartWindowObjects[]; - -//double chartCurvePositions[][3]; - - //+------------------------------------------------------------------+ //| Bind ZMQ sockets to ports | //+------------------------------------------------------------------+ @@ -152,12 +147,10 @@ bool BindSockets(){ //+------------------------------------------------------------------+ int OnInit(){ - // Settimg up error reporting + // Setting up error reporting mControl.SetAlert(true); - //mControl.SetPrint(true); mControl.SetSound(false); mControl.SetWriteFlag(false); - //mControl.SetPlaySoundFile("news.wav"); /* Bindinig ZMQ ports on init */ // Skip reloading of the EA script when the reason to reload is a chart timeframe change @@ -226,12 +219,6 @@ void OnDeinit(const int reason){ } } -/* -void OnTick(){ - -} -*/ - //+------------------------------------------------------------------+ //| Check if subscribed to symbol and timeframe combination | //+------------------------------------------------------------------+ @@ -296,9 +283,6 @@ void StreamPriceData(){ MqlRates rates[1]; int spread[1]; - //if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) { - // CheckError(__FUNCTION__, indicatorDataSocket); - //} if( chartTF == "TICK"){ if(SymbolInfoTick(symbol,tick) !=true) { /*mControl.Check();*/ } thisBar=(datetime) tick.time_msc; @@ -351,7 +335,6 @@ void StreamPriceData(){ } } } - // return true; } //+------------------------------------------------------------------+ @@ -378,8 +361,7 @@ void OnTimer(){ chartDataSocket.recv(chartMsg, true); if(chartMsg.size()>0){ if(debug) Print(chartMsg.getData()); - chartIndicatorSocket.send(chartMsg,true); - //ResetLastError(); + chartIndicatorSocket.send(chartMsg,true); } // Trigger the indicator JsonAPIIndicator to check for new Messages @@ -408,19 +390,9 @@ void RequestHandler(ZmqMsg &request){ if(debug) Print("Processing:"+msg); - // Deserialize msg to CJAVal array - /* - if(!message.Deserialize(msg)){ - ActionDoneOrError(65537, __FUNCTION__); - Alert("Deserialization Error"); - ExpertRemove(); - } - */ - if(!incomingMessage.Deserialize(msg)){ mControl.mSetUserError(65537, GetErrorID(65537)); CheckError(__FUNCTION__); - //ExpertRemove(); } // Send response to System socket that request was received @@ -440,8 +412,6 @@ void RequestHandler(ZmqMsg &request){ else if(action=="RESET") ResetSubscriptionsAndIndicators(); else if(action=="INDICATOR") IndicatorControl(incomingMessage); else if(action=="CHART") ChartControl(incomingMessage); - // Action command error processing - //else ActionDoneOrError(65538, __FUNCTION__); else { mControl.mSetUserError(65538, GetErrorID(65538)); CheckError(__FUNCTION__); @@ -463,16 +433,10 @@ void ScriptConfiguration(CJAVal &dataObject){ // to initialze with value 0 skips the first price symbolSubscriptions[symbolSubscriptionCount].lastBar = 0; symbolSubscriptionCount++; - /* - if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ - ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); - } - else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); - */ + mControl.mResetLastError(); SymbolInfoString(symbol, SYMBOL_DESCRIPTION); if(!CheckError(__FUNCTION__)) ActionDoneOrError(ERR_SUCCESS, __FUNCTION__, "ERR_SUCCESS"); - //if(!CheckError(__FUNCTION__, dataSocket)) ActionDoneOrError(ERR_SUCCESS, __FUNCTION__, dataSocket); } //+------------------------------------------------------------------+ @@ -670,9 +634,6 @@ void ChartControl(CJAVal &dataObject){ if(actionType=="ADDINDICATOR") { AddChartIndicator(dataObject); } - else if(actionType=="ADDOBJECT") { - AddObjectToChart(dataObject); - } else if(actionType=="OPEN") { OpenChart(dataObject); } @@ -740,238 +701,6 @@ void AddChartIndicator(CJAVal &dataObject){ } } -//+------------------------------------------------------------------+ -//| Draw on chart | -//+------------------------------------------------------------------+ -void ChartDrawY(CJAVal &dataObject){ -/* - Print("drawchart"); - - string id=dataObject["id"].ToStr(); - //int windowIdx = GetChartWindowIdxByChartWindowId(id); - - int datesSize = dataObject["data"][0].Size(); - int x[]; - int y[]; - double x_dbl[]; - double y_dbl[]; - ArrayResize(chartCurvePositions, datesSize); - ArrayResize(x,datesSize); - ArrayResize(y,datesSize); - ArrayResize(x_dbl,datesSize); - ArrayResize(y_dbl,datesSize); - for(int i=0;iprev_calculated){ Buffer[0] = EMPTY_VALUE; } - -/* -https://www.mql5.com/en/docs/constants/structures/mqlparam - -*/ - //--- return value of prev_calculated for next call return(rates_total); @@ -116,8 +96,6 @@ https://www.mql5.com/en/docs/constants/structures/mqlparam void SubscriptionHandler(ZmqMsg &chartMsg){ CJAVal message; - - //ResetLastError(); // Get data from reguest string msg=chartMsg.getData(); @@ -125,7 +103,6 @@ void SubscriptionHandler(ZmqMsg &chartMsg){ // Deserialize msg to CJAVal array if(!message.Deserialize(msg)){ - //ActionDoneOrError(65537, __FUNCTION__); Alert("Deserialization Error"); ExpertRemove(); } @@ -139,7 +116,6 @@ void WriteToBuffer(CJAVal &message) { int bufferSize = ArraySize(Buffer); int messageDataSize = message["data"].Size(); if(first==false) { - //ArrayFill(Buffer, 0, ArraySize(Buffer), EMPTY_VALUE); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,bufferSize-messageDataSize); first = true; } @@ -153,29 +129,8 @@ void WriteToBuffer(CJAVal &message) { } } Buffer[0] = EMPTY_VALUE; - - - - //ArrayResize(Values, bufferSize); - //for(int i=0;i ./Experts/JsonAPI.clean.mq5 sed -i '1s/^/\xef\xbb\xbf/' ./Experts/JsonAPI.clean.mq5 mv ./Experts/JsonAPI.clean.mq5 ./Experts/JsonAPI.mq5 - # Remove non-printable ASCII characters tr -cd '[:print:]\n\r' < ./Indicators/JsonAPIIndicator.mq5 > ./Indicators/JsonAPIIndicator.clean.mq5 # Add UTF-8 BOM From 2c770994305df1b78262c47d3d93e277acb3acf7 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Thu, 30 Apr 2020 14:42:43 +0200 Subject: [PATCH 12/17] update changelog --- changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog.md b/changelog.md index dbc4b3f..84f9100 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +### 30th April 2020 + +- add support for spreads +- add support for drawing custom indicator data to charts +- add support for indicator data output + ### 16th February 2020 - add support for candle ask/bid price spread From de6b4f054651734ff53a9c5043de597fa49b753c Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Thu, 30 Apr 2020 18:25:01 +0200 Subject: [PATCH 13/17] add support for plot labels for chart indicator lines cleanup --- Experts/JsonAPI.mq5 | 9 ++------- Indicators/JsonAPIIndicator.mq5 | 12 +++++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index aca5741..3ba75f4 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -32,11 +32,6 @@ #include #include #include -//#include -//#include -//#include -// Starts an Expert Advisor with specified parameters -//#include // Set ports and host for ZeroMQ string HOST="*"; @@ -274,7 +269,6 @@ void StreamPriceData(){ string symbol=symbolSubscriptions[i].symbol; string chartTF=symbolSubscriptions[i].chartTf; datetime lastBar=symbolSubscriptions[i].lastBar; - //Print(symbol," ", chartTF," ",lastBar); CJAVal Data; ENUM_TIMEFRAMES period = GetTimeframe(chartTF); @@ -676,6 +670,7 @@ void AddChartIndicator(CJAVal &dataObject){ string chartIndicatorId=dataObject["indicatorChartId"].ToStr(); int chartIndicatorSubWindow=dataObject["chartIndicatorSubWindow"].ToInt(); string shortname = dataObject["style"]["shortname"].ToStr(); + string linelabel = dataObject["style"]["linelabel"].ToStr(); string colorstyle = dataObject["style"]["color"].ToStr(); string linetype = dataObject["style"]["linetype"].ToStr(); string linestyle = dataObject["style"]["linestyle"].ToStr(); @@ -684,7 +679,7 @@ void AddChartIndicator(CJAVal &dataObject){ int idx = GetChartWindowIdxByChartWindowId(chartIdStr); long ChartId = chartWindows[idx].id; - double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortname,colorstyle,linetype,linestyle,linewidth); + double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortname,linelabel,colorstyle,linetype,linestyle,linewidth); if(ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle)){ chartWindows[idx].indicatorId = chartIndicatorId; diff --git a/Indicators/JsonAPIIndicator.mq5 b/Indicators/JsonAPIIndicator.mq5 index 2de3347..184a01f 100644 --- a/Indicators/JsonAPIIndicator.mq5 +++ b/Indicators/JsonAPIIndicator.mq5 @@ -22,7 +22,8 @@ Socket chartSubscriptionSocket(context,ZMQ_SUB); //--- input parameters input string IndicatorId=""; -input string ShortName="JsonAPI"; +input string ShortName="JsonAPI"; +input string LineLabel="Value"; input string ColorSyle = "clrRed"; input string LineType = "DRAW_LINE"; input string LineStyle = "STYLE_SOLID"; @@ -53,18 +54,20 @@ int OnInit() //--- indicator buffers mapping; ArraySetAsSeries(Buffer,true); SetIndexBuffer(0,Buffer,INDICATOR_DATA); + SetIndexBuffer(1,Buffer,INDICATOR_CALCULATIONS); color colorstyle = StringToColor(ColorSyle); int linetype = StringToEnumInt(LineType); int linestyle = StringToEnumInt(LineStyle); - SetStyle(ShortName, colorstyle, linetype, linestyle, LineWidth); + SetStyle(ShortName, LineLabel, colorstyle, linetype, linestyle, LineWidth); //--- return(INIT_SUCCEEDED); } -void SetStyle(string shortname, color colorstyle, int linetype, int linestyle, int linewidth) { +void SetStyle(string shortname, string linelabel, color colorstyle, int linetype, int linestyle, int linewidth) { IndicatorSetString(INDICATOR_SHORTNAME,shortname); + PlotIndexSetString(0,PLOT_LABEL,linelabel); PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,colorstyle); PlotIndexSetInteger(0,PLOT_DRAW_TYPE,linetype); PlotIndexSetInteger(0,PLOT_LINE_STYLE,linestyle); @@ -145,8 +148,7 @@ void CheckMessages(){ // Request recived if(chartMsg.size()>0){ - // Handle subscription SubscriptionHandler(). - Print(chartMsg.getData()); + // Handle subscription SubscriptionHandler() SubscriptionHandler(chartMsg); ChartRedraw(ChartID()); } From b550a423a159b80c93f0da4036bb74ffd84ec1f7 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Thu, 30 Apr 2020 18:58:17 +0200 Subject: [PATCH 14/17] changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 84f9100..7a8d774 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ - add support for spreads - add support for drawing custom indicator data to charts - add support for indicator data output +- new error reporting ### 16th February 2020 From ae6a2367b1a9f7f89a829078ad40b620fe647fd4 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Thu, 30 Apr 2020 19:03:23 +0200 Subject: [PATCH 15/17] fix formating --- Include/controlerrors.mqh | Bin 40742 -> 20370 bytes clean.sh | 8 +++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Include/controlerrors.mqh b/Include/controlerrors.mqh index ad0973e2f1fb40436dac35f0de923d7c93d5b857..e0499409c2cb0e979000791aadbf321aebca2ec0 100644 GIT binary patch literal 20370 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 literal 40742 zcmds=`HmgOb;kSe0C|TA12`14a$n>HW0H~yTcje6qyUCNa2Zmx#KoMEX+`#f<>iu` zUw`-T)N-o(-XUc;!C+?Y?XIr+&Z)Ckb@%`L_lMJm)4S9A{{TPlPX9dplcD)=`s=j! z6aQSBo=?xF@WN~HjaSr(60J$x-@Jv1dJsIH z9@$^C;Dvog0yz52zDrZk4A&y$c7Lh>u_4$5geH|Bk2waloeQSASEneD;Vwss&u4c}V7tZmCQ6Ag(nqs)a zsE6~IPwhW*uDNr)_};$LBOGI9vhe~{iA5TVRK$5{smnP3MgQK+HQI)xaaL5`FbZ6L z;cnfhdeg{GCoPVwPw!3rT;CjpA1{oeC)O7;BE5D=EqTye#?{h5Y)vjt@&SsnU^=V;BdnY-EOa2-uX{iF8A*|)1C3

5SAlg+;^8U@?&#EQw?c=_%zaYWmjG4CKg}zg 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 cGZ(ONbsyQ4vNl3jey*)ljaYProkEKL7v@~ diff --git a/clean.sh b/clean.sh index 62bfdca..a7d60c0 100755 --- a/clean.sh +++ b/clean.sh @@ -19,4 +19,10 @@ mv ./Indicators/JsonAPIIndicator.clean.mq5 ./Indicators/JsonAPIIndicator.mq5 tr -cd '[:print:]\n\r' < ./Include/StringToEnumInt.mqh > ./Include/StringToEnumInt.clean.mqh # Add UTF-8 BOM sed -i '1s/^/\xef\xbb\xbf/' ./Include/StringToEnumInt.clean.mqh -mv ./Include/StringToEnumInt.clean.mqh ./Include/StringToEnumInt.mqh \ No newline at end of file +mv ./Include/StringToEnumInt.clean.mqh ./Include/StringToEnumInt.mqh + +# Remove non-printable ASCII characters +tr -cd '[:print:]\n\r' < ./Include/controlerrors.mqh > ./Include/controlerrors.clean.mqh +# Add UTF-8 BOM +sed -i '1s/^/\xef\xbb\xbf/' ./Include/controlerrors.clean.mqh +mv ./Include/controlerrors.clean.mqh ./Include/controlerrors.mqh \ No newline at end of file From 1b2beab4c4b0fbe78167c4a07cda65d40aff2941 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Fri, 12 Jun 2020 22:48:42 +0200 Subject: [PATCH 16/17] update README and changelog minor update to JsonAPIIndicator --- Experts/JsonAPI.mq5 | 2 +- Indicators/JsonAPIIndicator.mq5 | 13 +- README.md | 282 +++++++++++++++++++++++++++----- changelog.md | 4 +- 4 files changed, 257 insertions(+), 44 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 3ba75f4..5b7af78 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -443,7 +443,7 @@ void IndicatorControl(CJAVal &dataObject){ if(actionType=="REQUEST") { GetIndicatorResult(dataObject); } - else if(actionType=="START") { + else if(actionType=="ATTACH") { StartIndicator(dataObject); } } diff --git a/Indicators/JsonAPIIndicator.mq5 b/Indicators/JsonAPIIndicator.mq5 index 184a01f..1aea4e8 100644 --- a/Indicators/JsonAPIIndicator.mq5 +++ b/Indicators/JsonAPIIndicator.mq5 @@ -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); diff --git a/README.md b/README.md index bdf5c24..c15ccfc 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/changelog.md b/changelog.md index 7a8d774..c4bb433 100644 --- a/changelog.md +++ b/changelog.md @@ -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 From d423745c935c21e9b5562bf2dc45802a229ca105 Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Wed, 29 Jul 2020 13:26:06 +0200 Subject: [PATCH 17/17] some fixes --- Experts/JsonAPI.mq5 | 89 +++++++++----- Indicators/JsonAPIIndicator.mq5 | 199 ++++++++++++++++++++++++++------ 2 files changed, 222 insertions(+), 66 deletions(-) diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 5b7af78..bc1576c 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -41,7 +41,7 @@ int LIVE_PORT=15557; int STR_PORT=15558; int INDICATOR_DATA_PORT=15559; int CHART_DATA_PORT=15560; -int CHART_INDICATOR_PORT=15562; +int CHART_INDICATOR_DATA_PORT=15562; // ZeroMQ Cnnections Context context("MQL5 JSON API"); @@ -51,7 +51,7 @@ Socket liveSocket(context,ZMQ_PUSH); Socket streamSocket(context,ZMQ_PUSH); Socket indicatorDataSocket(context,ZMQ_PUSH); Socket chartDataSocket(context,ZMQ_PULL); -Socket chartIndicatorSocket(context,ZMQ_PUB); +Socket chartIndicatorDataSocket(context,ZMQ_PUB); // Global variables \\ bool debug = false; @@ -84,12 +84,19 @@ int indicatorCount = 0; struct ChartWindow { long id; // Internal id string chartId; // UUID - string indicatorId; // UUID - int indicatorHandle; // Internal id/handle }; ChartWindow chartWindows[]; int chartWindowCount = 0; +struct ChartWindowIndicator { + long id; // Internal id + string indicatorId; // UUID + int indicatorHandle; // Internal id/handle +}; + +ChartWindowIndicator chartWindowIndicators[]; +int chartWindowIndicatorCount = 0; + // Refresh chart window interval for JsonAPIIndicator int chartWindowTimerInterval = 100; // Cycles of the globally set EventSetMillisecondTimer interval int chartWindowTimerCounter = 0; // Keeps track of the current cycle @@ -114,8 +121,8 @@ bool BindSockets(){ if (result == false) { return result; } else {Print("Bound 'Indicator Data' socket on port ", INDICATOR_DATA_PORT);} result = chartDataSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_DATA_PORT)); if (result == false) { return result; } else {Print("Bound 'Chart Data' socket on port ", CHART_DATA_PORT);} - result = chartIndicatorSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_INDICATOR_PORT)); - if (result == false) { return result; } else {Print("Bound 'JsonAPIIndicator Data' socket on port ", CHART_INDICATOR_PORT);} + result = chartIndicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_INDICATOR_DATA_PORT)); + if (result == false) { return result; } else {Print("Bound 'JsonAPIIndicator Data' socket on port ", CHART_INDICATOR_DATA_PORT);} sysSocket.setLinger(1000); dataSocket.setLinger(1000); @@ -123,7 +130,7 @@ bool BindSockets(){ streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); chartDataSocket.setLinger(1000); - chartIndicatorSocket.setLinger(1000); + chartIndicatorDataSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); @@ -132,7 +139,7 @@ bool BindSockets(){ streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); chartDataSocket.setReceiveHighWaterMark(1); // TODO confirm settings - chartIndicatorSocket.setReceiveHighWaterMark(1); + chartIndicatorDataSocket.setReceiveHighWaterMark(1); return result; } @@ -201,7 +208,7 @@ void OnDeinit(const int reason){ Print("Unbinding 'Chart Data' socket on port ", STR_PORT, ".."); streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, CHART_DATA_PORT)); Print("Unbinding 'JsonAPIIndicator Data' socket on port ", STR_PORT, ".."); - streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, CHART_INDICATOR_PORT)); + streamSocket.unbind(StringFormat("tcp://%s:%d", HOST, CHART_INDICATOR_DATA_PORT)); // Shutdown ZeroMQ Context context.shutdown(); @@ -256,6 +263,21 @@ int GetChartWindowIdxByChartWindowId(string chartWindowId) return -1; } +//+------------------------------------------------------------------+ +//| Get index of chart indicator handler array by indicator id string | +//+------------------------------------------------------------------+ +int GetChartIndicatorIdxByChartIndicatorId(string indicatorId) + { + for(int i=0;i0){ - if(debug) Print(chartMsg.getData()); - chartIndicatorSocket.send(chartMsg,true); + double values[]; + // Ensure that all indicators have finished intitailisation + for(int i=0;i= chartWindowTimerInterval) { for(int i=0;i +#include #include #include @@ -21,24 +21,34 @@ Context context("MQL5 JSON API"); Socket chartSubscriptionSocket(context,ZMQ_SUB); //--- input parameters +#property indicator_buffers 21 +#property indicator_plots 20 +#property indicator_label1 "JsonAPI" +#property indicator_type1 DRAW_NONE +#property indicator_type2 DRAW_NONE +#property indicator_type3 DRAW_NONE +//#property indicator_color3 CLR_NONE +#property indicator_type4 DRAW_NONE +#property indicator_type5 DRAW_NONE + input string IndicatorId=""; input string ShortName="JsonAPI"; -input string LineLabel="Value"; -input string ColorSyle = "clrRed"; -input string LineType = "DRAW_LINE"; -input string LineStyle = "STYLE_SOLID"; -input int LineWidth = 1; //--- indicator settings -double Buffer[]; -bool debug = false; -bool first = false; +double B0[], B1[], B2[], B3[], B4[], B5[], B6[], B7[], B8[], B9[], B10[], B11[], B12[], B13[], B14[], B15[], B16[], B17[], B18[], B19[], alive[]; +bool debug = true; +bool first = false; +int activeBufferCount = 0; + +int activeBufferCount = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() + { + bool result = chartSubscriptionSocket.connect(StringFormat("tcp://%s:%d", HOST, CHART_SUB_PORT)); if (result == false) {Print("Failed to subscrbe on port ", CHART_SUB_PORT);} else { @@ -46,32 +56,72 @@ int OnInit() // TODO subscribe only to own IndicatorId topic // Subscribe to all topics chartSubscriptionSocket.setSubscribe(""); - chartSubscriptionSocket.setLinger(1000); + //chartSubscriptionSocket.setLinger(1000); + chartSubscriptionSocket.setLinger(10000); // Number of messages to buffer in RAM. chartSubscriptionSocket.setReceiveHighWaterMark(5); // TODO confirm settings } + //--- indicator buffers mapping; - ArraySetAsSeries(Buffer,true); - SetIndexBuffer(0,Buffer,INDICATOR_DATA); - SetIndexBuffer(1,Buffer,INDICATOR_CALCULATIONS); - - color colorstyle = StringToColor(ColorSyle); - int linetype = StringToEnumInt(LineType); - int linestyle = StringToEnumInt(LineStyle); - SetStyle(ShortName, LineLabel, colorstyle, linetype, linestyle, LineWidth); + ArraySetAsSeries(B0,true); + ArraySetAsSeries(B1,true); + ArraySetAsSeries(B2,true); + ArraySetAsSeries(B3,true); + ArraySetAsSeries(B4,true); + ArraySetAsSeries(B5,true); + ArraySetAsSeries(B6,true); + ArraySetAsSeries(B7,true); + ArraySetAsSeries(B8,true); + ArraySetAsSeries(B9,true); + ArraySetAsSeries(B10,true); + ArraySetAsSeries(B11,true); + ArraySetAsSeries(B12,true); + ArraySetAsSeries(B13,true); + ArraySetAsSeries(B14,true); + ArraySetAsSeries(B15,true); + ArraySetAsSeries(B16,true); + ArraySetAsSeries(B17,true); + ArraySetAsSeries(B18,true); + ArraySetAsSeries(B19,true); + ArraySetAsSeries(alive,true); + + SetIndexBuffer(0,B0,INDICATOR_DATA); + SetIndexBuffer(1,B1,INDICATOR_DATA); + SetIndexBuffer(2,B2,INDICATOR_DATA); + SetIndexBuffer(3,B3,INDICATOR_DATA); + SetIndexBuffer(4,B4,INDICATOR_DATA); + SetIndexBuffer(5,B5,INDICATOR_DATA); + SetIndexBuffer(6,B6,INDICATOR_DATA); + SetIndexBuffer(7,B7,INDICATOR_DATA); + SetIndexBuffer(8,B8,INDICATOR_DATA); + SetIndexBuffer(9,B9,INDICATOR_DATA); + SetIndexBuffer(10,B10,INDICATOR_DATA); + SetIndexBuffer(11,B11,INDICATOR_DATA); + SetIndexBuffer(12,B12,INDICATOR_DATA); + SetIndexBuffer(13,B13,INDICATOR_DATA); + SetIndexBuffer(14,B14,INDICATOR_DATA); + SetIndexBuffer(15,B15,INDICATOR_DATA); + SetIndexBuffer(16,B16,INDICATOR_DATA); + SetIndexBuffer(17,B17,INDICATOR_DATA); + SetIndexBuffer(18,B18,INDICATOR_DATA); + SetIndexBuffer(19,B19,INDICATOR_DATA); + SetIndexBuffer(20,alive,INDICATOR_CALCULATIONS); // If the buffer index changes, the line starting with "CopyBuffer(chartWindowIndicators[i].indicatorHandle," in JsonAPI.mq5 has to be updated + + //--- + IndicatorSetString(INDICATOR_SHORTNAME,ShortName); + return(INIT_SUCCEEDED); } -void SetStyle(string shortname, string linelabel, color colorstyle, int linetype, int linestyle, int linewidth) { - IndicatorSetString(INDICATOR_SHORTNAME,shortname); - PlotIndexSetString(0,PLOT_LABEL,linelabel); - PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,colorstyle); - PlotIndexSetInteger(0,PLOT_DRAW_TYPE,linetype); - PlotIndexSetInteger(0,PLOT_LINE_STYLE,linestyle); - PlotIndexSetInteger(0,PLOT_LINE_WIDTH,linewidth); +void SetStyle(int bufferIdx, string linelabel, color colorstyle, int linetype, int linestyle, int linewidth) { + PlotIndexSetString(bufferIdx,PLOT_LABEL,linelabel); + PlotIndexSetInteger(bufferIdx,PLOT_LINE_COLOR,0,colorstyle); + PlotIndexSetInteger(bufferIdx,PLOT_DRAW_TYPE,linetype); + PlotIndexSetInteger(bufferIdx,PLOT_LINE_STYLE,linestyle); + PlotIndexSetInteger(bufferIdx,PLOT_LINE_WIDTH,linewidth); } //+------------------------------------------------------------------+ @@ -89,9 +139,31 @@ int OnCalculate(const int rates_total, const int &spread[]) { // While a new candle is forming, set the current value to be empty + if(rates_total>prev_calculated){ - Buffer[0] = EMPTY_VALUE; + B0[0] = EMPTY_VALUE; + B1[0] = EMPTY_VALUE; + B2[0] = EMPTY_VALUE; + B3[0] = EMPTY_VALUE; + B4[0] = EMPTY_VALUE; + B5[0] = EMPTY_VALUE; + B6[0] = EMPTY_VALUE; + B7[0] = EMPTY_VALUE; + B8[0] = EMPTY_VALUE; + B9[0] = EMPTY_VALUE; + B10[0] = EMPTY_VALUE; + B11[0] = EMPTY_VALUE; + B12[0] = EMPTY_VALUE; + B13[0] = EMPTY_VALUE; + B14[0] = EMPTY_VALUE; + B15[0] = EMPTY_VALUE; + B16[0] = EMPTY_VALUE; + B17[0] = EMPTY_VALUE; + B18[0] = EMPTY_VALUE; + B19[0] = EMPTY_VALUE; } + if(first==false) alive[0] = 1; + // ChartRedraw(0); //--- return value of prev_calculated for next call return(rates_total); @@ -99,19 +171,65 @@ int OnCalculate(const int rates_total, void SubscriptionHandler(ZmqMsg &chartMsg){ CJAVal message; - - // Get data from reguest + // Get data from request string msg=chartMsg.getData(); if(debug) Print("Processing:"+msg); - // Deserialize msg to CJAVal array if(!message.Deserialize(msg)){ Alert("Deserialization Error"); ExpertRemove(); } - if(message["indicatorChartId"]==IndicatorId) { - if(message["action"]=="PLOT") { - WriteToBuffer(message); + if(message["indicatorChartId"]==IndicatorId) { + + if(message["action"]=="PLOT" && message["actionType"]=="DATA") { + int bufferIdx = message["indicatorBufferId"].ToInt(); + if (bufferIdx == 0) WriteToBuffer(message, B0); + if (bufferIdx == 1) WriteToBuffer(message, B1); + if (bufferIdx == 2) WriteToBuffer(message, B2); + if (bufferIdx == 3) WriteToBuffer(message, B3); + if (bufferIdx == 4) WriteToBuffer(message, B4); + if (bufferIdx == 5) WriteToBuffer(message, B5); + if (bufferIdx == 6) WriteToBuffer(message, B6); + if (bufferIdx == 7) WriteToBuffer(message, B7); + if (bufferIdx == 8) WriteToBuffer(message, B8); + if (bufferIdx == 9) WriteToBuffer(message, B9); + if (bufferIdx == 10) WriteToBuffer(message, B10); + if (bufferIdx == 11) WriteToBuffer(message, B11); + if (bufferIdx == 12) WriteToBuffer(message, B12); + if (bufferIdx == 13) WriteToBuffer(message, B13); + if (bufferIdx == 14) WriteToBuffer(message, B14); + if (bufferIdx == 15) WriteToBuffer(message, B15); + if (bufferIdx == 16) WriteToBuffer(message, B16); + if (bufferIdx == 17) WriteToBuffer(message, B17); + if (bufferIdx == 18) WriteToBuffer(message, B18); + if (bufferIdx == 19) WriteToBuffer(message, B19); + } + else if(message["action"]=="PLOT" && message["actionType"]=="ADDBUFFER") { + string linelabel = message["style"]["linelabel"].ToStr(); + string colorstyleStr = message["style"]["color"].ToStr(); + string linetypeStr = message["style"]["linetype"].ToStr(); + string linestyleStr = message["style"]["linestyle"].ToStr(); + int linewidth = message["style"]["linewidth"].ToInt(); + + color colorstyle = StringToColor(colorstyleStr); + int linetype = StringToEnumInt(linetypeStr); + int linestyle = StringToEnumInt(linestyleStr); + + /* + //if (aa == false) { + Print("SETBUFF ActCount ",activeBufferCount); + if (activeBufferCount == 0) {SetIndexBuffer(0,B1,INDICATOR_DATA);} // Two semicolons ar required! No idea why. Seems to be a timing problem, better to keep it in init() + + if (activeBufferCount == 1) {SetIndexBuffer(1,B2,INDICATOR_DATA);;} + if (activeBufferCount == 2) {SetIndexBuffer(2,B3,INDICATOR_DATA);;} + if (activeBufferCount == 3) {SetIndexBuffer(3,B4,INDICATOR_DATA);;} + if (activeBufferCount == 4) {SetIndexBuffer(4,B5,INDICATOR_DATA);;} + + //aa = true;} + */ + + SetStyle(activeBufferCount, linelabel, colorstyle, linetype, linestyle, linewidth); + activeBufferCount = activeBufferCount + 1; } } } @@ -119,11 +237,17 @@ void SubscriptionHandler(ZmqMsg &chartMsg){ //+------------------------------------------------------------------+ //| Update indicator buffer function | //+------------------------------------------------------------------+ -void WriteToBuffer(CJAVal &message) { - int bufferSize = ArraySize(Buffer); +void WriteToBuffer(CJAVal &message, double &buffer[]) { + + int bufferSize = ArraySize(buffer); + int messageDataSize = message["data"].Size(); + // TODO check if this is working as expected. Seems to if(first==false) { - PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,bufferSize-messageDataSize); + for(int i=0;i