|
|
@@ -1 +1,82 @@
|
|
|
import * as Plot from '@observablehq/plot';
|
|
|
+import {cachedFetchJSON} from './cache';
|
|
|
+
|
|
|
+const tickerSelect = document.querySelector('select#ticker') as HTMLSelectElement;
|
|
|
+const charts = document.querySelector('#charts')!;
|
|
|
+
|
|
|
+(async function () {
|
|
|
+ const materials: Material[] = await fetch('https://rest.fnar.net/material/allmaterials').then((r) => r.json());
|
|
|
+ const selected = document.location.hash.substring(1);
|
|
|
+ for (const mat of materials.sort((a, b) => a.Ticker.localeCompare(b.Ticker))) {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = mat.Ticker;
|
|
|
+ option.textContent = `${mat.Ticker} ${mat.Name}`;
|
|
|
+ if (mat.Ticker === selected)
|
|
|
+ option.selected = true;
|
|
|
+ tickerSelect.appendChild(option);
|
|
|
+ }
|
|
|
+ if (selected)
|
|
|
+ render();
|
|
|
+})();
|
|
|
+
|
|
|
+tickerSelect.addEventListener('change', render);
|
|
|
+
|
|
|
+async function render() {
|
|
|
+ charts.innerHTML = '';
|
|
|
+ renderPriceChart(tickerSelect.value, 'IC1');
|
|
|
+}
|
|
|
+
|
|
|
+async function renderPriceChart(ticker: string, cx: string) {
|
|
|
+ const cxpc: PriceChartPoint[] = await cachedFetchJSON(`https://rest.fnar.net/exchange/cxpc/${ticker}.${cx}`);
|
|
|
+ const filtered = cxpc.filter((p) => p.Interval === 'HOUR_TWELVE').map((p) => ({
|
|
|
+ ...p,
|
|
|
+ Date: new Date(p.DateEpochMs)
|
|
|
+ }));
|
|
|
+ const plot = Plot.plot({
|
|
|
+ inset: 6,
|
|
|
+ width: 928,
|
|
|
+ grid: true,
|
|
|
+ y: {label: ticker},
|
|
|
+ color: {domain: [-1, 0, 1], range: ['#e41a1c', '#000000', '#4daf4a']},
|
|
|
+ marks: [
|
|
|
+ Plot.lineY(filtered, {
|
|
|
+ x: 'Date',
|
|
|
+ y: (p) => p.Volume / p.Traded,
|
|
|
+ }),
|
|
|
+ Plot.ruleX(filtered, {
|
|
|
+ x: 'Date',
|
|
|
+ y1: 'Low',
|
|
|
+ y2: 'High'
|
|
|
+ }),
|
|
|
+ /*
|
|
|
+ Plot.ruleX(filtered, {
|
|
|
+ x: 'Date',
|
|
|
+ y: 'Traded',
|
|
|
+ // stroke: (d) => Math.sign(d.Close - d.Open),
|
|
|
+ // strokeWidth: 4,
|
|
|
+ // strokeLinecap: 'round'
|
|
|
+ })
|
|
|
+ */
|
|
|
+ Plot.crosshairX(filtered, {
|
|
|
+ x: 'Date',
|
|
|
+ y: (p) => p.Traded / p.Volume,
|
|
|
+ tip: true,
|
|
|
+ })
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ charts.appendChild(plot);
|
|
|
+}
|
|
|
+
|
|
|
+interface Material {
|
|
|
+ Ticker: string;
|
|
|
+ Name: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface PriceChartPoint {
|
|
|
+ Interval: 'MINUTE_FIVE' | 'MINUTE_FIFTEEN' | 'MINUTE_THIRTY' | 'HOUR_ONE' | 'HOUR_TWO' | 'HOUR_FOUR' | 'HOUR_SIX' | 'HOUR_TWELVE' | 'DAY_ONE' | 'DAY_THREE';
|
|
|
+ DateEpochMs: number;
|
|
|
+ High: number;
|
|
|
+ Low: number;
|
|
|
+ Volume: number;
|
|
|
+ Traded: number;
|
|
|
+}
|