Skip to main content

CL Cascade Analyzer Blueprint

1. Executive Summary

What This Add-on Does

The CL Cascade Analyzer is a Bookmap Layer 1 Simplified API add-on that detects, decomposes, and scores liquidity sweep cascades on Crude Oil futures in real time. It operates as a context layer that complements Bookmap's built-in Sweeps indicator by providing the analytical depth required for trade decision-making.

The Problem It Solves

Bookmap's built-in Sweeps indicator answers: "Did a sweep happen?"

This add-on answers:

  • Was the book thinning before the sweep? (Early warning)
  • How fast did the spread blow out? (Cascade confirmation)
  • What is the wave structure of the cascade? (Severity assessment)
  • Is absorption occurring at the terminus? (Entry signal)
  • Is the recovery genuine or short-cover driven? (Hold vs. scalp decision)

Design Principles

  1. Single-class implementation — One Java file, no external dependencies beyond Bookmap API
  2. CL-specific calibration — All thresholds tuned to crude oil microstructure ($0.01 tick, thin book, headline sensitivity)
  3. Indicator output — All signals rendered as Bookmap chart indicators for visual consumption
  4. Log output — All events logged with full context for post-session analysis and future statistical validation
  5. No trading logic — Pure analysis; no order placement or position management

2. Architecture Overview

Class Name

CascadeAnalyzer

Package

com.bookmap.addon.cascade

Interfaces Implemented

InterfacePurpose
DepthDataListenerTrack order book depth changes at each price level
TradeDataListenerProcess individual trades for cascade wave detection
BboListenerMonitor best bid/offer for spread expansion detection
TimeListenerMaintain nanosecond timestamp synchronization
IntervalListenerPeriodic indicator updates and statistics computation
HistoricalModeListenerCalibrate baselines from historical data before real-time

Annotations

@Layer1SimpleAttachable
@Layer1StrategyName("CL Cascade Analyzer")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)

Indicator Registrations

Register these indicators in initialize():

Indicator NameGraphTypePurpose
"Book Depth Bid (Top 20)"BOTTOMRolling sum of bid-side resting size across top 20 levels
"Book Depth Ask (Top 20)"BOTTOMRolling sum of ask-side resting size across top 20 levels
"Depth Rate of Change"BOTTOMRate at which book depth is changing (thinning = negative)
"Spread (ticks)"BOTTOMCurrent bid-ask spread in ticks
"Spread Expansion Rate"BOTTOMHow fast the spread is widening (ticks per 100ms)
"Cascade Active"BOTTOMBinary: 1.0 when cascade is in progress, 0.0 otherwise
"Cascade Wave Count"BOTTOMNumber of discrete waves in current/recent cascade
"Absorption Score"BOTTOM0.0–1.0 score of absorption quality at cascade terminus
"Recovery Quality"BOTTOMRatio of buy-sweep volume to sell-sweep volume post-cascade

3. Internal Data Structures

3.1 Order Book Tracker

Maintains a real-time representation of bid and ask depth for rate-of-change computation.

STRUCTURE: OrderBookTracker
FIELDS:
bids: TreeMap<Integer, Integer> — key=price (descending), value=size
asks: TreeMap<Integer, Integer> — key=price (ascending), value=size
bidDepthHistory: CircularBuffer<DepthSnapshot> — rolling 5-second window
askDepthHistory: CircularBuffer<DepthSnapshot> — rolling 5-second window

INNER STRUCTURE: DepthSnapshot
FIELDS:
timestamp: long (nanoseconds)
totalBidDepth: int (sum of top 20 bid levels)
totalAskDepth: int (sum of top 20 ask levels)

Update logic (called from onDepth):

  • If size == 0, remove the price level from the appropriate side
  • If size > 0, put/update the price level
  • On every update, recompute top-20 depth sums
  • Store a DepthSnapshot in the circular buffer with current timestamp

Rate-of-change computation (called from onInterval every 100ms):

  • Compare current top-20 depth to depth 500ms ago and 1000ms ago
  • depthRateOfChange = (currentDepth - depth500msAgo) / depth500msAgo * 100
  • If depthRateOfChange < -30% (30% depth reduction in 500ms), flag as BOOK_THINNING

