Iceberg Detector Blueprint
Author: Nasser (via Claude Opus) Date: March 2026 Target: ES Futures via Rithmic MBO on Bookmap 7.6.0+
1. Project Overview
Build a Bookmap add-on that detects iceberg orders in real-time using MBO (Market By Order) data from Rithmic on CME ES futures, and renders detection markers directly on the main chart.
What Is an Iceberg Order?
An iceberg order is a limit order where only a fraction (the "displayed size") is visible in the order book. When that visible portion fills, the exchange internally refills it from a hidden reserve. The refill appears in MBO data as a NEW order on the same side, at the same price, during the atomic processing of a single aggressive order. This is physically impossible during normal matching — making it the definitive detection signal.
Detection Algorithm: Native (MBO-Based)
During the atomic processing of one aggressive order (bounded by isExecutionStart=true and isExecutionEnd=true on TradeInfo), if a NEW send event appears on the opposite side from the aggressor at the same price level where executions are occurring, that new order is an iceberg refill. No external orders can arrive during atomic processing — the only source is the exchange's internal refill mechanism.
Normal matching (Buy aggressor sweeping asks):
Execute Sell 20 @2777.00
Execute Sell 10 @2777.00
Execute Sell 25 @2777.00
→ Level cleared, done
With Iceberg Sell at 2777.00 (displayed=20):
Execute Sell 20 @2777.00
NEW Sell Order 20 @2777.00 ← ICEBERG REFILL (impossible otherwise)
Execute Sell 10 @2777.00
Execute Sell 20 @2777.00 ← The refilled portion executes
NEW Sell Order 20 @2777.00 ← Another refill
What We Render
On-chart Marker icons at the price level where an iceberg was detected:
- Green down-arrow (▼): Buy iceberg detected (passive buy being refilled — icon points toward bid side)
- Red up-arrow (▲): Sell iceberg detected (passive sell being refilled — icon points toward ask side)
- Volume label on the icon showing estimated displayed size
2. API Architecture Decision
Use Simplified API (VERSION1), NOT Advanced API (VERSION2)
Rationale: The Simplified API provides direct access to both TradeDataListener and MarketByOrderDepthDataListener through CustomModule + Api. It gives us Indicator.addIcon() for on-chart markers without needing the complexity of OnlineCalculatable, DataStructureInterface, or Layer1ApiProvider layer chain management.
Trade-off: No historical scrollback support for markers (they only appear from subscription start). This is acceptable for a real-time iceberg detector — icebergs are live events, not historical analysis.
Required Interfaces
CustomModule — lifecycle (initialize/stop)
TradeDataListener — onTrade(price, size, tradeInfo) with execution chain flags
MarketByOrderDepthDataListener — send/replace/cancel for individual MBO orders
TimeListener — onTimestamp(nanoseconds) for timing
CustomSettingsPanelProvider — settings UI panel
Required Annotations
@Layer1SimpleAttachable
@Layer1StrategyName("Iceberg Detector")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION1)
3. Project Structure
iceberg-detector/
├── build.gradle
├── settings.gradle
└── src/
└── main/
├── java/
│ └── com/
│ └── nasserhub/
│ └── iceberg/
│ ├── IcebergDetectorAddon.java # Entry point, wiring
│ ├── detection/
│ │ ├── ExecutionChainTracker.java # Tracks atomic execution chains
│ │ ├── MboBookTracker.java # Tracks MBO order lifecycle
│ │ └── IcebergEvent.java # Detected iceberg record
│ ├── rendering/
│ │ └── IcebergIconRenderer.java # Icon creation and caching
│ └── settings/
│ └── IcebergSettings.java # Persisted settings
└── resources/
└── (empty — icons generated programmatically)
build.gradle
plugins {
id 'java'
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
repositories {
mavenCentral()
// Bookmap API repository
maven { url 'https://maven.bookmap.com/maven2/releases/' }
}
dependencies {
compileOnly 'com.bookmap.api:api-core:7.6.0.0'
}
jar {
// Output jar goes into Bookmap's addons directory
archiveBaseName.set('iceberg-detector')
archiveVersion.set('1.0.0')
}
settings.gradle
rootProject.name = 'iceberg-detector'
4. Detailed Class Specifications
4.1 IcebergDetectorAddon.java — Entry Point
Role: Bookmap add-on entry point. Wires all components together, receives all data callbacks, delegates to detection engine, renders results.
Implements: CustomModule, TradeDataListener, MarkerByOrderDepthDataListener, TimeListener, CustomSettingsPanelProvider
Lifecycle:
initialize()— create components, register indicator, load settings- Data callbacks flow in:
onTimestamp,onTrade, MBOsend/replace/cancel - When detection fires → call
indicator.addIcon(price, icon, offsetX, offsetY) stop()— cleanup
Key fields:
private Api api;
private String alias;
private Indicator indicator; // Registered on GraphType.PRIMARY
private double pips; // From InstrumentInfo for price conversion
private ExecutionChainTracker chainTracker;
private MboBookTracker mboTracker;
private IcebergIconRenderer iconRenderer;
private IcebergSettings settings;
private long currentTimestamp; // From TimeListener
initialize() pseudocode:
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.alias = alias;
this.pips = info.pips;
this.settings = api.getSettings(IcebergSettings.class);
this.indicator = api.registerIndicator("Iceberg Detector", GraphType.PRIMARY);
this.iconRenderer = new IcebergIconRenderer();
this.mboTracker = new MboBookTracker();
this.chainTracker = new ExecutionChainTracker(mboTracker, this::onIcebergDetected);
}
Critical callback ordering: Bookmap delivers MBO events and trade events in sequence on the same thread. During one aggressive order's processing, you will see interleaved MBO send/cancel events and onTrade calls. The isExecutionStart and isExecutionEnd flags on TradeInfo mark the boundaries.
4.2 ExecutionChainTracker.java — Detection Engine
Role: The core detection logic. Tracks the boundaries of atomic execution chains and watches for interleaved MBO send events that signal iceberg refills.
State machine:
IDLE → (onTrade with isExecutionStart=true) → IN_CHAIN
IN_CHAIN → accumulate trades and watch MBO sends
IN_CHAIN → (onTrade with isExecutionEnd=true) → evaluate → emit events → IDLE
Key fields:
private boolean inChain = false;
private boolean aggressorIsBid; // true = buy aggressor sweeping asks
private int chainPriceLevel; // Price level being swept (in ticks)
private List<MboSendEvent> interleavesSends; // MBO sends seen during this chain
private List<TradeEvent> chainTrades; // Trades in this chain
private final MboBookTracker mboTracker;
private final Consumer<IcebergEvent> onDetection;
Detection logic inside the chain:
When mboTracker reports a new send event (from MarketByOrderDepthDataListener.send()):
- Check: are we currently
inChain? - Check: is the new order on the same side as the passive orders being executed? (i.e., if aggressor is buying, the new send must be a sell/ask order)
- Check: is the new order at the same price level where executions are occurring?
- If all three → record as an iceberg refill candidate
When the chain ends (isExecutionEnd=true):
- If we recorded any interleaved sends matching the criteria → emit
IcebergEvent - The estimated displayed size = the size of the interleaved send orders (typically consistent)
- The iceberg side = the side of the refill orders (passive side)
- Reset state to IDLE
Critical detail: The send callback from MarketByOrderDepthDataListener fires DURING the execution chain, interleaved with onTrade calls. This is NOT a race condition — Bookmap delivers these sequentially on the same processing thread.
4.3 MboBookTracker.java — MBO Order Lifecycle
Role: Maintains a map of all known MBO orders. Provides lookup for the detection engine to determine if a send event during a chain is genuinely new (not a modification of an existing order).
Implements: Receives forwarded calls from IcebergDetectorAddon's MBO callbacks.
Key fields:
// All known orders by orderId
private final Map<String, MboOrder> orders = new HashMap<>();
// Callback to notify chain tracker of new sends
private Consumer<MboSendEvent> onNewSend;
MboOrder record:
record MboOrder(String orderId, boolean isBid, int price, int size) {}
Methods:
void onSend(String orderId, boolean isBid, int price, int size) {
orders.put(orderId, new MboOrder(orderId, isBid, price, size));
if (onNewSend != null) {
onNewSend.accept(new MboSendEvent(orderId, isBid, price, size));
}
}
void onReplace(String orderId, int price, int size) {
MboOrder existing = orders.get(orderId);
if (existing != null) {
orders.put(orderId, new MboOrder(orderId, existing.isBid(), price, size));
}
}
void onCancel(String orderId) {
orders.remove(orderId);
}
4.4 IcebergEvent.java — Detection Record
public record IcebergEvent(
long timestamp, // Nanoseconds from TimeListener
int priceLevel, // In tick units (as received from API)
boolean isBuyIceberg, // true = passive buy iceberg, false = passive sell
int estimatedDisplaySize,// Size of the refill order(s)
int refillCount, // Number of refills detected in this chain
String firstRefillOrderId // MBO order ID of first detected refill
) {}
4.5 IcebergIconRenderer.java — Icon Factory
Role: Creates and caches BufferedImage icons for chart display.
Icons (created once in constructor, reused):
buyIcebergIcon: Green downward-pointing triangle (12×12 px) — buy iceberg = passive bid being refilledsellIcebergIcon: Red upward-pointing triangle (12×12 px) — sell iceberg = passive ask being refilled
Method:
BufferedImage getIcon(boolean isBuyIceberg) {
return isBuyIceberg ? buyIcebergIcon : sellIcebergIcon;
}
Icon creation pattern (from KB ChartVisualizationGuide.md):
private BufferedImage createArrowIcon(Color color, boolean pointUp) {
BufferedImage icon = new BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = icon.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
int[] xPoints = {6, 0, 12};
int[] yPoints = pointUp ? new int[]{0, 12, 12} : new int[]{12, 0, 0};
g.fillPolygon(xPoints, yPoints, 3);
g.dispose();
return icon;
}
4.6 IcebergSettings.java — Configuration
@StrategySettingsVersion(currentVersion = 1, compatibleVersions = {})
public class IcebergSettings implements Serializable {
/** Minimum refill count to qualify as iceberg detection */
public int minRefills = 1;
/** Minimum displayed size (contracts) to show on chart */
public int minDisplayedSize = 1;
/** Enable logging of detections */
public boolean enableLogging = true;
}
Settings panel (in IcebergDetectorAddon.getCustomSettingsPanels()):
StrategyPanel panel = new StrategyPanel("Iceberg Detection Settings");
// Add JSpinner for minRefills (1-10)
// Add JSpinner for minDisplayedSize (1-1000)
// Add JCheckBox for enableLogging
// Each change: update settings field, call api.setSettings(settings)
5. Data Flow Diagram
Bookmap Engine (Rithmic MBO Stream)
│
├─── onTimestamp(nanos) ──────────→ IcebergDetectorAddon.currentTimestamp
│
├─── MBO send(id, isBid, px, sz) → IcebergDetectorAddon
│ │ │
│ └──→ MboBookTracker.onSend() ──→ ExecutionChainTracker
│ (if inChain: check refill criteria)
│
├─── MBO replace(id, px, sz) ────→ MboBookTracker.onReplace()
│
├─── MBO cancel(id) ────────────→ MboBookTracker.onCancel()
│
└─── onTrade(px, sz, tradeInfo) → IcebergDetectorAddon
│
└──→ ExecutionChainTracker.onTrade()
│
├─ isExecutionStart? → enter IN_CHAIN, record aggressor side
├─ accumulate trade
└─ isExecutionEnd? → evaluate interleaved sends
│
└─ refills found? → IcebergEvent
│
▼
indicator.addIcon(price, icon, ox, oy)
Log.info("ICE detected: ...")
6. Critical Implementation Details
6.1 Price Coordinate for addIcon
The Indicator.addIcon(double price, BufferedImage icon, int iconCenterX, int iconCenterY) method expects price in the same units as onTrade — which is price/pips level numbers. From the KB (ApiInterface.md): the price parameter from onTrade is already in level units. Pass it directly.
// In the detection callback:
private void onIcebergDetected(IcebergEvent event) {
BufferedImage icon = iconRenderer.getIcon(event.isBuyIceberg());
indicator.addIcon(
event.priceLevel(), // price level as received from onTrade
icon,
icon.getWidth() / 2, // centerX
icon.getHeight() / 2 // centerY
);
}
6.2 Execution Chain Boundary Detection
From TradeInfo.java in the KB:
isExecutionStart= true if this trade starts a new order execution chainisExecutionEnd= true if this trade ends an order execution chain
A single aggressive order sweeping multiple price levels produces one chain. A single trade that starts AND ends is both isExecutionStart=true and isExecutionEnd=true simultaneously.
6.3 Determining Aggressor Side
From TradeInfo.isBidAggressor:
true= the aggressive order is a BUY (sweeping asks)false= the aggressive order is a SELL (sweeping bids)
If aggressor is buying → the iceberg refills will be SELL orders (ask side) → this is a sell iceberg. If aggressor is selling → the iceberg refills will be BUY orders (bid side) → this is a buy iceberg.
6.4 Thread Safety
All callbacks (onTrade, MBO send/replace/cancel, onTimestamp) arrive on the same Bookmap processing thread — no synchronization needed between them. The settings panel runs on Swing EDT — settings reads from the processing thread should use volatile or be tolerant of slight staleness.
6.5 Passive Order ID Correlation
TradeInfo.passiveOrderId (nullable) provides the MBO order ID of the resting order that was partially or fully consumed. This can be used to verify that the executed order at the price level is the same one that was just refilled. However, this field may be null depending on data source, so do not depend on it — use price-level matching as the primary criterion.
7. Implementation Phases
Phase 1: Skeleton + MBO Tracking
- Create project structure with build.gradle
- Implement
IcebergDetectorAddonwith all required interfaces - Implement
MboBookTracker— log every send/replace/cancel to verify MBO data is flowing - Register indicator on PRIMARY chart
- Validation: Enable addon on ES, verify MBO events appear in Bookmap log
Phase 2: Execution Chain Tracking
- Implement
ExecutionChainTrackerstate machine - Log chain boundaries: "Chain START: aggressor=BUY at price=XXXX" / "Chain END: N trades"
- Validation: Verify chains are properly bounded, compare with Bookmap's native trade dots
Phase 3: Iceberg Detection
- Add interleaved-send detection logic inside chain processing
- Create
IcebergEventrecords - Log detections: "ICEBERG DETECTED: Sell iceberg at 5600.00, displayedSize=20, refills=3"
- Validation: Compare detections against Bookmap's Stops & Icebergs add-on if available
Phase 4: Chart Rendering
- Implement
IcebergIconRenderer - Wire
onIcebergDetected→indicator.addIcon() - Validation: Visual markers should appear on chart at correct price levels during active trading
Phase 5: Settings + Polish
- Implement
IcebergSettingswith persistence - Implement
CustomSettingsPanelProviderfor min refills and min size filters - Add diagnostic logging via
AddonLoggerpattern from KB
8. Testing Strategy
Manual Validation (Primary)
- Load addon on ES during RTH with Rithmic
- Watch for markers during active sweeps of price levels (e.g., during news events, opening range)
- Cross-reference with Bookmap's native Stops & Icebergs On-Chart indicator (if licensed)
- Verify markers appear at correct price, correct side (buy vs sell iceberg), correct timing
Diagnostic Logging
Every detection should log:
[IcebergDetector] DETECTED: side=SELL_ICEBERG price=5612.50 displayedSize=20
refills=3 aggressorSide=BUY chainTrades=47 firstRefillId=CME-1234567
timestamp=2026-03-09T14:23:45.123Z
Edge Cases to Watch
- Very fast markets where chains are long (100+ trades) — ensure list sizes don't explode
- Chains that span multiple price levels — only detect refills at the same price where executions occur
isExecutionStartandisExecutionEndboth true on same trade — single-trade chain, unlikely to contain icebergpassiveOrderIdbeing null — fall back to price-level matching only- dxFeed historical data — MBO events will NOT be available; detection only works with Rithmic live/replay
9. Limitations (Be Honest About These)
- Hidden size is unknowable — we can estimate displayed size from refill order size, but the total iceberg size is fundamentally undetectable
- Detection only after first execution — icebergs are invisible until their displayed portion fills and refills
- Rithmic MBO only — dxFeed cloud backfill does not provide MBO events; detection works only during live sessions or .bmf replay with Rithmic data
- Data aggregation risk — if Bookmap's automatic aggregation kicks in under heavy load, MBO event sequences may be disrupted, causing missed or false detections
- HFT-mimicked icebergs — the Native algorithm detects only exchange-native icebergs (zero-latency refills). HFT firms that mimic icebergs with regular limit orders will NOT trigger detection because their refill orders arrive between execution chains, not during them
10. Future Enhancements (Not in V1)
- Iceberg CVD sub-chart line: Accumulate detected iceberg volume as buy/sell delta on a BOTTOM indicator
- Resistance algorithm mode: For HFT-mimicked detection, add a configurable delay window that checks for rapid refills BETWEEN chains
- Sound/text alerts: Use Bookmap's
Layer1ApiSoundAlertMessagesystem for iceberg burst alerts - Absorption scoring: Combine iceberg detection with CVD analysis to estimate whether absorption is holding or failing
See Also
- Market Mechanics — Exchange architecture and microstructure fundamentals
- Iceberg Orders — Iceberg order mechanics and detection algorithms
- Iceberg Detection Study Guide — Verification questions and answers for all concepts
- Data Listeners — Guide to Bookmap's data listener interfaces
- Chart Visualization Guide — Guide to chart rendering and indicator icons