
    djs$                   4   d Z ddlmZ ddlZddlmZmZmZmZ ddl	Z
	 ddlmZ ddlmZmZ dZerdd	lmZ  e        	 dd
lmZ  e         ej,                  d      Z G d dej0                        Zd Z G d d      Zy# e$ r dZdZY ]w xY w# e$ r Y Kw xY w)u  
================================================================
  🔌 TOPSTEPX ADAPTER — V15 (ASYNC NATIVE)
================================================================

  IMPORTANTE — CAMBIO ARCHITETTURALE vs versione precedente:
  
  La versione precedente tentava un wrapper SYNC sopra il SDK ASYNC
  usando un event loop in un thread separato. Non funzionava: il SDK
  `project-x-py` spawna task interni (WebSocket callbacks, position
  processor) che si aspettano un event loop NEL thread corrente.
  Risultato: spam di errori "no running event loop" illeggibili.
  
  Questa versione è ASYNC-NATIVA:
    - Ogni metodo è `async def`
    - Tutto gira nello stesso event loop
    - I Brain V14 (sync, CPU-bound, chiamano Claude API) vanno usati
      con `await asyncio.to_thread(brain.ask_entry, ...)` dal main

  API PUBBLICA:
    async connect(instruments)               -> bool
    async disconnect()
    async get_bars(symbol, timeframe, count) -> pd.DataFrame
    async get_current_price(symbol)          -> float | None
    async get_account_info()                 -> dict
    async positions_get(symbol=None)         -> List[dict]
    async place_market_bracket(symbol, side, size, sl, tp) -> dict
    async modify_sl(symbol, stop_order_id, new_sl)         -> bool
    async close_position(symbol, size=None)  -> dict
    async get_cumulative_delta(symbol, minutes) -> float | None

    (sync, solo lookup da cache locale)
    get_instrument_info(symbol) -> dict

  REQUIREMENTS:
    pip install project-x-py pandas
    )annotationsN)AnyOptionalListDict)TradingSuite)PositionBracketOrderResponseTF)apply_sdk_patches)apply_orderbook_patchtsx_adapterc                       e Zd ZdZg dZddZy)_JsonNoiseFilteru   
    Il SDK project-x-py emette log JSON strutturati rumorosi (warning
    psutil, DST init, throttle notices). Filtriamo dal root logger 
    per tenere la console leggibile.
    
    Filtra per contenuto specifico — NON nasconde errori veri.
    )zpsutil not availablezDST handling initializedthrottlezDynamic resource limitszError processing depth entryz"Orderbook reset due to RESET eventz.Gap will be filled when real-time data arrivesz&Dynamic resource configuration updatedzDynamic resource limits enabledz#Started dynamic resource monitoringzCleanup schedulerzon_reconnect not definedzConnection closed EOFc                R    |j                         }| j                  D ]  }||v s y y)NFT)
getMessageNOISE_SUBSTRINGS)selfrecordmsgnoises       ./home/work/apex_v16/broker/topstepx_adapter.pyfilterz_JsonNoiseFilter.filtere   s4    !** 	E|	     N)r   zlogging.LogRecordreturnbool)__name__
__module____qualname____doc__r   r    r   r   r   r   H   s    *r   r   c                     t        j                         } | j                  D ]  }t        |t              s y | j                  t	                      y)z0Installa il filtro sul root logger. Idempotente.N)logging	getLoggerfilters
isinstancer   	addFilter)rootfs     r   install_log_filterr*   m   sC    D\\ a)* 	NN#%&r   c                  j   e Zd ZdZdddddddddddddZdd	dd	d
Zd Z	 	 d6	 	 	 	 	 	 	 d7dZd Zd8dZ	d9dZ
d:dZd;dZd	ddddddd	ddddddZd<dZd=dZ	 d>	 	 	 	 	 	 	 d?dZ	 	 	 	 	 	 	 	 d@dZ	 	 	 	 	 	 	 	 dAdZdBdZdCd Zd! ZdDd"ZdCd#ZdEd$ZdFd%ZdGd&ZdGd'Z	 dH	 	 	 	 	 	 	 dId(Z	 dH	 	 	 dJd)ZdKd*Z	 	 	 	 dL	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dMd+Z	 	 dN	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dOd,Z 	 	 	 	 	 	 	 	 dPd-Z!	 	 	 	 	 	 dQd.Z"	 	 	 	 dRd/Z#	 	 dN	 	 	 	 	 	 	 dSd0Z$	 dT	 	 	 	 	 dUd1Z%dRd2Z&dVd3Z'd4 Z(d5 Z)y)WTopstepXAdapteraJ  
    Async-native facade sopra project-x-py TradingSuite.
    
    Usage:
        async def main():
            tsx = TopstepXAdapter()
            await tsx.connect(["MES", "MNQ", "MGC", "MCL"])
            bars = await tsx.get_bars("MES", "5min", 200)
            await tsx.disconnect()
        
        asyncio.run(main())
    5min15min1hr4hr1day)M5M15H1H4D1r-   r.   r/   r0   r1   1hour4hourr      )BUYSELLr   r9   c                    t         st        d      t                d | _        d| _        g | _        i | _        d| _        y )Nz=project-x-py non installato. Esegui: pip install project-x-pyFT)_SDK_AVAILABLERuntimeErrorr*   _suite
_connected_instruments_instrument_cache_enable_orderbookr   s    r   __init__zTopstepXAdapter.__init__   sE    3  	.2')<>!%r   Nc                  K   || _         || _        |xs g d}g }|r|j                  d       	 ddd}t        j                  ||||       d{   | _        d| _        t        j                  d| j                                 |D ]  }| j
                  |   }|j                  }	|	j                  ||	j                  t        |	j                        t        |	j                        |	j                   d	| j"                  |<   t        j                  d
| d|	j                   d|	j                   d|	j                           y7 # t$        $ r)}
t        j'                  d|
       d| _        Y d}
~
yd}
~
ww xY ww)u  
        Autentica, crea TradingSuite, sottoscrive ai dati real-time.
        
        NOTE sulla feature `enable_orderbook`:
        In SDK v3.5.x c'è un bug noto che genera spam di errori
        "Error processing depth entry" sul feed L2. Teniamo orderbook
        disabilitato in Fase 1 (comunque abbiamo volumi reali sulle barre).
        Lo riattiveremo in Fase 2 quando integreremo delta/volume profile
        con più care (eventualmente usando un workaround custom sul raw WS).
        )r-   r/   r0   	orderbookF)enable_tick_dataenable_level2_data)instruments
