Skip to content

View translation

WeeklyThursdaySeasonalityShortFundingFiltered

Hypotheses

Weekly Thursday Seasonality Short with Funding Filter

Hypotheses

Exploit weekly seasonality pattern where crypto tends to sell off on Thursdays, likely driven by Deribit options expiry hedging (Friday 08:00 UTC), weekly funding rate cycles, and institutional end-of-week risk reduction. Entry: Open short on Thursday (around 00:00-12:00 UTC) on Binance USD-M Futures, but ONLY when the current funding rate is positive (indicating crowded longs and liquidation potential). Exit: Close position Friday morning before options expiry (~08:00 UTC), or after a fixed 12-24h hold period. Stop-loss at 1.5-2% above entry to cap upside risk. Instruments: BTCUSDT and ETHUSDT on Binance Futures (MARGIN account). Can expand to other large-cap alts if the pattern holds. Supplementary data needed: funding_rates for entry filtering. Optionally open_interest to confirm elevated positioning. Expected edge: 0.2-0.5% avg per-trade return after fees (~0.10% round-trip). Low frequency (~1 trade/week per instrument) keeps fee drag minimal. Risk factors: Seasonality may be regime-dependent and decay over time. Thursday rallies during strong bull trends could trigger stops. Small sample size per year (~52 trades) requires multi-year validation.

Hypotheses

Iteration-4 fix (Layer 3 — sandbox smoke test exceeded 300s wall-clock). The funding lookup was already O(log n) via a precomputed sorted array + searchsorted, so the timeout came from unconditional per-bar overhead: _manage_all() ran on every primary AND extra bar and, for every asset on every bar, called get_account_equity(), cache.positions_open(), built a list, and constructed a tz-aware datetime — even on the ~99% of idle bars. Two smallest-possible changes eliminate that: (1) _wd_hour now derives weekday/hour with pure integer arithmetic (no datetime allocation, using the fact that 1970-01-01 was a Thursday=index 3); (2) a perf gate in _manage_all skips ALL cache/equity work unless the bar is inside the Thursday entry window or the asset is currently held (tracked in self._active, set on entry and cleared on exit). Idle bars now cost only a couple of integer ops, independent of history length. All signal/entry/exit logic, funding gating, sizing, caps and imports are unchanged, so Layers 1–2 remain green; correctness is preserved because positions only ever exist via our own entry path (which always adds to self._active), and held assets still run full exit checks every bar.

Hypotheses

failed deflated Sharpe: DSR=0.0, expected-max=3.9052 over 225 trials — the selected optimized Sharpe (0.801) sits FAR below the best-of-N luck bar and is statistically indistinguishable from noise (is_significant=false, sharpe_ci_low=-0.453 straddling 0; probabilistic_sharpe 0.9191 vs DSR 0.0 is the textbook PSR-vs-DSR selection-inflation trap). Decisively, sensitivity FAILED with 2 CALENDAR cliffs (entry_weekday, exit_weekday): the heatmap shows Sharpe of 0.65 exists ONLY at entry=Thursday(3)/exit=Friday(4) and inverts to -1.71/-3.81 (entry) and -2.00/-3.81 (exit) one day either side — the classic single-cell seasonality curve-fit, i.e. the Thursday-selloff premise is a fitted day-of-week coordinate, not a robust pattern. The walk-forward is is_overfitted=TRUE: avg IS 0.95 collapses to avg OOS -1.95 with OOS windows [0.83, -7.02, 0.34], a catastrophic middle fold. The holdout 'pass' is SPURIOUS — ratio 0 measured against a NEGATIVE WF-OOS baseline (-1.95), so holdout_sharpe 1.86 is not real forward validation. benchmark_meaningful=false, so on absolute metrics it still fails outright: Sharpe 0.65/0.80 << 1.5, max_dd 36%/24.7%, negative information ratio (-0.48/-0.54), max_drawdown_duration ~1085-1274 days. Not iterate (attempt 1 of 2): the only positive-Sharpe region IS the frozen calendar coordinate that constitutes the overfit, so there is no robust parameter region to tune toward and a second best-of-225 sweep re-selects the same single-cell fit. Not revise_hypothesis: per the overfit->abandon rule this is high-IS/negative-OOS overfitting on valid instruments (BTC/ETH), with no promoted sibling stranded on a dead target — re-pointing a day-of-week seasonality onto another day or asset just re-fits the calendar. FAILURE PATTERN: a day-of-week 'Thursday selloff' short gated by positive funding on BTC/ETH does NOT produce a promotable edge — the sensitivity surface exposes it as a single-cell calendar fit (Sharpe positive ONLY at Thu-in/Fri-out, strongly negative one day either side), and it fails every forward gate (DSR 0.0 with optimized Sharpe 0.80 below a 3.91 luck bar, is_overfitted with a -7.02 OOS window, a holdout 'pass' inflated by a negative WF-OOS baseline). A low market beta and a plausible microstructure narrative measure diversification and story, not significance or forward-generalization.

Implementation

Shorts the weekly Thursday seasonality on BTCUSDT and ETHUSDT (Binance USD-M, 1H bars), gated by positive funding (crowded longs). Enters SHORT during the Thursday 00:00-12:00 UTC window only when that asset's funding rate is positive; covers Friday 08:00 UTC before Deribit expiry, on a max-hold time-stop, or on a fixed-% stop above entry. Stop-distance-based sizing (risk_frac of equity), per-position notional cap, and a 1.0x gross cap (no leverage).

Verification Results

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).

Backtest Review

Large, clean sample: 722 short trades over 2020-2026, no liquidation, full history — trades match the hypothesis (all shorts, Thursday entry ~18h hold, funding-gated)

Backtest Review

