+
    ~_j}}                       R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t^ RI
Ht ^ RIHtHtHtHt ^ RIt^ RIt^ RIt]! ]4      P-                  4       P.                  ^,          t]! ]R,          4      ]P4                  9  d*   ]P4                  P7                  ^ ]! ]R,          4      4       R/ RRbRRbR	R
bR^4bRRbRRbRRbR^bR^bR^<bRRbRRbRRbR^bRRbR. RObRRbR . ROR"RR#R$R%R&R'R(R)R*R+^/CR,/ RR,bRRbR	R
bR^bRR-bRR.bRRbR^bR^bR^bR^bRR/bR. RObRR0bR R!R1.bR"RbR#R2bR3R4R'R5R)R6R+^/C/tR7t^xt^t]R8,          R9,          R:,          t ]R8,          R;,          t!]!PE                  RRR<7       Rs#]PH                  ! ]PJ                  R=]PL                  ! 4       ]PN                  ! ]!R>,          R?R@7      .RA7       ]PP                  ! ])4      t*]PV                  ! RBRC4      t,]PV                  ! RDRE4      t-Rs.RF t/RG t0RH t1RI t2]PV                  ! RJRK4      t3Rs4/ t5RL t6RM t7RN t8RO t9RRP lt:RQ t;RRR RS llt<RT RU lt=^ s>/ t?RV t@RRW ltARX tBRY tC] U u/ uF
  q ]B! 4       bK  	  up tD] U u/ uF
  q ]C! 4       bK  	  up tERZR/tF/ tGR[ tHR\ tIR] tJR^ tKR_ tLRR` ltMRa tNRRb ltORRc ltPRd tQRe tRRf tSRg tTRh tURi tVRj tW. ROtXRk tYRl tZRm t[Rn t\Ro t]Rp t^RRq lt_Rr t`Rs taRt tbRRu ltcRv tdRw teRxtfRy tgRz thR{ tiR| tjR} tkR~ tlR tmR tnR toR tpR tqR trRR ltsR ttR tu]v! 4       twR txR tyR tz])R8X  d
   ]z! 4        R# R# u up i u up i )u  
live_signal_monitor_v3.py  (v3.1 session-window patch, 2026-07-04)
==========================
Unified live execution monitor — EURUSD + USDJPY macro mean reversion models.

v3.3 PATCH (2026-07-04) — EU conditional boost + tail guard:
  1. EURUSD sized x boost_mult (2.0) at fill when no USDJPY position open.
     Validated by eu_boost_mc_v3 (true point-in-time intraday equity):
     0 daily-limit breach days in 21yrs, QUIET pass 95-100%. Config:
     boost_mult / boost_when_uj_flat in EURUSD PAIR_CONFIG.
  2. DAILY_LOSS_HALT -$4,000: blocks new entries after an unprecedented
     realized-loss day (0 backtest triggers). Not a validated-model rule —
     a protective circuit breaker that is inert on-model.
  NOTE: Telegram follower tier lots scale from the computed lot, so
  follower alerts inherit the boost automatically.

v3.2 PATCH (2026-07-04) — UJ P&L reconciliation fixes:
  1. USDJPY entry cooldown: one entry per 24h from entry (matches the
     validated backtest's last_exit rule). Prevents same-day re-entry on
     the persistent daily z signal (Apr 30 2026: 3 live entries vs 1
     backtest trade, -$2,023 excess). EURUSD unaffected (its backtest
     re-enters within clusters).
  2. USDJPY session end 17 -> 15 EET (exact match to generator gate).

v3.1 PATCH (2026-07-04) — frequency-audit fix:
  Session windows corrected to the validated backtest's actual entry hours,
  which are EET (not UTC — the trade lists inherit the 'Time (EET)' data
  convention with no conversion anywhere in the pipeline):
    • EURUSD: 9-18 EET -> 7-16 EET  (backtest entries 07:00-16:59 EET)
    • USDJPY: 7-17 EET -> 5-17 EET  (45% of backtest entries at 05-06 EET)
  Evidence: parity_replay_v1.py entry-hour histograms, 2026-07-04.

CHANGES FROM v2 (2026-05-13):
  EURUSD signal generation switched from 1H formula to validated 15M formula:
    • Signal cadence: every 15 minutes (was hourly)
    • Z-score computation: on 15M bars, pct_change(24) = 6h return,
      rolling(60) = 15h window (was 24h return / 60h window on 1H data)
    • Session window: see v3.1 patch above (v3's "UTC 7-16" claim was wrong)
    • All other infrastructure unchanged: MT5, telegram, watchdog,
      position management, dashboard hooks, daily summaries

USDJPY UNCHANGED:
  USDJPY uses daily spread z-score (already matches backtest in v2).
  USDJPY signal check remains hourly.

Architecture: PAIR_CONFIG drives everything.
  • Each pair has its own parameters, state, armed signal, and trade log.
  • EURUSD uses the rolling-beta lag z-score on 15M bars (Formula A).
  • USDJPY uses the simple spread z-score (US-JP 2Y, z30) — unchanged.
  • Both share: MT5 connection, rate scheduler, Telegram, dashboard state.

Pairs:
  EURUSD  z±2.75  TP=0.20%  SL=0.25%  hold=52h  z_exit=±1.5  signal every 15m
  USDJPY  z±2.00  TP=0.70%  SL=0.40%  hold=24h  z_exit=off   signal hourly

Run from project root:
  python src/execution/live_signal_monitor_v3.py
N)Path)datetimetimezone	timedeltadatesrcEURUSDsymbol	threshold      @fibgx&?
hold_hoursstop_pctg{Gzd?tp_pctgMb`?z_exit      ?session_startsession_endz_windowentry_cooldown_hours
boost_mult       @boost_when_uj_flatTreturn_lookbacksignal_timeframe15msizing_bandspip_size-C6?
rate_filesus2y.csvspread_colssignal_typebeta_model_15mbars_15mi:  	trade_logztrade_history_eurusd.json
state_filezstate_eurusd.jsondpUSDJPYgMbp?gy&1|?1h{Gz?jp2y.csvspread_zscorebars_1hi,  ztrade_history_usdjpy.jsonzstate_usdjpy.jsongQ~?datarawrateslogsparentsexist_okz'%(asctime)s [%(levelname)s] %(message)szlive_monitor_v2.logutf-8encoding)levelformathandlersTELEGRAM_TOKENz.8794831706:AAGzqHNhyvfWL2lG_kCQUupacilRf7GfNSITELEGRAM_CHAT_ID
1031071259c                 8    \         f   \        R,          s \         # )zNResolve the recipients file path (needs LOG_DIR which is set later in module).ztelegram_recipients.json)_TG_RECIPIENTS_FILELOG_DIR     YC:\Users\Administrator\OneDrive\fx_macro_intraday\src\execution\live_signal_monitor_v3.py_get_recipients_filerD      s     "%(BBrB   c                    \         '       d   \        \         4      P                  4       MRp \         '       dT   \        \         4      P                  R4       Uu. uF*  qP                  4       '       g   K  VP                  4       NK,  	  upM. p\	        V4      p \        4       pVP                  4       '       d   \        P                  ! VP                  RR7      4      pVP                  R. 4       FG  p\        VP                  RR4      4      P                  4       pV'       g   K6  VP                  V4       KI  	  \        T4      # u upi   \         d#   p\        P                  RT 24        Rp?L8Rp?ii ; i)	z
Return list of active chat IDs (strings). Always includes admin.
File format: {"recipients": [{"chat_id": "123", "added_at": "...", "name": "..."}]}
N,r5   r6   
recipientschat_id zRecipients file read failed: )r<   strstripsplitsetrD   existsjsonloads	read_textgetadd	Exceptionlogwarningsorted)	admincenv_listall_idspathr.   rcides	            rC   _load_recipientsr`      s   
 .>-=C !'')4EUeUe3'7#8#>#>s#CQ#Cawwy		#CQkmH(mG9#%;;==::dnngn>?DXXlB/!%%	2./5573C( 0
 '? R  93A37889s+   E4EB	E 'E E9E44E9c                    \        4       pRV R\        P                  ! 4       P                  4       /pVP	                  R4      pVP                  \        P                  ! V^R7      RR7       ^ RIpVP                  \        V4      \        V4      4       R#   \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)	z;Write recipients file. Atomic write to avoid partial reads.rG   
updated_at.tmpindentr5   r6   NzRecipients file save failed: )rD   r   now	isoformatwith_suffix
write_textrO   dumpsosreplacerJ   rT   rU   rV   )recipients_listr\   payloadtmp_osr_   s   &     rC   _save_recipientsrq      s    	9#%!9!9!;=v&tzz'!4wGCHc$i( 93A37889   BB C"C  Cc                    \         '       d4   \        \         4      P                  R4      ^ ,          P                  4       MRpT;'       d    \        V 4      P                  4       V8H  # )z8Only the configured admin chat ID can manage recipients.rF   N)r<   rJ   rL   rK   )rH   rX   s   & rC   	_is_adminrt      sN    ;K;KC !'',Q/557QUE22S\'')U22rB   DASHBOARD_BASE_URLz,https://bennettsdashboard.com/dashboard.htmlc                  8    \         f   \        R,          s \         # )Nzdashboard_tokens.json)_TG_DASHBOARD_TOKENS_FILEr@   rA   rB   rC   _get_dashboard_tokens_filerx      s     ($+.E$E!$$rB   c                     \        4       p V P                  4       '       g   . # \        P                  ! V P	                  RR7      4      pVP                  R. 4      #   \         d%   p\        P                  RT 24       . u Rp?# Rp?ii ; i)zCReturn list of dicts: {chat_id, name, token, granted_at, is_admin}.r5   r6   tokensz#Dashboard tokens file read failed: N)	rx   rN   rO   rP   rQ   rR   rT   rU   rV   )r\   r.   r_   s      rC   _load_dashboard_tokensr{      sr    )+{{}}Izz$..'.:;xx"%% 9!=>	s"   !A 7A B'B BBc                    \        4       pRV R\        P                  ! 4       P                  4       /pVP	                  R4      pVP                  \        P                  ! V^R7      RR7       ^ RIpVP                  \        V4      \        V4      4       R#   \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)	z&Atomic write of dashboard tokens file.rz   rb   rc   rd   r5   r6   NzDashboard tokens save failed: )rx   r   rf   rg   rh   ri   rO   rj   rk   rl   rJ   rT   rU   rV   )tokens_listr\   rn   ro   rp   r_   s   &     rC   _save_dashboard_tokensr~     s    	:)+[!9!9!;=v&tzz'!4wGCHc$i( :4QC899:rr   c                 ,    ^ RI p V P                  ^4      # )z)Generate a 16-char URL-safe random token.N)secretstoken_urlsafe)r   s    rC   _generate_dashboard_tokenr     s      $$rB   c                   \        4       p\        V 4      P                  4       p V F5  p\        VP                  R4      4      V 8X  g   K$  VP                  R4      u # 	  \	        4       pTP                  RT RT;'       g    RRVR\        P                  ! 4       P                  4       R\        V4      /4       \        V4       V# )zFGrant (or refresh) a dashboard token for a chat_id. Returns the token.rH   tokennamerI   
granted_atis_admin)r{   rJ   rK   rR   r   appendr   rf   rg   boolr~   )rH   r   r   rz   tr   s   &&&   rC   _grant_dashboard_tokenr     s    #%F'l  "GquuY G+55>!  &'E
MMgdjjbehlln..0d8n  6"LrB   c                   \        4       p\        V 4      P                  4       p V Uu. uF&  p\        VP                  R4      4      V 8w  g   K$  VNK(  	  pp\	        V4      \	        V4      8  d   \        V4       R# R# u upi )z<Remove a chat_id's dashboard token. Returns True if removed.rH   TF)r{   rJ   rK   rR   lenr~   )rH   rz   r   
new_tokenss   &   rC   _revoke_dashboard_tokenr   -  sk    #%F'l  "G#HVs155+;'<'G!!VJH
:V$z*	 Is   !A>A>c                $    V ^8  d   QhR\         /#    messagerJ   )r9   s   "rC   __annotate__r   7  s     "2 "23 "2rB   c                t   \         '       g   R# Vf   \        4       pMH\        V\        \        \
        34      '       d   V Uu. uF  p\        V4      NK  	  ppM\        V4      .pV'       g   R#  ^ RIpR\          R2pVP                  4       pRVn	        VP                  Vn        V F  p RV R\        P                  P                  V 4       R2p\        P                  P!                  WXP#                  4       RR	7      p	\        P                  P%                  V	^
