Market manipulation in crypto—spoofing, layering, momentum ignition, and quote stuffing—happens in milliseconds and leaves on-chain and order-book traces that streaming SQL can detect in real time, giving exchanges and regulators the evidence trail they need before manipulators erase their tracks.
Why Crypto Market Surveillance Matters
Crypto markets are 24/7, globally distributed, and until recently lightly surveilled. The same manipulation patterns that prompted the 2010 Dodd-Frank Act in traditional markets are now prevalent in crypto:
- Spoofing: placing large orders with no intent to execute, then canceling them once the price moves in the desired direction.
- Layering: placing multiple orders at different price levels to create a false impression of depth, then canceling all of them.
- Momentum ignition: executing a rapid sequence of trades to create artificial price momentum, then trading against the market reaction.
- Quote stuffing: flooding the order book with rapid-fire orders and cancellations to slow down other participants' systems.
- Wash trading: executing trades between accounts controlled by the same entity to create artificial volume.
Regulators in the US (CFTC, SEC), EU (MiCA framework), and Asia are increasingly scrutinizing these patterns. Exchanges that can demonstrate real-time surveillance infrastructure are better positioned in regulatory discussions than those relying on after-the-fact log analysis.
The surveillance patterns that matter most:
- Order-to-fill ratio: legitimate market makers have high fill rates; spoofing accounts place and cancel orders with low fill ratios.
- Cancel-within-milliseconds rate: orders canceled within 100ms of placement are characteristic of layering.
- Directional momentum followed by reversal: a cluster of same-direction trades followed by an opposite-direction trade of similar size is a momentum ignition signal.
- Quote stuffing rate: order placement rate per second per account that exceeds normal market-making activity.
How Streaming SQL Solves This
RisingWave ingests order book events and trade executions from Kafka and maintains materialized views of behavioral patterns per account and per instrument. Detection queries run continuously—no scheduled batch jobs, no alert delays.
Building It Step by Step
Step 1: Connect the Data Source
CREATE SOURCE order_events (
order_id VARCHAR,
account_id VARCHAR,
instrument_id VARCHAR,
event_type VARCHAR, -- 'PLACE', 'CANCEL', 'FILL', 'PARTIAL_FILL'
side VARCHAR, -- 'BID', 'ASK'
price NUMERIC,
quantity NUMERIC,
filled_quantity NUMERIC,
time_to_cancel_ms BIGINT, -- ms between place and cancel, null if not canceled
event_ts TIMESTAMPTZ
) WITH (
connector = 'kafka',
topic = 'exchange.order.events',
properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;
CREATE SOURCE trade_executions (
trade_id VARCHAR,
instrument_id VARCHAR,
buyer_id VARCHAR,
seller_id VARCHAR,
price NUMERIC,
quantity NUMERIC,
notional_usd NUMERIC,
aggressor_side VARCHAR, -- 'BID', 'ASK'
trade_ts TIMESTAMPTZ
) WITH (
connector = 'kafka',
topic = 'exchange.trades',
properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;
Step 2: Build the Core Materialized View
Compute behavioral indicators per account over rolling windows:
CREATE MATERIALIZED VIEW account_order_behavior AS
SELECT
account_id,
instrument_id,
COUNT(CASE WHEN event_type = 'PLACE' THEN 1 END) AS orders_placed,
COUNT(CASE WHEN event_type = 'CANCEL' THEN 1 END) AS orders_canceled,
COUNT(CASE WHEN event_type IN ('FILL','PARTIAL_FILL') THEN 1 END)
AS orders_filled,
COUNT(CASE WHEN event_type = 'CANCEL'
AND time_to_cancel_ms < 500 THEN 1 END) AS rapid_cancels,
-- Order-to-fill ratio: high = potential spoofing
COUNT(CASE WHEN event_type = 'PLACE' THEN 1 END)
/ NULLIF(COUNT(CASE WHEN event_type IN ('FILL','PARTIAL_FILL') THEN 1 END), 0)
AS order_fill_ratio,
-- Rapid cancel rate
COUNT(CASE WHEN event_type = 'CANCEL'
AND time_to_cancel_ms < 500 THEN 1 END)
/ NULLIF(COUNT(CASE WHEN event_type = 'PLACE' THEN 1 END), 0) * 100
AS rapid_cancel_rate_pct
FROM order_events
WHERE event_ts > NOW() - INTERVAL '1 hour'
GROUP BY account_id, instrument_id;
CREATE MATERIALIZED VIEW account_trade_behavior AS
SELECT
buyer_id AS account_id,
instrument_id,
COUNT(*) AS buy_count,
SUM(notional_usd) AS buy_volume_usd,
MAX(trade_ts) AS last_buy_ts
FROM trade_executions
WHERE trade_ts > NOW() - INTERVAL '1 hour'
GROUP BY buyer_id, instrument_id
UNION ALL
SELECT
seller_id,
instrument_id,
COUNT(*),
SUM(notional_usd),
MAX(trade_ts)
FROM trade_executions
WHERE trade_ts > NOW() - INTERVAL '1 hour'
GROUP BY seller_id, instrument_id;
Step 3: Detection Logic and Alerts
Flag accounts showing manipulation-indicative patterns:
CREATE MATERIALIZED VIEW manipulation_flags AS
SELECT
ob.account_id,
ob.instrument_id,
ob.orders_placed,
ob.orders_canceled,
ob.rapid_cancels,
ob.order_fill_ratio,
ob.rapid_cancel_rate_pct,
-- Spoofing flag: very high order-to-fill ratio with rapid cancels
CASE WHEN ob.order_fill_ratio > 20
AND ob.rapid_cancels > 10
THEN TRUE ELSE FALSE END AS spoofing_flag,
-- Layering flag: many orders placed then rapidly canceled
CASE WHEN ob.rapid_cancel_rate_pct > 80
AND ob.orders_placed > 30
THEN TRUE ELSE FALSE END AS layering_flag,
-- Quote stuffing flag: extreme order placement rate
CASE WHEN ob.orders_placed > 500
THEN TRUE ELSE FALSE END AS quote_stuffing_flag,
NOW() AS evaluated_at
FROM account_order_behavior ob
WHERE ob.order_fill_ratio > 10
OR ob.rapid_cancel_rate_pct > 60
OR ob.orders_placed > 300;
CREATE SINK surveillance_alert_sink AS
SELECT
account_id,
instrument_id,
order_fill_ratio,
rapid_cancel_rate_pct,
spoofing_flag,
layering_flag,
quote_stuffing_flag,
evaluated_at
FROM manipulation_flags
WHERE spoofing_flag OR layering_flag OR quote_stuffing_flag
WITH (
connector = 'kafka',
topic = 'compliance.market.surveillance',
properties.bootstrap.server = 'kafka:9092'
) FORMAT PLAIN ENCODE JSON;
Step 4: Querying Live Results
Pull the current surveillance watchlist sorted by highest order-to-fill ratio:
SELECT
account_id,
instrument_id,
orders_placed,
orders_canceled,
rapid_cancels,
ROUND(order_fill_ratio, 2) AS order_fill_ratio,
ROUND(rapid_cancel_rate_pct, 2) AS rapid_cancel_pct,
spoofing_flag,
layering_flag,
quote_stuffing_flag,
evaluated_at
FROM manipulation_flags
WHERE spoofing_flag OR layering_flag OR quote_stuffing_flag
ORDER BY order_fill_ratio DESC
LIMIT 10;
Comparison Table
| Surveillance Approach | Detection Latency | Evidence Quality | Regulatory Auditability |
| Manual log review | Days | High | Low (ad hoc) |
| End-of-day batch scripts | Hours | Moderate | Low |
| Real-time rules engine | Seconds | Moderate | Medium |
| Streaming SQL (RisingWave) | < 1 second | High | High (SQL audit trail) |
FAQ
What order-to-fill ratio threshold is appropriate for flagging spoofing? There is no universal threshold—it depends on the asset class and market conditions. High-frequency market makers legitimately cancel many orders as they update quotes. A baseline approach: compute the 95th percentile order-to-fill ratio across all accounts for a given instrument, and flag accounts more than 2 standard deviations above that baseline. This adaptive threshold is implementable as a materialized view that joins individual account ratios against the instrument-level distribution.
How do you detect wash trading with streaming SQL?
Wash trading leaves a signature in trade_executions: the same entity appears as both buyer and seller (directly or via affiliated accounts). A direct self-trade shows buyer_id = seller_id. Network-level wash trading (using related accounts) requires a related_accounts reference table mapping account IDs to controlling entities, then a join against trade_executions to find pairs where both counterparties map to the same entity.
Can this pipeline produce audit-ready evidence for regulatory submissions? Yes. RisingWave materialized views persist query results and maintain the computation logic in SQL—a format regulators can read. For audit purposes, supplement the surveillance pipeline with an append-only Kafka sink that captures every alert with the full event context (account, instrument, behavioral metrics, timestamp) as an immutable evidence record. This log can be produced on demand for regulatory requests.
Key Takeaways
- Spoofing, layering, and momentum ignition patterns manifest as statistical anomalies in order-to-fill ratios and cancel timing—detectable in real time with streaming SQL.
- RisingWave maintains rolling behavioral profiles per account as continuously updated materialized views, enabling instant comparison of current behavior against baseline.
- Streaming SQL surveillance infrastructure produces SQL-auditable evidence trails that satisfy regulatory examination requirements.
- Detection latency under one second means surveillance flags appear before manipulators have fully covered their tracks, enabling timely intervention.

