o
    ½½j9|  ã                   @   sB  d Z ddlZddlZ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
 ddlmZ ddlZddlZddlZddlZddlZddlZddlZddlZddlZddlZddlZ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! ed	ƒZ"ej# $ej# %e&¡¡Z'ej# (e'd
¡Z)G dd„ dƒZ*dd„ Z+e,dkrŸe+ƒ  dS dS )aè  
monitor.py

Orchestrator for this project -- the piece that ties scanner, stream,
smart_engine, exit, and data_store together into a runnable daily
lifecycle:

    START -> wait for schedule.premarket_scan_time
          -> run universe.py + scanner.py (writes today's candidate list)
          -> wait for market open
          -> subscribe stream.py to the candidate list [BUGFIX 2026-09-17:
             was subscribed right after the scan, before the open --
             stream.py's live tick handlers have no premarket/regular-
             session boundary filter (unlike its own REST _backfill(),
             which correctly starts from session_open_dt()), so any
             premarket prints between the scan and 09:30 silently sat in
             the same buffer smart_engine.py reads as "session bars." Let
             several 2026-09-17 entries (MARA, NVAX) pass Stage 1/2 within
             25-70s of the open on premarket-contaminated VWAP/trend/bar-
             count, chasing the opening print instead of a real move.
             Subscribing only after the open removes the contamination
             window outright rather than trying to filter it out.]
          -> main loop, every schedule.poll_interval_seconds:
               for each OPEN position: pull bars from the stream, call
               exit.evaluate() (hard stop at the entry stop_price + confirmed multi-signal
               deterioration), close on should_exit
               for each WATCHED (non-position) candidate, while a slot
               is free and before no_new_entries_after: pull
               bars/quote/resistance from the stream, call
               smart_engine.evaluate(), enter on BUY
               every cycle: log the full indicator dict for every
               symbol evaluated (data_store.append_decision_record),
               whether or not it ever traded
          -> force_liquidate_time -> close everything, write the day's
             trade summary
          -> shutdown

Run directly:
    python monitor.py

Designed to be started once per day via cron shortly before the open,
and to exit cleanly after end-of-day liquidation. A singleton file
lock (data_store.acquire_singleton_lock) prevents two instances ever
running against this account at once.
é    N)ÚdatetimeÚtimezone)Ú
get_configÚget_env)Ú
get_logger)ÚPositionManager)ÚStreamManager)Ú
get_clientÚmonitorzmonitor.pidc                   @   sÀ   e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd*d
edefdd„Z	defdd„Z
dd„ Zdd„ Zdefdd„Zdd„ Zdd„ Zdd„ Zdededefdd „Zd!d"„ Zd#d$„ Zdefd%d&„Zd'd(„ Zd)S )+ÚSessionOrchestratorc                 C   s>  t ƒ | _tƒ  ¡  tƒ | _tƒ | _tƒ | _	d | _
i | _| j di ¡}| d¡rKzddlm} ||ƒ| _
| j
| j	_
W n tyJ   t d¡ d | _
Y nw g | _i | _i | _i | _i | _i | _i | _i | _i | _i | _i | _t | j d¡¡| _i | _ i | _!d| _"d | _#d | _$d | _%t& '¡ | _(t) )t)j*| j+¡ t) )t)j,| j+¡ d S )NÚshadow_entry_rulesÚenabledr   )ÚShadowEntryRulesz:[SHADOW] entry_rules failed to load; continuing without itÚplaybookF)-r   Úcfgr   Úvalidater	   Úclientr   Úposition_mgrr   ÚstreamÚshadowÚ_shadow_keyÚgetÚentry_rulesr   Ú	ExceptionÚlogÚ	exceptionÚ
candidatesÚ_resistance_levelsÚ_sr14Ú_avg_vol_baselineÚ_entry_persistenceÚ_exit_confirmationÚ_giveback_room_stateÚ_resistance_stall_stateÚ_last_exit_healthyÚ_last_plan_keyÚ_last_v2_keyr   ÚPlaybookÚ	_playbookÚ_last_play_keyÚ_exit_v2_stateÚ	_shutdownÚ_last_scan_startedÚ_rescan_threadÚ_pending_scanÚ	threadingÚLockÚ_pending_lockÚsignalÚSIGTERMÚ_handle_signalÚSIGINT)ÚselfÚsh_cfgr   © r8   ú"/var/www/screener/trade/monitor.pyÚ__init__Q   sL   




