o
    ̿Sic                  
   @   s  d dl Z d dlZd dlZd dlmZ d dlmZ d dlmZ d dlmZ d dl	m
Z
mZmZmZmZ d dlmZ d dlmZ d d	lmZ d d
lmZ d dlmZ d dlmZ d dlmZ eeZdLddZ e   e!dd" Z#e$e!ddZ%e$e!ddZ&e$e!ddZ'e!ddZ(e!ddZ)e$e!ddZ*e$e!dd Z+e$e!d!d"Z,ed#Z-e . Z/e!d$d%pd%0 " d&vZ1dMd'ee de2fd(d)Z3dMd'ee dee fd*d+Z4d,ed-ede$fd.d/Z5d0e6d1e$d2e$de7e$e$f fd3d4Z8dMd'ee de2fd5d6Z9dee6 fd7d8Z:deee6 e
e6e6f f fd9d:Z;dee6 fd;d<Z<dee6 fd=d>Z=dee6 fd?d@Z>dee fdAdBZ?dCe6ddfdDdEZ@dCe6ddfdFdGZAdNdIe$ddfdJdKZBdS )O    N)RotatingFileHandler)datetime)	timedelta)ZoneInfo)DictAnyListOptionalTuple)get_mongo_db)ZerodhaClient)analyze_symbol_service) process_signal_and_market_update)#upsert_intraday_alert_from_snapshot)normalize_confidence)ist_date_strreturnc                  C   s2  zt jt jt jtd} t j| dd}t d|}|s#W dS t jt j|}|r6t j|dd tt	j
D ]}t|trRt|ddt j|krR W dS q;t|tt d	td
patd
tt ddpjdd}|tj |tjdd t	| dt	_W dS  ty   t	jddd Y dS w )zAttach a rotating file handler for GlobalIntraday logs.

    Uvicorn reload can re-import modules; avoid adding duplicate handlers.
    z../../..logszglobal_intraday.logGLOBAL_INTRADAY_LOG_PATHNT)exist_okbaseFilenameGLOBAL_INTRADAY_LOG_MAX_BYTESi  P  GLOBAL_INTRADAY_LOG_BACKUP_COUNT5)maxBytesbackupCountz4%(asctime)s - %(name)s - %(levelname)s - %(message)s)fmtz-[GlobalIntraday] Failed to attach file loggerexc_info)ospathabspathjoindirname__file__getenvmakedirslistloggerhandlers
isinstancer   getattrintstrsetLevelloggingINFOsetFormatter	Formatter
addHandler	propagate	Exceptiondebug)api_rootdefault_pathlog_pathlog_dirhhandler r=   ?/var/www/html/Trade-python/app/v1/background/global_intraday.py"_setup_global_intraday_file_logger   s:   "
r?   GLOBAL_ZERODHA_EMAILzkumar@movex.aiGLOBAL_INTRADAY_MAX_ANALYSES120%GLOBAL_INTRADAY_FULL_INTERVAL_SECONDS900+GLOBAL_INTRADAY_EARLY_FAST_INTERVAL_SECONDS300'GLOBAL_INTRADAY_EARLY_FAST_WINDOW_STARTz09:15%GLOBAL_INTRADAY_EARLY_FAST_WINDOW_ENDz10:00!GLOBAL_INTRADAY_ET_LIMIT_PER_SIDE20"GLOBAL_INTRADAY_EARLY_MOVERS_LIMIT40GLOBAL_INTRADAY_MANUAL_LIMIT50zAsia/Kolkata$GLOBAL_INTRADAY_ENFORCE_MARKET_HOURS1)0falsenooffnow_utcc                 C   s   ddl m} || S )zBBackward-compatible wrapper for configurable backend market hours.r   )is_backend_market_hours)app.v1.utils.market_timerV   )rU   rV   r=   r=   r>   _is_market_hours_ist`   s   rX   c                 C   s   ddl m} | pt }z|jtddt}W n ty'   t	t}Y nw | }|j|j
|jddd}||krA|tdd }| dkrT|tdd }| dksG|S )	z0Return the next backend market open time in IST.r   )backend_market_windowUTCtzinfohourminutesecondmicrosecond   )days   )rW   rY   r   utcnowreplacer   
astimezoneISTr5   nowstart_hstart_mr   weekday)rU   rY   ri   ist_noww	candidater=   r=   r>   _next_backend_market_open_istg   s   rp   dt_fromdt_toc                 C   s6   z
t ||   }W n ty   d}Y nw td|S )Nr   rd   )r,   total_secondsr5   max)rq   rr   secr=   r=   r>   _seconds_until   s   
rv   s	default_h	default_mc                C   s   zP| pd  }d|vr||fW S |dd\}}t|}t|}d|  kr*dkrBn nd|  kr6dkrLn n||fW S W ||fS W ||fS W ||fS W ||fS  ty\   Y ||fS w )N :rb   r      ;   )stripsplitr,   r5   )rw   rx   ry   rawhhmmr;   mr=   r=   r>   _parse_hhmm   s*   
0
r   c           	      C   s   | pt  }z|jtddt}W n ty!   t t}Y nw tt	ddd\}}tt
ddd\}}|j||ddd}|j||ddd}||  koO|k S   S )	NrZ   r[   	      )rx   ry   
   r   r]   )r   re   rf   r   rg   rh   r5   ri   r   EARLY_FAST_WINDOW_STARTEARLY_FAST_WINDOW_END)	rU   ri   rm   shsmehemstartendr=   r=   r>   _in_early_fast_window   s   r   c           
      C   sd  t  }| tdd }|d|i}|s|ji dgd}g }t|ts%|S t|dtr2|dni }t|dtrA|dng }t|dtrP|dng }|D ]&}t|trz|d	pad
 rz|d	pjd
 	 }	|	rz|	|vrz|