timeframesfeaturesdata_manager_configNTu   [TSX] Connected — account=)contract_idsymbolname	tick_size
tick_valuedescription[TSX] u    → contract=z, tick_size=z, tick_value=$z[TSX] Connect failed: )rA   rC   appendr   creater?   r@   loginfo_get_account_nameinstrument_infoidrP   floattickSize	tickValuerS   rB   	Exceptionerror)r   rJ   rK   enable_orderbooktfsrL   data_mgr_cfgsymctxinstes              r   connectzTopstepXAdapter.connect   s}      (!122OOK('	 %*&+L !- 3 3'!$0	! DK #DOHH3D4J4J4L3MNO" kk#&**$(GG$'$(II$)$--$8$)$..$9$($4$4/&&s+ SEy 9!!% 0##'>>"24  34  	II.qe45#DO	sA   ,E:!E EC1E E:E 	E7E2-E:2E77E:c                  K   | j                   r*	 | j                   j                          d {    d | _         d| _        t        j                  d       y 7 (# t        $ r"}t        j	                  d|        Y d }~Ld }~ww xY ww)Nz[TSX] Disconnect warning: Fz[TSX] Disconnected)r?   
disconnectr_   rW   warningr@   rX   )r   rg   s     r   rj   zTopstepXAdapter.disconnect   sp     ;;>kk,,... DK%& / >8<==>s=   BA AA $BA 	B!A>9B>BBc                :    | j                   xr | j                  d uS N)r@   r?   rD   s    r   is_connectedzTopstepXAdapter.is_connected   s    :4;;d#::r   c                v    	 | j                   j                  j                  j                  S # t        $ r Y yw xY w)Nunknown)r?   clientaccount_inforP   r_   rD   s    r   rY   z!TopstepXAdapter._get_account_name   s5    	;;%%22777 		s   ), 	88c                N    | j                   st        d      | j                   |   S )Nz$Not connected. Call connect() first.)r?   r>   r   rO   s     r   _get_ctxzTopstepXAdapter._get_ctx   s$    {{EFF{{6""r   c                    | j                   j                  |      }|st        |d      S |d   }t        t        ||z        |z  d      S )N   rQ      )rB   getround)r   rO   pricerX   tss        r   _align_to_tickzTopstepXAdapter._align_to_tick  sL    %%))&1?"+U52:&+Q//r            <        )1minr-   r.   30minr/   r0   1d1m5m15m30m1h4hc                v   	 ||j                   sd|j                  vryddlm}m} |d   j                  d   }t        |d      r|j                         }n|}|j                  |j                  |j                        }|j                  |j                        |z
  j                         S # t        $ r Y yw xY w)zBAge in seconds dell'ultima barra. None se df vuoto o senza 'time'.Ntimer   datetimetimezoneto_pydatetime)tzinfo)emptycolumnsr   r   ilochasattrr   r   replaceutcnowtotal_secondsr_   )r   dfr   r   last_tsdts         r   _df_last_age_secondsz$TopstepXAdapter._df_last_age_seconds  s    	zRXXrzz)A3joob)Gw0**,yy ZZx||Z4LL.3BBDD 		s   B, BB, ,	B87B8c                0   	 ||j                   sd|j                  vryt        |d   j                  d         }d|j                  v r|d   j                  d   nd}t	        | d      si | _        | d| }| j
                  j                  |      }||dd	| j
                  |<   |y|j                  d
      |k(  rMt        |j                  dd      |z
        dk  r,|j                  dd      dz   }|| j
                  |   d<   |dk\  S y# t        $ r Y yw xY w)u  
        True se il close dell'ultima barra è identico per N fetch consecutivi.
        
        Quando WS è disconnesso, `ctx.data.get_data` restituisce sempre gli stessi
        dati cached. La barra corrente non si aggiorna con i tick real-time → 
        close identico tra fetch consecutivi = WS probabilmente rotto.
        
        Track per (symbol, tf) key. Dopo 2 fetch consecutivi con stesso close,
        consideriamo i dati "frozen".
        NcloseFr   r   _last_seen_close:r9   )r   r|   countr|   r   g-q=r   rw   )	r   r   r\   r   r   r   ry   absr_   )	r   rO   tfr   current_close
current_tskeyprev	new_counts	            r   _is_close_frozenz TopstepXAdapter._is_close_frozen%  s0   "	zRXX

)B!"W+"2"22"67M06"**0DF,$J4!34(*%HAbT"C((,,S1D ' *D!!#& | *,DHHWa(=89EA HHWa014	6?%%c*73 A~% 		s   D	 BD	 'A D	 		DDc           	       K   ddl }ddl}|j                  dd      dk(  }| j                  j	                  ||      }| j
                  j	                  |      xs | j
                  j	                  |      xs d}t        | d      sd| _        d	| _        d
| _	        d| _
        |}	|s| j                  r}|j                         | j                  z
  }
|
| j                  k\  rOt        j                  d|
dd       d| _        d| _
        t        | d      r| j                  j                          nd}	|	r3| j                  |||       d{   }||S t!        j"                         S | j%                  |||       d{   }| j'                  |      }d|z  dz  }|duxr ||kD  }| j)                  |||      }|du xs |j*                  }|s|s|r5|rd}n|rd|dd}nd}| xj                  dz  c_
        d}| j                  |k\  rY| j                  sMt        j-                  d| j                   d| j                   d       d| _        |j                         | _        n.| j                  |k  rt        j-                  d| d| d| d       | j                  |||       d{   }|=|j*                  s1t        | d      r#| d| }| j                  j/                  |d       |S t        j1                  d| d| d        n+| j                  dkD  rt        j                  d!       d| _
        ||S t!        j"                         S 7  7 7 w)"a  
        DataFrame pandas con [time, open, high, low, close, volume, tick_volume].
        
        STRATEGIA V15 (hybrid realtime + REST fallback + sticky mode):
        
          MODE NORMAL (default):
            - Prova `ctx.data.get_data` (WS-backed, fast)
            - Fallback REST se: empty, timestamp stale, close frozen, FORCE_REST_ONLY
          
          MODE STICKY (auto-attivato dopo N frozen consecutivi):
            - Va SEMPRE direttamente a REST (skip WS, no noise)
            - Ogni `WS_RETRY_INTERVAL_SEC` (default 5min) ritenta WS una volta
              per auto-recovery se il WebSocket torna su
          
          MODE FORCED (env var FORCE_REST_ONLY=1):
            - Sempre e solo REST, ignora WS completamente
        r   NFORCE_REST_ONLY01r~   _ws_sticky_downF        i,  z[TSX] Retry WS dopo .0fzs sticky REST-onlyr   Tg      @r   r   z
timestamp zs stalezclose frozenr9      u)   [TSX] ⚠️  WebSocket down confermato (u)    frozen). Attivo modalità REST-only per srT    z data u    → fallback RESTr   z REST fallback also emptyu<   [TSX] ✅ WS ha ricevuto tick fresh → reset frozen counter)osr   getenvTIMEFRAME_MAPry   _TF_TO_MINUTESr   r   _ws_sticky_since_ws_retry_interval_sec_ws_sticky_frozen_countrW   rX   r   clear_get_bars_restpd	DataFrame_get_bars_realtimer   r   r   rk   popr`   )r   rO   	timeframer   r   r   
force_restr   
tf_minutesuse_rest_directelapseddf_restr   agestale_threshold_secis_time_stale	is_frozenis_emptyreasonSITCKY_THRESHOLDr   s                        r   get_barszTopstepXAdapter.get_barsT  sb    ( 	YY0#6#=
##Iy9((,,R0[D4G4G4K4KI4V[Z[
 t./#(D $'D!*-D'+,D( %d22iikD$9$99G$555/}<NOP',$/0,4!34))//1"& //
EJJG%17Er||~E**62u== ''+!J.34EC2E,E ))&"b9	 :)}	 %c#Yg6' ((A-(  !++/??H\H\?@\@\?] ^??C?Z?Z>[[\^ (,$(,		% //3CCKK "VF8;MN !//
EJJG"7==4!34#HAbT*C))--c48IIvhat+DEF ++a/WY/0,^r77s K >P Ks8   EMM2M8M9D(M!M"B$MMMc           	     |  K   	 | j                  |      }|j                  j                  ||       d{   }||j                         rt        j                         S | j                  ||      S 7 <# t        $ r<}t        j                  d| d| d|        t        j                         cY d}~S d}~ww xY ww)z2Fetch da realtime data manager (WebSocket-backed).)barsNz[TSX] realtime get_bars(, z	) error: )
ru   dataget_datar_   rW   rk   r   r   r   _pl_to_pandas)r   rO   r   r   re   df_plrg   s          r   r   z"TopstepXAdapter._get_bars_realtime  s     	"--'C((++BU+;;E
 =ENN,<<>!!!%// < 	"KK26("RD	!MN<<>!	"sC   B<1A4 A2A4 8B<2A4 4	B9=1B4.B9/B<4B99B<c           	     D  K   	 | j                  |||       d{   }||j                  s|S 	 | j
                  j                  }t        dd|z        }t        dt        d||z  dz               }|j                  |||	       d{   }	|	t        j                         S 	 t        |	d      r$|	j                         rt        j                         S t        |	d      r |	j                  rt        j                         S | j                  |	|      S 7 # t        $ r'}t        j	                  d| d| d       Y d}~d}~ww xY w7 # t        $ r<}t        j                  d
| d| d|        t        j                         cY d}~S d}~ww xY w# t        $ r Y w xY ww)u  
        Fetch via REST DIRETTA (bypass SDK cache).
        🔧 V15 27/04 v3: SDK get_bars è cached. Usa /api/History/retrieveBars
        direttamente per garantire bar fresche anche con WS down.
        Fallback al SDK solo se direct REST fallisce (es. errore network).
        NrT   z direct REST bars failed: z, fallback SDKr9   r   r   rw   )daysintervalz[TSX] REST get_bars(r   zmin) error: r   r   )_fetch_bars_direct_restr   r_   rW   rk   r?   rq   maxminr   r`   r   r   r   r   r   )
