Skip to content
All strategies

Strategies

DHLAOS by geraked (faithful port): Prior-Day High/Low Sweep + Andean Oscillator (50/9) Bull/Bear-over-Signal Cross Reversal, 7-Bar Swing Distance, 50% Break-Even Trail, 1.1x Grid (50 levels) with Basket TP and 20% Basket Trail, No Hard Stop, Long-Short (ETHUSDT.OKX 5m)

Outcome: Abandoned

DhlaosEthOkxGridLS

Outcome Summary

DhlaosEthOkxGridLS tried to port the DHLAOS EA, an Andean Oscillator plus prior-day high/low sweep reversal system with a 1.1x, 50-level grid and no hard stop, from AUDUSD 5m to ETHUSDT.OKX 5m perpetuals at 10x. Over 3 iterations the code implemented only the entry signal: should_exit() always returned False, no grid legs were ever submitted, and position size was inflated by the leverage multiplier. The sandbox showed one trade liquidated after 66 days for a -100% return. QA Layer 4 rejected it as a hypothesis mismatch, also warning that even a faithful 1.1x/50-level no-stop grid on a 10x ETH perp is a textbook blow-up profile, and the run was abandoned before backtest, optimization or analyst review.

Hypothesis

Faithful statement of the open-source MQL5 expert advisor 'DHLAOS' v1.5 by geraked (MIT licence, https://github.com/geraked/metatrader5/blob/d3eb29c382acf715727d5cd6a0414151e821fc2d/Experts/DHLAOS.mq5). All credit goes to the author. The rules come from four files at the same commit: Experts/DHLAOS.mq5, Include/EAUtils.mqh (BuyOpen/SellOpen, calcVolume, BuySL/SellSL, checkForTrail, checkForGrid), Indicators/AndeanOscillator.mq5 and Indicators/DailyHighLow.mq5. The source's own performance claim (AUDUSD 5M,……Show moreShow less

Faithful statement of the open-source MQL5 expert advisor 'DHLAOS' v1.5 by geraked (MIT licence, https://github.com/geraked/metatrader5/blob/d3eb29c382acf715727d5cd6a0414151e821fc2d/Experts/DHLAOS.mq5). All credit goes to the author. The rules come from four files at the same commit: Experts/DHLAOS.mq5, Include/EAUtils.mqh (BuyOpen/SellOpen, calcVolume, BuySL/SellSL, checkForTrail, checkForGrid), Indicators/AndeanOscillator.mq5 and Indicators/DailyHighLow.mq5. The source's own performance claim (AUDUSD 5M, 2021-02 to 2023-09) is unverified; our backtest with real fees decides. Full rule-by-rule translation, including the indicator formulas, the index convention, and the trail and grid arithmetic, is in the outbox file workspace/outbox/research_lead/hypothesis_92699ca40a.json. VENUE MAPPING: the source trades AUDUSD, a low-volatility FX major, on 5-minute bars. The crypto analogue is ETHUSDT.OKX 5-MINUTE bars (OKX USDT perpetual swap). 5m history runs continuously from 2019-12. OKX is 0.8% of the corpus against a target of at least 5%. OKX taker fee is 0.05%, about 0.10% round trip; leverage is 10x; only OHLCV data is used. FX points are converted to fractions of price: SLDev 60 points becomes 0.09%, and the library's minPoints 30 becomes 0.045%. INDICATORS: (1) Andean Oscillator with Length 50 and Signal EMA 9. Bull = sqrt(dn2 - dn1^2) and Bear = sqrt(up2 - up1^2), from recursive exponential envelopes of close/open and of their squares. Signal = EMA9 of max(Bull, Bear). (2) DailyHighLow with Previous=true: the previous UTC day's high and low. STOPS: IgnoreSL=IgnoreTP=true, and Grid also forces the stop off, so the first leg has no hard stop and no take-profit. d = |entry - swing stop| drives both the trail and the grid. SIZING: first leg = 0.5% of min(equity, free margin) / d. Each grid leg = 1.1x the largest existing leg, up to 50 legs; a leg is skipped when margin at 10x is insufficient. That is the source's 'no money' return-false behaviour. Only one basket is open at a time. Trail and grid are evaluated on each 5m bar close; the source uses a 30s timer. There is no time stop and no max-loss stop. The strategy is flat roughly 40-60% of the time.

Iteration 3 supplies the complete required FactoryStrategyConfig, including the ETHUSDT.OKX instrument, 5-minute bar type, and 10x leverage. Position sizing consumes the configured leverage.

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown……Show moreShow less

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown and the margin-skip count against the hypothesis's pre-registered drawdown. - [critical] The exit side of the hypothesis is not implemented at all. should_exit() always returns False, and nothing else in the code closes a position. The hypothesis names three exit mechanisms: (1) a 50% break-even trail, which moves the stop to break-even after price travels 50% of d; (2) a basket take-profit; (3) a 20% basket trail. None exists. The params trailing_stop_level_pct and grid_trailing_stop_level_pct are declared in config but never read. As coded, the only way a position can end is liquidation. The sandbox shows exactly that: 1 trade held 66 days, liquidated on 2026-06-26, total_return -100%. (line 56) — fix: Port EAUtils.mqh checkForTrail. After a move of trailing_stop_level_pct% of d in favour, move the stop to break-even (or to the source's trail level), and exit on a close through it. For an open grid basket, port the basket TP and the 20% basket trail on the volume-weighted basket entry price. Evaluate both on every 5m bar close from should_exit(). Read the configured pct params. - [critical] The grid (checkForGrid) is not implemented. The code never submits a grid leg. on_order_filled records _grid_qty and multiplies it by grid_mult, but no code path uses _grid_qty to place an order. grid_max_lvl, grid_enabled and margin_use are unused. The spacing rule (the next leg every d against the position), the 1.1x of the largest leg, the 50-level cap and the skip when margin is insufficient are all absent. The core mechanism of the hypothesis (the grid plus the basket exit) is therefore missing. (line 58) — fix: On each bar with an open basket, when price has moved k*d against the basket's last leg and level < grid_max_lvl, submit a same-side market order of grid_mult x the largest existing leg. First check free margin at the configured leverage (skip if insufficient) and the $5 min notional. Track legs, basket volume-weighted average price and level count explicitly. - [critical] The sizing formula multiplies by leverage, but the hypothesis does not. The hypothesis says first leg = 0.5% of min(equity, free margin) / d. The code computes equity * 0.005 * leverage(10) / d, which is 10x the specified risk (5% of equity per d). d is often at the ~0.045-0.2% floor. At d = 0.1% of price, notional = 0.05/0.001 = 50x equity, which is far beyond the 10x cap. The sandbox confirms this: avg_position_pct is 1207%. Nothing caps notional at equity x leverage, and no free-margin term is used. Combined with the missing exits, the first trade is a guaranteed liquidation. (line 57) — fix: Use qty = min(equity, free_margin) * risk / d, with no leverage multiplier. Then clamp notional to margin_use * equity * leverage and skip the entry if the clamp binds below min notional. - [warning] The on_order_filled bookkeeping is fragile. Any fill on the opposite side resets _side to 0 even on a partial reduction, and entry detection depends on _qty > 0 having been set by position_size. Once exits and grid are added, basket state (legs, VWAP, level) should be derived from the cache position or an explicit leg list rather than from fill-side heuristics. (line 60) — fix: Keep an explicit basket structure and reset it in on_position_closed only. - [warning] Compute budget (L176). There is 1 instrument, on ETH 5m from 2019-12, which is about 700k bars. No model fit is involved, but _signal() copies three deques of about 358 elements to lists on every bar, and calculate_signal copies _low/_high in full. That is roughly 1-2k element copies plus a loop of up to 300 steps per bar, per side. This is O(bars x ncheck), bounded by fixed-size windows so it does not grow with the span, but at ~700k bars it risks the full-backtest wall-clock limit. (line 31) — fix: Index the deques directly (deque supports negative indexing) or keep fixed-size lists; slice only the last sl_lookback elements for the swing stop. Skip the _signal scan unless the cheap bar-1 cross condition holds (it already returns early, but only after the list copies). - [warning] min_bars_required is about 754 bars. The Andean envelopes and the prior-day high/low only begin updating once the base template starts calling calculate_signal (after min_bars). Warmup is therefore about 2.6 days plus the indicator settling time, and the first prior-day high/low is available only after the first UTC day rollover inside calculate_signal. This is acceptable, but note that the indicators are not seeded from the buffered bars. (line 17)

Implementation

DHLAOS prior-day high/low sweep and Andean Oscillator reversal strategy on ETHUSDT.OKX 5-minute bars.

Verification Results

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown and the margin-skip……Show moreShow less

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown and the margin-skip count against the hypothesis's pre-registered drawdown. - [critical] The exit side of the hypothesis is not implemented at all. should_exit() always returns False, and nothing else in the code closes a position. The hypothesis names three exit mechanisms: (1) a 50% break-even trail, which moves the stop to break-even after price travels 50% of d; (2) a basket take-profit; (3) a 20% basket trail. None exists. The params trailing_stop_level_pct and grid_trailing_stop_level_pct are declared in config but never read. As coded, the only way a position can end is liquidation. The sandbox shows exactly that: 1 trade held 66 days, liquidated on 2026-06-26, total_return -100%. (line 56) — fix: Port EAUtils.mqh checkForTrail. After a move of trailing_stop_level_pct% of d in favour, move the stop to break-even (or to the source's trail level), and exit on a close through it. For an open grid basket, port the basket TP and the 20% basket trail on the volume-weighted basket entry price. Evaluate both on every 5m bar close from should_exit(). Read the configured pct params. - [critical] The grid (checkForGrid) is not implemented. The code never submits a grid leg. on_order_filled records _grid_qty and multiplies it by grid_mult, but no code path uses _grid_qty to place an order. grid_max_lvl, grid_enabled and margin_use are unused. The spacing rule (the next leg every d against the position), the 1.1x of the largest leg, the 50-level cap and the skip when margin is insufficient are all absent. The core mechanism of the hypothesis (the grid plus the basket exit) is therefore missing. (line 58) — fix: On each bar with an open basket, when price has moved k*d against the basket's last leg and level < grid_max_lvl, submit a same-side market order of grid_mult x the largest existing leg. First check free margin at the configured leverage (skip if insufficient) and the $5 min notional. Track legs, basket volume-weighted average price and level count explicitly. - [critical] The sizing formula multiplies by leverage, but the hypothesis does not. The hypothesis says first leg = 0.5% of min(equity, free margin) / d. The code computes equity * 0.005 * leverage(10) / d, which is 10x the specified risk (5% of equity per d). d is often at the ~0.045-0.2% floor. At d = 0.1% of price, notional = 0.05/0.001 = 50x equity, which is far beyond the 10x cap. The sandbox confirms this: avg_position_pct is 1207%. Nothing caps notional at equity x leverage, and no free-margin term is used. Combined with the missing exits, the first trade is a guaranteed liquidation. (line 57) — fix: Use qty = min(equity, free_margin) * risk / d, with no leverage multiplier. Then clamp notional to margin_use * equity * leverage and skip the entry if the clamp binds below min notional. - [warning] The on_order_filled bookkeeping is fragile. Any fill on the opposite side resets _side to 0 even on a partial reduction, and entry detection depends on _qty > 0 having been set by position_size. Once exits and grid are added, basket state (legs, VWAP, level) should be derived from the cache position or an explicit leg list rather than from fill-side heuristics. (line 60) — fix: Keep an explicit basket structure and reset it in on_position_closed only. - [warning] Compute budget (L176). There is 1 instrument, on ETH 5m from 2019-12, which is about 700k bars. No model fit is involved, but _signal() copies three deques of about 358 elements to lists on every bar, and calculate_signal copies _low/_high in full. That is roughly 1-2k element copies plus a loop of up to 300 steps per bar, per side. This is O(bars x ncheck), bounded by fixed-size windows so it does not grow with the span, but at ~700k bars it risks the full-backtest wall-clock limit. (line 31) — fix: Index the deques directly (deque supports negative indexing) or keep fixed-size lists; slice only the last sl_lookback elements for the swing stop. Skip the _signal scan unless the cheap bar-1 cross condition holds (it already returns early, but only after the list copies). - [warning] min_bars_required is about 754 bars. The Andean envelopes and the prior-day high/low only begin updating once the base template starts calling calculate_signal (after min_bars). Warmup is therefore about 2.6 days plus the indicator settling time, and the first prior-day high/low is available only after the first UTC day rollover inside calculate_signal. This is acceptable, but note that the indicators are not seeded from the buffered bars. (line 17)