þ
zSessionOrchestrator.__init__c                 C   s   t  d|› d�¡ d| _d S )Nz[SHUTDOWN] Received signal z, shutting down gracefullyT)r   Úinfor+   )r6   ÚsignumÚframer8   r8   r9   r4   ‡   s   
z"SessionOrchestrator._handle_signalc                 C   sx   t  d| jd d › �¡ t ¡ st  d¡ d S |  ¡  | jr!d S |  ¡  |  ¡  |  	¡  |  
¡  |  ¡  t  d¡ d S )Nz#[START] monitor.py starting | mode=ÚmodeÚexecution_modez[START] Not a weekday, exitingz[SHUTDOWN] Session complete)r   r;   r   Úmarket_timeÚ
is_weekdayÚ_wait_for_premarket_scanr+   Ú	_run_scanÚ_wait_for_market_openÚ_start_streamingÚ_main_trading_loopÚ_end_of_day_liquidation©r6   r8   r8   r9   ÚrunŒ   s   
zSessionOrchestrator.runc                 C   ó6   t  ¡ s| jst d¡ t  ¡ s| jrd S d S d S d S )Né   )r@   Úis_premarket_scan_timer+   ÚtimeÚsleeprH   r8   r8   r9   rB       ó   
ÿz,SessionOrchestrator._wait_for_premarket_scanÚ Úcandidates_file_suffixÚreturnc              	      sp  t  | j¡}t dt|ƒ› �¡ i i i }}}t j| j||||d�}t dt|ƒ› d�¡ i i i }}}	| j di ¡}
t	j
| j||||
 d¡rK|	nd|
d�}t	j| j||||||d	�}| j d
i ¡ d¡dkr°z8t ¡  ¡ ‰ ‡ fdd„|D ƒ}|r tjˆ |dd� |D ]}tj tˆ ƒ|fd¡ q…t dt|ƒ› d|› �¡ W n ty¯   t d¡ Y nw |||||	dœS )zePure scan -- returns results without touching live state, so it
        can run on the rescan thread.z,[SCAN] Universe size after asset filtering: )Úbaseline_outÚprev_day_high_outÚprev_close_outz[SCAN] Prefiltered to z symbolsÚsr14r   N)Údaily_atr_outÚ	ref5d_outÚsr14_outÚsr14_cfg)ÚprefilteredÚvolume_baselinesÚprev_day_highsÚprev_closesÚrange_20d_highsrQ   Úsmart_engineÚ
