
    3M=j                        d Z ddlZddl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mZ ej                  j3                   e ee      j'                         j(                  j(                               dZefdeded	ed
efd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ed
e
e   fdZ ded
ej:                  fdZ!ded
e"e#   fdZ$ded
efd Z%d!ed
e#fd"Z&	 dHded#e#ded
ejN                  fd$Z(d%ejN                  d
e
e   fd&Z)d'ejT                  d%ejN                  d
efd(Z+d
e,fd)Z-d*ej\                  d+ej\                  d,ej\                  d-ej\                  d.ed
e/ej\                  ej\                  ej\                  f   fd/Z0	 dId%ejN                  d#e#d0ejb                  d1e
ejb                     d
ejN                  f
d2Z2d%ejN                  d
e"e#   fd3Z3	 dJd4e"e#   d5e"e#   ded6e4d
e/e"e/   e"e#   e"e#   f   f
d7Z5d%ejN                  d8ed
e
ejT                     fd9Z6	 dId4e"e#   d%ejN                  ded0ejb                  d1e
ejb                     d
e
e#   fd:Z7dKd%ejN                  d;e4d<e4d
efd=Z8d>e#d%ejN                  d?eded
ef
d@Z9	 dLdAe#d%ejN                  d?edBe,d
ef
dCZ:dAe#d>e#d%ejN                  d.edDed
efdEZ;dF Z<e=dGk(  r e<        yy)Mu  
Compare TradingView Trade List vs Python Backtest Signals
=========================================================

Robust TV/Python parity diagnostic tool. Loads a TradingView trade-list
export, runs the Python strategy with the same params, matches trades using
timeframe-aware timestamps (TV fills at open[T+1]; Python signals fire at
bar-close T), then classifies each divergence by root cause.

Usage
-----
    # Basic comparison — auto-detects TF from data filename
    python tools/compare_tv_trades.py \
        --tv-trades data/ActivationScores_COINBASE_BTCUSD_2026-03-26.csv \
        --data data/COINBASE_BTCUSD-4H.csv \
        --params results/winners/optimization_winner_activation_scores_COINBASE_BTCUSD_4H.csv

    # MLP strategy comparison
    python tools/compare_tv_trades.py \
        --strategy-file strategy_mlp_scores.py \
        --tv-trades data/MLPScores_COINBASE_BTCUSD_2026-06-17.csv \
        --data data/COINBASE_BTCUSD-6H.csv \
        --params results/winners/optimization_winner_strategy_mlp_scores_COINBASE_BTCUSD_6H.csv

    # Verbose: show score context for every divergent trade
    python tools/compare_tv_trades.py ... --verbose

    # Show exit-detail tables for entry-matched but exit-mismatched trades
    python tools/compare_tv_trades.py ... --exit-detail

    # Override timeframe (if not inferrable from filename)
    python tools/compare_tv_trades.py ... --timeframe 4H

Timestamp offset note
---------------------
Pine fills at open[T+1], so TV records entry at time[T+1] while Python
records the signal at time[T].  For a 4H chart this is a 4-hour forward
offset.  The tool compensates automatically: when matching trades it
computes  TV_entry_time - TF_offset ≈ Python_signal_time.
Tolerance is ±2 bars by default (adjustable with --tolerance-bars).

Root-cause classification
-------------------------
  SCORE_DIFF    : Python score vs TV exported score differ near crossunder
  CASCADE       : Python state diverged from TV after a prior exit mismatch
  TRAILING_STOP : Same entry, exit diverges (trailing stop fired differently)
  EXIT_CONF     : Exit confirmation threshold caused a 1-bar timing difference
  UNKNOWN       : Cannot determine without TV score export on those specific bars
    N)Path)Optional)SCORE_START	TRAIN_ENDTRAIN_STARTg{Gzt?entry_pxexit_pxcommission_ratereturnc                 8    | d|z   z  }|d|z
  z  }||z
  |z  dz  S )u   Per-trade P&L % net of commission — identical model to
    strategies/validate_strategy.py: pay commission_rate on the entry notional
    and on the exit notional (0.5%/side default).   d    )r   r	   r
   	eff_entryeff_exits        B/Users/jameslopez/projects/TradingBot25/tools/compare_tv_trades.pynet_pnl_pctr   L   s7    
 A/0I1./Hy I-33       hours         r   )days)4H6H8H12H1Dr   r   r   r   )240360480720	data_pathc                 D   t        |       j                  j                         }t        D ].  }|j	                  d|       s|j	                  d|       s,|c S  t
        j                         D ]  \  }}|j	                  d|       s|c S  |j	                  d      ryy)zInfer timeframe from the data CSV filename.

    Handles both dash/underscore-suffix forms (COINBASE_BTCUSD-4H.csv) and
    the TV minute-based export form (COINBASE_BTCUSD, 360.csv).
    -_, z, 1Dr    N)r   stemupperTF_DURATIONSendswith_MINUTES_TO_TFitems)r%   nametfminss       r   detect_timeframer3   d   s     	?%%'D==1RD"dmmatH&=I  #((*b==2dV%I + }}Vr   tf_strc                 V    t         j                  | t        j                  d            S )Nr   r   )r,   getpd	Timedelta)r4   s    r   	tf_offsetr9   x   s    FBLLq$9::r   pathc                 f    t        j                   d      j                  j                  j	                         _        dt        dt        f fd} |ddd      } |d	      } |d
dd      } |dd      } |ddd      }t        fddD        d      }i }j                         D ]n  \  }	}
	 t        |
