土法炼钢兴趣小组的算法知识备份

【金融科技工程】反洗钱与 KYC:客户尽调、交易监测、STR/SAR、FATF

文章导航

分类入口
architecturefintech
标签入口
#aml#kyc#sanctions#fatf#ofac#chainalysis#travel-rule#compliance#edd#tms

目录

反欺诈(Anti-Fraud)回答的是”这笔交易是不是坏人发的”;反洗钱(Anti-Money Laundering,AML)回答的是更结构性、更合规化的问题:这个客户是不是坏人?这条资金链路是不是把”黑钱”洗白?这个对手方是不是我国或国际社会正在制裁的实体? 从工程角度看,前者关心毫秒级决策、F1 分数与体验;后者关心监管可解释性、文档留痕、报表准时、历史可回溯七到十年。做错了反欺诈,公司亏钱;做错了 AML,公司被罚款、管理层被追责、牌照被吊销。2012 年汇丰银行(HSBC)因对墨西哥毒贩资金清洗不力被美国司法部罚没 19.2 亿美元,2020 年 FinCEN 文件(FinCEN Files)揭露全球多家大型银行累计处理可疑交易超过 2 万亿美元——这两个事件重塑了每一家金融机构对 AML 的投入强度。

本篇读者画像:支付公司风控或合规工程师、数字银行 KYC 系统研发、加密资产交易所合规团队、需要理解”监管科技”(RegTech)落地方式的架构师。我们默认读者已阅读上一篇《反欺诈》,理解设备指纹、图谱与行为序列;本文聚焦合规框架下的系统设计:监管怎么说、客户怎么识别、名单怎么筛、交易怎么监测、可疑怎么上报、链上钱怎么追。最后一节讨论 GDPR 与 AML 留存期的张力——这是工程上最微妙的一处。

一、监管框架:工程师的”ABI”

工程师习惯把法律当”外部 API”——不必读全文,但必须知道每个端点返回什么、要求什么字段。AML 的监管栈有三层:国际标准(FATF)、主权国家法律、行业指引。

1.1 FATF 四十项建议

金融行动特别工作组(Financial Action Task Force,FATF)于 1989 年由 G7 发起,2012 年发布当前版本《四十项建议》(The FATF Recommendations),并通过相互评估(Mutual Evaluation)与”灰名单/黑名单”机制对各国施加压力。与工程相关的核心条款:

1.2 美国:BSA / USA PATRIOT Act / OFAC

SAR 与 CTR 上报给 FinCEN(Financial Crimes Enforcement Network),SAR 规定在发现后 30 天内提交,异常严重可 60 天。

1.3 欧盟:AMLD 5/6 与 MiCA

1.4 中国监管框架

1.5 香港、新加坡

工程上我们并不逐条实现法条,而是把所有来源汇集成一张”合规矩阵”:管辖地 × 客户类型 × 交易产品 × 风险等级 → 规则集合。下面的 KYC 生命周期就是矩阵的”横切”。

二、KYC 生命周期

flowchart LR
  A[获客/开户申请] --> B[CIP 身份识别]
  B --> C{PEP/制裁命中?}
  C -- 是 --> D[EDD 加强尽调]
  C -- 否 --> E[CDD 基础尽调]
  D --> F[风险评级 R1-R4]
  E --> F
  F --> G[关系建立]
  G --> H[持续监测]
  H --> I{触发事件?}
  I -- 信息变更/交易异常/名单更新 --> J[触发式复核]
  I -- 周期到期 --> K[定期复核]
  J --> F
  K --> F
  G --> L[关系终止]
  L --> M[档案封存 5-10 年]

2.1 CIP:客户识别

CIP 是开户的”门槛”。自然人最小字段集:姓名、出生日期、地址、身份证号(或护照号 + 签发国)。企业客户还需工商注册信息、法定代表人、受益所有人(UBO,通常持股 25% 以上)。

CREATE TABLE customer (
  customer_id      BIGINT PRIMARY KEY,
  customer_type    VARCHAR(16) NOT NULL,           -- INDIVIDUAL / CORPORATE
  legal_name       VARCHAR(200) NOT NULL,
  legal_name_en    VARCHAR(200),                   -- 英文/拼音,用于制裁筛查
  date_of_birth    DATE,
  nationality      CHAR(2),                        -- ISO 3166-1 alpha-2
  residence_country CHAR(2),
  id_type          VARCHAR(32),                    -- NATIONAL_ID / PASSPORT / BR_CODE
  id_number_hash   CHAR(64) NOT NULL,              -- SHA-256(id || salt)
  id_number_enc    VARBINARY(256),                 -- AES-GCM 密文
  created_at       TIMESTAMP NOT NULL,
  onboarded_by     VARCHAR(64) NOT NULL,           -- 业务线 / 渠道
  risk_rating      CHAR(2),                        -- R1..R4
  review_due_at    DATE,                           -- 下次定期复核
  status           VARCHAR(16) NOT NULL            -- ACTIVE / SUSPENDED / CLOSED
) PARTITION BY RANGE (YEAR(created_at));

CREATE INDEX idx_customer_name_en ON customer(legal_name_en);
CREATE INDEX idx_customer_review ON customer(review_due_at, status);

CREATE TABLE customer_document (
  doc_id           BIGINT PRIMARY KEY,
  customer_id      BIGINT NOT NULL,
  doc_type         VARCHAR(32) NOT NULL,           -- ID_FRONT / ID_BACK / PASSPORT / SELFIE / LIVENESS_VIDEO / UTILITY_BILL
  storage_uri      VARCHAR(512) NOT NULL,          -- S3 / OSS 加密桶
  ocr_json         JSON,
  verify_status    VARCHAR(16) NOT NULL,           -- PASS / FAIL / MANUAL
  hash_sha256      CHAR(64) NOT NULL,              -- 反篡改
  uploaded_at      TIMESTAMP NOT NULL
);

