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 datamanipulatiexml.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?