|         }t        |
j                  |d            j	                         j                         }||vri ||<   d|v r	|
||   d<   bd|v sg|
||   d<   p g }t        |      D ]  }||   }d|vsd|vr|d   }|d   }	 t        j                  t        ||               }t        j                  t        ||               }t        ||         }t        ||         }t        ||         }|r)t        |j                  |d            j	                         nd}|j!                  |||||||d|j                         v d        |S # t        t        f$ r Y sw xY w# t        $ r Y w xY w)a+  
    Parse a TradingView exported trade-list CSV.

    TV exports rows in order: Exit first, then Entry for each trade.
    Returns a list of dicts with keys:
        trade_num, entry_dt, exit_dt, entry_price, exit_price,
        net_pnl_pct, exit_signal, is_margin_call
    sorted by entry_dt.
    	utf-8-sigencodingnamesr   c                  v    | D ]  }|j                   v s|c S  t        d ddj                  |              )Nz%None of these columns are present in : r)   )columnsKeyErrorjoin)r?   r0   dfr:   s     r   colzload_tv_trades.<locals>.col   sD    Drzz!  >tfBtyyQVGWFXYZZr   zTrade #zTrade numberzTrade NumberTypezDate and timez	Date/TimeDatez	Price USDPricez	Net P&L %z	Net PnL %zNet profit %c              3   @   K   | ]  }|j                   v s|  y wNrB   ).0r0   rE   s     r   	<genexpr>z!load_tv_trades.<locals>.<genexpr>   s     R(;trzz?Qt(;s   )SignalOrderN entryexitmargin)	trade_numentry_dtexit_dtentry_price
exit_pricer   exit_signal	is_margin)r7   read_csvrB   strstripnextiterrowsint
ValueErrorrC   r6   lowersorted	Timestampfloat	Exceptionappend)r:   rF   	trade_coltype_coltime_col	price_colpnl_col
signal_colrawr(   rownumt_typetradeste_rowx_rowrV   rW   r   r	   pnl_pctsignalrE   s   `                      @r   load_tv_tradesry   ~   sV    
TK	0B%%'BJ[C [C [ I~~>I6{H?K8HK)I+{N;GR(;RTXYJC++-3	c)n%C SWWXr*+11399;c>CHf #CHWv"CHV   Fc{H!vQ'
&		Sx%9:HSx%9:GeI./HeI./GeGn-GBLEIIj"56<<>RTF 	$#$##"$6	
 		 2 MI H% 		0  		s%   3HBH#H H #	H0/H0strategy_filec                 N    t        |       j                  }|j                  d      S )zReturn winner-file stem for a strategy filename.

    Examples:
      strategy_activation_scores.py -> activation_scores
      strategy_mlp_scores.py        -> mlp_scores
    	strategy_)r   r*   removeprefix)rz   r*   s     r   strategy_stemr~      s%     ##D[))r   params_pathc                    | rt         j                  j                  |       si S | j                  d      r]t	        j
                  |       j                  d   j                         }h d}|j                         D ci c]  \  }}||vs|| c}}S | j                  d      rt        |       5 }t        j                  |      }d d d        i }j                         D ]J  \  }}t        |t              r0d|v r|j                  dd g      d   n|j                  dd      ||<   F|||<   L |S i S c c}}w # 1 sw Y   rxY w)N.csvr   >   % In MarketTotal P&L %Calmar RatioP&L/DD RatioSharpe RatioTotal TradesSortino RatioMax Drawdown %verified	CPU_Score	Composite	GPU_Score_pnl_dd_percentilez.jsonvaluesstart)osr:   existsr-   r7   r\   ilocto_dictr/   openjsonload
isinstancedictr6   )r   rp   dropkvfro   flats           r   load_paramsr      s   bggnn[9	F#kk+&++A.668Y "%>A1>>G$+!))A,C  IIKDAq!T"8@A!%%4&1!4155QXZ[K\QQ	  
 I ?s   <D/	D/.D55D>paramsc                 .   t        |      }|j                         s7t        t              j                         j                  j                  dz  |z  }|j                         st        d|       t        j                  j                  d|      }t        j                  j                  |      }|t        j                  d<   |j                  j                  |       t        j                   |       }|j"                  j$                  j'                         j$                  j)                         |_        t        j*                  |d   d      j,                  j/                  d      |d<    |j0                  |fi |}t3        |      |j4                  d<   |S )	z>Load data CSV, run generate_signals, return signals DataFrame.
strategieszStrategy file not found: strategy_moduletimeT)utcNr~   )r   is_absolute__file__resolveparentr   FileNotFoundError	importlibutilspec_from_file_locationmodule_from_specsysmodulesloaderexec_moduler7   r\   rB   r]   rc   r^   to_datetimedttz_localizegenerate_signalsr~   attrs)r%   r   rz   strategy_pathspecmodrE   signalss           r   run_python_strategyr      s=    'M$$&X..077>>MP]]!";M? KLL>>112C]SD
..
)
)$
/C%(CKK!"KKC 	Y	B%%'++113BJ6
588DDTJBvJ #c""200G%2=%AGMM/"Nr   r   c                     | j                   j                  d      }|dk(  rd}n
|dk(  rd}nd}|D ]  }|| j                  v s|c S  y)zCReturn the TV-exported score column present in a signal/data frame.r~   
mlp_scores)	mlp_scoremlp_score_pocactivation_scores)activation_score_pocactivation_scores_poc)r   r   r   r   N)r   r6   rB   )r   strategy
candidatesrF   s       r   tv_score_columnr     sX    }}  1H<3
	(	(F
4
'//!J  r   rp   c                 h    t        |      }|t        d      S | j                  |t        d            S )Nnan)r   rf   r6   )rp   r   rF   s      r   tv_score_valuer     s0    
'
"C
{U|773e%%r   c                 x    t        | t              r | j                         j                         dv S t	        |       S )N>   1yonyestrue)r   r]   r^   rc   bool)values    r   _param_boolr     s1    %{{}""$(GGG;r   entriesexits	close_arrhigh_arrtrail_stop_pctc                    t        |       }t        j                  |t        j                        }t        j                  |t              }t        j                  |t              }d}	d}
d}t        |      D ]]  }|	r:d}|dkD  r ||k7  r||   |
kD  r||   }
||   |
d|z
  z  k  }||   s|r"d}	d||<   d}
d}n| |   rd}	d||<   ||   }
|dz   }|	rdnd||<   _ |||fS )	z6Replay Pine's flat/long lifecycle from raw conditions.dtypeF              ?Tr   r   )lennpzerosint32r   range)r   r   r   r   r   nin_pos
exec_entry	exec_exitposition
trade_highfill_bari	trail_hits                 r   _replay_positionsr      s    	GAXXarxx(F!4(J$'IHJH1XI#=Xa[:%=!)!J%aLJ#:N,OO	Qx9 #	! 
qz $
1&q\
q5!Aqq	' * :y((r   r   endc           	      N	   h d}|j                  | j                        s| S | j                         }| j                  j	                  d      dk(  rddlm} t        |      }||d<   ||d<   t        j                  |j	                  dt        j                  d	|j                  
            d      j                  d	      j                  t        j                        } |||||d   j                  t        j                        |d   j                  t        j                              }	|	j                  j!                  | j                         |	S |d   }
t#        |j	                  dd            }t#        |j	                  dd            }t#        |j	                  dd            }t%        |j	                  dd            }t%        |j	                  dd            }|r0|
j'                  d      |k\  |
j'                  d      |k  z  |
|k  z  }nrt        j                  d|j                  
      }d|j                  v r|d   j                  d	      d	k7  }|
j'                  d      |k\  |
j'                  d      |k  z  |z  }|rT|
j'                  d      |k\  |
j'                  d      |k  z  |
|
j'                  d      kD  z  |j'                  dd       z  }n|
j'                  d      |k\  |
|k  z  }t)        |j	                  dd            }t#        |j	                  dd             }|dkD  r%|
j+                  |d!      j-                         |kD  }n!t        j                  d|j                  
      }t%        |j	                  d"d            rd#|j                  v r||d#   dk\  z  }t        j.                  |d$         }||k\  }||||k  z  }||z  |z  j                  d      j                  t0              }||z  j                  d      j                  t0              }t#        |j	                  d%d	            d&z  }t3        |||d   j                  t        j                        |d   j                  t        j                        |      \  }}}||d'<   ||d(<   ||d)<   |S )*ai  Rebuild executed trades with Pine's startDate/endDate gate.

    Strategy signal generation intentionally runs on full history so scores and
    crossunders have the same warm-up context as Pine. Pine still gates
    strategy.entry()/strategy.close() with `timeCondition`, so pre-start raw
    entries must not carry position state into the scoring window.
    >   highr   closeactivation_scorer~   r   r   )_score_to_signals_pine_time_start_pine_time_endstoch_peak_normr   indexcoerceerrorsr   r   r   r   !i_long_entry_activation_threshold     Z@ i_long_exit_activation_threshold     a@-i_long_exit_activation_confirmation_thresholdg4@@i_use_long_exit_confirmationTi_use_long_entry_confirmationF   r   )
fill_valuei_regime_windowi_regime_entry_min_scoreg     @)windowmin_periodsi_mvrv_suppress_bearmvrv_regimer   i_trailing_stop_thresholdg      Y@r   execute_entryexecute_exit)issubsetrB   copyr   r6   strategies.strategy_mlp_scoresr   r   r7   
to_numericSeriesr   fillnato_numpyr   float64updaterf   r   shiftra   rollingmeanr   r   r   )r   r   r   r   requiredrE   r   replay_params
stoch_peakreplayedscoreentry_thresholdexit_thresholdexit_conf_thresholduse_exit_confuse_entry_confexit_raw	entry_rawregime_window
regime_min	regime_okr   time_okr   r   r   r   s                              r   apply_pine_time_gater)  I  sX    =HW__-	B}})\9DV,1()*-&']]FF$bii288&DE
 &+hhRZZh0 	 %wK  rzz 2vJbjj1
 	gmm,!"EFJJ'JERSO6::&H%PQN

+Z\c de

+I4 PQM ,KU!STN[[^~-[[^n,.((* 	 YYuBHH5


*-.55c:cAJ[[^~-[[^n,. 	 [[^.[[^o-/U[[^#% ^^A%^001 	 [[^65?;RS	

#4a89Mvzz"<gFGJqMMAMFKKMPZZ	IId"((3	6::4e<=-SUS]S]B]M!2a!78	>>"V*%DemG
TS[)Y&088?HHtHTI7"**51:::FH6::&A3GH5PN$5
72::.
6
"**-%!FJ	 BzN$B"B~Ir   c                 
   g }d}dx}x}}| j                         D ]e  \  }}|s|d   rd}|d   }|d   }|}|s|d   s%d}|d   }|d   }	|	|z
  |z  dz  }
|j                  ||||	t        ||	      |
||d	       dx}x}}g |S )
z4Walk signals DataFrame and extract entry/exit pairs.FNr  Tr   r   r  r   )rV   rW   rX   rY   rw   gross_pnl_pct	entry_idxexit_idx)r`   rh   r   )r   rs   r   rV   r   r,  idxrp   rW   r	   	gross_pnls              r   extract_python_tradesr0    s    FF&**H*x)$$&S#o.FFHGHIN+F6{G7|G 8+x7#=IMM!)!(!)!( "-Xw!?!*!*!$  /32H2x)/ '0 Mr   	tv_trades	py_tradestolerance_barsc                    t        |      }||z  }| D cg c]
  }|d   r	| }}t        |      }t        |      }	g }
