297 lines
14 KiB
MQL5
297 lines
14 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| RBD_DBR_Pivots.mq5 |
|
||
|
|
//| Diterjemahkan oleh Gemini |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property indicator_chart_window
|
||
|
|
#property indicator_buffers 1
|
||
|
|
#property indicator_plots 1
|
||
|
|
|
||
|
|
//--- Plot ZigZag
|
||
|
|
#property indicator_label1 "Pivot ZigZag"
|
||
|
|
#property indicator_type1 DRAW_SECTION
|
||
|
|
#property indicator_color1 clrGold
|
||
|
|
#property indicator_style1 STYLE_SOLID
|
||
|
|
#property indicator_width1 2
|
||
|
|
|
||
|
|
//--- Inputs
|
||
|
|
input int InpMaxBars = 1000; // Maksimal Bar Historis yang Dipindai
|
||
|
|
input int InpLen = 3; // Lookback Pivot (Syarat Kiri & Kanan Pivot)
|
||
|
|
input int InpMaxSearch = 15; // Maksimal Pencarian Candle Kaki
|
||
|
|
input color InpRallyCol = clrTeal; // Warna Kotak Rally
|
||
|
|
input color InpDropCol = clrCrimson; // Warna Kotak Drop
|
||
|
|
input color InpExtWickCol = clrOrange; // Warna Penanda Ekstrem Pivot
|
||
|
|
input color InpZigZagCol = clrGold; // Warna Garis Zig-Zag
|
||
|
|
|
||
|
|
//--- Toggles Tampilan
|
||
|
|
input bool InpShowBoxes = true; // Opsi Menampilkan Kotak Zona (RBD/DBR)
|
||
|
|
input bool InpShowLines = true; // Opsi Menampilkan Garis Pivot Horizontal
|
||
|
|
input bool InpShowZigZag = true; // Opsi Menampilkan Garis Zig-Zag
|
||
|
|
|
||
|
|
input int InpHideLen = 0; // Batas Tampil Historis (0 = Tampil Semua)
|
||
|
|
|
||
|
|
//--- Buffer
|
||
|
|
double ZigZagBuffer[];
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Helper: Mendapatkan Prefix unik berdasarkan Timeframe |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
string GetPrefix() {
|
||
|
|
return "RBD_" + EnumToString(_Period) + "_";
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Custom indicator initialization & deinitialization |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int OnInit() {
|
||
|
|
SetIndexBuffer(0, ZigZagBuffer, INDICATOR_DATA);
|
||
|
|
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_SECTION);
|
||
|
|
PlotIndexSetInteger(0, PLOT_LINE_COLOR, InpZigZagCol);
|
||
|
|
ArraySetAsSeries(ZigZagBuffer, true);
|
||
|
|
|
||
|
|
return(INIT_SUCCEEDED);
|
||
|
|
}
|
||
|
|
|
||
|
|
void OnDeinit(const int reason) {
|
||
|
|
ObjectsDeleteAll(0, "RBD_");
|
||
|
|
ChartRedraw(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Helper functions untuk Candle & Body |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool IsGC(int index, const double &open[], const double &close[]) { return close[index] > open[index]; }
|
||
|
|
bool IsRC(int index, const double &open[], const double &close[]) { return close[index] < open[index]; }
|
||
|
|
|
||
|
|
double GetHighestBody(int idx1, int idx2, const double &open[], const double &close[]) {
|
||
|
|
int start = MathMin(idx1, idx2);
|
||
|
|
int end = MathMax(idx1, idx2);
|
||
|
|
double maxVal = MathMax(open[start], close[start]);
|
||
|
|
for(int i = start + 1; i <= end; i++) {
|
||
|
|
maxVal = MathMax(maxVal, MathMax(open[i], close[i]));
|
||
|
|
}
|
||
|
|
return maxVal;
|
||
|
|
}
|
||
|
|
|
||
|
|
double GetLowestBody(int idx1, int idx2, const double &open[], const double &close[]) {
|
||
|
|
int start = MathMin(idx1, idx2);
|
||
|
|
int end = MathMax(idx1, idx2);
|
||
|
|
double minVal = MathMin(open[start], close[start]);
|
||
|
|
for(int i = start + 1; i <= end; i++) {
|
||
|
|
minVal = MathMin(minVal, MathMin(open[i], close[i]));
|
||
|
|
}
|
||
|
|
return minVal;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Custom indicator iteration function |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
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[])
|
||
|
|
{
|
||
|
|
ArraySetAsSeries(open, true); ArraySetAsSeries(high, true);
|
||
|
|
ArraySetAsSeries(low, true); ArraySetAsSeries(close, true);
|
||
|
|
ArraySetAsSeries(time, true);
|
||
|
|
|
||
|
|
if (rates_total < InpMaxSearch * 2) return 0;
|
||
|
|
|
||
|
|
int limit = rates_total - prev_calculated;
|
||
|
|
if (limit == 0) limit = 1;
|
||
|
|
|
||
|
|
if (prev_calculated == 0) {
|
||
|
|
limit = rates_total - InpLen - 1;
|
||
|
|
if (InpMaxBars > 0 && limit > InpMaxBars) {
|
||
|
|
limit = InpMaxBars;
|
||
|
|
}
|
||
|
|
ObjectsDeleteAll(0, "RBD_");
|
||
|
|
ArrayInitialize(ZigZagBuffer, EMPTY_VALUE);
|
||
|
|
} else {
|
||
|
|
for(int i = limit; i >= 0; i--) ZigZagBuffer[i] = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
string currentPrefix = GetPrefix();
|
||
|
|
|
||
|
|
for(int shift = limit; shift >= 0; shift--)
|
||
|
|
{
|
||
|
|
// 1. Line Break Logic
|
||
|
|
if (InpShowLines) {
|
||
|
|
int totalObjects = ObjectsTotal(0, 0, OBJ_TREND);
|
||
|
|
for(int i = totalObjects - 1; i >= 0; i--) {
|
||
|
|
string name = ObjectName(0, i, 0, OBJ_TREND);
|
||
|
|
if(StringFind(name, currentPrefix + "Line") == 0 && ObjectGetInteger(0, name, OBJPROP_RAY_RIGHT)) {
|
||
|
|
double price = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);
|
||
|
|
bool isBroken = false;
|
||
|
|
|
||
|
|
if(StringFind(name, currentPrefix + "LineLow_") == 0 && close[shift] < price) isBroken = true;
|
||
|
|
else if(StringFind(name, currentPrefix + "LineHigh_") == 0 && close[shift] > price) isBroken = true;
|
||
|
|
|
||
|
|
if(isBroken) {
|
||
|
|
ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
|
||
|
|
ObjectSetInteger(0, name, OBJPROP_TIME, 1, time[shift]);
|
||
|
|
ObjectSetDouble(0, name, OBJPROP_PRICE, 1, price);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(shift + InpLen >= rates_total || shift - InpLen < 0) continue;
|
||
|
|
|
||
|
|
// 2. Cek Pivot Valid (Murni Price Action High/Low)
|
||
|
|
bool isHighest = true, isLowest = true;
|
||
|
|
for(int j=1; j<=InpLen; j++) {
|
||
|
|
if(high[shift+j] > high[shift] || high[shift-j] >= high[shift]) isHighest = false;
|
||
|
|
if(low[shift+j] < low[shift] || low[shift-j] <= low[shift]) isLowest = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Deteksi Pematangan RBD ---
|
||
|
|
if(isHighest && IsGC(shift + 1, open, close)) {
|
||
|
|
int d1 = -1, d2 = -1, rcCount = 0;
|
||
|
|
for(int j = shift - 1; j >= MathMax(0, shift - InpMaxSearch); j--) {
|
||
|
|
if(IsRC(j, open, close)) {
|
||
|
|
rcCount++;
|
||
|
|
if(rcCount == 1) d1 = j;
|
||
|
|
else if(rcCount == 2) { d2 = j; break; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(rcCount >= 2 && close[d2] < close[d1]) {
|
||
|
|
int r1 = -1;
|
||
|
|
for(int j = shift + 1; j <= MathMin(rates_total-1, shift + InpMaxSearch); j++) {
|
||
|
|
if(IsGC(j, open, close)) { r1 = j; break; }
|
||
|
|
}
|
||
|
|
if(r1 != -1) {
|
||
|
|
DrawPivotHigh(shift, r1, d2, open, high, low, close, time);
|
||
|
|
if (InpShowZigZag) ZigZagBuffer[shift] = high[shift];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Deteksi Pematangan DBR ---
|
||
|
|
if(isLowest && IsRC(shift + 1, open, close)) {
|
||
|
|
int r1 = -1, r2 = -1, gcCount = 0;
|
||
|
|
for(int j = shift - 1; j >= MathMax(0, shift - InpMaxSearch); j--) {
|
||
|
|
if(IsGC(j, open, close)) {
|
||
|
|
gcCount++;
|
||
|
|
if(gcCount == 1) r1 = j;
|
||
|
|
else if(gcCount == 2) { r2 = j; break; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(gcCount >= 2 && close[r2] > close[r1]) {
|
||
|
|
int d1 = -1;
|
||
|
|
for(int j = shift + 1; j <= MathMin(rates_total-1, shift + InpMaxSearch); j++) {
|
||
|
|
if(IsRC(j, open, close)) { d1 = j; break; }
|
||
|
|
}
|
||
|
|
if(d1 != -1) {
|
||
|
|
DrawPivotLow(shift, d1, r2, open, high, low, close, time);
|
||
|
|
if (InpShowZigZag) ZigZagBuffer[shift] = low[shift];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if(InpHideLen > 0) {
|
||
|
|
int thresholdIndex = MathMin(InpHideLen + InpMaxSearch, rates_total - 1);
|
||
|
|
datetime thresholdTime = time[thresholdIndex];
|
||
|
|
|
||
|
|
for(int i = ObjectsTotal(0) - 1; i >= 0; i--) {
|
||
|
|
string name = ObjectName(0, i);
|
||
|
|
if(StringFind(name, currentPrefix) == 0) {
|
||
|
|
datetime objTime = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0);
|
||
|
|
if(objTime < thresholdTime) ObjectDelete(0, name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for(int i = thresholdIndex; i < rates_total; i++) {
|
||
|
|
ZigZagBuffer[i] = EMPTY_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ChartRedraw(0);
|
||
|
|
return(rates_total);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Helper Draw: Extreme Wick Marker |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawExtWick(int idx, color col, double price, bool isAbove, const datetime &time[]) {
|
||
|
|
string extName = GetPrefix() + "ExtWick_" + TimeToString(time[idx]);
|
||
|
|
if(ObjectFind(0, extName) >= 0) return;
|
||
|
|
ObjectCreate(0, extName, OBJ_ARROW, 0, time[idx], price);
|
||
|
|
ObjectSetInteger(0, extName, OBJPROP_ARROWCODE, 119); // Diamond
|
||
|
|
ObjectSetInteger(0, extName, OBJPROP_ANCHOR, isAbove ? ANCHOR_BOTTOM : ANCHOR_TOP);
|
||
|
|
ObjectSetInteger(0, extName, OBJPROP_COLOR, col);
|
||
|
|
ObjectSetInteger(0, extName, OBJPROP_WIDTH, 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Fungsi Menggambar Zone Pivot High (RBD) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawPivotHigh(int idxBase, int idxLeft, int idxRight, const double &open[], const double &high[], const double &low[], const double &close[], const datetime &time[]) {
|
||
|
|
string prefix = GetPrefix();
|
||
|
|
string timeStr = TimeToString(time[idxBase]);
|
||
|
|
string lineName = prefix + "LineHigh_" + timeStr;
|
||
|
|
|
||
|
|
if(ObjectFind(0, lineName) >= 0) return;
|
||
|
|
|
||
|
|
if (InpShowBoxes) {
|
||
|
|
double hst_rally = GetHighestBody(idxLeft, idxBase, open, close);
|
||
|
|
double lst_rally = GetLowestBody(idxLeft, idxBase, open, close);
|
||
|
|
double hst_drop = GetHighestBody(idxBase, idxRight, open, close);
|
||
|
|
double lst_drop = GetLowestBody(idxBase, idxRight, open, close);
|
||
|
|
|
||
|
|
string rallyBox = prefix + "BoxRally_" + timeStr;
|
||
|
|
string dropBox = prefix + "BoxDrop_" + timeStr;
|
||
|
|
|
||
|
|
ObjectCreate(0, rallyBox, OBJ_RECTANGLE, 0, time[idxLeft], hst_rally, time[idxBase], lst_rally);
|
||
|
|
ObjectSetInteger(0, rallyBox, OBJPROP_COLOR, InpRallyCol); ObjectSetInteger(0, rallyBox, OBJPROP_BGCOLOR, InpRallyCol);
|
||
|
|
ObjectSetInteger(0, rallyBox, OBJPROP_FILL, true); ObjectSetInteger(0, rallyBox, OBJPROP_BACK, true);
|
||
|
|
|
||
|
|
ObjectCreate(0, dropBox, OBJ_RECTANGLE, 0, time[idxBase], hst_drop, time[idxRight], lst_drop);
|
||
|
|
ObjectSetInteger(0, dropBox, OBJPROP_COLOR, InpDropCol); ObjectSetInteger(0, dropBox, OBJPROP_BGCOLOR, InpDropCol);
|
||
|
|
ObjectSetInteger(0, dropBox, OBJPROP_FILL, true); ObjectSetInteger(0, dropBox, OBJPROP_BACK, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (InpShowLines) {
|
||
|
|
ObjectCreate(0, lineName, OBJ_TREND, 0, time[idxBase], high[idxBase], time[idxRight], high[idxBase]);
|
||
|
|
ObjectSetInteger(0, lineName, OBJPROP_COLOR, InpDropCol); ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, true);
|
||
|
|
ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, lineName, OBJPROP_BACK, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
DrawExtWick(idxBase, InpExtWickCol, high[idxBase], true, time);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Fungsi Menggambar Zone Pivot Low (DBR) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void DrawPivotLow(int idxBase, int idxLeft, int idxRight, const double &open[], const double &high[], const double &low[], const double &close[], const datetime &time[]) {
|
||
|
|
string prefix = GetPrefix();
|
||
|
|
string timeStr = TimeToString(time[idxBase]);
|
||
|
|
string lineName = prefix + "LineLow_" + timeStr;
|
||
|
|
|
||
|
|
if(ObjectFind(0, lineName) >= 0) return;
|
||
|
|
|
||
|
|
if (InpShowBoxes) {
|
||
|
|
double hst_drop = GetHighestBody(idxLeft, idxBase, open, close);
|
||
|
|
double lst_drop = GetLowestBody(idxLeft, idxBase, open, close);
|
||
|
|
double hst_rally = GetHighestBody(idxBase, idxRight, open, close);
|
||
|
|
double lst_rally = GetLowestBody(idxBase, idxRight, open, close);
|
||
|
|
|
||
|
|
string dropBox = prefix + "BoxDrop_" + timeStr;
|
||
|
|
string rallyBox = prefix + "BoxRally_" + timeStr;
|
||
|
|
|
||
|
|
ObjectCreate(0, dropBox, OBJ_RECTANGLE, 0, time[idxLeft], hst_drop, time[idxBase], lst_drop);
|
||
|
|
ObjectSetInteger(0, dropBox, OBJPROP_COLOR, InpDropCol); ObjectSetInteger(0, dropBox, OBJPROP_BGCOLOR, InpDropCol);
|
||
|
|
ObjectSetInteger(0, dropBox, OBJPROP_FILL, true); ObjectSetInteger(0, dropBox, OBJPROP_BACK, true);
|
||
|
|
|
||
|
|
ObjectCreate(0, rallyBox, OBJ_RECTANGLE, 0, time[idxBase], hst_rally, time[idxRight], lst_rally);
|
||
|
|
ObjectSetInteger(0, rallyBox, OBJPROP_COLOR, InpRallyCol); ObjectSetInteger(0, rallyBox, OBJPROP_BGCOLOR, InpRallyCol);
|
||
|
|
ObjectSetInteger(0, rallyBox, OBJPROP_FILL, true); ObjectSetInteger(0, rallyBox, OBJPROP_BACK, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (InpShowLines) {
|
||
|
|
ObjectCreate(0, lineName, OBJ_TREND, 0, time[idxBase], low[idxBase], time[idxRight], low[idxBase]);
|
||
|
|
ObjectSetInteger(0, lineName, OBJPROP_COLOR, InpRallyCol); ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, true);
|
||
|
|
ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, lineName, OBJPROP_BACK, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
DrawExtWick(idxBase, InpExtWickCol, low[idxBase], false, time);
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|