o
    SLNi*                  
   @   s\  d dl Z d dlZd dlZd dlmZ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mZmZ d dlmZ d dlmZ eeZeedd	Zeed
dZdZe  Zdeeef deeef fddZ dee fddZ!dee deeeeef f fddZ"d"dee dedede
e fddZ#d#ddZ$d#ddZ%d$deddfd d!Z&dS )%    N)datetime	timedelta)AnyDictListOptionalSet)get_mongo_db)_get_global_user_id_get_global_zerodha_client_is_market_hours_ist)analyze_symbol_service)normalize_confidencePORTFOLIO_INTRADAY_MAX_ANALYSES200$PORTFOLIO_INTRADAY_FRESHNESS_MINUTES30analysisreturnc                 C   s  t | tsi S | dp| dpd}t|  }|dkr!| S d| d< d| d< d| d< d| d< d}z| d	}|d
urFt |tsFt|}W n tyR   d}Y nw t	|d| d	< dD ]
}|| v rfd
| |< q\| dpmd }d}|r||d |  n|| d< | S )zPortfolio constraint: never publish short/SELL signals.

    If the upstream analysis suggests SELL, we downgrade to HOLD and clear
    any execution fields so consumers (alerts, UI, paper trading) don't
    treat it as an actionable short.
    decisionactionHOLDSELLLOW
confidence
convictiong        scoreNg     C@)entry_price
entry_zone	stop_lossstop_loss_zoneprice_targettargettargets
exec_entryexec_slexec_targetstrend_label	rationale z9[portfolio:no-shorting enforced: SELL downgraded to HOLD] )

isinstancedictgetstrstripupperboolfloat	Exceptionmin)r   r   	score_val	raw_scorekr(   suffix r9   B/var/www/html/Trade-python/app/v1/background/portfolio_intraday.py"_enforce_no_shorting_for_portfolio   s8   

r;   c                 C   sv   z| d  ddddid}W n ty   td g  Y S w g }|p%g D ]}t|tr8| r8||  q&|S )z<Return distinct ACTIVE portfolio stock_ids across all users.user_portfolio_itemsstock_idACTIVEz$neN)statusr=   zD[PortfolioIntraday] Failed to distinct user_portfolio_items.stock_id)distinctr3   logger	exceptionr+   r.   r/   append)dbidsoutsidr9   r9   r:   $_distinct_active_portfolio_stock_idsO   s"   
rH   	stock_idsc                 C   s   |si S | d  d|iddidddiigddd	i}i }|D ]}|d
}|dp,d  }|r=|r=||d< |||< q|S )z@Load stocks map for given stock_ids and filter to active+symbol.stocks$in	is_activeTz$existsF)r=   z$or_idr   r=   symbolr)   )findr-   r/   r0   )rD   rI   rJ   by_idsrG   symr9   r9   r:   _filter_to_active_stockse   s"   	
rS     cutoff
batch_sizec           
   	   C   s   t  }|s|S tdt||D ]=}||||  }z%| d d|id|idddd}|D ]}|d}	|	r<||	 q.W q tyL   td	 Y qw |S )
z@Return stock_ids that already have a snapshot newer than cutoff.r   stock_analysis_snapshotsrK   z$gte)r=   	timestamp   )rM   r=   r=   z4[PortfolioIntraday] Failed to query recent snapshots)	setrangelenrO   r-   addr3   rA   rB   )
rD   rI   rU   rV   freshibatchcurdocrG   r9   r9   r:   _get_fresh_stock_ids~   s*   


