horizon-code 0.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/assets/python/highlights.scm +137 -0
- package/assets/python/tree-sitter-python.wasm +0 -0
- package/bin/horizon.js +2 -0
- package/package.json +40 -0
- package/src/ai/client.ts +369 -0
- package/src/ai/system-prompt.ts +86 -0
- package/src/app.ts +1454 -0
- package/src/chat/messages.ts +48 -0
- package/src/chat/renderer.ts +243 -0
- package/src/chat/types.ts +18 -0
- package/src/components/code-panel.ts +329 -0
- package/src/components/footer.ts +72 -0
- package/src/components/hooks-panel.ts +224 -0
- package/src/components/input-bar.ts +193 -0
- package/src/components/mode-bar.ts +245 -0
- package/src/components/session-panel.ts +294 -0
- package/src/components/settings-panel.ts +372 -0
- package/src/components/splash.ts +156 -0
- package/src/components/strategy-panel.ts +489 -0
- package/src/components/tab-bar.ts +112 -0
- package/src/components/tutorial-panel.ts +680 -0
- package/src/components/widgets/progress-bar.ts +38 -0
- package/src/components/widgets/sparkline.ts +57 -0
- package/src/hooks/executor.ts +109 -0
- package/src/index.ts +22 -0
- package/src/keys/handler.ts +198 -0
- package/src/platform/auth.ts +36 -0
- package/src/platform/client.ts +159 -0
- package/src/platform/config.ts +121 -0
- package/src/platform/session-sync.ts +158 -0
- package/src/platform/supabase.ts +376 -0
- package/src/platform/sync.ts +149 -0
- package/src/platform/tiers.ts +103 -0
- package/src/platform/tools.ts +163 -0
- package/src/platform/types.ts +86 -0
- package/src/platform/usage.ts +224 -0
- package/src/research/apis.ts +367 -0
- package/src/research/tools.ts +205 -0
- package/src/research/widgets.ts +523 -0
- package/src/state/store.ts +256 -0
- package/src/state/types.ts +109 -0
- package/src/strategy/ascii-chart.ts +74 -0
- package/src/strategy/code-stream.ts +146 -0
- package/src/strategy/dashboard.ts +140 -0
- package/src/strategy/persistence.ts +82 -0
- package/src/strategy/prompts.ts +626 -0
- package/src/strategy/sandbox.ts +137 -0
- package/src/strategy/tools.ts +764 -0
- package/src/strategy/validator.ts +216 -0
- package/src/strategy/widgets.ts +270 -0
- package/src/syntax/setup.ts +54 -0
- package/src/theme/colors.ts +107 -0
- package/src/theme/icons.ts +27 -0
- package/src/util/hyperlink.ts +21 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Research tools — registered as AI SDK tools for the LLM to call
|
|
2
|
+
import { tool } from "ai";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
gammaEvents, gammaEventDetail, clobPriceHistory, clobOrderBook,
|
|
6
|
+
polymarketTrades, kalshiEvents, exaSearch, exaSentiment,
|
|
7
|
+
calaSearch, evCalculator, probabilityCalc,
|
|
8
|
+
} from "./apis.ts";
|
|
9
|
+
|
|
10
|
+
const t = tool as any;
|
|
11
|
+
|
|
12
|
+
export const researchTools: Record<string, any> = {
|
|
13
|
+
polymarketEvents: t({
|
|
14
|
+
description: "Browse and discover prediction markets on Polymarket. Use when user asks about markets, trending topics, or wants to explore.",
|
|
15
|
+
parameters: z.object({
|
|
16
|
+
query: z.string().optional().describe("Search tag: crypto, politics, sports, finance, etc."),
|
|
17
|
+
limit: z.number().optional().describe("Number of results (1-20, default 10)"),
|
|
18
|
+
}),
|
|
19
|
+
execute: async (args: any) => gammaEvents(args),
|
|
20
|
+
}),
|
|
21
|
+
|
|
22
|
+
polymarketEventDetail: t({
|
|
23
|
+
description: "Get detailed info on a specific Polymarket event including price history. Requires a slug from polymarketEvents.",
|
|
24
|
+
parameters: z.object({
|
|
25
|
+
slug: z.string().describe("Event slug from prior polymarketEvents results"),
|
|
26
|
+
}),
|
|
27
|
+
execute: async (args: any) => gammaEventDetail(args.slug),
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
polymarketPriceHistory: t({
|
|
31
|
+
description: "Get price history for a market with specific time range. Good for trend analysis.",
|
|
32
|
+
parameters: z.object({
|
|
33
|
+
slug: z.string().describe("Event slug"),
|
|
34
|
+
interval: z.string().optional().describe("Time range: 1h, 6h, 1d, 1w, 1m, max (default 1w)"),
|
|
35
|
+
fidelity: z.number().optional().describe("Resolution in minutes: 1, 5, 15, 60 (default 60)"),
|
|
36
|
+
}),
|
|
37
|
+
execute: async (args: any) => clobPriceHistory(args.slug, args.interval, args.fidelity),
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
polymarketOrderBook: t({
|
|
41
|
+
description: "Get order book depth, bid/ask levels, and spread analysis for a market.",
|
|
42
|
+
parameters: z.object({
|
|
43
|
+
slug: z.string().describe("Event slug"),
|
|
44
|
+
marketIndex: z.number().optional().describe("Sub-market index (default 0)"),
|
|
45
|
+
outcomeIndex: z.number().optional().describe("0=Yes, 1=No (default 0)"),
|
|
46
|
+
}),
|
|
47
|
+
execute: async (args: any) => clobOrderBook(args.slug, args.marketIndex, args.outcomeIndex),
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
whaleTracker: t({
|
|
51
|
+
description: "Track large trades and top holders for a market. Shows smart money flows.",
|
|
52
|
+
parameters: z.object({
|
|
53
|
+
slug: z.string().describe("Event slug"),
|
|
54
|
+
}),
|
|
55
|
+
execute: async (args: any) => polymarketTrades(args.slug),
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
newsSentiment: t({
|
|
59
|
+
description: "Analyze news sentiment for a market topic. Returns bullish/bearish score and relevant articles.",
|
|
60
|
+
parameters: z.object({
|
|
61
|
+
slug: z.string().describe("Event slug"),
|
|
62
|
+
}),
|
|
63
|
+
execute: async (args: any) => exaSentiment(args.slug),
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
webSearch: t({
|
|
67
|
+
description: "Search the internet for current events, breaking news, or facts. Use for general questions not specific to prediction markets.",
|
|
68
|
+
parameters: z.object({
|
|
69
|
+
query: z.string().describe("Search query"),
|
|
70
|
+
}),
|
|
71
|
+
execute: async (args: any) => exaSearch(args.query),
|
|
72
|
+
}),
|
|
73
|
+
|
|
74
|
+
kalshiEvents: t({
|
|
75
|
+
description: "Fetch active prediction markets from Kalshi. Good for cross-platform comparison.",
|
|
76
|
+
parameters: z.object({
|
|
77
|
+
query: z.string().optional().describe("Series ticker filter (e.g. KXBTC, KXFED)"),
|
|
78
|
+
limit: z.number().optional().describe("Number of results (1-20, default 10)"),
|
|
79
|
+
}),
|
|
80
|
+
execute: async (args: any) => kalshiEvents(args),
|
|
81
|
+
}),
|
|
82
|
+
|
|
83
|
+
evCalculator: t({
|
|
84
|
+
description: "Calculate expected value (EV) and Kelly criterion position sizing for a market.",
|
|
85
|
+
parameters: z.object({
|
|
86
|
+
slug: z.string().describe("Event slug"),
|
|
87
|
+
side: z.enum(["yes", "no"]).describe("Which side to analyze"),
|
|
88
|
+
estimatedEdge: z.number().describe("Your estimated edge (0.01-0.50, e.g. 0.05 = 5%)"),
|
|
89
|
+
bankroll: z.number().optional().describe("Your bankroll in dollars (default 1000)"),
|
|
90
|
+
}),
|
|
91
|
+
execute: async (args: any) => {
|
|
92
|
+
const event = await gammaEventDetail(args.slug);
|
|
93
|
+
const market = event.markets?.[0];
|
|
94
|
+
if (!market) return { error: "Market not found" };
|
|
95
|
+
const price = args.side === "yes" ? parseFloat(market.yesPrice ?? "0.5") : parseFloat(market.noPrice ?? "0.5");
|
|
96
|
+
return {
|
|
97
|
+
...evCalculator({ marketPrice: price, side: args.side, estimatedEdge: args.estimatedEdge, bankroll: args.bankroll ?? 1000 }),
|
|
98
|
+
title: event.title, slug: args.slug, url: event.url,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
probabilityCalculator: t({
|
|
104
|
+
description: "Calculate conditional probabilities P(A|B), joint probability between two markets.",
|
|
105
|
+
parameters: z.object({
|
|
106
|
+
slugA: z.string().describe("Slug of market A"),
|
|
107
|
+
slugB: z.string().describe("Slug of market B"),
|
|
108
|
+
}),
|
|
109
|
+
execute: async (args: any) => {
|
|
110
|
+
const [a, b] = await Promise.all([gammaEventDetail(args.slugA), gammaEventDetail(args.slugB)]);
|
|
111
|
+
const pA = parseFloat(a.markets?.[0]?.yesPrice ?? "0.5");
|
|
112
|
+
const pB = parseFloat(b.markets?.[0]?.yesPrice ?? "0.5");
|
|
113
|
+
return {
|
|
114
|
+
marketA: { title: a.title, slug: a.slug, probability: pA, url: a.url },
|
|
115
|
+
marketB: { title: b.title, slug: b.slug, probability: pB, url: b.url },
|
|
116
|
+
...probabilityCalc(pA, pB),
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
calaKnowledge: t({
|
|
122
|
+
description: "Search verified knowledge about companies, people, products, industries. Use for background research, NOT breaking news.",
|
|
123
|
+
parameters: z.object({
|
|
124
|
+
query: z.string().describe("Knowledge query about an entity, company, or topic"),
|
|
125
|
+
}),
|
|
126
|
+
execute: async (args: any) => calaSearch(args.query),
|
|
127
|
+
}),
|
|
128
|
+
|
|
129
|
+
historicalVolatility: t({
|
|
130
|
+
description: "Calculate realized volatility, Bollinger bands, and volatility regime for a market.",
|
|
131
|
+
parameters: z.object({
|
|
132
|
+
slug: z.string().describe("Event slug"),
|
|
133
|
+
interval: z.string().optional().describe("Time range: 1d, 1w, 1m, max (default 1w)"),
|
|
134
|
+
}),
|
|
135
|
+
execute: async (args: any) => {
|
|
136
|
+
const data = await clobPriceHistory(args.slug, args.interval ?? "1w", 60);
|
|
137
|
+
const prices = data.priceHistory.map((p: any) => p.p);
|
|
138
|
+
if (prices.length < 3) return { ...data, realizedVolatility: 0, regime: "insufficient data" };
|
|
139
|
+
|
|
140
|
+
// Log returns
|
|
141
|
+
const returns: number[] = [];
|
|
142
|
+
for (let i = 1; i < prices.length; i++) {
|
|
143
|
+
if (prices[i - 1] > 0) returns.push(Math.log(prices[i] / prices[i - 1]));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
|
147
|
+
const variance = returns.reduce((a, b) => a + (b - mean) ** 2, 0) / returns.length;
|
|
148
|
+
const stdDev = Math.sqrt(variance);
|
|
149
|
+
const annualized = +(stdDev * Math.sqrt(365 * 24)).toFixed(4);
|
|
150
|
+
|
|
151
|
+
// Regime
|
|
152
|
+
const recentReturns = returns.slice(-20);
|
|
153
|
+
const recentVar = recentReturns.reduce((a, b) => a + (b - mean) ** 2, 0) / recentReturns.length;
|
|
154
|
+
const recentVol = Math.sqrt(recentVar);
|
|
155
|
+
const regime = recentVol > stdDev * 1.5 ? "high" : recentVol < stdDev * 0.5 ? "low" : "normal";
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
...data,
|
|
159
|
+
realizedVolatility: annualized,
|
|
160
|
+
dailyStdDev: +stdDev.toFixed(6),
|
|
161
|
+
volatilityRegime: regime,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
|
|
166
|
+
compareMarkets: t({
|
|
167
|
+
description: "Compare pricing between Polymarket and Kalshi for arbitrage or cross-platform analysis.",
|
|
168
|
+
parameters: z.object({
|
|
169
|
+
topic: z.string().describe("Topic to compare across platforms (e.g. bitcoin, election, fed)"),
|
|
170
|
+
}),
|
|
171
|
+
execute: async (args: any) => {
|
|
172
|
+
const [poly, kal] = await Promise.all([
|
|
173
|
+
gammaEvents({ query: args.topic, limit: 10 }),
|
|
174
|
+
kalshiEvents({ query: args.topic, limit: 10 }),
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
// Simple word-overlap matching
|
|
178
|
+
const comparisons: any[] = [];
|
|
179
|
+
for (const p of poly) {
|
|
180
|
+
const pWords = new Set(p.title.toLowerCase().split(/\s+/).filter((w: string) => w.length > 3));
|
|
181
|
+
for (const k of kal) {
|
|
182
|
+
const kWords = k.title.toLowerCase().split(/\s+/).filter((w: string) => w.length > 3);
|
|
183
|
+
const overlap = kWords.filter((w: string) => pWords.has(w)).length;
|
|
184
|
+
const score = overlap / Math.max(pWords.size, kWords.length);
|
|
185
|
+
if (score > 0.3) {
|
|
186
|
+
const polyPrice = parseFloat(p.markets?.[0]?.yesPrice ?? "0");
|
|
187
|
+
const kalPrice = k.markets?.[0]?.yesBid ?? 0;
|
|
188
|
+
comparisons.push({
|
|
189
|
+
polymarket: { title: p.title, url: p.url, yesPrice: polyPrice },
|
|
190
|
+
kalshi: { title: k.title, url: k.url, yesPrice: kalPrice },
|
|
191
|
+
priceDiff: polyPrice && kalPrice ? +(polyPrice - kalPrice / 100).toFixed(4) : null,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
polymarketEvents: poly.slice(0, 5).map((p: any) => ({ title: p.title, slug: p.slug, url: p.url, yesPrice: p.markets?.[0]?.yesPrice, volume: p.volume })),
|
|
199
|
+
kalshiEvents: kal.slice(0, 5).map((k: any) => ({ title: k.title, ticker: k.ticker, url: k.url, yesBid: k.markets?.[0]?.yesBid, volume: k.markets?.[0]?.volume })),
|
|
200
|
+
comparisons: comparisons.slice(0, 5),
|
|
201
|
+
topic: args.topic,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
}),
|
|
205
|
+
};
|