Use the standard OHLCV endpoint when you need ordinary candles for a chart or analysis export. Normalize the returned fields at the edge of your application, preserve the request parameters, and inspect the actual time span instead of assuming a requested count is a complete dataset.
Request standard daily candles
Install requests and set API_KEY and NAVQUOTE_API_BASE in your server environment as described in the quickstart. Save the following as export_ohlcv.py and run python export_ohlcv.py. It writes two local files: candles.csv and candles.request.json.
The /api/price/ohlcv/{symbol} route explicitly requests standard candles. The generic price route can use a different candle construction by default, so it is not an interchangeable URL for this export.
import csv
import json
import math
import os
from datetime import datetime, timezone
import requests
symbol = "NASDAQ:AAPL"
params = {"timeframe": "D", "range": 10, "adjustment": "splits"}
base = os.environ["NAVQUOTE_API_BASE"].rstrip("/")
response = requests.get(
f"{base}/api/price/ohlcv/{symbol}",
params=params,
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
timeout=30,
)
response.raise_for_status()
result = response.json()
if result.get("success") is not True:
raise RuntimeError("Historical request failed")
by_time = {}
for bar in result["data"]["history"]:
fields = ("time", "open", "max", "min", "close")
if any(type(bar.get(k)) not in (int, float)
or not math.isfinite(bar[k]) for k in fields):
raise ValueError("Invalid candle: export stopped")
if bar["time"] <= 0 or bar["max"] < max(bar["open"], bar["close"], bar["min"]):
raise ValueError("Invalid candle bounds")
if bar["min"] > min(bar["open"], bar["close"]):
raise ValueError("Invalid candle bounds")
row = {
"time": bar["time"],
"time_utc": datetime.fromtimestamp(bar["time"], timezone.utc).isoformat(),
"open": bar["open"], "high": bar["max"],
"low": bar["min"], "close": bar["close"],
"volume": bar.get("volume"),
}
if row["time"] in by_time and by_time[row["time"]] != row:
raise ValueError("Conflicting candles at one timestamp")
by_time[row["time"]] = row
if not by_time:
raise ValueError("No candles returned")
rows = [by_time[stamp] for stamp in sorted(by_time)]
with open("candles.csv", "w", newline="", encoding="utf-8") as output:
writer = csv.DictWriter(output, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
with open("candles.request.json", "w", encoding="utf-8") as output:
json.dump({
"symbol": symbol, "parameters": params, "row_count": len(rows),
"retrieved_at": datetime.now(timezone.utc).isoformat(),
"degraded": result.get("degraded", False),
}, output, indent=2)
Normalize names and ordering once
The raw candle uses max and min; the CSV renames these to high and low. The raw timestamp is time in Unix seconds. Sorting by that timestamp makes the output ascending regardless of response order. Identical duplicates are collapsed; conflicting values for one timestamp stop the export so they cannot be silently selected by array order.
A missing volume stays blank in the CSV. Volume units and meaning depend on the instrument, so do not infer a stock-share unit for every market. The helper validates price bounds; add a volume policy appropriate to the dataset before consuming it in an analysis pipeline.
Save enough context to reproduce the request
The manifest records the symbol, timeframe, adjustment, count and retrieval time without credentials. Keep it beside the CSV. Using the same ticker with a different adjustment can produce different historical values, so filenames alone are insufficient provenance.
range=10 requests ten candles, not ten days. Inspect the resulting first and last timestamp and row count. Do not include data.current as an extra row without checking whether it duplicates an item in history.
Check whether the dataset fits the task
The most recent candle may still change until its interval closes. A timestamp alone does not prove finality; use verified session boundaries and the response characteristics for your chosen market. Fewer returned rows may indicate limited availability, not a successful complete export.
For a bounded analysis period, follow the historical window guide and filter the returned timestamps explicitly. This example demonstrates data preparation; it does not establish exchange coverage, uniform historical depth or suitability for a trading strategy.