
    pjl                   x   U d Z ddlmZ ddlmZ ddlmZ ddlZddl	Z
ddlmZmZ ddlmZ dZd	ed
<   eD  cg c]  } | d   	 c} Zded<   d4dZd5dZddddd	 	 	 	 	 	 	 	 	 	 	 	 	 d6dZddd7dZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d8dZd9dZd:dZd;dZd<dZd=dZ	 	 	 	 	 	 	 	 	 	 	 	 d>dZd?dZe G d  d!             Ze G d" d#             Z e G d$ d%             Z!e G d& d'             Z"e G d( d)             Z#d@d*Z$e G d+ d,             Z%e G d- d.             Z&dd/dAd0Z'dd/dBd1Z(dd/dCd2Z)dd/dCd3Z*yc c} w )Du  
Multi-strategy portfolio blender.

Ensemble consists of:
  * Macro book: Tactical All Weather (dynamic to cash using momentum+trend regime)
  * Alpha book: Market-neutral Long/Short equity momentum (AQR 12-minus-1), long momentum
    strength filter, SPY regime throttle, and trailing vol targeting on the L/S return stream.
  * Optional sector sleeve: SPDR sector ETFs, monthly top-``k`` by 12-minus-1 momentum, equal weight.
  * Optional defensive MR sleeve: when SPY daily-return z-score is extremely negative, tilt the
    sleeve toward long bonds (TLT); exit when z normalizes — aims to add ballast in equity panics.
  * Optional **buy-the-dip** sleeve: long-only **RSI dip** entries (e.g. RSI(5) < 20) in an SMA200
    uptrend, ranked by normalized short-term ATR; each trade held a fixed number of trading days;
    remainder in cash at the risk-free rate.

The sleeves are combined in ``EnsembleManager`` (configurable overlay weights).
    )annotations)	dataclass)DictN)BASE_WEIGHTSTacticalAllWeatherManager)rolling_zscore))XLEEnergy)XLF
Financials)XLVzHealth Care)XLIIndustrials)XLB	Materials)XLK
Technology)XLYzConsumer Discretionary)XLPzConsumer Staples)XLU	Utilities)XLREzReal Estate)XLCzCommunication Servicesztuple[tuple[str, str], ...]SPDR_SECTOR_ETFSz	list[str]SPDR_SECTOR_TICKERSc                D   | | j                   s| j                         }n2d|v r#|d   j                   s|d   j                         }nt        d      d|j                  vr@d|j                  v r'|d   j	                  t
        j                        |d<   |S t        d      |S )zBPrefer explicit SPY panel; else use equity_dict['SPY'] if present.SPYzRegime throttle requires SPY: pass spy_df=... or include 'SPY' in equity_dict with columns close (or Close), ret (optional), sma_200 (optional).closeClosez9SPY frame must include a 'close' or 'Close' price column.)emptycopy
ValueErrorcolumnsastypenpfloat64KeyError)spy_dfequity_dictouts      I/opt/rentech/trading_bot/RenTech/strategy_stack/multi_strategy_manager.py_resolve_spy_framer,   .   s    &,,kkm	+	k%&8&>&>% %%'D
 	
 ckk!ckk!w<..rzz:CL J VWWJ    c                J   | j                         }t        j                  |j                        j	                  d      |_        |j                         }|j                  |      j                         }|d   j                  t        j                        }d|j                  v r#|d   j                  t        j                        }n!|j                  dd      j                         }d|j                  v r"|d   j                  t        j                        n|j                         }|j                  dd      j                  d	      }|j                  d
d      j                         }|j!                         j#                         |j!                         j#                         z  }|||kD  j#                         z  }	t        j$                  |t        j$                  |	dd      d      }
|j!                         j#                         |j!                         j#                         z  |j#                         dkD  z  }||j#                         d|j#                         z  kD  z  }t        j$                  |dd      }|
|z  }t        j&                  ||dt        j                        S )u3  
    Daily regime exposure in (0, 1], executed next bar (caller shifts).

    Trend: SPY close > SMA200 -> factor 1.0; else 0.5.
    Vol: 20d SPY return vol > 1.5 × trailing 1y mean of that vol -> factor 0.5; else 1.0.

    Missing SMA / insufficient vol history: no penalty (factor 1.0) for that leg.
    Nr   sma_200   windowmin_periodsret      ddof   <         ?      ?-q=g      ?regime_exposureindexnamedtype)r!   pdto_datetimer@   tz_localize
sort_indexreindexffillr$   r%   r&   r#   rollingmean
pct_changestdnotnato_numpywhereSeries)spyr@   sr   smar4   market_vol_20
vol_avg_1yok_trend
trend_bulltrend_factorok_volvol_high
vol_factorgs                  r+   _regime_exposure_seriesr]   A   s    	
AnnQWW%11$7AG	A			% AgJbjj)EAII	l!!"**-mm3Cm8==?).!)));!E(//"**
%AQAQASCKKrrK:>>A>FM&&cr&BGGIJ{{}%%'#))+*>*>*@@HUS[2244J88Hbhhz3&DcJL 	&&(




%
%
'	( 5(	* 
 //1C*:M:M:O4OOPH(C-Jz!A99Qe*;2::NNr-   皙?       @   r5   )target_annual_vol	max_scalesmooth_window
vol_windowc               ,   | j                  t        j                        j                  d      }|j	                  ||      j                  d      }|t        j                  d      z  }d}	|}
||
z  }|j                  |
j                         |
|	kD  z  d      }|j                  t        j                  t        j                   gd      }|j                  t        |            }t        dt        |            }|j	                  ||      j                         }|j!                         j                  d      }|j#                  d      j                  d      j                  t        |            }t        |      }|j%                  t        j                  	      }|j%                  t        j                  	      }t        j&                  dd|z
        }||z  ||z  z   }t)        j*                  || j,                  t        j                  
      }||fS )z
    Scale pre-target L/S daily returns toward ``target_annual_vol`` using trailing realized vol.

    Returns (ls_daily_ret_scaled, scale_executed) where ``scale_executed`` is the lagged,
    smoothed, capped factor applied each day.
            r1   r6   r7        o@g:0yE>r;   upperrB   r@   rB   )r$   r%   r&   fillnarI   rL   sqrtrO   rM   replaceinfclipfloatmaxintrJ   rH   shiftrN   maximumrC   rP   r@   )
ls_pre_voldaily_rfra   rb   rc   rd   r
realized_drealized_annepsrvrawcappedswsmoothed
scale_execrfrR   rpcash_wr*   out_ss                         r+   _apply_ls_vol_targetingr   j   s    	"**%,,S1A**EIIqIQJ.L
C	B
b
 C
))BHHJ"s(+S
1C
++rvvw'
-CXXE),X-F	QM"	#B~~RR~8==?H~~&&s+H"))#.33%	:J3KJ	xB"**-A	
"**	%BZZS1W%F
b&6B;
CIIc!1!1DE*r-   &.>)r{   c                   |r| |kD  }n| | k  }g }d}t        |      }||k  rC||   s|dz  }|}||k  r||   r|dz  }||k  r||   r|j                  ||z
         |}||k  rC|S )zLLengths of consecutive days with long (positive) or short (negative) weight.r   r6   )lenappend)wpositiver{   activer*   injs           r+   _collect_spell_lengths_daysr      s    ScTC	AFA
a%ayFA!eq	FA !eq	

1q5 a% Jr-   c               |   |j                  d      |z  j                  d      j                  t        j                        }|j                  d      |z  j                  d      j                  t        j                        }|j                  d      j                  t        j                        j                  t        j                        }	|	|	dkD     }
|	|	dk     }d| d	<   t        t        |	            | d
<   t        |	      rt        |	dkD  j                               n
t        d      | d<   |
j                  rt        |
j                               n
t        d      | d<   |j                  rt        |j                               n
t        d      | d<   |j                  rt        |j                               n
t        d      }|
j                  r?|dk  r:t        j                  |      r%t        |
j                         t        |      z        n
t        d      | d<   t        |	      rt        |	j                               n
t        d      }t        |	      dkD  rt        |	j                  d            n
t        d      }|dkD  r||z  t        j                  d      z  n
t        d      | d<   t        j                   d|	z         }t        j"                  j%                  |      }||z  dz
  }t        |      rt        |j'                               n
t        d      | d<   |j                  t        j                        }|j                  t        j                        }|j                  d      j                  d      j                  t        j                        }|j                  d      j                  d      j                  t        j                         }|dkD  }|dkD  }t        j(                  |      rt        ||   dkD  j                               n
t        d      | d<   t        j(                  |      rt        ||   dkD  j                               n
t        d      | d<   t        |j                               | d<   t        |j                               | d<   t        |j*                  d         }|dk\  rg }g }t-        d|      D ]  }t/        t        j0                  ||dz
           j3                               }t/        t        j0                  ||         j3                               }|j5                  t        ||z               t/        t        j0                  ||dz
           j3                               }t/        t        j0                  ||         j3                               }|j5                  t        ||z                t        |      | d<   t        t        j                  |            | d<   t        t        j                  |            | d<   t        t        |            | d<   t        t        |            | d <   t        t        |      dz        | d!<   t        t        |      dz        | d"<   n#t        |      | d<   d#D ]  } t        d      | | <    t        |      | d$<   g }!g }"|j6                  D ]]  }#||#   j                  t        j                        }$|!j9                  t;        |$d%&             |"j9                  t;        |$d'&             _ |!rt        t        j                  |!            n