CREATE TABLE beneficial_owner (
  customer_id      BIGINT NOT NULL,
  ubo_name         VARCHAR(200) NOT NULL,
  ubo_id_hash      CHAR(64) NOT NULL,
  ownership_pct    DECIMAL(5,2) NOT NULL,
  is_pep           BOOLEAN NOT NULL DEFAULT FALSE,
  PRIMARY KEY (customer_id, ubo_id_hash)
);

几个工程细节:

2.2 CDD 与 EDD

CDD 的输出是客户风险评级(Customer Risk Rating,CRR)。典型权重模型(信息性,非监管规定):

维度 取值 得分
国籍/地区 中国大陆 / 港澳台 / FATF 黑名单国 10 / 20 / 80
行业 零售 / 贵金属 / 赌博 / 军工 10 / 50 / 70 / 80
产品 活期存款 / 跨境汇款 / 私行理财 10 / 40 / 60
渠道 面签 / 远程 / 第三方代理 10 / 30 / 50
PEP 否 / 国内 / 外国 0 / 30 / 60
UBO 透明度 清晰 / 多层 / 离岸壳公司 10 / 40 / 80

加权后映射到 R1(低)至 R4(高),R3/R4 进入 EDD:要求资金来源证明(Source of Funds)、财富来源证明(Source of Wealth)、高管审批。EDD 必须由独立于业务线的合规审批,形成”二线”。

2.3 持续监测与定期复核

三、身份验证工程:eKYC 栈

远程开户(eKYC)是近年快速演化的领域。一条典型 eKYC 链路:

sequenceDiagram
  participant U as 用户
  participant APP as 移动端
  participant KYC as KYC 服务
  participant OCR as OCR/NFC
  participant FR as 人脸/活体
  participant VC as 公安/运营商/银行联核
  participant S as 制裁/PEP
  U->>APP: 开户
  APP->>OCR: 身份证正反面
  OCR-->>APP: 结构化字段
  APP->>OCR: NFC 读取 eMRTD (护照/港澳台居住证)
  OCR-->>APP: DG1/DG2 + BAC/PACE 验签
  APP->>FR: 活体自拍 (动作/静默)
  FR-->>APP: liveness_score
  APP->>KYC: 提交 (字段 + 照片 + video_hash)
  KYC->>VC: 二/三/四要素核验
  VC-->>KYC: 比对结果
  KYC->>FR: 证件照 vs 自拍 (face_match)
  FR-->>KYC: similarity
  KYC->>S: 姓名 + 证件号 + 国籍
  S-->>KYC: 名单命中结果
  KYC-->>APP: 通过/转人工

3.1 OCR 与 NFC

中国二代身份证可通过 NFC 读取芯片信息,但 芯片解密必须由持牌公安数据接入方(例如经”互联网+可信身份认证平台”CTID)完成,普通开发者不能自行解芯片。常见方案:现场拍照 OCR + 后端调用”身份证二要素/三要素”核验 API 确认真伪;或接入 CTID 获得不落地号码的网证。

国际证件(护照、港澳台居住证)遵循 ICAO 9303 eMRTD 规范,芯片包含 DG1(MRZ)、DG2(人脸)、DG3(指纹,一般银行无权限)、SOD(签名)。工程上用 iOS Core NFC / Android NfcA 读取,BAC/PACE 派生会话密钥后读取数据组,并用签发国 CSCA 根证书验证 SOD 链。

3.2 活体检测与人脸比对

活体(Liveness)分两类:

人脸比对给出 similarity ∈ [0, 1],阈值与业务风险挂钩:开户 0.82、登录 0.75、刷脸支付 0.92(示例阈值,实际以算法供应商校准为准)。每次比对必须留存 原图 + 比对图 + 分数 + 模型版本,否则监管审计无法复现。

3.3 二/三/四要素

中国常见的身份联核:

“四要素”常被用作支付绑卡的最低标准。核验接口响应通常返回 “一致/不一致/无此记录”,不返回明文,避免”用他人身份证号+随机手机号反查”。

3.4 电子签名、eID、网证

电子签名须符合《电子签名法》与 ETSI EN 319 411(欧盟)、eIDAS 规则。金融开户合同需要可靠电子签名(可靠签名需要具备签名人专有控制、可检测签名后变动),常见方案是接入合规 CA 的远程签章服务(如中国金融认证中心 CFCA),签名数据写入 PDF LTV(Long-Term Validation)结构,证书吊销列表(CRL)或 OCSP 证据一并存档。

3.5 eKYC 风控反制:深伪(Deepfake)对抗

2024 年以来,香港、日本、新加坡多起”AI 换脸视频会议诈骗”案件让远程开户面临新威胁。工程上的常见对抗:

3.6 eKYC 流程的幂等与断点续传

移动网络极不稳定,一次开户常横跨 5–15 分钟,用户可能中断。系统必须幂等:

四、制裁与 PEP 名单筛查

4.1 数据源

名单 维护方 频率 格式
OFAC SDN / Non-SDN 美国财政部 不定期,通常当日 XML / CSV
UN Consolidated Sanctions 联合国安理会 不定期 XML
EU Consolidated List 欧盟理事会 按决议发布 XML / CSV
HMT / OFSI 英国财政部 每日 CSV / XML
人民银行反洗钱监测分析中心发布的 PBoC 人行系统内 内部
国际 PEP 库 Dow Jones / Refinitiv World-Check / LexisNexis 商业,每日 API

工程实现的第一步是把每家源做成标准化中间表

