
    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ddlZ ee      j                         j                   d   Zedz  dz  dz  Zd	d
dddddddZg dZdZdZdZddZdd	 	 	 	 	 	 	 	 	 	 	 ddZddZedk(  r e        yy)uG  
**Time-Series Momentum (TSMOM) / Managed Futures** backtest.

This is the academic strategy first published by Moskowitz, Ooi, and Pedersen (2012)
and the basis of most systematic CTA / managed-futures funds.

Core rule — for each asset each month:
  - Compute the trailing 12-minus-1-month return (skip most-recent month to avoid
    short-term reversal noise, i.e., use the close 252 trading days ago vs 21 days ago)
  - If the return is positive  → go LONG  that asset
  - If the return is negative  → go SHORT that asset
  - Scale each position so it contributes equal annualized volatility to the portfolio
    (vol-normalized sizing, a.k.a. risk-parity across assets)
  - Rebalance monthly

Universe (8 ETFs across 4 asset class buckets):
  Equity:        SPY (US large cap), EFA (intl developed), EEM (EM equities)
  Fixed income:  TLT (20yr US Treasury), IEF (7-10yr)
  Commodities:   GLD (gold), DBC (broad commodity index)
  FX/Dollar:     UUP (US dollar basket)

Why this works in multiple environments:
  - 2022 (rate-rising bear market): short SPY, short TLT, short EEM, long DBC, long UUP
  - 2020 COVID crash+rally: short equity early, long bonds, then flip to long equity
  - 2008: short equity/credit, long bonds/gold
  - Bull markets: long equity, long credit, short commodities when appropriate
  The key: it does NOT predict direction — it just rides whatever is already trending.

Example::

    cd /Users/robzingale/trading_bot
    PYTHONUNBUFFERED=1 .venv/bin/python \
        RenTech/strategy_stack/run_tsmom_managed_futures.py \
        --start 2016-01-04 --capital 100000
    )annotationsN)Path   RenTechdatalogszUS equities (S&P 500)zIntl developed equitieszEmerging market equitieszUS 20yr Treasury bondszUS 7-10yr Treasury bondsGoldzBroad commodity indexzUS Dollar basket)SPYEFAEEMTLTIEFGLDDBCUUP))?      )~   
   )      r   333333?gQ?c                
   t        j                  | ||dd      }|d   j                         }t        j                  |j
                        j                  d       |_        |j                         }|j                  d      }|S )NTF)startendauto_adjustprogressCloser   )limit)	yfdownloadcopypdto_datetimeindextz_localize
sort_indexffill)tickersr   r   rawcloses        L/opt/rentech/trading_bot/RenTech/strategy_stack/run_tsmom_managed_futures.pydownload_pricesr-   M   sm    
++gUPU
VCLE..-99$?EKEKKaK EL    T)verbosec               ~   t        j                  |       t        j                  d      z
  j                  d      }t	        t        t        j                               ||      }t        D cg c]  }||j                  v s| }}||   }|j                         }	|j                  d      j                         j                  }
t        d t        D              }t        |      }t        j                   d|j                  |      }t        j"                  d|      }|
D ]  }||j                  vr0|j                  |j                  |k     }t        |      d	k(  r=|d
   }|}|j                  j%                  |      }||t&        z   k  rlt        j"                  d|      }d	}t        D ]  \  }}||z
  }||z
  }|d	k  s|d	k  r|j(                  |   }|j(                  |   }||j+                  d	t,        j.                        z  dz
  j1                  d      }|||j3                         j5                         dz   z  z  }|dz  } |d	k(  r.||z  }t-        j6                  |      }|	j(                  t        d	|t&        z
        | }|j9                  d      t-        j:                  d      z  }|j+                  d	t,        j.                        j1                  d      }|t<        |z  z  }|j3                         |z  j?                         } | d	kD  r\t@        | z  }!tC        |!|j3                         j?                         d	kD  r!d|j3                         j?                         z  nd      }!||!z  }|j1                  d      }|
