o
    ½½jÈ% ã                   @   sô  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mZ ddlmZ ddl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 m!Z!m"Z#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/Z0ddl1Z2ee3ƒ 4¡ j5Z6edƒZ7dd„ Z8de9de9de	fdd„Z:de;de9dede<fdd„Z=G dd„ dƒZ>dd„ Z?dd„ Z@dd„ ZAdd „ ZBde;de9dede<fd!d"„ZCde9de;fd#d$„ZDde9fd%d&„ZEdPd(e	d)e	d*eFfd+d,„ZGd-e9d(e	d)e	d.efd/d0„ZH	1dQde;d(e	d)e	ded2eFf
d3d4„ZIG d5d6„ d6ƒZJG d7d8„ d8ƒZKdRd:ed;eLfd<d=„ZM		>	>	>	>	>	>							dSde9d?eFd@e;dedAeLdeKfdBdC„ZNdDe;de<fdEdF„ZOdDe;dGe9fdHdI„ZPdJe;de9fdKdL„ZQdMdN„ ZReSdOk�rxeRƒ  dS dS )Tah
  
simulate.py

Historical replay of a real trading day through the ACTUAL live code --
smart_engine.py's evaluate(), exit.py's evaluate(), stream.py's own
SymbolBuffer -- fed by Alpaca's historical tick trades/quotes instead of
the live SIP websocket. Not a reimplementation of the strategy: the
exact same decision functions monitor.py calls are called here, so a
replay run with the same max_positions monitor.py actually used should
reproduce that day's real trades almost exactly (see --sanity).

Built to answer one concrete question (2026-09-18): today's live run
traded with max_positions=3 the whole session (the config change to 6
was saved mid-day but a running monitor.py never reloads config, so it
only takes effect on the next restart) -- what would today have looked
like if 6 slots had been live from the open?

Memory design: this host has well under 1GB free RAM, and a full
session's tick-level quotes for 30 symbols does not fit in memory at
once (confirmed: one active symbol's full-day quotes alone ran
~150k-190k rows). So data is fetched and cached to disk PER SYMBOL, PER
HOUR CHUNK (bounding peak memory to one small chunk regardless of total
day volume), then the replay phase streams each symbol's cache file
line-by-line rather than holding it in memory -- SymbolBuffer itself is
already bounded (390-bar rolling window, 90s trade log), so total replay
memory stays small and flat regardless of session length.

Usage:
    python simulate.py --date 2026-09-18 --max-positions 3 --sanity
        Replays with 3 slots and diffs the result against the real
        data/trades/<date>_trades.jsonl -- run this FIRST to trust the
        replay's fidelity before trusting any hypothetical slot count.
        Fetches+caches tick data on first run; later runs (same date)
        reuse the cache instantly.

    python simulate.py --date 2026-09-18 --max-positions 6
        The actual question: what the day looks like with 6 slots live
        from the open. Reuses the cache from the --sanity run above.

Known simplifications:
- Position sizing uses ONE fixed equity snapshot (fetched live at
  simulate.py start), not the real intraday-fluctuating equity
  PositionManager.calculate_qty() reads every entry -- share counts and
  dollar P&L will be close but not identical to a real run even at the
  same slot count; %% P&L per trade is unaffected.
- 1-min bars are built purely from replayed trade ticks (same
  SymbolBuffer._accumulate_bar() logic the live bot uses from its own
  trade stream), not Alpaca's separately-aggregated official bars.
  Expected to be extremely close (same trades, same VWAP/OHLC math) but
  not bit-exact.
é    N)ÚThreadPoolExecutorÚas_completed)ÚdatetimeÚ	timedelta)ÚPath)ÚZoneInfo)Ú
get_config)Ú
get_client©ÚSymbolBuffer)ÚShadowEntryRulesÚBarÚrecord_decisionÚUTCc                   C   s   t tƒ d d ƒS )NÚscheduleÚtimezone)r   r   © r   r   ú#/var/www/screener/trade/simulate.pyÚ_tzY   s   r   Údate_strÚhhmmssÚreturnc              	   C   sL   dd„ |  d¡D ƒ\}}}dd„ |   d¡D ƒ\}}}t||||||tƒ d�S )Nc                 s   ó   � | ]}t |ƒV  qd S ©N©Úint©Ú.0Úxr   r   r   Ú	<genexpr>^   ó   € z_et_dt.<locals>.<genexpr>ú:c                 s   r   r   r   r   r   r   r   r   _   r    Ú-©Útzinfo)Úsplitr   r   )r   r   ÚhÚmÚsÚyÚmoÚdr   r   r   Ú_et_dt]   s   r,   ÚsymbolsÚ	cache_dirc                    s¢   |d }|  ¡ rt | ¡ ¡S t|dƒ t¡‰ |  |ˆ tdd� ˆ tdd� ¡}i }| 	¡ D ]\}}‡ fdd„|D ƒ}	t
 |	¡}
|
rF|
||< q.| t |¡¡ |S )	z [2026-09-23] Daily ATR(14) per symbol from sessions BEFORE date_str
    (what the live scanner would have seen that morning), cached next to
    the tick cache.zdaily_atr.jsonú00:00:00é-   ©Údaysé   ©Úsecondsc                    ó   g | ]
}|d  ˆ k r|‘qS ©Útr   ©r   Úb©Údayr   r   Ú
<listcomp>n   ó    z"load_daily_atr.<locals>.<listcomp>)ÚexistsÚjsonÚloadsÚ	read_textr,   Ú
astimezoner   Úget_daily_bars_bulkr   ÚitemsÚ
volatilityÚ	daily_atrÚ
write_textÚdumps)Úclientr-   r   r.   ÚpathÚbarsÚoutÚsymÚblÚdoneÚdatrr   r;   r   Úload_daily_atrc   s   "
€rR   c                   @   s(   e Zd ZdZdd„ Zdd„ Zdd„ ZdS )	Ú
_ShadowTapz�[2026-09-26] Forwards replayed ticks to the SymbolBuffer AND the shadow
    recorder (entry_rules.py), exactly as stream.py's handlers do live.c                 C   s   |||| _ | _| _d S r   )ÚsymbolÚbufÚshadow)ÚselfrT   rU   rV   r   r   r   Ú__init__y   s   z_ShadowTap.__init__c                 C   ó(   | j  |||¡ | j | j|||¡ d S r   )rU   Úon_traderV   rT   )rW   ÚpriceÚsizeÚtsr   r   r   rZ   |   ó   z_ShadowTap.on_tradec                 C   rY   r   )rU   Úon_quoterV   rT   )rW   ÚbidÚaskr]   r   r   r   r_   €   r^   z_ShadowTap.on_quoteN)Ú__name__Ú
__module__Ú__qualname__Ú__doc__rX   rZ   r_   r   r   r   r   rS   v   s
    rS   c                 C   s  |D ]A}|| j dd�}|dd… D ]0}	| |¡du s#|	d || krB|  |t|	d |	d |	d |	d |	d	 |	d
 ƒ¡ |	d ||< qq| ¡ D ]>\}}
| |d¡}|t|
ƒk r‚|
| jtdd� |kr‚|  ||
| ¡ |d7 }|t|
ƒk r‚|
| jtdd� |ksd|||< qHdS )zCFeed newly COMPLETED 1-min bars (and benchmark bars) to the shadow.F)Úinclude_formingéýÿÿÿNr8   Úor&   ÚlÚcÚvr   r3   ©Úminutes)Úget_barsÚgetÚon_barÚ	ShadowBarrE   Úlenr]   r   )rV   r-   ÚbuffersÚlast_barÚ
bench_barsÚ	bench_posÚt_utcrN   rL   r:   rO   Úir   r   r   Ú_shadow_push_bars…   s   2€ý$$ÿ
üry   c                 C   sB   i }dD ]}| j |t|dƒt|dƒdd�}dd„ |D ƒ||< q|S )N)ÚSPYÚIWMú09:30:00z16:00:00iè  ©ÚstartÚendÚlimitc              
   S   s>   g | ]}t |jt|jƒt|jƒt|jƒt|jƒt|jƒƒ‘qS r   )rq   Ú	timestampÚfloatÚopenÚhighÚlowÚcloseÚvolumer9   r   r   r   r=   ™   s
    &ÿÿz_bench_bars.<locals>.<listcomp>)Úget_minute_barsr,   )rJ   r   rM   rN   Úrawr   r   r   Ú_bench_bars”   s   ÿÿrŠ   c           
   	   C   sô   ddl }i }td d d d|› d� }| ¡ rIz't | |d¡¡}| d	i ¡ ¡ D ]\}}| d¡r=|d d d
 ||< q*W n	 tyH   Y nw |D ],}||vrwz| j	|t
