Each timestamp Massive returns marks a different moment in the trade's journey, so picking the wrong one shifts your whole series by a measurable amount rather than breaking anything visibly. Which field carries which moment, and what unit it is in, both depend on how you are reading the data, so the table below is per feed rather than universal.
Applies to
- Plans: a Stocks subscription. Other asset classes follow the same pattern with fewer fields; the options trades example below needs an Options plan that includes trades.
- Endpoints: /v3/trades/{ticker}, /v3/quotes/{ticker} and the flat-file trade and quote datasets; the T and Q channels on the /stocks WebSocket cluster.
- Asset classes: US stocks. Options and futures use nanosecond timestamps on REST too, but options trades older than the last few sessions are only precise to the millisecond, as described below.
What each timestamp measures
Each timestamp measures a different moment in the trade's journey, and the gaps between them are somebody else's processing rather than ours:
| Field on REST | Field on the WebSocket | What it measures |
|---|---|---|
| participant_timestamp | pt | When the exchange or reporting facility generated the event |
| sip_timestamp | t | When the SIP received it from the exchange |
| trf_timestamp | trft | When the Trade Reporting Facility received it, on off-exchange prints |
pt is always at or before t, because the SIP receives an event after the exchange produces it. So the difference between them is the SIP's own latency, which sits upstream of Massive entirely.
The unit differs by feed
The unit differs by feed, and this is the part that produces wrong answers. REST and the flat files use nanoseconds; the WebSocket uses milliseconds:
REST sip_timestamp 1788566398110367271 nanoseconds since the epoch
WS t 1788566398110 milliseconds since the epoch
Dividing by the wrong power of ten either drops your event onto 1 January 1970 or throws it tens of thousands of years into the future, which is usually obvious. What is not obvious is mixing the two feeds in one series, so normalize to a single unit as soon as you parse.
Options trade timestamps on REST
Options trade timestamps on Massive's REST trades endpoint are nanosecond values, but only trades from the most recent sessions carry nanosecond detail. Those trades have all nine digits after the second and a non-zero sequence_number. Older options trades have a sip_timestamp that ends in six zeros, which makes it a whole millisecond, a sequence_number of 0, and a participant_timestamp equal to sip_timestamp. The window of full-precision sessions moves forward as new sessions trade, so a session's options trades change from the first form to the second a few days after it closes.
curl -X GET "https://api.massive.com/v3/trades/O:AAPL240614C00195000?timestamp=2024-06-03&order=asc&limit=3&sort=timestamp&apiKey=YOUR_API_KEY"
Response
{
"results": [
{
"exchange": 308,
"participant_timestamp": 1717421400788000000,
"price": 2.95,
"sequence_number": 0,
"sip_timestamp": 1717421400788000000,
"size": 1
},
{
"exchange": 308,
"participant_timestamp": 1717421402273000000,
"price": 2.86,
"sequence_number": 0,
"sip_timestamp": 1717421402273000000,
"size": 5
},
{
"exchange": 308,
"participant_timestamp": 1717421405248000000,
"price": 2.86,
"sequence_number": 0,
"sip_timestamp": 1717421405248000000,
"size": 3
}
],
"status": "OK"
}
These are the first three trades of 3 June 2024 on an AAPL call that expired on 14 June 2024. 1717421400788000000 is 09:30:00.788 Eastern that day, and every value ends in six zeros. The participant_timestamp repeats the sip_timestamp, so the exchange-to-SIP latency cannot be measured on older options trades.
What this means for working with options trades:
- Check sequence_number to tell the two forms apart. A value of 0 means the row's timestamps are whole milliseconds; a non-zero value means they carry full nanoseconds.
- Do not drop options trades that look identical. Two rows on the same exchange in the same millisecond, at the same price and size, both count in the day bar's volume and number of transactions, so removing one makes your totals disagree with the bar.
- The options trades flat files hold whole-millisecond sip_timestamp values even for recent sessions, and they have no sequence_number or participant_timestamp column.
- Options quotes keep their nanosecond sip_timestamp and their sequence_number in older sessions too.
How to see all three on one trade
Ask for a trade that went through a reporting facility, because an exchange print carries only two of the three:
curl -X GET "https://api.massive.com/v3/trades/AAPL?timestamp.gte=1789042200000000000&limit=1&order=asc&sort=timestamp&apiKey=YOUR_API_KEY"
Response
{
"results": [
{
"conditions": [12, 37],
"exchange": 4,
"participant_timestamp": 1789042201676773107,
"trf_timestamp": 1789042201685083268,
"sip_timestamp": 1789042201685102864,
"trf_id": 202,
"price": 317.6537,
"size": 0,
"decimal_size": "0.000001",
"tape": 3
}
],
"status": "OK"
}
Read them in that order and the journey is visible: the venue stamped it, the reporting facility received it 8.31 milliseconds later, and the SIP 19.6 microseconds after that. The two gaps are three orders of magnitude apart, which is the point: the reporting hop is where the time goes, not the consolidation. A trade printed on an exchange rather than through a facility has no trf_timestamp at all, so treat the field as optional rather than absent-means-broken.
How to convert one
Every language has this built in. There is no need for a converter website:
from datetime import datetime, timezone
ns = 1788566398110367271
print(datetime.fromtimestamp(ns / 1_000_000_000, timezone.utc))
Keep the value as an integer for storage and comparison, and convert only for display. Converting to a local time zone early is how a trade ends up on the wrong session.
If you see an error
A timestamp that decodes to 1970 means you read a millisecond value as if it were nanoseconds: the WebSocket's 1788566398110 divided by 1e9 is 1,788 seconds after the epoch. A timestamp far in the future is the opposite mistake, a nanosecond value read as milliseconds or as seconds.
A trade that appears to be in the wrong session is usually a local time-zone conversion. All Massive timestamps are UTC; the US equities session is 09:30 to 16:00 Eastern, which is 14:30 to 21:00 UTC outside daylight saving and 13:30 to 20:00 UTC inside it.
trf_timestamp of 0, or the field being absent, means the trade was executed on an exchange rather than reported through a facility. That is normal.

