
    NjI                   *   U 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mZmZ ddlmZ ddlmZ ddlZej,                  j/                  d e ee      j5                         j6                  j6                               ddlmZ ddlmZ dd	l m!Z!m"Z"m#Z# dd
l$m%Z%m&Z&m'Z' ddl(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/m0Z0 ddl1m2Z2 ddl3m4Z4m5Z5m6Z6 ddl7m8Z8 ddl9m:Z: ddl;m<Z< d]dZ= G d d      Z> G d d      Z? G d d      Z@ G d d      ZA G d d      ZB G d d      ZCd^dZDd_dZEd`d ZFdadbd"ZG	 	 dc	 ddd#ZH ed$d%d&d'ddej                  (      ZJd)eKd*<   ddddddddd+d,d-
d.ZL G d/ d0      ZMdedfd1ZNdgd2ZOd3 ZPd4 ZQd5 ZRd6 ZSd7 ZTd8 ZUd9 ZVd: ZWd; ZXd< ZYd= ZZd> Z[d? Z\d@ Z]dA Z^dB Z_dC Z`dD Zad!dEdhdFZbdG ZcdH ZddI ZedJ ZfdK ZgdL ZhdM ZidN ZjdO ZkdP ZldQ ZmdR ZndS ZodT ZpdU ZqdV ZrdW ZsdX ZtdY ZudZ Zvdid[Zwexd\k(  r ej                   ew              yy)ju  
Phase B end-to-end tests for orchestrator + lifecycle wireup.

Covers:
  - Entry path: brain -> sizing -> risk -> opener -> state mutation
  - Exit path: brain.manage_exit -> closer -> risk hooks -> state cleanup
  - PARTIAL_50 with set_be_after_partial: closer.partial_close + modify_stop
  - MOVE_SL: broker.modify_stop + runtime.current_sl_price
  - HOLD: candle dedup persistence
  - State persistence across iterations

Pattern: FakeBroker (duck-typed, no BrokerBase subclass — orchestrator
duck-types its broker), FakeMarketDataProvider with canned bars,
FakeBrain (canned EntryDecision + manage_exit BrainDecision).

Run:
    cd ~/apex_v16
    python -m tests.test_orchestrator_phase_b
    )annotationsN)datetimetimezone	timedelta)Path)AnyTechSnapshot
AIResponse)ClosedTradeOrderResultPosition)RuntimeConfigRunModeAccountKind)BrainContextBrainDecisionEntryDecisionEntryEvalResultTradeAction
TradeEntryTradeRuntimeutc_now)Orchestrator)ActiveTradeSessionState
StateStore)RiskManager)TradeCloser)TradeOpenerc                     t        d|         y )Nz  ok  )print)labels    6/home/work/apex_v16/tests/test_orchestrator_phase_b.py_okr&   2   s    	F5'
    c                      e Zd ZdddZd Zy)FakeProviderNc                    |xs i | _         y N)bars)selfr,   s     r%   __init__zFakeProvider.__init__;   s    JB	r'   c                   K   | j                   j                  ||f      }||j                  rt        j                  g d      S |j                  |      j                  d      S w)N)openhighlowclosevolume)columnsT)drop)r,   getemptypd	DataFrametailreset_index)r-   symbol	timeframendfs        r%   get_barszFakeProvider.get_bars>   sT     YY]]FI./:<<(RSSwwqz%%4%00s   A%A'r+   returnNone)__name__
__module____qualname__r.   rA    r'   r%   r)   r)   :   s    1r'   r)   c                      e Zd ZddZddZy)FakeAINc                $   K   t        d d      S wNunknown)text
error_kindr   )r-   prompttemperature
max_tokenss       r%   askz
FakeAI.askF        t	::   c                $   K   t        d d      S wrL   r   )r-   rP   rR   wheres       r%   ask_for_decisionzFakeAI.ask_for_decisionI   rT   rU   )g?N)iX  N)rE   rF   rG   rS   rX   rH   r'   r%   rJ   rJ   E   s    ;;r'   rJ   c                  (    e Zd ZddZddddZd Zy)	FakeBrainNc                Z    || _         |xs t        dd      | _        d| _        d| _        y )NHOLDdefaultactionreasonr   )entry_decisionr   exit_decisionevaluate_callsmanage_calls)r-   ra   rb   s      r%   r.   zFakeBrain.__init__N   s/    ,*\m6R[.\r'           	bias_datalast_entry_eval_timec                  K   | xj                   dz  c_         t        t        |dd      xs d      xs d }t        | j                  |      S wN   candle_timer   )decisionevaluated_candle_time)rc   floatgetattrr   ra   )r-   r=   techrg   rh   cts         r%   evaluate_entryzFakeBrain.evaluate_entryT   sM     q 7427a8@D(("$
 	
s   AAc                L   K   | xj                   dz  c_         | j                  S wNrk   )rd   rb   )r-   ctxs     r%   manage_exitzFakeBrain.manage_exit\   s#     Q!!!s   "$)NN)rE   rF   rG   r.   rs   rw   rH   r'   r%   rZ   rZ   M   s     ?CY\ 
"r'   rZ   c                  H    e Zd ZdZddZddZd ZddZd Zd Z	dd	Z
d
 Zy)
FakeBrokerz5Duck-typed broker for live-path tests. Records calls.c                    d| _         g | _        g | _        g | _        g | _        g | _        d| _        d| _        d| _        d| _	        d| _
        g | _        y )Nr   )E1S1T1)CLOSE2S2T2F)positions_get_callsplace_callsclose_callsmodify_stop_callscancel_all_calls partial_close_via_opposite_callsnext_order_idsnext_partial_idsfail_modify_stopfail_close_positionfail_partial_close_via_oppositepositions_to_returnr-   s    r%   r.   zFakeBroker.__init__c   sd    #$ !# "02-0 6 %#( /4, *, r'   Nc                ^   K   | xj                   dz  c_         t        | j                        S wru   )r   listr   r-   r=   s     r%   positions_getzFakeBroker.positions_gets   s(       A% D,,--s   +-c           	        K   | j                   j                  |||||d       | j                  \  }}}	t        dd|||||	      S w)N)r=   	direction	contractssl_pricetp_priceT     @)successentry_pricer   r   entry_idstop_id	target_id)r   appendr   r   )
r-   r=   r   r   r   r   kwargsests
             r%   place_market_bracketzFakeBroker.place_market_bracketw   s[     99 h!
 	 %%1afx1
 	
   AAc                   K   | j                   j                  ||d       | j                  rt        d      t	        d      S w)N)r=   r   zbroker rejected close_positionT)r   )r   r   r   RuntimeErrorr   )r-   r=   r   s      r%   close_positionzFakeBroker.close_position   s?     6	 JK##?@@4((s   AAc	                   K   | j                   j                  ||||||||d       | j                  rt        dd      S | j                  \  }	}
}t        d|	|
|||      S w)N)r=   r   contracts_to_closeresidual_contractsnew_sl_pricenew_tp_priceold_stop_order_idold_target_order_idFzsimulated 400 partial reject)r   errorT)r   r   r   r   r   r   )r   r   r   r   r   )r-   r=   r   r   r   r   r   r   r   close_idr   r   s               r%    partial_close_via_opposite_orderz+FakeBroker.partial_close_via_opposite_order   s|      	--449"4"4(,!2#66
 	 //u4RSS..!Q8Q!!L
 	
s   AA!c                B   K   | j                   j                  |       ywNr   )r   r   r   s     r%   cancel_all_for_symbolz FakeBroker.cancel_all_for_symbol   s     $$V,s   c                6   K   t        t        | dg             S w)Nrecent_trades_to_return)r   rp   )r-   r=   sincelimits       r%   recent_tradeszFakeBroker.recent_trades   s     GD";R@AAs   c                   K   | j                   j                  |||d       | j                  rt        d      t	        d|      S w)N)r=   order_idr   zbroker rejected modify_stopT)r   r   )r   r   r   r   r   )r-   r=   r   r   s       r%   modify_stopzFakeBroker.modify_stop   sJ     %%(L'
 	   <==4,??r   rB   r+   )NN2   )rE   rF   rG   __doc__r.   r   r   r   r   r   r   r   rH   r'   r%   ry   ry   a   s/    ?, .

)
(B@r'   ry   c                      e Zd Zd Zd Zy)	JsonlSinkc                    g | _         y r+   )eventsr   s    r%   r.   zJsonlSink.__init__   s	    r'   c                B    | j                   j                  d|i|       y )Nevent)r   r   r-   r   fieldss      r%   writezJsonlSink.write   s    GU5f56r'   N)rE   rF   rG   r.   r   rH   r'   r%   r   r      s    7r'   r   c                  *    e Zd Zd Zd Zd Zd Zd Zy)
FakeLoggerc                    dd l }t               | _        t               | _        t               | _        |j                  d      | _        y )Nr   ztest.orch_b)loggingr   	brain_logsession_log	error_log	getLoggersystem)r-   r   s     r%   r.   zFakeLogger.__init__   s3    "$;"''6r'   c                >     | j                   j                  |fi | y r+   )r   r   r   s      r%   log_session_eventzFakeLogger.log_session_event   s    u//r'   c                B     | j                   j                  d||d| y )N)rW   r   )r   )r   r   )r-   rW   r   extras       r%   	log_errorzFakeLogger.log_error   s     HEH%Hr'   c                <     | j                   j                  di | y )N)trade_openedr   r   r-   r   s     r%   log_trade_openedzFakeLogger.log_trade_opened       6v6r'   c                <     | j                   j                  di | y )N)trade_closedr   r   s     r%   log_trade_closedzFakeLogger.log_trade_closed   r   r'   N)rE   rF   rG   r.   r   r   r   r   rH   r'   r%   r   r      s    70I77r'   r   c                     t        t        j                  t        j                        }dg|_        d|_        d|_        d|_        d|_	        | j                         D ]  \  }}t        |||        |S )N)modeaccountMESr   re   )r   r   PAPERr   
INELIGIBLEasset_filterloop_sleep_secondsscan_loop_phase_offset_secondsmanage_loop_interval_seconds!maintenance_loop_interval_secondsitemssetattr)overcfgkvs       r%   make_configr      sn    
W]]K4J4J
KCwCC
 *-C&'(C$,-C)

 1QJr'   c                     t               S r+   )r   rH   r'   r%   
make_stater      s
    >r'   c                    t        | dz        S )Nz
state.json)r   )tmps    r%   
make_storer      s    cL())r'   BUYc           	     B    t        | d| dk(  rdnd| dk(  rdndddd	
      S )Nr   r        @     @     @     @      ?P   testr   r   r   r   rr_multiplier
confidence	rationale)r   r   s    r%   make_entry_decisionr     s5    $-6$-6 r'   c           	         t        d'i d| d|d|d|ddd|dk(  rd	nd
d|dk(  rdnddt               t        d      z
  ddddddddddddddddd d!d"|d#d$d%d&S )(Nr=   
brain_namer   r   r   r   r   r   r   r   r   r   r   	opened_at
   minutesrsi_m5_at_entryg     K@rsi_h1_at_entryg      M@rsi_h4_at_entryg      N@atr_ratio_at_entry      ?market_structure_at_entryBULLISH_EXPANSIONregime_at_entryTRENDINGh1_compat_at_entryconfidence_at_entryK   entry_order_idr{   stop_order_idtarget_order_idr}   is_paperTrH   )r   r   r   )r=   brainr   r   r   s        r%   make_trade_entryr     s     

"'
3<
HQ

/8E/AVv
 %-6
 )i33	

 

 /3

 EI
 
 ;N
 #
 8;
 QS
 
 ,3
 EI
 
 
r'              tzinfor   FIXED_DAY_UTCTrk   )
r  stater   openercloserbrokerriskstorer  max_iterationsc        
        :   |xs
 t               }||n	t               }t               }
|xs t        |||
      }|xs t	        |||
      }|xs t        |||
      }| r| | dni }t        |t               t               ||xs
 t               |
|||||d |	      |
fS )Nr&  r  loggerr,  TFMRc                     t         S r+   r"  rH   r'   r%   <lambda>z$make_orch_for_test.<locals>.<lambda>       r'   config	ai_clientmarket_data_providerr#  r(  r,  brain_dispatchr$  r%  risk_managerr&  now_utc_providerr)  )
r   r   r   r!   r    r   r   rJ   r)   _MemoryStore)r  r#  r   r$  r%  r&  r'  r(  r  r)  r,  brainss               r%   make_orch_for_testr>    s     
C&EJLE\FS{&8FSFS{&8FSF9;sE&9D+0E'bFfh\^52LN6f4.%  r'   c                      e Zd ZddZd Zy)r<  c                     d| _         d | _        y r   )saveslastr   s    r%   r.   z_MemoryStore.__init__%  s    
	r'   c                X    | xj                   dz  c_         |j                         | _        y ru   )rA  to_dictrB  )r-   r#  s     r%   savez_MemoryStore.save(  s    

