tradeblocks-mcp 3.0.2 → 3.3.1
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 +85 -80
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
- package/dist/chunk-2I3S2ZLT.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
- package/dist/chunk-4HZLVUOZ.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
- package/dist/chunk-NWNSKALN.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
- package/dist/chunk-WNTYA7ER.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
- package/dist/chunk-YZA4RS76.js.map +1 -0
- package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
- package/dist/chunk-ZTJXC3LS.js.map +1 -0
- package/dist/daily-log-processor-MZW2XBZY.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-6U33TQUT.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
- package/dist/test-exports.js +876 -483
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-DKDCWGKD.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/package.json +2 -6
- package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
- package/server/chunk-536NSFED.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
- package/server/chunk-A3UKD64A.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
- package/server/chunk-OMUDNJBJ.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
- package/server/chunk-RJDJHZ5Y.js.map +1 -0
- package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
- package/server/chunk-RM4B2VKS.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
- package/server/chunk-SY6GDNVI.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-B2VPU2FQ.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +2497 -2029
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-L56RFHAC.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
- package/server/trade-processor-3PI4LHZI.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +38 -8
- package/src/db/connection.ts +107 -40
- package/src/db/data-root.ts +22 -0
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +139 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/index.ts +2 -0
- package/src/tools/blocks/paired-comparison.ts +425 -0
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +11 -14
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +69 -83
- package/src/utils/calibration-probe.ts +3 -4
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-QTTR7AAW.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/manifest.json +0 -141
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../lib/utils/combine-leg-groups.ts","../../lib/db/combined-trades-cache.ts","../../lib/db/performance-snapshot-cache.ts","../../lib/db/trades-store.ts","../../lib/db/index.ts","../../lib/db/daily-logs-store.ts"],"sourcesContent":["/**\n * Leg Group Combining Utility\n *\n * For MEIC (Multiple Entry Iron Condor) and similar strategies where the backtester\n * creates separate trade records for each leg group (e.g., calls and puts) that were\n * opened simultaneously but may have different exit conditions/times.\n *\n * This utility groups trades by entry timestamp and combines them into single trade records.\n */\n\nimport type { Trade } from \"../models/trade.ts\";\nimport type { ReportingTrade } from \"../models/reporting-trade.ts\";\nimport { yieldToMain, checkCancelled } from \"./async-helpers.ts\";\n\n/**\n * Key used to group trades that were opened at the same time\n */\nexport interface TradeGroupKey {\n dateOpened: string; // ISO date string\n timeOpened: string;\n strategy: string;\n}\n\n/**\n * Result of combining multiple leg groups into a single trade\n */\nexport interface CombinedTrade extends Trade {\n originalTradeCount: number; // Number of trades that were combined\n combinedLegs: string[]; // Array of leg strings from each trade\n}\n\n/**\n * Result of combining multiple ReportingTrade leg groups into a single trade\n */\nexport interface CombinedReportingTrade extends ReportingTrade {\n originalTradeCount: number; // Number of trades that were combined\n combinedLegs: string[]; // Array of leg strings from each trade\n}\n\n/**\n * Type guard to check if a Trade is a CombinedTrade\n */\nexport function isCombinedTrade(trade: Trade): trade is CombinedTrade {\n return \"originalTradeCount\" in trade && \"combinedLegs\" in trade;\n}\n\n/**\n * Type guard to check if a ReportingTrade is a CombinedReportingTrade\n */\nexport function isCombinedReportingTrade(trade: ReportingTrade): trade is CombinedReportingTrade {\n return \"originalTradeCount\" in trade && \"combinedLegs\" in trade;\n}\n\n/**\n * Generate a unique key for grouping trades by entry timestamp\n */\nfunction generateGroupKey(trade: Trade): string {\n const dateStr = trade.dateOpened.toISOString().split(\"T\")[0];\n return `${dateStr}|${trade.timeOpened}|${trade.strategy}`;\n}\n\n/**\n * Parse a group key back into its components\n * (Not currently used but kept for future API compatibility)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseGroupKey(key: string): TradeGroupKey {\n const [dateOpened, timeOpened, strategy] = key.split(\"|\");\n return { dateOpened, timeOpened, strategy };\n}\n\n/**\n * Group trades by their entry timestamp (date + time + strategy)\n * Returns a map where the key is the group identifier and value is array of trades\n */\nexport function groupTradesByEntry(trades: Trade[]): Map<string, Trade[]> {\n const groups = new Map<string, Trade[]>();\n\n for (const trade of trades) {\n const key = generateGroupKey(trade);\n const group = groups.get(key) || [];\n group.push(trade);\n groups.set(key, group);\n }\n\n return groups;\n}\n\n/**\n * Combine a group of trades that were opened at the same time into a single trade record\n *\n * Rules for combining:\n * - Opening fields: Use first trade's values (they should be identical)\n * - Closing fields: Use the last closing time among all trades\n * - Premium: Sum of all premiums\n * - P/L: Sum of all P/Ls\n * - Commissions: Sum of all commissions\n * - Margin: Use the maximum margin requirement\n * - Contracts: Sum of all contracts\n * - Legs: Concatenate all leg descriptions\n * - Closing price: Use weighted average based on premiums\n * - Funds at close: Use final funds from last closed trade\n */\nexport function combineLegGroup(trades: Trade[]): CombinedTrade {\n if (trades.length === 0) {\n throw new Error(\"Cannot combine empty trade group\");\n }\n\n // Sort trades by closing time (or use original order if not closed)\n const sortedTrades = [...trades].sort((a, b) => {\n if (!a.dateClosed && !b.dateClosed) return 0;\n if (!a.dateClosed) return 1;\n if (!b.dateClosed) return -1;\n\n const dateCompare = a.dateClosed.getTime() - b.dateClosed.getTime();\n if (dateCompare !== 0) return dateCompare;\n\n // Secondary sort by time if dates are equal\n const timeA = a.timeClosed || \"00:00:00\";\n const timeB = b.timeClosed || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n // Use first trade as template (opening info should be identical)\n const firstTrade = sortedTrades[0];\n const lastTrade = sortedTrades[sortedTrades.length - 1];\n\n // Aggregate numeric values\n const totalPremium = trades.reduce((sum, t) => sum + t.premium, 0);\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0);\n // Use the contract size of the first leg to represent the \"Strategy Unit Size\"\n // e.g. A 10-lot Iron Condor has 4 legs of 10 contracts.\n // We want the combined trade to say \"10 contracts\" (10 ICs), not 40.\n const totalContracts = firstTrade.numContracts;\n const totalOpeningCommissions = trades.reduce((sum, t) => sum + t.openingCommissionsFees, 0);\n const totalClosingCommissions = trades.reduce((sum, t) => sum + t.closingCommissionsFees, 0);\n\n // For margin:\n // - Debit trades (totalPremium < 0): Sum margin (e.g. Straddle = Call + Put cost)\n // - Credit trades (totalPremium >= 0): Max margin (e.g. Iron Condor = Max(Call side, Put side))\n const maxMargin =\n totalPremium < 0\n ? trades.reduce((sum, t) => sum + t.marginReq, 0)\n : Math.max(...trades.map((t) => t.marginReq));\n\n // Calculate weighted average closing price\n let weightedClosingPrice: number | undefined;\n if (trades.every((t) => t.closingPrice !== undefined)) {\n const totalPremiumForClosedTrades = trades\n .filter((t) => t.closingPrice !== undefined)\n .reduce((sum, t) => sum + Math.abs(t.premium), 0);\n\n if (totalPremiumForClosedTrades > 0) {\n weightedClosingPrice =\n trades\n .filter((t) => t.closingPrice !== undefined)\n .reduce((sum, t) => sum + t.closingPrice! * Math.abs(t.premium), 0) /\n totalPremiumForClosedTrades;\n }\n }\n\n // Calculate total closing cost if all trades have it recorded\n let avgClosingCost: number | undefined;\n const tradesWithClosingCost = trades.filter((t) => t.avgClosingCost !== undefined);\n if (tradesWithClosingCost.length === trades.length) {\n avgClosingCost = tradesWithClosingCost.reduce((sum, t) => sum + t.avgClosingCost!, 0);\n }\n\n // Combine leg descriptions\n const combinedLegs = trades.map((t) => t.legs);\n const combinedLegsString = combinedLegs.join(\" | \");\n\n // Use last trade's closing information (latest exit)\n const dateClosed = lastTrade.dateClosed;\n const timeClosed = lastTrade.timeClosed;\n const reasonForClose = lastTrade.reasonForClose;\n const fundsAtClose = lastTrade.fundsAtClose;\n const closingShortLongRatio = lastTrade.closingShortLongRatio;\n const closingVix = lastTrade.closingVix;\n\n // Calculate combined opening short/long ratio (weighted by premium)\n const totalAbsPremium = trades.reduce((sum, t) => sum + Math.abs(t.premium), 0);\n const weightedOpeningShortLongRatio =\n totalAbsPremium > 0\n ? trades.reduce((sum, t) => sum + t.openingShortLongRatio * Math.abs(t.premium), 0) /\n totalAbsPremium\n : trades[0].openingShortLongRatio;\n\n // For optional fields, use first trade's value or undefined\n const gap = firstTrade.gap;\n const movement = firstTrade.movement;\n\n // Max profit/loss handling:\n // - For single trades (originalTradeCount === 1): preserve original percentage values from CSV\n // - For combined trades (originalTradeCount > 1): derive dollar amounts from margin\n let maxProfit: number | undefined;\n let maxLoss: number | undefined;\n\n if (trades.length === 1) {\n // Single trade: preserve original values (percentages from CSV)\n maxProfit = firstTrade.maxProfit;\n maxLoss = firstTrade.maxLoss;\n // For debit trades without explicit maxLoss, use premium paid as max loss\n if (maxLoss === undefined && firstTrade.premium < 0) {\n maxLoss = firstTrade.premium;\n }\n } else {\n // Combined trades: sum maxProfit, use margin for maxLoss\n if (trades.every((t) => t.maxProfit !== undefined)) {\n maxProfit = trades.reduce((sum, t) => sum + t.maxProfit!, 0);\n }\n\n // Use margin requirement as ground truth for worst-case loss\n if (maxMargin && Number.isFinite(maxMargin) && maxMargin > 0) {\n maxLoss = -maxMargin;\n } else if (trades.every((t) => t.maxLoss !== undefined)) {\n maxLoss = trades.reduce((sum, t) => sum + t.maxLoss!, 0);\n } else if (totalPremium < 0) {\n // Fallback: For debit trades, the max loss is at least the premium paid\n maxLoss = totalPremium;\n }\n }\n\n const combined: CombinedTrade = {\n // Opening information (from first trade)\n dateOpened: firstTrade.dateOpened,\n timeOpened: firstTrade.timeOpened,\n openingPrice: firstTrade.openingPrice,\n legs: combinedLegsString,\n premium: totalPremium,\n premiumPrecision: firstTrade.premiumPrecision,\n openingVix: firstTrade.openingVix,\n\n // Closing information (from last closed trade)\n closingPrice: weightedClosingPrice,\n dateClosed,\n timeClosed,\n avgClosingCost,\n reasonForClose,\n closingVix,\n\n // Aggregated values\n pl: totalPL,\n numContracts: totalContracts,\n fundsAtClose,\n marginReq: maxMargin,\n openingCommissionsFees: totalOpeningCommissions,\n closingCommissionsFees: totalClosingCommissions,\n\n // Strategy and ratios\n strategy: firstTrade.strategy,\n openingShortLongRatio: weightedOpeningShortLongRatio,\n closingShortLongRatio,\n\n // Optional market data\n gap,\n movement,\n maxProfit,\n maxLoss,\n\n // Combined trade metadata\n originalTradeCount: trades.length,\n combinedLegs,\n };\n\n return combined;\n}\n\nexport interface CombineLegGroupsProgress {\n step: string;\n percent: number;\n}\n\nexport interface CombineLegGroupsOptions {\n onProgress?: (progress: CombineLegGroupsProgress) => void;\n signal?: AbortSignal;\n}\n\n/**\n * Process all trades and combine leg groups that share the same entry timestamp\n *\n * @param trades - Array of trades to process\n * @returns Array of trades with leg groups combined (single trades are preserved as-is)\n */\nexport function combineAllLegGroups(trades: Trade[]): CombinedTrade[] {\n const groups = groupTradesByEntry(trades);\n const combinedTrades: CombinedTrade[] = [];\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineLegGroup(groupTrades));\n }\n\n // Sort by date/time to maintain chronological order\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n\n return combinedTrades;\n}\n\n/**\n * Async version of combineAllLegGroups with progress reporting and cancellation support\n * Use this for large datasets to keep UI responsive\n *\n * @param trades - Array of trades to process\n * @param options - Progress callback and abort signal\n * @returns Array of trades with leg groups combined\n */\nexport async function combineAllLegGroupsAsync(\n trades: Trade[],\n options?: CombineLegGroupsOptions,\n): Promise<CombinedTrade[]> {\n const { onProgress, signal } = options ?? {};\n\n checkCancelled(signal);\n onProgress?.({ step: \"Grouping trades by entry\", percent: 0 });\n await yieldToMain();\n\n const groups = groupTradesByEntry(trades);\n const combinedTrades: CombinedTrade[] = [];\n const totalGroups = groups.size;\n let processedGroups = 0;\n\n checkCancelled(signal);\n onProgress?.({ step: \"Combining leg groups\", percent: 10 });\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineLegGroup(groupTrades));\n processedGroups++;\n\n // Yield every 100 groups to keep UI responsive\n if (processedGroups % 100 === 0) {\n checkCancelled(signal);\n const percent = 10 + Math.floor((processedGroups / totalGroups) * 70);\n onProgress?.({ step: `Combining leg groups (${processedGroups}/${totalGroups})`, percent });\n await yieldToMain();\n }\n }\n\n checkCancelled(signal);\n onProgress?.({ step: \"Sorting combined trades\", percent: 85 });\n await yieldToMain();\n\n // Sort by date/time to maintain chronological order\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n\n onProgress?.({ step: \"Complete\", percent: 100 });\n\n return combinedTrades;\n}\n\n/**\n * Identify which trades would be affected by combining leg groups\n * Useful for showing users what will change before they enable the feature\n *\n * @returns Object with statistics about grouping\n */\nexport function analyzeLegGroups(trades: Trade[]): {\n totalTrades: number;\n uniqueEntries: number;\n groupedEntries: number;\n maxGroupSize: number;\n avgGroupSize: number;\n groupSizeDistribution: Record<number, number>; // size -> count\n} {\n const groups = groupTradesByEntry(trades);\n\n const groupSizes = Array.from(groups.values()).map((g) => g.length);\n const groupedEntries = groupSizes.filter((size) => size > 1).length;\n\n const distribution: Record<number, number> = {};\n for (const size of groupSizes) {\n distribution[size] = (distribution[size] || 0) + 1;\n }\n\n return {\n totalTrades: trades.length,\n uniqueEntries: groups.size,\n groupedEntries,\n maxGroupSize: groupSizes.length > 0 ? Math.max(...groupSizes) : 0,\n avgGroupSize:\n groupSizes.length > 0 ? groupSizes.reduce((a, b) => a + b, 0) / groupSizes.length : 0,\n groupSizeDistribution: distribution,\n };\n}\n\n// =============================================================================\n// ReportingTrade Combining Functions\n// =============================================================================\n\n/**\n * Generate a unique key for grouping ReportingTrades by entry timestamp\n */\nfunction generateReportingGroupKey(trade: ReportingTrade): string {\n const dateStr = trade.dateOpened.toISOString().split(\"T\")[0];\n const timeStr = trade.timeOpened ?? \"00:00:00\";\n return `${dateStr}|${timeStr}|${trade.strategy}`;\n}\n\n/**\n * Group ReportingTrades by their entry timestamp (date + time + strategy)\n * Returns a map where the key is the group identifier and value is array of trades\n */\nexport function groupReportingTradesByEntry(\n trades: ReportingTrade[],\n): Map<string, ReportingTrade[]> {\n const groups = new Map<string, ReportingTrade[]>();\n\n for (const trade of trades) {\n const key = generateReportingGroupKey(trade);\n const group = groups.get(key) || [];\n group.push(trade);\n groups.set(key, group);\n }\n\n return groups;\n}\n\n/**\n * Combine a group of ReportingTrades that were opened at the same time into a single trade record\n *\n * Rules for combining (simpler than Trade - fewer fields):\n * - Opening fields: Use first trade's values (should be identical)\n * - Closing fields: Use the last closing time among all trades\n * - Premium: Sum of all initial premiums\n * - P/L: Sum of all P/Ls\n * - Contracts: Use first trade's contract count (strategy unit size)\n * - Legs: Concatenate all leg descriptions\n */\nexport function combineReportingLegGroup(trades: ReportingTrade[]): CombinedReportingTrade {\n if (trades.length === 0) {\n throw new Error(\"Cannot combine empty reporting trade group\");\n }\n\n // Sort trades by closing time\n const sortedTrades = [...trades].sort((a, b) => {\n if (!a.dateClosed && !b.dateClosed) return 0;\n if (!a.dateClosed) return 1;\n if (!b.dateClosed) return -1;\n\n const dateCompare = a.dateClosed.getTime() - b.dateClosed.getTime();\n if (dateCompare !== 0) return dateCompare;\n\n const timeA = a.timeClosed || \"00:00:00\";\n const timeB = b.timeClosed || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n const firstTrade = sortedTrades[0];\n const lastTrade = sortedTrades[sortedTrades.length - 1];\n\n // Aggregate numeric values\n const totalPremium = trades.reduce((sum, t) => sum + t.initialPremium, 0);\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0);\n const totalContracts = firstTrade.numContracts; // Strategy unit size\n\n // Combine leg descriptions\n const combinedLegs = trades.map((t) => t.legs);\n const combinedLegsString = combinedLegs.join(\" | \");\n\n // Calculate total closing cost if all trades have it\n let avgClosingCost: number | undefined;\n const tradesWithClosingCost = trades.filter((t) => t.avgClosingCost !== undefined);\n if (tradesWithClosingCost.length === trades.length) {\n avgClosingCost = tradesWithClosingCost.reduce((sum, t) => sum + t.avgClosingCost!, 0);\n }\n\n // Weighted average closing price\n let closingPrice: number | undefined;\n if (trades.every((t) => t.closingPrice !== undefined)) {\n const totalPremiumAbs = trades.reduce((sum, t) => sum + Math.abs(t.initialPremium), 0);\n if (totalPremiumAbs > 0) {\n closingPrice =\n trades.reduce((sum, t) => sum + t.closingPrice! * Math.abs(t.initialPremium), 0) /\n totalPremiumAbs;\n }\n }\n\n return {\n strategy: firstTrade.strategy,\n dateOpened: firstTrade.dateOpened,\n timeOpened: firstTrade.timeOpened,\n openingPrice: firstTrade.openingPrice,\n legs: combinedLegsString,\n initialPremium: totalPremium,\n numContracts: totalContracts,\n pl: totalPL,\n closingPrice,\n dateClosed: lastTrade.dateClosed,\n timeClosed: lastTrade.timeClosed,\n avgClosingCost,\n reasonForClose: lastTrade.reasonForClose,\n originalTradeCount: trades.length,\n combinedLegs,\n };\n}\n\n/**\n * Process all ReportingTrades and combine leg groups that share the same entry timestamp\n *\n * @param trades - Array of ReportingTrades to process\n * @returns Array of trades with leg groups combined\n */\nexport function combineAllReportingLegGroups(trades: ReportingTrade[]): CombinedReportingTrade[] {\n const groups = groupReportingTradesByEntry(trades);\n const combinedTrades: CombinedReportingTrade[] = [];\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineReportingLegGroup(groupTrades));\n }\n\n // Sort chronologically\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n const timeA = a.timeOpened || \"00:00:00\";\n const timeB = b.timeOpened || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n return combinedTrades;\n}\n","/**\n * Combined Trades Cache\n *\n * Caches pre-calculated combined leg group trades in IndexedDB\n * to avoid expensive recalculation on every page load.\n */\n\nimport type { CombinedTrade } from \"../utils/combine-leg-groups.ts\";\nimport {\n INDEXES,\n promisifyRequest,\n STORES,\n withReadTransaction,\n withWriteTransaction,\n} from \"./index.ts\";\n\n/**\n * Cache entry for combined trades\n */\ninterface CombinedTradesCache {\n id: string; // Format: `combined_trades_${blockId}`\n blockId: string;\n calculationType: \"combined_trades\";\n trades: CombinedTrade[];\n tradeCount: number;\n calculatedAt: Date;\n}\n\n/**\n * Generate the cache ID for a block\n */\nfunction getCacheId(blockId: string): string {\n return `combined_trades_${blockId}`;\n}\n\n/**\n * Store pre-calculated combined trades for a block\n */\nexport async function storeCombinedTradesCache(\n blockId: string,\n combinedTrades: CombinedTrade[],\n): Promise<void> {\n const cacheEntry: CombinedTradesCache = {\n id: getCacheId(blockId),\n blockId,\n calculationType: \"combined_trades\",\n trades: combinedTrades,\n tradeCount: combinedTrades.length,\n calculatedAt: new Date(),\n };\n\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n await promisifyRequest(store.put(cacheEntry));\n });\n}\n\n/**\n * Get cached combined trades for a block\n * Returns null if cache doesn't exist\n */\nexport async function getCombinedTradesCache(blockId: string): Promise<CombinedTrade[] | null> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n\n if (!result || result.calculationType !== \"combined_trades\") {\n return null;\n }\n\n const cache = result as CombinedTradesCache;\n\n // Restore Date objects that were serialized\n return cache.trades.map((trade) => ({\n ...trade,\n dateOpened: new Date(trade.dateOpened),\n dateClosed: trade.dateClosed ? new Date(trade.dateClosed) : undefined,\n }));\n });\n}\n\n/**\n * Delete cached combined trades for a block\n */\nexport async function deleteCombinedTradesCache(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n\n // Check if entry exists before trying to delete\n const existing = await promisifyRequest(store.get(cacheId));\n if (existing) {\n await promisifyRequest(store.delete(cacheId));\n }\n });\n}\n\n/**\n * Check if combined trades cache exists for a block\n */\nexport async function hasCombinedTradesCache(blockId: string): Promise<boolean> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n return result?.calculationType === \"combined_trades\";\n });\n}\n\n/**\n * Invalidate all calculation caches for a block\n * (including combined trades cache)\n */\nexport async function invalidateBlockCaches(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const index = store.index(INDEXES.CALCULATIONS_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n}\n","/**\n * Performance Snapshot Cache\n *\n * Caches pre-calculated performance snapshots in IndexedDB\n * to avoid expensive recalculation on every page load.\n */\n\nimport type { PortfolioStats } from \"../models/portfolio-stats.ts\";\nimport type { Trade } from \"../models/trade.ts\";\nimport type { DailyLogEntry } from \"../models/daily-log.ts\";\nimport type { SnapshotChartData } from \"../services/performance-snapshot.ts\";\nimport { promisifyRequest, STORES, withReadTransaction, withWriteTransaction } from \"./index.ts\";\n\n/**\n * Cache entry for performance snapshot\n */\ninterface PerformanceSnapshotCache {\n id: string; // Format: `performance_snapshot_${blockId}`\n blockId: string;\n calculationType: \"performance_snapshot\";\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n calculatedAt: Date;\n}\n\n/**\n * Public interface for cached snapshot data\n */\nexport interface CachedPerformanceSnapshot {\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n calculatedAt: Date;\n}\n\n/**\n * Generate the cache ID for a block\n */\nfunction getCacheId(blockId: string): string {\n return `performance_snapshot_${blockId}`;\n}\n\n/**\n * Store pre-calculated performance snapshot for a block\n */\nexport async function storePerformanceSnapshotCache(\n blockId: string,\n snapshot: {\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n },\n): Promise<void> {\n const cacheEntry: PerformanceSnapshotCache = {\n id: getCacheId(blockId),\n blockId,\n calculationType: \"performance_snapshot\",\n portfolioStats: snapshot.portfolioStats,\n chartData: snapshot.chartData,\n filteredTrades: snapshot.filteredTrades,\n filteredDailyLogs: snapshot.filteredDailyLogs,\n calculatedAt: new Date(),\n };\n\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n await promisifyRequest(store.put(cacheEntry));\n });\n}\n\n/**\n * Restore Date objects from serialized cache data\n */\nfunction restoreDates<T extends { dateOpened?: Date | string; dateClosed?: Date | string | null }>(\n items: T[],\n): T[] {\n return items.map((item) => ({\n ...item,\n dateOpened: item.dateOpened ? new Date(item.dateOpened) : undefined,\n dateClosed: item.dateClosed ? new Date(item.dateClosed) : undefined,\n }));\n}\n\n/**\n * Restore Date objects in daily logs\n */\nfunction restoreDailyLogDates(logs: DailyLogEntry[]): DailyLogEntry[] {\n return logs.map((log) => ({\n ...log,\n date: new Date(log.date),\n }));\n}\n\n/**\n * Restore Date objects in chart data\n */\nfunction restoreChartDataDates(chartData: SnapshotChartData): SnapshotChartData {\n return {\n ...chartData,\n // Most chart data uses ISO string dates, which is fine\n // Only restore where Date objects are expected\n };\n}\n\n/**\n * Get cached performance snapshot for a block\n * Returns null if cache doesn't exist\n */\nexport async function getPerformanceSnapshotCache(\n blockId: string,\n): Promise<CachedPerformanceSnapshot | null> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n\n if (!result || result.calculationType !== \"performance_snapshot\") {\n return null;\n }\n\n const cache = result as PerformanceSnapshotCache;\n\n // Restore Date objects that were serialized\n return {\n portfolioStats: cache.portfolioStats,\n chartData: restoreChartDataDates(cache.chartData),\n filteredTrades: restoreDates(cache.filteredTrades) as Trade[],\n filteredDailyLogs: restoreDailyLogDates(cache.filteredDailyLogs),\n calculatedAt: new Date(cache.calculatedAt),\n };\n });\n}\n\n/**\n * Delete cached performance snapshot for a block\n */\nexport async function deletePerformanceSnapshotCache(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n\n // Check if entry exists before trying to delete\n const existing = await promisifyRequest(store.get(cacheId));\n if (existing) {\n await promisifyRequest(store.delete(cacheId));\n }\n });\n}\n\n/**\n * Check if performance snapshot cache exists for a block\n */\nexport async function hasPerformanceSnapshotCache(blockId: string): Promise<boolean> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n return result?.calculationType === \"performance_snapshot\";\n });\n}\n","/**\n * Trades Store - CRUD operations for trade data\n */\n\nimport type { Trade } from \"../models/trade.ts\";\nimport { combineAllLegGroups, type CombinedTrade } from \"../utils/combine-leg-groups.ts\";\nimport {\n INDEXES,\n promisifyRequest,\n STORES,\n withReadTransaction,\n withWriteTransaction,\n} from \"./index.ts\";\nimport {\n deleteCombinedTradesCache,\n getCombinedTradesCache,\n storeCombinedTradesCache,\n} from \"./combined-trades-cache.ts\";\nimport { deletePerformanceSnapshotCache } from \"./performance-snapshot-cache.ts\";\n\n// Track in-flight combined cache writes to avoid redundant work when multiple callers miss the cache simultaneously.\nconst combinedCacheInflight = new Map<string, Promise<void>>();\n\n/**\n * Extended trade with block association\n */\nexport interface StoredTrade extends Trade {\n blockId: string;\n id?: number; // Auto-generated by IndexedDB\n}\n\n/**\n * Add trades for a block (batch operation)\n */\nexport async function addTrades(blockId: string, trades: Trade[]): Promise<void> {\n if (trades.length === 0) return;\n\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n\n // Use Promise.all for better performance with large datasets\n const promises = trades.map((trade) => {\n const storedTrade: StoredTrade = { ...trade, blockId };\n return promisifyRequest(store.add(storedTrade));\n });\n\n await Promise.all(promises);\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Get all trades for a block\n */\nexport async function getTradesByBlock(blockId: string): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const result = await promisifyRequest(index.getAll(blockId));\n\n // Sort by date opened (chronological order)\n return result.sort((a, b) => {\n const dateA = new Date(a.dateOpened).getTime();\n const dateB = new Date(b.dateOpened).getTime();\n if (dateA !== dateB) return dateA - dateB;\n\n // If same date, sort by time\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n });\n}\n\n/**\n * Get all trades for a block with optional leg group combining\n *\n * Uses cached combined trades when available for better performance.\n * Falls back to on-demand calculation if cache is missing.\n *\n * @param blockId - Block ID to fetch trades for\n * @param options.combineLegGroups - Whether to combine trades with same entry timestamp\n * @param options.skipCache - Force recalculation (bypass cache)\n * @returns Array of trades (combined or raw)\n */\nexport async function getTradesByBlockWithOptions(\n blockId: string,\n options: { combineLegGroups?: boolean; skipCache?: boolean } = {},\n): Promise<(StoredTrade | (CombinedTrade & { blockId: string }))[]> {\n // If combining is enabled, check cache FIRST before fetching raw trades\n // This avoids the expensive raw trade fetch when we have cached data\n if (options.combineLegGroups && !options.skipCache) {\n const cachedTrades = await getCombinedTradesCache(blockId);\n if (cachedTrades) {\n // Add blockId back to cached trades\n return cachedTrades.map((trade) => ({ ...trade, blockId }));\n }\n }\n\n // Fetch raw trades (only if not combining, or cache miss)\n const trades = await getTradesByBlock(blockId);\n\n if (!options.combineLegGroups) {\n return trades;\n }\n\n // Cache miss: calculate combined trades on-demand\n const tradesWithoutBlockId = trades.map((storedTrade) => {\n const { blockId: _ignored, ...trade } = storedTrade;\n void _ignored;\n return trade as Trade;\n });\n const combined = combineAllLegGroups(tradesWithoutBlockId);\n\n queueCombinedTradesCache(blockId, combined);\n\n // Add blockId back to combined trades\n return combined.map((trade) => ({ ...trade, blockId }));\n}\n\nfunction queueCombinedTradesCache(blockId: string, combinedTrades: CombinedTrade[]) {\n if (combinedCacheInflight.has(blockId)) {\n return;\n }\n\n const task = storeCombinedTradesCache(blockId, combinedTrades)\n .catch((err) => {\n console.warn(\"Failed to cache combined trades:\", err);\n })\n .finally(() => {\n combinedCacheInflight.delete(blockId);\n });\n\n combinedCacheInflight.set(blockId, task);\n}\n\n/**\n * Get trades by date range for a block\n */\nexport async function getTradesByDateRange(\n blockId: string,\n startDate: Date,\n endDate: Date,\n): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(\"composite_block_date\");\n\n // Create compound key range [blockId, startDate] to [blockId, endDate]\n const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);\n\n const result = await promisifyRequest(index.getAll(range));\n return result;\n });\n}\n\n/**\n * Get trades by strategy for a block\n */\nexport async function getTradesByStrategy(\n blockId: string,\n strategy: string,\n): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const blockIndex = store.index(INDEXES.TRADES_BY_BLOCK);\n const blockTrades = await promisifyRequest(blockIndex.getAll(blockId));\n\n // Filter by strategy (IndexedDB doesn't support compound queries easily)\n return blockTrades.filter((trade) => trade.strategy === strategy);\n });\n}\n\n/**\n * Get unique strategies for a block\n */\nexport async function getStrategiesByBlock(blockId: string): Promise<string[]> {\n const trades = await getTradesByBlock(blockId);\n const strategies = new Set(trades.map((trade) => trade.strategy));\n return Array.from(strategies).sort();\n}\n\n/**\n * Get trade count by block\n */\nexport async function getTradeCountByBlock(blockId: string): Promise<number> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const result = await promisifyRequest(index.count(blockId));\n return result;\n });\n}\n\n/**\n * Delete all trades for a block\n */\nexport async function deleteTradesByBlock(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Update trades for a block (replace all)\n */\nexport async function updateTradesForBlock(blockId: string, trades: Trade[]): Promise<void> {\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n // First delete existing trades\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const deleteRequest = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n deleteRequest.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n deleteRequest.onerror = () => reject(deleteRequest.error);\n });\n\n // Then add new trades\n const promises = trades.map((trade) => {\n const storedTrade: StoredTrade = { ...trade, blockId };\n return promisifyRequest(store.add(storedTrade));\n });\n\n await Promise.all(promises);\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Get trade statistics for a block (aggregated)\n */\nexport async function getTradeStatistics(blockId: string): Promise<{\n totalTrades: number;\n totalPl: number;\n winningTrades: number;\n losingTrades: number;\n totalCommissions: number;\n dateRange: { start: Date | null; end: Date | null };\n strategies: string[];\n}> {\n const trades = await getTradesByBlock(blockId);\n\n if (trades.length === 0) {\n return {\n totalTrades: 0,\n totalPl: 0,\n winningTrades: 0,\n losingTrades: 0,\n totalCommissions: 0,\n dateRange: { start: null, end: null },\n strategies: [],\n };\n }\n\n const totalTrades = trades.length;\n const totalPl = trades.reduce((sum, trade) => sum + trade.pl, 0);\n const winningTrades = trades.filter((trade) => trade.pl > 0).length;\n const losingTrades = trades.filter((trade) => trade.pl < 0).length;\n const totalCommissions = trades.reduce(\n (sum, trade) => sum + trade.openingCommissionsFees + trade.closingCommissionsFees,\n 0,\n );\n\n // Get date range\n const dates = trades.map((trade) => new Date(trade.dateOpened));\n const start = new Date(Math.min(...dates.map((d) => d.getTime())));\n const end = new Date(Math.max(...dates.map((d) => d.getTime())));\n\n // Get unique strategies\n const strategies = Array.from(new Set(trades.map((trade) => trade.strategy))).sort();\n\n return {\n totalTrades,\n totalPl,\n winningTrades,\n losingTrades,\n totalCommissions,\n dateRange: { start, end },\n strategies,\n };\n}\n\n/**\n * Search trades by text (strategy, legs, reason for close)\n */\nexport async function searchTrades(blockId: string, query: string): Promise<StoredTrade[]> {\n const trades = await getTradesByBlock(blockId);\n const lowerQuery = query.toLowerCase();\n\n return trades.filter(\n (trade) =>\n trade.strategy.toLowerCase().includes(lowerQuery) ||\n trade.legs.toLowerCase().includes(lowerQuery) ||\n (trade.reasonForClose && trade.reasonForClose.toLowerCase().includes(lowerQuery)),\n );\n}\n\n/**\n * Get trades with pagination\n */\nexport async function getTradesPage(\n blockId: string,\n offset: number,\n limit: number,\n): Promise<{ trades: StoredTrade[]; total: number }> {\n const allTrades = await getTradesByBlock(blockId);\n const total = allTrades.length;\n const trades = allTrades.slice(offset, offset + limit);\n\n return { trades, total };\n}\n\n/**\n * Export trades to CSV format (for backup/analysis)\n */\nexport async function exportTradesToCSV(blockId: string): Promise<string> {\n const trades = await getTradesByBlock(blockId);\n\n if (trades.length === 0) {\n return \"\";\n }\n\n // CSV headers\n const headers = [\n \"Date Opened\",\n \"Time Opened\",\n \"Opening Price\",\n \"Legs\",\n \"Premium\",\n \"Closing Price\",\n \"Date Closed\",\n \"Time Closed\",\n \"Avg. Closing Cost\",\n \"Reason For Close\",\n \"P/L\",\n \"No. of Contracts\",\n \"Funds at Close\",\n \"Margin Req.\",\n \"Strategy\",\n \"Opening Commissions + Fees\",\n \"Closing Commissions + Fees\",\n \"Opening Short/Long Ratio\",\n \"Closing Short/Long Ratio\",\n \"Opening VIX\",\n \"Closing VIX\",\n \"Gap\",\n \"Movement\",\n \"Max Profit\",\n \"Max Loss\",\n ];\n\n // Convert trades to CSV rows\n const rows = trades.map((trade) => {\n // Format dateOpened - handle both Date objects and strings\n const dateOpened =\n trade.dateOpened instanceof Date\n ? trade.dateOpened.toISOString().split(\"T\")[0]\n : typeof trade.dateOpened === \"string\"\n ? new Date(trade.dateOpened).toISOString().split(\"T\")[0]\n : trade.dateOpened;\n\n return [\n dateOpened,\n trade.timeOpened,\n trade.openingPrice.toString(),\n trade.legs,\n trade.premium.toString(),\n trade.closingPrice?.toString() || \"\",\n trade.dateClosed instanceof Date\n ? trade.dateClosed.toISOString().split(\"T\")[0]\n : typeof trade.dateClosed === \"string\" && trade.dateClosed !== \"\"\n ? new Date(trade.dateClosed).toISOString().split(\"T\")[0]\n : \"\",\n trade.timeClosed || \"\",\n trade.avgClosingCost?.toString() || \"\",\n trade.reasonForClose || \"\",\n trade.pl.toString(),\n trade.numContracts.toString(),\n trade.fundsAtClose.toString(),\n trade.marginReq.toString(),\n trade.strategy,\n trade.openingCommissionsFees.toString(),\n trade.closingCommissionsFees.toString(),\n trade.openingShortLongRatio.toString(),\n trade.closingShortLongRatio?.toString() || \"\",\n trade.openingVix?.toString() || \"\",\n trade.closingVix?.toString() || \"\",\n trade.gap?.toString() || \"\",\n trade.movement?.toString() || \"\",\n trade.maxProfit?.toString() || \"\",\n trade.maxLoss?.toString() || \"\",\n ];\n });\n\n // Combine headers and rows\n const csvContent = [headers, ...rows]\n .map((row) => row.map((field) => `\"${field}\"`).join(\",\"))\n .join(\"\\n\");\n\n return csvContent;\n}\n","/**\n * IndexedDB Database Service for TradeBlocks\n *\n * Manages the client-side database for storing blocks, trades, and daily logs.\n * Uses a versioned schema with migration support.\n */\n\n// Types imported for reference (commented out to avoid unused warnings)\n// import { ProcessedBlock } from '../models/block.ts'\n// import { Trade } from '../models/trade.ts'\n// import { DailyLogEntry } from '../models/daily-log.ts'\n// import { PortfolioStats, StrategyStats, PerformanceMetrics } from '../models/portfolio-stats.ts'\n\n// Database configuration\nexport const DB_NAME = \"TradeBlocksDB\";\nexport const DB_VERSION = 4;\n\n// Object store names\nexport const STORES = {\n BLOCKS: \"blocks\",\n TRADES: \"trades\",\n DAILY_LOGS: \"dailyLogs\",\n CALCULATIONS: \"calculations\",\n REPORTING_LOGS: \"reportingLogs\",\n WALK_FORWARD: \"walkForwardAnalyses\",\n STATIC_DATASETS: \"staticDatasets\",\n STATIC_DATASET_ROWS: \"staticDatasetRows\",\n} as const;\n\n// Index names\nexport const INDEXES = {\n TRADES_BY_BLOCK: \"blockId\",\n TRADES_BY_DATE: \"dateOpened\",\n TRADES_BY_STRATEGY: \"strategy\",\n DAILY_LOGS_BY_BLOCK: \"blockId\",\n DAILY_LOGS_BY_DATE: \"date\",\n CALCULATIONS_BY_BLOCK: \"blockId\",\n REPORTING_LOGS_BY_BLOCK: \"blockId\",\n REPORTING_LOGS_BY_STRATEGY: \"strategy\",\n WALK_FORWARD_BY_BLOCK: \"blockId\",\n STATIC_DATASET_ROWS_BY_DATASET: \"datasetId\",\n STATIC_DATASET_ROWS_BY_TIMESTAMP: \"timestamp\",\n} as const;\n\n/**\n * Database instance singleton\n */\nlet dbInstance: IDBDatabase | null = null;\n\n/**\n * Initialize the IndexedDB database\n */\nexport async function initializeDatabase(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n if (dbInstance) {\n resolve(dbInstance);\n return;\n }\n\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => {\n reject(new Error(`Failed to open database: ${request.error?.message}`));\n };\n\n request.onsuccess = () => {\n dbInstance = request.result;\n resolve(dbInstance);\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const transaction = (event.target as IDBOpenDBRequest).transaction!;\n\n // Create blocks store\n if (!db.objectStoreNames.contains(STORES.BLOCKS)) {\n const blocksStore = db.createObjectStore(STORES.BLOCKS, {\n keyPath: \"id\",\n });\n blocksStore.createIndex(\"name\", \"name\", { unique: false });\n blocksStore.createIndex(\"isActive\", \"isActive\", { unique: false });\n blocksStore.createIndex(\"created\", \"created\", { unique: false });\n blocksStore.createIndex(\"lastModified\", \"lastModified\", {\n unique: false,\n });\n }\n\n // Create trades store\n if (!db.objectStoreNames.contains(STORES.TRADES)) {\n const tradesStore = db.createObjectStore(STORES.TRADES, {\n autoIncrement: true,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_DATE, \"dateOpened\", {\n unique: false,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_STRATEGY, \"strategy\", {\n unique: false,\n });\n tradesStore.createIndex(\"pl\", \"pl\", { unique: false });\n tradesStore.createIndex(\"composite_block_date\", [\"blockId\", \"dateOpened\"], {\n unique: false,\n });\n }\n\n // Create daily logs store\n if (!db.objectStoreNames.contains(STORES.DAILY_LOGS)) {\n const dailyLogsStore = db.createObjectStore(STORES.DAILY_LOGS, {\n autoIncrement: true,\n });\n dailyLogsStore.createIndex(INDEXES.DAILY_LOGS_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n dailyLogsStore.createIndex(INDEXES.DAILY_LOGS_BY_DATE, \"date\", {\n unique: false,\n });\n dailyLogsStore.createIndex(\"composite_block_date\", [\"blockId\", \"date\"], { unique: false });\n }\n\n // Create reporting logs store\n if (!db.objectStoreNames.contains(STORES.REPORTING_LOGS)) {\n const reportingStore = db.createObjectStore(STORES.REPORTING_LOGS, {\n autoIncrement: true,\n });\n reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_STRATEGY, \"strategy\", {\n unique: false,\n });\n reportingStore.createIndex(\"composite_block_date\", [\"blockId\", \"dateOpened\"], {\n unique: false,\n });\n }\n\n // Create calculations store (for cached computations)\n if (!db.objectStoreNames.contains(STORES.CALCULATIONS)) {\n const calculationsStore = db.createObjectStore(STORES.CALCULATIONS, {\n keyPath: \"id\",\n });\n calculationsStore.createIndex(INDEXES.CALCULATIONS_BY_BLOCK, \"blockId\", { unique: false });\n calculationsStore.createIndex(\"calculationType\", \"calculationType\", {\n unique: false,\n });\n calculationsStore.createIndex(\"calculatedAt\", \"calculatedAt\", {\n unique: false,\n });\n }\n\n // Create walk-forward analysis store\n if (!db.objectStoreNames.contains(STORES.WALK_FORWARD)) {\n const walkForwardStore = db.createObjectStore(STORES.WALK_FORWARD, {\n keyPath: \"id\",\n });\n walkForwardStore.createIndex(INDEXES.WALK_FORWARD_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n walkForwardStore.createIndex(\"createdAt\", \"createdAt\", { unique: false });\n }\n\n // Create static datasets store (metadata)\n if (!db.objectStoreNames.contains(STORES.STATIC_DATASETS)) {\n const staticDatasetsStore = db.createObjectStore(STORES.STATIC_DATASETS, {\n keyPath: \"id\",\n });\n staticDatasetsStore.createIndex(\"name\", \"name\", { unique: true });\n staticDatasetsStore.createIndex(\"uploadedAt\", \"uploadedAt\", { unique: false });\n }\n\n // Create static dataset rows store (data rows)\n if (!db.objectStoreNames.contains(STORES.STATIC_DATASET_ROWS)) {\n const staticDatasetRowsStore = db.createObjectStore(STORES.STATIC_DATASET_ROWS, {\n autoIncrement: true,\n });\n staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_DATASET, \"datasetId\", {\n unique: false,\n });\n staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_TIMESTAMP, \"timestamp\", {\n unique: false,\n });\n staticDatasetRowsStore.createIndex(\n \"composite_dataset_timestamp\",\n [\"datasetId\", \"timestamp\"],\n { unique: false },\n );\n }\n\n transaction.oncomplete = () => {\n dbInstance = db;\n resolve(db);\n };\n };\n });\n}\n\n/**\n * Get database instance (initialize if needed)\n */\nexport async function getDatabase(): Promise<IDBDatabase> {\n if (dbInstance) {\n return dbInstance;\n }\n return initializeDatabase();\n}\n\n/**\n * Close database connection\n */\nexport function closeDatabase(): void {\n if (dbInstance) {\n dbInstance.close();\n dbInstance = null;\n }\n}\n\n/**\n * Delete the entire database (for testing/reset)\n * This version is more robust for corrupted databases:\n * - Doesn't require opening the database first\n * - Has timeout to prevent hanging forever\n * - Resolves on blocked (since deletion completes after reload)\n */\nexport async function deleteDatabase(): Promise<void> {\n return new Promise((resolve) => {\n // Close any existing connection (don't wait for it)\n if (dbInstance) {\n try {\n dbInstance.close();\n } catch {\n // Ignore close errors - database might be in bad state\n }\n dbInstance = null;\n }\n\n const deleteRequest = indexedDB.deleteDatabase(DB_NAME);\n\n // Timeout to prevent hanging forever on corrupted database\n const timeout = setTimeout(() => {\n console.warn(\"Database deletion timed out - will retry after reload\");\n resolve(); // Resolve anyway so we can reload\n }, 5000);\n\n deleteRequest.onsuccess = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n deleteRequest.onerror = () => {\n clearTimeout(timeout);\n console.error(\"Failed to delete database:\", deleteRequest.error);\n // Still resolve - user can retry after page reload\n resolve();\n };\n\n deleteRequest.onblocked = () => {\n clearTimeout(timeout);\n console.warn(\"Database deletion blocked - will complete after reload\");\n // Resolve instead of reject - the deletion will complete once all connections close\n // After page reload, there will be no connections blocking it\n resolve();\n };\n });\n}\n\n/**\n * Transaction helper for read operations\n */\nexport async function withReadTransaction<T>(\n stores: string | string[],\n callback: (transaction: IDBTransaction) => Promise<T>,\n): Promise<T> {\n const db = await getDatabase();\n const storeNames = Array.isArray(stores) ? stores : [stores];\n const transaction = db.transaction(storeNames, \"readonly\");\n\n return callback(transaction);\n}\n\n/**\n * Transaction helper for write operations\n */\nexport async function withWriteTransaction<T>(\n stores: string | string[],\n callback: (transaction: IDBTransaction) => Promise<T>,\n): Promise<T> {\n const db = await getDatabase();\n const storeNames = Array.isArray(stores) ? stores : [stores];\n const transaction = db.transaction(storeNames, \"readwrite\");\n\n return callback(transaction);\n}\n\n/**\n * Generic helper for promisifying IDBRequest\n */\nexport function promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\n/**\n * Storage quota management\n */\nexport interface StorageInfo {\n quota: number;\n usage: number;\n available: number;\n persistent: boolean;\n}\n\n/**\n * Get storage quota information\n */\nexport async function getStorageInfo(): Promise<StorageInfo> {\n if (\"storage\" in navigator && \"estimate\" in navigator.storage) {\n const estimate = await navigator.storage.estimate();\n const persistent = await navigator.storage.persisted();\n\n return {\n quota: estimate.quota || 0,\n usage: estimate.usage || 0,\n available: (estimate.quota || 0) - (estimate.usage || 0),\n persistent,\n };\n }\n\n // Fallback for browsers without storage API\n return {\n quota: 0,\n usage: 0,\n available: 0,\n persistent: false,\n };\n}\n\n/**\n * Request persistent storage\n */\nexport async function requestPersistentStorage(): Promise<boolean> {\n if (\"storage\" in navigator && \"persist\" in navigator.storage) {\n return navigator.storage.persist();\n }\n return false;\n}\n\n/**\n * Database error types\n */\nexport class DatabaseError extends Error {\n readonly operation: string;\n readonly store?: string;\n override readonly cause?: Error;\n constructor(message: string, operation: string, store?: string, cause?: Error) {\n super(message);\n this.name = \"DatabaseError\";\n this.operation = operation;\n this.store = store;\n this.cause = cause;\n }\n}\n\nexport class QuotaExceededError extends DatabaseError {\n constructor(operation: string, store?: string) {\n super(\"Storage quota exceeded\", operation, store);\n this.name = \"QuotaExceededError\";\n }\n}\n\nexport class TransactionError extends DatabaseError {\n constructor(message: string, operation: string, store?: string, cause?: Error) {\n super(message, operation, store, cause);\n this.name = \"TransactionError\";\n }\n}\n\n// Re-export functions from individual stores\nexport {\n createBlock,\n deleteBlock,\n getActiveBlock,\n getAllBlocks,\n getBlock,\n updateBlock,\n updateBlockStats,\n} from \"./blocks-store.ts\";\nexport {\n addDailyLogEntries,\n deleteDailyLogsByBlock,\n getDailyLogCountByBlock,\n getDailyLogsByBlock,\n updateDailyLogsForBlock,\n} from \"./daily-logs-store.ts\";\nexport type { StoredDailyLogEntry } from \"./daily-logs-store.ts\";\nexport {\n addReportingTrades,\n deleteReportingTradesByBlock,\n getReportingStrategiesByBlock,\n getReportingTradeCountByBlock,\n getReportingTradesByBlock,\n updateReportingTradesForBlock,\n} from \"./reporting-logs-store.ts\";\nexport {\n addTrades,\n deleteTradesByBlock,\n getTradeCountByBlock,\n getTradesByBlock,\n getTradesByBlockWithOptions,\n updateTradesForBlock,\n} from \"./trades-store.ts\";\nexport type { StoredTrade } from \"./trades-store.ts\";\nexport {\n saveWalkForwardAnalysis,\n getWalkForwardAnalysis,\n getWalkForwardAnalysesByBlock,\n deleteWalkForwardAnalysis,\n deleteWalkForwardAnalysesByBlock,\n} from \"./walk-forward-store.ts\";\nexport {\n storeCombinedTradesCache,\n getCombinedTradesCache,\n deleteCombinedTradesCache,\n hasCombinedTradesCache,\n invalidateBlockCaches,\n} from \"./combined-trades-cache.ts\";\nexport {\n storePerformanceSnapshotCache,\n getPerformanceSnapshotCache,\n deletePerformanceSnapshotCache,\n hasPerformanceSnapshotCache,\n} from \"./performance-snapshot-cache.ts\";\nexport type { CachedPerformanceSnapshot } from \"./performance-snapshot-cache.ts\";\nexport {\n storeEnrichedTradesCache,\n getEnrichedTradesCache,\n deleteEnrichedTradesCache,\n hasEnrichedTradesCache,\n} from \"./enriched-trades-cache.ts\";\nexport {\n createStaticDataset,\n getStaticDataset,\n getStaticDatasetByName,\n getAllStaticDatasets,\n updateStaticDatasetMatchStrategy,\n updateStaticDatasetName,\n deleteStaticDataset,\n isDatasetNameTaken,\n getStaticDatasetCount,\n} from \"./static-datasets-store.ts\";\nexport {\n addStaticDatasetRows,\n getStaticDatasetRows,\n getStaticDatasetRowsByRange,\n getStaticDatasetRowCount,\n deleteStaticDatasetRows,\n deleteStaticDatasetWithRows,\n getStaticDatasetDateRange,\n} from \"./static-dataset-rows-store.ts\";\n","/**\n * Daily Logs Store - CRUD operations for daily log data\n */\n\nimport type { DailyLogEntry } from \"../models/daily-log.ts\";\nimport {\n STORES,\n INDEXES,\n withReadTransaction,\n withWriteTransaction,\n promisifyRequest,\n} from \"./index.ts\";\n\n/**\n * Extended daily log entry with block association\n */\nexport interface StoredDailyLogEntry extends DailyLogEntry {\n blockId: string;\n id?: number; // Auto-generated by IndexedDB\n}\n\n/**\n * Add daily log entries for a block (batch operation)\n */\nexport async function addDailyLogEntries(blockId: string, entries: DailyLogEntry[]): Promise<void> {\n if (entries.length === 0) return;\n\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n\n // Use Promise.all for better performance with large datasets\n const promises = entries.map((entry) => {\n const storedEntry: StoredDailyLogEntry = { ...entry, blockId };\n return promisifyRequest(store.add(storedEntry));\n });\n\n await Promise.all(promises);\n });\n}\n\n/**\n * Get all daily log entries for a block\n */\nexport async function getDailyLogsByBlock(blockId: string): Promise<StoredDailyLogEntry[]> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const result = await promisifyRequest(index.getAll(blockId));\n\n // Sort by date (chronological order)\n return result.sort((a, b) => {\n const dateA = new Date(a.date).getTime();\n const dateB = new Date(b.date).getTime();\n return dateA - dateB;\n });\n });\n}\n\n/**\n * Get daily log entries by date range for a block\n */\nexport async function getDailyLogsByDateRange(\n blockId: string,\n startDate: Date,\n endDate: Date,\n): Promise<StoredDailyLogEntry[]> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(\"composite_block_date\");\n\n // Create compound key range [blockId, startDate] to [blockId, endDate]\n const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);\n\n const result = await promisifyRequest(index.getAll(range));\n return result;\n });\n}\n\n/**\n * Get daily log entry for a specific date\n */\nexport async function getDailyLogByDate(\n blockId: string,\n date: Date,\n): Promise<StoredDailyLogEntry | null> {\n const entries = await getDailyLogsByDateRange(blockId, date, date);\n return entries.length > 0 ? entries[0] : null;\n}\n\n/**\n * Get daily log count by block\n */\nexport async function getDailyLogCountByBlock(blockId: string): Promise<number> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const result = await promisifyRequest(index.count(blockId));\n return result;\n });\n}\n\n/**\n * Delete all daily log entries for a block\n */\nexport async function deleteDailyLogsByBlock(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n}\n\n/**\n * Update daily log entries for a block (replace all)\n */\nexport async function updateDailyLogsForBlock(\n blockId: string,\n entries: DailyLogEntry[],\n): Promise<void> {\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n // First delete existing entries\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const deleteRequest = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n deleteRequest.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n deleteRequest.onerror = () => reject(deleteRequest.error);\n });\n\n // Then add new entries\n const promises = entries.map((entry) => {\n const storedEntry: StoredDailyLogEntry = { ...entry, blockId };\n return promisifyRequest(store.add(storedEntry));\n });\n\n await Promise.all(promises);\n });\n}\n\n/**\n * Get daily log statistics for a block\n */\nexport async function getDailyLogStatistics(blockId: string): Promise<{\n totalEntries: number;\n dateRange: { start: Date | null; end: Date | null };\n finalPortfolioValue: number;\n maxDrawdown: number;\n totalPl: number;\n avgDailyPl: number;\n}> {\n const entries = await getDailyLogsByBlock(blockId);\n\n if (entries.length === 0) {\n return {\n totalEntries: 0,\n dateRange: { start: null, end: null },\n finalPortfolioValue: 0,\n maxDrawdown: 0,\n totalPl: 0,\n avgDailyPl: 0,\n };\n }\n\n const totalEntries = entries.length;\n\n // Get date range\n const dates = entries.map((entry) => new Date(entry.date));\n const start = new Date(Math.min(...dates.map((d) => d.getTime())));\n const end = new Date(Math.max(...dates.map((d) => d.getTime())));\n\n // Final portfolio value (last entry)\n const sortedEntries = entries.sort(\n (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),\n );\n const finalPortfolioValue = sortedEntries[0]?.netLiquidity || 0;\n\n // Calculate max drawdown (most negative value)\n const maxDrawdown = Math.min(...entries.map((entry) => entry.drawdownPct));\n\n // Total P/L (sum of all daily P/L)\n const totalPl = entries.reduce((sum, entry) => sum + entry.dailyPl, 0);\n\n // Average daily P/L\n const avgDailyPl = totalPl / totalEntries;\n\n return {\n totalEntries,\n dateRange: { start, end },\n finalPortfolioValue,\n maxDrawdown,\n totalPl,\n avgDailyPl,\n };\n}\n\n/**\n * Get portfolio value over time (for charts)\n */\nexport async function getPortfolioValueTimeSeries(blockId: string): Promise<\n Array<{\n date: Date;\n netLiquidity: number;\n dailyPl: number;\n drawdownPct: number;\n }>\n> {\n const entries = await getDailyLogsByBlock(blockId);\n\n return entries.map((entry) => ({\n date: new Date(entry.date),\n netLiquidity: entry.netLiquidity,\n dailyPl: entry.dailyPl,\n drawdownPct: entry.drawdownPct,\n }));\n}\n\n/**\n * Get daily P/L aggregated by month\n */\nexport async function getMonthlyPl(blockId: string): Promise<Record<string, number>> {\n const entries = await getDailyLogsByBlock(blockId);\n const monthlyPl: Record<string, number> = {};\n\n entries.forEach((entry) => {\n const date = new Date(entry.date);\n const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}`;\n\n if (!monthlyPl[monthKey]) {\n monthlyPl[monthKey] = 0;\n }\n monthlyPl[monthKey] += entry.dailyPl;\n });\n\n return monthlyPl;\n}\n\n/**\n * Get daily P/L aggregated by week\n */\nexport async function getWeeklyPl(blockId: string): Promise<Record<string, number>> {\n const entries = await getDailyLogsByBlock(blockId);\n const weeklyPl: Record<string, number> = {};\n\n entries.forEach((entry) => {\n const date = new Date(entry.date);\n const year = date.getFullYear();\n const week = getWeekNumber(date);\n const weekKey = `${year}-W${String(week).padStart(2, \"0\")}`;\n\n if (!weeklyPl[weekKey]) {\n weeklyPl[weekKey] = 0;\n }\n weeklyPl[weekKey] += entry.dailyPl;\n });\n\n return weeklyPl;\n}\n\n/**\n * Export daily logs to CSV format\n */\nexport async function exportDailyLogsToCSV(blockId: string): Promise<string> {\n const entries = await getDailyLogsByBlock(blockId);\n\n if (entries.length === 0) {\n return \"\";\n }\n\n // CSV headers\n const headers = [\n \"Date\",\n \"Net Liquidity\",\n \"Current Funds\",\n \"Withdrawn\",\n \"Trading Funds\",\n \"P/L\",\n \"P/L %\",\n \"Drawdown %\",\n ];\n\n // Convert entries to CSV rows\n const rows = entries.map((entry) => [\n entry.date instanceof Date ? entry.date.toISOString().split(\"T\")[0] : entry.date,\n entry.netLiquidity.toString(),\n entry.currentFunds.toString(),\n entry.withdrawn.toString(),\n entry.tradingFunds.toString(),\n entry.dailyPl.toString(),\n entry.dailyPlPct.toString(),\n entry.drawdownPct.toString(),\n ]);\n\n // Combine headers and rows\n const csvContent = [headers, ...rows]\n .map((row) => row.map((field) => `\"${field}\"`).join(\",\"))\n .join(\"\\n\");\n\n return csvContent;\n}\n\n/**\n * Helper function to get week number\n */\nfunction getWeekNumber(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n"],"mappings":"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","names":["getCacheId","getCacheId"]}
|
|
@@ -7,16 +7,18 @@ import * as protoLoader from "@grpc/proto-loader";
|
|
|
7
7
|
import protobuf from "protobufjs";
|
|
8
8
|
var moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
function protoCandidates() {
|
|
10
|
-
return Array.from(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
return Array.from(
|
|
11
|
+
/* @__PURE__ */ new Set([
|
|
12
|
+
resolve(moduleDir, "mdds.proto"),
|
|
13
|
+
resolve(moduleDir, "../../../mdds.proto"),
|
|
14
|
+
resolve(moduleDir, "../../../../server/mdds.proto"),
|
|
15
|
+
resolve(moduleDir, "../../../../dist/mdds.proto"),
|
|
16
|
+
resolve(process.cwd(), "packages/mcp-server/src/utils/providers/thetadata/mdds.proto"),
|
|
17
|
+
resolve(process.cwd(), "src/utils/providers/thetadata/mdds.proto"),
|
|
18
|
+
resolve(process.cwd(), "server/mdds.proto"),
|
|
19
|
+
resolve(process.cwd(), "dist/mdds.proto")
|
|
20
|
+
])
|
|
21
|
+
);
|
|
20
22
|
}
|
|
21
23
|
function resolveMddsProtoPath() {
|
|
22
24
|
const candidates = protoCandidates();
|
|
@@ -51,4 +53,4 @@ export {
|
|
|
51
53
|
loadMddsGrpcPackage,
|
|
52
54
|
loadMddsProtoRoot
|
|
53
55
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
56
|
+
//# sourceMappingURL=chunk-2E63THNI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/providers/thetadata/proto.ts"],"sourcesContent":["import { existsSync } from \"fs\";\nimport { dirname, resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport * as grpc from \"@grpc/grpc-js\";\nimport * as protoLoader from \"@grpc/proto-loader\";\nimport protobuf from \"protobufjs\";\nimport type { Root } from \"protobufjs\";\n\nconst moduleDir = dirname(fileURLToPath(import.meta.url));\n\nfunction protoCandidates(): string[] {\n return Array.from(\n new Set([\n resolve(moduleDir, \"mdds.proto\"),\n resolve(moduleDir, \"../../../mdds.proto\"),\n resolve(moduleDir, \"../../../../server/mdds.proto\"),\n resolve(moduleDir, \"../../../../dist/mdds.proto\"),\n resolve(process.cwd(), \"packages/mcp-server/src/utils/providers/thetadata/mdds.proto\"),\n resolve(process.cwd(), \"src/utils/providers/thetadata/mdds.proto\"),\n resolve(process.cwd(), \"server/mdds.proto\"),\n resolve(process.cwd(), \"dist/mdds.proto\"),\n ]),\n );\n}\n\nexport function resolveMddsProtoPath(): string {\n const candidates = protoCandidates();\n const found = candidates.find((candidate) => existsSync(candidate));\n if (!found) {\n throw new Error(`ThetaData MDDS proto not found. Checked: ${candidates.join(\", \")}`);\n }\n return found;\n}\n\nlet grpcPackage: unknown | null = null;\nlet protobufRoot: Root | null = null;\n\nexport function loadMddsGrpcPackage(): unknown {\n if (!grpcPackage) {\n const definition = protoLoader.loadSync(resolveMddsProtoPath(), {\n longs: Number,\n enums: String,\n defaults: false,\n oneofs: true,\n bytes: Buffer,\n });\n grpcPackage = grpc.loadPackageDefinition(definition);\n }\n return grpcPackage;\n}\n\nexport function loadMddsProtoRoot(): Root {\n if (!protobufRoot) protobufRoot = protobuf.loadSync(resolveMddsProtoPath());\n return protobufRoot;\n}\n"],"mappings":";AAAA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAC9B,YAAY,UAAU;AACtB,YAAY,iBAAiB;AAC7B,OAAO,cAAc;AAGrB,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAExD,SAAS,kBAA4B;AACnC,SAAO,MAAM;AAAA,IACX,oBAAI,IAAI;AAAA,MACN,QAAQ,WAAW,YAAY;AAAA,MAC/B,QAAQ,WAAW,qBAAqB;AAAA,MACxC,QAAQ,WAAW,+BAA+B;AAAA,MAClD,QAAQ,WAAW,6BAA6B;AAAA,MAChD,QAAQ,QAAQ,IAAI,GAAG,8DAA8D;AAAA,MACrF,QAAQ,QAAQ,IAAI,GAAG,0CAA0C;AAAA,MACjE,QAAQ,QAAQ,IAAI,GAAG,mBAAmB;AAAA,MAC1C,QAAQ,QAAQ,IAAI,GAAG,iBAAiB;AAAA,IAC1C,CAAC;AAAA,EACH;AACF;AAEO,SAAS,uBAA+B;AAC7C,QAAM,aAAa,gBAAgB;AACnC,QAAM,QAAQ,WAAW,KAAK,CAAC,cAAc,WAAW,SAAS,CAAC;AAClE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,4CAA4C,WAAW,KAAK,IAAI,CAAC,EAAE;AAAA,EACrF;AACA,SAAO;AACT;AAEA,IAAI,cAA8B;AAClC,IAAI,eAA4B;AAEzB,SAAS,sBAA+B;AAC7C,MAAI,CAAC,aAAa;AAChB,UAAM,aAAyB,qBAAS,qBAAqB,GAAG;AAAA,MAC9D,OAAO;AAAA,MACP,OAAO;AAAA,MACP,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO;AAAA,IACT,CAAC;AACD,kBAAmB,2BAAsB,UAAU;AAAA,EACrD;AACA,SAAO;AACT;AAEO,SAAS,oBAA0B;AACxC,MAAI,CAAC,aAAc,gBAAe,SAAS,SAAS,qBAAqB,CAAC;AAC1E,SAAO;AACT;","names":[]}
|