# indie:lang_version = 5
from indie import indicator, param, plot, color, MainContext
from indie.algorithms import Ema, Atr
import math

# Define helper functions at global scope
def is_downtrend(fast_ema: float, slow_ema: float) -> bool:
    return fast_ema < slow_ema

def is_uptrend(fast_ema: float, slow_ema: float) -> bool:
    return fast_ema > slow_ema

def is_significant_body(body: float, atr_value: float, minimum_body_atr: float) -> bool:
    return body > minimum_body_atr * atr_value

def is_doji(ctx: MainContext, i: int, atr_value: float, doji_ratio: float) -> bool:
    high, low = ctx.high[i], ctx.low[i]
    open_, close = ctx.open[i], ctx.close[i]
    body = abs(close - open_)
    range_ = high - low
    
    # Check if range is significant compared to market volatility
    is_significant = range_ > 0.3 * atr_value
    
    # Traditional doji has very small body relative to range
    return is_significant and body <= doji_ratio * range_

def is_small_body(ctx: MainContext, i: int) -> bool:
    open_, close = ctx.open[i], ctx.close[i]
    high, low = ctx.high[i], ctx.low[i]
    body = abs(close - open_)
    range_ = high - low
    
    return body < 0.3 * range_

def is_bullish_engulfing(ctx: MainContext, atr_value: float, minimum_body_atr: float, volume_ratio: float) -> bool:
    body_prev = abs(ctx.close[1] - ctx.open[1])
    body_curr = abs(ctx.close[0] - ctx.open[0])
    
    # Check for significant body size
    is_significant = is_significant_body(body_curr, atr_value, minimum_body_atr)
    
    return (
        ctx.close[1] < ctx.open[1] and  # Previous candle is bearish
        ctx.close[0] > ctx.open[0] and  # Current candle is bullish
        ctx.open[0] <= ctx.close[1] and ctx.close[0] >= ctx.open[1] and  # Body engulfing
        is_significant and
        ctx.volume[0] > ctx.volume[1] * volume_ratio  # Volume confirmation
    )

def is_bearish_engulfing(ctx: MainContext, atr_value: float, minimum_body_atr: float, volume_ratio: float) -> bool:
    body_prev = abs(ctx.close[1] - ctx.open[1])
    body_curr = abs(ctx.close[0] - ctx.open[0])
    
    # Check for significant body size
    is_significant = is_significant_body(body_curr, atr_value, minimum_body_atr)
    
    return (
        ctx.close[1] > ctx.open[1] and  # Previous candle is bullish
        ctx.close[0] < ctx.open[0] and  # Current candle is bearish
        ctx.open[0] >= ctx.close[1] and ctx.close[0] <= ctx.open[1] and  # Body engulfing
        is_significant and
        ctx.volume[0] > ctx.volume[1] * volume_ratio  # Volume confirmation
    )

def is_morning_star(ctx: MainContext, atr_value: float, minimum_body_atr: float, gap_atr_ratio: float) -> bool:
    open1, close1 = ctx.open[2], ctx.close[2]
    open2, close2 = ctx.open[1], ctx.close[1]
    open3, close3 = ctx.open[0], ctx.close[0]

    body1 = abs(close1 - open1)
    body3 = abs(close3 - open3)
    
    # First candle should be bearish and significant
    is_bearish1 = close1 < open1 
    is_significant1 = is_significant_body(body1, atr_value, minimum_body_atr)
    
    # Second candle should have a small body
    is_small_body2 = is_small_body(ctx, 1)
    
    # Third candle should be bullish and significant
    is_bullish3 = close3 > open3
    is_significant3 = is_significant_body(body3, atr_value, minimum_body_atr)
    
    # Check for gaps or near-gaps using ATR
    gap_down = min(open1, close1) - max(open2, close2) > gap_atr_ratio * atr_value
    gap_up = min(open3, close3) - max(open2, close2) > gap_atr_ratio * atr_value
    
    # Check if third candle closes above midpoint of first
    closes_above_midpoint = close3 > (open1 + close1) / 2

    return (
        is_bearish1 and is_significant1 and
        is_small_body2 and
        is_bullish3 and is_significant3 and
        closes_above_midpoint and
        (gap_down or gap_up)  # At least one gap should be present
    )

