Publish the preserved MT4/MT5 rolling signal-history framework with documentation, license, changelog, and flow image.
399 lines
17 KiB
MQL5
399 lines
17 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Custom Logic For Trading MT5 Mini.mq5 |
|
|
//| Jollie Roger |
|
|
//| SPDX-License-Identifier: MIT |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Jollie Roger"
|
|
#property link "https://www.mql5.com/en/market/product/121666"
|
|
#property version "2.00"
|
|
#property description "Automated trading script designed to execute trades based on custom indicator signals."
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
// Define the enum for Signal Types
|
|
enum SignalType
|
|
{
|
|
STCrossOverPrice, // Cross over price
|
|
STCrossUnderPrice, // Cross under price
|
|
STCrossOverLevel, // Cross over level
|
|
STCrossUnderLevel, // Cross under level
|
|
STSymbol // Symbol/arrow
|
|
};
|
|
|
|
// Define the enum for Logic Direction
|
|
enum LogicDirection
|
|
{
|
|
DirectLogic, // Direct
|
|
ReversalLogic // Reversal
|
|
};
|
|
|
|
// Define the enum for Trading Side
|
|
enum TradingSide
|
|
{
|
|
LongSideOnly, // Long
|
|
ShortSideOnly, // Short
|
|
BothSides // Both
|
|
};
|
|
|
|
// Input parameters
|
|
input string indicator_setting = "=== Indicator Setting ===";// Indicator Setting
|
|
input string indicator_name = ""; // Indicator name
|
|
input int buy_stream_index = 0; // Buy stream index
|
|
input double buy_level = 0; // Buy level
|
|
input SignalType buy_signal = STCrossUnderPrice; // Buy signal
|
|
input int sell_stream_index = 0; // Sell stream index
|
|
input double sell_level = 0; // Sell level
|
|
input SignalType sell_signal = STCrossOverPrice; // Sell signal
|
|
input string position_sizing = "=== Position Sizing ===";// Position Sizing
|
|
input double lots_value = 0.01; // Position size
|
|
input double take_profit = 0; // Take Profit in points
|
|
input double stop_loss = 0; // Stop Loss in points
|
|
input int slippage_points = 3; // Slippage, points
|
|
input int magic_number = 42; // Magic number
|
|
input string trade_comment = ""; // Comment for orders
|
|
input string trading_parameters = "=== Trading Parameters ==="; // Trading Parameters
|
|
input bool close_on_opposite = true; // Close on opposite signal
|
|
input LogicDirection logic_direction = DirectLogic; // Logic direction
|
|
input TradingSide trading_side = BothSides; // Trading side
|
|
input int shift = 0; // Shift
|
|
input int maximum_order = 0; // Maximum order allowed (0 = not used)
|
|
|
|
input string breakeven_feature = "=== Breakeven ===";// Breakeven feature
|
|
input bool use_break_even = false; // Use break even feature
|
|
input double break_even_trigger = 0; // Break even trigger in points
|
|
input double break_even_target = 0; // Break even target in points
|
|
|
|
// Global variables
|
|
CTrade trade;
|
|
int indicator_handle;
|
|
bool prev_buy_signal = false;
|
|
bool prev_sell_signal = false;
|
|
bool initial_buy_signal_ignored = false;
|
|
bool initial_sell_signal_ignored = false;
|
|
bool ready_to_buy = false;
|
|
bool ready_to_sell = false;
|
|
int max_position = 1;
|
|
bool long_signal_history[];
|
|
bool short_signal_history[];
|
|
int max_history_size = 1000;
|
|
|
|
// Function to get signal from the indicator
|
|
double GetSignal(string symbol, ENUM_TIMEFRAMES tf, int period, int stream)
|
|
{
|
|
double buffer[];
|
|
ArraySetAsSeries(buffer, true);
|
|
int copied = CopyBuffer(indicator_handle, stream, period, 1, buffer);
|
|
if(copied == 1)
|
|
return buffer[0];
|
|
return EMPTY_VALUE;
|
|
}
|
|
|
|
// Function to check long condition
|
|
bool IsLongCondition(int period, bool &signalAvailable)
|
|
{
|
|
double value = GetSignal(Symbol(), PERIOD_CURRENT, period, buy_stream_index);
|
|
signalAvailable = (value != EMPTY_VALUE && MathIsValidNumber(value));
|
|
if(!signalAvailable)
|
|
return false;
|
|
|
|
bool conditionMet = false; // Initialize conditionMet to false
|
|
switch(buy_signal)
|
|
{
|
|
case STCrossOverPrice:
|
|
conditionMet = value > iClose(Symbol(), PERIOD_CURRENT, period);
|
|
break;
|
|
case STCrossUnderPrice:
|
|
conditionMet = value < iClose(Symbol(), PERIOD_CURRENT, period);
|
|
break;
|
|
case STCrossOverLevel:
|
|
conditionMet = value > buy_level;
|
|
break;
|
|
case STCrossUnderLevel:
|
|
conditionMet = value < buy_level;
|
|
break;
|
|
case STSymbol:
|
|
conditionMet = value != 0 && value != EMPTY_VALUE;
|
|
break;
|
|
}
|
|
return logic_direction == DirectLogic ? conditionMet : !conditionMet;
|
|
}
|
|
|
|
// Function to check short condition
|
|
bool IsShortCondition(int period, bool &signalAvailable)
|
|
{
|
|
double value = GetSignal(Symbol(), PERIOD_CURRENT, period, sell_stream_index);
|
|
signalAvailable = (value != EMPTY_VALUE && MathIsValidNumber(value));
|
|
if(!signalAvailable)
|
|
return false;
|
|
|
|
bool conditionMet = false; // Initialize conditionMet to false
|
|
switch(sell_signal)
|
|
{
|
|
case STCrossOverPrice:
|
|
conditionMet = value > iClose(Symbol(), PERIOD_CURRENT, period);
|
|
break;
|
|
case STCrossUnderPrice:
|
|
conditionMet = value < iClose(Symbol(), PERIOD_CURRENT, period);
|
|
break;
|
|
case STCrossOverLevel:
|
|
conditionMet = value > sell_level;
|
|
break;
|
|
case STCrossUnderLevel:
|
|
conditionMet = value < sell_level;
|
|
break;
|
|
case STSymbol:
|
|
conditionMet = value != 0 && value != EMPTY_VALUE;
|
|
break;
|
|
}
|
|
return logic_direction == DirectLogic ? conditionMet : !conditionMet;
|
|
}
|
|
|
|
// IsLongCondition and IsShortCondition functions remain mostly the same
|
|
// Replace iClose with:
|
|
// double close = iClose(Symbol(), PERIOD_CURRENT, period);
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int PositionTotal()
|
|
{
|
|
int count = 0;
|
|
|
|
// Loop through all positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
// Select the position by index
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket <= 0)
|
|
continue;
|
|
|
|
// Check if position matches current symbol and magic number
|
|
string position_symbol = PositionGetString(POSITION_SYMBOL);
|
|
long position_magic = PositionGetInteger(POSITION_MAGIC);
|
|
|
|
if(position_symbol == Symbol() && position_magic == magic_number)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void ClosePosition(ENUM_POSITION_TYPE type)
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
if(PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_TYPE) == type && PositionGetInteger(POSITION_MAGIC) == magic_number)
|
|
{
|
|
ResetLastError();
|
|
bool closed = trade.PositionClose(ticket);
|
|
uint retcode = trade.ResultRetcode();
|
|
if(!closed || (retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_DONE_PARTIAL && retcode != TRADE_RETCODE_PLACED))
|
|
PrintFormat("Error closing position: retcode=%u (%s), error=%d", retcode, trade.ResultRetcodeDescription(), GetLastError());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void ExecutionTrade()
|
|
{
|
|
int currentPeriod = shift;
|
|
|
|
bool longSignalAvailable = false;
|
|
bool shortSignalAvailable = false;
|
|
bool longCondition = IsLongCondition(currentPeriod, longSignalAvailable);
|
|
bool shortCondition = IsShortCondition(currentPeriod, shortSignalAvailable);
|
|
|
|
if(close_on_opposite)
|
|
{
|
|
if(shortSignalAvailable && (trading_side == LongSideOnly || trading_side == BothSides) && shortCondition)
|
|
{
|
|
ClosePosition(POSITION_TYPE_BUY);
|
|
}
|
|
if(longSignalAvailable && (trading_side == ShortSideOnly || trading_side == BothSides) && longCondition)
|
|
{
|
|
ClosePosition(POSITION_TYPE_SELL);
|
|
}
|
|
}
|
|
bool isAvailableQuota = (maximum_order - PositionTotal() > 0 || maximum_order == 0);
|
|
|
|
// Update long signal history array only after a valid indicator read.
|
|
if(longSignalAvailable)
|
|
{
|
|
if(ArraySize(long_signal_history) >= max_history_size)
|
|
{
|
|
for(int i = 1; i < ArraySize(long_signal_history); i++)
|
|
long_signal_history[i - 1] = long_signal_history[i];
|
|
ArrayResize(long_signal_history, ArraySize(long_signal_history) - 1);
|
|
}
|
|
ArrayResize(long_signal_history, ArraySize(long_signal_history) + 1);
|
|
long_signal_history[ArraySize(long_signal_history) - 1] = longCondition;
|
|
}
|
|
|
|
// Update short signal history array only after a valid indicator read.
|
|
if(shortSignalAvailable)
|
|
{
|
|
if(ArraySize(short_signal_history) >= max_history_size)
|
|
{
|
|
for(int i = 1; i < ArraySize(short_signal_history); i++)
|
|
short_signal_history[i - 1] = short_signal_history[i];
|
|
ArrayResize(short_signal_history, ArraySize(short_signal_history) - 1);
|
|
}
|
|
ArrayResize(short_signal_history, ArraySize(short_signal_history) + 1);
|
|
short_signal_history[ArraySize(short_signal_history) - 1] = shortCondition;
|
|
}
|
|
|
|
// Check if the current long signal is valid (changed from false to true
|
|
|
|
if(longSignalAvailable && ArraySize(long_signal_history) >= 2 && long_signal_history[ArraySize(long_signal_history) - 1] && !long_signal_history[ArraySize(long_signal_history) - 2])
|
|
{
|
|
if((trading_side == LongSideOnly || trading_side == BothSides) && longCondition && isAvailableQuota)
|
|
{
|
|
Print("Valid Long Signal Detected");
|
|
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
|
|
double tpPrice = take_profit > 0 ? ask + take_profit * SymbolInfoDouble(Symbol(), SYMBOL_POINT) : 0;
|
|
double slPrice = stop_loss > 0 ? ask - stop_loss * SymbolInfoDouble(Symbol(), SYMBOL_POINT) : 0;
|
|
ResetLastError();
|
|
bool opened = trade.Buy(lots_value, Symbol(), ask, slPrice, tpPrice, trade_comment);
|
|
uint retcode = trade.ResultRetcode();
|
|
if(!opened || (retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_DONE_PARTIAL && retcode != TRADE_RETCODE_PLACED))
|
|
PrintFormat("Error opening buy position: retcode=%u (%s), error=%d", retcode, trade.ResultRetcodeDescription(), GetLastError());
|
|
}
|
|
}
|
|
|
|
if(shortSignalAvailable && ArraySize(short_signal_history) >= 2 && short_signal_history[ArraySize(short_signal_history) - 1] && !short_signal_history[ArraySize(short_signal_history) - 2])
|
|
{
|
|
if((trading_side == ShortSideOnly || trading_side == BothSides) && shortCondition && isAvailableQuota)
|
|
{
|
|
Print("Valid Short Signal Detected");
|
|
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
|
|
double tpPrice = take_profit > 0 ? bid - take_profit * SymbolInfoDouble(Symbol(), SYMBOL_POINT) : 0;
|
|
double slPrice = stop_loss > 0 ? bid + stop_loss * SymbolInfoDouble(Symbol(), SYMBOL_POINT) : 0;
|
|
ResetLastError();
|
|
bool opened = trade.Sell(lots_value, Symbol(), bid, slPrice, tpPrice, trade_comment);
|
|
uint retcode = trade.ResultRetcode();
|
|
if(!opened || (retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_DONE_PARTIAL && retcode != TRADE_RETCODE_PLACED))
|
|
PrintFormat("Error opening sell position: retcode=%u (%s), error=%d", retcode, trade.ResultRetcodeDescription(), GetLastError());
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CheckBreakEven()
|
|
{
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(PositionSelectByTicket(ticket))
|
|
{
|
|
if(PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == magic_number)
|
|
{
|
|
double breakEvenLevel;
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
|
|
{
|
|
breakEvenLevel = PositionGetDouble(POSITION_PRICE_OPEN) + break_even_target * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
|
|
if(SymbolInfoDouble(Symbol(), SYMBOL_BID) - PositionGetDouble(POSITION_PRICE_OPEN) >= break_even_trigger * SymbolInfoDouble(Symbol(), SYMBOL_POINT) &&
|
|
(PositionGetDouble(POSITION_SL) < PositionGetDouble(POSITION_PRICE_OPEN) || PositionGetDouble(POSITION_SL) == 0))
|
|
{
|
|
ResetLastError();
|
|
bool modified = trade.PositionModify(ticket, breakEvenLevel, PositionGetDouble(POSITION_TP));
|
|
uint retcode = trade.ResultRetcode();
|
|
if(!modified || (retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_DONE_PARTIAL && retcode != TRADE_RETCODE_PLACED))
|
|
PrintFormat("Error modifying buy position for break-even: retcode=%u (%s), error=%d", retcode, trade.ResultRetcodeDescription(), GetLastError());
|
|
}
|
|
}
|
|
else
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
|
|
{
|
|
breakEvenLevel = PositionGetDouble(POSITION_PRICE_OPEN) - break_even_target * SymbolInfoDouble(Symbol(), SYMBOL_POINT);
|
|
if(PositionGetDouble(POSITION_PRICE_OPEN) - SymbolInfoDouble(Symbol(), SYMBOL_ASK) >= break_even_trigger * SymbolInfoDouble(Symbol(), SYMBOL_POINT) &&
|
|
(PositionGetDouble(POSITION_SL) > PositionGetDouble(POSITION_PRICE_OPEN) || PositionGetDouble(POSITION_SL) == 0))
|
|
{
|
|
ResetLastError();
|
|
bool modified = trade.PositionModify(ticket, breakEvenLevel, PositionGetDouble(POSITION_TP));
|
|
uint retcode = trade.ResultRetcode();
|
|
if(!modified || (retcode != TRADE_RETCODE_DONE && retcode != TRADE_RETCODE_DONE_PARTIAL && retcode != TRADE_RETCODE_PLACED))
|
|
PrintFormat("Error modifying sell position for break-even: retcode=%u (%s), error=%d", retcode, trade.ResultRetcodeDescription(), GetLastError());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
ExecutionTrade();
|
|
|
|
if(use_break_even)
|
|
CheckBreakEven();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
if(indicator_name == "")
|
|
{
|
|
Print("Initialization failed: indicator_name is empty.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(lots_value <= 0 || shift < 0 || maximum_order < 0 || slippage_points < 0 ||
|
|
buy_stream_index < 0 || sell_stream_index < 0 || magic_number < 0 ||
|
|
take_profit < 0 || stop_loss < 0 || break_even_target < 0)
|
|
{
|
|
Print("Initialization failed: one or more numeric inputs are outside their valid range.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
if(use_break_even && break_even_trigger <= 0)
|
|
{
|
|
Print("Initialization failed: break_even_trigger must be greater than zero when break-even is enabled.");
|
|
return(INIT_PARAMETERS_INCORRECT);
|
|
}
|
|
|
|
trade.SetExpertMagicNumber(magic_number);
|
|
trade.SetDeviationInPoints(slippage_points);
|
|
trade.SetTypeFillingBySymbol(Symbol());
|
|
indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, indicator_name);
|
|
if(indicator_handle == INVALID_HANDLE)
|
|
{
|
|
Print("Failed to create handle of the indicator");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
initial_buy_signal_ignored = false;
|
|
initial_sell_signal_ignored = false;
|
|
ArrayResize(long_signal_history, 0);
|
|
ArrayResize(short_signal_history, 0);
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(indicator_handle != INVALID_HANDLE)
|
|
IndicatorRelease(indicator_handle);
|
|
ArrayResize(long_signal_history, 0);
|
|
ArrayResize(short_signal_history, 0);
|
|
Print("EA deinitialized");
|
|
}
|
|
//+------------------------------------------------------------------+
|