CL-specific constants:

  • TOP_LEVELS = 20 — Number of price levels to sum for depth
  • DEPTH_HISTORY_WINDOW_NS = 5_000_000_000L — 5-second rolling window
  • THINNING_THRESHOLD_PCT = -30.0 — 30% depth drop triggers thinning alert

3.2 Spread Monitor

Tracks BBO state and detects spread expansion.

STRUCTURE: SpreadMonitor
FIELDS:
currentBidPrice: int
currentBidSize: int
currentAskPrice: int
currentAskSize: int
currentSpreadTicks: int
baselineSpreadTicks: double — EMA of spread over session
spreadHistory: CircularBuffer<SpreadSnapshot> — rolling 2-second window
spreadExpansionRate: double — ticks per 100ms

INNER STRUCTURE: SpreadSnapshot
FIELDS:
timestamp: long
spreadTicks: int
bidSize: int
askSize: int

Update logic (called from onBbo):

  • Compute spreadTicks = askPrice - bidPrice (on CL with $0.01 tick, this is in cents)
  • Store SpreadSnapshot in circular buffer
  • Update EMA baseline: baselineSpreadTicks = 0.001 * spreadTicks + 0.999 * baselineSpreadTicks
  • Compute expansion rate: compare current spread to spread 100ms ago

Spread blowout detection:

  • If currentSpreadTicks > baselineSpreadTicks * 3 AND currentSpreadTicks >= 5, flag as SPREAD_BLOWOUT
  • CL normal spread: 1–2 ticks. Blowout: 5+ ticks. Severe: 15+ ticks.

CL-specific constants:

  • SPREAD_BLOWOUT_MULTIPLIER = 3.0 — Multiples of baseline spread that trigger blowout
  • SPREAD_BLOWOUT_MIN_TICKS = 5 — Absolute minimum spread for blowout (prevents false triggers during tight markets)
  • SPREAD_HISTORY_WINDOW_NS = 2_000_000_000L — 2-second rolling window
  • SPREAD_EMA_ALPHA = 0.001 — Very slow EMA for session-level baseline

3.3 Cascade Detector & Wave Decomposer

The core detection engine. Identifies cascade initiation, tracks individual waves, and determines cascade termination.

STRUCTURE: CascadeDetector
FIELDS:
state: CascadeState enum {IDLE, PRE_CASCADE, ACTIVE_CASCADE, ABSORPTION, RECOVERY}
currentCascade: CascadeEvent (null when IDLE)
recentCascades: List<CascadeEvent> — completed cascades for session statistics

// Detection parameters
aggressiveVolumeAccumulator: int — volume in current burst window
priceAtBurstStart: int — price when current burst began
burstStartTimestamp: long — when current burst began
levelsConsumed: int — price levels traversed in current burst

INNER STRUCTURE: CascadeEvent
FIELDS:
startTimestamp: long
endTimestamp: long
direction: enum {SELL_CASCADE, BUY_CASCADE}
startPrice: int
lowPrice: int (for sell cascade) / highPrice: int (for buy cascade)
endPrice: int — price when cascade declared over
totalAggressiveVolume: int
waves: List<CascadeWave>
preBookDepthBid: int — snapshot of bid depth before cascade
preBookDepthAsk: int — snapshot of ask depth before cascade
maxSpreadDuringCascade: int — widest spread observed
absorptionScore: double — 0.0 to 1.0
recoveryRatio: double — buy sweep volume / sell sweep volume post-cascade

INNER STRUCTURE: CascadeWave
FIELDS:
timestamp: long
startPrice: int
endPrice: int
volume: int
levelsConsumed: int
durationNs: long
interWavePauseNs: long — time since previous wave ended

State Machine Logic:

STATE: IDLE
TRANSITIONS:
→ PRE_CASCADE: When BOOK_THINNING detected AND spread begins expanding
→ ACTIVE_CASCADE: When aggressive burst meets cascade criteria directly
(skip PRE_CASCADE if cascade initiates without warning)

