
    i%              	      ,   d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	m
Z
 ddlZddlmZmZmZ  G d d	ee      Zdd
Z	 	 	 	 	 	 	 	 	 	 ddZddZddZe G d d             Zdddddej0                  ddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddZy)u  
Lightweight **research** backtests for options trade ideas using EOD chain data
from :mod:`RenTech.core.options_data_loader`.

This is not execution — it marks P&amp;L from historical bid/ask/mid on a schedule
you choose (e.g. roll every *N* trading days into a target-DTE / target-delta leg).

Limitations (explicit)
----------------------
* One leg at a time; no spreads, no commissions, no borrow, no assignment.
* Marks use that day’s chain only; if the exact strike/expiry row is missing on exit
  day, the trade is skipped with a warning (no interpolation).
* Delta targets are as reported in the vendor file (often Black–Scholes); not live IV.
    )annotationsN)	dataclass)Enum)Iterable)IVolatilityLoaderOptionChainOptionContractc                      e Zd ZdZdZdZy)ExecutionAssumptionzDHow to translate bid/ask/mid into fill prices for a **long** option.mid	realisticN)__name__
__module____qualname____doc__MID	REALISTIC     9/opt/rentech/trading_bot/RenTech/core/options_backtest.pyr   r      s    N
CIr   r   c                H    t        j                  |       j                         S N)pd	Timestamp	normalize)xs    r   _norm_tsr   $   s    <<?$$&&r   c                   t        |      }|}|j                         j                         dd }|dk(  s|j                         j                  d      rd}n&|dk(  s|j                         j                  d      rd}| j                  D ]d  }|j
                  |k7  rt        |j                        |k7  r,t        j                  t        |j                        t        |      dd	      sb|c S  y)
z4Locate the same series by (expiration, strike, C/P).N   CCALLPPUTr   g-C6?)rel_tolabs_tol)r   stripupper
startswith	contractsoption_type
expirationmathisclosefloatstrike)chainr+   r/   r*   exp_nwantotcs           r   find_contract_in_chainr5   (   s     Z ED					"	"	$Ra	(B	SyK%%'226:	sk'')44U;__ ==BALL!U*<<ahhtaNH r   c                |    |t         j                  k(  rt        | j                        S t        | j                        S r   )r   r   r.   r   askr4   hows     r   _long_entry_pricer:   @   -    
!%%%QUU|<r   c                |    |t         j                  k(  rt        | j                        S t        | j                        S r   )r   r   r.   r   bidr8   s     r   _long_exit_pricer>   F   r;   r   c                  |    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ed<   ded<   y)TradeRecordpd.Timestamp
entry_date	exit_dater+   r.   r/   strr*   entry_pxexit_pxpnl_dollarspnl_pctentry_deltaentry_ivN)r   r   r   __annotations__r   r   r   r@   r@   L   s?    MONNOr   r@         gr"   )trading_datesrebalance_every
target_dtetarget_deltar*   	executionstartendc               	   |dk  rt        d      |t        | j                               }	n"t        |D 
ch c]  }
t	        |
       c}
      }	| t	        |      }|	D 
cg c]
  }
|
|k\  s	|
 }	}
| t	        |      }|	D 
cg c]
  }
|
|k  s	|
 }	}
t        |	      dk  r.t        j                         t        j                  t              fS d}g }t        dt        |	      |      D ]  }|	|   }
| j                  |
      }|j                  s'|"t        ||d   |d   |d	         }t	        |d         }d}|t        ||      }n|
|kD  r=d
}t        j                   d|
j#                          d|j#                          dd       nHt        j                   d|
j#                          d|d	    d|d    d|j#                          d	d       d}|`|^|d   }||z
  dz  }|r||z
  |z  dz  n
t        d      }|j%                  t'        |d   |
|d   |d   |d	   |||||d   |d                d}	 |j)                  |||      }t+        ||      }t-        j.                  |      r|dk  r|
|j0                  t        |j2                        |j4                  |t        |j6                        t        |j8                        d} ||	r|	d   }t	        |      t	        |d         k7  rt	        |d         }| j                  |      }t        ||d   |d   |d	         }d}|t        ||      }nt	        |      |kD  rd
}|\|d   }||z
  dz  }|r||z
  |z  dz  n