VR
7      ;_uu_ 4         RRR4       K  	  R# u upi   + '       g   i     K  ; i  \&         d'   p
\(        P+                  RT RT
 24        Rp
?
K  Rp
?
ii ; i  \&         d$   p
\(        P+                  RT
 24        Rp
?
R# Rp
?
ii ; i)u   
Send message to all registered recipients.
Pass explicit chat_ids (list or single) to target specific recipients
instead — used by command handlers.
Nhttps://api.telegram.org/bot/sendMessageFchat_id=&text=&parse_mode=HTMLPOSTr.   methodtimeoutcontextTelegram send to 	 failed: Telegram send failed: )r;   r`   
isinstancelisttuplerM   rJ   sslcreate_default_contextcheck_hostname	CERT_NONEverify_modeurllibparsequoterequestRequestencodeurlopenrT   rU   rV   )r   chat_idstargetsrY   r   urlctxr^   r.   reqr_   s   &&         rC   send_telegramr   7  sx    >"$	HtUC0	1	1#+,8a3q68,x=/2,^,<LI((*" ]]CC"3% !3!3G!< =*, ~~--cf-U^^++CS+II JI  -$ JII C/uIaSABBC 2,QC0112sm   D<1;F	 -A?E,E.E6F	 EEF	 EF F;F	 FF	 	F7F22F7c                $    V ^8  d   QhR\         /# r   r   )r9   s   "rC   r   r   [  s       rB   c                   \         '       d	   V'       g   R# \        V\        \        \        34      '       d   V Uu. uF  p\        V4      NK  	  upM\        V4      .p^ p^ p ^ RIpR\          R2pVP                  4       pRVn        VP                  Vn
        V F  p	 RV	 R\        P                  P                  V 4       R2p
\        P                  P                  WzP!                  4       RR	7      p\        P                  P#                  V^
VR
7      ;_uu_ 4         RRR4       V^,          pK  	  WE3# u upi   + '       g   i     L%; i  \$         d0   pT^,          p\&        P)                  RT	 RT 24        Rp?K  Rp?ii ; i  \$         d%   p\&        P)                  RT 24        Rp?YE3# Rp?ii ; i)zy
Like send_telegram but returns (success_count, fail_count).
Used by /announcement command to confirm delivery to admin.
Nr   r   Fr   r   r   r   r   r   r   r   r   )    r   )r;   r   r   r   rM   rJ   r   r   r   r   r   r   r   r   r   r   r   r   rT   rU   rV   )r   r   rY   r   successfailr   r   r   r^   r.   r   r_   s   &&           rC   send_telegram_with_resultsr   [  s   
 >,6x$sAS,T,Tx(x!s1vx([^_g[hZiGGD2,^,<LI((*" ]]C
C"3% !3!3G!< =*, ~~--cf-U^^++CS+II J1  =/ ) JI  C	/uIaSABBC  2,QC011=2s`   D9$;F  A?ED>!E2F >E	EF$F F FF F=F88F=c                    \         '       g   R#  ^ RIp R\          R2pR\        ^,            R2pV P                  4       pRVn        V P
                  Vn        \        P                  P                  W,           RR7      p\        P                  P                  V^VR	7      ;_uu_ 4       p\        P                  ! VP                  4       P                  R
4      4      pRRR4       XP                  R4      '       g   R# VP                  R. 4       EF  p\!        \        VP                  R^ 4      4      sVP                  R4      ;'       g    VP                  R4      pV'       g   KX  \#        VP                  R/ 4      P                  RR4      4      p	VP                  R4      ;'       g    RP%                  4       p
VP                  R/ 4      P                  RR4      pVP                  R/ 4      P                  RR4      pV	'       g   K  V
'       g   EK  \'        WW4       EK  	  R#   + '       g   i     ELT; i  \(         d$   p\*        P-                  RT 24        Rp?R# Rp?ii ; i)z
Poll Telegram's getUpdates for incoming messages and process commands.
Runs every 30 seconds in the scheduler. Always non-blocking.
Nr   z/getUpdatesz?timeout=1&offset=z	&limit=20FGET)r   r   r5   okresult	update_idr   channel_postchatidrI   textfrom
first_nameusernamezTelegram poll failed: )r;   r   _telegram_update_offsetr   r   r   r   r   r   r   r   rO   rP   readdecoderR   maxrJ   rK   _handle_telegram_commandrT   rU   debug)r   r   paramsr   r   respbodyupdatemsgrH   r   r   r   r_   s                 rC   poll_telegram_commandsr     s    >0,^,<KH%&=&A%B)L((*" ]]nn$$S\%$@^^##CC#@@D::diik009:D Axx~~hhx,F&)*A6::k[\C]&^#**Y'EE6::n+EC#''&"-11$;<Gwwv,,"335D,00rBJ,00R@Hw44(
M -	 A@@  0		*1#.//0sW   BH; (4H'H; =AH; H; %AH; *AH; 	H; H; 'H8	2	H; ;I)I$$I)c                  a a( VP                  4       pVP                  4       pV'       d   V^ ,          P                  4       MRpVR,          p\        S 4      pT;'       g    T;'       g    RpVR~9   d^   V'       d   \	        RS .R7       R	# S \        4       9   p	Rp
V	'       d   \	        RV
,           S .R7       R	# \	        RV
,           S .R7       R	# VR
8X  d   \	        RS  RV 2S .R7       R	# VR8X  d   RVR\        P                  ! 4       P                  4       /\        S &   \        \        4      P                  R4      ^ ,          P                  4       pV'       d   \	        RV RS  RS  RV R2	V.R7       \	        RS .R7       R	# VR8X  d   \        V 3R l\        4        4       R	4      pV'       d'   \         RVR,           2p\	        RV R2S .R7       R	# RVR\        P                  ! 4       P                  4       /\        S &   \        \        4      P                  R4      ^ ,          P                  4       pV'       d   \	        RV RS  RS  RV R2	V.R7       \	        RS .R7       R	# VR 8X  Ed   V'       g   \	        R!S .R7       R	# V'       g   \	        R"S .R7       R	# V^ ,          P                  4       p\!        V4      ^8  d   RP#                  VR,          4      MRpV'       g)   V\        9   d   \        V,          P%                  RR4      p\'        WR#R$7      p\        P)                  VR	4       \         RV 2p\	        R%V R2V.R7       \	        R&T;'       g    T R'V R(2S .R7       R	# VR)8X  d   V'       g   \	        R!S .R7       R	# V'       g   \	        R*S .R7       R	# V^ ,          P                  4       p\+        V4      '       d   \	        R+V R(2S .R7       R	# \	        R,V R(2S .R7       R	# VR-8X  d   V'       g   \	        R!S .R7       R	# \        4       pV'       g   \	        R.S .R7       R	# R/R0.pV Fy  pVP%                  R14      '       d   R2MRpTP-                  R3VP%                  R4      ;'       g    R4 V R5VP%                  R64       R7VP%                  R8R4      R9,           24       K{  	  \	        R:P#                  V4      S .R7       R	# VR;8X  EdG   \        4       pS V9   Ed   V'       Eg    \/        4       pVP1                  4       '       d'   \2        P4                  ! VP7                  R<R=7      4      MR>. /pVP%                  R>. 4       Uu. uF&  p\        VP%                  R64      4      S 8w  g   K$  VNK(  	  upVR>&   \9        VR>,          4       \	        R?S .R7       \        \        4      P                  R4      ^ ,          P                  4       pV'       d   \	        R@V RAS  RB2V.R7       R	# V'       d   \	        RDS .R7       R	# \	        RES .R7       R	# VR9   d   V'       g   \	        RKS .R7       R	# VRH8X  Ed   \        4       p \/        4       pVP1                  4       '       d'   \2        P4                  ! VP7                  R<R=7      4      MR>. /pVP%                  R>. 4       Uu/ uF  p\        VP%                  R64      4      VbK   	  ppV'       g   \	        RLS .R7       R	# RMR0.pV F  p\        V4      '       d   RNMRpVP%                  V/ 4      pVP%                  RR4      pVP%                  RO4      '       d   VP%                  ROR4      R9,          MRpRPV R(2pV'       d   VRQV 2,          pV'       d   VRRV 2,          pVV,          pVP-                  V4       K  	  VP-                  R04       VP-                  RS\!        V4       24       \	        R:P#                  V4      S .R7       R	# VRI8X  d   \        '       g   \	        RTS .R7       R	# RUR0.p\        PA                  4        Fs  w  ppVP%                  RR4      pVP%                  RR4      RV,          PC                  RWR4      p VP-                  RPV RXV RQV  24       VP-                  RYV RV R(24       Ku  	  \	        R:P#                  V4      S .R7       R	# VRF8X  Ed   V'       g   \	        RZS .R7       R	# V^ ,          P                  4       o(\!        V4      ^8  d   RP#                  VR,          4      MRp \/        4       pVP1                  4       '       d'   \2        P4                  ! VP7                  R<R=7      4      MR>. /pVP%                  R>. 4      p\D        ;QJ d    V(3R[ lV 4       F  '       g   K   R\M	  R#M! V(3R[ lV 4       4      '       d   \	        R]S( R^2S .R7       R	# V'       g)   S(\        9   d   \        S(,          P%                  RR4      pVP-                  R6S(RVRO\        P                  ! 4       P                  4       R_S /4       \9        V4       \        P)                  S(R	4       \	        R`S( R(2V'       d   RaV Rb2MR,           Rc,           S .R7       \	        RdS(.R7       R	# VRG8X  Ed^   V'       g   \	        RfS .R7       R	# V^ ,          P                  4       p!\        V!4      '       d   \	        RgS .R7       R	#  \/        4       pVP1                  4       '       d'   \2        P4                  ! VP7                  R<R=7      4      MR>. /p\!        VP%                  R>. 4      4      p"VP%                  R>. 4       Uu. uF&  p\        VP%                  R64      4      V!8w  g   K$  VNK(  	  upVR>&   \!        VR>,          4      p#V"V#8X  d   \	        R]V! Rh2S .R7       R	# \9        VR>,          4       \	        RiV! Rj2S .R7       \	        RkV!.R7       R	# VRJ8X  d   V\!        V4      R	 P                  4       p$\!        V$4      ^8  d   \	        RmS .R7       R	# \        4       pV'       g   \	        RnS .R7       R	# RoV$ 2p%\	        Rp\!        V4       Rq2S .R7       \G        V%V4      w  p&p'V'^ 8X  d   \	        RrV& Rs2S .R7       M\	        RtV& RuV&V',            RvV' Rw2S .R7       \<        PI                  RxV& RyV' RzV$R{,           24       R	# VPK                  Ru4      '       d   \	        R|V R}2S .R7       R	# R	# u upi   \:         d$   p\<        P?                  RCT 24        R	p?R	# R	p?ii ; iu upi   \:         d    / p ELi ; i  \:         d   p\	        ReT 2S .R7        R	p?R	# R	p?ii ; iu upi   \:         d   p\	        RlT 2S .R7        R	p?R	# R	p?ii ; i)z+Process a single incoming Telegram command.rI      NNunknownu  🔐 <b>Admin commands available</b>
────────────────
/list — show all recipients
/add &lt;chat_id&gt; [name] — grant alert access
/remove &lt;chat_id&gt; — revoke alert access
/pending — see alert requests
/announcement &lt;message&gt; — broadcast to everyone
────────────────
📺 <b>Dashboard access</b>
/listdashboard — show dashboard recipients
/grantdashboard &lt;chat_id&gt; [name] — grant dashboard URL
/revokedashboard &lt;chat_id&gt; — revoke dashboard access
────────────────
/whoami — your chat ID
────────────────
<i>Commands must come from your admin chat only.</i>)r   ur  
────────────────
⚠️ <b>DISCLAIMER</b>

This bot is provided for <b>educational and informational purposes only</b>.

The signals, alerts, and commentary shared here are the output of a systematic quantitative model and <b>do not constitute financial advice</b>, investment advice, trading advice, or a recommendation to buy or sell any security or currency pair.

Past performance of the model in backtesting or paper trading is not indicative of future results. Systematic trading involves substantial risk of loss. You should consult with a licensed financial advisor before making any investment decisions.

The bot operator accepts no liability for any losses incurred from acting on information shared via this bot. <b>You are solely responsible for your own trading decisions.</b>

By using this bot you acknowledge and accept these terms.u   ✅ <b>You're subscribed to alerts.</b>
────────────────
Available commands:
/whoami — see your chat ID
/leave — stop receiving alerts
/requestdashboard — request dashboard URL
/help — show this message againu*  👋 <b>Welcome</b>
────────────────
You are NOT currently authorised to receive alerts.

Available commands:
/requestaccess — request access to trade alerts
/requestdashboard — request dashboard URL
/whoami — see your chat ID
/help — show this message againNz/whoamizYour chat ID: <code>z</code>
Name: z/requestaccessr   requested_atrF   uR   📥 <b>ACCESS REQUEST</b>
────────────────
From: z
Chat ID: <code>uP   </code>
────────────────
To approve: <code>/add  z</code>
To ignore: do nothinguE   ✅ Access request sent to the owner.
You'll be notified if approved.z/requestdashboardc              3   j   <"   T F(  p\        VP                  R 4      4      S8X  g   K$  Vx  K*  	  R# 5irH   NrJ   rR   ).0r   rH   s   & rC   	<genexpr>+_handle_telegram_command.<locals>.<genexpr>  s0      >$<qi 01W< $<s   !3
3z?key=r   uw   ✅ <b>You already have dashboard access</b>
────────────────
Your private link:
<code>u   </code>

<i>This link is personal to you. Do not share it — if it leaks, /requestdashboard again and I can issue a new one.</i>uU   📺 <b>DASHBOARD REQUEST</b>
────────────────
From: u[   </code>
────────────────
To approve: <code>/grantdashboard uO   ✅ Dashboard access request sent to the owner.
You'll be notified if approved.z/grantdashboardzAdmin command only.z:Usage: <code>/grantdashboard &lt;chat_id&gt; [name]</code>F)r   uo   🎉 <b>Dashboard access granted</b>
────────────────
Your private link:
<code>u#   ✅ Dashboard access granted to <b>z</b>
Token: <code>z</code>z/revokedashboardz4Usage: <code>/revokedashboard &lt;chat_id&gt;</code>u'   ✅ Dashboard access revoked for <code>u*   ⚠️ No dashboard token found for <code>z/listdashboardzNo dashboard tokens granted.u   📺 <b>Dashboard access</b>0   ────────────────r   z (admin)u   • z	(no name)z
  ID: <code>rH   z</code>
  Granted: r   N
   N
z/leaver5   r6   rG   uQ   👋 You've been removed from the alert list.
Use /requestaccess to rejoin later.u   ℹ️ z (<code>z</code>) left the alert list.zLeave command failed: z=Admin cannot /leave. Edit TELEGRAM_CHAT_ID env var if needed.z'You're not currently on the alert list./add/remove/list/pending/announcementuF   🚫 That command is admin-only.
Use /requestaccess to request access.zNo recipients configured.u   👥 <b>ACTIVE RECIPIENTS</b>u    👑 ADMINadded_at<code>    · u
    · added zTotal: u    📭 No pending access requests.u   📥 <b>PENDING REQUESTS</b>:N   NTu   </code> · z  Approve: <code>/add zYUsage: <code>/add &lt;chat_id&gt; [name]</code>
Example: <code>/add 987654321 John</code>c              3   ^   <"   T F"  p\        VP                  R 4      4      S8H  x  K$  	  R# 5ir   r   )r   r]   new_ids   & rC   r   r     s$     EHq3quuY'(F2Hs   *-Tu   ℹ️ <code>z</code> is already on the list.added_byu   ✅ Added <code> ()z to the alert list.u9  🎉 <b>You're in!</b>
────────────────
The owner has granted you access to the macro model alerts. You'll start receiving them on the next signal check.

Commands:
/help — show commands &amp; disclaimer
/whoami — your chat ID
/leave — stop receiving alerts
────────────────
⚠️ <b>DISCLAIMER</b>

This bot is provided for <b>educational and informational purposes only</b>. Alerts do <b>not constitute financial advice</b> or a recommendation to buy or sell any security. Past backtest performance is not indicative of future results. Systematic trading involves substantial risk of loss. <b>You are solely responsible for your own trading decisions.</b> By using this bot you acknowledge and accept these terms. Type /help to see the full disclaimer.u   ❌ Add failed: z+Usage: <code>/remove &lt;chat_id&gt;</code>u%   🚫 Cannot remove the admin chat ID.z</code> is not on the list.u   ✅ Removed <code>z</code> from the alert list.ua   👋 You've been removed from the macro model alerts.
Use /requestaccess if you'd like to rejoin.u   ❌ Remove failed: zUsage: <code>/announcement &lt;your message&gt;</code>
Example: <code>/announcement Model paused for 30 min for maintenance</code>

Message must be at least 5 characters.zNo recipients to send to.uU   📣 <b>ANNOUNCEMENT FROM ADMIN</b>
────────────────
u   📢 Broadcasting to <b>z</b> recipient(s)...u    ✅ Announcement sent to all <b>z</b> recipients.u   ⚠️ Announcement sent to <b>/z</b> recipients.
<b>u3   </b> delivery failure(s) — check log for details.zAnnouncement broadcast: z
 success, z fail | text: :NP   NzUnknown command: z
Type /help for options.)z/startz/help)r   r   r   r   r   )&rK   rL   lowerrt   r   r`   r   rf   rg   _pending_access_requestsrJ   r<   nextr{   ru   _pending_dashboard_requestsr   joinrR   r   popr   r   rD   rN   rO   rP   rQ   rq   rT   rU   rV   itemsrl   anyr   info
startswith))rH   r   r   r   partscmdargsr   display_namein_list
disclaimeradmin_idexistingr   	target_idr   r   rz   linesr   	admin_tagrG   r\   r.   r]   r_   richr^   r  addedlinereq_cidwhenrm_idbeforeaftermessage_text	broadcastr   r   r   s)   f&&&                                    @rC   r   r     s   ::<DJJLE %E!HNN2C"ID!H 66z66YL !!G  "#F 	_ !1!33GL * 8 !! &Y
0 	 8 !! &Y 	
i"7) ,!N$Y	

 	
LHLLN446-
 )
 '(..s3A6<<>% '"") +**1!L> B() #	 	.Y	

 	 !! >$:$< >?CE'(hw.?-@AC  VW "  LHLLN4460
#G, '(..s3A6<<>% '"") +55<IQ|n M() #	 	.Y	

 	 /7)DL! GMMO	*-d)a-CHHT"X&R	%@@.y9==fbID&yG#''	48%&eE73 E RS  [	
 	1$2C2C)1D E!7'+Y	

 	   /7)DF! GMMO	"9--9)GL! 		 <YKwO! 	 /7)D')8G9M/1cdA&'eeJ&7&7
RILLquuV}334YK @ uuY/0 1eeL"5c:;=  	dii&';
h%'
j :+-GK{{}}tzz$..'."BC[gikZl15,1K &L1KA*-aeeI.>*?7*J '(a1K &L\" l!34:%Y /066s;A>DDF!!,xy@]^"* 	 Y$+9. 	 CwiX GG8!
 
g~%'
	')DCG;;==4::dnngn>?WcegVhD6:hh|R6PQ6PCi()1,6PDQ 5	J  	 56hiE!-6s^^M	xxR(xx+9=*9M9MR05SUuG,4v.$Jug"66$	!T" " LLKLLL73z?"345$))E*gY?
j''<yQ 	 45ghE!9!?!?!Axx+xx3C8@@cJvgYk$tD6JK5gYavWMN	 "B
 $))E*gY?
f}<!
 a%(Y]sxxR!-	F')DCG;;==4::dnngn>?WcegVhDxxb1HsEHEsssEHEEEfX5TUah`ijF&>>/7;;FBGOO6HLLN446G	  X&$((6"6('2d4&lPRSVkk!
 M$ !'. 	
i=! QUAWIV	I')DCG;;==4::dnngn>?WcegVhD,34F-1XXlB-G "F-G&)!%%	*:&;u&D #$!-G "FD\*+EeW4OP\c[deT,/0.ug5QR^e]fg> 	 oCHI,,.|q 9 "	 %'
5	JAn 	 	&s:&77KLY	

 39jI1927);KL!
 1'!GdN;K LVNP!
 	+G9JtfNS_`cSdRefg s)#.GHT[S\] e&L  :4QC899 	:0 R 	D	p  	F,QC0G9EE	F&"F  	I/s3wiHH	Is   	Au '!u u A6u %Au; $u6'u; A&v (v ?v v 1v 9B?v A9v> !v92v981v> +2v>  u u3u..u36u; ;vvv6v11v69v> >w%	w  w%c                  "    R RRRRRRRRRRRRR/# )	position_openFposition_ticketNposition_signalentry_price
entry_timeposition_lotlast_zscorerA   rA   rB   rC   _blank_stater   n  s/    5444444 rB   c            
          R RRRRRRRRR/# )activeF	directionNtarget_price
armed_time
zscore_absrA   rA   rB   rC   _blank_armedr'  y  s%     rB   r   c                 @    \         \        V ,          R ,          ,          # )r&   )r@   PAIR_CONFIGpairs   &rC   _state_pathr,    s    [&|444rB   c                 
    \        \        V ,          4      pVP                  R 4      '       d   VR ,          P                  4       VR &   VP                  R4      '       d8   \	        VR,          \
        4      '       g   VR,          P                  4       VR&   \        \        V ,          4      pVP                  R4      '       d   VR,          P                  4       VR&   W!R&   \        V 4      pVP                  R4      pVP                  \        P                  ! V^R7      RR7       ^ RIpVP                  \        V4      \        V4      4       R#   \         d'   p\        P!                  R	T  R
T 24        Rp?R# Rp?ii ; i)r  cooldown_untilr%  _armedrc   rd   r5   r6   N[z] State save failed: )dict
PAIR_STATErR   rg   r   rJ   
PAIR_ARMEDr,  rh   ri   rO   rj   rk   rl   rT   rU   rV   )r+  r.   armedtargetro   rp   r_   s   &      rC   
save_stater6    s2   8Jt$%88L!!!%l!3!=!=!?D88$%%j>N9OQT.U.U%)*:%;%E%E%GD!"Z%&99\"""'"5"?"?"AE,XT"  (tzz$q1GDCHc&k* 8av21#6778s   AE  C/E FE==Fc           	         \        V 4      pVP                  4       '       g   R #  \        P                  ! VP	                  RR7      4      pVP                  R4      '       d-    \        P                  ! VR,          4      \        V ,          R&   VP                  R4      '       d   R F"  pVP                  V4      \        V ,          V&   K$  	  VP                  R4      '       d,   \        P                  ! VR,          4      \        V ,          R&   \        P                  RV  R	\        V ,          R,           24       \        R
V  R\        V ,          R,           R\        V ,          R,           R24       VP                  R/ 4      pVP                  R4      '       Ed   VP                  R4      pV'       Ed    \        P                  ! V4      p\        P                  ! 4       V,
          P                  4       R,          pVR8  d   R\        V ,          R&   VP                  R4      \        V ,          R&   VP                  R4      \        V ,          R&   V\        V ,          R&   VP                  R4      \        V ,          R&   \!        RV,
          ^4      p\        P                  RV  RVR R24       R # R # R # R #   \         d     EL<i ; i  \         d'   p	\        P#                  RT  RT	 24        R p	?	R # R p	?	ii ; i)Nz	utf-8-sigr6   r.  r  r  r  r  r0  u   ] State restored — ticket=u   ⚠️ <b>[zG] STATE RESTORED AFTER REBOOT</b>
Position was open at restart
Ticket: z
Entry: z
Resuming monitoring...r/  r"  r%          @Tr#  r$  r&  u   ] Armed signal restored — .1fzh remainingz] State restore failed: )r  r  r  r  r  r  )r,  rN   rO   rP   rQ   rR   r   fromisoformatr2  rT   rU   r  r   utcnowtotal_secondsr3  roundrV   )
r+  pr.   k
armed_dataat_strr%  hours_since	remainingr_   s
   &         rC   
load_staterE    s   DA88::*;
 zz!++{+;<88$%%5=5K5K)*6,
4 !12 88O$$B&*hhqk
4 #B xx%%191G1G\HZ1[
4 .HHq::d;KL];^:_`adV $%d+,=>? @$T*=9: ;)* XXh+
>>(##^^L1Fv%33F;
'0:=LLNQUU$7;Jt$X.7A~~k7RJt$[17A~~n7UJt$^47AJt$\27A~~l7SJt$\2 %cK&7 ;IHHq&B9S/Q\]^ %  $#  <  ;av5aS9::;sU   <K #+J< K %>K $B:K K 9C;K <KK 
KK K?K::K?c                  |   \         P                  ! 4       p \        R ,          V 8X  d   \        P	                  R4       R#  ^ RIHp \        P	                  R4       V! 4       pV \        R &   \        P	                  R4       V#   \         d    h \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)	r   u,   Rate data already updated today — skippingT)update_all_ratesz+Updating rate data (FinanceFlow primary)...z-Rate data updated successfully (US + DE + JP)zRate update failed: NF)
r   todayLAST_RATE_UPDATErU   r  ingestion.auto_rates_loaderrG  KeyboardInterruptrT   error)rH  rG  r   r_   s       rC   update_rate_datarM    s    JJLE5(?@@>?#( @A	  		(,-s    AB B;B;B66B;c                    ^ RI p \        R,          p RpV P                  4       pRVn        V P                  Vn        \        P                  P                  VRR/R7      p\        P                  P                  V^VR7      ;_uu_ 4       pVP                  4       P                  R	R
R7      pRRR4       XP                  4        Uu. uF5  qwP                  4       '       g   K  VP                  R4      '       d   K3  VNK7  	  pp. p	VR,           F  p
V
P                  R4       Uu. uF  qP                  4       NK  	  pp\!        V4      ^8  d   KB   \"        P$                  ! V^ ,          RR7      p\'        V^,          4      pV	P)                  RVP+                  R4      RV/4       K  	  V	'       dl   \"        P0                  ! V	4      P3                  R4      P5                  R4      pVP7                  VRR7       \8        P;                  R\!        V4       R24       R# R#   + '       g   i     EL|; iu upi u upi   \,        \.        3 d     EKD  i ; i  \<         d$   p\8        P?                  RT 24        Rp?R# Rp?ii ; i)u   
Basic JP2Y fallback — used only if fetch_jp2y_robust.py is not deployed.
Prefer fetch_jp2y_robust.update_jp2y_if_stale() which has all EU bugs fixed.
Nr+   z[https://www.mof.go.jp/english/policy/jgbs/reference/interest_rate/historical/jgbcme_all.csvFz
User-AgentzMozilla/5.0)headersr   r5   rl   errors#r   rF   z%Y/%m/%d)r9   r   %Y-%m-%djp2yindexzJP2Y updated: u    rows → jp2y.csvz#JP2Y update failed (using cached): ) r   	RATES_DIRr   r   r   r   r   r   r   r   r   r   
splitlinesrK   r  rL   r   pdto_datetimefloatr   strftime
ValueError
IndexError	DataFramedrop_duplicatessort_valuesto_csvrU   r  rT   rV   )r   	jp2y_pathr   r   r   r   r/   lr  rowsr  r?  r  dtvaldfr_   s                    rC   _update_jp2y_basicri    s   
 J&I?k((*"--~~%%cL-3P%Q^^##CS#AAT))+$$WY$?C BNN,T,q	!,,sBS,T"IID(,

381WWYE85zA~nnU1XjAE!HoVR[[%<fcJK  d#33F;GGOBIIiuI-HH~c"gY.@AB  BAAT 9 
+   ?9!=>>?s   A7I 
"H,I H3"H3;H3#I $H8<I AH=&I 2A)I H0	*I =II II J!I??Jc                    ^ RI p\        R,          R,          R,          R,          R,          pVP                  P                  RRR7       VP	                  RVP
                  ^ V 4      pVe   \        V4      ^ 8X  d*   \        P                  R	VP                  4        R
24       R# \        P                  ! V4      p\        P                  ! VR,          RRR7      P                  P                  R4      VR&   VP                  RR/R7      pV. RO,          P!                  R4      pVP#                  VRR7       R#   \$         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)u  Refresh EURUSD_1H_recent.csv with fresh bars from MT5.

Used by get_bars_1h for EURUSD. Failure to refresh is non-fatal — caller
will still read whatever's in the CSV from the last successful refresh.
Matches the format produced by src/research/export_recent_bars.py exactly.
Nr.   r/   pricesr   EURUSD_1H_recent.csvTr2   z3[EURUSD] recent CSV refresh: MT5 returned no bars (r   Ftimes)unitutcr   tick_volumevolumecolumnsrU  z0[EURUSD] recent CSV refresh failed (non-fatal): )r   openhighlowcloserr  )MetaTrader5	BASE_PATHparentmkdircopy_rates_from_posTIMEFRAME_H1r   rU   rV   
last_errorrY  r_  rZ  rf  tz_localizerenamera  rb  rT   )n_barsmt5out_pathr0   rh  r_   s   &     rC   _refresh_eurusd_recent_csvr    s,   !v%-88CF\\dT:''#2B2BAvN=CJ!OKKMcnnN^M__`ab\\% 6
$GJJVVW[\:YYx8Y9ABNNzZ
		(%	( FqcJKs   B#D< 'BD< <E*E%%E*c            	     4    \         R,          R,          R,          R,          p V R,          pV R,          p. ROp. pW3 EF  pVP                  4       '       g   K   \        P                  ! V4      pVP                   Uu. uF   qwP                  4       P                  4       NK"  	  upVn        RpV F  p	WP                  9   g   K  T	p M	  Vf<   \        P                  R	VP                   R
\        VP                  4       R24       K  \        P                  ! Wh,          RRR7      VR&   \        P                  ! VR,          RR7      VR&   VP                  RR.R7      pRR.p
R FF  pWP                  9   g   K  \        P                  ! Wk,          RR7      Wk&   V
P                  V4       KH  	  VP                  Wj,          4       EK  	  V'       g   R# \        P                   ! VRR7      P#                  R.RR7      P%                  R4      P'                  RR7      pV# u upi   \         d2   p\        P                  RTP                   RT 24        Rp?EK"  Rp?ii ; i  \         d$   p\        P)                  RT 24        Rp?R# Rp?ii ; i)uA  Load the canonical 1H EURUSD price history (2003 → today).

Combines:
  EURUSD_1H_2003_2026.csv  (full historical archive — 'Time (EET)' column,
                             'YYYY.MM.DD HH:MM:SS' format, 142k+ bars)
  EURUSD_1H_recent.csv     (rolling window kept fresh by MT5 — 'datetime'
                             column, ISO format, ~5k bars)

Both files are treated as EET (validated convention — matches the trading
session filter and the original backtest). No timezone conversion.

Returns None if neither file is readable — caller should fall back to MT5.
r.   r/   rk  r   zEURUSD_1H_2003_2026.csvrl  r   Nz	[EURUSD] z#: no timestamp column found (have: r   mixedcoerce)r9   rQ  rx  rP  subsetz[EURUSD] could not read : T)ignore_indexlast)r  keepdropz-[EURUSD] _load_eurusd_canonical_bars failed: )r   r   rm  	timestampz
time (eet)z	time(eet)time_eetztime eet)ru  rv  rw  rr  )rz  rN   rY  read_csvrt  rK   r   rU   rV   r   r   rZ  
to_numericdropnar   rT   concatr`  ra  reset_indexrL  )	price_dirarchiverecentTIMESTAMP_COL_CANDIDATESframesr\   rh  rY   dt_colcandcolsoptr_   combineds                 rC   _load_eurusd_canonical_barsr     s9   ;&.9HD	 99 66$
 
 %D;;==[[&9;DAggioo/D
 4Dzz)!% 5 >KK)DII; 7**.rzz*:);1!> ? "$
7S[!\:!#r'{8!L7YYz'&:Y; #G,<Cjj("$--"IC( = bh'? &H IIf48$_ZLv_F [, [d[+ 	 K E4  6tyykA3GH  		A!EFsv   AI) $H* &H%&H*AH*
I) A.H*?AH*I) A
I) %H**I&5%I!I) !I&&I) )J4JJc                j   V R8X  ds    \        RR7      p\        4       pVeC   \        V4      R8  d3   \        P	                  R\        V4      R RV'       d   R	MR
 R24       V# \        P                  R4        ^ RIpVP                  pVP                  W^ V4      pVe   \        V4      ^ 8X  d,   \        P                  RV  RVP                  4        24       R# \        P                  ! V4      p\        P                  ! VR,          RR7      VR&   V. RO,          P                  RR/R7      P!                  R4      P#                  RR7      #   \         d#   p\        P                  RT 24        Rp?LRp?ii ; i  \         d'   p\        P                  RT  RT 24        Rp?R# Rp?ii ; i)aV  Fetch 1H bars for a symbol.

For EURUSD: reads canonical CSV combination (gives ~143k bars, 5.7k daily,
enough to run the rolling OLS beta model the backtest validates).
Refreshes the recent CSV from MT5 inline before reading.
Falls back to MT5-only path on any failure.

For other symbols: pulls n_bars hourly from MT5 (original behaviour).
r     )r  Ni  z![EURUSD] 1H bars: canonical CSV (rF   z bars, refresh: r   staler   u?   [EURUSD] canonical CSV unavailable — falling back to MT5-onlyz:[EURUSD] canonical-CSV path failed (falling back to MT5): r0  z] 1H bars failed: rm  rn  ro  r   rq  rr  rs  Tr  z] get_bars_1h error: )r   ru  rv  rw  rx  rq  )r  r  r   rU   r  rV   rT   ry  r~  r}  rL  r  rY  r_  rZ  r  ra  r  )r	   r  	refreshedrh  r_   r  tfr0   s   &&      rC   get_bars_1hr  k  s    		Z2$?I,.B~#b'D.<SWQK H%-6TG$DAG H	KKY[!''Av>=CJ!OII&!3CNN4D3EFG\\% 6
=:HIPP"H- Q //:{:/F{{X\{G]	^  	ZKKTUVTWXYY	Z   		AfX21#67s>   AE %E ;A F A3F E>E99E>F2F--F2c                     ^ RI pVP                  pVP                  W^ V4      pVe   \        V4      ^ 8X  d,   \        P                  RV  RVP                  4        24       R# \        P                  ! V4      p\        P                  ! VR,          RR7      VR&   V. RO,          P                  R4      P                  RR	7      #   \         d'   p\        P                  RT  R
T 24        Rp?R# Rp?ii ; i)r   Nr0  z] 15M bars failed: rm  rn  r  r   Tr  z] get_bars_15m error: )r   ru  rv  rw  rx  )ry  TIMEFRAME_M15r}  r   rU   rL  r  rY  r_  rZ  ra  r  rT   )r	   r  r  r  r0   rh  r_   s   &&     rC   get_bars_15mr    s    !''Av>=CJ!OII&!4S^^5E4FGH\\% 6
=::;GG#.	/ 		AfX3A378s   A C $A!C C7C22C7c                      ^ RI pVP                  V 4      pV'       d   VP                  VP                  3# R#   \         d'   p\
        P                  RT  RT 24        Rp?R# Rp?ii ; i)r   Nr0  z] get_live_price error: )ry  symbol_info_tickbidaskrT   rU   rL  )r	   r  tickr_   s   &   rC   get_live_pricer    sa    !##F+'+$((#55 		AfX5aS9:s   9 9 9 A*A%%A*c                    \         V ,          pVR,          R8X  d   \        W4      # VR,          R8X  d   \        W4      # \        W4      # )zDispatch to correct z-score engine per pair.

`prices_df` is 15M bars for EURUSD (beta_model_15m), 1H bars for USDJPY (spread_zscore).
r"   r#   
beta_model)r)  _compute_eurusd_zscore_15m_compute_eurusd_zscore_compute_spread_zscore)r+  	prices_dfcfgs   && rC   compute_zscorer    sJ    
 d
C
=--))99	]	|	+%i55%i55rB   c           	     *  a  ^ RI Hp V P                  4       p V R,          P                  P	                  4       V R&   V P                  RRR7      P                  R0R7      P                  R4      P                  RR	7      pVR,          P                  4       VR
&   \        P                  ! VR,          4      VR&   R pV! RR4      pV! RR4      pV! RR4      pV! RR4      p\        P                  ! R\        P                  ! VR,          P                  4       VR,          P                  4       R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	! VR4      RR7      p
V
R,          V
R,          ,
          V
R&   V
R,          V
R,          ,
          V
R&   V
R,          P#                  ^4      V
R&   V
R,          P#                  ^4      V
R&   V
. R1O,          P%                  4       p
VP!                  V
RRR7      P%                  R
R.R7      p\'        V4      pV\(        ^,           8  Ed   \*        P-                  RV 24       Tp\.        P0                  ! V\.        P2                  4      p\.        P0                  ! V\.        P2                  4      p\5        \(        V4       EF  pVP6                  V\(        ,
          V P                  4       pVVR,          P9                  4       ^ 8  VR,          P9                  4       ^ 8  ,          ,          p\'        V4      ^8  d   K~  VP;                  VRR.,          4      p VP=                  VR
,          V4      P?                  4       pVP@                  PC                  R\.        P2                  4      VV&   VP@                  PC                  R\.        P2                  4      VV&   EK  	  \        PF                  ! V4      PI                  \J        R 7      PM                  4       PO                  ^4      PQ                  R2R!4      PR                  VR"&   \        PF                  ! V4      PI                  \J        R 7      PM                  4       PO                  ^4      PQ                  R3R#4      PR                  VR$&   VR",          VR,          ,          VR$,          VR,          ,          ,           VR%&   M:\*        PU                  R&V R'\(        ^,            R(24       VR,          R4,          VR%&   V. R5O,          pV P!                  VRRR7      pV. R6O,          PW                  4       V. R6O&   VPC                  R)^4      pVR,          P                  V4      VR*&   VR%,          VR*,          ,
          VR+&   VR,,          pVR+,          PY                  VVR-7      PM                  4       pVR+,          PY                  VVR-7      P[                  4       pVR+,          V,
          V,          VR.&   VP%                  R..R7      P                  RR	7      #   \D         d     EK  i ; i  \D         d8   p\*        P]                  R/T 24       ^ RI/pTPa                  4         Rp?R# Rp?ii ; i)7u%  EURUSD VALIDATED FORMULA (v3 — Formula A):

Z-score computed on 15M bars matching backtest exactly:
  - lag_gap = predicted_return_1d (daily, OLS) - eurusd_return_24bar (15M pct_change)
  - eurusd_return_24bar = 24 × 15M bars = 6-HOUR rolling return
  - z-score normalisation: rolling(60) on 15M = 15-HOUR window
  - Signal evaluation: every 15M bar

The daily predicted_return_1d series comes from the same rolling-OLS beta
model as v2 (120-day window, 20-day EWM smooth, ±clip, shift 1). It's
forward-filled onto every 15M bar within its day.
Nr   r   Fas_indexrx  rx  Tr  eurusd_return_1dc                 D   \         V ,          p\        P                  ! VR .R7      pVP                   Uu. uF  qDP	                  4       NK  	  upVn        \        P
                  ! W1,          RR7      W1&   VR V.,          P                  4       P                  R 4      # u upi r   )parse_datesr  rP  rW  rY  r  rt  r   r  r  ra  fnamecolr\   rh  rY   s   &&   rC   	load_rate-_compute_eurusd_zscore_15m.<locals>.load_rate  x    u$D;;t&:B-/ZZ8Z'')Z8BJmmBGH=BGvsm$++-99&AA 9   Br    us2y	us10y.csvus10yde2y.csvde2y	de10y.csvde10yDfreqc                    < SP                  4       P                  R R/4      pV P                  4       P                  R R/4      p\        P                  ! W#R RR7      # r   datetime64[us]backward)onr#  copyastyperY  
merge_asofrate_dfr  leftright
date_ranges   &&  rC   ffill)_compute_eurusd_zscore_15m.<locals>.ffill  O    OO%,,f6F-GHDLLN))63C*DEE==:NNrB   r  	spread_2y
spread_10yspread_2y_change_1dspread_10y_change_1dr  r  howr  z:[EURUSD/15m] z-score path: OLS (full model) | daily bars: span皙?beta_2y{Gz?beta_10ypredicted_return_1dzD[EURUSD/15m] z-score path: FALLBACK (hardcoded -0.03) | daily bars:  (need +)r   eurusd_return_24barlag_gap_24barr   min_periodszscorez[EURUSD/15m] zscore error: rx  r  r   r  r  皙{GzQr   r  r  r  r  r  r  1statsmodels.apiapir  rf  	normalizegroupbyaggra  r  
pct_changerY  rZ  r_  r  minr   mergediffr  r   BETA_WINDOWrU   r  npfullnanrangeilocabsadd_constantOLSfitr   rR   rT   SeriesewmSMOOTH_SPANmeanshiftclipvaluesrV   r  rollingstdrL  	traceback	print_exc)
prices_15mr  smdaily_pxr  r  r  r  r  r  spreadsmodel_dfn_dailynb2y_rawb10y_rawisampleXresrh  lookbackwr  r  r_   r  r  s   &&                         @rC   r  r    s   O$__&
'
366@@B
6&&v&>S0S1 [($)? 	 (0'8'C'C'E#$>>(6*:;	B *v.+w/*v.+w/\\62==V  "HV$4$8$8$:,F #G H
	O V$**5w+?6*JE%V,E7eE'>RV\8] 	 '76? B ' 0773C C*1+*>*C*CA*F%&*1,*?*D*DQ*G&'OPWWY>>'f&>AHH&'<= I ?h-kAo%HHQRYQZ[\Agga(GRWWQ5G(;*!q}Q7<<>(=!>!B!B!Dq!H"()?"@"D"D"F"J!L Mv;#XOOF,ABX+Y$Z['&&(:!;Q?CCEC"%**..1F"PGAJ"%**..1G"PHQK + $&99W#5#9#9{#9#K#P#P#R#X#XYZ#[#`#`afgk#l#s#sHY#%99X#6#:#:#:#L#Q#Q#S#Y#YZ[#\#a#abghl#m#t#tHZ #x0E'FF$x0F'GGH *+ KK^_f^ggnoz}~o~n  @B  C  D.67L.MPU.UH*+ghh6v>STZZ\ 	OP 77,b1$&wK$:$:8$D !$&'<$=CX@Y$Y?:/"**1!*<AAC/"**1!*<@@B?+d2c98yy
y+77T7BB3 !&$&4  		/s34)--/s>   M Y $BX=%IY =YY YY Z,ZZc           	       a  ^ RI Hp V P                  4       p V R,          P                  P	                  4       V R&   V P                  RRR7      P                  R/R7      P                  R4      P                  RR	7      pVR,          P                  4       VR
&   \        P                  ! VR,          4      VR&   R pV! RR4      pV! RR4      pV! RR4      pV! RR4      p\        P                  ! R\        P                  ! VR,          P                  4       VR,          P                  4       R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	! VR4      RR7      p
V
R,          V
R,          ,
          V
R&   V
R,          V
R,          ,
          V
R&   V
R,          P#                  ^4      V
R&   V
R,          P#                  ^4      V
R&   V
. R0O,          P%                  4       p
VP!                  V
RRR7      P%                  R
R.R7      p\'        V4      pV\(        ^,           8  Ed   \*        P-                  RV 24       Tp\.        P0                  ! V\.        P2                  4      p\.        P0                  ! V\.        P2                  4      p\5        \(        V4       EF  pVP6                  V\(        ,
          V P                  4       pVVR,          P9                  4       ^ 8  VR,          P9                  4       ^ 8  ,          ,          p\'        V4      ^8  d   K~  VP;                  VRR.,          4      p VP=                  VR
,          V4      P?                  4       pVP@                  PC                  R\.        P2                  4      VV&   VP@                  PC                  R\.        P2                  4      VV&   EK  	  \        PF                  ! V4      PI                  \J        R 7      PM                  4       PO                  ^4      PQ                  R1R!4      PR                  VR"&   \        PF                  ! V4      PI                  \J        R 7      PM                  4       PO                  ^4      PQ                  R2R#4      PR                  VR$&   VR",          VR,          ,          VR$,          VR,          ,          ,           VR%&   M:\*        PU                  R&V R'\(        ^,            R(24       VR,          R3,          VR%&   V. R4O,          pV P!                  VRRR7      pV. R5O,          PW                  4       V. R5O&   VR,          P                  ^4      VR)&   VR%,          VR),          ,
          VR*&   VR+,          pVR*,          PY                  VVR,7      PM                  4       pVR*,          PY                  VVR,7      P[                  4       pVR*,          V,
          V,          VR-&   VP%                  R-.R7      P                  RR	7      #   \D         d     EKo  i ; i  \D         d8   p\*        P]                  R.T 24       ^ RI/pTPa                  4         Rp?R# Rp?ii ; i)6z<EURUSD: rolling-beta lag z-score (original validated model).Nr   r   Fr  rx  r  Tr  r  c                 D   \         V ,          p\        P                  ! VR .R7      pVP                   Uu. uF  qDP	                  4       NK  	  upVn        \        P
                  ! W1,          RR7      W1&   VR V.,          P                  4       P                  R 4      # u upi r  r  r  s   &&   rC   r  )_compute_eurusd_zscore.<locals>.load_rate%  r  r  r    r  r  r  r  r  r  r  r  r  c                    < SP                  4       P                  R R/4      pV P                  4       P                  R R/4      p\        P                  ! W#R RR7      # r  r  r  s   &&  rC   r  %_compute_eurusd_zscore.<locals>.ffill1  r  rB   r  r  r  r  r  r  r  r  z6[EURUSD] z-score path: OLS (full model) | daily bars: r  r  r  r  r  r  z@[EURUSD] z-score path: FALLBACK (hardcoded -0.03) | daily bars: r  r  eurusd_return_24hlag_gap_24hr   r  r  z[EURUSD] zscore error: r  r  r  r  r  r   r  r  )	prices_1hr  r"  r#  r  r  r  r  r  r  r$  r%  r&  r'  r(  r)  r*  r+  r,  r-  rh  r/  r  r  r_   r  r  s   &&                        @rC   r  r    s   H$NN$	%j144>>@	&%%fu%=S/S0 [($)? 	 (0'8'C'C'E#$>>(6*:;	B *v.+w/*v.+w/\\62==V  "HV$4$8$8$:,F #G H
	O V$**5w+?6*JE%V,E7eE'>RV\8] 	 '76? B ' 0773C C*1+*>*C*CA*F%&*1,*?*D*DQ*G&'OPWWY>>'f&>AHH&'<= I ?h-kAo%HHMgYWXAgga(GRWWQ5G(;*!q}Q7<<>(=!>!B!B!Dq!H"()?"@"D"D"F"J!L Mv;#XOOF,ABX+Y$Z['&&(:!;Q?CCEC"%**..1F"PGAJ"%**..1G"PHQK + $&99W#5#9#9{#9#K#P#P#R#X#XYZ#[#`#`afgk#l#s#sHY#%99X#6#:#:#:#L#Q#Q#S#Y#YZ[#\#a#abghl#m#t#tHZ #x0E'FF$x0F'GGH *+ KKZ[bZccjkvyzkzj{{}~.67L.MPU.UH*+gh__X&f_=STZZ\ 	OP"$W+"8"8"<"$%:";bAT>U"U=:- (((:??A- (((:>>@=)D0C78yy
y+77T7BB' !&$&(  		+A3/0)--/s>   M X> $BX+%IX> +X;6X> :X;;X> >Z 	,Y;;Z c                    VR,          w  r#VR,          pR pV! V R2V4      pV! V R2V4      p\         P                  ! WgRRR7      P                  R4      pVP                  R4      pVP	                  \         P
                  ! VP                  P                  4       VP                  P                  4       RR	7      4      P                  4       P                  4       pW,          W,          ,
          VR
&   VR
,          P                  WDR7      P                  4       VR&   VR
,          P                  WDR7      P                  4       VR&   VR
,          VR,          ,
          VR,          ,          VR&   VP                  R.R7      pVR,          P                  4       p	RR.V	n        \         P                   ! V	R,          4      P#                  R4      V	R&   V P%                  4       p \         P                   ! V R,          4      P#                  R4      V R&   \         P&                  ! V P                  R4      V	P                  R4      RRRR7      p
V
P(                  '       g$   \+        V
R,          P,                  R ,          4      MRp\.        P1                  R\3        V4       RVR RVR
,          P,                  R ,          R RVR,          P,                  R ,          R RVR,          P,                  R ,          R R24       V
P                  R.R7      P                  RR7      #   \4         d7   p\.        P7                  RT 24       ^ RIqP;                  4         Rp?R# Rp?ii ; i)!u  
USDJPY: z-score of US-JP 2Y spread level.

CRITICAL: z_window=30 means 30 CALENDAR DAYS on daily spread data —
this matches the backtest exactly. Rolling on hourly bars would give
a 30-hour window which produces a completely wrong signal.

Method:
  1. Build daily spread series (us2y - jp2y)
  2. Compute 30-day rolling z-score on DAILY data
  3. Forward-fill daily z-score onto hourly bars
r!   r   c                 D   \         V ,          p\        P                  ! VR .R7      pVP                   Uu. uF  qDP	                  4       NK  	  upVn        \        P
                  ! W1,          RR7      W1&   VR V.,          P                  4       P                  R 4      # u upi r  r  r  s   &&   rC   r  )_compute_spread_zscore.<locals>.load_ratew  r  r  z.csvr   outerr  r  r  spreadr  z_meanz_stdr  r  r  r   r  )left_onright_onr#          z$[USDJPY] Spread z-score computed on z daily bars | current z=.4fz
 | spread=z% (mean=z std=r   Tr  z[USDJPY] zscore error: N)rY  r
  ra  	set_indexreindexr  rV  r	  r   r  r  r  r  r  r  rt  rZ  r  r  r  emptyr[  r  rU   r  r   rT   rL  r  r   )r7  r  spread_col1spread_col2r/  r  r1r2r0   daily_zrh  	current_zr_   r  s   &&            rC   r  r  f  s   9#&}#5 
O	B +d+[9+d+[9F8DDVL'MM%++//+U[[__->SI

%'&&( 	  ,u/AAh  /11!1CHHJh/11!1CGGIg ?U8_<gNhXJ/ /--/!8,..9@@AQRNN$	 "j!!##)6*:#; 	* ]]!!*-' 	
 9;E"X,++B/0c	7E
| D'_ - ?//3C8 9/..r237 8g++B/4A	7 	8 yy
y+77T7BB 		+A3/0--/s   LL   M!++MM!c                     \         V ,          R ,           F  w  r#pY!u;8:  d   V8  g   K   Vu #  K  	  \         V ,          R ,          R,          ^,          # )r   rC  )r)  )r+  r&  lohimults   &&   rC   get_multiplierrQ    sN    #D).99 b K ! : t^,R033rB   c                     ^ RI p\        V ,          pVP                  4       pVf   R# VP                  p\	        W4      pRpV R8X  d   VP                  R4      '       d   \        P                  R/ 4      P                  RR4      pV'       g9   \        VP                  R	R4      4      p\        P                  R
V  RV R24       M\        P                  R
V  R24       V\        ,          V,          V,          p	VP                  VR,          4      p
VR,          pVR,          pV R8X  dO   VP                  VR,          4      pV'       d   VP                  MRpVR,          V,          pW,          V,          pM%V
'       d   V
P                  ^
,          MRpW,          pV	VV,          ,          pV
'       d   V
P                  MRpV
'       d   V
P                   MRpV
'       d   V
P"                  MRp\%        V\'        V\)        VV,          4      V,          4      4      p\        P                  R
V  RVR RV	R RV RVR 2
4       V#   \*         d'   p\        P-                  R
T  RT 24        Rp?R# Rp?ii ; i)r   Nr*         ?r   r   r(   r  Fr   r0  z	] Boost xu$    applied — no USDJPY position openu6   ] Boost skipped — USDJPY position open (normal size)r	   r   r   g     b@i g      $@g      Y@z] Lot size: .2fz
 | Risk: $,.0fz	 | Mult: zx | Balance: $z] calculate_lot_size error: )ry  r)  account_infobalancerQ  rR   r2  r[  rU   r  BASE_RISK_PCTsymbol_infor  r  trade_tick_value
volume_min
volume_maxvolume_stepr   r	  r>  rT   rL  )r+  r&  r  r  accountrW  
multiplierboostuj_openrisk_dollarrY  r   r   r  price	pip_value	stop_pipslot_sizemin_lotmax_lotlot_stepr_   s   &&                    rC   calculate_lot_sizerj    s*   (! &%%'?4__#D5
8(< = = nnXr266NGcgglC891TF)E72VWX1TF"XYZ},z9EAooc(m4*o*o8 ''H6D $DHH%E 7*U2I (83I >I44r9dI +I)i"78.9;))t.9;))u.9;**twGU8h3F-G(-R ST1TF,xnJ{4>P Q$^GD>C 	D 		AdV7s;<sH   %I 6I  -I B<I 5I 4I 6I I "A#I I7I22I7c           
         . ROp\          F*  w  r#pW,          pVP                  RV RVR RV R24       K,  	  RP                  V4      # )zJReturn an HTML block listing lot size per notional tier + FTMO pass range.r   u    → z5.2fu    lots</code> · z
 FTMO passr   )rI   u?   <b>📊 LOT SIZE BY NOTIONAL</b> — pick one &amp; stick to itu>   <i>(based on a fresh $100k USD account · TP/SL identical)</i>)TIER_SCALESr   r   )rf  r  labelscalepassratelotss   &     rC   format_tier_lotsrq    sW    SE"-hveWHT$K7KH:U_`a #. 99UrB   c                     ^ RI p\        V ,          pVR,          pVR,          pVR,          p	VR,          p
V^8X  dB   VP                  p\        V^V,           ,          V
4      p\        V^V	,
          ,          V
4      pM@VP                  p\        V^V,
          ,          V
4      p\        V^V	,           ,          V
4      pRVP
                  RVRVRVR	VR
VRVRRRRVR 2RVP                  RVP                  R^/pVP                  V4      pVf   \        P                  RV  R24       R# VP                  VP                  8w  d5   \        P                  RV  RVP                   RVP                   24       R# \        P                  RT  RVP                   RV^8X  d   RMR RV RV RV RV 24       VP                  #   \          d'   p\        P                  RT  R T 24        Rp?R# Rp?ii ; i)!r   Nr	   r   r   r'   actionrr  typerc  sltpmagic4commentzz-score rT  	type_timetype_filling	deviationr0  z] order_send returned Nonez] order_send failed: r   z] Order placed: ticket=z dir=BUYSELLz lots=z entry=z tp=z sl=z] place_order error: )ry  r)  ORDER_TYPE_BUYr>  ORDER_TYPE_SELLTRADE_ACTION_DEALORDER_TIME_GTCORDER_FILLING_IOC
order_sendrU   rL  retcodeTRADE_RETCODE_DONEry  r  orderrT   )r+  signalr  rf  r&  r  r  r	   r   sl_pctr'   
order_typerv  ru  r   r   r_   s   &&&&&            rC   place_orderr    s   *!d#h-h-j/d)Q;++J{a&j126B{a&j126B,,J{a&j126B{a&j126BC11FHJKBBHhz#&67C..C11B
 (>II$9:;>>S333II$4V^^4DAfnnEUVW1TF1&,, @!'7 8!
'+d2$d2$H 	I || 		AdV045s&   D'G +AG :AG G4G//G4c                     ^ RI p\        V ,          R,          pVP                  VR7      pV'       g   \        P	                  RV  RV R24       R# V^ ,          pVP
                  VP                  8X  d   VP                  MVP                  pVP                  V4      pWsP                  8X  d   VP                  MVP                  p	RVP                  RVR	VP                  R
VRVRV	RRRVRVP                  RVP                  R^/p
VP!                  V
4      pV'       d<   VP"                  VP$                  8X  d!   \        P	                  RV  RV RV 24       R# \        P'                  RT  RV'       d   VP"                  MR 24       R#   \(         d'   p\        P'                  RT  RT 24        Rp?R# Rp?ii ; i)r   Nr	   ticketr0  z] Position u    not found — already closedTrs  rr  rt  positionrc  rw  rx  ry  rz  r{  r|  u    closed — reason: z] Close failed: NoneFz] close_position_market error: )ry  r)  positions_getrU   r  rt  POSITION_TYPE_BUYr  r  r  r  r  r  rr  r  r  r  r  r  rL  rT   )r+  r  reasonr  r	   	positionspossider  rc  r   r   r_   s   &&&          rC   close_position_marketr  "  s    !T"8,%%V%4	HHqk&1NOP|&)hh#2G2G&Gs""SM_M_##F+#5#55txx488C11FCJJDFEHFC..C11B
 $fnn(>(>>HHqk&1EfXNO		AdV+fFNN&+QRS 		AdV:1#>?s5   1F F CF  9F F 6F F=F88F=c                      ^ RI p\        V ,          R,          pVf   R# VP                  VR7      p\        V4      #   \         d     R# i ; i)r   Nr  Fr  )ry  r2  r  r   rT   )r+  r  r  r  s   &   rC   check_position_statusr  E  sK    #!D!"34>%%%V%4	I"U"s   = = AAc                     ^ RI p\        V ,          R,          pVP                  V4      pVf   R# VP                  VP                  8H  #   \
         d     R# i ; i)a  Return True if the broker market is open and fully tradeable.

Used by hold-limit, z-exit, and broker-detected-close paths to avoid
attempting to close positions when markets are closed. Saturday's bug:
v3 attempted hold-limit close while UJ was closed weekend, MT5 returned
error 10018 (TRADE_RETCODE_MARKET_CLOSED), but v3 didn't check and
proceeded to mark the position closed in state anyway. Result: state
diverged from broker truth and v3 lost track of the open position.
Nr	   F)ry  r)  rY  
trade_modeSYMBOL_TRADE_MODE_FULLrT   )r+  r  r	   sis   &   rC   _is_market_tradeabler  N  sV    	!T"8,__V$:}} : ::: s   -A
 A
 
AAc                    R \         V ,          R&   V\         V ,          R&   V\         V ,          R&   \        P                  ! \        P                  4      \         V ,          R&   V\         V ,          R&   \        V 4       \        P                  RT  RV^8X  d   RMR	 R
VR\        V ,          R,           R2 24       \        V 4       R# )Tr"  r#  r$  r%  r&  r0  z] Armed | dir=LONGSHORTz target=.r'   fN)
r3  r   rf   r   rp  r6  rU   r  r)  _schedule_fast_poll)r+  r#  r$  r&  s   &&&&rC   arm_fast_pollingr  d  s    '+JtX'0Jt[!'3Jt^$'/||HLL'AJt\"'1Jt\"tHHqny!|V$I J#Ak$&7&=%>a#?@B CrB   c                     R \         V ,          R&   V \        9   d8   \        V ,          '       d%   \        V ,          P                  4        R\        V &   \        V 4       \        P                  RV  RV 24       R# )Fr"  Nr0  u   ] Disarmed — )r3  _fast_poll_timercancelr6  rU   r  )r+  r  s   &&rC   disarm_fast_pollingr  o  sb    !&JtX$4T$:$:%%'!%tHHqofX./rB   c                 ~   a  \         P                  ! R V 3R l4      pRVn        VP                  4        V\        S &   R# )rS  c                     < \        S 4      # N)_run_fast_pollr*  s   rC   <lambda>%_schedule_fast_poll.<locals>.<lambda>~  s
    )=rB   TN)	threadingTimerdaemonstartr  )r+  timers   f rC   r  r  w  s1     OOC!=>EEL	KKM"TrB   c                     \         V ,          R ,          '       g   R#  \         V ,          p\        V ,          p\        V ,          p\        P                  ! \
        P                  4      pWAR,          ,
          P                  4       R,          pVR8  d   \        V R4       R# VR,          '       d   R# \        VR,          4      pVf   \        V 4       R# Vw  rxVR,          p	VR	,          p
