o
    ̿Si"                    @   s  d Z ddlmZmZmZmZmZ ddlmZm	Z	m
Z
mZ ddlZddlZddlmZmZ ddlZddlZddlmZ ddlmZ ddl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$m%Z%m&Z&m'Z'm(Z( ddlm)Z) e)edddZ*ddee defddZ+de,de-de-de.e-e-f fddZ/ddde,dee dee,e	f fddZ0dd l1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8 e Z9e:e;Z<d!e,dee, fd"d#Z=d$e,dee, fd%d&Z>d$e,d'e,ddfd(d)Z?d$e,d*e,d+e-de@fd,d-ZAd$e,d*e,ddfd.d/ZBd0ee,e	f d1e,dee,e	f fd2d3ZCd4ee, d5ee,e	f de
e, fd6d7ZDe9jEd8d9d:edd;d<eejFeefd1e,d=ee, fd>d?ZGe9jHd@dAd:eddBd<edCdDd<eejFeefdEee, dFe@fdGdHZIe9jEdIdJd:edKdLd<eejFeefdMe,fdNdOZJe9jHdPdQd:ei dReejFeefd1e,dSee,e	f fdTdUZKe9jEdVdWd:edXdYd<eejFeefdZe-fd[d\ZLe9jEd]d^d:edXd_d<edd`d<eddad<eejFeefdZe-d1ee, dbee, fdcddZMe9jHdedfd:edKeejFeefd1e,dSee,e	f fdgdhZNe9jHdidjd:edKeejFeefd1e,dSee,e	f fdkdlZOe9jEdmdnd:edKdod<eejFeefdMe,fdpdqZPe9jEdrdsd:edtdud<eddvd<eejFeefd1e,dZe-dwee, fdxdyZQe9jHdzd{d:edKeejFeefdSee,e	f fd|d}ZRe9jEd~dd:edtdd<eddd<eejFeefdZe-dee, fddZSe9jEddd:eddd<eddd<eejFeefde,dZe-fddZTe9jEddd:ee-eUddpdddddee-eUddpdddddee-eUddpdddddee-eUddpdddddeddd<eejFeefde-de-de-de-dee, f
ddZVe9jEddd:eejFeefddZWe9jHddd:edKeejFeefde,dSee,e	f fddZXdS )zfAnalysis + chat + orders + misc endpoints for platform teGPT.

NOTE: Move-only split from `teGPT.py`.
    )	APIRouterDependsHTTPExceptionBodyQuery)DictAnyListOptionalN)datetime	timedeltadatabase)get_current_userdetails)chat_with_stock_serviceget_user_signals_serviceget_zerodha_client_serviceplace_order_service)get_stock_history)build_early_movers_snapshot)normalize_confidence)ist_date_str)backend_market_windowformat_windowis_backend_market_hourslocal_time_strmarket_tz_name)timezone      )hoursminutesnow_utcreturnc                 C   sB   | pt  }z|jtjdtW S  ty    t jtd Y S w )N)tzinfo)tz)	r   utcnowreplacer   utc
astimezone_IST	Exceptionnow)r"   r,    r-   D/var/www/html/Trade-python/app/v1/routers/platform/teGPT_analysis.py_ist_now,   s   r/   v	default_h	default_mc                C   s   zR| pd  }|rd|vr||fW S |dd\}}t|}t|}d|  kr,dkrDn nd|  kr8dkrNn n||fW S W ||fS W ||fS W ||fS W ||fS  ty^   Y ||fS w )N :   r      ;   )stripsplitintr+   )r0   r1   r2   shhmmhmr-   r-   r.   _parse_hhmm4   s*   
0
r@   )r"   	today_istc           	      C   sp   t dd}t|ddd\}}t|}|j|jf||fk}t| p"| }|r'|n| }|| |||dd|ddS )	z}Return preferred early movers snapshot date key.

    Rule: after 20:00 IST, prefer next trading day; else prefer today.
    (EARLY_MOVERS_DEFAULT_NEXT_DAY_AFTER_HHMMz20:00   r   )r1   r2   02dr4   )	preferredrA   tomorrow_istafter_cutoffcutoff_hhmm)osgetenvr@   r/   hourminute_next_trading_day_key)	rA   r"   
cutoff_rawchcmnow_istrG   rF   rE   r-   r-   r.   _default_early_movers_date_keyC   s   rR   r5   )_confidence_score_extract_analysis_fields_format_analysis_for_stream_row_get_latest_snapshot_by_symbol_get_symbols_from_live_movers_normalize_mover_param_what_changeddate_keyc                 C   sl   zt t|   }W n
 ty   Y d S w |tdd }| dkr2|tdd }| dks%| S )Nr5   )daysr   )	r   fromisoformatstrr8   dater+   r   weekday	isoformat)rZ   basedr-   r-   r.   rM   e   s   rM   keyc                 C   s8   | d  d|i}|sd S |d}|d u rd S t|S )Nsystem_metarc   value)find_onegetr]   )dbrc   docr0   r-   r-   r.   _get_system_meta_valuep   s
   
rj   re   c                 C   s,   | d j d|id||t didd d S )Nrd   rc   $set)rc   re   
updated_atTupsert)
update_oner   r&   )rh   rc   re   r-   r-   r.   _set_system_meta_valuex   s
   
rp   ownerttl_secondsc             
   C   s   t  }|ttdt|d }z| d ||||d W dS  tyL } z!t|}t|dd }|dkp>d|v p>d	|	 v }	|	sB W Y d }~nd }~ww | d j
|d
d|iid
ddiid|igdd|||didd}
|
jdkS )Nr   )secondsrd   )rc   rq   
expires_atrl   Tcodei*  E11000zduplicate keyrt   z$ltz$existsFrq   )rc   z$orrk   )rq   rt   rl   rm   r5   )r   r&   r   maxr:   
insert_oner+   r]   getattrlowerro   modified_count)rh   rc   rq   rr   r,   expiresemsgru   is_dupresr-   r-   r.   _acquire_meta_lock   s2   	


r   c                C   sR   z| d  ||ddt t di W d S  ty(   tjddd Y d S w )Nrd   rc   rq   rk   )rt   rl   z)Early movers manual: error releasing lockT)exc_info)ro   r   r&   r+   loggerdebug)rh   rc   rq   r-   r-   r.   _release_meta_lock   s   
r   snapshotsymbolc                 C   s  |pd   }t| dtr| dni }t|dtr$|dng }t|dtr3|dng }t|D ]#\}}t|tr\|dpHd   |kr\d|d |t|d  S q9t|D ]#\}}t|tr|dppd   |krd	|d |t|d  S qad d d d dS )
Nr3   topbullishbearishr   BULLISHr5   )biasrankentrytotal_in_biasBEARISH)r8   upper
isinstancerg   dictlist	enumeratelen)r   r   symr   r   r   idxitr-   r-   r.   _find_early_mover_entry   s   $$r   r   algoc           
      C   s:  g }| pd   }|d}|d}|d}|d}|d}|d}	|dkrJ|d	 |d ur=|d
|  |d urI|d|  n!|dkrk|d |d ur_|d|  |d urk|d|  |d urw|d|  |d ur|d|  |d ur|d|  |	d ur|d|	  |S )Nr3   	key_leveldistance_to_level_pct	atr_ratio	vol_ratiorelative_strength_5dsma20_dist_pctr   z#Setup: bullish early breakout watchzKey resistance level: zDistance to resistance (%): r   z$Setup: bearish early breakdown watchzKey support level: zDistance to support (%): z$Volatility contraction (ATR ratio): z%Volume condition (vol ratio vs 20D): z!Relative strength vs index (5D): zSMA20 proximity (%): )r8   r   rg   append)
r   r   outbr   distr   r   rs5dsma20dr-   r-   r.   _early_mover_algo_explanation   s<   







r   z/early-movers/study/{symbol}z$DB-only: Early Mover Advantage study)summaryzGOptional snapshot date (YYYY-MM-DD). Defaults to today IST else latest.)descriptionsnapshot_datec              
      s  | pd   }|stddd|tdd }d}|r.t|  r.|dt|  i}|sDt }|d|i}|sD|ji d	gd
}t|t	sOtdddt
||}t|dt	ra|dni }	|d}
|d}|	d|	d|	d|	d|	d|	d|	d|	dd}t|	dt	r|	dnd}t|	dtr|	dng }|d |dddddd pi }|d!}|rt|t|pdd"ng }t|tsg }|ddu rt|rtzd#tttf d$tfd%d&}td'd( |D |d)}g }|D ]!}zt|d*}|dkr|| W q ty"   Y qw t|d+t	r1|d+ni }t|d,ttfrEt|d,nd}|durht|d-krh|d. |d/  d0 d1 }t|| d2|d< W n
 tys   Y nw i d3|d4|dd5|d6d+|d+d7|d7d|
d8|d9|d9d:|	r|	d:ndd;|	r|	d;ndd<|d|d=t|dt	r|dndd|d>t|
|d?d@dA|iS )BzReturn the early-movers snapshot entry + daily candles for a symbol.

    IMPORTANT: DB-only. Must not call Zerodha/ET/GPT.
    Weekly/monthly candles are derived by the frontend from daily candles.
    r3     symbol is requiredstatus_codedetail EARLY_MOVERS_SNAPSHOT_COLLECTIONearly_movers_snapshotsNr^   r^   sort  z&No early movers snapshot available yetr   r   r   r   r   atr10r   r   r   r   market_context)r   r   r   r   r   r   r   r   gpt
risk_flagsstocksNSE)r   exchanger   r5   )_idstock_idr   r   dayxr#   c                 S   s"   |  d}|rt|d d S dS )Nr^   
   r3   )rg   r]   )r   rb   r-   r-   r.   _dk  s   
zearly_mover_study.<locals>._dkc                 S   s   g | ]	}t |tr|qS r-   )r   r   ).0cr-   r-   r.   
<listcomp>"  s    z%early_mover_study.<locals>.<listcomp>rc   closemarketnifty_5d_return   r   i      ?      Y@   r   r   snapshot_generated_atgenerated_atuniverserank_in_biasr   
algo_scorefinal_scorer   snapshot_gptalgo_explanation)r   r   candlesdaily)r8   r   r   rI   rJ   r]   rf   r   r   r   r   rg   r   r   r   r   sortedfloatr   r+   r:   r   roundr   )r   r   rh   current_userr   collsnapist_datefoundr   r   r   r   r   r   	stock_docr   r   r   daily_sortedclosesr   clmktnifty_5dstock_5dr-   r-   r.   early_mover_study   s   



"



 (




	
 r   z/early-movers/runz#Run early movers snapshot on demandzrIST date to build snapshot for (YYYY-MM-DD). If omitted, uses next trading day from latest stock-history EOD meta.Fz6If true, allow re-run even if snapshot already exists.target_dateforcec                    s  t tdd  dv }td| r| pd ndt|t|t tt	  |sDt	 rDt
 }tddt| d	t  d
t  d|tdd }tdd}ttdd}dt  }	tdd}
| ro| pld nd}|stdd}t||}|stdd| dt|}|stddd|s|d|iddirddd|dS t|||	|d stdd!dt t }|d"d#t |t|t|trt|d$d?d%d&d'dnd|	|t|d(d)}z||
 jd|id*|idd+ W n ty   t|||	d, td-d.dw d/t d0t d1td2t d3t d4td5t fd6d7}ztj|||t||	|t||
d8dd9}|   W n* ty_   t|||	d, ||
 d|id*d:t t d;d<i td-d=dw dd||d>S )@zjManual trigger (for an admin button).

    NOTE: This may call GPT if `EARLY_MOVERS_GPT_MODE=rerank`.
    -EARLY_MOVERS_MANUAL_ALLOW_DURING_MARKET_HOURS0)1trueyesz`Early movers manual run requested | target_date=%s force=%s allow_during_mkt=%s now=%s in_mkt=%sr3   Ni  z9Manual Early Movers run is disabled during market hours ( z). Now: r   r   r   EARLY_MOVERS_LOCK_META_KEYearly_movers_snapshot_lockEARLY_MOVERS_LOCK_TTL_SECONDS1800zmanual:pid=EARLY_MOVERS_JOBS_COLLECTIONsystem_jobsSTOCK_HISTORY_LAST_EOD_META_KEY"stock_history_refresh_last_eod_runzMissing system_meta.r   zCould not compute target_dater^   r   r5   Tsnapshot_existsokskippedreasonr^   )rc   rq   rr   z Early movers is busy (lock held)early_movers_runqueuedrg   c                 S      d S Nr-   krb   r-   r-   r.   <lambda>      z"run_early_movers.<locals>.<lambda>email)rq   lock_keylock_ttl)r   typestatus
