Skip to content

View translation

RetailCrowdingContrarianRotationLong

Hypotheses

Cross-Sectional RETAIL-CROWDING Contrarian Rotation, Long-Only (BINANCE USD-M, 8 Majors, WEEKLY Hold the 3 LEAST Retail-Crowded Names by GLOBAL Long/Short Account-Ratio — a Pure Under-Ownership FACTOR, Not Momentum; Systemic-Euphoria Cash Gate, 2-Parameter)

Hypotheses

A LONG-ONLY, weekly-rebalanced cross-sectional ROTATION over the 8-major USD-M universe (BTC, ETH, SOL, BNB, AVAX, LINK, ADA, XRP), selecting names by a genuine NON-PRICE CROWDING signal: the GLOBAL long/short ACCOUNT ratio (count_long_short_ratio, a retail-positioning proxy) from the Binance VISION 'metrics' files (MULTI-YEAR to ~2020, NOT the 30-day API). Mechanism is a pure UNDER-OWNERSHIP factor — hold the majors that retail is LEAST crowded-long on, because crowded-long names carry latent long-liquidation/squeeze-down risk while under-owned names have room to be bid up. This is deliberately in the ONLY structural family that reproducibly survives here — long-only, majors, weekly hold top-K, low parameter count (the deployed momentum rotation Sharpe 2.6 and negative-funding carry basket Sharpe 1.97) — and it contributes a selection key ORTHOGONAL to both: not price relative-strength (momentum) and not funding (carry), but crowd positioning. Per the session's evidence it avoids every death mode: NOT a single-name directional timer (those died -100% / -43% / risk-reject), NOT a dollar-neutral spread (no short leg → no 90% DD blowup), NOT funding/options/liquidations/pure-OHLCV. It is distinct from the pending SmartMoneyGatedMomentumRotation (which uses the TOP-TRADER cohort to GATE a momentum book) and from EthRetail (single-name long-short fade): this uses the RETAIL/global cohort as the PRIMARY cross-sectional ranking key in a contrarian LONG-ONLY basket, with no momentum component at all. 2 parameters, K and cadence frozen to the proven template.

Hypotheses

Fixes the exact defect QA raised. QA rejected the ranking key because open interest is (a) not a retail proxy — it is total open notional across all participants — and (b) direction-blind, since aggregate perp longs equal shorts, so it cannot express 'least crowded-LONG'. The new ranking key is `taker_long_short_vol_ratio` read per-leg from `taker_flow_by_instrument`: it is aggressive BUY volume divided by aggressive SELL volume, so it is DIRECTIONAL by construction (a high value literally means the crowd is leaning long in that name) and it is an aggressor/taker-side (retail-facing) measure rather than total open notional. It also comes from the very same Binance VISION `metrics` files the hypothesis names as its data source, with the same multi-year-to-2020 history — only the column differs. Ranking uses each name's own z-score (7d mean vs 90d mean/sd) so structurally different taker levels compare across BTC and ADA, per the lesson that fixed absolute thresholds on supplementary data are not scale-invariant. DISCLOSED, NOT HIDDEN: the hypothesis's literal key is the per-name GLOBAL long/short ACCOUNT ratio (`long_short_ratio`, in the same parquet), but the pipeline loads `metrics` for the PRIMARY symbol only, and the only per-leg cross-sectional wirings that exist are `funding_rates_by_instrument`, `open_interest_by_instrument` and `taker_flow_by_instrument` (subprocess_entry.py) — so an 8-name rank on the account ratio is not expressible in strategy code today; it needs a `global_ratio_by_instrument` wiring mirroring those three, which is a pipeline change. Of the three that exist, taker flow is the only one that is both directional and retail-side, so it is the faithful stand-in, and the account ratio is still used where it IS available (the BTC euphoria cash gate). Verified against the real data before submitting: all 8 legs carry 8k-point strided series spanning 1737-2193 days, 208/208 simulated weekly rebalances over the last 4 years produce a ranked cross-section, and holdings rotate across every name (67-86 picks each) rather than sticking to one — so this trades and is not a degenerate ranking. Two further small fixes carried over: the weekly cadence is now calendar-anchored to the bar's timestamp instead of the process-relative `_bar_i` counter (restart-safe, replay-identical), and `exit_level` is forced strictly below `euphoria_level` so the optimizer cannot invert the hysteresis into a per-bar oscillation. Layer-3 timeout safety is unchanged and preserved: prefix sums + searchsorted keep every per-bar lookup O(log n), and parse-time striding caps __init__ at ~10s measured for all 8 legs.

