StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //+------------------------------------------------------------------+
|
| | | //| Bars.mqh|
|
| | | //| Copyright 2026, bandersoft |
|
| | | //| https://www.mql5.com/en/users/fats |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //| New-bar detection, bar extremes, candle patterns. |
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | #ifndef STOPFIRST_BARS_MQH
|
| | | #define STOPFIRST_BARS_MQH
|
| | |
|
| | | #include "Defs.mqh"
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Get highest high of last N bars |
|
| | | //| |
|
| | | //| Returns: the highest high in the window. 0 if the history does |
|
| | | //| not cover the whole window - indistinguishable from a |
|
2026-10-16 21:48:01 +03:00 | | | //| real price by value alone. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //+------------------------------------------------------------------+
|
| | | double GetHighestHigh(int bars, int shift = 0, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT,
|
| | | string symbol = "")
|
| | | {
|
| | | if(symbol == "") symbol = _Symbol;
|
| | | if(bars <= 0) return 0;
|
| | |
|
| | | double high[];
|
| | | ArraySetAsSeries(high, true);
|
| | |
|
| | | //--- fewer bars than asked is not the window the caller named: a
|
| | | //--- partial copy returned the extreme of a shorter one (found in
|
| | | //--- review 2026-10-14)
|
| | | if(CopyHigh(symbol, timeframe, shift, bars, high) < bars) return 0;
|
| | |
|
| | | int maxIdx = ArrayMaximum(high);
|
| | | return high[maxIdx];
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Get lowest low of last N bars |
|
| | | //| |
|
| | | //| Returns: the lowest low in the window. 0 if the history does |
|
| | | //| not cover the whole window - indistinguishable from a |
|
2026-10-16 21:48:01 +03:00 | | | //| real price by value alone. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //+------------------------------------------------------------------+
|
| | | double GetLowestLow(int bars, int shift = 0, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT,
|
| | | string symbol = "")
|
| | | {
|
| | | if(symbol == "") symbol = _Symbol;
|
| | | if(bars <= 0) return 0;
|
| | |
|
| | | double low[];
|
| | | ArraySetAsSeries(low, true);
|
| | |
|
| | | if(CopyLow(symbol, timeframe, shift, bars, low) < bars) return 0; // partial = shorter window
|
| | |
|
| | | int minIdx = ArrayMinimum(low);
|
| | | return low[minIdx];
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Detect candle pattern |
|
| | | //| Parameters: |
|
| | | //| pattern - which shape to test for |
|
| | | //| shift - bar to examine, 1 being the last closed bar |
|
| | | //| timeframe - PERIOD_CURRENT for the chart timeframe |
|
| | | //| symbol - empty for the current chart symbol |
|
| | | //| |
|
| | | //| Returns: an ENUM_SF_SIGNAL. |
|
| | | //| SF_SIGNAL_BULLISH / SF_SIGNAL_BEARISH - found, with a |
|
| | | //| direction. SF_SIGNAL_NEUTRAL - found, no direction, ie |
|
| | | //| a doji. SF_SIGNAL_NONE - not found, or no history. |
|
| | | //| |
|
2026-10-16 21:48:01 +03:00 | | | //| A doji returns SF_SIGNAL_NEUTRAL, not 0, so "found a doji" and |
|
| | | //| "found nothing" differ. And the body has a floor: on a bodyless |
|
| | | //| candle every "wick > body * n" test is trivially true, and one |
|
| | | //| doji would report as hammer, shooting star and pin bar at once - |
|
| | | //| a body under STOPFIRST_DOJI_BODY_RATIO of the range matches no |
|
| | | //| directional pattern. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //+------------------------------------------------------------------+
|
| | | int DetectCandlePattern(ENUM_SF_PATTERN pattern, int shift = 1,
|
| | | ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT,
|
| | | string symbol = "")
|
| | | {
|
| | | if(symbol == "") symbol = _Symbol;
|
| | | if(shift < 0) return SF_SIGNAL_NONE;
|
| | |
|
| | | double open[], high[], low[], close[];
|
| | | ArraySetAsSeries(open, true);
|
| | | ArraySetAsSeries(high, true);
|
| | | ArraySetAsSeries(low, true);
|
| | | ArraySetAsSeries(close, true);
|
| | |
|
| | | //--- two bars: the one under test and its predecessor, which
|
2026-10-16 21:48:01 +03:00 | | | //--- engulfing compares against
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //--- both bars or nothing: with "<= 0" a copy of ONE bar - the oldest
|
2026-10-16 21:48:01 +03:00 | | | //--- bar, or history still loading - would pass, and engulfing would
|
| | | //--- read [1], a critical array-out-of-range that stops the program
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | if(CopyOpen(symbol, timeframe, shift, 2, open) < 2) return SF_SIGNAL_NONE;
|
| | | if(CopyHigh(symbol, timeframe, shift, 2, high) < 2) return SF_SIGNAL_NONE;
|
| | | if(CopyLow(symbol, timeframe, shift, 2, low) < 2) return SF_SIGNAL_NONE;
|
| | | if(CopyClose(symbol, timeframe, shift, 2, close) < 2) return SF_SIGNAL_NONE;
|
| | |
|
| | | double range = high[0] - low[0];
|
| | | if(range <= 0) return SF_SIGNAL_NONE; // a flat bar has no shape
|
| | |
|
| | | double body = MathAbs(close[0] - open[0]);
|
| | | double bodyRatio = body / range;
|
| | | double upperWick = high[0] - MathMax(open[0], close[0]);
|
| | | double lowerWick = MathMin(open[0], close[0]) - low[0];
|
| | | bool hasBody = (bodyRatio >= STOPFIRST_DOJI_BODY_RATIO);
|
| | |
|
| | | if(pattern == SF_PATTERN_DOJI)
|
| | | return hasBody ? SF_SIGNAL_NONE : SF_SIGNAL_NEUTRAL;
|
| | |
|
| | | //--- every remaining pattern is directional, and direction is
|
| | | //--- meaningless on a candle with no body
|
| | | if(!hasBody) return SF_SIGNAL_NONE;
|
| | |
|
| | | if(pattern == SF_PATTERN_HAMMER)
|
| | | {
|
| | | if(lowerWick > body * 2 && upperWick < body * 0.5)
|
| | | return SF_SIGNAL_BULLISH;
|
| | | }
|
| | | else if(pattern == SF_PATTERN_SHOOTING_STAR)
|
| | | {
|
| | | if(upperWick > body * 2 && lowerWick < body * 0.5)
|
| | | return SF_SIGNAL_BEARISH;
|
| | | }
|
| | | else if(pattern == SF_PATTERN_ENGULFING)
|
| | | {
|
| | | //--- the current body must cover the previous body outright.
|
2026-10-16 21:48:01 +03:00 | | | //--- comparing sizes alone would call any large candle after a
|
| | | //--- small opposite one an engulfing
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | if(close[0] > open[0] && close[1] < open[1] &&
|
| | | close[0] >= open[1] && open[0] <= close[1])
|
| | | return SF_SIGNAL_BULLISH;
|
| | |
|
| | | if(close[0] < open[0] && close[1] > open[1] &&
|
| | | close[0] <= open[1] && open[0] >= close[1])
|
| | | return SF_SIGNAL_BEARISH;
|
| | | }
|
| | | else if(pattern == SF_PATTERN_PIN_BAR)
|
| | | {
|
| | | if(lowerWick > body * 2.5 && lowerWick > upperWick * 2)
|
| | | return SF_SIGNAL_BULLISH;
|
| | | if(upperWick > body * 2.5 && upperWick > lowerWick * 2)
|
| | | return SF_SIGNAL_BEARISH;
|
| | | }
|
| | |
|
| | | return SF_SIGNAL_NONE;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Detects a new bar on one symbol and timeframe |
|
| | | //| |
|
2026-10-16 21:48:01 +03:00 | | | //| A free function IsNewBar() with a function-level static would be |
|
| | | //| shared by every caller: ask it about EURUSD and GBPUSD in the |
|
| | | //| same loop and each call overwrites the other's bar time, so both |
|
| | | //| report a new bar on every tick. |
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | //| The state belongs to an object, one per series being watched. |
|
| | | //| |
|
| | | //| Usage - one member per series, initialised once: |
|
| | | //| |
|
| | | //| CNewBarDetector m1Bars; |
|
| | | //| int OnInit() { m1Bars.Init(_Symbol, PERIOD_M1); return 0; } |
|
| | | //| void OnTick() { if(m1Bars.IsNewBar()) ... } |
|
| | | //+------------------------------------------------------------------+
|
| | | class CNewBarDetector
|
| | | {
|
| | | private:
|
| | | string m_symbol;
|
| | | ENUM_TIMEFRAMES m_timeframe;
|
| | | datetime m_lastBarTime;
|
| | |
|
| | | public:
|
| | | CNewBarDetector(void);
|
| | | void Init(const string symbol = "",
|
| | | const ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT);
|
| | | bool IsNewBar(void);
|
| | | datetime LastBarTime(void) const { return m_lastBarTime; }
|
| | | string Symbol(void) const { return m_symbol; }
|
| | | ENUM_TIMEFRAMES Timeframe(void) const { return m_timeframe; }
|
| | | void Reset(void) { m_lastBarTime = 0; }
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Defaults to the current chart symbol and timeframe |
|
| | | //+------------------------------------------------------------------+
|
| | | CNewBarDetector::CNewBarDetector(void) : m_symbol(_Symbol),
|
| | | m_timeframe(PERIOD_CURRENT),
|
| | | m_lastBarTime(0)
|
| | | {
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Point the detector at a series |
|
| | | //| Parameters: |
|
| | | //| symbol - empty for the current chart symbol |
|
| | | //| timeframe - PERIOD_CURRENT for the chart timeframe |
|
| | | //| Returns: nothing. Forgets any bar seen before. |
|
| | | //+------------------------------------------------------------------+
|
| | | void CNewBarDetector::Init(const string symbol, const ENUM_TIMEFRAMES timeframe)
|
| | | {
|
| | | m_symbol = (symbol == "") ? _Symbol : symbol;
|
| | | m_timeframe = timeframe;
|
| | | m_lastBarTime = 0;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Has a new bar opened since the last call? |
|
| | | //| |
|
| | | //| Returns: true exactly once per bar. false on the first call for |
|
| | | //| a series - that call only records where we are, and |
|
| | | //| treating startup as a bar change would fire every |
|
| | | //| strategy once for no reason. false if the series has no |
|
| | | //| data. |
|
| | | //+------------------------------------------------------------------+
|
| | | bool CNewBarDetector::IsNewBar(void)
|
| | | {
|
| | | datetime currentBarTime = iTime(m_symbol, m_timeframe, 0);
|
| | | if(currentBarTime == 0) return false;
|
| | |
|
| | | if(m_lastBarTime == currentBarTime) return false;
|
| | |
|
| | | bool firstObservation = (m_lastBarTime == 0);
|
| | | m_lastBarTime = currentBarTime;
|
| | |
|
| | | return !firstObservation;
|
| | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Get time until next bar |
|
| | | //| |
|
| | | //| Returns: seconds until the current bar closes, never negative. |
|
| | | //| 0 if the series has no history, if the timeframe is |
|
| | | //| unknown, or if the bar is already overdue. |
|
| | | //+------------------------------------------------------------------+
|
| | | int SecondsToNextBar(ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT, string symbol = "")
|
| | | {
|
| | | if(symbol == "") symbol = _Symbol;
|
| | |
|
2026-10-16 21:48:01 +03:00 | | | //--- a series with no history gives a bar time of 0, and the
|
| | | //--- result would be a large negative number
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | datetime currentBarTime = iTime(symbol, timeframe, 0);
|
| | | if(currentBarTime == 0) return 0;
|
| | |
|
| | | int periodSeconds = PeriodSeconds(timeframe);
|
| | | if(periodSeconds <= 0) return 0;
|
| | |
|
2026-10-16 21:48:01 +03:00 | | | //--- PeriodSeconds(PERIOD_MN1) is a flat 30 days, so the 31st would
|
| | | //--- read 0 all day and February over-report by two. A monthly bar
|
| | | //--- ends at the first of the next month.
|
StopFirst 1.00, the read-only core
Fourteen headers, a header-only MQL5 library: lot size from risk, pips on
forex, gold and indices, broker checks, bars and candle patterns, cached ATR,
sessions in GMT, account risk, symbol suffixes, a calendar filter. Sends no
trade requests. Plus the LotByRisk example script.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 21:40:34 +03:00 | | | datetime barEnd = currentBarTime + periodSeconds;
|
| | | ENUM_TIMEFRAMES resolved = (timeframe == PERIOD_CURRENT) ? (ENUM_TIMEFRAMES)Period()
|
| | | : timeframe;
|
| | | if(resolved == PERIOD_MN1)
|
| | | {
|
| | | MqlDateTime next;
|
| | | TimeToStruct(currentBarTime, next);
|
| | | next.mon++;
|
| | | if(next.mon > 12) { next.mon = 1; next.year++; }
|
| | | next.day = 1; next.hour = 0; next.min = 0; next.sec = 0;
|
| | | barEnd = StructToTime(next);
|
| | | }
|
| | |
|
| | | int remaining = (int)(barEnd - TimeCurrent());
|
| | |
|
| | | //--- a bar can be overdue when ticks stop arriving; negative
|
| | | //--- seconds-until is not a meaningful answer
|
| | | return (remaining > 0) ? remaining : 0;
|
| | | }
|
| | |
|
| | | #endif // STOPFIRST_BARS_MQH
|
| | | //+------------------------------------------------------------------+
|