mat.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import * as Plot from '@observablehq/plot';
  2. import * as d3 from 'd3';
  3. import {cachedFetchJSON} from './cache';
  4. const tickerSelect = document.querySelector('select#ticker') as HTMLSelectElement;
  5. const charts = document.querySelector('#charts')!;
  6. (async function () {
  7. const materials: Material[] = await fetch('https://rest.fnar.net/material/allmaterials').then((r) => r.json());
  8. const selected = document.location.hash.substring(1);
  9. for (const mat of materials.sort((a, b) => a.Ticker.localeCompare(b.Ticker))) {
  10. const option = document.createElement('option');
  11. option.value = mat.Ticker;
  12. option.textContent = `${mat.Ticker} ${mat.Name}`;
  13. if (mat.Ticker === selected)
  14. option.selected = true;
  15. tickerSelect.appendChild(option);
  16. }
  17. if (selected)
  18. render();
  19. })();
  20. tickerSelect.addEventListener('change', async () => {
  21. await render();
  22. document.location.hash = tickerSelect.value;
  23. });
  24. async function render() {
  25. charts.innerHTML = '';
  26. renderPriceChart(tickerSelect.value, 'IC1');
  27. }
  28. async function renderPriceChart(ticker: string, cx: string) {
  29. const cxpc: PriceChartPoint[] = await cachedFetchJSON(`https://rest.fnar.net/exchange/cxpc/${ticker}.${cx}`);
  30. const filtered = cxpc.filter((p) => p.Interval === 'HOUR_TWELVE').map((p) => ({
  31. ...p,
  32. Date: new Date(p.DateEpochMs)
  33. }));
  34. const maxPrice = Math.max(...filtered.map((p) => p.High));
  35. const maxTraded = Math.max(...filtered.map((t) => t.Traded));
  36. charts.appendChild(Plot.plot({
  37. grid: true,
  38. y: {axis: 'left', label: 'ICA', domain: [0, maxPrice * 1.1]},
  39. marks: [
  40. Plot.axisY(d3.ticks(0, maxTraded * 2, 10), {
  41. label: 'traded',
  42. anchor: 'right',
  43. y: (d) => (d / maxTraded) * maxPrice / 3,
  44. }),
  45. Plot.rectY(filtered, {
  46. x: 'Date',
  47. y: (t) => (t.Traded / maxTraded) * maxPrice / 3, // scale traded to price
  48. interval: 'day',
  49. fill: '#272',
  50. fillOpacity: 0.1,
  51. }),
  52. Plot.ruleX(filtered, {
  53. x: 'Date',
  54. y1: 'Low',
  55. y2: 'High',
  56. strokeWidth: 5,
  57. stroke: '#42a',
  58. }),
  59. Plot.dot(filtered, {
  60. x: 'Date',
  61. y: (p) => p.Volume / p.Traded,
  62. fill: '#a37',
  63. r: 2,
  64. }),
  65. Plot.crosshairX(filtered, {
  66. x: 'Date',
  67. y: (p) => p.Volume / p.Traded,
  68. textStrokeWidth: 0,
  69. })
  70. ]
  71. }));
  72. }
  73. interface Material {
  74. Ticker: string;
  75. Name: string;
  76. }
  77. interface PriceChartPoint {
  78. Interval: 'MINUTE_FIVE' | 'MINUTE_FIFTEEN' | 'MINUTE_THIRTY' | 'HOUR_ONE' | 'HOUR_TWO' | 'HOUR_FOUR' | 'HOUR_SIX' | 'HOUR_TWELVE' | 'DAY_ONE' | 'DAY_THREE';
  79. DateEpochMs: number;
  80. High: number;
  81. Low: number;
  82. Volume: number;
  83. Traded: number;
  84. }