created_atr   r   requested_bymetark   rm   r     Failed to create job recordjob_idtdr   rq   r	  r
  	jobs_collc                 S   s  d }zFzddl m} ddlm}	 | \}}
|
| d| iddt t di |
t	dd	 }|s|
d
|iddir|
| d| iddt t ddd|ddi W W z|d urmddl m} t|
||d W n	 tyw   Y nw z|d ur|  W d S W d S  ty   Y d S w |	|
}|stdt }t|
||d}t |  }|
| d| iddt t t||di W nG ty } z:z)|d u rddl m} | \}}
|
| d| iddt t t|di W n
 ty   Y nw W Y d }~nd }~ww W z|d ur#ddl m} t|
||d W n
 ty.   Y nw z|d ur<|  W d S W d S  tyI   Y d S w z|d ur]ddl m} t|
||d W n
 tyh   Y nw z|d uru|  W w W w  ty   Y w w )Nr   r   )_get_global_zerodha_clientr   rk   runningr  
started_atrl   r   r   r^   r5   r   Tr   r   )r  finished_atrl   resultr   z!Global Zerodha client unavailable)rh   zerodha_clientr   r   )r  r  rl   duration_secondsr  errorr  r  rl   r  )app.dbr   !app.v1.background.global_intradayr  create_mongo_client_and_dbro   r   r&   rI   rJ   rf   r   r+   r   RuntimeErrorr   total_secondsr   r]   )r  r  r   rq   r	  r
  r  client_dbmodr  job_dbsnapshot_coll2zt0r  dt_sr}   r-   r-   r.   _run_job_in_background  s   (



z0run_early_movers.<locals>._run_job_in_background)r  r  r   rq   r	  r
  r  targetkwargsdaemonr  !Failed to start background threadr  Failed to start background job)r   enqueuedr  r^   r  )!r]   rI   rJ   r8   rz   r   infoboolr   r   r   r   r   r   r:   getpidrj   rM   rf   r   uuiduuid4r   r&   r   r   ry   ro   r+   r   	threadingThreadstart)r   r   rh   r   allow_duringwsnapshot_collr	  r
  rq   jobs_coll_namer  eod_keylast_eodr  job_docr+  tr-   r-   r.   run_early_moversI  s   

 &P		rC  z/early-movers/run/statusz,Get status of an early movers manual run job.z)Job id returned by POST /early-movers/runr  c                    sv   t dd}|| dt| idddddddddddd}|s'tddd|d	p-d
