hood-traders 0.1.0 → 0.2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/framework/risk.ts","../src/framework/agent.ts","../src/framework/journal.ts","../src/framework/kill.ts","../src/framework/market.ts","../src/framework/fleet.ts","../src/framework/config.ts","../src/strategies/launch-sniper.ts","../src/strategies/momentum.ts","../src/strategies/premium-watch.ts","../src/framework/llm.ts","../src/strategies/llm-strategist.ts","../src/server/dashboard.ts"],"sourcesContent":["import type { RefusalReason, RiskLimits } from './types.js'\n\n/** Everything the risk engine needs to rule on a single intent. */\nexport interface RiskContext {\n /** `buy` grows exposure and hits spend/position caps; `sell` reduces it and is exempt from those. */\n side: 'buy' | 'sell'\n /** USD notional of this order (agent computes it from live prices; USDG≈USD, WETH×ethUsd). */\n notionalUsd: number\n /** USD value the token position would reach AFTER a buy fills. */\n positionUsdAfter: number\n /** USD this agent has already spent this UTC day. */\n spentTodayUsd: number\n /** USD the whole fleet has spent this UTC day. */\n fleetSpentTodayUsd: number\n /** ms epoch of this agent's last executed trade, or null. */\n lastTradeAt: number | null\n /** Slippage bound (bps) the order would execute with. */\n slippageBps: number\n /** Whether the global kill switch is tripped. */\n killed: boolean\n /** Current time (ms) — injected for deterministic tests. */\n now: number\n /** Fleet-wide daily spend ceiling (USD). */\n fleetMaxDailySpendUsdg: number\n}\n\n/** Result of a risk check. */\nexport interface RiskVerdict {\n ok: boolean\n reason?: RefusalReason\n /** Human explanation, safe to journal and surface on the dashboard. */\n detail: string\n}\n\n/**\n * The risk engine. It is the last gate before any order — paper or live —\n * reaches execution, and it fails CLOSED: any check it cannot satisfy refuses\n * the trade rather than letting it through. Refusals are ordered from\n * cheapest/most-fatal (kill switch) to most-specific (caps) so the journaled\n * reason is the most meaningful one.\n *\n * Sells are intentionally exempt from spend and position caps: those caps exist\n * to bound how much risk you take ON, and refusing a de-risking sell because of\n * a spend cap would trap an agent in a losing position. Sells still honor the\n * kill switch, cooldown, and slippage bound.\n */\nexport class RiskEngine {\n constructor(private readonly limits: RiskLimits) {}\n\n get riskLimits(): RiskLimits {\n return this.limits\n }\n\n check(ctx: RiskContext): RiskVerdict {\n if (ctx.killed) {\n return { ok: false, reason: 'kill_switch', detail: 'kill switch is tripped — no new orders' }\n }\n\n if (ctx.notionalUsd <= 0) {\n return { ok: false, reason: 'zero_amount', detail: 'order notional is zero or negative' }\n }\n\n // Cooldown throttles trade frequency regardless of side.\n if (ctx.lastTradeAt !== null) {\n const elapsed = (ctx.now - ctx.lastTradeAt) / 1000\n if (elapsed < this.limits.cooldownSeconds) {\n const wait = (this.limits.cooldownSeconds - elapsed).toFixed(1)\n return { ok: false, reason: 'cooldown', detail: `cooldown active — ${wait}s until next trade allowed` }\n }\n }\n\n // Slippage bound must never exceed the configured cap.\n if (ctx.slippageBps > this.limits.maxSlippageBps) {\n return {\n ok: false,\n reason: 'slippage_bound',\n detail: `slippage ${ctx.slippageBps}bps exceeds cap ${this.limits.maxSlippageBps}bps`,\n }\n }\n\n // Spend/position caps only constrain buys (exposure-increasing orders).\n if (ctx.side === 'buy') {\n if (ctx.positionUsdAfter > this.limits.maxPositionUsdg) {\n return {\n ok: false,\n reason: 'position_cap',\n detail: `position would reach $${ctx.positionUsdAfter.toFixed(2)}, over cap $${this.limits.maxPositionUsdg}`,\n }\n }\n if (ctx.spentTodayUsd + ctx.notionalUsd > this.limits.maxDailySpendUsdg) {\n return {\n ok: false,\n reason: 'daily_cap',\n detail: `daily spend would reach $${(ctx.spentTodayUsd + ctx.notionalUsd).toFixed(2)}, over cap $${this.limits.maxDailySpendUsdg}`,\n }\n }\n if (ctx.fleetSpentTodayUsd + ctx.notionalUsd > ctx.fleetMaxDailySpendUsdg) {\n return {\n ok: false,\n reason: 'fleet_daily_cap',\n detail: `fleet daily spend would reach $${(ctx.fleetSpentTodayUsd + ctx.notionalUsd).toFixed(2)}, over cap $${ctx.fleetMaxDailySpendUsdg}`,\n }\n }\n }\n\n return { ok: true, detail: 'within all risk limits' }\n }\n}\n\n/** UTC-midnight ms boundary for `now` — the daily-cap accounting window. */\nexport function utcDayStart(now: number): number {\n const d = new Date(now)\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())\n}\n","import { formatUnits, type Account, type Address, type Hash } from 'viem'\nimport { buildSwapTx, ensureApproval, type SwapQuote } from 'hoodchain'\nimport type { Journal } from './journal.js'\nimport type { Market } from './market.js'\nimport { RiskEngine, utcDayStart } from './risk.js'\nimport type { KillSwitch } from './kill.js'\nimport type { Strategy } from './strategy.js'\nimport type {\n AgentStatus,\n Decision,\n Intent,\n Mode,\n Position,\n RiskLimits,\n TradeRecord,\n} from './types.js'\n\n/** Everything an {@link Agent} is constructed with. */\nexport interface AgentOptions {\n id: string\n strategy: Strategy\n market: Market\n limits: RiskLimits\n journal: Journal\n kill: KillSwitch\n mode: Mode\n /** Account for live execution; null in paper mode. */\n account: Account | null\n fleetMaxDailySpendUsdg: number\n /** Fleet-wide spend accessor + reporter, so the agent respects the global cap. */\n fleetSpentTodayUsd: () => number\n reportFleetSpend: (usd: number) => void\n /** Milliseconds between decision ticks. */\n tickIntervalMs: number\n /** Injected clock, for tests. Defaults to `Date.now`. */\n clock?: () => number\n}\n\nconst DUST = 1_000n // token smallest-units below which a position is considered closed\n\n/**\n * An autonomous trading agent = strategy + wallet + risk budget + journal.\n *\n * Each tick runs the full pipeline: observe (the strategy reads the {@link\n * Market}) → decide (the strategy returns intents) → simulate (a real QuoterV2\n * `eth_call`) → risk-check (the {@link RiskEngine}, fail-closed) → execute\n * (paper: record the simulated fill; live: sign the swap) → journal (every\n * decision, refusal, trade, and equity mark). The strategy proposes; the agent\n * disposes, and never lets an intent skip the risk gate.\n */\nexport class Agent {\n readonly id: string\n readonly strategy: Strategy\n readonly mode: Mode\n private readonly market: Market\n private readonly risk: RiskEngine\n private readonly journal: Journal\n private readonly kill: KillSwitch\n private readonly account: Account | null\n private readonly opts: AgentOptions\n private readonly clock: () => number\n\n private readonly positions = new Map<string, Position>()\n private lastTradeAt: number | null = null\n private realizedUsd = 0\n private spentTodayUsd = 0\n private spentDay = 0\n private ticks = 0\n private trades = 0\n private refusals = 0\n private lastTickAt: number | null = null\n private lastError: string | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private running = false\n private ticking = false\n\n constructor(opts: AgentOptions) {\n this.opts = opts\n this.id = opts.id\n this.strategy = opts.strategy\n this.mode = opts.mode\n this.market = opts.market\n this.risk = new RiskEngine(opts.limits)\n this.journal = opts.journal\n this.kill = opts.kill\n this.account = opts.account\n this.clock = opts.clock ?? Date.now\n }\n\n /** Begin the tick loop and wire the strategy's stream subscriptions. */\n async start(): Promise<void> {\n if (this.running) return\n this.running = true\n const log = (message: string, meta: Record<string, unknown> = {}) =>\n this.journal.recordDecision({ agentId: this.id, ts: this.clock(), kind: 'observe', detail: message, meta })\n await this.strategy.start?.({ market: this.market, log })\n this.kill.onKill((reason) => log(`kill switch tripped: ${reason} — halting new orders`))\n this.scheduleTick()\n }\n\n /** Stop the loop and the strategy's subscriptions. */\n stop(): void {\n this.running = false\n if (this.timer) clearTimeout(this.timer)\n this.timer = null\n this.strategy.stop?.()\n }\n\n private scheduleTick(): void {\n if (!this.running) return\n this.timer = setTimeout(async () => {\n await this.tick()\n this.scheduleTick()\n }, this.opts.tickIntervalMs)\n this.timer.unref?.()\n }\n\n /** Run one full pipeline pass. Safe to call directly (used by tests). */\n async tick(): Promise<void> {\n if (this.ticking) return\n this.ticking = true\n const now = this.clock()\n try {\n this.rolloverDay(now)\n await this.markPositions(now)\n\n if (this.kill.isKilled()) {\n // Halted: still mark equity so the curve shows the freeze, but propose nothing.\n this.recordEquity(now)\n this.ticks++\n this.lastTickAt = now\n return\n }\n\n const decision = await this.decide(now)\n for (const alert of decision.alerts) {\n this.journal.recordDecision({\n agentId: this.id,\n ts: now,\n kind: 'alert',\n detail: `[${alert.level}] ${alert.message}`,\n meta: alert.meta ?? {},\n })\n }\n for (const intent of decision.intents) {\n await this.processIntent(intent, now)\n }\n\n this.recordEquity(now)\n this.ticks++\n this.lastTickAt = now\n this.lastError = null\n } catch (err) {\n this.lastError = err instanceof Error ? err.message : String(err)\n this.journal.recordDecision({\n agentId: this.id,\n ts: now,\n kind: 'observe',\n detail: `tick error: ${this.lastError}`,\n meta: {},\n })\n } finally {\n this.ticking = false\n }\n }\n\n private async decide(now: number): Promise<Decision> {\n const { quoteToken, quoteSymbol, quoteDecimals } = this.quoteInfo()\n return this.strategy.tick({\n market: this.market,\n positions: [...this.positions.values()],\n now,\n quoteToken,\n quoteSymbol,\n quoteDecimals,\n log: (message, meta = {}) =>\n this.journal.recordDecision({ agentId: this.id, ts: now, kind: 'observe', detail: message, meta }),\n })\n }\n\n private quoteInfo(): { quoteToken: Address; quoteSymbol: string; quoteDecimals: number } {\n if (this.strategy.quote === 'weth') {\n return { quoteToken: this.market.weth, quoteSymbol: 'WETH', quoteDecimals: 18 }\n }\n return { quoteToken: this.market.usdg, quoteSymbol: 'USDG', quoteDecimals: this.market.usdgDecimals }\n }\n\n private async processIntent(intent: Intent, now: number): Promise<void> {\n const refuse = (reason: string, detail: string, meta: Record<string, unknown> = {}) => {\n this.refusals++\n this.journal.recordDecision({\n agentId: this.id,\n ts: now,\n kind: 'refused',\n detail: `${intent.side} ${intent.tokenSymbol}: ${detail}`,\n meta: { reason, intentReason: intent.reason, ...meta },\n })\n }\n\n const quoteToken = intent.quoteToken\n const quoteDecimals = intent.quoteSymbol === 'USDG' ? this.market.usdgDecimals : 18\n\n // ── simulate (real eth_call against live pools) ────────────────────────────\n let sim: SwapQuote | null\n if (intent.side === 'buy') {\n sim = await this.market.quoteBuy(quoteToken, intent.token, intent.amountIn)\n } else {\n sim = await this.market.quoteSell(intent.token, quoteToken, intent.amountIn)\n }\n if (!sim || sim.amountOut <= 0n) {\n refuse('no_route', 'no liquid route to simulate the fill')\n return\n }\n\n // ── notional in USD ────────────────────────────────────────────────────────\n const ethUsd = intent.quoteSymbol === 'WETH' ? await this.market.ethUsd(30_000, now) : 1\n if (ethUsd === null) {\n refuse('no_route', 'cannot price ETH to enforce USD caps')\n return\n }\n let notionalUsd: number\n if (intent.side === 'buy') {\n notionalUsd = Number(formatUnits(intent.amountIn, quoteDecimals)) * ethUsd\n } else {\n notionalUsd = Number(formatUnits(sim.amountOut, quoteDecimals)) * ethUsd\n }\n\n // ── position accounting for the cap ────────────────────────────────────────\n const existing = this.positions.get(intent.token.toLowerCase())\n if (intent.side === 'sell') {\n if (!existing || existing.amount < intent.amountIn - DUST) {\n refuse('insufficient_balance', 'position too small to sell requested amount')\n return\n }\n }\n const positionUsdAfter = intent.side === 'buy' ? (existing?.investedUsd ?? 0) + notionalUsd : 0\n\n // ── slippage bound ─────────────────────────────────────────────────────────\n const slippageBps = Math.min(intent.maxSlippageBps ?? this.risk.riskLimits.maxSlippageBps, 10_000)\n const minOut = (sim.amountOut * BigInt(10_000 - slippageBps)) / 10_000n\n\n // ── risk gate (fail closed) ────────────────────────────────────────────────\n const verdict = this.risk.check({\n side: intent.side,\n notionalUsd,\n positionUsdAfter,\n spentTodayUsd: this.spentTodayUsd,\n fleetSpentTodayUsd: this.opts.fleetSpentTodayUsd(),\n lastTradeAt: this.lastTradeAt,\n slippageBps,\n killed: this.kill.isKilled(),\n now,\n fleetMaxDailySpendUsdg: this.opts.fleetMaxDailySpendUsdg,\n })\n if (!verdict.ok) {\n refuse(verdict.reason ?? 'refused', verdict.detail, { notionalUsd: round(notionalUsd) })\n return\n }\n\n // ── execute ────────────────────────────────────────────────────────────────\n let txHash: Hash | null = null\n let amountOut = sim.amountOut\n if (this.mode === 'live') {\n const executed = await this.executeLive(intent, sim, slippageBps)\n if (!executed) {\n refuse('no_route', 'live execution failed (see logs)')\n return\n }\n txHash = executed.hash\n amountOut = executed.amountOutMinimum // conservative floor actually received ≥ this\n }\n\n // ── journal + book-keeping ──────────────────────────────────────────────────\n const trade: TradeRecord = {\n agentId: this.id,\n mode: this.mode,\n ts: now,\n side: intent.side,\n token: intent.token,\n tokenSymbol: intent.tokenSymbol,\n quoteToken,\n quoteSymbol: intent.quoteSymbol,\n amountIn: intent.amountIn,\n amountOut,\n txHash,\n reason: intent.reason,\n slippageBps,\n gasEstimate: sim.gasEstimate,\n meta: { ...(intent.meta ?? {}), notionalUsd: round(notionalUsd), minOut: minOut.toString() },\n }\n this.journal.recordTrade(trade)\n this.trades++\n this.lastTradeAt = now\n this.applyFill(intent, amountOut, notionalUsd, now)\n if (intent.side === 'buy') {\n this.spentTodayUsd += notionalUsd\n this.opts.reportFleetSpend(notionalUsd)\n }\n }\n\n private async executeLive(\n intent: Intent,\n sim: SwapQuote,\n slippageBps: number,\n ): Promise<{ hash: Hash; amountOutMinimum: bigint } | null> {\n if (!this.account) return null\n try {\n const tx = buildSwapTx(this.market.client, sim, { slippageBps })\n await ensureApproval(this.market.client, intent.side === 'buy' ? intent.quoteToken : intent.token, intent.amountIn)\n const hash = await this.market.client.wallet!.sendTransaction({\n to: tx.to,\n data: tx.data,\n value: tx.value,\n account: this.account,\n chain: this.market.client.chain,\n })\n await this.market.client.public.waitForTransactionReceipt({ hash })\n return { hash, amountOutMinimum: tx.amountOutMinimum }\n } catch (err) {\n this.lastError = err instanceof Error ? err.message : String(err)\n return null\n }\n }\n\n private applyFill(intent: Intent, amountOut: bigint, notionalUsd: number, now: number): void {\n const key = intent.token.toLowerCase()\n const existing = this.positions.get(key)\n if (intent.side === 'buy') {\n if (existing) {\n existing.amount += amountOut\n existing.costBasis += intent.amountIn\n existing.investedUsd += notionalUsd\n } else {\n this.positions.set(key, {\n token: intent.token,\n tokenSymbol: intent.tokenSymbol,\n amount: amountOut,\n costBasis: intent.amountIn,\n investedUsd: notionalUsd,\n quoteToken: intent.quoteToken,\n quoteSymbol: intent.quoteSymbol,\n openedAt: now,\n markUsd: null,\n meta: intent.meta ?? {},\n })\n }\n return\n }\n // sell: realize PnL on the sold fraction\n if (!existing) return\n const sellAmount = intent.amountIn > existing.amount ? existing.amount : intent.amountIn\n // fraction as a float only touches investedUsd (already a float, USD-scale — safe);\n // costBasis stays bigint-only arithmetic so large token-unit positions (amounts near\n // or past 2^53) don't lose precision through a Number() round-trip.\n const fraction = existing.amount > 0n ? Number(sellAmount) / Number(existing.amount) : 1\n const costFractionUsd = existing.investedUsd * fraction\n const costBasisSold = existing.amount > 0n ? (existing.costBasis * sellAmount) / existing.amount : existing.costBasis\n this.realizedUsd += notionalUsd - costFractionUsd\n existing.amount -= sellAmount\n existing.costBasis -= costBasisSold\n existing.investedUsd -= costFractionUsd\n if (existing.amount <= DUST) this.positions.delete(key)\n }\n\n /** Mark every open position to its live exit value (a real sell-side quote). */\n private async markPositions(now: number): Promise<void> {\n for (const pos of this.positions.values()) {\n const q = await this.market.quoteSell(pos.token, pos.quoteToken, pos.amount)\n if (!q) {\n pos.markUsd = null\n continue\n }\n const quoteDecimals = pos.quoteSymbol === 'USDG' ? this.market.usdgDecimals : 18\n const ethUsd = pos.quoteSymbol === 'WETH' ? await this.market.ethUsd(30_000, now) : 1\n if (ethUsd === null) {\n pos.markUsd = null\n continue\n }\n pos.markUsd = Number(formatUnits(q.amountOut, quoteDecimals)) * ethUsd\n }\n }\n\n private openValueUsd(): number {\n let sum = 0\n for (const pos of this.positions.values()) sum += pos.markUsd ?? pos.investedUsd\n return sum\n }\n\n private recordEquity(now: number): void {\n const openValueUsd = this.openValueUsd()\n this.journal.recordEquity({\n agentId: this.id,\n ts: now,\n realizedUsd: round(this.realizedUsd),\n openValueUsd: round(openValueUsd),\n equityUsd: round(this.realizedUsd + openValueUsd),\n })\n }\n\n private rolloverDay(now: number): void {\n const day = utcDayStart(now)\n if (day !== this.spentDay) {\n this.spentDay = day\n this.spentTodayUsd = 0\n }\n }\n\n /** Live status snapshot for the dashboard API. */\n status(): AgentStatus {\n const openValueUsd = this.openValueUsd()\n return {\n id: this.id,\n strategy: this.strategy.id,\n mode: this.mode,\n running: this.running,\n killed: this.kill.isKilled(),\n limits: this.risk.riskLimits,\n spentTodayUsd: round(this.spentTodayUsd),\n realizedUsd: round(this.realizedUsd),\n openValueUsd: round(openValueUsd),\n equityUsd: round(this.realizedUsd + openValueUsd),\n positions: [...this.positions.values()],\n lastTickAt: this.lastTickAt,\n lastError: this.lastError,\n ticks: this.ticks,\n trades: this.trades,\n refusals: this.refusals,\n }\n }\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6\n}\n","import Database from 'better-sqlite3'\nimport { mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport type { DecisionRecord, EquityPoint, TradeRecord } from './types.js'\n\n/**\n * The decision journal — the agent's black box recorder. Every observe, every\n * refusal, every trade (paper or live), and every equity mark lands here so the\n * dashboard can answer \"why did this trade fire?\" and the whole run is auditable\n * after the fact.\n *\n * SQLite via better-sqlite3 (synchronous, zero-config, embedded). bigints are\n * stored as decimal TEXT — SQLite integers are 64-bit signed and token amounts\n * routinely exceed that, so TEXT is the only lossless option.\n */\nexport class Journal {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n if (dbPath !== ':memory:') mkdirSync(dirname(dbPath), { recursive: true })\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS trades (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n mode TEXT NOT NULL,\n ts INTEGER NOT NULL,\n side TEXT NOT NULL,\n token TEXT NOT NULL,\n token_symbol TEXT NOT NULL,\n quote_token TEXT NOT NULL,\n quote_symbol TEXT NOT NULL,\n amount_in TEXT NOT NULL,\n amount_out TEXT NOT NULL,\n tx_hash TEXT,\n reason TEXT NOT NULL,\n slippage_bps INTEGER NOT NULL,\n gas_estimate TEXT NOT NULL,\n meta TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_trades_agent_ts ON trades(agent_id, ts);\n\n CREATE TABLE IF NOT EXISTS decisions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n ts INTEGER NOT NULL,\n kind TEXT NOT NULL,\n detail TEXT NOT NULL,\n meta TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_decisions_agent_ts ON decisions(agent_id, ts);\n\n CREATE TABLE IF NOT EXISTS equity (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n agent_id TEXT NOT NULL,\n ts INTEGER NOT NULL,\n realized_usd REAL NOT NULL,\n open_value_usd REAL NOT NULL,\n equity_usd REAL NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_equity_agent_ts ON equity(agent_id, ts);\n `)\n }\n\n recordTrade(t: TradeRecord): number {\n const info = this.db\n .prepare(\n `INSERT INTO trades\n (agent_id, mode, ts, side, token, token_symbol, quote_token, quote_symbol,\n amount_in, amount_out, tx_hash, reason, slippage_bps, gas_estimate, meta)\n VALUES (@agent_id,@mode,@ts,@side,@token,@token_symbol,@quote_token,@quote_symbol,\n @amount_in,@amount_out,@tx_hash,@reason,@slippage_bps,@gas_estimate,@meta)`,\n )\n .run({\n agent_id: t.agentId,\n mode: t.mode,\n ts: t.ts,\n side: t.side,\n token: t.token,\n token_symbol: t.tokenSymbol,\n quote_token: t.quoteToken,\n quote_symbol: t.quoteSymbol,\n amount_in: t.amountIn.toString(),\n amount_out: t.amountOut.toString(),\n tx_hash: t.txHash,\n reason: t.reason,\n slippage_bps: t.slippageBps,\n gas_estimate: t.gasEstimate.toString(),\n meta: JSON.stringify(t.meta ?? {}),\n })\n return Number(info.lastInsertRowid)\n }\n\n recordDecision(d: DecisionRecord): number {\n const info = this.db\n .prepare(\n `INSERT INTO decisions (agent_id, ts, kind, detail, meta)\n VALUES (@agent_id,@ts,@kind,@detail,@meta)`,\n )\n .run({\n agent_id: d.agentId,\n ts: d.ts,\n kind: d.kind,\n detail: d.detail,\n meta: JSON.stringify(d.meta ?? {}),\n })\n return Number(info.lastInsertRowid)\n }\n\n recordEquity(e: EquityPoint): void {\n this.db\n .prepare(\n `INSERT INTO equity (agent_id, ts, realized_usd, open_value_usd, equity_usd)\n VALUES (?,?,?,?,?)`,\n )\n .run(e.agentId, e.ts, e.realizedUsd, e.openValueUsd, e.equityUsd)\n }\n\n /** Sum of USDG-denominated spend by an agent since a UTC-day boundary (ms). */\n spentSince(agentId: string, sinceMs: number, quoteSymbol = 'USDG'): number {\n const rows = this.db\n .prepare(\n `SELECT amount_in FROM trades\n WHERE agent_id=? AND ts>=? AND side='buy' AND quote_symbol=?`,\n )\n .all(agentId, sinceMs, quoteSymbol) as { amount_in: string }[]\n // amount_in is USDG (6dp) smallest units → dollars\n return rows.reduce((sum, r) => sum + Number(BigInt(r.amount_in)) / 1e6, 0)\n }\n\n recentTrades(agentId: string, limit = 50): TradeRecord[] {\n const rows = this.db\n .prepare(`SELECT * FROM trades WHERE agent_id=? ORDER BY ts DESC LIMIT ?`)\n .all(agentId, limit) as Record<string, unknown>[]\n return rows.map(rowToTrade)\n }\n\n recentDecisions(agentId: string, limit = 100): DecisionRecord[] {\n const rows = this.db\n .prepare(`SELECT * FROM decisions WHERE agent_id=? ORDER BY ts DESC LIMIT ?`)\n .all(agentId, limit) as Record<string, unknown>[]\n return rows.map((r) => ({\n id: r.id as number,\n agentId: r.agent_id as string,\n ts: r.ts as number,\n kind: r.kind as DecisionRecord['kind'],\n detail: r.detail as string,\n meta: JSON.parse((r.meta as string) || '{}'),\n }))\n }\n\n equityCurve(agentId: string, limit = 500): EquityPoint[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM (\n SELECT * FROM equity WHERE agent_id=? ORDER BY ts DESC LIMIT ?\n ) ORDER BY ts ASC`,\n )\n .all(agentId, limit) as Record<string, unknown>[]\n return rows.map((r) => ({\n agentId: r.agent_id as string,\n ts: r.ts as number,\n realizedUsd: r.realized_usd as number,\n openValueUsd: r.open_value_usd as number,\n equityUsd: r.equity_usd as number,\n }))\n }\n\n /** All trades across every agent, newest first — for the fleet-wide feed. */\n allRecentTrades(limit = 100): TradeRecord[] {\n const rows = this.db\n .prepare(`SELECT * FROM trades ORDER BY ts DESC LIMIT ?`)\n .all(limit) as Record<string, unknown>[]\n return rows.map(rowToTrade)\n }\n\n close(): void {\n this.db.close()\n }\n}\n\nfunction rowToTrade(r: Record<string, unknown>): TradeRecord {\n return {\n id: r.id as number,\n agentId: r.agent_id as string,\n mode: r.mode as TradeRecord['mode'],\n ts: r.ts as number,\n side: r.side as 'buy' | 'sell',\n token: r.token as `0x${string}`,\n tokenSymbol: r.token_symbol as string,\n quoteToken: r.quote_token as `0x${string}`,\n quoteSymbol: r.quote_symbol as string,\n amountIn: BigInt(r.amount_in as string),\n amountOut: BigInt(r.amount_out as string),\n txHash: (r.tx_hash as `0x${string}` | null) ?? null,\n reason: r.reason as string,\n slippageBps: r.slippage_bps as number,\n gasEstimate: BigInt(r.gas_estimate as string),\n meta: JSON.parse((r.meta as string) || '{}'),\n }\n}\n","import { existsSync } from 'node:fs'\n\n/**\n * Global kill switch. Once tripped it is irreversible for the process lifetime:\n * every agent checks `isKilled()` before each decide/execute step and refuses to\n * open new positions. Three independent triggers, per the spec:\n *\n * 1. SIGINT / SIGTERM (Ctrl-C, container stop)\n * 2. the presence of a `KILL` file on disk (drop-a-file panic button)\n * 3. an HTTP `POST /kill` on the dashboard server (wired in server/)\n *\n * The switch never sells or unwinds on its own — it HALTS new risk. Unwinding is\n * an explicit operator action, because a forced market-sell into thin liquidity\n * during whatever caused the panic is usually worse than holding.\n */\nexport class KillSwitch {\n private killed = false\n private reason: string | null = null\n private readonly listeners = new Set<(reason: string) => void>()\n private fileTimer: ReturnType<typeof setInterval> | null = null\n\n constructor(private readonly killFilePath: string) {}\n\n /** Install SIGINT/SIGTERM handlers and begin polling the kill file. */\n arm(): void {\n const onSignal = (sig: string) => () => this.trip(`signal:${sig}`)\n process.once('SIGINT', onSignal('SIGINT'))\n process.once('SIGTERM', onSignal('SIGTERM'))\n\n // Poll the kill file. If it already exists at boot, trip immediately.\n const check = () => {\n if (!this.killed && existsSync(this.killFilePath)) this.trip('kill_file')\n }\n check()\n this.fileTimer = setInterval(check, 1000)\n // Do not keep the event loop alive solely for this poll.\n this.fileTimer.unref?.()\n }\n\n /** Trip the switch. Idempotent. */\n trip(reason: string): void {\n if (this.killed) return\n this.killed = true\n this.reason = reason\n for (const l of this.listeners) {\n try {\n l(reason)\n } catch {\n // a listener throwing must not stop the others from being notified\n }\n }\n }\n\n isKilled(): boolean {\n return this.killed\n }\n\n killReason(): string | null {\n return this.reason\n }\n\n /** Subscribe to the trip event. Returns an unsubscribe fn. */\n onKill(listener: (reason: string) => void): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Stop the file poller (used in tests/teardown). */\n dispose(): void {\n if (this.fileTimer) clearInterval(this.fileTimer)\n this.fileTimer = null\n this.listeners.clear()\n }\n}\n","import {\n createHoodClient,\n getQuote,\n quoteSwap,\n swapAddresses,\n listPricedStockTokens,\n MAINNET_ADDRESSES,\n TESTNET_ADDRESSES,\n USDG_DECIMALS,\n type HoodClient,\n type StockQuote,\n type SwapQuote,\n} from 'hoodchain'\nimport { formatUnits, parseUnits, type Account, type Address } from 'viem'\nimport type { FleetConfig } from './config.js'\n\n/** A memecoin/token spot price sourced from live Uniswap v3 liquidity. */\nexport interface SpotPrice {\n token: Address\n /** USD value of one whole token, derived from a probe quote against USDG (direct or via WETH). */\n priceUsd: number\n /** Quote token the probe routed through. */\n via: 'usdg' | 'weth'\n ts: number\n}\n\n/**\n * The market adapter. Everything the observe/simulate steps need, sourced from\n * live Robinhood Chain state through the `hoodchain` SDK — Chainlink stock\n * feeds, Uniswap v3 quotes (which ARE `eth_call` simulations against real\n * pools), and an ETH/USD reference derived from the on-chain USDG/WETH pool.\n *\n * No price here is fabricated: a token with no liquidity resolves to `null`, and\n * strategies must handle that rather than trade a made-up number.\n */\nexport class Market {\n readonly client: HoodClient\n readonly usdg: Address\n readonly weth: Address\n readonly usdgDecimals = USDG_DECIMALS\n private ethUsdCache: { value: number; ts: number } | null = null\n\n constructor(config: FleetConfig, account?: Account) {\n this.client = createHoodClient({\n chain: config.network,\n rpcUrl: config.rpcUrl,\n account,\n acknowledgeStockTokenEligibility: config.stockTokenEligible,\n })\n const addrs = config.network === 'testnet' ? TESTNET_ADDRESSES : MAINNET_ADDRESSES\n this.usdg = addrs.usdg\n this.weth = config.network === 'testnet' ? TESTNET_ADDRESSES.weth : MAINNET_ADDRESSES.weth\n }\n\n /** Latest block number — a cheap liveness/observe heartbeat. */\n blockNumber(): Promise<bigint> {\n return this.client.public.getBlockNumber()\n }\n\n /**\n * ETH price in USD from the on-chain USDG/WETH pool (quote 0.01 WETH → USDG).\n * Cached for 30s — it moves slowly relative to a trading tick and every call\n * is a real RPC round trip. Returns `null` if no USDG/WETH pool answers.\n */\n async ethUsd(maxAgeMs = 30_000, now = Date.now()): Promise<number | null> {\n if (this.ethUsdCache && now - this.ethUsdCache.ts < maxAgeMs) return this.ethUsdCache.value\n try {\n const probe = parseUnits('0.01', 18)\n const q = await quoteSwap(this.client, { tokenIn: this.weth, tokenOut: this.usdg, amountIn: probe })\n const usd = Number(formatUnits(q.amountOut, this.usdgDecimals)) / 0.01\n this.ethUsdCache = { value: usd, ts: now }\n return usd\n } catch {\n return null\n }\n }\n\n /**\n * Spot USD price of one whole `token` from live liquidity. Probes token→USDG\n * directly, then token→WETH→USDG, using a 1-token probe. Returns `null` when\n * neither route has liquidity.\n */\n async spotPrice(token: Address, tokenDecimals = 18, now = Date.now()): Promise<SpotPrice | null> {\n const probe = parseUnits('1', tokenDecimals)\n // direct → USDG\n try {\n const q = await quoteSwap(this.client, { tokenIn: token, tokenOut: this.usdg, amountIn: probe })\n return { token, priceUsd: Number(formatUnits(q.amountOut, this.usdgDecimals)), via: 'usdg', ts: now }\n } catch {\n /* fall through to WETH route */\n }\n try {\n const q = await quoteSwap(this.client, { tokenIn: token, tokenOut: this.weth, amountIn: probe })\n const eth = await this.ethUsd(30_000, now)\n if (eth === null) return null\n const priceEth = Number(formatUnits(q.amountOut, 18))\n return { token, priceUsd: priceEth * eth, via: 'weth', ts: now }\n } catch {\n return null\n }\n }\n\n /**\n * Quote a buy: how much `token` `amountIn` of `quoteToken` acquires, at live\n * liquidity. This is the simulate step — a QuoterV2 `eth_call`, no state\n * change. Returns `null` when no route fills.\n */\n async quoteBuy(quoteToken: Address, token: Address, amountIn: bigint): Promise<SwapQuote | null> {\n try {\n return await quoteSwap(this.client, { tokenIn: quoteToken, tokenOut: token, amountIn })\n } catch {\n return null\n }\n }\n\n /** Quote a sell: proceeds in `quoteToken` from selling `amount` of `token`. */\n async quoteSell(token: Address, quoteToken: Address, amount: bigint): Promise<SwapQuote | null> {\n try {\n return await quoteSwap(this.client, { tokenIn: token, tokenOut: quoteToken, amountIn: amount })\n } catch {\n return null\n }\n }\n\n /** Chainlink price for a Stock Token (already multiplier-adjusted). */\n async stockChainlinkPrice(symbol: string): Promise<StockQuote | null> {\n try {\n return await getQuote(this.client, symbol)\n } catch {\n return null\n }\n }\n\n /**\n * DEX price of a Stock Token in USD from the USDG pool — the number the\n * premium-watch strategy compares against the Chainlink oracle. Probes with a\n * flat 10 USDG order (small enough to keep impact low across the priced\n * registry) and backs out the implied per-token price from the fill.\n */\n async stockDexPrice(tokenAddress: Address, referenceUsd: number): Promise<number | null> {\n if (referenceUsd <= 0) return null\n // Buy ~$10 of the token with USDG and back out the implied price.\n const usdgIn = parseUnits('10', this.usdgDecimals)\n const q = await this.quoteBuy(this.usdg, tokenAddress, usdgIn)\n if (!q) return null\n const tokensOut = Number(formatUnits(q.amountOut, 18))\n if (tokensOut <= 0) return null\n return 10 / tokensOut\n }\n\n /** Priced Stock Tokens from the registry (those with a Chainlink feed). */\n pricedStockTokens() {\n return listPricedStockTokens()\n }\n\n /** Router/quoter/weth/usdg set for the active network. */\n addresses() {\n return swapAddresses(this.client)\n }\n}\n","import { privateKeyToAccount } from 'viem/accounts'\nimport type { Account } from 'viem'\nimport { Agent } from './agent.js'\nimport type { FleetConfig } from './config.js'\nimport { Journal } from './journal.js'\nimport { KillSwitch } from './kill.js'\nimport { Market } from './market.js'\nimport { utcDayStart } from './risk.js'\nimport type { Strategy } from './strategy.js'\nimport type { AgentStatus, Mode, RiskLimits } from './types.js'\n\n/** Definition of one agent within a fleet. */\nexport interface AgentSpec {\n id: string\n strategy: Strategy\n /** Overrides merged over the fleet default limits. */\n limits?: Partial<RiskLimits>\n tickIntervalMs?: number\n}\n\n/** Aggregate fleet numbers for the dashboard header. */\nexport interface FleetSummary {\n network: string\n mode: Mode\n killed: boolean\n killReason: string | null\n fleetSpentTodayUsd: number\n fleetMaxDailySpendUsdg: number\n realizedUsd: number\n openValueUsd: number\n equityUsd: number\n agents: number\n startedAt: number\n}\n\n/**\n * The fleet: owns the shared market client, journal, and kill switch, then runs\n * a set of agents against them and tracks the global daily-spend budget. It is\n * the process-level object the dashboard server reads and the kill switch acts\n * on.\n */\nexport class Fleet {\n readonly config: FleetConfig\n readonly journal: Journal\n readonly kill: KillSwitch\n readonly market: Market\n private readonly account: Account | null\n private readonly agents: Agent[] = []\n private fleetSpentTodayUsd = 0\n private spentDay = 0\n private startedAt = 0\n\n constructor(config: FleetConfig) {\n this.config = config\n this.account = config.privateKey ? privateKeyToAccount(config.privateKey) : null\n this.journal = new Journal(config.dbPath)\n this.kill = new KillSwitch(config.killFile)\n this.market = new Market(config, this.account ?? undefined)\n }\n\n /** Build agents from specs. */\n addAgents(specs: AgentSpec[]): void {\n for (const spec of specs) {\n const limits: RiskLimits = { ...this.config.defaultLimits, ...spec.limits }\n this.agents.push(\n new Agent({\n id: spec.id,\n strategy: spec.strategy,\n market: this.market,\n limits,\n journal: this.journal,\n kill: this.kill,\n mode: this.config.mode,\n account: this.account,\n fleetMaxDailySpendUsdg: this.config.fleetMaxDailySpendUsdg,\n fleetSpentTodayUsd: () => this.currentFleetSpend(),\n reportFleetSpend: (usd) => this.recordFleetSpend(usd),\n tickIntervalMs: spec.tickIntervalMs ?? 5000,\n }),\n )\n }\n }\n\n private currentFleetSpend(now = Date.now()): number {\n const day = utcDayStart(now)\n if (day !== this.spentDay) {\n this.spentDay = day\n this.fleetSpentTodayUsd = 0\n }\n return this.fleetSpentTodayUsd\n }\n\n private recordFleetSpend(usd: number): void {\n this.currentFleetSpend()\n this.fleetSpentTodayUsd += usd\n }\n\n /** Arm the kill switch and start every agent's loop. */\n async start(): Promise<void> {\n this.startedAt = Date.now()\n this.kill.arm()\n await Promise.all(this.agents.map((a) => a.start()))\n }\n\n /** Stop all agents (kill switch stays tripped if it was). */\n stop(): void {\n for (const a of this.agents) a.stop()\n }\n\n /** Run exactly one tick per agent, in parallel, without arming the interval scheduler. Used by E2E tests. */\n async tickAllOnce(): Promise<void> {\n await Promise.all(this.agents.map((a) => a.tick()))\n }\n\n /** Trip the kill switch — halts new orders across the fleet. */\n tripKill(reason: string): void {\n this.kill.trip(reason)\n }\n\n agentStatuses(): AgentStatus[] {\n return this.agents.map((a) => a.status())\n }\n\n summary(): FleetSummary {\n const statuses = this.agentStatuses()\n return {\n network: this.config.network,\n mode: this.config.mode,\n killed: this.kill.isKilled(),\n killReason: this.kill.killReason(),\n fleetSpentTodayUsd: round(this.currentFleetSpend()),\n fleetMaxDailySpendUsdg: this.config.fleetMaxDailySpendUsdg,\n realizedUsd: round(statuses.reduce((s, a) => s + a.realizedUsd, 0)),\n openValueUsd: round(statuses.reduce((s, a) => s + a.openValueUsd, 0)),\n equityUsd: round(statuses.reduce((s, a) => s + a.equityUsd, 0)),\n agents: this.agents.length,\n startedAt: this.startedAt,\n }\n }\n\n close(): void {\n this.stop()\n this.kill.dispose()\n this.journal.close()\n }\n}\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6\n}\n","import type { HoodNetwork } from 'hoodchain'\nimport type { Mode, RiskLimits } from './types.js'\nimport type { LlmClientConfig, LlmProvider } from './llm.js'\n\n/** Fleet-wide configuration resolved from the environment. */\nexport interface FleetConfig {\n network: HoodNetwork\n rpcUrl: string | undefined\n mode: Mode\n /** Set true only when HOOD_TRADERS_LIVE=1 AND a key is present. */\n hasWallet: boolean\n privateKey: `0x${string}` | undefined\n stockTokenEligible: boolean\n fleetMaxDailySpendUsdg: number\n dashboardPort: number\n killFile: string\n /** Path to the SQLite journal. */\n dbPath: string\n defaultLimits: RiskLimits\n}\n\nfunction num(name: string, fallback: number): number {\n const raw = process.env[name]\n if (raw === undefined || raw === '') return fallback\n const n = Number(raw)\n if (!Number.isFinite(n) || n < 0) {\n throw new Error(`Env ${name}=\"${raw}\" is not a non-negative number`)\n }\n return n\n}\n\nfunction bool(name: string, fallback: boolean): boolean {\n const raw = process.env[name]\n if (raw === undefined || raw === '') return fallback\n return raw === '1' || raw.toLowerCase() === 'true' || raw.toLowerCase() === 'yes'\n}\n\n/**\n * Resolve fleet configuration from the environment.\n *\n * Live mode is deliberately hard to enable by accident: it requires BOTH\n * `HOOD_TRADERS_LIVE=1` and a `ROBINHOOD_CHAIN_PRIVATE_KEY`. Missing either one\n * falls back to paper mode rather than erroring, so a mis-set flag can never\n * silently spend real funds.\n */\nexport function loadFleetConfig(env: NodeJS.ProcessEnv = process.env): FleetConfig {\n const network = (env.HOOD_NETWORK === 'testnet' ? 'testnet' : 'mainnet') as HoodNetwork\n const wantLive = bool('HOOD_TRADERS_LIVE', false)\n const privateKey = env.ROBINHOOD_CHAIN_PRIVATE_KEY as `0x${string}` | undefined\n const hasKey = typeof privateKey === 'string' && /^0x[0-9a-fA-F]{64}$/.test(privateKey)\n const mode: Mode = wantLive && hasKey ? 'live' : 'paper'\n\n return {\n network,\n rpcUrl: env.HOOD_RPC_URL || undefined,\n mode,\n hasWallet: hasKey,\n privateKey: hasKey ? privateKey : undefined,\n stockTokenEligible: bool('HOOD_STOCK_TOKEN_ELIGIBLE', false),\n fleetMaxDailySpendUsdg: num('FLEET_MAX_DAILY_SPEND_USDG', 250),\n dashboardPort: num('DASHBOARD_PORT', 4670),\n killFile: env.KILL_FILE || './KILL',\n dbPath: env.HOOD_TRADERS_DB || './data/hood-traders.db',\n defaultLimits: {\n maxPositionUsdg: num('AGENT_MAX_POSITION_USDG', 50),\n maxDailySpendUsdg: num('AGENT_MAX_DAILY_SPEND_USDG', 100),\n maxSlippageBps: num('AGENT_MAX_SLIPPAGE_BPS', 100),\n cooldownSeconds: num('AGENT_COOLDOWN_SECONDS', 60),\n },\n }\n}\n\nconst LLM_PROVIDERS: readonly LlmProvider[] = ['anthropic', 'openai', 'groq', 'openrouter']\n\n/**\n * Resolve LLM config for {@link LlmStrategist} from the environment. Returns\n * `null` when `HOOD_LLM_PROVIDER` or `HOOD_LLM_API_KEY` is unset — the\n * strategy is optional and simply isn't added to the fleet in that case (see\n * main.ts). Throws only when `HOOD_LLM_PROVIDER` is set to an unrecognized\n * value, since that is very likely a typo the operator would want to know\n * about immediately rather than silently running without the strategy.\n */\nexport function loadLlmConfig(env: NodeJS.ProcessEnv = process.env): LlmClientConfig | null {\n const provider = env.HOOD_LLM_PROVIDER\n const apiKey = env.HOOD_LLM_API_KEY\n if (!provider && !apiKey) return null\n if (!provider || !apiKey) {\n throw new Error(\n 'hood-traders config.ts: HOOD_LLM_PROVIDER and HOOD_LLM_API_KEY must both be set to enable llm-strategist (or both left unset to disable it).',\n )\n }\n if (!LLM_PROVIDERS.includes(provider as LlmProvider)) {\n throw new Error(`hood-traders config.ts: HOOD_LLM_PROVIDER=\"${provider}\" is not one of ${LLM_PROVIDERS.join(', ')}`)\n }\n return {\n provider: provider as LlmProvider,\n apiKey,\n model: env.HOOD_LLM_MODEL || undefined,\n timeoutMs: num('HOOD_LLM_TIMEOUT_MS', 9000),\n }\n}\n\n/** Minimum LLM confidence required to convert a `buy` verdict into a trade. */\nexport function loadLlmMinConfidence(env: NodeJS.ProcessEnv = process.env): number {\n return num('HOOD_LLM_MIN_CONFIDENCE', 0.6)\n}\n","import { erc20Abi, watchLaunches, type Launch } from 'hoodchain'\nimport { formatUnits, parseEther, type Address } from 'viem'\nimport type {\n Strategy,\n StrategyMeta,\n StrategyStartContext,\n StrategyTickContext,\n} from '../framework/strategy.js'\nimport type { Decision, Intent, Alert } from '../framework/types.js'\n\n/** Tunables for {@link LaunchSniper}. */\nexport interface LaunchSniperParams {\n /** WETH spent per entry. */\n entryWeth: number\n /** Take profit as a fraction (0.5 = +50%). */\n takeProfitPct: number\n /** Stop loss as a fraction (0.3 = -30%). */\n stopLossPct: number\n /** Force-exit a position after this many seconds regardless of PnL. */\n maxHoldSeconds: number\n /** Reject a launch if the deployer still holds more than this fraction of supply. */\n maxDeployerPct: number\n /** Reject if an immediate buy→sell round trip would lose more than this fraction (honeypot/thin-pool guard). */\n maxRoundTripLossPct: number\n /** Only consider launches at most this many seconds old when first seen. */\n maxLaunchAgeSeconds: number\n}\n\nconst DEFAULTS: LaunchSniperParams = {\n entryWeth: 0.01,\n takeProfitPct: 0.6,\n stopLossPct: 0.35,\n maxHoldSeconds: 30 * 60,\n maxDeployerPct: 0.15,\n maxRoundTripLossPct: 0.35,\n maxLaunchAgeSeconds: 5 * 60,\n}\n\ninterface Candidate {\n launch: Launch\n seenAt: number\n}\n\n/**\n * launch-sniper — enter brand-new launchpad coins that clear objective safety\n * filters, then exit on take-profit, stop, or a hard time limit.\n *\n * EDGE HYPOTHESIS: the first minutes after a NOXA instant-listing are the most\n * information-rich and most volatile window a memecoin ever has. A disciplined\n * buyer who (a) only touches tokens that are actually round-trippable (not\n * honeypots) and whose deployer is not sitting on the supply, and (b) exits\n * mechanically instead of falling in love, harvests a slice of that opening\n * volatility. The edge is speed + discipline, not prediction.\n *\n * FAILURE MODES: most new launches go to zero — the stop loss WILL fire often\n * and the strategy is negative-carry unless the winners pay for the losers.\n * Honeypots evolve (sell-tax toggled AFTER you buy); the round-trip check only\n * sees the state at entry. Odyssey tokens on the bonding curve have no Uniswap\n * pool yet, so they are skipped until graduation — this strategy is really a\n * NOXA/graduated-pool sniper. Paper fills assume the QuoterV2 mid; a real\n * sniper competes with faster bots and eats worse fills.\n */\nexport class LaunchSniper implements Strategy {\n readonly id = 'launch-sniper'\n readonly title = 'Launch Sniper'\n readonly quote = 'weth' as const\n private readonly p: LaunchSniperParams\n private readonly queue: Candidate[] = []\n private readonly seen = new Set<string>()\n private unwatch: (() => void) | null = null\n\n constructor(params: Partial<LaunchSniperParams> = {}) {\n this.p = { ...DEFAULTS, ...params }\n }\n\n get meta(): StrategyMeta {\n return {\n edge:\n 'Harvest opening-minutes volatility of freshly launched memecoins by buying only round-trippable, ' +\n 'non-deployer-heavy launches and exiting mechanically on TP/stop/time.',\n failureModes: [\n 'Most launches trend to zero — the stop loss fires frequently; profitability depends on winners covering losers.',\n 'Honeypots can enable a sell tax AFTER entry; the entry-time round-trip check cannot see that.',\n 'Odyssey bonding-curve tokens have no Uniswap pool pre-graduation and are skipped (this is effectively a NOXA sniper).',\n 'Paper fills use the QuoterV2 mid; live, faster bots win the best fills and you eat slippage.',\n ],\n params: { ...this.p },\n }\n }\n\n start(ctx: StrategyStartContext): void {\n // Real-time launch subscription across NOXA + The Odyssey.\n this.unwatch = watchLaunches(\n ctx.market.client,\n (launch) => {\n const key = launch.token.toLowerCase()\n if (this.seen.has(key)) return\n this.seen.add(key)\n this.queue.push({ launch, seenAt: Date.now() })\n ctx.log(`new launch queued: ${launch.launchpad} ${launch.token}`, { creator: launch.creator })\n },\n { onError: (e) => ctx.log(`launch watcher error: ${e.message}`) },\n )\n }\n\n stop(): void {\n this.unwatch?.()\n this.unwatch = null\n }\n\n async tick(ctx: StrategyTickContext): Promise<Decision> {\n const intents: Intent[] = []\n const alerts: Alert[] = []\n\n // ── exits first (protect open risk before taking on more) ──────────────────\n for (const pos of ctx.positions) {\n const ageSec = (ctx.now - pos.openedAt) / 1000\n const pnlPct = pos.markUsd !== null && pos.investedUsd > 0 ? pos.markUsd / pos.investedUsd - 1 : null\n let exitReason: string | null = null\n if (pnlPct !== null && pnlPct >= this.p.takeProfitPct) exitReason = `take-profit ${(pnlPct * 100).toFixed(1)}%`\n else if (pnlPct !== null && pnlPct <= -this.p.stopLossPct) exitReason = `stop-loss ${(pnlPct * 100).toFixed(1)}%`\n else if (ageSec >= this.p.maxHoldSeconds) exitReason = `time-exit ${Math.round(ageSec)}s held`\n if (exitReason) {\n intents.push({\n side: 'sell',\n token: pos.token,\n tokenSymbol: pos.tokenSymbol,\n amountIn: pos.amount,\n quoteToken: pos.quoteToken,\n quoteSymbol: pos.quoteSymbol,\n reason: exitReason,\n meta: { pnlPct },\n })\n }\n }\n\n // ── one new entry per tick (evaluate the oldest queued candidate) ──────────\n const candidate = this.queue.shift()\n if (candidate) {\n const decisionOrReject = await this.evaluate(ctx, candidate)\n if (decisionOrReject.intent) intents.push(decisionOrReject.intent)\n else if (decisionOrReject.alert) alerts.push(decisionOrReject.alert)\n }\n\n return { intents, alerts }\n }\n\n private async evaluate(\n ctx: StrategyTickContext,\n candidate: Candidate,\n ): Promise<{ intent?: Intent; alert?: Alert }> {\n const { launch } = candidate\n const ageSec = (ctx.now - candidate.seenAt) / 1000\n if (ageSec > this.p.maxLaunchAgeSeconds) {\n return { alert: { level: 'info', message: `skip ${launch.token}: stale (${Math.round(ageSec)}s old)`, meta: {} } }\n }\n // already holding it?\n if (ctx.positions.some((p) => p.token.toLowerCase() === launch.token.toLowerCase())) return {}\n\n const amountIn = parseEther(String(this.p.entryWeth))\n\n // Filter 1 — route exists (Odyssey pre-graduation tokens fail here and are skipped).\n const buyQuote = await ctx.market.quoteBuy(ctx.quoteToken, launch.token, amountIn)\n if (!buyQuote || buyQuote.amountOut <= 0n) {\n return { alert: { level: 'info', message: `skip ${launch.token}: no liquid Uniswap route`, meta: { launchpad: launch.launchpad } } }\n }\n\n // Filter 2 — round-trip retention (honeypot / thin-pool guard).\n const sellQuote = await ctx.market.quoteSell(launch.token, ctx.quoteToken, buyQuote.amountOut)\n if (!sellQuote || sellQuote.amountOut <= 0n) {\n return { alert: { level: 'warn', message: `skip ${launch.token}: cannot sell back (honeypot?)`, meta: {} } }\n }\n const retention = Number(sellQuote.amountOut) / Number(amountIn)\n if (1 - retention > this.p.maxRoundTripLossPct) {\n return {\n alert: {\n level: 'warn',\n message: `skip ${launch.token}: round-trip loss ${((1 - retention) * 100).toFixed(1)}% > ${(this.p.maxRoundTripLossPct * 100).toFixed(0)}%`,\n meta: { retention },\n },\n }\n }\n\n // Filter 3 — deployer concentration.\n const deployerPct = await this.deployerConcentration(ctx, launch)\n if (deployerPct !== null && deployerPct > this.p.maxDeployerPct) {\n return {\n alert: {\n level: 'warn',\n message: `skip ${launch.token}: deployer holds ${(deployerPct * 100).toFixed(1)}% > ${(this.p.maxDeployerPct * 100).toFixed(0)}%`,\n meta: { deployerPct },\n },\n }\n }\n\n return {\n intent: {\n side: 'buy',\n token: launch.token,\n tokenSymbol: shortToken(launch.token),\n amountIn,\n quoteToken: ctx.quoteToken,\n quoteSymbol: ctx.quoteSymbol,\n reason: `sniped ${launch.launchpad} launch — retention ${(retention * 100).toFixed(1)}%, deployer ${deployerPct === null ? 'n/a' : (deployerPct * 100).toFixed(1) + '%'}`,\n meta: { launchpad: launch.launchpad, deployerPct, retention },\n },\n }\n }\n\n private async deployerConcentration(ctx: StrategyTickContext, launch: Launch): Promise<number | null> {\n try {\n const [supply, bal] = await ctx.market.client.public.multicall({\n contracts: [\n { address: launch.token, abi: erc20Abi, functionName: 'totalSupply' as const },\n { address: launch.token, abi: erc20Abi, functionName: 'balanceOf' as const, args: [launch.creator] as const },\n ],\n allowFailure: false,\n })\n if ((supply as bigint) === 0n) return null\n return Number(formatUnits((bal as bigint) * 10_000n / (supply as bigint), 4))\n } catch {\n return null\n }\n }\n}\n\nfunction shortToken(addr: Address): string {\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`\n}\n","import { getRecentLaunches, parseUsdg, type Launch } from 'hoodchain'\nimport type { Address } from 'viem'\nimport type {\n Strategy,\n StrategyMeta,\n StrategyTickContext,\n} from '../framework/strategy.js'\nimport type { Decision, Intent, Alert } from '../framework/types.js'\n\n/** Tunables for {@link Momentum}. */\nexport interface MomentumParams {\n /** USDG spent per entry. */\n entryUsdg: number\n /** Minimum price gain over the lookback window to count as a breakout. */\n breakoutPct: number\n /** How many price samples (ticks) form the lookback window. */\n lookbackSamples: number\n /** Trailing stop: exit if price falls this fraction off its post-entry peak. */\n trailingStopPct: number\n /** Hard time exit regardless of PnL. */\n maxHoldSeconds: number\n /** How many graduated tokens to track price history for (most-recently-graduated first). */\n maxTracked: number\n /** Blocks to look back for discovering graduated tokens. */\n discoveryLookbackBlocks: bigint\n}\n\nconst DEFAULTS: MomentumParams = {\n entryUsdg: 10,\n breakoutPct: 0.15,\n lookbackSamples: 6,\n trailingStopPct: 0.2,\n maxHoldSeconds: 60 * 60,\n maxTracked: 15,\n discoveryLookbackBlocks: 200_000n,\n}\n\ninterface PriceHistory {\n token: Address\n symbol: string\n samples: { ts: number; priceUsd: number }[]\n}\n\n/**\n * momentum — volume+price breakout entries on already-graduated (liquid)\n * tokens, trailing-stop exits.\n *\n * EDGE HYPOTHESIS: a token that has already graduated to a locked Uniswap v3\n * pool has survived the highest-mortality phase (the bonding curve / first\n * minutes). A subsequent breakout — price up sharply over a short lookback,\n * confirmed by the pool actually being tradable at size — reflects real\n * incoming demand rather than launch-day noise, and trend-following that with\n * a trailing stop captures the middle of the move while giving back only the\n * tail.\n *\n * FAILURE MODES: breakouts on illiquid tokens are trivially fakeable by a\n * single wallet round-tripping the pool; the strategy only looks at price, not\n * depth, so it can chase a move that reverses the instant it stops buying.\n * Trailing stops whipsaw in choppy markets — expect a string of small losses\n * between real trends. Discovery only looks at NOXA (instant-listed, so\n * \"graduated\" from day one) and Odyssey `PoolMigrated` history within the\n * lookback window; a real breakout minutes after that window closes is missed\n * until the next discovery pass.\n */\nexport class Momentum implements Strategy {\n readonly id = 'momentum'\n readonly title = 'Momentum'\n readonly quote = 'usdg' as const\n private readonly p: MomentumParams\n private readonly history = new Map<string, PriceHistory>()\n private readonly peakSinceEntry = new Map<string, number>()\n private lastDiscoveryAt = 0\n\n constructor(params: Partial<MomentumParams> = {}) {\n this.p = { ...DEFAULTS, ...params }\n }\n\n get meta(): StrategyMeta {\n return {\n edge:\n 'Trend-follow price breakouts on already-graduated, liquid tokens — surviving the launch phase filters ' +\n 'for real demand, and a trailing stop rides the middle of a move.',\n failureModes: [\n 'Breakouts on thin pools are trivially fakeable by a single wallet; price-only signal has no depth check.',\n 'Trailing stops whipsaw in choppy conditions — a string of small losses between real trends is expected.',\n 'Discovery only scans a bounded block lookback; breakouts on tokens outside that window are missed until the next pass.',\n 'Momentum has no edge in a flat or declining market — it is a trend-only strategy by design.',\n ],\n params: { ...this.p },\n }\n }\n\n async tick(ctx: StrategyTickContext): Promise<Decision> {\n const intents: Intent[] = []\n const alerts: Alert[] = []\n\n // Refresh the tracked-token set periodically (cheap log scan, not every tick).\n if (ctx.now - this.lastDiscoveryAt > 5 * 60_000 || this.history.size === 0) {\n await this.discover(ctx)\n this.lastDiscoveryAt = ctx.now\n }\n\n // Sample current prices for tracked tokens.\n for (const h of this.history.values()) {\n const spot = await ctx.market.spotPrice(h.token)\n if (!spot) continue\n h.samples.push({ ts: ctx.now, priceUsd: spot.priceUsd })\n if (h.samples.length > this.p.lookbackSamples + 1) h.samples.shift()\n }\n\n // ── exits: trailing stop / time exit ────────────────────────────────────────\n for (const pos of ctx.positions) {\n const key = pos.token.toLowerCase()\n const spot = await ctx.market.spotPrice(pos.token)\n const ageSec = (ctx.now - pos.openedAt) / 1000\n if (spot) {\n const peak = Math.max(this.peakSinceEntry.get(key) ?? spot.priceUsd, spot.priceUsd)\n this.peakSinceEntry.set(key, peak)\n const drawdown = peak > 0 ? 1 - spot.priceUsd / peak : 0\n if (drawdown >= this.p.trailingStopPct) {\n intents.push(this.exitIntent(pos, ctx, `trailing stop: ${(drawdown * 100).toFixed(1)}% off peak $${peak.toFixed(6)}`))\n this.peakSinceEntry.delete(key)\n continue\n }\n }\n if (ageSec >= this.p.maxHoldSeconds) {\n intents.push(this.exitIntent(pos, ctx, `time-exit ${Math.round(ageSec)}s held`))\n this.peakSinceEntry.delete(key)\n }\n }\n\n // ── entries: breakout over the lookback window ──────────────────────────────\n for (const h of this.history.values()) {\n if (h.samples.length < this.p.lookbackSamples) continue\n if (ctx.positions.some((p) => p.token.toLowerCase() === h.token.toLowerCase())) continue\n const first = h.samples[h.samples.length - this.p.lookbackSamples]\n const last = h.samples[h.samples.length - 1]\n if (!first || !last || first.priceUsd <= 0) continue\n const gain = last.priceUsd / first.priceUsd - 1\n if (gain >= this.p.breakoutPct) {\n const amountIn = parseUsdg(String(this.p.entryUsdg))\n intents.push({\n side: 'buy',\n token: h.token,\n tokenSymbol: h.symbol,\n amountIn,\n quoteToken: ctx.quoteToken,\n quoteSymbol: ctx.quoteSymbol,\n reason: `breakout +${(gain * 100).toFixed(1)}% over ${this.p.lookbackSamples} samples ($${first.priceUsd.toFixed(6)}→$${last.priceUsd.toFixed(6)})`,\n meta: { gain, entryPriceUsd: last.priceUsd },\n })\n this.peakSinceEntry.set(h.token.toLowerCase(), last.priceUsd)\n } else {\n alerts.push({\n level: 'info',\n message: `${h.symbol} watch: ${(gain * 100).toFixed(1)}% vs ${(this.p.breakoutPct * 100).toFixed(0)}% breakout threshold`,\n })\n }\n }\n\n return { intents, alerts }\n }\n\n private exitIntent(pos: StrategyTickContext['positions'][number], ctx: StrategyTickContext, reason: string): Intent {\n return {\n side: 'sell',\n token: pos.token,\n tokenSymbol: pos.tokenSymbol,\n amountIn: pos.amount,\n quoteToken: pos.quoteToken,\n quoteSymbol: pos.quoteSymbol,\n reason,\n }\n }\n\n private async discover(ctx: StrategyTickContext): Promise<void> {\n let launches: Launch[]\n try {\n launches = await getRecentLaunches(ctx.market.client, { lookbackBlocks: this.p.discoveryLookbackBlocks })\n } catch (err) {\n ctx.log(`momentum discovery failed: ${err instanceof Error ? err.message : String(err)}`)\n return\n }\n // NOXA tokens are tradable from block one; Odyssey tokens need a migrated pool —\n // approximated here by simply attempting a spot price and dropping tokens with none.\n const graduated = launches.filter((l) => l.launchpad === 'noxa' || l.pool !== null)\n const candidates = graduated.slice(-this.p.maxTracked)\n for (const l of candidates) {\n const key = l.token.toLowerCase()\n if (this.history.has(key)) continue\n const spot = await ctx.market.spotPrice(l.token)\n if (!spot) continue\n this.history.set(key, { token: l.token, symbol: shortToken(l.token), samples: [{ ts: ctx.now, priceUsd: spot.priceUsd }] })\n }\n // Evict tokens no longer in the discovered window to keep the tracked set fresh.\n const keep = new Set(candidates.map((l) => l.token.toLowerCase()))\n for (const key of [...this.history.keys()]) {\n if (!keep.has(key) && !ctx.positions.some((p) => p.token.toLowerCase() === key)) this.history.delete(key)\n }\n }\n}\n\nfunction shortToken(addr: Address): string {\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`\n}\n","import { getStockToken, parseUsdg } from 'hoodchain'\nimport type {\n Strategy,\n StrategyMeta,\n StrategyTickContext,\n} from '../framework/strategy.js'\nimport type { Alert, Decision, Intent } from '../framework/types.js'\n\n/** Tunables for {@link PremiumWatch}. */\nexport interface PremiumWatchParams {\n /** Symbols to watch. Defaults to a handful of the most liquid priced Stock Tokens. */\n symbols: string[];\n /** Premium/discount (bps, |dex - oracle| / oracle) that triggers an alert. */\n alertThresholdBps: number\n /** Premium/discount (bps) that triggers a convergence TRADE — only reachable in trade-eligible config. */\n tradeThresholdBps: number\n /** USDG spent per convergence trade, when trading is enabled. */\n tradeSizeUsdg: number\n /** Exit once the premium reverts to within this many bps of fair. */\n exitThresholdBps: number\n /** Hard time exit for a convergence position regardless of premium. */\n maxHoldSeconds: number\n /**\n * Convergence TRADING is opt-in and requires BOTH this flag AND\n * `market.client.acknowledgeStockTokenEligibility === true` (the operator's\n * non-US-person affirmation, per `_shared.md`). Missing either keeps the\n * strategy strictly alerts-only, which is the default and the recommended mode.\n */\n enableTrading: boolean\n}\n\nconst DEFAULT_SYMBOLS = ['AAPL', 'TSLA', 'NVDA', 'AMZN', 'MSFT']\n\nconst DEFAULTS: PremiumWatchParams = {\n symbols: DEFAULT_SYMBOLS,\n alertThresholdBps: 50,\n tradeThresholdBps: 150,\n tradeSizeUsdg: 20,\n exitThresholdBps: 20,\n maxHoldSeconds: 2 * 60 * 60,\n enableTrading: false,\n}\n\n/**\n * premium-watch — tracks the spread between each Stock Token's Chainlink\n * oracle price and its live Uniswap v3 DEX price, and alerts on premium/discount.\n * Trades the convergence ONLY when explicitly enabled AND the operator has\n * affirmed Stock Token eligibility — otherwise it is alerts-only, which is the\n * default and the recommended mode for anyone who hasn't cleared the legal gate\n * in `_shared.md`.\n *\n * EDGE HYPOTHESIS: Stock Token pools are much thinner than the underlying\n * equity market, so DEX price can drift from the Chainlink oracle (itself fed\n * by the real market) when flow is one-sided. If the drift is a liquidity\n * artifact rather than new information, buying the discount / shorting the\n * premium (here: buying the discount and later selling back at fair, since the\n * SDK has no short primitive) captures the reversion as the pool re-equilibrates\n * via arbitrage or the next informed trade.\n *\n * FAILURE MODES: a premium can be RIGHT — the DEX price can be reacting to\n * information the Chainlink heartbeat (up to 24h) hasn't caught up to yet, in\n * which case \"fading\" it loses money on purpose. Stock Token pools are thin;\n * the $10 probe used to read `stockDexPrice` may not reflect the price your\n * actual trade size gets, and the trade itself moves the price you're trying to\n * arbitrage. Only long convergence is possible (no shorting), so premiums\n * (DEX rich) are alert-only even when trading is enabled — asymmetric coverage\n * by construction. Chainlink staleness (weekends, feed outages) can make a\n * quote look like a discount when it is just old.\n */\nexport class PremiumWatch implements Strategy {\n readonly id = 'premium-watch'\n readonly title = 'Premium Watch'\n readonly quote = 'usdg' as const\n private readonly p: PremiumWatchParams\n\n constructor(params: Partial<PremiumWatchParams> = {}) {\n this.p = { ...DEFAULTS, ...params }\n }\n\n get meta(): StrategyMeta {\n return {\n edge:\n 'Track Chainlink-vs-DEX spread on Stock Tokens and, only in eligible+opted-in configuration, buy discounts ' +\n 'expecting reversion as the thin pool re-equilibrates. Default mode is alerts-only.',\n failureModes: [\n 'A premium/discount can be correct — DEX price may reflect information the ≤24h Chainlink heartbeat has not caught up to; fading it then loses on purpose.',\n 'Stock Token pools are thin; the probe price does not reflect real trade-size slippage, and the trade itself moves the spread being arbitraged.',\n 'No shorting primitive exists, so only discounts (DEX cheap) are ever tradeable — premiums are alert-only by construction, not by choice.',\n 'Chainlink staleness (weekend gap, feed outage) can masquerade as a discount when the oracle, not the pool, is stale.',\n 'Trading requires an explicit eligibility affirmation (Stock Tokens cannot be sold to US/CA/UK/CH persons) — default is alerts-only until the operator opts in.',\n ],\n params: { ...this.p, enableTrading: this.p.enableTrading },\n }\n }\n\n async tick(ctx: StrategyTickContext): Promise<Decision> {\n const intents: Intent[] = []\n const alerts: Alert[] = []\n const tradingLive = this.p.enableTrading && ctx.market.client.acknowledgeStockTokenEligibility\n\n if (this.p.enableTrading && !ctx.market.client.acknowledgeStockTokenEligibility) {\n alerts.push({\n level: 'warn',\n message:\n 'premium-watch: enableTrading=true but the client has not acknowledged Stock Token eligibility — staying alerts-only',\n })\n }\n\n // ── exits: convergence positions revert or time out ────────────────────────\n for (const pos of ctx.positions) {\n const symbol = pos.tokenSymbol\n const spread = await this.readSpread(ctx, symbol)\n const ageSec = (ctx.now - pos.openedAt) / 1000\n let exitReason: string | null = null\n if (spread && Math.abs(spread.bps) <= this.p.exitThresholdBps) {\n exitReason = `converged: spread ${spread.bps.toFixed(0)}bps within exit band`\n } else if (ageSec >= this.p.maxHoldSeconds) {\n exitReason = `time-exit ${Math.round(ageSec)}s held`\n }\n if (exitReason) {\n intents.push({\n side: 'sell',\n token: pos.token,\n tokenSymbol: symbol,\n amountIn: pos.amount,\n quoteToken: pos.quoteToken,\n quoteSymbol: pos.quoteSymbol,\n reason: exitReason,\n })\n }\n }\n\n // ── observe + (maybe) enter ─────────────────────────────────────────────────\n for (const symbol of this.p.symbols) {\n const spread = await this.readSpread(ctx, symbol)\n if (!spread) continue\n\n const direction = spread.bps > 0 ? 'premium (DEX rich)' : 'discount (DEX cheap)'\n if (Math.abs(spread.bps) >= this.p.alertThresholdBps) {\n alerts.push({\n level: 'warn',\n message: `${symbol} ${direction}: oracle $${spread.oracleUsd.toFixed(2)} vs DEX $${spread.dexUsd.toFixed(2)} (${spread.bps.toFixed(0)}bps)`,\n meta: { symbol, oracleUsd: spread.oracleUsd, dexUsd: spread.dexUsd, bps: spread.bps },\n })\n }\n\n const alreadyHeld = ctx.positions.some((p) => p.tokenSymbol === symbol)\n const isDiscount = spread.bps <= -this.p.tradeThresholdBps\n if (tradingLive && isDiscount && !alreadyHeld) {\n const token = getStockToken(symbol)\n intents.push({\n side: 'buy',\n token: token.address,\n tokenSymbol: symbol,\n amountIn: parseUsdg(String(this.p.tradeSizeUsdg)),\n quoteToken: ctx.quoteToken,\n quoteSymbol: ctx.quoteSymbol,\n reason: `convergence entry: ${symbol} discount ${spread.bps.toFixed(0)}bps (oracle $${spread.oracleUsd.toFixed(2)} vs DEX $${spread.dexUsd.toFixed(2)})`,\n meta: { symbol, oracleUsd: spread.oracleUsd, dexUsd: spread.dexUsd, bps: spread.bps },\n })\n } else if (isDiscount && !tradingLive) {\n alerts.push({\n level: 'info',\n message: `${symbol} discount ${spread.bps.toFixed(0)}bps clears trade threshold but trading is disabled/ineligible — alert only`,\n })\n }\n }\n\n return { intents, alerts }\n }\n\n private async readSpread(\n ctx: StrategyTickContext,\n symbol: string,\n ): Promise<{ oracleUsd: number; dexUsd: number; bps: number } | null> {\n const oracle = await ctx.market.stockChainlinkPrice(symbol)\n if (!oracle) return null\n const token = getStockToken(symbol)\n const dexUsd = await ctx.market.stockDexPrice(token.address, oracle.priceUsd)\n if (dexUsd === null) return null\n const bps = ((dexUsd - oracle.priceUsd) / oracle.priceUsd) * 10_000\n return { oracleUsd: oracle.priceUsd, dexUsd, bps }\n }\n}\n","/**\n * Multi-provider LLM client for {@link LlmStrategist}. Bring your own key: the\n * operator picks exactly one of Anthropic Claude, OpenAI, Groq, or OpenRouter\n * via env config (see {@link loadLlmConfig} in ./config.js) and this module\n * speaks that provider's HTTP API directly with `fetch` — no SDK dependency,\n * matching the rest of this package (better-sqlite3, hoodchain, viem, ws are\n * the only runtime deps).\n *\n * The verdict contract is strict on purpose: a strategy that trades real money\n * must never trust free-text from an LLM. Every provider is prompted to return\n * ONLY a JSON object; the response is parsed with a tolerant extractor (finds\n * the first `{...}` blob, so a model that wraps the JSON in a sentence still\n * works) and validated field-by-field. A malformed or missing verdict throws —\n * the caller (LlmStrategist.tick) treats that as \"skip this candidate, alert\",\n * never as an implicit buy or an implicit skip-silently.\n */\n\nexport type LlmProvider = 'anthropic' | 'openai' | 'groq' | 'openrouter'\n\nexport interface LlmClientConfig {\n provider: LlmProvider\n apiKey: string\n /** Falls back to a sane per-provider default (see {@link DEFAULT_MODELS}) when unset. */\n model?: string\n /** Abort the request after this many ms. Default 9000. */\n timeoutMs?: number\n}\n\nexport interface LlmVerdict {\n buy: boolean\n /** Clamped to [0, 1]. */\n confidence: number\n thesis: string\n}\n\n/**\n * Default model per provider. Anthropic and OpenRouter defaults are stable\n * (a dated snapshot and an auto-router, respectively). OpenAI/Groq model\n * catalogs move faster — `HOOD_LLM_MODEL` overrides any of these; if a default\n * ever goes stale the provider call fails with a clear \"check HOOD_LLM_MODEL\"\n * error (see {@link callProvider}) rather than a silent misroute.\n */\nconst DEFAULT_MODELS: Record<LlmProvider, string> = {\n anthropic: 'claude-haiku-4-5-20251001',\n openai: 'gpt-4o-mini',\n groq: 'llama-3.3-70b-versatile',\n openrouter: 'openrouter/auto',\n}\n\nconst SYSTEM_PROMPT = [\n 'You are a risk-averse trading analyst judging a brand-new token launch on Robinhood Chain,',\n 'a 24/7 permissionless DEX environment where most launches are worthless or scams.',\n 'You will be given real on-chain facts about one launch: its launchpad, whether a buy-then-sell',\n 'round trip retains value (a honeypot signal), and what fraction of supply the deployer wallet',\n 'still holds (a rug-risk signal). You have no access to socials, team identity, or any off-chain',\n 'information — judge only from what is given.',\n '',\n 'Reply with ONLY a single JSON object, no prose before or after it, matching exactly:',\n '{\"buy\": boolean, \"confidence\": number between 0 and 1, \"thesis\": \"one sentence\"}',\n '',\n '\"buy\" should be true only when the facts given suggest this is unusually clean for a brand-new',\n 'launch (high retention, low deployer concentration) — most launches should get buy:false.',\n '\"confidence\" reflects how sure you are in that judgment given how thin the available signal is.',\n].join('\\n')\n\n/** Ask the configured LLM to judge a launch brief. Throws on any failure (timeout, HTTP error, malformed verdict). */\nexport async function judgeLaunch(cfg: LlmClientConfig, brief: string): Promise<LlmVerdict> {\n const model = cfg.model || DEFAULT_MODELS[cfg.provider]\n const timeoutMs = cfg.timeoutMs ?? 9000\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const text = await callProvider(cfg.provider, cfg.apiKey, model, brief, controller.signal)\n return parseVerdict(text)\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n throw new Error(`hood-traders llm.ts: ${cfg.provider} request timed out after ${timeoutMs}ms`)\n }\n throw err\n } finally {\n clearTimeout(timer)\n }\n}\n\nasync function callProvider(\n provider: LlmProvider,\n apiKey: string,\n model: string,\n brief: string,\n signal: AbortSignal,\n): Promise<string> {\n if (provider === 'anthropic') {\n const res = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n signal,\n headers: {\n 'content-type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model,\n max_tokens: 300,\n system: SYSTEM_PROMPT,\n messages: [{ role: 'user', content: brief }],\n }),\n })\n const body = await res.text()\n if (!res.ok) throw providerError('anthropic', model, res.status, body)\n const data = JSON.parse(body) as { content?: { text?: string }[] }\n const text = data.content?.[0]?.text\n if (!text) throw new Error(`hood-traders llm.ts: anthropic response had no content text: ${body.slice(0, 300)}`)\n return text\n }\n\n // openai, groq, and openrouter all speak the OpenAI chat-completions shape.\n const { url, extraHeaders } = openAiCompatibleEndpoint(provider)\n const res = await fetch(url, {\n method: 'POST',\n signal,\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${apiKey}`,\n ...extraHeaders,\n },\n body: JSON.stringify({\n model,\n max_tokens: 300,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: brief },\n ],\n }),\n })\n const body = await res.text()\n if (!res.ok) throw providerError(provider, model, res.status, body)\n const data = JSON.parse(body) as { choices?: { message?: { content?: string } }[] }\n const text = data.choices?.[0]?.message?.content\n if (!text) throw new Error(`hood-traders llm.ts: ${provider} response had no message content: ${body.slice(0, 300)}`)\n return text\n}\n\nfunction openAiCompatibleEndpoint(provider: 'openai' | 'groq' | 'openrouter'): {\n url: string\n extraHeaders: Record<string, string>\n} {\n switch (provider) {\n case 'openai':\n return { url: 'https://api.openai.com/v1/chat/completions', extraHeaders: {} }\n case 'groq':\n return { url: 'https://api.groq.com/openai/v1/chat/completions', extraHeaders: {} }\n case 'openrouter':\n return {\n url: 'https://openrouter.ai/api/v1/chat/completions',\n extraHeaders: {\n 'HTTP-Referer': 'https://github.com/nirholas/hood-traders',\n 'X-Title': 'hood-traders',\n },\n }\n }\n}\n\nfunction providerError(provider: LlmProvider, model: string, status: number, body: string): Error {\n return new Error(\n `hood-traders llm.ts: ${provider} rejected request (HTTP ${status}, model=\"${model}\"). ` +\n `If this is a model-not-found error, set HOOD_LLM_MODEL to a current model id for this provider. ` +\n `Response: ${body.slice(0, 300)}`,\n )\n}\n\n/** Extract the first `{...}` blob from `text` and validate it as an {@link LlmVerdict}. Throws on any mismatch. */\nexport function parseVerdict(text: string): LlmVerdict {\n const match = text.match(/\\{[\\s\\S]*\\}/)\n if (!match) throw new Error(`hood-traders llm.ts: no JSON object found in LLM response: ${text.slice(0, 300)}`)\n let raw: unknown\n try {\n raw = JSON.parse(match[0])\n } catch (err) {\n throw new Error(`hood-traders llm.ts: LLM response JSON did not parse: ${(err as Error).message}`)\n }\n if (typeof raw !== 'object' || raw === null) {\n throw new Error('hood-traders llm.ts: LLM verdict was not a JSON object')\n }\n const v = raw as Record<string, unknown>\n if (typeof v.buy !== 'boolean') {\n throw new Error(`hood-traders llm.ts: LLM verdict missing boolean \"buy\": ${JSON.stringify(v)}`)\n }\n if (typeof v.thesis !== 'string' || v.thesis.trim().length === 0) {\n throw new Error(`hood-traders llm.ts: LLM verdict missing non-empty \"thesis\": ${JSON.stringify(v)}`)\n }\n const confidenceRaw = typeof v.confidence === 'number' ? v.confidence : Number(v.confidence)\n if (!Number.isFinite(confidenceRaw)) {\n throw new Error(`hood-traders llm.ts: LLM verdict has non-numeric \"confidence\": ${JSON.stringify(v)}`)\n }\n const confidence = Math.min(1, Math.max(0, confidenceRaw))\n return { buy: v.buy, confidence, thesis: v.thesis.trim() }\n}\n","import { erc20Abi, watchLaunches, type Launch } from 'hoodchain'\nimport { formatUnits, parseEther, type Address } from 'viem'\nimport type {\n Strategy,\n StrategyMeta,\n StrategyStartContext,\n StrategyTickContext,\n} from '../framework/strategy.js'\nimport type { Decision, Intent, Alert } from '../framework/types.js'\nimport { judgeLaunch, type LlmClientConfig, type LlmVerdict } from '../framework/llm.js'\n\n/** Tunables for {@link LlmStrategist}. */\nexport interface LlmStrategistParams {\n llm: LlmClientConfig\n /** WETH spent per entry when the LLM says buy. */\n entryWeth: number\n /** Take profit as a fraction (0.5 = +50%). */\n takeProfitPct: number\n /** Stop loss as a fraction (0.3 = -30%). */\n stopLossPct: number\n /** Force-exit a position after this many seconds regardless of PnL. */\n maxHoldSeconds: number\n /** Minimum LLM confidence required to convert a `buy` verdict into an Intent. */\n minConfidence: number\n /** Only consider launches at most this many seconds old when first seen. */\n maxLaunchAgeSeconds: number\n}\n\nconst DEFAULTS: Omit<LlmStrategistParams, 'llm'> = {\n entryWeth: 0.01,\n takeProfitPct: 0.6,\n stopLossPct: 0.35,\n maxHoldSeconds: 30 * 60,\n minConfidence: 0.6,\n maxLaunchAgeSeconds: 5 * 60,\n}\n\ninterface Candidate {\n launch: Launch\n seenAt: number\n}\n\n/**\n * llm-strategist — an LLM (bring your own key: Claude, OpenAI, Groq, or\n * OpenRouter) judges each new launch from the same real on-chain signals\n * launch-sniper uses mechanically (round-trip retention, deployer\n * concentration), instead of applying fixed threshold cutoffs.\n *\n * EDGE HYPOTHESIS: launch-sniper's hard cutoffs treat every signal\n * independently — a launch either clears every threshold or it doesn't. A\n * model can weigh weak, correlated signals holistically (e.g. \"retention is\n * only okay but deployer concentration is very low\" vs \"retention is great\n * but deployer concentration is borderline\") and may make better marginal\n * calls than a fixed rule. It cannot see anything launch-sniper can't — same\n * inputs, different judgment function.\n *\n * FAILURE MODES: an LLM can be confidently wrong — high stated confidence is\n * not calibrated probability, and this strategy trusts it anyway once it\n * clears `minConfidence`. On-chain metadata (token/creator addresses, launch\n * names if ever added to the brief) is attacker-controlled input reaching the\n * model — a prompt-injection payload disguised as a token name could attempt\n * to manipulate the verdict; the current brief only includes numeric signals\n * for this reason, but any future addition of free-text on-chain fields to\n * the brief must be treated as untrusted. LLM latency means this strategy\n * reacts slower than launch-sniper to the same launch. Every LLM call costs\n * money regardless of verdict; a launch storm inflates API spend with no\n * trading to show for it. A single configured provider has no fallback — a\n * provider outage silently means zero coverage for the outage's duration.\n */\nexport class LlmStrategist implements Strategy {\n readonly id = 'llm-strategist'\n readonly title = 'LLM Strategist'\n readonly quote = 'weth' as const\n private readonly p: LlmStrategistParams\n private readonly queue: Candidate[] = []\n private readonly seen = new Set<string>()\n private readonly judged = new Set<string>()\n private unwatch: (() => void) | null = null\n\n constructor(params: Partial<Omit<LlmStrategistParams, 'llm'>> & { llm: LlmClientConfig }) {\n this.p = { ...DEFAULTS, ...params }\n }\n\n get meta(): StrategyMeta {\n return {\n edge:\n 'An LLM (bring your own key) weighs the same real on-chain launch signals launch-sniper checks ' +\n 'mechanically — buy/sell round-trip retention and deployer supply concentration — holistically ' +\n 'instead of against fixed cutoffs, and only trades when its stated confidence clears a threshold.',\n failureModes: [\n 'LLM confidence is not calibrated probability — a confidently wrong verdict trades exactly like a confidently right one.',\n 'On-chain data reaching the prompt is attacker-controlled; only numeric signals are included today specifically to limit prompt-injection surface.',\n 'LLM round-trip latency means this strategy reacts to a launch after launch-sniper already has (or hasn’t).',\n 'Every judged launch costs a real LLM API call regardless of verdict — a launch storm inflates spend with nothing to show for it.',\n 'One configured provider, no fallback chain — a provider outage means zero coverage until it recovers.',\n ],\n params: { ...this.p, llm: { ...this.p.llm, apiKey: this.p.llm.apiKey ? '<redacted>' : '' } },\n }\n }\n\n start(ctx: StrategyStartContext): void {\n this.unwatch = watchLaunches(\n ctx.market.client,\n (launch) => {\n const key = launch.token.toLowerCase()\n if (this.seen.has(key)) return\n this.seen.add(key)\n this.queue.push({ launch, seenAt: Date.now() })\n ctx.log(`llm-strategist: new launch queued: ${launch.launchpad} ${launch.token}`, { creator: launch.creator })\n },\n { onError: (e) => ctx.log(`llm-strategist: launch watcher error: ${e.message}`) },\n )\n }\n\n stop(): void {\n this.unwatch?.()\n this.unwatch = null\n }\n\n async tick(ctx: StrategyTickContext): Promise<Decision> {\n const intents: Intent[] = []\n const alerts: Alert[] = []\n\n // ── exits first (protect open risk before taking on more) ──────────────────\n for (const pos of ctx.positions) {\n const ageSec = (ctx.now - pos.openedAt) / 1000\n const pnlPct = pos.markUsd !== null && pos.investedUsd > 0 ? pos.markUsd / pos.investedUsd - 1 : null\n let exitReason: string | null = null\n if (pnlPct !== null && pnlPct >= this.p.takeProfitPct) exitReason = `take-profit ${(pnlPct * 100).toFixed(1)}%`\n else if (pnlPct !== null && pnlPct <= -this.p.stopLossPct) exitReason = `stop-loss ${(pnlPct * 100).toFixed(1)}%`\n else if (ageSec >= this.p.maxHoldSeconds) exitReason = `time-exit ${Math.round(ageSec)}s held`\n if (exitReason) {\n intents.push({\n side: 'sell',\n token: pos.token,\n tokenSymbol: pos.tokenSymbol,\n amountIn: pos.amount,\n quoteToken: pos.quoteToken,\n quoteSymbol: pos.quoteSymbol,\n reason: exitReason,\n meta: { pnlPct },\n })\n }\n }\n\n // ── one new candidate judged per tick ───────────────────────────────────────\n const candidate = this.queue.shift()\n if (candidate) {\n const key = candidate.launch.token.toLowerCase()\n if (!this.judged.has(key)) {\n this.judged.add(key)\n const result = await this.evaluate(ctx, candidate)\n if (result.intent) intents.push(result.intent)\n if (result.alert) alerts.push(result.alert)\n }\n }\n\n return { intents, alerts }\n }\n\n private async evaluate(\n ctx: StrategyTickContext,\n candidate: Candidate,\n ): Promise<{ intent?: Intent; alert?: Alert }> {\n const { launch } = candidate\n const ageSec = (ctx.now - candidate.seenAt) / 1000\n if (ageSec > this.p.maxLaunchAgeSeconds) {\n return { alert: { level: 'info', message: `llm-strategist: skip ${launch.token}: stale (${Math.round(ageSec)}s old)`, meta: {} } }\n }\n if (ctx.positions.some((p) => p.token.toLowerCase() === launch.token.toLowerCase())) return {}\n\n const amountIn = parseEther(String(this.p.entryWeth))\n\n const buyQuote = await ctx.market.quoteBuy(ctx.quoteToken, launch.token, amountIn)\n if (!buyQuote || buyQuote.amountOut <= 0n) {\n return { alert: { level: 'info', message: `llm-strategist: skip ${launch.token}: no liquid Uniswap route`, meta: { launchpad: launch.launchpad } } }\n }\n const sellQuote = await ctx.market.quoteSell(launch.token, ctx.quoteToken, buyQuote.amountOut)\n const retention = sellQuote && sellQuote.amountOut > 0n ? Number(sellQuote.amountOut) / Number(amountIn) : 0\n const deployerPct = await this.deployerConcentration(ctx, launch)\n\n const brief = [\n `Launchpad: ${launch.launchpad}`,\n `Round-trip retention (buy then immediately sell back): ${(retention * 100).toFixed(1)}% of input value returned.`,\n `Deployer wallet holds: ${deployerPct === null ? 'unknown (could not read on-chain balance)' : (deployerPct * 100).toFixed(1) + '% of total supply'}.`,\n `Age since first seen: ${Math.round(ageSec)} seconds.`,\n ].join('\\n')\n\n let verdict: LlmVerdict\n try {\n verdict = await judgeLaunch(this.p.llm, brief)\n } catch (err) {\n return {\n alert: {\n level: 'warn',\n message: `llm-strategist: skip ${launch.token}: LLM judge failed — ${(err as Error).message}`,\n meta: { launchpad: launch.launchpad },\n },\n }\n }\n\n if (!verdict.buy || verdict.confidence < this.p.minConfidence) {\n return {\n alert: {\n level: 'info',\n message: `llm-strategist: skip ${launch.token}: verdict buy=${verdict.buy} confidence=${verdict.confidence.toFixed(2)} — ${verdict.thesis}`,\n meta: { retention, deployerPct, verdict },\n },\n }\n }\n\n return {\n intent: {\n side: 'buy',\n token: launch.token,\n tokenSymbol: shortToken(launch.token),\n amountIn,\n quoteToken: ctx.quoteToken,\n quoteSymbol: ctx.quoteSymbol,\n reason: `LLM (${this.p.llm.provider}, confidence ${verdict.confidence.toFixed(2)}): ${verdict.thesis}`,\n meta: { launchpad: launch.launchpad, deployerPct, retention, llmProvider: this.p.llm.provider, confidence: verdict.confidence },\n },\n }\n }\n\n private async deployerConcentration(ctx: StrategyTickContext, launch: Launch): Promise<number | null> {\n try {\n const [supply, bal] = await ctx.market.client.public.multicall({\n contracts: [\n { address: launch.token, abi: erc20Abi, functionName: 'totalSupply' as const },\n { address: launch.token, abi: erc20Abi, functionName: 'balanceOf' as const, args: [launch.creator] as const },\n ],\n allowFailure: false,\n })\n if ((supply as bigint) === 0n) return null\n return Number(formatUnits((bal as bigint) * 10_000n / (supply as bigint), 4))\n } catch {\n return null\n }\n }\n}\n\nfunction shortToken(addr: Address): string {\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http'\nimport { readFile } from 'node:fs/promises'\nimport { extname, join } from 'node:path'\nimport type { Fleet } from '../framework/fleet.js'\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value))\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })\n res.end(payload)\n}\n\nasync function serveStatic(staticRoot: string, req: IncomingMessage, res: ServerResponse): Promise<boolean> {\n const url = new URL(req.url ?? '/', 'http://localhost')\n let rel = url.pathname === '/' ? '/index.html' : url.pathname\n if (rel.includes('..')) return false\n const path = join(staticRoot, rel)\n try {\n const body = await readFile(path)\n res.writeHead(200, { 'content-type': MIME[extname(path)] ?? 'application/octet-stream' })\n res.end(body)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * The fleet dashboard: a tiny read API over {@link Fleet} plus a `POST /kill`\n * panic button, serving the static `dashboard/` UI. No framework — this is a\n * small, auditable surface that is also the E2E harness talks to.\n *\n * `staticRoot` is resolved by the caller rather than guessed from this\n * module's own location: tsup flattens `src/**​/*.ts` into a single-level\n * `dist/`, so a path relative to *this* file's directory would differ between\n * `tsx` (unbundled, nested under `src/server/`) and the built bundle (flat).\n * `main.ts` sits at a stable one-level depth in both trees, so it computes\n * the path once and passes it in.\n */\nexport function createDashboardServer(fleet: Fleet, staticRoot: string): Server {\n return createServer(async (req, res) => {\n const url = new URL(req.url ?? '/', 'http://localhost')\n\n if (url.pathname === '/api/summary' && req.method === 'GET') {\n return json(res, 200, fleet.summary())\n }\n if (url.pathname === '/api/agents' && req.method === 'GET') {\n return json(res, 200, fleet.agentStatuses())\n }\n if (url.pathname.startsWith('/api/agents/') && url.pathname.endsWith('/trades') && req.method === 'GET') {\n const agentId = decodeURIComponent(url.pathname.split('/')[3] ?? '')\n return json(res, 200, fleet.journal.recentTrades(agentId, 100))\n }\n if (url.pathname.startsWith('/api/agents/') && url.pathname.endsWith('/journal') && req.method === 'GET') {\n const agentId = decodeURIComponent(url.pathname.split('/')[3] ?? '')\n return json(res, 200, fleet.journal.recentDecisions(agentId, 200))\n }\n if (url.pathname.startsWith('/api/agents/') && url.pathname.endsWith('/equity') && req.method === 'GET') {\n const agentId = decodeURIComponent(url.pathname.split('/')[3] ?? '')\n return json(res, 200, fleet.journal.equityCurve(agentId, 500))\n }\n if (url.pathname === '/api/trades' && req.method === 'GET') {\n return json(res, 200, fleet.journal.allRecentTrades(100))\n }\n if (url.pathname === '/api/kill' && req.method === 'POST') {\n fleet.tripKill('dashboard')\n return json(res, 200, { killed: true, reason: 'dashboard' })\n }\n if (url.pathname === '/api/health' && req.method === 'GET') {\n return json(res, 200, { ok: true })\n }\n\n if (await serveStatic(staticRoot, req, res)) return\n res.writeHead(404, { 'content-type': 'text/plain' })\n res.end('not found')\n })\n}\n"],"mappings":"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","names":["formatUnits","round","formatUnits","DEFAULTS","shortToken","parseUsdg","DEFAULTS","res","body","data","text","erc20Abi","watchLaunches","formatUnits","parseEther","DEFAULTS","watchLaunches","parseEther","shortToken","erc20Abi","formatUnits"]}
package/dist/index.d.ts CHANGED
@@ -156,6 +156,42 @@ declare class Journal {
156
156
  close(): void;
157
157
  }
158
158
 
159
+ /**
160
+ * Multi-provider LLM client for {@link LlmStrategist}. Bring your own key: the
161
+ * operator picks exactly one of Anthropic Claude, OpenAI, Groq, or OpenRouter
162
+ * via env config (see {@link loadLlmConfig} in ./config.js) and this module
163
+ * speaks that provider's HTTP API directly with `fetch` — no SDK dependency,
164
+ * matching the rest of this package (better-sqlite3, hoodchain, viem, ws are
165
+ * the only runtime deps).
166
+ *
167
+ * The verdict contract is strict on purpose: a strategy that trades real money
168
+ * must never trust free-text from an LLM. Every provider is prompted to return
169
+ * ONLY a JSON object; the response is parsed with a tolerant extractor (finds
170
+ * the first `{...}` blob, so a model that wraps the JSON in a sentence still
171
+ * works) and validated field-by-field. A malformed or missing verdict throws —
172
+ * the caller (LlmStrategist.tick) treats that as "skip this candidate, alert",
173
+ * never as an implicit buy or an implicit skip-silently.
174
+ */
175
+ type LlmProvider = 'anthropic' | 'openai' | 'groq' | 'openrouter';
176
+ interface LlmClientConfig {
177
+ provider: LlmProvider;
178
+ apiKey: string;
179
+ /** Falls back to a sane per-provider default (see {@link DEFAULT_MODELS}) when unset. */
180
+ model?: string;
181
+ /** Abort the request after this many ms. Default 9000. */
182
+ timeoutMs?: number;
183
+ }
184
+ interface LlmVerdict {
185
+ buy: boolean;
186
+ /** Clamped to [0, 1]. */
187
+ confidence: number;
188
+ thesis: string;
189
+ }
190
+ /** Ask the configured LLM to judge a launch brief. Throws on any failure (timeout, HTTP error, malformed verdict). */
191
+ declare function judgeLaunch(cfg: LlmClientConfig, brief: string): Promise<LlmVerdict>;
192
+ /** Extract the first `{...}` blob from `text` and validate it as an {@link LlmVerdict}. Throws on any mismatch. */
193
+ declare function parseVerdict(text: string): LlmVerdict;
194
+
159
195
  /** Fleet-wide configuration resolved from the environment. */
160
196
  interface FleetConfig {
161
197
  network: HoodNetwork;
@@ -181,6 +217,17 @@ interface FleetConfig {
181
217
  * silently spend real funds.
182
218
  */
183
219
  declare function loadFleetConfig(env?: NodeJS.ProcessEnv): FleetConfig;
220
+ /**
221
+ * Resolve LLM config for {@link LlmStrategist} from the environment. Returns
222
+ * `null` when `HOOD_LLM_PROVIDER` or `HOOD_LLM_API_KEY` is unset — the
223
+ * strategy is optional and simply isn't added to the fleet in that case (see
224
+ * main.ts). Throws only when `HOOD_LLM_PROVIDER` is set to an unrecognized
225
+ * value, since that is very likely a typo the operator would want to know
226
+ * about immediately rather than silently running without the strategy.
227
+ */
228
+ declare function loadLlmConfig(env?: NodeJS.ProcessEnv): LlmClientConfig | null;
229
+ /** Minimum LLM confidence required to convert a `buy` verdict into a trade. */
230
+ declare function loadLlmMinConfidence(env?: NodeJS.ProcessEnv): number;
184
231
 
185
232
  /** A memecoin/token spot price sourced from live Uniswap v3 liquidity. */
186
233
  interface SpotPrice {
@@ -680,6 +727,69 @@ declare class PremiumWatch implements Strategy {
680
727
  private readSpread;
681
728
  }
682
729
 
730
+ /** Tunables for {@link LlmStrategist}. */
731
+ interface LlmStrategistParams {
732
+ llm: LlmClientConfig;
733
+ /** WETH spent per entry when the LLM says buy. */
734
+ entryWeth: number;
735
+ /** Take profit as a fraction (0.5 = +50%). */
736
+ takeProfitPct: number;
737
+ /** Stop loss as a fraction (0.3 = -30%). */
738
+ stopLossPct: number;
739
+ /** Force-exit a position after this many seconds regardless of PnL. */
740
+ maxHoldSeconds: number;
741
+ /** Minimum LLM confidence required to convert a `buy` verdict into an Intent. */
742
+ minConfidence: number;
743
+ /** Only consider launches at most this many seconds old when first seen. */
744
+ maxLaunchAgeSeconds: number;
745
+ }
746
+ /**
747
+ * llm-strategist — an LLM (bring your own key: Claude, OpenAI, Groq, or
748
+ * OpenRouter) judges each new launch from the same real on-chain signals
749
+ * launch-sniper uses mechanically (round-trip retention, deployer
750
+ * concentration), instead of applying fixed threshold cutoffs.
751
+ *
752
+ * EDGE HYPOTHESIS: launch-sniper's hard cutoffs treat every signal
753
+ * independently — a launch either clears every threshold or it doesn't. A
754
+ * model can weigh weak, correlated signals holistically (e.g. "retention is
755
+ * only okay but deployer concentration is very low" vs "retention is great
756
+ * but deployer concentration is borderline") and may make better marginal
757
+ * calls than a fixed rule. It cannot see anything launch-sniper can't — same
758
+ * inputs, different judgment function.
759
+ *
760
+ * FAILURE MODES: an LLM can be confidently wrong — high stated confidence is
761
+ * not calibrated probability, and this strategy trusts it anyway once it
762
+ * clears `minConfidence`. On-chain metadata (token/creator addresses, launch
763
+ * names if ever added to the brief) is attacker-controlled input reaching the
764
+ * model — a prompt-injection payload disguised as a token name could attempt
765
+ * to manipulate the verdict; the current brief only includes numeric signals
766
+ * for this reason, but any future addition of free-text on-chain fields to
767
+ * the brief must be treated as untrusted. LLM latency means this strategy
768
+ * reacts slower than launch-sniper to the same launch. Every LLM call costs
769
+ * money regardless of verdict; a launch storm inflates API spend with no
770
+ * trading to show for it. A single configured provider has no fallback — a
771
+ * provider outage silently means zero coverage for the outage's duration.
772
+ */
773
+ declare class LlmStrategist implements Strategy {
774
+ readonly id = "llm-strategist";
775
+ readonly title = "LLM Strategist";
776
+ readonly quote: "weth";
777
+ private readonly p;
778
+ private readonly queue;
779
+ private readonly seen;
780
+ private readonly judged;
781
+ private unwatch;
782
+ constructor(params: Partial<Omit<LlmStrategistParams, 'llm'>> & {
783
+ llm: LlmClientConfig;
784
+ });
785
+ get meta(): StrategyMeta;
786
+ start(ctx: StrategyStartContext): void;
787
+ stop(): void;
788
+ tick(ctx: StrategyTickContext): Promise<Decision>;
789
+ private evaluate;
790
+ private deployerConcentration;
791
+ }
792
+
683
793
  /**
684
794
  * The fleet dashboard: a tiny read API over {@link Fleet} plus a `POST /kill`
685
795
  * panic button, serving the static `dashboard/` UI. No framework — this is a
@@ -694,4 +804,4 @@ declare class PremiumWatch implements Strategy {
694
804
  */
695
805
  declare function createDashboardServer(fleet: Fleet, staticRoot: string): Server;
696
806
 
697
- export { Agent, type AgentOptions, type AgentSpec, type AgentStatus, type Alert, type Decision, type DecisionRecord, type EquityPoint, Fleet, type FleetConfig, type FleetSummary, type Intent, Journal, KillSwitch, LaunchSniper, type LaunchSniperParams, Market, type Mode, Momentum, type MomentumParams, type Position, PremiumWatch, type PremiumWatchParams, type QuoteKind, type RefusalReason, type RiskContext, RiskEngine, type RiskLimits, type RiskVerdict, type SpotPrice, type Strategy, type StrategyMeta, type StrategyStartContext, type StrategyTickContext, type TradeRecord, createDashboardServer, loadFleetConfig, utcDayStart };
807
+ export { Agent, type AgentOptions, type AgentSpec, type AgentStatus, type Alert, type Decision, type DecisionRecord, type EquityPoint, Fleet, type FleetConfig, type FleetSummary, type Intent, Journal, KillSwitch, LaunchSniper, type LaunchSniperParams, type LlmClientConfig, type LlmProvider, LlmStrategist, type LlmStrategistParams, type LlmVerdict, Market, type Mode, Momentum, type MomentumParams, type Position, PremiumWatch, type PremiumWatchParams, type QuoteKind, type RefusalReason, type RiskContext, RiskEngine, type RiskLimits, type RiskVerdict, type SpotPrice, type Strategy, type StrategyMeta, type StrategyStartContext, type StrategyTickContext, type TradeRecord, createDashboardServer, judgeLaunch, loadFleetConfig, loadLlmConfig, loadLlmMinConfidence, parseVerdict, utcDayStart };
package/dist/index.js CHANGED
@@ -5,26 +5,36 @@ import {
5
5
  Journal,
6
6
  KillSwitch,
7
7
  LaunchSniper,
8
+ LlmStrategist,
8
9
  Market,
9
10
  Momentum,
10
11
  PremiumWatch,
11
12
  RiskEngine,
12
13
  createDashboardServer,
14
+ judgeLaunch,
13
15
  loadFleetConfig,
16
+ loadLlmConfig,
17
+ loadLlmMinConfidence,
18
+ parseVerdict,
14
19
  utcDayStart
15
- } from "./chunk-NXL2F7EP.js";
20
+ } from "./chunk-YZJCP67J.js";
16
21
  export {
17
22
  Agent,
18
23
  Fleet,
19
24
  Journal,
20
25
  KillSwitch,
21
26
  LaunchSniper,
27
+ LlmStrategist,
22
28
  Market,
23
29
  Momentum,
24
30
  PremiumWatch,
25
31
  RiskEngine,
26
32
  createDashboardServer,
33
+ judgeLaunch,
27
34
  loadFleetConfig,
35
+ loadLlmConfig,
36
+ loadLlmMinConfidence,
37
+ parseVerdict,
28
38
  utcDayStart
29
39
  };
30
40
  //# sourceMappingURL=index.js.map
package/dist/main.js CHANGED
@@ -2,11 +2,14 @@
2
2
  import {
3
3
  Fleet,
4
4
  LaunchSniper,
5
+ LlmStrategist,
5
6
  Momentum,
6
7
  PremiumWatch,
7
8
  createDashboardServer,
8
- loadFleetConfig
9
- } from "./chunk-NXL2F7EP.js";
9
+ loadFleetConfig,
10
+ loadLlmConfig,
11
+ loadLlmMinConfidence
12
+ } from "./chunk-YZJCP67J.js";
10
13
 
11
14
  // src/main.ts
12
15
  import { join } from "path";
@@ -15,18 +18,32 @@ var DASHBOARD_STATIC_ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "
15
18
  async function main() {
16
19
  const config = loadFleetConfig();
17
20
  const fleet = new Fleet(config);
21
+ const agentIds = ["sniper-1", "momentum-1", "premium-1"];
18
22
  fleet.addAgents([
19
23
  { id: "sniper-1", strategy: new LaunchSniper(), tickIntervalMs: 4e3 },
20
24
  { id: "momentum-1", strategy: new Momentum(), tickIntervalMs: 15e3 },
21
25
  { id: "premium-1", strategy: new PremiumWatch(), tickIntervalMs: 3e4 }
22
26
  ]);
27
+ const llm = loadLlmConfig();
28
+ if (llm) {
29
+ fleet.addAgents([
30
+ {
31
+ id: "llm-1",
32
+ strategy: new LlmStrategist({ llm, minConfidence: loadLlmMinConfidence() }),
33
+ tickIntervalMs: 2e4
34
+ }
35
+ ]);
36
+ agentIds.push("llm-1");
37
+ } else {
38
+ console.warn("HOOD_LLM_PROVIDER/HOOD_LLM_API_KEY not set: llm-strategist disabled (the other 3 strategies still run).");
39
+ }
23
40
  const banner = [
24
41
  "\u2500".repeat(60),
25
42
  " hood-traders \u2014 Robinhood Chain autonomous fleet",
26
43
  "\u2500".repeat(60),
27
44
  ` network : ${config.network} (${config.network === "testnet" ? 46630 : 4663})`,
28
45
  ` mode : ${config.mode.toUpperCase()}${config.mode === "paper" ? " (simulation only, no real funds move)" : " (REAL FUNDS \u2014 swaps will be signed and broadcast)"}`,
29
- ` agents : sniper-1, momentum-1, premium-1`,
46
+ ` agents : ${agentIds.join(", ")}`,
30
47
  ` fleet cap : $${config.fleetMaxDailySpendUsdg}/day`,
31
48
  ` dashboard : http://localhost:${config.dashboardPort}`,
32
49
  ` kill file : ${config.killFile}`,
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { loadFleetConfig } from './framework/config.js'\nimport { Fleet } from './framework/fleet.js'\nimport { LaunchSniper } from './strategies/launch-sniper.js'\nimport { Momentum } from './strategies/momentum.js'\nimport { PremiumWatch } from './strategies/premium-watch.js'\nimport { createDashboardServer } from './server/dashboard.js'\n\n// main.ts sits at a stable one-level depth in both trees: src/main.ts (tsx,\n// dev) and dist/main.js (tsup bundle, prod) — so \"one level up + dashboard\"\n// resolves correctly in both without guessing at bundler output shape.\nconst DASHBOARD_STATIC_ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..', 'dashboard')\n\nasync function main(): Promise<void> {\n const config = loadFleetConfig()\n const fleet = new Fleet(config)\n\n fleet.addAgents([\n { id: 'sniper-1', strategy: new LaunchSniper(), tickIntervalMs: 4000 },\n { id: 'momentum-1', strategy: new Momentum(), tickIntervalMs: 15000 },\n { id: 'premium-1', strategy: new PremiumWatch(), tickIntervalMs: 30000 },\n ])\n\n const banner = [\n '─'.repeat(60),\n ' hood-traders — Robinhood Chain autonomous fleet',\n '─'.repeat(60),\n ` network : ${config.network} (${config.network === 'testnet' ? 46630 : 4663})`,\n ` mode : ${config.mode.toUpperCase()}${config.mode === 'paper' ? ' (simulation only, no real funds move)' : ' (REAL FUNDS — swaps will be signed and broadcast)'}`,\n ` agents : sniper-1, momentum-1, premium-1`,\n ` fleet cap : $${config.fleetMaxDailySpendUsdg}/day`,\n ` dashboard : http://localhost:${config.dashboardPort}`,\n ` kill file : ${config.killFile}`,\n '─'.repeat(60),\n ].join('\\n')\n console.log(banner)\n\n if (config.mode === 'live') {\n console.warn(\n '\\n⚠ LIVE MODE — this process will sign and broadcast real transactions with real funds.\\n' +\n ' Risk caps are active but are not a guarantee against loss. Ctrl-C or POST /kill to halt.\\n',\n )\n }\n\n await fleet.start()\n\n const server = createDashboardServer(fleet, DASHBOARD_STATIC_ROOT)\n server.listen(config.dashboardPort, () => {\n console.log(`dashboard listening on :${config.dashboardPort}`)\n })\n\n const shutdown = () => {\n console.log('\\nshutting down — stopping agents, closing journal…')\n server.close()\n fleet.close()\n process.exit(0)\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n console.error('hood-traders fatal error:', err)\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAW9B,IAAM,wBAAwB,KAAK,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC,GAAG,MAAM,WAAW;AAElG,eAAe,OAAsB;AACnC,QAAM,SAAS,gBAAgB;AAC/B,QAAM,QAAQ,IAAI,MAAM,MAAM;AAE9B,QAAM,UAAU;AAAA,IACd,EAAE,IAAI,YAAY,UAAU,IAAI,aAAa,GAAG,gBAAgB,IAAK;AAAA,IACrE,EAAE,IAAI,cAAc,UAAU,IAAI,SAAS,GAAG,gBAAgB,KAAM;AAAA,IACpE,EAAE,IAAI,aAAa,UAAU,IAAI,aAAa,GAAG,gBAAgB,IAAM;AAAA,EACzE,CAAC;AAED,QAAM,SAAS;AAAA,IACb,SAAI,OAAO,EAAE;AAAA,IACb;AAAA,IACA,SAAI,OAAO,EAAE;AAAA,IACb,gBAAgB,OAAO,OAAO,KAAK,OAAO,YAAY,YAAY,QAAQ,IAAI;AAAA,IAC9E,gBAAgB,OAAO,KAAK,YAAY,CAAC,GAAG,OAAO,SAAS,UAAU,2CAA2C,yDAAoD;AAAA,IACrK;AAAA,IACA,iBAAiB,OAAO,sBAAsB;AAAA,IAC9C,iCAAiC,OAAO,aAAa;AAAA,IACrD,gBAAgB,OAAO,QAAQ;AAAA,IAC/B,SAAI,OAAO,EAAE;AAAA,EACf,EAAE,KAAK,IAAI;AACX,UAAQ,IAAI,MAAM;AAElB,MAAI,OAAO,SAAS,QAAQ;AAC1B,YAAQ;AAAA,MACN;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,MAAM,MAAM;AAElB,QAAM,SAAS,sBAAsB,OAAO,qBAAqB;AACjE,SAAO,OAAO,OAAO,eAAe,MAAM;AACxC,YAAQ,IAAI,2BAA2B,OAAO,aAAa,EAAE;AAAA,EAC/D,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,YAAQ,IAAI,+DAAqD;AACjE,WAAO,MAAM;AACb,UAAM,MAAM;AACZ,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,6BAA6B,GAAG;AAC9C,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { loadFleetConfig, loadLlmConfig, loadLlmMinConfidence } from './framework/config.js'\nimport { Fleet } from './framework/fleet.js'\nimport { LaunchSniper } from './strategies/launch-sniper.js'\nimport { Momentum } from './strategies/momentum.js'\nimport { PremiumWatch } from './strategies/premium-watch.js'\nimport { LlmStrategist } from './strategies/llm-strategist.js'\nimport { createDashboardServer } from './server/dashboard.js'\n\n// main.ts sits at a stable one-level depth in both trees: src/main.ts (tsx,\n// dev) and dist/main.js (tsup bundle, prod) — so \"one level up + dashboard\"\n// resolves correctly in both without guessing at bundler output shape.\nconst DASHBOARD_STATIC_ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..', 'dashboard')\n\nasync function main(): Promise<void> {\n const config = loadFleetConfig()\n const fleet = new Fleet(config)\n\n const agentIds = ['sniper-1', 'momentum-1', 'premium-1']\n fleet.addAgents([\n { id: 'sniper-1', strategy: new LaunchSniper(), tickIntervalMs: 4000 },\n { id: 'momentum-1', strategy: new Momentum(), tickIntervalMs: 15000 },\n { id: 'premium-1', strategy: new PremiumWatch(), tickIntervalMs: 30000 },\n ])\n\n const llm = loadLlmConfig()\n if (llm) {\n fleet.addAgents([\n {\n id: 'llm-1',\n strategy: new LlmStrategist({ llm, minConfidence: loadLlmMinConfidence() }),\n tickIntervalMs: 20000,\n },\n ])\n agentIds.push('llm-1')\n } else {\n console.warn('HOOD_LLM_PROVIDER/HOOD_LLM_API_KEY not set: llm-strategist disabled (the other 3 strategies still run).')\n }\n\n const banner = [\n '─'.repeat(60),\n ' hood-traders — Robinhood Chain autonomous fleet',\n '─'.repeat(60),\n ` network : ${config.network} (${config.network === 'testnet' ? 46630 : 4663})`,\n ` mode : ${config.mode.toUpperCase()}${config.mode === 'paper' ? ' (simulation only, no real funds move)' : ' (REAL FUNDS — swaps will be signed and broadcast)'}`,\n ` agents : ${agentIds.join(', ')}`,\n ` fleet cap : $${config.fleetMaxDailySpendUsdg}/day`,\n ` dashboard : http://localhost:${config.dashboardPort}`,\n ` kill file : ${config.killFile}`,\n '─'.repeat(60),\n ].join('\\n')\n console.log(banner)\n\n if (config.mode === 'live') {\n console.warn(\n '\\n⚠ LIVE MODE — this process will sign and broadcast real transactions with real funds.\\n' +\n ' Risk caps are active but are not a guarantee against loss. Ctrl-C or POST /kill to halt.\\n',\n )\n }\n\n await fleet.start()\n\n const server = createDashboardServer(fleet, DASHBOARD_STATIC_ROOT)\n server.listen(config.dashboardPort, () => {\n console.log(`dashboard listening on :${config.dashboardPort}`)\n })\n\n const shutdown = () => {\n console.log('\\nshutting down — stopping agents, closing journal…')\n server.close()\n fleet.close()\n process.exit(0)\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n\nmain().catch((err) => {\n console.error('hood-traders fatal error:', err)\n process.exit(1)\n})\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAY9B,IAAM,wBAAwB,KAAK,cAAc,IAAI,IAAI,KAAK,YAAY,GAAG,CAAC,GAAG,MAAM,WAAW;AAElG,eAAe,OAAsB;AACnC,QAAM,SAAS,gBAAgB;AAC/B,QAAM,QAAQ,IAAI,MAAM,MAAM;AAE9B,QAAM,WAAW,CAAC,YAAY,cAAc,WAAW;AACvD,QAAM,UAAU;AAAA,IACd,EAAE,IAAI,YAAY,UAAU,IAAI,aAAa,GAAG,gBAAgB,IAAK;AAAA,IACrE,EAAE,IAAI,cAAc,UAAU,IAAI,SAAS,GAAG,gBAAgB,KAAM;AAAA,IACpE,EAAE,IAAI,aAAa,UAAU,IAAI,aAAa,GAAG,gBAAgB,IAAM;AAAA,EACzE,CAAC;AAED,QAAM,MAAM,cAAc;AAC1B,MAAI,KAAK;AACP,UAAM,UAAU;AAAA,MACd;AAAA,QACE,IAAI;AAAA,QACJ,UAAU,IAAI,cAAc,EAAE,KAAK,eAAe,qBAAqB,EAAE,CAAC;AAAA,QAC1E,gBAAgB;AAAA,MAClB;AAAA,IACF,CAAC;AACD,aAAS,KAAK,OAAO;AAAA,EACvB,OAAO;AACL,YAAQ,KAAK,yGAAyG;AAAA,EACxH;AAEA,QAAM,SAAS;AAAA,IACb,SAAI,OAAO,EAAE;AAAA,IACb;AAAA,IACA,SAAI,OAAO,EAAE;AAAA,IACb,gBAAgB,OAAO,OAAO,KAAK,OAAO,YAAY,YAAY,QAAQ,IAAI;AAAA,IAC9E,gBAAgB,OAAO,KAAK,YAAY,CAAC,GAAG,OAAO,SAAS,UAAU,2CAA2C,yDAAoD;AAAA,IACrK,gBAAgB,SAAS,KAAK,IAAI,CAAC;AAAA,IACnC,iBAAiB,OAAO,sBAAsB;AAAA,IAC9C,iCAAiC,OAAO,aAAa;AAAA,IACrD,gBAAgB,OAAO,QAAQ;AAAA,IAC/B,SAAI,OAAO,EAAE;AAAA,EACf,EAAE,KAAK,IAAI;AACX,UAAQ,IAAI,MAAM;AAElB,MAAI,OAAO,SAAS,QAAQ;AAC1B,YAAQ;AAAA,MACN;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,MAAM,MAAM;AAElB,QAAM,SAAS,sBAAsB,OAAO,qBAAqB;AACjE,SAAO,OAAO,OAAO,eAAe,MAAM;AACxC,YAAQ,IAAI,2BAA2B,OAAO,aAAa,EAAE;AAAA,EAC/D,CAAC;AAED,QAAM,WAAW,MAAM;AACrB,YAAQ,IAAI,+DAAqD;AACjE,WAAO,MAAM;AACb,UAAM,MAAM;AACZ,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ,GAAG,UAAU,QAAQ;AAC7B,UAAQ,GAAG,WAAW,QAAQ;AAChC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,6BAA6B,GAAG;AAC9C,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}