Development of Transaction Monitoring System for AML
Transaction monitoring (TM) is a continuous process of analyzing transactions to identify suspicious activity. It's not just a blocklist check: TM detects patterns (structuring, rapid movement, round-trip) that individually look normal but in aggregate are money laundering signs.
TM System Architecture
Two approaches: rule-based (deterministic rules) and ML-based (anomaly detection). Production uses both: rules for known patterns, ML for detecting new ones.
Rule Engine
interface MonitoringRule {
id: string;
name: string;
category: "structuring" | "velocity" | "geographic" | "behavioral" | "watchlist";
evaluate: (context: TransactionContext) => Promise<RuleResult>;
alertLevel: AlertLevel;
action: AlertAction;
}
class TransactionMonitoringEngine {
private rules: MonitoringRule[];
async evaluateTransaction(tx: Transaction): Promise<EvaluationResult> {
const context = await this.buildContext(tx);
const results = await Promise.all(
this.rules.map(rule => rule.evaluate(context).catch(err => ({
triggered: false,
error: err.message,
})))
);
const triggered = results.filter(r => r.triggered);
if (triggered.length === 0) return { action: "ALLOW", ruleHits: [] };
const maxLevel = Math.max(...triggered.map(r => r.alertLevel));
const action = this.determineAction(maxLevel, triggered);
if (action !== "ALLOW") {
await this.createAlert(tx, triggered, action);
}
return { action, ruleHits: triggered };
}
private async buildContext(tx: Transaction): Promise<TransactionContext> {
const [history30d, history24h, userProfile, walletRisk] = await Promise.all([
this.db.getTransactionHistory(tx.userId, 30),
this.db.getTransactionHistory(tx.userId, 1),
this.db.getUserProfile(tx.userId),
this.amlProvider.getWalletRisk(tx.address, tx.asset),
]);
return { transaction: tx, history30d, history24h, userProfile, walletRisk };
}
}
Specific Rules
const STRUCTURING_RULE: MonitoringRule = {
id: "TM-001",
name: "Structuring Detection",
category: "structuring",
alertLevel: AlertLevel.HIGH,
action: AlertAction.FREEZE_AND_REVIEW,
async evaluate(ctx: TransactionContext): Promise<RuleResult> {
const REPORTING_THRESHOLD = 10000;
// Find transactions just below threshold within 3 days
const nearThreshold = ctx.history30d.filter(t =>
t.usdAmount >= REPORTING_THRESHOLD * 0.7 &&
t.usdAmount < REPORTING_THRESHOLD &&
Date.now() - t.timestamp < 3 * 86400000
);
const currentNearThreshold = ctx.transaction.usdAmount >= REPORTING_THRESHOLD * 0.7 &&
ctx.transaction.usdAmount < REPORTING_THRESHOLD;
if (currentNearThreshold && nearThreshold.length >= 2) {
return {
triggered: true,
alertLevel: AlertLevel.HIGH,
details: `${nearThreshold.length + 1} transactions just below $${REPORTING_THRESHOLD}`,
evidence: nearThreshold.map(t => t.id),
};
}
return { triggered: false };
},
};
ML-based Anomaly Detection
For advanced systems — Isolation Forest or Autoencoder for outlier detection:
from sklearn.ensemble import IsolationForest
import numpy as np
class TransactionAnomalyDetector:
def __init__(self):
self.model = IsolationForest(contamination=0.01, random_state=42)
def extract_features(self, transaction, user_history):
return [
transaction['usd_amount'],
transaction['usd_amount'] / (user_history['avg_30d'] + 1),
len(user_history['transactions_24h']),
transaction['hour_of_day'],
transaction['day_of_week'],
user_history['unique_counterparties_7d'],
transaction['aml_risk_score'],
]
def predict(self, features) -> float:
# Returns: -1 anomaly, 1 normal
score = self.model.score_samples([features])[0]
return (score + 0.5) * 2 # normalize to [0, 1]
Alert Management and SAR
class AlertManager {
async createAlert(tx: Transaction, rules: RuleResult[], action: AlertAction): Promise<Alert> {
const alert = await this.db.createAlert({
transactionId: tx.id,
userId: tx.userId,
triggeredRules: rules.map(r => r.ruleId),
maxAlertLevel: Math.max(...rules.map(r => r.alertLevel)),
action,
status: AlertStatus.OPEN,
assignedTo: await this.autoAssignCompliance(),
dueDate: this.calculateDueDate(action),
});
if (action === AlertAction.FREEZE_AND_REVIEW) {
await this.freezeUserAccount(tx.userId, alert.id);
}
await this.notifyComplianceTeam(alert);
return alert;
}
}
Stack
| Component | Technology |
|---|---|
| Rule engine | Node.js + TypeScript |
| ML detection | Python + scikit-learn or TensorFlow |
| Streaming | Apache Kafka for real-time |
| Storage | PostgreSQL + TimescaleDB (time-series) |
| Alerting | Custom + PagerDuty |
| Dashboard | React |
Complete TM system with rule engine, ML anomalies and SAR management: 2-3 months.







