gloomberb 0.9.7 → 0.9.9
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/auth.ts +20 -0
- package/src/api-client/index.test.ts +3 -3
- package/src/api-client/index.ts +48 -6
- package/src/api-client/request.test.ts +63 -0
- package/src/api-client/request.ts +53 -5
- package/src/api-client/types.ts +45 -2
- package/src/app/pane-runtime/layout-placement.ts +0 -1
- package/src/app/runtime/desktop-deeplink.test.ts +87 -0
- package/src/app/runtime/desktop-deeplink.ts +496 -0
- package/src/app.tsx +34 -0
- package/src/brokers/account-cache.ts +2 -10
- package/src/brokers/broker-portfolio-sync.ts +0 -1
- package/src/brokers/broker-ticker-sync.ts +20 -16
- package/src/brokers/remote-broker-adapter.ts +3 -0
- package/src/capabilities/registry.test.ts +25 -0
- package/src/capabilities/registry.ts +22 -5
- package/src/cli/commands/automation.ts +1 -3
- package/src/cli/commands/command-utils.ts +0 -7
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/commands/plugins.ts +34 -17
- package/src/cli/index.ts +2 -2
- package/src/cli.test.ts +0 -157
- package/src/components/chart/bar-chart-renderer.test.ts +87 -12
- package/src/components/chart/bar-chart-renderer.ts +111 -55
- package/src/components/chart/comparison/renderer.ts +0 -1
- package/src/components/chart/comparison-stock-chart/render-output.ts +3 -8
- package/src/components/chart/comparison-stock-chart/view.tsx +0 -2
- package/src/components/chart/core/index.test.ts +0 -24
- package/src/components/chart/core/renderer.ts +0 -4
- package/src/components/chart/core/resolution.test.ts +0 -17
- package/src/components/chart/core/resolution.ts +0 -26
- package/src/components/chart/core/scroll.test.ts +0 -6
- package/src/components/chart/core/scroll.ts +0 -22
- package/src/components/chart/core/viewport.test.ts +2 -23
- package/src/components/chart/core/viewport.ts +0 -27
- package/src/components/chart/market-session.ts +1 -1
- package/src/components/chart/native/surface/hooks.ts +10 -3
- package/src/components/chart/static/bar-chart-surface.tsx +104 -20
- package/src/components/chart/stock-chart/data-runtime.ts +0 -1
- package/src/components/chart/stock-chart/footer.test.ts +65 -0
- package/src/components/chart/stock-chart/footer.ts +68 -13
- package/src/components/chart/stock-chart/resolved.tsx +18 -4
- package/src/components/chart/stock-chart/viewport/effects.ts +0 -3
- package/src/components/command-bar/helpers.test.ts +1 -80
- package/src/components/command-bar/helpers.ts +1 -36
- package/src/components/command-bar/pane-settings.ts +1 -1
- package/src/components/command-bar/pane-templates/items.ts +11 -3
- package/src/components/command-bar/panel/runtime.ts +0 -2
- package/src/components/command-bar/routes/root/results.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +0 -11
- package/src/components/command-bar/surface/index.test.tsx +52 -16
- package/src/components/command-bar/surface/index.tsx +0 -1
- package/src/components/command-bar/workflow/ops.ts +81 -20
- package/src/components/feed-data-table/stack-view.test.tsx +36 -130
- package/src/components/feed-data-table/stack-view.tsx +0 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/shell/index.test.tsx +2 -113
- package/src/components/layout/shell/index.tsx +0 -1
- package/src/components/layout/shell/menu.ts +0 -1
- package/src/components/layout/shell/terminal-pointer-runtime.ts +0 -1
- package/src/components/onboarding/onboarding-wizard.tsx +3 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -23
- package/src/components/price-selector-dialog.tsx +3 -3
- package/src/components/ticker/badge/text.tsx +0 -1
- package/src/components/toggle-list.tsx +29 -73
- package/src/components/ui/button.tsx +4 -1
- package/src/components/ui/checkbox.tsx +76 -0
- package/src/components/ui/choice-dialog.tsx +15 -3
- package/src/components/ui/frame.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/message-composer.tsx +4 -0
- package/src/components/ui/native-select.tsx +4 -2
- package/src/components/ui/page-stack-view.tsx +1 -1
- package/src/components/ui/ui.test.tsx +0 -78
- package/src/core/state/app/layout-reducer.ts +0 -1
- package/src/data/config/layout.ts +3 -3
- package/src/data/resource-store.ts +6 -0
- package/src/data/sqlite/cache.test.ts +5 -0
- package/src/data/ticker-store.ts +0 -7
- package/src/market-data/coordinator/auxiliary.ts +1 -44
- package/src/market-data/coordinator/entries.ts +0 -1
- package/src/market-data/coordinator/events.test.ts +49 -0
- package/src/market-data/coordinator/events.ts +25 -4
- package/src/market-data/coordinator/index.test.ts +223 -10
- package/src/market-data/coordinator/index.ts +41 -21
- package/src/market-data/coordinator/quotes.ts +13 -11
- package/src/market-data/coordinator/subscriptions.test.ts +1 -0
- package/src/market-data/hooks.tsx +17 -13
- package/src/market-data/market/freshness.ts +58 -1
- package/src/market-data/market/status.ts +19 -1
- package/src/market-data/query-store.ts +0 -4
- package/src/market-data/quotes/freshness.test.ts +49 -1
- package/src/market-data/quotes/freshness.ts +8 -0
- package/src/market-data/quotes/resolution.test.ts +185 -0
- package/src/market-data/quotes/resolution.ts +96 -10
- package/src/market-data/request-types.test.ts +61 -1
- package/src/market-data/request-types.ts +42 -8
- package/src/market-data/result-types.ts +0 -11
- package/src/market-data/selectors.ts +1 -5
- package/src/news/aggregator.ts +5 -1
- package/src/pane-settings.test.ts +0 -18
- package/src/plugins/builtin/account-management/footer.ts +2 -5
- package/src/plugins/builtin/account-management/form-components.tsx +175 -59
- package/src/plugins/builtin/account-management/keyboard.ts +32 -2
- package/src/plugins/builtin/account-management/model.test.ts +99 -4
- package/src/plugins/builtin/account-management/model.ts +259 -23
- package/src/plugins/builtin/account-management/pane.tsx +784 -155
- package/src/plugins/builtin/broker-manager/index.test.tsx +0 -32
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/satellite-images.tsx +1 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -1
- package/src/plugins/builtin/buildout/format.ts +2 -11
- package/src/plugins/builtin/chat/chat.test.tsx +127 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -21
- package/src/plugins/builtin/chat/content/composer-cursor.ts +29 -0
- package/src/plugins/builtin/chat/content/composer-runtime.ts +22 -24
- package/src/plugins/builtin/chat/content/composer.tsx +84 -1
- package/src/plugins/builtin/chat/content/index.tsx +153 -16
- package/src/plugins/builtin/chat/content/layout-metrics.ts +10 -1
- package/src/plugins/builtin/chat/content/mentions.test.ts +74 -0
- package/src/plugins/builtin/chat/content/mentions.ts +88 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +264 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +47 -1
- package/src/plugins/builtin/chat/content/snapshot.ts +7 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +0 -5
- package/src/plugins/builtin/chat/controller/index.ts +0 -10
- package/src/plugins/builtin/chat/controller.test.ts +5 -5
- package/src/plugins/builtin/chat/message/profile-popover.test.tsx +36 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +124 -20
- package/src/plugins/builtin/chat/sidebar.test.tsx +83 -6
- package/src/plugins/builtin/chat/sidebar.tsx +26 -2
- package/src/plugins/builtin/chat/test-harness.tsx +22 -12
- package/src/plugins/builtin/cloud/plugin.tsx +8 -1
- package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -1
- package/src/plugins/builtin/congress-trades/model.ts +2 -7
- package/src/plugins/builtin/congress-trades/pane.tsx +0 -1
- package/src/plugins/builtin/correlation/index.tsx +4 -1
- package/src/plugins/builtin/correlation/relationship/controls.tsx +8 -2
- package/src/plugins/builtin/correlation/relationship/model.ts +22 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +9 -3
- package/src/plugins/builtin/earnings/index.tsx +0 -9
- package/src/plugins/builtin/econ/index.tsx +1 -5
- package/src/plugins/builtin/help/index.tsx +2 -2
- package/src/plugins/builtin/holders/pane.tsx +0 -5
- package/src/plugins/builtin/insider/index.tsx +1 -8
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +0 -26
- package/src/plugins/builtin/kelly-sizer/index.tsx +0 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -2
- package/src/plugins/builtin/news/index.tsx +0 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +0 -11
- package/src/plugins/builtin/options/view.test.tsx +1 -38
- package/src/plugins/builtin/plugin-groups.ts +17 -3
- package/src/plugins/builtin/portfolio-list/column-values.ts +15 -8
- package/src/plugins/builtin/portfolio-list/index.test.ts +52 -25
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +44 -7
- package/src/plugins/builtin/portfolio-list/pane/data.test.ts +54 -0
- package/src/plugins/builtin/portfolio-list/pane/data.ts +50 -3
- package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +314 -16
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -4
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +83 -24
- package/src/plugins/builtin/sec/filing-documents.ts +0 -11
- package/src/plugins/builtin/sec/index.tsx +0 -2
- package/src/plugins/builtin/substack/article-detail.tsx +1 -1
- package/src/plugins/builtin/thirteenf/api.ts +0 -15
- package/src/plugins/builtin/thirteenf/pane.tsx +1 -3
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +42 -6
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +415 -64
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +23 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +8 -1
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +3 -1
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +95 -1
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +0 -7
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +5 -34
- package/src/plugins/builtin/world-indices/index.tsx +0 -4
- package/src/plugins/builtin/world-indices/model.ts +0 -7
- package/src/plugins/ibkr/account-cache.ts +1 -1
- package/src/plugins/ibkr/gateway/market-data.ts +12 -5
- package/src/plugins/ibkr/gateway/service/index.test.ts +31 -0
- package/src/plugins/ibkr/portfolio-performance.ts +1 -1
- package/src/plugins/ibkr/trade/utils.ts +1 -7
- package/src/plugins/loader.ts +2 -1
- package/src/plugins/prediction-markets/detail.test.tsx +0 -34
- package/src/plugins/registry/context.ts +15 -0
- package/src/plugins/registry/contributions.ts +22 -11
- package/src/plugins/registry/index.test.ts +44 -0
- package/src/plugins/registry/index.ts +75 -26
- package/src/remote/controller.ts +1 -7
- package/src/remote/json-patch.ts +15 -1
- package/src/remote/server.ts +16 -6
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +14 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/bun/index.ts +42 -0
- package/src/renderers/electrobun/shared/protocol.ts +5 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +29 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/data-table/dom.ts +0 -1
- package/src/renderers/electrobun/view/desktop/controls.tsx +135 -4
- package/src/renderers/electrobun/view/desktop/list-view.tsx +38 -25
- package/src/renderers/electrobun/view/desktop-deeplink-bridge.ts +10 -0
- package/src/renderers/electrobun/view/host/box.tsx +0 -2
- package/src/renderers/electrobun/view/host/mouse.ts +0 -11
- package/src/renderers/electrobun/view/host/scroll-box.tsx +1 -2
- package/src/renderers/electrobun/view/host/text.tsx +1 -2
- package/src/renderers/electrobun/view/http-fetch.ts +15 -2
- package/src/renderers/electrobun/view/main.tsx +3 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -18
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +50 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +36 -0
- package/src/renderers/electrobun/view/ui-host.tsx +2 -0
- package/src/renderers/opentui/host.tsx +11 -3
- package/src/renderers/opentui/input-host.tsx +5 -1
- package/src/sources/gloomberb-cloud/index.test.ts +0 -72
- package/src/sources/gloomberb-cloud/index.ts +1 -30
- package/src/sources/gloomberb-cloud/news.ts +0 -12
- package/src/sources/provider-router/batches.ts +11 -3
- package/src/sources/provider-router/cached-financials.test.ts +123 -0
- package/src/sources/provider-router/financial-routes.ts +77 -5
- package/src/sources/provider-router/financials.test.ts +49 -0
- package/src/sources/provider-router/financials.ts +40 -3
- package/src/sources/provider-router/index.test.ts +314 -0
- package/src/sources/provider-router/primary.ts +17 -3
- package/src/sources/provider-router/search.ts +5 -0
- package/src/sources/provider-router/streaming.ts +6 -1
- package/src/sources/provider-router/supplemental.ts +4 -3
- package/src/sources/sec-edgar.test.ts +42 -0
- package/src/sources/sec-edgar.ts +1 -2
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/sources/yahoo-finance/mappers.test.ts +29 -0
- package/src/sources/yahoo-finance/mappers.ts +2 -1
- package/src/sources/yahoo-finance/options.ts +59 -14
- package/src/sources/yahoo-finance/quote-summary.ts +4 -1
- package/src/sources/yahoo-finance/requests.ts +2 -1
- package/src/sources/yahoo-finance/snapshots.ts +13 -2
- package/src/sources/yahoo-finance.test.ts +40 -21
- package/src/sources/yahoo-finance.ts +19 -10
- package/src/state/app/context/index.test.tsx +0 -81
- package/src/state/app/context/selector.test.tsx +6 -2
- package/src/state/hooks/quote-streaming.test.ts +14 -1
- package/src/state/hooks/quote-streaming.ts +5 -4
- package/src/sync/controller.test.ts +31 -0
- package/src/sync/controller.ts +300 -0
- package/src/sync/core-contributors.test.ts +251 -0
- package/src/sync/core-contributors.ts +400 -0
- package/src/sync/profile-analytics.ts +39 -0
- package/src/sync/react.ts +53 -0
- package/src/sync/types.ts +77 -0
- package/src/test-support/data-provider.ts +0 -1
- package/src/types/desktop-deeplink.ts +7 -0
- package/src/types/plugin.ts +3 -0
- package/src/ui/host.tsx +15 -1
- package/src/ui/index.tsx +2 -0
- package/src/updater.test.ts +0 -21
- package/src/updater.ts +20 -16
- package/src/utils/async-deadline.ts +26 -0
- package/src/utils/exchanges.test.ts +11 -1
- package/src/utils/exchanges.ts +5 -1
- package/src/utils/selection-clipboard.ts +2 -2
- package/src/version.ts +1 -1
- package/src/components/layout/pane/header.test.tsx +0 -39
- package/src/plugins/builtin/chat/channels.test.ts +0 -28
- package/src/plugins/builtin/chat/index.tsx +0 -4
- package/src/plugins/builtin/cloud-tweets/index.ts +0 -1
- package/src/plugins/builtin/earnings/index.test.ts +0 -12
- package/src/plugins/builtin/help/index.test.tsx +0 -242
- package/src/plugins/builtin/substack/article-detail.test.tsx +0 -10
- package/src/renderers/electrobun/bun/application-menu/index.test.ts +0 -21
- package/src/renderers/electrobun/bun/window/focus.test.ts +0 -35
- package/src/renderers/electrobun/view/host/mouse.test.ts +0 -37
- package/src/renderers/electrobun/view/host/text.test.ts +0 -22
- package/src/renderers/opentui/dialog-host.test.tsx +0 -44
- /package/src/{plugins/ibkr → utils}/hash.ts +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MarketDataRequestContext } from "../../types/data-provider";
|
|
2
|
-
import type { OptionContract, OptionsChain, Quote } from "../../types/financials";
|
|
2
|
+
import type { MarketState, OptionContract, OptionsChain, Quote } from "../../types/financials";
|
|
3
3
|
import { parseOptionSymbol } from "../../utils/options";
|
|
4
4
|
import { getYahooSymbolsToTry } from "./symbols";
|
|
5
5
|
|
|
@@ -14,16 +14,40 @@ interface LoadYahooOptionsChainOptions {
|
|
|
14
14
|
|
|
15
15
|
interface GetYahooOptionQuoteOptions {
|
|
16
16
|
context?: MarketDataRequestContext;
|
|
17
|
-
|
|
17
|
+
getOptionsChainResult: (
|
|
18
18
|
ticker: string,
|
|
19
19
|
exchange?: string,
|
|
20
20
|
expirationDate?: number,
|
|
21
21
|
context?: MarketDataRequestContext,
|
|
22
|
-
) => Promise<
|
|
22
|
+
) => Promise<YahooOptionsChainResult>;
|
|
23
23
|
providerId: string;
|
|
24
24
|
ticker: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface YahooOptionsChainResult {
|
|
28
|
+
chain: OptionsChain;
|
|
29
|
+
underlyingMarketState?: MarketState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MARKET_STATES = new Set<MarketState>([
|
|
33
|
+
"PRE",
|
|
34
|
+
"REGULAR",
|
|
35
|
+
"POST",
|
|
36
|
+
"PREPRE",
|
|
37
|
+
"POSTPOST",
|
|
38
|
+
"CLOSED",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
function normalizeYahooMarketState(value: unknown): MarketState | undefined {
|
|
42
|
+
const normalized = typeof value === "string" ? value.toUpperCase() as MarketState : undefined;
|
|
43
|
+
return normalized && MARKET_STATES.has(normalized) ? normalized : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function deriveOptionMarketState(underlyingMarketState?: MarketState): MarketState | undefined {
|
|
47
|
+
if (!underlyingMarketState) return undefined;
|
|
48
|
+
return underlyingMarketState === "REGULAR" ? "REGULAR" : "CLOSED";
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
function mapYahooOptionContract(raw: Record<string, any>): OptionContract {
|
|
28
52
|
return {
|
|
29
53
|
contractSymbol: raw.contractSymbol ?? "",
|
|
@@ -43,12 +67,12 @@ function mapYahooOptionContract(raw: Record<string, any>): OptionContract {
|
|
|
43
67
|
};
|
|
44
68
|
}
|
|
45
69
|
|
|
46
|
-
export async function
|
|
70
|
+
export async function loadYahooOptionsChainResult({
|
|
47
71
|
exchange,
|
|
48
72
|
expirationDate,
|
|
49
73
|
fetchJsonWithCrumb,
|
|
50
74
|
ticker,
|
|
51
|
-
}: LoadYahooOptionsChainOptions): Promise<
|
|
75
|
+
}: LoadYahooOptionsChainOptions): Promise<YahooOptionsChainResult> {
|
|
52
76
|
const symbolsToTry = getYahooSymbolsToTry(ticker, exchange);
|
|
53
77
|
let lastError: any;
|
|
54
78
|
|
|
@@ -62,6 +86,7 @@ export async function loadYahooOptionsChain({
|
|
|
62
86
|
result?: Array<{
|
|
63
87
|
underlyingSymbol?: string;
|
|
64
88
|
expirationDates?: number[];
|
|
89
|
+
quote?: { marketState?: unknown };
|
|
65
90
|
options?: Array<{
|
|
66
91
|
calls?: Array<Record<string, any>>;
|
|
67
92
|
puts?: Array<Record<string, any>>;
|
|
@@ -75,10 +100,13 @@ export async function loadYahooOptionsChain({
|
|
|
75
100
|
|
|
76
101
|
const opts = result.options?.[0];
|
|
77
102
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
103
|
+
chain: {
|
|
104
|
+
underlyingSymbol: result.underlyingSymbol ?? symbol,
|
|
105
|
+
expirationDates: result.expirationDates ?? [],
|
|
106
|
+
calls: (opts?.calls ?? []).map((contract) => mapYahooOptionContract(contract)),
|
|
107
|
+
puts: (opts?.puts ?? []).map((contract) => mapYahooOptionContract(contract)),
|
|
108
|
+
},
|
|
109
|
+
underlyingMarketState: normalizeYahooMarketState(result.quote?.marketState),
|
|
82
110
|
};
|
|
83
111
|
} catch (err) {
|
|
84
112
|
lastError = err;
|
|
@@ -87,15 +115,26 @@ export async function loadYahooOptionsChain({
|
|
|
87
115
|
throw lastError || new Error(`No options chain for ${ticker}`);
|
|
88
116
|
}
|
|
89
117
|
|
|
118
|
+
export async function loadYahooOptionsChain(
|
|
119
|
+
options: LoadYahooOptionsChainOptions,
|
|
120
|
+
): Promise<OptionsChain> {
|
|
121
|
+
return (await loadYahooOptionsChainResult(options)).chain;
|
|
122
|
+
}
|
|
123
|
+
|
|
90
124
|
export async function getYahooOptionQuote({
|
|
91
125
|
context,
|
|
92
|
-
|
|
126
|
+
getOptionsChainResult,
|
|
93
127
|
providerId,
|
|
94
128
|
ticker,
|
|
95
129
|
}: GetYahooOptionQuoteOptions): Promise<Quote> {
|
|
96
130
|
const parsed = parseOptionSymbol(ticker);
|
|
97
131
|
if (!parsed) throw new Error(`Unsupported option symbol ${ticker}`);
|
|
98
|
-
const chain = await
|
|
132
|
+
const { chain, underlyingMarketState } = await getOptionsChainResult(
|
|
133
|
+
parsed.underlying,
|
|
134
|
+
"",
|
|
135
|
+
parsed.expTs,
|
|
136
|
+
context,
|
|
137
|
+
);
|
|
99
138
|
const contracts = parsed.side === "C" ? chain.calls : chain.puts;
|
|
100
139
|
const contract = contracts.find((candidate) =>
|
|
101
140
|
Math.abs(candidate.strike - parsed.strike) < 0.001 &&
|
|
@@ -110,6 +149,12 @@ export async function getYahooOptionQuote({
|
|
|
110
149
|
: contract.ask > 0
|
|
111
150
|
? contract.ask
|
|
112
151
|
: undefined;
|
|
152
|
+
const marketState = deriveOptionMarketState(underlyingMarketState);
|
|
153
|
+
const lastUpdated = mark != null
|
|
154
|
+
? Date.now()
|
|
155
|
+
: contract.lastTradeDate > 0
|
|
156
|
+
? contract.lastTradeDate * 1000
|
|
157
|
+
: Date.now();
|
|
113
158
|
return {
|
|
114
159
|
symbol: ticker,
|
|
115
160
|
providerId,
|
|
@@ -122,13 +167,13 @@ export async function getYahooOptionQuote({
|
|
|
122
167
|
ask: contract.ask,
|
|
123
168
|
mark,
|
|
124
169
|
name: contract.contractSymbol,
|
|
125
|
-
lastUpdated
|
|
126
|
-
? contract.lastTradeDate * 1000
|
|
127
|
-
: Date.now(),
|
|
170
|
+
lastUpdated,
|
|
128
171
|
exchangeName: "OPTIONS",
|
|
129
172
|
fullExchangeName: "OPTIONS",
|
|
130
173
|
listingExchangeName: "OPTIONS",
|
|
131
174
|
listingExchangeFullName: "OPTIONS",
|
|
175
|
+
marketState,
|
|
176
|
+
sessionConfidence: marketState ? "derived" : "unknown",
|
|
132
177
|
dataSource: "delayed",
|
|
133
178
|
};
|
|
134
179
|
}
|
|
@@ -29,7 +29,10 @@ interface YahooQuoteSummaryOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
interface YahooCorporateActionsOptions extends YahooQuoteSummaryOptions {
|
|
32
|
-
fetchChart: (symbol: string, range: string, interval?: string) => Promise<
|
|
32
|
+
fetchChart: (symbol: string, range: string, interval?: string) => Promise<{
|
|
33
|
+
meta: NonNullable<ChartResult["meta"]>;
|
|
34
|
+
events?: ChartResult["events"];
|
|
35
|
+
}>;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export async function loadYahooHolders({
|
|
@@ -65,6 +65,7 @@ export async function fetchYahooExtendedHoursData(
|
|
|
65
65
|
http: YahooHttpClient,
|
|
66
66
|
symbol: string,
|
|
67
67
|
meta: NonNullable<ChartResult["meta"]>,
|
|
68
|
+
regularClose?: number,
|
|
68
69
|
): Promise<ExtendedHoursData> {
|
|
69
70
|
const marketState = deriveMarketState(meta);
|
|
70
71
|
if (marketState !== "PRE" && marketState !== "POST") return {};
|
|
@@ -80,7 +81,7 @@ export async function fetchYahooExtendedHoursData(
|
|
|
80
81
|
const result = data.chart?.result?.[0];
|
|
81
82
|
if (!result?.timestamp?.length) return {};
|
|
82
83
|
const closes = result.indicators?.quote?.[0]?.close || [];
|
|
83
|
-
return extractExtendedHoursPrices(meta, result.timestamp, closes, marketState);
|
|
84
|
+
return extractExtendedHoursPrices(meta, result.timestamp, closes, marketState, regularClose);
|
|
84
85
|
} catch {
|
|
85
86
|
return {};
|
|
86
87
|
}
|
|
@@ -42,6 +42,7 @@ interface YahooSnapshotLoaders {
|
|
|
42
42
|
fetchExtendedHoursData: (
|
|
43
43
|
symbol: string,
|
|
44
44
|
meta: NonNullable<ChartResult["meta"]>,
|
|
45
|
+
regularClose?: number,
|
|
45
46
|
) => Promise<ExtendedHoursData>;
|
|
46
47
|
fetchQuoteSupplement: (
|
|
47
48
|
symbol: string,
|
|
@@ -132,7 +133,12 @@ export async function loadYahooTickerFinancials(
|
|
|
132
133
|
const changePct = prev ? (change / prev) * 100 : 0;
|
|
133
134
|
|
|
134
135
|
const marketState = deriveMarketState(meta);
|
|
135
|
-
const
|
|
136
|
+
const extendedHoursBase = quoteSupplement.previousClose ?? prev;
|
|
137
|
+
const extHours = await loaders.fetchExtendedHoursData(
|
|
138
|
+
symbol,
|
|
139
|
+
meta,
|
|
140
|
+
extendedHoursBase == null ? undefined : extendedHoursBase * currencyDivisor,
|
|
141
|
+
);
|
|
136
142
|
if (currencyDivisor !== 1) {
|
|
137
143
|
normalizeExtendedHoursPrices(extHours, currencyDivisor);
|
|
138
144
|
}
|
|
@@ -207,7 +213,12 @@ export async function loadYahooQuote(
|
|
|
207
213
|
const change = prev != null ? price - prev : 0;
|
|
208
214
|
|
|
209
215
|
const marketState = deriveMarketState(meta);
|
|
210
|
-
const
|
|
216
|
+
const extendedHoursBase = quoteSupplement.previousClose ?? prev;
|
|
217
|
+
const extHours = await loaders.fetchExtendedHoursData(
|
|
218
|
+
symbol,
|
|
219
|
+
meta,
|
|
220
|
+
extendedHoursBase == null ? undefined : extendedHoursBase * currencyDivisor,
|
|
221
|
+
);
|
|
211
222
|
if (currencyDivisor !== 1) {
|
|
212
223
|
normalizeExtendedHoursPrices(extHours, currencyDivisor);
|
|
213
224
|
}
|
|
@@ -135,28 +135,38 @@ describe("YahooFinanceClient exchange aliases", () => {
|
|
|
135
135
|
|
|
136
136
|
test("maps IBKR option symbols to Yahoo option quote marks", async () => {
|
|
137
137
|
const provider = new YahooFinanceClient() as any;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
138
|
+
const expiration = 1_821_139_200;
|
|
139
|
+
let underlyingMarketState = "POST";
|
|
140
|
+
provider.http.fetchJsonWithCrumb = async () => ({
|
|
141
|
+
optionChain: {
|
|
142
|
+
result: [{
|
|
143
|
+
underlyingSymbol: "AMD",
|
|
144
|
+
expirationDates: [expiration],
|
|
145
|
+
quote: { marketState: underlyingMarketState },
|
|
146
|
+
options: [{
|
|
147
|
+
calls: [{
|
|
148
|
+
contractSymbol: "AMD270917C00230000",
|
|
149
|
+
strike: 230,
|
|
150
|
+
currency: "USD",
|
|
151
|
+
lastPrice: 251,
|
|
152
|
+
change: 1.5,
|
|
153
|
+
percentChange: 0.6,
|
|
154
|
+
volume: 10,
|
|
155
|
+
openInterest: 20,
|
|
156
|
+
bid: 250.25,
|
|
157
|
+
ask: 254.5,
|
|
158
|
+
impliedVolatility: 0.4,
|
|
159
|
+
inTheMoney: false,
|
|
160
|
+
expiration,
|
|
161
|
+
lastTradeDate: 1_800_000_000,
|
|
162
|
+
}],
|
|
163
|
+
puts: [],
|
|
164
|
+
}],
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
158
167
|
});
|
|
159
168
|
|
|
169
|
+
const requestedAt = Date.now();
|
|
160
170
|
const quote = await provider.getQuote("AMD 270917C00230000");
|
|
161
171
|
|
|
162
172
|
expect(quote).toMatchObject({
|
|
@@ -166,8 +176,17 @@ describe("YahooFinanceClient exchange aliases", () => {
|
|
|
166
176
|
bid: 250.25,
|
|
167
177
|
ask: 254.5,
|
|
168
178
|
providerId: "yahoo",
|
|
179
|
+
marketState: "CLOSED",
|
|
180
|
+
sessionConfidence: "derived",
|
|
181
|
+
});
|
|
182
|
+
expect(quote.lastUpdated).toBeGreaterThanOrEqual(requestedAt);
|
|
183
|
+
|
|
184
|
+
underlyingMarketState = "REGULAR";
|
|
185
|
+
const regularQuote = await provider.getQuote("AMD 270917C00230000");
|
|
186
|
+
expect(regularQuote).toMatchObject({
|
|
187
|
+
marketState: "REGULAR",
|
|
188
|
+
sessionConfidence: "derived",
|
|
169
189
|
});
|
|
170
|
-
expect(quote.lastUpdated).toBe(1_800_000_000_000);
|
|
171
190
|
});
|
|
172
191
|
|
|
173
192
|
test("preserves analyst rating price targets from upgrade history", async () => {
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import {
|
|
32
32
|
getYahooOptionQuote,
|
|
33
33
|
loadYahooOptionsChain,
|
|
34
|
+
loadYahooOptionsChainResult,
|
|
34
35
|
} from "./yahoo-finance/options";
|
|
35
36
|
import {
|
|
36
37
|
loadYahooAnalystResearch,
|
|
@@ -103,8 +104,12 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/** Fetch extended hours data using 1d intraday chart with pre/post market included */
|
|
106
|
-
private async fetchExtendedHoursData(
|
|
107
|
-
|
|
107
|
+
private async fetchExtendedHoursData(
|
|
108
|
+
symbol: string,
|
|
109
|
+
meta: NonNullable<ChartResult["meta"]>,
|
|
110
|
+
regularClose?: number,
|
|
111
|
+
) {
|
|
112
|
+
return fetchYahooExtendedHoursData(this.http, symbol, meta, regularClose);
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
private async fetchTimeseries(symbol: string, types: string[], period1 = "2010-01-01") {
|
|
@@ -151,8 +156,13 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
151
156
|
if (parseOptionSymbol(ticker)) {
|
|
152
157
|
return getYahooOptionQuote({
|
|
153
158
|
context,
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
getOptionsChainResult: (underlying, requestExchange, expirationDate) => (
|
|
160
|
+
loadYahooOptionsChainResult({
|
|
161
|
+
exchange: requestExchange ?? "",
|
|
162
|
+
expirationDate,
|
|
163
|
+
fetchJsonWithCrumb: (url) => this.http.fetchJsonWithCrumb(url),
|
|
164
|
+
ticker: underlying,
|
|
165
|
+
})
|
|
156
166
|
),
|
|
157
167
|
providerId: this.id,
|
|
158
168
|
ticker,
|
|
@@ -185,13 +195,12 @@ export class YahooFinanceClient implements DataProvider {
|
|
|
185
195
|
fromCurrency = normalized;
|
|
186
196
|
if (fromCurrency === "USD") return 1;
|
|
187
197
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
} catch {
|
|
193
|
-
return 1;
|
|
198
|
+
const { meta, history } = await this.fetchChart(`${fromCurrency}USD=X`, "1mo");
|
|
199
|
+
const rate = meta.regularMarketPrice ?? history[history.length - 1]?.close;
|
|
200
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0) {
|
|
201
|
+
throw new Error(`No exchange rate data for ${fromCurrency}/USD`);
|
|
194
202
|
}
|
|
203
|
+
return rate;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
206
|
/** Search for a ticker by name/symbol - uses direct fetch (no retry) for speed */
|
|
@@ -4,87 +4,6 @@ import { appReducer, createInitialState } from "./index";
|
|
|
4
4
|
import { removePane } from "../../../plugins/pane-manager";
|
|
5
5
|
|
|
6
6
|
describe("appReducer command bar state", () => {
|
|
7
|
-
test("opens the command bar with an explicit query", () => {
|
|
8
|
-
const state = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
9
|
-
const next = appReducer(state, { type: "SET_COMMAND_BAR", open: true, query: "Sync Broker Account" });
|
|
10
|
-
|
|
11
|
-
expect(next.commandBarOpen).toBe(true);
|
|
12
|
-
expect(next.commandBarQuery).toBe("Sync Broker Account");
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("clears the command bar query when closing", () => {
|
|
16
|
-
const state = appReducer(
|
|
17
|
-
createInitialState(createDefaultConfig("/tmp/gloomberb-test")),
|
|
18
|
-
{ type: "SET_COMMAND_BAR", open: true, query: "Disconnect Broker Account" },
|
|
19
|
-
);
|
|
20
|
-
const next = appReducer(state, { type: "SET_COMMAND_BAR", open: false });
|
|
21
|
-
|
|
22
|
-
expect(next.commandBarOpen).toBe(false);
|
|
23
|
-
expect(next.commandBarQuery).toBe("");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("stores and clears command bar launch requests", () => {
|
|
27
|
-
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
28
|
-
const opened = appReducer(initial, {
|
|
29
|
-
type: "SET_COMMAND_BAR",
|
|
30
|
-
open: true,
|
|
31
|
-
launch: { kind: "plugin-command", commandId: "set-alert" },
|
|
32
|
-
});
|
|
33
|
-
const relaunched = appReducer(opened, {
|
|
34
|
-
type: "SET_COMMAND_BAR",
|
|
35
|
-
open: true,
|
|
36
|
-
launch: { kind: "plugin-command", commandId: "set-alert" },
|
|
37
|
-
});
|
|
38
|
-
const closed = appReducer(relaunched, { type: "SET_COMMAND_BAR", open: false });
|
|
39
|
-
|
|
40
|
-
expect(opened.commandBarOpen).toBe(true);
|
|
41
|
-
expect(opened.commandBarLaunchRequest).toEqual({
|
|
42
|
-
kind: "plugin-command",
|
|
43
|
-
commandId: "set-alert",
|
|
44
|
-
sequence: 1,
|
|
45
|
-
});
|
|
46
|
-
expect(relaunched.commandBarLaunchRequest?.sequence).toBe(2);
|
|
47
|
-
expect(closed.commandBarLaunchRequest).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("stores ticker search command bar launch requests", () => {
|
|
51
|
-
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
52
|
-
const opened = appReducer(initial, {
|
|
53
|
-
type: "SET_COMMAND_BAR",
|
|
54
|
-
open: true,
|
|
55
|
-
launch: { kind: "ticker-search", query: "" },
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(opened.commandBarOpen).toBe(true);
|
|
59
|
-
expect(opened.commandBarLaunchRequest).toEqual({
|
|
60
|
-
kind: "ticker-search",
|
|
61
|
-
query: "",
|
|
62
|
-
sequence: 1,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("toggle opens blank and clears on close", () => {
|
|
67
|
-
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
68
|
-
const opened = appReducer(initial, { type: "TOGGLE_COMMAND_BAR" });
|
|
69
|
-
const closed = appReducer({ ...opened, commandBarQuery: "stale" }, { type: "TOGGLE_COMMAND_BAR" });
|
|
70
|
-
|
|
71
|
-
expect(opened.commandBarOpen).toBe(true);
|
|
72
|
-
expect(opened.commandBarQuery).toBe("");
|
|
73
|
-
expect(closed.commandBarOpen).toBe(false);
|
|
74
|
-
expect(closed.commandBarQuery).toBe("");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("updates the live command bar query without closing the overlay", () => {
|
|
78
|
-
const state = appReducer(
|
|
79
|
-
createInitialState(createDefaultConfig("/tmp/gloomberb-test")),
|
|
80
|
-
{ type: "SET_COMMAND_BAR", open: true, query: "PL " },
|
|
81
|
-
);
|
|
82
|
-
const next = appReducer(state, { type: "SET_COMMAND_BAR_QUERY", query: "PL notes" });
|
|
83
|
-
|
|
84
|
-
expect(next.commandBarOpen).toBe(true);
|
|
85
|
-
expect(next.commandBarQuery).toBe("PL notes");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
7
|
test("re-shows the gridlock tip and refreshes its sequence on every trigger", () => {
|
|
89
8
|
const initial = createInitialState(createDefaultConfig("/tmp/gloomberb-test"));
|
|
90
9
|
|
|
@@ -82,8 +82,12 @@ function createDesktopBridge(
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
describe("pane selectors", () => {
|
|
85
|
-
afterEach(() => {
|
|
86
|
-
testSetup
|
|
85
|
+
afterEach(async () => {
|
|
86
|
+
if (testSetup) {
|
|
87
|
+
await act(async () => {
|
|
88
|
+
testSetup!.renderer.destroy();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
87
91
|
testSetup = undefined;
|
|
88
92
|
capturedDispatch = null;
|
|
89
93
|
applyTheme("amber");
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildQuoteStreamSubscriptionKey,
|
|
4
|
+
normalizeQuoteStreamSubscriptionTarget,
|
|
5
|
+
} from "./quote-streaming";
|
|
3
6
|
|
|
4
7
|
describe("buildQuoteStreamSubscriptionKey", () => {
|
|
5
8
|
test("includes broker context so identical symbols can stream independently", () => {
|
|
@@ -28,4 +31,14 @@ describe("buildQuoteStreamSubscriptionKey", () => {
|
|
|
28
31
|
expect(workKey).not.toBe(personalKey);
|
|
29
32
|
});
|
|
30
33
|
|
|
34
|
+
test("keeps saved exchange aliases in stream targets", () => {
|
|
35
|
+
const target = normalizeQuoteStreamSubscriptionTarget({
|
|
36
|
+
symbol: "lpk",
|
|
37
|
+
exchange: "ibis",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(target?.symbol).toBe("LPK");
|
|
41
|
+
expect(target?.exchange).toBe("IBIS");
|
|
42
|
+
expect(buildQuoteStreamSubscriptionKey(target!)).toContain("LPK|IBIS");
|
|
43
|
+
});
|
|
31
44
|
});
|
|
@@ -2,18 +2,18 @@ import { useEffect } from "react";
|
|
|
2
2
|
import { useAppActive } from "../app/activity";
|
|
3
3
|
import type { QuoteSubscriptionTarget } from "../../types/data-provider";
|
|
4
4
|
import { debugLog } from "../../utils/debug-log";
|
|
5
|
-
import {
|
|
5
|
+
import { normalizeSymbol } from "../../utils/exchanges";
|
|
6
6
|
import { getSharedMarketDataCoordinator } from "../../market-data/coordinator";
|
|
7
7
|
|
|
8
8
|
const quoteStreamLog = debugLog.createLogger("quote-stream");
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
export function normalizeQuoteStreamSubscriptionTarget(target: QuoteSubscriptionTarget): QuoteSubscriptionTarget | null {
|
|
11
11
|
const symbol = normalizeSymbol(target.symbol);
|
|
12
12
|
if (!symbol) return null;
|
|
13
13
|
return {
|
|
14
14
|
...target,
|
|
15
15
|
symbol,
|
|
16
|
-
exchange:
|
|
16
|
+
exchange: target.exchange?.trim().toUpperCase() ?? "",
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -43,7 +43,7 @@ export function useQuoteStreaming(targets: QuoteSubscriptionTarget[]): void {
|
|
|
43
43
|
|
|
44
44
|
const normalizedEntries = new Map<string, QuoteSubscriptionTarget>();
|
|
45
45
|
for (const target of targets) {
|
|
46
|
-
const normalized =
|
|
46
|
+
const normalized = normalizeQuoteStreamSubscriptionTarget(target);
|
|
47
47
|
if (!normalized) continue;
|
|
48
48
|
const key = buildQuoteStreamSubscriptionKey(normalized);
|
|
49
49
|
normalizedEntries.set(key, normalized);
|
|
@@ -74,6 +74,7 @@ export function useQuoteStreaming(targets: QuoteSubscriptionTarget[]): void {
|
|
|
74
74
|
instrument: target.context?.instrument ?? null,
|
|
75
75
|
},
|
|
76
76
|
priority: {
|
|
77
|
+
route: target.route,
|
|
77
78
|
surface: target.surface,
|
|
78
79
|
visible: target.visible,
|
|
79
80
|
selected: target.selected,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import type { AppState } from "../core/state/app/state";
|
|
3
|
+
import type { TickerRepository } from "../data/ticker-repository";
|
|
4
|
+
import { CloudSyncController } from "./controller";
|
|
5
|
+
import type { SyncTransport } from "./types";
|
|
6
|
+
|
|
7
|
+
test("does not push local state when the initial pull fails", async () => {
|
|
8
|
+
let pushes = 0;
|
|
9
|
+
const transport: SyncTransport = {
|
|
10
|
+
id: "failing-pull",
|
|
11
|
+
isAvailable: () => true,
|
|
12
|
+
pullSnapshot: () => Promise.reject(new Error("pull failed")),
|
|
13
|
+
pushSnapshot: async () => {
|
|
14
|
+
pushes += 1;
|
|
15
|
+
return { revision: 1, updatedAt: new Date().toISOString() };
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
const controller = new CloudSyncController();
|
|
19
|
+
controller.setRuntime({
|
|
20
|
+
state: {} as AppState,
|
|
21
|
+
dispatch: () => {},
|
|
22
|
+
tickerRepository: {} as TickerRepository,
|
|
23
|
+
getContributors: () => [],
|
|
24
|
+
getTransport: () => ({ pluginId: "test", transport }),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await controller.requestSync({ force: true });
|
|
28
|
+
|
|
29
|
+
expect(pushes).toBe(0);
|
|
30
|
+
expect(controller.getStatus()).toMatchObject({ phase: "error", error: "pull failed" });
|
|
31
|
+
});
|