dkr6tdddd|dS )Nr   r   r   r5   )r   r  r  r  r  r  r   r   r  r  r  r   Job not foundr   r  r3   r   Tr   job)rI   rJ   rf   r]   r   rg   )r  rh   r   r>  ri   r-   r-   r.   early_movers_run_status  s   0
rG  z/analyze/{symbol}z#Analyze single symbol using ChatGPT)defaultpayloadc              
      st  zt || }|stddd|dpi }t|tr|d| p#d   |d}t|tr;|d|	  t
|dpE|d	|d
|dd|d< |d}|d}t|tr|ri }	t|trs|rs|	| t|	dtr|	dst|dtr|d|	d< |	ddu r|ddur|d|	d< |	d}
|d}t|tr|ri }t|
tr||
 | D ]\}}||vst||trt||s|||< q||	d< nt|
tr|
s|dur||	d< |	ddu r|ddur|d|	d< |	|d< d| |dW S  ty     ty9 } ztd|  tdt|dd}~ww )zDB-only analysis endpoint used by Stream UI.

    IMPORTANT: This endpoint must NOT call Zerodha or GPT.
    It returns the latest persisted analysis snapshot from
    `stock_analysis_snapshots` for the requested symbol.
    r   z"No analysis snapshot available yetr   analysisr   r3   	timestamp
