
    jzF                        d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlmZ ej                  j!                  d e ee      j'                         j(                  j(                               ddlmZ ddlmZ ddlmZ ddlmZ dd	lmZmZm Z  dd
l!m"Z" ddl#m$Z$m%Z%m&Z&m'Z' d"dZ(d Z) G d d      Z*d#dZ+d$dZ,d%dZ-d Z.d Z/d Z0d Z1d Z2d Z3d Z4d Z5d Z6d Z7d Z8d Z9d Z:d&d Z;e<d!k(  r ejz                   e;              yy)'u  
Same-candle entry-evaluation dedup (V16 patch).

Prevents the 20×-per-candle AI hammering observed in LIVE on 29 apr:
TF/MR pre-validations passed every 15s loop iter, calling ai.ask each
time on the same M5 candle for the same symbol. Now the brain compares
tech.candle_time with last_entry_eval_time (passed by orchestrator
from SessionState.entry_eval_cache) and short-circuits if equal.

Cache update policy (matches docstring in EntryEvalResult):
  text present (success or malformed JSON)        -> update
  text None + error_kind in {credit, invalid}     -> update (permanent)
  text None + error_kind = "unknown" (transient)  -> NOT updated (retry)
    )annotationsN)Path)TechSnapshot)
AIResponse)BrainMR)BrainTF)RuntimeConfigRunModeAccountKind)EntryEvalResult)EntryEvalCacheSCHEMA_VERSIONSessionState
StateStorec                     t        d|         y )Nz  ok  )print)labels    -/home/work/apex_v16/tests/test_entry_dedup.py_okr   %   s    	F5'
    c           
     |    ddl m}  || j                  | j                  t	        | dd      t	        | dd      d      S )	zBuild a BiasData from a TechSnapshot's algo-only bias fields.
    Production passes bias_data from BiasResolver (Source A); tests
    derive it from snap to keep legacy fixtures unchanged.r   )BiasDatah1_compatibility      ?	h1_reasontestF)biasallowed_directionr   r   	ambiguous)analysis.biasr   r   r   getattr)snapr   s     r   _bdr#   )   s@     'YY00 '93?$V4 r   c                      e Zd ZddZddZy)_CountingAIc                     || _         d| _        y )Nr   )respcalls)selfr'   s     r   __init__z_CountingAI.__init__<   s    	
