
    i                        d Z ddlmZmZ ddlmZm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 ddlmZmZ dd	lmZmZmZmZmZmZmZmZmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7m8Z8m9Z9m:Z:m;Z;m<Z<m=Z=m>Z>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZO e G d
 d             ZPe G d d             ZQe G d d             ZR G d d      ZS G d d      ZTy)u  
Hedge-Mart Recovery Engine.

Port of Auto Hedge-Mart-V4.mq4 strategy to Python backtesting framework.

Architecture mapping from MQ4:
  Ors struct       → SubOrderPair (additional buy+sell pair at same level)
  Order struct     → RecoveryOrderPair (main hedged buy+sell at a recovery level)
  Odr class        → RecoveryThread (one recovery grid sequence)
  OnTick entries   → HedgeMartStrategy (entry signals + thread management)
    )	dataclassfield)ListOptional)deque)normalize_lot_sizeget_pip_size)BacktestOrderEngine	OrderSide)OHLCV)
get_logger	log_trade);SYMBOLLOT_SIZEMAX_INITIAL_ORDERSENTRY_TP_PIPSENTRY_SL_PIPSENABLE_REVERSAL_ENTRIESENABLE_CONTINUATION_ENTRIESFIBO_BARS_BACKFIBO_BUY_ZONEFIBO_SELL_ZONEFIBO_RANGE_ZONERECOVERY_FIBO_ZONERECOVERY_GRID_STEPSRECOVERY_TPSTP_MULTIPLIERTP_MULT_START_LEVELTP_MULT_STOP_LEVELAUTO_CALCULATE_LOTSRECOVERY_PROFIT_TARGETRECOVERY_LOT_MULTIPLIEROUTSIDE_LOT_MULTIPLIERINSIDE_LOT_MULTIPLIEROUTSIDE_LOT_STARTOUTSIDE_LOT_STOPINSIDE_LOT_STARTINSIDE_LOT_STOPREPEAT_BOTH_SIDESREPEAT_SINGLE_SIDESMAX_LEVERAGEMARGIN_CAP_ENABLEDMARGIN_RESERVE_PCTPROGRESSIVE_RLM_ENABLEDPROGRESSIVE_RLM_STARTPROGRESSIVE_RLM_RAMPPROGRESSIVE_RLM_MAXMARGIN_RELIEF_ENABLEDMARGIN_RELIEF_LEVELMARGIN_RELIEF_CLOSE_SUBSMIN_ENTRY_SPACINGANTI_MART_ENABLEDANTI_MART_LOT_PCTANTI_MART_TP_PIPSANTI_MART_REOPENANTI_MART_MAX_LEVELSANTI_MART_LOT_MULTDYNAMIC_MO_ENABLEDDYNAMIC_MO_LOW_THRESHDYNAMIC_MO_HIGH_THRESHDYNAMIC_MO_MININVENTORY_BIAS_ENABLEDINVENTORY_BIAS_THRESHOLDBAR_REVERSAL_MODETHREAD_PROFIT_ENABLEDTHREAD_PROFIT_TARGETADAPTIVE_GRIDADAPTIVE_ATR_BARSREFERENCE_ATRADAPTIVE_MIN_SCALEADAPTIVE_MAX_SCALEc                       e Zd ZU dZdZeed<   dZeed<   dZe	ed<   dZ
e	ed<   dZeed	<   dZeed
<   dZeed<   dZeed<   dZe	ed<   dZe	ed<   y)SubOrderPairzAdditional order pair at same level with its own TP.
    Maps to MQ4 Ors struct.

    Each recovery level can have multiple sub-order pairs (recoveryTP2, TP3, TP4)
    that trade independently with their own TP and repeat logic.
    r   buy_order_idsell_order_id        tp_pipslevel_priceF
buy_tp_hitsell_tp_hit	is_closedlast_directionbuy_lots	sell_lotsN)__name__
__module____qualname____doc__rL   int__annotations__rM   rO   floatrP   rQ   boolrR   rS   rT   rU   rV        7/var/www/html/crpytotradingbot/hedge_recovery_engine.pyrK   rK   7   sm     L#M3GUKJKItNCHeIur`   rK   c                       e Zd ZU dZdZeed<   dZeed<   dZeed<   dZ	e
ed<   dZe
ed<   dZe
ed	<   d
Zeed<   dZeed<   dZe
ed<   y)AntiMartHedgea  Anti-martingale hedge grinder at a grid level.
    
    Ported from AntiMartingaleEA.mq4. Opens a small opposite-direction
    order with tight TP. On TP hit, immediately reopens at current price.
    Generates continuous small profits to offset martingale recovery costs.
    r   levelorder_id	directionrN   lotsentry_pricerO   T	is_activereopen_counttotal_profitN)rW   rX   rY   rZ   rd   r[   r\   re   rf   rg   r]   rh   rO   ri   r^   rj   rk   r_   r`   ra   rc   rc   K   sb     E3NHcIsD%KGUItL#L%r`   rc   c                       e Zd ZU dZdZeed<   dZeed<   dZe	ed<   dZ
e	ed<   dZe	ed<   dZe	ed	<   dZe	ed
<   dZeed<   dZeed<    ee      Zee   ed<    ee      Zee   ed<   y)RecoveryOrderPaira  Main hedged order pair at a recovery level.
    Maps to MQ4 Order struct.

    When a grid level is crossed, BOTH a buy and sell are opened simultaneously.
    The pair has a shared TP distance and links to the previous level's orders
    for cascade closing.
    r   rL   rM   rN   rO   	buy_entry
