#property description "Shows price momentum in percentage or points during the last N seconds." #property description "Alerts on above/below a given threshold." #property description "" #property description "Find More on https://t.me/ForexEaPremium" #property indicator_chart_window #property indicator_plots 0 #property indicator_buffers 1 // Use one buffer to store the current momentum value for EA access. enum ENUM_ALERT_BEHAVIOR { ENUM_ALERT_BEHAVIOR_NONE, // No alerts ENUM_ALERT_BEHAVIOR_SINGLE, // Single alert until next breach ENUM_ALERT_BEHAVIOR_CONSTANT, // Continuous alerts whenever condition is met ENUM_ALERT_BEHAVIOR_RESTRICTED // Alert on condition but with time limit on next alert }; enum ENUM_PRICE { ENUM_PRICE_ASK, // Ask ENUM_PRICE_BID, // Bid ENUM_PRICE_MIDPRICE // Midprice }; input group "Main" input int Seconds = 10; // Number of seconds to calculate momentum input int ThresholdPoints = 30; // Threshold in points input double ThresholdPercentage = 0.02; // Threshold in percentage input int DiscardIfOlder = 1; // Discard calculations if ticks are older than N seconds input ENUM_PRICE PriceToUse = ENUM_PRICE_BID; // PriceToUse: Which price to use? input group "Alerts" input ENUM_ALERT_BEHAVIOR AlertBehaviror = ENUM_ALERT_BEHAVIOR_NONE; input int AlertTimeLimitForRestricted = 5; // Alert time limit till next alert in seconds input bool EnableNativeAlerts = false; input bool EnableEmailAlerts = false; input bool EnablePushAlerts = false; input group "Display" input int Font_Size = 8; // Font size input color Up_Color = clrGreen; // Up color input color Down_Color = clrRed; // Down color input color No_Mvt_Color = clrBlue; // No change color input int X_Position_Text = 21; // X distance for text input int Y_Position_Text = 20; // Y distance for text input ENUM_BASE_CORNER Corner_Position_Text = CORNER_LEFT_LOWER; // Text corner input string Text_Object = "FM_Text"; // Text object name int OnInit() { if ((AlertBehaviror != ENUM_ALERT_BEHAVIOR_NONE) && (!EnableNativeAlerts) && (!EnableEmailAlerts) && (!EnablePushAlerts)) { Print("AlertBehaviror is set to issue alerts, but no alret type is enabled. There won't be any alerts."); } if ((AlertBehaviror == ENUM_ALERT_BEHAVIOR_NONE) && ((EnableNativeAlerts) || (EnableEmailAlerts) || (EnablePushAlerts))) { Print("One or more alert type is enabled, but AlertBehaviror is set to \"No alerts\". There won't be any alerts."); } return INIT_SUCCEEDED; } void OnDeinit(const int reason) { ObjectDelete(ChartID(), Text_Object); ChartRedraw(); } int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { static bool syncing = false; if (iBarShift(Symbol(), Period(), iTime(Symbol(), Period(), 0), true) < 0) // iBarShift failure. { // When chart data is in normal state, there shouldn't be an error when searching for the current bar with iBarShift. Print("Syncing..."); syncing = true; return prev_calculated; } if (syncing) { Print("Synced."); syncing = false; } MqlTick ticks_array[]; // Tick functions work with milliseconds. int end_time_seconds = (int)TimeCurrent(); ulong begin_time_ms = ulong(end_time_seconds - Seconds - DiscardIfOlder) * 1000; // First time. static bool not_enough_chart_data = false; if (begin_time_ms / 1000 < (ulong)Time[0]) // Time[0] - oldest bar. { Print("Not enough chart data..."); not_enough_chart_data = true; return prev_calculated; } if (not_enough_chart_data) { Print("Found enough chart data."); not_enough_chart_data = false; } // CopyTicks() has inconsistent behavior, so everything is handled with CopyTicksRange(). int n = CopyTicksRange(Symbol(), ticks_array, COPY_TICKS_ALL, begin_time_ms, (ulong)end_time_seconds * 1000); static bool waiting_for_ticks = false; if (n <= 0) { Print("Waiting for ticks... "); waiting_for_ticks = true; return prev_calculated; } if (waiting_for_ticks == true) { Print("Found enough ticks."); waiting_for_ticks = false; } int found_i = -1; // From oldest to newest. for (int i = 0; i < n; i++) { if (ticks_array[i].time > end_time_seconds - Seconds) // If newer than exact time, exit. Result - unknown. { if (i == 0) // Started with time, which is too close to the current time. { found_i = -1; } else if (i > 0) // There was at least one tick older than this. It can be used. { found_i = i - 1; } break; } else if (ticks_array[i].time == end_time_seconds - Seconds) { // Exact match. found_i = i; break; } } string text = "???"; color colour = No_Mvt_Color; int distance_points = 0; double distance_percentage = 0; long time_ms = 0; // Tick's time in milliseconds for alerts. if (found_i >= 0) { double price_old; double price_current; switch (PriceToUse) { case ENUM_PRICE_BID: price_old = ticks_array[found_i].bid; price_current = SymbolInfoDouble(Symbol(), SYMBOL_BID); break; case ENUM_PRICE_ASK: price_old = ticks_array[found_i].ask; price_current = SymbolInfoDouble(Symbol(), SYMBOL_ASK); break; case ENUM_PRICE_MIDPRICE: price_old = (ticks_array[found_i].bid + ticks_array[found_i].ask) / 2; price_current = (SymbolInfoDouble(Symbol(), SYMBOL_BID) + SymbolInfoDouble(Symbol(), SYMBOL_ASK)) / 2; break; default: price_old = ticks_array[found_i].bid; price_current = SymbolInfoDouble(Symbol(), SYMBOL_BID); break; } time_ms = ticks_array[found_i].time_msc; distance_points = (int)MathRound(MathAbs(price_current - price_old) / _Point); distance_percentage = 0; if (price_old != 0) { distance_percentage = MathAbs(price_current - price_old) / price_old * 100; } if (ThresholdPoints > 0) { text = IntegerToString(distance_points) + "p/" + IntegerToString(ThresholdPoints) + "p"; } if (ThresholdPercentage > 0) { if (text != "") text += ", "; text += DoubleToString(distance_percentage, 2) + "%/" + DoubleToString(ThresholdPercentage, 2) + "%"; } if (price_current > price_old) { colour = Up_Color; } else if (price_current < price_old) { colour = Down_Color; } } bool need_points_alert = false; bool need_percentage_alert = false; static bool prev_need_points_alert = false; static bool prev_need_percentage_alert = false; static datetime prev_points_alert = 0; static datetime prev_percentage_alert = 0; bool need_bigger_font = false; // Doing this check regardless of the alerts settings to change the display font size in case of a breach. if ((ThresholdPoints > 0) && (distance_points >= ThresholdPoints)) { need_points_alert = true; } else { need_points_alert = false; prev_need_points_alert = false; } if ((ThresholdPercentage > 0) && (distance_percentage >= ThresholdPercentage)) { need_percentage_alert = true; } else { need_percentage_alert = false; prev_need_percentage_alert = false; } // Set a flag requiring font size increase for the output text label. if ((need_points_alert) || (need_percentage_alert)) { need_bigger_font = true; } // If no alerts are needed, turn off alert variables and only use need_bigger_font. if ((AlertBehaviror == ENUM_ALERT_BEHAVIOR_NONE) || ((!EnableEmailAlerts) && (!EnableNativeAlerts) && (!EnablePushAlerts))) { need_points_alert = false; need_percentage_alert = false; } // If alerts are needed, check if they can be issued now. else { switch (AlertBehaviror) // Specific behavior. { case ENUM_ALERT_BEHAVIOR_CONSTANT: // Just do alert. break; case ENUM_ALERT_BEHAVIOR_SINGLE: // Only after reset. if (prev_need_points_alert) need_points_alert = false; else prev_need_points_alert = need_points_alert; if (prev_need_percentage_alert) need_percentage_alert = false; else prev_need_percentage_alert = need_percentage_alert; break; case ENUM_ALERT_BEHAVIOR_RESTRICTED: // Alert only if enough time has passed since the last alert. if (TimeCurrent() - prev_points_alert < AlertTimeLimitForRestricted) { need_points_alert = false; } else prev_points_alert = TimeCurrent(); // Will alert now. if (TimeCurrent() - prev_percentage_alert < AlertTimeLimitForRestricted) { need_percentage_alert = false; } else prev_percentage_alert = TimeCurrent(); // Will alert now. break; default: break; } } int font_size = Font_Size; if (need_bigger_font) { font_size += 4; } ShowObjects(text + " (" + IntegerToString(Seconds) + "s)", Text_Object, colour, font_size, Corner_Position_Text, X_Position_Text, Y_Position_Text); if ((need_points_alert) || (need_percentage_alert)) DoAlerts(need_points_alert, need_percentage_alert, distance_points, distance_percentage, time_ms, colour); return rates_total; } void ShowObjects(const string text, const string text_obj, const color colour, const int size, const ENUM_BASE_CORNER corner_pos_text, const int x_pos_text, const int y_pos_text) { if (ObjectFind(0, text_obj) < 0) { ObjectCreate(0, text_obj, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, text_obj, OBJPROP_CORNER, corner_pos_text); ObjectSetInteger(0, text_obj, OBJPROP_XDISTANCE, x_pos_text); ObjectSetInteger(0, text_obj, OBJPROP_YDISTANCE, y_pos_text); ObjectSetString(0, text_obj, OBJPROP_FONT, "Verdana"); } ObjectSetInteger(0, text_obj, OBJPROP_COLOR, colour); ObjectSetString(0, text_obj, OBJPROP_TEXT, "Momentum: " + text); ObjectSetInteger(0, text_obj, OBJPROP_FONTSIZE, size); } void DoAlerts(const bool need_points_alert, const bool need_percentage_alert, const int distance_points, const double distance_percentage, const long time_ms, const color colour) { string direction; if (colour == Up_Color) direction = "Up"; else direction = "Down"; string Text = "Momentum (" + direction + ") of "; if (need_points_alert) Text += IntegerToString(distance_points) + "p over " + IntegerToString(Seconds) + "s > " + IntegerToString(ThresholdPoints) + "p threshold."; if (need_percentage_alert) { if (need_points_alert) Text += " and "; Text += DoubleToString(distance_percentage, 2) + "% over " + IntegerToString(Seconds) + "s > " + DoubleToString(ThresholdPercentage, 2) + "% threshold."; } if (EnableNativeAlerts) Alert(Text); if (EnableEmailAlerts) SendMail("FlexibleMomentum Alert on " + Symbol(), Symbol() + ", " + TimeToString(int(time_ms / 1000), TIME_DATE|TIME_MINUTES|TIME_SECONDS) + ":" + StringFormat("%03i", time_ms - int(time_ms / 1000) * 1000) + " - " + Text); if (EnablePushAlerts) SendNotification(Symbol() + Text); } //+------------------------------------------------------------------+