
    pjr             
      <   U d Z ddlmZ ddlZddlZddlZddlmZ  ee      j                         j                  d   Z ee      ej                  vr"ej                  j                  d ee             ddlZddlZddlZddlmZ ddlmZmZmZmZ ddlZddlmZ dd	lmZ dd
lmZm Z m!Z! ddl"m#Z#m$Z$ ddl%m&Z& ddl'm(Z)m*Z*m+Z+ ed   Z,ed   Z-ed   Z. e/d      Z0de1d<   dZ2dZ3e3a4de1d<   ddZ5ddZ6dZ7dZ8dZ9dZ:dZ;dZ<ddgZ=d e1d!<   d"Z>d#Z?d$Z@d%ZAd&ZBd'ZCd(ZDd)d*gZEd e1d+<   dZFd,ZGd(ZHd-ZId.ZJd-ZKd/ZLd0d1gZMd e1d2<   dZNd3ZOd0d4gZPd e1d5<   dZQd3ZRd6ZSd7ZTd8ZUd9ZVd:ZWd;ZXd<ZYeYeYeHeYeYdZZd=e1d><   d?Z[d@Z\dAZ]dZ^d.Z_dBZ`dCZadDZbdEZcdFZddGZeedHz  dIz  Zfegej                  eief   ZjddJZkddKZl	 	 	 	 	 	 	 	 	 	 ddLZmddMZnddNZo	 	 	 	 	 	 	 	 	 	 ddOZpe3f	 	 	 	 	 	 	 ddQZqddRZrddSZsddTZtddUZuddVZvddWZwddXZxddYZyddZZzdd[Z{eVeWf	 	 	 	 	 dd\Z|eVeWfdd]Z}e G d^ d_             Z~e G d` dae~             ZddbZ	 	 	 	 	 	 	 	 ddcZ	 	 	 	 	 	 	 	 dddZe G de df             Zee~eef   Ze G dg dh             Ze G di dj             Z G dk dl      ZddmZ G dn do      ZddpZ	 	 	 	 	 	 	 	 	 	 ddqZddrZddsZddtZeduz  Z ej                   dvej"                        ZddwZddxZddyZddzZef	 	 	 	 	 dd{Zed|k(  refZdoe1d}<   dZd~e1d<   dZde1d<   dZdPe1d<   dZdPe1d<    e)j<                         r e+e)      Z e*ej@                          eee-eif    eejD                              ZejF                  r eejF                        ndZejH                  ZejJ                  Z ede) d       ej=                         rM ee      Z ee      \  ZZe ejT                  d      z
  jW                  d      Ze ejT                  dD      z   jW                  d      Z e|ee      Z eeej^                  ee      Z ee      dk(  r^ ejd                  dd        eej^                        Z eejm                         D  ch c]  }  ek|       ev s ek|        c}       Z ede         edejo                          dejo                          d ee       d        eeeXeeeee      Zejs                  e       nJ ejd                  de dd        e|eVeW      Z e#e      Z eeeXeeeee      Zejs                          ejy                           eejz                  e      Z ede d eejz                         d       yyc c} w )u  
4-regime VRP engine with **SPY > SMA(200)**; **R1/R3/R4** use **2%** risk per trade; **R2** opens **R2a** (2%
  diagonal, same rules as before) and **R2b** (1% put spread) **at once**, each with its own TP/SL/time stop:

- **SPY > SMA(200):** R1–R4 by VIX (weekly long strangle R1; **R2 = R2a put diagonal + R2b put spread** as two
  concurrent positions with separate exits; put credit spread R3; wider PCS R4).
- **Optional VVIX / VIX-momentum scaling:** ``load_spy_vix_from_yfinance`` pulls ``^VVIX``; when
  ``vol_risk_scaling=True`` (default), **global** target risk is cut if VVIX is high vs its 20d median;
  **R2** gets an additional cut if 5d VIX change or VVIX vs median signals stress.
- **R2 crossover filters:** when ``r2_crossover_filters=True`` (default), **only** in the R2 VIX band
  ``12 ≤ VIX ≤ 20``, entries require **SPY > SMA(50)** and block when **VIX MA(20) / VIX max(50)**
  is **too high** (VIX smoothed level too close to its 50-trading-day rolling max). (R1 / R3 / R4
  never use these checks.) **Global VVIX risk scaling** does **not** apply to **R1** (``VIX < 12``).
- **SPY ≤ SMA(200):** no new options; open positions are still marked and closed on rules.
- **Portfolio drawdown scaling (optional):** when enabled, **marked equity** (cash + open position MTM) vs a
  running **peak** defines drawdown. If DD ≥ ``dd_scale_enter``, new entries use ``dd_scale_mult`` on all
  risk sleeves until DD ≤ ``dd_scale_exit`` (hysteresis). This is **not** a delta hedge; a true hedge would be a
  separate overlay (e.g. long VIX calls, SPY puts) and would need its own legs and PnL in the engine.

Loaders: :class:`~RenTech.core.synthetic_data_loader.SyntheticLoader`,
:class:`~RenTech.core.options_data_loader.IVolatilityLoader` (Parquet), or
:class:`~RenTech.core.theta_chunks_loader.ThetaChunksLoader` (Theta 15:45 chunks).

**Macro overlay (optional):** when ``macro_overlay_enabled=True``, each session adds a small
**cash PnL** tied to Tier A index/ETF/synthetic **daily return** columns (Yahoo-aligned), keyed by
the same VIX band as R1–R4 (see ``tier_a_series.DEFAULT_MACRO_OVERLAY_BY_BAND``). This is a
**parallel sleeve** to the option legs, not a replacement for Theta fills.

**Overlap / DCA mode (optional):** when ``VRPBacktester(..., overlap_portfolio=True)``, new entries are
allowed **while positions are still open**. Each session adds ``overlap_slice_contracts`` (default 1)
per sleeve **if** today's VIX regime matches and aggregate **sizing commitment** for that sleeve
(open positions of that regime) stays within the same ``target_risk_usd`` cap as the legacy
single-wave sizing. Each ``ManagedPosition`` still has its own TP / SL / time stop (independent
lifecycle). R2 still opens **R2a + R2b together** when both legs build; otherwise neither adds.

**Execution:** :data:`SLIPPAGE_FACTOR` scales how much of the half-spread is paid vs mid
(0 = mid-only, 1 = full bid/ask cross). Applies to opens, closes, and MTM marks when bid/ask exist.

Default window: **2004-01-01 → 2024-01-01** (yfinance SPY/VIX panel).
    )annotationsN)Path   )	dataclass)AnyLiteralUnioncast)tqdm)find_contract_in_chain)IVolatilityLoaderOptionChainOptionContract)SyntheticLoaderSyntheticOptionChain)ThetaChunksLoader)DEFAULT_STRATEGY_CONFIG_PATH.apply_strategy_params_to_vrp_backtester_moduleload_strategy_config_file)buysellpmccdiagonal	r2_spreadnakedcredit_spread)fullr1_strangler2_diagonalr   r2_pairr3_put_spreadr4_credit_spreadzfrozenset[str]_VALID_PORTFOLIO_MODES      Y@g?float_active_slippagec                     t         S N)r'        A/opt/rentech/trading_bot/RenTech/strategy_stack/vrp_backtester.py	_slippager-   n   s    r+   c           	     B    t        t        dt        d|                   ay )N              ?)r&   maxminr'   )sfs    r,   _set_active_slippager4   r   s    Sc#rl34r+      g?g
ףp=
ǿ      ?g      )   g333333ӿr   )-   333333ÿr   "list[tuple[int, float, LegAction]]R2_DIAG_LEG_SPECSg     b@g      I@g     b      )\(̿{Gz?)r=   r>   r   )r=   g
ףp=
r   R2_CREDIT_LEG_SPECSr7   2      gffffff?)r8   r9   r   )r8   gQ뱿r   R3_CREDIT_LEG_SPECS   )r8   gr   R4_CREDIT_LEG_SPECSg      (@g      4@g      >@z
2004-01-01z
2024-01-01g     j@g{Gz?dict[Regime, float]DEFAULT_SLEEVE_RISK_FRACTIONSg      9@g333333?g?gffffff?g?   g       @g?g?zSPY-Option-Dataz spy_options_eod_combined.parquetc                H    t        j                  |       j                         S r)   )pd	Timestamp	normalize)tss    r,   	_norm_dayrN      s    <<%%''r+   c                    t        | j                        t        t        | j                        d      t        | j                        fS )N   )rN   
expirationroundr&   strikestroption_type)legs    r,   contract_keyrW      s1    cnn%uU3::->'BCDXYYr+   c           	        | j                   syt        j                  | j                        j	                         }t               }| j                   D ]\  }t        j                  |j                        j	                         t        |z
  j                        dk\  sL|j                         ^ |syg }|D ]  t        |z
  j                        }t        fd| j                   D              }	t        fd| j                   D              }
|	r|
s\|j                  t        |t        |      z
        f        |sy|j                  d        |D ]]  \  }| j                   D cg c]k  }t        j                  |j                        j	                         k(  r9|j                  dk(  r*t        j                   t#        |j$                              r|m }}| j                   D cg c]k  }t        j                  |j                        j	                         k(  r9|j                  dk(  r*t        j                   t#        |j$                              r|m }}|r|st'        |fd	      }t'        |fd
      }t#        |j(                        t#        |j(                        k\  rZ||fc S  yc c}w c c}w )z
    Same expiry (~``target_dte`` calendar days): long call near target call delta,
    long put near target put delta (typically more OTM than the call).
    N   c              3     K   | ]E  }t        j                  |j                        j                         k(  xr |j                  d k(   G yw)CNrJ   rK   rQ   rL   rU   .0xexps     r,   	<genexpr>z8find_weekly_long_strangle_same_expiry.<locals>.<genexpr>   B      
WXBLL&002c9Ramms>RR
   AAc              3     K   | ]E  }t        j                  |j                        j                         k(  xr |j                  d k(   G yw)PNr\   r]   s     r,   ra   z8find_weekly_long_strangle_same_expiry.<locals>.<genexpr>   rb   rc   c                    | d   S )Nr   r*   )kvs    r,   <lambda>z7find_weekly_long_strangle_same_expiry.<locals>.<lambda>
  s
    r!u r+   keyr[   re   c                X    t        t        | j                        t              z
        S r)   absr&   delta)ccall_target_deltas    r,   rh   z7find_weekly_long_strangle_same_expiry.<locals>.<lambda>  s    s5>EJ[D\3\/] r+   c                X    t        t        | j                        t              z
        S r)   rl   )ro   put_target_deltas    r,   rh   z7find_weekly_long_strangle_same_expiry.<locals>.<lambda>  s    c%.5IYCZ2Z.[ r+   )	contractsrJ   rK   as_ofrL   setrQ   intdaysaddanyappendrm   sortrU   mathisfiniter&   rn   r2   rS   )chain