|D ]  }d}|t        j                  d      z   }|D ]+  }|d   |z
  }t	        |d   |z
        }||k  s"||k  s(|}|}- |Q|
j                  ||f       |j                  |       ||	v sz|	j                  |        |
|	|fS c c}w )a  
    Match TV and Python trades by entry time.

    TV fills at open[T+1], so TV entry time = Python signal bar time + 1 bar.
    We subtract one TF offset from the TV entry time before comparing.

    Returns:
        matched       : list of (py_trade, tv_trade) pairs
        py_only       : Python trades with no TV match
        tv_only       : TV trades (non-margin) with no Python match
    r[   Nr   )secondsrV   )r9   listr7   r8   absrh   remove)r1  r2  r4   r3  offset	tolerancert   tv_realavailable_tvunmatched_pymatchedpybestbest_errtvtv_adjerrs                    r   match_tradesrE    s    " &!F'I#:)Q1[>q)G:=L	?LGr||A66B
^f,Fbnv-.CiC(N  NNB:&%\!##B'   L,..+ ;s
   
CCrB   c                     g }|D ]N  }|| j                   vrt        j                  | |   d      j                  d      dk7  }|j	                  |       P |sy|d   j                         }|dd D ]  }||z  }	 |S )zHReturn truthy marker mask for any available exported Data Window column.r   r   r   Nr   r   )rB   r7   r  r  rh   r  )r   rB   masksrF   markeroutmasks          r   _marker_maskrK    s    Egoo%ws|H=DDSISPV	 
 
