
    pj)/                        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dd	d	d
Zded<    eddh      Zded<    ed       G d d             ZddZddZe G d d             ZddddZeZddddZy)au  
Tactical All-Weather portfolio engine.

This module replaces the previous multi-pair risk-parity / kill-switch logic.

Core idea
---------
Maintain a static baseline allocation to a set of macro sleeves, but use a
momentum + trend regime gate to move individual sleeves to cash during bear
markets. A "Static All Weather" benchmark remains fully invested at BASE_WEIGHTS.
    )annotations)	dataclass)DictN333333?g?g333333?g333333?)SPYTLTIEFGLDDBCzdict[str, float]BASE_WEIGHTSr   r	   zfrozenset[str]BOND_TICKERST)frozenc                      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<   y)TacticalAWConfigz
    Gate and sizing parameters for :class:`TacticalAllWeatherManager`.

    Defaults reproduce the original binary SMA(200) + 12-1 momentum rules.
       int
sma_window   mom_skip_days   mom_lookback_days        floatmom_thresholdbinarystrweight_mode      ?partial_fracrisk_on_min_invested      ?bond_baseline_multg{Gz?cash_annual_yieldN)__name__
__module____qualname____doc__r   __annotations__r   r   r   r   r   r    r"   r#        I/opt/rentech/trading_bot/RenTech/strategy_stack/portfolio_risk_manager.pyr   r   "   sg     JM3 s M5KL%"%%% ###u#r*   r   c               z    | j                  t        |            }| j                  t        |            }||z  dz
  S )Nr!   )shiftr   )closeskiplookbackdenomnums        r+   _momentum_from_closer3   5   s5    KKH&E
++c$i
 C;r*   c               p    |D cg c]  }|| j                   vs| }}|rt        d| d|       y c c}w )NzMissing required columns for z: )columnsKeyError)dfcolstickercmissings        r+   _require_columnsr<   ;   sF    6Q!2::"5q6G66vhb	JKK  7s   33c                  L    e Zd ZU dZdZded<   dZded<   d	dZ	 d
	 	 	 	 	 ddZy)TacticalAllWeatherManagerz
    Dynamic asset allocation engine.

    build_portfolio expects, for each ticker:
      - close: price
      - ret: daily pct change (bar return)
    SMA and momentum are computed from ``close`` using :class:`TacticalAWConfig`.
    Nzdict[str, float] | Nonebaseline_weightsr   configc                |    | j                   t        t              | _         | j                  t	               | _        y y N)r?   dictr   r@   r   )selfs    r+   __post_init__z'TacticalAllWeatherManager.__post_init__O   s4      ($($6D!;;*,DK r*   c                   |st        d      | j                  }t        ||j                  n|      }t	        | j
                  j                               }|D cg c]	  }||vs| }}|rt        d|       |D cg c]3  }t        j                  ||   j                        j                  d      5 }}|d   }	|dd D ]  }
