tradeblocks-mcp 3.0.2 → 3.0.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.
Files changed (214) hide show
  1. package/README.md +82 -68
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +127 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../lib/processing/csv-parser.ts","../../lib/models/validators.ts","../../lib/utils/csv-headers.ts"],"sourcesContent":["/**\n * CSV Parser Service\n *\n * Handles CSV file parsing with progress tracking, error handling,\n * and validation for TradeBlocks data.\n */\n\nimport type { ParsingError } from '../models/index.ts'\n// import { ProcessingError } from '../models/index.ts'\n\n/**\n * CSV parsing configuration\n */\nexport interface CSVParseConfig {\n delimiter?: string\n quote?: string\n escape?: string\n skipEmptyLines?: boolean\n trimValues?: boolean\n maxRows?: number\n progressCallback?: (progress: number, rowsProcessed: number) => void\n}\n\n/**\n * CSV parsing result\n */\nexport interface CSVParseResult<T = Record<string, string>> {\n data: T[]\n headers: string[]\n totalRows: number\n validRows: number\n errors: ParsingError[]\n warnings: string[]\n}\n\n/**\n * CSV parsing progress info\n */\nexport interface ParseProgress {\n stage: 'reading' | 'parsing' | 'validating' | 'converting' | 'completed'\n progress: number // 0-100\n rowsProcessed: number\n totalRows: number\n errors: number\n}\n\n/**\n * Base CSV parser class with streaming support for large files\n */\nexport class CSVParser {\n private config: Required<CSVParseConfig>\n\n constructor(config: CSVParseConfig = {}) {\n this.config = {\n delimiter: ',',\n quote: '\"',\n escape: '\"',\n skipEmptyLines: true,\n trimValues: true,\n maxRows: 100000, // Safety limit\n progressCallback: () => {},\n ...config,\n }\n }\n\n /**\n * Parse CSV file content\n */\n async parseFile<T = Record<string, string>>(\n fileContent: string,\n validator?: (row: Record<string, string>, rowIndex: number) => T | null\n ): Promise<CSVParseResult<T>> {\n const errors: ParsingError[] = []\n const warnings: string[] = []\n const data: T[] = []\n\n try {\n // Split into lines and handle different line endings\n const lines = fileContent.split(/\\r?\\n/)\n const totalRows = lines.length\n\n if (totalRows === 0) {\n throw new Error('Empty CSV file')\n }\n\n // Parse headers\n const headerLine = lines[0]\n if (!headerLine || headerLine.trim() === '') {\n throw new Error('Missing CSV headers')\n }\n\n const headers = this.parseLine(headerLine)\n if (headers.length === 0) {\n throw new Error('No valid headers found')\n }\n\n // Clean headers (remove BOM, trim whitespace)\n const cleanHeaders = headers.map(header =>\n header.replace(/^\\ufeff/, '').trim()\n )\n\n let validRows = 0\n let processedRows = 0\n\n // Process data rows\n for (let i = 1; i < lines.length && processedRows < this.config.maxRows; i++) {\n const line = lines[i]\n\n // Skip empty lines if configured\n if (this.config.skipEmptyLines && (!line || line.trim() === '')) {\n continue\n }\n\n processedRows++\n\n try {\n const values = this.parseLine(line)\n\n // Create row object\n const row: Record<string, string> = {}\n cleanHeaders.forEach((header, index) => {\n const value = values[index] || ''\n row[header] = this.config.trimValues ? value.trim() : value\n })\n\n // Validate row if validator provided\n if (validator) {\n const validatedRow = validator(row, i)\n if (validatedRow) {\n data.push(validatedRow)\n validRows++\n }\n } else {\n data.push(row as T)\n validRows++\n }\n\n } catch (error) {\n const parsingError: ParsingError = {\n type: 'parsing',\n message: `Error parsing line ${i + 1}: ${error instanceof Error ? error.message : String(error)}`,\n details: { lineNumber: i + 1, line },\n line: i + 1,\n raw: line,\n }\n errors.push(parsingError)\n }\n\n // Report progress\n if (processedRows % 100 === 0 || processedRows === totalRows - 1) {\n const progress = Math.round((processedRows / (totalRows - 1)) * 100)\n this.config.progressCallback(progress, processedRows)\n }\n }\n\n // Check for truncation\n if (processedRows >= this.config.maxRows && lines.length > this.config.maxRows + 1) {\n warnings.push(`File truncated at ${this.config.maxRows} rows for performance`)\n }\n\n return {\n data,\n headers: cleanHeaders,\n totalRows: totalRows - 1, // Excluding header\n validRows,\n errors,\n warnings,\n }\n\n } catch (error) {\n const parsingError: ParsingError = {\n type: 'parsing',\n message: `CSV parsing failed: ${error instanceof Error ? error.message : String(error)}`,\n details: { fileContent: fileContent.substring(0, 500) + '...' },\n line: 0,\n raw: '',\n }\n\n return {\n data: [],\n headers: [],\n totalRows: 0,\n validRows: 0,\n errors: [parsingError],\n warnings,\n }\n }\n }\n\n /**\n * Parse CSV from File object with progress tracking\n */\n async parseFileObject<T = Record<string, string>>(\n file: File,\n validator?: (row: Record<string, string>, rowIndex: number) => T | null,\n progressCallback?: (progress: ParseProgress) => void\n ): Promise<CSVParseResult<T>> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n\n reader.onprogress = (event) => {\n if (event.lengthComputable && progressCallback) {\n const progress = Math.round((event.loaded / event.total) * 100)\n progressCallback({\n stage: 'reading',\n progress,\n rowsProcessed: 0,\n totalRows: 0,\n errors: 0,\n })\n }\n }\n\n reader.onload = async (event) => {\n try {\n const content = event.target?.result as string\n if (!content) {\n throw new Error('Failed to read file content')\n }\n\n // Update progress callback for parsing stage\n const parseConfig = {\n ...this.config,\n progressCallback: (progress: number, rowsProcessed: number) => {\n if (progressCallback) {\n progressCallback({\n stage: 'parsing',\n progress,\n rowsProcessed,\n totalRows: content.split(/\\r?\\n/).length - 1,\n errors: 0,\n })\n }\n },\n }\n\n const parser = new CSVParser(parseConfig)\n const result = await parser.parseFile(content, validator)\n\n if (progressCallback) {\n progressCallback({\n stage: 'completed',\n progress: 100,\n rowsProcessed: result.validRows,\n totalRows: result.totalRows,\n errors: result.errors.length,\n })\n }\n\n resolve(result)\n } catch (error) {\n reject(error)\n }\n }\n\n reader.onerror = () => {\n reject(new Error('Failed to read file'))\n }\n\n reader.readAsText(file)\n })\n }\n\n /**\n * Parse a single CSV line, handling quoted values and escapes\n */\n private parseLine(line: string): string[] {\n const result: string[] = []\n const { delimiter, quote, escape } = this.config\n\n let current = ''\n let inQuotes = false\n let i = 0\n\n while (i < line.length) {\n const char = line[i]\n const nextChar = line[i + 1]\n\n if (!inQuotes) {\n if (char === delimiter) {\n result.push(current)\n current = ''\n } else if (char === quote) {\n inQuotes = true\n } else {\n current += char\n }\n } else {\n if (char === escape && nextChar === quote) {\n // Escaped quote\n current += quote\n i++ // Skip next character\n } else if (char === quote) {\n inQuotes = false\n } else {\n current += char\n }\n }\n\n i++\n }\n\n // Add the last field\n result.push(current)\n\n return result\n }\n\n /**\n * Validate CSV file format before parsing\n */\n static validateCSVFile(file: File): { valid: boolean; error?: string } {\n // Check file type\n const validTypes = ['text/csv', 'application/vnd.ms-excel', 'text/plain']\n if (!validTypes.includes(file.type) && !file.name.toLowerCase().endsWith('.csv')) {\n return { valid: false, error: 'File must be a CSV file (.csv extension)' }\n }\n\n // Check file size (50MB limit)\n const maxSize = 50 * 1024 * 1024\n if (file.size > maxSize) {\n return { valid: false, error: 'File size must be less than 50MB' }\n }\n\n // Check for empty file\n if (file.size === 0) {\n return { valid: false, error: 'File is empty' }\n }\n\n return { valid: true }\n }\n\n /**\n * Detect CSV delimiter from sample content\n */\n static detectDelimiter(sampleContent: string): string {\n const delimiters = [',', ';', '\\t', '|']\n const lines = sampleContent.split(/\\r?\\n/).slice(0, 5) // Check first 5 lines\n\n let bestDelimiter = ','\n let maxScore = 0\n\n for (const delimiter of delimiters) {\n let score = 0\n let consistentCounts = true\n let expectedCount = -1\n\n for (const line of lines) {\n if (!line.trim()) continue\n\n const count = line.split(delimiter).length - 1\n if (expectedCount === -1) {\n expectedCount = count\n } else if (count !== expectedCount) {\n consistentCounts = false\n break\n }\n score += count\n }\n\n if (consistentCounts && score > maxScore) {\n maxScore = score\n bestDelimiter = delimiter\n }\n }\n\n return bestDelimiter\n }\n}","import { z } from 'zod'\n\n/**\n * Zod schema for validating raw trade data from CSV\n */\nexport const rawTradeDataSchema = z.object({\n \"Date Opened\": z.string().min(1, \"Date Opened is required\"),\n \"Time Opened\": z.string().min(1, \"Time Opened is required\"),\n \"Opening Price\": z.string().min(1, \"Opening Price is required\"),\n \"Legs\": z.string().min(1, \"Legs description is required\"),\n \"Premium\": z.string().min(1, \"Premium is required\"),\n \"Closing Price\": z.string().optional(),\n \"Date Closed\": z.string().optional(),\n \"Time Closed\": z.string().optional(),\n \"Avg. Closing Cost\": z.string().optional(),\n \"Reason For Close\": z.string().optional(),\n \"P/L\": z.string().min(1, \"P/L is required\"),\n \"No. of Contracts\": z.string().min(1, \"Number of Contracts is required\"),\n \"Funds at Close\": z.string().min(1, \"Funds at Close is required\"),\n \"Margin Req.\": z.string().min(1, \"Margin Requirement is required\"),\n \"Strategy\": z.string().min(1, \"Strategy is required\"),\n \"Opening Commissions + Fees\": z.string().default(\"0\"),\n \"Closing Commissions + Fees\": z.string().optional(),\n \"Opening Short/Long Ratio\": z.string().default(\"0\"),\n \"Closing Short/Long Ratio\": z.string().optional(),\n \"Opening VIX\": z.string().optional(),\n \"Closing VIX\": z.string().optional(),\n \"Gap\": z.string().optional(),\n \"Movement\": z.string().optional(),\n \"Max Profit\": z.string().optional(),\n \"Max Loss\": z.string().optional(),\n}).passthrough() // Allow custom columns to pass through validation\n\n/**\n * Zod schema for validating processed trade data\n */\nexport const tradeSchema = z.object({\n dateOpened: z.date(),\n timeOpened: z.string().regex(/^\\d{1,2}:\\d{2}:\\d{2}$/, \"Time must be in H:mm:ss or HH:mm:ss format\"),\n openingPrice: z.number().finite(),\n legs: z.string().min(1),\n premium: z.number().finite(),\n premiumPrecision: z.enum(['dollars', 'cents']).optional(),\n closingPrice: z.number().finite().optional(),\n dateClosed: z.date().optional(),\n timeClosed: z.string().regex(/^\\d{1,2}:\\d{2}:\\d{2}$/).optional(),\n avgClosingCost: z.number().finite().optional(),\n reasonForClose: z.string().optional(),\n pl: z.number().finite(),\n numContracts: z.number().int().positive(),\n fundsAtClose: z.number().finite(),\n marginReq: z.number().finite().min(0),\n strategy: z.string().min(1),\n openingCommissionsFees: z.number().finite().min(0),\n closingCommissionsFees: z.number().finite().min(0),\n openingShortLongRatio: z.number().finite().min(0),\n closingShortLongRatio: z.number().finite().min(0).optional(),\n openingVix: z.number().finite().min(0).optional(),\n closingVix: z.number().finite().min(0).optional(),\n gap: z.number().finite().optional(),\n movement: z.number().finite().optional(),\n maxProfit: z.number().finite().optional(),\n maxLoss: z.number().finite().optional(),\n syntheticCapitalRatio: z.number().finite().optional(),\n customFields: z.record(z.string(), z.union([z.number(), z.string()])).optional(),\n})\n\n/**\n * Zod schema for validating raw reporting trade data from strategy logs\n */\nexport const rawReportingTradeDataSchema = z.object({\n \"Strategy\": z.string().min(1, \"Strategy is required\"),\n \"Date Opened\": z.string().min(1, \"Date Opened is required\"),\n \"Time Opened\": z.string().optional(),\n \"Opening Price\": z.string().min(1, \"Opening Price is required\"),\n \"Legs\": z.string().min(1, \"Legs description is required\"),\n \"Initial Premium\": z.string().min(1, \"Initial Premium is required\"),\n \"No. of Contracts\": z.string().min(1, \"Number of Contracts is required\"),\n \"P/L\": z.string().min(1, \"P/L is required\"),\n \"Closing Price\": z.string().optional(),\n \"Date Closed\": z.string().optional(),\n \"Time Closed\": z.string().optional(),\n \"Avg. Closing Cost\": z.string().optional(),\n \"Reason For Close\": z.string().optional(),\n})\n\n/**\n * Zod schema for validating processed reporting trade data\n */\nexport const reportingTradeSchema = z.object({\n strategy: z.string().min(1),\n dateOpened: z.date(),\n timeOpened: z.string().optional(),\n openingPrice: z.number().finite(),\n legs: z.string().min(1),\n initialPremium: z.number().finite(),\n numContracts: z.number().finite(),\n pl: z.number().finite(),\n closingPrice: z.number().finite().optional(),\n dateClosed: z.date().optional(),\n timeClosed: z.string().optional(),\n avgClosingCost: z.number().finite().optional(),\n reasonForClose: z.string().optional(),\n})\n\n/**\n * Zod schema for validating raw daily log data from CSV\n */\nexport const rawDailyLogDataSchema = z.object({\n \"Date\": z.string().min(1, \"Date is required\"),\n \"Net Liquidity\": z.string().min(1, \"Net Liquidity is required\"),\n \"Current Funds\": z.string().min(1, \"Current Funds is required\"),\n \"Withdrawn\": z.string().default(\"0\"),\n \"Trading Funds\": z.string().min(1, \"Trading Funds is required\"),\n \"P/L\": z.string().min(1, \"P/L is required\"),\n \"P/L %\": z.string().min(1, \"P/L % is required\"),\n \"Drawdown %\": z.string().min(1, \"Drawdown % is required\"),\n}).passthrough() // Allow custom columns to pass through validation\n\n/**\n * Zod schema for validating processed daily log entry\n */\nexport const dailyLogEntrySchema = z.object({\n date: z.date(),\n netLiquidity: z.number().finite(),\n currentFunds: z.number().finite(),\n withdrawn: z.number().finite().min(0),\n tradingFunds: z.number().finite().min(0),\n dailyPl: z.number().finite(),\n dailyPlPct: z.number().finite(),\n drawdownPct: z.number().finite().max(0), // Drawdown should be negative or zero\n blockId: z.string().optional(),\n customFields: z.record(z.string(), z.union([z.number(), z.string()])).optional(),\n})\n\n/**\n * Zod schema for portfolio statistics\n */\nexport const portfolioStatsSchema = z.object({\n totalTrades: z.number().int().min(0),\n totalPl: z.number().finite(),\n winRate: z.number().min(0).max(1),\n avgWin: z.number().finite().min(0),\n avgLoss: z.number().finite().max(0),\n maxWin: z.number().finite().min(0),\n maxLoss: z.number().finite().max(0),\n sharpeRatio: z.number().finite().optional(),\n maxDrawdown: z.number().finite().max(0),\n avgDailyPl: z.number().finite(),\n totalCommissions: z.number().finite().min(0),\n netPl: z.number().finite(),\n profitFactor: z.number().finite().min(0),\n})\n\n/**\n * Zod schema for strategy statistics\n */\nexport const strategyStatsSchema = z.object({\n strategyName: z.string().min(1),\n tradeCount: z.number().int().min(0),\n totalPl: z.number().finite(),\n winRate: z.number().min(0).max(1),\n avgWin: z.number().finite().min(0),\n avgLoss: z.number().finite().max(0),\n maxWin: z.number().finite().min(0),\n maxLoss: z.number().finite().max(0),\n avgDte: z.number().finite().min(0).optional(),\n successRate: z.number().min(0).max(1),\n profitFactor: z.number().finite().min(0),\n})\n\n/**\n * Zod schema for analysis configuration\n */\nexport const analysisConfigSchema = z.object({\n useBusinessDaysOnly: z.boolean(),\n annualizationFactor: z.number().int().min(200).max(365),\n confidenceLevel: z.number().min(0.8).max(0.99),\n})\n\n/**\n * Zod schema for file validation\n */\nexport const fileSchema = z.object({\n name: z.string().min(1),\n size: z.number().int().positive(),\n type: z.string().refine(\n (type) => type === 'text/csv' || type === 'application/vnd.ms-excel',\n \"File must be a CSV file\"\n ),\n})\n\n/**\n * Zod schema for block creation request\n */\nexport const createBlockRequestSchema = z.object({\n name: z.string().min(1).max(100),\n description: z.string().max(500).optional(),\n analysisConfig: analysisConfigSchema.partial().optional(),\n})\n\n/**\n * Type exports for use with TypeScript\n */\nexport type RawTradeData = z.infer<typeof rawTradeDataSchema>\nexport type ValidatedTrade = z.infer<typeof tradeSchema>\nexport type RawReportingTradeData = z.infer<typeof rawReportingTradeDataSchema>\nexport type ValidatedReportingTrade = z.infer<typeof reportingTradeSchema>\nexport type RawDailyLogData = z.infer<typeof rawDailyLogDataSchema>\nexport type ValidatedDailyLogEntry = z.infer<typeof dailyLogEntrySchema>\nexport type ValidatedPortfolioStats = z.infer<typeof portfolioStatsSchema>\nexport type ValidatedStrategyStats = z.infer<typeof strategyStatsSchema>\nexport type ValidatedAnalysisConfig = z.infer<typeof analysisConfigSchema>\nexport type ValidatedFile = z.infer<typeof fileSchema>\nexport type ValidatedCreateBlockRequest = z.infer<typeof createBlockRequestSchema>\n","/**\n * CSV header utilities\n */\n\nexport interface HeaderValidationOptions {\n /** Optional map of alternate header names to canonical names */\n aliases?: Record<string, string> | Readonly<Record<string, string>>\n /** Human-readable label used in error messages */\n contextLabel?: string\n}\n\nconst BOM = '\\uFEFF'\n\n/**\n * Remove UTF-8 byte order mark from a string if present\n */\nexport function stripBom(value: string): string {\n return value.startsWith(BOM) ? value.slice(1) : value\n}\n\n/**\n * Parse a single CSV line into values, handling quoted fields and commas\n */\nexport function parseCsvLine(line: string): string[] {\n const result: string[] = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i]\n\n if (char === '\"') {\n const nextChar = line[i + 1]\n if (inQuotes && nextChar === '\"') {\n // Escaped quote inside quoted value\n current += '\"'\n i++\n } else {\n inQuotes = !inQuotes\n }\n continue\n }\n\n if (char === ',' && !inQuotes) {\n result.push(current.trim())\n current = ''\n } else {\n current += char\n }\n }\n\n result.push(current.trim())\n\n if (result.length > 0) {\n result[0] = stripBom(result[0])\n }\n\n return result\n}\n\n/**\n * Normalize a CSV header by trimming whitespace, stripping BOM, and applying aliases\n */\nexport function normalizeHeader(\n header: string,\n aliases?: Record<string, string> | Readonly<Record<string, string>>\n): string {\n const trimmed = stripBom(header).trim()\n return aliases?.[trimmed] ?? trimmed\n}\n\n/**\n * Normalize an array of headers\n */\nexport function normalizeHeaders(\n headers: string[],\n aliases?: Record<string, string> | Readonly<Record<string, string>>\n): string[] {\n return headers.map(header => normalizeHeader(header, aliases))\n}\n\n/**\n * Validate that required headers are present. Returns the missing headers without throwing.\n */\nexport function findMissingHeaders(\n headers: string[],\n required: readonly string[]\n): string[] {\n const headerSet = new Set(headers)\n return required.filter(requiredHeader => !headerSet.has(requiredHeader))\n}\n\n/**\n * Ensure required headers are present, throwing an Error with a helpful message when missing.\n */\nexport function assertRequiredHeaders(\n headers: string[],\n required: readonly string[],\n options: HeaderValidationOptions = {}\n): void {\n const missing = findMissingHeaders(headers, required)\n if (missing.length === 0) {\n return\n }\n\n const label = options.contextLabel ? `${options.contextLabel} ` : ''\n throw new Error(`Missing required ${label}columns: ${missing.join(', ')}`)\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
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(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\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,KAAK,oBAAI,IAAI;AAAA,IACxB,QAAQ,WAAW,YAAY;AAAA,IAC/B,QAAQ,WAAW,qBAAqB;AAAA,IACxC,QAAQ,WAAW,+BAA+B;AAAA,IAClD,QAAQ,WAAW,6BAA6B;AAAA,IAChD,QAAQ,QAAQ,IAAI,GAAG,8DAA8D;AAAA,IACrF,QAAQ,QAAQ,IAAI,GAAG,0CAA0C;AAAA,IACjE,QAAQ,QAAQ,IAAI,GAAG,mBAAmB;AAAA,IAC1C,QAAQ,QAAQ,IAAI,GAAG,iBAAiB;AAAA,EAC1C,CAAC,CAAC;AACJ;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":[]}
@@ -1 +0,0 @@
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 = 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 = trades\n .filter(t => t.closingPrice !== undefined)\n .reduce((sum, t) => sum + (t.closingPrice! * Math.abs(t.premium)), 0) / 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 = totalAbsPremium > 0\n ? trades.reduce((sum, t) => sum + (t.openingShortLongRatio * Math.abs(t.premium)), 0) / 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: 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(trades: ReportingTrade[]): 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 = trades.reduce(\n (sum, t) => sum + (t.closingPrice! * Math.abs(t.initialPremium)),\n 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(\n blockId: string\n): 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(\n blockId: string\n): 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(\n blockId: string\n): 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 {\n promisifyRequest,\n STORES,\n withReadTransaction,\n withWriteTransaction,\n} 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(\n blockId: string\n): 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(\n blockId: string\n): 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 {\n combineAllLegGroups,\n type CombinedTrade\n} 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(\n blockId: string,\n trades: Trade[]\n): 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(\n blockId: string\n): 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(\n [blockId, startDate],\n [blockId, endDate],\n false,\n false\n );\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(\n blockId: string,\n trades: Trade[]\n): 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) =>\n 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(\n new Set(trades.map((trade) => trade.strategy))\n ).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(\n blockId: string,\n query: string\n): 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 &&\n 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(\n \"composite_block_date\",\n [\"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(\n \"composite_block_date\",\n [\"blockId\", \"date\"],\n { unique: false }\n );\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(\n INDEXES.REPORTING_LOGS_BY_STRATEGY,\n \"strategy\",\n { unique: false }\n );\n reportingStore.createIndex(\n \"composite_block_date\",\n [\"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(\n INDEXES.CALCULATIONS_BY_BLOCK,\n \"blockId\",\n { unique: false }\n );\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(\n INDEXES.STATIC_DATASET_ROWS_BY_DATASET,\n \"datasetId\",\n { unique: false }\n );\n staticDatasetRowsStore.createIndex(\n INDEXES.STATIC_DATASET_ROWS_BY_TIMESTAMP,\n \"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(\n message: string,\n operation: string,\n store?: string,\n cause?: Error\n ) {\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(\n message: string,\n operation: string,\n store?: string,\n cause?: Error\n ) {\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 { STORES, INDEXES, withReadTransaction, withWriteTransaction, promisifyRequest } 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(\n [blockId, startDate],\n [blockId, endDate],\n false,\n false\n )\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(blockId: string, date: Date): 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(blockId: string, entries: DailyLogEntry[]): 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((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())\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<Array<{\n date: Date\n netLiquidity: number\n dailyPl: number\n drawdownPct: number\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', 'Net Liquidity', 'Current Funds', 'Withdrawn',\n 'Trading Funds', 'P/L', 'P/L %', '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}"],"mappings":"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","names":["getCacheId","getCacheId"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../lib/models/trade.ts","../../lib/processing/trade-processor.ts"],"sourcesContent":["/**\n * Trade model based on legacy Python Trade class\n * Represents individual trade record from portfolio CSV\n */\nexport interface Trade {\n // Core trade identification\n dateOpened: Date\n timeOpened: string // HH:mm:ss format\n openingPrice: number\n legs: string // Option legs description\n premium: number\n /**\n * Records how the premium value was encoded in the source CSV.\n * Some exports (OptionOmega) provide cents as whole numbers without decimals.\n */\n premiumPrecision?: 'dollars' | 'cents'\n\n // Closing information (optional for open trades)\n closingPrice?: number\n dateClosed?: Date\n timeClosed?: string\n avgClosingCost?: number\n reasonForClose?: string\n\n // Financial metrics\n pl: number // Profit/Loss\n numContracts: number\n fundsAtClose: number\n marginReq: number\n\n // Trade metadata\n strategy: string\n openingCommissionsFees: number\n closingCommissionsFees: number\n\n // Ratios and market data\n openingShortLongRatio: number\n closingShortLongRatio?: number\n openingVix?: number\n closingVix?: number\n\n // Additional metrics\n gap?: number\n movement?: number\n maxProfit?: number\n maxLoss?: number\n /**\n * Synthetic-only: ratio of the worst observed loss to account capital at the time\n * Used to scale synthetic losses relative to current account size\n */\n syntheticCapitalRatio?: number\n\n /**\n * Custom fields from extra columns in the trade CSV\n * Keys are the original column names, values are auto-detected as number or string\n */\n customFields?: Record<string, number | string>\n}\n\n/**\n * Raw trade data as it comes from CSV before processing\n */\nexport interface RawTradeData {\n \"Date Opened\": string\n \"Time Opened\": string\n \"Opening Price\": string\n \"Legs\": string\n \"Premium\": string\n \"Closing Price\"?: string\n \"Date Closed\"?: string\n \"Time Closed\"?: string\n \"Avg. Closing Cost\"?: string\n \"Reason For Close\"?: string\n \"P/L\": string\n \"No. of Contracts\": string\n \"Funds at Close\": string\n \"Margin Req.\": string\n \"Strategy\": string\n \"Opening Commissions + Fees\": string\n \"Closing Commissions + Fees\"?: string\n \"Opening Short/Long Ratio\": string\n \"Closing Short/Long Ratio\"?: string\n \"Opening VIX\"?: string\n \"Closing VIX\"?: string\n \"Gap\"?: string\n \"Movement\"?: string\n \"Max Profit\"?: string\n \"Max Loss\"?: string\n}\n\n/**\n * Column mapping from CSV headers to Trade interface properties\n */\nexport const TRADE_COLUMN_MAPPING = {\n \"Date Opened\": \"dateOpened\",\n \"Time Opened\": \"timeOpened\",\n \"Opening Price\": \"openingPrice\",\n \"Legs\": \"legs\",\n \"Premium\": \"premium\",\n \"Closing Price\": \"closingPrice\",\n \"Date Closed\": \"dateClosed\",\n \"Time Closed\": \"timeClosed\",\n \"Avg. Closing Cost\": \"avgClosingCost\",\n \"Reason For Close\": \"reasonForClose\",\n \"P/L\": \"pl\",\n \"No. of Contracts\": \"numContracts\",\n \"Funds at Close\": \"fundsAtClose\",\n \"Margin Req.\": \"marginReq\",\n \"Strategy\": \"strategy\",\n \"Opening Commissions + Fees\": \"openingCommissionsFees\",\n \"Closing Commissions + Fees\": \"closingCommissionsFees\",\n \"Opening Short/Long Ratio\": \"openingShortLongRatio\",\n \"Closing Short/Long Ratio\": \"closingShortLongRatio\",\n \"Opening VIX\": \"openingVix\",\n \"Closing VIX\": \"closingVix\",\n \"Gap\": \"gap\",\n \"Movement\": \"movement\",\n \"Max Profit\": \"maxProfit\",\n \"Max Loss\": \"maxLoss\",\n} as const\n\n/**\n * Column aliases for different CSV export variations\n */\nexport const TRADE_COLUMN_ALIASES = {\n \"Opening comms & fees\": \"Opening Commissions + Fees\",\n \"Opening Commissions & Fees\": \"Opening Commissions + Fees\",\n \"Closing comms & fees\": \"Closing Commissions + Fees\",\n \"Closing Commissions & Fees\": \"Closing Commissions + Fees\",\n \"P/L %\": \"P/L %\", // Recognized but ignored (we calculate our own plPct)\n} as const\n\n/**\n * Minimum required columns for a valid trade log\n */\nexport const REQUIRED_TRADE_COLUMNS = [\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] as const\n","/**\n * Trade Processor\n *\n * Handles parsing and processing of trade log CSV files from OptionOmega.\n * Converts raw CSV data to validated Trade objects.\n */\n\nimport { type Trade, TRADE_COLUMN_ALIASES, REQUIRED_TRADE_COLUMNS, TRADE_COLUMN_MAPPING } from '../models/trade.ts'\nimport type { ValidationError, ProcessingError } from '../models/index.ts'\nimport { rawTradeDataSchema, tradeSchema } from '../models/validators.ts'\nimport { CSVParser, type ParseProgress } from './csv-parser.ts'\nimport { findMissingHeaders, normalizeHeaders } from '../utils/csv-headers.ts'\n\n/**\n * Set of known trade column names (canonical names from TRADE_COLUMN_MAPPING)\n * Used to identify custom columns that should be preserved\n */\nconst KNOWN_TRADE_COLUMNS = new Set([\n ...Object.keys(TRADE_COLUMN_MAPPING),\n ...Object.keys(TRADE_COLUMN_ALIASES),\n])\n\n/**\n * Trade processing configuration\n */\nexport interface TradeProcessingConfig {\n maxTrades?: number\n strictValidation?: boolean\n progressCallback?: (progress: TradeProcessingProgress) => void\n}\n\n/**\n * Trade processing progress\n */\nexport interface TradeProcessingProgress extends ParseProgress {\n stage: 'reading' | 'parsing' | 'validating' | 'converting' | 'completed'\n validTrades: number\n invalidTrades: number\n}\n\n/**\n * Trade processing result\n */\nexport interface TradeProcessingResult {\n trades: Trade[]\n totalRows: number\n validTrades: number\n invalidTrades: number\n errors: ProcessingError[]\n warnings: string[]\n stats: {\n processingTimeMs: number\n strategies: string[]\n dateRange: { start: Date | null; end: Date | null }\n totalPL: number\n }\n}\n\n/**\n * Trade processor class\n */\nexport class TradeProcessor {\n private config: Required<TradeProcessingConfig>\n\n constructor(config: TradeProcessingConfig = {}) {\n this.config = {\n maxTrades: 50000,\n strictValidation: false,\n progressCallback: () => {},\n ...config,\n }\n }\n\n /**\n * Process trade log file\n */\n async processFile(file: File): Promise<TradeProcessingResult> {\n const startTime = Date.now()\n const errors: ProcessingError[] = []\n const warnings: string[] = []\n\n try {\n // Validate file\n const fileValidation = CSVParser.validateCSVFile(file)\n if (!fileValidation.valid) {\n throw new Error(fileValidation.error)\n }\n\n // Configure CSV parser\n const csvParser = new CSVParser({\n maxRows: this.config.maxTrades,\n progressCallback: (progress, rowsProcessed) => {\n this.config.progressCallback({\n stage: 'parsing',\n progress,\n rowsProcessed,\n totalRows: 0,\n errors: 0,\n validTrades: 0,\n invalidTrades: 0,\n })\n },\n })\n\n // Parse CSV with validation\n const parseResult = await csvParser.parseFileObject(\n file,\n (row, rowIndex) => this.validateRawTradeData(row, rowIndex),\n (progress) => {\n this.config.progressCallback({\n ...progress,\n validTrades: 0,\n invalidTrades: 0,\n })\n }\n )\n\n // Collect parsing errors\n errors.push(...parseResult.errors)\n warnings.push(...parseResult.warnings)\n\n // Check for required columns\n const normalizedHeaders = normalizeHeaders(parseResult.headers, TRADE_COLUMN_ALIASES)\n const missingColumns = findMissingHeaders(normalizedHeaders, REQUIRED_TRADE_COLUMNS)\n if (missingColumns.length > 0) {\n throw new Error(`Missing required columns: ${missingColumns.join(', ')}`)\n }\n\n // Update progress for conversion stage\n this.config.progressCallback({\n stage: 'converting',\n progress: 0,\n rowsProcessed: 0,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades: 0,\n invalidTrades: 0,\n })\n\n // Convert validated data to Trade objects\n const trades: Trade[] = []\n let validTrades = 0\n let invalidTrades = 0\n\n for (let i = 0; i < parseResult.data.length; i++) {\n try {\n const trade = this.convertToTrade(parseResult.data[i])\n trades.push(trade)\n validTrades++\n } catch (error) {\n invalidTrades++\n const errorMessage = `Trade conversion failed at row ${i + 2}: ${error instanceof Error ? error.message : String(error)}`\n\n // Log conversion errors to console for debugging\n console.warn(`[TradeProcessor] ${errorMessage}`)\n\n const validationError: ValidationError = {\n type: 'validation',\n message: errorMessage,\n details: { row: parseResult.data[i], rowIndex: i + 2 },\n field: 'unknown',\n value: parseResult.data[i],\n expected: 'Valid trade data',\n }\n errors.push(validationError)\n\n if (!this.config.strictValidation) {\n continue // Skip invalid row in non-strict mode\n } else {\n throw error // Fail fast in strict mode\n }\n }\n\n // Update progress\n if (i % 100 === 0 || i === parseResult.data.length - 1) {\n const progress = Math.round((i / parseResult.data.length) * 100)\n this.config.progressCallback({\n stage: 'converting',\n progress,\n rowsProcessed: i + 1,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades,\n invalidTrades,\n })\n }\n }\n\n // Sort trades for consistent ordering (handles simultaneous trades)\n trades.sort((a, b) => {\n const dateCompare = new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()\n if (dateCompare !== 0) return dateCompare\n\n // Secondary sort by time\n const timeCompare = a.timeOpened.localeCompare(b.timeOpened)\n if (timeCompare !== 0) return timeCompare\n\n // Tertiary sort by funds_at_close (lower first for simultaneous trades)\n return a.fundsAtClose - b.fundsAtClose\n })\n\n // Calculate statistics\n const processingTimeMs = Date.now() - startTime\n const strategies = Array.from(new Set(trades.map(t => t.strategy))).sort()\n const dates = trades.map(t => new Date(t.dateOpened))\n const dateRange = {\n start: dates.length > 0 ? new Date(Math.min(...dates.map(d => d.getTime()))) : null,\n end: dates.length > 0 ? new Date(Math.max(...dates.map(d => d.getTime()))) : null,\n }\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0)\n\n // Final progress update\n this.config.progressCallback({\n stage: 'completed',\n progress: 100,\n rowsProcessed: parseResult.data.length,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades,\n invalidTrades,\n })\n\n return {\n trades,\n totalRows: parseResult.totalRows,\n validTrades,\n invalidTrades,\n errors,\n warnings,\n stats: {\n processingTimeMs,\n strategies,\n dateRange,\n totalPL,\n },\n }\n\n } catch (error) {\n const processingError: ProcessingError = {\n type: 'parsing',\n message: `Trade processing failed: ${error instanceof Error ? error.message : String(error)}`,\n details: { fileName: file.name, fileSize: file.size },\n }\n\n return {\n trades: [],\n totalRows: 0,\n validTrades: 0,\n invalidTrades: 0,\n errors: [processingError, ...errors],\n warnings,\n stats: {\n processingTimeMs: Date.now() - startTime,\n strategies: [],\n dateRange: { start: null, end: null },\n totalPL: 0,\n },\n }\n }\n }\n\n /**\n * Validate raw trade data from CSV\n */\n private validateRawTradeData(row: Record<string, string>, rowIndex: number): Record<string, string> | null {\n try {\n // Apply column aliases to normalize variations\n const normalizedRow = { ...row }\n Object.entries(TRADE_COLUMN_ALIASES).forEach(([alias, canonical]) => {\n if (normalizedRow[alias] !== undefined) {\n normalizedRow[canonical] = normalizedRow[alias]\n delete normalizedRow[alias]\n }\n })\n\n // OptionOmega sometimes leaves strategy blank; default to Unknown so downstream stats still work\n if (!normalizedRow['Strategy'] || normalizedRow['Strategy'].trim() === '') {\n normalizedRow['Strategy'] = 'Unknown'\n }\n\n // Ensure required columns have values\n const requiredFields = ['Date Opened', 'P/L', 'Strategy']\n for (const field of requiredFields) {\n if (!normalizedRow[field] || normalizedRow[field].trim() === '') {\n throw new Error(`Missing required field: ${field}`)\n }\n }\n\n // Set default values for missing optional fields\n if (!normalizedRow['Opening Commissions + Fees']) {\n normalizedRow['Opening Commissions + Fees'] = '0'\n }\n if (!normalizedRow['Closing Commissions + Fees']) {\n normalizedRow['Closing Commissions + Fees'] = '0'\n }\n if (!normalizedRow['Opening Short/Long Ratio']) {\n normalizedRow['Opening Short/Long Ratio'] = '0'\n }\n\n const optionalNumericFieldsWithDefaultZero = [\n 'Opening VIX',\n 'Closing VIX',\n 'Gap',\n 'Movement',\n ] as const\n optionalNumericFieldsWithDefaultZero.forEach(field => {\n if (!normalizedRow[field] || normalizedRow[field].trim() === '') {\n normalizedRow[field] = '0'\n }\n })\n\n // Basic format validation (detailed validation happens in conversion)\n rawTradeDataSchema.parse(normalizedRow)\n\n return normalizedRow\n } catch (error) {\n // Log validation errors to console for debugging\n console.warn(`[TradeProcessor] Row ${rowIndex + 2} validation failed:`, error instanceof Error ? error.message : error)\n // Return null for invalid rows - they'll be counted as invalid\n return null\n }\n }\n\n /**\n * Parse a YYYY-MM-DD date string preserving the calendar date.\n *\n * Option Omega exports dates in Eastern time. JavaScript's new Date('YYYY-MM-DD')\n * parses as UTC midnight, which when converted to local time can shift to the\n * previous day (e.g., Dec 11 UTC → Dec 10 7pm EST).\n *\n * This method creates a Date representing midnight local time on the specified\n * calendar date, so Dec 11 in the CSV becomes Dec 11 in the app regardless of timezone.\n */\n private parseDatePreservingCalendarDay(dateStr: string): Date {\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/)\n if (match) {\n const [, year, month, day] = match\n // Create date at midnight local time - this preserves the calendar date\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day))\n }\n // Fall back to default parsing for other formats\n return new Date(dateStr)\n }\n\n /**\n * Convert validated CSV row to Trade object\n */\n private convertToTrade(rawData: Record<string, string>): Trade {\n try {\n // Parse dates preserving calendar day\n const dateOpened = this.parseDatePreservingCalendarDay(rawData['Date Opened'])\n if (isNaN(dateOpened.getTime())) {\n throw new Error(`Invalid Date Opened: ${rawData['Date Opened']}`)\n }\n\n const dateClosed = rawData['Date Closed'] ? this.parseDatePreservingCalendarDay(rawData['Date Closed']) : undefined\n if (dateClosed && isNaN(dateClosed.getTime())) {\n throw new Error(`Invalid Date Closed: ${rawData['Date Closed']}`)\n }\n\n // Normalize strategy name (handle empty strings)\n const strategy = (rawData['Strategy'] || '').trim() || 'Unknown'\n\n // Parse numeric values with error handling and NaN handling\n const parseNumber = (value: string | undefined, fieldName: string, defaultValue?: number): number => {\n if (!value || value.trim() === '' || value.toLowerCase() === 'nan') {\n if (defaultValue !== undefined) return defaultValue\n throw new Error(`Missing required numeric field: ${fieldName}`)\n }\n\n // Remove currency symbols and commas\n const cleaned = value.replace(/[$,]/g, '').trim()\n const parsed = parseFloat(cleaned)\n\n if (isNaN(parsed)) {\n if (defaultValue !== undefined) return defaultValue\n throw new Error(`Invalid numeric value for ${fieldName}: ${value}`)\n }\n\n return parsed\n }\n\n // Build trade object\n const rawPremiumString = (rawData['Premium'] || '').replace(/[$,]/g, '').trim()\n const premiumPrecision: Trade['premiumPrecision'] =\n rawPremiumString && !rawPremiumString.includes('.') ? 'cents' : 'dollars'\n\n const trade: Trade = {\n dateOpened,\n timeOpened: rawData['Time Opened'] || '00:00:00',\n openingPrice: parseNumber(rawData['Opening Price'], 'Opening Price'),\n legs: rawData['Legs'] || '',\n premium: parseNumber(rawData['Premium'], 'Premium'),\n premiumPrecision,\n closingPrice: rawData['Closing Price'] ? parseNumber(rawData['Closing Price'], 'Closing Price') : undefined,\n dateClosed,\n timeClosed: rawData['Time Closed'] || undefined,\n avgClosingCost: rawData['Avg. Closing Cost'] ? parseNumber(rawData['Avg. Closing Cost'], 'Avg. Closing Cost') : undefined,\n reasonForClose: rawData['Reason For Close'] || undefined,\n pl: parseNumber(rawData['P/L'], 'P/L'),\n numContracts: Math.round(parseNumber(rawData['No. of Contracts'], 'No. of Contracts')),\n fundsAtClose: parseNumber(rawData['Funds at Close'], 'Funds at Close'),\n marginReq: parseNumber(rawData['Margin Req.'], 'Margin Req.'),\n strategy,\n openingCommissionsFees: parseNumber(rawData['Opening Commissions + Fees'], 'Opening Commissions', 0),\n closingCommissionsFees: parseNumber(rawData['Closing Commissions + Fees'], 'Closing Commissions', 0),\n openingShortLongRatio: parseNumber(rawData['Opening Short/Long Ratio'], 'Opening Short/Long Ratio', 0),\n closingShortLongRatio: rawData['Closing Short/Long Ratio'] ? parseNumber(rawData['Closing Short/Long Ratio'], 'Closing Short/Long Ratio') : undefined,\n openingVix: rawData['Opening VIX'] ? parseNumber(rawData['Opening VIX'], 'Opening VIX') : undefined,\n closingVix: rawData['Closing VIX'] ? parseNumber(rawData['Closing VIX'], 'Closing VIX') : undefined,\n gap: rawData['Gap'] ? parseNumber(rawData['Gap'], 'Gap') : undefined,\n movement: rawData['Movement'] ? parseNumber(rawData['Movement'], 'Movement') : undefined,\n maxProfit: rawData['Max Profit'] ? parseNumber(rawData['Max Profit'], 'Max Profit') : undefined,\n maxLoss: rawData['Max Loss'] ? parseNumber(rawData['Max Loss'], 'Max Loss') : undefined,\n }\n\n // Extract custom fields (columns not in KNOWN_TRADE_COLUMNS)\n const customFields: Record<string, number | string> = {}\n for (const [key, value] of Object.entries(rawData)) {\n if (!KNOWN_TRADE_COLUMNS.has(key) && value !== undefined && value.trim() !== '') {\n // Auto-detect type: try to parse as number\n const cleaned = value.replace(/[$,%]/g, '').trim()\n const parsed = parseFloat(cleaned)\n if (!isNaN(parsed) && isFinite(parsed)) {\n customFields[key] = parsed\n } else {\n customFields[key] = value.trim()\n }\n }\n }\n\n // Only add customFields if there are any\n if (Object.keys(customFields).length > 0) {\n trade.customFields = customFields\n }\n\n // Final validation with Zod schema\n const validatedTrade = tradeSchema.parse(trade)\n return validatedTrade\n\n } catch (error) {\n throw new Error(`Trade conversion failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n /**\n * Process CSV content directly (for testing)\n */\n async processCSVContent(content: string): Promise<TradeProcessingResult> {\n // Create a mock File object for testing\n const blob = new Blob([content], { type: 'text/csv' })\n const file = new File([blob], 'test.csv', { type: 'text/csv' })\n return this.processFile(file)\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../lib/models/daily-log.ts","../../lib/processing/daily-log-processor.ts"],"sourcesContent":["/**\n * Daily log model based on legacy Python DailyLogEntry class\n * Represents daily portfolio performance data from OptionOmega\n */\nexport interface DailyLogEntry {\n date: Date\n netLiquidity: number\n currentFunds: number\n withdrawn: number\n tradingFunds: number\n dailyPl: number // P/L for the day\n dailyPlPct: number // P/L percentage\n drawdownPct: number // Drawdown percentage\n blockId?: string // Optional block ID for linking to trades\n\n /**\n * Custom fields from extra columns in the daily log CSV\n * Keys are the original column names, values are auto-detected as number or string\n * These fields can be joined to trades by date for analysis (e.g., dayOpenVix, spyOpen)\n */\n customFields?: Record<string, number | string>\n}\n\n/**\n * Raw daily log data as it comes from CSV before processing\n */\nexport interface RawDailyLogData {\n \"Date\": string\n \"Net Liquidity\": string\n \"Current Funds\": string\n \"Withdrawn\": string\n \"Trading Funds\": string\n \"P/L\": string\n \"P/L %\": string\n \"Drawdown %\": string\n}\n\n/**\n * Processed daily log collection with metadata\n */\nexport interface DailyLog {\n entries: DailyLogEntry[]\n uploadTimestamp: Date\n filename: string\n totalEntries: number\n dateRangeStart: Date\n dateRangeEnd: Date\n finalPortfolioValue: number\n maxDrawdown: number\n}\n\n/**\n * Column mapping from CSV headers to DailyLogEntry interface properties\n */\nexport const DAILY_LOG_COLUMN_MAPPING = {\n \"Date\": \"date\",\n \"Net Liquidity\": \"netLiquidity\",\n \"Current Funds\": \"currentFunds\",\n \"Withdrawn\": \"withdrawn\",\n \"Trading Funds\": \"tradingFunds\",\n \"P/L\": \"dailyPl\",\n \"P/L %\": \"dailyPlPct\",\n \"Drawdown %\": \"drawdownPct\",\n} as const\n\n/**\n * Required columns for daily log processing\n */\nexport const REQUIRED_DAILY_LOG_COLUMNS = [\n \"Date\",\n \"Net Liquidity\",\n \"Current Funds\",\n \"Trading Funds\",\n \"P/L\",\n \"P/L %\",\n \"Drawdown %\",\n] as const\n","/**\n * Daily Log Processor\n *\n * Handles parsing and processing of daily log CSV files from OptionOmega.\n * Converts raw CSV data to validated DailyLogEntry objects.\n */\n\nimport { type DailyLogEntry, REQUIRED_DAILY_LOG_COLUMNS, DAILY_LOG_COLUMN_MAPPING } from '../models/daily-log.ts'\n\n/**\n * Set of known daily log column names (canonical names from DAILY_LOG_COLUMN_MAPPING)\n * Used to identify custom columns that should be preserved\n */\nconst KNOWN_DAILY_LOG_COLUMNS = new Set([\n ...Object.keys(DAILY_LOG_COLUMN_MAPPING),\n 'Withdrawn', // Optional column that may not be in REQUIRED but is known\n])\nimport type { ValidationError, ProcessingError } from '../models/index.ts'\nimport { rawDailyLogDataSchema, dailyLogEntrySchema } from '../models/validators.ts'\nimport { CSVParser, type ParseProgress } from './csv-parser.ts'\nimport { findMissingHeaders } from '../utils/csv-headers.ts'\n// import { CSVParseResult } from './csv-parser.ts'\n\n/**\n * Daily log processing configuration\n */\nexport interface DailyLogProcessingConfig {\n maxEntries?: number\n strictValidation?: boolean\n progressCallback?: (progress: DailyLogProcessingProgress) => void\n}\n\n/**\n * Daily log processing progress\n */\nexport interface DailyLogProcessingProgress extends ParseProgress {\n stage: 'reading' | 'parsing' | 'validating' | 'converting' | 'completed'\n validEntries: number\n invalidEntries: number\n}\n\n/**\n * Daily log processing result\n */\nexport interface DailyLogProcessingResult {\n entries: DailyLogEntry[]\n totalRows: number\n validEntries: number\n invalidEntries: number\n errors: ProcessingError[]\n warnings: string[]\n stats: {\n processingTimeMs: number\n dateRange: { start: Date | null; end: Date | null }\n finalPortfolioValue: number\n maxDrawdown: number\n totalPL: number\n }\n}\n\n/**\n * Daily log processor class\n */\nexport class DailyLogProcessor {\n private config: Required<DailyLogProcessingConfig>\n\n constructor(config: DailyLogProcessingConfig = {}) {\n this.config = {\n maxEntries: 10000, // Reasonable limit for daily entries\n strictValidation: false,\n progressCallback: () => {},\n ...config,\n }\n }\n\n /**\n * Process daily log file\n */\n async processFile(file: File, blockId?: string): Promise<DailyLogProcessingResult> {\n const startTime = Date.now()\n const errors: ProcessingError[] = []\n const warnings: string[] = []\n\n try {\n // Validate file\n const fileValidation = CSVParser.validateCSVFile(file)\n if (!fileValidation.valid) {\n throw new Error(fileValidation.error)\n }\n\n // Configure CSV parser\n const csvParser = new CSVParser({\n maxRows: this.config.maxEntries,\n progressCallback: (progress, rowsProcessed) => {\n this.config.progressCallback({\n stage: 'parsing',\n progress,\n rowsProcessed,\n totalRows: 0,\n errors: 0,\n validEntries: 0,\n invalidEntries: 0,\n })\n },\n })\n\n // Parse CSV with validation\n const parseResult = await csvParser.parseFileObject(\n file,\n (row, rowIndex) => this.validateRawDailyLogData(row, rowIndex),\n (progress) => {\n this.config.progressCallback({\n ...progress,\n validEntries: 0,\n invalidEntries: 0,\n })\n }\n )\n\n // Collect parsing errors\n errors.push(...parseResult.errors)\n warnings.push(...parseResult.warnings)\n\n // Check for required columns\n const missingColumns = findMissingHeaders(parseResult.headers, REQUIRED_DAILY_LOG_COLUMNS)\n if (missingColumns.length > 0) {\n throw new Error(`Missing required columns: ${missingColumns.join(', ')}`)\n }\n\n // Update progress for conversion stage\n this.config.progressCallback({\n stage: 'converting',\n progress: 0,\n rowsProcessed: 0,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries: 0,\n invalidEntries: 0,\n })\n\n // Convert validated data to DailyLogEntry objects\n const entries: DailyLogEntry[] = []\n let validEntries = 0\n let invalidEntries = 0\n\n for (let i = 0; i < parseResult.data.length; i++) {\n try {\n const entry = this.convertToDailyLogEntry(parseResult.data[i], blockId)\n entries.push(entry)\n validEntries++\n } catch (error) {\n invalidEntries++\n const errorMessage = `Daily log entry conversion failed at row ${i + 2}: ${error instanceof Error ? error.message : String(error)}`\n\n // Log conversion errors to console for debugging\n console.warn(`[DailyLogProcessor] ${errorMessage}`)\n\n const validationError: ValidationError = {\n type: 'validation',\n message: errorMessage,\n details: { row: parseResult.data[i], rowIndex: i + 2 },\n field: 'unknown',\n value: parseResult.data[i],\n expected: 'Valid daily log entry data',\n }\n errors.push(validationError)\n\n if (!this.config.strictValidation) {\n continue // Skip invalid row in non-strict mode\n } else {\n throw error // Fail fast in strict mode\n }\n }\n\n // Update progress\n if (i % 50 === 0 || i === parseResult.data.length - 1) {\n const progress = Math.round((i / parseResult.data.length) * 100)\n this.config.progressCallback({\n stage: 'converting',\n progress,\n rowsProcessed: i + 1,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries,\n invalidEntries,\n })\n }\n }\n\n // Sort entries by date\n entries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())\n\n // Calculate statistics\n const processingTimeMs = Date.now() - startTime\n const dates = entries.map(e => new Date(e.date))\n const dateRange = {\n start: dates.length > 0 ? new Date(Math.min(...dates.map(d => d.getTime()))) : null,\n end: dates.length > 0 ? new Date(Math.max(...dates.map(d => d.getTime()))) : null,\n }\n const finalPortfolioValue = entries.length > 0 ? entries[entries.length - 1].netLiquidity : 0\n const maxDrawdown = entries.length > 0 ? Math.min(...entries.map(e => e.drawdownPct)) : 0\n const totalPL = entries.reduce((sum, e) => sum + e.dailyPl, 0)\n\n // Final progress update\n this.config.progressCallback({\n stage: 'completed',\n progress: 100,\n rowsProcessed: parseResult.data.length,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries,\n invalidEntries,\n })\n\n return {\n entries,\n totalRows: parseResult.totalRows,\n validEntries,\n invalidEntries,\n errors,\n warnings,\n stats: {\n processingTimeMs,\n dateRange,\n finalPortfolioValue,\n maxDrawdown,\n totalPL,\n },\n }\n\n } catch (error) {\n const processingError: ProcessingError = {\n type: 'parsing',\n message: `Daily log processing failed: ${error instanceof Error ? error.message : String(error)}`,\n details: { fileName: file.name, fileSize: file.size },\n }\n\n return {\n entries: [],\n totalRows: 0,\n validEntries: 0,\n invalidEntries: 0,\n errors: [processingError, ...errors],\n warnings,\n stats: {\n processingTimeMs: Date.now() - startTime,\n dateRange: { start: null, end: null },\n finalPortfolioValue: 0,\n maxDrawdown: 0,\n totalPL: 0,\n },\n }\n }\n }\n\n /**\n * Validate raw daily log data from CSV\n */\n private validateRawDailyLogData(row: Record<string, string>, rowIndex: number): Record<string, string> | null {\n try {\n // Set default values for missing optional fields\n const normalizedRow = { ...row }\n if (!normalizedRow['Withdrawn']) {\n normalizedRow['Withdrawn'] = '0'\n }\n\n // Ensure required columns have values\n for (const field of REQUIRED_DAILY_LOG_COLUMNS) {\n if (!normalizedRow[field] || normalizedRow[field].trim() === '') {\n throw new Error(`Missing required field: ${field}`)\n }\n }\n\n // Basic format validation (detailed validation happens in conversion)\n rawDailyLogDataSchema.parse(normalizedRow)\n\n return normalizedRow\n } catch (error) {\n // Log validation errors to console for debugging\n console.warn(`[DailyLogProcessor] Row ${rowIndex + 2} validation failed:`, error instanceof Error ? error.message : error)\n // Return null for invalid rows - they'll be counted as invalid\n return null\n }\n }\n\n /**\n * Parse a YYYY-MM-DD date string preserving the calendar date.\n * Same approach as trade-processor.ts for consistency.\n */\n private parseDatePreservingCalendarDay(dateStr: string): Date {\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/)\n if (match) {\n const [, year, month, day] = match\n // Create date at midnight local time - this preserves the calendar date\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day))\n }\n // Fall back to default parsing for other formats\n return new Date(dateStr)\n }\n\n /**\n * Convert validated CSV row to DailyLogEntry object\n */\n private convertToDailyLogEntry(rawData: Record<string, string>, blockId?: string): DailyLogEntry {\n try {\n // Parse date preserving calendar day (same as trade processor)\n const date = this.parseDatePreservingCalendarDay(rawData['Date'])\n if (isNaN(date.getTime())) {\n throw new Error(`Invalid Date: ${rawData['Date']}`)\n }\n\n // Parse numeric values with error handling\n const parseNumber = (value: string | undefined, fieldName: string, defaultValue?: number): number => {\n if (!value || value.trim() === '') {\n if (defaultValue !== undefined) return defaultValue\n throw new Error(`Missing required numeric field: ${fieldName}`)\n }\n\n // Remove currency symbols, commas, and percentage signs\n const cleaned = value.replace(/[$,%]/g, '').trim()\n const parsed = parseFloat(cleaned)\n\n if (isNaN(parsed)) {\n throw new Error(`Invalid numeric value for ${fieldName}: ${value}`)\n }\n\n return parsed\n }\n\n // Build daily log entry object\n const entry: DailyLogEntry = {\n date,\n netLiquidity: parseNumber(rawData['Net Liquidity'], 'Net Liquidity'),\n currentFunds: parseNumber(rawData['Current Funds'], 'Current Funds'),\n withdrawn: parseNumber(rawData['Withdrawn'], 'Withdrawn', 0),\n tradingFunds: parseNumber(rawData['Trading Funds'], 'Trading Funds'),\n dailyPl: parseNumber(rawData['P/L'], 'P/L'),\n dailyPlPct: parseNumber(rawData['P/L %'], 'P/L %'),\n drawdownPct: parseNumber(rawData['Drawdown %'], 'Drawdown %'),\n blockId,\n }\n\n // Keep percentage values as they are from CSV to match legacy behavior\n // Legacy Python expects percentage values (e.g., -5.55), not decimals (e.g., -0.0555)\n\n // Extract custom fields (columns not in KNOWN_DAILY_LOG_COLUMNS)\n const customFields: Record<string, number | string> = {}\n for (const [key, value] of Object.entries(rawData)) {\n if (!KNOWN_DAILY_LOG_COLUMNS.has(key) && value !== undefined && value.trim() !== '') {\n // Auto-detect type: try to parse as number\n const cleaned = value.replace(/[$,%]/g, '').trim()\n const parsed = parseFloat(cleaned)\n if (!isNaN(parsed) && isFinite(parsed)) {\n customFields[key] = parsed\n } else {\n customFields[key] = value.trim()\n }\n }\n }\n\n // Only add customFields if there are any\n if (Object.keys(customFields).length > 0) {\n entry.customFields = customFields\n }\n\n // Final validation with Zod schema\n const validatedEntry = dailyLogEntrySchema.parse(entry)\n return validatedEntry\n\n } catch (error) {\n throw new Error(`Daily log entry conversion failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n /**\n * Process CSV content directly (for testing)\n */\n async processCSVContent(content: string, blockId?: string): Promise<DailyLogProcessingResult> {\n // Create a mock File object for testing\n const blob = new Blob([content], { type: 'text/csv' })\n const file = new File([blob], 'test-daily.csv', { type: 'text/csv' })\n return this.processFile(file, blockId)\n }\n\n /**\n * Validate daily log data consistency\n */\n static validateDataConsistency(entries: DailyLogEntry[]): string[] {\n const warnings: string[] = []\n\n if (entries.length === 0) return warnings\n\n // Sort by date for chronological validation\n const sortedEntries = [...entries].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())\n\n // Check for gaps in dates (more than 7 days)\n for (let i = 1; i < sortedEntries.length; i++) {\n const prevDate = new Date(sortedEntries[i - 1].date)\n const currentDate = new Date(sortedEntries[i].date)\n const daysDiff = (currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24)\n\n if (daysDiff > 7) {\n warnings.push(`Large date gap detected: ${daysDiff.toFixed(0)} days between ${prevDate.toISOString().split('T')[0]} and ${currentDate.toISOString().split('T')[0]}`)\n }\n }\n\n // Check for negative net liquidity\n const negativeEntries = sortedEntries.filter(entry => entry.netLiquidity < 0)\n if (negativeEntries.length > 0) {\n warnings.push(`${negativeEntries.length} entries have negative net liquidity`)\n }\n\n // Check for extreme drawdowns (> 50%)\n const extremeDrawdowns = sortedEntries.filter(entry => entry.drawdownPct < -0.5)\n if (extremeDrawdowns.length > 0) {\n warnings.push(`${extremeDrawdowns.length} entries have extreme drawdowns (> 50%)`)\n }\n\n return warnings\n }\n}\n"],"mappings":"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","names":[]}