Does Massive have a continuous futures contract, a single ticker such as NQ1! that follows a product from one expiry to the next? Not today. The Massive Futures API has no continuous, back-adjusted or product-level price series, and a request for bars on a product code such as NQ, or on a charting symbol such as NQ1!, returns HTTP 200 with an empty results array rather than an error, which makes the missing series easy to mistake for missing data. The futures product page lists Continuous Contracts as coming soon.
Applies to
- Plans: every Futures plan. The contracts and aggregates endpoints are included in all of them.
- Endpoints or feeds: /futures/v1/contracts, /futures/v1/aggs/{ticker}
- Asset classes: futures
What a futures product code or NQ1! returns
A futures product code or a charting symbol such as NQ1! returns no bars, because the futures aggregates endpoint takes a contract ticker. The aggregates documentation describes that ticker as the base symbol plus the contract expiration, for example GCJ5 for the April 2025 gold contract. Both of these requests return HTTP 200 and nothing else:
curl -X GET "https://api.massive.com/futures/v1/aggs/NQ?resolution=1day&window_start.gte=2026-09-01&limit=3&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/NQ1%21?resolution=1day&window_start.gte=2026-09-01&limit=3&apiKey=YOUR_API_KEY"
Response, the same for both:
{
"results": [],
"status": "OK"
}
NQ1%21 is NQ1! with the ! URL-encoded. An empty array here means the ticker named a product rather than a contract, not that data is missing.
How to find the futures contract that was trading on a date
The futures contract that most of a product's trading had moved to on a date is the one with the most volume in that session, and it is not always the contract that expires first. Listing the product's contracts and comparing their daily volume finds it.
- List the product's contracts that were tradeable on the date. Pass product_code, date and active=true: a list filtered by date alone can also include contracts that expired before that date, with a negative days_to_maturity. The contracts endpoint cannot sort by expiry, so last_trade_date.lte keeps the list to the nearest few contracts.
curl -X GET "https://api.massive.com/futures/v1/contracts?product_code=MGC&date=2026-08-04&active=true&type=single&last_trade_date.lte=2026-12-31&sort=ticker.asc&apiKey=YOUR_API_KEY"
Response
{
"results": [
{ "ticker": "MGCQ6", "active": true, "days_to_maturity": 22, "last_trade_date": "2026-08-27" },
{ "ticker": "MGCV6", "active": true, "days_to_maturity": 84, "last_trade_date": "2026-10-28" },
{ "ticker": "MGCZ6", "active": true, "days_to_maturity": 146, "last_trade_date": "2026-12-29" }
],
"status": "OK"
}
On 4 August 2026 the MGC contract nearest to expiry was MGCQ6, whose last trade date was 27 August 2026. type=single removes spread contracts, such as MGCV6-MGCZ6, only for dates from 1 April 2026. For dates from 12 March 2025 to 31 March 2026 it returns them labeled single, and for dates before 12 March 2025 it returns no contracts at all, because the type field is null on those dates. For any date before 1 April 2026, leave type=single out and keep only the tickers in single-contract form, the product code followed by a month letter and year, such as MGCZ6.
- Request the daily bar for that session from each contract. A futures session opens the evening before the date it settles on, so the session that ended on 4 August 2026 is the one with window_start=2026-08-03.
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCQ6?resolution=1day&window_start=2026-08-03&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCV6?resolution=1day&window_start=2026-08-03&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCZ6?resolution=1day&window_start=2026-08-03&apiKey=YOUR_API_KEY"
Response, one line per request, in the same order:
{ "results": [ { "ticker": "MGCQ6", "session_end_date": "2026-08-04", "volume": 45, "close": 4077.1 } ], "status": "OK" }
{ "results": [ { "ticker": "MGCV6", "session_end_date": "2026-08-04", "volume": 27268, "close": 4103.9 } ], "status": "OK" }
{ "results": [ { "ticker": "MGCZ6", "session_end_date": "2026-08-04", "volume": 200401, "close": 4134.4 } ], "status": "OK" }
- Use the contract with the most volume. In the session that ended on 4 August 2026 that was MGCZ6, with 200,401 contracts. The nearest expiry, MGCQ6, traded 45, and the next month listed, MGCV6, traded 27,268. To build a longer series, repeat step 2 for each session and switch to whichever listed contract has the most volume, from the first session in which it passes the contract you hold.
Why a joined futures series jumps at each switch
A futures series built by joining one contract to the next jumps at each switch, because contracts on the same product trade at different prices. In the session that ended on 4 August 2026, MGCQ6 closed at 4077.1, MGCV6 at 4103.9 and MGCZ6 at 4134.4. Massive returns each contract's own prices with no adjustment between contracts. If you need a series without the jumps, choose an adjustment yourself, such as shifting the earlier contract's prices by the difference between the two closes at the switch, or scaling them by the ratio.
When trading moves to the next futures contract
Trading in a futures product moves to a later contract before the expiring contract's last trade date, and how long before differs by product. This table shows, for three recent expiries, the session in which the later contract's daily volume first passed the expiring contract's:
| Product | Expiring contract | Its last trade date | Later contract | Session the later contract passed it | Days before the last trade date |
|---|---|---|---|---|---|
| ES | ESM6 | 18 June 2026 | ESU6 | 15 June 2026 | 3 |
| MGC | MGCQ6 | 27 August 2026 | MGCZ6 | 29 July 2026 | 29 |
| NQ | NQU6 | 18 September 2026 | NQZ6 | 14 September 2026 | 4 |
The last trade dates come from the contracts endpoint:
curl -X GET "https://api.massive.com/futures/v1/contracts?ticker.any_of=NQU6,ESM6,MGCQ6&date=2026-06-01&sort=ticker.asc&apiKey=YOUR_API_KEY"
Response
{
"results": [
{ "ticker": "ESM6", "last_trade_date": "2026-06-18" },
{ "ticker": "MGCQ6", "last_trade_date": "2026-08-27" },
{ "ticker": "NQU6", "last_trade_date": "2026-09-18" }
],
"status": "OK"
}
The volumes come from daily bars for the session before the switch and the session of the switch. For ES:
curl -X GET "https://api.massive.com/futures/v1/aggs/ESM6?resolution=1day&window_start.gte=2026-06-11&window_start.lt=2026-06-15&sort=window_start.asc&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/ESU6?resolution=1day&window_start.gte=2026-06-11&window_start.lt=2026-06-15&sort=window_start.asc&apiKey=YOUR_API_KEY"
Response, one block per request, in the same order:
{ "results": [
{ "ticker": "ESM6", "session_end_date": "2026-06-12", "volume": 1184127 },
{ "ticker": "ESM6", "session_end_date": "2026-06-15", "volume": 579431 }
], "status": "OK" }
{ "results": [
{ "ticker": "ESU6", "session_end_date": "2026-06-12", "volume": 390961 },
{ "ticker": "ESU6", "session_end_date": "2026-06-15", "volume": 923610 }
], "status": "OK" }
For MGC:
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCQ6?resolution=1day&window_start.gte=2026-07-27&window_start.lt=2026-07-29&sort=window_start.asc&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCZ6?resolution=1day&window_start.gte=2026-07-27&window_start.lt=2026-07-29&sort=window_start.asc&apiKey=YOUR_API_KEY"
Response, one block per request, in the same order:
{ "results": [
{ "ticker": "MGCQ6", "session_end_date": "2026-07-28", "volume": 191057 },
{ "ticker": "MGCQ6", "session_end_date": "2026-07-29", "volume": 89559 }
], "status": "OK" }
{ "results": [
{ "ticker": "MGCZ6", "session_end_date": "2026-07-28", "volume": 23502 },
{ "ticker": "MGCZ6", "session_end_date": "2026-07-29", "volume": 244731 }
], "status": "OK" }
For NQ:
curl -X GET "https://api.massive.com/futures/v1/aggs/NQU6?resolution=1day&window_start.gte=2026-09-10&window_start.lt=2026-09-14&sort=window_start.asc&apiKey=YOUR_API_KEY"
curl -X GET "https://api.massive.com/futures/v1/aggs/NQZ6?resolution=1day&window_start.gte=2026-09-10&window_start.lt=2026-09-14&sort=window_start.asc&apiKey=YOUR_API_KEY"
Response, one block per request, in the same order:
{ "results": [
{ "ticker": "NQU6", "session_end_date": "2026-09-11", "volume": 444389 },
{ "ticker": "NQU6", "session_end_date": "2026-09-14", "volume": 312278 }
], "status": "OK" }
{ "results": [
{ "ticker": "NQZ6", "session_end_date": "2026-09-11", "volume": 27817 },
{ "ticker": "NQZ6", "session_end_date": "2026-09-14", "volume": 341924 }
], "status": "OK" }
For ES and NQ the later contract took over three and four days before the last trade date. For MGC it took over 29 days before, and the expiring MGCQ6 traded 45 contracts in the session that ended on 4 August 2026, more than three weeks before its own last trade date. So do not assume one fixed number of days for every product: compare the volume of the contracts for the product you follow.
Why this happens
Massive serves futures data per contract: the futures aggregates, trades and quotes endpoints each take one contract ticker, and the contracts endpoint lists the contracts for each product. A continuous series is built on top of those contracts by deciding when to switch from one contract to the next and whether to adjust prices at the switch, and Massive does not publish one today.
Futures bars thin out on an expiring contract in its last weeks, and are thin on a new contract before trading moves to it, because little of that contract trades then. The aggregates documentation says bars are built from the trades in each interval, so an interval with no trades has no bar. Compare the contract's daily volume with the other listed contracts' before you treat a thin stretch as missing data.
If you see an error
An empty results array with HTTP 200 for a product code such as NQ, or for a symbol such as NQ1!, means the request named a product rather than a contract. Fix: list the product's contracts with /futures/v1/contracts and request bars for a contract ticker.
An empty results array for a contract after a certain date means the contract had expired: its bars stop at its last session. The latest bar for MGCQ6 is the session that ended on its last trade date, 27 August 2026:
curl -X GET "https://api.massive.com/futures/v1/aggs/MGCQ6?resolution=1day&limit=1&apiKey=YOUR_API_KEY"
Response
{
"results": [
{ "ticker": "MGCQ6", "session_end_date": "2026-08-27", "volume": 2 }
],
"status": "OK"
}
A contracts list filtered by date alone can include contracts that had already expired on that date, with active false and a negative days_to_maturity, and then the contract with the smallest days_to_maturity is an expired one. On 4 September 2026 that is MGCQ6, at -12:
curl -X GET "https://api.massive.com/futures/v1/contracts?product_code=MGC&date=2026-09-04&type=single&last_trade_date.lte=2026-12-31&sort=ticker.asc&apiKey=YOUR_API_KEY"
Response
{
"results": [
{ "ticker": "MGCQ6", "active": false, "days_to_maturity": -12, "last_trade_date": "2026-08-27" },
{ "ticker": "MGCV6", "active": true, "days_to_maturity": 50, "last_trade_date": "2026-10-28" },
{ "ticker": "MGCZ6", "active": true, "days_to_maturity": 112, "last_trade_date": "2026-12-29" }
],
"status": "OK"
}
MGCQ6 stopped trading on 27 August 2026, eight calendar days earlier. days_to_maturity is not an exact calendar count from date: it can be a few days lower than the days to last_trade_date, most of all across a weekend or a holiday, and a contract can show a negative value on its own last trade date while it is still active. Count days from last_trade_date yourself when the exact number matters, and use active to tell whether a contract was trading.
Fix: add active=true, which keeps only the contracts that were tradeable on that date.
{"status":"ERROR","error":"Invalid query parameter: 'sort'. Expected one of [date, product_code, ticker], but found: 'days_to_maturity'."} with HTTP 400 means the contracts endpoint cannot sort by days_to_maturity; it sorts by date, product_code or ticker. Fix: narrow the list with last_trade_date.lte and compare last_trade_date yourself.