t        d      | d(<   |"rt        t        j                  |"            n
t        d      | d)<   t        t        |!            | d*<   t        t        |"            | d+<   d,| d-<   y.)/zWFill ``diagnostics`` in place (cross-sectional L/S sleeve, post vol-targeting returns).rf   lowerr6   axisrh   rj   r   cross_sectional_momentumenginetrading_daysnandaily_win_ratedaily_avg_windaily_avg_lossprofit_factorr7   r=   rg   sharpe_ratio_approxr;   max_drawdownr   long_leg_daily_win_rateshort_leg_daily_win_ratedays_with_long_exposuredays_with_short_exposure   monthly_rebalance_dates$avg_long_names_changed_per_rebalance%avg_short_names_changed_per_rebalancetotal_long_slot_changestotal_short_slot_changesapprox_long_leg_tradesapprox_short_leg_trades)r   r   r   r   r   r   top_n_per_legT)r   Favg_days_in_long_spellavg_days_in_short_spellnum_long_spells_all_tickersnum_short_spells_all_tickersBMErebalance_calendarN)rp   sumr$   r%   r&   rl   rN   rs   r   rq   rJ   sizeisfiniteabsrL   rm   cumprodru   
accumulateminanyshaperangesetflatnonzerotolistr   r#   extendr   )%diagnosticsweights_throttled
ret_filledls_daily_ret	long_mask
short_masktop_nlong_contribshort_contribrx   posnegloss_summusdeqpeakddlgsg
long_grossshort_grosslmsmM
long_turnsshort_turnsmlplcspsckall_long_spellsall_short_spellscolr   s%                                        r+   (_populate_cross_sectional_ls_diagnosticsr      sa    &***5
BGGQGOVVWYWaWabL&++#+6CHHaHPWWXZXbXbcMC ''

3<<2::<NA
AE(C
AE(C6K"%c!f+K=@VE1q5,,.$9uK !8;5#4eElK 9<E#((*$5uU|K !#&88uSWWYuH,/HHA"++V^J_cggi#h-'(ejkpeq    FqvvxeB!$Q!quu!u}	uBEG%Zb2775>)AUZ[`UaK%&	C!G	B::  $D	dSB58W%/%,K			RZZ		0B			bjj		1B"''c'266A6>GGbjjGYJ$)))488a8@IIPRPZPZI[[K	d	B	t	B&(ffRjr"vz!"eEl )* ')ffRjr"vz!"eEl *+ .1]K)*.1"&&(mK*+IOOAAAv "
!#q! 	-AR^^Ia!e$45<<>?BR^^IaL188:;Bc"r'l+R^^Jq1u$56==?@BR^^JqM299;<Bs27|,	- 25Q-.>CBGGJDW>X:;?DRWW[EY?Z;<14S_1E-.25c+6F2G./03C
Oq4H0I,-14S5E5J1K-.14Q-.
 	*A #5\KN	* $'u:K !#O"$ (( Pc"++"**+=:1tLM ;A NOP
 ,;bggo&'e () -=bgg&'(%, )* 25S5I1JK-.25c:J6K2LK./(-K$%r-   c                
   | sy| j                  d      dk7  ry| j                  d      rt        d| d    d       yt        d       t        d| j                  d	d
       d| j                  dt        d            dd| j                  dt        d            dz  dd       t        d| j                  dd      dz  dd| j                  dt        d            dz  dd| j                  dt        d            dz  dd| j                  dt        d            d       t        d| j                  dt        d            dz  dd | j                  d!d       d"       t        d#| j                  d$t        d            dz  dd | j                  d%d       d"       t        d&| j                  d'd
       d(| j                  d)d*       d+| j                  d,d
              t        d-| j                  d.t        d            dd/| j                  d0t        d            d       t        d1| j                  d2d
       d/| j                  d3d
       d4       t        d5| j                  d6d
       d/| j                  d7d
              t        d8| j                  d9t        d            d:d/| j                  d;t        d            d:d<| j                  d=d
       d>| j                  d?d
       d@	       t        dA       y)BzHPretty-print dict from :func:`_populate_cross_sectional_ls_diagnostics`.Nr   r   notez)
  --- L/S sleeve (AQR cross-sectional): z ---uD   
  --- L/S sleeve (AQR cross-sectional) — trade & return stats ---z  Trading days: r   u   —z  |  Sharpe (approx, ann.): r   r   .3fz  |  max drawdown: r   d   .2f%z  Daily win rate: r   r   z%  |  avg win day: r   z.4fz%  |  avg loss day: r   z%  |  profit factor: r   u>     Long leg (pre–vol-target PnL): win rate on days w/ longs: r   z%  (r   z days)u@     Short leg (pre–vol-target PnL): win rate on days w/ shorts: r   r   z  Monthly rebalance rows: r   z (r   r   z)  |  top_n per leg: r   u$     Avg names changed/rebal — long: r   z  |  short: r   u$     Cumulative slot changes — long: r   r   u)     (~½ = one-sided entries/exits per leg)u2     Approx. completed round-turns per leg — long: r   r   u     Avg days per spell — long: r   .1fr   z  (spells: r   z long / r   z short)u     Note: Leg win rates use pre–vol-targeting daily attribution; Sharpe/drawdown use final L/S daily returns after regime + vol scale.)getprintrq   )ds    r+   $print_cross_sectional_ls_diagnosticsr     sm   uuX44uuV}:1V9+TJK	
QR	
15578 9""#%%(=uU|"LS!Q R~uU|<sB3Gq	J
 

QUU#3Q7#=cB CouU|<sB3G H/u>DSI J%%u>sC	E 

H55*E%L9C?
D EEE+Q/0	8
 

J55+U5\:S@
E FEE,a01	9
 

$QUU+De%L$MRPQPUPUVjlqPrOs t%%78	: 

.quu5[]bch]i/jkn.o p%%?uNsS	U 

.quu5NPU/V.W X%%2E:; <2	3
 

<QUUC[]b=c<d e%%159:	< 

)!%%0H%PU,*WX[)\ ]%%15<@E FEE7?@OmotIuHvv}	
 
	Pr-   c                   t        |      }|dk  rt        d      | j                         }|j                  d      }| j                  d      }|j	                  dt        |      z  |d      j                         }|j	                  dt        |      z  |d      j                         }||j                  dt        j                        z  }ddd|z   z  z
  j                  t        j                        S )	zKRSI with Wilder smoothing (``period`` matches common RSI parameterization).r6   zRSI period must be >= 1rf   r   r;   Falphar3   adjust      Y@)rs   r"   diffrp   ewmrq   rJ   rn   r%   r   r$   r&   )	r   periodpdeltagainlossavg_gavg_lrss	            r+   _wilder_rsir   F  s    FA1u233JJLE::C: DF==s=#DHH3q>qHGLLNEHH3q>qHGLLNE	sBFF+	+BUcBh'(00<<r-   c                   t        |      }|dk  rt        d      |j                  d      }t        j                  | |z
  j                         | |z
  j                         ||z
  j                         gd      j                  d      }|j                  dt        |      z  |d      j                         j                  t        j                        S )z)Average True Range with Wilder smoothing.r6   zATR period must be >= 1r   r;   Fr   )rs   r"   rt   rC   concatr   rr   r   rq   rJ   r$   r%   r&   )highlowr   r   r   prevtrs          r+   _wilder_atrr  T  s    FA1u233;;q>D	
*			dTk..03:2B2B2DE
 
cqck  66eAhAe6DIIKRRSUS]S]^^r-   c                   |j                   }d| j                  v r2| d   j                  t        j                        j                  |      }nBd| j                  v r2| d   j                  t        j                        j                  |      }n|}d| j                  v r2| d   j                  t        j                        j                  |      }nBd| j                  v r2| d   j                  t        j                        j                  |      }n|}|j                  t        j                        |j                  t        j                        fS )zRUse OHLC when present; otherwise approximate range from close changes (ATR proxy).r  Highr  Low)r@   r#   r$   r%   r&   rG   )dfr   idxhilos        r+   _high_low_for_atrr  a  s    
++CZrzz*2237	2::	Zrzz*2237

Ybjj)11#6	"**	Ybjj)11#699RZZ "))BJJ"777r-   c                    |dk  rg S | dk  rdg|z  S | |z  }| |z  }t        |      D cg c]  }|||k  rdndz    c}S c c}w )z_Split ``n_slots`` into ``n_bins`` nonnegative integers that sum to ``n_slots`` (largest first).r   r6   )r   )n_slotsn_binsbaseremr   s        r+   _distribute_slotsr  s  sZ    {	!|sV|fD
F
C27-@QDSAa(@@@s   A c           	        | j                   \  }}t        j                  ||ft              }t        j                  ||ft              }t	        |      D ]  }	t        t	        |      D 
ch c]  }
||	|
f   s||
    c}
      }|rt        |t        |            }t        |      D ]  \  }}||   }|dk  rt        j                  ||k(  ||	   z        d   }|j                  dk(  rBt        t        |      t        |j                              }| |	|f   j                  t        j                        }t        j                  | |dz
        d| }d||	||   f<    t        t	        |      D 
ch c]  }
||	|
f   s||
    c}
      }|s;t        |t        |            }t        |      D ]  \  }}||   }|dk  rt        j                  ||k(  ||	   z        d   }|j                  dk(  rBt        t        |      t        |j                              }| |	|f   j                  t        j                        }t        j                  ||dz
        d| }d||	||   f<     ||fS c c}
w c c}
w )z
    Each rebalance month, split ``top_n`` long seats and ``top_n`` short seats across sectors
    that have eligible names on that leg; within a sector, rank by ``aqr_mom`` (long: high,
    short: low) with eligibility already encoded.
    rj   r   r6   NT)r   r%   zerosboolr   sortedr  r   	enumeraterO   r   r   rs   r$   r&   argpartition)aqr_arreligible_longeligible_short
sector_idsr   r   Nr   r   r   r   long_sectorsslotssirR   ksidxstakeaqpartshort_sectorss                        r+   _sector_neutral_ls_masksr*  ~  s_    ==DAq!Qt,I1a&-J1X 1eAhV-PQSTPTBUz!}VW%eS->?E"<0 
0A2Y7xxqM!4D DEaH99>3r7C		N3QW%,,RZZ8sD1H5et<+/	!T$Z-(
0 uQxX!>RSUVRVCW
1XY%eS-?@E"=1 
1A2Y7xxqN14E EFqI99>3r7C		N3QW%,,RZZ8r4!84Ud;,0
1d4j=)
1'1< j  ; W  Ys   +I-
8I-
1I2
>I2
c                    | st        d      | D cg c]  }|j                   }}t        j                  | d      }||_        |j                         }||fS c c}w )zk
    Concatenate a list of per-asset Series into a single DataFrame.

    Returns (panel_df, columns).
    zNo frames providedr6   r   )r"   rA   rC   r  r#   rF   )framesrR   colspanels       r+   _align_panel_framesr/    s^     -.."#qAFF#D#IIf1%EEME$;	 $s   Ac                  `    e Zd ZdZ	 ddddddddddd			 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dd
Zy)CrossSectionalMomentuma  
    Market-neutral Long/Short momentum strategy (monthly rebalance, daily PnL).

    Uses AQR 12-minus-1 momentum (`aqr_mom`) to rank a universe, then forms:
      * Long leg = top_n names (highest aqr_mom)
      * Short leg = bottom_n names (lowest aqr_mom), smart-short filter

    With ``sector_map``, the same ``top_n`` long/short *names* are split across sectors
    (within-sector momentum rank). That cuts sector crash risk but usually **hurts raw momentum
    PnL**: the factor often earns from concentrating in the strongest names wherever they sit,
    and sector quotas add lower-momentum longs and less extreme shorts.

    Optional SPY-based regime throttle scales gross exposure (trend + vol penalties)
    and parks the remainder in cash at ``cash_annual_yield``.

    Longs require ``aqr_mom > long_aqr_momentum_min`` (default 5%) to avoid weak/sideways names.

    Post-PnL volatility targeting: 20d realized vol of the L/S return stream, scale toward
    ``target_annual_vol`` (cap + smoothed scale, shifted 1 bar; cash on under-allocated sleeve).

    Lookahead avoidance:
      * monthly weights forward-filled, shifted 1 bar
      * regime exposure shifted 1 bar after SPY filters
      * vol scale shifted 1 bar after smoothing
    N{Gz?r^   r_   r`   g?T)	r(   cash_annual_yieldra   vol_scale_capvol_scale_smooth_dayslong_aqr_momentum_min
sector_mapverboser   c       	           |dk  rt        d      |st        d      t        |j                               }g }g }g }g }|D ]  }||   }h d}|j                  |j                        }|rt        | dt        |             t        j                  |j                        j                  d      }|j                         }||_	        |j                         }|j                  |d   j                  t        j                         j#                  |             |j                  |d   j                  t        j                         j#                  |             |j                  |d	   j                  t        j                         j#                  |             |j                  |d
   j                  t        j                         j#                  |              t%        |      \  }}t%        |      \  }}t%        |      \  }}t%        |      \  }}|j                  j'                  |j                        j'                  |j                        j'                  |j                        j)                         }|j+                  |      }|j+                  |      }|j+                  |      }|j+                  |      }|j-                         }|j-                         }|j-                         }|j/                  dd      j1                  d      }|j3                  d      j5                         j                  }|j3                  d      j5                         }|j3                  d      j5                         } |j3                  d      j5                         }!|j3                  d      j5                         }"|j7                  t        j                         }#| j7                  t        j                         }$|!j7                  t        j                         }%|"j7                  t        j                         }&t        j8                  |#      }'|#j:                  \  }(})|(dk  s|)dk  rEt        j<                  d|dt        j                         }*||j?                          d|d<   d|d<   |*S ||)kD  rt        d| d|)       |'|#tA        |      kD  z  }+|$|%k  |'z  },t        jB                  |(      dddf   }-|	ht        jD                  |j                  D cg c]&  }tG        |	jI                  tG        |      d            ( c}tJ              }.tM        |#|+|,|.|      \  }/}0nt        jN                  |+|#t        jP                         }1t        jR                  |1 |dz
  d      ddd|f   }2t        jT                  |(|)ftV              }/d|/|-|2f<   |/|+z  }/t        jN                  |,|#t        jP                        }3t        jR                  |3|dz
  d      ddd|f   }4t        jT                  |(|)ftV              }0d|0|-|4f<   |0|,z  }0|0jY                  d      j                  t        j                         }5t        jN                  t        j8                  |&      |&dkD  z  d|&z  d      }6|6|/z  }7|7jY                  d      }8t        jZ                  |7|8dddf   t        j\                  |7t        j                         |8dddf   dkD         }9|6|0z  }:|:jY                  d      };t        j^                  d|5tA        |      z        }<t        jZ                  |:|;dddf   t        j\                  |:t        j                         |;dddf   dkD         }=|= |<dddf   z  }=|9|=z   }>t        j`                  |>||j                  !      }?|?j+                  |      j-                         }@|@jc                  d      je                  d      }@|je                  d      j                  t        j                         }Atg        ||      }Bti        |B|      }C|Cjc                  d      je                  d      jk                  dd      }DtA        |      d"z  }E|@jm                  |Dd      }F|Fj7                  t        j                         |Aj7                  t        j                         z  jY                  d      }Gd|Dj7                  t        j                         z
  |Ez  }Ht        j<                  |G|Hz   |t        j                   #      }Ito        |I|EtA        |      tA        |      tq        |      d$      \  }J}K|