CREATE TABLE sanction_entity (
  list_code       VARCHAR(16) NOT NULL,       -- OFAC_SDN / UN / EU / HMT / PBOC
  entity_uid      VARCHAR(64) NOT NULL,       -- 源系统内 ID
  entity_type     VARCHAR(16) NOT NULL,       -- INDIVIDUAL / ENTITY / VESSEL / AIRCRAFT / CRYPTO_WALLET
  primary_name    VARCHAR(400) NOT NULL,
  dob             DATE,
  nationality     CHAR(2),
  ids_json        JSON,                       -- 证件号码集合
  program         VARCHAR(64),                -- SDGT / UKRAINE-EO13662 / CYBER2 ...
  updated_at      TIMESTAMP NOT NULL,
  PRIMARY KEY (list_code, entity_uid)
);

CREATE TABLE sanction_alias (
  list_code       VARCHAR(16) NOT NULL,
  entity_uid      VARCHAR(64) NOT NULL,
  alias_name      VARCHAR(400) NOT NULL,
  alias_type      VARCHAR(16),                -- AKA / FKA / NKA
  script          VARCHAR(16),                -- LATIN / CYRILLIC / ARABIC / HAN
  PRIMARY KEY (list_code, entity_uid, alias_name)
);

4.2 模糊匹配算法

制裁筛查的核心是”不能漏”。同一个人在不同名单里可能写作 “Vladimir Putin”、“Владимир Путин”、“普京”、“V. V. Putin”。算法组合:

import unicodedata
import re
from pypinyin import lazy_pinyin
from rapidfuzz import fuzz
from jellyfish import jaro_winkler_similarity, metaphone

TITLES = {"mr", "mrs", "ms", "dr", "sir", "madam",
          "先生", "女士", "博士", "教授", "同志"}

def normalize(name: str) -> str:
    s = unicodedata.normalize("NFKD", name)
    s = "".join(ch for ch in s if not unicodedata.combining(ch))
    s = s.lower()
    s = re.sub(r"[^\w\u4e00-\u9fff ]+", " ", s)
    tokens = [t for t in s.split() if t not in TITLES]
    return " ".join(tokens).strip()

def to_latin_tokens(name: str) -> list[str]:
    n = normalize(name)
    tokens = []
    for tok in n.split():
        if re.search(r"[\u4e00-\u9fff]", tok):
            tokens.extend(lazy_pinyin(tok))
        else:
            tokens.append(tok)
    return tokens

def name_score(query: str, candidate: str) -> float:
    q = " ".join(to_latin_tokens(query))
    c = " ".join(to_latin_tokens(candidate))
    if not q or not c:
        return 0.0
    jw = jaro_winkler_similarity(q, c)
    tsr = fuzz.token_set_ratio(q, c) / 100
    pho_q = metaphone(q.replace(" ", ""))
    pho_c = metaphone(c.replace(" ", ""))
    pho = 1.0 if pho_q and pho_q == pho_c else 0.0
    return 0.45 * jw + 0.45 * tsr + 0.10 * pho

def screen(query_name: str, aliases: list[str], threshold=0.88):
    hits = []
    for a in aliases:
        s = name_score(query_name, a)
        if s >= threshold:
            hits.append((a, round(s, 3)))
    return sorted(hits, key=lambda x: -x[1])

# 示例
candidates = ["Vladimir Vladimirovich Putin", "V. V. Putin",
              "普京", "Владимир Путин"]
print(screen("vladimir putin", candidates, threshold=0.80))

4.3 二次确认与白名单

高命中率必然带来高误报。工业界典型做法:

  1. 算法命中 → 合规人工复核:SLA 通常是 4 小时以内。
  2. 白名单(Good-Guy List):被确认为同名非本人的客户,写入白名单并记录复核人、证据、有效期(通常 1 年)。名单每日刷新时,如果源记录发生任何变化,白名单自动失效要求再审。
  3. 50% 规则(OFAC):被制裁实体直接或间接持有 ≥ 50% 的公司,自动也被视为受制裁,这部分必须通过 UBO 图谱展开筛查。

4.4 实时筛查 vs 事后扫描

不同业务场景对延迟与完整性的要求不同:

场景 模式 延迟要求 备注
开户 同步 < 2s 命中则阻断流程
跨境汇款 同步 < 500ms 单条 SWIFT MT 报文出库前
卡支付授权 异步 / 黑名单命中前置 名单查 <50ms 仅对 SDN 精确匹配阻断
日终客户重扫 批量 夜间 1–4h 名单变动全量扫描
新名单发布 事件驱动 发布后 1h 内重扫存量客户 OFAC 更新 RSS 触发
历史回溯 离线 按需 稽查要求重现

工程上把筛查能力沉淀为两套服务:同步 API(毫秒级,索引驻留内存,规则简化)与批量引擎(Spark 作业,全算法全阈值)。两者共用规则定义,但性能与召回率有意不同。同步 API 的漏报由批量引擎在 T+0/T+1 兜底,兜底命中即升级为案件,客户账户冻结。

4.5 SSN/ID 国别的坑

跨境合规系统里证件号校验常栽跟头:

筛查引擎做精确匹配前必须按 (国家, 证件类型) 规范化,否则”中国身份证 vs 俄罗斯内护” 的字符串撞车会制造大量误报。

五、交易监测系统(TMS)

TMS 的目标不是阻断交易(那是反欺诈干的事),而是发现异常模式、支持 STR 上报。上游是账务与支付事件流,下游是案件管理。

flowchart LR
  subgraph 源
    P1[支付系统]
    P2[核心账务]
    P3[跨境汇款]
    P4[证券清算]
    P5[链上交易]
  end
  P1 & P2 & P3 & P4 & P5 --> KAFKA((事件总线))
  KAFKA --> N[标准化 + 丰富<br/>客户画像/对手方/图谱]
  N --> RULE[规则引擎<br/>Drools/自研]
  N --> ML[异常检测模型]
  RULE --> ALERT[预警池]
  ML --> ALERT
  ALERT --> CASE[案件管理]
  CASE --> STR[STR/SAR 上报]
  CASE --> FB[标注反馈]
  FB --> ML

