Time synchronization is critical for trading systems. After building infrastructure synchronizing 100+ servers to within 100 nanoseconds, I've learned that PTP (Precision Time Protocol) and GPS are essential for sub-microsecond accuracy. This article covers production time sync.
Critical use cases:
Poor time sync = wrong latency metrics, compliance failures, trading errors.
1# Install PTP tools
2sudo apt-get install linuxptp
3
4# Configure network interface for hardware timestamping
5sudo ethtool -T eth0
6# Should show hardware-transmit and hardware-receive support
7
8# PTP daemon configuration /etc/ptp4l.conf
9[global]
10slaveOnly 1
11priority1 128
12priority2 128
13domainNumber 0
14clockClass 248
15clockAccuracy 0xFE
16offsetScaledLogVariance 0xFFFF
17free_running 0
18freq_est_interval 1
19dscp_event 46
20dscp_general 34
21network_transport UDPv4
22delay_mechanism E2E
231# Start PTP daemon (hardware timestamps)
2sudo ptp4l -i eth0 -m -H -f /etc/ptp4l.conf
3
4# Monitor PTP status
5sudo pmc -u -b 0 'GET TIME_STATUS_NP'
6
7# Expected offset: < 100 ns
8# Example output:
9# master_offset 45 ns
10# path_delay 1234 ns
11# freq_adjustment -2.5 ppb
121# Synchronize system clock to PTP hardware clock
2sudo phc2sys -s eth0 -c CLOCK_REALTIME -m -w
3
4# Monitor sync quality
5watch -n1 'sudo phc2sys -s eth0 -c CLOCK_REALTIME -m -q | grep "offset"'
61# /etc/chrony/chrony.conf
2# Use pool of NTP servers
3pool time.google.com iburst maxsources 4
4pool time.cloudflare.com iburst maxsources 2
5
6# GPS reference (if available)
7refclock SHM 0 offset 0.5 delay 0.2 refid NMEA
8
9# Allow local network sync
10allow 192.168.0.0/16
11
12# Log settings
13logdir /var/log/chrony
14log tracking measurements statistics
15
16# Harden system clock
17maxdistance 0.1
18maxdelay 0.1
19makestep 0.001 3
201# Check sync status
2chronyc tracking
3
4# Example output:
5# Offset : +0.000023456 s
6# Last offset: +0.000012345 s
7# RMS offset : 0.000034567 s
8
9# View sources
10chronyc sources -v
11
12# Statistics
13chronyc sourcestats
141#include <time.h>
2#include <stdint.h>
3
4class TimestampClock {
5public:
6 // Get current time in nanoseconds
7 static uint64_t now_ns() {
8 struct timespec ts;
9 clock_gettime(CLOCK_REALTIME, &ts);
10 return static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + ts.tv_nsec;
11 }
12
13 // Get monotonic time (for intervals)
14 static uint64_t monotonic_ns() {
15 struct timespec ts;
16 clock_gettime(CLOCK_MONOTONIC, &ts);
17 return static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + ts.tv_nsec;
18 }
19
20 // Get TAI time (no leap seconds)
21 static uint64_t tai_ns() {
22 struct timespec ts;
23 #ifdef CLOCK_TAI
24 clock_gettime(CLOCK_TAI, &ts);
25 #else
26 clock_gettime(CLOCK_REALTIME, &ts);
27 // Add TAI offset (currently 37 seconds)
28 ts.tv_sec += 37;
29 #endif
30 return static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + ts.tv_nsec;
31 }
32};
33
34// Latency measurement
35class LatencyTracker {
36private:
37 uint64_t start_time_;
38
39public:
40 void start() {
41 start_time_ = TimestampClock::monotonic_ns();
42 }
43
44 uint64_t elapsed_ns() const {
45 return TimestampClock::monotonic_ns() - start_time_;
46 }
47
48 double elapsed_us() const {
49 return elapsed_ns() / 1000.0;
50 }
51};
521use std::time::{SystemTime, UNIX_EPOCH};
2
3pub struct TimestampClock;
4
5impl TimestampClock {
6 pub fn now_ns() -> u64 {
7 let duration = SystemTime::now()
8 .duration_since(UNIX_EPOCH)
9 .expect("Time went backwards");
10
11 duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64
12 }
13
14 pub fn now_us() -> u64 {
15 Self::now_ns() / 1_000
16 }
17
18 // Using libc for higher precision
19 pub fn clock_realtime_ns() -> u64 {
20 unsafe {
21 let mut ts = libc::timespec {
22 tv_sec: 0,
23 tv_nsec: 0,
24 };
25 libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts);
26 (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
27 }
28 }
29}
30
31#[derive(Debug)]
32pub struct LatencyMeasurement {
33 start: u64,
34}
35
36impl LatencyMeasurement {
37 pub fn start() -> Self {
38 LatencyMeasurement {
39 start: TimestampClock::now_ns(),
40 }
41 }
42
43 pub fn elapsed_ns(&self) -> u64 {
44 TimestampClock::now_ns() - self.start
45 }
46
47 pub fn elapsed_us(&self) -> f64 {
48 self.elapsed_ns() as f64 / 1_000.0
49 }
50}
511import time
2import requests
3from dataclasses import dataclass
4from typing import List
5
6@dataclass
7class ClockSample:
8 local_time: float
9 remote_time: float
10 rtt: float # Round-trip time
11
12 @property
13 def offset(self) -> float:
14 """Estimated clock offset (accounting for RTT)."""
15 return self.remote_time - (self.local_time + self.rtt / 2)
16
17class ClockSkewMonitor:
18 """Monitor clock skew across distributed systems."""
19
20 def __init__(self, endpoints: List[str]):
21 self.endpoints = endpoints
22 self.samples = {ep: [] for ep in endpoints}
23
24 def measure_skew(self, endpoint: str) -> ClockSample:
25 """Measure clock offset to remote system."""
26 t1 = time.time()
27
28 response = requests.get(f"{endpoint}/time")
29
30 t2 = time.time()
31 rtt = t2 - t1
32
33 remote_time = float(response.text)
34
35 sample = ClockSample(
36 local_time=t1,
37 remote_time=remote_time,
38 rtt=rtt
39 )
40
41 self.samples[endpoint].append(sample)
42
43 return sample
44
45 def check_all(self) -> dict:
46 """Check skew to all endpoints."""
47 results = {}
48
49 for endpoint in self.endpoints:
50 sample = self.measure_skew(endpoint)
51
52 results[endpoint] = {
53 'offset_ms': sample.offset * 1000,
54 'rtt_ms': sample.rtt * 1000,
55 'status': 'OK' if abs(sample.offset) < 0.001 else 'WARN'
56 }
57
58 return results
59
60# Example usage
61if __name__ == "__main__":
62 monitor = ClockSkewMonitor([
63 'http://server1:8080',
64 'http://server2:8080',
65 'http://server3:8080'
66 ])
67
68 results = monitor.check_all()
69
70 for endpoint, metrics in results.items():
71 print(f"{endpoint}: offset={metrics['offset_ms']:.3f}ms, "
72 f"RTT={metrics['rtt_ms']:.3f}ms [{metrics['status']}]")
73Our time sync infrastructure (2020-2024):
Configuration Mean Offset Std Dev Max Offset
─────────────────────────────────────────────────────────
NTP (software) 250 μs 180 μs 1.2 ms
Chrony (software) 85 μs 45 μs 320 μs
PTP (hardware) 45 ns 25 ns 180 ns
GPS + PTP 12 ns 8 ns 45 ns
Requirement (MiFID II) Our System
───────────────────────────────────────
Gateway timestamp ±100 μs ±45 ns ✓
Matching engine ±100 μs ±25 ns ✓
Trade reporting ±1 ms ±85 μs ✓
After 4+ years managing production time sync:
Time synchronization is foundational infrastructure. Invest early, test thoroughly, monitor constantly.
Master time sync—it's the invisible foundation of reliable trading systems.
Technical Writer
NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.
Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.