rFjs                         jY                  d      j                  t        j                         }Ltu        |L      rtA        Ljw                               d%z  n
tA        d&      }Mtu        D      rtA        Djw                               n
tA        d&      }Nty        d'Md(d)|Nd*d+       tu        K      rtA        Kjw                               n
tA        d&      }Oty        d,tA        |      d(d-tA        |      d(d.tq        |       d/|Od*       |+|j?                          t{        |FAJ|/|0tq        |      0       Jj#                  d      S c c}w )1a  
        Parameters
        ----------
        equity_dict
            ticker -> DataFrame containing columns:
              * aqr_mom, ret, close, sma_200
        top_n
            Number of names on each leg (long + short).
        spy_df
            SPY daily panel (``close``, optional ``ret``, ``sma_200``). If omitted, uses
            ``equity_dict['SPY']`` when present.
        cash_annual_yield
            Annual yield on the capital not deployed to the L/S book when exposure < 1,
            and on the sleeve not used after vol scaling when scale < 1.
        target_annual_vol
            Annualized vol target for the L/S return stream (after regime throttle).
        vol_scale_cap
            Maximum vol-scaling leverage (e.g. 2.0).
        vol_scale_smooth_days
            Moving-average window on the capped scaling factor.
        long_aqr_momentum_min
            Minimum AQR momentum (fraction) required to be eligible for the long leg.
        sector_map
            If set, allocate long/short seats per sector (even split of ``top_n`` per leg);
            tickers missing from the map use sector "Unknown".
        verbose
            If True, print diagnostics (regime gross exposure, vol targeting).
        diagnostics
            If a dict is passed, it is cleared and filled with L/S trade and return
            statistics (see :func:`print_cross_sectional_ls_diagnostics`).

        Returns
        -------
        pd.Series
            Daily Long/Short returns indexed by date.
        r   top_n must be > 0equity_dict cannot be empty>   r4   r   aqr_momr/    missing required columns: Nr<  r4   r   r/   r5   r1   r6   r7   r   rj   r   rf   ls_retr?   r   r   $insufficient_monthly_rows_or_tickersr   top_n= cannot exceed universe size N=Unknown)kthr   Tr   r;   )r*   rO   r@   r#   rg   rk   rw   ra   rb   rc   rd   r   r   z,  L/S regime throttle: avg gross exposure = r   u;   % (full-throttle reference ≈ 200%; avg regime factor ≈ r   )z$  L/S vol targeting: target_ann_vol=z cap=z smooth=u   d | avg executed scale ≈ )r   r   r   r   r   r   )>r"   listkeys
differencer#   r'   r  rC   rD   r@   rE   r!   rF   r   r$   r%   r&   renamer/  unionsort_valuesrG   rH   rI   rL   resamplelastrN   r   r   rP   clearrq   arangearraystrr   objectr*  rO   ro   r  r  r  r   divide
zeros_likeminimum	DataFramert   rl   r,   r]   rp   mulr   rs   r   r   rJ   r   r   )Pselfr)   r   r(   r3  ra   r4  r5  r6  r7  r8  r   tickersaqr_panret_pan	close_pansma_pantr  requiredmissingr  df2aqr_df_ret_dfclose_dfsma_dfmaster_index	vol_20_dfbm_indexaqr_mclose_msma_mvol_mr  	close_arrsma_arrvol_arrvalid_ar   r   out_zr  r  rowsr  r   r   aqr_for_longidx_longaqr_for_short	idx_shortcount_shortinv_vollong_rawlong_raw_sumlong_w	short_rawshort_raw_sumscale_factorshort_w	weights_mweights_m_df	weights_dr   	spy_panel
regime_rawregime_execrw   r   ls_core
cash_carryrv   r   r   grossavg_gross_pctr   	avg_scalesP                                                                                   r+   generate_ls_returnsz*CrossSectionalMomentum.generate_ls_returns  s\	   f A:011:;;{'')*#%#%%'	#% 	HAQB=H))"**5G!$?w?PQRR..*66t<C'')CCI.."CNN3y>00<CCAFGNN3u:,,RZZ8??BCS\00<CCAFGNN3y>00<CCAFG	H" (0	'0	))4!'0	 ||))&,,7==hnnMSSTZT`T`ammo--##L1- >># NN""N=AAqAI	 ??5)..066&++-##E*//1&++-""5)..0..rzz.2$$2::$6	..rzz.2..rzz.2++g&}}1q5AE IIcHBJJWE&!!#(BH%&LF#L19veW,KA3OPP7U3H-I#IJ#g-8yy|AtG$!SXS`S`"aa3z~~c!fi'H#I"aiopJ$<
E%!Iz
 88M7RVVGDL}%!)!LQPVQVPVYWH!Qt4I(,IdHn%&I HH^WbffEM5191MaQWRWQWiXI1a&5J*.JtY'.(J !nn!n,33BJJ? ((2;;w/7S=A3=RUV Y&|||+D!hbjj9q$w'#-	
 j(	!1-zz#{U5\'AB))!T'"irzz:4(3.	
 (\!T'22 W$	||IXu}}U !((6<<>	OOA&--c2	 ]]3'..rzz:
 'v{;	,YE
 &&q)005::3D*+e3%MM+AM>$--BJJ-?*BUBU\^\f\fBUBggllrsltK00rzz0BBhN
YYw3<rzzZ
#:#$56M*34$
 j %))+//Q/7>>rzzJE;>u:E%**,/%75QV<M14[1AE+**,-uU|E>}S>Q RLLQRU;VWY 58
Ojoo/0uI6u=N7OPS6T U]+C0=R9S8T U,,5c?< "4"3%)#%%j ""8,,Y #bs   +j?
   )r)   Dict[str, pd.DataFrame]r   rs   r(   pd.DataFrame | Noner3  rq   ra   rq   r4  rq   r5  rs   r6  rq   r7  Dict[str, str] | Noner8  r  r   dict | Nonereturn	pd.Series)__name__
__module____qualname____doc__r   r-   r+   r1  r1    s    : r-
 '+#'#'"%&'+,0#'r-,r- r-
 $r- !r- !r- r-  #r-  %r- *r- r- !r- 