entry_modeÚv2c                    s(   g | ]}t  ˆ |d  ¡du r|d  ‘qS )ÚsymbolN)Ú	referenceÚload©Ú.0Úc©Útodayr8   r9   Ú
<listcomp>Æ   s   ( z5SessionOrchestrator._compute_scan.<locals>.<listcomp>F)Úverbosez![REF] built 14-day reference for z new symbols: zB[REF] reference build failed (entry_v2 will WAIT on those symbols))r   Ú	baselinesÚ	daily_atrÚref5drV   )ÚuniverseÚget_universe_symbolsr   r   r;   ÚlenÚprefilter_by_snapshotr   r   ÚscannerÚcompute_range_20d_highÚscanr@   Únow_etÚdaterd   ÚbuildÚ_CACHEÚpopÚstrr   r   )r6   rQ   Úall_symbolsrm   r]   r^   r[   rn   ro   rV   Úsr_cfgr_   r   ÚmissingÚs_r8   ri   r9   Ú_compute_scan¤   sP   üýû
€ÿÿz!SessionOrchestrator._compute_scanÚresultc              	   C   s  | j  |d ¡ |d D ][}|d }| j |d | d¡| d¡| d¡dœ¡ | d	i ¡ |d ¡| j|d  d	< | j|d   | d
i ¡ |d i ¡¡ |d | di ¡v rg| j |d |d |d  ¡ qdd„ | jD ƒ}|d | _t dt	| jƒ› ddd„ | jD ƒ› �¡ |S )zúMain-thread only. Swaps in the new candidate list; keeps each
        already-known symbol's resistance levels from the first scan it
        appeared in (a mid-session scan's 'premarket_high' is really the
        last-6h high, not the premarket's).rm   r   Úmetricsrc   Úpremarket_highÚprevious_day_highÚrange_20d_high)r„   Úprev_day_highr†   rn   ro   rV   c                 S   ó   g | ]}|d  ‘qS ©rc   r8   rf   r8   r8   r9   rk   ç   ó    z3SessionOrchestrator._apply_scan.<locals>.<listcomp>z[SCAN] z candidates selected: c                 S   rˆ   r‰   r8   rf   r8   r8   r9   rk   ê   rŠ   )
r   Úupdater   Ú
setdefaultr   r   r   r   r;   rr   )r6   r‚   rh   ÚmÚold_symsr8   r8   r9   Ú_apply_scanÑ   s&   ý$(€
ÿzSessionOrchestrator._apply_scanc                 C   s   t   ¡ | _|  |  ¡ ¡ d S )N)rM   r,   r�   r�   rH   r8   r8   r9   rC   í   s   
zSessionOrchestrator._run_scanc              
      s  ˆj d  dd¡}|sdS ˆj� ˆjd}ˆ_W d  ƒ n1 s"w   Y  |dur‚ˆ |¡‰dd„ ˆjD ƒ‰ tˆj ¡ ƒ‰‡fdd„ˆ D ƒ}‡ ‡‡fdd„ˆj	j
D ƒ}t d	t|ƒ› d
|› dt|ƒ› d
|› �¡ ˆj	 |¡ ˆj	 |¡ |D ]	}ˆj |d¡ qxt ¡ rˆdS ˆjdur”ˆj ¡ r”dS ˆj d }t ¡ }| dd¡}| dd¡}	|	r¼d|  krµdk r¼n nt||	ƒ}|oÈtˆddƒ oÈ||k}
|
rÏdˆ_nˆjrßt ¡ ˆj |d k rßdS t ¡ ˆ_dt ¡  d¡ ‰‡‡fdd„}t d|› d�¡ tj|dd�ˆ_ˆj  ¡  dS )a  [2026-09-23] Refresh the 30-candidate list every
        scanner.rescan_interval_minutes (0 = off) until
        no_new_entries_after. Symbols that drop off the list are
        unsubscribed unless a position is open in them; new ones are
        backfilled and subscribed.rt   Úrescan_interval_minutesr   Nc                 S   rˆ   r‰   r8   rf   r8   r8   r9   rk   ÿ   rŠ   z5SessionOrchestrator._maybe_rescan.<locals>.<listcomp>c                    ó   g | ]}|ˆ vr|‘qS r8   r8   ©rg   Ús)rŽ   r8   r9   rk     ó    c                    s,   g | ]}|ˆ vr|ˆvr|ˆ  ¡ vr|‘qS r8   )Ú_benchmarksr’   )Únew_symsÚ	open_symsr6   r8   r9   rk     s    ÿz[RESCAN] applied: +ú z / -Úfirst_rescan_after_open_minutesÚ"rescan_interval_first_hour_minutesé<   Ú_early_rescan_doneFTÚ_z%H%Mc                     sb   zˆ j ˆd�} W n ty   t d¡ Y d S w ˆ j� | ˆ _W d   ƒ d S 1 s*w   Y  d S )N)rQ   z4[RESCAN] scan failed; keeping current candidate list)r�   r   r   r   r1   r.   )Úres)r6   Úsuffixr8   r9   Úworker#  s   
