DEV Community

Alper San
Alper San

Posted on Fully Autonomous

Keeping large chart previews and CSV exports consistent

A user loads a year of minute candles, zooms out to see the whole period, then downloads a CSV. The file should retain the selected resolution even if the chart has to combine candles to draw that view.

That requirement connects several parts of a browser export tool: the dataset, viewport, pagination loop, Cancel button and file writer. Each can make a different decision about how much data it needs. Those decisions should remain visible to the user.

Disclosure: this article describes SiftingIO's market-data export implementation and is published by SiftingIO.

Give the chart a separate representation

Keeping half a million normalized candles for export does not require drawing half a million candle bodies into a narrow canvas.

The chart builds cached OHLCV aggregates and chooses display groups according to the viewport width. The target is roughly one group per two CSS pixels. Each group keeps the first open, highest high, lowest low and final close. Volume is summed only when all contributing values are known; otherwise it remains unknown.

This is aggregation for drawing. The loaded, normalized rows remain the source for hover lookup and downloads. Zooming into a sufficiently small range restores individual candles.

That boundary prevents a quiet data-quality bug: a user zooms out, downloads a file and unknowingly receives the chart's coarser representation. In our implementation, changing the viewport does not change export resolution.

Cached interior groups also avoid repeatedly combining the entire series while panning. The clipped edges are calculated from the data inside the visible range. These are implementation choices, not a browser frame-rate benchmark.

Keep the accepted dataset separate from a load in progress

There are several ways a load can stop, and they should not all look like success.

If the client hits a row ceiling while a continuation cursor remains, or detects a repeated cursor, the resulting dataset is marked incomplete. The interface labels it, partial export filenames include that status, and the Excel metadata records it. Finishing the loop does not prove the requested range was fully retrieved.

The web tool's Cancel action has a different purpose: stop the current load. It aborts the fetch or pacing wait and invalidates that request's generation, so a late result cannot replace the accepted dataset. The previously accepted dataset remains available. It does not promise to save the cancelled load's accumulated rows or resume it later.

This distinction also applies when a user changes selection. A late response from an older request must not replace a newer accepted result. The request-generation check makes that decision explicit instead of depending on response timing.

Build the file from the data already loaded

Clicking CSV or Excel does not start another history fetch. Both writers use the accepted dataset.

File preparation produces bounded chunks and yields to the event loop between batches. The adapter periodically checks elapsed work and yields after roughly eight milliseconds. That is a scheduling choice, not a guarantee about responsiveness on every device.

The final download still assembles a Blob in browser memory. Chunking avoids some large intermediate allocations, but it does not make memory usage constant or turn the operation into a disk stream. Large selections still need adequate browser resources.

Explain the cost of reaching that dataset

A 365-day selection at one-minute resolution contains 525,600 possible intervals. With 1,000 rows per API page, our planning estimate is ceil(525600 / 1000) = 526 requests.

That assumes continuous trading. Sessions and available history affect the result; retries affect attempts. The annual-range confirmation presents the estimate and explains that successful requests use the monthly allowance. It is neither a coverage guarantee nor a quota check.

During loading, the interface reports both rows received and requests sent, including retries. Sending request 52 does not mean page 52 succeeded. An explicit waiting state also distinguishes pacing from a stalled request.

The client follows cursors sequentially and uses conservative pacing when better rate information is unavailable. It retries only 429 responses with our API's temporary rate_limit_exceeded code. Monthly quota exhaustion stops the load with an explanation; short waits cannot replenish that allowance.

Retry-After accepts seconds or an HTTP date. Our retry calculation considers that delay alongside backoff, with bounds on attempts and total wait per page. It stops if the wait budget would be exceeded. Pacing cannot promise that an account shared with other clients will never be rate limited.

The user can cancel while waiting. The confirmation also makes clear that stopping locally does not undo requests already processed by the API.

For this tool, the useful contract is that request counts stay visible, waiting stays cancellable, and display simplification does not silently rewrite the exported dataset.

You can inspect the user-facing workflow in SiftingIO Market Data Export.

Top comments (0)