
    v?i5B                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
mZ e	j                  j                  d e ee      j!                         j"                  j"                               ddl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# 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/m0Z0m1Z1m2Z2 d0dZ3d Z4d	 Z5d
 Z6d Z7d Z8d Z9d Z:d Z;d Z<d Z=d Z>d Z?d Z@d ZAd ZBd ZCd ZDd ZEd ZF	 	 d1	 	 	 	 	 d2dZGd ZHd ZId ZJd ZKd  ZLd! ZMd" ZNd# ZOd$ ZPd% ZQd& ZRd' ZSd( ZTd) ZUd* ZVd+ ZWd, ZXd- ZYd3d.ZZe[d/k(  r e	j                   eZ              yy)4z
Smoke tests for analysis/indicators/* using synthetic OHLCV fixtures.

Each calculator is tested with controlled inputs where the expected
output is mathematically derivable, not approximate.

Run:
    cd ~/apex_v16
    python -m tests.test_indicators
    )annotationsN)Path)calc_absorptioncalc_atrcalc_candle_strengthcalc_divergence	calc_dojicalc_engulfingcalc_hammercalc_macd_decelcalc_market_structurecalc_piercing_darkcalc_rsicalc_star_patternscalc_volume_weakcalc_vwap_intradayidentify_swing_levels)bear_engulfing_at_endbull_engulfing_at_enddoji_at_end	downtrendhammer_at_endhh_hl_h1lh_ll_h1
ranging_h1shooting_star_at_endsidewaysuptrendwith_pivot_highwith_pivot_low
with_spikec                     t        d|         y )Nz  ok  )print)labels    ,/home/work/apex_v16/tests/test_indicators.py_okr&   8   s    	F5'
    c                    t        d      } t        |       j                  d   }d}||kD  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       d	z   d
|iz  }t        t        j                  |            d x}}t        d|dd       y )N2   F   >z%(py0)s > %(py3)srsipy0py3z"uptrend should give RSI > 70, got 
>assert %(py5)spy5zrsi: uptrend -> .1fz > 70)r   r   iloc
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgAssertionError_format_explanationr&   dfr/   @py_assert2@py_assert1@py_format4@py_format6s         r%   test_rsi_uptrend_highrF   @   s    	B
2,

B
C?38???3??????3???3??????9#???????
3s)5)*r'   c                    t        d      } t        |       j                  d   }d}||k  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       d	z   d
|iz  }t        t        j                  |            d x}}t        d|dd       y )Nr)   r*      <)z%(py0)s < %(py3)sr/   r0   z$downtrend should give RSI < 30, got r3   r4   zrsi: downtrend -> r5   z < 30)r   r   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r&   r@   s         r%   test_rsi_downtrend_lowrK   G   s    	2B
2,

B
CA38AAA3AAAAAA3AAA3AAAAAA;C5AAAAAAA
SIU+,r'   c                 4   t        ddd      } t        |       j                  d   }d}||k  }d}||k  }|r|st        j                  d||fd	|||f      t        j
                  |      d
t        j                         v st        j                  |      rt        j
                  |      nd
t        j
                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            d x}x}x}}t        d|dd       y )NP         Y@皙?mid	amplituder*   #   A   rJ   rJ   %(py1)s < %(py4)sz%(py4)s < %(py6)sr/   py1py4py6z$sideways RSI should be near 50, got 
>assert %(py8)spy8zrsi: sideways -> r5   z ~ 50)r   r   r6   r7   r8   r<   r9   r:   r;   r=   r>   r?   r&   )rA   r/   @py_assert0rB   @py_assert5@py_assert3@py_format7@py_format9s           r%   test_rsi_sideways_midrc   N   s    	"%3	/B
2,

B
CF2=FbFb=FFFF2bFFF2FFFFFFFFFFFFbFFF@FFFFFFFF
C9E*+r'   c                 2   t        ddd      } t        |       j                  d   }d}||k  }d}||k  }|r|st        j                  d||fd	|||f      t        j
                  |      d
t        j                         v st        j                  |      rt        j
                  |      nd
t        j
                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}x}x}}t        d|d       y)z*Bars with fixed body+wick -> ATR == range.(   皙?rO   bodywickr*         ?皙?rU   rV   atrrX   zatr ~0.6 expected, got r\   r]   Nzatr: constant-range bars -> .2f)r   r   r6   r7   r8   r<   r9   r:   r;   r=   r>   r?   r&   )rA   rl   r^   rB   r_   r`   ra   rb   s           r%   test_atr_constant_rangern   Y   s    	#C	(B
2,

B
C ;3?;s;s?;;;;3s;;;3;;;;;;;;;;;;s;;;5cU;;;;;;;;
&s3i01r'   c                    t        ddd      } t        |       j                  d   }t        | d      }t        |      j                  d   }d}||z  }||kD  }|st	        j
                  d	|fd
||f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }t	        j                  d|dd|d      dz   d|iz  }t        t	        j                  |            dx}x}}t        d|dd|dd       y)z(Spike bar should pull ATR ratio above 1.re   333333?rO   rg   r*   g      @)
spike_sizeg      ?r,   )z%(py0)s > (%(py2)s * %(py4)s)	atr_spike
atr_normalr1   py2rZ   zspike should raise ATR, rm   z -> z
>assert %(py7)spy7Nzatr: spike z (>1.5x))r   r   r6   r!   r7   r8   r9   r:   r;   r<   r=   r>   r?   r&   )	basers   spikedrr   r`   r_   rC   rE   @py_format8s	            r%   test_atr_spike_increases_ratiorz   c   s   2Cc*D$$$R(J-F %%b)I$'gzC'g9''ggg9'gggggg9ggg9ggggggzgggzgggCggg+CJsCSSWXabeWf)gggggggg+j%T)CABr'   c                    t        dd      } t        d| d   j                  d   d      }t        j                  | |gd	
      }t        |      }d	}||u }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}}t        d       y)z2Trend that is fading -> hist decelerating -> True.re   g333333?rh      closer*   皙?rP   Tignore_indexisz%(py0)s is %(py3)sdecelr0   assert %(py5)sr4   Nz-macd: trend-then-plateau -> decelerating True)r   r   r6   pdconcatr   r7   r8   r9   r:   r;   r<   r>   r?   r&   )df1flatrA   r   rB   rC   rD   rE   s           r%   "test_macd_decel_decelerating_trendr   q   s     "3
CBCL--b1TBD	C;T	2BBE5D=5D55D78r'   c                    t        dd      } | j                  d   }| d   j                  dd j                         }|dz
  | j                  |df<   | j                  |df   dz
  | j                  |d	f<   t        |       }t        | |      }d
}||v }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            dx}}t!        d|        y)z
    Build a series where close makes a NEW low at last bar but RSI does NOT.
    Use uptrend then a single-bar dip that breaks min(close)[-10:-1] without
    breaking RSI's 9-bar rolling min.
    r}   rf   r|   r*   r~   irj   rO   low)BULLISHNONEinz%(py0)s in %(py3)sdivr0   zunexpected r3   r4   Nz-divergence: artificial price-low scenario -> )r   indexr6   minlocr   r   r7   r8   r9   r:   r;   r<   r=   r>   r?   r&   )	rA   last_idx
window_minr/   r   rB   rC   rD   rE   s	            r%   test_divergence_bullishr      s	    
#	Bxx|HG!!#b)--/J *S 0BFF8W ffXw%67#=BFF8U?
2,C
"c
"C%:3%%:::3%::::::3:::3:::%:::SE':::::::
7u=>r'   c                    t        ddd      } t        | t        |             }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        j                  d
|       dz   d|iz  }t        t        j                  |            dx}}t        d       y)z<Sideways: neither price nor RSI breaks 9-bar window -> NONE.re   rN   r   rP   r   ==z%(py0)s == %(py3)sr   r0   zsideways should give NONE, got r3   r4   Nzdivergence: sideways -> NONE)r   r   r   r7   r8   r9   r:   r;   r<   r=   r>   r?   r&   rA   r   rB   rC   rD   rE   s         r%    test_divergence_none_on_sidewaysr      s    	"%4	0B
"hrl
+CA3&=AAA3&AAAAAA3AAA3AAA&AAA;C5AAAAAAA&'r'   c                    t        d      } t        | t        |             }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}t        d
       y	)u  
    Clean monotonic uptrend: price keeps making new highs but RSI
    saturates at 100, so rsi[-1] is NOT strictly > rolling max.
    Output: BEARISH (price-RSI divergence on the upside).
    Documents V15 behavior — useful for the calibration round.
    rH   BEARISHr   r   r   r0   r   r4   Nz6divergence: saturated uptrend -> BEARISH (RSI plateau))r   r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   r   s         r%   ,test_divergence_bearish_on_saturated_uptrendr      s     
B
"hrl
+C3)3)33)@Ar'   c                    t        dd      } t        |       }d}||k  }d}||k  }|r|st        j                  d||fd|||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }t        j                  d
|       dz   d|iz  }t        t        j                  |            d x}x}x}}t        d|        y )Nr}   rp   r|   rk   g333333?rU   rV   csrX   znormal candle ~1.0, got r\   r]   z#candle_strength: uniform bodies -> )r   r   r7   r8   r<   r9   r:   r;   r=   r>   r?   r&   )rA   r   r^   rB   r_   r`   ra   rb   s           r%   test_candle_strength_normalr      s    	#	B	b	!B:3>:c:c>::::3c:::3::::::::::::c:::5bT::::::::
-bT23r'   c                 2   t        dd      } | j                  d   }| j                  |df   dz
  | j                  |df<   t        |       }d}||kD  }|st	        j
                  d	|fd
||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }t	        j                  d|       dz   d|iz  }t        t	        j                  |            dx}}t        d|        y)z,Last bar with body 3x avg should give cs ~3.r}   rp   r|   r*   r~         ?openg      @r,   r.   r   r0   z"giant candle should be > 2.5, got r3   r4   Nzcandle_strength: giant bar -> )r   r   r   r   r7   r8   r9   r:   r;   r<   r=   r>   r?   r&   )rA   lastr   rB   rC   rD   rE   s          r%   test_candle_strength_giantr      s    	#	B88B<D66$-036BFF4<	b	!B>28>>>2>>>>>>2>>>2>>>>>>9">>>>>>>
(-.r'   c                    t               } t        |       \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}d}||u }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}t        d
       y )NTr   r   hr0   r   r4   Fssz&candles: hammer fixture -> hammer=True)r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   rA   r   r   rB   rC   rD   rE   s          r%   test_hammer_detectionr      s    	BOEAr191112;22201r'   c                    t               } t        |       \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}d}||u }|st        j                  d|fd||f      d	t	        j
                         v st        j                  |      rt        j                  |      nd	t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}t        d
       y )NTr   r   r   r0   r   r4   Fr   z)candles: shooting star fixture -> ss=True)r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   r   s          r%   test_shooting_star_detectionr      s    		BOEAr2:2221:11134r'   c                    t               } t        |       \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}t        d       y )	NTr   r   bullr0   r   r4   z candles: bull engulfing detected)r   r
   r7   r8   r9   r:   r;   r<   r>   r?   r&   rA   r   bearrB   rC   rD   rE   s          r%   test_bull_engulfing_detectionr      y    		 B#JD$44<44444*+r'   c                    t               } t        |       \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}t        d       y )	NTr   r   r   r0   r   r4   z candles: bear engulfing detected)r   r
   r7   r8   r9   r:   r;   r<   r>   r?   r&   r   s          r%   test_bear_engulfing_detectionr      r   r'   c                    t               } t        |       \  }}d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}d}||v }|st        j                  d	|fd
||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d x}}t        d| d       y )NTr   r   hasr0   r   r4   )	dragonfly
gravestonestandardr   r   dtzcandles: doji detected ())r   r	   r7   r8   r9   r:   r;   r<   r>   r?   r&   )rA   r   r   rB   rC   rD   rE   s          r%   test_doji_detectionr      s    	BmGB3$;3$33$88288888288888882888288888888888
"2$a()r'   c                 &   t        dd      } t        |       \  }}t        |       \  }}t        |       \  }}t	        |       \  }}t        |       \  }	}
||||||
g}t        |      }| }|sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d x}x}}t        d       y )Nre   rp   r|   z0assert not %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyrt   z.candles: clean uptrend -> no reversal patterns)r   r   r
   r	   r   r   r   r9   r:   r7   r;   r<   r>   r?   r&   )rA   r   r   r   r   r   has_dojipdcmsesrC   r`   r_   rE   s                  r%    test_no_pattern_on_clean_uptrendr      s    	#	BOEAr#JD$R=LBr"EAr#FBr42r23s233333333333s333s3332333333333389r'   c                    t        d      } d| j                  | j                  d   df<   t        |       }d}||u }|st	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              ndd	t        j                         v st	        j                  |       rt	        j                  |       nd	t	        j                  |      t	        j                  |      d
z  }dd|iz  }t        t	        j                  |            d x}x}}t        d       y )NrH   d   r*   volumeTr   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} is %(py6)sr   rA   r1   rY   r2   r[   assert %(py8)sr]   z.candles: volume_weak fires when last < 85% avg)r   r   r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   rA   rB   r_   @py_assert4ra   rb   s         r%   test_volume_weakr      s    	B%(BFF288B<!"B'4'4''''4'''''''''''''''B'''B''''''4'''''''89r'   c                 N   t        d      } t        |       }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}t        d
       y )NrH   Fr   r   r   rA   r   r   r]   z,candles: volume_weak False on uniform volume)r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   r   s         r%    test_volume_weak_false_on_normalr      s    	BB(5(5((((5(((((((((((((((B(((B((((((5(((((((67r'   c           	     B   t        ddddd      }||z  }||z  }| rd}||z   }	nd}||z
  }	t        ||	      ||z
  dz  z   }
t        ||	      ||z
  dz  z
  }t        j                  ||
||	d|z  |d	   j
                  d
   dg      }t        j                  ||gd      |fS )z
    Build a 25-bar OHLCV df with controlled last-bar absorption properties.
    Prefix: 24 bars at vol=1000, neutral candles. Last bar customized.
       rN           r   g{Gz?)start_pricesteprh   ri      g     @@timer*   )r   highr   r~   r   r   Tr   )r   maxr   r   	DataFramer6   r   )close_above_openvol_multrange_ratio
body_ratiorl   rA   rngrh   ocr   lr   s                r%   _absorption_fixturer     s     
St$	GB

CDHHAq	S4Z1$$AAq	S4Z1$$A<<1Q8#6
#  D
 99b$Zd3S88r'   c                 P   t        ddd      \  } }t        | |      \  }}g }d}||u }|}|rd}||u }	|	}|sXt        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }
d
d|
iz  }|j                  |       |rt        j                  d	fd|f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            d x}x}x}x}x}	}t        d       y )NT       @rp   r   r   r   Fr   z%(py2)s is %(py5)sbuy_absru   r4   %(py7)srv   z%(py9)s is %(py12)ssell_abspy9py12%(py14)spy14r   assert %(py17)spy17zBabsorption: BUY (bull candle, vol 2x, range 0.3xATR) -> True/Falser   r   r7   r8   r9   r:   r;   r<   append_format_boolopr>   r?   r&   rA   rl   r   r   rC   r   r`   r^   @py_assert11@py_assert10rE   ry   @py_format13@py_format15@py_format16@py_format18s                   r%   test_buy_absorption_classicr    s    !4#.13GB'C0GX0d07d?050x5000007d00000070007000d0000000x5000000x000x000500000000000000LMr'   c                 P   t        ddd      \  } }t        | |      \  }}g }d}||u }|}|rd}||u }	|	}|sXt        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      d	z  }
d
d|
iz  }|j                  |       |rt        j                  d	fd|f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            d x}x}x}x}x}	}t        d       y )NFr   rp   r   Tr   r   r   r   r   rv   r   r   r   r   r   r   r   r   zCabsorption: SELL (bear candle, vol 2x, range 0.3xATR) -> False/Truer   r   s                   r%   test_sell_absorption_classicr  &  s    !53.13GB'C0GX0e07e0D0D 000007e00000070007000e0000000D000000000000D00000000000000MNr'   c                    t        ddd      \  } }t        | |      }d}||k(  }|s7t        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              ndd	t	        j
                         v st        j                  |       rt        j                  |       nd	d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d       y )NTr   rp   r   FFr   z9%(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
} == %(py7)sr   rA   rl   r1   rY   ru   rZ   rv   assert %(py9)sr   z)absorption: vol 1.0x avg -> no absorptionr   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   rA   rl   r`   @py_assert6r_   ry   @py_format10s          r%    test_no_absorption_normal_volumer  .  s    !4#.13GB2s#5~5#~5555#~555555?555?55555525552555555s555s555#555~555555534r'   c                    t        ddd      \  } }t        | |      }d}||k(  }|s7t        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              ndd	t	        j
                         v st        j                  |       rt        j                  |       nd	d
t	        j
                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d       y )NTr   rk   r   r  r   r  r   rA   rl   r  r	  r   z*absorption: range 0.8xATR -> no absorptionr
  r  s          r%   test_no_absorption_wide_ranger  5  s    !4#.13GB2s#5~5#~5555#~555555?555?55555525552555555s555s555#555~555555545r'   c                    t        dddd      \  } }t        | |      }d}||k(  }|s7t        j                  d|fd||f      d	t	        j
                         v st        j                  t              rt        j                  t              nd	d
t	        j
                         v st        j                  |       rt        j                  |       nd
dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d       y )NTr   rp   r   )r   r   r   r   r  r   r  r   rA   rl   r  r	  r   z3absorption: doji body (<10% range) -> no absorptionr
  r  s          r%   test_no_absorption_doji_bodyr  <  s    !4#.1dDGB2s#5~5#~5555#~555555?555?55555525552555555s555s555#555~5555555=>r'   c                    t        ddd      \  } }d}t        | |      }d}||k(  }|s
t        j                  d|fd||f      d	t	        j
                         v st        j                  t              rt        j                  t              nd	d
t	        j
                         v st        j                  |       rt        j                  |       nd
t        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}x}}t        d       y )NTr   rp   r   r   r  r   z9%(py5)s
{%(py5)s = %(py0)s(%(py1)s, %(py3)s)
} == %(py8)sr   rA   r1   rY   r2   r4   r]   assert %(py10)spy10z(absorption: atr=0 -> safe (False, False)r
  )rA   _rB   r   @py_assert7r  rb   @py_format11s           r%   test_absorption_atr_zero_safer  D  s    ,/1EB"5?2s#5~5#~5555#~555555?555?55555525552555s555#555~555555523r'   c                    t        d      } d}t        | |      }d}||k(  }|s
t        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            d x}x}x}}t        d       y )N
   r   r  r   r  r   rA   r  r  r  z+absorption: <20 bars -> safe (False, False))r   r   r7   r8   r9   r:   r;   r<   r>   r?   r&   )rA   rB   r   r  r  rb   r  s          r%   !test_absorption_insufficient_barsr  K  s    	B"5?2s#5~5#~5555#~555555?555?55555525552555s555#555~555555556r'   c                 x   t        ddd      \  } }| j                  ddi      } t        | |      \  }}g }d}||u }|}|rd}||u }	|	}|sXt        j                  d	|fd
||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }
dd|
iz  }|j                  |       |rt        j                  d		fd|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            d x}x}x}x}x}	}t        d       y )NTr   rp   r   r   tick_volumecolumnsFr   r   r   r   r   rv   r   r   r   r   r   r   r   r   z-absorption: tick_volume column fallback works)r   renamer   r7   r8   r9   r:   r;   r<   r   r   r>   r?   r&   r   s                   r%   $test_absorption_tick_volume_fallbackr$  Q  s
   !4#.13GB	Hm4	5B'C0GX0d07d?050x5000007d00000070007000d0000000x5000000x000x00050000000000000078r'   c            
     >   t        d      } | j                  dg      j                  d      }t        |      \  }}|d   }|j                  } |       }||k  }|d   }|j
                  }	 |	       }
||
k  }|r|st        j                  d||fd|||
f      t        j                  |      t        j                  |      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |	      t        j                  |
      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}x}x}x}	}
t        d|dd|dd       y)zFWithout a timestamp column matching today, simple VWAP fallback fires.rH   r   r!  T)dropr~   )<=r'  )zB%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.min
}()
} <= %(py8)szG%(py8)s <= %(py14)s
{%(py14)s = %(py12)s
{%(py12)s = %(py10)s.max
}()
}vwap)rY   r2   r4   r]   r  r   r   zassert %(py16)spy16Nzvwap: simple fallback vwap=rm   z dev=z+.2f%)r   r&  reset_indexr   r   r   r7   r8   r<   r9   r:   r;   r>   r?   r&   )rA   
df_no_timer(  devr^   rB   r   r  @py_assert9r   @py_assert13r  r   @py_format17s                 r%   test_vwap_simple_no_sessionr1  ^  s:   	B&*66D6AJ":.ID#gI""I"$I$II
70CI0C0G0GI0G0II0IIIIII$0IIIIIII"III$IIIIIIIIIIII0CIII0GIII0IIIIIIIII
%d3ZuSJa@Ar'   c                 `   t        j                  g d      } t        |       \  }}g }d}||k(  }|}|r	d}||k(  }|}|sXt        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	dd	|	iz  }
|j                  |
       |rt        j                  dfd
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            d x}x}x}x}x}}t        d       y )N)r   r   r   r~   r   r!  r   r   )z%(py2)s == %(py5)svr   r   rv   )z%(py9)s == %(py12)sdr   r   r   r   r   r   zvwap: empty df -> (0,0))r   r   r   r7   r8   r9   r:   r;   r<   r   r   r>   r?   r&   )rA   r3  r4  rC   r   r`   r^   r   r   rE   ry   r   r   r   r  s                  r%   test_vwap_zero_on_emptyr5  i  s    	H	IBb!DAq  18 S S    1      1   1          S            S              !"r'   c                 P   t        d      } t        | d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}|d   }d}||k\  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}t        d|d           y )Nr}   MESmarket_structureBULLISH_EXPANSIONr   z%(py1)s == %(py4)srY   rZ   assert %(py6)sr[   h1_struct_bullTr   z%(py1)s is %(py4)sh1_struct_bearFtrend_maturity   )>=)z%(py1)s >= %(py4)sz1structure: HH+HL series -> BULLISH_EXPANSION mat=)r   r   r7   r8   r<   r>   r?   r&   rA   resr^   r`   rB   @py_format5ra   s          r%    test_structure_bullish_expansionrF  t  s~   	"B
E
*C!"9&99"&99999"&9999"999&99999999 (D( D(((( D((( (((D((((((( )E) E)))) E))) )))E))))))) %A% A%%%% A%%% %%%A%%%%%%%
;C@P<Q;RSTr'   c                 D   t        d      } t        | d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}t        d       y )Nr}   r7  r8  BEARISH_EXPANSIONr   r:  r;  r<  r[   r?  Tr   r>  r=  Fz,structure: LH+LL series -> BEARISH_EXPANSION)r   r   r7   r8   r<   r>   r?   r&   rC  s          r%    test_structure_bearish_expansionrI  ~  s   	"B
E
*C!"9&99"&99999"&9999"999&99999999 (D( D(((( D((( (((D((((((( )E) E)))) E))) )))E)))))))67r'   c                 H   t        d      } t        | d      }|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d x}x}}t        d
       y )Nr}   r7  r8  RANGINGr   r:  r;  r<  r[   z$structure: ranging series -> RANGINGr   r   r7   r8   r<   r>   r?   r&   rC  s          r%   test_structure_rangingrM    ss    	BB
E
*C!"/i/"i////"i///"///i///////./r'   c                    t        d      } t        | d      }t        | d      }|d   }|d   }||k(  }d}||k(  }|r|st        j                  d||fd|||f      t        j                  |      t        j                  |      t        j                  |      dz  }d	d
|iz  }	t        t        j                  |	            dx}x}x}x}}t        d       y)zMES uses 0.20, FX uses 0.15. Same data, both must classify identically here
    because ranging fixture doesn't trigger the threshold logic. Smoke check
    that the threshold lookup honors the symbol.r}   r7  6Er8  rK  )r   r   )z%(py1)s == %(py5)sz%(py5)s == %(py7)s)rY   r4   rv   r	  r   Nz0structure: asset-specific threshold lookup wiredrL  )
rA   abr^   r   rB   r  r`   ry   r  s
             r%   #test_structure_uses_asset_thresholdrR    s     
BBb%(Ab$'A FA&8$9F $9FFYF$9YFFFFF $9YFFF FFF$9FFFYFFFFFFF:;r'   c                 `   t        dddd      } t        | ddd	      }|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d   }d}||k  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d|d           y )Nre   r}   rN   r   n	pivot_idx
base_price
pivot_dropBUYrH   g      Y@lookbackentry_priceswing_foundTr   r>  r;  r<  r[   
swing_type	SWING_LOWr   r:  swing_pricerI   )rW   z"swing: pivot LOW for BUY -> price=r    r   r7   r8   r<   r>   r?   r&   rC  s          r%   test_swing_low_found_for_buyrb    s1   	"u	MB
EB-24C}%%%%%%%%%%%%%%%%%%%|+++++++++++++++++++}%%%%%%%%%%%%%%%%%%%
,S-?,@ABr'   c                 `   t        dddd      } t        | ddd	      }|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d   }d}||kD  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d|d           y )Nre   r}   rN   r   )rU  rV  rW  
pivot_jumpSELLrH   g     X@rZ  r]  Tr   r>  r;  r<  r[   r^  
SWING_HIGHr   r:  r`  r,   )z%(py1)s > %(py4)sz$swing: pivot HIGH for SELL -> price=)r   r   r7   r8   r<   r>   r?   r&   rC  s          r%   test_swing_high_found_for_sellrg    s1   	2#	NB
FR-13C}%%%%%%%%%%%%%%%%%%%|,,,,,,,,,,,,,,,,,,,}%%%%%%%%%%%%%%%%%%%
.s=/A.BCDr'   c                 p   t        dd      } t        | dd| d   j                  d         }|d	   }d
}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}t        d       y )Nre   rp   r|   rY  rH   r~   r*   rZ  r]  Fr   r>  r;  r<  r[   z-swing: monotonic uptrend -> swing_found=False)	r   r   r6   r7   r8   r<   r>   r?   r&   rC  s          r%   !test_swing_not_found_on_monotonicri    s    	#	B
EB-/[-=-=b-ACC}&&&&&&&&&&&&&&&&&&&78r'   c                 T   t        dddd      } t        | ddd	      }|d
   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}t        d       y)z2Swing on wrong side of entry must be filtered out.re   r}   rN   r   rT  rY  rH   g     X@rZ  r]  Fr   r>  r;  r<  r[   Nz1swing: entry-side filter rejects wrong-side pivotra  rC  s          r%   !test_swing_filtered_by_entry_siderk    s    	"u	MB
EB-13C }&&&&&&&&&&&&&&&&&&&;<r'   c                    t        d       t                t                t                t	                t                t                t                t                t                t                t                t                t                t                t                t!                t#                t%                t'                t)                t+                t-                t/                t1                t3                t5                t7                t9                t;                t=                t?                tA                tC                tE                tG                tI                tK                t        d       y)Nztest_indicators.pyzALL 37 TESTS PASSEDr   )&r#   rF   rK   rc   rn   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r$  r1  r5  rF  rI  rM  rR  rb  rg  ri  rk   r'   r%   mainrn    s    	
"$&($&02!  "!#!#$&$&! "$&!# "!#%'(*!$&$&') ""$%'%'	
 r'   __main__)r$   strreturnNone)rj   r   )
r   boolr   floatr   rt  r   rt  rl   rt  )rq  int)]__doc__
__future__r   builtinsr9   _pytest.assertion.rewrite	assertionrewriter7   syspathlibr   pathinsertrp  __file__resolveparentpandasr   analysis.indicatorsr   r   r   r   r	   r
   r   r   r   r   r   r   r   r   r   tests._fixtures.barsr   r   r   r   r   r   r   r   r   r   r   r   r    r!   r&   rF   rK   rc   rn   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r$  r1  r5  rF  rI  rM  rR  rb  rg  ri  rk  rn  __name__exitrm  r'   r%   <module>r     s  	 #   
  3tH~--/66==> ?     "   $+-,2C9?&(
B"4/25,,*::8 AD%(9%*98=9"94NO56?479B#U80<CE9=(V zCHHTV r'   