Handling partial closes in MQL5 requires strict volume normalization. Because a broker will reject an order to close 0.015 lots if the minimum volume step is 0.01, the script calculates exactly half of the active position size, rounds it down to the nearest valid lot step, and ensures the resulting volume meets the broker's minimum lot size before executing.
Code: Select all
//+------------------------------------------------------------------+
//| DynamicRiskHotkeyScalper.mq5 |
//+------------------------------------------------------------------+
#property copyright "Custom MQL5 Script"
#property version "1.20"
#include <Trade\Trade.mqh>
//--- Input parameters
input double InpRiskPercent = 1.0; // Account Risk per Trade (%)
input int InpAtrPeriod = 14; // ATR Period
input double InpAtrMultiplier = 1.0; // ATR Multiplier for Stop Loss
input ulong InpMagic = 123456; // Magic Number
input ulong InpSlippage = 3; // Max Slippage (Points)
CTrade trade;
int atrHandle;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(InpSlippage);
atrHandle = iATR(_Symbol, PERIOD_CURRENT, InpAtrPeriod);
if(atrHandle == INVALID_HANDLE)
{
Print("Failed to create ATR indicator handle.");
return(INIT_FAILED);
}
Print("Dynamic Risk Hotkeys Initialized.");
Print("'B'=Buy, 'S'=Sell, 'C'=Close All, 'Space'=Scale Out 50%.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
switch((int)lparam)
{
case 66: // 'B' key
ExecuteBuy();
break;
case 83: // 'S' key
ExecuteSell();
break;
case 67: // 'C' key
CloseAllCurrentSymbol();
break;
case 32: // Spacebar
ScaleOutCurrentSymbol();
break;
}
}
}
//+------------------------------------------------------------------+
//| Lot Size Calculation |
//+------------------------------------------------------------------+
double CalculateLotSize(double slDistanceInPrice)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmount = balance * (InpRiskPercent / 100.0);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize == 0 || tickValue == 0 || slDistanceInPrice == 0)
return 0.0;
double riskPerLot = (slDistanceInPrice / tickSize) * tickValue;
double rawLot = riskAmount / riskPerLot;
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double finalLot = MathFloor(rawLot / stepLot) * stepLot;
if(finalLot < minLot) finalLot = minLot;
if(finalLot > maxLot) finalLot = maxLot;
return finalLot;
}
//+------------------------------------------------------------------+
//| Execution Functions |
//+------------------------------------------------------------------+
void ExecuteBuy()
{
double atrArray[];
ArraySetAsSeries(atrArray, true);
if(CopyBuffer(atrHandle, 0, 1, 1, atrArray) <= 0)
{
Print("Error getting ATR data.");
return;
}
double currentAtr = atrArray[0];
double slDistance = currentAtr * InpAtrMultiplier;
double volume = CalculateLotSize(slDistance);
if(volume <= 0) return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - slDistance;
if(trade.Buy(volume, _Symbol, ask, sl, 0, "ATR Risk Buy"))
PrintFormat("Buy executed. Vol: %.2f, SL: %.5f", volume, sl);
else
Print("Buy failed. Error: ", GetLastError());
}
void ExecuteSell()
{
double atrArray[];
ArraySetAsSeries(atrArray, true);
if(CopyBuffer(atrHandle, 0, 1, 1, atrArray) <= 0)
{
Print("Error getting ATR data.");
return;
}
double currentAtr = atrArray[0];
double slDistance = currentAtr * InpAtrMultiplier;
double volume = CalculateLotSize(slDistance);
if(volume <= 0) return;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = bid + slDistance;
if(trade.Sell(volume, _Symbol, bid, sl, 0, "ATR Risk Sell"))
PrintFormat("Sell executed. Vol: %.2f, SL: %.5f", volume, sl);
else
Print("Sell failed. Error: ", GetLastError());
}
void CloseAllCurrentSymbol()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
string position_symbol = PositionGetSymbol(i);
if(position_symbol == _Symbol)
{
ulong ticket = PositionGetInteger(POSITION_TICKET);
trade.PositionClose(ticket);
}
}
}
void ScaleOutCurrentSymbol()
{
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
string position_symbol = PositionGetSymbol(i);
if(position_symbol == _Symbol)
{
ulong ticket = PositionGetInteger(POSITION_TICKET);
double currentVolume = PositionGetDouble(POSITION_VOLUME);
// Calculate half volume and round down to valid broker increment
double closeVolume = MathFloor((currentVolume / 2.0) / stepLot) * stepLot;
// Safety check: ensure we don't try to close 0 lots or less than broker minimum
if(closeVolume >= minLot)
{
if(trade.PositionClosePartial(ticket, closeVolume))
PrintFormat("Position %d scaled out. Closed volume: %.2f", ticket, closeVolume);
else
PrintFormat("Scale-out failed for ticket %d. Error: %d", ticket, GetLastError());
}
else
{
PrintFormat("Remaining volume (%.2f) on position %d is too small to scale out further.", currentVolume, ticket);
}
}
}
}