5.1 大额交易与可疑交易

在中国场景(人民银行令〔2016〕第 3 号)常见阈值(简化,实际以最新规定为准):

美国 FinCEN:CTR 单日现金 > 10,000 美元事后 15 天;SAR 发现后 30 天。

5.2 典型可疑场景

场景 特征 规则描述(伪代码)
分拆(Smurfing / Structuring) 故意低于阈值 近 7 日内 ≥ 5 笔 8,000–9,999 美元现金存款
快进快出(Pass-through) 大额入账后 24h 内 ≥ 90% 出账 out_amt / in_amt > 0.9 AND hold_hours < 24
异地集中 大量不同地域打款到同一账户 30 日内打款人分布于 ≥ 10 个城市且单笔 < 阈值
过桥账户 同人多账户轮转 图谱 2-hop 内回流 ≥ 80% 本金
账户睡眠激活 长期不动后突发大额 days_since_last_txn > 180 AND amount > R3_threshold
高风险国家 往来 FATF 黑/灰名单国家 对手方国家 ∈ high_risk_list
对敲(Mirror Trading) 两地 A/B 对敲汇款 同一实控人不同地域账户近 T 日内对敲规模
地下钱庄(Hawala 变体) 小额高频 + 无商业背景 月内日均 > N 笔且 MCC 聚焦于”个人转账”

一段简化规则引擎的 Python 原型:

from dataclasses import dataclass
from datetime import datetime, timedelta
from collections import defaultdict, deque

@dataclass
class Txn:
    id: str
    customer_id: str
    amount_usd: float
    direction: str          # IN / OUT
    ts: datetime
    counterparty: str
    country: str
    channel: str            # CASH / WIRE / CARD / CRYPTO

class StructuringRule:
    """近 N 天内小于阈值的现金存款次数达标 → 报警"""
    def __init__(self, window_days=7, threshold_low=8_000,
                 threshold_high=9_999, count=5):
        self.window = timedelta(days=window_days)
        self.low, self.high, self.count = threshold_low, threshold_high, count
        self.buf: dict[str, deque[Txn]] = defaultdict(deque)

    def feed(self, t: Txn):
        if not (t.channel == "CASH" and t.direction == "IN"
                and self.low <= t.amount_usd <= self.high):
            return None
        q = self.buf[t.customer_id]
        q.append(t)
        while q and q[0].ts < t.ts - self.window:
            q.popleft()
        if len(q) >= self.count:
            return {"rule": "STRUCTURING",
                    "customer": t.customer_id,
                    "txn_ids": [x.id for x in q],
                    "score": min(1.0, len(q) / 10)}
        return None

class PassThroughRule:
    """大额入账 24h 内 >= 90% 出账"""
    def __init__(self, min_in=100_000, out_ratio=0.9,
                 window_hours=24):
        self.min_in, self.ratio = min_in, out_ratio
        self.window = timedelta(hours=window_hours)
        self.in_events: dict[str, list[Txn]] = defaultdict(list)
        self.out_sum: dict[str, float] = defaultdict(float)

    def feed(self, t: Txn):
        key = t.customer_id
        if t.direction == "IN" and t.amount_usd >= self.min_in:
            self.in_events[key].append(t)
        elif t.direction == "OUT":
            self.out_sum[key] += t.amount_usd
        # 清理过期入账并评估
        alerts = []
        kept = []
        for ev in self.in_events[key]:
            if t.ts - ev.ts > self.window:
                continue
            kept.append(ev)
        self.in_events[key] = kept
        for ev in kept:
            if self.out_sum[key] / ev.amount_usd >= self.ratio:
                alerts.append({"rule": "PASS_THROUGH",
                               "customer": key,
                               "in_txn": ev.id,
                               "out_sum": self.out_sum[key]})
        return alerts or None

真实 TMS 通常跑在 Flink/Spark Streaming 上,状态后端 RocksDB,规则热更新;异常检测走 Isolation Forest、HMM、GraphSAGE 等。模型与规则不相互替代:规则用于监管可解释场景(大额、特定国别),模型用于”未见过的模式”。

5.3 模型的可解释性

合规报告的 可疑原因 字段必须是自然语言,“模型分数 0.97”不够。工程上常用 SHAP 或规则回译(Rule Distillation)把模型输出翻译成”近 30 天与高风险地区交易占比升至 68%,显著高于同业中位数 5%“。

5.4 特征工程与模型

机器学习模型在 TMS 中的角色不是”替代规则”,而是覆盖规则看不见的长尾。特征大致分四层:

层级 示例特征 窗口 备注
账户静态 开户渠道、KYC 等级、职业、行业 与 CRR 对齐
交易聚合 日/周/月笔数、金额、方差、最大值 1d/7d/30d/180d 滑动窗口
对手方网络 入度、出度、集聚系数、PageRank、2-hop 高风险比 每日离线 图计算
时序模式 日内分布熵、跨境比例、现金比例、节假日异常 7d/30d 周期性

模型组合常见做法:

  1. 无监督异常(Isolation Forest、AutoEncoder)先筛出 1% 的异常样本。
  2. 监督模型(XGBoost、LightGBM)用历史 STR 真阳性做目标,输出可疑概率。
  3. 图神经网络(GraphSAGE、GAT)刻画共享特征的团伙。
  4. 规则回译(Rule Distillation)将重要特征贡献翻译为自然语言理由。

离线训练与在线推理分离,特征存储使用 Feast / HBase / Redis,确保离线/在线一致性。模型上线前必须通过合规模型风险管理(Model Risk Management,MRM)评审:数据血缘、偏差测试、对保护群体的公平性(Disparate Impact)、降级方案。

