
    i7                    >   U 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	m
Z
 ddlmZmZ ddlmZmZmZmZ dd	lmZmZ erdd
lmZ ddlmZ  ej4                  d      Ze G d d             Ze G d d             Z G d d      Zddddddddddd
Zde d<   d!d Z!y)"u  
APEX V16 — State / broker reconciliation.

Root-cause solution for V15-BUG-9 (state-broker desync). Applies a
5-case decision matrix between `state.active_trades` and the broker's
view of the world (positions + pending orders), at startup and
periodically during the run.

Decision matrix
---------------
For each (state, broker) pair on a symbol:

  (i)   state OPEN  + broker OPEN  + sizes match
        -> OK, no action.

  (ii)  state OPEN  + broker FLAT
        -> position closed broker-side while bot was down. Recover
           realized P&L from `broker.recent_trades` if the trade matches
           by symbol + contracts + post-opened_at timestamp; classify
           win/loss and call risk_manager.update_daily_pnl + register_*.
           Either way, drop the trade from state.active_trades.

  (iii) state FLAT  + broker OPEN
        -> position exists broker-side that V16 didn't open. Conservative
           policy: LOG-ONLY (do NOT auto-adopt). Operator decides whether
           to flatten manually or restart with a state file that includes
           the trade. Auto-adopt would inject an unsafe TradeEntry with
           zero indicators-at-entry (V15-BUG-2/5 territory).

  (iv)  state OPEN  + broker OPEN  + sizes mismatch
        -> partial fill / partial close happened broker-side. Update
           runtime contracts and (best-effort) flag inconsistency.
           For V16 conservative path: LOG-ONLY warning; treat the trade
           as still open with state-recorded size.

  (v)   broker has pending SL/TP orders without a matching position
        ("naked orphan orders") -> cancel or LOG-ONLY (current policy:
        log-only, V15 _watchdog_naked_positions parity).

Why log-only for ambiguous cases:
  V16 is stricter than V15 on auto-adopt. V15 sometimes inferred
  positions from broker state; that masked the real bug (state save
  failures) and produced "phantom" entries with no indicators.
  V16 prefers explicit operator action over silent auto-recovery.

Watchdog cadence:
  Orchestrator calls `watchdog_naked_positions()` every N iterations
  (V15 parity: roughly every 30s). Cheaper than full reconcile_startup
  because it only checks for naked positions (case v), which is the
  only one that can newly emerge mid-run without a state mutation.
    )annotationsN)defaultdict)	dataclassfield)datetimetimezone)TYPE_CHECKINGOptional)
BrokerBaseClosedTradeOrderPosition)	BrainName	Direction)SessionState)RiskManagerreconciliationc                      e Zd ZU dZ ee      Zded<    ee      Zded<    ee      Z	ded<    ee      Z
ded<    ee      Zded<    ee      Zded	<   d
Zded<   y)ReconciliationReportzAOutcome of a reconcile_startup() call. Fields are counts by case.default_factory	list[str]	case_i_okcase_ii_state_open_broker_flatcase_ii_recovered_via_historycase_iii_state_flat_broker_opencase_iv_size_mismatchcase_v_naked_ordersg        floatpnl_recovered_usdN)__name__
__module____qualname____doc__r   listr   __annotations__r   r   r   r   r   r         ,/home/work/apex_v16/broker/reconciliation.pyr   r   L   so    K 6Iy605d0K"IK/4T/J!9J16t1L#YL',T'B9B%*4%@@"u"r(   r   c                  .    e Zd ZU dZ ee      Zded<   y)NakedPositionsReportz-Outcome of a watchdog_naked_positions() call.r   r   naked_symbolsN)r!   r"   r#   r$   r   r%   r,   r&   r'   r(   r)   r+   r+   X   s    7$T:M9:r(   r+   c                  v    e Zd ZdZ	 	 d	 	 	 	 	 	 	 ddZdd	 	 	 ddZddZddZe	 	 	 	 	 	 dd	       Z	dd
Z
y)
Reconcilerz
    Stateless coordinator that runs the 5-case matrix between
    `SessionState.active_trades` and the broker's open positions /
    pending orders / recent trade history.
    Nc                <    || _         || _        || _        || _        y)a  
        Args:
            broker:       a connected BrokerBase.
            state:        the live SessionState (mutated in case ii).
            risk_manager: required for case (ii) P&L recovery; if None,
                          case (ii) drops the trade without updating
                          daily counters (degraded mode).
            logger:       LoggerBundle. Reconciliation events go to brain_log.
        N)_broker_state_risk_logger)selfbrokerstaterisk_managerloggers        r)   __init__zReconciler.__init__i   s       !
