Single-owner autonomous trading dashboard for Kalshi (primary) with optional Polymarket modules. Heuristic signal generation gated by a multi-tier Claude AI reviewer, percentage-based risk that auto-scales with live account equity, and full audit-log coverage of every decision.
No guarantees — trading involves real risk of loss. The guardrails below exist to filter low-edge / ambiguous trades, not to eliminate risk.
| Layer | Technology |
|---|---|
| Frontend | React 19 + Vite 8 + Wouter + TanStack Query v5 + Tailwind v4 + shadcn/ui |
| Backend | Node 20 + Express 4 + tRPC v11 |
| Database | Neon Postgres (serverless HTTP driver) + Drizzle ORM |
| Auth | JWT (24h access / 7d refresh) in httpOnly cookies, optional TOTP 2FA |
| AI | Anthropic SDK direct — Haiku 4.5 → Sonnet 4.6 → Opus 4.7 escalation |
| Build | Vite (frontend → dist/public), esbuild (backend → dist/index.js) |
| Deploy | Railway (Docker), schedulers run in-process |
heuristic signal generation (server/_core/kalshiSignals.ts)
│
▼
confidence + market-condition + training-rule filters
│
▼
(optional) free OpenRouter pre-triage ← drops obvious junk, never approves
│
▼
Tier 1: Claude Haiku 4.5 (every reviewed signal, prompt-cached)
│
├── (optional) parallel Grok review when ENABLE_GROK_TEAM=true
▼
Tier 2: Claude Sonnet 4.6 (high-stakes / contested signals)
│
▼
Tier 3: Claude Opus 4.7 (catastrophic-bet unanimous gate, ≥10% capital)
│
▼
profit-guardrail entry gate → Kelly sizing → per-user mutex
│
▼
Kalshi REST order placement (RSA-PSS signed)
│
▼
order-sync + exit-monitor (30s tick, runs 24/7)
Every stage emits a typed audit-log event (kalshi_signal_pipeline,
kalshi_reviewer_telemetry, kalshi_order_placed,
kalshi_position_exit_signal, …).
All thresholds are percentages of live Kalshi capital (read from
server/_core/liveCapital.ts, 30s cache). When your balance changes,
caps scale automatically — no redeploy.
| Gate | Default | ENV override |
|---|---|---|
| Net EV (after exact Kalshi fees + amortized AI cost) | ≥ 2.5 % | MIN_NET_EV |
| Confidence after AI adjustment | ≥ 60 % | MIN_CONFIDENCE_AFTER_ADJUST |
| Kelly sizing fraction | ½ Kelly | KELLY_FRACTION |
| Kelly max position | 25 % capital | KELLY_MAX_PCT_OF_CAPITAL |
| Kelly min position | 1 % capital | KELLY_MIN_PCT_OF_CAPITAL |
| Per-trade outer cap | 6 % capital | MAX_RISK_PER_TRADE_PERCENT |
| Total open exposure | 35 % capital | MAX_PORTFOLIO_EXPOSURE_PCT |
| Per correlated category | 15 % capital | MAX_CORRELATED_GROUP_PCT |
| Min market liquidity | $40k | MIN_LIQUIDITY_USD |
| Daily drawdown breaker | 5 % | DAILY_DRAWDOWN_PAUSE_FRAC |
| Weekly drawdown breaker | 12 % | WEEKLY_DRAWDOWN_PAUSE_FRAC |
| Daily loss USD stop | $20 | DAILY_LOSS_LIMIT_USD |
| Cold streak | 6 losses or 7-day edge < 3 % | COLD_STREAK_LOSS_COUNT |
The MAX_RISK_PER_TRADE_PERCENT outer cap binds before KELLY_MAX_PCT_OF_CAPITAL,
so it's the hardest single-order constraint.
DAILY_LOSS_LIMIT_USD is a hard daily stop — when the day's realized net
P&L drops below -DAILY_LOSS_LIMIT_USD, every autonomy tick is skipped
until UTC midnight (emits kalshi_daily_loss_limit_triggered).
| Tier | Model | Triggers |
|---|---|---|
| 1 | Claude Haiku 4.5 | every reviewed signal (batch, prompt-cached) |
| 2 | Claude Sonnet 4.6 | high-stakes signals: ≥3% capital, ≤24h to resolution, or self-consistency split |
| 3 | Claude Opus 4.7 | gross EV ≥5% AND Tier-2 disagreement, OR catastrophic-bet (≥10% capital) — Tier 3 is a unanimous gate (all reviewers must approve) |
ANTHROPIC_API_KEY is required. Autonomy fails closed if the key is
missing — high-stakes signals are explicitly refused rather than approved
without escalation review.
- Grok (xAI) — set
XAI_API_KEYandGROK_REVIEWER_ENABLED=truefor parallel dual-bot consensus on Tier 2 (alternate reviewer for breaking-news categories with ≤72h resolution). Without it, the system gracefully degrades to Claude-only. - OpenRouter free pre-triage — set
OPENROUTER_API_KEYto run a free model (default Qwen3 235B) before paid Claude review. It drops obvious junk; it never approves trades; on errors it fails open to the full paid stack.
| Scheduler | Default | Notes |
|---|---|---|
| Kalshi autonomy | 10 min (prime) / 40 min (overnight) | Time-of-day adaptive — see below |
| Order sync + exit monitor | 30 s | Runs 24/7 — open positions need overnight stop-loss |
| Combinatorial arb scanner | 60 s | Risk-free math, no AI cost, detection-only |
| Wikipedia edit watcher | 5 min | Free real-time signal on watched politicians/companies |
| Daily plays (sports / moonshot) | 5 min poll | Idempotent within a UTC day |
| Weekly Brier calibration | 7 days | Reviewer score recalibration |
| Audit-log cleanup | 24 h | Purges entries older than AUDIT_LOG_RETENTION_DAYS (default 90) |
Kalshi liquidity is heavily US-skewed. The autonomy scheduler runs at
the AUTONOMY_INTERVAL_MS base interval during US prime hours, then
slows by AUTONOMY_OVERNIGHT_MULTIPLIER (default 4×) during quiet hours.
| Tier | UTC window | ET window | Cadence |
|---|---|---|---|
| prime | 13:00–05:00 | 9am–1am ET | base interval |
| overnight | 05:00–13:00 | 1am–9am ET | 4× slower |
Tunable via AUTONOMY_PRIME_START_UTC_HOUR, AUTONOMY_PRIME_END_UTC_HOUR,
AUTONOMY_OVERNIGHT_MULTIPLIER. Order sync + exit monitor are NOT
slowed — open positions still need overnight monitoring.
Selected tables — full schema in drizzle/schema.ts:
| Table | Purpose |
|---|---|
users |
Owner account, 2FA secrets, role |
auditLog |
Immutable event log (every trade decision, risk block, reviewer call) |
kalshiCredentials |
AES-256-GCM encrypted Kalshi API key + private key |
kalshiMarkets |
Latest market view (upserted on each fetch) |
kalshiMarketSnapshots |
Immutable timestamped market history |
kalshiSignals |
Saved signals with confidence, EV, reviewer reasoning |
kalshiOrders |
Order ledger (pending/filled/cancelled/rejected) |
kalshiPositions |
Open positions with exitState JSONB for trailing stops |
kalshiCapital |
Running balance + drawdown tracking |
tradingPreferences |
Per-user autonomy mode, risk posture, paper toggle |
autonomyRuns |
One row per scheduled run with outcome + reconciliation |
trainingInstructions + instructionRules |
User-defined signal/market filters |
deskMemory |
Per-desk win/loss tape (12 lessons, injected into Claude prompts) |
distributedLocks |
Postgres advisory lock preventing concurrent autonomy runs |
polymarketCredentials + polymarketPositions |
Polymarket integration |
Migrations: drop a new SQL file in drizzle/migrations/
using the next sequential number. scripts/applyMigrations.ts
runs them on every Railway deploy via pnpm migrate:apply, tracked in
the migrations_log table. Migrations should be SQL-idempotent
(ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS).
corepack pnpm install # install (pnpm v10, native binaries via onlyBuiltDependencies)
corepack pnpm dev # vite + tsx watch
corepack pnpm check # tsc --noEmit
corepack pnpm test -- --run # full vitest suite (~938 tests)
corepack pnpm build # frontend → dist/public
corepack pnpm build:server # backend → dist/index.js
corepack pnpm db:push # fast schema sync (dev)
corepack pnpm migrate:apply # apply drizzle/migrations/*.sql (production)
corepack pnpm tsx scripts/runCalibrationJob.ts # weekly Brier calibrationAll commands use corepack pnpm to pin the version from packageManager.
Never use npm or yarn — the lockfile is pnpm-lock.yaml.
Kalshi signs every private request with RSA-PSS over ${timestamp}${METHOD}${path}.
- Sign in at https://kalshi.com (production) or the demo environment.
- Account → API → "Create new key".
- Copy the Key ID (shown once) and download the private key PEM.
KALSHI_KEY_ID=<paste the key id>
KALSHI_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE...IDAQAB\n-----END PRIVATE KEY-----\n"
# OR
KALSHI_PRIVATE_KEY_PATH=/secrets/kalshi-private-key.pem
DEMO_MODE=false # true for sandboxcorepack pnpm install
cp .env.example .env # fill in values
corepack pnpm db:push
corepack pnpm devDeploys from main via the Dockerfile. Required env:
DATABASE_URL # Neon pooled connection string
JWT_SECRET # 32+ random chars
CREDENTIAL_ENCRYPTION_SECRET # 32+ random chars, MUST differ from JWT_SECRET
OWNER_EMAIL
OWNER_PASSWORD # 12+ chars in production
NODE_ENV=production
ANTHROPIC_API_KEY # required — autonomy fails closed without it
KALSHI_KEY_ID
KALSHI_PRIVATE_KEYMigrations apply automatically on deploy via pnpm migrate:apply.
# CORS + alerts
ALLOWED_ORIGIN # extra origin in production
ALERT_WEBHOOK_URL # Slack/Discord/PagerDuty inbound
# AI models (defaults shown)
CLAUDE_HAIKU_MODEL # claude-haiku-4-5 (Tier 1)
CLAUDE_SONNET_MODEL # claude-sonnet-4-6 (Tier 2)
CLAUDE_OPUS_MODEL # claude-opus-4-7 (Tier 3)
CLAUDE_HAIKU_TIMEOUT_MS # 15000
CLAUDE_SONNET_TIMEOUT_MS # 20000
CLAUDE_OPUS_TIMEOUT_MS # 45000
OPUS_ESCALATION_MIN_GROSS_EV # 0.15
# Optional secondary reviewers
XAI_API_KEY # enables Grok dual-bot
GROK_MODEL # grok-4-latest
GROK_REVIEWER_ENABLED # false (must opt in even when XAI_API_KEY set)
OPENROUTER_API_KEY # enables free pre-triage
OPENROUTER_TRIAGE_MODEL # qwen/qwen3-235b-a22b:free
OPENROUTER_TRIAGE_THRESHOLD # 0 (run on every non-empty batch)
OPENROUTER_TRIAGE_ENABLED # true (default when key set)
OPENROUTER_TIMEOUT_MS # 8000
# Schedulers + cadence
AUTONOMY_INTERVAL_MS # 600000 (10 min default)
AUTONOMY_OVERNIGHT_MULTIPLIER # 4 (overnight slowdown)
AUTONOMY_PRIME_START_UTC_HOUR # 13 (prime window starts 9am ET)
AUTONOMY_PRIME_END_UTC_HOUR # 5 (prime window ends 1am ET)
ORDER_SYNC_INTERVAL_MS # 30000
COMBINATORIAL_ARB_INTERVAL_MS # 60000
WIKIPEDIA_WATCH_INTERVAL_MS # 300000
SIGNAL_REVIEW_PRICE_DELTA_BPS # 50 (skip review if price moved <0.5%)
SIGNAL_REVIEW_STALE_TTL_MS # 600000 (force re-review every 10 min)
# Risk + sizing (all percentages of live capital)
MIN_NET_EV # 0.025
MIN_CONFIDENCE_AFTER_ADJUST # 0.60
MAX_PORTFOLIO_EXPOSURE_PCT # 0.35
MAX_CORRELATED_GROUP_PCT # 0.15
KELLY_FRACTION # 0.5
KELLY_MAX_PCT_OF_CAPITAL # 0.25
KELLY_MIN_PCT_OF_CAPITAL # 0.01
MAX_RISK_PER_TRADE_PERCENT # 6
MIN_LIQUIDITY_USD # 40000
MIN_ORDER_EXPOSURE_USD # 5 (dust-trade block)
DAILY_DRAWDOWN_PAUSE_FRAC # 0.05
WEEKLY_DRAWDOWN_PAUSE_FRAC # 0.12
DAILY_LOSS_LIMIT_USD # 20 (hard daily stop)
COLD_STREAK_LOSS_COUNT # 6
COLD_STREAK_MIN_REALIZED_EDGE_PCT # 0.03
# High-stakes triggers
HIGH_STAKES_PCT_OF_CAPITAL # 0.03
HIGH_STAKES_RESOLUTION_MINUTES # 1440
CATASTROPHIC_PCT_OF_CAPITAL # 0.10
# Operational
PAPER_TRADE_MODE # global emergency kill-switch (per-user toggle in dashboard otherwise)
AUTO_CLOSE_ON_EXIT_SIGNAL # false (emit audit signal only) / true (auto-close on stop-loss)
AUDIT_LOG_RETENTION_DAYS # 90
LOG_LEVEL # info
POLYMARKET_OWNER_ADDRESS # EOA proxy wallet for Polymarket position sync
# Kalshi fee schedule (override only if Kalshi changes it)
KALSHI_MAKER_FEE_MULTIPLIER # 0.0175
KALSHI_TAKER_FEE_MULTIPLIER # 0.07
PREFER_MAKER_ORDERS # trueSee .env.example for the full set with comments.
| File | Purpose |
|---|---|
| server/_core/index.ts | Boot sequence, scheduler runners, self-test, time-of-day cadence |
| server/_core/env.ts | Single source of truth for env validation + defaults |
| server/_core/liveCapital.ts | Live Kalshi balance reader (30s cache) |
| server/_core/autonomyCadence.ts | Time-of-day prime/overnight tier helper |
| server/_core/kalshiClient.ts | RSA-PSS signed Kalshi REST client |
| server/_core/kalshiSignals.ts | Heuristic signal generators (value / momentum / contrarian / sentiment) |
| server/_core/kalshiAutonomy.ts | Autonomy run orchestration + risk gates + execution |
| server/_core/kalshiRisk.ts | Hard risk blocks (size, exposure, daily loss, capital reserve) |
| server/_core/profitGuardrails.ts | Canonical entry-gate (checkFullEntry) |
| server/_core/feeCalculator.ts | Exact Kalshi fee math (rounded up to cent) + net EV |
| server/_core/kellySizer.ts | ½ Kelly with outer-cap clamps |
| server/_core/drawdownBreaker.ts | Daily / weekly drawdown + cold-streak pause |
| server/_core/dailyScoreboard.ts | Live P&L tracking + daily loss limit + tier (green/yellow/red) |
| server/_core/exitMonitor.ts | Stop-loss + profit-target evaluation per tick |
| server/_core/exitStrategy.ts | Trailing-stop state machine (persisted in kalshiPositions.exitState) |
| server/_core/highStakesDetector.ts | Tier escalation classifier |
| server/_core/ensembleConsensus.ts | 3-tier reviewer orchestrator |
| server/_core/claudeReviewer.ts | Sonnet + Opus reviewers (Anthropic SDK direct, prompt caching, extended thinking) |
| server/_core/tradingReviewer.ts | Tier-1 Haiku batch reviewer |
| server/_core/grokReviewer.ts | Optional Grok parallel reviewer |
| server/_core/openRouterTriage.ts | Optional free pre-triage |
| server/_core/categoryPersonas.ts | 16 desk persona system prompts |
| server/_core/marketCategoryRouter.ts | Market title → category classifier |
| server/_core/userMutex.ts | Per-user in-process async mutex for placeOrder |
| server/_core/distributedLock.ts | Postgres advisory lock for concurrent autonomy runs |
| server/_core/circuitBreaker.ts | Clock-injectable breaker (CLOSED / OPEN / HALF_OPEN) |
| server/_core/fetchWithRetry.ts | Exponential backoff + jitter HTTP retry |
| server/_core/alerting.ts | Webhook alerts (Slack / PagerDuty) |
| server/_core/startupSelfTest.ts | Boot diagnostic — blocks scheduler arming on prod failure |
| server/_core/calibrationJob.ts | Weekly Brier-score recalibration |
- Hard risk blocks never bypassed:
kalshiRisk.tshard blocks (max position, max daily loss, max open positions, capital reserve) cannot be overridden by AI confidence/EV adjustments. - Order placement is per-user serialized: every
placeOrderandcloseOrdercall is wrapped inwithUserLockto prevent TOCTOU races. - Audit-log coverage is total: every order placement, cancellation, risk block, autonomy run, AI reviewer call, exit signal, kill-switch trigger, and daily-loss-limit hit emits a typed audit event.
- Market responses validated at boundary:
normalizeKalshiMarketrejects malformed Kalshi responses before any downstream code sees them. Cent-scale prices are converted explicitly viacentsToDollars. - Resilient external calls: every Kalshi market-data fetch uses
fetchWithRetry+kalshiBreaker. Use the same pattern for any new external HTTP call. - Self-test gates scheduler arming: in production, any FAIL in
runStartupSelfTest()keeps the HTTP server up but prevents schedulers from arming. HTTP stays reachable so the operator can read the [SelfTest] log lines and fix env vars without crash-loop noise. - Kill switch is layered: disarms
tradingPreferences.liveTradingEnabledfirst, then closes positions sequentially underwithUserLock, then alerts on any partial failure.
GET /api/health/live— liveness, no DB touch (Railway restart probe)GET /api/health/ready— readiness, returns 503 on DB outage (point uptime monitoring here)GET /api/health— full body with DB latency, scheduler runtimes, uptime
corepack pnpm test -- --run # all (~938 tests)
corepack pnpm test -- --run server/kalshi.autonomy.test.ts # single fileAll external dependencies are vi.mock()-ed — actual HTTP and DB calls
never fire in the suite. Tests live in server/*.test.ts (not inside
_core/). Run corepack pnpm check && corepack pnpm test -- --run
before every commit.
MIT. Use at your own risk.