commit 890eaf31f55fbe1450d0a6c84f3e180c8d37e03d Author: Matt Corcoran Date: Fri Oct 25 12:08:52 2024 +0200 first push all - just my code diff --git a/CalculatePositionData.mqh b/CalculatePositionData.mqh new file mode 100644 index 0000000..fe1694e --- /dev/null +++ b/CalculatePositionData.mqh @@ -0,0 +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; +} \ No newline at end of file diff --git a/CustomMax.mqh b/CustomMax.mqh new file mode 100644 index 0000000..4e1fdc7 --- /dev/null +++ b/CustomMax.mqh @@ -0,0 +1,62 @@ +#property library +#include + +enum CUSTOM_MAX_TYPE{ + CM_WIN_LOSS_RATIO, + CM_WIN_PERCENT, + CM_WIN_PERCENT_200T +}; + +class CustomMax : public CObject{ + + protected: + double custom_criteria; + + double CustomMax::win_loss_ratio(); + double CustomMax::win_percent(); + double CustomMax::win_percent_min_trades_200(); + + 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(); + } + if(cm_type==CM_WIN_PERCENT_200T){ + custom_criteria = win_percent_min_trades_200(); + } + 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(){ + double wins = TesterStatistics(STAT_PROFIT_TRADES); + double total_trades = TesterStatistics(STAT_TRADES); + return wins / total_trades * 100; +} + +double CustomMax::win_percent_min_trades_200(){ + double wins = TesterStatistics(STAT_PROFIT_TRADES); + double total_trades = TesterStatistics(STAT_TRADES); + + if(total_trades<200){ + return 0; + } + + else { + return wins / total_trades * 100; + } +} \ No newline at end of file diff --git a/DealingWithTime.mqh b/DealingWithTime.mqh new file mode 100644 index 0000000..0492252 Binary files /dev/null and b/DealingWithTime.mqh differ diff --git a/DrawdownControl.mqh b/DrawdownControl.mqh new file mode 100644 index 0000000..bd8280c --- /dev/null +++ b/DrawdownControl.mqh @@ -0,0 +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; +} diff --git a/MyEnums.mqh b/MyEnums.mqh new file mode 100644 index 0000000..7f1f029 --- /dev/null +++ b/MyEnums.mqh @@ -0,0 +1,38 @@ +#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 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 +}; \ No newline at end of file diff --git a/MyFunctions.mqh b/MyFunctions.mqh new file mode 100644 index 0000000..9634c92 --- /dev/null +++ b/MyFunctions.mqh @@ -0,0 +1,171 @@ +#property library +#include +#include + +class MyFunctions : public CObject{ + + protected: + CTrade trade; + TradingWindow tw; + datetime previousTime; + datetime bar_open_time; + + public: + bool is_new_daily_bar(string symbol, datetime start_time); + double period_high(string symbol, int periods, int shift); + double period_low(string symbol, int periods, int shift); + 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); + bool trade_window(string t1, string t2, string time_zone, bool plot_range_inp=true); + +}; + +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_daily_bar(symbol, PERIOD_M1)){return;} +bool MyFunctions::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame){ + bar_open_time = iTime(symbol,time_frame,0); + if(previousTime!=bar_open_time){ + previousTime=bar_open_time; + return true; + } + return false; +} + +// e.g. if(!mf.is_new_daily_bar(symbol, StringToTime("00:06"))){return;} +bool MyFunctions::is_new_daily_bar(string symbol, datetime start_time){ + // https://www.youtube.com/watch?v=9BdnTcGrlUM (m-25:00) + bar_open_time = iTime(symbol,PERIOD_D1,0); + if(previousTime!=bar_open_time && TimeCurrent() > start_time){ + previousTime=bar_open_time; + return true; + } + return false; +} + + +double MyFunctions::period_high(string symbol, int periods, int shift){ + + double highs[]; + ArraySetAsSeries(highs,true); + CopyHigh(symbol,PERIOD_CURRENT,1,periods+1,highs); + + double high = 0; + high=highs[shift]; + for(int i=shift; ilows[i]){ + low=lows[i]; + } + } + return(low); +} + +void MyFunctions::draw_line(double value, string name,color clr){ + // 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(); +} + +bool MyFunctions::check_indicator_handles(int &indicator_handles[]){ + // TODO check if working before implementaion: + // e.g. call via: + // int indicator_handles[] = {handle1, handle2, handle..}; + // check_indicator_handles(indicator_handles); + + for(int i =0; i < ArraySize(indicator_handles); i++){ + + if(indicator_handles[i] == INVALID_HANDLE){ + Alert("Failed to create handle"); return false; + }; + } + + return true; +} + +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/OrderManagement.mqh new file mode 100644 index 0000000..46f1be4 --- /dev/null +++ b/OrderManagement.mqh @@ -0,0 +1,439 @@ +#property library +#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_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); + }; + +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; +} + +// 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){ + + 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){ + for(int i = PositionsTotal()-1; i >=0; i--){ + + 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"); + } + } + } + } + } + } + } + } +} + +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/RangeCalculator.mqh b/RangeCalculator.mqh new file mode 100644 index 0000000..aa62fc5 --- /dev/null +++ b/RangeCalculator.mqh @@ -0,0 +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(); +} + + diff --git a/TimeZones.mqh b/TimeZones.mqh new file mode 100644 index 0000000..340644c --- /dev/null +++ b/TimeZones.mqh @@ -0,0 +1,168 @@ +#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; +} + +// bool TimeZones::trading_window(string time_zone, string start_time, string end_time){ +// // https://www.youtube.com/watch?v=V_qh7sTbl80 +// // e.g: +// // bool trade_window = trading_window(x,x,x); +// // bool long_in = trade_window +// // && close < ma[0]; + +// tz_time = get_timezone_time(time_zone, false); +// Print(tz_time); + +// // Get the current date for the required time zone: +// tz_date = get_date_string_from_datetime(tz_time); + +// // Define the start and end times on correct date: +// time_start = StringToTime(tz_date + start_time); +// time_end = StringToTime(tz_date + end_time); + +// if(time_start>time_end){ +// time_start = time_start - PeriodSeconds(PERIOD_D1); +// } +// is_time = tz_time >= time_start && tz_time < time_end; + +// return is_time; +// } + +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 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; +} + +