5.5 阈值治理

任何阈值都是可审计对象。建立阈值注册表

CREATE TABLE rule_config (
  rule_code      VARCHAR(64) PRIMARY KEY,
  version        INT NOT NULL,
  params_json    JSON NOT NULL,
  effective_from TIMESTAMP NOT NULL,
  effective_to   TIMESTAMP,
  approved_by    VARCHAR(64) NOT NULL,
  change_reason  TEXT NOT NULL,
  jira_ref       VARCHAR(32)
);

每次阈值调整必须有合规官签字与工单号,生产系统只读取”当前生效”版本。审计时回放:用历史版本对历史数据重放,证明过去的决策基于当时有效的规则。

六、案件管理

stateDiagram-v2
  [*] --> New: 规则/模型命中
  New --> Triage: 分派
  Triage --> Investigating: 一线分析
  Investigating --> EscalatedL2: 升级二线
  Investigating --> ClosedFalse: 排除误报
  EscalatedL2 --> Filing: 决定上报
  EscalatedL2 --> ClosedFalse: 排除
  Filing --> Filed: 已提交 FIU
  Filed --> [*]
  ClosedFalse --> [*]

案件数据模型示例:

CREATE TABLE case_ (
  case_id         BIGINT PRIMARY KEY,
  customer_id     BIGINT NOT NULL,
  opened_at       TIMESTAMP NOT NULL,
  trigger         VARCHAR(64) NOT NULL,      -- RULE:STRUCTURING / MODEL:IF-0.91 / LIST:OFAC_SDN
  status          VARCHAR(16) NOT NULL,
  assignee        VARCHAR(64),
  severity        CHAR(2),                   -- S1..S4
  sla_due_at      TIMESTAMP NOT NULL,
  narrative       TEXT,                      -- 最终叙事
  decision        VARCHAR(16),               -- FILE / NO_FILE / SAR / CTR
  filed_at        TIMESTAMP
);

CREATE TABLE case_evidence (
  case_id    BIGINT,
  kind       VARCHAR(32),   -- TXN / KYC_DOC / NETWORK_GRAPH / CHAIN_TRACE
  ref        VARCHAR(256),
  added_by   VARCHAR(64),
  added_at   TIMESTAMP,
  PRIMARY KEY (case_id, kind, ref)
);

CREATE TABLE str_report (
  report_id     BIGINT PRIMARY KEY,
  case_id       BIGINT NOT NULL,
  authority     VARCHAR(32) NOT NULL,        -- FINCEN / CAMLMAC / FIU_NL ...
  format        VARCHAR(16) NOT NULL,        -- GOAML / FINCEN_SAR_XML / PBOC_CSV
  payload_uri   VARCHAR(512) NOT NULL,
  submitted_at  TIMESTAMP NOT NULL,
  ack_id        VARCHAR(128)
);

上报格式:

工程上必须保证幂等上报:提交失败重试时不重复,靠 report_id 唯一约束与回执 ack_id 追踪。

6.3 STR 报文样例与字段口径

不同 FIU 的报文格式各异。以 UN goAML 为例(许多司法辖区采用),一个简化的 <report> 片段:

<report>
  <rentity_id>BANK_ABC_0001</rentity_id>
  <submission_code>STR</submission_code>
  <report_code>STR</report_code>
  <submission_date>2026-04-15T09:12:44+08:00</submission_date>
  <currency_code_local>CNY</currency_code_local>
  <reason>疑似分拆现金存款,短期快进快出,疑涉非法集资</reason>
  <action>账户限制交易</action>
  <transaction>
    <transactionnumber>T-2026041500931</transactionnumber>
    <date_transaction>2026-04-12T10:22:00+08:00</date_transaction>
    <transmode_code>CASH_DEPOSIT</transmode_code>
    <amount_local>98500.00</amount_local>
    <t_from>
      <from_account>
        <institution_code>BANK_ABC</institution_code>
        <branch>SH001</branch>
        <account>6222****1234</account>
        <signatory>
          <t_person>
            <first_name>San</first_name>
            <last_name>Zhang</last_name>
            <birthdate>1985-07-02</birthdate>
            <ssn>3101XXXXXXXXXXXXXX</ssn>
            <nationality1>CN</nationality1>
          </t_person>
        </signatory>
      </from_account>
    </t_from>
    <t_to>
      <to_account>
        <institution_code>BANK_ABC</institution_code>
        <account>6222****9988</account>
      </to_account>
    </t_to>
  </transaction>
</report>

几个常见字段口径问题:

6.4 案件调查的数据编排

合规分析师的日常是”从告警出发,在数小时内还原资金图景”。一个高效的调查工作台应一键展开:

flowchart TB
  ALERT[告警事件] --> VW1[客户 360 视图]
  ALERT --> VW2[交易瀑布图]
  ALERT --> VW3[关系网络图]
  ALERT --> VW4[链上资金追踪]
  VW1 --> D1[KYC 档案/EDD 附件]
  VW1 --> D2[历史案件]
  VW2 --> D3[30/90/365 日对比]
  VW3 --> D4[2-hop 图谱/共享设备/共享 IP/UBO]
  VW4 --> D5[链上分析平台调用]
  VW1 & VW2 & VW3 & VW4 --> NAR[叙事生成草稿]
  NAR --> REVIEW[二线合规复核]

工具链的关键:

七、加密货币 AML

7.1 链上分析

链上地址天然伪匿名。商业链上分析平台(Chainalysis、TRM Labs、Elliptic、MistTrack、Arkham)做两件事:

  1. 实体归属:把地址聚类成实体(交易所、矿池、混币器、勒索团伙)。核心启发式:Common-Input Ownership(一次交易的多个输入同属一个实体)、找零检测、行为指纹。
  2. 风险评分:给地址打分,并追踪资金流向——例如一笔比特币经过 Tornado Cash 混币后进入某交易所存款地址,交易所 KYT(Know-Your-Transaction)系统会自动升级该笔入金的风险。