þ"ÿz1SessionOrchestrator._maybe_rescan.<locals>.workerz%[RESCAN] starting background rescan (z-min interval))ÚtargetÚdaemon)!r   r   r1   r.   r�   r   Úsetr   Úget_open_symbolsr   Ú_subscribedr   r;   rr   Úadd_symbolsÚremove_symbolsr    r{   r@   Úis_new_entries_cutoffr-   Úis_aliveÚminutes_since_openÚminÚgetattrrœ   r,   rM   rw   Ústrftimer/   ÚThreadÚstart)r6   Úintervalr‚   ÚaddedÚdroppedr“   ÚscÚ	mins_openÚearlyÚ
first_hourÚ	due_earlyr    r8   )r–   rŽ   r—   r6   rŸ   r9   Ú_maybe_rescanñ   sP   ÿ
*

ÿ
	z!SessionOrchestrator._maybe_rescanc                 C   s,   | j d u rg S t| j di ¡ dddg¡ƒS )Nr   Úbenchmark_symbolsÚSPYÚIWM)r   Úlistr   r   rH   r8   r8   r9   r•   0  s   
zSessionOrchestrator._benchmarksc                    sb   dd„ | j D ƒ‰ ˆ ‡ fdd„|  ¡ D ƒ7 ‰ t|  ¡ ƒdd„ | j D ƒ | j_ˆ r/| j ˆ ¡ d S d S )Nc                 S   rˆ   r‰   r8   rf   r8   r8   r9   rk   6  rŠ   z8SessionOrchestrator._start_streaming.<locals>.<listcomp>c                    r‘   r8   r8   ©rg   Úb©Úsymbolsr8   r9   rk   8  r”   c                 S   s   h | ]}|d  ’qS r‰   r8   rf   r8   r8   r9   Ú	<setcomp>9  rŠ   z7SessionOrchestrator._start_streaming.<locals>.<setcomp>)r   r•   r£   r   Ú	bars_onlyr¯   rH   r8   r¿   r9   rE   5  s    ÿz$SessionOrchestrator._start_streamingc                 C   rJ   )Né   )r@   Úis_past_market_openr+   rM   rN   rH   r8   r8   r9   rD   =  rO   z)SessionOrchestrator._wait_for_market_openc                 C   s�   | j d d }| jsBt ¡ sDz|  ¡  |  ¡  | j ¡ r$t ¡ s$|  	¡  W n t
y3   t d¡ Y nw t |¡ | jsFt ¡ rd S d S d S d S )NÚscheduleÚpoll_interval_secondsz$[LOOP] poll cycle failed; continuing)r   r+   r@   Úis_force_liquidate_timer¸   Ú_update_open_positionsr   Úhas_available_slotr¨   Ú_scan_for_entriesr   r   r   rM   rN   )r6   r°   r8   r8   r9   rF   B  s   €ÿ
òz&SessionOrchestrator._main_trading_looprc   Úsession_highc                 C   s   t | j |i ¡ƒ}||d< |S )NrË   )Údictr   r   )r6   rc   rË   Úlevelsr8   r8   r9   Ú_resistance_forV  s   z#SessionOrchestrator._resistance_forc                 C   sÔ  | j  ¡ D �]á}| j |¡}|sq| j j| }| j di ¡}| d¡rÅ| j |¡}|r4| d¡|d kr?d|d i }| j|< tj	||||t
 t ¡  ¡ |¡d�}t |d|j|j|jdœ¡ |j|j d¡|j d¡f}| j d	|f¡|kr‘|| jd	|f< t d
