o
    #¹j°%  ã                   @   sj   d Z ddlZddlmZmZ ddlmZ ddlmZ ddlm	Z	m
Z
mZ ddlZedƒZG dd	„ d	ƒZdS )
ay  
position_manager.py

Owns local position state for this project: sizing a new position off
the stop the entry rule module supplied (1% account risk, capped by
max_position_notional_pct_of_equity), submitting entry/exit orders
through alpaca_client, and persisting to data_store so a mid-day restart
doesn't lose track of what's actually open.

Core file: contains NO entry or exit strategy -- those live in the rule
modules (breakout_rules.py, reversal_rules.py, exit_rules.py). Reuses
the same wash-trade / position-not-found error handling already coded
into alpaca_client.py, since those are real broker-timing races
already proven to happen against this same paper-account family, not
something worth reinventing.

Known simplification: a filled position is recorded locally at the
entry decision price (the market price at the instant BUY
fired), not the order's actual average fill price -- a market order
can fill a few cents away. Same simplification alpaca_client.py's own
docstrings note sip_bot's position_manager.py makes at entry time.
Acceptable for now; revisit if fill slippage turns out to matter once
there's real trade data.
é    N)ÚdatetimeÚtimezone)Ú
get_config)Ú
get_logger)Ú
get_clientÚparse_wash_trade_errorÚparse_position_not_found_errorÚposition_managerc                   @   sì   e Zd Zdd„ Zdefdd„Zdefdd„Zdedefd	d
„Z	de
fdd„Zde
fdd„Zdedededefdd„Z	d&dededede
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ed!edefd"d#„Zd$d%„ ZdS )'ÚPositionManagerc                 C   s"   t ƒ | _tƒ d | _t ¡ | _d S )NÚtrading)r   Úclientr   ÚcfgÚ
data_storeÚload_positionsÚ	positions©Úself© r   ú,/var/www/screener/trade1/position_manager.pyÚ__init__&   s   zPositionManager.__init__Úreturnc                 C   s
   t | jƒS ©N)Úlenr   r   r   r   r   Úopen_positions_count,   ó   
z$PositionManager.open_positions_countc                 C   s   |   ¡ | jd k S )NÚmax_positions)r   r   r   r   r   r   Úhas_available_slot/   s   z"PositionManager.has_available_slotÚsymbolc                 C   s
   || j v S r   )r   )r   r   r   r   r   Úis_symbol_open2   r   zPositionManager.is_symbol_openc                 C   s   t | j ¡ ƒS r   )Úlistr   Úkeysr   r   r   r   Úget_open_symbols5   s   z PositionManager.get_open_symbolsc                 C   s   dd„ t  ¡ D ƒS )zjToday's closed trades, oldest first (rule modules see the last
        one per symbol as view.last_trade).c                 S   s   g | ]}|  d ¡dkr|‘qS )ÚstatusÚclosed)Úget)Ú.0Útr   r   r   Ú
<listcomp><   s    z7PositionManager.closed_trades_today.<locals>.<listcomp>)r   Úload_today_tradesr   r   r   r   Úclosed_trades_today9   s   z#PositionManager.closed_trades_todayÚequityÚentry_priceÚ
stop_pricec                 C   st   || }|dks
|sdS || j d  d }t || ¡}|| j d  d }t|t || ¡ƒ}|| j d kr8|S dS )z¹Risk account_risk_pct_per_trade% of equity on the distance
        between entry and the supplied stop, capped so no single
        position exceeds max_position_notional_pct_of_equity.r   Úaccount_risk_pct_per_tradeç      Y@Ú#max_position_notional_pct_of_equityÚ
min_shares)r   ÚmathÚfloorÚmin)r   r*   r+   r,   Úrisk_per_shareÚrisk_dollarsÚqtyÚnotional_capr   r   r   Úcalculate_qty>   s   zPositionManager.calculate_qtyNÚreasonsÚsetupÚplanc                 C   sL  |   |¡s	|  ¡ sdS | j ¡ }t|jƒ}|  |||¡}	|	dkr7t d|› d|d›d|d›d|d›d�	¡ dS z
| j 	||	d	¡}
W n t
y_ } zt d|› d
|› �¡ W Y d }~dS d }~ww t tj¡}||	||| ¡ tt|
ddƒƒ|pwg ||p{i dœ	| j|< |  ¡  t d|› d|	› d|d›d|› d|d›dd |pžg ¡› d�¡ dS )NFr   z[ENTRY] z sized to 0 shares (equity=$ú.2fz	, entry=$z, stop=$z) -- skippedÚbuyz order submission failed: ÚidÚ )	r   r6   r+   r,   Ú
entry_timeÚentry_order_idr9   r:   r;   z BUY z @ ~$z [z	] (stop $z, reasons: z; ú)T)r   r   r   Úget_accountÚfloatr*   r8   ÚlogÚinfoÚsubmit_market_orderÚ	ExceptionÚerrorr   Únowr   ÚutcÚ	isoformatÚstrÚgetattrr   Ú_saveÚjoin)r   r   r+   r,   r9   r:   r;   Úaccountr*   r6   ÚorderÚerJ   r   r   r   Úenter_positionO   sB   

