
    i3              
         d Z ddlmZ ddlmZm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Zd1dZddd2d	Zd
dd	 	 	 	 	 	 	 d3dZdd	 	 	 	 	 	 	 d4dZdddd	 	 	 	 	 	 	 	 	 	 	 d5dZdd	 	 	 	 	 	 	 d6dZdddd	 	 	 	 	 	 	 	 	 d7dZe G d d             Ze G d d             Zedk(  re	j<                  j?                  d      Z  ejB                  d d!d"#      Z"d$ e	jF                  e jI                  dd% e%e"      &            z   Z&d'e&z  d(z    e	jF                  e jI                  dd) e%e"      &            z   Z' ejP                  de'ie"*      Z) ejP                  de&ie"*      Z* ed+d,dd+-      Z+e+jY                  e)e*      Z- e.d.e+j^                          e.e-g d/   ja                         jc                  d0             yy# e$ rZdZdZeZY dZ[dZ[ww xY w)8u  
Mean-reversion engines:

* **Single-asset** — rolling z-score on one ``close`` series (:class:`StatArbEngine`).
* **Pairs / stat-arb** — Engle–Granger-style hedge ratio (OLS), ADF on the residual
  spread, then rolling z-score on that spread (:class:`PairStatArbEngine`).

Dependencies: pandas, numpy; pairs mode also requires **statsmodels**.

:class:`PairStatArbEngine` fits the hedge **only on past data** (expanding or
rolling window), forms a **causal** spread at each bar, runs ADF on that spread,
and sets **basket PnL** to ``log_ret_y - β_{t-1}·log_ret_x`` (or simple returns
when not using logs). A full-sample OLS is still stored on the instance for
diagnostics (``ols_result``) but does not drive signals.
    )annotations)	dataclassfield)AnyN)adfullerc                 @    t         t        t        dt               y )Nznstatsmodels is required for pair / ADF logic. Install with: python -m pip install statsmodels
Original error: )smr   ImportError	_SM_ERROR     A/opt/rentech/trading_bot/RenTech/strategy_stack/statarb_engine.py_require_statsmodelsr   $   s-    	zX%(k+
 	
 &r   )min_periodsc               >   ||nt        d|dz        }| j                  t        j                        }|j	                  ||      j                         }|j	                  ||      j                  d      }||z
  |j                  dt        j                        z  }|S )z|
    Z-score of the current value vs trailing window mean and std dev.

    Uses sample std (ddof=1) inside the window.
       )windowr      )ddof        )	maxastypenpfloat64rollingmeanstdreplacenan)seriesr   r   mpxmusigzs           r   rolling_zscorer&   -   s     $/SFaK5HBbjj!A	
&b	1	6	6	8B
))6r)
2
6
6A
6
>C	
R3;;sBFF++AHr          @r   entry_zexit_zc               t   t        j                  t        |       t         j                        }t        j                  d      }| j                  }| j                         }t        t        |            D ]  }||   }t        j                  |      s|||<   #|dk(  r7|| k  rt        j                  d      }nU||k\  rPt        j                  d      }n:|dk(  r||k\  r0t        j                  d      }n||k  rt        j                  d      }|||<    t        j                  ||t         j                        S )z
    Bar-by-bar position state machine on z-scores.

    Flat -> long (+1) when z <= -entry_z; flat -> short (-1) when z >= entry_z.
    Long exits when z >= exit_z; short exits when z <= exit_z.
    )dtyper   r   )indexr,   )
