SolSmartMoneyPositioningMomentumLS
Hypotheses
SOL USD-M Smart-Money Positioning Momentum, Long-Short (Single-Instrument BINANCE Perp, 4h Bars + Multi-Year TOP-TRADER LONG/SHORT POSITION-RATIO Feed — FOLLOW Shifts in Large-Account Net Positioning, Price-Confirmed Continuation, 3-Parameter)
Hypotheses
A LONG-SHORT, single-instrument, mid-horizon strategy on SOLUSDT.BINANCE (USD-M perpetual, 4h bars) driven by a genuine NON-PRICE feed with confirmed multi-year history: the Binance top-trader LONG/SHORT POSITION ratio (large-account net positioning, distinct from the global/retail account ratio). Mechanism is smart-money FOLLOW (positioning momentum), NOT a contrarian fade: when the largest accounts are actively rotating their net exposure in one direction and price confirms, that shift tends to persist over the next 1-3 days. This deliberately abandons the taker-flow exhaustion-reversion family (which just died in verification_loop) and the crowd-DIVERGENCE fade family (which died at ~16 trades because divergence extremes are too rare). By firing on ordinary directional shifts in top-trader positioning rather than rare joint extremes, it targets a measurable sample (>100 trades). Single-name to avoid basket/cross-venue fee death; 3 parameters to resist overfitting.
Hypotheses
Iteration-3 fix targets the ONLY reported failure (Layer 2 synthetic: `TypeError: _bar_ts() takes 1 positional argument but 2 were given`). Root cause: the base `_bar_ts` is a @staticmethod, but the Layer-2 `_StrategyProxy` re-binds staticmethods as instance methods, so `self._bar_ts(bar)` passed 2 args to a 1-arg function. Smallest change: replaced that single call with an inline `getattr(bar, 'ts_event')`/`ts_init` read normalized through the existing `_to_ns` helper (handles the ms synthetic timestamps). No other logic, imports, parameters, or signal code changed — every earlier layer (Layer 1 static) stays green and the follow-momentum edge is untouched.
Hypotheses
Supplementary-gated data wall with an unmeasurable, single-regime sample (L73) — not worth 2 hours of optimization. Although the SOL primary bars span 2020-09 → 2026-09 (13,079 bars), every one of the 41 trades falls in a ~5-month 2026 window: annual_returns is only {2026: 14.29}, daily_returns run 2026-04-02 → 2026-09-02, and funding_events_available is 252 — the Binance top-trader position-ratio feed only covers a recent slice, the identical data wall to the taker-flow single-perp cases abandoned this batch. 41 trades in one regime is below the ~100-trade measurability floor (L10) and cannot populate a 3-window walk-forward plus 20% holdout, so optimization would fit noise. There is also no significant edge: Sharpe 0.35 with bootstrap CI [-0.48, 1.10] straddling zero, PSR 0.58, information_ratio -1.02 (underperforms buy-and-hold on a meaningful benchmark); the +14.8% is one favorable 2026 SOL regime. avg_trade_return_pct 0.76% clears fees and PF 1.44 is positive, but on a single-regime 41-trade sample that is indistinguishable from luck, and no parameter tuning can buy the calendar coverage the feed lacks. Failure pattern: L73 single-perp supplementary-gated directional with recent-only data window and unmeasurable sample.
Implementation
Long-short SOLUSDT.BINANCE USD-M perp on 4h bars driven by the Binance top-trader LONG/SHORT POSITION ratio (large-account net positioning). Decision variable is the z-scored shift of log(position_ratio) vs its own recent baseline. Enters BUY when the shift z >= entry_thresh AND price momentum over confirm_bars is positive (smart-money FOLLOW, price-confirmed); enters SELL on the mirror. Exits on positioning-momentum fade (z crosses back through exit_z), a hard stop, or a time-box. Goes flat when the ratio feed is stale (no price-only fallback).
Verification Results
CLEAN RESTART 2026-09-04 — this run's verdict history and learning records were removed and it was restarted from verification. Its previous abandonment came from the pipeline, not from the market: the Layer-2 harness mis-bound @staticmethod helpers (fixed), QA issued terminal performance verdicts on an unoptimized smoke test (removed — QA now judges correctness only), and sandbox timeouts came from backtest-slot starvation (fixed). The hypothesis and the strategy code are unchanged. Verify the code on its merits; performance is decided later by the full backtest and the optimizer.
Verification Results
Full-history sandbox produced only 41 trades over ~6 years, below the hypothesis's own >100-trade target. Joint gate (shift z>=1.0 AND same-signed price momentum) on a slow-moving ratio makes qualifying shifts rare. entry_thresh/confirm_bars are optimizer-tunable — resolve via coverage check + retune before trusting metrics.
Verification Results
position_size returns Decimal(str(notional/price)) with no rounding to SOL size precision (3). Trades executed in sandbox so the engine quantizes it, but full-float quantities are fragile vs strict size-precision enforcement. Round to 3 dp like sibling strategies.
Backtest Review
Genuine non-price feed (top-trader position ratio), clean single-name follow logic, positive per-trade edge (avg_trade_return_pct 0.76%, PF 1.44) and low drawdown (13.6%) within the traded window
Backtest Review
Supplementary data wall (L73): all 41 trades fall in a single ~5-month 2026 window (annual_returns only {2026}), despite 2020-2026 bars — the position-ratio feed only covers a recent slice
Backtest Review
Unmeasurable sample: 41 trades in one regime cannot support a 3-window walk-forward + holdout (below the ~100-trade floor, L10)
Backtest Review
No significant edge: Sharpe 0.35 (CI [-0.48, 1.10] straddling zero), PSR 0.58, information_ratio -1.02 (underperforms buy-and-hold); +14.8% is one favorable 2026 regime
Backtest Review
L73 single-perp supplementary-gated directional family — the same data-wall signature as the taker-flow cases abandoned this batch
Backtest Review
~5mo (2026 only)
Backtest Review
multi-year
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['exit_z', 'confirm_bars', 'entry_thresh', 'min_notional', 'max_hold_bars', 'position_frac', 'stop_loss_pct', 'ratio_lookback', 'max_ratio_lag_sec']
Check that __init__ sets all attributes from self.parameters.get().
- steady_uptrend: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- steady_downtrend: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- flat_ranging: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- volatility_spike: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- zero_volume: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- price_gap: TypeError: SolSmartMoneyPositioningMomentumLS._to_ns() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['exit_z', 'confirm_bars', 'entry_thresh', 'min_notional', 'max_hold_bars', 'position_frac', 'stop_loss_pct', 'ratio_lookback', 'max_ratio_lag_sec']
Check that __init__ sets all attributes from self.parameters.get().
- steady_uptrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- steady_downtrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- flat_ranging: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- volatility_spike: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- zero_volume: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
- price_gap: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735690200000)
Iteration History
Verification failed (Layer 4 — QA review):
- MEASURED (not estimated) under-firing: the sandbox ran on 363 days and produced only 10 trades — decisively below the ~100-trade measurability floor. Sharpe -0.046 with CI [-1.74, +1.45] straddling zero, PF 0.984, kurtosis 32.95 with two fat-tail prints dominating. This directly refutes the hypothesis's central premise: it abandoned the divergence-fade family that 'died at ~16 trades because extremes are too rare' and promised 'ordinary directional shifts → >100 trades.' It delivered 10 — worse than the family it was meant to fix.
- Signal-source key ('top_position_ratio') must be verified as Binance's topLongShortPositionRatio (positions), not the account or global/retail ratio the hypothesis explicitly excludes.
- avg_trade_return_pct 0.1543% barely clears the 0.15% floor but dollar expectancy is negative (-$13.2/trade) over only 10 trades — no statistical weight.
Backtest and paper results are hypothetical. Trading involves risk of loss.