a
MMO	r'   NrB   )rE   rF   rG   r.   rE  rH   r'   r%   r<  r<  $  s    $r'   r<  c                .   t        dAi d| d|d|d|ddddd	d
dddd
dd
ddddddddddddddddddddd g d!d"d#d$d%dd&d"d'dd(dd)dd*dd+dd,dd-d d.dd/dd0dd1dd2dd3dd4dd5|d6d"d7d8d9d:d;dd<dd=d>d?d@S )BNr=   pricer0   rl   is_candle_closedTcandle_age_secondsg      $@rsi      I@rsi_prevg      H@rsi_h1rsi_h4atr_m5_points	atr_ratior  
vol_regimeNORMAL	vol_spikeFmarket_structurer  h1_struct_bullh1_struct_beartrend_maturityr  regimer  regime_reason regime_near_trendingdeviation_pctre   
divergenceNONEmacd_deceleratingmacd_hist_lastcandle_strength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_pctbias	RIALZISTAallowed_directionr   h1_compatibility	h1_reason	tick_sizeg      ?
tick_valueg      ?rH   r	   )r=   rG  rl   s      r%   _synthetic_techrx  0  s    ").<G26    )- 6: 	 '*	
 
 (- -  -2 CD  *, CE  '- AF    %* ;@  $) 59 DI  (- <A   !" #$ %$ (+%& '& -2'( )( )+)* +* $(+ r'   c                :    xs
 t               fd}|| _        y)z8Monkey-patch _build_tech to return a known TechSnapshot.c                   K   S wr+   rH   )r=   r   rq   s     r%   _btzpatch_tech.<locals>._btM  s     s   N)rx  _build_tech)orchrq   r{  s    ` r%   
patch_techr~  J  s    $?$DDr'   c            	        t        t                     } t        |       \  }}t        |       t	        j
                  |j                                d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      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}}|j                  j                  d   }	|	j                   }
|
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                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}
x}x}}|	j                   }
|
j$                  }d}||kD  }|st        j                  d|fd||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}
x}x}}|	j&                  }
|
j(                  }|	j                   }|j*                  }||k(  }|s,t        j                  d|fd||f      dt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |
      t        j                  |      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}x}x}}|j                  }
|
j.                  }|j0                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}x}}|j                  }
|
j.                  }|j2                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}x}x}}d |j4                  j6                  D        }
t9        |
      }|sddt        j                         v st        j                  t8              rt        j                  t8              ndt        j                  |
      t        j                  |      d z  }t        t        j                  |            d x}
}t;        d!       y )"Nra   r  r   inzP%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.state
}.active_trades
}r}  py1py3py5py7assert %(py9)spy9r   ==)zL%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.direction
} == %(py7)satpy0py2py4r  r   >)zK%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.contracts
} > %(py7)s)z%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.runtime
}.current_sl_price
} == %(py10)s
{%(py10)s = %(py8)s
{%(py8)s = %(py6)s.entry
}.sl_price
})r  r  r  py6py8py10zSL baseline must be entry.sl
>assert %(py12)spy12rk   zl%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.executed_count
} == %(py9)sr  r  r  r  r  assert %(py11)spy11zl%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.approved_count
} == %(py9)sc              3  ,   K   | ]  }|d    dk(    yw)r   r   NrH   .0r   s     r%   	<genexpr>z=test_entry_happy_path_creates_active_trade.<locals>.<genexpr>c  s     Mqz^+M   ,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr  r  r  zIentry happy path: active_trade created, counters incremented, log emitted)rZ   r  r>  r~  asynciorunr#  active_trades
@pytest_ar_call_reprcompare	_saferepr@py_builtinslocals_should_repr_global_nameAssertionError_format_explanationentryr   r   runtimecurrent_sl_pricer   _format_assertmsgdailyexecuted_countapproved_countr   r   r  r&   )r  r}  r,  @py_assert0@py_assert4@py_assert6@py_assert2@py_format8@py_format10r  @py_assert1@py_assert3@py_assert5@py_assert7@py_assert9@py_format11@py_format13@py_assert8@py_format12@py_format5s                       r%   *test_entry_happy_path_creates_active_trader  V  s\   %8%:;E%E2LD&tKK
,DJJ,J,,,5,,,,,5,,,,5,,,,,,D,,,D,,,J,,,,,,,,,,,		!	!%	(B88&8&&&&&&&&&&&&2&&&2&&&8&&&&&&&&&&&&&88!8!!!!!!!!!!!!2!!!2!!!8!!!!!!!!!!!!!::[:&&["(([(*;*;[&*;;[[[&*;[[[[[[2[[[2[[[:[[[&[[[[[["[[["[[[([[[*;[[[=[[[[[[[[::/:/**/a/*a////*a//////4///4///://////*///a///////::/:/**/a/*a////*a//////4///4///://////*///a///////MV5E5E5L5LMM3MMMMMMMMM3MMM3MMMMMMMMMMMMMMSTr'   c                    t               } t        |       }t        |      \  }}t               }t	        j
                  |dddddddd	d
dddd
      }t        ||       t        j                  |j                                |j                  j                  D cg c]  }|d   dk(  s| }}t        |      }d}||k(  }	|	st        j                  d|	fd||f      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t        j"                  |      t        j"                  |      dz  }
dd|
iz  }t%        t        j&                  |            dx}x}	}|d   }|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}||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}||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}||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}||u }|slt        j                  d#|fd$||f      t        j"                  |      t        j"                  |      dz  }dd|iz  }
t%        t        j&                  |
            dx}x}}|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	}||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
}||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}||u }|slt        j                  d#|fd$||f      t        j"                  |      t        j"                  |      dz  }dd|iz  }
t%        t        j&                  |
            dx}x}}|d*   }d}||u }|slt        j                  d#|fd$||f      t        j"                  |      t        j"                  |      dz  }dd|iz  }
t%        t        j&                  |
            dx}x}}|d+   }d}||u }|slt        j                  d#|fd$||f      t        j"                  |      t        j"                  |      dz  }dd|iz  }
t%        t        j&                  |
            dx}x}}|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}||u }|slt        j                  d#|fd$||f      t        j"                  |      t        j"                  |      dz  }dd|iz  }
t%        t        j&                  |
            dx}x}}t)        d.       yc c}w )/a  V18 dashboard radar: entry_approved must embed rsi_m5/rsi_h4/
    h1_compat/macd_*/pattern/atr_ratio/bias/h1_struct_*/volume_weak/
    divergence/bouncing_rsi from the TechSnapshot at decision time, so
    the web UI can render RADAR without secondary lookups.r  r  gfffff&K@gI@gYM@gq=
ףp?g<+i7T?Tgffffff?BULLISHF)rJ  rL  rN  rt  r`  r_  rb  rP  rq  rU  rV  rl  r]  rq   r   entry_approvedrk   r  z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenapprovedr  r  r  r  assert %(py8)sr  Nr   rsi_m5z%(py1)s == %(py4)sr  r  assert %(py6)sr  rN  	h1_compat	macd_hist
macd_decelisz%(py1)s is %(py4)spatternHAMMERrP  rq  rU  rV  rl  r]  bouncing_rsiz3entry_approved carries full RADAR tech fields (V18))r  rZ   r>  rx  dataclassesreplacer~  r  r  r   r   r  r  r  r  r  r  r  r  r  r&   )rm   r  r}  r,  custom_techr   r  r  r  r  @py_format7@py_format9evr  r  r  s                   r%   -test_entry_approved_carries_radar_tech_fieldsr  g  sa   
 #$HX.E%E2LD&!#K%%4EK t+&KK