V	^8X  d   W8*  pTpMWz8  pTpV'       g   \        V 4       R# V^<,          p\        P                  R
T  RV	^8X  d   RMR RVRVR,           R2 RVR R2	4       VR,          p\        W4      pVR8  d   \        V R4       R# \        W	WV4      pV'       Ed   RVR&   VVR&   WR&   WR&   VP                  RR7      VR&   WR&   VP!                  R4      '       d%   VR,          \#        VR,          R 7      ,           VR!&   \        V R"4       VR,          pV	^8X  d   V^VR#,          ,           ,          MV^VR#,          ,
          ,          pV	^8X  d   V^VR$,          ,
          ,          MV^VR$,          ,           ,          p^
V^,
          ) ,          pV	^8X  d	   W,
          MW,
          pVV,          pV^ 8w  d   VR% R&2MR'p\        P                  R
V  R(V R)V
RV R2 R*VRV R2 R+2	4       \%        R,T  R-V	^8X  d   RMR R.VRV R2 R/V
RV R2 R0V R1VR2 R3VRV R2 R4VRV R2 R5V 2R6,           \'        V4      ,           4       R# \        V R74       R#   \(         d2   p\        P+                  R
T  R8T 24       \        T 4        Rp?R# Rp?ii ; i)9r"  Nr%  r8  r9  z	6h expiryr  r	   r#  r$  r0  z] TARGET HIT | r  r  z	 | price=r  r'   r   | .0fzm since signalr&  r*   zlot size errorTr  r  r  tzinfor  r  r   hoursr.  zorder placedr   r   +.1fr?  z0.0pz] Entry slip vs target: z	 (target=z, fill=r   u	   📈 <b>[z!] ORDER PLACED</b>
Direction: <b>z</b>
Entry: z
Target was: z (slip z)
Lots: rT  z
TP: z
SL: z	
Ticket: r   zorder failedz] fast poll error: )r3  r2  r)  r   rf   r   rp  r=  r  r  r  rU   r  rj  r  rl   rR   r   r   rq  rT   rL  )r+  r4  stater  rf   elapsed_hoursrk  r  r  r#  r5  	entry_hit
exec_priceelapsed_minr&  rf  r  r'   tp_pxsl_pxr   raw_slip	slip_pipsslip_strr_   s   &                        rC   r  r    s   dH%%N"D!D!T"hll+\22AACdJCk2!!H.>%+&	.)>IJIJ%#b(1TF/IqL&g)N O$Qs4ykN33$N4 	5 <(
'9d?&67TjJO6'+E/"'-E#$'0#$'1- '*{{${'?E,'/.!ww-..*/*=	45A7 +7&'n5$iB6?lj!CM/2
TUVYZbVcTcHdE8A1j!C
O"34*VWX[\fXgVgJhE QiH1:a,jFYH 8+I09Q9T*!,FHHHq6xj A  &qAggj2$a5HK LD6 "!+4a<W E F$Qrd!G, -%at1WoWXJ ?!# 'Qrd!Gn %Qrd!Gn %!($  *(34
  n5 "		AdV.qc23D!!"sD   BM "M 3!M 1M M A(M ?GM M N&NNc                     \         \        V ,          R ,          ,          pVP                  4       '       d'   \        P                  ! VP                  RR7      4      M. pVP                  RV RVRVRVRVR\        V^4      R	\        P                  ! 4       P                  4       /4       VP                  \        P                  ! V^R
