
    a+j!V                        d Z ddlmZ ddlZddl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mZ  ed
       G d d             Z G d d      Zy)u6  
APEX V16 — Trade closer.

SINGLE POINT for closing a trade. Replaces V15's _close_trade (line 2148).

Responsibilities:
  - Compute final P&L from exit price (using pnl_calculator)
  - If live: call broker close_position + cancel orphan SL/TP orders
  - Build TradeCloseResult with all fields the orchestrator needs

NOT responsibilities:
  - Mutating state.active_trades (orchestrator does it after success)
  - Updating counters (orchestrator does it after success)
  - Saving state (orchestrator calls state_store.save() after)
  - Logging the trade_closed event (orchestrator logs)

Why split? V15-BUG-9: V15's _close_trade did everything inline
(close + del active + save_state) but in some error paths save_state
was skipped, leaving state file out of sync with broker. V16 separates
the broker-facing work from state mutation. Orchestrator owns the
sequencing and atomicity.

Failure modes:
  - Broker close fails -> result.success=False, error populated.
    Orchestrator decides: retry, mark as orphan, halt, etc.
  - In paper mode, never fails (just computes P&L).
    )annotationsN)	dataclass)datetimetimezone)Optional)
BrokerBase)
TradeEntry)compute_pnl	PnLResultT)frozenc                     e Zd ZU dZded<   ded<   ded<   ded<   ded	<   ded
<   dZded<   dZded<   dZded<   dZded<   dZ	ded<   dZ
ded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   y) TradeCloseResultz{
    Result of close_trade(). On success, the orchestrator uses this
    to update state, counters, and log the event.
    boolsuccessstrsymbol
brain_name	directionint	contractsreason        floatentry_price
exit_pricer   profit_ticksnet_profit_usdFis_winNzOptional[datetime]	closed_atduration_minutesbroker_orders_cancelled orphan_cancel_methoderror
error_kindzOptional[str]new_stop_order_idnew_target_order_id)__name__
__module____qualname____doc____annotations__r   r   r   r   r   r   r    r!   r#   r$   r%   r&   r'        +/home/work/apex_v16/trading/trade_closer.pyr   r   -   s     MKONNK KJL#NEFD %)I!(!e! $%S$ !##" E3OJ
 (,}+)--r.   r   c                      e Zd ZdZd	d
