opentradex 0.1.0 → 0.1.3
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 +43 -4
- package/README.md +43 -11
- package/gossip/polymarket.py +196 -0
- package/main.py +103 -25
- package/package.json +8 -4
- package/scripts/install.ps1 +25 -0
- package/scripts/install.sh +29 -0
- package/src/catalog.mjs +146 -0
- package/src/cli.mjs +25 -8
- package/src/index.mjs +576 -53
- package/web/next-env.d.ts +6 -0
- package/web/next.config.ts +7 -1
- package/web/public/install.ps1 +25 -0
- package/web/public/install.sh +29 -0
- package/web/src/app/api/agent/route.ts +25 -5
- package/web/src/app/api/agent/stream/route.ts +13 -0
- package/web/src/app/api/markets/route.ts +21 -10
- package/web/src/app/api/news/route.ts +14 -5
- package/web/src/app/api/portfolio/route.ts +6 -2
- package/web/src/app/api/trades/route.ts +14 -5
- package/web/src/app/dashboard/page.tsx +5 -0
- package/web/src/app/globals.css +92 -32
- package/web/src/app/layout.tsx +8 -7
- package/web/src/app/page.tsx +790 -348
- package/web/src/components/DashboardApp.tsx +362 -0
- package/web/src/components/TopBar.tsx +1 -1
- package/web/src/lib/db.ts +29 -3
- package/web/src/lib/demo-data.ts +299 -0
package/.env.example
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# Welcome to OpenTradex
|
|
2
|
+
# Our implementation. Your strategy.
|
|
3
|
+
|
|
4
|
+
# Runtime profile
|
|
5
|
+
OPENTRADEX_RUNTIME=claude-code
|
|
6
|
+
OPENTRADEX_PACKAGE_MANAGER=npm
|
|
7
|
+
OPENTRADEX_EXECUTION_MODE=paper
|
|
8
|
+
OPENTRADEX_PRIMARY_MARKET=kalshi
|
|
9
|
+
OPENTRADEX_ENABLED_MARKETS=kalshi
|
|
10
|
+
OPENTRADEX_ENABLED_INTEGRATIONS=apify,rss
|
|
11
|
+
OPENTRADEX_LIVE_EXECUTION_MARKET=kalshi
|
|
12
|
+
NEWS_PROVIDER=apify,rss
|
|
13
|
+
|
|
14
|
+
# Risk configuration
|
|
5
15
|
BANKROLL=30.00
|
|
6
16
|
MIN_EDGE=0.10
|
|
7
17
|
MAX_POSITION_PCT=0.30
|
|
8
18
|
CYCLE_INTERVAL=900
|
|
19
|
+
LIVE_TRADING=false
|
|
20
|
+
|
|
21
|
+
# Kalshi
|
|
22
|
+
KALSHI_API_KEY_ID=
|
|
23
|
+
KALSHI_PRIVATE_KEY_PATH=
|
|
24
|
+
KALSHI_USE_DEMO=true
|
|
25
|
+
|
|
26
|
+
# Polymarket
|
|
27
|
+
POLYMARKET_GAMMA_URL=https://gamma-api.polymarket.com
|
|
28
|
+
POLYMARKET_WALLET_ADDRESS=
|
|
29
|
+
POLYMARKET_PRIVATE_KEY=
|
|
30
|
+
|
|
31
|
+
# TradingView
|
|
32
|
+
TRADINGVIEW_USERNAME=
|
|
33
|
+
TRADINGVIEW_PASSWORD=
|
|
34
|
+
TRADINGVIEW_WATCHLIST=SPY,QQQ,BTCUSD,NQ1!
|
|
35
|
+
|
|
36
|
+
# Robinhood and Groww
|
|
37
|
+
ROBINHOOD_USERNAME=
|
|
38
|
+
ROBINHOOD_PASSWORD=
|
|
39
|
+
ROBINHOOD_MFA_CODE=
|
|
40
|
+
GROWW_ACCESS_TOKEN=
|
|
41
|
+
|
|
42
|
+
# Optional LLM provider secrets
|
|
43
|
+
OPENAI_API_KEY=
|
|
44
|
+
GEMINI_API_KEY=
|
|
45
|
+
|
|
46
|
+
# Data integrations
|
|
47
|
+
APIFY_API_TOKEN=
|
package/README.md
CHANGED
|
@@ -1,35 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenTradex
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Our implementation. Your strategy.**
|
|
4
|
+
|
|
5
|
+
OpenTradex is an open-source onboarding and execution layer for AI-assisted trading workflows. It helps you choose a runtime, wire market rails, connect optional data APIs, and launch a live dashboard without pretending to own your strategy.
|
|
4
6
|
|
|
5
7
|
Preferred setup: run `opentradex onboard`.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Welcome to OpenTradex:
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
#
|
|
13
|
-
npm install -g opentradex
|
|
14
|
-
|
|
15
|
-
# guided setup
|
|
14
|
+
# npm
|
|
15
|
+
npm install -g opentradex@latest
|
|
16
16
|
opentradex onboard
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Alternative entry points:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# npx
|
|
23
|
+
npx opentradex@latest onboard
|
|
24
|
+
|
|
25
|
+
# bunx
|
|
26
|
+
bunx opentradex@latest onboard
|
|
27
|
+
|
|
28
|
+
# curl bootstrap
|
|
29
|
+
curl -fsSL https://opentradex.vercel.app/install.sh | bash
|
|
30
|
+
```
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
`opentradex onboard` creates a workspace, writes a grouped `.env`, saves an `opentradex.config.json` workspace profile, optionally installs Python and web dependencies, and stores your default workspace in `~/.opentradex/config.json`.
|
|
33
|
+
|
|
34
|
+
During onboarding you can choose:
|
|
35
|
+
|
|
36
|
+
- Your agent runtime profile
|
|
37
|
+
- Your primary market rail
|
|
38
|
+
- Extra rails like `polymarket`, `tradingview`, `robinhood`, or `groww`
|
|
39
|
+
- Optional data integrations like `apify`, `rss`, `reddit`, `twitter`, `truthsocial`, and `tiktok`
|
|
40
|
+
- Your package manager for local web workflows
|
|
22
41
|
|
|
23
42
|
### CLI Commands
|
|
24
43
|
|
|
25
44
|
```bash
|
|
26
45
|
opentradex onboard # guided setup
|
|
27
|
-
opentradex doctor # verify
|
|
46
|
+
opentradex doctor # verify runtime, packages, env config, and rails
|
|
47
|
+
opentradex providers # show supported runtime, market, and data rails
|
|
28
48
|
opentradex start # run the continuous trading loop
|
|
29
49
|
opentradex cycle --rationale "..." # run one cycle or research a thesis
|
|
30
50
|
opentradex web # launch the Next.js dashboard
|
|
31
51
|
```
|
|
32
52
|
|
|
53
|
+
## Current Rail Support
|
|
54
|
+
|
|
55
|
+
| Rail | Current role |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Kalshi | Best live execution path |
|
|
58
|
+
| Polymarket | Public market discovery and comparison |
|
|
59
|
+
| TradingView | Watchlist and chart context |
|
|
60
|
+
| Robinhood | Broker profile placeholder |
|
|
61
|
+
| Groww | Broker profile placeholder |
|
|
62
|
+
|
|
63
|
+
Kalshi is still the strongest live execution rail today. The others are intentionally exposed as discovery/profile rails so you can build the workflow now and decide later what deserves real execution adapters.
|
|
64
|
+
|
|
33
65
|
---
|
|
34
66
|
|
|
35
67
|
## How It Works
|
|
@@ -229,7 +261,7 @@ cd web && npm run dev
|
|
|
229
261
|
# → http://localhost:3000
|
|
230
262
|
```
|
|
231
263
|
|
|
232
|
-
If you want the simpler package flow instead, use `npm install -g opentradex` and then `opentradex onboard`.
|
|
264
|
+
If you want the simpler package flow instead, use `npm install -g opentradex@latest` and then `opentradex onboard`.
|
|
233
265
|
|
|
234
266
|
## Stack
|
|
235
267
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket discovery rail.
|
|
3
|
+
|
|
4
|
+
CLI usage:
|
|
5
|
+
python3 gossip/polymarket.py scan --limit 40
|
|
6
|
+
python3 gossip/polymarket.py search "bitcoin"
|
|
7
|
+
python3 gossip/polymarket.py market <id-or-slug>
|
|
8
|
+
|
|
9
|
+
This module intentionally focuses on public Gamma API discovery so the agent can
|
|
10
|
+
compare Polymarket pricing with other rails without requiring a wallet up front.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from urllib.parse import urlencode
|
|
21
|
+
from urllib.request import Request, urlopen
|
|
22
|
+
|
|
23
|
+
from dotenv import load_dotenv
|
|
24
|
+
|
|
25
|
+
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
|
26
|
+
|
|
27
|
+
BASE_URL = os.getenv("POLYMARKET_GAMMA_URL", "https://gamma-api.polymarket.com").rstrip("/")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def log(message: str) -> None:
|
|
31
|
+
print(message, file=sys.stderr)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def api_get(path: str, params: dict | None = None):
|
|
35
|
+
url = f"{BASE_URL}{path}"
|
|
36
|
+
if params:
|
|
37
|
+
url = f"{url}?{urlencode(params)}"
|
|
38
|
+
|
|
39
|
+
request = Request(
|
|
40
|
+
url,
|
|
41
|
+
headers={
|
|
42
|
+
"User-Agent": "OpenTradex/0.1 (+https://github.com/deonmenezes/open-trademaxxxing)",
|
|
43
|
+
"Accept": "application/json",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
with urlopen(request, timeout=20) as response:
|
|
48
|
+
return json.loads(response.read().decode("utf-8"))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_outcome_prices(raw_prices: str | list | None) -> tuple[float | None, float | None]:
|
|
52
|
+
if not raw_prices:
|
|
53
|
+
return None, None
|
|
54
|
+
|
|
55
|
+
prices = raw_prices
|
|
56
|
+
if isinstance(raw_prices, str):
|
|
57
|
+
try:
|
|
58
|
+
prices = json.loads(raw_prices)
|
|
59
|
+
except json.JSONDecodeError:
|
|
60
|
+
return None, None
|
|
61
|
+
|
|
62
|
+
if not isinstance(prices, list) or len(prices) < 2:
|
|
63
|
+
return None, None
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
return float(prices[0]), float(prices[1])
|
|
67
|
+
except (TypeError, ValueError):
|
|
68
|
+
return None, None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def simplify_market(market: dict) -> dict:
|
|
72
|
+
yes_price, no_price = parse_outcome_prices(market.get("outcomePrices"))
|
|
73
|
+
event_title = ""
|
|
74
|
+
if market.get("events"):
|
|
75
|
+
event_title = market["events"][0].get("title", "")
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"id": market.get("id"),
|
|
79
|
+
"slug": market.get("slug"),
|
|
80
|
+
"question": market.get("question"),
|
|
81
|
+
"event_title": event_title,
|
|
82
|
+
"description": market.get("description", "")[:700],
|
|
83
|
+
"end_date": market.get("endDate"),
|
|
84
|
+
"yes_price": yes_price,
|
|
85
|
+
"no_price": no_price,
|
|
86
|
+
"volume": float(market.get("volumeNum") or market.get("volume") or 0),
|
|
87
|
+
"liquidity": float(market.get("liquidityNum") or market.get("liquidity") or 0),
|
|
88
|
+
"volume_24h": float(market.get("volume24hr") or 0),
|
|
89
|
+
"active": bool(market.get("active")),
|
|
90
|
+
"closed": bool(market.get("closed")),
|
|
91
|
+
"accepting_orders": bool(market.get("acceptingOrders")),
|
|
92
|
+
"url": f"https://polymarket.com/event/{market.get('slug')}" if market.get("slug") else "",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def scan_markets(limit: int = 40, min_volume: float = 1000, min_liquidity: float = 1000) -> list[dict]:
|
|
97
|
+
data = await api_get(
|
|
98
|
+
"/markets",
|
|
99
|
+
{
|
|
100
|
+
"active": "true",
|
|
101
|
+
"closed": "false",
|
|
102
|
+
"limit": max(limit * 3, 120),
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
markets = [simplify_market(m) for m in data]
|
|
106
|
+
filtered = [
|
|
107
|
+
market
|
|
108
|
+
for market in markets
|
|
109
|
+
if market["volume"] >= min_volume and market["liquidity"] >= min_liquidity
|
|
110
|
+
]
|
|
111
|
+
filtered.sort(key=lambda market: market["volume"], reverse=True)
|
|
112
|
+
return filtered[:limit]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def search_markets(query: str, limit: int = 20) -> list[dict]:
|
|
116
|
+
data = await api_get(
|
|
117
|
+
"/markets",
|
|
118
|
+
{
|
|
119
|
+
"active": "true",
|
|
120
|
+
"closed": "false",
|
|
121
|
+
"limit": 300,
|
|
122
|
+
},
|
|
123
|
+
)
|
|
124
|
+
needle = query.lower()
|
|
125
|
+
matched = []
|
|
126
|
+
for market in data:
|
|
127
|
+
haystack = " ".join(
|
|
128
|
+
[
|
|
129
|
+
str(market.get("question", "")),
|
|
130
|
+
str(market.get("description", "")),
|
|
131
|
+
str(market.get("slug", "")),
|
|
132
|
+
str(market.get("groupItemTitle", "")),
|
|
133
|
+
]
|
|
134
|
+
).lower()
|
|
135
|
+
if needle in haystack:
|
|
136
|
+
matched.append(simplify_market(market))
|
|
137
|
+
|
|
138
|
+
matched.sort(key=lambda market: (market["volume"], market["liquidity"]), reverse=True)
|
|
139
|
+
return matched[:limit]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def get_market(identifier: str) -> dict | None:
|
|
143
|
+
data = await api_get(
|
|
144
|
+
"/markets",
|
|
145
|
+
{
|
|
146
|
+
"active": "true",
|
|
147
|
+
"closed": "false",
|
|
148
|
+
"limit": 500,
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
for market in data:
|
|
152
|
+
if str(market.get("id")) == identifier or market.get("slug") == identifier:
|
|
153
|
+
return simplify_market(market)
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def main_async():
|
|
158
|
+
parser = argparse.ArgumentParser(description="Polymarket discovery rail")
|
|
159
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
160
|
+
|
|
161
|
+
scan_parser = subparsers.add_parser("scan")
|
|
162
|
+
scan_parser.add_argument("--limit", type=int, default=40)
|
|
163
|
+
scan_parser.add_argument("--min-volume", type=float, default=1000)
|
|
164
|
+
scan_parser.add_argument("--min-liquidity", type=float, default=1000)
|
|
165
|
+
|
|
166
|
+
search_parser = subparsers.add_parser("search")
|
|
167
|
+
search_parser.add_argument("query", type=str)
|
|
168
|
+
search_parser.add_argument("--limit", type=int, default=20)
|
|
169
|
+
|
|
170
|
+
market_parser = subparsers.add_parser("market")
|
|
171
|
+
market_parser.add_argument("identifier", type=str)
|
|
172
|
+
|
|
173
|
+
args = parser.parse_args()
|
|
174
|
+
|
|
175
|
+
if args.command == "scan":
|
|
176
|
+
result = await scan_markets(args.limit, args.min_volume, args.min_liquidity)
|
|
177
|
+
print(json.dumps(result, indent=2))
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
if args.command == "search":
|
|
181
|
+
result = await search_markets(args.query, args.limit)
|
|
182
|
+
print(json.dumps(result, indent=2))
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
result = await get_market(args.identifier)
|
|
186
|
+
if result is None:
|
|
187
|
+
log(f"No Polymarket market found for {args.identifier}")
|
|
188
|
+
print(json.dumps({"error": "market_not_found", "identifier": args.identifier}, indent=2))
|
|
189
|
+
return
|
|
190
|
+
print(json.dumps(result, indent=2))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
import asyncio
|
|
195
|
+
|
|
196
|
+
asyncio.run(main_async())
|
package/main.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
OpenTradex - agent orchestrator.
|
|
3
3
|
|
|
4
4
|
Spawns Claude Code as a subprocess. The agent reads SOUL.md for personality,
|
|
5
5
|
uses tools directly (web search, Apify, Kalshi API), and persists state to SQLite.
|
|
@@ -31,8 +31,77 @@ PROJECT_DIR = Path(__file__).resolve().parent
|
|
|
31
31
|
DATA_DIR = PROJECT_DIR / "data"
|
|
32
32
|
SESSION_FILE = DATA_DIR / "session_id.txt"
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
def parse_csv_env(name: str, fallback: str = "") -> list[str]:
|
|
36
|
+
raw = os.getenv(name, fallback)
|
|
37
|
+
return [item.strip().lower() for item in raw.split(",") if item.strip()]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_runtime_context() -> str:
|
|
41
|
+
runtime = os.getenv("OPENTRADEX_RUNTIME", "claude-code")
|
|
42
|
+
primary_market = os.getenv("OPENTRADEX_PRIMARY_MARKET", "kalshi")
|
|
43
|
+
enabled_markets = parse_csv_env("OPENTRADEX_ENABLED_MARKETS", primary_market)
|
|
44
|
+
integrations = parse_csv_env("OPENTRADEX_ENABLED_INTEGRATIONS", "apify,rss")
|
|
45
|
+
|
|
46
|
+
lines = [
|
|
47
|
+
"Workspace profile:",
|
|
48
|
+
f"- Runtime profile: {runtime}",
|
|
49
|
+
f"- Primary market: {primary_market}",
|
|
50
|
+
f"- Enabled market rails: {', '.join(enabled_markets)}",
|
|
51
|
+
f"- Enabled data integrations: {', '.join(integrations)}",
|
|
52
|
+
"- Use only the rails enabled in this workspace.",
|
|
53
|
+
"- Live execution is currently routed through Kalshi only. Other rails are for discovery, research, and cross-market context unless explicitly extended.",
|
|
54
|
+
"",
|
|
55
|
+
"Available market/data rails for this workspace:",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
if "kalshi" in enabled_markets:
|
|
59
|
+
lines.extend(
|
|
60
|
+
[
|
|
61
|
+
"- Kalshi discovery and execution:",
|
|
62
|
+
" `PYTHONPATH=. python3 gossip/kalshi.py quick --limit 60`",
|
|
63
|
+
" `PYTHONPATH=. python3 gossip/kalshi.py search \"specific topic\"`",
|
|
64
|
+
" `PYTHONPATH=. python3 gossip/trader.py trade TICKER --side yes/no --estimate 0.XX --confidence high/medium --reasoning \"...\"`",
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if "polymarket" in enabled_markets:
|
|
69
|
+
lines.extend(
|
|
70
|
+
[
|
|
71
|
+
"- Polymarket discovery rail:",
|
|
72
|
+
" `PYTHONPATH=. python3 gossip/polymarket.py scan --limit 40`",
|
|
73
|
+
" `PYTHONPATH=. python3 gossip/polymarket.py search \"specific topic\"`",
|
|
74
|
+
" Use Polymarket for cross-market validation, sentiment, and mispricing comparisons.",
|
|
75
|
+
]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if "tradingview" in enabled_markets:
|
|
79
|
+
lines.extend(
|
|
80
|
+
[
|
|
81
|
+
"- TradingView watchlist rail:",
|
|
82
|
+
" Read `TRADINGVIEW_WATCHLIST` from `.env` and use it to focus macro/equity/crypto research.",
|
|
83
|
+
" Treat this as a watchlist/context source unless a dedicated adapter is added.",
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if "robinhood" in enabled_markets:
|
|
88
|
+
lines.append("- Robinhood is enabled as a broker profile placeholder. Use it for planning and watchlist context unless you add a dedicated execution adapter.")
|
|
89
|
+
|
|
90
|
+
if "groww" in enabled_markets:
|
|
91
|
+
lines.append("- Groww is enabled as a broker profile placeholder. Use it for planning and watchlist context unless you add a dedicated execution adapter.")
|
|
92
|
+
|
|
93
|
+
if "apify" in integrations:
|
|
94
|
+
lines.append("- Apify-backed news scraping is available through `PYTHONPATH=. python3 gossip/news.py --keywords \"...\"`.")
|
|
95
|
+
if "rss" in integrations:
|
|
96
|
+
lines.append("- RSS/live news fallbacks are enabled through the web dashboard and news routes.")
|
|
97
|
+
|
|
98
|
+
return "\n".join(lines)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_cycle_prompt() -> str:
|
|
102
|
+
return f"""{build_runtime_context()}
|
|
103
|
+
|
|
104
|
+
Read SOUL.md for your identity and strategy principles.
|
|
36
105
|
Read data/strategy_notes.md for lessons from past sessions.
|
|
37
106
|
Check data/user_rationales.json for any pending user theses to research.
|
|
38
107
|
|
|
@@ -48,43 +117,45 @@ Then run a full trading cycle:
|
|
|
48
117
|
Review each open position's unrealized P&L and current market price vs your entry.
|
|
49
118
|
|
|
50
119
|
3. POSITION REVIEW — for each open position:
|
|
51
|
-
- If current price moved significantly toward your thesis
|
|
52
|
-
- If thesis has weakened or news contradicts it, EXIT: `PYTHONPATH=. python3 gossip/trader.py exit TICKER --reasoning "..."
|
|
120
|
+
- If current price moved significantly toward your thesis, consider selling now to lock profit vs waiting for settlement.
|
|
121
|
+
- If thesis has weakened or news contradicts it, EXIT: `PYTHONPATH=. python3 gossip/trader.py exit TICKER --reasoning "..."`.
|
|
53
122
|
- If thesis still holds and edge remains, HOLD.
|
|
54
123
|
- Use web search to verify — don't just check prices, check if the underlying event happened.
|
|
55
124
|
|
|
56
125
|
4. MARKET DISCOVERY — use targeted searches, not just broad scans:
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
- Skip sports, entertainment, and illiquid markets (volume < 500, spread > 15c)
|
|
126
|
+
- Use the enabled market rails above.
|
|
127
|
+
- Focus on categories where news creates edge: Politics, Economics, Macro events, liquid crypto and cross-market event contracts.
|
|
128
|
+
- Search for current events you already know about from news/web search.
|
|
129
|
+
- Skip thin or noisy setups unless the edge is exceptional.
|
|
62
130
|
|
|
63
131
|
5. RESEARCH — pick 3-5 promising markets:
|
|
64
|
-
- Use web search to find relevant news and primary sources
|
|
65
|
-
- Use `PYTHONPATH=. python3 gossip/news.py --keywords "..."` for broader news scraping
|
|
66
|
-
-
|
|
67
|
-
-
|
|
132
|
+
- Use web search to find relevant news and primary sources.
|
|
133
|
+
- Use `PYTHONPATH=. python3 gossip/news.py --keywords "..."` for broader news scraping.
|
|
134
|
+
- Use Polymarket or watchlist rails for comparison when they are enabled.
|
|
135
|
+
- Estimate the true probability based on evidence.
|
|
136
|
+
- Look for near-arbitrage: events that already happened but markets have not caught up.
|
|
68
137
|
|
|
69
138
|
6. TRADE if you find edge > 10pp with clear reasoning:
|
|
70
139
|
Before executing, answer in one line: "Evidence type: [hard/soft/speculation]. Weakest assumption: [X]."
|
|
71
140
|
If the evidence is speculation, PASS unless edge is overwhelming (>25pp).
|
|
72
|
-
If you
|
|
73
|
-
|
|
141
|
+
If you cannot name what would make you wrong, your thesis is not specific enough — PASS.
|
|
142
|
+
Only route live orders through supported execution rails.
|
|
74
143
|
|
|
75
144
|
7. Update data/strategy_notes.md with what you learned this cycle.
|
|
76
145
|
|
|
77
146
|
EXECUTION DISCIPLINE:
|
|
78
|
-
- Be decisive. Research
|
|
147
|
+
- Be decisive. Research -> conclude -> act. Do not loop endlessly.
|
|
79
148
|
- For each market: reach a YES/NO/PASS decision within 2-3 tool calls.
|
|
80
|
-
- Evaluate 3-5 markets per cycle, trade the best 1-2.
|
|
81
|
-
- If you
|
|
149
|
+
- Evaluate 3-5 markets per cycle, trade the best 1-2. Do not try to cover everything.
|
|
150
|
+
- If you cannot find edge after 5 minutes on a market, pass and move on.
|
|
82
151
|
- Write your conclusion even when you pass — future cycles benefit from it.
|
|
83
152
|
"""
|
|
84
153
|
|
|
85
154
|
|
|
86
155
|
def build_rationale_prompt(rationale: str) -> str:
|
|
87
|
-
return f"""
|
|
156
|
+
return f"""{build_runtime_context()}
|
|
157
|
+
|
|
158
|
+
Read SOUL.md for your identity and strategy principles.
|
|
88
159
|
|
|
89
160
|
A user has submitted this thesis for you to research and potentially trade on:
|
|
90
161
|
|
|
@@ -100,7 +171,7 @@ Your job:
|
|
|
100
171
|
7. Update data/strategy_notes.md if you learned something new.
|
|
101
172
|
|
|
102
173
|
Check portfolio first: `PYTHONPATH=. python3 gossip/trader.py portfolio`
|
|
103
|
-
Scan markets
|
|
174
|
+
Scan markets using the enabled rails above. Use Kalshi for execution and Polymarket/watchlist rails for comparison where available.
|
|
104
175
|
"""
|
|
105
176
|
|
|
106
177
|
|
|
@@ -115,6 +186,7 @@ def write_status(status: str, **extra) -> None:
|
|
|
115
186
|
|
|
116
187
|
def run_agent(prompt: str, timeout: int = 600) -> dict:
|
|
117
188
|
"""Spawn Claude Code as a subprocess. Stream output to live log file."""
|
|
189
|
+
configured_runtime = os.getenv("OPENTRADEX_RUNTIME", "claude-code")
|
|
118
190
|
cmd = [
|
|
119
191
|
"claude",
|
|
120
192
|
"--print", "-",
|
|
@@ -132,7 +204,7 @@ def run_agent(prompt: str, timeout: int = 600) -> dict:
|
|
|
132
204
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
133
205
|
# Clear live log for this cycle
|
|
134
206
|
LIVE_LOG.write_text("")
|
|
135
|
-
write_status("running")
|
|
207
|
+
write_status("running", configured_runtime=configured_runtime, runner="claude-code")
|
|
136
208
|
|
|
137
209
|
start = time.time()
|
|
138
210
|
session_id = None
|
|
@@ -241,7 +313,7 @@ def _now() -> str:
|
|
|
241
313
|
|
|
242
314
|
|
|
243
315
|
def main():
|
|
244
|
-
parser = argparse.ArgumentParser(description="
|
|
316
|
+
parser = argparse.ArgumentParser(description="OpenTradex agent")
|
|
245
317
|
parser.add_argument("--loop", action="store_true", help="Run continuously")
|
|
246
318
|
parser.add_argument("--interval", type=int, default=None, help="Cycle interval in seconds")
|
|
247
319
|
parser.add_argument("--prompt", type=str, default=None, help="Custom prompt")
|
|
@@ -251,18 +323,24 @@ def main():
|
|
|
251
323
|
|
|
252
324
|
args = parser.parse_args()
|
|
253
325
|
interval = args.interval or int(os.getenv("CYCLE_INTERVAL", "900"))
|
|
326
|
+
configured_runtime = os.getenv("OPENTRADEX_RUNTIME", "claude-code")
|
|
254
327
|
|
|
255
328
|
if args.rationale:
|
|
256
329
|
submit_rationale(args.rationale)
|
|
257
330
|
prompt = build_rationale_prompt(args.rationale)
|
|
258
331
|
else:
|
|
259
|
-
prompt = args.prompt or
|
|
332
|
+
prompt = args.prompt or build_cycle_prompt()
|
|
260
333
|
|
|
261
334
|
if args.dry_run:
|
|
262
335
|
print(prompt)
|
|
263
336
|
return
|
|
264
337
|
|
|
265
|
-
print(f"[
|
|
338
|
+
print(f"[OpenTradex] Starting agent", file=sys.stderr)
|
|
339
|
+
if configured_runtime != "claude-code":
|
|
340
|
+
print(
|
|
341
|
+
f" Runtime profile: {configured_runtime} (executing with Claude Code runner)",
|
|
342
|
+
file=sys.stderr,
|
|
343
|
+
)
|
|
266
344
|
print(f" Mode: {'loop (' + str(interval) + 's)' if args.loop else 'single cycle'}", file=sys.stderr)
|
|
267
345
|
|
|
268
346
|
while True:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opentradex",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "OpenTradex CLI
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "OpenTradex CLI, onboarding flow, and market-rail toolkit for AI-assisted trading workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"opentradex": "./bin/opentradex.mjs"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"bin/",
|
|
15
15
|
"src/",
|
|
16
|
+
"scripts/",
|
|
16
17
|
".env.example",
|
|
17
18
|
"README.md",
|
|
18
19
|
"CLAUDE.md",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
],
|
|
31
32
|
"scripts": {
|
|
32
33
|
"opentradex": "node ./bin/opentradex.mjs",
|
|
33
|
-
"test:cli": "node ./bin/opentradex.mjs --help && node ./bin/opentradex.mjs doctor --workspace ."
|
|
34
|
+
"test:cli": "node ./bin/opentradex.mjs --help && node ./bin/opentradex.mjs providers && node ./bin/opentradex.mjs doctor --workspace ."
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"kalshi",
|
|
@@ -43,5 +44,8 @@
|
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=22.0.0"
|
|
45
46
|
},
|
|
46
|
-
"license": "UNLICENSED"
|
|
47
|
+
"license": "UNLICENSED",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
47
51
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$ErrorActionPreference = "Stop"
|
|
2
|
+
|
|
3
|
+
$workspace = if ($env:OPENTRADEX_WORKSPACE) { $env:OPENTRADEX_WORKSPACE } else { Join-Path $HOME "opentradex" }
|
|
4
|
+
$packageManager = if ($env:OPENTRADEX_PACKAGE_MANAGER) { $env:OPENTRADEX_PACKAGE_MANAGER } else { "npm" }
|
|
5
|
+
|
|
6
|
+
Write-Host "Welcome to OpenTradex"
|
|
7
|
+
Write-Host "Our implementation. Your strategy."
|
|
8
|
+
Write-Host ""
|
|
9
|
+
|
|
10
|
+
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
|
11
|
+
throw "Node.js 22+ is required before running this installer."
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if ($packageManager -eq "bun" -and -not (Get-Command bun -ErrorAction SilentlyContinue)) {
|
|
15
|
+
Write-Host "Bun is not installed, so this installer is falling back to npm."
|
|
16
|
+
$packageManager = "npm"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if ($packageManager -eq "bun") {
|
|
20
|
+
bun add -g opentradex@latest
|
|
21
|
+
} else {
|
|
22
|
+
npm install -g opentradex@latest
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
opentradex onboard --workspace $workspace
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
WORKSPACE="${OPENTRADEX_WORKSPACE:-$HOME/opentradex}"
|
|
5
|
+
PACKAGE_MANAGER="${OPENTRADEX_PACKAGE_MANAGER:-npm}"
|
|
6
|
+
|
|
7
|
+
echo "Welcome to OpenTradex"
|
|
8
|
+
echo "Our implementation. Your strategy."
|
|
9
|
+
echo
|
|
10
|
+
|
|
11
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
12
|
+
echo "Node.js is required. Install Node.js 22+ and rerun this installer."
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [[ "$PACKAGE_MANAGER" == "bun" ]]; then
|
|
17
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
18
|
+
echo "Bun is not installed, so this installer is falling back to npm."
|
|
19
|
+
PACKAGE_MANAGER="npm"
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ "$PACKAGE_MANAGER" == "bun" ]]; then
|
|
24
|
+
bun add -g opentradex@latest
|
|
25
|
+
else
|
|
26
|
+
npm install -g opentradex@latest
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
opentradex onboard --workspace "$WORKSPACE"
|