r   rO   interval_minutesr   	df_directrg   rq   bars_per_dayr   r   s
             r   r   zTopstepXAdapter._get_bars_rest  s    	V"::6CSUZ[[I$Y__  
		"[[''Fq4+;#;=Lq#b5L#8A"=>?D //T,< *  E =<<>!	uj)enn.>||~%ug&5;;||~% !!%//; \  	VKK&(B1#^TUU	V  	"II,VHB7G6HUVTWXY<<>!	"  		s   F D DD F AE	 E	E	 F $/F F +F ?F D 	ED?9F ?EF E	 		F1F	FF 	FF 	FF FF c           	     
   t        |d      r	 |j                  d      }n|}ddddddd	d
}|j                  |j                         D ci c]  \  }}||j                  v s|| c}}      }d	|j                  v rd|j                  vr|d	   |d<   d|j                  v r0|j                  d      j                  |      j                  d      }|S # t        t        f$ rI t	        j
                  |j                  D ci c]  }|||   j                          nc c}w c}      }Y t        $ rI t	        j
                  |j                  D ci c]  }|||   j                          nc c}w c}      }Y Xw xY wc c}}w )zDConvert polars/pandas DF a pandas standard con colonne normalizzate.	to_pandasF)use_pyarrow_extension_arrayr   openhighlowr   volume)	timestamp	TimestampOpenHighLowCloseVolume)r   tick_volumeTdrop)r   r   ImportErrorModuleNotFoundErrorr   r   r   to_listr_   renameitemssort_valuestailreset_index)r   df_objr   r   col
rename_mapkvs           r   r   zTopstepXAdapter._pl_to_pandas  sj    6;'Z%%%%H B  

 YY1A1A1CWAqBJJ1WYXrzz!m2::&E "8B}RZZ',,U3??T?JB	-  !45 Z\\"X#3s(;(;(=#="X"XY Z\\"X#3s(;(;(=#="X"XYZ  Xs;   C E?
"E?
,E< D
E<+%E<E,
+E<;E<c                z  K   t        | dd      r| j                  |       d{   S 	 | j                  |      }|j                  j	                          d{   }|t        |      S 	 | j                  |       d{   S 7 _7 -# t        $ r%}t        j                  d| d|        Y d}~Fd}~ww xY w7 9w)u  
        Prezzo corrente dell'instrumento.
        
        Strategia:
          1. Se WS sticky down → REST diretto (da ultima M1 bar)
          2. Altrimenti prova ctx.data.get_current_price() (WS-backed)
          3. Se ritorna None o WS frozen → REST fallback
        r   FNz[TSX] get_current_price(z) WS failed: )	getattr_get_current_price_restru   r   get_current_pricer\   r_   rW   rk   )r   rO   re   prg   s        r   r  z!TopstepXAdapter.get_current_price  s      4*E255f===	M--'Chh0022A}Qx  11&999 > 3  	MKK26(-sKLL	M :sV   "B;BB;.B BB )B;?B9 B;B 	B6B1,B;1B66B;c                  K   ddl }ddl}ddl}ddlm}m}m}	 	 | j                  |      }
|
r|
j                  d      sy|
d   }|j                  d      }|j                  d      }|r|sy|j                  |j                        j                         }t        | dd      }t        | dd      }|r||k  r|}nZ|j                  d	||d
d      }|j                  dk7  ry|j                         j                  d      }|sy|| _        |dz   | _        d|z   dd}|j                  |j                        }t%        ||z  dz  |dz  z   d      }| |	|      z
  }|j                  d||d|j'                         |j'                         d||dz   ddd      }|j                  dk7  ry|j                         }|j                  d      sy|j                  d      xs g }|syg }|D ]x  }|j)                  |j                  d      |j                  d       |j                  d!      |j                  d"      |j                  d#      |j                  d$      xs dd%       z  |j*                  |      } |j,                  |d&   d'(      |d&<   |j/                  d&      j1                  |      j3                  d')      }|d*   |d+<   |S # t4        $ r%}t6        j9                  d,| d-|        Y d}~yd}~ww xY ww).z
        V15 27/04 v3: REST diretta /api/History/retrieveBars per N barre.
        Usato da _get_bars_rest per bypass cache SDK.
        Returns: pandas DataFrame o None se fail.
        r   Nr   r   	timedeltarN   PROJECT_X_API_KEYPROJECT_X_USERNAME_direct_rest_token_direct_rest_token_exp*https://api.topstepx.com/api/Auth/loginKeyuserNameapiKeyr~   jsontimeout   token  Bearer application/jsonAuthorizationzContent-Typer   
   i  minutes1https://api.topstepx.com/api/History/retrieveBarsFrw   
contractIdlive	startTimeendTimeunit
unitNumberlimitincludePartialBar   headersr  r  successr   tohlcr   )r   r   r   r   r   r   r   T)r   r   r   r   rT   z  _fetch_bars_direct_rest error: )r   requestspandasr   r   r  get_instrument_infory   r   r   r   r   r  poststatus_coder  r  r  r   	isoformatrU   r   to_datetimer   r   r   r_   rW   rk   )r   rO   r   r   r   r1  r   r   r   r  rX   rN   api_keyusernamenow_tscached_token
cached_expr  rr*  r   	mins_backstartr   r   rowsbr   rg   s                                r   r   z'TopstepXAdapter._fetch_bars_direct_rest1  s     	*)::G	++F3Dtxx6}-Kyy!45Gyy!56H(\\(,,/99;F"4)=tDL"4)A1EJ 3$MM@&.'B " 
 ==C'W-*/'.4tm+(1E(9K]^G,,x||,CE$44q8;Kb;PPRUVI)I66EC"-!!&!2"}}"2"QY).	   A }}#668D88I&88F#)rDD EE#JEE#JEE#J55:UU3ZeeCjoA  d#B'6