confidenceconfidence_leveldecision_probabilityscore)rN  rO  market_dataquoteinstrument_tokenNr   success)r  r   rJ  z+Failed to fetch DB analysis snapshot for %sr  )rV   r   rg   r   r   
setdefaultr8   r   r   r`   r   updateitemsr   r   r+   r   	exceptionr]   )r   rI  rh   r   r   rJ  tsmd_snapmd_analysismerged
c_analysisc_snapout_candlestfrowsr}   r-   r-   r.   analyze_symbol  sh   











&
 ra  z/alerts-legacyz*Get user-specific alerts (legacy, DB-only)rC   zMax items per sectionlimitc               
      sf  zt |d}t|d |ddddi}dd |D }|s0d	g g g g d
t dW S t|d dd|iidddd}dd |D  t|d dd|iidddd}dd |D } fdd|D }	i }
|	rd| v r|d |d|	iddg}|D ]}|dpd 	 }|r||
vr||
|< qg }g }g }g }|D ]} |}|sq|dpd 	 }|sqt|d d|idg
d}|sqt|||d }t|dkrt|||d nd}t|||d< ||pi }|d |d < |d!|d!< |
|}|r4|d"pd 	 }|d#v r)|| q|d$v r4|| q|d%p;d& 	 }t|d'pHd}|d(v rZ|dkrZ|| q|| qd)tt tf fd*d+}||||fD ]	}|j|d, qqd	|d|  |d|  |d|  |d|  d
t dW S  ty } ztd- td.t |d/d}~ww )0zDB-only alerts endpoint.

    Builds user-specific opportunities from:
    - `user_likes` (is_active=True)
    - latest `stock_analysis_snapshots`
    - optional `orders` for Triggered/Closed bucketing

    IMPORTANT: Must NOT call Zerodha or GPT.
    r   
user_likesT)user_id	is_activer   r5   c                 S       g | ]}| d r| d qS r   rg   )r   rb   r-   r-   r.   r   u       z#get_user_alerts.<locals>.<listcomp>r   )top_signals
monitoring	triggeredclosed)r  datarK  r   $in)r   r   rR  c                 S   "   i | ]}| d r| d |qS rg  rh  r   r;   r-   r-   r.   
<dictcomp>     " z#get_user_alerts.<locals>.<dictcomp>live_movers)r   r   
mover_typec                 S   rp  rg  rh  r   rr-   r-   r.   rr    rs  c                    s4   g | ]}  |p
i  d r  |pi  d qS )r   rh  )r   sidby_stock_idr-   r.   r     s    orders)rd  r   r  r   r   r3   stock_analysis_snapshotsrK  r      r   Nwhat_changedr   ru  r  )PLACEDOPEN	TRIGGERED)COMPLETECLOSED	CANCELLEDREJECTEDdecisionHOLDrL  BUYSELLrowc                 S   sL   |  d}t|trt|nd}t|  dpd}|  dpd}| ||fS )Nr   i'  rL  r3   rK  )rg   r   r:   rS   )r  r   rank_valconf_valrX  r-   r-   r.   sort_key  s
   
z!get_user_alerts.<locals>.sort_keyr   zFailed to build alertsr  r   )r]   rg   r   findr   r&   list_collection_namesr   r8   r   rb  rT   r   rY   r   rS   r   r   r+   r   rW  r   ) rb  rh   r   rd  likes	stock_idsr   	live_rowslive_by_stock_idsymbolslatest_order_by_symbolcuror   rj  rk  rl  rm  rx  stockr   snapscurrprevliveorderr  r  confr  arrr}   r-   ry  r.   get_user_alertsb  s   
 
$ 










r  z/signalsz"Get user's latest signals/analysiszMaximum number of signalszFilter by symbolz#Filter by decision: BUY, SELL, HOLDr  c              
      s^   zt |t|d| ||d}d|dW S  ty. } ztd tdt|dd}~ww )	z7Get user's latest trading signals from ChatGPT analysisr   )rh   rd  rb  r   r  rS  )r  signalszFailed to get signalsr  r   N)r   r]   rg   r+   r   rW  r   )rb  r   r  rh   r   r  r}   r-   r-   r.   get_signals  s   	
r  z/chat/{symbol}z(Interactive chat about a specific symbolc                    s   z&t ||}t||| |ddt|d|d|ddd}d|d	W S  tyC } ztd
|   tdt|dd}~ww )z
    Interactive chat about a symbol with real-time market data
    Payload: {
        "message": "What's the RSI looking like?",
        "include_fresh_data": true,
        "conversation_id": "optional-uuid"
    }
    messager3   r   conversation_idinclude_fresh_dataTrh   r  r   r  rd  r  r  rS  )r  responsezChat failed for symbol r  r   N)r   r   rg   r]   r+   r   rW  r   )r   rI  rh   r   r  r  r}   r-   r-   r.   chat_with_symbol  s$   


	r  z/chat/{symbol}/enqueuez Enqueue interactive chat (async)c                    s  t |trt|dnd}|stddd| pd  }|dp%d }|s0tddd|s8tdd	dtd
tdd}tt	
 }|dpRtt	
 }	t|dd}
|ddt t |||	||
t |trzt|dd*ddddndd}z|| jd|id|idd W n ty   tdddw dtdtdtdtd tdtdtfd!d"}ztj|||||||	|
d#dd$}|  W n" ty   || d|idd%t t d&d'i tdd(dw dd||	|d)S )+zNon-blocking chat.

    Returns immediately with a job_id; the frontend should poll the status endpoint.

    Payload: {
      "message": "...",
      "include_fresh_data": true,
      "conversation_id": "optional-uuid"
    }
    r   r3     Unauthorizedr   r  r   r   zmessage is requiredCHAT_JOBS_COLLECTIONr   r   r  r  Tchat_runr  rg   Nc                 S   r  r  r-   r  r-   r-   r.   r  >  r  z*chat_with_symbol_enqueue.<locals>.<lambda>r  )r   r  r  r  rl   rd  r   r  r  r  r  rk   rm   r  r  r  r  rd  r   r~   c                 S   s  d }zz^ddl m} | \}}	|	| d| iddt t di d|i}
d }|r1t|	|
}t }t|	||||||d}t |  }|	| d| iddt t t	||d	i W nE t
y } z9z)|d u rzddl m} | \}}	|	| d| idd
t t t|di W n	 t
y   Y nw W Y d }~nd }~ww W z|d ur|  W d S W d S  t
y   Y d S w z|d ur|  W w W w  t
y   Y w w )Nr   r   r   rk   r  r  r  r   )r  r  rl   r  r  r  r  )r  r   r!  ro   r   r&   r   r   r#  r   r+   r]   r   )r  r  rd  r   r~   r  r  r$  r%  r&  current_user_stubr  r)  r  r*  r}   r-   r-   r.   _run_chat_job_in_backgroundH  s   
	z=chat_with_symbol_enqueue.<locals>._run_chat_job_in_background)r  r  rd  r   r~   r  r  r,  r  r0  r  r1  )r   r2  r  r  r   r  )r   r   r]   rg   r   r8   r   rI   rJ   r6  r7  r4  r   r&   ry   ro   r+   r8  r9  r:  )r   rI  rh   r   rd  r   r~   r>  r  r  r  rA  r  rB  r-   r-   r.   chat_with_symbol_enqueue  sp    &C	r  z/chat/enqueue/statuszGet status of an async chat jobz.Job id returned by POST /chat/{symbol}/enqueuec                    s   t |trt|dnd}|stdddtdtdd}|| t| d	|d
dddddddddddd}|sCtdddd|dS )Nr   r3   r  r  r   r  r   r   r  )r   r  rd  r5   )r   r  r  r  r  r  r  r   r  r  r  r   rD  TrE  )r   r   r]   rg   r   rI   rJ   rf   )r  rh   r   rd  r>  ri   r-   r-   r.   chat_job_status  s   
r  z/chat/{symbol}/historyzGet chat history for a symbol2   zMaximum messageszOptional conversation idr  c           
   
      s   z8t |d}|| ddid}|r||d< t|d |dd|}|D ]
}t |d |d< q)d	|d
W S  tyU }	 ztd|   t	dt |	dd}	~	ww )z&Get chat history for a specific symbolr   z$neT)rd  r   is_context_snapshotr  chatsr  r   rS  )r  historyzFailed to get chat history for r  r   N
r]   rg   r   r  r   rb  r+   r   rW  r   )
r   rb  r  rh   r   rd  qr  chatr}   r-   r-   r.   get_chat_history  s   	 r  z/order/placez%Place order based on ChatGPT analysisc                    s   z5t ||}t||t|d| d| d| d| dd| d| d| d	d
d
}d|dW S  ty>     tyV } ztd tdt|dd}~ww )aq  
    Place trading order with ChatGPT risk validation
    Payload: {
        "symbol": "RELIANCE",
        "action": "BUY" | "SELL",
        "quantity": 10,
        "order_type": "MARKET" | "LIMIT",
        "price": 2500.0,  // Required for LIMIT orders
        "analysis_id": "optional-analysis-reference",
        "confirm": true  // Required for execution
    }
    r   r   actionquantity
order_typeMARKETpriceanalysis_idconfirmF)
rh   r  rd  r   r  r  r  r  r  	confirmedrS  )r  r  zOrder placement failedr  r   N)r   r   r]   rg   r   r+   r   rW  )rI  rh   r   r  r  r}   r-   r-   r.   place_trading_order  s.   



r  z/ordersGet user's order historyzMaximum orderszFilter by statusr  c           	   
      s   z4t |d}d|i}|r||d< t|d |dd| }|D ]
}t |d |d< q%d|dW S  tyN } ztd	 t	d
t |dd}~ww )r  r   rd  r  r{  r  r   rS  )r  r{  zFailed to get ordersr  r   Nr  )	rb  r  rh   r   rd  queryr{  r  r}   r-   r-   r.   
get_orders  s    
r  z/livez(Legacy compatibility - get live analysisgainerszgainers or losersr   zNumber of symbols to analyzemoverc           &         s  zdt dtt fdd dtttt f  dtttt f  dtt f fdd}d	td
tttt f  dtt dtt dtt dtt f fdd}d	td
tttt f  dtt dtt f fdd}t| }|dkrt|d ddiddddd	ddg
|}t|d ddiddddd	ddg
|}	||	 d| }
n!i }|dv r||d< t|d |ddddd	ddg
|}
dd |
pg D }|sd dg d!W S t|d" d#d$|iiddddd%}d&d' |D }i }|d( d#d$|ii	d)d*g}|D ]}|d#}|r||vr|||< qg }|
D ]}|d#}|s-q ||}|s7q |d+p>i }t|tsGq ||pNi }|d,pVd-  }|d.}t|trk|dnd}d}t|dtr|dpi d}|du rt|tr|d}d}t|dtr|d}nt|tr|}|||}|d/}g }t|trǇ fd0d|D }t|d
tr|d
nd} |d1}  |d}!| dur| n|!}"|t|d	d2||"d3}"|t|d	d2| |dp |d4|"p |d5|d6}|d}#d}$t|#ttfr5|#r5|#d }$|i d,|d,pC|pCd-  d7t|d d8|d8pWd-d	|d	d2d9|d9d:d.|d.durt|d.n|d;|d
|d<|d<d=|d=d1|"d/|d>|d>d|dd5|d5d?|d?d@|d@|d4|d	d2|d9d:dAdAdAdBdBdBd-dBdC
|dDg |dEi |dFi |d|$dG|dHdI q d t||d!W S  ty }% ztdJ tdKt|%dLd}%~%ww )MzDB-only legacy endpoint.

    IMPORTANT: Must NOT call Zerodha or GPT. Uses latest
    `stock_analysis_snapshots` for symbols in `live_movers`.
    r0   r#   c                 S   sd   z'| d u rW d S t | trW d S t| }||ks"|tdtdfv r%W d S |W S  ty1   Y d S w )Ninf-inf)r   r4  r   r+   )r0   fr-   r-   r.   _safe_float'  s   
z&get_live_analysis.<locals>._safe_floatrQ  rP  c                    s>  t | trk| rk| dp| dp| dp| dp| d} |}|d ur*|S  | dp4| d} | dp=i d	}|d urR|rR|d
krR|| d S  | d}|d urk|rk|d
krk|| | d S t |trr|std S |d}t |tr|sd S dD ]?}||}	t |	trt|	dk rq |	d pi d	}
 |	d pi d	}|
d u s|d u s|d
krq|
| | d   S zO|d}t |trt|dkr |d
 pi d} |d pi d	}
|d ur|
d ur|d
kr|
| | d W S W d S W d S W d S W d S W d S  ty   Y d S w )Nnet_change_percentagepercentage_changepercent_changepchangepChange
net_changechangeohlcr   r   r   
last_pricer   )r   DAY1day15minute15min5minuter  r   r  open)r   r   rg   r   r   r+   )rQ  rP  directr  netr  lastr   r_  series
last_close
prev_closes5
first_openr  r-   r.   _compute_change_pct4  sl   



z.get_live_analysis.<locals>._compute_change_pctr  
entry_zoneentry_pricesltargetsc                    s   fdd|D }d }t |tr> |dp|d} |dp&|d}|d ur>|d ur>|dkr>|dkr>|| d }|d urD|n|}	 |	}	 |}| pQd	  }
|	d u sb|d u sb|
d
vrh|d d S |
dkrp|	| n||	 }|d u s||dkr|d d S t|dkr|
dkr|	| n|	| }|
dkr|	d|  n|	d|  }t|dt|dgS t|dkrt|d }|
dkrt|| |	d|  }nt	|| |	d|  }|
tt|d |d d S )Nc                        g | ]} |d urt |qS r  r   )r   rB  r  r-   r.   r   n  ri  zBget_live_analysis.<locals>._ensure_two_targets.<locals>.<listcomp>lowrz   highr   r   g       @r3   r  r  r  r5   )r   r   rg   r8   r   r   r   r   rw   minr   )r  r  r  r  r  r   zone_midlohi	entry_refrb   riskt1t2r  r-   r.   _ensure_two_targetsf  s6   
  z.get_live_analysis.<locals>._ensure_two_targetsc           
         s"   |}|d u st |ts|S  |dp|d} |dp%|d}|d u s7|d u s7|dks7|dkr9|S | p<d  }ttddpId}tdt	d	|}t
|d
 }|dkrtt
|d|  }|t
|krm|S t	t
||S |dkrt
|d|  }	|t
|kr|	S tt
||	S |S )Nr  rz   r  r   r   r3   ENTRY_SL_BUFFER_BP10   g     @r  r   r  )r   r   rg   r8   r   r:   rI   rJ   rw   r  r   )
r  r  r  r  r  rb   bufbuf_pctmax_okmin_okr  r-   r.   _ensure_sl_side  s,    z*get_live_analysis.<locals>._ensure_sl_sideBOTHrt  ru  GAINERr   r5   )r   r   r   ru  r   r5   last_updatedr   LOSERN)r  r  c                 S   rf  rg  rh  rv  r-   r-   r.   r     ri  z%get_live_analysis.<locals>.<listcomp>rS  )r  scannedresultsr   r   ro  )r   r   r   rR  c                 S   rp  rg  rh  rq  r-   r-   r.   rr    rs  z%get_live_analysis.<locals>.<dictcomp>r}  r~  r   r   rJ  r   r3   rR  exec_targetsc                    r  r  r  r   r   r  r-   r.   r     ri  exec_slr  )r  r  r  current_price	stop_loss)r  r  r  r  r  r   trend_labelrL  LOW
change_pctentry_trigger_reasonsignal_stateexec_rr_ratioprice_targetrisk_reward_ratio        F)
r  rL  volume_dropmomentum_dropema_gaprsi_divergencemacd_divergencewedgepivot_levelnear_upper_circuit	rationalefeaturestechnical_indicators)r   exitrK  )r  metricsr   r!  r"  r  rK  zLegacy live analysis failedr  r   )r   r
   r   r   r]   r	   rX   r   r  r   rb  rg   r   r   r8   r   tupler   r   r+   r   rW  r   )&r  rb  rh   r   r  r  r   ru  r  losersr  r  r  r   stock_by_idlatest_by_idr  r   rx  formatted_resultsr  r  r  fallback_symbolfallback_tokenmdrQ  md_for_changer  raw_exec_targetsr
  r  sl_execsl_fallbackr  raw_targetsexit_targetr}   r-   r  r.   get_live_analysis  sh  62,"












 













0
r3  z/streamz(DB-only: stream (single call, multi-tab)STREAM_FRESHNESS_MINUTESr  <   z4Used only for reporting freshness stats (no compute))geler   STREAM_MOVERS_LIMIT20r  z'Max ET movers per side (gainers/losers)STREAM_EARLY_MOVERS_LIMIT40z+Max early movers (bullish+bearish combined)STREAM_WATCHLIST_LIMIT50zMax personal watchlist symbolszEarly movers snapshot date to show: 'today', 'tomorrow', or YYYY-MM-DD. If omitted, after 20:00 IST prefers next trading day (fallback to today).freshness_minutesmovers_limitearly_movers_limitwatchlist_limitearly_movers_datec           7         s	  zt |d}t }t tt| d }	t|d ||ddddd	d	d
g
t|}
dd |
D }|tdd }t|d}d}|pLd }|r| }|dkr^|d }n!|dkrg|d }nz
t|d |}W n ty~   tdddw |p|d }|d|i}|s||d kr|d|d i}|s|ji dgd}g }d}i t|trPt|dtr|dni }t|dtr|dng }t|d tr|d ng }t|d!tr|d!ng }g }|s|r|D ]}t|tr|| q|D ]}t|tr|| qn|D ]}t|tr'|| qd}|D ]}t|ts7q-|d"p>d  }|sHq-||v rOq-|| |d#p[d  pbd}|d$pjd  pqd}|s|r|d%krd&}n|d'krd(}||t|d)p|d*pdt|d+p|d,p|d)p|d*pd-t|d.p|d/p|d0pd|d1|< |d7 }q-|dkr|dt| }|d}d} |r||d kr|d } n|r||d kr|d } d2}!| r%zt|d| iddi}!W n ty$   d2}!Y nw |||d |d t|d3 |d4 | t|!|d5|d6|d7|d8|d9d:}g g }"|dkrt|d; d<d=idddd>	d?d@g
t|t|d; d<dAidddd>	d?d@g
t|}"dBttt tf  dCtt  fdDdE   fdFd |"D  }#ttg ||}$i |$rt|dG d"dH|$iidddddddI}%|%D ]}&|&d"pd  }|r|&|< qi |#rt|dG dJdH|#iidddddddI}%|%D ]}&|&dJ}'|'r|&|'< qg }(|$D ]}|p%i dJ}'|'r6|'|(vr6|(|' q|#D ]}'|'rI|'|(vrI|(|' q:i |(rw|dK dJdH|(ii	dLdMg})|)D ]}*|*dJ}'|'ru|'vru|*|'< qbdNt dOt dPtdCttt tf  ffdQdR}+dSt dOt dPtdCttt tf  ffdTdU},g }-d}.g }/d}0|D ]E}|+|dV|0dW}1|1sq|/|1 z"|1dX}2t|2t r|2rt|2 dYdZ}3|3|	kr|.d7 }.W n
 ty   Y nw |0d7 }0qd[tt tf dCtfd\d]}4|/j	|4d^d_ t!|/dd`D ]	\}5}1|5|1dP< q|-"|/ d}0D ]"}|dJ}'|'s-q!|,|'da|0db}1|1s9q!|-|1 |0d7 }0q!d}0|"D ]"}|dJ}'|'sTqH|,|'dc|0db}1|1s`qH|-|1 |0d7 }0qHd}0|D ]}|+|dd|0dW}1|1s}qo|-|1 |0d7 }0qode|-dft#|idft#idft#|"idft#|idg|t|.d| |t dh	W S  ty }6 zt$%di tdjt |6dd}6~6ww )kzSingle-call DB-only stream endpoint.

    Returns 4 tabs worth of data in ONE response:
    - Early Movers
    - ET Gainers
    - ET Losers
    - Personal Watchlist

    IMPORTANT: Must NOT call Zerodha, NSE, ET scraping, or GPT.
    r   )r!   user_portfolio_itemsACTIVE)rd  r   r  r   r5   )r   r   )rl   r   r|  c                 S   s4   g | ]}| d p
d r| d pd  qS )r   r3   )rg   r8   r   )r   r   r-   r-   r.   r     s   4 z'get_combined_stream.<locals>.<listcomp>r   r   )rA   Nr3   todayrA   tomorrowrF   z%Y-%m-%dr   z=Invalid early_movers_date; expected today|tomorrow|YYYY-MM-DDr   rE   r^   r   r   r   r   r   combinedr   r   strategyr   BREAKOUTr   	BREAKDOWNoverall_scorer   overall_score_floatfinal_score_floatr  early_mover_scorer   rO  )r   rH  rK  rL  rN  r   FrG   rH   r   r   r   r   r   )r^   requested_daterA   rF   rG   rH   alternate_datealternate_existsr   r   r   r   r   rt  ru  r  )r   r   r   r  r  r  r`  r#   c                 S   s6   g }| pg D ]}| d}|r||vr|| q|S )Nr   )rg   r   )r`  r   rw  rx  r-   r-   r.   	_sid_list  s   