(--/Cab	Dj Jr   c                 :   t        |dd      }|d|j                  vryt        |d      }|!t        j                  d|j                        }| D cg c]
  }|d   r	| }}t        |      }	t        j                  |d         }
t        |j                  |
      D ci c]  \  }}||
 }}}g }t               }|D ]p  }|d	   |	z
  }|j                  |       |j                  |      }||j                  ||d
f       Dt        |j                  |         r]|j                  ||df       r || z  |
|k\  z  }|||
|k  z  }t        |
|         }t        ||z
        }dt        |      t        |      t        |      z
  |t        |      |dS c c}w c c}}w )a1  Compare Strategy Tester entries with exported Pine entry markers.

    The TV trade list records fills one bar after the Pine signal. The chart
    export records the signal-bar `Entry (...)` marker. When these disagree,
    trade-list parity is not trustworthy even if Python and chart scores match.
    zentry (standard)zentry (confirmed)Nr   zmissed entryFr   r[   rV   z#no chart bar at adjusted entry timez.no exported Entry marker on adjusted entry barT)	availabletv_counttv_with_markertv_without_markerchart_exec_countchart_without_tv)rK  rB   r7   r  r   r9   r   zipsetaddr6   rh   r   locrd   r   )r1  r   r4   r   r   entry_markermissed_entryrt   real_tvr9  signal_timesr.  tstime_to_idxrP  tv_signal_timesrB  	signal_dt
chart_execchart_exec_timesrR  s                        r   check_tv_entry_markersra    s     );=PQLvW__<8Lyygmm<#:)Q1[>q)G:vF>>'&/2L*-gmm\*JK*JwsB2s7*JKKeOzNV+	I&ooi(;$$b)5Z%[\l&&s+,$$b)5e%fg  -1FGJ
<3#67
<
34.@A Lg,->)??. 01, - ; Ls   
FFFbar_idxcontextc                    t        d||z
        }t        t        |       dz
  ||z         }| j                  ||dz    }t	        |       }|xs d}dddddddd	d
d|dddddddg}|j                  d       t        |j                               D ]  \  }	\  }
}|
|k(  rdnd}t        ||       }t        j                  |      s|dnd}d| j                  v r|d   dnd}|j                  dd      rdnd}|j                  dd      rdnd}|j                  d|
ddt        |d         dd dd|d
d|dd|dd|d|         dj                  |      S )z;Return a compact table of activation scores around bar_idx.r   r   tv_score  bar>4r   z>19r  z>9z>12rR   z>5rS   zF  --------------------------------------------------------------------z <<<rQ   z.2fz   n/ar   r  FE.r  XN   
)maxminr   r   r   rh   	enumerater`   r   r   isnanrB   r6   r]   rD   )r   rb  rc  lohirowstv_coltv_labellinesr   ridxrp   rH  re  
tv_score_sscore_ventry_fexit_fs                     r   score_contextr}  H  s   	Q'!	"B	S\Aw0	1B<<26"DW%F#H%2fS\GB<r(3r'RTUWX^_aWbcdE	LL!#DMMO4;D#7?!#w/.0hhx.@~h
6HGOO6[S+,S1ai%8#c%8#cb	CF,Sb1#6bBzRUFVVXY`acXddfgmnpfqrxqyz	
 5 99Ur   tv_trader  c           	         t        |      }| d   |z
  }|dz  }|d   ||z
  k\  |d   ||z   k  z  }||   }|j                  ry|j                  |d   j                  |      j	                         j                            }	|j                  |	   }
|
j                  dt        d            }|	dkD  r|j                  |	dz
  df   n
t        d      }t        |
|      }t        j                  |      st	        ||z
        n
t        d      }t        j                  |       xr ||k\  xr ||k  }g }|r|j                  d	       nE|j                  d
|dd|dd| d       ||k\  r|j                  d       n|j                  d       t        j                  |      s#|dkD  r|j                  d|dd|dd|dd       dj                  |      S )zv
    Try to explain why Python missed a TV entry.
    Looks for the Python bar corresponding to TV entry - 1 bar.
    rV   r  r   z'UNKNOWN (no matching bar found in data)r   r   r   r   z9Python DID fire crossunder here (state diverged earlier?)zPython score at signal bar: .1fz (prev z, threshold )u@   → Score never dropped below threshold (score difference vs TV)uD   → Score was already below threshold (no prior above-threshold bar)   zTV exported score=z vs Python score=u    (Δ=u   ) → SCORE_DIFF | )r9   emptyr   subr7  argminrV  r6   rf   r   r   rq  rh   rD   )r~  r   r  r4   r9  py_signal_timetolrJ  nearr.  rp   	score_now
score_prevtv_score_now
score_diffcrossunder_pypartss                    r   classify_tv_onlyr  _  s    vFj)F2N
1*CFO~33>\_K_8_`D4=Dzz8
**T&\%%n599;BBD
EC
++c
C+U5\:I=@1WS1W&889%PU,J!#w/L79xx7M#i,./SXY^S_J*--tJ/4QtXadsXsMEPQ3Ic?'*UXIYYefuevvwxy'LL[\LL_`88JJN),s);;LYWZO[`aklo`p  qA  B  	C::er   py_trader"  c           
         | d   }d|j                   v }t        |      }|du}g }|r|dk\  r|r|j                  |dz
  df   n
t        d      }	|r|j                  |dz
  df   n
t        d      }
|r|j                  |dz
  |f   n
t        d      }|r|j                  |dz
  |f   n
t        d      }t	        j
                  |	      sDt	        j
                  |
      s/|	|k\  xr |
|k  }|j                  d|	dd	|
dd
| d|rdnd        t	        j
                  |      st	        j
                  |      s||k\  xr ||k  }|j                  d|dd	|dd|rdnd        t	        j
                  |
      st        |
|z
        dkD  r|j                  dt        |
|z
        dd       n|r|j                  |df   n
t        d      }|dkD  r|r|j                  |dz
  df   n
t        d      }|r|j                  ||f   n
t        d      }t	        j
                  |      sDt	        j
                  |      s/||k\  xr ||k  }|j                  d|dd	|dd
| d|rdnd        t	        j
                  |      s|dkD  r|j                  |dz
  |f   n
