Skip to content
Skip to content
← all research
REJECTED

"Edge proposal: CARRY × BASIS composite as a SIZING overlay on gate60"

2026-09-02

·

Date: 2026-09-02 Status: Research proposal (not yet implemented) Source of "this might work": the 13 crypto day-trading edges tested in reports/crypto_day_edge_test.md were ALL REJECTED as standalone signals, but every one of them carries real information. Standalone PFs were 0.10 to 1.21 with negative ret in the vast majority. None had a single tradable edge in isolation. What's never been tested: combining them as informationally-independent signals modulating a validated base signal.

The base signal here is gate60 (validated OOS Sharpe 2.48 with the depth-filter on, otherwise ~1.0). The overlay is CARRY × BASIS: a position-sizing multiplier that shrinks the gate60 position when the perp market is crowded/extended and grows it when funding/basis suggest the opposite side is paying you to be there.

#Why CARRY × BASIS

  • CARRY (carry_scores, Hyperliquid hourly funding) — extreme positive funding means longs pay shorts (crowded-long); extreme negative means the reverse. The signal is -z(annualized_funding) gated to |z| ≥ 1.5.
  • BASIS (basis_scores, Hyperliquid premium) — perp-vs-spot basis reversion. Extreme premium is a stretched basis; fade it. -z(premium) gated to |z| ≥ 2.0.

