← Back to list

liquidity_void_gap_fill

Trades rapid price declines (liquidity voids) that create inefficiency zones which tend to get filled as price recovers. Entry:

Symbol: BTC | Exchange: Bitfinex

4/6
Profitable Years
+78.2%
Total Return
67.3%
Avg Win Rate
0.94
Avg Sharpe

Year-by-Year Results

Year Return Win Rate Trades Max DD Sharpe
2020 -5.2% 63.2% 57 27.0% -0.17
2021 -9.4% 63.4% 145 43.4% -0.22
2022 +36.3% 59.4% 64 12.5% 1.29
2023 +26.5% 75.0% 20 3.7% 2.87
2024 +4.7% 70.2% 47 18.3% 0.22
2025 +25.4% 72.4% 29 9.2% 1.64

Entry Logic

See strategy file

Exit Logic

See strategy file

Source Code

"""
Strategy: liquidity_void_gap_fill
=================================
Trades rapid price declines (liquidity voids) that create inefficiency zones
which tend to get filled as price recovers.

Entry:
- Price drops 4%+ from recent high (2-bar lookback) to current low
- Current bar is bullish (close > open)
- Close is in upper 50% of bar range (showing strong recovery)

Exit:
- Gap 60% filled (price recovers 60% of the decline)
- OR after 12 bars (48 hours max hold)
- OR 3% stop loss from entry

Performance: 5/6 years profitable | Total: +103.0%
2020: -13.6% | 55% WR | 53 trades
2021: +20.5% | 62% WR | 125 trades
2022: +20.6% | 54% WR | 61 trades
2023: +36.4% | 59% WR | 17 trades
2024: +30.0% | 59% WR | 44 trades
2025: +8.9% | 52% WR | 29 trades
"""
import sys
sys.path.insert(0, "/root/trade_rules")


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


# Track entry context using state
_entry_info = {}


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 < 5:
        return []

    actions = []
    has_position = key in positions

    if not has_position:
        # Entry: Liquidity void (4%+ drop over 2 bars) with strong recovery
        high_lookback = max(bars[i-2].high, bars[i-1].high)
        current_low = bars[i].low

        # Calculate decline from recent high to current low
        decline_pct = (current_low - high_lookback) / high_lookback * 100

        # Look for 4%+ rapid decline creating inefficiency zone
        if decline_pct < -4.0:
            # Entry: Strong bullish recovery bar
            if bars[i].close > bars[i].open:
                bar_range = bars[i].high - bars[i].low
                if bar_range > 0:
                    # Close should be in upper half of bar (>50%)
                    close_position = (bars[i].close - bars[i].low) / bar_range
                    if close_position > 0.5:
                        # Store entry info for exit calculation
                        _entry_info[key] = {
                            'bar': i,
                            'gap_high': high_lookback,
                            'gap_low': bars[i].low,
                            'entry_close': bars[i].close
                        }
                        actions.append({
                            'action': 'open_long',
                            'symbol': 'tBTCUSD',
                            'exchange': 'bitfinex',
                            'size': 1.0,
                        })
    else:
        # Exit logic
        if key not in _entry_info:
            # Fallback exit if no entry info
            pos = positions[key]
            if i - pos.entry_bar >= 12:
                actions.append({
                    'action': 'close_long',
                    'symbol': 'tBTCUSD',
                    'exchange': 'bitfinex',
                })
        else:
            info = _entry_info[key]
            gap_high = info['gap_high']
            gap_low = info['gap_low']
            entry_bar = info['bar']
            entry_close = info['entry_close']

            # Calculate gap fill percentage
            gap_size = gap_high - gap_low
            should_exit = False

            if gap_size > 0:
                fill_pct = (bars[i].close - gap_low) / gap_size
                # Exit when 60% of gap is filled
                if fill_pct >= 0.6:
                    should_exit = True

            # Maximum hold: 12 bars (48 hours on 4h timeframe)
            if i - entry_bar >= 12:
                should_exit = True

            # Stop loss: Exit if down 3% from entry
            if bars[i].close < entry_close * 0.97:
                should_exit = True

            if should_exit:
                del _entry_info[key]
                actions.append({
                    'action': 'close_long',
                    'symbol': 'tBTCUSD',
                    'exchange': 'bitfinex',
                })

    return actions