//+------------------------------------------------------------------+
//| TranslatedPythonBotEA.mq5 |
//| Based on Python Bot Logic Mentor |
//| Your Name Here |
//+------------------------------------------------------------------+
#property copyright "Your Name Here"
#property link "Your Link Here"
#property version "1.01" // Incremented version
#property strict
// Include necessary standard library files
#include <Trade\Trade.mqh> // For CTrade class (trading functions)
#include <Trade\SymbolInfo.mqh> // For CSymbolInfo class (symbol properties)
#include <Trade\AccountInfo.mqh> // For CAccountInfo class (account properties)
// --- Input Parameters (User-configurable EA settings) ---
input group "Symbol & Timeframe Settings"
// string InpSymbolToTrade = "Volatility 75 Index"; // EA uses
_Symbol (chart symbol) by default
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M1; //
Timeframe for indicator calculations
input group "Indicator Parameters"
input int InpATRPeriod = 14;
input int InpBBandsPeriod = 20;
input double InpBBandsStdDev = 2.0;
input int InpRSIPeriod = 14;
input int InpEMAFastPeriod = 12;
input int InpEMASlowPeriod = 50;
input group "Risk & Trade Management"
input double InpRiskPerTradePercent = 1.0; // e.g., 1.0 for 1% of
equity
input double InpATR_SL_Multiplier = 1.5; // Stop Loss based on ATR *
Multiplier
input double InpATR_TP_Multiplier = 3.0; // Take Profit based on ATR
* Multiplier
input int InpDefaultSLPips = 100; // Fallback Stop Loss in
Pips if ATR fails
input int InpDefaultTPPips = 200; // Fallback Take Profit in
Pips if ATR fails
input ulong InpBotMagicNumber = 23052025; // Unique Magic Number
for this EA's trades
input int InpSlippagePips = 20; // Max allowed slippage in
points (not pips)
// --- Global Variables ---
CTrade trade; // Trading object from Trade.mqh
CSymbolInfo symbol; // Symbol information object from SymbolInfo.mqh
CAccountInfo account; // Account information object from
AccountInfo.mqh
string G_CurrentSymbol; // Stores the symbol the EA is running on
// Indicator Handles (to store references to initialised indicators)
int h_ATR;
int h_BB;
int h_RSI;
int h_EMAFast;
int h_EMASlow;
// Arrays to store indicator and price data
double atr_buffer[];
double bb_main_buffer[]; // Bollinger Bands Middle line
double bb_upper_buffer[]; // Bollinger Bands Upper line
double bb_lower_buffer[]; // Bollinger Bands Lower line
double rsi_buffer[];
double ema_fast_buffer[];
double ema_slow_buffer[];
MqlRates rates[]; // For OHLC, Time, Volume (price data)
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
G_CurrentSymbol = _Symbol; // Get the symbol of the chart the EA is attached to
PrintFormat("OnInit: EA Starting on symbol %s, timeframe %s", G_CurrentSymbol,
EnumToString(InpTimeframe));
// Initialize SymbolInfo and AccountInfo objects
if(!symbol.Name(G_CurrentSymbol)) // Set the symbol for the CSymbolInfo object
{
PrintFormat("Error setting symbol '%s' for CSymbolInfo: %d", G_CurrentSymbol,
GetLastError());
return(INIT_FAILED);
}
account.Refresh(); // Refresh account data (initial fetch)
// Initialize CTrade object (for sending trade orders)
trade.SetExpertMagicNumber(InpBotMagicNumber);
trade.SetDeviationInPoints(InpSlippagePips); // Set allowed slippage for
orders
trade.SetTypeFillingBySymbol(G_CurrentSymbol); // Auto-detect filling mode,
or set specific like ORDER_FILLING_IOC
// --- Initialize Indicator Handles ---
h_ATR = iATR(G_CurrentSymbol, InpTimeframe, InpATRPeriod);
if(h_ATR == INVALID_HANDLE) { PrintFormat("Error creating ATR indicator: %d",
GetLastError()); return(INIT_FAILED); }
h_BB = iBands(G_CurrentSymbol, InpTimeframe, InpBBandsPeriod, 0,
InpBBandsStdDev, PRICE_CLOSE);
if(h_BB == INVALID_HANDLE) { PrintFormat("Error creating Bollinger Bands
indicator: %d", GetLastError()); return(INIT_FAILED); }
h_RSI = iRSI(G_CurrentSymbol, InpTimeframe, InpRSIPeriod, PRICE_CLOSE);
if(h_RSI == INVALID_HANDLE) { PrintFormat("Error creating RSI indicator: %d",
GetLastError()); return(INIT_FAILED); }
h_EMAFast = iMA(G_CurrentSymbol, InpTimeframe, InpEMAFastPeriod, 0, MODE_EMA,
PRICE_CLOSE);
if(h_EMAFast == INVALID_HANDLE) { PrintFormat("Error creating Fast EMA
indicator: %d", GetLastError()); return(INIT_FAILED); }
h_EMASlow = iMA(G_CurrentSymbol, InpTimeframe, InpEMASlowPeriod, 0, MODE_EMA,
PRICE_CLOSE);
if(h_EMASlow == INVALID_HANDLE) { PrintFormat("Error creating Slow EMA
indicator: %d", GetLastError()); return(INIT_FAILED); }
// Set arrays as series (important for correct indexing: latest data at index 0)
ArraySetAsSeries(rates, true);
ArraySetAsSeries(atr_buffer, true);
ArraySetAsSeries(bb_main_buffer, true);
ArraySetAsSeries(bb_upper_buffer, true);
ArraySetAsSeries(bb_lower_buffer, true);
ArraySetAsSeries(rsi_buffer, true);
ArraySetAsSeries(ema_fast_buffer, true);
ArraySetAsSeries(ema_slow_buffer, true);
Print("OnInit: Initialization successful.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
PrintFormat("OnDeinit: EA Stopping. Reason code: %d", reason);
// Release indicator handles to free resources
IndicatorRelease(h_ATR);
IndicatorRelease(h_BB);
IndicatorRelease(h_RSI);
IndicatorRelease(h_EMAFast);
IndicatorRelease(h_EMASlow);
}
//+------------------------------------------------------------------+
//| Expert tick function (called on every new price tick) |
//+------------------------------------------------------------------+
void OnTick()
{
// --- 0. Check if trading is allowed ---
if(!TerminalInfoInteger(TERMINAL_CONNECTED))
{
// Comment("Not connected to trade server."); // Optional: display status on
chart
return;
}
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
{
// Comment("Trading is not allowed on this account/server (TerminalInfo).");
return;
}
if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) // Checks client terminal settings
{
// Comment("Trading is not allowed by client terminal settings (MQLInfo).");
return;
}
if(IsStopped()) // Check if EA was stopped from Experts tab
{
return;
}
// --- 1. Get Latest Data (OHLC and Indicators) ---
// Refresh symbol data (like current Ask/Bid, point size)
symbol.RefreshRates(); // Crucial for up-to-date Ask/Bid
// Copy price data (OHLCV) for a few recent bars
// Using 3 bars: index 0 = current (developing), 1 = last closed, 2 = bar before
last closed
if(CopyRates(G_CurrentSymbol, InpTimeframe, 0, 3, rates) < 3)
{
PrintFormat("Error copying rates for %s: %d", G_CurrentSymbol,
GetLastError());
return;
}
// Copy indicator buffers
// For signals, it's generally safer to use data from the most recently
COMPLETED bar (index 1)
int bars_to_copy = 3; // Copy data for a few bars
if(CopyBuffer(h_ATR, 0, 0, bars_to_copy, atr_buffer) < bars_to_copy)
{ Print("Error copying ATR buffer"); return; }
if(CopyBuffer(h_BB, 0, 0, bars_to_copy, bb_main_buffer) < bars_to_copy)
{ Print("Error copying BB Main buffer"); return; }
if(CopyBuffer(h_BB, 1, 0, bars_to_copy, bb_upper_buffer) < bars_to_copy)
{ Print("Error copying BB Upper buffer"); return; }
if(CopyBuffer(h_BB, 2, 0, bars_to_copy, bb_lower_buffer) < bars_to_copy)
{ Print("Error copying BB Lower buffer"); return; }
if(CopyBuffer(h_RSI, 0, 0, bars_to_copy, rsi_buffer) < bars_to_copy)
{ Print("Error copying RSI buffer"); return; }
if(CopyBuffer(h_EMAFast, 0, 0, bars_to_copy, ema_fast_buffer) < bars_to_copy)
{ Print("Error copying EMA Fast buffer"); return; }
if(CopyBuffer(h_EMASlow, 0, 0, bars_to_copy, ema_slow_buffer) < bars_to_copy)
{ Print("Error copying EMA Slow buffer"); return; }
// --- 2. Check for Existing Position by this Bot ---
if(CheckExistingPosition())
{
// Print("An active trade with magic number ", InpBotMagicNumber, " already
exists for ", G_CurrentSymbol);
return; // Don't open new trades if one is already open by this EA for this
symbol
}
// --- 3. Get Trading Signal ---
// Using data from index 1 (most recent fully closed bar) for signal generation
double ema_fast_prev = ema_fast_buffer[1];
double ema_slow_prev = ema_slow_buffer[1];
double rsi_prev = rsi_buffer[1]; // RSI on the last closed bar
// For EMA crossover, we need to compare the current state to the previous bar's
state
double ema_fast_bar2 = ema_fast_buffer[2]; // EMA fast on bar before previous
double ema_slow_bar2 = ema_slow_buffer[2]; // EMA slow on bar before previous
string signal = "HOLD";
// Buy signal: Fast EMA crossed ABOVE Slow EMA on the close of the last bar, AND
RSI is not overbought
if (ema_fast_bar2 <= ema_slow_bar2 && ema_fast_prev > ema_slow_prev && rsi_prev
< 70.0)
{
signal = "BUY";
}
// Sell signal: Fast EMA crossed BELOW Slow EMA on the close of the last bar,
AND RSI is not oversold
else if (ema_fast_bar2 >= ema_slow_bar2 && ema_fast_prev < ema_slow_prev &&
rsi_prev > 30.0)
{
signal = "SELL";
}
// Optional: Print signal status
// PrintFormat("Signal: %s, EMAFast(1):%.5f, EMASlow(1):%.5f, RSI(1):%.2f",
signal, ema_fast_prev, ema_slow_prev, rsi_prev);
// --- 4. If No Existing Position and Valid Signal, then Prepare and Execute
Trade ---
if(signal != "HOLD")
{
double current_atr_value = atr_buffer[1]; // Use ATR from the last closed bar
for stability
if(current_atr_value <= 0 || NormalizeDouble(current_atr_value,
symbol.Digits()) <= symbol.Point()/10.0) // Check if ATR is valid and not extremely
small
{
PrintFormat("ATR value (%.5f) is zero or too small. Using default Pips for
SL/TP.", current_atr_value);
// Convert default pips to price distance (points)
// A "pip" for Volatility Indices usually means the last digit shown, but
point is more precise
// If 1 pip = 10 points for your symbol (e.g. price 123.45, point = 0.01,
1 pip = 0.10 means 10 points)
// This needs careful checking for your specific VIX symbol. Assuming
InpDefaultSLPips means "points" for simplicity here.
// For true "pips", you'd need to know how many points constitute a pip
for that symbol.
// Let's assume InpDefaultSLPips are actually "points" as MT5 defines them
(smallest price change unit * InpDefaultSLPips)
double sl_distance_price_units = InpDefaultSLPips * symbol.Point();
double tp_distance_price_units = InpDefaultTPPips * symbol.Point();
if(sl_distance_price_units <=0 ) sl_distance_price_units = 100 *
symbol.Point(); // Min fallback
if(tp_distance_price_units <=0 ) tp_distance_price_units = 200 *
symbol.Point(); // Min fallback
PlaceTrade(signal, sl_distance_price_units, tp_distance_price_units);
}
else
{
// a. Calculate SL and TP distances in price units (e.g., for USDJPY, this
would be in JPY)
double sl_distance_price_units = current_atr_value * InpATR_SL_Multiplier;
double tp_distance_price_units = current_atr_value * InpATR_TP_Multiplier;
PlaceTrade(signal, sl_distance_price_units, tp_distance_price_units);
}
}
}
//+------------------------------------------------------------------+
//| Helper Function: Place Trade |
//+------------------------------------------------------------------+
void PlaceTrade(string signal_type, double sl_distance_price_units, double
tp_distance_price_units)
{
// b. Calculate Lot Size
double lot_size = CalculateLotSize(sl_distance_price_units);
if(lot_size < symbol.LotsMin())
{
PrintFormat("Calculated lot size %.2f is less than minimum allowed %.2f.
Skipping trade.", lot_size, symbol.LotsMin());
// Or optionally: lot_size = symbol.LotsMin(); if you want to trade min lot
return;
}
// c. Define SL and TP price levels
// Ensure symbol.RefreshRates() was called in OnTick for fresh Ask/Bid
double entry_price_ask = symbol.Ask();
double entry_price_bid = symbol.Bid();
double sl_price = 0.0;
double tp_price = 0.0;
string comment = "EA_Trade_" + TimeToString(TimeCurrent(), TIME_SECONDS);
if(signal_type == "BUY")
{
sl_price = entry_price_ask - sl_distance_price_units;
tp_price = entry_price_ask + tp_distance_price_units;
sl_price = NormalizeDouble(sl_price, symbol.Digits()); // Normalize to
symbol's decimal places
tp_price = NormalizeDouble(tp_price, symbol.Digits());
PrintFormat("Attempting BUY: Lot=%.2f, EntryAsk=%.*f, SL=%.*f, TP=%.*f",
lot_size, symbol.Digits(), entry_price_ask, symbol.Digits(),
sl_price, symbol.Digits(), tp_price);
if(!trade.Buy(lot_size, G_CurrentSymbol, entry_price_ask, sl_price, tp_price,
comment))
{
PrintFormat("BUY order failed: %d - %s", trade.ResultRetcode(),
trade.ResultComment());
}
else
{
PrintFormat("BUY order placed successfully. Ticket: %d",
(int)trade.ResultOrder());
}
}
else if(signal_type == "SELL")
{
sl_price = entry_price_bid + sl_distance_price_units;
tp_price = entry_price_bid - tp_distance_price_units;
sl_price = NormalizeDouble(sl_price, symbol.Digits());
tp_price = NormalizeDouble(tp_price, symbol.Digits());
PrintFormat("Attempting SELL: Lot=%.2f, EntryBid=%.*f, SL=%.*f, TP=%.*f",
lot_size, symbol.Digits(), entry_price_bid, symbol.Digits(),
sl_price, symbol.Digits(), tp_price);
if(!trade.Sell(lot_size, G_CurrentSymbol, entry_price_bid, sl_price,
tp_price, comment))
{
PrintFormat("SELL order failed: %d - %s", trade.ResultRetcode(),
trade.ResultComment());
}
else
{
PrintFormat("SELL order placed successfully. Ticket: %d",
(int)trade.ResultOrder());
}
}
}
//+------------------------------------------------------------------+
//| Helper Function: Calculate Lot Size based on Risk % and SL |
//+------------------------------------------------------------------+
double CalculateLotSize(double sl_distance_price_units) // sl_distance_price_units
is the distance from entry to SL in PRICE units
{
account.Refresh(); // Get latest account info
double acc_equity = account.Equity();
double risk_amount_deposit_currency = acc_equity * (InpRiskPerTradePercent /
100.0);
if(sl_distance_price_units <= symbol.Point() / 2.0) // SL distance too small or
zero
{
PrintFormat("SL distance (%.*f) is too small. Cannot accurately calculate lot
size. Using min lot.", symbol.Digits(), sl_distance_price_units);
return(symbol.LotsMin());
}
// Calculate value of 1 lot SL in deposit currency
// This requires knowing the value of a one-point move for the specific symbol.
// SYMBOL_TRADE_TICK_VALUE is the value of a SYMBOL_TRADE_TICK_SIZE move for 1
lot.
// If SYMBOL_TRADE_TICK_SIZE is not equal to SYMBOL_POINT, adjustment is needed.
double tick_value = symbol.TickValue(); // Value of a tick_size move for 1.0 lot
double tick_size = symbol.TickSize(); // Size of a tick (minimal price change
recognized by broker for contract)
double point_size = symbol.Point(); // Smallest possible price change (e.g.
0.00001 for EURUSD)
if(tick_size <= 0 || point_size <= 0)
{
Print("Symbol tick_size or point_size is zero or negative. Cannot calculate
lot. Using min lot.");
return(symbol.LotsMin());
}
// Value per point for 1 lot:
// (Value of a TickSize move / Number of Points in a TickSize move)
double value_per_point_one_lot = tick_value / (tick_size / point_size);
// Total value of the SL for 1 lot
double sl_value_for_one_lot = (sl_distance_price_units / point_size) *
value_per_point_one_lot;
if(sl_value_for_one_lot <= 0)
{
PrintFormat("SL value per lot (%.2f) is zero or negative. Cannot calculate
lot size. Using min lot.", sl_value_for_one_lot);
return(symbol.LotsMin());
}
double desired_lot_size = risk_amount_deposit_currency / sl_value_for_one_lot;
// Normalize lot size according to symbol's contract specification
double min_lot = symbol.LotsMin();
double max_lot = symbol.LotsMax(); // Make sure it's reasonable, some brokers
have high max_lot
double step_lot = symbol.LotsStep();
// Apply constraints
desired_lot_size = MathMax(min_lot, desired_lot_size);
if(max_lot > 0) desired_lot_size = MathMin(max_lot, desired_lot_size); // Only
apply max_lot if it's > 0
// Adjust to volume step: round down to the nearest valid step
if(step_lot > 0)
{
desired_lot_size = MathFloor(desired_lot_size / step_lot) * step_lot;
}
// Determine correct number of decimal places for lot size based on step_lot
int lot_digits = 0;
string step_str = DoubleToString(step_lot, 8);
int dot_pos = StringFind(step_str, ".");
if(dot_pos >= 0)
{
lot_digits = StringLen(step_str) - dot_pos - 1;
// Remove trailing zeros for correct digit count if step is e.g. 0.10
while(lot_digits > 0 && StringGetCharacter(step_str, StringLen(step_str) - 1)
== '0')
{
step_str = StringSubstr(step_str, 0, StringLen(step_str) -1);
lot_digits--;
}
}
desired_lot_size = NormalizeDouble(desired_lot_size, lot_digits);
// Final check against min_lot after normalization
if (desired_lot_size < min_lot) desired_lot_size = min_lot;
PrintFormat("LotCalc: Equity=%.2f, RiskAmt=%.2f, SL_Dist_Price=%.*f,
SL_Val_Per_Lot=%.2f, CalcLot=%.*f",
acc_equity, risk_amount_deposit_currency,
symbol.Digits(), sl_distance_price_units,
sl_value_for_one_lot,
lot_digits, desired_lot_size);
return(desired_lot_size);
}
//+------------------------------------------------------------------+
//| Helper Function: Check if a position by this EA already exists |
//+------------------------------------------------------------------+
bool CheckExistingPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--) // Loop through all open
positions
{
ulong pos_ticket = PositionGetTicket(i); // Get ticket of position at
index i
if(pos_ticket == 0) continue; // Should not happen, but good
check
// Select the position to access its properties.
// No need to call PositionSelectByTicket() if directly getting properties by
ticket.
// However, some PositionGet Functions require the position to be pre-
selected by ticket or symbol/index.
// Let's select to be safe and consistent.
if(PositionSelectByTicket(pos_ticket))
{
if(PositionGetString(POSITION_SYMBOL) == G_CurrentSymbol &&
PositionGetInteger(POSITION_MAGIC) == InpBotMagicNumber)
{
return(true); // Position found for this symbol and magic number
}
}
else
{
PrintFormat("Error selecting position ticket %d: %d", (int)pos_ticket,
GetLastError());
}
}
return(false); // No matching position found
}
//+------------------------------------------------------------------+