opentradex 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/.env.example +8 -0
- package/CLAUDE.md +98 -0
- package/README.md +246 -0
- package/SOUL.md +79 -0
- package/SPEC.md +317 -0
- package/SUBMISSION.md +30 -0
- package/architecture.excalidraw +170 -0
- package/architecture.png +0 -0
- package/bin/opentradex.mjs +4 -0
- package/data/.gitkeep +0 -0
- package/data/strategy_notes.md +158 -0
- package/gossip/__init__.py +0 -0
- package/gossip/dashboard.py +150 -0
- package/gossip/db.py +358 -0
- package/gossip/kalshi.py +492 -0
- package/gossip/news.py +235 -0
- package/gossip/trader.py +646 -0
- package/main.py +287 -0
- package/package.json +47 -0
- package/requirements.txt +7 -0
- package/src/cli.mjs +124 -0
- package/src/index.mjs +420 -0
- package/web/AGENTS.md +5 -0
- package/web/CLAUDE.md +1 -0
- package/web/README.md +36 -0
- package/web/components.json +25 -0
- package/web/eslint.config.mjs +18 -0
- package/web/next.config.ts +7 -0
- package/web/package-lock.json +11626 -0
- package/web/package.json +37 -0
- package/web/postcss.config.mjs +7 -0
- package/web/public/file.svg +1 -0
- package/web/public/globe.svg +1 -0
- package/web/public/next.svg +1 -0
- package/web/public/vercel.svg +1 -0
- package/web/public/window.svg +1 -0
- package/web/src/app/api/agent/route.ts +77 -0
- package/web/src/app/api/agent/stream/route.ts +87 -0
- package/web/src/app/api/markets/route.ts +15 -0
- package/web/src/app/api/news/live/route.ts +77 -0
- package/web/src/app/api/news/reddit/route.ts +118 -0
- package/web/src/app/api/news/route.ts +10 -0
- package/web/src/app/api/news/tiktok/route.ts +115 -0
- package/web/src/app/api/news/truthsocial/route.ts +116 -0
- package/web/src/app/api/news/twitter/route.ts +186 -0
- package/web/src/app/api/portfolio/route.ts +50 -0
- package/web/src/app/api/prices/route.ts +18 -0
- package/web/src/app/api/trades/route.ts +10 -0
- package/web/src/app/favicon.ico +0 -0
- package/web/src/app/globals.css +170 -0
- package/web/src/app/layout.tsx +36 -0
- package/web/src/app/page.tsx +366 -0
- package/web/src/components/AgentLog.tsx +71 -0
- package/web/src/components/LiveStream.tsx +394 -0
- package/web/src/components/MarketScanner.tsx +111 -0
- package/web/src/components/NewsFeed.tsx +561 -0
- package/web/src/components/PortfolioStrip.tsx +139 -0
- package/web/src/components/PositionsPanel.tsx +219 -0
- package/web/src/components/TopBar.tsx +127 -0
- package/web/src/components/ui/badge.tsx +52 -0
- package/web/src/components/ui/button.tsx +60 -0
- package/web/src/components/ui/card.tsx +103 -0
- package/web/src/components/ui/scroll-area.tsx +55 -0
- package/web/src/components/ui/separator.tsx +25 -0
- package/web/src/components/ui/tabs.tsx +82 -0
- package/web/src/components/ui/tooltip.tsx +66 -0
- package/web/src/lib/db.ts +81 -0
- package/web/src/lib/types.ts +130 -0
- package/web/src/lib/utils.ts +6 -0
- package/web/tsconfig.json +34 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Tabs({
|
|
9
|
+
className,
|
|
10
|
+
orientation = "horizontal",
|
|
11
|
+
...props
|
|
12
|
+
}: TabsPrimitive.Root.Props) {
|
|
13
|
+
return (
|
|
14
|
+
<TabsPrimitive.Root
|
|
15
|
+
data-slot="tabs"
|
|
16
|
+
data-orientation={orientation}
|
|
17
|
+
className={cn(
|
|
18
|
+
"group/tabs flex gap-2 data-horizontal:flex-col",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tabsListVariants = cva(
|
|
27
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
|
28
|
+
{
|
|
29
|
+
variants: {
|
|
30
|
+
variant: {
|
|
31
|
+
default: "bg-muted",
|
|
32
|
+
line: "gap-1 bg-transparent",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "default",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
function TabsList({
|
|
42
|
+
className,
|
|
43
|
+
variant = "default",
|
|
44
|
+
...props
|
|
45
|
+
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
|
|
46
|
+
return (
|
|
47
|
+
<TabsPrimitive.List
|
|
48
|
+
data-slot="tabs-list"
|
|
49
|
+
data-variant={variant}
|
|
50
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
57
|
+
return (
|
|
58
|
+
<TabsPrimitive.Tab
|
|
59
|
+
data-slot="tabs-trigger"
|
|
60
|
+
className={cn(
|
|
61
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
62
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
63
|
+
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
64
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
73
|
+
return (
|
|
74
|
+
<TabsPrimitive.Panel
|
|
75
|
+
data-slot="tabs-content"
|
|
76
|
+
className={cn("flex-1 text-sm outline-none", className)}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
function TooltipProvider({
|
|
8
|
+
delay = 0,
|
|
9
|
+
...props
|
|
10
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TooltipPrimitive.Provider
|
|
13
|
+
data-slot="tooltip-provider"
|
|
14
|
+
delay={delay}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
|
21
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
|
|
25
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipContent({
|
|
29
|
+
className,
|
|
30
|
+
side = "top",
|
|
31
|
+
sideOffset = 4,
|
|
32
|
+
align = "center",
|
|
33
|
+
alignOffset = 0,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: TooltipPrimitive.Popup.Props &
|
|
37
|
+
Pick<
|
|
38
|
+
TooltipPrimitive.Positioner.Props,
|
|
39
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
40
|
+
>) {
|
|
41
|
+
return (
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<TooltipPrimitive.Positioner
|
|
44
|
+
align={align}
|
|
45
|
+
alignOffset={alignOffset}
|
|
46
|
+
side={side}
|
|
47
|
+
sideOffset={sideOffset}
|
|
48
|
+
className="isolate z-50"
|
|
49
|
+
>
|
|
50
|
+
<TooltipPrimitive.Popup
|
|
51
|
+
data-slot="tooltip-content"
|
|
52
|
+
className={cn(
|
|
53
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
|
60
|
+
</TooltipPrimitive.Popup>
|
|
61
|
+
</TooltipPrimitive.Positioner>
|
|
62
|
+
</TooltipPrimitive.Portal>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const DB_PATH = path.join(process.cwd(), "..", "data", "gossip.db");
|
|
5
|
+
|
|
6
|
+
export function getDb(): Database.Database {
|
|
7
|
+
// Fresh connection each request so we always see the latest writes from the Python agent
|
|
8
|
+
const db = new Database(DB_PATH, { readonly: true });
|
|
9
|
+
db.pragma("journal_mode = WAL");
|
|
10
|
+
return db;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Trade {
|
|
14
|
+
id: number;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
ticker: string;
|
|
17
|
+
title: string;
|
|
18
|
+
category: string;
|
|
19
|
+
side: string;
|
|
20
|
+
action: string;
|
|
21
|
+
contracts: number;
|
|
22
|
+
entry_price: number;
|
|
23
|
+
cost: number;
|
|
24
|
+
fee: number;
|
|
25
|
+
estimated_prob: number;
|
|
26
|
+
edge: number;
|
|
27
|
+
confidence: string;
|
|
28
|
+
reasoning: string;
|
|
29
|
+
news_trigger: string;
|
|
30
|
+
sources: string;
|
|
31
|
+
settled: number;
|
|
32
|
+
outcome: string;
|
|
33
|
+
pnl: number;
|
|
34
|
+
exit_price: number | null;
|
|
35
|
+
exit_reasoning: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Portfolio {
|
|
39
|
+
bankroll: number;
|
|
40
|
+
total_pnl: number;
|
|
41
|
+
total_trades: number;
|
|
42
|
+
wins: number;
|
|
43
|
+
losses: number;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface NewsArticle {
|
|
48
|
+
id: number;
|
|
49
|
+
timestamp: string;
|
|
50
|
+
source: string;
|
|
51
|
+
keyword: string;
|
|
52
|
+
title: string;
|
|
53
|
+
url: string;
|
|
54
|
+
snippet: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface MarketSnapshot {
|
|
58
|
+
id: number;
|
|
59
|
+
timestamp: string;
|
|
60
|
+
ticker: string;
|
|
61
|
+
title: string;
|
|
62
|
+
category: string;
|
|
63
|
+
yes_bid: number;
|
|
64
|
+
yes_ask: number;
|
|
65
|
+
mid: number;
|
|
66
|
+
volume: number;
|
|
67
|
+
open_interest: number;
|
|
68
|
+
close_time: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface AgentCycle {
|
|
72
|
+
id: number;
|
|
73
|
+
timestamp: string;
|
|
74
|
+
session_id: string;
|
|
75
|
+
duration_s: number;
|
|
76
|
+
status: string;
|
|
77
|
+
markets_scanned: number;
|
|
78
|
+
news_scraped: number;
|
|
79
|
+
trades_made: number;
|
|
80
|
+
output_summary: string;
|
|
81
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export interface Portfolio {
|
|
2
|
+
bankroll: number;
|
|
3
|
+
total_pnl: number;
|
|
4
|
+
total_trades: number;
|
|
5
|
+
wins: number;
|
|
6
|
+
losses: number;
|
|
7
|
+
open_positions: Trade[];
|
|
8
|
+
total_news: number;
|
|
9
|
+
total_snapshots: number;
|
|
10
|
+
total_cycles: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Trade {
|
|
14
|
+
id: number;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
ticker: string;
|
|
17
|
+
title: string;
|
|
18
|
+
category: string;
|
|
19
|
+
side: string;
|
|
20
|
+
action: string;
|
|
21
|
+
contracts: number;
|
|
22
|
+
entry_price: number;
|
|
23
|
+
cost: number;
|
|
24
|
+
fee: number;
|
|
25
|
+
estimated_prob: number;
|
|
26
|
+
edge: number;
|
|
27
|
+
confidence: string;
|
|
28
|
+
reasoning: string;
|
|
29
|
+
news_trigger: string;
|
|
30
|
+
sources: string;
|
|
31
|
+
settled: number;
|
|
32
|
+
outcome: string;
|
|
33
|
+
pnl: number;
|
|
34
|
+
exit_price: number | null;
|
|
35
|
+
exit_reasoning: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface NewsArticle {
|
|
39
|
+
id: number;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
source: string;
|
|
42
|
+
keyword: string;
|
|
43
|
+
title: string;
|
|
44
|
+
url: string;
|
|
45
|
+
snippet: string;
|
|
46
|
+
image?: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Market {
|
|
50
|
+
id: number;
|
|
51
|
+
timestamp: string;
|
|
52
|
+
ticker: string;
|
|
53
|
+
title: string;
|
|
54
|
+
category: string;
|
|
55
|
+
yes_bid: number;
|
|
56
|
+
yes_ask: number;
|
|
57
|
+
mid: number;
|
|
58
|
+
volume: number;
|
|
59
|
+
open_interest: number;
|
|
60
|
+
close_time: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AgentCycle {
|
|
64
|
+
id: number;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
session_id: string;
|
|
67
|
+
duration_s: number;
|
|
68
|
+
status: string;
|
|
69
|
+
markets_scanned: number;
|
|
70
|
+
news_scraped: number;
|
|
71
|
+
trades_made: number;
|
|
72
|
+
output_summary: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PositionPrice {
|
|
76
|
+
ticker: string;
|
|
77
|
+
title: string;
|
|
78
|
+
side: string;
|
|
79
|
+
contracts: number;
|
|
80
|
+
entry_price: number;
|
|
81
|
+
mark_price: number;
|
|
82
|
+
mid: number;
|
|
83
|
+
cost: number;
|
|
84
|
+
current_value: number;
|
|
85
|
+
unrealized_pnl: number;
|
|
86
|
+
pnl_pct: number;
|
|
87
|
+
status: string;
|
|
88
|
+
result: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SocialPost {
|
|
92
|
+
id: string;
|
|
93
|
+
text: string;
|
|
94
|
+
author: string;
|
|
95
|
+
authorName: string;
|
|
96
|
+
authorImage?: string;
|
|
97
|
+
likes: number;
|
|
98
|
+
reposts: number;
|
|
99
|
+
replies?: number;
|
|
100
|
+
url: string;
|
|
101
|
+
timestamp: string;
|
|
102
|
+
platform: "twitter" | "truthsocial" | "reddit" | "tiktok";
|
|
103
|
+
images?: string[];
|
|
104
|
+
videoCover?: string;
|
|
105
|
+
videoDuration?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface StreamLine {
|
|
109
|
+
type: string;
|
|
110
|
+
text?: string;
|
|
111
|
+
tool?: string;
|
|
112
|
+
input?: string;
|
|
113
|
+
result?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function kalshiUrl(ticker: string, title?: string): string {
|
|
117
|
+
const parts = ticker.split("-");
|
|
118
|
+
const event = parts[0].toLowerCase();
|
|
119
|
+
const full = ticker.toLowerCase();
|
|
120
|
+
const slug = title
|
|
121
|
+
? title
|
|
122
|
+
.toLowerCase()
|
|
123
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
124
|
+
.replace(/\s+/g, "-")
|
|
125
|
+
.replace(/-+/g, "-")
|
|
126
|
+
.replace(/^-|-$/g, "")
|
|
127
|
+
.slice(0, 60)
|
|
128
|
+
: event;
|
|
129
|
+
return `https://kalshi.com/markets/${event}/${slug}/${full}`;
|
|
130
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|