7      RR7       \        R V 4       4      p\        P                  RV  R\!        V4       RVR 24       R#   \"         d'   p	\        P%                  RT  RT	 24        Rp	?	R# Rp	?	ii ; i)r%   r5   r6   r+  r  r  r#  rf  net_pnl	closed_atrd   c              3   2   "   T F  qR ,          x  K  	  R# 5ir  NrA   r   r   s   & rC   r   %save_trade_history.<locals>.<genexpr>  s     1&QiLL&   r0  z] Trade history: z trades | total=$rT  z] trade history save failed: N)r@   r)  rN   rO   rP   rQ   r   r>  r   rf   rg   ri   rj   sumrU   r  r   rT   rV   )
r+  r  r  r#  rf  r  r\   tradestotalr_   s
   &&&&&&    rC   save_trade_historyr    s   @T*;77AEDNNGN<=TV46;985!,8<<>335
 	 	

6!4wG1&111TF+CK=8I%PSUV @av:1#>??@s   4D CD D?D::D?c                    ^ RI p \        P
                  ! \        P                  4      pT\        T R7      ,
          pTP                  Y24      pTf   \        P                  R4       R# ^ p^p/ pT FV  pTP                  YV39  d   K  TP                  '       g   K+  TP                  TP                  . 4      P                  T4       KX  	  ^ p	^ p