7.2 Tornado Cash 制裁案

2022 年 8 月,OFAC 把 Tornado Cash 及其智能合约地址列入 SDN 名单,这是 OFAC 第一次把智能合约地址本身列入制裁名单,引发激烈争议。2023 年 8 月美国司法部起诉其开发者。2024 年 11 月,美国第五巡回上诉法院在 Van Loon v. Department of the Treasury 案中裁定 OFAC 对 Tornado Cash 不可变智能合约的制裁超越授权(因其不属于 IEEPA 下的”财产”),相关地址随后被移除。但开发者刑事案件独立进行——代码即言论的讨论进入了金融合规领域。对工程师的启示:合约地址级制裁技术上可以被执行(交易所拒绝相关地址),但法律上仍在演化,筛查规则的”白/黑”有时需要日级刷新。

7.3 旅行规则与 IVMS101

FATF R.16 在 2019 年的 Interpretive Note 明确要求 VASP 间传递发起人与受益人信息。国际证券化数据模型 IVMS101(InterVASP Messaging Standard 101)提供统一字段:

{
  "originator": {
    "originatorPersons": [{
      "naturalPerson": {
        "name": {"nameIdentifier": [{
          "primaryIdentifier": "Zhang",
          "secondaryIdentifier": "San",
          "nameIdentifierType": "LEGL"
        }]},
        "geographicAddress": [{
          "addressType": "HOME",
          "country": "CN",
          "townName": "Shanghai",
          "streetName": "Nanjing Road",
          "buildingNumber": "100"
        }],
        "nationalIdentification": {
          "nationalIdentifier": "3101...X",
          "nationalIdentifierType": "CCPT",
          "countryOfIssue": "CN"
        }
      }
    }],
    "accountNumber": ["0xabc...def"]
  },
  "beneficiary": { "...": "..." }
}

7.4 Travel Rule 协议族

行业没有统一协议,多家互操作:

协议 发起方 传输方式 特点
TRP (Travel Rule Protocol) OpenVASP 系 HTTPS 轻量、开放规范
TRISA (Travel Rule Info Sharing Alliance) CipherTrace gRPC + mTLS 去中心 PKI
Sygna Bridge CoolBitX HTTPS 早期亚洲主流
Shyft Veriscope Shyft 链上发现 以太坊智能合约
Notabene / SumSub / 21 Analytics 商业 多协议桥接 SaaS

一个合规工程要点:VASP 间对手方发现(Counterparty Discovery,CD)是先决问题——“你知道这个收款地址属于哪家 VASP 吗?”常见做法组合:地址归属库查询(链上分析平台)+ 用户自报 + 交易所联合白名单。发现失败时按 FATF 指南,对于自托管(Self-Hosted)钱包应取得附加信息(发起人声明、地址所有权证明)。

7.5 T+0 穿透式监管(中国)

中国对金融基础设施要求”穿透式监管”——支付机构、交易所、清算机构需要按日甚至实时向监管直报底层明细。人民银行”反洗钱现场非现场监管一体化平台”、证监会”全市场交易监测系统”、外汇局”跨境资金流动监测分析系统”都是这一思路。工程影响:机构要具备按明细维度(客户、账户、商户、对手方)生成 T+0 直报包的能力,数据口径一致性比速度更关键。

直报接入的工程清单:

7.8 UBO 图谱与 50% 规则

OFAC 的 “50% 规则” 是最容易被低估的工程难点:被制裁方直接或间接合计持有 ≥ 50% 的任何实体自动受制裁,即使该实体本身不在 SDN 列表上。合规筛查必须下钻到股权穿透。

一个最小图模型:

CREATE TABLE entity (
  entity_id    BIGINT PRIMARY KEY,
  name         VARCHAR(400) NOT NULL,
  name_en      VARCHAR(400),
  country      CHAR(2),
  type         VARCHAR(16) NOT NULL          -- NP / LP / TRUST
);

CREATE TABLE ownership (
  parent_id    BIGINT NOT NULL,              -- 持股方
  child_id     BIGINT NOT NULL,              -- 被持股方
  pct          DECIMAL(6,3) NOT NULL,
  as_of        DATE NOT NULL,
  source       VARCHAR(64) NOT NULL,         -- 工商/OpenCorporates/客户申报
  PRIMARY KEY (parent_id, child_id, as_of)
);

50% 规则的实现是一个带权图可达性算法。给定制裁种子 S,对每个其他节点 n 计算 S 对 n 的合并持股:沿多条路径求和,但环与相互持股必须处理。

def aggregated_ownership(edges, seeds, target, depth=8):
    """
    edges: list[(parent, child, pct)]  pct in [0,1]
    seeds: set[节点ID]  被视为"受制裁"的起点
    返回 seeds 对 target 的聚合持股比例 (0..1)。
    用 DFS + memo,允许多路径相加,避免简单回环。
    """
    adj = {}
    for p, c, v in edges:
        adj.setdefault(p, []).append((c, v))

    memo = {}
    def dfs(node, visited):
        if node == target:
            return 1.0
        if depth_exceeded := len(visited) > depth:
            return 0.0
        key = (node, frozenset(visited))
        if key in memo:
            return memo[key]
        total = 0.0
        for child, pct in adj.get(node, []):
            if child in visited:
                continue
            total += pct * dfs(child, visited | {child})
        memo[key] = total
        return total

    return sum(dfs(s, {s}) for s in seeds)

edges = [(1, 2, 0.60), (1, 3, 0.30), (2, 4, 0.80),
         (3, 4, 0.40), (4, 5, 0.70)]