t        d      }t	        j
                  |       xr ||k\  xr ||k  }|j                  d|dd	|dd|rdnd        t	        j
                  |      s3t        ||z
        dkD  r"|j                  dt        ||z
        dd       |sydj                  |      S )u   Try to explain why Python fired an entry that TV didn't.

    With use_entry_conf=True the crossunder was at bars t-2→t-1; the entry
    fires at t (confirmation bar).  Without confirmation it is t-1→t.
    r,  r   Nr  r   r   u   Python crossunder (t-2→t-1): r      → z (threshold z): YESzno crossunder?u   TV exported score (t-2→t-1): rA   zYES (TV should also fire)zNO (TV rightly quiet)r  zScore delta at crossunder bar: u    → SCORE_DIFFr   zPython crossunder: zTV exported score crossunder: zScore delta: UNKNOWNr  )	rB   r   rV  rf   r   rq  rh   r7  rD   )r  r   r  r"  r.  	has_scoreru  has_tv_scorer  xprevxcurrxprev_tvxcurr_tv
crossundertv_xur  r  re  tv_prevs                      r   classify_py_onlyr    si    ;
C"goo5IW%F%LE#(<EC!G%7785QV<<EC!G%7785QV<3?7;;sQw/U5\3?7;;sQw/U5\288E??2Q9PJLL1%E%/sJ5L\2]_ "bhhx&80Rx/7QELL1(3uXcN S38/>UVX 88E?s58+;'<q'@>s58CS?TUX>YYhij >GW[[&8!89ERWL	ADqYW[[q*<!<=\abg\h
1=W[[f-5<$(;$7Zi/>YJLL%j%5U9S//sJ5L\2]_ xx!69Aggkk#'6/25<G'**mO1KmRZ]lRlELL0U8C. Q38/>UVX 88I&3y8/C+Dq+H}SX1E-Fs,K?[\::er   r  c                 <   | d   }|d   }||z
  j                         dz  }d|j                          d|j                          d|ddg}|j                  d| d	   d
d|d   d
d       | d   }	|	t        |      k  r|j                  |	   nd}
|
d|j
                  v r|
d   nd}d}|I|	dk\  rDd|j
                  v r6|j                  |	dz
     d   }|j                  |	dz
     d   }||k\  r||k  rd}|r(|j                  d|dd       |j                  d       np|dkD  r?|j                  d|dd       ||j                  d|d       |j                  d       n,|j                  d|        ||j                  d|dd       d j                  |      S )!u  Classify why matched entry has different exit.

    Determines whether Python's exit was triggered by the trailing stop or by a
    score crossunder, by checking whether the 2-bar crossunder pattern is present
    at the Python exit bar.  Trailing stop and score-crossunder exits both call
    strategy.close("long") in Pine, so TV's trade list cannot distinguish them —
    only the Python-side patterns can.
    rW     zPython exit z / TV exit u    (Δ z+.0fh)zPython P&L rw   +.1fz% / TV P&L r   %r-  Nr   Fr  r   TzPython exit bar score: r  u    → SCORE_CROSSUNDER_MISMATCHzTV and Python exited on different score-crossunder events. If score parity passes, this is usually downstream of an earlier lifecycle/export mismatch.r   zTrailing stop .0fu   % active → TRAILING_STOPzqPython trail_high uses bar high (same as Pine); stop triggered at close. Residual timing difference is intrinsic.z!No trailing stop; exit_threshold=u    → EXIT_CONFr  )total_secondsdaterh   r   r   rB   rD   )r  r~  r   r   r  py_exittv_exitdiffr  r-  exit_rowr  is_score_crossunderprev1prev2s                  r   classify_exit_mismatchr    s    y!Gy!G //1D8DGLLN+;w||~6FeDQU;VXYZE	LL;x	248H]D[\`Caabcd
#H)1CL)@w||H%dH-5-AFX\c\k\kFkH'(rvE
  X]/AW__/TX\*+=>X\*+=>N"u~'="&.uSk9WXYj	
 
!	~nS%99STULL25+>?7	

 	88HIJLL25+^LM::er   c                  "  P t        j                  dt         j                  t              } | j	                  ddd       | j	                  ddd       | j	                  d	d d
       | j	                  ddd       | j	                  dd d       | j	                  dt
        dd       | j	                  ddd       | j	                  dt
        dd       | j	                  ddd       | j                         }|j                  xs t        |j                        }|t        d       t        d       d }t        d!| d"t        |       d#       	 d$d%lm}  ||j                        \  }}}|r+t        d&       |D ]  }t        d'|         t        d(       |j                  }|t!        |j                        j"                  }	|	j%                  d)d*      j'                         }
