
    Xj5                     d   d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZmZmZ  ed      ZdZ G d d	      Zed
k(  rg e       Z edej+                         j-                  dd      d        edej/                  ddd              ed eej3                                      yy)a  
Bybit Demo REST client (custom, bypass ccxt bug con api-demo.bybit.com).
Implementa solo i metodi necessari al live engine:
  - fetch_ohlcv(symbol, interval, limit)
  - fetch_balance()
  - fetch_positions(symbol)
  - create_order(symbol, side, qty)  market entry
  - create_reduce_order(symbol, side, qty, price)
  - set_trading_stop(symbol, sl_price)
Testato: HMAC manuale funziona, totalEquity 48462 USDT.
    N)Path)load_dotenv)OptionalListDictz/opt/charter/.envzhttps://api-demo.bybit.comc                   ~   e Zd Zd ZdededefdZd*deded	ed
edef
dZd+dedede	de
fdZdedefdZdededefdZdefdZd,dede
fdZdedededefdZ	 d-dedededededefdZ	 	 	 	 	 d.dedededededededefd Zded!edefd"Zd,dede
fd#Zd,ded$e
de	fd%Z	 	 d/ded&e	ded'edef
d(Zdedefd)Zy)0BybitDemoClientc                    t        t               t        j                  d      | _        t        j                  d      | _        | j                  r| j
                  st        dt               t        j                         | _	        y )NBYBIT_DEMO_API_KEYBYBIT_DEMO_SECRET_KEYzCredenziali mancanti in )
r   ENV_FILEosgetenvapi_keysecretRuntimeErrorrequestsSessionsession)selfs    -/opt/charter/live_deploy/bybit_demo_client.py__init__zBybitDemoClient.__init__   s\    Hyy!56ii 78||4;;!9(DEE'')    bodytsreturnc                     | | j                    d| }t        j                  | j                  j	                         |j	                         t
        j                        j                         S )zBybit V5 signature: timestamp + api_key + recv_window + body.
        body = querystring sorted (GET) oppure JSON body raw come inviato (POST).5000)r   hmacnewr   encodehashlibsha256	hexdigest)r   r   r   sign_strs       r   _signzBybitDemoClient._sign#   sP     T$,,tD62xx**,hoo.?PZZ\\r   Nmethodpathparamssignedc           
         |xs i }ddi}t         |z   }d}d}|rt        t        t        j                         dz              }	|dk(  r'dj	                  d |j                         D              }nt        j                  |d	      }| j                  |d
<   | j                  ||	      |d<   |	|d<   d|d<   |dk(  r | j                  j                  |||d      }
n4|dk(  r | j                  j                  |||d      }
nt        d| d      |
j                         }|j                  dd      dk7  r5t        d|j                  d       d|j                  d       d|d d        |S )NzContent-Typezapplication/json i  GET&c              3   0   K   | ]  \  }}| d |   yw)=N ).0kvs      r   	<genexpr>z+BybitDemoClient._request.<locals>.<genexpr>6   s     (O1A3as(Os   )z, : )
separatorszX-BAPI-API-KEYzX-BAPI-SIGNzX-BAPI-TIMESTAMPr   zX-BAPI-RECV-WINDOW
   )r)   headerstimeoutPOST)jsonr9   r:   zMethod z not supportedretCoder   z