print(aggregated_ownership(edges, seeds={1}, target=5))
# 1→2→4→5: 0.6*0.8*0.7=0.336
# 1→3→4→5: 0.3*0.4*0.7=0.084
# 合计对 4 的持股 = 0.48+0.12=0.60 ≥ 50%,4 自动被制裁
# 但若按"先对 4 聚合再向下"的口径,5 持股仅 0.60*0.70=0.42 < 50%

实际合规实现会分层判定:先判定实体 4 是否被穿透制裁(≥50%),是则把 4 加入种子集,再向 5 穿透;而不是简单把所有路径乘法加到终端。这与 OFAC 官方 FAQ 401/402 的”一旦某实体被穿透,即整体视为 blocked”相符。工程师容易写错的正是这一步。

7.9 不利媒体筛查(Adverse Media)

除名单与 PEP 外,不利媒体(Adverse Media / Negative News)是 EDD 必查项。工程流程:

  1. 多源新闻抓取(路透、彭博、新华、当地主流媒体),RSS + 商业数据源(Dow Jones Factiva、LexisNexis NewsDesk)。
  2. 命名实体识别(NER)抽取人名/机构名,与客户库匹配。
  3. 分类器识别涉及话题:Fraud、Corruption、Sanctions、Money Laundering、Terrorism、Tax Evasion、Organized Crime 等 FATF 上游罪 22 类。
  4. 多语种处理:同名歧义 + 翻译偏差。
  5. 人工复核,入客户档案。

陷阱:LLM 做 NER 容易”编造人物”;生产环境仍以 CRF/BERT-NER 结合规则为主,LLM 只做摘要与话题归类,保留原文引用。

7.10 持续客户尽调的信号源

持续监测不仅看交易,还看”客户画像 drift”:

信号 数据源 触发动作
职业变动 社交数据、年报披露 重新计算 PEP 评分
国籍/居住国变更 自助更新、证件续签 重新计算合规矩阵
股权结构变动 工商/OpenCorporates 变更订阅 重新 UBO 穿透
制裁名单新增 每日拉取 全量回溯筛查
交易偏离画像 TMS 输出 风险评级上调
媒体负面 不利媒体扫描 人工介入
监管处罚 证监/人行/外管局处罚公告 二线复核

这些信号统一进入”客户信号总线”,任何一条都能触发 review_due_at = today

7.6 链上 → 链下的风险穿透

一笔链上资金最终要经交易所上岸才能产生”洗白”效果。VASP 的 KYT(Know-Your-Transaction)默认要做:

  1. 入金地址扫描:对每笔充值调用链上分析 API,返回标签(交易所、混币器、制裁地址、勒索、诈骗、Darknet Market)。
  2. 资金溯源深度:追溯前 N 跳,若 N 跳内出现高危标签按规则冻结或要求说明。N 通常 3–5 跳。
  3. 出金地址审核:对用户自填的外部地址同样扫描,拒绝制裁地址。
  4. 实时 Mempool 拦截(进阶):在用户下单提款但 TX 尚未广播前,将地址与最新黑名单比对。
  5. 链上与链下对齐:把 KYC 身份与充/提地址以”账户-地址”表绑定,并周期性判断同一地址是否被多客户复用(互斥/团伙)。

7.7 VASP 间互操作的实操问题

Travel Rule 协议百花齐放,实际运营中头疼点:

7.11 旅行规则数据模型

为保证多协议互通,内部抽象出独立于协议的领域模型:

CREATE TABLE tr_message (
  tr_id          CHAR(36) PRIMARY KEY,        -- 内部 UUID
  direction      VARCHAR(8) NOT NULL,         -- OUT / IN
  originator_vasp VARCHAR(64),
  beneficiary_vasp VARCHAR(64),
  asset          VARCHAR(32) NOT NULL,        -- BTC / ETH / USDT-ERC20
  amount         DECIMAL(38,18) NOT NULL,
  tx_hash        VARCHAR(128),
  protocol       VARCHAR(16) NOT NULL,        -- TRP / TRISA / SYGNA / NOTABENE
  payload_enc    VARBINARY(8000) NOT NULL,    -- IVMS101 加密
  status         VARCHAR(16) NOT NULL,        -- PENDING / ACK / REJECTED / EXPIRED
  created_at     TIMESTAMP NOT NULL,
  acked_at       TIMESTAMP
);

设计上要点:payload 以 IVMS101 结构加密存储,对端公钥来自对手方 VASP Directory;所有协议适配器解码后落同一张表,便于稽查与跨协议迁移。

八、数据隐私与 AML 的张力

AML 要求”知道你的客户并留存 5–10 年”,隐私法要求”最小必要、到期删除”。典型冲突与工程处置:

冲突点 AML 立场 隐私立场 工程折中
证件号 留存 最小必要 明文加密 + 哈希索引;仅合规角色可解密
生物特征 留存比对记录 专项同意、分类保护 分桶存储,生物特征单独 KMS 与单独到期
交易明细 5+ 年 “目的达成即删除” AML 法律义务作为 GDPR Art.6(1)(c) 合法依据
跨境传输 AML 数据 集团内共享 跨境管控 SCC/BCR、数据本地化镜像、加密后传输
被拒客户信息 内部黑名单用于再次识别 尽量删除 保留最小字段(姓名哈希 + 原因码 + 保留期)

中国《个人信息保护法》第 13 条赋予”履行法定义务”作为合法性基础;欧盟 GDPR Art.6(1)(c)/(e) 同理。关键是把法律依据写进数据生命周期策略,而不是拍脑袋。

8.1 隐私增强技术在 AML 的落地

近年一批隐私增强技术(Privacy Enhancing Technologies,PET)开始在 AML 领域探索:

这些技术尚未普及,但在跨机构情报共享(例如 Project COSMIC 允许新加坡七家银行在高置信度阈值下共享可疑客户)中已有生产级试点。工程师应关注,但不应在主链路上引入未经监管认可的密码学方案。

