Trading the Breaking

Trading the Breaking

Alpha Lab

[WITH CODE] Data: Iceberg orders

Market makers exploit unseen order patterns, tipping the scales in high-frequency trading battles

Quant Beckman's avatar
Quant Beckman
Apr 07, 2025
∙ Paid

Table of contents:

  1. Introduction.

  2. What are iceberg orders?

  3. Finite state machine for native icebergs.

  4. Detection of synthetic icebergs.

  5. Kaplan–Meier estimation.

  6. Who benefits from iceberg order detection?


Introduction

Welcome to the golden age of finance, where trading platforms promise to turn your pocket change into a private jet—or at least a slightly nicer bicycle—using algorithms that apparently outperform Wall Street geniuses and basic math. Between the risk-free strategies that evaporate faster than your patience during a software update… a darker color scheme, it’s clear innovation here means inventing new ways to monetize your FOMO.

I'm talking about a specific company: #00k!@? I was browsing their website for some really cool photos with balloons and came across an interesting section. It's about the iceberg orders section—the rest, I have to say, is okay, not bad for a discretionary retailer. This made me ask myself a few questions like for example: Does it really make sense to invest time and computational resources in detecting iceberg orders?

The answer is nuanced. On one hand, for certain players—like HFT firms and market makers—knowledge about hidden liquidity can be invaluable. On the other hand, the complexity and inherent uncertainty in detecting synthetic icebergs may limit the practical benefit of these methods in a fast-moving trading environment. Furthermore, the reliance on historical order book data means that real-time implementation may require substantial adaptation.

What are iceberg orders?

Basically, it is a type of limit order used to hide the true trading intention of a participant. Instead of revealing the full order size, only a peak amount is visible in the order book. When this peak is executed, another tranche—or refill—is automatically submitted until the total volume is traded. The hidden volume remains concealed from the market, allowing large traders to minimize market impact. The term iceberg is used because, like a natural iceberg, the majority of the volume—the mass below the surface—is hidden from view.

The basic principle of how, for instance, Buy Iceberg orders operate is this:

Image from #00k!@?

The mechanics of Iceberg order matching is quite similar except that only its displayed size can advance in the order queue.

Image from #00k!@?

With this in mind, we can check the two types of iceberg orders. They come in two flavors:

  • Native icebergs: Managed by the exchange’s matching engine, they exhibit telltale characteristics such as a constant order ID and trade summary messages that sometimes indicate trade volumes larger than the current visible order size.

  • Synthetic icebergs: Managed by external systems—typically independent software vendors, or ISVs)—these orders are recreated by submitting multiple limit orders with the same price and volume characteristics. Their detection relies on time-based heuristics, as the exchange does not provide explicit markers.

You can see more about that in the work of Frey and Sandås:

The Impact of Iceberg Orders in Limit Order Books
380KB ∙ PDF file
Download
Download

To date, many methodologies have been attempted for its detection, each more eccentric than the last. Some firms have used analytical models, others claim to use ML—but in the end, we're talking about linear regression. It's all possible. Let's look at some of these applications and what the industry is using to model this chimera.

Finite state machine for native icebergs

For native iceberg orders, the detection algorithm tracks the order’s lifecycle. A new limit order enters the book, may be partially executed, and then gets updated or refilled with additional volume until it is either fully executed or cancelled. We can formally define the finite state machine as follows:

Let S={L,T,M,D} denote the set of states:

  • L: Limit—order appears in the book.

  • T: Trade—execution against the order.

  • M: Modify—refill or update indicating additional hidden volume.

  • D: Delete—order leaves the book.

Define a state transition function

\(\delta: \mathcal{S} \times \mathcal{A} \to \mathcal{S}\)

where A is the set of order actions—Limit, Trade, Modify, Delete. The transitions are governed by the following rules:

  1. δ(L,Trade)=T.

  2. δ(T,Modify)=M.

  3. δ(M,Trade)=T.

  4. δ(T,Delete)=D.

  5. δ(M,Delete)=D.

A typical sequence for an iceberg order is:

\(L \rightarrow T \rightarrow M \rightarrow T \rightarrow \cdots \rightarrow D.\)