Positive net-of-fees edge distributed across years: total_return 86.4%, CAGR 11.1%, PF 1.19, expectancy +$136/trade, positive in 5 of 7 years

Backtest Review

Low/negative beta (-0.15) and benchmark correlation (-0.37) — a genuine short-seasonality diversifier vs the long-biased pipeline

Backtest Review

3 clean tunables with real room to improve selectivity (funding_min, stop_pct, max_hold_hours)

Backtest Review

Thin edge: Sharpe 0.654 with sharpe_ci_low -0.65 (CI straddles 0), PF 1.19, information_ratio -0.48 — may not survive deflated-Sharpe/walk-forward

Backtest Review

Heavy cost drag: impact_cost_pct 17.8% of gross PnL and capacity_usd only $3.14M, commission 7.78% of gross — edge exists only at modest size

Backtest Review

Seasonality decay risk: 2024 negative (-8.8%), and the hypothesis itself flags regime dependence; OOS generalization is the real question

Backtest Review

High kurtosis (77) / skew 5.7 — some reliance on outlier short days (e.g. 2020-03-13 COVID crash +42.6%)

Analysis

Genuinely low/negative market beta (base -0.15, opt -0.11) and negative benchmark correlation (-0.37/-0.45), so the book is directionally short-biased as intended, not a closet long.

Analysis

Fee drag is modest (commission_pct_of_gross ~6.9-7.8%) and per-trade edge clears costs on paper; funding filter and gross cap are implemented cleanly.

Analysis

Sensitivity FAILED with 2 calendar cliffs (entry_weekday, exit_weekday): Sharpe of 0.65 exists ONLY at Thursday-in/Friday-out; one day either side collapses to -1.7 to -3.8 — the classic single-cell seasonality overfit signature.

Analysis

Deflated Sharpe = 0.0 (vs 0.95); optimized Sharpe 0.801 far below the 225-trial expected-max luck bar of 3.905; is_significant=false; sharpe_ci_low=-0.453 straddles 0.

Analysis

Walk-forward is_overfitted=TRUE: avg IS 0.95 collapses to avg OOS -1.95 with windows [0.83, -7.02, 0.34] — a catastrophic middle fold.

Analysis

Holdout 'pass' is spurious: ratio 0 measured against a NEGATIVE WF-OOS baseline (-1.95); not real forward validation.

Analysis

Absolute risk-adjusted metrics miss promotion: Sharpe 0.65/0.80 << 1.5, max_dd 36%/24.7%, negative information ratio (-0.48/-0.54), max_drawdown_duration ~1085-1274 days.

Outcome Summary

This strategy shorted a claimed Thursday sell-off in BTC/ETH, gated by positive funding, and unlike most siblings it survived the backtest-review gate ('optimize') on the strength of an 86.4% total return, PF 1.19, and a genuinely negative market beta. Optimization then exposed it: the positive Sharpe existed only at the frozen Thursday-in/Friday-out coordinate and inverted sharply one day either way, deflated Sharpe was 0.0 with the optimized Sharpe (0.80) far below a 3.91 luck bar, and walk-forward flipped from +0.95 in-sample to -1.95 out-of-sample. The analyst abandoned it after 4 iterations as a textbook single-cell calendar overfit with no robust parameter region to tune toward and no promoted sibling stranded on a dead target. It reached the ANALYZING stage but was never promoted to paper.

Outcome Summary

A day-of-week seasonality that is profitable only at one exact entry/exit calendar cell is a single-cell curve-fit, not a robust edge — low beta and a plausible microstructure story measure diversification and narrative, not statistical significance or out-of-sample generalization.

Outcome Summary

The analyst's post-optimization verdict was 'abandon': the sensitivity surface showed 2 calendar cliffs (positive Sharpe existed ONLY at Thursday-in/Friday-out, collapsing to -1.7 to -3.8 one day either side), deflated Sharpe was 0.0 against a 3.91 best-of-225 luck bar, and walk-forward was overfitted (avg IS 0.95 → avg OOS -1.95 with a -7.02 fold).

Outcome Summary

A low-frequency short-seasonality trade on Binance BTC/ETH that opened a short on Thursday (00:00–12:00 UTC) only when funding was positive (crowded longs) and covered Friday morning before Deribit expiry, betting on a recurring end-of-week sell-off.

Outcome Summary

The initial backtest looked promising over 722 short trades — total return 86.4%, CAGR 11.1%, profit factor 1.19, expectancy +$136/trade, and a diversifying beta of -0.15 — but the edge was thin (Sharpe 0.654 with CI straddling zero), and after optimization it held Sharpe just 0.801 while failing every forward-validation gate.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['assets', 'stop_pct', 'exit_hour', 'risk_frac', 'funding_min', 'exit_weekday', 'min_notional', 'entry_weekday', 'qty_precision', 'entry_end_hour', 'gross_cap_frac', 'max_hold_hours', 'entry_start_hour', 'max_position_frac', 'default_qty_precision'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000) - steady_downtrend: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000) - flat_ranging: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000) - volatility_spike: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000) - zero_volume: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000) - price_gap: TypeError: super(type, obj): obj must be an instance or subtype of type (bar timestamp: 1735689600000)

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['assets', 'stop_pct', 'exit_hour', 'risk_frac', 'funding_min', 'exit_weekday', 'min_notional', 'entry_weekday', 'qty_precision', 'entry_end_hour', 'gross_cap_frac', 'max_hold_hours', 'entry_start_hour', 'max_position_frac', 'default_qty_precision'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - steady_downtrend: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - flat_ranging: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - volatility_spike: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - zero_volume: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - price_gap: TypeError: WeeklyThursdaySeasonalityShortFundingFiltered._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000)
Strategy report

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