
    MjU                        d Z ddlmZ ddlmZ ddlZddlZd\  Z	Z
Z ed       G d d	             Zdd
Z e       fddZ e       fddZddZ	 	 	 	 	 	 ddZy)uw  Causal, low-churn routing utilities for the non-promoting MLP head probe.

The router is deliberately a small state machine rather than a per-bar MoE
gate.  It uses only contemporaneous or historical columns and cannot change
state more often than ``min_dwell_bars``.  The heads are residual output heads
on top of the promoted dense model's frozen 55→16→8 shared trunk.
    )annotations)	dataclassN)r      T)frozenc                  T    e Zd ZU dZded<   dZded<   dZded<   d	Zded
<   dZded<   y)SlowRouterConfigH   intsmoothing_span   trend_window皙?floatenter_threshold皙?exit_thresholdmin_dwell_barsN)	__name__
__module____qualname__r   __annotations__r   r   r   r        $strategies/mlp_slow_regime_router.pyr	   r	      s4    NCL#!OU! NE NCr   r	   c                z    || vrt        d| d      t        j                  | |   d      j                  d      S )NzSlow regime router requires 'z' in the chart exportcoerce)errors        )
ValueErrorpd
to_numericfillna)framenames     r   _columnr&      s@    58>STUU==tX6==cBBr   c                0   j                   dk  sj                  dk  rt        d      j                  dk  rt        d      t	        | d      }t	        | d      }t	        | d      }t	        | d      }||j                  j                        z  dz
  j                  d	      j                  d
d      dz  }fd}d ||      z  d ||      z  z   d ||      z  z   d ||      j                         z  z
  j                  t        j                        S )zReturn the causal state signal from on-chain, trend, vol and liquidity.

    Price trend is a trailing return; all smoothers use ``adjust=False`` so a
    value at t is a function of rows through t only.
    r   z0smoothing_span and trend_window must be positivezmin_dwell_bars must be positivemvrv_zscore_cont	rvol_normfed_net_liq_signcloseg      ?r   gr   g      4@c                Z    | j                  j                  d      j                         S )NF)spanadjust)ewmr   mean)valuesconfigs    r   <lambda>zrouter_signal.<locals>.<lambda>1   s!    FJJF,A,A%JPUUWr   g?g      ?r   g?dtype)r   r   r    r   r&   shiftr#   clipabsto_numpynpfloat64)r$   r2   mvrvrvolliqr+   trendsmooths    `      r   router_signalrA   "   s    q F$7$7!$;KLLq :;;5,-D5+&D
%+
,CE7#EU[[!4!455;CCCHMMeUYZ]aaEWF 6$<$"66s9KKVD\%%''()1

)CDr   c                F   t        | |      }t        j                  t        |      t        j                        }t
        |j                   }}t        |      D ]  \  }}||z
  |j                  k  r|||<   |}|t        k(  r-||j                   k  rt        }nx||j                  k  rit
        }nb|t        k(  r-||j                  k\  rt        }nC||j                   kD  r3t
        }n,||j                  k\  rt        }n||j                   k  rt        }||k7  r||}}|||<    |S )zGMap the causal signal to {-1, 0, +1} with hysteresis and minimum dwell.r4   )rA   r:   zeroslenint8NEUTRALr   	enumerateBULLr   BEARr   )	r$   r2   signalstatesstatelast_changeindexvalue	candidates	            r   slow_regime_statesrQ   8   s   5&)FXXc&k1F 6#8#8"8;E!&)u;!6!66!F5M	D=//// 	...#	d]... 	&////#	f,,,Iv----I!*E;Eu+ *, Mr   c           	        t        |      dk7  rt        d      t        j                  | t        j                        }|dd D ]  \  }}t        j
                  ddd      5  t        j                  |t        j                  |t        j                        j                  z  t        j                  |t        j                        z         }ddd        |S # 1 sw Y   xY w)zHForward through the frozen dense trunk, excluding its final output head.   z:Tiny-head experiment requires a 3-layer dense MLP artifactr4   Nr   ignoreoverdivideinvalid)rD   r    r:   asarrayr;   errstatetanhT)XlayersrO   weightsbiass        r   shared_trunk_forwardra   V   s    
6{aUVVJJq

+E[[hxJGGEBJJwbjj$I$K$KKbjjY]egeoeoNppqE KJ % L KJs   "A,CC#	c                   t        j                  |t         j                        dz   }t        j                  |d      j	                         st        d      t        j                  |d   t         j                        t        j                  |d   t         j                        }}t        j                  |d   t         j                        t        j                  |d   t         j                        }}|j                  d| j                  d   fk7  s|j                  dk7  rt        d      t        j                  d	d	d	
      5  t        j                  | t         j                        |j                  z  |z   j                  d      }	ddd       	t        j                  | t         j                        ||   z  j                  d      ||   z   z  }	t        j                  |	      dz  S # 1 sw Y   exY w)zHScore a frozen trunk with one small residual output head per slow state.r4   r   )r   r      z$states must contain only -1, 0, or 1r   rS   )rS   z9residual heads must have shapes (3, trunk_width) and (3,)rT   rU   r   N)axisg     @@)r:   rY   rE   isinallr    r;   shaperZ   r\   reshapesumr[   )
trunkrK   	base_headresidual_headsstate_indexbase_weights	base_biashead_weights	head_biaslogitss
             r   regime_head_scoresrs   a   sy    **V2773a7K77;	*..0?@@!zz)A,bjjI!zz)A,bjjI L!zz.*;2::N!zz.*;2::N LaQ00IOOt4KTUU	(8X	F**U"**5FR[[\^_ 
G
rzz%rzz2\+5NNSSYZS[^ghs^tttF776?V## 
G	Fs   AG11G:)r$   pd.DataFramer%   strreturnz	pd.Series)r$   rt   r2   r	   rv   
np.ndarray)r]   rw   r^   z#list[tuple[np.ndarray, np.ndarray]]rv   rw   )
rj   rw   rK   rw   rk   tuple[np.ndarray, np.ndarray]rl   rx   rv   rw   )__doc__
__future__r   dataclassesr   numpyr:   pandasr!   rI   rF   rH   r	   r&   rA   rQ   ra   rs   r   r   r   <module>r~      s    # !    gt $  C CSBT D, HXGY <$"?$'D$IS$r   