r   zeroslenint8r.   to_numpyrangeisfinitepdSeries)	r%   r)   r*   poscurrentidxvalsizis	            r   run_mean_reversion_positionsr=   ;   s     ((3q6
)CggajG
''C::<D3t9 !W{{2CFa<gX~''!*w''"+\V|''!*V|''!*A%( 99S27733r   close	price_colc                   | |g   j                  |di      }||g   j                  |di      }|j                  |d      }|j                  rt        d      |S )zDInner-join two OHLCV frames on timestamp; ``close_y`` / ``close_x``.close_y)columnsclose_xinner)howz:No overlapping timestamps between the two intraday series.)renamejoinempty
ValueError)ohlcv_yohlcv_xr@   yr"   mergeds         r   merge_intraday_pairrO   c   sl     	##Y	,B#CA##Y	,B#CAVVA7V#F||UVVMr   T   use_loghedge_window	min_trainc               4   t                ||dk  rd}|t        t        |      t        |            }t        j                  | |d      j                         }t        |      |dz   k  rt        dt        |             |rt        j                  |d         n!|d   j                  t        j                        }|rt        j                  |d         n!|d   j                  t        j                        }|j                         }|j                         }	t        |      }
t        j                  |
t        j                        }t        j                  |
t        j                        }t        j                  |
t        j                        }t        j                  |
t        j                        }t        |
      D ]  }|t!        d|t        |      z
        }nd}|}||z
  |k  r+||| }|	|| }t        j"                  t        j$                  ||z
        |f      }	 t        j&                  j)                  ||d      \  }}}}t-        |d         }t-        |d         }t-        ||         t-        |	|         }}|||<   |||<   |||z  z   ||<   |||   z
  ||<    |j.                  }t        j0                  ||	      t        j0                  ||	      t        j0                  ||	      t        j0                  ||	      fS # t*        $ r Y Vw xY w)
u  
    Causal OLS hedge: at bar *i*, fit ``Y ~ 1 + X`` using only **prior** rows
    ``[start, i-1]`` (expanding if ``hedge_window`` is ``None``, else rolling of
    length ``hedge_window``). Spread at *i* is the one-step-ahead residual
    ``y_i - (α + β x_i)`` with ``(α, β)`` from that fit.

    Returns
    -------
    spread, fitted, alpha, beta
        All ``pd.Series`` aligned to the common index (NaN until ``min_train``
        bars of history exist in the training slice).
    Nr   rB   rD   r   z0Need at least min_train+1 overlapping bars; got rB   rD   )rcondr.   )r   minintr5   	DataFramedropnar0   rJ   r   logr   r   r2   fullr   r3   r   column_stackoneslinalglstsq	Exceptionfloatr.   r6   )rB   rD   rR   rS   rT   dfy_rawx_rawyvxvnspreadfittedalpha_abeta_ar;   startendy_trainx_trainX_traincoef_abyixir9   s                               r   causal_ols_spreadrz   r   s   ( LA$5IL(9:		'g>	?	F	F	HB
2wQKCPRG9UVV%,BFF2i=!"Y-2F2Frzz2RE%,BFF2i=!"Y-2F2Frzz2RE		B		BBAWWQFWWQFgga GWWQF1X ##1s<001EE;"U3-U3-//2773;#7"AB	IIOOGWDOIMD!Q $q'N$q'Nr!uuRU|B
q	BJq	Nq	+#. ((C
		&$
		&$
		'%
		&$	   		s   #'L

	LLrR   c               P   t                t        j                  | |d      j                         }t	        |      dk  rt        dt	        |             |rt        j                  |d         n!|d   j                  t        j                        }|rt        j                  |d         n!|d   j                  t        j                        }t        j                  |      }t        j                  ||      j                         }t        j                  |j                  |j                         }t        j                  |j"                  |j                         }	t%        |j&                  j(                  d         }
t%        |j&                  j(                  d         }|	||
||fS )	u  
    First-stage Engle–Granger regression: regress Y on X (with constant).

    With logs: log(Y) = alpha + beta log(X) + epsilon; spread = epsilon (OLS residual).

    Returns
    -------
    spread
        Cointegrating residual series (aligned to common index).
    fitted
        In-sample fitted values (same index).
    alpha, beta
        Intercept and hedge coefficient on X.
    ols_result
        statsmodels ``OLS`` results object.
    rV   rP   z/Need more overlapping bars for regression; got rB   rD   rX   r   r   )r   r5   r[   r\   r0   rJ   r   r]   r   r   r	   add_constantOLSfitr6   fittedvaluesr.   residrd   paramsiloc)rB   rD   rR   re   rM   r"   Xolsrl   rk   alphabetas               r   engle_granger_spreadr      s1   , 	'g>	?	F	F	HB