8.2 数据本地化

多个司法辖区要求金融数据本地化:中国、俄罗斯、印度、印尼、越南、沙特。对跨国集团来说 AML 数据(尤其原始交易与 KYC 文档)必须留在本地;跨境共享需去标识化或走监管备案通道。工程上的落地:每个辖区一套独立数据底座,合规分析在本地完成,仅派生指标(红绿灯级别)回到集团总部。

九、工程坑点

  1. 名单刷新的”半小时盲区”:OFAC 更新在美国东部时间下午,亚洲机构次日早上才刷新,窗口内可能放行被新制裁实体。方案:订阅 OFAC RSS、接入商业 feed(Dow Jones/Refinitiv)实现准实时刷新;关键产品(跨境汇款)采用”交易时二次筛查”而不是”开户时一次筛查”。
  2. 字符编码陷阱:SDN 名单 XML 含有俄文、阿拉伯文、希伯来文、中日韩字形,没有统一归一化会漏命中。务必跑 NFKD → ASCII fold → 再做转写。
  3. UBO 深度限制:一些合规团队只查 2 层股权,实际离岸结构常达 5 层以上。设置层数与持股比例(≥ 25%)的组合截止条件,并保留”发现新层”的再查机制。
  4. 去重与上报二义性:同一笔交易可能同时符合大额与可疑,必须决定”一份报告”还是”两份报告”。按当地监管口径在策略表里配置。
  5. 历史数据回放:新规则上线要评估误报率,必须支持在离线数据湖上回放近 90 天交易。因此 TMS 的规则描述最好是数据+时间的纯函数,不带副作用。
  6. 生物样本跨模型兼容:更换人脸模型后旧嵌入不可比对。策略是双跑过渡期 + 新注册用新模型 + 旧用户按需重采。
  7. STR 上报的”tipping-off”风险:上报后不得告知客户(tipping-off 属刑事风险),工单系统的通知模板、催办邮件必须排除被上报客户。
  8. PEP 库误报率极高:共名情况严重(例:某国部长与众多同名普通公民),阈值一味拉高会漏;一味拉低会淹没合规人员。建议按国家/职位拆分阈值,并引入”生日差 ≤ 3 年 + 国籍相同”等上下文条件。
  9. 链上地址的可组合性:用户自托管多链钱包,同一实体多地址。Travel Rule 只覆盖 VASP 间,VASP-to-Self-Hosted 需要附加”地址归属证明”流程(Satoshi Test、签名验证)。
  10. 留存与审计对象存储成本:10 年交易 + KYC 档案 + 人脸视频 PB 级,必须分层(热/温/冷/归档)并有密钥轮转;合规稽查时从 Glacier 调回需要预案。

十、选型与落地清单

供应商层:

自建 vs 外采:

上线清单(Go-Live Checklist):

  1. 合规矩阵完成,产品 × 管辖 × 客户类型 × 风险级 全覆盖。
  2. KYC 数据模型与档案留存策略通过法务与 DPO 评审。
  3. 制裁名单源每日自动拉取 + 校验 + 回退机制。
  4. 模糊匹配阈值经回溯测试(F1、误报率、漏报率)。
  5. TMS 规则在生产镜像上回放近 90 天通过率。
  6. 案件管理 SLA 与岗位权限(一线/二线/三线)定义并纳入监控。
  7. STR / CTR 报文生成工具支持至少两个司法辖区,E2E 拨测通过。
  8. 审计日志与 WORM 存储接通,密钥分管与到期策略。
  9. 事故演练:误报大面积来袭 / 名单源故障 / FIU 对接中断 / 监管现场检查。
  10. 员工培训(尤其一线客服避免 tipping-off)。

十一、公开案例

十二、与本系列其他文章的关系

十三、参考资料


十四、小结

AML 工程与反欺诈最大的差异是可解释性与可稽查性优先于准确率:一套”召回高但无法向监管解释”的模型在 AML 场景是不可部署的。把监管要求翻译为”标准化合规矩阵 + 可回放的规则 + 可审计的模型”,把客户信息、交易流水、链上证据、名单命中、案件叙事沉淀为”一键重现十年前”的档案结构,这些都是反洗钱系统长期演进的主线。名单刷新更快、模糊匹配更准、链上穿透更深、跨境协作更紧密——每一项都直接牵动成本、用户体验与合规风险的平衡。

对中国市场的从业者,2025 年修订的《反洗钱法》与人民银行 3 号令将会把”受益所有人识别”“特定非金融机构接入”“分级分类管理”推得更深;对出海企业,MiCA、AMLR 与各辖区 Travel Rule 协议的互操作是未来两年的核心工程议题。把合规当工程问题做,而不是当文书工作做——这是本篇希望传递的底层态度。


上一篇《反欺诈:设备指纹、关系图谱、行为序列、黑产对抗》

下一篇《信用风险:评分卡、违约预测、巴塞尔 III》

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · architecture / fintech

金融科技工程

面向中国工程团队的金融科技系列。从账务底盘、支付、清结算、交易所、风控合规到可靠性与灾备,中国与全球视角并举,讲清楚金融系统在工程落地中的真实挑战。

2026-04-22 · architecture / fintech

【金融科技工程】金融科技工程全景:从支付到交易所的系统分类与读图

金融科技(FinTech)不是普通后端加一张账户表。钱的原子性、监管的硬边界、一个小数点的代价,把这个领域推进到工程强度最高的那一档。本文是【金融科技工程】25 篇的总目录与阅读地图:先交代为什么它比一般业务系统更难,再给出对账体、支付体、交易体、风控合规体四维分类,把后续 24 篇挂到骨架上,最后给出一份绿地项目的落地顺序建议。


By .