opencandle 0.12.0 → 0.13.0

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.
Files changed (75) hide show
  1. package/README.md +48 -180
  2. package/dist/cli-main.js +9 -8
  3. package/dist/config.d.ts +4 -0
  4. package/dist/config.js +1 -0
  5. package/dist/doctor/cli-command.js +7 -4
  6. package/dist/doctor/report.js +13 -0
  7. package/dist/gui/server/chat-event-adapter.js +145 -20
  8. package/dist/gui/server/history-snapshot-store.d.ts +8 -0
  9. package/dist/gui/server/history-snapshot-store.js +43 -0
  10. package/dist/gui/server/http-routes.d.ts +2 -0
  11. package/dist/gui/server/http-routes.js +32 -7
  12. package/dist/gui/server/live-chat-event-adapter.d.ts +2 -0
  13. package/dist/gui/server/live-chat-event-adapter.js +33 -15
  14. package/dist/gui/server/market-indices-api.d.ts +21 -0
  15. package/dist/gui/server/market-indices-api.js +26 -0
  16. package/dist/gui/server/market-indices-snapshot-store.d.ts +12 -0
  17. package/dist/gui/server/market-indices-snapshot-store.js +56 -0
  18. package/dist/gui/server/market-state-api.d.ts +112 -0
  19. package/dist/gui/server/market-state-api.js +246 -4
  20. package/dist/gui/server/model-setup.d.ts +2 -11
  21. package/dist/gui/server/model-setup.js +10 -6
  22. package/dist/gui/server/server.js +12 -9
  23. package/dist/gui/server/tool-metadata.d.ts +14 -14
  24. package/dist/gui/server/ws-hub.js +1 -1
  25. package/dist/infra/cache.d.ts +4 -0
  26. package/dist/infra/cache.js +4 -0
  27. package/dist/infra/lse-byte-budget.d.ts +10 -0
  28. package/dist/infra/lse-byte-budget.js +47 -0
  29. package/dist/infra/rate-limiter.js +1 -0
  30. package/dist/onboarding/providers.d.ts +16 -1
  31. package/dist/onboarding/providers.js +20 -0
  32. package/dist/onboarding/validate-model-key.js +33 -1
  33. package/dist/onboarding/validation.js +8 -0
  34. package/dist/pi/opencandle-extension.d.ts +2 -1
  35. package/dist/pi/opencandle-extension.js +2 -2
  36. package/dist/pi/session.d.ts +2 -3
  37. package/dist/pi/session.js +7 -5
  38. package/dist/pi/setup.d.ts +3 -3
  39. package/dist/pi/setup.js +51 -59
  40. package/dist/prompts/workflow-prompts.js +16 -14
  41. package/dist/providers/index.d.ts +1 -0
  42. package/dist/providers/index.js +1 -0
  43. package/dist/providers/lse.d.ts +35 -0
  44. package/dist/providers/lse.js +284 -0
  45. package/dist/providers/polymarket.js +22 -5
  46. package/dist/providers/wrap-provider.js +1 -0
  47. package/dist/providers/yahoo-finance.js +1 -0
  48. package/dist/routing/route-manifest.js +1 -0
  49. package/dist/routing/router.js +8 -1
  50. package/dist/runtime/evidence.d.ts +1 -0
  51. package/dist/runtime/evidence.js +1 -1
  52. package/dist/runtime/session-coordinator.d.ts +2 -2
  53. package/dist/runtime/session-coordinator.js +2 -2
  54. package/dist/tools/fundamentals/dcf.js +37 -8
  55. package/dist/tools/fundamentals/financials.js +88 -10
  56. package/dist/tools/index.d.ts +10 -5
  57. package/dist/tools/index.js +3 -0
  58. package/dist/tools/market/price-comparison.d.ts +30 -0
  59. package/dist/tools/market/price-comparison.js +202 -0
  60. package/dist/tools/market/stock-history.d.ts +18 -3
  61. package/dist/tools/market/stock-history.js +132 -17
  62. package/dist/tools/portfolio/correlation.d.ts +1 -1
  63. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  64. package/dist/types/market.d.ts +2 -0
  65. package/gui/web/dist/assets/CatalogOverlay-Cy8Fq0fn.js +1 -0
  66. package/gui/web/dist/assets/allocation-donut-DgBRIKCk.js +52 -0
  67. package/gui/web/dist/assets/index-DIlFyIOO.js +66 -0
  68. package/gui/web/dist/assets/index-DS4jESOB.css +2 -0
  69. package/gui/web/dist/assets/market-chart-DammaCjH.js +1 -0
  70. package/gui/web/dist/assets/utils-CnADgYgh.js +1 -0
  71. package/gui/web/dist/index.html +3 -2
  72. package/package.json +15 -17
  73. package/gui/web/dist/assets/CatalogOverlay-CAc7e3Pf.js +0 -1
  74. package/gui/web/dist/assets/index-BOEKd9wT.css +0 -2
  75. package/gui/web/dist/assets/index-C-H05cQ2.js +0 -65
