SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%

web: guide « Charting » (/docs/charts), finitions légende (éditeur au-dessus des lignes, fond translucide), table sans retour de ligne

Simon-Pierre Boucher committed 18 days ago (Sep 7, 2026) parent 2c18928

5 changed files +98 −3

added hfmarketdata/web/content/guides/charts.mdx +92 −0
@@ -0,0 +1,92 @@
1 +export const meta = { title: 'Charting', description: 'The /charts page: every symbol of the API from 1-minute to daily, series types, indicators, comparisons, drawing tools, keyboard shortcuts, shareable URLs and the data limits behind them.' }
2 +
3 +export const snippets = [
4 + {
5 + title: 'The same bars the chart loads',
6 + curl: `# newest 1 500 daily bars, exactly what /charts requests on first load
7 +curl "https://www.hfmarketdata.io/v1/bars/stock/AAPL?timeframe=1day&order=desc&limit=1500"
8 +
9 +# scrolling left loads the next page: end = oldest bar already on screen − 1 day
10 +curl "https://www.hfmarketdata.io/v1/bars/stock/AAPL?timeframe=1day&order=desc&limit=1500&end=2018-09-30"`,
11 + python: `import pandas as pd
12 +
13 +url = "https://www.hfmarketdata.io/v1/bars/stock/AAPL?timeframe=1day&order=desc&limit=1500&format=csv"
14 +bars = pd.read_csv(url, parse_dates=["datetime"]).sort_values("datetime")
15 +print(bars.tail())`,
16 + javascript: `const res = await fetch("https://www.hfmarketdata.io/v1/bars/stock/AAPL?timeframe=1day&order=desc&limit=1500");
17 +const { data } = await res.json();
18 +console.log(data.length, "bars, newest first:", data[0]);`,
19 + r: `library(readr)
20 +bars <- read_csv("https://www.hfmarketdata.io/v1/bars/stock/AAPL?timeframe=1day&order=desc&limit=1500&format=csv")
21 +tail(bars[order(bars$datetime), ])`,
22 + },
23 +]
24 +
25 +# Charting
26 +
27 +[/charts](/charts) is a full-screen chart on top of the API — every stock, ETF, index, FX pair, crypto asset, continuous futures series and individual futures contract, from 1-minute to daily bars. Nothing is pre-computed on a server: the page calls the same public endpoints you would, draws them with a purpose-built canvas engine, and only ever spends your quota when you ask for more data.
28 +
29 +## Symbols and timeframes
30 +
31 +Press <kbd>/</kbd> (or <kbd>⌘</kbd> <kbd>/</kbd>) to focus the symbol box and start typing. Results are grouped by asset — stocks, ETFs, indices, continuous futures (`ES`, `CL`…), individual contracts (`ESZ24`), crypto, FX — with prefix matches first, then substring matches, and your recent symbols when the box is empty. <kbd>↑</kbd> <kbd>↓</kbd> move, <kbd>Enter</kbd> picks, <kbd>Esc</kbd> closes.
32 +
33 +The timeframe switch offers the five intervals of the dataset: **1m · 5m · 30m · 1h · 1D**. Switching keeps the previous chart on screen at reduced opacity until the new bars arrive — there is never a blank flash. Intraday stamps are the exchange's US/Eastern wall-clock time for the v1 assets (badge **ET**) and UTC for individual futures contracts (badge **UTC**), exactly as documented in [Time zones &amp; sessions](/docs/time-zones).
34 +
35 +Equities and ETFs default to the split-and-dividend-adjusted series; the **Adjustment** menu switches to split-only or unadjusted. Continuous futures offer ratio-adjusted, back-adjusted and unadjusted rolls.
36 +
37 +## Series types, indicators, comparisons
38 +
39 +| Menu | What you get |
40 +| --- | --- |
41 +| **Series type** | Candles, hollow candles, OHLC bars, line, area, baseline, Heikin-Ashi, columns, HLC bars. |
42 +| **Indicators** | Overlays — SMA, EMA, WMA, VWAP, Bollinger, Keltner, Donchian, Supertrend, Ichimoku — and oscillators in their own pane — RSI, MACD, Stochastic, ATR, OBV, ADX, CCI, MFI, Volume MA. Each active indicator has a row in the legend: click its name to edit the parameters inline, the **×** removes it. Colours follow a fixed 8-colour palette so the same indicator keeps the same colour across symbols. |
43 +| **Compare** | Overlays other symbols as % change since the first visible bar (the price scale switches to percent while a comparison is active). Chips remove them. |
44 +| **Price scale** | Linear, logarithmic or percent; auto-scale can be turned off after you drag the price axis. |
45 +| **Settings** | Volume histogram, colour-blind mode (hollow candles + a blue / orange palette), watermark, magnet crosshair, reduced motion, and an optional API key kept **in memory only** for this tab. |
46 +
47 +The legend always shows the values under the crosshair — or the last bar when the pointer leaves the chart — and the **Table** button opens the same data as an accessible table with a CSV export, so nothing depends on hovering.
48 +
49 +## Drawing tools
50 +
51 +The vertical bar on the left holds the drawings: trend line, ray, horizontal and vertical lines, parallel channel, rectangle, Fibonacci retracement, measure, arrow, text and brush. Drawings snap to open / high / low / close when the magnet is on, can be selected, moved and resized, and are saved per symbol and timeframe in your browser.
52 +
53 +| Key | Action |
54 +| --- | --- |
55 +| <kbd>T</kbd> <kbd>H</kbd> <kbd>V</kbd> <kbd>R</kbd> <kbd>F</kbd> <kbd>M</kbd> <kbd>X</kbd> | Trend line · horizontal line · vertical line · rectangle · Fibonacci · measure · text |
56 +| <kbd>Esc</kbd> | Back to the cursor (cancels the current tool) |
57 +| <kbd>Delete</kbd> / <kbd>Backspace</kbd> | Remove the selected drawing |
58 +| <kbd>Ctrl</kbd> <kbd>Z</kbd> · <kbd>Ctrl</kbd> <kbd>Y</kbd> | Undo · redo |
59 +| <kbd>←</kbd> <kbd>→</kbd> · <kbd>+</kbd> <kbd>−</kbd> · <kbd>Home</kbd> <kbd>End</kbd> | Pan · zoom · jump to the oldest / latest bar |
60 +| Wheel · drag · double-click | Zoom around the cursor · pan · fit the whole series |
61 +
62 +## Shareable URLs
63 +
64 +Everything that defines the view lives in the URL, so a link reproduces the chart exactly:
65 +
66 +```text
67 +https://www.hfmarketdata.io/charts?s=AAPL&tf=1hour&type=hollow&ind=ema:20,ema:50,rsi:14&cmp=MSFT&scale=log
68 +```
69 +
70 +| Parameter | Values |
71 +| --- | --- |
72 +| `s` | Symbol (`AAPL`, `ES`, `ESZ24`, `EURUSD`, `BTC`) |
73 +| `asset` | `stock` (default), `etf`, `index`, `futures`, `contract`, `crypto`, `fx` |
74 +| `tf` | `1min`, `5min`, `30min`, `1hour`, `1day` (default) |
75 +| `type` | `candles` (default), `hollow`, `ohlc`, `line`, `area`, `baseline`, `heikin`, `columns`, `hlc` |
76 +| `ind` | Comma-separated `type:param1:param2…`, e.g. `sma:20`, `macd:12:26:9`, `bollinger:20:2` |
77 +| `cmp` | Comma-separated symbols, `TICKER@asset` for non-stocks (`ES@futures`) |
78 +| `scale` | `linear` (default), `log`, `percent` |
79 +| `vol` | `0` hides the volume histogram |
80 +| `adj` | `adj_split`, `UNADJUSTED`, `contin_UNadj`, `contin_adj_absolute`… |
81 +
82 +The **Share** button copies the current URL; **Screenshot** downloads a PNG of the chart with a small attribution.
83 +
84 +## Data limits
85 +
86 +The chart is a regular API client, so the [rate limits](/docs/rate-limits) apply: keyless visitors get 30 requests per hour and at most 5 000 bars per request, a free account 120 requests per minute and 50 000 bars. The first load asks for ~1 500 daily / hourly bars or ~3 000 minute bars (capped by your tier), and each scroll into older history costs one more request — the status bar shows how many requests are left in the window and says **start of history** once the API has returned everything. A 429 is displayed as *rate limited — not down* with a countdown; the chart you already have stays on screen.
87 +
88 +<Callout type="tip" title="Sign in for more history per request">
89 +Signed-in sessions apply your account tier automatically. You can also paste an API key in **Settings** — it stays in memory for this tab only and is never written to storage.
90 +</Callout>
91 +
92 +Zero-volume minutes are not in the dataset, so intraday charts have no rows for minutes without a trade; the time axis is indexed by bar, not by wall-clock time, so sessions and weekends leave no gaps — the same convention as professional terminals.
modified hfmarketdata/web/e2e/charts.spec.js +1 −0
@@ -205,6 +205,7 @@ test.describe('charts mobile', () => {
205 205 await page.getByTestId('ch-more').click()
206 206 const sheet = page.getByTestId('ch-sheet')
207 207 await expect(sheet).toBeVisible()
208 + await page.screenshot({ path: '/tmp/hfmd-shots/charts-mobile-sheet.png' })
208 209 await sheet.getByTestId('ch-sheet-indicators').click()
209 210 await page.getByTestId('ch-ind-rsi').click()
210 211 await expect(page.getByTestId('ch-legend-ind')).toContainText('RSI 14')
modified hfmarketdata/web/src/docs/guides.js +1 −0
@@ -24,6 +24,7 @@ export const GUIDES = [
24 24 { slug: 'options', title: 'Options chains & Greeks', section: 'guides', file: 'options', summary: 'End-of-day chains, IV, Greeks, contract histories.' },
25 25 { slug: 'time-zones', title: 'Time zones & sessions', section: 'guides', file: 'time-zones', summary: 'RTH / ETH, US/Eastern intraday stamps, UTC output in v2.' },
26 26 { slug: 'bulk-downloads', title: 'Bulk downloads', section: 'guides', file: 'bulk-downloads', summary: 'Whole-universe Parquet extracts outside the rows quota.' },
27 + { slug: 'charts', title: 'Charting', section: 'guides', file: 'charts', summary: 'The /charts page: shortcuts, tools, shareable URLs, data limits.' },
27 28 { slug: 'recipes/pandas-backtest', title: 'Backtest a strategy in pandas', section: 'recipes', file: 'recipe-pandas-backtest', summary: 'Moving-average crossover on ES continuous, end to end.' },
28 29 { slug: 'recipes/custom-continuous', title: 'Build a custom continuous contract', section: 'recipes', file: 'recipe-custom-continuous', summary: 'Stitch individual contracts with your own roll rule.' },
29 30 { slug: 'recipes/crude-term-structure', title: 'Analyse crude oil term structure', section: 'recipes', file: 'recipe-crude-term-structure', summary: 'Contango / backwardation from the CL curve.' },
modified hfmarketdata/web/src/pages/charts/Legend.jsx +1 −1
@@ -87,7 +87,7 @@ export default function Legend({ state, name, hover, lastBar, prevBar, indValues
87 87 const color = seriesColor(theme, ind.colorIndex ?? 0)
88 88 const vals = indValues?.[ind.id] || {}
89 89 return (
90 − <div key={ind.id} className="ch-legend-row" data-testid="ch-legend-ind">
90 + <div key={ind.id} className={`ch-legend-row ${editing === ind.id ? 'is-editing' : ''}`} data-testid="ch-legend-ind">
91 91 <span className="ch-swatch" style={{ background: color }} aria-hidden="true" />
92 92 <button type="button" className="ch-legend-btn" onClick={() => setEditing(editing === ind.id ? null : ind.id)} aria-expanded={editing === ind.id} title="Edit parameters">{indicatorShortLabel(ind)}</button>
93 93 <span className="ch-legend-vals mono">
modified hfmarketdata/web/src/pages/charts/charts.css +3 −2
@@ -95,7 +95,7 @@
95 95
96 96 /* legend */
97 97 .ch-legend { position: absolute; top: 8px; left: 10px; z-index: 2; display: flex; flex-direction: column; gap: 3px; max-width: calc(100% - 90px); font-size: var(--fs-0); pointer-events: none; }
98 −.ch-legend > * { pointer-events: auto; }
98 +.ch-legend > * { pointer-events: auto; width: fit-content; max-width: 100%; padding: 1px 6px; border-radius: 4px; background: color-mix(in srgb, var(--bg) 72%, transparent); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
99 99 .ch-legend-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
100 100 .ch-legend-sym { font-size: var(--fs-3); font-weight: 700; color: var(--fg); letter-spacing: -.01em; }
101 101 .ch-legend-name { color: var(--fg-2); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -107,6 +107,7 @@
107 107 .ch-legend-stamp { color: var(--fg-3); }
108 108 .ch-legend-delta { font-weight: 600; }
109 109 .ch-legend-row { position: relative; display: flex; align-items: center; gap: 6px; min-height: 22px; color: var(--fg-1); }
110 +.ch-legend-row.is-editing { z-index: 7; } /* the backdrop-filter rows are stacking contexts: lift the one hosting the editor */
110 111 .ch-swatch { width: 12px; height: 3px; border-radius: 2px; flex: none; }
111 112 .ch-legend-btn { min-height: 22px; padding: 0 4px; border: 0; border-radius: 4px; background: transparent; color: var(--fg-1); font: inherit; font-size: var(--fs-0); font-weight: 600; cursor: pointer; }
112 113 button.ch-legend-btn:hover, .ch-legend-btn[aria-expanded="true"] { background: var(--bg-2); color: var(--fg); }
@@ -129,7 +130,7 @@ button.ch-legend-btn:hover, .ch-legend-btn[aria-expanded="true"] { background: v
129 130 .ch-table { display: flex; flex-direction: column; width: 440px; max-width: 50%; border-left: 1px solid var(--line); background: var(--bg); flex: none; min-height: 0; }
130 131 .ch-table-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--line); font-size: var(--fs-1); }
131 132 .ch-table .table-wrap { flex: 1; min-height: 0; overflow: auto; border: 0; border-radius: 0; }
132 −.ch-table td, .ch-table th { font-size: var(--fs-0); }
133 +.ch-table td, .ch-table th { font-size: var(--fs-0); white-space: nowrap; }
133 134
134 135 /* status */
135 136 .ch-status { display: flex; align-items: center; gap: 12px; height: 30px; padding: 0 10px; border-top: 1px solid var(--line); color: var(--fg-2); font-size: var(--fs-0); flex: none; white-space: nowrap; overflow: hidden; }
136 137