+
    j=                       R t ^ RIHt ^ RIt^ RIHt ^ RIHtHt ^ RIH	t	 ^ RI
t^ RIt]! ]4      P                  4       P                   ^,          t]R,          R,          R,          t]R,          R	,          R
,          tR R ltR R ltR R ltR R ltR R ltR R ltR R ltR)R R lltR R ltR*R R llt]R8X  d   ^ RIt^ RI t ]!! ] PD                  4      ^8  dy   ] PD                  ^,          R 8X  da   ]! 4       t#]$! R!4       ]$! ]#PK                  R"R#7      4       ]$! R$]!! ]#4       R%24       ]$! R&4       ]$! ]#R',          PM                  4       4       R# ]! 4       t']$! ]PP                  ! ]'^])R(7      4       R# R# )+u  
regime_monitor.py
=================
Macro regime classifier for the Dual FX Macro Model.

Computes a 0-100 score from 4 components and classifies the current macro
environment into EXPANSION / STABLE / COMPRESSION regimes.

USES:
    - data/raw/rates/us2y.csv, de2y.csv, jp2y.csv (rate data)
    - data/logs/live_monitor_v2.log (recent z-score breaches)

CALL SITES:
    - dashboard_server.py: compute_current_regime() called once per refresh
                            to populate data["regime"] in dashboard_data.json
    - One-off calibration: compute_historical_regime_series() to validate
                            score thresholds against backtest signal counts.

The score is DESCRIPTIVE — it characterises the current macro environment.
It is NOT predictive of next-period signal counts and should not be used
to make trading decisions. Its purpose is psychological context: knowing
whether you are in a quiet or active regime helps interpret trade frequency.

Author: Bennett's FX Quant project. Module added 2026-05-12.
)annotationsN)Path)datetime	timedelta)Optionaldatarawrateslogszlive_monitor_v2.logc                    V ^8  d   QhRRRR/#    spreadfloatreturnint )formats   "HC:\Users\paul_\OneDrive\fx_macro_intraday\src\research\regime_monitor.py__annotate__r   6        	 	U 	s 	    c                    \        V 4      pVR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# ^ # )z1Score the US-DE 2Y spread |level|. Max 30 points.gffffff@g333333@g?g      ?g333333?皙?absr   abs_sps   & r   score_us_de_spread_levelr   6   sD    [F~b~b~b~b~a~ar   c                    V ^8  d   QhRRRR/# )r   vol_30dr   r   r   r   )r   s   "r   r   r   A   s      U s r   c                r    V R8  d   ^# V R8  d   ^# V R8  d   ^# V R8  d   ^# V R8  d   ^# V R8  d   ^# ^ # )z;Score the 30d std of US-DE 2Y daily changes. Max 20 points.gl?gp=
ף?g
ףp=
?gMb?gI+?gMb?r   )r    s   &r   score_us_de_volr"   A   sA    %%%%%%r   c                    V ^8  d   QhRRRR/# r   r   )r   s   "r   r   r   K   r   r   c                    \        V 4      pVR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# VR8  d   ^# ^ # )z1Score the US-JP 2Y spread |level|. Max 20 points.g@g@g@g      ?g      ?r   r   r   s   & r   score_us_jp_spread_levelr%   K   sD    [F~b~b~b~a~a~ar   c                    V ^8  d   QhRRRR/# )r   n_breaches_60dr   r   r   )r   s   "r   r   r   V   s      C C r   c                `    V ^8  d   ^# V ^	8  d   ^# V ^8  d   ^# V ^8  d   ^# V ^8  d   ^# ^ # )zCScore by combined (EU+UJ) z-score breaches in last 60 days. Max 30.r   )r'   s   &r   score_realised_breachesr)   V   s7    BBBAAr   c                    V ^8  d   QhRRRR/# )r   scorer   r   dictr   )r   s   "r   r   r   c   s      3 4 r   c                Z    V ^F8  d   RRRRRRRR/# V ^(8  d   RR	RR
RRRR/# RRRRRRRR/# )zGMap a 0-100 score to a regime label, colour, and frequency expectation.label	EXPANSION	indicatoru   🟢	frequencyz.8-12 signals/month combined (~2-3 trades/week)tonez8Active macro environment. Signals should fire regularly.STABLEu   🟡z-4-7 signals/month combined (~1-2 trades/week)z:Mixed macro environment. Normal signal frequency expected.COMPRESSIONu   🔴z*0-3 signals/month combined (<1 trade/week)zMQuiet macro environment. Low signal frequency is expected. Patience required.r   )r+   s   &r   classify_regimer5   c   st    {+&JT	
 	
 {(&IV	
 	
 	mfBe	 r   c               $    V ^8  d   QhRRRRRR/# )r   filenamestrcolr   pd.DataFramer   )r   s   "r   r   r   }   s&     Q Q Q3 Q< Qr   c                   \         V ,          p\        P                  ! V4      pVP                   Uu. uF  qDP	                  4       NK  	  upVn        \        P
                  ! VP                  R,          RR7      VR&   \        P                  ! VP                  R,          RR7      W1&   VRV.,          P                  4       P                  R4      P                  RR7      # u upi )NNNcoerce)errorsdateTdrop)r<       )r<      )	RATES_DIRpdread_csvcolumnslowerto_datetimeiloc
to_numericdropnasort_valuesreset_index)r7   r9   pathdfcs   &&   r   
_load_raterR   }   s    xD	T	B%'ZZ0Z'')Z0BJh?BvJrwwt}X>BGvsm##%11&9EE4EPP 1s   C!c                   V ^8  d   QhRR/# )r   r   r:   r   )r   s   "r   r   r      s     / /l /r   c            	       a \        RR4      p \        RR4      p\        RR4      p\        V R,          P                  4       VR,          P                  4       VR,          P                  4       4      p\        V R,          P                  4       VR,          P                  4       VR,          P                  4       4      p\        P                  ! R\        P
                  ! W4RR	7      /4      oV3R
 lpV! V R4      P                  V! VR4      RR7      P                  V! VR4      RR7      pVR,          VR,          ,
          VR&   VR,          VR,          ,
          VR&   VP                  4       P                  RR7      # )zEBuild daily US-DE 2Y and US-JP 2Y spread series with forward-filling.zus2y.csvus2yzde2y.csvde2yzjp2y.csvjp2yr?   Dfreqc                |   < \         P                  ! SP                  R R/4      V P                  R R/4      R RR7      # )r?   zdatetime64[us]backward)on	direction)rE   
