773 lines
21 KiB
MQL5
773 lines
21 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| C_Chapter4_SupplyDemand.mqh |
| |||
//+------------------------------------------------------------------+
| |||
#property strict
| |||
#property version "1.00"
| |||
| |||
#include "AbstractChapter.mqh"
| |||
| |||
//========================
| |||
// Inputs
| |||
//========================
| |||
input int ch4_zoneLookbackBars = 350;
| |||
input int ch4_baseLookbackBars = 8;
| |||
input int ch4_momentumAvgPeriod = 12;
| |||
input double ch4_momentumBodyFactor = 1.20;
| |||
input int ch4_protectedTolerancePts = 35;
| |||
input int ch4_minZoneSeparationBars = 3;
| |||
input int ch4_refineMode = 1; // 0=full, 1=body, 2=half
| |||
input int ch4_minQualityScore = 3; // BOS + momentum + liquidity + imbalance
| |||
input bool ch4_requireBos = true;
| |||
input bool ch4_requireMomentum = true;
| |||
input bool ch4_requireLiquidity = false;
| |||
input bool ch4_requireImbalance = false;
| |||
input bool ch4_hideInvalidZones = false;
| |||
| |||
//========================
| |||
// Enums
| |||
//========================
| |||
enum EZoneSide { ZONE_DEMAND = 0, ZONE_SUPPLY = 1 };
| |||
enum EZoneType { ZONE_OB = 0, ZONE_EXTREME = 1, ZONE_DECISIONAL = 2 };
| |||
enum EZoneState { ZONE_FRESH = 0, ZONE_TOUCHED = 1, ZONE_MITIGATED = 2, ZONE_INVALID = 3 };
| |||
| |||
struct SupplyDemandZone {
| |||
int id;
| |||
EZoneSide side;
| |||
EZoneType type;
| |||
EZoneState state;
| |||
| |||
int leftBar;
| |||
int baseBar;
| |||
int breakBar;
| |||
int rightBar;
| |||
| |||
double fullTop;
| |||
double fullBottom;
| |||
| |||
double refinedTop;
| |||
double refinedBottom;
| |||
| |||
bool hasBos;
| |||
bool hasMomentum;
| |||
bool hasLiquidity;
| |||
bool hasImbalance;
| |||
bool isInducement;
| |||
| |||
int qualityScore;
| |||
int touchBar;
| |||
int mitigateBar;
| |||
int invalidateBar;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
class C_Chapter4 : public CAbstractChapter {
| |||
private:
| |||
FairValue fairValue;
| |||
| |||
SupplyDemandZone zones[];
| |||
int nextZoneId;
| |||
| |||
int protectedHigh;
| |||
int protectedLow;
| |||
| |||
string K_ZONE(int id) { return "CH4_ZONE_" + (string)id; }
| |||
string K_ZONE_R(int id) { return "CH4_ZONE_R_" + (string)id; }
| |||
| |||
private:
| |||
//========================
| |||
// Core helpers
| |||
//========================
| |||
void PushZone(SupplyDemandZone &z) {
| |||
int n = ArraySize(zones);
| |||
ArrayResize(zones, n + 1);
| |||
zones[n] = z;
| |||
}
| |||
| |||
bool ZoneExists(EZoneSide side, int baseBar, int breakBar, double top, double bottom) {
| |||
for(int i = 0; i < ArraySize(zones); i++) {
| |||
if(zones[i].side != side)
| |||
continue;
| |||
| |||
if(zones[i].baseBar != baseBar)
| |||
continue;
| |||
| |||
if(zones[i].breakBar != breakBar)
| |||
continue;
| |||
| |||
if(MathAbs(zones[i].fullTop - top) <= _Point * 2 &&
| |||
MathAbs(zones[i].fullBottom - bottom) <= _Point * 2)
| |||
return true;
| |||
}
| |||
| |||
return false;
| |||
}
| |||
| |||
bool HasBullImbalanceAround(int breakBar) {
| |||
int from = breakBar - 1;
| |||
int to = breakBar + 1;
| |||
| |||
if(from < 1) from = 1;
| |||
if(to >= Bars()) to = Bars() - 1;
| |||
| |||
for(int i = from; i <= to; i++) {
| |||
Fvg fvg;
| |||
fairValue.getBullFvg(fvg, i);
| |||
if(fairValue.IsValidValue(fvg))
| |||
return true;
| |||
}
| |||
| |||
return false;
| |||
}
| |||
| |||
bool HasBearImbalanceAround(int breakBar) {
| |||
int from = breakBar - 1;
| |||
int to = breakBar + 1;
| |||
| |||
if(from < 1) from = 1;
| |||
if(to >= Bars()) to = Bars() - 1;
| |||
| |||
for(int i = from; i <= to; i++) {
| |||
Fvg fvg;
| |||
fairValue.getBearFvg(fvg, i);
| |||
if(fairValue.IsValidValue(fvg))
| |||
return true;
| |||
}
| |||
| |||
return false;
| |||
}
| |||
| |||
double AvgBodySize(int endIndex, int period) {
| |||
if(period <= 0)
| |||
return 0.0;
| |||
| |||
int from = endIndex - period + 1;
| |||
if(from < 0)
| |||
from = 0;
| |||
| |||
double sum = 0.0;
| |||
int count = 0;
| |||
| |||
for(int i = from; i <= endIndex; i++) {
| |||
if(!IsValidIndex(i))
| |||
continue;
| |||
| |||
sum += BodySize(i);
| |||
count++;
| |||
}
| |||
| |||
if(count == 0)
| |||
return 0.0;
| |||
| |||
return sum / count;
| |||
}
| |||
| |||
bool IsBullMomentumBar(int i) {
| |||
if(!IsBullishCandle(i))
| |||
return false;
| |||
| |||
double avg = AvgBodySize(i - 1, ch4_momentumAvgPeriod);
| |||
if(avg <= 0.0)
| |||
avg = BodySize(i);
| |||
| |||
return BodySize(i) >= avg * ch4_momentumBodyFactor;
| |||
}
| |||
| |||
bool IsBearMomentumBar(int i) {
| |||
if(!IsBearishCandle(i))
| |||
return false;
| |||
| |||
double avg = AvgBodySize(i - 1, ch4_momentumAvgPeriod);
| |||
if(avg <= 0.0)
| |||
avg = BodySize(i);
| |||
| |||
return BodySize(i) >= avg * ch4_momentumBodyFactor;
| |||
}
| |||
| |||
int FindBrokenSwingHigh(int breakBar) {
| |||
int start = breakBar - ch4_baseLookbackBars - 10;
| |||
if(start < 0) start = 0;
| |||
| |||
int last = -1;
| |||
| |||
for(int i = start; i < breakBar; i++) {
| |||
if(!IsSwingHigh(i))
| |||
continue;
| |||
| |||
if(BreakAboveHigh(i, breakBar))
| |||
last = i;
| |||
}
| |||
| |||
return last;
| |||
}
| |||
| |||
int FindBrokenSwingLow(int breakBar) {
| |||
int start = breakBar - ch4_baseLookbackBars - 10;
| |||
if(start < 0) start = 0;
| |||
| |||
int last = -1;
| |||
| |||
for(int i = start; i < breakBar; i++) {
| |||
if(!IsSwingLow(i))
| |||
continue;
| |||
| |||
if(BreakBelowLow(i, breakBar))
| |||
last = i;
| |||
}
| |||
| |||
return last;
| |||
}
| |||
| |||
int FindDemandBaseBar(int breakBar) {
| |||
int from = breakBar - ch4_baseLookbackBars;
| |||
if(from < 0) from = 0;
| |||
| |||
for(int i = breakBar - 1; i >= from; i--) {
| |||
if(IsBearishCandle(i) || IsSellToBuyCandle(i) || IsDojiCandle(i, 0.20))
| |||
return i;
| |||
}
| |||
| |||
return breakBar - 1;
| |||
}
| |||
| |||
int FindSupplyBaseBar(int breakBar) {
| |||
int from = breakBar - ch4_baseLookbackBars;
| |||
if(from < 0) from = 0;
| |||
| |||
for(int i = breakBar - 1; i >= from; i--) {
| |||
if(IsBullishCandle(i) || IsBuyToSellCandle(i) || IsDojiCandle(i, 0.20))
| |||
return i;
| |||
}
| |||
| |||
return breakBar - 1;
| |||
}
| |||
| |||
int FindBaseStartBar(int baseBar) {
| |||
if(!IsValidIndex(baseBar))
| |||
return -1;
| |||
| |||
int from = baseBar - ch4_baseLookbackBars;
| |||
if(from < 0) from = 0;
| |||
| |||
int left = baseBar;
| |||
| |||
for(int i = baseBar; i >= from; i--) {
| |||
if(RangeAt(i) <= 0.0)
| |||
continue;
| |||
| |||
left = i;
| |||
| |||
if(BodySize(i) > AvgBodySize(i, 3) * 1.4)
| |||
break;
| |||
}
| |||
| |||
return left;
| |||
}
| |||
| |||
void BuildDemandZone(int leftBar,int baseBar,int breakBar,double &fullTop,double &fullBottom,double &refinedTop,double &refinedBottom) {
| |||
fullTop = HighAt(baseBar);
| |||
fullBottom = LowAt(baseBar);
| |||
| |||
for(int i = leftBar; i <= baseBar; i++) {
| |||
if(HighAt(i) > fullTop) fullTop = HighAt(i);
| |||
if(LowAt(i) < fullBottom) fullBottom = LowAt(i);
| |||
}
| |||
| |||
if(ch4_refineMode == 0) {
| |||
refinedTop = fullTop;
| |||
refinedBottom = fullBottom;
| |||
} else if(ch4_refineMode == 1) {
| |||
refinedTop = BodyTop(baseBar);
| |||
refinedBottom = BodyBottom(baseBar);
| |||
} else {
| |||
refinedTop = fullTop;
| |||
refinedBottom = ZoneMid(fullTop, fullBottom);
| |||
}
| |||
}
| |||
| |||
void BuildSupplyZone(int leftBar,int baseBar,int breakBar,double &fullTop,double &fullBottom,double &refinedTop,double &refinedBottom) {
| |||
fullTop = HighAt(baseBar);
| |||
fullBottom = LowAt(baseBar);
| |||
| |||
for(int i = leftBar; i <= baseBar; i++) {
| |||
if(HighAt(i) > fullTop) fullTop = HighAt(i);
| |||
if(LowAt(i) < fullBottom) fullBottom = LowAt(i);
| |||
}
| |||
| |||
if(ch4_refineMode == 0) {
| |||
refinedTop = fullTop;
| |||
refinedBottom = fullBottom;
| |||
} else if(ch4_refineMode == 1) {
| |||
refinedTop = BodyTop(baseBar);
| |||
refinedBottom = BodyBottom(baseBar);
| |||
} else {
| |||
refinedTop = ZoneMid(fullTop, fullBottom);
| |||
refinedBottom = fullBottom;
| |||
}
| |||
}
| |||
| |||
bool NearProtectedLow(int baseBar) {
| |||
if(!IsValidIndex(protectedLow))
| |||
return false;
| |||
| |||
return NearPrice(LowAt(baseBar), LowAt(protectedLow), ch4_protectedTolerancePts);
| |||
}
| |||
| |||
bool NearProtectedHigh(int baseBar) {
| |||
if(!IsValidIndex(protectedHigh))
| |||
return false;
| |||
| |||
return NearPrice(HighAt(baseBar), HighAt(protectedHigh), ch4_protectedTolerancePts);
| |||
}
| |||
| |||
bool HasBullLiquidityContext(int baseBar, int breakBar) {
| |||
if(IsValidIndex(protectedLow)) {
| |||
int from = baseBar - 3;
| |||
if(from < 0) from = 0;
| |||
| |||
for(int i = from; i <= breakBar; i++) {
| |||
if(WickBelowLow(protectedLow, i) || BreakBelowLow(protectedLow, i))
| |||
return true;
| |||
}
| |||
}
| |||
| |||
int lowA = PrevSwingLowIndex(baseBar, 1);
| |||
int lowB = PrevSwingLowIndex(baseBar, 2);
| |||
if(IsValidIndex(lowA) && IsValidIndex(lowB)) {
| |||
if(NearPrice(LowAt(lowA), LowAt(lowB), ch4_protectedTolerancePts))
| |||
return true;
| |||
}
| |||
| |||
return false;
| |||
}
| |||
| |||
bool HasBearLiquidityContext(int baseBar, int breakBar) {
| |||
if(IsValidIndex(protectedHigh)) {
| |||
int from = baseBar - 3;
| |||
if(from < 0) from = 0;
| |||
| |||
for(int i = from; i <= breakBar; i++) {
| |||
if(WickAboveHigh(protectedHigh, i) || BreakAboveHigh(protectedHigh, i))
| |||
return true;
| |||
}
| |||
}
| |||
| |||
int highA = PrevSwingHighIndex(baseBar, 1);
| |||
int highB = PrevSwingHighIndex(baseBar, 2);
| |||
if(IsValidIndex(highA) && IsValidIndex(highB)) {
| |||
if(NearPrice(HighAt(highA), HighAt(highB), ch4_protectedTolerancePts))
| |||
return true;
| |||
}
| |||
| |||
return false;
| |||
}
| |||
| |||
EZoneType ClassifyDemandType(int baseBar, int breakBar) {
| |||
if(NearProtectedLow(baseBar))
| |||
return ZONE_EXTREME;
| |||
| |||
return ZONE_DECISIONAL;
| |||
}
| |||
| |||
EZoneType ClassifySupplyType(int baseBar, int breakBar) {
| |||
if(NearProtectedHigh(baseBar))
| |||
return ZONE_EXTREME;
| |||
| |||
return ZONE_DECISIONAL;
| |||
}
| |||
| |||
int ScoreZone(bool hasBos, bool hasMomentum, bool hasLiquidity, bool hasImbalance) {
| |||
int score = 0;
| |||
if(hasBos) score++;
| |||
if(hasMomentum) score++;
| |||
if(hasLiquidity) score++;
| |||
if(hasImbalance) score++;
| |||
return score;
| |||
}
| |||
| |||
bool PassesFilters(bool hasBos, bool hasMomentum, bool hasLiquidity, bool hasImbalance, int score) {
| |||
if(ch4_requireBos && !hasBos) return false;
| |||
if(ch4_requireMomentum && !hasMomentum) return false;
| |||
if(ch4_requireLiquidity && !hasLiquidity) return false;
| |||
if(ch4_requireImbalance && !hasImbalance) return false;
| |||
| |||
return score >= ch4_minQualityScore;
| |||
}
| |||
| |||
//========================
| |||
// Detection
| |||
//========================
| |||
void DetectDemandZones() {
| |||
int end = Bars() - 1;
| |||
if(end < 10)
| |||
return;
| |||
| |||
int start = end - ch4_zoneLookbackBars;
| |||
if(start < 5) start = 5;
| |||
| |||
for(int i = start; i <= end; i++) {
| |||
if(!IsBullMomentumBar(i))
| |||
continue;
| |||
| |||
int brokenHigh = FindBrokenSwingHigh(i);
| |||
bool hasBos = IsValidIndex(brokenHigh);
| |||
| |||
if(ch4_requireBos && !hasBos)
| |||
continue;
| |||
| |||
int baseBar = FindDemandBaseBar(i);
| |||
if(!IsValidIndex(baseBar))
| |||
continue;
| |||
| |||
int leftBar = FindBaseStartBar(baseBar);
| |||
if(!IsValidIndex(leftBar))
| |||
leftBar = baseBar;
| |||
| |||
double fullTop, fullBottom, refinedTop, refinedBottom;
| |||
BuildDemandZone(leftBar, baseBar, i, fullTop, fullBottom, refinedTop, refinedBottom);
| |||
| |||
bool hasMomentum = true;
| |||
bool hasLiquidity = HasBullLiquidityContext(baseBar, i);
| |||
bool hasImbalance = HasBullImbalanceAround(i);
| |||
| |||
int score = ScoreZone(hasBos, hasMomentum, hasLiquidity, hasImbalance);
| |||
if(!PassesFilters(hasBos, hasMomentum, hasLiquidity, hasImbalance, score))
| |||
continue;
| |||
| |||
if(ZoneExists(ZONE_DEMAND, baseBar, i, fullTop, fullBottom))
| |||
continue;
| |||
| |||
SupplyDemandZone z;
| |||
z.id = nextZoneId++;
| |||
z.side = ZONE_DEMAND;
| |||
z.type = ClassifyDemandType(baseBar, i);
| |||
z.state = ZONE_FRESH;
| |||
| |||
z.leftBar = leftBar;
| |||
z.baseBar = baseBar;
| |||
z.breakBar = i;
| |||
z.rightBar = i;
| |||
| |||
z.fullTop = fullTop;
| |||
z.fullBottom = fullBottom;
| |||
z.refinedTop = refinedTop;
| |||
z.refinedBottom = refinedBottom;
| |||
| |||
z.hasBos = hasBos;
| |||
z.hasMomentum = hasMomentum;
| |||
z.hasLiquidity = hasLiquidity;
| |||
z.hasImbalance = hasImbalance;
| |||
z.isInducement = false;
| |||
| |||
z.qualityScore = score;
| |||
z.touchBar = -1;
| |||
z.mitigateBar = -1;
| |||
z.invalidateBar = -1;
| |||
| |||
PushZone(z);
| |||
}
| |||
}
| |||
| |||
void DetectSupplyZones() {
| |||
int end = Bars() - 1;
| |||
if(end < 10)
| |||
return;
| |||
| |||
int start = end - ch4_zoneLookbackBars;
| |||
if(start < 5) start = 5;
| |||
| |||
for(int i = start; i <= end; i++) {
| |||
if(!IsBearMomentumBar(i))
| |||
continue;
| |||
| |||
int brokenLow = FindBrokenSwingLow(i);
| |||
bool hasBos = IsValidIndex(brokenLow);
| |||
| |||
if(ch4_requireBos && !hasBos)
| |||
continue;
| |||
| |||
int baseBar = FindSupplyBaseBar(i);
| |||
if(!IsValidIndex(baseBar))
| |||
continue;
| |||
| |||
int leftBar = FindBaseStartBar(baseBar);
| |||
if(!IsValidIndex(leftBar))
| |||
leftBar = baseBar;
| |||
| |||
double fullTop, fullBottom, refinedTop, refinedBottom;
| |||
BuildSupplyZone(leftBar, baseBar, i, fullTop, fullBottom, refinedTop, refinedBottom);
| |||
| |||
bool hasMomentum = true;
| |||
bool hasLiquidity = HasBearLiquidityContext(baseBar, i);
| |||
bool hasImbalance = HasBearImbalanceAround(i);
| |||
| |||
int score = ScoreZone(hasBos, hasMomentum, hasLiquidity, hasImbalance);
| |||
if(!PassesFilters(hasBos, hasMomentum, hasLiquidity, hasImbalance, score))
| |||
continue;
| |||
| |||
if(ZoneExists(ZONE_SUPPLY, baseBar, i, fullTop, fullBottom))
| |||
continue;
| |||
| |||
SupplyDemandZone z;
| |||
z.id = nextZoneId++;
| |||
z.side = ZONE_SUPPLY;
| |||
z.type = ClassifySupplyType(baseBar, i);
| |||
z.state = ZONE_FRESH;
| |||
| |||
z.leftBar = leftBar;
| |||
z.baseBar = baseBar;
| |||
z.breakBar = i;
| |||
z.rightBar = i;
| |||
| |||
z.fullTop = fullTop;
| |||
z.fullBottom = fullBottom;
| |||
z.refinedTop = refinedTop;
| |||
z.refinedBottom = refinedBottom;
| |||
| |||
z.hasBos = hasBos;
| |||
z.hasMomentum = hasMomentum;
| |||
z.hasLiquidity = hasLiquidity;
| |||
z.hasImbalance = hasImbalance;
| |||
z.isInducement = false;
| |||
| |||
z.qualityScore = score;
| |||
z.touchBar = -1;
| |||
z.mitigateBar = -1;
| |||
z.invalidateBar = -1;
| |||
| |||
PushZone(z);
| |||
}
| |||
}
| |||
| |||
//========================
| |||
// Inducement tagging
| |||
//========================
| |||
void MarkInducementZones() {
| |||
for(int i = 0; i < ArraySize(zones); i++) {
| |||
zones[i].isInducement = false;
| |||
}
| |||
| |||
for(int i = 0; i < ArraySize(zones); i++) {
| |||
for(int j = 0; j < ArraySize(zones); j++) {
| |||
if(i == j)
| |||
continue;
| |||
| |||
if(zones[i].side != zones[j].side)
| |||
continue;
| |||
| |||
if(zones[i].state == ZONE_INVALID || zones[j].state == ZONE_INVALID)
| |||
continue;
| |||
| |||
if(zones[i].side == ZONE_DEMAND) {
| |||
if(zones[i].baseBar < zones[j].baseBar &&
| |||
zones[i].fullBottom > zones[j].fullBottom &&
| |||
zones[i].qualityScore < zones[j].qualityScore) {
| |||
zones[i].isInducement = true;
| |||
}
| |||
} else {
| |||
if(zones[i].baseBar < zones[j].baseBar &&
| |||
zones[i].fullTop < zones[j].fullTop &&
| |||
zones[i].qualityScore < zones[j].qualityScore) {
| |||
zones[i].isInducement = true;
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//========================
| |||
// Lifecycle
| |||
//========================
| |||
void UpdateZoneState() {
| |||
int end = Bars() - 1;
| |||
| |||
for(int i = 0; i < ArraySize(zones); i++) {
| |||
if(zones[i].state == ZONE_INVALID)
| |||
continue;
| |||
| |||
for(int b = zones[i].breakBar + 1; b <= end; b++) {
| |||
bool touched = PriceTouchesZone(b, zones[i].fullTop, zones[i].fullBottom);
| |||
bool reached50 = false;
| |||
bool invalid = false;
| |||
| |||
if(zones[i].side == ZONE_DEMAND) {
| |||
reached50 = LowAt(b) <= ZoneMid(zones[i].fullTop, zones[i].fullBottom);
| |||
invalid = PriceClosesBeyondDemand(b, zones[i].fullBottom);
| |||
} else {
| |||
reached50 = HighAt(b) >= ZoneMid(zones[i].fullTop, zones[i].fullBottom);
| |||
invalid = PriceClosesBeyondSupply(b, zones[i].fullTop);
| |||
}
| |||
| |||
if(touched && zones[i].touchBar < 0) {
| |||
zones[i].touchBar = b;
| |||
if(zones[i].state == ZONE_FRESH)
| |||
zones[i].state = ZONE_TOUCHED;
| |||
}
| |||
| |||
if(reached50 && zones[i].mitigateBar < 0) {
| |||
zones[i].mitigateBar = b;
| |||
if(zones[i].state != ZONE_INVALID)
| |||
zones[i].state = ZONE_MITIGATED;
| |||
}
| |||
| |||
if(invalid) {
| |||
zones[i].invalidateBar = b;
| |||
zones[i].state = ZONE_INVALID;
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//========================
| |||
// Draw
| |||
//========================
| |||
string TypeText(EZoneType type) {
| |||
if(type == ZONE_EXTREME) return "EXT";
| |||
if(type == ZONE_DECISIONAL) return "DEC";
| |||
return "OB";
| |||
}
| |||
| |||
string StateText(EZoneState state) {
| |||
if(state == ZONE_FRESH) return "FRESH";
| |||
if(state == ZONE_TOUCHED) return "TOUCHED";
| |||
if(state == ZONE_MITIGATED) return "MITIGATED";
| |||
return "INVALID";
| |||
}
| |||
| |||
color ZoneColor(const SupplyDemandZone &z) {
| |||
if(z.state == ZONE_INVALID)
| |||
return clrSilver;
| |||
| |||
if(z.state == ZONE_MITIGATED)
| |||
return clrDarkGray;
| |||
| |||
if(z.side == ZONE_DEMAND)
| |||
return clrPaleGreen;
| |||
| |||
return clrTomato;
| |||
}
| |||
| |||
string ZoneText(const SupplyDemandZone &z) {
| |||
string txt = (z.side == ZONE_DEMAND ? "DEMAND " : "SUPPLY ");
| |||
txt += TypeText(z.type);
| |||
txt += " ";
| |||
txt += StateText(z.state);
| |||
| |||
if(z.isInducement)
| |||
txt += " IDM";
| |||
| |||
if(z.hasBos) txt += " BOS";
| |||
if(z.hasMomentum) txt += " MOM";
| |||
if(z.hasLiquidity) txt += " LIQ";
| |||
if(z.hasImbalance) txt += " IMB";
| |||
| |||
return txt;
| |||
}
| |||
| |||
void DrawZones() {
| |||
int end = Bars() - 1;
| |||
| |||
for(int i = 0; i < ArraySize(zones); i++) {
| |||
SupplyDemandZone z = zones[i];
| |||
| |||
if(ch4_hideInvalidZones && z.state == ZONE_INVALID)
| |||
continue;
| |||
| |||
int toIndex = end;
| |||
if(z.invalidateBar >= 0) toIndex = z.invalidateBar;
| |||
| |||
color fullColor = ZoneColor(z);
| |||
string txt = ZoneText(z);
| |||
| |||
DrawZone(
| |||
K_ZONE(z.id),
| |||
z.leftBar,
| |||
toIndex,
| |||
z.fullTop,
| |||
z.fullBottom,
| |||
txt,
| |||
fullColor,
| |||
0.06,
| |||
6,
| |||
false
| |||
);
| |||
| |||
DrawZone(
| |||
K_ZONE_R(z.id),
| |||
z.leftBar,
| |||
toIndex,
| |||
z.refinedTop,
| |||
z.refinedBottom,
| |||
"",
| |||
(z.side == ZONE_DEMAND ? clrLime : clrRed),
| |||
0.16,
| |||
5,
| |||
true
| |||
);
| |||
}
| |||
}
| |||
| |||
public:
| |||
C_Chapter4() {
| |||
nextZoneId = 1;
| |||
protectedHigh = -1;
| |||
protectedLow = -1;
| |||
ArrayResize(zones, 0);
| |||
}
| |||
| |||
~C_Chapter4() {}
| |||
| |||
void SetExternalRange(int protectedHighIndex, int protectedLowIndex) {
| |||
protectedHigh = protectedHighIndex;
| |||
protectedLow = protectedLowIndex;
| |||
}
| |||
| |||
int GetZonesCount() const {
| |||
return ArraySize(zones);
| |||
}
| |||
| |||
bool GetZone(int index, SupplyDemandZone &outZone) {
| |||
if(index < 0 || index >= ArraySize(zones))
| |||
return false;
| |||
| |||
outZone = zones[index];
| |||
return true;
| |||
}
| |||
| |||
int OnInit(string s, ENUM_TIMEFRAMES tf_, int lr = 3, int look = 1) {
| |||
timeframe = tf_;
| |||
symbol = s;
| |||
debug.Init(0, "Chapter4", true);
| |||
| |||
if(swing.Init("Fractal", s, tf_, lr, look) == -1) {
| |||
Print("Error creating Swing Indicator: ", GetLastError());
| |||
return INIT_FAILED;
| |||
}
| |||
| |||
if(fairValue.Init("Fair Value Gaps", s, tf_, lr, look) == -1) {
| |||
Print("Error creating Fair Value indicator: ", GetLastError());
| |||
return INIT_FAILED;
| |||
}
| |||
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
void OnCalculate() {
| |||
if(!OnNewBar())
| |||
return;
| |||
| |||
UpdateBars(3);
| |||
| |||
ArrayResize(zones, 0);
| |||
nextZoneId = 1;
| |||
| |||
DetectDemandZones();
| |||
DetectSupplyZones();
| |||
MarkInducementZones();
| |||
UpdateZoneState();
| |||
DrawZones();
| |||
}
| |||
};
| |||
//+------------------------------------------------------------------+
|