TT Lab
博客 开始
学习 学习路径 课程

银行现场的语言

做一个反洗钱规则引擎

在 TT Lab 中继续学习

目标

使用 SQL 和 Python 实现五条可疑交易检测规则,计算每位客户的预警分数,并仅依据事实,为分数最高的客户撰写可疑交易报告草稿。

为什么重要

反洗钱(AML)是银行 IT 最常遇到的监管业务之一。法律规定银行承担两类报告义务:一天内现金往来达到一定金额时,无论其他情况如何都要报告的大额现金交易报告(CTR);以及不论金额多少,只要怀疑存在洗钱行为就要提交的可疑交易报告(STR)。CTR 是机械式判定,而 STR 需要作出判断。规则引擎就是帮助完成这项判断的工具。

规则引擎并不是什么华丽的模型,而是由一组规则组成,例如:“单日现金总额是否超过阈值”“是否多次拆分成略低于阈值的金额”“是否与高风险国家发生交易”“客户身份核验是否已过去很久”“长期不活跃的账户是否突然转移大额资金”。单条规则会产生很多误报,但如果同一个人同时命中多条规则,就会构成信号。因此,需要为每条规则赋予权重并求和。

这项工作还伴随两项约束。报告中只写事实——交易编号、金额、日期和命中的规则。“可疑”不是结论,而是需要由证据支撑的判断。此外,不得告知客户已经提交报告。这是法律规定的保密义务,系统设计必须确保这一事实不会泄露到渠道界面或客户通知中。

本实验的规则(基准日为 2026-08-31)

步骤

  1. 将右侧示例中的生成器保存为 /root/bank/make_aml.py,并运行 python3 make_aml.py。aml.db 中会写入 20 位客户、124 笔交易和 3 个高风险国家。
  2. 将 R1 的结果写入 /root/bank/ctr.csv,表头为 cust_id,biz_date,direction,cash_total。direction 为 DEP 或 WDR。
  3. 将 R2 的结果写入 /root/bank/structuring.csv,表头为 cust_id,txn_count,cash_total,first_date,last_date。
  4. 将 R3 的结果写入 /root/bank/highrisk.csv,表头为 txn_id,cust_id,type,cp_country,amount。
  5. 将 R4 的结果写入 /root/bank/kyc_due.csv,表头为 cust_id,risk_grade,pep,kyc_date,days_since;将 R5 的结果写入 /root/bank/dormant.csv,表头为 cust_id,last_activity,first_txn_date,dormant_days,amount。
  6. 将至少命中一条规则的客户写入 /root/bank/alerts.csv,表头为 cust_id,rules,score;按分数降序排列,分数相同时按 cust_id 升序排列。
  7. 将分数最高客户的报告草稿写入 /root/bank/str_<cust_id>.json。键包括 cust_id、rules(列表)、score、txn_ids(该客户命中规则的交易编号列表)、total_amount(这些交易的金额总和)、narrative(至少 100 字,只写事实)、prepared_by(填写角色而非人名)和 customer_notified(必须为 false)。
  8. 在 /root/bank/aml_report.md 中撰写报告,包含 ## Detection rules and results(检测规则与结果)、## Reporting targets(报告对象)、## False positives and limitations(误报与局限)、## Confidentiality and personal information(保密与个人信息)四个章节。

参考

生成客户与交易数据

将右侧示例中的生成器保存为 /root/bank/make_aml.py,并运行 python3 make_aml.py。aml.db 中会写入 20 位客户、124 笔交易和 3 个高风险国家。

请将实验说明中的生成器保存为 /root/bank/make_aml.py,并使用 python3 运行。如果手工修改,后续步骤中的所有数字都会出现偏差。

R1 大额现金——按单日总额计算

将 R1 的结果写入 /root/bank/ctr.csv,表头为 cust_id,biz_date,direction,cash_total。direction 为 DEP 或 WDR。

只筛选现金类型,按 cust_id、substr(ts,1,10) 和方向执行 GROUP BY,再添加 HAVING SUM(amount) >= 10000000。存款和取款分别汇总,合计正好为 1,000 万韩元的日子也包括在内。看的不是单笔,而是当天的合计。

R2 疑似拆分——统计略低于阈值的交易

将 R2 的结果写入 /root/bank/structuring.csv,表头为 cust_id,txn_count,cash_total,first_date,last_date。

筛选现金类型且 amount 达到 8,000,000、低于 10,000,000 的交易,按 cust_id 分组,只保留 COUNT(*) >= 3 的客户。日期使用 MIN/MAX 提取。

R3 高风险国家——与清单核对

将 R3 的结果写入 /root/bank/highrisk.csv,表头为 txn_id,cust_id,type,cp_country,amount。

按 cp_country 对 txn 和 high_risk_country 执行 INNER JOIN 即可。没有金额条件。汇往 AE 的 2,000 万韩元交易不应命中。

R4·R5——客户核验超期与休眠后活动

将 R4 的结果写入 /root/bank/kyc_due.csv,表头为 cust_id,risk_grade,pep,kyc_date,days_since;将 R5 的结果写入 /root/bank/dormant.csv,表头为 cust_id,last_activity,first_txn_date,dormant_days,amount。

R4 仅使用 customer 即可:(risk_grade='HIGH' OR pep=1) AND julianday('2026-08-31')-julianday(kyc_date) >= 365。R5 先按客户求出 MIN(ts)(CTE),再重新连接该笔交易。休眠时间虽长但交易金额较小的客户不应命中。

合并规则并计算预警分数

将至少命中一条规则的客户写入 /root/bank/alerts.csv,表头为 cust_id,rules,score;按分数降序排列,分数相同时按 cust_id 升序排列。

从第 2~5 步的五个 CSV 中收集 cust_id,建立每位客户的规则集合,再加上权重(R1 1、R2 3、R3 2、R4 1、R5 2)。rules 按编号顺序用 + 连接;排序方式为分数降序、cust_id 升序。

仅依据事实撰写可疑交易报告草稿

将分数最高客户的报告草稿写入 /root/bank/str_<cust_id>.json。键包括 cust_id、rules(列表)、score、txn_ids(该客户命中规则的交易编号列表)、total_amount(这些交易的金额总和)、narrative(至少 100 字,只写事实)、prepared_by(填写角色而非人名)和 customer_notified(必须为 false)。

目标是 alerts.csv 的第一行。按该客户命中的每条规则收集相应交易(R1 为当天的现金交易,R2 为 800 万–1,000 万韩元的现金交易,R3 为高风险国家交易,R5 为 8 月首笔交易),去重后排序。没有命中规则的交易不要放进去。使用 json.dump 写入;customer_notified 在 Python 中为 False。

撰写 AML 检查报告

在 /root/bank/aml_report.md 中撰写报告,包含 ## Detection rules and results(检测规则与结果)、## Reporting targets(报告对象)、## False positives and limitations(误报与局限)、## Confidentiality and personal information(保密与个人信息)四个章节。

把 task 中的四个章节标题(反引号中的英文标题,或括号中意思相同的中文标题;韩文、日文标题也可以)分别写成以 ## 开头的一行。在检测规则章节中以“N 位”的形式写入预警客户数,在报告对象章节中写入最高分客户编号,在保密章节中写明不把报告事实告知客户的句子(no tipping-off——例如:不得告知客户 / the customer is not notified)。不要填写真实姓名。