2w|J3r7)TUU!(r)}bm.B.B2::.NA!(r)}bm.B.B2::.NA
A
&&A,


CYYs''rxx8FYYsyy1F#**//!$%E#$D65$++r   AICc)autolag
regressionmaxlagc                  t                | j                         j                  t        j                        }t        |      dk  rdt        t        |            dS d|i}|||d<   |||d<   t        |fi |\  }}}}	}
}|
t        |
      ni }
t        |      t        |      t        |      t        |	      |
t        ||
j                  dt        d            k        |d	S )
u   
    Augmented Dickey–Fuller test on the spread (stationarity of residual).

    ``regression`` is passed to ``adfuller`` (default ``'c'`` = constant only).
    rP   too_few_obs)errorrj   r   r   r   z5%r   )adf_statisticpvalueused_lagn_obscritical_valuesis_stationary_5pctic_best)r   r\   r   r   r   r0   rZ   r   dictrd   boolget)rk   r   r   r   skwstatr   usedlagnobscriticbests               r   adf_on_spreadr      s     rzz*A
1v{&SQ[99&
3B98080Ab0A-D&'4v)4:rDt-LT"4$((4u*F#FG r   c                  R    e Zd ZU dZdZded<   dZded<   dZded	<   d
Zded<   ddZ	y)StatArbEnginezC
    Single-asset rolling z-score mean reversion on ``close``.
       rZ   r   r'   rd   r)   r   r*   r>   strr@   c                J   | j                   |j                  vr-t        | j                    dt        |j                               |j	                         }t        || j                      | j                        }||d<   t        || j                  | j                        |d<   |S )Nz not in columns: zscorer(   micro_position)
r@   rC   KeyErrorlistcopyr&   r   r=   r)   r*   )selfohlcvoutr%   s       r   	transformzStatArbEngine.transform  s    >>.dnn-->tEMM?R>STUUjjl3t~~.<H <t||DKK!
 
r   N)r   pd.DataFramereturnr   )
__name__
__module____qualname____doc__r   __annotations__r)   r*   r@   r   r   r   r   r   r     s6     FCGUFEIs	r   r   c                      e Zd ZU dZdZded<   dZded<   dZded	<   d
Zded<   dZ	ded<   dZ
ded<   dZded<   dZded<   dZded<    eedd      Zded<    eddd      Zded <   d
d!	 	 	 	 	 	 	 d#d"Zy)$PairStatArbEngineu  
    Two-leg stat-arb: **causal** OLS hedge (expanding or rolling), ADF on that
    spread, rolling z-score, and **basket** bar returns for PnL.

    The **dependent** asset (Y) should match the name you use for downstream PnL
    (e.g. ``trade_ticker``); **X** is the hedge leg (e.g. sector ETF or basket).

    Columns added to the returned frame (aligned to Y after inner join):
      ``close_x``, ``pair_spread``, ``spread_fitted``, ``hedge_alpha``, ``hedge_beta``
      (time-varying), ``zscore``, ``micro_position``, ``spread_ret`` (``diff`` of
      causal spread), component returns, and ``basket_ret`` — the hedgeable
      return ``r_y - β_{t-1} r_x`` (log or simple, matching ``use_log_prices``).
    r   rZ   r   r'   rd   r)   r   r*   Tr   use_log_pricesN
int | NonerS   rP   min_hedge_obsr   
str | Noneadf_autolagr   r   adf_regressionr>   r@   F)default_factoryinitreprdict[str, Any]
adf_report)defaultr   r   r   
ols_result)attach_y_ohlcvc               |   | j                   }||j                  vs||j                  vrt        | d      t        |||      }t	        |d   |d   | j
                        ^ }}|| _        | j                  }||dk  rd}t        |d   |d   | j
                  || j                        \  }	}
}}t        |	| j                  | j                  	      | _        t        |	| j                        }t!        || j"                  | j$                  
      }|r*|j'                  |	j(                        j+                         }n t-        j.                  |	j(                        }|d   j'                  |j(                        }|d   j'                  |j(                        }|j'                  |j(                        }||d<   |	j'                  |j(                        |d<   |
