CAMT-bestanden zijn XML-rapportages die banken gebruiken om financiële mutaties en saldi door te geven. Denk aan je rekeningafschrift, maar dan in een gestandaardiseerd digitaal jasje. Het idee daarvan is dat software die bestanden makkelijker kan uitlezen en automatisch verwerken.
Verschillende versies
Er zijn verschillende “smaken” CAMT-bestanden die elk een ander doel dienen:
-
camt.052: Deze gaat vooral over intraday-transacties (tussenstanden gedurende de dag).
-
camt.053: Dit is je dagelijkse of periodieke rekeningafschrift.
-
camt.054: Geeft meldingen van individuele bij- en afschrijvingen (krediet- en debetnotificaties).
Ze lijken qua structuur behoorlijk op elkaar, maar de inhoud en timing verschillen. Over het algemeen is CAMT.053 (het “End-of-Day Statement”) het meest geschikt voor auditdoeleinden, omdat dit type een volledig overzicht van alle transacties en het eindsaldo voor een bepaalde periode (meestal per dag) biedt.
Hoewel CAMT.052 (intraday) en CAMT.054 (notificaties) ook nuttig kunnen zijn bij bijvoorbeeld het identificeren of traceren van transacties gedurende de dag, zijn deze berichten doorgaans minder geschikt als formele bron voor audits. Ze bieden immers geen ‘afgesloten’ periode‐overzicht, maar eerder (tussentijdse) statusrapporten of notificaties.
Welke banken gebruiken CAMT?
Veel Europese banken ondersteunen intussen CAMT-standaarden. In Nederland kun je denken aan banken als ING, Rabobank en ABN AMRO die het aanbieden als opvolger van de oude MT940-standaard. Ook in andere Europese landen zie je dat grootbanken steeds vaker CAMT-rapportages uitdelen. Het idee is een uniforme standaard, zodat bedrijven en accountants niet hoeven te puzzelen met telkens verschillende bestandsformaten.
Nadelen van batchdetails
Toch is niet alles rozengeur en maneschijn. Soms worden transacties in een batch verzameld. Een voorbeeld: als je een paar onlinebestellingen dezelfde dag doet via één partij, dan kunnen die betalingen gebundeld in je CAMT-bestand staan. Je ziet dan één totaalbedrag in plaats van meerdere losse transacties. Dat is dus wat minder handig als je precies wilt weten welke betalingen er allemaal in dat “gecombineerde” bedrag zitten.
Vaak als je dit soort batch-betalingen doet in de bank, moet hiervoor een SEPA-bestand importeren. Het is aan te raden om deze niet verwijderen; je kunt deze vaak namelijk nog wel achteraf koppelen met een CAMT-bestand, zodat je de details toch kunt inzien.
CAMT naar CSV of Excel
Wanneer je een CAMT-bestand opent, zul je daar niet direct veel van kunnen maken. Vandaar dat veel mensen zoeken naar een geschikte manier om dit om te zetten. Hieronder vind je een simpel Python-scripje. Het is niet supersonisch uitgebreid, maar het laat het principe zien. Je hebt dan nog wel wat XML-kennis nodig, vooral omdat CAMT-bestanden vaak namespaces hebben (zoals urn:iso:std:iso:20022:tech:xsd:camt.053.001.02
). Je moet dus even checken welke namespace in jouw CAMT-file staat, en die hierop aanpassen.
import pandas as pd
import xml.etree.ElementTree as ET
import os
def parse_camt_to_dataframe(camt_file):
#Parse een CAMT 053-bestand en retourneer een pandas DataFrame
#met de relevante kolommen.
#:param camt_file: Pad naar het CAMT XML-bestand.
#:return: pandas.DataFrame met alle uitgelezen transacties.
if not os.path.exists(camt_file):
raise FileNotFoundError(f"Het CAMT-bestand {camt_file} werd niet gevonden.")
# Parse het XML-bestand
try:
tree = ET.parse(camt_file)
root = tree.getroot()
except ET.ParseError as e:
raise ValueError(f"Fout bij het inlezen van XML: {e}")
# Dit is een voorbeeld-namespace (pas aan als jouw CAMT dat vereist).
ns = '{urn:iso:std:iso:20022:tech:xsd:camt.053.001.02}'
# Statement-level informatie (optioneel)
# IBAN van de eigen rekening
statement_iban_elem = root.find(f'.//{ns}Rpt/{ns}Acct/{ns}Id/{ns}IBAN')
if statement_iban_elem is None:
# In sommige CAMT-bestanden is het element iets anders gestructureerd
statement_iban = ''
else:
statement_iban = statement_iban_elem.text
# Zoek alle entries
entries = root.findall(f'.//{ns}Ntry')
# We gaan de data opslaan in een lijst van dicts om daarna naar een DataFrame te converteren
transaction_records = []
for entry in entries:
# Boekingsdatum
booking_date_elem = entry.find(f'.//{ns}BookgDt/{ns}Dt')
if booking_date_elem is None:
booking_date_elem = entry.find(f'.//{ns}BookgDt/{ns}DtTm')
booking_date = booking_date_elem.text if booking_date_elem is not None else ''
# Valutadatum
value_date_elem = entry.find(f'.//{ns}ValDt/{ns}Dt')
if value_date_elem is None:
value_date_elem = entry.find(f'.//{ns}ValDt/{ns}DtTm')
value_date = value_date_elem.text if value_date_elem is not None else ''
# Bedrag & valuta
amount_elem = entry.find(f'.//{ns}Amt')
if amount_elem is not None:
amount = amount_elem.text
currency = amount_elem.attrib.get('Ccy', '')
else:
amount = ''
currency = ''
# Credit of debit
cdt_dbt_elem = entry.find(f'.//{ns}CdtDbtInd')
credit_debit = cdt_dbt_elem.text if cdt_dbt_elem is not None else ''
# Bank transactiecode (BkTxCd)
bank_tx_code_elem = entry.find(f'.//{ns}BkTxCd/{ns}Domn/{ns}Fmly/{ns}SubFmlyCd')
bank_tx_code = bank_tx_code_elem.text if bank_tx_code_elem is not None else ''
# Meerdere detailregels in TxDtls?
transaction_details = entry.findall(f'.//{ns}TxDtls')
# Als er geen TxDtls is, schrijven we de info van de Entry zelf weg (1 regel)
if not transaction_details:
record = {
'Statement IBAN': statement_iban,
'Boekingsdatum': booking_date,
'Valutadatum': value_date,
'Bedrag': amount,
'Valuta': currency,
'Credit/Debit': credit_debit,
'Tegenrekening-IBAN': '',
'Naam-tegenpartij': '',
'BIC-tegenpartij': '',
'Bank Transactiecode': bank_tx_code,
'End2End ID': '',
'Omschrijving': ''
}
transaction_records.append(record)
else:
# Anders loop door elke TxDtls
for tx in transaction_details:
# Tegenrekening-IBAN
related_parties_iban = tx.find(f'.//{ns}RltdPties/{ns}CdtrAcct/{ns}Id/{ns}IBAN')
if related_parties_iban is None:
related_parties_iban = tx.find(f'.//{ns}RltdPties/{ns}DbtrAcct/{ns}Id/{ns}IBAN')
counterparty_iban = related_parties_iban.text if related_parties_iban is not None else ''
# Naam-tegenpartij
name_elem = tx.find(f'.//{ns}RltdPties/{ns}Cdtr/{ns}Nm')
if name_elem is None:
name_elem = tx.find(f'.//{ns}RltdPties/{ns}Dbtr/{ns}Nm')
counterparty_name = name_elem.text if name_elem is not None else ''
# BIC-tegenpartij
bic_elem = tx.find(f'.//{ns}RltdAgts/{ns}CdtrAgt/{ns}FinInstnId/{ns}BIC')
if bic_elem is None:
bic_elem = tx.find(f'.//{ns}RltdAgts/{ns}DbtrAgt/{ns}FinInstnId/{ns}BIC')
counterparty_bic = bic_elem.text if bic_elem is not None else ''
# End2End ID
end2end_elem = tx.find(f'.//{ns}Refs/{ns}EndToEndId')
end2end_id = end2end_elem.text if end2end_elem is not None else ''
# Omschrijving
remittance_elems = tx.findall(f'.//{ns}RmtInf/{ns}Ustrd')
if remittance_elems:
description = ' | '.join([remit.text for remit in remittance_elems if remit.text])
else:
description = ''
record = {
'Statement IBAN': statement_iban,
'Boekingsdatum': booking_date,
'Valutadatum': value_date,
'Bedrag': amount,
'Valuta': currency,
'Credit/Debit': credit_debit,
'Tegenrekening-IBAN': counterparty_iban,
'Naam-tegenpartij': counterparty_name,
'BIC-tegenpartij': counterparty_bic,
'Bank Transactiecode': bank_tx_code,
'End2End ID': end2end_id,
'Omschrijving': description
}
transaction_records.append(record)
# Zet de lijst van dicts om in een DataFrame
df = pd.DataFrame(transaction_records)
return df
def convert_camt_to_csv_and_excel(camt_file, csv_file='output.csv', excel_file='output.xlsx'):
# Maakt een DataFrame van het CAMT-bestand en schrijft zowel
# een CSV als een Excel-bestand weg.
df = parse_camt_to_dataframe(camt_file)
# Exporteer naar CSV
df.to_csv(csv_file, index=False, encoding='utf-8')
print(f"CSV-bestand opgeslagen als: {csv_file}")
# Exporteer naar Excel
df.to_excel(excel_file, index=False)
print(f"Excel-bestand opgeslagen als: {excel_file}")
# Voorbeeld van hoe je het kunt gebruiken:
# if __name__ == "__main__":
# convert_camt_to_csv_and_excel('dummy_camt053.xml', 'dummy_output.csv', 'dummy_output.xlsx')