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
- Single-class implementation — One Java file, no external dependencies beyond Bookmap API
- CL-specific calibration — All thresholds tuned to crude oil microstructure ($0.01 tick, thin book, headline sensitivity)
- Indicator output — All signals rendered as Bookmap chart indicators for visual consumption
- Log output — All events logged with full context for post-session analysis and future statistical validation
- No trading logic — Pure analysis; no order placement or position management
2. Architecture Overview
Class Name
CascadeAnalyzer
Package
com.bookmap.addon.cascade
Interfaces Implemented
| Interface | Purpose |
|---|---|
DepthDataListener | Track order book depth changes at each price level |
TradeDataListener | Process individual trades for cascade wave detection |
BboListener | Monitor best bid/offer for spread expansion detection |
TimeListener | Maintain nanosecond timestamp synchronization |
IntervalListener | Periodic indicator updates and statistics computation |
HistoricalModeListener | Calibrate baselines from historical data before real-time |
Annotations
@Layer1SimpleAttachable
@Layer1StrategyName("CL Cascade Analyzer")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
Indicator Registrations
Register these indicators in initialize():
| Indicator Name | GraphType | Purpose |
|---|---|---|
"Book Depth Bid (Top 20)" | BOTTOM | Rolling sum of bid-side resting size across top 20 levels |
"Book Depth Ask (Top 20)" | BOTTOM | Rolling sum of ask-side resting size across top 20 levels |
"Depth Rate of Change" | BOTTOM | Rate at which book depth is changing (thinning = negative) |
"Spread (ticks)" | BOTTOM | Current bid-ask spread in ticks |
"Spread Expansion Rate" | BOTTOM | How fast the spread is widening (ticks per 100ms) |
"Cascade Active" | BOTTOM | Binary: 1.0 when cascade is in progress, 0.0 otherwise |
"Cascade Wave Count" | BOTTOM | Number of discrete waves in current/recent cascade |
"Absorption Score" | BOTTOM | 0.0–1.0 score of absorption quality at cascade terminus |
"Recovery Quality" | BOTTOM | Ratio 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
DepthSnapshotin 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 depthDEPTH_HISTORY_WINDOW_NS = 5_000_000_000L— 5-second rolling windowTHINNING_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
SpreadSnapshotin 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 * 3ANDcurrentSpreadTicks >= 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 blowoutSPREAD_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 windowSPREAD_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:
aggressiveVolumeAccumulator >= 25contracts within 150ms (CL threshold)levelsConsumed >= 4price levels ($0.04 on CL)- 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
levelsConsumedif price moved to new level
- Increment
- 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(...)):
| Indicator | Color | RGBA |
|---|---|---|
| Book Depth Bid | Green | (0, 200, 0, 180) |
| Book Depth Ask | Red | (200, 0, 0, 180) |
| Depth Rate of Change | Orange | (255, 165, 0, 180) |
| Spread (ticks) | Yellow | (255, 255, 0, 180) |
| Spread Expansion Rate | Magenta | (255, 0, 255, 180) |
| Cascade Active | White | (255, 255, 255, 220) |
| Cascade Wave Count | Cyan | (0, 255, 255, 180) |
| Absorption Score | Lime | (0, 255, 128, 180) |
| Recovery Quality | Purple | (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(notCustomModuleAdapter— we need all interfaces explicit)- Also implements:
DepthDataListener, TradeDataListener, BboListener, TimeListener, IntervalListener, HistoricalModeListener initialize(): store alias, pips, api; register all 9 indicators; log startupstop(): 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
onIntervalat 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:
| Metric | Expected Value | Source |
|---|---|---|
| Cascade direction | SELL | Visual confirmation from screenshots |
| Approximate start price | ~$95.80 | Image 2 from first analysis |
| Approximate extreme price | ~$93.80–$94.20 | Image 2 from first analysis |
| Total sell-side sweep volume | ~1,400–1,500 contracts | Sum of sweep markers from calibrated indicator |
| Wave count | 10–15 discrete waves | Sweep marker count from Image 2 (latest settings) |
| Max individual wave volume | ~130–297 contracts | @297 marker in Image 2 |
| Max spread during cascade | 15–20+ ticks | BBO 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
- Load add-on on CLJ6 with replay file
- Advance to 10:45:50 EDT
- Play at 1x speed through the cascade
- Compare logged output against expected values above
- 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:
- Multi-instrument correlation — Detect when CL cascade triggers simultaneous moves in MCL, RB, HO, or BZ
- Headline integration — Correlate cascade timing with news feed timestamps
- Confluence scoring integration — Feed cascade detection signals into the existing Excel-based 10-metric confluence system
- Statistical threshold calibration — Once sufficient cascades are captured in the tick data pipeline, calibrate all thresholds empirically using the Polars + DuckDB infrastructure
- 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:
| What | Where to Find It |
|---|---|
| Indicator registration | Section 12 (Multi-Timeframe VWAP) — api.registerIndicator(name, GraphType, initialValue) |
| Indicator color | indicator.setColor(new java.awt.Color(r, g, b, a)) |
| Indicator update | indicator.addPoint(value) |
| DepthDataListener signature | Section 4 (MBP Order Book) — onDepth(boolean isBid, int price, int size) |
| TradeDataListener signature | Section 7 (VWAP) — onTrade(double price, int size, TradeInfo tradeInfo) |
| TradeInfo.isBidAggressor | Section 7 — boolean flag for aggressor side |
| BboListener signature | Section 10 (BBO Monitor) — onBbo(int bidPrice, int bidSize, int askPrice, int askSize) |
| TimeListener signature | Section 3 (Timestamp) — onTimestamp(long nanoseconds) |
| IntervalListener signature | Section 9 (Interval Manager) — getInterval() returns nanoseconds, onInterval() callback |
| Intervals constants | Intervals.INTERVAL_100_MILLISECONDS, Intervals.INTERVAL_1_SECOND, etc. |
| HistoricalModeListener | Section 8 (Historical Processor) — onRealtimeStart() callback |
| InstrumentInfo fields | info.pips (price increment), info.symbol, info.exchange |
| GraphType options | GraphType.PRIMARY (on price chart), GraphType.BOTTOM (sub-chart) |
| Logging | Log.info(String), Log.warn(String) |
See Also
- Cascade Analysis Terminology — Glossary of all cascade analysis terms
- Data Listeners — Guide to Bookmap's data listener interfaces
- Chart Visualization Guide — Guide to chart rendering and indicators