//+------------------------------------------------------------------+ //| StructureSystem.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "EventBus.mqh" #include "AbstractSystem.mqh" #include "../Data/StructureState.mqh" #include "../Interfaces/Events/IStructEvent.mqh" #include //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class StructureSystem : public AbstractSystem { private: StructureState *m_state; int m_swingStrength; bool m_trendBullish; //--- startup / seeking flags bool bullNoBosActive, bullSeekingReplacementNoBos; bool bearNoBosActive, bearSeekingReplacementNoBos; bool isStartup; //--- NO BOS (resistance / support) // TimePrice levels.bullNoBos; // TimePrice levels.bearNoBos; //--- EXTREMES (lowest / highest after NoBos) TimePrice noBullBosLow; TimePrice noBearBosHigh; //--- BOS (Break of Structure) TimePrice bosBull1;//, levels.bullConfirmedLow; TimePrice bosBull1B, confirmedLowB; TimePrice bosBull2;//, levels.protectedLow; TimePrice bosBull2B, protectedLowB; TimePrice bosBear1;//, levels.bearConfirmedHigh; TimePrice bosBear1B, confirmedHighB; TimePrice bosBear2;//, levels.protectedHigh; TimePrice bosBear2B, protectedHighB; // int levels.direction; // -1 bearish, 0 neutral/startup, 1 bullish //--- private methods void UpdateSwingPoints(int i); void OnInitTracking(bool isBull, int i); void UpdateTracker(bool isBull, int i); void FindDirection(int i); bool CheckBreak(bool isAbove, int i, TimePrice &tp) { if(isAbove) { if(com.CloseAt(i) > tp.y && com.IsSwingHigh(i)) return true; return false; } //--- BearBreak if(com.CloseAt(i) < tp.y && com.IsSwingLow(i)) return true; return false; } void UpdateNoBosLive(int i) { datetime t = com.TimeAt(i); if(levels.direction == 1) { if(com.LowAt(i) < noBullBosLow.y) noBullBosLow.Set(t, com.LowAt(i)); return; } if(levels.direction == -1) { if(com.HighAt(i) > noBearBosHigh.y) noBearBosHigh.Set(t, com.HighAt(i)); } } //----------------------------------------------------------------- // CHoCH = Change of Character – now only sets the B‑point, NO reversal //----------------------------------------------------------------- void CheckChoch(int i) { datetime t = com.TimeAt(i); if(levels.direction == 1) { if(CheckBreak(false, i, levels.bullConfirmedLow) && !confirmedLowB.Ok()) confirmedLowB.Set(t, com.LowAt(i)); } if(levels.direction == -1) { if(CheckBreak(true, i, levels.bearConfirmedHigh) && !confirmedHighB.Ok()) confirmedHighB.Set(t, com.HighAt(i)); } } void CheckBos(int i) { datetime t = com.TimeAt(i); //--- Bullish BOS (break above levels.bullNoBos) if(levels.direction == 1) { if(CheckBreak(true, i, levels.bullNoBos)) { if(noBullBosLow.y > levels.bullConfirmedLow.y) { levels.protectedLow = levels.bullConfirmedLow; protectedLowB.Reset(); bosBull2 = bosBull1; bosBull2B = bosBull1B; } levels.bullConfirmedLow = noBullBosLow; confirmedLowB.Reset(); bosBull1 = levels.bullNoBos; bosBull1B.Set(t, levels.bullNoBos.y); levels.bullNoBos.Set(t, com.HighAt(i)); noBullBosLow.Set(t, com.LowAt(i)); levels.direction = 1; return; } } //--- Bearish BOS (break below levels.bearNoBos) if(levels.direction == -1) { if(CheckBreak(false, i, levels.bearNoBos)) { if(noBearBosHigh.y < levels.bearConfirmedHigh.y) { levels.protectedHigh = levels.bearConfirmedHigh; protectedHighB.Reset(); bosBear2 = bosBear1; bosBear2B = bosBear1B; } levels.bearConfirmedHigh = noBearBosHigh; confirmedHighB.Reset(); bosBear1 = levels.bearNoBos; bosBear1B.Set(t, levels.bearNoBos.y); levels.bearNoBos.Set(t, com.LowAt(i)); noBearBosHigh.Set(t, com.HighAt(i)); levels.direction = -1; return; } } } void CheckReversal(int i) { datetime t = com.TimeAt(i); //--- Bullish → Bearish reversal if(levels.direction == 1) { // Case 1: Protected low exists → use it as reversal level if(levels.protectedLow.Ok()) { if(CheckBreak(false, i, levels.protectedLow)) { // Flip to bearish levels.bearConfirmedHigh = levels.bullNoBos; confirmedHighB.Reset(); bosBear1 = levels.bearNoBos; bosBear1B.Set(t, levels.bearNoBos.y); levels.bearNoBos.Set(t, com.LowAt(i)); noBearBosHigh.Set(t, com.HighAt(i)); levels.bullNoBos.Reset(); noBullBosLow.Reset(); levels.bullConfirmedLow.Reset(); levels.protectedLow.Reset(); protectedLowB.Reset(); levels.direction = -1; } return; } // Case 2: No protected low → use confirmed low as fallback else { if(CheckBreak(false, i, levels.bullConfirmedLow)) { levels.bearConfirmedHigh = levels.bullNoBos; confirmedHighB.Reset(); bosBear1 = levels.bearNoBos; bosBear1B.Set(t, levels.bearNoBos.y); levels.bearNoBos.Set(t, com.LowAt(i)); noBearBosHigh.Set(t, com.HighAt(i)); levels.bullNoBos.Reset(); noBullBosLow.Reset(); levels.bullConfirmedLow.Reset(); levels.protectedLow.Reset(); protectedLowB.Reset(); levels.direction = -1; } return; } } //--- Bearish → Bullish reversal (symmetrical to bullish side) if(levels.direction == -1) { // Case 1: Protected high exists → use it as reversal level if(levels.protectedHigh.Ok()) { if(CheckBreak(true, i, levels.protectedHigh)) { // Flip to bullish levels.bullConfirmedLow = levels.bearNoBos; confirmedLowB.Reset(); bosBull1 = levels.bullNoBos; bosBull1B.Set(t, levels.bullNoBos.y); levels.bullNoBos.Set(t, com.HighAt(i)); noBullBosLow.Set(t, com.LowAt(i)); levels.bearNoBos.Reset(); noBearBosHigh.Reset(); levels.bearConfirmedHigh.Reset(); levels.protectedHigh.Reset(); protectedHighB.Reset(); levels.direction = 1; } return; } // Case 2: No protected high → use confirmed high as fallback else { if(CheckBreak(true, i, levels.bearConfirmedHigh)) { levels.bullConfirmedLow = levels.bearNoBos; confirmedLowB.Reset(); bosBull1 = levels.bullNoBos; bosBull1B.Set(t, levels.bullNoBos.y); levels.bullNoBos.Set(t, com.HighAt(i)); noBullBosLow.Set(t, com.LowAt(i)); levels.bearNoBos.Reset(); noBearBosHigh.Reset(); levels.bearConfirmedHigh.Reset(); levels.protectedHigh.Reset(); protectedHighB.Reset(); levels.direction = 1; } return; } } } public: StructureSystem(); ~StructureSystem() { delete m_state; }; virtual int OnInit(StructureLevels* levels_, EventBus &_eventBus) override { AbstractSystem::OnInit(levels_, &_eventBus); m_state = new StructureState(); isStartup = true; bullNoBosActive = false; bearNoBosActive = false; bullSeekingReplacementNoBos = true; bearSeekingReplacementNoBos = true; levels.bullNoBos.Reset(); noBullBosLow.Reset(); levels.bullConfirmedLow.Reset(); levels.protectedLow.Reset(); protectedLowB.Reset(); levels.bearNoBos.Reset(); noBearBosHigh.Reset(); levels.bearConfirmedHigh.Reset(); levels.protectedHigh.Reset(); protectedHighB.Reset(); levels.direction = 0; return INIT_SUCCEEDED; } virtual void OnCalculate(int i, CommonHelper &commonHelper) override; bool IsBullish() { return m_trendBullish; } StructureState *GetState() { return m_state; } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ StructureSystem::StructureSystem() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void StructureSystem::OnCalculate(int i, CommonHelper &commonHelper) { AbstractSystem::OnCalculate(i, commonHelper); if(!com.IsValidIndex(i)) return; datetime curBarTime = com.TimeAt(i); m_state.ApplyDecay(curBarTime); UpdateSwingPoints(i); //--- Startup: locate first NoBos if(!bullNoBosActive && bullSeekingReplacementNoBos && isStartup) { OnInitTracking(true, i); return; } if(!bearNoBosActive && bearSeekingReplacementNoBos && isStartup) { OnInitTracking(false, i); } if(isStartup && bearNoBosActive && bullNoBosActive) { // Align extremes if(levels.bullNoBos.x < levels.bearNoBos.x) noBullBosLow.Set(levels.bearNoBos); else noBearBosHigh.Set(levels.bullNoBos); isStartup = false; } else if(!isStartup && (!bearNoBosActive || !bullNoBosActive)) return; FindDirection(i); UpdateNoBosLive(i); CheckBos(i); CheckChoch(i); CheckReversal(i); // Only updates B‑points, no reversal // Actual reversal logic //--- Visual objects ObjectsDeleteAll(0, 0, OBJ_TREND); Comment(levels.direction); if(levels.direction == 1) // Bullish { if(levels.bullNoBos.Ok()) createTrend("nobos", levels.bullNoBos.x, levels.bullNoBos.y, curBarTime, levels.bullNoBos.y, clrBlue); if(noBullBosLow.Ok()) createTrend("noBullBosLow", noBullBosLow.x, noBullBosLow.y, curBarTime, noBullBosLow.y, clrBlue); if(bosBull1.Ok()) createTrend("bosBull1", bosBull1.x, bosBull1.y, bosBull1B.x, bosBull1B.y, clrBlue); if(levels.bullConfirmedLow.Ok()) createTrend("levels.bullConfirmedLow", levels.bullConfirmedLow.x, levels.bullConfirmedLow.y, confirmedLowB.x == 0 ? curBarTime : confirmedLowB.x, levels.bullConfirmedLow.y, clrBlue); if(bosBull2.Ok()) createTrend("bosBull2", bosBull2.x, bosBull2.y, bosBull2B.x, bosBull2B.y, clrBlue); if(levels.protectedLow.Ok()) createTrend("levels.protectedLow", levels.protectedLow.x, levels.protectedLow.y, protectedLowB.x == 0 ? curBarTime : levels.protectedLow.x, levels.protectedLow.y, clrBlue); } else if(levels.direction == -1) // Bearish { if(noBearBosHigh.Ok()) createTrend("noBearBosHigh", noBearBosHigh.x, noBearBosHigh.y, curBarTime, noBearBosHigh.y, clrBrown); if(levels.bearNoBos.Ok()) createTrend("levels.bearNoBos", levels.bearNoBos.x, levels.bearNoBos.y, curBarTime, levels.bearNoBos.y, clrRed); if(bosBear1.Ok()) createTrend("bosBear1", bosBear1.x, bosBear1.y, bosBear1B.x, bosBear1B.y, clrAquamarine); if(levels.bearConfirmedHigh.Ok()) createTrend("levels.bearConfirmedHigh", levels.bearConfirmedHigh.x, levels.bearConfirmedHigh.y, confirmedHighB.x == 0 ? curBarTime : confirmedHighB.x, levels.bearConfirmedHigh.y, clrBlue); if(bosBear2.Ok()) createTrend("bosBear2", bosBear2.x, bosBear2.y, bosBear2B.x, bosBear2B.y, clrAquamarine); if(levels.protectedHigh.Ok()) createTrend("levels.protectedHigh", levels.protectedHigh.x, levels.protectedHigh.y, protectedHighB.x == 0 ? curBarTime : levels.protectedHigh.x, levels.protectedHigh.y, clrBlue); } else // levels.direction == 0 (startup) { if(levels.bullNoBos.Ok()) createTrend("nobos", levels.bullNoBos.x, levels.bullNoBos.y, levels.bullNoBos.x + PeriodSeconds(PERIOD_D1), levels.bullNoBos.y, clrBlue); if(levels.bearNoBos.Ok()) createTrend("levels.bearNoBos", levels.bearNoBos.x, levels.bearNoBos.y, levels.bearNoBos.x + PeriodSeconds(PERIOD_D1), levels.bearNoBos.y, clrRed); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void StructureSystem::FindDirection(int i) { if(levels.direction == 0) { // Bullish breakout if(CheckBreak(true, i, levels.bullNoBos)) { bosBull1.Set(levels.bullNoBos); bosBull1B.Set(com.TimeAt(i), levels.bullNoBos.y); levels.bullConfirmedLow.Set(noBullBosLow); levels.bullNoBos.Set(com.TimeAt(i), com.SwingHigh(i)); levels.direction = 1; return; } // Bearish breakout if(CheckBreak(false, i, levels.bearNoBos)) { bosBear1.Set(levels.bearNoBos); bosBear1B.Set(com.TimeAt(i), levels.bearNoBos.y); levels.bearConfirmedHigh.Set(noBearBosHigh); levels.bearNoBos.Set(com.TimeAt(i), com.SwingLow(i)); levels.direction = -1; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void StructureSystem::OnInitTracking(bool isBull, int i) { if(isBull) { if(!com.IsSwingHigh(i)) return; levels.bullNoBos.Set(i, com.TimeAt(i), com.HighAt(i)); noBullBosLow.Set(i, com.TimeAt(i), com.LowAt(i)); bullNoBosActive = true; return; } if(!com.IsSwingLow(i)) return; levels.bearNoBos.Set(i, com.TimeAt(i), com.LowAt(i)); noBearBosHigh.Set(i, com.TimeAt(i), com.HighAt(i)); bearNoBosActive = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void StructureSystem::UpdateSwingPoints(int i) { if(com.IsSwingHigh(i)) { double level = com.HighAt(i); datetime t = com.TimeAt(i); m_state.AddSwing(SWING_HIGH, level, t, com.TimeframeMinutes()); IEvent *e = new CStructEvent(STRUCT_DETECT_SWING_HIGH, level, t, t, com.TimeframeMinutes()); eventBus.Publish(e); } if(com.IsSwingLow(i)) { double level = com.LowAt(i); datetime t = com.TimeAt(i); m_state.AddSwing(SWING_LOW, level, t, com.TimeframeMinutes()); IEvent *e = new CStructEvent(STRUCT_DETECT_SWING_LOW, level, t, t, com.TimeframeMinutes()); eventBus.Publish(e); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void StructureSystem::UpdateTracker(bool isBull, int i) { // Placeholder – no additional tracking logic required yet } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //use same tracking system add internals..also store(ithase 3 timeprices..start,exteme,end)and draw /* Round 1 — Musical Alphabet 1. A,A#,B,C,C#,D,D#,E,F,F#,G,G# 2. B&C, E&F 3. B&C, E&F 4. G#/Ab 5. C#/Db Round 2 — Chromatic Scale - C,C#,D,D#,E,F,F#,G,G#,A,A#,B,C - A,A#,B,C,C#,D,D#,E,F,F#,G,G#,A Round 3 — Intervals 1. 2 2. 3 3. 4 4. 5 5. 6 6. 7 7. Eb 8. E 9. Gb 10. 5 11. Bb 12. B Round 4 — Major Scale Formula WS,WS,HS,WS,WS,WS,HS Round 5 — Build Scales 1. C,D,E,F,G,A,B,C 2. G,A,B,C,D,E,F#,G 3. D,E,F#,G,A,B,C#,D 4. F,G,A,A#,C,D,E,F 5. A,B,C#,D,E,F#,G#,A Round 6 — Fretboard Knowledge 1. 4th 2. 4th 3. 4th 4. 4th 5. 3rd 6. 4th Round 7 — Instant Recognition 1. major triad 2. minor triad 3. diminished triad 4. augmented triads Round 8 — Killer Test A,A#,B,C,C#,D,D#,E,F,F#,G,G# A,Bb,B,C,Db,D,Eb,E,F,Gb,G,Ab 1. Bb,C,D,Eb,F,G,A,Bb 2. Eb,F,G,Ab,Bb,C,D,Eb 3. E,Gb,G#,A,B,Db,D#,E 4. B,Db,Eb,E,Gb,Ab,A#,B 5. Ab,Bb,C,Db,Eb,F,G,Ab */ //+------------------------------------------------------------------+