target_dterp   rr   rt   expiriesro   scoreddtehas_chas_p_bucket_cbucket_pcall_legput_legr`   s     ``            @r,   %find_weekly_long_strangle_same_expiryr      s^    ??LL%//1E"%%H__ ll1<<(224e!!"a'LL
 /1F 
93;$$% 
\a\k\k
 
  
\a\k\k
 
 %s3Z01378
9 
KK$K% #3 __
||ALL)335<$eAGGn-	 
 
 __
||ALL)335<$eAGGn-	 
 
 xx%]^h$[\ E(//$::'""+#. -

s   -A0J:-A0J?c                |    	 t        |       }t        j                  |      xr |dkD  S # t        t        f$ r Y yw xY wNFr/   r&   	TypeError
ValueErrorr|   r}   )r_   vs     r,   	_quote_okr   %  sC    !H =='C' z"    ) ;;c                |    	 t        |       }t        j                  |      xr |dk\  S # t        t        f$ r Y yw xY wr   r   )midr_   s     r,   _mid_okr   -  sC    #J ==(S( z" r   c                n   t        |      }t        dt        d|            }	 t        |       }t        |      }t        j                  |      rt        j                  |      sBt        dt        j                  |      r|      S t        j                  |      r|      S d      S t        d|      }t        d|      }|dk  r|dk  ry|dkD  r@|dkD  r;||k  r||}}d||z   z  }d||z
  z  }|dk(  r	|||z  z   }	n|||z  z
  }	t        d|	      S |dkD  r|n|}
t        d|
      S # t        t        f$ r Y yw xY w)a{  
    Institutional fractional-spread fill vs mid.

    ``mid = (bid + ask) / 2``, ``half_spread = (ask - bid) / 2``.

    * **buy:** ``mid + half_spread * slippage_factor``
    * **sell:** ``mid - half_spread * slippage_factor``

    Broken quotes (non-positive bid/ask, inverted, or non-finite) fall back to a safe mid-style
    print so execution prices stay non-negative.
    r/   r0   r6   r   )r&   r1   r2   r   r   r|   r}   )bidaskactionslippage_factorr3   bar   half_spreadpxms              r,   calculate_fill_pricer   5  sE   " 
	B	S#c2,	B#J#J ==4==#33T]]1-TTq9IATTsTTCACACxAH3w1s7q5aqAQUmQUmU?{R''B{R''B3| W!AsA;3 z" s   D" "D43D4float | Nonec                   t        | j                        rt        | j                        nd}t        | j                        rt        | j                        nd}t	        | j
                        rt        | j
                        nd}|||dkD  r|dkD  rt        ||||      S |(t        j                  |      r|dk\  rt        ||||      S ||dkD  rt        ||||      S ||dkD  rt        ||||      S y)z
    Executable price for *open* or *close* using bid/ask when sane, else mid or single-side fallback.
    Used for Parquet EOD and Synthetic chains alike.
    Nr/   )	r   r   r&   r   r   r   r   r|   r}   )ro   r   r   bid_fask_fmid_fs         r,   execution_price_per_sharer   h  s     &aee,E!%%L$E%aee,E!%%L$E#AEENE!%%LEU.53;53;#E5&/JJT]]51esl#E5&/JJUS[#E5&/JJUS[#E5&/JJr+   c                    | y	 t        dt        |             }t	        j
                  |      syt        |||t                     S # t        t        f$ r Y yw xY w)zYMTM when only a stored mid exists (ffill / last leg.mid); do not use stale entry bid/ask.r/   )r1   r&   r   r   r|   r}   r   r-   )mid_raw	close_actr   s      r,   _exit_mark_mid_fallbackr     s\    U7^$ ==1i==	 z" s   A AAc                ,    t        | dt                     S Nr   r   r-   ro   s    r,   _open_buy_per_sharer         $Qy{;;r+   c                ,    t        | dt                     S Nr   r   r   s    r,   _open_sell_per_sharer         $Q	<<r+   c                ,    t        | dt                     S r   r   r   s    r,   _close_long_per_sharer     r   r+   c                ,    t        | dt                     S r   r   r   s    r,   _close_short_per_sharer     r   r+   c                D    t        dt        |       t        |      z
        S Nr/   r1   r&   rS   	spy_closes     r,   put_intrinsic_per_sharer     s    sE&ME)$4455r+   c                D    t        dt        |      t        |       z
        S r   r   r   s     r,   call_intrinsic_per_sharer     s    sE)$uV}455r+   c                    t        | j                        }t        | j                        j	                         }|dk(  rt        ||      S t        ||      S )Nr[   )r&   rS   rT   rU   upperr   r   )rV   r   kots       r,   intrinsic_per_share_legr     sH    cjjA	S__		#	#	%B	Sy'955"1i00r+   c                6    d}t         fd|D              st        d| d       j                         }t        j                  |j
                        j                         |_        ||j
                  j                  d          j                         }|d   j                  t        d	      j                         |d
t         <   |d   j                  t        d	      j                         |dt         <   |d   j                  t        d	      j                         |d<   d|j                  v rt        j                   |d   d      |d<   |d   j#                         j%                         |d<   |d   j                  t&        t        dt&        dz        	      j)                         |d<   |d   |d   j+                  t,              z
  |d<   d|j                  v r9t        j                   |d   d      j#                         j%                         |d<   |S )N)closesma_200	vix_closec              3  :   K   | ]  }|j                   v   y wr)   columns)r^   ro   spy_dfs     r,   ra   z#normalize_spy_df.<locals>.<genexpr>  s     1qqFNN"1s   zspy_df must contain columns .lastkeepr   rY   min_periodssma_r   vix_sma_
vix_max_50
vvix_closecoerceerrorsrH   rP   vvix_med_20
vix_chg_5dvix9d_close)allr   copyrJ   to_datetimeindexrL   
duplicated
sort_indexrollingR2_SPY_MA_SHORT_DAYSmeanR2_VIX_SLOW_MA_DAYSR2_VIX_ROLL_MAX_DAYSr1   r   
to_numericffillbfillVVIX_MEDIAN_WINDOWmedianshiftVIX_R2_CHANGE_DAYS)r   needouts   `  r,   normalize_spy_dfr     s   ,D1D117vQ?@@
++-Csyy)335CI
syy###00
1
<
<
>C),W)=)=>R`a)=)b)g)g)iC$#$%&,/,<,D,DEXfg,D,h,m,m,oC(&'()K(001EST0UYY[Cs{{"MM#l*;HML-335;;=L .66C3E3J,K 7 

&( 	M  ,s;/?/E/EFX/YYL#]]3}+=hOUUW]]_MJr+   c                2   | | j                   rt        d|       t        | j                  t        j
                        ryt        | j                  j                  d            }d|v r| d   }nd|v r| d   }n| j                  d d df   }t        |t        j                        r,|j                  d d df   }nd| j                  v r| d   n| d   }|j                         }t        |t        j                        r|j                  d d df   }t	        j                  |d      j                  t              }t	        j                  |j                        j!                         |_        ||j                  j#                  d          j%                         S )	Nzyfinance returned no rows for r   z	Adj CloseCloser   r   r   r   )emptyr   
isinstancer   rJ   
MultiIndexlistget_level_valuesiloc	DataFramesqueezer   astyper&   r   r   rL   r   r   )rawlabellv0colsers        r,   _extract_close_seriesr    sC   
{cii9%ABB#++r}}-3;;//23#k"C^g,C((1a4.Cc2<<(((1a4.C"-"<c+#g,
++-C#r||$hhq!tn
--H
-
4
4U
;Csyy)335CI		$$&$112==??r+   c                   	 dd l }|j                  d| |ddd      }|j                  d| |ddd      }|j                  d| |ddd      }|j                  d| |ddd      }t        |d      j	                  d	      }t        |d      j	                  d
      }		 t        |d      j	                  d      }
	 t        |d      j	                  d      }||	g}|
j                  s|j                  |
       |j                  s|j                  |       t        j                  |dd      j                         }|d
   j                         j                         |d
<   d|j                  v r$|d   j                         j                         |d<   d|j                  v r$|d   j                         j                         |d<   |j!                  d	g      }|d	   j#                  dd      j%                         |d<   |S # t        $ r}t        d      |d }~ww xY w# t
        $ r t        j                  t        d      }
Y w xY w# t
        $ r t        j                  t        d      }Y w xY w)Nr   z'yfinance required: pip install yfinanceSPYF)startendprogressauto_adjustthreadsz^VIXz^VVIXz^VIX9Dr   r   r   )dtypenamer   rY   outer)axisjoin)subset   r   r   )yfinanceImportErrordownloadr  renamer   rJ   Seriesr&   r   rz   concatr   r   r   r   dropnar   r   )r  r  yfespy_rawvix_rawvvix_raw	vix9d_rawr   r   r   r   partsdfs                 r,   load_spy_vix_from_yfinancer!    sP   L kk%u#SXbgkhGkk&3TYchkiG{{7%S5V[ej{kHHEsUX]glmI%gu5<<WEI%gv6==kJI?*8W=DD\R
A+Ix@GGV 	"EZ [!	5qw	/	:	:	<Bo++-335B{Orzz!l+11399;<

"}-335;;==	7)	$BwK''';@@BByMIA  LCD!KL  ?YYU>
?  Aiie-@As;   G8 H 5I  8	HHH$H=<H= $I('I(c                    t        | |      S r)   )r!  )r  r  s     r,   load_spy_from_yfinancer#    s    %eS11r+   c                      e Zd ZU dZded<   ded<   ded<   ded	<   d
ed<   d
ed<   e	 	 	 	 	 	 	 	 dd       ZddZ	 	 	 	 	 	 	 	 	 	 ddZy)TwoLegSpreadPositionuY   Two legs with explicit buy/sell; MTM is mark-to-market P&amp;L for **one** 1× structure.pd.Timestamp
entry_datez%tuple[OptionContract, OptionContract]legsztuple[LegAction, LegAction]leg_actionstuple[float, float]leg_entry_exec_per_sharer&   initial_net_premium
max_marginc           
        t        |      dk7  st        |      dk7  rt        d      g }d}t        ||      D ]  \  }}|dk(  r@t        |      }|&t        d|j                   d|j
                   d      ||t        z  z  }n?t        |      }|&t        d|j                   d|j
                   d      ||t        z  z  }|j                  t        |              t        |d	   j                        t        |d
   j                        }	}t        ||	z
        t        z  t        d|       z   }
t        t        |       |d	   |d
   f|d	   |d
   f|d	   |d
   ft        |      t        |
            S )Nr   zNeed 2 legs and 2 actions.r/   r   z No openable buy quote for leg K= exp=r   z!No openable sell quote for leg K=r   rY   )r'  r(  r)  r+  r,  r-  )lenr   zipr   rS   rQ   CONTRACT_MULTIPLIERr   rz   r&   rm   r1   r%  rN   )r'  r(  r)  execscredit_minus_debitrV   actr   k0k1max_ms              r,   	from_legszTwoLegSpreadPosition.from_legs  s    t9>S-29:: D+. 	$HCe|(-:$'G

|SXY\YgYgXhhi%jkk"b+>&>>")#.:$'HTYZ]ZhZhYiij%kll"b+>&>>"LLr#	$ tAw~~&d1gnn(=BBG22S?Q>Q5RR# ,q'47#$QQ8&+Aha%9 %&8 9U|
 	
r+   c                V    t        |      t        fd| j                  D              S )Nc              3  N   K   | ]  }t        |j                        k\    y wr)   )rN   rQ   )r^   ro   days     r,   ra   z8TwoLegSpreadPosition.all_legs_expired.<locals>.<genexpr>9  s     Ea3)ALL11Es   "%)rN   r   r(  )selfrt   r<  s     @r,   all_legs_expiredz%TwoLegSpreadPosition.all_legs_expired7  s"    E499EEEr+   c                   t        j                  t        |            st        d      t        |      }t	        |      }d}t        | j                  | j                  | j                        D ]  \  }}	}
t	        |j                        }||k\  }t        |      }t        ||j                  |j                  |j                        }|	dk(  rdnd}d }|t        ||t                     }|X|rt!        ||      }nI|j#                  |      }|*t%        |j&                        rt        |j&                        }t)        ||      }|	dk(  r|||
z
  t*        z  z  }||
|z
  t*        z  z  } t        |      S )N!spy_closing_price must be finite.r/   r   r   )r|   r}   r&   r   rN   r1  r(  r)  r+  rQ   rW   r   rS   rU   r   r-   r   getr   r   r   r2  )r=  current_chainspy_closing_pricert   	ffill_midspyr<  totalrV   r5  entexp_nexpiredr   liver   exit_pxr   s                     r,   calculate_current_mtmz*TwoLegSpreadPosition.calculate_current_mtm;  s]    }}U#456@AA%& D,<,<d>[>[\ 	?MCccnn-EUlGS!A)

	D .1E\6uI$(G3D)Y[Q5c3?G'mmA.G7377+;"'.5gyIGe|'C-+>>>#-+>>>;	?> U|r+   N)r'  r&  r(  list[OptionContract]r)  list[LegAction]returnr%  rt   r&  rO  bool
rB  r   rC  r&   rt   r&  rD  zdict[ContractKey, float]rO  r&   )	__name__
__module____qualname____doc____annotations__staticmethodr9  r>  rL  r*   r+   r,   r%  r%    s    c
//,,11"
 "
""
 %"
 
	"
 "
HF,", !, 	,
 ,, 
,r+   r%  c                  B    e Zd ZU dZdZded<   e	 	 	 	 	 	 	 	 dd       Zy)PutCreditSpreadPositionui   
    Same-expiry put credit spread: ``max_loss_per_contract`` = width×100 − net credit (dollars).
    r/   r&   max_loss_per_contractc                D   t         j                  | ||      }|d   dk(  rt        |d   j                        nt        |d   j                        }|d   dk(  rt        |d   j                        nt        |d   j                        }t	        ||z
        }t        dt        |j                              }t        |t        z  |z
  d      }t        |j                  |j                  |j                  |j                  |j                  |j                  t        |            S )Nr   r   rY   r/   ư>)r'  r(  r)  r+  r,  r-  r[  )r%  r9  r&   rS   rm   r1   r,  r2  rZ  r'  r(  r)  r+  r-  )	r'  r(  r)  basesell_kbuy_kwidthcreditmax_losss	            r,   r9  z!PutCreditSpreadPosition.from_legsr  s     $--j$L*5a.F*BtAw~~&dSTgnnH])4Q6)Ad1gnn%uTRSW^^G\FUN#S% 8 89:u22V;TB&((%)%B%B $ 8 8"'/
 	
r+   N)r'  r&  r(  rM  r)  rN  rO  rZ  )rS  rT  rU  rV  r[  rW  rX  r9  r*   r+   r,   rZ  rZ  j  sJ     $'5&
 
"
 %
 
!	
 
r+   rZ  c                <   g }t        | d      rt| j                  }|j                  t        |j                        t        |j                        t        j                  |j                        j                  d      d       nt        | d      rx| j                  D ]i  }|j                  t        |j                        t        |j                        t        j                  |j                        j                  d      d       k t        j                  |d      S )z?JSON list of ``{right, strike, expiry}`` for closed-trade logs.contract%Y-%m-%d)rightrS   expiryr(  ,:)
separators)hasattrre  rz   rT   rU   r&   rS   rJ   rK   rQ   strftimer(  jsondumps)posrowsro   rV   s       r,   _legs_json_from_position_bodyrs    s    !#DsJLLQ]]+/,,q||4==jI	
 
f	88 	CKK 1#CJJ/ ll3>>:CCJO	 ::dz22r+   c           	         t        dt        t        |      t        t              z              }t        |j                        |z
  t        j                  |j                        j                         }| j                  D cg c]n  }|j                  dk(  r]t        j                  |j                        j                         |k(  r-t        |j                        t        |j                        k  r|p }}|syt        |fd      S c c}w )ue   Long put = same expiry as short; strike ≈ short − round(spot × width_frac); must be below short.r0   re   Nc                F    t        t        | j                        z
        S r)   )rm   r&   rS   )ro   target_long_ks    r,   rh   z2_r2b_select_long_put_same_expiry.<locals>.<lambda>  s    Cahh-(G$H r+   ri   )r1   rR   r&   R2_SPREAD_WIDTH_FRAC_OF_SPOTrS   rJ   rK   rQ   rL   rs   rU   r2   )r~   	short_legspot	width_ptsrH  ro   typedrv  s          @r,    _r2b_select_long_put_same_expiryr|    s     CuT{U3O-PPQRI)**+i7MLL--.88:E ==CLL&002e;!((OeI$4$455	 	
E  uHIIs   A3Dc                   | j                   sg S | j                   D cg c]  }|j                  dk(  s| }}|sg S t        j                  | j                        j                         }i }|D ]R  }t        j                  |j                        j                         }t        ||z
  j                        }|dk\  sN|||<   T |sg S t        |j                         fd      \  }	}
|D cg c]\  }t        j                  |j                        j                         |	k(  r*t        j                  t        |j                              r|^ }}|j                  fd       |S c c}w c c}w )zeSame expiry bucket as ``find_target_leg``, puts sorted by closeness to ``target_delta`` (best first).re   r   c                8    t        | d   t              z
        S )NrY   )rm   rv   )rg   r   s    r,   rh   z3_r2b_ordered_short_put_candidates.<locals>.<lambda>  s    s2a53z?;R7S r+   ri   c                X    t        t        | j                        t              z
        S r)   rl   )ro   target_deltas    r,   rh   z3_r2b_ordered_short_put_candidates.<locals>.<lambda>  s    c%.53F"FG r+   )rs   rU   rJ   rK   rt   rL   rQ   rv   rw   r2   itemsr|   r}   r&   rn   r{   )r~   r   r  ro   r{  rt   r   r`   r   best_expr   buckets    ``         r,   !_r2b_ordered_short_put_candidatesr    sA    ??	@11==C+?Q@E@	LL%//1E(*H  ll1<<(2243;$$%!8HSM	 
 	hnn&,STKHa <<%//1X=$--PUVWV]V]P^B_ 	