|dƒt
|dƒd
d�}	|	rlt|	d jƒ||< W qK tyv   Y qKw qK|S )a  [2026-09-29] Official 9:30 opening prices. The replay builds bars from raw ticks, whose
    first print can be an odd lot far from the official open (AGEN 9/29: 9.56 vs 9.66); the
    live bot's bars are Alpaca's official ones, so v2 / the playbook get the official open.r   NÚreportsÚopen_windowrL   Úbars_z.json.gzÚrtr-   r3   r|   z09:31:00r}   )ÚgzipÚBASE_DIRr?   r@   Úloadrƒ   ro   rE   Ú	Exceptionrˆ   r,   r‚   )
rJ   r   r-   r�   rM   Úpr+   r(   rk   r‰   r   r   r   Ú_official_opensž   s4   
€þÿ€ÿûr”   c                 C   s<   | sd S t  t| ƒ ¡ ¡d }dd„ |D ƒ}t|dd„ d�S )NÚtradesc              	   S   s2   g | ]}|d  |d |  d¡t |d ¡dœ‘qS )rT   Ú
stop_priceÚreasonsÚ
entry_time)rT   r–   r—   Ú_t)ro   r   Úfromisoformat©r   r8   r   r   r   r=   ¼   s
    ÿ
ÿz_load_fixed.<locals>.<listcomp>c                 S   ó   | d S )Nr™   r   )r   r   r   r   Ú<lambda>¾   ó    z_load_fixed.<locals>.<lambda>©Úkey)r@   rA   r   rB   Úsorted)rK   r•   rM   r   r   r   Ú_load_fixed¸   s   ÿr¢   c                    sŒ   |d }|  ¡ rt | ¡ ¡S t|dƒ t¡‰ |  |ˆ tdd� ˆ tdd� ¡}‡ fdd„| 	¡ D ƒ}d	d„ | 	¡ D ƒ}| 
t |¡¡ |S )
z|[2026-09-25] 5-day reference frame per symbol as of date_str (last 5
    sessions BEFORE it), cached next to the tick cache.z
ref5d.jsonr/   é   r1   r3   r4   c                    s*   i | ]\}}|t  ‡ fd d„|D ƒ¡“qS )c                    r6   r7   r   r9   r;   r   r   r=   É   r>   z)load_ref5d.<locals>.<dictcomp>.<listcomp>)rF   Úfive_day_reference)r   rN   rO   r;   r   r   Ú
<dictcomp>É   s   * zload_ref5d.<locals>.<dictcomp>c                 S   s   i | ]	\}}|r||“qS r   r   )r   Úkrk   r   r   r   r¥   Ê   s    )r?   r@   rA   rB   r,   rC   r   rD   r   rE   rH   rI   )rJ   r-   r   r.   rK   rL   rM   r   r;   r   Ú
load_ref5dÁ   s   "r§   c                 C   sL   t d d | › d� }t|ƒ�}t |¡W  d   ƒ S 1 sw   Y  d S )NÚdataÚ
candidatesz_scanner.json)r�   rƒ   r@   r‘   )r   rK   Úfr   r   r   Úload_candidatesÏ   s   
$ÿr«   c                 C   s¾   t d d | › d� }| ¡ sdS tƒ }t|ƒ�}|D ]}| t |¡d ¡ qW d  ƒ n1 s2w   Y  tdd„ |D ƒƒ}|d g}|d	d… D ]}||d
   ¡ dkr\| 	|¡ qK|S )a$  [FIDELITY 2026-09-18] The real monitor.py loop's actual poll
    timestamps (data_store.append_decision_record logs one record per
    symbol evaluated, every poll) -- when available, replay drives its
    clock off these EXACT real timestamps instead of a synthetic 5s
    grid. Matters more than the ~5s cadence itself suggests: a synthetic
    grid starting at market_open_time on the dot vs. the real loop's
    actual first-poll offset (seen 2026-09-18: real polls landed at
    :06.03, not :00.00) is enough drift, by the time several candidates'
    18s Stage-2 holds are converging within a poll or two of each other
    near the open, to flip which of two near-simultaneous candidates
    wins a shared slot -- confirmed: the first synthetic-grid replay of
    2026-09-18 diverged from the real trade sequence starting at exactly
    that kind of close race (RUM vs AGEN for the 3rd slot). Returns None
    (caller falls back to the synthetic grid) if no decisions log exists
    for this date, e.g. simulating a day this tool didn't run against.
    r¨   Ú	decisionsú.jsonlNr�   c                 s   s"   � | ]}t  |¡jtd �V  qdS )r#   N)r   rš   Úreplacer   r›   r   r   r   r   í   s   €  z'load_real_poll_times.<locals>.<genexpr>r   r3   éÿÿÿÿç      ð?)
r�   r?   Úsetrƒ   Úaddr@   rA   r¡   Útotal_secondsÚappend)r   rK   Útimesrª   ÚlineÚdtsÚclustersr+   r   r   r   Úload_real_poll_timesÕ   s    
ÿÿ

€r¹   é   r~   r   rm   c                 c   sD   � | }t |d�}||k r t|| |ƒ}||fV  |}||k sd S d S )Nrl   )r   Úmin)r~   r   rm   ÚcurÚstepÚnxtr   r   r   Ú_chunksù   s   €

ýr¿   rT   Ú
cache_pathc                 C   sò   |  d¡}d}t|dƒ�^}t||ƒD ]P\}}	|  |||	¡}
|  |||	¡}dd„ |
D ƒ}|dd„ |D ƒ7 }|jdd„ d	� |D ]\}}}}| t | 	¡ |||g¡d
 ¡ q>|t
|ƒ7 }~
~~t ¡  qW d  ƒ n1 smw   Y  | |¡ |S )a  Low-memory by construction: fetches+writes one small (20-min)
    chunk at a time and drops it before the next -- this host has under
    1GB total RAM and a full-day chunk for an active symbol alone was
    enough to get OOM-killed with only 3 fetches in flight at once.z
.jsonl.tmpr   Úwc                 S   ó$   g | ]}|d  d|d |d f‘qS )r8   Útrader“   r(   r   r›   r   r   r   r=     ó   $ z cache_symbol.<locals>.<listcomp>c                 S   rÂ   )r8   Úquoter:   Úar   )r   Úqr   r   r   r=     rÄ   c                 S   rœ   )Nr   r   )Úer   r   r   r�     rž   zcache_symbol.<locals>.<lambda>rŸ   Ú
N)Úwith_suffixrƒ   r¿   Úget_historical_tradesÚget_historical_quotesÚsortÚwriter@   rI   Ú	isoformatrr   ÚgcÚcollectÚrename)rJ   rT   r~   r   rÀ   Útmp_pathÚn_eventsrª   Úchunk_startÚ	chunk_endr•   ÚquotesÚeventsr]   ÚkindrÆ   r:   r   r   r   Úcache_symbol  s$   
"
öÿ
rÚ   é   Úworkersc                    sr  ˆ j ddd� g }|D ]}ˆ |› d� }| ¡ r#td|› �tjd� q| |¡ q|s-d S t ¡ }	t|d��d‰‡ ‡‡‡‡fdd„|D ƒ}
d	}t|
ƒD ]H}|
| }z| 	¡ }W n! t
yw } ztd
|› d|› �tjd� d	}W Y d }~nd }~ww |d7 }td|› dt|ƒ› d|› d|› d�	tjd� qJW d   ƒ n1 s�w   Y  tdt|ƒ› dt ¡ |	 d›d�tjd� d S )NT©ÚparentsÚexist_okr­   z  [cache hit] ©Úfile)Úmax_workersc                    s,   i | ]}ˆ  tˆ|ˆˆˆ |› d � ¡|“qS ©r­   )ÚsubmitrÚ   ©r   r(   ©r.   rJ   r   Úpoolr~   r   r   r¥   '  s    $ÿz ensure_cache.<locals>.<dictcomp>r   z  [FAILED] ú: r3   z  [ú/z] z events cachedz
  fetched z symbols in ú.1fr(   )Úmkdirr?   ÚprintÚsysÚstderrr´   Útimer   r   Úresultr’   rr   )rJ   r-   r~   r   r.   rÜ   Útodor(   r“   Út0ÚfuturesrP   ÚfutrT   ÚnrÈ   r   ræ   r   Úensure_cache  s:   ÿ€þ.øü.rö   c                   @   s<   e Zd ZdZdefdd„Zdd„ Zdefdd	„Zd
d„ Z	dS )ÚSymbolEventReaderzlStreams one symbol's cached JSONL file line-by-line -- never holds
    more than the current line in memory.rK   c                 C   s*   |  ¡ r	t|dƒnd | _d | _|  ¡  d S )NÚr)r?   rƒ   Ú_fÚ_peekedÚ_advance_peek)rW   rK   r   r   r   rX   :  s   zSymbolEventReader.__init__c                 C   sV   | j d u r