r   Nc                L   K   | xj                   dz  c_         | j                  S w)N   )r(   r'   )r)   prompt
max_tokenswheres       r   ask_for_decisionz_CountingAI.ask_for_decision@   s     

a
yys   "$)r'   r   returnNone)i  N)__name__
__module____qualname__r*   r0    r   r   r%   r%   ;   s    r   r%   c                
   t        dMi d| j                  dd      dt        | j                  dd            dt        | j                  d| j                  dd                  dt        | j                  dd      xs d	      d
t	        | j                  d
d            dt        | j                  dd            dt        | j                  dd            dt        | j                  d| j                  dd                  dt        | j                  dd            dt        | j                  dd            dt        | j                  dd            dt        | j                  dd            d| j                  dd      dt	        | j                  dd            d| j                  dd      dt	        | j                  dd            d t	        | j                  d d            d!t        | j                  d!d"            d#| j                  d#d$      d%| j                  d%d&      d't        | j                  d'g             d(t        | j                  d(d)            d*| j                  d*d+      d,t	        | j                  d,d            d-t        | j                  d-d.            d/t        | j                  d/d0            d1t	        | j                  d1d            d2t	        | j                  d2d            d3t	        | j                  d3d            d4t	        | j                  d4d            d5t	        | j                  d5d            d6| j                  d6      d7t	        | j                  d7d            d8t	        | j                  d8d            d9t	        | j                  d9d            d:t	        | j                  d:d            d;t	        | j                  d;d            d<t	        | j                  d<d            d=t	        | j                  d=d            d>t        | j                  d>| j                  dd                  d?t        | j                  d?d.            d@| j                  d@dA      dB| j                  dBdC      dDt        | j                  dDd0            dE| j                  dEd&      dFt        | j                  dFdGdi            dHt        | j                  dHd	            dIt        | j                  dIdJ            dKt        | j                  dKdL            S )Nz
    Mirrors the dict-driven helper used in test_brain_tf.py: only
    fields named in `d` override defaults. Keeps this test resilient
    to future TechSnapshot field additions.
    symbolMESpriceg     @openg     @candle_timeiW/fr   is_candle_closedTcandle_age_secondsg      $@rsi      I@rsi_prevrsi_h1g      J@rsi_h4g     K@atr_m5_pointsg      >@	atr_ratiog?
vol_regimeNORMAL	vol_spikeFmarket_structureBULLISH_EXPANSIONh1_struct_bullh1_struct_beartrend_maturity   regimeTRENDINGregime_reason regime_near_trendingdeviation_pctg?
divergenceNONEmacd_deceleratingmacd_hist_last        candle_strengthr   hammershooting_starbull_engulfingbear_engulfingdoji	doji_typepiercing
dark_cloudmorning_starevening_starvolume_weakbuy_absorptionsell_absorptionvwapvwap_deviation_pctr   	RIALZISTAr   BUYr   r   
swing_dataswing_foundconsecutive_sl_count	tick_sizeg      ?
tick_valueg      ?r6   )r   getfloatintboollistdict)ds    r   _to_snaprx   E   s5     2uuXu%2AEE'6*+2 155w!7892 mZ8=A>	2
 aee$6=>2 !';T!BC2 !%%t$%2 quuZud);<=2 QUU8T*+2 QUU8T*+2 AEE/4892 k3/02 55x02 quu[%012 13FG2  AEE"2D9:!2" AEE"2E:;#2$ 155!1156%2& uuXz*'2( eeOR0)2* "!%%(>"CD+2, AEE/378-2. 55v./20 quu%8%@A122 QUU#3S9:324 aee$5s;<526 AEE(E*+728 155%8992: AEE"2E:;;2< AEE"2E:;=2> !%%&'?2@ %%$A2B aeeJ./C2D lE23E2F !%%67G2H !%%67I2J }e45K2L AEE"2E:;M2N QUU#4e<=O2P 155w!789Q2R !';S!ABS2T UU6;'U2V %% 3U;W2X quu%7=>Y2Z %%R([2\ l]E,BCD]2^ !'=q!AB_2` k401a2b |T23c2 2r   c                 ^    t        j                  dddddddddd	d	d
      } t        | d      S )NBUONO