Bybit err r6   retMsgz | body_for_sign=   )BASE_URLstrinttimejoinitemsr<   dumpsr   r&   r   getpost
ValueErrorr   )r   r'   r(   r)   r*   r9   urlbody_for_signbody_rawr   rdatas               r   _requestzBybitDemoClient._request)   ss   2!#56oSt+,-B
 !$(O(O O !%

6l K(,G$%%)ZZr%BGM"*,G&',2G()U?  VWb QAv!!#FGR!PAwvhn=>>vvx88Iq!Q&DHHY,?+@488HCUBVVghuvzwzh{g|}~~r   symbolintervallimitc                 B   d|t        |      t        |      d}| j                  dd|d      }t        |d   d         D cg c]R  }t        |d	         t	        |d
         t	        |d         t	        |d         t	        |d         t	        |d         gT c}S c c}w )zHsymbol: 'ZECUSDT' (senza /). interval: '240' (4h), '60' (1h), '15', etc.linear)categoryrP   rQ   rR   r-   z/v5/market/klineFr*   resultlistr                  )rA   rO   reversedrB   float)r   rP   rQ   rR   r)   rN   cs          r   fetch_ohlcvzBybitDemoClient.fetch_ohlcvJ   s    &&c(m^abg^hi}}U$6u}M!$x."89; QqTE!A$KqteAaDk51;PUVWXYVZP[\ ; 	; ;s   ABc                    	 | j                  ddd|dd      }|d   d   D ]E  }|j                  d	      |k(  s|j                  d
i       }t        |j                  dd            c S  	 y# t        $ r
}Y d}~yd}~ww xY w)zQRitorna lo step minimo di qty per il simbolo (es. 1.0 per AERO, 0.001 per altri).r-   z/v5/market/instruments-inforT   )rU   rP   FrV   rW   rX   rP   lotSizeFilterqtyStepg      ?N)rO   rG   r_   	Exception)r   rP   rN   instlotes         r   get_qty_stepzBybitDemoClient.get_qty_stepQ   s    	==(E/76"JSX ! ZDXv. :88H%/((?B7C C!899:   		s   7A, .A, )A, ,	A?:A?qtyc                    | j                  |      }|dk\  rt        t        |            S dt        |      v r5t	        t        |      j                  d      j                  d      d         nd}t        |||z  z
  |      S )z2Arrotonda qty al multiplo del qtyStep del simbolo.rY   .0r   )ri   r_   rB   rA   lenrstripsplitround)r   rP   rj   stepdecimalss        r   	round_qtyzBybitDemoClient.round_qty^   st      (19S?"@Cs4y@P3s4y'',2237;<VWSC$J'22r   c                 Z    | j                  ddddid      }|d   d   r|d   d   d	   S i S )
Nr-   z/v5/account/wallet-balanceaccountTypeUNIFIEDTrV   rW   rX   r   rO   )r   rN   s     r   fetch_balancezBybitDemoClient.fetch_balancef   sI    }}U$@,i8  G,0N6,BtH~f%a(JJr   c           	          ddd}|r||d<   | j                  dd|d      }|d	   d
   D cg c]'  }t        |j                  dd      xs d      dkD  s&|) c}S c c}w )NrT   USDTrU   
settleCoinrP   r-   /v5/position/listTrV   rW   rX   sizer   )rO   r_   rG   )r   rP   r)   rN   ps        r   fetch_positionszBybitDemoClient.fetch_positionsk   sj    &f=%F8}}U$7}M>&1VaU155;K;Pq5QTU5UVVVs   'AAsidec                 |    |j                         dv rdnd}d||dt        |      dd}| j                  dd	|d
      S )zFside: 'long'/'short' oppure 'buy'/'sell'. Bybit V5 vuole 'Buy'/'Sell'.buylongBuySellrT   MarketGTC)rU   rP   r   	orderTyperj   timeInForcer;   /v5/order/createTrV   lowerrA   rO   )r   rP   r   rj   
bybit_sider)   s         r   create_market_orderz#BybitDemoClient.create_market_orderr   sH    "jjlo=U6
&&*'CP}}V%7}MMr   pricereduce_onlyc           	          |j                         dv rdnd}d||dt        |      t        |      d|d}| j                  dd	|d
      S )Nr   r   r   rT   Limitr   )rU   rP   r   r   rj   r   r   
