When to Use
Use this skill when standing up or auditing the time-synchronization stack itself on a
Linux trading host: NIC hardware timestamping, ptp4l (grandmaster → PTP Hardware Clock),
phc2sys (PHC → CLOCK_REALTIME), and the telemetry that evidences all three are working.
PTP (IEEE 1588) exists here because software NTP over UDP carries millisecond-scale OS scheduler jitter and cannot evidence a microsecond-scale bound. Hardware timestamping on a PTP-capable NIC takes the timestamp at the wire, outside the kernel network stack.
PtpClockSyncManager in scripts/ parses ptp4l/phc2sys output and returns a
fail-closed sync verdict. It is a telemetry reader, not a controller.
When NOT to Use
- As the enforcement point. This module reports; it does not halt trading. Threshold
escalation, holdover grace and the latched kill-switch belong to
clock-drift-monitoring-alerting-thresholds. Do not build a second, divergent set of thresholds here. - As proof that timestamps are correct. A perfectly synchronized clock on the wrong timescale reads as green — see the TAI/UTC pitfall below. Offset telemetry measures agreement with the source, never correctness of the source or the timescale.
- To repair already-written timestamps. That is
clock-skew-correction-for-tick-timestamps. - As a substitute for the RTS 25 Article 4 traceability system. Article 4 requires a documented, annually reviewed traceability system — design, functioning, specifications, and the exact point at which each timestamp is applied. This is evidence produced inside that system, not the system.
- On a host without a PTP-capable NIC. Software timestamping (
ptp4l -S) degrades accuracy by orders of magnitude and cannot support a 100 µs claim. If the NIC has no PHC, the answer is different hardware, not different flags. - Under concurrency, unguarded.
PtpClockSyncManageris not thread-safe. Drive it from one reader loop or wrap it in your own lock.
Prerequisites
- A PTP-capable NIC. Confirm with
ethtool -T <iface>— you needSOF_TIMESTAMPING_TX_HARDWARE,SOF_TIMESTAMPING_RX_HARDWARE,SOF_TIMESTAMPING_RAW_HARDWAREand a non-nonePTP Hardware Clock index. This is a hardware property; no flag substitutes for it. linuxptp(ptp4l,phc2sys) installed, and the grandmaster's PTP profile documented: transport, domain number, delay mechanism and message rates. Every one of these must match or the port never leavesLISTENING.- Physical path to the grandmaster whose asymmetry you can characterise. PTP assumes a symmetric path; every nanosecond of asymmetry becomes half a nanosecond of fixed offset error that the servo cannot see and will never correct.
- An out-of-band UTC reference for periodic timescale verification (see Pitfalls).
- The divergence ceiling that binds your activity and jurisdiction — not a default.
See
references/standards.md.
Workflow
-
Verify the hardware before writing any config.
ethtool -T <iface>. If the PTP Hardware Clock index isnone, stop: everything downstream is software timestamping wearing a PTP costume. -
Take the transport and domain from the grandmaster, not from a tutorial.
linuxptpdefaults tonetwork_transport UDPv4;-2selects IEEE 802.3 (L2). Neither is universally right — the Enterprise Profile (RFC 9760) mandates UDP over IPv4/IPv6, while the ITU-T telecom profile G.8275.1 uses L2 multicast. Choosing-2because a document said so, against a UDP grandmaster, produces a host that never synchronizes.domainNumbermust likewise match; a mismatched domain looks identical to "no grandmaster on the segment". -
Run
ptp4lclient-only with hardware timestamping.-His already the linuxptp default; state it anyway so a config change cannot silently demote you.-senablesclientOnly(the oldslaveOnlyspelling is deprecated) and is what stops a trading host from ever advertising itself as a time source to its own venue segment. -
Run
phc2syswith-w, and understand why.ptp4lkeeps the PHC on the PTP timescale (TAI), which has no leap seconds. Perphc2sys(8),-w(absent-O) keeps the sink-to-source offset updated from thecurrentUtcOffsetobtained fromptp4l. Omit both andCLOCK_REALTIMEis disciplined onto TAI — a whole number of seconds away from UTC — while every offset reads in the single nanosecond digits. -
Confirm the port state transition, not just the offset. A healthy client walks
INITIALIZING → LISTENING → UNCALIBRATED → SLAVE, and the servo walkss0(unlocked) →s1(clock step) →s2(locked). An offset printed ins0is a measurement, not a synchronization; treat it as unlocked no matter how small the number. -
Feed both daemons' output to
PtpClockSyncManager, and setmax_sample_age_s. The manager's state is sticky by construction: with staleness detection off, a daemon that dies leaves the lasts2and the last good offset latched forever and the host keeps reporting compliant with no time synchronization at all. Set the age to your log interval times a tolerance. -
Judge on
combined_offset_ns, notmax_offset_ns. The grandmaster→PHC offset and the PHC→CLOCK_REALTIMEoffset are serial error terms: an event stamped fromCLOCK_REALTIMEcarries both. 60 µs on each leg is 120 µs on the record while the maximum reads a comfortable 60 µs. -
Verify the timescale out of band, on a schedule. Compare
CLOCK_REALTIMEagainst an independent UTC source. This is the only check that catches a correct-looking sync to a wrong or spoofed grandmaster, and it is the check the offset telemetry structurally cannot perform.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
phc2syswithout-wor-O: a 37-second error that reports as zero. The PHC is on TAI. Without the UTC offset,CLOCK_REALTIMEis disciplined to TAI and every timestamp the firm records is a whole number of seconds from UTC — a gross RTS 25 failure — whileptp4l,phc2sysand this module all report nanosecond agreement. This is the single most dangerous way to pass every green check and be entirely non-compliant.- Taking the maximum of the two offsets. They are serial, not alternatives. The maximum is not a bound on the error of the recorded timestamp; the sum of magnitudes is.
- Treating a silent daemon as a healthy one. The common failure is not a drifting
clock, it is a dead
ptp4l. A parser with no staleness deadline never notices, because nothing arrives to trigger it. - A parser that drops the lines that matter.
path delaycan be negative in real logs, and a negative path delay is a symptom of bad hardware timestamps. A regex that requires unsigned digits rejects the whole line, so the manager keeps serving the last good sample and the fault is invisible. The same applies tomessage_tag, which most orchestrated deployments set — it inserts text between the daemon timestamp and the keyword and silently blinds a rigid pattern. - Reading
rmsfrom a summary line as if it were an offset.summary_intervalandphc2sys -uemitrms N max Mlines with no servo state. RMS is an average magnitude; the divergence ceiling is a bound on the worst case, so comparemax. - Running
ptp4lwithout-s/clientOnly. A trading host that wins a BMCA election starts serving time to the segment from its own free-running oscillator. - Forgetting
phc2sysentirely.ptp4lalone disciplines the NIC's PHC. If the application stamps fromCLOCK_REALTIME, that clock is still free-running. - Assuming
-2because the document said so. Transport, domain and delay mechanism are properties of the grandmaster's profile. Guessing produces a port stuck inLISTENING, which is at least loud — guessing the domain on a shared segment can instead sync you to the wrong grandmaster, which is not. - Ignoring path asymmetry. PTP cannot observe it. A different fibre length or an asymmetric switch path in each direction is a fixed, uncorrectable offset that no amount of servo tuning removes, and it does not appear in the offset telemetry.
- Assuming 100 µs is "the clock rule". It is one row of one EU table. Non-HFT EU algo flow is bound to 1 ms; a US CAT reporter is bound by FINRA Rule 6820 to 50 ms — 500× looser. Enforcing 100 µs on flow it does not bind manufactures outages, not compliance.
Verification
- Parse
ptp4l[600.1]: [ens1f0] master offset 88 s2 freq -25937 path delay -2391; confirm the sample parses, withpath_delay_ns == -2391. A rigid parser drops this line. - Feed 61,000 ns from
ptp4land 45,000 ns fromphc2sys, boths2; confirmmax_offset_ns == 61000,combined_offset_ns == 106000andmifid_compliant is False. - Feed only
phc2systelemetry; confirmis_synced is Falseand reasonno ptp4l telemetry— never a passing verdict on half a stack. - With
max_sample_age_s=5.0, feed a locked pair, advance the injected clock past the deadline, and confirm the verdict flips to non-compliant withtelemetry_stale. - Feed
port 1: SLAVE to FAULTY on FAULT_DETECTEDafter a locked pair; confirm the verdict fails on the port state even though the last offsets were small. - Run
python -m unittest discover -s skills/clock-synchronization-ptp-for-trading-hosts/scripts.