736 lines
25 KiB
Text
736 lines
25 KiB
Text
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| SMC_OrderBlocks_TrendDiv.mq5 |
|
||
|
|
//| Order Blocks + 3-Touch Trendlines + Divergence |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "HorizonAI"
|
||
|
|
#property link ""
|
||
|
|
#property version "1.00"
|
||
|
|
#property indicator_chart_window
|
||
|
|
#property indicator_buffers 0
|
||
|
|
#property indicator_plots 0
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| INPUTS |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
input group "=== Order Blocks ==="
|
||
|
|
input int OB_ImpulseBars = 3; // Min impulse candles for OB
|
||
|
|
input int OB_MaxDisplay = 10; // Max OBs displayed
|
||
|
|
input bool OB_BodyOnly = false; // Body-only zones
|
||
|
|
input color OB_BullColor = clrDodgerBlue;
|
||
|
|
input color OB_BearColor = clrTomato;
|
||
|
|
input color OB_MitigatedColor = clrGray;
|
||
|
|
|
||
|
|
input group "=== Trendlines (3-Touch) ==="
|
||
|
|
input int TL_LeftBars = 5; // Pivot left bars
|
||
|
|
input int TL_RightBars = 3; // Pivot right bars
|
||
|
|
input int TL_MinTouches = 3; // Minimum touches to draw
|
||
|
|
input int TL_MaxLines = 5; // Max trendlines per TF
|
||
|
|
input color TL_UptrendColor = clrLime;
|
||
|
|
input color TL_DowntrendColor = clrRed;
|
||
|
|
input int TL_LineWidth = 2;
|
||
|
|
|
||
|
|
input group "=== RSI Divergence ==="
|
||
|
|
input int RSI_Period = 14; // RSI period
|
||
|
|
input int RSI_LeftBars = 5; // Pivot left bars for divergence
|
||
|
|
input int RSI_RightBars = 3; // Pivot right bars for divergence
|
||
|
|
input int RSI_MaxSignals = 10; // Max divergence signals
|
||
|
|
input color RSI_BullColor = clrLime;
|
||
|
|
input color RSI_BearColor = clrRed;
|
||
|
|
|
||
|
|
input group "=== Multi-Timeframe ==="
|
||
|
|
input bool Show_M15 = true; // Show M15 trendlines
|
||
|
|
input bool Show_H1 = true; // Show H1 trendlines
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| STRUCTS |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
struct OrderBlock
|
||
|
|
{
|
||
|
|
double top;
|
||
|
|
double bottom;
|
||
|
|
datetime time;
|
||
|
|
bool isBullish;
|
||
|
|
bool mitigated;
|
||
|
|
string objName;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct PivotPoint
|
||
|
|
{
|
||
|
|
double price;
|
||
|
|
int barIndex;
|
||
|
|
datetime time;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct Trendline
|
||
|
|
{
|
||
|
|
datetime time1;
|
||
|
|
double price1;
|
||
|
|
datetime time2;
|
||
|
|
double price2;
|
||
|
|
bool isUptrend;
|
||
|
|
int touches;
|
||
|
|
string objName;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct DivergenceSignal
|
||
|
|
{
|
||
|
|
datetime time1;
|
||
|
|
double price1;
|
||
|
|
datetime time2;
|
||
|
|
double price2;
|
||
|
|
bool isBullish;
|
||
|
|
string lineName;
|
||
|
|
string arrowName;
|
||
|
|
};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| GLOBALS |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
OrderBlock g_OBs[];
|
||
|
|
Trendline g_TL_M15[];
|
||
|
|
Trendline g_TL_H1[];
|
||
|
|
DivergenceSignal g_DivSignals[];
|
||
|
|
|
||
|
|
int g_rsiHandle;
|
||
|
|
int g_lastProcessedBar = 0;
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| INIT |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int OnInit()
|
||
|
|
{
|
||
|
|
// Create RSI handle
|
||
|
|
g_rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
|
||
|
|
if(g_rsiHandle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
Print("Failed to create RSI handle");
|
||
|
|
return INIT_FAILED;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clean up any leftover objects
|
||
|
|
ObjectsDeleteAll(0, "OB_");
|
||
|
|
ObjectsDeleteAll(0, "TL_M15_");
|
||
|
|
ObjectsDeleteAll(0, "TL_H1_");
|
||
|
|
ObjectsDeleteAll(0, "DIV_");
|
||
|
|
|
||
|
|
return INIT_SUCCEEDED;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| DEINIT |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnDeinit(const int reason)
|
||
|
|
{
|
||
|
|
ObjectsDeleteAll(0, "OB_");
|
||
|
|
ObjectsDeleteAll(0, "TL_M15_");
|
||
|
|
ObjectsDeleteAll(0, "TL_H1_");
|
||
|
|
ObjectsDeleteAll(0, "DIV_");
|
||
|
|
if(g_rsiHandle != INVALID_HANDLE)
|
||
|
|
IndicatorRelease(g_rsiHandle);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ON CALCULATE |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
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[])
|
||
|
|
{
|
||
|
|
if(rates_total < 50) return rates_total;
|
||
|
|
|
||
|
|
int start = (prev_calculated < 50) ? 50 : prev_calculated - 1;
|
||
|
|
|
||
|
|
//--- Order Blocks Detection
|
||
|
|
DetectOrderBlocks(rates_total, start, time, open, high, low, close);
|
||
|
|
UpdateOrderBlocks(rates_total, high, low, close, time);
|
||
|
|
|
||
|
|
//--- Trendlines on M15
|
||
|
|
if(Show_M15)
|
||
|
|
ProcessTrendlines(PERIOD_M15, "M15", g_TL_M15, TL_MaxLines);
|
||
|
|
|
||
|
|
//--- Trendlines on H1
|
||
|
|
if(Show_H1)
|
||
|
|
ProcessTrendlines(PERIOD_H1, "H1", g_TL_H1, TL_MaxLines);
|
||
|
|
|
||
|
|
//--- RSI Divergence
|
||
|
|
DetectRSIDivergence(rates_total, start, time, high, low, close);
|
||
|
|
|
||
|
|
return rates_total;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ORDER BLOCK DETECTION |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DetectOrderBlocks(const int rates_total, int start,
|
||
|
|
const datetime &time[],
|
||
|
|
const double &open[],
|
||
|
|
const double &high[],
|
||
|
|
const double &low[],
|
||
|
|
const double &close[])
|
||
|
|
{
|
||
|
|
for(int i = start; i < rates_total - OB_ImpulseBars; i++)
|
||
|
|
{
|
||
|
|
//--- Check for bullish impulse (3+ consecutive bullish candles)
|
||
|
|
bool bullImpulse = true;
|
||
|
|
for(int k = 0; k < OB_ImpulseBars; k++)
|
||
|
|
{
|
||
|
|
if(close[i + k] <= open[i + k])
|
||
|
|
{
|
||
|
|
bullImpulse = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(bullImpulse)
|
||
|
|
{
|
||
|
|
// Find last bearish candle before impulse
|
||
|
|
for(int j = i - 1; j >= MathMax(0, i - 10); j--)
|
||
|
|
{
|
||
|
|
if(close[j] < open[j]) // Bearish candle = Bullish OB
|
||
|
|
{
|
||
|
|
AddOrderBlock(high[j], low[j], time[j], true);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Check for bearish impulse
|
||
|
|
bool bearImpulse = true;
|
||
|
|
for(int k = 0; k < OB_ImpulseBars; k++)
|
||
|
|
{
|
||
|
|
if(close[i + k] >= open[i + k])
|
||
|
|
{
|
||
|
|
bearImpulse = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(bearImpulse)
|
||
|
|
{
|
||
|
|
// Find last bullish candle before impulse
|
||
|
|
for(int j = i - 1; j >= MathMax(0, i - 10); j--)
|
||
|
|
{
|
||
|
|
if(close[j] > open[j]) // Bullish candle = Bearish OB
|
||
|
|
{
|
||
|
|
AddOrderBlock(high[j], low[j], time[j], false);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ADD ORDER BLOCK |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void AddOrderBlock(double top, double bottom, datetime time, bool isBullish)
|
||
|
|
{
|
||
|
|
// Check if OB already exists at this time
|
||
|
|
for(int i = 0; i < ArraySize(g_OBs); i++)
|
||
|
|
{
|
||
|
|
if(g_OBs[i].time == time && g_OBs[i].isBullish == isBullish)
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
int idx = ArraySize(g_OBs);
|
||
|
|
ArrayResize(g_OBs, idx + 1);
|
||
|
|
|
||
|
|
g_OBs[idx].top = top;
|
||
|
|
g_OBs[idx].bottom = bottom;
|
||
|
|
g_OBs[idx].time = time;
|
||
|
|
g_OBs[idx].isBullish = isBullish;
|
||
|
|
g_OBs[idx].mitigated = false;
|
||
|
|
g_OBs[idx].objName = "OB_" + (isBullish ? "Bull_" : "Bear_") + IntegerToString(idx);
|
||
|
|
|
||
|
|
// Draw rectangle
|
||
|
|
datetime endTime = TimeCurrent() + PeriodSeconds() * 100;
|
||
|
|
color obColor = isBullish ? OB_BullColor : OB_BearColor;
|
||
|
|
|
||
|
|
if(ObjectFind(0, g_OBs[idx].objName) < 0)
|
||
|
|
{
|
||
|
|
ObjectCreate(0, g_OBs[idx].objName, OBJ_RECTANGLE, 0, time, top, endTime, bottom);
|
||
|
|
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_COLOR, obColor);
|
||
|
|
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_FILL, true);
|
||
|
|
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_BACK, true);
|
||
|
|
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_WIDTH, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Enforce max display
|
||
|
|
EnforceMaxOBs();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| UPDATE ORDER BLOCKS (mitigation check) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void UpdateOrderBlocks(const int rates_total,
|
||
|
|
const double &high[],
|
||
|
|
const double &low[],
|
||
|
|
const double &close[],
|
||
|
|
const datetime &time[])
|
||
|
|
{
|
||
|
|
for(int i = 0; i < ArraySize(g_OBs); i++)
|
||
|
|
{
|
||
|
|
if(g_OBs[i].mitigated) continue;
|
||
|
|
|
||
|
|
int lastBar = rates_total - 1;
|
||
|
|
|
||
|
|
// Bullish OB mitigated when price closes below bottom
|
||
|
|
if(g_OBs[i].isBullish && close[lastBar] < g_OBs[i].bottom)
|
||
|
|
{
|
||
|
|
g_OBs[i].mitigated = true;
|
||
|
|
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_COLOR, OB_MitigatedColor);
|
||
|
|
}
|
||
|
|
// Bearish OB mitigated when price closes above top
|
||
|
|
else if(!g_OBs[i].isBullish && close[lastBar] > g_OBs[i].top)
|
||
|
|
{
|
||
|
|
g_OBs[i].mitigated = true;
|
||
|
|
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_COLOR, OB_MitigatedColor);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extend rectangle right
|
||
|
|
if(!g_OBs[i].mitigated)
|
||
|
|
{
|
||
|
|
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_TIME, 1, TimeCurrent() + PeriodSeconds() * 100);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ENFORCE MAX OBs |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void EnforceMaxOBs()
|
||
|
|
{
|
||
|
|
int size = ArraySize(g_OBs);
|
||
|
|
if(size <= OB_MaxDisplay) return;
|
||
|
|
|
||
|
|
// Remove oldest mitigated OBs first
|
||
|
|
for(int i = 0; i < size && ArraySize(g_OBs) > OB_MaxDisplay; i++)
|
||
|
|
{
|
||
|
|
if(g_OBs[i].mitigated)
|
||
|
|
{
|
||
|
|
ObjectDelete(0, g_OBs[i].objName);
|
||
|
|
ArrayRemove(g_OBs, i, 1);
|
||
|
|
i--;
|
||
|
|
size--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If still over limit, remove oldest
|
||
|
|
while(ArraySize(g_OBs) > OB_MaxDisplay)
|
||
|
|
{
|
||
|
|
ObjectDelete(0, g_OBs[0].objName);
|
||
|
|
ArrayRemove(g_OBs, 0, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| PROCESS TRENDLINES FOR A TIMEFRAME |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void ProcessTrendlines(ENUM_TIMEFRAMES tf, string tfPrefix,
|
||
|
|
Trendline &tlArray[], int maxLines)
|
||
|
|
{
|
||
|
|
// Get data from the specified timeframe
|
||
|
|
int bars = iBars(_Symbol, tf);
|
||
|
|
if(bars < 50) return;
|
||
|
|
|
||
|
|
double high[], low[], close[];
|
||
|
|
datetime time[];
|
||
|
|
|
||
|
|
ArrayResize(high, bars);
|
||
|
|
ArrayResize(low, bars);
|
||
|
|
ArrayResize(close, bars);
|
||
|
|
ArrayResize(time, bars);
|
||
|
|
|
||
|
|
if(CopyHigh(_Symbol, tf, 0, bars, high) != bars) return;
|
||
|
|
if(CopyLow(_Symbol, tf, 0, bars, low) != bars) return;
|
||
|
|
if(CopyClose(_Symbol, tf, 0, bars, close) != bars) return;
|
||
|
|
if(CopyTime(_Symbol, tf, 0, bars, time) != bars) return;
|
||
|
|
|
||
|
|
// Find pivot highs and lows
|
||
|
|
PivotPoint pivotHighs[];
|
||
|
|
PivotPoint pivotLows[];
|
||
|
|
|
||
|
|
FindPivots(high, low, time, bars, pivotHighs, pivotLows);
|
||
|
|
|
||
|
|
// Clear old trendlines for this TF
|
||
|
|
string prefix = "TL_" + tfPrefix + "_";
|
||
|
|
ObjectsDeleteAll(0, prefix);
|
||
|
|
|
||
|
|
// Find uptrend lines (connecting higher lows with 3+ touches)
|
||
|
|
FindTrendlines(pivotLows, ArraySize(pivotLows), true, tlArray, prefix, maxLines, time, bars);
|
||
|
|
|
||
|
|
// Find downtrend lines (connecting lower highs with 3+ touches)
|
||
|
|
FindTrendlines(pivotHighs, ArraySize(pivotHighs), false, tlArray, prefix, maxLines, time, bars);
|
||
|
|
|
||
|
|
// Draw valid trendlines
|
||
|
|
for(int i = 0; i < ArraySize(tlArray); i++)
|
||
|
|
{
|
||
|
|
if(tlArray[i].touches >= TL_MinTouches && StringFind(tlArray[i].objName, tfPrefix) >= 0)
|
||
|
|
{
|
||
|
|
DrawTrendline(tlArray[i], tfPrefix);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| FIND PIVOTS |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void FindPivots(const double &high[], const double &low[],
|
||
|
|
const datetime &time[], int bars,
|
||
|
|
PivotPoint &pivotHighs[], PivotPoint &pivotLows[])
|
||
|
|
{
|
||
|
|
int phCount = 0, plCount = 0;
|
||
|
|
|
||
|
|
for(int i = TL_RightBars; i < bars - TL_RightBars; i++)
|
||
|
|
{
|
||
|
|
// Check pivot high
|
||
|
|
bool isPH = true;
|
||
|
|
for(int k = 1; k <= TL_LeftBars; k++)
|
||
|
|
if(high[i] < high[i - k]) { isPH = false; break; }
|
||
|
|
if(isPH)
|
||
|
|
for(int k = 1; k <= TL_RightBars; k++)
|
||
|
|
if(high[i] < high[i + k]) { isPH = false; break; }
|
||
|
|
|
||
|
|
if(isPH)
|
||
|
|
{
|
||
|
|
ArrayResize(pivotHighs, phCount + 1);
|
||
|
|
pivotHighs[phCount].price = high[i];
|
||
|
|
pivotHighs[phCount].barIndex = i;
|
||
|
|
pivotHighs[phCount].time = time[i];
|
||
|
|
phCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check pivot low
|
||
|
|
bool isPL = true;
|
||
|
|
for(int k = 1; k <= TL_LeftBars; k++)
|
||
|
|
if(low[i] > low[i - k]) { isPL = false; break; }
|
||
|
|
if(isPL)
|
||
|
|
for(int k = 1; k <= TL_RightBars; k++)
|
||
|
|
if(low[i] > low[i + k]) { isPL = false; break; }
|
||
|
|
|
||
|
|
if(isPL)
|
||
|
|
{
|
||
|
|
ArrayResize(pivotLows, plCount + 1);
|
||
|
|
pivotLows[plCount].price = low[i];
|
||
|
|
pivotLows[plCount].barIndex = i;
|
||
|
|
pivotLows[plCount].time = time[i];
|
||
|
|
plCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| FIND TRENDLINES WITH 3+ TOUCHES |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void FindTrendlines(PivotPoint &pivots[], int pivotCount, bool isUptrend,
|
||
|
|
Trendline &tlArray[], string prefix, int maxLines,
|
||
|
|
const datetime &time[], int bars)
|
||
|
|
{
|
||
|
|
if(pivotCount < 3) return;
|
||
|
|
|
||
|
|
// Try each pair of pivots as the base line
|
||
|
|
for(int i = 0; i < pivotCount - 1; i++)
|
||
|
|
{
|
||
|
|
for(int j = i + 1; j < pivotCount; j++)
|
||
|
|
{
|
||
|
|
// Calculate slope
|
||
|
|
double slope = (pivots[j].price - pivots[i].price) /
|
||
|
|
(double)(pivots[j].barIndex - pivots[i].barIndex);
|
||
|
|
|
||
|
|
// For uptrend, slope should be positive (higher lows)
|
||
|
|
// For downtrend, slope should be negative (lower highs)
|
||
|
|
if(isUptrend && slope <= 0) continue;
|
||
|
|
if(!isUptrend && slope >= 0) continue;
|
||
|
|
|
||
|
|
// Count touches
|
||
|
|
int touches = 0;
|
||
|
|
double tolerance = 0.0005 * pivots[i].price; // 0.05% tolerance
|
||
|
|
|
||
|
|
for(int k = 0; k < pivotCount; k++)
|
||
|
|
{
|
||
|
|
double linePrice = pivots[i].price + slope * (pivots[k].barIndex - pivots[i].barIndex);
|
||
|
|
if(MathAbs(pivots[k].price - linePrice) <= tolerance)
|
||
|
|
touches++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(touches >= TL_MinTouches)
|
||
|
|
{
|
||
|
|
// Check if we already have a similar line
|
||
|
|
bool duplicate = false;
|
||
|
|
for(int t = 0; t < ArraySize(tlArray); t++)
|
||
|
|
{
|
||
|
|
if(MathAbs(tlArray[t].price1 - pivots[i].price) < tolerance &&
|
||
|
|
MathAbs(tlArray[t].price2 - pivots[j].price) < tolerance)
|
||
|
|
{
|
||
|
|
duplicate = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!duplicate)
|
||
|
|
{
|
||
|
|
int idx = ArraySize(tlArray);
|
||
|
|
ArrayResize(tlArray, idx + 1);
|
||
|
|
|
||
|
|
tlArray[idx].time1 = pivots[i].time;
|
||
|
|
tlArray[idx].price1 = pivots[i].price;
|
||
|
|
tlArray[idx].time2 = time[bars - 1];
|
||
|
|
tlArray[idx].price2 = pivots[i].price + slope * (bars - 1 - pivots[i].barIndex);
|
||
|
|
tlArray[idx].isUptrend = isUptrend;
|
||
|
|
tlArray[idx].touches = touches;
|
||
|
|
tlArray[idx].objName = prefix + IntegerToString(idx);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| DRAW TRENDLINE |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawTrendline(Trendline &tl, string tfPrefix)
|
||
|
|
{
|
||
|
|
if(ObjectFind(0, tl.objName) >= 0) return;
|
||
|
|
|
||
|
|
ObjectCreate(0, tl.objName, OBJ_TREND, 0, tl.time1, tl.price1, tl.time2, tl.price2);
|
||
|
|
ObjectSetInteger(0, tl.objName, OBJPROP_COLOR, tl.isUptrend ? TL_UptrendColor : TL_DowntrendColor);
|
||
|
|
ObjectSetInteger(0, tl.objName, OBJPROP_WIDTH, TL_LineWidth);
|
||
|
|
ObjectSetInteger(0, tl.objName, OBJPROP_RAY_RIGHT, true);
|
||
|
|
ObjectSetInteger(0, tl.objName, OBJPROP_STYLE, STYLE_SOLID);
|
||
|
|
|
||
|
|
// Add label
|
||
|
|
string labelName = tl.objName + "_LBL";
|
||
|
|
ObjectCreate(0, labelName, OBJ_TEXT, 0, tl.time2, tl.price2);
|
||
|
|
ObjectSetString(0, labelName, OBJPROP_TEXT, " " + tfPrefix + " (" + IntegerToString(tl.touches) + "T)");
|
||
|
|
ObjectSetInteger(0, labelName, OBJPROP_COLOR, tl.isUptrend ? TL_UptrendColor : TL_DowntrendColor);
|
||
|
|
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| RSI DIVERGENCE DETECTION |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DetectRSIDivergence(const int rates_total, int start,
|
||
|
|
const datetime &time[],
|
||
|
|
const double &high[],
|
||
|
|
const double &low[],
|
||
|
|
const double &close[])
|
||
|
|
{
|
||
|
|
// Get RSI values
|
||
|
|
double rsi[];
|
||
|
|
ArrayResize(rsi, rates_total);
|
||
|
|
if(CopyBuffer(g_rsiHandle, 0, 0, rates_total, rsi) != rates_total) return;
|
||
|
|
|
||
|
|
// Find price pivot highs and lows
|
||
|
|
PivotPoint priceHighs[], priceLows[];
|
||
|
|
int phCount = 0, plCount = 0;
|
||
|
|
|
||
|
|
for(int i = RSI_RightBars; i < rates_total - RSI_RightBars; i++)
|
||
|
|
{
|
||
|
|
// Price pivot high
|
||
|
|
bool isPH = true;
|
||
|
|
for(int k = 1; k <= RSI_LeftBars; k++)
|
||
|
|
if(high[i] < high[i - k]) { isPH = false; break; }
|
||
|
|
if(isPH)
|
||
|
|
for(int k = 1; k <= RSI_RightBars; k++)
|
||
|
|
if(high[i] < high[i + k]) { isPH = false; break; }
|
||
|
|
|
||
|
|
if(isPH)
|
||
|
|
{
|
||
|
|
ArrayResize(priceHighs, phCount + 1);
|
||
|
|
priceHighs[phCount].price = high[i];
|
||
|
|
priceHighs[phCount].barIndex = i;
|
||
|
|
priceHighs[phCount].time = time[i];
|
||
|
|
phCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Price pivot low
|
||
|
|
bool isPL = true;
|
||
|
|
for(int k = 1; k <= RSI_LeftBars; k++)
|
||
|
|
if(low[i] > low[i - k]) { isPL = false; break; }
|
||
|
|
if(isPL)
|
||
|
|
for(int k = 1; k <= RSI_RightBars; k++)
|
||
|
|
if(low[i] > low[i + k]) { isPL = false; break; }
|
||
|
|
|
||
|
|
if(isPL)
|
||
|
|
{
|
||
|
|
ArrayResize(priceLows, plCount + 1);
|
||
|
|
priceLows[plCount].price = low[i];
|
||
|
|
priceLows[plCount].barIndex = i;
|
||
|
|
priceLows[plCount].time = time[i];
|
||
|
|
plCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find RSI pivot highs and lows
|
||
|
|
PivotPoint rsiHighs[], rsiLows[];
|
||
|
|
int rhCount = 0, rlCount = 0;
|
||
|
|
|
||
|
|
for(int i = RSI_RightBars; i < rates_total - RSI_RightBars; i++)
|
||
|
|
{
|
||
|
|
// RSI pivot high
|
||
|
|
bool isRH = true;
|
||
|
|
for(int k = 1; k <= RSI_LeftBars; k++)
|
||
|
|
if(rsi[i] < rsi[i - k]) { isRH = false; break; }
|
||
|
|
if(isRH)
|
||
|
|
for(int k = 1; k <= RSI_RightBars; k++)
|
||
|
|
if(rsi[i] < rsi[i + k]) { isRH = false; break; }
|
||
|
|
|
||
|
|
if(isRH)
|
||
|
|
{
|
||
|
|
ArrayResize(rsiHighs, rhCount + 1);
|
||
|
|
rsiHighs[rhCount].price = rsi[i];
|
||
|
|
rsiHighs[rhCount].barIndex = i;
|
||
|
|
rsiHighs[rhCount].time = time[i];
|
||
|
|
rhCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// RSI pivot low
|
||
|
|
bool isRL = true;
|
||
|
|
for(int k = 1; k <= RSI_LeftBars; k++)
|
||
|
|
if(rsi[i] > rsi[i - k]) { isRL = false; break; }
|
||
|
|
if(isRL)
|
||
|
|
for(int k = 1; k <= RSI_RightBars; k++)
|
||
|
|
if(rsi[i] > rsi[i + k]) { isRL = false; break; }
|
||
|
|
|
||
|
|
if(isRL)
|
||
|
|
{
|
||
|
|
ArrayResize(rsiLows, rlCount + 1);
|
||
|
|
rsiLows[rlCount].price = rsi[i];
|
||
|
|
rsiLows[rlCount].barIndex = i;
|
||
|
|
rsiLows[rlCount].time = time[i];
|
||
|
|
rlCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear old signals
|
||
|
|
ObjectsDeleteAll(0, "DIV_");
|
||
|
|
ArrayResize(g_DivSignals, 0);
|
||
|
|
|
||
|
|
//--- Bearish Divergence: Price higher high + RSI lower high
|
||
|
|
if(phCount >= 2 && rhCount >= 2)
|
||
|
|
{
|
||
|
|
for(int i = 0; i < phCount - 1; i++)
|
||
|
|
{
|
||
|
|
for(int j = 0; j < rhCount - 1; j++)
|
||
|
|
{
|
||
|
|
// Check if pivots are close in time (within 5 bars)
|
||
|
|
if(MathAbs(priceHighs[i].barIndex - rsiHighs[j].barIndex) <= 5)
|
||
|
|
{
|
||
|
|
// Check next pivots
|
||
|
|
for(int ii = i + 1; ii < phCount; ii++)
|
||
|
|
{
|
||
|
|
for(int jj = j + 1; jj < rhCount; jj++)
|
||
|
|
{
|
||
|
|
if(MathAbs(priceHighs[ii].barIndex - rsiHighs[jj].barIndex) <= 5)
|
||
|
|
{
|
||
|
|
// Price higher high, RSI lower high = bearish divergence
|
||
|
|
if(priceHighs[ii].price > priceHighs[i].price &&
|
||
|
|
rsiHighs[jj].price < rsiHighs[j].price)
|
||
|
|
{
|
||
|
|
AddDivergenceSignal(priceHighs[i].time, priceHighs[i].price,
|
||
|
|
priceHighs[ii].time, priceHighs[ii].price,
|
||
|
|
false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//--- Bullish Divergence: Price lower low + RSI higher low
|
||
|
|
if(plCount >= 2 && rlCount >= 2)
|
||
|
|
{
|
||
|
|
for(int i = 0; i < plCount - 1; i++)
|
||
|
|
{
|
||
|
|
for(int j = 0; j < rlCount - 1; j++)
|
||
|
|
{
|
||
|
|
if(MathAbs(priceLows[i].barIndex - rsiLows[j].barIndex) <= 5)
|
||
|
|
{
|
||
|
|
for(int ii = i + 1; ii < plCount; ii++)
|
||
|
|
{
|
||
|
|
for(int jj = j + 1; jj < rlCount; jj++)
|
||
|
|
{
|
||
|
|
if(MathAbs(priceLows[ii].barIndex - rsiLows[jj].barIndex) <= 5)
|
||
|
|
{
|
||
|
|
// Price lower low, RSI higher low = bullish divergence
|
||
|
|
if(priceLows[ii].price < priceLows[i].price &&
|
||
|
|
rsiLows[jj].price > rsiLows[j].price)
|
||
|
|
{
|
||
|
|
AddDivergenceSignal(priceLows[i].time, priceLows[i].price,
|
||
|
|
priceLows[ii].time, priceLows[ii].price,
|
||
|
|
true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ADD DIVERGENCE SIGNAL |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void AddDivergenceSignal(datetime t1, double p1, datetime t2, double p2, bool isBullish)
|
||
|
|
{
|
||
|
|
// Check for duplicate
|
||
|
|
for(int i = 0; i < ArraySize(g_DivSignals); i++)
|
||
|
|
{
|
||
|
|
if(g_DivSignals[i].time1 == t1 && g_DivSignals[i].time2 == t2)
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
int idx = ArraySize(g_DivSignals);
|
||
|
|
ArrayResize(g_DivSignals, idx + 1);
|
||
|
|
|
||
|
|
g_DivSignals[idx].time1 = t1;
|
||
|
|
g_DivSignals[idx].price1 = p1;
|
||
|
|
g_DivSignals[idx].time2 = t2;
|
||
|
|
g_DivSignals[idx].price2 = p2;
|
||
|
|
g_DivSignals[idx].isBullish = isBullish;
|
||
|
|
g_DivSignals[idx].lineName = "DIV_LINE_" + IntegerToString(idx);
|
||
|
|
g_DivSignals[idx].arrowName = "DIV_ARR_" + IntegerToString(idx);
|
||
|
|
|
||
|
|
// Draw divergence line on chart
|
||
|
|
color divColor = isBullish ? RSI_BullColor : RSI_BearColor;
|
||
|
|
|
||
|
|
ObjectCreate(0, g_DivSignals[idx].lineName, OBJ_TREND, 0, t1, p1, t2, p2);
|
||
|
|
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_COLOR, divColor);
|
||
|
|
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_WIDTH, 2);
|
||
|
|
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_STYLE, STYLE_DASH);
|
||
|
|
|
||
|
|
// Draw arrow at signal point
|
||
|
|
ENUM_OBJECT arrowType = isBullish ? OBJ_ARROW_UP : OBJ_ARROW_DOWN;
|
||
|
|
double arrowPrice = isBullish ? p2 - 10 * _Point : p2 + 10 * _Point;
|
||
|
|
|
||
|
|
ObjectCreate(0, g_DivSignals[idx].arrowName, arrowType, 0, t2, arrowPrice);
|
||
|
|
ObjectSetInteger(0, g_DivSignals[idx].arrowName, OBJPROP_COLOR, divColor);
|
||
|
|
ObjectSetInteger(0, g_DivSignals[idx].arrowName, OBJPROP_WIDTH, 3);
|
||
|
|
|
||
|
|
// Add label
|
||
|
|
string labelName = "DIV_LBL_" + IntegerToString(idx);
|
||
|
|
ObjectCreate(0, labelName, OBJ_TEXT, 0, t2, arrowPrice);
|
||
|
|
ObjectSetString(0, labelName, OBJPROP_TEXT, isBullish ? " Bull Div" : " Bear Div");
|
||
|
|
ObjectSetInteger(0, labelName, OBJPROP_COLOR, divColor);
|
||
|
|
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
|
||
|
|
|
||
|
|
// Enforce max signals
|
||
|
|
while(ArraySize(g_DivSignals) > RSI_MaxSignals)
|
||
|
|
{
|
||
|
|
ObjectDelete(0, g_DivSignals[0].lineName);
|
||
|
|
ObjectDelete(0, g_DivSignals[0].arrowName);
|
||
|
|
ObjectDelete(0, "DIV_LBL_0");
|
||
|
|
ArrayRemove(g_DivSignals, 0, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|