F 
 KKGKHM' As   E'E'-A!E,c                      e Zd ZU ded<   ded<   ded<   ded<   ded<   edd	       Zedd
       ZddZ	 	 	 	 	 	 	 	 	 	 ddZ	y)ShortPutPositionr&  r'  r   re  r&   entry_exec_per_sharer,  r-  c                    | j                   fS r)   )re  )r=  s    r,   r(  zShortPutPosition.legs  s    r+   c                   t        |      }|&t        d|j                   d|j                   d      t	        |      t
        z  }t	        |j                        }|t
        z  }t        t        |       |t	        |      ||      S )Nz'No openable sell quote for short put K=r/  r   )r'  re  r  r,  r-  )r   r   rS   rQ   r&   r2  r  rN   )r'  re  r   rb  rS   r8  s         r,   from_contractzShortPutPosition.from_contract  s    !(+:FxFWW\]e]p]p\qqrsttr00x',, ,!&r &
 	
r+   c                X    t        |      t        | j                  j                        k\  S r)   )rN   re  rQ   )r=  rt   s     r,   r>  z!ShortPutPosition.all_legs_expired  s!    9T]]-E-E#FFFr+   c                f   t        j                  t        |            st        d      t        |      }t	        |      }| j
                  }| j                  }t	        |j                        }	||	k\  }
t        |      }t        ||j                  |j                  |j                        }d}d }|t        ||t                     }|X|
rt        ||      }nI|j                  |      }|*t!        |j"                        rt        |j"                        }t%        ||      }t        ||z
  t&        z        S )Nr@  r   )r|   r}   r&   r   rN   re  r  rQ   rW   r   rS   rU   r   r-   r   rA  r   r   r   r2  )r=  rB  rC  rt   rD  rE  r<  rV   rG  rH  rI  r   rJ  r   rK  r   s                   r,   rL  z&ShortPutPosition.calculate_current_mtm  s    }}U#456@AA%&mm''#..),%NNJJOO	
  %	 $/iMG?1#s;#--*?wsww'7#CGGnG1'9EcGm'::;;r+   N)rO  tuple[OptionContract, ...])r'  r&  re  r   rO  r  rP  rR  )
rS  rT  rU  rW  propertyr(  rX  r  r>  rL  r*   r+   r,   r  r    s        
 
G&<"&< !&< 	&<
 ,&< 
&<r+   r  c                  J    e Zd ZU ded<   ded<   ded<   ded<   ded	<   ded
<   y)ManagedPositionRegimeregimerv   qtyPositionBodypositionr&   tp_totalsl_totaltime_stop_daysN)rS  rT  rU  rW  r*   r+   r,   r  r  #  s"    N	HOOr+   r  c                  v    e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   d
ed<   ded<   d
ed<   dZded<   y)ClosedTrader&  r'  	exit_dater&   pnl_usdrT   exit_reasonr,  r-  rv   days_in_trader  r  r   	legs_jsonN)rS  rT  rU  rW  r  r*   r+   r,   r  r  -  s?    NN	HIsr+   r  c                      e Zd ZdZdZddZy)_SyntheticLegsShimuc   ``.legs`` only — for :meth:`SyntheticLoader.attach_position_quotes` with multiple open positions.r(  c                    || _         y r)   r  )r=  r(  s     r,   __init__z_SyntheticLegsShim.__init__@  s	    	r+   N)r(  r  rO  None)rS  rT  rU  rV  	__slots__r  r*   r+   r,   r  r  ;  s    mIr+   r  c                   |dk(  r(t        t        t        | j                              d      S |dk(  r#t        t        | j                        t
              S |dv rVt        | t              s#t        t        | j                        t
              S t        t        | j                        t
              S t        t        | j                        t
              S )z
    Per-contract risk anchor used for legacy sizing and for overlap-mode caps (must match
    ``_try_open_regime*`` floor divisors).
    r   r]  r   )r   r   r   )	r1   rm   r&   r,  r-  SIZING_MIN_MAX_RISK_USDr   rZ  r[  )bodyr  s     r,   (_position_sizing_commitment_per_contractr  D  s    
 3uT5567>>5)+BCC88$ 78uT__-/FGG53346MNNuT__%'>??r+   c                     e Zd ZdZefdeeddddddddddddddddddddddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d,d	Ze	 	 	 	 	 	 d-d
       Z		 	 	 	 d.dZ