dZ	 	 	 	 	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddZy)TradeCloserz
    Closes a trade and returns a structured result.
    Used by orchestrator's track_trades loop and by direct exit signals.
    Nc                .    || _         || _        || _        y)z
        Args:
            broker: BrokerBase (used only in live mode)
            is_paper: True = simulation, no broker call
            logger: LoggerBundle (optional)
        N)brokeris_paperlogger)selfr3   r4   r5   s       r/   __init__zTradeCloser.__init__`   s      r.   c                  K   	 t        |j                  |j                  |j                  ||j                  |j
                  ||      }t        j                  t        j                        }||j                  z
  j                         dz  }	| j                   s|j                   rut        d|j                  |j                  |j                  |j
                  ||j                  ||j"                  |j$                  |j$                  d	kD  |t'        |	d
            S | j)                  ||||||	       d{   S # t        $ rH}t        d|j                  |j                  |j                  |j
                  |d| d      cY d}~S d}~ww xY w7 Xw)a#  
        Close a trade and return a TradeCloseResult.

        Args:
            entry: the immutable TradeEntry from when the trade opened
            exit_price: actual exit price (broker fill, or tech price for paper)
            reason: human-readable reason ("SL_HIT", "TP_HIT", "BRAIN_EXIT",
                    "TIME_STOP", "BROKER_NATURAL_TP_HIT", "FORCE_FLAT")
            tick_size: from config_futures (asset-specific)
            tick_value: from config_futures (asset-specific)

        Returns:
            TradeCloseResult.
        r   sl_pricetp_pricecurrent_pricer   r   	tick_size
tick_valueFP&L computation failed: config_errorr   r   r   r   r   r   r$   r%   N      N@Tr      )r   r   r   r   r   r   r   r   r   r   r   r   r    )r
   r   r:   r;   r   r   
ValueErrorr   r   r   r   nowr   utc	opened_attotal_secondsr4   r   net_profit_usd_intround_close_live)
r6   entryr   r   r=   r>   pnlerE   duration_mins
             r/   close_tradezTradeCloser.close_tradeo   sd    6	(!--(////#%	C. ll8<<(eoo-<<>E ==ENN#|| ++////!--% --"55--1!&|Q!7 " %%eZc<XXXI  
	#|| ++////04)	 	
	H YsB   FAD7 	C)F2F3F7	F =F=F>FFFc                  K   d}d}d}		 | j                   j                  |j                         d{   }
d}	 | j                   j                  |j                         d{   }t        |      }|ru|	d}|	}nd}d}t        d|j                  |j                  |j                  |j                  ||j                  ||j                  |j                  d|t        |d      ||      S d}d|j                  fd|j                   ffD ]J  \  }}|s		 | j                   j#                  |j                  |       d{   }t%        |dd      sF|dz  }L ||z  }|dkD  rd}d}	 | j                   j'                  |j                         d{   }||z  }|dkD  r|dk(  rd}|dk(  rYt)        j*                  d       d{    d}	 | j                   j'                  |j                         d{   }||z  }|dkD  rd}|	du }t        d/i d|d|j                  d|j                  d|j                  d |j                  d!|d"|j                  d#|d$|j                  d%|j                  d&|j                  dkD  d'|d(t        |d      d)|d*|d+|	xs d,d-|	rd.S d,S 7 t# t        $ r1}d| }	| j	                  d|j                   d|        Y d}~d}~ww xY w7 # t        $ r/}| j	                  d|j                   d| d	       d
}Y d}~d}~ww xY w7 # t        $ r2}| j	                  d|j                   d| d| d|        Y d}~Vd}~ww xY w7 # t        $ r,}| j	                  d|j                   d|        Y d}~d}~ww xY w7 7 # t        $ r,}| j	                  d|j                   d|        Y d}~d}~ww xY ww)0a  
        Live close: call broker.close_position, then cancel any orphan
        SL/TP orders that may still be active at broker.

        Note on "natural close" reasons (SL/TP hit broker-side): in those
        cases the position is already gone at broker; close_position is
        a no-op but cancel_all_for_symbol still cleans up orphan orders.

        V17 bug #3: orphan cancel uses a 3-stage cascade (same as
        orchestrator._check_external_close):
          Stage 1 (id_first):   direct cancel_order on entry.stop_order_id /
                                target_order_id (bypasses searchOpen filter)
          Stage 2 (searchopen): cancel_all_for_symbol (catches non-bracket
                                orphans like modify_stop residuals)
          Stage 3 (retry):      if total still 0, sleep 2s and retry searchOpen
                                once (handles broker order-list propagation race)
        r   noneNzclose_position failed: zclose_position z: Fz/positions_get probe failed after close attempt z; treating as still openTbroker_failure_position_openclose_success_but_position_openz7close_position returned success but position still openrC   )r   r   r   r   r   r   r   r   r   r   r   r   r    r$   r%   stoptargetzcancel_order(z, =z): r   id_firstzcancel_all_for_symbol 
searchopeng       @z retry: retryr   r   r   r   r   r   r   r   r   r   r   r    r!   r#   r$   r"   r%   broker_failurer-   )r3   close_positionr   	Exception_log_warningpositions_getr   r   r   r   r   r   r   rI   rJ   stop_order_idtarget_order_idcancel_ordergetattrcancel_all_for_symbolasynciosleep)r6   rL   r   r   rM   rE   rO   cancelled_countr#   broker_errorclose_resultrN   position_still_open	positionsr%   	error_msgcancelled_id_firsttagoidrescancelled_searchopencancelled_retryr   s                          r/   rK   zTradeCloser._close_live   s    4 %		E!%!;!;ELL!IIL0 $	'"kk77EEI"&y/ ';
(	>
M  $|| ++////!--% --"55!&|Q!7% ( U(()u,,-
 	(HC  KK44U\\3GG sIu-"a'"	( 	--!#-   !	L)-)J)J* $  	//!#(<(F#/  a--$$$O(,(I(ILL) # .O"'.$ $& 

<<
 ''
 oo	

 oo
 
 ))
 "
 ))
 11
 ))A-
 
 #<3
 %4
 "6
  $"!
" ,8'#
 	
" >@#
 	
y J  	E 5QC8L~RsCDD	E. F 	' A<<.1#%=? #'	'^ H !!#ELL>C5#c!E 	$  	L  6u||nBqcJKK		L %#  !!,U\\N(1#F s@  O(J) J&J) O(K) "K&#K) 2BO)L':L$;L'?OO#(M( M%M( .O>N ?O(N& .N#/N& 3B3O&J) )	K#2&KOK##O&K) )	L!2$LOL!!O$L''	M"0'MOM""O%M( (	N1!NONO#N& &	O/!OOOO)new_sl_pricenew_tp_pricec                 K   |dk  s||j                   k\  rt        d|j                    d|       	 t        |j                  |j                  |j
                  ||j                  |||      }	t        j                  t        j                        }||j                  z
  j                         d
z  }| j                   s|j                   rlt        d|j                  |j                  |j                  |||j                  ||	j"                  |	j$                  |	j$                  dkD  |t'        |d      d      S |j                   |z
  }||n|j                  }||n|j
                  }	 | j(                  j+                  |j                  |j                  ||t-        |      t-        |      |j.                  |j0                         d	{   }d}d}d	}d	}|;t5        |dd	      }|du rd}t5        |dd      }nt5        |dd	      }t5        |dd	      }t        d(i d|d|j                  d|j                  d|j                  d|d|d|j                  d|d|	j"                  d|r|	j$                  nd d!|r|	j$                  dkD  ndd"|d#t'        |d      d$dd|d%|sdndd&|d'|S # t        $ r>}
t        d|j                  |j                  |j                  ||d|
 d      cY d	}
~
S d	}
~
ww xY w7 2# t2        $ r>}
t        d|j                  |j                  |j                  ||d|
 d      cY d	}
~
S d	}
~
ww xY ww))u  
        Close a fraction of an open position. Used for PARTIAL_50 brain action.

        V18 12-mag — refactor: usa il nuovo `partial_close_via_opposite_order`
        invece di `close_position(size=N)`. Su alcuni asset ProjectX (6A
        confermato) `/Position/partialCloseContract` ritorna 400, mentre
        il flusso "market opposito + cancel SL/TP + nuovo bracket sul
        residuo" funziona universalmente.

        Il broker piazza:
          1. market order opposto × contracts_to_close (chiude metà)
          2. cancel SL+TP originali
          3. nuovo STOP @ `new_sl_price` × residual
          4. nuovo LIMIT @ `new_tp_price` × residual

        Caller (orchestrator) passa:
          - new_sl_price: be_price quando set_be_after_partial, altrimenti
                          il prezzo SL corrente (entry.sl_price o
                          runtime.current_sl_price).
          - new_tp_price: entry.tp_price (residuo mantiene stesso TP).

        Args:
            entry:               immutable TradeEntry of the open trade
            exit_price:          fill price stimato (paper: tech price;
                                 live: best-available market)
            contracts_to_close:  must be 0 < N < entry.contracts
            reason:              "PARTIAL_50_BRAIN" | "RSI50_PARTIAL" | ...
            tick_size, tick_value: ASSETS_MAP for this symbol
            new_sl_price:        SL price del nuovo stop sul residuo (live only)
            new_tp_price:        TP price del nuovo limit sul residuo (live only)

        Returns:
            TradeCloseResult con profit_ticks/net_profit_usd computati su
            `contracts_to_close`. In live, `new_stop_order_id` /
            `new_target_order_id` valorizzati con gli ID del nuovo bracket.

        Failure modes:
          - contracts_to_close <=0 or >= entry.contracts -> ValueError
          - Live broker rejection -> success=False, error populated
        r   z"contracts_to_close must be in (0, z), got r9   Fr?   r@   rA   NrB   TrC   )r   r   r   r   r   r   r   r   r   r   r   r   r    r!   )r   r   contracts_to_closeresidual_contractsrs   rt   old_stop_order_idold_target_order_idz)partial_close_via_opposite_order failed: r[   r"   r   r$   zbroker rejected partial closestop_id	target_idr   r   r   r   r   r   r   r   r   r   r   r   r    r!   r%   r&   r'   r-   )r   rD   r
   r   r:   r;   r   r   r   r   r   rE   r   rF   rG   rH   r4   r   rI   rJ   r3    partial_close_via_opposite_orderr   r`   ra   r]   rc   )r6   rL   r   rv   r   r=   r>   rs   rt   rM   rN   rE   rO   rw   sl_for_residualtp_for_residualri   okerrnew_stop_idnew_target_idok_attrs                         r/   partial_closezTradeCloser.partial_closes  s    h "&8EOO&K4U__4E F)*, 
	(!--(//,#%	C, ll8<<(eoo-<<>E ==ENN#|| ++//,!--% --"55--1!&|Q!7() ( #__/AA +7*B,*6*B,	!%!M!M||//#5#5"?3"?3"'"5"5$)$9$9 "N 	" 	L0 %)'+#lIt<G%lG5TU%lItD 'k4 H 

<<
 ''
 oo	

 )
 
 ))
 "
 ))
 68311S
 46C**Q.5
 
 #<3
 %&
 
  02'r!
