gloomberb 0.16.5 → 0.16.6
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/package.json +2 -1
- package/src/api-client/central-bank-rates.ts +6 -1
- package/src/api-client/chat.ts +26 -0
- package/src/api-client/credit-boards.ts +2 -0
- package/src/api-client/data.ts +49 -1
- package/src/api-client/europe-energy.ts +106 -0
- package/src/api-client/index.ts +9 -0
- package/src/api-client/market-discovery.ts +3 -0
- package/src/api-client/normalizers.ts +32 -0
- package/src/api-client/perps.ts +8 -0
- package/src/api-client/request.ts +9 -2
- package/src/api-client/socket.ts +29 -0
- package/src/api-client/themes.ts +22 -1
- package/src/api-client/types.ts +61 -1
- package/src/app/global-shortcuts.ts +35 -1
- package/src/app/keybindings/actions.ts +12 -1
- package/src/app/pane-runtime/plugin-bindings.ts +2 -2
- package/src/app/pane-runtime/ticker-open-runtime.ts +9 -3
- package/src/app/runtime/desktop-deeplink.ts +10 -5
- package/src/app/runtime/startup.ts +1 -1
- package/src/app.tsx +6 -0
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/ask-gloom/pane-context.ts +114 -0
- package/src/ask-gloom/pane-request.ts +70 -0
- package/src/ask-gloom/pane-text.ts +201 -0
- package/src/capabilities/chart-series.ts +8 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/central-bank-events.ts +23 -0
- package/src/cli/command-options.ts +17 -5
- package/src/cli/commands/crypto-hints.ts +28 -2
- package/src/cli/commands/fred-series.ts +89 -4
- package/src/cli/commands/market.ts +101 -18
- package/src/cli/commands/overview.ts +47 -14
- package/src/cli/commands/search.ts +254 -23
- package/src/cli/commands/ticker.ts +73 -13
- package/src/cli/desktop-pane-shot.ts +23 -6
- package/src/cli/errors.ts +7 -5
- package/src/cli/exit-codes.ts +8 -0
- package/src/cli/export-source.ts +58 -0
- package/src/cli/glossary.ts +62 -8
- package/src/cli/help.ts +10 -0
- package/src/cli/helpers.ts +2 -5
- package/src/cli/history-rows.ts +70 -0
- package/src/cli/index.ts +9 -3
- package/src/cli/known-gaps.ts +65 -8
- package/src/cli/listing-arg.ts +182 -89
- package/src/cli/not-a-ticker.ts +12 -4
- package/src/cli/pane-functions/access-gate.ts +8 -2
- package/src/cli/pane-functions/capabilities.ts +1 -1
- package/src/cli/pane-functions/catalog.ts +183 -39
- package/src/cli/pane-functions/data.ts +30 -0
- package/src/cli/pane-functions/dom.ts +17 -2
- package/src/cli/pane-functions/freshness.ts +48 -24
- package/src/cli/pane-functions/function-help.ts +19 -10
- package/src/cli/pane-functions/headless.ts +55 -15
- package/src/cli/pane-functions/index.ts +45 -16
- package/src/cli/pane-functions/options.ts +8 -0
- package/src/cli/pane-functions/resolver.ts +25 -7
- package/src/cli/pane-functions/screenshot.ts +145 -9
- package/src/cli/perpetual-contract.ts +60 -0
- package/src/cli/registry.ts +3 -1
- package/src/cli/report-tables.ts +23 -7
- package/src/cli/result.ts +41 -10
- package/src/cli/series-search.ts +111 -0
- package/src/cli/staged-tickers.ts +1 -1
- package/src/cli/symbol-args.ts +18 -0
- package/src/cli/symbol-list.ts +1 -19
- package/src/cli/symbol-rows.ts +154 -0
- package/src/cli/ticker-resolution.ts +99 -8
- package/src/components/chart/composite/axis-overlays.tsx +8 -1
- package/src/components/chart/composite/composite-chart.tsx +41 -15
- package/src/components/chart/composite/crosshair-readout.tsx +150 -0
- package/src/components/chart/composite/downsample.ts +1 -1
- package/src/components/chart/composite/format.ts +29 -0
- package/src/components/chart/composite/interactions.ts +9 -2
- package/src/components/chart/composite/legend.tsx +52 -32
- package/src/components/chart/composite/panel-bitmap.ts +70 -35
- package/src/components/chart/composite/panel-labels.ts +18 -17
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +6 -8
- package/src/components/chart/composite/rasterizer.ts +50 -8
- package/src/components/chart/composite/scene.ts +2 -1
- package/src/components/chart/composite/time-scale.ts +26 -9
- package/src/components/chart/composite/types.ts +18 -0
- package/src/components/chart/composite/use-composite-viewport.ts +30 -2
- package/src/components/chart/native/raster/crosshair.ts +8 -3
- package/src/components/chart/native/raster/primitives.ts +85 -21
- package/src/components/chart/native/surface/manager.ts +33 -8
- package/src/components/chart/surface3d/chart.tsx +1 -1
- package/src/components/command-bar/action-runtime.ts +1 -1
- package/src/components/command-bar/commands/collection-list.ts +210 -0
- package/src/components/command-bar/commands/collection.ts +40 -1
- package/src/components/command-bar/commands/direct/index.ts +4 -0
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/routes/root/plugin-install.ts +4 -4
- package/src/components/command-bar/routing/actions.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +35 -29
- package/src/components/layout/detached-pane-shell.tsx +59 -39
- package/src/components/layout/floating-pane.tsx +7 -1
- package/src/components/layout/header.tsx +5 -0
- package/src/components/layout/missing-pane.tsx +3 -3
- package/src/components/layout/pane/capture-stamp.ts +41 -0
- package/src/components/layout/pane/capture-status.ts +12 -0
- package/src/components/layout/pane/footer/keyboard.ts +3 -0
- package/src/components/layout/pane/footer/model.ts +7 -1
- package/src/components/layout/pane/header.tsx +26 -0
- package/src/components/layout/pane/index.tsx +9 -1
- package/src/components/layout/pane/sidebar.tsx +10 -11
- package/src/components/layout/shell/cursor-occlusion.ts +2 -1
- package/src/components/layout/shell/index.tsx +61 -60
- package/src/components/layout/shell/menu.ts +9 -1
- package/src/components/layout/shell/pane/actions.ts +32 -10
- package/src/components/layout/shell/pane/ask.ts +131 -0
- package/src/components/layout/shell/pane/capture.ts +162 -0
- package/src/components/layout/shell/pane/layers.tsx +26 -5
- package/src/components/layout/shell/pane/management-shortcuts.ts +46 -1
- package/src/components/layout/shell/shortcuts.ts +21 -4
- package/src/components/metric-treemap/heat-surface.tsx +1 -3
- package/src/components/reader/article-reader.tsx +642 -0
- package/src/components/reader/article-text.ts +527 -0
- package/src/components/ticker/list-table-view.tsx +19 -1
- package/src/components/ui/data-table/index.tsx +1 -0
- package/src/components/ui/icon.tsx +4 -1
- package/src/components/ui/query-bar.tsx +10 -1
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/portfolio-as-of-column.ts +57 -0
- package/src/docs/snapshot.json +162 -23
- package/src/i18n/es.ts +4 -1
- package/src/i18n/ja.ts +4 -1
- package/src/i18n/ko.ts +4 -1
- package/src/i18n/zh-cn.ts +4 -1
- package/src/i18n/zh-tw.ts +4 -1
- package/src/layout-marketplace/gallery.tsx +1 -1
- package/src/market-data/market/freshness.ts +3 -2
- package/src/market-data/market/session-close-note.ts +3 -2
- package/src/market-data/quotes/contributions.ts +2 -0
- package/src/market-data/quotes/metadata.ts +12 -0
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/notifications/app-notifier.ts +17 -1
- package/src/notifications/notification-log.ts +180 -0
- package/src/plugins/auto-update-node.ts +2 -2
- package/src/plugins/auto-update.ts +2 -2
- package/src/plugins/builtin/account-management/index.tsx +1 -0
- package/src/plugins/builtin/alerts/catalysts.ts +1 -1
- package/src/plugins/builtin/alerts/focus.ts +27 -0
- package/src/plugins/builtin/alerts/index.tsx +6 -2
- package/src/plugins/builtin/alerts/pane.tsx +19 -1
- package/src/plugins/builtin/analytics/index.tsx +4 -1
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -15
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/awards/headless.ts +88 -18
- package/src/plugins/builtin/awards/model.ts +41 -1
- package/src/plugins/builtin/awards/pane.tsx +19 -12
- package/src/plugins/builtin/backtest/headless.ts +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +1 -1
- package/src/plugins/builtin/cds/client.ts +11 -31
- package/src/plugins/builtin/cds/headless.ts +5 -2
- package/src/plugins/builtin/cds/model.ts +21 -1
- package/src/plugins/builtin/cds/pane.tsx +6 -4
- package/src/plugins/builtin/central-bank-rates/client.ts +4 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +46 -16
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -1
- package/src/plugins/builtin/central-bank-rates/model.ts +99 -0
- package/src/plugins/builtin/central-bank-rates/pane.tsx +1 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +19 -25
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +1 -1
- package/src/plugins/builtin/chart-composer/headless.ts +65 -5
- package/src/plugins/builtin/chart-composer/index.tsx +6 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +81 -38
- package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
- package/src/plugins/builtin/chat/content/composer.tsx +5 -1
- package/src/plugins/builtin/chat/content/footer.tsx +12 -0
- package/src/plugins/builtin/chat/content/index.tsx +89 -5
- package/src/plugins/builtin/chat/content/new-messages.tsx +89 -0
- package/src/plugins/builtin/chat/content/row-activity.ts +132 -0
- package/src/plugins/builtin/chat/content/scroll.ts +127 -27
- package/src/plugins/builtin/chat/content/shortcuts.ts +2 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +145 -78
- package/src/plugins/builtin/chat/content/types.ts +1 -0
- package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
- package/src/plugins/builtin/chat/controller/index.ts +121 -1
- package/src/plugins/builtin/chat/controller/message-loading.ts +3 -0
- package/src/plugins/builtin/chat/controller/messages.ts +7 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +10 -1
- package/src/plugins/builtin/chat/controller/send.ts +61 -1
- package/src/plugins/builtin/chat/controller/state.ts +8 -0
- package/src/plugins/builtin/chat/ghost-user.ts +20 -0
- package/src/plugins/builtin/chat/index.tsx +23 -5
- package/src/plugins/builtin/chat/layout.ts +78 -5
- package/src/plugins/builtin/chat/message/ai-badge.tsx +18 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +55 -6
- package/src/plugins/builtin/chat/message/header.tsx +16 -4
- package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
- package/src/plugins/builtin/chat/message/reactions.tsx +210 -0
- package/src/plugins/builtin/chat/message/render-state.ts +18 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +43 -4
- package/src/plugins/builtin/chat/message/types.ts +12 -0
- package/src/plugins/builtin/chat/reactions.ts +197 -0
- package/src/plugins/builtin/chat/share/flow.tsx +87 -0
- package/src/plugins/builtin/chat/share/targets.ts +27 -0
- package/src/plugins/builtin/chat/status-widget.tsx +6 -3
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +148 -27
- package/src/plugins/builtin/chat/unread-inbox.ts +22 -0
- package/src/plugins/builtin/cloud/askg/attached-pane.tsx +69 -0
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +94 -0
- package/src/plugins/builtin/cloud/askg/pane.tsx +141 -12
- package/src/plugins/builtin/cloud/askg/pending-question.ts +11 -0
- package/src/plugins/builtin/cloud/plugin.tsx +19 -2
- package/src/plugins/builtin/cloud/team/model.ts +5 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/team/store.ts +8 -3
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +2 -2
- package/src/plugins/builtin/company-kpis/headless.ts +97 -10
- package/src/plugins/builtin/company-kpis/model.ts +24 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +13 -14
- package/src/plugins/builtin/connections/index.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cpi/headless.ts +3 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +10 -1
- package/src/plugins/builtin/credit-boards/client.ts +6 -2
- package/src/plugins/builtin/credit-boards/headless.ts +108 -31
- package/src/plugins/builtin/credit-boards/model.ts +11 -1
- package/src/plugins/builtin/credit-conditions/client.ts +12 -9
- package/src/plugins/builtin/credit-conditions/headless.ts +13 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +16 -9
- package/src/plugins/builtin/credit-conditions/model.ts +19 -11
- package/src/plugins/builtin/credit-conditions/series.ts +8 -18
- package/src/plugins/builtin/credit-documents/headless.ts +2 -2
- package/src/plugins/builtin/debt-maturities/headless.ts +17 -11
- package/src/plugins/builtin/dividend-yield/client.ts +3 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +5 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/types.ts +2 -0
- package/src/plugins/builtin/doe/headless.ts +12 -8
- package/src/plugins/builtin/earnings/index.tsx +9 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +28 -2
- package/src/plugins/builtin/earnings-calls/index.tsx +1 -1
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -0
- package/src/plugins/builtin/econ/fred-catalog.ts +9 -0
- package/src/plugins/builtin/equity-screener/headless.ts +3 -3
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -3
- package/src/plugins/builtin/exposure/drivers.ts +13 -0
- package/src/plugins/builtin/exposure/headless.ts +205 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -0
- package/src/plugins/builtin/exposure/model.ts +23 -3
- package/src/plugins/builtin/exposure/pane.tsx +11 -11
- package/src/plugins/builtin/fear-greed/index.tsx +14 -10
- package/src/plugins/builtin/futures-curve/headless.ts +1 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/fx-matrix/client.ts +63 -0
- package/src/plugins/builtin/fx-matrix/export.ts +2 -1
- package/src/plugins/builtin/fx-matrix/headless.ts +154 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +3 -3
- package/src/plugins/builtin/gpu/headless.ts +4 -4
- package/src/plugins/builtin/help/function-index.ts +2 -0
- package/src/plugins/builtin/help/index.tsx +14 -1
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -1
- package/src/plugins/builtin/iv-history/client.ts +6 -2
- package/src/plugins/builtin/iv-history/format.ts +9 -0
- package/src/plugins/builtin/iv-history/headless.ts +50 -18
- package/src/plugins/builtin/iv-history/model.ts +35 -4
- package/src/plugins/builtin/jobs/headless.ts +6 -8
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/market-heatmap/headless.ts +20 -5
- package/src/plugins/builtin/market-heatmap/index.tsx +6 -0
- package/src/plugins/builtin/members/headless.ts +1 -1
- package/src/plugins/builtin/members/pane.tsx +3 -2
- package/src/plugins/builtin/mna/distress/headless.ts +4 -4
- package/src/plugins/builtin/mna/headless.ts +3 -3
- package/src/plugins/builtin/money-markets/headless.ts +5 -4
- package/src/plugins/builtin/news/index.tsx +0 -2
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +53 -104
- package/src/plugins/builtin/news/wire/news/footer.ts +2 -25
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +0 -2
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/options/index.tsx +3 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -3
- package/src/plugins/builtin/perps/client.ts +50 -10
- package/src/plugins/builtin/perps/equity-row.tsx +29 -16
- package/src/plugins/builtin/perps/headless.ts +2 -2
- package/src/plugins/builtin/perps/index.tsx +5 -3
- package/src/plugins/builtin/perps/list-perpetuals.ts +118 -0
- package/src/plugins/builtin/perps/open-market.ts +15 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +6 -6
- package/src/plugins/builtin/portfolio-list/allocation.ts +5 -4
- package/src/plugins/builtin/portfolio-list/cell-version.ts +8 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +23 -135
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +59 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +248 -51
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +5 -12
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +3 -2
- package/src/plugins/builtin/portfolio-list/column-values.ts +103 -25
- package/src/plugins/builtin/portfolio-list/headless.ts +51 -15
- package/src/plugins/builtin/portfolio-list/index.tsx +5 -1
- package/src/plugins/builtin/portfolio-list/mutations.ts +2 -9
- package/src/plugins/builtin/portfolio-list/pane/data.ts +5 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +46 -8
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +16 -10
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +21 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +17 -0
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +8 -3
- package/src/plugins/builtin/portfolio-list/table.tsx +30 -6
- package/src/plugins/builtin/power/evidence.ts +57 -8
- package/src/plugins/builtin/power/headless.ts +6 -2
- package/src/plugins/builtin/power/index.tsx +24 -2
- package/src/plugins/builtin/power/model.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +5 -0
- package/src/plugins/builtin/power/prices-client.ts +139 -0
- package/src/plugins/builtin/power/prices-headless.ts +137 -0
- package/src/plugins/builtin/power/prices-model.ts +103 -0
- package/src/plugins/builtin/power/prices.tsx +179 -0
- package/src/plugins/builtin/rate-path/headless.ts +38 -18
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/research/index.tsx +2 -0
- package/src/plugins/builtin/research-search/article.ts +65 -0
- package/src/plugins/builtin/research-search/document-view.tsx +33 -215
- package/src/plugins/builtin/research-search/model.ts +0 -9
- package/src/plugins/builtin/research-search/pane.tsx +41 -30
- package/src/plugins/builtin/research-search/snippet.ts +0 -29
- package/src/plugins/builtin/revenue-breakdown/headless.ts +18 -6
- package/src/plugins/builtin/sec/headless.ts +25 -3
- package/src/plugins/builtin/sec/index.tsx +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +15 -0
- package/src/plugins/builtin/shared/free-preview.ts +47 -0
- package/src/plugins/builtin/shared/report-freshness.ts +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +4 -1
- package/src/plugins/builtin/short-interest/watch-headless.ts +2 -2
- package/src/plugins/builtin/short-volume/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/supply-chain/headless.ts +43 -11
- package/src/plugins/builtin/supply-chain/model.ts +13 -0
- package/src/plugins/builtin/supply-chain/pane.tsx +5 -5
- package/src/plugins/builtin/themes/headless.ts +74 -10
- package/src/plugins/builtin/themes/model.ts +71 -4
- package/src/plugins/builtin/themes/pane.tsx +22 -5
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +114 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +57 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +19 -55
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +30 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +86 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +23 -11
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/time-sales/headless.ts +1 -1
- package/src/plugins/builtin/time-sales/index.tsx +2 -1
- package/src/plugins/builtin/vol-surface/evidence.ts +7 -1
- package/src/plugins/builtin/vol-surface/headless.ts +50 -17
- package/src/plugins/builtin/vol-surface/heat-grid.tsx +113 -0
- package/src/plugins/builtin/vol-surface/model.ts +39 -6
- package/src/plugins/builtin/vol-surface/pane.tsx +14 -13
- package/src/plugins/builtin/vol-surface/stored.ts +63 -3
- package/src/plugins/builtin/vol-surface/surface.tsx +30 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +6 -5
- package/src/plugins/builtin/world-venue-map/basemap.ts +6 -3
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +16 -9
- package/src/plugins/builtin/world-venue-map/map.tsx +144 -13
- package/src/plugins/builtin/world-venue-map/model.ts +56 -19
- package/src/plugins/builtin/world-venue-map/pane.tsx +5 -2
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/installer.ts +2 -2
- package/src/plugins/loader.ts +15 -0
- package/src/plugins/manager-node.ts +1 -1
- package/src/plugins/{builtin/plugin-marketplace → marketplace}/feed.ts +2 -2
- package/src/plugins/{builtin/plugin-marketplace → marketplace}/model.ts +6 -6
- package/src/plugins/{builtin/plugin-marketplace → marketplace}/store.ts +1 -1
- package/src/plugins/registry/host-actions.ts +11 -0
- package/src/portfolio/collection-name.ts +31 -0
- package/src/portfolio/valuation-basis.ts +79 -0
- package/src/public/time-series.ts +1 -1
- package/src/react/input.ts +33 -1
- package/src/react/key-repeat.ts +86 -0
- package/src/remote/controller.ts +21 -1
- package/src/remote/schema.ts +3 -2
- package/src/renderers/browser/main.tsx +1 -1
- package/src/renderers/browser/research-entry.ts +4 -2
- package/src/renderers/dom/data-table/row.tsx +3 -0
- package/src/renderers/dom/desktop/icons.tsx +9 -0
- package/src/renderers/dom/host/block-glyph-canvas.tsx +6 -3
- package/src/renderers/dom/host/box.tsx +7 -2
- package/src/renderers/dom/host/chart-surface.tsx +27 -0
- package/src/renderers/dom/host/scroll-box.tsx +32 -0
- package/src/renderers/dom/host/size-observer.ts +24 -9
- package/src/renderers/dom/host/sticky-scroll.ts +128 -0
- package/src/renderers/dom/host/style.ts +1 -1
- package/src/renderers/dom/host/surface-3d.tsx +13 -6
- package/src/renderers/dom/host/surface-fit.ts +406 -0
- package/src/renderers/dom/input-host.tsx +1 -0
- package/src/renderers/dom/styles.css +48 -12
- package/src/renderers/electrobun/bun/application-menu/click.ts +18 -0
- package/src/renderers/electrobun/bun/index.ts +3 -2
- package/src/renderers/electrobun/shared/protocol.ts +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +12 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +59 -24
- package/src/renderers/electrobun/view/main.tsx +3 -3
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +2 -0
- package/src/renderers/opentui/chart-surface.tsx +1 -0
- package/src/renderers/opentui/frame-capture.ts +33 -0
- package/src/renderers/opentui/host.tsx +10 -13
- package/src/renderers/opentui/pane-image/glyphs-license.txt +107 -0
- package/src/renderers/opentui/pane-image/glyphs.json +1 -0
- package/src/renderers/opentui/pane-image/index.ts +107 -0
- package/src/renderers/opentui/pane-image/raster.ts +428 -0
- package/src/renderers/opentui/start.tsx +3 -3
- package/src/renderers/opentui/test-utils.tsx +3 -0
- package/src/renderers/opentui/ui-host.tsx +29 -1
- package/src/series/resolve.ts +3 -2
- package/src/series/types.ts +8 -0
- package/src/series/validate.ts +1 -1
- package/src/shares/chat.ts +34 -0
- package/src/shares/dialog.tsx +295 -0
- package/src/shares/flow.tsx +56 -0
- package/src/shares/live.ts +14 -22
- package/src/shares/pane-image.ts +152 -0
- package/src/shares/personal-pane.ts +64 -0
- package/src/shares/portable-layout.ts +4 -2
- package/src/shares/prepare.ts +20 -0
- package/src/sources/gloomberb-cloud/index.ts +102 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/gloomberb-cloud/perpetuals.ts +249 -0
- package/src/sources/history-retention.ts +11 -0
- package/src/sources/listing-target.ts +4 -0
- package/src/sources/provider-router/index.ts +14 -0
- package/src/state/hooks/inline-tickers.ts +49 -11
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/theme-context.tsx +15 -1
- package/src/tickers/instrument-kind.ts +9 -1
- package/src/tickers/listing-name.ts +34 -0
- package/src/tickers/listing-venues.ts +132 -0
- package/src/tickers/open-target.ts +11 -3
- package/src/tickers/perpetual-listing.ts +151 -0
- package/src/tickers/prediction-listing.ts +26 -0
- package/src/tickers/search/index.ts +27 -4
- package/src/tickers/search/ranking.ts +66 -4
- package/src/tickers/ticker-changes.ts +213 -0
- package/src/time-series/economic.ts +2 -6
- package/src/time-series/ice-credit.ts +39 -0
- package/src/time-series/resolve.ts +116 -13
- package/src/time-series/studies.ts +1 -0
- package/src/time-series/types.ts +8 -0
- package/src/types/config.ts +4 -2
- package/src/types/data-provider.ts +6 -0
- package/src/types/financials.ts +10 -0
- package/src/types/headless.ts +52 -0
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +33 -2
- package/src/types/ticker-changes.ts +26 -0
- package/src/ui/context-menu.tsx +13 -1
- package/src/ui/host.tsx +43 -7
- package/src/ui/live-resize.ts +10 -0
- package/src/ui/live-stretch.ts +51 -19
- package/src/ui/sticky-scroll.ts +7 -0
- package/src/utils/central-banks.ts +76 -0
- package/src/utils/cli-output.ts +126 -16
- package/src/utils/currency-units.ts +11 -0
- package/src/utils/datetime-format.ts +27 -6
- package/src/utils/dom-screenshot.ts +3 -1
- package/src/utils/double-escape-close.ts +15 -1
- package/src/utils/exchanges.ts +36 -0
- package/src/utils/format.ts +19 -2
- package/src/utils/forward-dividend.ts +14 -0
- package/src/utils/fx-status.ts +6 -1
- package/src/utils/inline-content-tokenizer.ts +22 -3
- package/src/utils/market-capitalization.ts +8 -0
- package/src/utils/one-shot.ts +73 -0
- package/src/utils/options.ts +37 -8
- package/src/utils/utc-time.ts +12 -0
- package/src/ui/pane-fill.ts +0 -11
- /package/src/plugins/{builtin/plugin-marketplace → marketplace}/activation.ts +0 -0
- /package/src/plugins/{builtin/plugin-marketplace → marketplace}/ids.ts +0 -0
- /package/src/plugins/{builtin/plugin-marketplace → marketplace}/packs.ts +0 -0
|
@@ -3,6 +3,8 @@ import type { HeadlessPaneColumn, HeadlessPaneDefinition, HeadlessPaneRow } from
|
|
|
3
3
|
import { withoutQuietColumns } from "../shared/research-cells";
|
|
4
4
|
import { fetchPowerBoard, fetchAllPowerHistory, fetchPowerTickerScopes } from "./client";
|
|
5
5
|
import { powerQuery } from "./query";
|
|
6
|
+
import { loadPowerPricesReport } from "./prices-headless";
|
|
7
|
+
import { isCarbonQuery, powerPriceMarket } from "./prices-model";
|
|
6
8
|
import {
|
|
7
9
|
COVERAGE_COLUMNS, EXPOSURE_COLUMNS, GENERATION_COLUMNS, HISTORY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, UTILITY_COLUMNS,
|
|
8
10
|
historyRows, powerCoverageNote, powerLoadedSources, powerNumber, powerPercent, powerRegion, powerScopeOptions, powerTab, powerTickerEmpty, projectRows, tickerLabel, titleCase, UNMAPPED, type PowerTab,
|
|
@@ -47,18 +49,20 @@ export const powerHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
47
49
|
freshness: { source: "Grid operator queues and utility filings", status: "not-a-feed", basis: "published records", dataset: true },
|
|
48
50
|
argument: { kind: "free-text", optional: true, placeholder: "ticker", description: "Optional listed developer or utility ticker, with exchange suffix where needed." },
|
|
49
51
|
options: [
|
|
50
|
-
{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "queue", values: POWER_TABS.map(({ value }) => ({ value })), description: "Queue, history, outcomes, large loads, utility exposure, capacity or coverage." },
|
|
52
|
+
{ key: "tab", type: "enum", settingKey: "tab", defaultValue: "queue", values: POWER_TABS.map(({ value }) => ({ value })), description: "Queue, history, outcomes, large loads, utility exposure, capacity, European day-ahead prices or coverage." },
|
|
51
53
|
...["country", "region", "fuel", "status", "search", "sourceId", "from", "to"].map((key) => ({ key, type: "string" as const, settingKey: key, description: `Filter by ${key}.` })),
|
|
52
54
|
{ key: "context", type: "enum", settingKey: "context", values: [{ value: "capacity" }, { value: "generation" }, { value: "utility" }], defaultValue: "capacity", description: "Generation capacity, monthly generation or utility context on the Capacity tab." },
|
|
53
55
|
{ key: "loadClass", aliases: ["load-class"], type: "enum", settingKey: "loadClass", values: [{ value: "datacenter" }], description: "Only loads explicitly classified as datacenters." },
|
|
54
56
|
{ key: "historical", type: "boolean", settingKey: "historical", defaultValue: false, description: "Use historical benchmark queues separately from direct observations." },
|
|
55
57
|
],
|
|
56
58
|
discovery: { aliases: ["POWER"], dataRequirements: ["Gloom Cloud power queues and large-load disclosures"],
|
|
57
|
-
limitations: ["Pro dataset; free accounts see three rows per section and limited history", "Proposed projects are not committed supply; requests and approvals are distinct", "Coverage varies by jurisdiction; missing MW and unavailable sources remain explicit"] },
|
|
59
|
+
limitations: ["Pro dataset; free accounts see three rows per section and limited history.", "Proposed projects are not committed supply; requests and approvals are distinct.", "Coverage varies by jurisdiction; missing MW and unavailable sources remain explicit."] },
|
|
58
60
|
describe: "Power queues, large loads and utility exposure (Pro)",
|
|
59
61
|
async load(args, ctx) {
|
|
60
62
|
const tab = powerTab(args.options.tab);
|
|
61
63
|
const symbol = (Array.isArray(args.argument) ? args.argument.join(" ") : args.argument)?.trim();
|
|
64
|
+
// Prices are their own board (EPWR); a typed market narrows it, a ticker does not apply.
|
|
65
|
+
if (tab === "prices") return loadPowerPricesReport(powerPriceMarket(symbol) || isCarbonQuery(symbol) ? symbol : undefined, ctx.apiClient);
|
|
62
66
|
const filter = { ...powerQuery(args.options, symbol), limit: 500 };
|
|
63
67
|
const board = await fetchPowerBoard(filter, ctx.apiClient);
|
|
64
68
|
const projects = [...board.projects];
|
|
@@ -2,8 +2,14 @@ import type { PluginModule } from "../plugin-module";
|
|
|
2
2
|
import { powerBoardCache, powerDetailCache, powerHistoryCache } from "./client";
|
|
3
3
|
import { powerHeadless } from "./headless";
|
|
4
4
|
import { PowerPane } from "./pane";
|
|
5
|
+
import { carbonHistoryCache, carbonPriceCache, powerPriceHistoryCache, powerPricesCache } from "./prices-client";
|
|
6
|
+
import { powerPricesHeadless } from "./prices-headless";
|
|
7
|
+
import { CARBON_SELECTION, CARBON_SERIES, DAY_AHEAD_SERIES, isCarbonQuery, POWER_PRICE_MARKETS, powerPriceMarket } from "./prices-model";
|
|
8
|
+
|
|
9
|
+
const CACHES = [powerBoardCache, powerHistoryCache, powerDetailCache, powerPricesCache, powerPriceHistoryCache, carbonPriceCache, carbonHistoryCache];
|
|
5
10
|
|
|
6
11
|
export const powerModule: PluginModule = {
|
|
12
|
+
series: [...DAY_AHEAD_SERIES, ...CARBON_SERIES],
|
|
7
13
|
panes: [{ id: "power", name: "Power & Grid", icon: "P", component: PowerPane, defaultPosition: "right", defaultMode: "floating",
|
|
8
14
|
defaultFloatingSize: { width: 132, height: 34 }, tableExport: true, headless: powerHeadless }],
|
|
9
15
|
paneTemplates: [{ id: "power-pane", paneId: "power", label: "Power & Grid",
|
|
@@ -13,7 +19,23 @@ export const powerModule: PluginModule = {
|
|
|
13
19
|
createInstance: (_context, options) => {
|
|
14
20
|
const symbol = options?.symbol ?? options?.arg?.trim().toUpperCase() ?? "";
|
|
15
21
|
return { placement: "floating", params: { symbol }, ...(symbol ? { title: `POWER ${symbol}` } : {}) };
|
|
22
|
+
} }, {
|
|
23
|
+
id: "power-prices-pane", paneId: "power", label: "EU Power & Carbon",
|
|
24
|
+
description: "Day-ahead power prices for 32 European markets in EUR/MWh and the EU carbon allowance (EUA) auction price in EUR/tCO2: the latest day, 7- and 30-day averages, a year ago and the 52-week range, with daily history.",
|
|
25
|
+
keywords: ["day-ahead", "day ahead", "power price", "power prices", "electricity price", "electricity prices", "wholesale power",
|
|
26
|
+
"spot power", "baseload", "europe", "european", "eur/mwh", "carbon", "eu carbon", "eua", "eu ets", "carbon price",
|
|
27
|
+
"carbon allowance", "emission allowances", "co2", "eur/tco2"],
|
|
28
|
+
shortcut: { prefix: "EPWR", argKind: "text", argPlaceholder: "market", argOptional: true,
|
|
29
|
+
argOptions: () => [{ value: "EUA", label: "EU carbon allowance" }, ...POWER_PRICE_MARKETS.map(({ code, name }) => ({ value: code, label: name }))] },
|
|
30
|
+
headless: powerPricesHeadless,
|
|
31
|
+
createInstance: (_context, options) => {
|
|
32
|
+
const input = options?.arg?.trim();
|
|
33
|
+
const market = powerPriceMarket(input);
|
|
34
|
+
// EPWR EUA opens the tab with the carbon allowance charted above the board.
|
|
35
|
+
const carbon = isCarbonQuery(input);
|
|
36
|
+
if (input && !market && !carbon) throw new Error("Use a market such as DE, FR, GB or Spain, or EUA");
|
|
37
|
+
return { placement: "floating", settings: { tab: "prices" }, ...(market || carbon ? { params: { market: market?.code ?? CARBON_SELECTION } } : {}) };
|
|
16
38
|
} }],
|
|
17
|
-
setup(ctx) { for (const cache of
|
|
18
|
-
dispose() { for (const cache of
|
|
39
|
+
setup(ctx) { for (const cache of CACHES) cache.attach(ctx.persistence); },
|
|
40
|
+
dispose() { for (const cache of CACHES) cache.reset(); },
|
|
19
41
|
};
|
|
@@ -2,7 +2,7 @@ import type { PowerAggregate, PowerBoard, PowerCoverage, PowerExposure, PowerHis
|
|
|
2
2
|
import { staticSeries, type DataTableColumn } from "../../../components";
|
|
3
3
|
|
|
4
4
|
export const POWER_TABS = [{ value: "queue", label: "Queue" }, { value: "history", label: "History" }, { value: "outcomes", label: "Outcomes" },
|
|
5
|
-
{ value: "loads", label: "Loads" }, { value: "utilities", label: "Utilities" }, { value: "capacity", label: "Capacity" }, { value: "coverage", label: "Coverage" }] as const;
|
|
5
|
+
{ value: "loads", label: "Loads" }, { value: "utilities", label: "Utilities" }, { value: "capacity", label: "Capacity" }, { value: "prices", label: "Prices" }, { value: "coverage", label: "Coverage" }] as const;
|
|
6
6
|
export type PowerTab = typeof POWER_TABS[number]["value"];
|
|
7
7
|
export const powerTab = (value: unknown): PowerTab => POWER_TABS.find((tab) => tab.value === value)?.value ?? "queue";
|
|
8
8
|
const REGISTERS: Record<string, string> = { "neso-tec": "GB transmission", "neso-embedded": "GB embedded", "neso-interconnector": "GB interconnectors" };
|
|
@@ -62,7 +62,7 @@ export const powerTickerScopes = (tab: PowerTab, context: string) => POWER_TICKE
|
|
|
62
62
|
function powerTabRecords(tab: PowerTab, context: string): string {
|
|
63
63
|
if (tab === "capacity") return POWER_TICKER_SCOPES.find((scope) => scope.tab === "capacity" && scope.context === context)?.label ?? "capacity records";
|
|
64
64
|
return { queue: "interconnection queue records", history: "queue history records", outcomes: "queue outcomes", loads: "large-load requests",
|
|
65
|
-
utilities: "large-load requests or utility exposure", coverage: "records" }[tab];
|
|
65
|
+
utilities: "large-load requests or utility exposure", prices: "records", coverage: "records" }[tab];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -20,6 +20,7 @@ import { fetchPowerTickerScopes, loadPowerBoard, loadPowerHistory, validatePower
|
|
|
20
20
|
import { usePowerEvidence } from "./evidence";
|
|
21
21
|
import { POWER_SORT_COLUMNS, powerQuery } from "./query";
|
|
22
22
|
import { PowerDetailView } from "./detail";
|
|
23
|
+
import { PowerPricesView } from "./prices";
|
|
23
24
|
import { blendHex } from "../../../theme/colors";
|
|
24
25
|
import { missingCell, PartsBar, signedColor, toneColor, withoutQuietColumns, type StateTone } from "../shared/research-cells";
|
|
25
26
|
import { COVERAGE_COLUMNS, EXPOSURE_COLUMNS, HISTORY_COLUMNS, GENERATION_COLUMNS, UTILITY_COLUMNS, POWER_TABS, PROJECT_COLUMNS, RATE_COLUMNS, historyDate, historyId, historySeries, otherRows, powerCoverageNote, powerFigures, powerLoadedSources, powerRegion, powerNumber, powerPercent, powerTab, powerTickerEmpty, projectRows, titleCase, UNMAPPED, type PowerRow, type PowerTickerScope } from "./model";
|
|
@@ -38,7 +39,11 @@ const UTILITY_SORT: typeof DEFAULT_SORT = { column: "salesMwh", direction: "desc
|
|
|
38
39
|
export function PowerPane(props: PaneProps) {
|
|
39
40
|
const session = useResearchCloudSession();
|
|
40
41
|
const access = usePlanAccess();
|
|
42
|
+
const [openingTab] = usePaneSettingValue("tab", "queue");
|
|
43
|
+
const [savedTab] = usePluginPaneState("power:tab", openingTab);
|
|
41
44
|
const scope = `${session.requestKey}:${access.hasProAccess ? "full" : "preview"}`;
|
|
45
|
+
// Prices are open data from another source: their own view, free and without the queue board.
|
|
46
|
+
if (powerTab(savedTab) === "prices") return <PowerPricesView {...props} />;
|
|
42
47
|
return <PowerView key={scope} {...props} scope={scope} needsVerification={session.needsVerification} />;
|
|
43
48
|
}
|
|
44
49
|
function PowerView({ width, height, focused, scope, needsVerification }: PaneProps & { scope: string; needsVerification: boolean }) {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import type { CarbonBoard, CarbonHistory, EuropePowerBoard, EuropePowerHistory, PowerPriceRow } from "../../../api-client/europe-energy";
|
|
3
|
+
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
5
|
+
|
|
6
|
+
/** What the tab and the series say while the server has no prices to serve: before the backend ships, or before its first read. */
|
|
7
|
+
export const POWER_PRICES_NOT_AVAILABLE = "European power prices are not available yet.";
|
|
8
|
+
const CARBON_PRICE_NOT_AVAILABLE = "The EU carbon auction price is not available yet.";
|
|
9
|
+
|
|
10
|
+
// Daily data, rewritten once a day: a quarter hour is soon enough to catch it.
|
|
11
|
+
const policy = { staleMs: 15 * 60_000, expireMs: 14 * 86_400_000 };
|
|
12
|
+
export const powerPricesCache = createPluginCache<EuropePowerBoard>({ kind: "europe-power-board", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
13
|
+
export const powerPriceHistoryCache = createPluginCache<EuropePowerHistory>({ kind: "europe-power-history", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
14
|
+
export const carbonPriceCache = createPluginCache<CarbonBoard>({ kind: "europe-carbon-board", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
15
|
+
export const carbonHistoryCache = createPluginCache<CarbonHistory>({ kind: "europe-carbon-history", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
16
|
+
|
|
17
|
+
const record = (v: unknown): v is Record<string, any> => !!v && typeof v === "object" && !Array.isArray(v);
|
|
18
|
+
const text = (v: unknown): v is string => typeof v === "string";
|
|
19
|
+
const finite = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
20
|
+
const date = (v: unknown) => text(v) && /^\d{4}-\d{2}-\d{2}$/.test(v) && new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;
|
|
21
|
+
const dateOrNull = (v: unknown) => v === null || date(v);
|
|
22
|
+
const finiteOrNull = (v: unknown) => v === null || finite(v);
|
|
23
|
+
const instantOrNull = (v: unknown) => v === null || text(v) && Number.isFinite(Date.parse(v));
|
|
24
|
+
const source = (v: unknown) => record(v) && text(v.name) && text(v.licence) && text(v.url);
|
|
25
|
+
const publisher = (v: unknown) => record(v) && text(v.name) && text(v.url);
|
|
26
|
+
const nullableText = (v: unknown) => v === null || text(v);
|
|
27
|
+
const nonnegativeOrNull = (v: unknown) => v === null || finite(v) && v >= 0;
|
|
28
|
+
const points = (v: unknown) => Array.isArray(v) && v.every((p) => record(p) && date(p.date) && finite(p.value));
|
|
29
|
+
|
|
30
|
+
function validRow(row: PowerPriceRow, code = /^[A-Z]{2}$/): boolean {
|
|
31
|
+
return record(row) && code.test(row.code) && text(row.name) && dateOrNull(row.date) && finiteOrNull(row.price)
|
|
32
|
+
&& (row.price == null) === (row.date == null) && dateOrNull(row.previousDate) && dateOrNull(row.yearAgoDate)
|
|
33
|
+
&& [row.change, row.average7, row.average30, row.yearAgo, row.low52w, row.high52w].every(finiteOrNull) && points(row.trail);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A board that breaks the contract is refused whole rather than drawn with holes in it. */
|
|
37
|
+
export function validatePowerPrices(data: EuropePowerBoard): EuropePowerBoard {
|
|
38
|
+
if (!record(data) || !text(data.generatedAt) || !instantOrNull(data.generatedAt) || !["available", "unavailable"].includes(data.status)
|
|
39
|
+
|| data.market !== "day-ahead" || data.unit !== "EUR/MWh" || !source(data.source)
|
|
40
|
+
|| !instantOrNull(data.checkedAt) || !instantOrNull(data.publishedAt)
|
|
41
|
+
|| !Array.isArray(data.gaps) || !data.gaps.every(text)
|
|
42
|
+
|| !Array.isArray(data.rows) || !data.rows.every((row) => validRow(row)) || new Set(data.rows.map((row) => row.code)).size !== data.rows.length) {
|
|
43
|
+
throw new Error("The server returned unreadable power prices.");
|
|
44
|
+
}
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateHistory(data: EuropePowerHistory): EuropePowerHistory {
|
|
49
|
+
if (!record(data) || !/^[A-Z]{2}$/.test(data.code) || !text(data.name) || data.unit !== "EUR/MWh" || !source(data.source) || !points(data.points)) {
|
|
50
|
+
throw new Error("The server returned unreadable power price history.");
|
|
51
|
+
}
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An EUA board that breaks the contract is refused whole. */
|
|
56
|
+
export function validateCarbonPrice(data: CarbonBoard): CarbonBoard {
|
|
57
|
+
const row = data?.row;
|
|
58
|
+
if (!record(data) || !text(data.generatedAt) || !instantOrNull(data.generatedAt) || !["available", "unavailable"].includes(data.status)
|
|
59
|
+
|| data.market !== "primary auction" || data.unit !== "EUR/tCO2" || !publisher(data.source)
|
|
60
|
+
|| !instantOrNull(data.checkedAt) || !instantOrNull(data.publishedAt) || !Array.isArray(data.gaps) || !data.gaps.every(text)
|
|
61
|
+
|| !record(row) || !validRow(row, /^EUA$/) || !nonnegativeOrNull(row.volume) || !nonnegativeOrNull(row.auctions)
|
|
62
|
+
|| !Array.isArray(data.auctions) || !data.auctions.every((a) => record(a) && date(a.date) && text(a.auction) && text(a.contract) && text(a.status)
|
|
63
|
+
&& nullableText(a.zone) && [a.price, a.minBid, a.maxBid, a.meanBid, a.medianBid].every(finiteOrNull) && finite(a.volume)
|
|
64
|
+
&& [a.coverRatio, a.bidders, a.successfulBidders].every(nonnegativeOrNull) && typeof a.aviation === "boolean")) {
|
|
65
|
+
throw new Error("The server returned an unreadable EU carbon price.");
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateCarbonHistory(data: CarbonHistory): CarbonHistory {
|
|
71
|
+
if (!record(data) || data.code !== "EUA" || data.unit !== "EUR/tCO2" || !publisher(data.source) || !points(data.points)) {
|
|
72
|
+
throw new Error("The server returned an unreadable EU carbon price history.");
|
|
73
|
+
}
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type PricesApi = Pick<typeof apiClient, "getCloudEuropePower" | "getCloudEuropePowerHistory" | "getCloudEuropeCarbon" | "getCloudEuropeCarbonHistory">;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The board, or the not-available message when the server does not serve the
|
|
81
|
+
* route yet (404), cannot read its tables (503) or has no days stored.
|
|
82
|
+
*/
|
|
83
|
+
export async function fetchPowerPrices(client: Pick<PricesApi, "getCloudEuropePower"> = apiClient): Promise<EuropePowerBoard> {
|
|
84
|
+
let board: EuropePowerBoard;
|
|
85
|
+
try {
|
|
86
|
+
board = validatePowerPrices(await client.getCloudEuropePower());
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw unavailableOnServer(error, POWER_PRICES_NOT_AVAILABLE, [404, 503]);
|
|
89
|
+
}
|
|
90
|
+
if (board.status === "unavailable") throw new Error(POWER_PRICES_NOT_AVAILABLE);
|
|
91
|
+
return board;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One market's days from `from`, all of them without one. */
|
|
95
|
+
export async function fetchPowerPriceHistory(
|
|
96
|
+
code: string, from?: string | null, client: Pick<PricesApi, "getCloudEuropePowerHistory"> = apiClient,
|
|
97
|
+
): Promise<EuropePowerHistory> {
|
|
98
|
+
try {
|
|
99
|
+
return validateHistory(await client.getCloudEuropePowerHistory(code, from));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw unavailableOnServer(error, POWER_PRICES_NOT_AVAILABLE, [404, 503]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The EUA board, or the not-available message before the server serves it or has an auction stored. */
|
|
106
|
+
export async function fetchCarbonPrice(client: Pick<PricesApi, "getCloudEuropeCarbon"> = apiClient): Promise<CarbonBoard> {
|
|
107
|
+
let board: CarbonBoard;
|
|
108
|
+
try {
|
|
109
|
+
board = validateCarbonPrice(await client.getCloudEuropeCarbon());
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw unavailableOnServer(error, CARBON_PRICE_NOT_AVAILABLE, [404, 503]);
|
|
112
|
+
}
|
|
113
|
+
if (board.status === "unavailable") throw new Error(CARBON_PRICE_NOT_AVAILABLE);
|
|
114
|
+
return board;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The daily EUA price from `from`, every auction day since 2020 without one. */
|
|
118
|
+
export async function fetchCarbonHistory(
|
|
119
|
+
from?: string | null, client: Pick<PricesApi, "getCloudEuropeCarbonHistory"> = apiClient,
|
|
120
|
+
): Promise<CarbonHistory> {
|
|
121
|
+
try {
|
|
122
|
+
return validateCarbonHistory(await client.getCloudEuropeCarbonHistory(from));
|
|
123
|
+
} catch (error) {
|
|
124
|
+
throw unavailableOnServer(error, CARBON_PRICE_NOT_AVAILABLE, [404, 503]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const getCachedPowerPrices = () => cachedCloudResource(powerPricesCache, "board", validatePowerPrices);
|
|
129
|
+
export const loadPowerPrices = (force = false) =>
|
|
130
|
+
loadCloudResource(powerPricesCache, "board", () => fetchPowerPrices(), { force, validate: validatePowerPrices });
|
|
131
|
+
export const getCachedCarbonPrice = () => cachedCloudResource(carbonPriceCache, "board", validateCarbonPrice);
|
|
132
|
+
export const loadCarbonPrice = (force = false) =>
|
|
133
|
+
loadCloudResource(carbonPriceCache, "board", () => fetchCarbonPrice(), { force, validate: validateCarbonPrice });
|
|
134
|
+
/** Every auction day of the EUA since 2020. */
|
|
135
|
+
export const loadCarbonHistory = (force = false) =>
|
|
136
|
+
loadCloudResource(carbonHistoryCache, "EUA", () => fetchCarbonHistory(), { force, validate: validateCarbonHistory });
|
|
137
|
+
/** The whole history of one market, cached per market. */
|
|
138
|
+
export const loadPowerPriceHistory = (code: string, force = false) =>
|
|
139
|
+
loadCloudResource(powerPriceHistoryCache, code, () => fetchPowerPriceHistory(code), { force, validate: validateHistory });
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import type { CarbonBoard, EuropePowerBoard, PowerPriceRow } from "../../../api-client/europe-energy";
|
|
3
|
+
import type { HeadlessBundleResult, HeadlessBundleSection, HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
4
|
+
import { fetchCarbonPrice, fetchPowerPriceHistory, fetchPowerPrices } from "./prices-client";
|
|
5
|
+
import {
|
|
6
|
+
CARBON_PRICE_UNIT, formatPowerChange, formatPowerPrice, isCarbonQuery, POWER_PRICE_UNIT, powerPriceMarket, powerRangePosition,
|
|
7
|
+
} from "./prices-model";
|
|
8
|
+
|
|
9
|
+
type PricesClient = Pick<typeof apiClient, "getCloudEuropePower" | "getCloudEuropePowerHistory" | "getCloudEuropeCarbon">;
|
|
10
|
+
|
|
11
|
+
const price = (value: unknown) => typeof value === "number" ? formatPowerPrice(value) : "--";
|
|
12
|
+
const text = (value: unknown) => typeof value === "string" && value ? value : "--";
|
|
13
|
+
|
|
14
|
+
/** A board's columns, in its words: prices to the cent in the board's unit, each row dated by its own day. */
|
|
15
|
+
const boardColumns = (unit: string, label = "Market"): HeadlessPaneColumn[] => [
|
|
16
|
+
{ key: "market", header: label },
|
|
17
|
+
{ key: "date", header: "As of", format: text, shrink: false },
|
|
18
|
+
{ key: "price", header: unit, align: "right", format: price },
|
|
19
|
+
{ key: "change", header: "1D", align: "right", format: (value) => typeof value === "number" ? formatPowerChange(value) : "--" },
|
|
20
|
+
{ key: "average7", header: "7D avg", align: "right", format: price },
|
|
21
|
+
{ key: "average30", header: "30D avg", align: "right", format: price },
|
|
22
|
+
{ key: "yearAgo", header: "1Y ago", align: "right", format: price },
|
|
23
|
+
{ key: "low52w", header: "52W low", align: "right", format: price },
|
|
24
|
+
{ key: "high52w", header: "52W high", align: "right", format: price },
|
|
25
|
+
{ key: "range", header: "In 1Y range", align: "right", format: (value) => typeof value === "number" ? `${value}%` : "--" },
|
|
26
|
+
];
|
|
27
|
+
const COLUMNS = boardColumns(POWER_PRICE_UNIT);
|
|
28
|
+
const AUCTION_COLUMNS: HeadlessPaneColumn[] = [
|
|
29
|
+
{ key: "date", header: "Date", shrink: false },
|
|
30
|
+
{ key: "zone", header: "Zone", format: text },
|
|
31
|
+
{ key: "status", header: "Status" },
|
|
32
|
+
{ key: "price", header: CARBON_PRICE_UNIT, align: "right", format: price },
|
|
33
|
+
{ key: "volume", header: "Volume tCO2", align: "right", format: (value) => typeof value === "number" ? value.toLocaleString("en-US") : "--" },
|
|
34
|
+
{ key: "coverRatio", header: "Cover", align: "right", format: (value) => typeof value === "number" ? `${value.toFixed(2)}x` : "--" },
|
|
35
|
+
{ key: "bidders", header: "Bidders", align: "right", format: (value) => typeof value === "number" ? String(value) : "--" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function reportRow(row: Omit<PowerPriceRow, "code"> & { code: string }) {
|
|
39
|
+
const { trail: _trail, name, ...figures } = row;
|
|
40
|
+
return { market: name, ...figures, range: powerRangePosition(row as PowerPriceRow) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The EUA as of its latest auction day, then its latest general-allowance auctions. */
|
|
44
|
+
function carbonSections(carbon: CarbonBoard): HeadlessBundleSection[] {
|
|
45
|
+
const { volume: _volume, auctions: _auctions, ...row } = carbon.row;
|
|
46
|
+
return [
|
|
47
|
+
{ title: `EU carbon allowance (EUA) auction price, ${CARBON_PRICE_UNIT}`, columns: boardColumns(CARBON_PRICE_UNIT, "Allowance"), rows: [reportRow(row)] },
|
|
48
|
+
{ title: "Latest EUA auctions", columns: AUCTION_COLUMNS,
|
|
49
|
+
rows: carbon.auctions.filter((auction) => !auction.aviation).map(({ aviation: _aviation, ...auction }) => ({ ...auction })) },
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The delivery day most markets have reached: the median market's latest
|
|
55
|
+
* day. A few markets publish ahead of the rest (Ireland the day before, most
|
|
56
|
+
* of the continent four days behind), so the freshest one would understate
|
|
57
|
+
* how far behind the board is.
|
|
58
|
+
*/
|
|
59
|
+
function typicalPowerDate(rows: readonly Pick<PowerPriceRow, "date">[]): string | null {
|
|
60
|
+
const dates = rows.flatMap((row) => row.date ? [row.date] : []).sort();
|
|
61
|
+
return dates[Math.floor((dates.length - 1) / 2)] ?? null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One short line, the same in the report, the pane and a capture: the board
|
|
66
|
+
* is dated by the day most markets have reached and lags from it (`most
|
|
67
|
+
* markets 2026-10-07 · lag 4 days`); one market by its own delivery day; the
|
|
68
|
+
* EUA alone by its last auction.
|
|
69
|
+
*/
|
|
70
|
+
function freshness(board: EuropePowerBoard | null, carbon: CarbonBoard | null, market?: PowerPriceRow): HeadlessBundleResult["freshness"] {
|
|
71
|
+
const asOf = market ? market.date : board ? typicalPowerDate(board.rows) : carbon?.row.date ?? null;
|
|
72
|
+
return {
|
|
73
|
+
source: [board?.source.name, carbon?.source.name].filter(Boolean).join(", "),
|
|
74
|
+
asOf, oldest: null,
|
|
75
|
+
observation: market ? "delivery day" : board ? "most markets" : "last auction",
|
|
76
|
+
status: "not-a-feed", observedKey: "date", basis: board ? "day-ahead auction" : "primary auction", dataset: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const failure = (error: unknown) => error instanceof Error ? error.message : String(error);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Every market's latest day-ahead price and the EUA auction price; one
|
|
84
|
+
* market's figures and its last 30 delivery days when a market is named; the
|
|
85
|
+
* EUA and its latest auctions for EUA (or carbon).
|
|
86
|
+
*/
|
|
87
|
+
export async function loadPowerPricesReport(argument: string | undefined, client: PricesClient = apiClient): Promise<HeadlessBundleResult> {
|
|
88
|
+
if (isCarbonQuery(argument)) {
|
|
89
|
+
const carbon = await fetchCarbonPrice(client);
|
|
90
|
+
return { sections: carbonSections(carbon), notes: [...carbon.gaps], freshness: freshness(null, carbon),
|
|
91
|
+
metadata: { market: carbon.market, unit: carbon.unit, source: carbon.source, publishedAt: carbon.publishedAt, series: "CAP:carbon:eua" } };
|
|
92
|
+
}
|
|
93
|
+
const board = await fetchPowerPrices(client);
|
|
94
|
+
const notes = [...board.gaps];
|
|
95
|
+
if (argument?.trim()) {
|
|
96
|
+
const market = powerPriceMarket(argument);
|
|
97
|
+
const row = market ? board.rows.find((entry) => entry.code === market.code) : null;
|
|
98
|
+
if (!market || !row) throw new Error(`No day-ahead price for "${argument.trim()}". Use a market such as DE, FR, GB or Spain, or EUA.`);
|
|
99
|
+
const history = await fetchPowerPriceHistory(market.code, null, client);
|
|
100
|
+
return {
|
|
101
|
+
sections: [
|
|
102
|
+
{ title: `${market.name} day-ahead power, ${POWER_PRICE_UNIT}`, columns: COLUMNS, rows: [reportRow(row)] },
|
|
103
|
+
{ title: `${market.name}, last 30 delivery days`,
|
|
104
|
+
columns: [{ key: "date", header: "Date", shrink: false }, { key: "price", header: POWER_PRICE_UNIT, align: "right", format: price }],
|
|
105
|
+
rows: history.points.slice(-30).reverse().map((point) => ({ date: point.date, price: point.value })) },
|
|
106
|
+
],
|
|
107
|
+
notes, freshness: freshness(board, null, row),
|
|
108
|
+
metadata: { market: board.market, unit: board.unit, source: board.source, publishedAt: board.publishedAt, row, series: `CAP:day-ahead:${market.code.toLowerCase()}` },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// The carbon allowance rides along; when it cannot load, the power board still reports and says so.
|
|
112
|
+
const carbon = await fetchCarbonPrice(client).catch((error: unknown) => failure(error));
|
|
113
|
+
return {
|
|
114
|
+
sections: [{ title: `Day-ahead power, ${POWER_PRICE_UNIT}`, columns: COLUMNS, rows: board.rows.map(reportRow) },
|
|
115
|
+
...(typeof carbon === "string" ? [] : carbonSections(carbon))],
|
|
116
|
+
notes: [...notes, ...(typeof carbon === "string" ? [carbon] : carbon.gaps)],
|
|
117
|
+
freshness: freshness(board, typeof carbon === "string" ? null : carbon),
|
|
118
|
+
metadata: { market: board.market, unit: board.unit, source: board.source, publishedAt: board.publishedAt,
|
|
119
|
+
...(typeof carbon === "string" ? {} : { carbon: { market: carbon.market, unit: carbon.unit, source: carbon.source, publishedAt: carbon.publishedAt } }) },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const powerPricesHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
124
|
+
shape: "bundle",
|
|
125
|
+
argument: { kind: "free-text", optional: true, placeholder: "market", description: "A market such as DE, FR, GB or Spain, or EUA for the carbon auctions; omit for every market and the EUA." },
|
|
126
|
+
options: [],
|
|
127
|
+
freshness: { source: "Ember", status: "not-a-feed", basis: "day-ahead auction", dataset: true },
|
|
128
|
+
discovery: { aliases: ["EPWR"], dataRequirements: ["Gloom Cloud European power and carbon prices"],
|
|
129
|
+
limitations: ["Daily averages of the day-ahead auction, weighted by load, per country; not intraday or forward prices",
|
|
130
|
+
"Each market is dated by its own latest delivery day: continental markets usually trail by a few days",
|
|
131
|
+
"The EUA price is the primary auction clearing price on business days, volume-weighted across a day's auctions; not a futures settlement"] },
|
|
132
|
+
describe: "European day-ahead power prices by country (EUR/MWh) and the EU carbon allowance auction price (EUR/tCO2)",
|
|
133
|
+
async load(args, ctx) {
|
|
134
|
+
const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument ?? undefined;
|
|
135
|
+
return loadPowerPricesReport(argument, ctx.apiClient);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { PowerPriceRow } from "../../../api-client/europe-energy";
|
|
2
|
+
import type { SeriesDefinition } from "../../../series/types";
|
|
3
|
+
import { fetchCarbonHistory, fetchPowerPriceHistory } from "./prices-client";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The markets the server serves day-ahead prices for, by ISO 3166 alpha-2
|
|
7
|
+
* code, known before the board loads so the data catalog can list them. Great
|
|
8
|
+
* Britain is its own market; Northern Ireland trades with Ireland.
|
|
9
|
+
*/
|
|
10
|
+
export const POWER_PRICE_MARKETS = [
|
|
11
|
+
{ code: "AL", name: "Albania" }, { code: "AT", name: "Austria" }, { code: "BE", name: "Belgium" },
|
|
12
|
+
{ code: "BG", name: "Bulgaria" }, { code: "HR", name: "Croatia" }, { code: "CZ", name: "Czechia" },
|
|
13
|
+
{ code: "DK", name: "Denmark" }, { code: "EE", name: "Estonia" }, { code: "FI", name: "Finland" },
|
|
14
|
+
{ code: "FR", name: "France" }, { code: "DE", name: "Germany" }, { code: "GB", name: "Great Britain" },
|
|
15
|
+
{ code: "GR", name: "Greece" }, { code: "HU", name: "Hungary" }, { code: "IE", name: "Ireland" },
|
|
16
|
+
{ code: "IT", name: "Italy" }, { code: "LV", name: "Latvia" }, { code: "LT", name: "Lithuania" },
|
|
17
|
+
{ code: "LU", name: "Luxembourg" }, { code: "ME", name: "Montenegro" }, { code: "NL", name: "Netherlands" },
|
|
18
|
+
{ code: "MK", name: "North Macedonia" }, { code: "NO", name: "Norway" }, { code: "PL", name: "Poland" },
|
|
19
|
+
{ code: "PT", name: "Portugal" }, { code: "RO", name: "Romania" }, { code: "RS", name: "Serbia" },
|
|
20
|
+
{ code: "SK", name: "Slovakia" }, { code: "SI", name: "Slovenia" }, { code: "ES", name: "Spain" },
|
|
21
|
+
{ code: "SE", name: "Sweden" }, { code: "CH", name: "Switzerland" },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export type PowerPriceMarket = (typeof POWER_PRICE_MARKETS)[number];
|
|
25
|
+
|
|
26
|
+
/** Words a market goes by besides its name and code. */
|
|
27
|
+
const ALIASES: Readonly<Record<string, string>> = {
|
|
28
|
+
UK: "GB", "UNITED KINGDOM": "GB", BRITAIN: "GB", ENGLAND: "GB", GBR: "GB",
|
|
29
|
+
DEU: "DE", FRA: "FR", NLD: "NL", BEL: "BE", ESP: "ES", ITA: "IT", AUT: "AT", CHE: "CH", POL: "PL", IRL: "IE",
|
|
30
|
+
"CZECH REPUBLIC": "CZ", HOLLAND: "NL",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A typed market: its code (DE), alpha-3 code (DEU), name (germany) or a common alias (UK). */
|
|
34
|
+
export function powerPriceMarket(input: string | null | undefined): PowerPriceMarket | null {
|
|
35
|
+
const key = input?.trim().toUpperCase().replace(/\s+/g, " ") ?? "";
|
|
36
|
+
if (!key) return null;
|
|
37
|
+
const code = ALIASES[key] ?? key;
|
|
38
|
+
return POWER_PRICE_MARKETS.find((market) => market.code === code || market.name.toUpperCase() === key) ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const CARBON_PRICE_UNIT = "EUR/tCO2";
|
|
42
|
+
/** The Prices tab's selection when the band charts the carbon allowance instead of a market. */
|
|
43
|
+
export const CARBON_SELECTION = "EUA";
|
|
44
|
+
/** What `EPWR EUA` (or carbon, EU ETS, CO2) asks for: the carbon allowance instead of a market. */
|
|
45
|
+
const CARBON_WORDS = new Set(["EUA", "EUAS", "CARBON", "EU CARBON", "EU ETS", "ETS", "CO2", "EU ALLOWANCE", "EU ALLOWANCES"]);
|
|
46
|
+
export const isCarbonQuery = (input: string | null | undefined) =>
|
|
47
|
+
CARBON_WORDS.has(input?.trim().toUpperCase().replace(/[-\s]+/g, " ") ?? "");
|
|
48
|
+
|
|
49
|
+
/** Germany is the benchmark market a trader reads first. */
|
|
50
|
+
export const DEFAULT_PRICE_MARKET = "DE";
|
|
51
|
+
|
|
52
|
+
export const POWER_PRICE_UNIT = "EUR/MWh";
|
|
53
|
+
export const formatPowerPrice = (value: number | null | undefined) => value == null ? "--" : value.toFixed(2);
|
|
54
|
+
export const formatPowerChange = (value: number | null | undefined) =>
|
|
55
|
+
value == null ? "--" : `${value > 0 ? "+" : ""}${value.toFixed(2)}`;
|
|
56
|
+
/** Where the latest price sits in its 52-week range, 0 at the low and 100 at the high. */
|
|
57
|
+
export function powerRangePosition(row: PowerPriceRow): number | null {
|
|
58
|
+
if (row.price == null || row.low52w == null || row.high52w == null || row.high52w <= row.low52w) return null;
|
|
59
|
+
return Math.round(((row.price - row.low52w) / (row.high52w - row.low52w)) * 100);
|
|
60
|
+
}
|
|
61
|
+
const DAY_AHEAD_PAGE_URL = "https://ember-energy.org/data/european-wholesale-electricity-price-data/";
|
|
62
|
+
|
|
63
|
+
/** The benchmark markets a search lists first; the rest follow by name. */
|
|
64
|
+
const LEAD_MARKETS = ["DE", "FR", "GB", "NL", "BE", "IT", "ES"];
|
|
65
|
+
const leadRank = (code: string) => (LEAD_MARKETS.indexOf(code) + LEAD_MARKETS.length + 1) % (LEAD_MARKETS.length + 1);
|
|
66
|
+
|
|
67
|
+
/** Each market's daily day-ahead price, as `CAP:day-ahead:de`. */
|
|
68
|
+
export const DAY_AHEAD_SERIES: readonly SeriesDefinition[] = [...POWER_PRICE_MARKETS].sort((left, right) =>
|
|
69
|
+
leadRank(left.code) - leadRank(right.code)).map((market): SeriesDefinition => ({
|
|
70
|
+
id: `day-ahead:${market.code.toLowerCase()}`,
|
|
71
|
+
name: `${market.name} day-ahead power price`,
|
|
72
|
+
unit: POWER_PRICE_UNIT,
|
|
73
|
+
frequency: "daily",
|
|
74
|
+
group: "Energy",
|
|
75
|
+
tags: ["electricity", "power price", "day-ahead", "wholesale", "Europe", market.code, ...(market.code === "GB" ? ["UK"] : [])],
|
|
76
|
+
description: `Daily average of the ${market.name} day-ahead auction price, weighted by load, in euros per MWh.`,
|
|
77
|
+
publisher: "Ember",
|
|
78
|
+
url: DAY_AHEAD_PAGE_URL,
|
|
79
|
+
load: async ({ start }) => {
|
|
80
|
+
const history = await fetchPowerPriceHistory(market.code, start?.toISOString().slice(0, 10));
|
|
81
|
+
return history.points;
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The EU carbon allowance price, as `CAP:carbon:eua`: each business day's
|
|
87
|
+
* primary auction clearing price, volume-weighted across that day's auctions.
|
|
88
|
+
*/
|
|
89
|
+
export const CARBON_SERIES: readonly SeriesDefinition[] = [{
|
|
90
|
+
id: "carbon:eua",
|
|
91
|
+
name: "EU carbon allowance (EUA) auction price",
|
|
92
|
+
unit: CARBON_PRICE_UNIT,
|
|
93
|
+
frequency: "daily",
|
|
94
|
+
group: "Energy",
|
|
95
|
+
tags: ["carbon", "EU carbon", "EUA", "EU ETS", "carbon price", "carbon allowance", "emission allowances", "emissions", "CO2", "Europe"],
|
|
96
|
+
description: "Clearing price of each business day's EU ETS primary auctions of general allowances, volume-weighted, in euros per tonne of CO2. An auction price, not a futures settlement.",
|
|
97
|
+
publisher: "EEX",
|
|
98
|
+
url: "https://www.eex.com/en/markets/environmental-markets/emissions-auctions",
|
|
99
|
+
load: async ({ start }) => {
|
|
100
|
+
const history = await fetchCarbonHistory(start?.toISOString().slice(0, 10));
|
|
101
|
+
return history.points;
|
|
102
|
+
},
|
|
103
|
+
}];
|