e	 	 	 	 d/d       Zd0dZ	 	 	 	 	 	 	 	 	 	 d1dZddd	 	 	 	 	 	 	 	 	 	 	 d2dZd3dZddddd	 	 	 	 	 	 	 	 	 	 	 d4dZd5dZd6dZd7dZd8dZd9dZd:dZd;dZd<dZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d=dZ	 d>dd	 	 	 	 	 d?dZd d!	 	 	 	 	 	 	 	 	 d@d"Zdd#	 	 	 	 	 	 	 	 	 dAd$Zdd#	 	 	 	 	 	 	 	 	 dAd%Zdd#	 	 	 	 	 	 	 	 	 	 	 dBd&Zdd#	 	 	 	 	 	 	 	 	 dAd'Zdd#	 	 	 	 	 	 	 	 	 dAd(Z dCd)Z!dDd*Z"dEd+Z#y)FVRPBacktesteru  R1–R4 when SPY > SMA(200); R2 is R2a diagonal + R2b PCS concurrently (separate exits per leg).

    Set ``overlap_portfolio=True`` to allow **stacked** positions: each session can add
    ``overlap_slice_contracts`` per sleeve while aggregate sleeve commitment stays within the same
    risk budget as legacy lump sizing; each position still exits on its own TP/SL/time rules.

    Set ``portfolio_mode`` to an isolated sleeve (e.g. ``"r2_diagonal"``) to benchmark that
    structure alone in its VIX band; ``"full"`` is the production 4-regime stack.

    Set ``macro_overlay_enabled=True`` to add a small **Tier A** sleeve (Yahoo returns + synthetics)
    by VIX band; see ``RenTech.strategy_stack.tier_a_series.DEFAULT_MACRO_OVERLAY_BY_BAND``.
    NTFrY   r   r?   )r   spy_yfinance_startspy_yfinance_endr   vol_risk_scalingr2_crossover_filterssleeve_risk_fractionsoverlay_risk_fractionsoverlay_risk_cap_fractotal_risk_cap_fracdd_risk_scalingdd_scale_enterdd_scale_exitdd_scale_multoverlap_portfoliooverlap_slice_contractsdisable_pmccr2_vix_scale_contractsr2_term_structure_gateportfolio_modemacro_overlay_enabledmacro_overlay_total_fracmacro_overlay_requires_spy200"macro_overlay_exclude_optional_etfmacro_overlay_by_bandc                  || _         |t        |      nt        | _        t	        |      | _        t	        |      | _        | j                  |	      | _        | j                  |
xs i d      | _
        |t        |      nd | _        |t        |      nd | _        | j                  ,d| j                  cxk  rdk  st        d       t        d      | j                  ,d| j                  cxk  rdk  st        d       t        d      | j                  Rt        | j                  j                               }|| j                  dz   kD  rt        d|d	d
| j                  d	      | j                  {t!        | j                  j                               }t        | j                  j                               }||z   | j                  dz   kD  r t        d||z   d	d| j                  d	      t	        |      | _        t        ||nt$              | _        t        ||nt(              | _        t        ||nt,              | _        d| j*                  cxk  r| j&                  cxk  rdk  s(n t        d| j*                   d| j&                   d      d| j.                  cxk  rdk  st        d       t        d      t	        |      | _        t3        |      }|dk  rt        d      || _        t	        |      | _        t	        |      | _        t	        |      | _        t=        |      j?                         }|t@        vrt        dtC        t@               d|      tE        tF        |      | _$        t	        |      | _%        t        |      } d| cxk  rdk  st        d       t        d      | | _&        t	        |      | _'        t	        |      | _(        | jS                  |      | _*        d | _+        d| _,        t        |      | _-        d| _.        t        |      | _/        t        |      | _0        g | _1        g | _2        i | _3        g | _4        |tk        |      | _6        y tk        to        ||            | _6        y )Noverlayr   r/   r0   z'overlay_risk_cap_frac must be in (0, 1]z%total_risk_cap_frac must be in (0, 1]-q=zsum(overlay_risk_fractions)=z.6fz exceeds overlay_risk_cap_frac=z3max(core sleeve risk frac)+sum(overlay risk fracs)=z exceeds total_risk_cap_frac=zKdd_scale thresholds: need 0 < dd_scale_exit < dd_scale_enter < 1 (got exit=z, enter=)zdd_scale_mult must be in (0, 1]rY   z$overlap_slice_contracts must be >= 1zportfolio_mode must be one of z, got g      ?z-macro_overlay_total_frac must be in [0, 0.25]F)8loaderr&   SLIPPAGE_FACTORr   rQ  r  r  _build_sleeve_risk_fractionsr  _validate_risk_fraction_mapr  r  r  r   sumvaluesr1   r  DD_SCALE_ENTER_DEFAULT_dd_scale_enterDD_SCALE_EXIT_DEFAULT_dd_scale_exitDD_SCALE_MULT_DEFAULT_dd_scale_multr  rv   r  r  r  r  rT   stripr$   sortedr
   PortfolioModer  r  r  r  r  _coerce_macro_overlay_by_bandr  _macro_returns_df_macro_overlay_pnl_usd_equity_peak_dd_reduced_modeinitial_capitalcurrent_capitalactive_positions	trade_log
_ffill_mid_equity_curver   _spy_dfr!  )!r=  r  r  r   r  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  ov_sumcore_max_os_pm_mfs!                                    r,   r  zVRPBacktester.__init__b  s   > 9H9Tu_5Zi $%5 6$()=$>!%)%F%FG\%]"&*&F&F"(b 'G '
#
 -B,ME'(SW 	" BUA`5)<#=fj %%1$,,33FGG 4FGG##/t?W?W9^[^9^DEE :_DEE%%144;;=>F22U:: 26#,>]11#68  ##/455<<>?H44;;=>F& 4#;#;e#CC I(U[J[\_I` a3373K3KC2PR   $O4$,8N>T 
 $*6M<Q
 $*6M<Q
 d))FD,@,@F3F!001$:N:N9OqR  d))0S0>?? 1>??!%&7!8)*7CDD'*$ .&*+A&B#&*+A&B#.!''),,08N1O0PPVWZV]^  #=#6%)*?%@",-s"d"LMM #LMM(+%-12O-P*267Y2Z/%)%G%GH]%^"6:-0##(#9&+$_5$_579,.46?A+F3DL+*+=?OPDLr+   c                  i }| j                         D ]p  \  }}t        |      j                         }|st        | d      t	        |      }t        j                  |      r