r-r-   r1  c                     e Zd ZU dZ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<   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<   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 <   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*<   dZded+<   	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d2d,Z	 d3d!d-d.d/ddd0	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d4d1Zy!)5BuyTheDipSleevea  
    **Long-only buy-the-dip** sleeve (daily signals, fixed holding period, daily PnL).

    Classic rule set (configurable): **close > SMA(200)** (long-term uptrend) and washout signal
    (RSI or pct-drop). Among new signals each day, keep up to ``top_n`` names ranked by score.
    Each entry is held exactly ``hold_trading_days`` sessions; PnL uses close-to-close returns
    (signal at close *t*, first return day *t+1*). Optional ``max_concurrent`` caps **total**
    names held at once (skips new entries when the book is full).

    At most **one active trade per ticker**; overlapping names are weighted **equal** or
    **inverse trailing vol** among names active that day. Optional **SPY > SMA200** gate applies
    only when **opening** new positions.

    Same SPY regime throttle and trailing vol targeting on the sleeve return stream as other equity
    sleeves. Universe membership is **as loaded** (e.g. current S&P list), not point-in-time
    constituents.
    r`   rs   
rsi_periodg      4@rq   rsi_max
atr_periodhold_trading_daysr  rd   r0   sma_trend_windowequalrR  	weightingatr_normrank_byTr  dip_in_uptrendFonly_when_spy_bullrsisignal_modeQ?pct_drop_min      @min_atr_norm_pctrf   relative_spy_min	hedge_spyN
int | Nonemax_concurrentlegacyexecution_styleg?limit_atr_multr<   profit_atr_multexit_on_prior_highcracking_markets_purec                  |j                  t        j                        }|j                  t        j                        }|j                  t        j                        }|j                  t        j                        }|j                  \  }} t	        | j
                        }!t	        | j                        }"t        j                  | dt        j                        }#t        j                  | t        j                  t        j                        }$t        j                  | t        j                  t        j                        }%t        j                  | t              }&t        j                  | t        j                  t        j                        }'t        j                  | t        j                  t        j                        }(t        j                  | dt        j                        })t        j                  |t        j                        }*d}+d},t        |      D ]>  }-|&|)|-dz
  k  z  }.d|&|.<   |-dk\  rz|&|)|-dz
  k(  z  }/t        j                  |/      D ]W  }0|'|0   }1t        j                  |1      r6t        j                  ||-|0f         r||-|0f   |1k  r|-|#|0<   |1|$|0<   |(|0   |%|0<   d|&|0<   Y |#dk\  }2t        j                  | t        j                        }3t        j                  |2      rt        j                  |2      D ]  }0t!        |#|0         }4|4|-kD  r||-|0f   }5|4|-k(  r-|$|0   }6|6dkD  s.t        j                  |5      sD|5|6z  dz
  |3|0<   P|-dkD  sV||-dz
  |0f   }7|7dkD  sft        j                  |5      s||5|7z  dz
  |3|0<    t        j                  |2|#|-k  z        }8|8j"                  dkD  rFt%        |+t!        |8j"                              }+|dk(  rGt        j                  |8j"                  dt	        |8j"                        z  t        j                        }9n||-|8f   }:t        j&                  t        j                  |:      |:dkD  z  d|:z  d      };t	        |;j)                               }<|<dkD  r|;|<z  n5t        j                  |8j"                  dt	        |8j"                        z        }9t	        t        j*                  |9|3|8               |*|-<   t%        |,t	        |9j)                                     },t        j                  |#dk\        D ]  }0t!        |#|0         }4|4|-kD  r|-|4z
  }=|=|kD  }>| j,                  r0|-dkD  r+||-dz
  |0f   }?t        j                  |?      r||-|0f   |?kD  rd	}>|$|0   |%|0   }@}6t        j                  |6      r't        j                  @      r||-|0f   |6|"@z  z   k\  rd	}>|>sd|#|0<    |#dk  |& z  }At!        t        j(                  |#dk\              t!        t        j(                  |&            z   }Bt!        |      }C|t/        Ct%        d|Bz
              }CCdkD  s|-dz   |k  s't        j                  A||-   z        }D|Dj"                  dkD  sS|	j                  t        j                        |-Df   }Et        j&                  t        j                  |E      |Et        j0                         }Ft        j2                  |F d      }G|D|Gd
t/        C|Dj"                            }H|HD ]U  }0||-|0f   }I||-|0f   }Jt        j                  |I      rt        j                  J      s<d	|&|0<   |-|)|0<   J|!Iz  z
  |'|0<   |I|(|0<   W A t5        j6                  |*|t        j                        }Kt        | j8                        r]|rJt	        t        j:                  |*            dz  }Lt=        d|dd|dd|!dd| d|"dd|+ d|,dz  dd       Kj?                  d      S tA        ||      }M|MjC                  d      jE                  d      jG                  dd      }Nt	        |      dz  }Ot5        j6                  |*|      jI                         }P|Nj                  t        j                        }Q|*|Qz  }Rd|Qz
  |Oz  }St5        j6                  |R|Sz   |t        j                        }KtK        |K|Ot	        |      t	        |      t!        |      d      \  }T}U|r?t=        d |+ d!tM        U      rt	        UjO                               n
t	        d"      d#       Tj?                  d      S )$u8  
        CrackingMarkets article rules:
          * Signal: −3% day, SMA200 up, ATR%/close > 3%, rank ATR/close
          * Entry: next session limit at signal_close − limit_atr_mult × ATR(5)
          * Exit (first hit): hold > N days | close > prior high | close ≥ fill + profit_atr_mult × ATR
        rj   r   rf   r6   Fr;   r  TNstablekindrk   r   z!  CrackingMarkets dip (pct_drop>=.1%, ATR%>r\   z, limit=r   u   ×ATR, exits:>zd|prior-high|+z#ATR, rank ATR/px): peak concurrent=u    | peak gross≈r   r   dip_retrg   r@   r5   rE  z5  CrackingMarkets dip (+regime/vol): peak concurrent=    | avg vol scale ≈ r   r   )(rN   r%   r&   r   rq   r  r  fullint32r   r  r  r   r   r   r   rs   r   rr   rO   r   dotr  r   ro   argsortrC   rP   r  nanmeanr   rJ  r]   rt   rl   rp   r   r   r   rJ   )VrY  r)   r   r(   rh  rf  high_dflow_dfatr_dfatrn_dfrg  
ret_chg_dfdip_sigholdmax_concwgrq  r  spy_ret_arrr3  ra   r4  r5  r8  twmin_atr_pctpct_dropro  high_arrlow_arratr_arrDr   lim_mult	prof_multpos_fill_dayfill_px	entry_atrpendingpending_limitpending_atrpending_fromport_retpeak_concurrent
peak_grossr   stale
fill_todayr   limheldname_retfdc_dfpc_prevr%  r   vinvrR   	days_heldexit_nowhi_preveaflatn_openr"  candr   keyorderr&  atr_vclrv   r  r  r  rw   gross_proxyregcorer  dip_daily_retr   sV                                                                                         r+   "_generate_returns_cracking_marketsz2BuyTheDipSleeve._generate_returns_cracking_markets  s0   H %%BJJ%7	##"**#5//

/3//

/31,,-$../	wwq"BHH5''!RVV2::6GGArvvRZZ8	((1D)266<ggarzz:wwq"BHH588ARZZ0
q U	/A|a!e34E"GEN Av$A(=>

3 'A'*C{{3'BKK1,F7STVWSW=\_K_*+Q%(
'21~	!!&GAJ'  1$Dxx4Hvvd|- =A\!_-BAv #AqD/CQw$QZ6bkk#&6*-(S.HQKQ!*1q5!8!4!A:"++c*:*-,*<HQK= ~~dla.?&@A99q=&)/3tyy>&JOW}GGDIIsU4995E/ERZZX#AtG, hhr{{1~S'A37CP!#''),'(1uC!G"''$))S5QUQZQZK[E[2\"'q(4.(A"BHQK!$Zquuw!@J ^^LA$56 )a)6F	$t+**q1u&q1uax0G{{7+	!Q$'0I#' Yq\B;;r?r{{2 A"y2~*==#'&(LO)$ !1$'2D 123c"&&/6JJFJE#E3q(V*;#<=qyQUQY~~dWQZ&7899q= ))

);AtGDB((2;;r?B@CJJt(;E&=E499(= >?D! / '1&q!t_ "E 2r{{2$%)
*+Q+-50@+@a().A/[U	/n YYx|2::N
**+ %bjj&: ;e C7~W[YZO \%cN.nYWZO \55D4E F$$.$4S#9< $$Y//,YE
 &&q)005::3D*+e3ii=AAC"""4#~Ci8+
YYtj0BJJW
$;#$56M*34%
!z GGX Y%ADZU:??+<%=V[\aVbcf$gi ##I..r-   r2  r^   r_   r(   r3  ra   r4  r5  r8  c               "   |dk  rt        d      |st        d      t        | j                        }	t        | j                        }
t        | j                        }t        | j
                        }t        | j                        }|	dk  s|
dk  s
|dk  s|dk  rt        d      t        | j                        j                         j                         }|dvrt        d      t        | j                        }|dk  rt        d	      t        | j                        j                         j                         }|d
vrt        d      t        | j                        }t        | j                        }|dk(  r"d|cxk  rdk  st        d       t        d      t!        |j#                               }g }g }g }g }g }g }g }g }ddh}|D ]_  }||   }|j%                  |j&                        }|rt)        | dt+        |             t-        j.                  |j0                        j3                  d       } |j5                         }!| |!_        |!j7                         }!|!d   j9                  t:        j<                        }"t?        |!|"      \  }#}$tA        |#|$|"|
      }%|%|"jC                  dt:        jD                        z  j9                  t:        j<                        }&tG        |"|	      }'|"jI                  ||      jK                         }(|jM                  |!d   j9                  t:        j<                        jO                  |             |jM                  |"jO                  |             |jM                  |#jO                  |             |jM                  |$jO                  |             |jM                  |%jO                  |             |jM                  |'jO                  |             |jM                  |&jO                  |             |jM                  |(jO                  |             b tQ        |      \  })}*tQ        |      \  }+}*tQ        |      \  },}*tQ        |      \  }-}*tQ        |      \  }.}*tQ        |      \  }/}*tQ        |      \  }0}*tQ        |      \  }1}*|)j0                  jS                  |+j0                        jU                         }2|)jW                  |2      })|+jW                  |2      jY                         }+|,jW                  |2      jY                         },|-jW                  |2      jY                         }-|.jW                  |2      }.|/jW                  |2      }/|0jW                  |2      }0|1jW                  |2      }1|+j[                         }3t        | j\                        }4|)jI                  |4|4      j_                  d      }5ta        |2      ta        |+j&                        }7}6|6dk  s|7dk  r't-        jb                  d|2dt:        j<                        S ||7kD  rt        d| d|7       | jd                  }8|8t        |8      }8|8dk  rt        d      tg        ||      }9|9d   j9                  t:        j<                        }:t-        j.                  |:j0                        j3                  d       |:_        |:jW                  |2      jY                         }:d|9j&                  v ru|9d   j9                  t:        j<                        };t-        j.                  |;j0                        j3                  d       |;_        |;jW                  |2      jY                         };n!|:jI                  dd      jK                         };|:|;kD  ji                  d      jk                  tl              }<|:j[                         jW                  |2      ji                  d      }=|=jk                  t:        j<                        }>|+jk                  t:        j<                        }?|/jk                  t:        j<                        }@|1jk                  t:        j<                        }A|0jk                  t:        j<                        }B|3jk                  t:        j<                        }C|5jk                  t:        j<                        }Dt;        jn                  |?      t;        jn                  |B      z  }E|dk(  rmEt;        jn                  C      z  }E|E|C| k  z  }F|FBdz  |kD  z  }F|dkD  r\|>d d t:        jp                  f   }GC|Gz
  }HFt;        jn                  |H      |H| k  z  z  }Fn Et;        jn                  @      z  }E|E|@|k  z  }Ftm        | jr                        rFt;        jn                  A      |?|AkD  z  z  }Ft        | jt                        j                         j                         }I|Idk(  rjt        | jv                        j                         }J|Jd vrt        d!| jv                        | jy                  ||||2|+|,|-|.|0|1|3F||8JD|9|>||||||||"      S t;        jz                  |7d#t:        j|                        }Kt;        j~                  |6|7ft:        j<                        }Lt        | jv                        j                         }J|Jd vrt        d!| jv                        tm        | j                        }Md}Nt        |6      D ]<  }OKdk\  |O|K|z   kD  z  }Pd#|K|P<   |Kdk\  |K|Ok  z  |O|K|z   k  z  }Qt;        j                  |Q      }R|Rj                  dkD  rt        Nt        Rj                              }NJd$k(  r dt        Rj                        z  }S|SLO|Rf<   nbDORf   }Tt;        j                  t;        jn                  |T      |TdkD  z  d|Tz  d      }Ut        |Uj                               }V|VdkD  r
UVz  LORf<   Kdk  }Wt;        j                  |6tl              }XMr|<}XXO   s t;        j                  WFO   z        }Y|Yj                  dkD  sL|d%k(  rvBOYf   j9                  t:        j<                        }Zt;        j                  t;        jn                  |Z      |Zt:        j                         }[t;        j                  |[ d&'      }\n|d(k(  rCOYf   |>|O   z
  }]|] j9                  t:        j<                        }Zt;        j                  t;        jn                  |Z      |Zt:        j                         }[t;        j                  |[ d&'      }\ns@OYf   j9                  t:        j<                        }Zt;        j                  t;        jn                  |Z      |Zt:        j                        }[t;        j                  |[d&'      }\t        |      }^|8?Rj                  dkD  rt        Rj                        nd}_t        ^t        d|8|_z
              }^t        ^t        Yj                              }`|`dkD  s0Y\d `    }aOK|a<   ? t-        j                  L|2|+j&                  )      }b|)ji                  d      j9                  t:        j<                        }ct        |9|2      }d|dj                  d      ji                  d      j                  dd      }et        |      d*z  }f|bjk                  t:        j<                        }g|ejk                  t:        j<                        }h|g|hd d t:        jp                  f   z  }i|ij                  d+      }j|i|cjk                  t:        j<                        z  j                  d+      }ktm        | j                        rk|>jz  z
  }kdhz
  fz  }lt-        jb                  k|lz   |2t:        j<                  ,      }mt        |m|ft        |      t        |      t        |      d-.      \  }n}o|rVij                  r1t        t;        j                  ij                  d+                  dz  n