reduceOnlyr;   r   TrV   r   )r   rP   r   rj   r   r   r   r)   s           r   create_limit_orderz"BybitDemoClient.create_limit_ordery   sS    "jjlo=U6
&&*&s3x#e*!&kC }}V%7}MMr   sl_pricetp_pricetrailing_stopactive_pricesl_trigger_bytp_trigger_byc                     d|dd}|t        |      |d<   ||d<   |t        |      |d<   ||d<   |t        |      |d<   |t        |      |d	<   t        |      d
k  rt        d      | j                  dd|d      S )uS  Bybit V5 /v5/position/trading-stop — imposta SL, TP, trailing stop nativi
        sulla posizione corrente. Eseguiti DALL'EXCHANGE anche se il bot è offline.

        BUG_005 fix: aggiunti parametri opzionali per supportare Charter enforcement:
          - tp_price: take profit nativo (singolo prezzo, riduce TUTTA la posizione)
          - trailing_stop: distanza trailing (es. "0.5" = 0.5% sotto mark)
          - active_price: prezzo di attivazione trailing (es. entry*1.03 = dopo TP1)
        Mantiene backward-compat: set_trading_stop(symbol, sl_price) funziona come prima.
        NB: Bybit consente UN SOLO takeProfit nativo per posizione. Per split 50/50
        si usa TP medio pesato, oppure si combinano TP nativo (riduzione totale 50%)
        + ordine reduceOnly residuo (NO — vedi fix live_engine per la strategia scelta).
        rT   r   )rU   rP   positionIdxstopLossslTriggerBy
takeProfittpTriggerBytrailingStopactivePricer[   zGset_trading_stop: specificare almeno sl_price, tp_price o trailing_stopr;   z/v5/position/trading-stopTrV   )rA   ro   rI   rO   )	r   rP   r   r   r   r   r   r   r)   s	            r   set_trading_stopz BybitDemoClient.set_trading_stop   s    " '&K!$XF:$1F=!#&x=F< $1F=!$%(%7F>"#$'$5F=!v;!fgg}}V%@&QU}VVr   order_idc                 8    d||d}| j                  dd|d      S )zLCancella un singolo ordine pending per symbol. Usato per cleanup reduceOnly.rT   )rU   rP   orderIdr;   z/v5/order/cancelTrV   ry   )r   rP   r   r)   s       r   cancel_orderzBybitDemoClient.cancel_order   s&    &&XN}}V%7}MMr   c                     ddd}|r||d<   | j                  dd|d      }|j                  d	i       j                  d
g       S )zFRitorna lista ordini pending (Untriggered/New). Se symbol=None, tutti.rT   r|   r}   rP   r-   z/v5/order/realtimeTrV   rW   rX   rO   rG   )r   rP   r)   rN   s       r   get_open_orderszBybitDemoClient.get_open_orders   sN    &f=%F8}}U$8&}Nxx"%))&"55r   keep_order_idsc                    t        |xs g       }d}	 | j                  |      }|D ]v  }|j                  d      }t        |j                  dd            j	                         dk(  }|j                  dd      }	||v rW|sZ|	dv s_	 | j                  ||       |dz  }x |S # t        $ r}
t        d	| d
|
        Y d}
~
d}
~
ww xY w# t        $ r}
t        d|
        Y d}
~
yd}
~
ww xY w)a  BUG_005 FIX 3: cancella tutti i reduceOnly pendenti per symbol, tranne quelli in keep_order_ids.
        Usato come cleanup PRIMA di piazzare nuovi TP/SL, per evitare conflitti.
        Ritorna numero ordini cancellati.
        NB: NON cancella gli stop/positions Bybit V5 nativi (non sono ordini separate,
        sono campi sulla posizione). Cancella SOLO ordini 'order' veri con reduceOnly=True.
        r   r   r   r,   trueorderStatus)NewUntriggeredPartiallyFilledrY   z  [WARN] cancel_order z
 fallita: Nz  [WARN] cancel_reduce_orders: )setr   rG   rA   r   r   re   print)r   rP   r   keep	cancelledordersooid	is_reducestatusrh   s              r   cancel_reduce_ordersz$BybitDemoClient.cancel_reduce_orders   s    >'R(		))&1F KeeI&lB 78>>@FJ	}b1$;+T!TK))&#6!Q	K  % K 6se:aSIJJK  	3A378	sH   A,C  C B!C !	C*C ;C  CC 	C)C$$C)leveragebypass_if_alreadyc                 d   |rG	 | j                  |      }|j                  d      }|"t        |      t        |      k(  rdd| di ddS d|t        |      t        |      d	}	 | j	                  d