rc   c                    s"  t d t| }|st d d S t| }|st d d S t| }|s,t d d S t| |}|s:t d d S t t	t
dttd }t| t| |d  fd	d
| D }t dt|t|t tttt d}t }t|D ]}	|tkrt dt  n||	pi }
|
d}|sq{t|  }|sq{||v rq{|| zt| ||g ddd|dd}W n ty } zt d|| W Y d }~q{d }~ww t|tr|dnd }t|tr|dnd }tt|tr|dnd t|tr|dnd t|tr|dnd d}t|tr%||d< t|}t|tr0|dnd }d }t|trA|rA|d }|	t |d||d|d|dp_|d p_||d!d"|||d#}z	| d$  | W n ty   t d%| Y nw |d7 }q{t d&| d S )'Nz[PortfolioIntraday] Cycle startzE[PortfolioIntraday] Skipping cycle: global Zerodha client unavailablez?[PortfolioIntraday] Global user_id missing, cannot tag analysesz4[PortfolioIntraday] No ACTIVE portfolio stocks foundzC[PortfolioIntraday] No eligible stocks (active+symbol) after filterrY   )minutes)rU   c                    s   g | ]}| vr|qS r9   r9   ).0rG   	fresh_idsr9   r:   
<listcomp>   s    z(_run_portfolio_cycle.<locals>.<listcomp>zP[PortfolioIntraday] Universe=%d eligible=%d fresh=%d freshness_minutes=%d cap=%dr   zB[PortfolioIntraday] Reached per-cycle cap (max=%d); stopping earlyrN   )5minute15minute30minutedayweekmonthzgeneral portfolio analysisportfolio_intradayT)rD   zerodha_clientrN   
timeframesquestioncontextuser_idinclude_market_dataz.[PortfolioIntraday] Analysis failed for %s: %smarket_datafeaturesr   decision_probabilityr   )rx   r   r#   r   r   r   r!   r"   r(   	PORTFOLIO)r=   rX   r   r   entryr   r"   reasonsourcer   rv   rw   rW   z>[PortfolioIntraday] Failed to persist analysis snapshot for %sz0[PortfolioIntraday] Cycle finished | analyzed=%d)!rA   infor   errorr
   rH   rS   r   utcnowr   maxintFRESHNESS_MINUTESrc   listkeysr\   MAX_ANALYSES_PER_CYCLErZ   sortedr-   r.   r/   r0   r]   r   r3   rB   r+   r,   r   r;   
insert_one)rD   rp   rt   rI   stocks_by_idrU   eligible_idsanalyzedseen_symbolsrG   stockrN   sym_keyr   erv   rw   confr#   primary_targetsnapshot_docr9   rf   r:   _run_portfolio_cycle   s   





	




r   c                  C   sz   t  } t| }zt| 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 )Nz.[PortfolioIntraday] Error closing DB generatorT)exc_info)r	   nextr   closer3   rA   debug)db_genrD   r9   r9   r:   _run_portfolio_cycle_sync  s   
r     interval_secondsc              	      s  t dI dH  td|  	 t s1ddlm}m}m} td| ||  t | I dH  qt	
 rCtd t | I dH  qt	4 I dH $ z
t tI dH  W n tyb   td	 Y nw W d  I dH  n1 I dH ssw   Y  t | I dH  q)
a  Background loop for global portfolio-driven analysis.

    - Universe: distinct ACTIVE portfolio stocks across all users (deduped by stock_id)
    - Skips symbols with a recent snapshot (<= PORTFOLIO_INTRADAY_FRESHNESS_MINUTES)
    - Runs only during IST market hours (same gate as ET/global loops)

    Start from FastAPI startup via `asyncio.create_task(portfolio_intraday_loop(...))`.
       Nz:[PortfolioIntraday] Background loop started (interval=%ss)Tr   )backend_market_windowformat_windowlocal_time_strzD[PortfolioIntraday] Backend skipped due to time | now=%s | window=%szD[PortfolioIntraday] Previous cycle still running; skipping this tickz-[PortfolioIntraday] Unexpected error in cycle)asynciosleeprA   r}   r   app.v1.utils.market_timer   r   r   _PORTFOLIO_INTRADAY_LOCKlocked	to_threadr   r3   rB   )r   r   r   r   r9   r9   r:   portfolio_intraday_loop  s6   

(r   )rT   )r   N)r   )'r   loggingosr   r   typingr   r   r   r   r   app.db.databaser	   !app.v1.background.global_intradayr
   r   r   app.v1.services.teGPTr   app.v1.utils.confidencer   	getLogger__name__rA   r   getenvr   r   ISTLockr   r.   r;   rH   rS   rc   r   r   r   r9   r9   r9   r:   <module>   s*    
"4&$

s