Skip to content
All strategies

Strategies

51bitquant MartingleSpotStrategyV2: Faithful ETC Spot Drawdown-and-Bounce Accumulation

Outcome: Abandoned

EtcMartingleSpotStrategyV2

Outcome Summary

After two iterations, EtcMartingleSpotStrategyV2 produced 805 closed ETC spot baskets and passed backtest review for frozen evaluation. The final replay returned 36.298% with Sharpe 0.355 and 19.497% maximum drawdown, while a recovering basket remained open for 921 days. The analyst abandoned the faithful port because only 4 of 9 exposed years were profitable, below the enforced 60% requirement, with profits concentrated in 2021. Changing frozen parameters or adding protective rules would alter the requested strategy.

Hypothesis

Faithful port of MartingleSpotStrategyV2 by 51bitquant, using ETCUSDT.BINANCE_SPOT and 1-minute bars. Preserve executable defaults rather than contradictory comments: both drawdown thresholds are 0.04, not 0.05; the take-profit threshold is 0.02. Preserve close-priced resting LIMIT orders, not market orders or intrabar take-profit brackets. Orders submitted after a completed bar cannot fill retrospectively against that bar's earlier range. The source's 1000 USDT initial_trading_value is adapted to 1% of equity captured at the start of each accumulation cycle, matching 1000 USDT on the platform's 100000 USDT initial account. Freeze that base throughout the cycle. Preserve the 1.3 multiplier and counter semantics, allowing one initial purchase and up to seven additions. No stop-loss, time exit, trend filter or funding filter is added. Source trading_fee=0.00075 remains a diagnostic bookkeeping default; actual platform commissions determine economic results. The promotional link failed to resolve and supplied no additional strategy rules.

The submitted strategy is complete and testable with supported spot limit orders and netted inventory. Preserve its mechanism rather than adding filters based on previous failures. ETC avoids repeating the named DOT inventory-balancing and LINK Supertrend ports; this strategy buys only after a drawdown and adds after a decline-and-bounce, rather than maintaining a cash/coin grid or following trend indicators. Spot is underrepresented at 11.1% versus its 15% target. The requested faithful port necessarily remains single-symbol and long-only despite those dimensions being overrepresented. Corpus concentration is empty, so no claim is made that ETC is unused or below 15% share.

Reject this faithful ETC port for promotion: its enforced positive-years requirement fails at 4/9 years, with profits concentrated in 2021 and prolonged underwater accumulation. Per-trade return is positive at 1.802% versus the supplied 0.40% economics floor, so this is not a fee-edge failure. No remaining implementation defect is established; adding protective rules or changing frozen values would change the requested faithful strategy. Inconclusive significance is not the abandonment basis. No engine artifact is asserted, and this verdict does not reject other accumulation variants or the differently specified promoted sibling.

Implementation

Faithful port of 51bitquant MartingleSpotStrategyV2 on ETCUSDT Binance spot 1m bars. When the 2880-bar Donchian high is >=4% above the bar low and holdings are below 11 USDT, start a cycle with a close-priced GTC LIMIT BUY sized at 1% of equity (frozen per cycle). Add up to 7 times (size x1.3^n) after a >=4% decline from the last entry price followed by a >=1% bounce off the post-entry low. Exit the whole basket with a close-priced reduce-only LIMIT SELL once close/avg_price-1 >= 2%. No stop, time exit or filters.

Implementation fix only; the trading rules are unchanged. Likely cause of the single 3033-day position: the take-profit SELL was sized from self.current_pos, a float summed fill by fill, and rounded down to the size increment, so it could leave one increment of ETC unsold. That leftover (<11 USDT) let the next cycle start on top of it, and the engine position never reached zero. Fixes: (1) The basket SELL now uses the engine's open position quantity (cache.positions_open(...)[0].quantity) as is, without rounding……Show moreShow less

Implementation fix only; the trading rules are unchanged. Likely cause of the single 3033-day position: the take-profit SELL was sized from self.current_pos, a float summed fill by fill, and rounded down to the size increment, so it could leave one increment of ETC unsold. That leftover (<11 USDT) let the next cycle start on top of it, and the engine position never reached zero. Fixes: (1) The basket SELL now uses the engine's open position quantity (cache.positions_open(...)[0].quantity) as is, without rounding down, so a filled sell closes the position. I expect no leftover from fees because this CASH venue should charge commission in USDT, not ETC; I have not checked this. Any leftover from a partly filled sell that an addition later cancels is added to the next basket and sold with it. (2) On every bar, current_pos and avg_price are taken from the engine position (quantity, avg_px_open). The cycle state (avg_price, entry_lowest, last_entry_price, current_increase_pos_times) resets when the engine is flat. It also resets in on_order_filled when a SELL leaves less than the source's 11 USDT min_notional, matching the source's on_trade / new-cycle reset. (3) on_order_denied and on_order_rejected now log a warning and count refusals. Platform adaptations are unchanged: 1% of cycle-start equity replaces 1000 USDT, and orders are close-priced resting limits filled only by later market events. The source's trading_fee is used only for the diagnostic total_profit. The new code passes the static analyzer with no errors, but I did not run a backtest, so the expected count of closed cycles (hundreds) is a projection from the ~2,350 fills in the previous run, not a measurement.