d|d      S # t        $ r Y :w xY w# t
        $ r}dt        |      v rddi dcY d}~S  d}~ww xY w)a  Imposta la leva per il simbolo PRIMA di aprire un ordine.
        Charter Mattia 12/07: TARGET 3x per TUTTI gli asset.
          - Se l'asset e' GIA' a 3x su Bybit: BYPASSA, NON chiamare set_leverage.
          - Altrimenti (leva attuale diversa da 3x): chiama set_leverage(3).

        Comportamento:
          - bypass_if_already=True (default): legge get_leverage() prima; se gia' al
            target, ritorna {"retCode": 0, "retMsg": "bypassed (already at target)",
            "result": {}, "bypassed": True}.
          - bypass_if_already=False: chiama sempre set_leverage. Bybit ritorna 110043
            "leverage not modified" se gia' al target (gia' gestito come OK).

        Bybit V5 richiede SIA buyLeverage CHE sellLeverage (err 10001 se manca uno).
        r   Nr   zbypassed (leverage already at zx)T)r=   r>   rW   bypassedrT   )rU   rP   buyLeveragesellLeverager;   z/v5/position/set-leveragerV   110043iۭ z)leverage not modified (already at target))r=   r>   rW   )get_leveragerG   rA   re   rO   r   )	r   rP   r   r   r   currentcurrent_levr)   rh   s	            r   set_leveragezBybitDemoClient.set_leverage   s      ++F3%kk*5*s;/?3x=/P#$$B8*B"O"$$(	  '&!$XHO	==)DfUY=ZZ    	3q6!#)5`lnoo		s6   AA9 $B 9	BB	B/B*#B/)B**B/c                    | j                  ddd|ddd      }i }|j                  di       j                  d	g       D ]}  }|j                  d
      |k(  s|j                  d      |d<   |j                  d      |d<   |j                  d      |d<   |j                  d      |d<   |j                  d      |d<    |S  |S )u~  Ritorna la leva corrente per il simbolo. Richiede posizione aperta
        o che la leva sia stata precedentemente impostata. Ritorna dict con
        'leverage', 'avgPrice', 'size', 'side', 'unrealisedPnl'.
        NOTA firma: ordine querystring DEVE essere (category, symbol, settleCoin) — Bybit
        non accetta sort alfabetico (vedi err 10004). Manteniamo ordine esplicito.r-   r   rT   r|   )rU   rP   r~   TrV   rW   rX   rP   r   avgPricer   r   unrealisedPnlr   )r   rP   rN   rW   r   s        r   r   zBybitDemoClient.get_leverage   s     }}U$7+3vU[\%)  + (B'++FB7 	AuuX&(%&UU:%6z"%&UU:%6z"!"vv!"vv*+%%*@'	 r   )NF)r?   )N)T)NNNN	MarkPricer   )r   T)__name__
__module____qualname__r   rA   r&   dictboolrO   rB   rX   ra   r_   ri   ru   rz   r   r   r   r   r   r   r   r   r   r1   r   r   r	   r	      s   *]# ]3 ]3 ]s # t D ]a B;# ; ;S ;4 ;3 5 3 3% 3E 3Kt K
Wc WT WN# NS Nu N N 15N NC Ne NE N)-N9=N ?CIM04/:/:	Ws We W$)WAFW(-W *-W *-	W @D	W@N3 N# N$ N
6c 6T 63  PS 8 DJ04(3 (# (S ()-(9=(T3 4 r   r	   __main__zBalance:totalEquityzN/Ar|   zOHLCV ZEC 4H (last 2):ZECUSDT   rZ   zOpen positions:)__doc__r   rC   r   r"   r<   r   pathlibr   dotenvr   typingr   r   r   r   r@   r	   r   r`   r   rz   rG   ra   ro   r   r1   r   r   <module>r      s   
 
        ' '#$'l l^ zA	*aoo'++M5A6J	
"AMM)S!$DE	
S!2!2!456 r   