Files
2025-07-16 12:31:55 +02:00

512 lines
17 KiB
Plaintext

//+------------------------------------------------------------------+
//| RangeCalculator.mqh |
//| Determines a high/low price point within a time range |
//| |
//| 2025 xMattC (github.com/xMattC) |
//+------------------------------------------------------------------+
#property copyright "2025 xMattC (github.com/xMattC)"
#property link "https://github.com/xMattC"
#property version "1.00"
#property library
#include <Trade/Trade.mqh>
#include <MyLibs/Utils/TimeZones.mqh>
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);
};
// ---------------------------------------------------------------------
// Sets the allowed days for range calculation.
//
// Parameters:
// - _inp_sun : Allow Sunday.
// - _inp_mon : Allow Monday.
// - _inp_tue : Allow Tuesday.
// - _inp_wed : Allow Wednesday.
// - _inp_thu : Allow Thursday.
// - _inp_fri : Allow Friday.
// - _inp_sat : Allow Saturday.
// ---------------------------------------------------------------------
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;
}
// ---------------------------------------------------------------------
// Initializes the range parameters.
//
// Parameters:
// - inp_symbol : The symbol for the range.
// - _calc_period : Timeframe for range calculation.
// - t1 : Start time string.
// - t2 : End time string.
// - t3 : Expiry time string.
// - t4 : Close time string.
// - time_zone : Timezone name.
// - plot_range_inp : Whether to plot the range.
//
// Returns:
// - true if initialization was successful; false otherwise.
// ---------------------------------------------------------------------
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;
}
// ---------------------------------------------------------------------
// Converts time input strings to time deltas for range definition.
//
// Parameters:
// - t1 : Start time string.
// - t2 : End time string.
// - t3 : Expiry time string.
// - t4 : Close time string.
//
// Returns:
// - true if times were converted successfully; false on error.
// ---------------------------------------------------------------------
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;
}
// ---------------------------------------------------------------------
// Returns the high value of the current range.
//
// Returns:
// - High price of the range.
// ---------------------------------------------------------------------
double RangeCalculator::get_range_high(){
return high;
};
// ---------------------------------------------------------------------
// Returns the low value of the current range.
//
// Returns:
// - Low price of the range.
// ---------------------------------------------------------------------
double RangeCalculator::get_range_low(){
return low;
};
// ---------------------------------------------------------------------
// Returns the mid value of the current range.
//
// Returns:
// - Mid price of the range.
// ---------------------------------------------------------------------
double RangeCalculator::get_range_mid(){
return mid;
};
// ---------------------------------------------------------------------
// Returns the start time of the current range.
//
// Returns:
// - Range start time.
// ---------------------------------------------------------------------
datetime RangeCalculator::get_range_start(){
return start_time;
};
// ---------------------------------------------------------------------
// Returns the end time of the current range.
//
// Returns:
// - Range end time.
// ---------------------------------------------------------------------
datetime RangeCalculator::get_range_end(){
return end_time;
};
// ---------------------------------------------------------------------
// Returns the expiration time for range-based orders.
//
// Returns:
// - Order expiration time.
// ---------------------------------------------------------------------
datetime RangeCalculator::get_order_expire_time(){
return order_expire_time;
};
// ---------------------------------------------------------------------
// Returns the close time of the current range.
//
// Returns:
// - Range close time.
// ---------------------------------------------------------------------
datetime RangeCalculator::get_range_close(){
return close_time;
};
// ---------------------------------------------------------------------
// Returns the high breakout flag of the current range.
//
// Returns:
// - true if high breakout occurred; false otherwise.
// ---------------------------------------------------------------------
bool RangeCalculator::get_range_high_breakout(){
return f_high_breakout;
};
// ---------------------------------------------------------------------
// Returns the low breakout flag of the current range.
//
// Returns:
// - true if low breakout occurred; false otherwise.
// ---------------------------------------------------------------------
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();
}