|	 qT|D ]&}t|tr|d	pd
 r|d	pd
 	 }	|	r|	|vr|
|	 q}tdkr|dtt }|S )zALoad today's (or latest) early movers snapshot symbols (DB-only). EARLY_MOVERS_SNAPSHOT_COLLECTIONearly_movers_snapshotsdate)r   )sorttopbullishbearishsymbolrz   r   N)r   r   r%   find_oner*   dictgetr'   r~   upperappendEARLY_MOVERS_LIMITr,   )
dbist_date
early_collem_snapoutr   r   r   itsymr=   r=   r>   _load_early_movers_symbols   s4   


r   c                 C   s  g }i }t dkr||fS t| d ddiddddddgtt }t| d dd	iddddddgtt }g }|| D ]}|d
}t|tr]|r]||vr]|	| qF|sd||fS t| d d
d|iidddd}i }	|D ]}
|
d
}|
dpd
  }|r|r||	t|< qz|D ]!}|d
}|r|	t|nd}|r||vr|	| d||< q|D ]!}|d
}|r|	t|nd}|r||vr|	| d	||< q||fS )z4Load ET movers symbols from `live_movers` (DB-only).r   live_movers
mover_typeGAINERrb   )_idstock_idrank)r   rb   )last_updatedr   LOSERr   stocks$in)r   r   r   r   rz   N)ET_LIMIT_PER_SIDEr'   findr   limitr,   r   r*   r-   r   r~   r   )r   symbolssource_by_symbolgainerslosers	stock_idsrsidr   	sym_by_idrw   r   r=   r=   r>   _load_et_symbols_and_source   s^   





$




r   c                 C   s   t dkrg S t }g }| d |dddddddg}|D ]#}t|tt kr- |S |d	p3d
  }|rC||vrC|	| q |S )zFLoad distinct manual/watchlist symbols for today's IST date (DB-only).r   user_portfolio_itemsACTIVE)r   statusrb   )r   r   )
updated_atr   )
created_atr   r   rz   )
MANUAL_LIMITr   r   r   lenr,   r   r~   r   r   )r   r   r   curr   r   r=   r=   r>   _load_manual_symbols   s"   

r   c                 C   s4   | d  dti}|stdt dS t|dS )u   Resolve the global Zerodha user from email.

    We treat this user (kumar@movex.ai by default) as the single
    owner of the Zerodha connection and the logical "owner" of
    background GPT analyses. No per‑frontend user computation.
    usersemailzE[GlobalIntraday] Global Zerodha user %s not found in users collectionNr   )r   r@   r(   errorr-   r   )r   user_docr=   r=   r>   _get_global_user_id  s
   r   c                 C   s8   | d  dti}|sdS |d}|durt|S dS )z6Resolve the global Zerodha user's account_id (string).r   r   N
account_id)r   r@   r   r-   )r   r   acctr=   r=   r>   _get_global_account_id$  s
   
r   c              
   C   s   t | }|sdS | d d|i}|std| dS |d}|s*td| dS t|d|d|d	}z|  W |S  tyX } ztd