^ p\        P                  4        EF  w  rTR,          p\         TR,          ,          p TP#                  4       '       d'   \$        P&                  ! TP)                  RR7      4      M. pT Uu/ uF  p\-        TP/                  R	^ 4      4      TbK!  	  pp\1        TP3                  4       4      p. p^ pTP                  4        EFI  w  ppT Uu. uF  qP4                  T8X  g   K  TNK  	  ppT'       g   K4  \7        T4      ^8  dH   TP                  \-        T4      R
7      pT'       d%   \7        T4      \7        T4      8  d   \9        T4      p\;        TR R7      p\7        T4      ^8  d   K  \=        R T 4       4      p\-        T4      T9   d   T\-        T4      ,          p\?        TP/                  RR4      4      pTR8X  d   \A        T4      R8  dq   \C        T^4      TR&   \        PD                  ! TR8,          PF                  4      PI                  4       TR&   T^,          p\        PK                  RT RT RTR R24       T
^,          p
EK  T^ ,          pTR8,          pTPL                  pTP                  T8X  d   ^MR8p TPN                  p!Tp"\        PD                  ! TPF                  4      PI                  4       p#TP                  RTR	\-        T4      R\C        TTR,          4      RT R\C        T!^4      R\C        T"^4      RT#/4       EKL  	  T'       g   T'       g   EK  \9        TPQ                  4       4      T,           p$T$PS                  R R7       TPU                  \$        PV                  ! T$^R7      RR7       T	\7        T4      ,          p	TT,          pT Fs  pTR,          ^ 8  d   RMRp%\        PK                  R T RTR	,           R!TR,          ^8X  d   R"MR# R$TR,           R%T% R&TR,          R' R(TR,          R),           24       Ku  	  EK  	  T	^ 8  g   T^ 8  dJ   \        PK                  R*T	 R+T R,T
 R-24        T^ 8  d   R.T R/2MRp&\Y        R0T	 R1T& R2T
 R3T  R42	4       R# \        PK                  R5T
 R624       R#   \         d    \        P                  R4        R# i ; i  \*         d    . p ELWi ; iu upi u upi   \*         d     R# i ; i  \*         d8   p'\        P                  R7T' 24       ^ RI-p(T(P]                  4         Rp'?'R# Rp'?'ii ; i)9a  
Startup-time reconciliation. Scans MT5 deal history and back-fills any
closed trades missing from the trade_history JSON files.

Logic:
  1. Load existing tickets from each pair's trade_history file
  2. Query MT5 history_deals_get() for the last N days
  3. Group deals by position_id (ticket) + symbol
  4. For any ticket not already in the JSON, compute net P&L from its
     deals (entry + exit + commission + swap) and append to JSON
  5. Report: "reconciled N missing trades" or "all X trades in sync"
Nz+Reconciliation: MT5 not available, skippingdaysz.Reconciliation: MT5 deal history returned Noner	   r%   r5   r6   r  r  c                     V P                   # r  rm  ds   &rC   r  2reconcile_trade_history_from_mt5.<locals>.<lambda>8  s    affrB   keyc              3   ~   "   T F3  pVP                   VP                  ,           VP                  ,           x  K5  	  R # 5ir  profitswap
commissionr   r  s   & rC   r   3reconcile_trade_history_from_mt5.<locals>.<genexpr>=  s0      "7+5a #$((QVV"3all"B"B+5s   ;=r  rA  r*   r  z[RECONCILE-HEAL] z ticket u"    — net_pnl was $0.00, MT5 says $+,.2fu    — correctedr+  r  r'   r#  rf  c                 &    V P                  R R4      # )r  rI   rR   )r   s   &rC   r  r  m  s    AEE+r,BrB   rd   +rI   z[RECONCILE]     — r  r   @ r   $,.2fu    · closed r   z[RECONCILE] Back-filled z missing trade(s), healed z stale-zero entries (z already in sync)zHealed: <b>z</b> stale-zero entries
ue   🔄 <b>TRADE HISTORY RECONCILED</b>
────────────────
Back-filled <b>z</b> missing trade(s)
ud   from MT5 deal history on startup.
────────────────
Already in sync: z
Lookback: z daysz#[RECONCILE] Trade history in sync (z trades verified)zReconciliation failed: rC  )/ry  ImportErrorrU   rV   r   rf   r   rp  r   history_deals_getrt  position_id
setdefaultr   r)  r   r@   rN   rO   rP   rQ   rT   intrR   rM   keysr	   r   r   rW   r  r[  r  r>  fromtimestamprm  rg   r  rc  rr  r  sortri   rj   r   r  r   ))lookback_daysr  rf   r  dealsDEAL_BUY	DEAL_SELLby_positionr  total_reconciledtotal_already_syncedtotal_healedr+  r  r	   r\   r
  r   existing_by_ticketexisting_ticketsto_addhealed_in_pairpos_id	pos_deals
pair_dealspos_specificmt5_net_pnlexisting_entryexisting_pnl
entry_deal
close_dealr  r#  rf  r  r  r  signhealed_liner_   r  s)   &                                        rC    reconcile_trade_history_from_mt5r    s   !
N0ll8<<(i]33%%e1=KKHI 	Avvh22===""1=="5<<Q?   $**,ID]Fs;//DKO;;==4::dnngn&FG^` GO!Oh#aeeHa&8"91"<h!O"#5#:#:#<=FN%0%6%6%8!	)2IAhh&6Haa
I! z?Q&#&#8#8#f+#8#NL#L(9C
O(K%),%7
#J4DE
z?Q& " "7+5"7 7 v;"22 &8F%DN%*>+=+=i+M%NL#s*s;/?$/F49+q4Iy1 %22:b>3F3FGQQS '{3 '!+#4TF(6( K%%0$7~"G H )A-(']
'^
(..!+H!<A"	&--'$22:??CMMO	43v;!5c$i#@951#55!#49 q &9F  2 9 9 ;<vE"BC

8A >Q CK/ N2A"#I,!"33DHH|D6!H+e*+K.!*;I J""#M"2!34vQq|D>Q R''(~c':&;= >  m -z a<!#3HH/0@/A B+n --..?A B"."2 "-\N:ST8: &&6%77N"m((<'= >!!.u6	 HH -..?A B[  ABB   "P J@    0-aS12)--//0s   U( A!V? *0V? A.V? 
>VV? %V#1:V? +V(V(H-V? 6V? D"V? $'V- V? ( VVV V? V  V? -V<8V? ;V<<V? ?X
,W<<Xc                     ^ RI Hp  \        P                  ! V ! R4      4      #   \         d[    \        P                  ! \
        P                  4      p^TP                  u;8:  d   ^
8:  d   M M^M^pT\        TR7      ,           u # i ; i)u  Return current datetime in Eastern European Time (EET in winter, EEST
in summer). Uses the Europe/Helsinki zone — both EE timezone variants
follow DST consistently with that anchor.

Previous implementation used a naive `(UTC.hour + 2) % 24` which was
correct in winter but 1 hour off in summer (DST), causing v3 to skip
legitimate signals during the first hour of EEST sessions. Caught
2026-05-18 when EU broke threshold at z=-3.12 inside the validated
09:00-18:00 EET window but v3 logged "outside session — skipping".
)ZoneInfozEurope/Helsinkir  )	zoneinfor  r   rf   rT   r   rp  monthr   )r  rp  offsets      rC   _eet_nowr    si    -%||H%6788 - ll8<<(399**YV,,,-s   !$ A"B	B	c                     \         V ,          p\        4       P                  pVR ,          Tu;8*  ;'       d    VR,          8*  # u # )r   r   )r)  r  hour)r+  r  r  s   &  rC   
in_sessionr    s:    tC:??D4==3}+=====rB   i  c                    ^ RI p \        P                  ! 4       p\        VP                  VP                  VP
                  4      pV P                  W!\        ^R7      ,           4      ;'       g    . pRpV F^  p\        VRR4      R9   g   K  VP                  ^8X  g   K*  WEP                  VP                  ,           VP                  ,           ,          pK`  	  \        RV) 4      # )u   Positive $ of today's realized EU+UJ loss (UTC day; 0 if in profit).
Coarse guard — FTMO's reset is CE(S)T midnight, ±2h boundary slack is
acceptable for a tail-only circuit breaker.Nr  rA  r	   rI   r   r(   )ry  r   r<  yearr  dayr  r   getattrentryr  r  r  r   )r  rf   r  r  pnlr  s         rC   _todays_realized_lossr    s     OOCSXXsyy#''2E!!%yq/A)ABHHbE
C1h#';;188aff$q||33C  sSD>rB   c                    ^ RI p^ RIp^ RIHp ^p\        P                  ! VP
                  4      pV\        ^R7      ,
          p^pRp	. p
Rp\        ^V^,           4       EF   pVP                  VR7      pV'       g   VP                  Wv4      pVf/   \        P                  RV  RV 24       VP                  V	4       Ka  V Uu. uF  qP                  V8X  g   K  VNK  	  p
pV
 Uu. uF  p\        VRR4      V8X  g   K  VNK  	  ppV'       d1   V^ ,          pV^8  d   \        P                  RV  R	V R
V 24        MD\        P                  RV  RV R
V R\        V
4       RV	 R24       VP                  V	4       EK#  	  V
'       g"   \        P                  RV  RV RV R24       R(# Vf@   \        P                  RV  RV R24       \!        R V
 4       4      p\#        V
R R7      pRpM\!        R V
 4       4      pTpRpVP$                  pVP&                  '       d   VP&                  P)                  4       MRpRV9   g   RV9   d   RpMRV9   g   R V9   d   R!pMRp\        P                  RV  R"VR# R$V R%V R&V 2
4       \+        V^4      VVV3# u upi u upi   \,         d(   p\        P                  RT  R'T 24       R(u Rp?# Rp?ii ; i))uz  
Pull closed trade P&L from MT5 deal history.
Returns (net_pnl, close_price, reason, pnl_confident).

pnl_confident is True when we found the exit deal cleanly and net_pnl
reflects real broker numbers. False when retries were exhausted and
net_pnl may be approximate or zero-by-default.

PATCHED 2026-05-06: Retries with backoff to handle MT5 history table
population delay. The MT5 history_deals_get() result updates asynchronously
after a position closes — typical delay 100-2000ms. Without retry, we
sometimes find only the entry deal (profit=0) or partial data and report
wrong P&L on closes.

PATCHED 2026-05-14: Added confidence flag so the Telegram close
message can avoid the misleading "BREAK EVEN" label when P&L is
really just "unknown — exit deal wasn't in MT5 history table yet".

Strategy:
  • Filter strictly by position_id (no fallback to symbol-wide search,
    which previously caused cross-ticket P&L contamination)
  • Look specifically for the EXIT deal (entry == DEAL_ENTRY_OUT)
  • Retry up to 6 times with 1-second waits (max 6s wait total)
  • Sum profit/swap/commission across all deals for this ticket only
Nr   r  r   r  r0  z-] history_deals_get returned None on attempt r  z] Exit deal found on attempt r   z(] Exit deal not yet in history (attempt z, found u    deals for ticket) — waiting rn  z] No deals found for ticket z after z	 attemptsTP/SLFz!] Exit deal never appeared after uM   s — using all matched deals as-is (P&L may be 0 if exit deal still pending)c              3   |   "   T F2  qP                   VP                  ,           VP                  ,           x  K4  	  R # 5ir  r  r  s   & rC   r   "_get_closed_pnl.<locals>.<genexpr>  '     Qy!XX.==y   :<c                     V P                   # r  r  r  s   &rC   r  !_get_closed_pnl.<locals>.<lambda>  s    affrB   r  c              3   |   "   T F2  qP                   VP                  ,           VP                  ,           x  K4  	  R # 5ir  r  r  s   & rC   r   r"     r#  r$  TrI   rv  ztake profitu   Take Profit ✅ru  z	stop lossu   Stop Loss ❌z] Close P&L from MT5: $rT  z | close_px=z
 | reason=z | confident=z] _get_closed_pnl error: )rA  Nr   F)ry  rm  r   r   rf   rp  r   r  r  rU   rV   sleepr  r  r  r   r  r   rc  ry  r   r>  rT   )r+  r  r  _timer   DEAL_ENTRY_OUTrf   r  max_attemptswait_secondsr  	exit_dealattemptr  r  
exit_dealsr  r	  pnl_confidentclose_pxry  r  r_   s   &&                     rC   _get_closed_pnlr2    s   4X)!%X\\*iQ'' 		 Qq 01G ))6):E--e9}av%RSZR[\]L) %*EEq]]f-DEIE &/ JY$Q6.H !YJ J &qM	Q;HHq&CG9Al^\]HHq !!(	<. 9!)n--L\NZ[] ^ KK%9 2> KK!D6!=fXW\NZcde,,KK!D6!B'. )DE F QyQQGY,<=J!M QyQQG"J M## 1;0B0B0B*$$**,7?mw6&FW_w 6$FF1TF1'# ?%Jj 9+_. 	/ Wa (FMAAe FJb  )av6qc:;(()s]   CJ$ JJ"J$ (JJBJ$  J$ .A6J$ %A4J$ 
J$ $K/KKKc                   \         V ,          p\        V ,          p\        V ,          pVR,          pVR,          p\        P	                  RV  RVP                  RR4       R24       VP                  RR4      pVR8X  d<   \        WAP                  R	R
4      4      pVf   \        P                  RV  R24       R# M:\        WAP                  RR4      4      pVf   \        P                  RV  R24       R# \        W4      pVe   \        V4      ^8  d   \        P                  RV  R24       R# \        VR,          P                  R~,          4      p	WR&   \        P	                  RV  RV	R RVR,           R24       VR,          '       Ed   VR,          Ee   \        V 4      p
V
'       Eg'   VR,          pVR,          pVR,          pVP                  R^ 4      p\        P	                  RV  RV 24       \        W4      w  ppppRVR&   RVR&   RVR&   RVR&   RVR&   \        V 4       V'       d   Ve   \!        WWW4       \         V ,          R,          pV^ 8  d   RVR  2MR!\#        V4      R  2pV^ 8  d   R"pMV^ 8  d   R#pMR$pR%pV^8X  d   R&MR'pV'       d	   VR(V R)2 MR*p\%        R+V  R,V R-V R.VR(V R)2 R/V R0V R1VR2 R3V R424       R# VR,          Ee   \&        P(                  ! 4       VR,          ,
          P+                  4       R5,          pVVR6,          8  Ed   \-        V 4      '       g+   \        P	                  RV  R7VR8 R924       \        V 4       R# \        P	                  RV  R7VR8 R:24       VR,          pVR,          pVR,          pVP                  R^ 4      p\/        V VR;4      p\        V 4      p
V'       d	   V
'       d-   \        P                  RV  R<V R=V
 R>24       \        V 4       R# \        V V4      w  pppp RVR&   RVR&   RVR&   RVR&   RVR&   \        V 4       V'       d   Ve   \!        V VVVVV4       \         V ,          R,          p!V^ 8  d
   RVR  2p"R"p#MV^ 8  d   R!\#        V4      R  2p"R#p#MR%p"R$p#\%        R?V  R@VR6,           RAV# RBV" R42	4       R# VRC,          Ee   VR,          p$V$^8X  d   WRC,          ) 8:  g   V$R~8X  Ed   WRC,          8  Edr   \-        V 4      '       g+   \        P	                  RV  RDV	RE RF24       \        V 4       R# \        P	                  RV  RDV	RE 24       VR,          p%VR,          p&VR,          p'VP                  R^ 4      p(\/        V V%RG4      p)\        V 4      p*V)'       d	   V*'       d-   \        P                  RV  RHV) R=V* R>24       \        V 4       R# \        V V%4      w  p+p,pp-RVR&   RVR&   RVR&   RVR&   RVR&   \        V 4       V&'       d   V'e   \!        V V%V&V'V(V+4       V+^ 8  d
   RV+R  2p.R"p/MV+^ 8  d   R!\#        V+4      R  2p.R#p/MR%p.R$p/\%        RIV  RJV	RE RKV/ RBV. R42	4       R# \        P	                  RV  RLVR,           RMVR,           24       \        V 4       R# \#        V	4      VR,          8  d   \1        V 4      '       gY   \         V ,          p0\3        4       P4                  p1\        P	                  RV  RNV1RO RPV0RQ,          RO RRV0RS,          RO RTV	RE 2