@@ -0,0 +1,43 @@
1
+ export class HistorySnapshotStore {
2
+ maxAgeMs;
3
+ now;
4
+ entries = new Map();
5
+ constructor(maxAgeMs = 60_000, now = Date.now) {
6
+ this.maxAgeMs = maxAgeMs;
7
+ this.now = now;
8
+ }
9
+ get(key, build) {
10
+ const existing = this.entries.get(key);
11
+ if (existing?.hasValue && this.now() - existing.fetchedAtMs < this.maxAgeMs) {
12
+ return Promise.resolve(existing.value);
13
+ }
14
+ if (existing?.inFlight)
15
+ return existing.inFlight;
16
+ const entry = existing ??
17
+ { hasValue: false, fetchedAtMs: 0, inFlight: null };
18
+ let buildPromise;
19
+ try {
20
+ buildPromise = build();
21
+ }
22
+ catch (error) {
23
+ buildPromise = Promise.reject(error);
24
+ }
25
+ const inFlight = buildPromise
26
+ .then((value) => {
27
+ entry.value = value;
28
+ entry.hasValue = true;
29
+ entry.fetchedAtMs = this.now();
30
+ return value;
31
+ })
32
+ .finally(() => {
33
+ if (entry.inFlight === inFlight)
34
+ entry.inFlight = null;
35
+ });
36
+ entry.inFlight = inFlight;
37
+ this.entries.set(key, entry);
38
+ return inFlight;
39
+ }
40
+ clear() {
41
+ this.entries.clear();
42
+ }
43
+ }
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { type AgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
3
3
  import type { ToolInvokeController } from "./invoke-tool.js";
4
4
  import type { LocalSessionCoordinator, SessionActionEnvelope } from "./local-session-coordinator.js";
5
+ import type { MarketIndicesSnapshotStore } from "./market-indices-snapshot-store.js";
5
6
  import { type ModelSetupController } from "./model-setup.js";
6
7
  import type { QuoteSnapshotStore } from "./quote-snapshot-store.js";
7
8
  import { type SessionActionsController } from "./session-actions.js";
@@ -29,6 +30,7 @@ interface GuiHttpRouteOptions {
29
30
  sessionActionsController: SessionActionsController;
30
31
  toolInvokeController: ToolInvokeController;
31
32
  quoteSnapshotStore: QuoteSnapshotStore;
33
+ indicesSnapshotStore: MarketIndicesSnapshotStore;
32
34
  localSessionCoordinator?: LocalSessionCoordinator;
33
35
  }
34
36
  export interface ChatRunImageInput {
@@ -1,6 +1,6 @@
1
1
  import { createReadStream, existsSync } from "node:fs";
2
2
  import { extname, join, resolve } from "node:path";
3
- import { SessionManager } from "@earendil-works/pi-coding-agent";
3
+ import { ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
4
4
  import { buildDoctorReport } from "../../doctor/report.js";
5
5
  import { MarketStateService } from "../../market-state/service.js";
6
6
  import { formatLatestReportSummary, formatPortfolioSummary, formatWatchlistSummary, } from "../../market-state/summaries.js";
@@ -9,7 +9,7 @@ import { probeProviderStatus } from "../../onboarding/provider-status.js";
9
9
  import { clearPendingSessionAction, hasAcceptedSessionAction, hasPendingSessionAction, recordAcceptedSessionAction, recordPendingSessionAction, } from "../../pi/session-action-dedupe.js";
10
10
  import { sessionEntriesToChatEvents } from "./chat-event-adapter.js";
11
11
  import { createLiveChatEventAdapter } from "./live-chat-event-adapter.js";
12
- import { buildMarketStateSnapshot, getInstrumentQuoteSnapshot, getSavedMarketStateSymbols, searchInstrumentCandidates, } from "./market-state-api.js";
12
+ import { buildMarketStateSnapshot, getInstrumentHistorySnapshot, getInstrumentOverviewSnapshot, getInstrumentQuoteSnapshot, getSavedMarketStateSymbols, searchInstrumentCandidates, } from "./market-state-api.js";
13
13
  import { buildModelSetupState } from "./model-setup.js";
14
14
  import { isTrustedPrivateApiRequest, privateApiCookieHeader } from "./private-api-access.js";
15
15
  import { projectDashboard } from "./projector.js";
@@ -84,9 +84,10 @@ export function createHttpRequestHandler(options) {
84
84
  if (url.pathname === "/api/model-setup/refresh" && req.method === "POST") {
85
85
  if (!allowTrustedGuiRequest(req, res, "Model setup API", options))
86
86
  return;
87
- options.getSession().modelRegistry.refresh();
88
- options.wsHub.broadcastModelSetup();
89
- writeJson(res, await options.wsHub.buildBootstrapPayload());
87
+ await handleTrustedGuiMutation(req, res, options, async () => {
88
+ await options.getSession().modelRuntime.refresh();
89
+ options.wsHub.broadcastModelSetup();
90
+ });
90
91
  return;
91
92
  }
92
93
  if (url.pathname === "/api/model-setup/api-key" && req.method === "POST") {
@@ -128,6 +129,12 @@ export function createHttpRequestHandler(options) {
128
129
  writeJson(res, await options.quoteSnapshotStore.get());
129
130
  return;
130
131
  }
132
+ if (url.pathname === "/api/market-state/indices" && req.method === "GET") {
133
+ if (!allowTrustedGuiRequest(req, res, "Market-state API", options))
134
+ return;
135
+ writeJson(res, await options.indicesSnapshotStore.get());
136
+ return;
137
+ }
131
138
  if (url.pathname === "/api/doctor" && req.method === "GET") {
132
139
  if (!allowTrustedGuiRequest(req, res, "Diagnostics API", options))
133
140
  return;
@@ -143,7 +150,7 @@ export function createHttpRequestHandler(options) {
143
150
  reachable: true,
144
151
  healthEndpoint: `http://${options.host}:${options.port}/health`,
145
152
  },
146
- modelSetup: buildModelSetupState(session.modelRegistry, session.model),
153
+ modelSetup: buildModelSetupState(new ModelRegistry(session.modelRuntime), session.model),
147
154
  }));
148
155
  return;
149
156
  }
@@ -159,6 +166,23 @@ export function createHttpRequestHandler(options) {
159
166
  writeJson(res, await getInstrumentQuoteSnapshot(url.searchParams.get("symbol") ?? ""));
160
167
  return;
161
168
  }
169
+ if (url.pathname === "/api/instruments/overview" && req.method === "GET") {
170
+ if (!allowTrustedGuiRequest(req, res, "Market-state API", options))
171
+ return;
172
+ writeJson(res, await getInstrumentOverviewSnapshot(url.searchParams.get("symbol") ?? ""));
173
+ return;
174
+ }
175
+ if (url.pathname === "/api/instruments/history" && req.method === "GET") {
176
+ if (!allowTrustedGuiRequest(req, res, "Market-state API", options))
177
+ return;
178
+ const snapshot = await getInstrumentHistorySnapshot(url.searchParams.get("symbol") ?? "", url.searchParams.get("range") ?? "1D", url.searchParams.get("interval") ?? undefined);
179
+ if (snapshot.status === "invalid_request") {
180
+ writeJson(res, snapshot, 400);
181
+ return;
182
+ }
183
+ writeJson(res, snapshot);
184
+ return;
185
+ }
162
186
  if (url.pathname === "/api/diagnostics/twitter-cli" && req.method === "GET") {
163
187
  if (!allowTrustedGuiRequest(req, res, "Diagnostics API", options))
164
188
  return;
@@ -529,6 +553,7 @@ async function streamAcceptedSseChatRun({ res, options, activeRunSessionIds, tar
529
553
  startSeq: seq,
530
554
  emit: (event) => writeSse(res, event),
531
555
  originalPrompt: prompt,
556
+ dispatchedPrompt,
532
557
  originalAttachments: inputAttachmentLabels,
533
558
  });
534
559
  const observation = createPromptObservation();
@@ -563,7 +588,7 @@ async function streamAcceptedSseChatRun({ res, options, activeRunSessionIds, tar
563
588
  });
564
589
  try {
565
590
  recordPendingSessionAction(runSessionManager, actionId);
566
- const modelSetup = buildModelSetupState(runSession.modelRegistry, runSession.model);
591
+ const modelSetup = buildModelSetupState(new ModelRegistry(runSession.modelRuntime), runSession.model);
567
592
  if (!prompt.startsWith("/") && modelSetup.requirement !== "ready") {
568
593
  runSessionManager.appendMessage({ role: "user", content: prompt, timestamp: Date.now() });
569
594
  recordAcceptedAction();
@@ -10,6 +10,8 @@ export interface LiveChatEventAdapterOptions {
10
10
  * message with an expanded prompt; the live view renders this instead.
11
11
  */
12
12
  originalPrompt?: string;
13
+ /** Prompt sent to Pi before any workflow input transform. */
14
+ dispatchedPrompt?: string;
13
15
  originalAttachments?: MessageAttachmentChip[];
14
16
  }
15
17
  export interface LiveChatEventAdapter {
@@ -42,21 +42,39 @@ export function createLiveChatEventAdapter(options) {
42
42
  const message = event.message;
43
43
  if (message.role === "user") {
44
44
  const messageId = `${options.runId}-user-${++userCount}`;
45
- const text = userCount === 1 && options.originalPrompt
46
- ? options.originalPrompt
47
- : messageText(message.content);
48
- emit({ type: "message.created", runId: options.runId, messageId, role: "user" });
49
- emit({
50
- type: "message.completed",
51
- runId: options.runId,
52
- messageId,
53
- content: userMessageContent(message.content, text),
54
- ...(userCount === 1 &&
55
- options.originalAttachments &&
56
- options.originalAttachments.length > 0
57
- ? { attachments: options.originalAttachments }
58
- : {}),
59
- });
45
+ const promptText = messageText(message.content);
46
+ const text = userCount === 1 && options.originalPrompt ? options.originalPrompt : promptText;
47
+ const transformedFirstPrompt = userCount === 1 &&
48
+ options.dispatchedPrompt !== undefined &&
49
+ promptText.trim() !== options.dispatchedPrompt.trim();
50
+ const workflowStep = transformedFirstPrompt || userCount > 1;
51
+ if (userCount === 1 || !workflowStep) {
52
+ emit({ type: "message.created", runId: options.runId, messageId, role: "user" });
53
+ emit({
54
+ type: "message.completed",
55
+ runId: options.runId,
56
+ messageId,
57
+ content: userMessageContent(message.content, text),
58
+ ...(userCount === 1 &&
59
+ options.originalAttachments &&
60
+ options.originalAttachments.length > 0
61
+ ? { attachments: options.originalAttachments }
62
+ : {}),
63
+ });
64
+ }
65
+ if (workflowStep) {
66
+ emit({
67
+ type: "custom.message",
68
+ messageId: `${options.runId}-workflow-step-${userCount}`,
69
+ customType: "opencandle-workflow-step",
70
+ content: [{ type: "text", text: promptText }],
71
+ details: {
72
+ label: "Workflow step",
73
+ stage: "workflow",
74
+ step: userCount,
75
+ },
76
+ });
77
+ }
60
78
  return;
61
79
  }
62
80
  if (message.role === "assistant") {
@@ -0,0 +1,21 @@
1
+ import { type MarketSparklineSnapshot } from "./market-state-api.js";
2
+ export declare const MARKET_INDEX_SYMBOLS: readonly ["^GSPC", "^IXIC", "^DJI", "BTC-USD"];
3
+ export interface MarketIndexSnapshotEntry {
4
+ symbol: (typeof MARKET_INDEX_SYMBOLS)[number];
5
+ name?: string;
6
+ status: "ok" | "unavailable";
7
+ reason?: string;
8
+ price?: number;
9
+ change?: number;
10
+ changePercent?: number;
11
+ currency?: string | null;
12
+ marketState?: "PRE" | "REGULAR" | "POST" | "CLOSED";
13
+ dataAsOf?: string;
14
+ stale?: boolean;
15
+ sparkline?: MarketSparklineSnapshot;
16
+ }
17
+ export interface MarketIndicesSnapshot {
18
+ generatedAt: string;
19
+ indices: MarketIndexSnapshotEntry[];
20
+ }
21
+ export declare function buildMarketIndicesSnapshot(): Promise<MarketIndicesSnapshot>;
@@ -0,0 +1,26 @@
1
+ import { fetchQuoteSnapshot, fetchSparklineSnapshot, } from "./market-state-api.js";
2
+ export const MARKET_INDEX_SYMBOLS = ["^GSPC", "^IXIC", "^DJI", "BTC-USD"];
3
+ export async function buildMarketIndicesSnapshot() {
4
+ const indices = [];
5
+ for (const symbol of MARKET_INDEX_SYMBOLS) {
6
+ const quote = await fetchQuoteSnapshot(symbol);
7
+ if (quote.status === "unavailable") {
8
+ indices.push({ symbol, status: "unavailable", reason: quote.reason });
9
+ continue;
10
+ }
11
+ indices.push({
12
+ symbol,
13
+ name: quote.name,
14
+ status: "ok",
15
+ price: quote.price,
16
+ change: quote.change,
17
+ changePercent: quote.changePercent,
18
+ currency: quote.currency,
19
+ marketState: quote.marketState,
20
+ dataAsOf: quote.dataAsOf,
21
+ stale: quote.stale,
22
+ sparkline: await fetchSparklineSnapshot(symbol),
23
+ });
24
+ }
25
+ return { generatedAt: new Date().toISOString(), indices };
26
+ }
@@ -0,0 +1,12 @@
1
+ import type { MarketIndicesSnapshot } from "./market-indices-api.js";
2
+ export declare class MarketIndicesSnapshotStore {
3
+ private readonly build;
4
+ private readonly maxAgeMs;
5
+ private readonly now;
6
+ private snapshot;
7
+ private fetchedAtMs;
8
+ private inFlight;
9
+ constructor(build: () => Promise<MarketIndicesSnapshot>, maxAgeMs?: number, now?: () => number);
10
+ get(): Promise<MarketIndicesSnapshot>;
11
+ private refresh;
12
+ }
@@ -0,0 +1,56 @@
1
+ export class MarketIndicesSnapshotStore {
2
+ build;
3
+ maxAgeMs;
4
+ now;
5
+ snapshot = null;
6
+ fetchedAtMs = 0;
7
+ inFlight = null;
8
+ constructor(build, maxAgeMs = 60_000, now = Date.now) {
9
+ this.build = build;
10
+ this.maxAgeMs = maxAgeMs;
11
+ this.now = now;
12
+ }
13
+ async get() {
14
+ if (this.snapshot == null)
15
+ return this.refresh();
16
+ if (this.now() - this.fetchedAtMs >= this.maxAgeMs) {
17
+ this.refresh().catch(() => { });
18
+ return {
19
+ ...this.snapshot,
20
+ indices: this.snapshot.indices.map((entry) => ({ ...entry, stale: true })),
21
+ };
22
+ }
23
+ return this.snapshot;
24
+ }
25
+ refresh() {
26
+ if (this.inFlight)
27
+ return this.inFlight;
28
+ const inFlight = this.build()
29
+ .then((snapshot) => {
30
+ if (this.snapshot && snapshot.indices.some((entry) => entry.status === "unavailable")) {
31
+ const validRefreshCount = snapshot.indices.filter((entry) => entry.status === "ok").length;
32
+ if (validRefreshCount === 0)
33
+ return this.snapshot;
34
+ const priorBySymbol = new Map(this.snapshot.indices.map((entry) => [entry.symbol, entry]));
35
+ snapshot = {
36
+ ...snapshot,
37
+ indices: snapshot.indices.map((entry) => {
38
+ if (entry.status === "ok")
39
+ return entry;
40
+ const prior = priorBySymbol.get(entry.symbol);
41
+ return prior?.status === "ok" ? { ...prior, stale: true } : entry;
42
+ }),
43
+ };
44
+ }
45
+ this.snapshot = snapshot;
46
+ this.fetchedAtMs = this.now();
47
+ return snapshot;
48
+ })
49
+ .finally(() => {
50
+ if (this.inFlight === inFlight)
51
+ this.inFlight = null;
52
+ });
53
+ this.inFlight = inFlight;
54
+ return inFlight;
55
+ }
56
+ }
@@ -1,6 +1,62 @@
1
1
  import type Database from "better-sqlite3";
2
2
  import { searchYahooInstruments } from "../../market-state/resolve.js";
3
3
  import { MarketStateService } from "../../market-state/service.js";
4
+ import { HISTORY_INTERVALS, type HISTORY_RANGES } from "../../tools/market/stock-history.js";
5
+ type HistoryRange = (typeof HISTORY_RANGES)[number];
6
+ type HistoryInterval = (typeof HISTORY_INTERVALS)[number];
7
+ export interface ResolvedHistoryRange {
8
+ range: HistoryRange;
9
+ interval: HistoryInterval;
10
+ }
11
+ export interface InvalidHistoryRequest {
12
+ status: "invalid_request";
13
+ reason: string;
14
+ }
15
+ export interface InstrumentHistoryBar {
16
+ time: number;
17
+ open: number;
18
+ high: number;
19
+ low: number;
20
+ close: number;
21
+ volume: number;
22
+ }
23
+ export interface InstrumentHistorySnapshotOk {
24
+ status: "ok";
25
+ symbol: string;
26
+ range: string;
27
+ interval: HistoryInterval;
28
+ source: InstrumentHistorySource;
29
+ fetchedAt: string;
30
+ dataAsOf?: string;
31
+ stale: boolean;
32
+ prevClose: number | null;
33
+ bars: InstrumentHistoryBar[];
34
+ }
35
+ export interface InstrumentHistorySnapshotUnavailable {
36
+ status: "unavailable";
37
+ reason: string;
38
+ dataAsOf?: string;
39
+ stale?: boolean;
40
+ }
41
+ export type InstrumentHistorySnapshot = InstrumentHistorySnapshotOk | InstrumentHistorySnapshotUnavailable | InvalidHistoryRequest;
42
+ type InstrumentHistorySource = "Yahoo Finance" | "Alpha Vantage" | "London Strategic Edge";
43
+ export declare function resolveHistoryRange(rangeLabel: string, intervalOverride?: string): ResolvedHistoryRange | InvalidHistoryRequest;
44
+ export declare function getInstrumentHistorySnapshot(symbol: string, rangeLabel?: string, intervalOverride?: string): Promise<InstrumentHistorySnapshot>;
45
+ export declare function resetInstrumentHistoryMemoForTests(): void;
46
+ export type MarketSparklineSnapshot = {
47
+ status: "ok";
48
+ source: "Yahoo Finance";
49
+ points: number[];
50
+ fetchedAt: string;
51
+ dataAsOf?: string;
52
+ stale: false;
53
+ } | {
54
+ status: "unavailable";
55
+ source: "Yahoo Finance";
56
+ reason: string;
57
+ dataAsOf?: string;
58
+ stale?: boolean;
59
+ };
4
60
  export interface MarketStateSnapshot {
5
61
  instruments: Array<NonNullable<ReturnType<MarketStateService["getInstrument"]>>>;
6
62
  watchlists: ReturnType<MarketStateService["listWatchlists"]>;
@@ -42,6 +98,7 @@ export interface MarketStateQuoteSnapshot {
42
98
  extendedAsOf?: string;
43
99
  stale?: boolean;
44
100
  reason?: string;
101
+ sparkline: MarketSparklineSnapshot;
45
102
  }>;
46
103
  portfolioQuotes: Array<{
47
104
  lotId: number;
@@ -68,6 +125,7 @@ export interface MarketStateQuoteSnapshot {
68
125
  extendedAsOf?: string;
69
126
  stale?: boolean;
70
127
  reason?: string;
128
+ sparkline: MarketSparklineSnapshot;
71
129
  }>;
72
130
  portfolioSummary: {
73
131
  portfolioId: number;
@@ -104,6 +162,7 @@ export declare function createSavedSymbolsMemo(load: () => string[], options?: S
104
162
  export declare const getSavedMarketStateSymbols: () => string[];
105
163
  export declare function buildMarketStateSnapshot(db?: Database.Database): MarketStateSnapshot;
106
164
  export declare function buildMarketStateQuoteSnapshot(db?: Database.Database): Promise<MarketStateQuoteSnapshot>;
165
+ export declare function fetchSparklineSnapshot(symbol: string): Promise<MarketSparklineSnapshot>;
107
166
  export declare function searchInstrumentCandidates(query: string): Promise<{
108
167
  query: string;
109
168
  candidates: Awaited<ReturnType<typeof searchYahooInstruments>>;
@@ -121,6 +180,7 @@ export declare function getInstrumentQuoteSnapshot(symbol: string): Promise<{
121
180
  low: number;
122
181
  week52High: number;
123
182
  week52Low: number;
183
+ marketCap: number;
124
184
  fetchedAt: string;
125
185
  dataAsOf?: string;
126
186
  marketState?: "PRE" | "REGULAR" | "POST" | "CLOSED";
@@ -135,4 +195,56 @@ export declare function getInstrumentQuoteSnapshot(symbol: string): Promise<{
135
195
  status: "unavailable";
136
196
  reason: string;
137
197
  }>;
198
+ export type InstrumentOverviewSnapshot = {
199
+ symbol: string;
200
+ status: "ok";
201
+ name: string;
202
+ description: string;
203
+ exchange: string;
204
+ sector: string;
205
+ industry: string;
206
+ marketCap: number;
207
+ pe: number | null;
208
+ forwardPe: number | null;
209
+ eps: number | null;
210
+ dividendYield: number | null;
211
+ beta: number | null;
212
+ avgVolume: number;
213
+ profitMargin: number | null;
214
+ revenueGrowth: number | null;
215
+ week52High: number;
216
+ week52Low: number;
217
+ stale: boolean;
218
+ } | {
219
+ symbol: string;
220
+ status: "unavailable";
221
+ reason: string;
222
+ };
223
+ export declare function getInstrumentOverviewSnapshot(symbol: string): Promise<InstrumentOverviewSnapshot>;
224
+ export declare function resetInstrumentOverviewMemoForTests(): void;
225
+ export declare function fetchQuoteSnapshot(symbol: string): Promise<{
226
+ status: "ok";
227
+ name?: string;
228
+ price: number;
229
+ change: number;
230
+ changePercent: number;
231
+ volume: number;
232
+ high: number;
233
+ low: number;
234
+ week52High: number;
235
+ week52Low: number;
236
+ marketCap: number;
237
+ fetchedAt: string;
238
+ dataAsOf?: string;
239
+ marketState?: "PRE" | "REGULAR" | "POST" | "CLOSED";
240
+ extendedPrice?: number;
241
+ extendedChange?: number;
242
+ extendedChangePercent?: number;
243
+ extendedAsOf?: string;
244
+ stale: boolean;
245
+ currency: string | null;
246
+ } | {
247
+ status: "unavailable";
248
+ reason: string;
249
+ }>;
138
250
  export {};