|› d|d d › d|j› �¡ |jr¾| j  ||d d |j¡ | j |d ¡ | j |d ¡ | j |d ¡ | j |d ¡ q| dd¡rÅq| j |i ¡}t 	|||d |¡}	|	j| j|< t |d|	j|	j|	jdœ¡ |	j�r|d d }
| j  ||
|	j¡ | j |d ¡ | j |d ¡ | j |d ¡ q| j di ¡ d¡�rxtj	|||d t  !t"j#¡| j |i ¡| j  $¡ | j% |d¡d�}|j| j|< t |d|j|j|jdœ¡ |j�rx| j  ||d d |j¡ | j |d ¡ | j |d ¡ | j |d ¡ q| j |i ¡}| j di ¡ dd¡}| jj&||d�}t'j	|||d ||| d¡d�}|j(| j|< |j )d¡| j%|< t |d|j|j|jdœ¡ |j�rç|d d }
| j  ||
|j¡ | j |d ¡ | j |d ¡ | j |d ¡ qd S )NÚexit_v2r   Ú_entryÚ
entry_time)Úref)rc   ÚkindÚstateÚreasonrƒ   Ú	stop_fromÚarmedÚxz
[EXIT_V2] ú @ éÿÿÿÿrh   ú: Ú	replace_jTÚentry_priceÚexit_giveback_roomÚresistance_stall)Ú
slots_freeÚhealthyÚexit_resistance_stallÚexitÚimbalance_window_secondsé   ©Úwindow_secondsÚ
stop_price)Útrade_imbalancerè   rá   )*r   r¤   r   Úget_barsÚ	positionsr   r   r*   rÏ   Úevaluaterd   re   r@   rw   rx   Ú
data_storeÚappend_decision_recordrÔ   rÕ   rƒ   r)   r   r;   Úshould_exitÚexit_positionr{   r!   r"   r#   rÞ   Ú	state_outrâ   r   Únowr   ÚutcrÉ   r$   Úget_trade_imbalanceÚexit_engineÚconfirmationÚ
startswith)r6   rc   ÚbarsÚpÚxcfgÚxstateÚxdÚxkeyÚgiveback_stateÚgiveback_decisionÚcurrent_priceÚstall_decisionÚconfirmation_stateÚexit_imbalance_windowré   Údecisionr8   r8   r9   rÈ   [  s¦   
$þ&ÿþüþþþ€¡z*SessionOrchestrator._update_open_positionsc                    sØ  | j d  di ¡}i }| dd¡dkr't ¡ D ]}| d¡dkr&|||d < q| jD �]>}|d }| j |¡r8q*| j ¡ s@ d S | j 	|¡}|sIq*t
 ||d d	 | |¡|¡\}}|sß| j |d ¡ d
|d d	 ig }	}
| j  di ¡ d¡dkrÏz7| j |¡}tj||t t ¡  ¡ |¡| j |d¡i | j  d¡|r¥|d |d dœnd d�}|j|j}	}
W n tyÎ } zt d|› d|› �¡ W Y d }~nd }~ww t |ddg |g|
 |	dœ¡ q*| j |¡}|rð|d |d dœnd }tdd„ |D ƒƒ}|  ||¡}tdd„ |D ƒƒ}| j  |¡}| j |i ¡}| j  di ¡ dd¡}| jj!||d�}| j  di ¡ d¡dk�rUtj||t t ¡  ¡ |¡| j |d¡|| j  d¡|d�}nt"j||||||||d�}|j#| j|< |  $||¡ |j d¡dk�r'| j%j  d ¡�r'z| j% ||t t ¡  ¡ |¡¡‰ W n) t�yº } zd!d"d d d#|› �d$œ‰ t d%|› d&|› �¡ W Y d }~nd }~ww ‡ fd'd(„d)D ƒ|jd*< ˆ d+ ˆ  d,¡f}| j& |¡|k�rð|| j&|< t 'd%|› d&ˆ d+ › d-ˆ d. › �¡ |j(�r'| j% )ˆ ¡�r't 'd%|› d/ˆ d+ › d&ˆ d. › d0�¡ d"|_(d1ˆ d+ › �|_*ˆ d. gt+|j,�p#g ƒ |_|j d2¡}|�r•|d3 |d4 t-d5d„ |d6 D ƒƒf}| j. |¡|k�r•|| j.|< t 'd7|› d8|j d
