Guide · Fintech

Building an AML Transaction Monitoring Script in Python & SQL

A practical walkthrough of how I'd approach a basic AML (anti-money-laundering) transaction monitoring script — the same shape as the Reddit competitor and remittance monitor I built at Xuno, but pointed at payments data.

What transaction monitoring actually does

At its core, transaction monitoring is a job that reads new transactions on a schedule, runs each one through a set of rules, and raises an alert when something looks off. The complicated products you see on vendor websites are mostly a nicer UI on top of that loop. If you can write SQL and a Python script, you can build the loop.

The three pieces you need: a source of transactions (usually a database), a set of rules (thresholds, patterns, watchlists), and somewhere to send alerts (Slack, email, a case-management table).

The rules you start with

Real AML programs cover dozens of typologies. For a first script, four rules cover most of what regulators expect to see and are easy to reason about:

  • Large single transaction — anything over a fixed threshold (e.g. $10,000).
  • Structuring — several transactions under the threshold that sum above it within a short window.
  • High-risk geography — sends to or from countries on a sanctions or high-risk list.
  • Velocity — a user's transaction count or volume spiking versus their own recent baseline.

SQL for the heavy lifting

Push as much work into SQL as you can — it's faster and easier to audit than equivalent pandas. Structuring is a good example: a window function does the rolling sum per user cleanly.

-- Flag structuring: 24h rolling volume per user
SELECT
  user_id,
  transaction_id,
  amount,
  created_at,
  SUM(amount) OVER (
    PARTITION BY user_id
    ORDER BY created_at
    RANGE BETWEEN INTERVAL '24 hours' PRECEDING AND CURRENT ROW
  ) AS rolling_24h_volume
FROM transactions
WHERE created_at >= NOW() - INTERVAL '48 hours'
  AND amount < 10000
QUALIFY rolling_24h_volume >= 10000;

Python for the loop and the alerts

Python's job is to run the SQL, apply anything awkward to express in SQL (like fuzzy name matching against a watchlist), and push alerts out.

import os, requests, pandas as pd
from sqlalchemy import create_engine

engine = create_engine(os.environ["DATABASE_URL"])
SLACK = os.environ["SLACK_WEBHOOK_URL"]

def run_rule(name, sql):
    df = pd.read_sql(sql, engine)
    for _, row in df.iterrows():
        alert(name, row.to_dict())

def alert(rule, payload):
    requests.post(SLACK, json={
        "text": f":rotating_light: *{rule}* triggered\n```{payload}```"
    })

if __name__ == "__main__":
    run_rule("large_transaction",
             "SELECT * FROM transactions WHERE amount >= 10000 "
             "AND created_at >= NOW() - INTERVAL '15 minutes'")
    run_rule("structuring", open("sql/structuring.sql").read())

Schedule this with cron, a workflow tool, or (my preference) a small scheduler service that also records every run — you'll want an audit trail the first time compliance asks whether a rule fired last Tuesday.

What to log, and why it matters

For each alert, write a row to an alerts table with the rule name, the transaction and user IDs, a snapshot of the fields the rule evaluated, and the timestamp. This gives you two things for free: a queue analysts can work through, and a dataset you can measure precision on later to tune thresholds down.

Where this connects to my work

The Reddit competitor and remittance monitor on my portfolio home has the same skeleton: pull new records on a schedule, apply keyword and semantic rules, alert Slack. Swap Reddit posts for payments and the keyword rules for AML typologies and you've built the first useful version of a transaction monitoring system.