Skip to content
All strategies

Strategies

Faithful port of geraked NWERSIASF v1.4 (Nadaraya-Watson Envelope + RSI(5) + ATR-SL-Finder reversal, 1.1x martingale grid up to 20 levels, 50% trailing stop), 3-symbol basket on OKX USDT swaps, 2H, long-short

Outcome: Abandoned

NWERSIASFGridBasketOkx

Outcome Summary

This hypothesis set out to translate Geraked's NWERSIASF v1.4 expert advisor onto a 3-symbol OKX USDT-swap basket on 2H bars, keeping every default input. That included the no-stop 1.1x martingale grid of up to 20 levels. The developer produced partial strategy code with incremental NWE, RSI and ATR-SL-Finder indicators. After 2 iterations, the coding stage hit three timeouts and the run was abandoned as 'coding_stuck_3_timeouts'. No backtest, optimization or review stage was reached, so the strategy's edge was never measured.

Hypothesis

Line-for-line translation of the MIT-licensed MQL5 expert advisor 'NWERSIASF' v1.4 by Geraked (https://github.com/geraked/metatrader5, Experts/NWERSIASF.mq5, commit d3eb29c; helper library Include/EAUtils.mqh and indicators Indicators/NadarayaWatsonEnvelope.mq5 and Indicators/AtrSlFinder.mq5 at the same commit). Credit: the strategy is by Geraked. The original trades USDCAD, AUDUSD and EURCHF on 2H bars. We keep its structure: 2H bars, three symbols evaluated independently in one strategy, every input at its……Show moreShow less

Line-for-line translation of the MIT-licensed MQL5 expert advisor 'NWERSIASF' v1.4 by Geraked (https://github.com/geraked/metatrader5, Experts/NWERSIASF.mq5, commit d3eb29c; helper library Include/EAUtils.mqh and indicators Indicators/NadarayaWatsonEnvelope.mq5 and Indicators/AtrSlFinder.mq5 at the same commit). Credit: the strategy is by Geraked. The original trades USDCAD, AUDUSD and EURCHF on 2H bars. We keep its structure: 2H bars, three symbols evaluated independently in one strategy, every input at its default value. The symbols are mapped to OKX USDT perpetual swaps. OKX is 0.3% of the corpus, carries pure OHLCV with multi-year 2H history, and needs no supplementary feed (L160). The three names are chosen by a stated rule, not by hand: the three OKX USDT swaps with the oldest 2H history in the catalog, BTC excluded because it is over-concentrated in the corpus. That gives BCHUSDT.OKX (from 2019-12), ADAUSDT.OKX (2020-03) and ATOMUSDT.OKX (2020-05). The primary is BCHUSDT.OKX; the other two go in extra_instruments with their 2H bar types. Performance claims in the source ('2019-2023 on FX') are unverified; our fee-inclusive backtest decides. INDICATORS (reproduce exactly; bar-index notation as in MQL5, where index 0 is the forming bar, 1 the last closed bar and 2 the one before it; on our platform, at the close of bar t, index 1 = t and index 2 = t-1): (1) Nadaraya-Watson Envelope (NweBandWidth=8.0, NweMultiplier=3.0, NweWindowSize=500). This is the causal/endpoint form: it looks only at the current bar and older bars. yhat[t] = sum_{k=0..499} close[t-k]*g(k) / sum_{k=0..499} g(k), with g(k)=exp(-k^2/(2*8^2)) (k=0 is the current bar; truncate if fewer than 500 bars exist). resid[t] = |close[t]-yhat[t]|. mae[t] = (sum_{k=0..499} resid[t-k]) / 500 * 3.0. Note: it divides by 500 even when fewer are available, so require full warm-up. upper[t]=yhat[t]+mae[t] (buffer 0), lower[t]=yhat[t]-mae[t] (buffer 1). Precompute the 500 weights once, keep close and resid in fixed-length deques/numpy ring buffers, compute yhat as one dot product and mae as a running sum. That keeps per-bar cost O(500) and flat, avoiding the sandbox_timeout that killed recent runs. Warm-up is 1000 bars (~83 days). (2) RSI: MT5 iRSI, length 5, on close, Wilder smoothing. (3) ATR Stop-Loss Finder (AsfLength=14, AsfMultiplier=0.75): TR[t]=max(high-low, |high-close[t-1]|, |low-close[t-1]|). ma = SIMPLE mean of the last 14 TR (not Wilder). asfUp[t]=high[t]+0.75*ma, asfDn[t]=low[t]-0.75*ma. SIGNAL (CheckForSignal, per symbol, at each 2H bar close): BUY bc = low[2] < lower[2] AND close[1] > open[1] AND ask < lower[1] + 0.5*(upper[1]-lower[1]) AND RSI[2] < 30. SELL sc = high[2] > upper[2] AND close[1] < open[1] AND bid > upper[1] - 0.5*(upper[1]-lower[1]) AND RSI[2] > 70. Use the close of bar 1 for ask and bid. RSI[2] is the RSI value of the bar BEFORE the last closed bar; do not 'fix' this. BUY is checked first (else-if). Reverse=false. GATES (in source order): OpenNewPos=true. MarginLimit=4000: if any position is open anywhere and margin level (equity / used margin * 100, used margin = total open notional / leverage) < 4000%, no new signal entries on ANY symbol. Grid additions and exits are not gated by this. MultipleOpenPos=true. Skip a symbol if it already has an open position/basket (OPTotal>0). MinPosInterval=4: skip a symbol if a CLOSING fill (exit, not entry) happened on it within the last 4 bars (8h). SpreadLimit=-1 disabled. News=false. EquityDrawdownLimit=0 disabled. ENTRY AND SIZING (GerEA BuyOpen/SellOpen, RISK_DEFAULT, Risk=1.2): entry at market. Long: sl = asfDn[1], pd = |entry - sl|. Short: sl = asfUp[1]. Quantity = 0.012 * account balance / pd, so a move of pd costs 1.2% of balance. TP = entry +/- 1.5*pd is computed but IgnoreTP=true, so no TP is placed. With Grid=true the initial SL is NOT placed as a resting stop. pd is stored per basket as the grid step and trail unit. leverage=5 (OKX cap is higher). If an order would exceed available margin it is rejected and skipped, as the broker would do. There is no other size cap. GRID (checkForGrid; Grid=true, GridVolMult=1.1, GridMaxLvl=20, GridTrailingStopLevel=0). Per symbol basket with n open legs (1 <= n < 20). Long: lastPrice = lowest leg entry. Next level lvl = firstEntry - n*pd; if |lastPrice - pd - lvl| > 0.5*pd then lvl = lastPrice - pd. When price <= lvl, add a leg of qty = 1.1 * largest existing leg qty. Then set the basket TP (one reduce-only take-profit for the whole basket) at the price where the aggregate basket PnL equals target_prof. target_prof = (first-leg qty * pd, the original 1.2% risk amount) + accumulated basket cost (fees) if positive. Short is mirrored (highest entry, lvl = firstEntry + n*pd). Implement the level as a resting limit order so it fills intrabar, approximating the source's 120s timer polling. The basket closes only at the basket TP. GridTrailingStopLevel=0 means no grid trailing. At 20 legs nothing more is added, and there is NO stop loss: the basket is held until TP or until the engine's margin/liquidation logic intervenes. This is faithful to the source (IgnoreSL false, but grid mode suppresses the broker SL; EquityDrawdownLimit=0). TRAILING (checkForTrail, TrailingStopLevel=50 -> 0.5; applies while the basket has exactly one leg). Long: if bid > entry, d = bid - entry, candidate sl = max(entry, breakeven_incl_fees) + d - 0.5*pd. Apply only if candidate >= entry and it raises the existing stop (ratchet). In effect, once price is >= ~0.5*pd in profit, a stop trails 0.5*pd behind price, floored at breakeven. If the candidate stop is within 30 points (use 30 * price increment) of price and the position is net profitable after fees, close at market. Short is mirrored. Place the trail as a stop-market order updated at each bar close so it fills intrabar. Once a basket has >1 leg only the basket TP applies. EXITS: single leg -> trailing stop only (no TP, no SL). Multi-leg -> basket TP only. Flat state: a symbol is flat whenever it has no basket. The expected portfolio is flat ~55-70% of the time, because entries need a 3x-MAE envelope pierce plus an RSI(5) extreme plus a reversal candle.

A faithful port with every EA default. Platform adaptations: (1) The EA polls on a 120 s timer. Here the grid adds, basket TP and trailing stop are resting venue orders that fill intrabar. The trail is re-ratcheted and the orders are resynced at each 2H close, and again right after every fill (on_order_filled). (2) The 3 symbols share one NETTING account, so each symbol's grid is one netted position and one ledger trade per basket. The legs are tracked in a persisted per-symbol scalar dict. (3) Ask/bid = the……Show moreShow less

A faithful port with every EA default. Platform adaptations: (1) The EA polls on a 120 s timer. Here the grid adds, basket TP and trailing stop are resting venue orders that fill intrabar. The trail is re-ratcheted and the orders are resynced at each 2H close, and again right after every fill (on_order_filled). (2) The 3 symbols share one NETTING account, so each symbol's grid is one netted position and one ledger trade per basket. The legs are tracked in a persisted per-symbol scalar dict. (3) Ask/bid = the bar-1 close. All three legs must have a bar at the primary timestamp before a symbol is evaluated. (4) Broker margin rejection is modelled: an entry or add is skipped when total notional would exceed 95% of equity*leverage(5). With a 1.1x martingale, 20 levels need ~57x the first leg, so deep grids hit this cap long before level 20. (5) The margin level uses equity incl. floating PnL over notional/leverage. (6) Entries for all symbols, primary included, go out inside calculate_signal in the EA's loop order, so should_enter/should_exit return None/False by design. Honest note: this is a no-stop martingale, and on trending crypto alts expect rare but very deep drawdowns. The backtest will show whether the reversal edge pays for that tail.

coding_stuck_3_timeouts

Implementation

Stratmill translation of Geraked's MIT-licensed NWERSIASF v1.4 on 2H OKX USDT swaps (BCH primary, ADA, ATOM). Per symbol: a causal Nadaraya-Watson envelope (bandwidth 8, 3x MAE, 500-bar window), MT5 RSI(5) and ATR SL Finder (SMA-14 TR x0.75). BUY when bar t-1's low pierced the lower band, bar t is bullish, close is in the lower half of the envelope and RSI[t-1] < 30. SELL mirrors this. BUY is checked first. Entry is at market. Size = 1.2% of balance / pd, where pd = |close - ASF stop|. A single leg is protected only by a 50%-of-pd trailing stop floored at breakeven: a venue stop-market that ratchets each bar. A resting limit adds 1.1x the largest leg at firstEntry -/+ n*pd, with the source's re-anchoring rule, up to 20 legs. Once a symbol has 2+ legs, one reduce-only basket take-profit sits where the basket PnL equals first_qty*pd + fees. There is no stop loss. Gates: margin level < 4000% blocks new entries while anything is open, one basket per symbol, and a 4-bar cooldown after a close.

Iteration History

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (1666 days of BCHUSDT.OKX-2-HOUR-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Outcome Summary

Line-for-line ports of complex EAs can time out at the coding stage. This one combined a 500-bar kernel envelope, per-symbol martingale grid baskets, resting limit/stop orders and a cross-symbol margin gate. Splitting such a port into a smaller core first, for example the signal and single-leg trail without the grid, may get it to a backtest.

The run was abandoned in the coding stage with reason 'coding_stuck_3_timeouts' after 2 iterations. It never reached verification, backtesting, analyst review or risk review.

A faithful port of Geraked's MIT-licensed NWERSIASF v1.4 MQL5 expert advisor. It trades 2H mean-reversion entries on a Nadaraya-Watson envelope pierce plus an RSI(5) extreme plus a reversal candle, with a 1.1x martingale grid of up to 20 legs and no stop loss, and a 50% trailing stop on single legs. The basket is BCHUSDT, ADAUSDT and ATOMUSDT OKX USDT swaps.

No performance data exists. The backtest report is empty and no optimization was run, so there are no return, Sharpe or trade-count figures.

Analysis

NWERSIASF

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