dev-api-ui 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/package.json +40 -0
- package/ui/ThemeProvider.tsx +16 -0
- package/ui/components/ArticlePage.tsx +673 -0
- package/ui/components/AuthPage.tsx +109 -0
- package/ui/components/Callout.tsx +79 -0
- package/ui/components/Chart.tsx +100 -0
- package/ui/components/CodeTabs.tsx +145 -0
- package/ui/components/ComparePage.tsx +364 -0
- package/ui/components/DashboardPage.tsx +773 -0
- package/ui/components/DocsNav.tsx +80 -0
- package/ui/components/Footer.tsx +136 -0
- package/ui/components/HubPage.tsx +529 -0
- package/ui/components/Modal.tsx +412 -0
- package/ui/components/Nav.tsx +162 -0
- package/ui/components/OnThisPage.tsx +56 -0
- package/ui/components/ParamsTable.tsx +86 -0
- package/ui/components/RangeBar.tsx +68 -0
- package/ui/components/SeriesChart.tsx +218 -0
- package/ui/components/SeriesPage.tsx +461 -0
- package/ui/components/StatusMark.tsx +48 -0
- package/ui/components/publictrades/ExternalLink.tsx +37 -0
- package/ui/components/publictrades/FactsCard.tsx +40 -0
- package/ui/components/publictrades/LedgerFooter.tsx +22 -0
- package/ui/components/publictrades/LedgerNav.tsx +78 -0
- package/ui/components/publictrades/Mark.tsx +40 -0
- package/ui/components/publictrades/SourceBlock.tsx +91 -0
- package/ui/components/publictrades/Tape.tsx +164 -0
- package/ui/components/publictrades/ThreeStamps.tsx +47 -0
- package/ui/components/publictrades/types.ts +19 -0
- package/ui/data/series.ts +851 -0
- package/ui/index.ts +50 -0
- package/ui/publictrades.ts +16 -0
- package/ui/themes.css +81 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export type ArticleKind =
|
|
8
|
+
| 'Guide'
|
|
9
|
+
| 'Blog'
|
|
10
|
+
| 'Methodology'
|
|
11
|
+
| 'Learn'
|
|
12
|
+
| 'Report'
|
|
13
|
+
| 'FAQ'
|
|
14
|
+
| 'Glossary'
|
|
15
|
+
| 'Coverage';
|
|
16
|
+
|
|
17
|
+
interface RelatedCard {
|
|
18
|
+
type: string;
|
|
19
|
+
title: string;
|
|
20
|
+
href?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CompareRow {
|
|
24
|
+
q: string;
|
|
25
|
+
naive: string;
|
|
26
|
+
pit: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AuthorCard {
|
|
30
|
+
initials: string;
|
|
31
|
+
name: string;
|
|
32
|
+
bio: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ArticlePageProps {
|
|
36
|
+
kind: ArticleKind;
|
|
37
|
+
breadcrumb: string;
|
|
38
|
+
title: string;
|
|
39
|
+
lede: string;
|
|
40
|
+
date: string;
|
|
41
|
+
readTime: string;
|
|
42
|
+
author: AuthorCard;
|
|
43
|
+
keyPoints: string[];
|
|
44
|
+
compareRows: CompareRow[];
|
|
45
|
+
related: RelatedCard[];
|
|
46
|
+
brand?: string;
|
|
47
|
+
apiUrl?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function InlineCode({ children }: { children: string }) {
|
|
53
|
+
return (
|
|
54
|
+
<code
|
|
55
|
+
style={{
|
|
56
|
+
fontFamily: 'var(--mono)',
|
|
57
|
+
fontSize: 14,
|
|
58
|
+
background: '#F2F2EF',
|
|
59
|
+
border: '0.5px solid var(--hairline-2)',
|
|
60
|
+
borderRadius: 3,
|
|
61
|
+
padding: '1px 5px',
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</code>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function CheckIcon(): ReactNode {
|
|
70
|
+
return (
|
|
71
|
+
<svg
|
|
72
|
+
viewBox="0 0 24 24"
|
|
73
|
+
width="15"
|
|
74
|
+
height="15"
|
|
75
|
+
fill="none"
|
|
76
|
+
stroke="currentColor"
|
|
77
|
+
strokeWidth="2"
|
|
78
|
+
strokeLinecap="round"
|
|
79
|
+
strokeLinejoin="round"
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
>
|
|
82
|
+
<path d="M5 12l4 4 10-10" />
|
|
83
|
+
</svg>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function ArrowIcon(): ReactNode {
|
|
88
|
+
return (
|
|
89
|
+
<svg
|
|
90
|
+
viewBox="0 0 24 24"
|
|
91
|
+
width="13"
|
|
92
|
+
height="13"
|
|
93
|
+
fill="none"
|
|
94
|
+
stroke="currentColor"
|
|
95
|
+
strokeWidth="1.8"
|
|
96
|
+
strokeLinecap="round"
|
|
97
|
+
strokeLinejoin="round"
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
>
|
|
100
|
+
<path d="M5 12h14" />
|
|
101
|
+
<path d="M13 6l6 6-6 6" />
|
|
102
|
+
</svg>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Sub-sections ─────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function KeyPoints({ points }: { points: string[] }) {
|
|
109
|
+
return (
|
|
110
|
+
<section style={{ padding: '22px 0' }}>
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
background: 'var(--surface)',
|
|
114
|
+
border: '0.5px solid var(--hairline-2)',
|
|
115
|
+
borderRadius: 8,
|
|
116
|
+
padding: '18px 22px',
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
style={{
|
|
121
|
+
fontSize: 11,
|
|
122
|
+
fontWeight: 600,
|
|
123
|
+
textTransform: 'uppercase',
|
|
124
|
+
letterSpacing: '.07em',
|
|
125
|
+
color: 'var(--tertiary)',
|
|
126
|
+
marginBottom: 12,
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
Key points
|
|
130
|
+
</div>
|
|
131
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
132
|
+
{points.map((k, i) => (
|
|
133
|
+
<div
|
|
134
|
+
key={i}
|
|
135
|
+
style={{
|
|
136
|
+
display: 'flex',
|
|
137
|
+
alignItems: 'flex-start',
|
|
138
|
+
gap: 10,
|
|
139
|
+
fontSize: 15,
|
|
140
|
+
color: 'var(--secondary)',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
<span style={{ flex: 'none', color: 'var(--accent)', marginTop: 2 }}>
|
|
144
|
+
<CheckIcon />
|
|
145
|
+
</span>
|
|
146
|
+
<span>{k}</span>
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</section>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function CompareTable({ rows }: { rows: CompareRow[] }) {
|
|
156
|
+
const cols = [
|
|
157
|
+
{ key: 'q', label: 'Query' },
|
|
158
|
+
{ key: 'naive', label: 'Naive store' },
|
|
159
|
+
{ key: 'pit', label: 'Point-in-time' },
|
|
160
|
+
];
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
style={{
|
|
164
|
+
background: 'var(--surface)',
|
|
165
|
+
border: '0.5px solid var(--hairline-2)',
|
|
166
|
+
borderRadius: 8,
|
|
167
|
+
overflow: 'hidden',
|
|
168
|
+
marginTop: 18,
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{/* Tinted header */}
|
|
172
|
+
<div
|
|
173
|
+
style={{
|
|
174
|
+
display: 'grid',
|
|
175
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
176
|
+
gap: 16,
|
|
177
|
+
padding: '10px 18px',
|
|
178
|
+
borderBottom: '0.5px solid var(--hairline-2)',
|
|
179
|
+
background: '#F4F4F1',
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
{cols.map((c) => (
|
|
183
|
+
<span
|
|
184
|
+
key={c.key}
|
|
185
|
+
style={{
|
|
186
|
+
fontSize: 10,
|
|
187
|
+
fontWeight: 600,
|
|
188
|
+
textTransform: 'uppercase',
|
|
189
|
+
letterSpacing: '.07em',
|
|
190
|
+
color: 'var(--tertiary)',
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{c.label}
|
|
194
|
+
</span>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
{/* Rows — horizontal hairlines, no zebra */}
|
|
198
|
+
{rows.map((r, i) => (
|
|
199
|
+
<div
|
|
200
|
+
key={i}
|
|
201
|
+
style={{
|
|
202
|
+
display: 'grid',
|
|
203
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
204
|
+
gap: 16,
|
|
205
|
+
padding: '12px 18px',
|
|
206
|
+
borderBottom: '0.5px solid var(--hairline)',
|
|
207
|
+
fontSize: 14,
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
<span style={{ color: 'var(--secondary)' }}>{r.q}</span>
|
|
211
|
+
<span style={{ color: 'var(--down)' }}>{r.naive}</span>
|
|
212
|
+
<span style={{ color: 'var(--accent)' }}>{r.pit}</span>
|
|
213
|
+
</div>
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function PullQuote({ children }: { children: string }) {
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
style={{
|
|
223
|
+
margin: '30px 0',
|
|
224
|
+
padding: '4px 0',
|
|
225
|
+
borderTop: '0.5px solid var(--hairline)',
|
|
226
|
+
borderBottom: '0.5px solid var(--hairline)',
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<p
|
|
230
|
+
style={{
|
|
231
|
+
fontSize: 22,
|
|
232
|
+
color: 'var(--ink)',
|
|
233
|
+
lineHeight: 1.5,
|
|
234
|
+
fontWeight: 500,
|
|
235
|
+
padding: '20px 0',
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{children}
|
|
239
|
+
</p>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function AuthorSection({ author }: { author: AuthorCard }) {
|
|
245
|
+
return (
|
|
246
|
+
<section
|
|
247
|
+
style={{
|
|
248
|
+
padding: '30px 0',
|
|
249
|
+
marginTop: 18,
|
|
250
|
+
borderTop: '0.5px solid var(--hairline)',
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
|
|
254
|
+
<span
|
|
255
|
+
style={{
|
|
256
|
+
width: 44,
|
|
257
|
+
height: 44,
|
|
258
|
+
borderRadius: '50%',
|
|
259
|
+
background: 'var(--ink)',
|
|
260
|
+
color: '#fff',
|
|
261
|
+
display: 'inline-flex',
|
|
262
|
+
alignItems: 'center',
|
|
263
|
+
justifyContent: 'center',
|
|
264
|
+
fontFamily: 'var(--mono)',
|
|
265
|
+
fontSize: 15,
|
|
266
|
+
fontWeight: 600,
|
|
267
|
+
flex: 'none',
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
{author.initials}
|
|
271
|
+
</span>
|
|
272
|
+
<div>
|
|
273
|
+
<div style={{ fontSize: 15, fontWeight: 500 }}>{author.name}</div>
|
|
274
|
+
<div style={{ fontSize: 13, color: 'var(--tertiary)' }}>{author.bio}</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function RelatedCards({ cards }: { cards: RelatedCard[] }) {
|
|
282
|
+
return (
|
|
283
|
+
<section
|
|
284
|
+
style={{
|
|
285
|
+
maxWidth: 1100,
|
|
286
|
+
margin: '0 auto',
|
|
287
|
+
padding: '8px 32px 0',
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<div style={{ borderTop: '0.5px solid var(--hairline)', paddingTop: 30 }}>
|
|
291
|
+
<h2
|
|
292
|
+
style={{
|
|
293
|
+
fontFamily: 'var(--mono)',
|
|
294
|
+
fontSize: 20,
|
|
295
|
+
fontWeight: 600,
|
|
296
|
+
letterSpacing: '-.01em',
|
|
297
|
+
marginBottom: 16,
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
Keep reading
|
|
301
|
+
</h2>
|
|
302
|
+
<div
|
|
303
|
+
style={{
|
|
304
|
+
display: 'grid',
|
|
305
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
|
|
306
|
+
gap: 14,
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
{cards.map((a, i) => (
|
|
310
|
+
<a
|
|
311
|
+
key={i}
|
|
312
|
+
href={a.href ?? '#'}
|
|
313
|
+
style={{
|
|
314
|
+
display: 'flex',
|
|
315
|
+
flexDirection: 'column',
|
|
316
|
+
gap: 8,
|
|
317
|
+
background: 'var(--surface)',
|
|
318
|
+
border: '0.5px solid var(--hairline-2)',
|
|
319
|
+
borderRadius: 8,
|
|
320
|
+
padding: '18px 20px',
|
|
321
|
+
color: 'inherit',
|
|
322
|
+
textDecoration: 'none',
|
|
323
|
+
minHeight: 112,
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
<span
|
|
327
|
+
style={{
|
|
328
|
+
fontSize: 11,
|
|
329
|
+
fontWeight: 600,
|
|
330
|
+
textTransform: 'uppercase',
|
|
331
|
+
letterSpacing: '.07em',
|
|
332
|
+
color: 'var(--accent)',
|
|
333
|
+
}}
|
|
334
|
+
>
|
|
335
|
+
{a.type}
|
|
336
|
+
</span>
|
|
337
|
+
<span
|
|
338
|
+
style={{ fontSize: 15, fontWeight: 500, color: 'var(--ink)', lineHeight: 1.4 }}
|
|
339
|
+
>
|
|
340
|
+
{a.title}
|
|
341
|
+
</span>
|
|
342
|
+
<span
|
|
343
|
+
style={{
|
|
344
|
+
marginTop: 'auto',
|
|
345
|
+
display: 'inline-flex',
|
|
346
|
+
alignItems: 'center',
|
|
347
|
+
gap: 6,
|
|
348
|
+
fontSize: 13,
|
|
349
|
+
fontWeight: 500,
|
|
350
|
+
color: 'var(--accent)',
|
|
351
|
+
}}
|
|
352
|
+
>
|
|
353
|
+
Read <ArrowIcon />
|
|
354
|
+
</span>
|
|
355
|
+
</a>
|
|
356
|
+
))}
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</section>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ─── Main component ───────────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
export default function ArticlePage({
|
|
366
|
+
kind,
|
|
367
|
+
breadcrumb,
|
|
368
|
+
title,
|
|
369
|
+
lede,
|
|
370
|
+
date,
|
|
371
|
+
readTime,
|
|
372
|
+
author,
|
|
373
|
+
keyPoints,
|
|
374
|
+
compareRows,
|
|
375
|
+
related,
|
|
376
|
+
brand = 'Console',
|
|
377
|
+
apiUrl = 'https://api.console.dev',
|
|
378
|
+
}: ArticlePageProps) {
|
|
379
|
+
return (
|
|
380
|
+
<>
|
|
381
|
+
{/* Reading column */}
|
|
382
|
+
<article style={{ maxWidth: 740, margin: '0 auto', padding: '0 28px' }}>
|
|
383
|
+
|
|
384
|
+
{/* Header */}
|
|
385
|
+
<header style={{ padding: '34px 0 26px', borderBottom: '0.5px solid var(--hairline)' }}>
|
|
386
|
+
{/* Breadcrumb */}
|
|
387
|
+
<div
|
|
388
|
+
style={{
|
|
389
|
+
display: 'flex',
|
|
390
|
+
alignItems: 'center',
|
|
391
|
+
gap: 8,
|
|
392
|
+
fontSize: 13,
|
|
393
|
+
color: 'var(--tertiary)',
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
396
|
+
<a href="#" style={{ color: 'var(--secondary)', textDecoration: 'none' }}>
|
|
397
|
+
{kind}
|
|
398
|
+
</a>
|
|
399
|
+
<span>/</span>
|
|
400
|
+
<span style={{ color: 'var(--ink)' }}>{breadcrumb}</span>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
{/* Eyebrow */}
|
|
404
|
+
<div
|
|
405
|
+
style={{
|
|
406
|
+
display: 'inline-flex',
|
|
407
|
+
alignItems: 'center',
|
|
408
|
+
gap: 8,
|
|
409
|
+
fontSize: 12,
|
|
410
|
+
fontWeight: 600,
|
|
411
|
+
letterSpacing: '.04em',
|
|
412
|
+
textTransform: 'uppercase',
|
|
413
|
+
color: 'var(--accent)',
|
|
414
|
+
marginTop: 18,
|
|
415
|
+
}}
|
|
416
|
+
>
|
|
417
|
+
<span
|
|
418
|
+
style={{
|
|
419
|
+
width: 6,
|
|
420
|
+
height: 6,
|
|
421
|
+
borderRadius: '50%',
|
|
422
|
+
background: 'var(--accent)',
|
|
423
|
+
display: 'inline-block',
|
|
424
|
+
}}
|
|
425
|
+
/>
|
|
426
|
+
{kind}
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
{/* H1 */}
|
|
430
|
+
<h1
|
|
431
|
+
style={{
|
|
432
|
+
fontFamily: 'var(--mono)',
|
|
433
|
+
fontSize: 34,
|
|
434
|
+
fontWeight: 600,
|
|
435
|
+
letterSpacing: '-.02em',
|
|
436
|
+
marginTop: 12,
|
|
437
|
+
lineHeight: 1.14,
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
{title}
|
|
441
|
+
</h1>
|
|
442
|
+
|
|
443
|
+
{/* Lede */}
|
|
444
|
+
<p
|
|
445
|
+
style={{
|
|
446
|
+
fontSize: 19,
|
|
447
|
+
color: 'var(--secondary)',
|
|
448
|
+
lineHeight: 1.55,
|
|
449
|
+
marginTop: 14,
|
|
450
|
+
}}
|
|
451
|
+
>
|
|
452
|
+
{lede}
|
|
453
|
+
</p>
|
|
454
|
+
|
|
455
|
+
{/* Byline */}
|
|
456
|
+
<div
|
|
457
|
+
style={{
|
|
458
|
+
display: 'flex',
|
|
459
|
+
alignItems: 'center',
|
|
460
|
+
gap: 14,
|
|
461
|
+
flexWrap: 'wrap',
|
|
462
|
+
marginTop: 18,
|
|
463
|
+
fontSize: 13,
|
|
464
|
+
color: 'var(--tertiary)',
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
|
468
|
+
<span
|
|
469
|
+
style={{
|
|
470
|
+
width: 24,
|
|
471
|
+
height: 24,
|
|
472
|
+
borderRadius: '50%',
|
|
473
|
+
background: 'var(--ink)',
|
|
474
|
+
color: '#fff',
|
|
475
|
+
display: 'inline-flex',
|
|
476
|
+
alignItems: 'center',
|
|
477
|
+
justifyContent: 'center',
|
|
478
|
+
fontFamily: 'var(--mono)',
|
|
479
|
+
fontSize: 11,
|
|
480
|
+
fontWeight: 600,
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
{author.initials}
|
|
484
|
+
</span>
|
|
485
|
+
<span style={{ color: 'var(--ink)', fontWeight: 500 }}>{author.name}</span>
|
|
486
|
+
</span>
|
|
487
|
+
<span>·</span>
|
|
488
|
+
<span>{date}</span>
|
|
489
|
+
<span>·</span>
|
|
490
|
+
<span>{readTime}</span>
|
|
491
|
+
</div>
|
|
492
|
+
</header>
|
|
493
|
+
|
|
494
|
+
{/* Lede paragraph (answer-first) */}
|
|
495
|
+
<section style={{ padding: '26px 0 4px' }}>
|
|
496
|
+
<p style={{ fontSize: 18, color: 'var(--ink)', lineHeight: 1.65 }}>
|
|
497
|
+
<b style={{ fontWeight: 600 }}>
|
|
498
|
+
Point-in-time data returns the value that was actually published as of a given
|
|
499
|
+
timestamp
|
|
500
|
+
</b>
|
|
501
|
+
, with no later revisions folded in. It is the difference between a backtest you can
|
|
502
|
+
trust and one quietly contaminated by information the market did not yet have.
|
|
503
|
+
</p>
|
|
504
|
+
</section>
|
|
505
|
+
|
|
506
|
+
{/* Key points box */}
|
|
507
|
+
<KeyPoints points={keyPoints} />
|
|
508
|
+
|
|
509
|
+
{/* Body sections */}
|
|
510
|
+
<section style={{ padding: '8px 0 0' }}>
|
|
511
|
+
|
|
512
|
+
<h2
|
|
513
|
+
id="what-it-means"
|
|
514
|
+
style={{
|
|
515
|
+
fontFamily: 'var(--mono)',
|
|
516
|
+
fontSize: 22,
|
|
517
|
+
fontWeight: 600,
|
|
518
|
+
letterSpacing: '-.01em',
|
|
519
|
+
margin: '30px 0 4px',
|
|
520
|
+
scrollMarginTop: 70,
|
|
521
|
+
}}
|
|
522
|
+
>
|
|
523
|
+
What "point-in-time" means
|
|
524
|
+
</h2>
|
|
525
|
+
<p
|
|
526
|
+
style={{ fontSize: 17, color: '#3A3D44', lineHeight: 1.7, marginTop: 16 }}
|
|
527
|
+
>
|
|
528
|
+
Most data providers serve you the <em>current best estimate</em> of a historical value.
|
|
529
|
+
When a figure is revised — a GDP print restated, a settlement corrected — the old number
|
|
530
|
+
silently disappears. Query last March today and you get March as it is understood now,
|
|
531
|
+
not as it was understood in March.
|
|
532
|
+
</p>
|
|
533
|
+
<p
|
|
534
|
+
style={{ fontSize: 17, color: '#3A3D44', lineHeight: 1.7, marginTop: 16 }}
|
|
535
|
+
>
|
|
536
|
+
A point-in-time store keeps every vintage. Ask for the value{' '}
|
|
537
|
+
<InlineCode>as_of=2026-03-14</InlineCode> and you receive exactly what a subscriber
|
|
538
|
+
would have seen on that date, revisions excluded.
|
|
539
|
+
</p>
|
|
540
|
+
|
|
541
|
+
<h2
|
|
542
|
+
id="look-ahead"
|
|
543
|
+
style={{
|
|
544
|
+
fontFamily: 'var(--mono)',
|
|
545
|
+
fontSize: 22,
|
|
546
|
+
fontWeight: 600,
|
|
547
|
+
letterSpacing: '-.01em',
|
|
548
|
+
margin: '30px 0 4px',
|
|
549
|
+
scrollMarginTop: 70,
|
|
550
|
+
}}
|
|
551
|
+
>
|
|
552
|
+
Why look-ahead bias ruins backtests
|
|
553
|
+
</h2>
|
|
554
|
+
<p
|
|
555
|
+
style={{ fontSize: 17, color: '#3A3D44', lineHeight: 1.7, marginTop: 16 }}
|
|
556
|
+
>
|
|
557
|
+
If your historical series contains values that were only knowable later, your strategy is
|
|
558
|
+
implicitly trading on the future. Returns look spectacular in simulation and collapse in
|
|
559
|
+
production. The bias is subtle precisely because the numbers are <em>real</em> — just not
|
|
560
|
+
real <em>yet</em>.
|
|
561
|
+
</p>
|
|
562
|
+
|
|
563
|
+
{/* Warn callout — local inline (fs=15, margin 22px 0 per design; shared Callout default left at 14) */}
|
|
564
|
+
<div style={{
|
|
565
|
+
display: 'flex', alignItems: 'flex-start', gap: 12,
|
|
566
|
+
margin: '22px 0',
|
|
567
|
+
background: '#F4F4F1', border: '0.5px solid var(--hairline-2)',
|
|
568
|
+
borderRadius: 8, padding: '16px 18px',
|
|
569
|
+
}}>
|
|
570
|
+
<span style={{ flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 20, height: 20, color: 'var(--warn)', marginTop: 1 }}>
|
|
571
|
+
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
572
|
+
<path d="M12 9v4" /><path d="M12 17h.01" />
|
|
573
|
+
<path d="M10.3 4.3 2.5 18a2 2 0 0 0 1.7 3h15.6a2 2 0 0 0 1.7-3L13.7 4.3a2 2 0 0 0-3.4 0z" />
|
|
574
|
+
</svg>
|
|
575
|
+
</span>
|
|
576
|
+
<div>
|
|
577
|
+
<div style={{ fontSize: 15, fontWeight: 500, color: 'var(--ink)' }}>The tell</div>
|
|
578
|
+
<div style={{ fontSize: 15, color: 'var(--secondary)', marginTop: 3, lineHeight: 1.55 }}>
|
|
579
|
+
A backtest that beats live trading by a wide, consistent margin almost always has a
|
|
580
|
+
look-ahead leak. Point-in-time data closes the most common one.
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<h2
|
|
586
|
+
id="how-console-stamps"
|
|
587
|
+
style={{
|
|
588
|
+
fontFamily: 'var(--mono)',
|
|
589
|
+
fontSize: 22,
|
|
590
|
+
fontWeight: 600,
|
|
591
|
+
letterSpacing: '-.01em',
|
|
592
|
+
margin: '30px 0 4px',
|
|
593
|
+
scrollMarginTop: 70,
|
|
594
|
+
}}
|
|
595
|
+
>
|
|
596
|
+
How {brand} stamps every value
|
|
597
|
+
</h2>
|
|
598
|
+
<p
|
|
599
|
+
style={{ fontSize: 17, color: '#3A3D44', lineHeight: 1.7, marginTop: 16 }}
|
|
600
|
+
>
|
|
601
|
+
Every observation we store carries the timestamp it became public. Add an{' '}
|
|
602
|
+
<InlineCode>as_of</InlineCode> parameter and the API replays the series as it stood on
|
|
603
|
+
that date — no special endpoint, same envelope.
|
|
604
|
+
</p>
|
|
605
|
+
|
|
606
|
+
{/* Dark code block — static single-pane */}
|
|
607
|
+
<div style={{ borderRadius: 8, overflow: 'hidden', border: '0.5px solid #23262B', background: 'var(--code-bg)', marginTop: 18 }}>
|
|
608
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderBottom: '0.5px solid #23262B', padding: '10px 14px' }}>
|
|
609
|
+
<span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--code-dim, #6B7480)' }}>replay · point-in-time</span>
|
|
610
|
+
<span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--code-dim, #6B7480)' }}>copy</span>
|
|
611
|
+
</div>
|
|
612
|
+
<pre style={{ margin: 0, padding: '16px', fontFamily: 'var(--mono)', fontSize: 13, lineHeight: 1.7, color: 'var(--code-fg)', overflowX: 'auto' }}>
|
|
613
|
+
<span style={{ color: '#5BC0EB' }}>$</span>{` curl ${apiUrl}/v1/fx/eurusd?as_of=2026-03-14\n`}
|
|
614
|
+
<span style={{ color: '#6B7480' }}>{'# → the rate as published on 2026-03-14, not today\'s restatement'}</span>
|
|
615
|
+
</pre>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<h2
|
|
619
|
+
id="compare"
|
|
620
|
+
style={{
|
|
621
|
+
fontFamily: 'var(--mono)',
|
|
622
|
+
fontSize: 22,
|
|
623
|
+
fontWeight: 600,
|
|
624
|
+
letterSpacing: '-.01em',
|
|
625
|
+
margin: '30px 0 8px',
|
|
626
|
+
scrollMarginTop: 70,
|
|
627
|
+
}}
|
|
628
|
+
>
|
|
629
|
+
Naive vs point-in-time
|
|
630
|
+
</h2>
|
|
631
|
+
|
|
632
|
+
{/* Compare table — tinted header, horizontal hairlines, no zebra */}
|
|
633
|
+
<CompareTable rows={compareRows} />
|
|
634
|
+
|
|
635
|
+
{/* Pull-quote */}
|
|
636
|
+
<PullQuote>
|
|
637
|
+
“If you cannot reproduce the number you traded on, you do not have a backtest — you have
|
|
638
|
+
a story.”
|
|
639
|
+
</PullQuote>
|
|
640
|
+
|
|
641
|
+
<h2
|
|
642
|
+
id="putting-together"
|
|
643
|
+
style={{
|
|
644
|
+
fontFamily: 'var(--mono)',
|
|
645
|
+
fontSize: 22,
|
|
646
|
+
fontWeight: 600,
|
|
647
|
+
letterSpacing: '-.01em',
|
|
648
|
+
margin: '10px 0 4px',
|
|
649
|
+
scrollMarginTop: 70,
|
|
650
|
+
}}
|
|
651
|
+
>
|
|
652
|
+
Putting it together
|
|
653
|
+
</h2>
|
|
654
|
+
<p
|
|
655
|
+
style={{ fontSize: 17, color: '#3A3D44', lineHeight: 1.7, marginTop: 16 }}
|
|
656
|
+
>
|
|
657
|
+
Point-in-time is the default on every {brand} series — fx, metals, and power alike. You
|
|
658
|
+
do not opt in; you opt out by omitting <InlineCode>as_of</InlineCode> to get the latest.
|
|
659
|
+
Either way, every value names the feed and timestamp it came from, so the number is
|
|
660
|
+
auditable end to end.
|
|
661
|
+
</p>
|
|
662
|
+
</section>
|
|
663
|
+
|
|
664
|
+
{/* Author card */}
|
|
665
|
+
<AuthorSection author={author} />
|
|
666
|
+
|
|
667
|
+
</article>
|
|
668
|
+
|
|
669
|
+
{/* Keep reading — full-width rail, same max-width as rest of site */}
|
|
670
|
+
<RelatedCards cards={related} />
|
|
671
|
+
</>
|
|
672
|
+
);
|
|
673
|
+
}
|