merge_asofastype)rP   r9   datess   &&r   asof"_build_spread_series.<locals>.asof   s=    }}LL&"234IIv/01- 	-r   )r]   us_de_2yus_jp_2yTr@   )	rR   maxminrE   	DataFrame
date_rangemergerL   rN   )rU   rV   rW   startendrb   outra   s          @r   _build_spread_seriesrn      s7   j&)Dj&)Dj&)DV  "DL$4$4$6V8H8H8JKEV  "DL$4$4$6V8H8H8JKCLL&"--"EFGE- tV

"
"4f#5&
"
A"U4f#5&UA &kCK/C
O&kCK/C
O::<###..r   c                    V ^8  d   QhRRRR/# )r   window_daysr   r   r   )r   s   "r   r   r      s       S r   c                   \         P                  4       '       g   ^ #  \        P                  ! 4       \	        V R7      ,
          p^ p\
        P                  ! R4      p\        \         RRR7      ;_uu_ 4       pV F  pVP                  V4      pV'       g   K   \        P                  ! VP                  ^4      R4      pYq8  d   KM  \        TP                  ^4      4      p\        TP                  ^4      4      p	\        T4      T	8  g   K  T^,          pK  	  RRR4       V#   \         d     K  i ; i  + '       g   i     T# ; i  \         d     ^ # i ; i)zHCount combined EU+UJ z-score threshold breaches in the live monitor log.)daysus   ^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d),\d+ \[INFO\] \[(EURUSD|USDJPY)\] Z-score: (-?\d+\.\d+) \(threshold ±(\d+\.\d+)\)zutf-8replace)encodingr>   z%Y-%m-%d %H:%M:%SN)LOG_FILEexistsr   nowr   recompileopenmatchstrptimegroup