4       M0\        P	                  RV  RU\#        V	4      RE RVVR,           R24       \        V 4       R# \1        V 4      '       g4   \        P	                  RV  RW\#        V	4      RE RX24       \        V 4       R#  \7        4       \8        8  d;   \        P                  RV  RY\7        4       RZ R[\8        RZ R\24       \        V 4       R#  \        V ,          P                  R_4      p3VP                  R`4      '       d   V3'       d   \=        V3\>        4      '       d   \&        P@                  ! V34      p3V3'       d[   \&        PB                  ! 4       V38  dA   \        P	                  RV  RW\#        V	4      RE RaV3 RbVR`,           Rc2	4       \        V 4       R# VRd,          '       d   \        P	                  RV  Re24       R# WR,          8  d   ^MR~p4\#        V	4      p5VP                  R4      R8X  dK   VP                  R,          p6\        V6Rf,          4      p7\        V6Rg,          4      p8\        V6Rh,          4      p9M\        V^
4      p:V:e   V:PD                  '       d   \        P                  RV  Ri24       R# V:P                  R~,          p;\        V;Rf,          4      p7\        V;Rg,          4      p8\        V;Rh,          4      p9V4^8X  di   V9V8,
          p<V<Rj8:  dA   \        P	                  RV  RkV	Rl RmV<Rn,          R2 RoV7Rp RqV8Rp RrV9Rp 24       \        V 4       R# V9VRs,          V<,          ,
          p=MgV7V9,
          p<V<Rj8:  dA   \        P	                  RV  RkV	Rl RtV<Rn,          R2 RoV7Rp RqV8Rp RrV9Rp 24       \        V 4       R# V9VRs,          V<,          ,           p=\        P	                  RT  RuV	R RvV4^8X  d   R&MR' RwV=R(V R)2 24       \%        RxT  RyV	R RzVR,           R{V4^8X  d   R&MR' R|V=R(V R)2 R}24       \G        V V4V=V54       \        V 4       R#   \:         d(   p2\        P                  RT  R]T2 R^24        Rp2?2ELtRp2?2ii ; i  \:         d    Rp3 EL%i ; i)z?Signal check for one pair. Cadence is per-pair (EU=15m, UJ=1h).r	   r'   r0  u'   ] ── Signal check ── (cadence: r   r)   r   r   r$   iX  Nu   ] No 15M bars — skippingr-     u   ] No 1H bars — skippingz] Z-score computation failedr  r  z] Z-score: rB  u    (threshold ±r
   r  r  r  r  r  z+] Position closed by broker (TP/SL) ticket=Fr  +$r  -$u
   ✅ PROFITu   ❌ LOSSu$   ⚠️ UNKNOWN — check MT5 historyu   Pending — see brokerr  r  r  r     —u	   🏁 <b>[uR   ] POSITION CLOSED</b>
────────────────
Result: <b>z</b>
Direction: z	
Entry:  z	
Close:  z	
Reason: z	
Lots:   rT  u>   
────────────────
Net P&L: <b></b>r8  r   z] Hold limit reached (r:  u.   h) but market closed — will retry next checku   h) — closing
time_limitz-] Hold-limit close did NOT succeed (close_ok=z, still_open=u&   ) — leaving state intact, will retryu   ⏱️ <b>[u%   ] POSITION CLOSED</b> — hold limit zh
Result: <b>z</b>
Net P&L: <b>r   z] Z-exit triggered at z=.3fu,    but market closed — will retry next checkzscore_reversalz)] Z-exit close did NOT succeed (close_ok=u	   🔄 <b>[z ] Z-EXIT TRIGGERED</b>
Z-score: z
Result: <b>u%   ] Position open — holding | ticket=z	 | entry=z] Outside trading window (02dz:00 EET | session r   u   :00–r   z:00 EET) | z=z] No signal (|z|=z < z] Signal |z|=u    outside session — skippingu-   ] Signal suppressed — daily realized loss $rU  z >= $z haltz ] daily-loss halt check failed (u   ) — continuingr.  r   u%    suppressed — entry cooldown until z UTC (one entry per zh, matches backtest)r"  u(   ] Already armed — fast polling runningrv  rw  rx  z] No 15M bars for Fib entryr   z] SIGNAL z=+.3fz& dir=LONG NOT ARMED: bar pull (C-L) = i'  zp < 1.0p Fib guard | bar H=z.5fz L=z C=r   z' dir=SHORT NOT ARMED: bar pull (H-C) = z] SIGNAL DETECTED | z=z | dir=z
 | target=u	   🎯 <b>[z"] SIGNAL DETECTED</b>
Z-score: <b>u   </b> (threshold ±z)
Direction: <b>z</b>
Fib target: z"
Watching for entry (6h window)...rC  )$r)  r2  r3  rU   r  rR   r  rV   r  r  r   r[  r  r  r2  r6  r  r  r   r   rf   r=  r  r  r  r  r  r  DAILY_LOSS_HALTrT   r   rJ   r;  r<  rF  r  )>r+  r  r  r4  r	   r'   	timeframer  rh  rL  
still_openr  entry_px	entry_dir
entry_lotsr  r1  close_reasonr0  pnl_strr   dir_str	close_strelapsedticket_h
entry_px_hentry_dir_hentry_lots_hclose_ok	net_pnl_h
close_px_h_pnl_confident_hdp_h	pnl_str_hresult_hdir_openticket_z
entry_px_zentry_dir_zentry_lots_z
close_ok_zstill_open_z	net_pnl_z
close_px_zpnl_confident_z	pnl_str_zresult_zcfg2hour_eet_ecd_untilr#  r&  
signal_barbhblbcr$   last_barpullr5  s>   &                                                             rC   run_signal_check_pairrl  8  s   CtEtEMFIBHHq=cggFXZ^>_=``abc *D1IE S)AB	KK!D6!;<=   	3(?@	KK!D6!:;< 
	(B	zSWq[av9:;bl''+,I$-HHqk)Cs;?O>PPQRS _%(9":"F*40
z01F}-H01I>15JHHqI&RS=LT=Z:GX|]',E/"'+E#$'+E#$'+E- '+E,tI1"4#-8 "$'-B-4\74.)CLQUCV?WG {%1#
 A2 )QfGG088AbT7+eID6 "$X &%Y '#AbT7+ ,$+ &'. )%c* +&it	-  *||~l(;;JJLtSG#l++
 ,D11HHq&<WSM JK L M t$1TF"8^TU#$56#M2
#$56#ii:3D(LQ
 348
 ZKK!D6 *--5JmJ< PG!H I t$<KDRZ<[9	:q/+0o&+/'(+/'(+/m$+/l#4 +"9&tXz;'3Y@ &d+D1 q="$Yt$4 5I ,H]"$S^D$9 :I *H 8I FH!$'LSQ]M^L_ `""* ,##,+T3
  x=$./HQ9X#>B9X#>+D11HHq&>yo NK L Mt$1TF":9S/JK#$56#M2
#$56#ii:3D(DUV
4T:"|KK!D6 *--7Ll^ TG!H I t$<KDRZ<[9	:q/+0o&+/'(+/'(+/m$+/l#4 +"9&tXz;'3Y@ q="$Yt$4 5I ,H]"$S^D$9 :I *H 8I FHv &  )# /""* ,##,+T34
 1TF  !234 5./1 	2 	4 9~K(($t$DzHHHq8# G  $_ 5c:&mATUX@Y Z%c?, - HHq/Is/C3s;GWFXXYZ[4d1TF-Is';;XYZ4T "o5KK!D6 "13D9t>TTY[ \t	 6 $##$45H
ww%&&8h$$$228<)H4HHqmC	N3+? @--5J 7''*+A'B&CCWY Z tX1TFBCD!%5512Iy>J ww!"e+ WWR[
:f%&:e$%:g&'+x~~~KK!D6!<=>==$8F#$8E?#8G$%A~Bw6> HHqk)D)9 :))-eC(8 9 XSCBs8= > tc%j4''Bw6>HHqk)D)9 :))-eC(8 9 XSCBs8= > tc%j4''HHq C)Q,G'L Mat1Wo' ( 
D6  o%7K8H7I J#,a<W= >at1Wo &,	- T9fj9tg  Tav=bTAQRSST -h-s+   Al2 <m' 2m$=mm$'m87m8c                    \          F  p  \        V 4       K  	   \        4         \        4        R#   \         d    h \         d:   p\        P                  RT  RT 24       ^ RIq"P                  4         Rp?Ku  Rp?ii ; i  \         d#   p\        P                  RT 24        Rp?LRp?ii ; i  \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)z@Run signal check for all configured pairs. Fully exception-safe.r0  z] Signal check exception: NzLive P&L update failed:  Account snapshot update failed: )r)  rl  rK  rT   rU   rL  r  r   update_live_position_pnlrV   update_account_snapshot)r+  r_   r  s      rC   run_signal_checkrq  	  s    	4!$' 4 "
<!! ! 	 	4II$9!=>1133	4  4.qc2334  <6qc:;;<sI   2
B 
B4 BB.A<<BB1B,,B14C"?CC"c                     ^ RI p ^ RIHp RpV P                  4       pVf   R# \	        VP
                  4      p\	        VP                  4      pWT,
          pWB,
          p\        P                  ! VP                  4      pV\        ^ZR7      ,
          p	V P                  W4      p
V
f   . p
^ ^rRR0p/ pV
 Fu  pVP                  W39  d   K  VP                  V9  d   K*  VP                  '       g   K>  VP                  VP                  VP                  3. 4      P                  V4       Kw  	  \!        VP#                  4       4       F|  w  w  ppp\%        V4      ^8  g   K  V P                  VR7      pV'       g   K7  \%        V4      \%        V4      8  g   KR  V Uu. uF  pVP                  V8X  g   K  VNK  	  upVVV3&   K~  	  RRRR/pR^ R^ /pRpVP'                  4       pVP#                  4        F  w  w  ppp\%        V4      ^8  d   K  \)        R	 V 4       4      pVV;;,          V,          uu&   VV;;,          ^,          uu&   \        P*                  ! \-        R
 V 4       4      4      pVP'                  4       V8X  g   K  VV,          pK  	  RRRR/pR^ R^ /pR- F  p \.        \0        V,          R,          ,          pVP3                  4       '       dL   \4        P6                  ! VP9                  RR7      4      p \%        V 4      VV&   \)        R V  4       4      VV&   K  K  	  / p!/ p"R- F  pVV,          VV,          8X  d)   VV,          ^ 8  d   VV,          V!V&   VV,          V"V&   K@  VV,          V!V&   VV,          V"V&   VV,          VV,          8  g   Ko  \<        P?                  RV RVV,           RVV,           R24       K  	  \)        V"PA                  4       4      p#\-        RV) 4      p$\)        V!PA                  4       4      p%VV%,
          p&\C        V&4      R8  d"   \<        P?                  RVR RV%R RV&R 24       RVPE                  4       R\G        V^4      R\G        V^4      R\G        V^4      R\G        V%^4      R\G        V$^4      RT#RV!P#                  4        U'U(u/ uF  w  p'p(V'\G        V(^4      bK  	  up(p'R V"R!V/
p)\.        R",          pVPI                  R#4      p*V*PK                  \4        PL                  ! V)^R$7      RR7       ^ RI'p+V+PQ                  \S        V*4      \S        V4      4       \<        P?                  R%VR& R'V%R R(V# R)V"R,           R*V"R,           R+V$R& 24       R# u upi   \:         d     EK  i ; iu up(p'i   \T         d     R# \:         d$   p,\<        PW                  R,T, 24        Rp,?,R# Rp,?,ii ; i).u  
Write authoritative MT5 account state + per-pair trade stats to
data/logs/account_snapshot.json so the dashboard server can display
real balance-derived P&L without needing its own MT5 connection.

Strategy — combines two sources to avoid MT5 deal-history quirks:
  • Total P&L from MT5 balance − starting balance (perfectly accurate, net of fees)
  • Per-pair trade count from the JSON trade history files (monitor's
    authoritative record of which positions it executed — immune to
    MT5 occasionally missing a position in history_deals_get grouping)
  • Per-pair P&L: prefer MT5 deal-grouped sum if it matches the JSON
    count, else use the JSON-derived sum (gross) and log a warning
Nr  g     j@r  r   r(   r  rA  c              3   |   "   T F2  qP                   VP                  ,           VP                  ,           x  K4  	  R # 5ir  r  r  s   & rC   r   *update_account_snapshot.<locals>.<genexpr>	  s'     J	1hh'!,,66	r$  c              3   8   "   T F  qP                   x  K  	  R # 5ir  r  r  s   & rC   r   rt  	  s     3NIqFFIs   r%   r5   r6   c              3   D   "   T F  qP                  R ^ 4      x  K  	  R# 5ir  r  r  s   & rC   r   rt   
  s     0Ufy!1D1Dfs    r0  z$] Snapshot: using JSON counts (JSON=z vs MT5=r   g      @z"[snapshot] account balance delta $r  z vs strategy P&L $u    — non-strategy activity $snapshot_atrW  equityfloating_pnl	pnl_total
daily_losstrade_countpair_pnlpair_tradesstarting_balanceaccount_snapshot.jsonrc   rd   zAccount snapshot: balance=$r  u	    · P&L $u    · trades=z (EU:z UJ:u   ) · daily_loss=$z update_account_snapshot failed: r  ),ry  r   r   rV  r[  rW  rx  rf   rp  r   r  rt  r	   r  r  r   r   r   r   r   r  r  r   r@   r)  rN   rO   rP   rQ   rT   rU   r  r  r  rg   r>  rh   ri   rj   rk   rl   rJ   r  rV   )-r  _tzSTARTING_BALANCEacctrW  rx  floatingaccount_balance_deltanow_utcr  r  r  r  our_symbolsr  r  _sym_pid_dl_psmt5_pair_pnlmt5_pair_tradestoday_pnl_mt5rH  r	   r  net
close_timejson_pair_pnljson_pair_tradesr+  r\   r  final_pair_pnlfinal_pair_tradestotal_tradesr{  	total_pnlnon_strategy_pnlr@  vsnapshotro   rp   r_   s-                                                rC   rp  rp  	  s   O<!,$!<&%# !( :,,sww'I2..''7=E)* Avvh22xx{*===""AHHamm#<bAHHK  "&k&7&7&9!:LT4#3x!|++T+:33s8c#h.<? 1FCq45HH4D 23C 1FKt-	 "; $S(C8#Q(A6!,,.)4):):)<%NVTI9~!J	JJC s* F#q(#!//3NI3N0NOJ E)$ *= %c8S9$a8Q7(DT!2;!??;;==!ZZ(HIF-0[$T*-00Uf0U-UM$' ! ) (Dt$(8(>>CSTXCY\]C]*6t*<t$*9$*?!$' +8*=t$*:4*@!$'#D)OD,AAHHq '&&6t&<%=XoVZF[E\\]_ ` ) ,33563/
 --/0	
 19< 3&HH9:OPU9V W))25(9 ://?.FH I
  1 1 3gq 1fa 0h 2i 3j! 4N<P<P<R S<RDAqE!QK<R S 1 0
 00v&tzz(15HCHc$i(.wtn ="5) *'..?.I-J K(23 4  *40	2 	3s1F6  d !T"   <6qc:;;<s    V C
V /A,V  V <V V U63U69C	V V &A>U;$A6V DV +V$B0V 6V ;VV 
V	V W"W+W,W

Wc                 
    ^ RI p \        P                  4        EF  w  r TP	                  R4      '       d   TP	                  R4      f)   R F  pTP                  TR4       K  	  \        T4       KY  \        T,          pTR,          pT P                  TR	7      pT'       g   K  T^ ,          pTP	                  R
4      ;'       g    TP                  pTP	                  R4      p	T P                  TR,          4      p
T
'       g   K  T	^8X  d   T
P                  MT
P                  pTR,          pT	^8X  d   Y,
          T,          MY,
          T,          pRpRpTP	                  R4      pT'       d-   \        T\        4      '       d   \        P                   ! T4      pT'       dL   \        P$                  ! 4       T,
          P'                  4       R,          p\)        ^ TR,          T,
          4      p\+        YR,          4      TR&   \+        \-        TP.                  4      ^4      TR&   \+        T^4      TR&   Te   \+        T^4      MRTR&   Te   \+        T^4      MRTR&   \        T4       \0        P3                  RT RTP.                  R RTR RTR RTR,           R24       EK  	  R#   \         d     R# i ; i  \"         d    Rp EL0i ; i  \"         d(   p\0        P5                  RT RT 24        Rp?EK  Rp?ii ; i)a  
For any open position, query MT5 for current price + unrealized P&L
and persist to the state file so the dashboard can display floating P&L.

Writes these fields into state_{pair}.json when position_open=True:
  current_price   : latest bid/ask
  floating_pnl    : unrealized P&L in account currency (from MT5 position.profit)
  floating_pips   : distance from entry in pips
  bars_held       : hours held so far
  bars_remaining  : hours until hold_hours limit

Removes these fields when no position is open (keeps state clean).
Nr  r  current_pricery  floating_pips	bars_heldbars_remainingr  r  r  r	   r   r  r8  r   r'   r0  z] Live P&L: $+.2fr  r  z pips | held=r:  h/hz] Live P&L update failed: )r  ry  r  r  r  )ry  r  r2  r   rR   r   r6  r)  r  
price_openr  r  r  r   rJ   r   r;  rT   rf   r=  r   r>  r[  r  rU   r  rV   )r  r+  r  r@  r  r  r  r  rB  r#  r  
current_pxr   r  r  r  r  r_   s                     rC   ro  ro  I
  s   ! "'')3	A99_--;L1M1U9AIIa&9 4  &C,-F)))8I#A,C))M2DDcnnH))$56I..s8}=D&/1n$(($((Jj/HDMQRNj3x?#+#8H"D  I!N<0Jj#..&.&<&<Z&H%\\^j8GGIDP	!$QL(9I(E!F&+JD	&BE/"&+E#**,=q&AE.!&+M1&=E/"=F=ReIq&9X\E+BPB\eNA&>bfE"#tHHqmCJJt+<C%d+ ,&sO2c,.?-@C Da *  N %74j7  	AKK!D6!;A3?@@	Ass   J* AK3/K% K=KA)K0KJ<K$C?K*J98J9<K	KKKLK==Lc                    \         R,          pVP                  4       '       d   \        P                  ! VP	                  R4      4      pVP                  RR4      pV'       d    \        P                  ! VP                  RR4      4      p\        P                  ! VP                  4      V,
          P                  4       R,          pVR8:  dH   VP                  R	/ 4      P                  V R
4      pVP                  R/ 4      P                  V ^ 4      pWv3#   \         \        T ,          R,          ,          pTP                  4       '       d&   \        P                  ! TP	                  R4      4      M. p	\        T	4      \        R T	 4       4      3#   \         d     Li ; i  \         d     Li ; i  \         d    Ru # i ; i)z
Return (trade_count, pnl_total) for this pair.

Prefers account_snapshot.json (MT5-derived, includes swap/commission/spread)
over the raw trade history log (which records gross profit only).
Falls back to the log if snapshot is missing or stale.
r  r5   rw  rI   Zz+00:00r8  r   r}  rA  r~  r%   c              3   2   "   T F  qR ,          x  K  	  R# 5ir  rA   r  s   & rC   r   $_pair_trade_stats.<locals>.<genexpr>
  s     =f)fr  )r   rA  )r@   rN   rO   rP   rQ   rR   r   r;  rl   rf   r  r=  rT   r)  r   r  )
