Strategies
2MACDSTO by geraked (faithful port): MACD(34,144) Trend + MACD(13,21) Counter-Move + Stochastic(7,3,3) Cross out of the 20/80 Zone, Swing-Distance Risk Sizing, No Hard SL/TP, 50%-of-Risk Trailing Stop, Equal-Size 50-Level Averaging Grid with Basket TP, Long-Short (ETHUSDT.OKX 4H)
Outcome: Abandoned
TwoMacdStoGridEthOkx
Outcome Summary
TwoMacdStoGridEthOkx ported geraked's 2MACDSTO expert advisor to OKX ETH perpetuals on 4H bars, pairing a two-MACD, stochastic pullback entry with no-stop trailing exits and a 50-level equal-size averaging grid. Across 3 iterations it never passed verification. The sandbox backtest was liquidated on 2020-06-02 at -100%, with one trade losing about 29x starting capital. QA traced this to grid sizing that ignored unrealized losses and to the lack of any basket stop, and the strategy was abandoned before the backtest-review, optimization, analyst and risk stages.
Hypothesis
Hypothesis
Faithful statement of the open-source MQL5 expert advisor '2MACDSTO' v1.4 by geraked (Copyright 2023, MIT licence, https://github.com/geraked/metatrader5/blob/d3eb29c382acf715727d5cd6a0414151e821fc2d/Experts/2MACDSTO.mq5), with its trade-management helpers from Include/EAUtils.mqh at the same commit. Credit: the strategy is geraked's. The author tested it on NZDUSD 3H (2020-01-01 to 2023-10-08). Treat any claimed performance as unverified; our fee-inclusive backtest decides. It is a tradeable strategy, not a……Show moreShow less
Faithful statement of the open-source MQL5 expert advisor '2MACDSTO' v1.4 by geraked (Copyright 2023, MIT licence, https://github.com/geraked/metatrader5/blob/d3eb29c382acf715727d5cd6a0414151e821fc2d/Experts/2MACDSTO.mq5), with its trade-management helpers from Include/EAUtils.mqh at the same commit. Credit: the strategy is geraked's. The author tested it on NZDUSD 3H (2020-01-01 to 2023-10-08). Treat any claimed performance as unverified; our fee-inclusive backtest decides. It is a tradeable strategy, not a utility. MECHANISM: a pullback entry inside a slow trend. The slow MACD (34/144 EMA difference) sets the trend. The fast MACD (13/21) must point against it, which marks the pullback. A stochastic %K cross of %D out of the oversold or overbought zone times the re-entry. The EA places NO stop-loss and NO take-profit on the order (IgnoreSL=true, IgnoreTP=true). A single position exits only through a trailing stop. The stop engages once profit reaches half the swing distance. If price goes against the position instead, the EA averages down with an EQUAL-SIZE grid (GridVolMult=1.0), adding one position every swing distance, up to 50 levels. The whole basket then closes at a common take-profit that earns back the original risk amount plus costs. PLATFORM MAPPING: The venue is the OKX USDT perpetual swap ETHUSDT.OKX, on a MARGIN account. Fees are 0.02% maker and 0.05% taker, about 0.10% for a taker round trip. It must be a perp because the source needs shorts. OKX is 0.8% of the corpus against a target of at least 5%, while BINANCE USD-M is 65.9% against a cap of 40%. ETH is the most liquid OKX perp other than BTC. The mechanism uses price only, with no supplementary feed, so the L160 venue restriction does not apply. Timeframe: our catalog has no 3H bars, so the port uses the nearest native interval, 4-HOUR bars (ETHUSDT.OKX-4-HOUR-LAST-EXTERNAL). The EA code works on any timeframe because all its indicators use the chart timeframe. This is the only deviation from the source. Constants given in points are converted to fractions of price. On NZDUSD (point 0.00001, price about 0.60), SLDev=60 points is about 0.10% of price, and the helper's minPoints=30 is about 0.005% of price. IMPLEMENTATION CAUTION: an earlier faithful port from the same author (3MAF) died at sandbox_timeout. Every indicator must update INCREMENTALLY, in O(1) per bar: - running EMAs for the four MACD legs; - fixed-length deques for the 7-bar high and low and for the 3-bar slowing sums; - a 3-value buffer for %D; - basket state kept in scalars: first entry price, pd, first volume, level count, total quantity, quantity-weighted entry sum, accumulated commissions. Never rescan bar history, the positions list or the fills history inside on_bar.
Iteration 3 fixes all QA items: (1) removed the 30% drawdown and 50% margin-level hard stops; EquityDrawdownLimit is ported as the source input with default 0 (disabled). (2) get_account_equity() is treated as mark-to-market; balance = equity - unrealized, so unrealized PnL is counted once in the drawdown check and grid gross cap. (3) min_dist_frac raised to 0.4% so the 50% trailing move is >= 0.2%, above the ~0.10% OKX taker round trip plus slippage (deviation from the ~0.005% helper minimum, needed for crypto……Show moreShow less
Iteration 3 fixes all QA items: (1) removed the 30% drawdown and 50% margin-level hard stops; EquityDrawdownLimit is ported as the source input with default 0 (disabled). (2) get_account_equity() is treated as mark-to-market; balance = equity - unrealized, so unrealized PnL is counted once in the drawdown check and grid gross cap. (3) min_dist_frac raised to 0.4% so the 50% trailing move is >= 0.2%, above the ~0.10% OKX taker round trip plus slippage (deviation from the ~0.005% helper minimum, needed for crypto fees). (4) Stochastic now averages raw %K over the slowing window and %D averages slowed %K (note: MT5's built-in iStochastic uses a ratio of sums; switched per QA). (5) Grid adds exactly ONE level per check, anchored on the last level's price as the EA does, and only when the full equal-size level fits under the equity x leverage gross cap, so level count always equals filled levels. Leverage 3 with per-level notional capped at 0.25 x leverage x equity leaves margin room for grid levels; sizing reads self.config.leverage. Unavoidable platform deviation: 4H bars instead of 3H, with checks at bar close instead of a 30-second timer.
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit……Show moreShow less
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit input) is set to a non-zero value. Check the worst-basket drawdown before spending on optimization. - [critical] The grid gross cap and the drawdown check depend on a false premise. The comment says 'get_account_equity() already includes unrealized PnL (mark-to-market)'. It does not. resolve_account_equity() returns account.balances()[USDT].total, and on an NT MarginAccount that figure is realized cash only; unrealized PnL is not in balances. Three consequences follow. (a) `balance = equity - unreal` overstates the balance by the full unrealized loss. (b) `room = max(equity,0)*lev - qty*c` caps gross at 3x REALIZED cash, not at 3x true equity. As the basket goes underwater, true leverage on real equity grows without limit, and grid levels keep being added against equity that no longer exists. (c) If equity_drawdown_limit_pct is ever set above 0, the check compares realized cash against realized cash minus unrealized, so its sign and magnitude are wrong. The sandbox shows the result: liquidated=true on 2020-06-02, total_return -100%, max_drawdown 100%, and one trade lost $2,949,268, roughly 29x the starting capital. The developer's rationale point (2) claims this was fixed, but the fix rests on the wrong premise. (line 270) — fix: Compute true equity in the strategy as mtm_equity = get_account_equity() + d*(c - avg)*qty (realized cash plus this position's unrealized PnL). Use mtm_equity in the grid room check (room = mtm_equity*lev - qty*c, and refuse adds when mtm_equity <= 0) and in _level_qty for sizing. Use balance = get_account_equity() directly (it already is the balance) in the EquityDrawdownLimit check. - [critical] The basket has unbounded risk: no stop, 3x leverage, and a loss that can exceed equity (L148/L156). Once the gross cap blocks further adds, nothing bounds a basket running against a trend. A short basket in a rally frees no room, because qty*c rises, so it simply holds until the account is liquidated. The largest sandbox loss of about 29x initial capital proves this path is reachable. Neither 'stop distance x size <= 1-2% of equity' nor a worst-case gross relative to TRUE equity can be stated for this configuration. The source provides EquityDrawdownLimit as an input for exactly this purpose. Leaving it at 0 on a 3x-levered crypto port is a sizing and risk defect, not faithfulness. (line 60) — fix: Keep the mechanism as it is, but set equity_drawdown_limit_pct to a non-zero value (for example 20-30%) evaluated on MTM equity per the fix above. Alternatively, lower leverage and max_level_notional_mult so that worst-case gross = grid_max_lvl_reachable x level_notional <= mtm_equity x venue max leverage, and state that bound in the rationale. Both are source inputs, so the port stays faithful. - [warning] The base template does not call calculate_signal() while len(self._bars) < min_bars_required() (200). The EMAs therefore seed at bar 200, and EMA(144) and MACD(34,144) remain unconverged for roughly the next 150-300 bars, so early trend readings are biased toward the seed close. (line 112) — fix: Either require a signal warm-up of about 3x m2_slow bars inside calculate_signal before setting the buy/sell setups, or accept the bias and document it. - [warning] The slowed %K is computed as the mean of raw %K over the slowing window. MT5's iStochastic (which the EA calls) computes it as 100*SUM(C-LL, slowing)/SUM(HH-LL, slowing), a ratio of sums. The two differ whenever the range varies across the 3 bars, which shifts the 20/80 cross timing slightly. (line 139) — fix: For a faithful port, keep 3-bar deques of (c-ll) and (hh-ll) and compute %K as the ratio of their sums. - [warning] The sandbox ledger is not usable for per-trade economics (L140). avg_trade_return_pct is -259% x 5 trades, which is not reconcilable with total_return -100%. avg_holding_period is 443 days against data_days 162. There are 5 reported trades but 14 fills. Grid levels are netted into a single position, so the ledger counts baskets rather than legs. — fix: No code change is needed for the ledger. After the fixes, compare per-trade return to the fee floor on a per-leg basis (fills), not per basket.
Implementation
Port of geraked's 2MACDSTO v1.4 on ETHUSDT.OKX 4H: MACD(34,144) trend, MACD(13,21) counter-move, Stochastic(7,3,3) %K/%D cross out of 20/80. No hard SL/TP; single position trails at 50% of swing distance; adverse moves of one swing distance add one equal-size grid level (max 50) and the basket closes at a common TP recovering original risk plus costs.
Verification Results
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit input) is set to a……Show moreShow less
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit input) is set to a non-zero value. Check the worst-basket drawdown before spending on optimization. - [critical] The grid gross cap and the drawdown check depend on a false premise. The comment says 'get_account_equity() already includes unrealized PnL (mark-to-market)'. It does not. resolve_account_equity() returns account.balances()[USDT].total, and on an NT MarginAccount that figure is realized cash only; unrealized PnL is not in balances. Three consequences follow. (a) `balance = equity - unreal` overstates the balance by the full unrealized loss. (b) `room = max(equity,0)*lev - qty*c` caps gross at 3x REALIZED cash, not at 3x true equity. As the basket goes underwater, true leverage on real equity grows without limit, and grid levels keep being added against equity that no longer exists. (c) If equity_drawdown_limit_pct is ever set above 0, the check compares realized cash against realized cash minus unrealized, so its sign and magnitude are wrong. The sandbox shows the result: liquidated=true on 2020-06-02, total_return -100%, max_drawdown 100%, and one trade lost $2,949,268, roughly 29x the starting capital. The developer's rationale point (2) claims this was fixed, but the fix rests on the wrong premise. (line 270) — fix: Compute true equity in the strategy as mtm_equity = get_account_equity() + d*(c - avg)*qty (realized cash plus this position's unrealized PnL). Use mtm_equity in the grid room check (room = mtm_equity*lev - qty*c, and refuse adds when mtm_equity <= 0) and in _level_qty for sizing. Use balance = get_account_equity() directly (it already is the balance) in the EquityDrawdownLimit check. - [critical] The basket has unbounded risk: no stop, 3x leverage, and a loss that can exceed equity (L148/L156). Once the gross cap blocks further adds, nothing bounds a basket running against a trend. A short basket in a rally frees no room, because qty*c rises, so it simply holds until the account is liquidated. The largest sandbox loss of about 29x initial capital proves this path is reachable. Neither 'stop distance x size <= 1-2% of equity' nor a worst-case gross relative to TRUE equity can be stated for this configuration. The source provides EquityDrawdownLimit as an input for exactly this purpose. Leaving it at 0 on a 3x-levered crypto port is a sizing and risk defect, not faithfulness. (line 60) — fix: Keep the mechanism as it is, but set equity_drawdown_limit_pct to a non-zero value (for example 20-30%) evaluated on MTM equity per the fix above. Alternatively, lower leverage and max_level_notional_mult so that worst-case gross = grid_max_lvl_reachable x level_notional <= mtm_equity x venue max leverage, and state that bound in the rationale. Both are source inputs, so the port stays faithful. - [warning] The base template does not call calculate_signal() while len(self._bars) < min_bars_required() (200). The EMAs therefore seed at bar 200, and EMA(144) and MACD(34,144) remain unconverged for roughly the next 150-300 bars, so early trend readings are biased toward the seed close. (line 112) — fix: Either require a signal warm-up of about 3x m2_slow bars inside calculate_signal before setting the buy/sell setups, or accept the bias and document it. - [warning] The slowed %K is computed as the mean of raw %K over the slowing window. MT5's iStochastic (which the EA calls) computes it as 100*SUM(C-LL, slowing)/SUM(HH-LL, slowing), a ratio of sums. The two differ whenever the range varies across the 3 bars, which shifts the 20/80 cross timing slightly. (line 139) — fix: For a faithful port, keep 3-bar deques of (c-ll) and (hh-ll) and compute %K as the ratio of their sums. - [warning] The sandbox ledger is not usable for per-trade economics (L140). avg_trade_return_pct is -259% x 5 trades, which is not reconcilable with total_return -100%. avg_holding_period is 443 days against data_days 162. There are 5 reported trades but 14 fills. Grid levels are netted into a single position, so the ledger counts baskets rather than legs. — fix: No code change is needed for the ledger. After the fixes, compare per-trade return to the fee floor on a per-leg basis (fills), not per basket.
Iteration History
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The exit paths are asymmetric. A single position exits only on a trailing stop set at entry + 0.5*pd, so the minimum gross win is about 0.5*pd. pd can be as small as ~0.1% of price (the SLDev floor), which puts the minimum gross win near 0.05%, below the ~0.10% OKX taker round trip. The losing side is an averaging basket with no loss bound. The sandbox result fits this: 3 of 4 trades won small (avg win $3.1k), and one short basket……Show moreShow less
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The exit paths are asymmetric. A single position exits only on a trailing stop set at entry + 0.5*pd, so the minimum gross win is about 0.5*pd. pd can be as small as ~0.1% of price (the SLDev floor), which puts the minimum gross win near 0.05%, below the ~0.10% OKX taker round trip. The losing side is an averaging basket with no loss bound. The sandbox result fits this: 3 of 4 trades won small (avg win $3.1k), and one short basket lost $5.25M. Even with the stop-out fixed, check the distribution of pd/price at entry, and check that the trail exits clear 0.15% of notional. - [critical] Loss is unbounded, and the book can lose far more than the account. The EA's 'no SL' design only works on MT5 because the broker's margin stop-out force-closes the basket. The port has nothing that stands in for it. Once grid adds reach the gross cap (room <= 0), the basket sits with no exit except the basket TP. The single-position trail is disabled because _b_level > 1. There is no equity or margin stop-out and no time exit. The sandbox shows the result. A short basket was held for 554 days, with avg_trade_return_pct -290% of notional and a largest loss of -$5.25M against a total commission of $3.2k. That is a loss many times the account, and it kept running after the liquidation flag on 2020-05-30. Every figure after that date is meaningless. The design has no flat state and no bounded risk, which is exactly the L148/L156 case: worst-case gross is leverage (5) x equity with no stop. A +20% ETH move against a full short basket already wipes the account, and in the sandbox the loss ran to many times equity. (line 256) — fix: Add a margin stop-out that stands in for the MT5 broker's (this is broker behaviour, not a strategy SL, so the port stays faithful). Each bar, compute the basket's unrealized PnL from pos.avg_px_open and the close. Close the whole basket and reset state when equity including unrealized PnL falls to or below a stop-out level, e.g. margin level <= 50%, or unrealized loss >= X% of equity with X set from the pre-registered max_drawdown. Expose that level as a parameter. Also state the worst case in the rationale: max loss per basket = stop-out level, not 'truncated by leverage'. - [critical] The room for grid adds is sized from get_account_equity(). That is the realized USD-stable BALANCE, which excludes the open basket's unrealized loss. While a basket is underwater, each add is still sized against the pre-loss balance: room = 5 x balance - qty*c. The book keeps levering up into a drawdown that has already eaten a large share of real equity, so effective leverage on true equity can go well past 5x before any add stops. The same stale balance feeds _level_qty/_b_risk_amt for the basket TP. (line 265) — fix: Use mark-to-market equity for the grid gross cap: balance + unrealized PnL of the open position at close c. Stop adding when true-equity leverage reaches the cap. - [warning] The MACD trend and counter-move conditions read the [2] bar (_m2_prev/_m1_prev), while the stochastic cross reads [2]->[1]. The description says this matches the EA ('MACD of the prior bar'). I could not check the exact buffer indices against the pinned source commit in this review. If the EA reads MACD at [1], the entries are shifted by one bar. (line 143) — fix: Re-check the CopyBuffer indices in 2MACDSTO.mq5@d3eb29c and align the MACD shift. - [warning] The trailing stop and the basket TP are evaluated on bar closes only, with fills at the close. The EA checks on a 30-second timer. On 4H ETH bars, trail exits can fill well past the trail level, and basket-TP fills can overshoot. The developer documented this deviation. (line 243) — fix: Acceptable as documented. Consider a reduce-only stop resting at _b_trail, updated each bar. - [warning] The ledger books each grid basket as one trade in a single netting position (the developer noted this under L175). total_trades and avg_trade_return_pct therefore count baskets, not levels, so the per-trade fee floor comparison understates the cost of multiple fills. The sandbox also reports trades_count 15 against total_trades 4. — fix: Report the grid fill count alongside the basket count so the analyst can apply a per-fill cost.
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Smoke test loses 36.0% with 88.7% drawdown and PF 0.94; this is advisory, not the correctness verdict. - [critical] The implementation adds 30% equity-drawdown and 50% margin-level hard stops, contradicting the hypothesis's no-hard-SL/TP design. (line 254) — fix: Remove them or explicitly revise and re-register the hypothesis. - [critical] get_account_equity() already includes unrealized PnL, but the code adds unrealized PnL again to……Show moreShow less
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Smoke test loses 36.0% with 88.7% drawdown and PF 0.94; this is advisory, not the correctness verdict. - [critical] The implementation adds 30% equity-drawdown and 50% margin-level hard stops, contradicting the hypothesis's no-hard-SL/TP design. (line 254) — fix: Remove them or explicitly revise and re-register the hypothesis. - [critical] get_account_equity() already includes unrealized PnL, but the code adds unrealized PnL again to form true_eq. Drawdown, margin-stop, and grid-capacity calculations are therefore incorrect. (line 255) — fix: Derive balance by removing current unrealized PnL, then add it exactly once. - [critical] The minimum swing distance is 0.1%, but the 50% trailing stop yields only a 0.05% gross move, below the 0.15% viability floor and approximately 0.10% OKX taker round-trip cost. (line 79) — fix: Enforce a minimum effective trailing move above 0.15% with slippage margin. - [critical] Stochastic slowing uses ratio-of-sums over raw numerators and denominators, rather than averaging raw %K values as standard Stochastic(7,3,3) semantics require. (line 140) — fix: Average raw %K values over the slowing window, then average slowed %K values for %D. - [critical] A gap across multiple grid distances submits one aggregate order but increments the level count by n_add. If the cap truncates the order, state claims levels that were not filled, violating equal-size grid accounting. (line 306) — fix: Submit/account for each level separately or derive actual filled levels from the submitted quantity.
Outcome Summary
An averaging-grid or martingale port needs a basket-level equity stop and grid sizing based on true mark-to-market equity (realized cash plus unrealized PnL); a faithful port with the source's drawdown limit disabled cannot survive levered crypto trends.
After 3 iterations it was abandoned in a verification loop when the Layer 4 QA review failed it as a code_defect. The grid gross cap and drawdown check wrongly treated get_account_equity() as mark-to-market when it is realized cash only, and the 3x-levered equal-size grid had no basket stop (EquityDrawdownLimit was left at 0), so losses were unbounded.
A faithful port of geraked's MQL5 EA 2MACDSTO to ETHUSDT.OKX 4H: enter pullbacks in a MACD(34,144) trend when MACD(13,21) points the other way and Stochastic(7,3,3) crosses out of the 20/80 zone, with no hard SL/TP, a 50%-of-risk trailing stop, and an equal-size averaging grid of up to 50 levels that closes as a basket at a common take-profit.
No full backtest or optimization was run. The only performance evidence is the verification sandbox run, which was liquidated on 2020-06-02 with a -100% total return and 100% max drawdown; one trade lost about $2,949,268, roughly 29x starting capital.
Abandon Reason
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit……Show moreShow less
verification_loop: Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Even after the sizing fix, an equal-size 50-level averaging grid with no basket stop is structurally a short-volatility martingale. On ETH (which rose ~20x in 2020-21 and fell ~80% in 2022), a counter-trend basket that runs into a persistent trend has an unbounded loss once the gross cap stops further adds. No promotion drawdown floor can be met unless a basket-level equity stop (the source's own EquityDrawdownLimit input) is set to a non-zero value. Check the worst-basket drawdown before spending on optimization. - [critical] The grid gross cap and the drawdown check depend on a false premise. The comment says 'get_account_equity() already includes unrealized PnL (mark-to-market)'. It does not. resolve_account_equity() returns account.balances()[USDT].total, and on an NT MarginAccount that figure is realized cash only; unrealized PnL is not in balances. Three consequences follow. (a) `balance = equity - unreal` overstates the balance by the full unrealized loss. (b) `room = max(equity,0)*lev - qty*c` caps gross at 3x REALIZED cash, not at 3x true equity. As the basket goes underwater, true leverage on real equity grows without limit, and grid levels keep being added against equity that no longer exists. (c) If equity_drawdown_limit_pct is ever set above 0, the check compares realized cash against realized cash minus unrealized, so its sign and magnitude are wrong. The sandbox shows the result: liquidated=true on 2020-06-02, total_return -100%, max_drawdown 100%, and one trade lost $2,949,268, roughly 29x the starting capital. The developer's rationale point (2) claims this was fixed, but the fix rests on the wrong premise. (line 270) — fix: Compute true equity in the strategy as mtm_equity = get_account_equity() + d*(c - avg)*qty (realized cash plus this position's unrealized PnL). Use mtm_equity in the grid room check (room = mtm_equity*lev - qty*c, and refuse adds when mtm_equity <= 0) and in _level_qty for sizing. Use balance = get_account_equity() directly (it already is the balance) in the EquityDrawdownLimit check. - [critical] The basket has unbounded risk: no stop, 3x leverage, and a loss that can exceed equity (L148/L156). Once the gross cap blocks further adds, nothing bounds a basket running against a trend. A short basket in a rally frees no room, because qty*c rises, so it simply holds until the account is liquidated. The largest sandbox loss of about 29x initial capital proves this path is reachable. Neither 'stop distance x size <= 1-2% of equity' nor a worst-case gross relative to TRUE equity can be stated for this configuration. The source provides EquityDrawdownLimit as an input for exactly this purpose. Leaving it at 0 on a 3x-levered crypto port is a sizing and risk defect, not faithfulness. (line 60) — fix: Keep the mechanism as it is, but set equity_drawdown_limit_pct to a non-zero value (for example 20-30%) evaluated on MTM equity per the fix above. Alternatively, lower leverage and max_level_notional_mult so that worst-case gross = grid_max_lvl_reachable x level_notional <= mtm_equity x venue max leverage, and state that bound in the rationale. Both are source inputs, so the port stays faithful. - [warning] The base template does not call calculate_signal() while len(self._bars) < min_bars_required() (200). The EMAs therefore seed at bar 200, and EMA(144) and MACD(34,144) remain unconverged for roughly the next 150-300 bars, so early trend readings are biased toward the seed close. (line 112) — fix: Either require a signal warm-up of about 3x m2_slow bars inside calculate_signal before setting the buy/sell setups, or accept the bias and document it. - [warning] The slowed %K is computed as the mean of raw %K over the slowing window. MT5's iStochastic (which the EA calls) computes it as 100*SUM(C-LL, slowing)/SUM(HH-LL, slowing), a ratio of sums. The two differ whenever the range varies across the 3 bars, which shifts the 20/80 cross timing slightly. (line 139) — fix: For a faithful port, keep 3-bar deques of (c-ll) and (hh-ll) and compute %K as the ratio of their sums. - [warning] The sandbox ledger is not usable for per-trade economics (L140). avg_trade_return_pct is -259% x 5 trades, which is not reconcilable with total_return -100%. avg_holding_period is 443 days against data_days 162. There are 5 reported trades but 14 fills. Grid levels are netted into a single position, so the ledger counts baskets rather than legs. — fix: No code change is needed for the ledger. After the fixes, compare per-trade return to the fee floor on a per-leg basis (fills), not per basket.
Analysis
2MACDSTO
Backtest and paper results are hypothetical. Trading involves risk of loss.