d | _d S | j  ¡ }|sd | _d S t |¡\}}}}t |¡|||f| _d S r   )rù   rú   Úreadliner@   rA   r   rš   )rW   r¶   Úts_isorÙ   rÆ   r:   r   r   r   rû   ?  s   

zSymbolEventReader._advance_peekrU   c                 C   s|   | j d ur8| j d |kr:| j \}}}}|dkr| |||¡ n| |||¡ |  ¡  | j d ur<| j d |ksd S d S d S d S )Nr   rÃ   )rú   rZ   r_   rû   )rW   Úupto_tsrU   r]   rÙ   rÆ   r:   r   r   r   Údrain_up_toJ  s   (úzSymbolEventReader.drain_up_toc                 C   s   | j r
| j  ¡  d S d S r   )rù   r†   ©rW   r   r   r   r†   S  s   ÿzSymbolEventReader.closeN)
rb   rc   rd   re   r   rX   rû   r   rÿ   r†   r   r   r   r   r÷   6  s    	r÷   c                   @   s„   e Zd Zdededefdd„Zdefdd„Zd	e	defd
d„Z
dededefdd„Zd	e	dedefdd„Zd	e	dede	fdd„ZdS )ÚSimPositionManagerÚequityÚtrading_cfgÚmax_positionsc                 C   s"   || _ || _|| _i | _g | _d S r   )r  Úcfgr  Ú	positionsr•   )rW   r  r  r  r   r   r   rX   ]  s
   
zSimPositionManager.__init__r   c                 C   s   t | jƒ| jk S r   )rr   r  r  r   r   r   r   Úhas_available_slotd  s   z%SimPositionManager.has_available_slotrT   c                 C   s
   || j v S r   )r  )rW   rT   r   r   r   Úis_symbol_openg  s   
z!SimPositionManager.is_symbol_openÚentry_pricer–   c                 C   st   || }|dks
|sdS | j | jd  d }t|| ƒ}| j | jd  d }t|t|| ƒƒ}|| jd kr8|S dS )Nr   Úaccount_risk_pct_per_tradeç      Y@Ú#max_position_notional_pct_of_equityÚ
min_shares)r  r  r   r»   )rW   r	  r–   Úrisk_per_shareÚrisk_dollarsÚqtyÚnotional_capr   r   r   Úcalculate_qtyj  s   z SimPositionManager.calculate_qtyc                 C   sR   |   |¡s	|  ¡ sdS |  ||¡}|dkrdS ||||| ¡ |p!g dœ| j|< dS )NFr   )rT   r  r	  r–   r˜   r—   T)r  r  r  rÏ   r  )rW   rT   r	  r–   r]   r—   r  r   r   r   Úentert  s   þzSimPositionManager.enterÚ
exit_priceÚreasonc                 C   s  | j  |¡}|s
d S |d r||d  |d  d nd}||d  |d  }t| d|d ¡|ƒ}t| d|d ¡|ƒ}	||d  |d  }
i |¥|| ¡ |dt|dƒt|d	ƒ||	t||d  d
 d	ƒt|d |	 d
 d	ƒt|
d	ƒt|
| d	ƒdœ¥}| j |¡ | j |= d S )Nr	  r  ç        r  Ú
peak_priceÚ	low_priceÚclosedrÛ   é   éd   )r  Ú	exit_timeÚexit_reasonÚstatusÚpl_pctÚ
pl_dollarsr  r  Ú	mfe_centsÚ	mae_centsÚmfe_dollarsÚgiveback_dollars)r  ro   Úmaxr»   rÏ   Úroundr•   r´   )rW   rT   r  r  r]   r“   r  r   Úpeakr…   r#  rÃ   r   r   r   Úexit€  s&   $ùzSimPositionManager.exitN)rb   rc   rd   r‚   Údictr   rX   Úboolr  Ústrr  r  r  r(  r   r   r   r   r  \  s    
r  ç      4@rU   Úwindow_secondsc                 C   sŒ   | j }|sdS ddlm} |d d }|||d� }d }}t|ƒD ]\}}	}
||k r0 ||fS |	dkr9||
7 }q"|	dk rA||
7 }q"||fS )af  Raw (buy_vol, sell_vol) over the trailing window_seconds --
    SymbolBuffer.get_trade_imbalance() only exposes the ratio, but the
    giveback-exit experiment needs the two sides separately (see
    exit_giveback_experimental.py's demand-exhaustion vote). Same
    windowing logic as get_trade_imbalance() itself, just not collapsed
    into a single ratio.)r  r  r   )r   r¯   r4   r  )Ú
_trade_logr   r   Úreversed)rU   r-  ÚlogÚ_tdÚnow_tsÚcutoffÚbuy_volÚsell_volr]   Úsider\   r   r   r   Úget_buy_sell_volumes�  s"   ü
€r7  Fr  r©   r  c%           t         s|  t |pg ƒ}g }%|"rt |"¡nd }&i }'i }(tƒ })|)d }*|)d }+t| |*d ƒ},t| |*d ƒ t¡}-t| |*d ƒ}.|*d }/|d u r]g }|,}0|.}1|0|1kr]| |0 t¡¡ |0t|/d�7 }0|0|1ksJi }2i }3|D ]4}4|4d }5|5 	d	¡|5 	d
¡|5 	d¡|pxi  	|4d ¡dœ|p‚i  	|4d i ¡¥|2|4d < |5 	d¡|3|4d < qc|) 	di ¡ 	dd¡}6|) 	di ¡ 	dd¡}7dd„ |D ƒ}8dd„ |8D ƒ‰ ‡fdd„|8D ƒ}9t
||+|ƒ}:i };i ‰i ‰i ‰i ‰i ‰i ‰i ‰
i ‰i }<i }=i }>i i i }?}@}Ai i }B}Cˆ	d urü‡ ‡	fdd„|8D ƒni }Dt |¡d }E|D �]æ‰ˆ|. t¡k�r �nÚ|8D ]}F|9|F  ˆˆ	d u�r'|D|F nˆ |F ¡ �qˆ	d u�r@tˆ	|8ˆ |@|�p<i |Aˆƒ t|:j ¡ ƒD �]8‰ˆ ˆ }G|G ¡ }H|H�sW�qG|:jˆ }It|I 	d|Id ¡|Hd d ƒ|Id< t|I 	d|Id ¡|Hd d ƒ|Id< |#d u�rÙˆ|Id f}Jtj|H|I|( |Ji ¡|#t | ˆ¡d �}Kˆ|v �r±|% ˆ ¡ ˆd!|Kj|Kj|Kjd"œ¡ |Kj�rÐ|: ˆ|Hd d |Kjˆ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ �qG|# 	d#d$¡�rÙ�qG|�rOt!jˆ|H|:jˆ d ˆ 	ˆi ¡|d%�}L|Lj"ˆˆ< ˆ|v �r
|% ˆ ¡ ˆd&|Lj|Lj|Ljd"œ¡ |Lj�rO|Hd d }M|: ˆ|M|Ljˆ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ
  ˆd ¡ ˆ  ˆd ¡ �qG|d u�r¸|:jˆ }Nˆ|Nd f}Ot#jˆ|H|Nd ˆ|= 	|Oi ¡||: $¡ |> 	ˆd$¡d'�}P|Pj"|=|O< ˆ|v �r“|% ˆ ¡ ˆd(|Pj|Pj|Pjd"œ¡ |Pj�r¸|: ˆ|Hd d |Pjˆ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ |=  |Od ¡ �qG|d u�r|:jˆ }N|Hd d }Mt|< 	ˆ|Nd ¡|Mƒ}Q|Q|<ˆ< |Q|Nd  d) }R|R|k �r|M|Nd* k�r|: ˆ|Md+|Md,›d-|Nd* d,›�ˆ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ |<  ˆd ¡ �qG|G %|7¡}St&jˆ|H|:jˆ d ˆ 	ˆi ¡|S|:jˆ d* d.�}T|Tj'ˆˆ< |Tj (d/¡|>ˆ< ˆ|v �rR|% ˆ ¡ ˆd|Tj|Tj|Tjd"œ¡ d }U|�rŠ|Tj�sŠt)|G|7ƒ\}V}Wt* ˆ|H|V|Wˆ 	ˆi ¡¡}U|Uj"ˆˆ< ˆ|v �rŠ|% ˆ ¡ ˆd0|Uj|Uj|Ujd"œ¡ d }X|�r»|Tj�s»|U�rš|Uj�s»|G +¡ }Yt, ˆ|H|Y¡}Xˆ|v �r»|% ˆ ¡ ˆd1|Xj|Xj|Xjd"œ¡ d }Z|	�rÿ|Tj�sÿ|U�rË|Uj�sÿ|X�rÒ|Xj�sÿt- ˆ|H|:jˆ d |Sˆ 	ˆi ¡¡}Z|Zj"ˆˆ< ˆ|v �rÿ|% ˆ ¡ ˆd2|Zj|Zj|Zjd"œ¡ d }[|
