We use Python, and while some basic knowledge is required, we keep it understandable. For this analysis, you need:
- A CAMT.053 file containing transaction data.
- The Python packages pandas and xml.etree.ElementTree
If you haven’t installed Pandas yet, you can do so with:
pip install pandas
For a sample file to try out this analysis yourself, you can download this test CAMT file
Step 1: Reading the CAMT.053 File
A CAMT.053 file is an XML format containing bank transactions. We use pandas and xml.etree.ElementTree to process the file.
import xml.etree.ElementTree as ET
import pandas as pd
# Function to parse a CAMT.053 XML file
def parse_camt053(file_path):
tree = ET.parse(file_path) # Parse the XML file
root = tree.getroot() # Get the root element
ns = {'ns': 'urn:iso:std:iso:20022:tech:xsd:camt.053.001.02'} # Define XML namespace
transactions = []
for entry in root.findall('.//ns:Ntry', ns):
try:
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 for conversion errors
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 '-'
# Store extracted transaction details in a list
transactions.append({
"Date": date,
"Debtor": debtor,
"Debtor IBAN": debtor_iban,
"Creditor": creditor,
"Creditor IBAN": creditor_iban,
"Amount": amount,
"Currency": currency
})
return pd.DataFrame(transactions) # Convert to a Pandas DataFrame
# Load and parse the CAMT.053 file
file_path = "camt053_dummy.xml"
df = parse_camt053(file_path)
print(df.head()) # Display the first few rows
Step 2: Grouping Payments by Supplier and Date
We want to determine whether multiple payments were made to the same supplier on the same day.
# You can replace Beneficiary with IBAN
df_grouped = df.groupby(['Date', 'Creditor']).agg(
Transaction_Count=('Amount', 'count'), # Count the number of rows per group
Total_Amount=('Amount', 'sum') # Sum the amounts
).reset_index()
print(df_grouped)
This shows how much was transferred per day per supplier.
Step 3: Detecting Split Payments
Now, we look for patterns where multiple payments add up to a large invoice or where amounts remain just below an approval threshold.
Let's assume the approval threshold is €1,000.
approval_limit = 1000
# Check if multiple payments approach the threshold
df_splits = df_grouped[
(df_grouped['Total_Amount'] >= (approval_limit * 0.9)) &
(df_grouped['Transaction_Count'] > 1)
]
print(df_splits)
This filters transactions where the total amount per supplier and date is at least 90% of the approval limit.
Step 4: Marking Suspicious Splits
Now, we specifically check if individual payments within these group amounts remain just below the threshold.
suspicious_transactions = df[(df['Amount'] >= (approval_limit * 0.9)) & (df['Amount'] < approval_limit)]
print(suspicious_transactions)
This provides a list of suspicious transactions that may have been intentionally split.
Conclusion
With these steps, we can easily detect suspicious payments. Want to experiment further? Try different threshold values, analyze by IBAN, or visualize results with matplotlib.