Let’s see an example of how to make the computations for Peak Size. Consider an order that follows this sequence:

  1. A limit order with volume VL=6.

  2. A trade occurs with volume VT=12.

  3. The order is modified aka refilled with a new visible volume VM=7.

The peak size Vpeak is defined by the relation:

\(V_{\text{peak}} = \frac{V_T + V_L}{k + 1},\)

where k∈N0​ is the number of complete tranches executed prior to the current modification.

For instance, if k=1—indicating that one complete tranche has been executed—then

\(V_{\text{peak}} = \frac{12 + 6}{1 + 1} = \frac{18}{2} = 9.\)

This indicates that if the next refill is equal to 9, the order confirms the iceberg pattern.

This table illustrates a simplified sequence:

\(\begin{array}{ccc p{6cm}} \textbf{Step} & \textbf{Action} & \textbf{Volume} & \textbf{Explanation} \\ 1 & Limit & 6 & \text{Order enters with visible volume 6.} \\ 2 & Trade & 12 & \text{Trade occurs; $12 > 6$ implies hidden volume might be present.}\\ 3 & Modify & 7 & \text{Refill indicates hidden liquidity; compute $V_{\text{peak}} = \dfrac{12 + 6}{2} = 9$.} \\ 4 & Trade & 8 & \text{Subsequent trade further confirms iceberg behavior if trade volume exceeds current $V_M$.}\\ 5 & Modify & 1 & \text{Another refill action continues the process.}\\ 6 & Delete & 5 & \text{Order is finally deleted from the book.} \\ \end{array}\)

Beyond these basic computations, one may consider the cumulative traded volume ​ after i trades and the corresponding sequence of modifications. Formally, if

\(V_T^{(i)} = \sum_{j=1}^i v_j,\)

where vj​ is the volume traded in the j-th trade, then the admissible peak sizes satisfy

\(V_{\text{peak}} \in \left\{ n \in \mathbb{N} \,:\, n \geq V_L \text{ and } V_T^{(i)} + V_L \equiv 0 \pmod{n} \right\}.\)

Personally, a finite state machine seems pretty toyish to me. Sometimes I wonder to what extent firms will use this... but anyway, let's implement it.

Feel free to adjust or expand this to suit your own environment and order logic.

import enum

class OrderState(enum.Enum):
    """
    Order states for a native iceberg:
    - L: Limit (the order is live on the book with some visible size)
    - T: Trade (the order experiences a trade / fill event)
    - M: Modify (the order is modified or refilled)
    - D: Delete (the order is removed or canceled)
    """
    L = "Limit"
    T = "Trade"
    M = "Modify"
    D = "Delete"

