+
    (jM                     j   R t ^ RIt^ RIt^ RIHtHt ^ RIHt ^ RIt^ RI	t
RtRt^	t^t^t^Ft^Kt^2tRtRt. R*OtR^R	R
RRRRRR/R^R	RRR] R2RRRR/R^R	RRR]^d,          R R2RRR]/.tR R ltR+R R lltR R ltR R ltR,R  R! lltR" R# ltR$ R% ltR& R' ltR( t] R)8X  d
   ]! 4        R# R# )-uC  
Backtester for EMA 9/21 crossover + RSI strategy — 3-variant comparison.
Mirrors trading_bot.py logic using 1-hour bars over ~2 years.

Variants
  1. Base          — EMA 9/21 crossover + RSI only
  2. SPY Trend     — Base + buy only when SPY > its 50-day MA
  3. 5% Stop Loss  — Base + 5% hard stop loss per trade
N)datetime	timedelta)defaultdictg     @g     @@g?zbacktest_results.jsonidnamezBase Strategydescz)EMA 9/21 crossover + RSI, no extra filter
spy_filterF	stop_losszSPY Trend FilterzBase + buy only when SPY > z-day MATz5% Stop LosszBase + .0fz% hard stop loss per tradec                d    V ^8  d   QhR\         P                  R\        R\         P                  /#    seriesperiodreturnpdSeriesint)formats   "+/home/david-summers/trading-bot/backtest.py__annotate__r   ;   s)     8 8RYY 8 8		 8    c                 D    V P                  VR R7      P                  4       # )F)spanadjust)ewmmean)r   r   s   &&r   calc_emar   ;   s    ::6%:05577r   c                d    V ^8  d   QhR\         P                  R\        R\         P                  /# r   r   )r   s   "r   r   r   ?   s)     " "RYY " "RYY "r   c                 J   V P                  4       pVP                  ^ R7      pV) P                  ^ R7      pVP                  V^,
          RR7      P                  4       pVP                  V^,
          RR7      P                  4       pWV,          p^d^d^V,           ,          ,
          # )    lowerF)comr   )diffclipr   r   )r   r   deltagainlossavg_gainavg_lossrss   &&      r   calc_rsir-   ?   s    {{}Ezzz"D}}1}%DxxFQJux5::<HxxFQJux5::<H"B#R.!!r   c                0    V ^8  d   QhR\         R\        /# )r   symbolsr   )listdict)r   s   "r   r   r   K   s     - -$ -4 -r   c           
         \         P                  ! 4       pV\        R R7      ,
          p/ pV  EF  p\        RVR 2RRR7        \        P
                  ! WBVRRRR	7      p\        P                  ! R
4       VP                  '       d   \        R4       Kg  \        VP                  \        P                  4      '       d!   VP                  P                  ^ 4      Vn
        \        R VP                   4       R4      pVf   \        R4       K  WV.,          P                  VR/R7      P!                  4       pVP#                  R.RR7       \%        V4      \&        \(        ,           ^,           8  d   \        R\%        V4       R24       EK]  \+        VR,          \,        4      VR&   \+        VR,          \&        4      VR&   \/        VR,          \(        4      VR&   VP#                  RR7       WSV&   \        \%        V4      R RVP0                  ^ ,          P3                  4        RVP0                  R,          P3                  4        24       EK  	  V#   \4         d   p\        RT 24        Rp?EKC  Rp?ii ; i)i  )days  <8 Tendflush1hF)startr8   intervalauto_adjustprogressg      ?zno datac              3   R   "   T F  qP                  4       R 8X  g   K  Vx  K  	  R# 5i)closeNr"   ).0cs   & r   	<genexpr>download_all.<locals>.<genexpr>a   s     LAwwyG7Kaas   '