ÿ
ÿ€þù	 ÿ
ÿzPositionManager.enter_positionÚ
exit_priceÚreasonc                 C   sÈ  | j  |¡}|s
d S z| j |¡ W nR tyd } zFt|ƒ}|r.|  ||¡s-W Y d }~d S n,t|ƒrH|  ||¡sAt	 
d|› d�¡ W Y d }~d S t	 d|› d|› �¡ W Y d }~d S W Y d }~nd }~ww | d¡rˆ| j |d ¡}|d urˆt|jp{dƒdkrˆtt|jƒƒ|d< |d r˜||d  |d  d nd	}||d  |d  }	i |¥|t tj¡ ¡ |d
t|dƒt|	dƒdœ¥}
t |
¡ | j |= |  ¡  t	 d|› d|d›d|› d|d›d|	d›d�¡ d S )Nú[EXIT] z[ broker reports no position yet (entry order likely still settling) -- will retry next pollz close_position failed: Úcancelled_entry_order_idr   r6   r+   r.   g        r#   é   é   )rU   Ú	exit_timeÚexit_reasonr"   Úpl_pctÚ
pl_dollarsz
 SELL @ ~$r<   ú (z	) -- P/L z+.2fz% ($rB   )r   r$   r   Úclose_positionrH   r   Ú_resolve_wash_conflictr   Ú_entry_never_filledrE   ÚwarningrI   Ú	get_orderrD   Ú
filled_qtyÚintr   rJ   r   rK   rL   Úroundr   Úappend_trade_recordrO   rF   )r   r   rU   rV   ÚprS   Úorder_idrR   r]   r^   Útrader   r   r   Úexit_positionr   sR   ÿ÷€ø
$ÿý
ÿÿzPositionManager.exit_positionrj   c              	   C   sÒ   | j | }| j |¡}|du rt d|› d|› d�¡ dS t|jdt|jƒƒ ¡ }t|j	dt|j	ƒƒ ¡ }|dkrJt d|› d|› d	|› d
�¡ dS t d|› d|› d|› d�¡ | j 
|¡ ||d< |  ¡  dS )zËclose_position() was rejected because order_id is resting on
        the other side. True = the position is genuinely being closed
        (record the exit now); False = keep it tracked, retry next poll.NrW   z wash-trade conflict with zH but couldn't fetch that order -- keeping position, will retry next pollFÚvalueÚsellz	 a SELL (z, z2) is already working; treating as being closed outTz" close blocked by our resting BUY r_   z1); cancelling it and keeping the position trackedrX   )r   r   rd   rE   rc   rN   ÚsiderM   Úlowerr"   Úcancel_orderrO   )r   r   rj   ri   rR   ro   r"   r   r   r   ra       s   
z&PositionManager._resolve_wash_conflictri   c              	   C   s´   |  d¡p	|  d¡}|r| j |¡nd}|du rdS t|jdt|jƒƒ ¡ }t|jp+dƒ}|dvs5|dkr7dS | j 	|¡dkrAdS t
 d|› d	|› d
|› d�¡ | j|= |  ¡  dS )zÅBroker says no position. If the entry order is dead with zero
        fill and the broker really holds nothing, stop tracking it (there
        was no trade); otherwise the entry is still settling.rX   rA   NFrm   r   )ÚcanceledÚexpiredÚrejectedrW   z entry order Ú z5 with no fill -- no trade happened, dropping positionT)r$   r   rd   rN   r"   rM   rp   rD   re   Úget_position_qtyrE   rc   r   rO   )r   r   ri   rj   rR   r"   Úfilledr   r   r   rb   ¼   s   z#PositionManager._entry_never_filledc                 C   s   t  | j¡ d S r   )r   Úsave_positionsr   r   r   r   r   rO   Ð   s   zPositionManager._save)NNN)Ú__name__Ú
__module__Ú__qualname__r   rf   r   Úboolr   rM   r   r   r!   r)   rD   r8   ÚdictrT   rl   ra   rb   rO   r   r   r   r   r
   %   s,    ÿÿÿÿ
ÿ#.r
   )Ú__doc__r1   r   r   Úconfig_loaderr   Úlogger_setupr   Úalpaca_clientr   r   r   r   rE   r
   r   r   r   r   Ú<module>   s    