t)        |j*                        }t!        d+      d,| d*|
 d-z  }|j-                         rt/        |      }t        d.|        nt        d/| d0       t        d1|j0                          t3        |j0                        }|D cg c]
  }|d2   r	| }}|D cg c]
  }|d2   s	| }}t        d3t5        |       d4t5        |       d5       |rt7        |      ni }t        d6|j*                   d7|j                          t9        |j                  ||j*                  8      }t;        j<                  t>              }t;        j<                  t@              }tC        ||||9      }tE        |      }|D cg c]  }|d:   |k\  s| }}t        d;t5        |       d<|jG                          d=|jG                          d>t5        |       d?	       tI        |jK                  d@dA            }tI        |jK                  dBdC            }tI        |jK                  dDdE            }|jK                  dFdG      }|jK                  dHd      }t        dI       t        dJ| dK| dL       t        dM| dN| dL       t        dO| dP       tM        ||||jN                        \  }}}t        |      |jN                  z  }g g }!} |D ]Q  \  }"}#|#dQ   t        |      z
  }$tQ        |"dQ   |$z
        |k  r| jS                  |"|#f       ?|!jS                  |"|#f       S |rtU        dR |D              nt;        j<                  dS      }%|D &cg c]  }&|&d:   |%k  s|& }'}&|D &cg c]  }&|&d:   |%kD  s|& }(}&tW        |||||r|%t        |      z
  nd       })dT}*t        dUdV|*z          t        dW       t        dV|*z          t        dXt5        |      dYdZ|%jG                          dL       t        d[t5        |      dY       t        d\t5        |       dYd]t5        |       tU        t5        |      d^      z  d_z  d`da       t        dbt5        |!      dY       dct5        |'       dd|%jG                          det5        |(       df}+t        dgt5        |      dY|+        t        dht5        |      dY       |rt        dit5        |      dYdj       t        dV|*z          |)|)dk   },|)dl   }-|,s|-s|jX                  rt        dm       t        dn|)do    dp|)dq           t        dr|)ds    d]t5        |-       dt       |,r6t        du       |,d dv D ]#  \  }#}.}/t        dw|#dx    dy|. dz|#d:    d{|/        % |-r#t        d|       |-d dv D ]  }.t        d}|.         d~x}0}1|D ]  }|0d^|d   d_z  z   z  }0|1d^|d   d_z  z   z  }1  |0d^z
  d_z  }2|1d^z
  d_z  }3	 t;        jZ                  |j0                  d      PPj\                  j.                  j_                         P_.        ta        PfddD        d       }4|4r P|4   jc                         jd                  d   n
tI        d      }5tf        d_z  }6t5        |      t5        |      k(  }7ti        |5tH              xr tk        jl                  |5       xr |5}8t                t        dd|6z  d|2ddP       t        ddd|5ddP       |8r0|7r.tQ        |2|5z
        tQ        |5      z  d_z  }9t        d|9d`d       n&|8r$t        dt5        |       dt5        |       d       t        ddd|3ddP       t                | rt        dUd|*z          t        dt5        |        d       t        d|*z          dddddddddddddddddddddddd}:t        |:       t        dd)|*dz
  z  z          | D ]  \  }"}#|#d:   t        |      z
  };|#dQ   t        |      z
  }$|"d:   |;z
  jo                         dz  }<|"dQ   |$z
  jo                         dz  }=|"d   |#d   z
  }>tQ        |=      d^kD  rd|=ddnd}?t        d|#dx   ddt/        |"d:   jG                               ddt/        |#d:   jG                               ddt/        |"dQ   jG                               ddt/        |#dQ   jG                               dd|?dd|"d   dd|#d   dd|>ddP         |!rt        dUd|*z          t        dt5        |!       d       t        d|*z          |!D ]  \  }"}#t        d|#dx    d       tq        |"|#|||      }/|/js                  d      D ]  }@t        d|@         |jt                  sX|"d   ^t        tw        ||"d   |jv                                |'rt        dUd|*z          t        dt5        |'       d       t        d|*z          |'D ]  }"ty        |"||t{        |            }/t        d|"d:   jG                          d|"dQ   jG                          d|"d   ddP       |/js                  d      D ]  }@t        d|@         |jX                  s|"d   t        tw        ||"d   |jv                                |(rt        dUd|*z          t        dt5        |(       d|%jG                          d       t        d|*z          |(D ]=  }"t        d|"d:   jG                          d|"dQ   jG                          d|"d   dd       ? |r_t        dUd|*z          t        dt5        |       d       t        d|*z          |D ]!  }#t}        |#|||      }/t        d|#dx    d|#d:   jG                          d|#dQ   jG                          d|#d   ddP	       |/js                  d      D ]  }@t        d|@         t        |      }A|#d:   |Az
  }B|Adz  }C|d   |B|Cz
  k\  |d   |B|Cz   k  z  }D||D   }E|Ej~                  r|jX                  sEj                  |Ed   j                  B      jQ                         j                            }Ft        tw        ||F|jv                               $ |rst        dUd|*z          t        dī       t        d|*z          |D ]C  }#t        d|#dx    d|#d:   jG                          d|#dQ   jG                          d|#d   ddP	       E t        dUdV|*z          t        dƫ       t        dV|*z          d$}G|'D ]$  }"ty        |"||t{        |            }Hd|Hv s Gd^z  }G& |D ]  }#t}        |#|||      }Hd|Hv sGd^z  }G t5        |'      t5        |      dvz   kD  }Id$}Jd$}K|!D ]-  \  }L}Mtq        |L|M|||      }Nd|Nv rJd^z  }JdNv rKd^z  }K)Kd^z  }K/ g }OGd$kD  r5OjS                  dG d|j                   d| d|j                   d| dϝ       Ir*OjS                  dt5        |'       dt5        |       dҝ       |'s|r*OjS                  dt5        |'       dt5        |       d՝       |):|)dk   s|)dl   r0OjS                  dt5        |)dk          dt5        |)dl          d؝       Jd$kD  rOjS                  dJ dٝ       Kd$kD  rOjS                  dK d|dd۝       OsOjS                  dܫ       OD ]  }Ht        |H       t                 t        dݫ       t        dޫ       t        d߫       t        dV|*z          y # t        $ r Y w xY wc c}w c c}w c c}w c c}&w c c}&w # t        $ r tI        d      }5Y 	w xY w)NuJ   Compare TradingView trade list vs Python backtest — TV parity diagnostic)descriptionformatter_classepilogz--tv-tradesTz#TradingView exported trade-list CSV)r  helpz--dataz;OHLCV+indicator data CSV (e.g. data/COINBASE_BTCUSD-4H.csv)z--paramszGWinner params CSV or JSON (auto-detected from data filename if omitted))defaultr  z--strategy-filestrategy_mlp_scores.pyzEStrategy Python file in strategies/ (default: strategy_mlp_scores.py)z--timeframezGTimeframe override (4H/6H/8H/12H/1D). Auto-detected from data filename.z--tolerance-barsr  z*Entry-match tolerance in bars (default: 2))typer  r  z	--verbose
store_truez2Show score context table for every divergent trade)actionr  z--score-contextr   zIBars of score context to show per divergence in verbose mode (default: 4)z--exit-detailz;Show detailed exit-mismatch tables for entry-matched tradeszB[WARN] Could not detect timeframe from filename. Defaulting to 4H.z4       Pass --timeframe 4H/6H/8H/12H/1D to override.r   zTimeframe: z  (TV fills 1 bar ahead = z offset)r   )validate_fileuO   
  ⚠️  CHART-DATA INTEGRITY ERRORS — parity results below may be invalid:z       ERROR: uG          → re-export full clean history (tools/validate_chart_data.py)