|	j                  |
      }	 |	j                         }	g }g }t        dt        |j                               }|D ]  }||   }t#        |ddg|	       |j%                         }t        j                  |j                        j                  d      |_
        |j'                         }|j)                  |	      }|d   j+                         }|d   j-                  d
      }|j/                  |j1                  |             |j/                  |j1                  |              t        j2                  |d      }t        j2                  |d      }|j5                  ||      j7                         }t        j8                  |D ci c]:  }|t;        ||   t        |j<                        t        |j>                              < c}|	      }||kD  }|t        |j@                        kD  }tC        |jD                        jG                         jI                         }t        jJ                  | j
                  tL        jN                        j)                  |      }|D ]2  }|tP        v st        ||         t        |jR                        z  ||<   4 |jU                  tL        jN                        }|dk(  rt        |jV                        }tM        jX                  ||z  dd
      }|tM        jX                  || z  |d
      z   }|tM        jX                  | |z  |d
      z   }tM        jZ                  |d      }||z  }n:||z  }tM        jX                  |jU                  tL        j\                        |d
      }t        j8                  ||	|      }|j_                  d      j-                  d
      }t        |j`                        } | d
kD  rWd|v rR|d   |d   kD  |d   |j@                  kD  z  j_                  d      j-                  d      }!|jc                  d      }"| |"z
  je                  d
      }#|!|#dkD  z  }$|#dz  }%|#dz  }&d|jf                  v r0|jh                  |$df   |%jh                  |$   z   |jh                  |$df<   d|jf                  v r0|jh                  |$df   |&jh                  |$   z   |jh                  |$df<   |jc                  d      }'|'dkD  }(|(jk                         r:|jh                  |(   jm                  |'jh                  |(   d      |jh                  |(<   |jc                  d      })d|)z
  }*|dz  }+||z  jc                  d      |*|+z  z   },d|,z   jo                         dz
  }-||z  jc                  d      }.d|.z   jo                         dz
  }/t        jJ                  tL        jp                  |	tL        jN                        }0d|v rS|d   }1d|1jf                  v r@|1d   js                  tL        jN                        j)                  |	      j-                  d
      }0d|0z   jo                         dz
  }2t        j8                  |,|-|.|/|2|*|)d|	      }3|j1                  |D ci c]  }|d| 
 c}      }4t        j2                  |3|4gd      }3|3S c c}w c c}w c c}w c c}w ) a  
        Parameters
        ----------
        data_dict
            Dict: ticker -> DataFrame with columns: close, ret, sma_200, aqr_mom.
            All frames will be aligned to a common calendar index.
        cash_annual_yield
            Annualized cash yield (e.g. 0.04 for 4%), converted to daily via /252.

        Returns
        -------
        pd.DataFrame
            Contains:
              - portfolio_bar_ret, portfolio_cumulative_ret
              - static_all_weather_cumulative_ret
              - spy_cumulative_ret
              - cash_weight, total_invested_weight
              - weight_<TICKER> columns for each sleeve
        zdata_dict cannot be emptyNz-data_dict missing required baseline tickers: r         r.   ret)r9   r   )axis)windowmin_periods)r/   r0   )index)dtypepartialr!   )rM   r5   r   F)lowerg&.>gffffff?r   r
   g0D   ?g     o@)rM   rN   )portfolio_bar_retportfolio_cumulative_retstatic_all_weather_ret!static_all_weather_cumulative_retspy_cumulative_retcash_weighttotal_invested_weightweight_)r5   ):
ValueErrorr@   r   r#   listr?   keysr6   pdto_datetimerM   tz_localizeunionsort_valuesmaxr   r   r<   copy
sort_indexreindexffillfillnaappendrenameconcatrollingmean	DataFramer3   r   r   r   r   r   striprP   Seriesnpfloat64r   r"   to_numpyr   whereminimumbool_r-   r    sumclipr5   locanydivcumprodnanastype)5rD   	data_dictr#   cfgrf_yieldsleevestmissing_sleevesindicesmaster_indexidx	close_matret_matsma_winr7   dfxclose_sret_sclose_dfret_dfsma_dfmom_dftrendmom_okmodebase_wbwfracmulttarget_weight_arr
is_bullishtarget_weightmin_invspy_oktot	shortfallactiveadd_spyadd_gldrow_sumoverrW   rV   daily_rfrQ   rR   rS   rT   spy_retdf_spyrU   outweight_colss5                                                        r+   build_portfolioz)TacticalAllWeatherManager.build_portfolioU   sS   0 899kk2C2K..Qbct,,1134&-D)1C1DDJ?J[\]] RYYA2>>)A,"4"45AA$GYYqz12; 	3C'--c2L	3#//1 &(	#%b#cnn-. 	,A1BR'5!1!<'')Csyy1==dCCI.."C++l+C'l((*GJ%%c*EW^^A./NN5<<?+	, 99YQ/7+!!g!FKKM !  'QKS../ !6!67  

 6!% 1 1223??#))+113400

CKKGT 	MAL !&),uS5K5K/LLq		M __2::_.9))*D88EFNC5D"((5F7?D#>>D"((E6F?D#>>D::dC(D $r	J "##"((#3! 

 &++A.55c:001S=Ug-66%=3K\K\;\]ddfUm   ###+C 3,,3,7Iy4/0F$&G$&G---3@3D3DVU]3SV]VaVaW 4!!&%-0 ---3@3D3DVU]3SV]VaVaW 4!!&%-0 $''Q'/GZ'Dxxz*7*;*;D*A*E*EgkkRVFW^_*E*`!!$' - 1 1q 1 911e##m388a8@;QYCYY$'*;$;#D#D#F#L  #)6/!6!6A!6!>-03I-I,R,R,TWZ,Z) ))BFF,bjjIIu%F&5MVBJJ'W\*VC[	  "Gm446<ll%6,D*@5V&8*)> 
 $**g3VA}4D3V*Wiik*3
] E Z<P 4Ws   	]&$]&>8]+?]06]5)returnNonerB   )r}   zdict[str, pd.DataFrame]r#   zfloat | Noner   pd.DataFrame)	r$   r%   r&   r'   r?   r(   r@   rE   r   r)   r*   r+   r>   r>   A   sO     15-4#F#- +/m*m (m 
	mr*   r>   	save_pathc          	        g d}|D cg c]  }|| j                   vs| }}|rt        d|       	 ddlm} t        t        j                               }|D cg c]  }d| | j                   v sd|  }	}t        |	      t        |      k7  rt        d      |j                  dd	d
