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
package/README.md CHANGED
@@ -58,11 +58,11 @@ For detailed usage examples, see [../../docs/usage.md](../../docs/usage.md).
58
58
 
59
59
  ### Claude Desktop
60
60
 
61
- | Platform | Config Location |
62
- |----------|-----------------|
63
- | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
64
- | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
65
- | Linux | `~/.config/Claude/claude_desktop_config.json` |
61
+ | Platform | Config Location |
62
+ | -------- | ----------------------------------------------------------------- |
63
+ | macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
64
+ | Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
65
+ | Linux | `~/.config/Claude/claude_desktop_config.json` |
66
66
 
67
67
  ```json
68
68
  {
@@ -135,10 +135,10 @@ See [Web Platforms Guide](../../docs/web-platforms.md) for platform-specific set
135
135
 
136
136
  ## Transport Modes
137
137
 
138
- | Mode | Flag | Use Case | Platforms |
139
- |------|------|----------|-----------|
140
- | stdio | (default) | Local CLI tools | Claude Desktop, Claude Code, Codex CLI, Gemini CLI |
141
- | HTTP | `--http` | Web platforms, remote servers | ChatGPT, Google AI Studio, Julius AI |
138
+ | Mode | Flag | Use Case | Platforms |
139
+ | ----- | --------- | ----------------------------- | -------------------------------------------------- |
140
+ | stdio | (default) | Local CLI tools | Claude Desktop, Claude Code, Codex CLI, Gemini CLI |
141
+ | HTTP | `--http` | Web platforms, remote servers | ChatGPT, Google AI Studio, Julius AI |
142
142
 
143
143
  ```bash
144
144
  # stdio mode (default)
@@ -154,21 +154,21 @@ tradeblocks-mcp --directory ./data --blocks-dir ~/backtests
154
154
 
155
155
  ### Options
156
156
 
157
- | Flag | Description | Default |
158
- |------|-------------|---------|
159
- | `--http` | Start HTTP server instead of stdio | stdio |
160
- | `--port <n>` | HTTP server port | 3100 |
161
- | `--blocks-dir <path>` | Directory containing CSV block folders | same as data directory |
162
- | `--market-db <path>` | Path to market.duckdb | `<directory>/market.duckdb` |
163
- | `--no-auth` | Disable authentication (HTTP mode) | auth enabled |
157
+ | Flag | Description | Default |
158
+ | --------------------- | -------------------------------------- | --------------------------- |
159
+ | `--http` | Start HTTP server instead of stdio | stdio |
160
+ | `--port <n>` | HTTP server port | 3100 |
161
+ | `--blocks-dir <path>` | Directory containing CSV block folders | same as data directory |
162
+ | `--market-db <path>` | Path to market.duckdb | `<directory>/market.duckdb` |
163
+ | `--no-auth` | Disable authentication (HTTP mode) | auth enabled |
164
164
 
165
165
  ### Environment Variables
166
166
 
167
- | Variable | Description |
168
- |----------|-------------|
169
- | `BLOCKS_DIRECTORY` | Default data directory if not specified as argument |
167
+ | Variable | Description |
168
+ | ------------------------ | -------------------------------------------------------------- |
169
+ | `BLOCKS_DIRECTORY` | Default data directory if not specified as argument |
170
170
  | `TRADEBLOCKS_BLOCKS_DIR` | Directory for CSV block folders (overridden by `--blocks-dir`) |
171
- | `MARKET_DB_PATH` | Path to market.duckdb (overridden by `--market-db`) |
171
+ | `MARKET_DB_PATH` | Path to market.duckdb (overridden by `--market-db`) |
172
172
 
173
173
  ### ThetaData MDDS Credentials
174
174
 
@@ -201,6 +201,7 @@ docker run -d -p 3100:3100 -v ./data:/data --env-file .env romeo345/tradeblocks-
201
201
  ```
202
202
 
203
203
  Or with docker compose, set the image in `docker-compose.yml`:
204
+
204
205
  ```yaml
205
206
  services:
206
207
  tradeblocks:
