This commit is contained in:
Matt Corcoran
2025-07-04 16:56:12 +02:00
parent b3357f7bb7
commit 10c1d86a0c
27 changed files with 3161 additions and 2040 deletions
+52
View File
@@ -0,0 +1,52 @@
#include <Trade/Trade.mqh>
enum CUSTOM_MAX_TYPE {
CM_WIN_LOSS_RATIO,
CM_WIN_PERCENT
};
class CustomMax : public CObject {
protected:
double custom_criteria;
double win_loss_ratio(int min_required_trades);
double win_percent_min_trades(int min_required_trades);
public:
double calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades = 0);
};
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades) {
switch(cm_type) {
case CM_WIN_LOSS_RATIO:
custom_criteria = win_loss_ratio(min_trades);
break;
case CM_WIN_PERCENT:
custom_criteria = win_percent_min_trades(min_trades);
break;
default:
custom_criteria = 0;
break;
}
return custom_criteria;
}
// Returns the win/loss ratio, with min trades check
double CustomMax::win_loss_ratio(int min_required_trades) {
double wins = TesterStatistics(STAT_PROFIT_TRADES);
double losses = TesterStatistics(STAT_LOSS_TRADES);
double total_trades = TesterStatistics(STAT_TRADES);
if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0;
if(losses == 0) return 0; // Prevent division by zero
return wins / losses;
}
// Returns the win percentage, with min trades check
double CustomMax::win_percent_min_trades(int min_required_trades) {
double wins = TesterStatistics(STAT_PROFIT_TRADES);
double total_trades = TesterStatistics(STAT_TRADES);
if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0;
double result = wins / total_trades * 100;
if(!MathIsValidNumber(result)) return 0;
return result;
}
+51
View File
@@ -0,0 +1,51 @@
enum MODE_SPLIT_DATA{
NO_SPLIT,
ODD_YEARS,
EVEN_YEARS,
ODD_MONTHS,
EVEN_MONTHS,
ODD_WEEKS,
EVEN_WEEKS
};
class TestDataSplit {
public:
bool in_test_period(MODE_SPLIT_DATA data_split_method);
};
bool TestDataSplit::in_test_period(MODE_SPLIT_DATA data_split_method) {
string result[];
string string_tc = TimeToString(TimeCurrent());
// Extract components from datetime string (assumes YYYY.MM.DD format)
ushort u_sep = StringGetCharacter(".", 0);
StringSplit(string_tc, u_sep, result);
bool odd_year = int(result[0]) % 2;
bool odd_month = int(result[1]) % 2;
// Calculate week of the year (basic approximation)
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int iDay = (dt.day_of_week + 6) % 7 + 1; // Convert to 1=Mon,...,7=Sun
int iWeek = (dt.day_of_year - iDay + 10) / 7; // Estimate ISO week number
bool odd_week = iWeek % 2;
// Split logic depending on mode
if (data_split_method == NO_SPLIT)
return true;
if (data_split_method == ODD_YEARS && odd_year)
return true;
if (data_split_method == EVEN_YEARS && !odd_year)
return true;
if (data_split_method == ODD_MONTHS && odd_month)
return true;
if (data_split_method == EVEN_MONTHS && !odd_month)
return true;
if (data_split_method == ODD_WEEKS && odd_week)
return true;
if (data_split_method == EVEN_WEEKS && !odd_week)
return true;
return false;
}
-90
View File
@@ -1,90 +0,0 @@
#property library
#include <Trade/Trade.mqh>
enum CUSTOM_MAX_TYPE{
CM_WIN_LOSS_RATIO,
CM_WIN_PERCENT,
CM_WIN_PERCENT_200T,
CM_WIN_PERCENT_300T,
CM_WIN_PERCENT_400T,
CM_WIN_PERCENT_500T,
CM_WIN_PERCENT_600T,
CM_WIN_PERCENT_700T,
CM_WIN_PERCENT_800T,
CM_WIN_PERCENT_900T,
CM_WIN_PERCENT_1000T,
};
class CustomMax : public CObject{
protected:
double custom_criteria;
double CustomMax::win_loss_ratio();
double CustomMax::win_percent();
double CustomMax::win_percent_min_trades(int min_resuired_trades);
public:
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type);
};
// CM_WIN_LOSS_RATIO,
// CM_WIN_PERCENT
double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type){
if(cm_type==CM_WIN_LOSS_RATIO){
custom_criteria = win_loss_ratio();
}
if(cm_type==CM_WIN_PERCENT){
custom_criteria = win_percent_min_trades(0);
}
if(cm_type==CM_WIN_PERCENT_200T){
custom_criteria = win_percent_min_trades(200);
}
if(cm_type==CM_WIN_PERCENT_300T){
custom_criteria = win_percent_min_trades(300);
}
if(cm_type==CM_WIN_PERCENT_400T){
custom_criteria = win_percent_min_trades(400);
}
if(cm_type==CM_WIN_PERCENT_500T){
custom_criteria = win_percent_min_trades(500);
}
if(cm_type==CM_WIN_PERCENT_600T){
custom_criteria = win_percent_min_trades(600);
}
if(cm_type==CM_WIN_PERCENT_700T){
custom_criteria = win_percent_min_trades(700);
}
if(cm_type==CM_WIN_PERCENT_800T){
custom_criteria = win_percent_min_trades(800);
}
if(cm_type==CM_WIN_PERCENT_900T){
custom_criteria = win_percent_min_trades(900);
}
if(cm_type==CM_WIN_PERCENT_1000T){
custom_criteria = win_percent_min_trades(1000);
}
return custom_criteria;
}
double CustomMax::win_loss_ratio(){
double wins = TesterStatistics(STAT_PROFIT_TRADES);
double losses = TesterStatistics(STAT_LOSS_TRADES);
return wins/losses;
}
double CustomMax::win_percent_min_trades(int min_resuired_trades){
double wins = TesterStatistics(STAT_PROFIT_TRADES);
double total_trades = TesterStatistics(STAT_TRADES);
double result = wins / total_trades * 100;
if(!MathIsValidNumber(result) || total_trades<min_resuired_trades){
return 0;
}
else{
return result;
}
}
+274
View File
@@ -0,0 +1,274 @@
#include <Trade/Trade.mqh>
#include <MyLibs/Utils/TimeZones.mqh>
#include <MyLibs/Utils/MarketDataUtils.mqh>
class CalculatePositionData : public CObject{
protected:
CTrade trade;
CPositionInfo position;
MarketDataUtils mdu;
bool check_lots(double &lots, string symbol);
bool normalise_price(double price, double &normalizedPrice, string symbol);
public:
double calculate_stoploss(string symbol, double price, int order_side, string _sl_mode, double sl_var, ENUM_TIMEFRAMES atr_period);
double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_period);
double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var);
double calculate_trading_cost(string symbol, ulong position_ticket);
};
double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_period){
// order_side int must be 1 for BUY or 2 for
double sl=0;
if(mode_sl=="NO_STOPLOSS"){
sl=0;
}
if(mode_sl=="SL_BREAKEVEN"){
// https://www.youtube.com/watch?v=idPulZ3_iR0
Alert("Not implemented yet yet");
}
if(mode_sl=="SL_FIXED_PIPS"){
// pips/poins = https://www.mql5.com/en/forum/187757
double adj_point = mdu.adjusted_point(symbol);
if(order_side == 1){
sl = price - sl_var * adj_point;
if(!normalise_price(sl,sl,symbol)){return false;}
}
if(order_side == 2){
sl = price + sl_var * adj_point;
if(!normalise_price(sl,sl,symbol)){return false;}
}
}
if(mode_sl=="SL_FIXED_PERCENT"){
if(order_side == 1){
sl = (-1.0 * sl_var * price / 100.00) + price;
if(!normalise_price(sl,sl,symbol)){return false;}
}
if(order_side == 2){
sl = sl_var * price / 100.00 + price;
if(!normalise_price(sl,sl,symbol)){return false;}
}
}
if(mode_sl=="SL_ATR_MULTIPLE"){
int _atr_handle = iATR(symbol,atr_period,14);
double atr[];
ArraySetAsSeries(atr,true);
CopyBuffer(_atr_handle,MAIN_LINE,1,1,atr);
if(order_side == 1){
sl = price - (atr[0] * sl_var);
if(!normalise_price(sl,sl,symbol)){return false;}
}
if(order_side == 2){
sl = price + (atr[0] * sl_var);
if(!normalise_price(sl,sl,symbol)){return false;}
}
}
if(mode_sl=="SL_SPECIFIED_VALUE"){
double adj_point = mdu.adjusted_point(symbol);
if(order_side == 1){
double pip_50_sl = price - 10 * adj_point;
if(sl_var >= pip_50_sl){
sl = pip_50_sl;
}
else sl = sl_var;
if(!normalise_price(sl,sl,symbol)){return false;}
}
if(order_side == 2){
double pip_50_sl = price + 10 * adj_point;
if(sl_var <= pip_50_sl){
sl = pip_50_sl;
}
else sl = sl_var;
sl = sl = sl_var;
if(!normalise_price(sl,sl,symbol)){return false;}
}
}
return sl;
}
double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double _tp_var, ENUM_TIMEFRAMES atr_period){
// order_side int must be 1 for BUY or 2 for SELL
double tp=0;
if(mode_tp=="NO_TAKE_PROFIT"){
tp=0;
}
if(mode_tp=="TP_FIXED_PIPS"){
double adj_point = mdu.adjusted_point(symbol);
if(order_side == 1){
tp = price + _tp_var * adj_point;
if(!normalise_price(tp,tp,symbol)){return false;}
}
if(order_side == 2){
tp = price - _tp_var * adj_point;
if(!normalise_price(tp,tp,symbol)){return false;}
}
}
if(mode_tp=="TP_FIXED_PERCENT"){
if(order_side == 1){
tp = _tp_var * price / 100.00 + price;
if(!normalise_price(tp,tp,symbol)){return false;}
}
if(order_side == 2){
tp = (-1 * _tp_var * price / 100.00) + price;
if(!normalise_price(tp,tp,symbol)){return false;}
}
}
if(mode_tp=="TP_ATR_MULTIPLE"){
int _atr_handle = iATR(symbol,atr_period,14);
double atr[];
ArraySetAsSeries(atr,true);
CopyBuffer(_atr_handle,MAIN_LINE,1,1,atr);
if(order_side == 1){
tp = price + (atr[0] * _tp_var);
if(!normalise_price(tp,tp,symbol)){return false;}
}
if(order_side == 2){
tp = price - (atr[0] * _tp_var);
if(!normalise_price(tp,tp,symbol)){return false;}
}
}
if(mode_tp=="TP_SL_MULTIPLE"){
if(order_side == 1){
double sl_size = price - stoploss;
tp = price + (_tp_var * sl_size);
if(!normalise_price(tp,tp,symbol)){return false;}
}
if(order_side == 2){
double sl_size = stoploss - price;
tp = price - (_tp_var * sl_size);
if(!normalise_price(tp,tp,symbol)){return false;}
}
}
if(mode_tp=="TP_SPECIFIED_VALUE"){
if(_tp_var!=0){
double adj_point = mdu.adjusted_point(symbol);
if(order_side == 1){
double pip_limit = price + 10 * adj_point;
if(_tp_var <= pip_limit){
tp = pip_limit;
}
else tp = _tp_var;
if(!normalise_price(tp,tp,symbol)){return false;}
}
if(order_side == 2){
double pip_limit = price - 10 * adj_point;
if(_tp_var >= pip_limit){
tp = pip_limit;
}
else tp = _tp_var;
tp = tp = _tp_var;
if(!normalise_price(tp,tp,symbol)){return false;}
}
}
}
return tp;
}
double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var){
double lots = 0;
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE)),AccountInfoDouble(ACCOUNT_MARGIN_FREE));
double risk_money = account_value * lot_var / 100;
if(mode_lot=="LOT_MODE_FIXED"){
lots = lot_var;
}
if(mode_lot=="LOT_MODE_PCT_RISK"){
double money_lot_step = (sl_distance / tick_size) * tick_value * volume_step;
lots = MathFloor(risk_money/money_lot_step) * volume_step;
}
if(mode_lot=="LOT_MODE_PCT_ACCOUNT"){
double money_lot_step = (price / tick_size) * tick_value * volume_step;
lots = MathFloor(risk_money/money_lot_step) * volume_step;
}
if(!check_lots(lots, symbol)){return false;}
return lots;
}
bool CalculatePositionData::check_lots(double &lots, string symbol){
double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
if(lots<min){
Print("Lot size will be set to minimum allowed volume");
lots = min;
return true;
}
if(lots>max){
Print("Lot size greater than maximum allowed volume. lots:",lots,"max:",max);
return false;
}
lots = (int)MathFloor(lots/step) * step;
return true;
}
bool CalculatePositionData::normalise_price(double price, double &normalizedPrice, string symbol){
double tickSize;
if(!SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE,tickSize)){
Print("Failed to get tick size");
return false;
}
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
normalizedPrice = NormalizeDouble(MathRound(price/tickSize)*tickSize, symbol_digits);
return true;
}
double CalculatePositionData::calculate_trading_cost(string symbol, ulong position_ticket){
position.SelectByTicket(position_ticket);
double swap = PositionGetDouble(POSITION_SWAP);
double commission = PositionGetDouble(POSITION_COMMISSION);
double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
double lots = PositionGetDouble(POSITION_VOLUME);
double trading_cost = -1 * ((commission + swap) / tick_value * tick_size / lots);
return trading_cost;
}
+112
View File
@@ -0,0 +1,112 @@
#include <Trade/Trade.mqh>
#include <MyLibs/Orders/CalculatePositionData.mqh>
class EntryOrders {
protected:
CTrade trade;
CalculatePositionData calc;
double stop_loss;
double take_profit;
int total_open_buy_orders;
int total_open_sell_orders;
double current_price;
int count_open_positions(string symbol, int order_side, long magic_number);
public:
bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
bool open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
bool open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation,ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number);
bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation,ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var,string _lot_mode, double lot_var, long magic_number);
};
int EntryOrders::count_open_positions(string symbol, int order_side, long magic_number) {
int count = 0;
for (int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number) {
if ((order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ||
(order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)) {
count++;
}
}
}
return count;
}
bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long magic_number) {
if (condition) {
current_price = SymbolInfoDouble(symbol, SYMBOL_ASK);
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
if (total_open_buy_orders == 0) {
stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period);
take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
double sl_distance = current_price - stop_loss;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
trade.SetExpertMagicNumber(magic_number);
string comment = "Magic Number: " + IntegerToString(magic_number);
trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment);
}
}
return true;
}
bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode,
double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,
long magic_number) {
if (condition) {
current_price = SymbolInfoDouble(symbol, SYMBOL_BID);
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
if (total_open_sell_orders == 0) {
stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period);
take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss - current_price;
double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var);
trade.SetExpertMagicNumber(magic_number);
string comment = "Magic Number: " + IntegerToString(magic_number);
trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment);
}
}
return true;
}
bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation,
ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
double tp_var, string _lot_mode, double lot_var, long magic_number) {
if (condition) {
total_open_buy_orders = count_open_positions(symbol, 1, magic_number);
if (total_open_buy_orders == 0) {
stop_loss = calc.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period);
take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period);
double sl_distance = entry_price - stop_loss;
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
trade.SetExpertMagicNumber(magic_number);
string comment = "Magic Number: " + IntegerToString(magic_number);
trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment);
}
}
return true;
}
bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation,
ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode,
double tp_var, string _lot_mode, double lot_var, long magic_number) {
if (condition) {
total_open_sell_orders = count_open_positions(symbol, 2, magic_number);
if (total_open_sell_orders == 0) {
stop_loss = calc.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period);
take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period);
double sl_distance = stop_loss - entry_price;
double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var);
trade.SetExpertMagicNumber(magic_number);
string comment = "Magic Number: " + IntegerToString(magic_number);
trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment);
}
}
return true;
}
+153
View File
@@ -0,0 +1,153 @@
#include <Trade/Trade.mqh>
#include <MyLibs/Utils/TimeZones.mqh>
#include <MyLibs/Orders/CalculatePositionData.mqh>
class ExitOrders {
protected:
CTrade trade;
TimeZones tz;
CalculatePositionData cpd;
ulong posTicket;
long position_open_time;
long first_allowed_close_time;
public:
bool close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,long magic_number);
bool close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,long magic_number);
bool daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number);
bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number);
bool first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number);
};
bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,
long magic_number) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
posTicket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
trade.PositionClose(posTicket);
}
}
}
}
return true;
}
bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period,
long magic_number) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
posTicket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1;
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
if (condition || (close_bars > 0 && time_difference >= close_bars)) {
trade.PositionClose(posTicket);
}
}
}
}
return true;
}
bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
posTicket = PositionGetTicket(i);
position_open_time = PositionGetInteger(POSITION_TIME);
if ((int)position_open_time > 0) {
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
if (TimeCurrent() > first_allowed_close_time && TimeCurrent() >= exit_time) {
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
trade.PositionClose(posTicket);
}
}
}
}
return true;
}
bool ExitOrders::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time,
string cw_tzone, int delay_days, long magic_number) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
posTicket = PositionGetTicket(i);
position_open_time = PositionGetInteger(POSITION_TIME);
if ((int)position_open_time > 0) {
first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1));
if (TimeCurrent() > first_allowed_close_time) {
datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker");
if (TimeCurrent() >= broker_close_time &&
PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number) {
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
double bar_close = iClose(_Symbol, close_bar_period, 1); // shift 1 because 0 is live candle
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
bar_close > (position_open_price + spread + trading_cost)) {
trade.PositionClose(posTicket);
}
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
bar_close < (position_open_price - spread - trading_cost)) {
trade.PositionClose(posTicket);
}
}
}
}
}
return true;
}
bool ExitOrders::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number) {
position_open_time = PositionGetInteger(POSITION_TIME);
first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period);
if ((int)position_open_time > 0 && TimeCurrent() > first_allowed_close_time) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
posTicket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double spread = SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID);
double bar_close = iClose(_Symbol, close_bar_period, 1);
double trading_cost = cpd.calculate_trading_cost(symbol, posTicket);
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY &&
bar_close > (position_open_price + spread + trading_cost)) {
trade.PositionClose(posTicket);
}
if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL &&
bar_close < (position_open_price - spread - trading_cost)) {
trade.PositionClose(posTicket);
}
}
}
}
return true;
}
+69
View File
@@ -0,0 +1,69 @@
#include <Trade/OrderInfo.mqh>
#include <Trade/PositionInfo.mqh>
class OrderTracker {
protected:
COrderInfo m_order;
CPositionInfo m_position;
public:
int count_open_positions(string symbol, int order_side, long magic_number);
int count_all_positions(string symbol, long magic_number);
int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic);
};
int OrderTracker::count_open_positions(string symbol, int order_side, long magic_number) {
int count = 0;
for (int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number) {
if (order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) {
count++;
}
if (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) {
count++;
}
}
}
return count;
}
int OrderTracker::count_all_positions(string symbol, long magic_number) {
int count = 0;
for (int i = PositionsTotal() - 1; i >= 0; i--) {
ulong ticket = PositionGetTicket(i);
if (PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number) {
count++;
}
}
return count;
}
int OrderTracker::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic) {
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (m_order.SelectByIndex(i)) {
if (OrderGetInteger(ORDER_MAGIC) == magic && OrderGetString(ORDER_SYMBOL) == symbol) {
if (m_order.OrderType() == order_type) {
count++;
}
}
}
}
return count;
}
+21
View File
@@ -0,0 +1,21 @@
class StopLogic {
public:
double sl_specified_value_switch(string sl_mode, double inp_sl_var, double value);
double tp_specified_value_switch(string tp_mode, double inp_tp_var, double value);
};
double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, double value) {
if (sl_mode == "SL_SPECIFIED_VALUE") {
return value;
} else {
return inp_sl_var;
}
}
double StopLogic::tp_specified_value_switch(string tp_mode, double inp_tp_var, double value) {
if (tp_mode == "SL_SPECIFIED_VALUE") {
return value;
} else {
return inp_tp_var;
}
}
+95
View File
@@ -0,0 +1,95 @@
#include <Trade/Trade.mqh>
class TrailingLogic {
protected:
CTrade trade;
public:
void break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer);
void nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number);
};
void TrailingLogic::break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer) {
for (int i = PositionsTotal() - 1; i >= 0; i--) {
if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) {
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits);
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits);
if (be_trigger_points != 0) {
ulong ticket = PositionGetTicket(i);
if (PositionSelectByTicket(ticket)) {
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double position_volume = PositionGetDouble(POSITION_VOLUME);
double position_sl = PositionGetDouble(POSITION_SL);
double position_tp = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if (position_type == POSITION_TYPE_BUY &&
bid > position_open_price + be_trigger_points * symbol_point) {
double sl = NormalizeDouble(position_open_price + be_puffer * symbol_point, symbol_digits);
if (sl > position_sl) {
trade.PositionModify(ticket, sl, position_tp);
Print("-----------------------------------Stop moved to break even");
}
}
if (position_type == POSITION_TYPE_SELL &&
ask < position_open_price - be_trigger_points * symbol_point) {
double sl = NormalizeDouble(position_open_price - be_puffer * symbol_point, symbol_digits);
if (sl < position_sl) {
trade.PositionModify(ticket, sl, position_tp);
Print("-----------------------------------Stop moved to break even");
}
}
}
}
}
}
}
void TrailingLogic::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number) {
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) {
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT);
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits);
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits);
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double position_sl = PositionGetDouble(POSITION_SL);
double position_tp = PositionGetDouble(POSITION_TP);
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if (position_type == POSITION_TYPE_BUY &&
bid > position_open_price + (atr_value * tp_var)) {
double sl = NormalizeDouble(bid - (atr_value * sl_var), symbol_digits);
if (sl > (position_sl + (atr_value * 0.5))) {
trade.PositionModify(ticket, sl, position_tp);
}
}
if (position_type == POSITION_TYPE_SELL &&
ask < position_open_price - (atr_value * tp_var)) {
double sl = NormalizeDouble(ask + (atr_value * sl_var), symbol_digits);
if (sl < (position_sl + (atr_value * 0.5))) {
trade.PositionModify(ticket, sl, position_tp);
}
}
}
}
}
}
@@ -1,11 +1,11 @@
#property library #property library
#include <Trade/Trade.mqh> #include <Trade/Trade.mqh>
#include <MyLibs/MyFunctions.mqh> #include <MyLibs/Utils/BarUtils.mqh>
class DrawdownControl : public CObject { class DrawdownControl : public CObject {
protected: protected:
CTrade trade; CTrade trade;
MyFunctions mf; BarUtils bar_utils;
string data_file; string data_file;
double daily_max_dd_per; double daily_max_dd_per;
@@ -167,7 +167,7 @@ double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min
} }
// back-up to file every hour: // back-up to file every hour:
if(mf.is_new_bar(_Symbol, PERIOD_H1) == true){ if(bar_utils.is_new_bar(_Symbol, PERIOD_H1) == true){
write_global_var_data(); write_global_var_data();
} }
+111
View File
@@ -0,0 +1,111 @@
class EntryState {
public:
int last_trigger_bar_long;
int last_trigger_bar_short;
int last_bl_cross_long;
int last_bl_cross_short;
int last_entry_long;
int last_entry_short;
// Constructor
EntryState() {
reset();
}
void reset() {
last_trigger_bar_long = -1000;
last_trigger_bar_short = -1000;
last_bl_cross_long = -1000;
last_bl_cross_short = -1000;
last_entry_long = -1000;
last_entry_short = -1000;
}
void update_trigger(bool trig_long, bool trig_short, int curr_bar) {
if (trig_long) last_trigger_bar_long = curr_bar;
if (trig_short) last_trigger_bar_short = curr_bar;
}
void update_baseline_cross(int curr_bar, double price, double baseline, double prev_price, double prev_baseline) {
if (prev_price < prev_baseline && price > baseline)
last_bl_cross_long = curr_bar;
if (prev_price > prev_baseline && price < baseline)
last_bl_cross_short = curr_bar;
}
void update_entry(bool is_long, int curr_bar) {
if (is_long)
last_entry_long = curr_bar;
else
last_entry_short = curr_bar;
}
int get_last_trigger(bool is_long) {
return is_long ? last_trigger_bar_long : last_trigger_bar_short;
}
int get_last_cross(bool is_long) {
return is_long ? last_bl_cross_long : last_bl_cross_short;
}
int get_last_entry(bool is_long) {
return is_long ? last_entry_long : last_entry_short;
}
};
// Snapshot of conditions for a potential entry signal
struct EntryContext {
bool trigger;
bool confirm;
bool volume;
bool recent;
bool base_ok;
bool near;
bool far;
int last_entry;
int last_cross;
};
// Utility: check if signal occurred recently
bool is_recent(int signal_bar, int curr_bar, int lookback) {
return (curr_bar - signal_bar) < lookback;
}
// Utility: build current entry condition context
EntryContext build_entry_context(bool is_long, double price, double baseline, double atr,
int last_cross, int last_entry,
bool trigger, bool confirm, bool volume, bool recent) {
EntryContext ctx;
ctx.trigger = trigger;
ctx.confirm = confirm;
ctx.volume = volume;
ctx.recent = recent;
ctx.base_ok = is_long ? (price > baseline) : (price < baseline);
ctx.near = MathAbs(price - baseline) <= atr;
ctx.far = MathAbs(price - baseline) > atr;
ctx.last_entry = last_entry;
ctx.last_cross = last_cross;
return ctx;
}
// Class wrapper for entry logic
class EntryLogic {
public:
bool is_standard_entry(const EntryContext &ctx) {
return ctx.trigger && ctx.confirm && ctx.volume && ctx.recent && ctx.base_ok && ctx.near;
}
bool is_pullback_entry(const EntryContext &ctx, int curr_bar) {
return (curr_bar - ctx.last_cross <= 2) && ctx.confirm && ctx.volume && ctx.base_ok && ctx.far;
}
bool is_baseline_cross_entry(const EntryContext &ctx, double prev_price, double prev_baseline,double price, double baseline, bool is_long){
bool crossed = is_long ? (prev_price < prev_baseline && price > baseline): (prev_price > prev_baseline && price < baseline);
return crossed && ctx.confirm && ctx.volume && ctx.near;
}
bool is_continuation_entry(const EntryContext &ctx, int curr_bar, int look_back) {
return (curr_bar - ctx.last_entry <= look_back) && ctx.last_entry > ctx.last_cross && ctx.trigger && ctx.confirm && ctx.base_ok;
}
};
@@ -1,6 +1,6 @@
#property library #property library
#include <Trade/Trade.mqh> #include <Trade/Trade.mqh>
#include <MyLibs/TimeZones.mqh> #include <MyLibs/Utils/TimeZones.mqh>
class RangeCalculator : public CObject{ class RangeCalculator : public CObject{
+31
View File
@@ -0,0 +1,31 @@
#include <Object.mqh>
class ChartUtils : public CObject {
public:
void draw_line(double value, string name, color clr = clrBlack);
};
void ChartUtils::draw_line(double value, string name, color clr) {
if (ObjectFind(0, name) < 0) {
ResetLastError();
if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, value)) {
Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError());
return;
}
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
}
ResetLastError();
if (!ObjectMove(0, name, 0, 0, value)) {
Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError());
return;
}
ChartRedraw();
}
-12
View File
@@ -1,5 +1,3 @@
#property library
enum LOT_MODE{ enum LOT_MODE{
LOT_MODE_FIXED, // Fixed Lot Size LOT_MODE_FIXED, // Fixed Lot Size
LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed) LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed)
@@ -41,13 +39,3 @@ enum MULTI_SYM_MODE{
MULTI_SYM_FX_B5, // FX Benchmark 5 MULTI_SYM_FX_B5, // FX Benchmark 5
MULTI_SYM_FX_28 // FX 28 Majors MULTI_SYM_FX_28 // FX 28 Majors
}; };
// used to generate in and out of sample data sets
enum MODE_SPLIT_DATA{
NO_SPLIT,
ODD_YEARS,
EVEN_YEARS,
ODD_MONTHS,
EVEN_MONTHS,
ODD_WEEKS,
EVEN_WEEKS
};
@@ -1,12 +1,12 @@
#include <MyLibs/Myfunctions.mqh> #include <MyLibs/Myfunctions.mqh>
#include <MyLibs/OrderManagement.mqh> #include <MyLibs/OrderManagement.mqh>
#include <MyLibs/MyEnums.mqh> #include <MyLibs/Utils/MyEnums.mqh>
#include <MyLibs/CustomMax.mqh> #include <MyLibs/BacktestUtils/CustomMax.mqh>
CustomMax cm; #include <MyLibs/BacktestUtils/TestDataSplit.mqh>
MyFunctions mf; CustomMax c_max;
OrderManagment om; // MyFunctions mf;
// OrderManagment om;
//--- //---
string SymbolsArray[];
input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode
input double inp_lot_var = 2; // Lot Size Var input double inp_lot_var = 2; // Lot Size Var
input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode
@@ -16,8 +16,8 @@ input double inp_tp_var = 1; // Take-Profit Var
string lot_mode = EnumToString(inp_lot_mode); string lot_mode = EnumToString(inp_lot_mode);
string sl_mode = EnumToString(inp_sl_mode); string sl_mode = EnumToString(inp_sl_mode);
string tp_mode = EnumToString(inp_tp_mode); string tp_mode = EnumToString(inp_tp_mode);
input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT_200T; input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT;
input MULTI_SYM_MODE inp_sym_mode = MULTI_SYM_FX_B5; input int inp_opt_min_trades = 0; // 0/off
input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT; input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT;
input int inp_force_opt = 1; input int inp_force_opt = 1;
input group "-----------------------------------------" input group "-----------------------------------------"
+74
View File
@@ -0,0 +1,74 @@
class MarketDataUtils {
public:
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10");
double get_latest_buffer_value(int handle);
double get_buffer_value(int handle, int shift);
double adjusted_point(string symbol);
double get_bid_ask_price(string symbol, int price_side);
protected:
datetime previousTime; // Stores the last recorded bar open time
datetime bar_open_time; // Stores the current bar's open time
};
// Checks if a new bar has opened on the given timeframe and symbol
bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
bar_open_time = iTime(symbol, time_frame, 0); // Current open time
if (previousTime != bar_open_time) {
// For daily timeframe, wait for specific time (e.g., 00:10) before triggering
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
if (TimeCurrent() > StringToTime(daily_start_time)) {
previousTime = bar_open_time;
return true;
}
} else {
previousTime = bar_open_time;
return true;
}
}
return false; // No new bar
}
// Retrieves the latest value from an indicator buffer (shift 0)
double MarketDataUtils::get_latest_buffer_value(int handle) {
double val[];
ArraySetAsSeries(val, true); // Aligns array with bar indexing (0 = latest)
if (CopyBuffer(handle, 0, 0, 1, val) == 1)
return val[0]; // Latest value at shift 0
return 0.0;
}
// Retrieves a historical buffer value at specified shift
double MarketDataUtils::get_buffer_value(int handle, int shift) {
double val[];
ArraySetAsSeries(val, true);
if (CopyBuffer(handle, 0, shift, 1, val) == 1)
return val[0]; // Historical value at given shift
return 0.0;
}
// Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers)
double MarketDataUtils::adjusted_point(string symbol) {
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1;
double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT);
return point_val * digits_adjust; // Adjusted pip value
}
// Returns current Bid or Ask price for a symbol based on side (1 = Ask, 2 = Bid)
double MarketDataUtils::get_bid_ask_price(string symbol, int price_side) {
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits);
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits);
if (price_side == 1) return ask;
if (price_side == 2) return bid;
return 0.0; // Invalid input
}
+1 -1
View File
@@ -1,6 +1,6 @@
#property library #property library
#include <Trade/Trade.mqh> #include <Trade/Trade.mqh>
#include <MyLibs/DealingWithTime.mqh> #include <MyLibs/Utils/DealingWithTime.mqh>
class TimeZones: public CObject{ class TimeZones: public CObject{
+67
View File
@@ -0,0 +1,67 @@
#include <MyLibs/Utils/TimeZones.mqh>
class TradeSessionUtils {
protected:
TimeZones tz; // For handling timezone conversion
bool in_window; // Whether the current time is in the allowed window
datetime start_time; // Session start time (converted to Broker time)
datetime end_time; // Session end time (converted to Broker time)
public:
bool trade_window(string t1, string t2, string time_zone = "Broker", bool plot_range_inp = true);
};
bool TradeSessionUtils::trade_window(string t1, string t2, string time_zone, bool plot_range_inp) {
datetime _t1 = StringToTime(t1); // Convert string to datetime
datetime _t2 = StringToTime(t2); // Convert string to datetime
// Handle overnight windows (e.g. 22:0001:00)
if (_t1 > _t2) {
_t2 = _t2 + PeriodSeconds(PERIOD_D1);
}
int w_duration = (int)(_t2 - _t1); // Duration of the session in seconds
// Check if we're currently within the window
if (TimeCurrent() >= start_time && TimeCurrent() <= end_time) {
in_window = true;
}
// If we've moved beyond the previous window, define a new one
if (TimeCurrent() >= end_time) {
in_window = false;
// Convert start time to broker time based on user timezone input
start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker");
// If we've already passed today's start time, push it to tomorrow
if (TimeCurrent() >= start_time) {
start_time += PeriodSeconds(PERIOD_D1);
}
// End time is relative to updated start time
end_time = start_time + w_duration;
// Plot vertical lines if requested
if (plot_range_inp) {
string name = "Start Time" + (string)start_time;
if (start_time > 0) {
ObjectCreate(NULL, name, OBJ_VLINE, 0, start_time, 0);
ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
}
name = "End Time" + (string)end_time;
if (end_time > 0) {
ObjectCreate(NULL, name, OBJ_VLINE, 0, end_time, 0);
ObjectSetInteger(NULL, name, OBJPROP_COLOR, C'56,108,26');
ObjectSetInteger(NULL, name, OBJPROP_BACK, true);
}
ChartRedraw();
}
}
return in_window;
}
+35
View File
@@ -0,0 +1,35 @@
class BarUtils {
protected:
datetime previousTime; // Stores the previous bar open time to detect new bars
datetime bar_open_time; // Current bar open time
public:
bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10");
};
bool BarUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) {
// Get the open time of the current bar
bar_open_time = iTime(symbol, time_frame, 0);
// Check if it's different from the last seen time — this implies a new bar has formed
if (previousTime != bar_open_time) {
// Special logic for daily bars: wait until a specific time-of-day threshold
if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) {
// Don't trigger on midnight, wait until configured daily_start_time (e.g., "00:10")
if (TimeCurrent() > StringToTime(daily_start_time)) {
previousTime = bar_open_time; // Update the marker
return true;
}
} else {
// For all non-daily timeframes, treat any change in bar time as new bar
previousTime = bar_open_time;
return true;
}
}
// No new bar detected
return false;
}
+43
View File
@@ -0,0 +1,43 @@
class BufferUtils {
public:
double get_latest_buffer_value(int handle);
double get_buffer_value(int handle, int shift);
};
// ---- IMPLEMENTATION BELOW ----
/**
* Get the most recent value from the specified indicator buffer.
*
* param handle: Indicator handle (must be valid and previously created)
* return: Most recent buffer value (shift 0), or 0.0 if retrieval fails
*/
double BufferUtils::get_latest_buffer_value(int handle) {
double val[];
ArraySetAsSeries(val, true);
if (CopyBuffer(handle, 0, 0, 1, val) == 1)
return val[0];
return 0.0;
}
/**
* Get a specific historical value from the specified indicator buffer.
*
* param handle: Indicator handle
* param shift: Bar index (0 = current, 1 = previous, etc.)
* return: Buffer value at shift, or 0.0 if retrieval fails
*/
double BufferUtils::get_buffer_value(int handle, int shift) {
double val[];
ArraySetAsSeries(val, true);
if (CopyBuffer(handle, 0, shift, 1, val) == 1)
return val[0];
return 0.0;
}
+35
View File
@@ -0,0 +1,35 @@
class SymbolUtils {
public:
double adjusted_point(string symbol);
double get_bid_ask_price(string symbol, int price_side);
};
/**
* Adjusts the point size for symbols with 3 or 5 digits (e.g. JPY pairs or fractional pips).
* Example: if symbol has 5 digits, 1 pip = 10 points.
*/
double SymbolUtils::adjusted_point(string symbol) {
int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1;
double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT);
return point_val * digits_adjust;
}
/**
* Returns either bid or ask price for a symbol, normalised to correct digits.
*
* param price_side: 1 for ASK, 2 for BID
*/
double SymbolUtils::get_bid_ask_price(string symbol, int price_side) {
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits);
double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits);
if (price_side == 1) return ask;
if (price_side == 2) return bid;
return 0.0; // fallback if invalid side passed
}
Binary file not shown.