2026-02-15 05:13:55 +00:00
|
|
|
#ifndef UTILITIES_MQH
|
|
|
|
|
#define UTILITIES_MQH
|
|
|
|
|
|
|
|
|
|
string ModeToString(const int mode)
|
|
|
|
|
{
|
|
|
|
|
if(mode == MODE_TRENDING) return "TRENDING";
|
|
|
|
|
if(mode == MODE_RANGING) return "RANGING";
|
|
|
|
|
return "CHOP";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string StateToString(const int state)
|
|
|
|
|
{
|
|
|
|
|
if(state == STATE_CONTINUATION) return "CONTINUATION";
|
|
|
|
|
if(state == STATE_REVERSAL) return "REVERSAL";
|
|
|
|
|
return "PULLBACK";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double MidPrice()
|
|
|
|
|
{
|
|
|
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
|
|
|
return (bid + ask) * 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NearLevel(const double price, const double level, const double tolPoints)
|
|
|
|
|
{
|
|
|
|
|
return (MathAbs(price - level) <= tolPoints * _Point);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StochCrossUp()
|
|
|
|
|
{
|
|
|
|
|
return (Stoch_K_Previous < Stoch_D_Current && Stoch_K_Current >= Stoch_D_Current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StochCrossDown()
|
|
|
|
|
{
|
|
|
|
|
return (Stoch_K_Previous > Stoch_D_Current && Stoch_K_Current <= Stoch_D_Current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Bull_MA50_Reclaimed()
|
|
|
|
|
{
|
|
|
|
|
bool reclaimed = (MA_Previous[0] <= MA_Previous[3]) && (MA_Current[0] > MA_Current[3]);
|
|
|
|
|
double low0 = iLow(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
|
double close0 = iClose(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
|
bool rejected = (low0 <= MA_Current[3] && close0 > MA_Current[3] && (StochCrossUp() || Stoch_K_Current > 50));
|
|
|
|
|
return (reclaimed || rejected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Bear_MA50_Reclaimed()
|
|
|
|
|
{
|
|
|
|
|
bool reclaimed = (MA_Previous[0] >= MA_Previous[3]) && (MA_Current[0] < MA_Current[3]);
|
|
|
|
|
double high0 = iHigh(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
|
double close0 = iClose(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
|
bool rejected = (high0 >= MA_Current[3] && close0 < MA_Current[3] && (StochCrossDown() || Stoch_K_Current < 50));
|
|
|
|
|
return (reclaimed || rejected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ApplySymbolProfile()
|
|
|
|
|
{
|
2026-02-19 20:05:27 +00:00
|
|
|
// MQL5 StringToUpper mutates its argument.
|
|
|
|
|
string s = _Symbol;
|
|
|
|
|
StringToUpper(s);
|
2026-02-15 05:13:55 +00:00
|
|
|
|
|
|
|
|
Symbol_Profile_Name = "MANUAL";
|
|
|
|
|
Symbol_SL_Points = Manual_SL_Points;
|
|
|
|
|
Symbol_TP_Points = Manual_TP_Points;
|
|
|
|
|
|
|
|
|
|
if(!Use_Auto_Symbol_Config)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Initialized = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(StringFind(s, "XAU") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "XAUUSD";
|
|
|
|
|
Symbol_SL_Points = 500;
|
|
|
|
|
Symbol_TP_Points = 3500;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "US30") >= 0 || StringFind(s, "DJI") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "US30";
|
|
|
|
|
Symbol_SL_Points = 150;
|
|
|
|
|
Symbol_TP_Points = 1000;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "NAS100") >= 0 || StringFind(s, "USTEC") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "NAS100";
|
|
|
|
|
Symbol_SL_Points = 200;
|
|
|
|
|
Symbol_TP_Points = 1400;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "USOIL") >= 0 || StringFind(s, "WTI") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "USOIL";
|
|
|
|
|
Symbol_SL_Points = 80;
|
|
|
|
|
Symbol_TP_Points = 600;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "BTC") >= 0 || StringFind(s, "ETH") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "CRYPTO";
|
|
|
|
|
Symbol_SL_Points = 800;
|
|
|
|
|
Symbol_TP_Points = 6000;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "JPY") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "JPY";
|
|
|
|
|
Symbol_SL_Points = 60;
|
|
|
|
|
Symbol_TP_Points = 450;
|
|
|
|
|
}
|
|
|
|
|
else if(StringFind(s, "CAD") >= 0)
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "COMMODITY_FX";
|
|
|
|
|
Symbol_SL_Points = 40;
|
|
|
|
|
Symbol_TP_Points = 300;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Symbol_Profile_Name = "MAJOR_FX";
|
|
|
|
|
Symbol_SL_Points = 50;
|
|
|
|
|
Symbol_TP_Points = 350;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Symbol_Profile_Initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string FamilyToText(const int mode)
|
|
|
|
|
{
|
|
|
|
|
if(mode == MODE_TRENDING) return "Trending";
|
|
|
|
|
if(mode == MODE_RANGING) return "Ranging";
|
|
|
|
|
if(mode == MODE_CHOP) return "Chop";
|
|
|
|
|
return "Unknown";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string BiasToText(const int bias)
|
|
|
|
|
{
|
|
|
|
|
if(bias > 0) return "Bull";
|
|
|
|
|
if(bias < 0) return "Bear";
|
|
|
|
|
return "Neutral";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string StrengthToText(const double conf)
|
|
|
|
|
{
|
|
|
|
|
if(conf >= 70.0) return "Strong";
|
|
|
|
|
if(conf >= 55.0) return "Confirmed";
|
|
|
|
|
return "Weak";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double NormalizePrice(const double price)
|
|
|
|
|
{
|
|
|
|
|
return NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double NormalizeLots(double lots)
|
|
|
|
|
{
|
|
|
|
|
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
|
|
|
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
|
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
|
|
|
|
|
|
if(lot_step <= 0.0) lot_step = 0.01;
|
2026-07-14 16:19:12 -05:00
|
|
|
if(min_lot <= 0.0) min_lot = lot_step;
|
|
|
|
|
if(max_lot <= 0.0) max_lot = min_lot;
|
|
|
|
|
|
|
|
|
|
// Snap down onto the symbol's step grid first (epsilon guards float
|
|
|
|
|
// error), then clamp into the broker's allowed range so the result can
|
|
|
|
|
// never end up below VOLUME_MIN or off-grid.
|
|
|
|
|
lots = MathFloor(lots / lot_step + 0.0000001) * lot_step;
|
2026-02-15 05:13:55 +00:00
|
|
|
lots = MathMax(min_lot, MathMin(max_lot, lots));
|
2026-07-14 16:19:12 -05:00
|
|
|
|
|
|
|
|
// Round to the step's own precision: 0.001-step, 0.01-step and
|
|
|
|
|
// whole-lot symbols all normalize correctly.
|
|
|
|
|
int step_digits = 0;
|
|
|
|
|
double step = lot_step;
|
|
|
|
|
while(step_digits < 8 && MathAbs(step - MathRound(step)) > 0.0000001)
|
|
|
|
|
{
|
|
|
|
|
step *= 10.0;
|
|
|
|
|
step_digits++;
|
|
|
|
|
}
|
|
|
|
|
return NormalizeDouble(lots, step_digits);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shrink lots until the position's required margin fits inside free margin
|
|
|
|
|
// (90% cap keeps a buffer for floating swings). Returns 0.0 when even the
|
|
|
|
|
// broker's minimum lot cannot be afforded — caller must skip the trade
|
|
|
|
|
// instead of sending an order the server will reject with "not enough money".
|
|
|
|
|
double FitLotsToMargin(const bool is_buy, double lots, const double price)
|
|
|
|
|
{
|
|
|
|
|
ENUM_ORDER_TYPE type = is_buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
|
|
|
|
|
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE) * 0.90;
|
|
|
|
|
|
|
|
|
|
double margin = 0.0;
|
|
|
|
|
if(!OrderCalcMargin(type, _Symbol, lots, price, margin) || margin <= 0.0)
|
|
|
|
|
return lots; // margin unknown — let the server be the judge
|
|
|
|
|
|
|
|
|
|
if(margin <= free_margin)
|
|
|
|
|
return lots;
|
|
|
|
|
|
|
|
|
|
// Scale straight down to the affordable size, then re-grid it.
|
|
|
|
|
double affordable = NormalizeLots(lots * (free_margin / margin));
|
|
|
|
|
|
|
|
|
|
// NormalizeLots clamps back up to VOLUME_MIN, so re-check that the
|
|
|
|
|
// minimum actually fits; if not, there is no valid size to trade.
|
|
|
|
|
if(OrderCalcMargin(type, _Symbol, affordable, price, margin) && margin > free_margin)
|
|
|
|
|
return 0.0;
|
|
|
|
|
|
|
|
|
|
PrintFormat("FitLotsToMargin: reduced %.2f -> %.2f lots (free margin %.2f)",
|
|
|
|
|
lots, affordable, AccountInfoDouble(ACCOUNT_MARGIN_FREE));
|
|
|
|
|
return affordable;
|
2026-02-15 05:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|