|dkD  r|dk  st        | d| d|       |||<   r |S )Nz" risk fraction key cannot be emptyr/   r0   z risk fraction for 'z$' must be finite and in (0, 1], got )r  rT   r  r   r&   r|   r}   )mappingr   r   r   r   r  fvs          r,   r  z)VRPBacktester._validate_risk_fraction_map  s     !#MMO 		DAqq6<<>D E7*L!MNNqBMM"%"s(rSy g1$7[\][^_  CI		 
r+   c           
     `   t        t              }|rk| j                  t        |      d      }|j                         D ]<  \  }}||vr.t	        d| ddj                  |j                                      |||<   > |j                         D ci c]  \  }}|t        |       c}}S c c}}w )Nsleever  zUnknown sleeve 'z'. Valid sleeves: z, )dictrG   r  r  r   r  keysr&   )r=  customr^  cleanr   r   s         r,   r  z*VRPBacktester._build_sleeve_risk_fractions  s     1244T&\4RE 1D=$*1#-?		$))+@V?WX  Q )-

5158555s   B*c           
        ddl m}m} | -|j                         D ci c]  \  }}|t	        |       c}}S i }| j                         D ]  \  }}t        |      j                         j                         }|dvrt        d      g }|D ]J  }	t        |	      j                         }
|
|vrt        d|
dt        |             |j                  |
       L t	        |      ||<    |S c c}}w )Nr   )DEFAULT_MACRO_OVERLAY_BY_BANDMACRO_OVERLAY_ALLOWED_IDS)r1r2r3r4z<macro_overlay_by_band keys must be 'r1', 'r2', 'r3', or 'r4'zmacro overlay id z not in allowed set )$RenTech.strategy_stack.tier_a_seriesr  r  r  tuplerT   r  lowerr   r  rz   )r   r  r  r   r   r   tupkkcolsro   css              r,   r  z+VRPBacktester._coerce_macro_overlay_by_band  s    	

 ;,I,O,O,QRDAqAuQxKRR*,iik 	"FAsQ%%'B11 !_`` D  V\\^66$+B61EfMfFgEhi  B  DkCG	" 
 Ss   C)c                l    t        j                  |      sy |t        k  ry|t        k  ry|t        k  ryy)Nr  r  r  r  )r|   r}   
VIX_R1_MAX
VIX_R2_MAX
VIX_R3_MAX)r=  vxs     r,   _vix_macro_band_keyz!VRPBacktester._vix_macro_band_key  s4    }}R 
?r+   c                   | j                   sy| j                  | j                  j                  ry| j                  dk  ryt	        j
                  t        |            r3t        j                  |      rt	        j
                  t        |            syt        |      }| j                  rLt        j                  |      rt	        j
                  t        |            syt        |      t        |      kD  sy| j                  |      }|y| j                  j                  |      }|syt        |      }|| j                  j                  vry| j                  j                  |   }	g }
|D ]o  }|| j                  j                   vr|	|   }t        j                  |      s7t	        j
                  t        |            sV|
j#                  t        |             q |
syt        t%        |
      t'        |
      z        }t        | j(                        }|t        | j                        z  |z  }||z   | _        | xj*                  |z  c_        | j,                  j#                  || j(                  f       y)z
        Additive cash bump: ``capital += capital * macro_overlay_total_frac * mean(daily returns)``
        for Tier A columns registered for this VIX band (see ``tier_a_series`` defaults).
        Nr/   )r  r  r   r  r|   r}   r&   rJ   notnar  r  r  rA  rN   r   locr   rz   r  r0  r  r  r  )r=  rt   r   
spy_sma200r   r  bandnamesdrowrsro   zr_barc0bumps                   r,   _apply_macro_overlayz"VRPBacktester._apply_macro_overlay$  s    ))!!)T-C-C-I-I((C/eI./BHHY4GDMMZ_`iZjLk9--HHZ(T]]5;L-M)$uZ'88''+<**..t4eD**000$$((+ 	$A..666AAxx{t}}U1X6		%(#	$ c"gB'(4''(E$77885@!Dy##t+#!!5$*>*>"?@r+   r0   )global_multregime_multc                   t        | j                  |         }t        |      |z  t        |      z  t        |      z  t        |      z  S r)   )r&   r  )r=  r  capitaldd_multr  r  rfs          r,   _target_risk_usdzVRPBacktester._target_risk_usdW  sF     4--f56W~"U7^3eK6HH5Q\K]]]r+   c                @    t        fd| j                  D              S )Nc              3     K   | ]I  }|j                   k(  r8t        |j                  |j                         t        |j                        z   K y wr)   )r  r  r  r&   r  )r^   mpr  s     r,   ra   z@VRPBacktester._total_sizing_commitment_regime.<locals>.<genexpr>d  sD      
yyF" 5R[["))LuUWU[U[}\
s   AA)r  r  )r=  r  s    `r,   _total_sizing_commitment_regimez-VRPBacktester._total_sizing_commitment_regimec  s#     
++
 
 	
r+   r  r  r  r  c                  t        |      j                         }|| j                  vryt        || j                  n|      }|t        | j                  |         z  t        |      z  t        |      z  t        |      z  S )z
        Risk budget helper for true hedge overlays.

        Overlay-specific risk fractions are independent of core sleeves and scale with account equity.
        r/   )rT   r  r  r&   r  )r=  overlay_namer  r  r  r  r  caps           r,   overlay_target_risk_usdz%VRPBacktester.overlay_target_risk_usdj  s     < &&(t222GOD((IU466t<==gNQVWbQccfkg
 
 	
r+   c                    | j                   sy g }| j                   D ]'  }|j                  |j                  j                         ) |rt	        t        |            S d S r)   )r  extendr  r(  r  r  )r=  r(  r  s      r,   _unwrap_for_syntheticz#VRPBacktester._unwrap_for_synthetic  sU    $$%''' 	*BKK(()	*26!%+.@D@r+   c                    |j                   D ]C  }t        |j                        st        |j                        | j                  t        |      <   E y r)   )rs   r   r   r&   r  rW   )r=  r~   ro   s      r,   _refresh_ffill_from_chainz'VRPBacktester._refresh_ffill_from_chain  s<     	@Aquu~38<Q0	@r+   c                    |j                   D ]C  }t        |j                        st        |j                        | j                  t        |      <   E y r)   )r(  r   r   r&   r  rW   )r=  rq  rV   s      r,   _seed_ffill_from_positionz'VRPBacktester._seed_ffill_from_position  s>    88 	DCsww5:377^S 12	Dr+   c                8    |j                   j                  |      S r)   )r  r>  )r=  r  rt   s      r,   _all_legs_expiredzVRPBacktester._all_legs_expired  s    {{++E22r+   c                   | j                   sy| j                  }d|j                  vry|j                  |   }d}d}|j	                  d      }|j	                  d      }t        j                  |      rGt        j                  |      r2t        |      dkD  r$t        |      t        |      t        z  kD  rt        }d}|j	                  d      }	d|j                  v r)t        j                  |	      rt        |	      t        kD  rd}t        j                  |      rCt        j                  |      r.t        |      dkD  r t        |      t        |      t        z  kD  rd}|rt        }||fS )	z
        Returns (global_mult, r2_extra_mult) applied to target_risk.
        R2 uses global_mult * r2_extra_mult; other regimes use global_mult only.
        )r0   r0   r   r0   r   r/   Fr   T)r  r  r   r	  rA  rJ   r  r&   VVIX_GLOBAL_THRESHOLD_MULTVVIX_GLOBAL_RISK_SCALEVIX_R2_CHANGE_THRESHR2_VVIX_STRESS_MULTR2_STRESS_RISK_SCALE)
r=  r  sdfr  gr  vvvm	r2_stresschs
             r,   _vol_risk_multipliersz#VRPBacktester._vol_risk_multipliers  s   
 $$lls{{*ggajWW\"WW]#88B<BHHRLU2Y_Ry59'AAA*	WW\"3;;&288B<E"IH\<\I88B<BHHRLU2Y_Ry59'::: 	%B2wr+   c                   | j                   s{| j                  rn| j                  }|j                  |   }|j	                  d      }t        j                  |      r-t        j                  t        |            rt        |      |kD  ryyt        t              |cxk  rt        t              k  sy y| j                  }|j                  |   }| j                  rS|j	                  d      }t        j                  |      r-t        j                  t        |            rt        |      |kD  ryt        |d         }dt         }||j                  v rS|j	                  |      }t        j                  |      r-t        j                  t        |            r|t        |      k  ry|j	                  dt               }	|j	                  d      }
t        j                  |	      rt        j                  |
      rrt        j                  t        |	            rTt        j                  t        |
            r6t        t        |
      d      }t        |	      |z  }|t        t               k\  ryy)	uP  
        R2-only: apply SPY / VIX ratio gates only when ``12 <= VIX <= 20`` (the R2 band). Outside
        that range, return True (defensive). In this band, extra gates complement the global
        SPY>SMA(200) rule for R2 (diagonal + put spread). Complacency (VIX<12) and elevated fear (VIX>20) use
        other regimes.

        Optional ``r2_term_structure_gate``: block entries when VIX9D (9-day) > VIX (30-day), i.e.
        the front end of the VIX term structure is inverted — a near-term fear signal that makes
        short-vol R2 entries more likely to be stopped out.
        r   FTr   r   r   r   r]  )r  r  r  r	  rA  rJ   r  r|   r}   r&   r  r  r   r   r   r1   R2_VIX_MA_TO_MAX_BLOCK_ABOVE)r=  r  r  r4  r  vix9dspy_pxcol_spys_shortvmavmxdenomratios                r,   _r2_crossover_entry_okz$VRPBacktester._r2_crossover_entry_ok  s    ((**llggaj.88E?t}}U5\'BuU|VXGX j!R<5+<< =llggaj&&GGM*Exx4==u#>5<RTCTs7|$-./ckk!ggg&Gxx T]]5>%BU7^+ gg!4 567ggl#HHSMeCj)eCj)c
D)E#J&E:;;r+   c                   | j                   syt        |      }t        j                  |      s| j                  r| j
                  S dS t        | j                  |      | _        | j                  }|dkD  r||z
  |z  nd}| j                  s|| j                  k\  rd| _        n|| j                  k  rd| _        | j                  r| j
                  S dS )z
        Update peak / reduced-mode flag from **marked** portfolio equity (cash + open MTM).
        Returns a multiplier in ``(0, 1]`` applied to **new-entry** risk (all regimes).
        r0   r  r/   TF)
r  r&   r|   r}   r  r  r1   r  r  r  )r=  marked_equitymepeakdds        r,   _update_dd_state_and_multz'VRPBacktester._update_dd_state_and_mult  s    
 ##=!}}R *.*?*?4&&HSH 1 126  #'%<dRi4S$$T)))(,%T((((-%&*&;&;t""DDr+   c           	     r    | j                   r||S t        dt        ||z  t        t              z              S )u  
        When ``r2_vix_scale_contracts`` is enabled, scale the per-day contract slice for R2
        entries proportionally to VIX level. Higher VIX ⟹ richer premium ⟹ deploy more contracts.

        Formula: ``max(1, round(base * vx / VIX_R1_MAX))``.
        At VIX=12 (band floor): 1× base. At VIX=20 (band ceiling): ~1.67× base.
        If ``base`` is None (legacy non-overlap sizing), return None unchanged.
        rY   )r  r1   rR   r&   r  )r=  r  r^  s      r,   _r2_scaled_contractsz"VRPBacktester._r2_scaled_contracts  s6     **dlK1eD2Ij(99:;;r+   c          
     
   g }	t        j                  |      r3t        j                  |      rt        j                  t	        |            s|	S t        j                  |      rt        j                  t	        |            s|	S |t	        |      kD  s|	S t	        |      }
| j                  |      \  }}|
t        k  rd}| j                  }|dk(  r^|
t        k\  s| j                  r|	S | j                  ||| j                  d| j                  |d      |      }||	j                  |       |	S |dk(  rt        |
cxk  r
t        k  s|	S  |	S | j                  ||
      s|	S | j                  |
|      }| j                  d| j                  |||      }| j!                  ||||      }||	j                  |       |	S |d	k(  rt        |
cxk  r
t        k  s|	S  |	S | j                  ||
      s|	S | j                  |
|      }| j                  d	| j                  |||      }| j#                  |||||      }||	j                  |       |	S |d
k(  rt        |
cxk  r
t        k  s|	S  |	S | j                  ||
      s|	S | j                  |
|      }| j                  d	| j                  |||      }| j                  d| j                  |||      }| j!                  ||||      }| j#                  |||||      }|||	j%                  ||g       |	S |dk(  r[|
t        kD  r	|
t&        k  s|	S | j)                  ||| j                  d| j                  ||      |      }||	j                  |       |	S |dk(  rR|
t&        k  r|	S | j+                  ||| j                  d| j                  ||      |      }||	j                  |       |	S |
t        k  rS| j                  sE| j                  ||| j                  d| j                  ||      |      }||	j                  |       |	S |
t        k  r| j                  ||
      r| j                  |
|      }| j                  d	| j                  |||      }| j                  d| j                  |||      }| j!                  ||||      }| j#                  |||||      }|||	j%                  ||g       |	S |
t&        k  rG| j)                  ||| j                  d| j                  ||      |      }||	j                  |       |	S | j+                  ||| j                  d| j                  ||      |      }||	j                  |       |	S )a  
        When ``overlap_slice_contracts`` is set, each sleeve adds that many contracts per day
        subject to the same ``target_risk_usd`` cap as legacy lump sizing. When ``None``, legacy
        lump sizing (floor of risk / per-contract commitment) applies.
        r0   r   r   )r  r  r  r  r    r   r   r   r!   r"   r   r#   r   )r|   r}   rJ   r  r&   r:  r  r  r  !_try_open_regime1_weekly_strangler  r  rz   r  rE  rM  _try_open_regime2_diagonal_try_open_regime2_put_spread_br&  r  _try_open_regime3_put_spread_try_open_regime4_credit_spread)r=  todayr  r~   r   r
  r   r  r  r   r  g_multr2_multpmoner2_sl	diag_riskr2aspread_riskr2bs                       r,   _collect_new_positions_for_dayz,VRPBacktester._collect_new_positions_for_day  s   " &(i(RXXj-AdmmTYZdTeFfJ#eI6F(GJE*--J944Q7
?F  Z4#4#4
88%% 00# #	 &  )@ 9 
C 

3J"2
2
 3
..q"5
--b2IJE--,,"# . I 11(-	 2 C 

3J"2
2
 3
..q"5
--b2IJE//,,"# 0 K 55(- 6 C 

3J?"2
2
 3
..q"5
--b2IJE//,,"# 0 K --,,"# . I 11(-	 2 C 55(- 6 C 3?

C:&J Oj(8
33%% 00# &	 &  )@ 4 
C 

3J##Z
66%%# 00# &	 &  )@ 7 
C 

3J 
?$$<<)) $ 4 4 '$*	 *  -D = 
 ?JJsOz 
y :**1b111"6MN"33 00# & ' 4  !11 00# & ' 2 	 55,1	 6  99,1 :  ?sJJSz*: 
9 :33%% 00# &	 &  )@ 4 
C 

3 
 66%%# 00# &	 &  )@ 7 
C 

3
r+   )show_progressc                  | j                   }t        | j                  t              r|| j                  _        | j
                  | _        |j                  j                         | j
                  fg| _	        t        | j
                        | _        d| _        d| _        d | _        | j                  rLddlm}  ||| j$                        | _        | j                  j&                  rt)        j*                  dd       |;t-        | j                  j/                         D ch c]  }t1        |       c}      }n"t-        |D ch c]  }t1        |       c}      }|}|r|rt3        |d	d
dd      }t4        }t7        | j8                         	 |D ]F  }	t1        |	      }||j                  vr*t)        j*                  d|j;                          dd       Gt        |j<                  |df         }
|j<                  |df   }|j<                  |df   }| j                  j?                  |	      }t        | j                  t@              r*| j                  jC                  || jE                                |jF                  r| jI                  |       nR| jJ                  rFt        |tL              s6t)        j*                  d|	j;                          dd       tO        |g       }d}| jJ                  D ]F  }|jP                  jS                  ||
|| jT                        }||t        |jV                        z  z  }H | j                  |z   }| jY                  |      }g }| jJ                  D ]  }|jP                  }t[        |	|j\                  z
  j^                        }|jS                  ||
|| jT                        }|t        |jV                        z  }d }||j`                  k\  rd}n||jb                  k  rd}|| je                  ||      rd}n|||jf                  k\  rd}|| xj                  |z  c_        | jh                  jk                  tm        |j\                  |	||t        |jn                        |jV                  z  |jp                  |jV                  z  ||jr                  |jV                  tu        |      
             | j                  jk                  |	| j                  f       |jk                  |        || _%        | jv                  ra| jy                  |	|||
