Universal_Quant_EA/Include/UQ_Symbols.mqh
894 lignes
27 Kio
MQL5
BrutLien permanentVue normaleHistorique
//+------------------------------------------------------------------+
//| UQ_Symbols.mqh |
//| Universal Quant EA - symbol resolution and signal engine |
//| |
//| Deterministic volatility-normalised channel breakout: |
//| long : completed close > highest high of the preceding |
//| lookback window AND completed close > EMA |
//| short : completed close < lowest low of the preceding |
//| lookback window AND completed close < EMA |
//| Only completed candles are used; the forming bar is never read |
//| for confirmation. |
//| |
//| Resolution fails closed. An exact catalogue name is used when it |
//| exists; a broker prefix/suffix variant is accepted only when the |
//| broker convention is verified and exactly one eligible |
//| instrument remains; anything else is reported as an error. No |
//| arbitrary candidate is ever chosen. |
//+------------------------------------------------------------------+
#ifndef UQ_SYMBOLS_MQH
#define UQ_SYMBOLS_MQH
#include "UQ_Defs.mqh"
//+------------------------------------------------------------------+
//| Per-symbol runtime state |
//+------------------------------------------------------------------+
struct UQSymbol
{
string requested; // token supplied by the operator
string name; // resolved broker symbol
bool resolved;
int hEma; // EMA trend filter handle
int hAtr; // ATR handle
datetime lastBar; // last observed signal-bar open time
datetime lastEntryBar; // signal bar already acted upon
datetime lastLogTime; // throttling for repeated diagnostics
string lastNote;
};
//+------------------------------------------------------------------+
//| Split a catalogue name around a token: name = prefix+token+suffix |
//| Returns false when the token is absent or the surrounding |
//| extension is implausibly long for a broker prefix/suffix tag. |
//+------------------------------------------------------------------+
bool UQSymbolExtension(const string name, const string token,
string &extPrefix, string &extSuffix)
{
extPrefix = "";
extSuffix = "";
if(StringLen(name) <= 0 || StringLen(token) <= 0)
return(false);
string upperName = name;
string upperToken = token;
StringToUpper(upperName);
StringToUpper(upperToken);
const int pos = StringFind(upperName, upperToken);
if(pos < 0)
return(false);
extPrefix = StringSubstr(name, 0, pos);
extSuffix = StringSubstr(name, pos + StringLen(token));
if(StringLen(extPrefix) + StringLen(extSuffix) > UQ_SYMBOL_EXT_MAX_LEN)
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Standard pairs used to infer this broker's prefix/suffix layout. |
//| The list is fixed so detection stays deterministic across runs. |
//+------------------------------------------------------------------+
string UQProbeSymbol(const int index)
{
switch(index)
{
case 0: return("EURUSD");
case 1: return("GBPUSD");
case 2: return("USDJPY");
case 3: return("USDCHF");
case 4: return("AUDUSD");
case 5: return("USDCAD");
case 6: return("NZDUSD");
case 7: return("EURJPY");
case 8: return("EURGBP");
case 9: return("GBPJPY");
}
return("");
}
//+------------------------------------------------------------------+
//| Verify the broker symbol naming convention. |
//| |
//| A convention is only trusted when several independent standard |
//| pairs agree on the same prefix/suffix, so a single broker name |
//| that happens to contain a pair token can never define it. When |
//| no convention is verified the tag stays empty and the resolver |
//| falls back to an explicit ambiguity error instead of guessing. |
//+------------------------------------------------------------------+
bool UQDetectBrokerTag(string &tagPrefix, string &tagSuffix)
{
tagPrefix = "";
tagSuffix = "";
string keys[];
int counts[];
int distinct = 0;
int totalVote = 0;
const int catalogue = SymbolsTotal(false);
for(int probe = 0; probe < UQ_SYMBOL_PROBE_MAX; probe++)
{
const string token = UQProbeSymbol(probe);
if(StringLen(token) <= 0)
break;
string bestPre = "";
string bestSuf = "";
int bestLen = -1;
int tightest = 0;
for(int i = 0; i < catalogue; i++)
{
const string name = SymbolName(i, false);
string pre = "";
string suf = "";
if(!UQSymbolExtension(name, token, pre, suf))
continue;
const int len = StringLen(pre) + StringLen(suf);
if(bestLen < 0 || len < bestLen)
{
bestLen = len;
bestPre = pre;
bestSuf = suf;
tightest = 1;
continue;
}
if(len == bestLen)
tightest++;
}
//--- a probe with no candidate, or with several equally tight
//--- candidates, cannot vote for a convention
if(tightest != 1)
continue;
const string key = bestPre + "|" + bestSuf;
int slot = -1;
for(int k = 0; k < distinct; k++)
{
if(keys[k] == key)
{
slot = k;
break;
}
}
if(slot < 0)
{
ArrayResize(keys, distinct + 1);
ArrayResize(counts, distinct + 1);
keys[distinct] = key;
counts[distinct] = 0;
slot = distinct;
distinct++;
}
counts[slot]++;
totalVote++;
}
if(distinct <= 0 || totalVote <= 0)
return(false);
int bestVotes = 0;
int best = -1;
for(int k = 0; k < distinct; k++)
{
if(counts[k] > bestVotes)
{
bestVotes = counts[k];
best = k;
}
}
if(best < 0 || bestVotes < UQ_SYMBOL_PROBE_MIN_VOTES)
return(false);
if(bestVotes * 2 <= totalVote)
return(false); // a weak majority is not a verified convention
const string key = keys[best];
const int sep = StringFind(key, "|");
if(sep < 0)
return(false);
tagPrefix = StringSubstr(key, 0, sep);
tagSuffix = StringSubstr(key, sep + 1);
return(true);
}
//+------------------------------------------------------------------+
//| Fail-closed deterministic resolution of one requested symbol. |
//| |
//| 1. unique exact catalogue name, in either letter case |
//| 2. unique eligible broker prefix/suffix variant |
//| 3. the verified broker convention, when it leaves exactly one |
//| 4. otherwise EMPTY / NOT_FOUND / AMBIGUOUS / INELIGIBLE |
//+------------------------------------------------------------------+
ENUM_UQ_RESOLVE UQResolveSymbol(const string requested,
const string tagPrefix, const string tagSuffix,
string &resolved, string &detail)
{
resolved = "";
detail = "";
string token = requested;
StringTrimLeft(token);
StringTrimRight(token);
if(StringLen(token) <= 0)
{
detail = "the requested symbol is empty";
return(UQ_RESOLVE_EMPTY);
}
string tokenUpper = token;
StringToUpper(tokenUpper);
//--- 1) exact catalogue name
string exact = "";
int exactCount = 0;
const int catalogue = SymbolsTotal(false);
for(int i = 0; i < catalogue; i++)
{
const string name = SymbolName(i, false);
if(StringLen(name) <= 0)
continue;
string upper = name;
StringToUpper(upper);
if(upper != tokenUpper)
continue;
exact = name;
exactCount++;
}
if(exactCount > 1)
{
detail = "more than one catalogue symbol matches the request exactly";
return(UQ_RESOLVE_AMBIGUOUS);
}
if(exactCount == 1)
{
SymbolSelect(exact, true);
string why = "";
if(!UQSymbolEligible(exact, why))
{
detail = "'" + exact + "' cannot be traded by this strategy: " + why;
return(UQ_RESOLVE_INELIGIBLE);
}
resolved = exact;
return(UQ_RESOLVE_OK);
}
//--- 2) broker prefix/suffix variants
string candidates[UQ_SYMBOL_CANDIDATE_MAX];
int candidateCount = 0;
for(int i = 0; i < catalogue; i++)
{
const string name = SymbolName(i, false);
if(StringLen(name) <= 0)
continue;
string pre = "";
string suf = "";
if(!UQSymbolExtension(name, token, pre, suf))
continue;
if(StringLen(pre) + StringLen(suf) == 0)
continue; // identical name, handled above
if(candidateCount >= UQ_SYMBOL_CANDIDATE_MAX)
{
detail = "too many broker variants carry the requested token; specify the exact broker symbol name";
return(UQ_RESOLVE_AMBIGUOUS);
}
candidates[candidateCount] = name;
candidateCount++;
}
if(candidateCount <= 0)
{
detail = "no broker symbol carries the requested token";
return(UQ_RESOLVE_NOT_FOUND);
}
//--- only an eligible instrument may be selected
string eligible[UQ_SYMBOL_CANDIDATE_MAX];
int eligibleCount = 0;
string eligibleList = "";
string firstReason = "";
for(int i = 0; i < candidateCount; i++)
{
SymbolSelect(candidates[i], true);
string why = "";
if(UQSymbolEligible(candidates[i], why))
{
eligible[eligibleCount] = candidates[i];
eligibleCount++;
if(StringLen(eligibleList) > 0)
eligibleList += ", ";
eligibleList += candidates[i];
continue;
}
if(StringLen(firstReason) == 0)
firstReason = "'" + candidates[i] + "' cannot be traded by this strategy: " + why;
}
if(eligibleCount == 1)
{
resolved = eligible[0];
return(UQ_RESOLVE_OK);
}
if(eligibleCount <= 0)
{
detail = (StringLen(firstReason) > 0 ? firstReason :
"no eligible FX instrument carries the requested token");
return(UQ_RESOLVE_INELIGIBLE);
}
//--- 3) the verified broker convention breaks the tie
if(StringLen(tagPrefix) + StringLen(tagSuffix) > 0)
{
string tagged[UQ_SYMBOL_CANDIDATE_MAX];
int taggedCount = 0;
string taggedList = "";
for(int i = 0; i < eligibleCount; i++)
{
string pre = "";
string suf = "";
if(!UQSymbolExtension(eligible[i], token, pre, suf))
continue;
if(pre != tagPrefix || suf != tagSuffix)
continue;
tagged[taggedCount] = eligible[i];
taggedCount++;
if(StringLen(taggedList) > 0)
taggedList += ", ";
taggedList += eligible[i];
}
if(taggedCount == 1)
{
resolved = tagged[0];
return(UQ_RESOLVE_OK);
}
if(taggedCount > 1)
{
detail = "the verified broker convention still matches several instruments: " + taggedList;
return(UQ_RESOLVE_AMBIGUOUS);
}
}
detail = "ambiguous request; eligible candidates: " + eligibleList;
return(UQ_RESOLVE_AMBIGUOUS);
}
//+------------------------------------------------------------------+
//| Symbol registry and signal engine |
//+------------------------------------------------------------------+
class CUQSymbols
{
private:
UQSymbol m_sym[];
int m_count;
int m_skipped;
bool m_requireAll;
ENUM_TIMEFRAMES m_tf;
int m_channel;
int m_emaPeriod;
int m_atrPeriod;
string m_tagPrefix;
string m_tagSuffix;
string m_lastError;
public:
CUQSymbols(void);
~CUQSymbols(void);
bool Init(const ENUM_UQ_SYMBOL_MODE mode, const string list,
const ENUM_TIMEFRAMES tf, const int channel,
const int emaPeriod, const int atrPeriod,
const bool requireAll);
void Deinit(void);
int Count(void) const { return(m_count); }
int Skipped(void) const { return(m_skipped); }
string LastError(void) const { return(m_lastError); }
string TagText(void) const;
string Name(const int idx) const;
string Requested(const int idx) const;
int FindByName(const string name) const;
datetime LastEntryBar(const int idx) const;
void SetLastEntryBar(const int idx, const datetime t);
bool EnsureHandles(const int idx);
bool IsNewBar(const int idx, datetime &barTime);
bool EvaluateSignal(const int idx, int &direction, double &atrOut, string &reason);
double AtrValue(const int idx);
void Note(const int idx, const string text, const bool throttle);
};
//+------------------------------------------------------------------+
CUQSymbols::CUQSymbols(void)
{
m_count = 0;
m_skipped = 0;
m_requireAll = true;
m_tf = PERIOD_H1;
m_channel = 20;
m_emaPeriod = 100;
m_atrPeriod = 14;
m_tagPrefix = "";
m_tagSuffix = "";
m_lastError = "";
ArrayResize(m_sym, 0);
}
//+------------------------------------------------------------------+
CUQSymbols::~CUQSymbols(void)
{
Deinit();
}
//+------------------------------------------------------------------+
//| Verified broker prefix/suffix convention, for the status display |
//+------------------------------------------------------------------+
string CUQSymbols::TagText(void) const
{
if(StringLen(m_tagPrefix) + StringLen(m_tagSuffix) == 0)
return("unverified");
return("'" + m_tagPrefix + "<pair>" + m_tagSuffix + "'");
}
//+------------------------------------------------------------------+
//| Build the managed symbol set |
//| |
//| Every configured token is resolved deterministically. A token |
//| that cannot be resolved to exactly one eligible instrument is a |
//| configuration error, never a silently dropped symbol, unless the |
//| operator explicitly allowed a partial set. |
//+------------------------------------------------------------------+
bool CUQSymbols::Init(const ENUM_UQ_SYMBOL_MODE mode, const string list,
const ENUM_TIMEFRAMES tf, const int channel,
const int emaPeriod, const int atrPeriod,
const bool requireAll)
{
m_tf = tf;
m_channel = channel;
m_emaPeriod = emaPeriod;
m_atrPeriod = atrPeriod;
m_requireAll = requireAll;
m_lastError = "";
m_count = 0;
m_skipped = 0;
m_tagPrefix = "";
m_tagSuffix = "";
ArrayResize(m_sym, 0);
//--- the broker convention is inferred once, from a fixed probe set
if(UQDetectBrokerTag(m_tagPrefix, m_tagSuffix))
PrintFormat("[UQ] Broker symbol convention verified: prefix='%s' suffix='%s'.",
m_tagPrefix, m_tagSuffix);
else
PrintFormat("[UQ] Broker symbol convention could not be verified; only exact and unambiguous names are accepted.");
string tokens[];
int nTok = 0;
if(mode == UQ_SYM_CURRENT_CHART)
{
ArrayResize(tokens, 1);
tokens[0] = _Symbol;
nTok = 1;
}
else
{
string src = list;
StringReplace(src, ";", ",");
nTok = StringSplit(src, ',', tokens);
}
if(nTok <= 0)
{
m_lastError = "no symbols are configured";
return(false);
}
for(int i = 0; i < nTok; i++)
{
string t = tokens[i];
StringTrimLeft(t);
StringTrimRight(t);
if(StringLen(t) == 0)
continue;
bool duplicate = false;
for(int k = 0; k < m_count; k++)
{
if(StringCompare(m_sym[k].requested, t, false) == 0)
{
duplicate = true;
break;
}
}
if(duplicate)
{
PrintFormat("[UQ] WARNING: symbol '%s' is configured twice and is ignored the second time.", t);
continue;
}
string resolved = "";
string detail = "";
const ENUM_UQ_RESOLVE outcome = UQResolveSymbol(t, m_tagPrefix, m_tagSuffix, resolved, detail);
if(outcome != UQ_RESOLVE_OK)
{
m_skipped++;
PrintFormat("[UQ] SYMBOL REJECTED '%s': %s (%s).", t, UQResolveText(outcome), detail);
if(m_requireAll)
{
m_lastError = StringFormat("symbol '%s' could not be resolved: %s (%s)",
t, UQResolveText(outcome), detail);
return(false);
}
continue;
}
//--- two tokens may not resolve to the same broker instrument
if(FindByName(resolved) >= 0)
{
PrintFormat("[UQ] WARNING: '%s' resolves to '%s', which is already managed by an earlier token; it is ignored.",
t, resolved);
continue;
}
ArrayResize(m_sym, m_count + 1);
m_sym[m_count].requested = t;
m_sym[m_count].name = resolved;
m_sym[m_count].resolved = true;
m_sym[m_count].hEma = INVALID_HANDLE;
m_sym[m_count].hAtr = INVALID_HANDLE;
m_sym[m_count].lastBar = 0;
m_sym[m_count].lastEntryBar = 0;
m_sym[m_count].lastLogTime = 0;
m_sym[m_count].lastNote = "";
m_count++;
if(resolved == t)
PrintFormat("[UQ] Symbol '%s' resolved exactly to '%s'.", t, resolved);
else
PrintFormat("[UQ] Symbol '%s' resolved to broker symbol '%s'.", t, resolved);
}
if(m_count <= 0)
{
if(StringLen(m_lastError) <= 0)
m_lastError = "no usable symbol could be resolved";
return(false);
}
//--- indicator handles are created once, during initialisation
for(int i = 0; i < m_count; i++)
{
if(!EnsureHandles(i))
{
m_lastError = StringFormat("indicator handles could not be created for '%s'", m_sym[i].name);
return(false);
}
}
return(true);
}
//+------------------------------------------------------------------+
//| Release indicator handles |
//+------------------------------------------------------------------+
void CUQSymbols::Deinit(void)
{
for(int i = 0; i < m_count; i++)
{
if(m_sym[i].hEma != INVALID_HANDLE)
{
IndicatorRelease(m_sym[i].hEma);
m_sym[i].hEma = INVALID_HANDLE;
}
if(m_sym[i].hAtr != INVALID_HANDLE)
{
IndicatorRelease(m_sym[i].hAtr);
m_sym[i].hAtr = INVALID_HANDLE;
}
}
}
//+------------------------------------------------------------------+
string CUQSymbols::Name(const int idx) const
{
if(idx < 0 || idx >= m_count)
return("");
return(m_sym[idx].name);
}
//+------------------------------------------------------------------+
string CUQSymbols::Requested(const int idx) const
{
if(idx < 0 || idx >= m_count)
return("");
return(m_sym[idx].requested);
}
//+------------------------------------------------------------------+
int CUQSymbols::FindByName(const string name) const
{
for(int i = 0; i < m_count; i++)
{
if(m_sym[i].name == name)
return(i);
}
return(-1);
}
//+------------------------------------------------------------------+
datetime CUQSymbols::LastEntryBar(const int idx) const
{
if(idx < 0 || idx >= m_count)
return(0);
return(m_sym[idx].lastEntryBar);
}
//+------------------------------------------------------------------+
void CUQSymbols::SetLastEntryBar(const int idx, const datetime t)
{
if(idx < 0 || idx >= m_count)
return;
m_sym[idx].lastEntryBar = t;
}
//+------------------------------------------------------------------+
//| Create indicator handles on first use and validate readiness |
//+------------------------------------------------------------------+
bool CUQSymbols::EnsureHandles(const int idx)
{
if(idx < 0 || idx >= m_count)
return(false);
const string s = m_sym[idx].name;
if(m_sym[idx].hEma == INVALID_HANDLE)
{
m_sym[idx].hEma = iMA(s, m_tf, m_emaPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(m_sym[idx].hEma == INVALID_HANDLE)
{
Note(idx, StringFormat("EMA handle creation failed (%d)", GetLastError()), true);
return(false);
}
}
if(m_sym[idx].hAtr == INVALID_HANDLE)
{
m_sym[idx].hAtr = iATR(s, m_tf, m_atrPeriod);
if(m_sym[idx].hAtr == INVALID_HANDLE)
{
Note(idx, StringFormat("ATR handle creation failed (%d)", GetLastError()), true);
return(false);
}
}
//--- readiness is decided by the actual buffer copies, not by a cached
//--- BarsCalculated() value, which can stay unset for handles created
//--- inside the strategy tester.
const int emaBars = BarsCalculated(m_sym[idx].hEma);
const int atrBars = BarsCalculated(m_sym[idx].hAtr);
if(emaBars == -1 && atrBars == -1)
Note(idx, StringFormat("indicator buffers still initialising (ema bars=%d, atr bars=%d)",
emaBars, atrBars), true);
return(true);
}
//+------------------------------------------------------------------+
//| New-bar detection. The first observation only seeds the state so |
//| that attaching mid-bar cannot trigger a late historical entry. |
//+------------------------------------------------------------------+
bool CUQSymbols::IsNewBar(const int idx, datetime &barTime)
{
barTime = 0;
if(idx < 0 || idx >= m_count)
return(false);
datetime t[];
if(CopyTime(m_sym[idx].name, m_tf, 0, 1, t) < 1)
return(false);
barTime = t[0];
if(barTime <= 0)
return(false);
if(barTime == m_sym[idx].lastBar)
return(false);
const bool firstObservation = (m_sym[idx].lastBar == 0);
m_sym[idx].lastBar = barTime;
if(firstObservation)
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| ATR of the last completed signal bar |
//+------------------------------------------------------------------+
double CUQSymbols::AtrValue(const int idx)
{
if(idx < 0 || idx >= m_count)
return(0.0);
if(!EnsureHandles(idx))
return(0.0);
double atr[];
ArraySetAsSeries(atr, true);
if(CopyBuffer(m_sym[idx].hAtr, 0, 0, 3, atr) < 3)
return(0.0);
if(!MathIsValidNumber(atr[1]) || atr[1] <= 0.0)
return(0.0);
return(atr[1]);
}
//+------------------------------------------------------------------+
//| Evaluate the breakout signal on completed candles only. |
//| direction: +1 long, -1 short, 0 no signal |
//+------------------------------------------------------------------+
bool CUQSymbols::EvaluateSignal(const int idx, int &direction, double &atrOut, string &reason)
{
direction = 0;
atrOut = 0.0;
reason = "";
if(idx < 0 || idx >= m_count)
{
reason = "invalid symbol index";
return(false);
}
if(!m_sym[idx].resolved)
{
reason = "symbol is not resolved";
return(false);
}
if(!EnsureHandles(idx))
{
reason = "indicators are not ready";
return(false);
}
const string s = m_sym[idx].name;
const int sdig = (int)SymbolInfoInteger(s, SYMBOL_DIGITS);
const int need = m_channel + 5;
double high[], low[], close[];
datetime times[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(times, true);
const int nHigh = CopyHigh(s, m_tf, 0, need, high);
const int nLow = CopyLow(s, m_tf, 0, need, low);
const int nClose = CopyClose(s, m_tf, 0, need, close);
const int nTime = CopyTime(s, m_tf, 0, need, times);
if(nHigh < need || nLow < need || nClose < need || nTime < need)
{
reason = StringFormat("history not ready (high %d, low %d, close %d, time %d, need %d)",
nHigh, nLow, nClose, nTime, need);
return(false);
}
//--- channel of the completed bars preceding the last completed bar
double channelHigh = -DBL_MAX;
double channelLow = DBL_MAX;
for(int i = 2; i <= m_channel + 1; i++)
{
if(!MathIsValidNumber(high[i]) || !MathIsValidNumber(low[i]))
{
reason = "invalid channel price data";
return(false);
}
if(high[i] > channelHigh)
channelHigh = high[i];
if(low[i] < channelLow)
channelLow = low[i];
}
double ema[], atr[];
ArraySetAsSeries(ema, true);
ArraySetAsSeries(atr, true);
if(CopyBuffer(m_sym[idx].hEma, 0, 0, 3, ema) < 3)
{
reason = "EMA buffer is not available";
return(false);
}
if(CopyBuffer(m_sym[idx].hAtr, 0, 0, 3, atr) < 3)
{
reason = "ATR buffer is not available";
return(false);
}
const double lastClose = close[1];
const double emaValue = ema[1];
const double atrValue = atr[1];
if(!MathIsValidNumber(lastClose) || lastClose <= 0.0)
{
reason = "invalid completed close price";
return(false);
}
if(!MathIsValidNumber(emaValue) || emaValue <= 0.0)
{
reason = "invalid EMA value";
return(false);
}
if(!MathIsValidNumber(atrValue) || atrValue <= 0.0)
{
reason = "invalid ATR value";
return(false);
}
atrOut = atrValue;
if(lastClose > channelHigh && lastClose > emaValue)
{
direction = 1;
reason = StringFormat("close %s > channelHigh %s and EMA %s",
DoubleToString(lastClose, sdig),
DoubleToString(channelHigh, sdig),
DoubleToString(emaValue, sdig));
return(true);
}
if(lastClose < channelLow && lastClose < emaValue)
{
direction = -1;
reason = StringFormat("close %s < channelLow %s and EMA %s",
DoubleToString(lastClose, sdig),
DoubleToString(channelLow, sdig),
DoubleToString(emaValue, sdig));
return(true);
}
return(true);
}
//+------------------------------------------------------------------+
//| Throttled per-symbol diagnostic note |
//+------------------------------------------------------------------+
void CUQSymbols::Note(const int idx, const string text, const bool throttle)
{
if(idx < 0 || idx >= m_count)
return;
const datetime now = TimeCurrent();
if(throttle && m_sym[idx].lastNote == text && (now - m_sym[idx].lastLogTime) < 300)
return;
m_sym[idx].lastNote = text;
m_sym[idx].lastLogTime = now;
PrintFormat("[UQ] %s: %s", m_sym[idx].name, text);
}
#endif // UQ_SYMBOLS_MQH
//+------------------------------------------------------------------+