15、合规与审计:交易记录存储,监管报告生成(MiFID II/Dodd-Frank),反洗钱(AML)检查

做量化做市,技术再牛,合规不过关,一切都白搭。这不是危言耸听,我在一家欧洲的做市商公司待过,亲眼见过一个交易团队因为日志记录不规范,被监管罚了上百万欧元。说白了,合规不是束缚,而是做市商业务的「安全气囊」。

今天这一章,我们就来聊聊做市商系统里最「枯燥」但又最「致命」的三个模块:交易记录怎么存、监管报告怎么出、反洗钱怎么查。嗯,我会尽量讲得接地气一点。

核心观点:合规系统不是事后补丁,而是交易流水线的一部分。数据一旦丢失或格式不对,你连解释的机会都没有。

15.1 交易记录存储:不只是写日志那么简单

很多人觉得,交易记录不就是把订单、成交、撤单写进数据库吗?其实没那么简单。监管要求的是「完整、不可篡改、可追溯」的记录链。

我个人习惯把交易记录分为三层:

  • 原始事件层:交易所发来的每一个消息,包括行情、订单确认、成交回报、拒绝原因。这些是「证据」。
  • 业务对象层:订单、成交、持仓、资金变动。这些是「事实」。
  • 审计快照层:每隔一段时间(比如1分钟)对系统状态做一次全量快照。这些是「时间胶囊」。

为什么要做快照?我在项目中遇到过一个问题:某个订单在数据库里被更新了多次,但中间某个状态丢了。监管问「这个订单为什么在10:00:03变成了已撤销?」你拿不出证据。有了快照,你就能说「看,10:00:00的时候它还是活的,10:01:00它已经撤销了,中间发生了什么?这里有事件日志。」

我的建议:存储引擎选时序数据库(比如InfluxDB)配合关系型数据库。时序库存原始事件,关系库存业务对象。别把所有东西塞进一张大表,查询会慢到你怀疑人生。

下面是一个简化的存储模型,我用的是Python + SQLAlchemy:

from sqlalchemy import Column, String, BigInteger, Float, DateTime, Enum
from sqlalchemy.ext.declarative import declarative_base
import enum

Base = declarative_base()

class OrderStatus(enum.Enum):
    NEW = 'NEW'
    PARTIALLY_FILLED = 'PARTIALLY_FILLED'
    FILLED = 'FILLED'
    CANCELED = 'CANCELED'
    REJECTED = 'REJECTED'

class TradeEvent(Base):
    __tablename__ = 'trade_events'
    
    event_id = Column(BigInteger, primary_key=True, autoincrement=True)
    event_type = Column(String(32), nullable=False)  # 'NEW_ORDER', 'FILL', 'CANCEL'
    symbol = Column(String(16), nullable=False)
    order_id = Column(String(64), nullable=False, index=True)
    price = Column(Float)
    quantity = Column(Float)
    status = Column(Enum(OrderStatus))
    timestamp = Column(DateTime, nullable=False, index=True)
    raw_payload = Column(String(2048))  # 原始JSON,用于审计

class AuditSnapshot(Base):
    __tablename__ = 'audit_snapshots'
    
    snapshot_id = Column(BigInteger, primary_key=True)
    snapshot_time = Column(DateTime, nullable=False, index=True)
    positions_json = Column(String(8192))  # 全量持仓序列化
    orders_json = Column(String(8192))     # 全量活动订单序列化
    checksum = Column(String(64))          # 用于校验完整性

你可能会问:「存原始JSON有什么用?」我告诉你,太有用了。有一次监管问我们某个订单的报价时间戳和交易所的时间戳差了2毫秒,我们直接把原始消息翻出来,发现是网络延迟,不是系统作弊。没有原始数据,你百口莫辩。

15.2 监管报告生成:MiFID II 和 Dodd-Frank 的「噩梦」

做市商如果涉及欧美市场,MiFID II(欧洲)和 Dodd-Frank(美国)是绕不开的两座大山。这两个法规的核心要求是什么?说白了就是:你做的每一笔交易,都要在指定时间内,按指定格式,报告给监管机构。

MiFID II 要求交易报告在 T+1 日内提交,包含65个字段。Dodd-Frank 的 swap 报告更狠,要求实时或准实时报告。我刚开始接触时,觉得这简直是反人类——65个字段,每个字段都有严格的格式要求,填错一个就罚款。

举个例子,MiFID II 里有个字段叫「交易对手识别码」(LEI),必须是20位字母数字。如果你填了19位或者21位,系统直接拒收。我曾经因为一个字段的时区格式写错了(UTC vs UTC+1),被退回了一整批报告,加班到凌晨三点重新生成。

避坑指南:千万不要手动生成报告。一定要用自动化管道。我见过有人用Excel手工填,结果漏了一笔交易,罚款5万欧元。自动化管道至少能保证「不漏、不错、不迟」。

下面是一个简化的报告生成流水线架构:

# 伪代码:MiFID II 报告生成器
class MiFIDReportGenerator:
    def __init__(self, trade_store, report_client):
        self.trade_store = trade_store
        self.report_client = report_client
    
    def generate_daily_report(self, date):
        # 1. 获取当天所有成交
        trades = self.trade_store.get_trades_by_date(date)
        
        # 2. 转换为MiFID II格式
        report_entries = []
        for trade in trades:
            entry = {
                'reporting_firm_id': 'LEI1234567890',
                'trade_date': trade.exec_time.strftime('%Y-%m-%d'),
                'trade_time': trade.exec_time.strftime('%H:%M:%S.%f')[:-3],
                'instrument_id': trade.symbol,
                'price': round(trade.price, 4),
                'quantity': trade.quantity,
                'counterparty_id': trade.counterparty_lei,
                'transaction_type': 'BUY' if trade.side == 'BUY' else 'SELL',
                # ... 还有60多个字段
            }
            report_entries.append(entry)
        
        # 3. 校验字段
        errors = self.validate_entries(report_entries)
        if errors:
            raise ValueError(f"报告校验失败: {errors}")
        
        # 4. 提交到监管平台
        response = self.report_client.submit(report_entries)
        return response

你可能会觉得,65个字段写起来很烦。但更烦的是,每个字段的规则还在变。MiFID II 在2023年更新过一次,把「交易时间」的精度从毫秒改到了微秒。如果你的系统不支持微秒,那就等着被罚吧。

15.3 反洗钱(AML)检查:别让做市商变成洗钱通道

反洗钱,听起来像是银行的事。但做市商一样有责任。监管要求做市商对每一笔交易进行AML筛查,识别可疑交易模式。说白了,就是看有没有人利用你的流动性来洗钱。

常见的AML检查包括:

  • 大额交易监控:单笔超过某个阈值(比如1万美元)的交易,需要标记并上报。
  • 频繁小额交易:有人故意把大额拆成小额,规避阈值检查。这叫「结构化交易」(Structuring)。
  • 异常交易模式:比如一个账户在短时间内大量买卖同一品种,且价格明显偏离市场。这可能是「对倒交易」(Wash Trading)。
  • 制裁名单筛查:交易对手是否在OFAC(美国财政部外国资产控制办公室)的制裁名单上。

我在项目中遇到过最头疼的是「误报」问题。AML系统太敏感,一天能报几百个警报,但99%都是误报。合规团队的人天天看警报,看到最后都麻木了。真正有问题的交易反而被漏掉了。

我的经验:不要只依赖规则引擎。规则引擎(比如「单笔超过1万就报警」)太死板。引入机器学习模型,根据历史数据训练一个异常检测模型,可以大幅降低误报率。我们当时用了一个简单的孤立森林(Isolation Forest),误报率从90%降到了30%。

下面是一个简单的AML检查流程:

class AMLChecker:
    def __init__(self, sanction_list, threshold=10000):
        self.sanction_list = sanction_list
        self.threshold = threshold
    
    def check_trade(self, trade):
        alerts = []
        
        # 1. 制裁名单检查
        if trade.counterparty_id in self.sanction_list:
            alerts.append(f"交易对手在制裁名单中: {trade.counterparty_id}")
        
        # 2. 大额交易检查
        if trade.notional_value > self.threshold:
            alerts.append(f"大额交易: {trade.notional_value} > {self.threshold}")
        
        # 3. 结构化交易检查(简化版)
        recent_trades = self.get_recent_trades(trade.counterparty_id, minutes=5)
        total_value = sum(t.notional_value for t in recent_trades) + trade.notional_value
        if total_value > self.threshold and all(t.notional_value < self.threshold for t in recent_trades):
            alerts.append(f"疑似结构化交易: 累计 {total_value}")
        
        return alerts

嗯,这里要注意:AML检查不能只做一次。交易执行前要做一次预检查,执行后还要做一次后检查。因为有些信息(比如交易对手的最终受益人)可能在交易执行后才更新。

15.4 知识体系总览

说了这么多,我画了一张图,帮你把合规与审计的核心逻辑串起来。你看一眼就明白了:

合规与审计核心架构 交易执行引擎 数据采集层(原始事件 + 业务对象) 交易记录存储 原始事件 + 快照 不可篡改 + 可追溯 监管报告生成 MiFID II / Dodd-Frank 65字段 + 自动化管道 反洗钱(AML)检查 大额交易 + 结构化交易 制裁名单 + 异常检测 审计与合规输出 数据流方向:交易执行 → 采集 → 三个核心模块 → 审计输出

这张图其实就讲了三件事:存好数据、生成报告、检查异常。三者缺一不可。数据存不好,报告就出不来;报告出不来,监管就找你麻烦;AML没做好,可能直接吊销牌照。

最后说一句,合规系统最好在交易系统设计之初就考虑进去。不要等到上线了再补,那就像房子盖好了再挖地基,成本高得吓人。我个人习惯在每一个交易接口后面都加一个「审计钩子」,所有数据自动流入合规模块,不需要人工干预。

好了,这一章就到这里。合规虽然枯燥,但它是做市商业务的「护城河」。没有它,你的策略再赚钱,也只是一座沙堡。


无相订单流研究社 微信Lucian808555