|| W Y d}~dS d}~ww )zBuild ZerodhaClient from the global user's stored settings.

    This reuses the existing `zerodha_settings` collection: we look up
    the record where `user_id` matches the global user's Mongo _id.
    Nzerodha_settingsuser_idz:[GlobalIntraday] Zerodha settings not found for user_id=%saccess_tokenzC[GlobalIntraday] Zerodha access_token missing for global user_id=%sapi_key
api_secret)r   r   r   zG[GlobalIntraday] Zerodha profile check failed for global user_id=%s: %s)r   r   r(   r   r   r   get_profiler5   )r   r   settingsr   clienter=   r=   r>   _get_global_zerodha_client.  s8   

	r   modec          +      C   s~  t d| t| }|st d dS t| }t| \}}t| }|p$d  }|dkr9|}dd |D }	d}
n%t	t
g |||}i d	d |D |}	|D ]}|	|d
 qSd}
d}tdkrhtt}t d|t|t|t|t|t	r~t|nd|durt|nd |st d dS t	| d d|iddddddd}i }|D ]}|dpd  }|r|||< qg }|D ],}||}t|t
sq|ddu rq|d}t|tr| r|| |f q|st d dS t| }|st d dS t| }d}d}d}d}g }g }g }ttdd}|dkr2t ttd|d nd}|D ]\}}|durR||krRt d|t||  n|durz,| d j|d |id!d"d#gddd$d%}|r|d7 }t|d&k r|| W q6W n ty   t  d'| Y nw |d7 }zt!| ||g d(d)|
|d*d+} W n+ ty }! zt  d,||! |d7 }t|d-k r|| W Y d}!~!q6d}!~!ww || |	|pd.}"t| t
r| d/nd}#t| t
r| d0nd}$t"t| t
r| d1ndt| t
r| d2ndt| t
r| d3ndd4}%t| t
r-|%| d1< t| t
r8| d5nd}&d}'t|&t	rI|&rI|&d }'|t | d6|%| d7| d8| d9pg| d:pg|'| d;|"| |#|$d<}(d})z| d #|(}*t|*j$})W n ty   t  d=| Y nw z$|)rt%| |)|||"t| t
r| ni t|#t
r|#nd|(d>d? W n ty   t j&d@|d*dA Y nw zt'| ||||| t|#t
r|#nd|"dB|)|(d>|dC W n ty   t  dD| Y nw |d7 }q6t dEt||||||durt|ndt| |r#t dFt|| |r/t dGt|| |r=t dHt|| dS dS )IzRun one intraday analysis cycle.

    IMPORTANT:
    - DB-only list reads (early movers snapshot, live_movers, manual watchlist).
    - Must NOT scrape ET or rebuild live_movers.
    z&[GlobalIntraday] Cycle start | mode=%szE[GlobalIntraday] Global Zerodha client unavailable; skipping analysisNrz   
