AML Transaction Monitoring: Rule Design and Testing

A self-directed Excel project on a synthetic dataset of about 5 million transactions

Summary

Banks cannot investigate every payment, so they use rules to decide which ones to flag, and every rule trades missed laundering against false alarms. I downloaded a synthetic anti-money-laundering dataset of about 5 million transactions from Kaggle, reduced it to a testable sample, explored it for laundering patterns, wrote five rules, graded each against the labelled outcomes, and put everything on a dashboard that refreshes with one click.

The main finding is the trade-off. Flagging every ACH payment catches 87% of the laundering but also stops 11.5% of honest payments, while narrower rules are far more accurate but catch much less, and each time I widened the net the extra flags were less likely to be real.

Top of the Excel dashboard showing key numbers and a scoreboard of five rules Lower part of the dashboard showing a chart of detection against false alarms and the key findings
The dashboard: key numbers, rule scoreboard, chart and findings. A VBA macro refreshes everything with one click.

Request the workbook (macro-enabled Excel file)

The problem

A transaction-monitoring system flags suspicious payments for investigators to review. A good system must catch as much laundering as possible without drowning investigators in false alarms. This project builds a small version of that system and measures how well it works.

The data

I downloaded the synthetic dataset IBM Transactions for Anti Money Laundering (AML), published by IBM Research on Kaggle. The file was about 464 MB and held roughly 5 million transactions. Each row is one payment, with the banks, accounts, amounts, currencies, payment format and an Is Laundering label (1 for laundering, 0 for normal). The creators generated the laundering themselves, which is why every row is labelled. Real banks never have that label. I used it only to grade my rules, never to build them.

What I did, step by step

1. Loaded the data

I loaded the first 100,000 rows into Excel with Power Query to learn the tools. I checked the row count and that the bank codes kept their leading zeros (for example 010 and 03208), because codes like these must stay text and not turn into numbers.

2. Found a problem and fixed it

Only 5 of those 99,999 rows were laundering, far too few to test anything. I rebuilt the Power Query steps to read the whole 5-million-row file and keep every laundering transaction plus every 100th normal one. The result was 55,919 rows, 5,177 of them laundering (9.3%), and I confirmed the count by filtering the table and reading the row count in the status bar.

3. Explored the data with PivotTables

I looked for clues before writing any rules:

4. Wrote five rules

Each rule is an Excel formula in its own column that flags a payment 1 or 0, built only from information a bank would have: all ACH payments; ACH in US Dollar from 10,000 to 20,000; ACH in US Dollar or Euro from 5,000 to 20,000; ACH in US Dollar or Euro from 2,000 to 20,000; and the previous rule plus ACH payments in Saudi Riyal.

5. Graded every rule

I sorted each payment into four groups: laundering caught, honest payment wrongly flagged (a false alarm), laundering missed, and honest payment correctly left alone. A scoreboard built with COUNTIFS formulas then calculates three scores for all five rules automatically. Detection rate is how much of the laundering the rule catches, false positive rate is how many honest payments it wrongly flags, and precision is how many flagged payments are real.

6. Charted and built the dashboard

I plotted detection against false positive rate for each rule, then designed a one-page dashboard with key numbers, a linked scoreboard with data bars, the chart and a written list of findings.

7. Automated it

A VBA macro with a button refreshes every PivotTable, recalculates the scoreboard, and checks that each rule's four groups add up to all 55,919 payments, a reconciliation check like those used in compliance reporting.

Results

RulePayments flaggedDetection rateFalse positive ratePrecision
1. All ACH10,33686.6%11.5%43.4%
2. ACH, USD, 10,000 to 20,00065710.9%0.2%86.0%
3. ACH, USD or EUR, 5,000 to 20,0001,79528.5%0.6%82.2%
4. ACH, USD or EUR, 2,000 to 20,0003,14542.2%1.9%69.4%
5. Rule 4 plus Saudi Riyal ACH3,59949.2%2.1%70.7%

Rule 1 catches the most laundering (86.6%) but flags more than 10,000 payments and wrongly stops 11.5% of honest ones. The narrower rules are far more accurate, but the most precise of them catch only a fraction of the laundering, and the best balance, Rule 5, catches about half.

Key insight: diminishing returns

Widening a rule catches more laundering but also stops more honest payments, and the extra flags get less useful each time. As the rules widened, the added flags were 80% real, then 53%, then 32%. Flagging more is not a free fix, and the right rule depends on how many alerts an investigation team can review.

From alert to investigation

A flag is only the start of the work. In a bank, an alert goes to an investigator, who decides whether the activity has a legitimate explanation or must be escalated. This section shows how I would route alerts using the results above, and then walks through one flagged account from the dataset. The workbook itself stops at the flags, so this describes how I would use its output and is not something it automates.

How I would use each rule

RulePrecisionFalse positive rateHow I would use it
1. All ACH43.4%11.5%Too noisy to send straight to investigators, so I would use it only as one risk signal among others
2. ACH, USD, 10,000 to 20,00086.0%0.2%High-priority queue, since there are few alerts and most are real
3. ACH, USD or EUR, 5,000 to 20,00082.2%0.6%Standard review queue
4. ACH, USD or EUR, 2,000 to 20,00069.4%1.9%Review when capacity allows, or when the account already has other alerts
5. Rule 4 plus Saudi Riyal ACH70.7%2.1%The widest balanced rule, best used to group alerts by account into a single case

Example case from the data (synthetic)

The accounts and banks in this dataset are synthetic identifiers, so nothing here describes a real customer. The case below is account 808B1C350 at bank 19329.

What would worry an investigator

How I would handle it

  1. Group every alert on the account into one case, so the investigator reviews one account and not more than twenty separate payments.
  2. Compare the activity with what the customer is expected to do, using KYC information such as occupation, source of funds and expected turnover. This dataset holds none of that, so in a real bank the investigation would start here.
  3. Examine the counterparties: whether the senders and recipients are connected to each other or to the customer, and whether they appear in other cases.
  4. Screen the parties against sanctions, PEP and adverse media lists, and look at how the money moved through the account.
  5. Decide and record the decision. If there is no credible explanation, escalate to the money laundering reporting officer so that a suspicious activity report can be considered, with the timeline and the reasons written down. If there is one, close the case with the explanation noted.

What this shows about my rules

My rules judge one payment at a time, and Rule 5 flagged 21 of this account's 22 laundering payments. Yet the strongest evidence is the pattern across the payments, many senders in and many recipients out within days, and no single-payment rule can see that. This is the main weakness of the current approach, and the clearest next step is account-level rules, such as the number of different counterparties in a short window, followed by network analysis. The pattern resembles an account used to collect and then pass on funds, but I found it by examining labelled data after the fact, not because my rules were designed to target it.

Limitations

Next steps

Next, I plan to add account-level rules, such as counting the different counterparties an account deals with in a short window, to test the rules on data they were not built on, and to rebuild the analysis in SQL with a Power BI dashboard.