FAVOREVOLETK   rk   r   g(\?g      ?ok)step_1_qualita_scontostep_2_timing_pullbackstep_3_contesto_macroapproved
confidence	directionrisk_multipliersl_atr_multiplierrr_multiplierkey_riskreasonr,   )textattempts)jsondumpsr   )bodys    r   _ai_success_payloadr      sE    ::!(")!-! D 4!,,r   c                 T    t        t        j                  t        j                        S )N)modeaccount)r	   r
   PAPERr   
INELIGIBLEr6   r   r   	_make_cfgr      s    ]]K$:$: r   c                 T    d } t        j                   |               t        d       y )Nc                 D	  K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }t        |t              }|sddt        j                         v st        j                  t              rt        j                  t              ndd	t        j                         v st        j                  |      rt        j                  |      nd	d
t        j                         v st        j                  t              rt        j                  t              nd
t        j                  |      dz  }t        t        j                  |            d }|j                   }d}||u }|st        j"                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d x}x}}|j$                  }d }||u}|st        j"                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d x}x}}| j&                  }d}||k(  }|st        j"                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d x}x}}|j(                  }d}||k(  }|st        j"                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d x}x}}y 7 Dw)Nr@         H@r?   rA   r9   rY   	bias_datalast_entry_eval_timez5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}
isinstanceresr   )py0py1py2py4Fisz5%(py2)s
{%(py2)s = %(py0)s.dedup_skipped
} is %(py5)sr   r   py5assert %(py7)spy7is notz4%(py2)s
{%(py2)s = %(py0)s.decision
} is not %(py5)sr,   ==z-%(py2)s
{%(py2)s = %(py0)s.calls
} == %(py5)sai   ՋAz=%(py2)s
{%(py2)s = %(py0)s.evaluated_candle_time
} == %(py5)s)r%   r   r   r   rx   evaluate_entryr#   r   r   @py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanationdedup_skipped_call_reprcomparedecisionr(   evaluated_candle_time)
r   brainr"   r   @py_assert3@py_format5@py_assert1@py_assert4@py_format6@py_format8s
             r   runz*test_tf_first_call_invokes_ai.<locals>.run   s    ,./	R($78((43t93 ) 
 
 #////////z///z//////#///#///////////////////  )E) E)))) E))))))s)))s))) )))E)))))))||'4'|4''''|4''''''s'''s'''|'''4'''''''xx1x1}x1rrx1((8L8(L8888(L888888s888s888(888L8888888
s   AR RQR z:TF: first call invokes AI; evaluated_candle_time populatedasyncior   r   r   s    r   test_tf_first_call_invokes_air      s     9 KKDEr   c                 T    d } t        j                   |               t        d       y )Nc                 .  K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d}||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d }||u }|st        j                  d|fd||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j"                  }d }||u }|st        j                  d|fd||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}| j$                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }t        j&                  d      dz   d|iz  }t        t        j                  |            d x}x}}y 7 9w)Nr@   r   r   r9   r   r   Tr   r   r   r   r   r   z0%(py2)s
{%(py2)s = %(py0)s.decision
} is %(py5)sz=%(py2)s
{%(py2)s = %(py0)s.evaluated_candle_time
} is %(py5)sr   r   r   r   z*AI must NOT be called on same-candle dedup
>assert %(py7)s)r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r   r(   _format_assertmsg	r   r   r"   r   r   r   r   r   r   s	            r   r   z)test_tf_same_candle_skips_ai.<locals>.run   s(    ,./	R($78((43t9< ) 
 
   (D( D(((( D((((((s(((s((( (((D(((((((||#t#|t####|t######s###s###|###t#######((0D0(D0000(D000000s000s000(000D0000000xxJ1Jx1}JJJx1JJJJJJrJJJrJJJxJJJ1JJJJJJJJJJJ
s   ANNL9Nz,TF: same-candle dedup short-circuits AI callr   r   s    r   test_tf_same_candle_skips_air      s!    K KK67r   c                 T    d } t        j                   |               t        d       y )Nc                 z  K   t        t                     } t        t               |       }t	        dddddd      }|j                  d|t        |      d       d {   }|j                  }d}||u }|st        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d }||u }|st        j                  d	|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}| j"                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 7 \w)Ng      9@g      <@RANGING6E)r?   rA   rO   rI   r8   r   r   Tr   r   r   r   r   r   r   r   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z)test_mr_same_candle_skips_ai.<locals>.run   s    ,./	R($#,-6#') * (($#d), ) 
 
   (D( D(((( D((((((s(((s((( (((D(((((((||#t#|t####|t######s###s###|###t#######xx1x1}x1rrx1
s   AJ;J8IJ;z,MR: same-candle dedup short-circuits AI callr   r   s    r   test_mr_same_candle_skips_air      s      KK67r   c                 T    d } t        j                   |               t        d       y)un   Pre-val rejection (e.g. RSI outside zone) must leave
    evaluated_candle_time=None — cache stays untouched.c                   K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d}||u }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d }||u }|st        j                  d|fd||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j"                  }d }||u }|st        j                  d|fd||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}| j$                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 7 #w)Ng     @P@g      N@r   r9   rY   r   Fr   r   r   r   r   r   r   r   r   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z9test_pre_val_rejection_does_not_update_cache.<locals>.run   s    ,./	R($78((43t93 ) 
 
   )E) E)))) E))))))s)))s))) )))E)))))))||#t#|t####|t######s###s###|###t#######((0D0(D0000(D000000s000s000(000D0000000xx1x1}x1rrx1
s   AM?M<L#M?z?pre-val rejection: cache unchanged (evaluated_candle_time None)Nr   r   s    r   ,test_pre_val_rejection_does_not_update_cacher      s     
 KKIJr   c                 T    d } t        j                   |               t        d       y)z~error_kind='unknown' (covers 429/5xx/network/timeout) must leave
    evaluated_candle_time=None so the next iteration retries.c                 6  K   t        t        d dd            } t        t               |       }t	        ddd      }|j                  d|t        |      d	       d {   }|j                  }d }||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d}||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j"                  }d }||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j$                  d      dz   d|iz  }t        t        j                  |            d x}x}}| j&                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 7 9w)Nunknown   r   
error_kindr   r@   r   r   r9   rY   r   r   r   r   r   r   r   Fr   r   z0transient AI failure must NOT consume the candler   r,   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z<test_ai_transient_failure_does_not_update_cache.<locals>.run   ss    )aPQ	R($78((43t93 ) 
 
 ||#t#|t####|t######s###s###|###t#######  )E) E)))) E))))))s)))s))) )))E)))))))(( 	?D 	?(D0 	?->->	?(D 	? 	?8>	? 	?&>&>  	? 	?5>Y  	? 	?5>Y ) 	? 	?5>Y -1 	? 	?->->>	? 	? 	?+>+>	? 	?xx1x1}x1rrx1
s   ANNL9Nz:AI transient (unknown): cache NOT updated, retries enabledNr   r   s    r   /test_ai_transient_failure_does_not_update_cacher      s      KKDEr   c                 T    d } t        j                   |               t        d       y)zzerror_kind='credit' or 'invalid' is permanent -- retrying within
    the same candle just burns budget. Cache must update.c                   K   t        t        d dd            } t        t               |       }t	        ddd      }|j                  d|t        |      d	       d {   }|j                  }d }||u }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 7 w)Ncreditr,   r   r@   r   r   r9   rY   r   r   r   r   r   r   r   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r   s	            r   r   z4test_ai_permanent_failure_updates_cache.<locals>.run
  sC    (QOP	R($78((43t93 ) 
 
 ||#t#|t####|t######s###s###|###t#######((8L8(L8888(L888888s888s888(888L8888888	
s   AG5G2FG5z;AI permanent (credit/invalid): cache UPDATED, no retry loopNr   r   s    r   'test_ai_permanent_failure_updates_cacher     s     9 KKEFr   c                    t        j                         5 } t        t        |       dz        }t	               }d|j
                  j                  d<   d|j
                  j                  d<   |j                  |       |j                         }d }||u}|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }d