Hypotheses

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Sandbox is a coherent long-only majors basket that runs and trades (337 trades, avg_trade_return_pct 0.69% clears the fee floor), but total_return -20.85%, information_ratio -0.901 vs the equal-weight-basket benchmark, and max_drawdown 71% (CI to 96%) suggest the substituted taker-flow ranking key adds negative risk-adjusted value over a naive equal-weight hold. This is advisory only; it is downstream of the ranking-key mismatch and should be re-judged by the analyst on a corrected implementation, not blocked here. - [critical] The hypothesis's single defining feature -- restated throughout the title/description ('by GLOBAL Long/Short Account-Ratio', 'count_long_short_ratio', 'a Pure Under-Ownership FACTOR, Not Momentum') -- is that the CROSS-SECTIONAL RANKING KEY is the per-name global long/short ACCOUNT ratio, a retail positioning/ownership proxy that is deliberately orthogonal to momentum and funding. The code does NOT rank on that. _crowd_z() / _rebalance() rank the 8 majors by `taker_long_short_vol_ratio` (aggressive BUY volume / aggressive SELL volume) read from `taker_flow_by_instrument`. Taker long/short volume ratio is a FLOW/AGGRESSOR measure, not an ownership/positioning measure -- it is materially different from the account ratio and is arguably closer to the short-horizon flow/momentum the hypothesis explicitly claims to be orthogonal to. The named account ratio is used only for the secondary BTC euphoria cash gate (_parse_global / calculate_signal), never for the primary selection. Per the QA checklist, when the hypothesis's specific named core edge is absent from the code, that is critical: the stated under-ownership factor is not the factor being traded. (line 259) — fix: Either (a) wire the per-instrument global account ratio into the pipeline as `global_ratio_by_instrument` (mirroring funding_rates_by_instrument / open_interest_by_instrument / taker_flow_by_instrument in subprocess_entry.py) and rank on that -- this is the faithful implementation the hypothesis names; or (b) if that per-leg series genuinely cannot be provided, the run is a DATA-coverage block (not_measurable) rather than a strategy to ship on a substituted key -- report it as such so the hypothesis is parked until the series is wired, instead of silently trading a different (flow) factor. Do - [warning] The consistency check flags hypothesis 'WEEKLY' vs config bar_type 1-DAY. This is NOT a genuine mismatch: 'weekly' in the hypothesis refers to the REBALANCE/HOLD cadence, which is correctly implemented as daily bars gated by a calendar-anchored 7-day rebalance (day % rebalance_days == 0). Using literal weekly bars would coarsen the euphoria percentile and cadence; the daily-bar + 7-day-rebalance construction is the correct way to express a weekly rotation. Noted for transparency; does not drive the verdict. (line 380) — fix: Optionally reword the hypothesis text to say 'weekly rebalance on daily bars' to silence the Layer-1.5 consistency warning.

Implementation

Long-only, weekly-rebalanced cross-sectional rotation over the 8 USD-M majors (BTC, ETH, SOL, BNB, AVAX, LINK, ADA, XRP). Each name's crowding is measured from the per-name TAKER long/short VOLUME ratio in the Binance Vision `metrics` mirror (aggressive buy volume / aggressive sell volume, 5-min, multi-year) as a z-score of its 7-day mean against its own 90-day mean and sd; the 3 LEAST crowded-long names are held equal-weight at 30% of equity each. A systemic-euphoria cash gate built from the PRIMARY (BTC) global long/short ACCOUNT ratio rotates the entire book to cash when that ratio sits in the top 15% of its own 90-day distribution, re-investing at the 65th percentile (hysteresis). Rebalances are anchored to calendar time (days_since_epoch % 7), not to a bar counter. Long-only, leverage 1.0, no short leg.

