tradeblocks-mcp 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/test-exports.js +25 -19
- package/dist/test-exports.js.map +1 -1
- package/manifest.json +5 -5
- package/package.json +2 -2
- package/server/index.js +24 -18
- package/server/index.js.map +1 -1
package/dist/test-exports.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools/shared/filters.ts","../src/utils/schema-metadata.ts","../src/utils/field-timing.ts","../src/utils/data-availability.ts","../src/utils/market-enricher.ts","../src/utils/market-importer.ts","../src/utils/providers/massive.ts","../src/utils/black-scholes.ts","../src/utils/providers/thetadata.ts","../src/utils/market-provider.ts","../src/utils/analysis-stats.ts","../src/utils/filter-predicates.ts","../src/tools/profiles.ts","../src/utils/output-formatter.ts","../src/tools/profile-analysis.ts","../src/tools/regime-advisor.ts","../src/utils/trade-replay.ts","../src/tools/replay.ts","../src/utils/bar-cache.ts","../src/tools/snapshot.ts","../src/utils/greeks-decomposition.ts","../src/utils/exit-triggers.ts","../src/tools/exit-analysis.ts","../src/utils/batch-exit-analysis.ts","../src/tools/batch-exit-analysis.ts"],"sourcesContent":["/**\n * Shared Filter Utilities\n *\n * Common filtering functions used across block and report tools.\n */\n\nimport type { Trade, DailyLogEntry } from \"@tradeblocks/lib\";\n\n/**\n * Filter trades by strategy name (case-insensitive)\n */\nexport function filterByStrategy(trades: Trade[], strategy?: string): Trade[] {\n if (!strategy) return trades;\n return trades.filter(\n (t) => t.strategy.toLowerCase() === strategy.toLowerCase()\n );\n}\n\n/**\n * Validate that a date string is in YYYY-MM-DD format.\n * Returns the string if valid, undefined if not (skips that filter boundary).\n */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction validateDateParam(date: string | undefined): string | undefined {\n if (!date) return undefined;\n return DATE_RE.test(date) ? date : undefined;\n}\n\n/**\n * Extract YYYY-MM-DD calendar date from a Date or string.\n * Trades are parsed via parseDatePreservingCalendarDay() which creates dates at\n * local midnight. Use local date components to preserve the calendar date,\n * avoiding timezone shift when the server runs in UTC.\n */\nfunction toCalendarDateStr(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Filter trades by date range using string comparison on Eastern Time calendar dates.\n * Avoids timezone bugs from mixing UTC Date parsing with local time setHours.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterByDateRange(\n trades: Trade[],\n startDate?: string,\n endDate?: string\n): Trade[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = trades;\n\n if (start) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) <= end);\n }\n\n return filtered;\n}\n\n/**\n * Filter daily log entries by date range using string comparison on calendar dates.\n * Mirrors filterByDateRange but uses entry.date (Date object) instead of t.dateOpened.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterDailyLogsByDateRange(\n dailyLogs: DailyLogEntry[],\n startDate?: string,\n endDate?: string\n): DailyLogEntry[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = dailyLogs;\n\n if (start) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) <= end);\n }\n\n return filtered;\n}\n","/**\n * Schema Metadata\n *\n * Hardcoded descriptions for DuckDB tables and columns, plus example queries.\n * Used by describe_database tool to provide context for SQL query writing.\n *\n * Tables are organized by schema:\n * - trades: Trade data from CSV files\n * - market: Market context data — daily (per-ticker OHLCV + indicators),\n * context (global VIX/regime), intraday (raw bar data)\n */\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\nexport interface ColumnDescription {\n /** Human-readable description of what this column contains */\n description: string;\n /** True if this column is useful for hypothesis testing (filtering, grouping, analysis) */\n hypothesis: boolean;\n /** When this field's value is known relative to market open.\n * - 'open': Known at/before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * - 'close': Only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * - 'static': Calendar/metadata facts known before the day (Day_of_Week, Month, Is_Opex)\n * Only applicable to market.daily and market.context columns. Omit for non-market tables.\n */\n timing?: 'open' | 'close' | 'static';\n}\n\nexport interface TableDescription {\n /** Human-readable description of this table's purpose */\n description: string;\n /** Key columns that are most important for analysis */\n keyColumns: string[];\n /** Column descriptions by column name */\n columns: Record<string, ColumnDescription>;\n}\n\nexport interface SchemaDescription {\n /** Human-readable description of this schema's purpose */\n description: string;\n /** Tables in this schema */\n tables: Record<string, TableDescription>;\n}\n\nexport interface SchemaMetadata {\n trades: SchemaDescription;\n market: SchemaDescription;\n}\n\nexport interface ExampleQuery {\n /** What this query does */\n description: string;\n /** The SQL query */\n sql: string;\n}\n\nexport interface ExampleQueries {\n /** Basic single-table queries */\n basic: ExampleQuery[];\n /** JOIN queries between trades and market data */\n joins: ExampleQuery[];\n /** Hypothesis testing patterns */\n hypothesis: ExampleQuery[];\n}\n\n// ============================================================================\n// Schema Descriptions\n// ============================================================================\n\nexport const SCHEMA_DESCRIPTIONS: SchemaMetadata = {\n trades: {\n description:\n \"Trading data synced from CSV files. Contains trade records from all portfolio blocks, including both backtest (trade_data) and actual/reported (reporting_data) trades.\",\n tables: {\n trade_data: {\n description:\n \"Individual backtest trade records. Each row = one trade with entry/exit details, P&L, and strategy. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description (e.g., 'SPY 450P/445P')\",\n hypothesis: false,\n },\n premium: {\n description: \"Credit received (+) or debit paid (-)\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded\",\n hypothesis: false,\n },\n pl: {\n description: \"Gross P&L before commissions (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n margin_req: {\n description: \"Margin requirement for the position ($)\",\n hypothesis: false,\n },\n opening_commissions: {\n description: \"Commissions paid at entry ($)\",\n hypothesis: false,\n },\n closing_commissions: {\n description: \"Commissions paid at exit ($)\",\n hypothesis: false,\n },\n },\n },\n reporting_data: {\n description:\n \"Actual/reported trade records from reportinglog.csv. Each row = one live trade executed. Compare with trade_data (backtest) to analyze slippage and execution differences. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"legs\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description with strikes (e.g., 'SPY 450P/445P') - compare with trade_data.legs to identify strike differences\",\n hypothesis: true,\n },\n initial_premium: {\n description: \"Credit received (+) or debit paid (-) at entry\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded (often fewer than backtest)\",\n hypothesis: false,\n },\n pl: {\n description: \"Actual P&L realized (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n closing_price: {\n description: \"Price at exit\",\n hypothesis: false,\n },\n avg_closing_cost: {\n description: \"Average cost to close the position\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n opening_price: {\n description: \"Price at entry\",\n hypothesis: false,\n },\n },\n },\n },\n },\n market: {\n description:\n \"Market context data for hypothesis testing. Normalized into four tables: daily (per-ticker OHLCV + technical indicators + ivr/ivp for VIX-family tickers), _context_derived (cross-ticker derived fields like Vol_Regime), context (LEGACY — preserved for backward compat), and intraday (raw bar data). Source: CSV files in market/ folder.\",\n tables: {\n daily: {\n description:\n \"Per-ticker daily OHLCV with Tier 1 enrichment indicators and calendar fields. One row per ticker per trading day. JOIN with trades on ticker+date (e.g., d.ticker = 'SPX' AND t.date_opened = d.date). VIX-family tickers (VIX, VIX9D, VIX3M, etc.) also have ivr/ivp columns populated. For trade-entry queries, use LAG() on close-derived fields. Join market._context_derived (LEFT JOIN on date) for Vol_Regime, Term_Structure_State, etc.\",\n keyColumns: [\"ticker\", \"date\", \"RSI_14\", \"ATR_Pct\", \"Realized_Vol_20D\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Composite primary key with ticker.\",\n hypothesis: true,\n },\n // Raw OHLCV\n open: {\n description: \"Underlying open price\",\n hypothesis: false,\n timing: 'open',\n },\n high: {\n description: \"Underlying high price\",\n hypothesis: false,\n timing: 'close',\n },\n low: {\n description: \"Underlying low price\",\n hypothesis: false,\n timing: 'close',\n },\n close: {\n description: \"Underlying close price\",\n hypothesis: false,\n timing: 'close',\n },\n Prior_Close: {\n description: \"Previous day's close price\",\n hypothesis: false,\n timing: 'open',\n },\n // Tier 1 enrichment — open-known\n Gap_Pct: {\n description: \"Overnight gap percentage ((Open - Prior_Close) / Prior_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n Prev_Return_Pct: {\n description: \"Previous day's total return percentage (prior close to prior close)\",\n hypothesis: true,\n timing: 'open',\n },\n Prior_Range_vs_ATR: {\n description: \"Prior trading day's (high - low) / ATR ratio, measures prior day's range relative to average true range\",\n hypothesis: true,\n timing: 'open',\n },\n // Tier 1 enrichment — close-derived\n ATR_Pct: {\n description: \"Average True Range as percentage of price (14-day Wilder smoothing)\",\n hypothesis: true,\n timing: 'close',\n },\n RSI_14: {\n description: \"14-day RSI (0-100, >70 overbought, <30 oversold)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_EMA21_Pct: {\n description: \"Price vs 21-day EMA as percentage ((close - EMA21) / EMA21 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_SMA50_Pct: {\n description: \"Price vs 50-day SMA as percentage ((close - SMA50) / SMA50 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_5D: {\n description: \"5-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_20D: {\n description: \"20-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Return_5D: {\n description: \"5-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Return_20D: {\n description: \"20-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Range_Pct: {\n description: \"Intraday range as percentage ((High - Low) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Return_Pct: {\n description: \"Open to close return percentage ((Close - Open) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Close_Position_In_Range: {\n description: \"Where close is in day's range (0 = low, 1 = high)\",\n hypothesis: true,\n timing: 'close',\n },\n Gap_Filled: {\n description: \"Whether overnight gap was filled (1 = yes, 0 = no)\",\n hypothesis: true,\n timing: 'close',\n },\n Consecutive_Days: {\n description: \"Consecutive up/down days (positive=up, negative=down)\",\n hypothesis: true,\n timing: 'close',\n },\n // Tier 3 intraday timing (columns exist in schema, enrichment deferred)\n High_Time: {\n description: \"Time of day high as decimal hours (e.g., 10.5 = 10:30 AM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n Low_Time: {\n description: \"Time of day low as decimal hours (e.g., 14.25 = 2:15 PM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n High_Before_Low: {\n description: \"Did high occur before low? (1=yes, 0=no)\",\n hypothesis: true,\n timing: 'close',\n },\n Reversal_Type: {\n description: \"Reversal pattern type (1=morning reversal up, -1=morning reversal down, 0=trend day)\",\n hypothesis: true,\n timing: 'close',\n },\n Opening_Drive_Strength: {\n description: \"First-30-min range / full-day range ratio (0-1); higher = strong opening drive\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Realized_Vol: {\n description: \"Annualized realized volatility from intraday bar returns (decimal, e.g., 0.15 = 15%)\",\n hypothesis: true,\n timing: 'close',\n },\n // Calendar fields — static\n Day_of_Week: {\n description: \"Day of week (2=Monday through 6=Friday)\",\n hypothesis: true,\n timing: 'static',\n },\n Month: {\n description: \"Month number (1-12)\",\n hypothesis: true,\n timing: 'static',\n },\n Is_Opex: {\n description: \"Options expiration day flag (1=opex, 0=not)\",\n hypothesis: true,\n timing: 'static',\n },\n // VIX-family ticker IVR/IVP (populated for VIX, VIX9D, VIX3M, etc.)\n ivr: {\n description: \"Implied Volatility Rank (252-day): where current close sits in range (0=min, 100=max). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n ivp: {\n description: \"Implied Volatility Percentile (252-day): percentage of prior 251 trading days where close was at or below current level (0-100). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n },\n },\n _context_derived: {\n description:\n \"Cross-ticker derived market context fields per trading day. Contains Vol_Regime, Term_Structure_State, and other fields derived from multiple VIX tickers. JOIN with market.daily on date. VIX OHLCV and IVR/IVP are now in market.daily (ticker='VIX', 'VIX9D', etc.).\",\n keyColumns: [\"date\", \"Vol_Regime\", \"Term_Structure_State\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: true,\n },\n Vol_Regime: {\n description: \"Volatility regime classification based on VIX close (1=very low <10, 2=low 10-15, 3=normal 15-20, 4=elevated 20-25, 5=high 25-30, 6=extreme >30)\",\n hypothesis: true,\n timing: 'close',\n },\n Term_Structure_State: {\n description: \"VIX term structure state based on VIX9D/VIX ratio (-1=backwardation/inverted, 0=flat, 1=contango/normal). NULL when VIX9D data is absent.\",\n hypothesis: true,\n timing: 'close',\n },\n Trend_Direction: {\n description: \"Trend direction classification based on 20-day return: up (>1%), down (<-1%), flat (-1% to 1%). NULL if Return_20D unavailable.\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Spike_Pct: {\n description: \"VIX spike from open to high as percentage\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Gap_Pct: {\n description: \"VIX overnight gap percentage ((VIX_Open - prior VIX_Close) / prior VIX_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n },\n },\n context: {\n description:\n \"LEGACY: Global VIX and volatility term structure data. VIX ticker data has moved to market.daily (ticker='VIX', 'VIX9D', etc.) with ivr/ivp columns. Derived fields moved to market._context_derived. This table is maintained for backward compatibility but is no longer the primary data source.\",\n keyColumns: [\"date\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: false,\n },\n },\n },\n intraday: {\n description:\n \"Raw intraday bars per ticker. One row per bar (any resolution). Use for ORB calculations and intraday context enrichment. Time column is Eastern Time HH:MM format (e.g., '09:30'). Filter by ticker='VIX' to get VIX intraday data.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date and time).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Part of composite primary key.\",\n hypothesis: true,\n },\n time: {\n description: \"Bar time in HH:MM Eastern Time format (e.g., '09:30', '10:00'). Part of composite primary key.\",\n hypothesis: false,\n },\n open: {\n description: \"Bar open price\",\n hypothesis: false,\n },\n high: {\n description: \"Bar high price\",\n hypothesis: false,\n },\n low: {\n description: \"Bar low price\",\n hypothesis: false,\n },\n close: {\n description: \"Bar close price\",\n hypothesis: false,\n },\n },\n },\n },\n },\n};\n\n// ============================================================================\n// Example Queries\n// ============================================================================\n\nexport const EXAMPLE_QUERIES: ExampleQueries = {\n basic: [\n {\n description: \"Count trades by strategy with total P&L\",\n sql: `SELECT strategy, COUNT(*) as trades, SUM(pl) as total_pl\nFROM trades.trade_data\nGROUP BY strategy\nORDER BY total_pl DESC`,\n },\n {\n description: \"Daily P&L for a specific block\",\n sql: `SELECT date_opened, SUM(pl) as daily_pl\nFROM trades.trade_data\nWHERE block_id = 'my-block'\nGROUP BY date_opened\nORDER BY date_opened`,\n },\n {\n description: \"Recent market conditions (last 20 days)\",\n sql: `SELECT d.date, d.close, d.RSI_14, d.ATR_Pct,\n vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State, vix.ivr AS VIX_IVR, vix.ivp AS VIX_IVP\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\nORDER BY d.date DESC\nLIMIT 20`,\n },\n {\n description: \"Win/loss summary by block\",\n sql: `SELECT\n block_id,\n COUNT(*) as total_trades,\n SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) as winners,\n SUM(CASE WHEN pl <= 0 THEN 1 ELSE 0 END) as losers,\n ROUND(100.0 * SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data\nGROUP BY block_id\nORDER BY block_id`,\n },\n {\n description: \"Filter and paginate trades (replaces get_trades)\",\n sql: `SELECT date_opened, time_opened, strategy, legs, pl, num_contracts\nFROM trades.trade_data\nWHERE block_id = 'my-block'\n AND strategy ILIKE '%iron%'\n AND pl > 0\nORDER BY date_opened DESC\nLIMIT 50 OFFSET 0`,\n },\n {\n description: \"Market data query with VIX context\",\n sql: `SELECT d.date, d.close, d.Gap_Pct, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\n AND d.date BETWEEN '2024-01-01' AND '2024-06-30'\n AND vix.close > 20\nORDER BY d.date`,\n },\n {\n description: \"Compare backtest vs actual trades by date/strategy\",\n sql: `SELECT\n t.date_opened, t.strategy, t.legs as bt_legs, r.legs as actual_legs,\n t.pl as bt_pl, r.pl as actual_pl, r.pl - t.pl as slippage\nFROM trades.trade_data t\nJOIN trades.reporting_data r\n ON t.block_id = r.block_id\n AND t.date_opened = r.date_opened\n AND t.strategy = r.strategy\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n ],\n joins: [\n {\n description: \"Trade P&L with market context (lag-aware: multi-table JOIN before LAG for correctness)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close, d.Prev_Return_Pct,\n vix.open AS VIX_Open,\n d.RSI_14, d.Realized_Vol_20D,\n vix.close AS VIX_Close, vix.ivp AS VIX_IVP, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(RSI_14) OVER (PARTITION BY ticker ORDER BY date) AS prev_RSI_14,\n LAG(VIX_IVP) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_IVP,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open,\n m.prev_RSI_14, m.prev_VIX_IVP, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened DESC`,\n },\n {\n description: \"Trades with ORB context (opening range breakout from intraday bars)\",\n sql: `WITH orb_range AS (\n SELECT ticker, date,\n MAX(high) AS ORB_High,\n MIN(low) AS ORB_Low,\n MAX(high) - MIN(low) AS ORB_Range\n FROM market.intraday\n WHERE ticker = 'SPX'\n AND time >= '09:30' AND time <= '09:45'\n GROUP BY ticker, date\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n r.ORB_High, r.ORB_Low, r.ORB_Range\nFROM trades.trade_data t\nLEFT JOIN orb_range r ON t.date_opened = r.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n {\n description: \"VIX intraday data for a specific date (VIX bars are in market.intraday with ticker='VIX')\",\n sql: `SELECT time, open, high, low, close\nFROM market.intraday\nWHERE ticker = 'VIX'\n AND date = '2024-03-15'\nORDER BY time`,\n },\n {\n description: \"Trades on reversal days (lag-aware: Reversal_Type uses prior trading day via LAG)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.High_Before_Low, d.Reversal_Type\n FROM market.daily d\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Reversal_Type) OVER (PARTITION BY ticker ORDER BY date) AS prev_Reversal_Type,\n LAG(High_Before_Low) OVER (PARTITION BY ticker ORDER BY date) AS prev_High_Before_Low\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.prev_Reversal_Type, m.prev_High_Before_Low\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE m.prev_Reversal_Type != 0\n AND t.block_id = 'my-block'`,\n },\n {\n description: \"Enrich trades with market data (lag-aware: use enrich_trades tool for full enrichment)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close,\n vix.open AS VIX_Open,\n d.RSI_14, d.ATR_Pct,\n vix.close AS VIX_Close, cd.Vol_Regime\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nLEFT JOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'`,\n },\n ],\n hypothesis: [\n {\n description: \"Win rate by VIX regime (lag-aware: uses prior day's Vol_Regime from market._context_derived)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Vol_Regime\n FROM market.daily d\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n m.prev_Vol_Regime AS vol_regime,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) as winners,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Vol_Regime IS NOT NULL\nGROUP BY m.prev_Vol_Regime\nORDER BY m.prev_Vol_Regime`,\n },\n {\n description: \"P&L by day of week\",\n sql: `SELECT\n d.Day_of_Week,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl\nFROM trades.trade_data t\nJOIN market.daily d ON t.date_opened = d.date AND d.ticker = 'SPX'\nWHERE t.block_id = 'my-block'\nGROUP BY d.Day_of_Week\nORDER BY d.Day_of_Week`,\n },\n {\n description: \"Performance by VIX term structure (lag-aware: uses prior day's Term_Structure_State from market._context_derived)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Term_Structure_State) OVER (PARTITION BY ticker ORDER BY date) AS prev_Term_Structure_State\n FROM joined\n)\nSELECT\n CASE WHEN m.prev_Term_Structure_State = -1 THEN 'Backwardation'\n WHEN m.prev_Term_Structure_State = 1 THEN 'Contango'\n ELSE 'Flat' END as term_structure,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Term_Structure_State IS NOT NULL\nGROUP BY term_structure`,\n },\n {\n description: \"Aggregate by VIX buckets (lag-aware: uses prior day's VIX close from market.daily ticker='VIX')\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, vix.close AS VIX_Close\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close\n FROM joined\n)\nSELECT\n CASE\n WHEN m.prev_VIX_Close < 15 THEN '10-15'\n WHEN m.prev_VIX_Close < 20 THEN '15-20'\n WHEN m.prev_VIX_Close < 25 THEN '20-25'\n ELSE '25+'\n END as vix_bucket,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END)::FLOAT / COUNT(*) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_VIX_Close IS NOT NULL\nGROUP BY vix_bucket\nORDER BY vix_bucket`,\n },\n {\n description: \"Find similar days by conditions\",\n sql: `WITH ref AS (\n SELECT d.close, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX' AND d.date = '2024-01-15'\n)\nSELECT d.date, d.close, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date, ref\nWHERE d.ticker = 'SPX'\n AND d.date != '2024-01-15'\n AND cd.Vol_Regime = ref.Vol_Regime\n AND ABS(vix.close - ref.VIX_Close) < 3\nORDER BY ABS(vix.close - ref.VIX_Close)\nLIMIT 20`,\n },\n ],\n};\n","/**\n * Field Timing Utilities\n *\n * Derived sets and LAG CTE builder for lookahead-free market analytics.\n * All field classifications are derived from SCHEMA_DESCRIPTIONS timing annotations\n * in schema-metadata.ts -- no hardcoded column names.\n *\n * The normalized schema splits market data into:\n * - market.daily: per-ticker OHLCV + technical indicators (+ ivr/ivp for VIX-family)\n * - market._context_derived: derived cross-ticker fields (Vol_Regime, Term_Structure_State, etc.)\n * - market.context: LEGACY — preserved for backward compat but no longer the primary source\n *\n * buildLookaheadFreeQuery JOINs market.daily VIX ticker rows + market._context_derived inside a CTE\n * before applying LAG, ensuring LAG operates on the full ticker history (not just trade dates).\n * This guarantees Monday LAG returns Friday's values, not the previous trade day.\n *\n * Used by downstream tools (suggest_filters, analyze_regime_performance, etc.)\n * to ensure trade-entry queries only use data available at the time of trade entry.\n */\n\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.js\";\nimport { SCHEMA_DESCRIPTIONS } from \"./schema-metadata.js\";\n\nconst dailyColumns = SCHEMA_DESCRIPTIONS.market.tables.daily.columns;\nconst derivedColumns = SCHEMA_DESCRIPTIONS.market.tables._context_derived.columns;\n\nexport interface MarketLookupKey {\n date: string;\n ticker: string;\n}\n\n// ============================================================================\n// VIX field mapping — normalized schema\n// ============================================================================\n\n/**\n * VIX field mapping: column alias -> { table alias, source column, ticker, timing }\n * These are the VIX fields that downstream queries expect, mapped to the normalized schema.\n */\ninterface VixFieldMapping {\n alias: string; // Column name in query output (e.g., \"VIX_Close\")\n tableAlias: string; // SQL table alias (e.g., \"vix\")\n sourceCol: string; // Source column in market.daily (e.g., \"close\")\n ticker: string; // Ticker to join on (e.g., \"VIX\")\n timing: 'open' | 'close';\n}\n\nconst VIX_FIELD_MAPPINGS: VixFieldMapping[] = [\n // VIX fields\n { alias: \"VIX_Open\", tableAlias: \"vix\", sourceCol: \"open\", ticker: \"VIX\", timing: \"open\" },\n { alias: \"VIX_Close\", tableAlias: \"vix\", sourceCol: \"close\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_High\", tableAlias: \"vix\", sourceCol: \"high\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_Low\", tableAlias: \"vix\", sourceCol: \"low\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVR\", tableAlias: \"vix\", sourceCol: \"ivr\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVP\", tableAlias: \"vix\", sourceCol: \"ivp\", ticker: \"VIX\", timing: \"close\" },\n // VIX9D fields\n { alias: \"VIX9D_Open\", tableAlias: \"vix9d\", sourceCol: \"open\", ticker: \"VIX9D\", timing: \"open\" },\n { alias: \"VIX9D_Close\", tableAlias: \"vix9d\", sourceCol: \"close\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVR\", tableAlias: \"vix9d\", sourceCol: \"ivr\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVP\", tableAlias: \"vix9d\", sourceCol: \"ivp\", ticker: \"VIX9D\", timing: \"close\" },\n // VIX3M fields\n { alias: \"VIX3M_Open\", tableAlias: \"vix3m\", sourceCol: \"open\", ticker: \"VIX3M\", timing: \"open\" },\n { alias: \"VIX3M_Close\", tableAlias: \"vix3m\", sourceCol: \"close\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVR\", tableAlias: \"vix3m\", sourceCol: \"ivr\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVP\", tableAlias: \"vix3m\", sourceCol: \"ivp\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Unique VIX table aliases needed for the JOIN clause\nconst VIX_TABLE_ALIASES = [...new Set(VIX_FIELD_MAPPINGS.map(m => m.tableAlias))];\nconst VIX_TICKER_FOR_ALIAS = Object.fromEntries(\n VIX_FIELD_MAPPINGS.map(m => [m.tableAlias, m.ticker])\n);\n\n// Derived fields from _context_derived\nconst DERIVED_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\nconst DERIVED_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n// ============================================================================\n// Table-specific field sets (needed by CTE builder to know which table to alias)\n// ============================================================================\n\n/**\n * Open-known fields from market.daily (use as d.{field} in JOIN CTE)\n */\nexport const DAILY_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\n/**\n * Close-derived fields from market.daily (apply LAG as d.{field} in JOIN CTE)\n */\nexport const DAILY_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n/**\n * Static fields from market.daily (use as d.{field} in JOIN CTE — calendar facts)\n */\nexport const DAILY_STATIC_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'static')\n .map(([name]) => name)\n);\n\n/**\n * Open-known fields from VIX tickers + _context_derived (backward-compat alias of context open fields)\n */\nexport const CONTEXT_OPEN_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_FIELD_MAPPINGS.filter(m => m.timing === 'open').map(m => m.alias),\n ...DERIVED_OPEN_FIELDS,\n]);\n\n/**\n * Close-derived fields from VIX tickers + _context_derived (backward-compat alias of context close fields)\n */\nexport const CONTEXT_CLOSE_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_FIELD_MAPPINGS.filter(m => m.timing === 'close').map(m => m.alias),\n ...DERIVED_CLOSE_FIELDS,\n]);\n\n// ============================================================================\n// Combined field sets (for callers that don't need to know origin table)\n// ============================================================================\n\n/**\n * Fields known at or before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * Union of open-known fields from market.daily, VIX tickers, and _context_derived.\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const OPEN_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_OPEN_FIELDS,\n ...CONTEXT_OPEN_FIELDS,\n]);\n\n/**\n * Fields only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * Union of close-derived fields from market.daily, VIX tickers, and _context_derived.\n * Must use LAG() to get prior trading day's value in trade-entry queries.\n */\nexport const CLOSE_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_CLOSE_FIELDS,\n ...CONTEXT_CLOSE_FIELDS,\n]);\n\n/**\n * Calendar/metadata facts known before the trading day (Day_of_Week, Month, Is_Opex).\n * Only from market.daily (context has no static fields).\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const STATIC_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_STATIC_FIELDS,\n]);\n\n// ============================================================================\n// Query Builders\n// ============================================================================\n\n// Build the VIX JOIN clause (three LEFT JOINs: vix, vix9d, vix3m)\nfunction buildVixJoins(baseAlias: string = \"d\"): string {\n return VIX_TABLE_ALIASES\n .map(alias => `LEFT JOIN market.daily ${alias} ON ${alias}.date = ${baseAlias}.date AND ${alias}.ticker = '${VIX_TICKER_FOR_ALIAS[alias]}'`)\n .join(\"\\n \");\n}\n\n// SELECT columns from VIX tables: \"vix\".\"close\" AS \"VIX_Close\", ...\nfunction buildVixSelectCols(): string {\n return VIX_FIELD_MAPPINGS.map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`).join(\", \");\n}\n\n// SELECT columns from _context_derived: cd.\"Vol_Regime\", ...\nfunction buildDerivedSelectCols(): string {\n return [...DERIVED_OPEN_FIELDS, ...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n}\n\n/**\n * Builds a SQL query that joins trade keys to market.daily + market._context_derived + VIX tickers\n * with lookahead bias prevention:\n * - Open-known fields: used as-is (same-day values, known before market open)\n * - Static fields: used as-is (calendar facts, known in advance)\n * - Close-derived fields: LAG(field) OVER (PARTITION BY ticker ORDER BY date)\n * gives prior trading day's value\n *\n * The JOIN pattern is:\n * market.daily d\n * LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n * LEFT JOIN market.daily vix9d ON vix9d.date = d.date AND vix9d.ticker = 'VIX9D'\n * LEFT JOIN market.daily vix3m ON vix3m.date = d.date AND vix3m.ticker = 'VIX3M'\n * LEFT JOIN market._context_derived cd ON cd.date = d.date\n *\n * LAG operates on the FULL ticker history (all trading days for the ticker),\n * NOT just the requested dates. This ensures LAG sees the correct prior trading day\n * across weekends, holidays, and sparse trading strategies.\n *\n * @param tradeDatesOrKeys - Array of dates (legacy string[] overload) or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the parameter values)\n */\nexport function buildLookaheadFreeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.daily WHERE 1=0`, params: [] };\n }\n\n // Build field lists for the joined CTE\n const dailyOpenCols = [...DAILY_OPEN_FIELDS].map((f) => `d.\"${f}\"`).join(\", \");\n const dailyStaticCols = [...DAILY_STATIC_FIELDS].map((f) => `d.\"${f}\"`).join(\", \");\n const vixSelectCols = buildVixSelectCols();\n const derivedSelectCols = buildDerivedSelectCols();\n const vixJoins = buildVixJoins();\n\n // LAG columns — all close-derived fields from daily and VIX/derived\n const dailyLagCols = [...DAILY_CLOSE_FIELDS]\n .map((field) => `LAG(\"${field}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${field}\"`)\n .join(\",\\n \");\n const vixLagCols = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `LAG(\"${m.alias}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${m.alias}\"`)\n .join(\",\\n \");\n const derivedLagCols = [...DERIVED_CLOSE_FIELDS]\n .map(f => `LAG(\"${f}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${f}\"`)\n .join(\",\\n \");\n\n // Pass-through columns for the lagged CTE (unaliased, from joined CTE output)\n const dailyOpenPassthrough = [...DAILY_OPEN_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const dailyStaticPassthrough = [...DAILY_STATIC_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const vixOpenPassthrough = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'open')\n .map(m => `\"${m.alias}\"`)\n .join(\", \");\n const derivedOpenPassthrough = [...DERIVED_OPEN_FIELDS].map(f => `\"${f}\"`).join(\", \");\n\n // Legacy path for existing date-only callers (single ticker = DEFAULT_MARKET_TICKER)\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n const tradeDates = tradeDatesOrKeys as string[];\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n\n const sql = `WITH joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${[...DAILY_CLOSE_FIELDS].map((f) => `d.\"${f}\"`).join(\", \")}\n FROM market.daily d\n ${vixJoins}\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = $${tradeDates.length + 1}\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT * FROM lagged\n WHERE date IN (${placeholders})`;\n\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n }\n\n const tradeKeys = tradeDatesOrKeys as MarketLookupKey[];\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n ),\n joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${[...DAILY_CLOSE_FIELDS].map((f) => `d.\"${f}\"`).join(\", \")}\n FROM market.daily d\n ${vixJoins}\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker IN (SELECT DISTINCT ticker FROM requested)\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT lagged.*\n FROM lagged\n JOIN requested\n ON lagged.ticker = requested.ticker\n AND lagged.date = requested.date`;\n\n return { sql, params: values };\n}\n\n/**\n * Builds a SQL query that returns same-day close-derived values (no LAG).\n * Used for outcome/post-hoc analysis when includeOutcomeFields=true.\n *\n * These are values that were NOT available at trade entry time --\n * they represent the end-of-day result for the trade date itself.\n * Sources from market.daily, VIX ticker rows, and market._context_derived via LEFT JOIN.\n *\n * @param tradeDatesOrKeys - Array of dates or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the date values)\n */\nexport function buildOutcomeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.daily WHERE 1=0`, params: [] };\n }\n\n const dailyCloseCols = [...DAILY_CLOSE_FIELDS].map((f) => `d.\"${f}\"`).join(\", \");\n const vixCloseCols = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n const derivedCloseCols = [...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n const vixJoins = buildVixJoins();\n\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n const tradeDates = tradeDatesOrKeys as string[];\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n const sql = `SELECT d.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.daily d\n ${vixJoins}\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = $${tradeDates.length + 1}\n AND d.date IN (${placeholders})`;\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n }\n\n const tradeKeys = tradeDatesOrKeys as MarketLookupKey[];\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n )\n SELECT m.ticker, m.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.daily m\n ${buildVixJoins(\"m\")}\n LEFT JOIN market._context_derived cd ON cd.date = m.date\n JOIN requested\n ON m.ticker = requested.ticker\n AND m.date = requested.date`;\n\n return { sql, params: values };\n}\n","/**\n * Data Availability Helper\n *\n * Checks whether market data (daily, context, intraday) is available for a\n * given ticker and returns actionable warnings when data is missing.\n *\n * Used at the start of every market tool call to surface missing data with\n * clear import instructions rather than returning silent NULLs or cryptic errors.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\n\nexport interface DataAvailabilityReport {\n /** Whether market.daily has rows for the requested ticker */\n hasDailyData: boolean;\n /** Whether market.daily has VIX ticker data (normalized schema — replaces market.context check) */\n hasContextData: boolean;\n /** Whether market.intraday has rows for the requested ticker */\n hasIntradayData: boolean;\n /** Date range available in market.daily for the ticker, or null if no data */\n dailyDateRange: { min: string; max: string } | null;\n /** Date range of VIX data in market.daily, or null if no data */\n contextDateRange: { min: string; max: string } | null;\n /** Date range available in market.intraday for the ticker, or null if no data */\n intradayDateRange: { min: string; max: string } | null;\n /** Actionable warning messages for any missing data sources */\n warnings: string[];\n}\n\n/**\n * Checks data availability in market tables for the specified ticker.\n *\n * Queries COUNT, MIN(date), and MAX(date) from each relevant table.\n * Returns a report with boolean flags, date ranges, and actionable warning messages.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Ticker symbol to check (e.g., 'SPX')\n * @param options.checkIntraday - Whether to also check market.intraday (default: false)\n */\nexport async function checkDataAvailability(\n conn: DuckDBConnection,\n ticker: string,\n options?: { checkIntraday?: boolean }\n): Promise<DataAvailabilityReport> {\n const warnings: string[] = [];\n\n // Check market.daily for ticker\n let hasDailyData = false;\n let dailyDateRange: { min: string; max: string } | null = null;\n try {\n const dailyResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.daily WHERE ticker = $1`,\n [ticker]\n );\n const rows = dailyResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasDailyData = cnt > 0;\n if (hasDailyData && row.min_date && row.max_date) {\n dailyDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.daily table doesn't exist yet — treat as no data\n }\n\n if (!hasDailyData) {\n warnings.push(\n `No market.daily data for ticker ${ticker}. ` +\n `Import daily OHLCV with import_market_csv (target_table: \"daily\", ticker: \"${ticker}\") ` +\n `then run enrich_market_data.`\n );\n }\n\n // Check for VIX data in market.daily (normalized schema)\n let hasContextData = false;\n let contextDateRange: { min: string; max: string } | null = null;\n try {\n const contextResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.daily WHERE ticker = 'VIX'`\n );\n const rows = contextResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasContextData = cnt > 0;\n if (hasContextData && row.min_date && row.max_date) {\n contextDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.daily may not exist yet\n }\n\n if (!hasContextData) {\n warnings.push(\n `No VIX data in market.daily. ` +\n `Import VIX data with import_from_api (ticker: \"VIX\", target_table: \"daily\") ` +\n `or import_market_csv (target_table: \"context\") ` +\n `then run enrich_market_data for IVR/IVP enrichment.`\n );\n }\n\n // Optionally check market.intraday for ticker\n let hasIntradayData = false;\n let intradayDateRange: { min: string; max: string } | null = null;\n if (options?.checkIntraday) {\n try {\n const intradayResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.intraday WHERE ticker = $1`,\n [ticker]\n );\n const rows = intradayResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasIntradayData = cnt > 0;\n if (hasIntradayData && row.min_date && row.max_date) {\n intradayDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.intraday table doesn't exist yet — treat as no data\n }\n\n if (!hasIntradayData) {\n warnings.push(\n `No market.intraday data for ticker ${ticker}. ` +\n `Import intraday bars with import_market_csv (target_table: \"intraday\", ticker: \"${ticker}\").`\n );\n }\n }\n\n return {\n hasDailyData,\n hasContextData,\n hasIntradayData,\n dailyDateRange,\n contextDateRange,\n intradayDateRange,\n warnings,\n };\n}\n","/**\n * Pure TypeScript indicator functions for the market enrichment pipeline.\n *\n * All functions are pure (no DB access, no side effects) and take number arrays\n * or structured inputs returning computed arrays or values.\n *\n * Formulas follow TradingView Pine Script conventions:\n * - RSI: Wilder smoothing seeded with SMA of first period changes\n * - ATR: Wilder smoothing seeded with SMA of first period TR values\n * - EMA: Standard EMA seeded with SMA of first period bars\n * - Realized Vol: Population stddev, annualized by sqrt(252)*100\n *\n * References:\n * - Wilder, J.W. (1978) \"New Concepts in Technical Trading Systems\"\n * - TradingView Pine Script documentation (ta.rsi, ta.atr, ta.ema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { upsertMarketImportMetadata } from \"../sync/metadata.js\";\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.js\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface ContextRow {\n date: string;\n VIX_Open?: number | null;\n VIX_Close?: number | null;\n VIX_High?: number | null;\n VIX_RTH_Open?: number | null;\n VIX9D_Open?: number | null;\n VIX9D_Close?: number | null;\n VIX3M_Open?: number | null;\n VIX3M_Close?: number | null;\n}\n\nexport interface EnrichedContextRow extends ContextRow {\n VIX_Gap_Pct?: number | null;\n VIX_Change_Pct?: number | null;\n VIX9D_Change_Pct?: number | null;\n VIX3M_Change_Pct?: number | null;\n VIX9D_VIX_Ratio?: number | null;\n VIX_VIX3M_Ratio?: number | null;\n VIX_Spike_Pct?: number | null;\n Vol_Regime?: number | null;\n Term_Structure_State?: number | null;\n VIX_IVR?: number | null;\n VIX_IVP?: number | null;\n VIX9D_IVR?: number | null;\n VIX9D_IVP?: number | null;\n VIX3M_IVR?: number | null;\n VIX3M_IVP?: number | null;\n}\n\n// =============================================================================\n// Primitive Indicators\n// =============================================================================\n\n/**\n * Wilder's RSI.\n * Input: closing prices ordered oldest→newest.\n * Returns array same length as input; first `period` entries are NaN (warmup).\n *\n * Formula:\n * - Seed avgGain/avgLoss from SMA of first `period` changes (bars 1..period)\n * - result[period] = 100 - 100/(1 + avgGain/avgLoss)\n * - Subsequent: avgGain = (prev*(period-1) + gain)/period (Wilder smoothing)\n */\nexport function computeRSI(closes: number[], period = 14): number[] {\n const result = new Array<number>(closes.length).fill(NaN);\n if (closes.length < period + 1) return result;\n\n // Seed: average of first `period` gains and losses (changes at indices 1..period)\n let avgGain = 0;\n let avgLoss = 0;\n for (let i = 1; i <= period; i++) {\n const change = closes[i] - closes[i - 1];\n if (change > 0) avgGain += change;\n else avgLoss += Math.abs(change);\n }\n avgGain /= period;\n avgLoss /= period;\n\n result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < closes.length; i++) {\n const change = closes[i] - closes[i - 1];\n const gain = change > 0 ? change : 0;\n const loss = change < 0 ? Math.abs(change) : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n\n return result;\n}\n\n/**\n * Wilder's Average True Range (ATR).\n * Returns array same length as input; first `period` entries are NaN.\n *\n * True Range = max(high - low, |high - prevClose|, |low - prevClose|)\n * TR can be computed from bar index 1 (needs prevClose).\n * First ATR = SMA of TR[1..period] (simple average of first `period` TR values).\n * ATR[i] for i > period: (ATR_prev * (period-1) + TR[i]) / period (Wilder)\n */\nexport function computeATR(\n highs: number[],\n lows: number[],\n closes: number[],\n period = 14\n): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period + 1) return result;\n\n // Compute true ranges starting from index 1 (needs prevClose)\n const tr = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n const prevClose = closes[i - 1];\n tr[i] = Math.max(\n highs[i] - lows[i],\n Math.abs(highs[i] - prevClose),\n Math.abs(lows[i] - prevClose)\n );\n }\n\n // First ATR = SMA of TR[1..period]\n let atrSum = 0;\n for (let i = 1; i <= period; i++) {\n atrSum += tr[i];\n }\n let atr = atrSum / period;\n result[period] = atr;\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < n; i++) {\n atr = (atr * (period - 1) + tr[i]) / period;\n result[i] = atr;\n }\n\n return result;\n}\n\n/**\n * Exponential Moving Average (EMA) with SMA seed (TradingView convention).\n * Returns array same length as input; first `period-1` entries are NaN.\n *\n * Seed: EMA[period-1] = SMA of first `period` bars\n * k = 2 / (period + 1)\n * EMA[i] = close[i] * k + EMA[i-1] * (1 - k)\n */\nexport function computeEMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period) return result;\n\n // Seed from SMA of first period bars\n let seed = 0;\n for (let i = 0; i < period; i++) {\n seed += closes[i];\n }\n seed /= period;\n result[period - 1] = seed;\n\n const k = 2 / (period + 1);\n for (let i = period; i < n; i++) {\n result[i] = closes[i] * k + result[i - 1] * (1 - k);\n }\n\n return result;\n}\n\n/**\n * Simple Moving Average (SMA).\n * Returns array same length as input; first `period-1` entries are NaN.\n * SMA[i] = average of closes[i-period+1..i]\n */\nexport function computeSMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n for (let i = period - 1; i < n; i++) {\n let sum = 0;\n for (let j = i - period + 1; j <= i; j++) {\n sum += closes[j];\n }\n result[i] = sum / period;\n }\n\n return result;\n}\n\n// =============================================================================\n// Composite Indicators\n// =============================================================================\n\n/**\n * Realized Volatility using log returns, population stddev, annualized.\n * Returns array same length as input; first `period` entries are NaN\n * (need period+1 closes to compute period log returns).\n *\n * log_return[i] = ln(close[i] / close[i-1])\n * Vol[i] = stddev(log_returns[i-period+1..i], N) * sqrt(252) * 100\n */\nexport function computeRealizedVol(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n // Compute log returns (one less than closes count)\n const logReturns = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n logReturns[i] = Math.log(closes[i] / closes[i - 1]);\n }\n\n // Rolling stddev of log returns over `period` window\n // First valid: index = period (window uses log returns at i-period+1..i, earliest is i=period)\n for (let i = period; i < n; i++) {\n const window: number[] = [];\n for (let j = i - period + 1; j <= i; j++) {\n window.push(logReturns[j]);\n }\n\n const mean = window.reduce((a, b) => a + b, 0) / period;\n // Population stddev\n const variance = window.reduce((sum, v) => sum + (v - mean) ** 2, 0) / period;\n result[i] = Math.sqrt(variance) * Math.sqrt(252) * 100;\n }\n\n return result;\n}\n\n// =============================================================================\n// Row-Level Helpers\n// =============================================================================\n\n/**\n * Consecutive up/down days counter.\n * Positive = consecutive up days, negative = consecutive down days.\n * Resets to 0 on flat day.\n * First element is always 0 (no prior bar).\n */\nexport function computeConsecutiveDays(closes: number[]): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(0);\n\n for (let i = 1; i < n; i++) {\n if (closes[i] > closes[i - 1]) {\n // Up day: continue positive streak or start at +1\n result[i] = result[i - 1] >= 0 ? result[i - 1] + 1 : 1;\n } else if (closes[i] < closes[i - 1]) {\n // Down day: continue negative streak or start at -1\n result[i] = result[i - 1] <= 0 ? result[i - 1] - 1 : -1;\n } else {\n // Flat: reset to 0\n result[i] = 0;\n }\n }\n\n return result;\n}\n\n/**\n * Gap filled indicator.\n * Returns 1 if the price gap from prior close was filled intraday, 0 otherwise.\n *\n * Gap up (open > priorClose): filled if low <= priorClose\n * Gap down (open < priorClose): filled if high >= priorClose\n * No gap (open = priorClose): returns 0\n */\nexport function isGapFilled(\n open: number,\n high: number,\n low: number,\n priorClose: number\n): number {\n if (open > priorClose && low <= priorClose) return 1;\n if (open < priorClose && high >= priorClose) return 1;\n return 0;\n}\n\n/**\n * Options expiration (OPEX) detection.\n * Takes a YYYY-MM-DD string; returns 1 if 3rd Friday of month, 0 otherwise.\n *\n * Uses string parsing (not new Date(\"YYYY-MM-DD\")) to avoid timezone issues.\n * See CLAUDE.md Date Handling rules: calendar dates from CSVs use local Date constructor.\n */\nexport function isOpex(dateStr: string): number {\n // Parse via regex to avoid timezone issues (CLAUDE.md: use string parsing)\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(dateStr);\n if (!match) return 0;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // 0-indexed for Date constructor\n const day = parseInt(match[3], 10);\n\n // Use local Date constructor (avoids UTC midnight shift)\n // Check if this day is a Friday (getDay() === 5)\n const date = new Date(year, month, day);\n if (date.getDay() !== 5) return 0;\n\n // Find first Friday of month\n const firstDay = new Date(year, month, 1);\n const firstFridayDay = ((5 - firstDay.getDay() + 7) % 7) + 1; // day of month\n\n // Third Friday = first Friday + 14\n const thirdFriday = firstFridayDay + 14;\n\n return day === thirdFriday ? 1 : 0;\n}\n\n// =============================================================================\n// Tier 2 VIX Functions\n// =============================================================================\n\n/**\n * Compute VIX-derived fields for market.context rows.\n * Takes sorted context rows (oldest first) with VIX OHLCV data.\n * Returns enriched rows with pct change, ratio, and spike fields.\n *\n * Fields requiring prior row (NaN on first row):\n * - VIX_Gap_Pct: (VIX_Open - prior VIX_Close) / prior VIX_Close * 100\n * - VIX_Change_Pct: (VIX_Close - prior VIX_Close) / prior VIX_Close * 100\n * - VIX9D_Change_Pct: (VIX9D_Close - VIX9D_Open) / VIX9D_Open * 100\n * - VIX3M_Change_Pct: (VIX3M_Close - VIX3M_Open) / VIX3M_Open * 100\n *\n * Same-day fields (no lookback needed):\n * - VIX9D_VIX_Ratio: VIX9D_Close / VIX_Close\n * - VIX_VIX3M_Ratio: VIX_Close / VIX3M_Close\n * - VIX_Spike_Pct: (VIX_High - VIX_Open) / VIX_Open * 100\n */\nexport function computeVIXDerivedFields(rows: ContextRow[]): EnrichedContextRow[] {\n return rows.map((row, i): EnrichedContextRow => {\n const prev = i > 0 ? rows[i - 1] : null;\n\n // Effective open: prefer RTH open from intraday bars, fall back to daily VIX_Open\n const effectiveOpen = row.VIX_RTH_Open ?? row.VIX_Open;\n\n // Same-day ratio and spike fields\n const VIX9D_VIX_Ratio =\n row.VIX9D_Close != null && row.VIX_Close != null && row.VIX_Close !== 0\n ? row.VIX9D_Close / row.VIX_Close\n : null;\n\n const VIX_VIX3M_Ratio =\n row.VIX_Close != null && row.VIX3M_Close != null && row.VIX3M_Close !== 0\n ? row.VIX_Close / row.VIX3M_Close\n : null;\n\n const VIX_Spike_Pct =\n row.VIX_High != null && effectiveOpen != null && effectiveOpen !== 0\n ? ((row.VIX_High - effectiveOpen) / effectiveOpen) * 100\n : null;\n\n // Intraday change fields (same-day open to close)\n const VIX9D_Change_Pct =\n row.VIX9D_Close != null && row.VIX9D_Open != null && row.VIX9D_Open !== 0\n ? ((row.VIX9D_Close - row.VIX9D_Open) / row.VIX9D_Open) * 100\n : null;\n\n const VIX3M_Change_Pct =\n row.VIX3M_Close != null && row.VIX3M_Open != null && row.VIX3M_Open !== 0\n ? ((row.VIX3M_Close - row.VIX3M_Open) / row.VIX3M_Open) * 100\n : null;\n\n // Prior-row dependent fields\n const prevVIXClose = prev?.VIX_Close ?? null;\n\n const VIX_Gap_Pct =\n effectiveOpen != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((effectiveOpen - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n const VIX_Change_Pct =\n row.VIX_Close != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((row.VIX_Close - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n return {\n ...row,\n VIX_Gap_Pct,\n VIX_Change_Pct,\n VIX9D_Change_Pct,\n VIX3M_Change_Pct,\n VIX9D_VIX_Ratio,\n VIX_VIX3M_Ratio,\n VIX_Spike_Pct,\n };\n });\n}\n\n/**\n * Classify trend direction based on 20-day return percentage.\n *\n * Uses Return_20D thresholds:\n * > 1% = \"up\"\n * < -1% = \"down\"\n * else = \"flat\" (between -1% and 1% inclusive)\n *\n * Returns null for null/NaN input (no Return_20D data available).\n */\nexport function classifyTrendDirection(return20d: number | null): string | null {\n if (return20d === null || return20d === undefined || isNaN(return20d)) return null;\n if (return20d > 1) return \"up\";\n if (return20d < -1) return \"down\";\n return \"flat\";\n}\n\n/**\n * Classify VIX level into volatility regime 1-6.\n *\n * 1: Very Low VIX < 13\n * 2: Low 13 <= VIX < 16\n * 3: Normal 16 <= VIX < 20\n * 4: Elevated 20 <= VIX < 25\n * 5: High 25 <= VIX < 30\n * 6: Extreme VIX >= 30\n */\nexport function classifyVolRegime(vixClose: number): number {\n if (vixClose < 13) return 1;\n if (vixClose < 16) return 2;\n if (vixClose < 20) return 3;\n if (vixClose < 25) return 4;\n if (vixClose < 30) return 5;\n return 6;\n}\n\n/**\n * Classify VIX term structure state.\n * Returns:\n * 1 = Contango: VIX9D < VIX and VIX < VIX3M (normal, longer-dated vol is higher)\n * -1 = Backwardation: VIX9D > VIX or VIX > VIX3M (inverted — fear in front)\n * 0 = Flat: all three within ~1% tolerance of each other\n *\n * Flatness check: both ratios VIX9D/VIX and VIX/VIX3M within 1% of 1.0\n */\nexport function classifyTermStructure(\n vix9dClose: number,\n vixClose: number,\n vix3mClose: number\n): number {\n // Match PineScript: vix9dClose > vixClose ? -1 : vixClose > vix3mClose ? 0 : 1\n if (vix9dClose > vixClose) return -1;\n if (vixClose > vix3mClose) return 0;\n return 1;\n}\n\n/**\n * Implied Volatility Rank (IVR) over a rolling window.\n * IVR[i] = (current - min) / (max - min) * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Per D-10: Shows where current value sits in its 252-day range.\n * When range is 0 (all values identical), returns 50 (middle).\n */\nexport function computeIVR(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let min = Infinity, max = -Infinity;\n for (let j = i - period + 1; j <= i; j++) {\n if (values[j] < min) min = values[j];\n if (values[j] > max) max = values[j];\n }\n const range = max - min;\n result[i] = range > 0 ? ((values[i] - min) / range) * 100 : 50;\n }\n return result;\n}\n\n/**\n * Implied Volatility Percentile (IVP) over a rolling window.\n * IVP[i] = count(prior 251 days where value <= current) / 251 * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Per D-10: Shows what percentage of past year was at or below current.\n * Note: divides by (period - 1) = 251 because we compare against prior days only.\n */\nexport function computeIVP(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let countLessOrEqual = 0;\n // Compare current against prior (period-1) days (not including current day itself)\n for (let j = i - period + 1; j < i; j++) {\n if (values[j] <= values[i]) countLessOrEqual++;\n }\n result[i] = (countLessOrEqual / (period - 1)) * 100;\n }\n return result;\n}\n\n// =============================================================================\n// Enrichment Runner Types\n// =============================================================================\n\nexport interface EnrichmentOptions {\n forceFull?: boolean;\n}\n\nexport interface TierStatus {\n status: \"complete\" | \"skipped\" | \"error\";\n fieldsWritten?: number;\n reason?: string;\n}\n\nexport interface EnrichmentResult {\n ticker: string;\n tier1: TierStatus;\n tier2: TierStatus;\n tier3: TierStatus;\n rowsEnriched: number;\n enrichedThrough: string | null;\n}\n\n// =============================================================================\n// Enrichment Runner Private Helpers\n// =============================================================================\n\n/** Subtract N calendar days from a YYYY-MM-DD string, returns YYYY-MM-DD */\nfunction subtractDays(dateStr: string, days: number): string {\n const d = new Date(dateStr + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().split(\"T\")[0];\n}\n\n/** Parse YYYY-MM-DD to a local Date without timezone conversion */\nfunction parseDateStr(dateStr: string): Date | null {\n const m = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (!m) return null;\n return new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));\n}\n\n/** Batch UPDATE market.daily with computed enrichment fields */\nasync function batchUpdateDaily(\n conn: DuckDBConnection,\n rows: Array<Record<string, unknown>>,\n columns: string[]\n): Promise<void> {\n if (rows.length === 0) return;\n // Build VALUES list with $N params\n const allCols = [\"ticker\", \"date\", ...columns];\n const placeholders = rows\n .map((_, rowIdx) => {\n const params = allCols.map((__, colIdx) => `$${rowIdx * allCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n })\n .join(\", \");\n const setClauses = columns.map((c) => `${c} = v.${c}`).join(\", \");\n const sql = `\n UPDATE market.daily AS t\n SET ${setClauses}\n FROM (VALUES ${placeholders}) AS v(${allCols.join(\", \")})\n WHERE t.ticker = v.ticker AND t.date = v.date\n `;\n const params = rows.flatMap((row) => allCols.map((col) => row[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n}\n\n/** Run Tier 2: enrich market.daily (ivr/ivp) and market._context_derived with computed VIX fields */\nasync function runTier2(conn: DuckDBConnection): Promise<TierStatus> {\n // Step 1: Discover VIX-family tickers dynamically (per D-06)\n const tickerResult = await conn.runAndReadAll(\n `SELECT DISTINCT ticker FROM market.daily WHERE ticker LIKE 'VIX%' ORDER BY ticker`\n );\n const vixTickers = tickerResult.getRows().map(r => r[0] as string);\n if (vixTickers.length === 0 || !vixTickers.includes('VIX')) {\n return { status: \"skipped\", reason: \"no VIX data in market.daily — import VIX ticker first\" };\n }\n\n // Step 2: Compute IVR/IVP for each VIX-family ticker and write to market.daily\n for (const ticker of vixTickers) {\n const closeResult = await conn.runAndReadAll(\n `SELECT date, close FROM market.daily WHERE ticker = $1 AND close IS NOT NULL ORDER BY date ASC`,\n [ticker]\n );\n const rows = closeResult.getRows();\n if (rows.length === 0) continue;\n\n const dates = rows.map(r => r[0] as string);\n const closes = rows.map(r => r[1] as number);\n const ivrValues = computeIVR(closes, 252);\n const ivpValues = computeIVP(closes, 252);\n\n // Batch UPDATE market.daily SET ivr, ivp WHERE ticker = ? AND date = ?\n const BATCH_SIZE = 500;\n for (let start = 0; start < dates.length; start += BATCH_SIZE) {\n const batchDates = dates.slice(start, start + BATCH_SIZE);\n const batchIvr = ivrValues.slice(start, start + BATCH_SIZE);\n const batchIvp = ivpValues.slice(start, start + BATCH_SIZE);\n\n const placeholders = batchDates.map((_, rowIdx) => {\n const base = rowIdx * 3;\n return `($${base + 1}, $${base + 2}, $${base + 3})`;\n }).join(\", \");\n\n const sql = `\n UPDATE market.daily AS t\n SET ivr = v.ivr, ivp = v.ivp\n FROM (VALUES ${placeholders}) AS v(date, ivr, ivp)\n WHERE t.ticker = $${batchDates.length * 3 + 1} AND t.date = v.date\n `;\n const params: (string | number | null)[] = [];\n for (let i = 0; i < batchDates.length; i++) {\n params.push(batchDates[i]);\n params.push(isNaN(batchIvr[i]) ? null : batchIvr[i]);\n params.push(isNaN(batchIvp[i]) ? null : batchIvp[i]);\n }\n params.push(ticker);\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n }\n\n // Step 3: Build ContextRow objects from market.daily VIX tickers for derived fields\n // Query VIX close/open/high, VIX9D close/open, VIX3M close/open, plus Return_20D for Trend_Direction\n const contextQuery = `\n SELECT\n vix.date,\n vix.open AS VIX_Open,\n vix.close AS VIX_Close,\n vix.high AS VIX_High,\n vix9d.open AS VIX9D_Open,\n vix9d.close AS VIX9D_Close,\n vix3m.open AS VIX3M_Open,\n vix3m.close AS VIX3M_Close,\n spx.Return_20D\n FROM market.daily vix\n LEFT JOIN market.daily vix9d ON vix9d.date = vix.date AND vix9d.ticker = 'VIX9D'\n LEFT JOIN market.daily vix3m ON vix3m.date = vix.date AND vix3m.ticker = 'VIX3M'\n LEFT JOIN market.daily spx ON spx.date = vix.date AND spx.ticker = $1\n WHERE vix.ticker = 'VIX' AND vix.close IS NOT NULL\n ORDER BY vix.date ASC\n `;\n\n const rawResult = await conn.runAndReadAll(contextQuery, [DEFAULT_MARKET_TICKER]);\n const rawRows = rawResult.getRows();\n if (rawRows.length === 0) return { status: \"complete\", fieldsWritten: 0 };\n\n // Query VIX RTH open from intraday bars (same logic as before)\n const rthOpenByDate = new Map<string, number>();\n try {\n const rthReader = await conn.runAndReadAll(\n `SELECT date, open FROM market.intraday WHERE ticker = 'VIX' AND time >= '09:30' AND time <= '09:32' ORDER BY date, time ASC`\n );\n for (const r of rthReader.getRows()) {\n const dateStr = r[0] as string;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = r[1] as number | null;\n if (openVal != null) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n\n const return20dByDate = new Map<string, number | null>();\n const contextRows: ContextRow[] = rawRows.map((r) => {\n const dateStr = r[0] as string;\n return20dByDate.set(dateStr, r[8] as number | null);\n return {\n date: dateStr,\n VIX_Open: r[1] as number | null,\n VIX_Close: r[2] as number | null,\n VIX_High: r[3] as number | null,\n VIX_RTH_Open: rthOpenByDate.get(dateStr) ?? null,\n VIX9D_Open: r[4] as number | null,\n VIX9D_Close: r[5] as number | null,\n VIX3M_Open: r[6] as number | null,\n VIX3M_Close: r[7] as number | null,\n };\n });\n\n // Step 4: Compute derived fields (reuse existing pure functions unchanged)\n const enrichedContext = computeVIXDerivedFields(contextRows);\n\n // Step 5: Write derived fields to market._context_derived (INSERT OR REPLACE)\n const derivedCols = [\"date\", \"Vol_Regime\", \"Term_Structure_State\", \"Trend_Direction\", \"VIX_Spike_Pct\", \"VIX_Gap_Pct\"];\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {\n const batch = enrichedContext.slice(start, start + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = derivedCols.map((__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n\n const sql = `INSERT OR REPLACE INTO market._context_derived (${derivedCols.join(\", \")}) VALUES ${placeholders}`;\n const params = batch.flatMap((r) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return [\n r.date,\n vc !== null ? classifyVolRegime(vc) : null,\n v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n r.VIX_Spike_Pct ?? null,\n r.VIX_Gap_Pct ?? null,\n ];\n });\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n\n // Step 6: ALSO still write to market.context for backward compat during transition\n // This ensures existing queries against market.context keep working\n const tier2Cols = [\n \"VIX_RTH_Open\", \"VIX_Gap_Pct\", \"VIX_Change_Pct\",\n \"VIX9D_Change_Pct\", \"VIX3M_Change_Pct\",\n \"VIX9D_VIX_Ratio\", \"VIX_VIX3M_Ratio\", \"VIX_Spike_Pct\",\n \"Vol_Regime\", \"Term_Structure_State\",\n \"VIX_IVR\", \"VIX_IVP\", \"VIX9D_IVR\", \"VIX9D_IVP\", \"VIX3M_IVR\", \"VIX3M_IVP\",\n \"Trend_Direction\",\n ];\n // Compute IVR/IVP arrays for VIX, VIX9D, VIX3M from contextRows\n const vixCloses = contextRows.map(r => r.VIX_Close ?? NaN);\n const vixIVR = computeIVR(vixCloses, 252);\n const vixIVP = computeIVP(vixCloses, 252);\n const vix9dCloses = contextRows.map(r => r.VIX9D_Close ?? NaN);\n const vix9dIVR = computeIVR(vix9dCloses, 252);\n const vix9dIVP = computeIVP(vix9dCloses, 252);\n const vix3mCloses = contextRows.map(r => r.VIX3M_Close ?? NaN);\n const vix3mIVR = computeIVR(vix3mCloses, 252);\n const vix3mIVP = computeIVP(vix3mCloses, 252);\n\n try {\n const allCols2 = [\"date\", ...tier2Cols];\n const updateRows = enrichedContext.map((r, i) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return {\n date: r.date,\n VIX_RTH_Open: r.VIX_RTH_Open ?? null,\n VIX_Gap_Pct: r.VIX_Gap_Pct ?? null,\n VIX_Change_Pct: r.VIX_Change_Pct ?? null,\n VIX9D_Change_Pct: r.VIX9D_Change_Pct ?? null,\n VIX3M_Change_Pct: r.VIX3M_Change_Pct ?? null,\n VIX9D_VIX_Ratio: r.VIX9D_VIX_Ratio ?? null,\n VIX_VIX3M_Ratio: r.VIX_VIX3M_Ratio ?? null,\n VIX_Spike_Pct: r.VIX_Spike_Pct ?? null,\n Vol_Regime: vc !== null ? classifyVolRegime(vc) : null,\n Term_Structure_State: v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n VIX_IVR: isNaN(vixIVR[i]) ? null : vixIVR[i],\n VIX_IVP: isNaN(vixIVP[i]) ? null : vixIVP[i],\n VIX9D_IVR: isNaN(vix9dIVR[i]) ? null : vix9dIVR[i],\n VIX9D_IVP: isNaN(vix9dIVP[i]) ? null : vix9dIVP[i],\n VIX3M_IVR: isNaN(vix3mIVR[i]) ? null : vix3mIVR[i],\n VIX3M_IVP: isNaN(vix3mIVP[i]) ? null : vix3mIVP[i],\n Trend_Direction: classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n };\n });\n for (let start2 = 0; start2 < updateRows.length; start2 += BATCH_SIZE) {\n const batch = updateRows.slice(start2, start2 + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = allCols2.map((__, colIdx) => `$${rowIdx * allCols2.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n const setClauses = tier2Cols.map(c => `${c} = v.${c}`).join(\", \");\n const sql = `UPDATE market.context AS t SET ${setClauses} FROM (VALUES ${placeholders}) AS v(${allCols2.join(\", \")}) WHERE t.date = v.date`;\n const params = batch.flatMap(row => allCols2.map(col => (row as Record<string, unknown>)[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n } catch {\n // market.context may not exist or have rows — that's fine during transition\n }\n\n return { status: \"complete\", fieldsWritten: derivedCols.length - 1 }; // -1 for date\n}\n\n/** Check if any intraday data exists for a ticker */\nasync function hasTier3Data(conn: DuckDBConnection, ticker: string): Promise<boolean> {\n const r = await conn.runAndReadAll(\n `SELECT COUNT(*) FROM market.intraday WHERE ticker = $1 LIMIT 1`,\n [ticker]\n );\n return Number(r.getRows()[0]?.[0] ?? 0) > 0;\n}\n\n// =============================================================================\n// Context Enrichment (Tier 2 standalone)\n// =============================================================================\n\n/**\n * Run Tier 2 context enrichment directly, computing VIX-derived fields\n * (VIX_Gap_Pct, VIX_Change_Pct, VIX9D_VIX_Ratio, Vol_Regime, etc.) and\n * writing them to market.context.\n *\n * Used by importFromMassive() for context table imports — after importing\n * VIX/VIX9D/VIX3M bars, Tier 2 needs to run immediately to populate derived\n * fields. Unlike runEnrichment(), this does not require a ticker with daily data.\n *\n * Returns a TierStatus describing the outcome.\n */\nexport async function runContextEnrichment(conn: DuckDBConnection): Promise<TierStatus> {\n return runTier2(conn);\n}\n\n// =============================================================================\n// Enrichment Runner\n// =============================================================================\n\n/**\n * Run all three tiers of market enrichment for a given ticker.\n *\n * Tier 1: Compute and write OHLCV-derived fields to market.daily using a\n * 200-day lookback window from the enriched_through watermark.\n * Tier 2: Compute and write VIX-derived fields to market.context.\n * Tier 3: Compute intraday timing fields (High_Time, Low_Time, High_Before_Low,\n * Reversal_Type, Opening_Drive_Strength, Intraday_Realized_Vol) from\n * market.intraday bars; skips gracefully if no intraday data exists.\n *\n * The enriched_through watermark is upserted into market._sync_metadata with\n * source='enrichment', ticker, target_table='daily' after a successful Tier 1 run.\n *\n * Note: wilder_state column exists but is NOT written (superseded by 200-day lookback).\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Normalized ticker symbol (e.g., \"SPX\")\n * @param opts - Options including forceFull (reset watermark and reprocess all rows)\n */\nexport async function runEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n opts: EnrichmentOptions = {}\n): Promise<EnrichmentResult> {\n const { forceFull = false } = opts;\n\n // 1. Get enriched_through watermark\n const watermarkRow = await conn.runAndReadAll(\n `SELECT enriched_through FROM market._sync_metadata\n WHERE source = 'enrichment' AND ticker = $1 AND target_table = 'daily'`,\n [ticker]\n );\n const watermark: string | null = forceFull\n ? null\n : ((watermarkRow.getRows()[0]?.[0] as string | null) ?? null);\n\n // 2. Compute lookback start: watermark - 200 calendar days (as string comparison)\n const lookbackStart = watermark ? subtractDays(watermark, 200) : null;\n\n // 3. Fetch OHLCV rows from market.daily\n let fetchSql = `SELECT ticker, date, open, high, low, close FROM market.daily WHERE ticker = $1`;\n const fetchParams: unknown[] = [ticker];\n if (lookbackStart) {\n fetchSql += ` AND date >= $2`;\n fetchParams.push(lookbackStart);\n }\n fetchSql += ` ORDER BY date ASC`;\n const rawReader = await conn.runAndReadAll(fetchSql, fetchParams as (string | number | boolean | null | bigint)[]);\n const rawRows = rawReader.getRows();\n\n if (rawRows.length === 0) {\n return {\n ticker,\n tier1: { status: \"skipped\", reason: \"no data in market.daily\" },\n tier2: { status: \"skipped\", reason: \"no daily data\" },\n tier3: { status: \"skipped\", reason: \"no daily data\" },\n rowsEnriched: 0,\n enrichedThrough: null,\n };\n }\n\n // 4. Extract typed arrays from raw rows\n // Columns: ticker(0), date(1), open(2), high(3), low(4), close(5)\n const dates = rawRows.map((r) => r[1] as string);\n const opens = rawRows.map((r) => Number(r[2]));\n const highs = rawRows.map((r) => Number(r[3]));\n const lows = rawRows.map((r) => Number(r[4]));\n const closes = rawRows.map((r) => Number(r[5]));\n\n // 5. Compute Tier 1 indicators\n const rsi14 = computeRSI(closes, 14);\n const atrArr = computeATR(highs, lows, closes, 14);\n const ema21 = computeEMA(closes, 21);\n const sma50 = computeSMA(closes, 50);\n const rvol5 = computeRealizedVol(closes, 5);\n const rvol20 = computeRealizedVol(closes, 20);\n const consecutiveDays = computeConsecutiveDays(closes);\n\n // 6. Determine which rows to write back (only rows after watermark)\n const writeRows =\n watermark && !forceFull\n ? rawRows.map((_, i) => i).filter((i) => dates[i] > watermark)\n : rawRows.map((_, i) => i);\n\n if (writeRows.length === 0) {\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: 0, reason: \"already up to date\" },\n tier2: await runTier2(conn),\n tier3: {\n status: \"skipped\",\n reason: \"no intraday data in market.intraday\",\n },\n rowsEnriched: 0,\n enrichedThrough: watermark,\n };\n }\n\n // 7. Build enriched rows for batch UPDATE\n const enrichedRows = writeRows.map((i) => {\n const atrVal = atrArr[i];\n const atrPct = !isNaN(atrVal) && closes[i] > 0 ? (atrVal / closes[i]) * 100 : null;\n const priorClose = i > 0 ? closes[i - 1] : null;\n const priorReturn =\n i > 1 ? ((closes[i - 1] - closes[i - 2]) / closes[i - 2]) * 100 : null;\n const gapPct =\n priorClose !== null && priorClose > 0\n ? ((opens[i] - priorClose) / priorClose) * 100\n : null;\n const intradayRangePct =\n opens[i] > 0 ? ((highs[i] - lows[i]) / opens[i]) * 100 : null;\n const intradayReturnPct =\n opens[i] > 0 ? ((closes[i] - opens[i]) / opens[i]) * 100 : null;\n const hiLoRange = highs[i] - lows[i];\n const closePosInRange =\n hiLoRange > 0 ? (closes[i] - lows[i]) / hiLoRange : null;\n const ret5d =\n i >= 5 && closes[i - 5] > 0\n ? ((closes[i] - closes[i - 5]) / closes[i - 5]) * 100\n : null;\n const ret20d =\n i >= 20 && closes[i - 20] > 0\n ? ((closes[i] - closes[i - 20]) / closes[i - 20]) * 100\n : null;\n const gapFilled =\n priorClose !== null ? isGapFilled(opens[i], highs[i], lows[i], priorClose) : null;\n const dateObj = parseDateStr(dates[i]);\n const dayOfWeek = dateObj ? dateObj.getDay() : null; // 0=Sun..6=Sat\n const monthVal = dateObj ? dateObj.getMonth() + 1 : null;\n const opex = isOpex(dates[i]);\n const ema21val = ema21[i];\n const sma50val = sma50[i];\n const priceVsEma21 =\n !isNaN(ema21val) && ema21val > 0\n ? ((closes[i] - ema21val) / ema21val) * 100\n : null;\n const priceVsSma50 =\n !isNaN(sma50val) && sma50val > 0\n ? ((closes[i] - sma50val) / sma50val) * 100\n : null;\n const rsi14val = rsi14[i];\n\n // Prior_Range_vs_ATR: prior day's (high - low) / ATR[i-1]\n // Known at market open (prior day range and ATR are available before trading begins).\n // First bar (i=0) has no prior day, so null. ATR[i-1] must be valid (non-NaN).\n let priorRangeVsATR: number | null = null;\n if (i > 0) {\n const priorATR = atrArr[i - 1];\n if (!isNaN(priorATR) && priorATR > 0) {\n priorRangeVsATR = (highs[i - 1] - lows[i - 1]) / priorATR;\n }\n }\n\n return {\n ticker,\n date: dates[i],\n Prior_Close: priorClose,\n Gap_Pct: gapPct,\n RSI_14: isNaN(rsi14val) ? null : rsi14val,\n ATR_Pct: atrPct,\n Price_vs_EMA21_Pct: priceVsEma21,\n Price_vs_SMA50_Pct: priceVsSma50,\n Realized_Vol_5D: isNaN(rvol5[i]) ? null : rvol5[i],\n Realized_Vol_20D: isNaN(rvol20[i]) ? null : rvol20[i],\n Return_5D: ret5d,\n Return_20D: ret20d,\n Intraday_Range_Pct: intradayRangePct,\n Intraday_Return_Pct: intradayReturnPct,\n Close_Position_In_Range: closePosInRange,\n Gap_Filled: gapFilled,\n Consecutive_Days: consecutiveDays[i],\n Prev_Return_Pct: priorReturn,\n Day_of_Week: dayOfWeek,\n Month: monthVal,\n Is_Opex: opex,\n Prior_Range_vs_ATR: priorRangeVsATR,\n };\n });\n\n // 8. Batch UPDATE via DuckDB VALUES CTE, batches of 500\n const BATCH_SIZE = 500;\n const columns = [\n \"Prior_Close\",\n \"Gap_Pct\",\n \"RSI_14\",\n \"ATR_Pct\",\n \"Price_vs_EMA21_Pct\",\n \"Price_vs_SMA50_Pct\",\n \"Realized_Vol_5D\",\n \"Realized_Vol_20D\",\n \"Return_5D\",\n \"Return_20D\",\n \"Intraday_Range_Pct\",\n \"Intraday_Return_Pct\",\n \"Close_Position_In_Range\",\n \"Gap_Filled\",\n \"Consecutive_Days\",\n \"Prev_Return_Pct\",\n \"Day_of_Week\",\n \"Month\",\n \"Is_Opex\",\n \"Prior_Range_vs_ATR\",\n ];\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, columns);\n }\n\n // 9. Run Tier 2 (VIX context enrichment)\n const tier2Result = await runTier2(conn);\n\n // 10. Tier 3 — intraday timing fields from market.intraday\n const tier3Result = await runTier3(conn, ticker, dates);\n\n // 11. Update enriched_through watermark\n const newWatermark = dates[dates.length - 1];\n await upsertMarketImportMetadata(conn, {\n source: \"enrichment\",\n ticker,\n target_table: \"daily\",\n max_date: null,\n enriched_through: newWatermark,\n synced_at: new Date(),\n });\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: columns.length },\n tier2: tier2Result,\n tier3: tier3Result,\n rowsEnriched: enrichedRows.length,\n enrichedThrough: newWatermark,\n };\n}\n\n// =============================================================================\n// Tier 3: Intraday Timing Fields\n// =============================================================================\n\n/**\n * Convert HH:MM time string to decimal hours (e.g., \"10:30\" → 10.5).\n */\nfunction hhmmToDecimalHours(time: string): number {\n const [h, m] = time.split(\":\").map(Number);\n return h + m / 60;\n}\n\n/**\n * Compute intraday timing fields from raw OHLCV bars for a single date.\n *\n * Pure function — no DB access. Exported for unit testing.\n *\n * Fields computed:\n * - highTime: Decimal hours when day high occurred (e.g., 10.5 = 10:30)\n * - lowTime: Decimal hours when day low occurred\n * - highBeforeLow: true if high occurred before low\n * - reversalType: +1 = morning high + afternoon low, -1 = morning low + afternoon high, 0 = trend day\n * - openingDriveStrength: (first 30-min range) / (full day range), 0-1 scale; 0 if day range is 0\n * - intradayRealizedVol: Annualized realized vol from intraday bar-to-bar log returns (decimal, not %)\n *\n * @param bars - Array of {time: \"HH:MM\", open, high, low, close} ordered by time (oldest first)\n * @returns Computed fields or null if bars is empty\n */\nexport function computeIntradayTimingFields(\n bars: Array<{ time: string; open: number; high: number; low: number; close: number }>\n): {\n highTime: number;\n lowTime: number;\n highBeforeLow: boolean;\n reversalType: number;\n openingDriveStrength: number;\n intradayRealizedVol: number;\n} | null {\n if (bars.length === 0) return null;\n\n let maxHigh = -Infinity;\n let minLow = Infinity;\n let highTimeStr = bars[0].time;\n let lowTimeStr = bars[0].time;\n\n for (const bar of bars) {\n if (bar.high > maxHigh) {\n maxHigh = bar.high;\n highTimeStr = bar.time;\n }\n if (bar.low < minLow) {\n minLow = bar.low;\n lowTimeStr = bar.time;\n }\n }\n\n const highTime = hhmmToDecimalHours(highTimeStr);\n const lowTime = hhmmToDecimalHours(lowTimeStr);\n const highBeforeLow = highTime < lowTime;\n\n // Reversal type: morning = before 12:00, afternoon = 12:00 or later\n const highInMorning = highTime < 12;\n const lowInMorning = lowTime < 12;\n const highInAfternoon = highTime >= 12;\n const lowInAfternoon = lowTime >= 12;\n\n let reversalType = 0;\n if (highInMorning && lowInAfternoon) reversalType = 1; // High morning, low afternoon\n else if (lowInMorning && highInAfternoon) reversalType = -1; // Low morning, high afternoon\n\n // Opening Drive Strength: ratio of first-30-min range to full-day range\n // First 30 min = bars with time < 10:00 (market opens 09:30)\n const openingBars = bars.filter(b => hhmmToDecimalHours(b.time) < 10);\n let openingDriveStrength = 0;\n const fullDayRange = maxHigh - minLow;\n if (openingBars.length > 0 && fullDayRange > 0) {\n const openHigh = Math.max(...openingBars.map(b => b.high));\n const openLow = Math.min(...openingBars.map(b => b.low));\n openingDriveStrength = (openHigh - openLow) / fullDayRange;\n }\n\n // Intraday Realized Vol: annualized from bar-to-bar close log returns\n // Uses sqrt(252 * barsPerDay) annualization\n let intradayRealizedVol = 0;\n if (bars.length >= 2) {\n const logReturns: number[] = [];\n for (let i = 1; i < bars.length; i++) {\n if (bars[i - 1].close > 0 && bars[i].close > 0) {\n logReturns.push(Math.log(bars[i].close / bars[i - 1].close));\n }\n }\n if (logReturns.length > 0) {\n const mean = logReturns.reduce((s, r) => s + r, 0) / logReturns.length;\n const variance = logReturns.reduce((s, r) => s + (r - mean) ** 2, 0) / logReturns.length;\n const barStdDev = Math.sqrt(variance);\n // Annualize: multiply by sqrt(barsPerDay * 252)\n // barsPerDay = number of bars we actually have (adapts to timeframe)\n intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);\n }\n }\n\n return { highTime, lowTime, highBeforeLow, reversalType, openingDriveStrength, intradayRealizedVol };\n}\n\n/** Run Tier 3: compute intraday timing fields from market.intraday and write to market.daily */\nasync function runTier3(\n conn: DuckDBConnection,\n ticker: string,\n dates: string[]\n): Promise<TierStatus> {\n // Check if intraday data exists for this ticker\n const hasData = await hasTier3Data(conn, ticker);\n if (!hasData) {\n return {\n status: \"skipped\",\n reason: \"no intraday data in market.intraday — import intraday bars to populate Tier 3 fields\",\n };\n }\n\n // Query intraday bars for all dates in the enrichment range\n const result = await conn.runAndReadAll(\n `SELECT date, time, open, high, low, close\n FROM market.intraday\n WHERE ticker = $1 AND date >= $2 AND date <= $3\n ORDER BY date, time`,\n [ticker, dates[0], dates[dates.length - 1]]\n );\n\n const rows = result.getRows();\n const columns = result.columnNames();\n const dateIdx = columns.indexOf(\"date\");\n const timeIdx = columns.indexOf(\"time\");\n const openIdx = columns.indexOf(\"open\");\n const highIdx = columns.indexOf(\"high\");\n const lowIdx = columns.indexOf(\"low\");\n const closeIdx = columns.indexOf(\"close\");\n\n // Group bars by date\n const barsByDate = new Map<string, Array<{ time: string; open: number; high: number; low: number; close: number }>>();\n for (const row of rows) {\n const dateStr = String(row[dateIdx]);\n const bar = {\n time: String(row[timeIdx]),\n open: Number(row[openIdx]),\n high: Number(row[highIdx]),\n low: Number(row[lowIdx]),\n close: Number(row[closeIdx]),\n };\n if (!barsByDate.has(dateStr)) barsByDate.set(dateStr, []);\n barsByDate.get(dateStr)!.push(bar);\n }\n\n if (barsByDate.size === 0) {\n return {\n status: \"skipped\",\n reason: \"intraday data exists but no bars overlap with enrichment date range\",\n };\n }\n\n // Compute timing fields for each date and batch update market.daily\n const tier3Cols = [\"High_Time\", \"Low_Time\", \"High_Before_Low\", \"Reversal_Type\", \"Opening_Drive_Strength\", \"Intraday_Realized_Vol\"];\n const enrichedRows: Array<Record<string, unknown>> = [];\n\n for (const [dateStr, bars] of barsByDate) {\n const timing = computeIntradayTimingFields(bars);\n if (!timing) continue;\n\n enrichedRows.push({\n ticker,\n date: dateStr,\n High_Time: timing.highTime,\n Low_Time: timing.lowTime,\n High_Before_Low: timing.highBeforeLow ? 1 : 0,\n Reversal_Type: timing.reversalType,\n Opening_Drive_Strength: timing.openingDriveStrength,\n Intraday_Realized_Vol: timing.intradayRealizedVol,\n });\n }\n\n // Batch update using the existing batchUpdateDaily helper\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, tier3Cols);\n }\n\n return { status: \"complete\", fieldsWritten: tier3Cols.length };\n}\n","/**\n * Market Data Importer\n *\n * Core ingestion logic for importing market data from CSV files and external DuckDB databases\n * into the normalized market schema tables (market.daily, market.context, market.intraday).\n *\n * Design principles:\n * - Validate column mapping BEFORE any writes (fail-clean semantics)\n * - ON CONFLICT DO NOTHING for idempotent merges\n * - ATTACH/DETACH lifecycle for external DBs\n * - normalizeTicker() called at import boundary before any DB write\n * - No withFullSync usage (DB-09: market writes must not be wrapped in analytics.duckdb transactions)\n * - No upsertMarketSyncMetadata (old schema) — only upsertMarketImportMetadata (Phase 60 schema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport { normalizeTicker } from \"./ticker.js\";\nimport { upsertMarketImportMetadata } from \"../sync/metadata.js\";\nimport { runEnrichment, runContextEnrichment } from \"./market-enricher.js\";\nimport { getProvider, type AssetClass } from \"./market-provider.js\";\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst REQUIRED_SCHEMA_FIELDS: Record<string, string[]> = {\n daily: [\"date\", \"open\", \"high\", \"low\", \"close\"],\n context: [\"date\"],\n intraday: [\"date\", \"time\", \"open\", \"high\", \"low\", \"close\"],\n _context_derived: [\"date\"], // Phase 75: cross-ticker derived fields\n};\n\n// PK conflict target per table\nconst CONFLICT_TARGETS: Record<string, string> = {\n daily: \"(ticker, date)\",\n context: \"(date)\",\n intraday: \"(ticker, date, time)\",\n _context_derived: \"(date)\", // Phase 75: PK is date only\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ImportMarketCsvOptions {\n filePath: string;\n ticker: string;\n targetTable: \"daily\" | \"context\" | \"intraday\";\n columnMapping: Record<string, string>; // { csvColumn: schemaColumn }\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\nexport interface ImportFromDatabaseOptions {\n dbPath: string;\n query: string;\n ticker: string;\n targetTable: \"daily\" | \"context\" | \"intraday\";\n columnMapping: Record<string, string>; // { queryColumn: schemaColumn }\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\nexport interface ImportResult {\n rowsInserted: number;\n rowsUpdated: number;\n rowsSkipped: number;\n inputRowCount: number;\n dateRange: { min: string; max: string } | null;\n enrichment: { status: \"pending\" | \"complete\" | \"skipped\" | \"error\"; message: string };\n}\n\n// =============================================================================\n// Column Mapping Validation\n// =============================================================================\n\n/**\n * Validate that the column mapping covers all required schema fields for the target table.\n *\n * Special case for intraday: if `date` is mapped but `time` is not, the `time` field\n * will be auto-derived from the Unix timestamp in the date source column. In this case,\n * the missing `time` mapping is allowed — validation passes without it.\n *\n * @param columnMapping - Mapping from source columns to schema columns\n * @param targetTable - The target market table\n * @returns Validation result with missing field names\n */\nexport function validateColumnMapping(\n columnMapping: Record<string, string>,\n targetTable: \"daily\" | \"context\" | \"intraday\"\n): { valid: boolean; missingFields: string[] } {\n const schemaValues = Object.values(columnMapping);\n const required = REQUIRED_SCHEMA_FIELDS[targetTable] ?? [];\n let missing = required.filter((field) => !schemaValues.includes(field));\n\n // For intraday: allow missing `time` if `date` is mapped — time will be auto-derived\n // from the Unix timestamp in the date source column during applyColumnMapping.\n if (targetTable === \"intraday\" && missing.includes(\"time\") && schemaValues.includes(\"date\")) {\n missing = missing.filter((f) => f !== \"time\");\n }\n\n return { valid: missing.length === 0, missingFields: missing };\n}\n\n// =============================================================================\n// Private CSV Parsing Helpers\n// =============================================================================\n\n/**\n * Parse CSV content into rows with header mapping.\n * Strips UTF-8 BOM if present. Handles \\r\\n line endings.\n */\nfunction parseCSV(content: string): { headers: string[]; rows: Record<string, string>[] } {\n const lines = content.replace(/^\\uFEFF/, \"\").trim().split(\"\\n\");\n if (lines.length < 2) {\n return { headers: [], rows: [] };\n }\n\n const headers = lines[0].trim().split(\",\").map((h) => h.trim());\n const rows: Record<string, string>[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) continue;\n const values = line.split(\",\");\n const row: Record<string, string> = {};\n headers.forEach((h, idx) => {\n row[h] = values[idx]?.trim() ?? \"\";\n });\n rows.push(row);\n }\n\n return { headers, rows };\n}\n\n/**\n * Parse a date value flexibly:\n * - If numeric AND > 1e8: treat as Unix timestamp (seconds), convert to ET date\n * - If YYYY-MM-DD string: return as-is\n * - Otherwise: return null\n */\nfunction parseFlexibleDate(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n // Unix timestamp in seconds — convert to Eastern Time date\n return new Date(numeric * 1000).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n }\n\n // Check YYYY-MM-DD format\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return value;\n }\n\n return null;\n}\n\n/**\n * Extract HH:MM time from a value in Eastern Time.\n * - If numeric AND > 1e8: treat as Unix timestamp (seconds), extract HH:MM ET\n * - If already HH:MM: return as-is\n * - If HHMM (4 digits): convert to HH:MM\n * - Otherwise: return null\n */\nfunction parseFlexibleTime(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n // Unix timestamp in seconds — convert to Eastern Time HH:MM\n const d = new Date(numeric * 1000);\n return d.toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n }\n // Already in HH:MM format\n if (/^\\d{2}:\\d{2}$/.test(value)) {\n return value;\n }\n // HHMM format (4 digits), e.g. \"0930\"\n if (/^\\d{4}$/.test(value)) {\n return `${value.slice(0, 2)}:${value.slice(2)}`;\n }\n return null;\n}\n\n// =============================================================================\n// Private Column Mapping Helper\n// =============================================================================\n\n/**\n * Apply column mapping to raw rows, performing date parsing and numeric coercion.\n *\n * @param rows - Raw rows from CSV or query result\n * @param columnMapping - { sourceCol: schemaCol } mapping\n * @param ticker - Ticker to inject for daily/intraday tables\n * @param targetTable - Target table (controls whether ticker is injected)\n * @returns Mapped rows with schema column names, invalid date rows dropped\n */\nfunction applyColumnMapping(\n rows: Record<string, string>[],\n columnMapping: Record<string, string>,\n ticker: string,\n targetTable: \"daily\" | \"context\" | \"intraday\"\n): Array<Record<string, unknown>> {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const result: Array<Record<string, unknown>> = [];\n\n for (const row of rows) {\n const mapped: Record<string, unknown> = {};\n let hasNullDate = false;\n\n for (const [sourceCol, schemaCol] of Object.entries(columnMapping)) {\n const rawValue = row[sourceCol] ?? \"\";\n\n if (schemaCol === \"date\") {\n const parsedDate = parseFlexibleDate(rawValue);\n if (parsedDate === null) {\n // Log a warning and skip this row\n console.warn(`[market-importer] Skipping row with unparseable date value: \"${rawValue}\"`);\n hasNullDate = true;\n break;\n }\n mapped[schemaCol] = parsedDate;\n } else if (schemaCol === \"time\") {\n const parsedTime = parseFlexibleTime(rawValue);\n if (parsedTime === null) {\n console.warn(`[market-importer] Skipping row with unparseable time value: \"${rawValue}\"`);\n hasNullDate = true; // Reuse the skip mechanism\n break;\n }\n mapped[schemaCol] = parsedTime;\n } else {\n // Numeric coercion for non-date, non-ticker fields\n if (rawValue === \"\" || rawValue === \"NaN\" || rawValue === \"NA\") {\n mapped[schemaCol] = null;\n } else {\n const numVal = parseFloat(rawValue);\n mapped[schemaCol] = isNaN(numVal) ? rawValue : numVal;\n }\n }\n }\n\n if (hasNullDate) continue;\n if (!(\"date\" in mapped)) continue;\n\n // Auto-extract time from the date source column's Unix timestamp for intraday imports.\n // This allows users to map a single Unix timestamp column to \"date\" and get \"time\"\n // (HH:MM ET) auto-populated — no need to specify a separate time column in the mapping.\n if (targetTable === \"intraday\" && !(\"time\" in mapped)) {\n const dateSourceCol = Object.entries(columnMapping).find(([, schema]) => schema === \"date\")?.[0];\n if (dateSourceCol) {\n const rawDateValue = row[dateSourceCol] ?? \"\";\n const numericDate = Number(rawDateValue);\n if (!isNaN(numericDate) && numericDate > 1e8) {\n const parsedTime = parseFlexibleTime(rawDateValue);\n if (parsedTime) {\n mapped[\"time\"] = parsedTime;\n }\n }\n }\n }\n\n // Inject ticker for daily and intraday tables (context PK is date-only)\n if (targetTable === \"daily\" || targetTable === \"intraday\") {\n mapped[\"ticker\"] = normalizedTicker;\n }\n\n result.push(mapped);\n }\n\n return result;\n}\n\n// =============================================================================\n// Private Insert Helper\n// =============================================================================\n\n/**\n * Insert mapped rows into the target market table using ON CONFLICT DO NOTHING.\n * Batches inserts in groups of 500.\n *\n * @returns Count of inserted and skipped rows\n */\nasync function insertMappedRows(\n conn: DuckDBConnection,\n targetTable: \"daily\" | \"context\" | \"intraday\",\n mappedRows: Array<Record<string, unknown>>\n): Promise<{ inserted: number; updated: number; skipped: number }> {\n if (mappedRows.length === 0) {\n return { inserted: 0, updated: 0, skipped: 0 };\n }\n\n const tableName = `market.${targetTable}`;\n const conflictTarget = CONFLICT_TARGETS[targetTable];\n\n // Collect all distinct schema column names across all rows\n const columnSet = new Set<string>();\n for (const row of mappedRows) {\n for (const key of Object.keys(row)) {\n columnSet.add(key);\n }\n }\n const columns = Array.from(columnSet);\n\n // COUNT(*) before insert\n const beforeResult = await conn.runAndReadAll(`SELECT COUNT(*) FROM ${tableName}`);\n const beforeCount = Number(beforeResult.getRows()[0][0]);\n\n // Build ON CONFLICT clause: merge non-key columns into existing rows\n // (e.g., importing VIX9D_Close into a context row that already has VIX_Close)\n const conflictKeys = new Set(\n CONFLICT_TARGETS[targetTable].replace(/[()]/g, \"\").split(\",\").map((s: string) => s.trim())\n );\n const updateCols = columns.filter((c) => !conflictKeys.has(c));\n const conflictAction =\n updateCols.length > 0\n ? `DO UPDATE SET ${updateCols.map((c) => `${c} = EXCLUDED.${c}`).join(\", \")}`\n : \"DO NOTHING\";\n\n const columnList = columns.join(\", \");\n const BATCH_SIZE = 500;\n for (let i = 0; i < mappedRows.length; i += BATCH_SIZE) {\n const batch = mappedRows.slice(i, i + BATCH_SIZE);\n const values: unknown[] = [];\n const valuePlaceholders: string[] = [];\n\n for (const row of batch) {\n const rowPlaceholders: string[] = [];\n for (const col of columns) {\n values.push(row[col] ?? null);\n rowPlaceholders.push(`$${values.length}`);\n }\n valuePlaceholders.push(`(${rowPlaceholders.join(\", \")})`);\n }\n\n const sql =\n `INSERT INTO ${tableName} (${columnList}) VALUES ${valuePlaceholders.join(\", \")} ` +\n `ON CONFLICT ${conflictTarget} ${conflictAction}`;\n\n await conn.run(sql, values as (string | number | boolean | null | bigint)[]);\n }\n\n // COUNT(*) after insert — net new rows\n const afterResult = await conn.runAndReadAll(`SELECT COUNT(*) FROM ${tableName}`);\n const afterCount = Number(afterResult.getRows()[0][0]);\n\n const inserted = afterCount - beforeCount;\n // With DO UPDATE, non-inserted rows are updated (not skipped).\n // With DO NOTHING (updateCols empty), non-inserted rows are skipped.\n const updated = updateCols.length > 0 ? mappedRows.length - inserted : 0;\n const skipped = updateCols.length > 0 ? 0 : mappedRows.length - inserted;\n return { inserted, updated, skipped };\n}\n\n// =============================================================================\n// Private Date Range Helper\n// =============================================================================\n\n/**\n * Compute min/max date range from mapped rows.\n */\nfunction computeDateRange(\n rows: Array<Record<string, unknown>>\n): { min: string; max: string } | null {\n const dates: string[] = [];\n for (const row of rows) {\n const d = row[\"date\"];\n if (typeof d === \"string\" && d) {\n dates.push(d);\n }\n }\n if (dates.length === 0) return null;\n dates.sort();\n return { min: dates[0], max: dates[dates.length - 1] };\n}\n\n// =============================================================================\n// Enrichment Trigger\n// =============================================================================\n\n/**\n * Trigger market data enrichment after an import.\n *\n * Calls runEnrichment() for daily table imports. Context and intraday imports\n * are source data, not enrichment targets, so they are skipped here.\n *\n * Returns \"complete\", \"skipped\", or \"error\" — never \"pending\".\n */\nexport async function triggerEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n targetTable: string,\n _dateRange: { min: string; max: string } | null,\n skipEnrichment: boolean\n): Promise<{ status: \"pending\" | \"complete\" | \"skipped\" | \"error\"; message: string }> {\n // Only enrich daily table imports — context and intraday are source data, not enrichment targets\n if (skipEnrichment) {\n return {\n status: \"skipped\",\n message: \"skip_enrichment=true; call enrich_market_data to populate computed fields.\",\n };\n }\n if (targetTable !== \"daily\") {\n return {\n status: \"skipped\",\n message: `Enrichment only runs for daily table imports; skipping for ${targetTable}.`,\n };\n }\n\n try {\n const result = await runEnrichment(conn, ticker, {});\n const summaryParts = [\n `Tier 1: ${result.tier1.status}${result.tier1.fieldsWritten !== undefined ? ` (${result.tier1.fieldsWritten} fields)` : \"\"}${result.tier1.reason ? ` — ${result.tier1.reason}` : \"\"}`,\n `Tier 2: ${result.tier2.status}${result.tier2.fieldsWritten !== undefined ? ` (${result.tier2.fieldsWritten} fields)` : \"\"}${result.tier2.reason ? ` — ${result.tier2.reason}` : \"\"}`,\n `Tier 3: ${result.tier3.status}${result.tier3.reason ? ` — ${result.tier3.reason}` : \"\"}`,\n ];\n return {\n status: \"complete\",\n message: `Enriched ${result.rowsEnriched} rows for ${ticker} through ${result.enrichedThrough ?? \"N/A\"}. ${summaryParts.join(\"; \")}`,\n };\n } catch (err) {\n return {\n status: \"error\",\n message: `Enrichment failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\n// =============================================================================\n// importMarketCsvFile\n// =============================================================================\n\n/**\n * Import market data from a CSV file into the target market table.\n *\n * Steps:\n * 1. Validate column mapping (fail-clean before any writes)\n * 2. Read CSV file\n * 3. Parse and map columns\n * 4. Insert with ON CONFLICT DO NOTHING (or return preview if dry_run=true)\n * 5. Upsert import metadata\n * 6. Trigger enrichment stub\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param options - Import options including file path, ticker, and column mapping\n * @returns Import result with row counts, date range, and enrichment status\n */\nexport async function importMarketCsvFile(\n conn: DuckDBConnection,\n options: ImportMarketCsvOptions\n): Promise<ImportResult> {\n const { filePath, ticker, targetTable, columnMapping, dryRun = false, skipEnrichment = false } =\n options;\n\n // 1. Validate column mapping first\n const validation = validateColumnMapping(columnMapping, targetTable);\n if (!validation.valid) {\n throw new Error(\n `Column mapping missing required fields for market.${targetTable}: ${validation.missingFields.join(\", \")}`\n );\n }\n\n // 2. Read file\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read CSV file at \"${filePath}\": ${msg}`);\n }\n\n // 3. Parse CSV\n const { rows } = parseCSV(content);\n if (rows.length === 0) {\n throw new Error(`CSV file \"${filePath}\" has no data rows`);\n }\n\n // 4. Map columns\n const mappedRows = applyColumnMapping(rows, columnMapping, ticker, targetTable);\n if (mappedRows.length === 0) {\n throw new Error(\n `After applying column mapping, 0 valid rows remain from CSV file \"${filePath}\"`\n );\n }\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const dateRange = computeDateRange(mappedRows);\n\n // 5. Dry run: return preview without writing\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n // 6. Insert rows\n const { inserted, updated, skipped } = await insertMappedRows(conn, targetTable, mappedRows);\n\n // 7. Upsert import metadata\n await upsertMarketImportMetadata(conn, {\n source: `import_market_csv:${filePath}`,\n ticker: normalizedTicker,\n target_table: targetTable,\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n // 8. Trigger enrichment\n const enrichment = await triggerEnrichment(conn, normalizedTicker, targetTable, dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n}\n\n// =============================================================================\n// importFromDatabase\n// =============================================================================\n\n/**\n * Import market data from an external DuckDB database into the target market table.\n *\n * ATTACHes the external database as READ_ONLY, queries it, maps columns, and inserts\n * into market tables. DETACHes the external DB in a finally block regardless of outcome.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param options - Import options including DB path, query, ticker, and column mapping\n * @returns Import result with row counts, date range, and enrichment status\n */\nexport async function importFromDatabase(\n conn: DuckDBConnection,\n options: ImportFromDatabaseOptions\n): Promise<ImportResult> {\n const { dbPath, query, ticker, targetTable, columnMapping, dryRun = false, skipEnrichment = false } =\n options;\n\n // 1. Validate column mapping first\n const validation = validateColumnMapping(columnMapping, targetTable);\n if (!validation.valid) {\n throw new Error(\n `Column mapping missing required fields for market.${targetTable}: ${validation.missingFields.join(\", \")}`\n );\n }\n\n const EXT_ALIAS = \"ext_import_source\";\n\n // 2. Attach external DB as READ_ONLY\n await conn.run(`ATTACH '${dbPath}' AS ${EXT_ALIAS} (READ_ONLY)`);\n\n try {\n // 3. Execute query against external DB\n const rawResult = await conn.runAndReadAll(query);\n\n // 4. Convert result to Record<string, string>[]\n // Use columnNames() for column name strings — getColumns() returns column data arrays,\n // not column descriptor objects with .name properties.\n const resultColumnNames = rawResult.columnNames();\n const resultRows = rawResult.getRows();\n\n const rows: Record<string, string>[] = resultRows.map((row) => {\n const obj: Record<string, string> = {};\n resultColumnNames.forEach((colName, idx) => {\n const val = row[idx];\n obj[colName] = val === null || val === undefined ? \"\" : String(val);\n });\n return obj;\n });\n\n // 5. Map columns\n const mappedRows = applyColumnMapping(rows, columnMapping, ticker, targetTable);\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const dateRange = computeDateRange(mappedRows);\n\n // 6. Dry run: return preview without writing\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n // 7. Insert rows\n const { inserted, updated, skipped } = await insertMappedRows(conn, targetTable, mappedRows);\n\n // 8. Upsert import metadata\n await upsertMarketImportMetadata(conn, {\n source: `import_from_database:${dbPath}`,\n ticker: normalizedTicker,\n target_table: targetTable,\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n // 9. Trigger enrichment\n const enrichment = await triggerEnrichment(conn, normalizedTicker, targetTable, dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n } finally {\n // Always detach external DB regardless of success or failure\n try {\n await conn.run(`DETACH ${EXT_ALIAS}`);\n } catch {\n // Non-fatal: detach failure should not mask the original error\n }\n }\n}\n\n// =============================================================================\n// importFromApi\n// =============================================================================\n\n/**\n * Known index tickers that need the I: prefix when calling the Massive API.\n * Used for auto-detection of asset class when not provided.\n */\nconst INDEX_TICKERS = new Set([\n \"VIX\", \"VIX9D\", \"VIX3M\", \"SPX\", \"NDX\", \"RUT\", \"DJX\", \"VXN\", \"OVX\", \"GVZ\",\n]);\n\n/**\n * Regex for OCC option ticker format: letters + 6-digit date + C/P + 8-digit strike.\n * E.g., \"SPX251219C05000000\" or \"AAPL240119P00150000\"\n */\nconst OCC_TICKER_REGEX = /^[A-Z]+\\d{6}[CP]\\d{8}$/;\n\n/**\n * Auto-detect Massive asset class from ticker symbol.\n * - Known indices (VIX, SPX, NDX, etc.) → \"index\"\n * - OCC option format → \"option\"\n * - Otherwise → \"stock\"\n */\nfunction detectAssetClass(ticker: string): AssetClass {\n if (INDEX_TICKERS.has(ticker.toUpperCase())) return \"index\";\n if (OCC_TICKER_REGEX.test(ticker.toUpperCase())) return \"option\";\n return \"stock\";\n}\n\nexport interface ImportFromApiOptions {\n ticker: string;\n from: string; // YYYY-MM-DD\n to: string; // YYYY-MM-DD\n targetTable: \"daily\" | \"context\" | \"intraday\";\n timespan?: \"minute\" | \"hour\"; // only for intraday, default \"minute\"\n multiplier?: number; // only for intraday, default 1\n assetClass?: AssetClass; // default: auto-detect from ticker/targetTable\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\n/**\n * Import market data from the Massive.com REST API into DuckDB.\n *\n * Supports three import modes via `targetTable`:\n *\n * **daily** — Fetches OHLCV bars for any stock/index/option ticker and upserts\n * into market.daily. Auto-triggers enrichment (Tier 1+2+3) after insert unless\n * skipEnrichment=true.\n *\n * **context** — Ignores the `ticker` parameter. Makes three parallel fetchBars calls\n * for VIX, VIX9D, and VIX3M, merges results by date, and upserts into market.context.\n * Trigger Tier 2 context enrichment after insert unless skipEnrichment=true.\n *\n * **intraday** — Fetches minute or hour bars. Requires time field from Massive API\n * (populated by fetchBars when timespan != \"day\"). Strips volume before insert since\n * market.intraday schema does not include a volume column.\n *\n * Requires MASSIVE_API_KEY environment variable. Upserts on conflict — safe to\n * re-import overlapping date ranges.\n */\nexport async function importFromApi(\n conn: DuckDBConnection,\n options: ImportFromApiOptions\n): Promise<ImportResult> {\n const {\n ticker,\n from,\n to,\n targetTable,\n timespan = \"minute\",\n multiplier = 1,\n assetClass,\n dryRun = false,\n skipEnrichment = false,\n } = options;\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n\n if (targetTable === \"daily\") {\n // --- Daily import: single fetchBars call, insert directly ---\n const resolvedClass = assetClass ?? detectAssetClass(normalizedTicker);\n const rows = await getProvider().fetchBars({\n ticker: normalizedTicker,\n from,\n to,\n timespan: \"day\",\n multiplier: 1,\n assetClass: resolvedClass,\n });\n\n // MassiveBarRow fields mapped to market.daily schema columns.\n // Volume is intentionally omitted — market.daily schema has no volume column.\n const mappedRows: Array<Record<string, unknown>> = rows.map((row) => ({\n date: row.date,\n open: row.open,\n high: row.high,\n low: row.low,\n close: row.close,\n ticker: row.ticker,\n }));\n\n const dateRange = computeDateRange(mappedRows);\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"daily\", mappedRows);\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:daily:${normalizedTicker}`,\n ticker: normalizedTicker,\n target_table: \"daily\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n const enrichment = await triggerEnrichment(conn, normalizedTicker, \"daily\", dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n }\n\n if (targetTable === \"context\") {\n // --- Context convenience import: import VIX + VIX9D + VIX3M into market.daily (per D-12) ---\n const contextTickers = [\"VIX\", \"VIX9D\", \"VIX3M\"];\n let totalInserted = 0;\n let totalUpdated = 0;\n let totalSkipped = 0;\n let totalInput = 0;\n let combinedDateRange: { min: string; max: string } | null = null;\n\n for (const ctxTicker of contextTickers) {\n const bars = await getProvider().fetchBars({ ticker: ctxTicker, from, to, timespan: \"day\", assetClass: \"index\" });\n const mappedRows = bars.map(bar => ({\n ticker: ctxTicker,\n date: bar.date,\n open: bar.open,\n high: bar.high,\n low: bar.low,\n close: bar.close,\n }));\n\n totalInput += mappedRows.length;\n\n if (mappedRows.length === 0) continue;\n\n const dateRange = computeDateRange(mappedRows);\n if (dateRange) {\n if (!combinedDateRange) {\n combinedDateRange = { ...dateRange };\n } else {\n if (dateRange.min < combinedDateRange.min) combinedDateRange.min = dateRange.min;\n if (dateRange.max > combinedDateRange.max) combinedDateRange.max = dateRange.max;\n }\n }\n\n if (!dryRun) {\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"daily\", mappedRows);\n totalInserted += inserted;\n totalUpdated += updated;\n totalSkipped += skipped;\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:daily:${ctxTicker}`,\n ticker: ctxTicker,\n target_table: \"daily\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n }\n }\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: totalInput,\n dateRange: combinedDateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${totalInput} rows for ${contextTickers.join(\", \")} into market.daily.`,\n },\n };\n }\n\n // Trigger enrichment (Tier 2) to compute IVR/IVP + derived fields\n let enrichment: ImportResult[\"enrichment\"];\n if (skipEnrichment) {\n enrichment = {\n status: \"skipped\",\n message: \"skip_enrichment=true; call enrich_market_data to populate computed fields.\",\n };\n } else {\n try {\n const tier2Result = await runContextEnrichment(conn);\n enrichment = {\n status: tier2Result.status === \"complete\" || tier2Result.status === \"skipped\"\n ? \"complete\" as const\n : \"error\" as const,\n message: tier2Result.reason ?? `Tier 2 enrichment: ${tier2Result.fieldsWritten ?? 0} fields`,\n };\n } catch (e) {\n enrichment = {\n status: \"error\" as const,\n message: `Tier 2 enrichment failed: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n }\n\n return {\n rowsInserted: totalInserted,\n rowsUpdated: totalUpdated,\n rowsSkipped: totalSkipped,\n inputRowCount: totalInput,\n dateRange: combinedDateRange,\n enrichment,\n };\n }\n\n // targetTable === \"intraday\"\n // --- Intraday import: fetch minute/hour bars with time field ---\n const resolvedClass = assetClass ?? detectAssetClass(normalizedTicker);\n const rows = await getProvider().fetchBars({\n ticker: normalizedTicker,\n from,\n to,\n timespan: timespan,\n multiplier: multiplier,\n assetClass: resolvedClass,\n });\n\n // Strip volume — market.intraday schema does not include volume column.\n // Also strip time=undefined rows (safety guard — all intraday bars should have time).\n const mappedRows: Array<Record<string, unknown>> = rows\n .filter((row) => row.time !== undefined)\n .map((row) => ({\n ticker: row.ticker,\n date: row.date,\n time: row.time as string,\n open: row.open,\n high: row.high,\n low: row.low,\n close: row.close,\n // volume intentionally omitted — not in intraday schema\n }));\n\n const dateRange = computeDateRange(mappedRows);\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} intraday rows.`,\n },\n };\n }\n\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"intraday\", mappedRows);\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:intraday:${normalizedTicker}`,\n ticker: normalizedTicker,\n target_table: \"intraday\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: \"Enrichment only runs for daily and context table imports; skipping for intraday.\",\n },\n };\n}\n","/**\n * Massive.com (Polygon.io) Market Data Provider\n *\n * Implements MarketDataProvider for the Massive.com REST API.\n * Combines the former massive-client.ts (OHLCV bars) and massive-snapshot.ts\n * (option chain snapshots) into a single provider adapter.\n *\n * Key design decisions:\n * - D-01/D-02/D-03: API key read at call site via process.env.MASSIVE_API_KEY\n * - D-05: Pagination loop guard with seen-cursor Set + MAX_PAGES=500 safety net\n * - D-06: adjusted=false and limit=50000 in all aggregate API calls\n * - D-07: 429 retry with Retry-After header or exponential backoff\n * - D-09/D-10/D-11: Ticker prefixes (I: for indices, O: for options)\n * - Timestamps are Unix milliseconds from the Massive aggregates API\n */\n\nimport { z } from \"zod\";\nimport type {\n MarketDataProvider,\n BarRow,\n FetchBarsOptions,\n FetchSnapshotOptions,\n FetchSnapshotResult,\n OptionContract,\n AssetClass,\n} from \"../market-provider.js\";\nimport { computeLegGreeks } from \"../black-scholes.js\";\n\n// ===========================================================================\n// Zod Schemas — Aggregates (OHLCV Bars)\n// ===========================================================================\n\nexport const MassiveBarSchema = z.object({\n v: z.number().optional(),\n vw: z.number().optional(),\n o: z.number(),\n c: z.number(),\n h: z.number(),\n l: z.number(),\n t: z.number(),\n n: z.number().optional(),\n});\n\nexport type MassiveBar = z.infer<typeof MassiveBarSchema>;\n\nexport const MassiveAggregateResponseSchema = z.object({\n ticker: z.string(),\n queryCount: z.number(),\n resultsCount: z.number().optional(),\n adjusted: z.boolean().optional(),\n results: z.array(MassiveBarSchema).default([]),\n status: z.string(),\n request_id: z.string(),\n next_url: z.string().optional(),\n});\n\nexport type MassiveAggregateResponse = z.infer<typeof MassiveAggregateResponseSchema>;\n\n// ===========================================================================\n// Zod Schemas — Quotes (Historical Bid/Ask)\n// ===========================================================================\n\nexport const MassiveQuoteSchema = z.object({\n bid_price: z.number(),\n ask_price: z.number(),\n sip_timestamp: z.number(), // nanoseconds\n bid_size: z.number(),\n ask_size: z.number(),\n sequence_number: z.number(),\n});\n\nexport type MassiveQuote = z.infer<typeof MassiveQuoteSchema>;\n\nexport const MassiveQuotesResponseSchema = z.object({\n status: z.string(),\n request_id: z.string(),\n results: z.array(MassiveQuoteSchema).default([]),\n next_url: z.string().optional(),\n});\n\nexport type MassiveQuotesResponse = z.infer<typeof MassiveQuotesResponseSchema>;\n\n// ===========================================================================\n// Zod Schemas — Snapshot (Option Chain)\n// ===========================================================================\n\nexport const MassiveSnapshotGreeksSchema = z.object({\n delta: z.number(),\n gamma: z.number(),\n theta: z.number(),\n vega: z.number(),\n});\n\nexport const MassiveSnapshotDaySchema = z.object({\n open: z.number(),\n high: z.number(),\n low: z.number(),\n close: z.number(),\n change: z.number(),\n change_percent: z.number(),\n volume: z.number().optional(),\n vwap: z.number().optional(),\n previous_close: z.number(),\n last_updated: z.number(),\n});\n\nexport const MassiveSnapshotQuoteSchema = z.object({\n bid: z.number(),\n ask: z.number(),\n midpoint: z.number(),\n bid_size: z.number(),\n ask_size: z.number(),\n last_updated: z.number(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotTradeSchema = z.object({\n price: z.number(),\n size: z.number(),\n sip_timestamp: z.number(),\n conditions: z.array(z.number()).optional(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotDetailsSchema = z.object({\n ticker: z.string(),\n contract_type: z.string(),\n strike_price: z.number(),\n expiration_date: z.string(),\n exercise_style: z.string(),\n shares_per_contract: z.number(),\n});\n\nexport const MassiveSnapshotUnderlyingSchema = z.object({\n ticker: z.string(),\n price: z.number(),\n change_to_break_even: z.number(),\n last_updated: z.number(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotContractSchema = z.object({\n break_even_price: z.number(),\n implied_volatility: z.number(),\n open_interest: z.number(),\n greeks: MassiveSnapshotGreeksSchema.optional(),\n day: MassiveSnapshotDaySchema,\n last_quote: MassiveSnapshotQuoteSchema,\n last_trade: MassiveSnapshotTradeSchema.optional(),\n details: MassiveSnapshotDetailsSchema,\n underlying_asset: MassiveSnapshotUnderlyingSchema,\n});\n\nexport const MassiveSnapshotResponseSchema = z.object({\n request_id: z.string(),\n status: z.string(),\n results: z.array(MassiveSnapshotContractSchema),\n next_url: z.string().optional(),\n});\n\n// ===========================================================================\n// Constants\n// ===========================================================================\n\nexport const MASSIVE_BASE_URL = \"https://api.massive.com\";\nexport const MASSIVE_MAX_LIMIT = 50000;\nexport const MASSIVE_MAX_PAGES = 500;\n\n// ===========================================================================\n// Ticker Normalization\n// ===========================================================================\n\nexport function toMassiveTicker(ticker: string, assetClass: AssetClass): string {\n if (assetClass === \"index\") return ticker.startsWith(\"I:\") ? ticker : `I:${ticker}`;\n if (assetClass === \"option\") return ticker.startsWith(\"O:\") ? ticker : `O:${ticker}`;\n return ticker;\n}\n\nexport function fromMassiveTicker(apiTicker: string): string {\n return apiTicker.replace(/^[IO]:/, \"\");\n}\n\n// ===========================================================================\n// Timestamp Conversion\n// ===========================================================================\n\nexport function massiveTimestampToETDate(unixMs: number): string {\n return new Date(unixMs).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n });\n}\n\nexport function massiveTimestampToETTime(unixMs: number): string {\n return new Date(unixMs).toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n\n/**\n * Converts a nanosecond sip_timestamp to \"YYYY-MM-DD HH:MM\" ET minute key.\n * Used for matching quotes to intraday bars by minute bucket.\n */\nexport function nanosToETMinuteKey(nanosTimestamp: number): string {\n const ms = Math.floor(nanosTimestamp / 1_000_000);\n const date = massiveTimestampToETDate(ms);\n const time = massiveTimestampToETTime(ms);\n return `${date} ${time}`;\n}\n\n// ===========================================================================\n// Internal Helpers\n// ===========================================================================\n\nfunction getApiKey(): string {\n const key = process.env.MASSIVE_API_KEY;\n if (!key) {\n throw new Error(\n \"Set MASSIVE_API_KEY environment variable to use Massive.com data import\"\n );\n }\n return key;\n}\n\nasync function fetchWithRetry(\n url: string,\n headers: Record<string, string>,\n maxRetries = 2\n): Promise<Response> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (response.status === 429) {\n if (attempt === maxRetries) {\n throw new Error(\n \"Massive.com rate limit exceeded — try again in a few minutes\"\n );\n }\n const retryAfter = response.headers.get(\"Retry-After\");\n const backoffMs = retryAfter\n ? parseInt(retryAfter, 10) * 1000\n : Math.pow(2, attempt + 1) * 1000;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n continue;\n }\n\n return response;\n }\n throw new Error(\"Massive.com rate limit exceeded after retries\");\n}\n\n// ===========================================================================\n// Snapshot Helpers\n// ===========================================================================\n\nconst INDEX_TICKERS = new Set([\n \"SPX\", \"NDX\", \"RUT\", \"DJX\", \"VIX\", \"VIX9D\", \"VIX3M\", \"OEX\", \"XSP\",\n]);\n\nfunction detectSnapshotAssetClass(ticker: string): AssetClass {\n return INDEX_TICKERS.has(ticker.toUpperCase()) ? \"index\" : \"stock\";\n}\n\nfunction computeDTE(expirationDate: string): number {\n const expMatch = expirationDate.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!expMatch) return 0;\n const [, expYearS, expMonthS, expDayS] = expMatch;\n const expYear = parseInt(expYearS, 10);\n const expMonth = parseInt(expMonthS, 10);\n const expDay = parseInt(expDayS, 10);\n\n const todayET = new Date().toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n });\n const todayMatch = todayET.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!todayMatch) return 0;\n const [, todayYearS, todayMonthS, todayDayS] = todayMatch;\n const todayYear = parseInt(todayYearS, 10);\n const todayMonth = parseInt(todayMonthS, 10);\n const todayDay = parseInt(todayDayS, 10);\n\n const dte =\n (Date.UTC(expYear, expMonth - 1, expDay) -\n Date.UTC(todayYear, todayMonth - 1, todayDay)) /\n 86_400_000;\n\n return dte <= 0 ? 0.001 : dte;\n}\n\nfunction mapContract(\n contract: z.infer<typeof MassiveSnapshotContractSchema>,\n): OptionContract {\n const hasApiGreeks =\n contract.greeks != null && contract.greeks.delta != null;\n\n let delta: number | null = null;\n let gamma: number | null = null;\n let theta: number | null = null;\n let vega: number | null = null;\n let iv: number | null = null;\n let greeksSource: \"massive\" | \"computed\" = \"computed\";\n\n if (hasApiGreeks) {\n delta = contract.greeks!.delta;\n gamma = contract.greeks!.gamma;\n theta = contract.greeks!.theta;\n vega = contract.greeks!.vega;\n iv = contract.implied_volatility;\n greeksSource = \"massive\";\n } else {\n const optionPrice =\n contract.last_trade?.price ?? contract.last_quote.midpoint;\n const underlyingPrice = contract.underlying_asset.price;\n const strike = contract.details.strike_price;\n const dte = computeDTE(contract.details.expiration_date);\n const type = contract.details.contract_type === \"call\" ? \"C\" : \"P\";\n const riskFreeRate = 0.045;\n const dividendYield = 0.015;\n\n const result = computeLegGreeks(\n optionPrice,\n underlyingPrice,\n strike,\n dte,\n type as \"C\" | \"P\",\n riskFreeRate,\n dividendYield,\n );\n\n if (result.iv !== null) {\n delta = result.delta;\n gamma = result.gamma;\n theta = result.theta;\n vega = result.vega;\n iv = result.iv;\n }\n greeksSource = \"computed\";\n }\n\n return {\n ticker: fromMassiveTicker(contract.details.ticker),\n underlying_ticker: fromMassiveTicker(contract.underlying_asset.ticker),\n underlying_price: contract.underlying_asset.price,\n contract_type: contract.details.contract_type as \"call\" | \"put\",\n strike: contract.details.strike_price,\n expiration: contract.details.expiration_date,\n exercise_style: contract.details.exercise_style,\n delta,\n gamma,\n theta,\n vega,\n iv,\n greeks_source: greeksSource,\n bid: contract.last_quote.bid,\n ask: contract.last_quote.ask,\n midpoint: contract.last_quote.midpoint,\n last_price: contract.last_trade?.price ?? null,\n open_interest: contract.open_interest,\n volume: contract.day.volume ?? 0,\n break_even: contract.break_even_price,\n };\n}\n\n// ===========================================================================\n// MassiveProvider\n// ===========================================================================\n\nexport class MassiveProvider implements MarketDataProvider {\n readonly name = \"massive\";\n\n async fetchBars(options: FetchBarsOptions): Promise<BarRow[]> {\n const apiKey = getApiKey();\n const {\n ticker,\n from,\n to,\n timespan = \"day\",\n multiplier = 1,\n assetClass = \"stock\",\n } = options;\n\n const apiTicker = toMassiveTicker(ticker, assetClass);\n const storageTicker = fromMassiveTicker(apiTicker);\n const headers = { Authorization: `Bearer ${apiKey}` };\n\n let url: string | null =\n `${MASSIVE_BASE_URL}/v2/aggs/ticker/${encodeURIComponent(apiTicker)}/range/${multiplier}/${timespan}/${from}/${to}?adjusted=false&limit=${MASSIVE_MAX_LIMIT}`;\n\n const allRows: BarRow[] = [];\n const seenCursors = new Set<string>();\n let pageCount = 0;\n\n while (url) {\n pageCount++;\n if (pageCount > MASSIVE_MAX_PAGES) {\n throw new Error(\n `Pagination safety limit reached (${MASSIVE_MAX_PAGES} pages) — possible API issue`\n );\n }\n\n const response = await fetchWithRetry(url, headers);\n\n if (response.status === 401) {\n throw new Error(\n \"MASSIVE_API_KEY rejected by Massive.com — check your key\"\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Massive.com API error: HTTP ${response.status} ${response.statusText}`\n );\n }\n\n const json = await response.json();\n\n const parsed = MassiveAggregateResponseSchema.safeParse(json);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Massive API response validation failed: ${issues}`);\n }\n\n const data = parsed.data;\n\n for (const bar of data.results) {\n const row: BarRow = {\n date: massiveTimestampToETDate(bar.t),\n open: bar.o,\n high: bar.h,\n low: bar.l,\n close: bar.c,\n volume: bar.v ?? 0,\n ticker: storageTicker,\n };\n if (timespan !== \"day\") {\n row.time = massiveTimestampToETTime(bar.t);\n }\n allRows.push(row);\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n throw new Error(\n `Pagination loop detected — cursor repeated: ${cursor.slice(0, 50)}...`\n );\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n\n // Enrich option intraday bars with bid/ask from quotes endpoint (best-effort)\n // Gated by MASSIVE_QUOTES_ENABLED env var — quotes endpoint requires higher Massive tier\n const quotesEnabled = process.env.MASSIVE_QUOTES_ENABLED === 'true' || process.env.MASSIVE_QUOTES_ENABLED === '1';\n if (quotesEnabled && assetClass === \"option\" && timespan !== \"day\" && allRows.length > 0) {\n const quotesMap = await this.fetchQuotesForBars(apiTicker, headers, from, to);\n if (quotesMap.size > 0) {\n for (const row of allRows) {\n if (row.time != null) {\n const key = `${row.date} ${row.time}`;\n const quote = quotesMap.get(key);\n if (quote != null) {\n row.bid = quote.bid;\n row.ask = quote.ask;\n }\n }\n }\n }\n }\n\n return allRows;\n }\n\n /**\n * Fetches historical quotes (bid/ask) for an option ticker over a date range.\n * Returns a Map keyed by \"YYYY-MM-DD HH:MM\" ET minute key.\n * Any error (network, HTTP error, parse failure) silently returns an empty Map.\n */\n private async fetchQuotesForBars(\n apiTicker: string,\n headers: Record<string, string>,\n from: string,\n to: string,\n ): Promise<Map<string, { bid: number; ask: number }>> {\n const result = new Map<string, { bid: number; ask: number }>();\n try {\n let url: string | null =\n `${MASSIVE_BASE_URL}/v3/quotes/${encodeURIComponent(apiTicker)}?timestamp.gte=${from}×tamp.lte=${to}&order=asc&limit=${MASSIVE_MAX_LIMIT}`;\n\n const seenCursors = new Set<string>();\n const QUOTES_MAX_PAGES = 100;\n let pageCount = 0;\n\n while (url) {\n pageCount++;\n if (pageCount > QUOTES_MAX_PAGES) {\n break;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(30_000),\n });\n } catch {\n // Network error — return what we have so far (best-effort)\n return result;\n }\n\n if (!response.ok) {\n // 403 (tier restriction), 429 (rate limit), or any other HTTP error — swallow\n return result;\n }\n\n const json = await response.json();\n const parsed = MassiveQuotesResponseSchema.safeParse(json);\n if (!parsed.success) {\n // Schema mismatch — return what we have so far\n return result;\n }\n\n const data = parsed.data;\n // Since order=asc, later quotes for the same minute overwrite earlier (last quote wins)\n for (const quote of data.results) {\n const key = nanosToETMinuteKey(quote.sip_timestamp);\n result.set(key, { bid: quote.bid_price, ask: quote.ask_price });\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n break; // Pagination loop — stop gracefully\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n } catch {\n // Any unexpected error — return empty map (best-effort)\n return new Map();\n }\n\n return result;\n }\n\n async fetchQuotes(ticker: string, from: string, to: string): Promise<Map<string, { bid: number; ask: number }>> {\n const apiKey = getApiKey();\n const apiTicker = toMassiveTicker(ticker, \"option\");\n const headers = { Authorization: `Bearer ${apiKey}` };\n return this.fetchQuotesForBars(apiTicker, headers, from, to);\n }\n\n async fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult> {\n const apiKey = getApiKey();\n const { underlying } = options;\n\n const assetClass = detectSnapshotAssetClass(underlying);\n const apiTicker = toMassiveTicker(underlying, assetClass);\n const headers = { Authorization: `Bearer ${apiKey}` };\n\n const params = new URLSearchParams({ limit: \"250\" });\n if (options.strike_price_gte != null) {\n params.set(\"strike_price.gte\", String(options.strike_price_gte));\n }\n if (options.strike_price_lte != null) {\n params.set(\"strike_price.lte\", String(options.strike_price_lte));\n }\n if (options.expiration_date_gte != null) {\n params.set(\"expiration_date.gte\", options.expiration_date_gte);\n }\n if (options.expiration_date_lte != null) {\n params.set(\"expiration_date.lte\", options.expiration_date_lte);\n }\n if (options.contract_type != null) {\n params.set(\"contract_type\", options.contract_type);\n }\n\n let url: string | null =\n `${MASSIVE_BASE_URL}/v3/snapshot/options/${encodeURIComponent(apiTicker)}?${params.toString()}`;\n\n const allContracts: OptionContract[] = [];\n const seenCursors = new Set<string>();\n let pageCount = 0;\n let underlyingPrice = 0;\n let underlyingTicker = underlying;\n\n while (url) {\n pageCount++;\n if (pageCount > MASSIVE_MAX_PAGES) {\n throw new Error(\n `Pagination safety limit reached (${MASSIVE_MAX_PAGES} pages) — possible API issue`,\n );\n }\n\n const response = await fetchWithRetry(url, headers);\n\n if (response.status === 401) {\n throw new Error(\n \"MASSIVE_API_KEY rejected by Massive.com — check your key\",\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Massive.com API error: HTTP ${response.status} ${response.statusText}`,\n );\n }\n\n const json = await response.json();\n\n const parsed = MassiveSnapshotResponseSchema.safeParse(json);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => `${String(i.path.join(\".\"))}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Massive API response validation failed: ${issues}`);\n }\n\n const data = parsed.data;\n\n if (data.results.length > 0 && underlyingPrice === 0) {\n underlyingPrice = data.results[0].underlying_asset.price;\n underlyingTicker = fromMassiveTicker(\n data.results[0].underlying_asset.ticker,\n );\n }\n\n for (const contract of data.results) {\n allContracts.push(mapContract(contract));\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n throw new Error(\n `Pagination loop detected — cursor repeated: ${cursor.slice(0, 50)}...`,\n );\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n\n return {\n contracts: allContracts,\n underlying_price: underlyingPrice,\n underlying_ticker: underlyingTicker,\n };\n }\n}\n","/**\n * Black-Scholes option pricing, greeks computation, and IV solver.\n * Includes Bachelier (normal) model as fallback for short-dated options.\n *\n * Pure math module — no I/O, no DuckDB, no fetch.\n * European-style BS formula with continuous dividend yield.\n *\n * References:\n * - CDF approximation: Abramowitz & Stegun 26.2.17 (rational approximation)\n * - IV solver: Newton-Raphson with bisection fallback (D-11: maxIter=100, tolerance=1e-6)\n * - Bachelier model: Brenner-Subrahmanyam (1988) normal model for near-zero DTE\n */\n\n/**\n * DTE threshold below which Bachelier normal model is used instead of Black-Scholes.\n * At very short DTE (<0.1 days / ~2.4 hours), BS gamma explodes and the lognormal assumption breaks down.\n * Bachelier (normal) model handles near-zero DTE gracefully.\n */\nexport const BACHELIER_DTE_THRESHOLD = 0.1; // days (~2.4 hours)\n\n/**\n * Result of computing greeks for a single option leg.\n */\nexport interface GreeksResult {\n delta: number | null;\n gamma: number | null;\n theta: number | null; // per day\n vega: number | null; // per 1% IV move\n iv: number | null; // annualized implied volatility (0-N, not percentage)\n model?: 'bs' | 'bachelier'; // which pricing model was used; undefined if IV solve failed\n}\n\n// --- Internal helpers (exported for Bachelier model and testing) ---\n\n/** Standard normal probability density function */\nexport function pdf(x: number): number {\n return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);\n}\n\n/**\n * Cumulative standard normal distribution function.\n * Uses Abramowitz & Stegun 26.2.17 rational approximation.\n * Accuracy: |error| < 7.5e-8\n */\nexport function cdf(x: number): number {\n if (x < -10) return 0;\n if (x > 10) return 1;\n\n const sign = x < 0 ? -1 : 1;\n const absX = Math.abs(x);\n\n const p = 0.2316419;\n const b1 = 0.319381530;\n const b2 = -0.356563782;\n const b3 = 1.781477937;\n const b4 = -1.821255978;\n const b5 = 1.330274429;\n\n const t = 1.0 / (1.0 + p * absX);\n const t2 = t * t;\n const t3 = t2 * t;\n const t4 = t3 * t;\n const t5 = t4 * t;\n\n const poly = b1 * t + b2 * t2 + b3 * t3 + b4 * t4 + b5 * t5;\n const result = 1.0 - pdf(absX) * poly;\n\n return sign === 1 ? result : 1.0 - result;\n}\n\n/** Compute d1 and d2 for Black-Scholes formula */\nfunction d1d2(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): { d1: number; d2: number } {\n const sqrtT = Math.sqrt(T);\n const d1 = (Math.log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);\n const d2 = d1 - sigma * sqrtT;\n return { d1, d2 };\n}\n\n// --- Exported functions ---\n\n/**\n * European Black-Scholes option price with continuous dividend yield.\n *\n * @param type - \"call\" or \"put\"\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate (e.g., 0.045)\n * @param q - Continuous dividend yield (e.g., 0.015 for SPX)\n * @param sigma - Volatility (annualized, e.g., 0.20 for 20%)\n * @returns Option price\n */\nexport function bsPrice(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n // Edge case: at or past expiry\n if (T <= 0) {\n return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);\n }\n\n // Edge case: zero volatility — return discounted intrinsic\n if (sigma <= 0) {\n const forward = S * Math.exp((r - q) * T);\n if (type === 'call') {\n return Math.max(forward - K, 0) * Math.exp(-r * T);\n } else {\n return Math.max(K - forward, 0) * Math.exp(-r * T);\n }\n }\n\n const { d1, d2 } = d1d2(S, K, T, r, q, sigma);\n\n if (type === 'call') {\n return S * Math.exp(-q * T) * cdf(d1) - K * Math.exp(-r * T) * cdf(d2);\n } else {\n return K * Math.exp(-r * T) * cdf(-d2) - S * Math.exp(-q * T) * cdf(-d1);\n }\n}\n\n/**\n * Black-Scholes delta.\n * Call: N(d1) * e^(-qT)\n * Put: (N(d1) - 1) * e^(-qT)\n */\nexport function bsDelta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) {\n if (type === 'call') return S > K ? 1 : 0;\n return S < K ? -1 : 0;\n }\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n const eqT = Math.exp(-q * T);\n\n if (type === 'call') {\n return cdf(d1) * eqT;\n } else {\n return (cdf(d1) - 1) * eqT;\n }\n}\n\n/**\n * Black-Scholes gamma. Same for calls and puts.\n * Gamma = N'(d1) * e^(-qT) / (S * sigma * sqrt(T))\n */\nexport function bsGamma(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n return (pdf(d1) * Math.exp(-q * T)) / (S * sigma * Math.sqrt(T));\n}\n\n/**\n * Black-Scholes theta (per calendar day).\n * Returns the daily time decay (negative for long options).\n */\nexport function bsTheta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1, d2 } = d1d2(S, K, T, r, q, sigma);\n const sqrtT = Math.sqrt(T);\n const eqT = Math.exp(-q * T);\n const erT = Math.exp(-r * T);\n\n // First term: -(S * e^(-qT) * N'(d1) * sigma) / (2 * sqrt(T))\n const term1 = -(S * eqT * pdf(d1) * sigma) / (2 * sqrtT);\n\n if (type === 'call') {\n const term2 = q * S * eqT * cdf(d1);\n const term3 = -r * K * erT * cdf(d2);\n return (term1 - term2 - term3) / 365;\n } else {\n const term2 = q * S * eqT * cdf(-d1);\n const term3 = -r * K * erT * cdf(-d2);\n return (term1 + term2 + term3) / 365;\n }\n}\n\n/**\n * Black-Scholes vega (per 1% IV move).\n * Vega = S * e^(-qT) * N'(d1) * sqrt(T) / 100\n */\nexport function bsVega(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n return (S * Math.exp(-q * T) * pdf(d1) * Math.sqrt(T)) / 100;\n}\n\n/**\n * Solve for implied volatility using Newton-Raphson with bisection fallback.\n *\n * @param type - \"call\" or \"put\"\n * @param marketPrice - Observed market price of the option\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate\n * @param q - Dividend yield\n * @param maxIter - Maximum iterations (default 100, per D-11)\n * @param tolerance - Convergence tolerance (default 1e-6, per D-11)\n * @returns Implied volatility or null if convergence fails\n */\nexport function solveIV(\n type: 'call' | 'put',\n marketPrice: number,\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n maxIter: number = 100,\n tolerance: number = 1e-6,\n): number | null {\n // Guard: invalid inputs\n if (marketPrice <= 0 || T <= 0) return null;\n\n let sigma = 0.3; // initial guess\n let lo = 0.001;\n let hi = 5.0;\n\n for (let i = 0; i < maxIter; i++) {\n const price = bsPrice(type, S, K, T, r, q, sigma);\n const diff = price - marketPrice;\n\n if (Math.abs(diff) < tolerance) {\n return sigma;\n }\n\n // Vega for Newton-Raphson step (raw vega, not per-1%-move)\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n const rawVega = S * Math.exp(-q * T) * pdf(d1) * Math.sqrt(T);\n\n if (rawVega < 1e-10) {\n // Bisection fallback when vega is near zero\n const mid = (lo + hi) / 2;\n if (diff > 0) {\n hi = sigma;\n } else {\n lo = sigma;\n }\n sigma = mid;\n } else {\n // Newton-Raphson step\n const newSigma = sigma - diff / rawVega;\n // Clamp to reasonable range\n if (newSigma <= 0 || newSigma > 10) {\n // Fall back to bisection\n if (diff > 0) {\n hi = sigma;\n } else {\n lo = sigma;\n }\n sigma = (lo + hi) / 2;\n } else {\n sigma = newSigma;\n }\n }\n }\n\n // Did not converge\n return null;\n}\n\n// --- Bachelier (Normal) Model ---\n\n/**\n * Bachelier (normal) European option price with continuous dividend yield.\n * Uses forward price F = S * e^((r-q)*T) as the underlying.\n *\n * For near-zero DTE, the lognormal assumption in Black-Scholes breaks down.\n * Bachelier's model (normal distribution of returns) handles this gracefully.\n *\n * @param type - \"call\" or \"put\"\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate (e.g., 0.045)\n * @param q - Continuous dividend yield (e.g., 0.015 for SPX)\n * @param sigma_n - Normal (dollar) volatility (e.g., 800 for SPX ~$800/year normal vol)\n * @returns Option price\n */\nexport function bachelierPrice(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0) return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);\n if (sigma_n <= 0) {\n const forward = S * Math.exp((r - q) * T);\n return Math.exp(-r * T) * (type === 'call' ? Math.max(forward - K, 0) : Math.max(K - forward, 0));\n }\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n const discount = Math.exp(-r * T);\n if (type === 'call') {\n return discount * ((forward - K) * cdf(d) + sigma_n * sqrtT * pdf(d));\n } else {\n return discount * ((K - forward) * cdf(-d) + sigma_n * sqrtT * pdf(d));\n }\n}\n\n/**\n * Bachelier delta.\n * Call: e^(-rT) * N(d)\n * Put: -e^(-rT) * N(-d)\n */\nexport function bachelierDelta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) {\n if (type === 'call') return S > K ? 1 : 0;\n return S < K ? -1 : 0;\n }\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n const discount = Math.exp(-r * T);\n return type === 'call' ? discount * cdf(d) : -discount * cdf(-d);\n}\n\n/**\n * Bachelier gamma (same for calls and puts).\n * Gamma = e^(-rT) * n(d) / (sigma_n * sqrt(T))\n */\nexport function bachelierGamma(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n return Math.exp(-r * T) * pdf(d) / (sigma_n * sqrtT);\n}\n\n/**\n * Bachelier theta (per calendar day).\n * Returns the daily time decay (negative for long options).\n * Formula: -(e^(-rT) * sigma_n * n(d) / (2 * sqrt(T)) - r * price) / 365\n */\nexport function bachelierTheta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n const discount = Math.exp(-r * T);\n const price = bachelierPrice(type, S, K, T, r, q, sigma_n);\n // dV/dT = -e^(-rT) * sigma_n * n(d) / (2*sqrt(T)) + r * price (positive = more time = more value)\n // theta = -dV/dT per year, then / 365 for per-calendar-day\n // annualTheta is negative for long options (time decay)\n const annualTheta = -discount * sigma_n * pdf(d) / (2 * sqrtT) + r * price;\n return annualTheta / 365;\n}\n\n/**\n * Bachelier vega (per 1% normal vol move).\n * Raw vega = e^(-rT) * sqrt(T) * n(d). Per 1% = raw / 100.\n */\nexport function bachelierVega(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n return Math.exp(-r * T) * Math.sqrt(T) * pdf(d) / 100;\n}\n\n/**\n * Solve for normal (Bachelier) implied volatility using Newton-Raphson.\n *\n * @param type - \"call\" or \"put\"\n * @param marketPrice - Observed market price of the option\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate\n * @param q - Dividend yield\n * @param maxIter - Maximum iterations (default 100)\n * @param tolerance - Convergence tolerance (default 1e-6)\n * @returns Normal implied volatility (dollar vol, e.g., ~800 for SPX) or null\n */\nexport function solveNormalIV(\n type: 'call' | 'put',\n marketPrice: number,\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n maxIter: number = 100,\n tolerance: number = 1e-6,\n): number | null {\n if (marketPrice <= 0 || T <= 0) return null;\n // Initial guess: Brenner-Subrahmanyam for normal model\n // sigma_n ~ marketPrice / sqrt(T/(2*pi))\n let sigma_n = marketPrice / Math.sqrt(T / (2 * Math.PI));\n // Clamp initial guess to reasonable range\n sigma_n = Math.max(sigma_n, 1); // at least $1 normal vol\n\n // Bisection bounds — normal vol is in dollar terms (typically 10-10000 for index options)\n let lo = 0.01;\n let hi = 50000;\n\n for (let i = 0; i < maxIter; i++) {\n const price = bachelierPrice(type, S, K, T, r, q, sigma_n);\n const diff = price - marketPrice;\n if (Math.abs(diff) < tolerance) return sigma_n;\n // Raw vega for Newton step (without /100 per-1% scaling)\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n const rawVega = Math.exp(-r * T) * Math.sqrt(T) * pdf(d);\n\n if (rawVega < 1e-10) {\n // Bisection fallback when vega is near zero\n if (diff > 0) {\n hi = sigma_n;\n } else {\n lo = sigma_n;\n }\n sigma_n = (lo + hi) / 2;\n } else {\n // Newton-Raphson step\n const newSigma = sigma_n - diff / rawVega;\n if (newSigma <= 0 || newSigma > 100000) {\n // Fall back to bisection\n if (diff > 0) {\n hi = sigma_n;\n } else {\n lo = sigma_n;\n }\n sigma_n = (lo + hi) / 2;\n } else {\n sigma_n = newSigma;\n }\n }\n }\n return null;\n}\n\n/**\n * Compute all greeks for a single option leg.\n *\n * First solves for IV from the market price, then computes delta, gamma, theta, vega.\n * Returns all nulls if IV cannot be determined.\n *\n * @param optionPrice - Market price of the option\n * @param underlyingPrice - Current underlying price (S)\n * @param strike - Option strike price (K)\n * @param dte - Days to expiry (fractional days, converted to years internally)\n * @param type - \"C\" for call, \"P\" for put\n * @param riskFreeRate - Risk-free interest rate\n * @param dividendYield - Continuous dividend yield\n * @returns GreeksResult with delta, gamma, theta, vega, iv (all nullable)\n */\nexport function computeLegGreeks(\n optionPrice: number,\n underlyingPrice: number,\n strike: number,\n dte: number,\n type: 'C' | 'P',\n riskFreeRate: number,\n dividendYield: number,\n): GreeksResult {\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' : 'put';\n const nullResult: GreeksResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };\n\n if (dte < BACHELIER_DTE_THRESHOLD) {\n // Bachelier normal model for short-dated options (dte < 0.1 days / ~2.4 hours).\n // iv field stores normal (dollar) volatility, not log-normal vol.\n const iv = solveNormalIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);\n if (iv === null) return nullResult;\n return {\n delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n gamma: bachelierGamma(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n theta: bachelierTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n vega: bachelierVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n iv,\n model: 'bachelier',\n };\n }\n\n // Black-Scholes for longer-dated options (dte >= 0.1 days).\n // iv field stores annualized log-normal volatility (0-N, not percentage).\n const iv = solveIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);\n if (iv === null) return nullResult;\n return {\n delta: bsDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n gamma: bsGamma(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n theta: bsTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n vega: bsVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n iv,\n model: 'bs',\n };\n}\n","/**\n * ThetaData Market Data Provider (stub)\n *\n * Placeholder implementation — will be replaced with full ThetaData REST API\n * adapter in a subsequent task.\n */\n\nimport type {\n MarketDataProvider,\n BarRow,\n FetchBarsOptions,\n FetchSnapshotOptions,\n FetchSnapshotResult,\n} from \"../market-provider.js\";\n\nexport class ThetaDataProvider implements MarketDataProvider {\n readonly name = \"thetadata\";\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async fetchBars(options: FetchBarsOptions): Promise<BarRow[]> {\n throw new Error(\"ThetaData provider not yet implemented — set MARKET_DATA_PROVIDER=massive\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult> {\n throw new Error(\"ThetaData provider not yet implemented — set MARKET_DATA_PROVIDER=massive\");\n }\n}\n","/**\n * Market Data Provider Interface\n *\n * Defines the shared types and provider abstraction for fetching market data\n * from external APIs (Massive.com, ThetaData, etc.).\n *\n * All providers normalize their responses to BarRow and OptionContract types.\n * The factory function getProvider() selects the active provider based on the\n * MARKET_DATA_PROVIDER environment variable (default: \"massive\").\n */\n\nimport { MassiveProvider } from \"./providers/massive.js\";\nimport { ThetaDataProvider } from \"./providers/thetadata.js\";\n\n// ---------------------------------------------------------------------------\n// Shared Types\n// ---------------------------------------------------------------------------\n\n/** Normalized OHLCV bar — shared output type for all providers. */\nexport interface BarRow {\n date: string; // \"YYYY-MM-DD\" Eastern Time\n open: number;\n high: number;\n low: number;\n close: number;\n volume: number;\n ticker: string; // Plain storage format (no prefix)\n time?: string; // \"HH:MM\" ET — only set for intraday (minute/hour) bars\n bid?: number; // Best bid — only set when provider supplies quote data\n ask?: number; // Best ask — only set when provider supplies quote data\n}\n\n/** Asset classes supported by market data providers. */\nexport type AssetClass = \"stock\" | \"index\" | \"option\";\n\n/** Options for fetching OHLCV bars. */\nexport interface FetchBarsOptions {\n /** Plain ticker — VIX, AAPL, SPX251219C05000000 (no provider-specific prefix) */\n ticker: string;\n /** Start date \"YYYY-MM-DD\" */\n from: string;\n /** End date \"YYYY-MM-DD\" */\n to: string;\n /** Bar timespan (default: \"day\") */\n timespan?: \"day\" | \"minute\" | \"hour\";\n /** Bar multiplier (default: 1) */\n multiplier?: number;\n /** Asset class (default: \"stock\") */\n assetClass?: AssetClass;\n}\n\n/** Curated option contract returned by all providers. */\nexport interface OptionContract {\n ticker: string;\n underlying_ticker: string;\n underlying_price: number;\n contract_type: \"call\" | \"put\";\n strike: number;\n expiration: string; // \"YYYY-MM-DD\"\n exercise_style: string;\n delta: number | null;\n gamma: number | null;\n theta: number | null;\n vega: number | null;\n iv: number | null;\n greeks_source: \"massive\" | \"thetadata\" | \"computed\";\n bid: number;\n ask: number;\n midpoint: number;\n last_price: number | null;\n open_interest: number;\n volume: number;\n break_even: number;\n}\n\n/** Options for fetching option chain snapshots. */\nexport interface FetchSnapshotOptions {\n underlying: string;\n strike_price_gte?: number;\n strike_price_lte?: number;\n expiration_date_gte?: string;\n expiration_date_lte?: string;\n contract_type?: \"call\" | \"put\";\n}\n\n/** Result from fetching an option chain snapshot. */\nexport interface FetchSnapshotResult {\n contracts: OptionContract[];\n underlying_price: number;\n underlying_ticker: string;\n}\n\n/** The contract every market data provider must implement. */\nexport interface MarketDataProvider {\n readonly name: string;\n fetchBars(options: FetchBarsOptions): Promise<BarRow[]>;\n fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult>;\n /** Best-effort bid/ask quotes keyed by \"YYYY-MM-DD HH:MM\" ET. Optional — not all providers support this. */\n fetchQuotes?(ticker: string, from: string, to: string): Promise<Map<string, { bid: number; ask: number }>>;\n}\n\n// ---------------------------------------------------------------------------\n// Provider Factory (lazy singleton with static imports)\n// ---------------------------------------------------------------------------\n\nlet _cached: MarketDataProvider | null = null;\n\n/**\n * Get the active market data provider.\n *\n * Reads MARKET_DATA_PROVIDER env var (default: \"massive\").\n * Returns a lazy singleton — cached after first call.\n */\nexport function getProvider(): MarketDataProvider {\n if (_cached) return _cached;\n const name = (process.env.MARKET_DATA_PROVIDER ?? \"massive\").toLowerCase();\n switch (name) {\n case \"massive\":\n _cached = new MassiveProvider();\n break;\n case \"thetadata\":\n _cached = new ThetaDataProvider();\n break;\n default:\n throw new Error(\n `Unknown MARKET_DATA_PROVIDER: \"${name}\". Supported: massive, thetadata`\n );\n }\n return _cached!;\n}\n\n/** Reset cached provider — for test isolation only. */\nexport function _resetProvider(): void {\n _cached = null;\n}\n","/**\n * Shared Analysis Statistics\n *\n * Reusable stat computation extracted from analyze_regime_performance.\n * Used by portfolio_structure_map, analyze_structure_fit, and\n * analyze_regime_performance to compute per-slice statistics from P&L arrays.\n */\n\n/**\n * Statistics for a slice (segment, bucket, group) of trades.\n */\nexport interface SliceStats {\n tradeCount: number;\n wins: number;\n losses: number;\n /** Win rate as percentage (0-100) */\n winRate: number;\n totalPl: number;\n avgPl: number;\n avgWin: number;\n avgLoss: number;\n /** Gross wins / gross losses. null if no losses but has wins. 0 if no wins or empty. */\n profitFactor: number | null;\n}\n\n/**\n * Round a number to 2 decimal places.\n */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Compute statistics for an array of P&L values.\n * Wins are P&L > 0, losses are P&L <= 0.\n * All numeric values are rounded to 2 decimal places.\n *\n * @param pls - Array of P&L values (positive = win, zero/negative = loss)\n * @returns Computed slice statistics\n */\nexport function computeSliceStats(pls: number[]): SliceStats {\n if (pls.length === 0) {\n return {\n tradeCount: 0,\n wins: 0,\n losses: 0,\n winRate: 0,\n totalPl: 0,\n avgPl: 0,\n avgWin: 0,\n avgLoss: 0,\n profitFactor: 0,\n };\n }\n\n const winPls = pls.filter((p) => p > 0);\n const lossPls = pls.filter((p) => p <= 0);\n\n const wins = winPls.length;\n const losses = lossPls.length;\n const winRate = (wins / pls.length) * 100;\n const totalPl = pls.reduce((sum, p) => sum + p, 0);\n const avgPl = totalPl / pls.length;\n\n const avgWin = wins > 0 ? winPls.reduce((s, p) => s + p, 0) / wins : 0;\n const avgLoss = losses > 0 ? lossPls.reduce((s, p) => s + p, 0) / losses : 0;\n\n const grossWins = winPls.reduce((s, p) => s + p, 0);\n const grossLosses = Math.abs(lossPls.reduce((s, p) => s + p, 0));\n\n let profitFactor: number | null;\n if (grossLosses > 0) {\n profitFactor = grossWins / grossLosses;\n } else if (grossWins > 0) {\n profitFactor = null; // All winners, no losses to divide by\n } else {\n profitFactor = 0;\n }\n\n return {\n tradeCount: pls.length,\n wins,\n losses,\n winRate: round2(winRate),\n totalPl: round2(totalPl),\n avgPl: round2(avgPl),\n avgWin: round2(avgWin),\n avgLoss: round2(avgLoss),\n profitFactor: profitFactor !== null ? round2(profitFactor) : null,\n };\n}\n","/**\n * Filter Predicate Builder\n *\n * Converts EntryFilter objects into runtime predicates that can evaluate\n * market data records. Handles field timing awareness via CLOSE_KNOWN_FIELDS\n * to automatically apply the prev_ prefix for close-derived fields.\n *\n * Used by analyze_structure_fit and portfolio_structure_map to evaluate\n * entry filters against market data rows.\n */\n\nimport { CLOSE_KNOWN_FIELDS } from \"./field-timing.js\";\nimport type { EntryFilter } from \"../models/strategy-profile.js\";\n\n/**\n * A compiled filter predicate with metadata about the field key used.\n */\nexport interface FilterPredicate {\n /** Evaluate this predicate against a market data record */\n test: (market: Record<string, unknown>) => boolean;\n /** The actual field key used for lookup (may have prev_ prefix) */\n fieldKey: string;\n /** Whether the field was detected as close-derived and lagged */\n isLagged: boolean;\n}\n\n/**\n * Day-of-week name to number mapping (market data uses 1=Mon to 5=Fri).\n */\nconst DAY_NAME_TO_NUM: Record<string, number> = {\n monday: 1, mon: 1,\n tuesday: 2, tue: 2, tues: 2,\n wednesday: 3, wed: 3,\n thursday: 4, thu: 4, thurs: 4,\n friday: 5, fri: 5,\n};\n\n/**\n * If a filter value is a day-of-week name and the field is Day_of_Week,\n * convert it to the corresponding number. Returns null if not applicable.\n */\nfunction resolveDayName(value: unknown): number | null {\n if (typeof value !== \"string\") return null;\n return DAY_NAME_TO_NUM[value.toLowerCase()] ?? null;\n}\n\n/**\n * Safely extract a numeric value from a record.\n * Returns NaN if the value is missing, null, undefined, or non-numeric.\n */\nfunction getNum(record: Record<string, unknown>, key: string): number {\n const val = record[key];\n if (val === null || val === undefined) return NaN;\n const num = Number(val);\n return num;\n}\n\n/**\n * Safely extract a value from a record for loose equality comparison.\n * Returns undefined if the key is missing.\n */\nfunction getRaw(record: Record<string, unknown>, key: string): unknown {\n return record[key];\n}\n\n/**\n * Check whether a filter value is a cross-field reference (a string that\n * looks like a field name rather than a pure numeric literal).\n */\nfunction isCrossFieldRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n // If it parses as a finite number, it's a numeric literal, not a field ref\n if (value.trim() !== \"\" && isFinite(Number(value))) return false;\n return true;\n}\n\n/**\n * Resolve a cross-field reference value. If the value is a string that\n * already exists as a key in the market record, use it as-is. Otherwise,\n * if the bare field name (without prev_ prefix) is close-derived, try\n * the prev_ prefixed version.\n */\nfunction resolveFieldRef(\n refName: string,\n market: Record<string, unknown>\n): number {\n // Direct lookup first (handles cases like \"prev_VIX_Close\" spelled out)\n if (refName in market) {\n return getNum(market, refName);\n }\n // If the ref looks like a bare close-derived field, try prev_ prefix\n if (CLOSE_KNOWN_FIELDS.has(refName)) {\n return getNum(market, `prev_${refName}`);\n }\n return NaN;\n}\n\n/**\n * Build a runtime predicate from an EntryFilter.\n *\n * Automatically detects close-derived fields via CLOSE_KNOWN_FIELDS and\n * prepends \"prev_\" to the field key for correct lookahead-free evaluation.\n *\n * For comparison operators (>, <, >=, <=, ==), if the filter value is a\n * string that looks like a field name (not a pure numeric string), it is\n * treated as a cross-field reference. The referenced field's value is\n * looked up from the market record at evaluation time.\n *\n * NaN/null/undefined values in the market record always return false\n * (missing data never matches a filter).\n *\n * @param filter - Entry filter specification\n * @returns Compiled predicate with metadata\n */\nexport function buildFilterPredicate(filter: EntryFilter): FilterPredicate {\n const isLagged = CLOSE_KNOWN_FIELDS.has(filter.field);\n const fieldKey = isLagged ? `prev_${filter.field}` : filter.field;\n\n const { operator, value } = filter;\n\n const test = (market: Record<string, unknown>): boolean => {\n // For \"in\" and \"==\" operators, we may need raw value access\n if (operator === \"in\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n if (!Array.isArray(value)) return false;\n // Try day-of-week name resolution for each element\n return value.some((v) => {\n const dayNum = resolveDayName(v);\n if (dayNum !== null) return Number(raw) === dayNum;\n return v == raw; // eslint-disable-line eqeqeq\n });\n }\n\n if (operator === \"==\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n // Day-of-week name resolution (e.g., \"Tuesday\" == 2)\n const dayNum = resolveDayName(value);\n if (dayNum !== null) return Number(raw) === dayNum;\n // Cross-field reference for ==\n if (isCrossFieldRef(value)) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n return Number(raw) === refVal;\n }\n return value == raw; // eslint-disable-line eqeqeq\n }\n\n // Numeric operators: >, <, >=, <=, between\n const num = getNum(market, fieldKey);\n if (isNaN(num)) return false;\n\n // For comparison operators, check if value is a cross-field reference\n if (\n isCrossFieldRef(value) &&\n (operator === \">\" || operator === \"<\" || operator === \">=\" || operator === \"<=\")\n ) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n switch (operator) {\n case \">\":\n return num > refVal;\n case \"<\":\n return num < refVal;\n case \">=\":\n return num >= refVal;\n case \"<=\":\n return num <= refVal;\n }\n }\n\n switch (operator) {\n case \">\":\n return num > Number(value);\n case \"<\":\n return num < Number(value);\n case \">=\":\n return num >= Number(value);\n case \"<=\":\n return num <= Number(value);\n case \"between\": {\n if (!Array.isArray(value) || value.length < 2) return false;\n const lo = Number(value[0]);\n const hi = Number(value[1]);\n return num >= lo && num <= hi;\n }\n default:\n return false;\n }\n };\n\n return { test, fieldKey, isLagged };\n}\n","/**\n * Strategy Profile Tools\n *\n * MCP tools for CRUD operations on strategy profiles stored in DuckDB.\n * Wraps the Phase 60 storage layer (db/profile-schemas.ts) as conversational tools.\n *\n * Tools registered:\n * - profile_strategy — Create or update a strategy profile\n * - get_strategy_profile — Retrieve a single profile by block + strategy name\n * - list_profiles — List profiles (optionally filtered by block)\n * - delete_profile — Remove a profile (idempotent)\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.js\";\nimport {\n upsertProfile,\n getProfile,\n listProfiles,\n deleteProfile,\n} from \"../db/profile-schemas.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schemas (exported for testability)\n// ---------------------------------------------------------------------------\n\nexport const profileStrategySchema = z.object({\n blockId: z.string().describe(\"Block ID (block_id) to associate the profile with\"),\n strategyName: z.string().describe(\"Human-readable strategy name (e.g., 'Pickle RIC v2')\"),\n structureType: z\n .string()\n .describe(\n \"Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, \" +\n \"butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc.\"\n ),\n greeksBias: z\n .string()\n .describe(\n \"Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, \" +\n \"delta_negative, gamma_scalp, etc.\"\n ),\n thesis: z.string().default(\"\").describe(\"Free-text description of the strategy thesis\"),\n legs: z\n .array(\n z.object({\n type: z.string().describe(\"Leg type: long_put, short_call, long_call, short_put, etc.\"),\n strike: z.string().describe(\"Strike selection: ATM, 5-delta, 30-delta, etc.\"),\n expiry: z.string().describe(\"Expiry selection: same-day, weekly, 45-DTE, etc.\"),\n quantity: z.number().describe(\"Quantity (positive=long, negative=short)\"),\n strikeMethod: z.enum(['delta', 'dollar_price', 'offset', 'percentage']).optional().describe(\"How strike is selected\"),\n strikeValue: z.number().optional().describe(\"Numeric strike value (e.g., 25 for 25-delta)\"),\n })\n )\n .default([])\n .describe(\"Structured leg descriptions\"),\n entryFilters: z\n .array(\n z.object({\n field: z.string().describe(\"Market data field: VIX_Close, RSI_14, Vol_Regime, etc.\"),\n operator: z.string().describe(\"Comparison operator: >, <, >=, <=, ==, between, in\"),\n value: z\n .union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))])\n .describe(\"Filter value or array for between/in operators\"),\n description: z.string().optional().describe(\"Human-readable description of this filter\"),\n source: z.enum([\"market\", \"execution\"]).optional().describe(\"Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis.\"),\n })\n )\n .default([])\n .describe(\"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis).\"),\n exitRules: z\n .array(\n z.object({\n type: z.string().describe(\"Rule type: stop_loss, profit_target, time_exit, conditional\"),\n trigger: z.string().describe(\"Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'\"),\n description: z.string().optional().describe(\"Human-readable description\"),\n stopLossType: z.enum(['percentage', 'dollar', 'sl_ratio', 'debit_percentage']).optional().describe(\"Stop loss calculation method\"),\n stopLossValue: z.number().optional().describe(\"Stop loss numeric value\"),\n monitoring: z.object({\n granularity: z.enum(['intra_minute', 'candle_close', 'end_of_bar']).optional().describe(\"Price check frequency\"),\n priceSource: z.enum(['nbbo', 'mid', 'last']).optional().describe(\"Which price to use\"),\n }).optional().describe(\"Monitoring configuration for this rule\"),\n slippage: z.number().optional().describe(\"Per-rule slippage override\"),\n })\n )\n .default([])\n .describe(\"Exit rules and triggers\"),\n expectedRegimes: z\n .array(z.enum([\"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"]))\n .default([])\n .describe(\"VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+\"),\n keyMetrics: z\n .object({\n expectedWinRate: z.number().optional().describe(\"Expected win rate (0-1)\"),\n targetPremium: z.number().optional().describe(\"Target premium collected ($)\"),\n maxLoss: z.number().optional().describe(\"Maximum loss per contract ($)\"),\n profitTarget: z.number().optional().describe(\"Profit target ($ or %)\"),\n })\n .passthrough()\n .default({})\n .describe(\"Performance benchmarks and strategy-specific metrics\"),\n positionSizing: z\n .object({\n method: z.string().describe(\"Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary\"),\n allocationPct: z.number().optional().describe(\"Portfolio allocation percentage (e.g., 2 for 2%)\"),\n maxContracts: z.number().optional().describe(\"Maximum contracts per trade\"),\n maxAllocationDollar: z.number().optional().describe(\"Maximum dollar allocation per trade\"),\n maxOpenPositions: z.number().optional().describe(\"Maximum concurrent open positions\"),\n description: z.string().optional().describe(\"Free-text sizing notes\"),\n backtestAllocationPct: z.number().optional().describe(\"Allocation % used in backtest\"),\n liveAllocationPct: z.number().optional().describe(\"Allocation % used in live portfolio\"),\n maxContractsPerTrade: z.number().optional().describe(\"Per-entry contract cap (distinct from maxContracts hard cap)\"),\n })\n .optional()\n .describe(\"Position sizing rules. Per-block — same strategy in backtest vs portfolio may have different sizing.\"),\n underlying: z.string().optional().describe(\"Underlying symbol: SPX, QQQ, etc.\"),\n reEntry: z.boolean().optional().describe(\"Strategy supports re-entry on same day\"),\n capProfits: z.boolean().optional().describe(\"Profits are capped by structure\"),\n capLosses: z.boolean().optional().describe(\"Losses are capped by structure\"),\n requireTwoPricesPT: z.boolean().optional().describe(\"Profit target requires two prices\"),\n closeOnCompletion: z.boolean().optional().describe(\"Close entire position when any leg hits target\"),\n ignoreMarginReq: z.boolean().optional().describe(\"Strategy ignores standard margin requirements\"),\n});\n\nexport const getStrategyProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID to look up\"),\n strategyName: z.string().describe(\"Strategy name to look up\"),\n});\n\nexport const listProfilesSchema = z.object({\n blockId: z.string().optional().describe(\"Optional block ID filter. Omit to list all profiles across all blocks.\"),\n});\n\nexport const deleteProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID of the profile to delete\"),\n strategyName: z.string().describe(\"Strategy name of the profile to delete\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler functions (exported for testability)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle profile_strategy: create or update a strategy profile.\n */\nexport async function handleProfileStrategy(\n input: z.infer<typeof profileStrategySchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const stored = await upsertProfile(conn, {\n blockId: input.blockId,\n strategyName: input.strategyName,\n structureType: input.structureType,\n greeksBias: input.greeksBias,\n thesis: input.thesis,\n legs: input.legs,\n entryFilters: input.entryFilters,\n exitRules: input.exitRules,\n expectedRegimes: input.expectedRegimes,\n keyMetrics: input.keyMetrics,\n positionSizing: input.positionSizing,\n underlying: input.underlying,\n reEntry: input.reEntry,\n capProfits: input.capProfits,\n capLosses: input.capLosses,\n requireTwoPricesPT: input.requireTwoPricesPT,\n closeOnCompletion: input.closeOnCompletion,\n ignoreMarginReq: input.ignoreMarginReq,\n });\n return createToolOutput(\n `Profile saved: ${input.strategyName} for block ${input.blockId}`,\n { profile: stored }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n/**\n * Handle get_strategy_profile: retrieve a single profile.\n */\nexport async function handleGetStrategyProfile(\n input: z.infer<typeof getStrategyProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, input.blockId, input.strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}'`,\n { profile: null }\n );\n }\n return createToolOutput(\n `Profile: ${input.strategyName} in block ${input.blockId}`,\n { profile }\n );\n}\n\n/**\n * Handle list_profiles: list profiles with optional block filter.\n */\nexport async function handleListProfiles(\n input: z.infer<typeof listProfilesSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId);\n const summaryRows = profiles.map((p) => ({\n blockId: p.blockId,\n strategyName: p.strategyName,\n structureType: p.structureType,\n greeksBias: p.greeksBias,\n underlying: p.underlying ?? null,\n positionSizing: p.positionSizing?.method ?? null,\n updatedAt: p.updatedAt,\n }));\n return createToolOutput(\n `Found ${profiles.length} profile(s)${input.blockId ? ` for block ${input.blockId}` : \"\"}`,\n { count: profiles.length, profiles: summaryRows }\n );\n}\n\n/**\n * Handle delete_profile: remove a profile (idempotent).\n */\nexport async function handleDeleteProfile(\n input: z.infer<typeof deleteProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const deleted = await deleteProfile(conn, input.blockId, input.strategyName);\n if (deleted) {\n return createToolOutput(\n `Deleted profile: ${input.strategyName} from block ${input.blockId}`,\n { deleted: true }\n );\n }\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}' — nothing to delete`,\n { deleted: false }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all profile CRUD tools on the MCP server.\n *\n * @param server - McpServer instance to register tools on\n * @param baseDir - Base data directory (passed to connection helpers)\n */\nexport function registerProfileTools(server: McpServer, baseDir: string): void {\n // -------------------------------------------------------------------------\n // Tool: profile_strategy\n // -------------------------------------------------------------------------\n server.registerTool(\n \"profile_strategy\",\n {\n description:\n \"Create or update a strategy profile for a block. Stores structure type, greeks bias, \" +\n \"legs, entry filters, exit rules, expected regimes, key metrics, and position sizing. \" +\n \"If a profile with the same block_id + strategy_name already exists, it is overwritten (upsert). \" +\n \"When profiling the same strategy across multiple blocks (e.g., backtest vs live portfolio), \" +\n \"retrieve the existing profile with get_strategy_profile and copy its fields, updating only \" +\n \"positionSizing or other block-specific params rather than re-asking the user for all details.\",\n inputSchema: profileStrategySchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleProfileStrategy(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: get_strategy_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"get_strategy_profile\",\n {\n description:\n \"Retrieve a single strategy profile by block_id and strategy_name. \" +\n \"Returns the full profile including all schema fields, or a not-found message.\",\n inputSchema: getStrategyProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleGetStrategyProfile(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: list_profiles\n // -------------------------------------------------------------------------\n server.registerTool(\n \"list_profiles\",\n {\n description:\n \"List strategy profiles. Provide block_id to filter by block, or omit to list all profiles \" +\n \"across all blocks. Returns summary rows with block_id, strategy_name, structure_type, \" +\n \"greeks_bias, and updated_at.\",\n inputSchema: listProfilesSchema,\n },\n async (input) => {\n // list_profiles has optional blockId — when provided, sync the block first;\n // when omitted, query directly without sync (no block to validate).\n if (input.blockId) {\n const syncedHandler = withSyncedBlock(baseDir, async (syncInput: { blockId: string }, ctx) => {\n return handleListProfiles({ blockId: syncInput.blockId }, ctx.baseDir);\n });\n return syncedHandler({ blockId: input.blockId });\n }\n return handleListProfiles(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: delete_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"delete_profile\",\n {\n description:\n \"Delete a strategy profile by block_id and strategy_name. \" +\n \"Idempotent: deleting a nonexistent profile returns success with a not-found message.\",\n inputSchema: deleteProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleDeleteProfile(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Output Formatter\n *\n * Utilities for formatting MCP tool output.\n *\n * JSON-First Pattern:\n * Tools return structured JSON as the primary format. JSON is machine-readable,\n * enabling reliable data extraction without parsing natural language.\n *\n * A brief text summary is included for user visibility, but the JSON\n * is the authoritative source for all data and reasoning.\n */\n\n/**\n * MCP content item types\n */\nexport interface McpTextContent {\n type: \"text\";\n text: string;\n}\n\nexport interface McpResourceContent {\n type: \"resource\";\n resource: {\n uri: string;\n mimeType: string;\n text: string;\n };\n}\n\nexport type McpContent = McpTextContent | McpResourceContent;\n\nexport interface ToolOutput {\n [x: string]: unknown;\n content: McpContent[];\n}\n\n// Legacy alias for backward compatibility\nexport type DualOutput = ToolOutput;\n\n/**\n * Create JSON-first output for MCP tools.\n *\n * The structured JSON is the primary data source for Claude reasoning.\n * A brief text summary is provided for user visibility.\n *\n * @param summary - Brief text summary (1-3 lines) for user display\n * @param data - Structured data object - the authoritative data source\n * @returns MCP-compatible response with JSON as primary content\n */\nexport function createToolOutput(summary: string, data: object): ToolOutput {\n return {\n content: [\n { type: \"text\", text: summary },\n {\n type: \"resource\",\n resource: {\n uri: \"data:application/json\",\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n },\n ],\n };\n}\n\n/**\n * Legacy function - redirects to createToolOutput.\n * @deprecated Use createToolOutput instead\n */\nexport function createDualOutput(markdown: string, data: object): DualOutput {\n // Extract a brief summary from the markdown (first non-empty line or heading)\n const lines = markdown.split(\"\\n\").filter((l) => l.trim());\n const summary = lines[0]?.replace(/^#+\\s*/, \"\") || \"Results available\";\n return createToolOutput(summary, data);\n}\n\n/**\n * Format a number as currency ($1,234.56)\n */\nexport function formatCurrency(value: number): string {\n const isNegative = value < 0;\n const absValue = Math.abs(value);\n const formatted = absValue.toLocaleString(\"en-US\", {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return isNegative ? `-$${formatted}` : `$${formatted}`;\n}\n\n/**\n * Format a number as percentage (12.34%)\n */\nexport function formatPercent(value: number, decimals: number = 2): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format a ratio with specified decimals\n */\nexport function formatRatio(\n value: number | undefined,\n decimals: number = 2\n): string {\n if (value === undefined || value === null || !isFinite(value)) {\n return \"N/A\";\n }\n return value.toFixed(decimals);\n}\n","/**\n * Profile Analysis Tools\n *\n * MCP tools that use stored strategy profiles for targeted analysis:\n * - analyze_structure_fit: Dimension-based performance breakdown using profile context\n * - validate_entry_filters: Entry filter effectiveness analysis with ablation study\n * - portfolio_structure_map: Vol_Regime x Trend_Direction matrix across strategies\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.js\";\nimport { getProfile, listProfiles } from \"../db/profile-schemas.js\";\nimport { filterByStrategy } from \"./shared/filters.js\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.js\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.js\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.js\";\nimport { buildFilterPredicate, type FilterPredicate } from \"../utils/filter-predicates.js\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.js\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.js\";\nimport { syncAllBlocks } from \"../sync/index.js\";\n\n// =============================================================================\n// Utility Functions (local to this module)\n// =============================================================================\n\n/**\n * Format trade date to YYYY-MM-DD for market data matching.\n */\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\n// =============================================================================\n// Vol Regime Labels\n// =============================================================================\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\nconst TREND_LABELS = [\"up\", \"down\", \"flat\"] as const;\ntype TrendLabel = (typeof TREND_LABELS)[number];\n\n/**\n * Day of week labels (market data: 1=Mon to 5=Fri)\n */\nconst DAY_LABELS: Record<number, string> = {\n 1: \"Monday\",\n 2: \"Tuesday\",\n 3: \"Wednesday\",\n 4: \"Thursday\",\n 5: \"Friday\",\n};\n\n/**\n * Determine time-of-day bucket from timeOpened string (format \"HH:MM:SS\" or \"HH:MM\").\n */\nfunction getTimeBucket(timeOpened: string | undefined): string | null {\n if (!timeOpened) return null;\n const match = timeOpened.match(/^(\\d{1,2}):(\\d{2})/);\n if (!match) return null;\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const totalMinutes = hours * 60 + minutes;\n\n // morning: 09:30-11:00, midday: 11:00-14:00, afternoon: 14:00-16:00\n if (totalMinutes < 570) return null; // before 09:30\n if (totalMinutes < 660) return \"morning\"; // 09:30-11:00\n if (totalMinutes < 840) return \"midday\"; // 11:00-14:00\n if (totalMinutes <= 960) return \"afternoon\"; // 14:00-16:00\n return null; // after 16:00\n}\n\n/**\n * Safely get a raw value from a record.\n */\nfunction getRaw(record: Record<string, unknown>, field: string): unknown {\n return record[field];\n}\n\ninterface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n}\n\n/**\n * Load trades and market data for a strategy profile analysis.\n * Shared between analyze_structure_fit and validate_entry_filters.\n */\nasync function loadTradesAndMarket(\n baseDir: string,\n blockId: string,\n strategyName: string\n): Promise<{\n matched: TradeWithMarket[];\n unmatchedCount: number;\n allTrades: Trade[];\n}> {\n const block = await loadBlock(baseDir, blockId);\n let trades = filterByStrategy(block.trades, strategyName);\n\n // Single-strategy backtest blocks may have a different strategy name in the CSV\n // (e.g., blockId fallback \"2_3 dc\" vs profile name \"2/3 DC - v2\").\n // If no trades match by name and the block has only one unique strategy, use all trades.\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n return { matched: [], unmatchedCount: 0, allTrades: [] };\n }\n\n // Collect unique trade keys for market query\n const tradeKeys = uniqueTradeLookupKeys(trades);\n\n // Query market data\n const conn = await getConnection(baseDir);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n return { matched, unmatchedCount, allTrades: trades };\n}\n\n/**\n * Create numeric bucket labels from data values.\n * Divides sorted values into ~4 quartile-based ranges.\n */\nfunction createNumericBuckets(values: number[]): { label: string; min: number; max: number }[] {\n if (values.length === 0) return [];\n const sorted = [...values].sort((a, b) => a - b);\n\n const uniqueValues = [...new Set(sorted)];\n if (uniqueValues.length <= 4) {\n return uniqueValues.map((v) => ({\n label: String(Math.round(v * 100) / 100),\n min: v,\n max: v,\n }));\n }\n\n const buckets: { label: string; min: number; max: number }[] = [];\n const quartileSize = Math.ceil(sorted.length / 4);\n for (let i = 0; i < 4; i++) {\n const start = i * quartileSize;\n const end = Math.min((i + 1) * quartileSize - 1, sorted.length - 1);\n if (start > sorted.length - 1) break;\n const min = sorted[start];\n const max = sorted[end];\n const r = (n: number) => Math.round(n * 100) / 100;\n buckets.push({\n label: min === max ? `${r(min)}` : `${r(min)} to ${r(max)}`,\n min,\n max,\n });\n }\n\n return buckets;\n}\n\n/**\n * Find which bucket a value belongs to.\n */\nfunction findBucket(\n value: number,\n buckets: { label: string; min: number; max: number }[]\n): string | null {\n for (const bucket of buckets) {\n if (value >= bucket.min && value <= bucket.max) return bucket.label;\n }\n return null;\n}\n\n// =============================================================================\n// analyze_structure_fit Schema and Handler\n// =============================================================================\n\nexport const analyzeStructureFitSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per bucket for reliable stats (thin-data warning threshold)\"),\n});\n\nexport async function handleAnalyzeStructureFit(\n input: z.infer<typeof analyzeStructureFitSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Overall stats\n const allPls = matched.map((m) => m.trade.pl);\n const overall = computeSliceStats(allPls);\n\n // Dimension analysis\n const dimensions: Record<string, Record<string, SliceStats>> = {};\n\n // --- Fixed dimension: Vol_Regime ---\n const volRegimeBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val)) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!volRegimeBuckets[label]) volRegimeBuckets[label] = [];\n volRegimeBuckets[label].push(trade.pl);\n }\n const volRegimeStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(volRegimeBuckets)) {\n volRegimeStats[label] = computeSliceStats(pls);\n }\n dimensions[\"Vol_Regime\"] = volRegimeStats;\n\n // --- Fixed dimension: day_of_week ---\n const dowBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"Day_of_Week\");\n if (isNaN(val)) continue;\n const label = DAY_LABELS[val] || `day_${val}`;\n if (!dowBuckets[label]) dowBuckets[label] = [];\n dowBuckets[label].push(trade.pl);\n }\n const dowStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(dowBuckets)) {\n dowStats[label] = computeSliceStats(pls);\n }\n dimensions[\"day_of_week\"] = dowStats;\n\n // --- Fixed dimension: time_of_day ---\n const todBuckets: Record<string, number[]> = {};\n for (const { trade } of matched) {\n const bucket = getTimeBucket(trade.timeOpened);\n if (!bucket) continue;\n if (!todBuckets[bucket]) todBuckets[bucket] = [];\n todBuckets[bucket].push(trade.pl);\n }\n const todStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(todBuckets)) {\n todStats[label] = computeSliceStats(pls);\n }\n dimensions[\"time_of_day\"] = todStats;\n\n // --- Profile-derived dimensions from entry_filters (market-source only) ---\n for (const filter of profile.entryFilters.filter((f) => f.source !== \"execution\")) {\n const predicate = buildFilterPredicate(filter);\n const fieldKey = predicate.fieldKey;\n\n // Collect numeric values for this field from matched trades\n const fieldValues: { val: number; pl: number }[] = [];\n for (const { trade, market } of matched) {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) continue;\n const num = Number(raw);\n if (isNaN(num)) continue;\n fieldValues.push({ val: num, pl: trade.pl });\n }\n\n if (fieldValues.length === 0) continue;\n\n // Create buckets from the data\n const buckets = createNumericBuckets(fieldValues.map((f) => f.val));\n if (buckets.length === 0) continue;\n\n const filterBuckets: Record<string, number[]> = {};\n for (const { val, pl } of fieldValues) {\n const bucketLabel = findBucket(val, buckets);\n if (!bucketLabel) continue;\n if (!filterBuckets[bucketLabel]) filterBuckets[bucketLabel] = [];\n filterBuckets[bucketLabel].push(pl);\n }\n\n const filterStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(filterBuckets)) {\n filterStats[label] = computeSliceStats(pls);\n }\n dimensions[filter.field] = filterStats;\n }\n\n // Thin-data warnings\n for (const [dimName, bucketStats] of Object.entries(dimensions)) {\n for (const [bucketLabel, stats] of Object.entries(bucketStats)) {\n if (stats.tradeCount > 0 && stats.tradeCount < minTrades) {\n warnings.push(\n `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: { field: string; suggested: string; reason: string }[] = [];\n\n // Check Vol_Regime performance vs overall\n for (const [label, stats] of Object.entries(volRegimeStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (winRateDiff >= 20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: label,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${winRateDiff.toFixed(1)}pp above overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n if (winRateDiff <= -20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${Math.abs(winRateDiff).toFixed(1)}pp below overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check day_of_week for stark differences\n for (const [label, stats] of Object.entries(dowStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"day_of_week\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% on ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check time_of_day for stark differences\n for (const [label, stats] of Object.entries(todStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"time_of_day\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% during ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Summary text\n const dimNames = Object.keys(dimensions).join(\", \");\n const summaryText = `Structure fit analysis for '${strategyName}': ${matched.length} trades analyzed across ${Object.keys(dimensions).length} dimensions (${dimNames}). Overall win rate: ${overall.winRate.toFixed(1)}%, avg P&L: $${overall.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n overall,\n dimensions,\n profile_update_hints: profileUpdateHints,\n warnings,\n profile: {\n strategyName: profile.strategyName,\n structureType: profile.structureType,\n greeksBias: profile.greeksBias,\n thesis: profile.thesis,\n expectedRegimes: profile.expectedRegimes,\n },\n });\n}\n\n// =============================================================================\n// validate_entry_filters Schema and Handler\n// =============================================================================\n\nexport const validateEntryFiltersSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per group for reliable stats\"),\n maxAblationFilters: z\n .number()\n .optional()\n .default(8)\n .describe(\"Maximum number of filters for pairwise ablation (cap for combinatorial explosion)\"),\n});\n\nexport async function handleValidateEntryFilters(\n input: z.infer<typeof validateEntryFiltersSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n const maxAblationFilters = input.maxAblationFilters ?? 8;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Early return if no entry filters\n if (!profile.entryFilters || profile.entryFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has no entry_filters defined. Add filters via profile_strategy to enable validation.`,\n { no_filters: true }\n );\n }\n\n // Separate market-testable filters from execution-only filters\n const allFilters = profile.entryFilters;\n const marketFilters = allFilters.filter((f) => f.source !== \"execution\");\n const executionFilters = allFilters.filter((f) => f.source === \"execution\");\n\n if (marketFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has ${allFilters.length} filter(s) but all are tagged source:'execution' (platform-level). No market-data filters to validate.`,\n { no_market_filters: true, execution_filters: executionFilters }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (executionFilters.length > 0) {\n warnings.push(\n `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(\", \")}`\n );\n }\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Build predicates for market-testable filters only\n const filters = marketFilters;\n const predicates: FilterPredicate[] = filters.map((f) => buildFilterPredicate(f));\n\n // No-filters baseline: all matched trades\n const noFiltersPls = matched.map((m) => m.trade.pl);\n const noFiltersStats = computeSliceStats(noFiltersPls);\n\n // Per-filter comparison\n const perFilter: Record<\n string,\n { entered: SliceStats; filtered_out: SliceStats; no_data_count: number }\n > = {};\n\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n const predicate = predicates[i];\n const filterDesc =\n filter.description || `${filter.field} ${filter.operator} ${JSON.stringify(filter.value)}`;\n\n const enteredPls: number[] = [];\n const filteredOutPls: number[] = [];\n let noDataCount = 0;\n\n for (const { trade, market } of matched) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n noDataCount++;\n continue;\n }\n if (predicate.test(market)) {\n enteredPls.push(trade.pl);\n } else {\n filteredOutPls.push(trade.pl);\n }\n }\n\n perFilter[filterDesc] = {\n entered: computeSliceStats(enteredPls),\n filtered_out: computeSliceStats(filteredOutPls),\n no_data_count: noDataCount,\n };\n }\n\n // Ablation study\n // Baseline: all filters applied\n const baselinePls: number[] = [];\n for (const { trade, market } of matched) {\n let passesAll = true;\n let hasData = true;\n for (const predicate of predicates) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicate.test(market)) {\n passesAll = false;\n break;\n }\n }\n if (hasData && passesAll) {\n baselinePls.push(trade.pl);\n }\n }\n const baseline = computeSliceStats(baselinePls);\n\n // Single removal ablation\n const ablationSingle: Record<string, SliceStats> = {};\n for (let skip = 0; skip < filters.length; skip++) {\n const filterDesc =\n filters[skip].description ||\n `${filters[skip].field} ${filters[skip].operator} ${JSON.stringify(filters[skip].value)}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let j = 0; j < predicates.length; j++) {\n if (j === skip) continue;\n const raw = getRaw(market, predicates[j].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[j].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationSingle[filterDesc] = computeSliceStats(pls);\n }\n\n // Pairwise removal ablation (only if filter count <= maxAblationFilters)\n const ablationPairs: Record<string, SliceStats> = {};\n if (filters.length <= maxAblationFilters) {\n for (let i = 0; i < filters.length; i++) {\n for (let j = i + 1; j < filters.length; j++) {\n const descI =\n filters[i].description ||\n `${filters[i].field} ${filters[i].operator} ${JSON.stringify(filters[i].value)}`;\n const descJ =\n filters[j].description ||\n `${filters[j].field} ${filters[j].operator} ${JSON.stringify(filters[j].value)}`;\n const pairKey = `${descI} + ${descJ}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let k = 0; k < predicates.length; k++) {\n if (k === i || k === j) continue;\n const raw = getRaw(market, predicates[k].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[k].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationPairs[pairKey] = computeSliceStats(pls);\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: {\n field: string;\n action: \"remove\" | \"adjust\";\n reason: string;\n }[] = [];\n\n // Check per-filter: if entered performs worse than filtered_out, suggest removal\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (\n entered.tradeCount >= minTrades &&\n filtered_out.tradeCount >= minTrades\n ) {\n if (entered.avgPl < filtered_out.avgPl && filtered_out.avgPl > 0) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Entered avg P&L ($${entered.avgPl.toFixed(2)}) worse than filtered-out ($${filtered_out.avgPl.toFixed(2)}) — filter may be counterproductive`,\n });\n }\n }\n }\n\n // Check ablation: if removing a filter improves over baseline\n for (const [filterDesc, stats] of Object.entries(ablationSingle)) {\n if (stats.tradeCount >= minTrades && baseline.tradeCount >= minTrades) {\n if (stats.avgPl > baseline.avgPl && stats.winRate > baseline.winRate) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Removing this filter improves avg P&L ($${stats.avgPl.toFixed(2)} vs $${baseline.avgPl.toFixed(2)}) and win rate (${stats.winRate.toFixed(1)}% vs ${baseline.winRate.toFixed(1)}%)`,\n });\n }\n }\n }\n\n // Thin-data warnings\n if (baseline.tradeCount > 0 && baseline.tradeCount < minTrades) {\n warnings.push(\n `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (entered.tradeCount > 0 && entered.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n if (filtered_out.tradeCount > 0 && filtered_out.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n\n // Summary text\n const execNote = executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : \"\";\n const summaryText = `Filter validation for '${strategyName}': ${filters.length} market filter(s) analyzed across ${matched.length} trades${execNote}. Baseline (all market filters): ${baseline.tradeCount} trades, win rate ${baseline.winRate.toFixed(1)}%, avg P&L $${baseline.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n baseline,\n no_filters: noFiltersStats,\n per_filter: perFilter,\n ablation: {\n single: ablationSingle,\n pairs: ablationPairs,\n },\n execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),\n profile_update_hints: profileUpdateHints,\n warnings,\n });\n}\n\n// =============================================================================\n// portfolio_structure_map Schema and Handler\n// =============================================================================\n\nexport const portfolioStructureMapSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\"Block ID to analyze. When omitted, aggregate across all blocks.\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Thin-data warning threshold (default: 10)\"),\n});\n\nexport async function handlePortfolioStructureMap(\n input: z.infer<typeof portfolioStructureMapSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput> | { content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n const { blockId, minTrades } = portfolioStructureMapSchema.parse(input);\n const conn = await getConnection(baseDir);\n\n // Load profiles\n const profiles = await listProfiles(conn, blockId);\n if (profiles.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: blockId\n ? `No strategy profiles found for block '${blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n },\n ],\n };\n }\n\n // Collect all trades per strategy, matched to market data\n interface StrategyTradeMarket {\n strategyName: string;\n trade: Trade;\n market: Record<string, unknown>;\n }\n\n const allTradeMarkets: StrategyTradeMarket[] = [];\n const warnings: string[] = [];\n\n for (const profile of profiles) {\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback (see loadTradesAndMarket)\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n if (trades.length === 0) {\n warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);\n continue;\n }\n\n // Query market data for trade dates\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const dailyResult = await conn.runAndReadAll(sql, params);\n const dailyRecords = resultToRecords(dailyResult);\n const daily = recordsByTickerDate(dailyRecords);\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const marketKey = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = daily.get(marketKey);\n if (market) {\n allTradeMarkets.push({\n strategyName: profile.strategyName,\n trade,\n market,\n });\n }\n }\n }\n\n if (allTradeMarkets.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No trades could be matched to market data. Ensure market data is imported and enriched.\",\n },\n ],\n };\n }\n\n // Build the 18-cell matrix: Vol_Regime (6) x Trend_Direction (3)\n // Use prev_ prefix for both fields (both are close-derived, need LAG)\n const strategyNames = [...new Set(allTradeMarkets.map((t) => t.strategyName))];\n\n // Collect PLs per cell per strategy\n type CellKey = string; // \"regime:trend\"\n const cellPls = new Map<CellKey, Map<string, number[]>>();\n\n let unknownTrendCount = 0;\n const unknownTrendPls = new Map<string, number[]>(); // strategy -> pls for unknown trend\n\n for (const { strategyName, trade, market } of allTradeMarkets) {\n const volRegime = getNum(market, \"prev_Vol_Regime\");\n const trendRaw = market[\"prev_Trend_Direction\"];\n\n // Handle missing Vol_Regime\n if (isNaN(volRegime) || volRegime < 1 || volRegime > 6) continue;\n\n // Handle missing Trend_Direction\n let trend: TrendLabel | null = null;\n if (\n trendRaw === null ||\n trendRaw === undefined ||\n trendRaw === \"\"\n ) {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n const trendStr = String(trendRaw).toLowerCase();\n if (trendStr === \"up\" || trendStr === \"down\" || trendStr === \"flat\") {\n trend = trendStr as TrendLabel;\n } else {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n\n const regimeLabel = VOL_REGIME_LABELS[volRegime] || `regime_${volRegime}`;\n const cellKey = `${regimeLabel}:${trend}`;\n\n if (!cellPls.has(cellKey)) {\n cellPls.set(cellKey, new Map());\n }\n const cellMap = cellPls.get(cellKey)!;\n if (!cellMap.has(strategyName)) {\n cellMap.set(strategyName, []);\n }\n cellMap.get(strategyName)!.push(trade.pl);\n }\n\n // Build matrix output\n const matrix: Record<string, Record<string, Record<string, SliceStats>>> = {};\n const overlaps: Array<{\n regime: string;\n trend: string;\n strategies: string[];\n totalTrades: number;\n }> = [];\n const blindSpots: Array<{ regime: string; trend: string }> = [];\n let coveredCells = 0;\n let overlapCells = 0;\n\n for (const [, regimeLabel] of Object.entries(VOL_REGIME_LABELS)) {\n matrix[regimeLabel] = {};\n for (const trend of TREND_LABELS) {\n const cellKey = `${regimeLabel}:${trend}`;\n const cellMap = cellPls.get(cellKey);\n\n if (!cellMap || cellMap.size === 0) {\n blindSpots.push({ regime: regimeLabel, trend });\n matrix[regimeLabel][trend] = {};\n continue;\n }\n\n coveredCells++;\n const cellStats: Record<string, SliceStats> = {};\n const strategiesInCell: string[] = [];\n let totalTradesInCell = 0;\n\n for (const [stratName, pls] of cellMap) {\n cellStats[stratName] = computeSliceStats(pls);\n strategiesInCell.push(stratName);\n totalTradesInCell += pls.length;\n\n // Thin-data warning\n if (pls.length > 0 && pls.length < minTrades) {\n warnings.push(\n `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`\n );\n }\n }\n\n matrix[regimeLabel][trend] = cellStats;\n\n // Overlap detection: 2+ strategies in same cell\n if (strategiesInCell.length >= 2) {\n overlapCells++;\n overlaps.push({\n regime: regimeLabel,\n trend,\n strategies: strategiesInCell,\n totalTrades: totalTradesInCell,\n });\n }\n }\n }\n\n const blindSpotCells = blindSpots.length;\n\n // Handle unknown trend trades\n if (unknownTrendCount > 0) {\n warnings.push(\n `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`\n );\n }\n\n // Build unknown trend stats if any\n const unknownTrendStats: Record<string, SliceStats> | undefined =\n unknownTrendPls.size > 0\n ? Object.fromEntries(\n [...unknownTrendPls.entries()].map(([name, pls]) => [\n name,\n computeSliceStats(pls),\n ])\n )\n : undefined;\n\n const coverageSummary = {\n totalCells: 18,\n coveredCells,\n blindSpotCells,\n overlapCells,\n };\n\n const summary = `Portfolio structure map: ${strategyNames.length} strategies | ${coveredCells}/18 cells covered | ${overlapCells} overlaps | ${blindSpotCells} blind spots`;\n\n const structuredData: Record<string, unknown> = {\n strategies: strategyNames,\n matrix,\n overlaps,\n blind_spots: blindSpots,\n coverage_summary: coverageSummary,\n warnings,\n };\n\n if (unknownTrendStats) {\n structuredData.unknown_trend = unknownTrendStats;\n }\n\n return createToolOutput(summary, structuredData);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error building portfolio structure map: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\n/**\n * Register all profile analysis tools.\n * This includes portfolio_structure_map (from Plan 03) and\n * analyze_structure_fit + validate_entry_filters (from Plan 02, if present).\n */\nexport function registerProfileAnalysisTools(\n server: McpServer,\n baseDir: string\n): void {\n // portfolio_structure_map: optional blockId means we can't always use withSyncedBlock.\n // When blockId is provided, sync that block. When omitted, sync all blocks.\n server.registerTool(\n \"portfolio_structure_map\",\n {\n description:\n \"Build a Vol_Regime x Trend_Direction matrix (18 cells) across all profiled strategies. \" +\n \"Shows per-strategy stats in each cell, detects overlap (2+ strategies in same cell), \" +\n \"blind spots (cells with zero trades), and thin-data warnings. \" +\n \"Optionally filter to a single block or aggregate across all blocks.\",\n inputSchema: portfolioStructureMapSchema,\n },\n async (input) => {\n // Manual sync: if blockId provided, sync just that block; otherwise sync all\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.js\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n return handlePortfolioStructureMap(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: analyze_structure_fit\n // -------------------------------------------------------------------------\n server.registerTool(\n \"analyze_structure_fit\",\n {\n description:\n \"Analyze how well a strategy fits various market dimensions using its stored profile. \" +\n \"Returns performance breakdown by Vol_Regime, day-of-week, time-of-day, and profile-derived \" +\n \"dimensions from entry_filters. Includes profile_update_hints when data shows clear patterns \" +\n \"diverging from profile, and thin-data warnings for small buckets.\",\n inputSchema: analyzeStructureFitSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleAnalyzeStructureFit(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: validate_entry_filters\n // -------------------------------------------------------------------------\n server.registerTool(\n \"validate_entry_filters\",\n {\n description:\n \"Validate effectiveness of a strategy's entry filters. Splits trades into entered vs \" +\n \"filtered-out groups per filter and shows full stat suite for both. Runs ablation study \" +\n \"removing one filter at a time and testing all pairs. Returns profile_update_hints when \" +\n \"filters appear counterproductive.\",\n inputSchema: validateEntryFiltersSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleValidateEntryFilters(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Regime Allocation Advisor Tool\n *\n * Cross-references strategy profiles' expected regimes with actual trading\n * performance per regime. Surfaces thesis violations and hidden edges as\n * structured data without prescriptive recommendations.\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.js\";\nimport { listProfiles } from \"../db/profile-schemas.js\";\nimport { filterByStrategy } from \"./shared/filters.js\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.js\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.js\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.js\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.js\";\nimport { syncAllBlocks } from \"../sync/index.js\";\n\n// =============================================================================\n// Utility Functions (local to this module, copied from profile-analysis.ts)\n// =============================================================================\n\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface RegimeCell {\n stats: SliceStats;\n isExpected: boolean;\n classification: \"thesis_aligned\" | \"thesis_violation\" | \"hidden_edge\" | \"neutral\";\n}\n\ninterface StrategyRegimeComparison {\n strategyName: string;\n blockId: string;\n structureType: string;\n underlying?: string;\n allocationPct?: number;\n expectedRegimes: string[];\n regimePerformance: Record<string, RegimeCell>;\n tradeCount: number;\n matchedToMarket: number;\n unmatchedCount: number;\n}\n\n// =============================================================================\n// Schema\n// =============================================================================\n\nexport const regimeAllocationAdvisorSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\n \"Block ID to analyze. When omitted, aggregate across all profiled strategies.\"\n ),\n minTrades: z\n .number()\n .optional()\n .default(5)\n .describe(\n \"Minimum trades per regime cell for reliable stats (default: 5)\"\n ),\n});\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport async function handleRegimeAllocationAdvisor(\n input: z.infer<typeof regimeAllocationAdvisorSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const minTrades = input.minTrades ?? 5;\n const warnings: string[] = [];\n const profileUpgradeHints: string[] = [];\n\n // Load all profiles, optionally filtered by blockId\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId);\n\n if (profiles.length === 0) {\n return createToolOutput(\n input.blockId\n ? `No strategy profiles found for block '${input.blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n { error: \"no_profiles\" }\n );\n }\n\n const strategies: StrategyRegimeComparison[] = [];\n let skippedNoRegimes = 0;\n let skippedNoMarket = 0;\n const allThesisViolations: {\n strategyName: string;\n regime: string;\n winRate: number;\n expectedWinRate: number;\n }[] = [];\n const allHiddenEdges: {\n strategyName: string;\n regime: string;\n winRate: number;\n overallWinRate: number;\n }[] = [];\n\n // Per-regime aggregation across all strategies\n const regimeAggPls: Record<string, number[]> = {};\n\n for (const profile of profiles) {\n try {\n // Skip profiles without expectedRegimes\n if (!profile.expectedRegimes || profile.expectedRegimes.length === 0) {\n skippedNoRegimes++;\n profileUpgradeHints.push(\n `Strategy '${profile.strategyName}' (block: ${profile.blockId}) has no expectedRegimes. Add via profile_strategy.`\n );\n continue;\n }\n\n // Load trades\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(\n `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'. Skipped.`\n );\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n warnings.push(\n `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'. Skipped.`\n );\n continue;\n }\n\n // Query market data\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n interface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n }\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n if (matched.length === 0) {\n skippedNoMarket++;\n warnings.push(\n `No market data matched for strategy '${profile.strategyName}' (${trades.length} trades). Import and enrich market data first.`\n );\n continue;\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `Strategy '${profile.strategyName}': ${unmatchedCount} of ${trades.length} trades had no market data match.`\n );\n }\n\n // Compute overall stats for this strategy\n const allPls = matched.map((m) => m.trade.pl);\n const overallStats = computeSliceStats(allPls);\n\n // Group trades by Vol_Regime\n const regimePls: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val) || val < 1 || val > 6) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!regimePls[label]) regimePls[label] = [];\n regimePls[label].push(trade.pl);\n\n // Aggregate across strategies\n if (!regimeAggPls[label]) regimeAggPls[label] = [];\n regimeAggPls[label].push(trade.pl);\n }\n\n // Build per-regime comparison\n const expectedSet = new Set(\n profile.expectedRegimes.map((r) => r.toLowerCase())\n );\n const regimePerformance: Record<string, RegimeCell> = {};\n\n for (const [label, pls] of Object.entries(regimePls)) {\n const stats = computeSliceStats(pls);\n const isExpected = expectedSet.has(label.toLowerCase());\n\n // Classification logic:\n // thesis_aligned: isExpected AND performing reasonably (WR > 50% or > overall WR)\n // thesis_violation: isExpected AND WR significantly below overall (>10pp)\n // hidden_edge: NOT isExpected AND WR significantly above overall (>10pp) AND enough trades\n // neutral: everything else\n let classification: RegimeCell[\"classification\"];\n const wrDelta = stats.winRate - overallStats.winRate;\n\n if (isExpected) {\n if (wrDelta < -10) {\n classification = \"thesis_violation\";\n allThesisViolations.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n expectedWinRate: overallStats.winRate,\n });\n } else {\n classification = \"thesis_aligned\";\n }\n } else {\n if (wrDelta > 10 && stats.tradeCount >= minTrades) {\n classification = \"hidden_edge\";\n allHiddenEdges.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n overallWinRate: overallStats.winRate,\n });\n } else {\n classification = \"neutral\";\n }\n }\n\n regimePerformance[label] = { stats, isExpected, classification };\n }\n\n // Allocation from position sizing\n const allocationPct =\n profile.positionSizing?.liveAllocationPct ??\n profile.positionSizing?.allocationPct;\n\n strategies.push({\n strategyName: profile.strategyName,\n blockId: profile.blockId,\n structureType: profile.structureType,\n underlying: profile.underlying ?? undefined,\n allocationPct,\n expectedRegimes: profile.expectedRegimes,\n regimePerformance,\n tradeCount: trades.length,\n matchedToMarket: matched.length,\n unmatchedCount,\n });\n } catch (err) {\n warnings.push(\n `Error processing strategy '${profile.strategyName}' (block: ${profile.blockId}): ${(err as Error).message}`\n );\n }\n }\n\n // Build regime overview (aggregate stats per regime)\n const regimeOverview: Record<\n string,\n { strategiesActive: number; totalTrades: number; combinedStats: SliceStats }\n > = {};\n\n for (const [label, pls] of Object.entries(regimeAggPls)) {\n // Count how many strategies had trades in this regime\n let strategiesActive = 0;\n for (const strategy of strategies) {\n if (strategy.regimePerformance[label]) {\n strategiesActive++;\n }\n }\n regimeOverview[label] = {\n strategiesActive,\n totalTrades: pls.length,\n combinedStats: computeSliceStats(pls),\n };\n }\n\n const summary = {\n totalStrategies: profiles.length,\n profiled: strategies.length,\n skippedNoRegimes,\n skippedNoMarket,\n thesisViolations: allThesisViolations,\n hiddenEdges: allHiddenEdges,\n };\n\n const summaryText =\n `Regime allocation advisor: ${strategies.length}/${profiles.length} strategies analyzed. ` +\n `${allThesisViolations.length} thesis violation(s), ${allHiddenEdges.length} hidden edge(s). ` +\n (skippedNoRegimes > 0\n ? `${skippedNoRegimes} skipped (no expectedRegimes). `\n : \"\") +\n (skippedNoMarket > 0\n ? `${skippedNoMarket} skipped (no market data). `\n : \"\");\n\n return createToolOutput(summaryText, {\n strategies,\n summary,\n regimeOverview,\n warnings,\n profileUpgradeHints,\n });\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\nexport function registerRegimeAdvisorTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"regime_allocation_advisor\",\n {\n description:\n \"Cross-reference strategy profiles' expected regimes with actual trading performance. \" +\n \"Shows per-strategy, per-regime comparison with win rate, P&L, and trade count. \" +\n \"Classifications (thesis_aligned, thesis_violation, hidden_edge) emerge from data delta. \" +\n \"Optionally filter to a single block or aggregate across all profiled strategies.\",\n inputSchema: regimeAllocationAdvisorSchema,\n },\n async (input) => {\n // Manual sync pattern (same as portfolio_structure_map)\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.js\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n return handleRegimeAllocationAdvisor(input, baseDir);\n }\n );\n}\n","/**\n * Trade Replay Pure Logic Module\n *\n * OCC ticker construction, tradelog legs string parsing, multi-leg P&L path\n * computation with HL2 mark pricing, and MFE/MAE calculation.\n *\n * All functions are pure — no fetch, no DuckDB.\n */\n\nimport type { BarRow } from './market-provider.js';\nimport { computeLegGreeks, type GreeksResult } from './black-scholes.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A parsed leg from a tradelog \"legs\" string (before OCC ticker resolution). */\nexport interface ParsedLeg {\n root: string; // \"SPY\", \"SPX\", \"SPXW\"\n strike: number; // Numeric strike price\n type: 'C' | 'P'; // Call or Put\n quantity: number; // +1 or -1 (direction derived from position in spread)\n}\n\n/** A fully resolved leg ready for replay (after OCC ticker construction). */\nexport interface ReplayLeg {\n occTicker: string; // Full OCC ticker for Massive API fetch\n quantity: number; // Positive = long, negative = short\n entryPrice: number; // Per-contract entry price\n multiplier: number; // 100 for standard equity/index options\n}\n\n/** A single point on the strategy P&L path. */\nexport interface PnlPoint {\n timestamp: string; // \"YYYY-MM-DD HH:MM\" ET\n strategyPnl: number; // Combined P&L across all legs at this minute\n legPrices: number[]; // Mark price for each leg at this minute (bid/ask mid or HL2 fallback)\n // Per-leg greeks (Phase 69) — array parallel to legPrices\n legGreeks?: GreeksResult[];\n // Net position greeks — quantity-weighted aggregation across legs\n netDelta?: number | null;\n netGamma?: number | null;\n netTheta?: number | null;\n netVega?: number | null;\n // IVP from market.context\n ivp?: number | null;\n}\n\n/** Configuration for greeks computation in P&L path. */\nexport interface GreeksConfig {\n underlyingPrices: Map<string, number>; // timestamp -> underlying price\n legs: Array<{ strike: number; type: 'C' | 'P'; expiryDate: string }>; // per-leg BS inputs\n riskFreeRate: number; // e.g. 0.045\n dividendYield: number; // e.g. 0.015 for SPX, 0 otherwise\n ivpByDate?: Map<string, number>; // date -> IVP value\n /** Sorted intraday timestamps from underlyingPrices for nearest-timestamp binary search. */\n sortedTimestamps?: string[];\n}\n\n/** Complete replay result with P&L path, MFE/MAE, and metadata. */\nexport interface ReplayResult {\n pnlPath: PnlPoint[];\n mfe: number; // Max of strategyPnl series\n mae: number; // Min of strategyPnl series\n mfeTimestamp: string; // When MFE occurred\n maeTimestamp: string; // When MAE occurred\n totalPnl: number; // Final P&L at last bar\n totalBars?: number; // Total minute bars before format filtering\n legs: ReplayLeg[]; // The legs that were replayed\n greeksWarning?: string | null; // D-12: warning when >50% of leg-timestamps have null greeks\n}\n\n// ---------------------------------------------------------------------------\n// markPrice\n// ---------------------------------------------------------------------------\n\n/**\n * Prefer (bid+ask)/2 when both are present and non-zero; fall back to HL2.\n *\n * When providers supply bid/ask data (e.g., option chains), the midpoint is a\n * more accurate mark price than HL2. This is opt-in — existing data without\n * bid/ask continues to use HL2 identically.\n */\nexport function markPrice(bar: Pick<BarRow, 'high' | 'low' | 'bid' | 'ask'>): number {\n if (bar.bid != null && bar.ask != null && (bar.bid > 0 || bar.ask > 0)) {\n return (bar.bid + bar.ask) / 2;\n }\n return (bar.high + bar.low) / 2;\n}\n\n// ---------------------------------------------------------------------------\n// findNearestTimestamp\n// ---------------------------------------------------------------------------\n\n/**\n * Find the nearest timestamp in a sorted array within tolerance (seconds).\n * Uses binary search for O(log n) performance.\n *\n * Timestamps are compared by minutes-since-midnight (HH:MM format).\n * Returns undefined if no timestamp is within the tolerance.\n *\n * Per D-07/D-08: Tolerates up to 60s mismatch between option and underlying bars.\n */\nexport function findNearestTimestamp(\n sortedTimestamps: string[],\n target: string,\n toleranceSec: number = 60,\n): string | undefined {\n if (sortedTimestamps.length === 0) return undefined;\n\n const targetMin = timestampToMinutes(target);\n if (targetMin === null) return undefined;\n\n let lo = 0, hi = sortedTimestamps.length - 1;\n let bestIdx = 0;\n let bestDiff = Infinity;\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const midMin = timestampToMinutes(sortedTimestamps[mid]);\n if (midMin === null) { lo = mid + 1; continue; }\n\n const diff = Math.abs(midMin - targetMin);\n if (diff < bestDiff) { bestDiff = diff; bestIdx = mid; }\n if (midMin < targetMin) lo = mid + 1;\n else if (midMin > targetMin) hi = mid - 1;\n else break; // exact match\n }\n\n // bestDiff is in minutes; convert tolerance from seconds\n return bestDiff <= toleranceSec / 60 ? sortedTimestamps[bestIdx] : undefined;\n}\n\nfunction timestampToMinutes(ts: string): number | null {\n const timePart = ts.split(' ')[1];\n if (!timePart) return null;\n const [h, m] = timePart.split(':').map(Number);\n if (isNaN(h) || isNaN(m)) return null;\n return h * 60 + m;\n}\n\n// ---------------------------------------------------------------------------\n// parseLegsString\n// ---------------------------------------------------------------------------\n\n// Compact format with root: \"SPY 470C\", \"SPX 4500P\", \"SPY 0.50C\"\nconst COMPACT_LEG_RE = /^([A-Z]+)\\s+(\\d+(?:\\.\\d+)?)\\s*(C|P)$/i;\n\n// Compact format without root (subsequent legs in spreads): \"465C\", \"500C\"\nconst COMPACT_NO_ROOT_RE = /^(\\d+(?:\\.\\d+)?)\\s*(C|P)$/i;\n\n// Verbose format: \"SPY Jan25 470 Call\", \"SPX Feb25 4500 Put\"\nconst VERBOSE_LEG_RE = /^([A-Z]+)\\s+\\w+\\s+(\\d+(?:\\.\\d+)?)\\s+(Call|Put)$/i;\n\n// Option Omega format: \"{contracts} {Mon} {day} {strike} {P|C} {STO|BTO} {price}\"\n// Example: \"397 Mar 12 6610 P STO 35.85\"\n// Captures: (1)contracts (2)month (3)day (4)strike (5)C|P (6)STO|BTO (7)price\nconst OO_LEG_RE = /^(\\d+)\\s+(\\w+)\\s+(\\d+)\\s+(\\d+(?:\\.\\d+)?)\\s+(C|P)\\s+(STO|BTO|STC|BTC)\\s+(\\d+(?:\\.\\d+)?)$/i;\n\n/** Extended parsed leg with entry price from OO format. */\nexport interface ParsedLegOO extends ParsedLeg {\n entryPrice?: number; // Fill price from OO leg (e.g., 35.85)\n contracts?: number; // Contract count from OO leg\n expiryHint?: string; // \"Mon DD\" from OO format (e.g., \"Mar 12\") for multi-expiry strategies\n}\n\n/**\n * Parse a tradelog \"legs\" string into structured ParsedLeg objects.\n *\n * Supported formats:\n * - \"SPY 470C\" (single leg)\n * - \"SPY 470C/465C\" (two-leg spread, \"/\" delimiter)\n * - \"SPY 490C/500C/510C\" (butterfly)\n * - \"SPY Jan25 470 Call\" (verbose format)\n * - Option Omega pipe-delimited format:\n * \"397 Mar 12 6610 P STO 35.85 | 397 Mar 12 6925 C STO 10.90 | ...\"\n * Direction: STO = short (-1), BTO = long (+1)\n * Includes per-leg entry price and contract count\n *\n * @throws Error if legs string is empty or cannot be parsed\n */\nexport function parseLegsString(legsStr: string): ParsedLegOO[] {\n if (!legsStr || legsStr.trim() === '') {\n throw new Error('Cannot parse legs \"\" — use hypothetical mode with explicit strikes');\n }\n\n // Detect Option Omega pipe-delimited format\n if (legsStr.includes('|')) {\n return parseOOLegs(legsStr);\n }\n\n const parts = legsStr.includes('/') ? legsStr.split('/') : [legsStr];\n const legs: ParsedLegOO[] = [];\n let inheritedRoot = '';\n\n for (let i = 0; i < parts.length; i++) {\n const raw = parts[i].trim();\n let root: string;\n let strike: number;\n let type: 'C' | 'P';\n\n const compactMatch = raw.match(COMPACT_LEG_RE);\n if (compactMatch) {\n root = compactMatch[1].toUpperCase();\n strike = parseFloat(compactMatch[2]);\n type = compactMatch[3].toUpperCase() as 'C' | 'P';\n } else {\n // Try compact without root (e.g., \"465C\" in \"SPY 470C/465C\")\n const noRootMatch = raw.match(COMPACT_NO_ROOT_RE);\n if (noRootMatch && inheritedRoot) {\n root = inheritedRoot;\n strike = parseFloat(noRootMatch[1]);\n type = noRootMatch[2].toUpperCase() as 'C' | 'P';\n } else {\n const verboseMatch = raw.match(VERBOSE_LEG_RE);\n if (verboseMatch) {\n root = verboseMatch[1].toUpperCase();\n strike = parseFloat(verboseMatch[2]);\n type = verboseMatch[3].toLowerCase() === 'call' ? 'C' : 'P';\n } else {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" — use hypothetical mode with explicit strikes`\n );\n }\n }\n }\n\n // Propagate root to subsequent legs that may omit it\n if (i === 0) inheritedRoot = root;\n\n // First leg is bought (+1), subsequent alternate -1, +1, -1...\n const quantity = i === 0 ? 1 : (i % 2 === 0 ? 1 : -1);\n\n legs.push({ root, strike, type, quantity });\n }\n\n return legs;\n}\n\n/**\n * Parse Option Omega pipe-delimited legs format.\n *\n * Each segment: \"{contracts} {Mon} {day} {strike} {P|C} {STO|BTO} {price}\"\n * STO = sell-to-open (short, quantity = -1), BTO = buy-to-open (long, quantity = +1)\n *\n * Dedup key includes date+strike+type to handle:\n * - Calendar spreads: same strike, different expiry (both kept)\n * - Open+close fills: same strike, same date, opposite direction (close dropped)\n */\nfunction parseOOLegs(legsStr: string): ParsedLegOO[] {\n const segments = legsStr.split('|').map(s => s.trim());\n const legs: ParsedLegOO[] = [];\n const seen = new Set<string>();\n\n for (const seg of segments) {\n const match = seg.match(OO_LEG_RE);\n if (!match) {\n throw new Error(\n `Cannot parse OO leg segment \"${seg}\" — use hypothetical mode with explicit strikes`\n );\n }\n\n const contracts = parseInt(match[1], 10);\n const month = match[2];\n const day = match[3];\n const strike = parseFloat(match[4]);\n const type = match[5].toUpperCase() as 'C' | 'P';\n const direction = match[6].toUpperCase();\n const price = parseFloat(match[7]);\n\n // Dedup by date+strike+type: keeps calendar legs (different dates),\n // drops close fills (same date+strike+type, opposite direction)\n const key = `${month}${day}:${strike}${type}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n legs.push({\n root: '', // OO format doesn't include root — caller provides via trade's ticker field\n strike,\n type,\n quantity: direction === 'BTO' ? 1 : -1,\n entryPrice: price,\n contracts,\n expiryHint: `${month} ${day}`,\n });\n }\n\n return legs;\n}\n\n// ---------------------------------------------------------------------------\n// buildOccTicker\n// ---------------------------------------------------------------------------\n\n/**\n * Build an OCC-format option ticker from components.\n *\n * Format: {root}{YYMMDD}{C|P}{strike*1000 padded to 8 digits}\n *\n * Example: SPY, 2025-01-17, C, 470 -> \"SPY250117C00470000\"\n */\nexport function buildOccTicker(\n root: string,\n expiry: string,\n type: 'C' | 'P',\n strike: number,\n): string {\n // Extract YYMMDD from \"YYYY-MM-DD\"\n const [yyyy, mm, dd] = expiry.split('-');\n const yy = yyyy.slice(2);\n\n // Strike * 1000 padded to 8 digits\n const strikeInt = Math.round(strike * 1000);\n const strikePadded = String(strikeInt).padStart(8, '0');\n\n return `${root}${yy}${mm}${dd}${type}${strikePadded}`;\n}\n\n// ---------------------------------------------------------------------------\n// computeStrategyPnlPath\n// ---------------------------------------------------------------------------\n\n/**\n * Combine per-leg minute bars into a single strategy P&L path.\n *\n * Mark price at each minute = (bid+ask)/2 when available, else HL2 = (high + low) / 2.\n * Combined P&L = sum across legs of (currentMark - entryPrice) * quantity * multiplier.\n *\n * Only includes timestamps where ALL legs have a bar.\n * Returns empty array if any leg has no bars.\n */\nexport function computeStrategyPnlPath(\n legs: ReplayLeg[],\n barsByLeg: BarRow[][],\n greeksConfig?: GreeksConfig,\n): PnlPoint[] {\n if (legs.length === 0 || barsByLeg.length === 0) return [];\n\n // Check if any leg has no bars\n for (const bars of barsByLeg) {\n if (bars.length === 0) return [];\n }\n\n // Build maps of timestamp -> bar for each leg\n const legMaps: Map<string, BarRow>[] = barsByLeg.map((bars) => {\n const map = new Map<string, BarRow>();\n for (const bar of bars) {\n const ts = `${bar.date} ${bar.time ?? ''}`.trim();\n map.set(ts, bar);\n }\n return map;\n });\n\n // Collect ALL unique timestamps across ALL legs (union, not intersection)\n const allTimestamps = new Set<string>();\n for (const bars of barsByLeg) {\n for (const bar of bars) {\n allTimestamps.add(`${bar.date} ${bar.time ?? ''}`.trim());\n }\n }\n const sortedTimestamps = [...allTimestamps].sort();\n\n // Build P&L path with forward-fill for missing bars\n const path: PnlPoint[] = [];\n const lastBar: (BarRow | undefined)[] = new Array(legs.length).fill(undefined);\n\n\n for (const ts of sortedTimestamps) {\n let complete = true;\n const legPrices: number[] = [];\n let strategyPnl = 0;\n\n for (let i = 0; i < legs.length; i++) {\n const bar = legMaps[i].get(ts);\n if (bar) {\n lastBar[i] = bar;\n }\n const effective = bar ?? lastBar[i];\n if (!effective) {\n complete = false;\n break;\n }\n const hl2 = markPrice(effective);\n legPrices.push(hl2);\n strategyPnl += (hl2 - legs[i].entryPrice) * legs[i].quantity * legs[i].multiplier;\n }\n\n if (complete) {\n const point: PnlPoint = { timestamp: ts, strategyPnl, legPrices };\n\n // Compute greeks if config provided\n if (greeksConfig) {\n // Look up underlying price — try exact timestamp, then nearest within 60s, then date-only\n let underlyingPrice = greeksConfig.underlyingPrices.get(ts);\n if (underlyingPrice === undefined && greeksConfig.sortedTimestamps) {\n const nearest = findNearestTimestamp(greeksConfig.sortedTimestamps, ts, 60);\n if (nearest) underlyingPrice = greeksConfig.underlyingPrices.get(nearest);\n }\n if (underlyingPrice === undefined) {\n const dateOnly = ts.split(' ')[0];\n underlyingPrice = greeksConfig.underlyingPrices.get(dateOnly);\n }\n\n if (underlyingPrice !== undefined) {\n const legGreeksArr: GreeksResult[] = [];\n let netDelta = 0, netGamma = 0, netTheta = 0, netVega = 0;\n let allNull = true;\n\n for (let j = 0; j < legs.length; j++) {\n const legCfg = greeksConfig.legs[j];\n if (!legCfg || !legCfg.expiryDate) {\n legGreeksArr.push({ delta: null, gamma: null, theta: null, vega: null, iv: null });\n continue;\n }\n\n // Compute fractional DTE from bar timestamp to leg expiry\n const dateStr = ts.split(' ')[0];\n const timePart = ts.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = legCfg.expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n const dte = (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n\n if (dte <= 0) {\n legGreeksArr.push({ delta: null, gamma: null, theta: null, vega: null, iv: null });\n continue;\n }\n\n const g = computeLegGreeks(\n legPrices[j],\n underlyingPrice,\n legCfg.strike,\n dte,\n legCfg.type,\n greeksConfig.riskFreeRate,\n greeksConfig.dividendYield,\n );\n legGreeksArr.push(g);\n\n if (g.delta !== null) {\n allNull = false;\n const weight = legs[j].quantity * legs[j].multiplier / 100;\n netDelta += g.delta * weight;\n netGamma += g.gamma! * weight;\n netTheta += g.theta! * weight;\n netVega += g.vega! * weight;\n }\n }\n\n point.legGreeks = legGreeksArr;\n point.netDelta = allNull ? null : netDelta;\n point.netGamma = allNull ? null : netGamma;\n point.netTheta = allNull ? null : netTheta;\n point.netVega = allNull ? null : netVega;\n\n\n // IVP lookup by date\n const ivpDate = ts.split(' ')[0];\n point.ivp = greeksConfig.ivpByDate?.get(ivpDate) ?? null;\n }\n }\n\n path.push(point);\n }\n }\n\n return path;\n}\n\n// ---------------------------------------------------------------------------\n// computeReplayMfeMae\n// ---------------------------------------------------------------------------\n\n/**\n * Compute MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion)\n * from a P&L path.\n *\n * MFE = max of strategyPnl series\n * MAE = min of strategyPnl series\n */\nexport function computeReplayMfeMae(pnlPath: PnlPoint[]): {\n mfe: number;\n mae: number;\n mfeTimestamp: string;\n maeTimestamp: string;\n} {\n if (pnlPath.length === 0) {\n return { mfe: 0, mae: 0, mfeTimestamp: '', maeTimestamp: '' };\n }\n\n let mfe = pnlPath[0].strategyPnl;\n let mae = pnlPath[0].strategyPnl;\n let mfeTimestamp = pnlPath[0].timestamp;\n let maeTimestamp = pnlPath[0].timestamp;\n\n for (let i = 1; i < pnlPath.length; i++) {\n const pnl = pnlPath[i].strategyPnl;\n if (pnl > mfe) {\n mfe = pnl;\n mfeTimestamp = pnlPath[i].timestamp;\n }\n if (pnl < mae) {\n mae = pnl;\n maeTimestamp = pnlPath[i].timestamp;\n }\n }\n\n return { mfe, mae, mfeTimestamp, maeTimestamp };\n}\n","/**\n * Trade Replay Tools\n *\n * MCP tool for replaying trades using historical minute-level option bars\n * from Massive.com. Supports two modes:\n * A) Hypothetical replay — explicit legs with strikes/expiry/dates\n * B) Tradelog replay — block_id + trade_index to replay from existing trade data\n *\n * Tools registered:\n * - replay_trade — Replay a trade and compute minute-by-minute P&L path with MFE/MAE\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { fetchBarsWithCache } from \"../utils/bar-cache.js\";\nimport {\n parseLegsString,\n buildOccTicker,\n computeStrategyPnlPath,\n computeReplayMfeMae,\n markPrice,\n type ReplayLeg,\n type ReplayResult,\n type GreeksConfig,\n} from \"../utils/trade-replay.js\";\nimport type { BarRow } from \"../utils/market-provider.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const replayTradeSchema = z.object({\n // Mode A: Hypothetical / explicit legs\n legs: z\n .array(\n z.object({\n ticker: z.string().describe(\"Underlying ticker, e.g., 'SPY', 'SPX'\"),\n strike: z.number().describe(\"Strike price\"),\n type: z.enum([\"C\", \"P\"]).describe(\"Call or Put\"),\n expiry: z.string().describe(\"Expiration date YYYY-MM-DD\"),\n quantity: z.number().describe(\"Positive = long, negative = short\"),\n entry_price: z\n .number()\n .describe(\"Per-contract entry price (premium paid/received)\"),\n })\n )\n .optional()\n .describe(\"Explicit leg definitions for hypothetical replay\"),\n\n // Mode B: Tradelog replay\n block_id: z.string().optional().describe(\"Block ID to load trade from\"),\n trade_index: z\n .number()\n .optional()\n .describe(\n \"0-based index of trade in block's tradelog (ordered by date_opened)\"\n ),\n\n // Common fields\n open_date: z\n .string()\n .optional()\n .describe(\n \"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)\"\n ),\n close_date: z\n .string()\n .optional()\n .describe(\n \"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)\"\n ),\n multiplier: z\n .number()\n .default(100)\n .describe(\"Contract multiplier (default 100 for standard options)\"),\n format: z\n .enum([\"full\", \"summary\", \"sampled\"])\n .default(\"sampled\")\n .describe(\n \"Output format: 'sampled' returns path sampled at ~15min intervals (default), \" +\n \"'full' returns complete minute-by-minute P&L path, \" +\n \"'summary' returns MFE/MAE/P&L without minute-level path\"\n ),\n close_at: z\n .enum([\"trade\", \"expiry\"])\n .default(\"trade\")\n .describe(\n \"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, \" +\n \"'expiry' shows full path through option expiry. Only applies to tradelog mode.\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTH_MAP: Record<string, string> = {\n Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',\n Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',\n};\n\n/** Convert OO expiry hint \"Mar 13\" + year \"2026\" → \"2026-03-13\" */\nfunction resolveOOExpiryHint(hint: string, year: string): string {\n const [mon, day] = hint.split(' ');\n const mm = MONTH_MAP[mon] ?? '01';\n const dd = day.padStart(2, '0');\n return `${year}-${mm}-${dd}`;\n}\n\n/**\n * Derive fetch date range from OO leg expiryHints.\n *\n * For calendar spreads (different expiries): min(expiry)→max(expiry).\n * For single-expiry trades: tradeOpenDate→expiry.\n * Returns null if no legs have expiryHint (caller falls back to trade dates).\n */\nexport function resolveOODateRange(\n parsedLegs: import(\"../utils/trade-replay.js\").ParsedLegOO[],\n tradeYear: string,\n tradeOpenDate: string,\n): { from: string; to: string } | null {\n const hints = parsedLegs\n .filter(l => l.expiryHint)\n .map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));\n\n if (hints.length === 0) return null;\n\n const sorted = [...hints].sort();\n const minDate = sorted[0];\n const maxDate = sorted[sorted.length - 1];\n\n if (minDate === maxDate) {\n // Single expiry — fetch from trade open to expiry\n return { from: tradeOpenDate, to: maxDate };\n }\n // Calendar spread — near-term to far-term expiry\n return { from: minDate, to: maxDate };\n}\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleReplayTrade(\n params: z.infer<typeof replayTradeSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection\n): Promise<ReplayResult> {\n const { legs: inputLegs, block_id, trade_index, multiplier, close_at } = params;\n let { open_date, close_date } = params;\n let tradeCloseTimestamp: string | undefined; // \"YYYY-MM-DD HH:MM\" when trade actually closed\n\n let replayLegs: ReplayLeg[];\n\n if (inputLegs && inputLegs.length > 0) {\n // ----- Mode A: Hypothetical replay -----\n if (!open_date || !close_date) {\n throw new Error(\n \"open_date and close_date are required for hypothetical replay mode\"\n );\n }\n\n replayLegs = inputLegs.map((leg) => ({\n occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),\n quantity: leg.quantity,\n entryPrice: leg.entry_price,\n multiplier,\n }));\n } else if (block_id !== undefined && trade_index !== undefined) {\n // ----- Mode B: Tradelog replay -----\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const result = await conn.runAndReadAll(\n `SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed\n FROM trades.trade_data\n WHERE block_id = '${block_id.replace(/'/g, \"''\")}'\n ORDER BY date_opened\n LIMIT 1 OFFSET ${trade_index}`\n );\n\n const rows = result.getRows();\n if (rows.length === 0) {\n throw new Error(\n `No trade found at index ${trade_index} in block \"${block_id}\"`\n );\n }\n\n const row = rows[0];\n const legsStr = String(row[0] ?? \"\");\n const premium = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? \"\");\n const dateClosed = String(row[3] ?? \"\");\n const ticker = String(row[4] ?? \"\");\n const numContracts = Number(row[5] ?? 1);\n const timeClosed = String(row[6] ?? \"\");\n\n // Build actual trade close timestamp for path truncation\n if (dateClosed && timeClosed) {\n // time_closed is \"HH:MM:SS\" or \"HH:MM\" — normalize to \"HH:MM\"\n const normalizedTime = timeClosed.slice(0, 5);\n tradeCloseTimestamp = `${dateClosed} ${normalizedTime}`;\n }\n\n // Use trade dates if not provided\n open_date = open_date || dateOpened;\n close_date = close_date || dateClosed;\n\n // Parse legs from tradelog\n let parsedLegs;\n try {\n parsedLegs = parseLegsString(legsStr);\n } catch {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" from tradelog — use hypothetical mode with explicit strikes`\n );\n }\n\n // Build ReplayLeg[] from parsed legs\n const root = ticker || parsedLegs[0].root;\n const perContractPremium =\n numContracts > 0 ? premium / numContracts : premium;\n\n // OO format provides per-leg entry price, contract count, and expiry hint\n const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);\n\n // Resolve per-leg expiry: OO expiryHint (\"Mar 13\") + year from trade date\n const tradeYear = (open_date || dateOpened).split('-')[0];\n\n // Override fetch date range from OO expiryHints when available\n if (hasOOData) {\n const ooRange = resolveOODateRange(parsedLegs, tradeYear, open_date || dateOpened);\n if (ooRange) {\n open_date = ooRange.from;\n close_date = ooRange.to;\n }\n }\n\n replayLegs = parsedLegs.map((leg) => {\n let legExpiry = close_date!;\n if (hasOOData && leg.expiryHint) {\n legExpiry = resolveOOExpiryHint(leg.expiryHint, tradeYear);\n }\n return {\n occTicker: buildOccTicker(root, legExpiry, leg.type, leg.strike),\n quantity: hasOOData\n ? leg.quantity * (leg.contracts ?? 1)\n : leg.quantity * (numContracts > 0 ? numContracts : 1),\n entryPrice: hasOOData\n ? leg.entryPrice!\n : perContractPremium / parsedLegs.length,\n multiplier,\n };\n });\n } else {\n throw new Error(\n \"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode\"\n );\n }\n\n // ----- Fetch minute bars for each leg -----\n // Index options often trade under a weekly root on Massive (e.g., SPX→SPXW).\n // If the primary root fetch returns empty, retry with the mapped fallback root.\n const ROOT_FALLBACK_MAP: Record<string, string> = {\n SPX: 'SPXW',\n NDX: 'NDXP',\n RUT: 'RUTW',\n };\n\n const fetchLegBars = async (occTicker: string): Promise<BarRow[]> => {\n const bars = await fetchBarsWithCache({\n ticker: occTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: 'option',\n conn: injectedConn,\n baseDir,\n });\n if (bars.length > 0) return bars;\n\n // Fallback root retry (SPX→SPXW, NDX→NDXP, RUT→RUTW)\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const root = rootMatch ? rootMatch[1] : '';\n const fallbackRoot = ROOT_FALLBACK_MAP[root];\n if (fallbackRoot && !occTicker.startsWith(fallbackRoot)) {\n const fallbackTicker = fallbackRoot + occTicker.slice(root.length);\n const fallbackBars = await fetchBarsWithCache({\n ticker: fallbackTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: 'option',\n conn: injectedConn,\n baseDir,\n });\n if (fallbackBars.length > 0) {\n const leg = replayLegs.find(l => l.occTicker === occTicker);\n if (leg) leg.occTicker = fallbackTicker;\n return fallbackBars;\n }\n }\n return [];\n };\n\n const barsByLeg = await Promise.all(\n replayLegs.map((leg) => fetchLegBars(leg.occTicker))\n );\n\n // ----- Fetch underlying bars + build greeks config -----\n // Reverse-map weekly roots back to standard root for underlying fetch\n const REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n };\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n\n // Extract root from first leg's OCC ticker\n const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = firstRootMatch ? firstRootMatch[1] : '';\n const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n // Fetch underlying minute bars via shared cache utility (cache-read → API → cache-write)\n let underlyingBars: BarRow[] = await fetchBarsWithCache({\n ticker: underlyingTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: underlyingTicker === 'SPX' || underlyingTicker === 'NDX' || underlyingTicker === 'RUT' ? 'index' : 'stock',\n conn: injectedConn,\n baseDir,\n });\n\n // Daily fallback when minute bars unavailable\n if (underlyingBars.length === 0) {\n try {\n const conn = injectedConn ?? await getConnection(baseDir);\n const result = await conn.runAndReadAll(\n `SELECT date, close FROM market.daily\n WHERE ticker = '${underlyingTicker}'\n AND date >= '${open_date}' AND date <= '${close_date}'\n ORDER BY date`\n );\n const dailyRows = result.getRows();\n underlyingBars = dailyRows.map(r => ({\n date: String(r[0]),\n open: Number(r[1]),\n high: Number(r[1]),\n low: Number(r[1]),\n close: Number(r[1]),\n volume: 0,\n ticker: underlyingTicker,\n }));\n } catch {\n // No fallback available — greeks will be omitted\n }\n }\n\n // Build underlying price map for greeks config\n const underlyingPrices = new Map<string, number>();\n for (const b of underlyingBars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n underlyingPrices.set(ts, markPrice(b));\n }\n\n // Build sorted timestamps array for tolerant nearest-timestamp lookup (D-07/D-08)\n const sortedTimestamps = Array.from(underlyingPrices.keys())\n .filter(k => k.includes(' ')) // Only intraday timestamps, not date-only keys\n .sort();\n\n // IVP lookup from market.daily VIX ticker (normalized schema)\n let ivpByDate: Map<string, number> | undefined;\n try {\n const conn = injectedConn ?? await getConnection(baseDir);\n const ivpResult = await conn.runAndReadAll(\n `SELECT date, ivp FROM market.daily\n WHERE ticker = 'VIX'\n AND date >= '${open_date}' AND date <= '${close_date}'\n AND ivp IS NOT NULL\n ORDER BY date`\n );\n const ivpRows = ivpResult.getRows();\n if (ivpRows.length > 0) {\n ivpByDate = new Map();\n for (const r of ivpRows) {\n ivpByDate.set(String(r[0]), Number(r[1]));\n }\n }\n } catch {\n // IVP is optional enrichment — don't fail\n }\n\n // Build GreeksConfig\n let greeksConfig: GreeksConfig | undefined;\n if (underlyingPrices.size > 0) {\n greeksConfig = {\n underlyingPrices,\n sortedTimestamps,\n legs: replayLegs.map(leg => {\n // Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}\n const occMatch = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!occMatch) return { strike: 0, type: 'C' as const, expiryDate: '' };\n const yymmdd = occMatch[1];\n const type = occMatch[2] as 'C' | 'P';\n const strike = parseInt(occMatch[3], 10) / 1000;\n const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;\n return { strike, type, expiryDate };\n }),\n riskFreeRate: 0.045,\n dividendYield,\n ivpByDate,\n };\n }\n\n // ----- Compute P&L path + MFE/MAE -----\n let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);\n let { mfe, mae, mfeTimestamp, maeTimestamp } =\n computeReplayMfeMae(fullPath);\n let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;\n\n // Compute greeks warning (D-12): warn when >50% of leg-timestamps have null greeks\n let greeksNullCount = 0;\n let greeksTotalCount = 0;\n for (const point of fullPath) {\n if (point.legGreeks) {\n for (const lg of point.legGreeks) {\n greeksTotalCount++;\n if (lg.delta === null) greeksNullCount++;\n }\n }\n }\n const greeksWarning = greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5\n ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`\n : null;\n\n // Apply format filter\n // Truncate path at trade close timestamp when close_at === \"trade\" (default)\n // This ensures decompose_greeks and exit triggers only analyze the actual holding period\n if (close_at === \"trade\" && tradeCloseTimestamp && fullPath.length > 0) {\n const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);\n if (truncIdx > 0) {\n fullPath = fullPath.slice(0, truncIdx);\n // Recompute MFE/MAE/totalPnl on truncated path\n mfe = -Infinity;\n mae = Infinity;\n for (const p of fullPath) {\n if (p.strategyPnl > mfe) { mfe = p.strategyPnl; mfeTimestamp = p.timestamp; }\n if (p.strategyPnl < mae) { mae = p.strategyPnl; maeTimestamp = p.timestamp; }\n }\n totalPnl = fullPath[fullPath.length - 1].strategyPnl;\n }\n }\n\n const { format } = params;\n let pnlPath: typeof fullPath;\n if (format === \"summary\") {\n // Return only MFE, MAE, and boundary points (first, last, MFE timestamp, MAE timestamp)\n const keyTimestamps = new Set([\n fullPath[0]?.timestamp,\n fullPath[fullPath.length - 1]?.timestamp,\n mfeTimestamp,\n maeTimestamp,\n ]);\n pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));\n } else if (format === \"sampled\") {\n // Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)\n const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);\n pnlPath = fullPath.filter((p, i) =>\n i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp)\n );\n } else {\n pnlPath = fullPath;\n }\n\n return {\n pnlPath,\n mfe,\n mae,\n mfeTimestamp,\n maeTimestamp,\n totalPnl,\n totalBars: fullPath.length,\n legs: replayLegs,\n greeksWarning,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerReplayTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"replay_trade\",\n {\n description:\n \"Replay a trade using historical minute-level option bars. \" +\n \"Uses cached bars from market.intraday if available; fetches from Massive.com API on cache miss (requires MASSIVE_API_KEY). \" +\n \"Returns minute-by-minute P&L path with MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion). \" +\n \"Two modes: (A) Hypothetical — provide explicit legs with strikes, expiry, entry prices. \" +\n \"(B) Tradelog — provide block_id + trade_index to replay an existing trade from your data.\",\n inputSchema: replayTradeSchema,\n },\n async (params) => {\n try {\n const result = await handleReplayTrade(params, baseDir);\n\n const summary =\n `Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? \"trade dates\"} to ${params.close_date ?? \"trade dates\"}: ` +\n `$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +\n `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? 'yes' : 'no'}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error replaying trade: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * bar-cache.ts\n *\n * Shared fetch+cache utility for Massive.com bar data.\n *\n * Implements the cache-read → API-fetch → cache-write lifecycle for intraday bars:\n * 1. Cache-read: query market.intraday first (avoids redundant API calls)\n * 2. API fetch: call fetchBars on cache miss\n * 3. Cache-write: write fetched bars to market.intraday (best-effort, batched)\n *\n * Per D-02/D-03/D-04: Eliminates duplicated cache logic in replay.ts.\n * Historical option and underlying bars are immutable — cached bars are always valid.\n */\n\nimport type { BarRow, AssetClass } from './market-provider.js';\nimport { getProvider } from './market-provider.js';\nimport { getConnection } from '../db/connection.js';\nimport type { DuckDBConnection } from '@duckdb/node-api';\n\n/** Check if quotes enrichment is enabled via MASSIVE_QUOTES_ENABLED env var. */\nfunction quotesEnabled(): boolean {\n return process.env.MASSIVE_QUOTES_ENABLED === 'true' || process.env.MASSIVE_QUOTES_ENABLED === '1';\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FetchBarsWithCacheOptions {\n ticker: string;\n from: string;\n to: string;\n timespan?: \"day\" | \"minute\" | \"hour\";\n assetClass?: AssetClass;\n /** Pre-opened DuckDB connection (avoids re-opening in hot paths). */\n conn?: DuckDBConnection;\n /** Base directory for getConnection (used when conn is not provided). */\n baseDir?: string;\n}\n\n// ---------------------------------------------------------------------------\n// fetchBarsWithCache\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch minute bars for a ticker over a date range, using market.intraday as a cache.\n *\n * Steps:\n * 1. Try to read bars from market.intraday (cache hit → return immediately)\n * 2. On cache miss, call fetchBars (Massive API)\n * 3. Write fetched bars back to market.intraday in batches of 500 (best-effort)\n *\n * DuckDB errors in steps 1 or 3 are swallowed — step 2 is always attempted on miss.\n * Returns [] when both cache and API return no bars (or API throws).\n */\nexport async function fetchBarsWithCache(opts: FetchBarsWithCacheOptions): Promise<BarRow[]> {\n const { ticker, from, to, timespan, assetClass, baseDir } = opts;\n\n // 1. Cache-read from market.intraday\n try {\n const conn = opts.conn ?? await getConnection(baseDir ?? '.');\n const escaped = ticker.replace(/'/g, \"''\");\n const cached = await conn.runAndReadAll(\n `SELECT open, high, low, close, bid, ask, time, date\n FROM market.intraday\n WHERE ticker = '${escaped}'\n AND date >= '${from}'\n AND date <= '${to}'\n ORDER BY date, time`\n );\n const rows = cached.getRows() as unknown[][];\n if (rows.length > 0) {\n const bars = rows.map((row) => ({\n open: Number(row[0]),\n high: Number(row[1]),\n low: Number(row[2]),\n close: Number(row[3]),\n bid: row[4] != null ? Number(row[4]) : undefined,\n ask: row[5] != null ? Number(row[5]) : undefined,\n time: String(row[6]),\n date: String(row[7]),\n ticker,\n volume: 0, // market.intraday has no volume column\n }));\n\n // Backfill bid/ask from quotes endpoint if cached bars are missing them\n const missingQuotes = assetClass === \"option\" && quotesEnabled()\n && bars.some(b => b.bid == null && b.ask == null);\n if (missingQuotes) {\n try {\n const provider = getProvider();\n if (provider.fetchQuotes) {\n const quotesMap = await provider.fetchQuotes(ticker, from, to);\n if (quotesMap.size > 0) {\n const updates: string[] = [];\n for (const bar of bars) {\n if (bar.time != null) {\n const key = `${bar.date} ${bar.time}`;\n const quote = quotesMap.get(key);\n if (quote != null) {\n bar.bid = quote.bid;\n bar.ask = quote.ask;\n updates.push(\n `('${escaped}', '${bar.date}', '${bar.time}', ${quote.bid}, ${quote.ask})`\n );\n }\n }\n }\n // Persist enriched bid/ask back to cache (batched INSERT OR REPLACE)\n if (updates.length > 0) {\n const updateConn = opts.conn ?? await getConnection(baseDir ?? '.');\n for (let i = 0; i < updates.length; i += 500) {\n const chunk = updates.slice(i, i + 500);\n await updateConn.run(\n `UPDATE market.intraday AS m SET bid = v.bid, ask = v.ask\n FROM (VALUES ${chunk.join(', ')}) AS v(ticker, date, time, bid, ask)\n WHERE m.ticker = v.ticker AND m.date = v.date AND m.time = v.time`\n );\n }\n }\n }\n }\n } catch {\n // Best-effort — return cached bars without bid/ask\n }\n }\n\n return bars;\n }\n } catch {\n // Cache miss or table not available — fall through to API fetch\n }\n\n // 2. API fetch on cache miss\n let bars: BarRow[] = [];\n try {\n bars = await getProvider().fetchBars({\n ticker,\n from,\n to,\n timespan: timespan ?? 'minute',\n assetClass,\n });\n } catch {\n return [];\n }\n if (bars.length === 0) return [];\n\n // 3. Cache-write to market.intraday (best-effort, batched)\n try {\n const conn = opts.conn ?? await getConnection(baseDir ?? '.');\n const escaped = ticker.replace(/'/g, \"''\");\n const values = bars\n .filter(b => b.time)\n .map(b =>\n `('${escaped}', '${b.date}', '${b.time}', ${b.open}, ${b.high}, ${b.low}, ${b.close}, ${b.bid ?? 'NULL'}, ${b.ask ?? 'NULL'})`\n );\n for (let i = 0; i < values.length; i += 500) {\n const chunk = values.slice(i, i + 500);\n await conn.run(\n `INSERT OR REPLACE INTO market.intraday (ticker, date, time, open, high, low, close, bid, ask) VALUES ${chunk.join(', ')}`\n );\n }\n } catch {\n // Cache-write is best-effort — don't fail the fetch\n }\n\n return bars;\n}\n","/**\n * Option Snapshot Tools\n *\n * MCP tool for fetching live option chain snapshots from Massive.com.\n * Returns current greeks, IV, open interest, and quotes for option contracts\n * on a specified underlying.\n *\n * Tools registered:\n * - get_option_snapshot — Fetch live option chain with greeks/IV/OI\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getProvider } from \"../utils/market-provider.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const getOptionSnapshotSchema = z.object({\n underlying: z\n .string()\n .describe(\"Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')\"),\n strike_price_gte: z\n .number()\n .optional()\n .describe(\"Minimum strike price filter\"),\n strike_price_lte: z\n .number()\n .optional()\n .describe(\"Maximum strike price filter\"),\n expiration_date_gte: z\n .string()\n .optional()\n .describe(\"Earliest expiration date (YYYY-MM-DD)\"),\n expiration_date_lte: z\n .string()\n .optional()\n .describe(\"Latest expiration date (YYYY-MM-DD)\"),\n contract_type: z\n .enum([\"call\", \"put\"])\n .optional()\n .describe(\"Filter by call or put\"),\n limit: z\n .number()\n .optional()\n .default(50)\n .describe(\n \"Max contracts to return (default 50, use higher for full chain)\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleGetOptionSnapshot(\n params: z.infer<typeof getOptionSnapshotSchema>,\n): Promise<string> {\n try {\n const {\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n limit,\n } = params;\n\n const result = await getProvider().fetchOptionSnapshot({\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n });\n\n // Client-side limit truncation: API fetches all filtered contracts\n // (ensuring BS fallback runs on all), then we truncate for presentation\n const contractsTotal = result.contracts.length;\n const contracts =\n limit != null && contractsTotal > limit\n ? result.contracts.slice(0, limit)\n : result.contracts;\n\n return JSON.stringify({\n underlying_ticker: result.underlying_ticker,\n underlying_price: result.underlying_price,\n contracts_returned: contracts.length,\n contracts_total: contractsTotal,\n contracts,\n });\n } catch (error) {\n return JSON.stringify({\n error: (error as Error).message,\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerSnapshotTools(server: McpServer): void {\n server.registerTool(\n \"get_option_snapshot\",\n {\n description:\n \"Fetch live option chain snapshot with greeks, IV, open interest, and quotes from Massive.com. \" +\n \"Returns current market data for option contracts on the specified underlying. \" +\n \"Use filters to narrow by strike range, expiration range, or call/put type. \" +\n \"Replaces TastyTrade get_option_chain for analysis.\",\n inputSchema: getOptionSnapshotSchema,\n },\n async (params) => {\n const text = await handleGetOptionSnapshot(params);\n return {\n content: [{ type: \"text\" as const, text }],\n };\n }\n );\n}\n","/**\n * Greeks Decomposition Engine\n *\n * Decomposes a replay P&L path into ranked greek factor contributions\n * (delta, gamma, theta, vega, residual) using full revaluation P&L attribution.\n *\n * Full revaluation reprices each leg with one input changed at a time\n * (spot, time, vol) to capture all higher-order effects (charm, vanna, volga)\n * naturally. This produces near-zero residual for any strategy where the\n * pricing model (BS or Bachelier) can accurately price the options.\n *\n * Falls back to numerical decomposition (realized delta from price changes)\n * when full revaluation still produces >80% residual (model pricing failure).\n *\n * Pure logic module — no I/O, no DuckDB, no fetch.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\nimport { bsPrice, bachelierPrice, BACHELIER_DTE_THRESHOLD } from './black-scholes.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type FactorName = 'delta' | 'gamma' | 'theta' | 'vega' | 'charm' | 'vanna' | 'residual' | 'time_and_vol';\n\nexport interface FactorContribution {\n factor: FactorName;\n totalPnl: number; // Sum of step contributions\n pctOfTotal: number; // % of total abs P&L move\n steps: number[]; // Per-step contribution values\n}\n\nexport interface LegGroupVega {\n label: string; // e.g., \"front_month\", \"back_month\"\n legIndices: number[]; // Which legs are in this group\n totalVegaPnl: number; // Sum of vega P&L for this group\n avgIvChange: number; // Average IV change for this group's legs\n steps: number[]; // Per-step vega contribution for this group\n}\n\nexport interface GreeksDecompositionResult {\n factors: FactorContribution[]; // Sorted by abs(totalPnl) descending\n legGroupVega?: LegGroupVega[]; // Per-leg-group vega attribution\n totalPnlChange: number; // Actual P&L change from first to last point\n totalAttributed: number; // Sum of factor contributions (excl residual)\n totalResidual: number; // Total residual\n stepCount: number; // Number of steps (pnlPath.length - 1)\n summary: string; // Human-readable summary\n warning?: string | null; // D-13: high residual warning\n method: 'full_reval' | 'model' | 'numerical'; // which method produced the attribution\n}\n\nexport interface LegGroupDef {\n label: string;\n legIndices: number[];\n}\n\nexport interface LegPricingInput {\n strike: number;\n type: 'C' | 'P';\n expiryDate: string; // YYYY-MM-DD\n}\n\nexport interface GreeksDecompositionConfig {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n underlyingPrices?: Map<string, number>; // timestamp -> underlying price\n legGroups?: LegGroupDef[]; // Optional leg grouping for per-group vega\n /** Per-leg pricing inputs for full revaluation. When provided, uses full reval\n * instead of Taylor expansion. Falls back to Taylor when missing. */\n legPricingInputs?: LegPricingInput[];\n riskFreeRate?: number; // e.g. 0.045\n dividendYield?: number; // e.g. 0.015 for SPX\n}\n\n// ---------------------------------------------------------------------------\n// Time delta helper\n// ---------------------------------------------------------------------------\n\nconst TRADING_MINUTES_PER_DAY = 390;\n\n/**\n * Compute time delta in trading days between two timestamps.\n * Format: \"YYYY-MM-DD HH:MM\"\n *\n * Same day: minutes difference / 390\n * Cross day: calendar day difference (simplified — treats each gap as 1 day)\n */\nexport function computeTimeDeltaDays(ts1: string, ts2: string): number {\n const [date1, time1] = ts1.split(' ');\n const [date2, time2] = ts2.split(' ');\n\n if (date1 === date2) {\n // Same day: count minutes difference\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n const mins1 = h1 * 60 + m1;\n const mins2 = h2 * 60 + m2;\n const diffMins = Math.abs(mins2 - mins1);\n return diffMins / TRADING_MINUTES_PER_DAY;\n }\n\n // Cross-day: compute calendar day difference\n const d1 = new Date(date1 + 'T12:00:00'); // noon to avoid DST issues\n const d2 = new Date(date2 + 'T12:00:00');\n const diffMs = Math.abs(d2.getTime() - d1.getTime());\n const diffDays = Math.round(diffMs / (24 * 60 * 60 * 1000));\n\n // Add fractional day from time within each day\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n // Fraction of trading day for ts2's time (from market open ~9:30)\n const minsIntoDay2 = (h2 * 60 + m2) - (9 * 60 + 30);\n const fracDay2 = Math.max(0, minsIntoDay2) / TRADING_MINUTES_PER_DAY;\n // Fraction remaining in ts1's day\n const minsIntoDay1 = (h1 * 60 + m1) - (9 * 60 + 30);\n const fracDayRemaining1 = Math.max(0, 1 - minsIntoDay1 / TRADING_MINUTES_PER_DAY);\n\n // Total: remaining fraction of day1 + (diffDays - 1) full days + fraction of day2\n if (diffDays <= 1) {\n return fracDayRemaining1 + fracDay2;\n }\n return fracDayRemaining1 + (diffDays - 1) + fracDay2;\n}\n\n// ---------------------------------------------------------------------------\n// Numerical fallback decomposition (D-09/D-10/D-11)\n// ---------------------------------------------------------------------------\n\n/**\n * Numerical decomposition: compute realized delta from price changes when\n * model-based attribution has > 80% residual.\n *\n * Splits P&L into: delta (from realized delta), gamma (from delta changes),\n * and time_and_vol (everything else — theta + vega + unexplained).\n */\nfunction numericalDecomposition(\n config: GreeksDecompositionConfig,\n totalPnlChange: number,\n stepCount: number,\n): GreeksDecompositionResult {\n const { pnlPath, underlyingPrices } = config;\n\n const numDeltaSteps: number[] = [];\n const numGammaSteps: number[] = [];\n const numResidualSteps: number[] = [];\n\n let prevRealizedDelta: number | null = null;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n const actualChange = next.strategyPnl - cur.strategyPnl;\n\n // Underlying price change\n let underlyingChange = 0;\n if (underlyingPrices) {\n const p1 = underlyingPrices.get(cur.timestamp);\n const p2 = underlyingPrices.get(next.timestamp);\n if (p1 !== undefined && p2 !== undefined) {\n underlyingChange = p2 - p1;\n }\n }\n\n // Skip when underlying barely moves (< $0.01) — can't estimate delta\n if (Math.abs(underlyingChange) < 0.01) {\n numDeltaSteps.push(0);\n numGammaSteps.push(0);\n numResidualSteps.push(actualChange);\n // Do NOT update prevRealizedDelta — delta is unknown\n continue;\n }\n\n // Realized delta = total option PnL change / underlying change\n const realizedDelta = actualChange / underlyingChange;\n\n // Gamma from delta changes (D-10): only when we have a previous delta\n let gammaPnl = 0;\n if (prevRealizedDelta !== null) {\n const deltaChange = realizedDelta - prevRealizedDelta;\n gammaPnl = 0.5 * deltaChange * underlyingChange;\n }\n\n const pureDeltaPnl = realizedDelta * underlyingChange - gammaPnl;\n const residual = actualChange - pureDeltaPnl - gammaPnl;\n\n numDeltaSteps.push(pureDeltaPnl);\n numGammaSteps.push(gammaPnl);\n numResidualSteps.push(residual);\n\n prevRealizedDelta = realizedDelta;\n }\n\n const sumSteps = (s: number[]) => s.reduce((a, v) => a + v, 0);\n const totalDelta = sumSteps(numDeltaSteps);\n const totalGamma = sumSteps(numGammaSteps);\n const totalTimeAndVol = sumSteps(numResidualSteps);\n\n const rawFactors = [\n { factor: 'delta' as FactorName, totalPnl: totalDelta, steps: numDeltaSteps },\n { factor: 'gamma' as FactorName, totalPnl: totalGamma, steps: numGammaSteps },\n { factor: 'time_and_vol' as FactorName, totalPnl: totalTimeAndVol, steps: numResidualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const summaryParts = factors.map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(', ')}`;\n\n return {\n factors,\n legGroupVega: undefined, // Leg-group vega not available in numerical mode\n totalPnlChange,\n totalAttributed: totalDelta + totalGamma,\n totalResidual: totalTimeAndVol,\n stepCount,\n summary,\n warning: 'Model-based attribution had >80% residual. Switched to numerical method (realized delta from price changes).',\n method: 'numerical',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core decomposition\n// ---------------------------------------------------------------------------\n\n/**\n * Compute DTE in days from a bar timestamp to a leg's expiry (4:00 PM ET).\n */\nfunction computeDte(timestamp: string, expiryDate: string): number {\n const dateStr = timestamp.split(' ')[0];\n const timePart = timestamp.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n return (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n}\n\n/**\n * Price an option using the appropriate model (BS or Bachelier) based on DTE.\n * Returns null if pricing fails (DTE <= 0 or IV missing).\n */\nfunction priceOption(\n type: 'C' | 'P',\n S: number,\n K: number,\n dte: number,\n r: number,\n q: number,\n iv: number,\n): number | null {\n if (dte <= 0 || iv <= 0) return null;\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' as const : 'put' as const;\n if (dte < BACHELIER_DTE_THRESHOLD) {\n return bachelierPrice(bsType, S, K, T, r, q, iv);\n }\n return bsPrice(bsType, S, K, T, r, q, iv);\n}\n\n/**\n * Decompose a replay P&L path into ranked greek factor contributions.\n *\n * Uses full revaluation when legPricingInputs are provided:\n * For each step, reprices each leg with one input changed at a time\n * (spot only, time only, vol only) to isolate each factor's contribution.\n * This captures all higher-order effects (charm, vanna, volga) naturally.\n *\n * Falls back to numerical decomposition when full reval produces >80% residual\n * (pricing model failure for that strategy/DTE combination).\n */\nexport function decomposeGreeks(config: GreeksDecompositionConfig): GreeksDecompositionResult {\n const { pnlPath, legs, underlyingPrices, legGroups, legPricingInputs, riskFreeRate, dividendYield } = config;\n\n // Edge case: empty or single-point path\n if (pnlPath.length <= 1) {\n const emptyFactors: FactorContribution[] = [\n { factor: 'delta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'gamma', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'theta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'vega', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'residual', totalPnl: 0, pctOfTotal: 0, steps: [] },\n ];\n return {\n factors: emptyFactors,\n legGroupVega: legGroups ? legGroups.map(g => ({\n label: g.label,\n legIndices: g.legIndices,\n totalVegaPnl: 0,\n avgIvChange: 0,\n steps: [],\n })) : undefined,\n totalPnlChange: 0,\n totalAttributed: 0,\n totalResidual: 0,\n stepCount: 0,\n summary: 'No P&L path to decompose (0 steps)',\n warning: null,\n method: 'full_reval',\n };\n }\n\n const stepCount = pnlPath.length - 1;\n const canFullReval = legPricingInputs && legPricingInputs.length === legs.length\n && riskFreeRate !== undefined && dividendYield !== undefined && underlyingPrices;\n const r = riskFreeRate ?? 0.045;\n const q = dividendYield ?? 0.015;\n\n // Accumulators\n const deltaSteps: number[] = [];\n const thetaSteps: number[] = [];\n const vegaSteps: number[] = [];\n const charmSteps: number[] = [];\n const vannaSteps: number[] = [];\n const residualSteps: number[] = [];\n\n // Per-leg-group vega accumulators\n const groupSteps: number[][] | undefined = legGroups\n ? legGroups.map(() => [] as number[])\n : undefined;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n\n let stepDelta = 0;\n let stepTheta = 0;\n let stepVega = 0;\n let stepCharm = 0;\n let stepVanna = 0;\n let stepResidual = 0;\n\n const groupVegaAccum: number[] | undefined = legGroups ? legGroups.map(() => 0) : undefined;\n\n const legCount = Math.min(legs.length, cur.legPrices?.length ?? 0, next.legPrices?.length ?? 0);\n\n // Underlying prices at cur and next timestamps\n const S1 = underlyingPrices?.get(cur.timestamp);\n const S2 = underlyingPrices?.get(next.timestamp);\n\n for (let j = 0; j < legCount; j++) {\n const positionSize = legs[j].quantity * legs[j].multiplier;\n const legActualChange = ((next.legPrices?.[j] ?? 0) - (cur.legPrices?.[j] ?? 0)) * positionSize;\n\n const curIv = cur.legGreeks?.[j]?.iv;\n const nextIv = next.legGreeks?.[j]?.iv;\n const lpi = legPricingInputs?.[j];\n\n // Full revaluation: reprice with one input changed at a time\n if (canFullReval && lpi && S1 !== undefined && S2 !== undefined\n && curIv !== null && curIv !== undefined && curIv > 0\n && nextIv !== null && nextIv !== undefined && nextIv > 0) {\n\n const dte1 = computeDte(cur.timestamp, lpi.expiryDate);\n const dte2 = computeDte(next.timestamp, lpi.expiryDate);\n\n if (dte1 > 0 && dte2 > 0) {\n // Baseline: price at (S1, T1, IV1)\n const priceBase = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, curIv);\n\n // Delta: price at (S2, T1, IV1) — only spot changed\n const priceDelta = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, curIv);\n\n // Theta: price at (S1, T2, IV1) — only time changed\n const priceTheta = priceOption(lpi.type, S1, lpi.strike, dte2, r, q, curIv);\n\n // Vega: price at (S1, T1, IV2) — only vol changed\n const priceVega = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, nextIv);\n\n // Cross-term repricing: two inputs changed at once\n // Charm (spot×time): P(S2, T2, σ1) - base - delta - theta\n const priceCharm = priceOption(lpi.type, S2, lpi.strike, dte2, r, q, curIv);\n // Vanna (spot×vol): P(S2, T1, σ2) - base - delta - vega\n const priceVanna = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, nextIv);\n\n if (priceBase !== null && priceDelta !== null && priceTheta !== null\n && priceVega !== null && priceCharm !== null && priceVanna !== null) {\n const legDeltaPnl = (priceDelta - priceBase) * positionSize;\n const legThetaPnl = (priceTheta - priceBase) * positionSize;\n const legVegaPnl = (priceVega - priceBase) * positionSize;\n const legCharmPnl = ((priceCharm - priceBase) - (priceDelta - priceBase) - (priceTheta - priceBase)) * positionSize;\n const legVannaPnl = ((priceVanna - priceBase) - (priceDelta - priceBase) - (priceVega - priceBase)) * positionSize;\n const legResidual = legActualChange - legDeltaPnl - legThetaPnl - legVegaPnl - legCharmPnl - legVannaPnl;\n\n stepDelta += legDeltaPnl;\n stepTheta += legThetaPnl;\n stepVega += legVegaPnl;\n stepCharm += legCharmPnl;\n stepVanna += legVannaPnl;\n stepResidual += legResidual;\n\n // Per-leg-group vega\n if (legGroups && groupVegaAccum) {\n for (let g = 0; g < legGroups.length; g++) {\n if (legGroups[g].legIndices.includes(j)) {\n groupVegaAccum[g] += legVegaPnl;\n }\n }\n }\n continue; // leg handled by full reval\n }\n }\n }\n\n // Fallback: leg P&L goes to residual (no pricing possible)\n stepResidual += legActualChange;\n }\n\n deltaSteps.push(stepDelta);\n thetaSteps.push(stepTheta);\n vegaSteps.push(stepVega);\n charmSteps.push(stepCharm);\n vannaSteps.push(stepVanna);\n residualSteps.push(stepResidual);\n\n if (groupSteps && groupVegaAccum) {\n for (let g = 0; g < legGroups!.length; g++) {\n groupSteps[g].push(groupVegaAccum[g]);\n }\n }\n }\n\n const sumSteps = (steps: number[]): number => steps.reduce((s, v) => s + v, 0);\n\n // Full reval factors:\n // - delta: spot-only P&L (includes gamma — all spot-driven effects)\n // - theta: time-only P&L\n // - vega: vol-only P&L\n // - charm: spot×time cross-effect (delta changing with time)\n // - vanna: spot×vol cross-effect (delta changing with vol)\n // - residual: triple cross (spot+time+vol simultaneously) + model error\n const rawFactors: Array<{ factor: FactorName; totalPnl: number; steps: number[] }> = [\n { factor: 'delta', totalPnl: sumSteps(deltaSteps), steps: deltaSteps },\n { factor: 'theta', totalPnl: sumSteps(thetaSteps), steps: thetaSteps },\n { factor: 'vega', totalPnl: sumSteps(vegaSteps), steps: vegaSteps },\n { factor: 'charm', totalPnl: sumSteps(charmSteps), steps: charmSteps },\n { factor: 'vanna', totalPnl: sumSteps(vannaSteps), steps: vannaSteps },\n { factor: 'residual', totalPnl: sumSteps(residualSteps), steps: residualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const totalPnlChange = pnlPath[pnlPath.length - 1].strategyPnl - pnlPath[0].strategyPnl;\n const totalResidual = sumSteps(residualSteps);\n const totalAttributed = sumSteps(deltaSteps) + sumSteps(thetaSteps) + sumSteps(vegaSteps) + sumSteps(charmSteps) + sumSteps(vannaSteps);\n\n const residualPct = Math.abs(totalPnlChange) > 0.01\n ? Math.abs(totalResidual) / Math.abs(totalPnlChange)\n : 0;\n\n // Numerical fallback when full reval still produces >80% residual\n // (model pricing failure — BS/Bachelier can't accurately price these options)\n if (residualPct > 0.8 && pnlPath.length > 2) {\n return numericalDecomposition(config, totalPnlChange, stepCount);\n }\n\n // Build leg group vega results\n let legGroupVega: LegGroupVega[] | undefined;\n if (legGroups && groupSteps) {\n legGroupVega = legGroups.map((group, g) => {\n const steps = groupSteps[g];\n const totalVegaPnl = sumSteps(steps);\n\n let totalIvChange = 0;\n let ivStepCount = 0;\n for (let si = 0; si < stepCount; si++) {\n const cur = pnlPath[si];\n const nxt = pnlPath[si + 1];\n if (!cur.legGreeks || !nxt.legGreeks) continue;\n for (const j of group.legIndices) {\n const iv1 = cur.legGreeks[j]?.iv;\n const iv2 = nxt.legGreeks[j]?.iv;\n if (iv1 !== null && iv1 !== undefined && iv2 !== null && iv2 !== undefined) {\n totalIvChange += (iv2 - iv1) * 100;\n ivStepCount++;\n }\n }\n }\n\n return {\n label: group.label,\n legIndices: group.legIndices,\n totalVegaPnl,\n avgIvChange: ivStepCount > 0 ? totalIvChange / ivStepCount : 0,\n steps,\n };\n });\n }\n\n // Build summary\n const methodLabel = canFullReval ? 'full_reval' : 'model';\n const summaryParts = factors\n .filter(f => f.factor !== 'residual')\n .map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const residualFactor = factors.find(f => f.factor === 'residual');\n if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {\n summaryParts.push(`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`);\n }\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(', ')}`;\n\n const warning = residualPct > 0.5\n ? `Residual ${(residualPct * 100).toFixed(0)}% — attribution limited for some legs.`\n : null;\n\n return {\n factors,\n legGroupVega,\n totalPnlChange,\n totalAttributed,\n totalResidual,\n stepCount,\n summary,\n warning,\n method: canFullReval ? 'full_reval' : 'model',\n };\n}\n","/**\n * Exit Trigger Evaluation Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that evaluates 15 exit\n * trigger types against a greeks-enriched P&L path from trade replay.\n *\n * Provides the computational heart of the `analyze_exit_triggers` tool.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TriggerType =\n | 'profitTarget'\n | 'stopLoss'\n | 'trailingStop'\n | 'profitAction'\n | 'dteExit'\n | 'ditExit'\n | 'clockTimeExit'\n | 'underlyingPriceMove'\n | 'positionDelta'\n | 'perLegDelta'\n | 'vixMove'\n | 'vix9dMove'\n | 'vix9dVixRatio'\n | 'slRatioThreshold'\n | 'slRatioMove';\n\nexport interface PartialClose {\n index: number;\n pnlAtFire: number;\n allocation: number;\n trigger: string;\n}\n\nexport interface ExitTriggerConfig {\n type: TriggerType;\n threshold: number;\n unit?: 'percent' | 'dollar'; // D-07: default 'dollar', backwards compatible\n steps?: Array<{ armAt: number; stopAt: number; closeAllocationPct?: number }>;\n // Context-specific optional fields:\n expiry?: string; // YYYY-MM-DD for dteExit\n openDate?: string; // YYYY-MM-DD for ditExit\n clockTime?: string; // \"HH:MM\" for clockTimeExit (threshold ignored)\n trailAmount?: number; // Dollar trail for trailingStop\n // Directional delta fields (OO-style per-leg exits):\n legIndex?: number; // 0-based leg index for perLegDelta — targets specific leg\n exitAbove?: number; // Fire when value > exitAbove (directional, no abs)\n exitBelow?: number; // Fire when value < exitBelow (directional, no abs)\n // Data maps for triggers needing external prices:\n underlyingPrices?: Map<string, number>; // timestamp -> price\n vixPrices?: Map<string, number>; // timestamp -> VIX price\n vix9dPrices?: Map<string, number>; // timestamp -> VIX9D price\n // S/L ratio inputs:\n spreadWidth?: number; // Width of spread in dollars\n contracts?: number; // Number of contracts\n multiplier?: number; // Default 100\n // Internal: set by handler when unit='percent' to compute dollar threshold\n entryCost?: number; // D-11: cost/credit of entry (negative = credit received)\n}\n\nexport interface TriggerFireEvent {\n type: TriggerType;\n firedAt: string; // Timestamp when trigger fired\n pnlAtFire: number; // Strategy P&L when trigger fired\n index: number; // Index into pnlPath\n detail?: string; // Human-readable description\n}\n\nexport interface ExitTriggerResult {\n triggers: TriggerFireEvent[]; // All triggers that fired (sorted by fire time)\n firstToFire: TriggerFireEvent | null; // Earliest trigger\n actualExit?: {\n timestamp: string;\n pnl: number;\n pnlDifference: number; // firstToFire.pnl - actualExit.pnl\n };\n partialCloses?: PartialClose[]; // Partial position closes from profitAction steps\n summary: string;\n}\n\nexport interface LegGroupConfig {\n label: string;\n legIndices: number[];\n triggers: ExitTriggerConfig[];\n}\n\nexport interface LegGroupResult {\n label: string;\n result: ExitTriggerResult;\n groupPnl: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" to a Date at local midnight. */\nfunction parseDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}\n\n/** Extract date portion \"YYYY-MM-DD\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractDate(timestamp: string): string {\n return timestamp.slice(0, 10);\n}\n\n/** Extract time portion \"HH:MM\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractTime(timestamp: string): string {\n return timestamp.slice(11, 16);\n}\n\n/** Calendar days between two dates (absolute). */\nfunction calendarDaysBetween(a: Date, b: Date): number {\n const MS_PER_DAY = 86_400_000;\n return Math.abs(Math.floor((b.getTime() - a.getTime()) / MS_PER_DAY));\n}\n\n/** Compute S/L ratio for spread positions. */\nfunction computeSLRatio(\n point: PnlPoint,\n legs: ReplayLeg[],\n spreadWidth: number,\n contracts: number,\n multiplier: number,\n): number {\n // Spread value = sum of abs(markPrice * quantity * multiplier) for short legs\n let spreadValue = 0;\n for (let i = 0; i < legs.length; i++) {\n if (legs[i].quantity < 0) {\n const markPrice = point.legPrices[i] ?? 0;\n spreadValue += Math.abs(markPrice * legs[i].quantity * legs[i].multiplier);\n }\n }\n const maxLoss = spreadWidth * contracts * multiplier;\n if (maxLoss === 0) return 0;\n return spreadValue / maxLoss;\n}\n\n// ---------------------------------------------------------------------------\n// evaluateProfitAction — partial close aware evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a profitAction trigger with partial close support.\n * Steps with closeAllocationPct will close a fraction of the REMAINING position\n * when their armAt is first reached. The remaining position's P&L is scaled down.\n *\n * Returns both the fire event (stop hit on remaining) and any partial closes.\n */\nexport function evaluateProfitAction(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n _legs: ReplayLeg[], // eslint-disable-line @typescript-eslint/no-unused-vars\n): { fireEvent: TriggerFireEvent | null; partialCloses: PartialClose[] } {\n const partialCloses: PartialClose[] = [];\n\n if (pnlPath.length === 0 || !trigger.steps?.length) {\n return { fireEvent: null, partialCloses };\n }\n if (trigger.unit === 'percent' && trigger.entryCost == null) {\n return { fireEvent: null, partialCloses };\n }\n\n const scale = trigger.unit === 'percent'\n ? Math.abs(trigger.entryCost!)\n : 1;\n\n const normalizedSteps = [...trigger.steps]\n .sort((a, b) => a.armAt - b.armAt)\n .map((step) => ({\n armAt: step.armAt * scale,\n stopAt: step.stopAt * scale,\n closeAllocationPct: step.closeAllocationPct,\n }));\n\n let remainingAllocation = 1.0;\n let runningMaxPnl = -Infinity;\n // Track which steps have already triggered their partial close\n const stepPartialFired = new Array(normalizedSteps.length).fill(false);\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n // Check each step for partial close (only when armAt first reached)\n for (let s = 0; s < normalizedSteps.length; s++) {\n const step = normalizedSteps[s];\n if (!stepPartialFired[s] && step.closeAllocationPct && runningMaxPnl >= step.armAt) {\n stepPartialFired[s] = true;\n const closeAmt = remainingAllocation * step.closeAllocationPct;\n partialCloses.push({\n index: i,\n pnlAtFire: pnl * remainingAllocation * step.closeAllocationPct,\n allocation: closeAmt,\n trigger: 'profitAction',\n });\n remainingAllocation -= closeAmt;\n }\n }\n\n // Compute active stop floor (same logic as original)\n let activeFloor = -Infinity;\n for (const step of normalizedSteps) {\n if (runningMaxPnl >= step.armAt) {\n activeFloor = Math.max(activeFloor, step.stopAt);\n }\n }\n\n // Check if stop hit on remaining allocation\n // Scaled comparison: pnl * remainingAllocation <= activeFloor * remainingAllocation\n // Simplifies to: pnl <= activeFloor (when remainingAllocation > 0)\n if (activeFloor > -Infinity && remainingAllocation > 0 && pnl <= activeFloor) {\n const effectivePnl = pnl * remainingAllocation;\n const detail = trigger.unit === 'percent'\n ? `Profit action: stop adjusted to ${(activeFloor / scale * 100).toFixed(0)}% ($${activeFloor.toFixed(2)}) at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`\n : `Profit action: stop adjusted to $${activeFloor.toFixed(2)} at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`;\n\n return {\n fireEvent: {\n type: 'profitAction',\n firedAt: point.timestamp,\n pnlAtFire: effectivePnl,\n index: i,\n detail,\n },\n partialCloses,\n };\n }\n }\n\n return { fireEvent: null, partialCloses };\n}\n\n// ---------------------------------------------------------------------------\n// evaluateTrigger\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single trigger against the full P&L path.\n * Returns the first point where it fires, or null.\n */\nexport function evaluateTrigger(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n): TriggerFireEvent | null {\n if (pnlPath.length === 0) return null;\n\n const { type, threshold } = trigger;\n\n // State for triggers that track running values\n let runningMaxPnl = -Infinity;\n let initialSLRatio: number | null = null;\n let firstUnderlyingPrice: number | null = null;\n let firstVixPrice: number | null = null;\n let firstVix9dPrice: number | null = null;\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n // Update running max for trailingStop\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n let fired = false;\n let detail: string | undefined;\n\n switch (type) {\n case 'profitTarget': {\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdPT = trigger.unit === 'percent'\n ? threshold * Math.abs(trigger.entryCost!)\n : threshold;\n if (pnl >= dollarThresholdPT) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} >= ${(threshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} ($${dollarThresholdPT.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} >= target $${dollarThresholdPT.toFixed(2)}`;\n }\n break;\n }\n\n case 'stopLoss': {\n // Normalize negative threshold — users may pass -2 meaning \"stop at $2 loss\"\n const absThreshold = Math.abs(threshold);\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdSL = trigger.unit === 'percent'\n ? absThreshold * Math.abs(trigger.entryCost!)\n : absThreshold;\n if (pnl <= -dollarThresholdSL) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} <= -${(absThreshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} (-$${dollarThresholdSL.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} <= stop -$${dollarThresholdSL.toFixed(2)}`;\n }\n break;\n }\n\n case 'trailingStop': {\n const trailAmt = trigger.trailAmount ?? threshold;\n const dropdown = runningMaxPnl - pnl;\n if (dropdown >= trailAmt && runningMaxPnl > -Infinity) {\n fired = true;\n detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${trailAmt.toFixed(2)}`;\n }\n break;\n }\n\n case 'profitAction': {\n // Delegate to evaluateProfitAction for the full path evaluation\n // (evaluateTrigger is called point-by-point in the loop, but profitAction\n // needs full-path context for partial close tracking, so we handle it\n // by breaking out of the loop and evaluating the full path at once.)\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n return paResult.fireEvent;\n }\n\n case 'dteExit': {\n if (!trigger.expiry) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const expiryDate = parseDate(trigger.expiry);\n const dte = calendarDaysBetween(pointDate, expiryDate);\n // Only fire if point is before/on expiry\n if (pointDate <= expiryDate && dte <= threshold) {\n fired = true;\n detail = `DTE ${dte} <= threshold ${threshold}`;\n }\n break;\n }\n\n case 'ditExit': {\n if (!trigger.openDate) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const openDate = parseDate(trigger.openDate);\n const dit = calendarDaysBetween(openDate, pointDate);\n if (dit >= threshold) {\n fired = true;\n detail = `DIT ${dit} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'clockTimeExit': {\n const clockTime = trigger.clockTime ?? '15:00';\n const pointTime = extractTime(point.timestamp);\n if (pointTime >= clockTime) {\n fired = true;\n detail = `Time ${pointTime} >= ${clockTime}`;\n }\n break;\n }\n\n case 'underlyingPriceMove': {\n if (!trigger.underlyingPrices) break;\n const price = trigger.underlyingPrices.get(point.timestamp);\n if (price == null) break;\n if (firstUnderlyingPrice === null) {\n firstUnderlyingPrice = price;\n break; // Can't compute move on first price\n }\n const pctMove = ((price - firstUnderlyingPrice) / firstUnderlyingPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `Underlying moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'positionDelta': {\n const netDelta = point.netDelta ?? 0;\n if (trigger.exitAbove != null) {\n if (netDelta > trigger.exitAbove) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (netDelta < trigger.exitBelow) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else if (Math.abs(netDelta) >= threshold) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'perLegDelta': {\n if (!point.legGreeks) break;\n if (trigger.legIndex != null) {\n // Target a specific leg\n if (trigger.legIndex >= point.legGreeks.length) break;\n const legDelta = point.legGreeks[trigger.legIndex].delta ?? 0;\n if (trigger.exitAbove != null) {\n if (legDelta > trigger.exitAbove) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (legDelta < trigger.exitBelow) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else {\n // legIndex set but no directional fields — use abs() on that single leg\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n }\n } else {\n // No legIndex — iterate all legs with abs() (backward compat)\n for (let li = 0; li < point.legGreeks.length; li++) {\n const legDelta = point.legGreeks[li].delta ?? 0;\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${li} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n break;\n }\n }\n }\n break;\n }\n\n case 'vixMove': {\n if (!trigger.vixPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n if (vix == null) break;\n if (firstVixPrice === null) {\n firstVixPrice = vix;\n break;\n }\n const pctMove = ((vix - firstVixPrice) / firstVixPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dMove': {\n if (!trigger.vix9dPrices) break;\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix9d == null) break;\n if (firstVix9dPrice === null) {\n firstVix9dPrice = vix9d;\n break;\n }\n const pctMove = ((vix9d - firstVix9dPrice) / firstVix9dPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX9D moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dVixRatio': {\n if (!trigger.vixPrices || !trigger.vix9dPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix == null || vix9d == null || vix === 0) break;\n const ratio = vix9d / vix;\n // If threshold >= 1, fire when ratio >= threshold (contango deepening)\n // If threshold < 1, fire when ratio <= threshold (backwardation)\n const crosses = threshold >= 1 ? ratio >= threshold : ratio <= threshold;\n if (crosses) {\n fired = true;\n detail = `VIX9D/VIX ratio ${ratio.toFixed(4)} crossed threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioThreshold': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (slRatio >= threshold) {\n fired = true;\n detail = `S/L ratio ${slRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioMove': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (initialSLRatio === null) {\n initialSLRatio = slRatio;\n break; // Can't compute change on first point\n }\n const change = Math.abs(slRatio - initialSLRatio);\n if (change >= threshold) {\n fired = true;\n detail = `S/L ratio change ${change.toFixed(4)} from initial ${initialSLRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n }\n\n if (fired) {\n return {\n type,\n firedAt: point.timestamp,\n pnlAtFire: pnl,\n index: i,\n detail,\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// computeGroupPnl\n// ---------------------------------------------------------------------------\n\n/**\n * Compute per-group P&L at each timestamp from leg prices.\n * groupPnl[t] = sum over legIndices of (legPrices[i] - entryPrice[i]) * quantity[i] * multiplier[i]\n */\nfunction computeGroupPnl(\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n legIndices: number[],\n): number[] {\n return pnlPath.map((point) => {\n let groupPnl = 0;\n for (const idx of legIndices) {\n if (idx < legs.length && idx < point.legPrices.length) {\n const leg = legs[idx];\n const markPrice = point.legPrices[idx];\n groupPnl += (markPrice - leg.entryPrice) * leg.quantity * leg.multiplier;\n }\n }\n return groupPnl;\n });\n}\n\n// ---------------------------------------------------------------------------\n// analyzeExitTriggers\n// ---------------------------------------------------------------------------\n\n/**\n * Run all triggers against the P&L path, find first-to-fire,\n * compute actual exit comparison, and evaluate leg group triggers.\n */\nexport function analyzeExitTriggers(config: {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n triggers: ExitTriggerConfig[];\n actualExitTimestamp?: string;\n legGroups?: LegGroupConfig[];\n}): {\n overall: ExitTriggerResult;\n legGroups?: LegGroupResult[];\n} {\n const { pnlPath, legs, triggers, actualExitTimestamp, legGroups } = config;\n\n // Evaluate all triggers\n const fireEvents: TriggerFireEvent[] = [];\n let allPartialCloses: PartialClose[] = [];\n for (const trigger of triggers) {\n if (trigger.type === 'profitAction') {\n // Use the partial-close-aware helper for profitAction\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n if (paResult.fireEvent) {\n fireEvents.push(paResult.fireEvent);\n }\n if (paResult.partialCloses.length > 0) {\n allPartialCloses = allPartialCloses.concat(paResult.partialCloses);\n }\n } else {\n const event = evaluateTrigger(trigger, pnlPath, legs);\n if (event) {\n fireEvents.push(event);\n }\n }\n }\n\n // Sort by fire index (earliest first)\n fireEvents.sort((a, b) => a.index - b.index);\n\n const firstToFire = fireEvents.length > 0 ? fireEvents[0] : null;\n\n // Actual exit comparison\n let actualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && firstToFire) {\n // Find closest point to actualExitTimestamp\n let closestIdx = 0;\n let closestDist = Infinity;\n for (let i = 0; i < pnlPath.length; i++) {\n // Simple string comparison — timestamps are lexicographically ordered\n const dist = Math.abs(pnlPath[i].timestamp.localeCompare(actualExitTimestamp));\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n // Fallback: use last point if actualExitTimestamp is after all points\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualPnl = pnlPath[closestIdx].strategyPnl;\n actualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualPnl,\n pnlDifference: firstToFire.pnlAtFire - actualPnl,\n };\n }\n\n // Build summary\n let summary: string;\n if (!firstToFire) {\n summary = `No triggers fired across ${pnlPath.length} data points.`;\n } else if (actualExit) {\n const betterWorse = actualExit.pnlDifference > 0 ? 'better' : 'worse';\n summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). ` +\n `Trigger was $${Math.abs(actualExit.pnlDifference).toFixed(2)} ${betterWorse}.`;\n } else {\n summary = `${firstToFire.type} fired first at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `${fireEvents.length} trigger(s) fired total.`;\n }\n\n const overall: ExitTriggerResult = {\n triggers: fireEvents,\n firstToFire,\n actualExit,\n partialCloses: allPartialCloses.length > 0 ? allPartialCloses : undefined,\n summary,\n };\n\n // Leg group evaluation\n let legGroupResults: LegGroupResult[] | undefined;\n if (legGroups && legGroups.length > 0) {\n legGroupResults = legGroups.map((group) => {\n const groupPnlArr = computeGroupPnl(pnlPath, legs, group.legIndices);\n\n // Build a synthetic PnlPoint[] for this group with groupPnl as strategyPnl\n const groupPath: PnlPoint[] = pnlPath.map((point, idx) => ({\n ...point,\n strategyPnl: groupPnlArr[idx],\n // Filter legPrices/legGreeks to only this group's legs\n legPrices: group.legIndices.map((li) => point.legPrices[li] ?? 0),\n legGreeks: point.legGreeks\n ? group.legIndices.map((li) => point.legGreeks![li])\n : undefined,\n }));\n\n // Build group legs subset\n const groupLegs = group.legIndices.map((li) => legs[li]);\n\n // Evaluate per-group triggers\n const groupFireEvents: TriggerFireEvent[] = [];\n for (const trigger of group.triggers) {\n const event = evaluateTrigger(trigger, groupPath, groupLegs);\n if (event) groupFireEvents.push(event);\n }\n groupFireEvents.sort((a, b) => a.index - b.index);\n\n const groupFirstToFire = groupFireEvents.length > 0 ? groupFireEvents[0] : null;\n\n // Actual exit for group\n let groupActualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && groupFirstToFire) {\n let closestIdx = pnlPath.length - 1;\n for (let i = 0; i < pnlPath.length; i++) {\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n }\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualGroupPnl = groupPnlArr[closestIdx];\n groupActualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualGroupPnl,\n pnlDifference: groupFirstToFire.pnlAtFire - actualGroupPnl,\n };\n }\n\n const groupSummary = groupFirstToFire\n ? `${group.label}: ${groupFirstToFire.type} fired at ${groupFirstToFire.firedAt} (group P&L $${groupFirstToFire.pnlAtFire.toFixed(2)})`\n : `${group.label}: No triggers fired.`;\n\n return {\n label: group.label,\n result: {\n triggers: groupFireEvents,\n firstToFire: groupFirstToFire,\n actualExit: groupActualExit,\n summary: groupSummary,\n },\n groupPnl: groupPnlArr,\n };\n });\n }\n\n return {\n overall,\n legGroups: legGroupResults,\n };\n}\n","/**\n * Exit Analysis Tools\n *\n * MCP tools for analyzing exit triggers and decomposing P&L into greek factor\n * contributions. Both tools run trade replay internally -- a single tool call\n * fetches data, replays the trade, and analyzes the results.\n *\n * Tools registered:\n * - analyze_exit_triggers -- Evaluate 15 trigger types against a replay P&L path\n * - decompose_greeks -- Decompose P&L into delta/gamma/theta/vega/residual factors\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { handleReplayTrade } from \"./replay.js\";\nimport { getProvider } from \"../utils/market-provider.js\";\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type LegGroupConfig,\n} from \"../utils/exit-triggers.js\";\nimport {\n decomposeGreeks,\n type LegGroupDef,\n} from \"../utils/greeks-decomposition.js\";\nimport { markPrice } from \"../utils/trade-replay.js\";\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Leg schema (shared between both tools)\n// ---------------------------------------------------------------------------\n\nconst legSchema = z.object({\n ticker: z.string(),\n strike: z.number(),\n type: z.enum([\"C\", \"P\"]),\n expiry: z.string(),\n quantity: z.number(),\n entry_price: z.number(),\n});\n\n// ---------------------------------------------------------------------------\n// analyze_exit_triggers schema\n// ---------------------------------------------------------------------------\n\nexport const analyzeExitTriggersSchema = z.object({\n // Replay inputs (same as replay_trade per D-02)\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n // Trigger configs per D-03\n triggers: z.array(triggerConfigSchema)\n .describe(\"Exit triggers to evaluate against the P&L path\"),\n\n // Per D-05\n actual_exit_timestamp: z.string().optional()\n .describe(\"Actual exit time for comparison (format: YYYY-MM-DD HH:MM)\"),\n\n // Per D-06\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' omits per-step trigger states, 'full' includes all fire events\"),\n});\n\n// ---------------------------------------------------------------------------\n// decompose_greeks schema\n// ---------------------------------------------------------------------------\n\nexport const decomposeGreeksSchema = z.object({\n // Same replay inputs\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n // Per D-08\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n })).optional().describe(\"Leg grouping for per-group vega attribution (e.g., front_month vs back_month)\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' shows ranked factors, 'full' includes per-step contributions\"),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Reverse-map weekly roots to standard root for underlying/VIX fetching\nconst REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n};\n\n/**\n * Extract the underlying root ticker from the first replay leg's OCC ticker.\n * Maps weekly roots (SPXW, NDXP) back to their standard root.\n */\nfunction extractUnderlyingTicker(occTicker: string): string {\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n}\n\n/**\n * Fetch VIX or VIX9D minute bars and build a timestamp->price map.\n */\nasync function fetchPriceMap(\n ticker: string,\n from: string,\n to: string,\n): Promise<Map<string, number>> {\n const map = new Map<string, number>();\n try {\n const bars = await getProvider().fetchBars({\n ticker,\n from,\n to,\n timespan: \"minute\",\n assetClass: \"index\",\n });\n for (const b of bars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n map.set(ts, markPrice(b));\n }\n } catch {\n // VIX/VIX9D data is best-effort\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// handleAnalyzeExitTriggers\n// ---------------------------------------------------------------------------\n\nexport async function handleAnalyzeExitTriggers(\n params: z.infer<typeof analyzeExitTriggersSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<ReturnType<typeof analyzeExitTriggers>> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n triggers, actual_exit_timestamp, leg_groups,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // Compute entry cost for percentage-based triggers (D-11)\n const entryCost = replayLegs.reduce((sum, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n if (pnlPath.length === 0) {\n return {\n overall: {\n triggers: [],\n firstToFire: null,\n summary: 'No P&L data available from replay.',\n },\n };\n }\n\n // 2. Determine date range from replay path\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n // 3. Check which external data maps are needed\n const allTriggerTypes = new Set(triggers.map(t => t.type));\n const groupTriggerTypes = new Set(\n (leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))\n );\n for (const t of groupTriggerTypes) allTriggerTypes.add(t);\n\n // Determine underlying ticker for underlying price triggers\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0].occTicker);\n\n // Fetch VIX/VIX9D/underlying price maps as needed\n let vixPrices: Map<string, number> | undefined;\n let vix9dPrices: Map<string, number> | undefined;\n let underlyingPrices: Map<string, number> | undefined;\n\n const needsVix = allTriggerTypes.has('vixMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsVix9d = allTriggerTypes.has('vix9dMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsUnderlying = allTriggerTypes.has('underlyingPriceMove');\n\n if (needsVix) {\n vixPrices = await fetchPriceMap('VIX', firstDate, lastDate);\n }\n if (needsVix9d) {\n vix9dPrices = await fetchPriceMap('VIX9D', firstDate, lastDate);\n }\n if (needsUnderlying) {\n underlyingPrices = await fetchPriceMap(\n underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map tool trigger params to ExitTriggerConfig[] with data maps\n const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n }));\n\n // 5. Map leg groups with their triggers\n const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n })),\n }));\n\n // 6. Run the pure analysis engine\n return analyzeExitTriggers({\n pnlPath,\n legs: replayLegs,\n triggers: exitTriggers,\n actualExitTimestamp: actual_exit_timestamp,\n legGroups: legGroupConfigs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// handleDecomposeGreeks\n// ---------------------------------------------------------------------------\n\nexport async function handleDecomposeGreeks(\n params: z.infer<typeof decomposeGreeksSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<import(\"../utils/greeks-decomposition.js\").GreeksDecompositionResult> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n leg_groups, format,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // 2. Check greeks data availability\n if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {\n throw new Error(\n \"No greeks data available. Ensure MASSIVE_API_KEY is set and underlying price data exists.\"\n );\n }\n\n // 3. Fetch underlying prices for decomposition\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0]?.occTicker ?? '');\n let underlyingPrices: Map<string, number> | undefined;\n\n if (pnlPath.length > 0 && underlyingTicker) {\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n underlyingPrices = await fetchPriceMap(\n underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map leg groups\n const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n }));\n\n // 5. Build leg pricing inputs from OCC tickers for full revaluation\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n const legPricingInputs = replayLegs.map(leg => {\n const m = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!m) return { strike: 0, type: 'C' as const, expiryDate: '' };\n return {\n strike: parseInt(m[3], 10) / 1000,\n type: m[2] as 'C' | 'P',\n expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,\n };\n });\n\n // 6. Run decomposition with full revaluation\n const result = decomposeGreeks({\n pnlPath,\n legs: replayLegs,\n underlyingPrices,\n legGroups: legGroupDefs,\n legPricingInputs,\n riskFreeRate: 0.045,\n dividendYield: divYield,\n });\n\n // 7. Strip steps if format=\"summary\"\n if (format === \"summary\") {\n for (const factor of result.factors) {\n factor.steps = [];\n }\n if (result.legGroupVega) {\n for (const group of result.legGroupVega) {\n group.steps = [];\n }\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n): void {\n server.registerTool(\n \"analyze_exit_triggers\",\n {\n description:\n \"Analyze when exit triggers would fire on a trade replay. Runs replay internally \" +\n \"-- provide block_id + trade_index or explicit legs. Evaluates 14 trigger types \" +\n \"(profit target, stop loss, trailing stop, DTE, DIT, clock time, underlying move, \" +\n \"delta, VIX moves, S/L ratio) against minute-by-minute P&L path with greeks. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss (requires MASSIVE_API_KEY).\",\n inputSchema: analyzeExitTriggersSchema,\n },\n async (params) => {\n try {\n const result = await handleAnalyzeExitTriggers(params, baseDir);\n\n const summary = result.overall.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error analyzing exit triggers: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n server.registerTool(\n \"decompose_greeks\",\n {\n description:\n \"Decompose a trade's P&L into greek factor contributions (delta, gamma, theta, \" +\n \"vega, residual). Runs replay internally. Shows which factor drove P&L movement \" +\n \"and by how much. For calendar/double-calendar strategies, includes per-leg-group \" +\n \"vega attribution showing front vs back month IV divergence. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss (requires MASSIVE_API_KEY).\",\n inputSchema: decomposeGreeksSchema,\n },\n async (params) => {\n try {\n const result = await handleDecomposeGreeks(params, baseDir);\n\n const summary = result.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error decomposing greeks: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Batch Exit Analysis Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that takes pre-analyzed\n * trade inputs and a candidate exit policy, evaluates the policy against each\n * trade's P&L path, and computes aggregate statistics with per-trigger attribution.\n *\n * This is the computational heart of the `batch_exit_analysis` MCP tool.\n */\n\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type TriggerType,\n type LegGroupConfig,\n type PartialClose,\n} from './exit-triggers.js';\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type BaselineMode = 'actual' | 'holdToEnd';\n\nexport interface BatchExitConfig {\n /** Triggers to evaluate as candidate exit policy. */\n candidatePolicy: ExitTriggerConfig[];\n /** Optional per-group triggers (passed through to analyzeExitTriggers). */\n legGroups?: LegGroupConfig[];\n /** Baseline mode: 'actual' uses tradelog P&L, 'holdToEnd' uses last path point. */\n baselineMode: BaselineMode;\n /** Output density: 'summary' omits per-trade breakdown; 'full' includes it. */\n format: 'summary' | 'full';\n}\n\nexport interface TradeInput {\n tradeIndex: number;\n /** Trade open date YYYY-MM-DD */\n dateOpened: string;\n /** Actual P&L from tradelog pl field (used when baselineMode='actual'). */\n actualPnl: number;\n /** Full replay P&L path from trade-replay module. */\n pnlPath: PnlPoint[];\n /** Replay legs parallel to pnlPath.legPrices. */\n legs: ReplayLeg[];\n /** Entry cost for percentage-based triggers (D-11). */\n entryCost?: number;\n}\n\nexport interface TradeExitResult {\n tradeIndex: number;\n dateOpened: string;\n /** Actual P&L from tradelog. */\n actualPnl: number;\n /** P&L if candidate policy was applied. */\n candidatePnl: number;\n /** Baseline P&L (actual or holdToEnd). */\n baselinePnl: number;\n /** candidatePnl - baselinePnl */\n pnlDelta: number;\n /** Which trigger fired first, or 'noTrigger'. */\n triggerFired: TriggerType | 'noTrigger';\n /** Timestamp when trigger fired, or null. */\n fireTimestamp: string | null;\n /** Partial position closes from profitAction steps (if any). */\n partialCloses?: PartialClose[];\n}\n\nexport interface TriggerAttribution {\n trigger: TriggerType | 'noTrigger';\n /** How many trades this trigger fired first on. */\n count: number;\n /** Average candidate P&L when this trigger fired. */\n avgPnl: number;\n /** Sum candidate P&L for this trigger group. */\n totalPnl: number;\n /** Average pnlDelta vs baseline for this trigger group. */\n avgDelta: number;\n}\n\nexport interface AggregateStats {\n totalTrades: number;\n /** candidatePnl > 0 */\n winningTrades: number;\n /** candidatePnl < 0 */\n losingTrades: number;\n /** winningTrades / totalTrades */\n winRate: number;\n /** Sum of candidatePnl */\n totalPnl: number;\n /** Mean candidatePnl */\n avgPnl: number;\n /** Mean of winning candidatePnls */\n avgWin: number;\n /** Mean of losing candidatePnls */\n avgLoss: number;\n maxWin: number;\n maxLoss: number;\n /** sum(wins) / abs(sum(losses)); Infinity if no losses */\n profitFactor: number;\n /** Max sequential drawdown from equity curve (cumsum of candidatePnls) */\n maxDrawdown: number;\n /** mean/stddev of candidatePnls; null if < 2 trades */\n sharpeRatio: number | null;\n maxWinStreak: number;\n maxLossStreak: number;\n // Deltas vs baseline\n baselineTotalPnl: number;\n /** totalPnl - baselineTotalPnl */\n totalPnlDelta: number;\n baselineWinRate: number;\n}\n\nexport interface BatchExitResult {\n aggregate: AggregateStats;\n triggerAttribution: TriggerAttribution[];\n /** Empty if format='summary'. */\n perTrade: TradeExitResult[];\n baselineMode: BaselineMode;\n summary: string;\n profileContext?: {\n structureType: string;\n exitRules: string[];\n };\n /** Trades skipped due to replay errors (D-15). */\n skippedTrades?: Array<{ tradeIndex: number; dateOpened: string; error: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// computeAggregateStats\n// ---------------------------------------------------------------------------\n\n/**\n * Compute aggregate statistics from a set of per-trade exit results.\n */\nexport function computeAggregateStats(tradeResults: TradeExitResult[]): AggregateStats {\n if (tradeResults.length === 0) {\n return {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n };\n }\n\n const candidatePnls = tradeResults.map(r => r.candidatePnl);\n const baselinePnls = tradeResults.map(r => r.baselinePnl);\n\n const winningTrades = candidatePnls.filter(p => p > 0).length;\n const losingTrades = candidatePnls.filter(p => p < 0).length;\n const totalTrades = tradeResults.length;\n const winRate = winningTrades / totalTrades;\n\n const totalPnl = candidatePnls.reduce((sum, p) => sum + p, 0);\n const avgPnl = totalPnl / totalTrades;\n\n const wins = candidatePnls.filter(p => p > 0);\n const losses = candidatePnls.filter(p => p < 0);\n\n const avgWin = wins.length > 0 ? wins.reduce((s, p) => s + p, 0) / wins.length : 0;\n const avgLoss = losses.length > 0 ? losses.reduce((s, p) => s + p, 0) / losses.length : 0;\n const maxWin = wins.length > 0 ? Math.max(...wins) : 0;\n const maxLoss = losses.length > 0 ? Math.min(...losses) : 0;\n\n // Profit factor: sum(wins) / abs(sum(losses)), Infinity if no losses\n const sumWins = wins.reduce((s, p) => s + p, 0);\n const sumLosses = losses.reduce((s, p) => s + p, 0);\n const profitFactor = losses.length === 0\n ? Infinity\n : sumWins / Math.abs(sumLosses);\n\n // Max drawdown from equity curve (cumsum of candidatePnls)\n let runningPeak = 0;\n let equity = 0;\n let maxDrawdown = 0;\n for (const pnl of candidatePnls) {\n equity += pnl;\n if (equity > runningPeak) runningPeak = equity;\n const dd = runningPeak - equity;\n if (dd > maxDrawdown) maxDrawdown = dd;\n }\n\n // Sharpe ratio: mean / sample stddev (N-1), null if < 2 trades\n let sharpeRatio: number | null = null;\n if (totalTrades >= 2) {\n const mean = avgPnl;\n const variance =\n candidatePnls.reduce((sum, p) => sum + (p - mean) ** 2, 0) / (totalTrades - 1);\n const stddev = Math.sqrt(variance);\n sharpeRatio = stddev === 0 ? null : mean / stddev;\n }\n\n // Win/loss streaks\n let maxWinStreak = 0;\n let maxLossStreak = 0;\n let currentWinStreak = 0;\n let currentLossStreak = 0;\n for (const pnl of candidatePnls) {\n if (pnl > 0) {\n currentWinStreak++;\n currentLossStreak = 0;\n if (currentWinStreak > maxWinStreak) maxWinStreak = currentWinStreak;\n } else if (pnl < 0) {\n currentLossStreak++;\n currentWinStreak = 0;\n if (currentLossStreak > maxLossStreak) maxLossStreak = currentLossStreak;\n } else {\n // Breakeven — reset both streaks\n currentWinStreak = 0;\n currentLossStreak = 0;\n }\n }\n\n // Baseline aggregates\n const baselineTotalPnl = baselinePnls.reduce((sum, p) => sum + p, 0);\n const baselineWins = baselinePnls.filter(p => p > 0).length;\n const baselineWinRate = baselineWins / totalTrades;\n\n return {\n totalTrades,\n winningTrades,\n losingTrades,\n winRate,\n totalPnl,\n avgPnl,\n avgWin,\n avgLoss,\n maxWin,\n maxLoss,\n profitFactor,\n maxDrawdown,\n sharpeRatio,\n maxWinStreak,\n maxLossStreak,\n baselineTotalPnl,\n totalPnlDelta: totalPnl - baselineTotalPnl,\n baselineWinRate,\n };\n}\n\n// ---------------------------------------------------------------------------\n// computeTriggerAttribution\n// ---------------------------------------------------------------------------\n\n/**\n * Group trade results by which trigger fired first.\n * Returns attribution sorted by count descending.\n */\nexport function computeTriggerAttribution(\n tradeResults: TradeExitResult[],\n): TriggerAttribution[] {\n const groups = new Map<\n TriggerType | 'noTrigger',\n { count: number; totalPnl: number; totalDelta: number }\n >();\n\n for (const result of tradeResults) {\n const key = result.triggerFired;\n const existing = groups.get(key);\n if (existing) {\n existing.count++;\n existing.totalPnl += result.candidatePnl;\n existing.totalDelta += result.pnlDelta;\n } else {\n groups.set(key, {\n count: 1,\n totalPnl: result.candidatePnl,\n totalDelta: result.pnlDelta,\n });\n }\n }\n\n return Array.from(groups.entries())\n .map(([trigger, { count, totalPnl, totalDelta }]) => ({\n trigger,\n count,\n totalPnl,\n avgPnl: totalPnl / count,\n avgDelta: totalDelta / count,\n }))\n .sort((a, b) => b.count - a.count);\n}\n\n// ---------------------------------------------------------------------------\n// analyzeBatch\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a candidate exit policy against a set of trade replay results.\n *\n * For each trade:\n * 1. Run analyzeExitTriggers with the candidate policy against the trade's P&L path.\n * 2. candidatePnl = firstToFire.pnlAtFire if trigger fired, else last path point P&L.\n * 3. baselinePnl = trade.actualPnl if baselineMode='actual', else last path point P&L.\n * 4. Build TradeExitResult.\n *\n * Then compute aggregate stats and trigger attribution.\n */\nexport function analyzeBatch(\n trades: TradeInput[],\n config: BatchExitConfig,\n): BatchExitResult {\n if (trades.length === 0) {\n const emptyAggregate = computeAggregateStats([]);\n return {\n aggregate: emptyAggregate,\n triggerAttribution: [],\n perTrade: [],\n baselineMode: config.baselineMode,\n summary: 'Analyzed 0 trades: no data.',\n };\n }\n\n const { candidatePolicy, legGroups, baselineMode, format } = config;\n\n const perTradeResults: TradeExitResult[] = trades.map(trade => {\n const { pnlPath, legs, actualPnl, tradeIndex, dateOpened, entryCost } = trade;\n\n // Last path point P&L — used as holdToEnd value\n const lastPnl = pnlPath.length > 0\n ? pnlPath[pnlPath.length - 1].strategyPnl\n : 0;\n\n // Copy entryCost onto each trigger config for percentage-based triggers (D-11)\n const triggersWithCost = candidatePolicy.map(t => ({\n ...t,\n entryCost,\n }));\n\n const legGroupsWithCost = legGroups?.map(group => ({\n ...group,\n triggers: group.triggers.map(trigger => ({\n ...trigger,\n entryCost,\n })),\n }));\n\n // Run exit trigger analysis with candidate policy\n const analysisResult = analyzeExitTriggers({\n pnlPath,\n legs,\n triggers: triggersWithCost,\n legGroups: legGroupsWithCost,\n });\n\n const { firstToFire, partialCloses } = analysisResult.overall;\n\n // Candidate P&L: account for partial closes from profitAction steps\n let candidatePnl: number;\n if (partialCloses && partialCloses.length > 0) {\n // Sum of partial close P&Ls\n const partialPnl = partialCloses.reduce((sum, pc) => sum + pc.pnlAtFire, 0);\n const closedAllocation = partialCloses.reduce((sum, pc) => sum + pc.allocation, 0);\n const remainingAllocation = 1 - closedAllocation;\n // Remaining position: firstToFire.pnlAtFire already reflects remaining allocation,\n // or if no trigger fired, scale last P&L by remaining allocation\n const remainingPnl = firstToFire !== null\n ? firstToFire.pnlAtFire\n : lastPnl * remainingAllocation;\n candidatePnl = partialPnl + remainingPnl;\n } else {\n // No partial closes: original behavior\n candidatePnl = firstToFire !== null ? firstToFire.pnlAtFire : lastPnl;\n }\n\n // Baseline P&L depends on mode\n const baselinePnl = baselineMode === 'actual' ? actualPnl : lastPnl;\n\n const pnlDelta = candidatePnl - baselinePnl;\n\n const triggerFired: TriggerType | 'noTrigger' =\n firstToFire !== null ? firstToFire.type : 'noTrigger';\n const fireTimestamp = firstToFire !== null ? firstToFire.firedAt : null;\n\n return {\n tradeIndex,\n dateOpened,\n actualPnl,\n candidatePnl,\n baselinePnl,\n pnlDelta,\n triggerFired,\n fireTimestamp,\n partialCloses: partialCloses && partialCloses.length > 0 ? partialCloses : undefined,\n };\n });\n\n const aggregate = computeAggregateStats(perTradeResults);\n const triggerAttribution = computeTriggerAttribution(perTradeResults);\n\n // Build summary string\n const topTrigger = triggerAttribution.length > 0 ? triggerAttribution[0] : null;\n const topTriggerStr = topTrigger\n ? `Top trigger: ${topTrigger.trigger} fired on ${topTrigger.count} trades.`\n : 'No triggers fired.';\n\n const summary =\n `Analyzed ${trades.length} trades: candidate win rate ${(aggregate.winRate * 100).toFixed(1)}%, ` +\n `total P&L $${aggregate.totalPnl.toFixed(2)} (delta $${aggregate.totalPnlDelta.toFixed(2)} vs baseline). ` +\n topTriggerStr;\n\n return {\n aggregate,\n triggerAttribution,\n perTrade: format === 'summary' ? [] : perTradeResults,\n baselineMode,\n summary,\n };\n}\n","/**\n * Batch Exit Analysis Tool\n *\n * MCP tool that evaluates a candidate exit policy across multiple trades in a\n * block. Queries trades from DuckDB, replays each one (checking market.intraday\n * cache before fetching from Massive), evaluates the candidate policy via the\n * pure batch exit analysis engine, and returns aggregate statistics with\n * per-trigger attribution.\n *\n * Tools registered:\n * - batch_exit_analysis -- Evaluate a candidate exit policy across an entire block\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { handleReplayTrade } from \"./replay.js\";\nimport {\n analyzeBatch,\n type TradeInput,\n type BatchExitConfig,\n type BatchExitResult,\n} from \"../utils/batch-exit-analysis.js\";\nimport { getProfile } from \"../db/profile-schemas.js\";\nimport type { ExitTriggerConfig, LegGroupConfig } from \"../utils/exit-triggers.js\";\n\n// ---------------------------------------------------------------------------\n// Concurrency limiter — hand-rolled semaphore, no external dependency (D-15)\n// ---------------------------------------------------------------------------\n\n/**\n * Simple concurrency limiter. Runs async tasks with at most `limit` in flight.\n * No external dependency — hand-rolled semaphore pattern per D-15.\n */\nasync function mapWithLimit<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let idx = 0;\n\n async function worker(): Promise<void> {\n while (idx < items.length) {\n const i = idx++;\n results[i] = await fn(items[i]);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum (mirrors exit-analysis.ts)\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Zod Schema\n// ---------------------------------------------------------------------------\n\nexport const batchExitAnalysisSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze trades from\"),\n\n strategy: z.string().optional()\n .describe(\"Filter trades by strategy name (case-insensitive ILIKE)\"),\n\n date_range: z.object({\n from: z.string().optional().describe(\"Start date YYYY-MM-DD\"),\n to: z.string().optional().describe(\"End date YYYY-MM-DD\"),\n }).optional().describe(\"Filter trades by date range\"),\n\n candidate_policy: z.array(triggerConfigSchema)\n .describe(\"Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n baseline_mode: z.enum(['actual', 'holdToEnd']).default('actual')\n .describe(\"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp\"),\n\n limit: z.number().min(1).max(200).default(50)\n .describe(\"Max trades to analyze. Most recent trades selected\"),\n\n min_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L >= this value\"),\n\n max_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L <= this value\"),\n\n multiplier: z.number().default(100)\n .describe(\"Contract multiplier (default 100)\"),\n\n format: z.enum(['summary', 'full']).default('summary')\n .describe(\"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleBatchExitAnalysis(\n params: z.infer<typeof batchExitAnalysisSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<BatchExitResult> {\n const {\n block_id,\n strategy,\n date_range,\n candidate_policy,\n leg_groups,\n baseline_mode,\n limit,\n min_pl,\n max_pl,\n multiplier,\n format,\n } = params;\n\n // 1. Query trades from DuckDB with deterministic ROW_NUMBER ordering\n const conn = injectedConn ?? await getConnection(baseDir);\n const escapedBlockId = block_id.replace(/'/g, \"''\");\n\n // Build WHERE clauses\n const whereClauses: string[] = [`block_id = '${escapedBlockId}'`];\n\n if (strategy) {\n const escapedStrategy = strategy.replace(/'/g, \"''\");\n whereClauses.push(`strategy ILIKE '%${escapedStrategy}%'`);\n }\n if (date_range?.from) {\n whereClauses.push(`date_opened >= '${date_range.from}'`);\n }\n if (date_range?.to) {\n whereClauses.push(`date_opened <= '${date_range.to}'`);\n }\n if (min_pl !== undefined) {\n whereClauses.push(`pl >= ${min_pl}`);\n }\n if (max_pl !== undefined) {\n whereClauses.push(`pl <= ${max_pl}`);\n }\n\n // ROW_NUMBER must be computed over the FULL block (no strategy/date filters)\n // because handleReplayTrade resolves trade_index as OFFSET against the full block.\n // Filters are applied AFTER numbering to preserve the global index.\n const filterClauses = whereClauses.slice(1); // drop block_id clause (already in CTE)\n const query = `\n WITH numbered AS (\n SELECT *, ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_idx\n FROM trades.trade_data\n WHERE block_id = '${escapedBlockId}'\n )\n SELECT trade_idx, pl, date_opened\n FROM numbered\n ${filterClauses.length > 0 ? 'WHERE ' + filterClauses.join(' AND ') : ''}\n ORDER BY date_opened DESC\n LIMIT ${limit}\n `;\n\n const queryResult = await conn.runAndReadAll(query);\n const rows = queryResult.getRows();\n\n if (rows.length === 0) {\n const emptyResult: BatchExitResult = {\n aggregate: {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n },\n triggerAttribution: [],\n perTrade: [],\n baselineMode: baseline_mode,\n summary: 'Analyzed 0 trades: no matching trades found.',\n };\n return emptyResult;\n }\n\n // 2. Replay trades in parallel with concurrency limit (D-14)\n const MAX_CONCURRENT_REPLAYS = 5;\n\n type ReplayOutcome =\n | { ok: true; input: TradeInput }\n | { ok: false; tradeIndex: number; dateOpened: string; error: string };\n\n const outcomes = await mapWithLimit(\n rows,\n MAX_CONCURRENT_REPLAYS,\n async (row): Promise<ReplayOutcome> => {\n const tradeIdx = Number(row[0] ?? 0);\n const pl = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? '');\n\n try {\n // Always pass format:'full' to get complete pnlPath for analyzeBatch.\n // params.format controls the batch output density, not the replay resolution.\n const replayResult = await handleReplayTrade(\n {\n block_id,\n trade_index: tradeIdx,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n // Compute entry cost for percentage-based triggers (D-11)\n const tradeEntryCost = replayResult.legs.reduce((sum: number, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n return {\n ok: true,\n input: {\n tradeIndex: tradeIdx,\n dateOpened,\n actualPnl: pl,\n pnlPath: replayResult.pnlPath,\n legs: replayResult.legs,\n entryCost: tradeEntryCost,\n },\n };\n } catch (err) {\n return {\n ok: false,\n tradeIndex: Number(row[0] ?? 0),\n dateOpened: String(row[2] ?? ''),\n error: err instanceof Error ? err.message : String(err),\n };\n }\n },\n );\n\n const tradeInputs: TradeInput[] = [];\n const skippedTrades: Array<{ tradeIndex: number; dateOpened: string; error: string }> = [];\n\n for (const outcome of outcomes) {\n if (outcome.ok) {\n tradeInputs.push(outcome.input);\n } else {\n skippedTrades.push({\n tradeIndex: outcome.tradeIndex,\n dateOpened: outcome.dateOpened,\n error: outcome.error,\n });\n }\n }\n\n // 3. Build BatchExitConfig\n const config: BatchExitConfig = {\n candidatePolicy: candidate_policy as ExitTriggerConfig[],\n legGroups: leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers as ExitTriggerConfig[],\n })) as LegGroupConfig[] | undefined,\n baselineMode: baseline_mode,\n format,\n };\n\n // 4. Run the pure batch analysis engine\n const result = analyzeBatch(tradeInputs, config);\n\n // 5. Augment summary with skip info if any trades were skipped\n if (skippedTrades.length > 0) {\n result.summary = result.summary.replace(\n /^Analyzed (\\d+) trades/,\n `Analyzed ${tradeInputs.length} trades (${skippedTrades.length} skipped due to replay errors)`,\n );\n result.skippedTrades = skippedTrades;\n }\n\n // 6. Load profile context if strategy is specified (per D-16)\n if (strategy) {\n try {\n const profileConn = injectedConn ?? await getConnection(baseDir);\n const profile = await getProfile(profileConn, block_id, strategy);\n if (profile) {\n result.profileContext = {\n structureType: profile.structureType,\n exitRules: profile.exitRules.map(r =>\n r.description ?? `${r.type} ${r.trigger}`\n ),\n };\n }\n } catch {\n // Profile is informational context, not critical — swallow errors\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerBatchExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n): void {\n server.registerTool(\n \"batch_exit_analysis\",\n {\n description:\n \"Analyze how a candidate exit policy would perform across multiple trades in a block. \" +\n \"Replays each matching trade, evaluates exit triggers against the minute-level P&L path, \" +\n \"and returns aggregate statistics (win rate, Sharpe, profit factor, drawdown) comparable \" +\n \"to get_statistics. Includes per-trigger attribution showing which triggers drive outcomes. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss \" +\n \"(requires MASSIVE_API_KEY). Use with strategy profiles to iterate on exit rules.\",\n inputSchema: batchExitAnalysisSchema,\n },\n async (params) => {\n try {\n const result = await handleBatchExitAnalysis(params, baseDir);\n return createToolOutput(result.summary, result);\n } catch (error) {\n return {\n content: [{\n type: \"text\" as const,\n text: `Error in batch exit analysis: ${(error as Error).message}`,\n }],\n isError: true,\n };\n }\n }\n );\n}\n"],"mappings":"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","names":["sql","params","BATCH_SIZE","forward","iv","quotesEnabled","INDEX_TICKERS","resolvedClass","rows","mappedRows","dateRange","inserted","updated","skipped","z","z","z","getNum","getRaw","z","z","formatTradeDate","getTradeLookupKey","uniqueTradeLookupKeys","resultToRecords","recordsByTickerDate","getNum","VOL_REGIME_LABELS","z","z","bars","z","REVERSE_ROOT_MAP","z","z","h1","m1","h2","m2","markPrice","z","z","z","triggerTypeEnum","z","triggerConfigSchema"]}
|
|
1
|
+
{"version":3,"sources":["../src/tools/shared/filters.ts","../src/utils/schema-metadata.ts","../src/utils/field-timing.ts","../src/utils/data-availability.ts","../src/utils/market-enricher.ts","../src/utils/market-importer.ts","../src/utils/providers/massive.ts","../src/utils/black-scholes.ts","../src/utils/providers/thetadata.ts","../src/utils/market-provider.ts","../src/utils/analysis-stats.ts","../src/utils/filter-predicates.ts","../src/tools/profiles.ts","../src/utils/output-formatter.ts","../src/tools/profile-analysis.ts","../src/tools/regime-advisor.ts","../src/utils/trade-replay.ts","../src/tools/replay.ts","../src/utils/bar-cache.ts","../src/tools/snapshot.ts","../src/utils/greeks-decomposition.ts","../src/utils/exit-triggers.ts","../src/tools/exit-analysis.ts","../src/utils/batch-exit-analysis.ts","../src/tools/batch-exit-analysis.ts"],"sourcesContent":["/**\n * Shared Filter Utilities\n *\n * Common filtering functions used across block and report tools.\n */\n\nimport type { Trade, DailyLogEntry } from \"@tradeblocks/lib\";\n\n/**\n * Filter trades by strategy name (case-insensitive)\n */\nexport function filterByStrategy(trades: Trade[], strategy?: string): Trade[] {\n if (!strategy) return trades;\n return trades.filter(\n (t) => t.strategy.toLowerCase() === strategy.toLowerCase()\n );\n}\n\n/**\n * Validate that a date string is in YYYY-MM-DD format.\n * Returns the string if valid, undefined if not (skips that filter boundary).\n */\nconst DATE_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\nfunction validateDateParam(date: string | undefined): string | undefined {\n if (!date) return undefined;\n return DATE_RE.test(date) ? date : undefined;\n}\n\n/**\n * Extract YYYY-MM-DD calendar date from a Date or string.\n * Trades are parsed via parseDatePreservingCalendarDay() which creates dates at\n * local midnight. Use local date components to preserve the calendar date,\n * avoiding timezone shift when the server runs in UTC.\n */\nfunction toCalendarDateStr(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Filter trades by date range using string comparison on Eastern Time calendar dates.\n * Avoids timezone bugs from mixing UTC Date parsing with local time setHours.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterByDateRange(\n trades: Trade[],\n startDate?: string,\n endDate?: string\n): Trade[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = trades;\n\n if (start) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((t) => toCalendarDateStr(t.dateOpened) <= end);\n }\n\n return filtered;\n}\n\n/**\n * Filter daily log entries by date range using string comparison on calendar dates.\n * Mirrors filterByDateRange but uses entry.date (Date object) instead of t.dateOpened.\n * Malformed date inputs (not YYYY-MM-DD) are silently ignored.\n */\nexport function filterDailyLogsByDateRange(\n dailyLogs: DailyLogEntry[],\n startDate?: string,\n endDate?: string\n): DailyLogEntry[] {\n const start = validateDateParam(startDate);\n const end = validateDateParam(endDate);\n let filtered = dailyLogs;\n\n if (start) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) >= start);\n }\n\n if (end) {\n filtered = filtered.filter((entry) => toCalendarDateStr(entry.date) <= end);\n }\n\n return filtered;\n}\n","/**\n * Schema Metadata\n *\n * Hardcoded descriptions for DuckDB tables and columns, plus example queries.\n * Used by describe_database tool to provide context for SQL query writing.\n *\n * Tables are organized by schema:\n * - trades: Trade data from CSV files\n * - market: Market context data — daily (per-ticker OHLCV + indicators),\n * context (global VIX/regime), intraday (raw bar data)\n */\n\n// ============================================================================\n// Type Definitions\n// ============================================================================\n\nexport interface ColumnDescription {\n /** Human-readable description of what this column contains */\n description: string;\n /** True if this column is useful for hypothesis testing (filtering, grouping, analysis) */\n hypothesis: boolean;\n /** When this field's value is known relative to market open.\n * - 'open': Known at/before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * - 'close': Only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * - 'static': Calendar/metadata facts known before the day (Day_of_Week, Month, Is_Opex)\n * Only applicable to market.daily and market.context columns. Omit for non-market tables.\n */\n timing?: 'open' | 'close' | 'static';\n}\n\nexport interface TableDescription {\n /** Human-readable description of this table's purpose */\n description: string;\n /** Key columns that are most important for analysis */\n keyColumns: string[];\n /** Column descriptions by column name */\n columns: Record<string, ColumnDescription>;\n}\n\nexport interface SchemaDescription {\n /** Human-readable description of this schema's purpose */\n description: string;\n /** Tables in this schema */\n tables: Record<string, TableDescription>;\n}\n\nexport interface SchemaMetadata {\n trades: SchemaDescription;\n market: SchemaDescription;\n}\n\nexport interface ExampleQuery {\n /** What this query does */\n description: string;\n /** The SQL query */\n sql: string;\n}\n\nexport interface ExampleQueries {\n /** Basic single-table queries */\n basic: ExampleQuery[];\n /** JOIN queries between trades and market data */\n joins: ExampleQuery[];\n /** Hypothesis testing patterns */\n hypothesis: ExampleQuery[];\n}\n\n// ============================================================================\n// Schema Descriptions\n// ============================================================================\n\nexport const SCHEMA_DESCRIPTIONS: SchemaMetadata = {\n trades: {\n description:\n \"Trading data synced from CSV files. Contains trade records from all portfolio blocks, including both backtest (trade_data) and actual/reported (reporting_data) trades.\",\n tables: {\n trade_data: {\n description:\n \"Individual backtest trade records. Each row = one trade with entry/exit details, P&L, and strategy. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description (e.g., 'SPY 450P/445P')\",\n hypothesis: false,\n },\n premium: {\n description: \"Credit received (+) or debit paid (-)\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded\",\n hypothesis: false,\n },\n pl: {\n description: \"Gross P&L before commissions (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n margin_req: {\n description: \"Margin requirement for the position ($)\",\n hypothesis: false,\n },\n opening_commissions: {\n description: \"Commissions paid at entry ($)\",\n hypothesis: false,\n },\n closing_commissions: {\n description: \"Commissions paid at exit ($)\",\n hypothesis: false,\n },\n },\n },\n reporting_data: {\n description:\n \"Actual/reported trade records from reportinglog.csv. Each row = one live trade executed. Compare with trade_data (backtest) to analyze slippage and execution differences. Filter by block_id to query specific portfolios.\",\n keyColumns: [\"block_id\", \"date_opened\", \"strategy\", \"legs\", \"pl\"],\n columns: {\n block_id: {\n description: \"Portfolio block ID - filter by this to query specific portfolios\",\n hypothesis: true,\n },\n date_opened: {\n description: \"Trade entry date (DATE format, use for joins with market data)\",\n hypothesis: true,\n },\n time_opened: {\n description: \"Trade entry time in Eastern Time (e.g., '09:35:00')\",\n hypothesis: false,\n },\n strategy: {\n description: \"Strategy name (e.g., 'IronCondor', 'PutSpread')\",\n hypothesis: true,\n },\n legs: {\n description: \"Option legs description with strikes (e.g., 'SPY 450P/445P') - compare with trade_data.legs to identify strike differences\",\n hypothesis: true,\n },\n initial_premium: {\n description: \"Credit received (+) or debit paid (-) at entry\",\n hypothesis: false,\n },\n num_contracts: {\n description: \"Number of contracts traded (often fewer than backtest)\",\n hypothesis: false,\n },\n pl: {\n description: \"Actual P&L realized (DOUBLE)\",\n hypothesis: true,\n },\n date_closed: {\n description: \"Trade exit date (NULL if still open)\",\n hypothesis: false,\n },\n time_closed: {\n description: \"Trade exit time in Eastern Time\",\n hypothesis: false,\n },\n closing_price: {\n description: \"Price at exit\",\n hypothesis: false,\n },\n avg_closing_cost: {\n description: \"Average cost to close the position\",\n hypothesis: false,\n },\n reason_for_close: {\n description: \"Exit reason (e.g., 'Target', 'Stop', 'Expiration')\",\n hypothesis: true,\n },\n opening_price: {\n description: \"Price at entry\",\n hypothesis: false,\n },\n },\n },\n },\n },\n market: {\n description:\n \"Market context data for hypothesis testing. Normalized into four tables: daily (per-ticker OHLCV + technical indicators + ivr/ivp for VIX-family tickers), _context_derived (cross-ticker derived fields like Vol_Regime), context (LEGACY — preserved for backward compat), and intraday (raw bar data). Source: CSV files in market/ folder.\",\n tables: {\n daily: {\n description:\n \"Per-ticker daily OHLCV with Tier 1 enrichment indicators and calendar fields. One row per ticker per trading day. JOIN with trades on ticker+date (e.g., d.ticker = 'SPX' AND t.date_opened = d.date). VIX-family tickers (VIX, VIX9D, VIX3M, etc.) also have ivr/ivp columns populated. For trade-entry queries, use LAG() on close-derived fields. Join market._context_derived (LEFT JOIN on date) for Vol_Regime, Term_Structure_State, etc.\",\n keyColumns: [\"ticker\", \"date\", \"RSI_14\", \"ATR_Pct\", \"Realized_Vol_20D\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Composite primary key with ticker.\",\n hypothesis: true,\n },\n // Raw OHLCV\n open: {\n description: \"Underlying open price\",\n hypothesis: false,\n timing: 'open',\n },\n high: {\n description: \"Underlying high price\",\n hypothesis: false,\n timing: 'close',\n },\n low: {\n description: \"Underlying low price\",\n hypothesis: false,\n timing: 'close',\n },\n close: {\n description: \"Underlying close price\",\n hypothesis: false,\n timing: 'close',\n },\n Prior_Close: {\n description: \"Previous day's close price\",\n hypothesis: false,\n timing: 'open',\n },\n // Tier 1 enrichment — open-known\n Gap_Pct: {\n description: \"Overnight gap percentage ((Open - Prior_Close) / Prior_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n Prev_Return_Pct: {\n description: \"Previous day's total return percentage (prior close to prior close)\",\n hypothesis: true,\n timing: 'open',\n },\n Prior_Range_vs_ATR: {\n description: \"Prior trading day's (high - low) / ATR ratio, measures prior day's range relative to average true range\",\n hypothesis: true,\n timing: 'open',\n },\n // Tier 1 enrichment — close-derived\n ATR_Pct: {\n description: \"Average True Range as percentage of price (14-day Wilder smoothing)\",\n hypothesis: true,\n timing: 'close',\n },\n RSI_14: {\n description: \"14-day RSI (0-100, >70 overbought, <30 oversold)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_EMA21_Pct: {\n description: \"Price vs 21-day EMA as percentage ((close - EMA21) / EMA21 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Price_vs_SMA50_Pct: {\n description: \"Price vs 50-day SMA as percentage ((close - SMA50) / SMA50 * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_5D: {\n description: \"5-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Realized_Vol_20D: {\n description: \"20-day realized volatility (annualized standard deviation of log returns)\",\n hypothesis: true,\n timing: 'close',\n },\n Return_5D: {\n description: \"5-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Return_20D: {\n description: \"20-day cumulative return percentage\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Range_Pct: {\n description: \"Intraday range as percentage ((High - Low) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Return_Pct: {\n description: \"Open to close return percentage ((Close - Open) / Open * 100)\",\n hypothesis: true,\n timing: 'close',\n },\n Close_Position_In_Range: {\n description: \"Where close is in day's range (0 = low, 1 = high)\",\n hypothesis: true,\n timing: 'close',\n },\n Gap_Filled: {\n description: \"Whether overnight gap was filled (1 = yes, 0 = no)\",\n hypothesis: true,\n timing: 'close',\n },\n Consecutive_Days: {\n description: \"Consecutive up/down days (positive=up, negative=down)\",\n hypothesis: true,\n timing: 'close',\n },\n // Tier 3 intraday timing (columns exist in schema, enrichment deferred)\n High_Time: {\n description: \"Time of day high as decimal hours (e.g., 10.5 = 10:30 AM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n Low_Time: {\n description: \"Time of day low as decimal hours (e.g., 14.25 = 2:15 PM ET)\",\n hypothesis: true,\n timing: 'close',\n },\n High_Before_Low: {\n description: \"Did high occur before low? (1=yes, 0=no)\",\n hypothesis: true,\n timing: 'close',\n },\n Reversal_Type: {\n description: \"Reversal pattern type (1=morning reversal up, -1=morning reversal down, 0=trend day)\",\n hypothesis: true,\n timing: 'close',\n },\n Opening_Drive_Strength: {\n description: \"First-30-min range / full-day range ratio (0-1); higher = strong opening drive\",\n hypothesis: true,\n timing: 'close',\n },\n Intraday_Realized_Vol: {\n description: \"Annualized realized volatility from intraday bar returns (decimal, e.g., 0.15 = 15%)\",\n hypothesis: true,\n timing: 'close',\n },\n // Calendar fields — static\n Day_of_Week: {\n description: \"Day of week (2=Monday through 6=Friday)\",\n hypothesis: true,\n timing: 'static',\n },\n Month: {\n description: \"Month number (1-12)\",\n hypothesis: true,\n timing: 'static',\n },\n Is_Opex: {\n description: \"Options expiration day flag (1=opex, 0=not)\",\n hypothesis: true,\n timing: 'static',\n },\n // VIX-family ticker IVR/IVP (populated for VIX, VIX9D, VIX3M, etc.)\n ivr: {\n description: \"Implied Volatility Rank (252-day): where current close sits in range (0=min, 100=max). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n ivp: {\n description: \"Implied Volatility Percentile (252-day): percentage of prior 251 trading days where close was at or below current level (0-100). Populated for VIX-family tickers only.\",\n hypothesis: true,\n timing: 'close',\n },\n },\n },\n _context_derived: {\n description:\n \"Cross-ticker derived market context fields per trading day. Contains Vol_Regime, Term_Structure_State, and other fields derived from multiple VIX tickers. JOIN with market.daily on date. VIX OHLCV and IVR/IVP are now in market.daily (ticker='VIX', 'VIX9D', etc.).\",\n keyColumns: [\"date\", \"Vol_Regime\", \"Term_Structure_State\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: true,\n },\n Vol_Regime: {\n description: \"Volatility regime classification based on VIX close (1=very low <10, 2=low 10-15, 3=normal 15-20, 4=elevated 20-25, 5=high 25-30, 6=extreme >30)\",\n hypothesis: true,\n timing: 'close',\n },\n Term_Structure_State: {\n description: \"VIX term structure state based on VIX9D/VIX ratio (-1=backwardation/inverted, 0=flat, 1=contango/normal). NULL when VIX9D data is absent.\",\n hypothesis: true,\n timing: 'close',\n },\n Trend_Direction: {\n description: \"Trend direction classification based on 20-day return: up (>1%), down (<-1%), flat (-1% to 1%). NULL if Return_20D unavailable.\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Spike_Pct: {\n description: \"VIX spike from open to high as percentage\",\n hypothesis: true,\n timing: 'close',\n },\n VIX_Gap_Pct: {\n description: \"VIX overnight gap percentage ((VIX_Open - prior VIX_Close) / prior VIX_Close * 100)\",\n hypothesis: true,\n timing: 'open',\n },\n },\n },\n context: {\n description:\n \"LEGACY: Global VIX and volatility term structure data. VIX ticker data has moved to market.daily (ticker='VIX', 'VIX9D', etc.) with ivr/ivp columns. Derived fields moved to market._context_derived. This table is maintained for backward compatibility but is no longer the primary data source.\",\n keyColumns: [\"date\"],\n columns: {\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Primary key.\",\n hypothesis: false,\n },\n },\n },\n intraday: {\n description:\n \"Raw intraday bars per ticker. One row per bar (any resolution). Use for ORB calculations and intraday context enrichment. Time column is Eastern Time HH:MM format (e.g., '09:30'). Filter by ticker='VIX' to get VIX intraday data.\",\n keyColumns: [\"ticker\", \"date\", \"time\"],\n columns: {\n ticker: {\n description: \"Underlying ticker symbol (part of composite primary key with date and time).\",\n hypothesis: true,\n },\n date: {\n description: \"Trading date (VARCHAR, format YYYY-MM-DD). Part of composite primary key.\",\n hypothesis: true,\n },\n time: {\n description: \"Bar time in HH:MM Eastern Time format (e.g., '09:30', '10:00'). Part of composite primary key.\",\n hypothesis: false,\n },\n open: {\n description: \"Bar open price\",\n hypothesis: false,\n },\n high: {\n description: \"Bar high price\",\n hypothesis: false,\n },\n low: {\n description: \"Bar low price\",\n hypothesis: false,\n },\n close: {\n description: \"Bar close price\",\n hypothesis: false,\n },\n },\n },\n },\n },\n};\n\n// ============================================================================\n// Example Queries\n// ============================================================================\n\nexport const EXAMPLE_QUERIES: ExampleQueries = {\n basic: [\n {\n description: \"Count trades by strategy with total P&L\",\n sql: `SELECT strategy, COUNT(*) as trades, SUM(pl) as total_pl\nFROM trades.trade_data\nGROUP BY strategy\nORDER BY total_pl DESC`,\n },\n {\n description: \"Daily P&L for a specific block\",\n sql: `SELECT date_opened, SUM(pl) as daily_pl\nFROM trades.trade_data\nWHERE block_id = 'my-block'\nGROUP BY date_opened\nORDER BY date_opened`,\n },\n {\n description: \"Recent market conditions (last 20 days)\",\n sql: `SELECT d.date, d.close, d.RSI_14, d.ATR_Pct,\n vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State, vix.ivr AS VIX_IVR, vix.ivp AS VIX_IVP\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\nORDER BY d.date DESC\nLIMIT 20`,\n },\n {\n description: \"Win/loss summary by block\",\n sql: `SELECT\n block_id,\n COUNT(*) as total_trades,\n SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) as winners,\n SUM(CASE WHEN pl <= 0 THEN 1 ELSE 0 END) as losers,\n ROUND(100.0 * SUM(CASE WHEN pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data\nGROUP BY block_id\nORDER BY block_id`,\n },\n {\n description: \"Filter and paginate trades (replaces get_trades)\",\n sql: `SELECT date_opened, time_opened, strategy, legs, pl, num_contracts\nFROM trades.trade_data\nWHERE block_id = 'my-block'\n AND strategy ILIKE '%iron%'\n AND pl > 0\nORDER BY date_opened DESC\nLIMIT 50 OFFSET 0`,\n },\n {\n description: \"Market data query with VIX context\",\n sql: `SELECT d.date, d.close, d.Gap_Pct, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date\nWHERE d.ticker = 'SPX'\n AND d.date BETWEEN '2024-01-01' AND '2024-06-30'\n AND vix.close > 20\nORDER BY d.date`,\n },\n {\n description: \"Compare backtest vs actual trades by date/strategy\",\n sql: `SELECT\n t.date_opened, t.strategy, t.legs as bt_legs, r.legs as actual_legs,\n t.pl as bt_pl, r.pl as actual_pl, r.pl - t.pl as slippage\nFROM trades.trade_data t\nJOIN trades.reporting_data r\n ON t.block_id = r.block_id\n AND t.date_opened = r.date_opened\n AND t.strategy = r.strategy\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n ],\n joins: [\n {\n description: \"Trade P&L with market context (lag-aware: multi-table JOIN before LAG for correctness)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close, d.Prev_Return_Pct,\n vix.open AS VIX_Open,\n d.RSI_14, d.Realized_Vol_20D,\n vix.close AS VIX_Close, vix.ivp AS VIX_IVP, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(RSI_14) OVER (PARTITION BY ticker ORDER BY date) AS prev_RSI_14,\n LAG(VIX_IVP) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_IVP,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open,\n m.prev_RSI_14, m.prev_VIX_IVP, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened DESC`,\n },\n {\n description: \"Trades with ORB context (opening range breakout from intraday bars)\",\n sql: `WITH orb_range AS (\n SELECT ticker, date,\n MAX(high) AS ORB_High,\n MIN(low) AS ORB_Low,\n MAX(high) - MIN(low) AS ORB_Range\n FROM market.intraday\n WHERE ticker = 'SPX'\n AND time >= '09:30' AND time <= '09:45'\n GROUP BY ticker, date\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n r.ORB_High, r.ORB_Low, r.ORB_Range\nFROM trades.trade_data t\nLEFT JOIN orb_range r ON t.date_opened = r.date\nWHERE t.block_id = 'my-block'\nORDER BY t.date_opened`,\n },\n {\n description: \"VIX intraday data for a specific date (VIX bars are in market.intraday with ticker='VIX')\",\n sql: `SELECT time, open, high, low, close\nFROM market.intraday\nWHERE ticker = 'VIX'\n AND date = '2024-03-15'\nORDER BY time`,\n },\n {\n description: \"Trades on reversal days (lag-aware: Reversal_Type uses prior trading day via LAG)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.High_Before_Low, d.Reversal_Type\n FROM market.daily d\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Reversal_Type) OVER (PARTITION BY ticker ORDER BY date) AS prev_Reversal_Type,\n LAG(High_Before_Low) OVER (PARTITION BY ticker ORDER BY date) AS prev_High_Before_Low\n FROM joined\n)\nSELECT\n t.date_opened, t.strategy, t.pl,\n m.prev_Reversal_Type, m.prev_High_Before_Low\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE m.prev_Reversal_Type != 0\n AND t.block_id = 'my-block'`,\n },\n {\n description: \"Enrich trades with market data (lag-aware: use enrich_trades tool for full enrichment)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date,\n d.Gap_Pct, d.Prior_Close,\n vix.open AS VIX_Open,\n d.RSI_14, d.ATR_Pct,\n vix.close AS VIX_Close, cd.Vol_Regime\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT t.date_opened, t.strategy, t.pl,\n m.Gap_Pct, m.VIX_Open, m.prev_VIX_Close, m.prev_Vol_Regime\nFROM trades.trade_data t\nLEFT JOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'`,\n },\n ],\n hypothesis: [\n {\n description: \"Win rate by VIX regime (lag-aware: uses prior day's Vol_Regime from market._context_derived)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Vol_Regime\n FROM market.daily d\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Vol_Regime) OVER (PARTITION BY ticker ORDER BY date) AS prev_Vol_Regime\n FROM joined\n)\nSELECT\n m.prev_Vol_Regime AS vol_regime,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) as winners,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Vol_Regime IS NOT NULL\nGROUP BY m.prev_Vol_Regime\nORDER BY m.prev_Vol_Regime`,\n },\n {\n description: \"P&L by day of week\",\n sql: `SELECT\n d.Day_of_Week,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl\nFROM trades.trade_data t\nJOIN market.daily d ON t.date_opened = d.date AND d.ticker = 'SPX'\nWHERE t.block_id = 'my-block'\nGROUP BY d.Day_of_Week\nORDER BY d.Day_of_Week`,\n },\n {\n description: \"Performance by VIX term structure (lag-aware: uses prior day's Term_Structure_State from market._context_derived)\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(Term_Structure_State) OVER (PARTITION BY ticker ORDER BY date) AS prev_Term_Structure_State\n FROM joined\n)\nSELECT\n CASE WHEN m.prev_Term_Structure_State = -1 THEN 'Backwardation'\n WHEN m.prev_Term_Structure_State = 1 THEN 'Contango'\n ELSE 'Flat' END as term_structure,\n COUNT(*) as trades,\n SUM(t.pl) as total_pl,\n ROUND(AVG(t.pl), 2) as avg_pl,\n ROUND(100.0 * SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END) / COUNT(*), 1) as win_rate\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_Term_Structure_State IS NOT NULL\nGROUP BY term_structure`,\n },\n {\n description: \"Aggregate by VIX buckets (lag-aware: uses prior day's VIX close from market.daily ticker='VIX')\",\n sql: `WITH joined AS (\n SELECT d.ticker, d.date, vix.close AS VIX_Close\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n WHERE d.ticker = 'SPX'\n),\nlagged AS (\n SELECT *,\n LAG(VIX_Close) OVER (PARTITION BY ticker ORDER BY date) AS prev_VIX_Close\n FROM joined\n)\nSELECT\n CASE\n WHEN m.prev_VIX_Close < 15 THEN '10-15'\n WHEN m.prev_VIX_Close < 20 THEN '15-20'\n WHEN m.prev_VIX_Close < 25 THEN '20-25'\n ELSE '25+'\n END as vix_bucket,\n COUNT(*) as trades,\n SUM(CASE WHEN t.pl > 0 THEN 1 ELSE 0 END)::FLOAT / COUNT(*) as win_rate,\n SUM(t.pl) as total_pl\nFROM trades.trade_data t\nJOIN lagged m ON t.date_opened = m.date\nWHERE t.block_id = 'my-block'\n AND m.prev_VIX_Close IS NOT NULL\nGROUP BY vix_bucket\nORDER BY vix_bucket`,\n },\n {\n description: \"Find similar days by conditions\",\n sql: `WITH ref AS (\n SELECT d.close, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\n FROM market.daily d\n LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = 'SPX' AND d.date = '2024-01-15'\n)\nSELECT d.date, d.close, vix.close AS VIX_Close, cd.Vol_Regime, cd.Term_Structure_State\nFROM market.daily d\nLEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\nLEFT JOIN market._context_derived cd ON cd.date = d.date, ref\nWHERE d.ticker = 'SPX'\n AND d.date != '2024-01-15'\n AND cd.Vol_Regime = ref.Vol_Regime\n AND ABS(vix.close - ref.VIX_Close) < 3\nORDER BY ABS(vix.close - ref.VIX_Close)\nLIMIT 20`,\n },\n ],\n};\n","/**\n * Field Timing Utilities\n *\n * Derived sets and LAG CTE builder for lookahead-free market analytics.\n * All field classifications are derived from SCHEMA_DESCRIPTIONS timing annotations\n * in schema-metadata.ts -- no hardcoded column names.\n *\n * The normalized schema splits market data into:\n * - market.daily: per-ticker OHLCV + technical indicators (+ ivr/ivp for VIX-family)\n * - market._context_derived: derived cross-ticker fields (Vol_Regime, Term_Structure_State, etc.)\n * - market.context: LEGACY — preserved for backward compat but no longer the primary source\n *\n * buildLookaheadFreeQuery JOINs market.daily VIX ticker rows + market._context_derived inside a CTE\n * before applying LAG, ensuring LAG operates on the full ticker history (not just trade dates).\n * This guarantees Monday LAG returns Friday's values, not the previous trade day.\n *\n * Used by downstream tools (suggest_filters, analyze_regime_performance, etc.)\n * to ensure trade-entry queries only use data available at the time of trade entry.\n */\n\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.js\";\nimport { SCHEMA_DESCRIPTIONS } from \"./schema-metadata.js\";\n\nconst dailyColumns = SCHEMA_DESCRIPTIONS.market.tables.daily.columns;\nconst derivedColumns = SCHEMA_DESCRIPTIONS.market.tables._context_derived.columns;\n\nexport interface MarketLookupKey {\n date: string;\n ticker: string;\n}\n\n// ============================================================================\n// VIX field mapping — normalized schema\n// ============================================================================\n\n/**\n * VIX field mapping: column alias -> { table alias, source column, ticker, timing }\n * These are the VIX fields that downstream queries expect, mapped to the normalized schema.\n */\ninterface VixFieldMapping {\n alias: string; // Column name in query output (e.g., \"VIX_Close\")\n tableAlias: string; // SQL table alias (e.g., \"vix\")\n sourceCol: string; // Source column in market.daily (e.g., \"close\")\n ticker: string; // Ticker to join on (e.g., \"VIX\")\n timing: 'open' | 'close';\n}\n\nconst VIX_FIELD_MAPPINGS: VixFieldMapping[] = [\n // VIX fields\n { alias: \"VIX_Open\", tableAlias: \"vix\", sourceCol: \"open\", ticker: \"VIX\", timing: \"open\" },\n { alias: \"VIX_Close\", tableAlias: \"vix\", sourceCol: \"close\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_High\", tableAlias: \"vix\", sourceCol: \"high\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_Low\", tableAlias: \"vix\", sourceCol: \"low\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVR\", tableAlias: \"vix\", sourceCol: \"ivr\", ticker: \"VIX\", timing: \"close\" },\n { alias: \"VIX_IVP\", tableAlias: \"vix\", sourceCol: \"ivp\", ticker: \"VIX\", timing: \"close\" },\n // VIX9D fields\n { alias: \"VIX9D_Open\", tableAlias: \"vix9d\", sourceCol: \"open\", ticker: \"VIX9D\", timing: \"open\" },\n { alias: \"VIX9D_Close\", tableAlias: \"vix9d\", sourceCol: \"close\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVR\", tableAlias: \"vix9d\", sourceCol: \"ivr\", ticker: \"VIX9D\", timing: \"close\" },\n { alias: \"VIX9D_IVP\", tableAlias: \"vix9d\", sourceCol: \"ivp\", ticker: \"VIX9D\", timing: \"close\" },\n // VIX3M fields\n { alias: \"VIX3M_Open\", tableAlias: \"vix3m\", sourceCol: \"open\", ticker: \"VIX3M\", timing: \"open\" },\n { alias: \"VIX3M_Close\", tableAlias: \"vix3m\", sourceCol: \"close\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVR\", tableAlias: \"vix3m\", sourceCol: \"ivr\", ticker: \"VIX3M\", timing: \"close\" },\n { alias: \"VIX3M_IVP\", tableAlias: \"vix3m\", sourceCol: \"ivp\", ticker: \"VIX3M\", timing: \"close\" },\n];\n\n// Unique VIX table aliases needed for the JOIN clause\nconst VIX_TABLE_ALIASES = [...new Set(VIX_FIELD_MAPPINGS.map(m => m.tableAlias))];\nconst VIX_TICKER_FOR_ALIAS = Object.fromEntries(\n VIX_FIELD_MAPPINGS.map(m => [m.tableAlias, m.ticker])\n);\n\n// Derived fields from _context_derived\nconst DERIVED_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\nconst DERIVED_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(derivedColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n// ============================================================================\n// Table-specific field sets (needed by CTE builder to know which table to alias)\n// ============================================================================\n\n/**\n * Open-known fields from market.daily (use as d.{field} in JOIN CTE)\n */\nexport const DAILY_OPEN_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'open')\n .map(([name]) => name)\n);\n\n/**\n * Close-derived fields from market.daily (apply LAG as d.{field} in JOIN CTE)\n */\nexport const DAILY_CLOSE_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'close')\n .map(([name]) => name)\n);\n\n/**\n * Static fields from market.daily (use as d.{field} in JOIN CTE — calendar facts)\n */\nexport const DAILY_STATIC_FIELDS: ReadonlySet<string> = new Set(\n Object.entries(dailyColumns)\n .filter(([, desc]) => desc.timing === 'static')\n .map(([name]) => name)\n);\n\n/**\n * Open-known fields from VIX tickers + _context_derived (backward-compat alias of context open fields)\n */\nexport const CONTEXT_OPEN_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_FIELD_MAPPINGS.filter(m => m.timing === 'open').map(m => m.alias),\n ...DERIVED_OPEN_FIELDS,\n]);\n\n/**\n * Close-derived fields from VIX tickers + _context_derived (backward-compat alias of context close fields)\n */\nexport const CONTEXT_CLOSE_FIELDS: ReadonlySet<string> = new Set([\n ...VIX_FIELD_MAPPINGS.filter(m => m.timing === 'close').map(m => m.alias),\n ...DERIVED_CLOSE_FIELDS,\n]);\n\n// ============================================================================\n// Combined field sets (for callers that don't need to know origin table)\n// ============================================================================\n\n/**\n * Fields known at or before market open (Prior_Close, Gap_Pct, VIX_Open, etc.)\n * Union of open-known fields from market.daily, VIX tickers, and _context_derived.\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const OPEN_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_OPEN_FIELDS,\n ...CONTEXT_OPEN_FIELDS,\n]);\n\n/**\n * Fields only known after market close (RSI_14, Vol_Regime, Close, etc.)\n * Union of close-derived fields from market.daily, VIX tickers, and _context_derived.\n * Must use LAG() to get prior trading day's value in trade-entry queries.\n */\nexport const CLOSE_KNOWN_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_CLOSE_FIELDS,\n ...CONTEXT_CLOSE_FIELDS,\n]);\n\n/**\n * Calendar/metadata facts known before the trading day (Day_of_Week, Month, Is_Opex).\n * Only from market.daily (context has no static fields).\n * Safe to use as same-day values in trade-entry queries.\n */\nexport const STATIC_FIELDS: ReadonlySet<string> = new Set([\n ...DAILY_STATIC_FIELDS,\n]);\n\n// ============================================================================\n// Query Builders\n// ============================================================================\n\n// Build the VIX JOIN clause (three LEFT JOINs: vix, vix9d, vix3m)\nfunction buildVixJoins(baseAlias: string = \"d\"): string {\n return VIX_TABLE_ALIASES\n .map(alias => `LEFT JOIN market.daily ${alias} ON ${alias}.date = ${baseAlias}.date AND ${alias}.ticker = '${VIX_TICKER_FOR_ALIAS[alias]}'`)\n .join(\"\\n \");\n}\n\n// SELECT columns from VIX tables: \"vix\".\"close\" AS \"VIX_Close\", ...\nfunction buildVixSelectCols(): string {\n return VIX_FIELD_MAPPINGS.map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`).join(\", \");\n}\n\n// SELECT columns from _context_derived: cd.\"Vol_Regime\", ...\nfunction buildDerivedSelectCols(): string {\n return [...DERIVED_OPEN_FIELDS, ...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n}\n\n/**\n * Builds a SQL query that joins trade keys to market.daily + market._context_derived + VIX tickers\n * with lookahead bias prevention:\n * - Open-known fields: used as-is (same-day values, known before market open)\n * - Static fields: used as-is (calendar facts, known in advance)\n * - Close-derived fields: LAG(field) OVER (PARTITION BY ticker ORDER BY date)\n * gives prior trading day's value\n *\n * The JOIN pattern is:\n * market.daily d\n * LEFT JOIN market.daily vix ON vix.date = d.date AND vix.ticker = 'VIX'\n * LEFT JOIN market.daily vix9d ON vix9d.date = d.date AND vix9d.ticker = 'VIX9D'\n * LEFT JOIN market.daily vix3m ON vix3m.date = d.date AND vix3m.ticker = 'VIX3M'\n * LEFT JOIN market._context_derived cd ON cd.date = d.date\n *\n * LAG operates on the FULL ticker history (all trading days for the ticker),\n * NOT just the requested dates. This ensures LAG sees the correct prior trading day\n * across weekends, holidays, and sparse trading strategies.\n *\n * @param tradeDatesOrKeys - Array of dates (legacy string[] overload) or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the parameter values)\n */\nexport function buildLookaheadFreeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildLookaheadFreeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.daily WHERE 1=0`, params: [] };\n }\n\n // Build field lists for the joined CTE\n const dailyOpenCols = [...DAILY_OPEN_FIELDS].map((f) => `d.\"${f}\"`).join(\", \");\n const dailyStaticCols = [...DAILY_STATIC_FIELDS].map((f) => `d.\"${f}\"`).join(\", \");\n const vixSelectCols = buildVixSelectCols();\n const derivedSelectCols = buildDerivedSelectCols();\n const vixJoins = buildVixJoins();\n\n // LAG columns — all close-derived fields from daily and VIX/derived\n const dailyLagCols = [...DAILY_CLOSE_FIELDS]\n .map((field) => `LAG(\"${field}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${field}\"`)\n .join(\",\\n \");\n const vixLagCols = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `LAG(\"${m.alias}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${m.alias}\"`)\n .join(\",\\n \");\n const derivedLagCols = [...DERIVED_CLOSE_FIELDS]\n .map(f => `LAG(\"${f}\") OVER (PARTITION BY ticker ORDER BY date) AS \"prev_${f}\"`)\n .join(\",\\n \");\n\n // Pass-through columns for the lagged CTE (unaliased, from joined CTE output)\n const dailyOpenPassthrough = [...DAILY_OPEN_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const dailyStaticPassthrough = [...DAILY_STATIC_FIELDS].map((f) => `\"${f}\"`).join(\", \");\n const vixOpenPassthrough = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'open')\n .map(m => `\"${m.alias}\"`)\n .join(\", \");\n const derivedOpenPassthrough = [...DERIVED_OPEN_FIELDS].map(f => `\"${f}\"`).join(\", \");\n\n // Legacy path for existing date-only callers (single ticker = DEFAULT_MARKET_TICKER)\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n const tradeDates = tradeDatesOrKeys as string[];\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n\n const sql = `WITH joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${[...DAILY_CLOSE_FIELDS].map((f) => `d.\"${f}\"`).join(\", \")}\n FROM market.daily d\n ${vixJoins}\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker = $${tradeDates.length + 1}\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT * FROM lagged\n WHERE date IN (${placeholders})`;\n\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n }\n\n const tradeKeys = tradeDatesOrKeys as MarketLookupKey[];\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n ),\n joined AS (\n SELECT\n d.ticker,\n d.date,\n ${dailyOpenCols},\n ${dailyStaticCols},\n ${vixSelectCols},\n ${derivedSelectCols ? derivedSelectCols + \",\" : \"\"}\n ${[...DAILY_CLOSE_FIELDS].map((f) => `d.\"${f}\"`).join(\", \")}\n FROM market.daily d\n ${vixJoins}\n LEFT JOIN market._context_derived cd ON cd.date = d.date\n WHERE d.ticker IN (SELECT DISTINCT ticker FROM requested)\n ),\n lagged AS (\n SELECT\n ticker,\n date,\n ${dailyOpenPassthrough},\n ${dailyStaticPassthrough},\n ${vixOpenPassthrough ? vixOpenPassthrough + \",\" : \"\"}\n ${derivedOpenPassthrough ? derivedOpenPassthrough + \",\" : \"\"}\n ${dailyLagCols},\n ${vixLagCols ? vixLagCols + \",\" : \"\"}\n ${derivedLagCols}\n FROM joined\n )\n SELECT lagged.*\n FROM lagged\n JOIN requested\n ON lagged.ticker = requested.ticker\n AND lagged.date = requested.date`;\n\n return { sql, params: values };\n}\n\n/**\n * Builds a SQL query that returns same-day close-derived values (no LAG).\n * Used for outcome/post-hoc analysis when includeOutcomeFields=true.\n *\n * These are values that were NOT available at trade entry time --\n * they represent the end-of-day result for the trade date itself.\n * Sources from market.daily, VIX ticker rows, and market._context_derived via LEFT JOIN.\n *\n * @param tradeDatesOrKeys - Array of dates or ticker+date keys\n * @returns Object with `sql` (the query string) and `params` (the date values)\n */\nexport function buildOutcomeQuery(tradeDates: string[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(tradeKeys: MarketLookupKey[]): { sql: string; params: string[] };\nexport function buildOutcomeQuery(\n tradeDatesOrKeys: string[] | MarketLookupKey[]\n): { sql: string; params: string[] } {\n if (tradeDatesOrKeys.length === 0) {\n return { sql: `SELECT * FROM market.daily WHERE 1=0`, params: [] };\n }\n\n const vixCloseCols = VIX_FIELD_MAPPINGS\n .filter(m => m.timing === 'close')\n .map(m => `${m.tableAlias}.\"${m.sourceCol}\" AS \"${m.alias}\"`)\n .join(\", \");\n const derivedCloseCols = [...DERIVED_CLOSE_FIELDS].map(f => `cd.\"${f}\"`).join(\", \");\n\n if (typeof tradeDatesOrKeys[0] === \"string\") {\n return buildOutcomeQueryForDates(tradeDatesOrKeys as string[], vixCloseCols, derivedCloseCols);\n }\n\n return buildOutcomeQueryForKeys(tradeDatesOrKeys as MarketLookupKey[], vixCloseCols, derivedCloseCols);\n}\n\nfunction buildOutcomeQueryForDates(\n tradeDates: string[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const a = \"d\";\n const dailyCloseCols = [...DAILY_CLOSE_FIELDS].map((f) => `${a}.\"${f}\"`).join(\", \");\n const placeholders = tradeDates.map((_, i) => `$${i + 1}`).join(\", \");\n const sql = `SELECT ${a}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.daily ${a}\n ${buildVixJoins(a)}\n LEFT JOIN market._context_derived cd ON cd.date = ${a}.date\n WHERE ${a}.ticker = $${tradeDates.length + 1}\n AND ${a}.date IN (${placeholders})`;\n return { sql, params: [...tradeDates, DEFAULT_MARKET_TICKER] };\n}\n\nfunction buildOutcomeQueryForKeys(\n tradeKeys: MarketLookupKey[], vixCloseCols: string, derivedCloseCols: string\n): { sql: string; params: string[] } {\n const a = \"m\";\n const dailyCloseCols = [...DAILY_CLOSE_FIELDS].map((f) => `${a}.\"${f}\"`).join(\", \");\n const normalizedKeys = tradeKeys.map((k) => ({\n date: k.date,\n ticker: k.ticker || DEFAULT_MARKET_TICKER,\n }));\n\n const values: string[] = [];\n const valuePlaceholders = normalizedKeys.map((key) => {\n values.push(key.ticker, key.date);\n return `($${values.length - 1}, $${values.length})`;\n });\n\n const sql = `WITH requested(ticker, date) AS (\n VALUES ${valuePlaceholders.join(\", \")}\n )\n SELECT ${a}.ticker, ${a}.date, ${dailyCloseCols}, ${vixCloseCols}, ${derivedCloseCols}\n FROM market.daily ${a}\n ${buildVixJoins(a)}\n LEFT JOIN market._context_derived cd ON cd.date = ${a}.date\n JOIN requested\n ON ${a}.ticker = requested.ticker\n AND ${a}.date = requested.date`;\n\n return { sql, params: values };\n}\n","/**\n * Data Availability Helper\n *\n * Checks whether market data (daily, context, intraday) is available for a\n * given ticker and returns actionable warnings when data is missing.\n *\n * Used at the start of every market tool call to surface missing data with\n * clear import instructions rather than returning silent NULLs or cryptic errors.\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\n\nexport interface DataAvailabilityReport {\n /** Whether market.daily has rows for the requested ticker */\n hasDailyData: boolean;\n /** Whether market.daily has VIX ticker data (normalized schema — replaces market.context check) */\n hasContextData: boolean;\n /** Whether market.intraday has rows for the requested ticker */\n hasIntradayData: boolean;\n /** Date range available in market.daily for the ticker, or null if no data */\n dailyDateRange: { min: string; max: string } | null;\n /** Date range of VIX data in market.daily, or null if no data */\n contextDateRange: { min: string; max: string } | null;\n /** Date range available in market.intraday for the ticker, or null if no data */\n intradayDateRange: { min: string; max: string } | null;\n /** Actionable warning messages for any missing data sources */\n warnings: string[];\n}\n\n/**\n * Checks data availability in market tables for the specified ticker.\n *\n * Queries COUNT, MIN(date), and MAX(date) from each relevant table.\n * Returns a report with boolean flags, date ranges, and actionable warning messages.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Ticker symbol to check (e.g., 'SPX')\n * @param options.checkIntraday - Whether to also check market.intraday (default: false)\n */\nexport async function checkDataAvailability(\n conn: DuckDBConnection,\n ticker: string,\n options?: { checkIntraday?: boolean }\n): Promise<DataAvailabilityReport> {\n const warnings: string[] = [];\n\n // Check market.daily for ticker\n let hasDailyData = false;\n let dailyDateRange: { min: string; max: string } | null = null;\n try {\n const dailyResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.daily WHERE ticker = $1`,\n [ticker]\n );\n const rows = dailyResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasDailyData = cnt > 0;\n if (hasDailyData && row.min_date && row.max_date) {\n dailyDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.daily table doesn't exist yet — treat as no data\n }\n\n if (!hasDailyData) {\n warnings.push(\n `No market.daily data for ticker ${ticker}. ` +\n `Import daily OHLCV with import_market_csv (target_table: \"daily\", ticker: \"${ticker}\") ` +\n `then run enrich_market_data.`\n );\n }\n\n // Check for VIX data in market.daily (normalized schema)\n let hasContextData = false;\n let contextDateRange: { min: string; max: string } | null = null;\n try {\n const contextResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.daily WHERE ticker = 'VIX'`\n );\n const rows = contextResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasContextData = cnt > 0;\n if (hasContextData && row.min_date && row.max_date) {\n contextDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.daily may not exist yet\n }\n\n if (!hasContextData) {\n warnings.push(\n `No VIX data in market.daily. ` +\n `Import VIX data with import_from_api (ticker: \"VIX\", target_table: \"daily\") ` +\n `or import_market_csv (target_table: \"context\") ` +\n `then run enrich_market_data for IVR/IVP enrichment.`\n );\n }\n\n // Optionally check market.intraday for ticker\n let hasIntradayData = false;\n let intradayDateRange: { min: string; max: string } | null = null;\n if (options?.checkIntraday) {\n try {\n const intradayResult = await conn.runAndReadAll(\n `SELECT COUNT(*) as cnt, MIN(date) as min_date, MAX(date) as max_date\n FROM market.intraday WHERE ticker = $1`,\n [ticker]\n );\n const rows = intradayResult.getRowObjectsJson();\n if (rows.length > 0) {\n const row = rows[0] as { cnt: number | string; min_date: string | null; max_date: string | null };\n const cnt = typeof row.cnt === 'string' ? parseInt(row.cnt, 10) : Number(row.cnt);\n hasIntradayData = cnt > 0;\n if (hasIntradayData && row.min_date && row.max_date) {\n intradayDateRange = { min: row.min_date, max: row.max_date };\n }\n }\n } catch {\n // market.intraday table doesn't exist yet — treat as no data\n }\n\n if (!hasIntradayData) {\n warnings.push(\n `No market.intraday data for ticker ${ticker}. ` +\n `Import intraday bars with import_market_csv (target_table: \"intraday\", ticker: \"${ticker}\").`\n );\n }\n }\n\n return {\n hasDailyData,\n hasContextData,\n hasIntradayData,\n dailyDateRange,\n contextDateRange,\n intradayDateRange,\n warnings,\n };\n}\n","/**\n * Pure TypeScript indicator functions for the market enrichment pipeline.\n *\n * All functions are pure (no DB access, no side effects) and take number arrays\n * or structured inputs returning computed arrays or values.\n *\n * Formulas follow TradingView Pine Script conventions:\n * - RSI: Wilder smoothing seeded with SMA of first period changes\n * - ATR: Wilder smoothing seeded with SMA of first period TR values\n * - EMA: Standard EMA seeded with SMA of first period bars\n * - Realized Vol: Population stddev, annualized by sqrt(252)*100\n *\n * References:\n * - Wilder, J.W. (1978) \"New Concepts in Technical Trading Systems\"\n * - TradingView Pine Script documentation (ta.rsi, ta.atr, ta.ema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport { upsertMarketImportMetadata } from \"../sync/metadata.js\";\nimport { DEFAULT_MARKET_TICKER } from \"./ticker.js\";\n\n// =============================================================================\n// Interfaces\n// =============================================================================\n\nexport interface ContextRow {\n date: string;\n VIX_Open?: number | null;\n VIX_Close?: number | null;\n VIX_High?: number | null;\n VIX_RTH_Open?: number | null;\n VIX9D_Open?: number | null;\n VIX9D_Close?: number | null;\n VIX3M_Open?: number | null;\n VIX3M_Close?: number | null;\n}\n\nexport interface EnrichedContextRow extends ContextRow {\n VIX_Gap_Pct?: number | null;\n VIX_Change_Pct?: number | null;\n VIX9D_Change_Pct?: number | null;\n VIX3M_Change_Pct?: number | null;\n VIX9D_VIX_Ratio?: number | null;\n VIX_VIX3M_Ratio?: number | null;\n VIX_Spike_Pct?: number | null;\n Vol_Regime?: number | null;\n Term_Structure_State?: number | null;\n VIX_IVR?: number | null;\n VIX_IVP?: number | null;\n VIX9D_IVR?: number | null;\n VIX9D_IVP?: number | null;\n VIX3M_IVR?: number | null;\n VIX3M_IVP?: number | null;\n}\n\n// =============================================================================\n// Primitive Indicators\n// =============================================================================\n\n/**\n * Wilder's RSI.\n * Input: closing prices ordered oldest→newest.\n * Returns array same length as input; first `period` entries are NaN (warmup).\n *\n * Formula:\n * - Seed avgGain/avgLoss from SMA of first `period` changes (bars 1..period)\n * - result[period] = 100 - 100/(1 + avgGain/avgLoss)\n * - Subsequent: avgGain = (prev*(period-1) + gain)/period (Wilder smoothing)\n */\nexport function computeRSI(closes: number[], period = 14): number[] {\n const result = new Array<number>(closes.length).fill(NaN);\n if (closes.length < period + 1) return result;\n\n // Seed: average of first `period` gains and losses (changes at indices 1..period)\n let avgGain = 0;\n let avgLoss = 0;\n for (let i = 1; i <= period; i++) {\n const change = closes[i] - closes[i - 1];\n if (change > 0) avgGain += change;\n else avgLoss += Math.abs(change);\n }\n avgGain /= period;\n avgLoss /= period;\n\n result[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < closes.length; i++) {\n const change = closes[i] - closes[i - 1];\n const gain = change > 0 ? change : 0;\n const loss = change < 0 ? Math.abs(change) : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n result[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n\n return result;\n}\n\n/**\n * Wilder's Average True Range (ATR).\n * Returns array same length as input; first `period` entries are NaN.\n *\n * True Range = max(high - low, |high - prevClose|, |low - prevClose|)\n * TR can be computed from bar index 1 (needs prevClose).\n * First ATR = SMA of TR[1..period] (simple average of first `period` TR values).\n * ATR[i] for i > period: (ATR_prev * (period-1) + TR[i]) / period (Wilder)\n */\nexport function computeATR(\n highs: number[],\n lows: number[],\n closes: number[],\n period = 14\n): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period + 1) return result;\n\n // Compute true ranges starting from index 1 (needs prevClose)\n const tr = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n const prevClose = closes[i - 1];\n tr[i] = Math.max(\n highs[i] - lows[i],\n Math.abs(highs[i] - prevClose),\n Math.abs(lows[i] - prevClose)\n );\n }\n\n // First ATR = SMA of TR[1..period]\n let atrSum = 0;\n for (let i = 1; i <= period; i++) {\n atrSum += tr[i];\n }\n let atr = atrSum / period;\n result[period] = atr;\n\n // Wilder smoothing for subsequent bars\n for (let i = period + 1; i < n; i++) {\n atr = (atr * (period - 1) + tr[i]) / period;\n result[i] = atr;\n }\n\n return result;\n}\n\n/**\n * Exponential Moving Average (EMA) with SMA seed (TradingView convention).\n * Returns array same length as input; first `period-1` entries are NaN.\n *\n * Seed: EMA[period-1] = SMA of first `period` bars\n * k = 2 / (period + 1)\n * EMA[i] = close[i] * k + EMA[i-1] * (1 - k)\n */\nexport function computeEMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n if (n < period) return result;\n\n // Seed from SMA of first period bars\n let seed = 0;\n for (let i = 0; i < period; i++) {\n seed += closes[i];\n }\n seed /= period;\n result[period - 1] = seed;\n\n const k = 2 / (period + 1);\n for (let i = period; i < n; i++) {\n result[i] = closes[i] * k + result[i - 1] * (1 - k);\n }\n\n return result;\n}\n\n/**\n * Simple Moving Average (SMA).\n * Returns array same length as input; first `period-1` entries are NaN.\n * SMA[i] = average of closes[i-period+1..i]\n */\nexport function computeSMA(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n for (let i = period - 1; i < n; i++) {\n let sum = 0;\n for (let j = i - period + 1; j <= i; j++) {\n sum += closes[j];\n }\n result[i] = sum / period;\n }\n\n return result;\n}\n\n// =============================================================================\n// Composite Indicators\n// =============================================================================\n\n/**\n * Realized Volatility using log returns, population stddev, annualized.\n * Returns array same length as input; first `period` entries are NaN\n * (need period+1 closes to compute period log returns).\n *\n * log_return[i] = ln(close[i] / close[i-1])\n * Vol[i] = stddev(log_returns[i-period+1..i], N) * sqrt(252) * 100\n */\nexport function computeRealizedVol(closes: number[], period: number): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(NaN);\n\n // Compute log returns (one less than closes count)\n const logReturns = new Array<number>(n).fill(NaN);\n for (let i = 1; i < n; i++) {\n logReturns[i] = Math.log(closes[i] / closes[i - 1]);\n }\n\n // Rolling stddev of log returns over `period` window\n // First valid: index = period (window uses log returns at i-period+1..i, earliest is i=period)\n for (let i = period; i < n; i++) {\n const window: number[] = [];\n for (let j = i - period + 1; j <= i; j++) {\n window.push(logReturns[j]);\n }\n\n const mean = window.reduce((a, b) => a + b, 0) / period;\n // Population stddev\n const variance = window.reduce((sum, v) => sum + (v - mean) ** 2, 0) / period;\n result[i] = Math.sqrt(variance) * Math.sqrt(252) * 100;\n }\n\n return result;\n}\n\n// =============================================================================\n// Row-Level Helpers\n// =============================================================================\n\n/**\n * Consecutive up/down days counter.\n * Positive = consecutive up days, negative = consecutive down days.\n * Resets to 0 on flat day.\n * First element is always 0 (no prior bar).\n */\nexport function computeConsecutiveDays(closes: number[]): number[] {\n const n = closes.length;\n const result = new Array<number>(n).fill(0);\n\n for (let i = 1; i < n; i++) {\n if (closes[i] > closes[i - 1]) {\n // Up day: continue positive streak or start at +1\n result[i] = result[i - 1] >= 0 ? result[i - 1] + 1 : 1;\n } else if (closes[i] < closes[i - 1]) {\n // Down day: continue negative streak or start at -1\n result[i] = result[i - 1] <= 0 ? result[i - 1] - 1 : -1;\n } else {\n // Flat: reset to 0\n result[i] = 0;\n }\n }\n\n return result;\n}\n\n/**\n * Gap filled indicator.\n * Returns 1 if the price gap from prior close was filled intraday, 0 otherwise.\n *\n * Gap up (open > priorClose): filled if low <= priorClose\n * Gap down (open < priorClose): filled if high >= priorClose\n * No gap (open = priorClose): returns 0\n */\nexport function isGapFilled(\n open: number,\n high: number,\n low: number,\n priorClose: number\n): number {\n if (open > priorClose && low <= priorClose) return 1;\n if (open < priorClose && high >= priorClose) return 1;\n return 0;\n}\n\n/**\n * Options expiration (OPEX) detection.\n * Takes a YYYY-MM-DD string; returns 1 if 3rd Friday of month, 0 otherwise.\n *\n * Uses string parsing (not new Date(\"YYYY-MM-DD\")) to avoid timezone issues.\n * See CLAUDE.md Date Handling rules: calendar dates from CSVs use local Date constructor.\n */\nexport function isOpex(dateStr: string): number {\n // Parse via regex to avoid timezone issues (CLAUDE.md: use string parsing)\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(dateStr);\n if (!match) return 0;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // 0-indexed for Date constructor\n const day = parseInt(match[3], 10);\n\n // Use local Date constructor (avoids UTC midnight shift)\n // Check if this day is a Friday (getDay() === 5)\n const date = new Date(year, month, day);\n if (date.getDay() !== 5) return 0;\n\n // Find first Friday of month\n const firstDay = new Date(year, month, 1);\n const firstFridayDay = ((5 - firstDay.getDay() + 7) % 7) + 1; // day of month\n\n // Third Friday = first Friday + 14\n const thirdFriday = firstFridayDay + 14;\n\n return day === thirdFriday ? 1 : 0;\n}\n\n// =============================================================================\n// Tier 2 VIX Functions\n// =============================================================================\n\n/**\n * Compute VIX-derived fields for market.context rows.\n * Takes sorted context rows (oldest first) with VIX OHLCV data.\n * Returns enriched rows with pct change, ratio, and spike fields.\n *\n * Fields requiring prior row (NaN on first row):\n * - VIX_Gap_Pct: (VIX_Open - prior VIX_Close) / prior VIX_Close * 100\n * - VIX_Change_Pct: (VIX_Close - prior VIX_Close) / prior VIX_Close * 100\n * - VIX9D_Change_Pct: (VIX9D_Close - VIX9D_Open) / VIX9D_Open * 100\n * - VIX3M_Change_Pct: (VIX3M_Close - VIX3M_Open) / VIX3M_Open * 100\n *\n * Same-day fields (no lookback needed):\n * - VIX9D_VIX_Ratio: VIX9D_Close / VIX_Close\n * - VIX_VIX3M_Ratio: VIX_Close / VIX3M_Close\n * - VIX_Spike_Pct: (VIX_High - VIX_Open) / VIX_Open * 100\n */\nexport function computeVIXDerivedFields(rows: ContextRow[]): EnrichedContextRow[] {\n return rows.map((row, i): EnrichedContextRow => {\n const prev = i > 0 ? rows[i - 1] : null;\n\n // Effective open: prefer RTH open from intraday bars, fall back to daily VIX_Open\n const effectiveOpen = row.VIX_RTH_Open ?? row.VIX_Open;\n\n // Same-day ratio and spike fields\n const VIX9D_VIX_Ratio =\n row.VIX9D_Close != null && row.VIX_Close != null && row.VIX_Close !== 0\n ? row.VIX9D_Close / row.VIX_Close\n : null;\n\n const VIX_VIX3M_Ratio =\n row.VIX_Close != null && row.VIX3M_Close != null && row.VIX3M_Close !== 0\n ? row.VIX_Close / row.VIX3M_Close\n : null;\n\n const VIX_Spike_Pct =\n row.VIX_High != null && effectiveOpen != null && effectiveOpen !== 0\n ? ((row.VIX_High - effectiveOpen) / effectiveOpen) * 100\n : null;\n\n // Intraday change fields (same-day open to close)\n const VIX9D_Change_Pct =\n row.VIX9D_Close != null && row.VIX9D_Open != null && row.VIX9D_Open !== 0\n ? ((row.VIX9D_Close - row.VIX9D_Open) / row.VIX9D_Open) * 100\n : null;\n\n const VIX3M_Change_Pct =\n row.VIX3M_Close != null && row.VIX3M_Open != null && row.VIX3M_Open !== 0\n ? ((row.VIX3M_Close - row.VIX3M_Open) / row.VIX3M_Open) * 100\n : null;\n\n // Prior-row dependent fields\n const prevVIXClose = prev?.VIX_Close ?? null;\n\n const VIX_Gap_Pct =\n effectiveOpen != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((effectiveOpen - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n const VIX_Change_Pct =\n row.VIX_Close != null && prevVIXClose != null && prevVIXClose !== 0\n ? ((row.VIX_Close - prevVIXClose) / prevVIXClose) * 100\n : null;\n\n return {\n ...row,\n VIX_Gap_Pct,\n VIX_Change_Pct,\n VIX9D_Change_Pct,\n VIX3M_Change_Pct,\n VIX9D_VIX_Ratio,\n VIX_VIX3M_Ratio,\n VIX_Spike_Pct,\n };\n });\n}\n\n/**\n * Classify trend direction based on 20-day return percentage.\n *\n * Uses Return_20D thresholds:\n * > 1% = \"up\"\n * < -1% = \"down\"\n * else = \"flat\" (between -1% and 1% inclusive)\n *\n * Returns null for null/NaN input (no Return_20D data available).\n */\nexport function classifyTrendDirection(return20d: number | null): string | null {\n if (return20d === null || return20d === undefined || isNaN(return20d)) return null;\n if (return20d > 1) return \"up\";\n if (return20d < -1) return \"down\";\n return \"flat\";\n}\n\n/**\n * Classify VIX level into volatility regime 1-6.\n *\n * 1: Very Low VIX < 13\n * 2: Low 13 <= VIX < 16\n * 3: Normal 16 <= VIX < 20\n * 4: Elevated 20 <= VIX < 25\n * 5: High 25 <= VIX < 30\n * 6: Extreme VIX >= 30\n */\nexport function classifyVolRegime(vixClose: number): number {\n if (vixClose < 13) return 1;\n if (vixClose < 16) return 2;\n if (vixClose < 20) return 3;\n if (vixClose < 25) return 4;\n if (vixClose < 30) return 5;\n return 6;\n}\n\n/**\n * Classify VIX term structure state.\n * Returns:\n * 1 = Contango: VIX9D < VIX and VIX < VIX3M (normal, longer-dated vol is higher)\n * -1 = Backwardation: VIX9D > VIX or VIX > VIX3M (inverted — fear in front)\n * 0 = Flat: all three within ~1% tolerance of each other\n *\n * Flatness check: both ratios VIX9D/VIX and VIX/VIX3M within 1% of 1.0\n */\nexport function classifyTermStructure(\n vix9dClose: number,\n vixClose: number,\n vix3mClose: number\n): number {\n // Match PineScript: vix9dClose > vixClose ? -1 : vixClose > vix3mClose ? 0 : 1\n if (vix9dClose > vixClose) return -1;\n if (vixClose > vix3mClose) return 0;\n return 1;\n}\n\n/**\n * Implied Volatility Rank (IVR) over a rolling window.\n * IVR[i] = (current - min) / (max - min) * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Per D-10: Shows where current value sits in its 252-day range.\n * When range is 0 (all values identical), returns 50 (middle).\n */\nexport function computeIVR(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let min = Infinity, max = -Infinity;\n for (let j = i - period + 1; j <= i; j++) {\n if (values[j] < min) min = values[j];\n if (values[j] > max) max = values[j];\n }\n const range = max - min;\n result[i] = range > 0 ? ((values[i] - min) / range) * 100 : 50;\n }\n return result;\n}\n\n/**\n * Implied Volatility Percentile (IVP) over a rolling window.\n * IVP[i] = count(prior 251 days where value <= current) / 251 * 100\n * Returns array same length as input; first `period-1` entries are NaN.\n * Per D-10: Shows what percentage of past year was at or below current.\n * Note: divides by (period - 1) = 251 because we compare against prior days only.\n */\nexport function computeIVP(values: number[], period = 252): number[] {\n const n = values.length;\n const result = new Array<number>(n).fill(NaN);\n for (let i = period - 1; i < n; i++) {\n let countLessOrEqual = 0;\n // Compare current against prior (period-1) days (not including current day itself)\n for (let j = i - period + 1; j < i; j++) {\n if (values[j] <= values[i]) countLessOrEqual++;\n }\n result[i] = (countLessOrEqual / (period - 1)) * 100;\n }\n return result;\n}\n\n// =============================================================================\n// Enrichment Runner Types\n// =============================================================================\n\nexport interface EnrichmentOptions {\n forceFull?: boolean;\n}\n\nexport interface TierStatus {\n status: \"complete\" | \"skipped\" | \"error\";\n fieldsWritten?: number;\n reason?: string;\n}\n\nexport interface EnrichmentResult {\n ticker: string;\n tier1: TierStatus;\n tier2: TierStatus;\n tier3: TierStatus;\n rowsEnriched: number;\n enrichedThrough: string | null;\n}\n\n// =============================================================================\n// Enrichment Runner Private Helpers\n// =============================================================================\n\n/** Subtract N calendar days from a YYYY-MM-DD string, returns YYYY-MM-DD */\nfunction subtractDays(dateStr: string, days: number): string {\n const d = new Date(dateStr + \"T00:00:00Z\");\n d.setUTCDate(d.getUTCDate() - days);\n return d.toISOString().split(\"T\")[0];\n}\n\n/** Parse YYYY-MM-DD to a local Date without timezone conversion */\nfunction parseDateStr(dateStr: string): Date | null {\n const m = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (!m) return null;\n return new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));\n}\n\n/** Batch UPDATE market.daily with computed enrichment fields */\nasync function batchUpdateDaily(\n conn: DuckDBConnection,\n rows: Array<Record<string, unknown>>,\n columns: string[]\n): Promise<void> {\n if (rows.length === 0) return;\n // Build VALUES list with $N params\n const allCols = [\"ticker\", \"date\", ...columns];\n const placeholders = rows\n .map((_, rowIdx) => {\n const params = allCols.map((__, colIdx) => `$${rowIdx * allCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n })\n .join(\", \");\n const setClauses = columns.map((c) => `${c} = v.${c}`).join(\", \");\n const sql = `\n UPDATE market.daily AS t\n SET ${setClauses}\n FROM (VALUES ${placeholders}) AS v(${allCols.join(\", \")})\n WHERE t.ticker = v.ticker AND t.date = v.date\n `;\n const params = rows.flatMap((row) => allCols.map((col) => row[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n}\n\n/** Run Tier 2: enrich market.daily (ivr/ivp) and market._context_derived with computed VIX fields */\nasync function runTier2(conn: DuckDBConnection): Promise<TierStatus> {\n // Step 1: Discover VIX-family tickers dynamically (per D-06)\n const tickerResult = await conn.runAndReadAll(\n `SELECT DISTINCT ticker FROM market.daily WHERE ticker LIKE 'VIX%' ORDER BY ticker`\n );\n const vixTickers = tickerResult.getRows().map(r => r[0] as string);\n if (vixTickers.length === 0 || !vixTickers.includes('VIX')) {\n return { status: \"skipped\", reason: \"no VIX data in market.daily — import VIX ticker first\" };\n }\n\n // Step 2: Compute IVR/IVP for each VIX-family ticker and write to market.daily\n for (const ticker of vixTickers) {\n const closeResult = await conn.runAndReadAll(\n `SELECT date, close FROM market.daily WHERE ticker = $1 AND close IS NOT NULL ORDER BY date ASC`,\n [ticker]\n );\n const rows = closeResult.getRows();\n if (rows.length === 0) continue;\n\n const dates = rows.map(r => r[0] as string);\n const closes = rows.map(r => r[1] as number);\n const ivrValues = computeIVR(closes, 252);\n const ivpValues = computeIVP(closes, 252);\n\n // Batch UPDATE market.daily SET ivr, ivp WHERE ticker = ? AND date = ?\n const BATCH_SIZE = 500;\n for (let start = 0; start < dates.length; start += BATCH_SIZE) {\n const batchDates = dates.slice(start, start + BATCH_SIZE);\n const batchIvr = ivrValues.slice(start, start + BATCH_SIZE);\n const batchIvp = ivpValues.slice(start, start + BATCH_SIZE);\n\n const placeholders = batchDates.map((_, rowIdx) => {\n const base = rowIdx * 3;\n return `($${base + 1}, $${base + 2}, $${base + 3})`;\n }).join(\", \");\n\n const sql = `\n UPDATE market.daily AS t\n SET ivr = v.ivr, ivp = v.ivp\n FROM (VALUES ${placeholders}) AS v(date, ivr, ivp)\n WHERE t.ticker = $${batchDates.length * 3 + 1} AND t.date = v.date\n `;\n const params: (string | number | null)[] = [];\n for (let i = 0; i < batchDates.length; i++) {\n params.push(batchDates[i]);\n params.push(isNaN(batchIvr[i]) ? null : batchIvr[i]);\n params.push(isNaN(batchIvp[i]) ? null : batchIvp[i]);\n }\n params.push(ticker);\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n }\n\n // Step 3: Build ContextRow objects from market.daily VIX tickers for derived fields\n // Query VIX close/open/high, VIX9D close/open, VIX3M close/open, plus Return_20D for Trend_Direction\n const contextQuery = `\n SELECT\n vix.date,\n vix.open AS VIX_Open,\n vix.close AS VIX_Close,\n vix.high AS VIX_High,\n vix9d.open AS VIX9D_Open,\n vix9d.close AS VIX9D_Close,\n vix3m.open AS VIX3M_Open,\n vix3m.close AS VIX3M_Close,\n spx.Return_20D\n FROM market.daily vix\n LEFT JOIN market.daily vix9d ON vix9d.date = vix.date AND vix9d.ticker = 'VIX9D'\n LEFT JOIN market.daily vix3m ON vix3m.date = vix.date AND vix3m.ticker = 'VIX3M'\n LEFT JOIN market.daily spx ON spx.date = vix.date AND spx.ticker = $1\n WHERE vix.ticker = 'VIX' AND vix.close IS NOT NULL\n ORDER BY vix.date ASC\n `;\n\n const rawResult = await conn.runAndReadAll(contextQuery, [DEFAULT_MARKET_TICKER]);\n const rawRows = rawResult.getRows();\n if (rawRows.length === 0) return { status: \"complete\", fieldsWritten: 0 };\n\n // Query VIX RTH open from intraday bars (same logic as before)\n const rthOpenByDate = new Map<string, number>();\n try {\n const rthReader = await conn.runAndReadAll(\n `SELECT date, open FROM market.intraday WHERE ticker = 'VIX' AND time >= '09:30' AND time <= '09:32' ORDER BY date, time ASC`\n );\n for (const r of rthReader.getRows()) {\n const dateStr = r[0] as string;\n if (!rthOpenByDate.has(dateStr)) {\n const openVal = r[1] as number | null;\n if (openVal != null) rthOpenByDate.set(dateStr, openVal);\n }\n }\n } catch {\n // No intraday VIX data — continue\n }\n\n const return20dByDate = new Map<string, number | null>();\n const contextRows: ContextRow[] = rawRows.map((r) => {\n const dateStr = r[0] as string;\n return20dByDate.set(dateStr, r[8] as number | null);\n return {\n date: dateStr,\n VIX_Open: r[1] as number | null,\n VIX_Close: r[2] as number | null,\n VIX_High: r[3] as number | null,\n VIX_RTH_Open: rthOpenByDate.get(dateStr) ?? null,\n VIX9D_Open: r[4] as number | null,\n VIX9D_Close: r[5] as number | null,\n VIX3M_Open: r[6] as number | null,\n VIX3M_Close: r[7] as number | null,\n };\n });\n\n // Step 4: Compute derived fields (reuse existing pure functions unchanged)\n const enrichedContext = computeVIXDerivedFields(contextRows);\n\n // Step 5: Write derived fields to market._context_derived (INSERT OR REPLACE)\n const derivedCols = [\"date\", \"Vol_Regime\", \"Term_Structure_State\", \"Trend_Direction\", \"VIX_Spike_Pct\", \"VIX_Gap_Pct\"];\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedContext.length; start += BATCH_SIZE) {\n const batch = enrichedContext.slice(start, start + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = derivedCols.map((__, colIdx) => `$${rowIdx * derivedCols.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n\n const sql = `INSERT OR REPLACE INTO market._context_derived (${derivedCols.join(\", \")}) VALUES ${placeholders}`;\n const params = batch.flatMap((r) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return [\n r.date,\n vc !== null ? classifyVolRegime(vc) : null,\n v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n r.VIX_Spike_Pct ?? null,\n r.VIX_Gap_Pct ?? null,\n ];\n });\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n\n // Step 6: ALSO still write to market.context for backward compat during transition\n // This ensures existing queries against market.context keep working\n const tier2Cols = [\n \"VIX_RTH_Open\", \"VIX_Gap_Pct\", \"VIX_Change_Pct\",\n \"VIX9D_Change_Pct\", \"VIX3M_Change_Pct\",\n \"VIX9D_VIX_Ratio\", \"VIX_VIX3M_Ratio\", \"VIX_Spike_Pct\",\n \"Vol_Regime\", \"Term_Structure_State\",\n \"VIX_IVR\", \"VIX_IVP\", \"VIX9D_IVR\", \"VIX9D_IVP\", \"VIX3M_IVR\", \"VIX3M_IVP\",\n \"Trend_Direction\",\n ];\n // Compute IVR/IVP arrays for VIX, VIX9D, VIX3M from contextRows\n const vixCloses = contextRows.map(r => r.VIX_Close ?? NaN);\n const vixIVR = computeIVR(vixCloses, 252);\n const vixIVP = computeIVP(vixCloses, 252);\n const vix9dCloses = contextRows.map(r => r.VIX9D_Close ?? NaN);\n const vix9dIVR = computeIVR(vix9dCloses, 252);\n const vix9dIVP = computeIVP(vix9dCloses, 252);\n const vix3mCloses = contextRows.map(r => r.VIX3M_Close ?? NaN);\n const vix3mIVR = computeIVR(vix3mCloses, 252);\n const vix3mIVP = computeIVP(vix3mCloses, 252);\n\n try {\n const allCols2 = [\"date\", ...tier2Cols];\n const updateRows = enrichedContext.map((r, i) => {\n const vc = r.VIX_Close ?? null;\n const v9 = r.VIX9D_Close ?? null;\n const v3m = r.VIX3M_Close ?? null;\n return {\n date: r.date,\n VIX_RTH_Open: r.VIX_RTH_Open ?? null,\n VIX_Gap_Pct: r.VIX_Gap_Pct ?? null,\n VIX_Change_Pct: r.VIX_Change_Pct ?? null,\n VIX9D_Change_Pct: r.VIX9D_Change_Pct ?? null,\n VIX3M_Change_Pct: r.VIX3M_Change_Pct ?? null,\n VIX9D_VIX_Ratio: r.VIX9D_VIX_Ratio ?? null,\n VIX_VIX3M_Ratio: r.VIX_VIX3M_Ratio ?? null,\n VIX_Spike_Pct: r.VIX_Spike_Pct ?? null,\n Vol_Regime: vc !== null ? classifyVolRegime(vc) : null,\n Term_Structure_State: v9 !== null && vc !== null && v3m !== null ? classifyTermStructure(v9, vc, v3m) : null,\n VIX_IVR: isNaN(vixIVR[i]) ? null : vixIVR[i],\n VIX_IVP: isNaN(vixIVP[i]) ? null : vixIVP[i],\n VIX9D_IVR: isNaN(vix9dIVR[i]) ? null : vix9dIVR[i],\n VIX9D_IVP: isNaN(vix9dIVP[i]) ? null : vix9dIVP[i],\n VIX3M_IVR: isNaN(vix3mIVR[i]) ? null : vix3mIVR[i],\n VIX3M_IVP: isNaN(vix3mIVP[i]) ? null : vix3mIVP[i],\n Trend_Direction: classifyTrendDirection(return20dByDate.get(r.date) ?? null),\n };\n });\n for (let start2 = 0; start2 < updateRows.length; start2 += BATCH_SIZE) {\n const batch = updateRows.slice(start2, start2 + BATCH_SIZE);\n const placeholders = batch.map((_, rowIdx) => {\n const params = allCols2.map((__, colIdx) => `$${rowIdx * allCols2.length + colIdx + 1}`);\n return `(${params.join(\", \")})`;\n }).join(\", \");\n const setClauses = tier2Cols.map(c => `${c} = v.${c}`).join(\", \");\n const sql = `UPDATE market.context AS t SET ${setClauses} FROM (VALUES ${placeholders}) AS v(${allCols2.join(\", \")}) WHERE t.date = v.date`;\n const params = batch.flatMap(row => allCols2.map(col => (row as Record<string, unknown>)[col] ?? null));\n await conn.run(sql, params as (string | number | boolean | null | bigint)[]);\n }\n } catch {\n // market.context may not exist or have rows — that's fine during transition\n }\n\n return { status: \"complete\", fieldsWritten: derivedCols.length - 1 }; // -1 for date\n}\n\n/** Check if any intraday data exists for a ticker */\nasync function hasTier3Data(conn: DuckDBConnection, ticker: string): Promise<boolean> {\n const r = await conn.runAndReadAll(\n `SELECT COUNT(*) FROM market.intraday WHERE ticker = $1 LIMIT 1`,\n [ticker]\n );\n return Number(r.getRows()[0]?.[0] ?? 0) > 0;\n}\n\n// =============================================================================\n// Context Enrichment (Tier 2 standalone)\n// =============================================================================\n\n/**\n * Run Tier 2 context enrichment directly, computing VIX-derived fields\n * (VIX_Gap_Pct, VIX_Change_Pct, VIX9D_VIX_Ratio, Vol_Regime, etc.) and\n * writing them to market.context.\n *\n * Used by importFromMassive() for context table imports — after importing\n * VIX/VIX9D/VIX3M bars, Tier 2 needs to run immediately to populate derived\n * fields. Unlike runEnrichment(), this does not require a ticker with daily data.\n *\n * Returns a TierStatus describing the outcome.\n */\nexport async function runContextEnrichment(conn: DuckDBConnection): Promise<TierStatus> {\n return runTier2(conn);\n}\n\n// =============================================================================\n// Enrichment Runner\n// =============================================================================\n\n/**\n * Run all three tiers of market enrichment for a given ticker.\n *\n * Tier 1: Compute and write OHLCV-derived fields to market.daily using a\n * 200-day lookback window from the enriched_through watermark.\n * Tier 2: Compute and write VIX-derived fields to market.context.\n * Tier 3: Compute intraday timing fields (High_Time, Low_Time, High_Before_Low,\n * Reversal_Type, Opening_Drive_Strength, Intraday_Realized_Vol) from\n * market.intraday bars; skips gracefully if no intraday data exists.\n *\n * The enriched_through watermark is upserted into market._sync_metadata with\n * source='enrichment', ticker, target_table='daily' after a successful Tier 1 run.\n *\n * Note: wilder_state column exists but is NOT written (superseded by 200-day lookback).\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param ticker - Normalized ticker symbol (e.g., \"SPX\")\n * @param opts - Options including forceFull (reset watermark and reprocess all rows)\n */\nexport async function runEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n opts: EnrichmentOptions = {}\n): Promise<EnrichmentResult> {\n const { forceFull = false } = opts;\n\n // 1. Get enriched_through watermark\n const watermarkRow = await conn.runAndReadAll(\n `SELECT enriched_through FROM market._sync_metadata\n WHERE source = 'enrichment' AND ticker = $1 AND target_table = 'daily'`,\n [ticker]\n );\n const watermark: string | null = forceFull\n ? null\n : ((watermarkRow.getRows()[0]?.[0] as string | null) ?? null);\n\n // 2. Compute lookback start: watermark - 200 calendar days (as string comparison)\n const lookbackStart = watermark ? subtractDays(watermark, 200) : null;\n\n // 3. Fetch OHLCV rows from market.daily\n let fetchSql = `SELECT ticker, date, open, high, low, close FROM market.daily WHERE ticker = $1`;\n const fetchParams: unknown[] = [ticker];\n if (lookbackStart) {\n fetchSql += ` AND date >= $2`;\n fetchParams.push(lookbackStart);\n }\n fetchSql += ` ORDER BY date ASC`;\n const rawReader = await conn.runAndReadAll(fetchSql, fetchParams as (string | number | boolean | null | bigint)[]);\n const rawRows = rawReader.getRows();\n\n if (rawRows.length === 0) {\n return {\n ticker,\n tier1: { status: \"skipped\", reason: \"no data in market.daily\" },\n tier2: { status: \"skipped\", reason: \"no daily data\" },\n tier3: { status: \"skipped\", reason: \"no daily data\" },\n rowsEnriched: 0,\n enrichedThrough: null,\n };\n }\n\n // 4. Extract typed arrays from raw rows\n // Columns: ticker(0), date(1), open(2), high(3), low(4), close(5)\n const dates = rawRows.map((r) => r[1] as string);\n const opens = rawRows.map((r) => Number(r[2]));\n const highs = rawRows.map((r) => Number(r[3]));\n const lows = rawRows.map((r) => Number(r[4]));\n const closes = rawRows.map((r) => Number(r[5]));\n\n // 5. Compute Tier 1 indicators\n const rsi14 = computeRSI(closes, 14);\n const atrArr = computeATR(highs, lows, closes, 14);\n const ema21 = computeEMA(closes, 21);\n const sma50 = computeSMA(closes, 50);\n const rvol5 = computeRealizedVol(closes, 5);\n const rvol20 = computeRealizedVol(closes, 20);\n const consecutiveDays = computeConsecutiveDays(closes);\n\n // 6. Determine which rows to write back (only rows after watermark)\n const writeRows =\n watermark && !forceFull\n ? rawRows.map((_, i) => i).filter((i) => dates[i] > watermark)\n : rawRows.map((_, i) => i);\n\n if (writeRows.length === 0) {\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: 0, reason: \"already up to date\" },\n tier2: await runTier2(conn),\n tier3: {\n status: \"skipped\",\n reason: \"no intraday data in market.intraday\",\n },\n rowsEnriched: 0,\n enrichedThrough: watermark,\n };\n }\n\n // 7. Build enriched rows for batch UPDATE\n const enrichedRows = writeRows.map((i) => {\n const atrVal = atrArr[i];\n const atrPct = !isNaN(atrVal) && closes[i] > 0 ? (atrVal / closes[i]) * 100 : null;\n const priorClose = i > 0 ? closes[i - 1] : null;\n const priorReturn =\n i > 1 ? ((closes[i - 1] - closes[i - 2]) / closes[i - 2]) * 100 : null;\n const gapPct =\n priorClose !== null && priorClose > 0\n ? ((opens[i] - priorClose) / priorClose) * 100\n : null;\n const intradayRangePct =\n opens[i] > 0 ? ((highs[i] - lows[i]) / opens[i]) * 100 : null;\n const intradayReturnPct =\n opens[i] > 0 ? ((closes[i] - opens[i]) / opens[i]) * 100 : null;\n const hiLoRange = highs[i] - lows[i];\n const closePosInRange =\n hiLoRange > 0 ? (closes[i] - lows[i]) / hiLoRange : null;\n const ret5d =\n i >= 5 && closes[i - 5] > 0\n ? ((closes[i] - closes[i - 5]) / closes[i - 5]) * 100\n : null;\n const ret20d =\n i >= 20 && closes[i - 20] > 0\n ? ((closes[i] - closes[i - 20]) / closes[i - 20]) * 100\n : null;\n const gapFilled =\n priorClose !== null ? isGapFilled(opens[i], highs[i], lows[i], priorClose) : null;\n const dateObj = parseDateStr(dates[i]);\n const dayOfWeek = dateObj ? dateObj.getDay() : null; // 0=Sun..6=Sat\n const monthVal = dateObj ? dateObj.getMonth() + 1 : null;\n const opex = isOpex(dates[i]);\n const ema21val = ema21[i];\n const sma50val = sma50[i];\n const priceVsEma21 =\n !isNaN(ema21val) && ema21val > 0\n ? ((closes[i] - ema21val) / ema21val) * 100\n : null;\n const priceVsSma50 =\n !isNaN(sma50val) && sma50val > 0\n ? ((closes[i] - sma50val) / sma50val) * 100\n : null;\n const rsi14val = rsi14[i];\n\n // Prior_Range_vs_ATR: prior day's (high - low) / ATR[i-1]\n // Known at market open (prior day range and ATR are available before trading begins).\n // First bar (i=0) has no prior day, so null. ATR[i-1] must be valid (non-NaN).\n let priorRangeVsATR: number | null = null;\n if (i > 0) {\n const priorATR = atrArr[i - 1];\n if (!isNaN(priorATR) && priorATR > 0) {\n priorRangeVsATR = (highs[i - 1] - lows[i - 1]) / priorATR;\n }\n }\n\n return {\n ticker,\n date: dates[i],\n Prior_Close: priorClose,\n Gap_Pct: gapPct,\n RSI_14: isNaN(rsi14val) ? null : rsi14val,\n ATR_Pct: atrPct,\n Price_vs_EMA21_Pct: priceVsEma21,\n Price_vs_SMA50_Pct: priceVsSma50,\n Realized_Vol_5D: isNaN(rvol5[i]) ? null : rvol5[i],\n Realized_Vol_20D: isNaN(rvol20[i]) ? null : rvol20[i],\n Return_5D: ret5d,\n Return_20D: ret20d,\n Intraday_Range_Pct: intradayRangePct,\n Intraday_Return_Pct: intradayReturnPct,\n Close_Position_In_Range: closePosInRange,\n Gap_Filled: gapFilled,\n Consecutive_Days: consecutiveDays[i],\n Prev_Return_Pct: priorReturn,\n Day_of_Week: dayOfWeek,\n Month: monthVal,\n Is_Opex: opex,\n Prior_Range_vs_ATR: priorRangeVsATR,\n };\n });\n\n // 8. Batch UPDATE via DuckDB VALUES CTE, batches of 500\n const BATCH_SIZE = 500;\n const columns = [\n \"Prior_Close\",\n \"Gap_Pct\",\n \"RSI_14\",\n \"ATR_Pct\",\n \"Price_vs_EMA21_Pct\",\n \"Price_vs_SMA50_Pct\",\n \"Realized_Vol_5D\",\n \"Realized_Vol_20D\",\n \"Return_5D\",\n \"Return_20D\",\n \"Intraday_Range_Pct\",\n \"Intraday_Return_Pct\",\n \"Close_Position_In_Range\",\n \"Gap_Filled\",\n \"Consecutive_Days\",\n \"Prev_Return_Pct\",\n \"Day_of_Week\",\n \"Month\",\n \"Is_Opex\",\n \"Prior_Range_vs_ATR\",\n ];\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, columns);\n }\n\n // 9. Run Tier 2 (VIX context enrichment)\n const tier2Result = await runTier2(conn);\n\n // 10. Tier 3 — intraday timing fields from market.intraday\n const tier3Result = await runTier3(conn, ticker, dates);\n\n // 11. Update enriched_through watermark\n const newWatermark = dates[dates.length - 1];\n await upsertMarketImportMetadata(conn, {\n source: \"enrichment\",\n ticker,\n target_table: \"daily\",\n max_date: null,\n enriched_through: newWatermark,\n synced_at: new Date(),\n });\n\n return {\n ticker,\n tier1: { status: \"complete\", fieldsWritten: columns.length },\n tier2: tier2Result,\n tier3: tier3Result,\n rowsEnriched: enrichedRows.length,\n enrichedThrough: newWatermark,\n };\n}\n\n// =============================================================================\n// Tier 3: Intraday Timing Fields\n// =============================================================================\n\n/**\n * Convert HH:MM time string to decimal hours (e.g., \"10:30\" → 10.5).\n */\nfunction hhmmToDecimalHours(time: string): number {\n const [h, m] = time.split(\":\").map(Number);\n return h + m / 60;\n}\n\n/**\n * Compute intraday timing fields from raw OHLCV bars for a single date.\n *\n * Pure function — no DB access. Exported for unit testing.\n *\n * Fields computed:\n * - highTime: Decimal hours when day high occurred (e.g., 10.5 = 10:30)\n * - lowTime: Decimal hours when day low occurred\n * - highBeforeLow: true if high occurred before low\n * - reversalType: +1 = morning high + afternoon low, -1 = morning low + afternoon high, 0 = trend day\n * - openingDriveStrength: (first 30-min range) / (full day range), 0-1 scale; 0 if day range is 0\n * - intradayRealizedVol: Annualized realized vol from intraday bar-to-bar log returns (decimal, not %)\n *\n * @param bars - Array of {time: \"HH:MM\", open, high, low, close} ordered by time (oldest first)\n * @returns Computed fields or null if bars is empty\n */\nexport function computeIntradayTimingFields(\n bars: Array<{ time: string; open: number; high: number; low: number; close: number }>\n): {\n highTime: number;\n lowTime: number;\n highBeforeLow: boolean;\n reversalType: number;\n openingDriveStrength: number;\n intradayRealizedVol: number;\n} | null {\n if (bars.length === 0) return null;\n\n let maxHigh = -Infinity;\n let minLow = Infinity;\n let highTimeStr = bars[0].time;\n let lowTimeStr = bars[0].time;\n\n for (const bar of bars) {\n if (bar.high > maxHigh) {\n maxHigh = bar.high;\n highTimeStr = bar.time;\n }\n if (bar.low < minLow) {\n minLow = bar.low;\n lowTimeStr = bar.time;\n }\n }\n\n const highTime = hhmmToDecimalHours(highTimeStr);\n const lowTime = hhmmToDecimalHours(lowTimeStr);\n const highBeforeLow = highTime < lowTime;\n\n // Reversal type: morning = before 12:00, afternoon = 12:00 or later\n const highInMorning = highTime < 12;\n const lowInMorning = lowTime < 12;\n const highInAfternoon = highTime >= 12;\n const lowInAfternoon = lowTime >= 12;\n\n let reversalType = 0;\n if (highInMorning && lowInAfternoon) reversalType = 1; // High morning, low afternoon\n else if (lowInMorning && highInAfternoon) reversalType = -1; // Low morning, high afternoon\n\n // Opening Drive Strength: ratio of first-30-min range to full-day range\n // First 30 min = bars with time < 10:00 (market opens 09:30)\n const openingBars = bars.filter(b => hhmmToDecimalHours(b.time) < 10);\n let openingDriveStrength = 0;\n const fullDayRange = maxHigh - minLow;\n if (openingBars.length > 0 && fullDayRange > 0) {\n const openHigh = Math.max(...openingBars.map(b => b.high));\n const openLow = Math.min(...openingBars.map(b => b.low));\n openingDriveStrength = (openHigh - openLow) / fullDayRange;\n }\n\n // Intraday Realized Vol: annualized from bar-to-bar close log returns\n // Uses sqrt(252 * barsPerDay) annualization\n let intradayRealizedVol = 0;\n if (bars.length >= 2) {\n const logReturns: number[] = [];\n for (let i = 1; i < bars.length; i++) {\n if (bars[i - 1].close > 0 && bars[i].close > 0) {\n logReturns.push(Math.log(bars[i].close / bars[i - 1].close));\n }\n }\n if (logReturns.length > 0) {\n const mean = logReturns.reduce((s, r) => s + r, 0) / logReturns.length;\n const variance = logReturns.reduce((s, r) => s + (r - mean) ** 2, 0) / logReturns.length;\n const barStdDev = Math.sqrt(variance);\n // Annualize: multiply by sqrt(barsPerDay * 252)\n // barsPerDay = number of bars we actually have (adapts to timeframe)\n intradayRealizedVol = barStdDev * Math.sqrt(bars.length * 252);\n }\n }\n\n return { highTime, lowTime, highBeforeLow, reversalType, openingDriveStrength, intradayRealizedVol };\n}\n\n/** Run Tier 3: compute intraday timing fields from market.intraday and write to market.daily */\nasync function runTier3(\n conn: DuckDBConnection,\n ticker: string,\n dates: string[]\n): Promise<TierStatus> {\n // Check if intraday data exists for this ticker\n const hasData = await hasTier3Data(conn, ticker);\n if (!hasData) {\n return {\n status: \"skipped\",\n reason: \"no intraday data in market.intraday — import intraday bars to populate Tier 3 fields\",\n };\n }\n\n // Query intraday bars for all dates in the enrichment range\n const result = await conn.runAndReadAll(\n `SELECT date, time, open, high, low, close\n FROM market.intraday\n WHERE ticker = $1 AND date >= $2 AND date <= $3\n ORDER BY date, time`,\n [ticker, dates[0], dates[dates.length - 1]]\n );\n\n const rows = result.getRows();\n const columns = result.columnNames();\n const dateIdx = columns.indexOf(\"date\");\n const timeIdx = columns.indexOf(\"time\");\n const openIdx = columns.indexOf(\"open\");\n const highIdx = columns.indexOf(\"high\");\n const lowIdx = columns.indexOf(\"low\");\n const closeIdx = columns.indexOf(\"close\");\n\n // Group bars by date\n const barsByDate = new Map<string, Array<{ time: string; open: number; high: number; low: number; close: number }>>();\n for (const row of rows) {\n const dateStr = String(row[dateIdx]);\n const bar = {\n time: String(row[timeIdx]),\n open: Number(row[openIdx]),\n high: Number(row[highIdx]),\n low: Number(row[lowIdx]),\n close: Number(row[closeIdx]),\n };\n if (!barsByDate.has(dateStr)) barsByDate.set(dateStr, []);\n barsByDate.get(dateStr)!.push(bar);\n }\n\n if (barsByDate.size === 0) {\n return {\n status: \"skipped\",\n reason: \"intraday data exists but no bars overlap with enrichment date range\",\n };\n }\n\n // Compute timing fields for each date and batch update market.daily\n const tier3Cols = [\"High_Time\", \"Low_Time\", \"High_Before_Low\", \"Reversal_Type\", \"Opening_Drive_Strength\", \"Intraday_Realized_Vol\"];\n const enrichedRows: Array<Record<string, unknown>> = [];\n\n for (const [dateStr, bars] of barsByDate) {\n const timing = computeIntradayTimingFields(bars);\n if (!timing) continue;\n\n enrichedRows.push({\n ticker,\n date: dateStr,\n High_Time: timing.highTime,\n Low_Time: timing.lowTime,\n High_Before_Low: timing.highBeforeLow ? 1 : 0,\n Reversal_Type: timing.reversalType,\n Opening_Drive_Strength: timing.openingDriveStrength,\n Intraday_Realized_Vol: timing.intradayRealizedVol,\n });\n }\n\n // Batch update using the existing batchUpdateDaily helper\n const BATCH_SIZE = 500;\n for (let start = 0; start < enrichedRows.length; start += BATCH_SIZE) {\n const batch = enrichedRows.slice(start, start + BATCH_SIZE);\n await batchUpdateDaily(conn, batch, tier3Cols);\n }\n\n return { status: \"complete\", fieldsWritten: tier3Cols.length };\n}\n","/**\n * Market Data Importer\n *\n * Core ingestion logic for importing market data from CSV files and external DuckDB databases\n * into the normalized market schema tables (market.daily, market.context, market.intraday).\n *\n * Design principles:\n * - Validate column mapping BEFORE any writes (fail-clean semantics)\n * - ON CONFLICT DO NOTHING for idempotent merges\n * - ATTACH/DETACH lifecycle for external DBs\n * - normalizeTicker() called at import boundary before any DB write\n * - No withFullSync usage (DB-09: market writes must not be wrapped in analytics.duckdb transactions)\n * - No upsertMarketSyncMetadata (old schema) — only upsertMarketImportMetadata (Phase 60 schema)\n */\n\nimport type { DuckDBConnection } from \"@duckdb/node-api\";\nimport * as fs from \"fs/promises\";\nimport { normalizeTicker } from \"./ticker.js\";\nimport { upsertMarketImportMetadata } from \"../sync/metadata.js\";\nimport { runEnrichment, runContextEnrichment } from \"./market-enricher.js\";\nimport { getProvider, type AssetClass } from \"./market-provider.js\";\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst REQUIRED_SCHEMA_FIELDS: Record<string, string[]> = {\n daily: [\"date\", \"open\", \"high\", \"low\", \"close\"],\n context: [\"date\"],\n intraday: [\"date\", \"time\", \"open\", \"high\", \"low\", \"close\"],\n _context_derived: [\"date\"], // Phase 75: cross-ticker derived fields\n};\n\n// PK conflict target per table\nconst CONFLICT_TARGETS: Record<string, string> = {\n daily: \"(ticker, date)\",\n context: \"(date)\",\n intraday: \"(ticker, date, time)\",\n _context_derived: \"(date)\", // Phase 75: PK is date only\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ImportMarketCsvOptions {\n filePath: string;\n ticker: string;\n targetTable: \"daily\" | \"context\" | \"intraday\";\n columnMapping: Record<string, string>; // { csvColumn: schemaColumn }\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\nexport interface ImportFromDatabaseOptions {\n dbPath: string;\n query: string;\n ticker: string;\n targetTable: \"daily\" | \"context\" | \"intraday\";\n columnMapping: Record<string, string>; // { queryColumn: schemaColumn }\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\nexport interface ImportResult {\n rowsInserted: number;\n rowsUpdated: number;\n rowsSkipped: number;\n inputRowCount: number;\n dateRange: { min: string; max: string } | null;\n enrichment: { status: \"pending\" | \"complete\" | \"skipped\" | \"error\"; message: string };\n}\n\n// =============================================================================\n// Column Mapping Validation\n// =============================================================================\n\n/**\n * Validate that the column mapping covers all required schema fields for the target table.\n *\n * Special case for intraday: if `date` is mapped but `time` is not, the `time` field\n * will be auto-derived from the Unix timestamp in the date source column. In this case,\n * the missing `time` mapping is allowed — validation passes without it.\n *\n * @param columnMapping - Mapping from source columns to schema columns\n * @param targetTable - The target market table\n * @returns Validation result with missing field names\n */\nexport function validateColumnMapping(\n columnMapping: Record<string, string>,\n targetTable: \"daily\" | \"context\" | \"intraday\"\n): { valid: boolean; missingFields: string[] } {\n const schemaValues = Object.values(columnMapping);\n const required = REQUIRED_SCHEMA_FIELDS[targetTable] ?? [];\n let missing = required.filter((field) => !schemaValues.includes(field));\n\n // For intraday: allow missing `time` if `date` is mapped — time will be auto-derived\n // from the Unix timestamp in the date source column during applyColumnMapping.\n if (targetTable === \"intraday\" && missing.includes(\"time\") && schemaValues.includes(\"date\")) {\n missing = missing.filter((f) => f !== \"time\");\n }\n\n return { valid: missing.length === 0, missingFields: missing };\n}\n\n// =============================================================================\n// Private CSV Parsing Helpers\n// =============================================================================\n\n/**\n * Parse CSV content into rows with header mapping.\n * Strips UTF-8 BOM if present. Handles \\r\\n line endings.\n */\nfunction parseCSV(content: string): { headers: string[]; rows: Record<string, string>[] } {\n const lines = content.replace(/^\\uFEFF/, \"\").trim().split(\"\\n\");\n if (lines.length < 2) {\n return { headers: [], rows: [] };\n }\n\n const headers = lines[0].trim().split(\",\").map((h) => h.trim());\n const rows: Record<string, string>[] = [];\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i].trim();\n if (!line) continue;\n const values = line.split(\",\");\n const row: Record<string, string> = {};\n headers.forEach((h, idx) => {\n row[h] = values[idx]?.trim() ?? \"\";\n });\n rows.push(row);\n }\n\n return { headers, rows };\n}\n\n/**\n * Parse a date value flexibly:\n * - If numeric AND > 1e8: treat as Unix timestamp (seconds), convert to ET date\n * - If YYYY-MM-DD string: return as-is\n * - Otherwise: return null\n */\nfunction parseFlexibleDate(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n // Unix timestamp in seconds — convert to Eastern Time date\n return new Date(numeric * 1000).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n });\n }\n\n // Check YYYY-MM-DD format\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n return value;\n }\n\n return null;\n}\n\n/**\n * Extract HH:MM time from a value in Eastern Time.\n * - If numeric AND > 1e8: treat as Unix timestamp (seconds), extract HH:MM ET\n * - If already HH:MM: return as-is\n * - If HHMM (4 digits): convert to HH:MM\n * - Otherwise: return null\n */\nfunction parseFlexibleTime(value: string): string | null {\n const numeric = Number(value);\n if (!isNaN(numeric) && numeric > 1e8) {\n // Unix timestamp in seconds — convert to Eastern Time HH:MM\n const d = new Date(numeric * 1000);\n return d.toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n }\n // Already in HH:MM format\n if (/^\\d{2}:\\d{2}$/.test(value)) {\n return value;\n }\n // HHMM format (4 digits), e.g. \"0930\"\n if (/^\\d{4}$/.test(value)) {\n return `${value.slice(0, 2)}:${value.slice(2)}`;\n }\n return null;\n}\n\n// =============================================================================\n// Private Column Mapping Helper\n// =============================================================================\n\n/**\n * Apply column mapping to raw rows, performing date parsing and numeric coercion.\n *\n * @param rows - Raw rows from CSV or query result\n * @param columnMapping - { sourceCol: schemaCol } mapping\n * @param ticker - Ticker to inject for daily/intraday tables\n * @param targetTable - Target table (controls whether ticker is injected)\n * @returns Mapped rows with schema column names, invalid date rows dropped\n */\nfunction applyColumnMapping(\n rows: Record<string, string>[],\n columnMapping: Record<string, string>,\n ticker: string,\n targetTable: \"daily\" | \"context\" | \"intraday\"\n): Array<Record<string, unknown>> {\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const result: Array<Record<string, unknown>> = [];\n\n for (const row of rows) {\n const mapped: Record<string, unknown> = {};\n let hasNullDate = false;\n\n for (const [sourceCol, schemaCol] of Object.entries(columnMapping)) {\n const rawValue = row[sourceCol] ?? \"\";\n\n if (schemaCol === \"date\") {\n const parsedDate = parseFlexibleDate(rawValue);\n if (parsedDate === null) {\n // Log a warning and skip this row\n console.warn(`[market-importer] Skipping row with unparseable date value: \"${rawValue}\"`);\n hasNullDate = true;\n break;\n }\n mapped[schemaCol] = parsedDate;\n } else if (schemaCol === \"time\") {\n const parsedTime = parseFlexibleTime(rawValue);\n if (parsedTime === null) {\n console.warn(`[market-importer] Skipping row with unparseable time value: \"${rawValue}\"`);\n hasNullDate = true; // Reuse the skip mechanism\n break;\n }\n mapped[schemaCol] = parsedTime;\n } else {\n // Numeric coercion for non-date, non-ticker fields\n if (rawValue === \"\" || rawValue === \"NaN\" || rawValue === \"NA\") {\n mapped[schemaCol] = null;\n } else {\n const numVal = parseFloat(rawValue);\n mapped[schemaCol] = isNaN(numVal) ? rawValue : numVal;\n }\n }\n }\n\n if (hasNullDate) continue;\n if (!(\"date\" in mapped)) continue;\n\n // Auto-extract time from the date source column's Unix timestamp for intraday imports.\n // This allows users to map a single Unix timestamp column to \"date\" and get \"time\"\n // (HH:MM ET) auto-populated — no need to specify a separate time column in the mapping.\n if (targetTable === \"intraday\" && !(\"time\" in mapped)) {\n const dateSourceCol = Object.entries(columnMapping).find(([, schema]) => schema === \"date\")?.[0];\n if (dateSourceCol) {\n const rawDateValue = row[dateSourceCol] ?? \"\";\n const numericDate = Number(rawDateValue);\n if (!isNaN(numericDate) && numericDate > 1e8) {\n const parsedTime = parseFlexibleTime(rawDateValue);\n if (parsedTime) {\n mapped[\"time\"] = parsedTime;\n }\n }\n }\n }\n\n // Inject ticker for daily and intraday tables (context PK is date-only)\n if (targetTable === \"daily\" || targetTable === \"intraday\") {\n mapped[\"ticker\"] = normalizedTicker;\n }\n\n result.push(mapped);\n }\n\n return result;\n}\n\n// =============================================================================\n// Private Insert Helper\n// =============================================================================\n\n/**\n * Insert mapped rows into the target market table using ON CONFLICT DO NOTHING.\n * Batches inserts in groups of 500.\n *\n * @returns Count of inserted and skipped rows\n */\nasync function insertMappedRows(\n conn: DuckDBConnection,\n targetTable: \"daily\" | \"context\" | \"intraday\",\n mappedRows: Array<Record<string, unknown>>\n): Promise<{ inserted: number; updated: number; skipped: number }> {\n if (mappedRows.length === 0) {\n return { inserted: 0, updated: 0, skipped: 0 };\n }\n\n const tableName = `market.${targetTable}`;\n const conflictTarget = CONFLICT_TARGETS[targetTable];\n\n // Collect all distinct schema column names across all rows\n const columnSet = new Set<string>();\n for (const row of mappedRows) {\n for (const key of Object.keys(row)) {\n columnSet.add(key);\n }\n }\n const columns = Array.from(columnSet);\n\n // COUNT(*) before insert\n const beforeResult = await conn.runAndReadAll(`SELECT COUNT(*) FROM ${tableName}`);\n const beforeCount = Number(beforeResult.getRows()[0][0]);\n\n // Build ON CONFLICT clause: merge non-key columns into existing rows\n // (e.g., importing VIX9D_Close into a context row that already has VIX_Close)\n const conflictKeys = new Set(\n CONFLICT_TARGETS[targetTable].replace(/[()]/g, \"\").split(\",\").map((s: string) => s.trim())\n );\n const updateCols = columns.filter((c) => !conflictKeys.has(c));\n const conflictAction =\n updateCols.length > 0\n ? `DO UPDATE SET ${updateCols.map((c) => `${c} = EXCLUDED.${c}`).join(\", \")}`\n : \"DO NOTHING\";\n\n const columnList = columns.join(\", \");\n const BATCH_SIZE = 500;\n for (let i = 0; i < mappedRows.length; i += BATCH_SIZE) {\n const batch = mappedRows.slice(i, i + BATCH_SIZE);\n const values: unknown[] = [];\n const valuePlaceholders: string[] = [];\n\n for (const row of batch) {\n const rowPlaceholders: string[] = [];\n for (const col of columns) {\n values.push(row[col] ?? null);\n rowPlaceholders.push(`$${values.length}`);\n }\n valuePlaceholders.push(`(${rowPlaceholders.join(\", \")})`);\n }\n\n const sql =\n `INSERT INTO ${tableName} (${columnList}) VALUES ${valuePlaceholders.join(\", \")} ` +\n `ON CONFLICT ${conflictTarget} ${conflictAction}`;\n\n await conn.run(sql, values as (string | number | boolean | null | bigint)[]);\n }\n\n // COUNT(*) after insert — net new rows\n const afterResult = await conn.runAndReadAll(`SELECT COUNT(*) FROM ${tableName}`);\n const afterCount = Number(afterResult.getRows()[0][0]);\n\n const inserted = afterCount - beforeCount;\n // With DO UPDATE, non-inserted rows are updated (not skipped).\n // With DO NOTHING (updateCols empty), non-inserted rows are skipped.\n const updated = updateCols.length > 0 ? mappedRows.length - inserted : 0;\n const skipped = updateCols.length > 0 ? 0 : mappedRows.length - inserted;\n return { inserted, updated, skipped };\n}\n\n// =============================================================================\n// Private Date Range Helper\n// =============================================================================\n\n/**\n * Compute min/max date range from mapped rows.\n */\nfunction computeDateRange(\n rows: Array<Record<string, unknown>>\n): { min: string; max: string } | null {\n const dates: string[] = [];\n for (const row of rows) {\n const d = row[\"date\"];\n if (typeof d === \"string\" && d) {\n dates.push(d);\n }\n }\n if (dates.length === 0) return null;\n dates.sort();\n return { min: dates[0], max: dates[dates.length - 1] };\n}\n\n// =============================================================================\n// Enrichment Trigger\n// =============================================================================\n\n/**\n * Trigger market data enrichment after an import.\n *\n * Calls runEnrichment() for daily table imports. Context and intraday imports\n * are source data, not enrichment targets, so they are skipped here.\n *\n * Returns \"complete\", \"skipped\", or \"error\" — never \"pending\".\n */\nexport async function triggerEnrichment(\n conn: DuckDBConnection,\n ticker: string,\n targetTable: string,\n _dateRange: { min: string; max: string } | null,\n skipEnrichment: boolean\n): Promise<{ status: \"pending\" | \"complete\" | \"skipped\" | \"error\"; message: string }> {\n // Only enrich daily table imports — context and intraday are source data, not enrichment targets\n if (skipEnrichment) {\n return {\n status: \"skipped\",\n message: \"skip_enrichment=true; call enrich_market_data to populate computed fields.\",\n };\n }\n if (targetTable !== \"daily\") {\n return {\n status: \"skipped\",\n message: `Enrichment only runs for daily table imports; skipping for ${targetTable}.`,\n };\n }\n\n try {\n const result = await runEnrichment(conn, ticker, {});\n const summaryParts = [\n `Tier 1: ${result.tier1.status}${result.tier1.fieldsWritten !== undefined ? ` (${result.tier1.fieldsWritten} fields)` : \"\"}${result.tier1.reason ? ` — ${result.tier1.reason}` : \"\"}`,\n `Tier 2: ${result.tier2.status}${result.tier2.fieldsWritten !== undefined ? ` (${result.tier2.fieldsWritten} fields)` : \"\"}${result.tier2.reason ? ` — ${result.tier2.reason}` : \"\"}`,\n `Tier 3: ${result.tier3.status}${result.tier3.reason ? ` — ${result.tier3.reason}` : \"\"}`,\n ];\n return {\n status: \"complete\",\n message: `Enriched ${result.rowsEnriched} rows for ${ticker} through ${result.enrichedThrough ?? \"N/A\"}. ${summaryParts.join(\"; \")}`,\n };\n } catch (err) {\n return {\n status: \"error\",\n message: `Enrichment failed for ${ticker}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n}\n\n// =============================================================================\n// importMarketCsvFile\n// =============================================================================\n\n/**\n * Import market data from a CSV file into the target market table.\n *\n * Steps:\n * 1. Validate column mapping (fail-clean before any writes)\n * 2. Read CSV file\n * 3. Parse and map columns\n * 4. Insert with ON CONFLICT DO NOTHING (or return preview if dry_run=true)\n * 5. Upsert import metadata\n * 6. Trigger enrichment stub\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param options - Import options including file path, ticker, and column mapping\n * @returns Import result with row counts, date range, and enrichment status\n */\nexport async function importMarketCsvFile(\n conn: DuckDBConnection,\n options: ImportMarketCsvOptions\n): Promise<ImportResult> {\n const { filePath, ticker, targetTable, columnMapping, dryRun = false, skipEnrichment = false } =\n options;\n\n // 1. Validate column mapping first\n const validation = validateColumnMapping(columnMapping, targetTable);\n if (!validation.valid) {\n throw new Error(\n `Column mapping missing required fields for market.${targetTable}: ${validation.missingFields.join(\", \")}`\n );\n }\n\n // 2. Read file\n let content: string;\n try {\n content = await fs.readFile(filePath, \"utf-8\");\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to read CSV file at \"${filePath}\": ${msg}`);\n }\n\n // 3. Parse CSV\n const { rows } = parseCSV(content);\n if (rows.length === 0) {\n throw new Error(`CSV file \"${filePath}\" has no data rows`);\n }\n\n // 4. Map columns\n const mappedRows = applyColumnMapping(rows, columnMapping, ticker, targetTable);\n if (mappedRows.length === 0) {\n throw new Error(\n `After applying column mapping, 0 valid rows remain from CSV file \"${filePath}\"`\n );\n }\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const dateRange = computeDateRange(mappedRows);\n\n // 5. Dry run: return preview without writing\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n // 6. Insert rows\n const { inserted, updated, skipped } = await insertMappedRows(conn, targetTable, mappedRows);\n\n // 7. Upsert import metadata\n await upsertMarketImportMetadata(conn, {\n source: `import_market_csv:${filePath}`,\n ticker: normalizedTicker,\n target_table: targetTable,\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n // 8. Trigger enrichment\n const enrichment = await triggerEnrichment(conn, normalizedTicker, targetTable, dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n}\n\n// =============================================================================\n// importFromDatabase\n// =============================================================================\n\n/**\n * Import market data from an external DuckDB database into the target market table.\n *\n * ATTACHes the external database as READ_ONLY, queries it, maps columns, and inserts\n * into market tables. DETACHes the external DB in a finally block regardless of outcome.\n *\n * @param conn - Active DuckDB connection with market catalog attached\n * @param options - Import options including DB path, query, ticker, and column mapping\n * @returns Import result with row counts, date range, and enrichment status\n */\nexport async function importFromDatabase(\n conn: DuckDBConnection,\n options: ImportFromDatabaseOptions\n): Promise<ImportResult> {\n const { dbPath, query, ticker, targetTable, columnMapping, dryRun = false, skipEnrichment = false } =\n options;\n\n // 1. Validate column mapping first\n const validation = validateColumnMapping(columnMapping, targetTable);\n if (!validation.valid) {\n throw new Error(\n `Column mapping missing required fields for market.${targetTable}: ${validation.missingFields.join(\", \")}`\n );\n }\n\n const EXT_ALIAS = \"ext_import_source\";\n\n // 2. Attach external DB as READ_ONLY\n await conn.run(`ATTACH '${dbPath}' AS ${EXT_ALIAS} (READ_ONLY)`);\n\n try {\n // 3. Execute query against external DB\n const rawResult = await conn.runAndReadAll(query);\n\n // 4. Convert result to Record<string, string>[]\n // Use columnNames() for column name strings — getColumns() returns column data arrays,\n // not column descriptor objects with .name properties.\n const resultColumnNames = rawResult.columnNames();\n const resultRows = rawResult.getRows();\n\n const rows: Record<string, string>[] = resultRows.map((row) => {\n const obj: Record<string, string> = {};\n resultColumnNames.forEach((colName, idx) => {\n const val = row[idx];\n obj[colName] = val === null || val === undefined ? \"\" : String(val);\n });\n return obj;\n });\n\n // 5. Map columns\n const mappedRows = applyColumnMapping(rows, columnMapping, ticker, targetTable);\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n const dateRange = computeDateRange(mappedRows);\n\n // 6. Dry run: return preview without writing\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n // 7. Insert rows\n const { inserted, updated, skipped } = await insertMappedRows(conn, targetTable, mappedRows);\n\n // 8. Upsert import metadata\n await upsertMarketImportMetadata(conn, {\n source: `import_from_database:${dbPath}`,\n ticker: normalizedTicker,\n target_table: targetTable,\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n // 9. Trigger enrichment\n const enrichment = await triggerEnrichment(conn, normalizedTicker, targetTable, dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n } finally {\n // Always detach external DB regardless of success or failure\n try {\n await conn.run(`DETACH ${EXT_ALIAS}`);\n } catch {\n // Non-fatal: detach failure should not mask the original error\n }\n }\n}\n\n// =============================================================================\n// importFromApi\n// =============================================================================\n\n/**\n * Known index tickers that need the I: prefix when calling the Massive API.\n * Used for auto-detection of asset class when not provided.\n */\nconst INDEX_TICKERS = new Set([\n \"VIX\", \"VIX9D\", \"VIX3M\", \"SPX\", \"NDX\", \"RUT\", \"DJX\", \"VXN\", \"OVX\", \"GVZ\",\n]);\n\n/**\n * Regex for OCC option ticker format: letters + 6-digit date + C/P + 8-digit strike.\n * E.g., \"SPX251219C05000000\" or \"AAPL240119P00150000\"\n */\nconst OCC_TICKER_REGEX = /^[A-Z]+\\d{6}[CP]\\d{8}$/;\n\n/**\n * Auto-detect Massive asset class from ticker symbol.\n * - Known indices (VIX, SPX, NDX, etc.) → \"index\"\n * - OCC option format → \"option\"\n * - Otherwise → \"stock\"\n */\nfunction detectAssetClass(ticker: string): AssetClass {\n if (INDEX_TICKERS.has(ticker.toUpperCase())) return \"index\";\n if (OCC_TICKER_REGEX.test(ticker.toUpperCase())) return \"option\";\n return \"stock\";\n}\n\nexport interface ImportFromApiOptions {\n ticker: string;\n from: string; // YYYY-MM-DD\n to: string; // YYYY-MM-DD\n targetTable: \"daily\" | \"context\" | \"intraday\";\n timespan?: \"minute\" | \"hour\"; // only for intraday, default \"minute\"\n multiplier?: number; // only for intraday, default 1\n assetClass?: AssetClass; // default: auto-detect from ticker/targetTable\n dryRun?: boolean;\n skipEnrichment?: boolean;\n}\n\n/**\n * Import market data from the Massive.com REST API into DuckDB.\n *\n * Supports three import modes via `targetTable`:\n *\n * **daily** — Fetches OHLCV bars for any stock/index/option ticker and upserts\n * into market.daily. Auto-triggers enrichment (Tier 1+2+3) after insert unless\n * skipEnrichment=true.\n *\n * **context** — Ignores the `ticker` parameter. Makes three parallel fetchBars calls\n * for VIX, VIX9D, and VIX3M, merges results by date, and upserts into market.context.\n * Trigger Tier 2 context enrichment after insert unless skipEnrichment=true.\n *\n * **intraday** — Fetches minute or hour bars. Requires time field from Massive API\n * (populated by fetchBars when timespan != \"day\"). Strips volume before insert since\n * market.intraday schema does not include a volume column.\n *\n * Requires MASSIVE_API_KEY environment variable. Upserts on conflict — safe to\n * re-import overlapping date ranges.\n */\nexport async function importFromApi(\n conn: DuckDBConnection,\n options: ImportFromApiOptions\n): Promise<ImportResult> {\n const {\n ticker,\n from,\n to,\n targetTable,\n timespan = \"minute\",\n multiplier = 1,\n assetClass,\n dryRun = false,\n skipEnrichment = false,\n } = options;\n\n const normalizedTicker = normalizeTicker(ticker) ?? ticker.toUpperCase();\n\n if (targetTable === \"daily\") {\n // --- Daily import: single fetchBars call, insert directly ---\n const resolvedClass = assetClass ?? detectAssetClass(normalizedTicker);\n const rows = await getProvider().fetchBars({\n ticker: normalizedTicker,\n from,\n to,\n timespan: \"day\",\n multiplier: 1,\n assetClass: resolvedClass,\n });\n\n // MassiveBarRow fields mapped to market.daily schema columns.\n // Volume is intentionally omitted — market.daily schema has no volume column.\n const mappedRows: Array<Record<string, unknown>> = rows.map((row) => ({\n date: row.date,\n open: row.open,\n high: row.high,\n low: row.low,\n close: row.close,\n ticker: row.ticker,\n }));\n\n const dateRange = computeDateRange(mappedRows);\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} rows.`,\n },\n };\n }\n\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"daily\", mappedRows);\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:daily:${normalizedTicker}`,\n ticker: normalizedTicker,\n target_table: \"daily\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n const enrichment = await triggerEnrichment(conn, normalizedTicker, \"daily\", dateRange, skipEnrichment);\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment,\n };\n }\n\n if (targetTable === \"context\") {\n // --- Context convenience import: import VIX + VIX9D + VIX3M into market.daily (per D-12) ---\n const contextTickers = [\"VIX\", \"VIX9D\", \"VIX3M\"];\n let totalInserted = 0;\n let totalUpdated = 0;\n let totalSkipped = 0;\n let totalInput = 0;\n let combinedDateRange: { min: string; max: string } | null = null;\n\n for (const ctxTicker of contextTickers) {\n const bars = await getProvider().fetchBars({ ticker: ctxTicker, from, to, timespan: \"day\", assetClass: \"index\" });\n const mappedRows = bars.map(bar => ({\n ticker: ctxTicker,\n date: bar.date,\n open: bar.open,\n high: bar.high,\n low: bar.low,\n close: bar.close,\n }));\n\n totalInput += mappedRows.length;\n\n if (mappedRows.length === 0) continue;\n\n const dateRange = computeDateRange(mappedRows);\n if (dateRange) {\n if (!combinedDateRange) {\n combinedDateRange = { ...dateRange };\n } else {\n if (dateRange.min < combinedDateRange.min) combinedDateRange.min = dateRange.min;\n if (dateRange.max > combinedDateRange.max) combinedDateRange.max = dateRange.max;\n }\n }\n\n if (!dryRun) {\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"daily\", mappedRows);\n totalInserted += inserted;\n totalUpdated += updated;\n totalSkipped += skipped;\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:daily:${ctxTicker}`,\n ticker: ctxTicker,\n target_table: \"daily\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n }\n }\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: totalInput,\n dateRange: combinedDateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${totalInput} rows for ${contextTickers.join(\", \")} into market.daily.`,\n },\n };\n }\n\n // Trigger enrichment (Tier 2) to compute IVR/IVP + derived fields\n let enrichment: ImportResult[\"enrichment\"];\n if (skipEnrichment) {\n enrichment = {\n status: \"skipped\",\n message: \"skip_enrichment=true; call enrich_market_data to populate computed fields.\",\n };\n } else {\n try {\n const tier2Result = await runContextEnrichment(conn);\n enrichment = {\n status: tier2Result.status === \"complete\" || tier2Result.status === \"skipped\"\n ? \"complete\" as const\n : \"error\" as const,\n message: tier2Result.reason ?? `Tier 2 enrichment: ${tier2Result.fieldsWritten ?? 0} fields`,\n };\n } catch (e) {\n enrichment = {\n status: \"error\" as const,\n message: `Tier 2 enrichment failed: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n }\n\n return {\n rowsInserted: totalInserted,\n rowsUpdated: totalUpdated,\n rowsSkipped: totalSkipped,\n inputRowCount: totalInput,\n dateRange: combinedDateRange,\n enrichment,\n };\n }\n\n // targetTable === \"intraday\"\n // --- Intraday import: fetch minute/hour bars with time field ---\n const resolvedClass = assetClass ?? detectAssetClass(normalizedTicker);\n const rows = await getProvider().fetchBars({\n ticker: normalizedTicker,\n from,\n to,\n timespan: timespan,\n multiplier: multiplier,\n assetClass: resolvedClass,\n });\n\n // Strip volume — market.intraday schema does not include volume column.\n // Also strip time=undefined rows (safety guard — all intraday bars should have time).\n const mappedRows: Array<Record<string, unknown>> = rows\n .filter((row) => row.time !== undefined)\n .map((row) => ({\n ticker: row.ticker,\n date: row.date,\n time: row.time as string,\n open: row.open,\n high: row.high,\n low: row.low,\n close: row.close,\n // volume intentionally omitted — not in intraday schema\n }));\n\n const dateRange = computeDateRange(mappedRows);\n\n if (dryRun) {\n return {\n rowsInserted: 0,\n rowsUpdated: 0,\n rowsSkipped: 0,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: `dry_run=true; no data written. Would import ${mappedRows.length} intraday rows.`,\n },\n };\n }\n\n const { inserted, updated, skipped } = await insertMappedRows(conn, \"intraday\", mappedRows);\n\n await upsertMarketImportMetadata(conn, {\n source: `import_from_api:intraday:${normalizedTicker}`,\n ticker: normalizedTicker,\n target_table: \"intraday\",\n max_date: dateRange?.max ?? null,\n enriched_through: null,\n synced_at: new Date(),\n });\n\n return {\n rowsInserted: inserted,\n rowsUpdated: updated,\n rowsSkipped: skipped,\n inputRowCount: mappedRows.length,\n dateRange,\n enrichment: {\n status: \"skipped\",\n message: \"Enrichment only runs for daily and context table imports; skipping for intraday.\",\n },\n };\n}\n","/**\n * Massive.com (Polygon.io) Market Data Provider\n *\n * Implements MarketDataProvider for the Massive.com REST API.\n * Combines the former massive-client.ts (OHLCV bars) and massive-snapshot.ts\n * (option chain snapshots) into a single provider adapter.\n *\n * Key design decisions:\n * - D-01/D-02/D-03: API key read at call site via process.env.MASSIVE_API_KEY\n * - D-05: Pagination loop guard with seen-cursor Set + MAX_PAGES=500 safety net\n * - D-06: adjusted=false and limit=50000 in all aggregate API calls\n * - D-07: 429 retry with Retry-After header or exponential backoff\n * - D-09/D-10/D-11: Ticker prefixes (I: for indices, O: for options)\n * - Timestamps are Unix milliseconds from the Massive aggregates API\n */\n\nimport { z } from \"zod\";\nimport type {\n MarketDataProvider,\n BarRow,\n FetchBarsOptions,\n FetchSnapshotOptions,\n FetchSnapshotResult,\n OptionContract,\n AssetClass,\n} from \"../market-provider.js\";\nimport { computeLegGreeks } from \"../black-scholes.js\";\n\n// ===========================================================================\n// Zod Schemas — Aggregates (OHLCV Bars)\n// ===========================================================================\n\nexport const MassiveBarSchema = z.object({\n v: z.number().optional(),\n vw: z.number().optional(),\n o: z.number(),\n c: z.number(),\n h: z.number(),\n l: z.number(),\n t: z.number(),\n n: z.number().optional(),\n});\n\nexport type MassiveBar = z.infer<typeof MassiveBarSchema>;\n\nexport const MassiveAggregateResponseSchema = z.object({\n ticker: z.string(),\n queryCount: z.number(),\n resultsCount: z.number().optional(),\n adjusted: z.boolean().optional(),\n results: z.array(MassiveBarSchema).default([]),\n status: z.string(),\n request_id: z.string(),\n next_url: z.string().optional(),\n});\n\nexport type MassiveAggregateResponse = z.infer<typeof MassiveAggregateResponseSchema>;\n\n// ===========================================================================\n// Zod Schemas — Quotes (Historical Bid/Ask)\n// ===========================================================================\n\nexport const MassiveQuoteSchema = z.object({\n bid_price: z.number(),\n ask_price: z.number(),\n sip_timestamp: z.number(), // nanoseconds\n bid_size: z.number(),\n ask_size: z.number(),\n sequence_number: z.number(),\n});\n\nexport type MassiveQuote = z.infer<typeof MassiveQuoteSchema>;\n\nexport const MassiveQuotesResponseSchema = z.object({\n status: z.string(),\n request_id: z.string(),\n results: z.array(MassiveQuoteSchema).default([]),\n next_url: z.string().optional(),\n});\n\nexport type MassiveQuotesResponse = z.infer<typeof MassiveQuotesResponseSchema>;\n\n// ===========================================================================\n// Zod Schemas — Snapshot (Option Chain)\n// ===========================================================================\n\nexport const MassiveSnapshotGreeksSchema = z.object({\n delta: z.number(),\n gamma: z.number(),\n theta: z.number(),\n vega: z.number(),\n});\n\nexport const MassiveSnapshotDaySchema = z.object({\n open: z.number(),\n high: z.number(),\n low: z.number(),\n close: z.number(),\n change: z.number(),\n change_percent: z.number(),\n volume: z.number().optional(),\n vwap: z.number().optional(),\n previous_close: z.number(),\n last_updated: z.number(),\n});\n\nexport const MassiveSnapshotQuoteSchema = z.object({\n bid: z.number(),\n ask: z.number(),\n midpoint: z.number(),\n bid_size: z.number(),\n ask_size: z.number(),\n last_updated: z.number(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotTradeSchema = z.object({\n price: z.number(),\n size: z.number(),\n sip_timestamp: z.number(),\n conditions: z.array(z.number()).optional(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotDetailsSchema = z.object({\n ticker: z.string(),\n contract_type: z.string(),\n strike_price: z.number(),\n expiration_date: z.string(),\n exercise_style: z.string(),\n shares_per_contract: z.number(),\n});\n\nexport const MassiveSnapshotUnderlyingSchema = z.object({\n ticker: z.string(),\n price: z.number(),\n change_to_break_even: z.number(),\n last_updated: z.number(),\n timeframe: z.string(),\n});\n\nexport const MassiveSnapshotContractSchema = z.object({\n break_even_price: z.number(),\n implied_volatility: z.number(),\n open_interest: z.number(),\n greeks: MassiveSnapshotGreeksSchema.optional(),\n day: MassiveSnapshotDaySchema,\n last_quote: MassiveSnapshotQuoteSchema,\n last_trade: MassiveSnapshotTradeSchema.optional(),\n details: MassiveSnapshotDetailsSchema,\n underlying_asset: MassiveSnapshotUnderlyingSchema,\n});\n\nexport const MassiveSnapshotResponseSchema = z.object({\n request_id: z.string(),\n status: z.string(),\n results: z.array(MassiveSnapshotContractSchema),\n next_url: z.string().optional(),\n});\n\n// ===========================================================================\n// Constants\n// ===========================================================================\n\nexport const MASSIVE_BASE_URL = \"https://api.massive.com\";\nexport const MASSIVE_MAX_LIMIT = 50000;\nexport const MASSIVE_MAX_PAGES = 500;\n\n// ===========================================================================\n// Ticker Normalization\n// ===========================================================================\n\nexport function toMassiveTicker(ticker: string, assetClass: AssetClass): string {\n if (assetClass === \"index\") return ticker.startsWith(\"I:\") ? ticker : `I:${ticker}`;\n if (assetClass === \"option\") return ticker.startsWith(\"O:\") ? ticker : `O:${ticker}`;\n return ticker;\n}\n\nexport function fromMassiveTicker(apiTicker: string): string {\n return apiTicker.replace(/^[IO]:/, \"\");\n}\n\n// ===========================================================================\n// Timestamp Conversion\n// ===========================================================================\n\nexport function massiveTimestampToETDate(unixMs: number): string {\n return new Date(unixMs).toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n });\n}\n\nexport function massiveTimestampToETTime(unixMs: number): string {\n return new Date(unixMs).toLocaleTimeString(\"en-US\", {\n timeZone: \"America/New_York\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n });\n}\n\n/**\n * Converts a nanosecond sip_timestamp to \"YYYY-MM-DD HH:MM\" ET minute key.\n * Used for matching quotes to intraday bars by minute bucket.\n */\nexport function nanosToETMinuteKey(nanosTimestamp: number): string {\n const ms = Math.floor(nanosTimestamp / 1_000_000);\n const date = massiveTimestampToETDate(ms);\n const time = massiveTimestampToETTime(ms);\n return `${date} ${time}`;\n}\n\n// ===========================================================================\n// Internal Helpers\n// ===========================================================================\n\nfunction getApiKey(): string {\n const key = process.env.MASSIVE_API_KEY;\n if (!key) {\n throw new Error(\n \"Set MASSIVE_API_KEY environment variable to use Massive.com data import\"\n );\n }\n return key;\n}\n\nasync function fetchWithRetry(\n url: string,\n headers: Record<string, string>,\n maxRetries = 2\n): Promise<Response> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const response = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(30_000),\n });\n\n if (response.status === 429) {\n if (attempt === maxRetries) {\n throw new Error(\n \"Massive.com rate limit exceeded — try again in a few minutes\"\n );\n }\n const retryAfter = response.headers.get(\"Retry-After\");\n const backoffMs = retryAfter\n ? parseInt(retryAfter, 10) * 1000\n : Math.pow(2, attempt + 1) * 1000;\n await new Promise((resolve) => setTimeout(resolve, backoffMs));\n continue;\n }\n\n return response;\n }\n throw new Error(\"Massive.com rate limit exceeded after retries\");\n}\n\n// ===========================================================================\n// Snapshot Helpers\n// ===========================================================================\n\nconst INDEX_TICKERS = new Set([\n \"SPX\", \"NDX\", \"RUT\", \"DJX\", \"VIX\", \"VIX9D\", \"VIX3M\", \"OEX\", \"XSP\",\n]);\n\nfunction detectSnapshotAssetClass(ticker: string): AssetClass {\n return INDEX_TICKERS.has(ticker.toUpperCase()) ? \"index\" : \"stock\";\n}\n\nfunction computeDTE(expirationDate: string): number {\n const expMatch = expirationDate.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!expMatch) return 0;\n const [, expYearS, expMonthS, expDayS] = expMatch;\n const expYear = parseInt(expYearS, 10);\n const expMonth = parseInt(expMonthS, 10);\n const expDay = parseInt(expDayS, 10);\n\n const todayET = new Date().toLocaleDateString(\"en-CA\", {\n timeZone: \"America/New_York\",\n });\n const todayMatch = todayET.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!todayMatch) return 0;\n const [, todayYearS, todayMonthS, todayDayS] = todayMatch;\n const todayYear = parseInt(todayYearS, 10);\n const todayMonth = parseInt(todayMonthS, 10);\n const todayDay = parseInt(todayDayS, 10);\n\n const dte =\n (Date.UTC(expYear, expMonth - 1, expDay) -\n Date.UTC(todayYear, todayMonth - 1, todayDay)) /\n 86_400_000;\n\n return dte <= 0 ? 0.001 : dte;\n}\n\nfunction mapContract(\n contract: z.infer<typeof MassiveSnapshotContractSchema>,\n): OptionContract {\n const hasApiGreeks =\n contract.greeks != null && contract.greeks.delta != null;\n\n let delta: number | null = null;\n let gamma: number | null = null;\n let theta: number | null = null;\n let vega: number | null = null;\n let iv: number | null = null;\n let greeksSource: \"massive\" | \"computed\" = \"computed\";\n\n if (hasApiGreeks) {\n delta = contract.greeks!.delta;\n gamma = contract.greeks!.gamma;\n theta = contract.greeks!.theta;\n vega = contract.greeks!.vega;\n iv = contract.implied_volatility;\n greeksSource = \"massive\";\n } else {\n const optionPrice =\n contract.last_trade?.price ?? contract.last_quote.midpoint;\n const underlyingPrice = contract.underlying_asset.price;\n const strike = contract.details.strike_price;\n const dte = computeDTE(contract.details.expiration_date);\n const type = contract.details.contract_type === \"call\" ? \"C\" : \"P\";\n const riskFreeRate = 0.045;\n const dividendYield = 0.015;\n\n const result = computeLegGreeks(\n optionPrice,\n underlyingPrice,\n strike,\n dte,\n type as \"C\" | \"P\",\n riskFreeRate,\n dividendYield,\n );\n\n if (result.iv !== null) {\n delta = result.delta;\n gamma = result.gamma;\n theta = result.theta;\n vega = result.vega;\n iv = result.iv;\n }\n greeksSource = \"computed\";\n }\n\n return {\n ticker: fromMassiveTicker(contract.details.ticker),\n underlying_ticker: fromMassiveTicker(contract.underlying_asset.ticker),\n underlying_price: contract.underlying_asset.price,\n contract_type: contract.details.contract_type as \"call\" | \"put\",\n strike: contract.details.strike_price,\n expiration: contract.details.expiration_date,\n exercise_style: contract.details.exercise_style,\n delta,\n gamma,\n theta,\n vega,\n iv,\n greeks_source: greeksSource,\n bid: contract.last_quote.bid,\n ask: contract.last_quote.ask,\n midpoint: contract.last_quote.midpoint,\n last_price: contract.last_trade?.price ?? null,\n open_interest: contract.open_interest,\n volume: contract.day.volume ?? 0,\n break_even: contract.break_even_price,\n };\n}\n\n// ===========================================================================\n// MassiveProvider\n// ===========================================================================\n\nexport class MassiveProvider implements MarketDataProvider {\n readonly name = \"massive\";\n\n async fetchBars(options: FetchBarsOptions): Promise<BarRow[]> {\n const apiKey = getApiKey();\n const {\n ticker,\n from,\n to,\n timespan = \"day\",\n multiplier = 1,\n assetClass = \"stock\",\n } = options;\n\n const apiTicker = toMassiveTicker(ticker, assetClass);\n const storageTicker = fromMassiveTicker(apiTicker);\n const headers = { Authorization: `Bearer ${apiKey}` };\n\n let url: string | null =\n `${MASSIVE_BASE_URL}/v2/aggs/ticker/${encodeURIComponent(apiTicker)}/range/${multiplier}/${timespan}/${from}/${to}?adjusted=false&limit=${MASSIVE_MAX_LIMIT}`;\n\n const allRows: BarRow[] = [];\n const seenCursors = new Set<string>();\n let pageCount = 0;\n\n while (url) {\n pageCount++;\n if (pageCount > MASSIVE_MAX_PAGES) {\n throw new Error(\n `Pagination safety limit reached (${MASSIVE_MAX_PAGES} pages) — possible API issue`\n );\n }\n\n const response = await fetchWithRetry(url, headers);\n\n if (response.status === 401) {\n throw new Error(\n \"MASSIVE_API_KEY rejected by Massive.com — check your key\"\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Massive.com API error: HTTP ${response.status} ${response.statusText}`\n );\n }\n\n const json = await response.json();\n\n const parsed = MassiveAggregateResponseSchema.safeParse(json);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Massive API response validation failed: ${issues}`);\n }\n\n const data = parsed.data;\n\n for (const bar of data.results) {\n const row: BarRow = {\n date: massiveTimestampToETDate(bar.t),\n open: bar.o,\n high: bar.h,\n low: bar.l,\n close: bar.c,\n volume: bar.v ?? 0,\n ticker: storageTicker,\n };\n if (timespan !== \"day\") {\n row.time = massiveTimestampToETTime(bar.t);\n }\n allRows.push(row);\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n throw new Error(\n `Pagination loop detected — cursor repeated: ${cursor.slice(0, 50)}...`\n );\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n\n // Enrich option intraday bars with bid/ask from quotes endpoint (best-effort)\n // Gated by MASSIVE_QUOTES_ENABLED env var — quotes endpoint requires higher Massive tier\n const quotesEnabled = process.env.MASSIVE_QUOTES_ENABLED === 'true' || process.env.MASSIVE_QUOTES_ENABLED === '1';\n if (quotesEnabled && assetClass === \"option\" && timespan !== \"day\" && allRows.length > 0) {\n const quotesMap = await this.fetchQuotesForBars(apiTicker, headers, from, to);\n if (quotesMap.size > 0) {\n for (const row of allRows) {\n if (row.time != null) {\n const key = `${row.date} ${row.time}`;\n const quote = quotesMap.get(key);\n if (quote != null) {\n row.bid = quote.bid;\n row.ask = quote.ask;\n }\n }\n }\n }\n }\n\n return allRows;\n }\n\n /**\n * Fetches historical quotes (bid/ask) for an option ticker over a date range.\n * Returns a Map keyed by \"YYYY-MM-DD HH:MM\" ET minute key.\n * Any error (network, HTTP error, parse failure) silently returns an empty Map.\n */\n private async fetchQuotesForBars(\n apiTicker: string,\n headers: Record<string, string>,\n from: string,\n to: string,\n ): Promise<Map<string, { bid: number; ask: number }>> {\n const result = new Map<string, { bid: number; ask: number }>();\n try {\n let url: string | null =\n `${MASSIVE_BASE_URL}/v3/quotes/${encodeURIComponent(apiTicker)}?timestamp.gte=${from}×tamp.lte=${to}&order=asc&limit=${MASSIVE_MAX_LIMIT}`;\n\n const seenCursors = new Set<string>();\n const QUOTES_MAX_PAGES = 100;\n let pageCount = 0;\n\n while (url) {\n pageCount++;\n if (pageCount > QUOTES_MAX_PAGES) {\n break;\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(30_000),\n });\n } catch {\n // Network error — return what we have so far (best-effort)\n return result;\n }\n\n if (!response.ok) {\n // 403 (tier restriction), 429 (rate limit), or any other HTTP error — swallow\n return result;\n }\n\n const json = await response.json();\n const parsed = MassiveQuotesResponseSchema.safeParse(json);\n if (!parsed.success) {\n // Schema mismatch — return what we have so far\n return result;\n }\n\n const data = parsed.data;\n // Since order=asc, later quotes for the same minute overwrite earlier (last quote wins)\n for (const quote of data.results) {\n const key = nanosToETMinuteKey(quote.sip_timestamp);\n result.set(key, { bid: quote.bid_price, ask: quote.ask_price });\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n break; // Pagination loop — stop gracefully\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n } catch {\n // Any unexpected error — return empty map (best-effort)\n return new Map();\n }\n\n return result;\n }\n\n async fetchQuotes(ticker: string, from: string, to: string): Promise<Map<string, { bid: number; ask: number }>> {\n const apiKey = getApiKey();\n const apiTicker = toMassiveTicker(ticker, \"option\");\n const headers = { Authorization: `Bearer ${apiKey}` };\n return this.fetchQuotesForBars(apiTicker, headers, from, to);\n }\n\n async fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult> {\n const apiKey = getApiKey();\n const { underlying } = options;\n\n const assetClass = detectSnapshotAssetClass(underlying);\n const apiTicker = toMassiveTicker(underlying, assetClass);\n const headers = { Authorization: `Bearer ${apiKey}` };\n\n const params = new URLSearchParams({ limit: \"250\" });\n if (options.strike_price_gte != null) {\n params.set(\"strike_price.gte\", String(options.strike_price_gte));\n }\n if (options.strike_price_lte != null) {\n params.set(\"strike_price.lte\", String(options.strike_price_lte));\n }\n if (options.expiration_date_gte != null) {\n params.set(\"expiration_date.gte\", options.expiration_date_gte);\n }\n if (options.expiration_date_lte != null) {\n params.set(\"expiration_date.lte\", options.expiration_date_lte);\n }\n if (options.contract_type != null) {\n params.set(\"contract_type\", options.contract_type);\n }\n\n let url: string | null =\n `${MASSIVE_BASE_URL}/v3/snapshot/options/${encodeURIComponent(apiTicker)}?${params.toString()}`;\n\n const allContracts: OptionContract[] = [];\n const seenCursors = new Set<string>();\n let pageCount = 0;\n let underlyingPrice = 0;\n let underlyingTicker = underlying;\n\n while (url) {\n pageCount++;\n if (pageCount > MASSIVE_MAX_PAGES) {\n throw new Error(\n `Pagination safety limit reached (${MASSIVE_MAX_PAGES} pages) — possible API issue`,\n );\n }\n\n const response = await fetchWithRetry(url, headers);\n\n if (response.status === 401) {\n throw new Error(\n \"MASSIVE_API_KEY rejected by Massive.com — check your key\",\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Massive.com API error: HTTP ${response.status} ${response.statusText}`,\n );\n }\n\n const json = await response.json();\n\n const parsed = MassiveSnapshotResponseSchema.safeParse(json);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((i) => `${String(i.path.join(\".\"))}: ${i.message}`)\n .join(\"; \");\n throw new Error(`Massive API response validation failed: ${issues}`);\n }\n\n const data = parsed.data;\n\n if (data.results.length > 0 && underlyingPrice === 0) {\n underlyingPrice = data.results[0].underlying_asset.price;\n underlyingTicker = fromMassiveTicker(\n data.results[0].underlying_asset.ticker,\n );\n }\n\n for (const contract of data.results) {\n allContracts.push(mapContract(contract));\n }\n\n if (data.next_url) {\n const nextUrlObj = new URL(data.next_url);\n const cursor = nextUrlObj.searchParams.get(\"cursor\") ?? data.next_url;\n if (seenCursors.has(cursor)) {\n throw new Error(\n `Pagination loop detected — cursor repeated: ${cursor.slice(0, 50)}...`,\n );\n }\n seenCursors.add(cursor);\n url = data.next_url;\n } else {\n url = null;\n }\n }\n\n return {\n contracts: allContracts,\n underlying_price: underlyingPrice,\n underlying_ticker: underlyingTicker,\n };\n }\n}\n","/**\n * Black-Scholes option pricing, greeks computation, and IV solver.\n * Includes Bachelier (normal) model as fallback for short-dated options.\n *\n * Pure math module — no I/O, no DuckDB, no fetch.\n * European-style BS formula with continuous dividend yield.\n *\n * References:\n * - CDF approximation: Abramowitz & Stegun 26.2.17 (rational approximation)\n * - IV solver: Newton-Raphson with bisection fallback (D-11: maxIter=100, tolerance=1e-6)\n * - Bachelier model: Brenner-Subrahmanyam (1988) normal model for near-zero DTE\n */\n\n/**\n * DTE threshold below which Bachelier normal model is used instead of Black-Scholes.\n * At very short DTE (<0.1 days / ~2.4 hours), BS gamma explodes and the lognormal assumption breaks down.\n * Bachelier (normal) model handles near-zero DTE gracefully.\n */\nexport const BACHELIER_DTE_THRESHOLD = 0.1; // days (~2.4 hours)\n\n/**\n * Result of computing greeks for a single option leg.\n */\nexport interface GreeksResult {\n delta: number | null;\n gamma: number | null;\n theta: number | null; // per day\n vega: number | null; // per 1% IV move\n iv: number | null; // annualized implied volatility (0-N, not percentage)\n model?: 'bs' | 'bachelier'; // which pricing model was used; undefined if IV solve failed\n}\n\n// --- Internal helpers (exported for Bachelier model and testing) ---\n\n/** Standard normal probability density function */\nexport function pdf(x: number): number {\n return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);\n}\n\n/**\n * Cumulative standard normal distribution function.\n * Uses Abramowitz & Stegun 26.2.17 rational approximation.\n * Accuracy: |error| < 7.5e-8\n */\nexport function cdf(x: number): number {\n if (x < -10) return 0;\n if (x > 10) return 1;\n\n const sign = x < 0 ? -1 : 1;\n const absX = Math.abs(x);\n\n const p = 0.2316419;\n const b1 = 0.319381530;\n const b2 = -0.356563782;\n const b3 = 1.781477937;\n const b4 = -1.821255978;\n const b5 = 1.330274429;\n\n const t = 1.0 / (1.0 + p * absX);\n const t2 = t * t;\n const t3 = t2 * t;\n const t4 = t3 * t;\n const t5 = t4 * t;\n\n const poly = b1 * t + b2 * t2 + b3 * t3 + b4 * t4 + b5 * t5;\n const result = 1.0 - pdf(absX) * poly;\n\n return sign === 1 ? result : 1.0 - result;\n}\n\n/** Compute d1 and d2 for Black-Scholes formula */\nfunction d1d2(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): { d1: number; d2: number } {\n const sqrtT = Math.sqrt(T);\n const d1 = (Math.log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrtT);\n const d2 = d1 - sigma * sqrtT;\n return { d1, d2 };\n}\n\n// --- Exported functions ---\n\n/**\n * European Black-Scholes option price with continuous dividend yield.\n *\n * @param type - \"call\" or \"put\"\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate (e.g., 0.045)\n * @param q - Continuous dividend yield (e.g., 0.015 for SPX)\n * @param sigma - Volatility (annualized, e.g., 0.20 for 20%)\n * @returns Option price\n */\nexport function bsPrice(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n // Edge case: at or past expiry\n if (T <= 0) {\n return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);\n }\n\n // Edge case: zero volatility — return discounted intrinsic\n if (sigma <= 0) {\n const forward = S * Math.exp((r - q) * T);\n if (type === 'call') {\n return Math.max(forward - K, 0) * Math.exp(-r * T);\n } else {\n return Math.max(K - forward, 0) * Math.exp(-r * T);\n }\n }\n\n const { d1, d2 } = d1d2(S, K, T, r, q, sigma);\n\n if (type === 'call') {\n return S * Math.exp(-q * T) * cdf(d1) - K * Math.exp(-r * T) * cdf(d2);\n } else {\n return K * Math.exp(-r * T) * cdf(-d2) - S * Math.exp(-q * T) * cdf(-d1);\n }\n}\n\n/**\n * Black-Scholes delta.\n * Call: N(d1) * e^(-qT)\n * Put: (N(d1) - 1) * e^(-qT)\n */\nexport function bsDelta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) {\n if (type === 'call') return S > K ? 1 : 0;\n return S < K ? -1 : 0;\n }\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n const eqT = Math.exp(-q * T);\n\n if (type === 'call') {\n return cdf(d1) * eqT;\n } else {\n return (cdf(d1) - 1) * eqT;\n }\n}\n\n/**\n * Black-Scholes gamma. Same for calls and puts.\n * Gamma = N'(d1) * e^(-qT) / (S * sigma * sqrt(T))\n */\nexport function bsGamma(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n return (pdf(d1) * Math.exp(-q * T)) / (S * sigma * Math.sqrt(T));\n}\n\n/**\n * Black-Scholes theta (per calendar day).\n * Returns the daily time decay (negative for long options).\n */\nexport function bsTheta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1, d2 } = d1d2(S, K, T, r, q, sigma);\n const sqrtT = Math.sqrt(T);\n const eqT = Math.exp(-q * T);\n const erT = Math.exp(-r * T);\n\n // First term: -(S * e^(-qT) * N'(d1) * sigma) / (2 * sqrt(T))\n const term1 = -(S * eqT * pdf(d1) * sigma) / (2 * sqrtT);\n\n if (type === 'call') {\n const term2 = q * S * eqT * cdf(d1);\n const term3 = -r * K * erT * cdf(d2);\n return (term1 - term2 - term3) / 365;\n } else {\n const term2 = q * S * eqT * cdf(-d1);\n const term3 = -r * K * erT * cdf(-d2);\n return (term1 + term2 + term3) / 365;\n }\n}\n\n/**\n * Black-Scholes vega (per 1% IV move).\n * Vega = S * e^(-qT) * N'(d1) * sqrt(T) / 100\n */\nexport function bsVega(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma: number,\n): number {\n if (T <= 0 || sigma <= 0) return 0;\n\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n return (S * Math.exp(-q * T) * pdf(d1) * Math.sqrt(T)) / 100;\n}\n\n/**\n * Solve for implied volatility using Newton-Raphson with bisection fallback.\n *\n * @param type - \"call\" or \"put\"\n * @param marketPrice - Observed market price of the option\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate\n * @param q - Dividend yield\n * @param maxIter - Maximum iterations (default 100, per D-11)\n * @param tolerance - Convergence tolerance (default 1e-6, per D-11)\n * @returns Implied volatility or null if convergence fails\n */\nexport function solveIV(\n type: 'call' | 'put',\n marketPrice: number,\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n maxIter: number = 100,\n tolerance: number = 1e-6,\n): number | null {\n // Guard: invalid inputs\n if (marketPrice <= 0 || T <= 0) return null;\n\n let sigma = 0.3; // initial guess\n let lo = 0.001;\n let hi = 5.0;\n\n for (let i = 0; i < maxIter; i++) {\n const price = bsPrice(type, S, K, T, r, q, sigma);\n const diff = price - marketPrice;\n\n if (Math.abs(diff) < tolerance) {\n return sigma;\n }\n\n // Vega for Newton-Raphson step (raw vega, not per-1%-move)\n const { d1 } = d1d2(S, K, T, r, q, sigma);\n const rawVega = S * Math.exp(-q * T) * pdf(d1) * Math.sqrt(T);\n\n if (rawVega < 1e-10) {\n // Bisection fallback when vega is near zero\n const mid = (lo + hi) / 2;\n if (diff > 0) {\n hi = sigma;\n } else {\n lo = sigma;\n }\n sigma = mid;\n } else {\n // Newton-Raphson step\n const newSigma = sigma - diff / rawVega;\n // Clamp to reasonable range\n if (newSigma <= 0 || newSigma > 10) {\n // Fall back to bisection\n if (diff > 0) {\n hi = sigma;\n } else {\n lo = sigma;\n }\n sigma = (lo + hi) / 2;\n } else {\n sigma = newSigma;\n }\n }\n }\n\n // Did not converge\n return null;\n}\n\n// --- Bachelier (Normal) Model ---\n\n/**\n * Bachelier (normal) European option price with continuous dividend yield.\n * Uses forward price F = S * e^((r-q)*T) as the underlying.\n *\n * For near-zero DTE, the lognormal assumption in Black-Scholes breaks down.\n * Bachelier's model (normal distribution of returns) handles this gracefully.\n *\n * @param type - \"call\" or \"put\"\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate (e.g., 0.045)\n * @param q - Continuous dividend yield (e.g., 0.015 for SPX)\n * @param sigma_n - Normal (dollar) volatility (e.g., 800 for SPX ~$800/year normal vol)\n * @returns Option price\n */\nexport function bachelierPrice(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0) return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);\n if (sigma_n <= 0) {\n const forward = S * Math.exp((r - q) * T);\n return Math.exp(-r * T) * (type === 'call' ? Math.max(forward - K, 0) : Math.max(K - forward, 0));\n }\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n const discount = Math.exp(-r * T);\n if (type === 'call') {\n return discount * ((forward - K) * cdf(d) + sigma_n * sqrtT * pdf(d));\n } else {\n return discount * ((K - forward) * cdf(-d) + sigma_n * sqrtT * pdf(d));\n }\n}\n\n/**\n * Bachelier delta.\n * Call: e^(-rT) * N(d)\n * Put: -e^(-rT) * N(-d)\n */\nexport function bachelierDelta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) {\n if (type === 'call') return S > K ? 1 : 0;\n return S < K ? -1 : 0;\n }\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n const discount = Math.exp(-r * T);\n return type === 'call' ? discount * cdf(d) : -discount * cdf(-d);\n}\n\n/**\n * Bachelier gamma (same for calls and puts).\n * Gamma = e^(-rT) * n(d) / (sigma_n * sqrt(T))\n */\nexport function bachelierGamma(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n return Math.exp(-r * T) * pdf(d) / (sigma_n * sqrtT);\n}\n\n/**\n * Bachelier theta (per calendar day).\n * Returns the daily time decay (negative for long options).\n * Formula: -(e^(-rT) * sigma_n * n(d) / (2 * sqrt(T)) - r * price) / 365\n */\nexport function bachelierTheta(\n type: 'call' | 'put',\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const sqrtT = Math.sqrt(T);\n const d = (forward - K) / (sigma_n * sqrtT);\n const discount = Math.exp(-r * T);\n const price = bachelierPrice(type, S, K, T, r, q, sigma_n);\n // dV/dT = -e^(-rT) * sigma_n * n(d) / (2*sqrt(T)) + r * price (positive = more time = more value)\n // theta = -dV/dT per year, then / 365 for per-calendar-day\n // annualTheta is negative for long options (time decay)\n const annualTheta = -discount * sigma_n * pdf(d) / (2 * sqrtT) + r * price;\n return annualTheta / 365;\n}\n\n/**\n * Bachelier vega (per 1% normal vol move).\n * Raw vega = e^(-rT) * sqrt(T) * n(d). Per 1% = raw / 100.\n */\nexport function bachelierVega(\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n sigma_n: number,\n): number {\n if (T <= 0 || sigma_n <= 0) return 0;\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n return Math.exp(-r * T) * Math.sqrt(T) * pdf(d) / 100;\n}\n\n/**\n * Solve for normal (Bachelier) implied volatility using Newton-Raphson.\n *\n * @param type - \"call\" or \"put\"\n * @param marketPrice - Observed market price of the option\n * @param S - Underlying price\n * @param K - Strike price\n * @param T - Time to expiry in years\n * @param r - Risk-free rate\n * @param q - Dividend yield\n * @param maxIter - Maximum iterations (default 100)\n * @param tolerance - Convergence tolerance (default 1e-6)\n * @returns Normal implied volatility (dollar vol, e.g., ~800 for SPX) or null\n */\nexport function solveNormalIV(\n type: 'call' | 'put',\n marketPrice: number,\n S: number,\n K: number,\n T: number,\n r: number,\n q: number,\n maxIter: number = 100,\n tolerance: number = 1e-6,\n): number | null {\n if (marketPrice <= 0 || T <= 0) return null;\n // Initial guess: Brenner-Subrahmanyam for normal model\n // sigma_n ~ marketPrice / sqrt(T/(2*pi))\n let sigma_n = marketPrice / Math.sqrt(T / (2 * Math.PI));\n // Clamp initial guess to reasonable range\n sigma_n = Math.max(sigma_n, 1); // at least $1 normal vol\n\n // Bisection bounds — normal vol is in dollar terms (typically 10-10000 for index options)\n let lo = 0.01;\n let hi = 50000;\n\n for (let i = 0; i < maxIter; i++) {\n const price = bachelierPrice(type, S, K, T, r, q, sigma_n);\n const diff = price - marketPrice;\n if (Math.abs(diff) < tolerance) return sigma_n;\n // Raw vega for Newton step (without /100 per-1% scaling)\n const forward = S * Math.exp((r - q) * T);\n const d = (forward - K) / (sigma_n * Math.sqrt(T));\n const rawVega = Math.exp(-r * T) * Math.sqrt(T) * pdf(d);\n\n if (rawVega < 1e-10) {\n // Bisection fallback when vega is near zero\n if (diff > 0) {\n hi = sigma_n;\n } else {\n lo = sigma_n;\n }\n sigma_n = (lo + hi) / 2;\n } else {\n // Newton-Raphson step\n const newSigma = sigma_n - diff / rawVega;\n if (newSigma <= 0 || newSigma > 100000) {\n // Fall back to bisection\n if (diff > 0) {\n hi = sigma_n;\n } else {\n lo = sigma_n;\n }\n sigma_n = (lo + hi) / 2;\n } else {\n sigma_n = newSigma;\n }\n }\n }\n return null;\n}\n\n/**\n * Compute all greeks for a single option leg.\n *\n * First solves for IV from the market price, then computes delta, gamma, theta, vega.\n * Returns all nulls if IV cannot be determined.\n *\n * @param optionPrice - Market price of the option\n * @param underlyingPrice - Current underlying price (S)\n * @param strike - Option strike price (K)\n * @param dte - Days to expiry (fractional days, converted to years internally)\n * @param type - \"C\" for call, \"P\" for put\n * @param riskFreeRate - Risk-free interest rate\n * @param dividendYield - Continuous dividend yield\n * @returns GreeksResult with delta, gamma, theta, vega, iv (all nullable)\n */\nexport function computeLegGreeks(\n optionPrice: number,\n underlyingPrice: number,\n strike: number,\n dte: number,\n type: 'C' | 'P',\n riskFreeRate: number,\n dividendYield: number,\n): GreeksResult {\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' : 'put';\n const nullResult: GreeksResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };\n\n if (dte < BACHELIER_DTE_THRESHOLD) {\n // Bachelier normal model for short-dated options (dte < 0.1 days / ~2.4 hours).\n // iv field stores normal (dollar) volatility, not log-normal vol.\n const iv = solveNormalIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);\n if (iv === null) return nullResult;\n return {\n delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n gamma: bachelierGamma(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n theta: bachelierTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n vega: bachelierVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n iv,\n model: 'bachelier',\n };\n }\n\n // Black-Scholes for longer-dated options (dte >= 0.1 days).\n // iv field stores annualized log-normal volatility (0-N, not percentage).\n const iv = solveIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);\n if (iv === null) return nullResult;\n return {\n delta: bsDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n gamma: bsGamma(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n theta: bsTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n vega: bsVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),\n iv,\n model: 'bs',\n };\n}\n","/**\n * ThetaData Market Data Provider (stub)\n *\n * Placeholder implementation — will be replaced with full ThetaData REST API\n * adapter in a subsequent task.\n */\n\nimport type {\n MarketDataProvider,\n BarRow,\n FetchBarsOptions,\n FetchSnapshotOptions,\n FetchSnapshotResult,\n} from \"../market-provider.js\";\n\nexport class ThetaDataProvider implements MarketDataProvider {\n readonly name = \"thetadata\";\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async fetchBars(options: FetchBarsOptions): Promise<BarRow[]> {\n throw new Error(\"ThetaData provider not yet implemented — set MARKET_DATA_PROVIDER=massive\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult> {\n throw new Error(\"ThetaData provider not yet implemented — set MARKET_DATA_PROVIDER=massive\");\n }\n}\n","/**\n * Market Data Provider Interface\n *\n * Defines the shared types and provider abstraction for fetching market data\n * from external APIs (Massive.com, ThetaData, etc.).\n *\n * All providers normalize their responses to BarRow and OptionContract types.\n * The factory function getProvider() selects the active provider based on the\n * MARKET_DATA_PROVIDER environment variable (default: \"massive\").\n */\n\nimport { MassiveProvider } from \"./providers/massive.js\";\nimport { ThetaDataProvider } from \"./providers/thetadata.js\";\n\n// ---------------------------------------------------------------------------\n// Shared Types\n// ---------------------------------------------------------------------------\n\n/** Normalized OHLCV bar — shared output type for all providers. */\nexport interface BarRow {\n date: string; // \"YYYY-MM-DD\" Eastern Time\n open: number;\n high: number;\n low: number;\n close: number;\n volume: number;\n ticker: string; // Plain storage format (no prefix)\n time?: string; // \"HH:MM\" ET — only set for intraday (minute/hour) bars\n bid?: number; // Best bid — only set when provider supplies quote data\n ask?: number; // Best ask — only set when provider supplies quote data\n}\n\n/** Asset classes supported by market data providers. */\nexport type AssetClass = \"stock\" | \"index\" | \"option\";\n\n/** Options for fetching OHLCV bars. */\nexport interface FetchBarsOptions {\n /** Plain ticker — VIX, AAPL, SPX251219C05000000 (no provider-specific prefix) */\n ticker: string;\n /** Start date \"YYYY-MM-DD\" */\n from: string;\n /** End date \"YYYY-MM-DD\" */\n to: string;\n /** Bar timespan (default: \"day\") */\n timespan?: \"day\" | \"minute\" | \"hour\";\n /** Bar multiplier (default: 1) */\n multiplier?: number;\n /** Asset class (default: \"stock\") */\n assetClass?: AssetClass;\n}\n\n/** Curated option contract returned by all providers. */\nexport interface OptionContract {\n ticker: string;\n underlying_ticker: string;\n underlying_price: number;\n contract_type: \"call\" | \"put\";\n strike: number;\n expiration: string; // \"YYYY-MM-DD\"\n exercise_style: string;\n delta: number | null;\n gamma: number | null;\n theta: number | null;\n vega: number | null;\n iv: number | null;\n greeks_source: \"massive\" | \"thetadata\" | \"computed\";\n bid: number;\n ask: number;\n midpoint: number;\n last_price: number | null;\n open_interest: number;\n volume: number;\n break_even: number;\n}\n\n/** Options for fetching option chain snapshots. */\nexport interface FetchSnapshotOptions {\n underlying: string;\n strike_price_gte?: number;\n strike_price_lte?: number;\n expiration_date_gte?: string;\n expiration_date_lte?: string;\n contract_type?: \"call\" | \"put\";\n}\n\n/** Result from fetching an option chain snapshot. */\nexport interface FetchSnapshotResult {\n contracts: OptionContract[];\n underlying_price: number;\n underlying_ticker: string;\n}\n\n/** The contract every market data provider must implement. */\nexport interface MarketDataProvider {\n readonly name: string;\n fetchBars(options: FetchBarsOptions): Promise<BarRow[]>;\n fetchOptionSnapshot(options: FetchSnapshotOptions): Promise<FetchSnapshotResult>;\n /** Best-effort bid/ask quotes keyed by \"YYYY-MM-DD HH:MM\" ET. Optional — not all providers support this. */\n fetchQuotes?(ticker: string, from: string, to: string): Promise<Map<string, { bid: number; ask: number }>>;\n}\n\n// ---------------------------------------------------------------------------\n// Provider Factory (lazy singleton with static imports)\n// ---------------------------------------------------------------------------\n\nlet _cached: MarketDataProvider | null = null;\n\n/**\n * Get the active market data provider.\n *\n * Reads MARKET_DATA_PROVIDER env var (default: \"massive\").\n * Returns a lazy singleton — cached after first call.\n */\nexport function getProvider(): MarketDataProvider {\n if (_cached) return _cached;\n const name = (process.env.MARKET_DATA_PROVIDER ?? \"massive\").toLowerCase();\n switch (name) {\n case \"massive\":\n _cached = new MassiveProvider();\n break;\n case \"thetadata\":\n _cached = new ThetaDataProvider();\n break;\n default:\n throw new Error(\n `Unknown MARKET_DATA_PROVIDER: \"${name}\". Supported: massive, thetadata`\n );\n }\n return _cached!;\n}\n\n/** Reset cached provider — for test isolation only. */\nexport function _resetProvider(): void {\n _cached = null;\n}\n","/**\n * Shared Analysis Statistics\n *\n * Reusable stat computation extracted from analyze_regime_performance.\n * Used by portfolio_structure_map, analyze_structure_fit, and\n * analyze_regime_performance to compute per-slice statistics from P&L arrays.\n */\n\n/**\n * Statistics for a slice (segment, bucket, group) of trades.\n */\nexport interface SliceStats {\n tradeCount: number;\n wins: number;\n losses: number;\n /** Win rate as percentage (0-100) */\n winRate: number;\n totalPl: number;\n avgPl: number;\n avgWin: number;\n avgLoss: number;\n /** Gross wins / gross losses. null if no losses but has wins. 0 if no wins or empty. */\n profitFactor: number | null;\n}\n\n/**\n * Round a number to 2 decimal places.\n */\nfunction round2(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/**\n * Compute statistics for an array of P&L values.\n * Wins are P&L > 0, losses are P&L <= 0.\n * All numeric values are rounded to 2 decimal places.\n *\n * @param pls - Array of P&L values (positive = win, zero/negative = loss)\n * @returns Computed slice statistics\n */\nexport function computeSliceStats(pls: number[]): SliceStats {\n if (pls.length === 0) {\n return {\n tradeCount: 0,\n wins: 0,\n losses: 0,\n winRate: 0,\n totalPl: 0,\n avgPl: 0,\n avgWin: 0,\n avgLoss: 0,\n profitFactor: 0,\n };\n }\n\n const winPls = pls.filter((p) => p > 0);\n const lossPls = pls.filter((p) => p <= 0);\n\n const wins = winPls.length;\n const losses = lossPls.length;\n const winRate = (wins / pls.length) * 100;\n const totalPl = pls.reduce((sum, p) => sum + p, 0);\n const avgPl = totalPl / pls.length;\n\n const avgWin = wins > 0 ? winPls.reduce((s, p) => s + p, 0) / wins : 0;\n const avgLoss = losses > 0 ? lossPls.reduce((s, p) => s + p, 0) / losses : 0;\n\n const grossWins = winPls.reduce((s, p) => s + p, 0);\n const grossLosses = Math.abs(lossPls.reduce((s, p) => s + p, 0));\n\n let profitFactor: number | null;\n if (grossLosses > 0) {\n profitFactor = grossWins / grossLosses;\n } else if (grossWins > 0) {\n profitFactor = null; // All winners, no losses to divide by\n } else {\n profitFactor = 0;\n }\n\n return {\n tradeCount: pls.length,\n wins,\n losses,\n winRate: round2(winRate),\n totalPl: round2(totalPl),\n avgPl: round2(avgPl),\n avgWin: round2(avgWin),\n avgLoss: round2(avgLoss),\n profitFactor: profitFactor !== null ? round2(profitFactor) : null,\n };\n}\n","/**\n * Filter Predicate Builder\n *\n * Converts EntryFilter objects into runtime predicates that can evaluate\n * market data records. Handles field timing awareness via CLOSE_KNOWN_FIELDS\n * to automatically apply the prev_ prefix for close-derived fields.\n *\n * Used by analyze_structure_fit and portfolio_structure_map to evaluate\n * entry filters against market data rows.\n */\n\nimport { CLOSE_KNOWN_FIELDS } from \"./field-timing.js\";\nimport type { EntryFilter } from \"../models/strategy-profile.js\";\n\n/**\n * A compiled filter predicate with metadata about the field key used.\n */\nexport interface FilterPredicate {\n /** Evaluate this predicate against a market data record */\n test: (market: Record<string, unknown>) => boolean;\n /** The actual field key used for lookup (may have prev_ prefix) */\n fieldKey: string;\n /** Whether the field was detected as close-derived and lagged */\n isLagged: boolean;\n}\n\n/**\n * Day-of-week name to number mapping (market data uses 1=Mon to 5=Fri).\n */\nconst DAY_NAME_TO_NUM: Record<string, number> = {\n monday: 1, mon: 1,\n tuesday: 2, tue: 2, tues: 2,\n wednesday: 3, wed: 3,\n thursday: 4, thu: 4, thurs: 4,\n friday: 5, fri: 5,\n};\n\n/**\n * If a filter value is a day-of-week name and the field is Day_of_Week,\n * convert it to the corresponding number. Returns null if not applicable.\n */\nfunction resolveDayName(value: unknown): number | null {\n if (typeof value !== \"string\") return null;\n return DAY_NAME_TO_NUM[value.toLowerCase()] ?? null;\n}\n\n/**\n * Safely extract a numeric value from a record.\n * Returns NaN if the value is missing, null, undefined, or non-numeric.\n */\nfunction getNum(record: Record<string, unknown>, key: string): number {\n const val = record[key];\n if (val === null || val === undefined) return NaN;\n const num = Number(val);\n return num;\n}\n\n/**\n * Safely extract a value from a record for loose equality comparison.\n * Returns undefined if the key is missing.\n */\nfunction getRaw(record: Record<string, unknown>, key: string): unknown {\n return record[key];\n}\n\n/**\n * Check whether a filter value is a cross-field reference (a string that\n * looks like a field name rather than a pure numeric literal).\n */\nfunction isCrossFieldRef(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n // If it parses as a finite number, it's a numeric literal, not a field ref\n if (value.trim() !== \"\" && isFinite(Number(value))) return false;\n return true;\n}\n\n/**\n * Resolve a cross-field reference value. If the value is a string that\n * already exists as a key in the market record, use it as-is. Otherwise,\n * if the bare field name (without prev_ prefix) is close-derived, try\n * the prev_ prefixed version.\n */\nfunction resolveFieldRef(\n refName: string,\n market: Record<string, unknown>\n): number {\n // Direct lookup first (handles cases like \"prev_VIX_Close\" spelled out)\n if (refName in market) {\n return getNum(market, refName);\n }\n // If the ref looks like a bare close-derived field, try prev_ prefix\n if (CLOSE_KNOWN_FIELDS.has(refName)) {\n return getNum(market, `prev_${refName}`);\n }\n return NaN;\n}\n\n/**\n * Build a runtime predicate from an EntryFilter.\n *\n * Automatically detects close-derived fields via CLOSE_KNOWN_FIELDS and\n * prepends \"prev_\" to the field key for correct lookahead-free evaluation.\n *\n * For comparison operators (>, <, >=, <=, ==), if the filter value is a\n * string that looks like a field name (not a pure numeric string), it is\n * treated as a cross-field reference. The referenced field's value is\n * looked up from the market record at evaluation time.\n *\n * NaN/null/undefined values in the market record always return false\n * (missing data never matches a filter).\n *\n * @param filter - Entry filter specification\n * @returns Compiled predicate with metadata\n */\nexport function buildFilterPredicate(filter: EntryFilter): FilterPredicate {\n const isLagged = CLOSE_KNOWN_FIELDS.has(filter.field);\n const fieldKey = isLagged ? `prev_${filter.field}` : filter.field;\n\n const { operator, value } = filter;\n\n const test = (market: Record<string, unknown>): boolean => {\n // For \"in\" and \"==\" operators, we may need raw value access\n if (operator === \"in\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n if (!Array.isArray(value)) return false;\n // Try day-of-week name resolution for each element\n return value.some((v) => {\n const dayNum = resolveDayName(v);\n if (dayNum !== null) return Number(raw) === dayNum;\n return v == raw; // eslint-disable-line eqeqeq\n });\n }\n\n if (operator === \"==\") {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) return false;\n // Day-of-week name resolution (e.g., \"Tuesday\" == 2)\n const dayNum = resolveDayName(value);\n if (dayNum !== null) return Number(raw) === dayNum;\n // Cross-field reference for ==\n if (isCrossFieldRef(value)) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n return Number(raw) === refVal;\n }\n return value == raw; // eslint-disable-line eqeqeq\n }\n\n // Numeric operators: >, <, >=, <=, between\n const num = getNum(market, fieldKey);\n if (isNaN(num)) return false;\n\n // For comparison operators, check if value is a cross-field reference\n if (\n isCrossFieldRef(value) &&\n (operator === \">\" || operator === \"<\" || operator === \">=\" || operator === \"<=\")\n ) {\n const refVal = resolveFieldRef(value, market);\n if (isNaN(refVal)) return false;\n switch (operator) {\n case \">\":\n return num > refVal;\n case \"<\":\n return num < refVal;\n case \">=\":\n return num >= refVal;\n case \"<=\":\n return num <= refVal;\n }\n }\n\n switch (operator) {\n case \">\":\n return num > Number(value);\n case \"<\":\n return num < Number(value);\n case \">=\":\n return num >= Number(value);\n case \"<=\":\n return num <= Number(value);\n case \"between\": {\n if (!Array.isArray(value) || value.length < 2) return false;\n const lo = Number(value[0]);\n const hi = Number(value[1]);\n return num >= lo && num <= hi;\n }\n default:\n return false;\n }\n };\n\n return { test, fieldKey, isLagged };\n}\n","/**\n * Strategy Profile Tools\n *\n * MCP tools for CRUD operations on strategy profiles stored in DuckDB.\n * Wraps the Phase 60 storage layer (db/profile-schemas.ts) as conversational tools.\n *\n * Tools registered:\n * - profile_strategy — Create or update a strategy profile\n * - get_strategy_profile — Retrieve a single profile by block + strategy name\n * - list_profiles — List profiles (optionally filtered by block)\n * - delete_profile — Remove a profile (idempotent)\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection, upgradeToReadWrite, downgradeToReadOnly } from \"../db/connection.js\";\nimport {\n upsertProfile,\n getProfile,\n listProfiles,\n deleteProfile,\n} from \"../db/profile-schemas.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schemas (exported for testability)\n// ---------------------------------------------------------------------------\n\nexport const profileStrategySchema = z.object({\n blockId: z.string().describe(\"Block ID (block_id) to associate the profile with\"),\n strategyName: z.string().describe(\"Human-readable strategy name (e.g., 'Pickle RIC v2')\"),\n structureType: z\n .string()\n .describe(\n \"Option structure type: iron_condor, calendar_spread, double_calendar, vertical_spread, \" +\n \"butterfly, reverse_iron_condor, short_put_spread, short_call_spread, straddle, strangle, etc.\"\n ),\n greeksBias: z\n .string()\n .describe(\n \"Primary greeks exposure: theta_positive, vega_negative, delta_neutral, delta_positive, \" +\n \"delta_negative, gamma_scalp, etc.\"\n ),\n thesis: z.string().default(\"\").describe(\"Free-text description of the strategy thesis\"),\n legs: z\n .array(\n z.object({\n type: z.string().describe(\"Leg type: long_put, short_call, long_call, short_put, etc.\"),\n strike: z.string().describe(\"Strike selection: ATM, 5-delta, 30-delta, etc.\"),\n expiry: z.string().describe(\"Expiry selection: same-day, weekly, 45-DTE, etc.\"),\n quantity: z.number().describe(\"Quantity (positive=long, negative=short)\"),\n strikeMethod: z.enum(['delta', 'dollar_price', 'offset', 'percentage']).optional().describe(\"How strike is selected\"),\n strikeValue: z.number().optional().describe(\"Numeric strike value (e.g., 25 for 25-delta)\"),\n })\n )\n .default([])\n .describe(\"Structured leg descriptions\"),\n entryFilters: z\n .array(\n z.object({\n field: z.string().describe(\"Market data field: VIX_Close, RSI_14, Vol_Regime, etc.\"),\n operator: z.string().describe(\"Comparison operator: >, <, >=, <=, ==, between, in\"),\n value: z\n .union([z.string(), z.number(), z.array(z.union([z.string(), z.number()]))])\n .describe(\"Filter value or array for between/in operators\"),\n description: z.string().optional().describe(\"Human-readable description of this filter\"),\n source: z.enum([\"market\", \"execution\"]).optional().describe(\"Filter source: 'market' = testable against market data columns, 'execution' = platform-level (time windows, leg ratios). Defaults to 'market'. Execution filters are documented but skipped during validate_entry_filters analysis.\"),\n })\n )\n .default([])\n .describe(\"Entry condition filters. Tag each with source: 'market' (testable in analysis) or 'execution' (OO/platform-level, skipped in analysis).\"),\n exitRules: z\n .array(\n z.object({\n type: z.string().describe(\"Rule type: stop_loss, profit_target, time_exit, conditional\"),\n trigger: z.string().describe(\"Trigger condition: '200% of credit', '50% of max profit', '15:00 ET'\"),\n description: z.string().optional().describe(\"Human-readable description\"),\n stopLossType: z.enum(['percentage', 'dollar', 'sl_ratio', 'debit_percentage']).optional().describe(\"Stop loss calculation method\"),\n stopLossValue: z.number().optional().describe(\"Stop loss numeric value\"),\n monitoring: z.object({\n granularity: z.enum(['intra_minute', 'candle_close', 'end_of_bar']).optional().describe(\"Price check frequency\"),\n priceSource: z.enum(['nbbo', 'mid', 'last']).optional().describe(\"Which price to use\"),\n }).optional().describe(\"Monitoring configuration for this rule\"),\n slippage: z.number().optional().describe(\"Per-rule slippage override\"),\n })\n )\n .default([])\n .describe(\"Exit rules and triggers\"),\n expectedRegimes: z\n .array(z.enum([\"very_low\", \"low\", \"below_avg\", \"above_avg\", \"high\", \"extreme\"]))\n .default([])\n .describe(\"VIX-based vol regimes this strategy targets. very_low=VIX<13, low=13-16, below_avg=16-20, above_avg=20-25, high=25-30, extreme=30+\"),\n keyMetrics: z\n .object({\n expectedWinRate: z.number().optional().describe(\"Expected win rate (0-1)\"),\n targetPremium: z.number().optional().describe(\"Target premium collected ($)\"),\n maxLoss: z.number().optional().describe(\"Maximum loss per contract ($)\"),\n profitTarget: z.number().optional().describe(\"Profit target ($ or %)\"),\n })\n .passthrough()\n .default({})\n .describe(\"Performance benchmarks and strategy-specific metrics\"),\n positionSizing: z\n .object({\n method: z.string().describe(\"Sizing method: pct_of_portfolio, fixed_contracts, fixed_dollar, discretionary\"),\n allocationPct: z.number().optional().describe(\"Portfolio allocation percentage (e.g., 2 for 2%)\"),\n maxContracts: z.number().optional().describe(\"Maximum contracts per trade\"),\n maxAllocationDollar: z.number().optional().describe(\"Maximum dollar allocation per trade\"),\n maxOpenPositions: z.number().optional().describe(\"Maximum concurrent open positions\"),\n description: z.string().optional().describe(\"Free-text sizing notes\"),\n backtestAllocationPct: z.number().optional().describe(\"Allocation % used in backtest\"),\n liveAllocationPct: z.number().optional().describe(\"Allocation % used in live portfolio\"),\n maxContractsPerTrade: z.number().optional().describe(\"Per-entry contract cap (distinct from maxContracts hard cap)\"),\n })\n .optional()\n .describe(\"Position sizing rules. Per-block — same strategy in backtest vs portfolio may have different sizing.\"),\n underlying: z.string().optional().describe(\"Underlying symbol: SPX, QQQ, etc.\"),\n reEntry: z.boolean().optional().describe(\"Strategy supports re-entry on same day\"),\n capProfits: z.boolean().optional().describe(\"Profits are capped by structure\"),\n capLosses: z.boolean().optional().describe(\"Losses are capped by structure\"),\n requireTwoPricesPT: z.boolean().optional().describe(\"Profit target requires two prices\"),\n closeOnCompletion: z.boolean().optional().describe(\"Close entire position when any leg hits target\"),\n ignoreMarginReq: z.boolean().optional().describe(\"Strategy ignores standard margin requirements\"),\n});\n\nexport const getStrategyProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID to look up\"),\n strategyName: z.string().describe(\"Strategy name to look up\"),\n});\n\nexport const listProfilesSchema = z.object({\n blockId: z.string().optional().describe(\"Optional block ID filter. Omit to list all profiles across all blocks.\"),\n});\n\nexport const deleteProfileSchema = z.object({\n blockId: z.string().describe(\"Block ID of the profile to delete\"),\n strategyName: z.string().describe(\"Strategy name of the profile to delete\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler functions (exported for testability)\n// ---------------------------------------------------------------------------\n\n/**\n * Handle profile_strategy: create or update a strategy profile.\n */\nexport async function handleProfileStrategy(\n input: z.infer<typeof profileStrategySchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const stored = await upsertProfile(conn, {\n blockId: input.blockId,\n strategyName: input.strategyName,\n structureType: input.structureType,\n greeksBias: input.greeksBias,\n thesis: input.thesis,\n legs: input.legs,\n entryFilters: input.entryFilters,\n exitRules: input.exitRules,\n expectedRegimes: input.expectedRegimes,\n keyMetrics: input.keyMetrics,\n positionSizing: input.positionSizing,\n underlying: input.underlying,\n reEntry: input.reEntry,\n capProfits: input.capProfits,\n capLosses: input.capLosses,\n requireTwoPricesPT: input.requireTwoPricesPT,\n closeOnCompletion: input.closeOnCompletion,\n ignoreMarginReq: input.ignoreMarginReq,\n });\n return createToolOutput(\n `Profile saved: ${input.strategyName} for block ${input.blockId}`,\n { profile: stored }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n/**\n * Handle get_strategy_profile: retrieve a single profile.\n */\nexport async function handleGetStrategyProfile(\n input: z.infer<typeof getStrategyProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, input.blockId, input.strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}'`,\n { profile: null }\n );\n }\n return createToolOutput(\n `Profile: ${input.strategyName} in block ${input.blockId}`,\n { profile }\n );\n}\n\n/**\n * Handle list_profiles: list profiles with optional block filter.\n */\nexport async function handleListProfiles(\n input: z.infer<typeof listProfilesSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId);\n const summaryRows = profiles.map((p) => ({\n blockId: p.blockId,\n strategyName: p.strategyName,\n structureType: p.structureType,\n greeksBias: p.greeksBias,\n underlying: p.underlying ?? null,\n positionSizing: p.positionSizing?.method ?? null,\n updatedAt: p.updatedAt,\n }));\n return createToolOutput(\n `Found ${profiles.length} profile(s)${input.blockId ? ` for block ${input.blockId}` : \"\"}`,\n { count: profiles.length, profiles: summaryRows }\n );\n}\n\n/**\n * Handle delete_profile: remove a profile (idempotent).\n */\nexport async function handleDeleteProfile(\n input: z.infer<typeof deleteProfileSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n await upgradeToReadWrite(baseDir);\n try {\n const conn = await getConnection(baseDir);\n const deleted = await deleteProfile(conn, input.blockId, input.strategyName);\n if (deleted) {\n return createToolOutput(\n `Deleted profile: ${input.strategyName} from block ${input.blockId}`,\n { deleted: true }\n );\n }\n return createToolOutput(\n `No profile found for strategy '${input.strategyName}' in block '${input.blockId}' — nothing to delete`,\n { deleted: false }\n );\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registration\n// ---------------------------------------------------------------------------\n\n/**\n * Register all profile CRUD tools on the MCP server.\n *\n * @param server - McpServer instance to register tools on\n * @param baseDir - Base data directory (passed to connection helpers)\n */\nexport function registerProfileTools(server: McpServer, baseDir: string): void {\n // -------------------------------------------------------------------------\n // Tool: profile_strategy\n // -------------------------------------------------------------------------\n server.registerTool(\n \"profile_strategy\",\n {\n description:\n \"Create or update a strategy profile for a block. Stores structure type, greeks bias, \" +\n \"legs, entry filters, exit rules, expected regimes, key metrics, and position sizing. \" +\n \"If a profile with the same block_id + strategy_name already exists, it is overwritten (upsert). \" +\n \"When profiling the same strategy across multiple blocks (e.g., backtest vs live portfolio), \" +\n \"retrieve the existing profile with get_strategy_profile and copy its fields, updating only \" +\n \"positionSizing or other block-specific params rather than re-asking the user for all details.\",\n inputSchema: profileStrategySchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleProfileStrategy(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: get_strategy_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"get_strategy_profile\",\n {\n description:\n \"Retrieve a single strategy profile by block_id and strategy_name. \" +\n \"Returns the full profile including all schema fields, or a not-found message.\",\n inputSchema: getStrategyProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleGetStrategyProfile(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: list_profiles\n // -------------------------------------------------------------------------\n server.registerTool(\n \"list_profiles\",\n {\n description:\n \"List strategy profiles. Provide block_id to filter by block, or omit to list all profiles \" +\n \"across all blocks. Returns summary rows with block_id, strategy_name, structure_type, \" +\n \"greeks_bias, and updated_at.\",\n inputSchema: listProfilesSchema,\n },\n async (input) => {\n // list_profiles has optional blockId — when provided, sync the block first;\n // when omitted, query directly without sync (no block to validate).\n if (input.blockId) {\n const syncedHandler = withSyncedBlock(baseDir, async (syncInput: { blockId: string }, ctx) => {\n return handleListProfiles({ blockId: syncInput.blockId }, ctx.baseDir);\n });\n return syncedHandler({ blockId: input.blockId });\n }\n return handleListProfiles(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: delete_profile\n // -------------------------------------------------------------------------\n server.registerTool(\n \"delete_profile\",\n {\n description:\n \"Delete a strategy profile by block_id and strategy_name. \" +\n \"Idempotent: deleting a nonexistent profile returns success with a not-found message.\",\n inputSchema: deleteProfileSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleDeleteProfile(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Output Formatter\n *\n * Utilities for formatting MCP tool output.\n *\n * JSON-First Pattern:\n * Tools return structured JSON as the primary format. JSON is machine-readable,\n * enabling reliable data extraction without parsing natural language.\n *\n * A brief text summary is included for user visibility, but the JSON\n * is the authoritative source for all data and reasoning.\n */\n\n/**\n * MCP content item types\n */\nexport interface McpTextContent {\n type: \"text\";\n text: string;\n}\n\nexport interface McpResourceContent {\n type: \"resource\";\n resource: {\n uri: string;\n mimeType: string;\n text: string;\n };\n}\n\nexport type McpContent = McpTextContent | McpResourceContent;\n\nexport interface ToolOutput {\n [x: string]: unknown;\n content: McpContent[];\n}\n\n// Legacy alias for backward compatibility\nexport type DualOutput = ToolOutput;\n\n/**\n * Create JSON-first output for MCP tools.\n *\n * The structured JSON is the primary data source for Claude reasoning.\n * A brief text summary is provided for user visibility.\n *\n * @param summary - Brief text summary (1-3 lines) for user display\n * @param data - Structured data object - the authoritative data source\n * @returns MCP-compatible response with JSON as primary content\n */\nexport function createToolOutput(summary: string, data: object): ToolOutput {\n return {\n content: [\n { type: \"text\", text: summary },\n {\n type: \"resource\",\n resource: {\n uri: \"data:application/json\",\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n },\n ],\n };\n}\n\n/**\n * Legacy function - redirects to createToolOutput.\n * @deprecated Use createToolOutput instead\n */\nexport function createDualOutput(markdown: string, data: object): DualOutput {\n // Extract a brief summary from the markdown (first non-empty line or heading)\n const lines = markdown.split(\"\\n\").filter((l) => l.trim());\n const summary = lines[0]?.replace(/^#+\\s*/, \"\") || \"Results available\";\n return createToolOutput(summary, data);\n}\n\n/**\n * Format a number as currency ($1,234.56)\n */\nexport function formatCurrency(value: number): string {\n const isNegative = value < 0;\n const absValue = Math.abs(value);\n const formatted = absValue.toLocaleString(\"en-US\", {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n return isNegative ? `-$${formatted}` : `$${formatted}`;\n}\n\n/**\n * Format a number as percentage (12.34%)\n */\nexport function formatPercent(value: number, decimals: number = 2): string {\n return `${value.toFixed(decimals)}%`;\n}\n\n/**\n * Format a ratio with specified decimals\n */\nexport function formatRatio(\n value: number | undefined,\n decimals: number = 2\n): string {\n if (value === undefined || value === null || !isFinite(value)) {\n return \"N/A\";\n }\n return value.toFixed(decimals);\n}\n","/**\n * Profile Analysis Tools\n *\n * MCP tools that use stored strategy profiles for targeted analysis:\n * - analyze_structure_fit: Dimension-based performance breakdown using profile context\n * - validate_entry_filters: Entry filter effectiveness analysis with ablation study\n * - portfolio_structure_map: Vol_Regime x Trend_Direction matrix across strategies\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.js\";\nimport { getProfile, listProfiles } from \"../db/profile-schemas.js\";\nimport { filterByStrategy } from \"./shared/filters.js\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.js\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.js\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.js\";\nimport { buildFilterPredicate, type FilterPredicate } from \"../utils/filter-predicates.js\";\nimport { withSyncedBlock } from \"./middleware/sync-middleware.js\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.js\";\nimport { syncAllBlocks } from \"../sync/index.js\";\n\n// =============================================================================\n// Utility Functions (local to this module)\n// =============================================================================\n\n/**\n * Format trade date to YYYY-MM-DD for market data matching.\n */\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\n// =============================================================================\n// Vol Regime Labels\n// =============================================================================\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\nconst TREND_LABELS = [\"up\", \"down\", \"flat\"] as const;\ntype TrendLabel = (typeof TREND_LABELS)[number];\n\n/**\n * Day of week labels (market data: 1=Mon to 5=Fri)\n */\nconst DAY_LABELS: Record<number, string> = {\n 1: \"Monday\",\n 2: \"Tuesday\",\n 3: \"Wednesday\",\n 4: \"Thursday\",\n 5: \"Friday\",\n};\n\n/**\n * Determine time-of-day bucket from timeOpened string (format \"HH:MM:SS\" or \"HH:MM\").\n */\nfunction getTimeBucket(timeOpened: string | undefined): string | null {\n if (!timeOpened) return null;\n const match = timeOpened.match(/^(\\d{1,2}):(\\d{2})/);\n if (!match) return null;\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const totalMinutes = hours * 60 + minutes;\n\n // morning: 09:30-11:00, midday: 11:00-14:00, afternoon: 14:00-16:00\n if (totalMinutes < 570) return null; // before 09:30\n if (totalMinutes < 660) return \"morning\"; // 09:30-11:00\n if (totalMinutes < 840) return \"midday\"; // 11:00-14:00\n if (totalMinutes <= 960) return \"afternoon\"; // 14:00-16:00\n return null; // after 16:00\n}\n\n/**\n * Safely get a raw value from a record.\n */\nfunction getRaw(record: Record<string, unknown>, field: string): unknown {\n return record[field];\n}\n\ninterface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n}\n\n/**\n * Load trades and market data for a strategy profile analysis.\n * Shared between analyze_structure_fit and validate_entry_filters.\n */\nasync function loadTradesAndMarket(\n baseDir: string,\n blockId: string,\n strategyName: string\n): Promise<{\n matched: TradeWithMarket[];\n unmatchedCount: number;\n allTrades: Trade[];\n}> {\n const block = await loadBlock(baseDir, blockId);\n let trades = filterByStrategy(block.trades, strategyName);\n\n // Single-strategy backtest blocks may have a different strategy name in the CSV\n // (e.g., blockId fallback \"2_3 dc\" vs profile name \"2/3 DC - v2\").\n // If no trades match by name and the block has only one unique strategy, use all trades.\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n return { matched: [], unmatchedCount: 0, allTrades: [] };\n }\n\n // Collect unique trade keys for market query\n const tradeKeys = uniqueTradeLookupKeys(trades);\n\n // Query market data\n const conn = await getConnection(baseDir);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n return { matched, unmatchedCount, allTrades: trades };\n}\n\n/**\n * Create numeric bucket labels from data values.\n * Divides sorted values into ~4 quartile-based ranges.\n */\nfunction createNumericBuckets(values: number[]): { label: string; min: number; max: number }[] {\n if (values.length === 0) return [];\n const sorted = [...values].sort((a, b) => a - b);\n\n const uniqueValues = [...new Set(sorted)];\n if (uniqueValues.length <= 4) {\n return uniqueValues.map((v) => ({\n label: String(Math.round(v * 100) / 100),\n min: v,\n max: v,\n }));\n }\n\n const buckets: { label: string; min: number; max: number }[] = [];\n const quartileSize = Math.ceil(sorted.length / 4);\n for (let i = 0; i < 4; i++) {\n const start = i * quartileSize;\n const end = Math.min((i + 1) * quartileSize - 1, sorted.length - 1);\n if (start > sorted.length - 1) break;\n const min = sorted[start];\n const max = sorted[end];\n const r = (n: number) => Math.round(n * 100) / 100;\n buckets.push({\n label: min === max ? `${r(min)}` : `${r(min)} to ${r(max)}`,\n min,\n max,\n });\n }\n\n return buckets;\n}\n\n/**\n * Find which bucket a value belongs to.\n */\nfunction findBucket(\n value: number,\n buckets: { label: string; min: number; max: number }[]\n): string | null {\n for (const bucket of buckets) {\n if (value >= bucket.min && value <= bucket.max) return bucket.label;\n }\n return null;\n}\n\n// =============================================================================\n// analyze_structure_fit Schema and Handler\n// =============================================================================\n\nexport const analyzeStructureFitSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per bucket for reliable stats (thin-data warning threshold)\"),\n});\n\nexport async function handleAnalyzeStructureFit(\n input: z.infer<typeof analyzeStructureFitSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded from market-based analysis.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Overall stats\n const allPls = matched.map((m) => m.trade.pl);\n const overall = computeSliceStats(allPls);\n\n // Dimension analysis\n const dimensions: Record<string, Record<string, SliceStats>> = {};\n\n // --- Fixed dimension: Vol_Regime ---\n const volRegimeBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val)) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!volRegimeBuckets[label]) volRegimeBuckets[label] = [];\n volRegimeBuckets[label].push(trade.pl);\n }\n const volRegimeStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(volRegimeBuckets)) {\n volRegimeStats[label] = computeSliceStats(pls);\n }\n dimensions[\"Vol_Regime\"] = volRegimeStats;\n\n // --- Fixed dimension: day_of_week ---\n const dowBuckets: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"Day_of_Week\");\n if (isNaN(val)) continue;\n const label = DAY_LABELS[val] || `day_${val}`;\n if (!dowBuckets[label]) dowBuckets[label] = [];\n dowBuckets[label].push(trade.pl);\n }\n const dowStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(dowBuckets)) {\n dowStats[label] = computeSliceStats(pls);\n }\n dimensions[\"day_of_week\"] = dowStats;\n\n // --- Fixed dimension: time_of_day ---\n const todBuckets: Record<string, number[]> = {};\n for (const { trade } of matched) {\n const bucket = getTimeBucket(trade.timeOpened);\n if (!bucket) continue;\n if (!todBuckets[bucket]) todBuckets[bucket] = [];\n todBuckets[bucket].push(trade.pl);\n }\n const todStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(todBuckets)) {\n todStats[label] = computeSliceStats(pls);\n }\n dimensions[\"time_of_day\"] = todStats;\n\n // --- Profile-derived dimensions from entry_filters (market-source only) ---\n for (const filter of profile.entryFilters.filter((f) => f.source !== \"execution\")) {\n const predicate = buildFilterPredicate(filter);\n const fieldKey = predicate.fieldKey;\n\n // Collect numeric values for this field from matched trades\n const fieldValues: { val: number; pl: number }[] = [];\n for (const { trade, market } of matched) {\n const raw = getRaw(market, fieldKey);\n if (raw === null || raw === undefined) continue;\n const num = Number(raw);\n if (isNaN(num)) continue;\n fieldValues.push({ val: num, pl: trade.pl });\n }\n\n if (fieldValues.length === 0) continue;\n\n // Create buckets from the data\n const buckets = createNumericBuckets(fieldValues.map((f) => f.val));\n if (buckets.length === 0) continue;\n\n const filterBuckets: Record<string, number[]> = {};\n for (const { val, pl } of fieldValues) {\n const bucketLabel = findBucket(val, buckets);\n if (!bucketLabel) continue;\n if (!filterBuckets[bucketLabel]) filterBuckets[bucketLabel] = [];\n filterBuckets[bucketLabel].push(pl);\n }\n\n const filterStats: Record<string, SliceStats> = {};\n for (const [label, pls] of Object.entries(filterBuckets)) {\n filterStats[label] = computeSliceStats(pls);\n }\n dimensions[filter.field] = filterStats;\n }\n\n // Thin-data warnings\n for (const [dimName, bucketStats] of Object.entries(dimensions)) {\n for (const [bucketLabel, stats] of Object.entries(bucketStats)) {\n if (stats.tradeCount > 0 && stats.tradeCount < minTrades) {\n warnings.push(\n `${dimName}/${bucketLabel}: only ${stats.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: { field: string; suggested: string; reason: string }[] = [];\n\n // Check Vol_Regime performance vs overall\n for (const [label, stats] of Object.entries(volRegimeStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (winRateDiff >= 20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: label,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${winRateDiff.toFixed(1)}pp above overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n if (winRateDiff <= -20) {\n profileUpdateHints.push({\n field: \"expectedRegimes\",\n suggested: `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% in ${label} is ${Math.abs(winRateDiff).toFixed(1)}pp below overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check day_of_week for stark differences\n for (const [label, stats] of Object.entries(dowStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"day_of_week\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% on ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Check time_of_day for stark differences\n for (const [label, stats] of Object.entries(todStats)) {\n if (stats.tradeCount >= minTrades) {\n const winRateDiff = stats.winRate - overall.winRate;\n if (Math.abs(winRateDiff) >= 20) {\n profileUpdateHints.push({\n field: \"time_of_day\",\n suggested: winRateDiff > 0 ? `favor_${label}` : `avoid_${label}`,\n reason: `Win rate ${stats.winRate.toFixed(1)}% during ${label} vs overall ${overall.winRate.toFixed(1)}%`,\n });\n }\n }\n }\n\n // Summary text\n const dimNames = Object.keys(dimensions).join(\", \");\n const summaryText = `Structure fit analysis for '${strategyName}': ${matched.length} trades analyzed across ${Object.keys(dimensions).length} dimensions (${dimNames}). Overall win rate: ${overall.winRate.toFixed(1)}%, avg P&L: $${overall.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n overall,\n dimensions,\n profile_update_hints: profileUpdateHints,\n warnings,\n profile: {\n strategyName: profile.strategyName,\n structureType: profile.structureType,\n greeksBias: profile.greeksBias,\n thesis: profile.thesis,\n expectedRegimes: profile.expectedRegimes,\n },\n });\n}\n\n// =============================================================================\n// validate_entry_filters Schema and Handler\n// =============================================================================\n\nexport const validateEntryFiltersSchema = z.object({\n blockId: z.string().describe(\"Block ID to analyze\"),\n strategyName: z.string().describe(\"Strategy name matching a stored profile\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Minimum trades per group for reliable stats\"),\n maxAblationFilters: z\n .number()\n .optional()\n .default(8)\n .describe(\"Maximum number of filters for pairwise ablation (cap for combinatorial explosion)\"),\n});\n\nexport async function handleValidateEntryFilters(\n input: z.infer<typeof validateEntryFiltersSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const { blockId, strategyName } = input;\n const minTrades = input.minTrades ?? 10;\n const maxAblationFilters = input.maxAblationFilters ?? 8;\n\n // Load profile\n const conn = await getConnection(baseDir);\n const profile = await getProfile(conn, blockId, strategyName);\n if (!profile) {\n return createToolOutput(\n `No profile found for strategy '${strategyName}' in block '${blockId}'. Create one with profile_strategy first.`,\n { error: \"profile_not_found\" }\n );\n }\n\n // Early return if no entry filters\n if (!profile.entryFilters || profile.entryFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has no entry_filters defined. Add filters via profile_strategy to enable validation.`,\n { no_filters: true }\n );\n }\n\n // Separate market-testable filters from execution-only filters\n const allFilters = profile.entryFilters;\n const marketFilters = allFilters.filter((f) => f.source !== \"execution\");\n const executionFilters = allFilters.filter((f) => f.source === \"execution\");\n\n if (marketFilters.length === 0) {\n return createToolOutput(\n `Profile '${strategyName}' has ${allFilters.length} filter(s) but all are tagged source:'execution' (platform-level). No market-data filters to validate.`,\n { no_market_filters: true, execution_filters: executionFilters }\n );\n }\n\n // Load trades + market data\n const { matched, unmatchedCount, allTrades } = await loadTradesAndMarket(\n baseDir,\n blockId,\n strategyName\n );\n\n const warnings: string[] = [];\n\n if (executionFilters.length > 0) {\n warnings.push(\n `${executionFilters.length} execution-level filter(s) skipped (not testable against market data): ${executionFilters.map((f) => f.description || f.field).join(\", \")}`\n );\n }\n\n if (allTrades.length === 0) {\n return createToolOutput(\n `No trades found for strategy '${strategyName}' in block '${blockId}'.`,\n { error: \"no_trades\" }\n );\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `${unmatchedCount} of ${allTrades.length} trades had no matching market data and were excluded.`\n );\n }\n\n if (matched.length === 0) {\n return createToolOutput(\n `No trades could be matched to market data for strategy '${strategyName}'.`,\n { error: \"no_market_match\", warnings }\n );\n }\n\n // Build predicates for market-testable filters only\n const filters = marketFilters;\n const predicates: FilterPredicate[] = filters.map((f) => buildFilterPredicate(f));\n\n // No-filters baseline: all matched trades\n const noFiltersPls = matched.map((m) => m.trade.pl);\n const noFiltersStats = computeSliceStats(noFiltersPls);\n\n // Per-filter comparison\n const perFilter: Record<\n string,\n { entered: SliceStats; filtered_out: SliceStats; no_data_count: number }\n > = {};\n\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n const predicate = predicates[i];\n const filterDesc =\n filter.description || `${filter.field} ${filter.operator} ${JSON.stringify(filter.value)}`;\n\n const enteredPls: number[] = [];\n const filteredOutPls: number[] = [];\n let noDataCount = 0;\n\n for (const { trade, market } of matched) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n noDataCount++;\n continue;\n }\n if (predicate.test(market)) {\n enteredPls.push(trade.pl);\n } else {\n filteredOutPls.push(trade.pl);\n }\n }\n\n perFilter[filterDesc] = {\n entered: computeSliceStats(enteredPls),\n filtered_out: computeSliceStats(filteredOutPls),\n no_data_count: noDataCount,\n };\n }\n\n // Ablation study\n // Baseline: all filters applied\n const baselinePls: number[] = [];\n for (const { trade, market } of matched) {\n let passesAll = true;\n let hasData = true;\n for (const predicate of predicates) {\n const raw = getRaw(market, predicate.fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicate.test(market)) {\n passesAll = false;\n break;\n }\n }\n if (hasData && passesAll) {\n baselinePls.push(trade.pl);\n }\n }\n const baseline = computeSliceStats(baselinePls);\n\n // Single removal ablation\n const ablationSingle: Record<string, SliceStats> = {};\n for (let skip = 0; skip < filters.length; skip++) {\n const filterDesc =\n filters[skip].description ||\n `${filters[skip].field} ${filters[skip].operator} ${JSON.stringify(filters[skip].value)}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let j = 0; j < predicates.length; j++) {\n if (j === skip) continue;\n const raw = getRaw(market, predicates[j].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[j].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationSingle[filterDesc] = computeSliceStats(pls);\n }\n\n // Pairwise removal ablation (only if filter count <= maxAblationFilters)\n const ablationPairs: Record<string, SliceStats> = {};\n if (filters.length <= maxAblationFilters) {\n for (let i = 0; i < filters.length; i++) {\n for (let j = i + 1; j < filters.length; j++) {\n const descI =\n filters[i].description ||\n `${filters[i].field} ${filters[i].operator} ${JSON.stringify(filters[i].value)}`;\n const descJ =\n filters[j].description ||\n `${filters[j].field} ${filters[j].operator} ${JSON.stringify(filters[j].value)}`;\n const pairKey = `${descI} + ${descJ}`;\n\n const pls: number[] = [];\n for (const { trade, market } of matched) {\n let passesRemaining = true;\n let hasData = true;\n for (let k = 0; k < predicates.length; k++) {\n if (k === i || k === j) continue;\n const raw = getRaw(market, predicates[k].fieldKey);\n if (raw === null || raw === undefined) {\n hasData = false;\n break;\n }\n if (!predicates[k].test(market)) {\n passesRemaining = false;\n break;\n }\n }\n if (hasData && passesRemaining) {\n pls.push(trade.pl);\n }\n }\n ablationPairs[pairKey] = computeSliceStats(pls);\n }\n }\n }\n\n // Profile update hints\n const profileUpdateHints: {\n field: string;\n action: \"remove\" | \"adjust\";\n reason: string;\n }[] = [];\n\n // Check per-filter: if entered performs worse than filtered_out, suggest removal\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (\n entered.tradeCount >= minTrades &&\n filtered_out.tradeCount >= minTrades\n ) {\n if (entered.avgPl < filtered_out.avgPl && filtered_out.avgPl > 0) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Entered avg P&L ($${entered.avgPl.toFixed(2)}) worse than filtered-out ($${filtered_out.avgPl.toFixed(2)}) — filter may be counterproductive`,\n });\n }\n }\n }\n\n // Check ablation: if removing a filter improves over baseline\n for (const [filterDesc, stats] of Object.entries(ablationSingle)) {\n if (stats.tradeCount >= minTrades && baseline.tradeCount >= minTrades) {\n if (stats.avgPl > baseline.avgPl && stats.winRate > baseline.winRate) {\n profileUpdateHints.push({\n field: filterDesc,\n action: \"remove\",\n reason: `Removing this filter improves avg P&L ($${stats.avgPl.toFixed(2)} vs $${baseline.avgPl.toFixed(2)}) and win rate (${stats.winRate.toFixed(1)}% vs ${baseline.winRate.toFixed(1)}%)`,\n });\n }\n }\n }\n\n // Thin-data warnings\n if (baseline.tradeCount > 0 && baseline.tradeCount < minTrades) {\n warnings.push(\n `Baseline (all filters): only ${baseline.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n for (const [filterDesc, { entered, filtered_out }] of Object.entries(perFilter)) {\n if (entered.tradeCount > 0 && entered.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} entered: only ${entered.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n if (filtered_out.tradeCount > 0 && filtered_out.tradeCount < minTrades) {\n warnings.push(\n `${filterDesc} filtered_out: only ${filtered_out.tradeCount} trades (< ${minTrades} threshold)`\n );\n }\n }\n\n // Summary text\n const execNote = executionFilters.length > 0 ? ` (${executionFilters.length} execution filter(s) skipped)` : \"\";\n const summaryText = `Filter validation for '${strategyName}': ${filters.length} market filter(s) analyzed across ${matched.length} trades${execNote}. Baseline (all market filters): ${baseline.tradeCount} trades, win rate ${baseline.winRate.toFixed(1)}%, avg P&L $${baseline.avgPl.toFixed(2)}. ${profileUpdateHints.length} update hint(s).`;\n\n return createToolOutput(summaryText, {\n baseline,\n no_filters: noFiltersStats,\n per_filter: perFilter,\n ablation: {\n single: ablationSingle,\n pairs: ablationPairs,\n },\n execution_filters_skipped: executionFilters.map((f) => f.description || `${f.field} ${f.operator} ${f.value}`),\n profile_update_hints: profileUpdateHints,\n warnings,\n });\n}\n\n// =============================================================================\n// portfolio_structure_map Schema and Handler\n// =============================================================================\n\nexport const portfolioStructureMapSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\"Block ID to analyze. When omitted, aggregate across all blocks.\"),\n minTrades: z\n .number()\n .optional()\n .default(10)\n .describe(\"Thin-data warning threshold (default: 10)\"),\n});\n\nexport async function handlePortfolioStructureMap(\n input: z.infer<typeof portfolioStructureMapSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput> | { content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n const { blockId, minTrades } = portfolioStructureMapSchema.parse(input);\n const conn = await getConnection(baseDir);\n\n // Load profiles\n const profiles = await listProfiles(conn, blockId);\n if (profiles.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: blockId\n ? `No strategy profiles found for block '${blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n },\n ],\n };\n }\n\n // Collect all trades per strategy, matched to market data\n interface StrategyTradeMarket {\n strategyName: string;\n trade: Trade;\n market: Record<string, unknown>;\n }\n\n const allTradeMarkets: StrategyTradeMarket[] = [];\n const warnings: string[] = [];\n\n for (const profile of profiles) {\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(`Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'`);\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback (see loadTradesAndMarket)\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n if (trades.length === 0) {\n warnings.push(`No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'`);\n continue;\n }\n\n // Query market data for trade dates\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const dailyResult = await conn.runAndReadAll(sql, params);\n const dailyRecords = resultToRecords(dailyResult);\n const daily = recordsByTickerDate(dailyRecords);\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const marketKey = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = daily.get(marketKey);\n if (market) {\n allTradeMarkets.push({\n strategyName: profile.strategyName,\n trade,\n market,\n });\n }\n }\n }\n\n if (allTradeMarkets.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No trades could be matched to market data. Ensure market data is imported and enriched.\",\n },\n ],\n };\n }\n\n // Build the 18-cell matrix: Vol_Regime (6) x Trend_Direction (3)\n // Use prev_ prefix for both fields (both are close-derived, need LAG)\n const strategyNames = [...new Set(allTradeMarkets.map((t) => t.strategyName))];\n\n // Collect PLs per cell per strategy\n type CellKey = string; // \"regime:trend\"\n const cellPls = new Map<CellKey, Map<string, number[]>>();\n\n let unknownTrendCount = 0;\n const unknownTrendPls = new Map<string, number[]>(); // strategy -> pls for unknown trend\n\n for (const { strategyName, trade, market } of allTradeMarkets) {\n const volRegime = getNum(market, \"prev_Vol_Regime\");\n const trendRaw = market[\"prev_Trend_Direction\"];\n\n // Handle missing Vol_Regime\n if (isNaN(volRegime) || volRegime < 1 || volRegime > 6) continue;\n\n // Handle missing Trend_Direction\n let trend: TrendLabel | null = null;\n if (\n trendRaw === null ||\n trendRaw === undefined ||\n trendRaw === \"\"\n ) {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n const trendStr = String(trendRaw).toLowerCase();\n if (trendStr === \"up\" || trendStr === \"down\" || trendStr === \"flat\") {\n trend = trendStr as TrendLabel;\n } else {\n unknownTrendCount++;\n if (!unknownTrendPls.has(strategyName)) {\n unknownTrendPls.set(strategyName, []);\n }\n unknownTrendPls.get(strategyName)!.push(trade.pl);\n continue;\n }\n\n const regimeLabel = VOL_REGIME_LABELS[volRegime] || `regime_${volRegime}`;\n const cellKey = `${regimeLabel}:${trend}`;\n\n if (!cellPls.has(cellKey)) {\n cellPls.set(cellKey, new Map());\n }\n const cellMap = cellPls.get(cellKey)!;\n if (!cellMap.has(strategyName)) {\n cellMap.set(strategyName, []);\n }\n cellMap.get(strategyName)!.push(trade.pl);\n }\n\n // Build matrix output\n const matrix: Record<string, Record<string, Record<string, SliceStats>>> = {};\n const overlaps: Array<{\n regime: string;\n trend: string;\n strategies: string[];\n totalTrades: number;\n }> = [];\n const blindSpots: Array<{ regime: string; trend: string }> = [];\n let coveredCells = 0;\n let overlapCells = 0;\n\n for (const [, regimeLabel] of Object.entries(VOL_REGIME_LABELS)) {\n matrix[regimeLabel] = {};\n for (const trend of TREND_LABELS) {\n const cellKey = `${regimeLabel}:${trend}`;\n const cellMap = cellPls.get(cellKey);\n\n if (!cellMap || cellMap.size === 0) {\n blindSpots.push({ regime: regimeLabel, trend });\n matrix[regimeLabel][trend] = {};\n continue;\n }\n\n coveredCells++;\n const cellStats: Record<string, SliceStats> = {};\n const strategiesInCell: string[] = [];\n let totalTradesInCell = 0;\n\n for (const [stratName, pls] of cellMap) {\n cellStats[stratName] = computeSliceStats(pls);\n strategiesInCell.push(stratName);\n totalTradesInCell += pls.length;\n\n // Thin-data warning\n if (pls.length > 0 && pls.length < minTrades) {\n warnings.push(\n `Thin data: '${stratName}' has only ${pls.length} trades in ${regimeLabel}/${trend} (threshold: ${minTrades})`\n );\n }\n }\n\n matrix[regimeLabel][trend] = cellStats;\n\n // Overlap detection: 2+ strategies in same cell\n if (strategiesInCell.length >= 2) {\n overlapCells++;\n overlaps.push({\n regime: regimeLabel,\n trend,\n strategies: strategiesInCell,\n totalTrades: totalTradesInCell,\n });\n }\n }\n }\n\n const blindSpotCells = blindSpots.length;\n\n // Handle unknown trend trades\n if (unknownTrendCount > 0) {\n warnings.push(\n `${unknownTrendCount} trades had missing or unknown Trend_Direction. Consider running enrich_market_data to populate Trend_Direction.`\n );\n }\n\n // Build unknown trend stats if any\n const unknownTrendStats: Record<string, SliceStats> | undefined =\n unknownTrendPls.size > 0\n ? Object.fromEntries(\n [...unknownTrendPls.entries()].map(([name, pls]) => [\n name,\n computeSliceStats(pls),\n ])\n )\n : undefined;\n\n const coverageSummary = {\n totalCells: 18,\n coveredCells,\n blindSpotCells,\n overlapCells,\n };\n\n const summary = `Portfolio structure map: ${strategyNames.length} strategies | ${coveredCells}/18 cells covered | ${overlapCells} overlaps | ${blindSpotCells} blind spots`;\n\n const structuredData: Record<string, unknown> = {\n strategies: strategyNames,\n matrix,\n overlaps,\n blind_spots: blindSpots,\n coverage_summary: coverageSummary,\n warnings,\n };\n\n if (unknownTrendStats) {\n structuredData.unknown_trend = unknownTrendStats;\n }\n\n return createToolOutput(summary, structuredData);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error building portfolio structure map: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\n/**\n * Register all profile analysis tools.\n * This includes portfolio_structure_map (from Plan 03) and\n * analyze_structure_fit + validate_entry_filters (from Plan 02, if present).\n */\nexport function registerProfileAnalysisTools(\n server: McpServer,\n baseDir: string\n): void {\n // portfolio_structure_map: optional blockId means we can't always use withSyncedBlock.\n // When blockId is provided, sync that block. When omitted, sync all blocks.\n server.registerTool(\n \"portfolio_structure_map\",\n {\n description:\n \"Build a Vol_Regime x Trend_Direction matrix (18 cells) across all profiled strategies. \" +\n \"Shows per-strategy stats in each cell, detects overlap (2+ strategies in same cell), \" +\n \"blind spots (cells with zero trades), and thin-data warnings. \" +\n \"Optionally filter to a single block or aggregate across all blocks.\",\n inputSchema: portfolioStructureMapSchema,\n },\n async (input) => {\n // Manual sync: if blockId provided, sync just that block; otherwise sync all\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.js\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n\n return handlePortfolioStructureMap(input, baseDir);\n }\n );\n\n // -------------------------------------------------------------------------\n // Tool: analyze_structure_fit\n // -------------------------------------------------------------------------\n server.registerTool(\n \"analyze_structure_fit\",\n {\n description:\n \"Analyze how well a strategy fits various market dimensions using its stored profile. \" +\n \"Returns performance breakdown by Vol_Regime, day-of-week, time-of-day, and profile-derived \" +\n \"dimensions from entry_filters. Includes profile_update_hints when data shows clear patterns \" +\n \"diverging from profile, and thin-data warnings for small buckets.\",\n inputSchema: analyzeStructureFitSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleAnalyzeStructureFit(input, ctx.baseDir);\n })\n );\n\n // -------------------------------------------------------------------------\n // Tool: validate_entry_filters\n // -------------------------------------------------------------------------\n server.registerTool(\n \"validate_entry_filters\",\n {\n description:\n \"Validate effectiveness of a strategy's entry filters. Splits trades into entered vs \" +\n \"filtered-out groups per filter and shows full stat suite for both. Runs ablation study \" +\n \"removing one filter at a time and testing all pairs. Returns profile_update_hints when \" +\n \"filters appear counterproductive.\",\n inputSchema: validateEntryFiltersSchema,\n },\n withSyncedBlock(baseDir, async (input, ctx) => {\n return handleValidateEntryFilters(input, ctx.baseDir);\n })\n );\n}\n","/**\n * Regime Allocation Advisor Tool\n *\n * Cross-references strategy profiles' expected regimes with actual trading\n * performance per regime. Surfaces thesis violations and hidden edges as\n * structured data without prescriptive recommendations.\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { loadBlock } from \"../utils/block-loader.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport type { Trade } from \"@tradeblocks/lib\";\nimport { getConnection } from \"../db/connection.js\";\nimport { listProfiles } from \"../db/profile-schemas.js\";\nimport { filterByStrategy } from \"./shared/filters.js\";\nimport {\n buildLookaheadFreeQuery,\n type MarketLookupKey,\n} from \"../utils/field-timing.js\";\nimport {\n DEFAULT_MARKET_TICKER,\n marketTickerDateKey,\n resolveTradeTicker,\n} from \"../utils/ticker.js\";\nimport { computeSliceStats, type SliceStats } from \"../utils/analysis-stats.js\";\nimport {\n upgradeToReadWrite,\n downgradeToReadOnly,\n getConnectionMode,\n} from \"../db/connection.js\";\nimport { syncAllBlocks } from \"../sync/index.js\";\n\n// =============================================================================\n// Utility Functions (local to this module, copied from profile-analysis.ts)\n// =============================================================================\n\nfunction formatTradeDate(date: Date | string): string {\n if (typeof date === \"string\") {\n const match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (match) return `${match[1]}-${match[2]}-${match[3]}`;\n }\n const d = typeof date === \"string\" ? new Date(date) : date;\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction getTradeLookupKey(trade: Trade): MarketLookupKey {\n return {\n date: formatTradeDate(trade.dateOpened),\n ticker: resolveTradeTicker(trade, DEFAULT_MARKET_TICKER),\n };\n}\n\nfunction uniqueTradeLookupKeys(trades: Trade[]): MarketLookupKey[] {\n const byKey = new Map<string, MarketLookupKey>();\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n byKey.set(marketTickerDateKey(lookup.ticker, lookup.date), lookup);\n }\n return Array.from(byKey.values());\n}\n\nfunction resultToRecords(result: {\n columnCount: number;\n columnName(i: number): string;\n getRows(): Iterable<unknown[]>;\n}): Record<string, unknown>[] {\n const columnCount = result.columnCount;\n const colNames: string[] = [];\n for (let i = 0; i < columnCount; i++) {\n colNames.push(result.columnName(i));\n }\n const records: Record<string, unknown>[] = [];\n for (const row of result.getRows()) {\n const record: Record<string, unknown> = {};\n for (let i = 0; i < columnCount; i++) {\n const val = row[i];\n record[colNames[i]] = typeof val === \"bigint\" ? Number(val) : val;\n }\n records.push(record);\n }\n return records;\n}\n\nfunction recordsByTickerDate(\n records: Record<string, unknown>[]\n): Map<string, Record<string, unknown>> {\n const mapped = new Map<string, Record<string, unknown>>();\n for (const record of records) {\n const date = String(record[\"date\"] || \"\");\n const ticker = String(record[\"ticker\"] || DEFAULT_MARKET_TICKER);\n mapped.set(marketTickerDateKey(ticker, date), record);\n }\n return mapped;\n}\n\nfunction getNum(record: Record<string, unknown>, field: string): number {\n const val = record[field];\n if (val === null || val === undefined) return NaN;\n if (typeof val === \"bigint\") return Number(val);\n return val as number;\n}\n\nconst VOL_REGIME_LABELS: Record<number, string> = {\n 1: \"very_low\",\n 2: \"low\",\n 3: \"below_avg\",\n 4: \"above_avg\",\n 5: \"high\",\n 6: \"extreme\",\n};\n\n// =============================================================================\n// Types\n// =============================================================================\n\ninterface RegimeCell {\n stats: SliceStats;\n isExpected: boolean;\n classification: \"thesis_aligned\" | \"thesis_violation\" | \"hidden_edge\" | \"neutral\";\n}\n\ninterface StrategyRegimeComparison {\n strategyName: string;\n blockId: string;\n structureType: string;\n underlying?: string;\n allocationPct?: number;\n expectedRegimes: string[];\n regimePerformance: Record<string, RegimeCell>;\n tradeCount: number;\n matchedToMarket: number;\n unmatchedCount: number;\n}\n\n// =============================================================================\n// Schema\n// =============================================================================\n\nexport const regimeAllocationAdvisorSchema = z.object({\n blockId: z\n .string()\n .optional()\n .describe(\n \"Block ID to analyze. When omitted, aggregate across all profiled strategies.\"\n ),\n minTrades: z\n .number()\n .optional()\n .default(5)\n .describe(\n \"Minimum trades per regime cell for reliable stats (default: 5)\"\n ),\n});\n\n// =============================================================================\n// Handler\n// =============================================================================\n\nexport async function handleRegimeAllocationAdvisor(\n input: z.infer<typeof regimeAllocationAdvisorSchema>,\n baseDir: string\n): Promise<ReturnType<typeof createToolOutput>> {\n const minTrades = input.minTrades ?? 5;\n const warnings: string[] = [];\n const profileUpgradeHints: string[] = [];\n\n // Load all profiles, optionally filtered by blockId\n const conn = await getConnection(baseDir);\n const profiles = await listProfiles(conn, input.blockId);\n\n if (profiles.length === 0) {\n return createToolOutput(\n input.blockId\n ? `No strategy profiles found for block '${input.blockId}'. Use profile_strategy to create profiles first.`\n : \"No strategy profiles found. Use profile_strategy to create profiles first.\",\n { error: \"no_profiles\" }\n );\n }\n\n const strategies: StrategyRegimeComparison[] = [];\n let skippedNoRegimes = 0;\n let skippedNoMarket = 0;\n const allThesisViolations: {\n strategyName: string;\n regime: string;\n winRate: number;\n expectedWinRate: number;\n }[] = [];\n const allHiddenEdges: {\n strategyName: string;\n regime: string;\n winRate: number;\n overallWinRate: number;\n }[] = [];\n\n // Per-regime aggregation across all strategies\n const regimeAggPls: Record<string, number[]> = {};\n\n for (const profile of profiles) {\n try {\n // Skip profiles without expectedRegimes\n if (!profile.expectedRegimes || profile.expectedRegimes.length === 0) {\n skippedNoRegimes++;\n profileUpgradeHints.push(\n `Strategy '${profile.strategyName}' (block: ${profile.blockId}) has no expectedRegimes. Add via profile_strategy.`\n );\n continue;\n }\n\n // Load trades\n let block;\n try {\n block = await loadBlock(baseDir, profile.blockId);\n } catch {\n warnings.push(\n `Could not load block '${profile.blockId}' for strategy '${profile.strategyName}'. Skipped.`\n );\n continue;\n }\n\n let trades = filterByStrategy(block.trades, profile.strategyName);\n // Single-strategy block fallback\n if (trades.length === 0 && block.trades.length > 0) {\n const uniqueStrategies = new Set(block.trades.map((t) => t.strategy));\n if (uniqueStrategies.size === 1) {\n trades = block.trades;\n }\n }\n\n if (trades.length === 0) {\n warnings.push(\n `No trades found for strategy '${profile.strategyName}' in block '${profile.blockId}'. Skipped.`\n );\n continue;\n }\n\n // Query market data\n const tradeKeys = uniqueTradeLookupKeys(trades);\n const { sql, params } = buildLookaheadFreeQuery(tradeKeys);\n const result = await conn.runAndReadAll(sql, params);\n const marketRecords = resultToRecords(result);\n const marketMap = recordsByTickerDate(marketRecords);\n\n // Match trades to market records\n interface TradeWithMarket {\n trade: Trade;\n market: Record<string, unknown>;\n }\n const matched: TradeWithMarket[] = [];\n let unmatchedCount = 0;\n\n for (const trade of trades) {\n const lookup = getTradeLookupKey(trade);\n const key = marketTickerDateKey(lookup.ticker, lookup.date);\n const market = marketMap.get(key);\n if (market) {\n matched.push({ trade, market });\n } else {\n unmatchedCount++;\n }\n }\n\n if (matched.length === 0) {\n skippedNoMarket++;\n warnings.push(\n `No market data matched for strategy '${profile.strategyName}' (${trades.length} trades). Import and enrich market data first.`\n );\n continue;\n }\n\n if (unmatchedCount > 0) {\n warnings.push(\n `Strategy '${profile.strategyName}': ${unmatchedCount} of ${trades.length} trades had no market data match.`\n );\n }\n\n // Compute overall stats for this strategy\n const allPls = matched.map((m) => m.trade.pl);\n const overallStats = computeSliceStats(allPls);\n\n // Group trades by Vol_Regime\n const regimePls: Record<string, number[]> = {};\n for (const { trade, market } of matched) {\n const val = getNum(market, \"prev_Vol_Regime\");\n if (isNaN(val) || val < 1 || val > 6) continue;\n const label = VOL_REGIME_LABELS[val] || `regime_${val}`;\n if (!regimePls[label]) regimePls[label] = [];\n regimePls[label].push(trade.pl);\n\n // Aggregate across strategies\n if (!regimeAggPls[label]) regimeAggPls[label] = [];\n regimeAggPls[label].push(trade.pl);\n }\n\n // Build per-regime comparison\n const expectedSet = new Set(\n profile.expectedRegimes.map((r) => r.toLowerCase())\n );\n const regimePerformance: Record<string, RegimeCell> = {};\n\n for (const [label, pls] of Object.entries(regimePls)) {\n const stats = computeSliceStats(pls);\n const isExpected = expectedSet.has(label.toLowerCase());\n\n // Classification logic:\n // thesis_aligned: isExpected AND performing reasonably (WR > 50% or > overall WR)\n // thesis_violation: isExpected AND WR significantly below overall (>10pp)\n // hidden_edge: NOT isExpected AND WR significantly above overall (>10pp) AND enough trades\n // neutral: everything else\n let classification: RegimeCell[\"classification\"];\n const wrDelta = stats.winRate - overallStats.winRate;\n\n if (isExpected) {\n if (wrDelta < -10) {\n classification = \"thesis_violation\";\n allThesisViolations.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n expectedWinRate: overallStats.winRate,\n });\n } else {\n classification = \"thesis_aligned\";\n }\n } else {\n if (wrDelta > 10 && stats.tradeCount >= minTrades) {\n classification = \"hidden_edge\";\n allHiddenEdges.push({\n strategyName: profile.strategyName,\n regime: label,\n winRate: stats.winRate,\n overallWinRate: overallStats.winRate,\n });\n } else {\n classification = \"neutral\";\n }\n }\n\n regimePerformance[label] = { stats, isExpected, classification };\n }\n\n // Allocation from position sizing\n const allocationPct =\n profile.positionSizing?.liveAllocationPct ??\n profile.positionSizing?.allocationPct;\n\n strategies.push({\n strategyName: profile.strategyName,\n blockId: profile.blockId,\n structureType: profile.structureType,\n underlying: profile.underlying ?? undefined,\n allocationPct,\n expectedRegimes: profile.expectedRegimes,\n regimePerformance,\n tradeCount: trades.length,\n matchedToMarket: matched.length,\n unmatchedCount,\n });\n } catch (err) {\n warnings.push(\n `Error processing strategy '${profile.strategyName}' (block: ${profile.blockId}): ${(err as Error).message}`\n );\n }\n }\n\n // Build regime overview (aggregate stats per regime)\n const regimeOverview: Record<\n string,\n { strategiesActive: number; totalTrades: number; combinedStats: SliceStats }\n > = {};\n\n for (const [label, pls] of Object.entries(regimeAggPls)) {\n // Count how many strategies had trades in this regime\n let strategiesActive = 0;\n for (const strategy of strategies) {\n if (strategy.regimePerformance[label]) {\n strategiesActive++;\n }\n }\n regimeOverview[label] = {\n strategiesActive,\n totalTrades: pls.length,\n combinedStats: computeSliceStats(pls),\n };\n }\n\n const summary = {\n totalStrategies: profiles.length,\n profiled: strategies.length,\n skippedNoRegimes,\n skippedNoMarket,\n thesisViolations: allThesisViolations,\n hiddenEdges: allHiddenEdges,\n };\n\n const summaryText =\n `Regime allocation advisor: ${strategies.length}/${profiles.length} strategies analyzed. ` +\n `${allThesisViolations.length} thesis violation(s), ${allHiddenEdges.length} hidden edge(s). ` +\n (skippedNoRegimes > 0\n ? `${skippedNoRegimes} skipped (no expectedRegimes). `\n : \"\") +\n (skippedNoMarket > 0\n ? `${skippedNoMarket} skipped (no market data). `\n : \"\");\n\n return createToolOutput(summaryText, {\n strategies,\n summary,\n regimeOverview,\n warnings,\n profileUpgradeHints,\n });\n}\n\n// =============================================================================\n// Registration\n// =============================================================================\n\nexport function registerRegimeAdvisorTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"regime_allocation_advisor\",\n {\n description:\n \"Cross-reference strategy profiles' expected regimes with actual trading performance. \" +\n \"Shows per-strategy, per-regime comparison with win rate, P&L, and trade count. \" +\n \"Classifications (thesis_aligned, thesis_violation, hidden_edge) emerge from data delta. \" +\n \"Optionally filter to a single block or aggregate across all profiled strategies.\",\n inputSchema: regimeAllocationAdvisorSchema,\n },\n async (input) => {\n // Manual sync pattern (same as portfolio_structure_map)\n await upgradeToReadWrite(baseDir, { fallbackToReadOnly: true });\n if (getConnectionMode() === \"read_write\") {\n try {\n if (input.blockId) {\n const { syncBlock } = await import(\"../sync/index.js\");\n await syncBlock(input.blockId, baseDir);\n } else {\n await syncAllBlocks(baseDir);\n }\n } finally {\n await downgradeToReadOnly(baseDir);\n }\n }\n return handleRegimeAllocationAdvisor(input, baseDir);\n }\n );\n}\n","/**\n * Trade Replay Pure Logic Module\n *\n * OCC ticker construction, tradelog legs string parsing, multi-leg P&L path\n * computation with HL2 mark pricing, and MFE/MAE calculation.\n *\n * All functions are pure — no fetch, no DuckDB.\n */\n\nimport type { BarRow } from './market-provider.js';\nimport { computeLegGreeks, type GreeksResult } from './black-scholes.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A parsed leg from a tradelog \"legs\" string (before OCC ticker resolution). */\nexport interface ParsedLeg {\n root: string; // \"SPY\", \"SPX\", \"SPXW\"\n strike: number; // Numeric strike price\n type: 'C' | 'P'; // Call or Put\n quantity: number; // +1 or -1 (direction derived from position in spread)\n}\n\n/** A fully resolved leg ready for replay (after OCC ticker construction). */\nexport interface ReplayLeg {\n occTicker: string; // Full OCC ticker for Massive API fetch\n quantity: number; // Positive = long, negative = short\n entryPrice: number; // Per-contract entry price\n multiplier: number; // 100 for standard equity/index options\n}\n\n/** A single point on the strategy P&L path. */\nexport interface PnlPoint {\n timestamp: string; // \"YYYY-MM-DD HH:MM\" ET\n strategyPnl: number; // Combined P&L across all legs at this minute\n legPrices: number[]; // Mark price for each leg at this minute (bid/ask mid or HL2 fallback)\n // Per-leg greeks (Phase 69) — array parallel to legPrices\n legGreeks?: GreeksResult[];\n // Net position greeks — quantity-weighted aggregation across legs\n netDelta?: number | null;\n netGamma?: number | null;\n netTheta?: number | null;\n netVega?: number | null;\n // IVP from market.context\n ivp?: number | null;\n}\n\n/** Configuration for greeks computation in P&L path. */\nexport interface GreeksConfig {\n underlyingPrices: Map<string, number>; // timestamp -> underlying price\n legs: Array<{ strike: number; type: 'C' | 'P'; expiryDate: string }>; // per-leg BS inputs\n riskFreeRate: number; // e.g. 0.045\n dividendYield: number; // e.g. 0.015 for SPX, 0 otherwise\n ivpByDate?: Map<string, number>; // date -> IVP value\n /** Sorted intraday timestamps from underlyingPrices for nearest-timestamp binary search. */\n sortedTimestamps?: string[];\n}\n\n/** Complete replay result with P&L path, MFE/MAE, and metadata. */\nexport interface ReplayResult {\n pnlPath: PnlPoint[];\n mfe: number; // Max of strategyPnl series\n mae: number; // Min of strategyPnl series\n mfeTimestamp: string; // When MFE occurred\n maeTimestamp: string; // When MAE occurred\n totalPnl: number; // Final P&L at last bar\n totalBars?: number; // Total minute bars before format filtering\n legs: ReplayLeg[]; // The legs that were replayed\n greeksWarning?: string | null; // D-12: warning when >50% of leg-timestamps have null greeks\n}\n\n// ---------------------------------------------------------------------------\n// markPrice\n// ---------------------------------------------------------------------------\n\n/**\n * Prefer (bid+ask)/2 when both are present and non-zero; fall back to HL2.\n *\n * When providers supply bid/ask data (e.g., option chains), the midpoint is a\n * more accurate mark price than HL2. This is opt-in — existing data without\n * bid/ask continues to use HL2 identically.\n */\nexport function markPrice(bar: Pick<BarRow, 'high' | 'low' | 'bid' | 'ask'>): number {\n if (bar.bid != null && bar.ask != null && (bar.bid > 0 || bar.ask > 0)) {\n return (bar.bid + bar.ask) / 2;\n }\n return (bar.high + bar.low) / 2;\n}\n\n// ---------------------------------------------------------------------------\n// findNearestTimestamp\n// ---------------------------------------------------------------------------\n\n/**\n * Find the nearest timestamp in a sorted array within tolerance (seconds).\n * Uses binary search for O(log n) performance.\n *\n * Timestamps are compared by minutes-since-midnight (HH:MM format).\n * Returns undefined if no timestamp is within the tolerance.\n *\n * Per D-07/D-08: Tolerates up to 60s mismatch between option and underlying bars.\n */\nexport function findNearestTimestamp(\n sortedTimestamps: string[],\n target: string,\n toleranceSec: number = 60,\n): string | undefined {\n if (sortedTimestamps.length === 0) return undefined;\n\n const targetMin = timestampToMinutes(target);\n if (targetMin === null) return undefined;\n\n let lo = 0, hi = sortedTimestamps.length - 1;\n let bestIdx = 0;\n let bestDiff = Infinity;\n\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n const midMin = timestampToMinutes(sortedTimestamps[mid]);\n if (midMin === null) { lo = mid + 1; continue; }\n\n const diff = Math.abs(midMin - targetMin);\n if (diff < bestDiff) { bestDiff = diff; bestIdx = mid; }\n if (midMin < targetMin) lo = mid + 1;\n else if (midMin > targetMin) hi = mid - 1;\n else break; // exact match\n }\n\n // bestDiff is in minutes; convert tolerance from seconds\n return bestDiff <= toleranceSec / 60 ? sortedTimestamps[bestIdx] : undefined;\n}\n\nfunction timestampToMinutes(ts: string): number | null {\n const timePart = ts.split(' ')[1];\n if (!timePart) return null;\n const [h, m] = timePart.split(':').map(Number);\n if (isNaN(h) || isNaN(m)) return null;\n return h * 60 + m;\n}\n\n// ---------------------------------------------------------------------------\n// parseLegsString\n// ---------------------------------------------------------------------------\n\n// Compact format with root: \"SPY 470C\", \"SPX 4500P\", \"SPY 0.50C\"\nconst COMPACT_LEG_RE = /^([A-Z]+)\\s+(\\d+(?:\\.\\d+)?)\\s*(C|P)$/i;\n\n// Compact format without root (subsequent legs in spreads): \"465C\", \"500C\"\nconst COMPACT_NO_ROOT_RE = /^(\\d+(?:\\.\\d+)?)\\s*(C|P)$/i;\n\n// Verbose format: \"SPY Jan25 470 Call\", \"SPX Feb25 4500 Put\"\nconst VERBOSE_LEG_RE = /^([A-Z]+)\\s+\\w+\\s+(\\d+(?:\\.\\d+)?)\\s+(Call|Put)$/i;\n\n// Option Omega format: \"{contracts} {Mon} {day} {strike} {P|C} {STO|BTO} {price}\"\n// Example: \"397 Mar 12 6610 P STO 35.85\"\n// Captures: (1)contracts (2)month (3)day (4)strike (5)C|P (6)STO|BTO (7)price\nconst OO_LEG_RE = /^(\\d+)\\s+(\\w+)\\s+(\\d+)\\s+(\\d+(?:\\.\\d+)?)\\s+(C|P)\\s+(STO|BTO|STC|BTC)\\s+(\\d+(?:\\.\\d+)?)$/i;\n\n/** Extended parsed leg with entry price from OO format. */\nexport interface ParsedLegOO extends ParsedLeg {\n entryPrice?: number; // Fill price from OO leg (e.g., 35.85)\n contracts?: number; // Contract count from OO leg\n expiryHint?: string; // \"Mon DD\" from OO format (e.g., \"Mar 12\") for multi-expiry strategies\n}\n\n/**\n * Parse a tradelog \"legs\" string into structured ParsedLeg objects.\n *\n * Supported formats:\n * - \"SPY 470C\" (single leg)\n * - \"SPY 470C/465C\" (two-leg spread, \"/\" delimiter)\n * - \"SPY 490C/500C/510C\" (butterfly)\n * - \"SPY Jan25 470 Call\" (verbose format)\n * - Option Omega pipe-delimited format:\n * \"397 Mar 12 6610 P STO 35.85 | 397 Mar 12 6925 C STO 10.90 | ...\"\n * Direction: STO = short (-1), BTO = long (+1)\n * Includes per-leg entry price and contract count\n *\n * @throws Error if legs string is empty or cannot be parsed\n */\nexport function parseLegsString(legsStr: string): ParsedLegOO[] {\n if (!legsStr || legsStr.trim() === '') {\n throw new Error('Cannot parse legs \"\" — use hypothetical mode with explicit strikes');\n }\n\n // Detect Option Omega pipe-delimited format\n if (legsStr.includes('|')) {\n return parseOOLegs(legsStr);\n }\n\n const parts = legsStr.includes('/') ? legsStr.split('/') : [legsStr];\n const legs: ParsedLegOO[] = [];\n let inheritedRoot = '';\n\n for (let i = 0; i < parts.length; i++) {\n const raw = parts[i].trim();\n let root: string;\n let strike: number;\n let type: 'C' | 'P';\n\n const compactMatch = raw.match(COMPACT_LEG_RE);\n if (compactMatch) {\n root = compactMatch[1].toUpperCase();\n strike = parseFloat(compactMatch[2]);\n type = compactMatch[3].toUpperCase() as 'C' | 'P';\n } else {\n // Try compact without root (e.g., \"465C\" in \"SPY 470C/465C\")\n const noRootMatch = raw.match(COMPACT_NO_ROOT_RE);\n if (noRootMatch && inheritedRoot) {\n root = inheritedRoot;\n strike = parseFloat(noRootMatch[1]);\n type = noRootMatch[2].toUpperCase() as 'C' | 'P';\n } else {\n const verboseMatch = raw.match(VERBOSE_LEG_RE);\n if (verboseMatch) {\n root = verboseMatch[1].toUpperCase();\n strike = parseFloat(verboseMatch[2]);\n type = verboseMatch[3].toLowerCase() === 'call' ? 'C' : 'P';\n } else {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" — use hypothetical mode with explicit strikes`\n );\n }\n }\n }\n\n // Propagate root to subsequent legs that may omit it\n if (i === 0) inheritedRoot = root;\n\n // First leg is bought (+1), subsequent alternate -1, +1, -1...\n const quantity = i === 0 ? 1 : (i % 2 === 0 ? 1 : -1);\n\n legs.push({ root, strike, type, quantity });\n }\n\n return legs;\n}\n\n/**\n * Parse Option Omega pipe-delimited legs format.\n *\n * Each segment: \"{contracts} {Mon} {day} {strike} {P|C} {STO|BTO} {price}\"\n * STO = sell-to-open (short, quantity = -1), BTO = buy-to-open (long, quantity = +1)\n *\n * Dedup key includes date+strike+type to handle:\n * - Calendar spreads: same strike, different expiry (both kept)\n * - Open+close fills: same strike, same date, opposite direction (close dropped)\n */\nfunction parseOOLegs(legsStr: string): ParsedLegOO[] {\n const segments = legsStr.split('|').map(s => s.trim());\n const legs: ParsedLegOO[] = [];\n const seen = new Set<string>();\n\n for (const seg of segments) {\n const match = seg.match(OO_LEG_RE);\n if (!match) {\n throw new Error(\n `Cannot parse OO leg segment \"${seg}\" — use hypothetical mode with explicit strikes`\n );\n }\n\n const contracts = parseInt(match[1], 10);\n const month = match[2];\n const day = match[3];\n const strike = parseFloat(match[4]);\n const type = match[5].toUpperCase() as 'C' | 'P';\n const direction = match[6].toUpperCase();\n const price = parseFloat(match[7]);\n\n // Dedup by date+strike+type: keeps calendar legs (different dates),\n // drops close fills (same date+strike+type, opposite direction)\n const key = `${month}${day}:${strike}${type}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n legs.push({\n root: '', // OO format doesn't include root — caller provides via trade's ticker field\n strike,\n type,\n quantity: direction === 'BTO' ? 1 : -1,\n entryPrice: price,\n contracts,\n expiryHint: `${month} ${day}`,\n });\n }\n\n return legs;\n}\n\n// ---------------------------------------------------------------------------\n// buildOccTicker\n// ---------------------------------------------------------------------------\n\n/**\n * Build an OCC-format option ticker from components.\n *\n * Format: {root}{YYMMDD}{C|P}{strike*1000 padded to 8 digits}\n *\n * Example: SPY, 2025-01-17, C, 470 -> \"SPY250117C00470000\"\n */\nexport function buildOccTicker(\n root: string,\n expiry: string,\n type: 'C' | 'P',\n strike: number,\n): string {\n // Extract YYMMDD from \"YYYY-MM-DD\"\n const [yyyy, mm, dd] = expiry.split('-');\n const yy = yyyy.slice(2);\n\n // Strike * 1000 padded to 8 digits\n const strikeInt = Math.round(strike * 1000);\n const strikePadded = String(strikeInt).padStart(8, '0');\n\n return `${root}${yy}${mm}${dd}${type}${strikePadded}`;\n}\n\n// ---------------------------------------------------------------------------\n// computeStrategyPnlPath\n// ---------------------------------------------------------------------------\n\n/**\n * Combine per-leg minute bars into a single strategy P&L path.\n *\n * Mark price at each minute = (bid+ask)/2 when available, else HL2 = (high + low) / 2.\n * Combined P&L = sum across legs of (currentMark - entryPrice) * quantity * multiplier.\n *\n * Only includes timestamps where ALL legs have a bar.\n * Returns empty array if any leg has no bars.\n */\nexport function computeStrategyPnlPath(\n legs: ReplayLeg[],\n barsByLeg: BarRow[][],\n greeksConfig?: GreeksConfig,\n): PnlPoint[] {\n if (legs.length === 0 || barsByLeg.length === 0) return [];\n\n // Check if any leg has no bars\n for (const bars of barsByLeg) {\n if (bars.length === 0) return [];\n }\n\n // Build maps of timestamp -> bar for each leg\n const legMaps: Map<string, BarRow>[] = barsByLeg.map((bars) => {\n const map = new Map<string, BarRow>();\n for (const bar of bars) {\n const ts = `${bar.date} ${bar.time ?? ''}`.trim();\n map.set(ts, bar);\n }\n return map;\n });\n\n // Collect ALL unique timestamps across ALL legs (union, not intersection)\n const allTimestamps = new Set<string>();\n for (const bars of barsByLeg) {\n for (const bar of bars) {\n allTimestamps.add(`${bar.date} ${bar.time ?? ''}`.trim());\n }\n }\n const sortedTimestamps = [...allTimestamps].sort();\n\n // Build P&L path with forward-fill for missing bars\n const path: PnlPoint[] = [];\n const lastBar: (BarRow | undefined)[] = new Array(legs.length).fill(undefined);\n\n\n for (const ts of sortedTimestamps) {\n let complete = true;\n const legPrices: number[] = [];\n let strategyPnl = 0;\n\n for (let i = 0; i < legs.length; i++) {\n const bar = legMaps[i].get(ts);\n if (bar) {\n lastBar[i] = bar;\n }\n const effective = bar ?? lastBar[i];\n if (!effective) {\n complete = false;\n break;\n }\n const hl2 = markPrice(effective);\n legPrices.push(hl2);\n strategyPnl += (hl2 - legs[i].entryPrice) * legs[i].quantity * legs[i].multiplier;\n }\n\n if (complete) {\n const point: PnlPoint = { timestamp: ts, strategyPnl, legPrices };\n\n // Compute greeks if config provided\n if (greeksConfig) {\n // Look up underlying price — try exact timestamp, then nearest within 60s, then date-only\n let underlyingPrice = greeksConfig.underlyingPrices.get(ts);\n if (underlyingPrice === undefined && greeksConfig.sortedTimestamps) {\n const nearest = findNearestTimestamp(greeksConfig.sortedTimestamps, ts, 60);\n if (nearest) underlyingPrice = greeksConfig.underlyingPrices.get(nearest);\n }\n if (underlyingPrice === undefined) {\n const dateOnly = ts.split(' ')[0];\n underlyingPrice = greeksConfig.underlyingPrices.get(dateOnly);\n }\n\n if (underlyingPrice !== undefined) {\n const legGreeksArr: GreeksResult[] = [];\n let netDelta = 0, netGamma = 0, netTheta = 0, netVega = 0;\n let allNull = true;\n\n for (let j = 0; j < legs.length; j++) {\n const legCfg = greeksConfig.legs[j];\n if (!legCfg || !legCfg.expiryDate) {\n legGreeksArr.push({ delta: null, gamma: null, theta: null, vega: null, iv: null });\n continue;\n }\n\n // Compute fractional DTE from bar timestamp to leg expiry\n const dateStr = ts.split(' ')[0];\n const timePart = ts.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = legCfg.expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n const dte = (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n\n if (dte <= 0) {\n legGreeksArr.push({ delta: null, gamma: null, theta: null, vega: null, iv: null });\n continue;\n }\n\n const g = computeLegGreeks(\n legPrices[j],\n underlyingPrice,\n legCfg.strike,\n dte,\n legCfg.type,\n greeksConfig.riskFreeRate,\n greeksConfig.dividendYield,\n );\n legGreeksArr.push(g);\n\n if (g.delta !== null) {\n allNull = false;\n const weight = legs[j].quantity * legs[j].multiplier / 100;\n netDelta += g.delta * weight;\n netGamma += g.gamma! * weight;\n netTheta += g.theta! * weight;\n netVega += g.vega! * weight;\n }\n }\n\n point.legGreeks = legGreeksArr;\n point.netDelta = allNull ? null : netDelta;\n point.netGamma = allNull ? null : netGamma;\n point.netTheta = allNull ? null : netTheta;\n point.netVega = allNull ? null : netVega;\n\n\n // IVP lookup by date\n const ivpDate = ts.split(' ')[0];\n point.ivp = greeksConfig.ivpByDate?.get(ivpDate) ?? null;\n }\n }\n\n path.push(point);\n }\n }\n\n return path;\n}\n\n// ---------------------------------------------------------------------------\n// computeReplayMfeMae\n// ---------------------------------------------------------------------------\n\n/**\n * Compute MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion)\n * from a P&L path.\n *\n * MFE = max of strategyPnl series\n * MAE = min of strategyPnl series\n */\nexport function computeReplayMfeMae(pnlPath: PnlPoint[]): {\n mfe: number;\n mae: number;\n mfeTimestamp: string;\n maeTimestamp: string;\n} {\n if (pnlPath.length === 0) {\n return { mfe: 0, mae: 0, mfeTimestamp: '', maeTimestamp: '' };\n }\n\n let mfe = pnlPath[0].strategyPnl;\n let mae = pnlPath[0].strategyPnl;\n let mfeTimestamp = pnlPath[0].timestamp;\n let maeTimestamp = pnlPath[0].timestamp;\n\n for (let i = 1; i < pnlPath.length; i++) {\n const pnl = pnlPath[i].strategyPnl;\n if (pnl > mfe) {\n mfe = pnl;\n mfeTimestamp = pnlPath[i].timestamp;\n }\n if (pnl < mae) {\n mae = pnl;\n maeTimestamp = pnlPath[i].timestamp;\n }\n }\n\n return { mfe, mae, mfeTimestamp, maeTimestamp };\n}\n","/**\n * Trade Replay Tools\n *\n * MCP tool for replaying trades using historical minute-level option bars\n * from Massive.com. Supports two modes:\n * A) Hypothetical replay — explicit legs with strikes/expiry/dates\n * B) Tradelog replay — block_id + trade_index to replay from existing trade data\n *\n * Tools registered:\n * - replay_trade — Replay a trade and compute minute-by-minute P&L path with MFE/MAE\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { fetchBarsWithCache } from \"../utils/bar-cache.js\";\nimport {\n parseLegsString,\n buildOccTicker,\n computeStrategyPnlPath,\n computeReplayMfeMae,\n markPrice,\n type ReplayLeg,\n type ReplayResult,\n type GreeksConfig,\n} from \"../utils/trade-replay.js\";\nimport type { BarRow } from \"../utils/market-provider.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const replayTradeSchema = z.object({\n // Mode A: Hypothetical / explicit legs\n legs: z\n .array(\n z.object({\n ticker: z.string().describe(\"Underlying ticker, e.g., 'SPY', 'SPX'\"),\n strike: z.number().describe(\"Strike price\"),\n type: z.enum([\"C\", \"P\"]).describe(\"Call or Put\"),\n expiry: z.string().describe(\"Expiration date YYYY-MM-DD\"),\n quantity: z.number().describe(\"Positive = long, negative = short\"),\n entry_price: z\n .number()\n .describe(\"Per-contract entry price (premium paid/received)\"),\n })\n )\n .optional()\n .describe(\"Explicit leg definitions for hypothetical replay\"),\n\n // Mode B: Tradelog replay\n block_id: z.string().optional().describe(\"Block ID to load trade from\"),\n trade_index: z\n .number()\n .optional()\n .describe(\n \"0-based index of trade in block's tradelog (ordered by date_opened)\"\n ),\n\n // Common fields\n open_date: z\n .string()\n .optional()\n .describe(\n \"Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)\"\n ),\n close_date: z\n .string()\n .optional()\n .describe(\n \"Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)\"\n ),\n multiplier: z\n .number()\n .default(100)\n .describe(\"Contract multiplier (default 100 for standard options)\"),\n format: z\n .enum([\"full\", \"summary\", \"sampled\"])\n .default(\"sampled\")\n .describe(\n \"Output format: 'sampled' returns path sampled at ~15min intervals (default), \" +\n \"'full' returns complete minute-by-minute P&L path, \" +\n \"'summary' returns MFE/MAE/P&L without minute-level path\"\n ),\n close_at: z\n .enum([\"trade\", \"expiry\"])\n .default(\"trade\")\n .describe(\n \"When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, \" +\n \"'expiry' shows full path through option expiry. Only applies to tradelog mode.\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst MONTH_MAP: Record<string, string> = {\n Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',\n Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',\n};\n\n/** Convert OO expiry hint \"Mar 13\" + year \"2026\" → \"2026-03-13\" */\nfunction resolveOOExpiryHint(hint: string, year: string): string {\n const [mon, day] = hint.split(' ');\n const mm = MONTH_MAP[mon] ?? '01';\n const dd = day.padStart(2, '0');\n return `${year}-${mm}-${dd}`;\n}\n\n/**\n * Derive fetch date range from OO leg expiryHints.\n *\n * For calendar spreads (different expiries): min(expiry)→max(expiry).\n * For single-expiry trades: tradeOpenDate→expiry.\n * Returns null if no legs have expiryHint (caller falls back to trade dates).\n */\nexport function resolveOODateRange(\n parsedLegs: import(\"../utils/trade-replay.js\").ParsedLegOO[],\n tradeYear: string,\n tradeOpenDate: string,\n): { from: string; to: string } | null {\n const hints = parsedLegs\n .filter(l => l.expiryHint)\n .map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));\n\n if (hints.length === 0) return null;\n\n const sorted = [...hints].sort();\n const minDate = sorted[0];\n const maxDate = sorted[sorted.length - 1];\n\n if (minDate === maxDate) {\n // Single expiry — fetch from trade open to expiry\n return { from: tradeOpenDate, to: maxDate };\n }\n // Calendar spread — near-term to far-term expiry\n return { from: minDate, to: maxDate };\n}\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleReplayTrade(\n params: z.infer<typeof replayTradeSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection\n): Promise<ReplayResult> {\n const { legs: inputLegs, block_id, trade_index, multiplier, close_at } = params;\n let { open_date, close_date } = params;\n let tradeCloseTimestamp: string | undefined; // \"YYYY-MM-DD HH:MM\" when trade actually closed\n\n let replayLegs: ReplayLeg[];\n\n if (inputLegs && inputLegs.length > 0) {\n // ----- Mode A: Hypothetical replay -----\n if (!open_date || !close_date) {\n throw new Error(\n \"open_date and close_date are required for hypothetical replay mode\"\n );\n }\n\n replayLegs = inputLegs.map((leg) => ({\n occTicker: buildOccTicker(leg.ticker, leg.expiry, leg.type, leg.strike),\n quantity: leg.quantity,\n entryPrice: leg.entry_price,\n multiplier,\n }));\n } else if (block_id !== undefined && trade_index !== undefined) {\n // ----- Mode B: Tradelog replay -----\n const conn = injectedConn ?? await getConnection(baseDir);\n\n const result = await conn.runAndReadAll(\n `SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed\n FROM trades.trade_data\n WHERE block_id = '${block_id.replace(/'/g, \"''\")}'\n ORDER BY date_opened\n LIMIT 1 OFFSET ${trade_index}`\n );\n\n const rows = result.getRows();\n if (rows.length === 0) {\n throw new Error(\n `No trade found at index ${trade_index} in block \"${block_id}\"`\n );\n }\n\n const row = rows[0];\n const legsStr = String(row[0] ?? \"\");\n const premium = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? \"\");\n const dateClosed = String(row[3] ?? \"\");\n const ticker = String(row[4] ?? \"\");\n const numContracts = Number(row[5] ?? 1);\n const timeClosed = String(row[6] ?? \"\");\n\n // Build actual trade close timestamp for path truncation\n if (dateClosed && timeClosed) {\n // time_closed is \"HH:MM:SS\" or \"HH:MM\" — normalize to \"HH:MM\"\n const normalizedTime = timeClosed.slice(0, 5);\n tradeCloseTimestamp = `${dateClosed} ${normalizedTime}`;\n }\n\n // Use trade dates if not provided\n open_date = open_date || dateOpened;\n close_date = close_date || dateClosed;\n\n // Parse legs from tradelog\n let parsedLegs;\n try {\n parsedLegs = parseLegsString(legsStr);\n } catch {\n throw new Error(\n `Cannot parse legs \"${legsStr}\" from tradelog — use hypothetical mode with explicit strikes`\n );\n }\n\n // Build ReplayLeg[] from parsed legs\n const root = ticker || parsedLegs[0].root;\n const perContractPremium =\n numContracts > 0 ? premium / numContracts : premium;\n\n // OO format provides per-leg entry price, contract count, and expiry hint\n const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);\n\n // Resolve per-leg expiry: OO expiryHint (\"Mar 13\") + year from trade date\n const tradeYear = (open_date || dateOpened).split('-')[0];\n\n // Override fetch date range from OO expiryHints when available\n if (hasOOData) {\n const ooRange = resolveOODateRange(parsedLegs, tradeYear, open_date || dateOpened);\n if (ooRange) {\n open_date = ooRange.from;\n close_date = ooRange.to;\n }\n }\n\n replayLegs = parsedLegs.map((leg) => {\n let legExpiry = close_date!;\n if (hasOOData && leg.expiryHint) {\n legExpiry = resolveOOExpiryHint(leg.expiryHint, tradeYear);\n }\n return {\n occTicker: buildOccTicker(root, legExpiry, leg.type, leg.strike),\n quantity: hasOOData\n ? leg.quantity * (leg.contracts ?? 1)\n : leg.quantity * (numContracts > 0 ? numContracts : 1),\n entryPrice: hasOOData\n ? leg.entryPrice!\n : perContractPremium / parsedLegs.length,\n multiplier,\n };\n });\n } else {\n throw new Error(\n \"Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode\"\n );\n }\n\n // ----- Fetch minute bars for each leg -----\n // Index options often trade under a weekly root on Massive (e.g., SPX→SPXW).\n // If the primary root fetch returns empty, retry with the mapped fallback root.\n const ROOT_FALLBACK_MAP: Record<string, string> = {\n SPX: 'SPXW',\n NDX: 'NDXP',\n RUT: 'RUTW',\n };\n\n const fetchLegBars = async (occTicker: string): Promise<BarRow[]> => {\n const bars = await fetchBarsWithCache({\n ticker: occTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: 'option',\n conn: injectedConn,\n baseDir,\n });\n if (bars.length > 0) return bars;\n\n // Fallback root retry (SPX→SPXW, NDX→NDXP, RUT→RUTW)\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const root = rootMatch ? rootMatch[1] : '';\n const fallbackRoot = ROOT_FALLBACK_MAP[root];\n if (fallbackRoot && !occTicker.startsWith(fallbackRoot)) {\n const fallbackTicker = fallbackRoot + occTicker.slice(root.length);\n const fallbackBars = await fetchBarsWithCache({\n ticker: fallbackTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: 'option',\n conn: injectedConn,\n baseDir,\n });\n if (fallbackBars.length > 0) {\n const leg = replayLegs.find(l => l.occTicker === occTicker);\n if (leg) leg.occTicker = fallbackTicker;\n return fallbackBars;\n }\n }\n return [];\n };\n\n const barsByLeg = await Promise.all(\n replayLegs.map((leg) => fetchLegBars(leg.occTicker))\n );\n\n // ----- Fetch underlying bars + build greeks config -----\n // Reverse-map weekly roots back to standard root for underlying fetch\n const REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n };\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n\n // Extract root from first leg's OCC ticker\n const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = firstRootMatch ? firstRootMatch[1] : '';\n const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n // Fetch underlying minute bars via shared cache utility (cache-read → API → cache-write)\n let underlyingBars: BarRow[] = await fetchBarsWithCache({\n ticker: underlyingTicker,\n from: open_date!,\n to: close_date!,\n timespan: 'minute',\n assetClass: underlyingTicker === 'SPX' || underlyingTicker === 'NDX' || underlyingTicker === 'RUT' ? 'index' : 'stock',\n conn: injectedConn,\n baseDir,\n });\n\n // Daily fallback when minute bars unavailable\n if (underlyingBars.length === 0) {\n try {\n const conn = injectedConn ?? await getConnection(baseDir);\n const result = await conn.runAndReadAll(\n `SELECT date, close FROM market.daily\n WHERE ticker = '${underlyingTicker}'\n AND date >= '${open_date}' AND date <= '${close_date}'\n ORDER BY date`\n );\n const dailyRows = result.getRows();\n underlyingBars = dailyRows.map(r => ({\n date: String(r[0]),\n open: Number(r[1]),\n high: Number(r[1]),\n low: Number(r[1]),\n close: Number(r[1]),\n volume: 0,\n ticker: underlyingTicker,\n }));\n } catch {\n // No fallback available — greeks will be omitted\n }\n }\n\n // Build underlying price map for greeks config\n const underlyingPrices = new Map<string, number>();\n for (const b of underlyingBars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n underlyingPrices.set(ts, markPrice(b));\n }\n\n // Build sorted timestamps array for tolerant nearest-timestamp lookup (D-07/D-08)\n const sortedTimestamps = Array.from(underlyingPrices.keys())\n .filter(k => k.includes(' ')) // Only intraday timestamps, not date-only keys\n .sort();\n\n // IVP lookup from market.daily VIX ticker (normalized schema)\n let ivpByDate: Map<string, number> | undefined;\n try {\n const conn = injectedConn ?? await getConnection(baseDir);\n const ivpResult = await conn.runAndReadAll(\n `SELECT date, ivp FROM market.daily\n WHERE ticker = 'VIX'\n AND date >= '${open_date}' AND date <= '${close_date}'\n AND ivp IS NOT NULL\n ORDER BY date`\n );\n const ivpRows = ivpResult.getRows();\n if (ivpRows.length > 0) {\n ivpByDate = new Map();\n for (const r of ivpRows) {\n ivpByDate.set(String(r[0]), Number(r[1]));\n }\n }\n } catch {\n // IVP is optional enrichment — don't fail\n }\n\n // Build GreeksConfig\n let greeksConfig: GreeksConfig | undefined;\n if (underlyingPrices.size > 0) {\n greeksConfig = {\n underlyingPrices,\n sortedTimestamps,\n legs: replayLegs.map(leg => {\n // Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}\n const occMatch = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!occMatch) return { strike: 0, type: 'C' as const, expiryDate: '' };\n const yymmdd = occMatch[1];\n const type = occMatch[2] as 'C' | 'P';\n const strike = parseInt(occMatch[3], 10) / 1000;\n const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;\n return { strike, type, expiryDate };\n }),\n riskFreeRate: 0.045,\n dividendYield,\n ivpByDate,\n };\n }\n\n // ----- Compute P&L path + MFE/MAE -----\n let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);\n let { mfe, mae, mfeTimestamp, maeTimestamp } =\n computeReplayMfeMae(fullPath);\n let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;\n\n // Compute greeks warning (D-12): warn when >50% of leg-timestamps have null greeks\n let greeksNullCount = 0;\n let greeksTotalCount = 0;\n for (const point of fullPath) {\n if (point.legGreeks) {\n for (const lg of point.legGreeks) {\n greeksTotalCount++;\n if (lg.delta === null) greeksNullCount++;\n }\n }\n }\n const greeksWarning = greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5\n ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`\n : null;\n\n // Apply format filter\n // Truncate path at trade close timestamp when close_at === \"trade\" (default)\n // This ensures decompose_greeks and exit triggers only analyze the actual holding period\n if (close_at === \"trade\" && tradeCloseTimestamp && fullPath.length > 0) {\n const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);\n if (truncIdx > 0) {\n fullPath = fullPath.slice(0, truncIdx);\n // Recompute MFE/MAE/totalPnl on truncated path\n mfe = -Infinity;\n mae = Infinity;\n for (const p of fullPath) {\n if (p.strategyPnl > mfe) { mfe = p.strategyPnl; mfeTimestamp = p.timestamp; }\n if (p.strategyPnl < mae) { mae = p.strategyPnl; maeTimestamp = p.timestamp; }\n }\n totalPnl = fullPath[fullPath.length - 1].strategyPnl;\n }\n }\n\n const { format } = params;\n let pnlPath: typeof fullPath;\n if (format === \"summary\") {\n // Return only MFE, MAE, and boundary points (first, last, MFE timestamp, MAE timestamp)\n const keyTimestamps = new Set([\n fullPath[0]?.timestamp,\n fullPath[fullPath.length - 1]?.timestamp,\n mfeTimestamp,\n maeTimestamp,\n ]);\n pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));\n } else if (format === \"sampled\") {\n // Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)\n const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);\n pnlPath = fullPath.filter((p, i) =>\n i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp)\n );\n } else {\n pnlPath = fullPath;\n }\n\n return {\n pnlPath,\n mfe,\n mae,\n mfeTimestamp,\n maeTimestamp,\n totalPnl,\n totalBars: fullPath.length,\n legs: replayLegs,\n greeksWarning,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerReplayTools(\n server: McpServer,\n baseDir: string\n): void {\n server.registerTool(\n \"replay_trade\",\n {\n description:\n \"Replay a trade using historical minute-level option bars. \" +\n \"Uses cached bars from market.intraday if available; fetches from Massive.com API on cache miss (requires MASSIVE_API_KEY). \" +\n \"Returns minute-by-minute P&L path with MFE (Maximum Favorable Excursion) and MAE (Maximum Adverse Excursion). \" +\n \"Two modes: (A) Hypothetical — provide explicit legs with strikes, expiry, entry prices. \" +\n \"(B) Tradelog — provide block_id + trade_index to replay an existing trade from your data.\",\n inputSchema: replayTradeSchema,\n },\n async (params) => {\n try {\n const result = await handleReplayTrade(params, baseDir);\n\n const summary =\n `Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? \"trade dates\"} to ${params.close_date ?? \"trade dates\"}: ` +\n `$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +\n `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? 'yes' : 'no'}`;\n\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error replaying trade: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * bar-cache.ts\n *\n * Shared fetch+cache utility for Massive.com bar data.\n *\n * Implements the cache-read → API-fetch → cache-write lifecycle for intraday bars:\n * 1. Cache-read: query market.intraday first (avoids redundant API calls)\n * 2. API fetch: call fetchBars on cache miss\n * 3. Cache-write: write fetched bars to market.intraday (best-effort, batched)\n *\n * Per D-02/D-03/D-04: Eliminates duplicated cache logic in replay.ts.\n * Historical option and underlying bars are immutable — cached bars are always valid.\n */\n\nimport type { BarRow, AssetClass } from './market-provider.js';\nimport { getProvider } from './market-provider.js';\nimport { getConnection } from '../db/connection.js';\nimport type { DuckDBConnection } from '@duckdb/node-api';\n\n/** Check if quotes enrichment is enabled via MASSIVE_QUOTES_ENABLED env var. */\nfunction quotesEnabled(): boolean {\n return process.env.MASSIVE_QUOTES_ENABLED === 'true' || process.env.MASSIVE_QUOTES_ENABLED === '1';\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FetchBarsWithCacheOptions {\n ticker: string;\n from: string;\n to: string;\n timespan?: \"day\" | \"minute\" | \"hour\";\n assetClass?: AssetClass;\n /** Pre-opened DuckDB connection (avoids re-opening in hot paths). */\n conn?: DuckDBConnection;\n /** Base directory for getConnection (used when conn is not provided). */\n baseDir?: string;\n}\n\n// ---------------------------------------------------------------------------\n// fetchBarsWithCache\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch minute bars for a ticker over a date range, using market.intraday as a cache.\n *\n * Steps:\n * 1. Try to read bars from market.intraday (cache hit → return immediately)\n * 2. On cache miss, call fetchBars (Massive API)\n * 3. Write fetched bars back to market.intraday in batches of 500 (best-effort)\n *\n * DuckDB errors in steps 1 or 3 are swallowed — step 2 is always attempted on miss.\n * Returns [] when both cache and API return no bars (or API throws).\n */\nexport async function fetchBarsWithCache(opts: FetchBarsWithCacheOptions): Promise<BarRow[]> {\n const { ticker, from, to, timespan, assetClass, baseDir } = opts;\n\n // 1. Cache-read from market.intraday\n try {\n const conn = opts.conn ?? await getConnection(baseDir ?? '.');\n const escaped = ticker.replace(/'/g, \"''\");\n const cached = await conn.runAndReadAll(\n `SELECT open, high, low, close, bid, ask, time, date\n FROM market.intraday\n WHERE ticker = '${escaped}'\n AND date >= '${from}'\n AND date <= '${to}'\n ORDER BY date, time`\n );\n const rows = cached.getRows() as unknown[][];\n if (rows.length > 0) {\n const bars = rows.map((row) => ({\n open: Number(row[0]),\n high: Number(row[1]),\n low: Number(row[2]),\n close: Number(row[3]),\n bid: row[4] != null ? Number(row[4]) : undefined,\n ask: row[5] != null ? Number(row[5]) : undefined,\n time: String(row[6]),\n date: String(row[7]),\n ticker,\n volume: 0, // market.intraday has no volume column\n }));\n\n // Backfill bid/ask from quotes endpoint if cached bars are missing them\n const missingQuotes = assetClass === \"option\" && quotesEnabled()\n && bars.some(b => b.bid == null && b.ask == null);\n if (missingQuotes) {\n try {\n const provider = getProvider();\n if (provider.fetchQuotes) {\n const quotesMap = await provider.fetchQuotes(ticker, from, to);\n if (quotesMap.size > 0) {\n const updates: string[] = [];\n for (const bar of bars) {\n if (bar.time != null) {\n const key = `${bar.date} ${bar.time}`;\n const quote = quotesMap.get(key);\n if (quote != null) {\n bar.bid = quote.bid;\n bar.ask = quote.ask;\n updates.push(\n `('${escaped}', '${bar.date}', '${bar.time}', ${quote.bid}, ${quote.ask})`\n );\n }\n }\n }\n // Persist enriched bid/ask back to cache (batched INSERT OR REPLACE)\n if (updates.length > 0) {\n const updateConn = opts.conn ?? await getConnection(baseDir ?? '.');\n for (let i = 0; i < updates.length; i += 500) {\n const chunk = updates.slice(i, i + 500);\n await updateConn.run(\n `UPDATE market.intraday AS m SET bid = v.bid, ask = v.ask\n FROM (VALUES ${chunk.join(', ')}) AS v(ticker, date, time, bid, ask)\n WHERE m.ticker = v.ticker AND m.date = v.date AND m.time = v.time`\n );\n }\n }\n }\n }\n } catch {\n // Best-effort — return cached bars without bid/ask\n }\n }\n\n return bars;\n }\n } catch {\n // Cache miss or table not available — fall through to API fetch\n }\n\n // 2. API fetch on cache miss\n let bars: BarRow[] = [];\n try {\n bars = await getProvider().fetchBars({\n ticker,\n from,\n to,\n timespan: timespan ?? 'minute',\n assetClass,\n });\n } catch {\n return [];\n }\n if (bars.length === 0) return [];\n\n // 3. Cache-write to market.intraday (best-effort, batched)\n try {\n const conn = opts.conn ?? await getConnection(baseDir ?? '.');\n const escaped = ticker.replace(/'/g, \"''\");\n const values = bars\n .filter(b => b.time)\n .map(b =>\n `('${escaped}', '${b.date}', '${b.time}', ${b.open}, ${b.high}, ${b.low}, ${b.close}, ${b.bid ?? 'NULL'}, ${b.ask ?? 'NULL'})`\n );\n for (let i = 0; i < values.length; i += 500) {\n const chunk = values.slice(i, i + 500);\n await conn.run(\n `INSERT OR REPLACE INTO market.intraday (ticker, date, time, open, high, low, close, bid, ask) VALUES ${chunk.join(', ')}`\n );\n }\n } catch {\n // Cache-write is best-effort — don't fail the fetch\n }\n\n return bars;\n}\n","/**\n * Option Snapshot Tools\n *\n * MCP tool for fetching live option chain snapshots from Massive.com.\n * Returns current greeks, IV, open interest, and quotes for option contracts\n * on a specified underlying.\n *\n * Tools registered:\n * - get_option_snapshot — Fetch live option chain with greeks/IV/OI\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getProvider } from \"../utils/market-provider.js\";\n\n// ---------------------------------------------------------------------------\n// Zod schema\n// ---------------------------------------------------------------------------\n\nexport const getOptionSnapshotSchema = z.object({\n underlying: z\n .string()\n .describe(\"Underlying ticker symbol (e.g., 'SPX', 'SPY', 'AAPL')\"),\n strike_price_gte: z\n .number()\n .optional()\n .describe(\"Minimum strike price filter\"),\n strike_price_lte: z\n .number()\n .optional()\n .describe(\"Maximum strike price filter\"),\n expiration_date_gte: z\n .string()\n .optional()\n .describe(\"Earliest expiration date (YYYY-MM-DD)\"),\n expiration_date_lte: z\n .string()\n .optional()\n .describe(\"Latest expiration date (YYYY-MM-DD)\"),\n contract_type: z\n .enum([\"call\", \"put\"])\n .optional()\n .describe(\"Filter by call or put\"),\n limit: z\n .number()\n .optional()\n .default(50)\n .describe(\n \"Max contracts to return (default 50, use higher for full chain)\"\n ),\n});\n\n// ---------------------------------------------------------------------------\n// Handler (exported for testing)\n// ---------------------------------------------------------------------------\n\nexport async function handleGetOptionSnapshot(\n params: z.infer<typeof getOptionSnapshotSchema>,\n): Promise<string> {\n try {\n const {\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n limit,\n } = params;\n\n const result = await getProvider().fetchOptionSnapshot({\n underlying,\n strike_price_gte,\n strike_price_lte,\n expiration_date_gte,\n expiration_date_lte,\n contract_type,\n });\n\n // Client-side limit truncation: API fetches all filtered contracts\n // (ensuring BS fallback runs on all), then we truncate for presentation\n const contractsTotal = result.contracts.length;\n const contracts =\n limit != null && contractsTotal > limit\n ? result.contracts.slice(0, limit)\n : result.contracts;\n\n return JSON.stringify({\n underlying_ticker: result.underlying_ticker,\n underlying_price: result.underlying_price,\n contracts_returned: contracts.length,\n contracts_total: contractsTotal,\n contracts,\n });\n } catch (error) {\n return JSON.stringify({\n error: (error as Error).message,\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerSnapshotTools(server: McpServer): void {\n server.registerTool(\n \"get_option_snapshot\",\n {\n description:\n \"Fetch live option chain snapshot with greeks, IV, open interest, and quotes from Massive.com. \" +\n \"Returns current market data for option contracts on the specified underlying. \" +\n \"Use filters to narrow by strike range, expiration range, or call/put type. \" +\n \"Replaces TastyTrade get_option_chain for analysis.\",\n inputSchema: getOptionSnapshotSchema,\n },\n async (params) => {\n const text = await handleGetOptionSnapshot(params);\n return {\n content: [{ type: \"text\" as const, text }],\n };\n }\n );\n}\n","/**\n * Greeks Decomposition Engine\n *\n * Decomposes a replay P&L path into ranked greek factor contributions\n * (delta, gamma, theta, vega, residual) using full revaluation P&L attribution.\n *\n * Full revaluation reprices each leg with one input changed at a time\n * (spot, time, vol) to capture all higher-order effects (charm, vanna, volga)\n * naturally. This produces near-zero residual for any strategy where the\n * pricing model (BS or Bachelier) can accurately price the options.\n *\n * Falls back to numerical decomposition (realized delta from price changes)\n * when full revaluation still produces >80% residual (model pricing failure).\n *\n * Pure logic module — no I/O, no DuckDB, no fetch.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\nimport { bsPrice, bachelierPrice, BACHELIER_DTE_THRESHOLD } from './black-scholes.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type FactorName = 'delta' | 'gamma' | 'theta' | 'vega' | 'charm' | 'vanna' | 'residual' | 'time_and_vol';\n\nexport interface FactorContribution {\n factor: FactorName;\n totalPnl: number; // Sum of step contributions\n pctOfTotal: number; // % of total abs P&L move\n steps: number[]; // Per-step contribution values\n}\n\nexport interface LegGroupVega {\n label: string; // e.g., \"front_month\", \"back_month\"\n legIndices: number[]; // Which legs are in this group\n totalVegaPnl: number; // Sum of vega P&L for this group\n avgIvChange: number; // Average IV change for this group's legs\n steps: number[]; // Per-step vega contribution for this group\n}\n\nexport interface GreeksDecompositionResult {\n factors: FactorContribution[]; // Sorted by abs(totalPnl) descending\n legGroupVega?: LegGroupVega[]; // Per-leg-group vega attribution\n totalPnlChange: number; // Actual P&L change from first to last point\n totalAttributed: number; // Sum of factor contributions (excl residual)\n totalResidual: number; // Total residual\n stepCount: number; // Number of steps (pnlPath.length - 1)\n summary: string; // Human-readable summary\n warning?: string | null; // D-13: high residual warning\n method: 'full_reval' | 'model' | 'numerical'; // which method produced the attribution\n}\n\nexport interface LegGroupDef {\n label: string;\n legIndices: number[];\n}\n\nexport interface LegPricingInput {\n strike: number;\n type: 'C' | 'P';\n expiryDate: string; // YYYY-MM-DD\n}\n\nexport interface GreeksDecompositionConfig {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n underlyingPrices?: Map<string, number>; // timestamp -> underlying price\n legGroups?: LegGroupDef[]; // Optional leg grouping for per-group vega\n /** Per-leg pricing inputs for full revaluation. When provided, uses full reval\n * instead of Taylor expansion. Falls back to Taylor when missing. */\n legPricingInputs?: LegPricingInput[];\n riskFreeRate?: number; // e.g. 0.045\n dividendYield?: number; // e.g. 0.015 for SPX\n}\n\n// ---------------------------------------------------------------------------\n// Time delta helper\n// ---------------------------------------------------------------------------\n\nconst TRADING_MINUTES_PER_DAY = 390;\n\n/**\n * Compute time delta in trading days between two timestamps.\n * Format: \"YYYY-MM-DD HH:MM\"\n *\n * Same day: minutes difference / 390\n * Cross day: calendar day difference (simplified — treats each gap as 1 day)\n */\nexport function computeTimeDeltaDays(ts1: string, ts2: string): number {\n const [date1, time1] = ts1.split(' ');\n const [date2, time2] = ts2.split(' ');\n\n if (date1 === date2) {\n // Same day: count minutes difference\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n const mins1 = h1 * 60 + m1;\n const mins2 = h2 * 60 + m2;\n const diffMins = Math.abs(mins2 - mins1);\n return diffMins / TRADING_MINUTES_PER_DAY;\n }\n\n // Cross-day: compute calendar day difference\n const d1 = new Date(date1 + 'T12:00:00'); // noon to avoid DST issues\n const d2 = new Date(date2 + 'T12:00:00');\n const diffMs = Math.abs(d2.getTime() - d1.getTime());\n const diffDays = Math.round(diffMs / (24 * 60 * 60 * 1000));\n\n // Add fractional day from time within each day\n const [h1, m1] = time1.split(':').map(Number);\n const [h2, m2] = time2.split(':').map(Number);\n // Fraction of trading day for ts2's time (from market open ~9:30)\n const minsIntoDay2 = (h2 * 60 + m2) - (9 * 60 + 30);\n const fracDay2 = Math.max(0, minsIntoDay2) / TRADING_MINUTES_PER_DAY;\n // Fraction remaining in ts1's day\n const minsIntoDay1 = (h1 * 60 + m1) - (9 * 60 + 30);\n const fracDayRemaining1 = Math.max(0, 1 - minsIntoDay1 / TRADING_MINUTES_PER_DAY);\n\n // Total: remaining fraction of day1 + (diffDays - 1) full days + fraction of day2\n if (diffDays <= 1) {\n return fracDayRemaining1 + fracDay2;\n }\n return fracDayRemaining1 + (diffDays - 1) + fracDay2;\n}\n\n// ---------------------------------------------------------------------------\n// Numerical fallback decomposition (D-09/D-10/D-11)\n// ---------------------------------------------------------------------------\n\n/**\n * Numerical decomposition: compute realized delta from price changes when\n * model-based attribution has > 80% residual.\n *\n * Splits P&L into: delta (from realized delta), gamma (from delta changes),\n * and time_and_vol (everything else — theta + vega + unexplained).\n */\nfunction numericalDecomposition(\n config: GreeksDecompositionConfig,\n totalPnlChange: number,\n stepCount: number,\n): GreeksDecompositionResult {\n const { pnlPath, underlyingPrices } = config;\n\n const numDeltaSteps: number[] = [];\n const numGammaSteps: number[] = [];\n const numResidualSteps: number[] = [];\n\n let prevRealizedDelta: number | null = null;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n const actualChange = next.strategyPnl - cur.strategyPnl;\n\n // Underlying price change\n let underlyingChange = 0;\n if (underlyingPrices) {\n const p1 = underlyingPrices.get(cur.timestamp);\n const p2 = underlyingPrices.get(next.timestamp);\n if (p1 !== undefined && p2 !== undefined) {\n underlyingChange = p2 - p1;\n }\n }\n\n // Skip when underlying barely moves (< $0.01) — can't estimate delta\n if (Math.abs(underlyingChange) < 0.01) {\n numDeltaSteps.push(0);\n numGammaSteps.push(0);\n numResidualSteps.push(actualChange);\n // Do NOT update prevRealizedDelta — delta is unknown\n continue;\n }\n\n // Realized delta = total option PnL change / underlying change\n const realizedDelta = actualChange / underlyingChange;\n\n // Gamma from delta changes (D-10): only when we have a previous delta\n let gammaPnl = 0;\n if (prevRealizedDelta !== null) {\n const deltaChange = realizedDelta - prevRealizedDelta;\n gammaPnl = 0.5 * deltaChange * underlyingChange;\n }\n\n const pureDeltaPnl = realizedDelta * underlyingChange - gammaPnl;\n const residual = actualChange - pureDeltaPnl - gammaPnl;\n\n numDeltaSteps.push(pureDeltaPnl);\n numGammaSteps.push(gammaPnl);\n numResidualSteps.push(residual);\n\n prevRealizedDelta = realizedDelta;\n }\n\n const sumSteps = (s: number[]) => s.reduce((a, v) => a + v, 0);\n const totalDelta = sumSteps(numDeltaSteps);\n const totalGamma = sumSteps(numGammaSteps);\n const totalTimeAndVol = sumSteps(numResidualSteps);\n\n const rawFactors = [\n { factor: 'delta' as FactorName, totalPnl: totalDelta, steps: numDeltaSteps },\n { factor: 'gamma' as FactorName, totalPnl: totalGamma, steps: numGammaSteps },\n { factor: 'time_and_vol' as FactorName, totalPnl: totalTimeAndVol, steps: numResidualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const summaryParts = factors.map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (numerical): ${summaryParts.join(', ')}`;\n\n return {\n factors,\n legGroupVega: undefined, // Leg-group vega not available in numerical mode\n totalPnlChange,\n totalAttributed: totalDelta + totalGamma,\n totalResidual: totalTimeAndVol,\n stepCount,\n summary,\n warning: 'Model-based attribution had >80% residual. Switched to numerical method (realized delta from price changes).',\n method: 'numerical',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Core decomposition\n// ---------------------------------------------------------------------------\n\n/**\n * Compute DTE in days from a bar timestamp to a leg's expiry (4:00 PM ET).\n */\nfunction computeDte(timestamp: string, expiryDate: string): number {\n const dateStr = timestamp.split(' ')[0];\n const timePart = timestamp.split(' ')[1] ?? '09:30';\n const [eyy, emm, edd] = expiryDate.split('-').map(Number);\n const [byy, bmm, bdd] = dateStr.split('-').map(Number);\n const [hh, min] = timePart.split(':').map(Number);\n\n const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET\n const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;\n return (expiryMs - barMs) / (1000 * 60 * 60 * 24);\n}\n\n/**\n * Price an option using the appropriate model (BS or Bachelier) based on DTE.\n * Returns null if pricing fails (DTE <= 0 or IV missing).\n */\nfunction priceOption(\n type: 'C' | 'P',\n S: number,\n K: number,\n dte: number,\n r: number,\n q: number,\n iv: number,\n): number | null {\n if (dte <= 0 || iv <= 0) return null;\n const T = dte / 365;\n const bsType = type === 'C' ? 'call' as const : 'put' as const;\n if (dte < BACHELIER_DTE_THRESHOLD) {\n return bachelierPrice(bsType, S, K, T, r, q, iv);\n }\n return bsPrice(bsType, S, K, T, r, q, iv);\n}\n\n/**\n * Decompose a replay P&L path into ranked greek factor contributions.\n *\n * Uses full revaluation when legPricingInputs are provided:\n * For each step, reprices each leg with one input changed at a time\n * (spot only, time only, vol only) to isolate each factor's contribution.\n * This captures all higher-order effects (charm, vanna, volga) naturally.\n *\n * Falls back to numerical decomposition when full reval produces >80% residual\n * (pricing model failure for that strategy/DTE combination).\n */\nexport function decomposeGreeks(config: GreeksDecompositionConfig): GreeksDecompositionResult {\n const { pnlPath, legs, underlyingPrices, legGroups, legPricingInputs, riskFreeRate, dividendYield } = config;\n\n // Edge case: empty or single-point path\n if (pnlPath.length <= 1) {\n const emptyFactors: FactorContribution[] = [\n { factor: 'delta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'gamma', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'theta', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'vega', totalPnl: 0, pctOfTotal: 0, steps: [] },\n { factor: 'residual', totalPnl: 0, pctOfTotal: 0, steps: [] },\n ];\n return {\n factors: emptyFactors,\n legGroupVega: legGroups ? legGroups.map(g => ({\n label: g.label,\n legIndices: g.legIndices,\n totalVegaPnl: 0,\n avgIvChange: 0,\n steps: [],\n })) : undefined,\n totalPnlChange: 0,\n totalAttributed: 0,\n totalResidual: 0,\n stepCount: 0,\n summary: 'No P&L path to decompose (0 steps)',\n warning: null,\n method: 'full_reval',\n };\n }\n\n const stepCount = pnlPath.length - 1;\n const canFullReval = legPricingInputs && legPricingInputs.length === legs.length\n && riskFreeRate !== undefined && dividendYield !== undefined && underlyingPrices;\n const r = riskFreeRate ?? 0.045;\n const q = dividendYield ?? 0.015;\n\n // Accumulators\n const deltaSteps: number[] = [];\n const thetaSteps: number[] = [];\n const vegaSteps: number[] = [];\n const charmSteps: number[] = [];\n const vannaSteps: number[] = [];\n const residualSteps: number[] = [];\n\n // Per-leg-group vega accumulators\n const groupSteps: number[][] | undefined = legGroups\n ? legGroups.map(() => [] as number[])\n : undefined;\n\n for (let i = 0; i < stepCount; i++) {\n const cur = pnlPath[i];\n const next = pnlPath[i + 1];\n\n let stepDelta = 0;\n let stepTheta = 0;\n let stepVega = 0;\n let stepCharm = 0;\n let stepVanna = 0;\n let stepResidual = 0;\n\n const groupVegaAccum: number[] | undefined = legGroups ? legGroups.map(() => 0) : undefined;\n\n const legCount = Math.min(legs.length, cur.legPrices?.length ?? 0, next.legPrices?.length ?? 0);\n\n // Underlying prices at cur and next timestamps\n const S1 = underlyingPrices?.get(cur.timestamp);\n const S2 = underlyingPrices?.get(next.timestamp);\n\n for (let j = 0; j < legCount; j++) {\n const positionSize = legs[j].quantity * legs[j].multiplier;\n const legActualChange = ((next.legPrices?.[j] ?? 0) - (cur.legPrices?.[j] ?? 0)) * positionSize;\n\n const curIv = cur.legGreeks?.[j]?.iv;\n const nextIv = next.legGreeks?.[j]?.iv;\n const lpi = legPricingInputs?.[j];\n\n // Full revaluation: reprice with one input changed at a time\n if (canFullReval && lpi && S1 !== undefined && S2 !== undefined\n && curIv !== null && curIv !== undefined && curIv > 0\n && nextIv !== null && nextIv !== undefined && nextIv > 0) {\n\n const dte1 = computeDte(cur.timestamp, lpi.expiryDate);\n const dte2 = computeDte(next.timestamp, lpi.expiryDate);\n\n if (dte1 > 0 && dte2 > 0) {\n // Baseline: price at (S1, T1, IV1)\n const priceBase = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, curIv);\n\n // Delta: price at (S2, T1, IV1) — only spot changed\n const priceDelta = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, curIv);\n\n // Theta: price at (S1, T2, IV1) — only time changed\n const priceTheta = priceOption(lpi.type, S1, lpi.strike, dte2, r, q, curIv);\n\n // Vega: price at (S1, T1, IV2) — only vol changed\n const priceVega = priceOption(lpi.type, S1, lpi.strike, dte1, r, q, nextIv);\n\n // Cross-term repricing: two inputs changed at once\n // Charm (spot×time): P(S2, T2, σ1) - base - delta - theta\n const priceCharm = priceOption(lpi.type, S2, lpi.strike, dte2, r, q, curIv);\n // Vanna (spot×vol): P(S2, T1, σ2) - base - delta - vega\n const priceVanna = priceOption(lpi.type, S2, lpi.strike, dte1, r, q, nextIv);\n\n if (priceBase !== null && priceDelta !== null && priceTheta !== null\n && priceVega !== null && priceCharm !== null && priceVanna !== null) {\n const legDeltaPnl = (priceDelta - priceBase) * positionSize;\n const legThetaPnl = (priceTheta - priceBase) * positionSize;\n const legVegaPnl = (priceVega - priceBase) * positionSize;\n const legCharmPnl = ((priceCharm - priceBase) - (priceDelta - priceBase) - (priceTheta - priceBase)) * positionSize;\n const legVannaPnl = ((priceVanna - priceBase) - (priceDelta - priceBase) - (priceVega - priceBase)) * positionSize;\n const legResidual = legActualChange - legDeltaPnl - legThetaPnl - legVegaPnl - legCharmPnl - legVannaPnl;\n\n stepDelta += legDeltaPnl;\n stepTheta += legThetaPnl;\n stepVega += legVegaPnl;\n stepCharm += legCharmPnl;\n stepVanna += legVannaPnl;\n stepResidual += legResidual;\n\n // Per-leg-group vega\n if (legGroups && groupVegaAccum) {\n for (let g = 0; g < legGroups.length; g++) {\n if (legGroups[g].legIndices.includes(j)) {\n groupVegaAccum[g] += legVegaPnl;\n }\n }\n }\n continue; // leg handled by full reval\n }\n }\n }\n\n // Fallback: leg P&L goes to residual (no pricing possible)\n stepResidual += legActualChange;\n }\n\n deltaSteps.push(stepDelta);\n thetaSteps.push(stepTheta);\n vegaSteps.push(stepVega);\n charmSteps.push(stepCharm);\n vannaSteps.push(stepVanna);\n residualSteps.push(stepResidual);\n\n if (groupSteps && groupVegaAccum) {\n for (let g = 0; g < legGroups!.length; g++) {\n groupSteps[g].push(groupVegaAccum[g]);\n }\n }\n }\n\n const sumSteps = (steps: number[]): number => steps.reduce((s, v) => s + v, 0);\n\n // Full reval factors:\n // - delta: spot-only P&L (includes gamma — all spot-driven effects)\n // - theta: time-only P&L\n // - vega: vol-only P&L\n // - charm: spot×time cross-effect (delta changing with time)\n // - vanna: spot×vol cross-effect (delta changing with vol)\n // - residual: triple cross (spot+time+vol simultaneously) + model error\n const rawFactors: Array<{ factor: FactorName; totalPnl: number; steps: number[] }> = [\n { factor: 'delta', totalPnl: sumSteps(deltaSteps), steps: deltaSteps },\n { factor: 'theta', totalPnl: sumSteps(thetaSteps), steps: thetaSteps },\n { factor: 'vega', totalPnl: sumSteps(vegaSteps), steps: vegaSteps },\n { factor: 'charm', totalPnl: sumSteps(charmSteps), steps: charmSteps },\n { factor: 'vanna', totalPnl: sumSteps(vannaSteps), steps: vannaSteps },\n { factor: 'residual', totalPnl: sumSteps(residualSteps), steps: residualSteps },\n ];\n\n rawFactors.sort((a, b) => Math.abs(b.totalPnl) - Math.abs(a.totalPnl));\n const totalAbsSum = rawFactors.reduce((s, f) => s + Math.abs(f.totalPnl), 0);\n const factors: FactorContribution[] = rawFactors.map(f => ({\n ...f,\n pctOfTotal: totalAbsSum > 0 ? (Math.abs(f.totalPnl) / totalAbsSum) * 100 : 0,\n }));\n\n const totalPnlChange = pnlPath[pnlPath.length - 1].strategyPnl - pnlPath[0].strategyPnl;\n const totalResidual = sumSteps(residualSteps);\n const totalAttributed = sumSteps(deltaSteps) + sumSteps(thetaSteps) + sumSteps(vegaSteps) + sumSteps(charmSteps) + sumSteps(vannaSteps);\n\n const residualPct = Math.abs(totalPnlChange) > 0.01\n ? Math.abs(totalResidual) / Math.abs(totalPnlChange)\n : 0;\n\n // Numerical fallback when full reval still produces >80% residual\n // (model pricing failure — BS/Bachelier can't accurately price these options)\n if (residualPct > 0.8 && pnlPath.length > 2) {\n return numericalDecomposition(config, totalPnlChange, stepCount);\n }\n\n // Build leg group vega results\n let legGroupVega: LegGroupVega[] | undefined;\n if (legGroups && groupSteps) {\n legGroupVega = legGroups.map((group, g) => {\n const steps = groupSteps[g];\n const totalVegaPnl = sumSteps(steps);\n\n let totalIvChange = 0;\n let ivStepCount = 0;\n for (let si = 0; si < stepCount; si++) {\n const cur = pnlPath[si];\n const nxt = pnlPath[si + 1];\n if (!cur.legGreeks || !nxt.legGreeks) continue;\n for (const j of group.legIndices) {\n const iv1 = cur.legGreeks[j]?.iv;\n const iv2 = nxt.legGreeks[j]?.iv;\n if (iv1 !== null && iv1 !== undefined && iv2 !== null && iv2 !== undefined) {\n totalIvChange += (iv2 - iv1) * 100;\n ivStepCount++;\n }\n }\n }\n\n return {\n label: group.label,\n legIndices: group.legIndices,\n totalVegaPnl,\n avgIvChange: ivStepCount > 0 ? totalIvChange / ivStepCount : 0,\n steps,\n };\n });\n }\n\n // Build summary\n const methodLabel = canFullReval ? 'full_reval' : 'model';\n const summaryParts = factors\n .filter(f => f.factor !== 'residual')\n .map(f => `${f.factor} ${f.totalPnl.toFixed(2)} (${f.pctOfTotal.toFixed(0)}%)`);\n const residualFactor = factors.find(f => f.factor === 'residual');\n if (residualFactor && Math.abs(residualFactor.totalPnl) > 0.01) {\n summaryParts.push(`residual ${residualFactor.totalPnl.toFixed(2)} (${residualFactor.pctOfTotal.toFixed(0)}%)`);\n }\n const summary = `P&L of ${totalPnlChange.toFixed(2)} (${methodLabel}): ${summaryParts.join(', ')}`;\n\n const warning = residualPct > 0.5\n ? `Residual ${(residualPct * 100).toFixed(0)}% — attribution limited for some legs.`\n : null;\n\n return {\n factors,\n legGroupVega,\n totalPnlChange,\n totalAttributed,\n totalResidual,\n stepCount,\n summary,\n warning,\n method: canFullReval ? 'full_reval' : 'model',\n };\n}\n","/**\n * Exit Trigger Evaluation Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that evaluates 15 exit\n * trigger types against a greeks-enriched P&L path from trade replay.\n *\n * Provides the computational heart of the `analyze_exit_triggers` tool.\n */\n\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TriggerType =\n | 'profitTarget'\n | 'stopLoss'\n | 'trailingStop'\n | 'profitAction'\n | 'dteExit'\n | 'ditExit'\n | 'clockTimeExit'\n | 'underlyingPriceMove'\n | 'positionDelta'\n | 'perLegDelta'\n | 'vixMove'\n | 'vix9dMove'\n | 'vix9dVixRatio'\n | 'slRatioThreshold'\n | 'slRatioMove';\n\nexport interface PartialClose {\n index: number;\n pnlAtFire: number;\n allocation: number;\n trigger: string;\n}\n\nexport interface ExitTriggerConfig {\n type: TriggerType;\n threshold: number;\n unit?: 'percent' | 'dollar'; // D-07: default 'dollar', backwards compatible\n steps?: Array<{ armAt: number; stopAt: number; closeAllocationPct?: number }>;\n // Context-specific optional fields:\n expiry?: string; // YYYY-MM-DD for dteExit\n openDate?: string; // YYYY-MM-DD for ditExit\n clockTime?: string; // \"HH:MM\" for clockTimeExit (threshold ignored)\n trailAmount?: number; // Dollar trail for trailingStop\n // Directional delta fields (OO-style per-leg exits):\n legIndex?: number; // 0-based leg index for perLegDelta — targets specific leg\n exitAbove?: number; // Fire when value > exitAbove (directional, no abs)\n exitBelow?: number; // Fire when value < exitBelow (directional, no abs)\n // Data maps for triggers needing external prices:\n underlyingPrices?: Map<string, number>; // timestamp -> price\n vixPrices?: Map<string, number>; // timestamp -> VIX price\n vix9dPrices?: Map<string, number>; // timestamp -> VIX9D price\n // S/L ratio inputs:\n spreadWidth?: number; // Width of spread in dollars\n contracts?: number; // Number of contracts\n multiplier?: number; // Default 100\n // Internal: set by handler when unit='percent' to compute dollar threshold\n entryCost?: number; // D-11: cost/credit of entry (negative = credit received)\n}\n\nexport interface TriggerFireEvent {\n type: TriggerType;\n firedAt: string; // Timestamp when trigger fired\n pnlAtFire: number; // Strategy P&L when trigger fired\n index: number; // Index into pnlPath\n detail?: string; // Human-readable description\n}\n\nexport interface ExitTriggerResult {\n triggers: TriggerFireEvent[]; // All triggers that fired (sorted by fire time)\n firstToFire: TriggerFireEvent | null; // Earliest trigger\n actualExit?: {\n timestamp: string;\n pnl: number;\n pnlDifference: number; // firstToFire.pnl - actualExit.pnl\n };\n partialCloses?: PartialClose[]; // Partial position closes from profitAction steps\n summary: string;\n}\n\nexport interface LegGroupConfig {\n label: string;\n legIndices: number[];\n triggers: ExitTriggerConfig[];\n}\n\nexport interface LegGroupResult {\n label: string;\n result: ExitTriggerResult;\n groupPnl: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Parse \"YYYY-MM-DD\" to a Date at local midnight. */\nfunction parseDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}\n\n/** Extract date portion \"YYYY-MM-DD\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractDate(timestamp: string): string {\n return timestamp.slice(0, 10);\n}\n\n/** Extract time portion \"HH:MM\" from timestamp \"YYYY-MM-DD HH:MM\". */\nfunction extractTime(timestamp: string): string {\n return timestamp.slice(11, 16);\n}\n\n/** Calendar days between two dates (absolute). */\nfunction calendarDaysBetween(a: Date, b: Date): number {\n const MS_PER_DAY = 86_400_000;\n return Math.abs(Math.floor((b.getTime() - a.getTime()) / MS_PER_DAY));\n}\n\n/** Compute S/L ratio for spread positions. */\nfunction computeSLRatio(\n point: PnlPoint,\n legs: ReplayLeg[],\n spreadWidth: number,\n contracts: number,\n multiplier: number,\n): number {\n // Spread value = sum of abs(markPrice * quantity * multiplier) for short legs\n let spreadValue = 0;\n for (let i = 0; i < legs.length; i++) {\n if (legs[i].quantity < 0) {\n const markPrice = point.legPrices[i] ?? 0;\n spreadValue += Math.abs(markPrice * legs[i].quantity * legs[i].multiplier);\n }\n }\n const maxLoss = spreadWidth * contracts * multiplier;\n if (maxLoss === 0) return 0;\n return spreadValue / maxLoss;\n}\n\n// ---------------------------------------------------------------------------\n// evaluateProfitAction — partial close aware evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a profitAction trigger with partial close support.\n * Steps with closeAllocationPct will close a fraction of the REMAINING position\n * when their armAt is first reached. The remaining position's P&L is scaled down.\n *\n * Returns both the fire event (stop hit on remaining) and any partial closes.\n */\nexport function evaluateProfitAction(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n _legs: ReplayLeg[], // eslint-disable-line @typescript-eslint/no-unused-vars\n): { fireEvent: TriggerFireEvent | null; partialCloses: PartialClose[] } {\n const partialCloses: PartialClose[] = [];\n\n if (pnlPath.length === 0 || !trigger.steps?.length) {\n return { fireEvent: null, partialCloses };\n }\n if (trigger.unit === 'percent' && trigger.entryCost == null) {\n return { fireEvent: null, partialCloses };\n }\n\n const scale = trigger.unit === 'percent'\n ? Math.abs(trigger.entryCost!)\n : 1;\n\n const normalizedSteps = [...trigger.steps]\n .sort((a, b) => a.armAt - b.armAt)\n .map((step) => ({\n armAt: step.armAt * scale,\n stopAt: step.stopAt * scale,\n closeAllocationPct: step.closeAllocationPct,\n }));\n\n let remainingAllocation = 1.0;\n let runningMaxPnl = -Infinity;\n // Track which steps have already triggered their partial close\n const stepPartialFired = new Array(normalizedSteps.length).fill(false);\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n // Check each step for partial close (only when armAt first reached)\n for (let s = 0; s < normalizedSteps.length; s++) {\n const step = normalizedSteps[s];\n if (!stepPartialFired[s] && step.closeAllocationPct && runningMaxPnl >= step.armAt) {\n stepPartialFired[s] = true;\n const closeAmt = remainingAllocation * step.closeAllocationPct;\n partialCloses.push({\n index: i,\n pnlAtFire: pnl * remainingAllocation * step.closeAllocationPct,\n allocation: closeAmt,\n trigger: 'profitAction',\n });\n remainingAllocation -= closeAmt;\n }\n }\n\n // Compute active stop floor (same logic as original)\n let activeFloor = -Infinity;\n for (const step of normalizedSteps) {\n if (runningMaxPnl >= step.armAt) {\n activeFloor = Math.max(activeFloor, step.stopAt);\n }\n }\n\n // Check if stop hit on remaining allocation\n // Scaled comparison: pnl * remainingAllocation <= activeFloor * remainingAllocation\n // Simplifies to: pnl <= activeFloor (when remainingAllocation > 0)\n if (activeFloor > -Infinity && remainingAllocation > 0 && pnl <= activeFloor) {\n const effectivePnl = pnl * remainingAllocation;\n const detail = trigger.unit === 'percent'\n ? `Profit action: stop adjusted to ${(activeFloor / scale * 100).toFixed(0)}% ($${activeFloor.toFixed(2)}) at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`\n : `Profit action: stop adjusted to $${activeFloor.toFixed(2)} at max P&L $${runningMaxPnl.toFixed(2)}, hit at $${pnl.toFixed(2)} (remaining ${(remainingAllocation * 100).toFixed(0)}%)`;\n\n return {\n fireEvent: {\n type: 'profitAction',\n firedAt: point.timestamp,\n pnlAtFire: effectivePnl,\n index: i,\n detail,\n },\n partialCloses,\n };\n }\n }\n\n return { fireEvent: null, partialCloses };\n}\n\n// ---------------------------------------------------------------------------\n// evaluateTrigger\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a single trigger against the full P&L path.\n * Returns the first point where it fires, or null.\n */\nexport function evaluateTrigger(\n trigger: ExitTriggerConfig,\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n): TriggerFireEvent | null {\n if (pnlPath.length === 0) return null;\n\n const { type, threshold } = trigger;\n\n // State for triggers that track running values\n let runningMaxPnl = -Infinity;\n let initialSLRatio: number | null = null;\n let firstUnderlyingPrice: number | null = null;\n let firstVixPrice: number | null = null;\n let firstVix9dPrice: number | null = null;\n\n for (let i = 0; i < pnlPath.length; i++) {\n const point = pnlPath[i];\n const pnl = point.strategyPnl;\n\n // Update running max for trailingStop\n if (pnl > runningMaxPnl) runningMaxPnl = pnl;\n\n let fired = false;\n let detail: string | undefined;\n\n switch (type) {\n case 'profitTarget': {\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdPT = trigger.unit === 'percent'\n ? threshold * Math.abs(trigger.entryCost!)\n : threshold;\n if (pnl >= dollarThresholdPT) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} >= ${(threshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} ($${dollarThresholdPT.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} >= target $${dollarThresholdPT.toFixed(2)}`;\n }\n break;\n }\n\n case 'stopLoss': {\n // Normalize negative threshold — users may pass -2 meaning \"stop at $2 loss\"\n const absThreshold = Math.abs(threshold);\n // unit='percent' requires entryCost; if missing, cannot compute — no fire\n if (trigger.unit === 'percent' && trigger.entryCost == null) break;\n const dollarThresholdSL = trigger.unit === 'percent'\n ? absThreshold * Math.abs(trigger.entryCost!)\n : absThreshold;\n if (pnl <= -dollarThresholdSL) {\n fired = true;\n detail = trigger.unit === 'percent'\n ? `P&L $${pnl.toFixed(2)} <= -${(absThreshold * 100).toFixed(0)}% of $${Math.abs(trigger.entryCost!).toFixed(2)} (-$${dollarThresholdSL.toFixed(2)})`\n : `P&L $${pnl.toFixed(2)} <= stop -$${dollarThresholdSL.toFixed(2)}`;\n }\n break;\n }\n\n case 'trailingStop': {\n const trailAmt = trigger.trailAmount ?? threshold;\n const dropdown = runningMaxPnl - pnl;\n if (dropdown >= trailAmt && runningMaxPnl > -Infinity) {\n fired = true;\n detail = `Dropdown $${dropdown.toFixed(2)} from max $${runningMaxPnl.toFixed(2)} >= trail $${trailAmt.toFixed(2)}`;\n }\n break;\n }\n\n case 'profitAction': {\n // Delegate to evaluateProfitAction for the full path evaluation\n // (evaluateTrigger is called point-by-point in the loop, but profitAction\n // needs full-path context for partial close tracking, so we handle it\n // by breaking out of the loop and evaluating the full path at once.)\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n return paResult.fireEvent;\n }\n\n case 'dteExit': {\n if (!trigger.expiry) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const expiryDate = parseDate(trigger.expiry);\n const dte = calendarDaysBetween(pointDate, expiryDate);\n // Only fire if point is before/on expiry\n if (pointDate <= expiryDate && dte <= threshold) {\n fired = true;\n detail = `DTE ${dte} <= threshold ${threshold}`;\n }\n break;\n }\n\n case 'ditExit': {\n if (!trigger.openDate) break;\n const pointDate = parseDate(extractDate(point.timestamp));\n const openDate = parseDate(trigger.openDate);\n const dit = calendarDaysBetween(openDate, pointDate);\n if (dit >= threshold) {\n fired = true;\n detail = `DIT ${dit} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'clockTimeExit': {\n const clockTime = trigger.clockTime ?? '15:00';\n const pointTime = extractTime(point.timestamp);\n if (pointTime >= clockTime) {\n fired = true;\n detail = `Time ${pointTime} >= ${clockTime}`;\n }\n break;\n }\n\n case 'underlyingPriceMove': {\n if (!trigger.underlyingPrices) break;\n const price = trigger.underlyingPrices.get(point.timestamp);\n if (price == null) break;\n if (firstUnderlyingPrice === null) {\n firstUnderlyingPrice = price;\n break; // Can't compute move on first price\n }\n const pctMove = ((price - firstUnderlyingPrice) / firstUnderlyingPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `Underlying moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'positionDelta': {\n const netDelta = point.netDelta ?? 0;\n if (trigger.exitAbove != null) {\n if (netDelta > trigger.exitAbove) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (netDelta < trigger.exitBelow) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else if (Math.abs(netDelta) >= threshold) {\n fired = true;\n detail = `Net delta ${netDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'perLegDelta': {\n if (!point.legGreeks) break;\n if (trigger.legIndex != null) {\n // Target a specific leg\n if (trigger.legIndex >= point.legGreeks.length) break;\n const legDelta = point.legGreeks[trigger.legIndex].delta ?? 0;\n if (trigger.exitAbove != null) {\n if (legDelta > trigger.exitAbove) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} > exitAbove ${trigger.exitAbove}`;\n }\n } else if (trigger.exitBelow != null) {\n if (legDelta < trigger.exitBelow) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} < exitBelow ${trigger.exitBelow}`;\n }\n } else {\n // legIndex set but no directional fields — use abs() on that single leg\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${trigger.legIndex} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n }\n }\n } else {\n // No legIndex — iterate all legs with abs() (backward compat)\n for (let li = 0; li < point.legGreeks.length; li++) {\n const legDelta = point.legGreeks[li].delta ?? 0;\n if (Math.abs(legDelta) >= threshold) {\n fired = true;\n detail = `Leg ${li} delta ${legDelta.toFixed(4)} >= threshold ${threshold}`;\n break;\n }\n }\n }\n break;\n }\n\n case 'vixMove': {\n if (!trigger.vixPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n if (vix == null) break;\n if (firstVixPrice === null) {\n firstVixPrice = vix;\n break;\n }\n const pctMove = ((vix - firstVixPrice) / firstVixPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dMove': {\n if (!trigger.vix9dPrices) break;\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix9d == null) break;\n if (firstVix9dPrice === null) {\n firstVix9dPrice = vix9d;\n break;\n }\n const pctMove = ((vix9d - firstVix9dPrice) / firstVix9dPrice) * 100;\n if (Math.abs(pctMove) >= threshold) {\n fired = true;\n detail = `VIX9D moved ${pctMove.toFixed(2)}% (threshold ${threshold}%)`;\n }\n break;\n }\n\n case 'vix9dVixRatio': {\n if (!trigger.vixPrices || !trigger.vix9dPrices) break;\n const vix = trigger.vixPrices.get(point.timestamp);\n const vix9d = trigger.vix9dPrices.get(point.timestamp);\n if (vix == null || vix9d == null || vix === 0) break;\n const ratio = vix9d / vix;\n // If threshold >= 1, fire when ratio >= threshold (contango deepening)\n // If threshold < 1, fire when ratio <= threshold (backwardation)\n const crosses = threshold >= 1 ? ratio >= threshold : ratio <= threshold;\n if (crosses) {\n fired = true;\n detail = `VIX9D/VIX ratio ${ratio.toFixed(4)} crossed threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioThreshold': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (slRatio >= threshold) {\n fired = true;\n detail = `S/L ratio ${slRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n\n case 'slRatioMove': {\n const sw = trigger.spreadWidth ?? 0;\n const ct = trigger.contracts ?? 1;\n const mp = trigger.multiplier ?? 100;\n if (sw === 0) break;\n const slRatio = computeSLRatio(point, legs, sw, ct, mp);\n if (initialSLRatio === null) {\n initialSLRatio = slRatio;\n break; // Can't compute change on first point\n }\n const change = Math.abs(slRatio - initialSLRatio);\n if (change >= threshold) {\n fired = true;\n detail = `S/L ratio change ${change.toFixed(4)} from initial ${initialSLRatio.toFixed(4)} >= threshold ${threshold}`;\n }\n break;\n }\n }\n\n if (fired) {\n return {\n type,\n firedAt: point.timestamp,\n pnlAtFire: pnl,\n index: i,\n detail,\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// computeGroupPnl\n// ---------------------------------------------------------------------------\n\n/**\n * Compute per-group P&L at each timestamp from leg prices.\n * groupPnl[t] = sum over legIndices of (legPrices[i] - entryPrice[i]) * quantity[i] * multiplier[i]\n */\nfunction computeGroupPnl(\n pnlPath: PnlPoint[],\n legs: ReplayLeg[],\n legIndices: number[],\n): number[] {\n return pnlPath.map((point) => {\n let groupPnl = 0;\n for (const idx of legIndices) {\n if (idx < legs.length && idx < point.legPrices.length) {\n const leg = legs[idx];\n const markPrice = point.legPrices[idx];\n groupPnl += (markPrice - leg.entryPrice) * leg.quantity * leg.multiplier;\n }\n }\n return groupPnl;\n });\n}\n\n// ---------------------------------------------------------------------------\n// analyzeExitTriggers\n// ---------------------------------------------------------------------------\n\n/**\n * Run all triggers against the P&L path, find first-to-fire,\n * compute actual exit comparison, and evaluate leg group triggers.\n */\nexport function analyzeExitTriggers(config: {\n pnlPath: PnlPoint[];\n legs: ReplayLeg[];\n triggers: ExitTriggerConfig[];\n actualExitTimestamp?: string;\n legGroups?: LegGroupConfig[];\n}): {\n overall: ExitTriggerResult;\n legGroups?: LegGroupResult[];\n} {\n const { pnlPath, legs, triggers, actualExitTimestamp, legGroups } = config;\n\n // Evaluate all triggers\n const fireEvents: TriggerFireEvent[] = [];\n let allPartialCloses: PartialClose[] = [];\n for (const trigger of triggers) {\n if (trigger.type === 'profitAction') {\n // Use the partial-close-aware helper for profitAction\n const paResult = evaluateProfitAction(trigger, pnlPath, legs);\n if (paResult.fireEvent) {\n fireEvents.push(paResult.fireEvent);\n }\n if (paResult.partialCloses.length > 0) {\n allPartialCloses = allPartialCloses.concat(paResult.partialCloses);\n }\n } else {\n const event = evaluateTrigger(trigger, pnlPath, legs);\n if (event) {\n fireEvents.push(event);\n }\n }\n }\n\n // Sort by fire index (earliest first)\n fireEvents.sort((a, b) => a.index - b.index);\n\n const firstToFire = fireEvents.length > 0 ? fireEvents[0] : null;\n\n // Actual exit comparison\n let actualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && firstToFire) {\n // Find closest point to actualExitTimestamp\n let closestIdx = 0;\n let closestDist = Infinity;\n for (let i = 0; i < pnlPath.length; i++) {\n // Simple string comparison — timestamps are lexicographically ordered\n const dist = Math.abs(pnlPath[i].timestamp.localeCompare(actualExitTimestamp));\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n if (dist < closestDist) {\n closestDist = dist;\n closestIdx = i;\n }\n }\n // Fallback: use last point if actualExitTimestamp is after all points\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualPnl = pnlPath[closestIdx].strategyPnl;\n actualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualPnl,\n pnlDifference: firstToFire.pnlAtFire - actualPnl,\n };\n }\n\n // Build summary\n let summary: string;\n if (!firstToFire) {\n summary = `No triggers fired across ${pnlPath.length} data points.`;\n } else if (actualExit) {\n const betterWorse = actualExit.pnlDifference > 0 ? 'better' : 'worse';\n summary = `${firstToFire.type} fired at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `Actual exit at ${actualExit.timestamp} (P&L $${actualExit.pnl.toFixed(2)}). ` +\n `Trigger was $${Math.abs(actualExit.pnlDifference).toFixed(2)} ${betterWorse}.`;\n } else {\n summary = `${firstToFire.type} fired first at ${firstToFire.firedAt} (P&L $${firstToFire.pnlAtFire.toFixed(2)}). ` +\n `${fireEvents.length} trigger(s) fired total.`;\n }\n\n const overall: ExitTriggerResult = {\n triggers: fireEvents,\n firstToFire,\n actualExit,\n partialCloses: allPartialCloses.length > 0 ? allPartialCloses : undefined,\n summary,\n };\n\n // Leg group evaluation\n let legGroupResults: LegGroupResult[] | undefined;\n if (legGroups && legGroups.length > 0) {\n legGroupResults = legGroups.map((group) => {\n const groupPnlArr = computeGroupPnl(pnlPath, legs, group.legIndices);\n\n // Build a synthetic PnlPoint[] for this group with groupPnl as strategyPnl\n const groupPath: PnlPoint[] = pnlPath.map((point, idx) => ({\n ...point,\n strategyPnl: groupPnlArr[idx],\n // Filter legPrices/legGreeks to only this group's legs\n legPrices: group.legIndices.map((li) => point.legPrices[li] ?? 0),\n legGreeks: point.legGreeks\n ? group.legIndices.map((li) => point.legGreeks![li])\n : undefined,\n }));\n\n // Build group legs subset\n const groupLegs = group.legIndices.map((li) => legs[li]);\n\n // Evaluate per-group triggers\n const groupFireEvents: TriggerFireEvent[] = [];\n for (const trigger of group.triggers) {\n const event = evaluateTrigger(trigger, groupPath, groupLegs);\n if (event) groupFireEvents.push(event);\n }\n groupFireEvents.sort((a, b) => a.index - b.index);\n\n const groupFirstToFire = groupFireEvents.length > 0 ? groupFireEvents[0] : null;\n\n // Actual exit for group\n let groupActualExit: ExitTriggerResult['actualExit'];\n if (actualExitTimestamp && groupFirstToFire) {\n let closestIdx = pnlPath.length - 1;\n for (let i = 0; i < pnlPath.length; i++) {\n if (pnlPath[i].timestamp === actualExitTimestamp) {\n closestIdx = i;\n break;\n }\n }\n if (actualExitTimestamp > pnlPath[pnlPath.length - 1].timestamp) {\n closestIdx = pnlPath.length - 1;\n }\n const actualGroupPnl = groupPnlArr[closestIdx];\n groupActualExit = {\n timestamp: pnlPath[closestIdx].timestamp,\n pnl: actualGroupPnl,\n pnlDifference: groupFirstToFire.pnlAtFire - actualGroupPnl,\n };\n }\n\n const groupSummary = groupFirstToFire\n ? `${group.label}: ${groupFirstToFire.type} fired at ${groupFirstToFire.firedAt} (group P&L $${groupFirstToFire.pnlAtFire.toFixed(2)})`\n : `${group.label}: No triggers fired.`;\n\n return {\n label: group.label,\n result: {\n triggers: groupFireEvents,\n firstToFire: groupFirstToFire,\n actualExit: groupActualExit,\n summary: groupSummary,\n },\n groupPnl: groupPnlArr,\n };\n });\n }\n\n return {\n overall,\n legGroups: legGroupResults,\n };\n}\n","/**\n * Exit Analysis Tools\n *\n * MCP tools for analyzing exit triggers and decomposing P&L into greek factor\n * contributions. Both tools run trade replay internally -- a single tool call\n * fetches data, replays the trade, and analyzes the results.\n *\n * Tools registered:\n * - analyze_exit_triggers -- Evaluate 15 trigger types against a replay P&L path\n * - decompose_greeks -- Decompose P&L into delta/gamma/theta/vega/residual factors\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { handleReplayTrade } from \"./replay.js\";\nimport { getProvider } from \"../utils/market-provider.js\";\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type LegGroupConfig,\n} from \"../utils/exit-triggers.js\";\nimport {\n decomposeGreeks,\n type LegGroupDef,\n} from \"../utils/greeks-decomposition.js\";\nimport { markPrice } from \"../utils/trade-replay.js\";\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Leg schema (shared between both tools)\n// ---------------------------------------------------------------------------\n\nconst legSchema = z.object({\n ticker: z.string(),\n strike: z.number(),\n type: z.enum([\"C\", \"P\"]),\n expiry: z.string(),\n quantity: z.number(),\n entry_price: z.number(),\n});\n\n// ---------------------------------------------------------------------------\n// analyze_exit_triggers schema\n// ---------------------------------------------------------------------------\n\nexport const analyzeExitTriggersSchema = z.object({\n // Replay inputs (same as replay_trade per D-02)\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n // Trigger configs per D-03\n triggers: z.array(triggerConfigSchema)\n .describe(\"Exit triggers to evaluate against the P&L path\"),\n\n // Per D-05\n actual_exit_timestamp: z.string().optional()\n .describe(\"Actual exit time for comparison (format: YYYY-MM-DD HH:MM)\"),\n\n // Per D-06\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' omits per-step trigger states, 'full' includes all fire events\"),\n});\n\n// ---------------------------------------------------------------------------\n// decompose_greeks schema\n// ---------------------------------------------------------------------------\n\nexport const decomposeGreeksSchema = z.object({\n // Same replay inputs\n legs: z.array(legSchema).optional(),\n block_id: z.string().optional(),\n trade_index: z.number().optional(),\n open_date: z.string().optional(),\n close_date: z.string().optional(),\n multiplier: z.number().default(100),\n\n // Per D-08\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n })).optional().describe(\"Leg grouping for per-group vega attribution (e.g., front_month vs back_month)\"),\n\n format: z.enum([\"summary\", \"full\"]).default(\"summary\")\n .describe(\"'summary' shows ranked factors, 'full' includes per-step contributions\"),\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Reverse-map weekly roots to standard root for underlying/VIX fetching\nconst REVERSE_ROOT_MAP: Record<string, string> = {\n SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',\n};\n\n/**\n * Extract the underlying root ticker from the first replay leg's OCC ticker.\n * Maps weekly roots (SPXW, NDXP) back to their standard root.\n */\nfunction extractUnderlyingTicker(occTicker: string): string {\n const rootMatch = occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n return REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;\n}\n\n/**\n * Fetch VIX or VIX9D minute bars and build a timestamp->price map.\n */\nasync function fetchPriceMap(\n ticker: string,\n from: string,\n to: string,\n): Promise<Map<string, number>> {\n const map = new Map<string, number>();\n try {\n const bars = await getProvider().fetchBars({\n ticker,\n from,\n to,\n timespan: \"minute\",\n assetClass: \"index\",\n });\n for (const b of bars) {\n const ts = `${b.date} ${b.time ?? ''}`.trim();\n map.set(ts, markPrice(b));\n }\n } catch {\n // VIX/VIX9D data is best-effort\n }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// handleAnalyzeExitTriggers\n// ---------------------------------------------------------------------------\n\nexport async function handleAnalyzeExitTriggers(\n params: z.infer<typeof analyzeExitTriggersSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<ReturnType<typeof analyzeExitTriggers>> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n triggers, actual_exit_timestamp, leg_groups,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // Compute entry cost for percentage-based triggers (D-11)\n const entryCost = replayLegs.reduce((sum, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n if (pnlPath.length === 0) {\n return {\n overall: {\n triggers: [],\n firstToFire: null,\n summary: 'No P&L data available from replay.',\n },\n };\n }\n\n // 2. Determine date range from replay path\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n // 3. Check which external data maps are needed\n const allTriggerTypes = new Set(triggers.map(t => t.type));\n const groupTriggerTypes = new Set(\n (leg_groups ?? []).flatMap(g => g.triggers.map(t => t.type))\n );\n for (const t of groupTriggerTypes) allTriggerTypes.add(t);\n\n // Determine underlying ticker for underlying price triggers\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0].occTicker);\n\n // Fetch VIX/VIX9D/underlying price maps as needed\n let vixPrices: Map<string, number> | undefined;\n let vix9dPrices: Map<string, number> | undefined;\n let underlyingPrices: Map<string, number> | undefined;\n\n const needsVix = allTriggerTypes.has('vixMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsVix9d = allTriggerTypes.has('vix9dMove') || allTriggerTypes.has('vix9dVixRatio');\n const needsUnderlying = allTriggerTypes.has('underlyingPriceMove');\n\n if (needsVix) {\n vixPrices = await fetchPriceMap('VIX', firstDate, lastDate);\n }\n if (needsVix9d) {\n vix9dPrices = await fetchPriceMap('VIX9D', firstDate, lastDate);\n }\n if (needsUnderlying) {\n underlyingPrices = await fetchPriceMap(\n underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map tool trigger params to ExitTriggerConfig[] with data maps\n const exitTriggers: ExitTriggerConfig[] = triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n }));\n\n // 5. Map leg groups with their triggers\n const legGroupConfigs: LegGroupConfig[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers.map(t => ({\n type: t.type,\n threshold: t.threshold,\n unit: t.unit,\n entryCost,\n expiry: t.expiry,\n openDate: t.openDate,\n clockTime: t.clockTime,\n trailAmount: t.trailAmount,\n steps: t.steps,\n spreadWidth: t.spreadWidth,\n contracts: t.contracts,\n multiplier,\n underlyingPrices,\n vixPrices,\n vix9dPrices,\n })),\n }));\n\n // 6. Run the pure analysis engine\n return analyzeExitTriggers({\n pnlPath,\n legs: replayLegs,\n triggers: exitTriggers,\n actualExitTimestamp: actual_exit_timestamp,\n legGroups: legGroupConfigs,\n });\n}\n\n// ---------------------------------------------------------------------------\n// handleDecomposeGreeks\n// ---------------------------------------------------------------------------\n\nexport async function handleDecomposeGreeks(\n params: z.infer<typeof decomposeGreeksSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<import(\"../utils/greeks-decomposition.js\").GreeksDecompositionResult> {\n const {\n legs: inputLegs, block_id, trade_index,\n open_date, close_date, multiplier,\n leg_groups, format,\n } = params;\n\n // 1. Run replay to get full P&L path with greeks\n const replayResult = await handleReplayTrade(\n {\n legs: inputLegs,\n block_id,\n trade_index,\n open_date,\n close_date,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n const pnlPath = replayResult.pnlPath;\n const replayLegs = replayResult.legs;\n\n // 2. Check greeks data availability\n if (pnlPath.length > 0 && !pnlPath[0].legGreeks) {\n throw new Error(\n \"No greeks data available. Ensure MASSIVE_API_KEY is set and underlying price data exists.\"\n );\n }\n\n // 3. Fetch underlying prices for decomposition\n const underlyingTicker = extractUnderlyingTicker(replayLegs[0]?.occTicker ?? '');\n let underlyingPrices: Map<string, number> | undefined;\n\n if (pnlPath.length > 0 && underlyingTicker) {\n const firstDate = pnlPath[0].timestamp.slice(0, 10);\n const lastDate = pnlPath[pnlPath.length - 1].timestamp.slice(0, 10);\n\n underlyingPrices = await fetchPriceMap(\n underlyingTicker, firstDate, lastDate,\n );\n }\n\n // 4. Map leg groups\n const legGroupDefs: LegGroupDef[] | undefined = leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n }));\n\n // 5. Build leg pricing inputs from OCC tickers for full revaluation\n const DIVIDEND_YIELDS: Record<string, number> = {\n SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,\n };\n const rootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);\n const rawRoot = rootMatch ? rootMatch[1] : '';\n const divYield = DIVIDEND_YIELDS[rawRoot] ?? 0;\n\n const legPricingInputs = replayLegs.map(leg => {\n const m = leg.occTicker.match(/^[A-Z]+(\\d{6})([CP])(\\d{8})$/);\n if (!m) return { strike: 0, type: 'C' as const, expiryDate: '' };\n return {\n strike: parseInt(m[3], 10) / 1000,\n type: m[2] as 'C' | 'P',\n expiryDate: `20${m[1].slice(0, 2)}-${m[1].slice(2, 4)}-${m[1].slice(4, 6)}`,\n };\n });\n\n // 6. Run decomposition with full revaluation\n const result = decomposeGreeks({\n pnlPath,\n legs: replayLegs,\n underlyingPrices,\n legGroups: legGroupDefs,\n legPricingInputs,\n riskFreeRate: 0.045,\n dividendYield: divYield,\n });\n\n // 7. Strip steps if format=\"summary\"\n if (format === \"summary\") {\n for (const factor of result.factors) {\n factor.steps = [];\n }\n if (result.legGroupVega) {\n for (const group of result.legGroupVega) {\n group.steps = [];\n }\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n): void {\n server.registerTool(\n \"analyze_exit_triggers\",\n {\n description:\n \"Analyze when exit triggers would fire on a trade replay. Runs replay internally \" +\n \"-- provide block_id + trade_index or explicit legs. Evaluates 14 trigger types \" +\n \"(profit target, stop loss, trailing stop, DTE, DIT, clock time, underlying move, \" +\n \"delta, VIX moves, S/L ratio) against minute-by-minute P&L path with greeks. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss (requires MASSIVE_API_KEY).\",\n inputSchema: analyzeExitTriggersSchema,\n },\n async (params) => {\n try {\n const result = await handleAnalyzeExitTriggers(params, baseDir);\n\n const summary = result.overall.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error analyzing exit triggers: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n server.registerTool(\n \"decompose_greeks\",\n {\n description:\n \"Decompose a trade's P&L into greek factor contributions (delta, gamma, theta, \" +\n \"vega, residual). Runs replay internally. Shows which factor drove P&L movement \" +\n \"and by how much. For calendar/double-calendar strategies, includes per-leg-group \" +\n \"vega attribution showing front vs back month IV divergence. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss (requires MASSIVE_API_KEY).\",\n inputSchema: decomposeGreeksSchema,\n },\n async (params) => {\n try {\n const result = await handleDecomposeGreeks(params, baseDir);\n\n const summary = result.summary;\n return createToolOutput(summary, result);\n } catch (error) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error decomposing greeks: ${(error as Error).message}`,\n },\n ],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * Batch Exit Analysis Engine\n *\n * Pure logic module (no I/O, no DuckDB, no fetch) that takes pre-analyzed\n * trade inputs and a candidate exit policy, evaluates the policy against each\n * trade's P&L path, and computes aggregate statistics with per-trigger attribution.\n *\n * This is the computational heart of the `batch_exit_analysis` MCP tool.\n */\n\nimport {\n analyzeExitTriggers,\n type ExitTriggerConfig,\n type TriggerType,\n type LegGroupConfig,\n type PartialClose,\n} from './exit-triggers.js';\nimport type { PnlPoint, ReplayLeg } from './trade-replay.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type BaselineMode = 'actual' | 'holdToEnd';\n\nexport interface BatchExitConfig {\n /** Triggers to evaluate as candidate exit policy. */\n candidatePolicy: ExitTriggerConfig[];\n /** Optional per-group triggers (passed through to analyzeExitTriggers). */\n legGroups?: LegGroupConfig[];\n /** Baseline mode: 'actual' uses tradelog P&L, 'holdToEnd' uses last path point. */\n baselineMode: BaselineMode;\n /** Output density: 'summary' omits per-trade breakdown; 'full' includes it. */\n format: 'summary' | 'full';\n}\n\nexport interface TradeInput {\n tradeIndex: number;\n /** Trade open date YYYY-MM-DD */\n dateOpened: string;\n /** Actual P&L from tradelog pl field (used when baselineMode='actual'). */\n actualPnl: number;\n /** Full replay P&L path from trade-replay module. */\n pnlPath: PnlPoint[];\n /** Replay legs parallel to pnlPath.legPrices. */\n legs: ReplayLeg[];\n /** Entry cost for percentage-based triggers (D-11). */\n entryCost?: number;\n}\n\nexport interface TradeExitResult {\n tradeIndex: number;\n dateOpened: string;\n /** Actual P&L from tradelog. */\n actualPnl: number;\n /** P&L if candidate policy was applied. */\n candidatePnl: number;\n /** Baseline P&L (actual or holdToEnd). */\n baselinePnl: number;\n /** candidatePnl - baselinePnl */\n pnlDelta: number;\n /** Which trigger fired first, or 'noTrigger'. */\n triggerFired: TriggerType | 'noTrigger';\n /** Timestamp when trigger fired, or null. */\n fireTimestamp: string | null;\n /** Partial position closes from profitAction steps (if any). */\n partialCloses?: PartialClose[];\n}\n\nexport interface TriggerAttribution {\n trigger: TriggerType | 'noTrigger';\n /** How many trades this trigger fired first on. */\n count: number;\n /** Average candidate P&L when this trigger fired. */\n avgPnl: number;\n /** Sum candidate P&L for this trigger group. */\n totalPnl: number;\n /** Average pnlDelta vs baseline for this trigger group. */\n avgDelta: number;\n}\n\nexport interface AggregateStats {\n totalTrades: number;\n /** candidatePnl > 0 */\n winningTrades: number;\n /** candidatePnl < 0 */\n losingTrades: number;\n /** winningTrades / totalTrades */\n winRate: number;\n /** Sum of candidatePnl */\n totalPnl: number;\n /** Mean candidatePnl */\n avgPnl: number;\n /** Mean of winning candidatePnls */\n avgWin: number;\n /** Mean of losing candidatePnls */\n avgLoss: number;\n maxWin: number;\n maxLoss: number;\n /** sum(wins) / abs(sum(losses)); Infinity if no losses */\n profitFactor: number;\n /** Max sequential drawdown from equity curve (cumsum of candidatePnls) */\n maxDrawdown: number;\n /** mean/stddev of candidatePnls; null if < 2 trades */\n sharpeRatio: number | null;\n maxWinStreak: number;\n maxLossStreak: number;\n // Deltas vs baseline\n baselineTotalPnl: number;\n /** totalPnl - baselineTotalPnl */\n totalPnlDelta: number;\n baselineWinRate: number;\n}\n\nexport interface BatchExitResult {\n aggregate: AggregateStats;\n triggerAttribution: TriggerAttribution[];\n /** Empty if format='summary'. */\n perTrade: TradeExitResult[];\n baselineMode: BaselineMode;\n summary: string;\n profileContext?: {\n structureType: string;\n exitRules: string[];\n };\n /** Trades skipped due to replay errors (D-15). */\n skippedTrades?: Array<{ tradeIndex: number; dateOpened: string; error: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// computeAggregateStats\n// ---------------------------------------------------------------------------\n\n/**\n * Compute aggregate statistics from a set of per-trade exit results.\n */\nexport function computeAggregateStats(tradeResults: TradeExitResult[]): AggregateStats {\n if (tradeResults.length === 0) {\n return {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n };\n }\n\n const candidatePnls = tradeResults.map(r => r.candidatePnl);\n const baselinePnls = tradeResults.map(r => r.baselinePnl);\n\n const winningTrades = candidatePnls.filter(p => p > 0).length;\n const losingTrades = candidatePnls.filter(p => p < 0).length;\n const totalTrades = tradeResults.length;\n const winRate = winningTrades / totalTrades;\n\n const totalPnl = candidatePnls.reduce((sum, p) => sum + p, 0);\n const avgPnl = totalPnl / totalTrades;\n\n const wins = candidatePnls.filter(p => p > 0);\n const losses = candidatePnls.filter(p => p < 0);\n\n const avgWin = wins.length > 0 ? wins.reduce((s, p) => s + p, 0) / wins.length : 0;\n const avgLoss = losses.length > 0 ? losses.reduce((s, p) => s + p, 0) / losses.length : 0;\n const maxWin = wins.length > 0 ? Math.max(...wins) : 0;\n const maxLoss = losses.length > 0 ? Math.min(...losses) : 0;\n\n // Profit factor: sum(wins) / abs(sum(losses)), Infinity if no losses\n const sumWins = wins.reduce((s, p) => s + p, 0);\n const sumLosses = losses.reduce((s, p) => s + p, 0);\n const profitFactor = losses.length === 0\n ? Infinity\n : sumWins / Math.abs(sumLosses);\n\n // Max drawdown from equity curve (cumsum of candidatePnls)\n let runningPeak = 0;\n let equity = 0;\n let maxDrawdown = 0;\n for (const pnl of candidatePnls) {\n equity += pnl;\n if (equity > runningPeak) runningPeak = equity;\n const dd = runningPeak - equity;\n if (dd > maxDrawdown) maxDrawdown = dd;\n }\n\n // Sharpe ratio: mean / sample stddev (N-1), null if < 2 trades\n let sharpeRatio: number | null = null;\n if (totalTrades >= 2) {\n const mean = avgPnl;\n const variance =\n candidatePnls.reduce((sum, p) => sum + (p - mean) ** 2, 0) / (totalTrades - 1);\n const stddev = Math.sqrt(variance);\n sharpeRatio = stddev === 0 ? null : mean / stddev;\n }\n\n // Win/loss streaks\n let maxWinStreak = 0;\n let maxLossStreak = 0;\n let currentWinStreak = 0;\n let currentLossStreak = 0;\n for (const pnl of candidatePnls) {\n if (pnl > 0) {\n currentWinStreak++;\n currentLossStreak = 0;\n if (currentWinStreak > maxWinStreak) maxWinStreak = currentWinStreak;\n } else if (pnl < 0) {\n currentLossStreak++;\n currentWinStreak = 0;\n if (currentLossStreak > maxLossStreak) maxLossStreak = currentLossStreak;\n } else {\n // Breakeven — reset both streaks\n currentWinStreak = 0;\n currentLossStreak = 0;\n }\n }\n\n // Baseline aggregates\n const baselineTotalPnl = baselinePnls.reduce((sum, p) => sum + p, 0);\n const baselineWins = baselinePnls.filter(p => p > 0).length;\n const baselineWinRate = baselineWins / totalTrades;\n\n return {\n totalTrades,\n winningTrades,\n losingTrades,\n winRate,\n totalPnl,\n avgPnl,\n avgWin,\n avgLoss,\n maxWin,\n maxLoss,\n profitFactor,\n maxDrawdown,\n sharpeRatio,\n maxWinStreak,\n maxLossStreak,\n baselineTotalPnl,\n totalPnlDelta: totalPnl - baselineTotalPnl,\n baselineWinRate,\n };\n}\n\n// ---------------------------------------------------------------------------\n// computeTriggerAttribution\n// ---------------------------------------------------------------------------\n\n/**\n * Group trade results by which trigger fired first.\n * Returns attribution sorted by count descending.\n */\nexport function computeTriggerAttribution(\n tradeResults: TradeExitResult[],\n): TriggerAttribution[] {\n const groups = new Map<\n TriggerType | 'noTrigger',\n { count: number; totalPnl: number; totalDelta: number }\n >();\n\n for (const result of tradeResults) {\n const key = result.triggerFired;\n const existing = groups.get(key);\n if (existing) {\n existing.count++;\n existing.totalPnl += result.candidatePnl;\n existing.totalDelta += result.pnlDelta;\n } else {\n groups.set(key, {\n count: 1,\n totalPnl: result.candidatePnl,\n totalDelta: result.pnlDelta,\n });\n }\n }\n\n return Array.from(groups.entries())\n .map(([trigger, { count, totalPnl, totalDelta }]) => ({\n trigger,\n count,\n totalPnl,\n avgPnl: totalPnl / count,\n avgDelta: totalDelta / count,\n }))\n .sort((a, b) => b.count - a.count);\n}\n\n// ---------------------------------------------------------------------------\n// analyzeBatch\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a candidate exit policy against a set of trade replay results.\n *\n * For each trade:\n * 1. Run analyzeExitTriggers with the candidate policy against the trade's P&L path.\n * 2. candidatePnl = firstToFire.pnlAtFire if trigger fired, else last path point P&L.\n * 3. baselinePnl = trade.actualPnl if baselineMode='actual', else last path point P&L.\n * 4. Build TradeExitResult.\n *\n * Then compute aggregate stats and trigger attribution.\n */\nexport function analyzeBatch(\n trades: TradeInput[],\n config: BatchExitConfig,\n): BatchExitResult {\n if (trades.length === 0) {\n const emptyAggregate = computeAggregateStats([]);\n return {\n aggregate: emptyAggregate,\n triggerAttribution: [],\n perTrade: [],\n baselineMode: config.baselineMode,\n summary: 'Analyzed 0 trades: no data.',\n };\n }\n\n const { candidatePolicy, legGroups, baselineMode, format } = config;\n\n const perTradeResults: TradeExitResult[] = trades.map(trade => {\n const { pnlPath, legs, actualPnl, tradeIndex, dateOpened, entryCost } = trade;\n\n // Last path point P&L — used as holdToEnd value\n const lastPnl = pnlPath.length > 0\n ? pnlPath[pnlPath.length - 1].strategyPnl\n : 0;\n\n // Copy entryCost onto each trigger config for percentage-based triggers (D-11)\n const triggersWithCost = candidatePolicy.map(t => ({\n ...t,\n entryCost,\n }));\n\n const legGroupsWithCost = legGroups?.map(group => ({\n ...group,\n triggers: group.triggers.map(trigger => ({\n ...trigger,\n entryCost,\n })),\n }));\n\n // Run exit trigger analysis with candidate policy\n const analysisResult = analyzeExitTriggers({\n pnlPath,\n legs,\n triggers: triggersWithCost,\n legGroups: legGroupsWithCost,\n });\n\n const { firstToFire, partialCloses } = analysisResult.overall;\n\n // Candidate P&L: account for partial closes from profitAction steps\n let candidatePnl: number;\n if (partialCloses && partialCloses.length > 0) {\n // Sum of partial close P&Ls\n const partialPnl = partialCloses.reduce((sum, pc) => sum + pc.pnlAtFire, 0);\n const closedAllocation = partialCloses.reduce((sum, pc) => sum + pc.allocation, 0);\n const remainingAllocation = 1 - closedAllocation;\n // Remaining position: firstToFire.pnlAtFire already reflects remaining allocation,\n // or if no trigger fired, scale last P&L by remaining allocation\n const remainingPnl = firstToFire !== null\n ? firstToFire.pnlAtFire\n : lastPnl * remainingAllocation;\n candidatePnl = partialPnl + remainingPnl;\n } else {\n // No partial closes: original behavior\n candidatePnl = firstToFire !== null ? firstToFire.pnlAtFire : lastPnl;\n }\n\n // Baseline P&L depends on mode\n const baselinePnl = baselineMode === 'actual' ? actualPnl : lastPnl;\n\n const pnlDelta = candidatePnl - baselinePnl;\n\n const triggerFired: TriggerType | 'noTrigger' =\n firstToFire !== null ? firstToFire.type : 'noTrigger';\n const fireTimestamp = firstToFire !== null ? firstToFire.firedAt : null;\n\n return {\n tradeIndex,\n dateOpened,\n actualPnl,\n candidatePnl,\n baselinePnl,\n pnlDelta,\n triggerFired,\n fireTimestamp,\n partialCloses: partialCloses && partialCloses.length > 0 ? partialCloses : undefined,\n };\n });\n\n const aggregate = computeAggregateStats(perTradeResults);\n const triggerAttribution = computeTriggerAttribution(perTradeResults);\n\n // Build summary string\n const topTrigger = triggerAttribution.length > 0 ? triggerAttribution[0] : null;\n const topTriggerStr = topTrigger\n ? `Top trigger: ${topTrigger.trigger} fired on ${topTrigger.count} trades.`\n : 'No triggers fired.';\n\n const summary =\n `Analyzed ${trades.length} trades: candidate win rate ${(aggregate.winRate * 100).toFixed(1)}%, ` +\n `total P&L $${aggregate.totalPnl.toFixed(2)} (delta $${aggregate.totalPnlDelta.toFixed(2)} vs baseline). ` +\n topTriggerStr;\n\n return {\n aggregate,\n triggerAttribution,\n perTrade: format === 'summary' ? [] : perTradeResults,\n baselineMode,\n summary,\n };\n}\n","/**\n * Batch Exit Analysis Tool\n *\n * MCP tool that evaluates a candidate exit policy across multiple trades in a\n * block. Queries trades from DuckDB, replays each one (checking market.intraday\n * cache before fetching from Massive), evaluates the candidate policy via the\n * pure batch exit analysis engine, and returns aggregate statistics with\n * per-trigger attribution.\n *\n * Tools registered:\n * - batch_exit_analysis -- Evaluate a candidate exit policy across an entire block\n */\n\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { getConnection } from \"../db/connection.js\";\nimport { createToolOutput } from \"../utils/output-formatter.js\";\nimport { handleReplayTrade } from \"./replay.js\";\nimport {\n analyzeBatch,\n type TradeInput,\n type BatchExitConfig,\n type BatchExitResult,\n} from \"../utils/batch-exit-analysis.js\";\nimport { getProfile } from \"../db/profile-schemas.js\";\nimport type { ExitTriggerConfig, LegGroupConfig } from \"../utils/exit-triggers.js\";\n\n// ---------------------------------------------------------------------------\n// Concurrency limiter — hand-rolled semaphore, no external dependency (D-15)\n// ---------------------------------------------------------------------------\n\n/**\n * Simple concurrency limiter. Runs async tasks with at most `limit` in flight.\n * No external dependency — hand-rolled semaphore pattern per D-15.\n */\nasync function mapWithLimit<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let idx = 0;\n\n async function worker(): Promise<void> {\n while (idx < items.length) {\n const i = idx++;\n results[i] = await fn(items[i]);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Shared trigger type enum (mirrors exit-analysis.ts)\n// ---------------------------------------------------------------------------\n\nconst triggerTypeEnum = z.enum([\n 'profitTarget', 'stopLoss', 'trailingStop', 'profitAction',\n 'dteExit', 'ditExit', 'clockTimeExit',\n 'underlyingPriceMove', 'positionDelta', 'perLegDelta',\n 'vixMove', 'vix9dMove', 'vix9dVixRatio',\n 'slRatioThreshold', 'slRatioMove',\n]);\n\nconst triggerConfigSchema = z.object({\n type: triggerTypeEnum,\n threshold: z.number(),\n unit: z.enum(['percent', 'dollar']).default('dollar').optional(),\n expiry: z.string().optional(),\n openDate: z.string().optional(),\n clockTime: z.string().optional(),\n trailAmount: z.number().optional(),\n steps: z.array(z.object({\n armAt: z.number(),\n stopAt: z.number(),\n closeAllocationPct: z.number().min(0).max(1).optional()\n .describe(\"Fraction of REMAINING position to close at this milestone (0-1)\"),\n })).optional(),\n spreadWidth: z.number().optional(),\n contracts: z.number().optional(),\n legIndex: z.number().optional()\n .describe(\"0-based leg index for perLegDelta — targets specific leg\"),\n exitAbove: z.number().optional()\n .describe(\"Fire when value exceeds this (directional, no abs)\"),\n exitBelow: z.number().optional()\n .describe(\"Fire when value drops below this (directional, no abs)\"),\n});\n\n// ---------------------------------------------------------------------------\n// Zod Schema\n// ---------------------------------------------------------------------------\n\nexport const batchExitAnalysisSchema = z.object({\n block_id: z.string().describe(\"Block ID to analyze trades from\"),\n\n strategy: z.string().optional()\n .describe(\"Filter trades by strategy name (case-insensitive ILIKE)\"),\n\n date_range: z.object({\n from: z.string().optional().describe(\"Start date YYYY-MM-DD\"),\n to: z.string().optional().describe(\"End date YYYY-MM-DD\"),\n }).optional().describe(\"Filter trades by date range\"),\n\n candidate_policy: z.array(triggerConfigSchema)\n .describe(\"Candidate exit policy triggers to evaluate -- same schema as analyze_exit_triggers\"),\n\n leg_groups: z.array(z.object({\n label: z.string(),\n leg_indices: z.array(z.number()),\n triggers: z.array(triggerConfigSchema),\n })).optional().describe(\"Per-leg-group exit triggers for multi-structure strategies\"),\n\n baseline_mode: z.enum(['actual', 'holdToEnd']).default('actual')\n .describe(\"'actual' compares candidate vs trade's actual P&L; 'holdToEnd' compares vs last replay timestamp\"),\n\n limit: z.number().min(1).max(200).default(50)\n .describe(\"Max trades to analyze. Most recent trades selected\"),\n\n min_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L >= this value\"),\n\n max_pl: z.number().optional()\n .describe(\"Only include trades with actual P&L <= this value\"),\n\n multiplier: z.number().default(100)\n .describe(\"Contract multiplier (default 100)\"),\n\n format: z.enum(['summary', 'full']).default('summary')\n .describe(\"'summary' returns aggregate stats + trigger attribution; 'full' adds per-trade breakdown\"),\n});\n\n// ---------------------------------------------------------------------------\n// Handler\n// ---------------------------------------------------------------------------\n\nexport async function handleBatchExitAnalysis(\n params: z.infer<typeof batchExitAnalysisSchema>,\n baseDir: string,\n injectedConn?: import(\"@duckdb/node-api\").DuckDBConnection,\n): Promise<BatchExitResult> {\n const {\n block_id,\n strategy,\n date_range,\n candidate_policy,\n leg_groups,\n baseline_mode,\n limit,\n min_pl,\n max_pl,\n multiplier,\n format,\n } = params;\n\n // 1. Query trades from DuckDB with deterministic ROW_NUMBER ordering\n const conn = injectedConn ?? await getConnection(baseDir);\n const escapedBlockId = block_id.replace(/'/g, \"''\");\n\n // Build WHERE clauses\n const whereClauses: string[] = [`block_id = '${escapedBlockId}'`];\n\n if (strategy) {\n const escapedStrategy = strategy.replace(/'/g, \"''\");\n whereClauses.push(`strategy ILIKE '%${escapedStrategy}%'`);\n }\n if (date_range?.from) {\n whereClauses.push(`date_opened >= '${date_range.from}'`);\n }\n if (date_range?.to) {\n whereClauses.push(`date_opened <= '${date_range.to}'`);\n }\n if (min_pl !== undefined) {\n whereClauses.push(`pl >= ${min_pl}`);\n }\n if (max_pl !== undefined) {\n whereClauses.push(`pl <= ${max_pl}`);\n }\n\n // ROW_NUMBER must be computed over the FULL block (no strategy/date filters)\n // because handleReplayTrade resolves trade_index as OFFSET against the full block.\n // Filters are applied AFTER numbering to preserve the global index.\n const filterClauses = whereClauses.slice(1); // drop block_id clause (already in CTE)\n const query = `\n WITH numbered AS (\n SELECT *, ROW_NUMBER() OVER (ORDER BY date_opened, rowid) - 1 AS trade_idx\n FROM trades.trade_data\n WHERE block_id = '${escapedBlockId}'\n )\n SELECT trade_idx, pl, date_opened\n FROM numbered\n ${filterClauses.length > 0 ? 'WHERE ' + filterClauses.join(' AND ') : ''}\n ORDER BY date_opened DESC\n LIMIT ${limit}\n `;\n\n const queryResult = await conn.runAndReadAll(query);\n const rows = queryResult.getRows();\n\n if (rows.length === 0) {\n const emptyResult: BatchExitResult = {\n aggregate: {\n totalTrades: 0,\n winningTrades: 0,\n losingTrades: 0,\n winRate: 0,\n totalPnl: 0,\n avgPnl: 0,\n avgWin: 0,\n avgLoss: 0,\n maxWin: 0,\n maxLoss: 0,\n profitFactor: 0,\n maxDrawdown: 0,\n sharpeRatio: null,\n maxWinStreak: 0,\n maxLossStreak: 0,\n baselineTotalPnl: 0,\n totalPnlDelta: 0,\n baselineWinRate: 0,\n },\n triggerAttribution: [],\n perTrade: [],\n baselineMode: baseline_mode,\n summary: 'Analyzed 0 trades: no matching trades found.',\n };\n return emptyResult;\n }\n\n // 2. Replay trades in parallel with concurrency limit (D-14)\n const MAX_CONCURRENT_REPLAYS = 5;\n\n type ReplayOutcome =\n | { ok: true; input: TradeInput }\n | { ok: false; tradeIndex: number; dateOpened: string; error: string };\n\n const outcomes = await mapWithLimit(\n rows,\n MAX_CONCURRENT_REPLAYS,\n async (row): Promise<ReplayOutcome> => {\n const tradeIdx = Number(row[0] ?? 0);\n const pl = Number(row[1] ?? 0);\n const dateOpened = String(row[2] ?? '');\n\n try {\n // Always pass format:'full' to get complete pnlPath for analyzeBatch.\n // params.format controls the batch output density, not the replay resolution.\n const replayResult = await handleReplayTrade(\n {\n block_id,\n trade_index: tradeIdx,\n multiplier,\n format: 'full',\n close_at: 'trade',\n },\n baseDir,\n injectedConn,\n );\n\n // Compute entry cost for percentage-based triggers (D-11)\n const tradeEntryCost = replayResult.legs.reduce((sum: number, leg) => {\n return sum + leg.entryPrice * leg.quantity * leg.multiplier;\n }, 0);\n\n return {\n ok: true,\n input: {\n tradeIndex: tradeIdx,\n dateOpened,\n actualPnl: pl,\n pnlPath: replayResult.pnlPath,\n legs: replayResult.legs,\n entryCost: tradeEntryCost,\n },\n };\n } catch (err) {\n return {\n ok: false,\n tradeIndex: Number(row[0] ?? 0),\n dateOpened: String(row[2] ?? ''),\n error: err instanceof Error ? err.message : String(err),\n };\n }\n },\n );\n\n const tradeInputs: TradeInput[] = [];\n const skippedTrades: Array<{ tradeIndex: number; dateOpened: string; error: string }> = [];\n\n for (const outcome of outcomes) {\n if (outcome.ok) {\n tradeInputs.push(outcome.input);\n } else {\n skippedTrades.push({\n tradeIndex: outcome.tradeIndex,\n dateOpened: outcome.dateOpened,\n error: outcome.error,\n });\n }\n }\n\n // 3. Build BatchExitConfig\n const config: BatchExitConfig = {\n candidatePolicy: candidate_policy as ExitTriggerConfig[],\n legGroups: leg_groups?.map(g => ({\n label: g.label,\n legIndices: g.leg_indices,\n triggers: g.triggers as ExitTriggerConfig[],\n })) as LegGroupConfig[] | undefined,\n baselineMode: baseline_mode,\n format,\n };\n\n // 4. Run the pure batch analysis engine\n const result = analyzeBatch(tradeInputs, config);\n\n // 5. Augment summary with skip info if any trades were skipped\n if (skippedTrades.length > 0) {\n result.summary = result.summary.replace(\n /^Analyzed (\\d+) trades/,\n `Analyzed ${tradeInputs.length} trades (${skippedTrades.length} skipped due to replay errors)`,\n );\n result.skippedTrades = skippedTrades;\n }\n\n // 6. Load profile context if strategy is specified (per D-16)\n if (strategy) {\n try {\n const profileConn = injectedConn ?? await getConnection(baseDir);\n const profile = await getProfile(profileConn, block_id, strategy);\n if (profile) {\n result.profileContext = {\n structureType: profile.structureType,\n exitRules: profile.exitRules.map(r =>\n r.description ?? `${r.type} ${r.trigger}`\n ),\n };\n }\n } catch {\n // Profile is informational context, not critical — swallow errors\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerBatchExitAnalysisTools(\n server: McpServer,\n baseDir: string,\n): void {\n server.registerTool(\n \"batch_exit_analysis\",\n {\n description:\n \"Analyze how a candidate exit policy would perform across multiple trades in a block. \" +\n \"Replays each matching trade, evaluates exit triggers against the minute-level P&L path, \" +\n \"and returns aggregate statistics (win rate, Sharpe, profit factor, drawdown) comparable \" +\n \"to get_statistics. Includes per-trigger attribution showing which triggers drive outcomes. \" +\n \"Uses cached bars from market.intraday when available; fetches from Massive.com on cache miss \" +\n \"(requires MASSIVE_API_KEY). Use with strategy profiles to iterate on exit rules.\",\n inputSchema: batchExitAnalysisSchema,\n },\n async (params) => {\n try {\n const result = await handleBatchExitAnalysis(params, baseDir);\n return createToolOutput(result.summary, result);\n } catch (error) {\n return {\n content: [{\n type: \"text\" as const,\n text: `Error in batch exit analysis: ${(error as Error).message}`,\n }],\n isError: true,\n };\n }\n }\n );\n}\n"],"mappings":"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","names":["sql","params","BATCH_SIZE","forward","iv","quotesEnabled","INDEX_TICKERS","resolvedClass","rows","mappedRows","dateRange","inserted","updated","skipped","z","z","z","getNum","getRaw","z","z","formatTradeDate","getTradeLookupKey","uniqueTradeLookupKeys","resultToRecords","recordsByTickerDate","getNum","VOL_REGIME_LABELS","z","z","bars","z","REVERSE_ROOT_MAP","z","z","h1","m1","h2","m2","markPrice","z","z","z","triggerTypeEnum","z","triggerConfigSchema"]}
|