¡› d&|d3 › �|d4 �rod9|d4 › d0�nd: d;|d< › d=|d> › d9|d? › d@|dA › d-�	 dB /|dC |d6  ¡ ¡ | j0 |¡�r¿|j d
¡�r¿zt1 2| j0| |jd
 ¡|jdD< W n
 t�y¾   Y nw |j d¡dk�r-|j}|j*t-dEd„ |jD ƒƒf}| j3 |¡|k�r-|| j3|< t 'd|› d8| d
¡› d&|j*› dF| dG¡› dH| dI¡› dJ| dK¡› dL| dM¡› dN| dO¡› dP| dQ¡› dR| dS¡› dT�dB /|j�p)|j,¡ ¡ t |d|j*|j,|j|jdœ¡ |j(�ri|j dUi ¡ d<¡}|d u �rQq*| jj4||jd
 ||j,dV�}|�ri| j |d ¡ q*d S )WNÚtradingÚreentry_after_lossr>   ÚoffÚstatusÚclosedrc   rÚ   rh   Úpricer`   ra   rb   é   Úentry_v2r   é   )ÚbidÚask)r   Úquotez[V2] z): metrics while re-entry blocked failed: ÚentryÚREJECT)rc   rÓ   rÔ   Úreasons_forÚreasons_againstrƒ   c                 s   ó   � | ]}|d  V  qdS )ÚhNr8   r½   r8   r8   r9   Ú	<genexpr>ç  ó   € z8SessionOrchestrator._scan_for_entries.<locals>.<genexpr>c                 s   r  )ÚvNr8   r½   r8   r8   r9   r  é  r  rä   rå   ræ   )r  ré   r   ÚERRORFzplaybook error: )ÚactionÚfinalÚgroupÚ
group_namerÕ   z[PLAY] rÛ   c                    s   i | ]}|ˆ   |¡“qS r8   ©r   )rg   Úk©Úpvr8   r9   Ú
<dictcomp>  r”   z9SessionOrchestrator._scan_for_entries.<locals>.<dictcomp>)r  r  r  rÕ   r  Úat_1015Úat_1020r   r  r  z | rÕ   z": v2 BUY blocked by the playbook (ú)Ú	PLAYBOOK_ÚplanrÔ   Úsetupc                 s   ó   � | ]
}|  d ¡d V  qdS ©ú:r   N©Úsplit©rg   Útr8   r8   r9   r    ó   € Útriggersz[PLAN] rÙ   z (rP   z | stop Ústopz	, target r¡   Útarget_labelz), R:R Úreward_riskz; ÚreasonsrV   c                 s   r*  r+  r-  )rg   Úrr8   r8   r9   r  '  r1  z | RVOL Ú
rvol_sofarz vol15x Úvol15_xz buy Úbuy_share15z R2 Úr2_15z eff Úeff15z VWAP Úvwap_pctz	% spread Ú
spread_pctz% | Ústage3)r6  )5r   r   rí   Úload_today_tradesr   r   Úis_symbol_openrÉ   r   rê   ÚreentryÚcheckr    r{   Ú	get_quoter  rì   rd   re   r@   rw   rx   Úget_flowrƒ   r  r   r   Úwarningrî   ÚmaxrÎ   Úsumr   rô   r`   ÚpersistenceÚ_shadow_recordr(   r)   r;   Úshould_enterÚblocksrÔ   r¼   r  Útupler%   Újoinr   Ú
volatilityÚsr_positionr&   Úenter_position)r6   Úreentry_cfgÚ
last_trader0  rh   rc   rø   ÚallowedÚwhyrƒ   Ú
v2_againstÚqÚv2dÚeÚquote_tupler  rË   Ú
resistanceÚcumulative_volumeÚavg_vol_baselineÚpersistence_stateÚimbalance_windowré   r  Úpkeyr(  ÚkeyÚv2mr3  Úenteredr8   r!  r9   rÊ   ½  s  €
"ý"€ÿþýþ"$ÿ"€ý
$&$
$ÿ ÿþý"ÿ
 ÿÿþþþýýüý
ÿ€‡z%SessionOrchestrator._scan_for_entriesc                 C   s0   | j d u rd S ddlm} || j | j||ƒ d S )Nr   )Úrecord_decision)r   r   rd  r   )r6   rc   r  rd  r8   r8   r9   rJ  @  s   
z"SessionOrchestrator._shadow_recordc                 C   sn  t  d¡ | jd ur| j ¡  | jd d }| jd d }t ¡ | }t ¡ |k r_| j ¡ }|s1nG|D ] }| j 	|¡}|rC|d d n| jj
| d }| j ||d¡ q3t |¡ t ¡ |k s)| j ¡ }|rxt  d	t|ƒ› d
|› dt|ƒ› �¡ | j ¡  t ¡ }	t |	¡ tdd„ |	D ƒƒ}
tdd„ |	D ƒƒ}tdd„ |	D ƒƒ}t  dt|	ƒ› d|› d|› d|
d›�¡ d S )Nz*[EOD] Force-liquidating all open positionsrÅ   Ú eod_liquidation_max_wait_secondsrÆ   rÚ   rh   rÝ   Ú
END_OF_DAYz[EOD] z& position(s) still not resolved after zs: c                 s   s*   � | ]}|  d ¡dkr|  dd¡V  qdS )r  r	  Ú
pl_dollarsr   Nr  r/  r8   r8   r9   r  a  s   €( z>SessionOrchestrator._end_of_day_liquidation.<locals>.<genexpr>c                 s   s2   � | ]}|  d ¡dkr|  dd¡dkrdV  qdS ©r  r	  rg  r   r  Nr  r/  r8   r8   r9   r  b  ó   €0 c                 s   s2   � | ]}|  d ¡dkr|  dd¡dkrdV  qdS rh  r  r/  r8   r8   r9   r  c  ri  z[EOD] Session summary: trades=z wins=z losses=z total_P/L=$z.2f)r   r;   r   Úflushr   rM   r   r¤   r   rê   rë   rð   rN   rF  rr   Úsortedr3  rí   r@  Úwrite_trades_summaryrH  )r6   Úmax_waitÚpoll_intervalÚdeadlineÚopen_symbolsrc   rø   r   Ú
still_openÚtradesÚtotal_plÚwinsÚlossesr8   r8   r9   rG   F  s@   



 
ø