d|iz  }t        t        j                   |            d x}}|j"                  }|t$        k(  }|st        j                  d|fd|t$        f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t$              rt        j                  t$              nddz  }	dd|	iz  }
t        t        j                   |
            d x}}|j
                  j                  d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            d x}x}}|j
                  j                  d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                   |
            d x}x}}d d d        t'        d       y # 1 sw Y   xY w)Nz
state.jsonr   r9   g   vՋAr   r   )z%(py0)s is not %(py3)ss2r   py3assert %(py5)sr   r   )z6%(py2)s
{%(py2)s = %(py0)s.schema_version
} == %(py4)sr   )r   r   r   zassert %(py6)spy6)z%(py1)s == %(py4)s)r   r   zEEntryEvalCache survives save/load roundtrip (post-crash dedup intact))tempfileTemporaryDirectoryr   r   r   entry_eval_cache	last_evalsaveloadr   r   r   r   r   r   r   r   schema_versionr   r   )tmpstoresr   @py_assert2r   @py_format4r   r   r   @py_format7@py_assert0s               r   (test_dedup_cache_persists_across_restartr     s%   		$	$	& D#49|34N.:$$U+.:$$T*

1ZZ\r~rrr  2 N2222 N222222r222r222 222222N222N2222222"",,U3C|C3|CCCC3|CCC3CCC|CCCCCCC"",,T2C|C2|CCCC2|CCC2CCC|CCCCCCCD OPD Ds   MM,,M5c                 T    d } t        j                   |               t        d       y)zAis_candle_closed=False -> reject_reason CANDLE_NOT_CLOSED, no AI.c                   K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d }||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d}||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j"                  }d}||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j$                  }d }||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}| j&                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}y 7 w)NFg      ^r=   r>   r9   rY   r   r   r   r   r   r   r   CANDLE_NOT_CLOSEDr   z5%(py2)s
{%(py2)s = %(py0)s.reject_reason
} == %(py5)sr   r   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   reject_reasonr   r   r(   r   s	            r   r   z=test_evaluate_entry_skips_when_candle_not_closed.<locals>.run6  s    ,./	R(U/57 8((43t93 ) 
 
 ||#t#|t####|t######s###s###|###t#######  7$77 $77777 $7777777s777s777 777$77777777  )E) E)))) E))))))s)))s))) )))E)))))))((0D0(D0000(D000000s000s000(000D0000000xx1x1}x1rrx1
s   AQQO+Qz3candle gate: not closed -> CANDLE_NOT_CLOSED, no AINr   r   s    r   0test_evaluate_entry_skips_when_candle_not_closedr   4  s      KK=>r   c                 T    d } t        j                   |               t        d       y)z@age < cfg.candle_close_delay_seconds (5s) -> CANDLE_STABILIZING.c                 v  K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d }||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d}||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}| j"                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}y 7 ]w)NTg       @r   r9   rY   r   r   r   r   r   r   r   CANDLE_STABILIZINGr   r   r   r   r   r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z>test_evaluate_entry_skips_when_candle_stabilizing.<locals>.runI  s    ,./	R(T/24 5((43t93 ) 
 
 ||#t#|t####|t######s###s###|###t#######  8$88 $88888 $8888888s888s888 888$88888888xx1x1}x1rrx1
   AJ9J6IJ9z2candle gate: age < 5s -> CANDLE_STABILIZING, no AINr   r   s    r   1test_evaluate_entry_skips_when_candle_stabilizingr  G  s     
 KK<=r   c                 T    d } t        j                   |               t        d       y)z9age > cfg.candle_max_age_seconds (60s) -> CANDLE_TOO_OLD.c                 v  K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d }||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d}||k(  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}| j"                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}y 7 ]w)NTg     V@r   r9   rY   r   r   r   r   r   r   r   CANDLE_TOO_OLDr   r   r   r   r   r  r   s	            r   r   z:test_evaluate_entry_skips_when_candle_too_old.<locals>.runZ  s    ,./	R(T/35 6((43t93 ) 
 
 ||#t#|t####|t######s###s###|###t#######  4$44 $44444 $4444444s444s444 444$44444444xx1x1}x1rrx1