Iteration History

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] In the sandbox all 85 baskets lost (win rate 0, PF 0) and the account fell 35%, yet the average per-basket price move was +0.144%. The ledger reconciles: commissions of $15.4k plus impact of $26.2k come to $41.7k, more than the $35.4k loss, so gross PnL was about +$6k before costs. It is not an accounting defect. The damage comes from the design: the break-even trail and the basket TP cover only the ENTRY fees (the source's cost……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] In the sandbox all 85 baskets lost (win rate 0, PF 0) and the account fell 35%, yet the average per-basket price move was +0.144%. The ledger reconciles: commissions of $15.4k plus impact of $26.2k come to $41.7k, more than the $35.4k loss, so gross PnL was about +$6k before costs. It is not an accounting defect. The damage comes from the design: the break-even trail and the basket TP cover only the ENTRY fees (the source's cost term), and at 0.5%-risk-per-d sizing the first leg alone is about 1.7-5x equity (d is about 0.1-0.3% of price on 5m ETH). One taker exit plus impact then costs a large share of the 0.5%-of-equity basket target. The 0.144% gross is also below the 0.15% floor. The analyst should check first whether any basket clears its exit costs once the fee/impact drag at this notional is included. Tail risk: this is a 50-level 1.1x martingale with no max-loss stop on a trending crypto asset, as the hypothesis states. - [critical] The margin check is the only risk control on a martingale with no stop, and it uses cash balance instead of mark-to-market equity or free margin. _max_notional() = get_account_equity() * 0.95 * leverage. get_account_equity() returns balance.total (resolve_account_equity), and on a NautilusTrader margin account that EXCLUDES the open basket's unrealized PnL. The hypothesis and source size from min(equity, free margin), and skip a leg on MT5 'no money' (floating-inclusive free margin). Grid legs are added only while the basket is losing, which is exactly when balance overstates equity. Once the floating loss passes about 5% of balance, a basket capped at 9.5x cash balance is already more than 10x true equity, and each further 1.1x leg pushes it higher. Worst-case gross / MTM equity therefore has no bound up to liquidation, where the source would have refused the leg. The first-leg risk budget (eq*risk/d) has the same issue if equity is ever read while positions are open. (line 131) — fix: Compute equity_mtm = balance + self.portfolio.unrealized_pnl(self.instrument_id), and free_margin = equity_mtm - basket_notional/leverage. Place a grid leg only if (sum_vol+vol)*price <= equity_mtm*margin_use*leverage AND the leg's initial margin <= free_margin. Size the first leg from min(equity_mtm, free_margin), as in the hypothesis. In the review response, state the worst-case gross/MTM-equity. - [warning] The source's 30s management timer runs once per 5m close, a 10x decision-interval ratio (the L169 limit). The code mitigates this by resting the stop, basket-TP and grid levels as stop-market or MIT orders. However, the trail is computed from the bar's HIGH and then compared with the CLOSE. If the close is already through the new trail, the basket exits at market at the close, below the level the source would have filled at. This is conservative (no look-ahead) but biases exits worse than the source. (line 298) — fix: This is acceptable as documented. Optionally, on a bar where the trail crosses, exit at the stop price via a resting stop instead of the close. - [warning] Reducing fills return early without touching _sum_vol or _sum_vol_op. A partial fill of the reduce-only stop or TP would leave the basket average and size stale until the position closes. Market-style triggers fill fully in the backtest, so this is low risk there, but live partials are possible. (line 466) — fix: On a reducing fill, subtract qty from _sum_vol and qty*avg from _sum_vol_op. - [warning] The first partial fill of a grid lot clears _grid_oid. A later _sync can then submit a second grid order while the first is still partially working. (line 482) — fix: Clear _grid_oid only when the order is closed (event.leaves_qty == 0, or order.is_closed).

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Smoke test shows profit factor 0.0 and 85 losing trades; this is advisory evidence only, not the verdict basis. - [critical] The minimum distance is 0.045%, while the single-lot trail can exit after only 50% of d (0.0225% gross), below OKX's approximately 0.10% taker round trip and the 0.15% viability floor. (line 26) — fix: Require a fee-aware minimum expected move or increase trail/target geometry so exits cover fees and impact. -……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Smoke test shows profit factor 0.0 and 85 losing trades; this is advisory evidence only, not the verdict basis. - [critical] The minimum distance is 0.045%, while the single-lot trail can exit after only 50% of d (0.0225% gross), below OKX's approximately 0.10% taker round trip and the 0.15% viability floor. (line 26) — fix: Require a fee-aware minimum expected move or increase trail/target geometry so exits cover fees and impact. - [critical] Reduction fills subtract quantity multiplied by basket average entry price, not the actual fill price. Partial stop/TP fills therefore corrupt remaining average cost, basket TP, break-even trail, margin, and equity calculations. (line 489) — fix: Track per-lot fills or recompute remaining position cost using actual reduction fill prices, commissions, and quantities. - [critical] Grid minimum-notional validation uses `vol * lvl`, but crossed orders fill at `ref`. Gap-through-level fills can therefore be admitted or rejected using the wrong notional and margin price. (line 380) — fix: Validate notional and margin at the modeled actual fill price when a grid level is crossed, and revalidate on fill.

