
    G=j/                     V   d Z ddlZddlZddlZddlmZ ddlmZ ddlZ	ddl
Zej                  j                  d e ee      j!                         j"                  j"                               ddlmZmZ  ej*                  d       ej*                  d       ej*                  d	       ej*                  d
       ej*                  d      dZdddddZdZdZdZdZdZdedefdZdedee   fdZdedee e   e e   e!f   fdZ"d Z#e$dk(  r ejJ                   e#              yy)u	  
Chart-data integrity validator for TradingView exports
======================================================

Catches *broken or deficient* TV chart-data CSV exports BEFORE they distort
backtests, parity checks, or winner-metric refreshes.

Why this exists
---------------
On 2026-06-23 the same MLP winners produced **34× different** backtest P&L
(4H: 176% vs 5,987%) across TV exports taken minutes apart — because the
exported price history differed (a truncated / partially-loaded chart). Score
parity did NOT catch it (the recompute matched the stale score column). The
only reliable signal was the data itself: a deficient export has too few bars /
starts too late / has gaps. This tool checks the data directly.

Five preconditions (per CLAUDE.md Primary Directive):
  1. Objective metric : number of files failing integrity checks (target 0).
  2. Measurement      : the per-file structural checks below.
  3. Action space     : exit non-zero (blocks downstream use) + optional ntfy.
  4. Stopping         : one pass per invocation (run after each export, or wire
                        to a launchd WatchPaths agent — see --help epilog).
  5. Guardrail        : callers (compare_tv_trades, refresh_winner_metrics) can
                        import `validate_file` as a preflight and refuse to run
                        on ERROR-level data.

Checks (per data CSV)
---------------------
  ERROR (hard fail — data is unusable / will produce wrong backtests):
    - not a chart-data file (looks like a trade-list export)
    - missing OHLC/time columns
    - NaN / <=0 prices; high<low; high<max(open,close); low>min(open,close)
    - non-monotonic or duplicate timestamps
    - TRUNCATED history: first bar later than SCORE_START (IS window not covered)
      or row count << expected for the date span & timeframe
  WARN (review — may be benign):
    - first bar later than TRAIN_START (indicator warm-up may be short)
    - last bar far behind "now" (stale export)
    - large time gaps between bars
    - extreme single-bar moves (possible price glitch; legit crashes also land here)

Usage
-----
    # validate every chart CSV in data/mlp/ (default)
    python3 tools/validate_chart_data.py

    # validate specific files
    python3 tools/validate_chart_data.py "data/mlp/COINBASE_BTCUSD, 240.csv"

    # treat warnings as failures; push an ntfy alert on any failure
    python3 tools/validate_chart_data.py --strict --notify

Exit code: 0 = all clean (warnings allowed unless --strict); 1 = ≥1 file failed.
    N)Path)Optional)SCORE_STARTTRAIN_START   )hours            )days)4H6H8H12H1Dr   r   r   r   )240360480720)COINBASE_BTCUSDCOINBASE_ETHUSDg      ?g      ?g?   pathreturnc                     t        |       j                  }|j                  d      d   j                         j                  d      d   j                         S )zHAsset prefix from a TV export filename, e.g. 'COINBASE_BTCUSD, 240.csv'.,r   -)r   stemsplitstrip)r   r   s     D/Users/jameslopez/projects/TradingBot25/tools/validate_chart_data.pydetect_assetr#   X   sD    :??D::c?1##%++C0399;;    c                 H   t        |       j                  j                         }t        D ]B  }|j	                  d|       s)|j	                  d|       s|j	                  d|       s@|c S  t
        j                         D ]  \  }}|j	                  d|       s|c S  y )Nr   _, )r   r   upperTF_DURATIONSendswith_MINUTES_TO_TFitems)r   nametfminss       r"   detect_timeframer0   ^   s    :??  "D==1RD"dmmatH&=QSTVSWyAYI  #((*b==2dV%I + r$   c                    g }g }i }t        |       }|j                         s	d|  gg |fS 	 t        j                  |       j
                  j                  j                         j                  j                         _        dj
                  v sddhj                  j
                        rdgg |fS h d}|t        j
                        z
  }|r"|j                  d	t        |              |||fS t              }||d