'Nzno Close columnr@   )columns)subsetinplaceztoo few bars ()ema9ema21rsi14)rG   z5,z bars  u    → zERROR: )r   nowr   printyfdownloadtimesleepempty
isinstancerE   r   
MultiIndexget_level_valuesnextrenamecopydropnalenEMA_SLOW
RSI_PERIODr   EMA_FASTr-   indexdate	Exception)r/   r8   r;   datasymdf	close_colexcs   &       r   download_allrg   K   s   LLNC)%%ED3r(mD1#	#c4%B JJsOxxxi "**bmm44ZZ88;
LLdSI '(K''G0D'EJJLBIIgYI52wJ.22s2wiq12"2g;9BvJ"2g;9BwK"2g;
;BwKIIdI#ISWRL(8(8(:';5"ARARAT@UVWE N K  	#GC5/""	#s.   AIA6IA;I
B?II5I00I5c                D    V ^8  d   QhR\         P                  R\        /# )r   spy_dfr   )r   	DataFramer1   )r   s   "r   r   r   }   s     	 	",, 	4 	r   c                   V P                   P                  4       pV R,          P                  V4      P                  4       pVP	                  \
        4      P                  4       pW#8  P                  4       pVP                  4       # )u   
{normalized_timestamp: bool} — True when SPY daily close > 50-day SMA.
Uses daily closes derived from the hourly data already in memory.
r@   )	r_   	normalizegroupbylastrollingSPY_MA_PERIODr   rZ   to_dict)ri   datesdaily_closema50aboves   &    r   build_spy_ma50_lookuprv   }   si    
 ,,((*E/))%0557K%%m499;D%--/E==?r   c          
      p    V ^8  d   QhR\         R\        R\        R,          R\         R,          R\        /# )r   rb   r   r	   Nspy_ma50r   )r1   boolfloattuple)r   s   "r   r   r      sI     5 5
55 t|5 Tk	5
 5r   c                  
  a"a# \         p/ o#. p. p\        \        4       P                  ! R  V P	                  4        4       !  4      pV P                  4        UU	u/ uF  w  rV\        V	P                  4      bK  	  p