|
|kD     }"t        |"      r|"d	   n|j                  d
   }#|j                  |kD  |j                  |#k  z  }$|jD                  |jF                  |$<    |jI                  d      j1                  d      }t        j                  |       }%|j                  |%k\  }$|r%|$|j                  t        j                  |      k  z  }$|jF                  |$   }&|	jF                  |$   j1                  d      }'|&|'z  j?                  d      }(tK        |      })|)d|(z   jM                         z  }*t        |(      }+|+dz  },tK        |*j(                  d
         }-|-|)z  dz
  }.|,d	kD  r|-|)z  d|,z  z  dz
  n
tK        d      }/|*|*jO                         z  dz
  }0tK        |0jC                               }1tK        |(j9                  d            }2|2dkD  r2tK        |(j5                         |2z  t-        j:                  d      z        n
tK        d      }3|2t-        j:                  d      z  }4d|	j                  v r!|	d   jF                  |$   j1                  d      n t        j"                  d|'j                        }5tK        t        j                   |(|5d      jQ                         jS                         j(                  d         }6g }7|)}8|(jU                  |(j                  jV                        D ]  \  }9}:tK        |8d|:z   jY                         dz
  z        };|8tK        d|:z   jY                               z  }<tK        d|:z   jY                         dz
        dz  }=|7j[                  t]        |9      t_        |=d      t_        |<|8z
  d	      d       |<}8 t        j                   |7      }>|&|&j                  jV                  dk(     }?|'|'j                  jV                  dk(     }@i }A|D ]*  }tK        |?|   @|   z  |)z  j?                               A|<   , ta        |      }|jb                  je                  dd       |&jg                  d	      |)z  j?                  d      }B|& jg                  d	      |)z  j?                  d      }C|Bd z  |Cd!z  z   }Dt        j                   |(j                  j                  d      |(jD                  |(|)z  jD                  |*jD                  |BjD                  |CjD                  |DjD                  d"      }E|Eji                  | d#d       |>ji                  | d$d       i }F|&jU                  |&j                  jV                        D ]G  \  }9}:|:j(                  d
   }G|D ci c]  }|t_        tK        G|         d%       c}Ftk        |9      <   I d&tl         d'|  d(t]        |       }H|rHd)| z  }Hi d*d+d,t        t        j                               d-d.d/d0t<        d1d2t@        d1d3d4d5tk        |(j                  jC                         jo                               d6tk        |(j                  j                         jo                               d7|+d8|)d9t_        |-d      d:t_        |.dz  d;      d<t_        |/dz  d;      d=t_        |3d;      d>t_        |1dz  d;      d?t_        |4dz  d;      d@t_        |6d;      dAAjq                         D IJci c]  \  }I}J|It_        |Jd	       c}J}IFHdB}Kts        | dCdD      5 }Ltu        jv                  K|LdE       d d d        dFjy                  g dGdHjy                  |       dIKd5    dJ|Kd6    dK|+ dL|,dMdN|)dOdPt<        d1dQ|-dRdS|.dz  dTdU|/dz  dTdV|3dWdX|1dz  dTdY|4dz  dTdZ|6dWd[H d\      }Mts        | d]dD      5 }L|Lj{                  M       d d d        |r.t}        M       t}        d^       t}        |>j                  d             t}                t}        d_j                  |)             t        Ajq                         d` a      D ]1  \  }}Nt        j                  |dF      }Ot}        db|dcdb|Ndddb|O        3 t}                t}        de       Fjq                         D ]a  \  }9}P|Pjq                         D Jcg c]  \  }}Jt3        |J      dfkD  s|Jf }Q}}Jdbjy                  dg |QD              }Rt}        db|9 dh|R        c KS c c}w c c}w c c}J}Iw # 1 sw Y   xY w# 1 sw Y   ZxY wc c}J}w )iNr   )yearsz%Y-%m-%dBMEc              3  &   K   | ]	  \  }}|  y w)N ).0lb_s      r,   	<genexpr>zrun_tsmom.<locals>.<genexpr>m   s     8eb!r8s   g        )r%   columns)r%   r   g      ?g:0yE>   )ddofr   g?g      @)axisg     o@nang-q=r
   )pspy)r   r;   Fd   )year