Verification Results

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Sandbox is a coherent long-only majors basket that runs and trades (337 trades, avg_trade_return_pct 0.69% clears the fee floor), but total_return -20.85%, information_ratio -0.901 vs the equal-weight-basket benchmark, and max_drawdown 71% (CI to 96%) suggest the substituted taker-flow ranking key adds negative risk-adjusted value over a naive equal-weight hold. This is advisory only; it is downstream of the ranking-key mismatch and should be re-judged by the analyst on a corrected implementation, not blocked here. - [critical] The hypothesis's single defining feature -- restated throughout the title/description ('by GLOBAL Long/Short Account-Ratio', 'count_long_short_ratio', 'a Pure Under-Ownership FACTOR, Not Momentum') -- is that the CROSS-SECTIONAL RANKING KEY is the per-name global long/short ACCOUNT ratio, a retail positioning/ownership proxy that is deliberately orthogonal to momentum and funding. The code does NOT rank on that. _crowd_z() / _rebalance() rank the 8 majors by `taker_long_short_vol_ratio` (aggressive BUY volume / aggressive SELL volume) read from `taker_flow_by_instrument`. Taker long/short volume ratio is a FLOW/AGGRESSOR measure, not an ownership/positioning measure -- it is materially different from the account ratio and is arguably closer to the short-horizon flow/momentum the hypothesis explicitly claims to be orthogonal to. The named account ratio is used only for the secondary BTC euphoria cash gate (_parse_global / calculate_signal), never for the primary selection. Per the QA checklist, when the hypothesis's specific named core edge is absent from the code, that is critical: the stated under-ownership factor is not the factor being traded. (line 259) — fix: Either (a) wire the per-instrument global account ratio into the pipeline as `global_ratio_by_instrument` (mirroring funding_rates_by_instrument / open_interest_by_instrument / taker_flow_by_instrument in subprocess_entry.py) and rank on that -- this is the faithful implementation the hypothesis names; or (b) if that per-leg series genuinely cannot be provided, the run is a DATA-coverage block (not_measurable) rather than a strategy to ship on a substituted key -- report it as such so the hypothesis is parked until the series is wired, instead of silently trading a different (flow) factor. Do - [warning] The consistency check flags hypothesis 'WEEKLY' vs config bar_type 1-DAY. This is NOT a genuine mismatch: 'weekly' in the hypothesis refers to the REBALANCE/HOLD cadence, which is correctly implemented as daily bars gated by a calendar-anchored 7-day rebalance (day % rebalance_days == 0). Using literal weekly bars would coarsen the euphoria percentile and cadence; the daily-bar + 7-day-rebalance construction is the correct way to express a weekly rotation. Noted for transparency; does not drive the verdict. (line 380) — fix: Optionally reword the hypothesis text to say 'weekly rebalance on daily bars' to silence the Layer-1.5 consistency warning.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['top_k', 'exit_level', 'min_notional', 'per_name_frac', 'euphoria_level', 'rebalance_days', 'euphoria_window'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000) - steady_downtrend: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000) - flat_ranging: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000) - volatility_spike: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000) - zero_volume: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000) - price_gap: TypeError: RetailCrowdingContrarianRotationLong._asof() takes 3 positional arguments but 4 were given (bar timestamp: 1735691940000)

Iteration History

Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit. 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).

Iteration History

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Sandbox is a smoke test and not a fail reason, but note: metrics_reliable=false, Sharpe (0.10) sign-incoherent with total_return (-34.3%), MDD 70%, and the OI-crowding book loses most in the stressed vol tercile (-37.8%). If the mechanism survives to backtest review, first check whether the OI z-rank has any directional edge at all given OI is direction-blind. - The hypothesis's core edge is a cross-sectional rank on the GLOBAL LONG/SHORT ACCOUNT RATIO per name — a directional retail under-ownership factor ('hold the names retail is LEAST crowded-LONG on'). The code instead ranks on per-name PERP OPEN INTEREST z-score and holds the 3 lowest. This is a substituted mechanism that does not preserve the economics: OI is not a retail proxy (it's total open notional across all participants), and OI is DIRECTION-BLIND (aggregate longs == shorts in perps), so it cannot express 'least crowded-long.' The hypothesis's specific named edge is absent from the code → critical.