r'   r(   zresults/winnersoptimization_winner_r   zAuto-detected params: z)[WARN] No params file found for strategy z. Using strategy defaults.z
Loading TV trades from: r[   zTV trades: z real signal  +  z margin-callzRunning Python strategy z on: )rz   )r   r   rV   zPython trades: z (Pine timeCondition r  z; z total after time gate)r   r   r   r   r
  r   r  Fr   z
Key params:z  Entry threshold:  z  (use_entry_conf=r  z  Exit threshold:   z  (use_exit_conf=z  Trailing stop:    r  rW   c              3   &   K   | ]	  }|d      yw)rW   Nr   )rM   rt   s     r   rN   zmain.<locals>.<genexpr>p  s     5Wq|Ws   z
2099-01-01P   rm  =z  MATCH SUMMARYz  Total TV real trades  : rh  z  (coverage through z  Total Python trades   : z  Entry + exit matched  : z  (r   r   r  z% of TV)u     Entry matched, exit Δ : z  (IS: z, OOS after rA   u     — OOS are expected, not bugs)z  Python-only (extra)   : z  TV-only (missed)      : z  TV margin-call trades : z  (not modelled in Python)rP  rR  z
  TV/chart entry-marker check:z,    TV entries with exported chart marker : rO  /rN  z,    Chart executable markers in TV window : rQ  z not present in TV trade list)z*    First TV entries without chart marker:r  z	      TV#rU   z signal z (fill u   ) — z3    First chart executable markers missing from TV:z      r   rw   r+  r<   r=   c              3   >   K   | ]  }|j                   v r|  y wrK   rL   )rM   r0   	tv_df_raws     r   rN   zmain.<locals>.<genexpr>  s'      +Fd	))) Fs   )zCumulative P&L %zCumulative PnL %r   r   rf  z)Python cumulative P&L (%.1f%%/side comm):z<42z>12,.1fzTV cumulative P&L:u!       → net vs TV relative diff: z.%  (residual = fill price / slippage / timing)u,       → ⚠️  trade counts differ (Python z vs TV zW); cumulative totals are NOT comparable. See per-trade table / unmatched section below.z(Python gross (no commission, reference):u   ─z  FULLY MATCHED (z trades)#z>3zPy Entryz>10 zTV EntryzPy ExitzTV ExitzPy P&L%z>8zTV P&L%u   Δ P&L%z>7r  r   z(+r  r  rQ   6sz>8.1fz% z%  z>+7.1fu3     EXIT-MISMATCHED (same entry, different exit) — z tradesz
  TV#:r  z    r-  z  PYTHON-ONLY / IS (u3    trades — Python fires in IS period, TV does not))r"  z	
  Entry z  Exit z  P&L r  r,  z  PYTHON-ONLY / OOS (u!    trades — after TV export ends z, not a parity issue)z  Entry z%  [OOS]z  TV-ONLY (u&    trades — TV fires, Python does not)r   z<  TV MARGIN-CALL TRADES (micro-size, not modelled in Python)z  TV#z  DIAGNOSTIC RECOMMENDATIONS
SCORE_DIFFSCORE_CROSSUNDER_MISMATCHTRAILING_STOPz  [u    SCORE_DIFF] Python activation_score differs from the TV-exported score on crossunder bars.
    → For activation_scores, run tools/diagnose_tv_parity.py --data z
 --params u]   
      to see per-component breakdown.
    → For MLP, run tools/check_mlp_parity.py --data z' --weights <mlp_weights_file> --params zG
      and re-export from TradingView if mlp_score is missing or stale.z  [CASCADE RISK] Python has z extra IS trades vs u    TV-only.
    → Extra Python trades often stem from a single prior exit divergence that
      leaves Python "flat" while TV is "long", allowing Python to re-enter
      on bars where TV is already in position.z  [UNMATCHED TRADES] z Python-only IS trades and u
   TV-only trades remain.
    → Do not treat this run as trade-list parity. First confirm the chart export
      covers the full TV trade-list range and that the Strategy Tester export was
      generated from the same symbol, timeframe, preset, and script revision.z  [TV/CHART EXPORT MISMATCH] z0 TV entries lack a matching chart entry marker; uQ   chart executable markers are absent from the TV trade list.
    → The exported Data Window and Strategy Tester files do not describe the same
      Pine lifecycle, or the Strategy Tester export is from a different preset/script revision.
      Re-export both from the same chart state before interpreting Python lifecycle differences.uX   SCORE_CROSSUNDER_MISMATCH exits] TV/Python exits landed on different score-crossunder events.
    → First check score parity. If it passes, treat these as downstream lifecycle/export-state
      effects from an earlier unmatched entry or exit, not as score-computation drift.
    → Use --exit-detail flag to see score context on exit bars.z$ TRAILING_STOP exits] Trailing stop u   % active.
    → Python trail_high uses bar high (same as Pine); timing difference from close-evaluation vs
      intrabar evaluation in Pine. Not a bug — intrinsic fill difference.
    → Use --exit-detail flag to see score context on exit bars.zK  Parity looks good! Minor differences are expected from fill-price offset.zK  Tip: run with --verbose for score context tables on all divergent trades.zV       activation_scores: run tools/diagnose_tv_parity.py for per-component breakdown.zU       MLP: run tools/check_mlp_parity.py before interpreting trade-list differences.)CargparseArgumentParserRawDescriptionHelpFormatter__doc__add_argumentra   