r+  	snap_pathsnapsnap_atsnap_dtage_hr}  pair_cntr\   r  s
   &         rC   _pair_trade_statsr  
  sr   55	::i11':;Dhh}b1G&44W__S(5STG%\\'..9GCRRTW[[E|#'88J#;#?#?c#J#'88M2#>#B#B4#K'11 $;t,[998<DNN73426{C=f==== !    sN   "F! >F! %B/F 4F2 AF2 FF! FF! !F/.F/2GGc                 :    V ^ 8  d   RV R 2# R\        V 4      R 2# )r   r5  r  r6  )r  )tots   &rC   _format_pnlr  
  s(    !QhRDz?@bS$,@@rB   c                     \         V ,          pVP                  R 4      '       dW   VP                  R4      pVP                  R4      p\        V ,          R,          pV'       d   RV^8X  d   RMR RVRV R	2 2# R
# R# )r  r  r  r'   u   🔵 r  r  r  r  r  u	   🔵 Openu   ⚪ No position)r2  rR   r)  )r+  r  epr  r'   s   &    rC   _pair_position_strr  
  sw    tEyy!!YY}%YY()t$FHAv733r!B4qkBYkYrB   c                 ~    R \        V ^
,          4      ,          R^
\        V ^
,          4      ,
          ,          ,           # )   █   ░)r>  r?  s   &rC   	_prob_barr  
  s*    uQrT{*5"U1R4[.+AAArB   c                 *    V ^<8  d   R# V ^#8  d   R# R# <   u   🟢u   🟡u   ⚪rA   r  s   &rC   _prob_emojir  
  s    QU6J!R%JUJrB   c                x  a V'       g   R# V R8X  d   RMRpV'       Ed\   \        VR R7      pRRV R2R	.pV F  pVR
,          VR
,          8X  d   VR,          ^8  d   RMRpVR,          '       d'   RP                  R VR,          R,           4       4      MRp	VP                  \        VR,          4      R,           VR,          ,           R,           \	        VR,          4      ,           R,           \        VR,          4      ,           R,           V,           R,           V	,           4       K  	  VR	RVR,          ,           R,           \	        VR,          4      ,           R,           .,          pRP                  V4      # \        V3R lV 4       R4      p
V
'       g`   \        R V 4       R4      p
V
'       g   R# \        P                  ! 4       P                  R4      p\        V
4      p
V
R,          R ,           V
R&   V
R,          '       d'   RP                  R! V
R,          R",           4       4      MR#p	R$V,           R%,           V
R,          ,           R&,           \        V
R,          4      ,           R',           \	        V
R,          4      ,           R(,           \        V
R,          4      ,           R),           V	,           R*,           # )+zDBuild the signal outlook section in the exact user-preferred format.rI   r   EURUSD SIGNAL OUTLOOKUSDJPY SIGNAL OUTLOOKc                     V R ,          # )probrA   )xs   &rC   r  (_build_outlook_section.<locals>.<lambda>
  s    1V9rB   r  u   📅 <b>WEEK AHEAD — r8  r   r   r  u   ⭐events, c              3   2   "   T F  qR ,          x  K  	  R# 5i)eventNrA   r   r_   s   & rC   r   )_build_outlook_section.<locals>.<genexpr>
  s     AAwZZr  Nr   NNo key eventsz <b>r  z</b>  %  r   	   
    └ u   ⭐ Best day: <b>z</b> at %r   c              3   F   <"   T F  qR ,          S8X  g   K  Vx  K  	  R# 5ir   NrA   r   r  	today_strs   & rC   r   r  
  s     @g6i)?11gs   !
!Nc              3   >   "   T F  qR ,          f   K  Vx  K  	  R# 5i)r  NrA   r  s   & rC   r   r  
  s     C'QvYqq's   
%az (next trading day)c              3   z   "   T F1  pR VR,          ,           R,           VR,          ,           R,           x  K3  	  R# 5i)u       • r  r   countryr   NrA   r  s   & rC   r   r  
  s6      
% 7#d*Qy\9C??%s   9;N   Nu!       • No key macro events todayu   
📅 <b>TODAY'S r  u6   </b>
────────────────
z Signal probability: <b>z%</b>
z
Key events:
u1   
────────────────)r   r   r   r  rJ   r  r   r   rf   r\  r1  )r+  outlook	is_mondayr  
pair_labelbestr  r  starevstdday_names   &&&f        rC   _build_outlook_sectionr  
  s   ,0H,<(BYJyG!45%j\6>

 AfIf5!F)b.5bDEFx[[499A8RAAVeCLLAfI&/!E(:XEAfI!&')21V9)=>@CDFJK"#  	>$u+-
:Sf=NNQTT
 	
 yy@g@$GC'CTJB||~..t4HbB5	$99BuI \\ ii 
\"%
 
  C 	
 !:-7"U)C GA A6
#$&@A 6
O (( bj!	"%	
 ""
%??	
rB   c                 4   \         P                  ! \        P                  4      p\        e?   V\        ,
          P                  4       R8  d   V R8X  d   \        P                  R4       R # Vs\         P                  ! 4       P                  R4      p\         P                  ! 4       P                  4       ^ 8H  p\        R,          P                  R4      pVe   VR MRpV'       d   \        V4      R	,          ^d,          R
 R2MRp\        R4      w  rx\        R,          P                  R4      p	V	e   V	R MRp
V	'       d   \        V	4      R,          ^d,          R
 R2MRp\        R4      w  rW|,           pW,           pRpRp ^ RIHpHp T! \#        T;'       g    ^ 4      4      pT! \#        T	;'       g    ^ 4      4      p\%        RVW24      p\%        RVW24      p\+        RV  RV RV R\-        R4       RV R\/        V4       RV
 RV R\-        R4       RV R\/        V4       RV R\/        V4       RV V 24       R #   \&         dO   p\        P)                  RT 24       \         P                  ! 4       P                  R4      pRT R2pRp R p?LR p?ii ; i)N     📊 DAILY SUMMARYu5   Daily summary skipped — sent within last 30 minutesrS  r   r  r:  r7  r   r  z% of thresholdrI   r(   r   get_weekly_outlookget_usdjpy_weekly_outlookzOutlook fetch failed: r  u(   
📅 TODAY'S EURUSD SIGNAL OUTLOOK — u   
────────────────
Calendar unavailable — check log
────────────────z<b>uU   </b>
────────────────
🟢 <b>EURUSD</b>
Z-score: <b>z</b>  (z)
Position: 	
Trades: 
  |  PnL: uQ   
────────────────
🔵 <b>USDJPY</b>
Z-score: <b>u<   
────────────────
Combined: z trades  |  ur   
System: ✅ Running
────────────────
Thresholds: EURUSD ±2.75  ·  USDJPY ±2.00)r   rf   r   rp  _LAST_DAILY_SUMMARY_SENTr=  rU   r  r\  weekdayr2  rR   r  r  execution.dashboard_serverr  r  r[  r  rT   rV   r   r  r  )header_nowr  r  eu_zeu_z_seu_pcteu_neu_totuj_zuj_z_suj_pctuj_nuj_totco_nco_toteu_outlook_textuj_outlook_textr  r  eu_outuj_outrd  r  s   &                       rC   send_daily_summaryr   
  s    <<%D ,00??ADH..HI#''
3I&&(A-I!%%m4D".SzEF:>D	$s"3'~6BF$X.LD!%%m4D".SzEF9=D	#c!#&n52F$X.LD[D_FOO
	
 $E$))!$45*5+;<0fi40fi4 
fX  hgfX .'12 3&
;v#6"7 8 hgfX .'12 3&
;v#6"7 8F,{6':&; <7 

#	  
,RD12<<>**407z B? @ 	 
s    2H> 3%H> >J	AJJc                   aa  ^ RI Hp Hp \
        R,          P                  R4      ;'       g    Rp\
        R,          P                  R4      ;'       g    Rp T ! \        T4      4      pT! \        T4      4      pR	 oR
 oTT3R lpT'       d   TR MRpT'       d   TR MRp\        R4      w  r\        R4      w  rY,           pY,           pT! TRR4      pT! TRR4      p\        RT RT RT R\        T4       RT T 24       R#   \         d    \	        RR7        R# i ; i  \         d    \	        RR7        R# i ; i)u   
Sunday 06:05 UTC — week-ahead outlook replacing the daily summary.
Shows full 5-day signal probability calendar for the coming week.
r  u   📊 WEEKLY OUTLOOKr  Nr   r  rA  r(   c                 b    \        V ^
,          4      pRV,          R^
V,
          ,          ,           # )r   r  r  )r  )pctfilleds   & rC   _bar!send_weekly_summary.<locals>._bar_  s'    S2Xv~f 555rB   c                 *    V ^<8  d   R# V ^#8  d   R# R# r  rA   )r  s   &rC   _icon"send_weekly_summary.<locals>._iconc  s    "9V"9VrB   c                 b  < V '       g   R # \        V R R7      pVP                  R^ 4      pRV R2.pV  F  pVP                  R^ 4      pVP                  RR 4      pVP                  RR 4      p	VP                  RR	4      p
S! V4      pWcJ d
   V^8  d   R
MR pVP                  V RV	 RVR,           RV RS! V4       V RV
 24       K  	  VP                  R4       V^8  d(   VP                  RVP                  R4       RV R24       VP                  RV 24       RP                  V4      # )rI   c                 &    V P                  R ^ 4      # )signal_probabilityr  r  s   &rC   r  @send_weekly_summary.<locals>._build_week_block.<locals>.<lambda>k  s    AEE2F,JrB   r  r  u   
📅 <b>WEEK AHEAD — u5   </b>
────────────────r   r  	top_eventr  u    ⭐r   :   NN  r  r  r   u   ⭐ Best day: z at r  z% = prob z-score crosses r   )r   rR   r   r   )r  rm  threshold_labelbest_daybest_pctr  r  r  r   r  topiconr  r  r	  s   &&&          rC   _build_week_block.send_weekly_summary.<locals>._build_week_blockh  sA   "JKLL!5q9,UG3ijkAUU/3CUU6"%DUU5_CUU;7C3ZD }RVRDLL&#aRzC5DI;tf E%!  	GIr>LL>(,,u*=)>d8*ANO00ABCyyrB   r:  r7  r  u   ±2.75r  u   ±2.00ub   📆 <b>WEEKLY OUTLOOK</b>
────────────────
🟢 <b>EURUSD</b>  z=u   
🔵 <b>USDJPY</b>  z=r  r  u   
System: ✅ Running
)r  r  r  rT   r   r2  rR   r[  r  r   r  )r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  eu_weekuj_weekr  r	  s                    @@rC   send_weekly_summaryr  G  sY   
	
 h##M299cDh##M299cD#E$K0*5;7
6
 0  $c
F#c
F$X.LD$X.LD;D_F(?JG(?JG!!' )!!' )&
;v#6"7 8 ))		u  "78  "78s#   D	 "D' 	D$#D$'EEc                   a \         R,          P                  R4      p \         R,          P                  R4      pV e   V R MRpVe   VR MRpR pV! RV 4      pV! RV4      pR p\        P                  ! 4       P	                  R	4      oR
pR
p	 ^ RIHp
Hp T
! \        T ;'       g    ^ 4      4      pT! \        T;'       g    ^ 4      4      pV3R lpV! V4      pV! V4      p	\        P                  ! 4       P	                  R4      p\        RV RV RV RV! R4       RV RV RV RV! R4       RV	 R24       R#   \         d#   p\        P                  RT 24        Rp?LRp?ii ; i)u1  
Fires at 07:00 UTC (London open). Unlike the 06:05 daily summary which is
a full system report, this is a focused 'today's plan' briefing:
  • Current z-scores with sizing tier info
  • Top 3 scheduled events for each pair today (with times/forecasts)
  • Position status with floating P&L if open
r   r  r(   Nr=  r7  c                     Vf   R# \        V4      p\        V ,          pW#R,          8  d   W#R,          ,          ^d,          pVR R2# VR,           F   w  rVpYRu;8:  d
   V8  g   K  M K  RV R2u # 	  RVR,          R	,          ^,           R2# )
Nr7  r
   r  u%   % to signal (base 1.0× if triggered)r   zwould size u   × at current zu   × (max tier) at current zrC  )r  r)  )r+  zabsZr  r  rN  rO  rP  s   &&      rC   
_tier_text)send_morning_briefing.<locals>._tier_text  s    9U1v$k""[))C/C#YCDD//LBDB$TF/:: 0 S04Q788RSSrB   c                 h   \         V ,          pVP                  R 4      '       g   R# \        V ,          R,          pVP                  R4      ^8X  d   RMRpVP                  R4      pVP                  R4      pVP                  R4      pVP                  R	4      p\        V ,          R
,          pV RVRV R2 2.p	Ve$   V^ 8  d   RMRp
V	P                  RV
 RVR 24       Ve#   V^ 8  d   RMRp
V	P                  V
 VR R24       Ve   V	P                  VR RV R24       RP	                  V	4      # )r  zNo positionr'   r  r  r  r  ry  r  r  r   r  r  r  r  rI   zP&L r  r  r:  r?  r  r  r   )r2  rR   r)  r   r   )r+  r  r'   rG  r  fpnlfpipsheld