<   |dk(  rdgg |fS t        j                  d   dd      }	|	j                         j!                         r9|j                  t#        |	j                         j%                                d       |	j&                  j)                  d      }	|	j+                         j-                         j!                         r9|j                  t#        |	j-                         j%                                d       |	j+                         j.                  s|j                  d       |	j1                         |	j3                         }}
|
|c|d<   |d<   dD ]  }t        j4                  |   d      }|j                         j!                         r<|j                  | dt#        |j                         j%                                d       |dk  j!                         s|j                  | dt#        |dk  j%                                d        fddD        \  }}}}||k  j!                         r.|j                  t#        ||k  j%                                d       ||k  j!                         s||k  j!                         r4|j                  t#        ||k  ||k  z  j%                                d       ||kD  j!                         s||kD  j!                         r4|j                  t#        ||kD  ||kD  z  j%                                d       t        j6                  t8              }t        j6                  t:              }t=        |       }t        j>                  |
      r|
|kD  r@|t@        v r8|j                  d|
jC                          d |jC                          d!| d"       nv|
|kD  r8|j                  d#|
jC                          d |jC                          d$| d%       n9|
|kD  r4|j                  d#|
jC                          d&|jC                          d'       t        j6                  jE                  d(      jG                  d)      }t        j>                  |      rI||z
  jH                  tJ        kD  r3|j                  d*|jC                          d+||z
  jH                   d,       tM        |       }||d-<   |rt        j>                  |
      rt        j>                  |      rtN        |   }|	j+                         jQ                         j+                         }|||tR        z  kD     }t        |      r8|j                  t        |       d.tR         d/| d0|j3                          d1       t#        ||
z
  |z        d2z   }||d3<   ||tT        z  k  rD|j                  d4| d5| d6|
jC                          d7|jC                          d8| d9||z  d:d;       |jW                         jY                         }||tZ        kD     }t        |      r|j]                         }|j                  t        |       d<tZ        d:d=|j3                         d:d8t        j>                  |	j^                  |         r|	j^                  |   jC                         nd> d?       |||fS # t        $ r}d| gg |fcY d}~S d}~ww xY w)@u   Validate one chart-data CSV.

    Returns (errors, warnings, stats). Empty errors → usable data.
    Importable as a preflight: `errs, warns, _ = validate_file(p)`.
    zfile not found: zcould not read CSV: Nztrade numbertypesignalz.looks like a trade-list export, not chart data>   lowhighopentimeclosezmissing required columns: rowsr   zempty file (0 rows)r7   coerceT)errorsutcz unparseable timestampsz duplicate timestampsz&timestamps are not strictly increasingfirstlast)r6   r5   r4   r8   r;   z: z NaN/non-numeric valuesz values <= 0c              3   R   K   | ]  }t        j                  |   d          yw)r:   r?   N)pd
to_numeric).0xdfs     r"   	<genexpr>z validate_file.<locals>.<genexpr>   s&     bAaA"--1h77Aas   $'z bars with high < lowz bars with high < open/closez bars with low > open/closezTRUNCATED history: first bar z is after SCORE_START u    — zf has older history; the in-sample window is not fully covered (likely a partially-loaded chart export)z
first bar u5    — in-sample window not fully covered (expected if z; is younger than SCORE_START; a truncated export otherwise)z is after TRAIN_START u#    — indicator warm-up may be shortUTC)tzinfoz	last bar z is zd behind now (stale export?)r.   z time gaps >    ×z
 (largest )r   expected_rowszTRUNCATED/sparse: z rows but ~z expected for    →z at z (z.0%z
 coverage)z bars move >z in one bar (worst ?u   ) — verify not a price glitch)0r   existsrA   read_csv	Exceptioncolumnsstrlowerr!   issubsetsetappendsortedlento_datetimeisnaanyintsumdttz_localizedropna
duplicatedis_monotonic_increasingminmaxrB   	Timestampr   r   r#   notnaASSETS_FULL_HISTORYdatenowreplacer   STALE_EXPORT_DAYSr0   r)   diff
GAP_FACTORMIN_ROW_COVERAGE
pct_changeabsEXTREME_MOVEidxmaxiloc)r   r;   warningsstatspexcrequiredmissingntr=   r>   colvohlcscore_starttrain_startassetri   r.   intervaldiffsgapsexpectedretextremeworstrE   s                                 @r"   validate_filer   i   s    FHET
A88:"4&)*B559[[ %%'++113BJ #'9'B'B2::'N@A2uLL7HRZZ(G26'?2CDEx&&BAE&MAv%&E11 	r&z(=Avvx||~QVVX\\^,--DEF	Axxz""$Q\\^//1233HIJ88:-->?%%'15574E$)4!E'NE&M 0MM"S'(3668<<>MMSEC$7#88OPQF<<>MMSECa$7#8EF 0 cAabJAq!Q	A{{}a!e[[]+,,ABC	A{{}Qq1uQ/446788TUV	A{{}Qq1uQ/446788STU ,,{+K,,{+KE	xx;5,?#?MM/