STATE: PRE_CASCADE
DESCRIPTION: Book is thinning, spread expanding, but no cascade burst yet
TRANSITIONS:
→ ACTIVE_CASCADE: When first cascade burst detected
→ IDLE: If book depth stabilizes and spread normalizes within 2 seconds
ACTIONS:
- Snapshot current book depth (pre-cascade baseline)
- Begin enhanced monitoring (100ms interval checks)
- Log: "PRE_CASCADE: Book thinning detected. Bid depth: X, Ask depth: Y, Spread: Z"

STATE: ACTIVE_CASCADE
DESCRIPTION: Cascade is in progress. Track individual waves.
TRANSITIONS:
→ ABSORPTION: When 500ms passes with no new wave AND aggressive counter-volume appears
→ IDLE: When 2 seconds pass with no new wave AND no counter-volume
(cascade exhausted without absorption — dead cat bounce risk)
ACTIONS:
- On each qualifying burst: create CascadeWave, append to currentCascade.waves
- Track cumulative aggressive volume
- Track max spread
- Track extreme price (low for sell cascade, high for buy cascade)
- Update Cascade Active indicator = 1.0
- Log each wave: "WAVE N: price X→Y, volume Z, levels L, pause P ms"

STATE: ABSORPTION
DESCRIPTION: Cascade has paused and counter-volume is appearing
TRANSITIONS:
→ RECOVERY: When aggressive counter-volume exceeds 30% of cascade volume
AND bid/ask depth is rebuilding
→ ACTIVE_CASCADE: If another cascade wave fires (absorption failed)
→ IDLE: After 5 seconds if neither recovery nor new wave
ACTIONS:
- Compute absorption score (see Section 3.4)
- Track bid rebuild rate (for sell cascade) or ask rebuild rate (for buy cascade)
- Log: "ABSORPTION at price X. Score: Y. Counter-volume: Z"

STATE: RECOVERY
DESCRIPTION: Price is recovering from cascade extreme
TRANSITIONS:
→ IDLE: After 30 seconds OR when price recovers 50%+ of cascade range
ACTIONS:
- Compute recovery ratio (counter-sweep volume / cascade volume)
- Finalize CascadeEvent, add to recentCascades
- Update all indicators with final cascade metrics
- Log full cascade summary (see Section 6)

Cascade Burst Detection Criteria (applied in onTrade):

A cascade burst is detected when ALL of the following are true within a sliding window:

  1. aggressiveVolumeAccumulator >= 25 contracts within 150ms (CL threshold)
  2. levelsConsumed >= 4 price levels ($0.04 on CL)
  3. All/most trades are on the same aggressor side (>80% directional)

On each onTrade call:

  • If trade direction matches current burst direction AND timestamp within 150ms of burst start:
    • Increment aggressiveVolumeAccumulator += size
    • Update levelsConsumed if price moved to new level
  • If timestamp exceeds burst window:
    • Evaluate if accumulated burst meets criteria
    • If yes: create CascadeWave, check state transitions
    • Reset accumulator for next burst window

3.4 Absorption Scorer

Computes a 0.0–1.0 score indicating absorption quality at the cascade terminus.