return_pctpnl_usdi  T)parentsexist_ok)lowerr   g333333?)date	daily_retdaily_pnl_usd
equity_usdgross_long_notional_usdgross_short_notional_usd
margin_usdz
_daily.csvz_yearly.csv   zcd zd && PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/run_tsmom_managed_futures.py --start z --capital z --end strategytsmom_managed_futuresuniversesignalz112-minus-1-month return, direction = sign(signal)sizingzvol-norm per asset (z.0%z target), portfolio vol target 	rebalancezmonthly (business month-end)r   r   
n_sessionscapitalending_equity_usdtotal_return_pct   cagr_pctsharpe
max_dd_pctvol_ann_pctrho_spyasset_pnl_2022_usd)weight_snapshots_year_endcommandz
_meta.jsonw)indent zE=== Time-Series Momentum (Managed Futures) Backtest ===
Universe   : z, z
Window     : u    → z  (z sessions, z.2fzy)
Capital    : $z,.0fug   
Signal     : 12-minus-1 month trailing return → long/short
Sizing     : vol-normalized (each asset ~z2 ann vol)
Rebalance  : monthly

Ending equity  : $z>12,.2fz
Total return   : z>8.2fz%
CAGR           : z%
Sharpe         : z>8.3fz
Max drawdown   : z%
Ann. vol       : u   %
ρ(SPY)         : z

Command:
  
