Fraud ring detection in under 50ms

Detect circular money-laundering patterns up to length 4 by finding cycles in a transaction graph

All recipes· graph· 7 minutesintermediatecypher

Fraud ring detection in under 50ms

Objective

Fraud rings move money in circles — A pays B pays C pays A — to make stolen funds look like legitimate revenue. The pattern is invisible to row-level rules but obvious as a cycle in the transaction graph. This recipe builds a small money-flow graph with one embedded ring and finds it with a single MATCH on a closed path.

Step 1: Create the graph

MERGE (a:Account {id: "ACC-001", holder: "Sarah Chen",     country: "US"})
MERGE (b:Account {id: "ACC-002", holder: "Acme Logistics", country: "US"})
MERGE (c:Account {id: "ACC-003", holder: "Bay Tech LLC",   country: "US"})
MERGE (d:Account {id: "ACC-004", holder: "Raj Patel",      country: "US"})
MERGE (e:Account {id: "ACC-005", holder: "Mia Rossi",      country: "IT"})
MERGE (f:Account {id: "ACC-006", holder: "Leo Park",       country: "KR"})
MERGE (g:Account {id: "ACC-007", holder: "Zoe Williams",   country: "UK"})
MERGE (h:Account {id: "ACC-008", holder: "Eli Tanaka",     country: "JP"})

// Legitimate-looking cash flow
MERGE (a)-[:TRANSFER {amount: 1200,  date: "2026-04-02"}]->(b)
MERGE (b)-[:TRANSFER {amount: 980,   date: "2026-04-03"}]->(g)
MERGE (g)-[:TRANSFER {amount: 600,   date: "2026-04-05"}]->(h)
MERGE (d)-[:TRANSFER {amount: 1500,  date: "2026-04-06"}]->(e)

// THE RING: c -> d -> e -> f -> c, all over 50k, within a tight window
MERGE (c)-[:TRANSFER {amount: 78000, date: "2026-04-10"}]->(d)
MERGE (d)-[:TRANSFER {amount: 76500, date: "2026-04-11"}]->(e)
MERGE (e)-[:TRANSFER {amount: 74000, date: "2026-04-12"}]->(f)
MERGE (f)-[:TRANSFER {amount: 71500, date: "2026-04-13"}]->(c)

// Some additional decoy flow
MERGE (h)-[:TRANSFER {amount: 250,   date: "2026-04-14"}]->(a)
MERGE (b)-[:TRANSFER {amount: 410,   date: "2026-04-15"}]->(g);

Step 2: Find suspicious cycles

// Closed cycles of length 3 to 4 with every hop above $50k.
MATCH cycle = (start:Account)-[t:TRANSFER*3..4]->(start)
WHERE ALL(r IN t WHERE r.amount > 50000)
RETURN [n IN nodes(cycle) | n.id]            AS ring_accounts,
       [r IN relationships(cycle) | r.amount] AS hop_amounts,
       reduce(s = 0, r IN relationships(cycle) | s + r.amount) AS total_moved;

What's happening

  • Cypher's variable-length pattern *3..4 plus the same variable on both ends (start ... start) expresses "closed cycle" directly — the engine prunes paths that don't close.
  • ALL(r IN t WHERE r.amount > 50000) filters out rings of trivial amounts at traversal time.
  • reduce() totals the laundered amount inline, no second query.
  • In SQL this needs a 4-way self-join with equality on the start/end account plus AML thresholds — N^4 cost on the transaction table. On the graph it's local: only edges from start are visited.
  • Tight latency (<50ms on millions of edges) means rules can run on every transfer in real-time.

Try this next

MATCH (a:Account)-[t:TRANSFER]->(b:Account)
RETURN a.country AS from_country, b.country AS to_country,
       count(t) AS transfers, sum(t.amount) AS volume
ORDER BY volume DESC;
MATCH (a:Account)-[:TRANSFER*2..3]->(a)
RETURN DISTINCT a.id, a.holder;
MATCH (a:Account)-[t:TRANSFER]->(b:Account)
WHERE t.amount > 70000
RETURN a.holder AS sender, b.holder AS receiver, t.amount, t.date
ORDER BY t.date;

Tags

graphcypherfraudsecurityintermediate

Run this on your own machine

Install SynapCores Community Edition free, paste the SQL or Cypher above into the bundled web UI, and watch it run.

Download Free CE