t        d/      }pta        e      rt        ejK                               n
t        d/      }qtm        | jr                        rd0| d1nd2}rMrd3nd4}s|d%k(  rd5}tn
|d(k(  rd6}tnd7}t|dk(  rd8|d9d:|d;}u|dkD  rud<|d9z  }un	d=|	 d>|d;}utm        | j                        rd?nd@}vdAN |8dB|8 dCnd@z   }wt        dDu dE| dFr dGs dGt dGJ v dHpdIdJqdKdLta        o      rt        ojK                               n
t        d/      dKw        njO                  d      S )MNr   r:  r;  r6   zHrsi_period, atr_period, hold_trading_days, sma_trend_window must be >= 1)r  r  rel_underperfz5rank_by must be 'atr_norm', 'rsi', or 'rel_underperf'rf   zrelative_spy_min must be >= 0)r  r  z'signal_mode must be 'rsi' or 'pct_drop'r  r;   z7pct_drop_min must be in (0, 1) for pct_drop signal_moder   r4   z+ missing required columns for buy-the-dip: r1   r7   r   ls_short_retr?   r@  rA  z$max_concurrent must be >= 1 when setr/   r0   Frj   r   cracking_marketsr  rz  ,weighting must be 'equal' or 'inv_vol', got )r(   rh  rf  r  r  r  r  rg  r  r  r  r  r  rq  r  r  r3  ra   r4  r5  r8  r  r  r  r  r  r  r  r  r  rD  rg   r   rk   r5   rE  r   SMAz uptrendzno SMA filterzSPY>SMA200@entryzany SPYzrank ATR/pxzrank rel underperf vs SPYzrank RSIz
pct_drop>=r  r  r\   z, underperf SPY>=zRSI(z)<z, SPY-hedged z | max concurrent holdings=z (cap rF  z  Buy-the-dip (, hold d, z,    ): avg sleeve equity ≈ r      % | regime ≈ r   r  )Sr"   rs   r  r  r  r  rq   r  rR  r  r   stripr  r  r  r  rG  rH  rI  r#   r'   r  rC   rD   r@   rE   r!   rF   r$   r%   r&   r  r  rn   r   r   rI   rJ   r   rJ  r/  rK  rL  rG   rH   rK   rd   rL   r   rP   r  r,   rl   rN   r  r   newaxisr  r  r  r  r  r  r  r  r   r   r   rr   rO   r   onesro   r  r   rW  r]   rt   rp   r  r   r  r   )xrY  r)   r   r(   r3  ra   r4  r5  r8  rsi_patr_pr  r  rsi_thrrkrel_spy_minsig_moder  r  rZ  r\  r]  high_panlow_panatr_panrsi_panatrn_panr^  reqr_  r  ra  r  rb  close_sr  r  atr_satrn_srsi_ssma_sre  rd  rf  r  r  r  rsi_dfr  rg  rh  r  vwvol_dfr  r   r  r  	spy_closespy_sma
spy_bull_d	spy_ret_sr  ro  rsi_arrrp  atrn_arrret_chg_arrrq  base_okr  spy_row
rel_vs_spy
exec_styler  active_entryweights_matspy_gater  r   expiredin_trader%  w_eachr  r  rR   r  open_okr  r   r  r  relr"  r  take_nchosenr  r   r  r  rw   w_arrr  w_thr  r  r  rv   r  r   r  r   upr   rk_msgsig_msg	hedge_msgconc_msgsx                                                                                                                           r+   generate_returnsz BuyTheDipSleeve.generate_returns  s]    A:011:;;DOO$DOO$4))*&&'%19	TAXaghh$$&,,.99TUUD112<==t''(..0668..FGG**+D112z!3+?C+?VWW ,@VWW{'')*#%%'	$&#%#%#%$&#% 	,AQBnnRZZ0G!$OPVW^P_O`abb..*66t<C'')CCI.."C'l))"**5G&sG4FBB7Egooc266::BB2::NF/EOO22O>CCEENN3u:,,RZZ8??BCW^^A./OOBIIaL)NN299Q<(NN5<<?+NN5<<?+OOFMM!,-NN5<<?+5	,8 (0	))4!(2
'0	'0	'0	(2
'0	||))(..9EEG-##L1779//,/557-335--//,/-((*
!rr:>>A>F< #h&6&6"71q5AE99S>QSQ[Q[\\19veW,KA3OPP&&8}H!| !GHH&v{;	g&--bjj9	..9EEdK	%%l399;		)))	*11"**=GNN7==9EEdKGMool399;G''s'DIIKG')11%8AAAM
((*22<@GGL	((rzz(:%%BJJ%7	//

/3//

/3##"**#5 ))

);//

/3++i(2;;x+@@z!r{{;//G	!9:G5(K77GS %am4(72
2;;z2j[L6PQQr{{7++G7!23G##$r{{7+y7/BCCG--.446<<>
++T^^$**,B-- #OPTP^P^Oa!bcc::)!%!#'"3"3+&;'!5 ;  : wwq"BHH5hh1vRZZ8 &&())KDNNK]^__//0q /	1A#q(Q1D-DEG$&L!$)lQ.>?1W[H[C[\H>>(+Dyy1}"%os499~"F= 5#33F+1K4(4(A((2;;q>QW#=sQwLCcggi(A3w/2QwAtG,!#Dggat,G$qz~~dWQZ&7899q=Z'%ag.55bjjA hhr{{2RVVGD "

C4h ?.)!T'2[^C"d]]2::6 hhr{{2RVVGD "

C4h ?$QW-44RZZ@ hhr{{2BFFC "

3X >JE+3799q=TYYa #E3q(V2C+D E DII7Fz!%eGVn!5/0V,_/	1b LLL(JZJZ[	]]3'..rzz:
,YE
 &&q)005::3D*+e3"""4"""4s1bjj=))a z***<<AAqAI+--DCi8+
YYtj0BJJW
$;#$56M*34%
!z KO99E"**TXX1X-=">?%GZ_`eZfM14[1AE+**,-uU|E'+D,?,?'@3rd(#oB'/#YBZ&&4#:%&xnGK?K$!2;s2CDDG r'!5*.t~~*>BI-o->?+3+?VH:Q'RI  !''$s2$bBvhbQSPTU^T_ `))6s(;?5QT+ V%ADZU:??+<%=V[\aVbcf$g* ##N33r-   )6r)   r  r   rs   r(   r  rh  pd.DatetimeIndexrf  pd.DataFramer  rA  r  rA  r  rA  r  rA  rg  rA  r  rA  r  
np.ndarrayr  rs   r  r  r  rR  rq  rB  r  rA  r  rB  r3  rq   ra   rq   r4  rq   r5  rs   r8  r  r  rs   r  rq   r  rq   r  r  r  r)   r  r   rs   r(   r  r3  rq   ra   rq   r4  rq   r5  rs   r8  r  r  r  )r  r  r  r  r  __annotations__r  r  r  rd   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r?  r  r-   r+   r  r    s   $ JGUJsJcIsGSND$$KL%!e!!e!It!%NJ% $OS#NE OU ##"'4'p/,p/ p/
 $p/ 'p/ p/ p/ p/ p/ p/ p/ !p/ p/ p/  !p/" #p/$ %p/&  'p/(  )p/* !+p/, !-p/. /p/0  #1p/2 3p/4 5p/6 7p/8 9p/: 
;p/j j4
 '+#'#'"%&j4,j4 j4
 $j4 !j4 !j4 j4  #j4 j4 
j4r-   r  c                      e Zd ZU dZ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<   dZ
ded<   dZded<   dZded<   	 dddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZy)SellTheRipSleeveu  
    **Short sell-the-rip** sleeve — mirror image of :class:`BuyTheDipSleeve`.

    Signal (configurable): asset has a **rally day** (prior close-to-close return ≥ ``pct_rise``)
    while trading in a **downtrend** (close < SMA ``sma_trend_window``).  Among candidates
    each day, up to ``top_n`` names are **shorted** for ``hold_trading_days`` sessions, ranked
    by highest normalized ATR (most volatile expected to resume the down-leg fastest).

    Optional gate ``only_when_spy_bear=True``: only open new short positions when the SPY
    itself is below its SMA-200 (i.e. broad bear market).  Default is **off** — the strategy
    targets individual stock downtrends regardless of index regime.

    Returns are **negated** (short position) so a falling price produces positive P&L.
    Regime throttle and vol targeting applied to the resulting stream, identical to the
    long dip sleeve.
    r`   rs   r  r  r  rd   r0   r  r  rR  r  r  rq   pct_rise_minr  r  Fr  only_when_spy_bearNr2  r^   r_   Tr  c                  |dk  rt        d      |st        d      t        | j                        }	t        | j                        }
t        | j                        }t        | j                        }t        | j                        }d|cxk  rdk  st        d       t        d      t        |j                               }g }g }g }g }ddh}|D ]  }||   }|j                  |j                        }|rt        | d	t        |             t        j                  |j                         j#                  d       }|j%                         }||_        |j'                         }|d   j)                  t*        j,                        }t/        ||      \  }}t1        ||||	      }||j3                  dt*        j4                        z  j)                  t*        j,                        }|j7                  ||
      j9                         }|j;                  |d   j)                  t*        j,                        j=                  |             |j;                  |j=                  |             |j;                  |j=                  |             |j;                  |j=                  |              t?        |      \  }} t?        |      \  }!} t?        |      \  }"} t?        |      \  }#} |j                   jA                  |!j                         jC                         }$|jE                  |$      }|!jE                  |$      jG                         }!|"jE                  |$      }"|#jE                  |$      }#|!jI                         }%t        | jJ                        }&|j7                  |&|&
      jM                  d      }'tO        |$      tO        |!j                        })}(|(dk  s|)dk  r't        jP                  d|$dt*        j,                        S ||)kD  rt        d| d|)       tS        ||      }*|*d   j)                  t*        j,                        }+t        j                  |+j                         j#                  d       |+_        |+jE                  |$      jG                         }+d|*j                  v ru|*d   j)                  t*        j,                        },t        j                  |,j                         j#                  d       |,_        |,jE                  |$      jG                         },n!|+j7                  dd
      j9                         },|+|,k  jU                  d      jW                  tX              }-|!jW                  t*        j,                        }.|#jW                  t*        j,                        }/|"jW                  t*        j,                        }0|%jW                  t*        j,                        }1|'jW                  t*        j,                        }2t+        jZ                  |.      t+        jZ                  |0      z  t+        jZ                  |1      z  }3|3|1|k\  z  }4|4t+        jZ                  |/      |.|/k  z  z  }4|4|0dz  |kD  z  }4t+        j\                  |)dt*        j^                        }5t+        j`                  |(|)ft*        j,                        }6tc        | jd                        jg                         }7|7dvrt        d| jd                        tY        | jh                        }8tk        |(      D ]  }9|5dk\  |9|5|
z   kD  z  }:d|5|:<   |5dk\  |5|9k  z  |9|5|
z   k  z  };t+        jl                  |;      }<|<jn                  dkD  r|7dk(  rdt        |<jn                        z  |6|9|<f<   nb|2|9|<f   }=t+        jp                  t+        jZ                  |=      |=dkD  z  d|=z  d      }>t        |>js                               }?|?dkD  r
|>|?z  |6|9|<f<   |5dk  }@|8r|-nt+        jt                  |(tX              }A|A|9   st+        jl                  @|4|9   z        }B|Bjn                  dkD  s*|0|9Bf   j)                  t*        j,                        }Ct+        jp                  t+        jZ                  |C      |Ct*        jv                         }Dt+        jx                  |D d      }Et{        t        |      t        |Bjn                              }F|B|Ed |F    }G|9|5|G<    t        j|                  |6|$|!j                        }H|jU                  d      j)                  t*        j,                        }I|*}Jt        |J|$      }K|Kj                  d      jU                  d      j                  dd      }Lt        |      dz  }M|HjW                  t*        j,                        }N|LjW                  t*        j,                        }O|N|Od d t*        j                  f   z  }P|P|IjW                  t*        j,                        z  js                  d       }Qd|Oz
  |Mz  }Rt        jP                  |Q|Rz   |$t*        j,                         }St        |S|Mt        |      t        |      t        |      d!"      \  }T}U|rPjn                  r1t        t+        j                  Pjs                  d                  dz  n
t        d#      }VtO        L      rt        Lj9                               n
t        d#      }W|8rd$nd%}Xt        d&|d'd(|d)d*| d+|
 d,|X d-|7 d.Vd/d0Wd1d2tO        U      rt        Uj9                               n
t        d#      d1       Tj=                  d      S )3Nr   r:  r;  rf   r;   zpct_rise_min must be in (0, 1)r   r4   z, missing required columns for sell-the-rip: r1   r6   r7   r   sell_rip_retr?   r@  rA  r/   r0   Frj   r   r  r  r  r  r  r  rD  rg   r   rk   r5   rE  r   zSPY<SMA200@entryzany SPY (no bear gate)z  Sell-the-rip (pct_rise>=r  r  r\   z, close<SMAr  r	  z, rank ATR/px, r
  r   r  r   r  )Fr"   rs   r  r  r  rq   rG  r  rG  rH  rI  r#   r'   r  rC   rD   r@   rE   r!   rF   r$   r%   r&   r  r  rn   r   rI   rJ   r   rJ  r/  rK  rL  rG   rH   rK   rd   rL   r   rP   r,   rl   rN   r  r   r  r  r  rR  r  r   rH  r   r   r   rO   r   r  ro   r  r   rW  r]   rt   rp   r  r   r  r   )YrY  r)   r   r(   r3  ra   r4  r5  r8  r  r  r  pct_riser  rZ  r\  r]  r  r^  r  r_  r  ra  r  rb  r  r  r  r  r  r  re  rd  rf  r  rg  rh  r  r!  r"  r  r   r  r#  r$  
spy_bear_dro  rp  r(  r)  rq  r*  rip_sigr.  r/  r  r0  r   r1  r2  r%  r  r  rR   r  r4  r  r   r  r  r6  r7  r  r   spy_panel_for_regimer  r  rw   r8  r  r9  r  r  rv   rip_daily_retr   r  r   gate_msgsY                                                                                            r+   r?  z!SellTheRipSleeve.generate_returns  s	    A:011:;;DOO$4))*&&'**+D112h$$=>> %=>>{'')*#%%'	$&#% 	,AQBnnRZZ0G!$PQWX_Q`Pabcc..*66t<C'')CCI.."C'l))"**5G&sG4FBB7Egooc266::BB2::NFOO22O>CCEENN3u:,,RZZ8??BCW^^A./OOFMM!,-NN5<<?+)	,, (0	))4!(2
'0	||))(..9EEG-##L1779//,/-((*
!rr:>>A>F< #h&6&6"71q5AE99S>QSQ[Q[\\19veW,KA3OPP&v{;	g&--bjj9	..9EEdK	%%l399;		)))	*11"**=GNN7==9EEdKGMool399;G''s'DIIKG')11%8AAAM
%%BJJ%7	//

/3##"**#5 ))

);//

/3 ++i(2;;x+@@2;;{C[[[H452;;w'9w+>??Hu$33wwq"BHH5hh1vRZZ8 &&())KDNNK]^__//0q 	-A#q(Q1D-DEG$&L!$)lQ.>?1W[H[C[\H>>(+Dyy1}=+.tyy1A+AK4(4(A((2;;q>QW#=sQwLCcggi(A3w/2QwAtG,!#D$,j"''!42HGqz~~dWQZ&7899q=!!T'*11"**=B((2;;r?B@CJJt(;E US^<F!%.1F+,L(7	-: LLL(JZJZ[	]]3'..rzz:
  ),-A<P
 &&q)005::3D*+e3"""4"""4s1bjj=))
++"**+==BBBJJCi8+
YYtj0BJJW
$;#$56M*34%
!z KO99E"**TXX1X-=">?%GZ_`eZfM14[1AE+**,-uU|E-5);SH,XcN'+a Q4wtfCz M))6s(;?5QT+ V%ADZU:??+<%=V[\aVbcf$gi ##N33r-   r  rC  )r  r  r  r  r  rD  r  rd   r  r  rG  r  rH  r?  r  r-   r+   rF  rF    s    " JsJcIsL%!e!$$
 s4
 '+#'#'"%&s4,s4 s4
 $s4 !s4 !s4 s4  #s4 s4 
s4r-   rF  c                  L    e Zd ZdZ	 dddd	 	 	 	 	 	 	 	 	 d	dZ	 d	 	 	 	 	 d
dZy)SectorETFRotationaa  
    Long-only sector rotation using SPDR sector ETFs.

    Each month-end, rank ETFs by ``aqr_mom`` (12-minus-1 on ``close``), hold the top ``top_k``
    at equal weight (100% invested in those names). Weights are forward-filled and executed
    from the next bar; any slack weight earns ``cash_annual_yield`` (e.g. early sample or all-NaN month).
    r2  Tr3  r8  c               .
   |dk  rt        d      |st        d      t        |j                               }g }g }|D ]  }||   }	h d}
|
j                  |	j                        }|rt        | dt        |             t        j                  |	j                        j                  d       }|	j                         }||_        |j                         }|j                  |d   j                  t        j                        j!                  |             |j                  |d   j                  t        j                        j!                  |              t#        |      \  }}t#        |      \  }}|j                  j%                  |j                        j'                         }|j)                  |      j+                         }|j)                  |      }|j-                  d      j                  t        j                        }|j/                  d	      j1                         j                  }|j/                  d	      j1                         }|j3                  t        j                  
      }|j4                  \  }}|dk  s|dk  r't        j6                  d|dt        j                        S t        j8                  ||ft        j                  
      }t;        |      D ]  }||   }t        j<                  |      }t        j>                  |      d   }|j@                  dk(  rEtC        tE        |      tE        |j@                              }||   }t        jF                  | |dz
        d | }||   } dtI        |      z  ||| f<    t        jJ                  |||j                        }!|!j)                  |      j+                         jM                  d      j-                  d      }"tI        |      dz  }#|"j3                  t        j                  
      }$|j3                  t        j                  
      }%|$|%z  jO                  d      }&|$jO                  d      }'|&t        jP                  dd|'z
        |#z  z   }(|rn|dkD  ritI        t        jR                  t;        |      D )cg c]  })t        jN                  ||)   dkD         c})            }*tU        dtE        |       d|*d       t        j6                  |(|dt        j                        S c c})w )Nr   top_k must be > 0etf_dict cannot be empty>   r4   r   r<  r=  r<  r4   rf   r   rj   r6   sector_rot_retr?   r;   rD  rg   r   z  Sector ETF sleeve: top_k=u    | avg ETFs held/month ≈ r   )+r"   r  rH  rI  r#   r'   rC   rD   r@   rE   r!   rF   r   r$   r%   r&   rJ  r/  rK  rL  rG   rH   rl   rM  rN  rN   r   rP   r  r   r   nonzeror   r   rs   r  rq   rW  rt   r   ru   rJ   r   )+rY  etf_dicttop_kr3  r8  rZ  r[  r\  r_  r  r`  ra  r  rb  rc  rd  re  rh  r   rj  rk  r  r   r   r  r   momvalid	idx_validr   sub_mom
pick_localr7  r  r  rw   r   rx   r  wsumr*   r   avg_ks+                                              r+   r?  z"SectorETFRotation.generate_returns  s'    A:011788)#%#% 	DA!B2H))"**5G!$?w?PQRR..*66t<C'')CCI.."CNN3y>00<CCAFGNN3u:,,RZZ8??BC	D (0	'0	||))&,,7CCE-335-]]3'..rzz:
??5)..066&++-..rzz.2}}1q5AE99S;KSUS]S]^^HHaV2::6	q 
	2A!*CKK$E

5)!,I~~"CJINN 34A)nG'1q59"1=Jz*F#&q>Iai 
	2 ||IXv~~V ((6<<>DDQGNNsS	*+e3RZZ0bjj1A{{{"uu!u}RZZS4Z08;;q1u"''U1X"N266)A,*:#;"NOPE/E
|;VW\]`VabcyyL7GrzzZZ #Os   #"Tc                D	   |dk  rt        d      |st        d      t        |j                               }g }|D ]  }||   }h d}|j                  |j                        }|rt        | dt        |             t        j                  |j                        j                  d      }	|j                         }
|	|
_        |
j                         }
|j                  |
d   j                  t        j                        j!                  |              t#        |      \  }}|j                  j%                         }|j'                  d      j)                         j                  }|j'                  d      j)                         }|j+                  t        j                  	      }|j,                  \  }}|d
k  rt        j.                         S g }t1               }t3        |      D ]  }t        j4                  ||         j7                         }||   }t        j8                  |      }t        j:                  |      d   }|j<                  dk(  rt1               }vt?        tA        |      tA        |j<                              }||   }t        jB                  | |d
z
        d| }||   }t        jD                  ||          }||   }|D ch c]  }|tA        |          } }| |z
  }!|| z
  }"|||kD     }#tG        |#      r&t        j4                  |#d         j7                         n|}$|d
z   |k  r*t        j4                  ||d
z            j7                         }%n&t        j4                  |d         j7                         }%tI        |d
      D ]  \  }&}|tA        |         }'|j                  |jK                  d      |$jK                  d      |%jK                  d      |'dtM        |      z  tM        ||         tA        |&      |'|!v |'|"v tA        |      d
        t        |"      D ]]  }'|j                  |jK                  d      |$jK                  d      |%jK                  d      |'dtM        d      dddtA        |      d
       _ | } t        j.                  |      S c c}w )z
        Month-end rebalance audit: one row per held sector per signal month.

        Columns include signal_date (BME), effective_date (first session weights apply),
        ticker, weight, aqr_mom, rank, and entry/exit flags vs prior month.
        r   rU  rV  >   r4   r   r<  r=  Nr<  r   rj   r6   r  start%Y-%m-%dr;   )
signal_dateeffective_dateperiod_end_datetickerweightr<  rankis_new_entryis_exit_from_priorrZ  rf   r   FT)'r"   r  rH  rI  r#   r'   rC   rD   r@   rE   r!   rF   r   r$   r%   r&   rJ  r/  rL  rM  rN  rN   r   rW  r   r   	Timestamp	normalizer   rX  r   r   rs   r  r  r   r  strftimerq   )(rY  rY  rZ  rZ  r[  r_  r  r`  ra  r  dfxrc  rd  rh  rj  rk  r  r   r   rt  	prev_heldr   	signal_dtr[  r\  r]  r   r^  r_  r7  r  r   r  entriesexitsaftereffective_dt
period_endrank_itkrs(                                           r+   generate_rebalance_logz(SectorETFRotation.generate_rebalance_log  s4    A:011788)#% 
	HA!B2H))"**5G!$?w?PQRR..*66t<C'')CCI.."CNN3y>00<CCAFG
	H (0	||//1??5)..066&++-..rzz.2}}1q5<<>!!e	q =	AXa[1;;=I!*CKK$E

5)!,I~~"E	CJINN 34A)nG'1q59"1=Jz*FJJ
 334EE]F-34GCFO4D4Y&G$E 	!9:E u: U1X&002 
 1uqy\\(1q5/:DDF
\\,r*:;EEG
&vQ7 	c!fo'0'9'9*'E*6*?*?
*K+5+>+>z+J"%"%a.#(Q= #F(+w.1Ul!$Q  e} '0'9'9*'E*6*?*?
*K+5+>+>z+J"%"%#(< !(-.2!$Q I{=	~ ||D!!a 5s   
RN   
rY  r  rZ  rs   r3  rq   r8  r  r  r  rY  r  rZ  rs   r  rA  r  r  r  r  r?  r{  r  r-   r+   rR  rR    s     G[
 $(G[)G[ G[
 !G[ G[ 
G[X h")h" h" 
	h"r-   rR  c                  L    e Zd ZdZ	 dddd	 	 	 	 	 	 	 	 	 d	dZ	 d	 	 	 	 	 d
dZy)SectorETFLongShortu6  
    **Market-neutral** SPDR sector rotation: long top-k / short bottom-k by ``aqr_mom``.

    Each leg is equal-weight; long weights sum to +1.0 and short weights sum to −1.0
    (200% gross, ~zero beta vs SPY in aggregate). Monthly rebalance, weights
    forward-filled and executed from the next bar.
    r2  TrS  c          
     
   |dk  rt        d      |st        d      t        |j                               }|dz  t        |      kD  r t        d| dd|z   dt        |             g }g }|D ]  }||   }	h d}
|
j	                  |	j
                        }|rt        | d	t        |             t        j                  |	j                        j                  d       }|	j                         }||_	        |j                         }|j                  |d
   j                  t        j                         j#                  |             |j                  |d   j                  t        j                         j#                  |              t%        |      \  }}t%        |      \  }}|j                  j'                  |j                        j)                         }|j+                  |      j-                         }|j+                  |      }|j/                  d      j                  t        j                         }|j1                  d      j3                         j                  }|j1                  d      j3                         }|j5                  t        j                         }|j6                  \  }}|dk  s|dk  r't        j8                  d|dt        j                         S t        j:                  ||ft        j                         }t=        |      D ]  }||   }t        j>                  |      }t        j@                  |      d   }|jB                  d|z  k  rHtE        |      }||   }t        jF                  | |dz
        d | }t        jF                  ||dz
        d | } ||   }!||    }"dtI        |      z  |||!f<   dtI        |      z  |||"f<    t        jJ                  |||j
                        }#|#j+                  |      j-                         jM                  d      j/                  d      }$tI        |      dz  }%|$j5                  t        j                         }&|j5                  t        j                         }'|&|'z  jO                  d      }(t        jP                  |&      jO                  d      })|(t        jR                  dd|)z
        |%z  z   }*|rk|dkD  rft        jT                  |)dkD        r$tI        t        jV                  |)|)dkD                 n
tI        d      }+tY        dtE        |       d|+dz  dd       t        j8                  |*|dt        j                         S )Nr   rU  rV  r   ztop_k=z requires at least z ETFs; got >   r4   r   r<  r=  r<  r4   rf   r   rj   r6   sector_ls_retr?   r;         rD  rg   r   r   z  Sector L/S sleeve: top_k=u"    per leg | avg gross exposure ≈ r   r   r   )-r"   r  rH  r   rI  r#   r'   rC   rD   r@   rE   r!   rF   r   r$   r%   r&   rJ  r/  rK  rL  rG   rH   rl   rM  rN  rN   r   rP   r  r   r   rX  r   rs   r  rq   rW  rt   r   r   ru   r   rJ   r   ),rY  rY  rZ  r3  r8  rZ  r[  r\  r_  r  r`  ra  r  rb  rc  rd  re  rh  r   rj  rk  r  r   r   r  r   r[  r\  r]  r   r^  
long_localshort_locallong_idx	short_idxr  r  rw   r   rx   r  r  r*   	avg_grosss,                                               r+   r?  z#SectorETFLongShort.generate_returnsw  s    A:011788)19s7|#21u9+[WW  $&#% 	DA!B2H))"**5G!$?w?PQRR..*66t<C'')CCI.."CNN3y>00<CCAFGNN3u:,,RZZ8??BC	D (0	'0	||))&,,7CCE-335-]]3'..rzz:
??5)..066&++-..rzz.2}}1q5AE99S?RTR\R\]]HHaV2::6	q 	6A!*CKK$E

5)!,I~~E	)E
A)nG'1q59"1=J//'1q59"1=K ,H!+.I%(58^Iak"&*U1XoIal#	6 ||IXv~~V ((6<<>DDQGNNsS	*+e3RZZ0bjj1A{{{"q	1%RZZS5[1H<<q1u<>FF519<MbggeEAI&678SXY^S_I-c%j\ :**3c/#)>aA
 yyLbjjYYr-   c                   |dk  rt        d      t        |j                               }g }|D ]  }||   }t        j                  |j
                        j                  d      }|j                         }||_        |j                         }|j                  |d   j                  t        j                        j                  |              t        |      \  }	}
|	j
                  j                         }|	j!                  d      j#                         j
                  }|	j!                  d      j#                         }|j%                  t        j                        }|j&                  \  }}|dk  rt        j(                         S g }t+        |      D ]  }t        j,                  ||         j/                         }||   }t        j0                  |      }t        j2                  |      d   }|j4                  dt7        |      z  k  rxt7        |      }||   }t        j8                  | |dz
        d| }t        j8                  ||dz
        d| }|t        j:                  ||             }|t        j:                  ||            }|||kD     }t=        |      r&t        j,                  |d         j/                         n|}|dz   |k  r)t        j,                  ||dz            j/                         n%t        j,                  |d	         j/                         }t?        |d
      D ]z  \  } }!|j                  |jA                  d      |jA                  d      |jA                  d      |t7        |!         ddtC        |      z  tC        ||!         t7        |       |d	       | t?        |d
      D ]z  \  } }!|j                  |jA                  d      |jA                  d      |jA                  d      |t7        |!         ddtC        |      z  tC        ||!         t7        |       |d	       |  t        j(                  |      S )u<   Month-end audit: long (+) and short (−) legs with weights.r   rU  Nr<  r   rj   r6   r   r  rc  re  longr;   )	rf  rg  rh  ri  legrj  r<  rk  rZ  shortr  )"r"   r  rH  rC   rD   r@   rE   r!   rF   r   r$   r%   r&   rJ  r/  rL  rM  rN  rN   r   rW  r   rn  ro  r   rX  r   rs   r  r  r   r  rp  rq   )"rY  rY  rZ  rZ  r[  r_  r  r  rq  rc  rd  rh  rj  rk  r  r   r   rt  r   rs  r[  r\  r]  r   r^  r  r  r  r  rv  rw  rx  ry  r   s"                                     r+   r{  z)SectorETFLongShort.generate_rebalance_log  s    A:011)#% 	HA!B..*66t<C'')CCI.."CNN3y>00<CCAFG	H (0	||//1??5)..066&++-..rzz.2}}1q5<<>!q 1	AXa[1;;=I!*CKK$E

5)!,I~~CJ.E
A)nG'1q59"1=J//'1q59"1=K WZ-@,@!ABH!"**W[-A"BCI 	!9:EADU2<<a1;;=QZL q519 Xa!e_-779\\,r"23==?  'xq9 	'0'9'9*'E*6*?*?
*K+5+>+>z+J")#a&/%"%a.#(Q= #F!"
 'y: 	'0'9'9*'E*6*?*?
*K+5+>+>z+J")#a&/&"&q/#(Q= #F!"
I1	d ||D!!r-   Nr|  r~  r  r  r  r-   r+   r  r  m  s     RZ
 $(RZ)RZ RZ
 !RZ RZ 
RZn N")N" N" 
	N"r-   r  c               H   t        j                  t        |       t         j                        }t        j                  d      }| j	                  t         j
                        }| j                  }t        |       }t        t        |            D ]k  }||   }	t        j                  |	      s|||<   #|dk(  r%|	t        |       k  r0t        j                  d      }n|	|k\  rt        j                  d      }|||<   m t        j                  ||t         j                        S )z
    Hysteresis on negative return z-scores only.

    Activates (1) when ``z <= -entry_z``, clears when ``z >= -exit_z`` (with ``entry_z``,
    ``exit_z`` positive magnitudes, e.g. 2.0 and 0.5).
    rj   r   r6   rk   )r%   r  r   int8rN   r&   r@   rq   r   r   rC   rP   )
zentry_zexit_zr   curvalsr  neg_exitr   zis
             r+   _panic_stress_stater    s     XXc!fBGG,F
''!*C::BJJ:'D
''Cf~H3t9 !W{{2F1I!8eGn_$ggajX~ggajq	 99V3bgg66r-   c                  l    e Zd ZU dZdZded<   dZded<   dZded	<   d
Zded<   ddd	 	 	 	 	 	 	 	 	 ddZ	y)DefensiveMeanReversionSleevea  
    Bond-tilt sleeve triggered by **mean-reversion setup** on SPY: extremely negative
    realized daily returns vs a rolling window (z-score). When active, the sleeve holds
    long-duration Treasuries (TLT); otherwise it sits in cash at the risk-free rate.

    This is not a forecast of the equity bounce; it leans on the empirical tendency for
    flight-to-quality into bonds during equity return panics. Shifted 1 bar for execution.
    r5   rs   z_windowr_   rq   r  r<   r  r;   tlt_weight_when_activer2  TrS  c               b   d|fd|ffD ]!  \  }}d|j                   vst        | d       t        j                  |j                        j                  d       j                  t        j                  |j                        j                  d             j                         }|d   j                  t        j                        j                         }t        j                  |j                        j                  d       |_        |j                  |      j                  d      }	|d   j                  t        j                        j                         }
t        j                  |j                        j                  d       |
_        |
j                  |      j                  d      }t        |	t        | j                               }t#        |t%        | j&                        t%        | j(                              }|j                  t        j                        t%        | j*                        z  }|j-                  d      j                  d      j/                  dd      }t%        |      d	z  }||z  d|z
  |z  z   }|rt1        |      d
kD  r|t1        |      rt%        |dkD  j3                               nd}t5        dt        | j                          dt%        | j&                        ddt%        | j(                        dd|d       t        j6                  ||dt        j                        S )Nr   TLTr4   z DataFrame must include 'ret'rf   )r  r  r6   r;   rg   r   r<   z   Defensive MR sleeve: z_window=z	 entry_z=r   z exit_z=u     | share of days TLT-tilted ≈ r  defensive_mr_retr?   )r#   r'   rC   rD   r@   rE   rK  rL  r$   r%   r&   r!   rG   rl   r   rs   r  r  rq   r  r  r  rt   rp   r   rJ   r   rP   )rY  r(   tlt_dfr3  r8  rA   r  rh  spy_rspy_rettlt_rtlt_retr  stressw_targetr   rw   r*   fracs                      r+   r?  z-DefensiveMeanReversionSleeve.generate_returnsI  s     5&/: 	GHD"BJJ&$'DEFF	G ~~fll3??EKKNN6<<(44T:

+- 	 u$$RZZ0557nnV\\2>>tD---44S9u$$RZZ0557nnV\\2>>tD---44S97C$67$Qdll0CERVR]R]L^_==,uT5P5P/QQNN1$$S)..sC8*+e3'kS1W00s3x!|.1!f5!c')*#D23t}}3E2FiPUVZVbVbPcdgOh i,S11QRVWZQ[]
 yyL7IQSQ[Q[\\r-   N)
r(   rA  r  rA  r3  rq   r8  r  r  r  )
r  r  r  r  r  rD  r  r  r  r?  r  r-   r+   r  r  9  su     HcGUFE$'E' $(']'] ']
 !'] '] 
']r-   r  c            
          e Zd ZU dZ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
<   	 dddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ
y)EnsembleManagerz
    Blends Macro (Tactical All Weather) + monthly AQR L/S + optional buy-the-dip (long-only) +
    sector rotation + defensive MR sleeve.
    r;   rq   weight_macro333333?weight_alpharf   weight_ls_shortweight_sectorweight_defensiveNr2  r  )r3  r   r   r7  sector_daily_retdefensive_daily_retls_short_daily_retls_diagnosticsc          	        t        t              }|j                  ||      }|d   j                  t        j
                        }|v|st        d      |j                  d      }||j                  rt        d      t               }|j                  ||||||
      j                  t        j
                        }n|j                  t        j
                        }t        | j                        d	kD  xr |du}|r |j                  t        j
                        }n0t        j                  d	|j                  t        j
                  
      }t        | j                         d	kD  xr |du}|r |j                  t        j
                        }n0t        j                  d	|j                  t        j
                  
      }t        | j"                        d	kD  xr |	du}|r |	j                  t        j
                        }n0t        j                  d	|j                  t        j
                  
      }|j                  j%                  |j                        }|r|j%                  |j                        }|r|j%                  |j                        }|r|j%                  |j                        }|j'                  |      j)                  d	      }|j'                  |      j)                  d	      }|j'                  |      j)                  d	      }|j'                  |      j)                  d	      }|j'                  |      j)                  d	      }|rt        | j                        nd	}|rt        | j                         nd	}|rt        | j"                        nd	}|| j*                  z  || j,                  z  z   ||z  z   ||z  z   ||z  z   }d|z   j/                         dz
  }d|z   j/                         dz
  }d|z   j/                         dz
  }d|z   j/                         dz
  } d|z   j/                         dz
  }!d|z   j/                         dz
  }"t        j0                  |||| |!|"d|      }#|#S )a.  
        Parameters
        ----------
        macro_dict
            ticker -> daily DataFrame with columns close, ret, sma_200, aqr_mom.
        equity_dict
            Equity universe for the AQR / cross-sectional L/S engine (required unless
            ``ls_daily_ret`` is provided).
        cash_annual_yield
            Annualized yield applied to cash weight (macro book).
        top_n
            L/S leg size (AQR engine only).
        ls_daily_ret
            If provided, skip the built-in :class:`CrossSectionalMomentum` path and use this
            daily L/S return series (e.g. from :class:`MLMomentumEngine`).
        sector_map
            Passed to :class:`CrossSectionalMomentum` when building the AQR sleeve (ignored if
            ``ls_daily_ret`` is set).
        sector_daily_ret
            Optional :class:`SectorETFRotation` daily returns. Used only if ``weight_sector > 0``.
        defensive_daily_ret
            Optional :class:`DefensiveMeanReversionSleeve` daily returns. Used only if
            ``weight_defensive > 0``.
        ls_short_daily_ret
            Optional :class:`BuyTheDipSleeve` daily returns (long-only; column name unchanged).
            Used only if ``weight_ls_short > 0``.
        ls_diagnostics
            If ``ls_daily_ret`` is None and this dict is provided, passed through to
            :meth:`CrossSectionalMomentum.generate_ls_returns` and filled with sleeve stats.

        Returns
        -------
        pd.DataFrame
            Columns:
              * macro_cumulative_ret
              * ls_cumulative_ret
              * ls_short_cumulative_ret (zeros if buy-the-dip sleeve off)
              * sector_cumulative_ret (zeros if sector sleeve off)
              * defensive_cumulative_ret (zeros if defensive sleeve off)
              * ensemble_cumulative_ret
        )baseline_weights)r3  portfolio_bar_retNz:equity_dict is required when ls_daily_ret is not provided.r   zPmacro_dict must include a non-empty 'SPY' DataFrame for the L/S regime throttle.)r   r(   r3  r7  r   rf   rk   r;   )macro_cumulative_retls_cumulative_retls_short_cumulative_retsector_cumulative_retdefensive_cumulative_retensemble_cumulative_retr  )r   r   build_portfolior$   r%   r&   r"   r   r    r1  r  rq   r  rC   rP   r@   r  r  intersectionrG   rl   r  r  r   rW  )$rY  
macro_dictr)   r3  r   r   r7  r  r  r  r  tacticalmacro_dfmacro_daily_ret	spy_macro	ls_engine
use_sectorsec_aluse_defdef_aluse_ls_sls_s_alcommon_indexmacro_alls_alwswdwlsensemble_daily_ret	macro_cumls_cumls_short_cum
sector_cumdefensive_cumensemble_cumr*   s$                                       r+   build_ensemblezEnsembleManager.build_ensemble  s   p -lK++JJ[+\"#67>>rzzJ  !]^^"u-I IOO !stt.0I$88 "3%* 9  fRZZ   (..rzz:L4--.4U9IQU9U
%,,RZZ8FYYs/*?*?rzzRF--.4X9LTX9X(//

;FYYs/*?*?rzzRF--.4W9KSW9W(//

;Gii?+@+@

SG ',,99,:L:LM'44V\\BL'44V\\BL'44W]]CL"**<8??D$$\299#>-44S9-44S9//,/66s;*4U4%%&#-4U4(()#-5eD(()3t(((d'''(m rk rk	 	 8^,,.4	+&&(3.g..036Fl++-3
v..0360099;cAll(1%++7)3,9+7 

 
r-   )N)r  r  r)   zDict[str, pd.DataFrame] | Noner3  rq   r   rs   r   pd.Series | Noner7  r  r  r  r  r  r  r  r  r  r  rA  )r  r  r  r  r  rD  r  r  r  r  r  r  r-   r+   r  r  s  s     L%L% OU M5!e!
 7;P
 $()-,0-104/3&*P+P 4P
 !P P 'P *P +P .P -P $P 
Pr-   r  )	save_pathc                  g d}|D cg c]  }|| j                   vs| }}|rt        d|       	 ddlm} |j                  d      \  }}|j                  | j                  | d   j                  d	d
       |j                  | j                  | d   j                  dd       d| j                   v r| d   j                  t        j                        }	t        t        j                  t        j                  |	j                                    dkD  r*|j                  | j                  |	j                  ddd       d| j                   v r| d   j                  t        j                        }
t        t        j                  t        j                  |
j                                    dkD  r*|j                  | j                  |
j                  ddd       d| j                   v r| d   j                  t        j                        }t        t        j                  t        j                  |j                                    dkD  r*|j                  | j                  |j                  ddd       |j                  | j                  | d   j                  ddd       |j                  d d!d"#       |j!                  d$       |j#                  d%       |j%                  d&d'(       |j'                  d)*       |j)                          |r%|j+                  |d+,       |j-                  |       y|j/                          |j-                  |       yc c}w # t        $ r}t	        d      |d}~ww xY w)-z
    Plot Macro vs L/S vs optional sector vs Ensemble equity curves.

    Expects:
      * macro_cumulative_ret
      * ls_cumulative_ret
      * ensemble_cumulative_ret
      * sector_cumulative_ret (optional; plotted if present and non-flat)
    )r  r  r  zensemble_df missing columns: r   N$matplotlib is required for plotting.r  r`   figsizer  zMacro (Tactical All Weather)r_   )label	linewidthr  zEquity L/S Momentumg?r  r=   z$Sector ETF rotation (standalone leg)gffffff?:)r  r  	linestyler  zBuy the dip (long-only sleeve)g?)r   )r}  r6   r6   r6   r  zDefensive MR (TLT panic tilt)z-.r  zEnsemble (blended)g      @z--rf   grayr<   colorr  z%Multi-Strategy Ensemble Equity CurvesCumulative ReturnTr  r   bestloc   dpi)r#   r'   matplotlib.pyplotpyplotImportErrorsubplotsplotr@   valuesr$   r%   r&   rq   nanmaxr   axhline	set_title
set_ylabelgridlegendtight_layoutsavefigr   show)ensemble_dfr  r`  cra  pltefigaxrR   ls_sr   s               r+   plot_ensembler    s   H
 #CQa{/B/B&BqCGC6wi@AAI' ll7l+GCGG*+22,	   GG'(//#	   +"5"55/077

C266!((+,-5GG!!<   !K$7$7745<<RZZH266$++./058GG!!6+   "[%8%8823::2::F266!((+,-5GG!!5   GG-.55"   JJs&CJ0LL89MM%&GGDGII&II3'		#
		#M D  I@AqHIs!   MMM 	M/M**M/c                  | j                  d      j                  t        j                        }d|z   j	                         dz
  }	 ddlm} |j                  d      \  }}|j                  |j                  |j                  dd	d
       |j                  ddd       |j                  d       |j                  d       |j                  dd       |j!                  d       |j#                          |r%|j%                  |d       |j'                  |       y|j)                          |j'                  |       y# t        $ r}t        d      |d}~ww xY w)z_
    Plot cumulative equity for the standalone L/S momentum sleeve (daily simple returns).
    rf   r;   r   Nr  r  r  z Equity L/S Momentum (standalone)r_   C1r  r  r  r  r<   r  z#Cross-Sectional Equity L/S Momentumr  Tr  r  r  r  r  r  rl   r$   r%   r&   r   r  r  r  r  r  r@   r  r  r  r  r  r  r  r  r   r  )r   r  rx   cumr   r  r  r  s           r+   plot_ls_standaloner  m  s$    	C ''

3A7



#CI' ll7l+GCGGCIIszz)KWZbfGgJJs&CJ0LL67MM%&GGDGII&II3'		#
		#!  I@AqHI   D? ?	EEEc                  | j                  d      j                  t        j                        }d|z   j	                         dz
  }	 ddlm} |j                  d      \  }}|j                  |j                  |j                  dd	d
       |j                  ddd       |j                  d       |j                  d       |j                  dd       |j!                  d       |j#                          |r%|j%                  |d       |j'                  |       y|j)                          |j'                  |       y# t        $ r}t        d      |d}~ww xY w)zOPlot cumulative equity for the standalone defensive MR (TLT panic tilt) sleeve.rf   r;   r   Nr  r  r  zDefensive MR sleever_   C3r  r  r<   r  u4   Defensive Mean Reversion (SPY return z → TLT tilt)r  Tr  r  r  r  r  r  r	  	daily_retr  rx   r
  r   r  r  r  s           r+   plot_defensive_mr_standaloner    s!   $$RZZ0A7



#CI' ll7l+GCGGCIIszz)>#UYGZJJs&CJ0LLGHMM%&GGDGII&II3'		#
		#!  I@AqHIr  c                  | j                  d      j                  t        j                        }d|z   j	                         dz
  }	 ddlm} |j                  d      \  }}|j                  |j                  |j                  dd	d
       |j                  ddd       |j                  d       |j                  d       |j                  dd       |j!                  d       |j#                          |r%|j%                  |d       |j'                  |       y|j)                          |j'                  |       y# t        $ r}t        d      |d}~ww xY w)zEPlot cumulative equity for the standalone sector ETF rotation sleeve.rf   r;   r   Nr  r  r  zSector ETF momentum (top-k)r_   C2r  r  r<   r  z(SPDR Sector ETF Rotation (12-1 momentum)r  Tr  r  r  r  r  r  r	  r  s           r+   plot_sector_rotation_standaloner    s"   $$RZZ0A7



#CI' ll7l+GCGGCIIszz)FRU]aGbJJs&CJ0LL;<MM%&GGDGII&II3'		#
		#!  I@AqHIr  )r(   r  r)   r  r  rA  )rQ   rA  r@   r@  r  r  )rv   r  rw   rq   ra   rq   rb   rq   rc   rs   rd   rs   r  tuple[pd.Series, pd.Series])r   rB  r   r  r{   rq   r  	list[int])r   dictr   rA  r   rA  r   r  r   rB  r   rB  r   rs   r  None)r   r  r  r  )r   r  r   rs   r  r  )
r  r  r  r  r   r  r   rs   r  r  )r  rA  r   r  r  r  )r  rs   r  rs   r  r  )r  rB  r  rB  r  rB  r  rB  r   rs   r  ztuple[np.ndarray, np.ndarray])r,  zlist[pd.Series]r  ztuple[pd.DataFrame, list[str]])r  r  r  rq   r  rq   r  r  )r  rA  r  
str | Noner  r  )r   r  r  r  r  r  )r  r  r  r  r  r  )+r  
__future__r   dataclassesr   typingr   numpyr%   pandasrC   -RenTech.strategy_stack.portfolio_risk_managerr   r   %RenTech.strategy_stack.statarb_enginer   r   rD  r   r,   r]   r   r   r   r   r   r  r  r  r*  r/  r1  r  rF  rR  r  r  r  r  r  r  r  r  )r_  s   0r+   <module>r!     s  " # !    a @1 -  1A!A1!A$!A Y A&&OZ  $&& & 	&
 & & & !&R PT *`.`. $`. 	`.
 `. `. `. `. 
`.F8v=
_8$A-!-!-! -! 	-!
 -! #-!` M- M- M-` K4 K4 K4\ N4 N4 N4b z" z" z"z k" k" k"\7: 6] 6] 6]r ^ ^ ^B IM Up LP 8 SW 4 VZ qC "Bs   D7