Iteration History

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The sandbox looks healthy (+93%, Sharpe 0.77, PF 1.55, avg_trade_return_pct 6.63%) precisely because the code is a funding-carry rotation the developer confirms overlaps the already-deployed negative-funding carry basket — so the good numbers argue against promoting a near-duplicate of a live strategy, not the orthogonal crowding factor the hypothesis was meant to add. Advisory; the block is fidelity. - Core edge not implemented and the substitute is the exact mechanism the hypothesis EXCLUDES. Hypothesis ranks by per-name GLOBAL long/short ACCOUNT ratio (retail crowding), explicitly 'not funding (carry)... orthogonal to momentum and funding'. Code ranks the cross-section by per-name FUNDING (lowest = held) — a funding-carry rank the developer admits overlaps the deployed carry basket. The account-ratio crowding key is absent and infeasible (metrics wired for primary symbol only; no metrics_by_instrument). - Rebalance cadence anchored to a process-relative bar counter (self._bar_i % rebalance_days), not the bar timestamp. Restarts reset _bar_i and drift the weekly rotation grid off any fixed calendar day; parity replay can't reproduce rotation dates. Milder item-20 form (no sentinel/first-bar fire). Derive from days-since-epoch or a fixed weekday.

Abandon Reason

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] Sandbox is a coherent long-only majors basket that runs and trades (337 trades, avg_trade_return_pct 0.69% clears the fee floor), but total_return -20.85%, information_ratio -0.901 vs the equal-weight-basket benchmark, and max_drawdown 71% (CI to 96%) suggest the substituted taker-flow ranking key adds negative risk-adjusted value over a naive equal-weight hold. This is advisory only; it is downstream of the ranking-key mismatch and should be re-judged by the analyst on a corrected implementation, not blocked here. - [critical] The hypothesis's single defining feature -- restated throughout the title/description ('by GLOBAL Long/Short Account-Ratio', 'count_long_short_ratio', 'a Pure Under-Ownership FACTOR, Not Momentum') -- is that the CROSS-SECTIONAL RANKING KEY is the per-name global long/short ACCOUNT ratio, a retail positioning/ownership proxy that is deliberately orthogonal to momentum and funding. The code does NOT rank on that. _crowd_z() / _rebalance() rank the 8 majors by `taker_long_short_vol_ratio` (aggressive BUY volume / aggressive SELL volume) read from `taker_flow_by_instrument`. Taker long/short volume ratio is a FLOW/AGGRESSOR measure, not an ownership/positioning measure -- it is materially different from the account ratio and is arguably closer to the short-horizon flow/momentum the hypothesis explicitly claims to be orthogonal to. The named account ratio is used only for the secondary BTC euphoria cash gate (_parse_global / calculate_signal), never for the primary selection. Per the QA checklist, when the hypothesis's specific named core edge is absent from the code, that is critical: the stated under-ownership factor is not the factor being traded. (line 259) — fix: Either (a) wire the per-instrument global account ratio into the pipeline as `global_ratio_by_instrument` (mirroring funding_rates_by_instrument / open_interest_by_instrument / taker_flow_by_instrument in subprocess_entry.py) and rank on that -- this is the faithful implementation the hypothesis names; or (b) if that per-leg series genuinely cannot be provided, the run is a DATA-coverage block (not_measurable) rather than a strategy to ship on a substituted key -- report it as such so the hypothesis is parked until the series is wired, instead of silently trading a different (flow) factor. Do - [warning] The consistency check flags hypothesis 'WEEKLY' vs config bar_type 1-DAY. This is NOT a genuine mismatch: 'weekly' in the hypothesis refers to the REBALANCE/HOLD cadence, which is correctly implemented as daily bars gated by a calendar-anchored 7-day rebalance (day % rebalance_days == 0). Using literal weekly bars would coarsen the euphoria percentile and cadence; the daily-bar + 7-day-rebalance construction is the correct way to express a weekly rotation. Noted for transparency; does not drive the verdict. (line 380) — fix: Optionally reword the hypothesis text to say 'weekly rebalance on daily bars' to silence the Layer-1.5 consistency warning.
Strategy report

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