=BvJ',,U3??T?JB "8B}I 	KK&(HLM	s   K6$K K6+K *K6+A3K K6!K  K6B$K %K6&!K K6K K6 C$K K6	K3K.)K6.K33K6c                  K   ddl }ddl}ddlm}m}m} 	 | j                  |      }|r|j                  d      sy|d   }	|j                  d      }
|j                  d      }|
r|sy|j                  |j                        j                         }t        | dd      }t        | dd      }|r||k  r|}nZ|j                  d	||
d
d      }|j                  dk7  ry|j                         j                  d      }|sy|| _        |dz   | _        d|z   dd}|j                  |j                        }| ||      z
  }|j                  d||	d|j#                         |j#                         dd|dz   ddd      }|j                  dk7  ry|j                         }|j                  d      sy|j                  d      xs g }|sy|d   S # t$        $ r%}t&        j)                  d| d|        Y d}~yd}~ww xY ww)z
        V15 27/04: REST diretta retrieveBars per bypassare cache SDK.
        Usa live=False (live=True ritorna errorCode 1).
        r   Nr  rN   r	  r
  r  r  r  r  r~   r  r  r  r  r  r  r  r  r  Frw   r9   r  r)  r+  r   rT   z direct REST failed: )r   r1  r   r   r  r3  ry   r   r   r   r   r  r4  r5  r  r  r  r6  r_   rW   rk   )r   rO   minutes_backr   r1  r   r   r  rX   rN   r8  r9  r:  r;  r<  r  r=  r*  r   r?  r   r   rg   s                          r   _fetch_bar_direct_restz&TopstepXAdapter._fetch_bar_direct_rest  s    
 	::8	++F3Dtxx6}-Kyy!45Gyy!56H(\\(,,/99;F"4)=tDL"4)A1EJ 3$MM@&.'B " 
 ==C'W-*/'.4tm+(1E(9K]^G,,x||,C)L99EC"-!!&!2"}}"#)A-).	   A }}#668D88I&88F#)rD7N 	KK&(=aSAB	s   G:$G	 G:+G	 &G:'A3G	 G:!G	 <G:=BG	 	G:
!G	 +G:,G	 G:G	 G:		G7G2-G:2G77G:c           	       K   ddl m }m} d}	 | j                  |d       d{   }|sT| j                  |dd       d{   }||j                  sd	|j
                  vryt        |d	   j                  d
         S |j                  d      }|ryddl m } |j                  |j                  dd            }	|j                  |j                        |	z
  j                         }
|
|kD  r!t        j                  d| d|
dd| d       y|j                  d      }|t        |      S y7 7 # t         $ r%}t        j                  d| d|        Y d}~yd}~ww xY ww)u+  
        Prezzo corrente via REST DIRETTA (bypass SDK cache).
        🔧 V15 27/04 v2: SDK get_bars è cached (non aggiorna se WS down).
        Usa _fetch_bar_direct_rest che chiama /api/History/retrieveBars
        direttamente. Scarta bar > 180s (= 3 M1 chiuse di ritardo accettabile).
        r   r      r~   )rC  Nr9   )r   r   r   r   r,  )r   Zz+00:00rT   z REST direct stale: bar age=r   zs (> zs)r0  z$[TSX] get_current_price REST direct(
) failed: )r   r   rD  r   r   r   r\   r   ry   fromisoformatr   r   r   r   rW   rk   r_   )r   rO   r   r   MAX_AGE_SECbarr   ts_str_dtr   r   r   rg   s                r   r  z'TopstepXAdapter._get_current_price_rest  s_     	0	33F3KKC..vQR.SS:WBJJ-FR[--b122 WWS\F4++FNN3,IJ||HLL1G;JJL$KK&0LSQTIUZ[fZggi jkGGCLE U|#) L T$  	KK>vhjQRPSTU	su   E D/ D*D/ D- D/ #E $D/ >E ?BD/ 
E D/ (E *D/ -D/ /	E8EE EE c                8    | j                   j                  |      S )z,Sync: lookup da cache popolata al connect().)rB   ry   rt   s     r   r3  z#TopstepXAdapter.get_instrument_info  s    %%))&11r   c                  K   	 | j                   j                  j                  }|j                  t	        |j
                        t        |dd       dS # t        $ r$}t        j                  d|        i cY d }~S d }~ww xY ww)Nr[   )rP   balancer[   z[TSX] get_account_info failed: )
r?   rq   rr   rP   r\   rP  r  r_   rW   rk   )r   accrg   s      r   get_account_infoz TopstepXAdapter.get_account_info  ss     		++$$11C88 -"3d3 
  	KK9!=>I	s5   BAA B	A?A:4A?5B:A??Bc                l  K   ddl }ddlddlddlm}m} fd}	 |j                  d      }|j                  d      }|r|sg S |j                  |j                        j                         }t        | dd      }t        | dd      }	|r||	k  r|}
nS |d	||d
d      }|j                  dk7  rg S |j                         j                  d      }
|
sg S |
| _        |dz   | _        | j                  r| j                  j                   nd}|rt        |dd      nd}|rt        |dd      nd}|sg S  |dd|
z   dddt#        |      id      }|j                  dk7  rg S |j                         j                  dg       xs g S # t$        $ r$}t&        j)                  d|        g cY d}~S d}~ww xY ww)u#  
        🔧 V15 27/04: REST diretta a /api/Position/searchOpen, bypassa SDK
        rate limiter che ritarda fino a timeout. Usato da positions_get
        come primary path; fallback a _raw_positions_search se fail.
        Token cache 30min condivisa con _fetch_bar_direct_rest.
        r   Nr   c                    d }t        d      D ]  }	  j                  | fi |c S  |# j                  j                  f$ r"}|}|dk  rj	                  d       Y d }~Td }~ww xY w)Nr   rw   g?)ranger4  TimeoutConnectionErrorsleep)urlkwargslast_excattemptrg   r1  r   s        r   _post_retryz;TopstepXAdapter._positions_direct_rest.<locals>._post_retry  sz     H 8 (((8==777( N	 !(((*B*BC ( H{

