tradeblocks-mcp 3.0.1 → 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.
- package/README.md +82 -68
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/{server/chunk-JAAQMESY.js → dist/chunk-VDU25Z6X.js} +735 -164
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +303 -0
- package/dist/market-provider-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +367 -255
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +4 -12
- package/package.json +4 -2
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/{dist/chunk-CCITWAAI.js → server/chunk-ZBJCF4ZG.js} +491 -364
- package/server/chunk-ZBJCF4ZG.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-ENEUT22A.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +1773 -1540
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +303 -0
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +12 -4
- package/src/db/connection.ts +107 -40
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +141 -68
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +134 -122
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +9 -12
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +426 -0
- package/src/utils/iv-solver-worker.ts +112 -0
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +267 -78
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +18 -30
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-CCITWAAI.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-GGLA7ACC.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-JAAQMESY.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-D5SI47K7.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-GGLA7ACC.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-D5SI47K7.js.map → market-provider-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
- /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
|
|
64
|
-
| Windows
|
|
65
|
-
| Linux
|
|
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
|
|
139
|
-
|
|
140
|
-
| stdio | (default) | Local CLI tools
|
|
141
|
-
| HTTP
|
|
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
|
|
158
|
-
|
|
159
|
-
| `--http`
|
|
160
|
-
| `--port <n>`
|
|
161
|
-
| `--blocks-dir <path>` | Directory containing CSV block folders | same as data directory
|
|
162
|
-
| `--market-db <path>`
|
|
163
|
-
| `--no-auth`
|
|
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
|
|
168
|
-
|
|
169
|
-
| `BLOCKS_DIRECTORY`
|
|
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`
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
|
329
|
-
| `
|
|
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`
|
|
332
|
-
| `get_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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
372
|
-
| `
|
|
373
|
-
| `
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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(
|
|
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-
|
|
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"]}
|