ValueErrorr   r   	Exception)
rp   cutoffnpatflinemtszthreshs
   &         r   _count_breaches_in_logr      s   ??)"==jj@
 (WY??1IIdO!**1771:7JKB ;!''!*%qwwqz*q6V#FA  @  "  @?  s[   AE	 6!D5&D#>A	D5D5	E	 #D2	.D51D2	2D55E	 E	 E	 	EEc                   V ^8  d   QhRR/# )r   r   r,   r   )r   s   "r   r   r      s     M
 M
 M
r   c                 b    \        4       p V P                  R,          p\        VR,          4      p\        VR,          4      pV P                  ^<4      P	                  4       pVR,          P                  4       VR&   \        VR,          P                  ^4      P                  4       4      p\        P                  ! V4      '       g   Rp\        ^<R7      p\        V4      p\        V4      p\        V4      p	\        V4      p
Wx,           V	,           V
,           p\        V4      pR pR\        V4      RVR,          R	VR	,          R
VR
,          RVR,          RRR\!        V^4      RVR^RV! V^4      RR/RR\!        V^4      RVR^RV! V^4      RR/RR\!        V^4      RV	R^RV! V	^4      RR/RRVRV
R^RV! V
^4      RR//R\"        P$                  ! 4       P'                  4       /#   \(         d?   pR^ RRR	RR
RRRT 2R/ R\"        P$                  ! 4       P'                  4       /u Rp?# Rp?ii ; i)u  Compute the current macro regime score and classification.

Returns a dict ready to write into dashboard_data.json["regime"]:
  {
    "score":      <int 0-100>,
    "label":      "EXPANSION" | "STABLE" | "COMPRESSION",
    "indicator":  "🟢" | "🟡" | "🔴",
    "frequency":  "...",
    "tone":       "...",
    "components": {
        "us_de_spread":   {"value": 1.27, "score": 12, "max": 30, "status": "..."},
        "us_de_vol_30d":  {"value": 0.04, "score": 10, "max": 20, "status": "..."},
        "us_jp_spread":   {"value": 2.51, "score": 10, "max": 20, "status": "..."},
        "breaches_60d":   {"value": 9,    "score": 24, "max": 30, "status": "..."},
    },
    "timestamp":  "<ISO>",
  }

On any failure, returns a graceful fallback dict so the dashboard never breaks.
rd   re   us_de_change        )rp   c                L    W,          pVR 8  d   R# VR8  d   R# V^ 8  d   R# R# )g      ?Highg?NormalLowzVery lowr   )r+   max_ptspcts   && r   
status_for*compute_current_regime.<locals>.status_for   s'    /Cd{6d{8ax5r   r+   r.   r0   r1   r2   
componentsus_de_spreadvaluerf   statusunit%us_de_vol_30d us_jp_spreadbreaches_60d	timestampUNKNOWNu   ⚪u;   Unable to compute regime — check rate data and log files.zRegime calculator failed: N)rn   rJ   r   tailcopydiffstdnpisfiniter   r   r"   r%   r)   r5   r   roundr   rw   	isoformatr   )spreadslatestr   r   recentr    r   s_levels_vols_us_jps_breachtotalregimer   es                  r   compute_current_regimer      s   *8
&( b!VJ/0VJ/0 b!&&(!'
!3!8!8!:~~.33B7;;=>{{7##G-"= -\:#G,,\:+L9Og-8 '	 #e*&/&-&-&.'5q+A7GV[]_aikuv}  @B  lC  FL  NQ  "R'5!+<GUW\^`bjlvw|  A  mB  GM  OQ  "R'5q+A7GV[]_aikuv}  @B  lC  FL  NQ  "R'<GXW\^`bjlvw  BD  mE  GM  OQ  "R	 (,,.224
 	
  	
!)%W6qc:"(,,.224
 	
	
s   G"G% %H.03H)#H.)H.c               $    V ^8  d   QhRRRRRR/# )r   
start_yearr   end_yearr   r:   r   )r   s   "r   r   r     s&     ( ((( (r   c                `   \        4       pVR,          P                  4       VR&   \        P                  ! V  R2V R2RR7      p. pV EFM  pW"R,          V8*  ,          p\	        V4      ^<8  d   K(  \        VR,          P                  R,          4      p\        VR,          P                  R,          4      p\        VR,          P                  ^4      P                  4       4      p	\        P                  ! V	4      '       g   R	p	\        V4      p
\        V	4      p\        V4      pW,           V,           pVP                  R
VR\        V^4      R\        V	^4      R\        V^4      RVR\!        \#        V^d,          ^F,          4      4      R,          /4       EKP  	  \        P$                  ! V4      # )a8  Compute monthly regime scores across history for threshold calibration.

For each month, this computes what the regime score WOULD have been on
the first day of the month, using only data available at that point.
Returns a DataFrame with columns:
  month, us_de_spread, us_de_vol_30d, us_jp_spread, score, label
rd   r   z-01-01z-12-01MSrY   r?   re   r   monthr   r   r   score_no_breachlabel_no_breachr.   r   )rn   r   rE   ri   lenr   rJ   r   r   r   r   r   r"   r%   appendr   r5   r   rh   )r   r   r   monthsrowsm_startsnapus_deus_jpvol30r   r   r   total_no_breachs   &&            r    compute_historical_regime_seriesr     ss    #$G%j1668GN]]j\0XJf2EDQFDv'12t9r>d:&++B/0d:&++B/0d>*//3779:{{5!!E+E2"5)+E2!/G3WU5!_U5!_U5!_s?S3H23M/NOPWX
 	! 0 <<r   __main__	calibratez8
Historical monthly regime scores (no-breach component):F)index
z months computed.z
Label distribution:r   )indentdefault)<   )i  i  )*__doc__
__future__r   rx   pathlibr   r   r   typingr   numpyr   pandasrE   __file__resolveparents	BASE_PATHrD   ru   r   r"   r%   r)   r5   rR   rn   r   r   r   __name__jsonsysr   argvrP   print	to_stringvalue_countsresultdumpsr8   r   r   r   <module>r      sH  2 # 	  (   
 N""$,,Q/	&0	'*??		4Q/,DM
d(Z z
388}qSXXa[K7-/IJblll'(3r7),-.%&b"#0023')djj378 r   