Logo
Afbeelding

Analyse op bankboekingen van/naar risicolanden

Geschreven door The Audit Analytics | 6 minuten

Bij financiële audits is het monitoren van geldstromen naar risicovolle landen een belangrijk onderdeel. Dit kan namelijk duiden op fraude, witwassen of terrorismefinanciering. De FATF-lijst (Financial Action Task Force) bevat landen met een verhoogd risico. In dit artikel zetten we met behulp van die lijst, een export van de bank (CAMT) en python een analyse op!

Betalingen risicovolle betaalstromingen

Benodigde Python-packages en bestanden

Zoals gezegd gebruiken we bij deze analyse CAMT-bestanden. Dit zijn XML-bestanden die door banken worden gebruikt om transacties te rapporteren. De meeste Europese banken bieden vanaf 2025 zo'n export.

Voor een oefenbestand om deze analyse zelf uit te voeren, kun je dit CAMT-bestand met dummy data downloaden. Meer informatie over het CAMT.053-formaat en de specificaties ervan kun je vinden op deze externe pagina.

Daarnaast gebruiken we python en maken we gebruik van de volgende Python-packages:

  • pandas voor datamanipulatie
  • xml.etree.ElementTree voor het parseren van XML-bestanden. Deze zit standaard in python.

Als je dat nog niet eerder gedaan hebt, kun je pandas installeren met het volgende commando:

pip install pandas

Stap 1: Definieer een lijst van risicovolle landen

Om betalingen naar risicovolle landen te detecteren, definiëren we allereerst een lijst op basis van de FATF-lijst.

# Lijst van risicovolle landen (voorbeeld, gebaseerd op de FATF-lijst)
risicovolle_landcodes = {"IR", "KP", "SY", "SD", "PK", "YE", "MM", "CU", "AF", "ZM"}

Stap 2: Lees en verwerk het CAMT.053 XML-bestand

Het CAMT.053-bestand bevat alle transacties van een bankrekening. We extraheren de relevante velden zoals datum, bedrag, debiteur, crediteur en IBAN.

# inladen van ElemenTree wat we nodig hebben om de XML in te lezen. 
import xml.etree.ElementTree as ET
import pandas as pd

# Het ontleden van de CAMT 053 met een leesbaar df als resultaat
def parse_camt053(file_path):
    tree = ET.parse(file_path)
    root = tree.getroot()
    ns = {'ns': 'urn:iso:std:iso:20022:tech:xsd:camt.053.001.02'}

    transactions = []
    
    # Loop over alle entries heen
    for entry in root.findall('.//ns:Ntry', ns):
        try:
            # Als er geen bedrag gevonden wordt voor de entry, wordt deze op 0,0 gezet. 
            amount = float(entry.find('ns:Amt', ns).text) if entry.find('ns:Amt', ns) is not None else 0.0
        except ValueError:
            amount = 0.0  # Fallback bij conversiefouten
        
        # Hieronder zoekt hij de betreffende velden en koppelt hij ze aan de variabele voor ons overzicht. 
        currency = entry.find('ns:Amt', ns).attrib.get('Ccy', 'N/A') if entry.find('ns:Amt', ns) is not None else 'N/A'
        date = entry.find('.//ns:BookgDt/ns:Dt', ns).text if entry.find('.//ns:BookgDt/ns:Dt', ns) is not None else '-'
        debtor = entry.find('.//ns:Dbtr/ns:Nm', ns).text if entry.find('.//ns:Dbtr/ns:Nm', ns) is not None else '-'
        debtor_iban = entry.find('.//ns:DbtrAcct/ns:Id/ns:IBAN', ns).text if entry.find('.//ns:DbtrAcct/ns:Id/ns:IBAN', ns) is not None else '-'
        creditor = entry.find('.//ns:Cdtr/ns:Nm', ns).text if entry.find('.//ns:Cdtr/ns:Nm', ns) is not None else '-'
        creditor_iban_element = entry.find('.//ns:CdtrAcct/ns:Id/ns:IBAN', ns)
        creditor_iban = creditor_iban_element.text if creditor_iban_element is not None else '-'

        # Koppelt de entry als regel aan ons DataFrame
        transactions.append({
            "Datum": date,
            "Debiteur": debtor,
            "Debiteur IBAN": debtor_iban,
            "Crediteur": creditor,
            "Crediteur IBAN": creditor_iban,
            "Bedrag": amount,
            "Munteenheid": currency
        })
    
    return pd.DataFrame(transactions)

# Laad het CAMT.053-bestand
file_path = "camt053_dummy.xml"
df = parse_camt053(file_path)

# Toon de bovenste rijen
print(df.head())

Stap 3: Extractie van landcodes uit IBAN

De eerste twee letters van een IBAN vertegenwoordigen de landcode van de rekeninghouder. Dit helpt ons bepalen of een betaling naar een risicoland is gegaan.

# Functie om het land te extraheren uit IBAN
def get_country_from_iban(iban):
    return iban[:2]  # De eerste twee tekens van IBAN zijn de landcode

# Voeg landcodes toe aan dataframe
df["Crediteur Landcode"] = df["Crediteur IBAN"].apply(get_country_from_iban)
df["Debiteur Landcode"] = df["Debiteur IBAN"].apply(get_country_from_iban)

Stap 4: Filter betalingen naar risicovolle landen

Nu filteren we de transacties naar landen uit de FATF-lijst.

# Filter betalingen naar risicovolle landen
df_risicovol = df[df["Crediteur Landcode"].isin(risicovolle_landcodes) | df["Debiteur Landcode"].isin(risicovolle_landcodes)]

Stap 5: Analyseer de frequentie en bedragen

Een overzicht van het aantal transacties en totale bedragen naar risicovolle landen.

# Groepeer op land en bereken totalen
df_analyse = df_risicovol.groupby(["Crediteur Landcode", "Debiteur Landcode"]).agg(
    Aantal_Transacties=("Bedrag", "count"),
    Totaal_Bedrag=("Bedrag", "sum")
).reset_index()

Conclusie

Met dit script kun je dus achterhalen op basis van de betalingen of er betaalstromen hebben plaatsgevonden met risicovolle landen. Lukt het je ook met niet-dummy data?