From 10c1d86a0c3e9d2f1ef80351aea09bd34d4906ef Mon Sep 17 00:00:00 2001 From: Matt Corcoran Date: Fri, 4 Jul 2025 16:56:12 +0200 Subject: [PATCH] update --- BacktestUtils/CustomMax.mqh | 52 + BacktestUtils/TestDataSplit.mqh | 51 + CustomMax.mqh | 90 -- Orders/CalculatePositionData.mqh | 274 ++++ Orders/EntryOrders.mqh | 112 ++ Orders/ExitOrders.mqh | 153 +++ Orders/OrderTracker.mqh | 69 + Orders/StopLogic.mqh | 21 + Orders/TrailingLogic.mqh | 95 ++ .../DrawdownControl.mqh | 410 +++--- Strategy/EntryLogic_nnfx.mqh | 111 ++ .../Range/RangeCalculator.mqh | 828 ++++++------ Utils/ChartUtils.mqh | 31 + .../DealingWithTime.mqh | Bin MyEnums.mqh => Utils/Enums.mqh | 94 +- .../ExpertFileHeader.mqh | 56 +- Utils/MarketDataUtils.mqh | 74 ++ TimeZones.mqh => Utils/TimeZones.mqh | 286 ++--- Utils/TradeWindow.mqh | 67 + _old/BarUtils.mqh | 35 + _old/BufferUtils.mqh | 43 + .../CalculatePositionData.mqh | 554 ++++---- MyFunctions.mqh => _old/MyFunctions.mqh | 420 +++---- .../OrderManagement.mqh | 1110 ++++++++--------- _old/SymbolUtils.mqh | 35 + TradingWindow.mqh => _old/TradingWindow.mqh | 130 +- _old/_MyLibs_old.zip | Bin 0 -> 26431 bytes 27 files changed, 3161 insertions(+), 2040 deletions(-) create mode 100644 BacktestUtils/CustomMax.mqh create mode 100644 BacktestUtils/TestDataSplit.mqh delete mode 100644 CustomMax.mqh create mode 100644 Orders/CalculatePositionData.mqh create mode 100644 Orders/EntryOrders.mqh create mode 100644 Orders/ExitOrders.mqh create mode 100644 Orders/OrderTracker.mqh create mode 100644 Orders/StopLogic.mqh create mode 100644 Orders/TrailingLogic.mqh rename DrawdownControl.mqh => RiskManagement/DrawdownControl.mqh (96%) create mode 100644 Strategy/EntryLogic_nnfx.mqh rename RangeCalculator.mqh => Strategy/Range/RangeCalculator.mqh (96%) create mode 100644 Utils/ChartUtils.mqh rename DealingWithTime.mqh => Utils/DealingWithTime.mqh (100%) rename MyEnums.mqh => Utils/Enums.mqh (82%) rename NNFX_TestFileHeader.mqh => Utils/ExpertFileHeader.mqh (73%) create mode 100644 Utils/MarketDataUtils.mqh rename TimeZones.mqh => Utils/TimeZones.mqh (96%) create mode 100644 Utils/TradeWindow.mqh create mode 100644 _old/BarUtils.mqh create mode 100644 _old/BufferUtils.mqh rename CalculatePositionData.mqh => _old/CalculatePositionData.mqh (97%) rename MyFunctions.mqh => _old/MyFunctions.mqh (96%) rename OrderManagement.mqh => _old/OrderManagement.mqh (97%) create mode 100644 _old/SymbolUtils.mqh rename TradingWindow.mqh => _old/TradingWindow.mqh (96%) create mode 100644 _old/_MyLibs_old.zip diff --git a/BacktestUtils/CustomMax.mqh b/BacktestUtils/CustomMax.mqh new file mode 100644 index 0000000..2d411dd --- /dev/null +++ b/BacktestUtils/CustomMax.mqh @@ -0,0 +1,52 @@ +#include + +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; +} diff --git a/BacktestUtils/TestDataSplit.mqh b/BacktestUtils/TestDataSplit.mqh new file mode 100644 index 0000000..9458589 --- /dev/null +++ b/BacktestUtils/TestDataSplit.mqh @@ -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; +} diff --git a/CustomMax.mqh b/CustomMax.mqh deleted file mode 100644 index fb6e8d1..0000000 --- a/CustomMax.mqh +++ /dev/null @@ -1,90 +0,0 @@ -#property library -#include - -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 +#include +#include + +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(lotsmax){ + 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; +} \ No newline at end of file diff --git a/Orders/EntryOrders.mqh b/Orders/EntryOrders.mqh new file mode 100644 index 0000000..e38c2d9 --- /dev/null +++ b/Orders/EntryOrders.mqh @@ -0,0 +1,112 @@ +#include +#include + +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; +} diff --git a/Orders/ExitOrders.mqh b/Orders/ExitOrders.mqh new file mode 100644 index 0000000..e60bc35 --- /dev/null +++ b/Orders/ExitOrders.mqh @@ -0,0 +1,153 @@ +#include +#include +#include + +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; +} diff --git a/Orders/OrderTracker.mqh b/Orders/OrderTracker.mqh new file mode 100644 index 0000000..8ab8eb1 --- /dev/null +++ b/Orders/OrderTracker.mqh @@ -0,0 +1,69 @@ +#include +#include + +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; +} diff --git a/Orders/StopLogic.mqh b/Orders/StopLogic.mqh new file mode 100644 index 0000000..e39e661 --- /dev/null +++ b/Orders/StopLogic.mqh @@ -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; + } +} diff --git a/Orders/TrailingLogic.mqh b/Orders/TrailingLogic.mqh new file mode 100644 index 0000000..88a73c4 --- /dev/null +++ b/Orders/TrailingLogic.mqh @@ -0,0 +1,95 @@ +#include + +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); + } + } + } + } + } +} diff --git a/DrawdownControl.mqh b/RiskManagement/DrawdownControl.mqh similarity index 96% rename from DrawdownControl.mqh rename to RiskManagement/DrawdownControl.mqh index bd8280c..862e27a 100644 --- a/DrawdownControl.mqh +++ b/RiskManagement/DrawdownControl.mqh @@ -1,205 +1,205 @@ -#property library -#include -#include - -class DrawdownControl : public CObject { - protected: - CTrade trade; - MyFunctions mf; - - string data_file; - double daily_max_dd_per; - string daily_reset_time; - bool print_statments; - - double acc_max_dd_per; - double equaty_control_high; - double equaty_control_low; - - - double daily_equity_start; - double daily_max_dd_target; - bool daily_dd_limit_reached; - - bool write_global_var_data(); - bool print_messages(); - - public: - void init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true); - bool determine_daily_dd_limit(); - double lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20); - double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor); -}; - -void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) { - - data_file = inp_data_file; - acc_max_dd_per = inp_acc_max_dd_per; - daily_max_dd_per = inp_daily_max_dd_per; - daily_reset_time = inp_daily_reset_time; - print_statments = inp_print_statments; - - // If no data file exisits, create one and set global vairiables: - if(FileIsExist(data_file) == false) { - daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); - daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); - daily_dd_limit_reached = false; - equaty_control_high = 9999999; - equaty_control_low = 0; - write_global_var_data(); - } - // If file exisits read file: - if(FileIsExist(data_file) == true) { - - int file_handle = FileOpen(data_file, FILE_READ | FILE_ANSI | FILE_TXT); - if(file_handle == INVALID_HANDLE) { - Print("Error opening file: ", data_file); - } - - // If data file is older than 24h 10min create a new file and reset global vars: - long modifided_date = FileGetInteger(file_handle, FILE_MODIFY_DATE); - long time_delta = ((long)TimeCurrent() - modifided_date) / 60; - - if(time_delta >= 1450) { - daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); - daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); - daily_dd_limit_reached = false; - equaty_control_high = equaty_control_high; - equaty_control_low = equaty_control_low; - write_global_var_data(); - Print(data_file, " is older than 24h and 10min; global vars reset!"); - } - // If data file is younger than 24h+10 min read data and set global vars: - else { - daily_equity_start = (double)FileReadString(file_handle, 0); - daily_max_dd_target = (double)FileReadString(file_handle, 1); - daily_dd_limit_reached = FileReadBool(file_handle); - equaty_control_high = (double)FileReadString(file_handle, 3); - equaty_control_low = (double)FileReadString(file_handle, 4);; - } - FileClose(file_handle); - } - print_messages(); -} - -bool DrawdownControl::determine_daily_dd_limit() { - - // Reset max equity at the start of each day: - string ct = TimeToString(TimeCurrent(), TIME_MINUTES); - if(ct == daily_reset_time) { - daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); - daily_max_dd_target = (daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100))); - daily_dd_limit_reached = false; - write_global_var_data(); - print_messages(); - } - - // If in drawdown close all positions and delete orders - if(daily_dd_limit_reached || AccountInfoDouble(ACCOUNT_EQUITY) <= daily_max_dd_target) { - - if(daily_dd_limit_reached == false) { - daily_dd_limit_reached = true; - write_global_var_data(); - print_messages(); - } - - for(int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); - trade.PositionClose(ticket); - } - - for(int i = OrdersTotal() - 1; i >= 0; i--) { - ulong ticket = OrderGetTicket(i); - trade.OrderDelete(ticket); - } - } - return daily_dd_limit_reached; -} - -// Reduces lot size as account apporchaes max allowed drawdown limit. -double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) { - - double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); - double lot_factor; - - // Interpolate to find lot factor between given min and max values. - if (account_value < acc_equity_start){ - - double acc_equity_min = acc_equity_start - (acc_equity_start * (acc_max_dd_per / 100)); - double y1 = min_lot_factor; - double y2 = max_lot_factor; - double x1 = acc_equity_min; - double x = account_value; - double x2 = acc_equity_start; - lot_factor = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); - } - - else if(account_value >= acc_equity_start) { - - if(dynm_lot_factor=true){ - lot_factor = lot_correction_dynamic(dlf_trail_per, min_lot_factor, max_lot_factor); - } - - else { - lot_factor = max_lot_factor; - } - } - return max_lot_factor; -} - - -double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) { - - double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); - double trail_point = account_value - (account_value * (acc_dd_percent / 100)); - - if(equaty_control_low < trail_point){ - equaty_control_low = trail_point; - } - - if(equaty_control_high < account_value){ - equaty_control_high = account_value; - } - - if(account_value < equaty_control_low){ - equaty_control_low = account_value; - equaty_control_high = account_value + (account_value * (acc_dd_percent / 100)); - } - - // back-up to file every hour: - if(mf.is_new_bar(_Symbol, PERIOD_H1) == true){ - write_global_var_data(); - } - - // Linear interpolation: - double y1 = min_lot_factor; - double y2 = max_lot_factor; - double x1 = equaty_control_low; - double x = account_value; - double x2 = equaty_control_high; - - double y = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); - - return y; -} - -bool DrawdownControl::write_global_var_data() { - int file_handle = FileOpen(data_file, FILE_WRITE | FILE_ANSI | FILE_TXT); - FileWrite(file_handle, daily_equity_start); - FileWrite(file_handle, daily_max_dd_target); - FileWrite(file_handle, daily_dd_limit_reached); - FileClose(file_handle); - Print(data_file, " written"); - return true; -} - -bool DrawdownControl::print_messages() { - if(print_statments == true) { - Print("TimeCurrent(): ", TimeToString(TimeCurrent())); - Print("Daily Equity Start: ", (int)daily_equity_start); - Print("Current Equity: ", (int)AccountInfoDouble(ACCOUNT_EQUITY)); - Print("Daily Drawdown Limit: ", (int)daily_max_dd_target, " (", daily_max_dd_per, "%) of DES"); - Print("Daily Drawdown Limit Hit: ", daily_dd_limit_reached); - } - return true; -} +#property library +#include +#include + +class DrawdownControl : public CObject { + protected: + CTrade trade; + BarUtils bar_utils; + + string data_file; + double daily_max_dd_per; + string daily_reset_time; + bool print_statments; + + double acc_max_dd_per; + double equaty_control_high; + double equaty_control_low; + + + double daily_equity_start; + double daily_max_dd_target; + bool daily_dd_limit_reached; + + bool write_global_var_data(); + bool print_messages(); + + public: + void init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true); + bool determine_daily_dd_limit(); + double lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20); + double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor); +}; + +void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) { + + data_file = inp_data_file; + acc_max_dd_per = inp_acc_max_dd_per; + daily_max_dd_per = inp_daily_max_dd_per; + daily_reset_time = inp_daily_reset_time; + print_statments = inp_print_statments; + + // If no data file exisits, create one and set global vairiables: + if(FileIsExist(data_file) == false) { + daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); + daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); + daily_dd_limit_reached = false; + equaty_control_high = 9999999; + equaty_control_low = 0; + write_global_var_data(); + } + // If file exisits read file: + if(FileIsExist(data_file) == true) { + + int file_handle = FileOpen(data_file, FILE_READ | FILE_ANSI | FILE_TXT); + if(file_handle == INVALID_HANDLE) { + Print("Error opening file: ", data_file); + } + + // If data file is older than 24h 10min create a new file and reset global vars: + long modifided_date = FileGetInteger(file_handle, FILE_MODIFY_DATE); + long time_delta = ((long)TimeCurrent() - modifided_date) / 60; + + if(time_delta >= 1450) { + daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); + daily_max_dd_target = daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100)); + daily_dd_limit_reached = false; + equaty_control_high = equaty_control_high; + equaty_control_low = equaty_control_low; + write_global_var_data(); + Print(data_file, " is older than 24h and 10min; global vars reset!"); + } + // If data file is younger than 24h+10 min read data and set global vars: + else { + daily_equity_start = (double)FileReadString(file_handle, 0); + daily_max_dd_target = (double)FileReadString(file_handle, 1); + daily_dd_limit_reached = FileReadBool(file_handle); + equaty_control_high = (double)FileReadString(file_handle, 3); + equaty_control_low = (double)FileReadString(file_handle, 4);; + } + FileClose(file_handle); + } + print_messages(); +} + +bool DrawdownControl::determine_daily_dd_limit() { + + // Reset max equity at the start of each day: + string ct = TimeToString(TimeCurrent(), TIME_MINUTES); + if(ct == daily_reset_time) { + daily_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); + daily_max_dd_target = (daily_equity_start - (daily_equity_start * (daily_max_dd_per / 100))); + daily_dd_limit_reached = false; + write_global_var_data(); + print_messages(); + } + + // If in drawdown close all positions and delete orders + if(daily_dd_limit_reached || AccountInfoDouble(ACCOUNT_EQUITY) <= daily_max_dd_target) { + + if(daily_dd_limit_reached == false) { + daily_dd_limit_reached = true; + write_global_var_data(); + print_messages(); + } + + for(int i = PositionsTotal() - 1; i >= 0; i--) { + ulong ticket = PositionGetTicket(i); + trade.PositionClose(ticket); + } + + for(int i = OrdersTotal() - 1; i >= 0; i--) { + ulong ticket = OrderGetTicket(i); + trade.OrderDelete(ticket); + } + } + return daily_dd_limit_reached; +} + +// Reduces lot size as account apporchaes max allowed drawdown limit. +double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) { + + double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); + double lot_factor; + + // Interpolate to find lot factor between given min and max values. + if (account_value < acc_equity_start){ + + double acc_equity_min = acc_equity_start - (acc_equity_start * (acc_max_dd_per / 100)); + double y1 = min_lot_factor; + double y2 = max_lot_factor; + double x1 = acc_equity_min; + double x = account_value; + double x2 = acc_equity_start; + lot_factor = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); + } + + else if(account_value >= acc_equity_start) { + + if(dynm_lot_factor=true){ + lot_factor = lot_correction_dynamic(dlf_trail_per, min_lot_factor, max_lot_factor); + } + + else { + lot_factor = max_lot_factor; + } + } + return max_lot_factor; +} + + +double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) { + + double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); + double trail_point = account_value - (account_value * (acc_dd_percent / 100)); + + if(equaty_control_low < trail_point){ + equaty_control_low = trail_point; + } + + if(equaty_control_high < account_value){ + equaty_control_high = account_value; + } + + if(account_value < equaty_control_low){ + equaty_control_low = account_value; + equaty_control_high = account_value + (account_value * (acc_dd_percent / 100)); + } + + // back-up to file every hour: + if(bar_utils.is_new_bar(_Symbol, PERIOD_H1) == true){ + write_global_var_data(); + } + + // Linear interpolation: + double y1 = min_lot_factor; + double y2 = max_lot_factor; + double x1 = equaty_control_low; + double x = account_value; + double x2 = equaty_control_high; + + double y = y1 + (x - x1) * ((y2 - y1) / (x2 - x1)); + + return y; +} + +bool DrawdownControl::write_global_var_data() { + int file_handle = FileOpen(data_file, FILE_WRITE | FILE_ANSI | FILE_TXT); + FileWrite(file_handle, daily_equity_start); + FileWrite(file_handle, daily_max_dd_target); + FileWrite(file_handle, daily_dd_limit_reached); + FileClose(file_handle); + Print(data_file, " written"); + return true; +} + +bool DrawdownControl::print_messages() { + if(print_statments == true) { + Print("TimeCurrent(): ", TimeToString(TimeCurrent())); + Print("Daily Equity Start: ", (int)daily_equity_start); + Print("Current Equity: ", (int)AccountInfoDouble(ACCOUNT_EQUITY)); + Print("Daily Drawdown Limit: ", (int)daily_max_dd_target, " (", daily_max_dd_per, "%) of DES"); + Print("Daily Drawdown Limit Hit: ", daily_dd_limit_reached); + } + return true; +} diff --git a/Strategy/EntryLogic_nnfx.mqh b/Strategy/EntryLogic_nnfx.mqh new file mode 100644 index 0000000..6592bd4 --- /dev/null +++ b/Strategy/EntryLogic_nnfx.mqh @@ -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; + } +}; diff --git a/RangeCalculator.mqh b/Strategy/Range/RangeCalculator.mqh similarity index 96% rename from RangeCalculator.mqh rename to Strategy/Range/RangeCalculator.mqh index aa62fc5..97b71d3 100644 --- a/RangeCalculator.mqh +++ b/Strategy/Range/RangeCalculator.mqh @@ -1,414 +1,414 @@ -#property library -#include -#include - -class RangeCalculator : public CObject{ - - protected: - TimeZones tz; - - bool days_initlised; - bool range_initlised; - string symbol; - ENUM_TIMEFRAMES calc_period; - - string inp_r_start_string; - int r_duration; - int r_expire; - int r_close; - string inp_timezone; - - bool sun; - bool mon; - bool tue; - bool wed; - bool thu; - bool fri; - bool sat; - bool plot_range; - datetime start_time; // Start of the range - datetime end_time; // end of the range - datetime order_expire_time; // end of the range - datetime close_time; // Close time - double high; // high of the range - double low; // low of the range - double mid; // mid of the range - bool f_entry; // flag if we are inside of the range - bool f_high_breakout; // flag if a high breakout occurred - bool f_low_breakout; // flag if a low breakout occurred - bool above_last; - bool above_current; - bool below_last; - bool below_current; - - // private functions - void update_objects(); - void draw_objects(); - void define_new_range(); - bool convert_input_time_strings(string t1, string t2, string t3, string t4); - - - public: - void calculate_range(); - - double get_range_high(); - double get_range_low(); - double get_range_mid(); - datetime get_range_start(); - datetime get_range_end(); - datetime get_order_expire_time(); - datetime get_range_close(); - bool get_range_high_breakout(); - bool get_range_low_breakout(); - bool initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t0, string t1, string t2, string t3, string time_zone, bool plot_range_inp); - void range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat); - -}; - -void RangeCalculator::range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat){ - sun = _inp_sun; - mon = _inp_mon; - tue = _inp_tue; - wed = _inp_wed; - thu = _inp_thu; - fri = _inp_fri; - sat = _inp_sat; - days_initlised = true; -} - -bool RangeCalculator::initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t1, string t2, string t3, string t4, string time_zone, bool plot_range_inp){ - inp_r_start_string = t1; - inp_timezone = time_zone; - symbol = inp_symbol; - calc_period =_calc_period; - plot_range = plot_range_inp; - start_time = 0; - end_time = 0; - close_time = 0; - high = 0; - low = DBL_MAX; - mid = 0; - f_entry = false; - f_high_breakout = false; - f_low_breakout = false; - above_last = false; - above_current= false; - below_last= false; - below_current= false; - if(!days_initlised){ - sun = true; - mon = true; - tue = true; - wed = true; - thu = true; - fri = true; - sat = true; - } - range_initlised = true; - - bool corret_inputs = convert_input_time_strings(t1, t2, t3, t4); - if(corret_inputs = false){ - return false; - } - return true; -} - - -bool RangeCalculator::convert_input_time_strings(string t1, string t2, string t3, string t4){ - - datetime _t1 = StringToTime(t1); - datetime _t2 = StringToTime(t2); - datetime _t3 = StringToTime(t3); - datetime _t4 = StringToTime(t4); - - - if(_t1 > _t2){ - _t2 = _t2 + PeriodSeconds(PERIOD_D1); - _t3 = _t3 + PeriodSeconds(PERIOD_D1); - _t4 = _t4 + PeriodSeconds(PERIOD_D1); - } - - if(_t2 > _t3){ - _t3 = _t3 + PeriodSeconds(PERIOD_D1); - _t4 = _t4 + PeriodSeconds(PERIOD_D1); - } - - if(_t3 > _t4){ - _t4 = _t4 + PeriodSeconds(PERIOD_D1); - } - - r_duration = (int)(_t2 - _t1); - r_expire = (int)(_t3 - _t1); - r_close = (int)(_t4 - _t1); - - if(_t4 - _t1 >= PeriodSeconds(PERIOD_D1)){ - Alert("INCORRECT RANGE INPUTS!"); - return false; - } - - return true; -} - -// high of the range -double RangeCalculator::get_range_high(){ - return high; -}; - -// low of the range -double RangeCalculator::get_range_low(){ - return low; -}; - -// mid of the range -double RangeCalculator::get_range_mid(){ - return mid; -}; - - -datetime RangeCalculator::get_range_start(){ - return start_time; -}; - -datetime RangeCalculator::get_range_end(){ - return end_time; -}; - -datetime RangeCalculator::get_order_expire_time(){ - return order_expire_time; -}; - -datetime RangeCalculator::get_range_close(){ - return close_time; -}; - -// flag if a high breakout occurred -bool RangeCalculator::get_range_high_breakout(){ - return f_high_breakout; -}; - -// flag if a low breakout occurred -bool RangeCalculator::get_range_low_breakout(){ - return f_low_breakout; -}; - - -void RangeCalculator::calculate_range(){ - - f_high_breakout = false; - f_low_breakout = false; - - double last_bar_high = iHigh(symbol, calc_period, 1); // shift 1 because 0 = live candle: - double last_bar_low = iLow(symbol, calc_period, 1); // shift 1 because 0 = live candle: - - // range calculation - if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){ - - // set flag - f_entry = true; - - // new high - if(last_bar_high > high){ - high = last_bar_high; - mid = (high + low)/2; - if(plot_range){ - update_objects(); - } - } - - // new low - if(last_bar_low < low){ - low = last_bar_low; - mid = (high + low)/2; - if(plot_range){ - update_objects(); - } - } - } - - // calculate new reange if - if( (TimeCurrent() >= close_time) // close time reached - || (end_time == 0) // range not calculated yet - || (end_time !=0 && TimeCurrent() > end_time && !f_entry) // there was a range calculated but no tick inside. - ){ - define_new_range(); - } - - // check if we are after the range end - if(TimeCurrent() >= end_time && end_time > 0 && f_entry){ - - if(!f_high_breakout && last_bar_high >= high){ - above_last = above_current; - above_current= true; - - if(above_last==false && above_current == true){ - f_high_breakout = true; - } - else(f_high_breakout = false); - } - - if(!f_low_breakout && last_bar_low >= low){ - below_last = below_current; - below_current = true; - if(below_last == false && below_current == true){ - f_low_breakout = true; - } - else(f_low_breakout = false); - } - - } -} - -void RangeCalculator::define_new_range(){ - - // reset range vars - start_time = 0; - end_time = 0; - order_expire_time = 0; - close_time = 0; - high = 0; - low = INT_MAX; - mid = 0; - f_entry = false; - - // calculate range start time: - datetime r_st = StringToTime(inp_r_start_string); - start_time = tz.timezone_conversions(inp_timezone, r_st, "Broker"); - - - for(int i=0; i<8; i++){ - - MqlDateTime tmp; - TimeToStruct(start_time,tmp); - int dow = tmp.day_of_week; - - if(TimeCurrent()>=start_time - || (dow==0 && !sun) - || (dow==1 && !mon) - || (dow==2 && !tue) - || (dow==3 && !wed) - || (dow==4 && !thu) - || (dow==5 && !fri) - || (dow==6 && !sat) - ){ - start_time += PeriodSeconds(PERIOD_D1); - } - } - - - end_time = start_time + r_duration; - order_expire_time = start_time + r_expire; - close_time = start_time + r_close; - - if(plot_range){ - draw_objects(); - } -} - -void RangeCalculator::update_objects(){ - - string name = "Range Mid " + (string)start_time; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, mid); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, mid); - // ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid"); - - name = "Order expire " + (string)order_expire_time; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - - name = "Range start " + (string)start_time; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - - name = "Range end " + (string)end_time; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - - datetime rct = r_close>=0 ? close_time : INT_MAX; - name = "Range close " + (string)rct; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - - name = "Range High " + (string)rct; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, high); - - name = "Range Low " + (string)rct; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, low); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - - name = "range box "+ (string)start_time; - ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); - ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,0, high); - ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,1, low); - -} - -void RangeCalculator::draw_objects(){ - - datetime rct = r_close>=0 ? close_time : INT_MAX; - - // Range mid line - string name = "Range Mid " + (string)start_time;; - ObjectCreate(NULL, name, OBJ_TREND, 0, start_time, mid, rct, mid); - ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid" + (string)mid); - ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrGray); - ObjectSetInteger(NULL, name, OBJPROP_WIDTH, 1); - ObjectSetInteger(NULL, name, OBJPROP_STYLE, STYLE_DOT); - - // order lines - string name2 = "Order expire " + (string)order_expire_time; - ObjectCreate(NULL, name2, OBJ_TREND, 0, order_expire_time, low, order_expire_time, high); - ObjectSetString(NULL, name2, OBJPROP_TOOLTIP, "start of the range \n" + TimeToString(order_expire_time,TIME_DATE|TIME_MINUTES)); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, C'139,41,41'); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); - - name2 = "Range start " + (string)start_time; - ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, start_time, high); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); - - name2 = "Range end " + (string)end_time; - ObjectCreate(NULL, name2, OBJ_TREND, 0, end_time, low, end_time, high); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); - - name2 = "Range close " + (string)rct; - ObjectCreate(NULL, name2, OBJ_TREND, 0, rct, low, rct, high); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); - - name2 = "Range High " + (string)rct; - ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, high, rct, high); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); - - name2 = "Range Low " + (string)rct; - ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, rct, low); - ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); - ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); - ObjectSetInteger(NULL, name2 ,OBJPROP_BACK, true); - - // Box - name = "range box " + (string)start_time; - ObjectCreate(NULL, name, OBJ_RECTANGLE, 0, start_time, high, end_time, low); - ObjectSetString(NULL,name,OBJPROP_TOOLTIP,"\n"); - ObjectSetInteger(NULL, name,OBJPROP_COLOR, C'128,177,173'); - ObjectSetInteger(NULL, name,OBJPROP_FILL, true); - ObjectSetInteger(NULL, name,OBJPROP_BACK, true); - - ObjectCreate(NULL, name + " ", OBJ_RECTANGLE, 0, end_time, high, rct, low); - ObjectSetString(NULL, name+ " ", OBJPROP_TOOLTIP, "\n"); - ObjectSetInteger(NULL, name + " ",OBJPROP_FILL, true); - ObjectSetInteger(NULL, name + " ",OBJPROP_COLOR, C'165,220,215' ); - ObjectSetInteger(NULL, name + " ",OBJPROP_BACK, true); - - ChartRedraw(); -} - - +#property library +#include +#include + +class RangeCalculator : public CObject{ + + protected: + TimeZones tz; + + bool days_initlised; + bool range_initlised; + string symbol; + ENUM_TIMEFRAMES calc_period; + + string inp_r_start_string; + int r_duration; + int r_expire; + int r_close; + string inp_timezone; + + bool sun; + bool mon; + bool tue; + bool wed; + bool thu; + bool fri; + bool sat; + bool plot_range; + datetime start_time; // Start of the range + datetime end_time; // end of the range + datetime order_expire_time; // end of the range + datetime close_time; // Close time + double high; // high of the range + double low; // low of the range + double mid; // mid of the range + bool f_entry; // flag if we are inside of the range + bool f_high_breakout; // flag if a high breakout occurred + bool f_low_breakout; // flag if a low breakout occurred + bool above_last; + bool above_current; + bool below_last; + bool below_current; + + // private functions + void update_objects(); + void draw_objects(); + void define_new_range(); + bool convert_input_time_strings(string t1, string t2, string t3, string t4); + + + public: + void calculate_range(); + + double get_range_high(); + double get_range_low(); + double get_range_mid(); + datetime get_range_start(); + datetime get_range_end(); + datetime get_order_expire_time(); + datetime get_range_close(); + bool get_range_high_breakout(); + bool get_range_low_breakout(); + bool initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t0, string t1, string t2, string t3, string time_zone, bool plot_range_inp); + void range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat); + +}; + +void RangeCalculator::range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat){ + sun = _inp_sun; + mon = _inp_mon; + tue = _inp_tue; + wed = _inp_wed; + thu = _inp_thu; + fri = _inp_fri; + sat = _inp_sat; + days_initlised = true; +} + +bool RangeCalculator::initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t1, string t2, string t3, string t4, string time_zone, bool plot_range_inp){ + inp_r_start_string = t1; + inp_timezone = time_zone; + symbol = inp_symbol; + calc_period =_calc_period; + plot_range = plot_range_inp; + start_time = 0; + end_time = 0; + close_time = 0; + high = 0; + low = DBL_MAX; + mid = 0; + f_entry = false; + f_high_breakout = false; + f_low_breakout = false; + above_last = false; + above_current= false; + below_last= false; + below_current= false; + if(!days_initlised){ + sun = true; + mon = true; + tue = true; + wed = true; + thu = true; + fri = true; + sat = true; + } + range_initlised = true; + + bool corret_inputs = convert_input_time_strings(t1, t2, t3, t4); + if(corret_inputs = false){ + return false; + } + return true; +} + + +bool RangeCalculator::convert_input_time_strings(string t1, string t2, string t3, string t4){ + + datetime _t1 = StringToTime(t1); + datetime _t2 = StringToTime(t2); + datetime _t3 = StringToTime(t3); + datetime _t4 = StringToTime(t4); + + + if(_t1 > _t2){ + _t2 = _t2 + PeriodSeconds(PERIOD_D1); + _t3 = _t3 + PeriodSeconds(PERIOD_D1); + _t4 = _t4 + PeriodSeconds(PERIOD_D1); + } + + if(_t2 > _t3){ + _t3 = _t3 + PeriodSeconds(PERIOD_D1); + _t4 = _t4 + PeriodSeconds(PERIOD_D1); + } + + if(_t3 > _t4){ + _t4 = _t4 + PeriodSeconds(PERIOD_D1); + } + + r_duration = (int)(_t2 - _t1); + r_expire = (int)(_t3 - _t1); + r_close = (int)(_t4 - _t1); + + if(_t4 - _t1 >= PeriodSeconds(PERIOD_D1)){ + Alert("INCORRECT RANGE INPUTS!"); + return false; + } + + return true; +} + +// high of the range +double RangeCalculator::get_range_high(){ + return high; +}; + +// low of the range +double RangeCalculator::get_range_low(){ + return low; +}; + +// mid of the range +double RangeCalculator::get_range_mid(){ + return mid; +}; + + +datetime RangeCalculator::get_range_start(){ + return start_time; +}; + +datetime RangeCalculator::get_range_end(){ + return end_time; +}; + +datetime RangeCalculator::get_order_expire_time(){ + return order_expire_time; +}; + +datetime RangeCalculator::get_range_close(){ + return close_time; +}; + +// flag if a high breakout occurred +bool RangeCalculator::get_range_high_breakout(){ + return f_high_breakout; +}; + +// flag if a low breakout occurred +bool RangeCalculator::get_range_low_breakout(){ + return f_low_breakout; +}; + + +void RangeCalculator::calculate_range(){ + + f_high_breakout = false; + f_low_breakout = false; + + double last_bar_high = iHigh(symbol, calc_period, 1); // shift 1 because 0 = live candle: + double last_bar_low = iLow(symbol, calc_period, 1); // shift 1 because 0 = live candle: + + // range calculation + if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){ + + // set flag + f_entry = true; + + // new high + if(last_bar_high > high){ + high = last_bar_high; + mid = (high + low)/2; + if(plot_range){ + update_objects(); + } + } + + // new low + if(last_bar_low < low){ + low = last_bar_low; + mid = (high + low)/2; + if(plot_range){ + update_objects(); + } + } + } + + // calculate new reange if + if( (TimeCurrent() >= close_time) // close time reached + || (end_time == 0) // range not calculated yet + || (end_time !=0 && TimeCurrent() > end_time && !f_entry) // there was a range calculated but no tick inside. + ){ + define_new_range(); + } + + // check if we are after the range end + if(TimeCurrent() >= end_time && end_time > 0 && f_entry){ + + if(!f_high_breakout && last_bar_high >= high){ + above_last = above_current; + above_current= true; + + if(above_last==false && above_current == true){ + f_high_breakout = true; + } + else(f_high_breakout = false); + } + + if(!f_low_breakout && last_bar_low >= low){ + below_last = below_current; + below_current = true; + if(below_last == false && below_current == true){ + f_low_breakout = true; + } + else(f_low_breakout = false); + } + + } +} + +void RangeCalculator::define_new_range(){ + + // reset range vars + start_time = 0; + end_time = 0; + order_expire_time = 0; + close_time = 0; + high = 0; + low = INT_MAX; + mid = 0; + f_entry = false; + + // calculate range start time: + datetime r_st = StringToTime(inp_r_start_string); + start_time = tz.timezone_conversions(inp_timezone, r_st, "Broker"); + + + for(int i=0; i<8; i++){ + + MqlDateTime tmp; + TimeToStruct(start_time,tmp); + int dow = tmp.day_of_week; + + if(TimeCurrent()>=start_time + || (dow==0 && !sun) + || (dow==1 && !mon) + || (dow==2 && !tue) + || (dow==3 && !wed) + || (dow==4 && !thu) + || (dow==5 && !fri) + || (dow==6 && !sat) + ){ + start_time += PeriodSeconds(PERIOD_D1); + } + } + + + end_time = start_time + r_duration; + order_expire_time = start_time + r_expire; + close_time = start_time + r_close; + + if(plot_range){ + draw_objects(); + } +} + +void RangeCalculator::update_objects(){ + + string name = "Range Mid " + (string)start_time; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, mid); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, mid); + // ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid"); + + name = "Order expire " + (string)order_expire_time; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + + name = "Range start " + (string)start_time; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + + name = "Range end " + (string)end_time; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + + datetime rct = r_close>=0 ? close_time : INT_MAX; + name = "Range close " + (string)rct; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + + name = "Range High " + (string)rct; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, high); + + name = "Range Low " + (string)rct; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, low); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + + name = "range box "+ (string)start_time; + ObjectSetDouble(NULL, name, OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name, OBJPROP_PRICE,1, low); + ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,0, high); + ObjectSetDouble(NULL, name +" ", OBJPROP_PRICE,1, low); + +} + +void RangeCalculator::draw_objects(){ + + datetime rct = r_close>=0 ? close_time : INT_MAX; + + // Range mid line + string name = "Range Mid " + (string)start_time;; + ObjectCreate(NULL, name, OBJ_TREND, 0, start_time, mid, rct, mid); + ObjectSetString(NULL, name , OBJPROP_TOOLTIP, "Range Mid" + (string)mid); + ObjectSetInteger(NULL, name, OBJPROP_COLOR, clrGray); + ObjectSetInteger(NULL, name, OBJPROP_WIDTH, 1); + ObjectSetInteger(NULL, name, OBJPROP_STYLE, STYLE_DOT); + + // order lines + string name2 = "Order expire " + (string)order_expire_time; + ObjectCreate(NULL, name2, OBJ_TREND, 0, order_expire_time, low, order_expire_time, high); + ObjectSetString(NULL, name2, OBJPROP_TOOLTIP, "start of the range \n" + TimeToString(order_expire_time,TIME_DATE|TIME_MINUTES)); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, C'139,41,41'); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); + + name2 = "Range start " + (string)start_time; + ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, start_time, high); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); + + name2 = "Range end " + (string)end_time; + ObjectCreate(NULL, name2, OBJ_TREND, 0, end_time, low, end_time, high); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); + + name2 = "Range close " + (string)rct; + ObjectCreate(NULL, name2, OBJ_TREND, 0, rct, low, rct, high); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); + + name2 = "Range High " + (string)rct; + ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, high, rct, high); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2,OBJPROP_BACK, true); + + name2 = "Range Low " + (string)rct; + ObjectCreate(NULL, name2, OBJ_TREND, 0, start_time, low, rct, low); + ObjectSetInteger(NULL, name2, OBJPROP_COLOR, clrBlack); + ObjectSetInteger(NULL, name2 ,OBJPROP_WIDTH, 2); + ObjectSetInteger(NULL, name2 ,OBJPROP_BACK, true); + + // Box + name = "range box " + (string)start_time; + ObjectCreate(NULL, name, OBJ_RECTANGLE, 0, start_time, high, end_time, low); + ObjectSetString(NULL,name,OBJPROP_TOOLTIP,"\n"); + ObjectSetInteger(NULL, name,OBJPROP_COLOR, C'128,177,173'); + ObjectSetInteger(NULL, name,OBJPROP_FILL, true); + ObjectSetInteger(NULL, name,OBJPROP_BACK, true); + + ObjectCreate(NULL, name + " ", OBJ_RECTANGLE, 0, end_time, high, rct, low); + ObjectSetString(NULL, name+ " ", OBJPROP_TOOLTIP, "\n"); + ObjectSetInteger(NULL, name + " ",OBJPROP_FILL, true); + ObjectSetInteger(NULL, name + " ",OBJPROP_COLOR, C'165,220,215' ); + ObjectSetInteger(NULL, name + " ",OBJPROP_BACK, true); + + ChartRedraw(); +} + + diff --git a/Utils/ChartUtils.mqh b/Utils/ChartUtils.mqh new file mode 100644 index 0000000..79eeaa9 --- /dev/null +++ b/Utils/ChartUtils.mqh @@ -0,0 +1,31 @@ +#include + +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(); +} diff --git a/DealingWithTime.mqh b/Utils/DealingWithTime.mqh similarity index 100% rename from DealingWithTime.mqh rename to Utils/DealingWithTime.mqh diff --git a/MyEnums.mqh b/Utils/Enums.mqh similarity index 82% rename from MyEnums.mqh rename to Utils/Enums.mqh index 015c9b4..71624e3 100644 --- a/MyEnums.mqh +++ b/Utils/Enums.mqh @@ -1,53 +1,41 @@ -#property library - -enum LOT_MODE{ - LOT_MODE_FIXED, // Fixed Lot Size - LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed) - LOT_MODE_PCT_RISK // Percent of Account at Risk (from SL) -}; -enum SL_MODE{ - SL_FIXED_PIPS, // Fixed Pips - SL_FIXED_PERCENT, // Fixed Percent - SL_ATR_MULTIPLE, // ATR Multiple - SL_SPECIFIED_VALUE, // Bespoke calculation in code - NO_STOPLOSS, // No Stop-loss - SL_BREAKEVEN, // Breakeven -}; -enum TP_MODE{ - TP_FIXED_PIPS, // Fixed Pips - TP_FIXED_PERCENT, // Fixed Percent - TP_ATR_MULTIPLE, // ATR Multiple - TP_SL_MULTIPLE, // Multiple of Risk (from sl) - TP_SPECIFIED_VALUE, // Bespoke calculation in code - NO_TAKE_PROFIT, // No Take-Profit -}; - -enum TSL_MODE{ - TSL_ATR_MULTIPLE, // ATR Multiple - TSL_FIXED_PIPS, // Fixed Pips - TSL_FIXED_PERCENT, // Fixed Percent -}; - -enum TIME_ZONES{ - NY, // New York - Lon, // London - Ffm, // Frankfurt - Syd, // Sidney - Mosc, // Moscow - Tok, // Tokyo - no DST -}; -enum MULTI_SYM_MODE{ - MULTI_SYM_CHART, // Chart Symbol only - MULTI_SYM_FX_B5, // FX Benchmark 5 - 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 -}; \ No newline at end of file +enum LOT_MODE{ + LOT_MODE_FIXED, // Fixed Lot Size + LOT_MODE_PCT_ACCOUNT, // Percent of Account (fixed) + LOT_MODE_PCT_RISK // Percent of Account at Risk (from SL) +}; +enum SL_MODE{ + SL_FIXED_PIPS, // Fixed Pips + SL_FIXED_PERCENT, // Fixed Percent + SL_ATR_MULTIPLE, // ATR Multiple + SL_SPECIFIED_VALUE, // Bespoke calculation in code + NO_STOPLOSS, // No Stop-loss + SL_BREAKEVEN, // Breakeven +}; +enum TP_MODE{ + TP_FIXED_PIPS, // Fixed Pips + TP_FIXED_PERCENT, // Fixed Percent + TP_ATR_MULTIPLE, // ATR Multiple + TP_SL_MULTIPLE, // Multiple of Risk (from sl) + TP_SPECIFIED_VALUE, // Bespoke calculation in code + NO_TAKE_PROFIT, // No Take-Profit +}; + +enum TSL_MODE{ + TSL_ATR_MULTIPLE, // ATR Multiple + TSL_FIXED_PIPS, // Fixed Pips + TSL_FIXED_PERCENT, // Fixed Percent +}; + +enum TIME_ZONES{ + NY, // New York + Lon, // London + Ffm, // Frankfurt + Syd, // Sidney + Mosc, // Moscow + Tok, // Tokyo - no DST +}; +enum MULTI_SYM_MODE{ + MULTI_SYM_CHART, // Chart Symbol only + MULTI_SYM_FX_B5, // FX Benchmark 5 + MULTI_SYM_FX_28 // FX 28 Majors +}; diff --git a/NNFX_TestFileHeader.mqh b/Utils/ExpertFileHeader.mqh similarity index 73% rename from NNFX_TestFileHeader.mqh rename to Utils/ExpertFileHeader.mqh index b630dec..79a697d 100644 --- a/NNFX_TestFileHeader.mqh +++ b/Utils/ExpertFileHeader.mqh @@ -1,28 +1,28 @@ -#include -#include -#include -#include -CustomMax cm; -MyFunctions mf; -OrderManagment om; -//--- -string SymbolsArray[]; -input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode -input double inp_lot_var = 2; // Lot Size Var -input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode -input double inp_sl_var = 1.5; // Stop-loss Var -input TP_MODE inp_tp_mode = TP_ATR_MULTIPLE; // Take-profit Mode -input double inp_tp_var = 1; // Take-Profit Var -string lot_mode = EnumToString(inp_lot_mode); -string sl_mode = EnumToString(inp_sl_mode); -string tp_mode = EnumToString(inp_tp_mode); -input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT_200T; -input MULTI_SYM_MODE inp_sym_mode = MULTI_SYM_FX_B5; -input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT; -input int inp_force_opt = 1; -input group "-----------------------------------------" - - - - - +#include +#include +#include +#include +#include +CustomMax c_max; +// MyFunctions mf; +// OrderManagment om; +//--- +input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode +input double inp_lot_var = 2; // Lot Size Var +input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode +input double inp_sl_var = 1.5; // Stop-loss Var +input TP_MODE inp_tp_mode = TP_ATR_MULTIPLE; // Take-profit Mode +input double inp_tp_var = 1; // Take-Profit Var +string lot_mode = EnumToString(inp_lot_mode); +string sl_mode = EnumToString(inp_sl_mode); +string tp_mode = EnumToString(inp_tp_mode); +input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT; +input int inp_opt_min_trades = 0; // 0/off +input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT; +input int inp_force_opt = 1; +input group "-----------------------------------------" + + + + + diff --git a/Utils/MarketDataUtils.mqh b/Utils/MarketDataUtils.mqh new file mode 100644 index 0000000..4551312 --- /dev/null +++ b/Utils/MarketDataUtils.mqh @@ -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 +} diff --git a/TimeZones.mqh b/Utils/TimeZones.mqh similarity index 96% rename from TimeZones.mqh rename to Utils/TimeZones.mqh index 1be9dbf..fd41c7a 100644 --- a/TimeZones.mqh +++ b/Utils/TimeZones.mqh @@ -1,144 +1,144 @@ -#property library -#include -#include - -class TimeZones: public CObject{ - - protected: - string dt_s; - int len; - string dt_string; - datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok; - datetime tz_time; - string tz_date; - datetime time_start; - datetime time_end; - bool is_time; - datetime tGIVEN; - datetime tREQ; - datetime tzt; - datetime tz_req; - double ny_daily_close_protected(string symbol, int shift_days, bool print_data=false); - double required_close; - - public: - string get_date_string_from_datetime(datetime dt); - datetime get_timezone_time(string time_zone, bool print_time); - datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required); - double ny_daily_close(string symbol, int shift_days, bool print_data=false); -}; - -string TimeZones::get_date_string_from_datetime(datetime dt){ - dt_s = TimeToString(dt); - len = StringLen(dt_s); - dt_string = StringSubstr(dt_s, 0, len-5); - return dt_string; -} - - -datetime TimeZones::get_timezone_time(string time_zone, bool print_time){ - // https://www.mql5.com/en/code/45287 - // https://www.mql5.com/en/articles/9926 - // https://www.mql5.com/en/articles/9929 - - checkTimeOffset(TimeCurrent()); // check changes of DST - // cto(); - - tC = TimeCurrent(); - tGMT = TimeCurrent() + OffsetBroker.actOffset; // GMT - tNY = tGMT - (NYShift+DST_USD); // time in New York (EST) - tLon = tGMT - (LondonShift+DST_EUR); // time in London - tFfm = tGMT - (FfmShift+DST_EUR); // time in Frankfurt - tSyd = tGMT - (SidneyShift+DST_AUD); // time in Sidney - tMosc = tGMT - (MoskwaShift+DST_RUS); // time in Moscow - tTok = tGMT - (TokyoShift); // time in Tokyo - no DST - - if(print_time==true){ - Print("----------------------------------"); - Print("Broker: ", tC); - Print("GMT: ", tGMT); - Print("time in New York: ", tNY); - Print("time in London: ", tLon); - Print("time in Frankfurt: ", tFfm); - Print("time in Sidney: ", tSyd); - Print("time in Moscow: ", tMosc); - Print("time in Tokyo: ", tTok); - } - - if(time_zone=="NY"){return tNY;} - if(time_zone=="Lon"){return tLon;} - if(time_zone=="Ffm"){return tFfm;} - if(time_zone=="Syd"){return tSyd;} - if(time_zone=="Mosc"){return tMosc;} - if(time_zone=="Tok"){return tTok;} - - return NULL; -} - - -datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required){ - // https://www.mql5.com/en/code/45287 - // https://www.mql5.com/en/articles/9926 - // https://www.mql5.com/en/articles/9929 - - tGIVEN = time_given; //StringToTime(time_given); - - checkTimeOffset(tGIVEN); // check changes of DST - - // Get GMT: - if(time_zone_known=="GMT" ){tGMT = tGIVEN;} - if(time_zone_known=="Broker" ){tGMT = tGIVEN + OffsetBroker.actOffset;} - if(time_zone_known=="NY" ){tGMT = tGIVEN + (NYShift+DST_USD);} - if(time_zone_known=="Lon" ){tGMT = tGIVEN + (LondonShift+DST_EUR);} - if(time_zone_known=="Ffm" ){tGMT = tGIVEN + (FfmShift+DST_EUR);} - if(time_zone_known=="Syd" ){tGMT = tGIVEN + (SidneyShift+DST_AUD);} - if(time_zone_known=="Mosc" ){tGMT = tGIVEN + (MoskwaShift+DST_RUS);} - if(time_zone_known=="Tok" ){tGMT = tGIVEN + (TokyoShift);} - - // define the required time: - tREQ = NULL; - if(time_zone_required=="GMT" ){tREQ = tGMT;} - if(time_zone_required=="Broker" ){tREQ = tGMT - OffsetBroker.actOffset;} - if(time_zone_required=="NY" ){tREQ = tGMT - (NYShift+DST_USD);} - if(time_zone_required=="Lon" ){tREQ = tGMT - (LondonShift+DST_EUR);} - if(time_zone_required=="Ffm" ){tREQ = tGMT - (FfmShift+DST_EUR);} - if(time_zone_required=="Syd" ){tREQ = tGMT - (SidneyShift+DST_AUD) ;} - if(time_zone_required=="Mosc" ){tREQ = tGMT - (MoskwaShift+DST_RUS);} - if(time_zone_required=="Tok" ){tREQ = tGMT - (TokyoShift);} - - return tREQ; -} - -// Calculte NY close time: -double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data=false){ - required_close = ny_daily_close_protected(symbol, shift_days, print_data); - return required_close; -} -double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data=false){ - - // Get the brokers times for when NY openend today and tomorrow: - datetime time_5pm = iTime(symbol, PERIOD_D1 , 0) - (PeriodSeconds(PERIOD_H1) * 7); - datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker"); - datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1); // ny close tomorrow - - if(TimeCurrent() +#include + +class TimeZones: public CObject{ + + protected: + string dt_s; + int len; + string dt_string; + datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok; + datetime tz_time; + string tz_date; + datetime time_start; + datetime time_end; + bool is_time; + datetime tGIVEN; + datetime tREQ; + datetime tzt; + datetime tz_req; + double ny_daily_close_protected(string symbol, int shift_days, bool print_data=false); + double required_close; + + public: + string get_date_string_from_datetime(datetime dt); + datetime get_timezone_time(string time_zone, bool print_time); + datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required); + double ny_daily_close(string symbol, int shift_days, bool print_data=false); +}; + +string TimeZones::get_date_string_from_datetime(datetime dt){ + dt_s = TimeToString(dt); + len = StringLen(dt_s); + dt_string = StringSubstr(dt_s, 0, len-5); + return dt_string; +} + + +datetime TimeZones::get_timezone_time(string time_zone, bool print_time){ + // https://www.mql5.com/en/code/45287 + // https://www.mql5.com/en/articles/9926 + // https://www.mql5.com/en/articles/9929 + + checkTimeOffset(TimeCurrent()); // check changes of DST + // cto(); + + tC = TimeCurrent(); + tGMT = TimeCurrent() + OffsetBroker.actOffset; // GMT + tNY = tGMT - (NYShift+DST_USD); // time in New York (EST) + tLon = tGMT - (LondonShift+DST_EUR); // time in London + tFfm = tGMT - (FfmShift+DST_EUR); // time in Frankfurt + tSyd = tGMT - (SidneyShift+DST_AUD); // time in Sidney + tMosc = tGMT - (MoskwaShift+DST_RUS); // time in Moscow + tTok = tGMT - (TokyoShift); // time in Tokyo - no DST + + if(print_time==true){ + Print("----------------------------------"); + Print("Broker: ", tC); + Print("GMT: ", tGMT); + Print("time in New York: ", tNY); + Print("time in London: ", tLon); + Print("time in Frankfurt: ", tFfm); + Print("time in Sidney: ", tSyd); + Print("time in Moscow: ", tMosc); + Print("time in Tokyo: ", tTok); + } + + if(time_zone=="NY"){return tNY;} + if(time_zone=="Lon"){return tLon;} + if(time_zone=="Ffm"){return tFfm;} + if(time_zone=="Syd"){return tSyd;} + if(time_zone=="Mosc"){return tMosc;} + if(time_zone=="Tok"){return tTok;} + + return NULL; +} + + +datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required){ + // https://www.mql5.com/en/code/45287 + // https://www.mql5.com/en/articles/9926 + // https://www.mql5.com/en/articles/9929 + + tGIVEN = time_given; //StringToTime(time_given); + + checkTimeOffset(tGIVEN); // check changes of DST + + // Get GMT: + if(time_zone_known=="GMT" ){tGMT = tGIVEN;} + if(time_zone_known=="Broker" ){tGMT = tGIVEN + OffsetBroker.actOffset;} + if(time_zone_known=="NY" ){tGMT = tGIVEN + (NYShift+DST_USD);} + if(time_zone_known=="Lon" ){tGMT = tGIVEN + (LondonShift+DST_EUR);} + if(time_zone_known=="Ffm" ){tGMT = tGIVEN + (FfmShift+DST_EUR);} + if(time_zone_known=="Syd" ){tGMT = tGIVEN + (SidneyShift+DST_AUD);} + if(time_zone_known=="Mosc" ){tGMT = tGIVEN + (MoskwaShift+DST_RUS);} + if(time_zone_known=="Tok" ){tGMT = tGIVEN + (TokyoShift);} + + // define the required time: + tREQ = NULL; + if(time_zone_required=="GMT" ){tREQ = tGMT;} + if(time_zone_required=="Broker" ){tREQ = tGMT - OffsetBroker.actOffset;} + if(time_zone_required=="NY" ){tREQ = tGMT - (NYShift+DST_USD);} + if(time_zone_required=="Lon" ){tREQ = tGMT - (LondonShift+DST_EUR);} + if(time_zone_required=="Ffm" ){tREQ = tGMT - (FfmShift+DST_EUR);} + if(time_zone_required=="Syd" ){tREQ = tGMT - (SidneyShift+DST_AUD) ;} + if(time_zone_required=="Mosc" ){tREQ = tGMT - (MoskwaShift+DST_RUS);} + if(time_zone_required=="Tok" ){tREQ = tGMT - (TokyoShift);} + + return tREQ; +} + +// Calculte NY close time: +double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data=false){ + required_close = ny_daily_close_protected(symbol, shift_days, print_data); + return required_close; +} +double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data=false){ + + // Get the brokers times for when NY openend today and tomorrow: + datetime time_5pm = iTime(symbol, PERIOD_D1 , 0) - (PeriodSeconds(PERIOD_H1) * 7); + datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker"); + datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1); // ny close tomorrow + + if(TimeCurrent() + +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:00–01: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; +} diff --git a/_old/BarUtils.mqh b/_old/BarUtils.mqh new file mode 100644 index 0000000..4900d3c --- /dev/null +++ b/_old/BarUtils.mqh @@ -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; +} + diff --git a/_old/BufferUtils.mqh b/_old/BufferUtils.mqh new file mode 100644 index 0000000..fb3164e --- /dev/null +++ b/_old/BufferUtils.mqh @@ -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; +} diff --git a/CalculatePositionData.mqh b/_old/CalculatePositionData.mqh similarity index 97% rename from CalculatePositionData.mqh rename to _old/CalculatePositionData.mqh index fe1694e..254eda0 100644 --- a/CalculatePositionData.mqh +++ b/_old/CalculatePositionData.mqh @@ -1,278 +1,278 @@ -#property library -#include -#include -#include - -class CalculatePositionData : public CObject{ - - protected: - CTrade trade; - TimeZones tz; - CPositionInfo position; - MyFunctions mf; - - bool check_lots(double &lots, string symbol); - bool normalise_price(double price, double &normalizedPrice, string symbol); - // double adjusted_point(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 = mf.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 = mf.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 = mf.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 = mf.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(lotsmax){ - 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; +#property library +#include +#include +#include + +class CalculatePositionData : public CObject{ + + protected: + CTrade trade; + TimeZones tz; + CPositionInfo position; + MyFunctions mf; + + bool check_lots(double &lots, string symbol); + bool normalise_price(double price, double &normalizedPrice, string symbol); + // double adjusted_point(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 = mf.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 = mf.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 = mf.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 = mf.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(lotsmax){ + 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; } \ No newline at end of file diff --git a/MyFunctions.mqh b/_old/MyFunctions.mqh similarity index 96% rename from MyFunctions.mqh rename to _old/MyFunctions.mqh index ee71dc1..f48cbf0 100644 --- a/MyFunctions.mqh +++ b/_old/MyFunctions.mqh @@ -1,211 +1,211 @@ -#property library -#include -#include -#include - -class MyFunctions : public CObject{ - - protected: - CTrade trade; - TradingWindow tw; - datetime previousTime; - datetime bar_open_time; - - public: - void draw_line(double value, string name,color clr); - bool check_indicator_handles(int &indicator_handles[]); - double adjusted_point(string symbol); - double get_bid_ask_price(string symbol, int price_side); - bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"); - bool trade_window(string t1, string t2, string time_zone, bool plot_range_inp=true); - bool in_test_period(MODE_SPLIT_DATA data_period); - void get_white_list(MULTI_SYM_MODE mode, string& DataArray[]); -}; - -void MyFunctions::get_white_list(MULTI_SYM_MODE mode, string& DataArray[]){ - - if(mode==MULTI_SYM_CHART){ - string a[] = {_Symbol}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } - if(mode==MULTI_SYM_FX_B5){ - string a[] = {"EURUSD", "AUDNZD", "EURGBP", "AUDCAD", "CHFJPY"}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } - if(mode==MULTI_SYM_FX_28){ - string a[] = {"EURUSD","AUDNZD","AUDUSD","AUDJPY","EURCHF","EURGBP","EURJPY","GBPCHF","GBPJPY","GBPUSD","NZDUSD","USDCAD","USDCHF","USDJPY","CADJPY","EURAUD","CHFJPY","EURCAD","AUDCAD","AUDCHF","CADCHF","EURNZD","GBPAUD","GBPCAD","GBPNZD","NZDCAD","NZDCHF","NZDJPY",}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } -} - -bool MyFunctions::trade_window(string t1, string t2, string time_zone="Broker", bool plot_range_inp=true){ - bool in_window = tw.define_window(t1, t2, time_zone, plot_range_inp); - return in_window; -} - -//if(!mf.is_new_bar(symbol, PERIOD_D1, "00:06")){return;} -bool MyFunctions::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"){ - - bar_open_time = iTime(symbol, time_frame, 0); - if(previousTime!=bar_open_time){ - - 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; -} - -//if(!mf.in_test_period(data_split_method){return;} -bool MyFunctions::in_test_period(MODE_SPLIT_DATA data_split_method){ - - string result[]; - string string_tc = TimeToString(TimeCurrent()); - ushort u_sep = StringGetCharacter(".",0); - int split_string = StringSplit(string_tc, u_sep, result); - bool odd_year = int(result[0]) % 2; - bool odd_month = int(result[1]) % 2; - - // get week of the year. rough estimate can be late the first week of jan: - MqlDateTime dt; - TimeToStruct(TimeCurrent(),dt); - int iDay = (dt.day_of_week + 6 ) % 7 + 1; // convert day to standard index (1=Mon,...,7=Sun) - int iWeek = (dt.day_of_year - iDay + 10 ) / 7; // calculate standard week number - - bool odd_week = iWeek % 2; - - - if(data_split_method==NO_SPLIT){ - return true; - } - - if(data_split_method==ODD_YEARS){ - if (odd_year){ - return true; - } - } - - if(data_split_method==EVEN_YEARS){ - if (!odd_year){ - return true; - } - } - - if(data_split_method==ODD_MONTHS){ - if (odd_month){ - return true; - } - } - - if(data_split_method==EVEN_MONTHS){ - - if (!odd_month){ - return true; - } - } - - if(data_split_method==ODD_WEEKS){ - if (odd_week){ - return true; - } - } - - if(data_split_method==EVEN_WEEKS){ - - if (!odd_week){ - return true; - } - } - - return false; -} - -void MyFunctions::draw_line(double value, string name,color clr=clrBlack){ - // EG: - // ArrayResize(bar,1000); - // ArraySetAsSeries(bar, true); - // CopyRates(symbol,PERIOD_CURRENT,1,1000,bar); - // double close = bar[0].close; - // draw_line(close,"CLOSE",clrBlue); - - 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(); -} - -double MyFunctions::adjusted_point(string symbol){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - int digits_adjust=1; - if(symbol_digits==3 || symbol_digits==5){ - digits_adjust=10; - } - - double symbol_point_val = SymbolInfoDouble(symbol,SYMBOL_POINT); - double m_adjusted_point; - m_adjusted_point = symbol_point_val * digits_adjust; - - return m_adjusted_point; - -} -// price side - 1 for the ask price and 2 for the bid price -double MyFunctions::get_bid_ask_price(string symbol, int price_side){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - - double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(bid, symbol_digits); - - double price = 0; - - if(price_side==1){ - price = ask; - } - - else if(price_side==2){ - price = bid; - } - - return price; - +#property library +#include +#include +#include + +class MyFunctions : public CObject{ + + protected: + CTrade trade; + TradingWindow tw; + datetime previousTime; + datetime bar_open_time; + + public: + void draw_line(double value, string name,color clr); + bool check_indicator_handles(int &indicator_handles[]); + double adjusted_point(string symbol); + double get_bid_ask_price(string symbol, int price_side); + bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"); + bool trade_window(string t1, string t2, string time_zone, bool plot_range_inp=true); + bool in_test_period(MODE_SPLIT_DATA data_period); + void get_white_list(MULTI_SYM_MODE mode, string& DataArray[]); +}; + +void MyFunctions::get_white_list(MULTI_SYM_MODE mode, string& DataArray[]){ + + if(mode==MULTI_SYM_CHART){ + string a[] = {_Symbol}; + ArrayResize(DataArray, ArraySize(a)); + for(int i = 0; i < ArraySize(DataArray); i++){ + DataArray[i]=a[i]; + } + } + if(mode==MULTI_SYM_FX_B5){ + string a[] = {"EURUSD", "AUDNZD", "EURGBP", "AUDCAD", "CHFJPY"}; + ArrayResize(DataArray, ArraySize(a)); + for(int i = 0; i < ArraySize(DataArray); i++){ + DataArray[i]=a[i]; + } + } + if(mode==MULTI_SYM_FX_28){ + string a[] = {"EURUSD","AUDNZD","AUDUSD","AUDJPY","EURCHF","EURGBP","EURJPY","GBPCHF","GBPJPY","GBPUSD","NZDUSD","USDCAD","USDCHF","USDJPY","CADJPY","EURAUD","CHFJPY","EURCAD","AUDCAD","AUDCHF","CADCHF","EURNZD","GBPAUD","GBPCAD","GBPNZD","NZDCAD","NZDCHF","NZDJPY",}; + ArrayResize(DataArray, ArraySize(a)); + for(int i = 0; i < ArraySize(DataArray); i++){ + DataArray[i]=a[i]; + } + } +} + +bool MyFunctions::trade_window(string t1, string t2, string time_zone="Broker", bool plot_range_inp=true){ + bool in_window = tw.define_window(t1, t2, time_zone, plot_range_inp); + return in_window; +} + +//if(!mf.is_new_bar(symbol, PERIOD_D1, "00:06")){return;} +bool MyFunctions::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"){ + + bar_open_time = iTime(symbol, time_frame, 0); + if(previousTime!=bar_open_time){ + + 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; +} + +//if(!mf.in_test_period(data_split_method){return;} +bool MyFunctions::in_test_period(MODE_SPLIT_DATA data_split_method){ + + string result[]; + string string_tc = TimeToString(TimeCurrent()); + ushort u_sep = StringGetCharacter(".",0); + int split_string = StringSplit(string_tc, u_sep, result); + bool odd_year = int(result[0]) % 2; + bool odd_month = int(result[1]) % 2; + + // get week of the year. rough estimate can be late the first week of jan: + MqlDateTime dt; + TimeToStruct(TimeCurrent(),dt); + int iDay = (dt.day_of_week + 6 ) % 7 + 1; // convert day to standard index (1=Mon,...,7=Sun) + int iWeek = (dt.day_of_year - iDay + 10 ) / 7; // calculate standard week number + + bool odd_week = iWeek % 2; + + + if(data_split_method==NO_SPLIT){ + return true; + } + + if(data_split_method==ODD_YEARS){ + if (odd_year){ + return true; + } + } + + if(data_split_method==EVEN_YEARS){ + if (!odd_year){ + return true; + } + } + + if(data_split_method==ODD_MONTHS){ + if (odd_month){ + return true; + } + } + + if(data_split_method==EVEN_MONTHS){ + + if (!odd_month){ + return true; + } + } + + if(data_split_method==ODD_WEEKS){ + if (odd_week){ + return true; + } + } + + if(data_split_method==EVEN_WEEKS){ + + if (!odd_week){ + return true; + } + } + + return false; +} + +void MyFunctions::draw_line(double value, string name,color clr=clrBlack){ + // EG: + // ArrayResize(bar,1000); + // ArraySetAsSeries(bar, true); + // CopyRates(symbol,PERIOD_CURRENT,1,1000,bar); + // double close = bar[0].close; + // draw_line(close,"CLOSE",clrBlue); + + 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(); +} + +double MyFunctions::adjusted_point(string symbol){ + + int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + int digits_adjust=1; + if(symbol_digits==3 || symbol_digits==5){ + digits_adjust=10; + } + + double symbol_point_val = SymbolInfoDouble(symbol,SYMBOL_POINT); + double m_adjusted_point; + m_adjusted_point = symbol_point_val * digits_adjust; + + return m_adjusted_point; + +} +// price side - 1 for the ask price and 2 for the bid price +double MyFunctions::get_bid_ask_price(string symbol, int price_side){ + + int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); + + double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); + ask = NormalizeDouble(ask, symbol_digits); + + double bid = SymbolInfoDouble(symbol, SYMBOL_BID); + bid = NormalizeDouble(bid, symbol_digits); + + double price = 0; + + if(price_side==1){ + price = ask; + } + + else if(price_side==2){ + price = bid; + } + + return price; + } \ No newline at end of file diff --git a/OrderManagement.mqh b/_old/OrderManagement.mqh similarity index 97% rename from OrderManagement.mqh rename to _old/OrderManagement.mqh index d3ed70e..5f5b35a 100644 --- a/OrderManagement.mqh +++ b/_old/OrderManagement.mqh @@ -1,556 +1,556 @@ -#property library -#include -#include -#include -#include -#include -#include -#include - -class OrderManagment : public CObject{ - - protected: - CTrade trade; - TimeZones tz; - CalculatePositionData cpd; - CPositionInfo m_position; - COrderInfo m_order; - - double stop_loss; - double take_profit; - ulong posTicket; - int time_difference; - int total_open_buy_orders; - int total_open_sell_orders; - double current_price; - int total_pos; - long position_open_time; - long first_allowed_close_time; - datetime current_bar_open_time; - - 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_nnfx_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_nnfx_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); - bool close_buy_orders(string symbol, bool buy_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); - bool close_sell_orders(string symbol, bool sell_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); - bool first_profitable_close_exit(string symbol, 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 tz, int delay_days, long magic_number); - int count_all_positions(string symbol, long magic_number); - int count_pending_orders(string symbol, ENUM_ORDER_TYPE pendingType, long magic); - 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); - int count_open_positions(string symbol,int order_side, long magic_number); - 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); - }; - -bool OrderManagment::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 == true){ - current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = current_price-stop_loss; - double lots = cpd.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 OrderManagment::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 == true){ - - // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} - current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-current_price; - double lots = cpd.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 OrderManagment::open_nnfx_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 == true){ - current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = current_price-stop_loss; - double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var/2); - - 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); - trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,0,comment); - } - } - return true; -} - - -bool OrderManagment::open_nnfx_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 == true){ - - // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} - current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-current_price; - double lots = cpd.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); - trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,0,comment); - } - } - return true; -} -// some usfull comment here -bool OrderManagment::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 == true){ - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = entry_price-stop_loss; - double lots = cpd.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 OrderManagment::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 == true){ - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-entry_price; - double lots = cpd.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; -} - -bool OrderManagment::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){ - - time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - - if(condition){ - trade.PositionClose(posTicket); - } - - if(close_bars > 0){ - if(time_difference >= close_bars){ - trade.PositionClose(posTicket); - } - } - } - } - } - return true; -} - -bool OrderManagment::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){ - - time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - - if(condition){trade.PositionClose(posTicket);} - - if(close_bars > 0){ - if(time_difference >= close_bars){ - trade.PositionClose(posTicket); - } - } - } - } - } - return true; -} - -// order_side int must be 1 for BUY or 2 for SELL -int OrderManagment::count_open_positions(string symbol,int order_side, long magic_number){ - - - int count = 0; - bool match = (PositionGetInteger(POSITION_MAGIC)==magic_number); - - for(int i = PositionsTotal()-1; i >=0; i--){ - ulong ticket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC)==magic_number){ - - // Count only Buy orders: - if(order_side == 1){ - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - count = count + 1; - } - } - - // Count only Sell orders: - if(order_side == 2){ - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - count = count + 1; - } - } - } - } - return count; -} - -int OrderManagment::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 = count + 1; - } - } - return count; -} - -bool OrderManagment::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){ - - // datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); - if(TimeCurrent()>= exit_time){ - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - trade.PositionClose(posTicket); - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - trade.PositionClose(posTicket); - } - } - } - } - } - } -return true; -} - -bool OrderManagment::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number){ - - // om.daily_timed_profit_exit(_Symbol, PERIOD_CURRENT, "16:45", "17:00", "NY", 1, inp_magic); - - 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){ - - 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); // shift 1 because 0 = live candle. - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - if(bar_close > (position_open_price + spread + trading_cost)){ - trade.PositionClose(posTicket); - - } - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - if(bar_close < position_open_price - spread - trading_cost){ - trade.PositionClose(posTicket); - } - } - } - } - } - } - } -return true; -} - - -bool OrderManagment::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number){ - // om.first_profitable_close_exit(_Symbol, PERIOD_CURRENT, inp_magic); - - position_open_time = PositionGetInteger(POSITION_TIME); - first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period); - - if((int)position_open_time>0){ - - if(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); // shift 1 because 0 = live candle. - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - if(bar_close > (position_open_price + spread + trading_cost)){ - trade.PositionClose(posTicket); - - } - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - if(bar_close < position_open_price - spread - trading_cost){ - trade.PositionClose(posTicket); - } - } - } - } - } - } -return true; -} - -// e.g. int buy_stop_count = om.count_pending_orders(symbol, ORDER_TYPE_BUY_STOP, inp_magic); -// order types: ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, ORDER_TYPE_BUY_STOP, ORDER_TYPE_SELL_STOP -int OrderManagment::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); -} - -void OrderManagment::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 = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(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){ - - if(bid > position_open_price + be_trigger_points * symbol_point){ - - double sl = position_open_price + be_puffer * symbol_point; - sl = NormalizeDouble(sl, symbol_digits); - if(sl > position_sl){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - Print("-----------------------------------Stop moved to break even"); - } - } - } - } - else if(position_type == POSITION_TYPE_SELL){ - - if(ask < position_open_price - be_trigger_points * symbol_point){ - - double sl = position_open_price - be_puffer * symbol_point; - sl = NormalizeDouble(sl, symbol_digits); - if(sl < position_sl){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - Print("-----------------------------------Stop moved to break even"); - } - } - } - } - } - } - } - } -} - - -void OrderManagment::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number){ - - MyFunctions mf3; - - 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 = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(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){ - - if(bid > position_open_price + (atr_value * tp_var)){ - - double sl = bid - (atr_value * sl_var); - sl = NormalizeDouble(sl, symbol_digits); - if(sl > (position_sl + (atr_value * 0.5))){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - - } - } - } - } - - - else if(position_type == POSITION_TYPE_SELL){ - - if(ask < position_open_price - (atr_value * tp_var)){ - - double sl = ask + (atr_value * sl_var); - sl = NormalizeDouble(sl, symbol_digits); - if(sl < (position_sl + (atr_value * 0.5))){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - } - } - } - } - } - } - - - } -} - -double OrderManagment::sl_specified_value_switch(string _sl_mode, double _inp_sl_var, double value){ - double sl = 0; - if(_sl_mode=="SL_SPECIFIED_VALUE"){sl = value;} - if(_sl_mode!="SL_SPECIFIED_VALUE"){sl = _inp_sl_var;} - return sl; -} -double OrderManagment::tp_specified_value_switch(string _tp_mode, double _inp_tp_var, double value){ - double tp = 0; - if(_tp_mode=="SL_SPECIFIED_VALUE"){tp = value;} - if(_tp_mode!="SL_SPECIFIED_VALUE"){tp = _inp_tp_var;} - return tp; +#property library +#include +#include +#include +#include +#include +#include +#include + +class OrderManagment : public CObject{ + + protected: + CTrade trade; + TimeZones tz; + CalculatePositionData cpd; + CPositionInfo m_position; + COrderInfo m_order; + + double stop_loss; + double take_profit; + ulong posTicket; + int time_difference; + int total_open_buy_orders; + int total_open_sell_orders; + double current_price; + int total_pos; + long position_open_time; + long first_allowed_close_time; + datetime current_bar_open_time; + + 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_nnfx_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_nnfx_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); + bool close_buy_orders(string symbol, bool buy_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); + bool close_sell_orders(string symbol, bool sell_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); + bool first_profitable_close_exit(string symbol, 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 tz, int delay_days, long magic_number); + int count_all_positions(string symbol, long magic_number); + int count_pending_orders(string symbol, ENUM_ORDER_TYPE pendingType, long magic); + 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); + int count_open_positions(string symbol,int order_side, long magic_number); + 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); + }; + +bool OrderManagment::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 == true){ + current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side + + total_open_buy_orders = count_open_positions(symbol, 1, magic_number); + if(total_open_buy_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + + double sl_distance = current_price-stop_loss; + double lots = cpd.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 OrderManagment::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 == true){ + + // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} + current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side + + total_open_sell_orders = count_open_positions(symbol, 2, magic_number); + if(total_open_sell_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); + + double sl_distance = stop_loss-current_price; + double lots = cpd.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 OrderManagment::open_nnfx_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 == true){ + current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side + + total_open_buy_orders = count_open_positions(symbol, 1, magic_number); + if(total_open_buy_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + + double sl_distance = current_price-stop_loss; + double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var/2); + + 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); + trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,0,comment); + } + } + return true; +} + + +bool OrderManagment::open_nnfx_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 == true){ + + // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} + current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side + + total_open_sell_orders = count_open_positions(symbol, 2, magic_number); + if(total_open_sell_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); + + double sl_distance = stop_loss-current_price; + double lots = cpd.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); + trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,0,comment); + } + } + return true; +} +// some usfull comment here +bool OrderManagment::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 == true){ + + total_open_buy_orders = count_open_positions(symbol, 1, magic_number); + if(total_open_buy_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + + double sl_distance = entry_price-stop_loss; + double lots = cpd.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 OrderManagment::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 == true){ + + total_open_sell_orders = count_open_positions(symbol, 2, magic_number); + if(total_open_sell_orders == 0){ + + stop_loss = cpd.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period); + take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period); + + double sl_distance = stop_loss-entry_price; + double lots = cpd.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; +} + +bool OrderManagment::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){ + + time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ + + if(condition){ + trade.PositionClose(posTicket); + } + + if(close_bars > 0){ + if(time_difference >= close_bars){ + trade.PositionClose(posTicket); + } + } + } + } + } + return true; +} + +bool OrderManagment::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){ + + time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ + + if(condition){trade.PositionClose(posTicket);} + + if(close_bars > 0){ + if(time_difference >= close_bars){ + trade.PositionClose(posTicket); + } + } + } + } + } + return true; +} + +// order_side int must be 1 for BUY or 2 for SELL +int OrderManagment::count_open_positions(string symbol,int order_side, long magic_number){ + + + int count = 0; + bool match = (PositionGetInteger(POSITION_MAGIC)==magic_number); + + for(int i = PositionsTotal()-1; i >=0; i--){ + ulong ticket = PositionGetTicket(i); + + if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC)==magic_number){ + + // Count only Buy orders: + if(order_side == 1){ + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ + count = count + 1; + } + } + + // Count only Sell orders: + if(order_side == 2){ + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ + count = count + 1; + } + } + } + } + return count; +} + +int OrderManagment::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 = count + 1; + } + } + return count; +} + +bool OrderManagment::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){ + + // datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); + if(TimeCurrent()>= exit_time){ + + if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ + trade.PositionClose(posTicket); + } + + // Sell orders: + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ + trade.PositionClose(posTicket); + } + } + } + } + } + } +return true; +} + +bool OrderManagment::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number){ + + // om.daily_timed_profit_exit(_Symbol, PERIOD_CURRENT, "16:45", "17:00", "NY", 1, inp_magic); + + 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){ + + 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); // shift 1 because 0 = live candle. + double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); + + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ + if(bar_close > (position_open_price + spread + trading_cost)){ + trade.PositionClose(posTicket); + + } + } + + // Sell orders: + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ + if(bar_close < position_open_price - spread - trading_cost){ + trade.PositionClose(posTicket); + } + } + } + } + } + } + } +return true; +} + + +bool OrderManagment::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number){ + // om.first_profitable_close_exit(_Symbol, PERIOD_CURRENT, inp_magic); + + position_open_time = PositionGetInteger(POSITION_TIME); + first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period); + + if((int)position_open_time>0){ + + if(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); // shift 1 because 0 = live candle. + double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); + + + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ + if(bar_close > (position_open_price + spread + trading_cost)){ + trade.PositionClose(posTicket); + + } + } + + // Sell orders: + if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ + if(bar_close < position_open_price - spread - trading_cost){ + trade.PositionClose(posTicket); + } + } + } + } + } + } +return true; +} + +// e.g. int buy_stop_count = om.count_pending_orders(symbol, ORDER_TYPE_BUY_STOP, inp_magic); +// order types: ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, ORDER_TYPE_BUY_STOP, ORDER_TYPE_SELL_STOP +int OrderManagment::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); +} + +void OrderManagment::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 = SymbolInfoDouble(symbol, SYMBOL_ASK); + ask = NormalizeDouble(ask, symbol_digits); + + double bid = SymbolInfoDouble(symbol, SYMBOL_BID); + bid = NormalizeDouble(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){ + + if(bid > position_open_price + be_trigger_points * symbol_point){ + + double sl = position_open_price + be_puffer * symbol_point; + sl = NormalizeDouble(sl, symbol_digits); + if(sl > position_sl){ + + if(trade.PositionModify(ticket, sl, position_tp)){ + Print("-----------------------------------Stop moved to break even"); + } + } + } + } + else if(position_type == POSITION_TYPE_SELL){ + + if(ask < position_open_price - be_trigger_points * symbol_point){ + + double sl = position_open_price - be_puffer * symbol_point; + sl = NormalizeDouble(sl, symbol_digits); + if(sl < position_sl){ + + if(trade.PositionModify(ticket, sl, position_tp)){ + Print("-----------------------------------Stop moved to break even"); + } + } + } + } + } + } + } + } +} + + +void OrderManagment::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number){ + + MyFunctions mf3; + + 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 = SymbolInfoDouble(symbol, SYMBOL_ASK); + ask = NormalizeDouble(ask, symbol_digits); + + double bid = SymbolInfoDouble(symbol, SYMBOL_BID); + bid = NormalizeDouble(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){ + + if(bid > position_open_price + (atr_value * tp_var)){ + + double sl = bid - (atr_value * sl_var); + sl = NormalizeDouble(sl, symbol_digits); + if(sl > (position_sl + (atr_value * 0.5))){ + + if(trade.PositionModify(ticket, sl, position_tp)){ + + } + } + } + } + + + else if(position_type == POSITION_TYPE_SELL){ + + if(ask < position_open_price - (atr_value * tp_var)){ + + double sl = ask + (atr_value * sl_var); + sl = NormalizeDouble(sl, symbol_digits); + if(sl < (position_sl + (atr_value * 0.5))){ + + if(trade.PositionModify(ticket, sl, position_tp)){ + } + } + } + } + } + } + + + } +} + +double OrderManagment::sl_specified_value_switch(string _sl_mode, double _inp_sl_var, double value){ + double sl = 0; + if(_sl_mode=="SL_SPECIFIED_VALUE"){sl = value;} + if(_sl_mode!="SL_SPECIFIED_VALUE"){sl = _inp_sl_var;} + return sl; +} +double OrderManagment::tp_specified_value_switch(string _tp_mode, double _inp_tp_var, double value){ + double tp = 0; + if(_tp_mode=="SL_SPECIFIED_VALUE"){tp = value;} + if(_tp_mode!="SL_SPECIFIED_VALUE"){tp = _inp_tp_var;} + return tp; } \ No newline at end of file diff --git a/_old/SymbolUtils.mqh b/_old/SymbolUtils.mqh new file mode 100644 index 0000000..3212e8a --- /dev/null +++ b/_old/SymbolUtils.mqh @@ -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 +} diff --git a/TradingWindow.mqh b/_old/TradingWindow.mqh similarity index 96% rename from TradingWindow.mqh rename to _old/TradingWindow.mqh index 4c37219..4c72a94 100644 --- a/TradingWindow.mqh +++ b/_old/TradingWindow.mqh @@ -1,65 +1,65 @@ -#property library -#include -#include - -class TradingWindow : public CObject{ - - protected: - TimeZones tz; - bool in_window; - datetime start_time; - datetime end_time; - - public: - bool define_window(string t1, string t2, string time_zone, bool plot_range_inp=true); -}; - - -bool TradingWindow::define_window(string t1, string t2, string time_zone, bool plot_range=true){ - - datetime _t1 = StringToTime(t1); - datetime _t2 = StringToTime(t2); - if(_t1 > _t2){ - _t2 = _t2 + PeriodSeconds(PERIOD_D1); - } - int w_duration = (int)(_t2 - _t1); - - // window flag - if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){ - in_window = true; - } - - // define new window - if(TimeCurrent() >= end_time){ - - in_window = false; - start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker"); - - if(TimeCurrent()>=start_time){ - start_time += PeriodSeconds(PERIOD_D1); - } - - end_time = start_time + w_duration; - - if(plot_range){ - - 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; -} - - +#property library +#include +#include + +class TradingWindow : public CObject{ + + protected: + TimeZones tz; + bool in_window; + datetime start_time; + datetime end_time; + + public: + bool define_window(string t1, string t2, string time_zone, bool plot_range_inp=true); +}; + + +bool TradingWindow::define_window(string t1, string t2, string time_zone, bool plot_range=true){ + + datetime _t1 = StringToTime(t1); + datetime _t2 = StringToTime(t2); + if(_t1 > _t2){ + _t2 = _t2 + PeriodSeconds(PERIOD_D1); + } + int w_duration = (int)(_t2 - _t1); + + // window flag + if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){ + in_window = true; + } + + // define new window + if(TimeCurrent() >= end_time){ + + in_window = false; + start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker"); + + if(TimeCurrent()>=start_time){ + start_time += PeriodSeconds(PERIOD_D1); + } + + end_time = start_time + w_duration; + + if(plot_range){ + + 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; +} + + diff --git a/_old/_MyLibs_old.zip b/_old/_MyLibs_old.zip new file mode 100644 index 0000000000000000000000000000000000000000..a1c1ea6bf185a95365c1f823cdb95ef598e524e3 GIT binary patch literal 26431 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 z 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