z_metrics.txtz=== Return by year ===z<=== 2022 asset-level P&L attribution (${:,.0f} notional) ===c                    | d    S )Nr;   r4   )xs    r,   <lambda>zrun_tsmom.<locals>.<lambda>7  s    AaD5 r.   )keyz  z<5z>+9,.0fzG=== Year-end position snapshot (weight = gross fraction of capital) ===g{Gzt?c              3  2   K   | ]  \  }}| d |d  yw):z+.2fNr4   )r5   tvs      r,   r8   zrun_tsmom.<locals>.<genexpr>>  s      FDAq1#QqhFs   z: )Cr#   	Timestamp
DateOffsetstrftimer-   listUNIVERSEkeysr9   
pct_changeresamplelastr%   maxSIGNAL_LOOKBACKSlen	DataFrameSeriesget_loc
VOL_WINDOWilocreplacenpr>   fillnaabsmeansignstdsqrtTARGET_ASSET_VOLsumPORT_VOL_TARGETminvalueslocshiftfloatcumprodcummaxdropnacorrgroupbyrB   prodappendintroundr   parentmkdirclipto_csvstr_REPOrH   itemsopenjsondumpjoinwriteprint	to_stringformatsortedget)Sr   r   rW   
out_prefixr/   fetch_startpricesrm   r)   retsrebal_datesmax_lookbackNweightsprev_weightsrdpriorposi_nowsignal_votesvalid_lookbacksr6   skipi_pasti_skip	price_now
price_past
ret_signal	directionvol_window_datarealized_volasset_weightgross_vol_estimate
port_scalenext_rebal_datesnext_rdmaskstart_tsrc   rportfolio_retcapeqnr1   end_eq	total_retcagrddmax_ddsdr\   vol_annspy_rr_   yr_rowseq_curyrgr?   end_yret_yyr_dfw22r22asset_pnl_2022gross_long_notionalgross_short_notionalrN   daily_dfweight_snaprw   cmdkrn   metafmetrics_txtpnldescsnaps	positionspos_strsS                                                                                      r,   	run_tsmomr   W   sY    <<&Q)??II*UKT(--/2KEF":Qa6>>&9q:G:G_F D //%(--/55K 8'788LGAll3fllGDG99S0L ;0V\\!LL!34E5zQrB$$S)<*,,
 yyG4( 	!HBRZFT\FzVaZV,IV,J#j&8&8BFF&CCcIQQRUVJ J*..*:*?*?*AD*HIILq O	! a'GGL)	 ))C5:+=$>F&***2RWWS\A#++Arvv6==dC $4|$CD
 +..0<?DDF!(+==JZ<K[K[K]KaKaKcfgKg|/?/?/A/E/E/G)GmpqJJ&L#**3/ '{R'78),-=)>"1%FLLQSDTr!fllg&=>(//Dw;0| mmA%%c*G||E"HLLH$D
c!223DAc"AUKKQK'M .C	m#,,.	.BMAIE2772;Fs"I49AIFSLcEk*S05<D	biik	C	B2668_F	}  a (	)B@BU
U=%%'",rwws|;<PUV[P\F2773<G 27$,,1FDKOOD!((-BIIVYabahahLiEBLL}U!CDKKMRRTYYZ^_`G GF&&}':':'?'?@ A49&QqSJJLQRN:S4T!zz|,,qsjjl1n%c)BuUASXY^agYgijSklm LL!E AGGLLD 
!C
AGGLLD 
!CN C!CFSVOs#:"?"?"ABqC j!JD48 666?S05515=RIIAI.499q9A$t+.BT.IIJ||##,,Z8"))'#-55ii#6#=#=$8$?$? '' H OOzl*-UO;	LLJ<{+5L9 K177<<( NAvvbzELM5tAw#; ;MCGN
 eW 'S\N	4 
 +D) 	E 	()9#(>>]^mnq]rs	
 	3 	]((,,.3356 	s=&&**,1134 	a 	3 	U61- 	E)c/15 	E$*a( 	%" 	eFSL!, 	uWs]A.  	5!$!" 	.:N:N:PQ$!Qq%1+~Q#$ &1'D* 
J'	- %		$!$%   ii !"7m_!"&u+.1234?@Ec{K 4j*
 +;3)?
@ '"# C-&' s(5!"   *U#$ #+e$% 5/"" %#"#K& 
L)3	/ 1	 k&'eooEo*+LSSTWXY^113I 	6FAs<<2&DBqfBs7m2dV45	6 	WX$**, 	(IB,1KKMLDAqSVe^!QLILiiFIFFGBrd"WI&'	(
 K} ;l  N6 R% %,  Ms<   ,r r" rr
rr,r9r9r),r6c                    t        j                  t        j                  d      d         } | j	                  dd       | j	                  dd       | j	                  d	t
        d
       | j	                  dt        t        dz         | j                         }t        |j                  |j                  xs d|j                  |j                  d       y )Nz

r   )descriptionz--startz
2016-01-04)defaultz--endre   z	--capitalg     j@)typer   z--out-prefixrQ   T)r   r   rW   r   r/   )argparseArgumentParser__doc__splitadd_argumentr   r   LOGS
parse_argsr   r   r   rW   r   )apargss     r,   mainr   D  s    		 	 W]]6-B1-E	FBOOI|O4OOGRO(OOKeYO?OONt>U7UOV==?DjjHHN??r.   __main__)r)   z	list[str]r   r   r   r   returnzpd.DataFrame)r   r   r   r   rW   r   r   r   r/   boolr   dict)r   None)r   
__future__r   r   r   syspathlibr   numpyr   pandasr#   yfinancer    __file__resolverE   r   r   rs   ry   r~   r   r   r-   r   r   __name__r4   r.   r,   <module>r     s   "H #   
    X ((+y6!F* $%&$&#	 
 
   jj	j j
 j j 
jZ" zF r.   