diff --git a/.gitattributes b/.gitattributes index fa6847ada..16aadd380 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,37 +1,19 @@ -# default: self-detect the content type (text vs. binary) -* text=auto - - -# enforce content type "text" to fix mixed line-endings in the repo -*.h text -*.htm text -*.html text -*.ini text -*.md text -*.mq4 text -*.mq5 text -*.mqh text -*.sh text -*.txt text - # set content encodings -* encoding=UTF-8 -*.mqh encoding=CP1252 -*.mq4 encoding=CP1252 -*.mq5 encoding=CP1252 -*.tpl encoding=CP1252 +* encoding=UTF-8 +*.mqh encoding=CP1252 text +*.mq4 encoding=CP1252 text +*.mq5 encoding=CP1252 text +*.tpl encoding=CP1252 text # checkout with Windows line-endings (not yet supported by EGit) -*.tpl text eol=crlf -hotkeys.ini text eol=crlf +hotkeys.ini text eol=crlf # Gitgub Linguist overrides -/etc/** linguist-detectable=false -/files/** linguist-detectable=false -/sounds/** linguist-detectable=false -/templates/** linguist-detectable=false -*.mqh linguist-language=MQL4 - +/etc/** linguist-detectable=false +/files/** linguist-detectable=false +/sounds/** linguist-detectable=false +/templates/** linguist-detectable=false +*.mqh linguist-language=MQL4 diff --git a/.github/workflows/compile-mql.yml b/.github/workflows/compile-mql.yml index 5ce0cd1f3..8897a5a09 100644 --- a/.github/workflows/compile-mql.yml +++ b/.github/workflows/compile-mql.yml @@ -123,7 +123,6 @@ jobs: -d 'artifact-id=${{ steps.artifact.outputs.artifact-id }}')" echo "HTTP status: $status" cat "$response_file" - ((status >= 400)) && exit 1 || : # ------------------------------------------------------------------------------------------------------------------------------------ diff --git a/bin/mqlc b/bin/mqlc index 8870369df..3e087a1db 100755 --- a/bin/mqlc +++ b/bin/mqlc @@ -262,8 +262,8 @@ function compareDirs() { # @param $3 - include directory # function compileMql40() { - local srcFile="$1" version="$2" include="$3" s_compiler s_srcFile - local compiler errorStatus=0 startms endms time='' output=() line errors=0 epl='' warnings=0 wpl='' retVal + local srcFile="$1" version="$2" include="$3" s_compiler s_srcFile logFile + local compiler errorStatus=0 startms endms time='' output=() line i errors=0 epl='' warnings=0 wpl='' retVal ((originalOutput)) || echo "Compiling \"$srcFile\" as $(versionName "$version")" @@ -271,6 +271,7 @@ function compileMql40() { setCompilerPath 'compiler' "$version" "$include" [[ "$compiler" == /* ]] && compiler="$(cygpath -w "$compiler")" [[ "$srcFile" == /* ]] && srcFile="$(cygpath -w "$srcFile")" + logFile="${srcFile%.*}.log" ((debug)) && { [[ "$compiler" == *\ * ]] && s_compiler="\"$compiler\"" || s_compiler="$compiler" [[ "$srcFile" == *\ * ]] && s_srcFile="\"$srcFile\"" || s_srcFile="$srcFile" @@ -289,12 +290,20 @@ function compileMql40() { # on success if ((!errorStatus)); then [[ "${output[-1]}" =~ ^(Exp|Library exp)\ file\ .+\ produced\ -\ ([0-9]+)\ error.+\ ([0-9]+)\ warning.+ ]] || { - fail "unexpected compiler output in last line: \"${output[-1]}\""; + fail "unexpected compiler output in last line:${NL}${output[-1]}"; } - errors="${BASH_REMATCH[2]}"; ((errors == 1)) && epl='' || epl='s' - warnings="${BASH_REMATCH[3]}"; ((warnings == 1)) && wpl='' || wpl='s' + errors="${BASH_REMATCH[2]}" + warnings="${BASH_REMATCH[3]}" if ((!originalOutput)); then + # reset some warnings + for ((i=3; i < ${#output[@]}; ++i)); do + [[ "${output[i]}" == '1;'* ]] && [[ "${output[i]}" == *'is not referenced and will be removed from exp-file' ]] && ((--warnings)) + done + + # add final results + ((errors == 1)) && epl='' || epl='s' + ((warnings == 1)) && wpl='' || wpl='s' time="$(duration "$startms" "$endms")" output[-1]="Result: $errors error$epl, $warnings warning$wpl, $time elapsed" ((!warnings)) && output+=('Success') @@ -310,9 +319,8 @@ function compileMql40() { ((++errors)) else # count errors and warnings - local i for ((i=3; i < ${#output[@]}; ++i)); do - [[ "${output[i]}" == '1;'* ]] && ((++warnings)) + [[ "${output[i]}" == '1;'* ]] && [[ "${output[i]}" != *'is not referenced and will be removed from exp-file' ]] && ((++warnings)) [[ "${output[i]}" == '2;'* ]] && ((++errors)) done fi @@ -349,6 +357,7 @@ function compileMql40() { printf '%s\n' "${output[@]}" ((warn2error)) || warnings=0 + ((!errors && !warnings)) && rm -f "$logFile" ((retVal = errors + warnings)) ((retVal > 255)) && retVal=255 return "$retVal" @@ -434,33 +443,38 @@ function compileMql5() { # # Reformat a single log message produced by the MQL4.0 compiler. # -# @param _InOut_ $1 - named reference to the log message +# @param _InOut_ $1 - reference to the log message # @param _In_ $2 - source file of the compilation -# @param _In_ $3 - number of errors during the compilation -# @param _In_ $4 - number of warnings during the compilation +# @param _In_ $3 - number of errors during compilation +# @param _In_ $4 - number of warnings during compilation # function formatCompiler40Msg() { ((BASH_SUBSHELL)) && fail "${FUNCNAME[0]}() can't write to outer scope from subshell" local -n logmsg="$1" - local srcFile="$2" errors="$3" warnings="$4" type code level levelS file location message len + local srcFile="$2" errors="$3" warnings="$4" type code level levelS file line location='' message len - [[ "$logmsg" =~ ^([0-9]+)\;([0-9]+)\;(.+)\;([0-9]+:[0-9]+)?\;(.*)$ ]] || fail "unexpected format of compiler message:$NL\"$logmsg\"" + [[ "$logmsg" =~ ^([0-9]+)\;([0-9]+)\;(.+)?\;([0-9]+:[0-9]+)?\;(.*)$ ]] || fail "unexpected format of compiler message:${NL}${logmsg}" type="${BASH_REMATCH[1]}" code="${BASH_REMATCH[2]}" file="${BASH_REMATCH[3]}" - location="${BASH_REMATCH[4]}" + line="${BASH_REMATCH[4]}" message="${BASH_REMATCH[5]}" - [[ "$type" == [12] ]] || fail "unexpected message type $type in compiler message:$NL\"$logmsg\"" - ((type == 1)) && level='warn' || level='error' + [[ "$type" == [12] ]] || fail "unexpected message type $type in compiler message:${NL}${logmsg}" + if ((type == 1)); then + [[ "$message" == *'is not referenced and will be removed from exp-file' ]] && level='info' || level='warn' + else + level='error' + fi ((errors)) && len=6 || len=5 levelS="$level: " levelS="${levelS:0:$len}" - [[ -n "$location" ]] && location=" ($location)" - - logmsg="${levelS} ${file}${location}: ${message}" - #logmsg="${file}${location}: ${level}: ${message}" + if [[ -n "$file" ]]; then + [[ -n "$line" ]] && line=" ($line)" + location="${file}${line}: " + fi + logmsg="${levelS} ${location}${message}" } @@ -1186,15 +1200,14 @@ metalang.exe 1=warn 2=error -2 ; 124 ; C:\z\F\Projects\mt4\mql\bin\Dow Jones Breakout.mq4 ; 146:12 ; 'HandleCommand' - function is not defined 1 ; 33 ; C:\z\F\Projects\mt4\mql\bin\Dow Jones Breakout.mq4 ; 149:8 ; '5' - comparison expression expected -2 ; 89 ; C:\z\F\Projects\mt4\mql\bin\Dow Jones Breakout.mq4 ; 526:1 ; '\end_of_program' - unbalanced left parenthesis +2 ; 124 ; C:\z\F\Projects\mt4\mql\bin\Dow Jones Breakout.mq4 ; 146:12 ; 'HandleCommand' - function is not defined 2 ; 114 ; C:\z\F\Projects\mt4\mql\bin\output.txt ; 11:53 ; 'some text' - more than 1 symbol 1 ; 41 ; C:\z\F\Projects\mt4\mql\bin\metalang.exe ; 1:1 ; 'MZ???' - expression on global scope not allowed 2 ; 75 ; C:\z\F\Projects\mt4\mql\bin\metalang.exe ; 1:1 ; 'MZ???' - variable not defined 2 ; 76 ; ; ; cannot open the source file 2 ; 52 ; ; ; cannot open the output file - +1 ; 39 ; ; ; Function "Foo" is not referenced and will be removed from exp-file metaeditor.exe -------------- diff --git a/config/hotkeys.ini b/config/hotkeys.ini index 9b41a9ffa..0069c4a12 100644 --- a/config/hotkeys.ini +++ b/config/hotkeys.ini @@ -12,6 +12,7 @@ Chart.ToggleTradeHistory=Alt+H Chart.ToggleUnitSize=Alt+D Config=Alt+C EA.ToggleMetrics=Alt+M +EA.TogglePercent=Alt+P Empty=Ctrl+P MarketWatch.Symbols=Ctrl+S ParameterStepper.Down=Alt+L @@ -20,4 +21,3 @@ ReopenAlerts=Alt+A SuperBars.TimeframeDown=Ctrl+L SuperBars.TimeframeUp=Ctrl+K - diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 585eb3b8e..4441213aa 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -1,29 +1,40 @@ /** - * An EA trading ZigZag reversals. + * ZigZag EA * - * The EA must not run permanently. If run permanently, it will not be profitable. - * Instead, it must be activated/deactivated depending on market sentiment. + * The EA trades ZigZag reversals. That's breakouts from a Donchian Channel which is the basis for ZigZag. * * * Requirements * ------------ - * • /mql4.0/indicators/ZigZag.mq4 + * • "mql40/experts/ZigZag EA" (this EA) + * • "mql40/indicators/ZigZag" (the MetaQuotes version is not suitable) + * • "mql40/scripts/Config" + * • "mql40/scripts/Chart.ToggleOpenOrders" + * • "mql40/scripts/Chart.ToggleTradeHistory" + * • "mql40/scripts/EA.Start" + * • "mql40/scripts/EA.EntrySignal" + * • "mql40/scripts/EA.Stop" + * • "mql40/scripts/EA.TogglePercent" + * • "mql40/scripts/EA.ToggleMetrics" + * • "mql40/libraries/rsfMT4Expander.dll" + * • "mql40/libraries/rsfStdlib" + * • "mql40/libraries/rsfHistory[123]" (three files) * * - * External control - * ---------------- - * • EA.Start: When a "start" command is received the EA opens a position in direction of the current ZigZag leg. There are - * two sub-commands "start:long" and "start:short" to start the EA in a predefined direction. - * The command is ignored if the EA already manages an open position. - * • EA.Stop: When a "stop" command is received the EA closes all open positions and stops waiting for new reversals. - * The command is ignored if the EA is already in status "stopped". - * • EA.Wait: When a "wait" command is received a stopped EA will wait for new reversals and start trading accordingly. - * The command is ignored if the EA is already in status "waiting". - * • EA.ToggleMetrics - * • Chart.ToggleOpenOrders - * • Chart.ToggleTradeHistory + * Inputs + * ------ + * • ZigZag.Periods: Lookback periods of the Donchian channel. * * + * Manual control + * -------------- + * • EA.Start: When a "start" command is received a stopped EA switches to status "waiting", waits for new signals + * and trades accordingly. The command is ignored if the EA is not in status "stopped". + * • EA.EntrySignal: When an "entry-signal" command is received the EA switches to status "trading" and opens a position in + * direction of the current ZigZag leg. The command is ignored if the EA already is in status "trading". + * • EA.Stop: When a "stop" command is received the EA closes all open positions and switches to status "stopped". + * The command is ignored if the EA already is in status "stopped". + * * * TODO: * - entry management @@ -55,21 +66,13 @@ * GBPJPY,M5 2024.01.15-2024.02.02, ZigZag(30), ControlPoints: 3.0 sec, 93 trades 243.000 ticks * * - stop on reverse signal - * - signals MANUAL_LONG|MANUAL_SHORT * - track and display total slippage * - reduce slippage on reversal: Close+Open => Hedge+CloseBy * - reduce slippage on short reversal: enter market via StopSell * - * - trading functionality - * support command "wait" in status "progressing" - * reverse trading and command EA.Reverse - * * - performance tracking * notifications for price feed outages * - * - status display - * parameter: ZigZag.Periods - * * - trade breaks * - full session (24h) with trade breaks * - partial session (e.g. 09:00-16:00) with trade breaks @@ -96,19 +99,15 @@ int __virtualTicks = 10000; // every 10 seconds to continue ope ////////////////////////////////////////////////////// Configuration //////////////////////////////////////////////////////// extern string Instance.ID = ""; // instance to load from a status file, format "[T]123" -extern string Instance.StartAt = "@time(01:02)"; // @time(datetime|time) -extern string Instance.StopAt = "@time(22:59)"; // @time(datetime|time) | @profit(numeric[%]) +extern string Instance.StartAt = "@time(00:02)"; // @time(datetime|time) +extern string Instance.StopAt = "@time(23:59)"; // @time(datetime|time) | @profit(numeric[%]) extern string ___a__________________________ = "=== Signal settings ==="; -extern int ZigZag.Periods = 30; +extern int ZigZag.Periods = 100; extern string ___b__________________________ = "=== Trade settings ==="; -extern int EntryOrder.Distance = 0; // in punits: entry order distance from the signal level extern double Lots = 0.1; -extern string ___c__________________________ = "=== Status ==="; -extern bool ShowProfitInPercent = false; // whether PnL is displayed in money or percent - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // framework @@ -138,11 +137,12 @@ extern bool ShowProfitInPercent = false; // whether #include #include -#include +#include #include #include +#include #include #include #include @@ -154,8 +154,8 @@ extern bool ShowProfitInPercent = false; // whether #include #include -#include -#include +#include +#include #include #include #include @@ -172,6 +172,7 @@ extern bool ShowProfitInPercent = false; // whether #include #include #include +#include #include @@ -237,12 +238,12 @@ int onTick() { double signal[3]; if (__isChart) { - if (!HandleCommands()) return(last_error); // process incoming commands (may switch the instance on/off) + if (!HandleCommands()) return(last_error); // process incoming commands } if (instance.status != STATUS_STOPPED) { if (instance.status == STATUS_WAITING) { - if (IsStartSignal(signal)) { + if (IsTradeSignal(signal)) { StartTrading(signal); } } @@ -275,25 +276,20 @@ bool onCommand(string cmd, string params, int keys) { string fullCmd = cmd +":"+ params +":"+ keys; if (cmd == "start") { + switch (instance.status) { + case STATUS_STOPPED: + logInfo("onCommand(3) "+ instance.name +" "+ DoubleQuoteStr(fullCmd)); + instance.status = STATUS_WAITING; + return(SaveStatus()); + } + } + else if (cmd == "entry-signal") { switch (instance.status) { case STATUS_WAITING: case STATUS_STOPPED: - string sDetail = " "; - int logLevel = LOG_INFO; - - double signal[3]; - signal[SIG_TYPE ] = 0; - signal[SIG_PRICE] = 0; - if (params == "long") signal[SIG_OP] = SIG_OP_LONG; - else if (params == "short") signal[SIG_OP] = SIG_OP_SHORT; - else { - signal[SIG_OP] = ifInt(GetZigZagTrend(0) > 0, SIG_OP_LONG, SIG_OP_SHORT); - if (params != "") { - sDetail = " skipping unsupported parameter in command "; - logLevel = LOG_NOTICE; - } - } - log("onCommand(1) "+ instance.name + sDetail + DoubleQuoteStr(fullCmd), NO_ERROR, logLevel); + double signal[] = {0,0,0}; + signal[SIG_OP] = ifInt(GetZigZagTrend(0) > 0, SIG_OP_LONG, SIG_OP_SHORT); + log("onCommand(1) "+ instance.name +" "+ DoubleQuoteStr(fullCmd), NO_ERROR, LOG_INFO); return(StartTrading(signal)); } } @@ -306,13 +302,8 @@ bool onCommand(string cmd, string params, int keys) { return(StopTrading(dNull)); } } - else if (cmd == "wait") { - switch (instance.status) { - case STATUS_STOPPED: - logInfo("onCommand(3) "+ instance.name +" "+ DoubleQuoteStr(fullCmd)); - instance.status = STATUS_WAITING; - return(SaveStatus()); - } + else if (cmd == "toggle-percent") { + return(ToggleProfitUnit()); } else if (cmd == "toggle-metrics") { int direction = ifInt(keys & F_VK_SHIFT, METRIC_PREVIOUS, METRIC_NEXT); @@ -324,7 +315,9 @@ bool onCommand(string cmd, string params, int keys) { else if (cmd == "toggle-trade-history") { return(ToggleTradeHistory()); } - else return(!logNotice("onCommand(4) "+ instance.name +" unsupported command: "+ DoubleQuoteStr(fullCmd))); + else { + return(!logNotice("onCommand(4) "+ instance.name +" unsupported command: "+ DoubleQuoteStr(fullCmd))); + } return(!logWarn("onCommand(5) "+ instance.name +" cannot execute command "+ DoubleQuoteStr(fullCmd) +" in status "+ StatusToStr(instance.status))); } @@ -574,11 +567,11 @@ bool IsTradingTime() { /** * Whether conditions are fullfilled to start trading. * - * @param _Out_ double &signal[] - array receiving entry signal details + * @param _Out_ double &signal[] - array receiving the entry signal * * @return bool */ -bool IsStartSignal(double &signal[]) { +bool IsTradeSignal(double &signal[]) { if (last_error || instance.status!=STATUS_WAITING) return(false); signal[SIG_TYPE ] = 0; signal[SIG_PRICE] = 0; @@ -844,19 +837,19 @@ double stop.profitPct.AbsValue() { /** - * Stop trading and close open positions (if any). + * Close open positions and stop trading. Depending on the passed trigger condition status changes to "waiting" or "stopped". * - * @param double signal[] - signal infos causing the call + * @param double trigger[] - trigger condition causing the call * * @return bool - success status */ -bool StopTrading(double signal[]) { +bool StopTrading(double trigger[]) { if (last_error != NULL) return(false); if (instance.status!=STATUS_WAITING && instance.status!=STATUS_TRADING) return(!catch("StopTrading(1) "+ instance.name +" cannot stop "+ StatusDescription(instance.status) +" instance", ERR_ILLEGAL_STATE)); - int sigType = signal[SIG_TYPE]; - double sigPrice = signal[SIG_PRICE]; - int sigOp = signal[SIG_OP]; + int sigType = trigger[SIG_TYPE]; + double sigPrice = trigger[SIG_PRICE]; + int sigOp = trigger[SIG_OP]; // close an open position if (instance.status == STATUS_TRADING) { @@ -966,6 +959,9 @@ bool UpdateStatus() { int error; if (IsError(onPositionClose("UpdateStatus(2) "+ instance.name +" "+ ComposePositionCloseMsg(error), error))) return(false); if (!MovePositionToHistory(OrderCloseTime(), exitPrice, exitPriceSig)) return(false); + if (error == ERR_CONCURRENT_MODIFICATION) { + SendChartCommand("EA.command", "stop"); // asynchronously stop the sequence + } } // update PnL stats @@ -1004,7 +1000,7 @@ bool SaveStatus() { } else if (IsTestInstance()) return(true); // don't modify the status file of a finished test - string section="", separator="", file=GetStatusFilename(); + string section="", separator="", file=GetStatusFileName(); bool fileExists = IsFile(file, MODE_SYSTEM); if (!fileExists) separator = CRLF; // an empty line separator SS.All(); // update trade stats and global string representations @@ -1019,8 +1015,6 @@ bool SaveStatus() { WriteIniString(file, section, "Instance.StopAt", /*string */ Instance.StopAt); WriteIniString(file, section, "ZigZag.Periods", /*int */ ZigZag.Periods); WriteIniString(file, section, "Lots", /*double */ NumberToStr(Lots, ".+")); - WriteIniString(file, section, "EntryOrder.Distance", /*int */ EntryOrder.Distance); - WriteIniString(file, section, "ShowProfitInPercent", /*bool */ ShowProfitInPercent); WriteIniString(file, section, "EA.Recorder", /*string */ EA.Recorder + separator); // trade stats @@ -1074,7 +1068,7 @@ bool ReadStatus() { if (IsLastError()) return(false); if (!instance.id) return(!catch("ReadStatus(1) "+ instance.name +" illegal value of instance.id: "+ instance.id, ERR_ILLEGAL_STATE)); - string section="", file=GetStatusFilename(); + string section="", file=GetStatusFileName(); if (file == "") return(!catch("ReadStatus(2) "+ instance.name +" status file not found", ERR_RUNTIME_ERROR)); if (!IsFile(file, MODE_SYSTEM)) return(!catch("ReadStatus(3) "+ instance.name +" file \""+ file +"\" not found", ERR_FILE_NOT_FOUND)); @@ -1088,8 +1082,6 @@ bool ReadStatus() { Instance.StopAt = GetIniStringA(file, section, "Instance.StopAt", ""); // string Instance.StopAt = @time(datetime|time) | @profit(numeric[%]) ZigZag.Periods = GetIniInt (file, section, "ZigZag.Periods" ); // int ZigZag.Periods = 40 Lots = GetIniDouble (file, section, "Lots" ); // double Lots = 0.1 - EntryOrder.Distance = GetIniInt (file, section, "EntryOrder.Distance" ); // int EntryOrder.Distance = 12 - ShowProfitInPercent = GetIniBool (file, section, "ShowProfitInPercent" ); // bool ShowProfitInPercent = 1 EA.Recorder = GetIniStringA(file, section, "EA.Recorder", ""); // string EA.Recorder = 1,2,4 // [Runtime status] @@ -1199,7 +1191,7 @@ bool SynchronizeStatus() { double netProfitP = grossProfitP + MathDiv(swapM + commissionM, PointValue(lots)); logWarn("SynchronizeStatus(4) "+ instance.name +" orphaned closed position found: #"+ ticket +", adding to instance..."); - if (IsEmpty(AddHistoryRecord(ticket, 0, 0, lots, 1, openType, openTime, openPrice, openPrice, stopLoss, takeProfit, closeTime, closePrice, closePrice, slippageP, swapM, commissionM, grossProfitM, netProfitM, netProfitP, grossProfitP, grossProfitP, grossProfitP, grossProfitP, grossProfitP))) return(false); + if (IsEmpty(AddHistoryRecord(ticket, 0, 0, openType, lots, 1, openTime, openPrice, openPrice, stopLoss, takeProfit, closeTime, closePrice, closePrice, slippageP, swapM, commissionM, grossProfitM, netProfitM, netProfitP, grossProfitP, grossProfitP, grossProfitP, grossProfitP, grossProfitP))) return(false); // update closed PL numbers stats[NET_MONEY][S_CLOSED_PROFIT] += netProfitM; @@ -1218,7 +1210,7 @@ bool SynchronizeStatus() { } SS.All(); - if (open.ticket!=prevOpenTicket || ArrayRange(history, 0)!=prevHistorySize) { + if (open.ticket != prevOpenTicket || ArrayRange(history, 0) != prevHistorySize) { CalculateStats(true); return(SaveStatus()); // immediately save status if orders changed } @@ -1226,16 +1218,6 @@ bool SynchronizeStatus() { } -/** - * Return a distinctive instance detail to be inserted in the status/log filename. - * - * @return string - */ -string GetStatusFilenameData() { - return("P="+ ZigZag.Periods); -} - - /** * Whether the specified ticket exists in the local history of closed positions. * @@ -1259,7 +1241,6 @@ string prev.Instance.StopAt = ""; int prev.ZigZag.Periods; double prev.Lots; int prev.EntryOrder.Distance; -bool prev.ShowProfitInPercent; // backed-up runtime variables affected by changing input parameters bool prev.start.time.condition; @@ -1295,8 +1276,6 @@ void BackupInputs() { prev.Instance.StopAt = StringConcatenate(Instance.StopAt, ""); prev.ZigZag.Periods = ZigZag.Periods; prev.Lots = Lots; - prev.EntryOrder.Distance = EntryOrder.Distance; - prev.ShowProfitInPercent = ShowProfitInPercent; // affected runtime variables prev.start.time.condition = start.time.condition; @@ -1330,8 +1309,6 @@ void RestoreInputs() { Instance.StopAt = prev.Instance.StopAt; ZigZag.Periods = prev.ZigZag.Periods; Lots = prev.Lots; - EntryOrder.Distance = prev.EntryOrder.Distance; - ShowProfitInPercent = prev.ShowProfitInPercent; // affected runtime variables start.time.condition = prev.start.time.condition; @@ -1384,13 +1361,13 @@ bool ValidateInputs() { for (int i=0; i < sizeOfExprs; i++) { // validate each expression expr = StrTrim(exprs[i]); - if (!StringLen(expr)) continue; + if (expr == "") continue; if (StringGetChar(expr, 0) != '@') return(!onInputError("ValidateInputs(2) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt))); if (Explode(expr, "(", sValues, NULL) != 2) return(!onInputError("ValidateInputs(3) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt))); if (!StrEndsWith(sValues[1], ")")) return(!onInputError("ValidateInputs(4) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt))); key = StrTrim(sValues[0]); sValue = StrTrim(StrLeft(sValues[1], -1)); - if (!StringLen(sValue)) return(!onInputError("ValidateInputs(5) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt))); + if (sValue == "") return(!onInputError("ValidateInputs(5) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt))); if (key == "@time") { if (isTimeCondition) return(!onInputError("ValidateInputs(6) "+ instance.name +" invalid input parameter Instance.StartAt: "+ DoubleQuoteStr(Instance.StartAt) +" (multiple time conditions)")); @@ -1423,13 +1400,13 @@ bool ValidateInputs() { for (i=0; i < sizeOfExprs; i++) { // validate each expression expr = StrTrim(exprs[i]); - if (!StringLen(expr)) continue; // support both OR operators "||" and "|" + if (expr == "") continue; // support both OR operators "||" and "|" if (StringGetChar(expr, 0) != '@') return(!onInputError("ValidateInputs(9) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt))); if (Explode(expr, "(", sValues, NULL) != 2) return(!onInputError("ValidateInputs(10) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt))); if (!StrEndsWith(sValues[1], ")")) return(!onInputError("ValidateInputs(11) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt))); key = StrTrim(sValues[0]); sValue = StrTrim(StrLeft(sValues[1], -1)); - if (!StringLen(sValue)) return(!onInputError("ValidateInputs(12) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt))); + if (sValue == "") return(!onInputError("ValidateInputs(12) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt))); if (key == "@time") { if (isTimeCondition) return(!onInputError("ValidateInputs(13) "+ instance.name +" invalid input parameter Instance.StopAt: "+ DoubleQuoteStr(Instance.StopAt) +" (multiple time conditions)")); @@ -1497,16 +1474,11 @@ bool ValidateInputs() { if (LT(Lots, 0)) return(!onInputError("ValidateInputs(23) "+ instance.name +" invalid input parameter Lots: "+ NumberToStr(Lots, ".1+") +" (too small)")); if (NE(Lots, NormalizeLots(Lots))) return(!onInputError("ValidateInputs(24) "+ instance.name +" invalid input parameter Lots: "+ NumberToStr(Lots, ".1+") +" (not a multiple of MODE_LOTSTEP="+ NumberToStr(MarketInfo(Symbol(), MODE_LOTSTEP), ".+") +")")); - // EntryOrder.Distance - if (isInitParameters && EntryOrder.Distance!=prev.EntryOrder.Distance) { - if (instanceWasStarted) return(!onInputError("ValidateInputs(25) "+ instance.name +" cannot change input parameter EntryOrder.Distance of "+ StatusDescription(instance.status) +" instance")); - } - // EA.Recorder: on | off* | 1,2,3=1000,... if (!Recorder_ValidateInputs(IsTestInstance())) return(false); SS.All(); - return(!catch("ValidateInputs(26)")); + return(!catch("ValidateInputs(25)")); } @@ -1528,7 +1500,13 @@ void SS.All() { * ShowStatus: Update the string representation of the instance name. */ void SS.InstanceName() { - instance.name = "Z."+ StrPadLeft(instance.id, 3, "0"); + if (!instance.id) { + // calling SS.All() and thus SS.InstanceName() before CreateInstanceId() is valid (e.g. after input validation of a new instance) + instance.name = "ZZ."; + } + else { + instance.name = "ZZ."+ StrPadLeft(instance.id, 3, "0"); + } } @@ -1540,7 +1518,7 @@ void SS.StartStopConditions() { // start conditions string sValue = ""; if (start.time.descr != "") { - sValue = sValue + ifString(sValue=="", "", " | ") + ifString(start.time.condition, "@", "!") + start.time.descr; + sValue = sValue + ifString(sValue=="", "", " || ") + ifString(start.time.condition, "@", "!") + start.time.descr; } if (sValue == "") status.startConditions = "-"; else status.startConditions = sValue; @@ -1548,13 +1526,13 @@ void SS.StartStopConditions() { // stop conditions sValue = ""; if (stop.time.descr != "") { - sValue = sValue + ifString(sValue=="", "", " | ") + ifString(stop.time.condition, "@", "!") + stop.time.descr; + sValue = sValue + ifString(sValue=="", "", " || ") + ifString(stop.time.condition, "@", "!") + stop.time.descr; } if (stop.profitPct.descr != "") { - sValue = sValue + ifString(sValue=="", "", " | ") + ifString(stop.profitPct.condition, "@", "!") + stop.profitPct.descr; + sValue = sValue + ifString(sValue=="", "", " || ") + ifString(stop.profitPct.condition, "@", "!") + stop.profitPct.descr; } if (stop.profitPunit.descr != "") { - sValue = sValue + ifString(sValue=="", "", " | ") + ifString(stop.profitPunit.condition, "@", "!") + stop.profitPunit.descr; + sValue = sValue + ifString(sValue=="", "", " || ") + ifString(stop.profitPunit.condition, "@", "!") + stop.profitPunit.descr; } if (sValue == "") status.stopConditions = "-"; else status.stopConditions = sValue; @@ -1583,30 +1561,35 @@ int ShowStatus(int error = NO_ERROR) { string sStatus="", sError=""; switch (instance.status) { - case NULL: sStatus = " not initialized"; break; - case STATUS_WAITING: sStatus = " waiting"; break; - case STATUS_TRADING: sStatus = " trading"; break; - case STATUS_STOPPED: sStatus = " stopped"; break; + case NULL: sStatus = " not initialized"; break; + case STATUS_WAITING: sStatus = " waiting for signal"; break; + case STATUS_TRADING: sStatus = " trading"; break; + case STATUS_STOPPED: sStatus = " stopped"; break; default: return(catch("ShowStatus(1) "+ instance.name +" illegal instance status: "+ instance.status, ERR_ILLEGAL_STATE)); } if (__STATUS_OFF) sError = StringConcatenate(" [switched off => ", ErrorDescription(__STATUS_OFF.reason), "]"); - string text = StringConcatenate(WindowExpertName(), " ID: ", sInstanceId, sStatus, sError, NL, - NL, - "Start: ", status.startConditions, NL, - "Stop: ", status.stopConditions, NL, - NL, - status.metricDescription, NL, - "Open: ", status.openLots, NL, - "Closed: ", status.closedTrades, NL, - "Profit: ", status.totalProfit, " ", status.profitStats, NL + string text = StringConcatenate(WindowExpertName(), " ID ", sInstanceId, sStatus, sError, NL, + NL, + "Start: ", status.startConditions, NL, + "Stop: ", status.stopConditions, NL, + NL, + status.metricDescription, NL, + "Open: ", status.openLots, NL, + "Closed: ", status.closedTrades, NL, + "Profit: ", status.totalProfit, " ", status.profitStats, NL ); - // 3 lines margin-top for instrument and indicator legends - Comment(NL, NL, NL, text); + // some lines margin-top for instrument and indicator legends + Comment(NL, NL, NL, NL, NL, text); if (__CoreFunction == CF_INIT) WindowRedraw(); + // 0 legends: 20 + // 1 legends: 39 + // 2 legends: 58 => 4*NL + // 3 legends: 77 => 5*NL + // store status in the chart to enable sending of chart commands string label = "EA.status"; if (ObjectFind(label) != 0) { @@ -1630,7 +1613,8 @@ int ShowStatus(int error = NO_ERROR) { bool CreateStatusBox() { if (!__isChart) return(true); - int x[]={2, 102}, y=50, fontSize=76, sizeofX=ArraySize(x); + int x[]={2, 102}, fontSize=76, sizeofX=ArraySize(x); + int y = ResolveTopDistance(); color bgColor = LemonChiffon; for (int i=0; i < sizeofX; i++) { @@ -1651,14 +1635,11 @@ bool CreateStatusBox() { * @return string */ string InputsToStr() { - return(StringConcatenate("Instance.ID=", DoubleQuoteStr(Instance.ID), ";"+ NL + - "Instance.StartAt=", DoubleQuoteStr(Instance.StartAt), ";"+ NL + - "Instance.StopAt=", DoubleQuoteStr(Instance.StopAt), ";"+ NL + - - "ZigZag.Periods=", ZigZag.Periods, ";"+ NL + - "Lots=", NumberToStr(Lots, ".1+"), ";"+ NL + - "EntryOrder.Distance=", EntryOrder.Distance, ";"+ NL + + return(StringConcatenate("Instance.ID=", DoubleQuoteStr(Instance.ID), ";"+ NL + + "Instance.StartAt=", DoubleQuoteStr(Instance.StartAt), ";"+ NL + + "Instance.StopAt=", DoubleQuoteStr(Instance.StopAt), ";"+ NL + - "ShowProfitInPercent=", BoolToStr(ShowProfitInPercent), ";") + "ZigZag.Periods=", ZigZag.Periods, ";"+ NL + + "Lots=", NumberToStr(Lots, ".1+"), ";") ); } diff --git a/mql40/experts/tools/HtmlReport2Chart.mq4 b/mql40/experts/tools/HtmlReport2Chart.mq4 index a278bc12d..d76f7f24b 100644 --- a/mql40/experts/tools/HtmlReport2Chart.mq4 +++ b/mql40/experts/tools/HtmlReport2Chart.mq4 @@ -524,7 +524,7 @@ bool UpdateHistoryRecord(int ticket, double stopLoss, double takeProfit, datetim int size = ArrayRange(history, 0); // find the ticket to update - for (int i=size-1; i >= 0; i--) { // iterate from the end (in most use cases faster) + for (int i=size-1; i >= 0; i--) { // iterate from the end (in most cases faster) if (ticket == history[i][H_TICKET]) break; } if (i < 0) return(!catch("UpdateHistoryRecord(1) ticket #"+ ticket +" not found", ERR_INVALID_PARAMETER)); diff --git a/mql40/include/rsf/MT4Expander.mqh b/mql40/include/rsf/MT4Expander.mqh index 2aa6597ed..d667962ca 100644 --- a/mql40/include/rsf/MT4Expander.mqh +++ b/mql40/include/rsf/MT4Expander.mqh @@ -176,12 +176,13 @@ // other string GetInternalWindowTextA(int hWnd); int GetLastWin32Error(); + int GetPressedVirtualKeys(int flags); bool IsProgramType(int type); bool IsVirtualKeyDown(int vKey); bool IsWindowAreaVisible(int hWnd); int PlaySoundA(string soundfile); - // Virtual no-ops. Automatically overwritten by MQL implementations of the same name. + // Virtual no-ops. Overridden by custom MQL implementations of the same name. int onInit(); int onInitUser(); int onInitParameters(); diff --git a/mql40/include/rsf/api.mqh b/mql40/include/rsf/api.mqh index 929355660..cb5812205 100644 --- a/mql40/include/rsf/api.mqh +++ b/mql40/include/rsf/api.mqh @@ -38,8 +38,9 @@ string ColorToHtmlStr(color value);; string ColorToRGBStr(color value);; string ColorToStr(color value);; void CopyMemory(int destination, int source, int bytes);; +int CountDays(datetime from, datetime to);; int CountDecimals(double number);; -int CountWorkdays(datetime from, datetime to);; +int CountWeekdays(datetime from, datetime to);; bool CreateDirectory(string path, int flags);; string CreateString(int length);; datetime DateTime1(int year, int month=1, int day=1, int hours=0, int minutes=0, int seconds=0);; @@ -786,6 +787,7 @@ string GetMqlDirectoryA();; string GetMqlDirectoryW();; string GetMqlSandboxPathA(int inTester);; string GetMqlSandboxPathW(int inTester);; +int GetPressedVirtualKeys(int flags);; string GetReparsePointTargetA(string name);; string GetStringA(int address);; string GetStringW(int address);; @@ -837,7 +839,7 @@ bool IsStandardTimeframe(int timeframe);; bool IsSymlinkA(string path);; bool IsTerminalConfigKeyA(string section, string key);; bool IsUIThread(int threadId=NULL);; -bool IsVirtualKeyDown(int vKey);; +bool IsVirtualKeyDown(int key);; bool IsWindowAreaVisible(int hWnd);; int LeaveContext(int &ec[]);; bool LoadMqlProgramA(int hChart, int programType, string programName);; diff --git a/mql40/include/rsf/expander/defines.h b/mql40/include/rsf/expander/defines.h index 63ee75c4f..79ea2f639 100644 --- a/mql40/include/rsf/expander/defines.h +++ b/mql40/include/rsf/expander/defines.h @@ -212,6 +212,7 @@ #define F_VK_MENU 32 // ALT key #define F_VK_LWIN 64 // left Windows key #define F_VK_RWIN 128 // right Windows key +#define F_VK_ALL 255 // F_VK_ESCAPE|F_VK_TAB|F_VK_CAPITAL|F_VK_SHIFT|F_VK_CONTROL|F_VK_MENU|F_VK_LWIN|F_VK_RWIN // order and operation types diff --git a/mql40/include/rsf/experts/deinit.mqh b/mql40/include/rsf/experts/deinit.mqh index 99bf681de..27ba504dd 100644 --- a/mql40/include/rsf/experts/deinit.mqh +++ b/mql40/include/rsf/experts/deinit.mqh @@ -95,7 +95,7 @@ int onDeinitTemplate() { * @return int - error status */ int onDeinitRemove() { - if (instance.status != STATUS_STOPPED) { + if (instance.status && instance.status != STATUS_STOPPED) { SS.TotalProfit(); SS.ProfitStats(); logInfo("onDeinitRemove(1) "+ instance.name +" expert removed in status \""+ StatusDescription(instance.status) +"\", profit: "+ status.totalProfit +" "+ status.profitStats); diff --git a/mql40/include/rsf/experts/init.mqh b/mql40/include/rsf/experts/init.mqh index 9849fa017..bf5aad663 100644 --- a/mql40/include/rsf/experts/init.mqh +++ b/mql40/include/rsf/experts/init.mqh @@ -23,7 +23,7 @@ int onInit() { int onInitUser() { if (ValidateInputs.ID()) { // TRUE: a valid instance id was specified if (RestoreInstance()) { // try to reload the given instance - logInfo("onInitUser(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFilename(true) +"\""); + logInfo("onInitUser(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFileName(true) +"\""); } } else if (StrTrim(Instance.ID) == "") { // no instance id was specified @@ -33,8 +33,8 @@ int onInitUser() { Instance.ID = ifString(instance.isTest, "T", "") + StrPadLeft(instance.id, 3, "0"); SS.InstanceName(); instance.created = GetLocalTime(); // local system time (also in tester) instance.started = TimeServer(); // trade server time (modeled in tester) - instance.status = STATUS_WAITING; - SetStatusFilename(); + instance.status = ifInt(__isTesting, STATUS_WAITING, STATUS_STOPPED); + SetStatusFileName(); logInfo("onInitUser(2) instance "+ instance.name +" created"); SaveStatus(); } @@ -88,7 +88,7 @@ int onInitSymbolChange() { int onInitTemplate() { if (RestoreVolatileStatus()) { // an instance id was found and restored if (RestoreInstance()) { // the instance was restored - logInfo("onInitTemplate(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFilename(true) +"\""); + logInfo("onInitTemplate(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFileName(true) +"\""); } return(last_error); } @@ -104,7 +104,7 @@ int onInitTemplate() { int onInitRecompile() { if (RestoreVolatileStatus()) { // same as for onInitTemplate() if (RestoreInstance()) { - logInfo("onInitRecompile(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFilename(true) +"\""); + logInfo("onInitRecompile(1) "+ instance.name +" restored in status \""+ StatusDescription(instance.status) +"\" from file \""+ GetStatusFileName(true) +"\""); } return(last_error); } @@ -118,8 +118,8 @@ int onInitRecompile() { * @return int - error status */ int afterInit() { - if (__isTesting || !IsTestInstance()) { // open the log file (flushes the log buffer) except if a finished test - string filename = GetLogFilename(); + if (__isTesting || !IsTestInstance()) { // open the log file (flushes the log buffer) except if in a finished test + string filename = GetLogFileName(); if (filename == "") return(last_error); if (!SetLogfile(filename)) return(catch("afterInit(1)")); } @@ -132,8 +132,3 @@ int afterInit() { StoreVolatileStatus(); // store the instance id for template reload/restart/recompilation etc. return(catch("afterInit(2)")); } - - -#import "rsfMT4Expander.dll" - string GetStatusFilenameData(); // A no-op in the Expander allows the user to optionally provide his own data. -#import diff --git a/mql40/include/rsf/experts/instance/CreateInstanceId.mqh b/mql40/include/rsf/experts/instance/CreateInstanceId.mqh index deddb3c52..53456e9df 100644 --- a/mql40/include/rsf/experts/instance/CreateInstanceId.mqh +++ b/mql40/include/rsf/experts/instance/CreateInstanceId.mqh @@ -32,8 +32,9 @@ int CreateInstanceId() { else { // online: generate a random id while (!magicNumber) { + instanceId = 0; while (instanceId < INSTANCE_ID_MIN || instanceId > INSTANCE_ID_MAX) { - instanceId = MathRand(); // select random id between ID_MIN and ID_MAX + instanceId = MathRand(); // pseudo-random id between ID_MIN and ID_MAX } magicNumber = CalculateMagicNumber(instanceId); if (!magicNumber) return(NULL); diff --git a/mql40/include/rsf/experts/instance/SetInstanceId.mqh b/mql40/include/rsf/experts/instance/SetInstanceId.mqh index 4d2ccb48e..f25d13ce2 100644 --- a/mql40/include/rsf/experts/instance/SetInstanceId.mqh +++ b/mql40/include/rsf/experts/instance/SetInstanceId.mqh @@ -14,7 +14,7 @@ bool SetInstanceId(string value, bool &error, string caller) { error = false; value = StrTrim(value); - if (!StringLen(value)) return(false); + if (value == "") return(false); bool isTest = false; int instanceId = 0; diff --git a/mql40/include/rsf/experts/log/GetLogFilename.mqh b/mql40/include/rsf/experts/log/GetLogFileName.mqh similarity index 63% rename from mql40/include/rsf/experts/log/GetLogFilename.mqh rename to mql40/include/rsf/experts/log/GetLogFileName.mqh index 7d0da3a6a..99f441046 100644 --- a/mql40/include/rsf/experts/log/GetLogFilename.mqh +++ b/mql40/include/rsf/experts/log/GetLogFileName.mqh @@ -3,8 +3,8 @@ * * @return string - filename or an empty string in case of errors */ -string GetLogFilename() { - string name = GetStatusFilename(); - if (!StringLen(name)) return(""); +string GetLogFileName() { + string name = GetStatusFileName(); + if (name == "") return(""); return(StrLeftTo(name, ".", -1) +".log"); } diff --git a/mql40/include/rsf/experts/status/CreateStatusBox_6.mqh b/mql40/include/rsf/experts/status/CreateStatusBox_6.mqh index f4b6ac937..e30827982 100644 --- a/mql40/include/rsf/experts/status/CreateStatusBox_6.mqh +++ b/mql40/include/rsf/experts/status/CreateStatusBox_6.mqh @@ -7,7 +7,8 @@ bool CreateStatusBox() { if (!__isChart) return(true); - int x[]={2, 66, 136}, y=50, fontSize=54, sizeofX=ArraySize(x); + int x[]={2, 66, 136}, fontSize=54, sizeofX=ArraySize(x); + int y = ResolveTopDistance(); color bgColor = LemonChiffon; for (int i=0; i < sizeofX; i++) { diff --git a/mql40/include/rsf/experts/status/ResolveTopDistance.mqh b/mql40/include/rsf/experts/status/ResolveTopDistance.mqh new file mode 100644 index 000000000..75f7d69cb --- /dev/null +++ b/mql40/include/rsf/experts/status/ResolveTopDistance.mqh @@ -0,0 +1,35 @@ +/** + * Resolve the top-distance of the status box to create. The box is placed below all existing chart legends. + * + * @return int - offset from top + */ +int ResolveTopDistance() { + // count existing chart legends + int objects = ObjectsTotal(); + int labels = ObjectsTotal(OBJ_LABEL); + int prefixLength = StringLen(CHARTLEGEND_PREFIX); + int legends = 0; + + for (int i=objects-1; i >= 0 && labels; i--) { + string name = ObjectName(i); + + if (ObjectType(name) == OBJ_LABEL) { + if (StrStartsWith(name, CHARTLEGEND_PREFIX)) { + string data = StrRight(name, -prefixLength); + int pid = StrToInteger(data); + int hChart = StrToInteger(StrRightFrom(data, ".")); + + if (pid && hChart==__ExecutionContext[EC.chart]) { + legends++; + } + } + labels--; + } + } + + // calculate position of the next line + int yDist = 20; // y-position of the top-most legend + int lineHeight = 19; // line height of legends + int y = yDist + legends * lineHeight; + return(y); +} diff --git a/mql40/include/rsf/experts/status/SS.ClosedTrades.mqh b/mql40/include/rsf/experts/status/SS.ClosedTrades.mqh index ca426f1cb..20c76847e 100644 --- a/mql40/include/rsf/experts/status/SS.ClosedTrades.mqh +++ b/mql40/include/rsf/experts/status/SS.ClosedTrades.mqh @@ -7,17 +7,26 @@ void SS.ClosedTrades() { status.closedTrades = "-"; } else { - CalculateStats(); + if (!CalculateStats()) return; + string trades = " trade"+ Pluralize(size); switch (status.activeMetric) { case METRIC_NET_MONEY: - status.closedTrades = size +" trades avg: "+ NumberToStr(stats[METRIC_NET_MONEY][S_TRADES_AVG_PROFIT], "R+.2") +" "+ AccountCurrency(); + if (status.profitInPercent) { + double totalPerformance = 1 + MathDiv(stats[METRIC_NET_MONEY][S_TOTAL_PROFIT], instance.startEquity); + double avgPerformance = MathPow(totalPerformance, 1./size); + double avgTrade = (avgPerformance - 1) * 100; + status.closedTrades = size + trades +" avg: "+ NumberToStr(avgTrade, "R+.2") +"%"; + } + else { + status.closedTrades = size + trades +" avg: "+ NumberToStr(stats[METRIC_NET_MONEY][S_TRADES_AVG_PROFIT], "R+.2") +" "+ AccountCurrency(); + } break; case METRIC_NET_UNITS: - status.closedTrades = size +" trades avg: "+ NumberToStr(stats[METRIC_NET_UNITS][S_TRADES_AVG_PROFIT]/pUnit, "R+."+ pDigits) +" "+ spUnit; + status.closedTrades = size + trades +" avg: "+ NumberToStr(stats[METRIC_NET_UNITS][S_TRADES_AVG_PROFIT]/pUnit, "R+."+ pDigits) +" "+ spUnit; break; case METRIC_SIG_UNITS: - status.closedTrades = size +" trades avg: "+ NumberToStr(stats[METRIC_SIG_UNITS][S_TRADES_AVG_PROFIT]/pUnit, "R+."+ pDigits) +" "+ spUnit; + status.closedTrades = size + trades +" avg: "+ NumberToStr(stats[METRIC_SIG_UNITS][S_TRADES_AVG_PROFIT]/pUnit, "R+."+ pDigits) +" "+ spUnit; break; default: diff --git a/mql40/include/rsf/experts/status/SS.MetricDescription.mqh b/mql40/include/rsf/experts/status/SS.MetricDescription.mqh index 99ba2f555..4d26785ab 100644 --- a/mql40/include/rsf/experts/status/SS.MetricDescription.mqh +++ b/mql40/include/rsf/experts/status/SS.MetricDescription.mqh @@ -4,13 +4,18 @@ void SS.MetricDescription() { switch (status.activeMetric) { case METRIC_NET_MONEY: - status.metricDescription = "Net PnL after all costs in "+ AccountCurrency() + NL + "-----------------------------------"; + if (status.profitInPercent) { + status.metricDescription = "Net PnL in %"+ NL + "----------------"; + } + else { + status.metricDescription = "Net PnL in "+ AccountCurrency() + NL + "------------------"; + } break; case METRIC_NET_UNITS: - status.metricDescription = "Net PnL after all costs in "+ spUnit + NL + "---------------------------------"+ ifString(spUnit=="point", "--", ""); + status.metricDescription = "Net PnL in "+ spUnit + NL + "-----------------"+ ifString(spUnit=="point", "--", ""); break; case METRIC_SIG_UNITS: - status.metricDescription = "Signal PnL before spread/any costs in "+ spUnit + NL + "-------------------------------------------------"+ ifString(spUnit=="point", "--", ""); + status.metricDescription = "Signal PnL in "+ spUnit + NL + "--------------------"+ ifString(spUnit=="point", "--", ""); break; default: diff --git a/mql40/include/rsf/experts/status/SS.ProfitStats.mqh b/mql40/include/rsf/experts/status/SS.ProfitStats.mqh index c3dacea0c..cb9af5ffb 100644 --- a/mql40/include/rsf/experts/status/SS.ProfitStats.mqh +++ b/mql40/include/rsf/experts/status/SS.ProfitStats.mqh @@ -12,7 +12,7 @@ void SS.ProfitStats() { switch (metric) { case METRIC_NET_MONEY: - if (ShowProfitInPercent) { + if (status.profitInPercent) { sMaxProfit = NumberToStr(MathDiv(stats[metric][S_MAX_PROFIT ], instance.startEquity) * 100, "R+.2"); sMaxDrawdown = NumberToStr(MathDiv(stats[metric][S_MAX_ABS_DRAWDOWN], instance.startEquity) * 100, "R+.2"); } diff --git a/mql40/include/rsf/experts/status/SS.TotalProfit.mqh b/mql40/include/rsf/experts/status/SS.TotalProfit.mqh index 9a6237349..f21b78c10 100644 --- a/mql40/include/rsf/experts/status/SS.TotalProfit.mqh +++ b/mql40/include/rsf/experts/status/SS.TotalProfit.mqh @@ -10,8 +10,8 @@ void SS.TotalProfit() { int metric = status.activeMetric; switch (metric) { case METRIC_NET_MONEY: - if (ShowProfitInPercent) status.totalProfit = NumberToStr(MathDiv(stats[metric][S_TOTAL_PROFIT], instance.startEquity) * 100, "R+.2") +"%"; - else status.totalProfit = NumberToStr(stats[metric][S_TOTAL_PROFIT], "R+.2") +" "+ AccountCurrency(); + if (status.profitInPercent) status.totalProfit = NumberToStr(MathDiv(stats[metric][S_TOTAL_PROFIT], instance.startEquity) * 100, "R+.2") +"%"; + else status.totalProfit = NumberToStr(stats[metric][S_TOTAL_PROFIT], "R+.2") +" "+ AccountCurrency(); break; case METRIC_NET_UNITS: diff --git a/mql40/include/rsf/experts/status/defines.mqh b/mql40/include/rsf/experts/status/defines.mqh index 4f0580d11..7480fca4c 100644 --- a/mql40/include/rsf/experts/status/defines.mqh +++ b/mql40/include/rsf/experts/status/defines.mqh @@ -8,6 +8,7 @@ // volatile status vars string status.filename = ""; // filepath relative to the MQL sandbox directory +bool status.profitInPercent = true; int status.activeMetric = 1; bool status.showOpenOrders; bool status.showTradeHistory; diff --git a/mql40/include/rsf/experts/status/file/FindStatusFile.mqh b/mql40/include/rsf/experts/status/file/FindStatusFile.mqh index 9d6a1a8b6..443b33979 100644 --- a/mql40/include/rsf/experts/status/file/FindStatusFile.mqh +++ b/mql40/include/rsf/experts/status/file/FindStatusFile.mqh @@ -1,5 +1,10 @@ /** - * Find an existing status file for the specified instance. + * Find an existing status file for the specified instance id. Finds any file matching + * + * ", ,* id=.set" (no matter what the custom name is) + * + * but doesn't scan subdirectories. The EA will use whatever was found for status and logfile. + * A more strict pattern is used for name generation in SetStatusFileName(). * * @param int instanceId - instance id * @param bool isTest - whether the instance is a test instance @@ -13,7 +18,7 @@ string FindStatusFile(int instanceId, bool isTest) { string sandboxDir = GetMqlSandboxPath() +"\\"; string statusDir = "presets\\"+ ifString(isTest, "Tester", GetAccountCompanyId()) +"\\"; - string basePattern = ProgramName() +", "+ Symbol() +",* id="+ StrPadLeft(""+ instanceId, 3, "0") +".set"; // matches files with and w/o user-specified data in the name + string basePattern = ProgramName() +", "+ Symbol() +",* id="+ StrPadLeft(""+ instanceId, 3, "0") +".set"; // matches files with custom names string pathPattern = sandboxDir + statusDir + basePattern; string result[]; diff --git a/mql40/include/rsf/experts/status/file/GetStatusFilename.mqh b/mql40/include/rsf/experts/status/file/GetStatusFileName.mqh similarity index 77% rename from mql40/include/rsf/experts/status/file/GetStatusFilename.mqh rename to mql40/include/rsf/experts/status/file/GetStatusFileName.mqh index cf15ea2b4..86b0776f7 100644 --- a/mql40/include/rsf/experts/status/file/GetStatusFilename.mqh +++ b/mql40/include/rsf/experts/status/file/GetStatusFileName.mqh @@ -1,16 +1,16 @@ /** - * Return the name of the status file. If the name is not yet set, an attempt is made to find an existing status file. + * Return the name of the status file. If the name is not yet set, an attempt is made to find an existing file. * * @param bool relative [optional] - whether to return the absolute path or the path relative to the MQL "files" directory * (default: absolute path) * * @return string - filename or an empty string in case of errors */ -string GetStatusFilename(bool relative = false) { +string GetStatusFileName(bool relative = false) { relative = relative!=0; if (status.filename == "") { - status.filename = FindStatusFile(instance.id, instance.isTest); // intentionally trigger an error if instance.id is not set + status.filename = FindStatusFile(instance.id, instance.isTest); if (status.filename == "") return(""); } diff --git a/mql40/include/rsf/experts/status/file/ReadStatus.TradeStats.mqh b/mql40/include/rsf/experts/status/file/ReadStatus.TradeStats.mqh index 2467a5a2f..7f7238621 100644 --- a/mql40/include/rsf/experts/status/file/ReadStatus.TradeStats.mqh +++ b/mql40/include/rsf/experts/status/file/ReadStatus.TradeStats.mqh @@ -35,6 +35,7 @@ bool ReadStatus.TradeStats(string file) { stats[METRIC_SIG_UNITS][S_MAX_ABS_DRAWDOWN] = GetIniDouble(file, section, "MaxAbsDrawdown") * pUnit; // double maxAbsDrawdown = -2345.6 stats[METRIC_SIG_UNITS][S_MAX_REL_DRAWDOWN] = GetIniDouble(file, section, "MaxRelDrawdown") * pUnit; // double maxRelDrawdown = -2345.6 - CalculateStats(); - return(!catch("ReadStatus.TradeStats(1)")); + if (CalculateStats()) + return(!catch("ReadStatus.TradeStats(1)")); + return(false); } diff --git a/mql40/include/rsf/experts/status/file/SaveStatus.TradeHistory.mqh b/mql40/include/rsf/experts/status/file/SaveStatus.TradeHistory.mqh index 8f8bd393e..4e9817f86 100644 --- a/mql40/include/rsf/experts/status/file/SaveStatus.TradeHistory.mqh +++ b/mql40/include/rsf/experts/status/file/SaveStatus.TradeHistory.mqh @@ -13,18 +13,20 @@ bool SaveStatus.TradeHistory(string file, bool fileExists) { if (!fileExists) separator = CRLF; // an empty line separator double netProfit, netProfitP, sigProfitP; + int sizeHistory = ArrayRange(history, 0), sizePartials = ArrayRange(partialClose, 0); - string section = "Trade history"; - int size = ArrayRange(history, 0); - for (int i=0; i < size; i++) { - WriteIniString(file, section, "full."+ i, HistoryRecordToStr(i, false)); + string section = "Trade history", suffix = ""; + for (int i=0; i < sizeHistory; i++) { + if (sizePartials && i == sizeHistory-1) { + suffix = separator; + } + WriteIniString(file, section, "full."+ i, HistoryRecordToStr(i, false) + suffix); netProfit += history[i][H_NETPROFIT_M ]; netProfitP += history[i][H_NETPROFIT_P ]; sigProfitP += history[i][H_SIG_PROFIT_P]; } - size = ArrayRange(partialClose, 0); - for (i=0; i < size; i++) { + for (i=0; i < sizePartials; i++) { WriteIniString(file, section, "part."+ i, HistoryRecordToStr(i, true)); } diff --git a/mql40/include/rsf/experts/status/file/SetStatusFileName.mqh b/mql40/include/rsf/experts/status/file/SetStatusFileName.mqh new file mode 100644 index 000000000..b5b3b5a4d --- /dev/null +++ b/mql40/include/rsf/experts/status/file/SetStatusFileName.mqh @@ -0,0 +1,16 @@ +/** + * Generates and initializes the name of the status file. Requires 'instance.id' and 'instance.created' to be set. + * + * @return bool - success status + */ +bool SetStatusFileName() { + if (status.filename != "") return(!catch("SetStatusFileName(1) "+ instance.name +" cannot modify an already set status filename: \""+ status.filename +"\"", ERR_ILLEGAL_STATE)); + if (!instance.id) return(!catch("SetStatusFileName(2) "+ instance.name +" illegal value of instance.id: 0", ERR_ILLEGAL_STATE)); + if (!instance.created) return(!catch("SetStatusFileName(3) "+ instance.name +" cannot create status filename (instance.created not set)", ERR_ILLEGAL_STATE)); + + string directory = "presets\\"+ ifString(IsTestInstance(), "Tester", GetAccountCompanyId()) +"\\"; + string baseName = ProgramName() +", "+ Symbol() +","+ PeriodDescription() +" "+ GmtTimeFormat(instance.created, "%Y.%m.%d %H.%M") +", id="+ StrPadLeft(instance.id, 3, "0") +".set"; + status.filename = directory + baseName; + + return(!catch("SetStatusFileName(4)")); +} diff --git a/mql40/include/rsf/experts/status/file/SetStatusFilename.mqh b/mql40/include/rsf/experts/status/file/SetStatusFilename.mqh deleted file mode 100644 index 72d6633dc..000000000 --- a/mql40/include/rsf/experts/status/file/SetStatusFilename.mqh +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Initializes the name of the used status file. Requires 'instance.id' and 'instance.created' to be set. - * - * If the strategy implements the function GetStatusFilenameData() the returned string will be inserted into the resulting - * filename. This can be used to insert distinctive runtime parameters into the name (e.g. SL/TP vars or trading modes). - * - * @return bool - success status - */ -bool SetStatusFilename() { - if (status.filename != "") return(!catch("SetStatusFilename(1) "+ instance.name +" cannot modify an already set status filename: \""+ status.filename +"\"", ERR_ILLEGAL_STATE)); - if (!instance.id) return(!catch("SetStatusFilename(2) "+ instance.name +" illegal value of instance.id: 0", ERR_ILLEGAL_STATE)); - if (!instance.created) return(!catch("SetStatusFilename(3) "+ instance.name +" cannot create status filename (instance.created not set)", ERR_ILLEGAL_STATE)); - - string userData = StrTrim(GetStatusFilenameData()); - if (userData != "") userData = userData +", "; - - string directory = "presets\\"+ ifString(IsTestInstance(), "Tester", GetAccountCompanyId()) +"\\"; - string baseName = ProgramName() +", "+ Symbol() +","+ PeriodDescription() +" "+ userData + GmtTimeFormat(instance.created, "%Y-%m-%d %H.%M") +", id="+ StrPadLeft(instance.id, 3, "0") +".set"; - status.filename = directory + baseName; - - return(!catch("SetStatusFilename(4)")); -} diff --git a/mql40/include/rsf/experts/status/volatile/RemoveVolatileStatus.mqh b/mql40/include/rsf/experts/status/volatile/RemoveVolatileStatus.mqh index 1bd8e104b..c127279cf 100644 --- a/mql40/include/rsf/experts/status/volatile/RemoveVolatileStatus.mqh +++ b/mql40/include/rsf/experts/status/volatile/RemoveVolatileStatus.mqh @@ -13,6 +13,13 @@ bool RemoveVolatileStatus() { Chart.RestoreString(key, sValue, true); } + // bool status.profitInPercent + if (__isChart) { + key = name +".status.profitInPercent"; + bool bValue = RemoveWindowIntegerA(__ExecutionContext[EC.chart], key); + Chart.RestoreBool(key, bValue, true); + } + // int status.activeMetric if (__isChart) { key = name +".status.activeMetric"; @@ -23,7 +30,7 @@ bool RemoveVolatileStatus() { // bool status.showOpenOrders if (__isChart) { key = name +".status.showOpenOrders"; - bool bValue = RemoveWindowIntegerA(__ExecutionContext[EC.chart], key); + bValue = RemoveWindowIntegerA(__ExecutionContext[EC.chart], key); Chart.RestoreBool(key, bValue, true); } diff --git a/mql40/include/rsf/experts/status/volatile/RestoreVolatileStatus.mqh b/mql40/include/rsf/experts/status/volatile/RestoreVolatileStatus.mqh index f1c0b4ee6..45928e07a 100644 --- a/mql40/include/rsf/experts/status/volatile/RestoreVolatileStatus.mqh +++ b/mql40/include/rsf/experts/status/volatile/RestoreVolatileStatus.mqh @@ -24,11 +24,23 @@ bool RestoreVolatileStatus() { } } + // bool status.profitInPercent + if (__isChart) { + key = name +".status.profitInPercent"; + int iValue = GetWindowIntegerA(__ExecutionContext[EC.chart], key); + if (iValue != 0) { + status.profitInPercent = (iValue > 0); + } + else if (!Chart.RestoreBool(key, status.profitInPercent, false)) { + status.profitInPercent = true; // reset to default value + } + } + // int status.activeMetric if (__isChart) { key = name +".status.activeMetric"; while (true) { - int iValue = GetWindowIntegerA(__ExecutionContext[EC.chart], key); + iValue = GetWindowIntegerA(__ExecutionContext[EC.chart], key); if (iValue != 0) { if (iValue > 0 && iValue <= 3) { // valid metrics: 1-3 status.activeMetric = iValue; diff --git a/mql40/include/rsf/experts/status/volatile/StoreVolatileStatus.mqh b/mql40/include/rsf/experts/status/volatile/StoreVolatileStatus.mqh index 941511144..914741d83 100644 --- a/mql40/include/rsf/experts/status/volatile/StoreVolatileStatus.mqh +++ b/mql40/include/rsf/experts/status/volatile/StoreVolatileStatus.mqh @@ -15,6 +15,13 @@ bool StoreVolatileStatus() { Chart.StoreString(key, value); } + // bool status.profitInPercent + if (__isChart) { + key = name +".status.profitInPercent"; + SetWindowIntegerA(__ExecutionContext[EC.chart], key, ifInt(status.profitInPercent, 1, -1)); + Chart.StoreBool(key, status.profitInPercent); + } + // int status.activeMetric if (__isChart) { key = name +".status.activeMetric"; diff --git a/mql40/include/rsf/experts/status/volatile/ToggleMetrics.mqh b/mql40/include/rsf/experts/status/volatile/ToggleMetrics.mqh index 685470b6c..e0dec2544 100644 --- a/mql40/include/rsf/experts/status/volatile/ToggleMetrics.mqh +++ b/mql40/include/rsf/experts/status/volatile/ToggleMetrics.mqh @@ -1,5 +1,5 @@ /** - * Toggle the EA display status between available metrics. + * Toggle the EA display between available metrics. * * @param int direction - METRIC_NEXT|METRIC_PREVIOUS * @param int minId - min metric id diff --git a/mql40/include/rsf/experts/status/volatile/ToggleProfitUnit.mqh b/mql40/include/rsf/experts/status/volatile/ToggleProfitUnit.mqh new file mode 100644 index 000000000..8f348f16d --- /dev/null +++ b/mql40/include/rsf/experts/status/volatile/ToggleProfitUnit.mqh @@ -0,0 +1,11 @@ +/** + * Toggle the displayed profit unit between absolute and percentage values. + * + * @return bool - success status + */ +bool ToggleProfitUnit() { + status.profitInPercent = !status.profitInPercent; + StoreVolatileStatus(); + SS.All(); + return(true); +} diff --git a/mql40/include/rsf/experts/trade/AddHistoryRecord.mqh b/mql40/include/rsf/experts/trade/AddHistoryRecord.mqh index 0777223a7..44880df25 100644 --- a/mql40/include/rsf/experts/trade/AddHistoryRecord.mqh +++ b/mql40/include/rsf/experts/trade/AddHistoryRecord.mqh @@ -36,7 +36,7 @@ int AddHistoryRecord(int ticket, int fromTicket, int toTicket, int type, double if (isPartial) { // resolve the partialClose[] index to insert at int size = ArrayRange(partialClose, 0); - for (int i=size-1; i >= 0; i--) { // iterate from the end (in most use cases faster) + for (int i=size-1; i >= 0; i--) { // iterate from the end (in most cases faster) if (ticket == partialClose[i][H_TICKET]) return(_EMPTY(catch("AddHistoryRecord(1) "+ instance.name +" cannot add record, ticket #"+ ticket +" already exists (partialClose["+ i +"])", ERR_INVALID_PARAMETER))); if (openTime > partialClose[i][H_OPENTIME]) { @@ -92,7 +92,7 @@ int AddHistoryRecord(int ticket, int fromTicket, int toTicket, int type, double else { // resolve the history[] index to insert at size = ArrayRange(history, 0); - for (i=size-1; i >= 0; i--) { // iterate from the end (in most use cases faster) + for (i=size-1; i >= 0; i--) { // iterate from the end (faster in most cases) if (ticket == history[i][H_TICKET]) return(_EMPTY(catch("AddHistoryRecord(2) "+ instance.name +" cannot add record, ticket #"+ ticket +" already exists (history["+ i +"])", ERR_INVALID_PARAMETER))); if (openTime > history[i][H_OPENTIME]) { diff --git a/mql40/include/rsf/experts/trade/ComposePositionCloseMsg.mqh b/mql40/include/rsf/experts/trade/ComposePositionCloseMsg.mqh index b516284a0..30c3e9b82 100644 --- a/mql40/include/rsf/experts/trade/ComposePositionCloseMsg.mqh +++ b/mql40/include/rsf/experts/trade/ComposePositionCloseMsg.mqh @@ -6,7 +6,7 @@ * @return string */ string ComposePositionCloseMsg(int &error) { - // #1 Sell 0.1 GBPUSD at 1.5457'2 was [unexpectedly ]closed [by SL|TP ]at 1.5457'2 ([slippage: -0.3 pip, ]market: Bid/Ask) [sl|tp|so: 47.7%/169.20/354.40] + // #1 Sell 0.1 GBPUSD at 1.5457'2 was [unexpectedly ]closed [by SL|TP ]at 1.5457'2 ([better|worse: -0.3 pip, ]market: Bid/Ask) [sl|tp|so: 47.7%/169.20/354.40] error = NO_ERROR; int ticket = OrderTicket(); @@ -48,7 +48,7 @@ string ComposePositionCloseMsg(int &error) { string sUnexpected = ifString(closedBySL || closedByTP || (__isTesting && __CoreFunction==CF_DEINIT), "", "unexpectedly "); string sBySL = ifString(closedBySL, "by SL ", ""); string sByTP = ifString(closedByTP, "by TP ", ""); - string sSlippage = ifString(!slippage, "", "slippage: "+ NumberToStr(slippage/pUnit, "R+."+ pDigits) + ifString(pUnit==1, "", " "+ spUnit) +", "); + string sSlippage = ifString(!slippage, "", ifString(GT(slippage, 0, Digits), "better: ", "worse: ") + NumberToStr(slippage/pUnit, "R+."+ pDigits) + ifString(pUnit==1, "", " "+ spUnit) +", "); string sComment = ifString(comment==instance.name, "", " "+ comment); string msg = "#"+ ticket +" "+ sType +" "+ NumberToStr(lots, ".+") +" "+ OrderSymbol() +" at "+ sOpenPrice +" was "+ sUnexpected +"closed "+ sBySL + sByTP +"at "+ sClosePrice; diff --git a/mql40/include/rsf/experts/trade/MovePositionToHistory.mqh b/mql40/include/rsf/experts/trade/MovePositionToHistory.mqh index 18ee1bf4d..3adc18869 100644 --- a/mql40/include/rsf/experts/trade/MovePositionToHistory.mqh +++ b/mql40/include/rsf/experts/trade/MovePositionToHistory.mqh @@ -135,7 +135,7 @@ bool MovePositionToHistory(datetime closeTime, double closePrice, double closePr // validate the aggregated record and add it to history[] if (a.fromTicket != 0) return(!catch("MovePositionToHistory(3) "+ instance.name +" fromTicket #"+ a.fromTicket +" not found in partialClose[]", ERR_ILLEGAL_STATE)); if (NE(a.part, 1, 2)) return(!catch("MovePositionToHistory(4) "+ instance.name +" not all partial closes from ticket #"+ open.ticket +" found (found "+ NumberToStr(a.part, ".1+") +" of 1.0)", ERR_ILLEGAL_STATE)); - a.lots = NormalizeDouble(a.lots, 2); // normalize calculated fields + a.lots = NormalizeDouble(a.lots, 2); // normalize calculated fields a.part = 1; a.slippageP = NormalizeDouble(a.slippageP, Digits); a.swapM = NormalizeDouble(a.swapM, 2); @@ -143,7 +143,7 @@ bool MovePositionToHistory(datetime closeTime, double closePrice, double closePr a.grossProfitM = NormalizeDouble(a.grossProfitM, 2); a.netProfitM = NormalizeDouble(a.netProfitM, 2); - // we can't use AddHistoryRecord() as it invalidates the cache used by CalculateStats(), thus negatively impacting test speed + // we can't use AddHistoryRecord() as it invalidates the cache used by CalculateStats(), thus negatively impacting tester speed i = ArrayRange(history, 0); ArrayResize(history, i+1); history[i][H_TICKET ] = a.ticket; @@ -201,7 +201,7 @@ bool MovePositionToHistory(datetime closeTime, double closePrice, double closePr } if (__isChart) { - CalculateStats(); + if (!CalculateStats()) return(false); SS.OpenLots(); SS.ClosedTrades(); } diff --git a/mql40/include/rsf/experts/trade/stats/CalculateStats.mqh b/mql40/include/rsf/experts/trade/stats/CalculateStats.mqh index f1fe159b9..d4f8d9122 100644 --- a/mql40/include/rsf/experts/trade/stats/CalculateStats.mqh +++ b/mql40/include/rsf/experts/trade/stats/CalculateStats.mqh @@ -1,5 +1,5 @@ /** - * Update/re-calculate trade statistics. Most important numbers: + * Update/re-calculate statistics for the trade history. Most important numbers: * * - Profit factor = GrossProfit / GrossLoss * - MaxRelativeDrawdown = Max(EquityPeak - EquityValley) @@ -7,9 +7,9 @@ * - Sortino ratio = AnnualizedReturn / DownsideVolatility * - Calmar ratio = AnnualizedReturn / MaxRelativeDrawdown * - * @param bool fullRecalculation [optional] - whether to process new history entries only or to perform a full recalculation - * (default: new history entries only) - * @return void + * @param bool recalculate [optional] - whether to process new history entries only or to perform a full recalculation + * (default: new history entries only) + * @return bool - success status * * * TODO: @@ -18,16 +18,16 @@ * - Zephyr Pain Index: https://investexcel.net/zephyr-pain-index/ * - Zephyr K-Ratio: http://web.archive.org/web/20210116024652/https://www.styleadvisor.com/resources/statfacts/zephyr-k-ratio * - * @link https://invidious.nerdvpn.de/watch?v=GhrxgbQnEEU# [Linear Regression by Hand] + * @link https://www.youtube.com/watch?v=GhrxgbQnEEU# [Linear Regression by Hand] */ -void CalculateStats(bool fullRecalculation = false) { +bool CalculateStats(bool recalculate = false) { int hstTrades = ArrayRange(history, 0); int processedTrades = stats[1][S_TRADES]; - if (!hstTrades || hstTrades < processedTrades || fullRecalculation) { + if (!hstTrades || hstTrades < processedTrades || recalculate) { processedTrades = 0; } - if (processedTrades >= hstTrades) return; + if (processedTrades >= hstTrades) return(true); int metrics = ArrayRange(stats, 0) - 1; int profitFields [] = {0, H_NETPROFIT_M, H_NETPROFIT_P, H_SIG_PROFIT_P }, iProfitField; @@ -152,15 +152,16 @@ void CalculateStats(bool fullRecalculation = false) { } } - // calculate number of workdays the instance was running (don't use OpenTime/CloseTime) - datetime startTime = instance.started; - datetime endTime = ifInt(instance.stopped, instance.stopped, Tick.time); - int workdays = CountWorkdays(startTime, endTime); + // calculate number of days the instance was trading + datetime startTime = history[0][H_OPENTIME]; + datetime endTime = history[i-1][H_CLOSETIME]; + int days = CountDays(startTime, endTime); + if (days < 1) return(false); // calculate summaries, percentages, averages, ratios for (m=1; m <= metrics; m++) { - stats[m][S_TRADES ] = hstTrades; - stats[m][S_WORKDAYS] = workdays; + stats[m][S_TRADES] = hstTrades; + stats[m][S_DAYS ] = days; stats[m][S_TRADES_LONG_PCT ] = MathDiv(stats[m][S_TRADES_LONG ], stats[m][S_TRADES ]); stats[m][S_TRADES_SHORT_PCT ] = MathDiv(stats[m][S_TRADES_SHORT ], stats[m][S_TRADES ]); @@ -187,10 +188,12 @@ void CalculateStats(bool fullRecalculation = false) { stats[m][S_LOSERS_AVG_DRAWDOWN ] = MathDiv(stats[m][S_LOSERS_SUM_DRAWDOWN ], stats[m][S_LOSERS ]); stats[m][S_PROFIT_FACTOR] = MathAbs(MathDiv(stats[m][S_WINNERS_GROSS_PROFIT], stats[m][S_LOSERS_GROSS_LOSS], 99999)); // 99999: alias for +Infinity - stats[m][S_SHARPE_RATIO ] = CalculateSharpeRatio(m); - stats[m][S_SORTINO_RATIO] = CalculateSortinoRatio(m); - stats[m][S_CALMAR_RATIO ] = CalculateCalmarRatio(m); + stats[m][S_SHARPE_RATIO ] = CalculateSharpeRatio(m); if (!stats[m][S_SHARPE_RATIO ]) return(false); + stats[m][S_SORTINO_RATIO] = CalculateSortinoRatio(m); if (!stats[m][S_SORTINO_RATIO]) return(false); + stats[m][S_CALMAR_RATIO ] = CalculateCalmarRatio(m); if (!stats[m][S_CALMAR_RATIO ]) return(false); } + + return(!catch("CalculateStats(1)")); } @@ -220,9 +223,9 @@ double CalculateSharpeRatio(int metric) { if (trades > ArrayRange(history, 0)) return(!catch("CalculateSharpeRatio(1) illegal value of stats["+ metric +"][S_TRADES]: "+ trades +" (out-of-range)", ERR_ILLEGAL_STATE)); // annualize total return - int workdays = stats[metric][S_WORKDAYS]; - if (workdays <= 0) return(!catch("CalculateSharpeRatio(2) illegal value of stats["+ metric +"][S_WORKDAYS]: "+ workdays +" (must be positive)", ERR_ILLEGAL_STATE)); - double annualizedReturn = totalReturn/workdays * 252; // commonly used number of working days + int days = stats[metric][S_DAYS]; + if (days <= 0) return(!catch("CalculateSharpeRatio(2) illegal value of stats["+ metric +"][S_DAYS]: "+ days +" (must be positive)", ERR_ILLEGAL_STATE)); + double annualizedReturn = totalReturn/days * 365; // prepare dataset for iStdDevOnArray() int profitFields[] = {0, H_NETPROFIT_M, H_NETPROFIT_P, H_SIG_PROFIT_P}, iProfit=profitFields[metric]; @@ -274,9 +277,9 @@ double CalculateSortinoRatio(int metric) { if (trades > ArrayRange(history, 0)) return(!catch("CalculateSortinoRatio(1) illegal value of stats["+ metric +"][S_TRADES]: "+ trades +" (out-of-range)", ERR_ILLEGAL_STATE)); // annualize total return - int workdays = stats[metric][S_WORKDAYS]; - if (workdays <= 0) return(!catch("CalculateSortinoRatio(2) illegal value of stats["+ metric +"][S_WORKDAYS]: "+ workdays +" (must be positive)", ERR_ILLEGAL_STATE)); - double annualizedReturn = totalReturn/workdays * 252; // commonly used number of working days + int days = stats[metric][S_DAYS]; + if (days <= 0) return(!catch("CalculateSortinoRatio(2) illegal value of stats["+ metric +"][S_DAYS]: "+ days +" (must be positive)", ERR_ILLEGAL_STATE)); + double annualizedReturn = totalReturn/days * 365; // prepare dataset for iStdDevOnArray() int profitFields[] = {0, H_NETPROFIT_M, H_NETPROFIT_P, H_SIG_PROFIT_P}, iProfit=profitFields[metric]; @@ -327,9 +330,9 @@ double CalculateCalmarRatio(int metric) { if (!trades) return(0); // annualize total return - int workdays = stats[metric][S_WORKDAYS]; - if (workdays <= 0) return(!catch("CalculateCalmarRatio(1) illegal value of stats["+ metric +"][S_WORKDAYS]: "+ workdays +" (must be positive)", ERR_ILLEGAL_STATE)); - double annualizedReturn = totalReturn/workdays * 252; // commonly used number of working days + int days = stats[metric][S_DAYS]; + if (days <= 0) return(!catch("CalculateCalmarRatio(1) illegal value of stats["+ metric +"][S_DAYS]: "+ days +" (must be positive)", ERR_ILLEGAL_STATE)); + double annualizedReturn = totalReturn/days * 365; // calculate final ratio double drawdown = stats[metric][S_MAX_REL_DRAWDOWN]; diff --git a/mql40/include/rsf/experts/trade/stats/defines.mqh b/mql40/include/rsf/experts/trade/stats/defines.mqh index c9c1305f0..4d2ad9a7f 100644 --- a/mql40/include/rsf/experts/trade/stats/defines.mqh +++ b/mql40/include/rsf/experts/trade/stats/defines.mqh @@ -13,7 +13,7 @@ double stats[4][77]; // trade statistics with metric #define S_SHARPE_RATIO 7 #define S_SORTINO_RATIO 8 #define S_CALMAR_RATIO 9 -#define S_WORKDAYS 10 // number of trading days the instance was running (for annualization) +#define S_DAYS 10 // number of calendar days the instance was running (for annualization) #define S_TRADES 11 // all closed trades #define S_TRADES_LONG 12 diff --git a/mql40/include/rsf/functions/HandleCommands.mqh b/mql40/include/rsf/functions/HandleCommands.mqh index e937f6a01..82404461e 100644 --- a/mql40/include/rsf/functions/HandleCommands.mqh +++ b/mql40/include/rsf/functions/HandleCommands.mqh @@ -2,8 +2,8 @@ * Retrieve received commands and pass them to the command handler. Command format: "cmd[:params[:modifiers]]" * * cmd: command identifier (required) - * params: one or more command parameters separated by comma "," (optional) - * modifiers: one or more virtual key modifiers separated by comma "," (optional) + * params: one or more command parameters separated by comma (optional) + * modifiers: one or more virtual key modifiers separated by comma (optional) * * @param string channel [optional] - channel to check for commands (default: the program's standard command channel) * @@ -19,24 +19,38 @@ bool HandleCommands(string channel = "") { int size = ArraySize(commands); for (int i=0; i < size && !last_error; i++) { - string cmd="", params="", modifier="", values[]; + string cmd="", params="", modifiers="", sValue="", sValues[]; - int parts = Explode(commands[i], ":", values, NULL), virtKeys=0; - if (parts > 0) cmd = StrTrim(values[0]); - if (parts > 1) params = StrTrim(values[1]); + int parts = Explode(commands[i], ":", sValues, NULL), iValue=0, keys=0; + if (parts > 0) cmd = StrTrim(sValues[0]); + if (parts > 1) params = StrTrim(sValues[1]); if (parts > 2) { - parts = Explode(values[2], ",", values, NULL); - for (int n=0; n < parts; n++) { - modifier = StrTrim(values[n]); - if (modifier == "VK_ESCAPE") virtKeys |= F_VK_ESCAPE; - else if (modifier == "VK_TAB") virtKeys |= F_VK_TAB; - else if (modifier == "VK_CAPITAL") virtKeys |= F_VK_CAPITAL; // CAPSLOCK key - else if (modifier == "VK_SHIFT") virtKeys |= F_VK_SHIFT; - else if (modifier == "VK_CONTROL") virtKeys |= F_VK_CONTROL; - else if (modifier == "VK_MENU") virtKeys |= F_VK_MENU; // ALT key - else if (modifier == "VK_LWIN") virtKeys |= F_VK_LWIN; // left Windows key - else if (modifier == "VK_RWIN") virtKeys |= F_VK_RWIN; // right Windows key - else if (modifier != "") logNotice("HandleCommands(1) skipping unsupported key modifier: "+ modifier); + modifiers = StrTrim(sValues[2]); + if (StrIsDigits(modifiers)) { + iValue = StrToInteger(modifiers); + if (iValue & F_VK_ESCAPE && 1) keys |= F_VK_ESCAPE; + if (iValue & F_VK_TAB && 1) keys |= F_VK_TAB; + if (iValue & F_VK_CAPITAL && 1) keys |= F_VK_CAPITAL; // CAPSLOCK key + if (iValue & F_VK_SHIFT && 1) keys |= F_VK_SHIFT; + if (iValue & F_VK_CONTROL && 1) keys |= F_VK_CONTROL; + if (iValue & F_VK_MENU && 1) keys |= F_VK_MENU; // ALT key + if (iValue & F_VK_LWIN && 1) keys |= F_VK_LWIN; + if (iValue & F_VK_RWIN && 1) keys |= F_VK_RWIN; + } + else { + parts = Explode(modifiers, ",", sValues, NULL); + for (int n=0; n < parts; n++) { + sValue = StrTrim(sValues[n]); + if (sValue == "VK_ESCAPE") keys |= F_VK_ESCAPE; + else if (sValue == "VK_TAB") keys |= F_VK_TAB; + else if (sValue == "VK_CAPITAL") keys |= F_VK_CAPITAL; + else if (sValue == "VK_SHIFT") keys |= F_VK_SHIFT; + else if (sValue == "VK_CONTROL") keys |= F_VK_CONTROL; + else if (sValue == "VK_MENU") keys |= F_VK_MENU; + else if (sValue == "VK_LWIN") keys |= F_VK_LWIN; + else if (sValue == "VK_RWIN") keys |= F_VK_RWIN; + else if (sValue != "") logNotice("HandleCommands(1) skipping unsupported key modifier: "+ sValue); + } } } @@ -44,7 +58,7 @@ bool HandleCommands(string channel = "") { logNotice("HandleCommands(2) skipping empty command: \""+ commands[i] +"\""); continue; } - onCommand(cmd, params, virtKeys); + onCommand(cmd, params, keys); } } return(!last_error); diff --git a/mql40/include/rsf/functions/chartlegend.mqh b/mql40/include/rsf/functions/chartlegend.mqh index 5d3914694..2d8613cc5 100644 --- a/mql40/include/rsf/functions/chartlegend.mqh +++ b/mql40/include/rsf/functions/chartlegend.mqh @@ -1,7 +1,3 @@ - -#define CHARTLEGEND_PREFIX "rsf.Legend." - - /** * Create a text label object in the main chart for an indicator's chart legend. * @@ -85,7 +81,7 @@ bool RearrangeChartLegends() { if (size > 0) { ArraySort(pids); for (i=0; i < size; i++) { - name = CHARTLEGEND_PREFIX + pids[i] +"."+ __ExecutionContext[EC.chart]; + name = StringConcatenate(CHARTLEGEND_PREFIX, pids[i], ".", __ExecutionContext[EC.chart]); ObjectSet(name, OBJPROP_CORNER, CORNER_TOP_LEFT); ObjectSet(name, OBJPROP_XDISTANCE, xDist); ObjectSet(name, OBJPROP_YDISTANCE, yDist + i*lineHeight); diff --git a/mql40/include/rsf/indicators/chartinfos/init.mqh b/mql40/include/rsf/indicators/chartinfos/init.mqh index ac036f1f8..8c0ed5f5d 100644 --- a/mql40/include/rsf/indicators/chartinfos/init.mqh +++ b/mql40/include/rsf/indicators/chartinfos/init.mqh @@ -124,13 +124,14 @@ int afterInit() { positions.showAbsProfits = true; } else { + hWndDesktop = GetDesktopWindow(); + // register an order event listener if (mode.intern && Track.Orders) { - hWndDesktop = GetDesktopWindow(); orderTracker.key = "rsf::order-tracker::"+ GetAccountNumber() +"::"; - string name = orderTracker.key + StrToLower(Symbol()); - int counter = Max(GetWindowPropertyA(hWndDesktop, name), 0) + 1; - SetWindowPropertyA(hWndDesktop, name, counter); + string property = orderTracker.key + StrToLower(Symbol()); + int counter = Max(GetWindowPropertyA(hWndDesktop, property), 0) + 1; + SetWindowPropertyA(hWndDesktop, property, counter); } // setup a chart ticker diff --git a/mql40/include/rsf/stddefines.mqh b/mql40/include/rsf/stddefines.mqh index d6bf9fe6d..71699b804 100644 --- a/mql40/include/rsf/stddefines.mqh +++ b/mql40/include/rsf/stddefines.mqh @@ -275,6 +275,10 @@ double INF; // 1.#INF | #define CLR_CLOSED Orange // exit marker +// prefix of chart legend labels, used by indicators and experts for ato-positioning +#define CHARTLEGEND_PREFIX "rsf.Legend." + + // timeseries identifiers, see ArrayCopySeries(), iLowest(), iHighest() #define MODE_OPEN 0 // open price #define MODE_LOW 1 // low price diff --git a/mql40/include/rsf/stdfunctions.mqh b/mql40/include/rsf/stdfunctions.mqh index 3fb33fb0f..27e5457f4 100644 --- a/mql40/include/rsf/stdfunctions.mqh +++ b/mql40/include/rsf/stdfunctions.mqh @@ -1237,7 +1237,7 @@ string ifString(bool condition, string thenValue, string elseValue) { /** - * Inlined color OR statement. Returns the first color or the second color if the first color is CLR_NONE. + * Inlined OR statement for colors. Returns the first value or the second one if the first value is CLR_NONE. * * @param color value * @param color altValue @@ -1252,7 +1252,7 @@ color colorOr(color value, color altValue) { /** - * Inlined integer OR statement. Returns the first parameter or the second parameter if the first parameter evaluates to NULL. + * Inlined OR statement for integers. Returns the first value or the second one if the first value evaluates to NULL. * * @param int value * @param int altValue @@ -1267,7 +1267,7 @@ int intOr(int value, int altValue) { /** - * Inlined double OR statement. Returns the first parameter or the second parameter if the first parameter evaluates to NULL. + * Inlined OR statement for doubles. Returns the first value or the second one if the first value evaluates to NULL. * * @param double value * @param double altValue @@ -1282,7 +1282,7 @@ double doubleOr(double value, double altValue) { /** - * Inlined string OR statement. Returns the first parameter or the second parameter if the first parameter evaluates to empty. + * Inlined OR statement for strings. Returns the first value or the second one if the first value evaluates to empty. * * @param string value * @param string altValue @@ -2598,14 +2598,39 @@ int TimeYearEx(datetime time) { /** - * Count the number of non-weekend days between two times. This function doesn't account for Holidays. + * Count the number of days covering two times. * - * @param datetime from - start time (the whole day is included in the calculation) - * @param datetime to - end time (the whole day is included in the calculation) + * @param datetime from - start time + * @param datetime to - end time * - * @return int + * @return int - number of days (min 1) or EMPTY (-1) in case of errors + */ +int CountDays(datetime from, datetime to) { + if (from > to) { + return(_EMPTY(catch("CountDays(1) illegal time range of parameters from="+ TimeToStr(from, TIME_FULL) +" / to="+ TimeToStr(to, TIME_FULL), ERR_INVALID_PARAMETER))); + } + + datetime startDate = from - from % DAYS; + datetime endDate = to - to % DAYS; + + int days = (endDate - startDate) / DAYS; + return(days + 1); +} + + +/** + * Count the number of weekdays (Monday-Fridy) covering two times. + * + * @param datetime from - start time + * @param datetime to - end time + * + * @return int - number of weekdays or EMPTY (-1) in case of errors */ -int CountWorkdays(datetime from, datetime to) { +int CountWeekdays(datetime from, datetime to) { + if (from > to) { + return(_EMPTY(catch("CountWeekdays(1) illegal time range of parameters from="+ TimeToStr(from, TIME_FULL) +" / to="+ TimeToStr(to, TIME_FULL), ERR_INVALID_PARAMETER))); + } + datetime startDate = from - from % DAYS; int startDow = TimeDayOfWeekEx(startDate); if (startDow == SAT) { startDate += 2*DAYS; startDow = MON; } @@ -2616,17 +2641,14 @@ int CountWorkdays(datetime from, datetime to) { if (endDow == SAT) { endDate -= 1*DAY; endDow = FRI; } else if (endDow == SUN) { endDate -= 2*DAYS; endDow = FRI; } - int workdays = 0; - if (startDate <= endDate) { - int days = (endDate-startDate)/DAYS + 1; - workdays = days/7 * 5; - days %= 7; - if (days > 0) { - if (endDow < startDow) days -= 2; - workdays += days; - } + int days = CountDays(startDate, endDate); + int weekdays = days/7 * 5; + days %= 7; + if (days > 0) { + if (endDow < startDow) days -= 2; + weekdays += days; } - return(workdays); + return(weekdays); } @@ -6221,7 +6243,7 @@ string ShellExecuteErrorDescription(int error) { * @return bool - success status */ bool SendChartCommand(string cmdObject, string cmd, string mutex = "") { - if (!StringLen(mutex)) { + if (mutex == "") { mutex = StringConcatenate("mutex.", cmdObject); // generate mutex if needed } if (!AquireLock(mutex)) return(false); // aquire write-lock @@ -6658,8 +6680,9 @@ void __DummyCalls() { ColorToStr(NULL); CompareDoubles(NULL, NULL); CopyMemory(NULL, NULL, NULL); + CountDays(NULL, NULL); CountDecimals(NULL); - CountWorkdays(NULL, NULL); + CountWeekdays(NULL, NULL); CreateDirectory(NULL, NULL); CreateString(NULL); DateTime1(NULL); diff --git a/mql40/include/rsf/structs/OrderExecution.mqh b/mql40/include/rsf/structs/OrderExecution.mqh index 13b7133c3..a7954537d 100644 --- a/mql40/include/rsf/structs/OrderExecution.mqh +++ b/mql40/include/rsf/structs/OrderExecution.mqh @@ -25,8 +25,8 @@ * int profit; // 4 => oe[21] // profit amount in hundredths of 1 account currency unit (get/set return account currency units) * szchar comment[28]; // 28 => oe[22] // order comment terminated by * int duration; // 4 => oe[29] // duration of the execution in milliseconds - * int requotes; // 4 => oe[30] // number of requotes occurred - * int slippage; // 4 => oe[31] // slippage occurred in MQL points (get/set return quote units) + * int requotes; // 4 => oe[30] // number of requotes + * int slippage; // 4 => oe[31] // slippage in MQL points (get/set return quote units) * int remainingTicket; // 4 => oe[32] // additionally created ticket after partial close * int remainingLots; // 4 => oe[33] // remaining order volume in hundredths of 1 lot after partial close (get/set return lots) * } oe; // 136 byte = int[34] diff --git a/mql40/indicators/ChartInfos.mq4 b/mql40/indicators/ChartInfos.mq4 index b5810a1b9..700c88f7e 100644 --- a/mql40/indicators/ChartInfos.mq4 +++ b/mql40/indicators/ChartInfos.mq4 @@ -110,7 +110,7 @@ double config.terms[][5]; // @see Custom // data of configured custom positions: size(config.sData) == size(config.dData) == number-of-configured-custom-positions string config.sData[][2]; // data: {Key, Comment} -double config.dData[][8]; // data: {BemEnabled, MfaeEnabled, MfaeSignal, MfeMark, MfeValueM, MaeValueM, MaxLots, MaxRisk} +double config.dData[][6]; // data: {BemEnabled, MfaeEnabled, MfaeSignal, MfeMark, MfeValueM, MaeValueM} // indexes of config.sData[] #define I_CONFIG_KEY 0 // @@ -121,17 +121,14 @@ double config.dData[][8]; // data: {BemE #define I_MFAE_ENABLED 1 // whether to track MFE/MAE of a custom position #define I_MFAE_SIGNAL 2 // whether to signal new MFE/MAE of a custom position #define I_MARK_MFE 3 // whether to mark MFE levels -#define I_PROFIT_MFE 4 // MFE in money -#define I_PROFIT_MAE 5 // MAE in money -#define I_MAX_LOTS 6 // -#define I_MAX_RISK 7 // +#define I_PROFIT_MFE 4 // current MFE maximum in money +#define I_PROFIT_MAE 5 // current MAE minimum in money // displayed custom position entries (may be larger than configured entries) double positions.data[][17]; // data: {ConfigLine, CustomType, PositionType, DirectionalLots, HedgedLots, PipDistance|BreakevenPrice, AdjustedProfit, TotalProfitM, TotalProfitPct, MfePrice, MfePct, MaePrice, MaePct, ProfitMarkerPrice, ProfitMarkerPct, LossMarkerPrice, LossMarkerPct} bool positions.analyzed; // bool positions.showAbsProfits; // for column adjustment (default: online=FALSE, tester=TRUE) bool positions.showMfae; // for column adjustment: whether at least one active config entry has the MFAE tracker enabled -bool positions.showMaxRisk; // default: FALSE #define CUSTOM_REAL_POSITION 1 // config line types: real position #define CUSTOM_VIRTUAL_POSITION 2 // virtual position (pure virtual or composite virtual/real) @@ -334,10 +331,6 @@ bool onCommand(string cmd, string params, int keys) { if (!CustomPositions.ToggleProfits()) return(false); } - else if (cmd == "toggle-risk") { - if (!CustomPositions.ToggleRisk()) return(false); - } - else if (cmd == "trade-account") { string key = StrReplace(params, ",", ":"); if (!InitTradeAccount(key)) return(false); @@ -977,17 +970,6 @@ string OrderMarkerText(int type, int magic, string comment) { } -/** - * Toggle the MaxLots/MaxRisk display of custom positions. - * - * @return bool - success status - */ -bool CustomPositions.ToggleRisk() { - positions.showMaxRisk = !positions.showMaxRisk; // toggle status and update positions - return(UpdatePositions()); -} - - /** * Toggle PnL amounts of custom positions between "absolute" und "percentage". * @@ -1462,9 +1444,6 @@ bool UpdatePositions() { sProfitMinMax = StringConcatenate("(", sProfitMin, "/", sProfitMax, ")"); } sComment = config.sData[configLine][I_CONFIG_COMMENT]; - if (positions.showMaxRisk) { - sComment = StringConcatenate("(", NumberToStr(config.dData[configLine][I_MAX_LOTS], ".+"), " lot/R?%) ", sComment); - } } // history only @@ -1842,8 +1821,9 @@ bool UpdateStopoutLevel() { if (OrderSymbol() != Symbol()) continue; if (OrderType() == OP_BUY) longPosition += OrderLots(); // Gesamtposition je Richtung aufaddieren else shortPosition += OrderLots(); - if (!isPendings) /*&&*/ if (OrderStopLoss() || OrderTakeProfit()) // Pendings-Status tracken + if (!isPendings) /*&&*/ if (OrderStopLoss() || OrderTakeProfit()) { // Pendings-Status tracken isPendings = true; + } sortKeys[n][0] = OrderOpenTime(); // Sortierschlόssel der Tickets auslesen sortKeys[n][1] = OrderTicket(); @@ -1927,25 +1907,21 @@ bool UpdateStopoutLevel() { if (line >= 0) { if (lineSkipped) { - // if the line is skipped we can reset a set MFE/MAE signaling flag + // if the line gets skipped reset tracked MFAE signal levels int hWnd; - string sEvent = ""; - if (config.dData[line][I_PROFIT_MFE] != 0) { - hWnd = ifInt(__isTesting, __ExecutionContext[EC.chart], GetDesktopWindow()); - sEvent = GetMfaeSignalKey(config.sData[line][I_CONFIG_KEY], I_PROFIT_MFE); - SetWindowPropertyA(hWnd, sEvent, 0); + string property = ""; + if (!__isTesting && config.dData[line][I_PROFIT_MFE]) { + property = GetMfaeSignalKey(config.sData[line][I_CONFIG_KEY], I_PROFIT_MFE); + SetWindowPropertyA(hWndDesktop, property, 0); } - if (config.dData[line][I_PROFIT_MAE] != 0) { - hWnd = ifInt(__isTesting, __ExecutionContext[EC.chart], GetDesktopWindow()); - sEvent = GetMfaeSignalKey(config.sData[line][I_CONFIG_KEY], I_PROFIT_MAE); - SetWindowPropertyA(hWnd, sEvent, 0); + if (!__isTesting && config.dData[line][I_PROFIT_MAE]) { + property = GetMfaeSignalKey(config.sData[line][I_CONFIG_KEY], I_PROFIT_MAE); + SetWindowPropertyA(hWndDesktop, property, 0); } // reset existing stats config.dData[line][I_PROFIT_MFE] = 0; config.dData[line][I_PROFIT_MAE] = 0; - config.dData[line][I_MAX_LOTS ] = 0; - config.dData[line][I_MAX_RISK ] = 0; } else { positions.showMfae = positions.showMfae || config.dData[line][I_MFAE_ENABLED]; @@ -2360,7 +2336,7 @@ int SearchLfxTicket(int ticket) { bool CustomPositions.ReadConfig() { double confTerms[][5]; ArrayResize(confTerms, 0); if (ArrayRange(confTerms, 1) != ArrayRange(config.terms, 1)) return(!catch("CustomPositions.ReadConfig(1) array mis-match config.terms[] / confTerms[]", ERR_INCOMPATIBLE_ARRAY)); string confsData[][2]; ArrayResize(confsData, 0); if (ArrayRange(confsData, 1) != ArrayRange(config.sData, 1)) return(!catch("CustomPositions.ReadConfig(2) array mis-match config.sData[] / confsData[]", ERR_INCOMPATIBLE_ARRAY)); - double confdData[][8]; ArrayResize(confdData, 0); if (ArrayRange(confdData, 1) != ArrayRange(config.dData, 1)) return(!catch("CustomPositions.ReadConfig(3) array mis-match config.dData[] / confdData[]", ERR_INCOMPATIBLE_ARRAY)); + double confdData[][6]; ArrayResize(confdData, 0); if (ArrayRange(confdData, 1) != ArrayRange(config.dData, 1)) return(!catch("CustomPositions.ReadConfig(3) array mis-match config.dData[] / confdData[]", ERR_INCOMPATIBLE_ARRAY)); // parse configuration string keys[], values[], iniValue="", sValue="", comment="", confComment="", openComment="", hstComment="", sNull, symbol=Symbol(), stdSymbol=StdSymbol(); @@ -2689,8 +2665,6 @@ bool CustomPositions.ReadConfig() { confdData[i][I_PROFIT_MFE] = config.dData[n][I_PROFIT_MFE]; confdData[i][I_PROFIT_MAE] = config.dData[n][I_PROFIT_MAE]; } - confdData[i][I_MAX_LOTS] = config.dData[n][I_MAX_LOTS]; - confdData[i][I_MAX_RISK] = config.dData[n][I_MAX_RISK]; break; } } @@ -3895,6 +3869,7 @@ bool StoreCustomPosition(bool isVirtual, double longPosition, double shortPositi totalProfit = NormalizeDouble(totalProfit, 2); if (configLine >= 0) { + // theoretically there can be a single tick with new MFE/MAE which should be signaled if configured config.dData[configLine][I_PROFIT_MFE] = MathMax(totalProfit, config.dData[configLine][I_PROFIT_MFE]); config.dData[configLine][I_PROFIT_MAE] = MathMin(totalProfit, config.dData[configLine][I_PROFIT_MAE]); positions.data[n][I_PROFIT_MFE_PCT] = MathDiv(config.dData[configLine][I_PROFIT_MFE], equity100Pct) * 100; @@ -3970,13 +3945,12 @@ bool StoreCustomPosition(bool isVirtual, double longPosition, double shortPositi markMfe = false; if (configLine >= 0) { - isNewMfe = (config.dData[configLine][I_MFAE_SIGNAL] && config.dData[configLine][I_PROFIT_MFE] && totalProfit > config.dData[configLine][I_PROFIT_MFE]); - isNewMae = (config.dData[configLine][I_MFAE_SIGNAL] && config.dData[configLine][I_PROFIT_MAE] && totalProfit < config.dData[configLine][I_PROFIT_MAE]); markMfe = (config.dData[configLine][I_MFAE_ENABLED] && config.dData[configLine][I_MARK_MFE]); + isNewMfe = (config.dData[configLine][I_MFAE_SIGNAL] && totalProfit > config.dData[configLine][I_PROFIT_MFE]); + isNewMae = (config.dData[configLine][I_MFAE_SIGNAL] && totalProfit < config.dData[configLine][I_PROFIT_MAE]); - config.dData[configLine][I_PROFIT_MFE] = MathMax(totalProfit, config.dData[configLine][I_PROFIT_MFE]); - config.dData[configLine][I_PROFIT_MAE] = MathMin(totalProfit, config.dData[configLine][I_PROFIT_MAE]); - config.dData[configLine][I_MAX_LOTS ] = MathMax(totalPosition, config.dData[configLine][I_MAX_LOTS ]); + config.dData[configLine][I_PROFIT_MFE] = MathMax(totalProfit, config.dData[configLine][I_PROFIT_MFE]); + config.dData[configLine][I_PROFIT_MAE] = MathMin(totalProfit, config.dData[configLine][I_PROFIT_MAE]); positions.data[n][I_PROFIT_MFE_PCT] = MathDiv(config.dData[configLine][I_PROFIT_MFE], equity100Pct) * 100; positions.data[n][I_PROFIT_MAE_PCT] = MathDiv(config.dData[configLine][I_PROFIT_MAE], equity100Pct) * 100; @@ -4086,13 +4060,12 @@ bool StoreCustomPosition(bool isVirtual, double longPosition, double shortPositi markMfe = false; if (configLine >= 0) { - isNewMfe = (config.dData[configLine][I_MFAE_SIGNAL] && config.dData[configLine][I_PROFIT_MFE] && totalProfit > config.dData[configLine][I_PROFIT_MFE]); - isNewMae = (config.dData[configLine][I_MFAE_SIGNAL] && config.dData[configLine][I_PROFIT_MAE] && totalProfit < config.dData[configLine][I_PROFIT_MAE]); markMfe = (config.dData[configLine][I_MFAE_ENABLED] && config.dData[configLine][I_MARK_MFE]); + isNewMfe = (config.dData[configLine][I_MFAE_SIGNAL] && totalProfit > config.dData[configLine][I_PROFIT_MFE]); + isNewMae = (config.dData[configLine][I_MFAE_SIGNAL] && totalProfit < config.dData[configLine][I_PROFIT_MAE];); - config.dData[configLine][I_PROFIT_MFE] = MathMax(totalProfit, config.dData[configLine][I_PROFIT_MFE]); - config.dData[configLine][I_PROFIT_MAE] = MathMin(totalProfit, config.dData[configLine][I_PROFIT_MAE]); - config.dData[configLine][I_MAX_LOTS ] = MathMax(-totalPosition, config.dData[configLine][I_MAX_LOTS ]); + config.dData[configLine][I_PROFIT_MFE] = MathMax(totalProfit, config.dData[configLine][I_PROFIT_MFE]); + config.dData[configLine][I_PROFIT_MAE] = MathMin(totalProfit, config.dData[configLine][I_PROFIT_MAE]); positions.data[n][I_PROFIT_MFE_PCT] = MathDiv(config.dData[configLine][I_PROFIT_MFE], equity100Pct) * 100; positions.data[n][I_PROFIT_MAE_PCT] = MathDiv(config.dData[configLine][I_PROFIT_MAE], equity100Pct) * 100; @@ -4530,7 +4503,7 @@ bool AnalyzePos.ProcessLfxProfits() { /** - * Store the runtime status. + * Store runtime status. * - in the chart: for init cycles and terminal restart * - in the chart window: for loading of templates * @@ -4552,29 +4525,17 @@ bool StoreStatus() { SetWindowIntegerA(__ExecutionContext[EC.chart], key, iValue); // chart window Chart.StoreInt(key, iValue); // chart - // bool positions.showMaxRisk - key = indicatorName +".positions.showMaxRisk"; - iValue = ifInt(positions.showMaxRisk, 1, -1); // GetWindowInteger() cannot restore integer 0 - SetWindowIntegerA(__ExecutionContext[EC.chart], key, iValue); // chart window - Chart.StoreInt(key, iValue); // chart - // risk/MFE/MAE stats of custom positions - string keys="", configKey=""; + string keys = ""; int size = ArrayRange(config.sData, 0); for (int i=0; i < size; i++) { - configKey = config.sData[i][I_CONFIG_KEY]; - key = indicatorName +"."+ Symbol() +".config."+ configKey +".risk"; - sValue = NumberToStr(config.dData[i][I_MAX_LOTS], ".1+") +"|"+ NumberToStr(config.dData[i][I_MAX_RISK], ".1+"); - SetWindowStringA(__ExecutionContext[EC.chart], key, sValue); // chart window - Chart.StoreString(key, sValue); // chart - if (config.dData[i][I_MFAE_ENABLED] > 0) { - key = indicatorName +"."+ Symbol() +".config."+ configKey +".mfae"; + key = indicatorName +"."+ Symbol() +".config."+ config.sData[i][I_CONFIG_KEY] +".mfae"; sValue = NumberToStr(config.dData[i][I_PROFIT_MFE], ".1+") +"|"+ NumberToStr(config.dData[i][I_PROFIT_MAE], ".1+"); SetWindowStringA(__ExecutionContext[EC.chart], key, sValue); // chart window Chart.StoreString(key, sValue); // chart } - keys = keys +"="+ configKey; // config keys can't contain equal signs "=" + keys = keys +"="+ config.sData[i][I_CONFIG_KEY]; // config keys can't contain equal signs "=" } // config keys of custom positions @@ -4625,13 +4586,6 @@ bool RestoreStatus() { Chart.RestoreInt(key, iValue2); positions.showAbsProfits = (iValue1==1 || iValue2==1); - // bool positions.showMaxRisk - key = indicatorName +".positions.showMaxRisk"; - iValue1 = RemoveWindowIntegerA(__ExecutionContext[EC.chart], key); // +1 || -1 - iValue2 = 0; - Chart.RestoreInt(key, iValue2); - positions.showMaxRisk = (iValue1==1 || iValue2==1); - // config keys of custom positions string configKeys[]; key = indicatorName +"."+ Symbol() +".config.keys"; @@ -4650,14 +4604,6 @@ bool RestoreStatus() { config.sData[i][I_CONFIG_KEY ] = configKeys[i]; config.sData[i][I_CONFIG_COMMENT] = ""; - key = indicatorName +"."+ Symbol() +".config."+ configKeys[i] +".risk"; - sValue1 = RemoveWindowStringA(__ExecutionContext[EC.chart], key); - sValue2 = ""; - Chart.RestoreString(key, sValue2); - if (!StringLen(sValue1)) sValue1 = sValue2; - config.dData[i][I_MAX_LOTS] = StrToDouble(StrLeftTo(sValue1, "|")); - config.dData[i][I_MAX_RISK] = StrToDouble(StrRightFrom(sValue1, "|")); - key = indicatorName +"."+ Symbol() +".config."+ configKeys[i] +".mfae"; sValue1 = RemoveWindowStringA(__ExecutionContext[EC.chart], key); sValue2 = ""; @@ -5018,11 +4964,11 @@ bool onNewMFE(string configKey, double profit) { // skip the signal if it was already processed elsewhere // sound: once per system if (!__isTesting) { - int hWndDesktop = GetDesktopWindow(); - string propertyName = GetMfaeSignalKey(configKey, I_PROFIT_MFE) +"|sound"; + string property = GetMfaeSignalKey(configKey, I_PROFIT_MFE); + int iStored = GetWindowPropertyA(hWndDesktop, property); - if (GetWindowPropertyA(hWndDesktop, propertyName) < iProfit) { - SetWindowPropertyA(hWndDesktop, propertyName, iProfit); + if (iProfit > iStored) { + SetWindowPropertyA(hWndDesktop, property, iProfit); PlaySoundEx("Beacon.wav"); } } @@ -5051,11 +4997,11 @@ bool onNewMAE(string configKey, double profit) { // skip the signal if it was already processed elsewhere // sound: once per system if (!__isTesting) { - int hWndDesktop = GetDesktopWindow(); - string propertyName = GetMfaeSignalKey(configKey, I_PROFIT_MAE) +"|sound"; + string property = GetMfaeSignalKey(configKey, I_PROFIT_MAE); + int iStored = GetWindowPropertyA(hWndDesktop, property); - if (GetWindowPropertyA(hWndDesktop, propertyName) > iProfit) { - SetWindowPropertyA(hWndDesktop, propertyName, iProfit); + if (iProfit < iStored) { + SetWindowPropertyA(hWndDesktop, property, iProfit); PlaySoundEx("Windows Ping.wav"); } } @@ -5064,10 +5010,10 @@ bool onNewMAE(string configKey, double profit) { /** - * Return the window properties key for a MFE/MAE event. + * Return the window property name for a new MFE/MAE event. * * @param string configKey - configuration key of the custom position - * @param int type - resulting key type: I_PROFIT_MFE | I_PROFIT_MAE + * @param int type - event type: I_PROFIT_MFE | I_PROFIT_MAE * * @return string - properties key or an empty string in case of errors */ diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index b8ab7d2b4..01569d1f9 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -658,7 +658,7 @@ int onTick() { else fontColor = Red; string name = shortName + ifString(eventType==EVENT_REVERSAL_UP, ".up.", ".down.") + TimeToStr(eventTime); ObjectCreateRegister(name, OBJ_TEXT, 0, eventTime, eventPrice-markerOffset); - ObjectSetText(name, NumberToStr(balance/pUnit, ",'R."+ pDigits), fontSize, fontName, fontColor); + ObjectSetText(name, NumberToStr(balance/pUnit, ",'R.0"), fontSize, fontName, fontColor); // reset positive balances if (balance > -HalfPoint) balance = 0; @@ -882,7 +882,7 @@ int FindPrevSemaphore(int bar, int &type) { /** - * Update buffers after a SINGLE or DOUBLE upper band crossing at the specified bar offset. Resolves the preceeding ZigZag + * Update buffers after an upper band crossing at the specified bar offset. Resolves the preceeding ZigZag * semaphore and counts the trend forward from there. * * @param int bar - offset @@ -932,14 +932,14 @@ bool ProcessUpperCross(int bar) { sema2 = sema1; sema1 = Low[prevSem]; lastLegHigh = 0; // reset last leg high - if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_LONG); + if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_LONG, upperCross[bar]); } return(true); } /** - * Update buffers after a SINGLE or DOUBLE lower band crossing at the specified bar offset. Resolves the preceeding ZigZag + * Update buffers after a lower band crossing at the specified bar offset. Resolves the preceeding ZigZag * semaphore and counts the trend forward from there. * * @param int bar - offset @@ -990,7 +990,7 @@ bool ProcessLowerCross(int bar) { sema1 = High[prevSem]; lastLegLow = 0; // reset last leg low - if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_SHORT); + if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_SHORT, lowerCross[bar]); } return(true); } @@ -1024,11 +1024,12 @@ void UpdateTrend(int fromBar, int fromValue, int toBar, bool resetReversalBuffer /** * Event handler signaling new ZigZag reversals. * - * @param int direction - reversal direction: D_LONG | D_SHORT + * @param int direction - reversal direction: D_LONG | D_SHORT + * @param double level - the crossed price level causing the signal * * @return bool - success status */ -bool onReversal(int direction) { +bool onReversal(int direction, double level) { if (direction!=D_LONG && direction!=D_SHORT) return(!catch("onReversal(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); if (!__isChart) return(true); if (IsPossibleDataPumping()) return(true); // skip signals during possible data pumping @@ -1036,7 +1037,7 @@ bool onReversal(int direction) { // skip the signal if it was already processed elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +"(P="+ ZigZag.Periods +").onReversal("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; - string message1 = ifString(direction==D_LONG, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; + string message1 = ifString(direction==D_LONG, "up", "down") +" (level: "+ NumberToStr(level, PriceFormat) +")"; string message2 = Symbol() +","+ sPeriod +": "+ WindowExpertName() +"("+ ZigZag.Periods +") reversal "+ message1; int hWndTerminal=GetTerminalMainWindow(), hWndDesktop=GetDesktopWindow(); @@ -1291,7 +1292,7 @@ bool SetIndicatorOptions(bool redraw = false) { redraw = redraw!=0; string name = ProgramName(), donchianName=""; - indicatorName = name +"("+ ifString(ZigZag.Periods.Step, "step:", "") + ZigZag.Periods +")"; + indicatorName = name +"("+ ZigZag.Periods + ifString(ZigZag.Periods.Step, ":"+ ZigZag.Periods.Step, "") +")"; shortName = name +"("+ ZigZag.Periods +")"; donchianName = "Donchian("+ ZigZag.Periods +")"; IndicatorShortName(shortName); diff --git a/mql40/libraries/rsfMT4Expander.dll b/mql40/libraries/rsfMT4Expander.dll index 2a0cf31c5..f9738aa4e 100644 Binary files a/mql40/libraries/rsfMT4Expander.dll and b/mql40/libraries/rsfMT4Expander.dll differ diff --git a/mql40/libraries/rsfStdlib.mq4 b/mql40/libraries/rsfStdlib.mq4 index 9d98de187..17c3580f8 100644 --- a/mql40/libraries/rsfStdlib.mq4 +++ b/mql40/libraries/rsfStdlib.mq4 @@ -4980,7 +4980,7 @@ int OrderSendEx(string symbol/*=NULL*/, int type, double lots, double price, int * @return string */ string OrderSendEx.SuccessMsg(int oe[]) { - // opened #1 Buy 0.5 GBPUSD "SR.1234.+1" at 1.5524'8[ instead of 1.5522'0], sl=1.5500'0, tp=1.5600'0 (slippage: -2.8 pip, market: Bid/Ask) after 0.345 s and 1 requote + // opened #1 Buy 0.5 GBPUSD "SR.1234.+1" at 1.5524'8[ instead of 1.5522'0], sl=1.5500'0, tp=1.5600'0 (worse|better: -2.8 pip, market: Bid/Ask) after 0.345 s and 1 requote int digits = MathMax(oe.Digits(oe), 2); int pipDigits = digits & (~1); @@ -5010,7 +5010,7 @@ string OrderSendEx.SuccessMsg(int oe[]) { if (NE(slippage, 0, digits)) { sPrice = sPrice +" instead of "+ NumberToStr(ifDouble(oe.Type(oe)==OP_SELL, oe.Bid(oe), oe.Ask(oe)), priceFormat); sSlippage = NumberToStr(slippage/pUnit, "+."+ pDigits) + spUnit; - sSlippage = "slippage: "+ sSlippage +", "; + sSlippage = ifString(GT(slippage, 0, digits), "better: ", "worse: ") + sSlippage +", "; } string message = "opened #"+ oe.Ticket(oe) +" "+ sType +" "+ sLots +" "+ symbol + sComment +" at "+ sPrice; if (NE(oe.StopLoss (oe), 0)) message = message +", sl="+ NumberToStr(oe.StopLoss(oe), priceFormat); @@ -5475,8 +5475,8 @@ bool OrderCloseEx(int ticket, double lots, int slippage, color markerColor, int oe.setCommission(oe, OrderCommission()); oe.setProfit (oe, OrderProfit()); oe.setRequotes (oe, requotes); - if (OrderType() == OP_BUY) double dSlippage = oe.Bid(oe) - OrderClosePrice(); - else dSlippage = OrderClosePrice() - oe.Ask(oe); + if (OrderType() == OP_BUY) double dSlippage = OrderClosePrice() - oe.Bid(oe); + else dSlippage = oe.Ask(oe) - OrderClosePrice(); oe.setSlippage(oe, NormalizeDouble(dSlippage, digits)); // find a remaining position @@ -5571,7 +5571,7 @@ bool OrderCloseEx(int ticket, double lots, int slippage, color markerColor, int * @return string */ string OrderCloseEx.SuccessMsg(int oe[]) { - // closed #1 Buy 0.6 GBPUSD "SR.1234.+2" from 1.5520'0 [partially] at 1.5534'4[ instead of 1.5532'2][, remainder: #2 Buy 0.1 GBPUSD] ([slippage: -2.8 pip, ]market: Bid/Ask) after 0.123 s and 1 requote + // closed #1 Buy 0.6 GBPUSD "SR.1234.+2" from 1.5520'0 [partially] at 1.5534'4[ instead of 1.5532'2][, remainder: #2 Buy 0.1 GBPUSD] ([worse|better: -2.8 pip, ]market: Bid/Ask) after 0.123 s and 1 requote int digits = MathMax(oe.Digits(oe), 2); int pipDigits = digits & (~1); @@ -5603,7 +5603,7 @@ string OrderCloseEx.SuccessMsg(int oe[]) { if (NE(slippage, 0, digits)) { sClosePrice = sClosePrice +" instead of "+ NumberToStr(ifDouble(!oe.Type(oe), oe.Bid(oe), oe.Ask(oe)), priceFormat); sSlippage = NumberToStr(slippage/pUnit, "+."+ pDigits) + spUnit; - sSlippage = "slippage: "+ sSlippage +", "; + sSlippage = ifString(GT(slippage, 0, digits), "better: ", "worse: ") + sSlippage +", "; } int remainder = oe.RemainingTicket(oe); string message = "closed #"+ oe.Ticket(oe) +" "+ sType +" "+ sLots +" "+ symbol + comment +" from "+ sOpenPrice + ifString(!remainder, "", " partially") +" at "+ sClosePrice; diff --git a/mql40/scripts/Chart.ToggleAccountBalance.mq4 b/mql40/scripts/Chart.ToggleAccountBalance.mq4 index 437d5bdf7..0052fc6f3 100644 --- a/mql40/scripts/Chart.ToggleAccountBalance.mq4 +++ b/mql40/scripts/Chart.ToggleAccountBalance.mq4 @@ -1,7 +1,7 @@ /** * Chart.ToggleAccountBalance * - * Send a command to the ChartInfos indicator to toggle the display of the account balance. + * Sends a command to the ChartInfos indicator in the current chart to toggle display of the account balance. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -16,7 +16,9 @@ int __DeinitFlags[]; * @return int - error status */ int onStart() { - string modifiers = ""; + string command = "toggle-account-balance"; + string params = ""; + string modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key @@ -25,7 +27,10 @@ int onStart() { if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - SendChartCommand("ChartInfos.command", "toggle-account-balance::"+ StrRight(modifiers, -1)); + command = command +":"+ params +":"+ modifiers; + + SendChartCommand("ChartInfos.command", command); return(catch("onStart(1)")); } diff --git a/mql40/scripts/Chart.ToggleOpenOrders.mq4 b/mql40/scripts/Chart.ToggleOpenOrders.mq4 index 6bf063e01..9b1e474e7 100644 --- a/mql40/scripts/Chart.ToggleOpenOrders.mq4 +++ b/mql40/scripts/Chart.ToggleOpenOrders.mq4 @@ -1,7 +1,7 @@ /** * Chart.ToggleOpenOrders * - * Send a command to a running EA or the ChartInfos indicator to toggle the display of open orders. + * Sends a command to EA or ChartInfos indicator in the current chart to toggle the display of open orders. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,14 +18,15 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string command = "toggle-open-orders"; - string params = ""; - string modifiers = ifString(IsVirtualKeyDown(VK_SHIFT), "VK_SHIFT", ""); + int virtKeys = GetPressedVirtualKeys(F_VK_ALL); + string command = "toggle-open-orders::"+ virtKeys; - command = command +":"+ params +":"+ modifiers; + bool isEA = (ObjectFind("EA.status") == 0); + bool isShiftKey = (virtKeys & F_VK_SHIFT && 1); + bool isWinKey = (virtKeys & F_VK_LWIN && 1); - // send to a running EA or the ChartInfos indicator - if (ObjectFind("EA.status") == 0) SendChartCommand("EA.command", command); - else SendChartCommand("ChartInfos.command", command); + // send the command to an existing EA or the chart + if (isEA && !isShiftKey && !isWinKey) SendChartCommand("EA.command", command); + else SendChartCommand("ChartInfos.command", command); return(last_error); } diff --git a/mql40/scripts/Chart.ToggleTradeHistory.mq4 b/mql40/scripts/Chart.ToggleTradeHistory.mq4 index d23be0f55..cad36ec4a 100644 --- a/mql40/scripts/Chart.ToggleTradeHistory.mq4 +++ b/mql40/scripts/Chart.ToggleTradeHistory.mq4 @@ -1,7 +1,7 @@ /** * Chart.ToggleTradeHistory * - * Send a command to a running EA or the ChartInfos indicator to toggle the display of the trade history. + * Sends a command to EA or ChartInfos indicator in the current chart to toggle the display of closed trades. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -19,14 +19,15 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string command = "toggle-trade-history"; - string params = ""; - string modifiers = ifString(IsVirtualKeyDown(VK_SHIFT), "VK_SHIFT", ""); + int virtKeys = GetPressedVirtualKeys(F_VK_ALL); + string command = "toggle-trade-history::"+ virtKeys; - command = command +":"+ params +":"+ modifiers; + bool isEA = (ObjectFind("EA.status") == 0); + bool isShiftKey = (virtKeys & F_VK_SHIFT && 1); + bool isWinKey = (virtKeys & F_VK_LWIN && 1); - // send to a running EA or the ChartInfos indicator - if (ObjectFind("EA.status") == 0) SendChartCommand("EA.command", command); - else SendChartCommand("ChartInfos.command", command); + // send the command to an existing EA or the chart + if (isEA && !isShiftKey && !isWinKey) SendChartCommand("EA.command", command); + else SendChartCommand("ChartInfos.command", command); return(last_error); } diff --git a/mql40/scripts/Chart.ToggleUnitSize.mq4 b/mql40/scripts/Chart.ToggleUnitSize.mq4 index 89e46b094..b5a47b2d0 100644 --- a/mql40/scripts/Chart.ToggleUnitSize.mq4 +++ b/mql40/scripts/Chart.ToggleUnitSize.mq4 @@ -1,7 +1,8 @@ /** * Chart.ToggleUnitSize * - * Sends a command to the ChartInfos indicator to toggle the location of the unitsize display between "top" and "bottom". + * Sends a command to the ChartInfos indicator in the current chart to toggle the "unitsize" location between + * "top" and "bottom". */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -16,7 +17,9 @@ int __DeinitFlags[]; * @return int - error status */ int onStart() { - string modifiers = ""; + string command = "toggle-unit-size"; + string params = ""; + string modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key @@ -25,7 +28,10 @@ int onStart() { if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - SendChartCommand("ChartInfos.command", "toggle-unit-size::"+ StrRight(modifiers, -1)); + command = command +":"+ params +":"+ modifiers; + + SendChartCommand("ChartInfos.command", command); return(catch("onStart(1)")); } diff --git a/mql40/scripts/CustomPositions.LogOrders.mq4 b/mql40/scripts/CustomPositions.LogOrders.mq4 index 05dbd0545..e4f6005e8 100644 --- a/mql40/scripts/CustomPositions.LogOrders.mq4 +++ b/mql40/scripts/CustomPositions.LogOrders.mq4 @@ -1,7 +1,7 @@ /** * CustomPositions.LogOrders * - * Send a command to the ChartInfos indicator to log all order tickets of custom positions. + * Sends a command to the ChartInfos indicator in the current chart to log all order tickets of custom positions. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,7 +18,16 @@ int __DeinitFlags[]; int onStart() { string command = "log-custom-positions"; string params = ""; - string modifiers = ifString(IsVirtualKeyDown(VK_SHIFT), "VK_SHIFT", ""); + string modifiers = ","; + if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; + if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; + if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (IsVirtualKeyDown(VK_SHIFT)) modifiers = modifiers +",VK_SHIFT"; + if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; + if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key + if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; + if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); command = command +":"+ params +":"+ modifiers; diff --git a/mql40/scripts/CustomPositions.ToggleProfit.mq4 b/mql40/scripts/CustomPositions.ToggleProfit.mq4 index a63cf88fa..0d4a955d0 100644 --- a/mql40/scripts/CustomPositions.ToggleProfit.mq4 +++ b/mql40/scripts/CustomPositions.ToggleProfit.mq4 @@ -1,6 +1,8 @@ /** - * Schickt dem ChartInfos-Indikator des aktuellen Charts die Nachricht, die Anzeige der PL-Betrδge der Positionen von - * "absolut" zu "prozentual" umzuschaltem. + * CustomPositions.ToggleProfit + * + * Sends a command to the ChartInfos indicator in the current chart to toggle displayed profits of custom positions between + * absolute and percentage values. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -10,11 +12,26 @@ int __DeinitFlags[]; /** - * Main-Funktion + * Main function * * @return int - error status */ int onStart() { - SendChartCommand("ChartInfos.command", "toggle-profit"); + string command = "toggle-profit"; + string params = ""; + string modifiers = ","; + if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; + if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; + if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (IsVirtualKeyDown(VK_SHIFT)) modifiers = modifiers +",VK_SHIFT"; + if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; + if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key + if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; + if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); + + command = command +":"+ params +":"+ modifiers; + + SendChartCommand("ChartInfos.command", command); return(catch("onStart(1)")); } diff --git a/mql40/scripts/CustomPositions.ToggleRisk.mq4 b/mql40/scripts/CustomPositions.ToggleRisk.mq4 deleted file mode 100644 index e69cd4acb..000000000 --- a/mql40/scripts/CustomPositions.ToggleRisk.mq4 +++ /dev/null @@ -1,21 +0,0 @@ -/** - * CustomPositions.ToggleRisk - * - * Send a command to the ChartInfos indicator to toggle the MaxRisk display of custom positions. - */ -#include -int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; -int __DeinitFlags[]; -#include -#include - - -/** - * Main function - * - * @return int - error status - */ -int onStart() { - SendChartCommand("ChartInfos.command", "toggle-risk"); - return(catch("onStart(1)")); -} diff --git a/mql40/scripts/EA.EntrySignal.mq4 b/mql40/scripts/EA.EntrySignal.mq4 new file mode 100644 index 000000000..4bb59e7c9 --- /dev/null +++ b/mql40/scripts/EA.EntrySignal.mq4 @@ -0,0 +1,44 @@ +/** + * EA.EntrySignal + * + * Sends a simulated "entry signal" to an EA in the current chart. + */ +#include +int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; +int __DeinitFlags[]; +#include +#include + + +/** + * Main function + * + * @return int - error status + */ +int onStart() { + // supporting EAs maintain a chart object holding the instance id and the status + string sid="", status="", label="EA.status"; + bool isActive = false; + + // check chart for an active EA + if (ObjectFind(label) == 0) { + string text = StrTrim(ObjectDescription(label)); // format: {sid}|{status} + sid = StrLeftTo(text, "|"); + status = StrRightFrom(text, "|"); + isActive = (status!="" && status!="undefined"); + } + + if (isActive) { + if (__isTesting) Tester.Pause(); + + PlaySoundEx("Windows Notify.wav"); // confirm sending the command + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to send an \"entry-signal\" to the EA?", MB_ICONQUESTION|MB_OKCANCEL); + if (button != IDOK) return(catch("onStart(1)")); + SendChartCommand("EA.command", "entry-signal"); + } + else { + PlaySoundEx("Windows Chord.wav"); + MessageBoxEx(ProgramName(), "No EA found.", MB_ICONEXCLAMATION|MB_OK); + } + return(catch("onStart(2)")); +} diff --git a/mql40/scripts/EA.Resume.mq4 b/mql40/scripts/EA.Resume.mq4 index cb2a5dcf0..628e93370 100644 --- a/mql40/scripts/EA.Resume.mq4 +++ b/mql40/scripts/EA.Resume.mq4 @@ -1,7 +1,7 @@ /** * EA.Resume * - * Send a "resume" command to a running EA. + * Send a "resume" command to an EA in the current chart. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to resume EA instance "+ sid +"?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to resume the EA?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "resume"); } @@ -42,10 +42,3 @@ int onStart() { } return(catch("onStart(2)")); } - - - - - - - diff --git a/mql40/scripts/EA.Start Long.mq4 b/mql40/scripts/EA.Start Long.mq4 index 9748ea603..c218a2a36 100644 --- a/mql40/scripts/EA.Start Long.mq4 +++ b/mql40/scripts/EA.Start Long.mq4 @@ -1,7 +1,7 @@ /** * EA.Start Long * - * Send a "start:long" command to a running EA. + * Send a "start:long" command to an EA in the current chart. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to start EA instance "+ sid +" long?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want the EA to go long?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "start:long"); } diff --git a/mql40/scripts/EA.Start Short.mq4 b/mql40/scripts/EA.Start Short.mq4 index 31260da57..f77e3e184 100644 --- a/mql40/scripts/EA.Start Short.mq4 +++ b/mql40/scripts/EA.Start Short.mq4 @@ -1,7 +1,7 @@ /** * EA.Start Short * - * Send a "start:short" command to a running EA. + * Send a "start:short" command to an EA in the current chart. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to start EA instance "+ sid +" short?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want the EA to go short?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "start:short"); } diff --git a/mql40/scripts/EA.Start.mq4 b/mql40/scripts/EA.Start.mq4 index 3af240525..62045da18 100644 --- a/mql40/scripts/EA.Start.mq4 +++ b/mql40/scripts/EA.Start.mq4 @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to start EA instance "+ sid +"?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to start the EA?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "start"); } diff --git a/mql40/scripts/EA.Stop.mq4 b/mql40/scripts/EA.Stop.mq4 index 781898d7e..cce275663 100644 --- a/mql40/scripts/EA.Stop.mq4 +++ b/mql40/scripts/EA.Stop.mq4 @@ -1,7 +1,7 @@ /** * EA.Stop * - * Send a "stop" command to a running EA. + * Sends a "stop" command to an EA in the current chart. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to stop EA instance "+ sid +"?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to stop the EA?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "stop"); } @@ -42,10 +42,3 @@ int onStart() { } return(catch("onStart(2)")); } - - - - - - - diff --git a/mql40/scripts/EA.ToggleMetrics.mq4 b/mql40/scripts/EA.ToggleMetrics.mq4 index bf1011c55..6b0bc34f9 100644 --- a/mql40/scripts/EA.ToggleMetrics.mq4 +++ b/mql40/scripts/EA.ToggleMetrics.mq4 @@ -1,7 +1,7 @@ /** * EA.ToggleMetrics * - * Send a command to a running EA to toggle the status between calculated/displayed metrics. + * Sends a command to an EA in the current chart to toggle displayed status between available metrics. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,7 +18,16 @@ int __DeinitFlags[]; int onStart() { string command = "toggle-metrics"; string params = ""; - string modifiers = ifString(IsVirtualKeyDown(VK_SHIFT), "VK_SHIFT", ""); + string modifiers = ","; + if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; + if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; + if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (IsVirtualKeyDown(VK_SHIFT)) modifiers = modifiers +",VK_SHIFT"; + if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; + if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key + if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; + if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); command = command +":"+ params +":"+ modifiers; diff --git a/mql40/scripts/EA.TogglePercent.mq4 b/mql40/scripts/EA.TogglePercent.mq4 new file mode 100644 index 000000000..c26704d2a --- /dev/null +++ b/mql40/scripts/EA.TogglePercent.mq4 @@ -0,0 +1,39 @@ +/** + * EA.TogglePercent + * + * Sends a command to an EA in the current chart to toggle displayed profits between absolute and percentage values. + */ +#include +int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; +int __DeinitFlags[]; +#include +#include + + +/** + * Main function + * + * @return int - error status + */ +int onStart() { + string command = "toggle-percent"; + string params = ""; + string modifiers = ","; + if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; + if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; + if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (IsVirtualKeyDown(VK_SHIFT)) modifiers = modifiers +",VK_SHIFT"; + if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; + if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key + if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; + if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); + + command = command +":"+ params +":"+ modifiers; + + // send to an active EA + if (ObjectFind("EA.status") == 0) { + SendChartCommand("EA.command", command); + } + return(last_error); +} diff --git a/mql40/scripts/EA.Wait.mq4 b/mql40/scripts/EA.Wait.mq4 index 0558cdd21..21efafddb 100644 --- a/mql40/scripts/EA.Wait.mq4 +++ b/mql40/scripts/EA.Wait.mq4 @@ -32,7 +32,7 @@ int onStart() { if (__isTesting) Tester.Pause(); PlaySoundEx("Windows Notify.wav"); // confirm sending the command - int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to enable status \"waiting\" on EA instance "+ sid +"?", MB_ICONQUESTION|MB_OKCANCEL); + int button = MessageBoxEx(ProgramName(), ifString(IsDemoFix(), "", "- Real Account -\n\n") +"Do you really want to switch the EA to status \"wait\"?", MB_ICONQUESTION|MB_OKCANCEL); if (button != IDOK) return(catch("onStart(1)")); SendChartCommand("EA.command", "wait"); } diff --git a/mql40/scripts/ParameterStepper.Down.mq4 b/mql40/scripts/ParameterStepper.Down.mq4 index fc9bb1a24..5c4880ae4 100644 --- a/mql40/scripts/ParameterStepper.Down.mq4 +++ b/mql40/scripts/ParameterStepper.Down.mq4 @@ -1,7 +1,7 @@ /** * ParameterStepper Down * - * Send a command to listening programs to decrease a program-specific parameter. + * Sends a command to listening programs to decrease a program-specific parameter. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,7 +18,9 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string modifiers = ""; + string command = "parameter"; + string params = "down"; + string modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key @@ -27,7 +29,10 @@ int onStart() { if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - SendChartCommand("ParameterStepper.command", "parameter:down:"+ StrRight(modifiers, -1)); + command = command +":"+ params +":"+ modifiers; + + SendChartCommand("ParameterStepper.command", command); return(catch("onStart(1)")); } diff --git a/mql40/scripts/ParameterStepper.Up.mq4 b/mql40/scripts/ParameterStepper.Up.mq4 index 31545ff1f..7cf16137f 100644 --- a/mql40/scripts/ParameterStepper.Up.mq4 +++ b/mql40/scripts/ParameterStepper.Up.mq4 @@ -1,7 +1,7 @@ /** * ParameterStepper Up * - * Send a command to listening programs to increase a program-specific parameter. + * Sends a command to listening programs to increase a program-specific parameter. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,7 +18,9 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string modifiers = ""; + string command = "parameter"; + string params = "up"; + string modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key @@ -27,7 +29,10 @@ int onStart() { if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - SendChartCommand("ParameterStepper.command", "parameter:up:"+ StrRight(modifiers, -1)); + command = command +":"+ params +":"+ modifiers; + + SendChartCommand("ParameterStepper.command", command); return(catch("onStart(1)")); } diff --git a/mql40/scripts/SuperBars.TimeframeDown.mq4 b/mql40/scripts/SuperBars.TimeframeDown.mq4 index be76b6081..2540b5f84 100644 --- a/mql40/scripts/SuperBars.TimeframeDown.mq4 +++ b/mql40/scripts/SuperBars.TimeframeDown.mq4 @@ -1,7 +1,7 @@ /** * SuperBars Down * - * Send a command to the SuperBars indicator to switch to the next lower timeframe. + * Sends a command to the SuperBars indicator in the current chart to switch to the next lower timeframe. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,22 +18,29 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string modifiers = ""; + bool isVkShift = IsVirtualKeyDown(VK_SHIFT); + string command = "", params = "", modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (isVkShift) modifiers = modifiers +",VK_SHIFT"; if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - if (IsVirtualKeyDown(VK_SHIFT)) { - modifiers = modifiers +",VK_SHIFT"; - SendChartCommand("TrendBars.command", "barwidth:decrease:"+ StrRight(modifiers, -1)); + if (isVkShift) { + command = "barwidth"; + params = "decrease"; + command = command +":"+ params +":"+ modifiers; + SendChartCommand("TrendBars.command", command); } else { - SendChartCommand("SuperBars.command", "timeframe:down:"+ StrRight(modifiers, -1)); + command = "timeframe"; + params = "down"; + command = command +":"+ params +":"+ modifiers; + SendChartCommand("SuperBars.command", command); } return(catch("onStart(1)")); } - diff --git a/mql40/scripts/SuperBars.TimeframeUp.mq4 b/mql40/scripts/SuperBars.TimeframeUp.mq4 index 82507cb98..eb84f9188 100644 --- a/mql40/scripts/SuperBars.TimeframeUp.mq4 +++ b/mql40/scripts/SuperBars.TimeframeUp.mq4 @@ -1,7 +1,7 @@ /** * SuperBars Up * - * Send a command to the SuperBars indicator to switch to the next higher timeframe. + * Sends a command to the SuperBars indicator in the current chart to switch to the next higher timeframe. */ #include int __InitFlags[] = {INIT_NO_BARS_REQUIRED}; @@ -18,21 +18,29 @@ int __DeinitFlags[]; int onStart() { if (__isTesting) Tester.Pause(); - string modifiers = ""; + bool isVkShift = IsVirtualKeyDown(VK_SHIFT); + string command = "", params = "", modifiers = ","; if (IsVirtualKeyDown(VK_ESCAPE)) modifiers = modifiers +",VK_ESCAPE"; if (IsVirtualKeyDown(VK_TAB)) modifiers = modifiers +",VK_TAB"; if (IsVirtualKeyDown(VK_CAPITAL)) modifiers = modifiers +",VK_CAPITAL"; // CAPSLOCK key + if (isVkShift) modifiers = modifiers +",VK_SHIFT"; if (IsVirtualKeyDown(VK_CONTROL)) modifiers = modifiers +",VK_CONTROL"; if (IsVirtualKeyDown(VK_MENU)) modifiers = modifiers +",VK_MENU"; // ALT key if (IsVirtualKeyDown(VK_LWIN)) modifiers = modifiers +",VK_LWIN"; if (IsVirtualKeyDown(VK_RWIN)) modifiers = modifiers +",VK_RWIN"; + modifiers = StrRight(modifiers, -1); - if (IsVirtualKeyDown(VK_SHIFT)) { - modifiers = modifiers +",VK_SHIFT"; - SendChartCommand("TrendBars.command", "barwidth:increase:"+ StrRight(modifiers, -1)); + if (isVkShift) { + command = "barwidth"; + params = "increase"; + command = command +":"+ params +":"+ modifiers; + SendChartCommand("TrendBars.command", command); } else { - SendChartCommand("SuperBars.command", "timeframe:up:"+ StrRight(modifiers, -1)); + command = "timeframe"; + params = "up"; + command = command +":"+ params +":"+ modifiers; + SendChartCommand("SuperBars.command", command); } return(catch("onStart(1)")); } diff --git a/templates4/03 Default (FF Brackets).tpl b/templates4/03 Default (FF Brackets).tpl index f0b913717..40476a81f 100644 --- a/templates4/03 Default (FF Brackets).tpl +++ b/templates4/03 Default (FF Brackets).tpl @@ -1,6 +1,6 @@ - diff --git a/templates4/04 Default (US Brackets).tpl b/templates4/04 Default (US Brackets).tpl index b28fa505a..79b5dbebf 100644 --- a/templates4/04 Default (US Brackets).tpl +++ b/templates4/04 Default (US Brackets).tpl @@ -1,5 +1,5 @@ - diff --git a/templates4/17 ZigZag(50,30) + Donchian.tpl b/templates4/17 ZigZag(50,30) + Donchian.tpl new file mode 100644 index 000000000..998c124c9 --- /dev/null +++ b/templates4/17 ZigZag(50,30) + Donchian.tpl @@ -0,0 +1,191 @@ + + + +symbol=GBPUSD +period=60 +digits=5 + +leftpos=9229 +scale=2 +graph=1 +fore=0 +grid=0 +volume=0 +ohlc=0 +askline=0 +days=0 +descriptions=1 +scroll=1 +shift=1 +shift_size=10 + +fixed_pos=620 +window_left=0 +window_top=0 +window_right=1292 +window_bottom=812 +window_type=3 + +background_color=16316664 +foreground_color=0 +barup_color=30720 +bardown_color=210 +bullcandle_color=30720 +bearcandle_color=210 +chartline_color=11119017 +volumes_color=30720 +grid_color=14474460 +askline_color=11823615 +stops_color=17919 + + +height=5000 +fixed_height=0 + + +name=main + + + +name=Custom Indicator + +name=Grid +flags=347 +window_num=0 + +show_data=0 + + + +name=Custom Indicator + +name=ChartInfos +flags=347 +window_num=0 + +show_data=0 + + + +name=Custom Indicator + +name=SuperBars +flags=339 +window_num=0 + +show_data=0 + + + +name=Custom Indicator + +name=Inside Bars +flags=339 +window_num=0 + +Timeframe=H1 +NumberOfInsideBars=3 + + +period_flags=3 +show_data=0 + + + +name=Custom Indicator + +name=Moving Average +flags=339 + +MA.Method=SMA | LWMA | EMA* | SMMA | ALMA +MA.Periods=144 +MA.Periods.Step=0 +Draw.Type=Line* | Dot +Draw.Width=3 +UpTrend.Color=65535 +DownTrend.Color=65535 +Background.Color=11119017 +ShowChartLegend=1 +AutoConfiguration=0 + + +show_data=1 + + + +name=Custom Indicator + +name=ZigZag +flags=339 +window_num=0 + +ZigZag.Periods=50 +ZigZag.Type=Lines* | Semaphores +ZigZag.Width=0 +ZigZag.Color=16711935 +Donchian.ShowChannel=0 +Donchian.Channel.UpperColor=16711680 +Donchian.Channel.LowerColor=255 +Donchian.ShowCrossings=off | first* | all +Donchian.Crossing.Width=2 +Signal.onReversal=1 +Signal.onReversal.Types=sound* | alert* | mail | sms +Signal.onBreakout=0 +Sound.onChannelWidening=0 + + +style_2=2 +style_3=2 +show_data=1 + + + +name=Custom Indicator + +name=ZigZag +flags=339 +window_num=0 + +ZigZag.Periods=30 +ZigZag.Width=0 +Donchian.ShowChannel=1 +Donchian.Channel.UpperColor=16711680 +Donchian.Channel.LowerColor=16711935 +Donchian.ShowCrossings=off | first* | all +Donchian.Crossing.Symbol=dot* | narrow-ring | ring | bold-ring +Donchian.Crossing.Width=1 +Signal.onReversal=1 +Signal.onReversal.Types=sound* | alert* | mail | sms +Signal.onBreakout=0 +Sound.onChannelWidening=0 + + +style_2=2 +style_3=2 +color_6=4294967295 +color_7=4294967295 +show_data=1 + + + +name=Custom Indicator + +name=Donchian Channel Width +flags=339 +window_num=1 + +Donchian.Periods=30 + + +levels_color=16316664 +levels_style=2 +level_0=200 + +period_flags=0 +show_data=1 + + + + diff --git a/templates4/23 ZigZag(10xM5).tpl b/templates4/23 ZigZag(10xM5).tpl index 1aba1c1d7..400f49266 100644 --- a/templates4/23 ZigZag(10xM5).tpl +++ b/templates4/23 ZigZag(10xM5).tpl @@ -1,6 +1,6 @@ - diff --git a/templates4/24 ZigZag(10xM5) + NLMA.tpl b/templates4/24 ZigZag(10xM5) + NLMA.tpl index 3e76bc9b1..3cbcfebe2 100644 --- a/templates4/24 ZigZag(10xM5) + NLMA.tpl +++ b/templates4/24 ZigZag(10xM5) + NLMA.tpl @@ -1,6 +1,6 @@ -