From 14522262e6ff5f0965b10935874e8bf1437593a7 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Fri, 19 Jun 2026 15:16:03 +0000 Subject: [PATCH] =?UTF-8?q?chore(reset):=20v2.0.0=20=E2=80=94=20storico=20?= =?UTF-8?q?certificato=20Deribit=20mainnet,=20ripartenza=20pulita?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 + CLAUDE.md | 740 +++--------------- API_REFERENCE.md => Old/API_REFERENCE.md | 0 Dockerfile => Old/Dockerfile | 0 .../STRATEGIA_GRIGLIA.md | 0 docker-compose.yml => Old/docker-compose.yml | 0 {docs => Old/docs}/TODO.md | 0 {docs => Old/docs}/diary/2026-05-26.md | 0 {docs => Old/docs}/diary/2026-05-27.md | 0 .../diary/2026-05-28-honest-strategies.md | 0 {docs => Old/docs}/diary/2026-05-28.md | 0 .../docs}/diary/2026-05-29-exploration.md | 0 {docs => Old/docs}/diary/2026-05-29-shape.md | 0 {docs => Old/docs}/diary/2026-05-29.md | 0 .../docs}/diary/2026-05-31-exit-study.md | 0 .../docs}/diary/2026-05-31-live-status.md | 0 .../diary/2026-05-31-options-rejected.md | 0 .../diary/2026-05-31-ta-classic-rejected.md | 0 .../diary/2026-06-01-sh01-horizon-exit-bug.md | 0 .../2026-06-01-sh01-wiring-squeeze-bug.md | 0 .../docs}/diary/2026-06-01-tp-min-edge.md | 0 .../docs}/diary/2026-06-02-fade-lossguard.md | 0 .../diary/2026-06-02-fractal-argo-search.md | 0 .../docs}/diary/2026-06-04-exit-lab.md | 0 .../diary/2026-06-04-partial-tp-ladder.md | 0 .../diary/2026-06-04-shadow-divergence.md | 0 .../2026-06-05-confirm-sl-forming-bar.md | 0 .../diary/2026-06-05-sh01-sl-research.md | 0 .../diary/2026-06-06-improvement-sweep.md | 0 .../docs}/diary/2026-06-07-dip01-exit16.md | 0 .../docs}/diary/2026-06-07-pairspos-gate.md | 0 .../diary/2026-06-07-sh01-trainwindow.md | 0 .../docs}/diary/2026-06-07-sweep-fixes.md | 0 .../docs}/diary/2026-06-07-trendmax-gate.md | 0 ...026-06-08-dispersion-correlation-search.md | 0 .../2026-06-08-mr02eth-replace-search.md | 0 .../docs}/diary/2026-06-08-pairs-executor.md | 0 .../diary/2026-06-08-real-only-portfolio.md | 0 .../diary/2026-06-09-blind-traders-game.md | 0 .../diary/2026-06-09-blind-traders-game2.md | 0 .../2026-06-09-cerbero-bite-credit-spread.md | 0 .../diary/2026-06-09-pairs15m-live-path.md | 0 .../diary/2026-06-09-pairs15m-port06-gate.md | 0 .../diary/2026-06-09-pairs15m-robustezza.md | 0 .../diary/2026-06-09-stats-per-anno-real.md | 0 .../diary/2026-06-09-xs01-cross-sectional.md | 0 .../docs}/diary/2026-06-10-funding-carry.md | 0 .../diary/2026-06-10-grid-traders-game3.md | 0 .../diary/2026-06-10-real-truth-ledger.md | 0 .../diary/2026-06-10-xs01-dispersion-gate.md | 0 .../docs}/diary/2026-06-11-stability-sweep.md | 0 .../docs}/diary/2026-06-11-system-audit.md | 0 .../docs}/diary/2026-06-12-accel50.md | 0 .../docs}/diary/2026-06-12-fade-tf-sweep.md | 0 .../docs}/diary/2026-06-12-fade15m-gate.md | 0 .../diary/2026-06-12-improvements-sweep.md | 0 .../2026-06-12-resting-reconcile-feedgap.md | 0 .../docs}/diary/2026-06-12-xex-divergence.md | 0 .../docs}/diary/2026-06-13-dashboard.md | 0 .../2026-06-13-ledger-vs-backtest-report.md | 0 .../diary/2026-06-13-orphan-swap-guard.md | 0 .../2026-06-13-rebalance-conservation.md | 0 .../2026-06-14-timing-sweep-pairs-honest.md | 0 .../diary/2026-06-15-frozen-feed-gate.md | 0 .../diary/2026-06-16-inverted-tp-guard.md | 0 .../diary/2026-06-16-mainnet-fase0-smoke.md | 0 .../diary/2026-06-18-price-ladder-search.md | 0 {docs => Old/docs}/diary/README.md | 0 .../docs}/report/strategie_attive.html | 0 .../2026-05-27-multi-strategy-paper-trader.md | 0 .../docs}/specs/mainnet-microtest-plan.md | 0 ...05-29-portfolios-phase2B-honest-workers.md | 0 .../plans/2026-05-29-portfolios.md | 0 .../specs/2026-05-29-portfolios-design.md | 0 portfolios.yml => Old/portfolios.yml | 8 +- .../scripts/analysis}/__init__.py | 0 .../scripts}/analysis/_port06_gate_common.py | 0 .../scripts}/analysis/accel50_research.py | 0 Old/scripts/analysis/binance_fade_check.py | 100 +++ .../analysis/cerbero_bite_credit_spread.py | 0 Old/scripts/analysis/cerbero_probe.py | 77 ++ Old/scripts/analysis/clean_fade_rerun.py | 84 ++ Old/scripts/analysis/clean_feed.py | 274 +++++++ .../scripts}/analysis/combine_portfolio.py | 0 .../scripts}/analysis/combine_v2.py | 0 .../analysis/daily_equity_bfill_impact.py | 0 Old/scripts/analysis/deribit_history_plan.py | 146 ++++ .../scripts}/analysis/dip01_exit16_impact.py | 0 .../dispersion_edges/gate_index_comp_disp.py | 0 .../dispersion_edges/index_comp_disp.py | 0 .../dispersion_edges/rel_idio_fade.py | 0 .../scripts}/analysis/dispersion_lab.py | 0 .../scripts}/analysis/drift_monitor.py | 0 .../scripts}/analysis/eth_collar_gate.py | 0 .../scripts}/analysis/exit16_port06_impact.py | 0 {scripts => Old/scripts}/analysis/exit_lab.py | 0 .../exit_policies/01_trail_atr_ride.py | 0 .../exit_policies/02_trail_atr_keep_tp.py | 0 .../analysis/exit_policies/03_trail_pct.py | 0 .../analysis/exit_policies/04_breakeven.py | 0 .../analysis/exit_policies/05_ratchet.py | 0 .../analysis/exit_policies/06_sar_trail.py | 0 .../analysis/exit_policies/07_tp_decay.py | 0 .../analysis/exit_policies/08_sl_tighten.py | 0 .../exit_policies/09_tp_extend_momentum.py | 0 .../exit_policies/10_tp_moving_mean.py | 0 .../analysis/exit_policies/11_z_overshoot.py | 0 .../exit_policies/12_partial_tp_trail.py | 0 .../analysis/exit_policies/13_hurst_exit.py | 0 .../analysis/exit_policies/14_giveback.py | 0 .../exit_policies/15_loser_timestop.py | 0 .../exit_policies/16_close_confirm_sl.py | 0 .../exit_policies/17_wide_sl_trail.py | 0 .../analysis/exit_policies/18_swing_stop.py | 0 .../exit_policies/19_donchian_trail.py | 0 .../exit_policies/20_ema_cross_exit.py | 0 .../analysis/exit_policies/21_vol_rescale.py | 0 .../analysis/exit_policies/22_no_sl.py | 0 .../analysis/exit_policies/23_sl_tp_ride.py | 0 .../exit_policies/verify_02_overfit.py | 0 .../verify_02_trail_atr_keep_tp_leakage.py | 0 .../verify_02_trail_atr_keep_tp_stress.py | 0 .../exit_policies/verify_16_leakage.py | 0 .../exit_policies/verify_16_overfit.py | 0 .../exit_policies/verify_16_stress.py | 0 .../exit_policies/verify_22_no_sl_leakage.py | 0 .../exit_policies/verify_22_no_sl_overfit.py | 0 .../exit_policies/verify_22_no_sl_stress.py | 0 .../exit_policies/verify_tail_risk.py | 0 .../scripts}/analysis/explore_lab.py | 0 .../scripts}/analysis/fade15m_port06_gate.py | 0 .../scripts}/analysis/fade_diag_by_regime.py | 0 .../scripts}/analysis/fade_loss_by_regime.py | 0 .../analysis/fade_lossguard_port_test.py | 0 .../analysis/fade_lossguard_workflow.js | 0 .../scripts}/analysis/fade_tf_sweep.py | 0 .../analysis/fractal_argo_master_corr.py | 0 .../scripts}/analysis/fractal_argo_peryear.py | 0 .../analysis/fractal_argo_workflow.js | 0 .../analysis/funding_carry_research.py | 0 .../scripts}/analysis/grid_game_gate.py | 0 .../scripts}/analysis/honest_final.py | 0 .../scripts}/analysis/honest_improve.py | 0 .../scripts}/analysis/honest_improve2.py | 0 .../scripts}/analysis/honest_lab.py | 0 .../scripts}/analysis/honest_matrix.py | 0 .../scripts}/analysis/honest_rotation.py | 0 .../scripts}/analysis/honest_trend.py | 0 .../scripts}/analysis/intrabar_test.py | 0 .../scripts}/analysis/ladder_regate_clean.py | 0 .../scripts}/analysis/ladder_search.py | 0 .../scripts}/analysis/ladder_sltp_study.py | 0 .../scripts}/analysis/ledger_vs_backtest.py | 0 .../scripts}/analysis/leverage_sweep.py | 0 .../scripts}/analysis/live_exec_smoke.py | 0 .../scripts}/analysis/live_pairs_smoke.py | 0 Old/scripts/analysis/live_real_yearly.py | 74 ++ .../scripts}/analysis/live_shadow_smoke.py | 0 .../scripts}/analysis/live_smoke_pairs.py | 0 Old/scripts/analysis/mainnet_fade_check.py | 123 +++ .../scripts}/analysis/make_strategy_doc.py | 0 .../analysis/mr02eth_options.workflow.js | 0 .../scripts}/analysis/mr02eth_port06_gate.py | 0 .../analysis/mr02eth_search.workflow.js | 0 .../scripts}/analysis/oos_validation.py | 0 .../scripts}/analysis/option_overlay_lab.py | 0 .../scripts}/analysis/options_chain.py | 0 .../scripts}/analysis/options_fetcher.py | 0 .../scripts}/analysis/pairs15m_flatcheck.py | 0 .../scripts}/analysis/pairs15m_gate_final.py | 0 .../scripts}/analysis/pairs15m_live_smoke.py | 0 .../scripts}/analysis/pairs15m_port06_gate.py | 0 .../scripts}/analysis/pairs30m_gate.py | 0 .../scripts}/analysis/pairs_research.py | 0 .../scripts}/analysis/pairs_zstop_research.py | 0 .../analysis/pairspos_port06_impact.py | 0 .../scripts}/analysis/paper_status.py | 0 .../scripts}/analysis/partial_tp_ladder.py | 0 .../scripts}/analysis/projection_3y.py | 0 Old/scripts/analysis/real_dip_pairs_check.py | 137 ++++ .../scripts}/analysis/reconcile_account.py | 0 .../scripts}/analysis/regime_fetcher.py | 0 .../scripts}/analysis/regime_lab.py | 0 {scripts => Old/scripts}/analysis/report.py | 0 .../scripts}/analysis/report_families.py | 0 .../scripts}/analysis/reset_flatten.py | 0 .../scripts}/analysis/risk_management.py | 0 .../scripts}/analysis/sh01_exit_lab.py | 0 .../01_atr_fixed_intrabar.py | 0 .../02_atr_fixed_close_confirm.py | 0 .../sh01_exit_policies/03_pct_fixed.py | 0 .../sh01_exit_policies/04_chandelier_trail.py | 0 .../05_breakeven_ratchet.py | 0 .../sh01_exit_policies/06_giveback.py | 0 .../sh01_exit_policies/07_loser_timestop.py | 0 .../08_disaster_wide_close.py | 0 .../sh01_exit_policies/09_swing_stop.py | 0 .../sh01_exit_policies/10_vol_regime_stop.py | 0 .../11_disaster_wide_intrabar.py | 0 .../analysis/sh01_trainwindow_validate.py | 0 .../analysis/shape_analog_research.py | 0 .../analysis/shape_candle_research.py | 0 .../scripts}/analysis/shape_lab.py | 0 .../scripts}/analysis/shape_ml_research.py | 0 .../scripts}/analysis/shape_ml_validate.py | 0 .../scripts}/analysis/shape_pivot_research.py | 0 .../analysis/shape_template_research.py | 0 .../scripts}/analysis/sleeve_clustering.py | 0 .../scripts}/analysis/smoke_portfolio.py | 0 .../scripts}/analysis/strategy_research.py | 0 .../scripts}/analysis/strategy_research_v2.py | 0 .../analysis/timing_sweep_pairs_honest.py | 0 .../scripts}/analysis/trades_status.py | 0 .../analysis/trendmax_port06_impact.py | 0 .../scripts}/analysis/tsmom_research.py | 0 .../analysis/validate_fade_intrabar.py | 0 .../scripts}/analysis/validate_grid_worker.py | 0 .../analysis/validate_honest_workers.py | 0 .../analysis/validate_portfolio_runner.py | 0 .../scripts}/analysis/validate_worker_mr01.py | 0 .../analysis/validate_worker_pairs.py | 0 .../scripts}/analysis/validate_xsec_worker.py | 0 .../analysis/xex_divergence_research.py | 0 .../scripts}/analysis/xs01_dispersion_gate.py | 0 .../scripts}/analysis/xs01_tranche_gate.py | 0 .../analysis/xs01_tranche_research.py | 0 .../scripts}/analysis/xsec_port06_gate.py | 0 {scripts => Old/scripts}/games/agent_brief.py | 0 {scripts => Old/scripts}/games/arena.py | 0 {scripts => Old/scripts}/games/engine.py | 0 {scripts => Old/scripts}/games/grid_arena.py | 0 {scripts => Old/scripts}/games/grid_brief.py | 0 {scripts => Old/scripts}/games/grid_engine.py | 0 .../scripts}/games/opt_calibrate.py | 0 .../scripts}/games/options_arena.py | 0 .../scripts}/games/options_engine.py | 0 {scripts => Old/scripts}/games/run_game.py | 0 .../scripts}/games/session_arena.py | 0 .../scripts}/games/session_engine.py | 0 .../scripts}/portfolios/PORT01_honest.py | 0 .../scripts}/portfolios/PORT02_fade.py | 0 .../scripts}/portfolios/PORT03_master.py | 0 .../portfolios/PORT04_master_pairs.py | 0 .../portfolios/PORT05_master_esteso.py | 0 .../portfolios/PORT06_master_shape.py | 0 .../scripts/portfolios}/__init__.py | 0 {scripts => Old/scripts}/portfolios/_defs.py | 0 .../scripts}/portfolios/compare_all.py | 0 .../scripts}/portfolios/hourly_report.py | 0 .../scripts}/strategies/DIP01_dip_buy.py | 0 .../strategies/DIP01_dip_reversion.py | 0 .../strategies/FR01_hurst_calm_fade.py | 0 .../strategies/MR01_bollinger_fade.py | 0 .../scripts}/strategies/MR02_donchian_fade.py | 0 .../strategies/MR07_return_reversal.py | 0 .../scripts}/strategies/PORT01_portfolio.py | 0 .../scripts}/strategies/PORT02_fade_master.py | 0 .../scripts}/strategies/PORT03_all_master.py | 0 .../strategies/PR01_pairs_reversion.py | 0 .../strategies/ROT02_dual_momentum.py | 0 .../scripts}/strategies/SH01_shape_ml.py | 0 .../scripts}/strategies/TR01_ema_trend.py | 0 .../strategies/XS01_cross_sectional.py | 0 .../scripts}/waste/AD01_adaptive_squeeze.py | 0 .../waste/CM01_cross_market_momentum.py | 0 .../scripts}/waste/ML01_squeeze_gbm.py | 0 .../scripts}/waste/MR03_keltner_fade.py | 0 .../waste/MT01_squeeze_mtf_momentum.py | 0 .../waste/PD01_price_volume_divergence.py | 0 .../waste/REF_11_volatility_breakout.py | 0 .../waste/REF_13_squeeze_ml_hybrid.py | 0 .../waste/REF_s3_01_squeeze_improved.py | 0 .../scripts}/waste/REF_s3_02_lead_lag.py | 0 .../scripts}/waste/REF_s3_03_ultimate.py | 0 .../scripts}/waste/ROT01_xsect_rotation.py | 0 .../scripts}/waste/SQ01_squeeze_base.py | 0 .../waste/SQ02_squeeze_antifake_vol.py | 0 .../waste/SQ03_squeeze_all_filters.py | 0 .../scripts}/waste/SQ04_squeeze_ultimate.py | 0 .../scripts}/waste/W01_baseline.py | 0 .../scripts}/waste/W02_dtw_pattern.py | 0 {scripts => Old/scripts}/waste/W03_fourier.py | 0 .../scripts}/waste/W04_regime_fractal_ml.py | 0 .../scripts}/waste/W05_enhanced_fractal.py | 0 .../scripts}/waste/W06_structural_pattern.py | 0 {scripts => Old/scripts}/waste/W07_lstm.py | 0 .../scripts}/waste/W08_ensemble.py | 0 .../scripts}/waste/W09_walkforward.py | 0 .../scripts}/waste/W10_high_precision.py | 0 .../scripts}/waste/W11_mean_reversion.py | 0 .../scripts}/waste/W12_funding_rate.py | 0 .../scripts}/waste/W13_vol_selling.py | 0 .../scripts}/waste/W14_momentum_micro.py | 0 .../scripts}/waste/W15_gap_fade.py | 0 .../scripts}/waste/W16_iron_condor.py | 0 .../scripts}/waste/W17_vol_premium.py | 0 .../scripts}/waste/W18_vrp_honest.py | 0 .../scripts}/waste/W19_vrp_per_year.py | 0 .../scripts}/waste/W20_vrp_filtered.py | 0 .../scripts}/waste/W21_vrp_real_dvol.py | 0 .../scripts}/waste/W22_perpetual_only.py | 0 .../scripts}/waste/W23_inside_bar.py | 0 .../scripts}/waste/W24_donchian.py | 0 .../scripts}/waste/W25_squeeze_retest.py | 0 .../scripts}/waste/W26_mean_reversion_rsi.py | 0 .../scripts}/waste/W27_volume_spike.py | 0 .../scripts}/waste/W28_squeeze_mr.py | 0 {src => Old/src}/data/instruments.py | 0 {src/nn => Old/src/live}/__init__.py | 0 {src => Old/src}/live/bars.py | 0 {src => Old/src}/live/basket_trend_worker.py | 0 {src => Old/src}/live/books.py | 0 {src => Old/src}/live/cerbero_client.py | 0 {src => Old/src}/live/dashboard.py | 0 {src => Old/src}/live/execution.py | 0 {src => Old/src}/live/grid_worker.py | 0 {src => Old/src}/live/multi_runner.py | 0 {src => Old/src}/live/pairs_worker.py | 0 {src => Old/src}/live/paper_trader.py | 0 {src => Old/src}/live/rotation_worker.py | 0 {src => Old/src}/live/signal_engine.py | 0 {src => Old/src}/live/strategy_loader.py | 0 {src => Old/src}/live/strategy_worker.py | 0 {src => Old/src}/live/telegram_notifier.py | 0 {src => Old/src}/live/tsmom_worker.py | 0 {src => Old/src}/live/xsec_worker.py | 0 {src/portfolio => Old/src/nn}/__init__.py | 0 {tests => Old/src/portfolio}/__init__.py | 0 {src => Old/src}/portfolio/base.py | 0 {src => Old/src}/portfolio/ledger.py | 0 {src => Old/src}/portfolio/runner.py | 0 {src => Old/src}/portfolio/sleeves.py | 0 {src => Old/src}/portfolio/weighting.py | 0 {src => Old/src}/strategies/fade_base.py | 0 strategies.yml => Old/strategies.yml | 0 {tests/portfolio => Old/tests}/__init__.py | 0 Old/tests/portfolio/__init__.py | 0 .../tests}/portfolio/test_backtest_parity.py | 0 .../portfolio/test_backtest_parity_cap.py | 0 {tests => Old/tests}/portfolio/test_bars.py | 0 .../tests}/portfolio/test_basket_worker.py | 0 .../tests}/portfolio/test_capital_lineage.py | 0 .../tests}/portfolio/test_cerbero_v2.py | 0 .../tests}/portfolio/test_close_confirm_sl.py | 0 {tests => Old/tests}/portfolio/test_config.py | 0 .../tests}/portfolio/test_definitions.py | 0 {tests => Old/tests}/portfolio/test_dip01.py | 0 .../tests}/portfolio/test_feed_book_gap.py | 0 .../tests}/portfolio/test_freeze_gate.py | 0 .../tests}/portfolio/test_horizon_exit.py | 0 .../tests}/portfolio/test_hurst_lossguard.py | 0 .../tests}/portfolio/test_intrabar_exit.py | 0 .../tests}/portfolio/test_inverted_tp.py | 0 {tests => Old/tests}/portfolio/test_ledger.py | 0 .../tests}/portfolio/test_min_tp_frac.py | 0 .../tests}/portfolio/test_netting_close.py | 0 .../tests}/portfolio/test_pairs_execution.py | 0 .../portfolio/test_pairs_forming_bar.py | 0 .../tests}/portfolio/test_real_close_dsl.py | 0 .../tests}/portfolio/test_real_truth.py | 0 .../portfolio/test_reconcile_resting.py | 0 .../tests}/portfolio/test_rotation_worker.py | 0 .../tests}/portfolio/test_runner_build.py | 0 .../tests}/portfolio/test_runner_honest.py | 0 .../tests}/portfolio/test_runner_rebalance.py | 0 .../tests}/portfolio/test_sh01_last_block.py | 0 .../portfolio/test_single_leg_orphan.py | 0 .../tests}/portfolio/test_sleeves.py | 0 .../tests}/portfolio/test_stale_feed.py | 0 .../tests}/portfolio/test_tp_phantom.py | 0 .../tests}/portfolio/test_tsmom_worker.py | 0 .../portfolio/test_venue_lock_breaker.py | 0 .../tests}/portfolio/test_weighting.py | 0 .../tests}/portfolio/test_win_net_of_fees.py | 0 VERSION | 2 +- .../Libro_frattali.summary.md | 0 .../Pythagoras_Trading_Prediction.summary.md | 0 docs/diary/2026-06-19-deribit-history.md | 109 +++ scripts/analysis/audit_feed.py | 132 ++++ scripts/analysis/certify_feed.py | 259 ++++++ scripts/analysis/clean_feed.py | 140 ---- scripts/analysis/multi_source_check.py | 128 +++ scripts/analysis/rebuild_history.py | 213 +++++ 383 files changed, 1971 insertions(+), 779 deletions(-) rename API_REFERENCE.md => Old/API_REFERENCE.md (100%) rename Dockerfile => Old/Dockerfile (100%) rename STRATEGIA_GRIGLIA.md => Old/STRATEGIA_GRIGLIA.md (100%) rename docker-compose.yml => Old/docker-compose.yml (100%) rename {docs => Old/docs}/TODO.md (100%) rename {docs => Old/docs}/diary/2026-05-26.md (100%) rename {docs => Old/docs}/diary/2026-05-27.md (100%) rename {docs => Old/docs}/diary/2026-05-28-honest-strategies.md (100%) rename {docs => Old/docs}/diary/2026-05-28.md (100%) rename {docs => Old/docs}/diary/2026-05-29-exploration.md (100%) rename {docs => Old/docs}/diary/2026-05-29-shape.md (100%) rename {docs => Old/docs}/diary/2026-05-29.md (100%) rename {docs => Old/docs}/diary/2026-05-31-exit-study.md (100%) rename {docs => Old/docs}/diary/2026-05-31-live-status.md (100%) rename {docs => Old/docs}/diary/2026-05-31-options-rejected.md (100%) rename {docs => Old/docs}/diary/2026-05-31-ta-classic-rejected.md (100%) rename {docs => Old/docs}/diary/2026-06-01-sh01-horizon-exit-bug.md (100%) rename {docs => Old/docs}/diary/2026-06-01-sh01-wiring-squeeze-bug.md (100%) rename {docs => Old/docs}/diary/2026-06-01-tp-min-edge.md (100%) rename {docs => Old/docs}/diary/2026-06-02-fade-lossguard.md (100%) rename {docs => Old/docs}/diary/2026-06-02-fractal-argo-search.md (100%) rename {docs => Old/docs}/diary/2026-06-04-exit-lab.md (100%) rename {docs => Old/docs}/diary/2026-06-04-partial-tp-ladder.md (100%) rename {docs => Old/docs}/diary/2026-06-04-shadow-divergence.md (100%) rename {docs => Old/docs}/diary/2026-06-05-confirm-sl-forming-bar.md (100%) rename {docs => Old/docs}/diary/2026-06-05-sh01-sl-research.md (100%) rename {docs => Old/docs}/diary/2026-06-06-improvement-sweep.md (100%) rename {docs => Old/docs}/diary/2026-06-07-dip01-exit16.md (100%) rename {docs => Old/docs}/diary/2026-06-07-pairspos-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-07-sh01-trainwindow.md (100%) rename {docs => Old/docs}/diary/2026-06-07-sweep-fixes.md (100%) rename {docs => Old/docs}/diary/2026-06-07-trendmax-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-08-dispersion-correlation-search.md (100%) rename {docs => Old/docs}/diary/2026-06-08-mr02eth-replace-search.md (100%) rename {docs => Old/docs}/diary/2026-06-08-pairs-executor.md (100%) rename {docs => Old/docs}/diary/2026-06-08-real-only-portfolio.md (100%) rename {docs => Old/docs}/diary/2026-06-09-blind-traders-game.md (100%) rename {docs => Old/docs}/diary/2026-06-09-blind-traders-game2.md (100%) rename {docs => Old/docs}/diary/2026-06-09-cerbero-bite-credit-spread.md (100%) rename {docs => Old/docs}/diary/2026-06-09-pairs15m-live-path.md (100%) rename {docs => Old/docs}/diary/2026-06-09-pairs15m-port06-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-09-pairs15m-robustezza.md (100%) rename {docs => Old/docs}/diary/2026-06-09-stats-per-anno-real.md (100%) rename {docs => Old/docs}/diary/2026-06-09-xs01-cross-sectional.md (100%) rename {docs => Old/docs}/diary/2026-06-10-funding-carry.md (100%) rename {docs => Old/docs}/diary/2026-06-10-grid-traders-game3.md (100%) rename {docs => Old/docs}/diary/2026-06-10-real-truth-ledger.md (100%) rename {docs => Old/docs}/diary/2026-06-10-xs01-dispersion-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-11-stability-sweep.md (100%) rename {docs => Old/docs}/diary/2026-06-11-system-audit.md (100%) rename {docs => Old/docs}/diary/2026-06-12-accel50.md (100%) rename {docs => Old/docs}/diary/2026-06-12-fade-tf-sweep.md (100%) rename {docs => Old/docs}/diary/2026-06-12-fade15m-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-12-improvements-sweep.md (100%) rename {docs => Old/docs}/diary/2026-06-12-resting-reconcile-feedgap.md (100%) rename {docs => Old/docs}/diary/2026-06-12-xex-divergence.md (100%) rename {docs => Old/docs}/diary/2026-06-13-dashboard.md (100%) rename {docs => Old/docs}/diary/2026-06-13-ledger-vs-backtest-report.md (100%) rename {docs => Old/docs}/diary/2026-06-13-orphan-swap-guard.md (100%) rename {docs => Old/docs}/diary/2026-06-13-rebalance-conservation.md (100%) rename {docs => Old/docs}/diary/2026-06-14-timing-sweep-pairs-honest.md (100%) rename {docs => Old/docs}/diary/2026-06-15-frozen-feed-gate.md (100%) rename {docs => Old/docs}/diary/2026-06-16-inverted-tp-guard.md (100%) rename {docs => Old/docs}/diary/2026-06-16-mainnet-fase0-smoke.md (100%) rename {docs => Old/docs}/diary/2026-06-18-price-ladder-search.md (100%) rename {docs => Old/docs}/diary/README.md (100%) rename {docs => Old/docs}/report/strategie_attive.html (100%) rename {docs => Old/docs}/specs/2026-05-27-multi-strategy-paper-trader.md (100%) rename {docs => Old/docs}/specs/mainnet-microtest-plan.md (100%) rename {docs => Old/docs}/superpowers/plans/2026-05-29-portfolios-phase2B-honest-workers.md (100%) rename {docs => Old/docs}/superpowers/plans/2026-05-29-portfolios.md (100%) rename {docs => Old/docs}/superpowers/specs/2026-05-29-portfolios-design.md (100%) rename portfolios.yml => Old/portfolios.yml (88%) rename {scripts/portfolios => Old/scripts/analysis}/__init__.py (100%) rename {scripts => Old/scripts}/analysis/_port06_gate_common.py (100%) rename {scripts => Old/scripts}/analysis/accel50_research.py (100%) create mode 100644 Old/scripts/analysis/binance_fade_check.py rename {scripts => Old/scripts}/analysis/cerbero_bite_credit_spread.py (100%) create mode 100644 Old/scripts/analysis/cerbero_probe.py create mode 100644 Old/scripts/analysis/clean_fade_rerun.py create mode 100644 Old/scripts/analysis/clean_feed.py rename {scripts => Old/scripts}/analysis/combine_portfolio.py (100%) rename {scripts => Old/scripts}/analysis/combine_v2.py (100%) rename {scripts => Old/scripts}/analysis/daily_equity_bfill_impact.py (100%) create mode 100644 Old/scripts/analysis/deribit_history_plan.py rename {scripts => Old/scripts}/analysis/dip01_exit16_impact.py (100%) rename {scripts => Old/scripts}/analysis/dispersion_edges/gate_index_comp_disp.py (100%) rename {scripts => Old/scripts}/analysis/dispersion_edges/index_comp_disp.py (100%) rename {scripts => Old/scripts}/analysis/dispersion_edges/rel_idio_fade.py (100%) rename {scripts => Old/scripts}/analysis/dispersion_lab.py (100%) rename {scripts => Old/scripts}/analysis/drift_monitor.py (100%) rename {scripts => Old/scripts}/analysis/eth_collar_gate.py (100%) rename {scripts => Old/scripts}/analysis/exit16_port06_impact.py (100%) rename {scripts => Old/scripts}/analysis/exit_lab.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/01_trail_atr_ride.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/02_trail_atr_keep_tp.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/03_trail_pct.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/04_breakeven.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/05_ratchet.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/06_sar_trail.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/07_tp_decay.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/08_sl_tighten.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/09_tp_extend_momentum.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/10_tp_moving_mean.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/11_z_overshoot.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/12_partial_tp_trail.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/13_hurst_exit.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/14_giveback.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/15_loser_timestop.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/16_close_confirm_sl.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/17_wide_sl_trail.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/18_swing_stop.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/19_donchian_trail.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/20_ema_cross_exit.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/21_vol_rescale.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/22_no_sl.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/23_sl_tp_ride.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_02_overfit.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_02_trail_atr_keep_tp_leakage.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_02_trail_atr_keep_tp_stress.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_16_leakage.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_16_overfit.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_16_stress.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_22_no_sl_leakage.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_22_no_sl_overfit.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_22_no_sl_stress.py (100%) rename {scripts => Old/scripts}/analysis/exit_policies/verify_tail_risk.py (100%) rename {scripts => Old/scripts}/analysis/explore_lab.py (100%) rename {scripts => Old/scripts}/analysis/fade15m_port06_gate.py (100%) rename {scripts => Old/scripts}/analysis/fade_diag_by_regime.py (100%) rename {scripts => Old/scripts}/analysis/fade_loss_by_regime.py (100%) rename {scripts => Old/scripts}/analysis/fade_lossguard_port_test.py (100%) rename {scripts => Old/scripts}/analysis/fade_lossguard_workflow.js (100%) rename {scripts => Old/scripts}/analysis/fade_tf_sweep.py (100%) rename {scripts => Old/scripts}/analysis/fractal_argo_master_corr.py (100%) rename {scripts => Old/scripts}/analysis/fractal_argo_peryear.py (100%) rename {scripts => Old/scripts}/analysis/fractal_argo_workflow.js (100%) rename {scripts => Old/scripts}/analysis/funding_carry_research.py (100%) rename {scripts => Old/scripts}/analysis/grid_game_gate.py (100%) rename {scripts => Old/scripts}/analysis/honest_final.py (100%) rename {scripts => Old/scripts}/analysis/honest_improve.py (100%) rename {scripts => Old/scripts}/analysis/honest_improve2.py (100%) rename {scripts => Old/scripts}/analysis/honest_lab.py (100%) rename {scripts => Old/scripts}/analysis/honest_matrix.py (100%) rename {scripts => Old/scripts}/analysis/honest_rotation.py (100%) rename {scripts => Old/scripts}/analysis/honest_trend.py (100%) rename {scripts => Old/scripts}/analysis/intrabar_test.py (100%) rename {scripts => Old/scripts}/analysis/ladder_regate_clean.py (100%) rename {scripts => Old/scripts}/analysis/ladder_search.py (100%) rename {scripts => Old/scripts}/analysis/ladder_sltp_study.py (100%) rename {scripts => Old/scripts}/analysis/ledger_vs_backtest.py (100%) rename {scripts => Old/scripts}/analysis/leverage_sweep.py (100%) rename {scripts => Old/scripts}/analysis/live_exec_smoke.py (100%) rename {scripts => Old/scripts}/analysis/live_pairs_smoke.py (100%) create mode 100644 Old/scripts/analysis/live_real_yearly.py rename {scripts => Old/scripts}/analysis/live_shadow_smoke.py (100%) rename {scripts => Old/scripts}/analysis/live_smoke_pairs.py (100%) create mode 100644 Old/scripts/analysis/mainnet_fade_check.py rename {scripts => Old/scripts}/analysis/make_strategy_doc.py (100%) rename {scripts => Old/scripts}/analysis/mr02eth_options.workflow.js (100%) rename {scripts => Old/scripts}/analysis/mr02eth_port06_gate.py (100%) rename {scripts => Old/scripts}/analysis/mr02eth_search.workflow.js (100%) rename {scripts => Old/scripts}/analysis/oos_validation.py (100%) rename {scripts => Old/scripts}/analysis/option_overlay_lab.py (100%) rename {scripts => Old/scripts}/analysis/options_chain.py (100%) rename {scripts => Old/scripts}/analysis/options_fetcher.py (100%) rename {scripts => Old/scripts}/analysis/pairs15m_flatcheck.py (100%) rename {scripts => Old/scripts}/analysis/pairs15m_gate_final.py (100%) rename {scripts => Old/scripts}/analysis/pairs15m_live_smoke.py (100%) rename {scripts => Old/scripts}/analysis/pairs15m_port06_gate.py (100%) rename {scripts => Old/scripts}/analysis/pairs30m_gate.py (100%) rename {scripts => Old/scripts}/analysis/pairs_research.py (100%) rename {scripts => Old/scripts}/analysis/pairs_zstop_research.py (100%) rename {scripts => Old/scripts}/analysis/pairspos_port06_impact.py (100%) rename {scripts => Old/scripts}/analysis/paper_status.py (100%) rename {scripts => Old/scripts}/analysis/partial_tp_ladder.py (100%) rename {scripts => Old/scripts}/analysis/projection_3y.py (100%) create mode 100644 Old/scripts/analysis/real_dip_pairs_check.py rename {scripts => Old/scripts}/analysis/reconcile_account.py (100%) rename {scripts => Old/scripts}/analysis/regime_fetcher.py (100%) rename {scripts => Old/scripts}/analysis/regime_lab.py (100%) rename {scripts => Old/scripts}/analysis/report.py (100%) rename {scripts => Old/scripts}/analysis/report_families.py (100%) rename {scripts => Old/scripts}/analysis/reset_flatten.py (100%) rename {scripts => Old/scripts}/analysis/risk_management.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_lab.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/01_atr_fixed_intrabar.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/02_atr_fixed_close_confirm.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/03_pct_fixed.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/04_chandelier_trail.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/05_breakeven_ratchet.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/06_giveback.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/07_loser_timestop.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/08_disaster_wide_close.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/09_swing_stop.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/10_vol_regime_stop.py (100%) rename {scripts => Old/scripts}/analysis/sh01_exit_policies/11_disaster_wide_intrabar.py (100%) rename {scripts => Old/scripts}/analysis/sh01_trainwindow_validate.py (100%) rename {scripts => Old/scripts}/analysis/shape_analog_research.py (100%) rename {scripts => Old/scripts}/analysis/shape_candle_research.py (100%) rename {scripts => Old/scripts}/analysis/shape_lab.py (100%) rename {scripts => Old/scripts}/analysis/shape_ml_research.py (100%) rename {scripts => Old/scripts}/analysis/shape_ml_validate.py (100%) rename {scripts => Old/scripts}/analysis/shape_pivot_research.py (100%) rename {scripts => Old/scripts}/analysis/shape_template_research.py (100%) rename {scripts => Old/scripts}/analysis/sleeve_clustering.py (100%) rename {scripts => Old/scripts}/analysis/smoke_portfolio.py (100%) rename {scripts => Old/scripts}/analysis/strategy_research.py (100%) rename {scripts => Old/scripts}/analysis/strategy_research_v2.py (100%) rename {scripts => Old/scripts}/analysis/timing_sweep_pairs_honest.py (100%) rename {scripts => Old/scripts}/analysis/trades_status.py (100%) rename {scripts => Old/scripts}/analysis/trendmax_port06_impact.py (100%) rename {scripts => Old/scripts}/analysis/tsmom_research.py (100%) rename {scripts => Old/scripts}/analysis/validate_fade_intrabar.py (100%) rename {scripts => Old/scripts}/analysis/validate_grid_worker.py (100%) rename {scripts => Old/scripts}/analysis/validate_honest_workers.py (100%) rename {scripts => Old/scripts}/analysis/validate_portfolio_runner.py (100%) rename {scripts => Old/scripts}/analysis/validate_worker_mr01.py (100%) rename {scripts => Old/scripts}/analysis/validate_worker_pairs.py (100%) rename {scripts => Old/scripts}/analysis/validate_xsec_worker.py (100%) rename {scripts => Old/scripts}/analysis/xex_divergence_research.py (100%) rename {scripts => Old/scripts}/analysis/xs01_dispersion_gate.py (100%) rename {scripts => Old/scripts}/analysis/xs01_tranche_gate.py (100%) rename {scripts => Old/scripts}/analysis/xs01_tranche_research.py (100%) rename {scripts => Old/scripts}/analysis/xsec_port06_gate.py (100%) rename {scripts => Old/scripts}/games/agent_brief.py (100%) rename {scripts => Old/scripts}/games/arena.py (100%) rename {scripts => Old/scripts}/games/engine.py (100%) rename {scripts => Old/scripts}/games/grid_arena.py (100%) rename {scripts => Old/scripts}/games/grid_brief.py (100%) rename {scripts => Old/scripts}/games/grid_engine.py (100%) rename {scripts => Old/scripts}/games/opt_calibrate.py (100%) rename {scripts => Old/scripts}/games/options_arena.py (100%) rename {scripts => Old/scripts}/games/options_engine.py (100%) rename {scripts => Old/scripts}/games/run_game.py (100%) rename {scripts => Old/scripts}/games/session_arena.py (100%) rename {scripts => Old/scripts}/games/session_engine.py (100%) rename {scripts => Old/scripts}/portfolios/PORT01_honest.py (100%) rename {scripts => Old/scripts}/portfolios/PORT02_fade.py (100%) rename {scripts => Old/scripts}/portfolios/PORT03_master.py (100%) rename {scripts => Old/scripts}/portfolios/PORT04_master_pairs.py (100%) rename {scripts => Old/scripts}/portfolios/PORT05_master_esteso.py (100%) rename {scripts => Old/scripts}/portfolios/PORT06_master_shape.py (100%) rename {src/live => Old/scripts/portfolios}/__init__.py (100%) rename {scripts => Old/scripts}/portfolios/_defs.py (100%) rename {scripts => Old/scripts}/portfolios/compare_all.py (100%) rename {scripts => Old/scripts}/portfolios/hourly_report.py (100%) rename {scripts => Old/scripts}/strategies/DIP01_dip_buy.py (100%) rename {scripts => Old/scripts}/strategies/DIP01_dip_reversion.py (100%) rename {scripts => Old/scripts}/strategies/FR01_hurst_calm_fade.py (100%) rename {scripts => Old/scripts}/strategies/MR01_bollinger_fade.py (100%) rename {scripts => Old/scripts}/strategies/MR02_donchian_fade.py (100%) rename {scripts => Old/scripts}/strategies/MR07_return_reversal.py (100%) rename {scripts => Old/scripts}/strategies/PORT01_portfolio.py (100%) rename {scripts => Old/scripts}/strategies/PORT02_fade_master.py (100%) rename {scripts => Old/scripts}/strategies/PORT03_all_master.py (100%) rename {scripts => Old/scripts}/strategies/PR01_pairs_reversion.py (100%) rename {scripts => Old/scripts}/strategies/ROT02_dual_momentum.py (100%) rename {scripts => Old/scripts}/strategies/SH01_shape_ml.py (100%) rename {scripts => Old/scripts}/strategies/TR01_ema_trend.py (100%) rename {scripts => Old/scripts}/strategies/XS01_cross_sectional.py (100%) rename {scripts => Old/scripts}/waste/AD01_adaptive_squeeze.py (100%) rename {scripts => Old/scripts}/waste/CM01_cross_market_momentum.py (100%) rename {scripts => Old/scripts}/waste/ML01_squeeze_gbm.py (100%) rename {scripts => Old/scripts}/waste/MR03_keltner_fade.py (100%) rename {scripts => Old/scripts}/waste/MT01_squeeze_mtf_momentum.py (100%) rename {scripts => Old/scripts}/waste/PD01_price_volume_divergence.py (100%) rename {scripts => Old/scripts}/waste/REF_11_volatility_breakout.py (100%) rename {scripts => Old/scripts}/waste/REF_13_squeeze_ml_hybrid.py (100%) rename {scripts => Old/scripts}/waste/REF_s3_01_squeeze_improved.py (100%) rename {scripts => Old/scripts}/waste/REF_s3_02_lead_lag.py (100%) rename {scripts => Old/scripts}/waste/REF_s3_03_ultimate.py (100%) rename {scripts => Old/scripts}/waste/ROT01_xsect_rotation.py (100%) rename {scripts => Old/scripts}/waste/SQ01_squeeze_base.py (100%) rename {scripts => Old/scripts}/waste/SQ02_squeeze_antifake_vol.py (100%) rename {scripts => Old/scripts}/waste/SQ03_squeeze_all_filters.py (100%) rename {scripts => Old/scripts}/waste/SQ04_squeeze_ultimate.py (100%) rename {scripts => Old/scripts}/waste/W01_baseline.py (100%) rename {scripts => Old/scripts}/waste/W02_dtw_pattern.py (100%) rename {scripts => Old/scripts}/waste/W03_fourier.py (100%) rename {scripts => Old/scripts}/waste/W04_regime_fractal_ml.py (100%) rename {scripts => Old/scripts}/waste/W05_enhanced_fractal.py (100%) rename {scripts => Old/scripts}/waste/W06_structural_pattern.py (100%) rename {scripts => Old/scripts}/waste/W07_lstm.py (100%) rename {scripts => Old/scripts}/waste/W08_ensemble.py (100%) rename {scripts => Old/scripts}/waste/W09_walkforward.py (100%) rename {scripts => Old/scripts}/waste/W10_high_precision.py (100%) rename {scripts => Old/scripts}/waste/W11_mean_reversion.py (100%) rename {scripts => Old/scripts}/waste/W12_funding_rate.py (100%) rename {scripts => Old/scripts}/waste/W13_vol_selling.py (100%) rename {scripts => Old/scripts}/waste/W14_momentum_micro.py (100%) rename {scripts => Old/scripts}/waste/W15_gap_fade.py (100%) rename {scripts => Old/scripts}/waste/W16_iron_condor.py (100%) rename {scripts => Old/scripts}/waste/W17_vol_premium.py (100%) rename {scripts => Old/scripts}/waste/W18_vrp_honest.py (100%) rename {scripts => Old/scripts}/waste/W19_vrp_per_year.py (100%) rename {scripts => Old/scripts}/waste/W20_vrp_filtered.py (100%) rename {scripts => Old/scripts}/waste/W21_vrp_real_dvol.py (100%) rename {scripts => Old/scripts}/waste/W22_perpetual_only.py (100%) rename {scripts => Old/scripts}/waste/W23_inside_bar.py (100%) rename {scripts => Old/scripts}/waste/W24_donchian.py (100%) rename {scripts => Old/scripts}/waste/W25_squeeze_retest.py (100%) rename {scripts => Old/scripts}/waste/W26_mean_reversion_rsi.py (100%) rename {scripts => Old/scripts}/waste/W27_volume_spike.py (100%) rename {scripts => Old/scripts}/waste/W28_squeeze_mr.py (100%) rename {src => Old/src}/data/instruments.py (100%) rename {src/nn => Old/src/live}/__init__.py (100%) rename {src => Old/src}/live/bars.py (100%) rename {src => Old/src}/live/basket_trend_worker.py (100%) rename {src => Old/src}/live/books.py (100%) rename {src => Old/src}/live/cerbero_client.py (100%) rename {src => Old/src}/live/dashboard.py (100%) rename {src => Old/src}/live/execution.py (100%) rename {src => Old/src}/live/grid_worker.py (100%) rename {src => Old/src}/live/multi_runner.py (100%) rename {src => Old/src}/live/pairs_worker.py (100%) rename {src => Old/src}/live/paper_trader.py (100%) rename {src => Old/src}/live/rotation_worker.py (100%) rename {src => Old/src}/live/signal_engine.py (100%) rename {src => Old/src}/live/strategy_loader.py (100%) rename {src => Old/src}/live/strategy_worker.py (100%) rename {src => Old/src}/live/telegram_notifier.py (100%) rename {src => Old/src}/live/tsmom_worker.py (100%) rename {src => Old/src}/live/xsec_worker.py (100%) rename {src/portfolio => Old/src/nn}/__init__.py (100%) rename {tests => Old/src/portfolio}/__init__.py (100%) rename {src => Old/src}/portfolio/base.py (100%) rename {src => Old/src}/portfolio/ledger.py (100%) rename {src => Old/src}/portfolio/runner.py (100%) rename {src => Old/src}/portfolio/sleeves.py (100%) rename {src => Old/src}/portfolio/weighting.py (100%) rename {src => Old/src}/strategies/fade_base.py (100%) rename strategies.yml => Old/strategies.yml (100%) rename {tests/portfolio => Old/tests}/__init__.py (100%) create mode 100644 Old/tests/portfolio/__init__.py rename {tests => Old/tests}/portfolio/test_backtest_parity.py (100%) rename {tests => Old/tests}/portfolio/test_backtest_parity_cap.py (100%) rename {tests => Old/tests}/portfolio/test_bars.py (100%) rename {tests => Old/tests}/portfolio/test_basket_worker.py (100%) rename {tests => Old/tests}/portfolio/test_capital_lineage.py (100%) rename {tests => Old/tests}/portfolio/test_cerbero_v2.py (100%) rename {tests => Old/tests}/portfolio/test_close_confirm_sl.py (100%) rename {tests => Old/tests}/portfolio/test_config.py (100%) rename {tests => Old/tests}/portfolio/test_definitions.py (100%) rename {tests => Old/tests}/portfolio/test_dip01.py (100%) rename {tests => Old/tests}/portfolio/test_feed_book_gap.py (100%) rename {tests => Old/tests}/portfolio/test_freeze_gate.py (100%) rename {tests => Old/tests}/portfolio/test_horizon_exit.py (100%) rename {tests => Old/tests}/portfolio/test_hurst_lossguard.py (100%) rename {tests => Old/tests}/portfolio/test_intrabar_exit.py (100%) rename {tests => Old/tests}/portfolio/test_inverted_tp.py (100%) rename {tests => Old/tests}/portfolio/test_ledger.py (100%) rename {tests => Old/tests}/portfolio/test_min_tp_frac.py (100%) rename {tests => Old/tests}/portfolio/test_netting_close.py (100%) rename {tests => Old/tests}/portfolio/test_pairs_execution.py (100%) rename {tests => Old/tests}/portfolio/test_pairs_forming_bar.py (100%) rename {tests => Old/tests}/portfolio/test_real_close_dsl.py (100%) rename {tests => Old/tests}/portfolio/test_real_truth.py (100%) rename {tests => Old/tests}/portfolio/test_reconcile_resting.py (100%) rename {tests => Old/tests}/portfolio/test_rotation_worker.py (100%) rename {tests => Old/tests}/portfolio/test_runner_build.py (100%) rename {tests => Old/tests}/portfolio/test_runner_honest.py (100%) rename {tests => Old/tests}/portfolio/test_runner_rebalance.py (100%) rename {tests => Old/tests}/portfolio/test_sh01_last_block.py (100%) rename {tests => Old/tests}/portfolio/test_single_leg_orphan.py (100%) rename {tests => Old/tests}/portfolio/test_sleeves.py (100%) rename {tests => Old/tests}/portfolio/test_stale_feed.py (100%) rename {tests => Old/tests}/portfolio/test_tp_phantom.py (100%) rename {tests => Old/tests}/portfolio/test_tsmom_worker.py (100%) rename {tests => Old/tests}/portfolio/test_venue_lock_breaker.py (100%) rename {tests => Old/tests}/portfolio/test_weighting.py (100%) rename {tests => Old/tests}/portfolio/test_win_net_of_fees.py (100%) rename Libro_frattali.summary.md => docs/Libro_frattali.summary.md (100%) rename Pythagoras_Trading_Prediction.summary.md => docs/Pythagoras_Trading_Prediction.summary.md (100%) create mode 100644 docs/diary/2026-06-19-deribit-history.md create mode 100644 scripts/analysis/audit_feed.py create mode 100644 scripts/analysis/certify_feed.py delete mode 100644 scripts/analysis/clean_feed.py create mode 100644 scripts/analysis/multi_source_check.py create mode 100644 scripts/analysis/rebuild_history.py diff --git a/.gitignore b/.gitignore index 68567bb..67c6c66 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,7 @@ data/_reset_backup/ # game artifacts (log/json di scripts/games e gate) data/games/ .env.mainnet + +# archived data (mirrors top-level data/ ignores, which are top-level-anchored) +Old/data/ +Old/**/__pycache__/ diff --git a/CLAUDE.md b/CLAUDE.md index a7f7951..489b40e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,667 +1,133 @@ # PythagorasGoal — Istruzioni per agenti -## Panoramica +## Stato del progetto — v2.0.0 RESET (2026-06-19) -Progetto di ricerca: riconoscimento pattern frattali per trading algoritmico su criptovalute (BTC, ETH). L'obiettivo è arrivare a €50/giorno di profitto partendo da €1.000, entro 6–8 mesi. +**LEGGERE PRIMA DI TUTTO.** Il progetto è stato resettato dopo aver scoperto che l'intera +libreria di strategie "validata OOS" (FADE, PAIRS, DIP01, TR01, ROT02, TSM01, XS01, SH01) era +**artefatto di uno storico contaminato** — print fantasma del feed Cerbero **testnet** + storico +**Binance/USDT**. Ri-testate sul feed reale, tutte perdono ogni anno (vedi +`docs/diary/2026-06-19-deribit-history.md`, il documento di fondazione). + +Cosa è cambiato: +- Lo storico è stato **ricostruito da Deribit mainnet** e **certificato**. Universo affidabile = + **solo BTC/ETH** (tutti i TF). Gli alt sono esclusi (illiquidi/divergenti/non certificabili). +- Tutto il codice vecchio (strategie, stack live, ~100 script di ricerca/gate, dati non + certificati, 60+ diari) è **archiviato in `Old/`** (preservato in git, non cancellato). +- L'esecuzione è **DISABILITATA**, il conto mainnet è flat. **Non c'è trading live attivo.** +- Si riparte dalla ricerca di strategie NUOVE, su dati certi, con la metodologia qui sotto. + +## Obiettivo + +Ricerca: riconoscimento pattern frattali per trading algoritmico su crypto. Target dichiarato +€50/giorno partendo da €1.000. **Onestà prima di tutto**: nessun numero va creduto finché non è +netto fee, out-of-sample, robusto su griglia, e su dati certificati + liquidi + eseguibili. ## Stack -- **Linguaggio:** Python 3.11+ -- **Package manager:** uv (dipendenze in `pyproject.toml`, lock in `uv.lock`) -- **Dati:** Parquet in `data/raw/` (non committati, ~70 MB) -- **ML:** scikit-learn (GradientBoostingClassifier) -- **Analisi:** numpy, pandas, scipy -- **API dati:** Cerbero MCP su `cerbero-mcp.tielogic.xyz` (Deribit, Bybit, Hyperliquid), ccxt/Binance come fallback -- **Config:** pyyaml per `strategies.yml` +- **Linguaggio:** Python 3.11+ — **Package manager:** uv (`pyproject.toml`, `uv.lock`) +- **Dati:** Parquet in `data/raw/` (gitignored). Solo BTC/ETH (5m/15m/1h). +- **Analisi/ML:** numpy, pandas, scipy, scikit-learn +- **Fonte dati storici:** Deribit mainnet via `ccxt` (pubblico, tokenless) -## Struttura +## Struttura (post-reset) ``` -src/data/ → download e caricamento dati - downloader.py → download/caricamento parquet (gate: solo strumenti validati) - instruments.py → discovery + validazione strumenti per exchange, registry -src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py) -src/backtest/ → engine di backtesting (engine.py) -src/strategies/ → classe base Strategy ABC + indicatori condivisi - base.py → Strategy, Signal, BacktestResult, YearlyStats - indicators.py → keltner_ratio, detect_squeezes, ema, atr, rv, correlation -src/live/ → paper trading live multi-strategia - multi_runner.py → orchestratore: carica YAML (strategies + pairs), fetch candele, tick worker - strategy_worker.py → worker single-leg: capital, trade log, stato persistente. - Exit guidati da strategia (TP/SL/max_bars via Signal.metadata), - fallback hold_bars/stop -2%. Usa fee_rt della strategia. - pairs_worker.py → worker a 2 GAMBE per PR01 (market-neutral): long A / short B sullo - z-score del log-ratio, exit |z|<=z_exit o max_bars, fee su 2 gambe. - strategy_loader.py → import dinamico classi Strategy da scripts/strategies/ - cerbero_client.py → client HTTP per Cerbero MCP (Deribit testnet): dati + ordini - (place_order market/reduce_only, get_trade_history, get_positions) - execution.py → ExecutionClient: esecuzione REALE su Deribit (shadow). notional→ - amount (lineari USDC + inverse), open/close_amount reduce-only, - verifica sul trade (order_id), fee reali dai trades[] - signal_engine.py → squeeze + ML real-time (legacy ML01, ora in waste) + validazione OOS - telegram_notifier.py → notifiche Telegram per trade -src/portfolio/ → portafogli di prima classe (capitale-pool, backtest+live) - base.py → SleeveSpec, Portfolio (.backtest), load_active_portfolio - weighting.py → schemi pesi: equal/cap/inverse_vol/cluster_rp/manual - sleeves.py → builder unificato equity-per-sleeve (fonte unica, parità report) - ledger.py → PortfolioLedger: capitale/PnL/DD/persistenza+resume - runner.py → PortfolioRunner live (data Cerbero v2, sizing, ribilancio) -src/version.py → APP_VERSION (legge il file VERSION) — mostrato nei msg Telegram -src/strategies/fade_base.py → FadeStrategy + helper: atr, trend_distance, hurst_skip_mask (loss-guard), - exit close-confirm SL via param sl_confirm_atr (EXIT-16) -scripts/strategies/ → strategie con edge validato OOS: FADE (MR01/MR02/MR07), - HONEST (DIP01/TR01/ROT02), PAIRS (PR01), TSMOM + portafogli (PORT01/02/03); - FR01_hurst_calm_fade.py = record ricerca (robusto ma NON deployato) -scripts/portfolios/ → definizioni PORT01-06 (_defs.py) + report run() + hourly_report.py (Telegram) -scripts/waste/ → strategie scartate (W01-W28 + famiglia squeeze SQ/MT/ML/AD/CM/PD) -scripts/analysis/ → ricerca/validazione OOS fee-aware (strategy_research, oos_validation, ...); - regime_fetcher.py + regime_lab.py (DVOL/funding/feature regime per la ricerca); - exit_lab.py + exit_policies/ (harness ricerca exit: cache segnali, train/OOS); - options_fetcher.py + options_chain.py (storico opzioni REALE da cerbero-bite); - option_overlay_lab.py (overlay opzioni prezzato; mr02eth_port06_gate.py = gate swap-sleeve); - pairs15m_*.py (gate/flatcheck/smoke ETH/BTC 15m -> sleeve PR_ETHBTC_15M) -scripts/games/ → gioco "Blind Traders": 100 agenti ciechi su BTC/ETH anonimi (engine.py, - arena.py epoche+cull, agent_brief.py digest, run_game.py). Origine del BLEND 15m. - Varianti: options_* (strutture in opzioni, BS+skew+DVOL; opt_calibrate da - cerbero-bite), session_* (pattern orari), grid_* (griglie da STRATEGIA_GRIGLIA.md; - gate scripts/analysis/grid_game_gate.py). arena: GAME_NO_LIVE=1 vieta le - strategie gia' deployate (pairs, fade zscore/breakout/momentum) -scripts/bump_version.py, scripts/deploy.sh → versionamento e deploy (bump+commit+rebuild) -VERSION → versione semver (cotta nell'immagine, +1 ad ogni deploy) -strategies.yml → config multi-strategy paper trader -docs/diary/ → diario di ricerca giornaliero -docs/specs/ → specifiche di design -data/raw/ → file .parquet OHLCV (gitignored) | data/regime/ → DVOL+funding+feature (gitignored) -data/options/ → storico catena opzioni per-strike (bid/ask/IV/greche/OI) importato da cerbero-bite (gitignored) -data/instruments_registry.json → allowlist strumenti validati (gate del downloader) +src/data/downloader.py → load_data(asset, tf): legge i parquet certificati da data/raw/ +src/strategies/base.py → Strategy (ABC), Signal, BacktestResult, YearlyStats +src/strategies/indicators.py → indicatori condivisi (ema, atr, keltner, ...) +src/fractal/ → indicatori frattali (patterns.py, indicators.py, similarity.py) +src/backtest/engine.py → engine di backtesting riusabile +src/version.py → APP_VERSION (legge il file VERSION) +scripts/analysis/ → SOLO i tool dati certificati: + rebuild_history.py → (ri)costruisce lo storico da Deribit mainnet (base 5m + resample) + certify_feed.py → certifica il feed (integrità, coerenza resample, spike, cross-venue) + audit_feed.py → audit per-barra vs riferimento esterno + multi_source_check.py → cross-check multi-venue (quale venue è "vero") +data/raw/ → btc/eth × {5m,15m,1h} (gitignored). UNICO dato attivo. +data/instruments_registry.json → registry strumenti (reference) +docs/diary/ → diario di ricerca (1 voce: il reset; aggiungere dopo ogni esperimento) +Old/ → ARCHIVIO: tutto il vecchio (strategie, live, ricerca, dati, diari) +VERSION → semver (2.0.0) ``` ## Comandi ```bash -uv sync # installa dipendenze -uv run python -m src.data.downloader # scarica dati storici (solo strumenti validati) -uv run python -m src.data.instruments # (ri)costruisci il registry strumenti validati -uv run python scripts/strategies/MR01_bollinger_fade.py # backtest una strategia (es. fade) -uv run python scripts/strategies/PR01_pairs_reversion.py # backtest pairs market-neutral -uv run python scripts/analysis/strategy_research.py # ricerca strategie fee-aware OOS -uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata -uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie -uv run python scripts/analysis/validate_worker_pairs.py # replay worker 2 gambe == backtest -uv run python -m src.live.multi_runner # paper trading live multi-strategia (strategie + pairs) -uv run python scripts/portfolios/PORT06_master_shape.py # report backtest portafoglio (default) -uv run python -m src.portfolio.runner # paper trading a PORTAFOGLIO (capitale pool) -uv run python scripts/analysis/smoke_portfolio.py # smoke live data layer Cerbero v2 -uv run python scripts/analysis/live_exec_smoke.py # smoke ESECUZIONE reale (ordine→verifica→fee, testnet) -uv run python scripts/analysis/live_shadow_smoke.py # smoke catena shadow nel worker (open/close reali) -uv run python scripts/analysis/regime_fetcher.py # fetch DVOL+funding (Deribit mainnet) -> data/regime/ -uv run python scripts/analysis/exit_lab.py # (ri)costruisci cache segnali exit-lab + parity check -uv run python -m src.live.dashboard --port 8787 # dashboard web (servizio compose 'dashboard', porta 8787) -uv run python scripts/analysis/ledger_vs_backtest.py --since 2026-06-13 # report fuga di esecuzione (reale vs sim) -uv run python scripts/analysis/trades_status.py [--since ISO] # report "stato trades": POOL reale vs PAPER-STATS, realizzato + unrealized -./scripts/deploy.sh [patch|minor|major] # DEPLOY: bump versione + commit + rebuild Docker -uv run pytest # test +uv sync # installa dipendenze +uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # (ri)costruisci storico da Deribit mainnet +uv run python scripts/analysis/certify_feed.py # certifica i feed (locale + cross-venue) +uv run python scripts/analysis/certify_feed.py --local # solo check locali (veloce) +uv run pytest # test (da ripopolare con le nuove strategie) ``` -> **Dashboard web (`src/live/dashboard.py`, servizio compose `dashboard`, porta 8787).** -> Stdlib http.server, legge `data/`: equity (restyling), PnL totale e per-strategia, -> **grafico equity per famiglia**, trade attivi in tempo reale (entry/mark/PnL **REALI** — -> il feed sim-decisione testnet è dislocato, vedi sotto) + chiusi, modal per-strategia -> (curva reale vs sim + scheda `strategie_attive.html`), **area PAPER distinta** (equity -> propria dei 4 multi-asset), strategie **ritirate** marcate (staleness >30min) + versione -> sistema/strategia. Nessuna auth → solo rete interna. `docker compose up -d --build dashboard`. - -> **Cron host (monitoraggio, già schedulati):** `hourly_report.py` (orario), `drift_monitor.py` -> (07:15), `reconcile_account.py --telegram` (:40), **`ledger_vs_backtest.py --since 2026-06-13 -> --telegram` (08:30)** = il GATE per scalare il capitale (per gli sleeve eseguiti sim==backtest -> per costruzione → reale-vs-sim = fuga di esecuzione: slippage+fee+netting; verdetto 🟢/🟡/🔴). - -> **Deploy.** Il sorgente è **COPY nell'immagine Docker** (non montato) → `docker compose restart` -> NON ricarica il codice: serve **`docker compose up -d --build`** (o `./scripts/deploy.sh`, che bumpa -> la versione, committa e rebuilda). Il volume `data/` persiste → i worker fanno RESUME dello stato. -> La **versione** (file `VERSION`, semver, +1 ad ogni deploy via `deploy.sh`) compare nei messaggi -> Telegram (notifiche trade + report orario) → correli ogni msg al codice che l'ha generato. - -## Dati storici - -Scaricati e salvati localmente in Parquet. Per rigenerarli: - ```python -from src.data.downloader import download_all, load_data -download_all() # scarica BTC + ETH su 5m/15m/1h dal 2018 -df = load_data("ETH", "15m") # carica un asset/timeframe +from src.data.downloader import load_data +df = load_data("BTC", "1h") # OK. load_data("SOL", ...) -> FileNotFoundError (guardrail: solo dati certi) ``` -Fonte primaria: Cerbero MCP (endpoint `/mcp-deribit/tools/get_historical`). -Token observer: nel file `secrets/observer.token` del progetto CerberoSuite. +## IL DATO — fonte di verità (regola di prim'ordine) -### Strumenti & validazione (gate raccolta dati) +- **La verità è Deribit mainnet**, perché è dove (in futuro) eseguiamo. Cross-check multi-venue: + Deribit mainnet è a 0-1 bps dal consenso. **Binance NON è la verità** (è USDT, ~10 bps fuori, e + sotto depeg USDT fino al 3% off) → usare Binance/Coinbase SOLO come audit indipendente, mai come + ancora per "ripulire" i dati. +- **Aggiornare lo storico SOLO con `rebuild_history.py`** (ccxt Deribit mainnet, base 5m unica + + resample → coerenza interna garantita). **MAI** il vecchio downloader Cerbero (token testnet = + feed farlocco: è la causa della contaminazione). +- **Certificare sempre** dopo un rebuild con `certify_feed.py` (integrità OHLC, zero gap, coerenza + resample maxΔ≈0, spike = solo crash reali, accordo cross-venue per-anno vs Coinbase USD). -`src/data/instruments.py` scopre e **valida** gli strumenti per ogni exchange -implementato — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e **Bybit**, -il cui feed testnet è farlocco). Per ogni perpetuo enumera via `get_instruments` -/`get_markets` e verifica sui **dati storici realmente raccoglibili**: -esistenza, congruenza OHLC, not-flat (scarta contratti morti), liquidità (volume -daily) e **congruenza prezzo cross-exchange** (scostamento dalla mediana del -base-coin ≤ 5% → scarta outlier come `SOL-PERPETUAL`=9.6 vs SOL reale ~82). +### Universo ricercabile certificato +- **BTC / ETH**: puliti (2-6 bps vs Coinbase USD su tutta la storia), liquidi (~0% barre flat a 1h), + storia lunga (2018/2019→oggi) → **ogni timeframe (5m/15m/1h)**. È l'unico dato in `data/raw`. +- **Alt (SOL/XRP/ADA/LTC/DOGE/BNB): FUORI.** Illiquidi (LTC 5m 82% barre flat O=H=L=C, run fino a + ~3 giorni), divergenti (LTC/DOGE >1% su 10-21% delle barre 2022-23), o non certificabili + (XRP delistato da Coinbase per causa SEC; BNB non listato + storia da 2024-10). Sono archiviati in + `Old/data/raw`. Riammetterne uno richiede prima una ricertificazione che dimostri liquidità + accordo. -Output: `data/instruments_registry.json` (strumenti validi, timeframe, start-date). -**Gate:** `_download_cerbero_range` rifiuta gli strumenti non validati (override -`allow_unvalidated=True` solo per casi eccezionali). Rigenera con -`python -m src.data.instruments`. +## Metodologia obbligatoria per ogni nuova strategia -> **NB testnet.** Il token Cerbero punta a testnet; la congruenza cross-exchange -> è il filtro che distingue i feed realistici (Deribit, Hyperliquid) da quelli -> farlocchi (Bybit). Simboli Deribit: BTC/ETH = `-PERPETUAL` (inverse); -> alt = `_USDC-PERPETUAL` (lineari USDC). Registry attuale: Deribit 18/106, -> Hyperliquid 66/74 validi (major liquidi: BTC dal 2018, alt dal 2022). +1. **Ingresso eseguibile**: direzione e prezzo decisi con dati **fino a `close[i]`**, mai + `close[i-1]` con direzione presa da `i`; mai entry sull'estremo (high/low) di una candela. +2. **Backtest NETTO** dopo fee realistiche Deribit (**0.10% RT** taker; maker ~0%) + leva. +3. **Out-of-sample** held-out + robustezza su **griglia parametri** (entrambi gli asset, tutte le + celle positive) + **sweep fee** (0.00-0.20% RT, margine ampio). +4. **Liquidità & plausibilità** (lezione v2.0.0): incrociare ogni edge con la liquidità reale del + book (quota di barre flat) e con la plausibilità del prezzo (cross-venue). Un edge full+OOS + robusto su un book fermo o su wick fantasma NON è un edge. +5. Strategia in `scripts/strategies/` (codice univoco), test in `tests/`, diario aggiornato. -## Strategie attive +## Lezioni critiche (da NON ripetere — la storia di questo progetto) -> **LEZIONE CRITICA (2026-05-28).** L'intera famiglia squeeze-breakout (SQ01-04, -> MT01, ML01, AD01, CM01, PD01) è stata **scartata in `scripts/waste/`**: le -> accuratezze storiche 76-82% erano un **artefatto di look-ahead**. Quei backtest -> decidono la direzione con `sign(close[i]-close[i-1])` (la candela di breakout `i`) -> ma entrano a `close[i-1]` — cioè comprano *prima* della candela che usano per -> scegliere la direzione. Dal vivo il worker scopre il breakout solo a `close[i]` -> ed entra lì: l'edge sparisce (win-rate ~47%, lancio di moneta). Sotto ingresso -> onesto e fee reali **tutte perdono, anche a fee zero**. Inoltre i breakout -> *rientrano* (mean-reversion > continuation). Vedi `scripts/analysis/oos_validation.py` -> e `intrabar_test.py`. - -Tutte le strategie estendono `src.strategies.base.Strategy` -(`generate_signals() → backtest()`). Le strategie mean-reversion condividono -`src.strategies.fade_base.FadeStrategy` (backtest intrabar TP/SL/max_bars). -**Strategie con edge netto validato OOS fee-aware (tutte fade/mean-reversion):** - -| Codice | Nome | Meccanismo | Edge OOS netto (1h, fee 0.10% RT) | DD | Note | -|--------|------|-----------|-----------------------------------|----|------| -| **MR01** | Bollinger Fade | banda std attorno a SMA | BTC +201% / ETH +1238% | 15-72% | Fada la banda, TP alla media, SL ad ATR | -| **MR02** | Donchian Fade | estremi canale H/L | BTC +172% / ETH enorme | 30-42% | Fada la rottura del canale, TP al centro | -| **MR07** | Return Reversal | z dei rendimenti di barra | BTC +105% / ETH +195% | 25-46% | Fada il movimento estremo, exit in ATR; esposizione ~8% | - -> **MR03 Keltner Fade** spostata in `scripts/waste/`: era la fade più debole -> (BTC Sharpe 1.22, il filtro trend la peggiorava) e ridondante con MR01 (stessa -> idea di banda). Rimuoverla dal portafoglio ne ha *migliorato* le metriche. -> La funzione `keltner_fade` resta in `strategy_research_v2.py` come record. - -**Lezione confermata:** l'edge è sempre *mean-reversion* (i breakout rientrano). -Il trend-following (Donchian trend, RSI cross) e gli oscillatori senza filtro -(RSI revert, ADX-filtered fade) perdono netti → restano scartati. - -Ogni strategia è robusta su **tutta** la sua griglia parametri (entrambi gli asset -→ tutte positive OOS) e su **tutte** le fee 0.00-0.20% RT (margine ampio). -MR01 validato col worker reale: BTC +196% / ETH +251% OOS (nov 2023→mag 2026). -Ricerca completa: `scripts/analysis/strategy_research.py` (MR01) e -`scripts/analysis/strategy_research_v2.py` (MR02/MR03/MR07). -Validazione live-path: `scripts/analysis/oos_validation.py`. - -**Filtro trend (riduzione DD + aumento Acc).** Tutte le fade accettano i parametri -opzionali `trend_max` / `ema_long`: saltano i segnali quando il prezzo è troppo -esteso rispetto al trend di fondo (`|close − EMA(ema_long)| / ATR(14) > trend_max`), -cioè quando si starebbe fadando un trend/crollo estremo. Con `trend_max=3.0`, -`ema_long=200` (default in `strategies.yml`): accuratezza su tutti gli sleeve -e DD giù drasticamente su ETH (MR01 71%→26%, MR02 42%→25%, MR03 66%→34%, -MR07 46%→21%), edge OOS confermato (vedi `scripts/analysis/risk_management.py`). -Unica eccezione: MR03 BTC, dove il filtro peggiora entrambe → lasciato disattivo. -Leva non robusta scartate: vol-target sizing e skip-alta-volatilità (peggiorano). - -**SWAP filtri fade: hurst→trend (2026-06-07).** Il gate PORT06 sul path live -(`scripts/analysis/trendmax_port06_impact.py`, parità 1.00000 col canonico) ha mostrato che -**post-EXIT-16 il loss-guard Hurst è ridondante-dannoso**: EXIT-16 ha eliminato i wick-stop che -hurst evitava → gli ingressi saltati (66% delle barre!) sono in maggioranza tornati vincenti. -Su PORT06: LIVE hurst-only FULL Sh 7.23 / OOS 9.35-DD 1.68 vs **TREND-ONLY 7.89 / 9.91-DD 1.20** -(domina su tutte le metriche; hurst+trend insieme over-filtra: 7.11, metà dei trade; plateau -trend_max 2.5/3.0/3.5 robusto). TREND-ONLY è la config che la ricerca EXIT-16 aveva davvero -promosso (entries trend-filtrate, no hurst) e che il live non aveva mai eseguito. **Live ora: -`trend_max=3.0, ema_long=200` nelle 6 fade di `_defs.py`, `hurst_max` rimosso** (la maschera resta -in `fade_base`). Monitor: `hourly_report` traccia lo stop-rate per epoca PRE→HURST→TREND. -Lezione: ri-gateare ogni filtro quando cambia l'exit engine. Diario -`docs/diary/2026-06-07-trendmax-gate.md`. Il paragrafo sotto resta come record storico: - -**Loss-guard Hurst (storico: live dal 2026-06-02 al 2026-06-07, poi sostituito dal filtro trend).** Le fade accettano `hurst_max`: saltano i segnali in -regime PERSISTENTE/trending (rolling-Hurst ≥ soglia), dove si concentrano stop-loss e perdite -(diagnosi: stop-rate 43% per hurst>0.55 vs 21% anti-persistente; i peggiori 1% trade hanno hurst -medio 0.61). Helper `src.strategies.fade_base.hurst_skip_mask` (rolling-Hurst causale **dalle sole -close** → nessun feed esterno; step=6 per velocità live). **`hurst_max=0.55` attivo sulle 6 fade in -`_defs.py`**: il test decisivo a livello PORT06 lo conferma — **FULL DD 4.10%→2.39% (quasi dimezzato), -Sharpe 6.62→6.76, OOS Sharpe 8.89→9.15**. È l'UNICO meccanismo anti-perdite che supera il gate (ADX, -vol-expansion/vratio, efficiency-ratio, time-stop, vol-target FALLISCONO: tagliano i winner insieme ai -loser; i claim esterni ADX/ATR-ratio non si replicano su queste fade crypto). NB: il filtro agisce solo -sul path LIVE (`spec.params`); il backtest canonico (`build_everything`/regression-lock) NON è filtrato -→ il live farà MEGLIO del backtest sul DD. Ricerca: `scripts/analysis/fade_lossguard_workflow.js`, -diagnosi `fade_loss_by_regime.py`, diario `docs/diary/2026-06-02-fade-lossguard.md`. -**Effetto misurato (backtest):** stop-loss fade −67% in numero (1881→621), perdite totali −68%, coda -−61%→−48% (lo stop-RATE per-trade scende poco, 42→38%: il guard lavora riducendo l'ESPOSIZIONE nel -regime tossico, non rendendo sicuro ogni trade). **Monitor live:** `hourly_report.py` traccia lo -stop-rate fade PRIMA/DOPO l'attivazione (14:34 UTC del 2026-06-02) e dà il verdetto su Telegram quando -il campione DOPO ≥30 (già confermato: stop-rate live PRIMA 42% == backtest 42.1%). - -**FIX EXIT-16 live — confirm su barra COMPLETATA (2026-06-05).** Il worker valutava il confirm-SL -sul prezzo della candela IN FORMAZIONE ad ogni poll → reintroduceva la wick-sensitivity che EXIT-16 -elimina (audit: 2 stop su 3 del crash ETH erano wick-stop che il backtest non avrebbe preso in quel -momento). Ora `tick` valuta il confirm SOLO sul close dell'ultima barra completata (detection: la -riga -1 del df è la candela in corso finché `now < ts[-1]+bar_ms`), buf dall'ATR della stessa barra; -fill al prezzo corrente (≈ stress lag_close_exit, OK in exit-lab); TP intrabar invariato. La concausa -feed-gap NON è mitigabile lato exit (fill reali ≈ sim) e l'entry-guard post-flat è BOCCIATA (skippare -i segnali dopo barre flat PEGGIORA tutti gli sleeve ETH: la candela-gap è l'overshoot che la fade -fada). Aggiunto alert Telegram `STALE_FEED` (≥2 barre 1h flat → notifica + gap % al risveglio, solo -osservabilità). Diario `docs/diary/2026-06-05-confirm-sl-forming-bar.md`. - -**EXIT-16 close-confirm SL (ATTIVO LIVE, 2026-06-04; esteso a DIP01 il 2026-06-07).** Le fade E -DIP01 accettano `sl_confirm_atr` (live: 0.5 in `_defs.py`): lo SL **intrabar è disattivato** e lo stop scatta solo se il **CLOSE** della barra -sfonda `sl ∓ 0.5·ATR14`, con uscita al close (TP intrabar al livello e max_bars invariati; in modalità -confirm il TP ha priorità nel bar). Scoperta della ricerca exit-lab (34 agenti, 23 famiglie esplorate + -10 verifiche avversariali + test PORT06): **gli stop intrabar da wick sono falsi negativi** — l'overshoot -che buca lo stop e rientra è esattamente il movimento che la fade fada. Verificato: indipendente dal -loss-guard Hurst, plateau buffer 0.4-1.0, regge fee 2x/lag/slippage, coda ≈ base nei crash veri (FTX: -+2.4% vs −39% del no-SL puro). **PORT06: FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS Sharpe 8.82→10.06 DD -1.30→1.15.** La famiglia "cavalca il prezzo" (trailing/ride/partial-runner, 15 varianti) è invece tutta -SCARTATA: oltre il TP=media non c'è coda (4ª conferma). Collaterali: l'engine intrabar filla gli stop -"al livello" anche su gap-through (54% dei casi per stop tight) → bias PRO stop-stretti nelle ricerche -future; mai deployare strategie con `sl=0` (il fallback −2% del worker non si applicherebbe). Harness -riusabile `scripts/analysis/exit_lab.py` + policy in `exit_policies/`. Implementazione: `fade_base.backtest` -+ `StrategyWorker.tick` (param `sl_confirm_atr`, None = comportamento storico; il backtest canonico -`build_everything` resta NON filtrato → il live farà meglio del backtest, come per il loss-guard). -Diario `docs/diary/2026-06-04-exit-lab.md`. - -**Portafoglio.** Diversificare su sotto-conti indipendenti equipesati (le 4 strategie -× BTC/ETH, pos 0.15 ciascuno) abbatte il DD aggregato: ~14% full / ~10% OOS sul -paniere di 8 sleeve, contro il 20-70% del singolo. È la vera leva anti-drawdown. - -**Combinare le due famiglie (fade + honest).** Le fade (reversione intraday 1h) e le -honest (DIP/TR/ROT trend+rotazione multi-crypto) sono **quasi scorrelate** -(correlazione cross-famiglia ~0.05). Combinarle in un unico portafoglio migliora il -rischio/rendimento rispetto a ciascuna famiglia da sola: equal-weight dei 9 sleeve -→ DD 5.2% full / 4.7% OOS e Sharpe 4.23 full / 4.33 OOS (vs honest-only 12.6% DD / -2.20 Sharpe e fade-only 8.2% DD / 4.09 Sharpe), CAGR ~47% mantenuta. Studio in -`scripts/analysis/combine_portfolio.py`. - -**ROT02 — riduzione DD (top_k 2→3).** La rotazione dual-momentum honest concentrava -il book su 2 soli asset (DD 40%). Diversificare su 3 (`top_k=3`) dimezza quasi il DD -(40%→26%) e *alza* pure il ritorno full (+1095%→+1303%, ret/DD da 27 a 50); il -vol-target abbassa il DD ma sacrifica ritorno, quindi si tiene top_k=3 senza VT. -Applicato a `ROT02_dual_momentum.py` e a `_rot_daily_equity` (usata dai portafogli). - -**Portafogli pronti (artefatti accorpati e migliorati).** Oltre a `PORT01` (solo -honest), due script in `scripts/strategies/`: -- `PORT02_fade_master.py` — le 3 fade × BTC/ETH accorpate (6 sleeve, filtro trend), - equal-weight daily: DD ~8.2% full / 5.9% OOS, Sharpe 3.95/4.09, CAGR ~46%. -- `PORT03_all_master.py` — portafoglio MASTER (fade + honest, 9 sleeve). Due varianti: - `equal` (massimo Sharpe: DD 5.2%/4.7% OOS, Sharpe 4.23/4.33) e `5050` fra le due - famiglie (minimo DD: 5.0% full / 4.5% OOS). È la configurazione consigliata. -Come `PORT01`, sono meta-portafogli (script `run()` di report), non `Strategy` con -`generate_signals`, quindi non nel `strategy_loader`. - -**Esplorazione famiglie alternative (branch `strategy_explore`, 2026-05-29).** Esplorate -9 famiglie nuove con agenti paralleli su harness onesto condiviso -(`scripts/analysis/explore_lab.py`). 7 sono rumore (rifiutate: stagionalità oraria/mensile, -cross-sectional reversal, opening-range breakout, lead-lag BTC→alt, continuation intraday — -quest'ultima riconferma la dominanza mean-reversion). Due edge reali: -- **PR01 Pairs** (`scripts/strategies/PR01_pairs_reversion.py`): spread reversion - market-neutral sul log-ratio z-score, **config UNIVERSALE** `n=50 z_in=2.0 z_exit=0.75 - max_bars=72` (anti-overfit, niente tuning per-coppia). **5 coppie robuste**: ETH/BTC - (Sharpe 4.36), LTC/ETH (3.08), ADA/ETH (2.69), BTC/LTC (2.36, robusta anche 4h), ETH/SOL - (1.96, la più debole). Pattern: sempre alt-liquido vs major. Plateau confermato - (heatmap 20/20 Sharpe>1) + walk-forward (ETH/BTC 11/12 finestre+). **BNB/ETH scartata** - (overfit). Corr col mercato ~0.02-0.08. Fee su **2 gambe**: worker live implementato - (`src/live/pairs_worker.py`, sezione `pairs:` in `strategies.yml`). LOGICA validata - (`validate_worker_pairs.py`: replay == backtest ESATTO). LIVE (`live_smoke_pairs.py`, - smoke reale Cerbero): **tutte e 5 le coppie con feed live fresco**. Naming Deribit: - BTC/ETH = `-PERPETUAL` (inverse); alt = `_USDC-PERPETUAL` (lineari USDC, - storia dal 2022). Trappola: `LTC-PERPETUAL`/`SOL-PERPETUAL` danno vuoto/dati errati → - usare sempre `_USDC-PERPETUAL`. Resta da verificare solo liquidità/fill in esecuzione. - Verifica edge: `pairs_research.py`. -- **TSM01** (`scripts/analysis/tsmom_research.py`): TSMOM multi-orizzonte 3/6/12m + risk-off, - **gross 0.30**, distinto da ROT02 (corr 0.62), DD 15-22%, mai un anno negativo. Robusto - (36/36 config OOS+) ma diversificatore, non motore di ritorno (rende meno di ROT02). - -Aggiungere i **5 pairs** al MASTER (quasi scorrelati, ~0.02-0.09) è il free-lunch più -grande (`scripts/analysis/combine_v2.py`). **Numeri sobri onesti** (l'OOS singolo 2024-25 -è regime calmo → ottimistico ~50%): worst-DD su 90g rolling **~6%** (non 2.3%), Sharpe -atteso **~5** (mediana semestrale), ogni anno positivo dal 2021, regge **leva 2x + -slippage doppio** (CAGR 36%, Sharpe 5.1). Config robusta raccomandata: **MASTER-esteso -equal-weight, leva 2x, cap pairs ~30-35%** (i pairs sono ~57% del rischio; worker live a -2 gambe implementato, validato e con feed live su tutte e 5 le coppie — resta da -verificare liquidità/fill in esecuzione reale). La confluenza multi-TF è stata SCARTATA (overfit). - -**Pattern del segnale per FORMA (branch `shape_patterns`, 2026-05-29).** Esplorate 5 famiglie -di *shape forecasting* con agenti paralleli su harness onesto (`scripts/analysis/shape_lab.py`: -analog kNN causale, no-look-ahead verificato). **4/5 sono RUMORE** (riconfermano la dominanza -mean-reversion): analog kNN sulla forma grezza (solo BTC-overfit), encoding candele -UP/DOWN/DOJI+body/shadow (hit-rate ~50%), DTW+template geometrici (DTW *peggiora* l'euclidea; -template overfit), PIP/pivot/zig-zag (0/48 robuste). Vedi `scripts/analysis/shape_*_research.py`. -- **SH01 Shape-ML** (`scripts/strategies/SH01_shape_ml.py`): UNICO edge. Una LogisticRegression - legge 17 feature di forma (body/shadow, rendimenti, pendenza/curvatura, pos max/min, RSI, - estensione) e predice il segno del rendimento a H barre in **walk-forward** (scaler+modello - solo sul passato, no leakage). Config **W24 H12 th0.58**. A differenza dello squeeze - **regge fee 0.20% RT**. Win-rate ~50% → l'edge è nell'**asimmetria**, non nella frequenza. - Validazione (`scripts/analysis/shape_ml_validate.py`): BTC robusto OVUNQUE (expanding +219%/ - OOS +42% Sharpe 2.72 8-9 anni; rolling 2y +166%/+96%; stress 2x+slippage OK), ETH/ADA - robusti solo expanding (secondari), LTC/SOL/XRP scartati. Griglia: **5/27 celle robuste su - cresta stretta W24/H8-12** → overfit moderato, scelta la config conservativa. **Valore vero: - diversificatore** (corr +0.08 col MASTER); aggiungerlo migliora l'OOS del MASTER (Sharpe - 4.33→5.10, DD 4.7%→4.2%). NON motore standalone. **LIVE (2026-06-01): gira come StrategyWorker - reale** (vedi fix wiring sotto in SCOPE LIVE). Diario: `docs/diary/2026-05-29-shape.md`. - -**ARGO / GEX opzioni (analisi 2026-06-01, SCARTATO).** Valutato ARGO (lettura del gamma-exposure dei -dealer Deribit) come filtro di regime. Esito **NO-GO**: il net-GEX si calcola live (Deribit mainnet -public API, OI reale ~368k contratti, DVOL/funding storici gratis) ma **lo storico per-strike dell'OI -non è gratuito → non backtestabile OOS** (stesso muro delle opzioni W18/19/21). Niente evidenza crypto, -segno fragile, mercato dominato dai perp. Diario `docs/diary/2026-06-01-argo-gex-feasibility.md`. - -**Storico opzioni REALE da cerbero-bite (2026-06-09, il muro ARGO è caduto per il periodo recente).** -Il container **cerbero-bite** (accanto, `/opt/docker/cerbero-bite`) accumula in continuo lo **storico -per-strike** della catena opzioni Deribit (BTC+ETH) nella tabella `option_chain_snapshots` del suo -SQLite (`bite-data:/app/data/state.sqlite`, root-only): bid/ask/mid/**IV per-strike**/greche/OI/volume, -**dal 2026-05-01**, cadenza ~12 min (~110k righe/asset). È esattamente il dato che ARGO/W18-21 -credevano non-gratuito. `scripts/analysis/options_fetcher.py` lo importa via `docker exec` → -`data/options/{eth,btc}_chain.parquet`; `scripts/analysis/options_chain.py` (`OptionChain`) espone -loader + `skew_curve()` / `premium_levels()` (aggregati ROBUSTI) + `quote()` causale best-effort. -**Caveat granularità:** cerbero-bite snapshotta una **fetta rotante** (~1 scadenza per ciclo) → ottimo -per skew/premi **aggregati**, limitato per il pricing **per-trade** preciso (usare la finestra di -staleness). **Pannello regime `market_snapshots`** (anch'esso importato → `data/options/market_snapshots.parquet`, -loader `options_chain.load_market(asset)`; merge causale su prezzo via `options_chain.attach_market(df, asset)`): -feature REALI pre-calcolate — **spot, dvol, realized_vol_30d, iv_minus_rv (VRP), funding perp/cross, -`dealer_net_gamma` (net-GEX dealer!), `gamma_flip_level`, oi_delta_pct_4h, liquidation_long/short_risk**. -È il segnale ARGO/GEX che il progetto dichiarò non-backtestabile: ora accumula, reale e allineato (lo -`spot` elimina il proxy ATM). **Copertura reale:** spot/dvol dal 2026-03-26 (sparse/daily fino a fine -aprile), **net-GEX denso orario solo da ~2026-05-01** → ~5-6 settimane, **un singolo regime calmo**: -analisi esplorativa OK ma NIENTE edge validabile ora (i prior GEX/VRP non si replicano su 9 sett.; -`liquidation_risk` costante 'low'). Valore FORWARD: rivalutare quando il pannello attraversa -gamma-flip/liquidazioni/crash con held-out. **NB look-ahead:** usare `attach_market` (merge_asof -causale, NaN prima della copertura), MAI `astype(int64)` su un timestamp datetime (darebbe ns → -match all'ultimo snapshot = leak). **Numeri reali misurati (ETH):** skew put 10% OTM = **×1.1** (liquido, spread ~7%, NON ×1.6 -come sembrava da un singolo snapshot a 1g, illiquido); premio reale put 10% OTM ≈ **1.0%/mese** (0.65%/sett), -catastrofe 15%+ OTM ≈ 0.33%/mese. **Vincolo strutturale:** gli strike **10% OTM a 24h NON esistono** -(Deribit lista solo near-ATM sui tenor corti) → un overlay **per-trade a 24h è infattibile**; l'unica -struttura eseguibile è una **put settimanale/mensile standing** (catastrofe-cap di sleeve), da gateare -coi premi reali. Vedi `docs/diary/2026-06-08-mr02eth-replace-search.md`. - -**Frattali del segnale × Regime ARGO (ricerca 100 agenti, 2026-06-02, RECORD).** Cercata una strategia -che combini un segnale frattale (Hurst/Higuchi/Williams/analog) con un gate di regime (DVOL/VRP/funding). -Infrastruttura riusabile: `scripts/analysis/regime_fetcher.py` (DVOL+funding da Deribit mainnet → -**`data/regime/`**, NON `data/raw/` che è solo OHLCV) e `regime_lab.py` (feature regime+frattali causali, -cache, harness netto-OOS). Esito: **15 strategie robuste e causali, ma NESSUNA batte/migliora il PORT06** -(diversificatori sovrapposti alle fade). **Finding: il prior ARGO "VRP>0=range=fade" è SMENTITO** — -l'edge è su **VRP<0 + DVOL bassa**. Il vincitore `FR01_hurst_calm_fade.py` è robusto ma DILUISCE il -PORT06 (OOS Sharpe 8.89→8.72) → **non deployato** (in `scripts/strategies/` ma NON in MODULE_MAP/yml). -Il sottoprodotto utile è stato il **loss-guard Hurst** (vedi sopra), che invece MIGLIORA il PORT06. -Diario `docs/diary/2026-06-02-fractal-argo-search.md`. - -**Metodologia obbligatoria per ogni nuova strategia** (per non ripetere l'errore squeeze): -1. Ingresso eseguibile: direzione e prezzo decisi con dati **fino a `close[i]`**, mai `close[i-1]` con direzione da `i`. -2. Backtest **NETTO** dopo fee realistiche Deribit (**0.10% RT** taker, non 0.20%) + leva. -3. Validazione **out-of-sample** (held-out) + robustezza su griglia parametri + sweep fee. -4. Crea script in `scripts/strategies/`, aggiungi a `MODULE_MAP` (`strategy_loader.py`) e a `strategies.yml`. - -Strategie scartate storiche in `scripts/waste/` (W01-W28 + la famiglia squeeze). - -**Verso €50/giorno.** Con 4 strategie indipendenti (MR01/MR02/MR03/MR07) × 2 asset -(BTC/ETH) su €1000 ciascuna, il PnL medio storico aggregato è ben oltre €50/giorno; -ma quei numeri sono backtest a leva 3x su 8 anni e includono anni eccezionali (es. -ETH 2024). Stima onesta: il target è *plausibile* su un portafoglio diversificato di -queste fade, ma va confermato col paper trader live prima di rischiare capitale reale. - -## Portafogli - -- Un `Portfolio` è un oggetto di prima classe (`src/portfolio/`) con definizione (sleeve + schema pesi) e due facce sulla **STESSA** definizione: `.backtest()` (riusa il builder unico di `sleeves.py` → parità esatta con `report_families`) e live (`PortfolioRunner`: capitale pool condiviso, sizing per peso, ribilancio giornaliero, ledger aggregato in `data/portfolios/{code}/`). -- **Schemi peso:** `equal` (default), `cap` (tetto per famiglia, es. pairs 33% — config raccomandata), `inverse_vol`, `cluster_rp` (equal fra cluster naturali poi inverse-vol intra-cluster), `manual`. Definiti in `weighting.py`; la chiave cap è la famiglia (PAIRS/FADE/HONEST/SHAPE/TSM/XSEC). -- **Default `portfolios.yml`:** PORT06 (master+shape), `weighting=cap pairs 0.33 + shape 0.0588`, **leva 3x** (era 2x fino al 2026-06-12, vedi sotto), ribilancio 1D. Backtest PORT06 canonico (dati al 2026-05-28, pre-cap-shape): FULL Sharpe 6.47 DD 4.10% / OOS Sharpe 8.82 DD 1.30%; **con EXIT-16 close-confirm: FULL 7.84 / 2.60%, OOS 10.06 / 1.15%** (i vecchi 6.07/8.19 erano pre-loss-guard/pre-refresh dati). Col cap SHAPE (2026-06-05): FULL 6.43 / 3.96%, OOS 8.58 / 1.36% — assicurazione sulla coda SH01, vedi sotto. Col BLEND ETH/BTC 15m (2026-06-09, v1.1.16): FULL 7.20 / 3.68%, OOS 9.66 / 1.31% — 18 sleeve. Con XS01 (2026-06-09): OOS Sharpe 10.07, FULL DD 3.46 — 19 sleeve (pool live real-only 15; i 4 book multi-asset TR01/ROT02/TSM01/XS01 girano in statistica). **Con SWAP fade 1h→15m (config live attuale, v1.1.30, 2026-06-12, vedi sotto): FULL Sharpe 8.13 DD 2.47% / OOS Sharpe 10.86 DD 2.09%** (regression-lock `test_backtest_parity_cap`). -- **LEVA 3x (v1.1.29, 2026-06-12, scelta utente).** Era 2x ("sobrio") dal primo deploy live. Frontiera ACCEL50 (`scripts/analysis/accel50_research.py`): a Sharpe OOS ~10 il collo di bottiglia è la TAGLIA, non il rischio — lev 2→3 porta gli anni-a-€50/g da 3.3 a 1.9 con FULL DD 3.5→5.2% (modello lineare). PAIRS `position_size_family` 0.20→0.13 per CONSERVARE l'esposizione validata ~0.40 (la leva accelera le famiglie con stop, non i pairs no-stop). Diario `docs/diary/2026-06-12-accel50.md`. -- **SWAP fade 1h→15m (v1.1.30, 2026-06-12, scelta utente "swap secco").** Le 6 fade (MR01/02/07 × BTC/ETH) girano a **15m** invece di 1h: stessi `sid` (pesi/alloc/ledger intatti), `tf="15m"` in `_defs.FADE` + `combine_portfolio.FADE_TF` (le due facce backtest/live sulla stessa def). Gate `scripts/analysis/fade15m_port06_gate.py`: parametri 1h NON ri-tunati (anti-overfit), corr daily 15m↔1h media **0.26** (diversificazione vera, non lo stesso edge più veloce), edge ETH regge il flat-entry-skip (non stale-print). SWAP promosso: FULL 7.34→8.13 / OOS 10.07→10.86 (OOS DD 1.48→2.09 accettato). DIP01 resta 1h (non era nel gate). `max_bars=24` ora = 6h; EXIT-16 confirm sulla barra 15m completata. Caveat: MR02_BTC 15m è il più fee-sensitive (fee2x OOS Sh 0.60) → monitorare le fee reali. **TF sweep** (`fade_tf_sweep.py`): 1m/2m chiusi (fee-death + flat 13-26%), 5m no-swap (MR02_BTC muore a fee2x), 10m in watchlist (ADD bocciato: OOS 10.86→10.76). Diari `docs/diary/2026-06-12-fade15m-gate.md` / `-fade-tf-sweep.md`. -- **XS01 — Cross-Sectional Reversion (ATTIVO LIVE 2026-06-09; dispersion-gate v1.1.20, 2026-06-10).** - Famiglia nuova XSEC, distinta da pairs (pairwise) e fade (single-asset): ogni `hold=12` ore - classifica 8 crypto (UNIVERSE8) per rendimento a `lb=48` ore e va long i perdenti relativi / - short i vincenti (peso ∝ −(ret − media cross-section)), market-neutral gross 1, fee 0.20% - RT/book (turnover 2). Scorrelato ~0 da pairs e fade. FULL Sharpe ~3.3, plateau lb 12-72 × - hold 6-24; cost-sensitive (muore a ~0.35% RT/book). Gate PORT06: OOS Sharpe 9.66→10.07, - FULL DD 3.68→3.46. **Dispersion-gate** (`disp_min=0.0313` = p50 TRAIN): entra solo se la - std cross-section del momentum ≥ soglia — diagnostica monotona TRAIN+OOS, plateau p30-p70, - standalone Sharpe 2.50→3.46 (regge fee 2x), PORT06 OOS 10.07→10.37 a DD pari. Come - trend/hurst sulle fade, il gate agisce SOLO sul path live (backtest canonico non filtrato - → il live farà meglio del backtest). 8 gambe → PAPER (niente esecuzione reale a €2k). - Worker `CrossSectionalWorker` validato (`validate_xsec_worker`: replay == backtest esatto). - **PHASE-TRANCHING (2026-06-11, `tranches=3`)**: la fase del roll non-sovrapposto è arbitraria - e da sola muove Sharpe daily FULL 1.52-2.33 / DD 13.8-33% (timing-luck) → live gira con 3 - sub-book sfasati di hold/3 su capitale comune (PnL/K), ensemble di fase SENZA parametri - fittati. Gate `xs01_tranche_gate.py`: plateau K=2 E K=3 promossi (PORT06 OOS Sh 10.07→10.15, - DD 1.48→1.38, FULL pari); validatore esteso (K=1 == xsec_sim esatto; K=3 == unione fasi - 0/4/8 esatto). Solo path live (backtest canonico single-phase). Strategia - `scripts/strategies/XS01_cross_sectional.py`, gate `scripts/analysis/xsec_port06_gate.py` / - `xs01_dispersion_gate.py`. NB scartata nella stessa tornata: FC01 funding-carry (nessun - edge su 6.5 anni). Diari `docs/diary/2026-06-09/10-*`. -- **BLEND ETH/BTC 15m flat-skip (ATTIVO LIVE v1.1.16, 2026-06-09).** Sleeve `PR_ETHBTC_15M` accanto al 1h: pairs ETH/BTC a **15m** (n=66, z_in=1.674, z_exit=1.0, max_bars=35) a **mezza size** (`params.position_size=0.10`). **Origine: gioco "Blind Traders"** (100 agenti ciechi su BTC/ETH anonimizzati → riscoprono la mean-reversion, vincitore = spread ETH/BTC 15m; `scripts/games/`). Gate PORT06 sul serio: **non duplicato (corr 1h↔15m = 0.37)**, robusto (16/16 celle Sharpe>1), e l'edge **NON è artefatto delle candele flat ETH 15m** (16% storico; filtrandole resta l'83% dello Sharpe). Engine `pairs_research.pairs_sim_flat` con uscita **LIVE-REALIZABLE** (`flat_skip`: niente entry/exit su barre O=H=L=C, esce alla 1a barra pulita), **regression-lock** a `pairs_sim` con flat_skip=False. **Worker validato** (`validate_worker_pairs` a 15m: replay == backtest, 8452 vs 8453 trade; 1h byte-exact). Runner: fetch sub-orario diretto da Cerbero + `pos_for_spec` override **per-sleeve** (params.position_size > famiglia > globale). Mezza size perché a peso pieno il 15m pesava il 25.8% del rischio PORT06 (→ 11.5%, bilanciato col 1h) — blend-tilt prudente sul **caveat slippage** (a slippage realistico il vantaggio di Sharpe regge, quello di DD si assottiglia: il vero banco di prova è il ledger reale shadow). Diari `docs/diary/2026-06-09-pairs15m-*.md`, gate `scripts/analysis/pairs15m_port06_gate.py` / `pairs15m_gate_final.py` / `pairs15m_flatcheck.py`, smoke `pairs15m_live_smoke.py`. -- **SH01 SENZA STOP-LOSS — by design, CONFERMATO da ricerca (2026-06-05).** Dopo il crash ETH (−15.6% su un trade SH01 live), ricerca multi-agente con harness dedicato `scripts/analysis/sh01_exit_lab.py` (cache segnali walk-forward, engine con **fill gap-aware** `worse(livello, open)`, parity esatta con explore_lab, protocollo train≤2023-11-01/OOS): **11 famiglie di stop testate (ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop, disaster-cap close+intrabar, swing, vol-regime), 0 sopravvissute** al gate (ETH migliorato senza degradare BTC, train E oos, plateau). Pattern: ogni stop stretto abbastanza da toccare la coda ETH rompe BTC; ogni stop largo non arriva alla coda; nei crash il fill è al gap, non al livello (lo stop "protettivo" PEGGIORA la coda OOS). Mitigazione adottata: **cap famiglia SHAPE a 0.0588 in PORT06** (≈ dimezzata; costo OOS Sharpe −0.24, FULL DD −0.14pp) — la prossima coda impatta il conto per metà. NON impostare mai `sl`/`sl_confirm_atr` su SH_BTC/SH_ETH. Direzione futura: liquidity-gate sull'entry (skip dopo feed flat). Diario `docs/diary/2026-06-05-sh01-sl-research.md`. -- **Data layer Cerbero v2:** `get_historical_v2` unificato + `get_instruments` (naming robusto) + `get_ticker_batch`. Trading su Deribit. -- **SCOPE LIVE (fase 2 completata):** il runner esegue TUTTI gli sleeve di PORT06. Worker: single `StrategyWorker` (fade MR01/02/07, DIP01, **e SH01**), `PairsWorker` (PR01 2 gambe), e i multi-asset dedicati `BasketTrendWorker` (TR01 4h), `RotationWorker` (ROT02 1d), `TsmomWorker` (TSM01 1d). Il runner fetcha 1h da Cerbero v2 e **resampla a 4h/1d** (lookback dimensionato sui daily: TSM01 usa 252g). Validazione: runner pool/ribilancio/ledger == backtest (`validate_portfolio_runner.py`, identico); worker multi-asset == reference (`validate_honest_workers.py`: TSM01 esatto, ROT02 +1303% canonico, TR01 +32% vs +42% — residuo di convenzione capitale-unico vs media-equity; il bug `mean(rets)` sui soli asset in posizione, che dava −44%, è FIXATO il 2026-06-11: sovrappesava N/k a paniere parziale). -- **SH01 bootstrap full-history (punto-10, 2026-06-07).** La ri-validazione col train-window del - regime live (`sh01_trainwindow_validate.py`) ha mostrato che SH01 a train 365g **NON è robusto** - (BTC fee-2x FULL −42%, ETH Sharpe −0.02, trade-rate 22-26% vs 10% validato: LogReg over-confident, - soglia inerte — la diagnosi sweep era esatta). L'edge è MONOTONO nella memoria: solo l'expanding - full-history passa il gate. Fix live: il runner passa agli sleeve `ml` la storia FULL - (`_with_history`: parquet locale + feed, gap-guard con WARN) e `ml_wf_entries(last_block_only=True)` - fitta SOLO l'ultimo blocco del walk-forward → segnali **identici per costruzione** al WF completo - (parity test esatto), 0.6 s/tick su 73k barre. Manutenzione: tenere fresco il parquet - (`download_all()`). MAI ri-tunare la soglia nel regime corto (instabile/incoerente fra asset). - Diario `docs/diary/2026-06-07-sh01-trainwindow.md`. -- **FIX SH01 wiring (2026-06-01).** SH01 gira come **`StrategyWorker` NORMALE** (NON il vecchio `MLWorkerWrapper` di `multi_runner`, che usava il `SignalEngine` **squeeze SCARTATO**: apriva senza metadata ed usciva a `hold_bars=3`, ignorando del tutto SH01_shape_ml). `SH01_shape_ml.generate_signals` fa il walk-forward (retraining) internamente ad ogni tick ed emette `metadata.max_bars=12` → exit a orizzonte via `StrategyWorker.tick`. Serve ≥4000 barre 1h (`_ML_LOOKBACK_DAYS=365`). Vedi `docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md`. -- **Altri fix StrategyWorker (2026-06-01).** Exit a orizzonte puro per strategie senza TP/SL (`elif self.max_bars`, SH01 esce a H=12 non hold_bars=3); `is_win = net > 0` (win NETTO fee, non lordo); filtro `min_tp_frac` (salta micro-scalp col TP entro le fee); loss-guard `hurst_max=0.55` sulle fade (vedi sopra). -- **Exit intrabar (fase 3, risolto):** lo `StrategyWorker` ora esce sui TP/SL toccati INTRABAR (high/low della barra, al livello, SL prioritario) come il backtest — non più solo sul close. Allinea fade/DIP01 live al backtest intrabar (`tests/portfolio/test_intrabar_exit.py`). Caveat residuo onesto: nel paper trading l'high/low usato è quello della barra in corso al poll; su un fill reale conterebbe il momento del tocco. -- **ESECUZIONE REALE — pairs 2 gambe + SH01 (2026-06-08).** Estesa oltre i fade: i **5 pairs PR01** - eseguono reale a 2 gambe (`PairsExecutionClient`: open/close long A/short B, leg-risk unwind, MAI - close_position; `pairs_enabled: true` acceso a conto flat, v1.1.12); **SH01** (BTC/ETH) esegue - single-leg con exit a orizzonte H=12 — niente TP/SL, `_place_real_tp` no-op e `_real_close` chiude - tutto market reduce-only, disaster-bracket on-book come unica protezione di coda (v1.1.13). Motivo: - SH01 è il diversificatore più decorrelato (senza i 5 sleeve PAPER il DD del portafoglio sale - 3.96→5.35%). Copertura reale ora 15 sleeve su 19 (fade+DIP+**6 pairs incl. ETH/BTC 15m**+SH01); - restano simulati TR01/ROT02/TSM01/XS01 (book multi-asset, bloccati dal capitale: rumore - arrotondamento 20-30% a €2k, serve ~€20k). Diari `docs/diary/2026-06-08-pairs-executor.md`. -- **ESECUZIONE REALE — shadow (v1.0.3, 2026-06-03).** I **6 fade** (MR01/MR02/MR07 × BTC/ETH) eseguono ordini **REALI su Deribit testnet** accanto al fill simulato (*shadow*: sim + reale in parallelo, il sim resta la verità che guida le decisioni). `src/live/execution.py` `ExecutionClient`: `open` (market) + `close_amount` (market **reduce-only della SOLA quota del worker** — i 3 fade BTC condividono lo strumento e le posizioni si nettano per conto, quindi NON si usa `close_position` che flatterebbe le quote altrui); **verifica l'esecuzione sul TRADE** (order_id in `get_trade_history`, non sulla size netta aggregata); **fee REALI lette dai `trades[]`**. Strumenti = **lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`, amount nel base-coin, step 0.0001/0.001): scelti perché il **payoff lineare == matematica del backtest** (l'inverse `*-PERPETUAL` introdurrebbe una base 1/prezzo) e fee/PnL sono in USDC. Lo `StrategyWorker` tiene un **ledger reale parallelo** (`real_capital`, persistito) e logga `REAL_OPEN`/`REAL_CLOSE` col confronto **slippage** (prezzo sim vs eseguito) e **fee** (assunta 0.10% vs reale). Config: `portfolios.yml` → `overrides.execution {enabled, sleeves:[MR01,MR02,MR07,DIP01], instruments:{BTC:BTC_USDC-PERPETUAL, ETH:ETH_USDC-PERPETUAL}}` (**DIP01 aggiunto il 2026-06-04**: stesso wiring single-leg, TP resting incluso); pairs/rotation/tsmom/shape restano **simulati** (pairs richiede un executor a 2 gambe con gestione leg-risk; shape non ha TP). **Fee reali misurate = 0.05%/lato = 0.10% RT** (== assunto del backtest, su ETH; BTC inverse era ~0.094%). **Alert Telegram:** `REAL_EXEC_LIVE` (primo ordine reale verificato per worker) + `REAL_OPEN_FAIL`. Smoke (testnet, €0): `scripts/analysis/live_exec_smoke.py` (layer: ordine→verifica→fee) e `live_shadow_smoke.py` (catena worker open/close). **Capitale live portato a 2000** (notional fade ~$35) per ridurre il rumore di arrotondamento su BTC (step lineare ~$6.7). NB: ledger reale ≠ ledger sim — i worker già in posizione sim a un restart non hanno quota reale corrispondente; lo shadow reale parte pulito dalla prossima apertura. -- **Disaster-bracket on-book + alert outage (v1.1.4, 2026-06-07).** A ogni `REAL_OPEN` dei fade - eseguiti il worker piazza uno **STOP_MARKET reduce-only a ~−30%** dall'ingresso (trigger sul mark, - `ExecutionClient.place_disaster_sl`, cancellato in `_real_close`): assicurazione per gli outage - (poll-loop fermo = exit non valutati), in operatività normale non scatta mai → 0 costo Sharpe. - Config `overrides.execution.disaster_sl_pct` (0.30; 0=off). NB: il `set_stop_loss` di cerbero-mcp - è un `private/edit` (solo ordini APERTI) → inutilizzabile su market fillati; la cancel di un - trigger order risponde `untriggered` (= successo, verificato testnet). Alert Telegram `FEED_OUTAGE` - dopo 5 poll falliti consecutivi (con elenco posizioni reali aperte) + notifica di ripresa. I fix - di parità della stessa tornata (v1.1.3): TR01 fee×leva + forming-bar TR01/Pairs + WARN - `PANEL_SHORT` su TSM01/ROT02; `hourly_report` ora mostra i multi-asset (sezione MULTI-ASSET). - Diario `docs/diary/2026-06-07-sweep-fixes.md`. -- **VERITÀ CONTABILE su netting di conto (v1.1.24, 2026-06-11, da audit live).** Il modello - "quote per-worker con reduce-only" si rompe quando worker dello stesso strumento hanno - direzioni OPPOSTE (pairs long ETH vs fade short ETH): Deribit cappa/respinge i reduce-only - in silenzio. L'audit ha trovato: close fillato 0.078 ma bookato 0.105 (`Fill.amount` era il - RICHIESTO), 3 gambe pairs mai eseguite col PnL sim sommato al ledger reale (gamba orfana sul - conto, ETH/SOL di fatto short nudo), e il conto short 0.027 ETH più dei libri (riallineato a - mano). Fix: (1) `Fill.filled_amount` (fonte `order.filled_amount`) usato da TUTTI i ledger; - (2) `REAL_CLOSE_PARTIAL` (log+alert) quando il close filla meno del residuo; (3) pairs: - PnL bookato SOLO per le gambe verificate, gamba respinta → `orphan_legs` persistito + - alert `PAIR_LEG_ORPHAN`, `applied` solo con ENTRAMBE le gambe (altrimenti sim_fallback - dichiarato); (4) `REAL_DIVERGENCE` anche su jsonl (prima solo Telegram); (5) runner: - tick isolato per-worker (un'eccezione non salta gli altri; alert `WORKER_ERROR_STREAK` a 5). - **RISOLTO in v1.1.25 col NETTING delle chiusure**: `close_amount` tenta il reduce-only - (sicurezza storica: un bug di stato filla 0) e riesegue il RESIDUO cappato/respinto in - **market puro** — che muove il conto esattamente del delta del libro, cioè netta contro le - quote opposte → niente più gambe orfane/close cappati per costruzione (copre anche i pairs - via `close_pair`). Il chiamante riceve UN Fill combinato (prezzo medio pesato, fee sommate, - `notes` con 'netting'); evento `NET_CLOSE` (log+Telegram) a ogni fallback. La sicurezza - persa sul residuo è coperta dal **reconciler orario** (`reconcile_account.py`, cron host - :40, alert `ACCOUNT_DRIFT`): conto vs Σ libri+orfani, tolleranza 1.5×step, anti-race. - `orphan_legs`/`REAL_CLOSE_PARTIAL` restano come ultima difesa (se fallisce anche il market - puro). Test `tests/portfolio/test_netting_close.py`. -- **TP_PHANTOM — il tocco TP va confermato dal book reale (v1.1.23, 2026-06-11).** Il feed - testnet stampa wick anomali che (a) generano segnali fade su ETH e (b) "toccano" il TP - intrabar della stessa barra: il sim bookava +4% fantasma a bars_held=0 e `_real_close` - chiudeva A MERCATO una posizione il cui resting TP non aveva mai fillato (−fee/spread a - giro, 14 giri l'11-06, report Telegram 26/0 vs reale 11/15 — fix conteggio in v1.1.22). - Gate in `StrategyWorker._tp_phantom` (zero parametri, verita' d'esecuzione, NON un filtro - di strategia): tocco sim + **resting LIMIT a zero fill** + prezzo corrente che non ha - raggiunto il livello → exit SOPPRESSA (il limit sul book reale e' l'oracolo: se il prezzo - avesse scambiato li', avrebbe fillato); SL close-confirm e max_bars restano attivi. - Fill (anche parziale) o prezzo oltre il livello o worker non eseguito → comportamento - storico. Fail-open su errori di rete. Log `TP_PHANTOM` dedup per barra + alert Telegram. - Test `tests/portfolio/test_tp_phantom.py`. -- **INVERTED_TP_SKIP — l'entry con TP dal lato sbagliato va soppressa (v1.1.33, 2026-06-16).** - Un wick transitorio sul feed puo' far calcolare alla strategia un `tp` dal lato SBAGLIATO - dell'entry (es. donchian: segnale su barra wickata in basso → tp=centro canale, entry al - prezzo RECUPERATO sopra il proprio tp). L'exit intrabar `bar_high>=tp` (long)/`bar_low<=tp` - (short) scatta a `bars_held=0` in PERDITA, con churn di fee e TP reduce-only respinti - (16-06: 8 giri MR02_BTC 15m, sim −17.9 / reale −2.3 grazie al real-truth; TP_PHANTOM NON - lo prende — niente resting oracle, il prezzo HA superato il livello). Gate in - `StrategyWorker._open_position` (zero parametri, verita' d'esecuzione): se `tp<=entry` - (long) / `tp>=entry` (short) → **entry soppressa** (niente ordine reale/disaster-SL), log - `INVERTED_TP_SKIP` dedup per-barra + alert Telegram. Solo path live (il backtest entra al - close del bar del segnale → mai invertito, resta non filtrato → live meglio del backtest). - Cerotto testnet (il fix vero e' mainnet). Test `tests/portfolio/test_inverted_tp.py`, - diario `docs/diary/2026-06-16-inverted-tp-guard.md`. -- **TP reale = LIMIT reduce-only AL LIVELLO (2026-06-04).** Misurati +235 bps di slippage medio sulle uscite take-profit market-on-poll (sim esce al livello intrabar, il reale chiudeva al poll post-rimbalzo: sim +11.85 vs reale +0.62 USD sui primi 7 close). Fix: a ogni `REAL_OPEN` il worker piazza un **limit reduce-only al TP** (`ExecutionClient.place_tp_limit`, prezzo quantizzato al tick, SOLA quota del worker) → `REAL_TP_RESTING`; a ogni chiusura sim `_real_close` **cancella il resting → riconcilia i fill (anche parziali) via `get_trade_history` per order_id → market reduce-only solo del residuo** → ledger su prezzo combinato. `real_tp_order_id` persistito in `status.json` (resume-safe). Lo **SL resta market-on-poll** (deliberato: i trigger Deribit generano un nuovo order_id al trigger → fill non verificabile per order_id; e sul SL il rimbalzo lavora a favore). Fill da resting = fee **maker ~0%**. Smoke: `live_shadow_smoke.py` (2 scenari, testnet). Diario `docs/diary/2026-06-04-shadow-divergence.md`. -- **position_size per-famiglia (2026-06-07).** `portfolios.yml` accetta `position_size_family` - (chiave = `weighting.family_of`); plumbing `runner.pos_for_spec`. **PAIRS a 0.20** (esposizione - 0.40 ≈ il validato 0.45): la famiglia è senza stop e col globale 0.5×lev2 girava a ~2.2x il - validato (ETH/BTC DD grezzo 78% a quella taglia). PORT06 OOS DD 3.40→1.26% al costo di OOS - Sharpe 9.05→8.43 — assicurazione come il cap SHAPE. Gate `pairspos_port06_impact.py`, - diario `docs/diary/2026-06-07-pairspos-gate.md`. NB (2026-06-11): il **disaster-cap sullo - z** (exit se |z|≥z_stop, griglia pre-registrata 3.0-5.0 su tutte le coppie) è **BOCCIATO** - — coda e Sharpe OOS peggiorano ovunque (lo stop realizza la perdita al massimo overshoot e - l'engine rientra nello spread divergente: churn di fee), **5ª conferma** che gli stop su - mean-reversion sono falsi negativi. Record `scripts/analysis/pairs_zstop_research.py`, - diario `docs/diary/2026-06-11-stability-sweep.md`. -- **REAL-TRUTH ledger (2026-06-10, scelta utente).** Con `overrides.execution.real_truth: true` - (ATTIVO) il `capital` dei worker eseguiti si aggiorna col **PnL dei FILL REALI** (fee reali - incluse) invece del PnL sim: `_real_close`/`_real_close_pair` ritornano `(real_pnl, applied)` e - `_close_position`/`_close` applicano il reale al ledger; il sim resta SOLO diagnostica nel log - CLOSE (`pnl_source`/`sim_pnl`/`real_pnl`). Fallback al sim dichiarato (`pnl_source=sim_fallback`) - solo se il trade reale non è mai esistito/fillato (REAL_OPEN_FAIL/leg-fail). Equity → pesi → - allocazioni → notional derivano così dai soldi veri sul conto (il notional reale era GIÀ la - formula sim `capital·ps·lev`; il gap storico sim/reale era contabile: ledger separati + spike - print delle candele testnet che il sim bookava e il reale no). Le DECISIONI (entry/exit) restano - guidate dal feed; i multi-asset (TR01/ROT02/TSM01/XS01) restano sim per costruzione. Test: - `tests/portfolio/test_real_truth.py`. Diario `docs/diary/2026-06-10-real-truth-ledger.md`. -- **RIBILANCIO CONSERVA L'EQUITY (fix v1.1.31, 2026-06-13).** Il ribilancio giornaliero - gonfiava l'equity quando un worker era in posizione: i flat si dividevano l'INTERO `total` - (che includeva il capitale degli in-position) e gli in-position lo trattenevano in più → - doppio conteggio di Σ(capital−alloc) sugli in-pos. Caso reale: MR02_BTC 15m seedato a 181.19 - (eredità INIT_LINEAGE) e in posizione al ribilancio delle 00:01 → **+4.77 di equity dal nulla** - (scoperto perché l'equity saliva senza un trade dietro). Fix: `ledger.allocate(weights, reserved={sid:cap})` - — gli in-position TRATTENGONO il loro capitale (deployato, non spostabile), i flat si dividono - `total − Σreserved` per peso RINORMALIZZATO → `Σalloc == total` sempre, equity conservata per - costruzione. `runner.rebalance_allocations` passa i `reserved` dai worker `in_position`. Parità - runner intatta (5.8e-08). Test `test_ledger.py`/`test_runner_rebalance.py`. Diario - `docs/diary/2026-06-13-rebalance-conservation.md`. -- **INIT_LINEAGE — eredità capitale al cambio timeframe (2026-06-12).** Un worker al primo avvio - (niente status.json) eredita `capital`/`real_capital` dal worker più recente di STESSA - strategia+asset su altro tf (glob `{strategy}__{asset}__*`), MAI la posizione. Nato dallo swap - fade 1h→15m: i worker nuovi partivano dall'allocazione del pool scartando il PnL del gemello - (−16.8 di equity, riallineata a mano col seed la prima volta). `StrategyWorker._inherit_lineage_capital`, - evento `INIT_LINEAGE`. Test `tests/portfolio/test_capital_lineage.py`. -- **STALE_REAL_POSITION — guard anti-orfano nel reconciler (v1.1.31, 2026-06-13).** Lo swap - 1h→15m aveva ritirato MR02_BTC 1h dal config mentre teneva uno short REALE → posizione nuda non - gestita per 13h, e il reconciler NON la vedeva (lo status fermo del worker morto contava ancora - come "libro" → conto == libri). `reconcile_account.compute_stale_real_positions(max_age_min=15)`: - un worker con `real_in_position` e status.json fermo da >15 min = non gestito → alert - `STALE_REAL_POSITION`. Discriminante = STALENESS (un worker vivo riscrive ogni poll): cattura - ritirati-da-swap, crashati, rimossi dal config. Orfano chiuso a mano (testnet, +0.85). Resta da - fare la PREVENZIONE lato runner (flattare/consegnare la posizione del ritirato al boot). Diario - `docs/diary/2026-06-13-orphan-swap-guard.md`. -- **Limite noto:** al ribilancio le posizioni APERTE restano sul loro notional (non travasate); fedele al backtest daily-rebalanced entro il turnover infragiornaliero. - -## Multi-Strategy Paper Trader - -Orchestratore che esegue N strategie in parallelo su dati live Cerbero, ognuna con €1000 USDC virtuali indipendenti. - -**Config:** `strategies.yml` — due sezioni: `strategies` (single-leg: fade/honest) e -`pairs` (a 2 gambe). Attive: 6 fade (MR01/MR02/MR07 × BTC/ETH) + 5 coppie PR01. -**Due worker:** `strategy_worker.py` (single-leg) e `pairs_worker.py` (2 gambe, -long A / short B sullo z-score del log-ratio, fee su 2 gambe). -**Persistenza:** `data/paper_trades/{worker_id}/` con `trades.jsonl` (append-only) + `status.json` (resume al restart). -**Hot-add:** aggiungi riga YAML → `docker compose restart` → storico intatto. -**Exit strategia:** se un `Signal` porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), il worker esce su take-profit/stop-loss/time-limit; i pairs escono su |z|≤z_exit o max_bars. -**Naming Deribit (feed live):** major = `-PERPETUAL` (inverse); alt = `_USDC-PERPETUAL` (lineari USDC). Vedi INSTRUMENT_MAP in `multi_runner.py`. -**Notifiche:** Telegram per ogni trade (richiede `.env` con `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID`). +- **Feed contaminato → libreria fasulla (v2.0.0).** Print fantasma testnet + Binance/USDT hanno + prodotto edge inesistenti (+201%/+1238%/+16492% "OOS"). Tutti spariti sul feed reale. Lezione: il + dato viene prima della strategia; certificare sempre. +- **Look-ahead squeeze (storico).** L'intera famiglia squeeze-breakout aveva accuratezze 76-82% che + erano artefatto: decideva la direzione con la candela di breakout `i` ma entrava a `close[i-1]`. + Con ingresso onesto: lancio di moneta. (Dettagli nei diari in `Old/`.) +- **Entry sugli estremi di candela.** Strategie che entrano a `close` quando `close` è all'estremo + del range (≤0.1% o ≥99.9%) gonfiano i ritorni in modo irrealistico (ETH 2024: +30.848% → +2.725% + rimuovendoli). Spesso è un artefatto di dato o di entry non eseguibile. +- **Mean-reversion vs breakout.** Sui dati storici l'unica direzione che mostrava edge era la + mean-reversion (i breakout rientrano) — MA anche quegli edge erano per lo più artefatto del feed: + da riverificare da zero su dati certi. +- **Fee** = vincolo di prim'ordine. 0.10% RT baseline. Molte operazioni = morte per fee. +- **Leva**: testare 3x; 5x raddoppia il drawdown. I numeri a leva alta NON sono il caso base. +- **Data leakage** con rendimenti log: `returns[k] = log(close[k+1]/close[k])` usa `close[k+1]`. I + feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`. Verificare SEMPRE. ## Convenzioni -- Strategie in `scripts/strategies/` con codice univoco (MR01, ...). -- Script scartati in `scripts/waste/` (W01-W28 + famiglia squeeze). -- Diario in `docs/diary/YYYY-MM-DD.md`. Aggiornare dopo ogni esperimento significativo. -- Nessun dato sensibile nei commit (token, chiavi API). Usare `.gitignore`. -- Verificare sempre assenza di data leakage prima di fidarsi dei risultati. In particolare: `returns[i-w : i]` include `close[i]` che è un candle nel futuro — usare `returns[i-w : i-1]`. +- Strategie in `scripts/strategies/` con codice univoco; scartate documentate nel diario. +- Diario in `docs/diary/YYYY-MM-DD.md`, aggiornato dopo ogni esperimento significativo. +- **Nessun segreto nei commit** (token/chiavi). `.env` e `.env.mainnet` sono gitignored. +- Versionamento: `VERSION` (semver) + `scripts/bump_version.py`. `src/version.py` lo legge. -## Attenzione +## Archivio `Old/` -- **Data leakage:** è stata trovata e corretta nello script 05. Ogni volta che si usano rendimenti logaritmici (`np.diff(np.log(close))`), ricordare che `returns[k]` usa `close[k+1]`. I feature devono fermarsi a `returns[i-2]` se il prezzo corrente è `close[i-1]`. -- **Fee:** Deribit perp reale = taker ~0.05%/lato (**0.10% round-trip**), maker ~0%. Usare 0.10% RT come baseline (lo 0.20% storico era pessimista 2x). Includere SEMPRE nel backtest: sono vincolo di prim'ordine, molte operazioni = morte per fee. Il worker usa `strategy.fee_rt` (MR01 = 0.001). -- **Leva:** testato con 3x. Aumentare a 5x migliora i rendimenti ma raddoppia il drawdown. -- **GBM:** GradientBoostingClassifier di scikit-learn. Ensemble di alberi decisionali sequenziali. Walk-forward per evitare leakage temporale. -- **Report "stato trades" — separare SEMPRE pool reale e paper-stats.** La directory è la - fonte di verità: `data/portfolio_paper/` = sleeve nel POOL (capitale reale, muovono - equity/conto); `data/portfolio_paper_stats/` = TR01/ROT02/TSM01/**XS01** in SOLA statistica - (PnL a precisione frazionaria, MAI nel ledger). **NON** fare glob su `portfolio_paper*` - (matcha entrambi → gli utili sim dei paper-stats inquinano il realizzato: errore reale del - 2026-06-17, +8.15 XS01 attribuiti per sbaglio all'equity). Verità aggregata = ledger PORT06 - (`equity`, `total_capital`; unrealized = equity−total_capital). Usare - `scripts/analysis/trades_status.py` (riconcilia: realizzato POOL + Δunrealized ≈ Δequity). -- **Cerbero `get_historical` (fix 2026-05-28):** `end_date` come data nuda è inclusivo dell'intera giornata fino all'ultima candela chiusa (es. `end=oggi` arriva fino ad ora, non più a mezzanotte); accettati anche timestamp con orario (`...T14:00:00`, naive=UTC); nessun cap a ~5000 righe (paginazione interna). Il client passa già `end=oggi`, ora corretto. Prima del fix il paper trader restava a zero trade perché il feed era fermo a mezzanotte. -- **Dati ETH Deribit 15m:** 14-30%/anno di candele *flat* (O=H=L=C, volume 0, run fino a ~54h) per bassa liquidità del perpetuo. Verificato (2026-05-28): escluderle NON cambia i backtest (Δacc ≤0.5pp) → edge robusto. Resta un caveat operativo (slippage/fill in trading reale, irrilevante per paper). BTC pulito eccetto picco ~8% nel 2024. -- **FEED TESTNET INAFFIDABILE — `ETH-PERPETUAL` inverse CONGELATO (2026-06-13).** Il feed di - DECISIONE che il runner usa per BTC/ETH (inverse perp, `INSTRUMENT_MAP`) può congelarsi sul - testnet: il 13-06 `ETH-PERPETUAL` era fermo a **1661.95 per 12h+** (1 solo valore) mentre il - reale (`ETH_USDC-PERPETUAL`, lineare, dove si ESEGUE) si muoveva (gap −1.3%); BTC era vivo. - Effetto: tutte le decisioni ETH (SH01_ETH, 3 fade ETH 15m, gambe ETH dei pairs) girano su un - prezzo morto → **spiega lo 0-trade delle fade ETH 15m** (feed piatto = niente rottura banda). - È un GUASTO testnet, non di strategia: su **mainnet** l'arbitraggio tiene inverse ≈ lineare ≈ - realtà. Fix testnet possibile (reroute decisione ETH a USDC lineare in `INSTRUMENT_MAP`, con - caveat bootstrap SH01) NON applicato — il fix vero è andare a mainnet. Nota: la dashboard mostra - entry/mark/PnL **REALI** (non il sim-feed dislocato); il sim resta solo nel modal diagnostico. - **Update 2026-06-14: ANCORA congelato a 1661.95 (36h+).** Confermato anche un vincolo strutturale - del feed: **Cerbero v2 (`get_historical_v2`) serve SOLO 5m/15m/1h** — 30m/10m danno HTTP 400 in - ogni formato (l'endpoint legacy `get_historical` è 404, rimosso). La voce `"30m"` in - `runner._SUBHOURLY` era **speculativa, mai testata live** → qualsiasi TF sub-orario oltre 5m/15m - va DERIVATO resamplando nel runner (come 4h/1d dal 1h), non fetchato diretto. -- **GATE FEED CONGELATO — sleeve ETH-leg sospesi (v1.1.32, 2026-06-15, scelta utente "congela - gli sleeve ETH-leg finché il feed non si sblocca").** Audit "stato ordini": il feed ETH congelato - stava generando **perdite REALI** (SH01_ETH −2.83 reali vs −0.09 sim su un close, poi riapertura - della stessa trappola; 4 pairs con gamba ETH entrati su z-score spuri −3/−5/+5.6 = artefatto del - log-ratio con ETH pinnato). Fix: `runner._frozen_assets` + `_feed_gated_sids` — quando il feed di - decisione 1h di un asset è CONGELATO, gli sleeve CONCENTRATI (single/ml/pairs) che ne dipendono - **saltano il tick** (entry E exit) finché non si sblocca (come un outage; disaster-SL on-book = - protezione di coda). **Auto-guarente**: si rilascia alla prima barra COMPLETA non-flat (NON è - l'entry-guard post-flat bocciata: quella salta la candela-gap di ripresa, questo no). **Detector - guasto-vs-illiquido**: conta la run di close **INVARIATE** (prezzo mai cambiato), NON le barre flat - — sul feed reale ETH/BNB/DOGE hanno run 40-64 con 1-4 valori distinti/48h (MORTI) mentre - SOL/LTC/ADA sono flat ma VIVI (run ≤12, 5-31 valori/48h: si muovono ogni ~10 barre). Soglia - `feed_freeze_gate_bars=24` (1 giorno di prezzo immobile, configurabile, 0=off): gatea le **9 gambe - ETH esatte** (3 fade ETH 15m + SH_ETH + 5 pairs con gamba ETH) lasciando attivi BTC-only, - **PR_BTCLTC** (BTC vivo + LTC solo illiquido) e i multi-asset PAPER. Le posizioni ETH già aperte - NON vengono flattate (freeze=pausa: un close forzato passerebbe per lo stesso feed morto). Alert - Telegram `FEED_FROZEN_GATE` GATED/RIPRESO. Test `tests/portfolio/test_freeze_gate.py`, diario - `docs/diary/2026-06-15-frozen-feed-gate.md`. **Resta un cerotto testnet: il fix vero è mainnet.** -- **TIMING SWEEP pairs & honest (2026-06-14, NESSUN deploy).** Domanda utente: pairs/honest - beneficiano di TF più veloci come le fade (swap 1h→15m)? Esito: **no, niente deployato.** Vincolo - dati (alt 1h-only) → sweepabili solo **pairs ETH/BTC** e **DIP01 (BTC)**; TR01/ROT02 fuori scope - (multi-asset alt + lente). Il gate full+OOS migliorava PORT06 (+10m/30m/5m su tutte le metriche), - MA due muri: (a) **30m/10m feed-blocked** (v2 serve solo 5/15m/1h, vedi sopra); (b) **5m** è - nativo ma nel regime ATTUALE è il PEGGIORE (−10.5% 30g, DD 19.5%/180g vs 3% del 1h, Sharpe sotto - 15m/1h) + flat-share ETH 29% (slippage reale) → guadagno backtest modesto (FULL Sh +0.39) non vale - il rischio su soldi reali. **Lezione: il gate PORT06 full+OOS è necessario ma NON sufficiente** — - incrociare con (1) la fattibilità del FEED live e (2) il regime RECENTE (un edge full-history può - essere un relitto di volatilità passata: "più veloce = più Sharpe storico" si rovescia in "più - veloce = più DD nel regime calmo attuale"). Artefatti: `scripts/analysis/timing_sweep_pairs_honest.py`, - diario `docs/diary/2026-06-14-timing-sweep-pairs-honest.md`. (Config live invariata: 19 sleeve.) -- **MICRO-TEST MAINNET = il gate per scalare il capitale (piano pronto, 2026-06-13).** Testnet - valida solo la MECCANICA (feed/fill farlocchi); l'edge sopravvive ai fill veri? si risponde solo - con poco denaro reale su mainnet. **Switch già eseguibile via `.env`**: `CerberoClient` legge il - token da env (`CERBERO_TOKEN`, default testnet invariato; `is_mainnet()` helper) → puntare a - mainnet = solo `.env`, niente codice. Piano completo (fasi smoke→fade-only €1000 2-4 sett→verdetto - ledger-vs-backtest→espansione; sizing: fade €1000 = arrotondamento 2.6% BTC, pairs esclusi al 30%): - `docs/specs/mainnet-microtest-plan.md`. **Stato 2026-06-14:** token mainnet (`MAINNET_TOKEN` di - cerbero-mcp, che ha già le chiavi Deribit LIVE) **wired e VERIFICATO** — `is_mainnet()=True`, auth - OK su `get_account_summary` read-only. Tenuto in **`.env.mainnet`** dedicato (chmod 600, gitignored), - FUORI dal `.env` condiviso → il runner testnet NON flippa a mainnet a un riavvio accidentale (il - micro-test girerà come **servizio separato** con dir dati pulita + portfolios fade-only, per non - mescolare i ledger). NB architettura token: il `MAINNET_TOKEN` è la chiave d'accesso *all'MCP* + - selettore d'ambiente (non una chiave Deribit) → il nostro client DEVE presentarlo per essere - instradato al live. **UNICO BLOCCO: conto Deribit mainnet VUOTO (€0 USDC/BTC/ETH) → serve deposito - ~€1000 USDC** prima di Fase 0 smoke. Minimo assoluto €500 (rumore BTC ~5%), raccomandato €1000 (2.6%). +Tutto il lavoro pre-reset (preservato in git per consultazione storica): strategie +(`Old/scripts/strategies`), stack live e portafogli (`Old/src/live`, `Old/src/portfolio`, +`Old/scripts/portfolios`), ricerca/gate (`Old/scripts/analysis`), dati non certificati +(`Old/data`), 60+ diari (`Old/docs/diary`), test (`Old/tests`). Consultabile come riferimento +("come facevamo X"), ma **nessun edge lì dentro è fidato** finché non è ri-validato su dati certi. +``` diff --git a/API_REFERENCE.md b/Old/API_REFERENCE.md similarity index 100% rename from API_REFERENCE.md rename to Old/API_REFERENCE.md diff --git a/Dockerfile b/Old/Dockerfile similarity index 100% rename from Dockerfile rename to Old/Dockerfile diff --git a/STRATEGIA_GRIGLIA.md b/Old/STRATEGIA_GRIGLIA.md similarity index 100% rename from STRATEGIA_GRIGLIA.md rename to Old/STRATEGIA_GRIGLIA.md diff --git a/docker-compose.yml b/Old/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to Old/docker-compose.yml diff --git a/docs/TODO.md b/Old/docs/TODO.md similarity index 100% rename from docs/TODO.md rename to Old/docs/TODO.md diff --git a/docs/diary/2026-05-26.md b/Old/docs/diary/2026-05-26.md similarity index 100% rename from docs/diary/2026-05-26.md rename to Old/docs/diary/2026-05-26.md diff --git a/docs/diary/2026-05-27.md b/Old/docs/diary/2026-05-27.md similarity index 100% rename from docs/diary/2026-05-27.md rename to Old/docs/diary/2026-05-27.md diff --git a/docs/diary/2026-05-28-honest-strategies.md b/Old/docs/diary/2026-05-28-honest-strategies.md similarity index 100% rename from docs/diary/2026-05-28-honest-strategies.md rename to Old/docs/diary/2026-05-28-honest-strategies.md diff --git a/docs/diary/2026-05-28.md b/Old/docs/diary/2026-05-28.md similarity index 100% rename from docs/diary/2026-05-28.md rename to Old/docs/diary/2026-05-28.md diff --git a/docs/diary/2026-05-29-exploration.md b/Old/docs/diary/2026-05-29-exploration.md similarity index 100% rename from docs/diary/2026-05-29-exploration.md rename to Old/docs/diary/2026-05-29-exploration.md diff --git a/docs/diary/2026-05-29-shape.md b/Old/docs/diary/2026-05-29-shape.md similarity index 100% rename from docs/diary/2026-05-29-shape.md rename to Old/docs/diary/2026-05-29-shape.md diff --git a/docs/diary/2026-05-29.md b/Old/docs/diary/2026-05-29.md similarity index 100% rename from docs/diary/2026-05-29.md rename to Old/docs/diary/2026-05-29.md diff --git a/docs/diary/2026-05-31-exit-study.md b/Old/docs/diary/2026-05-31-exit-study.md similarity index 100% rename from docs/diary/2026-05-31-exit-study.md rename to Old/docs/diary/2026-05-31-exit-study.md diff --git a/docs/diary/2026-05-31-live-status.md b/Old/docs/diary/2026-05-31-live-status.md similarity index 100% rename from docs/diary/2026-05-31-live-status.md rename to Old/docs/diary/2026-05-31-live-status.md diff --git a/docs/diary/2026-05-31-options-rejected.md b/Old/docs/diary/2026-05-31-options-rejected.md similarity index 100% rename from docs/diary/2026-05-31-options-rejected.md rename to Old/docs/diary/2026-05-31-options-rejected.md diff --git a/docs/diary/2026-05-31-ta-classic-rejected.md b/Old/docs/diary/2026-05-31-ta-classic-rejected.md similarity index 100% rename from docs/diary/2026-05-31-ta-classic-rejected.md rename to Old/docs/diary/2026-05-31-ta-classic-rejected.md diff --git a/docs/diary/2026-06-01-sh01-horizon-exit-bug.md b/Old/docs/diary/2026-06-01-sh01-horizon-exit-bug.md similarity index 100% rename from docs/diary/2026-06-01-sh01-horizon-exit-bug.md rename to Old/docs/diary/2026-06-01-sh01-horizon-exit-bug.md diff --git a/docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md b/Old/docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md similarity index 100% rename from docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md rename to Old/docs/diary/2026-06-01-sh01-wiring-squeeze-bug.md diff --git a/docs/diary/2026-06-01-tp-min-edge.md b/Old/docs/diary/2026-06-01-tp-min-edge.md similarity index 100% rename from docs/diary/2026-06-01-tp-min-edge.md rename to Old/docs/diary/2026-06-01-tp-min-edge.md diff --git a/docs/diary/2026-06-02-fade-lossguard.md b/Old/docs/diary/2026-06-02-fade-lossguard.md similarity index 100% rename from docs/diary/2026-06-02-fade-lossguard.md rename to Old/docs/diary/2026-06-02-fade-lossguard.md diff --git a/docs/diary/2026-06-02-fractal-argo-search.md b/Old/docs/diary/2026-06-02-fractal-argo-search.md similarity index 100% rename from docs/diary/2026-06-02-fractal-argo-search.md rename to Old/docs/diary/2026-06-02-fractal-argo-search.md diff --git a/docs/diary/2026-06-04-exit-lab.md b/Old/docs/diary/2026-06-04-exit-lab.md similarity index 100% rename from docs/diary/2026-06-04-exit-lab.md rename to Old/docs/diary/2026-06-04-exit-lab.md diff --git a/docs/diary/2026-06-04-partial-tp-ladder.md b/Old/docs/diary/2026-06-04-partial-tp-ladder.md similarity index 100% rename from docs/diary/2026-06-04-partial-tp-ladder.md rename to Old/docs/diary/2026-06-04-partial-tp-ladder.md diff --git a/docs/diary/2026-06-04-shadow-divergence.md b/Old/docs/diary/2026-06-04-shadow-divergence.md similarity index 100% rename from docs/diary/2026-06-04-shadow-divergence.md rename to Old/docs/diary/2026-06-04-shadow-divergence.md diff --git a/docs/diary/2026-06-05-confirm-sl-forming-bar.md b/Old/docs/diary/2026-06-05-confirm-sl-forming-bar.md similarity index 100% rename from docs/diary/2026-06-05-confirm-sl-forming-bar.md rename to Old/docs/diary/2026-06-05-confirm-sl-forming-bar.md diff --git a/docs/diary/2026-06-05-sh01-sl-research.md b/Old/docs/diary/2026-06-05-sh01-sl-research.md similarity index 100% rename from docs/diary/2026-06-05-sh01-sl-research.md rename to Old/docs/diary/2026-06-05-sh01-sl-research.md diff --git a/docs/diary/2026-06-06-improvement-sweep.md b/Old/docs/diary/2026-06-06-improvement-sweep.md similarity index 100% rename from docs/diary/2026-06-06-improvement-sweep.md rename to Old/docs/diary/2026-06-06-improvement-sweep.md diff --git a/docs/diary/2026-06-07-dip01-exit16.md b/Old/docs/diary/2026-06-07-dip01-exit16.md similarity index 100% rename from docs/diary/2026-06-07-dip01-exit16.md rename to Old/docs/diary/2026-06-07-dip01-exit16.md diff --git a/docs/diary/2026-06-07-pairspos-gate.md b/Old/docs/diary/2026-06-07-pairspos-gate.md similarity index 100% rename from docs/diary/2026-06-07-pairspos-gate.md rename to Old/docs/diary/2026-06-07-pairspos-gate.md diff --git a/docs/diary/2026-06-07-sh01-trainwindow.md b/Old/docs/diary/2026-06-07-sh01-trainwindow.md similarity index 100% rename from docs/diary/2026-06-07-sh01-trainwindow.md rename to Old/docs/diary/2026-06-07-sh01-trainwindow.md diff --git a/docs/diary/2026-06-07-sweep-fixes.md b/Old/docs/diary/2026-06-07-sweep-fixes.md similarity index 100% rename from docs/diary/2026-06-07-sweep-fixes.md rename to Old/docs/diary/2026-06-07-sweep-fixes.md diff --git a/docs/diary/2026-06-07-trendmax-gate.md b/Old/docs/diary/2026-06-07-trendmax-gate.md similarity index 100% rename from docs/diary/2026-06-07-trendmax-gate.md rename to Old/docs/diary/2026-06-07-trendmax-gate.md diff --git a/docs/diary/2026-06-08-dispersion-correlation-search.md b/Old/docs/diary/2026-06-08-dispersion-correlation-search.md similarity index 100% rename from docs/diary/2026-06-08-dispersion-correlation-search.md rename to Old/docs/diary/2026-06-08-dispersion-correlation-search.md diff --git a/docs/diary/2026-06-08-mr02eth-replace-search.md b/Old/docs/diary/2026-06-08-mr02eth-replace-search.md similarity index 100% rename from docs/diary/2026-06-08-mr02eth-replace-search.md rename to Old/docs/diary/2026-06-08-mr02eth-replace-search.md diff --git a/docs/diary/2026-06-08-pairs-executor.md b/Old/docs/diary/2026-06-08-pairs-executor.md similarity index 100% rename from docs/diary/2026-06-08-pairs-executor.md rename to Old/docs/diary/2026-06-08-pairs-executor.md diff --git a/docs/diary/2026-06-08-real-only-portfolio.md b/Old/docs/diary/2026-06-08-real-only-portfolio.md similarity index 100% rename from docs/diary/2026-06-08-real-only-portfolio.md rename to Old/docs/diary/2026-06-08-real-only-portfolio.md diff --git a/docs/diary/2026-06-09-blind-traders-game.md b/Old/docs/diary/2026-06-09-blind-traders-game.md similarity index 100% rename from docs/diary/2026-06-09-blind-traders-game.md rename to Old/docs/diary/2026-06-09-blind-traders-game.md diff --git a/docs/diary/2026-06-09-blind-traders-game2.md b/Old/docs/diary/2026-06-09-blind-traders-game2.md similarity index 100% rename from docs/diary/2026-06-09-blind-traders-game2.md rename to Old/docs/diary/2026-06-09-blind-traders-game2.md diff --git a/docs/diary/2026-06-09-cerbero-bite-credit-spread.md b/Old/docs/diary/2026-06-09-cerbero-bite-credit-spread.md similarity index 100% rename from docs/diary/2026-06-09-cerbero-bite-credit-spread.md rename to Old/docs/diary/2026-06-09-cerbero-bite-credit-spread.md diff --git a/docs/diary/2026-06-09-pairs15m-live-path.md b/Old/docs/diary/2026-06-09-pairs15m-live-path.md similarity index 100% rename from docs/diary/2026-06-09-pairs15m-live-path.md rename to Old/docs/diary/2026-06-09-pairs15m-live-path.md diff --git a/docs/diary/2026-06-09-pairs15m-port06-gate.md b/Old/docs/diary/2026-06-09-pairs15m-port06-gate.md similarity index 100% rename from docs/diary/2026-06-09-pairs15m-port06-gate.md rename to Old/docs/diary/2026-06-09-pairs15m-port06-gate.md diff --git a/docs/diary/2026-06-09-pairs15m-robustezza.md b/Old/docs/diary/2026-06-09-pairs15m-robustezza.md similarity index 100% rename from docs/diary/2026-06-09-pairs15m-robustezza.md rename to Old/docs/diary/2026-06-09-pairs15m-robustezza.md diff --git a/docs/diary/2026-06-09-stats-per-anno-real.md b/Old/docs/diary/2026-06-09-stats-per-anno-real.md similarity index 100% rename from docs/diary/2026-06-09-stats-per-anno-real.md rename to Old/docs/diary/2026-06-09-stats-per-anno-real.md diff --git a/docs/diary/2026-06-09-xs01-cross-sectional.md b/Old/docs/diary/2026-06-09-xs01-cross-sectional.md similarity index 100% rename from docs/diary/2026-06-09-xs01-cross-sectional.md rename to Old/docs/diary/2026-06-09-xs01-cross-sectional.md diff --git a/docs/diary/2026-06-10-funding-carry.md b/Old/docs/diary/2026-06-10-funding-carry.md similarity index 100% rename from docs/diary/2026-06-10-funding-carry.md rename to Old/docs/diary/2026-06-10-funding-carry.md diff --git a/docs/diary/2026-06-10-grid-traders-game3.md b/Old/docs/diary/2026-06-10-grid-traders-game3.md similarity index 100% rename from docs/diary/2026-06-10-grid-traders-game3.md rename to Old/docs/diary/2026-06-10-grid-traders-game3.md diff --git a/docs/diary/2026-06-10-real-truth-ledger.md b/Old/docs/diary/2026-06-10-real-truth-ledger.md similarity index 100% rename from docs/diary/2026-06-10-real-truth-ledger.md rename to Old/docs/diary/2026-06-10-real-truth-ledger.md diff --git a/docs/diary/2026-06-10-xs01-dispersion-gate.md b/Old/docs/diary/2026-06-10-xs01-dispersion-gate.md similarity index 100% rename from docs/diary/2026-06-10-xs01-dispersion-gate.md rename to Old/docs/diary/2026-06-10-xs01-dispersion-gate.md diff --git a/docs/diary/2026-06-11-stability-sweep.md b/Old/docs/diary/2026-06-11-stability-sweep.md similarity index 100% rename from docs/diary/2026-06-11-stability-sweep.md rename to Old/docs/diary/2026-06-11-stability-sweep.md diff --git a/docs/diary/2026-06-11-system-audit.md b/Old/docs/diary/2026-06-11-system-audit.md similarity index 100% rename from docs/diary/2026-06-11-system-audit.md rename to Old/docs/diary/2026-06-11-system-audit.md diff --git a/docs/diary/2026-06-12-accel50.md b/Old/docs/diary/2026-06-12-accel50.md similarity index 100% rename from docs/diary/2026-06-12-accel50.md rename to Old/docs/diary/2026-06-12-accel50.md diff --git a/docs/diary/2026-06-12-fade-tf-sweep.md b/Old/docs/diary/2026-06-12-fade-tf-sweep.md similarity index 100% rename from docs/diary/2026-06-12-fade-tf-sweep.md rename to Old/docs/diary/2026-06-12-fade-tf-sweep.md diff --git a/docs/diary/2026-06-12-fade15m-gate.md b/Old/docs/diary/2026-06-12-fade15m-gate.md similarity index 100% rename from docs/diary/2026-06-12-fade15m-gate.md rename to Old/docs/diary/2026-06-12-fade15m-gate.md diff --git a/docs/diary/2026-06-12-improvements-sweep.md b/Old/docs/diary/2026-06-12-improvements-sweep.md similarity index 100% rename from docs/diary/2026-06-12-improvements-sweep.md rename to Old/docs/diary/2026-06-12-improvements-sweep.md diff --git a/docs/diary/2026-06-12-resting-reconcile-feedgap.md b/Old/docs/diary/2026-06-12-resting-reconcile-feedgap.md similarity index 100% rename from docs/diary/2026-06-12-resting-reconcile-feedgap.md rename to Old/docs/diary/2026-06-12-resting-reconcile-feedgap.md diff --git a/docs/diary/2026-06-12-xex-divergence.md b/Old/docs/diary/2026-06-12-xex-divergence.md similarity index 100% rename from docs/diary/2026-06-12-xex-divergence.md rename to Old/docs/diary/2026-06-12-xex-divergence.md diff --git a/docs/diary/2026-06-13-dashboard.md b/Old/docs/diary/2026-06-13-dashboard.md similarity index 100% rename from docs/diary/2026-06-13-dashboard.md rename to Old/docs/diary/2026-06-13-dashboard.md diff --git a/docs/diary/2026-06-13-ledger-vs-backtest-report.md b/Old/docs/diary/2026-06-13-ledger-vs-backtest-report.md similarity index 100% rename from docs/diary/2026-06-13-ledger-vs-backtest-report.md rename to Old/docs/diary/2026-06-13-ledger-vs-backtest-report.md diff --git a/docs/diary/2026-06-13-orphan-swap-guard.md b/Old/docs/diary/2026-06-13-orphan-swap-guard.md similarity index 100% rename from docs/diary/2026-06-13-orphan-swap-guard.md rename to Old/docs/diary/2026-06-13-orphan-swap-guard.md diff --git a/docs/diary/2026-06-13-rebalance-conservation.md b/Old/docs/diary/2026-06-13-rebalance-conservation.md similarity index 100% rename from docs/diary/2026-06-13-rebalance-conservation.md rename to Old/docs/diary/2026-06-13-rebalance-conservation.md diff --git a/docs/diary/2026-06-14-timing-sweep-pairs-honest.md b/Old/docs/diary/2026-06-14-timing-sweep-pairs-honest.md similarity index 100% rename from docs/diary/2026-06-14-timing-sweep-pairs-honest.md rename to Old/docs/diary/2026-06-14-timing-sweep-pairs-honest.md diff --git a/docs/diary/2026-06-15-frozen-feed-gate.md b/Old/docs/diary/2026-06-15-frozen-feed-gate.md similarity index 100% rename from docs/diary/2026-06-15-frozen-feed-gate.md rename to Old/docs/diary/2026-06-15-frozen-feed-gate.md diff --git a/docs/diary/2026-06-16-inverted-tp-guard.md b/Old/docs/diary/2026-06-16-inverted-tp-guard.md similarity index 100% rename from docs/diary/2026-06-16-inverted-tp-guard.md rename to Old/docs/diary/2026-06-16-inverted-tp-guard.md diff --git a/docs/diary/2026-06-16-mainnet-fase0-smoke.md b/Old/docs/diary/2026-06-16-mainnet-fase0-smoke.md similarity index 100% rename from docs/diary/2026-06-16-mainnet-fase0-smoke.md rename to Old/docs/diary/2026-06-16-mainnet-fase0-smoke.md diff --git a/docs/diary/2026-06-18-price-ladder-search.md b/Old/docs/diary/2026-06-18-price-ladder-search.md similarity index 100% rename from docs/diary/2026-06-18-price-ladder-search.md rename to Old/docs/diary/2026-06-18-price-ladder-search.md diff --git a/docs/diary/README.md b/Old/docs/diary/README.md similarity index 100% rename from docs/diary/README.md rename to Old/docs/diary/README.md diff --git a/docs/report/strategie_attive.html b/Old/docs/report/strategie_attive.html similarity index 100% rename from docs/report/strategie_attive.html rename to Old/docs/report/strategie_attive.html diff --git a/docs/specs/2026-05-27-multi-strategy-paper-trader.md b/Old/docs/specs/2026-05-27-multi-strategy-paper-trader.md similarity index 100% rename from docs/specs/2026-05-27-multi-strategy-paper-trader.md rename to Old/docs/specs/2026-05-27-multi-strategy-paper-trader.md diff --git a/docs/specs/mainnet-microtest-plan.md b/Old/docs/specs/mainnet-microtest-plan.md similarity index 100% rename from docs/specs/mainnet-microtest-plan.md rename to Old/docs/specs/mainnet-microtest-plan.md diff --git a/docs/superpowers/plans/2026-05-29-portfolios-phase2B-honest-workers.md b/Old/docs/superpowers/plans/2026-05-29-portfolios-phase2B-honest-workers.md similarity index 100% rename from docs/superpowers/plans/2026-05-29-portfolios-phase2B-honest-workers.md rename to Old/docs/superpowers/plans/2026-05-29-portfolios-phase2B-honest-workers.md diff --git a/docs/superpowers/plans/2026-05-29-portfolios.md b/Old/docs/superpowers/plans/2026-05-29-portfolios.md similarity index 100% rename from docs/superpowers/plans/2026-05-29-portfolios.md rename to Old/docs/superpowers/plans/2026-05-29-portfolios.md diff --git a/docs/superpowers/specs/2026-05-29-portfolios-design.md b/Old/docs/superpowers/specs/2026-05-29-portfolios-design.md similarity index 100% rename from docs/superpowers/specs/2026-05-29-portfolios-design.md rename to Old/docs/superpowers/specs/2026-05-29-portfolios-design.md diff --git a/portfolios.yml b/Old/portfolios.yml similarity index 88% rename from portfolios.yml rename to Old/portfolios.yml index 5cfb431..34d3627 100644 --- a/portfolios.yml +++ b/Old/portfolios.yml @@ -56,7 +56,13 @@ overrides: # 6 fade (MR01/MR02/MR07 x BTC/ETH 15m) + DIP01 (BTC 1h). Ordini sui LINEARI USDC # (payoff lineare = matematica del backtest; fee/PnL in USDC). execution: - enabled: true + # DISATTIVATA 2026-06-18: la verifica su dati REALI (Binance spot + perp Deribit + # MAINNET) ha mostrato che le 6 fade NON hanno edge sui prezzi veri (tutte negative + # ogni anno, Sharpe -3..-5) -> il +1000% "validato OOS" era un artefatto dei print + # fantasma del feed testnet Cerbero. Micro-test fermato e conto flattato a mano. + # Tenuta a false come blindatura: un 'docker compose up'/deploy NON deve riprendere + # a tradere reale finche' non ri-validiamo l'intera libreria su dati reali. + enabled: false # SOLO fade + DIP01 in Fase 1 (SH01 e pairs -> paper, vedi paper_sleeves). sleeves: [MR01, MR02, MR07, DIP01] instruments: diff --git a/scripts/portfolios/__init__.py b/Old/scripts/analysis/__init__.py similarity index 100% rename from scripts/portfolios/__init__.py rename to Old/scripts/analysis/__init__.py diff --git a/scripts/analysis/_port06_gate_common.py b/Old/scripts/analysis/_port06_gate_common.py similarity index 100% rename from scripts/analysis/_port06_gate_common.py rename to Old/scripts/analysis/_port06_gate_common.py diff --git a/scripts/analysis/accel50_research.py b/Old/scripts/analysis/accel50_research.py similarity index 100% rename from scripts/analysis/accel50_research.py rename to Old/scripts/analysis/accel50_research.py diff --git a/Old/scripts/analysis/binance_fade_check.py b/Old/scripts/analysis/binance_fade_check.py new file mode 100644 index 0000000..0500e71 --- /dev/null +++ b/Old/scripts/analysis/binance_fade_check.py @@ -0,0 +1,100 @@ +"""CONFERMA su feed PURO Binance spot — la fade ha edge reale o era artefatto-print? + +Il clean close-aware ha spliciato barre Binance-spot dentro la serie Deribit-perp: +il crollo del backtest potrebbe (a) rivelare la verita' (l'edge era print) o (b) essere +un artefatto dello splice (basis perp/spot ai punti di giunzione). Test decisivo: +girare lo STESSO engine fade su una serie 100% Binance spot (sorgente coerente, niente +splice). Se anche qui la fade e' negativa -> edge confermato finto. +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np, pandas as pd, ccxt +from scripts.analysis.risk_management import build_trades, strats_for + +EX = ccxt.binance({"enableRateLimit": True}) +SYM = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"} +START = "2020-06-01" # warmup per EMA200/ATR; il report usa 2021+ +YEARS = [2021, 2022, 2023, 2024, 2025, 2026] + + +def fetch(asset, tf="15m"): + start_ms = int(pd.Timestamp(START, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp("2026-05-26", tz="UTC").timestamp() * 1000) + tf_ms = 15 * 60 * 1000 + rows = [] + since = start_ms + while since <= end_ms: + r = EX.fetch_ohlcv(SYM[asset], tf, since=since, limit=1000) + if not r: + break + rows += r + nxt = int(r[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + df = pd.DataFrame(rows, columns=["timestamp", "open", "high", "low", "close", "volume"]) + df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + return df[df["timestamp"] <= end_ms] + + +def yearly(rows_by_year_ret, ts, trades, pos=0.15): + # per-anno compound + yr = {y: 1000.0 for y in YEARS} + # ricostruisco compound per anno separato (reset capitale ogni anno per ret% annuo) + by = {y: [] for y in YEARS} + for i, j, r in trades: + y = ts.iloc[i].year + if y in by: + by[y].append(r) + out = {} + for y in YEARS: + cap = 1000.0 + for r in by[y]: + cap = max(cap + cap * pos * r, 10.0) + out[y] = (cap / 1000 - 1) * 100 + return out + + +def full_oos(ts, trades, pos=0.15, split_date="2024-10-12"): + sd = pd.Timestamp(split_date, tz="UTC") + def comp(sub): + cap = 1000.0; rets = [] + for i, j, r in sub: + cap = max(cap + cap * pos * r, 10.0); rets.append(r * pos) + return cap, rets + capF, rF = comp(trades) + oos = [(i, j, r) for i, j, r in trades if ts.iloc[i] >= sd] + capO, rO = comp(oos) + shF = float(np.mean(rF) / np.std(rF) * np.sqrt(len(rF))) if len(rF) > 1 and np.std(rF) > 0 else 0.0 + shO = float(np.mean(rO) / np.std(rO) * np.sqrt(len(rO))) if len(rO) > 1 and np.std(rO) > 0 else 0.0 + return (capF / 1000 - 1) * 100, shF, (capO / 1000 - 1) * 100, shO + + +def main(): + print(f"Fetch Binance 15m (da {START})...\n") + data = {a: fetch(a) for a in ("BTC", "ETH")} + print("=" * 92) + print(" FADE su PURO Binance spot 15m | RET% per anno (pos 0.15, leva 3x, trend 3.0)") + print("=" * 92) + print(f" {'sleeve':<12s}" + "".join(f"{y:>9d}" for y in YEARS) + " | FULL% Shrp | OOS% Shrp") + print(" " + "-" * 88) + for asset in ("BTC", "ETH"): + df = data[asset].copy() + df = df[pd.to_datetime(df["timestamp"], unit="ms", utc=True).dt.year >= 2021].reset_index(drop=True) \ + if False else df # tengo il warmup, filtro nei trade + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + for code in ("MR01", "MR02", "MR07"): + fn, params = strats_for(asset)[code] + trades = build_trades(fn(df, **params), df, trend_max=3.0) + trades = [(i, j, r) for i, j, r in trades if ts.iloc[i].year >= 2021] + yr = yearly(None, ts, trades) + fF, shF, fO, shO = full_oos(ts, trades) + print(f" {code+'_'+asset:<12s}" + "".join(f"{yr[y]:>+9.0f}" for y in YEARS) + + f" | {fF:>+8.0f} {shF:>5.2f} | {fO:>+6.0f} {shO:>5.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/cerbero_bite_credit_spread.py b/Old/scripts/analysis/cerbero_bite_credit_spread.py similarity index 100% rename from scripts/analysis/cerbero_bite_credit_spread.py rename to Old/scripts/analysis/cerbero_bite_credit_spread.py diff --git a/Old/scripts/analysis/cerbero_probe.py b/Old/scripts/analysis/cerbero_probe.py new file mode 100644 index 0000000..156fa2d --- /dev/null +++ b/Old/scripts/analysis/cerbero_probe.py @@ -0,0 +1,77 @@ +"""PROBE CERBERO MCP — quali exchange/fonti serve davvero? (cerca IBKR & alt) + +Non si fida del commento nel codice: interroga il server v2 (/mcp/tools/get_historical +con `exchange=...`) su una matrice di nomi exchange + naming strumento, e riporta chi +risponde con candele vere. Cerca in particolare IBKR e Alpaca (spot US reale). + + uv run python scripts/analysis/cerbero_probe.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import requests +from src.live.cerbero_client import CerberoClient, is_mainnet + +C = CerberoClient() +START, END, INTERVAL = "2026-05-20", "2026-05-27", "1h" + +# exchange -> naming strumento BTC da provare (varianti) +EXCHANGES = { + "deribit": ["BTC-PERPETUAL"], + "hyperliquid": ["BTC"], + "bybit": ["BTCUSDT", "BTC-PERPETUAL", "BTCUSD"], + "alpaca": ["BTC/USD", "BTCUSD", "BTC/USDT", "BTC"], + "ibkr": ["BTC", "BTC.USD", "BTCUSD"], + "interactivebrokers": ["BTC", "BTCUSD"], + "binance": ["BTCUSDT", "BTC/USDT", "BTC-PERPETUAL"], + "coinbase": ["BTC-USD", "BTC/USD", "BTCUSD"], + "kraken": ["XBTUSD", "BTC/USD", "BTCUSD"], + "okx": ["BTC-USDT", "BTC-USD-SWAP"], +} + + +def try_v2(exchange: str, instrument: str) -> str: + try: + candles = C.get_historical_v2(instrument, START, END, INTERVAL, exchange=exchange) + if candles: + c0, c1 = candles[0], candles[-1] + return f"OK {len(candles):>4} candele close {c1.get('close')}" + return "vuoto (0 candele)" + except requests.HTTPError as e: + code = e.response.status_code if e.response is not None else "?" + return f"HTTP {code}" + except Exception as e: + return f"{type(e).__name__}: {str(e)[:50]}" + + +def list_tools() -> None: + """Tenta di enumerare i tool/endpoint del server (best-effort).""" + for path in ("/mcp/tools", "/mcp/tools/list", "/tools", "/mcp"): + try: + r = requests.post(f"{C.base_url}{path}", headers=C._headers(), json={}, timeout=10) + print(f" POST {path:<18} -> HTTP {r.status_code} {str(r.text)[:200]}") + except Exception as e: + print(f" POST {path:<18} -> {type(e).__name__}: {str(e)[:60]}") + + +def main(): + print("=" * 80) + print(f" PROBE CERBERO MCP @ {C.base_url} (mainnet={is_mainnet()})") + print("=" * 80) + print("\n[1] Enumerazione endpoint/tool (best-effort):") + list_tools() + print(f"\n[2] get_historical_v2 BTC {INTERVAL} {START}->{END} per exchange:") + print(f" {'exchange':<20s}{'instrument':<16s}esito") + print(" " + "-" * 70) + for ex, syms in EXCHANGES.items(): + for sym in syms: + res = try_v2(ex, sym) + print(f" {ex:<20s}{sym:<16s}{res}") + if res.startswith("OK"): + break # trovato il naming giusto, basta + + +if __name__ == "__main__": + main() diff --git a/Old/scripts/analysis/clean_fade_rerun.py b/Old/scripts/analysis/clean_fade_rerun.py new file mode 100644 index 0000000..509c172 --- /dev/null +++ b/Old/scripts/analysis/clean_fade_rerun.py @@ -0,0 +1,84 @@ +"""RI-ESECUZIONE FADE sul feed PULITO (data/raw ricostruito da Deribit mainnet, 2026-06-19). + +Dopo il rebuild (scripts/analysis/rebuild_history.py) i parquet canonici in data/raw +sono storia Deribit mainnet reale (ccxt pubblico), certificata vs Coinbase USD. Qui giro +le 6 fade (MR01/MR02/MR07 x BTC/ETH) con l'ENGINE CANONICO (risk_management.build_trades, +strats_for) sul feed pulito, su ENTRAMBI i timeframe: + - 1h = config dei claim storici "validati OOS" (CLAUDE.md: MR01 BTC +201% / ETH +1238%) + - 15m = config LIVE attuale (swap 1h->15m, v1.1.30) + +Stessi parametri del live: pos 0.15, leva 3x, trend_max 3.0, fee 0.10% RT. OOS = ultimo 30% +per indice (convenzione OOS_FRAC del progetto). Read-only, nessuna scrittura. + + uv run python scripts/analysis/clean_fade_rerun.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np +import pandas as pd +from src.data.downloader import load_data +from scripts.analysis.risk_management import strats_for, build_trades, POS, LEV, OOS_FRAC + +TFS = ["1h", "15m"] +YEARS = list(range(2018, 2027)) + + +def metrics(ts, trades, split_idx, pos=POS): + """trades = [(i, j, r_netto)]. Ritorna (per-anno%, FULL%, FULL Sharpe, OOS%, OOS Sharpe).""" + by = {y: 0.0 for y in YEARS} + capF = capO = 1000.0 + rF, rO = [], [] + for i, j, r in trades: + y = ts.iloc[i].year + if y in by: + by[y] += r * pos * 1000.0 # contributo lineare per la riga annuale + capF = max(capF + capF * pos * r, 10.0) + rF.append(r * pos) + if i >= split_idx: + capO = max(capO + capO * pos * r, 10.0) + rO.append(r * pos) + yr = {y: by[y] / 1000.0 * 100 for y in YEARS} + shF = float(np.mean(rF) / np.std(rF) * np.sqrt(len(rF))) if len(rF) > 1 and np.std(rF) > 0 else 0.0 + shO = float(np.mean(rO) / np.std(rO) * np.sqrt(len(rO))) if len(rO) > 1 and np.std(rO) > 0 else 0.0 + return yr, (capF / 1000 - 1) * 100, shF, (capO / 1000 - 1) * 100, shO + + +def main(): + years_present = set() + results = {} + for tf in TFS: + for asset in ("BTC", "ETH"): + df = load_data(asset, tf) + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + years_present |= set(ts.dt.year.unique().tolist()) + split_idx = int(len(df) * (1 - OOS_FRAC)) + cov = f"{ts.iloc[0].date()} -> {ts.iloc[-1].date()} ({len(df)} barre, OOS da {ts.iloc[split_idx].date()})" + for code in ("MR01", "MR02", "MR07"): + fn, params = strats_for(asset)[code] + trades = build_trades(fn(df, **params), df, trend_max=3.0) + results[(tf, asset, code)] = (metrics(ts, trades, split_idx), len(trades), cov) + + years = [y for y in YEARS if y in years_present] + for tf in TFS: + print("\n" + "=" * (62 + 9 * len(years))) + print(f" FADE su FEED PULITO (Deribit mainnet) — {tf} | pos {POS}, leva {LEV:.0f}x, trend 3.0, fee 0.10% RT") + # mostra la copertura una volta per asset + for asset in ("BTC", "ETH"): + print(f" {asset}: {results[(tf, asset, 'MR01')][2]}") + print("=" * (62 + 9 * len(years))) + print(f" {'sleeve':<11s}" + "".join(f"{y:>9d}" for y in years) + + f"{'Trd':>7s}{'FULL%':>9s}{'Shrp':>7s}{'OOS%':>8s}{'Shrp':>7s}") + print(" " + "-" * (60 + 9 * len(years))) + for asset in ("BTC", "ETH"): + for code in ("MR01", "MR02", "MR07"): + (yr, fF, shF, fO, shO), ntr, _ = results[(tf, asset, code)] + print(f" {code+'_'+asset:<11s}" + "".join(f"{yr[y]:>+9.0f}" for y in years) + + f"{ntr:>7d}{fF:>+9.0f}{shF:>7.2f}{fO:>+8.0f}{shO:>7.2f}") + print() + + +if __name__ == "__main__": + main() diff --git a/Old/scripts/analysis/clean_feed.py b/Old/scripts/analysis/clean_feed.py new file mode 100644 index 0000000..03de840 --- /dev/null +++ b/Old/scripts/analysis/clean_feed.py @@ -0,0 +1,274 @@ +"""CLEAN FEED — ripara gli spike-print del feed Deribit/Cerbero coi dati reali di Binance. + +Motivo (2026-06-18): la ricerca Price Ladder ha rivelato che data/raw/btc_1h.parquet (e gli +altri TF/asset) contengono barre con WICK FASULLI (es. BTC 2024-02-13: low 38.580 con +close ~49.968, BTC reale ~50k) — lo stesso spike-print testnet documentato in CLAUDE.md +(TP_PHANTOM / feed congelato). Sono pochi (decine per file) ma avvelenano i backtest +(stop/entry su prezzi mai avvenuti) e gonfiano le code (la "FULL DD BTC ~54%" del ladder era +in gran parte questo). + +Metodo (conservativo, fonte di verita' = Binance spot via ccxt, gia' cablato nel progetto): + 1. DETECT: barra sospetta = high/low che sfora >15% il cluster di close locale [i-1,i,i+1] + (close sano + wick fasullo). Soglia larga: tanto e' Binance ad arbitrare. + 2. ARBITRA: per ogni sospetta, scarica la barra Binance reale (BTC/USDT, ETH/USDT) allo + stesso tf/timestamp. Sostituisce O/H/L/C SOLO se Binance dissente materialmente (>2% su + high o low) -> un wick VERO confermato da Binance resta intatto. Volume/timestamp invariati. + 3. BACKUP (data/_feed_backup/) + scrittura atomica + VALIDAZIONE (re-scan = 0 sospette, + n righe invariato). Log dettagliato di ogni barra riparata (old OHLC -> new). + + uv run python scripts/analysis/clean_feed.py [ASSET_TF ...] # default: tutti BTC/ETH x TF + uv run python scripts/analysis/clean_feed.py BTC_1h # un solo file +""" +from __future__ import annotations + +import shutil +import sys +import time +from pathlib import Path + +import numpy as np +import pandas as pd + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) + +from src.data.downloader import _parquet_path, DATA_DIR + +BACKUP = PROJECT_ROOT / "data" / "_feed_backup" +SYMBOL = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"} +WICK_THR = 0.15 # detect: wick oltre 15% il cluster di close locale +REPLACE_THR = 0.02 # arbitra: sostituisci solo se Binance dissente >2% su high/low +CLOSE_THR = 0.01 # close-aware: sostituisci la barra se il CLOSE Deribit dista >1% da Binance +TF_MS = {"5m": 5, "8m": 8, "13m": 13, "15m": 15, "19m": 19, "30m": 30, "1h": 60} +_EX = None + + +def _binance(): + global _EX + if _EX is None: + import ccxt + _EX = ccxt.binance({"enableRateLimit": True}) + return _EX + + +def suspect_mask(df: pd.DataFrame) -> np.ndarray: + c = df["close"].to_numpy(float); h = df["high"].to_numpy(float); l = df["low"].to_numpy(float) + cp = np.roll(c, 1); cp[0] = c[0]; cn = np.roll(c, -1); cn[-1] = c[-1] + locmax = np.maximum.reduce([c, cp, cn]); locmin = np.minimum.reduce([c, cp, cn]) + return (h > locmax * (1 + WICK_THR)) | (l < locmin * (1 - WICK_THR)) + + +def _binance_bar(symbol: str, tf: str, ts_ms: int): + """OHLC reale Binance alla barra ts_ms (None se assente).""" + try: + rows = _binance().fetch_ohlcv(symbol, tf, since=ts_ms - 1, limit=3) + except Exception as e: + print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}") + return None + for r in rows: + if int(r[0]) == ts_ms: + return float(r[1]), float(r[2]), float(r[3]), float(r[4]) + return None + + +def clean_file(asset: str, tf: str) -> dict: + path = _parquet_path(asset, tf) + if not path.exists(): + return {"file": f"{asset}_{tf}", "skip": "no-file"} + df = pd.read_parquet(path) + mask = suspect_mask(df) + idx = np.where(mask)[0] + n0 = len(df) + if len(idx) == 0: + return {"file": f"{asset}_{tf}", "suspect": 0, "repaired": 0, "kept_real": 0, + "missing_binance": 0, "rows_before": n0, "rows_after": n0, + "still_suspect": 0, "log": []} + repaired, kept, missing = 0, 0, 0 + log = [] + for i in idx: + ts = int(df.iloc[i]["timestamp"]) + b = _binance_bar(SYMBOL[asset], tf, ts) + oh, ol = float(df.iloc[i]["high"]), float(df.iloc[i]["low"]) + if b is None: + missing += 1 + continue + bo, bh, bl, bc = b + if abs(oh - bh) / bh > REPLACE_THR or abs(ol - bl) / max(bl, 1e-9) > REPLACE_THR: + df.iat[i, df.columns.get_loc("open")] = bo + df.iat[i, df.columns.get_loc("high")] = bh + df.iat[i, df.columns.get_loc("low")] = bl + df.iat[i, df.columns.get_loc("close")] = bc + repaired += 1 + ts_s = pd.to_datetime(ts, unit="ms", utc=True).strftime("%Y-%m-%d %H:%M") + log.append(f" {ts_s} H {oh:,.0f}->{bh:,.0f} L {ol:,.0f}->{bl:,.0f}") + else: + kept += 1 # Binance conferma il wick: barra reale, intatta + if repaired: + BACKUP.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.bak") + tmp = path.with_suffix(".parquet.tmp") + df.to_parquet(tmp, index=False) + tmp.replace(path) + # validazione + df2 = pd.read_parquet(path) + still = int(suspect_mask(df2).sum()) + return {"file": f"{asset}_{tf}", "suspect": len(idx), "repaired": repaired, + "kept_real": kept, "missing_binance": missing, "rows_before": n0, + "rows_after": len(df2), "still_suspect": still, "log": log} + + +def _binance_series(asset: str, tf: str, start_ms: int, end_ms: int) -> dict: + """OHLC reale Binance per l'intero range -> dict ts_ms -> (o,h,l,c). Bulk paginato.""" + ex = _binance() + tf_ms = TF_MS[tf] * 60 * 1000 + out: dict[int, tuple] = {} + since = start_ms + while since <= end_ms: + try: + rows = ex.fetch_ohlcv(SYMBOL[asset], tf, since=since, limit=1000) + except Exception as e: + print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}") + break + if not rows: + break + for r in rows: + out[int(r[0])] = (float(r[1]), float(r[2]), float(r[3]), float(r[4])) + nxt = int(rows[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + if len(rows) < 1000 and since > end_ms: + break + return out + + +def clean_file_close(asset: str, tf: str, thr: float = CLOSE_THR, backup_dir: Path | None = None) -> dict: + """CLOSE-AWARE: sostituisce O/H/L/C con Binance per ogni barra il cui CLOSE Deribit + dista > thr da Binance (1% default). Cattura i print 'silenziosi' che il wick-check + >15% non vede (close fantasma su barra di range piccolo). Fonte di verita' = Binance + spot (il feed storico e' perp testnet -> inaffidabile; lo spot ~ mainnet via arbitraggio).""" + if tf not in TF_MS: + return {"file": f"{asset}_{tf}", "skip": "tf-non-binance"} + path = _parquet_path(asset, tf) + if not path.exists(): + return {"file": f"{asset}_{tf}", "skip": "no-file"} + df = pd.read_parquet(path) + n0 = len(df) + tms = df["timestamp"].to_numpy("int64") + c = df["close"].to_numpy(float) + bz = _binance_series(asset, tf, int(tms[0]), int(tms[-1])) + col = {k: df.columns.get_loc(k) for k in ("open", "high", "low", "close")} + fixed, by_year, missing = 0, {}, 0 + log = [] + for i in range(n0): + b = bz.get(int(tms[i])) + if b is None: + missing += 1 + continue + bo, bh, bl, bc = b + if bc <= 0: + continue + orig = float(c[i]) # cattura PRIMA della scrittura (to_numpy puo' essere una view) + if abs(orig - bc) / bc > thr: + df.iat[i, col["open"]] = bo + df.iat[i, col["high"]] = bh + df.iat[i, col["low"]] = bl + df.iat[i, col["close"]] = bc + fixed += 1 + y = pd.to_datetime(int(tms[i]), unit="ms", utc=True).year + by_year[y] = by_year.get(y, 0) + 1 + if len(log) < 10: + ts_s = pd.to_datetime(int(tms[i]), unit="ms", utc=True).strftime("%Y-%m-%d %H:%M") + log.append(f" {ts_s} C {orig:,.2f}->{bc:,.2f} ({abs(orig-bc)/bc*100:.1f}%)") + if fixed: + bdir = backup_dir or BACKUP + bdir.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, bdir / f"{asset.lower()}_{tf}.parquet.bak") + tmp = path.with_suffix(".parquet.tmp") + df.to_parquet(tmp, index=False) + tmp.replace(path) + # validazione: ri-scan, 0 barre residue oltre soglia (fra quelle coperte da Binance) + df2 = pd.read_parquet(path) + c2 = df2["close"].to_numpy(float) + still = sum(1 for i in range(len(df2)) + if (b := bz.get(int(tms[i]))) and b[3] > 0 and abs(c2[i] - b[3]) / b[3] > thr) + return {"file": f"{asset}_{tf}", "covered": n0 - missing, "fixed": fixed, + "missing_binance": missing, "rows_before": n0, "rows_after": len(df2), + "still_over_thr": still, "by_year": by_year, "log": log} + + +def main(): + args = [a for a in sys.argv[1:] if not a.startswith("--")] + close_mode = "--close" in sys.argv + dry = "--dry" in sys.argv + if close_mode: + targets = args or [f"{a}_{tf}" for a in ("BTC", "ETH") for tf in ("5m", "15m", "1h")] + stamp = time.strftime("%Y%m%d-%H%M%S") + bdir = BACKUP / f"pre_close_clean_{stamp}" + print(f"CLEAN FEED (close-aware vs Binance, thr={CLOSE_THR*100:.0f}%) — " + f"{'DRY-RUN (nessuna scrittura)' if dry else f'backup in {bdir}'}\n") + grand = 0 + for t in targets: + asset, tf = t.split("_", 1) + if dry: + # dry: conta soltanto, niente scrittura + r = _dry_close(asset, tf) + else: + r = clean_file_close(asset, tf, backup_dir=bdir) + if r.get("skip"): + print(f" {t:<9} SKIP ({r['skip']})"); continue + grand += r.get("fixed", 0) + yr = " ".join(f"{y}:{n}" for y, n in sorted(r.get("by_year", {}).items())) + print(f" {r['file']:<9} coperte={r.get('covered',0):>7} riparate={r.get('fixed',0):>4} " + f"no-binance={r.get('missing_binance',0):>5} | righe {r['rows_before']}=={r.get('rows_after',r['rows_before'])} " + f"residue>thr={r.get('still_over_thr','-')}") + if yr: + print(f" per anno: {yr}") + for line in r.get("log", []): + print(line) + print(f"\n TOTALE barre riparate (close-aware): {grand}") + return + targets = args or [f"{a}_{tf}" for a in ("BTC", "ETH") for tf in ("5m", "15m", "30m", "1h")] + print(f"CLEAN FEED — backup in {BACKUP}\n") + grand = 0 + for t in targets: + asset, tf = t.split("_", 1) + r = clean_file(asset, tf) + if r.get("skip"): + print(f" {t:<9} SKIP ({r['skip']})"); continue + grand += r.get("repaired", 0) + print(f" {r['file']:<9} sospette={r['suspect']:>3} riparate={r['repaired']:>3} " + f"reali-tenute={r.get('kept_real',0):>3} no-binance={r.get('missing_binance',0):>2} " + f"| righe {r['rows_before']}=={r['rows_after']} residue-sospette={r['still_suspect']}") + for line in r.get("log", [])[:8]: + print(line) + if len(r.get("log", [])) > 8: + print(f" ... (+{len(r['log'])-8} altre)") + print(f"\n TOTALE barre riparate: {grand}") + + +def _dry_close(asset: str, tf: str, thr: float = CLOSE_THR) -> dict: + """Conta soltanto quante barre verrebbero riparate (nessuna scrittura).""" + if tf not in TF_MS: + return {"file": f"{asset}_{tf}", "skip": "tf-non-binance"} + path = _parquet_path(asset, tf) + if not path.exists(): + return {"file": f"{asset}_{tf}", "skip": "no-file"} + df = pd.read_parquet(path) + tms = df["timestamp"].to_numpy("int64"); c = df["close"].to_numpy(float) + bz = _binance_series(asset, tf, int(tms[0]), int(tms[-1])) + fixed, by_year, missing = 0, {}, 0 + for i in range(len(df)): + b = bz.get(int(tms[i])) + if b is None: + missing += 1; continue + if b[3] > 0 and abs(c[i] - b[3]) / b[3] > thr: + fixed += 1 + y = pd.to_datetime(int(tms[i]), unit="ms", utc=True).year + by_year[y] = by_year.get(y, 0) + 1 + return {"file": f"{asset}_{tf}", "covered": len(df) - missing, "fixed": fixed, + "missing_binance": missing, "rows_before": len(df), "by_year": by_year, "log": []} + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/combine_portfolio.py b/Old/scripts/analysis/combine_portfolio.py similarity index 100% rename from scripts/analysis/combine_portfolio.py rename to Old/scripts/analysis/combine_portfolio.py diff --git a/scripts/analysis/combine_v2.py b/Old/scripts/analysis/combine_v2.py similarity index 100% rename from scripts/analysis/combine_v2.py rename to Old/scripts/analysis/combine_v2.py diff --git a/scripts/analysis/daily_equity_bfill_impact.py b/Old/scripts/analysis/daily_equity_bfill_impact.py similarity index 100% rename from scripts/analysis/daily_equity_bfill_impact.py rename to Old/scripts/analysis/daily_equity_bfill_impact.py diff --git a/Old/scripts/analysis/deribit_history_plan.py b/Old/scripts/analysis/deribit_history_plan.py new file mode 100644 index 0000000..7f0d9de --- /dev/null +++ b/Old/scripts/analysis/deribit_history_plan.py @@ -0,0 +1,146 @@ +"""PIANO STORICO DERIBIT — quanta storia copre davvero il venue dove ESEGUIAMO? + +Obiettivo: scegliere la fonte migliore per ricostruire lo storico di backtest, dato +che si esegue su Deribit. Principio (gia' misurato in multi_source_check): l'ancora +giusta e' il VENUE DI ESECUZIONE, non Binance/USDT. Qui rispondo con i numeri a: + + 1. COPERTURA: da quando esiste OHLCV su Deribit MAINNET (ccxt pubblico, no token) per + gli strumenti che tradiamo — inverse (BTC/ETH-PERPETUAL) e lineari USDC. + 2. TIMEFRAME nativi disponibili su Deribit. + 3. FEDELTA' inverse-vs-lineare (stesso indice? -> posso usare l'inverse, storia lunga, + come price-series e i lineari recenti sono ridondanti per il PREZZO). + 4. GAP pre-Deribit: quanto indietro vanno le strategie e cosa manca -> da gap-fillare + con Coinbase USD (spot, NON USDT). + +Tutto via ccxt pubblico Deribit (= api.deribit.com mainnet, reale). Non modifica nulla. + + uv run python scripts/analysis/deribit_history_plan.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np +import pandas as pd +import ccxt + +DERIBIT = ccxt.deribit({"enableRateLimit": True}) +COINBASE = ccxt.coinbase({"enableRateLimit": True}) + + +def earliest(symbol: str, tf: str = "1d") -> tuple[str | None, int, str | None]: + """Trova la prima candela disponibile (probe since 2016) + n candele totali stimate.""" + since = int(pd.Timestamp("2016-01-01", tz="UTC").timestamp() * 1000) + try: + rows = DERIBIT.fetch_ohlcv(symbol, tf, since=since, limit=5000) + except Exception as e: + return None, 0, f"{type(e).__name__}: {str(e)[:60]}" + if not rows: + return None, 0, "no-data" + first = pd.to_datetime(int(rows[0][0]), unit="ms", utc=True) + last = pd.to_datetime(int(rows[-1][0]), unit="ms", utc=True) + return f"{first.date()} -> {last.date()}", len(rows), None + + +def list_perps() -> dict: + """Risolve i simboli ccxt reali dei perp Deribit per BTC/ETH (inverse + lineari).""" + DERIBIT.load_markets() + found = {} + for sym, m in DERIBIT.markets.items(): + if not m.get("swap"): + continue + base = m.get("base") + if base not in ("BTC", "ETH"): + continue + settle = m.get("settle") + kind = "inverse" if m.get("inverse") else "linear" + found[f"{base}-{kind}({settle})"] = sym + return found + + +def fetch_series(ex, symbol, tf, start, end, limit=1000): + start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) + tf_ms = ex.parse_timeframe(tf) * 1000 + out, since = {}, start_ms + while since <= end_ms: + try: + rows = ex.fetch_ohlcv(symbol, tf, since=since, limit=limit) + except Exception: + break + if not rows: + break + for r in rows: + if start_ms <= int(r[0]) <= end_ms and r[4]: + out[int(r[0])] = float(r[4]) + nxt = int(rows[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + if len(rows) < limit and since > end_ms: + break + return pd.Series(out) + + +def dev_bps(a: pd.Series, b: pd.Series) -> tuple[int, float, float, float]: + df = pd.concat([a.rename("a"), b.rename("b")], axis=1, join="inner") + if len(df) == 0: + return 0, 0, 0, 0 + d = (df["a"] - df["b"]).abs() / df["b"] * 1e4 + return len(df), float(d.median()), float(d.quantile(.95)), float(d.max()) + + +def main(): + print("=" * 84) + print(" PIANO STORICO DERIBIT MAINNET (ccxt pubblico, reale)") + print("=" * 84) + + print("\n[1] Simboli perp Deribit BTC/ETH risolti:") + perps = list_perps() + for k, v in perps.items(): + print(f" {k:<22s} -> {v}") + + print("\n[2] COPERTURA storica (1d, probe da 2016):") + print(f" {'strumento':<22s}{'range disponibile':<28s}{'giorni':>8s}") + cov = {} + for k, sym in perps.items(): + rng, n, err = earliest(sym, "1d") + cov[k] = (sym, rng, n) + print(f" {k:<22s}{(rng or err or '-'):<28s}{n:>8d}") + + print("\n[3] TIMEFRAME nativi Deribit (test su BTC inverse, oggi):") + bsym = next((s for k, s in perps.items() if k.startswith("BTC-inverse")), "BTC/USD:BTC") + tfs = [] + for tf in ("1m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "3h", "4h", "6h", "12h", "1d"): + try: + r = DERIBIT.fetch_ohlcv(bsym, tf, limit=3) + tfs.append(tf if r else f"{tf}:vuoto") + except Exception: + tfs.append(f"{tf}:NO") + print(f" ok: {[t for t in tfs if ':' not in t]}") + print(f" ko: {[t for t in tfs if ':' in t]}") + + print("\n[4] FEDELTA' inverse-vs-lineare USDC (close 1h, ultimi ~40g):") + for base in ("BTC", "ETH"): + inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None) + lin = next((s for k, s in perps.items() if k.startswith(f"{base}-linear")), None) + if not inv or not lin: + print(f" {base}: manca inverse o lineare"); continue + a = fetch_series(DERIBIT, inv, "1h", "2026-04-15", "2026-05-27") + b = fetch_series(DERIBIT, lin, "1h", "2026-04-15", "2026-05-27") + n, med, p95, mx = dev_bps(a, b) + print(f" {base}: barre={n} inverse-vs-lineare med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}") + + print("\n[5] GAP pre-Deribit: Deribit inverse vs Coinbase USD su finestra PROFONDA (2020-06, 1d):") + for base in ("BTC", "ETH"): + inv = next((s for k, s in perps.items() if k.startswith(f"{base}-inverse")), None) + a = fetch_series(DERIBIT, inv, "1d", "2020-06-01", "2020-09-01") + b = fetch_series(COINBASE, f"{base}/USD", "1d", "2020-06-01", "2020-09-01", limit=300) + n, med, p95, mx = dev_bps(a, b) + cov_first = cov.get(f"{base}-inverse(BTC)" if base == "BTC" else f"{base}-inverse(ETH)", (None, "?", 0))[1] + print(f" {base}: Deribit-vs-Coinbase barre={n} med {med:.1f} bps p95 {p95:.1f} max {mx:.1f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/dip01_exit16_impact.py b/Old/scripts/analysis/dip01_exit16_impact.py similarity index 100% rename from scripts/analysis/dip01_exit16_impact.py rename to Old/scripts/analysis/dip01_exit16_impact.py diff --git a/scripts/analysis/dispersion_edges/gate_index_comp_disp.py b/Old/scripts/analysis/dispersion_edges/gate_index_comp_disp.py similarity index 100% rename from scripts/analysis/dispersion_edges/gate_index_comp_disp.py rename to Old/scripts/analysis/dispersion_edges/gate_index_comp_disp.py diff --git a/scripts/analysis/dispersion_edges/index_comp_disp.py b/Old/scripts/analysis/dispersion_edges/index_comp_disp.py similarity index 100% rename from scripts/analysis/dispersion_edges/index_comp_disp.py rename to Old/scripts/analysis/dispersion_edges/index_comp_disp.py diff --git a/scripts/analysis/dispersion_edges/rel_idio_fade.py b/Old/scripts/analysis/dispersion_edges/rel_idio_fade.py similarity index 100% rename from scripts/analysis/dispersion_edges/rel_idio_fade.py rename to Old/scripts/analysis/dispersion_edges/rel_idio_fade.py diff --git a/scripts/analysis/dispersion_lab.py b/Old/scripts/analysis/dispersion_lab.py similarity index 100% rename from scripts/analysis/dispersion_lab.py rename to Old/scripts/analysis/dispersion_lab.py diff --git a/scripts/analysis/drift_monitor.py b/Old/scripts/analysis/drift_monitor.py similarity index 100% rename from scripts/analysis/drift_monitor.py rename to Old/scripts/analysis/drift_monitor.py diff --git a/scripts/analysis/eth_collar_gate.py b/Old/scripts/analysis/eth_collar_gate.py similarity index 100% rename from scripts/analysis/eth_collar_gate.py rename to Old/scripts/analysis/eth_collar_gate.py diff --git a/scripts/analysis/exit16_port06_impact.py b/Old/scripts/analysis/exit16_port06_impact.py similarity index 100% rename from scripts/analysis/exit16_port06_impact.py rename to Old/scripts/analysis/exit16_port06_impact.py diff --git a/scripts/analysis/exit_lab.py b/Old/scripts/analysis/exit_lab.py similarity index 100% rename from scripts/analysis/exit_lab.py rename to Old/scripts/analysis/exit_lab.py diff --git a/scripts/analysis/exit_policies/01_trail_atr_ride.py b/Old/scripts/analysis/exit_policies/01_trail_atr_ride.py similarity index 100% rename from scripts/analysis/exit_policies/01_trail_atr_ride.py rename to Old/scripts/analysis/exit_policies/01_trail_atr_ride.py diff --git a/scripts/analysis/exit_policies/02_trail_atr_keep_tp.py b/Old/scripts/analysis/exit_policies/02_trail_atr_keep_tp.py similarity index 100% rename from scripts/analysis/exit_policies/02_trail_atr_keep_tp.py rename to Old/scripts/analysis/exit_policies/02_trail_atr_keep_tp.py diff --git a/scripts/analysis/exit_policies/03_trail_pct.py b/Old/scripts/analysis/exit_policies/03_trail_pct.py similarity index 100% rename from scripts/analysis/exit_policies/03_trail_pct.py rename to Old/scripts/analysis/exit_policies/03_trail_pct.py diff --git a/scripts/analysis/exit_policies/04_breakeven.py b/Old/scripts/analysis/exit_policies/04_breakeven.py similarity index 100% rename from scripts/analysis/exit_policies/04_breakeven.py rename to Old/scripts/analysis/exit_policies/04_breakeven.py diff --git a/scripts/analysis/exit_policies/05_ratchet.py b/Old/scripts/analysis/exit_policies/05_ratchet.py similarity index 100% rename from scripts/analysis/exit_policies/05_ratchet.py rename to Old/scripts/analysis/exit_policies/05_ratchet.py diff --git a/scripts/analysis/exit_policies/06_sar_trail.py b/Old/scripts/analysis/exit_policies/06_sar_trail.py similarity index 100% rename from scripts/analysis/exit_policies/06_sar_trail.py rename to Old/scripts/analysis/exit_policies/06_sar_trail.py diff --git a/scripts/analysis/exit_policies/07_tp_decay.py b/Old/scripts/analysis/exit_policies/07_tp_decay.py similarity index 100% rename from scripts/analysis/exit_policies/07_tp_decay.py rename to Old/scripts/analysis/exit_policies/07_tp_decay.py diff --git a/scripts/analysis/exit_policies/08_sl_tighten.py b/Old/scripts/analysis/exit_policies/08_sl_tighten.py similarity index 100% rename from scripts/analysis/exit_policies/08_sl_tighten.py rename to Old/scripts/analysis/exit_policies/08_sl_tighten.py diff --git a/scripts/analysis/exit_policies/09_tp_extend_momentum.py b/Old/scripts/analysis/exit_policies/09_tp_extend_momentum.py similarity index 100% rename from scripts/analysis/exit_policies/09_tp_extend_momentum.py rename to Old/scripts/analysis/exit_policies/09_tp_extend_momentum.py diff --git a/scripts/analysis/exit_policies/10_tp_moving_mean.py b/Old/scripts/analysis/exit_policies/10_tp_moving_mean.py similarity index 100% rename from scripts/analysis/exit_policies/10_tp_moving_mean.py rename to Old/scripts/analysis/exit_policies/10_tp_moving_mean.py diff --git a/scripts/analysis/exit_policies/11_z_overshoot.py b/Old/scripts/analysis/exit_policies/11_z_overshoot.py similarity index 100% rename from scripts/analysis/exit_policies/11_z_overshoot.py rename to Old/scripts/analysis/exit_policies/11_z_overshoot.py diff --git a/scripts/analysis/exit_policies/12_partial_tp_trail.py b/Old/scripts/analysis/exit_policies/12_partial_tp_trail.py similarity index 100% rename from scripts/analysis/exit_policies/12_partial_tp_trail.py rename to Old/scripts/analysis/exit_policies/12_partial_tp_trail.py diff --git a/scripts/analysis/exit_policies/13_hurst_exit.py b/Old/scripts/analysis/exit_policies/13_hurst_exit.py similarity index 100% rename from scripts/analysis/exit_policies/13_hurst_exit.py rename to Old/scripts/analysis/exit_policies/13_hurst_exit.py diff --git a/scripts/analysis/exit_policies/14_giveback.py b/Old/scripts/analysis/exit_policies/14_giveback.py similarity index 100% rename from scripts/analysis/exit_policies/14_giveback.py rename to Old/scripts/analysis/exit_policies/14_giveback.py diff --git a/scripts/analysis/exit_policies/15_loser_timestop.py b/Old/scripts/analysis/exit_policies/15_loser_timestop.py similarity index 100% rename from scripts/analysis/exit_policies/15_loser_timestop.py rename to Old/scripts/analysis/exit_policies/15_loser_timestop.py diff --git a/scripts/analysis/exit_policies/16_close_confirm_sl.py b/Old/scripts/analysis/exit_policies/16_close_confirm_sl.py similarity index 100% rename from scripts/analysis/exit_policies/16_close_confirm_sl.py rename to Old/scripts/analysis/exit_policies/16_close_confirm_sl.py diff --git a/scripts/analysis/exit_policies/17_wide_sl_trail.py b/Old/scripts/analysis/exit_policies/17_wide_sl_trail.py similarity index 100% rename from scripts/analysis/exit_policies/17_wide_sl_trail.py rename to Old/scripts/analysis/exit_policies/17_wide_sl_trail.py diff --git a/scripts/analysis/exit_policies/18_swing_stop.py b/Old/scripts/analysis/exit_policies/18_swing_stop.py similarity index 100% rename from scripts/analysis/exit_policies/18_swing_stop.py rename to Old/scripts/analysis/exit_policies/18_swing_stop.py diff --git a/scripts/analysis/exit_policies/19_donchian_trail.py b/Old/scripts/analysis/exit_policies/19_donchian_trail.py similarity index 100% rename from scripts/analysis/exit_policies/19_donchian_trail.py rename to Old/scripts/analysis/exit_policies/19_donchian_trail.py diff --git a/scripts/analysis/exit_policies/20_ema_cross_exit.py b/Old/scripts/analysis/exit_policies/20_ema_cross_exit.py similarity index 100% rename from scripts/analysis/exit_policies/20_ema_cross_exit.py rename to Old/scripts/analysis/exit_policies/20_ema_cross_exit.py diff --git a/scripts/analysis/exit_policies/21_vol_rescale.py b/Old/scripts/analysis/exit_policies/21_vol_rescale.py similarity index 100% rename from scripts/analysis/exit_policies/21_vol_rescale.py rename to Old/scripts/analysis/exit_policies/21_vol_rescale.py diff --git a/scripts/analysis/exit_policies/22_no_sl.py b/Old/scripts/analysis/exit_policies/22_no_sl.py similarity index 100% rename from scripts/analysis/exit_policies/22_no_sl.py rename to Old/scripts/analysis/exit_policies/22_no_sl.py diff --git a/scripts/analysis/exit_policies/23_sl_tp_ride.py b/Old/scripts/analysis/exit_policies/23_sl_tp_ride.py similarity index 100% rename from scripts/analysis/exit_policies/23_sl_tp_ride.py rename to Old/scripts/analysis/exit_policies/23_sl_tp_ride.py diff --git a/scripts/analysis/exit_policies/verify_02_overfit.py b/Old/scripts/analysis/exit_policies/verify_02_overfit.py similarity index 100% rename from scripts/analysis/exit_policies/verify_02_overfit.py rename to Old/scripts/analysis/exit_policies/verify_02_overfit.py diff --git a/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_leakage.py b/Old/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_leakage.py similarity index 100% rename from scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_leakage.py rename to Old/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_leakage.py diff --git a/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_stress.py b/Old/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_stress.py similarity index 100% rename from scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_stress.py rename to Old/scripts/analysis/exit_policies/verify_02_trail_atr_keep_tp_stress.py diff --git a/scripts/analysis/exit_policies/verify_16_leakage.py b/Old/scripts/analysis/exit_policies/verify_16_leakage.py similarity index 100% rename from scripts/analysis/exit_policies/verify_16_leakage.py rename to Old/scripts/analysis/exit_policies/verify_16_leakage.py diff --git a/scripts/analysis/exit_policies/verify_16_overfit.py b/Old/scripts/analysis/exit_policies/verify_16_overfit.py similarity index 100% rename from scripts/analysis/exit_policies/verify_16_overfit.py rename to Old/scripts/analysis/exit_policies/verify_16_overfit.py diff --git a/scripts/analysis/exit_policies/verify_16_stress.py b/Old/scripts/analysis/exit_policies/verify_16_stress.py similarity index 100% rename from scripts/analysis/exit_policies/verify_16_stress.py rename to Old/scripts/analysis/exit_policies/verify_16_stress.py diff --git a/scripts/analysis/exit_policies/verify_22_no_sl_leakage.py b/Old/scripts/analysis/exit_policies/verify_22_no_sl_leakage.py similarity index 100% rename from scripts/analysis/exit_policies/verify_22_no_sl_leakage.py rename to Old/scripts/analysis/exit_policies/verify_22_no_sl_leakage.py diff --git a/scripts/analysis/exit_policies/verify_22_no_sl_overfit.py b/Old/scripts/analysis/exit_policies/verify_22_no_sl_overfit.py similarity index 100% rename from scripts/analysis/exit_policies/verify_22_no_sl_overfit.py rename to Old/scripts/analysis/exit_policies/verify_22_no_sl_overfit.py diff --git a/scripts/analysis/exit_policies/verify_22_no_sl_stress.py b/Old/scripts/analysis/exit_policies/verify_22_no_sl_stress.py similarity index 100% rename from scripts/analysis/exit_policies/verify_22_no_sl_stress.py rename to Old/scripts/analysis/exit_policies/verify_22_no_sl_stress.py diff --git a/scripts/analysis/exit_policies/verify_tail_risk.py b/Old/scripts/analysis/exit_policies/verify_tail_risk.py similarity index 100% rename from scripts/analysis/exit_policies/verify_tail_risk.py rename to Old/scripts/analysis/exit_policies/verify_tail_risk.py diff --git a/scripts/analysis/explore_lab.py b/Old/scripts/analysis/explore_lab.py similarity index 100% rename from scripts/analysis/explore_lab.py rename to Old/scripts/analysis/explore_lab.py diff --git a/scripts/analysis/fade15m_port06_gate.py b/Old/scripts/analysis/fade15m_port06_gate.py similarity index 100% rename from scripts/analysis/fade15m_port06_gate.py rename to Old/scripts/analysis/fade15m_port06_gate.py diff --git a/scripts/analysis/fade_diag_by_regime.py b/Old/scripts/analysis/fade_diag_by_regime.py similarity index 100% rename from scripts/analysis/fade_diag_by_regime.py rename to Old/scripts/analysis/fade_diag_by_regime.py diff --git a/scripts/analysis/fade_loss_by_regime.py b/Old/scripts/analysis/fade_loss_by_regime.py similarity index 100% rename from scripts/analysis/fade_loss_by_regime.py rename to Old/scripts/analysis/fade_loss_by_regime.py diff --git a/scripts/analysis/fade_lossguard_port_test.py b/Old/scripts/analysis/fade_lossguard_port_test.py similarity index 100% rename from scripts/analysis/fade_lossguard_port_test.py rename to Old/scripts/analysis/fade_lossguard_port_test.py diff --git a/scripts/analysis/fade_lossguard_workflow.js b/Old/scripts/analysis/fade_lossguard_workflow.js similarity index 100% rename from scripts/analysis/fade_lossguard_workflow.js rename to Old/scripts/analysis/fade_lossguard_workflow.js diff --git a/scripts/analysis/fade_tf_sweep.py b/Old/scripts/analysis/fade_tf_sweep.py similarity index 100% rename from scripts/analysis/fade_tf_sweep.py rename to Old/scripts/analysis/fade_tf_sweep.py diff --git a/scripts/analysis/fractal_argo_master_corr.py b/Old/scripts/analysis/fractal_argo_master_corr.py similarity index 100% rename from scripts/analysis/fractal_argo_master_corr.py rename to Old/scripts/analysis/fractal_argo_master_corr.py diff --git a/scripts/analysis/fractal_argo_peryear.py b/Old/scripts/analysis/fractal_argo_peryear.py similarity index 100% rename from scripts/analysis/fractal_argo_peryear.py rename to Old/scripts/analysis/fractal_argo_peryear.py diff --git a/scripts/analysis/fractal_argo_workflow.js b/Old/scripts/analysis/fractal_argo_workflow.js similarity index 100% rename from scripts/analysis/fractal_argo_workflow.js rename to Old/scripts/analysis/fractal_argo_workflow.js diff --git a/scripts/analysis/funding_carry_research.py b/Old/scripts/analysis/funding_carry_research.py similarity index 100% rename from scripts/analysis/funding_carry_research.py rename to Old/scripts/analysis/funding_carry_research.py diff --git a/scripts/analysis/grid_game_gate.py b/Old/scripts/analysis/grid_game_gate.py similarity index 100% rename from scripts/analysis/grid_game_gate.py rename to Old/scripts/analysis/grid_game_gate.py diff --git a/scripts/analysis/honest_final.py b/Old/scripts/analysis/honest_final.py similarity index 100% rename from scripts/analysis/honest_final.py rename to Old/scripts/analysis/honest_final.py diff --git a/scripts/analysis/honest_improve.py b/Old/scripts/analysis/honest_improve.py similarity index 100% rename from scripts/analysis/honest_improve.py rename to Old/scripts/analysis/honest_improve.py diff --git a/scripts/analysis/honest_improve2.py b/Old/scripts/analysis/honest_improve2.py similarity index 100% rename from scripts/analysis/honest_improve2.py rename to Old/scripts/analysis/honest_improve2.py diff --git a/scripts/analysis/honest_lab.py b/Old/scripts/analysis/honest_lab.py similarity index 100% rename from scripts/analysis/honest_lab.py rename to Old/scripts/analysis/honest_lab.py diff --git a/scripts/analysis/honest_matrix.py b/Old/scripts/analysis/honest_matrix.py similarity index 100% rename from scripts/analysis/honest_matrix.py rename to Old/scripts/analysis/honest_matrix.py diff --git a/scripts/analysis/honest_rotation.py b/Old/scripts/analysis/honest_rotation.py similarity index 100% rename from scripts/analysis/honest_rotation.py rename to Old/scripts/analysis/honest_rotation.py diff --git a/scripts/analysis/honest_trend.py b/Old/scripts/analysis/honest_trend.py similarity index 100% rename from scripts/analysis/honest_trend.py rename to Old/scripts/analysis/honest_trend.py diff --git a/scripts/analysis/intrabar_test.py b/Old/scripts/analysis/intrabar_test.py similarity index 100% rename from scripts/analysis/intrabar_test.py rename to Old/scripts/analysis/intrabar_test.py diff --git a/scripts/analysis/ladder_regate_clean.py b/Old/scripts/analysis/ladder_regate_clean.py similarity index 100% rename from scripts/analysis/ladder_regate_clean.py rename to Old/scripts/analysis/ladder_regate_clean.py diff --git a/scripts/analysis/ladder_search.py b/Old/scripts/analysis/ladder_search.py similarity index 100% rename from scripts/analysis/ladder_search.py rename to Old/scripts/analysis/ladder_search.py diff --git a/scripts/analysis/ladder_sltp_study.py b/Old/scripts/analysis/ladder_sltp_study.py similarity index 100% rename from scripts/analysis/ladder_sltp_study.py rename to Old/scripts/analysis/ladder_sltp_study.py diff --git a/scripts/analysis/ledger_vs_backtest.py b/Old/scripts/analysis/ledger_vs_backtest.py similarity index 100% rename from scripts/analysis/ledger_vs_backtest.py rename to Old/scripts/analysis/ledger_vs_backtest.py diff --git a/scripts/analysis/leverage_sweep.py b/Old/scripts/analysis/leverage_sweep.py similarity index 100% rename from scripts/analysis/leverage_sweep.py rename to Old/scripts/analysis/leverage_sweep.py diff --git a/scripts/analysis/live_exec_smoke.py b/Old/scripts/analysis/live_exec_smoke.py similarity index 100% rename from scripts/analysis/live_exec_smoke.py rename to Old/scripts/analysis/live_exec_smoke.py diff --git a/scripts/analysis/live_pairs_smoke.py b/Old/scripts/analysis/live_pairs_smoke.py similarity index 100% rename from scripts/analysis/live_pairs_smoke.py rename to Old/scripts/analysis/live_pairs_smoke.py diff --git a/Old/scripts/analysis/live_real_yearly.py b/Old/scripts/analysis/live_real_yearly.py new file mode 100644 index 0000000..685788b --- /dev/null +++ b/Old/scripts/analysis/live_real_yearly.py @@ -0,0 +1,74 @@ +"""Statistica storica PER ANNO degli sleeve TUTTORA in LIVE REALE (no paper). + +Config live = micro-test mainnet PORT06 Fase 1 (portfolios.yml): nel POOL reale +ci sono SOLO i 7 single-leg con esecuzione reale su Deribit mainnet: + - 6 FADE 15m: MR01/MR02/MR07 x BTC/ETH + - DIP01 (BTC 1h) +Tutto il resto (pairs PR01, SH01, TR01/ROT02/TSM01/XS01) e' PAPER -> escluso. + +Backtest NETTO fee, leva 3x, pos 15%/sleeve, finestra 2021-2026, OOS = ultimo 30%. +NB: i fade live girano col filtro trend 3.0 (gia' applicato in fade_daily_equity); +i guard live (EXIT-16 confirm, freeze-gate, ecc.) agiscono SOLO sul path live -> +il backtest canonico NON e' filtrato (il live fa MEGLIO sul DD). +""" +from __future__ import annotations + +import sys +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) + +from scripts.analysis.combine_portfolio import ( + build_all_sleeves, port_returns, metrics, yearly_returns, SPLIT, OOS_DATE, IDX, +) + +YEARS = sorted(set(IDX.year)) + +# I 7 sleeve eseguiti reale (vedi portfolios.yml -> execution.sleeves + DIP01). +LIVE_REAL = ["MR01_BTC", "MR01_ETH", "MR02_BTC", "MR02_ETH", + "MR07_BTC", "MR07_ETH", "DIP01_BTC"] + + +def main(): + print("Costruzione equity giornaliere (puo' richiedere ~1-2 min)...\n") + S = build_all_sleeves() + live = {k: S[k] for k in LIVE_REAL} # filtra ai soli 7 reali + + # ---------- (A) RET% NETTO per anno, per sleeve ---------- + print("=" * 96) + print(" (A) RET% NETTO PER ANNO — sleeve in LIVE REALE (no paper) | leva 3x, fee netta, pos 15%") + print("=" * 96) + print(f" {'sleeve':<14s}" + "".join(f"{y:>9d}" for y in YEARS)) + print(" " + "-" * 90) + yr_each = {k: yearly_returns(v.pct_change().fillna(0.0)) for k, v in live.items()} + for k in LIVE_REAL: + print(f" {k.replace('_',' '):<14s}" + "".join(f"{yr_each[k].get(y, 0):>+9.0f}" for y in YEARS)) + # pool equal-weight dei 7 (== il pool reale del micro-test) + pool = port_returns(live) + print(" " + "-" * 90) + yr_pool = yearly_returns(pool) + print(f" {'POOL-7 (eq)':<14s}" + "".join(f"{yr_pool.get(y, 0):>+9.0f}" for y in YEARS)) + + # ---------- (B) metriche FULL / OOS per sleeve + pool ---------- + print("\n" + "=" * 96) + print(f" (B) METRICHE — FULL (2021->) | OOS da {OOS_DATE} (ultimo 30%)") + print("=" * 96) + print(f" {'sleeve':<14s}{'Ret%':>9s}{'CAGR':>7s}{'DD%':>7s}{'Shrp':>7s}" + f" | {'oRet%':>9s}{'oDD%':>7s}{'oShrp':>7s}") + print(" " + "-" * 80) + for k in LIVE_REAL: + dr = live[k].pct_change().fillna(0.0) + f, o = metrics(dr), metrics(dr, lo=SPLIT) + print(f" {k.replace('_',' '):<14s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}" + f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}") + print(" " + "-" * 80) + f, o = metrics(pool), metrics(pool, lo=SPLIT) + print(f" {'POOL-7 (eq)':<14s}{f['ret']:>+9.0f}{f['cagr']:>7.0f}{f['dd']:>7.1f}{f['sharpe']:>7.2f}" + f" | {o['ret']:>+9.0f}{o['dd']:>7.1f}{o['sharpe']:>7.2f}") + print("\n NB: il backtest canonico NON applica i guard live (EXIT-16 confirm, freeze-gate,") + print(" TP_PHANTOM, ...) -> sul DD il live fa MEGLIO del backtest. Numeri leva 3x.") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/live_shadow_smoke.py b/Old/scripts/analysis/live_shadow_smoke.py similarity index 100% rename from scripts/analysis/live_shadow_smoke.py rename to Old/scripts/analysis/live_shadow_smoke.py diff --git a/scripts/analysis/live_smoke_pairs.py b/Old/scripts/analysis/live_smoke_pairs.py similarity index 100% rename from scripts/analysis/live_smoke_pairs.py rename to Old/scripts/analysis/live_smoke_pairs.py diff --git a/Old/scripts/analysis/mainnet_fade_check.py b/Old/scripts/analysis/mainnet_fade_check.py new file mode 100644 index 0000000..558a77a --- /dev/null +++ b/Old/scripts/analysis/mainnet_fade_check.py @@ -0,0 +1,123 @@ +"""VERIFICA DEFINITIVA — la fade ha edge sul perp Deribit MAINNET REALE? + +Testnet feed = print fantasma (edge finto). Binance spot = fade morta. Resta da +escludere che la fade viva sulla microstruttura del perp Deribit MAINNET reale (dove +ESEGUE davvero). Scarico lo storico 15m reale (BTC/ETH-PERPETUAL mainnet via Cerbero v2, +token .env.mainnet) e giro lo STESSO engine. Cache in data/raw/_mainnet_*.parquet +(NON tocca i file canonici). +""" +from __future__ import annotations +import sys +from pathlib import Path +from datetime import datetime, timedelta +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import requests, numpy as np, pandas as pd +from src.live.cerbero_client import TESTNET_TOKEN +from scripts.analysis.risk_management import build_trades, strats_for + +URL = "https://cerbero-mcp.tielogic.xyz/mcp/tools/get_historical" +START, END = "2020-06-01", "2026-05-26" +YEARS = [2021, 2022, 2023, 2024, 2025, 2026] +INSTR = {"BTC": "BTC-PERPETUAL", "ETH": "ETH-PERPETUAL"} + + +def _token(): + env = {} + for ln in (PROJECT_ROOT / ".env.mainnet").read_text().splitlines(): + ln = ln.strip() + if ln and not ln.startswith("#") and "=" in ln: + k, v = ln.split("=", 1); env[k] = v.strip() + assert env["CERBERO_TOKEN"] != TESTNET_TOKEN, "token e' TESTNET, non mainnet!" + return env["CERBERO_TOKEN"], env.get("CERBERO_BOT_TAG", "pythagoras-verify") + + +def fetch_mainnet(asset, tf="15m"): + cache = PROJECT_ROOT / "data" / "raw" / f"_mainnet_{asset.lower()}_{tf}.parquet" + if cache.exists(): + return pd.read_parquet(cache) + tok, tag = _token() + H = {"Authorization": f"Bearer {tok}", "X-Bot-Tag": tag, "Content-Type": "application/json"} + cur, end = datetime.fromisoformat(START), datetime.fromisoformat(END) + step = timedelta(days=30) + rows = [] + while cur < end: + ce = min(cur + step, end) + for attempt in range(3): + try: + r = requests.post(URL, headers=H, json={ + "exchange": "deribit", "instrument": INSTR[asset], "interval": tf, + "start_date": cur.strftime("%Y-%m-%d"), "end_date": ce.strftime("%Y-%m-%d")}, + timeout=40) + r.raise_for_status() + rows += r.json().get("candles", []) + break + except Exception as e: + if attempt == 2: + print(f" SKIP {cur.date()}->{ce.date()}: {str(e)[:60]}") + cur = ce + df = pd.DataFrame(rows) + if len(df) == 0: + return df + df["timestamp"] = df["timestamp"].astype("int64") + df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + df.to_parquet(cache, index=False) + return df + + +def yearly(ts, trades, pos=0.15): + by = {y: [] for y in YEARS} + for i, j, r in trades: + y = ts.iloc[i].year + if y in by: by[y].append(r) + out = {} + for y in YEARS: + cap = 1000.0 + for r in by[y]: cap = max(cap + cap * pos * r, 10.0) + out[y] = (cap / 1000 - 1) * 100 + return out + + +def full_oos(ts, trades, pos=0.15, split_date="2024-10-12"): + sd = pd.Timestamp(split_date, tz="UTC") + def comp(sub): + cap = 1000.0; rets = [] + for i, j, r in sub: + cap = max(cap + cap * pos * r, 10.0); rets.append(r * pos) + return cap, rets + capF, rF = comp(trades) + capO, rO = comp([(i, j, r) for i, j, r in trades if ts.iloc[i] >= sd]) + shF = float(np.mean(rF)/np.std(rF)*np.sqrt(len(rF))) if len(rF) > 1 and np.std(rF) > 0 else 0.0 + shO = float(np.mean(rO)/np.std(rO)*np.sqrt(len(rO))) if len(rO) > 1 and np.std(rO) > 0 else 0.0 + return (capF/1000-1)*100, shF, (capO/1000-1)*100, shO + + +def main(): + print(f"Fetch Deribit MAINNET 15m REALE ({START}->{END})...\n") + data = {} + for a in ("BTC", "ETH"): + df = fetch_mainnet(a) + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + print(f" {INSTR[a]}: {len(df)} candele {ts.min()} -> {ts.max()} " + f"(anni: {sorted(ts.dt.year.unique().tolist())})") + data[a] = df + print("\n" + "=" * 94) + print(" FADE su perp Deribit MAINNET REALE 15m | RET% per anno (pos 0.15, leva 3x, trend 3.0)") + print("=" * 94) + print(f" {'sleeve':<12s}" + "".join(f"{y:>9d}" for y in YEARS) + " | FULL% Shrp | OOS% Shrp") + print(" " + "-" * 90) + for asset in ("BTC", "ETH"): + df = data[asset] + ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) + for code in ("MR01", "MR02", "MR07"): + fn, params = strats_for(asset)[code] + trades = build_trades(fn(df, **params), df, trend_max=3.0) + trades = [(i, j, r) for i, j, r in trades if ts.iloc[i].year >= 2021] + yr = yearly(ts, trades) + fF, shF, fO, shO = full_oos(ts, trades) + print(f" {code+'_'+asset:<12s}" + "".join(f"{yr[y]:>+9.0f}" for y in YEARS) + + f" | {fF:>+8.0f} {shF:>5.2f} | {fO:>+6.0f} {shO:>5.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/make_strategy_doc.py b/Old/scripts/analysis/make_strategy_doc.py similarity index 100% rename from scripts/analysis/make_strategy_doc.py rename to Old/scripts/analysis/make_strategy_doc.py diff --git a/scripts/analysis/mr02eth_options.workflow.js b/Old/scripts/analysis/mr02eth_options.workflow.js similarity index 100% rename from scripts/analysis/mr02eth_options.workflow.js rename to Old/scripts/analysis/mr02eth_options.workflow.js diff --git a/scripts/analysis/mr02eth_port06_gate.py b/Old/scripts/analysis/mr02eth_port06_gate.py similarity index 100% rename from scripts/analysis/mr02eth_port06_gate.py rename to Old/scripts/analysis/mr02eth_port06_gate.py diff --git a/scripts/analysis/mr02eth_search.workflow.js b/Old/scripts/analysis/mr02eth_search.workflow.js similarity index 100% rename from scripts/analysis/mr02eth_search.workflow.js rename to Old/scripts/analysis/mr02eth_search.workflow.js diff --git a/scripts/analysis/oos_validation.py b/Old/scripts/analysis/oos_validation.py similarity index 100% rename from scripts/analysis/oos_validation.py rename to Old/scripts/analysis/oos_validation.py diff --git a/scripts/analysis/option_overlay_lab.py b/Old/scripts/analysis/option_overlay_lab.py similarity index 100% rename from scripts/analysis/option_overlay_lab.py rename to Old/scripts/analysis/option_overlay_lab.py diff --git a/scripts/analysis/options_chain.py b/Old/scripts/analysis/options_chain.py similarity index 100% rename from scripts/analysis/options_chain.py rename to Old/scripts/analysis/options_chain.py diff --git a/scripts/analysis/options_fetcher.py b/Old/scripts/analysis/options_fetcher.py similarity index 100% rename from scripts/analysis/options_fetcher.py rename to Old/scripts/analysis/options_fetcher.py diff --git a/scripts/analysis/pairs15m_flatcheck.py b/Old/scripts/analysis/pairs15m_flatcheck.py similarity index 100% rename from scripts/analysis/pairs15m_flatcheck.py rename to Old/scripts/analysis/pairs15m_flatcheck.py diff --git a/scripts/analysis/pairs15m_gate_final.py b/Old/scripts/analysis/pairs15m_gate_final.py similarity index 100% rename from scripts/analysis/pairs15m_gate_final.py rename to Old/scripts/analysis/pairs15m_gate_final.py diff --git a/scripts/analysis/pairs15m_live_smoke.py b/Old/scripts/analysis/pairs15m_live_smoke.py similarity index 100% rename from scripts/analysis/pairs15m_live_smoke.py rename to Old/scripts/analysis/pairs15m_live_smoke.py diff --git a/scripts/analysis/pairs15m_port06_gate.py b/Old/scripts/analysis/pairs15m_port06_gate.py similarity index 100% rename from scripts/analysis/pairs15m_port06_gate.py rename to Old/scripts/analysis/pairs15m_port06_gate.py diff --git a/scripts/analysis/pairs30m_gate.py b/Old/scripts/analysis/pairs30m_gate.py similarity index 100% rename from scripts/analysis/pairs30m_gate.py rename to Old/scripts/analysis/pairs30m_gate.py diff --git a/scripts/analysis/pairs_research.py b/Old/scripts/analysis/pairs_research.py similarity index 100% rename from scripts/analysis/pairs_research.py rename to Old/scripts/analysis/pairs_research.py diff --git a/scripts/analysis/pairs_zstop_research.py b/Old/scripts/analysis/pairs_zstop_research.py similarity index 100% rename from scripts/analysis/pairs_zstop_research.py rename to Old/scripts/analysis/pairs_zstop_research.py diff --git a/scripts/analysis/pairspos_port06_impact.py b/Old/scripts/analysis/pairspos_port06_impact.py similarity index 100% rename from scripts/analysis/pairspos_port06_impact.py rename to Old/scripts/analysis/pairspos_port06_impact.py diff --git a/scripts/analysis/paper_status.py b/Old/scripts/analysis/paper_status.py similarity index 100% rename from scripts/analysis/paper_status.py rename to Old/scripts/analysis/paper_status.py diff --git a/scripts/analysis/partial_tp_ladder.py b/Old/scripts/analysis/partial_tp_ladder.py similarity index 100% rename from scripts/analysis/partial_tp_ladder.py rename to Old/scripts/analysis/partial_tp_ladder.py diff --git a/scripts/analysis/projection_3y.py b/Old/scripts/analysis/projection_3y.py similarity index 100% rename from scripts/analysis/projection_3y.py rename to Old/scripts/analysis/projection_3y.py diff --git a/Old/scripts/analysis/real_dip_pairs_check.py b/Old/scripts/analysis/real_dip_pairs_check.py new file mode 100644 index 0000000..3182e80 --- /dev/null +++ b/Old/scripts/analysis/real_dip_pairs_check.py @@ -0,0 +1,137 @@ +"""VERIFICA su dati REALI — DIP01 e i 6 pairs hanno edge su prezzi veri? + +Le 6 fade sono morte su mainnet/Binance (edge = artefatto-print testnet). Restano i +candidati piu' probabili a sopravvivere: i pairs (market-neutral sul log-ratio -> i +print di singolo asset si elidono in parte) e DIP01. Test: monkeypatch di load_data / +get_df -> serie 100% Binance spot (provato ~ mainnet: disc <0.13%), STESSO engine +canonico (pairs_sim / dip_market_gated). Cache in data/raw/_real_*.parquet (NON tocca +i canonici). +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np, pandas as pd, ccxt + +SYM = {"BTC": "BTC/USDT", "ETH": "ETH/USDT", "LTC": "LTC/USDT", "ADA": "ADA/USDT", "SOL": "SOL/USDT"} +START, END = "2020-06-01", "2026-05-26" +YEARS = [2021, 2022, 2023, 2024, 2025, 2026] +_EX = None +_CACHE: dict[tuple, pd.DataFrame] = {} + + +def _ex(): + global _EX + if _EX is None: + _EX = ccxt.binance({"enableRateLimit": True}) + return _EX + + +def fetch(asset, tf): + key = (asset, tf) + if key in _CACHE: + return _CACHE[key] + cache = PROJECT_ROOT / "data" / "raw" / f"_real_{asset.lower()}_{tf}.parquet" + if cache.exists(): + df = pd.read_parquet(cache); _CACHE[key] = df; return df + tf_ms = {"15m": 15, "1h": 60}[tf] * 60 * 1000 + start_ms = int(pd.Timestamp(START, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp(END, tz="UTC").timestamp() * 1000) + rows, since = [], start_ms + while since <= end_ms: + r = _ex().fetch_ohlcv(SYM[asset], tf, since=since, limit=1000) + if not r: + break + rows += r + nxt = int(r[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + df = pd.DataFrame(rows, columns=["timestamp", "open", "high", "low", "close", "volume"]) + df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + df = df[df["timestamp"] <= end_ms].reset_index(drop=True) + df.to_parquet(cache, index=False); _CACHE[key] = df + return df + + +# ---- monkeypatch dei loader dei due engine canonici ---- +def _patched_load_data(asset, tf="1h"): + return fetch(asset, tf) + + +def _patched_get_df(asset, tf="1h"): + return fetch(asset, tf) + + +def daily_from_eq(eq_ts, eq_v): + idx = pd.date_range("2021-01-01", END, freq="1D", tz="UTC") + s = pd.Series(eq_v, index=pd.to_datetime(eq_ts, utc=True)).resample("1D").last().reindex(idx).ffill().bfill() + return s / s.iloc[0] + + +def metrics_from_daily(s, split_date="2024-10-12"): + r = s.pct_change().fillna(0.0) + def m(rr): + eq = (1 + rr).cumprod(); peak = eq.cummax() + dd = float(((peak - eq) / peak).max() * 100) + sh = float(rr.mean() / rr.std() * np.sqrt(365)) if rr.std() > 0 else 0.0 + return (eq.iloc[-1] - 1) * 100, dd, sh + sd = pd.Timestamp(split_date, tz="UTC") + fF, ddF, shF = m(r) + ro = r[r.index >= sd] + fO, ddO, shO = m(ro) + yr = {int(y): float(((1 + r[r.index.year == y]).prod() - 1) * 100) for y in YEARS} + return yr, fF, ddF, shF, fO, ddO, shO + + +def main(): + print("Fetch Binance spot (1h: BTC/ETH/LTC/ADA/SOL ; 15m: BTC/ETH)...\n") + for a in SYM: + fetch(a, "1h") + for a in ("BTC", "ETH"): + fetch(a, "15m") + + import scripts.analysis.pairs_research as PR + import scripts.analysis.honest_improve2 as HI + PR.load_data = _patched_load_data + HI.get_df = _patched_get_df + from scripts.analysis.pairs_research import pairs_sim, pairs_sim_flat, OOS_FRAC + from scripts.strategies.PR01_pairs_reversion import PAIRS + + # ---------- DIP01 ---------- + print("=" * 96) + print(" DIP01 (BTC 1h dip-buy) su Binance spot REALE | RET% per anno + FULL/OOS (leva 3x)") + print("=" * 96) + d = HI.dip_market_gated("BTC", market_n=0, return_equity=True) + s = daily_from_eq(d["eq_ts"], d["eq_v"]) + yr, fF, ddF, shF, fO, ddO, shO = metrics_from_daily(s) + print(f" {'':<10s}" + "".join(f"{y:>9d}" for y in YEARS) + " | FULL% DD% Shrp | OOS% Shrp") + print(f" {'DIP01_BTC':<10s}" + "".join(f"{yr[y]:>+9.0f}" for y in YEARS) + + f" | {fF:>+7.0f}{ddF:>5.0f}{shF:>6.2f} | {fO:>+6.0f}{shO:>6.2f}") + + # ---------- PAIRS (5 univ + BLEND 15m) ---------- + print("\n" + "=" * 96) + print(" PAIRS PR01 su Binance spot REALE | fee 0.20% RT/coppia, leva 3x | (canonico CLAUDE.md fra parentesi)") + print("=" * 96) + print(f" {'coppia':<12s}{'trd':>6s}{'win%':>6s}{'CAGR%':>7s}{'DD%':>6s}{'Shrp':>6s}{'oDD%':>6s}{'anni+':>7s}") + canon = {"ETH/BTC": 4.36, "LTC/ETH": 3.08, "ADA/ETH": 2.69, "BTC/LTC": 2.36, "ETH/SOL": 1.96} + for a, b, p in PAIRS: + f = pairs_sim(a, b, **p) + o = pairs_sim(a, b, **p, split_frac=1 - OOS_FRAC) + yrs = f["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0) + name = f"{a}/{b}" + print(f" {name:<12s}{f['trades']:>6d}{f['win']:>6.1f}{f['cagr']:>7.0f}{f['dd']:>6.0f}" + f"{f['sharpe']:>6.2f}{o['dd']:>6.0f}{pos_y:>5d}/{len(yrs)} (canon Sh {canon.get(name,'?')})") + # BLEND ETH/BTC 15m (mezza size, flat-skip) come nel portafoglio + r15 = pairs_sim_flat("ETH", "BTC", tf="15m", n=66, z_in=1.674, z_exit=1.0, + max_bars=35, flat_skip=True, pos=0.075) + o15 = pairs_sim_flat("ETH", "BTC", tf="15m", n=66, z_in=1.674, z_exit=1.0, + max_bars=35, flat_skip=True, pos=0.075, split_frac=1 - OOS_FRAC) + yrs = r15["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0) + print(f" {'ETH/BTC 15m':<12s}{r15['trades']:>6d}{r15['win']:>6.1f}{r15['cagr']:>7.0f}{r15['dd']:>6.0f}" + f"{r15['sharpe']:>6.2f}{o15['dd']:>6.0f}{pos_y:>5d}/{len(yrs)} (BLEND mezza size)") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/reconcile_account.py b/Old/scripts/analysis/reconcile_account.py similarity index 100% rename from scripts/analysis/reconcile_account.py rename to Old/scripts/analysis/reconcile_account.py diff --git a/scripts/analysis/regime_fetcher.py b/Old/scripts/analysis/regime_fetcher.py similarity index 100% rename from scripts/analysis/regime_fetcher.py rename to Old/scripts/analysis/regime_fetcher.py diff --git a/scripts/analysis/regime_lab.py b/Old/scripts/analysis/regime_lab.py similarity index 100% rename from scripts/analysis/regime_lab.py rename to Old/scripts/analysis/regime_lab.py diff --git a/scripts/analysis/report.py b/Old/scripts/analysis/report.py similarity index 100% rename from scripts/analysis/report.py rename to Old/scripts/analysis/report.py diff --git a/scripts/analysis/report_families.py b/Old/scripts/analysis/report_families.py similarity index 100% rename from scripts/analysis/report_families.py rename to Old/scripts/analysis/report_families.py diff --git a/scripts/analysis/reset_flatten.py b/Old/scripts/analysis/reset_flatten.py similarity index 100% rename from scripts/analysis/reset_flatten.py rename to Old/scripts/analysis/reset_flatten.py diff --git a/scripts/analysis/risk_management.py b/Old/scripts/analysis/risk_management.py similarity index 100% rename from scripts/analysis/risk_management.py rename to Old/scripts/analysis/risk_management.py diff --git a/scripts/analysis/sh01_exit_lab.py b/Old/scripts/analysis/sh01_exit_lab.py similarity index 100% rename from scripts/analysis/sh01_exit_lab.py rename to Old/scripts/analysis/sh01_exit_lab.py diff --git a/scripts/analysis/sh01_exit_policies/01_atr_fixed_intrabar.py b/Old/scripts/analysis/sh01_exit_policies/01_atr_fixed_intrabar.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/01_atr_fixed_intrabar.py rename to Old/scripts/analysis/sh01_exit_policies/01_atr_fixed_intrabar.py diff --git a/scripts/analysis/sh01_exit_policies/02_atr_fixed_close_confirm.py b/Old/scripts/analysis/sh01_exit_policies/02_atr_fixed_close_confirm.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/02_atr_fixed_close_confirm.py rename to Old/scripts/analysis/sh01_exit_policies/02_atr_fixed_close_confirm.py diff --git a/scripts/analysis/sh01_exit_policies/03_pct_fixed.py b/Old/scripts/analysis/sh01_exit_policies/03_pct_fixed.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/03_pct_fixed.py rename to Old/scripts/analysis/sh01_exit_policies/03_pct_fixed.py diff --git a/scripts/analysis/sh01_exit_policies/04_chandelier_trail.py b/Old/scripts/analysis/sh01_exit_policies/04_chandelier_trail.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/04_chandelier_trail.py rename to Old/scripts/analysis/sh01_exit_policies/04_chandelier_trail.py diff --git a/scripts/analysis/sh01_exit_policies/05_breakeven_ratchet.py b/Old/scripts/analysis/sh01_exit_policies/05_breakeven_ratchet.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/05_breakeven_ratchet.py rename to Old/scripts/analysis/sh01_exit_policies/05_breakeven_ratchet.py diff --git a/scripts/analysis/sh01_exit_policies/06_giveback.py b/Old/scripts/analysis/sh01_exit_policies/06_giveback.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/06_giveback.py rename to Old/scripts/analysis/sh01_exit_policies/06_giveback.py diff --git a/scripts/analysis/sh01_exit_policies/07_loser_timestop.py b/Old/scripts/analysis/sh01_exit_policies/07_loser_timestop.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/07_loser_timestop.py rename to Old/scripts/analysis/sh01_exit_policies/07_loser_timestop.py diff --git a/scripts/analysis/sh01_exit_policies/08_disaster_wide_close.py b/Old/scripts/analysis/sh01_exit_policies/08_disaster_wide_close.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/08_disaster_wide_close.py rename to Old/scripts/analysis/sh01_exit_policies/08_disaster_wide_close.py diff --git a/scripts/analysis/sh01_exit_policies/09_swing_stop.py b/Old/scripts/analysis/sh01_exit_policies/09_swing_stop.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/09_swing_stop.py rename to Old/scripts/analysis/sh01_exit_policies/09_swing_stop.py diff --git a/scripts/analysis/sh01_exit_policies/10_vol_regime_stop.py b/Old/scripts/analysis/sh01_exit_policies/10_vol_regime_stop.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/10_vol_regime_stop.py rename to Old/scripts/analysis/sh01_exit_policies/10_vol_regime_stop.py diff --git a/scripts/analysis/sh01_exit_policies/11_disaster_wide_intrabar.py b/Old/scripts/analysis/sh01_exit_policies/11_disaster_wide_intrabar.py similarity index 100% rename from scripts/analysis/sh01_exit_policies/11_disaster_wide_intrabar.py rename to Old/scripts/analysis/sh01_exit_policies/11_disaster_wide_intrabar.py diff --git a/scripts/analysis/sh01_trainwindow_validate.py b/Old/scripts/analysis/sh01_trainwindow_validate.py similarity index 100% rename from scripts/analysis/sh01_trainwindow_validate.py rename to Old/scripts/analysis/sh01_trainwindow_validate.py diff --git a/scripts/analysis/shape_analog_research.py b/Old/scripts/analysis/shape_analog_research.py similarity index 100% rename from scripts/analysis/shape_analog_research.py rename to Old/scripts/analysis/shape_analog_research.py diff --git a/scripts/analysis/shape_candle_research.py b/Old/scripts/analysis/shape_candle_research.py similarity index 100% rename from scripts/analysis/shape_candle_research.py rename to Old/scripts/analysis/shape_candle_research.py diff --git a/scripts/analysis/shape_lab.py b/Old/scripts/analysis/shape_lab.py similarity index 100% rename from scripts/analysis/shape_lab.py rename to Old/scripts/analysis/shape_lab.py diff --git a/scripts/analysis/shape_ml_research.py b/Old/scripts/analysis/shape_ml_research.py similarity index 100% rename from scripts/analysis/shape_ml_research.py rename to Old/scripts/analysis/shape_ml_research.py diff --git a/scripts/analysis/shape_ml_validate.py b/Old/scripts/analysis/shape_ml_validate.py similarity index 100% rename from scripts/analysis/shape_ml_validate.py rename to Old/scripts/analysis/shape_ml_validate.py diff --git a/scripts/analysis/shape_pivot_research.py b/Old/scripts/analysis/shape_pivot_research.py similarity index 100% rename from scripts/analysis/shape_pivot_research.py rename to Old/scripts/analysis/shape_pivot_research.py diff --git a/scripts/analysis/shape_template_research.py b/Old/scripts/analysis/shape_template_research.py similarity index 100% rename from scripts/analysis/shape_template_research.py rename to Old/scripts/analysis/shape_template_research.py diff --git a/scripts/analysis/sleeve_clustering.py b/Old/scripts/analysis/sleeve_clustering.py similarity index 100% rename from scripts/analysis/sleeve_clustering.py rename to Old/scripts/analysis/sleeve_clustering.py diff --git a/scripts/analysis/smoke_portfolio.py b/Old/scripts/analysis/smoke_portfolio.py similarity index 100% rename from scripts/analysis/smoke_portfolio.py rename to Old/scripts/analysis/smoke_portfolio.py diff --git a/scripts/analysis/strategy_research.py b/Old/scripts/analysis/strategy_research.py similarity index 100% rename from scripts/analysis/strategy_research.py rename to Old/scripts/analysis/strategy_research.py diff --git a/scripts/analysis/strategy_research_v2.py b/Old/scripts/analysis/strategy_research_v2.py similarity index 100% rename from scripts/analysis/strategy_research_v2.py rename to Old/scripts/analysis/strategy_research_v2.py diff --git a/scripts/analysis/timing_sweep_pairs_honest.py b/Old/scripts/analysis/timing_sweep_pairs_honest.py similarity index 100% rename from scripts/analysis/timing_sweep_pairs_honest.py rename to Old/scripts/analysis/timing_sweep_pairs_honest.py diff --git a/scripts/analysis/trades_status.py b/Old/scripts/analysis/trades_status.py similarity index 100% rename from scripts/analysis/trades_status.py rename to Old/scripts/analysis/trades_status.py diff --git a/scripts/analysis/trendmax_port06_impact.py b/Old/scripts/analysis/trendmax_port06_impact.py similarity index 100% rename from scripts/analysis/trendmax_port06_impact.py rename to Old/scripts/analysis/trendmax_port06_impact.py diff --git a/scripts/analysis/tsmom_research.py b/Old/scripts/analysis/tsmom_research.py similarity index 100% rename from scripts/analysis/tsmom_research.py rename to Old/scripts/analysis/tsmom_research.py diff --git a/scripts/analysis/validate_fade_intrabar.py b/Old/scripts/analysis/validate_fade_intrabar.py similarity index 100% rename from scripts/analysis/validate_fade_intrabar.py rename to Old/scripts/analysis/validate_fade_intrabar.py diff --git a/scripts/analysis/validate_grid_worker.py b/Old/scripts/analysis/validate_grid_worker.py similarity index 100% rename from scripts/analysis/validate_grid_worker.py rename to Old/scripts/analysis/validate_grid_worker.py diff --git a/scripts/analysis/validate_honest_workers.py b/Old/scripts/analysis/validate_honest_workers.py similarity index 100% rename from scripts/analysis/validate_honest_workers.py rename to Old/scripts/analysis/validate_honest_workers.py diff --git a/scripts/analysis/validate_portfolio_runner.py b/Old/scripts/analysis/validate_portfolio_runner.py similarity index 100% rename from scripts/analysis/validate_portfolio_runner.py rename to Old/scripts/analysis/validate_portfolio_runner.py diff --git a/scripts/analysis/validate_worker_mr01.py b/Old/scripts/analysis/validate_worker_mr01.py similarity index 100% rename from scripts/analysis/validate_worker_mr01.py rename to Old/scripts/analysis/validate_worker_mr01.py diff --git a/scripts/analysis/validate_worker_pairs.py b/Old/scripts/analysis/validate_worker_pairs.py similarity index 100% rename from scripts/analysis/validate_worker_pairs.py rename to Old/scripts/analysis/validate_worker_pairs.py diff --git a/scripts/analysis/validate_xsec_worker.py b/Old/scripts/analysis/validate_xsec_worker.py similarity index 100% rename from scripts/analysis/validate_xsec_worker.py rename to Old/scripts/analysis/validate_xsec_worker.py diff --git a/scripts/analysis/xex_divergence_research.py b/Old/scripts/analysis/xex_divergence_research.py similarity index 100% rename from scripts/analysis/xex_divergence_research.py rename to Old/scripts/analysis/xex_divergence_research.py diff --git a/scripts/analysis/xs01_dispersion_gate.py b/Old/scripts/analysis/xs01_dispersion_gate.py similarity index 100% rename from scripts/analysis/xs01_dispersion_gate.py rename to Old/scripts/analysis/xs01_dispersion_gate.py diff --git a/scripts/analysis/xs01_tranche_gate.py b/Old/scripts/analysis/xs01_tranche_gate.py similarity index 100% rename from scripts/analysis/xs01_tranche_gate.py rename to Old/scripts/analysis/xs01_tranche_gate.py diff --git a/scripts/analysis/xs01_tranche_research.py b/Old/scripts/analysis/xs01_tranche_research.py similarity index 100% rename from scripts/analysis/xs01_tranche_research.py rename to Old/scripts/analysis/xs01_tranche_research.py diff --git a/scripts/analysis/xsec_port06_gate.py b/Old/scripts/analysis/xsec_port06_gate.py similarity index 100% rename from scripts/analysis/xsec_port06_gate.py rename to Old/scripts/analysis/xsec_port06_gate.py diff --git a/scripts/games/agent_brief.py b/Old/scripts/games/agent_brief.py similarity index 100% rename from scripts/games/agent_brief.py rename to Old/scripts/games/agent_brief.py diff --git a/scripts/games/arena.py b/Old/scripts/games/arena.py similarity index 100% rename from scripts/games/arena.py rename to Old/scripts/games/arena.py diff --git a/scripts/games/engine.py b/Old/scripts/games/engine.py similarity index 100% rename from scripts/games/engine.py rename to Old/scripts/games/engine.py diff --git a/scripts/games/grid_arena.py b/Old/scripts/games/grid_arena.py similarity index 100% rename from scripts/games/grid_arena.py rename to Old/scripts/games/grid_arena.py diff --git a/scripts/games/grid_brief.py b/Old/scripts/games/grid_brief.py similarity index 100% rename from scripts/games/grid_brief.py rename to Old/scripts/games/grid_brief.py diff --git a/scripts/games/grid_engine.py b/Old/scripts/games/grid_engine.py similarity index 100% rename from scripts/games/grid_engine.py rename to Old/scripts/games/grid_engine.py diff --git a/scripts/games/opt_calibrate.py b/Old/scripts/games/opt_calibrate.py similarity index 100% rename from scripts/games/opt_calibrate.py rename to Old/scripts/games/opt_calibrate.py diff --git a/scripts/games/options_arena.py b/Old/scripts/games/options_arena.py similarity index 100% rename from scripts/games/options_arena.py rename to Old/scripts/games/options_arena.py diff --git a/scripts/games/options_engine.py b/Old/scripts/games/options_engine.py similarity index 100% rename from scripts/games/options_engine.py rename to Old/scripts/games/options_engine.py diff --git a/scripts/games/run_game.py b/Old/scripts/games/run_game.py similarity index 100% rename from scripts/games/run_game.py rename to Old/scripts/games/run_game.py diff --git a/scripts/games/session_arena.py b/Old/scripts/games/session_arena.py similarity index 100% rename from scripts/games/session_arena.py rename to Old/scripts/games/session_arena.py diff --git a/scripts/games/session_engine.py b/Old/scripts/games/session_engine.py similarity index 100% rename from scripts/games/session_engine.py rename to Old/scripts/games/session_engine.py diff --git a/scripts/portfolios/PORT01_honest.py b/Old/scripts/portfolios/PORT01_honest.py similarity index 100% rename from scripts/portfolios/PORT01_honest.py rename to Old/scripts/portfolios/PORT01_honest.py diff --git a/scripts/portfolios/PORT02_fade.py b/Old/scripts/portfolios/PORT02_fade.py similarity index 100% rename from scripts/portfolios/PORT02_fade.py rename to Old/scripts/portfolios/PORT02_fade.py diff --git a/scripts/portfolios/PORT03_master.py b/Old/scripts/portfolios/PORT03_master.py similarity index 100% rename from scripts/portfolios/PORT03_master.py rename to Old/scripts/portfolios/PORT03_master.py diff --git a/scripts/portfolios/PORT04_master_pairs.py b/Old/scripts/portfolios/PORT04_master_pairs.py similarity index 100% rename from scripts/portfolios/PORT04_master_pairs.py rename to Old/scripts/portfolios/PORT04_master_pairs.py diff --git a/scripts/portfolios/PORT05_master_esteso.py b/Old/scripts/portfolios/PORT05_master_esteso.py similarity index 100% rename from scripts/portfolios/PORT05_master_esteso.py rename to Old/scripts/portfolios/PORT05_master_esteso.py diff --git a/scripts/portfolios/PORT06_master_shape.py b/Old/scripts/portfolios/PORT06_master_shape.py similarity index 100% rename from scripts/portfolios/PORT06_master_shape.py rename to Old/scripts/portfolios/PORT06_master_shape.py diff --git a/src/live/__init__.py b/Old/scripts/portfolios/__init__.py similarity index 100% rename from src/live/__init__.py rename to Old/scripts/portfolios/__init__.py diff --git a/scripts/portfolios/_defs.py b/Old/scripts/portfolios/_defs.py similarity index 100% rename from scripts/portfolios/_defs.py rename to Old/scripts/portfolios/_defs.py diff --git a/scripts/portfolios/compare_all.py b/Old/scripts/portfolios/compare_all.py similarity index 100% rename from scripts/portfolios/compare_all.py rename to Old/scripts/portfolios/compare_all.py diff --git a/scripts/portfolios/hourly_report.py b/Old/scripts/portfolios/hourly_report.py similarity index 100% rename from scripts/portfolios/hourly_report.py rename to Old/scripts/portfolios/hourly_report.py diff --git a/scripts/strategies/DIP01_dip_buy.py b/Old/scripts/strategies/DIP01_dip_buy.py similarity index 100% rename from scripts/strategies/DIP01_dip_buy.py rename to Old/scripts/strategies/DIP01_dip_buy.py diff --git a/scripts/strategies/DIP01_dip_reversion.py b/Old/scripts/strategies/DIP01_dip_reversion.py similarity index 100% rename from scripts/strategies/DIP01_dip_reversion.py rename to Old/scripts/strategies/DIP01_dip_reversion.py diff --git a/scripts/strategies/FR01_hurst_calm_fade.py b/Old/scripts/strategies/FR01_hurst_calm_fade.py similarity index 100% rename from scripts/strategies/FR01_hurst_calm_fade.py rename to Old/scripts/strategies/FR01_hurst_calm_fade.py diff --git a/scripts/strategies/MR01_bollinger_fade.py b/Old/scripts/strategies/MR01_bollinger_fade.py similarity index 100% rename from scripts/strategies/MR01_bollinger_fade.py rename to Old/scripts/strategies/MR01_bollinger_fade.py diff --git a/scripts/strategies/MR02_donchian_fade.py b/Old/scripts/strategies/MR02_donchian_fade.py similarity index 100% rename from scripts/strategies/MR02_donchian_fade.py rename to Old/scripts/strategies/MR02_donchian_fade.py diff --git a/scripts/strategies/MR07_return_reversal.py b/Old/scripts/strategies/MR07_return_reversal.py similarity index 100% rename from scripts/strategies/MR07_return_reversal.py rename to Old/scripts/strategies/MR07_return_reversal.py diff --git a/scripts/strategies/PORT01_portfolio.py b/Old/scripts/strategies/PORT01_portfolio.py similarity index 100% rename from scripts/strategies/PORT01_portfolio.py rename to Old/scripts/strategies/PORT01_portfolio.py diff --git a/scripts/strategies/PORT02_fade_master.py b/Old/scripts/strategies/PORT02_fade_master.py similarity index 100% rename from scripts/strategies/PORT02_fade_master.py rename to Old/scripts/strategies/PORT02_fade_master.py diff --git a/scripts/strategies/PORT03_all_master.py b/Old/scripts/strategies/PORT03_all_master.py similarity index 100% rename from scripts/strategies/PORT03_all_master.py rename to Old/scripts/strategies/PORT03_all_master.py diff --git a/scripts/strategies/PR01_pairs_reversion.py b/Old/scripts/strategies/PR01_pairs_reversion.py similarity index 100% rename from scripts/strategies/PR01_pairs_reversion.py rename to Old/scripts/strategies/PR01_pairs_reversion.py diff --git a/scripts/strategies/ROT02_dual_momentum.py b/Old/scripts/strategies/ROT02_dual_momentum.py similarity index 100% rename from scripts/strategies/ROT02_dual_momentum.py rename to Old/scripts/strategies/ROT02_dual_momentum.py diff --git a/scripts/strategies/SH01_shape_ml.py b/Old/scripts/strategies/SH01_shape_ml.py similarity index 100% rename from scripts/strategies/SH01_shape_ml.py rename to Old/scripts/strategies/SH01_shape_ml.py diff --git a/scripts/strategies/TR01_ema_trend.py b/Old/scripts/strategies/TR01_ema_trend.py similarity index 100% rename from scripts/strategies/TR01_ema_trend.py rename to Old/scripts/strategies/TR01_ema_trend.py diff --git a/scripts/strategies/XS01_cross_sectional.py b/Old/scripts/strategies/XS01_cross_sectional.py similarity index 100% rename from scripts/strategies/XS01_cross_sectional.py rename to Old/scripts/strategies/XS01_cross_sectional.py diff --git a/scripts/waste/AD01_adaptive_squeeze.py b/Old/scripts/waste/AD01_adaptive_squeeze.py similarity index 100% rename from scripts/waste/AD01_adaptive_squeeze.py rename to Old/scripts/waste/AD01_adaptive_squeeze.py diff --git a/scripts/waste/CM01_cross_market_momentum.py b/Old/scripts/waste/CM01_cross_market_momentum.py similarity index 100% rename from scripts/waste/CM01_cross_market_momentum.py rename to Old/scripts/waste/CM01_cross_market_momentum.py diff --git a/scripts/waste/ML01_squeeze_gbm.py b/Old/scripts/waste/ML01_squeeze_gbm.py similarity index 100% rename from scripts/waste/ML01_squeeze_gbm.py rename to Old/scripts/waste/ML01_squeeze_gbm.py diff --git a/scripts/waste/MR03_keltner_fade.py b/Old/scripts/waste/MR03_keltner_fade.py similarity index 100% rename from scripts/waste/MR03_keltner_fade.py rename to Old/scripts/waste/MR03_keltner_fade.py diff --git a/scripts/waste/MT01_squeeze_mtf_momentum.py b/Old/scripts/waste/MT01_squeeze_mtf_momentum.py similarity index 100% rename from scripts/waste/MT01_squeeze_mtf_momentum.py rename to Old/scripts/waste/MT01_squeeze_mtf_momentum.py diff --git a/scripts/waste/PD01_price_volume_divergence.py b/Old/scripts/waste/PD01_price_volume_divergence.py similarity index 100% rename from scripts/waste/PD01_price_volume_divergence.py rename to Old/scripts/waste/PD01_price_volume_divergence.py diff --git a/scripts/waste/REF_11_volatility_breakout.py b/Old/scripts/waste/REF_11_volatility_breakout.py similarity index 100% rename from scripts/waste/REF_11_volatility_breakout.py rename to Old/scripts/waste/REF_11_volatility_breakout.py diff --git a/scripts/waste/REF_13_squeeze_ml_hybrid.py b/Old/scripts/waste/REF_13_squeeze_ml_hybrid.py similarity index 100% rename from scripts/waste/REF_13_squeeze_ml_hybrid.py rename to Old/scripts/waste/REF_13_squeeze_ml_hybrid.py diff --git a/scripts/waste/REF_s3_01_squeeze_improved.py b/Old/scripts/waste/REF_s3_01_squeeze_improved.py similarity index 100% rename from scripts/waste/REF_s3_01_squeeze_improved.py rename to Old/scripts/waste/REF_s3_01_squeeze_improved.py diff --git a/scripts/waste/REF_s3_02_lead_lag.py b/Old/scripts/waste/REF_s3_02_lead_lag.py similarity index 100% rename from scripts/waste/REF_s3_02_lead_lag.py rename to Old/scripts/waste/REF_s3_02_lead_lag.py diff --git a/scripts/waste/REF_s3_03_ultimate.py b/Old/scripts/waste/REF_s3_03_ultimate.py similarity index 100% rename from scripts/waste/REF_s3_03_ultimate.py rename to Old/scripts/waste/REF_s3_03_ultimate.py diff --git a/scripts/waste/ROT01_xsect_rotation.py b/Old/scripts/waste/ROT01_xsect_rotation.py similarity index 100% rename from scripts/waste/ROT01_xsect_rotation.py rename to Old/scripts/waste/ROT01_xsect_rotation.py diff --git a/scripts/waste/SQ01_squeeze_base.py b/Old/scripts/waste/SQ01_squeeze_base.py similarity index 100% rename from scripts/waste/SQ01_squeeze_base.py rename to Old/scripts/waste/SQ01_squeeze_base.py diff --git a/scripts/waste/SQ02_squeeze_antifake_vol.py b/Old/scripts/waste/SQ02_squeeze_antifake_vol.py similarity index 100% rename from scripts/waste/SQ02_squeeze_antifake_vol.py rename to Old/scripts/waste/SQ02_squeeze_antifake_vol.py diff --git a/scripts/waste/SQ03_squeeze_all_filters.py b/Old/scripts/waste/SQ03_squeeze_all_filters.py similarity index 100% rename from scripts/waste/SQ03_squeeze_all_filters.py rename to Old/scripts/waste/SQ03_squeeze_all_filters.py diff --git a/scripts/waste/SQ04_squeeze_ultimate.py b/Old/scripts/waste/SQ04_squeeze_ultimate.py similarity index 100% rename from scripts/waste/SQ04_squeeze_ultimate.py rename to Old/scripts/waste/SQ04_squeeze_ultimate.py diff --git a/scripts/waste/W01_baseline.py b/Old/scripts/waste/W01_baseline.py similarity index 100% rename from scripts/waste/W01_baseline.py rename to Old/scripts/waste/W01_baseline.py diff --git a/scripts/waste/W02_dtw_pattern.py b/Old/scripts/waste/W02_dtw_pattern.py similarity index 100% rename from scripts/waste/W02_dtw_pattern.py rename to Old/scripts/waste/W02_dtw_pattern.py diff --git a/scripts/waste/W03_fourier.py b/Old/scripts/waste/W03_fourier.py similarity index 100% rename from scripts/waste/W03_fourier.py rename to Old/scripts/waste/W03_fourier.py diff --git a/scripts/waste/W04_regime_fractal_ml.py b/Old/scripts/waste/W04_regime_fractal_ml.py similarity index 100% rename from scripts/waste/W04_regime_fractal_ml.py rename to Old/scripts/waste/W04_regime_fractal_ml.py diff --git a/scripts/waste/W05_enhanced_fractal.py b/Old/scripts/waste/W05_enhanced_fractal.py similarity index 100% rename from scripts/waste/W05_enhanced_fractal.py rename to Old/scripts/waste/W05_enhanced_fractal.py diff --git a/scripts/waste/W06_structural_pattern.py b/Old/scripts/waste/W06_structural_pattern.py similarity index 100% rename from scripts/waste/W06_structural_pattern.py rename to Old/scripts/waste/W06_structural_pattern.py diff --git a/scripts/waste/W07_lstm.py b/Old/scripts/waste/W07_lstm.py similarity index 100% rename from scripts/waste/W07_lstm.py rename to Old/scripts/waste/W07_lstm.py diff --git a/scripts/waste/W08_ensemble.py b/Old/scripts/waste/W08_ensemble.py similarity index 100% rename from scripts/waste/W08_ensemble.py rename to Old/scripts/waste/W08_ensemble.py diff --git a/scripts/waste/W09_walkforward.py b/Old/scripts/waste/W09_walkforward.py similarity index 100% rename from scripts/waste/W09_walkforward.py rename to Old/scripts/waste/W09_walkforward.py diff --git a/scripts/waste/W10_high_precision.py b/Old/scripts/waste/W10_high_precision.py similarity index 100% rename from scripts/waste/W10_high_precision.py rename to Old/scripts/waste/W10_high_precision.py diff --git a/scripts/waste/W11_mean_reversion.py b/Old/scripts/waste/W11_mean_reversion.py similarity index 100% rename from scripts/waste/W11_mean_reversion.py rename to Old/scripts/waste/W11_mean_reversion.py diff --git a/scripts/waste/W12_funding_rate.py b/Old/scripts/waste/W12_funding_rate.py similarity index 100% rename from scripts/waste/W12_funding_rate.py rename to Old/scripts/waste/W12_funding_rate.py diff --git a/scripts/waste/W13_vol_selling.py b/Old/scripts/waste/W13_vol_selling.py similarity index 100% rename from scripts/waste/W13_vol_selling.py rename to Old/scripts/waste/W13_vol_selling.py diff --git a/scripts/waste/W14_momentum_micro.py b/Old/scripts/waste/W14_momentum_micro.py similarity index 100% rename from scripts/waste/W14_momentum_micro.py rename to Old/scripts/waste/W14_momentum_micro.py diff --git a/scripts/waste/W15_gap_fade.py b/Old/scripts/waste/W15_gap_fade.py similarity index 100% rename from scripts/waste/W15_gap_fade.py rename to Old/scripts/waste/W15_gap_fade.py diff --git a/scripts/waste/W16_iron_condor.py b/Old/scripts/waste/W16_iron_condor.py similarity index 100% rename from scripts/waste/W16_iron_condor.py rename to Old/scripts/waste/W16_iron_condor.py diff --git a/scripts/waste/W17_vol_premium.py b/Old/scripts/waste/W17_vol_premium.py similarity index 100% rename from scripts/waste/W17_vol_premium.py rename to Old/scripts/waste/W17_vol_premium.py diff --git a/scripts/waste/W18_vrp_honest.py b/Old/scripts/waste/W18_vrp_honest.py similarity index 100% rename from scripts/waste/W18_vrp_honest.py rename to Old/scripts/waste/W18_vrp_honest.py diff --git a/scripts/waste/W19_vrp_per_year.py b/Old/scripts/waste/W19_vrp_per_year.py similarity index 100% rename from scripts/waste/W19_vrp_per_year.py rename to Old/scripts/waste/W19_vrp_per_year.py diff --git a/scripts/waste/W20_vrp_filtered.py b/Old/scripts/waste/W20_vrp_filtered.py similarity index 100% rename from scripts/waste/W20_vrp_filtered.py rename to Old/scripts/waste/W20_vrp_filtered.py diff --git a/scripts/waste/W21_vrp_real_dvol.py b/Old/scripts/waste/W21_vrp_real_dvol.py similarity index 100% rename from scripts/waste/W21_vrp_real_dvol.py rename to Old/scripts/waste/W21_vrp_real_dvol.py diff --git a/scripts/waste/W22_perpetual_only.py b/Old/scripts/waste/W22_perpetual_only.py similarity index 100% rename from scripts/waste/W22_perpetual_only.py rename to Old/scripts/waste/W22_perpetual_only.py diff --git a/scripts/waste/W23_inside_bar.py b/Old/scripts/waste/W23_inside_bar.py similarity index 100% rename from scripts/waste/W23_inside_bar.py rename to Old/scripts/waste/W23_inside_bar.py diff --git a/scripts/waste/W24_donchian.py b/Old/scripts/waste/W24_donchian.py similarity index 100% rename from scripts/waste/W24_donchian.py rename to Old/scripts/waste/W24_donchian.py diff --git a/scripts/waste/W25_squeeze_retest.py b/Old/scripts/waste/W25_squeeze_retest.py similarity index 100% rename from scripts/waste/W25_squeeze_retest.py rename to Old/scripts/waste/W25_squeeze_retest.py diff --git a/scripts/waste/W26_mean_reversion_rsi.py b/Old/scripts/waste/W26_mean_reversion_rsi.py similarity index 100% rename from scripts/waste/W26_mean_reversion_rsi.py rename to Old/scripts/waste/W26_mean_reversion_rsi.py diff --git a/scripts/waste/W27_volume_spike.py b/Old/scripts/waste/W27_volume_spike.py similarity index 100% rename from scripts/waste/W27_volume_spike.py rename to Old/scripts/waste/W27_volume_spike.py diff --git a/scripts/waste/W28_squeeze_mr.py b/Old/scripts/waste/W28_squeeze_mr.py similarity index 100% rename from scripts/waste/W28_squeeze_mr.py rename to Old/scripts/waste/W28_squeeze_mr.py diff --git a/src/data/instruments.py b/Old/src/data/instruments.py similarity index 100% rename from src/data/instruments.py rename to Old/src/data/instruments.py diff --git a/src/nn/__init__.py b/Old/src/live/__init__.py similarity index 100% rename from src/nn/__init__.py rename to Old/src/live/__init__.py diff --git a/src/live/bars.py b/Old/src/live/bars.py similarity index 100% rename from src/live/bars.py rename to Old/src/live/bars.py diff --git a/src/live/basket_trend_worker.py b/Old/src/live/basket_trend_worker.py similarity index 100% rename from src/live/basket_trend_worker.py rename to Old/src/live/basket_trend_worker.py diff --git a/src/live/books.py b/Old/src/live/books.py similarity index 100% rename from src/live/books.py rename to Old/src/live/books.py diff --git a/src/live/cerbero_client.py b/Old/src/live/cerbero_client.py similarity index 100% rename from src/live/cerbero_client.py rename to Old/src/live/cerbero_client.py diff --git a/src/live/dashboard.py b/Old/src/live/dashboard.py similarity index 100% rename from src/live/dashboard.py rename to Old/src/live/dashboard.py diff --git a/src/live/execution.py b/Old/src/live/execution.py similarity index 100% rename from src/live/execution.py rename to Old/src/live/execution.py diff --git a/src/live/grid_worker.py b/Old/src/live/grid_worker.py similarity index 100% rename from src/live/grid_worker.py rename to Old/src/live/grid_worker.py diff --git a/src/live/multi_runner.py b/Old/src/live/multi_runner.py similarity index 100% rename from src/live/multi_runner.py rename to Old/src/live/multi_runner.py diff --git a/src/live/pairs_worker.py b/Old/src/live/pairs_worker.py similarity index 100% rename from src/live/pairs_worker.py rename to Old/src/live/pairs_worker.py diff --git a/src/live/paper_trader.py b/Old/src/live/paper_trader.py similarity index 100% rename from src/live/paper_trader.py rename to Old/src/live/paper_trader.py diff --git a/src/live/rotation_worker.py b/Old/src/live/rotation_worker.py similarity index 100% rename from src/live/rotation_worker.py rename to Old/src/live/rotation_worker.py diff --git a/src/live/signal_engine.py b/Old/src/live/signal_engine.py similarity index 100% rename from src/live/signal_engine.py rename to Old/src/live/signal_engine.py diff --git a/src/live/strategy_loader.py b/Old/src/live/strategy_loader.py similarity index 100% rename from src/live/strategy_loader.py rename to Old/src/live/strategy_loader.py diff --git a/src/live/strategy_worker.py b/Old/src/live/strategy_worker.py similarity index 100% rename from src/live/strategy_worker.py rename to Old/src/live/strategy_worker.py diff --git a/src/live/telegram_notifier.py b/Old/src/live/telegram_notifier.py similarity index 100% rename from src/live/telegram_notifier.py rename to Old/src/live/telegram_notifier.py diff --git a/src/live/tsmom_worker.py b/Old/src/live/tsmom_worker.py similarity index 100% rename from src/live/tsmom_worker.py rename to Old/src/live/tsmom_worker.py diff --git a/src/live/xsec_worker.py b/Old/src/live/xsec_worker.py similarity index 100% rename from src/live/xsec_worker.py rename to Old/src/live/xsec_worker.py diff --git a/src/portfolio/__init__.py b/Old/src/nn/__init__.py similarity index 100% rename from src/portfolio/__init__.py rename to Old/src/nn/__init__.py diff --git a/tests/__init__.py b/Old/src/portfolio/__init__.py similarity index 100% rename from tests/__init__.py rename to Old/src/portfolio/__init__.py diff --git a/src/portfolio/base.py b/Old/src/portfolio/base.py similarity index 100% rename from src/portfolio/base.py rename to Old/src/portfolio/base.py diff --git a/src/portfolio/ledger.py b/Old/src/portfolio/ledger.py similarity index 100% rename from src/portfolio/ledger.py rename to Old/src/portfolio/ledger.py diff --git a/src/portfolio/runner.py b/Old/src/portfolio/runner.py similarity index 100% rename from src/portfolio/runner.py rename to Old/src/portfolio/runner.py diff --git a/src/portfolio/sleeves.py b/Old/src/portfolio/sleeves.py similarity index 100% rename from src/portfolio/sleeves.py rename to Old/src/portfolio/sleeves.py diff --git a/src/portfolio/weighting.py b/Old/src/portfolio/weighting.py similarity index 100% rename from src/portfolio/weighting.py rename to Old/src/portfolio/weighting.py diff --git a/src/strategies/fade_base.py b/Old/src/strategies/fade_base.py similarity index 100% rename from src/strategies/fade_base.py rename to Old/src/strategies/fade_base.py diff --git a/strategies.yml b/Old/strategies.yml similarity index 100% rename from strategies.yml rename to Old/strategies.yml diff --git a/tests/portfolio/__init__.py b/Old/tests/__init__.py similarity index 100% rename from tests/portfolio/__init__.py rename to Old/tests/__init__.py diff --git a/Old/tests/portfolio/__init__.py b/Old/tests/portfolio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/portfolio/test_backtest_parity.py b/Old/tests/portfolio/test_backtest_parity.py similarity index 100% rename from tests/portfolio/test_backtest_parity.py rename to Old/tests/portfolio/test_backtest_parity.py diff --git a/tests/portfolio/test_backtest_parity_cap.py b/Old/tests/portfolio/test_backtest_parity_cap.py similarity index 100% rename from tests/portfolio/test_backtest_parity_cap.py rename to Old/tests/portfolio/test_backtest_parity_cap.py diff --git a/tests/portfolio/test_bars.py b/Old/tests/portfolio/test_bars.py similarity index 100% rename from tests/portfolio/test_bars.py rename to Old/tests/portfolio/test_bars.py diff --git a/tests/portfolio/test_basket_worker.py b/Old/tests/portfolio/test_basket_worker.py similarity index 100% rename from tests/portfolio/test_basket_worker.py rename to Old/tests/portfolio/test_basket_worker.py diff --git a/tests/portfolio/test_capital_lineage.py b/Old/tests/portfolio/test_capital_lineage.py similarity index 100% rename from tests/portfolio/test_capital_lineage.py rename to Old/tests/portfolio/test_capital_lineage.py diff --git a/tests/portfolio/test_cerbero_v2.py b/Old/tests/portfolio/test_cerbero_v2.py similarity index 100% rename from tests/portfolio/test_cerbero_v2.py rename to Old/tests/portfolio/test_cerbero_v2.py diff --git a/tests/portfolio/test_close_confirm_sl.py b/Old/tests/portfolio/test_close_confirm_sl.py similarity index 100% rename from tests/portfolio/test_close_confirm_sl.py rename to Old/tests/portfolio/test_close_confirm_sl.py diff --git a/tests/portfolio/test_config.py b/Old/tests/portfolio/test_config.py similarity index 100% rename from tests/portfolio/test_config.py rename to Old/tests/portfolio/test_config.py diff --git a/tests/portfolio/test_definitions.py b/Old/tests/portfolio/test_definitions.py similarity index 100% rename from tests/portfolio/test_definitions.py rename to Old/tests/portfolio/test_definitions.py diff --git a/tests/portfolio/test_dip01.py b/Old/tests/portfolio/test_dip01.py similarity index 100% rename from tests/portfolio/test_dip01.py rename to Old/tests/portfolio/test_dip01.py diff --git a/tests/portfolio/test_feed_book_gap.py b/Old/tests/portfolio/test_feed_book_gap.py similarity index 100% rename from tests/portfolio/test_feed_book_gap.py rename to Old/tests/portfolio/test_feed_book_gap.py diff --git a/tests/portfolio/test_freeze_gate.py b/Old/tests/portfolio/test_freeze_gate.py similarity index 100% rename from tests/portfolio/test_freeze_gate.py rename to Old/tests/portfolio/test_freeze_gate.py diff --git a/tests/portfolio/test_horizon_exit.py b/Old/tests/portfolio/test_horizon_exit.py similarity index 100% rename from tests/portfolio/test_horizon_exit.py rename to Old/tests/portfolio/test_horizon_exit.py diff --git a/tests/portfolio/test_hurst_lossguard.py b/Old/tests/portfolio/test_hurst_lossguard.py similarity index 100% rename from tests/portfolio/test_hurst_lossguard.py rename to Old/tests/portfolio/test_hurst_lossguard.py diff --git a/tests/portfolio/test_intrabar_exit.py b/Old/tests/portfolio/test_intrabar_exit.py similarity index 100% rename from tests/portfolio/test_intrabar_exit.py rename to Old/tests/portfolio/test_intrabar_exit.py diff --git a/tests/portfolio/test_inverted_tp.py b/Old/tests/portfolio/test_inverted_tp.py similarity index 100% rename from tests/portfolio/test_inverted_tp.py rename to Old/tests/portfolio/test_inverted_tp.py diff --git a/tests/portfolio/test_ledger.py b/Old/tests/portfolio/test_ledger.py similarity index 100% rename from tests/portfolio/test_ledger.py rename to Old/tests/portfolio/test_ledger.py diff --git a/tests/portfolio/test_min_tp_frac.py b/Old/tests/portfolio/test_min_tp_frac.py similarity index 100% rename from tests/portfolio/test_min_tp_frac.py rename to Old/tests/portfolio/test_min_tp_frac.py diff --git a/tests/portfolio/test_netting_close.py b/Old/tests/portfolio/test_netting_close.py similarity index 100% rename from tests/portfolio/test_netting_close.py rename to Old/tests/portfolio/test_netting_close.py diff --git a/tests/portfolio/test_pairs_execution.py b/Old/tests/portfolio/test_pairs_execution.py similarity index 100% rename from tests/portfolio/test_pairs_execution.py rename to Old/tests/portfolio/test_pairs_execution.py diff --git a/tests/portfolio/test_pairs_forming_bar.py b/Old/tests/portfolio/test_pairs_forming_bar.py similarity index 100% rename from tests/portfolio/test_pairs_forming_bar.py rename to Old/tests/portfolio/test_pairs_forming_bar.py diff --git a/tests/portfolio/test_real_close_dsl.py b/Old/tests/portfolio/test_real_close_dsl.py similarity index 100% rename from tests/portfolio/test_real_close_dsl.py rename to Old/tests/portfolio/test_real_close_dsl.py diff --git a/tests/portfolio/test_real_truth.py b/Old/tests/portfolio/test_real_truth.py similarity index 100% rename from tests/portfolio/test_real_truth.py rename to Old/tests/portfolio/test_real_truth.py diff --git a/tests/portfolio/test_reconcile_resting.py b/Old/tests/portfolio/test_reconcile_resting.py similarity index 100% rename from tests/portfolio/test_reconcile_resting.py rename to Old/tests/portfolio/test_reconcile_resting.py diff --git a/tests/portfolio/test_rotation_worker.py b/Old/tests/portfolio/test_rotation_worker.py similarity index 100% rename from tests/portfolio/test_rotation_worker.py rename to Old/tests/portfolio/test_rotation_worker.py diff --git a/tests/portfolio/test_runner_build.py b/Old/tests/portfolio/test_runner_build.py similarity index 100% rename from tests/portfolio/test_runner_build.py rename to Old/tests/portfolio/test_runner_build.py diff --git a/tests/portfolio/test_runner_honest.py b/Old/tests/portfolio/test_runner_honest.py similarity index 100% rename from tests/portfolio/test_runner_honest.py rename to Old/tests/portfolio/test_runner_honest.py diff --git a/tests/portfolio/test_runner_rebalance.py b/Old/tests/portfolio/test_runner_rebalance.py similarity index 100% rename from tests/portfolio/test_runner_rebalance.py rename to Old/tests/portfolio/test_runner_rebalance.py diff --git a/tests/portfolio/test_sh01_last_block.py b/Old/tests/portfolio/test_sh01_last_block.py similarity index 100% rename from tests/portfolio/test_sh01_last_block.py rename to Old/tests/portfolio/test_sh01_last_block.py diff --git a/tests/portfolio/test_single_leg_orphan.py b/Old/tests/portfolio/test_single_leg_orphan.py similarity index 100% rename from tests/portfolio/test_single_leg_orphan.py rename to Old/tests/portfolio/test_single_leg_orphan.py diff --git a/tests/portfolio/test_sleeves.py b/Old/tests/portfolio/test_sleeves.py similarity index 100% rename from tests/portfolio/test_sleeves.py rename to Old/tests/portfolio/test_sleeves.py diff --git a/tests/portfolio/test_stale_feed.py b/Old/tests/portfolio/test_stale_feed.py similarity index 100% rename from tests/portfolio/test_stale_feed.py rename to Old/tests/portfolio/test_stale_feed.py diff --git a/tests/portfolio/test_tp_phantom.py b/Old/tests/portfolio/test_tp_phantom.py similarity index 100% rename from tests/portfolio/test_tp_phantom.py rename to Old/tests/portfolio/test_tp_phantom.py diff --git a/tests/portfolio/test_tsmom_worker.py b/Old/tests/portfolio/test_tsmom_worker.py similarity index 100% rename from tests/portfolio/test_tsmom_worker.py rename to Old/tests/portfolio/test_tsmom_worker.py diff --git a/tests/portfolio/test_venue_lock_breaker.py b/Old/tests/portfolio/test_venue_lock_breaker.py similarity index 100% rename from tests/portfolio/test_venue_lock_breaker.py rename to Old/tests/portfolio/test_venue_lock_breaker.py diff --git a/tests/portfolio/test_weighting.py b/Old/tests/portfolio/test_weighting.py similarity index 100% rename from tests/portfolio/test_weighting.py rename to Old/tests/portfolio/test_weighting.py diff --git a/tests/portfolio/test_win_net_of_fees.py b/Old/tests/portfolio/test_win_net_of_fees.py similarity index 100% rename from tests/portfolio/test_win_net_of_fees.py rename to Old/tests/portfolio/test_win_net_of_fees.py diff --git a/VERSION b/VERSION index 73a29c9..227cea2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.34 +2.0.0 diff --git a/Libro_frattali.summary.md b/docs/Libro_frattali.summary.md similarity index 100% rename from Libro_frattali.summary.md rename to docs/Libro_frattali.summary.md diff --git a/Pythagoras_Trading_Prediction.summary.md b/docs/Pythagoras_Trading_Prediction.summary.md similarity index 100% rename from Pythagoras_Trading_Prediction.summary.md rename to docs/Pythagoras_Trading_Prediction.summary.md diff --git a/docs/diary/2026-06-19-deribit-history.md b/docs/diary/2026-06-19-deribit-history.md new file mode 100644 index 0000000..02589b4 --- /dev/null +++ b/docs/diary/2026-06-19-deribit-history.md @@ -0,0 +1,109 @@ +# 2026-06-19 — Reset: storico certificato da Deribit mainnet, ripartenza pulita + +Documento di fondazione per la nuova ricerca. Riassume perché la libreria "validata" è +crollata, come abbiamo ricostruito e **certificato** lo storico, e quali sono le regole +e l'universo dati su cui costruire da qui in poi. Lo storico delle 60+ voci di diario +precedenti e tutto il codice/strategie vecchio sono archiviati in `Old/` (non cancellati). + +--- + +## 1. Cosa è successo — la contaminazione + +L'intera libreria ("+201% / +1238% / +16492% validato OOS") era basata su uno **storico +contaminato**. Radice nel codice (`src/data/downloader.py`, ora in `Old/`): la fase +"Deribit" veniva pescata da Cerbero col **token TESTNET** (print fantasma) e la fase +storica da **Binance/USDT**. Gli edge nascevano da wick fantasma e da entry su estremi di +candela mai scambiati. + +**Binance NON è la verità** (cross-check multi-venue): è denominato USDT, sistematicamente +~10 bps fuori dal consenso, e sotto depeg USDT (mag-2022) fino al **3.16%** lontano dal +prezzo USD reale. "Ripulire verso Binance" avrebbe iniettato errore. Il venue più vicino al +consenso (0-1 bps) è **Deribit mainnet** — che è anche **dove eseguiamo**: ancora migliore e +ancora rilevante coincidono. + +## 2. La via scelta — rebuild da Deribit mainnet + +`scripts/analysis/rebuild_history.py` (TENUTO): ricostruzione da **`ccxt.deribit` mainnet** +(pubblico, tokenless). Principi: +- **Strumento prezzo**: BTC/ETH → inverse (storia lunga 2018/2019, ~3 bps dal lineare USDC + che eseguiamo); alt → lineare USDC (unica opzione, solo dal 2022). +- **Base unica + resample**: si scarica un solo TF base (5m) e si derivano 15m/1h → + coerenza interna garantita (si valida una serie, non venti). +- **Copertura onesta**: major 2018/2019→oggi; alt SOLO dal 2022 (prima non eseguibili su + Deribit → backtestarli pre-2022 = validare un mercato che non potevamo tradare). + +Eseguito su 8 asset → 24 feed (5m/15m/1h) il 2026-06-19. + +## 3. Il verdetto — la libreria era artefatto + +Ri-eseguiti tutti i backtest sul feed pulito con gli engine canonici (ora in `Old/`). + +**FADE** (6 sleeve, 1h E 15m): tutte NEGATIVE, ogni anno. Es. MR01_BTC −41% (claim +201%), +MR01_ETH −25% (claim +1238%), MR02_ETH −83% a 15m. I numeri 15m coincidono col check mainnet +indipendente del 18-06 → due path-dati separati, stessa conclusione. + +**Intera libreria** (`report_families.py`, ora in `Old/`): ogni portafoglio profondamente +negativo (MASTER Sharpe −2.3…−3.0, DD ~40%; il claim "Sharpe ~5, ogni anno positivo" è falso). +- ☠️ **FADE / PAIRS / XS01 / TSM01**: morti o inerti. Il flagship PR_ETHBTC e il blend 15m + deployato live sono negativi ogni anno. +- 🟡 **HONEST** (TR01 volatile, DIP01 marginale) e **SH01** (specie ETH, 5/6 anni +) mostrano + segnale residuo — ma NON bastano a rendere positivo alcun portafoglio, e vanno distinti da + fortuna di regime. Sono gli UNICI candidati a sopravvivere a una ri-validazione seria. + +## 4. La certificazione dello storico + +`scripts/analysis/certify_feed.py` (TENUTO) — suite read-only, 4 blocchi: +1. **Integrità**: 24/24 feed OK (zero violazioni OHLC, NaN, prezzi≤0, dup; **zero gap**). +2. **Coerenza resample**: 5m→15m/1h ricalcolato == salvato, **maxΔ 0.00 bps**. +3. **Spike**: i pochi sospetti sono crash REALI (COVID 2020, mag-2021, FTX 2022), non contaminazione. +4. **Cross-venue vs Coinbase USD** (venue indipendente, USD non USDT), storia intera per anno: + +| Asset | median bps | barre >1% | Verdetto | +|---|---|---|---| +| **BTC** | 2.0–6.4 | 0.0–0.2% | 🟢 certificato pulito, 2018→oggi | +| **ETH** | 2.0–6.0 | 0.0–0.2% | 🟢 certificato pulito, 2019→oggi | +| SOL | 4–8 | 0–1.3% | 🟡 pulito da ~2024; 2022-23 rumoroso | +| ADA | 9–13 | 0.3–3.5% | 🟡 basis largo persistente | +| XRP | — | — | ⚠️ non certificabile (Coinbase delistò XRP 2021-23, causa SEC) | +| LTC / DOGE | 24–36 (2022) | 1–21% | 🔴 divergenti + 50-82% barre flat | +| BNB | — | — | ⚠️ non listato su Coinbase + storia da 2024-10 | + +I max bps su BTC/ETH (889/594) sono singole barre di crash; mediana annua 2-6 bps → **la +storia vecchia, dove viveva la contaminazione, è ora pulita contro un venue USD indipendente.** + +**Illiquidità alt** (barre flat O=H=L=C): BTC/ETH ~0% a 1h; alt pesanti (LTC 5m 82%, run fino a +~3 giorni; DOGE/BNB ~78%). I 5m/15m alt "tradano" un book fermo — spiega a posteriori la +fragilità di pairs-su-LTC e XS01. + +## 5. REGOLE per la nuova ricerca + +- **Universo certificato = BTC / ETH**, ogni timeframe (5m/15m/1h). È l'unico dato attivo in + `data/raw` (gli alt sono archiviati in `Old/data/raw`). Il guardrail è fisico: `load_data` + su un alt solleva `FileNotFoundError`. +- **Fonte di verità = Deribit mainnet** (dove eseguiamo). Binance/USDT solo come check, mai + come ancora. Aggiornare il dato con `rebuild_history.py`, MAI col vecchio `download_all` + (token testnet). +- **Metodologia** (CLAUDE.md, invariata): ingresso eseguibile (direzione/prezzo decisi entro + `close[i]`, mai entry su estremi di candela), backtest netto fee 0.10% RT, OOS held-out + + griglia + sweep fee. **In più, lezione di oggi**: il gate PORT06 full+OOS NON basta — un + edge va incrociato con la **liquidità reale** del book e con la **plausibilità del prezzo** + (cross-venue), o si finisce per "validare" un book fermo / wick fantasma. + +## 6. Stato della ripartenza (cleanup eseguito) + +- **Archiviato in `Old/`** (git preserva tutto): tutte le strategie, lo stack live + (`src/live`, `src/portfolio`, runner/executor, yml, docker), i ~100 script di ricerca/gate, + waste/games/portfolios, i dati non certificati + cache, le 60+ voci di diario, le spec vecchie. +- **Skeleton tenuto**: `src/strategies/{base,indicators}` (Strategy ABC), `src/fractal`, + `src/backtest/engine`, `src/data/downloader` (`load_data`); i 4 tool dati certificati + (`rebuild_history`, `certify_feed`, `audit_feed`, `multi_source_check`); dati BTC/ETH; + config base; i due summary sui frattali (tesi di ricerca). +- **Esecuzione DISABILITATA**, conto mainnet flat. La riorganizzazione è in staging (non + committata). + +### Code minori rimasti +- 3 dir di stato live root-owned (`data/portfolio_paper{,_stats}`, `data/portfolios`) non + archiviabili senza `sudo` → spostare a mano in `Old/data/` quando comodo. +- `clean_feed.py` (arbitrava verso Binance) è già archiviato — approccio superato. +- XRP ri-certificabile solo su venue USD con storia profonda (marginale, non core). +- `CLAUDE.md` è in gran parte stale (descrive strategie morte) → va riscritto per il reset. diff --git a/scripts/analysis/audit_feed.py b/scripts/analysis/audit_feed.py new file mode 100644 index 0000000..146b1c1 --- /dev/null +++ b/scripts/analysis/audit_feed.py @@ -0,0 +1,132 @@ +"""AUDIT INTEGRITA' — confronta OGNI parquet storico col prezzo REALE Binance. + +Dopo la scoperta che fade/pairs/DIP01 erano edge FINTI (print fantasma del feed testnet +Cerbero), serve CERTEZZA del dato: quanto e' contaminato OGNI file? Per ogni parquet +confronta il CLOSE col Binance spot allineato (merge_asof nearest) e riporta la quota di +barre >1% e >3% fuori dalla realta', per file e per anno peggiore. + +Riferimento: Binance 5m (BTC/ETH sub-orari) e 1h (tutti), provato ~ mainnet (disc <0.13%). +NON modifica nulla: solo lettura + report. Cache ref in data/raw/_ref_*.parquet. + + uv run python scripts/analysis/audit_feed.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np, pandas as pd, ccxt + +RAW = PROJECT_ROOT / "data" / "raw" +SYM = {"btc": "BTC/USDT", "eth": "ETH/USDT", "ada": "ADA/USDT", "bnb": "BNB/USDT", + "doge": "DOGE/USDT", "ltc": "LTC/USDT", "sol": "SOL/USDT", "xrp": "XRP/USDT"} +TF_MIN = {"5m": 5, "8m": 8, "13m": 13, "15m": 15, "19m": 19, "30m": 30, "1h": 60, + "2h": 120, "3h": 180, "4h": 240, "5h": 300, "6h": 360, "8h": 480, "12h": 720, + "13h": 780, "19h": 1140, "24h": 1440, "36h": 2160, "48h": 2880} +_EX = None + + +def ex(): + global _EX + if _EX is None: + _EX = ccxt.binance({"enableRateLimit": True}) + return _EX + + +def get_ref(asset: str, base: str) -> pd.DataFrame: + """Serie Binance di riferimento (cache). Riusa _real_/_ref_ se presenti.""" + for pref in ("_real_", "_ref_"): + c = RAW / f"{pref}{asset}_{base}.parquet" + if c.exists(): + return pd.read_parquet(c)[["timestamp", "close"]] + cache = RAW / f"_ref_{asset}_{base}.parquet" + start = "2018-01-01" if base == "5m" else "2017-08-01" + start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp("2026-05-27", tz="UTC").timestamp() * 1000) + tf_ms = TF_MIN[base] * 60 * 1000 + rows, since = [], start_ms + while since <= end_ms: + for _ in range(3): + try: + r = ex().fetch_ohlcv(SYM[asset], base, since=since, limit=1000); break + except Exception: + r = [] + if not r: + break + rows += r + nxt = int(r[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + df = pd.DataFrame(rows, columns=["timestamp", "open", "high", "low", "close", "volume"]) + df = df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + df[["timestamp", "open", "high", "low", "close", "volume"]].to_parquet(cache, index=False) + return df[["timestamp", "close"]] + + +def audit(asset: str, tf: str, ref5, ref1h): + f = RAW / f"{asset}_{tf}.parquet" + if not f.exists(): + return None + df = pd.read_parquet(f)[["timestamp", "close"]].copy() + df["timestamp"] = df["timestamp"].astype("int64") + df = df.dropna(subset=["close"]).sort_values("timestamp").reset_index(drop=True) + base = "5m" if (TF_MIN[tf] < 60 and asset in ("btc", "eth")) else "1h" + ref = (ref5 if base == "5m" else ref1h).get(asset) + if ref is None or len(ref) == 0: + return {"file": f"{asset}_{tf}", "rows": len(df), "no_ref": True} + ref = ref.rename(columns={"close": "cref"}).sort_values("timestamp") + tol = TF_MIN[base] * 60 * 1000 + m = pd.merge_asof(df, ref, on="timestamp", direction="nearest", tolerance=tol) + m = m.dropna(subset=["cref"]) + m = m[m["cref"] > 0] + if len(m) == 0: + return {"file": f"{asset}_{tf}", "rows": len(df), "covered": 0, "no_ref": True} + disc = (m["close"] - m["cref"]).abs() / m["cref"] + yr = pd.to_datetime(m["timestamp"], unit="ms", utc=True).dt.year + worst_y, worst_p = 0, 0.0 + for y, g in disc.groupby(yr): + p = float((g > 0.01).mean() * 100) + if p > worst_p: + worst_p, worst_y = p, int(y) + return {"file": f"{asset}_{tf}", "rows": len(df), "covered": len(m) / len(df) * 100, + "p1": float((disc > 0.01).mean() * 100), "p3": float((disc > 0.03).mean() * 100), + "med": float(disc.median() * 100), "worst_y": worst_y, "worst_p": worst_p} + + +def main(): + assets = list(SYM) + print("Fetch riferimenti Binance (1h tutti + 5m BTC/ETH)...", flush=True) + ref1h = {a: get_ref(a, "1h") for a in assets} + ref5 = {a: get_ref(a, "5m") for a in ("btc", "eth")} + print("\n" + "=" * 96) + print(" AUDIT INTEGRITA' FEED — % barre con CLOSE >1% (e >3%) fuori da Binance spot") + print("=" * 96) + print(f" {'file':<10s}{'righe':>8s}{'cov%':>6s}{'med%':>7s}{'>1%':>7s}{'>3%':>7s}{'worst-anno':>13s} giudizio") + print(" " + "-" * 92) + rows = [] + for a in assets: + tfs = [tf for tf in TF_MIN if (RAW / f"{a}_{tf}.parquet").exists()] + # ordina per minuti + for tf in sorted(tfs, key=lambda t: TF_MIN[t]): + r = audit(a, tf, ref5, ref1h) + if r: + rows.append(r) + # ordina per contaminazione discendente + clean = [r for r in rows if not r.get("no_ref")] + clean.sort(key=lambda r: -r.get("p1", 0)) + poisoned = sum(1 for r in clean if r["p1"] >= 1.0) + for r in clean: + verdict = "PULITO" if r["p1"] < 0.5 else ("sospetto" if r["p1"] < 1.0 else "CONTAMINATO") + print(f" {r['file']:<10s}{r['rows']:>8d}{r['covered']:>6.0f}{r['med']:>7.2f}" + f"{r['p1']:>7.1f}{r['p3']:>7.1f}{('%d:%.0f%%'%(r['worst_y'],r['worst_p'])):>13s} {verdict}") + noref = [r for r in rows if r.get("no_ref")] + print(" " + "-" * 92) + print(f" Totale file audited: {len(clean)} | CONTAMINATI (>1% barre fuori): {poisoned} | " + f"PULITI (<0.5%): {sum(1 for r in clean if r['p1']<0.5)} | senza-ref: {len(noref)}") + if noref: + print(" senza riferimento Binance:", ", ".join(r["file"] for r in noref)) + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/certify_feed.py b/scripts/analysis/certify_feed.py new file mode 100644 index 0000000..2fdfde8 --- /dev/null +++ b/scripts/analysis/certify_feed.py @@ -0,0 +1,259 @@ +"""CERTIFICAZIONE STORICO — dimostrare che data/raw (rebuild Deribit mainnet) e' PULITO. + +Dopo che il feed contaminato (print fantasma testnet) ha invalidato l'intera libreria, +NESSUNA nuova ricerca vale finche' lo storico non e' CERTIFICATO. Ricostruire non basta: +va dimostrato pulito, su TUTTA la storia (non solo il recente) e su tutte le dimensioni. + +Quattro blocchi, read-only (nessuna scrittura): + (1) INTEGRITA' STRUTTURALE (locale, tutti asset/TF): invarianti OHLC (high>=low, + high>=max(o,c), low<=min(o,c)), prezzi>0, volume>=0, NaN, ts monotono/dup, GAP + (barre mancanti vs griglia attesa), run di barre FLAT (O=H=L=C). + (2) COERENZA RESAMPLE (locale): ricalcolo 15m/1h dal 5m e confronto col salvato -> + la "base unica + resample" e' davvero internamente coerente (errore ~0). + (3) SCAN SPIKE / FAKE-WICK (locale): firma della contaminazione (high/low che sfora + >THR il cluster di close vicino). Sul feed pulito deve essere ~0. + (4) ACCORDO CROSS-VENUE (rete): close 1h vs COINBASE USD (venue indipendente, USD non + USDT), STORIA INTERA per-anno: bps med/p95/max + %barre >50bps/>1%/>3%. E' il test + di VERITA' del prezzo, specie sui vecchi anni dove viveva la contaminazione. + + uv run python scripts/analysis/certify_feed.py # tutto (locale + rete) + uv run python scripts/analysis/certify_feed.py --local # solo blocchi 1-3 (veloce) + uv run python scripts/analysis/certify_feed.py --asset BTC ETH +""" +from __future__ import annotations +import sys +import time +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np +import pandas as pd +import ccxt + +RAW = PROJECT_ROOT / "data" / "raw" +ASSETS = ["BTC", "ETH", "SOL", "ADA", "XRP", "LTC", "DOGE", "BNB"] +TFS = ["5m", "15m", "1h"] +TF_MIN = {"5m": 5, "15m": 15, "1h": 60} +SPIKE_THR = 0.10 # high/low oltre +-10% dal cluster di close vicino = sospetto +# Coinbase USD: venue indipendente da Deribit, denominato USD (non USDT). BNB non listato. +COINBASE_SYM = {"BTC": "BTC/USD", "ETH": "ETH/USD", "SOL": "SOL/USD", "ADA": "ADA/USD", + "XRP": "XRP/USD", "LTC": "LTC/USD", "DOGE": "DOGE/USD", "BNB": None} + + +def load(asset, tf): + p = RAW / f"{asset.lower()}_{tf}.parquet" + if not p.exists(): + return None + return pd.read_parquet(p) + + +# ----------------------------- (1) INTEGRITA' ----------------------------- +def integrity(asset, tf, df): + o, h, l, c = (df[x].values for x in ("open", "high", "low", "close")) + v = df["volume"].values + ts = df["timestamp"].values + issues = [] + nan = int(df[["open", "high", "low", "close", "volume"]].isna().sum().sum()) + if nan: issues.append(f"NaN={nan}") + nonpos = int((np.minimum.reduce([o, h, l, c]) <= 0).sum()) + if nonpos: issues.append(f"prezzo<=0={nonpos}") + if (v < 0).sum(): issues.append(f"vol<0={int((v<0).sum())}") + hl = int((h < l).sum()) + if hl: issues.append(f"high np.minimum(o, c)).sum()) + if lo: issues.append(f"low>min(o,c)={lo}") + dts = np.diff(ts) + dup = int((dts == 0).sum()) + if dup: issues.append(f"ts-dup={dup}") + if (dts < 0).sum(): issues.append(f"ts-non-monotono={int((dts<0).sum())}") + step = TF_MIN[tf] * 60_000 + gaps = dts[dts > step] + miss = int((gaps // step - 1).sum()) if len(gaps) else 0 + flat = int(((o == h) & (h == l) & (l == c)).sum()) + # run flat piu' lunga + fmask = (o == h) & (h == l) & (l == c) + longest = cur = 0 + for f in fmask: + cur = cur + 1 if f else 0 + longest = max(longest, cur) + status = "OK" if not issues else "**" + " ".join(issues) + return dict(n=len(df), gaps=len(gaps), miss=miss, flat=flat, flatpct=flat/len(df)*100, + flatrun=longest, status=status) + + +# ----------------------------- (2) RESAMPLE ----------------------------- +def resample(base, tf): + g = base.copy() + g.index = pd.to_datetime(g["timestamp"], unit="ms", utc=True) + rule = {"15m": "15min", "1h": "1h"}[tf] + out = g.resample(rule, label="left", closed="left").agg( + {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}) + out = out.dropna(subset=["open"]) + epoch = pd.Timestamp("1970-01-01", tz="UTC") + out.insert(0, "timestamp", ((out.index - epoch) // pd.Timedelta(milliseconds=1)).astype("int64")) + return out.reset_index(drop=True) + + +def resample_coherence(asset): + base = load(asset, "5m") + if base is None: + return "no 5m" + res = [] + for tf in ("15m", "1h"): + stored = load(asset, tf) + if stored is None: + res.append(f"{tf}:no-file"); continue + rb = resample(base, tf) + m = stored.merge(rb, on="timestamp", suffixes=("_s", "_r"), how="inner") + if len(m) == 0: + res.append(f"{tf}:0-overlap"); continue + maxerr = 0.0 + for col in ("open", "high", "low", "close"): + d = (m[f"{col}_s"] - m[f"{col}_r"]).abs() / m[f"{col}_r"].replace(0, np.nan) + maxerr = max(maxerr, float(d.max(skipna=True)) if len(d) else 0.0) + nmiss = abs(len(stored) - len(rb)) + res.append(f"{tf}: maxΔ {maxerr*1e4:.2f}bps Δrows {nmiss}") + return " | ".join(res) + + +# ----------------------------- (3) SPIKE ----------------------------- +def spike_scan(df, thr=SPIKE_THR): + c = df["close"].values.astype(float) + h = df["high"].values.astype(float) + l = df["low"].values.astype(float) + n = len(c) + s = pd.Series(c) + # cluster di close vicino (mediana finestra 7 centrata, causale-agnostica = audit) + cluster = s.rolling(7, center=True, min_periods=3).median().values + hi = h > cluster * (1 + thr) + lo = l < cluster * (1 - thr) + susp = hi | lo + idx = np.where(susp)[0] + worst = [] + if len(idx): + dev = np.where(hi, h / cluster - 1, np.where(lo, 1 - l / cluster, 0.0)) + order = idx[np.argsort(-np.abs(dev[idx]))][:3] + ts = pd.to_datetime(df["timestamp"].values, unit="ms", utc=True) + for i in order: + worst.append(f"{ts[i].date()} {dev[i]*100:+.0f}%") + return int(susp.sum()), worst + + +# ----------------------------- (4) CROSS-VENUE ----------------------------- +def fetch_coinbase_1h(sym, start_ms, end_ms): + ex = ccxt.coinbase({"enableRateLimit": True}) + if not ex.markets: + ex.load_markets() + if sym not in ex.markets: + return None, f"simbolo {sym} assente su Coinbase" + out, since, guard = {}, start_ms, 0 + while since <= end_ms and guard < 4000: + guard += 1 + for attempt in range(3): + try: + r = ex.fetch_ohlcv(sym, "1h", since=since, limit=300) + break + except Exception as e: + if attempt == 2: + return (pd.Series(out) if out else None), f"err {type(e).__name__}" + time.sleep(2 ** attempt) + r = [x for x in r if int(x[0]) >= since] + if not r: + break + for x in r: + if start_ms <= int(x[0]) <= end_ms and x[4]: + out[int(x[0])] = float(x[4]) + nxt = int(r[-1][0]) + 3600_000 + if nxt <= since: + break + since = nxt + return (pd.Series(out) if out else None), f"{len(out)} barre" + + +def cross_venue(asset): + sym = COINBASE_SYM[asset] + if sym is None: + return None, "no-coinbase-ref (BNB non listato)" + df1h = load(asset, "1h") + if df1h is None: + return None, "no 1h locale" + ts = pd.to_datetime(df1h["timestamp"], unit="ms", utc=True) + s_ms = int(df1h["timestamp"].iloc[0]) + e_ms = int(df1h["timestamp"].iloc[-1]) + ref, note = fetch_coinbase_1h(sym, s_ms, e_ms) + if ref is None: + return None, note + a = df1h.set_index("timestamp")["close"] + m = pd.concat([a.rename("a"), ref.rename("b")], axis=1, join="inner").dropna() + if len(m) == 0: + return None, "0 overlap" + m["bps"] = (m["a"] - m["b"]).abs() / m["b"] * 1e4 + m["year"] = pd.to_datetime(m.index, unit="ms", utc=True).year + rows = [] + for y, g in m.groupby("year"): + rows.append((y, len(g), g["bps"].median(), g["bps"].quantile(.95), g["bps"].max(), + (g["bps"] > 50).mean()*100, (g["bps"] > 100).mean()*100, (g["bps"] > 300).mean()*100)) + return rows, f"overlap {len(m)} barre vs Coinbase {sym}" + + +def main(): + argv = sys.argv[1:] + local_only = "--local" in argv + assets = ASSETS + if "--asset" in argv: + i = argv.index("--asset") + assets = [a.upper() for a in argv[i+1:] if not a.startswith("--")] + + print("=" * 100) + print(" (1) INTEGRITA' STRUTTURALE — per asset/TF") + print("=" * 100) + print(f" {'file':<11s}{'barre':>9s}{'gap':>6s}{'mancanti':>9s}{'flat':>8s}{'flat%':>7s}{'flatRun':>8s} status") + print(" " + "-" * 96) + for a in assets: + for tf in TFS: + df = load(a, tf) + if df is None: + print(f" {a.lower()+'_'+tf:<11s} (assente)"); continue + r = integrity(a, tf, df) + print(f" {a.lower()+'_'+tf:<11s}{r['n']:>9d}{r['gaps']:>6d}{r['miss']:>9d}{r['flat']:>8d}" + f"{r['flatpct']:>6.1f}%{r['flatrun']:>8d} {r['status']}") + + print("\n" + "=" * 100) + print(" (2) COERENZA RESAMPLE (5m -> 15m/1h ricalcolato == salvato)") + print("=" * 100) + for a in assets: + print(f" {a:<5s} {resample_coherence(a)}") + + print("\n" + "=" * 100) + print(f" (3) SCAN SPIKE / FAKE-WICK (high/low oltre +-{SPIKE_THR*100:.0f}% dal cluster close vicino)") + print("=" * 100) + for a in assets: + for tf in TFS: + df = load(a, tf) + if df is None: + continue + n, worst = spike_scan(df) + tag = "OK" if n == 0 else f"** {n} sospetti — {', '.join(worst)}" + print(f" {a.lower()+'_'+tf:<11s} {tag}") + + if local_only: + print("\n (--local: salto il blocco rete cross-venue)") + return + + print("\n" + "=" * 100) + print(" (4) ACCORDO CROSS-VENUE — close 1h vs COINBASE USD, per anno (bps; 1bps=0.01%)") + print("=" * 100) + for a in assets: + rows, note = cross_venue(a) + print(f"\n {a} ({note})") + if rows is None: + continue + print(f" {'anno':>5s}{'barre':>7s}{'med':>7s}{'p95':>7s}{'max':>8s}{'>50bp%':>8s}{'>1%':>7s}{'>3%':>7s}") + for y, n, med, p95, mx, g50, g100, g300 in rows: + print(f" {y:>5d}{n:>7d}{med:>7.1f}{p95:>7.1f}{mx:>8.1f}{g50:>8.1f}{g100:>7.1f}{g300:>7.1f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/clean_feed.py b/scripts/analysis/clean_feed.py deleted file mode 100644 index e21742b..0000000 --- a/scripts/analysis/clean_feed.py +++ /dev/null @@ -1,140 +0,0 @@ -"""CLEAN FEED — ripara gli spike-print del feed Deribit/Cerbero coi dati reali di Binance. - -Motivo (2026-06-18): la ricerca Price Ladder ha rivelato che data/raw/btc_1h.parquet (e gli -altri TF/asset) contengono barre con WICK FASULLI (es. BTC 2024-02-13: low 38.580 con -close ~49.968, BTC reale ~50k) — lo stesso spike-print testnet documentato in CLAUDE.md -(TP_PHANTOM / feed congelato). Sono pochi (decine per file) ma avvelenano i backtest -(stop/entry su prezzi mai avvenuti) e gonfiano le code (la "FULL DD BTC ~54%" del ladder era -in gran parte questo). - -Metodo (conservativo, fonte di verita' = Binance spot via ccxt, gia' cablato nel progetto): - 1. DETECT: barra sospetta = high/low che sfora >15% il cluster di close locale [i-1,i,i+1] - (close sano + wick fasullo). Soglia larga: tanto e' Binance ad arbitrare. - 2. ARBITRA: per ogni sospetta, scarica la barra Binance reale (BTC/USDT, ETH/USDT) allo - stesso tf/timestamp. Sostituisce O/H/L/C SOLO se Binance dissente materialmente (>2% su - high o low) -> un wick VERO confermato da Binance resta intatto. Volume/timestamp invariati. - 3. BACKUP (data/_feed_backup/) + scrittura atomica + VALIDAZIONE (re-scan = 0 sospette, - n righe invariato). Log dettagliato di ogni barra riparata (old OHLC -> new). - - uv run python scripts/analysis/clean_feed.py [ASSET_TF ...] # default: tutti BTC/ETH x TF - uv run python scripts/analysis/clean_feed.py BTC_1h # un solo file -""" -from __future__ import annotations - -import shutil -import sys -import time -from pathlib import Path - -import numpy as np -import pandas as pd - -PROJECT_ROOT = Path(__file__).resolve().parents[2] -sys.path.insert(0, str(PROJECT_ROOT)) - -from src.data.downloader import _parquet_path, DATA_DIR - -BACKUP = PROJECT_ROOT / "data" / "_feed_backup" -SYMBOL = {"BTC": "BTC/USDT", "ETH": "ETH/USDT"} -WICK_THR = 0.15 # detect: wick oltre 15% il cluster di close locale -REPLACE_THR = 0.02 # arbitra: sostituisci solo se Binance dissente >2% su high/low -_EX = None - - -def _binance(): - global _EX - if _EX is None: - import ccxt - _EX = ccxt.binance({"enableRateLimit": True}) - return _EX - - -def suspect_mask(df: pd.DataFrame) -> np.ndarray: - c = df["close"].to_numpy(float); h = df["high"].to_numpy(float); l = df["low"].to_numpy(float) - cp = np.roll(c, 1); cp[0] = c[0]; cn = np.roll(c, -1); cn[-1] = c[-1] - locmax = np.maximum.reduce([c, cp, cn]); locmin = np.minimum.reduce([c, cp, cn]) - return (h > locmax * (1 + WICK_THR)) | (l < locmin * (1 - WICK_THR)) - - -def _binance_bar(symbol: str, tf: str, ts_ms: int): - """OHLC reale Binance alla barra ts_ms (None se assente).""" - try: - rows = _binance().fetch_ohlcv(symbol, tf, since=ts_ms - 1, limit=3) - except Exception as e: - print(f" ! binance err: {type(e).__name__}: {str(e)[:80]}") - return None - for r in rows: - if int(r[0]) == ts_ms: - return float(r[1]), float(r[2]), float(r[3]), float(r[4]) - return None - - -def clean_file(asset: str, tf: str) -> dict: - path = _parquet_path(asset, tf) - if not path.exists(): - return {"file": f"{asset}_{tf}", "skip": "no-file"} - df = pd.read_parquet(path) - mask = suspect_mask(df) - idx = np.where(mask)[0] - n0 = len(df) - if len(idx) == 0: - return {"file": f"{asset}_{tf}", "suspect": 0, "repaired": 0, "kept_real": 0, - "missing_binance": 0, "rows_before": n0, "rows_after": n0, - "still_suspect": 0, "log": []} - repaired, kept, missing = 0, 0, 0 - log = [] - for i in idx: - ts = int(df.iloc[i]["timestamp"]) - b = _binance_bar(SYMBOL[asset], tf, ts) - oh, ol = float(df.iloc[i]["high"]), float(df.iloc[i]["low"]) - if b is None: - missing += 1 - continue - bo, bh, bl, bc = b - if abs(oh - bh) / bh > REPLACE_THR or abs(ol - bl) / max(bl, 1e-9) > REPLACE_THR: - df.iat[i, df.columns.get_loc("open")] = bo - df.iat[i, df.columns.get_loc("high")] = bh - df.iat[i, df.columns.get_loc("low")] = bl - df.iat[i, df.columns.get_loc("close")] = bc - repaired += 1 - ts_s = pd.to_datetime(ts, unit="ms", utc=True).strftime("%Y-%m-%d %H:%M") - log.append(f" {ts_s} H {oh:,.0f}->{bh:,.0f} L {ol:,.0f}->{bl:,.0f}") - else: - kept += 1 # Binance conferma il wick: barra reale, intatta - if repaired: - BACKUP.mkdir(parents=True, exist_ok=True) - shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.bak") - tmp = path.with_suffix(".parquet.tmp") - df.to_parquet(tmp, index=False) - tmp.replace(path) - # validazione - df2 = pd.read_parquet(path) - still = int(suspect_mask(df2).sum()) - return {"file": f"{asset}_{tf}", "suspect": len(idx), "repaired": repaired, - "kept_real": kept, "missing_binance": missing, "rows_before": n0, - "rows_after": len(df2), "still_suspect": still, "log": log} - - -def main(): - targets = sys.argv[1:] or [f"{a}_{tf}" for a in ("BTC", "ETH") - for tf in ("5m", "15m", "30m", "1h")] - print(f"CLEAN FEED — backup in {BACKUP}\n") - grand = 0 - for t in targets: - asset, tf = t.split("_", 1) - r = clean_file(asset, tf) - if r.get("skip"): - print(f" {t:<9} SKIP ({r['skip']})"); continue - grand += r.get("repaired", 0) - print(f" {r['file']:<9} sospette={r['suspect']:>3} riparate={r['repaired']:>3} " - f"reali-tenute={r.get('kept_real',0):>3} no-binance={r.get('missing_binance',0):>2} " - f"| righe {r['rows_before']}=={r['rows_after']} residue-sospette={r['still_suspect']}") - for line in r.get("log", [])[:8]: - print(line) - if len(r.get("log", [])) > 8: - print(f" ... (+{len(r['log'])-8} altre)") - print(f"\n TOTALE barre riparate: {grand}") - - -if __name__ == "__main__": - main() diff --git a/scripts/analysis/multi_source_check.py b/scripts/analysis/multi_source_check.py new file mode 100644 index 0000000..4b6841c --- /dev/null +++ b/scripts/analysis/multi_source_check.py @@ -0,0 +1,128 @@ +"""CROSS-CHECK MULTI-FONTE — "chi ci dice che Binance e' corretto?" + +Risponde col numero, non con la fede: scarica BTC/ETH 1h da 4 venue INDIPENDENTI +(Binance USDT, Coinbase USD, Kraken USD, Deribit perp mainnet REALE via ccxt), +li allinea per timestamp e misura di quanto divergono dal CONSENSO (mediana cross-venue). + +Due finestre: + A) regime CALMO recente -> baseline di concordanza + B) STRESS depeg USDT (mag 2022) -> dove l'assunzione "Binance=verita'" si crepa + (BTC/USDT != BTC/USD quando USDT perde il peg; noi eseguiamo USDC) + +NON modifica nulla. Solo lettura + report. Deribit via ccxt = API pubblica MAINNET (reale). + + uv run python scripts/analysis/multi_source_check.py +""" +from __future__ import annotations +import sys +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np +import pandas as pd +import ccxt + +TF = "1h" +TF_MS = 60 * 60 * 1000 + +# (label, ccxt_id, {asset: symbol}, per_request_limit) +SOURCES = [ + ("binance(USDT)", "binance", {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}, 1000), + ("okx(USDT)", "okx", {"BTC": "BTC/USDT", "ETH": "ETH/USDT"}, 100), + ("coinbase(USD)", "coinbase", {"BTC": "BTC/USD", "ETH": "ETH/USD"}, 300), + ("kraken(USD)", "kraken", {"BTC": "BTC/USD", "ETH": "ETH/USD"}, 720), + ("deribit(perp)", "deribit", {"BTC": "BTC/USD:BTC", "ETH": "ETH/USD:ETH"}, 1000), +] + +WINDOWS = [ + ("CALMO recente", "2026-04-15", "2026-05-27"), + ("STRESS depeg USDT","2022-05-08", "2022-05-16"), +] + + +def _ex(eid): + return getattr(ccxt, eid)({"enableRateLimit": True}) + + +def fetch(eid, symbol, start_ms, end_ms, limit): + """OHLCV 1h paginato -> dict ts_ms -> close. Tollerante agli errori per-venue.""" + try: + ex = _ex(eid) + ex.load_markets() + except Exception as e: + return None, f"load_markets {type(e).__name__}: {str(e)[:60]}" + if symbol not in ex.markets: + # prova l'id grezzo (es. BTC-PERPETUAL) come fallback + alt = symbol.split("/")[0] + "-PERPETUAL" + symbol = alt if alt in ex.markets else symbol + out, since, last_err = {}, start_ms, None + while since <= end_ms: + try: + rows = ex.fetch_ohlcv(symbol, TF, since=since, limit=limit) + except Exception as e: + last_err = f"{type(e).__name__}: {str(e)[:60]}" + break + if not rows: + break + for r in rows: + t = int(r[0]) + if start_ms <= t <= end_ms and r[4]: + out[t] = float(r[4]) + nxt = int(rows[-1][0]) + TF_MS + if nxt <= since: + break + since = nxt + if len(rows) < limit and since > end_ms: + break + if not out: + return None, last_err or "no-data (storia non servita dalla venue)" + return out, None + + +def analyze(asset, wname, start, end): + start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) + series, notes = {}, {} + for label, eid, syms, lim in SOURCES: + d, err = fetch(eid, syms[asset], start_ms, end_ms, lim) + if d: + series[label] = pd.Series(d, name=label) + else: + notes[label] = err + if len(series) < 2: + print(f" {asset} [{wname}] insufficienti venue ({list(series)}) — {notes}") + return + df = pd.concat(series.values(), axis=1, join="inner").sort_index() + if len(df) == 0: + print(f" {asset} [{wname}] nessun timestamp comune fra {list(series)}") + return + cons = df.median(axis=1) # consenso = mediana cross-venue + print(f"\n {asset} [{wname}] barre comuni={len(df)} venue={len(df.columns)}") + if notes: + for k, v in notes.items(): + print(f" (assente: {k} — {v})") + print(f" {'venue':<15s}{'med.bps':>9s}{'p95.bps':>9s}{'max.bps':>9s}" + f"{'>10bps':>8s}{'>50bps':>8s}") + for col in df.columns: + dev = (df[col] - cons).abs() / cons * 1e4 # bps di scostamento dal consenso + print(f" {col:<15s}{dev.median():>9.1f}{dev.quantile(.95):>9.1f}" + f"{dev.max():>9.1f}{(dev>10).mean()*100:>7.1f}%{(dev>50).mean()*100:>7.1f}%") + # spread max-min fra venue, barra per barra (quanto e' "largo" il disaccordo) + spread = (df.max(axis=1) - df.min(axis=1)) / cons * 1e4 + print(f" -> spread cross-venue (max-min): med {spread.median():.1f} bps, " + f"p95 {spread.quantile(.95):.1f} bps, max {spread.max():.1f} bps") + + +def main(): + print("=" * 78) + print(" CROSS-CHECK MULTI-FONTE — scostamento dal CONSENSO (mediana cross-venue), in bps") + print(" 1 bps = 0.01%. 'Binance corretto' == vicino al consenso di venue indipendenti?") + print("=" * 78) + for wname, start, end in WINDOWS: + print(f"\n--- FINESTRA: {wname} ({start} -> {end}) ---") + for asset in ("BTC", "ETH"): + analyze(asset, wname, start, end) + + +if __name__ == "__main__": + main() diff --git a/scripts/analysis/rebuild_history.py b/scripts/analysis/rebuild_history.py new file mode 100644 index 0000000..9dc60a8 --- /dev/null +++ b/scripts/analysis/rebuild_history.py @@ -0,0 +1,213 @@ +"""REBUILD STORICO — fonte di verita' = DERIBIT MAINNET (il venue dove ESEGUIAMO). + +Decisione (analisi 2026-06-19, vedi docs/diary/2026-06-19-deribit-history.md): +la contaminazione di data/raw nasce dal downloader che pesca la fase "Deribit" da +Cerbero col token TESTNET (feed farlocco) + la fase storica da Binance/USDT (wedge +USDT ~10bps, fino a 3% sotto depeg). Il cross-check multi-venue ha mostrato che il +venue PIU' vicino al consenso e' Deribit mainnet (0-1 bps), che e' anche dove si +esegue. Quindi: ricostruire lo storico da Deribit MAINNET (ccxt pubblico, NO token). + +ARCHITETTURA (la "via migliore"): + - SORGENTE: ccxt.deribit pubblico = api.deribit.com MAINNET (reale, tokenless; + il token serve solo per trading/account, NON per gli OHLCV). + - STRUMENTO per il PREZZO: + BTC/ETH -> INVERSE perp (BTC/USD:BTC, ETH/USD:ETH): storia lunga (2018-08/2019-03) + e ~3 bps dal lineare USDC che eseguiamo (misurato) -> proxy fedele. + alt -> LINEARE USDC (X/USDC:USDC): unica opzione su Deribit, dal 2022. + - BASE UNICA + RESAMPLE: si scarica UN solo timeframe base (default 5m) e si + derivano tutti gli altri per aggregazione -> coerenza interna GARANTITA + (15m == agg dei 5m per costruzione) e si valida UNA serie, non 20. + - COPERTURA ONESTA a due livelli: major 2018/2019->oggi; alt SOLO dal 2022 + (prima non erano tradabili su Deribit -> backtestarli su Binance pre-2022 = + validare un edge su un mercato che non potevamo eseguire). + +NB: le daily NATIVE Deribit chiudono alle 08:00 UTC (settlement); qui il 1d e' DERIVATO +dal base per resample -> confini 00:00 UTC standard e coerenti con gli altri TF. + + uv run python scripts/analysis/rebuild_history.py --smoke # prova su slice, NON tocca data/raw + uv run python scripts/analysis/rebuild_history.py --smoke --asset ETH + uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # FULL rebuild (scrive data/raw, con backup) + uv run python scripts/analysis/rebuild_history.py # FULL tutti gli asset +""" +from __future__ import annotations +import sys +import time +import shutil +from pathlib import Path +PROJECT_ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(PROJECT_ROOT)) +import numpy as np +import pandas as pd +import ccxt + +from src.data.downloader import _parquet_path, DATA_DIR + +BACKUP = PROJECT_ROOT / "data" / "_feed_backup" + +# asset -> (simbolo ccxt Deribit, start storico reale sul venue) +DERIBIT_INSTR = { + "BTC": ("BTC/USD:BTC", "2018-08-14"), # inverse, storia lunga ~ lineare entro 3 bps + "ETH": ("ETH/USD:ETH", "2019-03-14"), + "SOL": ("SOL/USDC:USDC", "2022-03-15"), # alt: solo lineare USDC, solo dal 2022 + "ADA": ("ADA/USDC:USDC", "2022-03-21"), + "XRP": ("XRP/USDC:USDC", "2022-03-16"), + "LTC": ("LTC/USDC:USDC", "2022-04-28"), + "DOGE": ("DOGE/USDC:USDC","2022-04-28"), + "BNB": ("BNB/USDC:USDC", "2024-10-01"), # ATTENZIONE: storia cortissima (~1.7y) +} + +BASE_TF = "5m" # base unica (5m: supporta tutti i TF standard) +TARGET_TFS = ["5m", "15m", "1h"] # = set del download_all; 4h/1d il runner li resampla dal 1h +SCHEMA = ["timestamp", "open", "high", "low", "close", "volume"] +COINBASE = ccxt.coinbase({"enableRateLimit": True}) + + +def _deribit(): + return ccxt.deribit({"enableRateLimit": True}) + + +def fetch_base(symbol: str, tf: str, start: str, end: str | None = None) -> pd.DataFrame: + """OHLCV Deribit mainnet paginato in AVANTI (since esplicito che avanza — evita + l'ancoraggio-a-now di ccxt deribit con since storico). Ritorna schema canonico.""" + ex = _deribit() + tf_ms = ex.parse_timeframe(tf) * 1000 + start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) + end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) if end else int(time.time() * 1000) + rows: dict[int, list] = {} + since, guard = start_ms, 0 + while since <= end_ms and guard < 5000: + guard += 1 + for attempt in range(3): + try: + r = ex.fetch_ohlcv(symbol, tf, since=since, limit=1000) + break + except Exception as e: + if attempt == 2: + print(f" ! {type(e).__name__}: {str(e)[:60]}") + r = [] + time.sleep(2 ** attempt) + r = [x for x in r if int(x[0]) >= since] + if not r: + break + for x in r: + t = int(x[0]) + if start_ms <= t <= end_ms: + rows[t] = [t, float(x[1]), float(x[2]), float(x[3]), float(x[4]), float(x[5] or 0)] + nxt = int(r[-1][0]) + tf_ms + if nxt <= since: + break + since = nxt + if not rows: + return pd.DataFrame(columns=SCHEMA) + df = pd.DataFrame(rows.values(), columns=SCHEMA) + return df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True) + + +def resample(base: pd.DataFrame, tf: str) -> pd.DataFrame: + """Aggrega il base al timeframe tf (confini 00:00 UTC). Coerente per costruzione.""" + if tf == BASE_TF: + return base.copy() + g = base.copy() + g.index = pd.to_datetime(g["timestamp"], unit="ms", utc=True) + rule = {"5m": "5min", "15m": "15min", "30m": "30min", "1h": "1h", "2h": "2h", + "3h": "3h", "4h": "4h", "6h": "6h", "12h": "12h", "1d": "1D"}[tf] + out = g.resample(rule, label="left", closed="left").agg( + {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}) + out = out.dropna(subset=["open"]) + # ms epoch resolution-independent (pandas 2.x indicizza datetime64[ms]: view/asi8 + # cambiano risoluzione e darebbero ts corrotti -> uso una differenza di Timedelta) + epoch = pd.Timestamp("1970-01-01", tz="UTC") + ts_ms = ((out.index - epoch) // pd.Timedelta(milliseconds=1)).astype("int64") + out.insert(0, "timestamp", ts_ms) + return out.reset_index(drop=True)[SCHEMA] + + +def cross_audit(df1h: pd.DataFrame, asset: str, start: str, end: str) -> str: + """Certifica il 1h ricostruito contro Coinbase USD (venue indipendente, NON USDT).""" + sym = {"BTC": "BTC/USD", "ETH": "ETH/USD", "SOL": "SOL/USD", "ADA": "ADA/USD", + "XRP": "XRP/USD", "LTC": "LTC/USD", "DOGE": "DOGE/USD", "BNB": None}.get(asset) + if sym is None: + return "no-coinbase-ref" + try: + if not COINBASE.markets: + COINBASE.load_markets() + if sym not in COINBASE.markets: + return f"coinbase: simbolo {sym} assente" + s_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000) + e_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) + out, since = {}, s_ms + while since <= e_ms: + r = COINBASE.fetch_ohlcv(sym, "1h", since=since, limit=300) + r = [x for x in r if int(x[0]) >= since] + if not r: + break + for x in r: + if s_ms <= int(x[0]) <= e_ms and x[4]: + out[int(x[0])] = float(x[4]) + nxt = int(r[-1][0]) + 3600_000 + if nxt <= since: + break + since = nxt + ref = pd.Series(out) + except Exception as e: + return f"coinbase-err {type(e).__name__}" + a = df1h.set_index("timestamp")["close"] + m = pd.concat([a.rename("a"), ref.rename("b")], axis=1, join="inner") + if len(m) == 0: + return "0 overlap" + d = (m["a"] - m["b"]).abs() / m["b"] * 1e4 + return f"vs Coinbase USD: barre={len(m)} med {d.median():.1f} bps p95 {d.quantile(.95):.1f} max {d.max():.1f}" + + +def build(asset: str, write: bool, smoke: bool) -> None: + symbol, full_start = DERIBIT_INSTR[asset] + if smoke: + start, end = "2026-04-15", "2026-05-27" + else: + start, end = full_start, None + print(f"\n [{asset}] {symbol} base {BASE_TF} da {start}{' -> '+end if end else ' -> oggi'}") + base = fetch_base(symbol, BASE_TF, start, end) + if base.empty: + print(" VUOTO — skip"); return + f0 = pd.to_datetime(base['timestamp'].iloc[0], unit='ms', utc=True).date() + f1 = pd.to_datetime(base['timestamp'].iloc[-1], unit='ms', utc=True).date() + print(f" base: {len(base)} barre {BASE_TF} [{f0} -> {f1}]") + built = {tf: resample(base, tf) for tf in TARGET_TFS} + for tf, d in built.items(): + print(f" {tf:<4} -> {len(d):>7} barre") + # certifica il 1h + aud_start = "2026-04-15" if smoke else "2026-03-01" + aud_end = "2026-05-27" if smoke else f1.isoformat() + print(f" AUDIT 1h {cross_audit(built['1h'], asset, aud_start, aud_end)}") + if not write: + print(" (smoke: NON scrivo data/raw)") + return + BACKUP.mkdir(parents=True, exist_ok=True) + for tf, d in built.items(): + path = _parquet_path(asset, tf) + if path.exists(): + shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak") + tmp = path.with_suffix(".parquet.tmp") + d.to_parquet(tmp, index=False) + tmp.replace(path) + print(f" scritto {path.name}") + + +def main(): + argv = sys.argv[1:] + smoke = "--smoke" in argv + assets = [] + if "--asset" in argv: + i = argv.index("--asset") + assets = [a.upper() for a in argv[i + 1:] if not a.startswith("--")] + assets = assets or list(DERIBIT_INSTR) + print("=" * 78) + print(f" REBUILD STORICO da DERIBIT MAINNET — {'SMOKE (no write)' if smoke else 'FULL (scrive data/raw, backup)'}") + print(f" asset: {assets} base={BASE_TF} target={TARGET_TFS}") + print("=" * 78) + for a in assets: + build(a, write=not smoke, smoke=smoke) + + +if __name__ == "__main__": + main()