#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);
}
//+------------------------------------------------------------------+
Comments