r(   F)post_reconnectc                 K   t               }|| _        | j                  j                          d{   }| j                  j	                          d{   }|D ci c]  }|j
                  | }}t        | j                  j                  j                               }t        |j                               }t        |      D ]U  }	| j                  j                  |	   }
|
j                  }|	|v r||	   }|j                  |j                  k(  r:|j                  j                  |	       | j                  d|	|j                         |j                   j                  |	       | j                  d|	|j                  |j                         |j"                  j                  |	       | j%                  |	       d{   }|0|j&                  j                  |	       |xj(                  |z  c_        | j                  j                  j+                  |	d       X ||z
  D ]K  }	||	   }|j,                  j                  |	       | j                  d|	|j                  |j.                         M | j1                  ||      |_        |j2                  D ]  }	| j                  d|		        | j                  d
t5        | dd      t7        |j                        t7        |j"                        t7        |j&                        t7        |j,                        t7        |j                         t7        |j2                        |j(                  	       |S 7 7 c c}w 7 w)a   
        Full reconciliation between state and broker. Run once at
        boot, after broker.connect, before orchestrator.run(). Also
        run after mid-loop reconnect (C2b), with post_reconnect=True
        so the events log distinguishes the two phases.

        Returns a ReconciliationReport. Mutates state.active_trades for
        case (ii) only (drop the trade after attempting P&L recovery).

        Idempotent: a second invocation finds no remaining case (ii)
        ghosts and is effectively a no-op for state mutation.
        Nrecon_case_i_ok)symbol	contractsrecon_case_iv_size_mismatch)r=   state_contractsbroker_contracts%recon_case_iii_state_flat_broker_open)r=   rA   	directionrecon_case_v_naked_orderr=   recon_startup_done_post_reconnectF)r:   case_icase_iicase_ii_recoveredcase_iiicase_ivcase_vr    )r   rG   r0   positions_getpending_ordersr=   setr1   active_tradeskeysr%   entryr>   r   append_logr   r   _recover_case_iir   r    popr   rC   _find_naked_ordersr   getattrlen)r4   r:   report	positionspendingpbroker_pos_by_symbolstate_symbolsbroker_symbolsr=   atrS   bprecovered_pnls                 r)   reconcile_startupzReconciler.reconcile_startup   s     &'-,,4466	3355 "+5
AHHaK5
 5
 DKK55::<=16689 =) 	<F**62BHHE--)&1<<5??2$$++F3II/(*  6 0077?II;%+.3oo/1||  = 55<<VD&*&;&;F&C C ,88??G,,=,))--fd;1	<6 %}4 	.F%f-B2299&AII=#')|| "  .	. &*%<%<Y%P"00 	AFII0I@	A 			&!(/@%!HV--.fCCD$'(L(L$MvEEFf::;V778$*$<$< 	 	> A 755
6 !DsA   /MM!MMMM0EM<M=FMMMc                4  K   | j                   j                          d{   }|s
t               S | j                   j                          d{   }| j	                  ||      }|D ]  }| j                  d|        t        t        |            S 7 s7 Gw)a  
        Lightweight periodic check: scan broker positions + pending orders
        for naked positions (position without protective SL). V15 parity
        (_watchdog_naked_positions, riga 2035-2115).

        Currently LOG-ONLY: V15 chose not to auto-flatten because operator
        review is safer on naked positions (a missed SL is rare and usually
        a transient broker quirk). V16 calibration may decide to escalate.
        Nwatchdog_naked_positionrE   )r,   )r0   rN   r+   rO   rX   rU   r%   )r4   r\   r]   nakedr=   s        r)   watchdog_naked_positionsz#Reconciler.watchdog_naked_positions   s      ,,4466	'))3355''	7; 	@FII/I?	@#$u+>> 7 6s"   BB-BBABBc           	       K   | j                   y| j                  j                  j                  |      }|y	 |j                  j
                  }|j                   |j                  t        j                        }| j                  j                  ||d       d{   }|s| j                  d|       y|d	   }t!        |j"                        }|j                  j$                  }|t&        j(                  j*                  t&        j,                  j*                  fvrt&        j(                  j*                  }|d	kD  }	| j                   j/                  ||	|
       |	r| j                   j1                  |       n| j                   j3                  |       | j                  d|||	||j4                  |j6                         |S 7 %# t        $ r%}t        j                  d| d|        Y d}~yd}~ww xY ww)a  
        Try to fetch the close P&L from broker history and apply it to
        risk_manager counters. Returns the recovered pnl_usd if a match
        was found, None otherwise.

        Match heuristic (V15 _fetch_last_close_price parity):
          - symbol contractId tag matches
          - profitAndLoss != 0
          - closed_at >= entry.opened_at (allow 5min skew either way)
          - take the most recent matching trade
        N)tzinfo2   )r=   sincelimitz_recover_case_ii(z) recent_trades raised: recon_case_ii_no_history_matchrE   r   )is_winbrainrecon_case_ii_recovered)r=   pnl_usdrp   rq   
