kalshi-trading-bot-cli 2.1.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 +21 -0
- package/README.md +360 -0
- package/assets/kalshi-flow-light.png +0 -0
- package/assets/screenshot.png +0 -0
- package/env.example +43 -0
- package/kalshi-flow-light.png +0 -0
- package/package.json +66 -0
- package/src/agent/agent.ts +249 -0
- package/src/agent/channels.ts +53 -0
- package/src/agent/index.ts +29 -0
- package/src/agent/prompts.ts +171 -0
- package/src/agent/run-context.ts +23 -0
- package/src/agent/scratchpad.ts +465 -0
- package/src/agent/token-counter.ts +33 -0
- package/src/agent/tool-executor.ts +166 -0
- package/src/agent/types.ts +221 -0
- package/src/audit/index.ts +25 -0
- package/src/audit/reader.ts +43 -0
- package/src/audit/trail.ts +29 -0
- package/src/audit/types.ts +133 -0
- package/src/backtest/discovery.ts +170 -0
- package/src/backtest/fetcher.ts +247 -0
- package/src/backtest/metrics.ts +165 -0
- package/src/backtest/renderer.ts +196 -0
- package/src/backtest/types.ts +45 -0
- package/src/cli.ts +943 -0
- package/src/commands/alerts.ts +48 -0
- package/src/commands/analyze.ts +662 -0
- package/src/commands/backtest.ts +276 -0
- package/src/commands/clear-cache.ts +24 -0
- package/src/commands/config.ts +107 -0
- package/src/commands/dispatch.ts +473 -0
- package/src/commands/edge.ts +62 -0
- package/src/commands/formatters.ts +339 -0
- package/src/commands/help.ts +263 -0
- package/src/commands/helpers.ts +48 -0
- package/src/commands/index.ts +287 -0
- package/src/commands/json.ts +43 -0
- package/src/commands/parse-args.ts +229 -0
- package/src/commands/portfolio.ts +236 -0
- package/src/commands/review.ts +176 -0
- package/src/commands/scan-formatters.ts +98 -0
- package/src/commands/scan.ts +38 -0
- package/src/commands/search-edge.ts +139 -0
- package/src/commands/status.ts +70 -0
- package/src/commands/themes.ts +117 -0
- package/src/commands/watch.ts +295 -0
- package/src/components/answer-box.ts +57 -0
- package/src/components/approval-prompt.ts +34 -0
- package/src/components/browse-list.ts +134 -0
- package/src/components/chat-log.ts +291 -0
- package/src/components/custom-editor.ts +18 -0
- package/src/components/debug-panel.ts +52 -0
- package/src/components/index.ts +17 -0
- package/src/components/intro.ts +92 -0
- package/src/components/select-list.ts +155 -0
- package/src/components/tool-event.ts +127 -0
- package/src/components/user-query.ts +18 -0
- package/src/components/working-indicator.ts +87 -0
- package/src/controllers/agent-runner.ts +283 -0
- package/src/controllers/browse.ts +1013 -0
- package/src/controllers/index.ts +7 -0
- package/src/controllers/input-history.ts +76 -0
- package/src/controllers/model-selection.ts +244 -0
- package/src/db/alerts.ts +77 -0
- package/src/db/edge.ts +105 -0
- package/src/db/event-index.ts +323 -0
- package/src/db/events.ts +41 -0
- package/src/db/index.ts +60 -0
- package/src/db/octagon-cache.ts +118 -0
- package/src/db/positions.ts +71 -0
- package/src/db/risk.ts +51 -0
- package/src/db/schema.ts +227 -0
- package/src/db/themes.ts +34 -0
- package/src/db/trades.ts +50 -0
- package/src/eval/brier.ts +90 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/performance.ts +87 -0
- package/src/gateway/access-control.ts +253 -0
- package/src/gateway/agent-runner.ts +75 -0
- package/src/gateway/alerts/formatter.ts +90 -0
- package/src/gateway/alerts/index.ts +4 -0
- package/src/gateway/alerts/router.ts +32 -0
- package/src/gateway/alerts/terminal.ts +16 -0
- package/src/gateway/alerts/types.ts +13 -0
- package/src/gateway/channels/index.ts +9 -0
- package/src/gateway/channels/manager.ts +153 -0
- package/src/gateway/channels/types.ts +48 -0
- package/src/gateway/channels/whatsapp/README.md +234 -0
- package/src/gateway/channels/whatsapp/auth-store.ts +140 -0
- package/src/gateway/channels/whatsapp/dedupe.ts +60 -0
- package/src/gateway/channels/whatsapp/error.ts +122 -0
- package/src/gateway/channels/whatsapp/inbound.ts +326 -0
- package/src/gateway/channels/whatsapp/index.ts +5 -0
- package/src/gateway/channels/whatsapp/lid.ts +56 -0
- package/src/gateway/channels/whatsapp/logger.ts +25 -0
- package/src/gateway/channels/whatsapp/login.ts +94 -0
- package/src/gateway/channels/whatsapp/outbound.ts +119 -0
- package/src/gateway/channels/whatsapp/plugin.ts +54 -0
- package/src/gateway/channels/whatsapp/reconnect.ts +40 -0
- package/src/gateway/channels/whatsapp/runtime.ts +122 -0
- package/src/gateway/channels/whatsapp/session.ts +89 -0
- package/src/gateway/channels/whatsapp/types.ts +32 -0
- package/src/gateway/commands/handler.ts +64 -0
- package/src/gateway/commands/index.ts +7 -0
- package/src/gateway/commands/parser.ts +29 -0
- package/src/gateway/commands/wa-formatters.ts +92 -0
- package/src/gateway/config.ts +244 -0
- package/src/gateway/extension-points.ts +17 -0
- package/src/gateway/gateway.ts +301 -0
- package/src/gateway/group/history-buffer.ts +75 -0
- package/src/gateway/group/index.ts +8 -0
- package/src/gateway/group/member-tracker.ts +60 -0
- package/src/gateway/group/mention-detection.ts +42 -0
- package/src/gateway/heartbeat/index.ts +8 -0
- package/src/gateway/heartbeat/prompt.ts +73 -0
- package/src/gateway/heartbeat/runner.ts +200 -0
- package/src/gateway/heartbeat/suppression.ts +74 -0
- package/src/gateway/index.ts +138 -0
- package/src/gateway/routing/resolve-route.ts +119 -0
- package/src/gateway/sessions/store.ts +65 -0
- package/src/gateway/types.ts +11 -0
- package/src/gateway/utils.ts +82 -0
- package/src/index.tsx +30 -0
- package/src/model/llm.ts +247 -0
- package/src/providers.ts +94 -0
- package/src/risk/circuit-breaker.ts +113 -0
- package/src/risk/correlation.ts +40 -0
- package/src/risk/gate.ts +125 -0
- package/src/risk/index.ts +10 -0
- package/src/risk/kelly.ts +230 -0
- package/src/scan/alerter.ts +64 -0
- package/src/scan/edge-computer.ts +164 -0
- package/src/scan/invoker.ts +199 -0
- package/src/scan/loop.ts +184 -0
- package/src/scan/octagon-client.ts +627 -0
- package/src/scan/octagon-events-api.ts +105 -0
- package/src/scan/octagon-prefetch.ts +172 -0
- package/src/scan/theme-resolver.ts +179 -0
- package/src/scan/types.ts +62 -0
- package/src/scan/watchdog.ts +126 -0
- package/src/setup/wizard.ts +659 -0
- package/src/theme.ts +67 -0
- package/src/tools/fetch/cache.ts +95 -0
- package/src/tools/fetch/external-content.ts +200 -0
- package/src/tools/fetch/index.ts +1 -0
- package/src/tools/fetch/web-fetch-utils.ts +122 -0
- package/src/tools/fetch/web-fetch.ts +419 -0
- package/src/tools/index.ts +10 -0
- package/src/tools/kalshi/api.ts +251 -0
- package/src/tools/kalshi/dlq.ts +35 -0
- package/src/tools/kalshi/events.ts +84 -0
- package/src/tools/kalshi/exchange.ts +24 -0
- package/src/tools/kalshi/historical.ts +89 -0
- package/src/tools/kalshi/index.ts +11 -0
- package/src/tools/kalshi/kalshi-search.ts +437 -0
- package/src/tools/kalshi/kalshi-trade.ts +102 -0
- package/src/tools/kalshi/markets.ts +76 -0
- package/src/tools/kalshi/portfolio.ts +100 -0
- package/src/tools/kalshi/search-index.ts +198 -0
- package/src/tools/kalshi/series.ts +16 -0
- package/src/tools/kalshi/trading.ts +115 -0
- package/src/tools/kalshi/types.ts +199 -0
- package/src/tools/registry.ts +160 -0
- package/src/tools/search/index.ts +25 -0
- package/src/tools/search/tavily.ts +35 -0
- package/src/tools/types.ts +53 -0
- package/src/tools/v2/edge-query.ts +135 -0
- package/src/tools/v2/octagon-report.ts +112 -0
- package/src/tools/v2/portfolio-query.ts +79 -0
- package/src/tools/v2/portfolio-review.ts +59 -0
- package/src/tools/v2/risk-status.ts +94 -0
- package/src/tools/v2/scan.ts +78 -0
- package/src/types/qrcode-terminal.d.ts +7 -0
- package/src/types/whiskeysockets-baileys.d.ts +41 -0
- package/src/types.ts +22 -0
- package/src/utils/ai-message.ts +26 -0
- package/src/utils/bot-config.ts +219 -0
- package/src/utils/cache.ts +195 -0
- package/src/utils/config.ts +113 -0
- package/src/utils/env.ts +111 -0
- package/src/utils/errors.ts +313 -0
- package/src/utils/history-context.ts +32 -0
- package/src/utils/in-memory-chat-history.ts +268 -0
- package/src/utils/index.ts +28 -0
- package/src/utils/input-key-handlers.ts +64 -0
- package/src/utils/logger.ts +67 -0
- package/src/utils/long-term-chat-history.ts +138 -0
- package/src/utils/markdown-table.ts +227 -0
- package/src/utils/model.ts +70 -0
- package/src/utils/ollama.ts +37 -0
- package/src/utils/paths.ts +12 -0
- package/src/utils/progress-channel.ts +84 -0
- package/src/utils/telemetry.ts +103 -0
- package/src/utils/text-navigation.ts +81 -0
- package/src/utils/thinking-verbs.ts +18 -0
- package/src/utils/tokens.ts +36 -0
- package/src/utils/tool-description.ts +61 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export interface KalshiBalance {
|
|
2
|
+
balance: number;
|
|
3
|
+
portfolio_value: number;
|
|
4
|
+
updated_ts?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface KalshiMarket {
|
|
8
|
+
ticker: string;
|
|
9
|
+
event_ticker: string;
|
|
10
|
+
market_type: string;
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle: string;
|
|
13
|
+
yes_sub_title: string;
|
|
14
|
+
no_sub_title: string;
|
|
15
|
+
open_time: string;
|
|
16
|
+
close_time: string;
|
|
17
|
+
expected_expiration_time: string;
|
|
18
|
+
expiration_time: string;
|
|
19
|
+
latest_expiration_time: string;
|
|
20
|
+
settlement_timer_seconds: number;
|
|
21
|
+
status: string;
|
|
22
|
+
response_price_units: string;
|
|
23
|
+
notional_value: number;
|
|
24
|
+
tick_size: number;
|
|
25
|
+
yes_bid: number;
|
|
26
|
+
yes_ask: number;
|
|
27
|
+
no_bid: number;
|
|
28
|
+
no_ask: number;
|
|
29
|
+
last_price: number;
|
|
30
|
+
previous_yes_bid: number;
|
|
31
|
+
previous_yes_ask: number;
|
|
32
|
+
previous_price: number;
|
|
33
|
+
volume: number;
|
|
34
|
+
volume_fp?: string;
|
|
35
|
+
volume_24h: number;
|
|
36
|
+
volume_24h_fp?: string;
|
|
37
|
+
liquidity: number;
|
|
38
|
+
open_interest: number;
|
|
39
|
+
result: string;
|
|
40
|
+
settlement_value: string;
|
|
41
|
+
can_close_early: boolean;
|
|
42
|
+
expiration_value: string;
|
|
43
|
+
category: string;
|
|
44
|
+
risk_limit_cents: number;
|
|
45
|
+
strike_type: string;
|
|
46
|
+
floor_strike: number;
|
|
47
|
+
cap_strike: number;
|
|
48
|
+
supports_fractional?: boolean;
|
|
49
|
+
// New API format (yes_*_dollars)
|
|
50
|
+
yes_bid_dollars?: string;
|
|
51
|
+
yes_ask_dollars?: string;
|
|
52
|
+
no_bid_dollars?: string;
|
|
53
|
+
no_ask_dollars?: string;
|
|
54
|
+
last_price_dollars?: string;
|
|
55
|
+
// Legacy API format (dollar_yes_*)
|
|
56
|
+
dollar_yes_bid?: string;
|
|
57
|
+
dollar_yes_ask?: string;
|
|
58
|
+
dollar_no_bid?: string;
|
|
59
|
+
dollar_no_ask?: string;
|
|
60
|
+
dollar_last_price?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface KalshiEvent {
|
|
64
|
+
event_ticker: string;
|
|
65
|
+
series_ticker: string;
|
|
66
|
+
sub_title: string;
|
|
67
|
+
title: string;
|
|
68
|
+
mutually_exclusive: boolean;
|
|
69
|
+
category: string;
|
|
70
|
+
strike_date: string;
|
|
71
|
+
markets?: KalshiMarket[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface KalshiSeries {
|
|
75
|
+
ticker: string;
|
|
76
|
+
frequency: string;
|
|
77
|
+
title: string;
|
|
78
|
+
category: string;
|
|
79
|
+
tags: string[];
|
|
80
|
+
settlement_sources: Array<{ url: string; name: string }>;
|
|
81
|
+
contract_url: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface KalshiOrder {
|
|
85
|
+
order_id: string;
|
|
86
|
+
user_id: string;
|
|
87
|
+
ticker: string;
|
|
88
|
+
client_order_id?: string;
|
|
89
|
+
status: string;
|
|
90
|
+
yes_price_dollars?: string;
|
|
91
|
+
no_price_dollars?: string;
|
|
92
|
+
/** @deprecated old API field */
|
|
93
|
+
yes_price?: number;
|
|
94
|
+
/** @deprecated old API field */
|
|
95
|
+
no_price?: number;
|
|
96
|
+
created_time: string;
|
|
97
|
+
expiration_time?: string | null;
|
|
98
|
+
action: string;
|
|
99
|
+
side: string;
|
|
100
|
+
type: string;
|
|
101
|
+
initial_count_fp?: string;
|
|
102
|
+
remaining_count_fp?: string;
|
|
103
|
+
fill_count_fp?: string;
|
|
104
|
+
/** @deprecated old API field */
|
|
105
|
+
contracts_count?: number;
|
|
106
|
+
/** @deprecated old API field */
|
|
107
|
+
remaining_count?: number;
|
|
108
|
+
maker_fees_dollars?: string;
|
|
109
|
+
taker_fees_dollars?: string;
|
|
110
|
+
maker_fill_cost_dollars?: string;
|
|
111
|
+
taker_fill_cost_dollars?: string;
|
|
112
|
+
order_group_id?: string | null;
|
|
113
|
+
subaccount_number?: number;
|
|
114
|
+
last_update_time?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface KalshiPosition {
|
|
118
|
+
ticker: string;
|
|
119
|
+
event_ticker: string;
|
|
120
|
+
position: number;
|
|
121
|
+
position_fp?: number;
|
|
122
|
+
resting_orders_count: number;
|
|
123
|
+
market_exposure: number;
|
|
124
|
+
market_exposure_dollars?: string;
|
|
125
|
+
realized_pnl: number;
|
|
126
|
+
realized_pnl_dollars?: string;
|
|
127
|
+
total_traded: number;
|
|
128
|
+
total_traded_dollars?: string;
|
|
129
|
+
fees_paid: number;
|
|
130
|
+
fees_paid_dollars?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface KalshiFill {
|
|
134
|
+
trade_id: string;
|
|
135
|
+
order_id: string;
|
|
136
|
+
ticker: string;
|
|
137
|
+
side: string;
|
|
138
|
+
action: string;
|
|
139
|
+
count: number;
|
|
140
|
+
yes_price: number;
|
|
141
|
+
no_price: number;
|
|
142
|
+
is_taker: boolean;
|
|
143
|
+
created_time: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface KalshiOrderbookEntry {
|
|
147
|
+
price: number;
|
|
148
|
+
delta: number;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface KalshiOrderbook {
|
|
152
|
+
ticker: string;
|
|
153
|
+
yes: KalshiOrderbookEntry[];
|
|
154
|
+
no: KalshiOrderbookEntry[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface KalshiCandlestick {
|
|
158
|
+
ts: number;
|
|
159
|
+
yes_bid: { close: number; high: number; low: number; open: number };
|
|
160
|
+
yes_ask: { close: number; high: number; low: number; open: number };
|
|
161
|
+
last_price: { close: number; high: number; low: number; open: number };
|
|
162
|
+
volume: number;
|
|
163
|
+
open_interest: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface KalshiSettlement {
|
|
167
|
+
ticker: string;
|
|
168
|
+
settled_time: string;
|
|
169
|
+
market_result: string;
|
|
170
|
+
no_count: number;
|
|
171
|
+
no_total_cost: number;
|
|
172
|
+
yes_count: number;
|
|
173
|
+
yes_total_cost: number;
|
|
174
|
+
revenue: number;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface KalshiExchangeStatus {
|
|
178
|
+
exchange_active: boolean;
|
|
179
|
+
trading_active: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface KalshiDollarOrderRequest {
|
|
183
|
+
ticker: string;
|
|
184
|
+
action: "buy" | "sell";
|
|
185
|
+
side: "yes" | "no";
|
|
186
|
+
type: "limit" | "market";
|
|
187
|
+
count: number;
|
|
188
|
+
dollar_price?: string;
|
|
189
|
+
expiration_ts?: number;
|
|
190
|
+
client_order_id?: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface KalshiExchangeSchedule {
|
|
194
|
+
schedule: Array<{
|
|
195
|
+
open_time: string;
|
|
196
|
+
close_time: string;
|
|
197
|
+
maintenance_windows?: Array<{ start_time: string; end_time: string }>;
|
|
198
|
+
}>;
|
|
199
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { StructuredToolInterface } from '@langchain/core/tools';
|
|
2
|
+
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createKalshiSearch, KALSHI_SEARCH_DESCRIPTION } from './kalshi/kalshi-search.js';
|
|
5
|
+
import { createKalshiTrade, KALSHI_TRADE_DESCRIPTION } from './kalshi/kalshi-trade.js';
|
|
6
|
+
import { getExchangeStatus } from './kalshi/exchange.js';
|
|
7
|
+
import { callKalshiApi } from './kalshi/api.js';
|
|
8
|
+
import { tavilySearch, WEB_SEARCH_DESCRIPTION } from './search/index.js';
|
|
9
|
+
import { webFetchTool, WEB_FETCH_DESCRIPTION } from './fetch/web-fetch.js';
|
|
10
|
+
import { formatToolResult } from './types.js';
|
|
11
|
+
import { edgeQueryTool, EDGE_QUERY_DESCRIPTION } from './v2/edge-query.js';
|
|
12
|
+
import { portfolioQueryTool, PORTFOLIO_QUERY_DESCRIPTION } from './v2/portfolio-query.js';
|
|
13
|
+
import { riskStatusTool, RISK_STATUS_DESCRIPTION } from './v2/risk-status.js';
|
|
14
|
+
import { octagonReportTool, OCTAGON_REPORT_DESCRIPTION } from './v2/octagon-report.js';
|
|
15
|
+
import { scanTool, SCAN_DESCRIPTION } from './v2/scan.js';
|
|
16
|
+
import { portfolioReviewTool, PORTFOLIO_REVIEW_DESCRIPTION } from './v2/portfolio-review.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A registered tool with its rich description for system prompt injection.
|
|
20
|
+
*/
|
|
21
|
+
export interface RegisteredTool {
|
|
22
|
+
/** Tool name (must match the tool's name property) */
|
|
23
|
+
name: string;
|
|
24
|
+
/** The actual tool instance */
|
|
25
|
+
tool: StructuredToolInterface;
|
|
26
|
+
/** Rich description for system prompt (includes when to use, when not to use, etc.) */
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Direct portfolio overview tool (balance + positions in one call)
|
|
31
|
+
const portfolioOverviewTool = new DynamicStructuredTool({
|
|
32
|
+
name: 'portfolio_overview',
|
|
33
|
+
description: 'Get a quick overview of the Kalshi portfolio: balance and open positions.',
|
|
34
|
+
schema: z.object({}),
|
|
35
|
+
func: async () => {
|
|
36
|
+
const [balanceData, positionsData] = await Promise.all([
|
|
37
|
+
callKalshiApi('GET', '/portfolio/balance'),
|
|
38
|
+
callKalshiApi('GET', '/portfolio/positions'),
|
|
39
|
+
]);
|
|
40
|
+
return formatToolResult({ balance: balanceData, positions: positionsData });
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const PORTFOLIO_OVERVIEW_DESCRIPTION = `
|
|
45
|
+
Quick portfolio overview tool. Returns current account balance and all open positions in a single call.
|
|
46
|
+
|
|
47
|
+
## When to Use
|
|
48
|
+
- User asks "what's my portfolio?" or "show me my balance and positions"
|
|
49
|
+
- Quick portfolio check before or after trading
|
|
50
|
+
|
|
51
|
+
## When NOT to Use
|
|
52
|
+
- Detailed fills or order history (use kalshi_search instead)
|
|
53
|
+
`.trim();
|
|
54
|
+
|
|
55
|
+
const EXCHANGE_STATUS_DESCRIPTION = `
|
|
56
|
+
Check whether the Kalshi exchange is currently active and trading is enabled.
|
|
57
|
+
|
|
58
|
+
## When to Use
|
|
59
|
+
- "Is Kalshi open?" or "Can I trade right now?"
|
|
60
|
+
`.trim();
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all registered tools with their descriptions.
|
|
64
|
+
*
|
|
65
|
+
* @param model - The model name (needed for sub-agent meta-tools)
|
|
66
|
+
* @returns Array of registered tools
|
|
67
|
+
*/
|
|
68
|
+
export function getToolRegistry(model: string): RegisteredTool[] {
|
|
69
|
+
const tools: RegisteredTool[] = [
|
|
70
|
+
{
|
|
71
|
+
name: 'kalshi_search',
|
|
72
|
+
tool: createKalshiSearch(model),
|
|
73
|
+
description: KALSHI_SEARCH_DESCRIPTION,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'kalshi_trade',
|
|
77
|
+
tool: createKalshiTrade(model),
|
|
78
|
+
description: KALSHI_TRADE_DESCRIPTION,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'portfolio_overview',
|
|
82
|
+
tool: portfolioOverviewTool,
|
|
83
|
+
description: PORTFOLIO_OVERVIEW_DESCRIPTION,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'exchange_status',
|
|
87
|
+
tool: getExchangeStatus,
|
|
88
|
+
description: EXCHANGE_STATUS_DESCRIPTION,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'web_fetch',
|
|
92
|
+
tool: webFetchTool,
|
|
93
|
+
description: WEB_FETCH_DESCRIPTION,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'edge_query',
|
|
97
|
+
tool: edgeQueryTool,
|
|
98
|
+
description: EDGE_QUERY_DESCRIPTION,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'portfolio_query',
|
|
102
|
+
tool: portfolioQueryTool,
|
|
103
|
+
description: PORTFOLIO_QUERY_DESCRIPTION,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'risk_status',
|
|
107
|
+
tool: riskStatusTool,
|
|
108
|
+
description: RISK_STATUS_DESCRIPTION,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'octagon_report',
|
|
112
|
+
tool: octagonReportTool,
|
|
113
|
+
description: OCTAGON_REPORT_DESCRIPTION,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'scan_markets',
|
|
117
|
+
tool: scanTool,
|
|
118
|
+
description: SCAN_DESCRIPTION,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'portfolio_review',
|
|
122
|
+
tool: portfolioReviewTool,
|
|
123
|
+
description: PORTFOLIO_REVIEW_DESCRIPTION,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
// Include web_search if Tavily API key is configured
|
|
128
|
+
if (process.env.TAVILY_API_KEY) {
|
|
129
|
+
tools.push({
|
|
130
|
+
name: 'web_search',
|
|
131
|
+
tool: tavilySearch,
|
|
132
|
+
description: WEB_SEARCH_DESCRIPTION,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return tools;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get just the tool instances for binding to the LLM.
|
|
141
|
+
*
|
|
142
|
+
* @param model - The model name
|
|
143
|
+
* @returns Array of tool instances
|
|
144
|
+
*/
|
|
145
|
+
export function getTools(model: string): StructuredToolInterface[] {
|
|
146
|
+
return getToolRegistry(model).map((t) => t.tool);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build the tool descriptions section for the system prompt.
|
|
151
|
+
* Formats each tool's rich description with a header.
|
|
152
|
+
*
|
|
153
|
+
* @param model - The model name
|
|
154
|
+
* @returns Formatted string with all tool descriptions
|
|
155
|
+
*/
|
|
156
|
+
export function buildToolDescriptions(model: string): string {
|
|
157
|
+
return getToolRegistry(model)
|
|
158
|
+
.map((t) => `### ${t.name}\n\n${t.description}`)
|
|
159
|
+
.join('\n\n');
|
|
160
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich description for the web_search tool.
|
|
3
|
+
*/
|
|
4
|
+
export const WEB_SEARCH_DESCRIPTION = `
|
|
5
|
+
Search the web for current information on any topic. Returns relevant search results with URLs and content snippets.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- Background research on real-world events behind prediction markets
|
|
10
|
+
- Current events, breaking news, recent developments
|
|
11
|
+
- Verifying claims about real-world state
|
|
12
|
+
- Researching topics to inform market analysis
|
|
13
|
+
|
|
14
|
+
## When NOT to Use
|
|
15
|
+
|
|
16
|
+
- Kalshi market data (use kalshi_search instead)
|
|
17
|
+
- Questions you can answer from knowledge
|
|
18
|
+
|
|
19
|
+
## Usage Notes
|
|
20
|
+
|
|
21
|
+
- Provide specific, well-formed search queries for best results
|
|
22
|
+
- Returns up to 5 results with URLs and content snippets
|
|
23
|
+
`.trim();
|
|
24
|
+
|
|
25
|
+
export { tavilySearch } from './tavily.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
2
|
+
import { TavilySearch } from '@langchain/tavily';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { formatToolResult, parseSearchResults } from '../types.js';
|
|
5
|
+
import { logger } from '../../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
// Lazily initialized to avoid errors when API key is not set
|
|
8
|
+
let tavilyClient: TavilySearch | null = null;
|
|
9
|
+
|
|
10
|
+
function getTavilyClient(): TavilySearch {
|
|
11
|
+
if (!tavilyClient) {
|
|
12
|
+
tavilyClient = new TavilySearch({ maxResults: 5 });
|
|
13
|
+
}
|
|
14
|
+
return tavilyClient;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const tavilySearch = new DynamicStructuredTool({
|
|
18
|
+
name: 'web_search',
|
|
19
|
+
description:
|
|
20
|
+
'Search the web for current information on any topic. Returns relevant search results with URLs and content snippets.',
|
|
21
|
+
schema: z.object({
|
|
22
|
+
query: z.string().describe('The search query to look up on the web'),
|
|
23
|
+
}),
|
|
24
|
+
func: async (input) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = await getTavilyClient().invoke({ query: input.query });
|
|
27
|
+
const { parsed, urls } = parseSearchResults(result);
|
|
28
|
+
return formatToolResult(parsed, urls);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
+
logger.error(`[Tavily API] error: ${message}`);
|
|
32
|
+
throw new Error(`[Tavily API] ${message}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface ToolResult {
|
|
2
|
+
data: unknown;
|
|
3
|
+
sourceUrls?: string[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function formatToolResult(data: unknown, sourceUrls?: string[]): string {
|
|
7
|
+
const result: ToolResult = { data };
|
|
8
|
+
if (sourceUrls?.length) {
|
|
9
|
+
result.sourceUrls = sourceUrls;
|
|
10
|
+
}
|
|
11
|
+
return JSON.stringify(result);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse search results from a search provider response.
|
|
16
|
+
* Handles both string and object responses, extracting URLs from results.
|
|
17
|
+
* Supports multiple response shapes from different providers.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSearchResults(result: unknown): { parsed: unknown; urls: string[] } {
|
|
20
|
+
// Safely parse JSON strings
|
|
21
|
+
let parsed: unknown;
|
|
22
|
+
if (typeof result === 'string') {
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(result);
|
|
25
|
+
} catch {
|
|
26
|
+
// If parsing fails, treat the string as the result itself
|
|
27
|
+
parsed = result;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
parsed = result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract URLs from multiple possible response shapes
|
|
34
|
+
let urls: string[] = [];
|
|
35
|
+
|
|
36
|
+
// Shape 1: { results: [{ url: string }] } (Exa format)
|
|
37
|
+
if (parsed && typeof parsed === 'object' && 'results' in parsed) {
|
|
38
|
+
const results = (parsed as { results?: unknown[] }).results;
|
|
39
|
+
if (Array.isArray(results)) {
|
|
40
|
+
urls = results
|
|
41
|
+
.map((r) => (r && typeof r === 'object' && 'url' in r ? (r as { url?: string }).url : null))
|
|
42
|
+
.filter((url): url is string => Boolean(url));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Shape 2: [{ url: string }] (direct array, Tavily format)
|
|
46
|
+
else if (Array.isArray(parsed)) {
|
|
47
|
+
urls = parsed
|
|
48
|
+
.map((r) => (r && typeof r === 'object' && 'url' in r ? (r as { url?: string }).url : null))
|
|
49
|
+
.filter((url): url is string => Boolean(url));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { parsed, urls };
|
|
53
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDb } from '../../db/index.js';
|
|
4
|
+
import { getLatestEdge, getActionableEdges, getEdgesByExactConfidence, getEdgeHistory } from '../../db/edge.js';
|
|
5
|
+
import { formatToolResult } from '../types.js';
|
|
6
|
+
|
|
7
|
+
export const edgeQueryTool = new DynamicStructuredTool({
|
|
8
|
+
name: 'edge_query',
|
|
9
|
+
description: 'Query edge signals and mispricing data from the local scan database.',
|
|
10
|
+
schema: z.object({
|
|
11
|
+
ticker: z.string().optional().describe('Specific market ticker to query'),
|
|
12
|
+
theme: z.string().optional().describe('Theme ID to filter by'),
|
|
13
|
+
minConfidence: z.enum(['low', 'moderate', 'high', 'very_high']).optional().describe('Minimum confidence level (returns this level and above). Use exactConfidence instead if user wants only one level.'),
|
|
14
|
+
exactConfidence: z.enum(['low', 'moderate', 'high', 'very_high']).optional().describe('Exact confidence level (returns only this level, not above). Use when user says "moderate edges" or "only high confidence".'),
|
|
15
|
+
excludeKeywords: z.array(z.string()).optional().describe('Exclude edges whose ticker or event title contains any of these keywords (case-insensitive). Use for "skip trump", "exclude crypto", etc.'),
|
|
16
|
+
}),
|
|
17
|
+
func: async ({ ticker, theme, minConfidence, exactConfidence, excludeKeywords }) => {
|
|
18
|
+
const db = getDb();
|
|
19
|
+
|
|
20
|
+
if (ticker) {
|
|
21
|
+
const latest = getLatestEdge(db, ticker);
|
|
22
|
+
if (!latest) return formatToolResult({ message: `No edge data found for ${ticker}` });
|
|
23
|
+
|
|
24
|
+
const history = getEdgeHistory(db, ticker, 0).slice(-10); // last 10 entries
|
|
25
|
+
return formatToolResult({
|
|
26
|
+
latest: {
|
|
27
|
+
...latest,
|
|
28
|
+
drivers: latest.drivers_json ? JSON.parse(latest.drivers_json) : [],
|
|
29
|
+
sources: latest.sources_json ? JSON.parse(latest.sources_json) : [],
|
|
30
|
+
catalysts: latest.catalysts_json ? JSON.parse(latest.catalysts_json) : [],
|
|
31
|
+
},
|
|
32
|
+
recentHistory: history.map((h) => ({
|
|
33
|
+
timestamp: h.timestamp,
|
|
34
|
+
edge: h.edge,
|
|
35
|
+
modelProb: h.model_prob,
|
|
36
|
+
marketProb: h.market_prob,
|
|
37
|
+
confidence: h.confidence,
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (theme) {
|
|
43
|
+
const themeRows = db.query(
|
|
44
|
+
`SELECT DISTINCT ticker FROM edge_history
|
|
45
|
+
WHERE event_ticker IN (SELECT event_ticker FROM events WHERE theme_id = $theme)`
|
|
46
|
+
).all({ $theme: theme }) as { ticker: string }[];
|
|
47
|
+
|
|
48
|
+
let edges = themeRows
|
|
49
|
+
.map((t) => getLatestEdge(db, t.ticker))
|
|
50
|
+
.filter((r) => r !== null)
|
|
51
|
+
.sort((a, b) => Math.abs(b!.edge) - Math.abs(a!.edge));
|
|
52
|
+
|
|
53
|
+
if (excludeKeywords?.length) {
|
|
54
|
+
edges = applyKeywordExclusion(db, edges, excludeKeywords);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return formatToolResult({ theme, edges, count: edges.length });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Default: actionable edges
|
|
61
|
+
let edges = exactConfidence
|
|
62
|
+
? getEdgesByExactConfidence(db, exactConfidence)
|
|
63
|
+
: getActionableEdges(db, minConfidence ?? 'moderate');
|
|
64
|
+
edges.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge));
|
|
65
|
+
|
|
66
|
+
if (excludeKeywords?.length) {
|
|
67
|
+
edges = applyKeywordExclusion(db, edges, excludeKeywords);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const confidenceFilter = exactConfidence
|
|
71
|
+
? { exactConfidence }
|
|
72
|
+
: { minConfidence: minConfidence ?? 'moderate' };
|
|
73
|
+
|
|
74
|
+
return formatToolResult({
|
|
75
|
+
edges: edges.slice(0, 20).map((e) => ({
|
|
76
|
+
ticker: e.ticker,
|
|
77
|
+
edge: e.edge,
|
|
78
|
+
edgePct: `${(e.edge * 100).toFixed(1)}%`,
|
|
79
|
+
modelProb: e.model_prob,
|
|
80
|
+
marketProb: e.market_prob,
|
|
81
|
+
confidence: e.confidence,
|
|
82
|
+
timestamp: e.timestamp,
|
|
83
|
+
})),
|
|
84
|
+
count: edges.length,
|
|
85
|
+
filter: confidenceFilter,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** Filter out edges whose ticker or event title matches any excluded keyword. */
|
|
91
|
+
function applyKeywordExclusion<T extends { ticker: string; event_ticker: string }>(
|
|
92
|
+
db: ReturnType<typeof getDb>,
|
|
93
|
+
edges: T[],
|
|
94
|
+
keywords: string[],
|
|
95
|
+
): T[] {
|
|
96
|
+
const lowerKeywords = keywords.map((k) => k.toLowerCase());
|
|
97
|
+
|
|
98
|
+
// Build a title lookup from event_index for all relevant event tickers
|
|
99
|
+
const eventTickers = [...new Set(edges.map((e) => e.event_ticker))];
|
|
100
|
+
const titleMap = new Map<string, string>();
|
|
101
|
+
if (eventTickers.length > 0) {
|
|
102
|
+
const placeholders = eventTickers.map(() => '?').join(', ');
|
|
103
|
+
const rows = db.query(
|
|
104
|
+
`SELECT event_ticker, title FROM event_index WHERE event_ticker IN (${placeholders})`
|
|
105
|
+
).all(...eventTickers) as { event_ticker: string; title: string }[];
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
titleMap.set(row.event_ticker, row.title.toLowerCase());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return edges.filter((e) => {
|
|
112
|
+
const tickerLower = e.ticker.toLowerCase();
|
|
113
|
+
const titleLower = titleMap.get(e.event_ticker) ?? '';
|
|
114
|
+
return !lowerKeywords.some((kw) => tickerLower.includes(kw) || titleLower.includes(kw));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const EDGE_QUERY_DESCRIPTION = `
|
|
119
|
+
Query edge signals and mispricing data from the local scan database.
|
|
120
|
+
|
|
121
|
+
## When to Use
|
|
122
|
+
- User asks about current edges, mispricings, or opportunities
|
|
123
|
+
- "What's the edge on crypto?" or "Show me high-confidence edges"
|
|
124
|
+
- Checking if a specific market has an actionable edge signal
|
|
125
|
+
- Filtering edges: "show moderate edges", "skip trump", "exclude crypto"
|
|
126
|
+
|
|
127
|
+
## When NOT to Use
|
|
128
|
+
- For live market data from Kalshi (use kalshi_search)
|
|
129
|
+
- For placing trades (use kalshi_trade)
|
|
130
|
+
|
|
131
|
+
## Parameters
|
|
132
|
+
- **minConfidence**: Returns edges at this level AND above. "high" → high + very_high.
|
|
133
|
+
- **exactConfidence**: Returns edges at ONLY this level. "moderate" → moderate only, not high/very_high. Prefer this when the user asks for a specific level like "show moderate edges".
|
|
134
|
+
- **excludeKeywords**: Filters out edges whose ticker or event title contains any keyword. Use for "skip trump", "but not crypto", etc.
|
|
135
|
+
`.trim();
|