r  z/candle gate: age > 60s -> CANDLE_TOO_OLD, no AINr   r   s    r   -test_evaluate_entry_skips_when_candle_too_oldr
  X  s     
 KK9:r   c                 T    d } t        j                   |               t        d       y)z>delay <= age <= max_age -> AI is called (no candle gate skip).c                 t  K   t        t                     } t        t               |       }t	        ddd      }|j                  d|t        |      d       d {   }|j                  }d }||u }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}|j                   }d }||u}|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}| j"                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      d
z  }dd|iz  }t        t        j                  |            d x}x}}y 7 \w)NTg      (@r   r9   rY   r   r   )z5%(py2)s
{%(py2)s = %(py0)s.reject_reason
} is %(py5)sr   r   r   r   r   r   r,   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z=test_evaluate_entry_proceeds_when_candle_settled.<locals>.runk  s    ,./	R(T/35 6((43t93 ) 
 
   (D( D(((( D((((((s(((s((( (((D(((((((||'4'|4''''|4''''''s'''s'''|'''4'''''''xx1x1}x1rrx1
s   AJ8J5IJ8z;candle gate: 5 <= age <= 60 -> AI called, decision returnedNr   r   s    r   0test_evaluate_entry_proceeds_when_candle_settledr  i  s     
 KKEFr   c                    ddl m}  d}| |k(  }|st        j                  d|fd| |f      dt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}}t        dddd      }|j                  }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}}|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}}t        dddd      }	|	j                  }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}}|	j                  }d}| }
||
k(  }|st        j                  d|fd||
f      dt	        j
                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}x}}
t        d       y
)zbuild_tech_snapshot derives age from candle_time (bar OPEN) +
    V16_M5_BAR_SECONDS using injected now_utc. Verify the formula
    directly without hitting the full builder by reading from a
    constructed TechSnapshot.r   )V16_M5_BAR_SECONDSi,  r   )z%(py0)s == %(py3)sr  r   r   r   Ni  Tg      @r<   r=   r>   r   )z8%(py2)s
{%(py2)s = %(py0)s.is_candle_closed
} is %(py5)sr"   r   r   r   )z:%(py2)s
{%(py2)s = %(py0)s.candle_age_seconds
} == %(py5)sFg      snap2)z;%(py2)s
{%(py2)s = %(py0)s.candle_age_seconds
} == -%(py5)szassert %(py8)spy8z5candle age: open-time convention, bar duration = 300s)analysis.tech_snapshotr  r   r   r   r   r   r   r   r   rx   r=   r>   r   )r  r   r   r   r   r"   r   r   r   r  @py_assert6r   @py_format9s                r   0test_candle_age_calculation_open_time_conventionr  z  sr   
 :!$$$$$$$$$$$$$$$$$$$$$$$$$ D)-+.0 1D   (D( D(((( D((((((4(((4((( (((D((((((("")c)"c))))"c))))))4)))4)))")))c))))))) T*/,02 3E !!*U*!U****!U******5***5***!***U*******##++t+#t++++#t++++++5+++5+++#++++++++++?@r   c                 T    d } t        j                   |               t        d       y)zoFail-closed policy: candle_time=0 (data unavailable) must NOT
    permit AI calls. Skip with CANDLE_NOT_CLOSED.c                   K   t        t                     } t        t               |       }t	        dddd      }|j                  d|t        |      d       d {   }|j                  }d}||k(  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}| j                   }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}y 7 w)Nr   FrY   r  r9   r   r   r   r   r   r   r   r   r   r   )r%   r   r   r   rx   r   r#   r   r   r   r   r   r   r   r   r   r(   r   s	            r   r   z8test_candle_time_zero_treated_as_not_closed.<locals>.run  sK    ,./	R(-2/24 5 ((43t93 ) 
 
   7$77 $77777 $7777777s777s777 777$77777777xx1x1}x1rrx1	
s   AG3G0FG3z0candle gate: candle_time=0 -> fail closed, no AINr   r   s    r   +test_candle_time_zero_treated_as_not_closedr    s     
 KK:;r   c                 4   t        d       t                t                t                t	                t                t                t                t                t                t                t                t                t                t        d       y)Nztest_entry_dedup.pyzALL 13 TESTS PASSEDr   )r   r   r   r   r   r   r   r   r   r  r
  r  r  r  r6   r   r   mainr    se    	
 !# " "0235+-,.4657134646/1	
 r   __main__)r   strr1   r2   )rw   rv   r1   r   )r1   r   )r1   r	   )r1   rs   )>__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   r   r   sysr   pathlibr   pathinsertr  __file__resolveparentr  r   brain.ai_clientr   brain.brain_mrr   brain.brain_tfr   core.configr	   r
   r   core.contractsr   persistence.state_storer   r   r   r   r   r#   r%   rx   r   r   r   r   r   r   r   r   r   r   r  r
  r  r  r  r  r3   exitr6   r   r   <module>r2     s   #     
   3tH~--/66==> ? / & " " ; ; * 
$ 8v-"F$8*8.K,F0G(Q2?&>";"G"A0<,& zCHHTV r   