ddddgi      \  }
\  }}|j                  | j                  | d   j                  dd       |j                  | j                  | d   j                  ddd       |j                  | j                  | d   j                  dddd       |j                  ddd       |j                  d        |j                  dd!"       |j!                  d#$       |	D cg c]$  }| |   j#                  t$        j&                        & c}| d%   j#                  t$        j&                        gz   }|	D cg c]  }|j)                  dd&       c}d'gz   } |j*                  | j                  g||dd( |j                  d)       |j-                  dd*       |j                  dd+"       |j!                  d,dd-.       |
j/                          |r%|
j1                  |d/0       |j3                  |
       y|j5                          |j3                  |
       yc c}w # t        $ r}t	        d      |d}~ww xY wc c}w c c}w c c}w )1z
    Plot tactical portfolio vs static all-weather + SPY buy-and-hold.

    Also includes a secondary subplot (stacked area) showing dynamic allocation
    including cash over time.
    )rR   rT   rU   rV   z'portfolio_df missing required columns: r   Nz$matplotlib is required for plotting.rX   zAportfolio_df missing expected weight_<TICKER> columns for sleeves   rG   )      Theight_ratiosg       @g333333?)figsizesharexgridspec_kwrR   zTactical All Weather)label	linewidthrT   zStatic All Weatherg      ?g?)r   r   alpharU   zSPY (buy & hold)z--)r   r   	linestyler   r   grayr   )colorr   zCumulative Returnr   )r   best)rw   rV    CASH)labelsr   
Allocationr!   g?z
upper leftr   )rw   ncolsfontsize   )dpi)r5   r6   matplotlib.pyplotpyplotImportErrorrZ   r   r[   lensubplotsplotrM   valuesaxhline
set_ylabelgridlegendr|   ro   rp   replace	stackplotset_ylimtight_layoutsavefigr.   show)portfolio_dfr   requiredr:   r;   plter   r   r   figax_eqax_alloccolalloc_seriesalloc_labelss                   r+   plot_tactical_portfolior     s   H #DQa|/C/C&CqDGD@	JKKI' <$$&'G*1[Qwqc]lFZFZ5ZWQC=[K[
;3w<'Z[[ \\		$sCj1 * C	% 
JJ/077$	   
JJ89@@"   
JJ)*11    
MM#VsM3	()	JJt3J	LLVL EPPSL%,,RZZ8P]#**2::6T L ;FF3CKK	2.F&QLH|))YLYUXY%c3MM$cM"OOAO:I3'		#
		#C E  I@AqHI \N Q Gs9   K
K
K K,2K,)K1&K6	K)K$$K)c                   t        | |       y)z!Compatibility wrapper (old name).r   N)r   )r   r   s     r+   plot_portfolior   Z  s    LI>r*   )r.   	pd.Seriesr/   r   r0   r   r   r   )r7   r   r8   z	list[str]r9   r   r   r   )r   r   r   z
str | Noner   r   )r'   
__future__r   dataclassesr   typingr   numpyro   pandasr\   r   r(   	frozensetr   r   r3   r<   r>   r   PortfolioManagerr   r)   r*   r+   <module>r      s   
 # !   
 "   )%8n 8 $$ $ $$L @ @ @F TX Nd -  KO ?r*   