Trading bot clock synchronization: fix timestamp errors
A trading request can have the right key, signature, symbol, and quantity yet still be rejected because its timestamp is stale or ahead of the exchange. Clock problems are easy to dismiss as random API failures, but they can leave an exit unsent. This runbook explains why signed requests depend on time, how to measure server offset without fooling yourself, and when the bot should pause.
Why signed trading requests need accurate time
Many private exchange endpoints require a timestamp or nonce inside the signed request. The venue checks both the signature and freshness. That prevents an intercepted instruction from being replayed much later. If the client's clock is too far ahead, too far behind, or the request waited too long between signing and arrival, the freshness check fails.
Clock-related failures have several sources:
- host drift — the server clock gradually diverges because synchronization is disabled or unhealthy;
- clock step — virtualization, suspend/resume, or a time service moves wall time abruptly;
- queue age — a request is signed, then sits behind other work before it is sent;
- network delay — routing or venue latency consumes the freshness window;
- unit errors — seconds are sent where milliseconds are expected, or a value is rounded incorrectly;
- stale offset — the client measured venue time once and kept that correction indefinitely.
These are authentication failures, not trading signals. Retrying the same already-signed payload only makes it older. Generate a fresh timestamp and signature for each attempt, subject to the safe retry rules in trading bot error handling.
Measure server offset and network uncertainty
If a venue exposes public server time, take a local timestamp immediately before the request and immediately after the response. The midpoint of those local readings is a better estimate than the receive time alone:
offset estimateround_trip = local_after - local_before
local_midpoint = (local_before + local_after) / 2
estimated_offset = venue_time - local_midpoint
uncertainty is at least about half the round trip
Take several samples and prefer a low-latency median rather than trusting one slow response. Store offset and round-trip time as separate metrics. A small estimated offset with huge round-trip uncertainty is not a clean health signal.
| Observation | Likely cause | Next check |
|---|---|---|
| Offset grows steadily | Host synchronization is unhealthy | Time-service status and peer reachability |
| Offset jumps suddenly | Wall clock stepped or host resumed | System and virtualization events |
| Offset stable, failures during load | Signed requests age in a queue | Sign-to-send and send-to-response latency |
| Only one venue fails | Venue window, units, or endpoint behavior | Current official API documentation |
| All venues fail together | Host time or network path | Local synchronization and routing |
Setting the display timezone to UTC avoids conversion mistakes, but it does not keep the clock correct. A UTC clock can still be several seconds wrong. Monitor actual offset and the health of the host time service.
Design rules that prevent drift failures
- Synchronize every host. Use the operating system's supported time service with multiple reliable peers, then monitor whether it is actually synchronized.
- Sign immediately before send. Do not build signed requests in a queue. Queue the intent, then timestamp and sign at the network boundary.
- Use wall time only where required. Signed calendar timestamps need wall time; elapsed durations and cooldowns should use a monotonic clock that cannot move backward.
- Measure venue offset. Refresh it periodically and after reconnect, resume, migration, or a timestamp rejection.
- Set a drift threshold. Warn well before the venue rejects requests; pause new exposure if reliable authenticated execution is in doubt.
- Keep freshness windows intentional. A larger receive window can tolerate latency, but it should not be used to conceal a broken clock or overloaded queue.
- Keep clocks out of strategy bars. Use exchange event timestamps consistently for candles and fills, while preserving receive time for latency analysis.
Never call wall time to measure a timeout. If the system clock steps backward, a wall-time timeout may last far too long; if it steps forward, it may fire immediately. Monotonic timers are appropriate for request deadlines, heartbeat age, cooldowns, and retry backoff.
Timestamp-error incident runbook
Repeated timestamp rejections can mean the bot cannot reliably manage exposure. Use a defined response:
- pause new entries at the order gateway;
- record the venue error, local wall time, monotonic latency, queue age, and last known offset;
- discard stale signed payloads and stop blind retry loops;
- verify host time-service synchronization without changing unrelated system settings;
- sample the venue time endpoint and estimate offset plus round-trip uncertainty;
- check timestamp units and confirm signing happens immediately before send;
- send a safe authenticated read, then reconcile orders and positions;
- resume only after several healthy samples and successful account reads.
If risk-reducing orders also require authenticated timestamps, the issue belongs in the highest operational alert tier. A separate broker interface or server-side protective order may provide defense in depth, but never assume a local stop can act while authentication is broken. Connect repeated clock failures to the trading bot kill switch.
Monitor the complete time path
A single “system clock okay” metric is not enough. Track:
- host synchronization state and estimated local offset;
- venue server-time offset and round-trip time;
- age from intent creation to signing and from signing to network send;
- timestamp rejection count by venue and endpoint;
- request queue depth and oldest item age;
- wall-clock step or host resume events;
- last successful authenticated read, order action, and reconciliation.
In a test adapter, inject an ahead clock, a behind clock, a sudden time jump, long queue delays, seconds-versus-milliseconds mistakes, and slow venue responses. Verify that monotonic deadlines still work, payloads are re-signed, the bot does not duplicate orders, and new entries remain paused until state is known. Also combine the test with simulated API throttling; rate-limit queues are a common source of stale signed requests.
Freshness is a security control against replay. Repair synchronization and request timing, then use the venue's supported window as documented.
Frequently asked questions
Why do exchanges reject trading bot timestamps?
Authenticated requests often include a timestamp so old signed messages cannot be replayed. A venue can reject the request when the bot's timestamp falls outside its accepted window because of clock drift, a wall-clock jump, queue delay, or network latency.
Should a bot just increase its exchange timestamp window?
A larger allowed window may hide small latency but should not replace accurate time. Oversized windows weaken freshness checks and do not fix a drifting host or long internal queue. Synchronize the host, measure server offset, and keep the window no larger than the venue and application require.
What is the difference between wall time and monotonic time in a trading bot?
Wall time represents a calendar timestamp used for signed API requests and can jump when the system clock is corrected. Monotonic time only moves forward and is better for durations, cooldowns, order timeouts, and latency measurement.
What should a bot do after repeated timestamp errors?
Pause new entries, preserve risk-reducing actions when possible, check host synchronization and server offset, clear stale queued requests, verify network latency, create fresh signatures immediately before send, and resume only after several healthy checks and account reconciliation.