z&get_combined_stream.<locals>._sid_listc                    s   g | ]
}| vr|qS r-   r-   r  )rR  gainers_rowsr-   r.   r     s    r   ro  )r   r   r   r   rR  namer   r}  r~  r	  r   r  r   c           
         s0   | pi }| d}|r |nd }t|tr't| dtr'| dnd }t|tr;t| dtr;| dnd }|d u rL| dd| dd}n+t|}|d|  | dd u rk| dd urk| d|d< |d urw|d|  t|||d	}|d
kr  | pd  }	t|	tr|	|d< |S )Nr   rJ  rK  r  r  rR  r   r  rL  rR  r   r   r  earlymoversr3   early_mover)	rg   r   r   r   rT  r`   rU   r8   r   )
r   r  r   r  rx  r   rJ  ts_dtr  em)early_info_by_symbolr(  stocks_by_symbolr-   r.   _row_for_symbolR  s.   
((
z,get_combined_stream.<locals>._row_for_symbolrx  c                    s   | pi }| dpd  }|sd S   | }t|tr-t| dtr-| dnd }t|trAt| dtrA| dnd }|d u rR|dd| dd}n+t|}|d| | dd u rq| dd urq| d|d< |d ur}|d|  t|||d	S )
Nr   r3   rJ  rK  r  r  rR  rU  rV  )	rg   r8   r   r   r   r   rT  r`   rU   )rx  r  r   r  r   r   rJ  rY  )r(  mover_stocks_by_idr-   r.   _row_for_stock_idp  s(   
((z.get_combined_stream.<locals>._row_for_stock_idrW  )r   r  r   rK  Zz+00:00r  c                 S   sT   |  d}zt|}||ks|tdtdfv rW dS t|W S  ty)   Y dS w )Nr  r  r  g Ngm)rg   r   absr+   )r  r0   r  r-   r-   r.   _abs_change  s   

