Skip to content
All strategies

Strategies

ETH Spot Ping-Pong Limit Pair (port of FMZ 'Buy then Sell' by Zero): Daily-Anchored Resting Buy 2.5% Below the UTC-Day Open, Resting Sell at Buy x 8000/7800 (+2.56%), Long-Only, No Leverage, 30-Day Safety Clock (ETHUSDT.BINANCE_SPOT, 1H fill resolution)

Outcome: Abandoned

EthSpotPingPongDailyLimitPair

Outcome Summary

EthSpotPingPongDailyLimitPair tried to turn a single-trade public FMZ script into a repeatable daily limit-pair strategy on ETH spot, betting that its +2.56% target, about 13x the spot round-trip cost, would survive fees. The last iteration changed only order-cache access, adding a defensive _cache_orders helper. Across 3 iterations the code never passed Layer 1 static analysis; the final failure was a syntax error at line 1, and the run was abandoned as a verification loop. The strategy never reached backtesting, so the hypothesis is still untested.

Hypothesis

Port of the FMZ open-source strategy 'Buy then Sell (ping-pong strategy)' by Zero (fmz.com/strategy/169227). The original rests ONE limit buy at a fixed absolute price (BUYPRICE=7800, BUYAMOUNT=0.1 BTC). Once the fill shows up in the balance, it rests a limit sell for the filled quantity at a fixed absolute price (SELLPRICE=8000). It never stops out and never repeats. Taken literally it is a single trade at 2019 BTC prices: 7800/8000 cannot fill against today's prices, and one trade cannot be backtested. So it……Show moreShow less

Port of the FMZ open-source strategy 'Buy then Sell (ping-pong strategy)' by Zero (fmz.com/strategy/169227). The original rests ONE limit buy at a fixed absolute price (BUYPRICE=7800, BUYAMOUNT=0.1 BTC). Once the fill shows up in the balance, it rests a limit sell for the filled quantity at a fixed absolute price (SELLPRICE=8000). It never stops out and never repeats. Taken literally it is a single trade at 2019 BTC prices: 7800/8000 cannot fill against today's prices, and one trade cannot be backtested. So it counts as a strategy, but it is not viable as written. The only scale-free content in the defaults is the buy/sell geometry: the sell sits 8000/7800 - 1 = +2.564% above the buy, and the buy sits 7800/8000 = 97.5% of the sell level. This port keeps that geometry exactly. It turns the absolute prices into a reference-relative pair: the reference R is the sell-side anchor (the '8000'), and the buy rests at 0.975 x R (the '7800'). To produce a sample, the pair re-arms every UTC day while flat. Adaptations limited to venue, instrument, sizing and fees: (1) venue BINANCE_SPOT. The original is a long-only, unleveraged cash buy-then-sell, which is exactly the spot CASH account. (2) instrument ETHUSDT instead of BTC_USD. BTC is heavily over-represented in the corpus and the mechanism is not BTC-specific; ETH spot has 8+ years of Binance history. (3) sizing is 50% of equity per cycle instead of a fixed 0.1 BTC. (4) one platform-mandated addition that the original lacks: a 30-day safety clock (market exit), so a position cannot be held without limit. The platform's holding-period gate is <=90 days and an unbounded bag-hold cannot pass risk review. This is flagged as a deviation, and a 'no clock' variant can be scored separately. Honest caveat (lesson L167): public-script ports usually die. This one survives the fee regime on paper because its target (+2.56%) is ~13x the spot round trip. The real question is whether the fat left tail from the clock exits eats the high hit rate. Flat state: flat between a sell fill and the next day's re-arm, and on days the buy does not fill. Expected time-in-market is ~45-60%.

Iteration 3 fixes the Layer 2 crash: the verifier's _MockCache has no orders_inflight(). All cache order queries now go through _cache_orders(), which calls the method only if the cache has it and otherwise returns an empty list. With the real engine nothing changes, because both orders_open and orders_inflight are still checked. Signal, entry, exit and sizing logic are unchanged. The FMZ buy/sell spacing is kept exactly: buy at 97.5% of the reference, sell 2.564% above the buy. The only change the platform requires is the 30-day clock.

verification_loop: Verification failed (Layer 1 — static analysis): - Syntax error: invalid syntax. Perhaps you forgot a comma? (<unknown>, line 1) (line 1)

Implementation

Long-only ETH spot ping-pong. Each UTC day while flat, a GTC limit buy rests at 0.975 x the UTC-day open and is re-priced in place daily. Once it fills, a GTC limit sell rests at buy x 8000/7800 (+2.564%). A 30-day safety clock cancels the sell and closes at market. Sizing is 50% of equity per cycle, with no leverage.

Verification Results

Verification failed (Layer 1 — static analysis): - Syntax error: invalid syntax. Perhaps you forgot a comma? (<unknown>, line 1) (line 1)