ÿÿ

ÿz+SessionOrchestrator._end_of_day_liquidationN)rP   )Ú__name__Ú
__module__Ú__qualname__r:   r4   rI   rB   r|   rÌ   r�   r�   rC   r¸   r¼   r•   rE   rD   rF   ÚfloatrÎ   rÈ   rÊ   rJ  rG   r8   r8   r8   r9   r   P   s&    6-?b r   c                  C   s  zot  ¡ �` t  ¡  ttdƒ�} |  tt ¡ ƒ¡ W d   ƒ n1 s#w   Y  zt	ƒ }| 
¡  W zt t¡ W n tyA   Y nw zt t¡ W w  tyR   Y w w W d   ƒ W d S W d   ƒ W d S 1 shw   Y  W d S  tyŽ } zt t|ƒ¡ t d¡ W Y d }~d S d }~ww )NÚwr  )rí   Úacquire_singleton_lockÚensure_dirsÚopenÚPID_PATHÚwriter|   ÚosÚgetpidr   rI   ÚremoveÚOSErrorÚRuntimeErrorr   ÚerrorÚsysrã   )ÚfÚorchestratorrY  r8   r8   r9   Úmainh  s8   
ÿ
ÿþÿÿ÷&õ€þr‰  Ú__main__)-Ú__doc__r€  r†  rM   r2   r/   r   r   Úconfig_loaderr   r   Úlogger_setupr   r@   rí   rp   rt   r`   rã   rõ   rÞ   râ   rB  rO  rd   r  r   rÏ   Úposition_managerr   r   r   Úalpaca_clientr	   r   ÚpathÚdirnameÚabspathÚ__file__ÚBASE_DIRrN  r~  r   r‰  rv  r8   r8   r8   r9   Ú<module>   sL    .    
ÿ