z(get_combined_stream.<locals>._abs_changeT)rc   reverse)r:  r  )rx  r  r   r&  	portfoliorS  count)rW  r  r&  rd  )	r  r  tabsearly_movers_analysiscachedcomputedr>  r   rK  zFailed to build combined streamr  )&r]   rg   r   r   r&   r   r:   r   r  r   rb  rI   rJ   rR   r8   rz   strptimer+   r   rf   r   r   r   r   r   r4  r	   r   r   fromkeysr
   r\   r'   r   extendr   r   rW  )7r>  r?  r@  rA  rB  rh   r   rd  r   cutoffmanual_itemsportfolio_symbols
early_collprefrequested_keyraw_reqr  preferred_keyem_snapearly_symbols
early_metar   r   r   rG  orderedr   rnkr   r   rH  	used_datealt_date
alt_existslosers_rowsmover_stock_idsall_symbolsdocsr;   rx  stock_ids_for_snapsr  r   r]  r_  r  fresh
early_rowsrw  r  rX  dtrb  ir}   r-   )rR  r[  rS  r(  r^  r\  r.   get_combined_stream[  s  ,












6*









" 






.,














r  z/profilezGet Zerodha profile statusc              
      s   z+t | |}z| }dd|dW W S  ty, } zddddW  Y d}~W S d}~ww  ty[ } z#|jd	krEddd