3(s   +A(A##A(r	  r
  r  r  r  r  r~   r  r  r  r  rr   r[   z0https://api.topstepx.com/api/Position/searchOpenr  r  r  	accountId   r)  	positionsz$[TSX] _positions_direct_rest error: )r   r   r1  r   r   r   r   r   r   r  r5  r  ry   r  r  r?   rq   intr_   rW   rk   )r   r   r   r   r]  r8  r9  r:  r;  r<  r  r=  rq   rQ  
account_idrg   r1  r   s                   @@r   _positions_direct_restz&TopstepXAdapter._positions_direct_rest  s     	"!/	'	yy!45Gyy!56H(	\\(,,/99;F"4)=tDL"4)A1EJ 3$@&.'B
 ==C'IW-I*/'.4tm++/;;T[[''DF;A'&.$7tC58dD1dJ	B*3e*;M_`!3z?3	A }}#	668<<R06B6 	KK>qcBCI	sr   F4'F F4A+F 3F44"F F4AF 1F42-F F4 #F F4	F1F,&F1'F4,F11F4c                  K   | j                   sg S 	 | j                   j                  }t        |dd      }|rt        |dd      nd}|sg S dt        |      i}|j	                  dd|       d{   }t        |t              sg S |j                  dg       xs g }|S 7 .# t        $ r$}t        j                  d	|        g cY d}~S d}~ww xY ww)
uh  
        Raw REST call a /Position/searchOpen — bypassa la classe Position
        della SDK (che crasha con 'Position.__init__ got unexpected kwarg
        contractDisplayName' quando ProjectX API ha campi nuovi).
        
        Ritorna lista di dict con le posizioni aperte dell'account corrente,
        usando i campi raw del JSON di risposta.
        rr   Nr[   r^  POSTz/Position/searchOpenr   r`  z$[TSX] _raw_positions_search failed: )r?   rq   r  ra  _make_requestr&   dictry   r_   rW   rk   )r   rq   rQ  rb  payloadresppositions_rawrg   s           r   _raw_positions_searchz%TopstepXAdapter._raw_positions_search;  s      {{I	[[''F&.$7C58dD1dJ	"C
O4G--& .  D
 dD)	 HH["5;M    	KK>qcBCI	s^   C7B 	C
$B .B/B CB CB 	C(CCCCCc                B  K   | j                   sdddS |dk  rdd| dS 	 | j                   j                  }|%t        |dd      }|rt        |dd      nd}|sdd	dS t        |      t	        |      t        |      d
}t
        j                  d| d|        |j                  dd|       d{   }t        |t              r|S dd|dS 7 # t        $ rB}t        |      j                   d| }	t
        j                  d|	        d|	ddcY d}~S d}~ww xY ww)u\  
        Raw REST call a /Position/partialCloseContract — bypassa il wrapper
        SDK `close_position_by_contract` che internamente chiama
        `get_position` → `get_all_positions` → istanzia la classe Position
        → crasha su 'contractDisplayName' → ritorna None → wrapper risponde
        "No open position found" anche se la posizione esiste davvero.
        
        Endpoint confermato dal source SDK 3.5.9
        (position_manager/operations.py:267):
            POST /Position/partialCloseContract
            { "accountId": int, "contractId": str, "closeSize": int }
        
        Ritorna dict con:
          - success (bool)
          - orderId (str, se success)
          - errorMessage (str, se fail)
          - status_code (int, se errore HTTP)
          - server_response (dict, raw body server per diagnostica)
        Fzadapter not connected)r+  errorMessager   zclose_size must be > 0, got Nrr   r[   zno account_id available)r^  r   	closeSizezF[TSX] _raw_partial_close POST /Position/partialCloseContract contract= size=re  z/Position/partialCloseContractrf  zunexpected response shape)r+  rn  raw: z![TSX] _raw_partial_close failed: T)r+  rn  	exception)r?   rq   r  ra  strrW   rX   rg  r&   rh  r_   typer   r`   )
r   rN   
close_sizerb  rq   rQ  ri  rj  rg   err_strs
             r   _raw_partial_closez"TopstepXAdapter._raw_partial_closeZ  s\    2 {{$6MNN?$8TU_T`6abb#	R[[''F!fnd;9<WS$5$
!',>WXX !_!+. _G HH'=zl<
  --0 .  D $%$6QZ^__  	Ra))*"QC0GII9'CD$gDQQ		Rs_   D<C DAC 2C3C D	C DC 	D7DDDDDc                  K   | j                   sg S | j                  j                         D ci c]  \  }}|d   | }}}|r|hnd}g }| j                          d{   }|s| j	                          d{   }|D ]  }|j                  dd      }	|j                  |	      }|s)|r||vr0|j                  dd      }
t        |j                  dd            }|
dk(  }|r|n| }|j                  ||	|j                  d	      ||t        |j                  d
d            |rdnd||j                  d      d	        |S c c}}w 7 7 ѭw)u  
        Lista posizioni (dict compat V14).
        
        NOTA: La SDK project_x_py 3.5.9 ha un bug noto — la classe Position
        crasha con 'unexpected keyword contractDisplayName' perché ProjectX
        API ha aggiunto campi nuovi. Il logger SDK assorbe l'eccezione e
        ritorna lista vuota silenziosamente (falso negativo pericoloso).
        
        Per questo motivo, saltiamo direttamente al raw REST che bypassa
        la classe Position e legge i campi raw dal JSON.
        rN   Nr    ru  r   sizer9   r[   averagePricer   r:   r;   r^  )	rO   rN   position_idr{  abs_size	avg_priceru  is_buyrb  )	r?   rB   r   rc  rl  ry   ra  rU   r\   )r   rO   rd   rX   contract_to_symbolsymbols_filterresultsraw_positionsr  rN   pos_typesize_valr  signed_sizes                 r   positions_getzTopstepXAdapter.positions_get  sn     {{I
 "3399;
T $
 
 &,&(*
 #99;;"&"<"<">>M 	A%%b1K$((5C#^";uuVQ'H155+,H!mF&,(8)KNN"* uuT{*'$QUU>3%?@(.uF% uu[1
 
	2 Q
 < ?s.   ,EEEEE7E	8CE	Ec                \   K   | j                  |       d {   }t        |      dkD  S 7 w)Nr   )r  len)r   rO   poss      r   has_open_positionz!TopstepXAdapter.has_open_position  s,     &&v..3x!| /s   ,*,c
           
     p  K   | j                   j                  t        |t              r|j	                         n|      }
|
dd| dS | j
                  j                  |      }|sdd| dS |d   }| j                  ||      }| j                  ||      }|d   }| j                  |      }ddl}	 t        j                  d	| d
| d| d|        |j                  j                  ||
t        |             d{   }t        |dd      xs t        |dd      }|s@t        |dd      xs t        |dd      xs d}t        j                  d|        dd| dS t        j                  d|        d}d}ddl}|j#                         dz   }d}|j#                         |k  r|dz  }|j%                  d       d{    	 | j'                  |       d{   }|ra|d   }t)        |j                  dd            }||k\  r<t+        |j                  dd            }d}t        j                  d| d| d|        n|j#                         |k  r|s	 |j                  j1                  t        |             d{   }t        t        |d!d"            }t        j/                  d#| d$|        	 |j                  j3                  t        |             d{    t        j                  d#| d&       d|d(| d)d*S |Y|dkD  rT|
dk(  r| j                  ||||z  z         }n| j                  ||||z  z
        }t        j                  d+| d,| d-|        |,| j                  ||      }t        j                  d.| d/       n[|Y|dkD  rT|
dk(  r| j                  ||||z  z
        }n| j                  ||||z  z         }t        j                  d0| d1| d-|        |
dk(  rj||k\  rAt        j                  d2| d3| d4       | j5                  |       d{    d|d5| d6| d*S ||k  rt        j/                  d7| d8| d9       d}ni||k  rAt        j                  d:| d8| d4       | j5                  |       d{    d|d5| d6| d*S ||k\  rt        j/                  d;| d3| d9       d}|
dk(  rdnd} d}!	 t        j                  d<|        |j                  j7                  || t        |      |=       d{   }"t        |"dd      xs t        |"dd      }!t        j                  d>|!        d}$|~	 t        j                  dG|        |j                  j;                  || t        |      |H       d{   }%t        |%dd      xs t        |%dd      }$t        j                  dI|$        nt        j/                  dL| dM       d||!|$||||	ddN	S 7 7 47 # t,        $ r&}t        j/                  d| d |        Y d}~d}~ww xY w7 # t,        $ r#}t        j/                  d%|        Y d}~d}~ww xY w7 # t,        $ r#}t        j/                  d'|        Y d}~d}~ww xY w7 l7 7 # t,        $ r}t        j                  d?|        t        j                  d@| dA       	 | j5                  |       d{  7   d|dB| d*cY d}~S # t,        $ r4}#t        j9                  dC|# dD       d|dE| dF|# d*cY d}#~#cY d}~S d}#~#ww xY wd}~ww xY w7 # t,        $ r$}t        j                  dJ| dK       Y d}~d}~ww xY w# t,        $ r3}t        j=                  d	| dO|        dt        |      dcY d}~S d}~ww xY ww)Pu  
        Entry market + OCO SL + TP — REST-only implementation (no WS dependency).
        
        ═══════════════════════════════════════════════════════════════════════
        🎯 NON-WS IMPLEMENTATION (v15.1+) + STRUCTURAL-AWARE (v15.2+)
        ═══════════════════════════════════════════════════════════════════════
        
        Il metodo `place_bracket_order` nativo della SDK ASPETTA il fill event
        via WebSocket (timeout 60s). Con WS down → ordine viene davvero fillato
        dal broker ma la SDK lo dichiara "timeout" → orfani → duplicati al
        prossimo iter.
        
        Questa versione bypassa la dipendenza WS:
          1. Piazza market order entry via REST (place_market_order)
          2. Polling REST di positions_get() per confermare fill (1-3s)
          3. RICALCOLA SL/TP dal fill_price reale (se sl_ticks/tp_ticks passati)
          4. Se fill confermato → piazza SL stop order + TP limit order via REST
          5. Ritorna tutti gli IDs come l'API originale
        
        SL/TP Placement (v15.2+):
          - Se `sl_absolute_price` fornito (structural): piazza SL a quel prezzo
            ESATTAMENTE, indipendentemente dal fill_price.
          - Se `sl_ticks` fornito (ATR fallback): piazza SL a fill_price ± sl_ticks × tick
          - Se nulla fornito (legacy): usa sl_price passato as-is (vecchio comportamento, BUGGATO con slippage)
          - TP: sempre ricalcolato da fill_price con tp_ticks se fornito, altrimenti tp_price legacy
        
        Vantaggi:
          - Zero dipendenza WebSocket
          - Latenza fill detection 1-3s vs 60s
          - Zero orfani (se il market order non fillerà, positions_get lo vede)
          - SL/TP ancorati al FILL reale, non al prezzo Brain-time → edge preservato
        ═══════════════════════════════════════════════════════════════════════
        NFzinvalid side: r+  r`   no contract cached for rQ   rN   r   z[TSX] place_market_bracket(z) STEP1: market entry r   u   ct → contract=rN   sider{  orderIdorder_idrn  error_messagezno entry order ID returnedz![TSX] place_market_order failed: zentry order failed: u!   [TSX] ✅ Entry order placed: id=g      .@r9   g      ?r{  r  Tu.   [TSX] ✅ Fill confermato via REST (tentativo z): size=z, avg_price=z*[TSX] positions_get poll error (tentativo z): statusrp   z[TSX] Entry order z non fillato in 15s, status=z![TSX] Cannot check order status: z cancellato (non fillato)z[TSX] Cancel order failed: zentry order z did not fill within 15s)r+  entry_idr`   z#[TSX] TP ricalcolato da fill: fill=z + zt = z[TSX] SL STRUCTURAL @ z (fisso, livello invalidazione)z'[TSX] SL ATR ricalcolato da fill: fill=u    ± u   [TSX] 🚨 SL invalido BUY: sl=z	 >= fill=u    — emergency closezSL on wrong side: sl=z fill=u   [TSX] ⚠️ TP dubbio BUY: tp=z	 <= fill=u    — skip TPu    [TSX] 🚨 SL invalido SELL: sl=u    [TSX] ⚠️ TP dubbio SELL: tp=z [TSX] STEP3: piazzo STOP LOSS @ rN   r  r{  