Verification Results

No change required; behavior matches the source.

Additions require the full chain to complete: decline >= 4%, then bounce >= 1%, then the close-priced GTC limit BUY fills. That limit stays open until the next decision cancels it. An unfilled addition blocks new additions (buy_working) until the take-profit branch cancels it. The source does the same with its buy_orders list, so this is faithful, but in a fast rebound an addition can sit unfilled.

None; noted for traceability.

sync_with_engine overwrites current_pos and avg_price from the engine position (avg_px_open) on every bar, while on_order_filled also updates them per fill. The two agree for a single netted long, so there is no behavioral difference. avg_px_open is engine-side and excludes fees, the same as the source's avg_price.

None.

Fixed parameters initial_trading_value (1000), trading_fee and history_days are read but have no effect on trading. Sizing uses 1% of cycle-start equity, as the hypothesis states; trading_fee only feeds the diagnostic total_profit. This is the disclosed adaptation.

This is a martingale averaging-down design with no hard stop; the hypothesis specifies that, so it is not a defect here. Worst-case inventory per cycle: 1% x sum(1.3^k, k=0..7) = about 23.9% of cycle-start equity. Spot is 1x, so the book can never ask for more than the available cash. That also bounds the worst loss on one stuck cycle to about 24% of equity, against the contract's 25% max-drawdown constraint. The sandbox gives no evidence on that tail. It closed only 4 baskets, all winners: PF 0.0 means there were no losing closed trades (see L188), not an accounting fault. Its drawdown was already 28.6% (mark-to-market, 12 days), which is above the 25% constraint. Spot round-trip cost is about 0.20% versus the 2% basket take-profit, so fees are not the risk. The risk is a long ETC decline that strands the basket after 7 additions. The analyst should check worst open-inventory drawdown, how long stuck baskets are held, and win-rate concentration on full history.

Backtest Review

Sharpe
0.35
Total return
35.99%
Max drawdown
19.50%
Trades
805
Win rate
99.9%
Profit factor
3.16

Prior inventory fix worked: 805 closed long baskets. Sampled exits and variable accumulation sizes match the specified mechanism.

Net average trade return 1.801% exceeds the 0.25% spot review floor; PF 3.156 and total return +35.990% justify proceeding with the frozen, all-fixed evaluation.

Accounting reconciles; terminal unrealized return is zero. Sampled buy-and-hold return is -38.745%, with approximately 94.634% drawdown, versus strategy drawdown 19.497%.

The 99.876% win rate reflects take-profit-only exits and one terminal losing closure, not demonstrated robustness. The final basket lost $16,692 after 337 days; no artifact mechanism is established.

QA's stranded-inventory concern is borne out: one basket lasted 921 days with -81.632% trade MAE before recovery. Account drawdown nevertheless remains within the contracted 25% limit.

MTM Sharpe is 0.353, five calendar years are negative, and exposure is 95.05%. Forward validation remains necessary.

Analysis

Final fixed-parameter replay closely matches baseline: 805 closed baskets, Sharpe 0.355 and total return 36.298%.

Average net trade return 1.802% exceeds the supplied 0.40% economics floor; PF 3.216 and drawdown 19.497% clears the contractual 25% limit.

Full-history trade-count, return, per-trade-return and drawdown predictions were met. OOS significance remains unmeasured.

Sampled buy-and-hold return is -38.745%, with approximately 94.634% drawdown; benchmark underperformance is not the rejection reason.

Enforced hard failure: only 4 of 9 exposed calendar years are profitable, 44.4% versus the required 60%. This is not classified as soak advisory.

2021 accounts for 112.3% of profit; Calmar is 0.194 and drawdown recovery lasts 921 days.

The 99.876% win rate reflects profit-target exits while losing inventory remains open. A winning basket lasted 921 days with -81.63% adverse excursion. The terminal basket lost $16,692 after 336 days, approximately 255 average wins. These observations do not establish an engine artifact.