|||| jz                        }|D ]8  }| j}                  |jP                         | jJ                  jk                  |       : nP| jJ                  sD| jy                  |	|||
|||d       }|r)|D ]  }| j}                  |jP                          || _%        | j                  |	|
||       I 	 t7        |       | jh                  S c c}w c c}w # t7        |       w xY w)NFr/   r   )load_macro_returns_aligned)exclude_optional_etfzOmacro_overlay_enabled but Yahoo macro return matrix is empty; overlay is inert.r   
stacklevelzVRP backtestr<  g333333?g?)descunitmininterval	smoothingzNo SPY row for z; skipping day.r   r   r   zEmpty option chain on z"; MTM uses ffill / intrinsic only.)rt   rs   )rC  rt   rD  take_profit	stop_lossr>  	time_stop)
r'  r  r  r  r,  r-  r  r  r  r  )r   r
  r   r  r  )@r  r   r  r   r   r  r  r   r2   r  r&   r  r  r  r  r  r  rb  r  r   warningswarnr  iter_chain_datesrN   r   r'   r4   r   dater	  get_chain_for_dater   attach_position_quotesr'  rs   r)  r  r   r   r  rL  r  r  rK  rv   r'  rw   r  r  r-  r  r  rz   r  r,  r-  r  rs  r  r_  r  r+  r  )r=  trading_daysr`  r4  rb  r  rw   day_iterprev_sfrU  r   r
  r   r~   open_pnl_totalmp0pnl_one0rG  r  
still_openr  rq  days_inpnl_one	pnl_totalreasonnew_psops                               r,   run_backtestzVRPBacktester.run_backtest  s    lldkk#45!$DKK#33"yy}}0D0DEF!$"6"67 %&)#!%%%W%?%)%L%L&D" %%++e 
 1M1M1OPA9Q<PQD>A9Q<>?DT#H #T112q	*! mSe$CII%MMOAFFH:_"MZ[\!#''!W*"56	 WWQ	\2
GGA{N3	66u=dkk?;KK66ud>X>X>Z[??2259**:eEY3ZMM0>`a#$ (a2>E!$00 @C"||AA*3"&//	  B  H #hsww&??N@ !% 4 4~ E88G46
// (.B++C!53>>#9"?"?@G!77*3"&//	 8 G !(%- 7I)-F BKK/!."bkk1!,~$*@*@Q*G!3Gr7H7H,H!,),,	9,--'+.>>*/(1,249#:Q:Q4RUWU[U[4[+.>>BFF+B.5')yy$&FF*G*L **115$:N:N2OP"))"-Q(.R )3%))!@@"+#-"+ '040L0L A 	F % 966r{{C--44R89 ..!@@"+#-"+ '04 A 	F "( HB ::2;;GH06-))%J	R[mS` !)~~G Q>~ !)s   W1W6QW; ;Xre   )rU   c                  |j                   st        |t              sy g }g }|D ]~  \  }}}		 |j                  |||      }
t        |
j                        s,t        |
j                        st        |
j                        s y |j                  |
       |j                  |	        	 t        j                  |||      S # t        $ r6}t        j                  d|j                          d| d       Y d }~ y d }~ww xY w# t        $ r5}t        j                  d|j                          d| d       Y d }~y d }~ww xY w)Nr   r  rU   zTwo-leg open failed on : r   rd  )rs   r   r   find_target_legr   rm  rn  rp  r   r   r   r   r   rz   r%  r9  )r=  rU  r~   specsrU   r(  actionsr   rn   r   rV   r  s               r,   _open_two_legzVRPBacktester._open_two_leg  s#    z%9M'N%'#%"' 	#C++"!& + ,  377#Ysww-?9SWWCUKKNN6"	#	'11%wGG   7

~RsKXYZ  	MM3EJJL>A3GTUV	s/   B=&C? =	C<+C77C<?	D=+D88D=rO  c          	     >   |j                   st        |t              sy t        |t        t
        t              }|y |\  }}||g}|D ]E  }	t        |	j                        rt        |	j                        r/t        |	j                        rE y  	 t        j                  ||ddg      }
t%        t'        |
j(                              }|dk  ry |}|,t+        dt-        t/        j0                  ||z                    }n5t+        dt-        |            }| j3                  d      ||z  z   |d	z   kD  ry |}t4        |z  |z  }t6        |z  |z  }t9        d||
||t:        
      S # t        $ r5}t        j                   d|j#                          d| d       Y d }~y d }~ww xY w)Nr   z"R1 weekly strangle open failed on r  r   rd  r]  rY   r   &.>r  r  r  r  r  r  )rs   r   r   r   R1_STRANGLE_DTER1_CALL_DELTAR1_PUT_DELTAr   r   r   r   r   r%  r9  r   rm  rn  rp  rm   r&   r,  r1   rv   r|   floorr  
R1_TP_FRAC
R1_SL_FRACr  R1_TIME_STOP_DAYS)r=  rU  r~   target_risk_usdr  pairr   r   r(  rV   r  r  debitmax_riskr  slr  r  s                     r,   rP  z/VRPBacktester._try_open_regime1_weekly_strangle  s    z%9M'N4	
 < ''" 	C377#Ysww-?9SWWCU		'11%u~ND E$22344<"*aTZZ((BCDECQ345B33F;hmKo`dNddC%+%+,
 	
'  	MM4UZZ\N"QCH 	s   E 	F'+FFc          	        |j                   st        |t              sy| j                  ||t	        t
                    }|yt        t        |j                        t              }|,t        dt        t        j                  ||z                    }n5t        dt        |            }| j                  d      ||z  z   |dz   kD  ry|}t        |z  }	t        |z  }
t!        d|||	|
t"              S )u?   R2a — put diagonal only (same economics as pre-composite R2).NrY   r   r  r  )rs   r   r   r  r   r;   r1   r&   r-  r  rv   r|   r  r  R2_TP_PER_CONTRACTR2_SL_PER_CONTRACTr  R2_TIME_STOP_DAYS)r=  rU  r~   r  r  r  diag_risk_per_contractr  r  r  r  s              r,   rQ  z(VRPBacktester._try_open_regime2_diagonal  s     z%9M'N!!%5F0GH< "%U4??%;=T!U"*JJ'*@@C Q345B44Z@CY\^C^^!D() C%+%+,
 	
r+   c          	        |j                   st        |t              syt        j                  t        |            rt        |      dk  ryt        |t        t              t        t                    }d}d}|D ]  }	t        |	j                        s+t        |	j                        st        |	j                        sCt        ||	t        |            }
|
\t        |
j                        s+t        |
j                        st        |
j                        s|	|
}} n ||6t!        j"                  d|j%                          dt'        |       dd       y||g}dd	g}	 t(        j+                  |||      }t/        dt        |j0                              }t        |j2                        }t/        |t4              }|,t/        dt        t        j6                  ||z                    }n5t/        dt        |            }| j9                  d      ||z  z   |dz   kD  ry|}t:        |z  |z  }d|z  |z  }t=        d||||t>              S # t,        $ r5}t!        j"                  d
|j%                          d| d       Y d}~yd}~ww xY w)uh   R2b — put credit spread: short ~−22Δ @ ~30D; long same expiry at short − round(spot×width_frac).Nr   z&R2b put spread: no short/long pair on z (tried z short candidates)r   rd  r   r   zR2b put spread open failed on r  r/   rY   r   r        r  ) rs   r   r   r|   r}   r&   r  rv   R2_SPREAD_TARGET_DTER2_SPREAD_SHORT_DELTAr   r   r   r   r   r|  rm  rn  rp  r0  rZ  r9  r   r1   r,  r[  r  r  r  R2_SPREAD_TP_FRACr  R2_SPREAD_TIME_STOP_DAYS)r=  rU  r~   r  r   r  
candidatesrx  long_legcandlgr(  r  r  r  rb  r  per_cr  r  r  r  s                         r,   rR  z,VRPBacktester._try_open_regime2_put_spread_b%  sj    z%9M'N}}U9-.%	2Ba2G63+,e4I.J

 ,0	*. 		D488$i.AYtxxEX1%uY?OPBz266?Ibff,=266AR"&xI		  0MM8 Gj/**<>
 8$5/	*44UD'JD S% 8 89:334H56"*JJ'%/C Q345B33K@52:MP_bfPffC$v-3(?S(3
 	
-  	MM:5::<.1#N[\]	s   I 	J+I>>Jc          	     (   |j                   st        |t              sy g }g }t        D ]~  \  }}}		 |j	                  ||d      }
t        |
j                        s,t        |
j                        st        |
j                        s y |j                  |
       |j                  |	        	 t        j!                  |||      }t#        dt%        |j&                              }t%        |j(                        }t#        |t*              }|,t#        dt-        t/        j0                  ||z                    }n5t#        dt-        |            }| j3                  d	      ||z  z   |d
z   kD  ry |}t4        |z  |z  }d|z  |z  }t7        d	||||t8              S # t
        $ r6}t        j                  d|j                          d| d       Y d }~ y d }~ww xY w# t
        $ r5}t        j                  d|j                          d| d       Y d }~y d }~ww xY w)Nre   r  zR3 put spread open failed on r  r   rd  r/   rY   r   r  r  r  )rs   r   r   rC   r  r   rm  rn  rp  r   r   r   r   r   rz   rZ  r9  r1   r&   r,  r[  r  rv   r|   r  r  
R3_TP_FRACr  R3_TIME_STOP_DAYSr=  rU  r~   r  r  r(  r  r   rn   r   rV   r  r  rb  r  r  r  r  r  r  s                       r,   rS  z*VRPBacktester._try_open_regime3_put_spreadl  s    z%9M'N%'#%"5 	#C++"!& # ,  377#Ysww-?9SWWCUKKNN6"	#	*44UD'JD S% 8 89:334H56"*JJ'%/C Q345B33G<urzIO^bLbbC&,(?S(,
 	
?   =ejjl^2aSQ^_`  	MM9%**,r!MZ[\	/   F*G 	G+GG	H+HHc          	     (   |j                   st        |t              sy g }g }t        D ]~  \  }}}		 |j	                  ||d      }
t        |
j                        s,t        |
j                        st        |
j                        s y |j                  |
       |j                  |	        	 t        j!                  |||      }t#        dt%        |j&                              }t%        |j(                        }t#        |t*              }|,t#        dt-        t/        j0                  ||z                    }n5t#        dt-        |            }| j3                  d	      ||z  z   |d