hold_limitr  r
  s   &          rC   	_pos_line(send_morning_briefing.<locals>._pos_line  s2   4 yy))-t$!II&78A=&7		-(yy(		/*yy% &|4
9Cat1W~./!)3DLL4vQtDk231*3"DLLD6%A./LLD:R
|156{{5!!rB   rS  zNo calendar datar  c                   < \        V3R  lV  4       R4      pV'       d   VP                  R4      '       g   R# . pVR,          R,           F{  pVP                  RR4      ;'       g    RpVP                  RR4      pV'       d   R	V 2MRpVP                  R
V R
VR,           RVP                  RR4      R,           RV 24       K}  	  RP                  V4      # )c              3   V   <"   T F  qP                  R 4      S8X  g   K  Vx  K   	  R# 5ir  r  r  s   & rC   r   ?send_morning_briefing.<locals>._format_today.<locals>.<genexpr>        H'QUU6]i-Gqq'   )
)Nr  zNo key events todayr  rm  rI   z--:--forecastu	    · Fcst r  r  r   r  r  r   r   )r   rR   r   r   )r  r  r  r_   time_strfcfc_partr  s   &      rC   _format_today,send_morning_briefing.<locals>._format_today  s    H'H$OBRVVH--,E\"%%55,77UU:r*.0IbT*br(2aj\AEE)B<OPR<S;TTUV]U^_`	 &
 99U##rB   z'Morning briefing outlook fetch failed: z	%A, %d %Bu   🌅 <b>MORNING BRIEFING — uL   </b>
────────────────
🟢 <b>EURUSD</b>  z=z
   z
   Position: z
   Events today:
uH   
────────────────
🔵 <b>USDJPY</b>  z=u`   
────────────────
London session: 07:00–17:00 EET · Good luck)r2  rR   r   rf   r\  r  r  r  r[  rT   rU   rV   r   )r  r  r  r  r   eu_tieruj_tierr'  eu_today_eventsuj_today_eventsr  r  r  r  r2  r_   r  r  s                    @rC   send_morning_briefingr8    s    h##M2Dh##M2D#/T{UF#/T{UF
T 4(G4(G", ''
3I(O(OC	
 $E$))!$45*5+;<
	$ (/'/ ||~&&{3H
'z 2!!' )Y !(+, -,- .!!' )Y !(+, -,- .9	:  C=aSABBCs$   D< 'D< !D< <E)E$$E)c                    V'       d	   V '       g   R# VP                  R4      w  r#\        P                  ! V R4      P                  \	        V4      \	        V4      R7      p\        P
                  ! \        P                  4      P                  RR7      p^VP                  u;8  d   ^8  d   M M^M^ pV\        VR7      ,           pWG,
          P                  4       ^<,          pV#   \         d     R# i ; i)z
Calendar times from FinanceFlow are already converted to UK time by the
dashboard_server's _fetch_te_calendar. Compare to current UK time.
Returns minutes until event (negative if already past), or None if unparseable.
N:rS  )r  minuter  r  )rL   r   strptimerl   r  rf   r   rp  r  r   r=  rT   )	event_date_strevent_time_strhhmmevent_dtr  	uk_offsetnow_uk	delta_mins	   &&       rC   _minutes_until_event_ukrE    s    ^%%c*$$^Z@HHRR I 
 ,,x||,44D4AW]]/R/AQ	9955&557"<	 s   C' C' CC' 'C65C6c                 z  aaa  ^ RI Hp Hp \        P                  ! 4       P                  R4      o\        R,          P                  R4      ;'       g    Ro\        R,          P                  R4      ;'       g    RoTTT3R	 lp T ! S4      pT! TR4        T! S4      pT! TR4       R#   \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i  \         d#   p\        P                  R
T 24        Rp?LmRp?ii ; i  \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)a   
Run every 5 minutes. Scans today's cached weekly outlook for any event
with score >= 80 (FOMC, ECB rate decision, CPI, NFP, BoJ) that's
scheduled to release in the next 30-35 minutes.
Fires one Telegram alert per event (deduplicated via _pre_event_sent).
r  zPre-event alert import failed: NrS  r   r  rA  r(   c                   <  \        V3R  lV  4       R4      pV'       d   VP                  R4      '       g   R# VR,           EF   pVP                  R^ 4      pV^P8  d   K  VP                  RR4      pV'       g   K;  \        SV4      pVf   KM  ^Tu;8:  d   ^#8:  g   M K`  S RV RVP                  RR4       2pV\        9   d   K  \        P	                  V4       VR	8X  d   SR
rMSRrV'       d   \        V4      V	,          ^d,          M^ p
V'       d   V	\        V4      ,
          MT	pV'       d   RVR RV
R RVR R2MRpVP                  RR4      pVP                  RR4      p. pV'       d   VP                  RV 24       V'       d   VP                  RV 24       V'       d   RP                  V4      MRp\        R\        \        V4      4       RVP                  RR4       RVP                  RR4       RV RV R V R!V R"V 24       \        P                  R#V RVP                  RR4       R$VR R%24       EK  	  R#   \         d'   p\        P                  R&T R'T 24        Rp?R# Rp?ii ; i)(c              3   V   <"   T F  qP                  R 4      S8X  g   K  Vx  K   	  R# 5ir  r  r  s   & rC   r   8check_pre_event_alerts.<locals>._scan.<locals>.<genexpr>%  r,  r-  Nr  scorerm  rI   r   |r  r   r   r   zz = r=  r   r  z% of threshold, r  u   σ away)zz unavailabler.  previouszFcst zPrev r   zNo consensusu   ⏰ <b>EVENT IN ~u:    MIN</b>
────────────────
?r  z)
Time: u    UK · Impact: z/100
u2   
────────────────
z
 current: zPre-event alert fired: z in r	  zPre-event scan failed for r  )r   rR   rE  _pre_event_sentrS   r  r   r   r   r  r>  rU   r  rT   rV   )r  r  r  evrJ  ev_timeminsr  r  thz_pctgap	z_contextr0  prev	ctx_partsr   r_   r  r  r  s   &&                rC   _scan%check_pre_event_alerts.<locals>._scan#  sw   1	HH'H$OBRVVH--llw*2:&&,.y'B<d(b("1WIQrvvgb/A.BC/)##C() $r #r-.Ac)A%&b3q6kB !  $Ad82eC[8HT
RZ['6  VVJ+vvj"-	y''%t5))E$.909fkk),~'E$K(8'9 :Ivvgc*+2bffYs.C-D E$I_UG6e I!l*YK9 2:,awr@R?SSWX\]`WaadefW #X  	HKK4ZL1#FGG	HsN   I
 I
 AI
 <A8I
 5"I
 I
 4>I
 3I
 I
 +BI
 
I;I66I;zEURUSD pre-event scan failed: zUSDJPY pre-event scan failed: )r  r  r  rT   rU   rV   r   rf   r\  r2  rR   )	r  r  r_   rX  r  r  r  r  r  s	         @@@rC   check_pre_event_alertsrZ    s   	
 ''
3Ih##M299cDh##M299cD2Hj:#D)fh:*40fhI  5aS9:@  :4QC899:
  :4QC899:sG   B+ C D +C6CCD	'DD	D:D55D:c                    aaa \         P                  R84       \         P                  R4       \        P                  4        FH  w  r\         P                  RV  RVR,           RVR,          R RVR	,          R R
VR,           R24       KJ  	  \         P                  R84        ^ RIpVP                  4       '       g=   \         P                  RVP                  4        24       \        P                  ! ^4       \         P                  RVP                  4       P                   24       \         F  p \        V 4       K  	  \        ^ZR7       \!        4        R oV3R lpV3R lpR pR p\#        R4      P%                  4       oV3R lp/ oV3R lp\&        P(                  ! ^4      P*                  P-                  V4       \         P                  R4       \&        P(                  ! 4       P.                  P1                  R4      P-                  V4       \&        P(                  ! 4       P.                  P1                  R4      P-                  V4       \&        P(                  ! 4       P.                  P1                  R4      P-                  V4       \&        P(                  ! 4       P.                  P1                  R4      P-                  V4       \&        P(                  ! 4       P.                  P1                  R4      P-                  V4       \&        P(                  ! 4       P2                  P1                  R 4      P-                  V4       \&        P(                  ! 4       P2                  P1                  R!4      P-                  V4       R" p	\&        P(                  ! 4       P2                  P1                  R#4      P-                  V	4       R$ p
\&        P(                  ! ^4      P*                  P-                  V
4       R% p\&        P(                  ! ^4      P4                  P-                  V4       \         P                  R&4       \         P                  R'4       \         P                  R(4       \         P                  R)4       \         P                  R*4       \         P                  R+4       \         P                  R,4       \         P                  R-4       \7        4        ^ RIpVP;                  R. R/R07      pVP=                  4        ^ p^
p  \&        P>                  ! 4        ^ p\F        PH                  ! ^4       K1    \         d    \         P                  R4        EL/i ; i  \@         dn   pT^,          p\         P                  R1T R2T 24       Y8  d:   \         P                  R34       \C        R4T R5\E        T4      R6,           R724       ^ p Rp?LRp?ii ; i)9=u*   LIVE SIGNAL MONITOR v3 — EURUSD + USDJPYr  u   : threshold=±r
   z TP=r   z.2%z SL=r   z hold=r   r  NzMT5 init failed: zMT5 connected | terminal=u8   MetaTrader5 not installed — running in simulation mode)r  c                   \        V 4        \        4        R#   \         d    h \         d&   p\        P	                  RT  RT 24        Rp?LCRp?ii ; i  \         d$   p\        P                  RT 24        Rp?R# Rp?ii ; i)al  Run signal check for a single pair, exception-safe.

Also refreshes the account snapshot after each signal check so the
dashboard never sees more than ~15 minutes of staleness. Previously
update_account_snapshot() was only called from the legacy combined
run_signal_check() function which only ran on startup, leading to
15+ hour stale snapshots on the dashboard.
r0  z] signal_check job error: Nrn  )rl  rK  rT   rU   rL  rp  rV   )r+  r_   s   & rC   _safe_signal_check_pair%main.<locals>._safe_signal_check_pair  st     #4(
	@#% !'%Qsyy1TF2LQC)PQQQ
  	@KK:1#>??	@s0    
A AAAAB"B  Bc                     < S ! R 4       R# )r   NrA   r^  s   rC   _safe_signal_check_eurusd'main.<locals>._safe_signal_check_eurusd      )rB   c                     < S ! R 4       R# )r(   NrA   ra  s   rC   _safe_signal_check_usdjpy'main.<locals>._safe_signal_check_usdjpy  rd  rB   c                     \        4        R#   \         d    h \         d$   p \        P	                  RT  24        Rp ? R# Rp ? ii ; i)u8   Legacy combined check — kept for startup priming only.zsignal_check job error: N)rq  rK  rT   rU   rL  r_   s    rC   _safe_signal_check main.<locals>._safe_signal_check  s2     '%Hsyy+CA3)GHHH   
 AAAAc                      \        4        R#   \         d    h \         d$   p \        P	                  R T  24        Rp ? R# Rp ? ii ; i)zrate_update job error: N)rM  rK  rT   rU   rL  ri  s    rC   _safe_rate_updatemain.<locals>._safe_rate_update  s2     '%Gsyy+B1#)FGGGrl  r  c                  ^   < ^ RI p V3R lpV P                  VRR7      P                  4        R# )r   Nc                     < S ;_uu_ 4        \         P                  ! 4       P                  4       ^8X  d   \        4        M
\	        4        RRR4       R#   + '       g   i     R# ; i)   N)r   rf   r  r  r   )_summary_locks   rC   _run/main.<locals>._safe_daily_summary.<locals>._run  s7    <<>))+q0')&(	 s   >AA%	Tr5  r  r  Threadr  )_t2rt  rs  s     rC   _safe_daily_summary!main.<locals>._safe_daily_summary  s&    	) 	

$t
,224rB   c                 ^   < ^ RI p V3R lpV P                  VRR7      P                  4        R# )u  
Runs every 5 minutes inside the existing monitor process.
Checks:
  1. State files updated recently (detects hung/frozen state)
  2. Rate CSVs updated within 36h (detects data pipeline failure)
  3. MT5 connection still alive (detects broker disconnect)
  4. Open positions still exist in MT5 (detects orphaned state)
Sends Telegram alerts on issues — no separate process needed.
Nc                  ^  < \         P                  ! \        P                  4      p \         F  p\
        \        V,          R ,          ,          pVP                  4       '       g   K:  V P                  4       VP                  4       P                  ,
          R,          pVR8  g   Ky  RV 2pSP                  V4      pVe   W,
          P                  4       R8  g   K  V SV&   \        P                  RV RVR R	24       \        R
V RVR R24       K  	  RRRRRR/p. pVP                  4        F  w  r\         V	,          p
V
P                  4       '       g   VP#                  V R24       K>  V P                  4       V
P                  4       P                  ,
          R,          pV^$8  g   K}  VP#                  V RVR R24       K  	  V'       dn   RpSP                  V4      pVe   W,
          P                  4       R8  d<   V SV&   \        P                  RV 24       \        RRP%                  V4       R24        ^ RIpVP)                  4       '       gX   RpSP                  V4      pVe   W,
          P                  4       R8  d&   V SV&   \        P                  R4       \        R4        ^ RIp\         EFJ  p\,        T,          pTP                  R 4      '       g   K*  TP                  R!4      '       g   KC  TR!,          pTP/                  TR"7      pT'       d   Kh  \
        \        T,          R ,          ,          pTP                  4       '       d@   T P                  4       TP                  4       P                  ,
          ^<,          pT^Z8  d   K  R#T R$T 2pSP                  T4      pTe   Y,
          P                  4       R8  g   EK  T ST&   \        P                  RT R%T R&24       \        R'T R(T R)24       EKM  	  R#   \*         d     ELji ; i  \*         d     R# i ; i)*r&   r8  r   stale_state_Ni   z[WATCHDOG] z state file stale (r:  zh)u   ⚠️ <b>[WATCHDOG] z$ STATE FILE STALE</b>
Last updated: zVh ago
Monitor may be running but not writing state.
Check live_monitor.log for errors.r  r    r  r  rT  r+   z
 (missing)r   r  zh old)stale_ratesz[WATCHDOG] Stale rate files: u0   ⚠️ <b>[WATCHDOG] RATE DATA STALE</b>
Files: r  u`   
FinanceFlow pipeline may have failed.
Signals continue on last known rates — monitor closely.mt5_disconnectr  z&[WATCHDOG] MT5 terminal not respondingu   ⚠️ <b>[WATCHDOG] MT5 DISCONNECTED</b>
Terminal not responding.
Attempting reconnect on next signal check.
Check open positions in MT5 manually.r  r  r  orphan_rQ  u    orphaned state — ticket z not in MT5u   🔴 <b>[WATCHDOG] z4 ORPHANED POSITION</b>
State file says open (ticket zv)
MT5 has no matching position.
The trade was likely closed externally.
Monitor will detect this on next hourly check.)r   rf   r   rp  r)  r@   rN   r  statst_mtimerR   r=  rU   rV   r   r   rW  r   r   ry  terminal_inforT   r2  r  )rf   r+  sfr  r  r  r   r  r   r  rfr  r  r  r  
sf_age_min_last_watchdog_alerts                   rC   _run_watchdog;main.<locals>._watchdog_health_check.<locals>._run_watchdog  s   ,,x||,C#{40>>yy{{2779+=+==E3;(/C/33C8D|
'A'A'Cd'J47,S1k$7J5QT+UW$XY%3D6 :--23K 8AB $$ !*fj&*UJK)//1&yy{{&&$z':;2779+=+==E2:&&$r%F'CD  2 #+//4<CJ#=#=#?$#F03(-KK"?} MN!""&))K"8!9 :TU)((***C/33C8D|
'A'A'Cd'J47,S1$LM%D")'KD&t,Eyy11eii@Q6R6R!&'8!9$'$5$5V$5$D	(y ");t+<\+J!JB!yy{{.1mmo	@R@R.RVX-X
#-?$,$+D66(";C#7#;#;C#@D#|
/I/I/Kd/R<? 4S 9 #&1$ 8..4X[%B!" !.&9$ @DDJ8 LU%V!"1 (  P  sJ   >P AP 0/P $P = P "4P A4P 8P PPP,+P,Trv  rw  )_wtr  r  s     rC   _watchdog_health_check$main.<locals>._watchdog_health_check  s*     	 c	L 	

-
5;;=rB   u9   Inline watchdog enabled — health checks every 5 minutesz:01z:16z:31z:46z:02z06:00z06:05c                  V    ^ RI p R pV P                  VRR7      P                  4        R# )r   Nc                  |    \        4        R#   \         d$   p \        P                  R T  24        Rp ? R# Rp ? ii ; i)zmorning briefing error: N)r8  rT   rU   rL  ri  s    rC   rt  2main.<locals>._safe_morning_briefing.<locals>._run:  s*    &(L399/Gs-K#L#LL   
 ;6;Trv  rw  )_t3rt  s     rC   _safe_morning_briefing$main.<locals>._safe_morning_briefing8  s&    	M 	

$t
,224rB   z07:00c                  |    \        4        R#   \         d$   p \        P                  R T  24        Rp ? R# Rp ? ii ; i)zpre-event alert error: N)rZ  rT   rU   rL  ri  s    rC   _safe_pre_event_check#main.<locals>._safe_pre_event_checkA  s*    #%Gsyy+B1#)FGGGr  c                  V    ^ RI p R pV P                  VRR7      P                  4        R# )r   Nc                  |    \        4        R#   \         d$   p \        P                  R T  24        Rp ? R# Rp ? ii ; i)zTelegram poll error: N)r   rT   rU   r   ri  s    rC   rt  /main.<locals>._safe_telegram_poll.<locals>._runJ  s*    ')I399/DQC-H#I#IIr  Trv  rw  )_tptrt  s     rC   _safe_telegram_poll!main.<locals>._safe_telegram_pollH  s&     	J 	4-335rB   zScheduler runninguO     EURUSD signal check: every 15min (:01, :16, :31, :46) — Formula A validateduA     USDJPY signal check: every hour at :02 — daily spread z-scorez   Rate update daily at 06:00 UTCz%  Daily Telegram summary at 06:05 UTCz3  Morning briefing daily at 07:00 UTC (London open)u@     Pre-event alerts: every 5min, 30min before score≥80 releasesz  Telegram commands: polled every 30s (/add /remove /list /requestaccess /announcement /requestdashboard /grantdashboard /revokedashboard /listdashboard)c                      \        R R7      # )u'   🚀 MONITOR STARTED (v3 — Formula A)r  )r   rA   rB   rC   r  main.<locals>.<lambda>b  s    )1Z[rB   Trv  zScheduler loop error #r  u<   Too many consecutive errors — sending alert and continuingu5   ⚠️ <b>MONITOR WARNING</b>
Scheduler loop error ×z
Last error: :Nd   Nu.   
Monitor is still running — investigate log.z<============================================================)%rU   r  r)  r   ry  
initializerL  r  sysexitr  r   r  rV   rE  r  rM  
__import__Lockscheduleeveryminutesdor  atr  secondsrq  r  rx  r  run_pendingrT   r   rJ   rm  r(  )r+  r  r  rb  rf  rj  rn  rz  r  r  r  r  
_threading_t_consecutive_errors_MAX_ERRORS	_loop_errr  r^  rs  s                    @@@rC   mainr  d  s   HHVHH9: &&(	2dV>#k*:); <8}S)c*oc-B C\*+1. 	/ ) HHVP!~~II)#..*:);<=HHQK,S->->-@-E-E,FGH
 4  %26 @&**IH
 {+002M	5 q>f NN1  !78HHHI
 NNU#&&'@ANNU#&&'@ANNU#&&'@ANNU#&&'@ANNU#&&'@ANNG$''(9:NNG$''(;<5 NNG$''(>?H NN1  !676 NN2!!"56HH !HH^_HHPQHH/0HH45HHBCHHOPHH R S
  #			[ 
 
B HHJ
 K
	(  ""# 	

1Q  PNOPx  	(1$II./B.C2i[QR"1		XY..A-B C##&y>$#7"8 9DE '(#	(s,   $BT6 U 6 UUW(A#WW__main__))r         @rS  )r        @r   )r       X@r   )r    r  r  r  )r  r  ))r         @rS  )r  r  r   )r  r  r   )r  rT  )Fr  )rI   rI   )r  )r4  )   ))z$300krS  z73-85%)z$150kg      ?z91-95%)z$100kg1ZGU?z92-97%)rI   )Z   )r  ){__doc__rk   r  rm  rO   loggingr  urllib.parser   urllib.requestpathlibr   r   r   r   r   numpyr  pandasrY  r  __file__resolver3   rz  rJ   r\   insertr)  rX  r  r  rW  r@   r|  r  basicConfigINFOStreamHandlerFileHandler	getLogger__name__rU   getenvr;   r<   r?   rD   r`   rq   rt   ru   rw   r   rx   r{   r~   r   r   r   r   r   r   r   r   r   r   r'  r2  r3  rI  r  r,  r6  rE  rM  ri  r  r  r  r  r  r  r  r  r  rQ  rj  rl  rq  r  r  r  r  r  r  r  r  r  r  r  r  r?  r  r2  rl  rq  rp  ro  r  r  r  r  r  r  r   r  r8  rM   rN  rE  rZ  r  )r@  s   0rC   <module>r     sp  9v 
 
        8 8   N""$,,Q/	y5)HHOOAs9u,-.  "("$" 	%" 	"	"
 	&" 	&" 	#" 	!" 	"" 	""  	!"" 	##"$ 	d%", 	2-". 	E/"0 	S1"2 	'3"4 	L**%5-!C"F  (# 	% 	"	
 	& 	& 	$ 	! 	"$ 	"%& 	'( 	D)* 	N+, 	%-. 	:z2/0 	*12 	/34 	#5-!;ICL F"U*W4	 f
v
% dT *      
,,4 	G&;;gN		 ! 99-1ab 99/>   (93 YY324  !   %
:%
&"2HD   !0FH^V	 *55A5
)45A5
D>  58(.;b( ?F2IV&P 6\|JXFR4)d
+Z!F#,	0
#Q"h@2a0J-*> 
r)hWr
<2]<~GAT"HA B J2
hJZL^_D %,Q:hXt zF yO 65s   M4M