class IcebergOrder:
    """
    A simple class modeling a native iceberg order using a state machine.
    
    Attributes:
        initial_visible (float): The initial visible portion of the iceberg.
        total_volume    (float): The total size of the iceberg (visible + hidden).
        state          (OrderState): The current state of the order.
        traded_volume  (float): How much volume has been traded so far.
        completed_tranches (int): Number of times the visible portion has been fully filled.
        
    Methods:
        trade(volume): Simulate a trade (fill) of 'volume'.
        modify(new_visible): Simulate a modification of the order's visible portion.
        delete(): Cancel (delete) the order.
        refill(): Automatically refill the visible portion if hidden volume remains.
        compute_peak_size(): Recompute the new peak size based on the example formula.
        step_through_sequence(): Demonstration of a typical L -> T -> M -> T -> ... -> D flow.
    """
    def __init__(self, initial_visible: float, total_volume: float):
        self.initial_visible = initial_visible
        self.total_volume = total_volume
        self.state = OrderState.L

        # Tracking volumes:
        self.visible_volume = initial_visible
        self.hidden_volume = max(total_volume - initial_visible, 0.0)
        self.traded_volume = 0.0
        self.completed_tranches = 0

    def trade(self, volume: float):
        """
        Simulate a trade (fill) of 'volume' units. 
        Transitions state to T (Trade).
        """
        if self.state == OrderState.D:
            print("Order is deleted, cannot trade.")
            return
        
        self.state = OrderState.T

        # Actual traded amount is limited by the visible volume
        fill = min(self.visible_volume, volume)
        self.visible_volume -= fill
        self.traded_volume += fill
        
        print(f"[TRADE] Filled {fill} units. "
              f"Remaining visible = {self.visible_volume}, "
              f"Traded so far = {self.traded_volume}")

        # If visible is fully filled, we count a completed tranche
        if self.visible_volume <= 0.0:
            self.completed_tranches += 1
            # Attempt to refill if hidden volume remains
            self.refill()

    def modify(self, new_visible: float):
        """
        Simulate modifying the order's visible portion.
        Transitions state to M (Modify).
        """
        if self.state == OrderState.D:
            print("Order is deleted, cannot modify.")
            return

        self.state = OrderState.M
        # Adjust the visible portion (within what remains in the hidden volume)
        # For demonstration, just set it to new_visible (bounded by hidden).
        self.visible_volume = min(new_visible, self.hidden_volume + self.visible_volume)
        self.hidden_volume = self.total_volume - self.traded_volume - self.visible_volume
        
        print(f"[MODIFY] New visible portion set to {self.visible_volume}. "
              f"Hidden volume is now {self.hidden_volume}.")

    def delete(self):
        """
        Delete (cancel) the order.
        Transitions state to D (Delete).
        """
        self.state = OrderState.D
        print("[DELETE] Order is deleted.")

    def refill(self):
        """
        If the visible portion has been completely filled, 
        try to refill from the hidden portion (iceberg logic).
        Typically triggers a Modify state, but we can automate here.
        """
        if self.hidden_volume > 0.0:
            # Move to modify state to refill
            self.state = OrderState.M
            # Compute the new peak size (example formula) or just use initial_visible
            new_peak = self.compute_peak_size()

            # The new visible portion is the smaller of new_peak or what's left hidden
            refill_amount = min(new_peak, self.hidden_volume)
            self.visible_volume = refill_amount
            self.hidden_volume -= refill_amount

            print(f"[REFILL] Refilled visible portion to {self.visible_volume}. "
                  f"Hidden volume is now {self.hidden_volume}.")

            # After modifying, we can switch back to 'L' to indicate it's a valid limit again
            self.state = OrderState.L
        else:
            # No more hidden volume left -> entire order is filled
            print("[REFILL] No hidden volume left. The order is fully executed.")
            self.delete()

    def compute_peak_size(self) -> float:
        """
        Compute the new peak size using the example formula:
            V_peak = (traded_volume + initial_visible) / (completed_tranches + 1)
        This is just one interpretation from your example.
        """
        if self.completed_tranches == 0:
            # If no tranches have completed, just return the initial visible
            return self.initial_visible

        # Example formula from your snippet:
        # V_peak = (V_T + V_L) / (k + 1)
        # Where k is the number of completed tranches so far
        return (self.traded_volume + self.initial_visible) / (self.completed_tranches + 1)

    def step_through_sequence(self):
        """
        Demonstration of a typical sequence:
            L -> T -> M -> T -> ... -> D
        You can tailor these steps to your actual use case.
        """
        print(f"\n[STEP] Starting state: {self.state}, "
              f"visible = {self.visible_volume}, hidden = {self.hidden_volume}\n")

        # 1) Trade event
        self.trade(6.0)  # Suppose a partial fill of 6
        print(f"State after trade: {self.state}\n")

        # 2) Modify event (change the visible portion)
        self.modify(7.0)
        print(f"State after modify: {self.state}\n")

        # 3) Another trade event
        self.trade(12.0)  # Another fill
        print(f"State after trade: {self.state}\n")

        # 4) Finally, delete the order
        self.delete()
        print(f"State after delete: {self.state}\n")

def main():
    # Example usage:
    # Create an iceberg order with an initial visible volume of 6 and total volume of 18
    iceberg = IcebergOrder(initial_visible=6.0, total_volume=18.0)
    
    # Show a demonstration sequence
    iceberg.step_through_sequence()

if __name__ == "__main__":
    main()

Your output must be:

You can tailor:

  • The peak-size logic to match your exact refill strategy.

  • The sequence of trades and modifications to reflect your real-world use cases.

  • Any additional constraints—e.g., partial refills, partial modifies, multi-step trades.

Detection of synthetic icebergs

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 Quant Beckman
Publisher Privacy ∙ Publisher Terms
Substack
Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture