gloomberb 0.18.1 → 0.18.2
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 +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +281 -154
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +100 -50
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +11 -2
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +58 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -9,9 +9,12 @@ interface SegmentedControlOption {
|
|
|
9
9
|
label: string;
|
|
10
10
|
value: string;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
+
tone?: "positive" | "negative";
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface SegmentedControlProps {
|
|
16
|
+
/** Name of the choice group for assistive technology and semantic access. */
|
|
17
|
+
accessibleLabel?: string;
|
|
15
18
|
options: SegmentedControlOption[];
|
|
16
19
|
value: string;
|
|
17
20
|
onChange?: (value: string) => void;
|
|
@@ -21,9 +24,13 @@ export interface SegmentedControlProps {
|
|
|
21
24
|
shortcutScope?: string;
|
|
22
25
|
width?: number | "100%";
|
|
23
26
|
wrap?: boolean;
|
|
27
|
+
size?: "default" | "large";
|
|
28
|
+
/** One filled selection without an enclosing frame. */
|
|
29
|
+
variant?: "default" | "flat";
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export function SegmentedControl({
|
|
33
|
+
accessibleLabel,
|
|
27
34
|
options,
|
|
28
35
|
value,
|
|
29
36
|
onChange,
|
|
@@ -32,13 +39,15 @@ export function SegmentedControl({
|
|
|
32
39
|
shortcutScope,
|
|
33
40
|
width,
|
|
34
41
|
wrap = false,
|
|
42
|
+
size = "default",
|
|
43
|
+
variant = "default",
|
|
35
44
|
}: SegmentedControlProps) {
|
|
36
45
|
const colors = useThemeColors();
|
|
37
46
|
const ui = useUiHost();
|
|
38
47
|
const HostSegmentedControl = ui.SegmentedControl as ComponentType<SegmentedControlProps> | undefined;
|
|
39
48
|
useRemoteUiNode({
|
|
40
49
|
role: "select",
|
|
41
|
-
label: "Segmented control",
|
|
50
|
+
label: accessibleLabel ?? "Segmented control",
|
|
42
51
|
actions: {
|
|
43
52
|
select: (input) => {
|
|
44
53
|
const next = typeof input === "string" ? input : (input as { value?: string } | null)?.value;
|
|
@@ -78,12 +87,15 @@ export function SegmentedControl({
|
|
|
78
87
|
if (HostSegmentedControl) {
|
|
79
88
|
return (
|
|
80
89
|
<HostSegmentedControl
|
|
90
|
+
accessibleLabel={accessibleLabel}
|
|
81
91
|
options={options}
|
|
82
92
|
value={value}
|
|
83
93
|
onChange={onChange}
|
|
84
94
|
focused={focused}
|
|
85
95
|
width={width}
|
|
86
96
|
wrap={wrap}
|
|
97
|
+
size={size}
|
|
98
|
+
variant={variant}
|
|
87
99
|
/>
|
|
88
100
|
);
|
|
89
101
|
}
|
|
@@ -100,7 +112,9 @@ export function SegmentedControl({
|
|
|
100
112
|
return (
|
|
101
113
|
<Box
|
|
102
114
|
key={option.value}
|
|
103
|
-
|
|
115
|
+
flexGrow={size === "large" ? 1 : undefined}
|
|
116
|
+
justifyContent="center"
|
|
117
|
+
backgroundColor={option.disabled ? colors.panel : active && option.tone ? colors[option.tone] : active ? (focused ? colors.borderFocused : colors.selected) : colors.panel}
|
|
104
118
|
onMouseDown={() => {
|
|
105
119
|
if (!option.disabled) onChange?.(option.value);
|
|
106
120
|
}}
|
|
@@ -109,7 +123,7 @@ export function SegmentedControl({
|
|
|
109
123
|
<Text
|
|
110
124
|
// The focused fill is the primary button's, so its text is too:
|
|
111
125
|
// selected text on it is unreadable in the white theme.
|
|
112
|
-
fg={option.disabled ? colors.textMuted : active ? (focused ? colors.bg : colors.selectedText) : colors.textDim}
|
|
126
|
+
fg={option.disabled ? colors.textMuted : active && option.tone ? colors.bg : option.tone ? colors[option.tone] : active ? (focused ? colors.bg : colors.selectedText) : colors.textDim}
|
|
113
127
|
attributes={active ? TextAttributes.BOLD : 0}
|
|
114
128
|
>
|
|
115
129
|
{` ${option.label} `}
|
|
@@ -3,7 +3,7 @@ import { findPaneInstance, type PaneBinding } from "../../../types/config";
|
|
|
3
3
|
import type { BrokerContractRef, TickerListingRef } from "../../../types/instrument";
|
|
4
4
|
import type { TickerRecord } from "../../../types/ticker";
|
|
5
5
|
import { instrumentFromTicker, type InstrumentRef } from "../../../market-data/request-types";
|
|
6
|
-
import {
|
|
6
|
+
import { heldOnListing, sameBrokerContract } from "../../../utils/instrument-identity";
|
|
7
7
|
import { isBrokerPortfolioId } from "../../../utils/broker-instances";
|
|
8
8
|
import { searchContractKey } from "../../../tickers/search/identity";
|
|
9
9
|
import { canonicalExchange } from "../../../utils/exchanges";
|
|
@@ -77,10 +77,15 @@ function contractListingExchange(ticker: TickerRecord, instrument: BrokerContrac
|
|
|
77
77
|
export function tickerForInstrument(ticker: TickerRecord | null, instrument: BrokerContractRef | null | undefined, listing?: TickerListingRef): TickerRecord | null {
|
|
78
78
|
if (!ticker || instrument === undefined) return null;
|
|
79
79
|
if (!instrument && !listing && !ticker.metadata.broker_contracts?.length) return ticker;
|
|
80
|
-
|
|
80
|
+
// A position is this contract's however the broker routed it: SPY held through SMART is the ARCX listing's.
|
|
81
81
|
const positions = ticker.metadata.positions.filter((position) => {
|
|
82
82
|
const selected = instrumentFromTicker({ ...ticker, metadata: { ...ticker.metadata, positions: [position] } }, null, { portfolioId: position.portfolio });
|
|
83
|
-
|
|
83
|
+
const held = selected?.instrument;
|
|
84
|
+
if (!selected) return false;
|
|
85
|
+
if (!held) return !instrument;
|
|
86
|
+
// A pane opened on a listing from search has no broker contract; a share held there is still its.
|
|
87
|
+
if (!instrument) return !!listing && heldOnListing(held, listing);
|
|
88
|
+
return held.brokerId === instrument.brokerId && (held.brokerInstanceId ?? "") === (instrument.brokerInstanceId ?? "") && sameBrokerContract(held, instrument);
|
|
84
89
|
});
|
|
85
90
|
return { ...ticker, metadata: {
|
|
86
91
|
...ticker.metadata,
|
|
@@ -24,6 +24,7 @@ import { migrateSavedConfig, type ConfigMigrationHost } from "./migrations";
|
|
|
24
24
|
import { encodeBuiltinDisabledPluginIds, expandBuiltinPluginGroups } from "../../../plugins/ownership";
|
|
25
25
|
import { sanitizeSavedPaneState } from "./pane-state";
|
|
26
26
|
import { isRecord } from "../../../utils/guards";
|
|
27
|
+
import { normalizeStoredBaseCurrency } from "../../../utils/base-currency";
|
|
27
28
|
import { debugLog } from "../../../utils/debug-log";
|
|
28
29
|
import { DEFAULT_THEME, themes } from "../../../theme/themes";
|
|
29
30
|
import { sanitizeStarPrompt } from "../../../app/star-prompt/model";
|
|
@@ -80,7 +81,9 @@ export function normalizeLoadedConfig(
|
|
|
80
81
|
const config: AppConfig = {
|
|
81
82
|
dataDir,
|
|
82
83
|
configVersion: CURRENT_CONFIG_VERSION,
|
|
83
|
-
|
|
84
|
+
// A name or sign an older version saved (Canadian dollar, CA$) reads as its code; nothing turns into USD unasked.
|
|
85
|
+
baseCurrency: typeof candidate.baseCurrency === "string" && candidate.baseCurrency.trim()
|
|
86
|
+
? normalizeStoredBaseCurrency(candidate.baseCurrency) : defaults.baseCurrency,
|
|
84
87
|
refreshIntervalMinutes: typeof candidate.refreshIntervalMinutes === "number" ? candidate.refreshIntervalMinutes : defaults.refreshIntervalMinutes,
|
|
85
88
|
portfolios: sanitizePortfolios(candidate.portfolios, defaults.portfolios),
|
|
86
89
|
watchlists: sanitizeWatchlists(candidate.watchlists, defaults.watchlists),
|
package/src/data/plugin-cache.ts
CHANGED
|
@@ -44,6 +44,24 @@ export function createPluginCache<T, Stored = T>(options: {
|
|
|
44
44
|
source: options.source,
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Listeners by key, kept apart from the queries so they outlive a reset:
|
|
49
|
+
* a pane that subscribed before the plugin attached its store still hears
|
|
50
|
+
* the loads made after.
|
|
51
|
+
*/
|
|
52
|
+
const listeners = new Map<string, Set<() => void>>();
|
|
53
|
+
const emit = (key: string) => {
|
|
54
|
+
for (const listener of [...listeners.get(key) ?? []]) listener();
|
|
55
|
+
};
|
|
56
|
+
const queryFor = (key: string): CachedQuery<T> => {
|
|
57
|
+
let query = queries.get(key);
|
|
58
|
+
if (!query) {
|
|
59
|
+
query = new CachedQuery({ read: (allowExpired) => read(key, allowExpired) });
|
|
60
|
+
query.subscribe(() => emit(key));
|
|
61
|
+
queries.set(key, query);
|
|
62
|
+
}
|
|
63
|
+
return query;
|
|
64
|
+
};
|
|
47
65
|
const reset = () => {
|
|
48
66
|
generation += 1;
|
|
49
67
|
persistence = null;
|
|
@@ -53,21 +71,29 @@ export function createPluginCache<T, Stored = T>(options: {
|
|
|
53
71
|
};
|
|
54
72
|
return {
|
|
55
73
|
attach(next: PluginPersistence) {
|
|
56
|
-
if (persistence
|
|
74
|
+
if (persistence === next) return;
|
|
75
|
+
reset();
|
|
57
76
|
persistence = next;
|
|
77
|
+
// What a listener reads now comes from another store.
|
|
78
|
+
for (const key of [...listeners.keys()]) emit(key);
|
|
58
79
|
},
|
|
59
80
|
reset,
|
|
81
|
+
subscribe(key: string, listener: () => void): () => void {
|
|
82
|
+
const keyed = listeners.get(key) ?? new Set<() => void>();
|
|
83
|
+
listeners.set(key, keyed);
|
|
84
|
+
keyed.add(listener);
|
|
85
|
+
return () => {
|
|
86
|
+
keyed.delete(listener);
|
|
87
|
+
if (keyed.size === 0 && listeners.get(key) === keyed) listeners.delete(key);
|
|
88
|
+
};
|
|
89
|
+
},
|
|
60
90
|
get(key: string, { allowExpired = false } = {}): PluginCacheResult<T> | null {
|
|
61
91
|
const cached = read(key, allowExpired) ?? queries.get(key)?.getSnapshot().result;
|
|
62
92
|
if (!cached || (!allowExpired && cached.expiresAt <= Date.now())) return null;
|
|
63
93
|
return { data: cached.value, fetchedAt: cached.fetchedAt, stale: cached.staleAt <= Date.now(), source: "cache" };
|
|
64
94
|
},
|
|
65
95
|
async load(key: string, loader: () => Promise<T>, { force = false, replace = false } = {}): Promise<PluginCacheResult<T>> {
|
|
66
|
-
|
|
67
|
-
if (!query) {
|
|
68
|
-
query = new CachedQuery({ read: (allowExpired) => read(key, allowExpired) });
|
|
69
|
-
queries.set(key, query);
|
|
70
|
-
}
|
|
96
|
+
const query = queryFor(key);
|
|
71
97
|
const owner = generation;
|
|
72
98
|
const before = query.getSnapshot().result;
|
|
73
99
|
const result = await query.load({ force, replace, fetch: async () => {
|
package/src/docs/snapshot.json
CHANGED
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
"code": "EXEC",
|
|
481
481
|
"name": "Executives",
|
|
482
482
|
"description": "Officers and pay from the proxy",
|
|
483
|
-
"detail": "Named executive officers and their compensation, read from the proxy statement and checked against the filing.",
|
|
483
|
+
"detail": "Named executive officers and their compensation, read from the proxy statement and checked against the filing. Free shows each executive's total pay; Pro adds the breakdown and earlier years.",
|
|
484
484
|
"category": "research",
|
|
485
485
|
"arg": "NVDA",
|
|
486
486
|
"access": "pro",
|
|
@@ -695,7 +695,7 @@
|
|
|
695
695
|
"code": "MOST",
|
|
696
696
|
"name": "Market movers",
|
|
697
697
|
"description": "Gainers, losers, pre-market, gaps",
|
|
698
|
-
"detail": "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap
|
|
698
|
+
"detail": "Today's top gainers, losers, most active and trending tickers with price, change, volume against average and market cap, and the pre-market and after-hours movers. Pro adds the gaps at the open.",
|
|
699
699
|
"category": "markets",
|
|
700
700
|
"shot": "https://gloom.sh/screenshots/fn/MOST.png",
|
|
701
701
|
"lesson": "/academy/macro-markets-and-news/markets/movers-highs-halts"
|
|
@@ -1401,9 +1401,9 @@
|
|
|
1401
1401
|
},
|
|
1402
1402
|
{
|
|
1403
1403
|
"code": "ALRT",
|
|
1404
|
-
"name": "
|
|
1405
|
-
"description": "Price
|
|
1406
|
-
"detail": "
|
|
1404
|
+
"name": "Alerts",
|
|
1405
|
+
"description": "Price, earnings, filing, news and flow alerts",
|
|
1406
|
+
"detail": "Price alerts with current price, distance to target, and trigger state, plus alerts on earnings, CPI and FOMC dates, filings and 13F, news keywords, analyst changes, 52-week highs and lows, unusual volume, short interest, insider and congress trades, catalysts, IV spikes, options flow, and perps.",
|
|
1407
1407
|
"category": "workspace",
|
|
1408
1408
|
"shot": "https://gloom.sh/screenshots/fn/ALRT.png",
|
|
1409
1409
|
"lesson": "/academy/terminal-and-workspace/your-workspace/alerts"
|
package/src/i18n/es.ts
CHANGED
|
@@ -380,8 +380,7 @@ export const es: Record<string, string> = {
|
|
|
380
380
|
"Insider transactions for a ticker; alone, the companies whose insiders are buying or selling.": "Operaciones de insiders de un ticker; sin ticker, las empresas cuyos insiders están comprando o vendiendo.",
|
|
381
381
|
"Recent SEC filings for US equities": "Presentaciones recientes ante la SEC de acciones de EE. UU.",
|
|
382
382
|
"Recent SEC filings for the selected ticker.": "Presentaciones recientes ante la SEC del ticker seleccionado.",
|
|
383
|
-
"Price
|
|
384
|
-
"Price trigger alerts with notifications": "Alertas de precio con notificaciones",
|
|
383
|
+
"Price, earnings, filing, news and flow alerts": "Alertas de precio, resultados, presentaciones, noticias y flujo de opciones",
|
|
385
384
|
"Create a price alert from a symbol, condition, and target price": "Crear una alerta de precio con símbolo, condición y precio objetivo",
|
|
386
385
|
"Public market heatmap for the largest US stocks and ETFs.": "Mapa de calor de las mayores acciones y ETF de EE. UU.",
|
|
387
386
|
"Top gainers, losers, most active, and trending tickers": "Mayores alzas, bajas, más activos y tickers en tendencia",
|
|
@@ -665,7 +664,7 @@ export const es: Record<string, string> = {
|
|
|
665
664
|
"Real-time": "Tiempo real",
|
|
666
665
|
"real-time options": "opciones en tiempo real",
|
|
667
666
|
"mixed real-time and delayed options": "opciones mixtas en tiempo real y diferidas",
|
|
668
|
-
"options
|
|
667
|
+
"Delayed options quotes, real-time stream unavailable": "Cotizaciones de opciones diferidas, flujo en tiempo real no disponible",
|
|
669
668
|
"crossed quote": "cotización cruzada",
|
|
670
669
|
"one-sided quote": "cotización unilateral",
|
|
671
670
|
"no bid/ask": "sin compra/venta",
|
|
@@ -1626,4 +1625,11 @@ export const es: Record<string, string> = {
|
|
|
1626
1625
|
"It is kept once it has been read with a connection.": "Se guarda al leerla una vez con conexión.",
|
|
1627
1626
|
"Academy: {title}": "Academia: {title}",
|
|
1628
1627
|
"{minutes} min": "{minutes} min",
|
|
1628
|
+
"Delisted {date}": "Excluida de bolsa el {date}",
|
|
1629
|
+
"Last traded {date}": "Última negociación el {date}",
|
|
1630
|
+
"Account data": "Datos de la cuenta",
|
|
1631
|
+
"Sync with Gloom Cloud": "Sincronizar con Gloom Cloud",
|
|
1632
|
+
"Keep on this device": "Mantener en este dispositivo",
|
|
1633
|
+
"Daily roundups, the weekly brief, position alerts, the mobile app and Ask Gloom will go without this account.": "Los resúmenes diarios, el informe semanal, las alertas de posiciones, la app móvil y Ask Gloom no incluirán esta cuenta.",
|
|
1634
|
+
"Account data stays on this device, out of Gloom Cloud": "Los datos de la cuenta se quedan en este dispositivo, fuera de Gloom Cloud",
|
|
1629
1635
|
};
|
package/src/i18n/ja.ts
CHANGED
|
@@ -381,8 +381,7 @@ export const ja: Record<string, string> = {
|
|
|
381
381
|
"Insider transactions for a ticker; alone, the companies whose insiders are buying or selling.": "銘柄のインサイダー取引。銘柄を指定しないと、インサイダーが買っている、または売っている企業の一覧。",
|
|
382
382
|
"Recent SEC filings for US equities": "米国株の最新 SEC 開示",
|
|
383
383
|
"Recent SEC filings for the selected ticker.": "選択した銘柄の最新 SEC 開示。",
|
|
384
|
-
"Price
|
|
385
|
-
"Price trigger alerts with notifications": "通知付きの価格トリガーアラート",
|
|
384
|
+
"Price, earnings, filing, news and flow alerts": "価格、決算、開示、ニュース、オプションフローのアラート",
|
|
386
385
|
"Create a price alert from a symbol, condition, and target price": "銘柄コード、条件、目標価格から価格アラートを作成",
|
|
387
386
|
"Public market heatmap for the largest US stocks and ETFs.": "米国大型株と ETF の市場ヒートマップ。",
|
|
388
387
|
"Top gainers, losers, most active, and trending tickers": "値上がり、値下がり、出来高、注目銘柄",
|
|
@@ -679,7 +678,7 @@ export const ja: Record<string, string> = {
|
|
|
679
678
|
"Go Pro for real-time data at gloom.sh/cloud, free for 7 days.": "gloom.sh/cloud で Pro にアップグレードするとリアルタイムデータを 7 日間無料で利用できます。",
|
|
680
679
|
"real-time options": "リアルタイムオプション",
|
|
681
680
|
"mixed real-time and delayed options": "リアルタイムと遅延の混在オプション",
|
|
682
|
-
"options
|
|
681
|
+
"Delayed options quotes, real-time stream unavailable": "オプション気配値は遅延中、リアルタイム配信は利用できません",
|
|
683
682
|
"crossed quote": "クロス気配",
|
|
684
683
|
"one-sided quote": "片側気配",
|
|
685
684
|
"no bid/ask": "気配なし",
|
|
@@ -1622,4 +1621,11 @@ export const ja: Record<string, string> = {
|
|
|
1622
1621
|
"It is kept once it has been read with a connection.": "一度オンラインで読むと保存されます。",
|
|
1623
1622
|
"Academy: {title}": "アカデミー: {title}",
|
|
1624
1623
|
"{minutes} min": "{minutes} 分",
|
|
1624
|
+
"Delisted {date}": "上場廃止 {date}",
|
|
1625
|
+
"Last traded {date}": "最終取引 {date}",
|
|
1626
|
+
"Account data": "口座データ",
|
|
1627
|
+
"Sync with Gloom Cloud": "Gloom Cloud と同期",
|
|
1628
|
+
"Keep on this device": "このデバイスに保存",
|
|
1629
|
+
"Daily roundups, the weekly brief, position alerts, the mobile app and Ask Gloom will go without this account.": "デイリーまとめ、週次ブリーフ、ポジションアラート、モバイルアプリ、Ask Gloom にはこの口座が含まれなくなります。",
|
|
1630
|
+
"Account data stays on this device, out of Gloom Cloud": "口座データはこのデバイスに保存され、Gloom Cloud には送られません",
|
|
1625
1631
|
};
|
package/src/i18n/ko.ts
CHANGED
|
@@ -379,8 +379,7 @@ export const ko: Record<string, string> = {
|
|
|
379
379
|
"Insider transactions for a ticker; alone, the companies whose insiders are buying or selling.": "종목의 내부자 거래. 종목 없이 열면 내부자가 매수 또는 매도 중인 기업 목록.",
|
|
380
380
|
"Recent SEC filings for US equities": "미국 주식의 최신 SEC 공시",
|
|
381
381
|
"Recent SEC filings for the selected ticker.": "선택 종목의 최신 SEC 공시.",
|
|
382
|
-
"Price
|
|
383
|
-
"Price trigger alerts with notifications": "알림이 있는 목표가 알림",
|
|
382
|
+
"Price, earnings, filing, news and flow alerts": "가격, 실적, 공시, 뉴스, 옵션 플로우 알림",
|
|
384
383
|
"Create a price alert from a symbol, condition, and target price": "종목코드, 조건 및 목표가로 가격 알림 생성",
|
|
385
384
|
"Public market heatmap for the largest US stocks and ETFs.": "미국 대형주 및 ETF 시장 히트맵.",
|
|
386
385
|
"Top gainers, losers, most active, and trending tickers": "상승률·하락률·거래량 상위 및 인기 종목",
|
|
@@ -677,7 +676,7 @@ export const ko: Record<string, string> = {
|
|
|
677
676
|
"Go Pro for real-time data at gloom.sh/cloud, free for 7 days.": "gloom.sh/cloud에서 Pro로 업그레이드하면 실시간 데이터를 7일간 무료로 사용할 수 있습니다.",
|
|
678
677
|
"real-time options": "실시간 옵션",
|
|
679
678
|
"mixed real-time and delayed options": "실시간 및 지연 옵션 혼합",
|
|
680
|
-
"options
|
|
679
|
+
"Delayed options quotes, real-time stream unavailable": "옵션 시세 지연, 실시간 스트림 사용 불가",
|
|
681
680
|
"crossed quote": "교차 호가",
|
|
682
681
|
"one-sided quote": "일방 호가",
|
|
683
682
|
"no bid/ask": "호가 없음",
|
|
@@ -1620,4 +1619,11 @@ export const ko: Record<string, string> = {
|
|
|
1620
1619
|
"It is kept once it has been read with a connection.": "연결된 상태에서 한 번 읽으면 저장됩니다.",
|
|
1621
1620
|
"Academy: {title}": "아카데미: {title}",
|
|
1622
1621
|
"{minutes} min": "{minutes}분",
|
|
1622
|
+
"Delisted {date}": "상장폐지 {date}",
|
|
1623
|
+
"Last traded {date}": "최종 거래 {date}",
|
|
1624
|
+
"Account data": "계좌 데이터",
|
|
1625
|
+
"Sync with Gloom Cloud": "Gloom Cloud와 동기화",
|
|
1626
|
+
"Keep on this device": "이 기기에 보관",
|
|
1627
|
+
"Daily roundups, the weekly brief, position alerts, the mobile app and Ask Gloom will go without this account.": "일일 요약, 주간 브리핑, 포지션 알림, 모바일 앱, Ask Gloom에 이 계좌가 포함되지 않습니다.",
|
|
1628
|
+
"Account data stays on this device, out of Gloom Cloud": "계좌 데이터는 Gloom Cloud에 올라가지 않고 이 기기에 남습니다",
|
|
1623
1629
|
};
|
package/src/i18n/zh-cn.ts
CHANGED
|
@@ -328,7 +328,7 @@ export const zhCN: Record<string, string> = {
|
|
|
328
328
|
"Insider Trading": "内部人交易",
|
|
329
329
|
"SEC": "SEC 文件",
|
|
330
330
|
"Notes": "笔记",
|
|
331
|
-
"Alerts": "
|
|
331
|
+
"Alerts": "提醒",
|
|
332
332
|
"Market Heatmap": "市场热力图",
|
|
333
333
|
"Market Movers": "市场异动",
|
|
334
334
|
"Sector Performance": "板块表现",
|
|
@@ -381,8 +381,7 @@ export const zhCN: Record<string, string> = {
|
|
|
381
381
|
"Insider transactions for a ticker; alone, the companies whose insiders are buying or selling.": "某只股票的内部人交易;不指定股票时,列出内部人正在买入或卖出的公司。",
|
|
382
382
|
"Recent SEC filings for US equities": "美股最新 SEC 申报文件",
|
|
383
383
|
"Recent SEC filings for the selected ticker.": "所选股票的最新 SEC 申报文件。",
|
|
384
|
-
"Price
|
|
385
|
-
"Price trigger alerts with notifications": "带通知的价格触发提醒",
|
|
384
|
+
"Price, earnings, filing, news and flow alerts": "价格、财报、申报文件、新闻和期权资金流提醒",
|
|
386
385
|
"Create a price alert from a symbol, condition, and target price": "用代码、条件和目标价创建价格提醒",
|
|
387
386
|
"Public market heatmap for the largest US stocks and ETFs.": "美股大盘股与 ETF 的市场热力图。",
|
|
388
387
|
"Top gainers, losers, most active, and trending tickers": "涨幅榜、跌幅榜、成交活跃与热门股票",
|
|
@@ -679,7 +678,7 @@ export const zhCN: Record<string, string> = {
|
|
|
679
678
|
"Go Pro for real-time data at gloom.sh/cloud, free for 7 days.": "在 gloom.sh/cloud 升级 Pro 获取实时数据,免费试用 7 天。",
|
|
680
679
|
"real-time options": "实时期权",
|
|
681
680
|
"mixed real-time and delayed options": "实时与延迟期权混合",
|
|
682
|
-
"options
|
|
681
|
+
"Delayed options quotes, real-time stream unavailable": "期权报价延迟,实时数据流不可用",
|
|
683
682
|
"crossed quote": "交叉报价",
|
|
684
683
|
"one-sided quote": "单边报价",
|
|
685
684
|
"no bid/ask": "无买卖报价",
|
|
@@ -1622,4 +1621,11 @@ export const zhCN: Record<string, string> = {
|
|
|
1622
1621
|
"It is kept once it has been read with a connection.": "联网阅读过一次后即会保存。",
|
|
1623
1622
|
"Academy: {title}": "学院:{title}",
|
|
1624
1623
|
"{minutes} min": "{minutes} 分钟",
|
|
1624
|
+
"Delisted {date}": "已退市 {date}",
|
|
1625
|
+
"Last traded {date}": "最后交易 {date}",
|
|
1626
|
+
"Account data": "账户数据",
|
|
1627
|
+
"Sync with Gloom Cloud": "与 Gloom Cloud 同步",
|
|
1628
|
+
"Keep on this device": "保留在此设备上",
|
|
1629
|
+
"Daily roundups, the weekly brief, position alerts, the mobile app and Ask Gloom will go without this account.": "每日汇总、每周简报、持仓提醒、移动应用和 Ask Gloom 将不再包含此账户。",
|
|
1630
|
+
"Account data stays on this device, out of Gloom Cloud": "账户数据保留在此设备上,不上传到 Gloom Cloud",
|
|
1625
1631
|
};
|
package/src/i18n/zh-tw.ts
CHANGED
|
@@ -328,7 +328,7 @@ export const zhTW: Record<string, string> = {
|
|
|
328
328
|
"Insider Trading": "內部人士交易",
|
|
329
329
|
"SEC": "SEC 申報",
|
|
330
330
|
"Notes": "筆記",
|
|
331
|
-
"Alerts": "
|
|
331
|
+
"Alerts": "提醒",
|
|
332
332
|
"Market Heatmap": "市場熱力圖",
|
|
333
333
|
"Market Movers": "市場異動",
|
|
334
334
|
"Sector Performance": "類股表現",
|
|
@@ -381,8 +381,7 @@ export const zhTW: Record<string, string> = {
|
|
|
381
381
|
"Insider transactions for a ticker; alone, the companies whose insiders are buying or selling.": "某檔股票的內部人士交易;不指定股票時,列出內部人士正在買進或賣出的公司。",
|
|
382
382
|
"Recent SEC filings for US equities": "美股最新 SEC 申報檔案",
|
|
383
383
|
"Recent SEC filings for the selected ticker.": "所選股票的最新 SEC 申報檔案。",
|
|
384
|
-
"Price
|
|
385
|
-
"Price trigger alerts with notifications": "透過通知的價格觸發提醒",
|
|
384
|
+
"Price, earnings, filing, news and flow alerts": "價格、財報、申報文件、新聞與選擇權資金流提醒",
|
|
386
385
|
"Create a price alert from a symbol, condition, and target price": "用代號、條件和目標價建立價格提醒",
|
|
387
386
|
"Public market heatmap for the largest US stocks and ETFs.": "美股大盤股與 ETF 的市場熱力圖。",
|
|
388
387
|
"Top gainers, losers, most active, and trending tickers": "漲幅榜、跌幅榜、成交活躍與熱門股票",
|
|
@@ -679,7 +678,7 @@ export const zhTW: Record<string, string> = {
|
|
|
679
678
|
"Go Pro for real-time data at gloom.sh/cloud, free for 7 days.": "在 gloom.sh/cloud 升級 Pro 取得即時資料,免費試用 7 天。",
|
|
680
679
|
"real-time options": "即時選擇權",
|
|
681
680
|
"mixed real-time and delayed options": "即時與延遲選擇權混合",
|
|
682
|
-
"options
|
|
681
|
+
"Delayed options quotes, real-time stream unavailable": "選擇權報價延遲,即時資料流無法使用",
|
|
683
682
|
"crossed quote": "交叉報價",
|
|
684
683
|
"one-sided quote": "單邊報價",
|
|
685
684
|
"no bid/ask": "無買賣報價",
|
|
@@ -1622,4 +1621,11 @@ export const zhTW: Record<string, string> = {
|
|
|
1622
1621
|
"It is kept once it has been read with a connection.": "連線閱讀過一次後就會儲存。",
|
|
1623
1622
|
"Academy: {title}": "學院:{title}",
|
|
1624
1623
|
"{minutes} min": "{minutes} 分鐘",
|
|
1624
|
+
"Delisted {date}": "已下市 {date}",
|
|
1625
|
+
"Last traded {date}": "最後交易 {date}",
|
|
1626
|
+
"Account data": "帳戶資料",
|
|
1627
|
+
"Sync with Gloom Cloud": "與 Gloom Cloud 同步",
|
|
1628
|
+
"Keep on this device": "保留在此裝置上",
|
|
1629
|
+
"Daily roundups, the weekly brief, position alerts, the mobile app and Ask Gloom will go without this account.": "每日摘要、每週簡報、持倉提醒、行動應用程式和 Ask Gloom 將不再包含此帳戶。",
|
|
1630
|
+
"Account data stays on this device, out of Gloom Cloud": "帳戶資料保留在此裝置上,不上傳至 Gloom Cloud",
|
|
1625
1631
|
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { futuresContractByCode, type FuturesContract } from "./futures-board";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The names a reader types for the commodities on the futures board, by the
|
|
5
|
+
* board code they trade under. A news search that names one ("arabica",
|
|
6
|
+
* "sugar prices") offers the future's own news route (news KC=F), and a
|
|
7
|
+
* future's news searches the stories of its commodity by the first name
|
|
8
|
+
* listed for its code (KC=F reads "coffee"): news is filed under companies,
|
|
9
|
+
* and the service matches the same names to the stories filed under each
|
|
10
|
+
* future. Longer names win where one holds another ("soybean oil", "oil"),
|
|
11
|
+
* and a name in any of its forms counts (hogs, soybeans), as the service
|
|
12
|
+
* reads it. A null code is an oil no future trades: "palm oil" is not crude.
|
|
13
|
+
*/
|
|
14
|
+
const COMMODITY_NAMES: ReadonlyArray<readonly [name: string, code: string | null]> = [
|
|
15
|
+
["coffee", "KC"], ["arabica", "KC"],
|
|
16
|
+
["sugar", "SB"], ["cocoa", "CC"], ["cotton", "CT"], ["orange juice", "OJ"], ["lumber", "LBR"],
|
|
17
|
+
["wheat", "ZW"], ["wheat", "KE"], ["corn", "ZC"],
|
|
18
|
+
["soybean", "ZS"], ["soy", "ZS"], ["soybean meal", "ZM"], ["soybean oil", "ZL"], ["soy oil", "ZL"],
|
|
19
|
+
// Cattle finds the ranchers' and packers' stories "live cattle" misses; a bare "hog" finds Harley-Davidson (HOG).
|
|
20
|
+
["cattle", "LE"], ["live cattle", "LE"], ["lean hog", "HE"], ["hog", "HE"],
|
|
21
|
+
["gold", "GC"], ["silver", "SI"], ["copper", "HG"], ["platinum", "PL"], ["palladium", "PA"],
|
|
22
|
+
["aluminum", "ALI"], ["aluminium", "ALI"],
|
|
23
|
+
["oil", "CL"], ["crude oil", "CL"], ["crude", "CL"], ["wti", "CL"], ["oil", "QM"], ["oil", "MCL"],
|
|
24
|
+
["brent", "BZ"], ["heating oil", "HO"], ["gasoline", "RB"],
|
|
25
|
+
["natural gas", "NG"], ["natgas", "NG"], ["natural gas", "QG"], ["natural gas", "MNG"], ["natural gas", "HH"],
|
|
26
|
+
...["palm", "olive", "cooking", "vegetable", "corn", "edible", "sunflower", "canola", "rapeseed", "coconut", "peanut", "sesame",
|
|
27
|
+
"avocado", "cottonseed", "linseed", "flaxseed", "fish", "castor", "essential", "hemp", "cbd", "motor", "engine",
|
|
28
|
+
"lubricating", "baby", "snake"].map((kind) => [`${kind} oil`, null] as const),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** Words that end in s without being plurals, as the service keeps them whole. */
|
|
32
|
+
const NOT_PLURAL = new Set(["news", "series", "species"]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A word and its simple singular or plural, as the service's search reads a
|
|
36
|
+
* word: hogs and hog, soybeans and soybean, gases and gas.
|
|
37
|
+
*/
|
|
38
|
+
function wordForms(word: string): string[] {
|
|
39
|
+
if (!/^\p{L}+$/u.test(word) || NOT_PLURAL.has(word)) return [word];
|
|
40
|
+
if (word.length > 3 && word.endsWith("s") && !/(ss|us|is)$/.test(word)) {
|
|
41
|
+
const singulars = [word.slice(0, -1)];
|
|
42
|
+
if (word.length > 4 && word.endsWith("ies")) singulars.push(`${word.slice(0, -3)}y`);
|
|
43
|
+
else if (/(s|x|z|ch|sh)es$/.test(word)) singulars.push(word.slice(0, -2));
|
|
44
|
+
return [word, ...singulars];
|
|
45
|
+
}
|
|
46
|
+
if (word.length < 3) return [word];
|
|
47
|
+
const plural = /[^aeiou]y$/.test(word) ? `${word.slice(0, -1)}ies` : /(s|x|z|ch|sh)$/.test(word) ? `${word}es` : `${word}s`;
|
|
48
|
+
return [word, plural];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const PHRASES = COMMODITY_NAMES.map(([name, code]) => ({ words: name.split(" "), code }))
|
|
52
|
+
.sort((a, b) => b.words.length - a.words.length);
|
|
53
|
+
|
|
54
|
+
/** The words of a text as the service reads them: lower case, split on anything but letters and digits. */
|
|
55
|
+
const words = (text: string) => text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
56
|
+
|
|
57
|
+
/** Whether the typed words are the name's, each in any of its forms. */
|
|
58
|
+
const sameWords = (typed: readonly string[], name: readonly string[]) => typed.length === name.length
|
|
59
|
+
&& name.every((word, index) => wordForms(typed[index]!).includes(word));
|
|
60
|
+
|
|
61
|
+
/** The first commodity a text names, as its future on the board: "arabica" and "sugar prices" name KC and SB; "palm oil" names none. */
|
|
62
|
+
export function commodityNamedIn(text: string): FuturesContract | null {
|
|
63
|
+
const typed = words(text);
|
|
64
|
+
for (let index = 0; index < typed.length;) {
|
|
65
|
+
const phrase = PHRASES.find((entry) => sameWords(typed.slice(index, index + entry.words.length), entry.words));
|
|
66
|
+
if (phrase?.code) return futuresContractByCode(phrase.code);
|
|
67
|
+
index += phrase?.words.length ?? 1;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Whether the text is only a commodity's name, in any of its forms: "soybeans" searches what "soybean" does. */
|
|
73
|
+
export function isCommodityName(text: string, name: string): boolean {
|
|
74
|
+
return sameWords(words(text), words(name));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The name a future's stories are searched by: coffee for KC=F, wheat for ZW=F and KE=F. Null for a future with no commodity name (indices, rates, currencies, TTF). */
|
|
78
|
+
export function commoditySearchName(contract: Pick<FuturesContract, "code">): string | null {
|
|
79
|
+
return COMMODITY_NAMES.find(([, code]) => code === contract.code)?.[0] ?? null;
|
|
80
|
+
}
|
|
@@ -66,11 +66,13 @@ export function loadOptionsEntry(options: {
|
|
|
66
66
|
const attempt = createAttempt(dataProvider.id, startedAt, "unsupported", "UNSUPPORTED_RANGE", "Options are not available");
|
|
67
67
|
return store.update(key, (current) => errorEntry(current, attempt));
|
|
68
68
|
}
|
|
69
|
+
// This path has no handle on the provider's cache, so a refresh it asks for must reach the source, and a
|
|
70
|
+
// stored chain past its stale time (kept for two days) is refetched rather than shown as current.
|
|
69
71
|
const data = await dataProvider.getOptionsChain(
|
|
70
72
|
request.instrument.symbol,
|
|
71
73
|
request.instrument.exchange ?? "",
|
|
72
74
|
request.expirationDate,
|
|
73
|
-
toMarketDataContext(request.instrument),
|
|
75
|
+
{ ...toMarketDataContext(request.instrument), cacheMode: forceRefresh ? "refresh" : "current" },
|
|
74
76
|
);
|
|
75
77
|
const attempts = [createAttempt(dataProvider.id, startedAt, data.expirationDates.length > 0 ? "success" : "empty", data.expirationDates.length === 0 ? "NO_DATA" : undefined)];
|
|
76
78
|
return store.update(key, (current) => ({
|
|
@@ -61,8 +61,10 @@ export function normalizeFreshChartData(
|
|
|
61
61
|
: request.granularity === "detail" ? request.barSize : undefined;
|
|
62
62
|
const session = parseHistorySession(history?.session, { symbol: target.symbol, exchange: target.exchange ?? "",
|
|
63
63
|
interval: interval ?? undefined }) ?? undefined;
|
|
64
|
+
// A listing that no longer trades ends on its last session for good.
|
|
64
65
|
if (
|
|
65
|
-
|
|
66
|
+
!history?.delisting
|
|
67
|
+
&& isIntradayChartRequest(request, history)
|
|
66
68
|
&& isPriceHistoryStaleForCurrentWindow(normalized, Date.now(), { symbol: target.symbol, exchange: target.exchange,
|
|
67
69
|
intervalMs: interval ? priceHistoryIntervalMs(interval) : undefined, session, fetchedAt: fetchedAt ?? undefined })
|
|
68
70
|
) {
|
|
@@ -45,6 +45,8 @@ export interface FuturesContract {
|
|
|
45
45
|
* index, and RS1, MES, MYM, M2K, MCL, MNG and HH are listed abroad.
|
|
46
46
|
*/
|
|
47
47
|
bareCode?: true;
|
|
48
|
+
/** Everyday names the board's own does not hold: oil is WTI crude, not heating or soybean oil. */
|
|
49
|
+
aliases?: readonly string[];
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/** 0.005 -> 3, 0.25 -> 2, 1 -> 0. Written for ticks, not general numbers. */
|
|
@@ -100,7 +102,7 @@ export const FUTURES_CONTRACTS: FuturesContract[] = [
|
|
|
100
102
|
{ symbol: "ZB=F", code: "ZB", name: "30-Year T-Bond", sector: "rates", venue: "CBT", bareCode: true },
|
|
101
103
|
{ symbol: "UB=F", code: "UB", name: "Ultra T-Bond", sector: "rates", venue: "CBT", bareCode: true },
|
|
102
104
|
|
|
103
|
-
{ symbol: "CL=F", code: "CL", name: "WTI Crude Oil", sector: "energy", venue: "NYM", everyMonth: true, tick: 0.01 },
|
|
105
|
+
{ symbol: "CL=F", code: "CL", name: "WTI Crude Oil", sector: "energy", venue: "NYM", everyMonth: true, tick: 0.01, aliases: ["oil"] },
|
|
104
106
|
{ symbol: "BZ=F", code: "BZ", name: "Brent Crude Oil", sector: "energy", venue: "NYM", everyMonth: true, tick: 0.01 },
|
|
105
107
|
{ symbol: "NG=F", code: "NG", name: "Natural Gas", sector: "energy", venue: "NYM", everyMonth: true, tick: 0.001 },
|
|
106
108
|
// Quoted in euros per MWh.
|
|
@@ -197,6 +199,41 @@ export function futuresBoardContract(value: unknown): FuturesContract | null {
|
|
|
197
199
|
return BY_SYMBOL.get(typed(value)) ?? futuresContractByCode(value);
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
const nameWords = (text: string) => text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The item a typed name means: every word of it starts a word of the item's
|
|
206
|
+
* name ("orange juice", "sugar" for Sugar #11, "soybean" for Soybeans). Among
|
|
207
|
+
* those that match, the name with the fewest other words wins, then the
|
|
208
|
+
* list's order: "crude" is WTI before Brent and the micro, "yen" the Japanese
|
|
209
|
+
* yen before the Nikkei quoted in yen. A name of fewer than three letters
|
|
210
|
+
* matches nothing, so a slip of a code does not land on a random row.
|
|
211
|
+
*/
|
|
212
|
+
export function bestNameMatch<T>(items: readonly T[], names: (item: T) => readonly string[], value: unknown): T | null {
|
|
213
|
+
const wanted = typeof value === "string" ? nameWords(value) : [];
|
|
214
|
+
if (wanted.join("").length < 3) return null;
|
|
215
|
+
let best: T | null = null;
|
|
216
|
+
let fewest = Number.POSITIVE_INFINITY;
|
|
217
|
+
for (const item of items) {
|
|
218
|
+
for (const name of names(item)) {
|
|
219
|
+
const have = nameWords(name);
|
|
220
|
+
if (have.length >= fewest || !wanted.every((word) => have.some((part) => part.startsWith(word)))) continue;
|
|
221
|
+
best = item;
|
|
222
|
+
fewest = have.length;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return best;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The board row a commodity's name stands for, as a reader types it into a
|
|
230
|
+
* futures function: cocoa, coffee, sugar, cotton, orange juice, gold, copper,
|
|
231
|
+
* crude, natural gas. Codes and symbols are `futuresBoardContract`'s.
|
|
232
|
+
*/
|
|
233
|
+
export function futuresContractByName(value: unknown): FuturesContract | null {
|
|
234
|
+
return bestNameMatch(FUTURES_CONTRACTS, (contract) => [contract.name, ...contract.aliases ?? []], value);
|
|
235
|
+
}
|
|
236
|
+
|
|
200
237
|
/**
|
|
201
238
|
* The future a bare symbol typed for a security stands for: a code no listed
|
|
202
239
|
* security answers to (TTF is TTF=F). Null for every other symbol, and for a
|