Outcome Summary

In a grid/martingale port, the exit, grid-leg and margin-capped sizing logic are the strategy. Implement and sandbox-check them before anything else, and never scale risk-based sizing by leverage.

It was abandoned after 3 iterations in a verification loop. The Layer 4 QA review failed it as a hypothesis mismatch: none of the exits (break-even trail, basket TP, basket trail) or the grid were implemented, and the first-leg size was multiplied by leverage, giving 10x the specified risk.

A faithful port of geraked's MQL5 DHLAOS v1.5 EA to ETHUSDT.OKX 5m bars. It traded long and short reversals when a prior-UTC-day high/low sweep lined up with an Andean Oscillator (50/9) bull/bear-over-signal cross, and managed each trade with a 1.1x martingale grid of up to 50 legs, a 50% break-even trail and a basket take-profit/trail, with no hard stop.

The strategy never reached a full backtest or optimization, so there are no pipeline performance metrics. The only evidence came from the QA review of the sandbox run: 1 trade held for 66 days, liquidated on 2026-06-26, for a total return of -100% and an average position of 1207% of equity.

Abandon Reason

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown……Show moreShow less

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Even when implemented faithfully, a 1.1x martingale grid with up to 50 levels and no hard stop on a 10x ETH perp is a textbook blow-up profile. On a trending ETH move, the geometric leg growth (1.1^49 ~ 107x the first leg) will exhaust margin long before level 50. Expect a long run of small basket wins broken by occasional near-total drawdowns. The first thing to check in the full backtest is max drawdown and the margin-skip count against the hypothesis's pre-registered drawdown. - [critical] The exit side of the hypothesis is not implemented at all. should_exit() always returns False, and nothing else in the code closes a position. The hypothesis names three exit mechanisms: (1) a 50% break-even trail, which moves the stop to break-even after price travels 50% of d; (2) a basket take-profit; (3) a 20% basket trail. None exists. The params trailing_stop_level_pct and grid_trailing_stop_level_pct are declared in config but never read. As coded, the only way a position can end is liquidation. The sandbox shows exactly that: 1 trade held 66 days, liquidated on 2026-06-26, total_return -100%. (line 56) — fix: Port EAUtils.mqh checkForTrail. After a move of trailing_stop_level_pct% of d in favour, move the stop to break-even (or to the source's trail level), and exit on a close through it. For an open grid basket, port the basket TP and the 20% basket trail on the volume-weighted basket entry price. Evaluate both on every 5m bar close from should_exit(). Read the configured pct params. - [critical] The grid (checkForGrid) is not implemented. The code never submits a grid leg. on_order_filled records _grid_qty and multiplies it by grid_mult, but no code path uses _grid_qty to place an order. grid_max_lvl, grid_enabled and margin_use are unused. The spacing rule (the next leg every d against the position), the 1.1x of the largest leg, the 50-level cap and the skip when margin is insufficient are all absent. The core mechanism of the hypothesis (the grid plus the basket exit) is therefore missing. (line 58) — fix: On each bar with an open basket, when price has moved k*d against the basket's last leg and level < grid_max_lvl, submit a same-side market order of grid_mult x the largest existing leg. First check free margin at the configured leverage (skip if insufficient) and the $5 min notional. Track legs, basket volume-weighted average price and level count explicitly. - [critical] The sizing formula multiplies by leverage, but the hypothesis does not. The hypothesis says first leg = 0.5% of min(equity, free margin) / d. The code computes equity * 0.005 * leverage(10) / d, which is 10x the specified risk (5% of equity per d). d is often at the ~0.045-0.2% floor. At d = 0.1% of price, notional = 0.05/0.001 = 50x equity, which is far beyond the 10x cap. The sandbox confirms this: avg_position_pct is 1207%. Nothing caps notional at equity x leverage, and no free-margin term is used. Combined with the missing exits, the first trade is a guaranteed liquidation. (line 57) — fix: Use qty = min(equity, free_margin) * risk / d, with no leverage multiplier. Then clamp notional to margin_use * equity * leverage and skip the entry if the clamp binds below min notional. - [warning] The on_order_filled bookkeeping is fragile. Any fill on the opposite side resets _side to 0 even on a partial reduction, and entry detection depends on _qty > 0 having been set by position_size. Once exits and grid are added, basket state (legs, VWAP, level) should be derived from the cache position or an explicit leg list rather than from fill-side heuristics. (line 60) — fix: Keep an explicit basket structure and reset it in on_position_closed only. - [warning] Compute budget (L176). There is 1 instrument, on ETH 5m from 2019-12, which is about 700k bars. No model fit is involved, but _signal() copies three deques of about 358 elements to lists on every bar, and calculate_signal copies _low/_high in full. That is roughly 1-2k element copies plus a loop of up to 300 steps per bar, per side. This is O(bars x ncheck), bounded by fixed-size windows so it does not grow with the span, but at ~700k bars it risks the full-backtest wall-clock limit. (line 31) — fix: Index the deques directly (deque supports negative indexing) or keep fixed-size lists; slice only the last sl_lookback elements for the swing stop. Skip the _signal scan unless the cheap bar-1 cross condition holds (it already returns early, but only after the list copies). - [warning] min_bars_required is about 754 bars. The Andean envelopes and the prior-day high/low only begin updating once the base template starts calling calculate_signal (after min_bars). Warmup is therefore about 2.6 days plus the indicator settling time, and the first prior-day high/low is available only after the first UTC day rollover inside calculate_signal. This is acceptable, but note that the indicators are not seeded from the buffered bars. (line 17)

Analysis

DHLAOS

Backtest and paper results are hypothetical. Trading involves risk of loss.