Both are informationally independent in construction (funding rate ≠ premium index, despite being related), and both have known positive carry properties at the strategy level: being SHORT crowded/extended perps has historically been profitable regardless of direction (you earn the funding/basis even if you're right or wrong about the move). The problem in the day-trading test was they were standalone — they tried to predict direction. As a sizing overlay on a slow trend (gate60), they say "the trend is right, but the entry is overextended; size down."

#Hypothesis (pre-registered)

When gate60 is long and CARRY and BASIS are both signalling the same side (e.g. both stretched to the long side), the gate60 long is entering a position that will pay funding AND pay to close the stretched basis. The expected short-term move is still positive (trend wins), but the immediate expected return is reduced. A position-sizing multiplier of 0.5x on those days improves risk-adjusted return without changing the directional thesis.

Conversely, when gate60 is long and CARRY+BASIS are stretched to the short side, gate60 is the funding-recipient (shorts pay longs). The immediate expected return is enhanced. A 1.25-1.5x multiplier on those days is appropriate.

#Construction (causal, no look-ahead)

Inputs:

  • gate60_target[t] ∈ {0, 1} from strategy.gate60.decide (already validated; lag-1 by construction).
  • carry_z[t] = -z(annualized_funding, look=168h, z_enter=1.5) from signals.crypto_edges.carry_scores (Hyperliquid cache).
  • basis_z[t] = -z(premium_index, look=168h, z_enter=2.0) from signals.crypto_edges.basis_scores (Hyperliquid cache).

Sizing multiplier s[t] ∈ {0.5, 1.0, 1.25}:

  • if gate60_target[t] == 0: s[t] = 0 (no position; overlay is inert)
  • elif carry_z[t] and basis_z[t] are both > 0 (both say "fade the long side", meaning longs are crowded): s[t] = 0.5 (half-size; you're paying funding into a stretched basis)
  • elif carry_z[t] and basis_z[t] are both < 0 (both say "fade the short side", shorts are crowded, longs collect funding): s[t] = 1.25 (boosted; you're collecting funding)
  • else (mixed signals or below z_enter): s[t] = 1.0 (no change)

The s[t] is a continuous scaling on the existing gate60 deploy fraction inside production/signal.py's effective_target and the runner's order sizing — no new strategy, no new entry/exit mechanics, just a multiplier.

#Validation protocol

The same protocol as the depth-filter study (validation/depth_filter_gate60.py):

  1. Window: 2023-01-15 to 2026-07-31 (the depth-filter study's window).
  2. ETH daily bars from validation.common.load_daily_bars("ETH"); BTC from same.
  3. Hyperliquid funding cache for carry_z, basis_z (both hourly).
  4. gate60 target via strategy.gate60.decide (frozen, unchanged).
  5. Returns: hold during day D → close_D/open_D - 1, matches the 00:15 UTC live cycle.
  6. Pre-registered primary metric: Sharpe ratio of (gate60 raw position × s[t]) vs (gate60 raw position, no overlay). Bootstrap p-value for "overlay beats baseline" with n=5000.
  7. Secondary metrics: total ret, maxDD, profit factor, fraction of days at each sizing bucket.
  8. Pre-registered gate: pass if OOS Sharpe improves by ≥ 0.10 AND bootstrap p < 0.05 AND maxDD does not increase by > 5pp absolute.
  9. Robustness: vary look ∈ {84h, 168h, 336h} and z_enter ∈ {1.0, 1.5, 2.0}; report full grid; do NOT pick the IS winner and then test it (that's exactly the look-ahead bug class from R11).

#Why this might fail (and what to do)

  1. Both signals are mostly below z_enter on a slow-trend window. Funding is mean-reverting; on a slow trend the funding doesn't get extreme enough. If < 30% of gate60-long days have CARRY or BASIS above their z_enter, the overlay will be inert and the experiment is a wash. → check this in the first 30 minutes of the validation run.
  2. CARRY and BASIS are too correlated in the post-2022 data. If corr(carry_z, basis_z) > 0.5, the composite is essentially one-dimensional and the threshold tuning becomes the look-ahead bug. → report the correlation in the study's first table.
  3. Sizing at 0.5x is the wrong magnitude. If the optimal is 0.0 (i.e. fully skip those days), that's a different (better?) study and should be a follow-up. If the optimal is 0.75, then the 0.5x is too conservative and vice versa.
  4. Multipliers don't compose with the depth-filter. The depth-filter is a separate skip-or-pass decision; the sizing overlay is what to do when the depth-filter passes. They are independent. Document the order of operations explicitly: depth-filter decides pass/skip; CARRY×BASIS decides size; gate60 decides direction.

#Code locations to touch (when implementing)

  • validation/carry_basis_overlay.py (new) — the validation study.
  • production/signal.py — no change; the overlay is upstream.
  • production/depth_filter.py — no change; orthogonal decision.
  • production/equity_fraction — a new field in the runner that multiplies the deploy fraction. The cleanest place is production/live_runner.py:_act, which already calls depth_filter_evaluate and uses effective_target. Add a sizing_overlay(raw_target, now, instrument) call right after, which returns a mult ∈ {0.5, 1.0, 1.25}. The deploy fraction is multiplied by mult before the order sizing math.
  • start_bots.py — register the env flag P_CARRY_BASIS_OVERLAY=0 (default off).
  • tests/test_carry_basis_overlay.py (new) — unit tests on the multiplier function (no-lookahead, fail-open on missing aux, threshold-bucket correctness, two-side symmetry).

#Why this is a good idea and not just another threshold grid

  • Every threshold grid in edge-hunt rounds 10-13 (acceleration, VRP, MAX, Halloween) was rejected because the "passes IS" was an artifact of threshold optimization on the same data. The protocol above pre-registers the thresholds (z_enter=1.5 and 2.0 from the day edge test's winners, and a robustness grid that is reported, not selected).
  • The composite combines two signals that are economically orthogonal (funding is a cost; basis is a price differential; they are related but not the same — the test will report the cross-correlation).
  • It is implemented as a SIZING overlay on a validated strategy, not as a standalone edge. This means even if the overlay is mildly positive, it lives inside the existing risk envelope and inherits the gate60's kill-switches and the depth-filter's skip logic.
  • The test can be run on the same 1292-day window the depth-filter study used, in < 5 minutes of compute. Cost is the binding constraint for the other "good ideas" (paid news history, options IV surface); this one is essentially free.

All trading described is paper (simulated). Not investment advice.

← all research