!++22UaajDT6TUHUx=A=A=A33xx=A	!Bh<4<4<4<4h<4<4<4<4k?"d"?d""""?d"""?"""d"""""""k?&h&?h&&&&?h&&&?&&&h&&&&&&&l#t#t####t######t#######i=$H$=H$$$$=H$$$=$$$H$$$$$$$k?"d"?d""""?d"""?"""d"""""""f:"":"""":""":""""""""""'4'4''''4''''''4'''''''(5(5((((5((((((5(((((((m%%%%%%%%%%%%%%%%%%%l(y(y((((y((((((y(((((((n%%%%%%%%%%%%%%%%%%%=>! Vs   a)ac                 ,   t               } | j                  j                  dddddddddd	d

       t        |       }t	        |      \  }}t        |       t        j                  |j                                |j                  j                  D cg c]  }|d   dk(  s| }}t        |      }d}||k(  }|st        j                  d|fd||f      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t        j                   |      t        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}x}}|d   }|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 }||k(  }|slt        j                  d|fd||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}|d!   }| j&                  }||k(  }|st        j                  d|fd"||f      t        j                   |      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}x}}|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}||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}||u }|slt        j                  d*|fd+||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}|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}||k(  }|slt        j                  d|fd||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}t)        d.       yc c}w )/u  
    BUG 7 fix: brain_log must contain an entry_approved event after a
    Brain decision is accepted (symmetric to entry_rejected). Pre-fix:
    only rejections were logged on brain_log; successful approvals were
    only visible on trade_log -> incomplete decision flow trace.

    TP variante γ: brain emits tp_price=0.0 sentinel + rr_multiplier;
    entry_approved logs tp_price_pre_resolve and rr_multiplier_ai. The
    finalized tp_price lives in the separate tp_resolved event.
    333333?F   US500r  r   BUONO
FAVOREVOLEzmacro headwind)
sl_atr_multiplierclamp_activesl_ticks_usedprofile_originrisk_multiplierrr_multiplier_aistep_1step_2step_3key_riskr  r  r   r  rk   r  r  r  r  r  r  r  Nr   r=   r   r  r  r  r  r   r   r   )z2%(py1)s == %(py5)s
{%(py5)s = %(py3)s.confidence
}rm   r  r  r  assert %(py7)sr  r  r  r  r  r  r  r  z?entry_approved emitted on brain_log with AI/profile diagnostics)r  metadataupdaterZ   r>  r~  r  r  r   r   r  r  r  r  r  r  r  r  r  r   r&   )rm   r  r}  r,  r   r  r  r  r  r  r  r  r  r  r  @py_format6r  s                    r%   .test_entry_approved_event_emitted_in_brain_logr    s    #$H"! $  X.E%E2LD&tKK
!++22UaajDT6TUHUx=A=A=A33xx=A	!Bh< 5 <5    <5   <   5       k?#e#?e####?e###?###e#######l2x222222222222222222x222x22222222222!"+e+"e++++"e+++"+++e+++++++ !)T)!T))))!T)))!)))T)))))))n&&&&&&&&&&&&&&&&&&&*7*7****7******7*******h<"7"<7""""<7"""<"""7"""""""IJ Vs   X Xc                    t               } | j                  j                  dddddd       t        |       }t	        |      \  }}t        |       t        j                  |j                                |j                  j                  D cg c]  }|d	   d
k(  s| }}t        |      }d}||k(  }|st        j                  d|fd||f      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t        j                   |      t        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}x}}|d   }|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}||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}||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}||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}||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}||kD  }|slt        j                  d#|fd$||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}|d%   }d}||kD  }|slt        j                  d#|fd$||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}|d&   }|d'   }||kD  }|slt        j                  d#|fd$||f      t        j                   |      t        j                   |      dz  }dd|iz  }	t#        t        j$                  |	            dx}x}}t'        d(       yc c}w ))u   
    TP variante γ: orchestrator finalizes tp_price post-sizing via
    tp_resolver.resolve_tp_price(...) and emits a tp_resolved event on
    brain_log with the dollar-domain values + rr_effective. Lifecycle
    event, separate from entry_approved.
    r  Fr  r  r   )r  r  r  r  r  r  r  r   tp_resolvedrk   r  r  r  resolvedr  r  r  Nr   r=   r   r  r  r  r  r   r   r  rr_multiplier_usedtp_ticks_final>=)z%(py1)s >= %(py4)ssl_usd_actualr  )z%(py1)s > %(py4)stp_usd_targetr   r   zDtp_resolved emitted on brain_log post-sizing with rr/usd diagnostics)r  r  r  rZ   r>  r~  r  r  r   r   r  r  r  r  r  r  r  r  r  r&   )rm   r  r}  r,  r   r  r  r  r  r  r  r  r  r  r  s                  r%   *test_tp_resolved_event_emitted_post_sizingr    s    #$H"!   X.E%E2LD&tKK
!++22RaajM6QRHRx=A=A=A33xx=A	!Bh< 5 <5    <5   <   5       k?#e#?e####?e###?###e####### !)T)!T))))!T)))!)))T)))))))"#+t+#t++++#t+++#+++t+++++++$1$1$$$$1$$$$$$1$$$$$$$o"""""""""""""""""""o"""""""""""""""""""j>-B}-->----->---->-----------NO Ss   WWc                    t        d       } t        |       \  }}t        |       t        j                  |j	                                |j
                  }|j                  }i }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}}|j
                  }|j                  }|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                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d x}x}x}x}
}	t#        d       y )Nr  r  r  zP%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.active_trades
} == %(py7)sr}  r  r  r  r   r  r  r  r  z,entry: brain returns None -> no state change)rZ   r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r  r  r&   )r  r}  _r  r  r  r  r  r  r  r  r  s               r%   %test_entry_brain_none_no_state_changer
    sa   T*E u-GD!tKK
::):##)r)#r))))#r))))))4)))4))):)))#)))r)))))))::/:/**/a/*a////*a//////4///4///://////*///a///////67r'   c            	        t               } d| j                  _        t        ddddddd	      }t	        |
      }t        ||       \  }}t        |       t        j                  |j                                |j                  }|j                  }i }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      t        j                   |      t        j                   |      dz  }	t        j"                  d      dz   d|	iz  }
t%        t        j&                  |
            dx}x}x}}|j                  }|j                  }|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                   |      t        j                   |      t        j                   |      dz  }
dd|
iz  }t%        t        j&                  |            dx}x}x}x}}|j*                  j,                  D cg c]  }|d   dk(  s| }}d |D        }t/        |      }|st        j"                  |      dz   dt        j                         v st        j                  t.              rt        j                   t.              ndt        j                   |      t        j                   |      dz  }t%        t        j&                  |            dx}}t1        d        yc c}w )!uf  sizing skip -> SIZING_SKIP rule -> no opener call.

    Con worst-case sizing su MES (sl_ticks→MAX_SL_TICKS=40, sl_usd=$50/ct)
    e MAX_CONTRACTS_PER_TRADE[MES]=6, real_risk=$300. Spingiamo daily_pnl
    vicino al hard_stop in modo che il daily budget cap (0.33 × remaining)
    scenda sotto $300 → trigger risk_exceeds_daily_budget dentro sizing.
    g     r   r   r   r   r   r   zsizing-skip targetr   r  r  r#  r  r  r}  r  zno trade must be opened
>assert %(py9)sr  Nrk   r  )zl%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.rejected_count
} >= %(py9)sr  r  r  r   scan_skip_riskc              3  D   K   | ]  }|j                  d       dk(    yw)ruleSIZING_SKIPNr7   r  s     r%   r  z6test_entry_skipped_when_sizing_skip.<locals>.<genexpr>  s     B!quuV}-Bs    .
>assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}r  r  z8entry: huge SL -> SIZING_SKIP propagated, no opener call)r   r  	daily_pnlr   rZ   r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r  rejected_countr   r   r  r&   )r#  rm   r  r}  r,  r  r  r  r  r  r  r  r  r  r   
risk_skipsr  s                    r%   #test_entry_skipped_when_sizing_skipr    s=    LE#EKKVfvr5IKH X.E%E?LD&tKK
::D:##DrD#r)DDD#rDDDDDD4DDD4DDD:DDD#DDDrDDD+DDDDDDDD::/:/**/a/*a////*a//////4///4///://////*///a///////#--44W'
FV8V!WJWBzBN3BBNBNNJNNNNNN3NNN3NNNBNNNBNNNNNNBC Xs   N*Nc                 H   t               } d| _        d| _        t        t	                     }t        ||       \  }}t        |       t        j                  |j                                |j                  }|j                  }i }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                   |      ndt        j                   |      t        j                   |      t        j                   |      dz  }d	d
|iz  }	t#        t        j$                  |	            d x}x}x}}d |j&                  j(                  D        }t+        |      }|sddt        j                         v st        j                  t*              rt        j                   t*              ndt        j                   |      t        j                   |      dz  }
t#        t        j$                  |
            d x}}t-        d       y )NTmanualr  r  r  r  r}  r  r  r  c              3  X   K   | ]"  }|d    dk(  xr |j                  d      dk(   $ yw)r   r  r  HALTEDNr  r  s     r%   r  z6test_entry_skipped_when_risk_halted.<locals>.<genexpr>  s7       	
'
&&D155=H+DDs   (*r  r  r  z-entry: state.halted -> HALTED rule, no opener)r   haltedhalt_reasonrZ   r  r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r   r   r  r&   )r#  r  r}  r,  r  r  r  r  r  r  r  s              r%   #test_entry_skipped_when_risk_haltedr    s   LEEL E%8%:;E%E?LD&tKK
::):##)r)#r))))#r))))))4)))4))):)))#)))r)))))))!!(( 3                         78r'   c                    t               } d| j                  _        t        d      }t	        t                     }t        || |      \  }}t        |       t        j                  |j                                |j                  }|j                  }i }||k(  }|st        j                  d|fd||f      dt        j                         v st        j                   |      rt        j"                  |      ndt        j"                  |      t        j"                  |      t        j"                  |      dz  }	d	d
|	iz  }
t%        t        j&                  |
            dx}x}x}}|j(                  j*                  D cg c]  }|d   dk(  s| }}|s{t        j,                  d      dz   ddt        j                         v st        j                   |      rt        j"                  |      ndiz  }t%        t        j&                  |            |d   }|j.                  }d} ||      }d}||v }|st        j                  d|fd||f      t        j"                  |      t        j"                  |      t        j"                  |      t        j"                  |      t        j"                  |      dz  }t        j,                  |d         dz   d|iz  }t%        t        j&                  |            dx}x}x}x}x}}t1        d       yc c}w )u  
    With daily_pnl <= hard_stop, the daily remaining budget is 0 ->
    sizing.size_for_entry skips with reason 'budget_hard' -> the SIZING_SKIP
    gate (rule #1, ordering by design) fires BEFORE the DAILY_LOSS_HARD_STOP_HIT
    gate. The DAILY_LOSS_HARD_STOP_HIT rule is reachable only when sizing
    somehow succeeds (inconsistent config).

    Either way, the entry is blocked and no trade opens — what the test asserts.
    g     p)daily_loss_hard_stopr  )r  r#  r   r  r  r}  r  r  r  Nr   r  z*expected at least one scan_skip_risk eventz
>assert %(py0)sr  r  r  )r  DAILY_LOSS_HARD_STOP_HITr  )zJ%(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.get
}(%(py5)s)
} in %(py10)s)r  r  r  r  r  r  r  zIentry: daily_pnl <= hard_stop -> entry blocked (SIZING_SKIP or HARD_STOP))r   r  r  r   rZ   r  r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r   r   r  r7   r&   )r#  r   r  r}  r,  r  r  r  r  r  r  r   r  @py_format1r  r  r  r  r  r  r  s                        r%   0test_entry_skipped_when_daily_loss_hard_stop_hitr$  #  sQ    LE#EKK
7
3C%8%:;E%ECHLD&tKK
::):##)r)#r))))#r))))))4)))4))):)))#)))r)))))))#--44W'
FV8V!WJWCCCCCCCCC:CCC:CCCCCb> > f f%  * % *  % *  I   I   I %  I &  I*  z"~     ST Xs   =L
L
c                    t        t                     } t        |       \  }}t        |       t	        j
                  |j                                |j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }	t        t        j                   |	            d x}x}x}x}}|j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }	t        t        j                   |	            d x}x}x}x}}|j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd	|iz  }	t        t        j                   |	            d x}x}x}x}}t'        d       y )Nr  r  rk   r  r  r}  r  r  r  r  r   )zl%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.rejected_count
} == %(py9)sz/entry: approved + executed counters incremented)rZ   r  r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r  r  r  r&   )
r  r}  r	  r  r  r  r  r  r  r  s
             r%   test_entry_increments_countersr&  B  s   %8%:;E u-GD!tKK
::/:/**/a/*a////*a//////4///4///://////*///a///////::/:/**/a/*a////*a//////4///4///://////*///a///////::/:/**/a/*a////*a//////4///4///://////*///a///////9:r'   c                 r   t        t                     } t               }t               }t	               } G d d      }t        |t               t               |t               || | d |       t        dd      t        ||      d d	
      }t        |       t        j                  |j                                |j                  }i }||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.                  }|j0                  }d}||k(  }|st        j                   d|fd||f      dt#        j$                         v st        j&                  |      rt        j(                  |      ndt        j(                  |      t        j(                  |      t        j(                  |      dz  }
dd|
iz  }t+        t        j,                  |            dx}x}x}}d |j2                  j4                  D        }t7        |      }|sddt#        j$                         v st        j&                  t6              rt        j(                  t6              ndt        j(                  |      t        j(                  |      dz  }t+        t        j,                  |            dx}}t9        d       y)z0Opener returns success=False -> state untouched.r  c                      e Zd Zd Zy)Atest_entry_opener_failure_no_state_mutation.<locals>.BrokenOpenerc                ,   K   ddl m}  |ddd      S w)Nr   )TradeOpenResultFboomconfig_error)r   r   rO   )trading.trade_openerr+  )r-   r   r+  s      r%   
open_tradezLtest_entry_opener_failure_no_state_mutation.<locals>.BrokenOpener.open_tradeY  s     <"5>ZZr  N)rE   rF   rG   r/  rH   r'   r%   BrokenOpenerr)  X  s    	[r'   r0  r.  NTr&  r  c                     t         S r+   r2  rH   r'   r%   r3  z=test_entry_opener_failure_no_state_mutation.<locals>.<lambda>b  r4  r'   rk   )r6  r7  r8  r#  r(  r,  r9  r$  r%  r:  r;  r)  r  )z5%(py2)s
{%(py2)s = %(py0)s.active_trades
} == %(py5)sr#  r  r  r  r  r  r   )zQ%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.daily
}.executed_count
} == %(py7)sr  r  r  c              3  ,   K   | ]  }|d    dk(    yw)r   open_failedNrH   r  s     r%   r  z>test_entry_opener_failure_no_state_mutation.<locals>.<genexpr>i  s     Lqqz]*Lr  r  r  r  z>entry: opener failure -> no state mutation, open_failed logged)rZ   r  r   r   r   r   rJ   r)   r<  r    r   r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r   r  r&   )r  r   r#  r,  r0  r}  r  r  r  r  r  r  r  r  r  s                  r%   +test_entry_opener_failure_no_state_mutationr6  Q  s   %8%:;E
-CLE\F[ [ fh\^<>&#51~k&M e,.D tKK
$"$"$$$$"$$$$$$5$$$5$$$$$$"$$$$$$$;;*;%%**%****%******5***5***;***%**********LF4D4D4K4KLL3LLLLLLLLL3LLL3LLLLLLLLLLLLLLHIr'   c                    t               } dD ]0  }t        |      }t        |t                     | j                  |<   2 t        t                     }t        ||       \  }}t        |       t        j                  |j                                d}|j                  }|j                  }||v}	|	st        j                  d|	fd||f      t        j                  |      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 )N)6EGCCLNQRTY)r=   r  r  r  r  r   not inzT%(py1)s not in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.state
}.active_trades
}r}  r  r  r  z>entry: 5/5 open trades -> MAX_OPEN_TRADES_REACHED, MES skipped)r   r  r   r   r  rZ   r  r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r&   )r#  symr   r  r}  r,  r  r  r  r  r  r  s               r%   /test_entry_skipped_when_max_open_trades_reachedrB  q  s   LE. PC(#.Q#OC P %8%:;E%E?LD&tKK
0

0
000500000500005000000000000
00000000000HIr'   c                    t               } t               }t        |t                     | j                  d<   t        d t        dd            }t        ||       \  }}t        |       t        j                  |j                                d}|j                  }|j                  }||v}|st        j                  d|fd	||f      t        j                  |      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}}d |j(                  j*                  D        }t-        |      }|sddt        j                          v st        j"                  t,              rt        j                  t,              ndt        j                  |      t        j                  |      dz  }t%        t        j&                  |            d x}}t/        d       y )Nr=  r   EXIT
brain_exitr^   ra   rb   r  r>  r@  r}  r  r  r  c              3  ,   K   | ]  }|d    dk(    yw)r   r   NrH   )r  r  s     r%   r  z0test_exit_action_closes_trade.<locals>.<genexpr>  s     Or'{n,Or  r  r  r  z@exit: action=EXIT -> closer success, state.active_trades cleared)r   r  r   r   r  rZ   r   r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r   r   r  r&   )r#  r   r  r}  r,  r  r  r  r  r  r  r  r  r  s                 r%   test_exit_action_closes_traderH    sg   LEA!,1ln!ME#6,GE &E?LD&tKK
0

0
000500000500005000000000000
00000000000Ov7G7G7N7NOO3OOOOOOOOO3OOO3OOOOOOOOOOOOOOJKr'   c                 v   t               } t        d      }t        |t                     | j                  d<   t        d t        dd            }t        || 	      \  }}t        |t        d
             t        j                  |j                                | j                  }|j                  }d}i } |||      }	|	j                  }
d}d} |
||      }d}||k(  }|sMt        j                  d|fd||f      dt!        j"                         v st        j$                  |       rt        j&                  |       ndt        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |	      t        j&                  |
      t        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            d x}x}x}x}x}	x}
x}x}x}x}}d}| j,                  }|j.                  }||v}|st        j                  d|fd||f      t        j&                  |      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}}t1        d       y )Nr   r  r=  r   rD  	tp_targetr^   rF  r  r   rG  r  consecutive_sl_countr   r  z%(py18)s
{%(py18)s = %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.metadata
}.get
}(%(py6)s, %(py8)s)
}.get
}(%(py14)s, %(py16)s)
} == %(py21)sr#  r  r  r  r  r  r  r  py14py16py18py21assert %(py23)spy23r>  )zX%(py1)s not in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.cooldown
}.cooldown_until
}r  r  r  z>exit-win: register_tp_hit -> consecutive_sl=0, no cooldown setr   r  r   r   r  rZ   r   r>  r~  rx  r  r  r  r7   r  r  r  r  r  r  r  r  cooldowncooldown_untilr&   r#  r   r  r}  r	  r  r  r  r  r  @py_assert11@py_assert13@py_assert15@py_assert17@py_assert20@py_assert19@py_format22@py_format24r  r  r  r  r  r  s                           r%   #test_exit_win_calls_register_tp_hitra    s   LE5)A!,1ln!ME#6+FE
 !uE:GD!t/78KK
>>L>L4LbL4b9L9==LeLQL=eQGL1LG1LLLLG1LLLLLL5LLL5LLL>LLLLLL4LLLbLLL9LLL=LLLeLLLQLLLGLLL1LLLLLLLLL5555555555555555555555555555555555555555HIr'   c                 v   t               } t        d      }t        |t                     | j                  d<   t        d t        dd            }t        || 	      \  }}t        |t        d
             t        j                  |j                                | j                  }|j                  }d}i } |||      }	|	j                  }
d}d} |
||      }d}||k(  }|sMt        j                  d|fd||f      dt!        j"                         v st        j$                  |       rt        j&                  |       ndt        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |	      t        j&                  |
      t        j&                  |      t        j&                  |      t        j&                  |      t        j&                  |      dz  }dd|iz  }t)        t        j*                  |            d x}x}x}x}x}	x}
x}x}x}x}}d}| j,                  }|j.                  }||v }|st        j                  d|fd||f      t        j&                  |      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}}t1        d       y )Nr   r  r=  r   rD  sl_hitr^   rF  r  r   rK  r  rL  r   rk   r  rM  r#  rN  rS  rT  r  )zT%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.cooldown
}.cooldown_until
}r  r  r  z?exit-loss: register_sl_hit -> consecutive=1, cooldown_until setrU  rX  s                           r%   2test_exit_loss_calls_register_sl_hit_with_cooldownrd    s   LE5)A!,1ln!ME#6(CE
 !uE:GD!t/78KK
>>L>L4LbL4b9L9==LeLQL=eQGL1LG1LLLLG1LLLLLL5LLL5LLL>LLLLLL4LLLbLLL9LLL=LLLeLLLQLLLGLLL1LLLLLLLLL1ENN1N111511111511115111111E111E111N11111111111IJr'   c                 4   t               } t               }t        |t                     | j                  d<   t        d t        dd            }t               }d|_        t        ddd	d
      g|_
        t               }t               }t        |d|      }t        di i |j                  j!                         D ci c]#  }|j"                  t%        ||j"                        % c}ddi}t        |t                     | j                  d<   t'        || |      }	t)        |t+               t-               | t/               |||dt1        |d      ||	|d d      }
t3        |
       t5        j6                  |
j7                                d}|
j8                  }|j                  }||v }|st;        j<                  d|fd||f      t;        j>                  |      dtA        jB                         v st;        jD                  |
      rt;        j>                  |
      ndt;        j>                  |      t;        j>                  |      dz  }t;        jF                  d      dz   d|iz  }tI        t;        jJ                  |            d x}x}x}}tM        d       y c c}w )Nr=  r   rD  rE  r^   rF  Tr      r   r=   r   r   	avg_priceFr+  r  r-  r.  r1  c                     t         S r+   r2  rH   r'   r%   r3  z=test_exit_closer_failure_keeps_active_trade.<locals>.<lambda>  r4  r'   rk   r5  r  r  r}  r  z*closer failure must NOT clear active_trader  r  z:exit: broker close_position fail -> active_trade preservedrH   )'r   r  r   r   r  rZ   r   ry   r   r   r   r   r   r    r   __dataclass_fields__valuesnamerp   r   r   rJ   r)   r<  r!   r~  r  r  r#  r  r  r  r  r  r  r  r  r  r&   )r#  r   r  r&  r   r,  r%  fe_liver'  r}  r  r  r  r  r  r  s                    r%   +test_exit_closer_failure_keeps_active_tradero    s    LEA!,1ln!ME#6,GE
 \F!%F
 	!vN"F -C\FvFF !2H2H2O2O2QRQaffga((R uF "-6<>!REsE&1Dfh\^<>&#51&59D.D tKK
ZDJJZJ,,Z5,,ZZZ5,ZZZ5ZZZZZZDZZZDZZZJZZZ,ZZZ.ZZZZZZZZDE# Ss   ;(Jc                    t               } t               }t        dEi i |j                  j	                         D ci c]#  }|j
                  t        ||j
                        % c}ddi}t        |t                     | j                  d<   t        dt        dd      	      }t               }g |_        t               }t               }t!        |d|
      }t#        || |      }	t%        |t'               t)               | t+               |||dt-        |d      ||	|d d      }
t/        |
       t1        j2                  |
j3                                d}|
j4                  }|j                  }||v}|st7        j8                  d|fd||f      t7        j:                  |      dt=        j>                         v st7        j@                  |
      rt7        j:                  |
      ndt7        j:                  |      t7        j:                  |      dz  }t7        jB                  d      dz   d|iz  }tE        t7        jF                  |            dx}x}x}}d}|jH                  }||v }|st7        j8                  d|fd||f      t7        j:                  |      dt=        j>                         v st7        j@                  |      rt7        j:                  |      ndt7        j:                  |      dz  }t7        jB                  d|jH                         dz   d|iz  }tE        t7        jF                  |            dx}x}}|jJ                  }d}||k(  }|st7        j8                  d |fd!||f      d"t=        j>                         v st7        j@                  |      rt7        j:                  |      nd"t7        j:                  |      t7        j:                  |      d#z  }t7        jB                  d$|jJ                         dz   d|iz  }tE        t7        jF                  |            dx}x}}|jL                  }g }||k(  }|st7        j8                  d |fd%||f      dt=        j>                         v st7        j@                  |      rt7        j:                  |      ndt7        j:                  |      t7        j:                  |      d#z  }t7        jB                  d&|jL                         dz   d|iz  }tE        t7        jF                  |            dx}x}}|jN                  jP                  D cg c]  }|d'   d(k(  r| }}tS        |      }d}||k(  }|st7        j8                  d |fd)||f      d*t=        j>                         v st7        j@                  tR              rt7        j:                  tR              nd*d+t=        j>                         v st7        j@                  |      rt7        j:                  |      nd+t7        j:                  |      t7        j:                  |      d,z  }t7        jB                  d-|       d.z   d/|iz  }tE        t7        jF                  |            dx}x}}|d   }|d0   }d}||k(  }|slt7        j8                  d |fd1||f      t7        j:                  |      t7        j:                  |      d2z  }d3d4|iz  }tE        t7        jF                  |            dx}x}}|d"   }d5}||k(  }|slt7        j8                  d |fd1||f      t7        j:                  |      t7        j:                  |      d2z  }d3d4|iz  }tE        t7        jF                  |            dx}x}}|d6   }d7}||k(  }|slt7        j8                  d |fd1||f      t7        j:                  |      t7        j:                  |      d2z  }d3d4|iz  }tE        t7        jF                  |            dx}x}}|d8   }|jT                  }||k(  }|st7        j8                  d |fd9||f      t7        j:                  |      d:t=        j>                         v st7        j@                  |      rt7        j:                  |      nd:t7        j:                  |      dz  }d;d|iz  }tE        t7        jF                  |            dx}x}}|d<   }|jV                  }||k(  }|st7        j8                  d |fd=||f      t7        j:                  |      d:t=        j>                         v st7        j@                  |      rt7        j:                  |      nd:t7        j:                  |      dz  }d;d|iz  }tE        t7        jF                  |            dx}x}}|d>   }|jX                  }||k(  }|st7        j8                  d |fd?||f      t7        j:                  |      d:t=        j>                         v st7        j@                  |      rt7        j:                  |      nd:t7        j:                  |      dz  }d;d|iz  }tE        t7        jF                  |            dx}x}}|d@   }dA}||k(  }|slt7        j8                  d |fd1||f      t7        j:                  |      t7        j:                  |      d2z  }d3d4|iz  }tE        t7        jF                  |            dx}x}}|dB   }dC}||k(  }|slt7        j8                  d |fd1||f      t7        j:                  |      t7        j:                  |      d2z  }d3d4|iz  }tE        t7        jF                  |            dx}x}}t[        dD       yc c}w c c}w )Fu  
    BUG A fix: ProjectX SL/TP not server-side OCO. When TP fills,
    SL stays open (and viceversa). manage_one polls positions_get
    before brain.manage_exit — if position is gone, cancel orphans
    + cleanup state + emit position_closed_externally event.
    r  Fr=  r   NrD  should_not_runr^   rF  r+  r-  r.  r1  c                     t         S r+   r2  rH   r'   r%   r3  zCtest_manage_skips_when_position_closed_externally.<locals>.<lambda>  r4  r'   rk   r5  r>  r@  r}  r  z%external close must drop active_trader  r  r  )z8%(py1)s in %(py5)s
{%(py5)s = %(py3)s.cancel_all_calls
}r&  r  z4orphan cleanup must call cancel_all_for_symbol, got 
>assert %(py7)sr  r   r  z4%(py2)s
{%(py2)s = %(py0)s.manage_calls
} == %(py5)sr  r3  z>manage_exit must NOT run when position closed externally, got z3%(py2)s
{%(py2)s = %(py0)s.close_calls
} == %(py5)sz9close_position must NOT be called on external close, got r   position_closed_externallyr  r  r   r  expected 1 event, got 
>assert %(py8)sr  r=   r  r  r  r  r/  r   r   r   z3%(py1)s == %(py5)s
{%(py5)s = %(py3)s.entry_price
}rn  r  r   )z0%(py1)s == %(py5)s
{%(py5)s = %(py3)s.sl_price
}r   z0%(py1)s == %(py5)s
{%(py5)s = %(py3)s.tp_price
}r   r|   r   r}   z?BUG A: external close -> cancel orphans + state cleanup + eventrH   ).r   r  r   rj  rk  rl  rp   r   r   r  rZ   r   ry   r   r   r   r    r   r   rJ   r)   r<  r!   r~  r  r  r#  r  r  r  r  r  r  r  r  r  r   rd   r   r   r   r  r   r   r   r&   )r#  r   rm  rn  r  r&  r   r,  r%  r'  r}  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  s                             r%   1test_manage_skips_when_position_closed_externallyr{    s    LEA !2H2H2O2O2QRQaffga((R uF "-6<>!RE $6:JK	E \F!#F
-C\FvFFsE&1Dfh\^<>&#51&59D.D tKK
  0

 0
00 0500 0//050 0 0&/i  0 0)/0 0//  0 0&/i  0 0&/i # 0 0&/i 1 0 0///0 0 0//0 0 0  YF++ Y5++ YGXGXY5+ Y YOXy  Y YRXRXY Y@X@X  Y YOXy  Y YOXy , Y YGXGX
>v?V?V>WXY Y YEXEXY Y  ^ ^" ^L]L]^ ^ ^W]W]^ ^E]E]  ^ ^T]T]  ^ ^T]T]  ^ ^T]T] "# ^ ^L]L]
HI[I[H\]^ ^ ^J]J]^ ^  Y Y# YGXGXY Y YRXRXY Y@X@X  Y YOXy  Y YOXy  Y YOXy "$ Y YGXGX
CFDVDVCWXY Y YEXEXY Y "++22 >RG <<  >F >v;>!>;!>>>;!>>>>>>3>>>3>>>>>>v>>>v>>>;>>>!>>>5fX>>>>>>>>	Bh< 5 <5    <5   <   5       g;$;$;$;$k?#e#?e####?e###?###e#######m2 2 22 22222 2222222222222222 22222222j>,V__,>_,,,,>_,,,>,,,,,,V,,,V,,,_,,,,,,,j>,V__,>_,,,,>_,,,>,,,,,,V,,,V,,,_,,,,,,,i= D =D    =D   =   D       k?"d"?d""""?d"""?"""d"""""""IJk SR>s   (m*mc                 l
   t               } t               }t        d/i i |j                  j	                         D ci c]#  }|j
                  t        ||j
                        % c}ddi}t        |t                     | j                  d<   t        dt        dd      	      }t               }t        dd
dd      g|_        t               }t!               }t#        |d|      }t%        || |      }	t'        |t)               t+               | t-               |||dt/        |d      ||	|d d      }
t1        |
       t3        j4                  |
j5                                d}|
j6                  }|j                  }||v }|st9        j:                  d|fd||f      t9        j<                  |      dt?        j@                         v st9        jB                  |
      rt9        j<                  |
      ndt9        j<                  |      t9        j<                  |      dz  }t9        jD                  d      dz   d|iz  }tG        t9        jH                  |            dx}x}x}}|jJ                  }g }||k(  }|st9        j:                  d|fd||f      dt?        j@                         v st9        jB                  |      rt9        j<                  |      ndt9        j<                  |      t9        j<                  |      dz  }t9        jD                  d |jJ                         d!z   d"|iz  }tG        t9        jH                  |            dx}x}}|jL                  }d}||k(  }|st9        j:                  d|fd#||f      d$t?        j@                         v st9        jB                  |      rt9        j<                  |      nd$t9        j<                  |      t9        j<                  |      dz  }t9        jD                  d%|jL                         d!z   d"|iz  }tG        t9        jH                  |            dx}x}}|jN                  jP                  D cg c]  }|d&   d'k(  r| }}g }||k(  }|st9        j:                  d|fd(||f      d)t?        j@                         v st9        jB                  |      rt9        j<                  |      nd)t9        j<                  |      d*z  }t9        jD                  d+|       d,z   d-|iz  }tG        t9        jH                  |            dx}}tS        d.       yc c}w c c}w )0zt
    BUG A fix: positions_get returns the open position -> manage_exit
    runs normally (no spurious cleanup).
    r  Fr=  r   Nr\   hold_defaultr^   rF  r   rf  r   rg  r+  r-  r.  r1  c                     t         S r+   r2  rH   r'   r%   r3  zAtest_manage_runs_brain_when_position_still_open.<locals>.<lambda>N  r4  r'   rk   r5  r  r  r}  r  z$open position must NOT be cleaned upr  r  r  )z8%(py2)s
{%(py2)s = %(py0)s.cancel_all_calls
} == %(py5)sr&  r3  z/no cleanup expected when position is open, got rs  r  rt  r  z0manage_exit must run when position is open, got r   rv  )z%(py0)s == %(py3)sr   r  r  zno event expected, got 
>assert %(py5)sr  z=BUG A: position still open -> brain runs, no spurious cleanuprH   )*r   r  r   rj  rk  rl  rp   r   r   r  rZ   r   ry   r   r   r   r   r    r   r   rJ   r)   r<  r!   r~  r  r  r#  r  r  r  r  r  r  r  r  r  r   rd   r   r   r&   )r#  r   rm  rn  r  r&  r   r,  r%  r'  r}  r  r  r  r  r  r  r  r  r  r  r   @py_format4s                          r%   /test_manage_runs_brain_when_position_still_openr  0  s   
 LEA !2H2H2O2O2QRQaffga((R uF "-6<>!RE#6.IE \F!vN"F -C\FvFFsE&1Dfh\^<>&#51&59D.D tKK
  /DJJ /J,, /5,, /../5, / /%.Y  / /(./ /..  / /%.Y  / /%.Y  / /%.Y - / /.../ / /../ / / "" Tb T"b( TBSBST"b T TMSVT T;S;S  T TJS)  T TJS) # T TJS) ') T TBSBS
9&:Q:Q9RST T T@S@ST T  P P" P>O>OP P PIOP P7O7O  P PFOi  P PFOi  P PFOi "# P P>O>O
:5;M;M:NOP P P<O<OP P "++22 >RG <<  >F >;6R<;;;6R;;;;;;6;;;6;;;R;;;26(;;;;;;;GHS SL>s   (T,T1c                    t               } t               }t        |t                     | j                  d<   t        dt        dd            }t               }g |_        t        || |      \  }}t        |       t        j                  |j                                d}|j                  }|j                  }||v }	|	st        j                  d	|	fd
||f      t        j                   |      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}}|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|j,                         dz   d|iz  }
t)        t        j*                  |
            dx}x}}t1        d       y)z
    BUG A fix: paper trades have no broker-side counterpart, so
    positions_get must NOT be polled. Check skipped via is_paper guard.
    r=  r   Nr\   holdr^   rF  r  r#  r&  r  r  r}  r  r  r  r   r  )z;%(py2)s
{%(py2)s = %(py0)s.positions_get_calls
} == %(py5)sr&  r3  z-paper trade must not poll positions_get, got rs  r  z*BUG A: paper trade skips broker-side check)r   r  r   r   r  rZ   r   ry   r   r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r   r  r&   )r#  r   r  r&  r}  r,  r  r  r  r  r  r  r  r  r  s                  r%   1test_manage_skips_external_check_for_paper_tradesr  d  s   
 LEA!,1ln!ME#6&AE \F!#F%EvNLD&tKK
 ,DJJ,J,,,5,,,,,5,,,,5,,,,,,D,,,D,,,J,,,,,,,,,,,
 %% U U%* UCTCTU% U UNTfU U<T<T  U UKT9  U UKT9 & U UKT9 *+ U UCTCT
78R8R7STU U UATATU U45r'   r  c                   t               }t        |      }t        di i |j                  j	                         D ci c]#  }|j
                  t        ||j
                        % c}ddi}t        |t                     |j                  d<   t        dt        dd	      
      }t               }g |_        | |_        t               }t!               }	t#        |d|	      }
t%        |||	      }t'        |t)               t+               |t-               |	||dt/        |d      |
||d d      }t1        |       |||||	fS c c}w )zHelper: build a live trade closed externally, broker returns the
    given recent_trades list. Returns (orch, broker, risk, state, logger).
    r  r  Fr=  r   NrD  rq  r^   rF  r+  r-  r.  r1  c                     t         S r+   r2  rH   r'   r%   r3  z'_external_close_setup.<locals>.<lambda>  r4  r'   rk   r5  rH   )r   r  r   rj  rk  rl  rp   r   r   r  rZ   r   ry   r   r   r   r   r    r   r   rJ   r)   r<  r!   r~  )r   r   r#  r   rm  rn  r  r&  r   r,  r%  r'  r}  s                r%   _external_close_setupr    s<    LE9-A !2H2H2O2O2QRQaffga((R uF "-6<>!RE#6:JKE \F!#F%2F"
-C\FvFFsE&1Dfh\^<>&#51&59D.D tuf,,3 Ss   (E c            	        t        ddddddd      } t        | gd	
      \  }}}}}t        j                  |j                                d}|j                  }||v}|st        j                  d|fd||f      t        j                  |      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}x}}|j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}x}}|j                  }|j                   }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  }|j                   }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  j&                  j)                  d i       }|j(                  }d} ||      }d}||k(  }|st        j                  d|fd!||f      d"t        j                         v st        j                  |      rt        j                  |      nd"t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        j*                  d#|       d$z   d|iz  }t        t        j                  |            dx}x}x}x}}d}|j                  }|j,                  }|j.                  }||v }|st        j                  d%|fd&||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d'z  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j0                  j2                  D cg c]  }|d(   d)k(  r| }}t5        |      }d}||k(  }|st        j                  d|fd*||f      d+t        j                         v st        j                  t4              rt        j                  t4              nd+d,t        j                         v st        j                  |      rt        j                  |      nd,t        j                  |      t        j                  |      d-z  }t        j*                  d.|       d/z   d0|iz  }t        t        j                  |            dx}x}}|d   d1   }d}| }||k(  }|slt        j                  d|fd2||f      t        j                  |      t        j                  |      d3z  }	dd|	iz  }
t        t        j                  |
            dx}x}x}}|d   d4   }d5}||k(  }|slt        j                  d|fd6||f      t        j                  |      t        j                  |      d3z  }d7d8|iz  }t        t        j                  |            dx}x}}|d   d9   }d}||k(  }|slt        j                  d|fd6||f      t        j                  |      t        j                  |      d3z  }d7d8|iz  }t        t        j                  |            dx}x}}t7        d:       yc c}w );ay  
    V18: external close with broker.recent_trades returning a closing
    leg (BUY position -> SELL closing trade) with negative P&L:
      - pnl_usd recovered from broker history
      - daily_pnl decremented
      - register_sl_hit fires (consecutive_sl counter += 1, cooldown set)
      - tf_losses += 1
      - position_closed_externally event has pnl_source="broker"
    t1CON.F.US.ES.M25rf  SELLr   g      I2026-04-28T14:05:00Ztrade_idr=   r   side
exit_pricepnl_usd	closed_atr   r   r   r   r>  z9%(py1)s not in %(py5)s
{%(py5)s = %(py3)s.active_trades
}r#  r  r  r  NrK  r  )zh%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.daily_pnl
} == -%(py9)sr'  r  assert %(py12)sr  rk   zg%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.brain
}.tf_losses
} == %(py9)sr  r  r   ze%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.brain
}.tf_wins
} == %(py9)srL  zI%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s)
} == %(py9)scountersz$consecutive_sl_count must be 1, got 
>assert %(py11)sr  )zo%(py1)s in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.state
}.cooldown
}.cooldown_until
}r  r  r  r  r  r   rv  r  r  r   r  rw  rx  r  r  )z%(py1)s == -%(py4)sr  
pnl_sourcer&  r  r  r  r  z@V18: external close LOSS -> pnl recovered, register_sl_hit fires)r   r  r  r  r  r  r  r  r  r  r  r  r  r#  r  r  r  	tf_lossestf_winsr  r7   r  rV  rW  r   r   r  r&   )closingr}  r&  r'  r#  r,  r  r  r  r  r  r  r  r  r  @py_assert10r  r  r  r  r  r  r  r  r   r  r  r  s                               r%   :test_external_close_loss_recovers_pnl_and_registers_sl_hitr    s    /&54JG
 )>i5)%D&$v KK
 ++++5+++++5++++5++++++++++++++++++++::.:.%%.$.$.%....%......4...4...:......%...$........::*:*%%**%****%******4***4***:******%**********::(:(##(q(#q((((#q((((((4(((4(((:((((((#(((q(((((((zz""&&'=rBH<<VV<V!V!#VVV!VVVVVV8VVV8VVV<VVVVVVVVV!VVV'KH:%VVVVVVVV6DJJ6J''6'666566666566665666666D666D666J666'66666666666!++22 >RG <<  >F >v;>!>;!>>>;!>>>>>>3>>>3>>>>>>v>>>v>>>;>>>!>>>5fX>>>>>>>>!9Y(D(D5(5((((5((((((D(((((((!9\".h."h...."h..."...h.......!9\",f,"f,,,,"f,,,",,,f,,,,,,,JK>s   e1c            	        t        ddddddd      } t        | gd	
      \  }}}}}ddi|j                  j                  d<   t	        j
                  |j                                d}|j                  }||v}|st        j                  d|fd||f      t        j                  |      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}x}}|j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}x}}|j                  }|j"                  }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  }|j"                  }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  j                  j)                  di       }|j(                  }d} ||      }d}||k(  }|st        j                  d|fd ||f      d!t        j                         v st        j                  |      rt        j                  |      nd!t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }t        j*                  d"|       d#z   d|iz  }t        t        j                  |            dx}x}x}x}}|j,                  j.                  D cg c]  }|d$   d%k(  r| }}|d   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}x}}|d   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}}t1        d.       yc c}w )/z
    V18: external close with positive P&L resets consecutive_sl_count
    via register_tp_hit and increments tf_wins. No cooldown extension.
    t2r  rf  r  r   g      T@r  r  r   r  r   rL  r>  r  r#  r  r  r  Nr  )zh%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.daily_pnl
} == +%(py9)sr'  r  r  r  rk   r  r  r  r   r  r  r  z5register_tp_hit must reset consecutive_sl_count, got r  r   rv  r  )z%(py1)s == +%(py4)sr  r  r&  r  r  r  z@V18: external close WIN -> register_tp_hit, consecutive_sl reset)r   r  r#  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r7   r  r   r   r&   )r  r}  r&  r'  r#  r,  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  s                             r%   Gtest_external_close_win_calls_register_tp_hit_and_resets_consecutive_slr    s   
 /&V!7G
 )>i5)%D&$v 49!*DJJ./KK
++++5+++++5++++5++++++++++++++++++++::.:.%%.$.$.%....%......4...4...:......%...$........::(:(##(q(#q((((#q((((((4(((4(((:((((((#(((q(((((((::*:*%%**%****%******4***4***:******%**********zz""&&'=rBH<< K K< K! K!# K9J9JK! K KDJFK K2J2J  K KAJ  K KAJ  K KAJ  K KAJ  K KAJ #$ K K9J9J
?zJK K K7J7JK K K!++22 >RG <<  >F >!9Y(D(D5(5((((5((((((D(((((((!9\".h."h...."h..."...h.......JK	>s   *[c            	        t        ddddddd      } t        | gd	      \  }}}}}t        j                  |j                                d
}|j                  }||v}|st        j                  d|fd||f      t        j                  |      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}x}}|j                  }|j                  }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  }|j                   }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  }|j                   }|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                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j                  j&                  j)                  di       }|j(                  }d
}d} |||      }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }dd |iz  }t        t        j                  |            dx}x}x}x}x}}d
}|j                  }|j*                  }|j,                  }||v}|st        j                  d|fd!||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      d"z  }dd|iz  }t        t        j                  |            dx}x}x}x}}|j.                  j0                  D cg c]  }|d#   d$k(  r| }}t3        |      }d%}||k(  }|st        j                  d|fd&||f      d't        j                         v st        j                  t2              rt        j                  t2              nd'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}}|d   d,   }d}||u }|slt        j                  d-|fd.||f      t        j                  |      t        j                  |      d/z  }d0d1|iz  }t        t        j                  |            dx}x}}|d   d2   }d3}||k(  }|slt        j                  d|fd4||f      t        j                  |      t        j                  |      d/z  }d0d1|iz  }t        t        j                  |            dx}x}}t5        d5       yc c}w )6u  
    V18: when recent_trades returns no matching closing leg (or the API
    failed), risk_manager hooks must NOT fire — counters stay un-touched.
    The trade is still dropped from state, but pnl_source="unknown" is
    emitted so the operator can reconcile manually.
    t3r  rf  r   r   re   z2026-04-28T13:55:00Zr  r  r   r>  r  r#  r  r  r  Nr  )zg%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.state
}.daily
}.daily_pnl
} == %(py9)sr'  r  r  r  r   r  r  rL  )zS%(py8)s
{%(py8)s = %(py2)s
{%(py2)s = %(py0)s.get
}(%(py4)s, %(py6)s)
} == %(py11)sr  )r  r  r  r  r  r  zassert %(py13)spy13)zs%(py1)s not in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.state
}.cooldown
}.cooldown_until
}r  r   rv  rk   r  r  r   r  r  r  r  r  r  r  r  r  r  rM   r  z?V18: external close no-match -> counters un-touched, gap logged)r   r  r  r  r  r  r  r  r  r  r  r  r  r#  r  r  r  r  r  r  r7   rV  rW  r   r   r  r&   )	same_sider}  r&  r'  r#  r,  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  @py_format14r  r  r   r  r  r  s                               r%   -test_external_close_no_match_skips_risk_hooksr    s    /%F5I
 )> kU)%D&$v KK
 ++++5+++++5++++5++++++++++++++++++++::,:,%%,,%,,,,%,,,,,,4,,,4,,,:,,,,,,%,,,,,,,,,,::(:(##(q(#q((((#q((((((4(((4(((:((((((#(((q(((((((::*:*%%**%****%******4***4***:******%**********zz""&&'=rBH<<&&q&<q!&Q&!Q&&&&!Q&&&&&&8&&&8&&&<&&&&&&q&&&!&&&Q&&&&&&&&:

:
++:+:::5:::::5::::5::::::::::::
:::+:::::::::::!++22 >RG <<  >F >v;!;!;!33vv;!!9Y'4'4''''4''''''4'''''''!9\"/i/"i////"i///"///i///////IJ>s   c	c                 x   t               } t        d      }t        |t        |j                              | j
                  d<   t        dddd	i
      }t        d|      }t               }t               }t               }t        || |      }t        |t               t               | t               |||dt!        |d	      t#        |d	      ||d      }t%        |t'        d             t)        j*                  |j+                                |j,                  j
                  d   }	|	j.                  }
|
j0                  }d	}||u }|st3        j4                  d|fd||f      dt7        j8                         v st3        j:                  |	      rt3        j<                  |	      ndt3        j<                  |
      t3        j<                  |      t3        j<                  |      dz  }dd|iz  }t?        t3        j@                  |            dx}
x}x}}|jB                  }
g }|
|k(  }|st3        j4                  d|fd|
|f      dt7        j8                         v st3        j:                  |      rt3        j<                  |      ndt3        j<                  |
      t3        j<                  |      dz  }t3        jD                  |jB                        dz   d |iz  }t?        t3        j@                  |            dx}
x}}|	j.                  }
|
jF                  }|jH                  }||k(  }|st3        j4                  d|fd!||f      dt7        j8                         v st3        j:                  |	      rt3        j<                  |	      ndt3        j<                  |
      t3        j<                  |      d"t7        j8                         v st3        j:                  |      rt3        j<                  |      nd"t3        j<                  |      d#z  }d$d%|iz  }t?        t3        j@                  |            dx}
x}x}}tK        d&       y)'u   V18 12-mag — partial via opposite order: SL passa a BE direttamente
    nel nuovo bracket (no separate modify_stop). In paper mode, il closer
    skip-pa il broker call ma l'orchestrator aggiorna comunque
    runtime.current_sl_price al nuovo prezzo.r  r   r  r=  r   
PARTIAL_50rsi50_partialset_be_after_partialTr_   r`   r  NrF  r-  r.  r1  rk   r6  r7  r8  r#  r(  r,  r9  r$  r%  r:  r&  r)  r   rK  r  r  zQ%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.runtime
}.partial_done
} is %(py7)sr  r  r  r  r  z9%(py2)s
{%(py2)s = %(py0)s.modify_stop_calls
} == %(py5)sr&  r3  rs  r  zv%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.runtime
}.current_sl_price
} == %(py8)s
{%(py8)s = %(py6)s.entry_price
}r   r  r  r  r  r  assert %(py10)sr  zHpartial_50 + set_be: partial_done=True, no modify_stop, runtime SL=entry)&r   r  r   r   r   r  r   rZ   ry   r   r   r   r   rJ   r)   r<  r!   r    r~  rx  r  r  r#  r  partial_doner  r  r  r  r  r  r  r  r   r  r  r   r&   )r#  r   rm   r  r&  r   r,  r'  r}  r  r  r  r  r  r  r  r  r  r  r  r  s                        r%   5test_partial_50_with_set_be_modifies_stop_and_runtimer    s   
 LE1%A!,1l? "E O($/H TBE\F
-C\FsE&1Dfh\^<>&#51&48&48&D t/78KK
		!	!%	(B::*:""*d*"d****"d******2***2***:***"***d*******##CrC#r)CCC#rCCCCCC6CCC6CCC#CCCrCCC6+C+CCCCCCCC::7:&&7!--7&-7777&-77777727772777:777&777777!777!777-7777777RSr'   c                    t               } t        d      }t        j                  |ddd      }t	        |t        |j                              | j                  d	<   t        d
dd|j                  d      }t        d|      }t               }d|_        t        d	ddd      g|_        t               }t!               }t#        || |      }t%        |t'               t)               | t+               |||dt-        |d      t/        |d      ||d      }	t1        |	t3        d             t5        j6                  |	j7                                |	j8                  j                  d	   }
|j:                  }t=        |      }d}||k(  }|s
t?        j@                  d|fd||f      dtC        jD                         v st?        jF                  t<              rt?        jH                  t<              ndd tC        jD                         v st?        jF                  |      rt?        jH                  |      nd t?        jH                  |      t?        jH                  |      t?        jH                  |      d!z  }d"d#|iz  }tK        t?        jL                  |            dx}x}x}}|j:                  d$   }|d%   }d	}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|d*   }d}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|d+   }d,}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|d-   }d,}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|d.   }|j                  }||k(  }|st?        j@                  d|fd/||f      t?        jH                  |      d0tC        jD                         v st?        jF                  |      rt?        jH                  |      nd0t?        jH                  |      d1z  }d2d3|iz  }tK        t?        jL                  |            dx}x}}|d4   }|jN                  }||k(  }|st?        j@                  d|fd5||f      t?        jH                  |      d0tC        jD                         v st?        jF                  |      rt?        jH                  |      nd0t?        jH                  |      d1z  }d2d3|iz  }tK        t?        jL                  |            dx}x}}|d6   }d}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|d7   }d}||k(  }|slt?        j@                  d|fd&||f      t?        jH                  |      t?        jH                  |      d'z  }d(d)|iz  }tK        t?        jL                  |            dx}x}}|jP                  }g }||k(  }|st?        j@                  d|fd8||f      d tC        jD                         v st?        jF                  |      rt?        jH                  |      nd t?        jH                  |      t?        jH                  |      d9z  }d2d3|iz  }tK        t?        jL                  |            dx}x}}|
jR                  }|jT                  }d}||u }|st?        j@                  d:|fd;||f      d<tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd<t?        jH                  |      t?        jH                  |      t?        jH                  |      d=z  }d>d?|iz  }tK        t?        jL                  |            dx}x}x}}|
jR                  }|jV                  }|j                  }||k(  }|st?        j@                  d|fd@||f      d<tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd<t?        jH                  |      t?        jH                  |      d0tC        jD                         v st?        jF                  |      rt?        jH                  |      nd0t?        jH                  |      dAz  }d"d#|iz  }tK        t?        jL                  |            dx}x}x}}|
jX                  }|jZ                  }dB}||k(  }|st?        j@                  d|fdC||f      d<tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd<t?        jH                  |      t?        jH                  |      t?        jH                  |      d=z  }d>d?|iz  }tK        t?        jL                  |            dx}x}x}}|
jX                  }|j\                  }dD}||k(  }|st?        j@                  d|fdE||f      d<tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd<t?        jH                  |      t?        jH                  |      t?        jH                  |      d=z  }d>d?|iz  }tK        t?        jL                  |            dx}x}x}}t_        dF       y)Gu   V18 12-mag — live partial: broker.partial_close_via_opposite_order
    returns new stop/target IDs; orchestrator swaps them into active.entry.r  r  zOLD-STOPz
OLD-TARGETF)r  r  r  r  r=  r   r  r  Tr  be_pricer  NrF  )zCLOSE-XNEW-STOP
NEW-TARGETr   r   rg  r-  r.  r1  rk   r  r   rK  r  r  zf%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.partial_close_via_opposite_calls
})
} == %(py8)sr  r&  r  r  r  r  r  r  r  r   r=   r  r  r  r  r   r   rf  r   r   ry  r   r  r  r  r   rz  r   r   ru  r3  r  r  r  r  r  r  r  r  r  )zP%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.stop_order_id
} == %(py7)sr  )zR%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.target_order_id
} == %(py7)sz<partial_50 live: opposite-order called + bracket IDs rewired)0r   r  r  r  r   r   r   r  r   r   rZ   ry   r   r   r   r   r   r   r   rJ   r)   r<  r!   r    r~  rx  r  r  r#  r   r  r  r  r  r  r  r  r  r  r   r   r  r  r  r  r  r  r&   )r#  r   
e_with_idsrm   r  r&  r   r,  r'  r}  r  r  r  r  r  r  r  callr  r  r  r  r  r  r  r  r  s                              r%   3test_partial_50_live_rewires_bracket_ids_on_successr  B  s    LE1%A $$	\J "-:|#,,H "E O*.AMMJH TBE\FCF 	!vN"F -C\FsE&1Dfh\^<>&#51&59&59&	D t/78KK
		!	!%	(B66<367<1<71<<<<71<<<<<<3<<<3<<<<<<v<<<v<<<6<<<7<<<1<<<<<<<2215D>"U">U"""">U""">"""U"""""""%%%%%%%%%%%%%%%%%%%$%**%****%***%**********$%**%****%***%**********01==0=0000=00000000010001000=0000000-1::-:----:---------1---1---:-------#$2
2$
2222$
222$222
2222222%&6,6&,6666&,666&666,6666666############6###6#############::*:""*d*"d****"d******2***2***:***"***d*******::7:&&7!--7&-7777&-77777727772777:777&777777!777!777-777777788/8!!/Z/!Z////!Z//////2///2///8///!///Z///////8838##3|3#|3333#|333333233323338333#333|3333333FGr'   c                    t               } t        d      }t        j                  |d      }t	        |t        |j                  d            | j                  d<   t        dd	d
|j                  d      }t        d|      }t               }d
|_        t        dddd      g|_        t               }t!               }t#        || |      }t%        |t'               t)               | t+               |||dt-        |d      t/        |d      ||d      }	t1        |	t3        d             t5        j6                  |	j7                                |	j8                  j                  d   }
|j:                  }t=        |      }d}||k(  }|s
t?        j@                  d|fd||f      dtC        jD                         v st?        jF                  t<              rt?        jH                  t<              nddtC        jD                         v st?        jF                  |      rt?        jH                  |      ndt?        jH                  |      t?        jH                  |      t?        jH                  |      dz  }dd |iz  }tK        t?        jL                  |            dx}x}x}}|
jN                  }|jP                  }d
}||u }|st?        j@                  d!|fd"||f      d#tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd#t?        jH                  |      t?        jH                  |      t?        jH                  |      d$z  }d%d&|iz  }tK        t?        jL                  |            dx}x}x}}|
jR                  }|jT                  }|jT                  }||k(  }|st?        j@                  d|fd'||f      d#tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd#t?        jH                  |      t?        jH                  |      d(tC        jD                         v st?        jF                  |      rt?        jH                  |      nd(t?        jH                  |      d)z  }dd |iz  }tK        t?        jL                  |            dx}x}x}}|
jR                  }|jV                  }|jV                  }||k(  }|st?        j@                  d|fd*||f      d#tC        jD                         v st?        jF                  |
      rt?        jH                  |
      nd#t?        jH                  |      t?        jH                  |      d(tC        jD                         v st?        jF                  |      rt?        jH                  |      nd(t?        jH                  |      d)z  }dd |iz  }tK        t?        jL                  |            dx}x}x}}tY        d+       y),u   V18 12-mag — broker rejects partial → runtime.partial_done=True
    anche su fallimento, per evitare retry infiniti ogni tick.r  r  F)r  )r  r  r=  r   r  auto_partialTr  r  NrF  r   r   rg  r-  r.  r1  rk   r  r   rK  r  r  r  r  r&  r  r  r  r  r  r  r  r  r  )zs%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.stop_order_id
} == %(py8)s
{%(py8)s = %(py6)s.stop_order_id
}r   r  )zw%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.entry
}.target_order_id
} == %(py8)s
{%(py8)s = %(py6)s.target_order_id
}zJpartial_50 failure: partial_done=True (block retries), entry IDs preserved)-r   r  r  r  r   r   r   r  r   r   rZ   ry   r   r   r   r   r   r   r   rJ   r)   r<  r!   r    r~  rx  r  r  r#  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r&   )r#  e_paperr   rm   r  r&  r   r,  r'  r}  r  r  r  r  r  r  r  r  r  r  r  r  s                         r%   :test_partial_50_failure_sets_partial_done_to_block_retriesr    s    LE+GGe4A!,1l? "E N*.AMMJH TBE\F-1F*!vN"F -C\FsE&1Dfh\^<>&#51&59&59&D t/78KK
		!	!%	(B66<367<1<71<<<<71<<<<<<3<<<3<<<<<<v<<<v<<<6<<<7<<<1<<<<<<<::*:""*d*"d****"d******2***2***:***"***d*******8848!!4Q__4!_4444!_444444244424448444!444444Q444Q444_44444448888##8q'8'88#'88888#'8888888288828888888#888888q888q888'88888888TUr'   c                    t               } t        d      }t        |t        |j                              | j
                  d<   t        ddi       }t        d |	      }t               }t        || |
      \  }}t        |t        d             t        j                  |j                                |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,                  |      t#        j,                  |	      t#        j,                  |
      dz  }dd|iz  }t/        t#        j0                  |            d x}x}	x}}
|j2                  }g }||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#        j0                  |            d x}x}	}|j                  }|j4                  }	|j                  }|	|k(  }|st#        j$                  d|fd|	|f      dt'        j(                         v st#        j*                  |      rt#        j,                  |      ndt#        j,                  |      t#        j,                  |	      dt'        j(                         v st#        j*                  |      rt#        j,                  |      ndt#        j,                  |      dz  }dd|iz  }t/        t#        j0                  |            d x}x}	x}}t7        d        y )!Nr  r  r  r=  r   r  r  r  rF  r  r   rK  r  Tr  r  r  r  r  r  r  r  r&  r3  r  r  )zs%(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.runtime
}.current_sl_price
} == %(py8)s
{%(py8)s = %(py6)s.sl_price
}r   r  r  r  zEpartial_50 no set_be: partial_done=True, NO modify_stop, SL unchanged)r   r  r   r   r   r  r   rZ   ry   r>  r~  rx  r  r  r#  r  r  r  r  r  r  r  r  r  r  r   r  r&   )r#  r   rm   r  r&  r}  r	  r  r  r  r  r  r  r  r  r  r  r  r  s                      r%   test_partial_50_without_set_ber    sR   LE1%A!,1l? "E NH TBE\F uE&IGD!t/78KK
		!	!%	(B::*:""*d*"d****"d******2***2***:***"***d*******##)r)#r))))#r))))))6)))6)))#)))r)))))))::4:&&4!**4&*4444&*44444424442444:444&444444!444!444*4444444OPr'   c                 v   t               } t               }t        |t        |j                              | j
                  d<   t        dd|j                  ddi      }t        d |	      }t               }t        || |
      \  }}t        |       t        j                  |j                                |j                  d   d   }|j                  }||k(  }	|	st        j                   d|	fd||f      t        j"                  |      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}x}	}|j.                  j
                  d   }|j0                  }	|	j2                  }|j                  }||k(  }|st        j                   d|fd||f      t        j"                  |      t        j"                  |	      t        j"                  |      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}x}	x}x}}t5        d       y )Nr  r=  r   MOVE_SL
move_to_be	sl_target	breakevenr_   r`   
move_sl_tor  rF  r  r!  r   r  ry  r   r  r  r  )zv%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.runtime
}.current_sl_price
} == %(py9)s
{%(py9)s = %(py7)s.entry_price
}r  r  r  zNMOVE_SL breakeven: broker.modify_stop called, runtime.current_sl_price updated)r   r  r   r   r   r  r   r   rZ   ry   r>  r~  r  r  r   r  r  r  r  r  r  r  r  r#  r  r  r&   )r#  r   rm   r  r&  r}  r	  r  r  r  r  r  r  r  r  r  s                   r%   /test_move_sl_breakeven_updates_runtime_sl_pricer    s   LEA!,1l? "E !--{+H TBE\F uE&IGD!tKK
##B'7H1==H7=HHHH7=HHH7HHHHHH1HHH1HHH=HHHHHHH::##E*T*22T2CCTq}}TC}TTTTC}TTT*TTT2TTTCTTTTTTqTTTqTTT}TTTTTTTXYr'   c                    t               } t               }t        |t        |j                              | j
                  d<   d}t        dd|ddi	      }t        d |
      }t               }t        || |      \  }}t        |       t        j                  |j                                |j                  d   d   }||k(  }	|	st        j                  d|	fd||f      t        j                   |      dt#        j$                         v st        j&                  |      rt        j                   |      nddz  }
dd|
iz  }t)        t        j*                  |            d x}}	|j,                  j
                  d   }|j.                  }	|	j0                  }||k(  }|st        j                  d|fd||f      t        j                   |      t        j                   |	      t        j                   |      dt#        j$                         v st        j&                  |      rt        j                   |      nddz  }dd|iz  }t)        t        j*                  |            d x}x}	x}}t3        d       y )Nr  r=  r        @r  trailr  trailingr  rF  r  r!  r   r  )z%(py1)s == %(py3)s	new_trailr  r  zassert %(py5)sr  )zU%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.runtime
}.current_sl_price
} == %(py7)sr  r  r  z2MOVE_SL trailing: runtime.current_sl_price updated)r   r  r   r   r   r  r   rZ   ry   r>  r~  r  r  r   r  r  r  r  r  r  r  r  r#  r  r  r&   )r#  r   r  rm   r  r&  r}  r	  r  r  r  r  r  r  r  r  s                   r%   .test_move_sl_trailing_updates_runtime_sl_pricer    s   LEA!,1l? "E IYz*H TBE\F uE&IGD!tKK
##B'7D79DDDD79DDD7DDDDDD9DDD9DDDDDDD::##E*P*22P2CCPCyPPPPCyPPP*PPP2PPPCPPPPPPyPPPyPPPPPPP<=r'   c                    t               } t               }t        |t                     | j                  d<   t        ddddi      }t        d |      }t        || 	      \  }}t        |       t        j                  |j                                |j                  j                  d   }|j                  }|j                  }d
}	||	k(  }
|
st        j                  d|
fd||	f      t        j                   |      t        j                   |      t        j                   |      t        j                   |	      dz  }dd|iz  }t#        t        j$                  |            d x}x}x}x}
}	t'        d       y )Nr=  r   r\   grace_periodrn   逴gr  rF  r  g    mAr  zX%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.runtime
}.last_exit_eval_time
} == %(py8)sr  r  r  r  r  r  z:HOLD: candle_time persisted in runtime.last_exit_eval_time)r   r  r   r   r  r   rZ   r>  r~  r  r  r#  r  last_exit_eval_timer  r  r  r  r  r&   )r#  r   rm   r  r}  r	  r  r  r  r  r  r  r  s                r%   .test_hold_persists_runtime_last_exit_eval_timer    s   LEA!,1ln!MEn):6H TBE uE:GD!tKK
::##E*V*22V2FFV,VF,VVVVF,VVV*VVV2VVVFVVV,VVVVVVVDEr'   c                    t               } t               }t        |t                     | j                  d<   t        dddddd	      }t        d |
      }t               }t        || |      \  }}t        |       t        j                  |j                                |j                  j                  d   }|j                  }|j                  }	d}
|	|
k(  }|st        j                   d|fd|	|
f      t        j"                  |      t        j"                  |      t        j"                  |	      t        j"                  |
      dz  }dd|iz  }t%        t        j&                  |            d x}x}x}	x}}
t)        d       y )Nr=  r   r  r  r  r  itg)r  rn   r  rF  r  g   mAr  r  r  r  r  z7candle dedup: MOVE_SL also persists last_exit_eval_time)r   r  r   r   r  r   rZ   ry   r>  r~  r  r  r#  r  r  r  r  r  r  r  r&   )r#  r   rm   r  r&  r}  r	  r  r  r  r  r  r  r  s                 r%   2test_candle_dedup_runtime_field_updated_on_move_slr  	  s   LEA!,1ln!MEV)JOH TBE\F uE&IGD!tKK
::##E*V*22V2FFV,VF,VVVVF,VVV*VVV2VVVFVVV,VVVVVVVABr'   c                    t               } t               dg G fddt              } |       }t        || d      \  }}t	        |       t        j                  |j                                d}|j                  }|j                  }||v }|st        j                  d|fd||f      t        j                  |      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}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}}|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|j&                         dz   d|iz  }	t!        t        j"                  |	            dx}x}}t)        d       y)zJOpen trade in iter 1, manage_exit HOLD in iter 2 -> active_trade survives.Nc                  2     e Zd Z fdZdddfd
Z xZS )8test_state_persists_across_iterations.<locals>._SeqBrainc                F    t         |           t        dd      | _        y )Nr\   r  r^   )superr.   r   rb   )r-   	__class__s    r%   r.   zAtest_state_persists_across_iterations.<locals>._SeqBrain.__init__#  s    G!.fV!LDr'   Nre   rf   c                  K   | xj                   dz  c_         t        | j                   dz
  d         }t        t        |dd      xs d      xs d }t	        ||      S wrj   )rc   minro   rp   r   )r-   r=   rq   rg   rh   drr   	decisionss          r%   rs   zGtest_state_persists_across_iterations.<locals>._SeqBrain.evaluate_entry&  s^     1$#d11A5q9:Awt]A6;!<DB"ARHHs   AA )rE   rF   rG   r.   rs   __classcell__)r  r  s   @r%   	_SeqBrainr  "  s    	M CG]` 	I 	Ir'   r  rf  )r  r#  r)  r   r  r  r}  r  z*trade opened in iter 1 must survive iter 2r  r  rk   r  z6%(py2)s
{%(py2)s = %(py0)s.evaluate_calls
} == %(py5)sr  r3  z:iter 2 must skip evaluate_entry because MES already activers  r  rt  z2expected 2 manage_exit calls (iter1 + iter2), got zIstate persistence: open in iter 1, hold in iter 2, active_trade preserved)r   r  rZ   r>  r~  r  r  r#  r  r  r  r  r  r  r  r  r  r  rc   rd   r&   )r#  r  r  r}  r	  r  r  r  r  r  r  r  r  r  r  s                 @r%   %test_state_persists_across_iterationsr    s   LE$&-III I KE uE!LGD!tKK
ZDJJZJ,,Z5,,ZZZ5,ZZZ5ZZZZZZDZZZDZZZJZZZ,ZZZ.ZZZZZZZZ E1 E1$ E3D3DE1 E E>DfE E,D,D  E E;D9  E E;D9   E E;D9 $% E E3D3DDE E E1D1DE E  R R" R@Q@QR R RKQ6R R9Q9Q  R RHQ	  R RHQ	  R RHQ	 "# R R@Q@Q
<U=O=O<PQR R R>Q>QR RSTr'   c                 *   t               } | j                         }dD ]  }||v }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |      rt        j                  |      nddz  }t        j                  d|       dz   d	|iz  }t        t        j                  |            d } |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}}t        d       y )N)rJ  rM  rN  rP  rT  rX  rt  r  )z%(py0)s in %(py2)skeyr  )r  r  zmissing key for opener: z
>assert %(py4)sr  rT  r  r  r  r  r  r  z?TechSnapshot.to_dict: all 7 fields used by trade_opener present)rx  rD  r  r  r  r  r  r  r  r  r  r&   )
snapr  r  r  @py_format3r  r  r  r  r  s
             r%   0test_techsnapshot_to_dict_provides_opener_fieldsr  =  s   DAB :ax999sa999999s999s999999a999a99993C59999999:  7$77 $77777 $7777 777$77777777IJr'   c                 L   t        d      } t        |       \  }}t        |       t        j                  |j	                                |j
                  j                  D cg c]  }|d   dk(  s| }}|D cg c]  }|j                  d       }}d}||v}|st        j                  d|fd	||f      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }t        j                  d|       dz   d|iz  }	t        t        j                   |	            dx}}d}||v }|st        j                  d|fd||f      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
dz  }t        j                  d|       dz   d|iz  }	t        t        j                   |	            dx}}t#        d       yc c}w c c}w )z
    FIXED_DAY_UTC=14:00, MES allowed_hours=[13..18]. Gate passes;
    Brain returns None setup -> scan_skip with brain_no_setup, NOT
    OUTSIDE_TRADING_HOURS. Verifies the gate doesn't false-positive.
    Nr  r  r   	scan_skipr`   OUTSIDE_TRADING_HOURSr>  z%(py1)s not in %(py3)sreasonsr  z.hour 14:00 inside MES [13..18] must pass; got r  r  brain_no_setupr  )z%(py1)s in %(py3)szexpected brain_no_setup, got z=trading_hours: 14:00 UTC inside MES window -> passes to brain)rZ   r>  r~  r  r  r   r   r7   r  r  r  r  r  r  r  r  r  r&   )
r  r}  r,  r   skipsr  r  r  r  r  s
             r%   )test_trading_hours_inside_passes_to_brainr  K  s    T*E%E2LD&tKK
((//M11W:3LQMEM(-.1quuX.G." C"'1 C1B1BC"' C C9B # C C<BFC C*B*B +2 C C9B +2 C C1B1B
8	BC C C/B/BC CQw&QQQwQQQQQQQQQwQQQwQQQQ*Gy(QQQQQQQGH N.s   "H0H:H!c                    t               } t               }t               }t        d      }t	        | t               t               |t               |||dt        dd|      t        dd|      t        | ||      dd d	      }t        |       t        j                  |j                                |j                  j                  D cg c]  }|d
   dk(  s| }}t!        |      }d}||k\  }	|	st#        j$                  d|	fd||f      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t#        j,                  |      t#        j,                  |      dz  }
dd|
iz  }t/        t#        j0                  |            dx}x}	}|d   }|d   }d}||k(  }|slt#        j$                  d|fd||f      t#        j,                  |      t#        j,                  |      dz  }dd|iz  }
t/        t#        j0                  |
            dx}x}}|d   }d}||k(  }|slt#        j$                  d|fd||f      t#        j,                  |      t#        j,                  |      dz  }dd|iz  }
t/        t#        j0                  |
            dx}x}}d}|d   }||v }|slt#        j$                  d|fd ||f      t#        j,                  |      t#        j,                  |      dz  }dd|iz  }
t/        t#        j0                  |
            dx}x}}|j2                  }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#        j4                  d$|j2                   d%      d&z   d'|iz  }t/        t#        j0                  |            dx}x}}	t7        d(       yc c}w ))z
    Clock at 22:00 UTC, MES allowed [13..18] -> OUTSIDE_TRADING_HOURS.
    Skip happens BEFORE brain is called: brain.evaluate_entry must
    NOT have been invoked (V14 cost-saver).
    Nr  r.  Tr+  r-  c            	     B    t        ddddddt        j                        S Nr  r  r     r   r   r   r   utcrH   r'   r%   r3  z?test_trading_hours_outside_skips_before_brain.<locals>.<lambda>p  s    $2r1a!U r'   rk   r5  r   r  r  z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} >= %(py6)sr  r  r  r  r  r   r`   r  r  r  r  r  r  now_hour_utcr  r  allowed_hoursr  z%(py1)s in %(py4)sr  r  r3  brain called u   × despite outside-hours gaters  r  zStrading_hours: 22:00 UTC outside MES window -> OUTSIDE_TRADING_HOURS, no brain call)r   r   r   rZ   r   rJ   r)   r<  r!   r    r   r~  r  r  r   r   r  r  r  r  r  r  r  r  r  rc   r  r&   )r   r#  r,  r  r}  r   r  r  r  r  r  r  r  r  r  r  r  r  r  s                      r%   -test_trading_hours_outside_skips_before_brainr
  ]  s>    -CLE\FT*Efh\^<>&#51$fE$fE eF;UD tKK
((//M11W:3LQMEMu::?:33uu:	qBh<222<22222<2222<22222222222n###################$O$$2$$$$$2$$$$2$$$$$$$$$$$ L1 L1$ L:K:KL1 L LEKVL L3K3K  L LBK)  L LBK)   L LBK) $% L L:K:K-,,--JKL L L8K8KL L]^ Ns   	Q)Q)c                 v   ddl m}  | j                  j                  d      }	 t	               }t               }t               }t        d      }t        |t               t               |t               |||dt        dd|      t        dd|      t        |||      dd	 d
      }t        |       t!        j"                  |j#                                || j                  d<   |j$                  j&                  D cg c]  }|d   dk(  s| }}|D cg c]  }|j)                  d       }	}d}
|
|	v}|st+        j,                  d|fd|
|	f      t+        j.                  |
      dt1        j2                         v st+        j4                  |	      rt+        j.                  |	      nddz  }t+        j6                  d|	       dz   d|iz  }t9        t+        j:                  |            dx}
}t=        |	      }d
}||k\  }|st+        j,                  d|fd||f      dt1        j2                         v st+        j4                  t<              rt+        j.                  t<              nddt1        j2                         v st+        j4                  |	      rt+        j.                  |	      ndt+        j.                  |      t+        j.                  |      dz  }t+        j6                  d      dz   d|iz  }t9        t+        j:                  |            dx}x}}t?        d       y# || j                  d<   w xY wc c}w c c}w )z
    Symbol not in TRADING_HOURS -> gate is fail-open (don't block
    onboarding of new assets). Even at 22:00 UTC, scan proceeds.
    Achieved by monkey-patching TRADING_HOURS to delete "MES" entry.
    r   Nr   r  r.  Tr+  r-  c            	     B    t        ddddddt        j                        S r  r  rH   r'   r%   r3  z=test_trading_hours_unknown_symbol_fail_open.<locals>.<lambda>  s    XdAr2q!HLL%Y r'   rk   r5  r   r  r`   r  r>  r  r  r  z1unknown symbol must fail-open on hours gate, got r  r  r  r  r  r  z&scan must have run past the hours gaterx  r  zLtrading_hours: symbol absent from TRADING_HOURS -> fail-open (gate bypassed)) core.config_futuresconfig_futuresTRADING_HOURSpopr   r   r   rZ   r   rJ   r)   r<  r!   r    r   r~  r  r  r   r   r7   r  r  r  r  r  r  r  r  r  r  r&   )cfg_futsavedr   r#  r,  r  r}  r   r  r  r  r  r  r  r  r  r  r  s                     r%   +test_trading_hours_unknown_symbol_fail_openr    s}    *!!%%e,E-m.&(|~f"'u5dT&IdT&I$S%?Y

 	4DHHJ',e$((//M11W:3LQMEM(-.1quuX.G." F"'1 F4E4EF"' F F<EI # F F?EvF F-E-E +2 F F<EI +2 F F4E4E
;G9EF F F2E2EF F
 w<F1F<1FFF<1FFFFFF3FFF3FFFFFFwFFFwFFF<FFF1FFFFFFFFFFFVW (-e$M.s   B0L :L1L1L6L.c                    ddl m}  | j                  }dD ]3  }d}||   }||v }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  | d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}||   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  | d      d	z   d
|iz  }t        t        j                  |            dx}x}}6 d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d }|d   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d!      d	z   d
|iz  }t        t        j                  |            dx}x}}d}|d"   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d }|d"   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d"   }||v}|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$   }||v }	|	st        j                  d|	fd%||f      d&t        j                         v st        j                  |      rt        j
                  |      nd&t        j
                  |      d'z  }
t        j                  d(| d)      d*z   d+|
iz  }t        t        j                  |            dx}	} d,D ]  }|d$   }||v}	|	st        j                  d|	fd-||f      d&t        j                         v st        j                  |      rt        j
                  |      nd&t        j
                  |      d'z  }
t        j                  d.| d/      d*z   d+|
iz  }t        t        j                  |            dx}	} d0D ]  }|d1   }||v }	|	st        j                  d|	fd%||f      d&t        j                         v st        j                  |      rt        j
                  |      nd&t        j
                  |      d'z  }
t        j                  d2| d3      d*z   d+|
iz  }t        t        j                  |            dx}	} d4D ]  }|d1   }||v}	|	st        j                  d|	fd-||f      d&t        j                         v st        j                  |      rt        j
                  |      nd&t        j
                  |      d'z  }
t        j                  d5| d3      d*z   d+|
iz  }t        t        j                  |            dx}	} d}|d6   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d7      d	z   d
|iz  }t        t        j                  |            dx}x}}d8}|d6   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d6   }||v }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd
|iz  }t        t        j                  |            dx}x}}d}|d6   }||v}|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d9      d	z   d
|iz  }t        t        j                  |            dx}x}}t        d:       y);u  
    Liquidity-tuned UTC ranges sanity (post V14 baseline + market review):
      - Equity index micros (MES/MNQ/MYM): cash open + EU/US overlap,
        DROP 18 UTC (= 14 ET "lunch lull" chop).
      - MGC: London AM/PM fix + US data window, DROP pre-London thin
        (< 8 UTC).
      - MCL: NYMEX pit window 13-17, DROP 18 (pre-pit-close gap risk).
      - 6E: Frankfurt → London close, DROP pre-Frankfurt + post-London.
      - 6B: London-bound, ends at 16 UTC (London close fix).
      - 6A: Tokyo prime + US session, DROP EU mattina (6-8) + US PM zombie.
      - 6J: Tokyo + London + US-prime, DROP Tokyo lunch (6) + US PM lull.
      - 6C: USA-only futures, restricted to [12-17] (CAD thin outside US).
    r   N)r   MNQMYMr  r  r  r  z$ must allow 14:00 UTC (US cash open)z
>assert %(py6)sr     r>  )z%(py1)s not in %(py4)sz! must DROP 18:00 UTC (lunch lull)   MGCz&MGC must DROP 06 UTC (pre-London thin)   z&MGC must DROP 07 UTC (pre-London thin)   z,MGC must allow 08 UTC (London AM warming up)z2MGC must allow 18 UTC (Gold stays liquid in US PM)   MCLr     z-MCL must DROP 18 UTC (NYMEX pit closes 18:30)   z.MCL must DROP 11 UTC (no European pre-session)r8  z(6E must DROP 06 UTC (pre-Frankfurt thin)   z06E must DROP 17 UTC (post-London, USD-only chop)6B)r         6A)z%(py0)s in %(py3)shr  z6A must allow z:00 UTC (Asia core)r  r  )r  r  r  r  r  )z%(py0)s not in %(py3)sz6A must DROP z:00 UTC (zombie hour))r   r"  r#  r  r  r  6Jz6J must allow z:00 UTC)r  r  r  z6J must DROP 6Cz!6C must DROP 11 UTC (CAD thin EU)   z&6C must DROP 18 UTC (CAD post-US thin)zEtrading_hours: liquidity-tuned UTC ranges per-asset coverage verified)r  r  r  r  r  r  r  r  r  r  r  r  r&   )r  thrA  r  r  r  r  r  r%  r  r  r  s               r%   5test_trading_hours_per_asset_coverage_liquidity_tunedr*    s-	    *			B % LJRWJrW}JJJrWJJJrJJJWJJJ%IJJJJJJJJKCKr KKKrKKKrKKKKKKSE)J"KKKKKKKKL
 GBuIG1IGGG1IGGG1GGGIGGGGGGGGGGGGBuIG1IGGG1IGGG1GGGIGGGGGGGGGGGI5	I1	>III1	III1III	IIIIIIIIIIIPEP2?PPP2PPP2PPPPPPPPPPPPPP E2?22E2?22ORYO2YOOO2YOOO2OOOYOOO OOOOOOOOPRYP2YPPP2YPPP2PPPYPPP PPPPPPPP HBtHH1HHHH1HHHH1HHHHHHHHHHHHHHH41=11D2>22QRXQ2XQQQ2XQQQ2QQQXQQQQQQQQQQQ 41=11D2>22RX2X2X2X  FtHEqH}EEEqHEEEEEEqEEEqEEEHEEEqc1DEEEEEEEF K4Jq JJJqJJJJJJqJJJqJJJJJJM!4I"JJJJJJJK " :tH9qH}999qH999999q999q999H999qc9999999: =4<q <<<q<<<<<<q<<<q<<<<<<M!G"<<<<<<<= BRXB2XBBB2XBBB2BBBXBBBBBBBBBBBD2>22D2>22GRXG2XGGG2XGGG2GGGXGGGGGGGGGGGOPr'   c                    ddl m}  | j                  j                         D ]  \  }}d |D        }t	        |      }|st        j                  | d|       dz   dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}d |D        }t	        |      }|st        j                  | d	|       dz   dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}} t        d
       y)zDAll assets respect Topstep no-overnight 18 UTC cap (no entry after).r   Nc              3  &   K   | ]	  }|d k    yw)r  NrH   r  r%  s     r%   r  z8test_trading_hours_18_utc_cap_applied.<locals>.<genexpr>  s     *q17*s   z4 has hour > 18 UTC (Topstep no-overnight violated): r  allr  c              3  <   K   | ]  }d |cxk  xr dk  nc   yw)r      NrH   r-  s     r%   r  z8test_trading_hours_18_utc_cap_applied.<locals>.<genexpr>  s     /A1<R<</s   z has hour outside [0,23]: z5trading_hours: 18 UTC cap respected across all assets)r  r  r  r   r.  r  r  r  r  r  r  r  r  r&   )r  rA  hoursr  r  r  s         r%   %test_trading_hours_18_utc_cap_appliedr2    s   )++113 6
U*E* 	Ps** 	P* 	P>O>OseGwO	P 	PIO	P 	P7O7O  	P 	PFOi  	P 	PFOi + 	P 	PFOi + 	P 	P 	P<O<O	P 	P// 	6s// 	6/ 	6$5$5se-eW5	6 	6/5v	6 	655  	6 	6,5I  	6 	6,5I 0 	6 	6,5I 0 	6 	6 	6"5"5	6 	66
 ?@r'   c                 x   ddl m} m} t         |ddddd      } | t	        d	
      z   ddd      g|_        |_        t               }t               }t               }t        d      }t        |t               t               |t               |||dt        dd|      t!        dd|      t#        |||      d|fdd      }t%        |       t'        j(                  |j)                                |j*                  j,                  D cg c]  }|d   dk(  s| }	}|	D cg c]  }|j/                  d      dk(  s| }
}t1        |
      }d}||k(  }|s,t3        j4                  d|fd||f      dt7        j8                         v st3        j:                  t0              rt3        j<                  t0              nddt7        j8                         v st3        j:                  |
      rt3        j<                  |
      ndt3        j<                  |      t3        j<                  |      dz  }t3        j>                  d|	D cg c]  }|j/                  d       c}       d z   d!|iz  }tA        t3        jB                  |            dx}x}}|
d   }|d"   }d}||k(  }|slt3        j4                  d|fd#||f      t3        j<                  |      t3        j<                  |      d$z  }d%d&|iz  }tA        t3        jB                  |            dx}x}}|d'   }d}||k(  }|slt3        j4                  d|fd#||f      t3        j<                  |      t3        j<                  |      d$z  }d%d&|iz  }tA        t3        jB                  |            dx}x}}|d(   }d}||k(  }|slt3        j4                  d|fd#||f      t3        j<                  |      t3        j<                  |      d$z  }d%d&|iz  }tA        t3        jB                  |            dx}x}}|d)   }d*}||k(  }|slt3        j4                  d|fd#||f      t3        j<                  |      t3        j<                  |      d$z  }d%d&|iz  }tA        t3        jB                  |            dx}x}}d+}|d,   }||k  }d-}||k  }|r|st3        j4                  d.||fd/|||f      t3        j<                  |      t3        j<                  |      t3        j<                  |      d0z  }d1d2|iz  }tA        t3        jB                  |            dx}x}x}x}}|jD                  }d}||k(  }|st3        j4                  d|fd3||f      d4t7        j8                         v st3        j:                  |      rt3        j<                  |      nd4t3        j<                  |      t3        j<                  |      d5z  }t3        j>                  d6|jD                   d7      d8z   d9|iz  }tA        t3        jB                  |            dx}x}}tG        d:       yc c}w c c}w c c}w );z
    HIGH USD event at +5min within before_min=45 -> scan_skip
    NEWS_BLOCK with full event details. Brain MUST NOT be called
    (gate is pre-fetch H4 / pre-Brain to save AI cost).
    r   	NewsEvent
NewsFilterT-      https://test.invalidNenabled
before_min	after_min
source_urlr,  r#  r  USDHighzFOMC Statementdtcountryimpacttitler  r.  r+  r-  c                      S r+   rH   fixeds   r%   r3  z4test_news_block_skips_before_brain.<locals>.<lambda>       r'   rk   r6  r7  r8  r#  r(  r,  r9  r$  r%  r:  r&  news_filterr;  r)  r   r  r`   
NEWS_BLOCKr  r  r  blkr  zexpected 1 NEWS_BLOCK, got rx  r  rE  r  r  r  r  rC  rD  window_reasonBEFOREr  minutes_to_eventr  )<rQ  )z%(py1)s < %(py5)sz%(py5)s < %(py7)s)r  r  r  r  r  r  r  r3  r	  u   × despite NEWS_BLOCK gaters  r  z=news_block: HIGH USD event +5min -> NEWS_BLOCK, no brain call)$core.news_filterr5  r6  r"  r   	_upcoming_last_sync_atr   r   r   rZ   r   rJ   r)   r<  r!   r    r   r~  r  r  r   r   r7   r  r  r  r  r  r  r  r  r  r  rc   r&   )r5  r6  nfr   r#  r,  r  r}  r   r  rM  r  r  r  r  r   r  r  r  r  r  r  r  r  r  r  rH  s                             @r%   "test_news_block_skips_before_brainrV    s    7E	r)$
B 9Q''f,< BL B
-CLE\FT*Efh\^<>&#51$fE$fE eF;&D tKK
((//M11W:3LQMEM
?quuX,>1
?C
?s8ZqZ8q=ZZZ8qZZZZZZ3ZZZ3ZZZZZZsZZZsZZZ8ZZZqZZZ7RW8XQx8X7YZZZZZZZZ	QBg;***;*****;****;***********i=!E!=E!!!!=E!!!=!!!E!!!!!!!h<!6!<6!!!!<6!!!<!!!6!!!!!!!o*(*(****(******(*******)r$%)1%)))%)))))1%)))1)))%)))))))))) I1 I1$ I7H7HI1 I IBH&I I0H0H  I I?Hy  I I?Hy   I I?Hy $% I I7H7H-,,--GHI I I5H5HI IGH N
?8Xs   X-X-&X2 X2 X7
c                    ddl m} m} t         |ddddd      } | t	        d	
      z   ddd      g|_        t               }t               }t               }t        d      }t        |t               t               |t               |||dt        dd|      t        dd|      t!        |||      d|fdd      }t#        |       t%        j&                  |j'                                |j(                  j*                  D cg c]  }|d   dk(  s| }	}|	D cg c]  }|j-                  d       }
}d}||
v}|st/        j0                  d|fd||
f      t/        j2                  |      dt5        j6                         v st/        j8                  |
      rt/        j2                  |
      nddz  }t/        j:                  d|
       d z   d!|iz  }t=        t/        j>                  |            dx}}tA        d"       yc c}w c c}w )#zDENABLE_NEWS_FILTER=False (filter constructed disabled) never blocks.r   r4  Fr7  r8  r9  Nr:  r#  r  r?  r@  FOMCrA  r  r.  Tr+  r-  c                      S r+   rH   rG  s   r%   r3  z;test_news_filter_disabled_passes_to_brain.<locals>.<lambda>G  rI  r'   rk   rJ  r   r  r`   rL  r>  r  r  r  z3disabled filter must never produce NEWS_BLOCK, got r  r  z?news_filter: disabled -> no NEWS_BLOCK, passes through to brain)!rR  r5  r6  r"  r   rS  r   r   r   rZ   r   rJ   r)   r<  r!   r    r   r~  r  r  r   r   r7   r  r  r  r  r  r  r  r  r  r&   )r5  r6  rU  r   r#  r,  r  r}  r   r  r  r  r  r  r  rH  s                  @r%   )test_news_filter_disabled_passes_to_brainrZ  ,  s   6E	")$
B
 9Q''fF BL
 -CLE\FT*Efh\^<>&#51$fE$fE eF;&D tKK
((//M11W:3LQMEM(-.1quuX.G. H<w& H6G6GH<w H H>Gi  H HAGH H/G/G  ' H H>Gi  ' H H6G6G
=gYGH H H4G4GH HIJ	 N.s   HHHc                 (   t        d       t                t                t                t	                t                t                t                t                t                t                t                t                t                t                t                t!                t#                t%                t'                t)                t+                t-                t/                t1                t3                t5                t7                t9                t;                t=                t?                tA                tC                tE                tG                tI                tK                tM                t        d       y)Nztest_orchestrator_phase_b.pyzALL TESTS PASSEDr   )'r#   r  r  r  r  r
  r  r  r$  r&  r6  rB  rH  ra  rd  ro  r{  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r*  r2  rV  rZ  rH   r'   r%   mainr\  W  s    	
().02413.0)+')')46"$/135!#')68/1573557>@KM139;79>@"$35242468)+46-/13/19;)+&(-/	
r'   __main__)r$   strrC   rD   )rC   r   )rC   r   )r   r   rC   r   )r   )rC   r   )r   r/  r   rf  r|   )rC   r   )r   r   r  )rC   r
   r+   )r   zlist[ClosedTrade])rC   int)zr   
__future__r   builtinsr  _pytest.assertion.rewrite	assertionrewriter  r  r  systempfiler   r   r   pathlibr   typingr   pandasr9   pathinsertr^  __file__resolveparentanalysis.tech_snapshotr
   brain.ai_clientr   broker.broker_baser   r   r   core.configr   r   r   core.contractsr   r   r   r   r   r   r   r   orchestratorr   persistence.state_storer   r   r   trading.risk_managerr   trading.trade_closerr    r.  r!   r&   r)   rJ   rZ   ry   r   r   r   r   r   r  r  r  r"  __annotations__r>  r<  rx  r~  r  r  r  r  r
  r  r  r$  r&  r6  rB  rH  ra  rd  ro  r{  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r
  r  r*  r2  rV  rZ  r\  rE   exitrH   r'   r%   <module>rz     s  ( #     
  2 2    3tH~--/66==> ? / & A A ; ;   & I I , , ,1 1; ;" "(I@ I@X7 77 72 * :?*.3=$ #4BAqNx N
 
	
<$ $4U"*?Z'KTPL8D:9(U>;J@J&L(J&K*'F\?KD1Ih6B JO  -F&LRLB"KR$TN;H|)VXQ8Z(>2F(C*U>KI$!_H$XN?QDA0If$KV)X zCHHTV r'   