sell_entryrU   rV   buy_close_idsell_close_id)default_factory	sub_pairsold_sub_pairsN)rW   rX   rY   rZ   rL   r[   r\   rM   rO   r]   rn   ro   rU   rV   rp   rq   r   listrs   r   rK   rt   r_   r`   ra   rm   rm   ^   s     L#M3GUIuJHeIuL#M3$)$$?ItL!?(-d(CM4%Cr`   rm   c                      e Zd ZdZ	 	 	 	 d7dedededededed	ed
edee   dee   dedefdZd Z	dedede
defdZdededefdZdededede
def
dZdede
defdZ	 d8dede
defdZ	 d9dede
ded ed!ef
d"Zd#ed$ede
fd%Z	 d8d#ed$ede
defd&Z	 d8d#ed$ede
defd'Zde
fd(Zde
fd)Zd*ed#ed$ede
fd+Z	 d8d*ede
defd,Zde
defd-Zde
fd.Zde
d/efd0Zd1ede
fd2Zdede
fd3Zdede
fd4Zde
de fd5Z!de
defd6Z"y):RecoveryThreadu#  One recovery grid sequence tracking an initial trade.

    When an initial trade is opened, a RecoveryThread is created. It builds
    a grid of price levels above and below the entry. When price crosses
    a level, it opens hedged buy+sell pairs. Each pair's lots are sized to
    cover the previous level's loss plus a fixed profit target.

    The grid levels are CUMULATIVE from entry using the step array:
      Level  1 = entry ± step[0] pips
      Level  2 = Level 1 ± step[1] pips
      Level  3 = Level 2 ± step[2] pips
      ...
    Ninitial_order_idrh   lot_sizerf   magicrecovery_tp_pipssymbolpip_size
grid_stepsrecovery_tpsentry_tp_pips	vol_scalec                 :   || _         || _        || _        || _        || _        || _        || _        || _        d| _        || _	        |
|
nt        t              | _        ||nt        | _        d| _        d| _        d| _        d| _        g | _        |	t(        }	|	| _        i | _        |}|}t/        |	      D ]M  \  }}||z  }||z  }||z  }t1        |d      | j,                  |dz   <   t1        |d      | j,                  |dz    <   O g | _        t5               | _        y )NTr   F      )rx   rh   ry   rf   rz   take_profit_pipsr|   r}   ri   r   ru   r   r   r   r   current_levellevel_countholdthread_modeanti_mart_hedgesr   r~   levels	enumerateroundorder_pairsr   logger)selfrx   rh   ry   rf   rz   r{   r|   r}   r~   r   r   r   p_upp_dnistep
step_prices                     ra   __init__zRecoveryThread.__init__   s:    !1& "
 0 " -9,DL$|J\.;.G]]	  68 ,J$ , 	3GAtJJDJD!&tQDKKA$)$NDKK!a%!	3 57 lr`   c                     | j                   dk(  rd}d}n,| j                   dz   }| j                   dz
  }|dk(  rd}|dk(  rd}| j                  j                  |d      }| j                  j                  |d      }||||fS )zGet the next up and down level IDs and prices.

        Navigates the grid: from current_level, next up = current+1,
        next down = current-1, skipping level 0 (entry point).
        r   r   rN   )r   r   get)r   up_iddn_idup_pricedn_prices        ra   get_next_levelszRecoveryThread.get_next_levels   s     "EE&&*E&&*Ezz;;??5#.;;??5#.hx//r`   
order_type	in_or_outorder_enginereturnc                    t        | j                        }t        r|dk(  rj| j                  t        k\  rW| j                  t
        k  rD|dkD  r*| j                  | j                  d   |      }|t        z  }n^| j                  t        z  }nI|dk(  rh| j                  t        k\  rU| j                  t        k  rB|dkD  r)| j                  | j                  d   |      }|t        z  }n| j                  t        z  }n|dkD  r"| j                  | j                  d   |      }|}n| j                  }n| j                  | j                  z  }|dkD  r?| j                  | j                  d   |||      }|dkD  r|t        z   |z  n| j                  }nK|| j                   k(  r| j                  }n/| j#                  ||      }|dkD  r|t        z   |z  n| j                  }t        sn|| j                  kD  r_t$        rG| j                  }	|	t&        k\  rDt(        r |	t&        z
  }
t+        d|
dz  z   t,              }nt,        }||z  }nt.        dk7  r	|t.        z  }t0        rs|q|j3                         }|dkD  r\|dt4        z
  z  }|j7                         }t9        d|t:        z  |z
        }t=        |d      r|j>                  nd}|dkD  r||z  }||kD  ryt9        || j                        }tA        || jB                        S )	u  Calculate lot size for recovery order.

        MQ4 GetLots() port. Two modes:
        - autocalculatelots=true: multiplier-based (prev lots × multiplier)
        - autocalculatelots=false: loss-recovery (cover loss + fixed profit)

        Cross-reference: getLotSize(type) in MQ4 uses type==1→buyTicket, else→sellTicket.
        So when calculating BUY lots (type=0), it references the previous SELL lots,
        and vice versa. This is intentional for hedging.

        Args:
            order_type: 0=buy, 1=sell
            in_or_out: 0=inside (same dir as break), 1=outside (opposite)
            order_engine: For price/position lookups
        r   r   r         ?g?_current_bid順 rN   )"lenr   r    r   r%   r&   _get_cross_lotsr#   ry   r'   r(   r$   r   r}   _get_loss_at_tpr!   rf   _get_initial_loss_at_tpr.   r/   r0   minr1   r"   r,   