�rM|Tj�sM|U�r|Uj�sM|X�r|Xj�sM|Z�r|Zj�sMt.jˆ|H|:jˆ d |Sˆˆ 	ˆi ¡|d%�}[|[j"ˆˆ< ˆ|v �rM|% ˆ ¡ ˆd3|[j|[j|[jd"œ¡ d }\|�r¥|Tj�s¥|U�r]|Uj�s¥|X�rd|Xj�s¥|Z�rk|Zj�s¥|[�rr|[j�s¥|G +¡ }Yt/jˆ|H|Y|:jˆ d ˆ 	ˆi ¡|d%�}\|\j"ˆˆ< ˆ|v �r¥|% ˆ ¡ ˆd4|\j|\j|\jd"œ¡ d }]|�rÿ|Tj�sÿ|U�rµ|Uj�sÿ|X�r¼|Xj�sÿ|Z�rÃ|Zj�sÿ|[�rÊ|[j�sÿ|\�rÑ|\j�sÿt0jˆ|H|:jˆ d ˆ 	ˆi ¡|d%�}]|]j"ˆˆ< ˆ|v �rÿ|% ˆ ¡ ˆd5|]j|]j|]jd"œ¡ d }^|�r�|Tj�s�|U�r|Uj�s�|X�r|Xj�s�|Z�r|Zj�s�|[�r$|[j�s�|\�r+|\j�s�|]�r2|]j�s�|}_|�rb|:jˆ }N|Nd �rO|Nd |Nd*  |Nd  d) n|�pSi  	d6¡}`i |�p\i ¥d6|`i¥}_t1jˆ|H|:jˆ d ˆ
 	ˆi ¡|_d%�}^|^j"ˆ