The 608-day holdout has 114 trades and Sharpe -0.500, but its HAC assessment is inconclusive, not statistically rejected. Pooled OOS significance lacks complete MTM coverage, and consistency is invalid because return frequencies differ.

The drawdown-threshold sensitivity cliff is advisory. Null sweep measurements are unknown. The faithful-port contract fixes every parameter.

soft

hard

IMPLEMENTATION FIX — the hypothesis is unchanged: make the code do what it already says. Do not add or change trading rules. Implementation fix only; do not change the hypothesis or any fixed contract value. The engine position for ETCUSDT.BINANCE_SPOT must go fully flat at the end of every accumulation cycle, so each cycle is reported as one closed position. In the backtest it opened 2018-06-21 and never closed (one 3033-day position, about 2,350 fills). Fixes: (1) Size the take-profit SELL from the engine's……Show moreShow less

IMPLEMENTATION FIX — the hypothesis is unchanged: make the code do what it already says. Do not add or change trading rules. Implementation fix only; do not change the hypothesis or any fixed contract value. The engine position for ETCUSDT.BINANCE_SPOT must go fully flat at the end of every accumulation cycle, so each cycle is reported as one closed position. In the backtest it opened 2018-06-21 and never closed (one 3033-day position, about 2,350 fills). Fixes: (1) Size the take-profit SELL from the engine's actual open position quantity (cache.positions_open(instrument_id)[0].quantity, net of any base-asset commission), not from self.current_pos. Make sure the quantity is not truncated below the held amount. If the held quantity is not a multiple of size_increment, sell the full held quantity; any sub-increment dust left after the fill must be flattened, or must not count as an open cycle. (2) On every bar and in on_order_filled, reconcile self.current_pos with the engine position quantity. After a SELL fill brings the position to zero or dust, reset avg_price, entry_lowest, last_entry_price and current_increase_pos_times, so the next cycle starts clean, as in the source. (3) Log or assert if a reduce-only SELL is denied or rejected. (4) Re-run and confirm: closed positions number in the hundreds or more, avg_holding is hours to weeks, and the final open position at the backtest end is at most one cycle's inventory. Then avg_trade_return_pct, PF and win rate can be judged against the 0.25% spot floor. ## Library refinements (from the knowledge library; test them, do not assume them) The library holds the exact MartingleSpotStrategyV2 source. Its rules support the analyst's fix: exit the whole basket from the real position and reset the cycle when flat. The author's own V3 and two FMZ DCA/grid design articles say the risky parts are no trend gate, no basket stop and no time exit, plus a win rate measured only on completed cycles. Refinements 2-5 change the rules, so they belong in a variant hypothesis, not in the faithful port. 1. [exit] Basket exit sized from the engine position, cycle reset on flat: Implementation only, rules unchanged. The take-profit SELL quantity is the engine's open position quantity, cache.positions_open(instrument_id)[0].quantity, not self.current_pos. Do not floor it below the held amount. On every bar and in on_order_filled, set current_pos = engine quantity and avg_price = the engine position's avg_px_open. When the position is flat, or holds less than 11 USDT of notional (the source's min_notional), reset avg_price=0, entry_lowest=0, last_entry_price=0, current_increase_pos_times=0 and clear the buy/sell order lists. Log any denied or rejected reduce-only SELL. Judge the re-run on mark-to-market equity, not on the win rate of completed cycles. — The backtest shows 1 position over 3033 days, 6 'trades' and a 100% win rate. Cycles never close, so per-trade metrics mean nothing. The source sells abs(current_pos) and runs the next cycle only when current_pos*close < min_notional (V2 on_bar/on_trade), so local and real holdings must match. D-Man V3 exits all filled layers as one basket at the exchange's average position price, to avoid 碎片持仓 and orphan orders. The Kelly-grid article freezes new risk when 持仓与本地账本不一致. It also warns that a completed-grid win rate (its example: 96.8%) hides open losses, so it measures the strategy on mark-to-market equity. (source: MartingleSpotStrategyV2 p.1; 用 Rust 在 FMZ 实现 D-Man V3 布林带反转DCA策略 p.1; 网格不该决定仓位:用分数凯利给只做多网格加一道资金预算 p.1) 2. [regime] Trend gate on new cycles only: Variant only. Start a new cycle (the initial BUY) only if, at the bar close, EMA(3000) >= EMA(12000) on 1m closes (about 50h and 200h) AND close >= EMA(12000). Additions and exits inside an open cycle are unchanged. Raise warmup_bars from 4320 to 12000. — V2 buys every 4% drop from the 2-day high whatever the trend. On ETC that buys into long bear legs and builds up to 8 layers of inventory. The long-only Kelly grid allows new grid entries only when FastEMA >= SlowEMA and Close >= SlowEMA. Its reasoning: 正向漂移都是待验证假设 (positive drift is an assumption to test, not a law). The anchor must not chase price down while a position is open. (source: 网格不该决定仓位:用分数凯利给只做多网格加一道资金预算 p.1) 3. [stop] Basket stop at 2% of cycle-start equity plus a time exit: Variant only. Exit the whole basket at the bar close if the unrealised loss is at least 2.0% of the equity captured at cycle start: (avg_price - close) * position_qty >= 0.02 * cycle_start_equity. Also exit if the cycle has been open more than 43200 bars (30 days). After a stop, wait 1440 bars before a new cycle can start. — Fully loaded, the cycle holds 1% * (1 + 1.3 + ... + 1.3^7) ≈ 23.9% of equity, and nothing ever exits it at a loss. This backtest's 19.5% max drawdown came from one inventory held for years. D-Man V3 exits the basket on a stop of 3 × ScaleRatio and after MaxHoldingBars. The Kelly grid has a latched RISK_HALT on a hard stop or an equity drawdown. A 2%-of-equity stop also meets the platform's risk-control option (a). (source: 用 Rust 在 FMZ 实现 D-Man V3 布林带反转DCA策略 p.1; 网格不该决定仓位:用分数凯利给只做多网格加一道资金预算 p.1) 4. [exit] Author's V3 trailing take-profit: Variant only. Replace the fixed 2% take-profit limit. Track entry_highest = max(high) since the last fill. Exit the basket at the close when close/avg_price - 1 >= 0.02 AND entry_highest/close - 1 >= 0.01. Keep exit_profit_pct=0.02 so each trade still clears the 0.25% spot floor plus about 0.2% round-trip spot fees. — In MartingleSpotStrategyV3, 51bitquant replaced V2's fixed take-profit with 利润超过1%,且最高价回调1%后平仓 (exit_profit_pct=0.01, exit_pull_back_pct=0.01), so winners can run past the target. D-Man V3 also uses a trailing take-profit, armed at 0.8 × ScaleRatio. We keep the 2% profit floor rather than V3's 1% because spot fees on our venue are 0.1% per side. (source: MartingleSpotStrategyV3 p.1; 用 Rust 在 FMZ 实现 D-Man V3 布林带反转DCA策略 p.1) 5. [parameter] Volatility-scaled add spacing with a cost floor, frozen per cycle: Variant only. At cycle start, freeze dump_down_pct = clamp(max(0.04, 3 * ATR(14 hourly, built from 840 1m bars)/close, 2*(0.001+0.0005)*2), 0.04, 0.12). Set bounce_back_pct = 0.25 * dump_down_pct, with a minimum of 0.01. Both stay fixed for the whole cycle. — A fixed 4% add step is tight when ETC is volatile, so all 7 additions fill early in a crash, and loose when it is calm. The Kelly grid sets spacing = max(MinGridPct, ATR/Price × AtrMultiplier, 2 × (fee+slippage) × CostFloorMultiple). It notes that filled layers must not have their targets moved by later ATR changes. D-Man V3 also scales its DCA distances by band half-width, clamped between 0.2% and 8%. (source: 网格不该决定仓位:用分数凯利给只做多网格加一道资金预算 p.1; 用 Rust 在 FMZ 实现 D-Man V3 布林带反转DCA策略 p.1)

Outcome Summary

A near-perfect profit-target win rate can coexist with years of underwater inventory and weak performance across calendar years.

The analyst abandoned it because the enforced positive-years requirement failed: 44.4% versus 60%, alongside concentrated profits and prolonged underwater accumulation. Fee economics passed; inconclusive significance and an engine artifact were not the abandonment basis, and risk review was not reached.

Faithfully port ETC spot drawdown-and-bounce accumulation using close-priced limit orders, a frozen 1% cycle equity base, up to seven additions at a 1.3 multiplier, and a 2% profit target without a stop-loss.

The baseline backtest returned 35.990% with Sharpe 0.353, 805 closed baskets and 19.497% maximum drawdown; the final fixed-parameter replay returned 36.298% with Sharpe 0.355. Final average net trade return was 1.802%, but only 4 of 9 exposed calendar years were profitable and 2021 contributed 112.3% of profit.

Analysis

MartingleSpotStrategyV2

Analysis

martingle_spot_strategyV2.py

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