early_onlyc                 S      i | ]}|d qS EARLY_MOVERSr=   .0rw   r=   r=   r>   
<dictcomp>o      z'_run_intraday_cycle.<locals>.<dictcomp>global_intraday_early_fastc                 S   r   r   r=   r   r=   r=   r>   r   t  r   MANUALglobal_intradayr   zM[GlobalIntraday] Universe | mode=%s early=%d et=%d manual=%d unique=%d cap=%s	UNLIMITEDz&[GlobalIntraday] No symbols to analyzer   r   NSE)r   exchangerb   )r   r   r   instrument_tokenr   r   r   z2[GlobalIntraday] No eligible stocks in master listz<[GlobalIntraday] Global user_id missing, cannot tag analyses!GLOBAL_INTRADAY_FRESHNESS_MINUTESrQ   )minuteszY[GlobalIntraday] Reached per-cycle cap (max=%d); stopping early | eligible=%d analyzed=%dstock_analysis_snapshotsz$gte)r   	timestamp)r   r   )r   r   )r   r   )r   
projection   z.[GlobalIntraday] Freshness check failed for %s)5minute15minute30minutedayweekmonthzgeneral intraday analysisT)r   zerodha_clientr   
timeframesquestioncontextr   include_market_dataz+[GlobalIntraday] Analysis failed for %s: %sr   	ET_MOVERSmarket_datafeatures
confidencedecision_probabilityscore)r   r   targetsdecisionentry_price	stop_lossprice_targettarget	rationale)r   r   r  r   entryr  r  reasonsourceanalysisr   r   z;[GlobalIntraday] Failed to persist analysis snapshot for %sr   )snapshot_idr   r   r	  r
  r   snapshot_timestampz4[GlobalIntraday] Alert materialization failed for %sr   r   )r   r   r   r   r   r
  r   r	  preferred_timeframer  r  r   z7[GlobalIntraday] Paper trading simulation failed for %sz}[GlobalIntraday] Cycle finished | eligible=%d attempted=%d analyzed=%d skipped_fresh=%d failed=%d cap=%s freshness_minutes=%sz7[GlobalIntraday] Skipped (fresh) symbols (first %d): %sz+[GlobalIntraday] Succeeded symbols (%d): %sz.[GlobalIntraday] Failed symbols (first %d): %s)(r(   infor   r   r   r   r   r~   lowerr'   r   fromkeys
setdefaultMAX_ANALYSES_PER_CYCLEr,   r   r*   r-   r   r   r   r   r   r   r   r%   r   re   r   rt   r   r5   	exceptionr   r   
insert_oneinserted_idr   r6   r   )+r   r   r   
early_symset_symset_source_by_symmanual_syms	mode_normr   r   r   rw   capr   stock_by_symbolr   eligiblestr   r   r   analyzed	attemptedskipped_freshfailedfailed_symbolssuccess_symbolsskipped_symbolsfreshness_minutescutoffr   recentr
  r   r	  r   r   confr   primary_targetsnapshot_docr  insr=   r=   r>   _run_intraday_cycleX  s  







&






r-  c                 C   s~   t  }t|}z t|| d W z|  W dS  ty'   tjddd Y dS w z|  W w  ty>   tjddd Y w w )zBThread entrypoint: open DB in-thread and run one cycle for `mode`.)r   z+[GlobalIntraday] Error closing DB generatorTr   N)r   nextr-  closer5   r(   r6   )r   db_genr   r=   r=   r>   _run_intraday_cycle_sync_modeS  s   r1  <   interval_secondsc           	   	      s  t dI dH  t| dkrt| ntt}ttdkr!ttnd}td||ttt	 d}	 t
rtt stt }|rwzt jtddt}W n ty]   tt}Y nw td	|d
|d
 t t||I dH  q/t |I dH  q/t rtd t t r|n|I dH  q/t4 I dH K z1t }t|}d}|r|du s||  t|krd}|}nd}nd}|}t t|I dH  W n ty   td Y nw W d  I dH  n1 I dH sw   Y  t t r|n|I dH  q0)u  Background loop for global Zerodha + GPT intraday analysis.

        Runs forever.

        Scheduling semantics (IST):
        - Full cycle: every 15 minutes (default 900s).
        - Early-movers fast cycle: every 5 minutes (default 300s) ONLY during
            the first 45 minutes window (default 09:15–10:00 IST).

        IMPORTANT:
        - Must NOT scrape ET or rebuild live_movers.
            `movers_refresh_loop` is responsible for ET scraping and `live_movers` rebuild.
    rd   Nr   i,  zZ[GlobalIntraday] Background loop started | full=%ss fast=%ss window=%s-%s | global_user=%sTrZ   r[   zL[GlobalIntraday] Skipped: outside backend market hours | now=%s next_open=%sz%Y-%m-%d %H:%MzA[GlobalIntraday] Previous cycle still running; skipping this tickfullr   z*[GlobalIntraday] Unexpected error in cycle)asynciosleepr,   FULL_INTERVAL_SECONDSEARLY_FAST_INTERVAL_SECONDSr(   r  r   r   r@   rO   rX   r   re   rp   rf   r   rg   rh   r5   ri   strftimerv   _GLOBAL_INTRADAY_LOCKlockedr   rs   float	to_threadr1  r  )	r3  full_intervalfast_intervallast_full_run_utcnxtrm   ri   in_fastr   r=   r=   r>   global_intraday_loopa  sf   	
(rC  )r   N)N)r2  )Cr5  r/   r   logging.handlersr   r   r   zoneinfor   typingr   r   r   r	   r
   app.db.databaser   app.v1.services.zerodha.clientr   app.v1.services.teGPTr   app.v1.services.paper_tradingr   app.v1.services.alertsr   app.v1.utils.confidencer   "app.v1.services.intraday_watchlistr   	getLogger__name__r(   r?   r%   r  r@   r,   r  r7  r8  r   r   r   r   r   rh   Lockr:  r~   rO   boolrX   rp   rv   r-   tupler   r   r   r   r   r   r   r   r-  r1  rC  r=   r=   r=   r>   <module>   sZ    

+"""7
* |