t        d      }|j%                  t'        |d   ||d   |d   |d	   |||||d   |d                |s.t        j                         t        j                  t              fS t        j                  |D cg c]  }|j:                   c}      }t        j<                  |d         |d<   t        j<                  |d         |d<   t        j<                  |d         |d<   |j?                  d      }|d   jA                         }t        j                  |jB                  |d   d       }||fS c c}
w c c}
w c c}
w # t         $ r Y w xY wc c}w )!a  
    Open a single long leg on every *rebalance_every*-th trading day; close the prior
    leg on the same day (same chain) before opening the new one.

    P&amp;L per round-trip (one contract): ``(exit_px - entry_px) * 100``.

    Parameters
    ----------
    trading_dates
        If ``None``, uses all dates in the Parquet file (sorted), optionally clipped
        by ``start`` / ``end``.
    rebalance_every
        1 = trade every listed trading day; 5 = every 5th chain date in the list.
    r   zrebalance_every must be >= 1N   )dtyper   r+   r/   r*           zExit z is after expiry uC   ; marking long leg at $0 (OTM expiry assumption — not intrinsic).)
stacklevelzNo matching contract on exit z for  u    — skipping round-trip.rE   g      Y@nanrB   rI   rJ   )rB   rC   r+   r/   r*   rE   rF   rG   rH   rI   rJ   )rP   rQ   r*   )rB   r+   r/   r*   rE   rI   rJ   rC   rG   cum_pnl_usd)indexname)"
ValueErrorlistiter_chain_datessortedr   lenr   	DataFrameSeriesr.   rangeget_chain_for_dater)   r5   r>   warningswarndateappendr@   find_target_legr:   r,   isfiniter+   r/   r*   deltaiv__dict__to_datetimesort_valuescumsumvalues)loaderrN   rO   rP   rQ   r*   rR   rS   rT   datesdsependingrecordsir0   exr1   rF   rE   pnlrH   leglast_drdfcumequitys                                r   run_rebalance_long_backtestr   [   s1   4 788V,,./];;<UO!,qQ!V,,
SM!,qQ!V,,
5zA~||~ryyu555G!#G1c%j/2 K
!H))!,'%!&	B W\23E$(G~*2y9UAFFH:%6uzz|n EX X  3AFFH:U}-.a0A/B!EJJL>Qjl 
 "w':":.)U2EM7X-9EASXY^S_#*<#8"##*<#8&x0$+M$:!) '$' '$+M$:!(!4 	''%)' ( C %S)4}}X&(a- ..CJJ'??  +cff
GK
\ urFx(=>>W\23E--f5E'%!&	B %)G~*2y9&!E)"":.)U2EM7X-9EASXY^S_#*<#8"(#*<#8&x0$+M$:!) '$' '$+M$:!(!4  ||~ryyu555	73aqzz3	4B~~b&67B|nnR_5B{O~~b&67B| 
	$B
]

"
"
$CYYszzK}MFv:] < - -H  		r 4s5   R 
R$+R$
R)R)6R.'R>.	R;:R;c                8   | j                   rddiS | d   }t        t        |             t        |j	                               t        |j                               t        |      rt        |dkD  j                               ndt        |j                               dS )z%Basic stats on completed round-trips.n_tradesr   rG   rX   )r   total_pnl_usdmean_pnl_usdwin_ratemedian_pnl_usd)emptyintrd   r.   summeanmedian)tradesps     r   summarize_tradesr     su    ||A}AF$quuwaffh-0VE1q5,,.)
+ r   )r   zpd.Timestamp | objectreturnrA   )
r0   r   r+   rA   r/   r.   r*   rD   r   zOptionContract | None)r4   r	   r9   r   r   r.   )rv   r   rN   zIterable[pd.Timestamp] | NonerO   r   rP   r   rQ   r.   r*   rD   rR   r   rS   pd.Timestamp | NonerT   r   r   ztuple[pd.DataFrame, pd.Series])r   zpd.DataFramer   zdict[str, float | int])r   
__future__r   r,   ri   dataclassesr   enumr   typingr   pandasr    RenTech.core.options_data_loaderr   r   r	   rD   r   r   r5   r:   r>   r@   r   r   r   r   r   r   <module>r      s(   #   !    [ [#t '  	
 0   " 48%8%<%<!%#nn 1n 	n
 n n n #n n 
n $nbr   