Replace your existing code in MetaEditor with this updated version and press Compile (F7).
Code: Select all
//+------------------------------------------------------------------+
//| WilliamsFractalChaosEA.mq5 |
//| Bill Williams Fractal + Alligator Awake + AO Pyramiding (v1.20)|
//+------------------------------------------------------------------+
#property copyright "Forex Strategy Deep Dive"
#property version "1.20"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
//--- Enum for Alligator Evaluation Bar
enum ENUM_AWAKE_EVAL_MODE
{
AWAKE_AT_SIGNAL_BAR = 0, // Check at Bar 1 (When Fractal Confirms)
AWAKE_AT_FRACTAL_BAR = 1, // Check at Bar 3 (At Fractal Peak/Trough)
AWAKE_AT_BOTH_BARS = 2 // Require Awake State at Both Bar 3 and Bar 1
};
//--- Enum for Awesome Oscillator Filter Modes
enum ENUM_AO_FILTER_MODE
{
AO_FILTER_NONE = 0, // Disabled (Fractals + Alligator Only)
AO_FILTER_ZERO_LINE = 1, // Require AO > 0 (Buy) or AO < 0 (Sell)
AO_FILTER_SAUCER = 2, // Require AO Saucer Pattern (Bullish/Bearish)
AO_FILTER_ZERO_OR_SAUCER= 3 // Require either Zero-Line alignment OR Saucer pattern
};
//--- Input Parameters: Alligator Settings
input group "=== Alligator Core Settings ==="
input int InpJawPeriod = 13; // Jaw Period (Blue)
input int InpJawShift = 8; // Jaw Future Shift
input int InpTeethPeriod = 8; // Teeth Period (Red - Filter)
input int InpTeethShift = 5; // Teeth Future Shift
input int InpLipsPeriod = 5; // Lips Period (Green)
input int InpLipsShift = 3; // Lips Future Shift
input ENUM_MA_METHOD InpAlligatorMethod = MODE_SMMA; // Smoothing Method
input ENUM_APPLIED_PRICE InpAlligatorPrice = PRICE_MEDIAN; // Applied Price ((H+L)/2)
//--- Input Parameters: Alligator 'Awake' Mouth-Width Filter
input group "=== Alligator 'Awake' Filter Settings ==="
input bool InpUseAwakeFilter = true; // Enable Alligator Awake Filter
input ENUM_AWAKE_EVAL_MODE InpAwakeEvalMode = AWAKE_AT_SIGNAL_BAR;// Bar to Check Awake Condition
input bool InpRequireStrictFan = true; // Require Lips/Teeth/Jaw Fan Alignment
input double InpMinMouthWidthPips = 3.0; // Min Total Mouth Width |Lips - Jaw| (Pips)
input double InpMinAdjacentGapPips = 0.8; // Min Gap Between Adjacent Lines (Pips)
input int InpATRPeriod = 14; // ATR Period for Dynamic Width Filter
input double InpMinMouthATRMult = 0.25; // Min Mouth Width as ATR Multiple (0 = Off)
input bool InpDeleteOrderIfSleeping= true; // Cancel Pending Orders if Alligator Sleeps
//--- Input Parameters: Awesome Oscillator (AO) Settings
input group "=== Awesome Oscillator (AO) Settings ==="
input ENUM_AO_FILTER_MODE InpAOFilterMode = AO_FILTER_ZERO_LINE;// AO Filter Mode
input int InpAOFastPeriod = 5; // AO Fast MA Period
input int InpAOSlowPeriod = 34; // AO Slow MA Period
input bool InpAllowPyramiding = true; // Allow Multiple Positions (Pyramiding via New Fractals)
input int InpMaxPyramidTrades = 3; // Max Concurrent Pyramiding Positions
//--- Input Parameters: Fractal & Execution Rules
input group "=== Fractal Breakout & Stop Rules ==="
input double InpEntryBufferPips = 1.5; // Breakout Buffer Above/Below Fractal (Pips)
input double InpSLBufferPips = 2.0; // Stop-Loss Buffer Beyond Opposite Fractal (Pips)
input int InpTrailFractalRank = 2; // Trail Behind N-th Opposite Fractal (1 or 2)
input int InpFractalLookback = 100; // Max Bars to Search for Opposite Fractals
input int InpOrderExpirationHrs = 48; // Pending Order Expiration (Hours, 0 = GTC)
input double InpMinSLDistancePips = 10.0; // Minimum Stop-Loss Distance (Pips)
input double InpMaxSLDistancePips = 150.0; // Maximum Stop-Loss Distance (Pips)
//--- Input Parameters: Risk & Trade Management
input group "=== Risk & Money Management ==="
input double InpRiskPercent = 1.0; // Risk Per Trade (% of Balance, 0 = Fixed Lot)
input double InpFixedLot = 0.10; // Fixed Lot Size (if Risk % is 0)
input double InpRewardRiskRatio = 0.0; // Take Profit R:R Ratio (0 = Disable, Trail Only)
input ulong InpMagicNumber = 199505; // EA Magic Number
input ulong InpSlippagePoints = 10; // Max Slippage (Points)
//--- Global Variables & Handles
CTrade ExtTrade;
CPositionInfo ExtPosition;
COrderInfo ExtOrder;
int ExtFractalsHandle = INVALID_HANDLE;
int ExtAlligatorHandle = INVALID_HANDLE;
int ExtATRHandle = INVALID_HANDLE;
int ExtAOHandle = INVALID_HANDLE;
datetime ExtLastBarTime = 0;
double ExtPipValue = 0.0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ExtTrade.SetExpertMagicNumber(InpMagicNumber);
ExtTrade.SetDeviationInPoints(InpSlippagePoints);
ExtTrade.SetTypeFillingBySymbol(_Symbol);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
ExtPipValue = (digits == 3 || digits == 5) ? _Point * 10.0 : _Point;
//--- Initialize Indicators
ExtFractalsHandle = iFractals(_Symbol, _Period);
if(ExtFractalsHandle == INVALID_HANDLE) { Print("Error iFractals"); return(INIT_FAILED); }
ExtAlligatorHandle = iAlligator(_Symbol, _Period,
InpJawPeriod, InpJawShift,
InpTeethPeriod, InpTeethShift,
InpLipsPeriod, InpLipsShift,
InpAlligatorMethod, InpAlligatorPrice);
if(ExtAlligatorHandle == INVALID_HANDLE) { Print("Error iAlligator"); return(INIT_FAILED); }
ExtATRHandle = iATR(_Symbol, _Period, InpATRPeriod);
if(ExtATRHandle == INVALID_HANDLE) { Print("Error iATR"); return(INIT_FAILED); }
ExtAOHandle = iAO(_Symbol, _Period);
if(ExtAOHandle == INVALID_HANDLE) { Print("Error iAO"); return(INIT_FAILED); }
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(ExtFractalsHandle != INVALID_HANDLE) IndicatorRelease(ExtFractalsHandle);
if(ExtAlligatorHandle != INVALID_HANDLE) IndicatorRelease(ExtAlligatorHandle);
if(ExtATRHandle != INVALID_HANDLE) IndicatorRelease(ExtATRHandle);
if(ExtAOHandle != INVALID_HANDLE) IndicatorRelease(ExtAOHandle);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == ExtLastBarTime)
return;
int maxShift = MathMax(InpJawShift, MathMax(InpTeethShift, InpLipsShift));
if(Bars(_Symbol, _Period) < InpFractalLookback + maxShift + 10)
return;
ExtLastBarTime = currentBarTime;
ManageOpenPositions();
ValidatePendingOrders();
CheckForFractalSignals();
}
//+------------------------------------------------------------------+
//| Retrieve Visually Aligned Alligator Lines |
//+------------------------------------------------------------------+
bool GetAlligatorLinesAtBar(int barIdx, double &jaw, double &teeth, double &lips)
{
double jawBuf[], teethBuf[], lipsBuf[];
ArraySetAsSeries(jawBuf, true);
ArraySetAsSeries(teethBuf, true);
ArraySetAsSeries(lipsBuf, true);
if(CopyBuffer(ExtAlligatorHandle, 0, barIdx + InpJawShift, 1, jawBuf) <= 0) return(false);
if(CopyBuffer(ExtAlligatorHandle, 1, barIdx + InpTeethShift, 1, teethBuf) <= 0) return(false);
if(CopyBuffer(ExtAlligatorHandle, 2, barIdx + InpLipsShift, 1, lipsBuf) <= 0) return(false);
jaw = jawBuf[0];
teeth = teethBuf[0];
lips = lipsBuf[0];
return(jaw > 0.0 && teeth > 0.0 && lips > 0.0);
}
//+------------------------------------------------------------------+
//| Check Alligator Awake Condition |
//+------------------------------------------------------------------+
bool IsAlligatorAwakeAtBar(int barIdx, bool isBullish)
{
double jaw = 0.0, teeth = 0.0, lips = 0.0;
if(!GetAlligatorLinesAtBar(barIdx, jaw, teeth, lips))
return(false);
if(InpRequireStrictFan)
{
if(isBullish && !(lips > teeth && teeth > jaw)) return(false);
if(!isBullish && !(lips < teeth && teeth < jaw)) return(false);
}
double totalMouthWidth = MathAbs(lips - jaw);
if(totalMouthWidth < (InpMinMouthWidthPips * ExtPipValue)) return(false);
if(MathAbs(lips - teeth) < (InpMinAdjacentGapPips * ExtPipValue) ||
MathAbs(teeth - jaw) < (InpMinAdjacentGapPips * ExtPipValue)) return(false);
if(InpMinMouthATRMult > 0.0)
{
double atrBuf[];
ArraySetAsSeries(atrBuf, true);
if(CopyBuffer(ExtATRHandle, 0, barIdx, 1, atrBuf) <= 0) return(false);
if(totalMouthWidth < (atrBuf[0] * InpMinMouthATRMult)) return(false);
}
return(true);
}
bool CheckAlligatorAwakeFilter(bool isBullish)
{
if(!InpUseAwakeFilter) return(true);
switch(InpAwakeEvalMode)
{
case AWAKE_AT_SIGNAL_BAR: return IsAlligatorAwakeAtBar(1, isBullish);
case AWAKE_AT_FRACTAL_BAR: return IsAlligatorAwakeAtBar(3, isBullish);
case AWAKE_AT_BOTH_BARS: return (IsAlligatorAwakeAtBar(3, isBullish) && IsAlligatorAwakeAtBar(1, isBullish));
}
return(false);
}
//+------------------------------------------------------------------+
//| Awesome Oscillator (AO) Filter Validation |
//+------------------------------------------------------------------+
bool CheckAwesomeOscillatorFilter(bool isBullish)
{
if(InpAOFilterMode == AO_FILTER_NONE)
return(true);
double aoBuf[];
ArraySetAsSeries(aoBuf, true);
// Copy 4 bars of AO (Index 1 = Bar 1 closed, Index 2, 3 for saucer detection)
if(CopyBuffer(ExtAOHandle, 0, 1, 4, aoBuf) < 4)
return(false);
double ao1 = aoBuf[0]; // Bar 1
double ao2 = aoBuf[1]; // Bar 2
double ao3 = aoBuf[2]; // Bar 3
bool zeroLinePass = false;
bool saucerPass = false;
//--- 1. Zero-Line Check
if(isBullish && ao1 > 0.0) zeroLinePass = true;
if(!isBullish && ao1 < 0.0) zeroLinePass = true;
//--- 2. Saucer Check (Bill Williams rules)
// Bullish Saucer: AO is above zero (or dipping), Bar 3 > Bar 2, and Bar 1 > Bar 2 (Red bar surrounded by two green bars)
if(isBullish)
{
if(ao2 < ao3 && ao1 > ao2 && ao1 > 0.0)
saucerPass = true;
}
// Bearish Saucer: AO is below zero, Bar 3 < Bar 2, and Bar 1 < Bar 2 (Green bar surrounded by two red bars)
else
{
if(ao2 > ao3 && ao1 < ao2 && ao1 < 0.0)
saucerPass = true;
}
//--- 3. Apply Filter Mode Evaluation
switch(InpAOFilterMode)
{
case AO_FILTER_ZERO_LINE: return zeroLinePass;
case AO_FILTER_SAUCER: return saucerPass;
case AO_FILTER_ZERO_OR_SAUCER: return (zeroLinePass || saucerPass);
}
return(true);
}
//+------------------------------------------------------------------+
//| Find N-th Most Recent Confirmed Fractal |
//+------------------------------------------------------------------+
double GetConfirmedFractalLevel(bool isUpFractal, int rank)
{
double fracBuf[];
ArraySetAsSeries(fracBuf, true);
int bufferNum = isUpFractal ? 0 : 1;
if(CopyBuffer(ExtFractalsHandle, bufferNum, 0, InpFractalLookback, fracBuf) <= 0)
return(0.0);
int foundCount = 0;
for(int i = 3; i < InpFractalLookback; i++)
{
if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
{
foundCount++;
if(foundCount >= rank)
return(fracBuf[i]);
}
}
if(rank > 1 && foundCount == 1)
{
for(int i = 3; i < InpFractalLookback; i++)
if(fracBuf[i] != EMPTY_VALUE && fracBuf[i] > 0.0)
return(fracBuf[i]);
}
return(0.0);
}
//+------------------------------------------------------------------+
//| Evaluate Fractals + Alligator + AO and Place Orders |
//+------------------------------------------------------------------+
void CheckForFractalSignals()
{
double upFracBuf[], dnFracBuf[];
ArraySetAsSeries(upFracBuf, true);
ArraySetAsSeries(dnFracBuf, true);
if(CopyBuffer(ExtFractalsHandle, 0, 3, 1, upFracBuf) <= 0) return;
if(CopyBuffer(ExtFractalsHandle, 1, 3, 1, dnFracBuf) <= 0) return;
double jaw3 = 0.0, teethAtFractal = 0.0, lips3 = 0.0;
double jaw1 = 0.0, teethAtCurrent = 0.0, lips1 = 0.0;
if(!GetAlligatorLinesAtBar(3, jaw3, teethAtFractal, lips3)) return;
if(!GetAlligatorLinesAtBar(1, jaw1, teethAtCurrent, lips1)) return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double spread = ask - bid;
int openCount = CountOpenPositions();
if(!InpAllowPyramiding && openCount > 0)
return;
if(openCount >= InpMaxPyramidTrades)
return;
//================================================================
// BULLISH SETUP: Up Fractal + Awake Alligator + AO Filter
//================================================================
if(upFracBuf[0] != EMPTY_VALUE && upFracBuf[0] > teethAtFractal)
{
if(CheckAlligatorAwakeFilter(true) && CheckAwesomeOscillatorFilter(true))
{
double fractalHigh = upFracBuf[0];
double entryPrice = NormalizeDouble(fractalHigh + (InpEntryBufferPips * ExtPipValue) + spread, _Digits);
if(entryPrice > ask && fractalHigh > teethAtCurrent && !HasOpenPositionDirection(POSITION_TYPE_BUY, fractalHigh))
{
double oppFractalLow = GetConfirmedFractalLevel(false, InpTrailFractalRank);
double slPrice = (oppFractalLow > 0.0 && oppFractalLow < entryPrice)
? oppFractalLow - (InpSLBufferPips * ExtPipValue)
: teethAtCurrent - (InpSLBufferPips * ExtPipValue);
slPrice = NormalizeDouble(slPrice, _Digits);
double slDistPips = (entryPrice - slPrice) / ExtPipValue;
if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
{
double tpPrice = 0.0;
if(InpRewardRiskRatio > 0.0)
tpPrice = NormalizeDouble(entryPrice + (entryPrice - slPrice) * InpRewardRiskRatio, _Digits);
double lots = CalculateLotSize(entryPrice - slPrice);
PlaceOrReplaceStopOrder(ORDER_TYPE_BUY_STOP, lots, entryPrice, slPrice, tpPrice);
}
}
}
}
//================================================================
// BEARISH SETUP: Down Fractal + Awake Alligator + AO Filter
//================================================================
if(dnFracBuf[0] != EMPTY_VALUE && dnFracBuf[0] < teethAtFractal)
{
if(CheckAlligatorAwakeFilter(false) && CheckAwesomeOscillatorFilter(false))
{
double fractalLow = dnFracBuf[0];
double entryPrice = NormalizeDouble(fractalLow - (InpEntryBufferPips * ExtPipValue), _Digits);
if(entryPrice < bid && fractalLow < teethAtCurrent && !HasOpenPositionDirection(POSITION_TYPE_SELL, fractalLow))
{
double oppFractalHigh = GetConfirmedFractalLevel(true, InpTrailFractalRank);
double slPrice = (oppFractalHigh > 0.0 && oppFractalHigh > entryPrice)
? oppFractalHigh + (InpSLBufferPips * ExtPipValue) + spread
: teethAtCurrent + (InpSLBufferPips * ExtPipValue) + spread;
slPrice = NormalizeDouble(slPrice, _Digits);
double slDistPips = (slPrice - entryPrice) / ExtPipValue;
if(slDistPips >= InpMinSLDistancePips && slDistPips <= InpMaxSLDistancePips)
{
double tpPrice = 0.0;
if(InpRewardRiskRatio > 0.0)
tpPrice = NormalizeDouble(entryPrice - (slPrice - entryPrice) * InpRewardRiskRatio, _Digits);
double lots = CalculateLotSize(slPrice - entryPrice);
PlaceOrReplaceStopOrder(ORDER_TYPE_SELL_STOP, lots, entryPrice, slPrice, tpPrice);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Place or Replace Stop Orders |
//+------------------------------------------------------------------+
void PlaceOrReplaceStopOrder(ENUM_ORDER_TYPE orderType, double lots, double price, double sl, double tp)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(ExtOrder.SelectByIndex(i))
{
if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber && ExtOrder.OrderType() == orderType)
{
ExtTrade.OrderDelete(ExtOrder.Ticket());
}
}
}
ENUM_ORDER_TYPE_TIME timeType = ORDER_TIME_GTC;
datetime expiration = 0;
if(InpOrderExpirationHrs > 0)
{
timeType = ORDER_TIME_SPECIFIED;
expiration = TimeCurrent() + (InpOrderExpirationHrs * 3600);
}
if(orderType == ORDER_TYPE_BUY_STOP)
ExtTrade.BuyStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Up Pyramid");
else if(orderType == ORDER_TYPE_SELL_STOP)
ExtTrade.SellStop(lots, price, _Symbol, sl, tp, timeType, expiration, "Williams Down Pyramid");
}
//+------------------------------------------------------------------+
//| Validate Pending Orders |
//+------------------------------------------------------------------+
void ValidatePendingOrders()
{
double close1 = iClose(_Symbol, _Period, 1);
double jaw1 = 0.0, teeth1 = 0.0, lips1 = 0.0;
if(!GetAlligatorLinesAtBar(1, jaw1, teeth1, lips1)) return;
bool awakeBull = IsAlligatorAwakeAtBar(1, true);
bool awakeBear = IsAlligatorAwakeAtBar(1, false);
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(ExtOrder.SelectByIndex(i))
{
if(ExtOrder.Symbol() == _Symbol && ExtOrder.Magic() == InpMagicNumber)
{
if(ExtOrder.OrderType() == ORDER_TYPE_BUY_STOP)
{
if(close1 < teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBull))
ExtTrade.OrderDelete(ExtOrder.Ticket());
}
if(ExtOrder.OrderType() == ORDER_TYPE_SELL_STOP)
{
if(close1 > teeth1 || (InpUseAwakeFilter && InpDeleteOrderIfSleeping && !awakeBear))
ExtTrade.OrderDelete(ExtOrder.Ticket());
}
}
}
}
}
//+------------------------------------------------------------------+
//| Manage Open Positions & Trailing Stops |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double spread = ask - bid;
double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
double trailDownFractal = GetConfirmedFractalLevel(false, InpTrailFractalRank);
double trailUpFractal = GetConfirmedFractalLevel(true, InpTrailFractalRank);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(ExtPosition.SelectByIndex(i))
{
if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber)
continue;
ulong ticket = ExtPosition.Ticket();
double currentSL = ExtPosition.StopLoss();
double currentTP = ExtPosition.TakeProfit();
if(ExtPosition.PositionType() == POSITION_TYPE_BUY && trailDownFractal > 0.0)
{
double newSL = NormalizeDouble(trailDownFractal - (InpSLBufferPips * ExtPipValue), _Digits);
if(newSL > currentSL + _Point && (bid - newSL) > stopLevel)
{
ExtTrade.PositionModify(ticket, newSL, currentTP);
}
}
else if(ExtPosition.PositionType() == POSITION_TYPE_SELL && trailUpFractal > 0.0)
{
double newSL = NormalizeDouble(trailUpFractal + (InpSLBufferPips * ExtPipValue) + spread, _Digits);
if((currentSL == 0.0 || newSL < currentSL - _Point) && (newSL - ask) > stopLevel)
{
ExtTrade.PositionModify(ticket, newSL, currentTP);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Helper Counts & Position Checks |
//+------------------------------------------------------------------+
int CountOpenPositions()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(ExtPosition.SelectByIndex(i))
{
if(ExtPosition.Symbol() == _Symbol && ExtPosition.Magic() == InpMagicNumber)
count++;
}
}
return(count);
}
bool HasOpenPositionDirection(ENUM_POSITION_TYPE posType, double fractalPrice)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(ExtPosition.SelectByIndex(i))
{
if(ExtPosition.Symbol() == _Symbol &&
ExtPosition.Magic() == InpMagicNumber &&
ExtPosition.PositionType() == posType)
{
// Prevent duplicate entries for the exact same fractal level
if(MathAbs(ExtPosition.PriceOpen() - fractalPrice) < (_Point * 2.0))
return(true);
}
}
}
return(false);
}
//+------------------------------------------------------------------+
//| Dynamic Position Sizing |
//+------------------------------------------------------------------+
double CalculateLotSize(double slPriceDistance)
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(InpRiskPercent <= 0.0 || slPriceDistance <= 0.0)
return(MathMax(minLot, MathMin(maxLot, InpFixedLot)));
double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (InpRiskPercent / 100.0);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0.0 || tickValue <= 0.0) return(minLot);
double moneyRiskPerLot = (slPriceDistance / tickSize) * tickValue;
if(moneyRiskPerLot <= 0.0) return(minLot);
double rawLots = riskMoney / moneyRiskPerLot;
double steppedLots = MathFloor(rawLots / lotStep) * lotStep;
return(NormalizeDouble(MathMax(minLot, MathMin(maxLot, steppedLots)), 2));
}
//+------------------------------------------------------------------+