z   kD  ry |}t4        |z  |z  }d|z  |z  }t7        d	||||t8              S # t
        $ r6}t        j                  d|j                          d| d       Y d }~ y d }~ww xY w# t
        $ r5}t        j                  d|j                          d| d       Y d }~y d }~ww xY w)Nre   r  zCredit spread open failed on r  r   rd  r/   rY   r   r  r  r  )rs   r   r   rE   r  r   rm  rn  rp  r   r   r   r   r   rz   rZ  r9  r1   r&   r,  r[  r  rv   r|   r  r  
R4_TP_FRACr  R4_TIME_STOP_DAYSr  s                       r,   rT  z-VRPBacktester._try_open_regime4_credit_spread  s    z%9M'N%'#%"5 	#C++"!& # ,  377#Ysww-?9SWWCUKKNN6"	#	*44UD'JD S% 8 89: 334H56"*JJ'%/C Q345B33ODurzQTcfjTjjC&,(?S(",
 	
C   =ejjl^2aSQ^_`  	MM9%**,r!MZ[\	r  c                    |D cg c]  }|j                   |k(  s| }}t        |      }|dk(  rddddS t        d |D              }|||z  t        t        d |D              |z        dS c c}w )Nr   r/   )nwin_rateavg_pnlc              3  @   K   | ]  }|j                   d kD  sd  ywr   rY   Nr  r^   ts     r,   ra   z.VRPBacktester._regime_stats.<locals>.<genexpr>  s     3QYY]13   c              3  4   K   | ]  }|j                     y wr)   r  r  s     r,   ra   z.VRPBacktester._regime_stats.<locals>.<genexpr>  s      8q 8s   )r  r0  r  r&   )r=  tradesr  r  subr  winss          r,   _regime_statszVRPBacktester._regime_stats  s~     7QAHH$6q77H6<<3c33qS 8C 881<=
 	
 8s
   A-A-c                   | j                   }t        |      }| j                  }| j                  }|j                  j                         |j                  j                         }}t        ||z
  j                  dz  d      }| j                  r|| j                  z  dz
  n
t        d      }| j                  dkD  r|dkD  r|| j                  z  d|z  z  dz
  }	nt        d      }	| j                  }
d}| j                  D ])  \  }}t        |
|      }
|
dkD  st        ||
|z
  |
z        }+ ||rt        d |D              |z  nd| j                  |||	|t        | j                        t        | j                        t        | j                        t        | j                         d}d	D ]5  }| j#                  ||      }|d
   || d<   |d   || d<   |d   || d<   7 |S )Ng     v@r  r0   nanr   r/   c              3  @   K   | ]  }|j                   d kD  sd  ywr  r  r  s     r,   ra   z(VRPBacktester.metrics.<locals>.<genexpr>  s     @1!))a-Q@r  )total_tradesr  starting_capitalending_capitaltotal_returncagrmax_drawdownr  r  macro_overlay_pnl_usdr  r   r  _tradesr  	_win_rater  _avg_pnl)r  r0  r  r  r   r2   r1   rw   r  r&   r  r  rQ  r  r  r  r  r  )r=  r  r  end_capr4  t0t1yearsr  r  rI  max_ddr   r#  r^  rsts                    r,   metricszVRPBacktester.metrics  s   K&&ll#))--/BR"WNNV+T2AEAUAU$"6"66#=[`af[g!#!d222eDsJD<D##(( 	:FAstS>DaxVdSjD%89	: IJ@@@1DPS $ 4 4%("#D$8$89%)$*D*D%E%*4+F+F%G(-d.K.K(L 

 
	1A ##FA.B"$S'DA3g$&zNDA3i!#%i=DA3h 
	1 r+   c                <   | j                         }d}|j                  d      r,d| j                  dd| j                  dd| j                  d}d}| j
                  r"| j                  rdnd}d	| j                   | d
}g }| j                  r|j                  d       | j                  r|j                  d       |rddj                  |      z   nd}d}| j                  dk7  rd| j                   d}d}| j                  r$d| j                  dd|j                  dd      d}t        d| | | | |        t        d       t        d|d   d       t        d|d   d       t        d|d   d       t        d |d!   d       |d"   }	t        d#t!        |	t"              rt%        j&                  |	      r|	dnd$z          t        d%|d&           t        d'       d(D ]0  \  }
}t        d)|
 d*||d+z       d,||d-z      dd.||d/z      d       2 y )0Nr  r  u    | DD sizing: enter≥.0%u    → ×z.2fu	   , exit≤z +VIX-scalez | overlap_portfolio=ON slice=z. (daily add per sleeve within legacy risk cap)zPMCC/R1-disabledzR2-term-struct-gatez |  r   z | portfolio_mode=z (isolated benchmark)z | macro_overlay frac=z.2%z cumulative $r  r/   z,.2fzI4-Regime VRP | SPY > SMA(200) required for new trades | base risk sleevesz  --- Overall ---z  Starting capital ($): r  z  Ending capital ($):   r  z  Total return:         r  z  Max drawdown:         r  r  z  CAGR (full window):   zn/az  Total trades:         r  u#     --- By regime (R1–R4 + R2b) ---))zR1 Weekly strangle (VIX<12)r   )u   R2a Put diagonal (12–20)r   )u   R2b Put spread (12–20)r   )u   R3 Put spread (20–30)r   )zR4 Credit spread (VIX>30)r   z  r  r  z trades, WR r  z, avg $r  )r  rA  r  r  r  r  r  r  r  rz   r  r  r  r  r  printr   r&   r|   r}   )r=  r   dd_noteoverlap_notevix_scale_tagfilter_notesfilter_note	mode_note
macro_note_cagrr   rj   s               r,   print_metricszVRPBacktester.print_metrics   s   LLN55"#()=)=c(B'$J]J]^aIb c--c24  !!-1-H-HMbM01M1M0N}o ^@ @  #% 23&& 56:Fusxx55B	&(,T-@-@,AAVWI
%%()F)Fs(K L uu%<cB4HJ  	WX_W`am`nozn{  }F  |G  HR  GS  T	
 	!"(+=)>t(DEF(+;)<T(BCD(>):3(?@A(>):3(?@A&	&!+E5!9dmmE>R%X]_	
 	(>):(;<=34
 	JE3 UG2ai01 2k)*3/ 0#
*+D13	r+   )8r  z<Union[SyntheticLoader, IVolatilityLoader, ThetaChunksLoader]r  r&   r   zpd.DataFrame | Noner  rT   r  rT   r   r   r  rQ  r  rQ  r  dict[Regime, float] | Noner  dict[str, float] | Noner  r   r  r   r  rQ  r  r   r  r   r  r   r  rQ  r  rv   r  rQ  r  rQ  r  rQ  r  r  r  rQ  r  r&   r  rQ  r  rQ  r  !dict[str, tuple[str, ...]] | NonerO  r  )r  dict[str, float]r   rT   rO  r  )r  r  rO  rF   )r   r  rO  zdict[str, tuple[str, ...]])r  r&   rO  z
str | None)
rt   r&  r   r&   r
  objectr   r  rO  r  )r  r  r  r&   r  r&   r  r&   r  r&   rO  r&   )r  r  rO  r&   )r"  rT   r  r   r  r&   r  r&   r  r&   rO  r&   )rO  z_SyntheticLegsShim | None)r~   r   rO  r  )rq  r  rO  r  )r  r  rt   r&  rO  rQ  )r  r&  rO  r*  )r  r&  r  r&   rO  rQ  )rG  r&   rO  r&   )r  r&   r^  
int | NonerO  r  )rU  r&  r  r&  r~   r   r   r&   r
  r&   r   r&   r  r&   r  r  rO  zlist[ManagedPosition]r)   )rs  zlist[pd.Timestamp] | Noner`  rQ  rO  list[ClosedTrade])
rU  r&  r~   r   r  r:   rU   rT   rO  zTwoLegSpreadPosition | None)
rU  r&  r~   r   r  r&   r  r  rO  ManagedPosition | None)rU  r&  r~   r   r  r&   r   r&   r  r  rO  r  )r  r  r  r  rO  dict[str, Any]rO  r  )rO  r  )$rS  rT  rU  rV  DEFAULT_STARTING_CAPITALDEFAULT_YF_STARTDEFAULT_YF_ENDr  rX  r  r  r  r  r  r  r  r$  r'  r)  r+  r-  r:  rE  rK  rM  r_  r  r  rP  rQ  rR  rS  rT  r  r  r  r*   r+   r,   r  r  T  s3     ":x
 '+"2 .(,!%%)<@:>.2,0 %'+&*&*"''("',',(.&+*..238CG;xLx x
 $x  x x &x x #x  :x !8x  ,x *x x  %!x" $#x$ $%x&  'x( "%)x* +x, !%-x. !%/x0 &1x2  $3x4 #(5x6 (,7x8 -19x:  A;x< 
=xt !  
	 $6*6 
6 .	# 4	1A1A 1A 	1A
 1A 
1Ar ! 
^
^ 	
^
 
^ 
^ 
^ 

^
 !%  

 	

 
 
 
 

,A@
D
3:0dE*<vv v 	v v v v v ",v 
vt 37b #	b/b 	b
 
bT   2	  
%L /33
3
 3
 	3
 ",3
 
 3
v /3,
,
 ,
 	,
 ",,
 
 ,
j /3E
E
 E
 	E

 E
 ",E
 
 E
Z /39
9
 9
 	9
 ",9
 
 9
B /3;
;
 ;
 	;
 ",;
 
 ;
z

.`8r+   r  c                   	 ddl m} t        |       j	                         }|j                  |      }d}d}t        |j                        D ]  }|j                  |dg      j                  d      }t        j                  |j                         d      j                  j                         }	|	j                         }	|	j                   r|	j#                         |	j%                         }}
||
nt#        ||
      }||nt%        ||      } ||t'        d|       t        j(                  |      t        j(                  |      fS # t        $ r}t        d      |d}~ww xY w)	zHMin/max ``date`` in the options Parquet (row-group scan; needs pyarrow).r   Nz(pyarrow required for Parquet date boundsrp  r   r   r   zNo dates column data in )pyarrow.parquetparquetr  r   