pp	/ p/ o"V EFS  pV'       d/   Ve+   \        VP                  VP                  4       R4      4      pMRp\         EF  pW9  g   WV,          9  d   K  W,          P                  V,          p\        VR,          4      pVS"V&   W9  d   WV&   KU  W,          p\        VR,          4      p\        VR,          4      p\        VR,          4      p\        VR,          4      p\        VR,          4      pVV8*  ;'       d    VV8  pVV8  ;'       d    VV8  pVS#9  d   V'       d   V\        8  d   V'       d   \        \        V4      p\!        VV,          4      pV^8  ds   VV,          pVV,          pRVR	V/S#V&   VP#                  R
VP%                  4       RVRRR\'        V^4      RVR\'        V^4      R\'        V^4      R\'        V^4      /4       EM4RpVe&   VS#V,          R	,          ^V,
          ,          8:  d   RpVf   V\(        8  d   RpM
V'       d   RpV'       d   S#P+                  V4      pVR,          V,          pVVR,          VR	,          ,          ,
          pVVR,          VR	,          ,          ,          ^d,          pVV,          pVP#                  R
VP%                  4       RVRRR\'        V^4      RVR,          R\'        V^4      R\'        V^4      R\'        V^4      R\'        V^4      RVR\'        V^4      /4       WV&   EK  	  V\-        V"V#3R lS# 4       4      ,           p VP#                  V\'        V ^4      34       EKV  	  V'       d
   VR,          MRp!\/        S#P                  4       4       F  w  ppS"P                  V4      pVf   K  VR,          V,          pVVR,          VR	,          ,          ,
          pVVR,          VR	,          ,          ,          ^d,          pVV,          pTP#                  R
V!'       d   V!P%                  4       MRRVRRR\'        V^4      RVR,          R\'        V^4      R\'        V^4      R\'        V^4      R\'        V^4      RRRR/4       K  	  V\'        V^4      V3# u up	pi )c              3   L   "   T F  p\        VP                  4      x  K  	  R # 5iN)setr_   rA   rd   s   & r   rC   run_backtest.<locals>.<genexpr>   s     %Lmc"((mmms   "$NFTr@   rI   rJ   rK   qtyavg_cost	timestampsymbolactionBUYpricecost
cash_afterrsi	STOP_LOSSRSI_OVERBOUGHTBEARISH_CROSSSELLproceedspnlpnl_pctexit_reasonc              3   p   <"   T F+  qS9   g   K  SV,          R ,          SV,          ,          x  K-  	  R# 5i)r   N )rA   s
last_price	positionss   & r   rC   r      s3      
z/ 0IaL*Q-//s   6&6 END_OF_BACKTESTrL   )STARTING_CASHsortedr   unionvaluesitemsr_   ry   getrl   	WATCHLISTlocrz   RSI_BUY_MAXminPOSITION_SIZE_USDr   append	isoformatroundRSI_SELL_TRIGGERpopsumr0   )$rb   r   r	   rx   cashtradesportfolio_historyall_tsrc   rd   
index_setsprev_rowtsspy_okrowr   pre9_nowe21_nowe9_preve21_prevrsi_vbullishbearishavailr   r   r   posr   r   r   pvlast_tsr   r   s$   &&&&                              @@r   run_backtestr      s    DIF%Ldkkm%LMNF48JJLAL#s288}$LJAHJ(.(,,r||~u=>FF9C"sO";IMM"%C#g,'E#JsO" #}BS[)FS\*GRZ(GR[)HS\*E(*BB'1AG(*BB'1AG)#u{2v 148E.Cax #e*/j%)H	#'",,.$#$%#%q/!#"%a.(%a.!%q/	' 	 #(	#z :a)m LL&1&//&6 &5(}}S1C"5zE1H'#e*s:*FFC"c%j3z?&BCcIGx'DMM#r||~ s vuUAs5z"uXq'9uS!}!uWa'8$uT1~%{uUA#   SMQ T C 

 
 
 	  "eBl!34k p #fRjG*+Ss#=u:%c%j3z?::#e*s:67#=8'7,,.r365?3u:51-5a=5!,5q>,4
 	 ,, 5q>#444g Bs   !T
c                H    V ^8  d   QhR\         R\        R\         R\        /# )r   r   
final_cashr   r   )r0   rz   r1   )r   s   "r   r   r     s.     H H H5 HT Hd Hr   c                 *   V  Uu. uF  q3R ,          R8X  g   K  RV9   g   K  VNK  	  pp\        R V  4       4      p\        V4      pV Uu. uF  q3R,          ^ 8  g   K  VNK  	  ppV Uu. uF  q3R,          ^ 8:  g   K  VNK  	  ppV'       d   \        V4      V,          ^d,          MRp	V'       d   \        VR R7      MRp
V'       d   \        VR R7      MRpV\        ,
          \        ,          ^d,          p\        pRpV F1  w  ppVV8  d   TpVV,
          V,          ^d,          pVV8  g   K/  TpK3  	  \        R	 4      pV FA  w  ppVP                  R
4      pVV,          R,          f   VVV,          R&   VVV,          R&   KC  	  / p\        VP                  4       4       F  w  ppVR,          ;'       g    RpVR,          ;'       g    RpR\        V^4      R\        V^4      R\        VV,
          ^4      RV'       d"   \        VV,
          V,          ^d,          ^4      MR/VV&   K  	  \        R 4      p\        \        4      pV F  pVVR,          ,          pVR;;,          ^,          uu&   VR;;,          VR,          ,          uu&   VR,          ^ 8  d   VR;;,          ^,          uu&   MVR;;,          ^,          uu&   VVP                  RR4      ;;,          ^,          uu&   K  	  RR\        RVR\        V^4      RVRVR\        V	^4      R\        V4      R \        V4      R!V
R"VR#\        V^4      /R$\        V4      R%TR&VP                  4        UUu/ uF  w  ppV\        V4      bK  	  uppR'V /# u upi u upi u upi u uppi )(r   r   r   c              3   D   "   T F  qR ,          R8X  g   K  ^x  K  	  R# 5i)r   r   Nr   )rA   ts   & r   rC   calc_metrics.<locals>.<genexpr>  s     =f(u(<11fs    
         c                     V R ,          # r   r   r   s   &r   <lambda>calc_metrics.<locals>.<lambda>      ahr   )keyNc                     V R ,          # r   r   r   s   &r   r   r     r   r   c                      R RRR/# )r;   Nr8   r   r   r   r   r   r   %  s    $t(Dr   z%Y-%mr;   r8   start_value	end_value