STRUCTURE: AbsorptionScorer
INPUTS (measured over 2-second window after cascade's last wave):
counterAggressiveVolume: int — aggressive volume opposing the cascade direction
passiveLiquidityRebuilding: boolean — are resting orders being placed at/near the extreme?
spreadNormalizing: boolean — is spread returning toward baseline?
priceStabilizing: boolean — has price stopped moving in cascade direction?
deltaAtExtreme: int — net delta at the cascade extreme price level

SCORING FORMULA:
score = 0.0

// Component 1: Counter-aggressive volume (0–0.30)
// Measures aggressive buying at sell cascade low (or aggressive selling at buy cascade high)
volumeRatio = counterAggressiveVolume / (cascadeVolumeInLastWave + 1)
score += min(0.30, volumeRatio * 0.30)

// Component 2: Passive liquidity rebuild (0–0.25)
// Are market makers re-establishing quotes near the extreme?
IF passiveLiquidityRebuilding:
score += 0.25
ELSE:
score += 0.0

// Component 3: Spread normalization (0–0.20)
// Is the spread returning to normal? (market makers returning)
spreadRatio = baselineSpread / currentSpread // 1.0 = fully normalized
score += min(0.20, spreadRatio * 0.20)

// Component 4: Price stabilization (0–0.15)
// Has price stopped making new extremes for 500ms+?
IF timeSinceLastExtreme > 500ms:
score += 0.15
ELIF timeSinceLastExtreme > 250ms:
score += 0.08

// Component 5: Delta divergence signal (0–0.10)
// Is delta at the extreme showing counter-direction pressure?
IF deltaAtExtreme opposes cascade direction:
score += 0.10

OUTPUT:
absorptionScore = score // 0.0 to 1.0

INTERPRETATION:
0.0–0.3: No meaningful absorption. Dead cat bounce likely.
0.3–0.5: Weak absorption. Partial recovery possible but unreliable.
0.5–0.7: Moderate absorption. Recovery probable but may retest.
0.7–1.0: Strong absorption. High-confidence reversal. Institutional buyers/sellers present.

4. Listener Implementation Specifications

4.1 onDepth(boolean isBid, int price, int size)

PURPOSE: Maintain order book and compute depth rate-of-change

LOGIC:
1. Update OrderBookTracker:
IF size == 0:
Remove price from bids/asks map
ELSE:
Put price → size in bids/asks map

2. Recompute top-20 depth sums:
bidDepthTop20 = sum of first 20 values in bids TreeMap
askDepthTop20 = sum of first 20 values in asks TreeMap

3. Store DepthSnapshot with currentTimestamp

4. IF state == ABSORPTION:
Check if resting orders are being placed near cascade extreme price
Update passiveLiquidityRebuilding flag

5. IF state == IDLE or PRE_CASCADE:
Compute depth rate-of-change vs 500ms ago
IF rateOfChange < THINNING_THRESHOLD_PCT:
IF state == IDLE:
Transition to PRE_CASCADE
Snapshot pre-cascade book depth
Log: "BOOK THINNING: Bid depth dropped X% in 500ms"

4.2 onTrade(double price, int size, TradeInfo tradeInfo)

PURPOSE: Detect cascade bursts and track wave structure

LOGIC:
1. Determine aggressor side: tradeInfo.isBidAggressor

2. Accumulate into current burst window:
IF no active burst OR timestamp - burstStartTimestamp > BURST_WINDOW_NS:
// Evaluate previous burst (if any)
evaluateBurst()
// Start new burst
burstStartTimestamp = currentTimestamp
priceAtBurstStart = (int) price
aggressiveVolumeAccumulator = size
burstDirection = tradeInfo.isBidAggressor
levelsConsumed = 1
directionalTradeCount = 1
totalTradeCount = 1
ELSE:
aggressiveVolumeAccumulator += size
totalTradeCount++
IF trade direction matches burstDirection:
directionalTradeCount++
Update levelsConsumed based on price movement from burstStartPrice

3. IF state == ABSORPTION or RECOVERY:
Track counter-aggressive volume for absorption scoring and recovery ratio

FUNCTION evaluateBurst():
directionality = directionalTradeCount / totalTradeCount

IF aggressiveVolumeAccumulator >= BURST_VOLUME_THRESHOLD
AND levelsConsumed >= BURST_LEVELS_THRESHOLD
AND directionality >= 0.80:

// This is a qualifying cascade wave
wave = new CascadeWave(...)

IF state == IDLE or PRE_CASCADE:
// CASCADE INITIATION
Create new CascadeEvent
Snapshot pre-cascade book depth (if not already done in PRE_CASCADE)
Transition to ACTIVE_CASCADE
Log: "CASCADE INITIATED: direction=SELL/BUY, trigger volume=X at price Y"

ELIF state == ACTIVE_CASCADE:
// ADDITIONAL WAVE
Append wave to currentCascade.waves
Update extreme price
Log wave details

ELIF state == ABSORPTION:
// ABSORPTION FAILED — cascade resuming
Transition back to ACTIVE_CASCADE
Log: "ABSORPTION FAILED — new wave detected"

4.3 onBbo(int bidPrice, int bidSize, int askPrice, int askSize)

PURPOSE: Monitor spread dynamics and detect spread blowouts

LOGIC:
1. Compute spreadTicks = askPrice - bidPrice

2. Store SpreadSnapshot in circular buffer

3. Update baseline EMA:
baselineSpreadTicks = SPREAD_EMA_ALPHA * spreadTicks + (1 - SPREAD_EMA_ALPHA) * baselineSpreadTicks

4. Compute spread expansion rate:
snapshot100msAgo = findSnapshotAtTime(currentTimestamp - 100_000_000)
IF snapshot100msAgo != null:
spreadExpansionRate = (spreadTicks - snapshot100msAgo.spreadTicks) / 0.1 // ticks per second

5. Detect spread blowout:
IF spreadTicks > baselineSpreadTicks * SPREAD_BLOWOUT_MULTIPLIER
AND spreadTicks >= SPREAD_BLOWOUT_MIN_TICKS:
Log: "SPREAD BLOWOUT: current=X ticks, baseline=Y ticks, expansion rate=Z ticks/sec"
IF state == IDLE:
// Spread blowout alone can signal PRE_CASCADE
Transition to PRE_CASCADE

6. IF state == ACTIVE_CASCADE:
Track max spread: maxSpreadDuringCascade = max(maxSpreadDuringCascade, spreadTicks)

7. IF state == ABSORPTION:
Check spread normalization for absorption scoring:
spreadNormalizing = (spreadTicks < baselineSpreadTicks * 1.5)

8. Update indicators:
spreadIndicator.addPoint(spreadTicks)
spreadExpansionRateIndicator.addPoint(spreadExpansionRate)

4.4 onTimestamp(long nanoseconds)

PURPOSE: Synchronize all time-dependent computations

LOGIC:
currentTimestamp = nanoseconds

// Check state timeouts
IF state == PRE_CASCADE AND (currentTimestamp - preCascadeEntryTime > 2_000_000_000):
// 2 seconds without cascade initiation — false alarm
Transition to IDLE
Log: "PRE_CASCADE expired — book depth stabilized"

IF state == ACTIVE_CASCADE:
timeSinceLastWave = currentTimestamp - lastWaveTimestamp
IF timeSinceLastWave > 500_000_000: // 500ms
// Check for counter-volume to decide ABSORPTION vs IDLE
IF counterVolumeDetected:
Transition to ABSORPTION
Begin absorption scoring
ELIF timeSinceLastWave > 2_000_000_000: // 2 seconds
Transition to IDLE
Finalize cascade as "exhaustion without absorption"

IF state == ABSORPTION AND (currentTimestamp - absorptionEntryTime > 5_000_000_000):
// 5 seconds in absorption without resolution
Finalize cascade
Transition to IDLE

IF state == RECOVERY AND (currentTimestamp - recoveryEntryTime > 30_000_000_000):
// 30 seconds — recovery phase complete
Finalize cascade
Transition to IDLE

4.5 getInterval() and onInterval()

PURPOSE: Periodic indicator updates and depth rate-of-change computation

INTERVAL: Intervals.INTERVAL_100_MILLISECONDS (100ms)

LOGIC:
1. Compute depth rate-of-change:
current = getCurrentBidDepthTop20() + getCurrentAskDepthTop20()
historical = getDepthAt(currentTimestamp - 500_000_000) // 500ms ago
IF historical > 0:
rateOfChange = (current - historical) / (double) historical * 100.0
ELSE:
rateOfChange = 0.0

2. Update indicators:
bidDepthIndicator.addPoint(getCurrentBidDepthTop20())
askDepthIndicator.addPoint(getCurrentAskDepthTop20())
depthRocIndicator.addPoint(rateOfChange)
cascadeActiveIndicator.addPoint(state == ACTIVE_CASCADE ? 1.0 : 0.0)
waveCountIndicator.addPoint(currentCascade != null ? currentCascade.waves.size() : 0)
absorptionScoreIndicator.addPoint(currentAbsorptionScore)
recoveryQualityIndicator.addPoint(currentRecoveryRatio)

4.6 onRealtimeStart()

PURPOSE: Calibrate baselines from historical data

LOGIC:
Log: "Historical processing complete. Calibrating baselines..."
Log: "Baseline bid depth: X, ask depth: Y, spread: Z ticks"
Log: "Transitioning to real-time mode"

// The EMA baselines for spread and depth are already populated
// from historical data processing. No special action needed
// beyond logging the calibrated values.

5. CL-Specific Constants

All thresholds in one centralized location for easy tuning:

// === BOOK DEPTH MONITORING ===
TOP_LEVELS = 20 // Price levels to sum for depth
DEPTH_HISTORY_WINDOW_NS = 5_000_000_000L // 5-second rolling window
THINNING_THRESHOLD_PCT = -30.0 // 30% depth drop = thinning alert
DEPTH_SNAPSHOT_INTERVAL_NS = 100_000_000L // Store snapshot every 100ms

// === SPREAD MONITORING ===
SPREAD_BLOWOUT_MULTIPLIER = 3.0 // 3x baseline = blowout
SPREAD_BLOWOUT_MIN_TICKS = 5 // Absolute minimum for blowout
SPREAD_HISTORY_WINDOW_NS = 2_000_000_000L // 2-second window
SPREAD_EMA_ALPHA = 0.001 // Very slow baseline EMA

// === CASCADE BURST DETECTION ===
BURST_WINDOW_NS = 150_000_000L // 150ms burst aggregation window
BURST_VOLUME_THRESHOLD = 25 // Min contracts per burst
BURST_LEVELS_THRESHOLD = 4 // Min price levels consumed ($0.04 on CL)
BURST_DIRECTIONALITY_THRESHOLD = 0.80 // 80% same-side trades

// === STATE MACHINE TIMEOUTS ===
PRE_CASCADE_TIMEOUT_NS = 2_000_000_000L // 2 seconds to initiate or expire
CASCADE_WAVE_GAP_NS = 500_000_000L // 500ms gap between waves → possible absorption
CASCADE_EXHAUSTION_NS = 2_000_000_000L // 2 seconds no wave → exhaustion
ABSORPTION_TIMEOUT_NS = 5_000_000_000L // 5 seconds to resolve absorption
RECOVERY_TIMEOUT_NS = 30_000_000_000L // 30 seconds to complete recovery

// === ABSORPTION SCORING ===
ABSORPTION_COUNTER_VOLUME_THRESHOLD = 0.30 // 30% counter-volume → enter RECOVERY
ABSORPTION_WINDOW_NS = 2_000_000_000L // 2-second scoring window

// === CIRCULAR BUFFER SIZES ===
DEPTH_BUFFER_SIZE = 100 // ~10 seconds at 100ms intervals
SPREAD_BUFFER_SIZE = 200 // ~2 seconds at ~10ms BBO update rate

// === INDICATOR UPDATE INTERVAL ===
INDICATOR_INTERVAL = Intervals.INTERVAL_100_MILLISECONDS

6. Logging Specification

All log output uses Log.info() for normal events and Log.warn() for anomalies. Format all prices by multiplying the integer price by pips (obtained from InstrumentInfo.pips in initialize()).

Event Log Formats

// Pre-cascade warning
"[CASCADE] PRE_CASCADE: Book thinning detected. Bid depth: {bidTop20} ({roc}% vs 500ms ago), Spread: {spread} ticks (baseline: {baseline})"

// Cascade initiation
"[CASCADE] INITIATED: direction={SELL|BUY}, trigger price={price}, pre-book bid depth={depth}, pre-book ask depth={depth}"

// Individual wave
"[CASCADE] WAVE {N}: price {startPrice}→{endPrice}, volume={vol}, levels={levels}, duration={durationMs}ms, pause={pauseMs}ms since prev wave"

// Spread blowout during cascade
"[CASCADE] SPREAD BLOWOUT: {spreadTicks} ticks (baseline: {baseline}, expansion: {rate} ticks/sec)"

// Absorption detected
"[CASCADE] ABSORPTION at price {price}. Score: {score}/1.00. Counter-volume: {vol}. Spread normalizing: {yes|no}. Book rebuilding: {yes|no}"

// Absorption failed
"[CASCADE] ABSORPTION FAILED: New wave detected at price {price}. Cascade resuming."

// Recovery
"[CASCADE] RECOVERY: Price recovering from {extremePrice}. Recovery ratio: {buyVol}/{sellVol} = {ratio}"

// Cascade complete — FULL SUMMARY
"[CASCADE] ===== CASCADE COMPLETE ====="
"[CASCADE] Direction: {SELL|BUY}"
"[CASCADE] Duration: {totalMs}ms"
"[CASCADE] Price Range: {startPrice} → {extremePrice} ({rangeTicks} ticks / ${rangeUSD})"
"[CASCADE] Total Volume: {totalVol} contracts across {waveCount} waves"
"[CASCADE] Avg Wave Size: {avgVol} contracts"
"[CASCADE] Max Wave Size: {maxVol} contracts"
"[CASCADE] Max Spread: {maxSpread} ticks"
"[CASCADE] Pre-Cascade Depth: Bid={bidDepth}, Ask={askDepth}"
"[CASCADE] Absorption Score: {score}/1.00"
"[CASCADE] Recovery Ratio: {ratio}"
"[CASCADE] =========================="

7. Indicator Color Scheme

Set in initialize() using indicator.setColor(new java.awt.Color(...)):

IndicatorColorRGBA
Book Depth BidGreen(0, 200, 0, 180)
Book Depth AskRed(200, 0, 0, 180)
Depth Rate of ChangeOrange(255, 165, 0, 180)
Spread (ticks)Yellow(255, 255, 0, 180)
Spread Expansion RateMagenta(255, 0, 255, 180)
Cascade ActiveWhite(255, 255, 255, 220)
Cascade Wave CountCyan(0, 255, 255, 180)
Absorption ScoreLime(0, 255, 128, 180)
Recovery QualityPurple(128, 0, 255, 180)

8. Build Configuration

Use the existing pom.xml in the project. The required dependencies are already present:

<dependency>
<groupId>com.bookmap.api</groupId>
<artifactId>api-core</artifactId>
<version>7.7.0.3</version>
</dependency>
<dependency>
<groupId>com.bookmap.api</groupId>
<artifactId>api-simplified</artifactId>
<version>7.7.0.3</version>
</dependency>

The build output JAR is automatically copied to C:\Bookmap\addons\my_addons\ via the maven-antrun-plugin already configured.

File Structure

src/main/java/com/bookmap/addon/cascade/
CascadeAnalyzer.java ← Single implementation file

No additional files, resources, or dependencies required.


9. Implementation Sequence

Implement in this order:

Step 1: Skeleton with Lifecycle

  • Class declaration with all annotations
  • implements CustomModule (not CustomModuleAdapter — we need all interfaces explicit)
  • Also implements: DepthDataListener, TradeDataListener, BboListener, TimeListener, IntervalListener, HistoricalModeListener
  • initialize(): store alias, pips, api; register all 9 indicators; log startup
  • stop(): log session statistics; clean up all data structures

Step 2: Timestamp + Order Book + BBO

  • Implement onTimestamp — store nanosecond timestamp
  • Implement onDepth — maintain bid/ask TreeMaps, compute top-20 sums, store snapshots in circular buffer
  • Implement onBbo — maintain current BBO state, compute spread, store snapshots, compute EMA baseline
  • Implement onInterval at 100ms — compute depth rate-of-change, update all indicators
  • Test: Load add-on, verify bid/ask depth and spread indicators render correctly on CL

Step 3: Pre-Cascade Detection

  • Add book thinning detection logic in onDepth / onInterval
  • Add spread blowout detection in onBbo
  • Implement PRE_CASCADE state entry and timeout logic in onTimestamp
  • Test: Replay CLJ6 March 12 data file, verify PRE_CASCADE flags before the 10:46 cascade

Step 4: Cascade Burst Detection + Wave Decomposition

  • Implement burst accumulation logic in onTrade
  • Implement evaluateBurst() function
  • Implement ACTIVE_CASCADE state with wave tracking
  • Test: Replay CLJ6 March 12 data, verify cascade waves match the sweep markers observed in Bookmap's built-in Sweeps indicator

Step 5: Absorption Scoring

  • Implement absorption scorer with 5-component formula
  • Implement ABSORPTION state transitions
  • Track counter-aggressive volume, bid rebuild, spread normalization, price stabilization, delta divergence
  • Test: Replay CLJ6 March 12 data, verify absorption score at the ~$94.20–$94.40 low reflects the strong V-recovery observed

Step 6: Recovery Tracking + Final Logging

  • Implement RECOVERY state with recovery ratio computation
  • Implement full cascade summary logging (Section 6 format)
  • Add session-level statistics to stop() method
  • Test: Full replay verification. Cross-reference wave counts and volumes against Bookmap Sweeps indicator at settings (0.15s, 30 contracts, 5 levels)

10. Testing Strategy

Primary Test Data

The CLJ6 March 12, 2026 replay file already loaded in Bookmap. This session contains the 10:46 cascade event we've analyzed in detail.

Expected Outputs for the March 12 Cascade

Based on our analysis of the actual sweep data:

MetricExpected ValueSource
Cascade directionSELLVisual confirmation from screenshots
Approximate start price~$95.80Image 2 from first analysis
Approximate extreme price~$93.80–$94.20Image 2 from first analysis
Total sell-side sweep volume~1,400–1,500 contractsSum of sweep markers from calibrated indicator
Wave count10–15 discrete wavesSweep marker count from Image 2 (latest settings)
Max individual wave volume~130–297 contracts@297 marker in Image 2
Max spread during cascade15–20+ ticksBBO data from millisecond screenshots
Absorption score>0.5 (strong V-recovery observed)Recovery behavior from initial screenshots
Recovery ratio~0.30 (446 buy / 1477 sell)Sweep marker volume comparison

Validation Procedure

  1. Load add-on on CLJ6 with replay file
  2. Advance to 10:45:50 EDT
  3. Play at 1x speed through the cascade
  4. Compare logged output against expected values above
  5. Verify indicator rendering on chart — depth should show clear drop before cascade, spread should spike during, absorption score should rise at terminus

11. Future Extensions (Not in Scope for V1)

These are noted for reference but should NOT be implemented in the initial build:

  1. Multi-instrument correlation — Detect when CL cascade triggers simultaneous moves in MCL, RB, HO, or BZ
  2. Headline integration — Correlate cascade timing with news feed timestamps
  3. Confluence scoring integration — Feed cascade detection signals into the existing Excel-based 10-metric confluence system
  4. Statistical threshold calibration — Once sufficient cascades are captured in the tick data pipeline, calibrate all thresholds empirically using the Polars + DuckDB infrastructure
  5. Sound/visual alerts — Trigger Bookmap popup or sound on PRE_CASCADE detection (would require UI integration beyond simple indicators)

12. API Reference Quick-Look

Key classes and methods needed from the Bookmap API:

WhatWhere to Find It
Indicator registrationSection 12 (Multi-Timeframe VWAP) — api.registerIndicator(name, GraphType, initialValue)
Indicator colorindicator.setColor(new java.awt.Color(r, g, b, a))
Indicator updateindicator.addPoint(value)
DepthDataListener signatureSection 4 (MBP Order Book) — onDepth(boolean isBid, int price, int size)
TradeDataListener signatureSection 7 (VWAP) — onTrade(double price, int size, TradeInfo tradeInfo)
TradeInfo.isBidAggressorSection 7 — boolean flag for aggressor side
BboListener signatureSection 10 (BBO Monitor) — onBbo(int bidPrice, int bidSize, int askPrice, int askSize)
TimeListener signatureSection 3 (Timestamp) — onTimestamp(long nanoseconds)
IntervalListener signatureSection 9 (Interval Manager) — getInterval() returns nanoseconds, onInterval() callback
Intervals constantsIntervals.INTERVAL_100_MILLISECONDS, Intervals.INTERVAL_1_SECOND, etc.
HistoricalModeListenerSection 8 (Historical Processor) — onRealtimeStart() callback
InstrumentInfo fieldsinfo.pips (price increment), info.symbol, info.exchange
GraphType optionsGraphType.PRIMARY (on price chart), GraphType.BOTTOM (sub-chart)
LoggingLog.info(String), Log.warn(String)

See Also