@@ -274,6 +275,7 @@ tradeblocks-mcp uninstall-skills
274
275
  ```
275
276
 
276
277
  Skills provide structured prompts for tasks like:
278
+
277
279
  - Strategy health checks
278
280
  - Walk-forward analysis interpretation
279
281
  - Portfolio addition recommendations
@@ -299,12 +301,14 @@ backtests/
299
301
  ### CSV Formats
300
302
 
301
303
  **tradelog.csv** - Trade records with these key columns:
304
+
302
305
  - Date Opened, Time Opened, Date Closed, Time Closed
303
306
  - P/L (gross profit/loss)
304
307
  - Strategy, Legs (or Symbol)
305
308
  - No. of Contracts, Premium (optional)
306
309
 
307
310
  **dailylog.csv** - Daily portfolio values:
311
+
308
312
  - Date
309
313
  - Net Liquidity (or Portfolio Value, Equity)
310
314
  - P/L, Drawdown % (optional)
@@ -314,68 +318,75 @@ backtests/
314
318
  ## Available Tools
315
319
 
316
320
  ### Core Tools
317
- | Tool | Description |
318
- |------|-------------|
319
- | `list_blocks` | List all available blocks with summary stats |
320
- | `get_block_info` | Detailed info for a specific block |
321
- | `get_statistics` | Performance metrics (Sharpe, Sortino, drawdown, etc.) |
322
- | `get_strategy_comparison` | Compare strategies within a block |
323
- | `compare_blocks` | Compare statistics across multiple blocks |
321
+
322
+ | Tool | Description |
323
+ | ------------------------- | ----------------------------------------------------- |
324
+ | `list_blocks` | List all available blocks with summary stats |
325
+ | `get_block_info` | Detailed info for a specific block |
326
+ | `get_statistics` | Performance metrics (Sharpe, Sortino, drawdown, etc.) |
327
+ | `get_strategy_comparison` | Compare strategies within a block |
328
+ | `compare_blocks` | Compare statistics across multiple blocks |
324
329
 
325
330
  ### Analysis Tools
326
- | Tool | Description |
327
- |------|-------------|
328
- | `run_walk_forward` | Walk-forward analysis with configurable windows |
329
- | `run_monte_carlo` | Monte Carlo simulation with worst-case scenarios |
331
+
332
+ | Tool | Description |
333
+ | ------------------------ | -------------------------------------------------------- |
334
+ | `run_walk_forward` | Walk-forward analysis with configurable windows |
335
+ | `run_monte_carlo` | Monte Carlo simulation with worst-case scenarios |
330
336
  | `get_correlation_matrix` | Strategy correlation matrix (Kendall, Spearman, Pearson) |
331
- | `get_tail_risk` | Tail dependence and copula-based risk analysis |
332
- | `get_position_sizing` | Kelly criterion position sizing |
337
+ | `get_tail_risk` | Tail dependence and copula-based risk analysis |
338
+ | `get_position_sizing` | Kelly criterion position sizing |
333
339
 
334
340
  ### Performance Tools
335
- | Tool | Description |
336
- |------|-------------|
337
- | `get_performance_charts` | 16 chart types (equity, drawdown, distribution) |
338
- | `get_period_returns` | Returns aggregated by time period |
339
- | `compare_backtest_to_actual` | Backtest vs live performance comparison |
341
+
342
+ | Tool | Description |
343
+ | ---------------------------- | ----------------------------------------------- |
344
+ | `get_performance_charts` | 16 chart types (equity, drawdown, distribution) |
345
+ | `get_period_returns` | Returns aggregated by time period |
346
+ | `compare_backtest_to_actual` | Backtest vs live performance comparison |
340
347
 
341
348
  ### SQL Tools
342
- | Tool | Description |
343
- |------|-------------|
344
- | `run_sql` | Execute SQL queries against trades and market data |
349
+
350
+ | Tool | Description |
351
+ | ------------------- | ---------------------------------------------------- |
352
+ | `run_sql` | Execute SQL queries against trades and market data |
345
353
  | `describe_database` | Schema discovery with table info and example queries |
346
354
 
347
355
  ### Market Data Tools
348
- | Tool | Description |
349
- |------|-------------|
350
- | `import_market_csv` | Import market data CSV with column mapping |
351
- | `import_from_database` | Import from external DuckDB databases |
352
- | `import_flat_file` | Import a local Parquet or CSV flat file for a ticker/timespan |
353
- | `fetch_bars` | Fetch daily or intraday OHLCV bars from configured provider |
354
- | `fetch_quotes` | Fetch option minute quotes from configured provider |
355
- | `fetch_chain` | Fetch option chain snapshot for an underlying on a given date |
356
- | `compute_vix_context` | Compute cross-ticker VIX regime fields for a date range |
357
- | `refresh_market_data` | Composite daily refresh: fetch bars, auto-fire VIX context, return coverage report |
358
- | `enrich_market_data` | Compute ~40 derived indicators from raw OHLCV |
359
- | `enrich_trades` | Enrich trades with market context (lookahead-free) |
360
- | `analyze_regime_performance` | Analyze P&L by market regime |
361
- | `suggest_filters` | Suggest trade filters based on market conditions |
362
- | `calculate_orb` | Opening range breakout analysis from intraday bars |
356
+
357
+ | Tool | Description |
358
+ | ---------------------------- | ---------------------------------------------------------------------------------- |
359
+ | `import_market_csv` | Import market data CSV with column mapping |
360
+ | `import_from_database` | Import from external DuckDB databases |
361
+ | `import_flat_file` | Import a local Parquet or CSV flat file for a ticker/timespan |
362
+ | `fetch_bars` | Fetch daily or intraday OHLCV bars from configured provider |
363
+ | `fetch_quotes` | Fetch option minute quotes from configured provider |
364
+ | `fetch_chain` | Fetch option chain snapshot for an underlying on a given date |
365
+ | `compute_vix_context` | Compute cross-ticker VIX regime fields for a date range |
366
+ | `refresh_market_data` | Composite daily refresh: fetch bars, auto-fire VIX context, return coverage report |
367
+ | `enrich_market_data` | Compute ~40 derived indicators from raw OHLCV |
368
+ | `enrich_trades` | Enrich trades with market context (lookahead-free) |
369
+ | `analyze_regime_performance` | Analyze P&L by market regime |
370
+ | `suggest_filters` | Suggest trade filters based on market conditions |
371
+ | `calculate_orb` | Opening range breakout analysis from intraday bars |
363
372
 
364
373
  ### Strategy Profile Tools
365
- | Tool | Description |
366
- |------|-------------|
367
- | `profile_strategy` | Create or update a strategy profile with structured metadata |
368
- | `get_strategy_profile` | Retrieve a stored strategy profile |
369
- | `list_profiles` | List all strategy profiles (optionally filtered by block) |
370
- | `delete_profile` | Delete a strategy profile |
371
- | `analyze_structure_fit` | Analyze strategy performance by regime/condition dimensions |
372
- | `validate_entry_filters` | Test each entry filter's contribution to edge |
373
- | `portfolio_structure_map` | Regime x structure coverage matrix across strategies |
374
+
375
+ | Tool | Description |
376
+ | ------------------------- | ------------------------------------------------------------ |
377
+ | `profile_strategy` | Create or update a strategy profile with structured metadata |
378
+ | `get_strategy_profile` | Retrieve a stored strategy profile |
379
+ | `list_profiles` | List all strategy profiles (optionally filtered by block) |
380
+ | `delete_profile` | Delete a strategy profile |
381
+ | `analyze_structure_fit` | Analyze strategy performance by regime/condition dimensions |
382
+ | `validate_entry_filters` | Test each entry filter's contribution to edge |
383
+ | `portfolio_structure_map` | Regime x structure coverage matrix across strategies |
374
384
 
375
385
  ### Import Tools
376
- | Tool | Description |
377
- |------|-------------|
378
- | `import_csv` | Import a CSV file as a new block *(CLI only - not available in Claude Desktop)* |
386
+
387
+ | Tool | Description |
388
+ | ------------ | ------------------------------------------------------------------------------- |
389
+ | `import_csv` | Import a CSV file as a new block _(CLI only - not available in Claude Desktop)_ |
379
390
 
380
391
  ## Development
381
392
 
@@ -398,11 +409,13 @@ npm run mcpb:pack
398
409
  For market context (VIX regimes, intraday timing, gap analysis), import market data using MCP tools:
399
410
 
400
411
  **From a data provider (Massive.com default, or ThetaData):**
412
+
401
413
  1. **Fetch bars** via `fetch_bars { tickers, timespan, from, to }` — writes directly to Parquet
402
414
  2. **Fetch VIX context** via `fetch_bars` for VIX/VIX9D/VIX3M then `compute_vix_context`
403
415
  3. **Or use** `refresh_market_data` for a combined daily refresh in one call
404
416
 
405
417
  **From TradingView CSV exports:**
418
+
406
419
  1. **Export** from TradingView (any chart: SPX daily, VIX daily, SPX 5-min, etc.)
407
420
  2. **Import** via `import_market_csv` with a column mapping or `import_flat_file` for Parquet
408
421
  3. **Enrich** via `enrich_market_data` to compute ~40 derived indicators
@@ -410,6 +423,7 @@ For market context (VIX regimes, intraday timing, gap analysis), import market d
410
423
  No Pine Scripts needed — TradingView exports raw OHLCV natively.
411
424
 
412
425
  Market data lives in a separate `market.duckdb` (configurable via `MARKET_DB_PATH` or `--market-db`). Canonical v3.0 datasets:
426
+
413
427
  - `market.spot` — Raw per-minute OHLCV bars, ticker-first layout (keyed by `ticker, date, time`)
414
428
  - `market.spot_daily` — RTH-aggregated daily OHLCV view derived from `market.spot` (keyed by `ticker, date`)
415
429
  - `market.enriched` — Per-ticker computed enrichment indicators and calendar fields; OHLCV is NOT stored here (join `market.spot_daily` for OHLCV — keyed by `ticker, date`)
@@ -254,12 +254,7 @@ async function getTradesByDateRange(blockId, startDate, endDate) {
254
254
  return withReadTransaction(STORES.TRADES, async (transaction) => {
255
255
  const store = transaction.objectStore(STORES.TRADES);
256
256
  const index = store.index("composite_block_date");
257
- const range = IDBKeyRange.bound(
258
- [blockId, startDate],
259
- [blockId, endDate],
260
- false,
261
- false
262
- );
257
+ const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);
263
258
  const result = await promisifyRequest(index.getAll(range));
264
259
  return result;
265
260
  });
@@ -356,9 +351,7 @@ async function getTradeStatistics(blockId) {
356
351
  const dates = trades.map((trade) => new Date(trade.dateOpened));
357
352
  const start = new Date(Math.min(...dates.map((d) => d.getTime())));
358
353
  const end = new Date(Math.max(...dates.map((d) => d.getTime())));
359
- const strategies = Array.from(
360
- new Set(trades.map((trade) => trade.strategy))
361
- ).sort();
354
+ const strategies = Array.from(new Set(trades.map((trade) => trade.strategy))).sort();
362
355
  return {
363
356
  totalTrades,
364
357
  totalPl,
@@ -517,11 +510,9 @@ async function initializeDatabase() {
517
510
  unique: false
518
511
  });
519
512
  tradesStore.createIndex("pl", "pl", { unique: false });
520
- tradesStore.createIndex(
521
- "composite_block_date",
522
- ["blockId", "dateOpened"],
523
- { unique: false }
524
- );
513
+ tradesStore.createIndex("composite_block_date", ["blockId", "dateOpened"], {
514
+ unique: false
515
+ });
525
516
  }
526
517
  if (!db.objectStoreNames.contains(STORES.DAILY_LOGS)) {
527
518
  const dailyLogsStore = db.createObjectStore(STORES.DAILY_LOGS, {
@@ -533,11 +524,7 @@ async function initializeDatabase() {
533
524
  dailyLogsStore.createIndex(INDEXES.DAILY_LOGS_BY_DATE, "date", {
534
525
  unique: false
535
526
  });
536
- dailyLogsStore.createIndex(
537
- "composite_block_date",
538
- ["blockId", "date"],
539
- { unique: false }
540
- );
527
+ dailyLogsStore.createIndex("composite_block_date", ["blockId", "date"], { unique: false });
541
528
  }
542
529
  if (!db.objectStoreNames.contains(STORES.REPORTING_LOGS)) {
543
530
  const reportingStore = db.createObjectStore(STORES.REPORTING_LOGS, {
@@ -546,26 +533,18 @@ async function initializeDatabase() {
546
533
  reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_BLOCK, "blockId", {
547
534
  unique: false
548
535
  });
549
- reportingStore.createIndex(
550
- INDEXES.REPORTING_LOGS_BY_STRATEGY,
551
- "strategy",
552
- { unique: false }
553
- );
554
- reportingStore.createIndex(
555
- "composite_block_date",
556
- ["blockId", "dateOpened"],
557
- { unique: false }
558
- );
536
+ reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_STRATEGY, "strategy", {
537
+ unique: false
538
+ });
539
+ reportingStore.createIndex("composite_block_date", ["blockId", "dateOpened"], {
540
+ unique: false
541
+ });
559
542
  }
560
543
  if (!db.objectStoreNames.contains(STORES.CALCULATIONS)) {
561
544
  const calculationsStore = db.createObjectStore(STORES.CALCULATIONS, {
562
545
  keyPath: "id"
563
546
  });
564
- calculationsStore.createIndex(
565
- INDEXES.CALCULATIONS_BY_BLOCK,
566
- "blockId",
567
- { unique: false }
568
- );
547
+ calculationsStore.createIndex(INDEXES.CALCULATIONS_BY_BLOCK, "blockId", { unique: false });
569
548
  calculationsStore.createIndex("calculationType", "calculationType", {
570
549
  unique: false
571
550
  });
@@ -593,16 +572,12 @@ async function initializeDatabase() {
593
572
  const staticDatasetRowsStore = db.createObjectStore(STORES.STATIC_DATASET_ROWS, {
594
573
  autoIncrement: true
595
574
  });
596
- staticDatasetRowsStore.createIndex(
597
- INDEXES.STATIC_DATASET_ROWS_BY_DATASET,
598
- "datasetId",
599
- { unique: false }
600
- );
601
- staticDatasetRowsStore.createIndex(
602
- INDEXES.STATIC_DATASET_ROWS_BY_TIMESTAMP,
603
- "timestamp",
604
- { unique: false }
605
- );
575
+ staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_DATASET, "datasetId", {
576
+ unique: false
577
+ });
578
+ staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_TIMESTAMP, "timestamp", {
579
+ unique: false
580
+ });
606
581
  staticDatasetRowsStore.createIndex(
607
582
  "composite_dataset_timestamp",
608
583
  ["datasetId", "timestamp"],
@@ -669,12 +644,7 @@ async function getDailyLogsByDateRange(blockId, startDate, endDate) {
669
644
  return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {
670
645
  const store = transaction.objectStore(STORES.DAILY_LOGS);
671
646
  const index = store.index("composite_block_date");
672
- const range = IDBKeyRange.bound(
673
- [blockId, startDate],
674
- [blockId, endDate],
675
- false,
676
- false
677
- );
647
+ const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);
678
648
  const result = await promisifyRequest(index.getAll(range));
679
649
  return result;
680
650
  });
@@ -750,7 +720,9 @@ async function getDailyLogStatistics(blockId) {
750
720
  const dates = entries.map((entry) => new Date(entry.date));
751
721
  const start = new Date(Math.min(...dates.map((d) => d.getTime())));
752
722
  const end = new Date(Math.max(...dates.map((d) => d.getTime())));
753
- const sortedEntries = entries.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
723
+ const sortedEntries = entries.sort(
724
+ (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
725
+ );
754
726
  const finalPortfolioValue = sortedEntries[0]?.netLiquidity || 0;
755
727
  const maxDrawdown = Math.min(...entries.map((entry) => entry.drawdownPct));
756
728
  const totalPl = entries.reduce((sum, entry) => sum + entry.dailyPl, 0);
@@ -864,4 +836,4 @@ export {
864
836
  getTradesPage,
865
837
  exportTradesToCSV
866
838
  };
867
- //# sourceMappingURL=chunk-LDKTV7GW.js.map
839
+ //# sourceMappingURL=chunk-27S67XW3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../lib/utils/combine-leg-groups.ts","../../lib/db/combined-trades-cache.ts","../../lib/db/performance-snapshot-cache.ts","../../lib/db/trades-store.ts","../../lib/db/index.ts","../../lib/db/daily-logs-store.ts"],"sourcesContent":["/**\n * Leg Group Combining Utility\n *\n * For MEIC (Multiple Entry Iron Condor) and similar strategies where the backtester\n * creates separate trade records for each leg group (e.g., calls and puts) that were\n * opened simultaneously but may have different exit conditions/times.\n *\n * This utility groups trades by entry timestamp and combines them into single trade records.\n */\n\nimport type { Trade } from \"../models/trade.ts\";\nimport type { ReportingTrade } from \"../models/reporting-trade.ts\";\nimport { yieldToMain, checkCancelled } from \"./async-helpers.ts\";\n\n/**\n * Key used to group trades that were opened at the same time\n */\nexport interface TradeGroupKey {\n dateOpened: string; // ISO date string\n timeOpened: string;\n strategy: string;\n}\n\n/**\n * Result of combining multiple leg groups into a single trade\n */\nexport interface CombinedTrade extends Trade {\n originalTradeCount: number; // Number of trades that were combined\n combinedLegs: string[]; // Array of leg strings from each trade\n}\n\n/**\n * Result of combining multiple ReportingTrade leg groups into a single trade\n */\nexport interface CombinedReportingTrade extends ReportingTrade {\n originalTradeCount: number; // Number of trades that were combined\n combinedLegs: string[]; // Array of leg strings from each trade\n}\n\n/**\n * Type guard to check if a Trade is a CombinedTrade\n */\nexport function isCombinedTrade(trade: Trade): trade is CombinedTrade {\n return \"originalTradeCount\" in trade && \"combinedLegs\" in trade;\n}\n\n/**\n * Type guard to check if a ReportingTrade is a CombinedReportingTrade\n */\nexport function isCombinedReportingTrade(trade: ReportingTrade): trade is CombinedReportingTrade {\n return \"originalTradeCount\" in trade && \"combinedLegs\" in trade;\n}\n\n/**\n * Generate a unique key for grouping trades by entry timestamp\n */\nfunction generateGroupKey(trade: Trade): string {\n const dateStr = trade.dateOpened.toISOString().split(\"T\")[0];\n return `${dateStr}|${trade.timeOpened}|${trade.strategy}`;\n}\n\n/**\n * Parse a group key back into its components\n * (Not currently used but kept for future API compatibility)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction parseGroupKey(key: string): TradeGroupKey {\n const [dateOpened, timeOpened, strategy] = key.split(\"|\");\n return { dateOpened, timeOpened, strategy };\n}\n\n/**\n * Group trades by their entry timestamp (date + time + strategy)\n * Returns a map where the key is the group identifier and value is array of trades\n */\nexport function groupTradesByEntry(trades: Trade[]): Map<string, Trade[]> {\n const groups = new Map<string, Trade[]>();\n\n for (const trade of trades) {\n const key = generateGroupKey(trade);\n const group = groups.get(key) || [];\n group.push(trade);\n groups.set(key, group);\n }\n\n return groups;\n}\n\n/**\n * Combine a group of trades that were opened at the same time into a single trade record\n *\n * Rules for combining:\n * - Opening fields: Use first trade's values (they should be identical)\n * - Closing fields: Use the last closing time among all trades\n * - Premium: Sum of all premiums\n * - P/L: Sum of all P/Ls\n * - Commissions: Sum of all commissions\n * - Margin: Use the maximum margin requirement\n * - Contracts: Sum of all contracts\n * - Legs: Concatenate all leg descriptions\n * - Closing price: Use weighted average based on premiums\n * - Funds at close: Use final funds from last closed trade\n */\nexport function combineLegGroup(trades: Trade[]): CombinedTrade {\n if (trades.length === 0) {\n throw new Error(\"Cannot combine empty trade group\");\n }\n\n // Sort trades by closing time (or use original order if not closed)\n const sortedTrades = [...trades].sort((a, b) => {\n if (!a.dateClosed && !b.dateClosed) return 0;\n if (!a.dateClosed) return 1;\n if (!b.dateClosed) return -1;\n\n const dateCompare = a.dateClosed.getTime() - b.dateClosed.getTime();\n if (dateCompare !== 0) return dateCompare;\n\n // Secondary sort by time if dates are equal\n const timeA = a.timeClosed || \"00:00:00\";\n const timeB = b.timeClosed || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n // Use first trade as template (opening info should be identical)\n const firstTrade = sortedTrades[0];\n const lastTrade = sortedTrades[sortedTrades.length - 1];\n\n // Aggregate numeric values\n const totalPremium = trades.reduce((sum, t) => sum + t.premium, 0);\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0);\n // Use the contract size of the first leg to represent the \"Strategy Unit Size\"\n // e.g. A 10-lot Iron Condor has 4 legs of 10 contracts.\n // We want the combined trade to say \"10 contracts\" (10 ICs), not 40.\n const totalContracts = firstTrade.numContracts;\n const totalOpeningCommissions = trades.reduce((sum, t) => sum + t.openingCommissionsFees, 0);\n const totalClosingCommissions = trades.reduce((sum, t) => sum + t.closingCommissionsFees, 0);\n\n // For margin:\n // - Debit trades (totalPremium < 0): Sum margin (e.g. Straddle = Call + Put cost)\n // - Credit trades (totalPremium >= 0): Max margin (e.g. Iron Condor = Max(Call side, Put side))\n const maxMargin =\n totalPremium < 0\n ? trades.reduce((sum, t) => sum + t.marginReq, 0)\n : Math.max(...trades.map((t) => t.marginReq));\n\n // Calculate weighted average closing price\n let weightedClosingPrice: number | undefined;\n if (trades.every((t) => t.closingPrice !== undefined)) {\n const totalPremiumForClosedTrades = trades\n .filter((t) => t.closingPrice !== undefined)\n .reduce((sum, t) => sum + Math.abs(t.premium), 0);\n\n if (totalPremiumForClosedTrades > 0) {\n weightedClosingPrice =\n trades\n .filter((t) => t.closingPrice !== undefined)\n .reduce((sum, t) => sum + t.closingPrice! * Math.abs(t.premium), 0) /\n totalPremiumForClosedTrades;\n }\n }\n\n // Calculate total closing cost if all trades have it recorded\n let avgClosingCost: number | undefined;\n const tradesWithClosingCost = trades.filter((t) => t.avgClosingCost !== undefined);\n if (tradesWithClosingCost.length === trades.length) {\n avgClosingCost = tradesWithClosingCost.reduce((sum, t) => sum + t.avgClosingCost!, 0);\n }\n\n // Combine leg descriptions\n const combinedLegs = trades.map((t) => t.legs);\n const combinedLegsString = combinedLegs.join(\" | \");\n\n // Use last trade's closing information (latest exit)\n const dateClosed = lastTrade.dateClosed;\n const timeClosed = lastTrade.timeClosed;\n const reasonForClose = lastTrade.reasonForClose;\n const fundsAtClose = lastTrade.fundsAtClose;\n const closingShortLongRatio = lastTrade.closingShortLongRatio;\n const closingVix = lastTrade.closingVix;\n\n // Calculate combined opening short/long ratio (weighted by premium)\n const totalAbsPremium = trades.reduce((sum, t) => sum + Math.abs(t.premium), 0);\n const weightedOpeningShortLongRatio =\n totalAbsPremium > 0\n ? trades.reduce((sum, t) => sum + t.openingShortLongRatio * Math.abs(t.premium), 0) /\n totalAbsPremium\n : trades[0].openingShortLongRatio;\n\n // For optional fields, use first trade's value or undefined\n const gap = firstTrade.gap;\n const movement = firstTrade.movement;\n\n // Max profit/loss handling:\n // - For single trades (originalTradeCount === 1): preserve original percentage values from CSV\n // - For combined trades (originalTradeCount > 1): derive dollar amounts from margin\n let maxProfit: number | undefined;\n let maxLoss: number | undefined;\n\n if (trades.length === 1) {\n // Single trade: preserve original values (percentages from CSV)\n maxProfit = firstTrade.maxProfit;\n maxLoss = firstTrade.maxLoss;\n // For debit trades without explicit maxLoss, use premium paid as max loss\n if (maxLoss === undefined && firstTrade.premium < 0) {\n maxLoss = firstTrade.premium;\n }\n } else {\n // Combined trades: sum maxProfit, use margin for maxLoss\n if (trades.every((t) => t.maxProfit !== undefined)) {\n maxProfit = trades.reduce((sum, t) => sum + t.maxProfit!, 0);\n }\n\n // Use margin requirement as ground truth for worst-case loss\n if (maxMargin && Number.isFinite(maxMargin) && maxMargin > 0) {\n maxLoss = -maxMargin;\n } else if (trades.every((t) => t.maxLoss !== undefined)) {\n maxLoss = trades.reduce((sum, t) => sum + t.maxLoss!, 0);\n } else if (totalPremium < 0) {\n // Fallback: For debit trades, the max loss is at least the premium paid\n maxLoss = totalPremium;\n }\n }\n\n const combined: CombinedTrade = {\n // Opening information (from first trade)\n dateOpened: firstTrade.dateOpened,\n timeOpened: firstTrade.timeOpened,\n openingPrice: firstTrade.openingPrice,\n legs: combinedLegsString,\n premium: totalPremium,\n premiumPrecision: firstTrade.premiumPrecision,\n openingVix: firstTrade.openingVix,\n\n // Closing information (from last closed trade)\n closingPrice: weightedClosingPrice,\n dateClosed,\n timeClosed,\n avgClosingCost,\n reasonForClose,\n closingVix,\n\n // Aggregated values\n pl: totalPL,\n numContracts: totalContracts,\n fundsAtClose,\n marginReq: maxMargin,\n openingCommissionsFees: totalOpeningCommissions,\n closingCommissionsFees: totalClosingCommissions,\n\n // Strategy and ratios\n strategy: firstTrade.strategy,\n openingShortLongRatio: weightedOpeningShortLongRatio,\n closingShortLongRatio,\n\n // Optional market data\n gap,\n movement,\n maxProfit,\n maxLoss,\n\n // Combined trade metadata\n originalTradeCount: trades.length,\n combinedLegs,\n };\n\n return combined;\n}\n\nexport interface CombineLegGroupsProgress {\n step: string;\n percent: number;\n}\n\nexport interface CombineLegGroupsOptions {\n onProgress?: (progress: CombineLegGroupsProgress) => void;\n signal?: AbortSignal;\n}\n\n/**\n * Process all trades and combine leg groups that share the same entry timestamp\n *\n * @param trades - Array of trades to process\n * @returns Array of trades with leg groups combined (single trades are preserved as-is)\n */\nexport function combineAllLegGroups(trades: Trade[]): CombinedTrade[] {\n const groups = groupTradesByEntry(trades);\n const combinedTrades: CombinedTrade[] = [];\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineLegGroup(groupTrades));\n }\n\n // Sort by date/time to maintain chronological order\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n\n return combinedTrades;\n}\n\n/**\n * Async version of combineAllLegGroups with progress reporting and cancellation support\n * Use this for large datasets to keep UI responsive\n *\n * @param trades - Array of trades to process\n * @param options - Progress callback and abort signal\n * @returns Array of trades with leg groups combined\n */\nexport async function combineAllLegGroupsAsync(\n trades: Trade[],\n options?: CombineLegGroupsOptions,\n): Promise<CombinedTrade[]> {\n const { onProgress, signal } = options ?? {};\n\n checkCancelled(signal);\n onProgress?.({ step: \"Grouping trades by entry\", percent: 0 });\n await yieldToMain();\n\n const groups = groupTradesByEntry(trades);\n const combinedTrades: CombinedTrade[] = [];\n const totalGroups = groups.size;\n let processedGroups = 0;\n\n checkCancelled(signal);\n onProgress?.({ step: \"Combining leg groups\", percent: 10 });\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineLegGroup(groupTrades));\n processedGroups++;\n\n // Yield every 100 groups to keep UI responsive\n if (processedGroups % 100 === 0) {\n checkCancelled(signal);\n const percent = 10 + Math.floor((processedGroups / totalGroups) * 70);\n onProgress?.({ step: `Combining leg groups (${processedGroups}/${totalGroups})`, percent });\n await yieldToMain();\n }\n }\n\n checkCancelled(signal);\n onProgress?.({ step: \"Sorting combined trades\", percent: 85 });\n await yieldToMain();\n\n // Sort by date/time to maintain chronological order\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n\n onProgress?.({ step: \"Complete\", percent: 100 });\n\n return combinedTrades;\n}\n\n/**\n * Identify which trades would be affected by combining leg groups\n * Useful for showing users what will change before they enable the feature\n *\n * @returns Object with statistics about grouping\n */\nexport function analyzeLegGroups(trades: Trade[]): {\n totalTrades: number;\n uniqueEntries: number;\n groupedEntries: number;\n maxGroupSize: number;\n avgGroupSize: number;\n groupSizeDistribution: Record<number, number>; // size -> count\n} {\n const groups = groupTradesByEntry(trades);\n\n const groupSizes = Array.from(groups.values()).map((g) => g.length);\n const groupedEntries = groupSizes.filter((size) => size > 1).length;\n\n const distribution: Record<number, number> = {};\n for (const size of groupSizes) {\n distribution[size] = (distribution[size] || 0) + 1;\n }\n\n return {\n totalTrades: trades.length,\n uniqueEntries: groups.size,\n groupedEntries,\n maxGroupSize: groupSizes.length > 0 ? Math.max(...groupSizes) : 0,\n avgGroupSize:\n groupSizes.length > 0 ? groupSizes.reduce((a, b) => a + b, 0) / groupSizes.length : 0,\n groupSizeDistribution: distribution,\n };\n}\n\n// =============================================================================\n// ReportingTrade Combining Functions\n// =============================================================================\n\n/**\n * Generate a unique key for grouping ReportingTrades by entry timestamp\n */\nfunction generateReportingGroupKey(trade: ReportingTrade): string {\n const dateStr = trade.dateOpened.toISOString().split(\"T\")[0];\n const timeStr = trade.timeOpened ?? \"00:00:00\";\n return `${dateStr}|${timeStr}|${trade.strategy}`;\n}\n\n/**\n * Group ReportingTrades by their entry timestamp (date + time + strategy)\n * Returns a map where the key is the group identifier and value is array of trades\n */\nexport function groupReportingTradesByEntry(\n trades: ReportingTrade[],\n): Map<string, ReportingTrade[]> {\n const groups = new Map<string, ReportingTrade[]>();\n\n for (const trade of trades) {\n const key = generateReportingGroupKey(trade);\n const group = groups.get(key) || [];\n group.push(trade);\n groups.set(key, group);\n }\n\n return groups;\n}\n\n/**\n * Combine a group of ReportingTrades that were opened at the same time into a single trade record\n *\n * Rules for combining (simpler than Trade - fewer fields):\n * - Opening fields: Use first trade's values (should be identical)\n * - Closing fields: Use the last closing time among all trades\n * - Premium: Sum of all initial premiums\n * - P/L: Sum of all P/Ls\n * - Contracts: Use first trade's contract count (strategy unit size)\n * - Legs: Concatenate all leg descriptions\n */\nexport function combineReportingLegGroup(trades: ReportingTrade[]): CombinedReportingTrade {\n if (trades.length === 0) {\n throw new Error(\"Cannot combine empty reporting trade group\");\n }\n\n // Sort trades by closing time\n const sortedTrades = [...trades].sort((a, b) => {\n if (!a.dateClosed && !b.dateClosed) return 0;\n if (!a.dateClosed) return 1;\n if (!b.dateClosed) return -1;\n\n const dateCompare = a.dateClosed.getTime() - b.dateClosed.getTime();\n if (dateCompare !== 0) return dateCompare;\n\n const timeA = a.timeClosed || \"00:00:00\";\n const timeB = b.timeClosed || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n const firstTrade = sortedTrades[0];\n const lastTrade = sortedTrades[sortedTrades.length - 1];\n\n // Aggregate numeric values\n const totalPremium = trades.reduce((sum, t) => sum + t.initialPremium, 0);\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0);\n const totalContracts = firstTrade.numContracts; // Strategy unit size\n\n // Combine leg descriptions\n const combinedLegs = trades.map((t) => t.legs);\n const combinedLegsString = combinedLegs.join(\" | \");\n\n // Calculate total closing cost if all trades have it\n let avgClosingCost: number | undefined;\n const tradesWithClosingCost = trades.filter((t) => t.avgClosingCost !== undefined);\n if (tradesWithClosingCost.length === trades.length) {\n avgClosingCost = tradesWithClosingCost.reduce((sum, t) => sum + t.avgClosingCost!, 0);\n }\n\n // Weighted average closing price\n let closingPrice: number | undefined;\n if (trades.every((t) => t.closingPrice !== undefined)) {\n const totalPremiumAbs = trades.reduce((sum, t) => sum + Math.abs(t.initialPremium), 0);\n if (totalPremiumAbs > 0) {\n closingPrice =\n trades.reduce((sum, t) => sum + t.closingPrice! * Math.abs(t.initialPremium), 0) /\n totalPremiumAbs;\n }\n }\n\n return {\n strategy: firstTrade.strategy,\n dateOpened: firstTrade.dateOpened,\n timeOpened: firstTrade.timeOpened,\n openingPrice: firstTrade.openingPrice,\n legs: combinedLegsString,\n initialPremium: totalPremium,\n numContracts: totalContracts,\n pl: totalPL,\n closingPrice,\n dateClosed: lastTrade.dateClosed,\n timeClosed: lastTrade.timeClosed,\n avgClosingCost,\n reasonForClose: lastTrade.reasonForClose,\n originalTradeCount: trades.length,\n combinedLegs,\n };\n}\n\n/**\n * Process all ReportingTrades and combine leg groups that share the same entry timestamp\n *\n * @param trades - Array of ReportingTrades to process\n * @returns Array of trades with leg groups combined\n */\nexport function combineAllReportingLegGroups(trades: ReportingTrade[]): CombinedReportingTrade[] {\n const groups = groupReportingTradesByEntry(trades);\n const combinedTrades: CombinedReportingTrade[] = [];\n\n for (const [, groupTrades] of groups) {\n combinedTrades.push(combineReportingLegGroup(groupTrades));\n }\n\n // Sort chronologically\n combinedTrades.sort((a, b) => {\n const dateCompare = a.dateOpened.getTime() - b.dateOpened.getTime();\n if (dateCompare !== 0) return dateCompare;\n const timeA = a.timeOpened || \"00:00:00\";\n const timeB = b.timeOpened || \"00:00:00\";\n return timeA.localeCompare(timeB);\n });\n\n return combinedTrades;\n}\n","/**\n * Combined Trades Cache\n *\n * Caches pre-calculated combined leg group trades in IndexedDB\n * to avoid expensive recalculation on every page load.\n */\n\nimport type { CombinedTrade } from \"../utils/combine-leg-groups.ts\";\nimport {\n INDEXES,\n promisifyRequest,\n STORES,\n withReadTransaction,\n withWriteTransaction,\n} from \"./index.ts\";\n\n/**\n * Cache entry for combined trades\n */\ninterface CombinedTradesCache {\n id: string; // Format: `combined_trades_${blockId}`\n blockId: string;\n calculationType: \"combined_trades\";\n trades: CombinedTrade[];\n tradeCount: number;\n calculatedAt: Date;\n}\n\n/**\n * Generate the cache ID for a block\n */\nfunction getCacheId(blockId: string): string {\n return `combined_trades_${blockId}`;\n}\n\n/**\n * Store pre-calculated combined trades for a block\n */\nexport async function storeCombinedTradesCache(\n blockId: string,\n combinedTrades: CombinedTrade[],\n): Promise<void> {\n const cacheEntry: CombinedTradesCache = {\n id: getCacheId(blockId),\n blockId,\n calculationType: \"combined_trades\",\n trades: combinedTrades,\n tradeCount: combinedTrades.length,\n calculatedAt: new Date(),\n };\n\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n await promisifyRequest(store.put(cacheEntry));\n });\n}\n\n/**\n * Get cached combined trades for a block\n * Returns null if cache doesn't exist\n */\nexport async function getCombinedTradesCache(blockId: string): Promise<CombinedTrade[] | null> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n\n if (!result || result.calculationType !== \"combined_trades\") {\n return null;\n }\n\n const cache = result as CombinedTradesCache;\n\n // Restore Date objects that were serialized\n return cache.trades.map((trade) => ({\n ...trade,\n dateOpened: new Date(trade.dateOpened),\n dateClosed: trade.dateClosed ? new Date(trade.dateClosed) : undefined,\n }));\n });\n}\n\n/**\n * Delete cached combined trades for a block\n */\nexport async function deleteCombinedTradesCache(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n\n // Check if entry exists before trying to delete\n const existing = await promisifyRequest(store.get(cacheId));\n if (existing) {\n await promisifyRequest(store.delete(cacheId));\n }\n });\n}\n\n/**\n * Check if combined trades cache exists for a block\n */\nexport async function hasCombinedTradesCache(blockId: string): Promise<boolean> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n return result?.calculationType === \"combined_trades\";\n });\n}\n\n/**\n * Invalidate all calculation caches for a block\n * (including combined trades cache)\n */\nexport async function invalidateBlockCaches(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const index = store.index(INDEXES.CALCULATIONS_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n}\n","/**\n * Performance Snapshot Cache\n *\n * Caches pre-calculated performance snapshots in IndexedDB\n * to avoid expensive recalculation on every page load.\n */\n\nimport type { PortfolioStats } from \"../models/portfolio-stats.ts\";\nimport type { Trade } from \"../models/trade.ts\";\nimport type { DailyLogEntry } from \"../models/daily-log.ts\";\nimport type { SnapshotChartData } from \"../services/performance-snapshot.ts\";\nimport { promisifyRequest, STORES, withReadTransaction, withWriteTransaction } from \"./index.ts\";\n\n/**\n * Cache entry for performance snapshot\n */\ninterface PerformanceSnapshotCache {\n id: string; // Format: `performance_snapshot_${blockId}`\n blockId: string;\n calculationType: \"performance_snapshot\";\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n calculatedAt: Date;\n}\n\n/**\n * Public interface for cached snapshot data\n */\nexport interface CachedPerformanceSnapshot {\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n calculatedAt: Date;\n}\n\n/**\n * Generate the cache ID for a block\n */\nfunction getCacheId(blockId: string): string {\n return `performance_snapshot_${blockId}`;\n}\n\n/**\n * Store pre-calculated performance snapshot for a block\n */\nexport async function storePerformanceSnapshotCache(\n blockId: string,\n snapshot: {\n portfolioStats: PortfolioStats;\n chartData: SnapshotChartData;\n filteredTrades: Trade[];\n filteredDailyLogs: DailyLogEntry[];\n },\n): Promise<void> {\n const cacheEntry: PerformanceSnapshotCache = {\n id: getCacheId(blockId),\n blockId,\n calculationType: \"performance_snapshot\",\n portfolioStats: snapshot.portfolioStats,\n chartData: snapshot.chartData,\n filteredTrades: snapshot.filteredTrades,\n filteredDailyLogs: snapshot.filteredDailyLogs,\n calculatedAt: new Date(),\n };\n\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n await promisifyRequest(store.put(cacheEntry));\n });\n}\n\n/**\n * Restore Date objects from serialized cache data\n */\nfunction restoreDates<T extends { dateOpened?: Date | string; dateClosed?: Date | string | null }>(\n items: T[],\n): T[] {\n return items.map((item) => ({\n ...item,\n dateOpened: item.dateOpened ? new Date(item.dateOpened) : undefined,\n dateClosed: item.dateClosed ? new Date(item.dateClosed) : undefined,\n }));\n}\n\n/**\n * Restore Date objects in daily logs\n */\nfunction restoreDailyLogDates(logs: DailyLogEntry[]): DailyLogEntry[] {\n return logs.map((log) => ({\n ...log,\n date: new Date(log.date),\n }));\n}\n\n/**\n * Restore Date objects in chart data\n */\nfunction restoreChartDataDates(chartData: SnapshotChartData): SnapshotChartData {\n return {\n ...chartData,\n // Most chart data uses ISO string dates, which is fine\n // Only restore where Date objects are expected\n };\n}\n\n/**\n * Get cached performance snapshot for a block\n * Returns null if cache doesn't exist\n */\nexport async function getPerformanceSnapshotCache(\n blockId: string,\n): Promise<CachedPerformanceSnapshot | null> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n\n if (!result || result.calculationType !== \"performance_snapshot\") {\n return null;\n }\n\n const cache = result as PerformanceSnapshotCache;\n\n // Restore Date objects that were serialized\n return {\n portfolioStats: cache.portfolioStats,\n chartData: restoreChartDataDates(cache.chartData),\n filteredTrades: restoreDates(cache.filteredTrades) as Trade[],\n filteredDailyLogs: restoreDailyLogDates(cache.filteredDailyLogs),\n calculatedAt: new Date(cache.calculatedAt),\n };\n });\n}\n\n/**\n * Delete cached performance snapshot for a block\n */\nexport async function deletePerformanceSnapshotCache(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n\n // Check if entry exists before trying to delete\n const existing = await promisifyRequest(store.get(cacheId));\n if (existing) {\n await promisifyRequest(store.delete(cacheId));\n }\n });\n}\n\n/**\n * Check if performance snapshot cache exists for a block\n */\nexport async function hasPerformanceSnapshotCache(blockId: string): Promise<boolean> {\n return withReadTransaction(STORES.CALCULATIONS, async (transaction) => {\n const store = transaction.objectStore(STORES.CALCULATIONS);\n const cacheId = getCacheId(blockId);\n const result = await promisifyRequest(store.get(cacheId));\n return result?.calculationType === \"performance_snapshot\";\n });\n}\n","/**\n * Trades Store - CRUD operations for trade data\n */\n\nimport type { Trade } from \"../models/trade.ts\";\nimport { combineAllLegGroups, type CombinedTrade } from \"../utils/combine-leg-groups.ts\";\nimport {\n INDEXES,\n promisifyRequest,\n STORES,\n withReadTransaction,\n withWriteTransaction,\n} from \"./index.ts\";\nimport {\n deleteCombinedTradesCache,\n getCombinedTradesCache,\n storeCombinedTradesCache,\n} from \"./combined-trades-cache.ts\";\nimport { deletePerformanceSnapshotCache } from \"./performance-snapshot-cache.ts\";\n\n// Track in-flight combined cache writes to avoid redundant work when multiple callers miss the cache simultaneously.\nconst combinedCacheInflight = new Map<string, Promise<void>>();\n\n/**\n * Extended trade with block association\n */\nexport interface StoredTrade extends Trade {\n blockId: string;\n id?: number; // Auto-generated by IndexedDB\n}\n\n/**\n * Add trades for a block (batch operation)\n */\nexport async function addTrades(blockId: string, trades: Trade[]): Promise<void> {\n if (trades.length === 0) return;\n\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n\n // Use Promise.all for better performance with large datasets\n const promises = trades.map((trade) => {\n const storedTrade: StoredTrade = { ...trade, blockId };\n return promisifyRequest(store.add(storedTrade));\n });\n\n await Promise.all(promises);\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Get all trades for a block\n */\nexport async function getTradesByBlock(blockId: string): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const result = await promisifyRequest(index.getAll(blockId));\n\n // Sort by date opened (chronological order)\n return result.sort((a, b) => {\n const dateA = new Date(a.dateOpened).getTime();\n const dateB = new Date(b.dateOpened).getTime();\n if (dateA !== dateB) return dateA - dateB;\n\n // If same date, sort by time\n return a.timeOpened.localeCompare(b.timeOpened);\n });\n });\n}\n\n/**\n * Get all trades for a block with optional leg group combining\n *\n * Uses cached combined trades when available for better performance.\n * Falls back to on-demand calculation if cache is missing.\n *\n * @param blockId - Block ID to fetch trades for\n * @param options.combineLegGroups - Whether to combine trades with same entry timestamp\n * @param options.skipCache - Force recalculation (bypass cache)\n * @returns Array of trades (combined or raw)\n */\nexport async function getTradesByBlockWithOptions(\n blockId: string,\n options: { combineLegGroups?: boolean; skipCache?: boolean } = {},\n): Promise<(StoredTrade | (CombinedTrade & { blockId: string }))[]> {\n // If combining is enabled, check cache FIRST before fetching raw trades\n // This avoids the expensive raw trade fetch when we have cached data\n if (options.combineLegGroups && !options.skipCache) {\n const cachedTrades = await getCombinedTradesCache(blockId);\n if (cachedTrades) {\n // Add blockId back to cached trades\n return cachedTrades.map((trade) => ({ ...trade, blockId }));\n }\n }\n\n // Fetch raw trades (only if not combining, or cache miss)\n const trades = await getTradesByBlock(blockId);\n\n if (!options.combineLegGroups) {\n return trades;\n }\n\n // Cache miss: calculate combined trades on-demand\n const tradesWithoutBlockId = trades.map((storedTrade) => {\n const { blockId: _ignored, ...trade } = storedTrade;\n void _ignored;\n return trade as Trade;\n });\n const combined = combineAllLegGroups(tradesWithoutBlockId);\n\n queueCombinedTradesCache(blockId, combined);\n\n // Add blockId back to combined trades\n return combined.map((trade) => ({ ...trade, blockId }));\n}\n\nfunction queueCombinedTradesCache(blockId: string, combinedTrades: CombinedTrade[]) {\n if (combinedCacheInflight.has(blockId)) {\n return;\n }\n\n const task = storeCombinedTradesCache(blockId, combinedTrades)\n .catch((err) => {\n console.warn(\"Failed to cache combined trades:\", err);\n })\n .finally(() => {\n combinedCacheInflight.delete(blockId);\n });\n\n combinedCacheInflight.set(blockId, task);\n}\n\n/**\n * Get trades by date range for a block\n */\nexport async function getTradesByDateRange(\n blockId: string,\n startDate: Date,\n endDate: Date,\n): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(\"composite_block_date\");\n\n // Create compound key range [blockId, startDate] to [blockId, endDate]\n const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);\n\n const result = await promisifyRequest(index.getAll(range));\n return result;\n });\n}\n\n/**\n * Get trades by strategy for a block\n */\nexport async function getTradesByStrategy(\n blockId: string,\n strategy: string,\n): Promise<StoredTrade[]> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const blockIndex = store.index(INDEXES.TRADES_BY_BLOCK);\n const blockTrades = await promisifyRequest(blockIndex.getAll(blockId));\n\n // Filter by strategy (IndexedDB doesn't support compound queries easily)\n return blockTrades.filter((trade) => trade.strategy === strategy);\n });\n}\n\n/**\n * Get unique strategies for a block\n */\nexport async function getStrategiesByBlock(blockId: string): Promise<string[]> {\n const trades = await getTradesByBlock(blockId);\n const strategies = new Set(trades.map((trade) => trade.strategy));\n return Array.from(strategies).sort();\n}\n\n/**\n * Get trade count by block\n */\nexport async function getTradeCountByBlock(blockId: string): Promise<number> {\n return withReadTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const result = await promisifyRequest(index.count(blockId));\n return result;\n });\n}\n\n/**\n * Delete all trades for a block\n */\nexport async function deleteTradesByBlock(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Update trades for a block (replace all)\n */\nexport async function updateTradesForBlock(blockId: string, trades: Trade[]): Promise<void> {\n await withWriteTransaction(STORES.TRADES, async (transaction) => {\n // First delete existing trades\n const store = transaction.objectStore(STORES.TRADES);\n const index = store.index(INDEXES.TRADES_BY_BLOCK);\n const deleteRequest = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n deleteRequest.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n deleteRequest.onerror = () => reject(deleteRequest.error);\n });\n\n // Then add new trades\n const promises = trades.map((trade) => {\n const storedTrade: StoredTrade = { ...trade, blockId };\n return promisifyRequest(store.add(storedTrade));\n });\n\n await Promise.all(promises);\n });\n\n // Invalidate caches since trades changed\n await deleteCombinedTradesCache(blockId);\n await deletePerformanceSnapshotCache(blockId);\n}\n\n/**\n * Get trade statistics for a block (aggregated)\n */\nexport async function getTradeStatistics(blockId: string): Promise<{\n totalTrades: number;\n totalPl: number;\n winningTrades: number;\n losingTrades: number;\n totalCommissions: number;\n dateRange: { start: Date | null; end: Date | null };\n strategies: string[];\n}> {\n const trades = await getTradesByBlock(blockId);\n\n if (trades.length === 0) {\n return {\n totalTrades: 0,\n totalPl: 0,\n winningTrades: 0,\n losingTrades: 0,\n totalCommissions: 0,\n dateRange: { start: null, end: null },\n strategies: [],\n };\n }\n\n const totalTrades = trades.length;\n const totalPl = trades.reduce((sum, trade) => sum + trade.pl, 0);\n const winningTrades = trades.filter((trade) => trade.pl > 0).length;\n const losingTrades = trades.filter((trade) => trade.pl < 0).length;\n const totalCommissions = trades.reduce(\n (sum, trade) => sum + trade.openingCommissionsFees + trade.closingCommissionsFees,\n 0,\n );\n\n // Get date range\n const dates = trades.map((trade) => new Date(trade.dateOpened));\n const start = new Date(Math.min(...dates.map((d) => d.getTime())));\n const end = new Date(Math.max(...dates.map((d) => d.getTime())));\n\n // Get unique strategies\n const strategies = Array.from(new Set(trades.map((trade) => trade.strategy))).sort();\n\n return {\n totalTrades,\n totalPl,\n winningTrades,\n losingTrades,\n totalCommissions,\n dateRange: { start, end },\n strategies,\n };\n}\n\n/**\n * Search trades by text (strategy, legs, reason for close)\n */\nexport async function searchTrades(blockId: string, query: string): Promise<StoredTrade[]> {\n const trades = await getTradesByBlock(blockId);\n const lowerQuery = query.toLowerCase();\n\n return trades.filter(\n (trade) =>\n trade.strategy.toLowerCase().includes(lowerQuery) ||\n trade.legs.toLowerCase().includes(lowerQuery) ||\n (trade.reasonForClose && trade.reasonForClose.toLowerCase().includes(lowerQuery)),\n );\n}\n\n/**\n * Get trades with pagination\n */\nexport async function getTradesPage(\n blockId: string,\n offset: number,\n limit: number,\n): Promise<{ trades: StoredTrade[]; total: number }> {\n const allTrades = await getTradesByBlock(blockId);\n const total = allTrades.length;\n const trades = allTrades.slice(offset, offset + limit);\n\n return { trades, total };\n}\n\n/**\n * Export trades to CSV format (for backup/analysis)\n */\nexport async function exportTradesToCSV(blockId: string): Promise<string> {\n const trades = await getTradesByBlock(blockId);\n\n if (trades.length === 0) {\n return \"\";\n }\n\n // CSV headers\n const headers = [\n \"Date Opened\",\n \"Time Opened\",\n \"Opening Price\",\n \"Legs\",\n \"Premium\",\n \"Closing Price\",\n \"Date Closed\",\n \"Time Closed\",\n \"Avg. Closing Cost\",\n \"Reason For Close\",\n \"P/L\",\n \"No. of Contracts\",\n \"Funds at Close\",\n \"Margin Req.\",\n \"Strategy\",\n \"Opening Commissions + Fees\",\n \"Closing Commissions + Fees\",\n \"Opening Short/Long Ratio\",\n \"Closing Short/Long Ratio\",\n \"Opening VIX\",\n \"Closing VIX\",\n \"Gap\",\n \"Movement\",\n \"Max Profit\",\n \"Max Loss\",\n ];\n\n // Convert trades to CSV rows\n const rows = trades.map((trade) => {\n // Format dateOpened - handle both Date objects and strings\n const dateOpened =\n trade.dateOpened instanceof Date\n ? trade.dateOpened.toISOString().split(\"T\")[0]\n : typeof trade.dateOpened === \"string\"\n ? new Date(trade.dateOpened).toISOString().split(\"T\")[0]\n : trade.dateOpened;\n\n return [\n dateOpened,\n trade.timeOpened,\n trade.openingPrice.toString(),\n trade.legs,\n trade.premium.toString(),\n trade.closingPrice?.toString() || \"\",\n trade.dateClosed instanceof Date\n ? trade.dateClosed.toISOString().split(\"T\")[0]\n : typeof trade.dateClosed === \"string\" && trade.dateClosed !== \"\"\n ? new Date(trade.dateClosed).toISOString().split(\"T\")[0]\n : \"\",\n trade.timeClosed || \"\",\n trade.avgClosingCost?.toString() || \"\",\n trade.reasonForClose || \"\",\n trade.pl.toString(),\n trade.numContracts.toString(),\n trade.fundsAtClose.toString(),\n trade.marginReq.toString(),\n trade.strategy,\n trade.openingCommissionsFees.toString(),\n trade.closingCommissionsFees.toString(),\n trade.openingShortLongRatio.toString(),\n trade.closingShortLongRatio?.toString() || \"\",\n trade.openingVix?.toString() || \"\",\n trade.closingVix?.toString() || \"\",\n trade.gap?.toString() || \"\",\n trade.movement?.toString() || \"\",\n trade.maxProfit?.toString() || \"\",\n trade.maxLoss?.toString() || \"\",\n ];\n });\n\n // Combine headers and rows\n const csvContent = [headers, ...rows]\n .map((row) => row.map((field) => `\"${field}\"`).join(\",\"))\n .join(\"\\n\");\n\n return csvContent;\n}\n","/**\n * IndexedDB Database Service for TradeBlocks\n *\n * Manages the client-side database for storing blocks, trades, and daily logs.\n * Uses a versioned schema with migration support.\n */\n\n// Types imported for reference (commented out to avoid unused warnings)\n// import { ProcessedBlock } from '../models/block.ts'\n// import { Trade } from '../models/trade.ts'\n// import { DailyLogEntry } from '../models/daily-log.ts'\n// import { PortfolioStats, StrategyStats, PerformanceMetrics } from '../models/portfolio-stats.ts'\n\n// Database configuration\nexport const DB_NAME = \"TradeBlocksDB\";\nexport const DB_VERSION = 4;\n\n// Object store names\nexport const STORES = {\n BLOCKS: \"blocks\",\n TRADES: \"trades\",\n DAILY_LOGS: \"dailyLogs\",\n CALCULATIONS: \"calculations\",\n REPORTING_LOGS: \"reportingLogs\",\n WALK_FORWARD: \"walkForwardAnalyses\",\n STATIC_DATASETS: \"staticDatasets\",\n STATIC_DATASET_ROWS: \"staticDatasetRows\",\n} as const;\n\n// Index names\nexport const INDEXES = {\n TRADES_BY_BLOCK: \"blockId\",\n TRADES_BY_DATE: \"dateOpened\",\n TRADES_BY_STRATEGY: \"strategy\",\n DAILY_LOGS_BY_BLOCK: \"blockId\",\n DAILY_LOGS_BY_DATE: \"date\",\n CALCULATIONS_BY_BLOCK: \"blockId\",\n REPORTING_LOGS_BY_BLOCK: \"blockId\",\n REPORTING_LOGS_BY_STRATEGY: \"strategy\",\n WALK_FORWARD_BY_BLOCK: \"blockId\",\n STATIC_DATASET_ROWS_BY_DATASET: \"datasetId\",\n STATIC_DATASET_ROWS_BY_TIMESTAMP: \"timestamp\",\n} as const;\n\n/**\n * Database instance singleton\n */\nlet dbInstance: IDBDatabase | null = null;\n\n/**\n * Initialize the IndexedDB database\n */\nexport async function initializeDatabase(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n if (dbInstance) {\n resolve(dbInstance);\n return;\n }\n\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => {\n reject(new Error(`Failed to open database: ${request.error?.message}`));\n };\n\n request.onsuccess = () => {\n dbInstance = request.result;\n resolve(dbInstance);\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const transaction = (event.target as IDBOpenDBRequest).transaction!;\n\n // Create blocks store\n if (!db.objectStoreNames.contains(STORES.BLOCKS)) {\n const blocksStore = db.createObjectStore(STORES.BLOCKS, {\n keyPath: \"id\",\n });\n blocksStore.createIndex(\"name\", \"name\", { unique: false });\n blocksStore.createIndex(\"isActive\", \"isActive\", { unique: false });\n blocksStore.createIndex(\"created\", \"created\", { unique: false });\n blocksStore.createIndex(\"lastModified\", \"lastModified\", {\n unique: false,\n });\n }\n\n // Create trades store\n if (!db.objectStoreNames.contains(STORES.TRADES)) {\n const tradesStore = db.createObjectStore(STORES.TRADES, {\n autoIncrement: true,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_DATE, \"dateOpened\", {\n unique: false,\n });\n tradesStore.createIndex(INDEXES.TRADES_BY_STRATEGY, \"strategy\", {\n unique: false,\n });\n tradesStore.createIndex(\"pl\", \"pl\", { unique: false });\n tradesStore.createIndex(\"composite_block_date\", [\"blockId\", \"dateOpened\"], {\n unique: false,\n });\n }\n\n // Create daily logs store\n if (!db.objectStoreNames.contains(STORES.DAILY_LOGS)) {\n const dailyLogsStore = db.createObjectStore(STORES.DAILY_LOGS, {\n autoIncrement: true,\n });\n dailyLogsStore.createIndex(INDEXES.DAILY_LOGS_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n dailyLogsStore.createIndex(INDEXES.DAILY_LOGS_BY_DATE, \"date\", {\n unique: false,\n });\n dailyLogsStore.createIndex(\"composite_block_date\", [\"blockId\", \"date\"], { unique: false });\n }\n\n // Create reporting logs store\n if (!db.objectStoreNames.contains(STORES.REPORTING_LOGS)) {\n const reportingStore = db.createObjectStore(STORES.REPORTING_LOGS, {\n autoIncrement: true,\n });\n reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n reportingStore.createIndex(INDEXES.REPORTING_LOGS_BY_STRATEGY, \"strategy\", {\n unique: false,\n });\n reportingStore.createIndex(\"composite_block_date\", [\"blockId\", \"dateOpened\"], {\n unique: false,\n });\n }\n\n // Create calculations store (for cached computations)\n if (!db.objectStoreNames.contains(STORES.CALCULATIONS)) {\n const calculationsStore = db.createObjectStore(STORES.CALCULATIONS, {\n keyPath: \"id\",\n });\n calculationsStore.createIndex(INDEXES.CALCULATIONS_BY_BLOCK, \"blockId\", { unique: false });\n calculationsStore.createIndex(\"calculationType\", \"calculationType\", {\n unique: false,\n });\n calculationsStore.createIndex(\"calculatedAt\", \"calculatedAt\", {\n unique: false,\n });\n }\n\n // Create walk-forward analysis store\n if (!db.objectStoreNames.contains(STORES.WALK_FORWARD)) {\n const walkForwardStore = db.createObjectStore(STORES.WALK_FORWARD, {\n keyPath: \"id\",\n });\n walkForwardStore.createIndex(INDEXES.WALK_FORWARD_BY_BLOCK, \"blockId\", {\n unique: false,\n });\n walkForwardStore.createIndex(\"createdAt\", \"createdAt\", { unique: false });\n }\n\n // Create static datasets store (metadata)\n if (!db.objectStoreNames.contains(STORES.STATIC_DATASETS)) {\n const staticDatasetsStore = db.createObjectStore(STORES.STATIC_DATASETS, {\n keyPath: \"id\",\n });\n staticDatasetsStore.createIndex(\"name\", \"name\", { unique: true });\n staticDatasetsStore.createIndex(\"uploadedAt\", \"uploadedAt\", { unique: false });\n }\n\n // Create static dataset rows store (data rows)\n if (!db.objectStoreNames.contains(STORES.STATIC_DATASET_ROWS)) {\n const staticDatasetRowsStore = db.createObjectStore(STORES.STATIC_DATASET_ROWS, {\n autoIncrement: true,\n });\n staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_DATASET, \"datasetId\", {\n unique: false,\n });\n staticDatasetRowsStore.createIndex(INDEXES.STATIC_DATASET_ROWS_BY_TIMESTAMP, \"timestamp\", {\n unique: false,\n });\n staticDatasetRowsStore.createIndex(\n \"composite_dataset_timestamp\",\n [\"datasetId\", \"timestamp\"],\n { unique: false },\n );\n }\n\n transaction.oncomplete = () => {\n dbInstance = db;\n resolve(db);\n };\n };\n });\n}\n\n/**\n * Get database instance (initialize if needed)\n */\nexport async function getDatabase(): Promise<IDBDatabase> {\n if (dbInstance) {\n return dbInstance;\n }\n return initializeDatabase();\n}\n\n/**\n * Close database connection\n */\nexport function closeDatabase(): void {\n if (dbInstance) {\n dbInstance.close();\n dbInstance = null;\n }\n}\n\n/**\n * Delete the entire database (for testing/reset)\n * This version is more robust for corrupted databases:\n * - Doesn't require opening the database first\n * - Has timeout to prevent hanging forever\n * - Resolves on blocked (since deletion completes after reload)\n */\nexport async function deleteDatabase(): Promise<void> {\n return new Promise((resolve) => {\n // Close any existing connection (don't wait for it)\n if (dbInstance) {\n try {\n dbInstance.close();\n } catch {\n // Ignore close errors - database might be in bad state\n }\n dbInstance = null;\n }\n\n const deleteRequest = indexedDB.deleteDatabase(DB_NAME);\n\n // Timeout to prevent hanging forever on corrupted database\n const timeout = setTimeout(() => {\n console.warn(\"Database deletion timed out - will retry after reload\");\n resolve(); // Resolve anyway so we can reload\n }, 5000);\n\n deleteRequest.onsuccess = () => {\n clearTimeout(timeout);\n resolve();\n };\n\n deleteRequest.onerror = () => {\n clearTimeout(timeout);\n console.error(\"Failed to delete database:\", deleteRequest.error);\n // Still resolve - user can retry after page reload\n resolve();\n };\n\n deleteRequest.onblocked = () => {\n clearTimeout(timeout);\n console.warn(\"Database deletion blocked - will complete after reload\");\n // Resolve instead of reject - the deletion will complete once all connections close\n // After page reload, there will be no connections blocking it\n resolve();\n };\n });\n}\n\n/**\n * Transaction helper for read operations\n */\nexport async function withReadTransaction<T>(\n stores: string | string[],\n callback: (transaction: IDBTransaction) => Promise<T>,\n): Promise<T> {\n const db = await getDatabase();\n const storeNames = Array.isArray(stores) ? stores : [stores];\n const transaction = db.transaction(storeNames, \"readonly\");\n\n return callback(transaction);\n}\n\n/**\n * Transaction helper for write operations\n */\nexport async function withWriteTransaction<T>(\n stores: string | string[],\n callback: (transaction: IDBTransaction) => Promise<T>,\n): Promise<T> {\n const db = await getDatabase();\n const storeNames = Array.isArray(stores) ? stores : [stores];\n const transaction = db.transaction(storeNames, \"readwrite\");\n\n return callback(transaction);\n}\n\n/**\n * Generic helper for promisifying IDBRequest\n */\nexport function promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\n/**\n * Storage quota management\n */\nexport interface StorageInfo {\n quota: number;\n usage: number;\n available: number;\n persistent: boolean;\n}\n\n/**\n * Get storage quota information\n */\nexport async function getStorageInfo(): Promise<StorageInfo> {\n if (\"storage\" in navigator && \"estimate\" in navigator.storage) {\n const estimate = await navigator.storage.estimate();\n const persistent = await navigator.storage.persisted();\n\n return {\n quota: estimate.quota || 0,\n usage: estimate.usage || 0,\n available: (estimate.quota || 0) - (estimate.usage || 0),\n persistent,\n };\n }\n\n // Fallback for browsers without storage API\n return {\n quota: 0,\n usage: 0,\n available: 0,\n persistent: false,\n };\n}\n\n/**\n * Request persistent storage\n */\nexport async function requestPersistentStorage(): Promise<boolean> {\n if (\"storage\" in navigator && \"persist\" in navigator.storage) {\n return navigator.storage.persist();\n }\n return false;\n}\n\n/**\n * Database error types\n */\nexport class DatabaseError extends Error {\n readonly operation: string;\n readonly store?: string;\n override readonly cause?: Error;\n constructor(message: string, operation: string, store?: string, cause?: Error) {\n super(message);\n this.name = \"DatabaseError\";\n this.operation = operation;\n this.store = store;\n this.cause = cause;\n }\n}\n\nexport class QuotaExceededError extends DatabaseError {\n constructor(operation: string, store?: string) {\n super(\"Storage quota exceeded\", operation, store);\n this.name = \"QuotaExceededError\";\n }\n}\n\nexport class TransactionError extends DatabaseError {\n constructor(message: string, operation: string, store?: string, cause?: Error) {\n super(message, operation, store, cause);\n this.name = \"TransactionError\";\n }\n}\n\n// Re-export functions from individual stores\nexport {\n createBlock,\n deleteBlock,\n getActiveBlock,\n getAllBlocks,\n getBlock,\n updateBlock,\n updateBlockStats,\n} from \"./blocks-store.ts\";\nexport {\n addDailyLogEntries,\n deleteDailyLogsByBlock,\n getDailyLogCountByBlock,\n getDailyLogsByBlock,\n updateDailyLogsForBlock,\n} from \"./daily-logs-store.ts\";\nexport type { StoredDailyLogEntry } from \"./daily-logs-store.ts\";\nexport {\n addReportingTrades,\n deleteReportingTradesByBlock,\n getReportingStrategiesByBlock,\n getReportingTradeCountByBlock,\n getReportingTradesByBlock,\n updateReportingTradesForBlock,\n} from \"./reporting-logs-store.ts\";\nexport {\n addTrades,\n deleteTradesByBlock,\n getTradeCountByBlock,\n getTradesByBlock,\n getTradesByBlockWithOptions,\n updateTradesForBlock,\n} from \"./trades-store.ts\";\nexport type { StoredTrade } from \"./trades-store.ts\";\nexport {\n saveWalkForwardAnalysis,\n getWalkForwardAnalysis,\n getWalkForwardAnalysesByBlock,\n deleteWalkForwardAnalysis,\n deleteWalkForwardAnalysesByBlock,\n} from \"./walk-forward-store.ts\";\nexport {\n storeCombinedTradesCache,\n getCombinedTradesCache,\n deleteCombinedTradesCache,\n hasCombinedTradesCache,\n invalidateBlockCaches,\n} from \"./combined-trades-cache.ts\";\nexport {\n storePerformanceSnapshotCache,\n getPerformanceSnapshotCache,\n deletePerformanceSnapshotCache,\n hasPerformanceSnapshotCache,\n} from \"./performance-snapshot-cache.ts\";\nexport type { CachedPerformanceSnapshot } from \"./performance-snapshot-cache.ts\";\nexport {\n storeEnrichedTradesCache,\n getEnrichedTradesCache,\n deleteEnrichedTradesCache,\n hasEnrichedTradesCache,\n} from \"./enriched-trades-cache.ts\";\nexport {\n createStaticDataset,\n getStaticDataset,\n getStaticDatasetByName,\n getAllStaticDatasets,\n updateStaticDatasetMatchStrategy,\n updateStaticDatasetName,\n deleteStaticDataset,\n isDatasetNameTaken,\n getStaticDatasetCount,\n} from \"./static-datasets-store.ts\";\nexport {\n addStaticDatasetRows,\n getStaticDatasetRows,\n getStaticDatasetRowsByRange,\n getStaticDatasetRowCount,\n deleteStaticDatasetRows,\n deleteStaticDatasetWithRows,\n getStaticDatasetDateRange,\n} from \"./static-dataset-rows-store.ts\";\n","/**\n * Daily Logs Store - CRUD operations for daily log data\n */\n\nimport type { DailyLogEntry } from \"../models/daily-log.ts\";\nimport {\n STORES,\n INDEXES,\n withReadTransaction,\n withWriteTransaction,\n promisifyRequest,\n} from \"./index.ts\";\n\n/**\n * Extended daily log entry with block association\n */\nexport interface StoredDailyLogEntry extends DailyLogEntry {\n blockId: string;\n id?: number; // Auto-generated by IndexedDB\n}\n\n/**\n * Add daily log entries for a block (batch operation)\n */\nexport async function addDailyLogEntries(blockId: string, entries: DailyLogEntry[]): Promise<void> {\n if (entries.length === 0) return;\n\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n\n // Use Promise.all for better performance with large datasets\n const promises = entries.map((entry) => {\n const storedEntry: StoredDailyLogEntry = { ...entry, blockId };\n return promisifyRequest(store.add(storedEntry));\n });\n\n await Promise.all(promises);\n });\n}\n\n/**\n * Get all daily log entries for a block\n */\nexport async function getDailyLogsByBlock(blockId: string): Promise<StoredDailyLogEntry[]> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const result = await promisifyRequest(index.getAll(blockId));\n\n // Sort by date (chronological order)\n return result.sort((a, b) => {\n const dateA = new Date(a.date).getTime();\n const dateB = new Date(b.date).getTime();\n return dateA - dateB;\n });\n });\n}\n\n/**\n * Get daily log entries by date range for a block\n */\nexport async function getDailyLogsByDateRange(\n blockId: string,\n startDate: Date,\n endDate: Date,\n): Promise<StoredDailyLogEntry[]> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(\"composite_block_date\");\n\n // Create compound key range [blockId, startDate] to [blockId, endDate]\n const range = IDBKeyRange.bound([blockId, startDate], [blockId, endDate], false, false);\n\n const result = await promisifyRequest(index.getAll(range));\n return result;\n });\n}\n\n/**\n * Get daily log entry for a specific date\n */\nexport async function getDailyLogByDate(\n blockId: string,\n date: Date,\n): Promise<StoredDailyLogEntry | null> {\n const entries = await getDailyLogsByDateRange(blockId, date, date);\n return entries.length > 0 ? entries[0] : null;\n}\n\n/**\n * Get daily log count by block\n */\nexport async function getDailyLogCountByBlock(blockId: string): Promise<number> {\n return withReadTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const result = await promisifyRequest(index.count(blockId));\n return result;\n });\n}\n\n/**\n * Delete all daily log entries for a block\n */\nexport async function deleteDailyLogsByBlock(blockId: string): Promise<void> {\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const request = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n request.onerror = () => reject(request.error);\n });\n });\n}\n\n/**\n * Update daily log entries for a block (replace all)\n */\nexport async function updateDailyLogsForBlock(\n blockId: string,\n entries: DailyLogEntry[],\n): Promise<void> {\n await withWriteTransaction(STORES.DAILY_LOGS, async (transaction) => {\n // First delete existing entries\n const store = transaction.objectStore(STORES.DAILY_LOGS);\n const index = store.index(INDEXES.DAILY_LOGS_BY_BLOCK);\n const deleteRequest = index.openCursor(IDBKeyRange.only(blockId));\n\n await new Promise<void>((resolve, reject) => {\n deleteRequest.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n cursor.delete();\n cursor.continue();\n } else {\n resolve();\n }\n };\n deleteRequest.onerror = () => reject(deleteRequest.error);\n });\n\n // Then add new entries\n const promises = entries.map((entry) => {\n const storedEntry: StoredDailyLogEntry = { ...entry, blockId };\n return promisifyRequest(store.add(storedEntry));\n });\n\n await Promise.all(promises);\n });\n}\n\n/**\n * Get daily log statistics for a block\n */\nexport async function getDailyLogStatistics(blockId: string): Promise<{\n totalEntries: number;\n dateRange: { start: Date | null; end: Date | null };\n finalPortfolioValue: number;\n maxDrawdown: number;\n totalPl: number;\n avgDailyPl: number;\n}> {\n const entries = await getDailyLogsByBlock(blockId);\n\n if (entries.length === 0) {\n return {\n totalEntries: 0,\n dateRange: { start: null, end: null },\n finalPortfolioValue: 0,\n maxDrawdown: 0,\n totalPl: 0,\n avgDailyPl: 0,\n };\n }\n\n const totalEntries = entries.length;\n\n // Get date range\n const dates = entries.map((entry) => new Date(entry.date));\n const start = new Date(Math.min(...dates.map((d) => d.getTime())));\n const end = new Date(Math.max(...dates.map((d) => d.getTime())));\n\n // Final portfolio value (last entry)\n const sortedEntries = entries.sort(\n (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),\n );\n const finalPortfolioValue = sortedEntries[0]?.netLiquidity || 0;\n\n // Calculate max drawdown (most negative value)\n const maxDrawdown = Math.min(...entries.map((entry) => entry.drawdownPct));\n\n // Total P/L (sum of all daily P/L)\n const totalPl = entries.reduce((sum, entry) => sum + entry.dailyPl, 0);\n\n // Average daily P/L\n const avgDailyPl = totalPl / totalEntries;\n\n return {\n totalEntries,\n dateRange: { start, end },\n finalPortfolioValue,\n maxDrawdown,\n totalPl,\n avgDailyPl,\n };\n}\n\n/**\n * Get portfolio value over time (for charts)\n */\nexport async function getPortfolioValueTimeSeries(blockId: string): Promise<\n Array<{\n date: Date;\n netLiquidity: number;\n dailyPl: number;\n drawdownPct: number;\n }>\n> {\n const entries = await getDailyLogsByBlock(blockId);\n\n return entries.map((entry) => ({\n date: new Date(entry.date),\n netLiquidity: entry.netLiquidity,\n dailyPl: entry.dailyPl,\n drawdownPct: entry.drawdownPct,\n }));\n}\n\n/**\n * Get daily P/L aggregated by month\n */\nexport async function getMonthlyPl(blockId: string): Promise<Record<string, number>> {\n const entries = await getDailyLogsByBlock(blockId);\n const monthlyPl: Record<string, number> = {};\n\n entries.forEach((entry) => {\n const date = new Date(entry.date);\n const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, \"0\")}`;\n\n if (!monthlyPl[monthKey]) {\n monthlyPl[monthKey] = 0;\n }\n monthlyPl[monthKey] += entry.dailyPl;\n });\n\n return monthlyPl;\n}\n\n/**\n * Get daily P/L aggregated by week\n */\nexport async function getWeeklyPl(blockId: string): Promise<Record<string, number>> {\n const entries = await getDailyLogsByBlock(blockId);\n const weeklyPl: Record<string, number> = {};\n\n entries.forEach((entry) => {\n const date = new Date(entry.date);\n const year = date.getFullYear();\n const week = getWeekNumber(date);\n const weekKey = `${year}-W${String(week).padStart(2, \"0\")}`;\n\n if (!weeklyPl[weekKey]) {\n weeklyPl[weekKey] = 0;\n }\n weeklyPl[weekKey] += entry.dailyPl;\n });\n\n return weeklyPl;\n}\n\n/**\n * Export daily logs to CSV format\n */\nexport async function exportDailyLogsToCSV(blockId: string): Promise<string> {\n const entries = await getDailyLogsByBlock(blockId);\n\n if (entries.length === 0) {\n return \"\";\n }\n\n // CSV headers\n const headers = [\n \"Date\",\n \"Net Liquidity\",\n \"Current Funds\",\n \"Withdrawn\",\n \"Trading Funds\",\n \"P/L\",\n \"P/L %\",\n \"Drawdown %\",\n ];\n\n // Convert entries to CSV rows\n const rows = entries.map((entry) => [\n entry.date instanceof Date ? entry.date.toISOString().split(\"T\")[0] : entry.date,\n entry.netLiquidity.toString(),\n entry.currentFunds.toString(),\n entry.withdrawn.toString(),\n entry.tradingFunds.toString(),\n entry.dailyPl.toString(),\n entry.dailyPlPct.toString(),\n entry.drawdownPct.toString(),\n ]);\n\n // Combine headers and rows\n const csvContent = [headers, ...rows]\n .map((row) => row.map((field) => `\"${field}\"`).join(\",\"))\n .join(\"\\n\");\n\n return csvContent;\n}\n\n/**\n * Helper function to get week number\n */\nfunction getWeekNumber(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));\n const dayNum = d.getUTCDay() || 7;\n d.setUTCDate(d.getUTCDate() + 4 - dayNum);\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);\n}\n"],"mappings":"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","names":["getCacheId","getCacheId"]}