" *#
$ !.%
 	
a  
	#|| ++//,04)	 	
	V	  
	#|| ++//,A!E+	 	
	sy   0M;J- .C6M%A K: K7K: 
C#M-	K463K/)K4*M/K44M7K: :	M3L<6M7M<MMc                    | j                   )| j                   j                  j                  d|        y t        d|        y )Nz[trade_closer] z[trade_closer WARN] )r5   systemwarningprint)r6   msgs     r/   r^   zTradeCloser._log_warning!  s;    ;;"KK&&'>?(./r.   )N)r3   r   r4   r   returnNone)rL   r	   r   r   r   r   r=   r   r>   r   r   r   )rL   r	   r   r   r   r   rM   r   rE   r   rO   r   r   r   )rL   r	   r   r   rv   r   r   r   r=   r   r>   r   rs   Optional[float]rt   r   r   r   )r   r   r   r   )	r(   r)   r*   r+   r7   rP   rK   r   r^   r-   r.   r/   r1   r1   Z   s!   
	JYJY JY 	JY
 JY JY 
JY`n
n
 n
 	n

 n
 n
 n
 
n
z )-(,h
h
 h
  	h

 h
 h
 h
 &h
 &h
 
h
\0r.   r1   )r+   
__future__r   re   dataclassesr   r   r   typingr   broker.broker_baser   core.contractsr	   trading.pnl_calculatorr
   r   r   r1   r-   r.   r/   <module>r      sO   8 #  ! '  ) % 9 $%. %. %.XK0 K0r.   