return_pctc                      R ^ R^ R^ RR/# )r   winslosses	total_pnlr   r   r   r   r   r   r   8  s    1fa1k3Gr   r   r   r   r   r   r   UNKNOWNsummarystarting_cashr   total_return_pcttotal_tradesclosed_tradeswin_rate_pctwinning_tradeslosing_trades
best_tradeworst_trademax_drawdown_pctexit_reasonsmonthly_pnlsymbol_stats
all_trades)r   r[   maxr   r   r   strftimer   r   r   r   r   r1   )r   r   r   r   closedn_buysn_closedwinnersloserswin_ratebestworsttotal_returnpeakmax_dd_vddmonthlyts_objmr   dr   e	sym_statsr   stks   &&&                          r   calc_metricsr    sB   !J6ax[F%:uz6FJ=f==F6{H!26auX\6G2!36auX]6F308s7|h&,cH39C./tD39C./tE.-?#ELDF!1t8DQh$$;F "   DEG&	OOG$1:g&"#GAJw
5	 ' Kw}}'1gJ#eH#5A;5A;5Q?!5!a%1s!2A6	
A ( "GI %S)Lq{#
81
;1U8#U8a<vJ!OJxLALQUU=)45:5  	
lA 6h 2GFfa 0
 	\*	0AB0A1DG0AB# k K 33B Cs-   N N N NN+N
?N
!Nc                >    V ^8  d   QhR\         R,          R\        /# )r   r   Nr   )r1   str)r   s   "r   r   r   \  s"     D D$+ D# Dr   c                 Z    V '       g   R # V R,           RV R,          R RV R,          R R2# )	u   —r   r6   r   z+,.0fz (r   z+.0f%)r   r   s   &r   
_trade_strr  \  s3    k]!AeHU+2a	l4-@CCr   c                0    V ^8  d   QhR\         R\         /# )r   variantsresults)r0   )r   s   "r   r   r   b  s     e et ed er   c                 
  aaa ^Tp^o^oRQVV3R llpRRR VV3R lllp\        RRV,          ,           4       \        R4       \        R4       \        RV,          4       V  F-  p\        R	VR
,           RVR,          R RVR,           24       K/  	  \        RV,          4       T! RV  Uu. uF  pRVR
,           RVR,           2NK  	  up4       V! R4       S Uu. uF  qfR,          NK  	  ppT! RV Uu. uF  pRVR,          R 2NK  	  up4       T! RV Uu. uF  qR,          R R2NK  	  up4       T! RV Uu. uF  qR,          R R2NK  	  up4       V! 4        T! RV Uu. uF  qR,          R  NK  	  up4       T! R!V Uu. uF  qR",          R  NK  	  up4       T! R#V Uu. uF  qR$,          R% R2NK  	  up4       T! R&V Uu. uF  qR',           R(VR),           2NK  	  up4       V! 4        T! R*V Uu. uF  p\        VR+,          4      NK  	  up4       T! R,V Uu. uF  p\        VR-,          4      NK  	  up4       V! 4        \        \        4       P                  ! R. S 4       !  4      p	V	 F5  p
T! R/V
 2S Uu. uF  qfR0,          P                  V
^ 4      NK  	  up4       K7  	  \        RV,          4       \        \        4       P                  ! R1 S 4       !  4      p\        R24       \        R3RS R3RT R3RT R3RT 24       \        R3R4R5 R3R6R R3R7R R3R8R 24       \        R3RS R3RT R3RT R3RT 24       V F  p. pS FY  pVR9,          P                  VR:^ R;^ /4      pVR:,          pVR;,          pV^ 8  d   R<MR=pVP                  RVR> R3VR? RV 24       K[  	  \        R3V R3R3P                  V4       24       K  	  \        \        4       P                  ! R@ S 4       !  V3RA lRBRC7      pV'       d   \        RD4       \        R3RU R3RT R3RT R3RT 24       \        R3RERF R3R6R R3R7R R3R8R 24       \        R3RU R3RT R3RT R3RT 24       V F  p. pS Fg  pVRG,          P                  V4      pV'       d4   VP                  RVRH,          RI R3VRJ,           RKVRL,           RM24       KV  VP                  RN4       Ki  	  \        R3VRF R3R3P                  V4       24       K  	  \        RO\         24       \        RV,          R,           4       RP# u upi u upi u upi u upi u upi u upi u upi u upi u upi u upi u upi u upi )VT      ─c                 n   < \        R V S,           R V S,           R V S,           R V S,           24       R# )r4   N)rN   )chCWLWs   &r   divider!print_comparison.<locals>.dividerg  s4    2b5'BrE7"RUG2beW56r   c                0    V ^8  d   QhR\         R\        /# )r   labelvals)r  r0   )r   s   "r   r   &print_comparison.<locals>.__annotate__j  s     + +3 +d +r   c                 z   <a R V S R2oRP                  V3R lV 4       4      p\        RV RS 2 RV 24       R# )z{:}r4   c              3   X   <"   T F  pSP                  \        V4      4      x  K!  	  R # 5ir~   )r   r  )rA   r   fmts   & r   rC   0print_comparison.<locals>.row.<locals>.<genexpr>l  s!     ;d#**SV,,ds   '*<N)joinrN   )r  r  aligncellsr  r  r  s   &&& @r   r   print_comparison.<locals>.rowj  sF    E72$b!		;d;;52$-r%)*r   
   ═uG     STRATEGY COMPARISON  —  EMA 9/21 + RSI  |  1-Hour Bars  |  ~2 YearszA  Universe: 13 stocks + ES=F + MES=F  |  $10,000 starting capital  Vr   : r   z<18u    — r   r   Vr   zFinal Portfolio$r   ,.2fzTotal Returnr   +.2f%zMax Drawdownr   z.2fzTrades Enteredr   ,zTrades Closedr   zWin Rater   .1fzWinners / Losersr   /r   z
Best Trader   zWorst Trader   c              3   N   "   T F  qR ,          P                  4       x  K  	  R# 5i)r   NkeysrA   rs   & r   rC   #print_comparison.<locals>.<genexpr>       @1',,..   #%z  Exit: r   c              3   N   "   T F  qR ,          P                  4       x  K  	  R# 5i)r   Nr.  r0  s   & r   rC   r2    s     %Ow!&6&;&;&=&=wr4  z"
  MONTHLY P&L  (P&L $ / Return %)r4   Monthz<9zV1: BasezV2: SPY FilterzV3: Stop Lossr   r   r   u   ▲u   ▼z>+8,.0fz>+5.1fc              3   N   "   T F  qR ,          P                  4       x  K  	  R# 5i)r   Nr.  r0  s   & r   rC   r2    r3  r4  c                 d   < S^ ,          R,          P                  V / 4      P                  R^ 4      # )r!   r   r   )r   )r   r	  s   &r   r   "print_comparison.<locals>.<lambda>  s(    gaj044Q;??QOr   T)r   reversez.
  PER-SYMBOL P&L  (total $ across all trades)Symbolr5   r   r   z>+7,.0fr   zW/r   Lz	no tradesu   
  Full results saved → N)r  )r  u   ─────────u6   ──────────────────u   ────────)	rN   r  r   r   r   r   r   r  RESULTS_FILE)r  r	  Wr  r   r   r1  r   xall_reasonsreason