get_equityr-   get_margin_usedmaxr+   hasattrr   r   r|   )r   r   r   r   s	prev_lotsrg   tp_price_distlossdepthramp_levelsrlmequityusable_equitymargin_usedavailable_margincurrent_pricerequired_margins                     ra   get_lotszRecoveryThread.get_lots   s       !Q((,==((+;;q5 $ 4 4T5E5Eb5I: VI$'==D==+AADq.((,<<((?:q5 $ 4 4T5E5Eb5I: VI$'<<D==+@@Dq5 $ 4 4T5E5Eb5I: VI$D==D !11DMMAM1u++D,<,<R,@*m]ijJWZ[J[55Faeanan/==D77|TDN[^_N_D#99]JeiererD #tdmm';&((11+&+.C&C!#c(9"9;NO1#:D(C/55 ,":!,,.Fz &#0B*B C*::<#&q-,*F*T#U =D\Sa=b 9 9hn 1$&*]&:O&)99"4'!$44r`   pairc                 <    |dk(  r|j                   S |j                  S )u.  Get lot size from previous pair with MQ4 cross-reference.

        MQ4 getLotSize(type): type==1 → buyTicket lots, else → sellTicket lots.
        So for calculating BUY (type=0) → returns SELL lots from previous.
        For calculating SELL (type=1) → returns BUY lots from previous.
        r   )rU   rV   )r   r   r   s      ra   r   zRecoveryThread._get_cross_lots3  s     ?== >>!r`   tp_distc                 z   |j                         \  }}|dk(  rR||z   }|j                  j                  |j                        }|r%|j                  t        |j                  |z
        z  S y||z
  }|j                  j                  |j                        }|r%|j                  t        |j                  |z
        z  S y)z6Calculate projected loss of opposite side at TP price.r   rN   )get_current_price	positionsr   rM   rg   absrh   rL   )	r   r   r   r   r   bidask	tp_targetposs	            ra   r   zRecoveryThread._get_loss_at_tp?  s      113S?gI((,,T-?-?@Cxx#coo	&A"BBB 	 gI((,,T->->?Cxx#coo	&A"BBBr`   c                     |j                   j                  | j                        }|rR|j                         \  }}| j                  dk(  r||z   }n||z
  }|j
                  t        |j                  |z
        z  S y)z5Calculate loss from the initial order at TP distance.r   rN   )r   r   rx   r   rf   rg   r   rh   )r   r   r   r   r   r   r   s          ra   r   z&RecoveryThread._get_initial_loss_at_tpO  sq    $$(()>)>?#557HC~~"'M	'M	88c#//I"=>>>r`   break_direction
fibo_valuec                    | j                   t        k\  r,| j                   t        k  r| xj                  t        z  c_        |j                         \  }}|dk(  rdnd}|dk(  rdnd}| j                  d||      }| j                  d||      }	|dk(  s|	dk(  ry|j                  || j                  d| j                    d      }
|j                  |	| j                  d| j                    d      }t        |
r|
j                  nd|r|j                  nd| j                  |
r|
j                  n||r|j                  n|||		      }t        \  }}||cxk  xr |k  nc }|}t        | j                  dd d      D ]  \  }}|dkD  s|s|j                  || j                  d| j                    d
| d      }|j                  |	| j                  d| j                    d
| d      }|j                   j#                  t%        |r|j                  nd|r|j                  nd||||	              t'        | j(                        }|dkD  rd| j(                  d   }|j*                  |_        |j.                  |_        t3        |j                         |_        |j                   D ]	  }d|_         n"| j8                  |_        | j8                  |_        | j(                  j#                  |       t:        r:t'        | j<                        t>        k  r| jA                  | j                   |||       tC        d| jD                  |
r|
j                  nd|| j                         y)a  Open hedged buy+sell pair at current level.

        MQ4 addOrder() port. Opens main pair + sub-order pairs (Ors).
        MQ4 line 906-920: sub-pairs only created when Fibo condition met.

        Args:
            break_direction: 0=price went UP, 1=price went DOWN
            order_engine: For placing orders
            fibo_value: Current Fibo zone value for sub-pair gate
        r   r   rN   NREC_L_BUYrg   rz   comment_SELL)rL   rM   rO   rn   ro   rU   rV   _S)rL   rM   rO   rP   rU   rV   r   T
HEDGE_PAIR)#r   r   r   r   r   r   r   place_buy_orderrz   place_sell_orderrm   re   pricer   r   r   rs   appendrK   r   r   rL   rp   rM   rq   ru   rt   rS   rx   r6   r   r:   _open_anti_mart_hedger   r|   )r   r   r   r   r   r   
buy_in_outsell_in_outrU   rV   	buy_order
sell_orderr   fibo_minfibo_maxsub_fibo_okrP   r   sub_tpsub_buysub_sellr   prevsps                           ra   	add_orderzRecoveryThread.add_order[  sF     33$$'99!!]2!113S *Q.QA
*a/aQ==J=MM![,?	 s?i3. !00D,,-T2 1 
	 "22$**D,,-U3 3 


 !/8++a1;*--)))2ioo+5z''3
 0(*88"4#4#4QR#8!< 	IAvzk&66!#D$4$4#5Rs$? 7  (88"$**#D$4$4#5Rs%@ 9  %%l5<!1!1!7?("3"3Q" +%'' 	(   !q5##B'D $ 1 1D!%!3!3D!%dnn!5Dnn $#$ !% 5 5D!%!6!6D% T%:%:!;>R!R&&t'7'7sCP,%.)//ADJJ	(r`   rd   r   r   rj   c                    t         t        z  }|t        t        d|dz
        z  z  }t	        || j
                        }| j                  dk(  r%|j                  || j                  d| d|       }n$|j                  || j                  d| d|       }|rX| j                  dk(  rdnd}	| j                  j                  t        ||j                  |	||j                  t        d|             y	y	)
