Lesson 4 · Python for Banking and Finance
Using Python Functions to Automate Financial Rules in Banking and Finance
This lesson covers how to code financial rules and business logic using Python functions. You will discover why functions are essential in automating,…
- CoursePython for Banking and Finance
- Lesson4 of 24
- Video26 min
- FormatJupyter notebook · 18 code cells
- Data1 dataset
What you'll learn
- Understanding the Core Data for Banking Business Rules
- Beginner Example 1: A Simple Minimum Amount Rule
- Beginner Example 2: Customer-Based Rule with Function
- Beginner Example 3: Channel Restriction with a Function
- Intermediate Example 1: Function for Transaction Daily Limits
- Intermediate Example 2: Function to Enforce Weekly Debit Limit
- Intermediate Example 3: Function for Allowed Channels per Account Type
- Advanced Example 1: Flag Multiple Violations with Composite Function
Datasets used in this lesson
Save these next to the notebook. In Google Colab, upload them with the 📁 icon on the left first.
- violations_report.csv11.8 KB
📓 Full notebook
Download .ipynbFunctions for Financial Rules in Banking#
This lesson covers how to code financial rules and business logic using Python functions.
You will discover why functions are essential in automating, testing, and enforcing policies in real-world banking systems.
By the end, you will know how to build, debug, and test banking rules for fraud checks, transaction limits, fees, and more.
Functions make your code reusable, allow for clean business audits, and help ensure regulatory compliance.
All exercises use realistic banking transaction data so you can practice practical use cases.
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
Understanding the Core Data for Banking Business Rules#
Transaction, customer, and account tables record real-world operations.
Each row in a transaction table is a transfer of value with metadata like time, type, and amount.
Beginners sometimes mix up debit and credit, or forget to handle negative values.
Columns like customer_id link records between datasets.
Financial rules must respect the data structure to avoid errors.
Always check your data for missing or illogical values before building any rules.
# Set seed to 42 for reproducibility
np.random.seed(42)
# Create synthetic banking transactions
n_transactions = 1000
n_customers = 200
df = pd.DataFrame({
'transaction_id': range(1, n_transactions + 1),
'customer_id': np.random.choice([f'CUST_{i:04d}' for i in range(1, n_customers + 1)], n_transactions),
'amount': np.round(np.random.normal(150, 60, n_transactions), 2),
'transaction_type': np.random.choice(['Debit', 'Credit'], n_transactions),
'channel': np.random.choice(['ATM', 'Online', 'Branch', 'POS'], n_transactions),
'date': pd.date_range(start='2024-01-01', periods=n_transactions, freq='h')
})
print(df.shape)
print(df.head(3))
customer_ids = [f'CUST_{i:04d}' for i in range(1, 201)]
customers = pd.DataFrame({
'customer_id': customer_ids,
'segment': ['Retail'] * 150 + ['Business'] * 50,
'region': ['Metro'] * 100 + ['Regional'] * 100
})
print(customers.shape)
print(customers.head(3))
accounts = pd.DataFrame({
'account_id': [f'ACC_{i:05d}' for i in range(1, 201)],
'customer_id': customer_ids,
'account_type': np.random.choice(['Savings', 'Cheque', 'Credit'], size=200),
'open_date': pd.date_range(start='2015-01-01', periods=200, freq='30D')
})
print(accounts.shape)
print(accounts.head(3))
Beginner Example 1: A Simple Minimum Amount Rule#
- Many banks block or flag transactions below a set minimum (e.g. for AML or fee reasons).
- Let us design a reusable function for this, instead of repeating your logic everywhere.
def is_minimum_amount(amount, threshold=50.0):
return amount >= threshold
# Apply the rule to transaction amounts
df['meets_minimum'] = df['amount'].apply(is_minimum_amount)
print(df[['amount', 'meets_minimum']].head(6))
Beginner Example 2: Customer-Based Rule with Function#
- Some banks set special rules for business customers.
- Let us make a function that checks if a customer is in the Business segment.
def is_business_customer(customer_id):
segment = customers.loc[customers['customer_id'] == customer_id, 'segment'].values
return segment[0] == 'Business' if len(segment) > 0 else False
# Test on first few customers
for cid in df['customer_id'].unique()[:6]:
print(cid, ':', is_business_customer(cid))
Beginner Example 3: Channel Restriction with a Function#
- Sometimes a bank wants to block high-value transactions via POS terminals.
- Use a function to flag such cases for review.
def is_high_pos(amount, channel, limit=500):
return channel == 'POS' and amount > limit
# Flag in dataframe
df['blocked_pos'] = df.apply(lambda x: is_high_pos(x['amount'], x['channel']), axis=1)
print(df[['amount', 'channel', 'blocked_pos']].head(8))
Intermediate Example 1: Function for Transaction Daily Limits#
- Most banking systems set daily transaction count or value limits.
- Let us write a function to count transactions for each customer per day.
df['date_only'] = df['date'].dt.date
def num_daily_txns(customer_id, date_only):
return len(df[(df['customer_id'] == customer_id) & (df['date_only'] == date_only)])
count = num_daily_txns(df['customer_id'].iloc[0], df['date_only'].iloc[0])
print('Customer', df['customer_id'].iloc[0], 'had', count, 'transactions on', df['date_only'].iloc[0])
Intermediate Example 2: Function to Enforce Weekly Debit Limit#
- Weekly debit caps are a core anti-fraud control.
- We will create a function for total debits this week for a customer.
def weekly_debit_total(customer_id, this_date):
end_date = pd.to_datetime(this_date)
start_date = end_date - pd.Timedelta(days=6)
mask = (df['customer_id'] == customer_id) & (df['transaction_type'] == 'Debit') & (df['date'] >= start_date) & (df['date'] <= end_date)
return df.loc[mask, 'amount'].sum()
example_cust = df['customer_id'].iloc[10]
example_date = df['date'].iloc[10]
print('Total debit for customer', example_cust, 'from', example_date - pd.Timedelta(days=6), 'to', example_date, ':', weekly_debit_total(example_cust, example_date))
Intermediate Example 3: Function for Allowed Channels per Account Type#
- Not all products are meant for all channels. For example, a Credit account may not allow ATM withdrawals.
- Let us design a function to enforce account type/channel rules.
def allowed_channel(customer_id, channel):
acct_type = accounts.loc[accounts['customer_id'] == customer_id, 'account_type'].values
if len(acct_type) == 0:
return False
if acct_type[0] == 'Credit' and channel == 'ATM':
return False
return True
# Try on a few records
for i in range(4):
row = df.iloc[i]
print(row['customer_id'], row['channel'], allowed_channel(row['customer_id'], row['channel']))
Advanced Example 1: Flag Multiple Violations with Composite Function#
- In real banking, you need to check many rules at once and report which ones failed.
- We will build a function returning a dictionary of violations for a single transaction.
def check_transaction_rules(row):
rules = {}
rules['min_amt'] = is_minimum_amount(row['amount'])
rules['high_pos'] = not is_high_pos(row['amount'], row['channel'])
rules['allowed_channel'] = allowed_channel(row['customer_id'], row['channel'])
return rules
# See violations for a sample row
sample_row = df.iloc[0]
print(check_transaction_rules(sample_row))
Advanced Example 2: Vectorized Application and Scoring for All Transactions#
- Real systems need to apply rules to millions of records quickly.
- We will vectorize and summarize violations for the entire DataFrame.
rule_results = df.apply(check_transaction_rules, axis=1, result_type='expand')
df = pd.concat([df, rule_results], axis=1)
print(df[['amount', 'channel', 'min_amt', 'high_pos', 'allowed_channel']].head(8))
Advanced Example 3: Exporting Violations for Audit#
- Banks require robust audit trails of rule triggers.
- We will export all violations to a CSV file for review.
violations = df[~(df['min_amt'] & df['high_pos'] & df['allowed_channel'])]
violations_file = 'violations_report.csv'
violations.to_csv(violations_file, index=False)
print(f'{violations.shape[0]} violations written to {violations_file}')
Error Handling Example: Defensive Functions for Data Quality Issues#
- What if customer_id does not exist or data contains missing values?
- Let us improve our previous function to handle these cases and log warnings.
def strict_is_business_customer(customer_id):
if customer_id is None or customer_id not in set(customers['customer_id']):
print(f'Warning: Unknown or missing customer_id: {customer_id}')
return False
segment = customers.loc[customers['customer_id'] == customer_id, 'segment'].values
return segment[0] == 'Business'
# Try with an unknown ID
print(strict_is_business_customer('CUST_0999'))
Error Handling Example: Handling Non-Numeric Amounts and Missing Data#
- Bad data can crash rule engines. Ensure your amount fields are always numeric.
- We will build a safe function to handle inappropriate types or NaN values.
def is_valid_amount(amount):
try:
a = float(amount)
return not np.isnan(a)
except (ValueError, TypeError):
return False
# Examples
print(is_valid_amount(10))
print(is_valid_amount('foo'))
print(is_valid_amount(np.nan))
Best Practices: Naming, Documentation, and Testing#
- Use clear names describing what your function does.
- Add docstrings and type hints.
- Test on simple and edge cases, every time you add or change a rule.
def is_minimum_amount(amount: float, threshold: float = 50.0) -> bool:
"""Return True if amount greater than or equal to threshold."""
return is_valid_amount(amount) and amount >= threshold
# Simple unit test
print(is_minimum_amount(50), 'Expected: True')
print(is_minimum_amount(20), 'Expected: False')
print(is_minimum_amount('bad'), 'Expected: False')
Best Practices: Logging and Traceability in Rule Functions#
- Banks must trace why a transaction was allowed or blocked.
- Add simple print statements or use logging for real-world code.
def is_high_pos(amount, channel, limit=500):
if channel == 'POS' and amount > limit:
print(f'Flag: POS transaction above limit (${amount})')
return True
return False
# See a flagged transaction
print(is_high_pos(700, 'POS'))
print(is_high_pos(200, 'ATM'))
End-to-End Challenge: Detect Debit Rule Breaches and Warn Customers#
- Let us combine everything: for each customer, flag all debit transactions exceeding a $500 daily limit.
- Output a warning per customer who violates this rule at least once.
# Create a table with total daily debits per customer
daily_debits = df[df['transaction_type']=='Debit'].groupby(['customer_id', 'date_only'])['amount'].sum().reset_index()
daily_debits['exceeds_limit'] = daily_debits['amount'] > 500
warned_customers = set(daily_debits.loc[daily_debits['exceeds_limit'], 'customer_id'])
for cid in warned_customers:
print(f'Warning: Customer {cid} has exceeded the daily debit limit.')
Congratulations! You Can Now Code Practical Financial Rules#
- Practice with real banking rules and see how functions supercharge your compliance and automation.
- For more lessons and practical walkthroughs, look up Python rule engines on YouTube.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