parse_args	timeframer3   dataprintr9   tools.validate_chart_datar  rg   r   r   r*   replacer+   r~   rz   r   r]   r1  ry   r   r   r   r7   re   r   r   r)  r0  r  rf   r6   rE  r3  r7  rh   rn  ra  verboser\   rB   r^   r_   dropnar   COMMISSION_RATEr   r   rq  r  r  splitexit_detailr}  r  r   r  r  r   r  r  )Qparserargsr4   r  _derrs_dwarnsr(   _er   r*   asset_tfstrategy_name	candidater1  rt   r;  	tv_marginr   r   
pine_startpine_endall_py_tradesr2  r  r  r   r"  r!  r>  py_onlytv_onlyexit_tolerance
full_matchexit_mismatchr?  rB  tv_exit_adj
last_tv_dtp
py_only_ispy_only_oosmarker_checkWoos_note
tv_missingchart_missingr^  reasonpy_equity_netpy_equity_gross
py_pnl_netpy_pnl_grosscum_col
tv_cum_pnlpctcounts_alignedtv_cum_validrelhdrtv_entry_adjentry_dexit_dpnl_dflagpartr9  	py_bar_dtr  rJ  r  r.  score_diff_countrcascade_riskexit_score_xunder_countexit_trailing_countr  r~  labelrecsr  sQ                                                                                   @r   mainr    s   $$` <<F
 B  D
Z  \

f  h
)3Kd  f
f  h
*aI  K
|Q  S
)Qh  j
Z  \D ^^:/		:F~RSDE	Kx9)F:K9LH
UV	;*4995derd+, \]
 ++KDII##<<S)//1%d&8&89*+0D]OSTU]T^^b.cc	i.K*;-89=m_Lfgh 
&t~~&6
78t~~.I%<IqQ{^IG<%8Iq;II8	KG~%6s9~6Fl
ST)4[%"F	$T%7%7$8dii[
IJ!$))V4CUCUVGk*J||I&H"7F*(SG)'2M
 *IMqQz]j-HMII	
#i.) *)01x}}6Gr}
5	7 VZZ(KUSTOVZZ(JUSTNVZZ(CSIJNzz"A5INzz"@$GM	M	  11CNCSST
UV	  00A-PQ
RS	  0
23 !-Y	64K^K^ _GWg v&)<)<<N "BJBmi&77r)}{*+~=r2h'  "b*  :A35W55bllS_F`J&Fw!!J-:*EAwJF&Fw!!J-:*EAwKF)*1
Yv&&tL 	A	Bs1ug,	O	SUG	&s7|B&77KJOOL]K^^_
`a	&s9~b&9
:;	&s:r&:#c*ocRUV]R^_`Na>abe>efi=jjr
st	'M(:2'>
?@Z)joo6G5H3{K[J\\|}H	&s7|B&7z
BC	&s7|B&7
89*3y>"*==WXY	SUG!"56
$%78$,,46> 012!L4L3MO > 234 5&''EG
 BC-7^)B	6#B{O#4HYK H!!#J 0vh@ .<
 KL!.r!2IF9+./ "3 '*)MOA)s 223A/ 2S 889  "A%,J#a'3.L
"[[+F	%--11779	+F +

 >EYw'..055b9%PU,
 C
C^s7|3N":u5N"((::NO^T^L	G	B:S@EjQXEYYZ
[\	B#C(G(<A
>?*z)*S_<sB1#c ;< = 	>	 	<S^<LGW ?@ 	A 
B9#>|G>TTU
VW	G 57)n!#j/!2(;<q	3r("Z,Aj-=R	#aPYZ]^`ajkm`nnopyz|o}}  AJ  KM  @N  Oc
dSAE]"# FBj>If,==Li=If,==K*~4CCELG)}3BBDLFmb&77E-0[1_6#,b)"DR_R(r*~**,-c2!C:8K8K8M4Ns3SSUr)}))+,S13r)}7I7I7K3LS2QQRSWXZR[[]i='r"]*;E)B#eF^STV ! 57)nCCDVCWW^_`q	#FBGB{O,A./+BG^^\FU+TFm$ ,BzN$>mGR
^T=O=OPQ $ 57)n$S_$55hijq	B%b'?SWXfSghFJr*~2245WR	]=O=O=Q<RRXY[\eYfgkXllmnoU+TFm$ ,||; ;mGR_d>P>PQR  57)n%c+&6%77XYcYhYhYjXk  lA  B  	Cq	BHR
^002372i=;M;M;O:PPVWYZcWdeiVjjrst  57)nCL>)OPQq	B%b'?FKFGB{O,HR
^5H5H5J4K7SUV_S`SeSeSgRhhnoqr  pA  BF  oG  GH  I  JU+TFm$ , #6*FJ&0I!C!&/Y_<T]`cTcAcdD D::$,,jjf!1!1)!<!@!@!B!I!I!KLmGS$2D2DEF   57)nLNq	BE"[/*(2j>3F3F3H2IQST]Q^QcQcQePfflmop}m~  @D  mE  EF  G  H  
Bs1ug,	
()	SUGR/$~J^_1!  R/6B1! 
 z?S\A%55L  +(&x7NTbc&%/#q(#%1$1$ , D!"# $UUYU^U^T__ijuiv wEEIYYKOv  xC  wD DUV	
 *3z?*;;OPST[P\~ ^= >	
 W#C
O#44OPST[P\~ ^\ ]	
 ()\:L-M+< 34566f< 2345 6op	
 ")* +P Q	
 Q%&&J>Z]J^ _P Q	
 aba  
WY	bd	ac	SUG[
  ( =8 JF GFJ  "5\
"sv   A	AD =
AD.AD.
AD3AD3-AD8;AD85AD=AD=AEAE?BAE D	AD+D*AD+EAEEAE__main__)r  rK   )r  )r  )F)>r  r  importlib.utilr   r   r   r   pathlibr   typingr   numpyr   pandasr7   r:   insertr]   r   r   r   configr   r   r   rh   r  rf   r   r8   r,   r.   r3   r9   r6  r   ry   r~   r   	DataFramer   r   r  r   r   r   ndarraytupler   re   r)  r0  ra   rE  rK  ra  r}  r  r  r  r  __name__r   r   r   <module>r     s  0d    	 
     3tH~--/66==> ? 6 6 DN**,33::; <  *94% 4% 4!&4=B4 2<<a 2<<a 2<<a 2<<b!2<<Q d4F  (;c ;bll ;B Bd BN* * *S T 2 .F3  '*JL,,6R\\ hsm  &		 &BLL &U &$ &)ZZ&)::&) zz&) jj	&)
 &) 2::rzz2::-.&)Z #'	d\\dd <<d 
",,		d
 \\dN2<< DJ N 	)/d)/d)/ )/ 	)/
 4;T
DJ./)/\",, # (299:M * #'2Dz2\\2 2 <<	2
 
",,	2 d^2n2<< #  C .&t &bll &U & &%(&T -2;t ;bll ;U ;%);69;|3T 3T 3BLL 3+03BG3LO3pyx zF r   