dW  Y d}~S |jdkrVddddW  Y d}~S  d}~w tyy } ztd ddt|dW  Y d}~S d}~ww )z-Get Zerodha profile and authentication statusr   T)r  authenticatedprofiler  FzZerodha authentication required)r  r  r  Nr   zZerodha settings not foundi  zZerodha not authenticatedzProfile check failed)r   r  r+   r   r   r   rW  r]   )rh   r   r  r  r}   r-   r-   r.   get_profile  s8   




r  z/stream/feedback/{analysis_id}zProvide feedback on analysisr  c              
      s  zg| d}|dvrtddd|d d| i}|s'|d dt| i}|s/tdd	d| d
}|s<tdddt| d}t }|dk}	|d j||d|	||dd|iddd d|||	dW S  typ     ty }
 zt	d tdt|
dd}
~
ww )a.  Provide thumbs up/down feedback on analysis.

    Stored in `user_likes` as a per-user preference signal:
    - feedback="up"   -> is_active=True
    - feedback="down" -> is_active=False

    We resolve `stock_id` by looking up the snapshot that contains
    `analysis.analysis_id == analysis_id`.
    feedback)updownr   zfeedback must be 'up' or 'down'r   r}  zanalysis.analysis_idr   zAnalysis snapshot not foundr   zSnapshot missing stock_idr   r  rc  )rd  r   )re  last_feedbackrl   r  )rk   z$setOnInsertTrm   r   )r  r  r   re  zFeedback update failedr  N)
rg   r   rf   r]   r   r&   ro   r+   r   rW  )r  rI  rh   r   r  r   r   rd  r,   re  r}   r-   r-   r.   stream_feedback  sF   



r  r  )Y__doc__fastapir   r   r   r   r   typingr   r   r	   r
   loggingrI   r   r   r8  r6  r  r   app.v1.dependencies.authr   app.v1.services.teGPTr   r   r   r   app.v1.services.stock_historyr   app.v1.services.early_moversr   app.v1.utils.confidencer   "app.v1.services.intraday_watchlistr   app.v1.utils.market_timer   r   r   r   r   r   r*   r/   r]   r:   r%  r@   rR   teGPT_helpersrS   rT   rU   rV   rW   rX   rY   router	getLogger__name__r   rM   rj   rp   r4  r   r   r   r   rg   get_mongo_dbr   postrC  rG  ra  r  r  r  r  r  r  r  r  r3  rJ   r  r  r  r-   r-   r-   r.   <module>   s   "($

%&
&#
f

 4

Q
u




 



'



  B   !
