0206ef7cbb
Real-time orderflow trading system with absorption, initiative, sweep, exhaustion, and divergence detection. Features volume profile framing, state machine trade lifecycle, MT5 + Bybit feeds, FastAPI dashboard, and Telegram alerts for 30+ instruments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
"""
|
|
Book Sweep Detector
|
|
Detects when price moves rapidly through multiple price levels with low volume.
|
|
|
|
From Fabio:
|
|
"Low effort and high result. A lot of executed orders on the bottom side of the
|
|
candle and then the candle closes with an amazing reward... there is movement
|
|
of the candle but an absence of participants. No sell limit players in all this area."
|
|
|
|
Logic:
|
|
LOW effort + HIGH displacement = BOOK SWEEP
|
|
- Multiple orderbook levels consumed rapidly
|
|
- Low volume per level (vacuum / no resistance)
|
|
- Price jumps through thin areas
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Optional
|
|
|
|
from orderflow_system.data.models import Candle, Signal, SignalType, Side
|
|
from orderflow_system.analytics.orderbook import OrderbookTracker, BookState
|
|
from orderflow_system.analytics.footprint import FootprintBar
|
|
from orderflow_system.config.settings import SweepConfig
|
|
|
|
|
|
class SweepDetector:
|
|
"""
|
|
Detects book sweeping by monitoring orderbook level consumption.
|
|
|
|
Sweep = price moves through multiple thin levels quickly with little
|
|
resistance. The market finds a vacuum and jets through it.
|
|
"""
|
|
|
|
def __init__(self, config: SweepConfig):
|
|
self.config = config
|
|
self._signal_history: list[Signal] = []
|
|
self._last_signal_ms: int = 0
|
|
self._cooldown_ms: int = 5000 # Min 5s between sweep signals
|
|
|
|
def check(
|
|
self,
|
|
orderbook_tracker: OrderbookTracker,
|
|
candle: Candle,
|
|
footprint: FootprintBar,
|
|
) -> Optional[Signal]:
|
|
"""
|
|
Check for book sweep based on recent level consumptions.
|
|
"""
|
|
now_ms = int(time.time() * 1000)
|
|
if now_ms - self._last_signal_ms < self._cooldown_ms:
|
|
return None
|
|
|
|
# Check asks swept (bullish sweep — price going UP through thin asks)
|
|
bull_signal = self._check_side(
|
|
orderbook_tracker, candle, footprint, side="ask", direction=Side.BUY
|
|
)
|
|
if bull_signal:
|
|
return bull_signal
|
|
|
|
# Check bids swept (bearish sweep — price going DOWN through thin bids)
|
|
bear_signal = self._check_side(
|
|
orderbook_tracker, candle, footprint, side="bid", direction=Side.SELL
|
|
)
|
|
return bear_signal
|
|
|
|
def _check_side(
|
|
self,
|
|
tracker: OrderbookTracker,
|
|
candle: Candle,
|
|
footprint: FootprintBar,
|
|
side: str,
|
|
direction: Side,
|
|
) -> Optional[Signal]:
|
|
"""Check sweep on one side of the book."""
|
|
levels_swept = tracker.count_swept_levels(
|
|
time_window_ms=int(self.config.max_time_ms), side=side
|
|
)
|
|
total_vol = tracker.total_consumed_volume(
|
|
time_window_ms=int(self.config.max_time_ms), side=side
|
|
)
|
|
|
|
if levels_swept < self.config.min_levels_swept:
|
|
return None
|
|
|
|
# Compute efficiency: levels per unit of volume
|
|
vol_per_level = total_vol / levels_swept if levels_swept > 0 else float("inf")
|
|
|
|
# Low effort = low volume per level
|
|
if vol_per_level > self.config.max_volume_per_level:
|
|
return None
|
|
|
|
# Also verify with footprint: check that the candle body is large
|
|
# relative to volume (high displacement, low effort)
|
|
if candle.volume > 0:
|
|
displacement_per_vol = candle.range_size / candle.volume
|
|
else:
|
|
displacement_per_vol = 0
|
|
|
|
# Compute strength
|
|
efficiency = levels_swept / max(vol_per_level, 0.01)
|
|
strength = min(100.0, (
|
|
levels_swept * 15
|
|
+ efficiency * 20
|
|
+ displacement_per_vol * 1000
|
|
))
|
|
|
|
# Check thin book confirmation from current state
|
|
book_state = tracker.latest_state
|
|
thin_confirm = False
|
|
if book_state:
|
|
if direction == Side.BUY and len(book_state.thin_asks) >= 2:
|
|
thin_confirm = True
|
|
elif direction == Side.SELL and len(book_state.thin_bids) >= 2:
|
|
thin_confirm = True
|
|
|
|
if thin_confirm:
|
|
strength = min(100.0, strength + 15)
|
|
|
|
if strength < 30:
|
|
return None
|
|
|
|
self._last_signal_ms = int(time.time() * 1000)
|
|
|
|
signal = Signal(
|
|
timestamp_ms=candle.timestamp_ms,
|
|
signal_type=SignalType.SWEEP,
|
|
direction=direction,
|
|
price_level=candle.close,
|
|
strength=strength,
|
|
details={
|
|
"levels_swept": levels_swept,
|
|
"total_volume_consumed": round(total_vol, 1),
|
|
"vol_per_level": round(vol_per_level, 2),
|
|
"efficiency": round(efficiency, 2),
|
|
"thin_book_confirmed": thin_confirm,
|
|
"candle_range": round(candle.range_size, 4),
|
|
},
|
|
)
|
|
self._signal_history.append(signal)
|
|
return signal
|