stop_priceu    [TSX] ✅ Stop order placed: id=u+   [TSX] ⚠️  Stop order placement failed: u   [TSX] 🚨 EMERGENCY CLOSE z: posizione senza SL!z0SL placement failed, emergency-closed position: u'   [TSX] 🚨🚨 EMERGENCY CLOSE FAILED: u"    — intervento MANUALE richiesto!z&SL failed AND emergency close failed: z / z"[TSX] STEP4: piazzo TAKE PROFIT @ rN   r  r{  limit_priceu   [TSX] ✅ TP order placed: id=u#   [TSX] ⚠️  TP placement failed: u$    — posizione ha comunque SL attivoz[TSX] TP skipped per z# (tp_aligned=None dal safety check))	r+  r  stop_id	target_identry_pricesl_pricetp_price	sl_sourcer`   z) unexpected error: )SIDE_MAPry   r&   rt  upperrB   r}   ru   asynciorW   rX   ordersplace_market_orderra  r  r`   r   	monotonicrX  r  r   r\   r_   rk   get_order_by_idcancel_orderclose_positionplace_stop_ordercriticalplace_limit_orderrs  )&r   rO   r  r{  r  r  sl_absolute_pricesl_tickstp_ticksr  side_intrX   rQ   
sl_aligned
tp_alignedrN   re   _aio
entry_respentry_order_iderr
fill_priceposition_found_timepoll_deadliner\  r`  r  pos_sizerg   order_status
status_strsl_side_intstop_order_idsl_respe2target_order_idtp_resps&                                         r   place_market_bracketz$TopstepXAdapter.place_market_bracket  s    \ ==$$Zc5JTZZ\PTU$v/FGG%%))&1$1H/QRR%	 ((:
((:
 =)mmF#K	7 HH26(:PvQtf$4[MC D  #zz<<'Y  =   J %ZDA C$ZTB  "j.$? 3j/4@32  		=cUCD#(5I#3OPPHH88HIJ
 J"N !OO-4MG//#m31jjo%%^&*&8&8&@ @I 'l#&swwvq'9#:#t+).sww{A/F)GJ-1NHH'UV]U^ _--5Jl:,&P Q! //#m3" "I),)C)CCDW)X#XL!$W\8Y%O!PJKK"4^4DD`ak`l mn
C**11#n2EFFFHH1.1AAZ[\
  % .+N+;;ST  #1q=!%!4!4VZ(U^J^=^!_J!%!4!4VZ(U^J^=^!_J>zl#hZW[\f[ghi !,!009JK
1*=\]^%(Q,q=!%!4!4VZ(U^J^=^!_J!%!4!4VZ(U^J^=^!_JB:,dS[R\\`ak`lmn 1}+II ?
|9U_T``tuv--f555',.'<ZLzl%[] ]+KK"A*YWaVbbn op!%J+II @IV`Uaauvw--f555',.'<ZLzl%[] ]+KK"B:,iXbWcco pq!%J  (1}!!K M;J<HI #

 ; ; +$T)	 !< !  !(D A !C 'T B ;M?KL0 #O%mHHA*NO$'JJ$@$@$/( Y$.	 %A % G '.gy$&G 'I&-gz4&H $HH=o=NOP 3F8;^_`  $-,.)))(#
 
m< & @ ! ^KK"LWIUXYZX[ \]]^ $Y ! IKK"CA3 GHHI
 G  CKK"=aS ABBCV 6 6  		GsKL		7x?TUV--f555#($2#STUSV!W 
 ! LL#J2$Np!qr#($2#I!CPRt!T  6 ! mII CA3Fjkllm,  	7MM7x?STUSVWX$s1v66	7s0  B.^61A]7 <W1=A!]7 ^6A!]7  W4]7 W: W7A&W: ]7 ]7 'X/  X,5X/ 7'Y! YY! <	]7 ^6D]7 $Z%]7 4^65A]7 Z]7 ^6.]7 AZ Z8Z 
]7 A] ]8] %]7 0^61]7 4]7 7W: :	X)X$]7 $X))]7 ,X/ /	Y8Y]7 Y]7 Y! !	Z*Z]7 Z]7 ]7 Z 	]"1\<[<([+)[<6]7]7 ;^6<	\9$\4)\9*\<.]/]7 3^64\99\<<]]7 ] 	]4]/)]7 /]44]7 7	^3 (^.(^3)^6.^33^6c	           
     
  K   | j                   j                  t        |t              r|j	                         n|      }	|	dd| dS |	dk(  rdnd}
| j
                  j                  |      }|sdd| dS |d   }| j                  |      }	 t        j                  d	| d
| d| d       |j                  j                  ||
t        |             d{   }t        |dd      xs t        |dd      }|s@t        |dd      xs t        |dd      xs d}t        j                  d|        dd| dS 	 |rE	 |j                  j                  t        |             d{    t        j                  d|        |rE	 |j                  j                  t        |             d{    t        j                  d|        	 | j#                  |       d{   }t        |j                  dd            }|dkD  r.t        j!                  d| d| d|j                  d        d       | j%                  ||      }d}	 t        j                  d#| d$| d%       |j                  j'                  ||
t        |      |&       d{   }t        |dd      xs t        |dd      }|st)        d'      t        j                  d(|        | j%                  ||      }d}	 t        j                  d/| d$| d%       |j                  j/                  ||
t        |      |0       d{   }t        |dd      xs t        |dd      }t        j                  d1|        d4|||||dd5S 7 # t        $ r*}t        j                  d|        dd| dcY d}~S d}~ww xY w7 F# t        $ r&}t        j!                  d| d|        Y d}~Vd}~ww xY w7 4# t        $ r&}t        j!                  d| d|        Y d}~Dd}~ww xY w7 7# t        $ r$}t        j!                  d!| d"       Y d}~d}~ww xY w7 # t        $ rw}t        j                  d)| d*       	 | j+                  |       d{  7   n/# t        $ r#}t        j-                  d+| d,       Y d}~nd}~ww xY wd|d-| d.cY d}~S d}~ww xY w7 # t        $ r$}t        j!                  d2| d3       Y d}~d}~ww xY ww)6u)  V18 12-mag — partial close via opposite market order + bracket rebuild.

        Workaround per `/Position/partialCloseContract` che ritorna 400 su
        alcuni asset ProjectX (6A confermato). Sostituisce la chiamata raw
        con un flusso a 4 step:

          STEP 1: market opposite-direction × contracts_to_close (chiude metà)
          STEP 2: cancel old SL + cancel old TP (bracket originale)
          STEP 3: nuovo STOP @ new_sl_price × residual_contracts
          STEP 4: nuovo LIMIT @ new_tp_price × residual_contracts

        Se STEP 1 fallisce: ritorna fallimento, niente effetti.
        Se STEP 1 ok ma STEP 3 (SL) fallisce: emergency close del residuo
        per non lasciare posizione senza SL.
        STEP 2 e STEP 4 sono best-effort: SL già piazzato copre il rischio.
        NFzinvalid direction: r  r   r9   r  rN   z'[TSX] partial_close_via_opposite_order(z) STEP1: opposite market zct (orig dir=)r  r  r  rn  r  zno order id from closing marketz"[TSX] partial close STEP1 failed: zclosing market failed: z%[TSX] partial close STEP1 exception: zclosing market raised: z,[TSX] partial close STEP2: cancelled old SL z([TSX] partial close STEP2 cancel old SL u#    failed (benigno se già fillato): z,[TSX] partial close STEP2: cancelled old TP z([TSX] partial close STEP2 cancel old TP cancelled_countz,[TSX] partial close STEP2 sweep: cancellati z ordini orfani residui su z (	order_idsz/[TSX] partial close STEP2 orphan sweep failed: u7    (non blocking — new SL at STEP3 protegge il residuo)z$[TSX] partial close STEP3: new SL @ u    × ctr  zplace_stop_order returned no idu   [TSX] ✅ New SL placed: id=u'   [TSX] 🚨 partial close STEP3 FAILED: u&    — residuo senza SL, emergency closeu4   [TSX] 🚨🚨 EMERGENCY CLOSE FAILED post-partial: u    — manuale!z'new SL placement failed after partial: )r+  closing_order_idr`   z$[TSX] partial close STEP4: new TP @ r  u   [TSX] ✅ New TP placed: id=z'[TSX] partial close STEP4 (TP) failed: u    — residuo coperto da SLT)r+  r  new_stop_idnew_target_idnew_sl_pricenew_tp_pricer`   )r  ry   r&   rt  r  rB   ru   rW   rX   r  r  ra  r  r`   r_   r  rk   cancel_all_orders_for_contractr}   r  r>   r  r  r  )r   rO   	directioncontracts_to_closeresidual_contractsr  r  old_stop_order_idold_target_order_identry_side_intopposite_side_intrX   rN   re   
close_respr  r  rg   sweepsweptnew_sl_alignedr  r  r  new_tp_alignedr  r  s                              r    partial_close_via_opposite_orderz0TopstepXAdapter.partial_close_via_opposite_order  s    6 **!+Is!;IOO
 !$1DYK/PQQ!/1!4A!%%))&1$1H/QRR=)mmF#	NHH9& B##5"6mI;aQ  #zz<<'&+,  =   J 