exit_price	closed_at)r2   r1   rQ   getrS   	opened_atrk   replacer   utcr0   recent_trades	ExceptionlogwarningrU   r   rs   
brain_namer   TFvalueMRupdate_daily_pnlregister_tp_hitregister_sl_hitrt   ru   )
r4   r=   rb   since_dttradeselatestpnlrq   rp   s
             r)   rV   zReconciler._recover_case_ii   s     ::[[&&**62:		xx))H&#++8<<+@<<55XR 6  F II6vIF FNN###++Y\\-?-?@@LL&&Eq

##Ce#DJJ&&v.JJ&&v.		+#..",, 	 	. 
E  	KK+F83KA3OP	sB   6G5A#G GG !D G5G 	G2G-(G5-G22G5c                   t        t              }|D ]*  }||j                     j                  |j                         , g }| D ]B  t        fd|j                         D              }|r(|j                  j                         D |S )z
        V15 parity (_watchdog_naked_positions riga 2080-2105):
        a position is "naked" if it has no STOP order on its contractId.
        Missing TP is not flagged.
        c              3  \   K   | ]#  \  }}t        j                  |      xr d |v  % yw)STOPN)_contract_matches_symbolr=   ).0cidkindsr^   s      r)   	<genexpr>z0Reconciler._find_naked_orders.<locals>.<genexpr>>  s;       C )37 $eO$s   ),)r   rP   r=   addkindanyitemsrT   )r\   r]   protection_per_contractorh   has_stopr^   s         @r)   rX   zReconciler._find_naked_orders,  s     8C37G 	:A#AHH-11!&&9	:  		'A   #:"?"?"A H
 QXX&		' r(   c                    | j                   )	  | j                   j                  j                  |fi | y t        j                  d||       y # t        $ r Y #w xY w)Nz[recon] %s %s)r3   	brain_logwriter{   r|   info)r4   eventfieldss      r)   rU   zReconciler._logG  sY    <<#,&&,,U=f= 	%0  s   'A 	AA)NN)r5   r   r6   z'SessionState'r7   zOptional['RiskManager']returnNone)r:   boolr   r   )r   r+   )r=   strr   zOptional[float])r\   zlist[Position]r]   zlist[Order]r   r   )r   r   r   r   )r!   r"   r#   r$   r9   re   ri   rV   staticmethodrX   rU   r'   r(   r)   r.   r.   b   s     15  .	 
4 ).R!%R	Rp?.9v ! 
 41r(   r.   BP6EU6A6JY6CA6ESNQYMMGCMCL)
6B6E6A6J6CMESMNQMYMr   r   zdict[str, str]_SYMBOL_TAG_MAPc                L    | r|sy|| k(  ryt         j                  | |       }||v S )NFT)r   rv   )r=   contract_idtags      r)   r   r   ]  s2    f


ff
-C+r(   )r=   r   r   r   r   r   )"r$   
__future__r   loggingcollectionsr   dataclassesr   r   r   r   typingr	   r
   broker.broker_baser   r   r   r   core.contractsr   r   persistence.state_storer   trading.risk_managerr   	getLoggerr|   r   r+   r.   r   r&   r   r'   r(   r)   <module>r      s   2h #  # ( ' * G G /40 g() # # # ; ; ;l1 l1l uDUd5# r(   