zOpen a small opposite-direction order with tight TP.
        
        Ported from AntiMartingaleEA.mq4 OpenHedgeOrder().
        Direction is OPPOSITE to the thread's initial direction.
        r   r   AMT_L_SELL_Rr   _BUY_RT)rd   re   rf   rg   rh   rO   ri   rj   N)r   r7   r;   r   r   r|   rf   r   rz   r   r   r   rc   re   r   r8   )
r   rd   r   r   r   rj   base_lot
hedge_lotsorder	hedge_dirs
             ra   r   z$RecoveryThread._open_anti_mart_hedge  s     //!3s1eai7H!HI
'
DKK@
 >>Q 11tzzwgl^< 2 E
 !00tzzwf\N; 1 E
 !^^q0aI!!((#!KK))	* 	 r`   highlowc                 2   t         r| j                  sy|j                         \  }}g }| j                  D ]  }|j                  s|j                  j                  |j                        }|d|_        @|j                  | j                  z  }	d}
|j                  dk(  r|j                  |	z   }||k\  rd}
n|j                  |	z
  }||k  rd}
|
s|j                  |j                        }||xj                  |z  c_        d|_        t        s|j                  |j                  |j                   dz   f        |D ]R  \  }}t#        | j                  D cg c]  }|j                  s| c}      t$        k  s>| j'                  |||||       T yc c}w )zCheck anti-mart hedge TPs and reopen if configured.
        
        Ported from AntiMartingaleEA.mq4 ProcessTPHits() + HandleHedgeTP().
        NFr   Tr   )r6   r   r   ri   r   r   re   rO   r}   rf   rh   close_positionrk   r9   r   rd   rj   r   r:   r   )r   r   r   r   r   r   reopenshedger   r   hittp_priceprofitrd   rj   hs                   ra   check_anti_mart_hedgesz%RecoveryThread.check_anti_mart_hedges  s    !(=(=113S** 	JE??((,,U^^<C{"'mmdmm3GC!# ,,w68#C ,,w6(?C%44U^^D%&&&0&"' $NNEKK1C1Ca1G#HI=	JB $+ 	XE<t44D!ADEH\\**5,S,W	XDs   F
)F
c                    | j                   syt        \  }}||cxk  r|k  sy yt        | j                        dz  }t	        |      D ]!  }| j                         \  }	}
}}d}|
dkD  r}||
k\  rx| xj                  dz  c_        |	| _        t        r%| j                  t        k\  r| j                  ||
       t        r| j                  |      dk  r| j                  d||       d}n|dkD  r|||k  rw| xj                  dz  c_        || _        t        r%| j                  t        k\  r| j                  ||       t        r| j                  |      dk  r| j                  d||       d}|r" y y)a[  Check if price crossed any grid levels within candle range.

        Loops to handle multiple level crossings within a single candle
        (common for crypto where a single candle can span many grid levels).

        MQ4 V4 (line 710-720): recovery grid level crossings gated by
        CalcFibo() within RANGE_ZONE_MIN..RANGE_ZONE_MAX.
        N   Fr   r   r   T)ri   r   r   r~   ranger   r   r   r2   r3   _relieve_marginrB   _count_active_ordersr   )r   r   r   r   r   r   r   max_iterations_r   r   r   r   crosseds                 ra   check_levelszRecoveryThread.check_levels  sh    ~~ 0(J2(2 3T__-1~& 	A/3/C/C/E,E8UHG !| 0  A% %*"(T-=-=AT-T((x@(D,E,El,SVW,WNN1lzNJ A#/  A% %*"(T-=-=AT-T((x@(D,E,El,SVW,WNN1lzNJ9	r`   c                 n   | j                   sy| j                  D ]  }|j                  | j                  z  }|j                  D ]7  }|j
                  r| j                  ||||       | j                  |||       9 |j                  j                  |j                        }|r||j                  |z   k\  r|j                  |j                  |j                  |z          | j                  |j                  |       | j                  |j                  |       | j!                  ||       |j"                  |j                  v }	|	s| j%                  ||       |j                  j                  |j"                        }
|
sd||j&                  |z
  k  sx|j                  |j"                  |j&                  |z
         | j                  |j                  |       | j                  |j                  |       | j!                  ||       |j                  |j                  v }|r| j%                  ||        y)z;Check TP hits for all recovery pairs using candle high/low.Nr   )ri   r   rO   r}   rs   rS   _check_sub_tp_check_sub_repeatr   r   rL   rn   r   _close_if_openrp   rq   _close_old_subsrM   _close_current_subsro   )r   r   r   r   r   r   r   r   buy_possell_still_opensell_posbuy_still_opens               ra   	check_tpszRecoveryThread.check_tpsH  s    ~~$$ 	ADllT]]2G nn P<<""2tS,?&&r<J&O	P #,,001B1BCG44>>G#;;++D,=,=t~~PW?WX##D$5$5|D##D$6$6E$$T<8"&"4"48N8N"N&,,T<@ $--11$2D2DEHC4??W#<<++D,>,>RY@YZ##D$5$5|D##D$6$6E$$T<8!%!2!2l6L6L!L%,,T<@?	Ar`   c                     | j                   sy| j                  st        r4| j                  |      }|t        k\  r| j                  |       d| _         yyy)z#Check thread profit exit condition.NF)ri   r   rC   get_total_profitrD   	close_all)r   r   totals      ra   check_thread_profitz"RecoveryThread.check_thread_profito  sN    ~~4)),7E,,|,!& -  5r`   c                 N   | j                   sy|j                  j                  | j                        }|r?|j	                         r/|j
                  dkD  r t        | j                        dk(  rd| _         yt        | j                        dkD  r| j                  |      sd| _         yyy)z"Check if thread should deactivate.Nr   F)	ri   ordersr   rx   rS   r   r   r   _has_active_orders)r   r   
init_orders      ra   check_deactivationz!RecoveryThread.check_deactivationz  s    ~~ "((,,T-B-BC
*..0  1$T-=-=)>!)C!& t 1$T-D-D\-R"DN .S$r`   r   c                 @   |j                   | j                  z  }|j                  j                  |j                        }|j                  |j
                  v rD|rB||j                  |z   k\  r0|j                  |j                  |j                  |z          d|_        |j                  j                  |j                        }|j                  |j
                  v rG|rD||j                  |z
  k  r1|j                  |j                  |j                  |z
         d|_
        yyyy)zCheck TP for a sub-order pair.TN)rO   r}   r  r   rL   r   r   r   rQ   rM   rR   )r   r   r   r   r   r   r   r   s           ra   r  zRecoveryThread._check_sub_tp  s     **t}}, !''++BOO<	??l444y00++BOOY__w=VW $ "((,,R-=-=>