Iteration History

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Once the bugs are fixed the geometry is sound: the +2.56% target is ~13x the 0.20% spot round trip. The real question stays the one the hypothesis names: 30-day clock exits in trending declines against a high hit rate. ETH touches -2.5% below the UTC-day open on about 331 of 834 recent days, so the fixed book should produce a sample of hundreds of cycles. The sandbox's 1 trade / +1.03% says nothing about edge, because the run was cut……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Once the bugs are fixed the geometry is sound: the +2.56% target is ~13x the 0.20% spot round trip. The real question stays the one the hypothesis names: 30-day clock exits in trending declines against a high hit rate. ETH touches -2.5% below the UTC-day open on about 331 of 834 recent days, so the fixed book should produce a sample of hundreds of cycles. The sandbox's 1 trade / +1.03% says nothing about edge, because the run was cut short by the defect below. - [critical] Every buy fill places TWO full-size limit SELLs, which over-sells the spot CASH account and halts the backtest. I reproduced this on ETHUSDT.BINANCE_SPOT 1H from 2024-01-01. The buy filled at 2296.46 (ts 1704283200e9). on_order_filled() submitted SELL 21.7727 @ 2355.34. In the same bar, calculate_signal -> _manage -> _place_sell_for() submitted a SECOND SELL 21.7727 @ 2355.34. The guard `self._open_orders(OrderSide.SELL)` uses cache.orders_open(), which does NOT include an order that has just been submitted and is still in flight (SUBMITTED, not yet ACCEPTED), so the guard misses the first sell. When price reached 2355.33, both sells filled. The engine then stopped with `AccountBalanceNegative(balance=-21.7727, currency=ETH)` on 2024-01-08. That is a short on a CASH account, and the backtest ends at that point. This explains the sandbox: 3 fills (buy, sell, sell) and 1 closed trade over a ~20k-bar / ~833-day window where the buy level is touched on ~40% of days. The run died after its first cycle, and the full backtest would do the same. (line 140) — fix: Place the take-profit from exactly one code path. Either on_order_filled only, or _manage only. And/or guard with in-flight orders too: check `self.cache.orders_open(...) + self.cache.orders_inflight(...)` for the instrument/side, or track the sell's client_order_id in a scalar attribute and skip if it is set and not yet closed. Also cap the sell quantity at position.quantity minus the quantity of any SELL already open or in flight. - [critical] The daily cancel-and-re-arm is denied by the RiskEngine on the next day's re-arm while flat, so the pair does not re-arm daily as the hypothesis requires. `cancel_all_orders()` is followed immediately by `submit_order()` for a new buy sized at 50% of TOTAL USDT equity. The old buy's cancel has not yet released its locked balance. Reproduced: on 2024-01-02 00:00 the order was DENIED with NOTIONAL_EXCEEDS_FREE_BALANCE (free=49999.95 USDT, impact=49999.97 USDT). So the buy is armed roughly every other flat day, not every day. With equity_frac > 0.5 (the declared optimizer bound goes up to 1.0), every re-arm after an unfilled day is denied. That silently changes the sample the hypothesis defines. (line 130) — fix: Re-price the resting buy in place with `self.modify_order(order, quantity=..., price=...)` instead of cancel+new. Alternatively, size the new buy from the FREE balance, net of what the pending cancel releases. Or defer the new submit to on_order_canceled for the old buy. Keep the once-per-UTC-day schedule keyed on the bar timestamp. - [warning] The 30-day safety clock has the same race. `cancel_all_orders()` is followed immediately by `close_position()`, while the resting SELL still locks the full ETH quantity. On a CASH account the market close can be denied for insufficient free base balance. If the cancel is processed after the close fills, the cancelled sell cannot also fill, so this is not an over-sell. But the clock exit may slip by a bar. (line 117) — fix: On a clock exit, cancel the sell and submit the market close from on_order_canceled, or use modify_order to turn the exit into a marketable limit on the existing sell. - [warning] The anchor R is the close of the first bar seen in a new UTC day. If the 00:00-stamped bar is missing, R silently becomes a later hour's close, not the day open. This is minor, but it deviates from the 'UTC-day open' definition. (line 93) — fix: Take R only when ts % _DAY_NS == 0. Otherwise skip arming that day, or use bar.open of the first bar of the day.

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['sell_mult', 'buy_offset', 'equity_frac', 'min_notional', '_param_bounds', 'max_hold_days'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - steady_downtrend: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - flat_ranging: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - volatility_spike: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - zero_volume: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - price_gap: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000)

Outcome Summary

Before resubmitting an iteration, check that the artifact is complete, parseable Python (for example with ast.parse), because a syntax error at line 1 means the whole iteration fails before any economic test can run.

After 3 iterations the run was abandoned in a verification loop: the code failed Layer 1 static analysis with a syntax error at line 1 ('invalid syntax. Perhaps you forgot a comma?'). Backtesting, backtest review, optimization, analyst review and risk review never ran.

A port of FMZ's 'Buy then Sell' ping-pong strategy to ETHUSDT on Binance spot: each UTC day while flat, rest a limit buy 2.5% below the day's open and a limit sell +2.56% above the fill (the original 7800/8000 geometry), long-only and unleveraged, with 50% of equity per cycle and a 30-day market-exit safety clock.

No performance data exists: the backtest report is empty and no optimization was run, so there is no return, Sharpe or trade count.

Abandon Reason

verification_loop: Verification failed (Layer 1 — static analysis): - Syntax error: invalid syntax. Perhaps you forgot a comma? (<unknown>, line 1) (line 1)

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