It4 9:z48  $J= 9z?DA98 
 		>seDE#(5LSE3RSS $ jj--c2C.DEEEGHYGZ[\ jj--c2E.FGGGGH[G\]^	==fEEE		"3Q78EqyB5' J))/599[3I2J!M ,,V\B%)	HH6~6FdK]J^^`a  JJ77'&+,)	 8  G D1 67J5  "#DEEHH3K=AB$ ,,V\B'+	HH6~6FdK]J^^`a  JJ88'&+,*	 9  G D1 67J5  HH3M?CD  0&***
 	
s"  	NII=aSAB$1H/LMM	N F >?P>Q R99:=  H >?R>S T99:=  F  	KKA! EI J 	  	II9!<bc))&111 J2$m\ 
 !$4B1#F 	.  	KK9!<VW 	s  B
T A	M4 M1A!M4 8T ='N- $N*%N- T 'O" +O,O" 	P PAP 0T AQ
 QAQ
 T 'AS .S/8S '
T 1M4 4	N'=N"N'T "N''T *N- -	O6OT OT O" "	P+PT PT P 	Q P?9T ?QT Q
 
	S
S-RRRS	R4R/*S/R44S?S
 T S

T S 	S=S82T 8S==T c                   K   | j                  ||      }	 | j                  |      }t        |j                  j	                  t        |      |       d {         S 7 # t        $ r"}t        j                  d|        Y d }~yd }~ww xY ww)N)r  r  z[TSX] modify_sl failed: F)	r}   ru   r   r  modify_orderra  r_   rW   r`   )r   rO   r  new_slr  re   rg   s          r   	modify_slzTopstepXAdapter.modify_sl  s      ,,VV<	--'Ccjj55]+) 6       	II045	s@   B?A  AA  BA   	B)BBBBc                  K   	 | j                  |      }|j                  j                  t        |             d{   }t        j                  d| d|        y7  # t        $ rj}t        |      j                         }d|v sd|v sd|v r t        j                  d| d	|        Y d}~yt        j                  d
| d|        Y d}~yd}~ww xY ww)zO
        Cancella un ordine pendente (usato per cleanup SL/TP orfani).
        Nu   [TSX] ✅ Cancelled order z on Talreadyfilledz	not foundz[TSX] Order u$    già fillato/cancellato (benigno): z[TSX] cancel_order(rH  F)
ru   r  r  ra  rW   rX   r_   rt  lowerrk   )r   rO   r  re   resultrg   err_msgs          r   r  zTopstepXAdapter.cancel_order  s     	--'C::223x=AAFHH1(4xHI B  	!fllnGG#x7':kW>T<z1UVWUXYZKK-hZz!EF	sK   C8A AA CA 	C(A C(C-CCCCc           
     f  K   | j                   j                  |      }|s	dg d| dS |d   }g }	 | j                  j                  }t	        |dd      }|rt	        |dd      nd}|sdg ddS d	t        |      i}|j                  d
d|       d{   }	t        |	t              r|	j                  dg       xs g }
|
D ]k  }t        |t              s|j                  d      xs |j                  d      }||k7  r>|j                  d      }|R|j                  t        |             m |st        j                  d| d       dg ddS t        j                  d| dt        |       d|        d}g }|D ]5  }	 | j!                  ||       d{   }|r|dz  }|j                  |       7 ||ddS 7 '# t        $ r4}t        j                  d| d|        dg t        |      dcY d}~S d}~ww xY w7 g# t        $ r(}t        j                  d| d| d|        Y d}~d}~ww xY ww)a:  
        Cancella TUTTI gli ordini pendenti sul contract (senza bisogno di IDs).
        
        Usato per cleanup orfani quando SL/TP natural-hit chiudono la posizione
        broker-side senza passare per close_position() di V15.
        
        Strategia:
          1. Raw REST POST /Order/searchOpen per listare ordini pendenti
          2. Filtra per accountId e contractId del symbol
          3. Cancella ciascun ordine via ctx.orders.cancel_order
        
        Ritorna:
          {"cancelled_count": int, "order_ids": [int], "error": Optional[str]}
        r   no contract for )r  r  r`   rN   rr   Nr[   zno account_idr^  re  z/Order/searchOpenrf  r  r   z$[TSX] cancel_all: search failed for rr  z[TSX] cancel_all z: nessun ordine pendentez
: trovati u    ordini → r9   z: order 	 failed: )rB   ry   r?   rq   r  ra  rg  r&   rh  rU   r_   rW   rk   rt  rX   r  r  )r   rO   rX   rN   open_ordersrq   rQ  rb  ri  rj  
orders_raworderorder_contractr  rg   	cancelledcancelled_idsoks                     r   r  z.TopstepXAdapter.cancel_all_orders_for_contract  s    " %%))&1'(rFVW]V^D_``=) "$	L[[''F&.$7C58dD1dJ+,2XX"C
O4G--# .  D $%!XXh39r
' 	:E%eT2 %*YY|%<%X		-@XN%4 $yyH+#**3x=9	: HH(0HIJ'(rDII 	$VHJs;7G6HU`Tabc	# 	XHX,,VX>>NI!((2	X  ),#
 	
O$  	LKK>vhbLM'(rCFKK	L ?  X/xxzSTRUVWWXs   .H1;F; ,H1-$F; F8A=F; F; ,AH1>G=G;G=0H18F; ;	G8)G3-G8.H13G88H1;G==	H.H)$H1)H..H1c                &  K   | j                   j                  |      }|sdd| dS |d   }	 || j                  |t        |             d{   }t	        |j                  dd            }|sBt
        j                  d| d	| d
|j                  dd       d|j                  di        d	       |d|d||j                  d      |j                  d      dS | j                  |      }|j                  j                  ||       d{   }	d}
|rR|Pt
        j                  d| d|        |D ]0  }|	 | j                  |t        |             d{   }|r|
dz  }
2 t        |	t              rt	        |	j                  dd            ndd||
|	dS 7 K7 7 E# t        $ r%}t
        j                  d| d|        Y d}~d}~ww xY w# t        $ r3}t
        j                  d| d|        dt!        |      dcY d}~S d}~ww xY ww)u  
        size=None → close full
        size=int  → close partial
        
        bracket_order_ids: lista di order IDs (SL, TP) da cancellare dopo la close.
                           Evita ordini orfani quando chiudiamo la posizione a market
                           invece di far scattare SL/TP naturalmente.
                           Per partial close, NON cancellare (gli SL/TP rimangono
                           validi per la size rimanente).
        Fr  r  rN   N)rN   rv  r+  z[TSX] partial close rp  r  rn  rp   z	 (server=server_responser  partialr   r  )r+  modeclosed_sizer  rq  rn  r  z![TSX] Cleanup bracket orders per rr  r9   z[TSX] cleanup order Tfull)r+  r  r  r  rq  z[TSX] close_position(rH  )rB   ry   rx  ra  r   rW   r`   ru   r`  close_position_by_contractrX   r  r_   rk   r&   rh  rt  )r   rO   r{  bracket_order_idsrX   rN   raw_respr+  re   rj  r  r  r  rg   s                 r   r  zTopstepXAdapter.close_position  sf      %%))&1$1A&/JKK=);	7 !%!8!8 +"4y "9 "  x||Iu=>II.vhfTF)#<<	BC D##+<<0A2#F"GqJ (/'0'+'('/'/||N'C'/||I'>  --'CAA' B  D  O T\<VHBGXFYZ[ 1 LH' L#'#4#4VS]#KK+q0OL GQQUW[F\4D(A#Bbf#)#'#2#' W. L % L&:8*Bqc$JKKL  	7II-fXZsCD$s1v66	7s   +H!G FB
G H1G F-G ;F!FF!%4G HG G F!!	G*G
G 
GG 	H(H	HH	HHc                   K   | j                   sy 	 | j                  |      }t        |dd       }|y |j                  |       d {   }|t	        |      S d S 7 # t
        $ r%}t        j                  d| d|        Y d }~y d }~ww xY ww)NrG   )time_window_minutesz[TSX] get_cumulative_delta(rH  )rC   ru   r  get_cumulative_deltar\   r_   rW   debug)r   rO   r  re   obr  rg   s          r   r  z$TopstepXAdapter.get_cumulative_deltae  s      %%		--'Ck40Bz22w2OOF$*$65=@D@ P 	II3F8:aSIJ	sV   B A BA AA BA BA 	B&BBBBc                   K   | j                   si S 	 | j                  |      }t        |dd       }|i S |j                          d {   xs i S 7 # t        $ r i cY S w xY wwNrG   )rC   ru   r  get_trade_flow_summaryr_   r   rO   re   r  s       r   r  z&TopstepXAdapter.get_trade_flow_summaryu  sl     %%I	--'Ck40Bz	2244::4 	I	D   A#!A A#A AA A#A A A#A  A#c                   K   | j                   sg S 	 | j                  |      }t        |dd       }|g S |j                          d {   xs g S 7 # t        $ r g cY S w xY wwr   )rC   ru   r  get_liquidity_levelsr_   r  s       r   r  z$TopstepXAdapter.get_liquidity_levels  sl     %%I	--'Ck40Bz	00228b82 	I	r  c                   K   | S wrm   r!   rD   s    r   
__aenter__zTopstepXAdapter.__aenter__  s     s   c                @   K   | j                          d {    y 7 wrm   )rj   )r   exc_typeexctbs       r   	__aexit__zTopstepXAdapter.__aexit__  s     oos   )NF)rJ   z	List[str]rK   zOptional[List[str]]ra   r   r   r   )r   r   )r   rt  )rO   rt  )rO   rt  r{   r\   r   r\   )r   pd.DataFramer   Optional[float])rO   rt  r   rt  r   r  r   r   )r  )rO   rt  r   rt  r   ra  r   r  )rO   rt  r   rt  r   ra  r   r  )rO   rt  r   ra  r   ra  r   r  )r   ra  r   r  )rO   rt  r   r  )r  )rO   rt  r   zOptional[Dict[str, Any]])r   Dict[str, Any])r   List[Dict[str, Any]]rm   )rN   rt  rv  ra  rb  Optional[int]r   r  )rO   Optional[str]r   r  )rO   rt  r   r   )NNNATR)rO   rt  r  rt  r{  ra  r  r\   r  r\   r  r  r  r  r  r  r  rt  r   r  )NN)rO   rt  r  rt  r  ra  r  ra  r  r\   r  r\   r  r  r  r  r   r  )rO   rt  r  ra  r  r\   r   r   )rO   rt  r  ra  r   r   )rO   rt  r   r  )rO   rt  r{  r  r  zOptional[List[int]]r   r  )r   )rO   rt  r  ra  r   r  )rO   rt  r   r  )*r   r   r   r    r   r  rE   rh   rj   rn   rY   ru   r}   r   r   r   r   r   r   r   r  r   rD  r  r3  rR  rc  rl  rx  r  r  r  r  r  r  r  r  r  r  r  r  r  r!   r   r   r,   r,   y   s    M$ !a0H&$ +/!&	?? (? 	?
 
?B';#
0 1rB#TqB	N"-` 9<k8k8&)k825k8	k8Z00"0+.0	0 (0(0-0(09<(0	(0T<:0Ob?B"H2
>@F %)	BRBR BR "	BR
 
BRJ '+:#:	:x .2"&"&N7N7 N7 	N7
 N7 N7 +N7  N7  N7 N7 
N7p ,0-1o
o
 o
  	o

  o
 o
 o
 )o
 +o
 
o
b*-7<	%(	(L
L
	L
b #15	P7P7 P7 /	P7
 
P7j +-$'	 

 r   r,   )r    
__future__r   r#   typingr   r   r   r   r2  r   project_x_pyr   project_x_py.modelsr	   r
   r=   r   broker._sdk_patchesr   orderbook_patchr   r$   rW   Filterr   r*   r,   r!   r   r   <module>r     s   $J #  , , )BN 5	5 g&"w~~ "J'Z  Z Q  NL  		s"   B  B  	BBBB