j'                  |j(                        |d<   |j'                  |j(                        |d<   ||d<   |j'                  |j(                        |d<   |j'                  |j(                        j1                  d      j3                  t4        j6                        |d<   |d   j9                         |d<   | j
                  rUt5        j:                  ||j=                  d      z        |d<   t5        j:                  ||j=                  d      z        |d<   n&|j?                         |d<   |j?                         |d<   |d   |j=                  d      |d   z  z
  |d<   |S )u  
        Causal spread from past-only OLS; ADF on that spread; z-score; MR positions.

        ``ols_result`` is set to **full-sample** Engle–Granger OLS for diagnostics
        only. Signals and ``adf_report`` use the **causal** spread series.

        If ``attach_y_ohlcv``, start from ``ohlcv_y`` rows that align with the merge;
        otherwise return only merge columns + signals.
        z required on both framesr?   rB   rD   r{   Nr   rQ   )r   r   r(   rX   pair_spreadspread_fittedhedge_alpha
hedge_betar   r   
spread_retr   ret_yret_x
basket_ret) r@   rC   r   rO   r   r   r   rS   rz   r   r   r   r   r   r&   r   r=   r)   r*   reindexr.   r   r5   r[   fillnar   r   r1   diffr]   shift
pct_change)r   rK   rL   r   pyrN   ru   ols_fshwrk   rl   alpha_sbeta_sr%   r7   r   cycxhbs                      r   r   zPairStatArbEngine.transform7  s     ^^W__$'//(AbT!9:;;$WgD)99''

F
 !>bAgB*;99''((+
' (8H8HUYUhUhi64;;/*1dll4;;W//&,,/446C,,V\\2CI&&syy1I&&syy1^^CII&I#^^CII6M%~~cii8O$__SYY7ML		#)),H #CII 6 = =a @ G G P.335L66"rxx{"23CL66"rxx{"23CL==?CL==?CLL288A;W+EEL
r   )rK   r   rL   r   r   r   r   r   )r   r   r   r   r   r   r)   r*   r   rS   r   r   r   r@   r   r   r   r   r   r   r   r   r   r     s     FCGUFEND#L*#M3#K#NCIs!&t%e!TJTDu5AJA  $EE E
 E 
Er   r   __main__z
2024-01-01i  h)periodsfreqd   g?)sizeg      ?
   g?rX   (   g      ?)r   r)   r*   r   zADF (causal spread):)r>   rD   r   r   r   r      )r   None)r    	pd.Seriesr   rZ   r   r   r   r   )r%   r   r)   rd   r*   rd   r   r   )rK   r   rL   r   r@   r   r   r   )rB   r   rD   r   rR   r   rS   r   rT   rZ   r   z1tuple[pd.Series, pd.Series, pd.Series, pd.Series])rB   r   rD   r   rR   r   r   z.tuple[pd.Series, pd.Series, float, float, Any])
rk   r   r   r   r   r   r   r   r   r   )2r   
__future__r   dataclassesr   r   typingr   numpyr   pandasr5   statsmodels.apiapir	   statsmodels.tsa.stattoolsr   r   r
   er   r&   r=   rO   rz   r   r   r   r   r   randomdefault_rngrng
date_ranger9   cumsumnormalr0   r"   rM   r[   dfydfxengr   r   printr   r\   headr   r   r   <module>r      s    # (    2 I
 QU " 	%4%4 %4 	%4
 %4X 	 	
 & #DDD 	D
 D D 7DV 	#,#,#, 	#,
 4#,R  !! ! 	!
 ! !H   , a a aH z
))


"C
"--c
<Cibii

1cC
9::Aa"yryyAt#c(!CDDA
",,|3
/C
",,|3
/C
2s3b
QC
--S
!C	
 #..1	#[
\
c
c
e
j
jkl
mn G  	BHIs   G GGG