all_monthsr   colsr   r   retarrowall_symsrc   r   r  r  s   &f                   @@r   print_comparisonrG  b  s   	A	B	B7 7+ + 
$
	
ST	
MN	%!)AdG9BqyoU1V9+>? 	%!):Aq4	AfI;':;EN  ''w!9wA'!L!Q!L/$!78!LM!L!Q#5!6t <A>!LM!L!Q#5!6s ;1=!LMI!L!Q>!21 56!LM!L!Q?!3A 67!LM
!L!Q>!23 7q9!LMUVWUVPQ#3!4 5Qq7I6JKUVWXI!L!Qj<9!LM!L!Qj=)9:!LM I@@AK hvhG!TGqN"3"7"7"BG!TU  
%!) %Ow%OPQJ	/1	Bwir(2hZr(
<=	
WRLc
"C
 3
	! 
Bwir(2hZr(
<=Am$((UA|Q,GHAeHClOC AXE5EKK!C=3v,aw?@  	1#R		$()*  @@AOH
 ?A7)2hZr(2hZ@A"R#b$Bs#%	
 	7)2hZr(2hZ@ACD~&**3/KKB{OG4Bf:,bHa9
 KK,  Bs2hb4 123  
'~
67	%!)d
a ; 	(LLLLLLWLL "UsH   ,T
T#8T(
T-
?T2
)T7

T<
+U
U
 U
)U
	!Uc                     ^Tp \        RV ,          4       \        R4       \        R4       \        RV ,          4       \        \        4      pV'       g   \        R4       R# RpRV9   da   \        VR,          4      p\	        R VP                  4        4       4      p\        V4      p\        RV R	V R
W4,          ^d,          R R24       \        R\        \        4       R\	        R VP                  4        4       4      R R24       . p\         F  p\        RVR,           RVR,           R2RRR7       \        VVR,          VR,          VR7      w  rxp	\        WxV	4      p
WjR&   V	 UUu. uF  w  rRVP                  4       RV/NK  	  uppV
R &   VP                  V
4       V
R!,          p\        R"VR#,          R$ R%VR&,          R' R(VR),          R* R+VR,,          R* R-2	4       K  	  \        \        R.4      ;_uu_ 4       p\        P                  ! R/\        R0V/V^\         R17       RRR4       \#        \        V4       R# u uppi   + '       g   i     L(; i)2r  r"  u:     BACKTESTER  —  EMA 9/21 + RSI  |  3-Variant Comparisonz4  Fetching ~2 years of 1-hour bars for 15 symbols...zNo data downloaded. Exiting.NSPYc              3   8   "   T F  q'       g   K  ^x  K  	  R# 5i)   Nr   )rA   r   s   & r   rC   main.<locals>.<genexpr>  s     9"3Qqqq"3s   	
z!
  SPY 50-day MA: above trend on r,  z days (r
   r  z
  Running z variants over c              3   8   "   T F  p\        V4      x  K  	  R # 5ir~   )r[   r   s   & r   rC   rL    s     1=RR=s   r*  z total bars...
r#  r   r$  r   z...r6   Tr7   r   r	   )r   r	   rx   variantr   valuer   r   r&  r   r'  z  (r   r(  z%)  win=r   r+  z%  dd=r   r)  wr  r	  )indentdefault)rN   rg   r   rv   r   r   r[   VARIANTSr   r  r   r   openr=  jsondumpr  rG  )r>  rb   rx   ru   totalall_resultsr   r   r   phmetricsr   valr   fs                  r   mainr]    s?   
A	%!)	
FG	
@A	%!)	"D,- H}(e59(//"399x=25'5'UXY\H]]_`a	LX14;;=11!44DF G KAdG9Bqyk-3dC!-n	"
B v26	GI(
GIGB[",,.'37r(
#$ 	7#I,% &$%d+ ,^$S) *&',A/	
! . 
lC	 	 A		:xK@!AWZ[ 
! X{+!(
 
!	 s   +I ?'II	__main__)AAPLMSFTNVDAAMZNGOOGLMETATSLAAMDrI  QQQNBISSOFIBEzES=FzMES=F)   )FNN)!__doc__rU  rQ   r   r   collectionsr   pandasr   yfinancerO   r   r   r^   r\   r]   r   r   rp   STOP_LOSS_PCTr=  r   rS  r   r-   rg   rv   r   r  r  rG  r]  __name__r   r   r   <module>rr     s$     ( #    
 +	 	a;e[$ 	a+-m_GDd[$ 	a'-+C00JKe[-*8"-d	5HHZDeT2,j zF r   