#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ СВЕРКА /opt/arena/runs С БАЗОЙ — можно ли удалять. Только чтение. Вопрос: каталоги прогонов — избыточные выгрузки или единственная копия торговой истории? Удалять 5 ГБ по признаку «похоже на мусор» нельзя. Для каждого каталога ищем таблицу trades_<бот> в базе и сравниваем. Вердикт по каждому: ДУБЛЬ — история есть в базе, каталог избыточен, можно удалять АРХИВ — в базе таблицы нет или она пуста, каталог единственная копия НЕПОНЯТНО — имя не сопоставилось, руками """ import json, os, re, subprocess, sys RUNS = "/opt/arena/runs" # список таблиц вшит: публикатор возит только .py, а частичный # деплой на Vercel затирает набор файлов целиком (правило №10) TABLES = {"trades_uv1_test_btc_sfp": 182, "trades_v100_sfp_long_only": 113, "trades_v101_bos_short_only": 58, "trades_v102_trend_alignment": 0, "trades_v103_mid_cap_alts": 3250, "trades_v104_meme_only": 1986, "trades_v105_us_session_btc": 59, "trades_v106_asia_session_alts": 1823, "trades_v107_overheat_short": 13, "trades_v108_reset_long": 312, "trades_v109_strict_signals": 409, "trades_v10_sfp_only": 125, "trades_v11_bos_only": 276, "trades_v12_reversal_only": 56, "trades_v13_multi_confirm": 352, "trades_v14_eth_1h_long": 13, "trades_v15_sol_only": 38, "trades_v16_btc_eth_equal": 192, "trades_v17_asia_session": 85, "trades_v18_us_session": 200, "trades_v19_reset_only": 295, "trades_v1_core": 0, "trades_v200_no_be": 295, "trades_v201_be_strong": 536, "trades_v202_be_default_fixed": 291, "trades_v20_adaptive_tp": 312, "trades_v210_btc_30m": 107, "trades_v211_btc_1h": 47, "trades_v212_btc_4h": 47, "trades_v21_vol_adaptive": 392, "trades_v22_anti_corr": 99, "trades_v230_sfp_btc_focus": 90, "trades_v231_cr_eth_focus": 90, "trades_v232_sfp_eth_focus": 147, "trades_v23_dd_adaptive": 262, "trades_v240_chop_sfp_btc": 84, "trades_v241_chop_sfp_eth": 134, "trades_v242_chop_sfp_majors": 361, "trades_v243_peak_ny_only": 330, "trades_v244_evolved_sfp_only_sub": 591, "trades_v245_evolved_bos_only_sub": 567, "trades_v246_evolved_sfp_only_sub": 218, "trades_v247_evolved_sfp_only_sub": 635, "trades_v248_evolved_strict_session": 285, "trades_v24_hybrid_hardened_bos": 117, "trades_v250_sfp_short_btc_down": 97, "trades_v251_sfp_short_eth_down": 115, "trades_v252_cr_short_majors_down": 365, "trades_v253_bos_short_btc_aggressive": 73, "trades_v254_18utc_peak_only": 94, "trades_v255_no_ml_filter": 616, "trades_v256_high_capital_focused": 194, "trades_v257_strict_confluence": 160, "trades_v258_zen_low_freq": 18, "trades_v259_warrior_scalp": 226, "trades_v25_hybrid_eth_reset": 87, "trades_v260_oracle": 367, "trades_v261_panda_btc_purist": 161, "trades_v262_phoenix_recovery": 361, "trades_v263_samurai_disciplined": 200, "trades_v264_chameleon_adapt": 535, "trades_v26_hybrid_pyramid_vol": 241, "trades_v270_hodler": 28, "trades_v271_sniper": 240, "trades_v272_catfish": 406, "trades_v273_kraken": 1050, "trades_v274_ninja": 140, "trades_v275_buddha": 71, "trades_v276_wolf": 249, "trades_v277_owl": 125, "trades_v278_dragon": 116, "trades_v279_unicorn": 35, "trades_v27_hybrid_us_bos": 90, "trades_v28_ensemble_vote": 100, "trades_v29_llm_scorer": 230, "trades_v2_multi": 0, "trades_v300_invert_bos_short_btc": 529, "trades_v300_v_current": 1004, "trades_v301_invert_bos_short_alts": 3348, "trades_v301_v_pre_w41": 453, "trades_v302_invert_cr_alts": 2297, "trades_v302_v_pre_w38": 578, "trades_v303_invert_cr_memes": 2878, "trades_v303_v_production": 562, "trades_v304_evolved_asia_only": 244, "trades_v304_invert_combo_btc": 685, "trades_v305_evolved_aggressive": 776, "trades_v305_invert_sfp_short_btc": 360, "trades_v306_evolved_sfp_only_sub": 468, "trades_v307_evolved_strict_session": 256, "trades_v30_meta_ranker": 236, "trades_v310_invert_bos_long": 443, "trades_v311_invert_sfp_long_15m": 38, "trades_v312_invert_cr_long_5m": 152, "trades_v313_btc_session_short": 6, "trades_v314_btc_block_anti_longs": 104, "trades_v31_volatility_on": 149, "trades_v32_quiet_ranges": 196, "trades_v33_volume_gate_bos": 323, "trades_v34_htf_bias_long": 107, "trades_v35_news_blackout": 410, "trades_v36_btc_alpha_only": 152, "trades_v37_time_exit_scalp": 625, "trades_v38_dd_dampener": 231, "trades_v39_alts_major": 1234, "trades_v3_hardened": 0, "trades_v400_ict_eth_1h": 0, "trades_v40_alts_midcap": 1803, "trades_v41_alts_narrative": 1354, "trades_v42_in_play": 1564, "trades_v43_pump_fade": 64, "trades_v44_dump_bounce": 202, "trades_v45_pump_ride": 485, "trades_v46_alt_cr_short": 5721, "trades_v47_btc_short_specialist": 68, "trades_v48_op_specialist": 211, "trades_v49_no_5m_clean": 369, "trades_v4_edge": 0, "trades_v50_bos_1m_pure": 74, "trades_v51_btc_short_strict": 150, "trades_v52_btc_no_5m_clean": 262, "trades_v53_btc_strong_only": 190, "trades_v54_funding_bias": 52, "trades_v55_cvd_divergence": 109, "trades_v56_cvd_funding_combo": 155, "trades_v57_btc_dashboard_strict": 159, "trades_v58_btc_dashboard_loose": 156, "trades_v59_regime_strict": 122, "trades_v5_experimental": 0, "trades_v600_btc_sfp_long_only": 0, "trades_v601_btc_bos_short_purist": 127, "trades_v602_btc_bos_long_only": 62, "trades_v603_btc_bos_strict": 0, "trades_v604_btc_all_signals_strict": 211, "trades_v605_bch_cr_short": 0, "trades_v606_trump_cr_short": 0, "trades_v607_ondo_tia_cr_short": 0, "trades_v608_ordi_inj_cr_short": 0, "trades_v609_btc_quality_only": 142, "trades_v60_vpvr_poc": 152, "trades_v610_btc_ml_high_only": 221, "trades_v611_bch_cr_short_5m": 0, "trades_v612_bch_cr_short_15m": 0, "trades_v613_bch_cr_short_strict": 0, "trades_v614_tao_specialist": 234, "trades_v615_ton_specialist": 466, "trades_v616_ltc_specialist": 215, "trades_v617_alt_winners_basket": 1613, "trades_v618_alt_winners_strict": 1238, "trades_v61_full_canonical": 141, "trades_v62_pump_ultra_aggressive": 318, "trades_v63_antifragile": 116, "trades_v64_mtf_confluence": 104, "trades_v6_ultra_conservative": 153, "trades_v70_skeptic": 12, "trades_v71_chaos_pure": 208, "trades_v72_session_ronin": 30, "trades_v73_btc_purist": 88, "trades_v74_meme_lord": 2658, "trades_v750_us_session_only": 162, "trades_v751_asia_session_only": 83, "trades_v752_skip_dead_hours": 206, "trades_v753_us_close_only": 24, "trades_v75_zen_master": 288, "trades_v76_phoenix": 137, "trades_v77_assassin": 305, "trades_v780_ml_off": 689, "trades_v781_ml_strict": 714, "trades_v790_no_edge_map": 330, "trades_v791_no_scorer": 425, "trades_v792_no_range_filter": 684, "trades_v793_full_bypass": 341, "trades_v795_no_dead_alts": 1294, "trades_v797_inverted_signals": 207, "trades_v7_aggressive": 103, "trades_v800_core_btc_sfp_long": 28, "trades_v801_core_btc_sfp_long_bypass": 58, "trades_v802_core_btc_sfp_long_us": 21, "trades_v803_core_btc_sfp_long_strict": 34, "trades_v804_core_btc_sfp_long_widestop": 2, "trades_v805_core_btc_sfp_long_regime": 0, "trades_v80_btc_eth_pair": 0, "trades_v810_survivors_down": 0, "trades_v811_survivors_up": 0, "trades_v812_winners_run": 0, "trades_v813_full_improved": 0, "trades_v814_trend_rider": 0, "trades_v815_no_repaint": 0, "trades_v81_sfp_short_only": 110, "trades_v820_winners_combo": 0, "trades_v82_no_candle_reversal": 2869, "trades_v82a_invert_candle_reversal": 362, "trades_v83_morning_btc": 71, "trades_v84_evening_alts": 1010, "trades_v85_regime_balance": 3, "trades_v86_regime_trend": 155, "trades_v87_high_vol": 402, "trades_v88_low_vol": 253, "trades_v89_bos_long_only": 128, "trades_v8_pyramid": 351, "trades_v900_grid_btc": 0, "trades_v900_grid_eth": 0, "trades_v900_grid_sol": 0, "trades_v900_grid_test": 0, "trades_v90_divergence_only": 0, "trades_v90_pure_noise": 402, "trades_v91_chaos_amplified": 386, "trades_v92_random_walk_short": 455, "trades_v93_random_walk_long": 248, "trades_v94_max_chaos_alts": 9074, "trades_v95_evolved_asia_only": 257, "trades_v9_anti_martingale": 150, "trades_v_score": 0} def sh(c, t=30): try: r = subprocess.run(c, shell=True, capture_output=True, text=True, timeout=t) return (r.stdout or "").strip() except Exception: return "" tabs = TABLES print(f"таблиц trades_* в базе: {len(tabs)}") def norm(s): return re.sub(r"[^a-z0-9]", "", s.lower()) index = {norm(k[len("trades_"):]): (k, v) for k, v in tabs.items()} dirs = sorted(d for d in os.listdir(RUNS) if os.path.isdir(os.path.join(RUNS, d))) print(f"каталогов в runs: {len(dirs)}\n") verdict = {"ДУБЛЬ": [], "АРХИВ": [], "НЕПОНЯТНО": []} size_of = {} for d in dirs: kb = sh(f"du -xsk '{os.path.join(RUNS,d)}' 2>/dev/null | cut -f1") size_of[d] = int(kb) if kb.isdigit() else 0 n = norm(d) hit = index.get(n) if hit is None: # пробуем без ведущего V и хвостов for k, v in index.items(): if k.startswith(n) or n.startswith(k): hit = v break if hit is None: verdict["НЕПОНЯТНО"].append(d) elif hit[1] > 0: verdict["ДУБЛЬ"].append(d) else: verdict["АРХИВ"].append(d) for k in ("ДУБЛЬ", "АРХИВ", "НЕПОНЯТНО"): mb = sum(size_of[d] for d in verdict[k]) / 1024 print(f"{k:11} каталогов {len(verdict[k]):>4} {mb:>8.0f} МБ") print("\n--- крупнейшие ДУБЛИ (можно удалять) ---") for d in sorted(verdict["ДУБЛЬ"], key=lambda x: -size_of[x])[:8]: print(f" {d:40} {size_of[d]/1024:>7.0f} МБ") print("\n--- крупнейшие АРХИВЫ (в базе пусто, НЕ удалять) ---") for d in sorted(verdict["АРХИВ"], key=lambda x: -size_of[x])[:8]: print(f" {d:40} {size_of[d]/1024:>7.0f} МБ") print("\n--- НЕПОНЯТНО (разобрать руками) ---") for d in sorted(verdict["НЕПОНЯТНО"], key=lambda x: -size_of[x])[:8]: print(f" {d:40} {size_of[d]/1024:>7.0f} МБ") # что вообще внутри — на примере крупнейшего if verdict["ДУБЛЬ"]: big = max(verdict["ДУБЛЬ"], key=lambda x: size_of[x]) p = os.path.join(RUNS, big) print(f"\n--- что внутри {big} ---") print(sh(f"ls '{p}' | head -5 | sed 's/^/ /'")) f = sh(f"find '{p}' -name '*.jsonl' | head -1") if f: line = sh(f"head -1 '{f}'")[:300] try: print(" поля записи:", list(json.loads(line).keys())[:12]) except Exception: print(" первая строка:", line[:150]) json.dump({k: v for k, v in verdict.items()}, open("/tmp/runs_verdict.json", "w")) print("\nвердикт сохранён: /tmp/runs_verdict.json")