|555*j&&00++B,<,<j>N>NQX>XY!% 1 ;E5r`   c                 T   |j                   ryt        \  }}||cxk  r|k  sy y|j                         \  }}t        r&|j                  r|j
                  sd|_        y|j                  s|j
                  rd|_        y|j                  r|j
                  rd}|j                  dk(  r||j                  k\  rd}n |j                  dk(  r||j                  k  rd}|rd|_        d|_        |j                  |j                  | j                  d      }	|j                  |j                  | j                  d      }
|	r|	j                  |_        |
r|
j                  |_        yyyyyt         r|j                  rQ||j                  k  rBd|_        |j                  |j                  | j                  d      }	|	r|	j                  |_        |j
                  rT||j                  k\  rDd|_        |j                  |j                  | j                  d      }
|
r|
j                  |_        yyyyy)	z.Check if sub-pair should repeat after TPs hit.Nr   r   FTSUB_RPT_BUYr   SUB_RPT_SELL)rS   r   r   r)   rQ   rR   rT   rP   r   rU   rz   r   rV   re   rL   rM   r*   )r   r   r   r   r   r   r   r   should_repeatr   r   s              ra   r  z RecoveryThread._check_sub_repeat  s    << 0(J2(2 3//1Q}}R^^$%!]]r~~$%!2>> %$$)cR^^.C$(M&&!+r~~0E$(M $)BM%*BN , < <[[

 - != !I ".!>!>\\ . "? "J !*3*<*<!+5+>+>( " ! $2, !}}!6 %(88DJJ) 9 	 &/&8&8BO~~#"7!&)::TZZ* ; 
 '1':':B$  #8~ !r`   c                    d}|j                   j                  | j                        }|r||j                  z  }| j                  D ]  }|j
                  |j                  fD ]/  }|j                   j                  |      }|s!||j                  z  }1 |j                  D ]Y  }|j                  r|j
                  |j                  fD ]/  }|j                   j                  |      }|s!||j                  z  }1 [  | j                  D ]F  }|j                  s|j                   j                  |j                        }|s8||j                  z  }H |j                  D ]+  }|j                  | j                  k(  s||j                  z  }- |S )aW  Get total P&L across all positions in this thread (open + closed).

        MQ4 uses OrderProfit()+OrderSwap()+OrderCommission() which includes
        already-realized P&L from closed sub-pairs. We sum:
        1. Unrealized P&L from still-open positions
        2. Realized P&L from closed orders matching this thread's magic number
        rN   )r   r   rx   unrealized_pnlr   rL   rM   rs   rS   r   ri   re   trade_historyrz   r   )	r   r   r  r   r   oidr   r   closed_orders	            ra   r  zRecoveryThread.get_total_profit  s     $$(()>)>?S'''E $$ 	4D))4+=+=> 0",,005S///E0
 nn 4<<OOR-=-=> 4C&0044S9C!3!3344	4 ** 	0E",,00@S///E		0 )66 	-L!!TZZ/,,,	- r`   c                 R   | j                  | j                  |       | j                  D ]  }| j                  |j                  |       | j                  |j                  |       | j                  |j
                  |       | j                  |j                  |       |j                  D ]:  }| j                  |j                  |       | j                  |j                  |       <  | j                  D ]2  }|j                  s| j                  |j                  |       d|_	        4 y)z#Close all positions in this thread.FN)r  rx   r   rL   rM   rp   rq   rs   r   ri   re   )r   r   r   r   r   s        ra   r  zRecoveryThread.close_all  s    D11<@$$ 	DD 1 1<@ 2 2LA 1 1<@ 2 2LAnn D##BOO\B##B$4$4lCD	D ** 	(E##ENNLA"'	(r`   r   c                    t         rt        | j                        dk  ryd}| j                  dd D ]  }|j                  D ]  }|j                  r|j
                  ra|j
                  |j                  v rI|j                  |j
                     }|j                  dk  r!|j                  |j
                  |       |dz  }|j                  ra|j                  |j                  v rI|j                  |j                     }|j                  dk  r!|j                  |j                  |       |dz  }|dkD  sd|_          |dkD  r,| j                  j                  d| d	| j                          yy)
zClose losing sub-pairs from earlier levels to free margin for deeper recovery.
        Only called at deep recovery levels (>= MARGIN_RELIEF_LEVEL).
        Closes sub-pairs from all but the latest recovery pair.r   Nr   r   r   TzMargin relief: closed z losing sub-positions at L)r4   r   r   rs   rS   rL   r   r  r   rM   r   debugr   )r   r   r   closed_countr   subr   s          ra   r   zRecoveryThread._relieve_margin  sa    (3t/?/?+@1+D$$Sb) 	)D~~ )==##(8(8L<R<R(R&001A1ABC))B.$33C4D4DmT$)$$):):l>T>T)T&001B1BCC))B.$33C4E4E}U$)!#$(CM!)	)$ !KK 6|nD^_c_o_o^pqr r`   re   c                 J    |r!||j                   v r|j                  |       yyy)z$Close a position if it's still open.N)r   r   )r   re   r   s      ra   r  zRecoveryThread._close_if_open2  s'    L$:$::''1 ;8r`   c                     |j                   D ]A  }| j                  |j                  |       | j                  |j                  |       d|_        C y)z.Close old sub-order pairs from previous level.TN)rt   r  rL   rM   rS   )r   r   r   osps       ra   r  zRecoveryThread._close_old_subs7  sJ    %% 	!C 0 0,? 1 1<@ CM	!r`   c                     |j                   D ]A  }| j                  |j                  |       | j                  |j                  |       d|_        C y)zClose current sub-order pairs.TN)rs   r  rL   rM   rS   )r   r   r   r   s       ra   r  z"RecoveryThread._close_current_subs>  sF    .. 	 B> 0 0,?BL	 r`   c                 h   | j                   |j                  v ry| j                  D ]  }|j                  |j                  v r y|j                  |j                  v r y|j
                  D ]E  }|j                  r|j                  |j                  v r  y|j                  |j                  v sD  y  y)z2Check if any orders in this thread are still open.TF)rx   r   r   rL   rM   rs   rS   )r   r   r   r   s       ra   r  z!RecoveryThread._has_active_ordersE  s      L$:$::$$ 
	$D  L$:$::!!\%;%;;nn $||,*@*@@#''<+A+AA#$
	$ r`   c                     d}| j                   D ]=  }|j                  |j                  v r|dz  }|j                  |j                  v s9|dz  }? |S )z&Count active positions in order pairs.r   r   )r   rL   r   rM   )r   r   countr   s       ra   r   z#RecoveryThread._count_active_ordersV  s]    $$ 	D  L$:$::
!!\%;%;;
		
 r`   )NNNr   )      ?)r   )#rW   rX   rY   rZ   r[   r]   strr   r   r   r
   r   rm   r   r   r   r   r   r   r  r  r  r  rK   r  r  r  r  r   r  r  r  r^   r  r   r_   r`   ra   rw   rw   {   s   0 !%"&!8#8# 8# 	8#
 8# 8#  8# 8# 8# I8# 3i8# 8# 8#t0,W53 W53 W5FY W5^c W5r
"$5 
"3 
"5 
"$5 3 !&6INS 
u 
DW 
\a 
 '*`( `(<O `(#`(J KL#3 #>Q ##(#/4#DG#J/X5 /Xu /X-@/Xh *-/ /U /BU /!&/d '*%Ae %A% %A?R %A#%AN	'0C 	'#/B # & &E & &$7&( /29;L 9;@S 9;&+9;v+-@ +U +Z(&9 ("s,? sPU s82s 2:M 2
!$5 !EX ! (9  I\  /B t "1D  r`   rw   c                       e Zd ZdZdedefdZdefdZe	fdede
fdZefdede
fd	Zde
fd
Zde
defdZdedefdZdedefdZdefdZdefdZdefdZde
de
fdZdefdZdefdZy)HedgeMartStrategyuz  Main strategy engine for Auto Hedge-Mart V4.

    Entry logic:
    1. Continuation: two consecutive same-direction bars in Fibo zone → trade
    2. Reversal: previous bar opposite + current bar direction → trade (disabled by default)

    Recovery:
    - Each initial trade gets a RecoveryThread
    - Threads manage grid levels, hedged pairs, TP management, lot sizing
    r|   r   c                    || _         || _        t        |      | _        g | _        t
        rt        t        t              nt        }t        t        |dz   d            | _
        d| _        d| _        t               | _        y )N      )maxlenr   r   )r|   r   r	   r}   recovery_threadsrE   r   r   rF   r   bar_historymagic_countercurrent_vol_scaler   r   )r   r|   r   atr_lookbacks       ra   r   zHedgeMartStrategy.__init__q  si    ($V,68ANs>+<=Tb"'s<!3CR/H"I!$ lr`   r   c                 J    | xj                   dz  c_         d| j                   z   S )Nr   r   )r9  r   s    ra   _next_magiczHedgeMartStrategy._next_magic|  s$    a****r`   	bars_backc                 2   t        | j                        |k  ryt        | j                        | d }t        d |D              }t	        d |D              }||k  ry| j
                  j                         \  }}|dk  r|d   j                  }||z
  ||z
  z  S )zCalculate Fibonacci zone position.

        MQ4: (Ask - Low[N bars]) / (High[N bars] - Low[N bars])
        Returns 0.0 (at range low) to 1.0 (at range high).
        r/  Nc              3   4   K   | ]  }|j                     y wN)r   .0bs     ra   	<genexpr>z.HedgeMartStrategy.calc_fibo.<locals>.<genexpr>  s     (A!%%(   c              3   4   K   | ]  }|j                     y wrB  )r   rC  s     ra   rF  z.HedgeMartStrategy.calc_fibo.<locals>.<genexpr>  s     *a166*rG  r   r   )r   r8  ru   r   r   r   r   close)r   r?  recentr   r   r   r   s          ra   	calc_fibozHedgeMartStrategy.calc_fibo  s     t 9,d&&'
4(((*6**3;""4463!8*""Cc	dSj))r`   c                     t        | j                        |k  rt        S t        | j                        | d }|D cg c]  }|j                  |j
                  z
   }}t        |      t        |      z  S c c}w )znCalculate Average True Range over recent bars.

        Returns average (high - low) in dollar terms.
        N)r   r8  rG   ru   r   r   sum)r   r?  rJ  rE  rangess        ra   calc_atrzHedgeMartStrategy.calc_atr  sm    
 t 9,  d&&'
4*01Q!&&155.116{S[(( 2s    A5c                     t         sy| j                         }t        dkD  r	|t        z  nd}t        t        t        t        |            }|S )u   Get current volatility scale factor.

        scale = current_atr / reference_atr, clamped to [min, max].
        > 1.0 means market is hotter than baseline → widen grid/TP
        < 1.0 means market is quieter → tighten grid/TP
        r   r   )rE   rO  rG   r   rH   r   rI   )r   atrscales      ra   get_vol_scalezHedgeMartStrategy.get_vol_scale  sB     mmo'4q'8m#c&,>(FGr`   r   c                     t         D cg c]  }t        ||z         }}t        D cg c]  }|dkD  rt        ||z        nd }}t        t        |z        }|||dS c c}w c c}w )zBScale grid steps, recovery TPs, and entry TP by volatility factor.r   )r~   r   r   )r   r   r   r   )r   r   r   scaled_gridtscaled_rec_tpsscaled_entry_tps          ra   get_scaled_paramsz#HedgeMartStrategy.get_scaled_params  sp    5HIuQ]+IIHTU1!a%%I.Q>UU	 9:%*,
 	
 JUs
   AA#offsetc                     |dz    }t        |      t        | j                        kD  ry| j                  |   }|j                  |j                  kD  ry|j                  |j                  k  ryy)u   Get bar direction at offset from end. 0=bull, 1=bear, 2=doji.

        offset=1 → last completed bar (MQ4 bar[1])
        offset=2 → two bars ago (MQ4 bar[2])
        r   r   r   )r   r   r8  rI  open)r   rZ  idxbars       ra   _bar_directionz HedgeMartStrategy._bar_direction  sb     
ms8c$**++s#99sxxYY!r`   sidec                 L   d}| j                   D ]  }|j                  s| j                  j                  j	                  |j
                        }|sB|dk(  r#|j                  t        j                  k(  r|dz  }j|dk(  sp|j                  t        j                  k(  s|dz  } |S )z=Count active initial orders for a given side (0=buy, 1=sell).r   r   )
r7  ri   r   r   r   rx   r`  r   BUYSELL)r   r`  r.  rV  r   s        ra   _count_initial_ordersz'HedgeMartStrategy._count_initial_orders  s    && 	A;;##--11!2D2DEC19Y]]!:QJEQY388y~~#=QJE	 r`   candlec                    | j                   j                  |       t        | j                         dk  ry| j                         | _        | j                         }| j                  d      }| j                  d      }t        }t        r|t        | j                  j                        }|t        k\  rt        }nM|t        k\  rD|t        z
  t        dt        t        z
        z  }t        t        t        t        d|z
  z              }|}|}	t         rd}
d}| j"                  D ])  }|j$                  s|j&                  dk(  r|
dz  }
%|dz  }+ |
|z   }|
|z
  }t)        |      t        |d      z  }|t*        kD  r,|dkD  rt        t        |dz        }nt        t        |dz        }	t,        r| j/                  d      }| j/                  d      }t0        d   |cxk  xr t0        d   k  nc }|r$||	k  r||k  r|dk(  r|dv r| j3                  d       |r$||k  r||	k  r|dk(  r|dv r| j3                  d       t4        r| j/                  d      }| j/                  d      }|dk(  r<|dk(  r7||	k  r2||k  r-t6        d   |cxk  rt6        d   k  rn n| j3                  d       |dk(  rA|dk(  r;||k  r5||	k  r/t8        d   |cxk  rt8        d   k  rn y| j3                  d       yyyyyyy)u_   Process new bar — check for entry signals.

        MQ4 OnTick → IsNewBar section.
           Nr   r   r   )r   r   )r   r   )r8  r   r   rS  r:  rK  rd  r   r<   r   r   r>   r?   r=   r   r[   r@   r7  ri   rf   r   rA   r   r_  r   _open_initial_trader   r   r   )r   re  fibo	buy_count
sell_counteffective_mon_positionsratiobuy_mosell_mobuy_threadssell_threadsthreadtotal_threadsnet_threadsimbalance_ratior   currin_rangebar1bar2s                        ra   
on_new_barzHedgeMartStrategy.on_new_bar  s   
 	't 1$ "&!3!3!5~~..q1	//2
 *d//99:K44- 55$'<<AG]`uGu@vv">37IQQVY7W3XY
 ! KL// *##''1,#q($)* (,6M%4K!+.]A1FFO!99? 1BCF!.,!2CDG #&&q)D&&q)D&q)TG_Q5GGH"W,!F*	((+!F*"W,	((+ '&&q)D&&q)D	dai"W,!F*%a(DDM!4DD((+	dai!F*"W,&q)TF^A5FF((+ G - + (	 'r`   rf   c                    | j                   j                         \  }}t        dkD  rYt        | j                        dkD  rA| j                  D ]2  }|j
                  st        ||j                  z
        }|t        k  s2 y | j                         }|dk(  r#| j                   j                  t        |d      }n"| j                   j                  t        |d      }|r| j                  }| j                  |      }	|	d   | j                  z  }
|dk(  r|j                  |
z   }n|j                  |
z
  }t!        |j"                  |j                  t        |||	d   d   | j$                  | j                  |	d   |	d   |	d   |	      }| j                  j'                  |       t)        |dk(  rdnd| j$                  |j                  t        ||
       yy)a  Open initial trade and create recovery thread.

        When ADAPTIVE_GRID is enabled, the current volatility scale is locked
        into the thread at creation time. The thread's grid, TPs, and entry TP
        all reflect the market conditions when the trade was opened.
        r   N	ENTRY_BUYr   
ENTRY_SELLr   r   r~   )rx   rh   ry   rf   rz   r{   r|   r}   r~   r   r   r   )tp)r   r   r5   r   r7  ri   r   rh   r>  r   r   r   r:  rY  r}   r   rw   re   r|   r   r   )r   rf   r   r   rs  distrz   r   vsr   r   r   s               ra   rh  z%HedgeMartStrategy._open_initial_trade;  s    $$668S q S)>)>%?!%C// ''3!3!334++   ">%%55UK 6 E %%66UL 7 E ''B''+B)DMM9GA~ ;;0 ;;0#!&!KK!#!#N!3A!6{{l+/ 1F !!((0(A~<U[[(Eh7 r`   c                 t   | j                         }t        | j                        D ]  }|j                  s| j                  j
                  j                  |j                        }d}|r)|j                  dkD  rt        |j                        dk(  rd}|r3|j                  |j                  |j                  | j                  |       |j                  |j                  |j                  | j                         |j                  |j                  |j                  | j                  |       |j                   | j"                  z  }t$        dkD  rt$        | j"                  z  nd}| j                  j
                  j                  |j                        }|r:|j&                  dk(  r|j(                  |z   }|dkD  r|j(                  |z
  nd}	|j                  |k\  r'| j                  j+                  |j                  |       n|dkD  r|j                  |	k  r| j                  j+                  |j                  |	       n|j(                  |z
  }|dkD  r|j(                  |z   nd}	|j                  |k  r'| j                  j+                  |j                  |       n:|dkD  r5|j                  |	k\  r&| j                  j+                  |j                  |	       |j-                  | j                         |j/                  | j                          | j                  D 
cg c]  }
|
j                  s|
 c}
| _        yc c}
w )aT  Process recovery threads against candle data.

        Order of operations within a candle:
        1. Check grid level crossings (recovery triggers)
        2. Check recovery pair TPs
        3. Check initial order TP/SL (after recovery, so grid can trigger first)
        4. Check thread profit exit
        5. Deactivation check
        Tr   Fr   N)rK  ru   r7  ri   r   r   r   rx   r  r   r   r  r   r   r   r  r   r}   r   rf   rh   r   r  r  )r   re  ri  rs  init_posrun_recoverytp_dist_initsl_dist_initr   	sl_targetrV  s              ra   process_candlez HedgeMartStrategy.process_candlez  s    ~~4001 -	9F## ((2266v7N7NOHLH33a7C@R@R<SWX<X$##FKKT=N=N/3 $ 5 ))&++vzz4CTCTU V[[&**d6G6G(,  . "//$--?L<IA<M=4==8STL((2266v7N7NOH##q( & 2 2\ AIEQTUEU 2 2\ A[\I{{i/))889P9PR[\%)fjjI.E))889P9PR[\ & 2 2\ AIEQTUEU 2 2\ A[\IzzY.))889P9PR[\%)fkkY.F))889P9PR[\ &&t'8'89 %%d&7&78[-	9` -1,A,A QqQ[[ Q Qs   L5(L5r   r   c                 x   | j                         }t        | j                        D ]  }|j                  s| j                  j
                  j                  |j                        }d}|r)|j                  dkD  rt        |j                        dk(  rd}|r|j                  ||| j                  |       |j                  ||| j                  |       |j                  | j                         |j                  | j                          | j                  D cg c]  }|j                  s| c}| _        yc c}w )u6   Process tick — run recovery threads at exact prices.Tr   Fr   N)rK  ru   r7  ri   r   r   r   rx   r  r   r   r  r  r  r  )r   r   r   ri  rs  r  r  rV  s           ra   on_tickzHedgeMartStrategy.on_tick  s   ~~4001 	9F##((2266v7N7NOHLH33a7C@R@R<SWX<X$##Cd.?.?D#QS#t'8'8TJ&&t'8'89%%d&7&78	9  -1,A,A QqQ[[ Q Qs   D7*D7c                 ,    t        | j                        S rB  )r   r7  r=  s    ra   get_active_thread_countz)HedgeMartStrategy.get_active_thread_count  s    4(())r`   c                 \    d}| j                   D ]  }|t        |j                        z  } |S )Nr   )r7  r   r   )r   r  rV  s      ra   get_total_recovery_ordersz+HedgeMartStrategy.get_total_recovery_orders  s4    && 	(AS''E	(r`   N)rW   rX   rY   rZ   r0  r
   r   r[   r>  r   r]   rK  rF   rO  rS  dictrY  r_  rd  r   r{  rh  r  r  r  r  r_   r`   ra   r2  r2  e  s    		#s 	#2E 	#+S + *8 *3 *E *, ): 
)# 
)e 
)u 	
5 	
T 	
S S  # #  Y, Y,v;S ;~<RU <R|R5 Ru R,* *3 r`   r2  N)UrZ   dataclassesr   r   typingr   r   collectionsr   normalizationr   r	   r   r
   r   data_loaderr   utilsr   r   settingsr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   rF   rG   rH   rI   rK   rc   rm   rw   r2  r_   r`   ra   <module>r     s   
 ) !  : 7  '               B   &   $ D D D8c cTp pr`   