Order book analytics using the N-Dimensional array
In version 9, QuestDB gained a major new data type: the N-dimensional array. The best way to think of it is "a NumPy array in a single database column". The array, no matter how many dimensions it has, is stored as a single, uninterrupted block of numbers. This allows it to be processed by the CPU at the blazing speed of pure sequential access, using vectorized SIMD instructions.
In the initial incarnation we focused on the use case of order book analytics.
The array can currently only store DOUBLE
values, which is what we need here.
We model an order book as a two-dimensional array with two rows: the prices, and
the corresponding order sizes (volumes).
row/col | 1 | 2 | 3 | ... |
---|---|---|---|---|
1 | price1 | price2 | price3 | ... |
2 | size1 | size2 | size3 | ... |
An order book documents all the outstanding market orders, sorted from the most to the least favorable one. For buy orders, the book proceeds from the highest bid price to the lowest one; for sell orders, it starts with the lowest price. The best bid price is always less than the best ask price, otherwise the orders would have been matched and executed.
At any given column, the two rows document all orders issued at the exact price stated in the first row, and their combined volume in the second row.
Actors on the market use the order book to gain all kinds of insight into the pulse of the market, way beyond just the current bid/ask price and their spread. In this blog post we'll go over a number of specific examples with the most typical analytics you can do on order books.
Why N-dimensional arrays for order books?
QuestDB adopters have so far stored order book data using multiple columns for each price level, creating schemas like this:
CREATE TABLE market_data (timestamp TIMESTAMP,symbol SYMBOL,bid_price_1 DOUBLE, bid_volume_1 DOUBLE,bid_price_2 DOUBLE, bid_volume_2 DOUBLE,bid_price_3 DOUBLE, bid_volume_3 DOUBLE,-- ... up to bid_price_20, bid_volume_20ask_price_1 DOUBLE, ask_volume_1 DOUBLE,ask_price_2 DOUBLE, ask_volume_2 DOUBLE,ask_price_3 DOUBLE, ask_volume_3 DOUBLE-- ... up to ask_price_20, ask_volume_20) TIMESTAMP(timestamp) PARTITION BY HOUR;
To calculate total liquidity, you'd need verbose, repetitive queries:
SELECTtimestamp,bid_volume_1 + bid_volume_2 + bid_volume_3 + ... + bid_volume_20 total_bid_liquidity,ask_volume_1 + ask_volume_2 + ask_volume_3 + ... + ask_volume_20 total_ask_liquidityFROM market_data WHERE symbol='EURUSD';
N-dimensional arrays eliminate this complexity, turning 40+ column references
into simple array functions like array_sum(bids[2])
.
The table schema for examples
The examples will use a simple database schema, capturing the essence of an
order book table. Each row has a timestamp, a symbol identifying the financial
instrument, and two order books: bids
and asks
.
CREATE TABLE market_data (timestamp TIMESTAMP,symbol SYMBOL,bids DOUBLE[][],asks DOUBLE[][]) TIMESTAMP(timestamp) PARTITION BY HOUR;
We'll provide both purpose-built sample queries, and the INSERT statements required to replicate these queries.
Basic order book analytics
What is the bid-ask spread at any moment?
The bid-ask spread is the difference between the highest bid price and the lowest ask price, serving as a fundamental indicator of market liquidity and trading costs. A tighter spread typically indicates higher liquidity and lower transaction costs, while wider spreads may signal periods of market uncertainty or lower trading activity.
This query extracts the best bid and ask prices from the first position of each
side of the order book (where bids[1][1]
and asks[1][1]
are the top-of-book
prices) and calculates the spread. Monitoring spread dynamics over time helps
traders identify optimal entry points and assess market conditions, as spreads
tend to widen during volatile periods or outside major trading sessions.
SELECT timestamp, spread(bids[1][1], asks[1][1]) * 1000 spread_pipsFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [1.1498, 1.1495], [2_500_000, 1_800_000] ],ARRAY[ [1.1502, 1.1505], [3_000_000, 2_200_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [1.1499, 1.1497], [2_800_000, 2_100_000] ],ARRAY[ [1.1503, 1.1506], [2_600_000, 1_900_000] ]);
timestamp | spread_pips |
---|---|
2025-07-01T12:00:00 | 0.4 |
2025-07-01T12:00:01 | 0.4 |
What is the total liquidity available on each side?
Total liquidity measurement provides a fundamental view of market depth by summing all available volume across all price levels on both bid and ask sides of the order book. This metric serves as a primary indicator of market health and trading opportunity, as deeper liquidity generally translates to lower transaction costs and reduced price impact for large orders.
Traders and portfolio managers use total liquidity measurements to assess venue quality, timing market entries, and estimating the market's capacity to absorb their trading activity without significant price disruption. During periods of market stress, monitoring liquidity depletion can serve as an early warning system for potential volatility spikes.
SELECTtimestamp,array_sum(bids[2]) total_bid_liquidity,array_sum(asks[2]) total_ask_liquidity,total_bid_liquidity + total_ask_liquidity total_liquidityFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD',ARRAY[ [1.1498, 1.1495, 1.1492], [2_500_000, 2_000_000, 1_500_000] ],ARRAY[ [1.1502, 1.1505, 1.1508], [3_000_000, 1_800_000, 1_200_000] ]),('2025-07-01T12:00:01Z', 'EURUSD',ARRAY[ [1.1499, 1.1496, 1.1493], [1_800_000, 2_200_000, 1_000_000] ],ARRAY[ [1.1501, 1.1504, 1.1507], [1_500_000, 2_500_000, 2_000_000] ]);
timestamp | total_bid_liquidity | total_ask_liquidity | total_liquidity |
---|---|---|---|
2025-07-01T12:00:00 | 6,000,000 | 6,000,000 | 12,000,000 |
2025-07-01T12:00:01 | 5,000,000 | 6,000,000 | 11,000,000 |
Liquidity-driven execution
How much volume is available within 1% of the best price?
When executing large orders, traders need to understand how much liquidity is available near the current market price to minimize slippage and market impact. This query provides the cumulative volume available within a 1% price tolerance from the best ask price, which is crucial for institutional traders planning significant position entries.
The query leverages array slicing to identify all price levels within the specified threshold, then sums the corresponding volumes to calculate total available liquidity.
By monitoring liquidity depth at various price distances, a trader can optimize their execution strategy, choosing between aggressive market orders when sufficient depth exists or implementing gradual accumulation strategies when liquidity is thin. This metric becomes particularly valuable during volatile market conditions, when traditional liquidity patterns may be disrupted.
DECLARE@prices := asks[1],@volumes := asks[2],@best_price := @prices[1],@multiplier := 1.01,@target_price := @multiplier * @best_price,@relevant_volume_levels := @volumes[1:insertion_point(@prices, @target_price)]SELECT timestamp, array_sum(@relevant_volume_levels) total_volumeFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510, 1.1512, 1.1514],[1_000_000, 1_500_000, 1_300_000, 1_200_000, 1_800_000, 2_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510, 1.1512, 1.1514],[100_000, 500_000, 300_000, 200_000, 800_000, 1_000_000] ]);
timestamp | volume |
---|---|
2025-07-01T12:00:00 | 8,800,000 |
2025-07-01T12:00:01 | 900,000 |
How much of a large order can be executed without moving the price more than a set amount?
Market impact analysis is essential for large order execution, as significant trades can push prices unfavorably against the trader's position. This query calculates the maximum order size that can be absorbed by the order book before the price moves beyond a predetermined threshold — in this case, 10 pips above the best ask price.
By identifying the exact order book level where the cumulative volume would cause the price to breach the acceptable range, a trader can determine optimal order sizing and execution timing.
This is particularly valuable for institutional traders who need to execute substantial positions while maintaining stealth and minimizing adverse price movements.
The query finds the order book level at which the price passes a threshold, and then sum the sizes up to that level.
DECLARE@prices := asks[1],@volumes := asks[2],@best_price := @prices[1],@price_delta := 0.1,@target_price := @best_price + @price_delta,@relevant_volumes := @volumes[1:insertion_point(@prices, @target_price)]SELECT timestamp, array_sum(@relevant_volumes) volumeFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510, 1.1512, 1.1514],[1_000_000, 1_500_000, 1_300_000, 1_200_000, 1_800_000, 2_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1510, 1.1512, 1.1514, 1.1516, 1.1518],[100_000, 500_000, 300_000, 200_000, 800_000, 1_000_000] ]);
timestamp | volume |
---|---|
2025-07-01T12:00:00 | 3,800,000 |
2025-07-01T12:00:01 | 600,000 |
What price level will a buy order for the given volume reach?
Price impact estimation is crucial for traders who need to predict the final execution price of their orders before committing to a trade. This query performs a reverse calculation from the previous example, determining what price level a buy order of specified volume (3 million EUR in this case) will reach by walking through the order book's ask side. The analysis uses cumulative volume summation to find the exact point where the target order size would be fully filled, then identifies the corresponding price at that level.
This forward-looking approach enables traders to calculate expected slippage costs and compare different execution strategies before placing orders. For algorithmic trading systems, this type of analysis is essential for optimizing order routing decisions and determining whether to execute immediately at market prices or wait for more favorable liquidity conditions to develop.
DECLARE@prices := asks[1],@volumes := asks[2],@target_volume := 3_000_000SELECTtimestamp,array_cum_sum(@volumes) cum_volumes,insertion_point(cum_volumes, @target_volume, true) target_level,@prices[target_level] priceFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510, 1.1512, 1.1514],[1_000_000, 1_500_000, 1_300_000, 1_200_000, 1_800_000, 2_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510, 1.1512, 1.1514],[1_000_000, 500_000, 300_000, 1_200_000, 1_800_000, 2_000_000] ]);
timestamp | cum_volumes | target_level | price |
---|---|---|---|
2025-07-01T12:00:00 | [1,000,000, 2,500,000, 3,800,000, 5,000,000, ...] | 3 | 1.1504 |
2025-07-01T12:00:01 | [1,000,000, 1,500,000, 1,800,000, 3,000,000, ...] | 4 | 1.1510 |
What will be Level-2 price of a large order?
Level-2 pricing analysis is essential for institutional traders who need to understand the true cost of executing large orders that will consume multiple price levels in the order book.
Unlike simple market orders that execute at the best available price,
substantial trades must "walk the book," filling against progressively worse
prices as they exhaust liquidity at each level. QuestDB natively supports the
l2price()
function, and we use it here to calculate the volume-weighted
average price (VWAP) that a 5 million EUR buy order would achieve by consuming
ask-side liquidity across multiple price tiers.
This type of analysis is crucial for comparing execution costs across different venues, timing strategies, and order sizing decisions, as it reveals the true cost of immediacy versus the potential benefits of breaking large orders into smaller parcels executed over time.
SELECT l2price(5_000_000, asks[2], asks[1]) l2priceFROM market_data where symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1505, 1.1510],[1_400_000, 1_600_000, 2_300_000, 1_200_000] ]);
Inserted data in tabular form:
Price | Size |
---|---|
1.1500 | 1,400,000 |
1.1502 | 1,600,000 |
1.1505 | 2,300,000 |
1.1510 | 1,200,000 |
A buy market order with the size of 5,000,000 would wipe out the first two price levels of the Ask side of the book, and would also trade on the third level.
The full price of the trade:
1,400,000 × $1.1500 + 1,600,000 × $1.1502 + (5,000,000 - 1,400,000 - 1,600,000) × $1.1505 = $5,751,320
The average price of the instrument in this trade:
$5,751,320 ÷ 5,000,000 = $1.150264
Therefore, the result of running the query is this:
l2price |
---|
1.150264 |
Order book imbalance
Imbalance at the top level of the order book
Order book imbalance at the top level provides critical insights into immediate buying and selling pressure, often serving as a leading indicator of short-term price movement. When bid volume significantly exceeds ask volume at the best prices, it suggests strong buying interest that may push prices higher, while the opposite scenario indicates potential downward pressure.
This ratio calculation compares the volume available at the best bid price to the volume at the best ask price, a simple yet powerful metric for gauging market sentiment.
Professional traders and market makers closely monitor these imbalances as they can signal impending price movements before they occur, particularly during periods of high market activity. Values significantly above 1.0 indicate bid-heavy conditions, while ratios below 1.0 suggest ask-heavy order flow, helping traders anticipate the likely direction of immediate price action.
SELECT timestamp, bids[2, 1] / asks[2, 1] imbalanceFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [0.0,0], [2_000_000, 2_500_000] ],ARRAY[ [0.0,0], [1_000_000, 1_500_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [0.0,0], [1_400_000, 4_500_000] ],ARRAY[ [0.0,0], [1_500_000, 200_000] ]);
timestamp | imbalance |
---|---|
2025-07-01T12:00:00 | 2.00 |
2025-07-01T12:00:01 | 0.93 |
Cumulative imbalance (Top 3 Levels)
While top-of-book imbalance provides immediate directional signals, examining cumulative imbalance across multiple price levels offers a more comprehensive view of market depth and potential price sustainability.
Multi-level imbalance analysis is particularly valuable because it captures the strength of support and resistance zones, revealing whether apparent price pressure at the top level is backed by substantial volume at nearby levels. When cumulative bid volume substantially outweighs ask volume across these levels, it suggests robust buying interest that could sustain upward price movement, while the inverse indicates potential selling pressure.
This query aggregates bid and ask volumes across the top three levels of the order book, creating a broader picture of buying versus selling interest that extends beyond just the best prices.
DECLARE@bid_volumes := bids[2],@ask_volumes := asks[2]SELECTtimestamp,array_sum(@bid_volumes[1:4]) bid_vol,array_sum(@ask_volumes[1:4]) ask_vol,bid_vol / ask_vol ratioFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [0.0,0,0,0], [2_000_000, 2_500_000, 2_300_000, 2_200_000] ],ARRAY[ [0.0,0,0,0], [1_000_000, 1_500_000, 1_300_000, 1_200_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [0.0,0,0,0], [1_400_000, 4_500_000, 2_200_000, 500_000] ],ARRAY[ [0.0,0,0,0], [1_500_000, 200_000, 2_000_000, 2_300_000] ]);
timestamp | bid_vol | ask_vol | ratio |
---|---|---|---|
2025-07-01T12:00:00 | 6,800,000 | 3,800,000 | 1.79 |
2025-07-01T12:00:01 | 8,100,000 | 3,700,000 | 2.19 |
Detect quote stuffing/fading (Volume dropoff)
Order book depth analysis beyond the top levels reveals crucial market microstructure patterns that can indicate either genuine liquidity constraints or potential market manipulation, like stuffing or fading.
When volume drops precipitously after the first few price levels, it may signal two distinct scenarios: natural market thinning where genuine liquidity becomes scarce, or artificial order book stuffing where large orders at the top are designed to create a false impression of depth.
This query compares the average volume in the immediate top levels against deeper levels, flagging instances where the ratio exceeds a specified threshold (3.0 in this case). Such dramatic volume dropoffs can be particularly problematic for large order execution, as they suggest that apparent liquidity may evaporate quickly once trading begins.
Smart order routing algorithms often incorporate these depth patterns to avoid executing in markets with potentially unstable liquidity structures, instead seeking venues with more consistent volume distribution across multiple price levels.
DECLARE@volumes := asks[2],@dropoff_ratio := 3.0SELECT * FROM (SELECTtimestamp,array_avg(@volumes[1:3]) top,array_avg(@volumes[3:6]) deepFROM market_data WHERE symbol='EURUSD')WHERE top > @dropoff_ratio * deep;
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [0.0,0,0,0,0,0],[2_000_000, 1_500_000, 1_300_000, 1_200_000, 1_800_000, 2_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [0.0,0,0,0,0,0],[2_000_000, 2_500_000, 300_000, 700_000, 500_000, 200_000] ]);
timestamp | top | deep |
---|---|---|
2025-07-01T12:00:01 | 2,250,000 | 500,000 |
Detect sudden bid/ask drop
Sudden volume withdrawals at the best bid and ask prices often serve as early warning signals of impending price movements, as informed traders typically pull their orders before adverse market shifts occur.
This pattern recognition technique identifies moments when top-level liquidity mysteriously disappears, which can indicate that market participants with superior information are repositioning ahead of news releases, economic data, or other market-moving events.
When large orders are suddenly withdrawn from the top of the book, it creates a vacuum that can lead to increased volatility and wider spreads, making execution more challenging for remaining market participants. The detection of such withdrawal patterns can serve as a risk management tool, alerting traders to potential market instability and prompting them to adjust their trading strategies or position sizes accordingly.
DECLARE@top_bid_volume := bids[2, 1],@top_ask_volume := asks[2, 1],@drop_ratio := 1.5SELECT * FROM (SELECTtimestamp,lag(@top_bid_volume) OVER () prev_bid_vol,@top_bid_volume curr_bid_vol,lag(@top_ask_volume) OVER () prev_ask_vol,@top_ask_volume curr_ask_volFROM market_data WHERE symbol='EURUSD')WHERE prev_bid_vol > curr_bid_vol * @drop_ratio OR prev_ask_vol > curr_ask_vol * @drop_ratio;
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [0.0], [1_000_000] ], ARRAY[ [0.0], [1_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [0.0], [900_000] ], ARRAY[ [0.0], [900_000] ]),('2025-07-01T12:00:02Z', 'EURUSD', ARRAY[ [0.0], [800_000] ], ARRAY[ [0.0], [400_000] ]),('2025-07-01T12:00:03Z', 'EURUSD', ARRAY[ [0.0], [400_000] ], ARRAY[ [0.0], [400_000] ]);
timestamp | prev_bid_vol | curr_bid_vol | prev_ask_vol | curr_ask_vol |
---|---|---|---|---|
2025-07-01T12:00:02 | 900,000 | 800,000 | 900,000 | 400,000 |
2025-07-01T12:00:03 | 800,000 | 400,000 | 400,000 | 400,000 |
Price-weighted volume imbalance
Price-weighted volume imbalance provides a sophisticated measure of market pressure that goes beyond simple volume comparisons by incorporating the distance of each order from the current market center.
This query weights volume at each price level by its deviation from the mid-price. Orders placed further from the mid-price carry more weight in this calculation, as they represent stronger conviction from market participants willing to pay premium prices or accept discounted sales.
This approach is particularly valuable for detecting subtle shifts in market sentiment that might not be apparent in traditional volume-based metrics, as it reveals whether significant orders are clustered near the market or positioned at more distant levels, indicating different degrees of urgency and conviction among market participants.
DECLARE@bid_prices := bids[1],@bid_volumes := bids[2],@ask_prices := asks[1],@ask_volumes := asks[2],@best_bid_price := bids[1, 1],@best_ask_price := asks[1, 1]SELECTtimestamp,round((@best_bid_price + @best_ask_price) / 2, 2) mid_price,(mid_price - @bid_prices) * @bid_volumes weighted_bid_pressure,(@ask_prices - mid_price) * @ask_volumes weighted_ask_pressureFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [1.1498, 1.1496], [1_000_000, 2_000_000] ], ARRAY[ [1.1502, 1.1504], [1_500_000, 2_500_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [1.1499, 1.1497], [2_000_000, 2_500_000] ], ARRAY[ [1.1501, 1.1503], [2_000_000, 900_000] ]);
timestamp | mid_price | weighted_bid_pressure | weighted_ask_pressure |
---|---|---|---|
2025-07-01T12:00:00 | 1.15 | [200.0, 800.0] | [300.0, 1000.0] |
2025-07-01T12:00:01 | 1.15 | [2000.0, 750.0] | [300.0, 270.0] |
Order book depth distribution
Understanding how volume is distributed across different price levels provides crucial insights into market structure and potential support/resistance zones. When volume is heavily concentrated at specific price levels, these areas often act as significant psychological barriers that can halt or reverse price movements.
This query calculates the percentage of total ask-side volume that exists at each price level, helping traders identify where the most significant liquidity pools reside. High-volume concentrations often represent institutional order placement or algorithmic strategies, while sparse volume areas may indicate potential breakout zones where prices could move rapidly once initial resistance is overcome.
Market makers and institutional traders use this distribution analysis to optimize their order placement strategies, avoiding areas of heavy competition while targeting zones with favorable risk-reward profiles.
DECLARE@volumes := asks[2],@total_volume := array_sum(@volumes)SELECTtimestamp,@volumes volumes,(@volumes * 100.0 / @total_volume) volume_pctFROM market_data WHERE symbol='EURUSD';
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510],[2_000_000, 1_500_000, 3_500_000, 1_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [1.1500, 1.1502, 1.1504, 1.1510],[1_000_000, 4_000_000, 500_000, 2_500_000] ]);
timestamp | volumes | volume_pct |
---|---|---|
2025-07-01T12:00:00 | [2,000,000, 1,500,000, 3,500,000, 1,000,000] | [25.0, 18.75, 43.75, 12.5] |
2025-07-01T12:00:01 | [1,000,000, 4,000,000, 500,000, 2,500,000] | [12.5, 50.0, 6.25, 31.25] |
Risk Management and Monitoring
Assess liquidity concentration risk
Liquidity concentration analysis identifies scenarios where a disproportionate amount of available volume is clustered at just a few price levels, creating potential execution bottlenecks. When liquidity is highly concentrated, the apparent market depth can be misleading, as exhausting these key levels may leave traders facing significantly worse prices for the remainder of their orders.
This query calculates what percentage of total volume is held in the top three price levels compared to all available levels, flagging situations where concentration exceeds a risk threshold (75% in this example). High concentration ratios suggest that market depth is more fragile than it appears, and large orders could quickly exhaust the available liquidity.
Risk management systems use concentration metrics to adjust maximum order sizes and implement more gradual execution strategies when liquidity distribution appears unstable or overly concentrated at specific price points.
DECLARE@volumes := asks[2],@total_volume := array_sum(@volumes),@top3_volume := array_sum(@volumes[1:4]),@concentration_threshold := 0.75SELECT * FROM (SELECTtimestamp,@top3_volume top3_volume,@total_volume total_volume,round(@top3_volume / @total_volume, 3) concentration_ratioFROM market_dataWHERE symbol='EURUSD') WHERE concentration_ratio > @concentration_threshold;
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', NULL, ARRAY[ [0,0,0,0,0,0],[4_000_000, 3_500_000, 2_000_000, 300_000, 100_000, 100_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', NULL, ARRAY[ [0,0,0,0,0,0],[1_500_000, 1_800_000, 2_200_000, 1_200_000, 800_000, 500_000] ]);
timestamp | top3,volume | total_volume | concentration_ratio |
---|---|---|---|
2025-07-01T12:00:00 | 9,500,000 | 10,000,000 | 0.950 |
Detect abnormal spread widening
Abnormal spread widening often signals market stress, impending volatility, or deteriorating liquidity conditions that can significantly impact trading costs and execution quality. When spreads suddenly expand beyond normal ranges, it typically indicates that market makers are withdrawing liquidity or demanding higher compensation for providing it due to increased uncertainty or risk.
This query monitors bid-ask spreads over time and flags instances where the current spread exceeds the recent average by more than a specified multiple (2.5x in this example). Such dramatic spread expansion can occur during news events, economic data releases, or periods of market instability, serving as an early warning system for traders to adjust their execution strategies or postpone non-urgent trades until normal market conditions resume.
Automated trading systems often incorporate spread monitoring to avoid executing during periods of artificially inflated transaction costs, instead waiting for spreads to normalize or routing orders to alternative venues with more stable pricing.
DECLARE@current_spread := asks[1][1] - bids[1][1],@spread_multiplier := 2.5SELECTtimestamp,current_spread * 1000 current_spread_pips,avg_spread * 1000 avg_spread_pipsFROM (SELECTtimestamp,@current_spread current_spread,avg(@current_spread) OVER (ROWS BETWEEN 10 PRECEDING AND 1 PRECEDING) avg_spreadFROM market_dataWHERE symbol='EURUSD') WHERE current_spread > avg_spread * @spread_multiplier;
Sample data and result
INSERT INTO market_data VALUES('2025-07-01T12:00:00Z', 'EURUSD', ARRAY[ [1.1499], [1_000_000] ],ARRAY[ [1.1501], [1_000_000] ]),('2025-07-01T12:00:01Z', 'EURUSD', ARRAY[ [1.1498], [1_000_000] ],ARRAY[ [1.1502], [1_000_000] ]),('2025-07-01T12:00:02Z', 'EURUSD', ARRAY[ [1.1497], [1_000_000] ],ARRAY[ [1.1503], [1_000_000] ]),('2025-07-01T12:00:03Z', 'EURUSD', ARRAY[ [1.1495], [1_000_000] ],ARRAY[ [1.1508], [1_000_000] ]);
timestamp | current_spread_pips | avg_spread_pips |
---|---|---|
2025-07-01T12:00:03 | 1.3 | 0.4 |
Conclusion
N-dimensional arrays represent a fundamental shift in how financial market data can be stored and analyzed in QuestDB. By modeling order books as compact 2D arrays, we've eliminated the complexity of traditional multi-column approaches while unlocking powerful vectorized analytics that were previously cumbersome or impossible.
These example queries showcase just the beginning of what's possible — from basic spread calculations to sophisticated market microstructure analysis, all executed with unprecedented performance thanks to SIMD-optimized array operations.
The combination of QuestDB's time-series database capabilities with NumPy-style array operations opens up entirely new possibilities for understanding market dynamics at scale.
Ready to get started? Check out the QuestDB documentation for installation instructions and begin exploring N-dimensional arrays with your own market data.