
    pj:=                    ^   d Z ddlmZ ddlZddlZddlZddlmZ ddlZ	ddl
Z ee      j                         j                  d   Z ee      ej"                  vr"ej"                  j%                  d ee             ddlmZ edz  dz  d	z  Zed
z  Z	 ddlZd,dZd-dZd.dZd/dZd0dZd1dZd1dZ d1dZ!d2dZ"d1dZ#d1dZ$d3dZ%d1dZ&d1dZ'	 	 	 	 	 	 	 	 	 	 d4dZ(d1dZ)d1dZ*d5dZ+d6dZ,	 	 	 	 	 	 	 	 d7dZ-d d  d! d" d# d$ d% d& d' d( d)
Z.d8d*Z/e0d+k(  r e/        yy# e$ r dZY w xY w)9ug  
Backtest QuantifiedStrategies-style **top-tier** ETF sleeves (2016+ window).

Families (one rule set each, plain-English QS conventions):
  1. seasonality_turnaround_tuesday   — long SPY Tue if Mon return < 0
  2. seasonality_turn_of_month        — long last 1 + first 3 sessions of month
  3. seasonality_opex_week            — long Mon–Fri of week with 3rd Friday
  4. overnight_close_to_open          — long SPY every close → next open
  5. overnight_5d_low                 — close at 5-day low → next open
  6. overnight_3down                  — after 3 down days → next open
  7. mr_rsi2_sma200                   — Connors RSI(2)<10, >SMA200; exit RSI>70 or 5d
  8. mr_ibs_sma200                    — IBS<0.2, >SMA200; exit IBS>0.8 or 3d
  9. dual_momentum_spy_tlt            — Antonacci 12m abs+rel mom (SPY vs TLT)
 10. rotation_spy_tlt_gld             — monthly top-1 12m momentum

Example::

    cd /Users/robzingale/trading_bot && PYTHONUNBUFFERED=1 \
      .venv/bin/python RenTech/strategy_stack/run_qs_top_ideas_backtest.py \
      --start 2016-01-04 --end 2026-04-02 --capital 100000
    )annotationsN)Path   )_standardize_ohlcvRenTechdatalogsqs_top_ideas_backtestc                   t         t        d      t        j                  |       j                  |dd      }t	        |d      }t        j                  |j                        j                  d      |_        |j                         S )zSAdjusted OHLC (auto_adjust=True) so open/close are on one scale for overnight math.Nzyfinance required1dT)periodintervalauto_adjustF)prefer_adjusted)
yfImportErrorTickerhistoryr   pdto_datetimeindextz_localize
sort_index)tickerr   rawouts       L/opt/rentech/trading_bot/RenTech/strategy_stack/run_qs_top_ideas_backtest.py_fetch_ohlcr   1   sk    	z-..
))F

#
#6Dd
#
SC
S%
8Csyy)55d;CI>>    c                \   | j                         }|j                  d      }| j                  d      }|j                  d|z  |d      j                         }|j                  d|z  |d      j                         }||j	                  dt
        j                        z  }ddd|z   z  z
  S )N        )lower      ?F)alphamin_periodsadjustr         Y@)diffclipewmmeanreplacenpnan)closendupdnavg_upavg_dnrss           r   _rsir7   ;   s    

A	
c	B"	BVV#'qV?DDFFVV#'qV?DDFF	&..BFF+	+BES2X&''r   c                p    | d   | d   z
  j                  dt        j                        }| d   | d   z
  |z  S )Nhighlowr   r/   )r,   r-   r.   )dfrngs     r   _ibsr=   E   s=    f:5	!
*
*1bff
5CwK"U)#s**r   c                b   | j                  d      j                  t        j                        } t	        |       }|dk  r|dddddS |d| z   j                         z  }|dz  }t        |j                  d         }||z  dz
  }|dkD  r||z  d|z  z  dz
  n
t        d      }||j                         z  dz
  }t        | j                  d	
            }	|	dkD  r2t        | j                         |	z  t        j                  d      z        nd}
|t        |dz  d      t        |dz  d      t        |
d      t        t        |j                               dz  d      t        |	t        j                  d      z  dz  d      t        |d      t        t        | dk7  j                               dz  d	      dS )Nr!   r   )n_daystotal_return_pctcagr_pctsharpe
max_dd_pctr#   g     o@r   r.      )ddofg-q=r'      )r?   r@   rA   rB   rC   vol_ann_pctending_equity_usdpct_days_invested)fillnaastyper-   float64lencumprodfloatiloccummaxstdr+   sqrtroundmin)rcapitalr0   eqyearsend_eq	total_retcagrddsdrB   s              r   _metricsr`   J   s   	RZZ(AAA1u#QTdghh	C!G$$&	&BIE2772;F 3&I8=	FW#+.4uU|D	biik	C	B	quu!u}	B685jU1668b=2775>12cF!)e"3Q7$,*"E"&&(Oe3Q7R"''%.058!<"61-"5!s():#;e#CQG	 	r   c                   | j                         }t        j                  |j                        j	                  d       |_        |j                  |k\  }|||j                  |k  z  }|j
                  |   j                         S N)r   r   r   r   r   loccopy)r;   startendr   masks        r   _alignrh   c   si    
--/Csyy)55d;CI99D
		S  774=r   c                F    | d   j                         j                  d      S )Nr/   r!   )
pct_changerK   )spys    r   _spy_close_to_closerl   l   s     w<""$++C00r   c                   t        |       }| j                  j                  dk(  |j                  d      dk  z  }t	        j
                  d| j                        }t        dt        |             D ]d  }| j                  |   j                  dk(  s | j                  |dz
     j                  dk(  s@|j                  |dz
     dk  sVd|j                  |<   f |j                  |d      S )zDLong SPY on Tuesday when Monday's close-to-close return is negative.r   Fr   rE   Tr!   )
rl   r   	dayofweekshiftr   SeriesrangerN   rQ   where)rk   c2cmon_negsignalis        r   seasonality_turnaround_tuesdayrx   p   s    
c
"Cyy""a'CIIaL1,<=GYYuCII.F1c#h &99Q<!!Q&399QU+;+E+E+JxxA"!%A& 99VS!!r   c                   | j                   }t        j                  d|      }t        j                  ||      j                  j	                  d      }|j                  |      D ]T  \  }}t        |j                         }|s|d   h}|dd D ]  }|j                  |        |D ]  }d|j                  |<    V t        |       j                  |d      S )	zOLong last session of month + first 3 sessions of next month (QS turn-of-month).Frn   MrD   NrG   Tr!   )r   r   rq   dt	to_periodgroupbylistaddrc   rl   rs   )	rk   idx	in_windowmonthsperdatesdlistpicksr1   s	            r   seasonality_turn_of_monthr   }   s    
))C		%s+IYYs#&))33C8FnnV, 	$
UU[[!rr 	AIIaL	 	$A#IMM!	$	$ s#)))S99r   c                    t        j                  | |d      }t        j                  ||t         j                  j	                  d      z   d      }t        |      dk\  r|d   S |d   S )	NrE   )yearmonthdayr   zW-FRI)freqrG   r   rD   )r   	Timestamp
date_rangeoffsetsMonthEndrN   )r   r   r1   fridayss       r   _third_fridayr      sX    
$e3AmmAq2::#6#6q#99HGW*71:;;r   c                2   | j                   }t               }|j                  d      j                         D ]  }t	        |j
                  |j                        }|t        j                  |j                        z
  }t        d      D ]8  }|j                  |t        j                  |      z   j                                :  t        j                  |D cg c]  }|j                         |v  c}|      }t        |       j                  |d      S c c}w )uF   Long Mon–Fri of calendar week containing the 3rd Friday (OPEX week).rz   )days   rn   r!   )r   setr|   uniquer   r   r   r   	Timedeltaro   rr   r   	normalizerq   rl   rs   )	rk   r   	opex_daysr   tf
week_startkr1   in_weeks	            r   seasonality_opex_weekr      s    
))C#&5I}}S!((* K388SYY/",,BLL99
q 	KAMM:!(<<GGIJ	KK
 iiSA)3AMGs#))'377 Bs   Dc                ^    | d   | d   j                  d      z  dz
  }|j                  d      S )zABuy every close, sell next open; PnL booked on next session date.openr/   rE   r#   r!   )rp   rK   )rk   ocs     r   overnight_close_to_openr      s2    	Vs7|))!,	,s	2B99S>r   c                   | d   | d   j                  d      z  dz
  }t        j                  d| j                        }t	        dt        |             D ]g  }t        |j                  |dz
           st        j                  |j                  |         rt        |j                  |         nd|j                  |<   i |S )z>Enter at signal-day close, exit next open; return on exit day.r   r/   rE   r#   r!   rn   )rp   r   rq   r   rr   rN   boolrQ   r-   isfiniterP   )rk   
entry_maskr   r   rw   s        r   _overnight_one_nightr      s    	Vs7|))!,	,s	2B
))Csyy
)C1c#h P
A&'/1{{2771:/F%
+CCHHQKP Jr   c                n    | d   | d   j                  dd      j                         k  }t        | |      S )Nr/   r   r%   )rollingrV   r   )rk   low5s     r   overnight_5d_lowr      s:    w<3w<//q/AEEGGDT**r   c                    t        |       }|dk  |j                  d      dk  z  |j                  d      dk  z  }t        | |      S )Nr   rE   r   )rl   rp   r   )rk   rt   threes      r   overnight_3downr      sF    
c
"C1W1)*ciilQ.>?EU++r   c                   t        |       }t        j                  t        |       t              }d}t        t        |             D ]l  }|r||dz
     nd}|r3|dz  }t	        |j                  |dz
           s||kD  rd||<   d}>d||<   D|dkD  sJt	        |j                  |dz
           sfd||<   d}n t        j                  || j                        }	|j                  |	d      S )uL   Signal at close t → position earns close-to-close returns from t+1 onward.)dtyper   rE   FTrn   r!   )rl   r-   zerosrN   r   rr   rQ   r   rq   r   rs   )
rk   entryexit_sigmax_holdrt   posholdrw   prevrg   s
             r   _mr_state_machiner      s     c
"C
((3s84
(CD3s8_ s1q5zEAIDHMM!a%()TH_AAUtEJJq1u-.CFD 99S		*D99T3r   c                    t        | d   d      }| d   j                  dd      j                         }|dk  | d   |kD  z  }|dkD  }t        | ||d	      S )
Nr/   r      d   r   
   F   r   r   )r7   r   r+   r   )rk   rsi2sma200r   r   s        r   mr_rsi2_sma200r      se    Ga D\!!#3!7<<>FBY3w<&01EbyHS%A>>r   c                    t        |       }| d   j                  dd      j                         }|dk  | d   |kD  z  }|dkD  }t        | ||d      S )	Nr/   r   r   r   g?g?rG   r   )r=   r   r+   r   )rk   ibsr   r   r   s        r   mr_ibs_sma200r      s^    
s)C\!!#3!7<<>F3Y3w<&01ESyHS%A>>r   c                0    | | j                  d      z  dz
  S )N   r#   )rp   )r/   s    r   _mom_12mr      s    5;;s##c))r   c                Z   | j                   j                  |j                         }| j                  |df   }|j                  |df   }t        |      }t        |      }|j	                         j                  d      }|j	                         j                  d      }t        j                  ||      j                  j                  d      }	t        j                  d|t              }
|	j                  |	      D ]  \  }}|j                   d   }t        |j                  |         t        |j                  |         }}t        j                  |      rt        j                  |      sd}nt        ||      dk  rd}n	||k\  rdnd	}|j                   D ]  }||
j                  |<     t        j                  d|      }|D ]g  }|
j                  |   }|dk(  r&t        |j                  |         |j                  |<   =|d	k(  sCt        |j                  |         |j                  |<   i |S )
zEAntonacci: pick SPY or TLT by 12m momentum; cash if absolute mom < 0.r/   r!   rn   rz   cashr   r   r   rk   tlt)r   intersectionrc   r   rj   rK   r   rq   r{   r|   objectr}   rP   r-   r   max)rk   r   r   spy_ctlt_cm_spym_tltspy_rtlt_rr   choicer   r   d0msmtpickr1   retcs                       r   dual_momentum_spy_tltr      s   
))
 
 
+CGGCL!EGGCL!EUOEUOE%%c*E%%c*EIIc%((2237EYYvS7FmmE* 
!
U[[^uyy}%uUYYr]';BBBKKODR[AD"H5%D 	!A FJJqM	!
! ))Cs
#C -JJqM:uyy|,CGGAJ%Zuyy|,CGGAJ- Jr   c           	     v   | j                   j                  |j                         j                  |j                         }| j                  |df   |j                  |df   |j                  |df   d}|j                         D ci c]%  \  }}||j	                         j                  d      ' }}}|j                         D ci c]  \  }}|t        |       }}}t        j                  ||      j                  j                  d      }	t        j                  d|t              }
|	j                  |	      D ]  \  }}|j                   d   }|D ci c]  }|t        ||   j                  |           }}t        d	 |j                         D              sd}nt!        ||j"                  
      }|j                   D ]  }||
j                  |<     t        j                  d|      }|D ]7  }t        ||
j                  |      j                  |         |j                  |<   9 |S c c}}w c c}}w c c}w )z7Monthly hold top-1 of SPY/TLT/GLD by 12-month momentum.r/   rk   r   gldr!   rn   rz   rk   r   r   c              3  F   K   | ]  }t        j                  |        y wrb   )r-   r   ).0vs     r   	<genexpr>z'rotation_spy_tlt_gld.<locals>.<genexpr>  s     ;a2;;q>;s   !)key)r   r   rc   itemsrj   rK   r   r   rq   r{   r|   r   r}   rP   allvaluesr   get)rk   r   r   r   panelsr   r   retsmomsr   r   r   r   r   scoresr   r1   r   s                     r   rotation_spy_tlt_gldr     s    ))
 
 
+
8
8
CCWWS'\*3773<3HQTQXQXY\^eYeQfgF6<llnEdaAq||~$$S))EDE'-||~6tq!Ax{N6D6IIc%((2237EYYuCv6FmmE* !
U[[^5;<!U47;;r?++<<;6==?;;Dv6::.D 	!A FJJqM	!! ))Cs
#C 74

1.22156
7J! F6
 =s   *H*H0#H6c                    t        |       S rb   )rx   r   s      r   <lambda>r   &  s    <Z[^<_ r   c                    t        |       S rb   )r   r   s      r   r   r   '  s    7PQT7U r   c                    t        |       S rb   )r   r   s      r   r   r   (  s    3H3M r   c                    t        |       S rb   )r   r   s      r   r   r   )  s    5LS5Q r   c                    t        |       S rb   )r   r   s      r   r   r   *  s    .>s.C r   c                    t        |       S rb   )r   r   s      r   r   r   +  s    _S-A r   c                    t        |       S rb   )r   r   s      r   r   r   ,  s    N3,? r   c                    t        |       S rb   )r   r   s      r   r   r   -  s    =+= r   c                    t        | |      S rb   )r   r   s      r   r   r   .  s    3Hc3R r   c                    t        | ||      S rb   )r   r   s      r   r   r   /  s    2FsCQT2U r   )
rx   r   r   r   r   r   r   r   r   r   c                    t        j                  t        j                  d      d         } | j	                  dd       | j	                  dd       | j	                  d	t
        d
       | j	                  dd       | j	                  dt        t               | j                         }t        j                  |j                        }t        |j                        j                         rt        j                  |j                        nd }t        |j                        }t!        t#        d|j$                        ||      }t!        t#        d|j$                        ||      }t!        t#        d|j$                        ||      }t'        |      }i }	d|i}