expanduserParquetFilerangenum_row_groupsread_row_groupcolumnrJ   r   	to_pandasdtrL   r  r   r2   r1   r   rK   )parquet_pathpqr  pathpfdmindmaxir  slohis               r,   parquet_file_date_boundsr  [  sB   M$ ((*D		B $D $D2$$% 5F84;;A>NN3==?8<??IIKHHJ77!%%'B\rs4}\rs4}5 |t|3D6:;;<<r||D111#  MDE1LMs   E	 		E#EE#c                    t        |      t        |      }}g }| j                         D ]2  }t        |      }||cxk  r|k  sn ||v s"|j                  |       4 t        |      S )zQChain dates from the loader that fall in [start, end] and exist on ``spy_index``.)rN   ro  rz   r  )	r  	spy_indexr  r  start_nend_nr   r  dns	            r,   trading_days_intersecting_spyr  s  sf     u%y~UG C$$& q\b!E!bIoJJrN #;r+   c                    t        |      }|}d}| D ]+  }||z  }t        ||      }|dkD  st        |||z
  |z        }- t        |      S )zOMax drawdown on equity = initial + cumulative PnL (chronological trade stream).r/   r   )r&   r1   )pnlsinitialeqrI  mddps         r,   !standalone_max_drawdown_from_pnlsr    s`    	wBD
C /
a4}!8cD2I-.C	/
 :r+   c                V   d}| D cg c]  }t        |j                         }}t        d |D              }i }|D ]  }| D cg c]  }|j                  |k(  s| }}|D cg c]  }t        |j                         }	}t	        |	      }
|
dk(  rdddddddddddt        d      d||<   qt        d |	D              }t        d	 |	D              }t        d
 |	D              }t        |d       }|D cg c]  }t        |j                         }}t        ||      }|dk  r|t        |      z  n
t        d      }|dk  r||z  nd}|
||
z  t        t        j                  |	            t        t        j                  |	            t        t        |	            |
dkD  rt        t        j                  |	            ndt        t        |	            t        t        |	            t        |      t        |      t        |      t        |      d||<    |S c c}w c c}w c c}w c c}w )z
    Research metrics per regime: median, total PnL, standalone max DD on regime-only PnL stream,
    downside share of portfolio losses, profit factor.
    r   c              3  ,   K   | ]  }|d k  s	|  ywr   Nr*   r^   r  s     r,   ra   z*extended_regime_metrics.<locals>.<genexpr>  s     21AEQ2   
r   r/   Nr  )r  r  r  
median_pnl	total_pnlstd_pnlworstbestsum_negative_pnldownside_share_of_lossesstandalone_max_ddprofit_factorc              3  ,   K   | ]  }|d kD  s	d  ywr  r*   r  s     r,   ra   z*extended_regime_metrics.<locals>.<genexpr>  s     ,a!e1,r  c              3  ,   K   | ]  }|d k  s	|  ywr  r*   r  s     r,   ra   z*extended_regime_metrics.<locals>.<genexpr>       /AQa/r  c              3  ,   K   | ]  }|d kD  s	|  ywr  r*   r  s     r,   ra   z*extended_regime_metrics.<locals>.<genexpr>  r   r  c                    | j                   S r)   )r  )r  s    r,   rh   z)extended_regime_metrics.<locals>.<lambda>  s
    !++ r+   ri   infrY   )r&   r  r  r  r0  r  r  rm   
statisticsr   r   pstdevr2   r1   )r  r  regimesr  all_pnls
total_lossr   r  r  r
  r  r  neg_sumpos_sumchron
pnls_chronstandalone_ddr  downside_shares                      r,   extended_regime_metricsr/    s   
 #^G*01Qaii 1H1222JC )
 2QAHHMq22*-.Qaii ..I6! $',/%(!&uCF ,d,,//////s 560561eAII&6
69*oV'.{Ws7|#e3=>'J.sqZ__T23
 1 1$ 78s4y)9:QuZ..t45C3t9%#d)$ %g(-n(=!&}!5"2Y
A9)
T J[ 2 3., 7s   HHH"H!4H&c            
     X    ddt          t        t        t        dt        t
        t        d	S )zPStatic R1 (weekly long strangle) parameters for sanity checks vs the design doc.z%R1 weekly long strangle (same expiry)zVIX < z-buy call + buy put (put strike < call strike))	regime_namevix_band_strictr   rp   rr   r(  r  take_profit_frac_of_debitstop_loss_frac_of_debit)r  r  r  r  r  r  r  r*   r+   r,   r1_structure_digestr5    s0     ?#J<0%*(?+%/#-
 
r+   zvrp_fund_pitch.htmlzC(    // VRP_LEDGER_DATA_BEGIN\n)(.*?)(\n    // VRP_LEDGER_DATA_END)c                D    | dk\  rt        | |z
        S t        || z
        S )zLImplied closing mark (debit to close for credit; value recovered for debit).r/   )r&   )initial_netpnls     r,   _exit_mark_usdr9    s*    c[3&''{"##r+   c                   | dk(  r#dt          dt        ddt        t              ddS | dk(  rKdt        d	   d	    d
t        t        d	   d         ddt        d   d	    d
t        t        d   d         dd	S | dk(  r>dt
        d	   d	    d
t        t
        d	   d         ddt        t
        d   d         ddS | dk(  ry| dk(  ryy)Nr   u   Weekly long strangle · ~zD buy ~r  u   Δ C / buy ~u	   Δ P (R1)r   zR2a: short r   zD -rY   u   Δ P / long u   Δ P · put diagonalr   zR2b: short u   Δ P / long same-exp -u   Δ P · put credit spreadr   u=   Short 45D -15Δ P / Long 45D -7Δ P · put credit spread (R3)r   u8   Short 45D -15Δ P / Long 45D -5Δ P · put credit spreadzUnknown structure)r  r  rm   r  r;   r@   r  s    r,   _structure_label_for_regimer<    s.   ''8c?R S%c*)5	
 +A.q12#c:KA:Nq:Q6RSV5W X%a(+,C4Ea4H4K0LS/QQeg	
 -a034C<OPQ<RST<U8VWZ7[ \!"5a"8";<SAAZ\	
 N Ir+   c                @    | dk(  ry| dk(  ry| dk(  ry| dk(  ry| d	k(  ry
y)Nr   )rY   u   R1 — Weekly strangler   )r   u   R2a — Put diagonalr   )rH   u   R2b — Put spreadr   )   u   R3 — Put spreadr   )rP   u   R4 — Wide PCS)r   Unknownr*   r;  s    r,   _regime_code_and_labelr@    s<    *(&% #r+   c                    t        | j                        \  }}t        | j                        }t        | j                        }t        | j
                        }|dkD  r||z  dz  nd}t        | j                        j                  d      t        | j                        j                  d      ||t        | j                        t        |d      t        t        ||      d      t        |d      t        |d      t        | j                        d
S )z@Shape matches `tradeHistoryData` objects in vrp_fund_pitch.html.r  r%   r/   rf  r   )
	entryDateexitDater  regimeLabel	structure	entryMarkexitMarknetPnlreturnOnRiskdaysHeld)r@  r  r&   r,  r  r-  rN   r'  rn  r  r<  rR   r9  rv   r  )tradecoderlabelentryr8  mrgrors          r,   closed_trade_to_dashboard_rowrQ  	  s    )%,,7LD&%++,E

C
  
!C!$t39
Cu//099*Eeoo.77
C0>5!_.4a8Q-c1++, r+   c                <   t        |      }| D cg c]  }t        |       }}|j                  d        t        j                  |dd      }d| d}|j                  d	      }t        j                  |      }|st        d
| d      |d|j                          |j                  d      z   |z   |j                  d      z   ||j                         d z   }	|j                  |	d	       |j                  d	      }
d|
vsd|
vrt        d| d      |S c c}w )z
    Serialize ``trade_log`` into JSON and inject it into ``vrp_fund_pitch.html`` between
    ``VRP_LEDGER_DATA_BEGIN`` / ``VRP_LEDGER_DATA_END``, replacing the placeholder
    ``const tradeHistoryData = [];``.
    c                    | d   S )NrB  r*   )r  s    r,   rh   z'export_trades_to_html.<locals>.<lambda>+	  s
    AkN r+   ri   Fri  )ensure_asciirl  z    const tradeHistoryData = z;
zutf-8)encodingz4Could not find VRP_LEDGER_DATA_BEGIN/END markers in z4. Regenerate vrp_fund_pitch.html or add the markers.NrY   r>  z</html>zfunction formatUsdzexport_trades_to_html wrote z but the file no longer contains the pitch deck tail (</html> / formatUsd). Restore vrp_fund_pitch.html from git or vrp_fund_pitch_script_tail.inc.)r   rQ  r{   ro  rp  	read_text_LEDGER_BLOCKsearchr   r  groupr  
write_textRuntimeError)r  html_file_pathr  r  rr  js_arrayinnertextr   new_textverifys              r,   export_trades_to_htmlrb   	  s9    D6?@)!,@D@II*I+zz$UzJH+H:S9E>>7>+DT"AB4& IA A
 	

 Kaggi 1771:-5
BT!%%')_THOOHwO/^^W^-F"6f"D*4& 1. .
 	

 K- As   D__main___btr  _sleeve_cfgr  _overlay_fracs_overlay_cap
_total_capz  Parity config: z (sleeves + strategy_params)i  )rw   rf  z[No intersecting Parquet/yfinance dates; falling back to every chain date present in spy_df.rd  z  IVolatilityLoader Parquet: z  Continuous window: u    → z (z chain days with SPY/VIX))r  r   r  r  r  r  )rs  zNo Parquet at uw    — run combine_csvs_to_parquet on iVolatility CSVs. Falling back to SyntheticLoader over the default yfinance window.z  Trade ledger written: z rows))rO  r&   )r3   r&   rO  r  )rM   zpd.Timestamp | objectrO  r&  )rV   r   rO  ContractKey)
r~   r   r   rv   rp   r&   rr   r&   rO  z,tuple[OptionContract, OptionContract] | None)r_   r   rO  rQ  )r   r   rO  rQ  )
r   r&   r   r&   r   	LegActionr   r&   rO  r&   )ro   r   r   rj  r   r&   rO  r   )r   r   r   rj  rO  r&   )ro   r   rO  r   )rS   r&   r   r&   rO  r&   )rV   r   r   r&   rO  r&   )r   pd.DataFramerO  rk  )r   rk  r   rT   rO  z	pd.Series)r  rT   r  rT   rO  rk  )rq  r  rO  rT   )r~   r   rx  r   ry  r&   rO  zOptionContract | None)r~   r   r   rv   r  r&   rO  rM  )r  r  r  r  rO  r&   )r  
str | PathrO  z!tuple[pd.Timestamp, pd.Timestamp])
r  z<Union[IVolatilityLoader, ThetaChunksLoader, SyntheticLoader]r  zpd.Indexr  r&  r  r&  rO  zlist[pd.Timestamp])r
  zlist[float]r  r&   rO  r&   )r  r  r  r&   rO  r  r  )r7  r&   r8  r&   rO  r&   )r  r  rO  rT   )r  r  rO  ztuple[int, str])rK  r  rO  r  )r  r  r\  rl  rO  r   )rV  
__future__r   ro  resyspathlibr   __file__resolveparents
_REPO_ROOTrT   r  insertr|   r$  rm  dataclassesr   typingr   r   r	   r
   pandasrJ   r   RenTech.core.options_backtestr    RenTech.core.options_data_loaderr   r   r   "RenTech.core.synthetic_data_loaderr   r    RenTech.core.theta_chunks_loaderr   *RenTech.strategy_stack.vrp_strategy_configr   DEFAULT_SLEEVE_RISK_JSON_PATHr   r   rj  r  r  	frozensetr$   rW  r2  r  r'   r-   r4   r  r  r  r  r  r  r;   R2_RISK_PER_CONTRACTr  r  r  r  r  rw  r@   r  r  R2_PUT_SPREAD_RISK_FRACTIONr   r   r   r<  rC   r  r  rE   r  r  r  r  r  r  r  r  RISK_FRACTION_PER_TRADErG   r  r  r  r  r   r/  r0  r   r1  r2  r3  DEFAULT_IV_PARQUET_PATHr  rK   r&   ri  rN   rW   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r!  r#  r%  rZ  rs  r|  r  r  r  r  r  r  r  r  r  r  r  r/  r5  DEFAULT_PITCH_HTML_PATHcompileDOTALLrW  r9  r<  r@  rQ  rb  rS  _pqre  rf  rg  rh  is_file_cfgstrategy_paramsr  r  r  r  r  r  _ld_iv_d0_d1	Timedeltarn  	_yf_start_yf_end	_spy_wider   _daysr0  rn  ru   _in_spyr  ro  rp  rd  r  
_spy_panel_ldr  r  _out)r  s   0r,   <module>r     s`  'R #  	 
 (^##%--a0
z?#(("HHOOAs:'    ! , ,   @ [ [ T >  M"		
  *3
*  
  ) % )5  

 9 5         # ; 7    "    # 
 ; 7  
  ; 7  
  


   $   $',$,6 2       
  !       %'88;]] BLL%,-(Z??? ? 	?
 2?D()0	0	0 0 	0
 0l -  	0
><==<661,@0 "&&	& &R )9^ 2 _ _ _D 
2 
 
<30JJJ J 	J*  	< D< D< D<N     
 
 
 @ D DN(20H  
	
  
3l" %'<< 

JII$.. "9     
 F z
!C	.2K+2.2N+2!%L,%#J#,$,,.()FG6t7K7KL4.T5O5O0PQ>B>Y>Yd99:_c11--
!"?!@@\]^
{{}"3'+C0S<2<<S11;;JG	1--77
C.y'B	-fioosCPu:?HMMF
 )//*G'-'>'>'@\!IaLT[D[1\E 	-cU34#CHHJ<uSXXZL AE
|46	
 4"-#1". *
 	e,SE "P P	

 00@.Q
j)4"-#1". *
 	 0GHD	$TF"S-?,@
GHG < ]s   V
V