ˆ< ˆ|v �r�|% ˆ ¡ ˆd7|^j|^j|^jd"œ¡ ‡‡‡‡‡‡‡
‡‡f	d8d9„}a|Tj�r¶|Hd d }M|: ˆ|M|Tjˆ¡ |aƒ  �qG|Ud u�rÓ|Uj�rÓ|Hd d }M|: ˆ|M|Ujˆ¡ |aƒ  �qG|Xd u�rð|Xj�rð|Hd d }M|: ˆ|M|Xjˆ¡ |aƒ  �qG|Zd u�r|Zj�r|Hd d }M|: ˆ|M|Zjˆ¡ |aƒ  �qG|[d u�r*|[j�r*|Hd d }M|: ˆ|M|[jˆ¡ |aƒ  �qG|\d u�rG|\j�rG|Hd d }M|: ˆ|M|\jˆ¡ |aƒ  �qG|]d u�rd|]j�rd|Hd d }M|: ˆ|M|]jˆ¡ |aƒ  �qG|^d u�r|^j�r|Hd d }M|: ˆ|M|^jˆ¡ |aƒ  �qG|d u�r|D ]z}b|b 	d:¡�s—|bd; ˆk�r™�qˆd$|bd:< ˆ |bd  }G|G ¡ }H|H�r²|: 2|bd ¡�r¸d$|bd<< �qˆ|bd* }c|Ed= �rï|Gj3}dt4 5|Hd d |2|bd   	d>¡|d�rÞ|dd? |dd@  ndA|E¡}1t|c|Hd d |1dB  ƒ}c|: 6|bd |Hd d |cˆ|b 	dC¡¡ �qˆ�q|: $¡ �ríˆ|-k �rí|D �]Ú}4|4d ‰|: 2ˆ¡�r �q|: $¡ �s( �nÅ|d u�r?t7‡fdDdE„| 	ˆg ¡D ƒƒ�s?�qˆ ˆ }G|G ¡ }H|H�sL�q|�ry‡fdFd„|:j8D ƒ}et9 :ˆ|Hd d |e�rh|ed nd |¡\}f}g|f�sy|;  ˆd ¡ �q|Gj3}h|h�rˆ|hd@ |hd? dGœnd }itdHdE„ |HD ƒƒ}ji |2ˆ ¥dI|ji¥}kt;dJdE„ |HD ƒƒ}l|; 	ˆi ¡}m|G %|6¡}S|$�rÚˆ|$v �rÚ|H�rÚ|Hd@ dK  t<ƒ ¡ =dL¡dMk�rÚi |Hd@ ¥dN|$ˆ i¥|Hd@< |!d u�rôt>jˆ|Ht | ˆ¡|G ?dO¡|mˆ|!|idP�}Tntjˆ|H|l|3 	ˆ¡|k|m|i|Sˆ|dQ�
}T|Tj@|;ˆ< |&d u�rK|!d u�rK|& :ˆ|Ht | ˆ¡¡‰‡fdRd„dSD ƒ|TjdT< |TjA�rK|& Bˆ¡�rK|' 	ˆd@¡d? |'ˆ< dU|T_AdVˆdW › �|T_|TjA�rš|�ršˆ	 CˆdXˆd ¡ 	dYi ¡ 	dZ¡}n|C 	ˆ¡�olˆ|Cˆ k }o|o�su|n|v�rš|o�s„| �r„ˆt| d[� |Cˆ< |B 	ˆd@¡d? |Bˆ< dU|T_Ad\|n�p–d]› �|T_ˆ	d u�r¨tDˆ	|?ˆ|Tˆd^� ˆ|v �rÀ|% ˆ ¡ ˆd_|Tj|TjE|TjF|Tjd`œ¡ |TjA�rë|Tj 	dai ¡ 	db¡}c|cd u �rÕ�q|: 6ˆ|Tjdc |cˆ|TjE¡}p|p�rë|;  ˆd ¡ �q�q|. t¡}qt|:j ¡ ƒD ]+‰|9ˆ  |qˆ ˆ ¡ ˆ ˆ  ¡ }H|H�	r|Hd d n|:jˆ d }r|: ˆ|rdd|q¡ �qû|9 G¡ D ]}s|s H¡  �	q+|%|:_I|B|:_J|'|:_K|:S )eNr   ÚtradingÚmarket_open_timeÚno_new_entries_afterÚforce_liquidate_timeÚpoll_interval_secondsr4   ÚmetricsÚpremarket_highÚprevious_day_highÚrange_20d_highrT   )r>  Úprev_day_highr@  rG   Úavg_daily_volumeÚsmart_engineÚimbalance_window_secondsrº   r(  c                 S   ó   g | ]}|d  ‘qS ©rT   r   ©r   rj   r   r   r   r=   ê  ó    z"run_simulation.<locals>.<listcomp>c                 S   s   i | ]}|t d d�“qS )i†  )Úmaxlenr
   rå   r   r   r   r¥   ë  ó    z"run_simulation.<locals>.<dictcomp>c                    s    i | ]}|t ˆ |› d � ƒ“qS rã   )r÷   rå   )r.   r   r   r¥   ì  s     c                    s   i | ]}|t |ˆ | ˆƒ“qS r   )rS   )r   rN   )rs   rV   r   r   r¥   ÿ  ó    Úmin_stopr  r	  r¯   rj   r  r˜   )ÚrefÚexit_v2)r�   rT   rÙ   Ústater  r=  Ú	replace_jT)r  Úgiveback_room_exit)r  Ú
slots_freeÚhealthyÚresistance_stallr  r–   zscanner stop: price $ú.4fz
 <= stop $)Útrade_imbalancer–   rS  Úgiveback_exitÚfast_slope_exitÚprf_exitÚresistance_timer_exitÚvelocity_exitÚcents_grace_exitÚinitial_distance_pctÚstep_lock_exitc                      sd   ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ ˆ   ˆd ¡ ˆ  ˆd ¡ ˆ  ˆd ¡ d S r   )Úpopr   )	Úcents_grace_stateÚexit_confirmationÚgiveback_room_stateÚgiveback_stateÚ	prf_stateÚresistance_timer_stateÚstep_lock_staterT   Úvelocity_stater   r   Ú_pop_all_exit_stateÖ  s   z+run_simulation.<locals>._pop_all_exit_stateÚ_doner™   Ú_skippedÚenabledrG   r3   r   r  Úfloorr—   c                 3   s,   � | ]\}}|ˆ   ko|k n  V  qd S r   r   ©r   rÆ   r:   )rw   r   r   r     s   €* z!run_simulation.<locals>.<genexpr>c                    s   g | ]
}|d  ˆ kr|‘qS rF  r   )r   ÚtrrF  r   r   r=   &  r>   )r`   ra   c                 s   ó   � | ]}|d  V  qdS )r&   Nr   r9   r   r   r   r   -  r    Úsession_highc                 s   ro  )rk   Nr   r9   r   r   r   r   /  r    r8   z%H:%Mz09:30rh   r£   )Úas_ofr  rÅ   )rÅ   rV  rq  r  c                    s   i | ]}|ˆ   |¡“qS r   )ro   )r   r¦   )Úpvr   r   r¥   B  rJ  )ÚactionÚgroupÚ
group_namer  ÚplaybookFÚ	PLAYBOOK_rs  ÚGATErV   Údecisionrl   ÚGATE_ÚNO_DATA)ÚnowÚentry)r�   rT   rÙ   rO  Úreasons_forÚreasons_againstr=  Ústage3Ústopr[   Ú
END_OF_DAY)Lr±   rv  ÚPlaybookr   r,   rC   r   r´   r   ro   r  rC  Ú
_merge_cfgrÿ   ry   Úlistr  Úkeysrn   r%  r»   rN  ÚevaluateÚ
setdefaultÚ	referencer‘   rÏ   rO  r  r=  Úshould_exitr(  r_  Úexit_giveback_roomÚ	state_outÚexit_resistance_stallr  Úget_trade_imbalanceÚexit_engineÚconfirmationÚ
startswithr7  rW  Úget_bars_subrX  rY  rZ  r[  r\  r^  r  Úlatest_quoterF   Ú
stop_floorr  Úanyr•   ÚreentryÚcheckÚsumr   ÚstrftimeÚentry_v2Úget_flowÚpersistenceÚshould_enterÚblocksÚ_build_snapshotr   r~  r  Úvaluesr†   ÚtraceÚgate_blockedÚpbook_blocked)tr   r  r©   r.   r  Ú
poll_timesÚtrace_symbolsÚuse_giveback_exitÚuse_fast_slope_exitÚuse_prf_exitÚuse_resistance_timer_exitÚresistance_timer_cfgÚuse_velocity_exitÚvelocity_cfgÚuse_cents_grace_exitÚcents_grace_cfgÚuse_step_lock_exitÚstep_lock_cfgÚstep_lock_use_atr_distanceÚuse_giveback_room_exitÚgiveback_room_cfgÚsmart_engine_cfgÚscanner_stop_centsÚresistance_stall_cfgrG   Úref5dÚreentry_cfgÚfixed_entriesÚwatchrV   ru   Úshadow_gateÚshadow_gate_block_minutesÚentry_v2_cfgÚplaybook_cfgÚexit_v2_cfgÚofficial_openr¡  Úpbookr£  Úexit_v2_stater  Úschedr  Úmarket_openÚno_new_entriesÚforce_liquidateÚpoll_intervalr8   ÚflÚresistance_levelsÚavg_vol_baselinerj   r'   Úentry_imbalance_windowÚexit_imbalance_windowr-   ÚreadersÚpmÚentry_persistenceÚscanner_stop_peakÚresistance_stall_stateÚlast_exit_healthyÚshadow_keysÚshadow_last_barrv   r¢  Ú	gate_lockÚshadow_tapsÚfixed_min_stopr(   rU   rL   Ú_posÚxkeyÚxdÚgr_decisionÚcurrent_priceÚposr    Úrs_decisionr'  Úpeak_gain_centsrV  ry  Úgb_decisionr4  r5  Úfs_decisionÚsub_barsÚprf_decisionÚrt_decisionÚvel_decisionÚcg_decisionÚsl_decisionÚcall_cfgÚatr_stop_pctrh  Úfer�  rÇ   ÚpriorÚallowedÚ_whyÚquote_tuplerÅ   rp  Ú
resistanceÚcumulative_volumeÚpersistence_stateÚverdictÚlockedÚenteredÚfl_utcr[   rø   r   )rs   r.   r`  ra  rb  rc  rd  rr  re  rV   rf  rT   rw   rg  r   Úrun_simulationµ  s–  þüû
 
*

""

ÿ
ÿþ

þ

ý

þ


þ
ý

þÿ

þ
þÿÿ
þ

þÿÿþþýýþ

þÿÿþþýýüüþ

þÿÿþþýýüüûûþ

þÿÿþþýýüüûûúú
 ÿÿþ

þ







€

ÿ(

**
6

þý


ý
€
 rö  r•   c                 C   sN   t dd„ | D ƒƒ}t dd„ | D ƒƒ}t dd„ | D ƒƒ}t| ƒ||t|dƒdœS )Nc                 s   ro  )r   Nr   r›   r   r   r   r   w  r    zsummarize.<locals>.<genexpr>c                 s   s    � | ]}|d  dkrdV  qdS ©r   r   r3   Nr   r›   r   r   r   r   x  ó   € c                 s   s    � | ]}|d  dkrdV  qdS r÷  r   r›   r   r   r   r   y  rø  r  )r•   ÚwinsÚlossesÚtotal_pl)r˜  rr   r&  )r•   rû  rù  rú  r   r   r   Ú	summarizev  s   rü  Úlabelc                 C   sì   t d|› dt| ƒ› d�ƒ | D ]F}t d|d dd… › d|d	 d
›d|d d›d|d d›d|d dd… › d|d d›d|d d›d|d d›d|d d d… › d�ƒ qt| ƒ}t d|d › d |d! › d"|d# › d$|d% d›d&�	ƒ d S )'Nz
=== z (z trades) ===z  r˜   é   é   Ú rT   Ú6sz BUY r  z>4dz @ r	  rU  z  -> r  z SELL @ r  z  P/L r  z+.2fz% ($r   z)  [r  é<   ú]z  --- total: r•   z	 trades, rù  zW/rú  zL, $rû  z ---)rì   rr   rü  )r•   rý  r8   r(   r   r   r   Úprint_trades}  s   :ÿÿþþþ8r  Ú
sim_tradesc                 C   s¶   t d d |› d� }| ¡ std|› d�ƒ d S dd„ t|ƒD ƒ}t|dƒ d	d„ |D ƒ}d
d„ | D ƒ}td|› �ƒ td|› �ƒ t|ƒ}t| ƒ}td|› �ƒ td|› �ƒ d S )Nr¨   r•   z_trades.jsonlz!
[sanity] no real trades file at z
, skippingc                 S   s   g | ]}t  |¡‘qS r   )r@   rA   )r   r¶   r   r   r   r=   Œ  s    z sanity_check.<locals>.<listcomp>zREAL (actual live run today)c                 S   rE  rF  r   r›   r   r   r   r=   �  rH  c                 S   rE  rF  r   r›   r   r   r   r=   �  rH  z
[sanity] real sequence:      z[sanity] simulated sequence: z[sanity] real:      z[sanity] simulated: )r�   r?   rì   rƒ   r  rü  )r  r   Ú	real_pathÚreal_tradesÚ	real_symsÚsim_symsÚreal_sÚsim_sr   r   r   Úsanity_check‡  s   
r  c                  C   sÞ  t  ¡ } | jdddd� | jdtdd� | jddd	d
� | jdtddd� | jdddd
� | jdddd� | jdddd
� | jdddd
� | jdddd
� | jdddd
� | jdtddd� | jd td!d"d� | jd#dd$d
� | jd%td&d'd� | jd(td)d*d� | jd+dd,d
� | jd-td.d/d� | jd0td1d2d� | jd3td4d5d� | jd6dd7d
� | jd8td9d:d� | jd;td<d=d� | jd>td d?d� | jd@td!dAd� | jdBddCd
� | jdDddEd
� | jdFtdGdHd� | jdItd!dJd� | jdKddLd
� | jdMtdNdOd� | jdPtd dQd� | jdRtd dSd� | jdTddUd
� | jdVddWd
� | jdXtd dYd� | jdZtd d[d� | jd\td d]d� | jd^d_d`gd dadb� | jdctddded� | jdfg dg¢d dhdb� | jdid djd� | jdkddld
� | jdmddnd
� | jdoddpd
� | jdqdrg dsdt� | jduddvd
� | jdwdrg dxdt� | jdydrg dzdt� | jd{d|d}gd d~db� | jdtd d€d� | jd�d‚dƒgd d„db� | jd…td†d‡d� | jdˆd d‰d� | jdŠd d‹d� | jdŒdd�d
� | jdŽdd�d
� | jd�dd‘d
� | jd’d d“d� | jd”d•d–gd d—db� | jd˜td d™d� | jdšdd›d
� | jdœdd�d
� | jdžddŸd
� | jd td d¡d� | jd¢d£d¤gd£d¥db� | jd¦dd§d
� |  ¡ }|j�rS|j�sS|  d¨¡ d©dª„ |j	 
d«¡D ƒ}d }|j�r�t t|jƒ ¡ ¡}d¬dª„ | ¡ D ƒ}d­d®„ | ¡ D ƒ}n|j�r�t t|jƒ ¡ ¡}nt|jƒ}d¯dª„ |D ƒ}td°t|ƒ› d±|j› d²|› �tjd³� tƒ }tƒ d´ }t|j|dµ ƒ t¡}	t|j|d¶ ƒ t¡}
td· d¸ d¹ |j }tdº|› d»�tjd³� t|||	|
||jd¼� |  ¡ }t|j!ƒ}td½|d¾›�tjd³� |j"�rd nt#|jƒ}|�r"td¿t|ƒ› dÀ|j› dÁ�tjd³� ntdÂtjd³� |j$�r:dtƒ  %dÃi ¡ %dÄi ¡dÅ< |j&�rGdtƒ  %dÃi ¡dÆ< |j'�rTdtƒ  %dÃi ¡dÇ< |j(�rb|j(tƒ  %dÃi ¡dÈ< |j)d u�rr|j)tƒ  %dÃi ¡dÉ< |j*�r€|j*tƒ  %dÊi ¡dË< |j+�r‘dÌtƒ  %dÃi ¡ %dÍi ¡dÅ< |j,�r¢dtƒ  %dÃi ¡ %dÍi ¡dÅ< i }|j-d u�r¯|j-|dÎ< |j.d u�rÅ|j.d†k|j.�p¾dÏ|j/dÐœ|dÑ< |j0�rÍd|dÒ< |j1�rúi tƒ  2dÓi ¡ 2dÔi ¡¥dÅdi¥|j3�rêdÕ|j3ini ¥|j4�rõdÖ|j4ini ¥|dÔ< |j5�ri tƒ  2dÓi ¡ 2d×i ¡¥d|j5|j6dØœ¥|d×< |�r#i tƒ  2dÓi ¡¥|¥}t7|j|j8|||fi dÙ|“dÚ|“dÛ|j9“dÜ|j:“dÝ|j;“dÞ|j<“dß|j=|j>dàœ“dá|j?“dâ|j@|jAdãœ“dä|jB“då|jC|jD|jEdæœ“dç|jF“dè|jG|jH|jI|jJ|jKdéœ“dê|jL“dë|jM“dìi tƒ  2díi ¡¥|jN|jO|jP|jQ dîœ¥“dï|�pšd “dð|jR“dñ|jS�rÔi tTjU¥tƒ  2dòi ¡¥dÅdi¥|jV�r¼dó|jVini ¥|jW�rÆdôdini ¥|jX�rÑdõ|jXini ¥nd “dötY|||j|ƒ“d÷tZ|||j|ƒ“døt[|j\ƒ“dù|“dú|j�rþt]|jdÌdûœƒnd “dü|j�rt^||jƒnd “dý|j_“dþ|j`�r*i tƒ  2dÿ¡�pi ¥�d d®„ |jaD ƒ¥nd “�d|j`�r?tb||j�ddª„ |D ƒƒnd “�d|jc�r`i tƒ  2�d¡�pQi ¥dÅdi¥�dd®„ |jdD ƒ¥nd “�d|je�r„i tƒ  2�d¡�pri ¥d�d�d	œ¥�d
d®„ |jfD ƒ¥nd “�d|j�r›�dh�d�dh�dœ|j nd “�d|jg�r¶i tƒ �d  2�di ¡¥�d|jgi¥n	tƒ �d  2�d¡“Ž}�d|j8› �|jM�rÞ�d|jN�d›�d|jO�d›�d�nd |j-d u�rï�d|j-�d›�nd |jS�r�d|jV�ptƒ  2dòi ¡ 2dó�d¡�d›�d�nd |j0�r�dnd |j,�r�dnd |j5�r7|j5d_k�r4�d |j6› �d�n�d!nd |j.d u�rM�d"|j.› �d#|j/› �d�nd |jRd u�r`�d$|jR�d›�d%�nd |j9�ri�d&nd |j:�rr�d'nd |j;�r{�d(nd |j<�r’�d)|j=�d›�d*|j>d¾›�d�nd |j?�r©�d+|j@�d›�d,|jAd¾›�d�nd |jB�rÄ�d-|jCd¾›�d.|jDd¾›�d/|jE› �d�nd |jF�r�d0|jL�rÓ�d1n|jG�d›�d2�› �d3|jH�d›�d2�|jId u�r÷�d4|jI�d›�d2�nd |jJ�r�d5|jJ�d›�d2�nd |jK�r�d6nd �d nd }th|ji|ƒ |jj�r+tk|ji|jƒ td· d¸ }|jldd�d7� |j9�r@�d8nd|j:�rH�d9nd |j;�rQ�d:nd |j<�rZ�d;nd |j?�rc�d<nd |jB�rl�d=nd |jF�ru�d>nd |jM�r~�d?nd |jM�r›tm|jOdN ƒ�d@k�r›�dA|jO�dB �d›�nd |j-d u�r¬�dC|j-�d›�nd |j.d u�rÇ�dD|j.› |j/d£k�rÂ�dEn�dF› �nd |jRd u�rØ�dG|jR�d›�nd |jS�rô�dHt|jV�pðtƒ  2dòi ¡ 2dó�d¡ƒ› �nd |j0�rý�dInd |j,�r�dJnd |j)d u�r�dKt|j)�dL ƒ› �nd |j(�r'�dM|j(› �nd |jg�r4�dN|jg› �nd |j\�r=�dOnd |j&�rM�d;tnt|j&ƒƒ nd |j'�rV�dPnd |j�r_�dQnd |j1�rh�dRnd |j`�rq�dSnd |je�rz�dTnd |jc�rƒ�dUnd d o�dV�dW„ |jdD ƒ¡ d o�dX�dW„ |jfD ƒ¡ d o�dY�dW„ |jaD ƒ¡ |j3�r½�dZ|j3› |j4�pºd› �nd |jW�rÆ�d[nd |j$�rÏ�d\nd |jX�rÞ�d]t|jXƒ› �nd |j*�rë�d^|j*› �nd |j5�	r|j5d_k�	r�d_t|j6�dB ƒ› �n�d`nd |j�	r�da|j› �nd |j_�	r"�dbt|j_ƒ› �nd |j�	r+�dcnd }||j› �dd|j8› |› �de� }tp|�dfƒ�%}tjq|j|j8||jitr|jiƒts|�dgi ƒ�dhœ|�di�dj� W d   ƒ n	1 �	smw   Y  |j�	rƒt�dkts|�dgi ƒ› �ƒ ts|�dld ƒ�	r”t�dm|jt› �ƒ t�dn|› �tjd³� |�	rí||j› �do|j8› �dp� }tp|�dfƒ�}|juD ]}| vt w|¡�dq ¡ �	qºW d   ƒ n	1 �	sÔw   Y  t�drt|juƒ› �ds|› �tjd³� d S d S (t  Nz--dateTz
YYYY-MM-DD)ÚrequiredÚhelpz--max-positions)Útyper  z--sanityÚ
store_truez,diff against data/trades/<date>_trades.jsonl)rs  r  z	--workersrÛ   zparallel fetch workers)r  Údefaultr  z--synthetic-gridz„use an exact-5s poll grid instead of the real logged poll times (default prefers real times when data/decisions/<date>.jsonl exists)z--trace-symbolsÚ z`comma-separated symbols to dump every entry/exit decision's full metrics dict for, e.g. BNC,GLOO)r  r  z--giveback-exitzxalso check the experimental giveback-from-peak 4th exit layer (exit_giveback_experimental.py) alongside the real exit.pyz--fast-slope-exitzalso check the experimental fast sub-minute price-slope exit layer (exit_fast_slope_experimental.py) alongside the real exit.pyz
--prf-exitzsalso check the experimental Price Response Failure exit layer (exit_prf_experimental.py) alongside the real exit.pyz--resistance-timer-exitzŒalso check the experimental resistance + fixed recovery-window exit layer (exit_resistance_timer_experimental.py) alongside the real exit.pyz--resistance-timer-windowg      N@zIrecovery_window_seconds override for --resistance-timer-exit (default 60)z--resistance-timer-imbalancer  zžresistance_imbalance_threshold override for --resistance-timer-exit (default 0.0, i.e. any positive imbalance counts -- PRF's own 'strong buying' bar is 0.70)z--velocity-exitzˆalso check the experimental decline-velocity + participation-ratio exit layer (exit_velocity_experimental.py) alongside the real exit.pyz--velocity-min-pct-per-ming      @zKmin_decline_velocity_pct_per_min override for --velocity-exit (default 6.0)z--velocity-max-participationr°   zJmax_participation_ratio_to_exit override for --velocity-exit (default 1.0)z--cents-grace-exitz³also check the experimental flat-cents trailing stop + health-gated grace exit (exit_cents_grace_experimental.py, ported from the sibling premarket bot) alongside the real exit.pyz--cents-grace-basegš™™™™™¹?zBbase_giveback_cents override for --cents-grace-exit (default 0.10)z--cents-grace-amountgš™™™™™©?z:grace_cents override for --cents-grace-exit (default 0.05)z--cents-grace-max-extensionsr3   z@max_grace_extensions override for --cents-grace-exit (default 1)z--step-lock-exitz�also check the experimental cent-for-cent-to-breakeven then %%-step-lock exit layer (exit_step_lock_experimental.py) alongside the real exit.pyz--step-lock-distanceg      ø?z@initial_distance_pct override for --step-lock-exit (default 1.5)z--step-lock-stepg      @z4step_pct override for --step-lock-exit (default 5.0)z--step-lock-capzGgiveback_cap_pct override for --step-lock-exit (default None, i.e. off)z--step-lock-tolerancezŒbreakeven_tolerance_pct override for --step-lock-exit (default 0.0) -- the breakeven floor sits this %% below entry instead of exactly at itz--step-lock-use-atr-distancea  for --step-lock-exit: use each trade's OWN entry-time ATR-based stop distance as initial_distance_pct instead of --step-lock-distance's fixed value -- ignores --step-lock-distance when set. Combine with --step-lock-step 0 for a pure breakeven-guarantee stop (no phase-2 lock).z--giveback-room-exitz§also check the new production giveback-room exit layer (exit_giveback_room.py) -- checked FIRST, ahead of exit.py, same priority order as monitor.py's real live wiringz--giveback-room-min-centsg      .@zDmin_peak_gain_cents override for --giveback-room-exit (default 15.0)z--breakeven-lockzNgiveback-room: once peak gain >= this many cents, exit at entry + 1c (0 = off)z--giveback-no-slopezKgiveback-room: fire the giveback floor without waiting for a negative slopez--giveback-room-ratiogUUUUUUå?zAgiveback_ratio override for --giveback-room-exit (default 0.6667)z--scanner-stop-centszŒmirror monitor.py's scanner_stop_phase: until the peak since entry has run up this many cents, the entry stop is the only exit (default off)z--min-entry-scorezÔsmart_engine.min_entry_score override -- reject Stage-3-passed entries below this live entry_score (0-100, see smart_engine._compute_entry_score). Default None leaves config.json's value (0.0, i.e. off) in place.z--resistance-stall-exitz�enable exit_resistance_stall (sell at the ceiling after ~60 min of failed tests with no breakout); settings from config.json's resistance_stallz--stall-low-ceilingz„resistance_stall.low_ceiling_enabled: once armed, sell at the last-15-min ceiling when no slot is free or the position isn't healthyz--dead-money-minutesz>resistance_stall.dead_money_minutes (with --stall-low-ceiling)z--exit-trend-barszIexit.deterioration_trend_bars: 'trend negative' sign over the last N barsz--stall-minuteszCoverride resistance_stall.stall_minutes for --resistance-stall-exitz--min-stop-styler¦   ÚtierszFenable smart_engine.min_stop (volatility-scaled minimum stop distance))Úchoicesr  r  z--min-stop-kg333333Ã?z-for --min-stop-style k: floor = k x daily ATRz--reentry-after-loss)ÚoffÚblockÚabove_entryzQtrading.reentry_after_loss.mode override (entry selectivity after a losing trade)z--fixed-entriesz�exit-isolation mode: path to a previous run's JSON; enter exactly its trades (same symbol/time/stop), nothing else, no slot limitz--max-ext-openzWenable smart_engine.max_extension_from_open (3-5%% above today's open by volume/buying)z
--entry-v2zV[2026-09-28] entries by entry_v2.py (config entry_v2 settings) instead of smart_enginez	--exit-v2zL[2026-09-29] exit_v2 (smooth + breakeven stop trail; config exit_v2 section)z	--xv2-setr´   zIwith --exit-v2: override one exit_v2 setting, key=json_value (repeatable))rs  r  r  z
--playbookz\[2026-09-29] with --entry-v2: the situation playbook controls buys (config playbook section)z--pb-setzKwith --playbook: override one playbook setting, key=json_value (repeatable)z--v2-setzX[2026-09-29] with --entry-v2: override one entry_v2 setting, key=json_value (repeatable)z--ext-anchorrƒ   ÚvwapzP[2026-09-28] with --max-ext-open: measure the stretch from the open or from VWAPz--ext-vwap-basezZwith --ext-anchor vwap: allowed %% above VWAP before the volume/buying steps (default 2.0)z--shadow-gateÚpassÚpass_incompletez�[2026-09-26] only buy when entry_rules says PASS (or PASS/INCOMPLETE) at the moment the bot wants to enter; requires --shadow-logz--shadow-gate-block-minutesr   zQwith --shadow-gate: after a refusal, the symbol can't enter for this many minutesz--candidates-filez†[2026-09-26] load the candidate list from this JSON file instead of data/candidates/<date>_scanner.json (e.g. reconstructed past days)z--shadow-logzWrun the entry_rules.py shadow recorder during the replay, writing snapshots to this dirz--plan-5d-levelsz]setup_plan.five_day_levels: 5-day high as a level + realistic target cap (open + 1 daily ATR)z--plan-real-targetszHsetup_plan.real_level_targets: never target high_of_day / young ceilingsz--plan-reclaimz@setup_plan.reclaim_breakouts: count reclaim breakouts of a levelz--watch-windowsz¯JSON {symbol: {windows: [[start_iso, end_iso], ...], metrics: {...}}} -- replays intraday rescans: candidates = these symbols, each only evaluated for entry inside its windowsz--plan-breakout-stopÚpivotÚlevelz.override setup_plan.breakout_stop for this runz--plan-min-rrz0override setup_plan.min_reward_risk for this runz--plan-no-contextzGforce setup_plan.context_rules OFF for this run (overrides config.json)z--plan-contextzœenable setup_plan.context_rules (breakout needs rising slope; pullback needs reclaim, fading selling, support held; lower-highs reject waived for pullbacks)z--setup-planz�use setup_analyzer's per-tick trade plan in place of Stage 3 + entry_score (its stop replaces 3x ATR); settings from config.json's setup_planz--entry-slope-barsz‰enable smart_engine's entry_slope_rule: the last N 1-min bars must slope up on every read of the hold (default off / config.json's value)z--entry-slope-modeÚpositiveÚ
above_zerozafor --entry-slope-bars: 'positive' = above the ATR-scaled flat band, 'above_zero' = any slope > 0z--step-lock-handoffa   for --step-lock-exit: only protect the opening leg (entry -> breakeven) -- once the peak clears initial_distance_pct, stop enforcing any stop here at all and let the real exit.py hard-stop + deterioration layers run the rest of the trade, instead of holding a flat breakeven floor foreverz --shadow-gate needs --shadow-logc                 S   s   g | ]
}|  ¡ r|  ¡ ‘qS r   )Ústriprå   r   r   r   r=   A  r>   zmain.<locals>.<listcomp>ú,c                 S   s   g | ]\}}||d  dœ‘qS )r=  )rT   r=  r   ©r   rN   rk   r   r   r   r=   F  rK  c                 S   s$   i | ]\}}|d d„ |d D ƒ“qS )c                 S   s$   g | ]\}}t  |¡t  |¡f‘qS r   )r   rš   rm  r   r   r   r=   G  rÄ   z#main.<locals>.<dictcomp>.<listcomp>Úwindowsr   r!  r   r   r   r¥   G  s    ÿzmain.<locals>.<dictcomp>c                 S   rE  rF  r   rG  r   r   r   r=   M  rH  zLoaded z candidates for rè   rà   r   r9  Úmarket_close_timer¨   ÚsimulationsÚcachezEnsuring tick-data cache in z ...)rÜ   zUsing fixed equity snapshot: $z.2fzDriving replay off z- REAL logged poll timestamps (data/decisions/z.jsonl)zCNo real decisions log found -- using a synthetic exact-5s poll gridÚ
setup_planÚfive_day_levelsrk  Úreal_level_targetsÚreclaim_breakoutsÚbreakout_stopÚmin_reward_riskr(  Údeterioration_trend_barsFÚcontext_rulesÚmin_entry_scoreé   )rk  Úlookback_barsÚmodeÚentry_slope_ruleÚuse_setup_planrC  Úmax_extension_from_openÚanchorÚvwap_base_pctrL  )rk  Ústyler¦   r¤  r¥  r¦  r§  r¨  r©  rª  )Úrecovery_window_secondsÚresistance_imbalance_thresholdr«  r¬  )Ú min_decline_velocity_pct_per_minÚmax_participation_ratio_to_exitr­  r®  )Úbase_giveback_centsÚgrace_centsÚmax_grace_extensionsr¯  r°  )r]  Ústep_pctÚgiveback_cap_pctÚbreakeven_tolerance_pctÚhandoff_at_breakevenr±  r²  r³  Úgiveback_room)Úmin_peak_gain_centsÚgiveback_ratioÚbreakeven_lock_centsÚrequire_sloper´  rµ  r¶  rT  Ústall_minutesÚlow_ceiling_enabledÚdead_money_minutesrG   r·  r¹  rº  rV   )Úlog_dirÚtrack_outcomesru   r¼  r½  rš  c              	   S   ó0   i | ]}|  d d¡d t |  d d¡d ¡“qS ©Ú=r3   r   ©r%   r@   rA   ©r   Úkvr   r   r   r¥   ¼  ó   0 rÀ  c                 S   rE  rF  r   rG  r   r   r   r=   ¾  rH  r¿  rN  c              	   S   rM  rN  rP  rQ  r   r   r   r¥   Á  rS  r¾  rv  Úcontrol)rk  r1  c              	   S   rM  rN  rP  rQ  r   r   r   r¥   Ä  rS  r»  ÚPASSÚ
INCOMPLETE)r  r  r¸  r8  Úreentry_after_lossr1  zmax_positions=z +giveback-room-exit(min=rê   zc,ratio=z.3fú)z +min-entry-score=z +resistance-stall(r  z.0fzm)z +setup-planz +plan-contextz +min-stop(k=z +min-stop(tiers)z +entry-slope(z bars,z +scanner-stop-phase(zc)z +giveback-exitz +fast-slope-exitz
 +prf-exitz +resistance-timer-exit(zs,imb>=z +velocity-exit(vel>=z%/min,part<z +cents-grace-exit($z+$r   z +step-lock-exit(D=z	ATR@entryú%z,step=z,cap=z,tol=z,handoffrÝ   Ú	_givebackÚ
_fastslopeÚ_prfÚ_rtÚ_velÚ_cgÚ_slÚ_grgü©ñÒMbP?Ú_rr  Ú_mesÚ_esr“   ÚzÚ_ssÚ_rsÚ_planÚ_ctxÚ_rré
   Ú_bsÚ_reÚ_fixedÚ_rcÚ_wwÚ_extÚ_v2Ú_pbÚ_xv2c                 s   ó0   � | ]}d |  dd¡  d d¡dd…  V  qdS ©Ú_rO  r  Né   ©r®   rQ  r   r   r   r     ó   €. zmain.<locals>.<genexpr>c                 s   ru  rv  ry  rQ  r   r   r   r   	  rz  c                 s   ru  rv  ry  rQ  r   r   r   r   
  rz  Ú_ancÚ_lcÚ_5dÚ_dmÚ_etÚ_mskÚ_mstÚ_gateÚ_gbÚ_cfÚ_sim_maxposz.jsonrÁ   r¢  )Údater  Úequity_usedr•   Úsummaryr¢  r  )Úindentz$shadow gate blocked entry attempts: r£  zplaybook blocked v2 buys: z
Wrote Ú_trace_maxposr­   rÉ   zWrote z trace records to )xÚargparseÚArgumentParserÚadd_argumentr   r‚   Ú
parse_argsr»  Ú
shadow_logÚerrorr¥  r%   Úwatch_windowsr@   rA   r   rB   rE   Úcandidates_filer«   r†  rì   rr   rí   rî   r	   r   r,   rC   r   r�   rö   rÜ   Úget_accountr  Úsynthetic_gridr¹   Úplan_5d_levelsrˆ  Úplan_real_targetsÚplan_reclaimÚplan_breakout_stopÚplan_min_rrÚexit_trend_barsÚplan_no_contextÚplan_contextr.  Úentry_slope_barsÚentry_slope_moder&  Úmax_ext_openro   Ú
ext_anchorÚext_vwap_baseÚmin_stop_styleÚ
min_stop_krö  r  rW  rX  rY  rZ  Úresistance_timer_windowÚresistance_timer_imbalancer[  Úvelocity_min_pct_per_minÚvelocity_max_participationr\  Úcents_grace_baseÚcents_grace_amountÚcents_grace_max_extensionsr^  Ústep_lock_distanceÚstep_lock_stepÚstep_lock_capÚstep_lock_toleranceÚstep_lock_handoffr±  rQ  Úgiveback_room_min_centsÚgiveback_room_ratioÚbreakeven_lockÚgiveback_no_sloperµ  Úresistance_stall_exitr�  ÚDEFAULT_CONFIGrH  Ústall_low_ceilingrJ  rR   r§   r¢   r¹  r   rŠ   r¼  rš  Úv2_setr”   rN  Úxv2_setrv  Úpb_setrW  r  r•   Úsanityr  rë   Úabsr+  Újoinrƒ   Údumprü  Úgetattrr£  r¡  rÎ   rI   )ÚapÚargsr¥  rº  Úwjr©   r-   rJ   rÃ  r~   r   r.   Úaccountr  r¤  Úse_overridesrÎ  rý  Úout_dirÚsuffixÚout_pathrª   Ú
trace_pathÚrecr   r   r   Úmain™  sn  ÿÿÿÿÿÿÿ
ÿ
ÿÿ
ÿ
ÿÿ
ÿ
ÿ
ÿÿ
ÿ
ÿ
ÿ
ÿÿÿ
ÿ
ÿÿ
ÿ
ÿ
ÿÿÿ
ÿ
ÿ
ÿÿ
ÿÿÿÿÿÿ
ÿÿ
ÿ
ÿÿ
ÿÿ
ÿÿÿÿÿÿÿÿ
ÿÿÿÿ
ÿÿÿ
ÿ
&

ÿÿ


þÿþý
ÿÿÿþýüûÿúø	ÿ÷õþôñüðëêüé€äã$ûÿþýü€Ü%Û&Ú
'Ù'Ù)ÿ€×*€Ö+Õ.þÿ€Ò0ÿ€Ð3"þÿ€Í6(þÿ€Ê8"ÿ€È:ÿÿ€Å=ÿ
ÿþ ý4ÿûúù	(ÿ÷ÿõ$ôóòñÿ
ÿïÿ
ÿí ÿ
ÿë,ûÿ$þ ýüûå"ÿÿþþýýÿû ú&ÿø 	÷0ÿõôó&òñðïîíìëêéèçæå$äãâá à".ÿÞ#Ý$Ü%Û &ý
ýÿ
ÿÿ(ûrÉ  Ú__main__)rº   )rÛ   )r,  ) NNFFFFNFNFNFNFFNNNNNNNNNNNNr   NNNN)Tre   r‹  rÐ   r@   rí   rï   Úconcurrent.futuresr   r   r   r   Úpathlibr   Úzoneinfor   Úconfig_loaderr   Úalpaca_clientr	   Ústreamr   rC  r(  r�  r‹  r�  rF   r–  rš  rv  rN  r‰  Úentry_rulesr   r   rq   r   Úexit_giveback_experimentalrW  Úexit_fast_slope_experimentalrX  Úexit_prf_experimentalrY  Ú"exit_resistance_timer_experimentalrZ  Úexit_velocity_experimentalr[  Úexit_cents_grace_experimentalr\  Úexit_step_lock_experimentalr^  Ú__file__ÚresolveÚparentr�   r   r   r+  r,   r…  r)  rR   rS   ry   rŠ   r”   r¢   r§   r«   r¹   r   r¿   rÚ   rö   r÷   r  r‚   r7  rö  rü  r  r  rÉ  rb   r   r   r   r   Ú<module>   sª    4
	$	ÿ
ÿ&Aòÿ
ò   D
   

ÿ