def is_evening_star(ctx: MainContext, atr_value: float, minimum_body_atr: float, gap_atr_ratio: float) -> bool:
    open1, close1 = ctx.open[2], ctx.close[2]
    open2, close2 = ctx.open[1], ctx.close[1]
    open3, close3 = ctx.open[0], ctx.close[0]

    body1 = abs(close1 - open1)
    body3 = abs(close3 - open3)
    
    # First candle should be bullish and significant
    is_bullish1 = close1 > open1
    is_significant1 = is_significant_body(body1, atr_value, minimum_body_atr)
    
    # Second candle should have a small body
    is_small_body2 = is_small_body(ctx, 1)
    
    # Third candle should be bearish and significant
    is_bearish3 = close3 < open3
    is_significant3 = is_significant_body(body3, atr_value, minimum_body_atr)
    
    # Check for gaps or near-gaps using ATR
    gap_up = min(open2, close2) - max(open1, close1) > gap_atr_ratio * atr_value
    gap_down = min(open2, close2) - max(open3, close3) > gap_atr_ratio * atr_value
    
    # Check if third candle closes below midpoint of first
    closes_below_midpoint = close3 < (open1 + close1) / 2

    return (
        is_bullish1 and is_significant1 and
        is_small_body2 and
        is_bearish3 and is_significant3 and
        closes_below_midpoint and
        (gap_up or gap_down)  # At least one gap should be present
    )

def is_hammer(ctx: MainContext, in_downtrend: bool, atr_value: float, shadow_body_ratio: float) -> bool:
    open_, close = ctx.open[0], ctx.close[0]
    high, low = ctx.high[0], ctx.low[0]
    body = abs(close - open_)
    range_ = high - low
    upper_shadow = high - max(open_, close)
    lower_shadow = min(open_, close) - low
    
    is_significant = range_ > 0.5 * atr_value
    
    return (
        in_downtrend and
        is_significant and
        body > 0 and  # Ensure there is a body
        lower_shadow > shadow_body_ratio * body and  # Long lower shadow
        upper_shadow < 0.5 * body and  # Small upper shadow
        close >= open_  # Preferably bullish (close >= open)
    )

def is_shooting_star(ctx: MainContext, in_uptrend: bool, atr_value: float, shadow_body_ratio: float) -> bool:
    open_, close = ctx.open[0], ctx.close[0]
    high, low = ctx.high[0], ctx.low[0]
    body = abs(close - open_)
    range_ = high - low
    upper_shadow = high - max(open_, close)
    lower_shadow = min(open_, close) - low
    
    is_significant = range_ > 0.5 * atr_value
    
    return (
        in_uptrend and
        is_significant and
        body > 0 and  # Ensure there is a body
        upper_shadow > shadow_body_ratio * body and  # Long upper shadow
        lower_shadow < 0.5 * body and  # Small lower shadow
        close <= open_  # Preferably bearish (close <= open)
    )

def is_dark_cloud_cover(ctx: MainContext, in_uptrend: bool, atr_value: float, minimum_body_atr: float) -> bool:
    open1, close1 = ctx.open[1], ctx.close[1]
    open2, close2 = ctx.open[0], ctx.close[0]
    
    body1 = abs(close1 - open1)
    body2 = abs(close2 - open2)
    body_mid = (open1 + close1) / 2
    
    is_significant1 = is_significant_body(body1, atr_value, minimum_body_atr)
    is_significant2 = is_significant_body(body2, atr_value, minimum_body_atr)
    
    return (
        in_uptrend and
        close1 > open1 and  # First candle is bullish
        close2 < open2 and  # Second candle is bearish
        open2 > close1 and  # Second candle opens above first candle's close
        close2 < body_mid and  # Second candle closes below midpoint of first
        close2 > open1 and  # Second candle doesn't close below first candle's open
        is_significant1 and is_significant2
    )

def is_piercing(ctx: MainContext, in_downtrend: bool, atr_value: float, minimum_body_atr: float) -> bool:
    open1, close1 = ctx.open[1], ctx.close[1]
    open2, close2 = ctx.open[0], ctx.close[0]
    
    body1 = abs(close1 - open1)
    body2 = abs(close2 - open2)
    body_mid = (open1 + close1) / 2
    
    is_significant1 = is_significant_body(body1, atr_value, minimum_body_atr)
    is_significant2 = is_significant_body(body2, atr_value, minimum_body_atr)
    
    return (
        in_downtrend and
        close1 < open1 and  # First candle is bearish
        close2 > open2 and  # Second candle is bullish
        open2 < close1 and  # Second candle opens below first candle's close
        close2 > body_mid and  # Second candle closes above midpoint of first
        close2 < open1 and  # Second candle doesn't close above first candle's open
        is_significant1 and is_significant2
    )

