← Back to list

price_inefficiency_zone_fill

Gap fill continuation pattern - when price creates inefficiency zones through weak bars, then recovers through them showing buyer strength.

Symbol: BTC | Exchange: Bitfinex

5/6
Profitable Years
+120.0%
Total Return
39.7%
Avg Win Rate
0.71
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 +4.7% 42.9% 14 9.6% 0.25
2021 +60.8% 45.0% 20 8.5% 1.72
2022 +5.8% 50.0% 10 4.7% 0.85
2023 +3.9% 12.5% 16 16.1% 0.14
2024 +60.7% 66.7% 9 3.7% 2.71
2025 -15.9% 21.1% 19 15.3% -1.43

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: price_inefficiency_zone_fill
======================================
Gap fill continuation pattern - when price creates inefficiency zones
through weak bars, then recovers through them showing buyer strength.

Performance: 5/6 years profitable | Total: +123.3%
2020: +4.7% | 42% WR | 14 trades
2021: +60.9% | 45% WR | 20 trades
2022: +5.8% | 50% WR | 10 trades
2023: +5.9% | 12% WR | 16 trades
2024: +60.7% | 66% WR | 9 trades
2025: -14.7% | 21% WR | 19 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")
from lib import ema


def init_strategy():
    return {
        'name': 'price_inefficiency_zone_fill',
        'subscriptions': [
            {'symbol': 'tBTCUSD', 'exchange': 'bitfinex', 'timeframe': '4h'},
        ],
        'parameters': {}
    }


def process_time_step(ctx):
    key = ('tBTCUSD', 'bitfinex')
    bars = ctx['bars'].get(key, [])
    i = ctx['i']
    positions = ctx['positions']

    if not bars or i >= len(bars) or i < 50:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: Gap fill continuation pattern
        curr = bars[i]

        # Calculate EMA30 for trend
        closes_slice = [bars[j].close for j in range(max(0, i-100), i+1)]
        ema30_vals = ema(closes_slice, 30)

        if ema30_vals[-1] is None:
            return []

        # Must be above EMA30
        if curr.close < ema30_vals[-1]:
            return []

        # Look for 2-3 weak bars in recent past (2-4 bars ago)
        weak_count = 0
        highest_of_weak = 0

        for j in range(i-4, i-1):
            if j < 0:
                continue
            bar = bars[j]
            rng = bar.high - bar.low
            if rng == 0:
                continue

            close_pos = (bar.close - bar.low) / rng
            if close_pos < 0.3:  # Weak bar (close in lower 30%)
                weak_count += 1
                highest_of_weak = max(highest_of_weak, bar.high)

        # Need at least 2 weak bars creating an inefficiency zone
        if weak_count < 2:
            return []

        # Current bar must close above that weak zone (filled the gap)
        if curr.close <= highest_of_weak:
            return []

        # Must break 8-bar high (momentum)
        recent_high = max(bars[j].high for j in range(i-8, i))
        if curr.high <= recent_high:
            return []

        # Current bar must be strong (close in upper 60%)
        rng = curr.high - curr.low
        if rng == 0:
            return []

        close_pos = (curr.close - curr.low) / rng
        if close_pos < 0.6:
            return []

        actions.append({
            'action': 'open_long',
            'symbol': 'tBTCUSD',
            'exchange': 'bitfinex',
            'size': 1.0,
        })
    else:
        # Exit: close below EMA18
        closes_slice = [bars[j].close for j in range(max(0, i-100), i+1)]
        ema_vals = ema(closes_slice, 18)

        if ema_vals[-1] is not None and bars[i].close < ema_vals[-1]:
            actions.append({
                'action': 'close_long',
                'symbol': 'tBTCUSD',
                'exchange': 'bitfinex',
            })

    return actions