t(        j+                         D ]  \  }} ||||      j-                  |j.                        j1                  d      }t3        ||      }t5        |      r1t        |j.                  j7                         j9                               nd|d<   t5        |      r1t        |j.                  j;                         j9                               nd|d<   ||	|<   ||
|<   t=        |dd|d   dd|d   dd|d   d d!|d"   dd#|d$   d%d&d'(        t3        ||      }||	d<   t=        ddd|d   dd|d   dd|d   d d!|d"   dd&
d'(       t        j>                  |
      jA                  d)*      }|jC                         jE                  d+      }t=        d,d'(       t(        D ]&  }t=        d-|dd.|jF                  |df   d/d'(       ( tI        |	j+                         D cg c]  \  }}|dk7  s||d   f c}}d0 d'1      }t=        d2d'(       tK        |d3      D ]!  \  }\  }}t=        d-|d4d5| d6|d/d7d'(       # |jL                  jO                         jQ                         }|jR                  jU                  d'd'8       t        | d9      }t        | d:      }t        | d;      }t        | d<      }d=jW                  tX        jZ                        t        |j9                               |t        |j9                               nd||	|D cg c]
  \  }}||d> c}}d?}|j]                  t_        j`                  |d@A             t        j>                  |
j+                         D ci c]  \  }}||jb                   c}}|j.                  B      }dC|j.                  _2        |jg                  |       |jg                  |       dD|j9                          dE|r|j9                         ndF dG|dHddIdd=dJdKd=dLdMd=dNdMd=dOdPd=dQdRg}ti        t(        jk                               dgz   D ]M  }|	|   }|jm                  d$dS      }|jo                  |dd=|d   dTd=|d   dd=|d   dUd=|d"   dVd=|dW       O |jq                  ddXg       tK        |d3      D ]#  \  }\  }}|jo                  d-| d5| dY|d/       % |j]                  dZjW                  |      dZz          t=        d[| d'(       t=        d\| d'(       y c c}}w c c}}w c c}}w )]Nz

r   )descriptionz--startz
2016-01-04)defaultz--endz
2026-04-02z	--capitalg     j@)typer   z--yahoo-periodr   z--out-prefixSPYTLTGLDSPY_buy_holdr!    
first_date	last_date32sz  ret=r@   z7.2fz%  CAGR=rA   z6.2fz
%  Sharpe=rB   z5.2fz  DD=rC   z%  invested=rJ   z5.1f%T)flushr   )howrG   z
Correlation vs SPY buy-hold:z  z  rho=z.3fc                    | d   S )NrE    )xs    r   r   zmain.<locals>.<lambda>i  s
    ad r   )r   reversez
Ranked by Sharpe:rE   2dz. z ())parentsexist_okz
_meta.jsonz_correlation.csvz
_daily.csvz_metrics.txt )namerB   )commandre   rf   rX   
strategiesranked_by_sharper   )indentrn   datezQS top ideas backtest  u    → latestz  capital=$z,.0fstrategyzreturn%z>9szCAGR%z>7sSharpezmaxDD%z>8szinv%z>6sr'   z9.2fz7.3fz8.2fz6.1fzRanked by Sharpe:u    — 
z
Wrote zWrote )9argparseArgumentParser__doc__splitadd_argumentrP   r   DEFAULT_OUT
parse_argsr   r   re   strrf   striprX   rh   r   yahoo_periodrl   
STRATEGIESr   reindexr   rK   r`   rN   rV   r  r   print	DataFramedropnacorrrU   rc   sorted	enumerate
out_prefix
expanduserresolveparentmkdirjoinsysargv
write_textjsondumpsr   r  to_csvr~   keysr   appendextend) apargsre   rf   caprk   r   r   spy_bhresultsdaily_panelr  fnrW   mspy_bh_mcorr_dfr,  r   r   rankedrw   sprefix	meta_path	corr_path
daily_pathmetrics_pathmeta	out_dailylinesinvs                                    r   mainrS  3  s   		 	 W]]6-B1-E	FBOOI|O4OOG\O2OOKeYO?OO$eO4OON{OC==?DLL$E$'M$7$7$9",,txx
 tC

C
UD$5$56s
CC
UD$5$56s
CC
UD$5$56s
CC %F!G)7(@K$$& 
bsC%%cii077<Q7:1v#aggkkm00232,69!fQWW[[]//12"+DCjq!34T: ;jM$'z!H+d1C DL/$'|A6I4J43PPQS 		

 $H&GN	#
fX.@%A$$G H$T**Xh5G4M N|$T*!	- 	 ll;'..5.9G<<>"D	
*$7 U4*F488D.,@#A#"FGtTU &-mmoMdan9L!Qx[	MF
 

t,vq) 6	6Aq1R&1#R#wa(56 __'')113F
MMt4xz*+Ix/01I
+,J6(,/0L 88CHH%UZZ\""%/s388:rBHI$!Qa15ID D34!,!2!2!45AAHH5mmI "IOOZ KK	 "%**,u3SXXZH4UU`adei`jk
c
!Ic?!GC=(3qRUVWX^_bWcdE
 Z__&'>*:: 
DMee'/Cj!./5Qq}T6J!{4 !L/$!7qT
D	

 
LL")*+vq) 0	6Aqr!Bqcqg./0DIIe,t34	HYK
 -	F:,
t,g 	N* J
 	6s   Z9
	Z9
>Z?[
__main__)r   r$  r   r$  returnpd.DataFrame)r/   	pd.Seriesr0   intrU  rW  )r;   rV  rU  rW  )rW   rW  rX   rP   rU  zdict[str, float])r;   rV  re   pd.Timestamprf   zpd.Timestamp | NonerU  rV  )rk   rV  rU  rW  )r   rX  r   rX  rU  rY  )rk   rV  r   rW  rU  rW  )
rk   rV  r   rW  r   rW  r   rX  rU  rW  )r/   rW  rU  rW  )rk   rV  r   rV  rU  rW  )rk   rV  r   rV  r   rV  rU  rW  )rU  None)1r  
__future__r   r  r8  r5  pathlibr   numpyr-   pandasr   __file__r1  r  _REPOr$  pathinsert"RenTech.strategy_stack.data_loaderr   LOGSr"  yfinancer   r   r   r7   r=   r`   rh   rl   rx   r   r   r   r   r   r   r   r   r   r   r   r   r   r'  rS  __name__r  r   r   <module>rg     s  , #   
   X ((+u:SXXHHOOAs5z" Ay6!F*,,
(+
2 1
":$<
8+
, 	     	 
  4??*B	(/;4 '`!UMQCA?=RU
h-V zF c  	Bs   D" "D,+D,