~=S##%&eE7 3WX
 [ OOUZZ\N*@AQAQAS@T UGGLg NBC
 [ OOUZZ\N*@AQAQAS@T U5 6 ,,

5
!
)
)
)
6C	xx~3:++.??)DIIK=cDj5F5F4GGcde 
$	BE$K	bhhuo"((4.#
!((*UX
223t9OOt9+]:,bJtxxzlRST u01A5!)ox***MM$QC{8*N::<.DIIK=RD1x<:LJX ,,.


C#$%G
7|

7|nLc(: ;ggi_D!&&QV-AX););)=^a(b c,-	
 8U""M  9&se,-r5889s   ^' '	_0^>8_>_c            
      X   t        j                  dt         j                  d      } | j                  ddd       | j                  dd	d
       | j                  dd	d       | j                  dd	d       | j	                         }|j
                  }|sht        t              j                         j                  j                  }t        d t        j                  t        |dz  dz  dz              D              }|st        d       ydx}}g }|D ]r  }t        |      \  }}	}
| xr |	 xs |j                   }|r|j                   r9|s|	sdn|rdnd}d}|
j#                  d      dd|
j#                  dd       dt%        j&                  |
d         j)                          dt%        j&                  |
d          j)                          d!}t        | d"t        |      j*                   |        |D ]  }t        d#|         |	D ]  }t        d$|         |r+|d%z  }|j-                  t        |      j*                         8|	s<|d%z  }|j                  sO|j-                  t        |      j*                         u t        d&t/        |       d'| d(| d)       t1        |      xs |j                  xr t1        |      }|r2|j2                  r&	 dd*lm}  |d+d,j7                  |       d-d.d/g0       |rd%S dS # t8        $ r}t        d1| d2       Y d }~#d }~ww xY w)3Nz:Validate TradingView chart-data CSV exports for integrity.uB  Wire as an on-change guard (externally-created files) via launchd WatchPaths:
  ~/Library/LaunchAgents/com.james.tradingbot-data-validator.plist with
  WatchPaths=[<repo>/data/mlp] running:
    python3 tools/validate_chart_data.py --notify   (alerts on ERRORs only)
  Logs → projects/logs/tradingbot-data-validator.log.
)descriptionformatter_classepilogfiles*z*chart CSVs (default: data/mlp chart files))nargshelpz--strict
store_trueztreat warnings as failures)actionr   z--quietzonly print files with problemsz--notifyzsend an ntfy alert on failurec              3   h   K   | ]*  }t        |      j                  j                  d       s| , yw)
MLPScores_N)r   r-   
startswith)rC   fs     r"   rF   zmain.<locals>.<genexpr>   s.      
F!7<<**<8 Fs   02datamlpz*.csvz&No chart-data files found to validate.r   u   ✅u   ❌u   ⚠️  r=   z  [r9   rM   z rows, rL   r>   ] z     ERROR: z     warn:  r   
z
 file(s): z with errors, z with warnings only.)notifyzChart-data validation FAILED: r'   zTradingBot data integrityr5   warning)titleprioritytagsz(ntfy failed: rJ   )argparseArgumentParserRawDescriptionHelpFormatteradd_argument
parse_argsr   r   __file__resolveparentrW   globrR   printr   strictquietgetrA   re   rh   r-   rV   rX   boolr   
tools.ntfyjoinrP   )apargsr   rootn_errn_warnfailed_filesr   r;   rt   ru   okiconspanewfailedr   rw   s                      r"   mainr      s   		 	 P <<H	

B OOG3-YOZOOJ|:VOWOOIl9YOZOOJ|:YOZ==?DJJEH~%%'..55 
yyTF]U%:W%D!EF
 
 67EFL"/"2%Z=\<_$**"8u6%y99W)6#./wr||E'N7S7X7X7Z6[[^_a_k_klqrxly_z__  `B  _C  CD  EDaQ~dV,-AL$% AL$% QJEQ-aKF{{##DGLL1) , 
Bs5zl*UG>&AU
VW%[:T[[9T&\F$++		+)0<1H0IJ1[	 1A  	+N3%q)**	+s   %L 	L)L$$L)__main__)&__doc__r   r   syspathlibr   typingr   numpynppandasrA   r   insertrR   r   r   r   configr   r   	Timedeltar)   r+   rg   rq   rm   rn   rk   r#   r0   tuplelistdictr   r   __name__exit r$   r"   <module>r      sM  5n   
     3tH~--/66==> ? + ",,Q
|r||!'<
",,Q
2(>
",,A

 d4F
 =  
  <s <s <3 8C= v# v#d3icD&@ A v#rBJ zCHHTV r$   