Find Circular Money Flow with Neo4j
By: Vlad Batushkov
Tags: Neo4j, Money Laundering, Fraud Detection, Cypher, Graph

How to use the subgraph technique to detect the circular money flow (money laundering) in the Neo4j Database of a finance system

Photo by Allie Smith on Unsplash

Goal

This article is written for educational purposes to share cool stuff about Neo4j graph data modelling, subgraph technique, and Cypher query building. Our goal is to illustrate a type of money laundering activity called “Circular money flow”, of a very simplified financial system using a graph database. This post provides a conceptual view of how this can be approached using graph techniques. It can be useful study-case material for general purposes for beginners, who are already familiar with basic concepts of the Neo4j Database.

Database Schema

We will use a financial system to represent how money transfer from one business entity to another. Business entities involved in this “money-game” are split into three types: Client, Company, and ATM.

The domain model initially based on the explanations from the article “How to generate a huge financial graph with money laundering patterns?” written by M grin. Thank you, M grin, for a well-explained domain.

Entity Nodes

(:Company) — a business entity, that can send and receive money.

(:Client) — a business entity, that can send and receive money.

(:ATM) — a business entity, used as an exit point for the money, so ATM cannot be a money sender.

In a real system, nodes would likely contain specific information about entities, such as identifiers and other attributes. However, for the purposes of this example, we are only concerned about the existence of nodes. In this tutorial, we don’t need any properties on our nodes.

Clients, Companies, and ATM and the money flows between them

The arrows represent money transfers. From the diagram, you may think, that Transactions can be relationships in our graph. Well, it is a possible option, but, I do not recommend you use relationships to represent Transactions in this instance. The Transaction should be a node, and here is why.

Data modelling of Transactions

Transaction — is a noun. A good old definition of a node for data modelling.

From the practical experience, it is also a valid choice. We want to manage them as independent entities. For example, querying all Transaction that were made in the last month is a trivial task on a :Transaction node label:

MATCH (t:Transaction)

WHERE t.timestamp.month = date().month - 1

RETURN t

Last, but not least: Indexes. The index is a well-known feature of Node’s properties and unfortunately non-trivial story for Relationships.

Transaction Node & Relationships

(:Transaction) — an entity, that represents an operation of money transfer between two members. It is a self-sufficient fact, that entity “A” sends money to entity “B”. Relationships between (A) — (Transaction) — (B) helps us to understand who is a sender and who is a receiver.

[:OUT] relationship type — connects the entity of the source node and Transaction . Relationship means that Entity is a money sender.

[:IN] relationship type — connects the entity of a destination node and Transaction . Relationship means that Entity is the receiver of money.

CALL db.schema.visualization()

Initial Schema in Neo4j Database

Reminder: ATM used as an exit point for the money, so ATM cannot be a money sender.

The Transaction node has 2 properties: the amount of money and timestamp of operation. This is important information for our task because we need to know how much money was sent and when it happened.

Visualization of a graph

Now, knowing all of the domain details, we can imagine our graph. Business entity nodes are connected to each other via Transaction nodes. It is also worth mentioning that between the same entities there can be many transactions.

Clients, Companies, and ATM send money to each other using transfer money Transactions.

Task Requirements

Circular money flows is a type of “money laundering” activity in which the money is sent to different accounts, and sent back to the starting node after a certain number of hops. Our goal is to find all of these circular flows in the graph. We don’t need to find extremely long chains, so we will limit our investigations to 10 transactions in the money flow.

Each participant in the money flow may send a marginally less money in the next transaction compare to the previous one. The total “payment” for the whole travel should not exceed some % loss of the source amount.

The flow of transactions will expect that each “next” transaction happens only after the “previous” one has completed. Transactions should be ordered by the creation timestamp.

Example of circular money flow with 3% loss, looped at the Red client

Graph Generation

Now, the practical part. First of all, we need an empty Neo4j Database on a local machine. I will use my Neo4j docker boilerplate to build a simple Docker image from the official one, containing the APOC & GDS plugins.

Simple way is to pull ready-to-use blank image from my docker hub profile and build it.

docker pull vladbatushkov/neo4j-apoc-gds:latest docker run -p 7474:7474 -p 7473:7473 -p 7687:7687 --name=money1 vladbatushkov/neo4j-apoc-gds:latest

Important to setup enough space in the heap and page cache to be able to process a huge amount of data. My image configured for next settings.

ENV NEO4J_dbms_memory_heap_initial__size=4G

ENV NEO4J_dbms_memory_heap_max__size=4G

ENV NEO4J_dbms_memory_pagecache_size=2G

You can also build totally same image and configure it manually doing this.

Dockerfile

Build and run a custom docker container.

docker build . -t=money:dev --no-cache docker run -p 7474:7474 -p 7473:7473 -p 7687:7687 --name=money1 money:dev

Data Generation

Now let’s talk about how to generate the required graph. The generation of a graph data set is always an interesting task to solve. In this particular case, I will use a well-known APOC Graph Generator procedure.

apoc.generate.er(noNodes, noEdges, label, type)

// generates a random graph according to the Erdos-Renyi model

My approach to building a required graph is based on the following steps:

Generate N nodes ( Client ) with M random relationships ( TRANSFER_TO ) between them— our initial set of nodes and relationships of business entities. Partition them between Clients , Companies , and ATM . Break the previously created relationship TRANSFER_TO into the Transaction node structure, as follows:

()-[OUT]->(:Transaction)<-[:IN]-()

The final thing is to clean up the TRANSFER_TO relationships and add Indexes on Transaction properties. Do not forget to turn on the multi-statement mode in your browser.

The script is idempotent, you can run it many times with parameters to fit your needs. Parameters I used are:

500 000 Entities of

5 000 ATMs

50 000 Companies

445 000 Clients

5 000 000 Transactions of

amount of money is a random value up to 1000 conventional units

of money is a random value up to 1000 conventional units timestamp is a random value from today minus up to 1000 minutes earlier