def is_three_black_crows(ctx: MainContext, in_uptrend: bool, atr_value: float, minimum_body_atr: float) -> bool:
    # Calculate bearish body sizes
    b1 = ctx.open[2] - ctx.close[2]
    b2 = ctx.open[1] - ctx.close[1]
    b3 = ctx.open[0] - ctx.close[0]
    
    # Calculate shadows
    upper_shadow1 = ctx.high[2] - ctx.open[2]
    upper_shadow2 = ctx.high[1] - ctx.open[1]
    upper_shadow3 = ctx.high[0] - ctx.open[0]
    
    lower_shadow1 = ctx.close[2] - ctx.low[2]
    lower_shadow2 = ctx.close[1] - ctx.low[1]
    lower_shadow3 = ctx.close[0] - ctx.low[0]
    
    # Significant bodies and small shadows relative to bodies
    are_significant = (
        b1 > minimum_body_atr * atr_value and 
        b2 > minimum_body_atr * atr_value and 
        b3 > minimum_body_atr * atr_value
    )
    
    small_shadows = (
        upper_shadow1 < 0.3 * b1 and upper_shadow2 < 0.3 * b2 and upper_shadow3 < 0.3 * b3 and
        lower_shadow1 < 0.3 * b1 and lower_shadow2 < 0.3 * b2 and lower_shadow3 < 0.3 * b3
    )
    
    # Each opens within the previous candle's body (traditional definition)
    proper_opens = (
        ctx.open[1] <= ctx.open[2] and ctx.open[1] >= ctx.close[2] and
        ctx.open[0] <= ctx.open[1] and ctx.open[0] >= ctx.close[1]
    )
    
    return (
        in_uptrend and
        # All three candles are bearish
        ctx.close[2] < ctx.open[2] and ctx.close[1] < ctx.open[1] and ctx.close[0] < ctx.open[0] and
        # Each closes lower than the previous
        ctx.close[1] < ctx.close[2] and ctx.close[0] < ctx.close[1] and
        are_significant and
        small_shadows and
        proper_opens
    )

def is_three_white_soldiers(ctx: MainContext, in_downtrend: bool, atr_value: float, minimum_body_atr: float) -> bool:
    # Calculate bullish body sizes
    b1 = ctx.close[2] - ctx.open[2]
    b2 = ctx.close[1] - ctx.open[1]
    b3 = ctx.close[0] - ctx.open[0]
    
    # Calculate shadows
    upper_shadow1 = ctx.high[2] - ctx.close[2]
    upper_shadow2 = ctx.high[1] - ctx.close[1]
    upper_shadow3 = ctx.high[0] - ctx.close[0]
    
    lower_shadow1 = ctx.open[2] - ctx.low[2]
    lower_shadow2 = ctx.open[1] - ctx.low[1]
    lower_shadow3 = ctx.open[0] - ctx.low[0]
    
    # Significant bodies and small shadows relative to bodies
    are_significant = (
        b1 > minimum_body_atr * atr_value and 
        b2 > minimum_body_atr * atr_value and 
        b3 > minimum_body_atr * atr_value
    )
    
    small_shadows = (
        upper_shadow1 < 0.3 * b1 and upper_shadow2 < 0.3 * b2 and upper_shadow3 < 0.3 * b3 and
        lower_shadow1 < 0.3 * b1 and lower_shadow2 < 0.3 * b2 and lower_shadow3 < 0.3 * b3
    )
    
    # Each opens within the previous candle's body (traditional definition)
    proper_opens = (
        ctx.open[1] >= ctx.open[2] and ctx.open[1] <= ctx.close[2] and
        ctx.open[0] >= ctx.open[1] and ctx.open[0] <= ctx.close[1]
    )
    
    return (
        in_downtrend and
        # All three candles are bullish
        ctx.close[2] > ctx.open[2] and ctx.close[1] > ctx.open[1] and ctx.close[0] > ctx.open[0] and
        # Each closes higher than the previous
        ctx.close[1] > ctx.close[2] and ctx.close[0] > ctx.close[1] and
        are_significant and
        small_shadows and
        proper_opens
    )

