Infrastructure
MiFID II Clock Sync Requirements and How They Translate to Crypto Compliance
MiFID II RTS-25 timestamp requirements explained for practitioners, with the full implementation pipeline I built at Upside and what it means for UAE/DIFC crypto firms.
At Upside, MiFID II compliance was not optional - we were a regulated trading firm with European clients, and the clock synchronization requirements of RTS-25 applied to every transaction. I built the timestamp pipeline from scratch, including the PTP infrastructure, the audit log format, and the attestation artifacts that our compliance team submitted to regulators.
When I started building ZeroCopy’s infrastructure for regulated crypto trading, I mapped the MiFID II requirements to what UAE ADGM, DIFC, and crypto-native regulators are actually asking for today. The specific numbers differ, but the architecture is nearly identical. This post covers both.
What RTS-25 Actually Requires
The regulatory framework is Commission Delegated Regulation (EU) 2017/574, commonly called RTS-25 (Regulatory Technical Standards on clock synchronisation). The headline requirement is deceptively simple:
All operators of trading venues and their members or participants shall synchronise the business clocks they use to record the date and time of any reportable event with Coordinated Universal Time (UTC).
The actual requirements have precision tiers that vary by firm type:
Category Max Divergence Max Granularity
──────────────────────────────────────────────────────────────────────────
High-frequency trading (algorithmic, 100 microseconds 1 microsecond
messages > 1/second)
Algorithmic trading (non-HFT) 1 millisecond 1 millisecond
Voice brokers 1 second 1 second
Systematic internalisers 1 millisecond 1 millisecond
“Divergence” means the maximum allowed offset of your business clock from UTC at any point during trading hours. “Granularity” is the required precision of timestamp fields in your transaction records.
If you are an HFT firm under RTS-25, you need to demonstrate that your clocks are within 100µs of UTC at all times, and that your transaction logs carry 1µs-resolution timestamps. This is not achievable with NTP alone. The specification explicitly requires PTP (or GPS-derived time) for category 1 firms.
What Auditors Actually Check
Regulators do not just verify that you claim to use PTP. During an audit at Upside, the inspectors requested:
-
Clock synchronization topology diagram: which servers sync to what sources, with IP addresses and port numbers.
-
NTP/PTP configuration files: the actual running configuration. They checked domain numbers, sync sources, and fallback behavior.
-
Drift measurement artifacts: continuous logs from
phc2sysandchronyc trackingshowing offset-from-UTC over the audit period (typically 3-6 months). They compute the maximum observed drift and verify it is within the required bound. -
Alarm configuration: evidence that your monitoring will alert and halt trading if clock sync is lost. They want to see the alert threshold and the trading halt procedure.
-
Transaction log format: the exact fields in your audit log and the timestamp format. They check that the timestamp field carries sufficient precision (≥1µs for category 1) and that the timezone is UTC or TAI with documented offset.
-
Synchronization source traceability: your GPS/GNSS reference or your link to a stratum-1 source, with documentation of that source’s UTC traceability.
The most common deficiency I saw: firms with good infrastructure but poor documentation. The clock sync was working correctly, but the evidence artifacts - the continuous drift logs, the alert configurations, the topology diagram - did not exist or were stale.
The Implementation Pipeline I Built at Upside
The full pipeline, from hardware to audit log:
GPS/GNSS receiver (roof antenna)
↓
Stratum-1 server (local data center)
↓ PTP grandmaster
Solarflare SFN8522 NICs
↓ ptp4l (hardware timestamping)
PHC (PTP Hardware Clock on NIC)
↓ phc2sys
CLOCK_REALTIME / CLOCK_TAI (Linux system clock)
↓ application
Transaction timestamps (CLOCK_TAI)
↓ audit log pipeline
Transaction records with UTC timestamps + TAI source flag
The GPS receiver gave us UTC traceability independent of internet connectivity - important for a co-located trading system where we did not want our compliance infrastructure to depend on internet time sources. The local stratum-1 served as the PTP grandmaster for all trading servers.
For the audit log format, we chose TAI internally and converted to UTC at log emission time. The conversion is UTC = TAI - 37 (as of 2026), hardcoded with an explicit version tag:
# audit_timestamp.py
import time
from dataclasses import dataclass
# IERS leap second table - update when new leap seconds are announced
# Last update: 2017-01-01 (most recent leap second)
TAI_UTC_OFFSET_SECONDS = 37
@dataclass
class AuditTimestamp:
tai_ns: int # Internal reference (never smears)
utc_ns: int # Derived from TAI - offset
tai_offset: int # Offset used for conversion (audit trail)
source: str # "PTP_HW" | "PTP_SW" | "NTP" | "SYSTEM"
def to_iso8601_utc(self) -> str:
"""Returns UTC timestamp at microsecond precision per RTS-25 category 1."""
utc_seconds = self.utc_ns // 1_000_000_000
utc_micros = (self.utc_ns % 1_000_000_000) // 1_000
import datetime
dt = datetime.datetime.fromtimestamp(utc_seconds, tz=datetime.timezone.utc)
return f"{dt.strftime('%Y-%m-%dT%H:%M:%S')}.{utc_micros:06d}Z"
def get_audit_timestamp() -> AuditTimestamp:
# Read CLOCK_TAI (requires Linux 3.10+)
CLOCK_TAI = 11 # Linux clock ID
import ctypes
class timespec(ctypes.Structure):
_fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)]
libc = ctypes.CDLL('librt.so.6')
ts = timespec()
libc.clock_gettime(CLOCK_TAI, ctypes.byref(ts))
tai_ns = ts.tv_sec * 1_000_000_000 + ts.tv_nsec
utc_ns = tai_ns - TAI_UTC_OFFSET_SECONDS * 1_000_000_000
return AuditTimestamp(
tai_ns=tai_ns,
utc_ns=utc_ns,
tai_offset=TAI_UTC_OFFSET_SECONDS,
source="PTP_HW",
)
The monitoring that feeds the drift measurement artifacts regulators request:
#!/bin/bash
# clock_drift_monitor.sh - runs continuously, logs drift to CSV
# Ingest into your SIEM / compliance archival system
LOG_FILE="/var/log/trading/clock_drift_$(date +%Y-%m).csv"
echo "timestamp_utc,offset_from_master_ns,path_delay_ns,phc_system_offset_ns,status" > "$LOG_FILE"
while true; do
# Get PTP offset from master
PTP_STATUS=$(pmc -u -b 0 'GET CURRENT_DATA_SET' 2>/dev/null)
OFFSET=$(echo "$PTP_STATUS" | grep offsetFromMaster | awk '{print $2}')
DELAY=$(echo "$PTP_STATUS" | grep meanPathDelay | awk '{print $2}')
# Get phc2sys offset (PHC vs system clock)
PHC_OFFSET=$(journalctl -u phc2sys -n 1 --no-pager 2>/dev/null | \
grep -oP 'phc offset\s+\K[-0-9]+')
# Determine status
ABS_OFFSET=${OFFSET#-}
if [ "${ABS_OFFSET:-9999999}" -gt 100000 ]; then
STATUS="CRITICAL"
elif [ "${ABS_OFFSET:-9999999}" -gt 10000 ]; then
STATUS="WARNING"
else
STATUS="OK"
fi
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%S.%6NZ)
echo "${TIMESTAMP},${OFFSET:-UNKNOWN},${DELAY:-UNKNOWN},${PHC_OFFSET:-UNKNOWN},${STATUS}" >> "$LOG_FILE"
# Alert on CRITICAL
if [ "$STATUS" = "CRITICAL" ]; then
# Trigger trading halt - implementation depends on your system
systemctl stop trading-engine
# Send alert
logger -t clock_monitor "CRITICAL: PTP offset ${OFFSET}ns exceeds 100µs threshold - trading halted"
fi
sleep 1
done
The output of this script is the primary evidence artifact for RTS-25 audits. Six months of this CSV, with all entries showing offsets under 100µs, satisfies the requirement. The script also implements the trading halt required by RTS-25 Article 2: trading must stop if synchronization is lost.
How MiFID II Requirements Translate to Crypto Firms
The current state of crypto clock sync regulation, as of 2026:
ADGM (Abu Dhabi Global Market) FSRA: The Markets Infrastructure Rulebook requires “appropriate” timestamp accuracy for multilateral trading facilities and organized trading facilities. There is no explicit nanosecond requirement, but the FSRA has indicated that HFT-equivalent systems should apply standards comparable to RTS-25 category 2 (1ms granularity). For licensed trading firms, implementing PTP with 1ms-or-better accuracy and documented drift monitoring is the safe approach.
DIFC (Dubai International Financial Centre): DFSA’s Conduct of Business Rulebook requires transaction record-keeping with “sufficient” timestamp precision. In practice, DFSA inspectors have been applying RTS-25 by analogy when examining algorithmic trading operations. Expect 1ms requirements for algorithmic systems.
MiCA (EU, effective 2024-2025): Markets in Crypto Assets regulation applies to CASPs (Crypto Asset Service Providers) operating in the EU. MiCA imports the timestamp requirements from MiFID II for regulated exchanges and asset managers. If you are a CASP with EU clients, MiFID II clock requirements apply.
SEC/CFTC (US): Not yet explicitly mandated at the precision level of RTS-25, but CFTC has moved toward microsecond-precision timestamps for swap data reporting. Direction of travel is toward RTS-25 parity.
The practical implementation recommendation for any regulated crypto trading firm today:
Tier 1 (HFT, algorithmic, > 1 msg/second):
PTP hardware timestamping → CLOCK_TAI → 1µs precision audit logs
Evidence: phc2sys logs, pmc offset logs, topology diagram
Target: < 100µs UTC divergence
Tier 2 (manual/semi-automated, < 1 msg/second):
NTP with chrony → CLOCK_TAI → 1ms precision audit logs
Evidence: chronyc tracking logs, topology diagram
Target: < 1ms UTC divergence
All tiers:
Trading halt if sync lost
Audit log retention: 5 years minimum
Timestamp field: UTC with TAI source documented
The Reporting Pipeline
Regulators do not just care about your clock being accurate - they care about the audit record format. A transaction record that satisfies RTS-25 for a category 1 firm:
{
"trade_id": "GEM-20260120-183045-001",
"instrument": "BTC-USDT-PERP",
"side": "BUY",
"quantity": "1.000000",
"price": "43250.00",
"venue": "GEMINI-PERP",
"execution_timestamp": {
"utc": "2026-01-20T18:30:45.123456Z",
"precision_us": 1,
"source": "PTP_HARDWARE",
"clock_id": "CLOCK_TAI",
"tai_offset_s": 37,
"max_drift_ns": 85
},
"order_received_timestamp": "2026-01-20T18:30:45.087234Z",
"venue_ack_timestamp": "2026-01-20T18:30:45.125891Z"
}
The max_drift_ns field is the maximum observed offset-from-master during the 24 hours preceding this transaction, pulled from the drift monitoring log. This is what allows a regulator to bound the absolute timestamp error for any given transaction.
How This Breaks in Production
Failure 1: PTP grandmaster losing GPS signal and going holdover. Your local GPS receiver loses satellite lock (building obstruction, antenna damage). The stratum-1 server enters holdover mode - it continues serving PTP but is no longer GPS-disciplined, drifting at its oscillator’s natural rate (~1µs/second on a good OCXO, ~100µs/second on a commodity oscillator). After 10 minutes of holdover, a commodity oscillator has drifted 1ms - your RTS-25 category 1 compliance is gone. Fix: monitor GPS lock status separately from PTP sync status. Alert immediately on GPS lock loss, not when drift exceeds threshold (by then you are already non-compliant).
Failure 2: Transaction timestamp injected at wrong point in the lifecycle. You inject the timestamp when the order is submitted to the venue, not when it is received by your system. RTS-25 requires the timestamp of the “reportable event” - which for an order is when it enters your system, not when it is sent to the exchange. The difference can be hundreds of microseconds for a complex order routing path. Fix: timestamp at reception, before any processing. Pass timestamps through the processing chain rather than re-reading the clock at submission.
Failure 3: Leap second handling not tested. On the day of a leap second insertion, your CLOCK_REALTIME-based audit log shows timestamps that go: ...23:59:59.999999 → 23:59:59.000000 → 00:00:00.000001. The smear on AWS turns this into a continuous drift. Transaction records in this window have timestamps that cannot be ordered without external reference. Fix: use CLOCK_TAI in your audit log (no leap seconds, ever). Document the TAI offset in each record so anyone reading the log can convert to UTC.
Failure 4: Monitoring alert threshold set too high. Clock drift monitoring alerts at 1ms divergence, but your compliance requirement is 100µs. By the time the alert fires, you have been non-compliant for 900µs. Fix: set monitoring thresholds conservatively below the regulatory requirement. For RTS-25 category 1, alert at 10µs and halt trading at 100µs - not alert at 100µs and halt at 1ms.
Failure 5: Audit log retention system applying compression or deduplication. A log management system deduplicates identical log lines. Two consecutive orders with the same prices and quantities produce one log entry. The transaction count in your audit trail does not match your execution system’s records. Regulators require complete records of every transaction. Fix: every transaction record must have a unique identifier (trade ID + sequence number) that prevents deduplication. Validate audit log count against execution system count daily.
Failure 6: Multiple servers with different TAI offset values. Server A was configured with phc2sys -O 37 in 2024. Server B was deployed in 2026 with the correct offset of 37 (no new leap seconds since 2016 as of this writing). But if a leap second were inserted between deployments, Server A would be wrong. Fix: automate TAI offset from the IERS bulletin. Never hardcode it in a config file without a comment pointing to the update procedure.
Related reading: PTP in Production: Solarflare Hardware Timestamping covers the hardware implementation. Why Trading Time Is Different: TSC and CLOCK_MONOTONIC covers the clock source selection for your application code.
Continue Reading
Enjoyed this?
Get one deep infrastructure insight per week.
Free forever. Unsubscribe anytime.
You're in. Check your inbox.