opencandle 0.4.0 → 0.5.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.
- package/LICENSE +1 -1
- package/README.md +106 -14
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -3
- package/dist/config.js +61 -2
- package/dist/config.js.map +1 -1
- package/dist/infra/browser.d.ts +1 -3
- package/dist/infra/browser.js +1 -1
- package/dist/infra/browser.js.map +1 -1
- package/dist/infra/rate-limiter.d.ts +4 -0
- package/dist/infra/rate-limiter.js +5 -1
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +28 -11
- package/dist/memory/manager.js.map +1 -1
- package/dist/memory/storage.d.ts +3 -2
- package/dist/memory/storage.js.map +1 -1
- package/dist/memory/types.js +4 -0
- package/dist/memory/types.js.map +1 -1
- package/dist/pi/opencandle-extension.js +230 -36
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/setup.js +10 -0
- package/dist/pi/setup.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +18 -3
- package/dist/prompts/context-builder.js +102 -16
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/prompts/disclaimer.js +1 -1
- package/dist/prompts/disclaimer.js.map +1 -1
- package/dist/prompts/policy-cards.d.ts +13 -0
- package/dist/prompts/policy-cards.js +197 -0
- package/dist/prompts/policy-cards.js.map +1 -0
- package/dist/prompts/sections.js +3 -3
- package/dist/prompts/sections.js.map +1 -1
- package/dist/prompts/workflow-prompts.js +170 -18
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/alpha-vantage.js +23 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/sec-edgar.d.ts +8 -1
- package/dist/providers/sec-edgar.js +172 -5
- package/dist/providers/sec-edgar.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +2 -0
- package/dist/providers/yahoo-finance.js +134 -3
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.d.ts +3 -0
- package/dist/routing/classify-intent.js +82 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/defaults.js +3 -3
- package/dist/routing/defaults.js.map +1 -1
- package/dist/routing/entity-extractor.d.ts +1 -0
- package/dist/routing/entity-extractor.js +158 -12
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/index.d.ts +7 -1
- package/dist/routing/index.js +4 -0
- package/dist/routing/index.js.map +1 -1
- package/dist/routing/legacy-rule-router.d.ts +9 -0
- package/dist/routing/legacy-rule-router.js +12 -0
- package/dist/routing/legacy-rule-router.js.map +1 -0
- package/dist/routing/planning.d.ts +54 -0
- package/dist/routing/planning.js +531 -0
- package/dist/routing/planning.js.map +1 -0
- package/dist/routing/route-manifest.d.ts +35 -0
- package/dist/routing/route-manifest.js +221 -0
- package/dist/routing/route-manifest.js.map +1 -0
- package/dist/routing/router-prompt.js +45 -42
- package/dist/routing/router-prompt.js.map +1 -1
- package/dist/routing/router-types.d.ts +9 -0
- package/dist/routing/router.d.ts +1 -0
- package/dist/routing/router.js +456 -12
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.js +46 -6
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/routing/turn-context.d.ts +44 -0
- package/dist/routing/turn-context.js +45 -0
- package/dist/routing/turn-context.js.map +1 -0
- package/dist/routing/types.d.ts +13 -1
- package/dist/runtime/answer-contracts.d.ts +82 -0
- package/dist/runtime/answer-contracts.js +414 -0
- package/dist/runtime/answer-contracts.js.map +1 -0
- package/dist/runtime/artifact-contracts.d.ts +14 -0
- package/dist/runtime/artifact-contracts.js +57 -0
- package/dist/runtime/artifact-contracts.js.map +1 -0
- package/dist/runtime/planning-evidence.d.ts +99 -0
- package/dist/runtime/planning-evidence.js +445 -0
- package/dist/runtime/planning-evidence.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +20 -2
- package/dist/runtime/session-coordinator.js +47 -14
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/system-prompt.js +4 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.js +1 -1
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +1 -1
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +1 -1
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.js +1 -1
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.js +1 -1
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.d.ts +1 -0
- package/dist/tools/fundamentals/sec-filings.js +19 -2
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/macro/fear-greed.js +1 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.js +29 -5
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +18 -2
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +1 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/search-ticker.js +1 -1
- package/dist/tools/market/search-ticker.js.map +1 -1
- package/dist/tools/market/stock-history.js +1 -1
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +1 -1
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/greeks.js +0 -1
- package/dist/tools/options/greeks.js.map +1 -1
- package/dist/tools/options/option-chain.js +9 -4
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.js +1 -1
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
- package/dist/tools/portfolio/holdings-overlap.js +105 -0
- package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
- package/dist/tools/portfolio/predictions.js +1 -1
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.js +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.js +1 -1
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.js +12 -4
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js +1 -1
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.js +57 -2
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js +1 -1
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.js +32 -3
- package/dist/tools/sentiment/web-search.js.map +1 -1
- package/dist/tools/sentiment/web-sentiment.js +1 -1
- package/dist/tools/sentiment/web-sentiment.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +2 -2
- package/dist/tools/technical/backtest.js +41 -27
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +1 -3
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/options.d.ts +10 -0
- package/dist/types/portfolio.d.ts +27 -0
- package/dist/workflows/compare-assets.js +38 -2
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/options-screener.js +88 -7
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.js +7 -3
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +82 -0
- package/gui/server/gui-session-manager.ts +5 -0
- package/gui/server/projector.ts +47 -5
- package/gui/server/prompt-observation.ts +61 -0
- package/gui/server/server.ts +119 -8
- package/gui/server/session-entry-wait.ts +81 -0
- package/gui/web/dist/assets/{CatalogOverlay-D1ImSJTe.js → CatalogOverlay-Bmp6Knu7.js} +1 -1
- package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
- package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +18 -12
- package/src/cli.ts +2 -1
- package/src/config.ts +89 -5
- package/src/infra/browser.ts +1 -1
- package/src/infra/rate-limiter.ts +10 -1
- package/src/memory/manager.ts +43 -10
- package/src/memory/storage.ts +3 -2
- package/src/memory/types.ts +4 -0
- package/src/pi/opencandle-extension.ts +273 -42
- package/src/pi/setup.ts +10 -0
- package/src/prompts/context-builder.ts +128 -17
- package/src/prompts/disclaimer.ts +1 -1
- package/src/prompts/policy-cards.ts +220 -0
- package/src/prompts/sections.ts +3 -3
- package/src/prompts/workflow-prompts.ts +172 -18
- package/src/providers/alpha-vantage.ts +24 -1
- package/src/providers/sec-edgar.ts +220 -4
- package/src/providers/web-search.ts +1 -1
- package/src/providers/yahoo-finance.ts +171 -4
- package/src/routing/classify-intent.ts +94 -3
- package/src/routing/defaults.ts +3 -3
- package/src/routing/entity-extractor.ts +164 -13
- package/src/routing/index.ts +44 -0
- package/src/routing/legacy-rule-router.ts +13 -0
- package/src/routing/planning.ts +732 -0
- package/src/routing/route-manifest.ts +287 -0
- package/src/routing/router-prompt.ts +50 -46
- package/src/routing/router-types.ts +21 -0
- package/src/routing/router.ts +511 -12
- package/src/routing/slot-resolver.ts +44 -6
- package/src/routing/turn-context.ts +111 -0
- package/src/routing/types.ts +13 -1
- package/src/runtime/answer-contracts.ts +633 -0
- package/src/runtime/artifact-contracts.ts +76 -0
- package/src/runtime/planning-evidence.ts +591 -0
- package/src/runtime/session-coordinator.ts +78 -12
- package/src/system-prompt.ts +4 -1
- package/src/tools/fundamentals/company-overview.ts +1 -1
- package/src/tools/fundamentals/comps.ts +1 -1
- package/src/tools/fundamentals/dcf.ts +1 -1
- package/src/tools/fundamentals/earnings.ts +1 -1
- package/src/tools/fundamentals/financials.ts +1 -1
- package/src/tools/fundamentals/sec-filings.ts +25 -2
- package/src/tools/index.ts +3 -0
- package/src/tools/macro/fear-greed.ts +1 -1
- package/src/tools/macro/fred-data.ts +31 -5
- package/src/tools/market/crypto-history.ts +18 -2
- package/src/tools/market/crypto-price.ts +1 -1
- package/src/tools/market/search-ticker.ts +1 -1
- package/src/tools/market/stock-history.ts +1 -1
- package/src/tools/market/stock-quote.ts +1 -1
- package/src/tools/options/greeks.ts +0 -1
- package/src/tools/options/option-chain.ts +9 -4
- package/src/tools/portfolio/correlation.ts +1 -1
- package/src/tools/portfolio/holdings-overlap.ts +123 -0
- package/src/tools/portfolio/predictions.ts +1 -1
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/portfolio/tracker.ts +1 -1
- package/src/tools/portfolio/watchlist.ts +10 -4
- package/src/tools/sentiment/reddit-sentiment.ts +1 -1
- package/src/tools/sentiment/sentiment-summary.ts +62 -2
- package/src/tools/sentiment/twitter-sentiment.ts +1 -1
- package/src/tools/sentiment/web-search.ts +36 -3
- package/src/tools/sentiment/web-sentiment.ts +1 -1
- package/src/tools/technical/backtest.ts +50 -29
- package/src/tools/technical/indicators.ts +1 -3
- package/src/types/options.ts +17 -0
- package/src/types/portfolio.ts +32 -0
- package/src/workflows/compare-assets.ts +38 -2
- package/src/workflows/options-screener.ts +85 -7
- package/src/workflows/portfolio-builder.ts +7 -3
- package/dist/runtime/index.d.ts +0 -16
- package/dist/runtime/index.js +0 -10
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/provider-ids.d.ts +0 -14
- package/dist/runtime/provider-ids.js +0 -14
- package/dist/runtime/provider-ids.js.map +0 -1
- package/gui/web/dist/assets/index-DBrWq43L.css +0 -1
- package/gui/web/dist/assets/index-RflHaj0y.js +0 -67
- package/src/runtime/index.ts +0 -55
- package/src/runtime/provider-ids.ts +0 -15
|
@@ -19,6 +19,10 @@ import { PromptContextBuilder, type FallbackContext } from "../prompts/context-b
|
|
|
19
19
|
import { getAddonToolDescriptions } from "../tool-kit.js";
|
|
20
20
|
import type { WorkflowDefinition } from "./prompt-step.js";
|
|
21
21
|
import { toStepDefinitions, promptStepOutput } from "./prompt-step.js";
|
|
22
|
+
import type { ResolvedTurnContext } from "../routing/turn-context.js";
|
|
23
|
+
import type { RouterRouteKind } from "../routing/router-types.js";
|
|
24
|
+
import type { MemoryEntry } from "../memory/types.js";
|
|
25
|
+
import type { FilteredMemoryEntry } from "../memory/manager.js";
|
|
22
26
|
import type Database from "better-sqlite3";
|
|
23
27
|
|
|
24
28
|
const PROMPT_SETTLE_POLL_MS = 25;
|
|
@@ -57,6 +61,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
57
61
|
async function waitForPromptSettlement(
|
|
58
62
|
ctx: QueueContext,
|
|
59
63
|
isCurrentRun: () => boolean,
|
|
64
|
+
options: { requireActivity?: boolean } = {},
|
|
60
65
|
): Promise<boolean> {
|
|
61
66
|
let sawBusyOrPending = !isReadyForNextPrompt(ctx);
|
|
62
67
|
const startedAt = Date.now();
|
|
@@ -71,7 +76,12 @@ async function waitForPromptSettlement(
|
|
|
71
76
|
return true;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
!options.requireActivity &&
|
|
81
|
+
!sawBusyOrPending &&
|
|
82
|
+
ready &&
|
|
83
|
+
Date.now() - startedAt >= IMMEDIATE_IDLE_GRACE_MS
|
|
84
|
+
) {
|
|
75
85
|
return true;
|
|
76
86
|
}
|
|
77
87
|
|
|
@@ -150,7 +160,7 @@ export class SessionCoordinator {
|
|
|
150
160
|
entities: object,
|
|
151
161
|
resolved: object,
|
|
152
162
|
defaultsUsed: unknown[],
|
|
153
|
-
turnType
|
|
163
|
+
turnType = "workflow",
|
|
154
164
|
): void {
|
|
155
165
|
this.storage?.insertWorkflowRun({
|
|
156
166
|
sessionId: this.sessionId,
|
|
@@ -263,11 +273,24 @@ export class SessionCoordinator {
|
|
|
263
273
|
return { profileSnapshot, recentWorkflowRuns: runs, priorTurns };
|
|
264
274
|
}
|
|
265
275
|
|
|
276
|
+
retrieveMemoryForRoute(
|
|
277
|
+
routeKind: RouterRouteKind,
|
|
278
|
+
workflowType?: string,
|
|
279
|
+
overriddenSlots?: string[],
|
|
280
|
+
): { entries: MemoryEntry[]; filtered: FilteredMemoryEntry[] } {
|
|
281
|
+
if (!this.memoryManager) return { entries: [], filtered: [] };
|
|
282
|
+
return this.memoryManager.retrieveDetailed(
|
|
283
|
+
workflowType ?? routeKind,
|
|
284
|
+
overriddenSlots,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
266
288
|
/** Build system prompt using composable sections. */
|
|
267
289
|
buildSystemPrompt(
|
|
268
290
|
basePrompt: string,
|
|
269
291
|
workflowType?: string,
|
|
270
292
|
fallbackContext?: FallbackContext,
|
|
293
|
+
resolvedTurnContext?: ResolvedTurnContext,
|
|
271
294
|
): string {
|
|
272
295
|
const builder = new PromptContextBuilder();
|
|
273
296
|
|
|
@@ -277,7 +300,9 @@ export class SessionCoordinator {
|
|
|
277
300
|
: undefined;
|
|
278
301
|
|
|
279
302
|
const memoryContext = this.memoryManager
|
|
280
|
-
? this.memoryManager.buildContext(
|
|
303
|
+
? this.memoryManager.buildContext(
|
|
304
|
+
resolvedTurnContext?.workflow ?? workflowType ?? resolvedTurnContext?.routeKind ?? "unclassified",
|
|
305
|
+
)
|
|
281
306
|
: undefined;
|
|
282
307
|
|
|
283
308
|
builder.populateFromOptions({
|
|
@@ -285,6 +310,7 @@ export class SessionCoordinator {
|
|
|
285
310
|
memoryContext: memoryContext || undefined,
|
|
286
311
|
addonToolDescriptions: addonDescriptions,
|
|
287
312
|
fallbackContext,
|
|
313
|
+
resolvedTurnContext,
|
|
288
314
|
});
|
|
289
315
|
|
|
290
316
|
const toolDefaults = formatToolDefaultsForPrompt();
|
|
@@ -301,17 +327,28 @@ export class SessionCoordinator {
|
|
|
301
327
|
* subsequent turns do not inherit stale fallback directives.
|
|
302
328
|
*/
|
|
303
329
|
private pendingFallbackContext: FallbackContext | null = null;
|
|
330
|
+
private pendingResolvedTurnContext: ResolvedTurnContext | null = null;
|
|
304
331
|
|
|
305
332
|
setPendingFallbackContext(ctx: FallbackContext | null): void {
|
|
306
333
|
this.pendingFallbackContext = ctx;
|
|
307
334
|
}
|
|
308
335
|
|
|
336
|
+
setPendingResolvedTurnContext(ctx: ResolvedTurnContext | null): void {
|
|
337
|
+
this.pendingResolvedTurnContext = ctx;
|
|
338
|
+
}
|
|
339
|
+
|
|
309
340
|
consumePendingFallbackContext(): FallbackContext | null {
|
|
310
341
|
const ctx = this.pendingFallbackContext;
|
|
311
342
|
this.pendingFallbackContext = null;
|
|
312
343
|
return ctx;
|
|
313
344
|
}
|
|
314
345
|
|
|
346
|
+
consumePendingResolvedTurnContext(): ResolvedTurnContext | null {
|
|
347
|
+
const ctx = this.pendingResolvedTurnContext;
|
|
348
|
+
this.pendingResolvedTurnContext = null;
|
|
349
|
+
return ctx;
|
|
350
|
+
}
|
|
351
|
+
|
|
315
352
|
/**
|
|
316
353
|
* Execute a workflow definition through the WorkflowRunner,
|
|
317
354
|
* sending prompts via Pi with settlement-based sequencing.
|
|
@@ -320,21 +357,46 @@ export class SessionCoordinator {
|
|
|
320
357
|
pi: ExtensionAPI,
|
|
321
358
|
definition: WorkflowDefinition,
|
|
322
359
|
ctx: QueueContext,
|
|
360
|
+
): void {
|
|
361
|
+
this.startWorkflowRun(pi, definition, ctx, "send");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Start workflow tracking for an input handler that will return a Pi
|
|
366
|
+
* transform result. The current prompt becomes the first workflow prompt;
|
|
367
|
+
* only later steps are sent through Pi.
|
|
368
|
+
*/
|
|
369
|
+
transformWorkflowInput(
|
|
370
|
+
pi: ExtensionAPI,
|
|
371
|
+
definition: WorkflowDefinition,
|
|
372
|
+
ctx: QueueContext,
|
|
373
|
+
): string | undefined {
|
|
374
|
+
if (definition.steps.length === 0) return undefined;
|
|
375
|
+
this.startWorkflowRun(pi, definition, ctx, "transform");
|
|
376
|
+
return definition.steps[0].prompt;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private startWorkflowRun(
|
|
380
|
+
pi: ExtensionAPI,
|
|
381
|
+
definition: WorkflowDefinition,
|
|
382
|
+
ctx: QueueContext,
|
|
383
|
+
firstPromptMode: "send" | "transform",
|
|
323
384
|
): void {
|
|
324
385
|
if (definition.steps.length === 0) return;
|
|
325
386
|
|
|
326
387
|
const runner = this.runner;
|
|
327
388
|
const runRef = { active: true };
|
|
328
389
|
|
|
329
|
-
|
|
330
|
-
const [firstStep, ...restSteps] = definition.steps;
|
|
331
|
-
const startedBusy = !isReadyForNextPrompt(ctx);
|
|
390
|
+
const [firstStep] = definition.steps;
|
|
332
391
|
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
392
|
+
if (firstPromptMode === "send") {
|
|
393
|
+
const startedBusy = !isReadyForNextPrompt(ctx);
|
|
394
|
+
if (startedBusy) {
|
|
395
|
+
pi.sendUserMessage(firstStep.prompt, { deliverAs: "followUp" });
|
|
396
|
+
ctx.ui?.notify?.("Analysis queued as follow-up.", "info");
|
|
397
|
+
} else {
|
|
398
|
+
pi.sendUserMessage(firstStep.prompt);
|
|
399
|
+
}
|
|
338
400
|
}
|
|
339
401
|
|
|
340
402
|
// Make the run's ProviderTracker accessible to tools during execution
|
|
@@ -352,7 +414,11 @@ export class SessionCoordinator {
|
|
|
352
414
|
pi.sendUserMessage(definition.steps[stepIndex].prompt);
|
|
353
415
|
} else {
|
|
354
416
|
// For the first step, just wait for it to settle
|
|
355
|
-
const settled = await waitForPromptSettlement(
|
|
417
|
+
const settled = await waitForPromptSettlement(
|
|
418
|
+
ctx,
|
|
419
|
+
() => runRef.active,
|
|
420
|
+
{ requireActivity: firstPromptMode === "transform" },
|
|
421
|
+
);
|
|
356
422
|
if (!settled || !runRef.active) {
|
|
357
423
|
throw new Error("run_cancelled");
|
|
358
424
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -10,7 +10,7 @@ ${memoryContext}`
|
|
|
10
10
|
return `You are OpenCandle, a research analyst for investors and traders.
|
|
11
11
|
|
|
12
12
|
## Your Role
|
|
13
|
-
You are an analyst, not a fiduciary advisor. When asked for entry levels, price targets, stops, position sizes, or allocations, you COMMIT to specific numbers backed by the data you fetched. Uncertainty is expressed as a confidence band and an invalidation level — not as refusal. Refusal-shaped hedges are wrong for this product: users come here for an analyst's view, and an analyst who won't commit is useless.
|
|
13
|
+
You are an analyst, not a fiduciary advisor. When asked for entry levels, price targets, stops, position sizes, or allocations, you COMMIT to specific numbers backed by the data you fetched. Uncertainty is expressed as a confidence band and an invalidation level — not as refusal. Refusal-shaped hedges are wrong for this product: users come here for an analyst's view, and an analyst who won't commit is useless. For conceptual education questions, teach the concept directly, do not name tool functions, and do not append analyst-view, confidence-band, or invalidation boilerplate. For valuation-metric education, start with "Bottom line", use a heading exactly named "Practical workflow" with numbered question-driven application steps, explain where the metric misleads, include a compact cross-check table with why/when each metric helps, include relevant trailing, forward, normalized, or cyclically adjusted variants when useful, and end with a heading exactly named "Quick checklist".
|
|
14
14
|
|
|
15
15
|
## Available Tools
|
|
16
16
|
- **Market Data**: get_stock_quote, get_stock_history, get_crypto_price, get_crypto_history — real-time and historical price data
|
|
@@ -45,11 +45,14 @@ Calibrate explanation depth from conversational signals: the user's vocabulary i
|
|
|
45
45
|
|
|
46
46
|
## Guidelines
|
|
47
47
|
- Always fetch data with tools before stating prices, ratios, or metrics. Never guess financial numbers. Every substantive response should be backed by at least one tool call — if you find yourself writing a response with zero tool calls, stop and think about what data would make it better.
|
|
48
|
+
- For current single-stock recommendations, state the quote or tool-output date in the final answer. If tool output says the market is closed, the quote is delayed, or this is the last available quote, carry that freshness note into the final answer. If DCF or another valuation model is unavailable or not meaningful, do not let that tool failure become the whole valuation view; use supported fallback valuation lenses such as relative multiples, growth-adjusted multiples, cash-flow quality, balance-sheet risk, and historical range context. Do not make missing fundamentals the main thesis when quote, earnings, technicals, sentiment, or news are available; use those data points plus structural business risks to give a clear call, position sizing, and entry strategy.
|
|
49
|
+
- For ticker-specific sentiment prompts, call get_stock_quote before the final answer and state whether sentiment diverges from price action. For sentiment-only prompts, include source-coverage risk, low sample counts, missing sources, and how those gaps downgrade confidence.
|
|
48
50
|
- For options analysis, use get_option_chain to see the full chain with Greeks. Pay attention to put/call ratio, unusual volume, and IV levels.
|
|
49
51
|
- Present numerical data in tables when comparing multiple securities.
|
|
50
52
|
- Include data timestamps so users know how fresh the information is.
|
|
51
53
|
- Be concise and actionable. Lead with the commitment, then the reasoning chain.
|
|
52
54
|
- Flag downside and risks loudly. Commitment is not optimism — a bearish analyst view with conviction is valid output. Risk is expressed through the invalidation level and confidence band, never through refusal.
|
|
55
|
+
- Conceptual education prompts are not committal responses. Do not append "Analyst View", "Commitment", "Reasoning Chain", "Confidence Band", or "Invalidation Level" sections when the user asked for an explanation, definition, or learning framework rather than a trade, allocation, or recommendation.
|
|
53
56
|
- Reuse prior tool outputs when they already answer the question. Do not re-fetch the same symbol and parameters unless you need a missing field or fresher timestamp.
|
|
54
57
|
- If one provider is missing data, continue with the remaining tools and clearly label unavailable metrics instead of aborting the entire response.
|
|
55
58
|
|
|
@@ -16,7 +16,7 @@ export const companyOverviewTool: AgentTool<typeof params, CompanyOverview | { c
|
|
|
16
16
|
description:
|
|
17
17
|
"Get company fundamentals: P/E ratio, EPS, market cap, sector, dividend yield, profit margin, beta, and description. Requires Alpha Vantage.",
|
|
18
18
|
parameters: params,
|
|
19
|
-
async execute(
|
|
19
|
+
async execute(_toolCallId, args) {
|
|
20
20
|
return withCredentialCheck("alpha_vantage", async () => {
|
|
21
21
|
const apiKey = getConfig().alphaVantageApiKey!;
|
|
22
22
|
const result = await wrapProvider("alphavantage", () => getOverview(args.symbol.toUpperCase(), apiKey));
|
|
@@ -96,7 +96,7 @@ export const compsTool: AgentTool<typeof params> = {
|
|
|
96
96
|
description:
|
|
97
97
|
"Compare 2-6 companies side-by-side on key valuation and financial metrics: P/E, Forward P/E, EPS, Profit Margin, Revenue Growth, Dividend Yield, Beta. Identifies the cheapest and most expensive on each metric.",
|
|
98
98
|
parameters: params,
|
|
99
|
-
async execute(
|
|
99
|
+
async execute(_toolCallId, args) {
|
|
100
100
|
return withCredentialCheck("alpha_vantage", async () => {
|
|
101
101
|
const config = getConfig();
|
|
102
102
|
const symbols = args.symbols.map((s) => s.toUpperCase());
|
|
@@ -148,7 +148,7 @@ export const dcfTool: AgentTool<typeof params> = {
|
|
|
148
148
|
description:
|
|
149
149
|
"Compute a Discounted Cash Flow (DCF) intrinsic value estimate for a stock. Uses free cash flow, growth projections, and a discount rate to estimate what the stock is worth. Returns intrinsic value per share, margin of safety vs current price, and a sensitivity table.",
|
|
150
150
|
parameters: params,
|
|
151
|
-
async execute(
|
|
151
|
+
async execute(_toolCallId, args) {
|
|
152
152
|
return withCredentialCheck("alpha_vantage", async () => {
|
|
153
153
|
const symbol = args.symbol.toUpperCase();
|
|
154
154
|
const config = getConfig();
|
|
@@ -16,7 +16,7 @@ export const earningsTool: AgentTool<typeof params, EarningsData | { credentialR
|
|
|
16
16
|
description:
|
|
17
17
|
"Get quarterly earnings: reported EPS, estimated EPS, and surprise percentage for the last 8 quarters. Requires Alpha Vantage.",
|
|
18
18
|
parameters: params,
|
|
19
|
-
async execute(
|
|
19
|
+
async execute(_toolCallId, args) {
|
|
20
20
|
return withCredentialCheck("alpha_vantage", async () => {
|
|
21
21
|
const apiKey = getConfig().alphaVantageApiKey!;
|
|
22
22
|
const result = await wrapProvider("alphavantage", () => getEarnings(args.symbol.toUpperCase(), apiKey));
|
|
@@ -16,7 +16,7 @@ export const financialsTool: AgentTool<typeof params, FinancialStatement[] | { c
|
|
|
16
16
|
description:
|
|
17
17
|
"Get annual income statement data: revenue, gross profit, operating income, net income, and EPS. Requires Alpha Vantage.",
|
|
18
18
|
parameters: params,
|
|
19
|
-
async execute(
|
|
19
|
+
async execute(_toolCallId, args) {
|
|
20
20
|
return withCredentialCheck("alpha_vantage", async () => {
|
|
21
21
|
const apiKey = getConfig().alphaVantageApiKey!;
|
|
22
22
|
const result = await wrapProvider("alphavantage", () =>
|
|
@@ -13,6 +13,9 @@ const params = Type.Object({
|
|
|
13
13
|
limit: Type.Optional(
|
|
14
14
|
Type.Number({ description: "Maximum number of filings to return (default: 10)" }),
|
|
15
15
|
),
|
|
16
|
+
include_snippets: Type.Optional(
|
|
17
|
+
Type.Boolean({ description: "Include short filing-text evidence snippets for risk/MD&A/litigation themes. Default: true." }),
|
|
18
|
+
),
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
export const secFilingsTool: AgentTool<typeof params> = {
|
|
@@ -21,12 +24,15 @@ export const secFilingsTool: AgentTool<typeof params> = {
|
|
|
21
24
|
description:
|
|
22
25
|
"Search SEC EDGAR for company filings (10-K annual reports, 10-Q quarterly reports, 8-K material events). Returns filing dates, types, and direct links to the documents. Free API, no key required.",
|
|
23
26
|
parameters: params,
|
|
24
|
-
async execute(
|
|
27
|
+
async execute(_toolCallId, args) {
|
|
25
28
|
const symbol = args.symbol.toUpperCase();
|
|
26
29
|
const formTypes = args.form_types ?? ["10-K", "10-Q", "8-K"];
|
|
27
30
|
const limit = args.limit ?? 10;
|
|
31
|
+
const includeSnippets = args.include_snippets ?? true;
|
|
28
32
|
|
|
29
|
-
const result = await wrapProvider("sec-edgar", () =>
|
|
33
|
+
const result = await wrapProvider("sec-edgar", () =>
|
|
34
|
+
searchFilings(symbol, formTypes, limit, { includeSnippets, snippetLimitPerFiling: 2 })
|
|
35
|
+
);
|
|
30
36
|
if (result.status === "unavailable") {
|
|
31
37
|
return {
|
|
32
38
|
content: [{ type: "text", text: `⚠ SEC filings unavailable for ${symbol} (${result.reason}).` }],
|
|
@@ -51,6 +57,23 @@ export const secFilingsTool: AgentTool<typeof params> = {
|
|
|
51
57
|
``,
|
|
52
58
|
`Links:`,
|
|
53
59
|
...filings.map((f) => ` ${f.formType} (${f.filedDate}): ${f.url}`),
|
|
60
|
+
``,
|
|
61
|
+
`Primary documents:`,
|
|
62
|
+
...filings.flatMap((f) => [
|
|
63
|
+
` ${f.formType} (${f.filedDate}): ${f.primaryDocumentUrl ?? "N/A"}`,
|
|
64
|
+
...(f.items?.length ? [` 8-K items: ${f.items.join(", ")}`] : []),
|
|
65
|
+
]),
|
|
66
|
+
...(includeSnippets
|
|
67
|
+
? [
|
|
68
|
+
``,
|
|
69
|
+
`Evidence snippets (short excerpts; review source filing for full context):`,
|
|
70
|
+
...filings.flatMap((f) =>
|
|
71
|
+
(f.evidenceSnippets?.length
|
|
72
|
+
? f.evidenceSnippets.map((snippet) => ` ${f.formType} ${f.filedDate}: ${snippet}`)
|
|
73
|
+
: [` ${f.formType} ${f.filedDate}: No keyword snippet found in fetched primary document.`])
|
|
74
|
+
),
|
|
75
|
+
]
|
|
76
|
+
: []),
|
|
54
77
|
];
|
|
55
78
|
|
|
56
79
|
return {
|
package/src/tools/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { portfolioTrackerTool } from "./portfolio/tracker.js";
|
|
|
16
16
|
import { riskAnalysisTool } from "./portfolio/risk-analysis.js";
|
|
17
17
|
import { watchlistTool } from "./portfolio/watchlist.js";
|
|
18
18
|
import { correlationTool } from "./portfolio/correlation.js";
|
|
19
|
+
import { holdingsOverlapTool } from "./portfolio/holdings-overlap.js";
|
|
19
20
|
import { optionChainTool } from "./options/option-chain.js";
|
|
20
21
|
import { dcfTool } from "./fundamentals/dcf.js";
|
|
21
22
|
import { compsTool } from "./fundamentals/comps.js";
|
|
@@ -52,6 +53,7 @@ export { portfolioTrackerTool } from "./portfolio/tracker.js";
|
|
|
52
53
|
export { riskAnalysisTool } from "./portfolio/risk-analysis.js";
|
|
53
54
|
export { watchlistTool } from "./portfolio/watchlist.js";
|
|
54
55
|
export { correlationTool } from "./portfolio/correlation.js";
|
|
56
|
+
export { holdingsOverlapTool } from "./portfolio/holdings-overlap.js";
|
|
55
57
|
export { predictionsTool } from "./portfolio/predictions.js";
|
|
56
58
|
export { optionChainTool } from "./options/option-chain.js";
|
|
57
59
|
|
|
@@ -78,6 +80,7 @@ export function getAllTools(): AgentTool<any>[] {
|
|
|
78
80
|
riskAnalysisTool,
|
|
79
81
|
watchlistTool,
|
|
80
82
|
correlationTool,
|
|
83
|
+
holdingsOverlapTool,
|
|
81
84
|
predictionsTool,
|
|
82
85
|
optionChainTool,
|
|
83
86
|
webSearchTool,
|
|
@@ -12,7 +12,7 @@ export const fearGreedTool: AgentTool<typeof params, FearGreedData> = {
|
|
|
12
12
|
description:
|
|
13
13
|
"Get the Crypto Fear & Greed Index (alternative.me) — a sentiment indicator from 0 (Extreme Fear) to 100 (Extreme Greed). Includes current value and previous close.",
|
|
14
14
|
parameters: params,
|
|
15
|
-
async execute(
|
|
15
|
+
async execute(_toolCallId, _args) {
|
|
16
16
|
const result = await wrapProvider("feargreed", () => getFearGreedIndex());
|
|
17
17
|
if (result.status === "unavailable") {
|
|
18
18
|
return {
|
|
@@ -22,14 +22,15 @@ export const fredDataTool: AgentTool<typeof params, FredSeries | { credentialReq
|
|
|
22
22
|
description:
|
|
23
23
|
"Get economic data from FRED (Federal Reserve Economic Data): interest rates, CPI, GDP, unemployment, yield curve, and more. Requires FRED.",
|
|
24
24
|
parameters: params,
|
|
25
|
-
async execute(
|
|
25
|
+
async execute(_toolCallId, args) {
|
|
26
26
|
return withCredentialCheck("fred", async () => {
|
|
27
27
|
const apiKey = getConfig().fredApiKey!;
|
|
28
|
-
const
|
|
29
|
-
const
|
|
28
|
+
const seriesId = args.series_id.toUpperCase();
|
|
29
|
+
const limit = normalizeLimit(seriesId, args.limit);
|
|
30
|
+
const result = await wrapProvider("fred", () => getSeries(seriesId, apiKey, limit));
|
|
30
31
|
if (result.status === "unavailable") {
|
|
31
32
|
return {
|
|
32
|
-
content: [{ type: "text", text: `⚠ FRED data unavailable for ${
|
|
33
|
+
content: [{ type: "text", text: `⚠ FRED data unavailable for ${seriesId} (${result.reason}).` }],
|
|
33
34
|
details: null as any,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -39,12 +40,15 @@ export const fredDataTool: AgentTool<typeof params, FredSeries | { credentialReq
|
|
|
39
40
|
const header = `**${series.title}** (${series.id})`;
|
|
40
41
|
const meta = `Units: ${series.units} | Frequency: ${series.frequency} | Last updated: ${series.lastUpdated}`;
|
|
41
42
|
const current = latest ? `Latest: ${latest.value} (${latest.date})` : "No data";
|
|
43
|
+
const derived = formatDerivedChange(series);
|
|
42
44
|
|
|
43
45
|
// Show last 10 observations
|
|
44
46
|
const recent = series.observations.slice(-10);
|
|
45
47
|
const table = recent.map((o) => `${o.date}: ${o.value}`).join("\n");
|
|
46
48
|
|
|
47
|
-
const text = [header, meta, current, "", "Recent observations:", table]
|
|
49
|
+
const text = [header, meta, current, derived, "", "Recent observations:", table]
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join("\n");
|
|
48
52
|
const prefix = result.stale
|
|
49
53
|
? `⚠ Using cached FRED data from ${result.timestamp} (FRED unavailable)\n`
|
|
50
54
|
: "";
|
|
@@ -52,3 +56,25 @@ export const fredDataTool: AgentTool<typeof params, FredSeries | { credentialReq
|
|
|
52
56
|
});
|
|
53
57
|
},
|
|
54
58
|
};
|
|
59
|
+
|
|
60
|
+
function normalizeLimit(seriesId: string, requested: number | undefined): number {
|
|
61
|
+
const limit = requested ?? 30;
|
|
62
|
+
if (seriesId === FRED_SERIES.CPI && limit < 13) return 13;
|
|
63
|
+
return limit;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatDerivedChange(series: FredSeries): string | null {
|
|
67
|
+
const latest = series.observations.at(-1);
|
|
68
|
+
if (!latest) return null;
|
|
69
|
+
if (!/monthly/i.test(series.frequency) || !/\bindex\b/i.test(series.units)) return null;
|
|
70
|
+
|
|
71
|
+
const latestYear = Number(latest.date.slice(0, 4));
|
|
72
|
+
const monthDay = latest.date.slice(4);
|
|
73
|
+
const prior = series.observations.find((observation) =>
|
|
74
|
+
observation.date === `${latestYear - 1}${monthDay}`
|
|
75
|
+
);
|
|
76
|
+
if (!prior || prior.value === 0) return null;
|
|
77
|
+
|
|
78
|
+
const pct = ((latest.value / prior.value) - 1) * 100;
|
|
79
|
+
return `Derived YoY change: ${pct.toFixed(2)}% (${prior.date} to ${latest.date}).`;
|
|
80
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
|
3
3
|
import { getCryptoHistory } from "../../providers/coingecko.js";
|
|
4
4
|
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
5
5
|
import type { OHLCV } from "../../types/market.js";
|
|
6
|
+
import { computeRiskMetrics } from "../portfolio/risk-analysis.js";
|
|
6
7
|
|
|
7
8
|
const params = Type.Object({
|
|
8
9
|
id: Type.String({
|
|
@@ -20,7 +21,7 @@ export const cryptoHistoryTool: AgentTool<typeof params, OHLCV[]> = {
|
|
|
20
21
|
label: "Crypto History",
|
|
21
22
|
description: "Get historical OHLC data for a cryptocurrency",
|
|
22
23
|
parameters: params,
|
|
23
|
-
async execute(
|
|
24
|
+
async execute(_toolCallId, args) {
|
|
24
25
|
const id = args.id.toLowerCase();
|
|
25
26
|
const days = args.days ?? 180;
|
|
26
27
|
const result = await wrapProvider("coingecko", () => getCryptoHistory(id, days));
|
|
@@ -45,7 +46,22 @@ export const cryptoHistoryTool: AgentTool<typeof params, OHLCV[]> = {
|
|
|
45
46
|
)
|
|
46
47
|
.join("\n");
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
+
const riskLines = buildRiskLines(id, bars);
|
|
50
|
+
const text = [...summary, ...riskLines, "", "Recent bars:", table].join("\n");
|
|
49
51
|
return { content: [{ type: "text", text }], details: bars };
|
|
50
52
|
},
|
|
51
53
|
};
|
|
54
|
+
|
|
55
|
+
function buildRiskLines(id: string, bars: OHLCV[]): string[] {
|
|
56
|
+
if (bars.length < 30) return [];
|
|
57
|
+
const metrics = computeRiskMetrics(id.toUpperCase(), bars.map((bar) => bar.close));
|
|
58
|
+
return [
|
|
59
|
+
"",
|
|
60
|
+
"Risk metrics from crypto history:",
|
|
61
|
+
`Annualized Return: ${(metrics.annualizedReturn * 100).toFixed(2)}%`,
|
|
62
|
+
`Annualized Volatility: ${(metrics.annualizedVolatility * 100).toFixed(2)}%`,
|
|
63
|
+
`Sharpe Ratio: ${metrics.sharpeRatio.toFixed(2)}`,
|
|
64
|
+
`Max Drawdown: ${(metrics.maxDrawdown * 100).toFixed(2)}%`,
|
|
65
|
+
`Value at Risk (95%, daily): ${(metrics.var95 * 100).toFixed(2)}%`,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
@@ -17,7 +17,7 @@ export const cryptoPriceTool: AgentTool<typeof params, CryptoPrice> = {
|
|
|
17
17
|
description:
|
|
18
18
|
"Get current crypto price, 24h change, market cap, volume, ATH, and supply data",
|
|
19
19
|
parameters: params,
|
|
20
|
-
async execute(
|
|
20
|
+
async execute(_toolCallId, args) {
|
|
21
21
|
const result = await wrapProvider("coingecko", () => getCryptoPrice(args.id.toLowerCase()));
|
|
22
22
|
if (result.status === "unavailable") {
|
|
23
23
|
return {
|
|
@@ -25,7 +25,7 @@ export const searchTickerTool: AgentTool<typeof params> = {
|
|
|
25
25
|
description:
|
|
26
26
|
"Search for any ticker symbol — stocks, crypto, ETFs, indices, forex. Returns matching symbols with names and exchange info. Use this when you don't know the exact ticker for an asset.",
|
|
27
27
|
parameters: params,
|
|
28
|
-
async execute(
|
|
28
|
+
async execute(_toolCallId, args) {
|
|
29
29
|
const url = `https://query1.finance.yahoo.com/v1/finance/search?q=${encodeURIComponent(args.query)}"esCount=10&newsCount=0`;
|
|
30
30
|
const data = await httpGet<YahooSearchResponse>(url, {
|
|
31
31
|
headers: { "User-Agent": "OpenCandle/1.0" },
|
|
@@ -29,7 +29,7 @@ export const stockHistoryTool: AgentTool<typeof params, OHLCV[]> = {
|
|
|
29
29
|
label: "Stock History",
|
|
30
30
|
description: "Get historical OHLCV (open, high, low, close, volume) data for a stock",
|
|
31
31
|
parameters: params,
|
|
32
|
-
async execute(
|
|
32
|
+
async execute(_toolCallId, args) {
|
|
33
33
|
const symbol = args.symbol.toUpperCase();
|
|
34
34
|
const range = args.range ?? "6mo";
|
|
35
35
|
const interval = args.interval ?? "1d";
|
|
@@ -16,7 +16,7 @@ export const stockQuoteTool: AgentTool<typeof params, StockQuote> = {
|
|
|
16
16
|
description:
|
|
17
17
|
"Get real-time stock price, volume, market cap, and 52-week range for a ticker symbol",
|
|
18
18
|
parameters: params,
|
|
19
|
-
async execute(
|
|
19
|
+
async execute(_toolCallId, args) {
|
|
20
20
|
const symbol = args.symbol.toUpperCase();
|
|
21
21
|
const apiKey = getConfig().alphaVantageApiKey;
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ export const optionChainTool: AgentTool<typeof params, OptionsChain> = {
|
|
|
25
25
|
description:
|
|
26
26
|
"Get the full options chain for a stock with strikes, bids, asks, volume, open interest, implied volatility, and computed Greeks (Delta, Gamma, Theta, Vega, Rho via Black-Scholes). No API key required.",
|
|
27
27
|
parameters: params,
|
|
28
|
-
async execute(
|
|
28
|
+
async execute(_toolCallId, args) {
|
|
29
29
|
const symbol = args.symbol.toUpperCase();
|
|
30
30
|
const normalizedType = args.type?.toLowerCase();
|
|
31
31
|
const expirationTs = args.expiration
|
|
@@ -44,6 +44,11 @@ export const optionChainTool: AgentTool<typeof params, OptionsChain> = {
|
|
|
44
44
|
const lines: string[] = [
|
|
45
45
|
`**${chain.symbol} Options Chain** — Expiry: ${chain.expirationDate}`,
|
|
46
46
|
`Underlying: $${chain.underlyingPrice.toFixed(2)}`,
|
|
47
|
+
`Quote status: ${chain.quoteStatus.marketSession} / ${chain.quoteStatus.bidAskState}`,
|
|
48
|
+
"Option bid/ask and last prices are quoted per share; multiply by 100 for one standard contract premium.",
|
|
49
|
+
...(chain.quoteStatus.warning
|
|
50
|
+
? [`⚠ ${chain.quoteStatus.warning} do not treat zero bid/ask as confirmed live illiquidity without broker verification; do not stop at the stale quote caveat. Disclose the gap, avoid naming tradable live premiums, and finish the strategy explanation with mechanics, assignment outcomes, labeled hypotheticals, and what live broker quotes would change.`]
|
|
51
|
+
: []),
|
|
47
52
|
`Available expirations: ${formatAvailableExpirations(chain.expirationDates)}`,
|
|
48
53
|
"",
|
|
49
54
|
];
|
|
@@ -53,7 +58,7 @@ export const optionChainTool: AgentTool<typeof params, OptionsChain> = {
|
|
|
53
58
|
|
|
54
59
|
if (showCalls && chain.calls.length > 0) {
|
|
55
60
|
lines.push(`**CALLS** (${chain.calls.length} contracts, volume: ${chain.totalCallVolume.toLocaleString()})`);
|
|
56
|
-
lines.push("Strike | Bid/Ask | Last | Vol | OI | IV | Delta | Theta");
|
|
61
|
+
lines.push("Strike | Bid/Ask (per share) | Last (per share) | Vol | OI | IV | Delta | Gamma | Theta | Vega | Rho");
|
|
57
62
|
const topCalls = sortByVolume(chain.calls).slice(0, 10);
|
|
58
63
|
for (const c of topCalls) {
|
|
59
64
|
lines.push(formatContract(c));
|
|
@@ -63,7 +68,7 @@ export const optionChainTool: AgentTool<typeof params, OptionsChain> = {
|
|
|
63
68
|
|
|
64
69
|
if (showPuts && chain.puts.length > 0) {
|
|
65
70
|
lines.push(`**PUTS** (${chain.puts.length} contracts, volume: ${chain.totalPutVolume.toLocaleString()})`);
|
|
66
|
-
lines.push("Strike | Bid/Ask | Last | Vol | OI | IV | Delta | Theta");
|
|
71
|
+
lines.push("Strike | Bid/Ask (per share) | Last (per share) | Vol | OI | IV | Delta | Gamma | Theta | Vega | Rho");
|
|
67
72
|
const topPuts = sortByVolume(chain.puts).slice(0, 10);
|
|
68
73
|
for (const c of topPuts) {
|
|
69
74
|
lines.push(formatContract(c));
|
|
@@ -87,5 +92,5 @@ function formatAvailableExpirations(expirationDates: string[]): string {
|
|
|
87
92
|
|
|
88
93
|
function formatContract(c: OptionContract): string {
|
|
89
94
|
const itm = c.inTheMoney ? "*" : " ";
|
|
90
|
-
return `${itm}$${c.strike.toFixed(2)} | $${c.bid.toFixed(2)}/$${c.ask.toFixed(2)} | $${c.lastPrice.toFixed(2)} | ${c.volume} | ${c.openInterest} | ${(c.impliedVolatility * 100).toFixed(1)}% | ${c.greeks.delta.toFixed(3)} | ${c.greeks.theta.toFixed(3)}`;
|
|
95
|
+
return `${itm}$${c.strike.toFixed(2)} | $${c.bid.toFixed(2)}/$${c.ask.toFixed(2)} | $${c.lastPrice.toFixed(2)} | ${c.volume} | ${c.openInterest} | ${(c.impliedVolatility * 100).toFixed(1)}% | ${c.greeks.delta.toFixed(3)} | ${c.greeks.gamma.toFixed(3)} | ${c.greeks.theta.toFixed(3)} | ${c.greeks.vega.toFixed(3)} | ${c.greeks.rho.toFixed(3)}`;
|
|
91
96
|
}
|
|
@@ -86,7 +86,7 @@ export const correlationTool: AgentTool<typeof params> = {
|
|
|
86
86
|
description:
|
|
87
87
|
"Compute pairwise return correlations between 2+ stocks. Identifies highly correlated positions (|r| > 0.7) as concentration risk. Useful for portfolio diversification analysis.",
|
|
88
88
|
parameters: params,
|
|
89
|
-
async execute(
|
|
89
|
+
async execute(_toolCallId, args) {
|
|
90
90
|
const symbols = args.symbols.map((s) => s.toUpperCase());
|
|
91
91
|
const period = args.period ?? "1y";
|
|
92
92
|
|