@indicator('Enhanced Candlestick Patterns v3', overlay_main_pane=True)
@param.float('doji_ratio', default=0.05, min=0.01, max=0.2, title='Doji Body/Range Ratio')
@param.float('shadow_body_ratio', default=2.0, min=1.0, max=5.0, title='Shadow/Body Ratio')
@param.float('minimum_body_atr', default=0.3, min=0.1, max=1.0, title='Minimum Body/ATR Ratio')
@param.float('gap_atr_ratio', default=0.1, min=0.0, max=0.5, title='Gap/ATR Ratio')
@param.float('volume_ratio', default=1.2, min=1.0, max=3.0, title='Volume Increase Ratio')
@param.int('fast_ema', default=20, min=5, max=50, title='Fast EMA Length')
@param.int('slow_ema', default=50, min=20, max=200, title='Slow EMA Length')
# Bullish Patterns (varying shades of green/teal)
@plot.marker(color=color.GREEN, text='BE', style=plot.marker_style.LABEL, position=plot.marker_position.BELOW)  # Bullish Engulfing
@plot.marker(color=color.TEAL, text='MORN', style=plot.marker_style.LABEL, position=plot.marker_position.BELOW)  # Morning Star
@plot.marker(color=color.LIME, text='H', style=plot.marker_style.LABEL, position=plot.marker_position.BELOW)  # Hammer
@plot.marker(color=color.rgba(0, 180, 80, 1), text='P', style=plot.marker_style.LABEL, position=plot.marker_position.BELOW)  # Piercing
@plot.marker(color=color.rgba(0, 128, 64, 1), text='TWS', style=plot.marker_style.LABEL, position=plot.marker_position.BELOW)  # Three White Soldiers

# Bearish Patterns (varying shades of red/purple)
@plot.marker(color=color.RED, text='SE', style=plot.marker_style.LABEL, position=plot.marker_position.ABOVE)  # Bearish Engulfing
@plot.marker(color=color.PURPLE, text='EVE', style=plot.marker_style.LABEL, position=plot.marker_position.ABOVE)  # Evening Star
@plot.marker(color=color.FUCHSIA, text='SS', style=plot.marker_style.LABEL, position=plot.marker_position.ABOVE)  # Shooting Star
@plot.marker(color=color.rgba(180, 0, 80, 1), text='DCC', style=plot.marker_style.LABEL, position=plot.marker_position.ABOVE)  # Dark Cloud Cover
@plot.marker(color=color.MAROON, text='TBC', style=plot.marker_style.LABEL, position=plot.marker_position.ABOVE)  # Three Black Crows
def Main(self, doji_ratio, shadow_body_ratio, minimum_body_atr, gap_atr_ratio, volume_ratio, fast_ema, slow_ema):
    # Main indicator logic
    h = self.high[0] - self.low[0]

    # Compute indicators
    fast_ema_series = Ema.new(self.close, fast_ema)
    slow_ema_series = Ema.new(self.close, slow_ema)
    atr14 = Atr.new(14)

    in_downtrend = is_downtrend(fast_ema_series[0], slow_ema_series[0])
    in_uptrend = is_uptrend(fast_ema_series[0], slow_ema_series[0])

    return (
        self.low[0] - h * 0.05 if is_bullish_engulfing(self, atr14[0], minimum_body_atr, volume_ratio) else math.nan,
        self.high[0] + h * 0.05 if is_bearish_engulfing(self, atr14[0], minimum_body_atr, volume_ratio) else math.nan,
        self.low[0] - h * 0.1 if is_morning_star(self, atr14[0], minimum_body_atr, gap_atr_ratio) else math.nan,
        self.high[0] + h * 0.1 if is_evening_star(self, atr14[0], minimum_body_atr, gap_atr_ratio) else math.nan,
        self.low[0] - h * 0.15 if is_hammer(self, in_downtrend, atr14[0], shadow_body_ratio) else math.nan,
        self.high[0] + h * 0.15 if is_shooting_star(self, in_uptrend, atr14[0], shadow_body_ratio) else math.nan,
        self.high[0] + h * 0.2 if is_dark_cloud_cover(self, in_uptrend, atr14[0], minimum_body_atr) else math.nan,
        self.low[0] - h * 0.2 if is_piercing(self, in_downtrend, atr14[0], minimum_body_atr) else math.nan,
        self.high[0] + h * 0.25 if is_three_black_crows(self, in_uptrend, atr14[0], minimum_body_atr) else math.nan,
        self.low[0] - h * 0.25 if is_three_white_soldiers(self, in_downtrend, atr14[0], minimum_body_atr) else math.nan
    )