tradeblocks-mcp 3.0.2 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +85 -80
  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-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
  7. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  8. package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
  9. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  10. package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
  11. package/dist/chunk-NWNSKALN.js.map +1 -0
  12. package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
  13. package/dist/chunk-WNTYA7ER.js.map +1 -0
  14. package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
  15. package/dist/chunk-YZA4RS76.js.map +1 -0
  16. package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
  17. package/dist/chunk-ZTJXC3LS.js.map +1 -0
  18. package/dist/daily-log-processor-MZW2XBZY.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-6U33TQUT.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
  24. package/dist/test-exports.js +876 -483
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-DKDCWGKD.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/package.json +2 -6
  29. package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
  30. package/server/chunk-536NSFED.js.map +1 -0
  31. package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
  32. package/server/chunk-A3UKD64A.js.map +1 -0
  33. package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
  34. package/server/chunk-OMUDNJBJ.js.map +1 -0
  35. package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
  36. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  37. package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
  38. package/server/chunk-RM4B2VKS.js.map +1 -0
  39. package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
  40. package/server/chunk-SY6GDNVI.js.map +1 -0
  41. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  42. package/server/chunk-T66KH2XH.js.map +1 -0
  43. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  44. package/server/chunk-W5E7FHC4.js.map +1 -0
  45. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  46. package/server/config-DK7KOMNL.js.map +1 -0
  47. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  48. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  49. package/server/http-server.js +2 -5
  50. package/server/index.js +2497 -2029
  51. package/server/index.js.map +1 -1
  52. package/server/iv-solver-worker.js +9 -1
  53. package/server/market-provider-L56RFHAC.js +17 -0
  54. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  55. package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
  56. package/server/trade-processor-3PI4LHZI.js +10 -0
  57. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  58. package/src/auth/clients-store.ts +4 -4
  59. package/src/auth/code-store.ts +1 -1
  60. package/src/auth/config.ts +11 -12
  61. package/src/auth/login-page.ts +8 -10
  62. package/src/auth/provider.ts +35 -30
  63. package/src/auth/token.ts +17 -15
  64. package/src/db/backtest-schemas.ts +38 -8
  65. package/src/db/connection.ts +107 -40
  66. package/src/db/data-root.ts +22 -0
  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 +139 -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/index.ts +2 -0
  114. package/src/tools/blocks/paired-comparison.ts +425 -0
  115. package/src/tools/blocks/similarity.ts +316 -283
  116. package/src/tools/edge-decay.ts +124 -197
  117. package/src/tools/exit-analysis.ts +130 -77
  118. package/src/tools/greeks-attribution.ts +84 -35
  119. package/src/tools/guides.ts +4 -6
  120. package/src/tools/imports.ts +11 -14
  121. package/src/tools/market-data.ts +1067 -757
  122. package/src/tools/market-enrichment.ts +3 -3
  123. package/src/tools/market-fetch.ts +148 -67
  124. package/src/tools/market-imports.ts +12 -12
  125. package/src/tools/middleware/sync-middleware.ts +5 -6
  126. package/src/tools/performance.ts +185 -302
  127. package/src/tools/profile-analysis.ts +52 -66
  128. package/src/tools/profiles.ts +106 -69
  129. package/src/tools/regime-advisor.ts +20 -45
  130. package/src/tools/replay.ts +81 -77
  131. package/src/tools/reports/discrepancies.ts +298 -328
  132. package/src/tools/reports/fields.ts +7 -25
  133. package/src/tools/reports/helpers.ts +18 -49
  134. package/src/tools/reports/predictive.ts +27 -70
  135. package/src/tools/reports/slippage-trends.ts +315 -345
  136. package/src/tools/reports/slippage.ts +1 -4
  137. package/src/tools/reports/strategy-matches.ts +399 -441
  138. package/src/tools/schema.ts +43 -40
  139. package/src/tools/shared/filters.ts +3 -9
  140. package/src/tools/snapshot.ts +9 -30
  141. package/src/tools/sql.ts +15 -14
  142. package/src/tools/tickers.ts +1 -4
  143. package/src/utils/batch-exit-analysis.ts +31 -42
  144. package/src/utils/black-scholes.ts +39 -29
  145. package/src/utils/block-loader.ts +69 -83
  146. package/src/utils/calibration-probe.ts +3 -4
  147. package/src/utils/chain-loader.ts +3 -3
  148. package/src/utils/csv-discovery.ts +16 -22
  149. package/src/utils/data-quality.ts +24 -36
  150. package/src/utils/exit-triggers.ts +91 -96
  151. package/src/utils/field-timing.ts +94 -79
  152. package/src/utils/filter-predicates.ts +13 -9
  153. package/src/utils/flatfile-importer.ts +94 -64
  154. package/src/utils/greeks-decomposition.ts +152 -100
  155. package/src/utils/iv-solver-pool.ts +55 -25
  156. package/src/utils/iv-solver-worker.ts +5 -5
  157. package/src/utils/market-enricher.ts +528 -497
  158. package/src/utils/market-importer.ts +31 -12
  159. package/src/utils/market-provider.ts +21 -23
  160. package/src/utils/massive-tier.ts +5 -7
  161. package/src/utils/migrate-option-data-helpers.ts +2 -8
  162. package/src/utils/option-quote-greeks.ts +25 -31
  163. package/src/utils/option-time.ts +4 -8
  164. package/src/utils/output-formatter.ts +1 -4
  165. package/src/utils/provider-capabilities.ts +1 -4
  166. package/src/utils/providers/massive.ts +59 -93
  167. package/src/utils/providers/thetadata/backfill.ts +14 -23
  168. package/src/utils/providers/thetadata/client.ts +12 -8
  169. package/src/utils/providers/thetadata/decode.ts +2 -20
  170. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  171. package/src/utils/providers/thetadata/join.ts +11 -10
  172. package/src/utils/providers/thetadata/proto.ts +12 -10
  173. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  174. package/src/utils/providers/thetadata.ts +11 -10
  175. package/src/utils/quote-enricher.ts +4 -4
  176. package/src/utils/quote-parquet-projection.ts +3 -11
  177. package/src/utils/sample-date-selector.ts +3 -5
  178. package/src/utils/schema-metadata.ts +102 -70
  179. package/src/utils/ticker.ts +5 -9
  180. package/src/utils/trade-replay.ts +77 -68
  181. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  182. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  183. package/dist/chunk-FGZH632F.js.map +0 -1
  184. package/dist/chunk-LDKTV7GW.js.map +0 -1
  185. package/dist/chunk-PRAYH3RT.js.map +0 -1
  186. package/dist/chunk-QTTR7AAW.js.map +0 -1
  187. package/dist/chunk-W2PP3LEH.js.map +0 -1
  188. package/dist/chunk-XXYOUIZY.js.map +0 -1
  189. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  190. package/dist/market-provider-VDRJUEF2.js +0 -16
  191. package/dist/trade-processor-NHU4VWRX.js +0 -9
  192. package/manifest.json +0 -141
  193. package/server/chunk-5EBXHT6C.js.map +0 -1
  194. package/server/chunk-72GKJE2U.js.map +0 -1
  195. package/server/chunk-CSDVJPBB.js.map +0 -1
  196. package/server/chunk-FBNDMCT5.js.map +0 -1
  197. package/server/chunk-NRFXAJF7.js.map +0 -1
  198. package/server/chunk-NZO6PT64.js.map +0 -1
  199. package/server/chunk-PNKG7RY7.js.map +0 -1
  200. package/server/chunk-WA5AAPCH.js.map +0 -1
  201. package/server/config-6IZXEFEX.js.map +0 -1
  202. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  203. package/server/market-provider-VOYYVYWT.js +0 -17
  204. package/server/trade-processor-JWVS37KM.js +0 -10
  205. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  206. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  207. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
  208. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  209. /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
  210. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  211. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  212. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  213. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  214. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
  215. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  216. /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
  217. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
  218. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -0,0 +1,9 @@
1
+ import {
2
+ TradeProcessor
3
+ } from "./chunk-WNTYA7ER.js";
4
+ import "./chunk-YZA4RS76.js";
5
+ import "./chunk-5WRI5ZAA.js";
6
+ export {
7
+ TradeProcessor
8
+ };
9
+ //# sourceMappingURL=trade-processor-DKDCWGKD.js.map
@@ -12,7 +12,7 @@ import {
12
12
  getTradesPage,
13
13
  searchTrades,
14
14
  updateTradesForBlock
15
- } from "./chunk-LDKTV7GW.js";
15
+ } from "./chunk-27S67XW3.js";
16
16
  import "./chunk-5WRI5ZAA.js";
17
17
  export {
18
18
  addTrades,
@@ -29,4 +29,4 @@ export {
29
29
  searchTrades,
30
30
  updateTradesForBlock
31
31
  };
32
- //# sourceMappingURL=trades-store-LOL3FQSF.js.map
32
+ //# sourceMappingURL=trades-store-R3NL25T2.js.map
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "tradeblocks-mcp",
3
- "version": "3.0.2",
3
+ "version": "3.3.1",
4
4
  "description": "MCP server for options trade analysis",
5
5
  "author": "David Romeo <davidmromeo@gmail.com>",
6
6
  "type": "module",
7
- "main": "dist/skill-installer.js",
8
7
  "bin": {
9
8
  "tradeblocks-mcp": "server/index.js"
10
9
  },
@@ -130,7 +129,6 @@
130
129
  "dev": "tsup --watch",
131
130
  "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
132
131
  "verify:exports": "node scripts/verify-exports-surface.mjs",
133
- "mcpb:pack": "npm run build && node scripts/pack-mcpb.js",
134
132
  "pack:beta": "npm run build && npm pack --pack-destination .",
135
133
  "prepublishOnly": "npm run build"
136
134
  },
@@ -151,7 +149,6 @@
151
149
  "@types/express": "^5.0.6",
152
150
  "@types/jest": "^30.0.0",
153
151
  "@types/node": "^25.9.3",
154
- "archiver": "^7.0.1",
155
152
  "esbuild": "^0.27.2",
156
153
  "jest": "^30.3.0",
157
154
  "ts-jest": "^29.4.9",
@@ -165,8 +162,7 @@
165
162
  "server",
166
163
  "dist",
167
164
  "src",
168
- "agent-skills",
169
- "manifest.json"
165
+ "agent-skills"
170
166
  ],
171
167
  "keywords": [
172
168
  "mcp",
@@ -5,15 +5,15 @@ import {
5
5
  normalizeHeaders,
6
6
  rawTradeDataSchema,
7
7
  tradeSchema
8
- } from "./chunk-5EBXHT6C.js";
8
+ } from "./chunk-SY6GDNVI.js";
9
9
 
10
10
  // ../lib/models/trade.ts
11
11
  var TRADE_COLUMN_MAPPING = {
12
12
  "Date Opened": "dateOpened",
13
13
  "Time Opened": "timeOpened",
14
14
  "Opening Price": "openingPrice",
15
- "Legs": "legs",
16
- "Premium": "premium",
15
+ Legs: "legs",
16
+ Premium: "premium",
17
17
  "Closing Price": "closingPrice",
18
18
  "Date Closed": "dateClosed",
19
19
  "Time Closed": "timeClosed",
@@ -23,15 +23,15 @@ var TRADE_COLUMN_MAPPING = {
23
23
  "No. of Contracts": "numContracts",
24
24
  "Funds at Close": "fundsAtClose",
25
25
  "Margin Req.": "marginReq",
26
- "Strategy": "strategy",
26
+ Strategy: "strategy",
27
27
  "Opening Commissions + Fees": "openingCommissionsFees",
28
28
  "Closing Commissions + Fees": "closingCommissionsFees",
29
29
  "Opening Short/Long Ratio": "openingShortLongRatio",
30
30
  "Closing Short/Long Ratio": "closingShortLongRatio",
31
31
  "Opening VIX": "openingVix",
32
32
  "Closing VIX": "closingVix",
33
- "Gap": "gap",
34
- "Movement": "movement",
33
+ Gap: "gap",
34
+ Movement: "movement",
35
35
  "Max Profit": "maxProfit",
36
36
  "Max Loss": "maxLoss"
37
37
  };
@@ -274,7 +274,10 @@ var TradeProcessor = class {
274
274
  rawTradeDataSchema.parse(normalizedRow);
275
275
  return normalizedRow;
276
276
  } catch (error) {
277
- console.warn(`[TradeProcessor] Row ${rowIndex + 2} validation failed:`, error instanceof Error ? error.message : error);
277
+ console.warn(
278
+ `[TradeProcessor] Row ${rowIndex + 2} validation failed:`,
279
+ error instanceof Error ? error.message : error
280
+ );
278
281
  return null;
279
282
  }
280
283
  }
@@ -342,9 +345,21 @@ var TradeProcessor = class {
342
345
  fundsAtClose: parseNumber(rawData["Funds at Close"], "Funds at Close"),
343
346
  marginReq: parseNumber(rawData["Margin Req."], "Margin Req."),
344
347
  strategy,
345
- openingCommissionsFees: parseNumber(rawData["Opening Commissions + Fees"], "Opening Commissions", 0),
346
- closingCommissionsFees: parseNumber(rawData["Closing Commissions + Fees"], "Closing Commissions", 0),
347
- openingShortLongRatio: parseNumber(rawData["Opening Short/Long Ratio"], "Opening Short/Long Ratio", 0),
348
+ openingCommissionsFees: parseNumber(
349
+ rawData["Opening Commissions + Fees"],
350
+ "Opening Commissions",
351
+ 0
352
+ ),
353
+ closingCommissionsFees: parseNumber(
354
+ rawData["Closing Commissions + Fees"],
355
+ "Closing Commissions",
356
+ 0
357
+ ),
358
+ openingShortLongRatio: parseNumber(
359
+ rawData["Opening Short/Long Ratio"],
360
+ "Opening Short/Long Ratio",
361
+ 0
362
+ ),
348
363
  closingShortLongRatio: rawData["Closing Short/Long Ratio"] ? parseNumber(rawData["Closing Short/Long Ratio"], "Closing Short/Long Ratio") : void 0,
349
364
  openingVix: rawData["Opening VIX"] ? parseNumber(rawData["Opening VIX"], "Opening VIX") : void 0,
350
365
  closingVix: rawData["Closing VIX"] ? parseNumber(rawData["Closing VIX"], "Closing VIX") : void 0,
@@ -371,7 +386,9 @@ var TradeProcessor = class {
371
386
  const validatedTrade = tradeSchema.parse(trade);
372
387
  return validatedTrade;
373
388
  } catch (error) {
374
- throw new Error(`Trade conversion failed: ${error instanceof Error ? error.message : String(error)}`);
389
+ throw new Error(
390
+ `Trade conversion failed: ${error instanceof Error ? error.message : String(error)}`
391
+ );
375
392
  }
376
393
  }
377
394
  /**
@@ -387,4 +404,4 @@ var TradeProcessor = class {
387
404
  export {
388
405
  TradeProcessor
389
406
  };
390
- //# sourceMappingURL=chunk-CSDVJPBB.js.map
407
+ //# sourceMappingURL=chunk-536NSFED.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../lib/models/trade.ts","../../lib/processing/trade-processor.ts"],"sourcesContent":["/**\n * Trade model based on legacy Python Trade class\n * Represents individual trade record from portfolio CSV\n */\nexport interface Trade {\n // Core trade identification\n dateOpened: Date;\n timeOpened: string; // HH:mm:ss format\n openingPrice: number;\n legs: string; // Option legs description\n premium: number;\n /**\n * Records how the premium value was encoded in the source CSV.\n * Some exports (OptionOmega) provide cents as whole numbers without decimals.\n */\n premiumPrecision?: \"dollars\" | \"cents\";\n\n // Closing information (optional for open trades)\n closingPrice?: number;\n dateClosed?: Date;\n timeClosed?: string;\n avgClosingCost?: number;\n reasonForClose?: string;\n\n // Financial metrics\n pl: number; // Profit/Loss\n numContracts: number;\n fundsAtClose: number;\n marginReq: number;\n\n // Trade metadata\n strategy: string;\n openingCommissionsFees: number;\n closingCommissionsFees: number;\n\n // Ratios and market data\n openingShortLongRatio: number;\n closingShortLongRatio?: number;\n openingVix?: number;\n closingVix?: number;\n\n // Additional metrics\n gap?: number;\n movement?: number;\n maxProfit?: number;\n maxLoss?: number;\n /**\n * Synthetic-only: ratio of the worst observed loss to account capital at the time\n * Used to scale synthetic losses relative to current account size\n */\n syntheticCapitalRatio?: number;\n\n /**\n * Custom fields from extra columns in the trade CSV\n * Keys are the original column names, values are auto-detected as number or string\n */\n customFields?: Record<string, number | string>;\n}\n\n/**\n * Raw trade data as it comes from CSV before processing\n */\nexport interface RawTradeData {\n \"Date Opened\": string;\n \"Time Opened\": string;\n \"Opening Price\": string;\n Legs: string;\n Premium: string;\n \"Closing Price\"?: string;\n \"Date Closed\"?: string;\n \"Time Closed\"?: string;\n \"Avg. Closing Cost\"?: string;\n \"Reason For Close\"?: string;\n \"P/L\": string;\n \"No. of Contracts\": string;\n \"Funds at Close\": string;\n \"Margin Req.\": string;\n Strategy: string;\n \"Opening Commissions + Fees\": string;\n \"Closing Commissions + Fees\"?: string;\n \"Opening Short/Long Ratio\": string;\n \"Closing Short/Long Ratio\"?: string;\n \"Opening VIX\"?: string;\n \"Closing VIX\"?: string;\n Gap?: string;\n Movement?: string;\n \"Max Profit\"?: string;\n \"Max Loss\"?: string;\n}\n\n/**\n * Column mapping from CSV headers to Trade interface properties\n */\nexport const TRADE_COLUMN_MAPPING = {\n \"Date Opened\": \"dateOpened\",\n \"Time Opened\": \"timeOpened\",\n \"Opening Price\": \"openingPrice\",\n Legs: \"legs\",\n Premium: \"premium\",\n \"Closing Price\": \"closingPrice\",\n \"Date Closed\": \"dateClosed\",\n \"Time Closed\": \"timeClosed\",\n \"Avg. Closing Cost\": \"avgClosingCost\",\n \"Reason For Close\": \"reasonForClose\",\n \"P/L\": \"pl\",\n \"No. of Contracts\": \"numContracts\",\n \"Funds at Close\": \"fundsAtClose\",\n \"Margin Req.\": \"marginReq\",\n Strategy: \"strategy\",\n \"Opening Commissions + Fees\": \"openingCommissionsFees\",\n \"Closing Commissions + Fees\": \"closingCommissionsFees\",\n \"Opening Short/Long Ratio\": \"openingShortLongRatio\",\n \"Closing Short/Long Ratio\": \"closingShortLongRatio\",\n \"Opening VIX\": \"openingVix\",\n \"Closing VIX\": \"closingVix\",\n Gap: \"gap\",\n Movement: \"movement\",\n \"Max Profit\": \"maxProfit\",\n \"Max Loss\": \"maxLoss\",\n} as const;\n\n/**\n * Column aliases for different CSV export variations\n */\nexport const TRADE_COLUMN_ALIASES = {\n \"Opening comms & fees\": \"Opening Commissions + Fees\",\n \"Opening Commissions & Fees\": \"Opening Commissions + Fees\",\n \"Closing comms & fees\": \"Closing Commissions + Fees\",\n \"Closing Commissions & Fees\": \"Closing Commissions + Fees\",\n \"P/L %\": \"P/L %\", // Recognized but ignored (we calculate our own plPct)\n} as const;\n\n/**\n * Minimum required columns for a valid trade log\n */\nexport const REQUIRED_TRADE_COLUMNS = [\n \"Date Opened\",\n \"Time Opened\",\n \"Opening Price\",\n \"Legs\",\n \"Premium\",\n \"Closing Price\",\n \"Date Closed\",\n \"Time Closed\",\n \"Avg. Closing Cost\",\n \"Reason For Close\",\n \"P/L\",\n \"No. of Contracts\",\n \"Funds at Close\",\n \"Margin Req.\",\n \"Strategy\",\n] as const;\n","/**\n * Trade Processor\n *\n * Handles parsing and processing of trade log CSV files from OptionOmega.\n * Converts raw CSV data to validated Trade objects.\n */\n\nimport {\n type Trade,\n TRADE_COLUMN_ALIASES,\n REQUIRED_TRADE_COLUMNS,\n TRADE_COLUMN_MAPPING,\n} from \"../models/trade.ts\";\nimport type { ValidationError, ProcessingError } from \"../models/index.ts\";\nimport { rawTradeDataSchema, tradeSchema } from \"../models/validators.ts\";\nimport { CSVParser, type ParseProgress } from \"./csv-parser.ts\";\nimport { findMissingHeaders, normalizeHeaders } from \"../utils/csv-headers.ts\";\n\n/**\n * Set of known trade column names (canonical names from TRADE_COLUMN_MAPPING)\n * Used to identify custom columns that should be preserved\n */\nconst KNOWN_TRADE_COLUMNS = new Set([\n ...Object.keys(TRADE_COLUMN_MAPPING),\n ...Object.keys(TRADE_COLUMN_ALIASES),\n]);\n\n/**\n * Trade processing configuration\n */\nexport interface TradeProcessingConfig {\n maxTrades?: number;\n strictValidation?: boolean;\n progressCallback?: (progress: TradeProcessingProgress) => void;\n}\n\n/**\n * Trade processing progress\n */\nexport interface TradeProcessingProgress extends ParseProgress {\n stage: \"reading\" | \"parsing\" | \"validating\" | \"converting\" | \"completed\";\n validTrades: number;\n invalidTrades: number;\n}\n\n/**\n * Trade processing result\n */\nexport interface TradeProcessingResult {\n trades: Trade[];\n totalRows: number;\n validTrades: number;\n invalidTrades: number;\n errors: ProcessingError[];\n warnings: string[];\n stats: {\n processingTimeMs: number;\n strategies: string[];\n dateRange: { start: Date | null; end: Date | null };\n totalPL: number;\n };\n}\n\n/**\n * Trade processor class\n */\nexport class TradeProcessor {\n private config: Required<TradeProcessingConfig>;\n\n constructor(config: TradeProcessingConfig = {}) {\n this.config = {\n maxTrades: 50000,\n strictValidation: false,\n progressCallback: () => {},\n ...config,\n };\n }\n\n /**\n * Process trade log file\n */\n async processFile(file: File): Promise<TradeProcessingResult> {\n const startTime = Date.now();\n const errors: ProcessingError[] = [];\n const warnings: string[] = [];\n\n try {\n // Validate file\n const fileValidation = CSVParser.validateCSVFile(file);\n if (!fileValidation.valid) {\n throw new Error(fileValidation.error);\n }\n\n // Configure CSV parser\n const csvParser = new CSVParser({\n maxRows: this.config.maxTrades,\n progressCallback: (progress, rowsProcessed) => {\n this.config.progressCallback({\n stage: \"parsing\",\n progress,\n rowsProcessed,\n totalRows: 0,\n errors: 0,\n validTrades: 0,\n invalidTrades: 0,\n });\n },\n });\n\n // Parse CSV with validation\n const parseResult = await csvParser.parseFileObject(\n file,\n (row, rowIndex) => this.validateRawTradeData(row, rowIndex),\n (progress) => {\n this.config.progressCallback({\n ...progress,\n validTrades: 0,\n invalidTrades: 0,\n });\n },\n );\n\n // Collect parsing errors\n errors.push(...parseResult.errors);\n warnings.push(...parseResult.warnings);\n\n // Check for required columns\n const normalizedHeaders = normalizeHeaders(parseResult.headers, TRADE_COLUMN_ALIASES);\n const missingColumns = findMissingHeaders(normalizedHeaders, REQUIRED_TRADE_COLUMNS);\n if (missingColumns.length > 0) {\n throw new Error(`Missing required columns: ${missingColumns.join(\", \")}`);\n }\n\n // Update progress for conversion stage\n this.config.progressCallback({\n stage: \"converting\",\n progress: 0,\n rowsProcessed: 0,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades: 0,\n invalidTrades: 0,\n });\n\n // Convert validated data to Trade objects\n const trades: Trade[] = [];\n let validTrades = 0;\n let invalidTrades = 0;\n\n for (let i = 0; i < parseResult.data.length; i++) {\n try {\n const trade = this.convertToTrade(parseResult.data[i]);\n trades.push(trade);\n validTrades++;\n } catch (error) {\n invalidTrades++;\n const errorMessage = `Trade conversion failed at row ${i + 2}: ${error instanceof Error ? error.message : String(error)}`;\n\n // Log conversion errors to console for debugging\n console.warn(`[TradeProcessor] ${errorMessage}`);\n\n const validationError: ValidationError = {\n type: \"validation\",\n message: errorMessage,\n details: { row: parseResult.data[i], rowIndex: i + 2 },\n field: \"unknown\",\n value: parseResult.data[i],\n expected: \"Valid trade data\",\n };\n errors.push(validationError);\n\n if (!this.config.strictValidation) {\n continue; // Skip invalid row in non-strict mode\n } else {\n throw error; // Fail fast in strict mode\n }\n }\n\n // Update progress\n if (i % 100 === 0 || i === parseResult.data.length - 1) {\n const progress = Math.round((i / parseResult.data.length) * 100);\n this.config.progressCallback({\n stage: \"converting\",\n progress,\n rowsProcessed: i + 1,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades,\n invalidTrades,\n });\n }\n }\n\n // Sort trades for consistent ordering (handles simultaneous trades)\n trades.sort((a, b) => {\n const dateCompare = new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();\n if (dateCompare !== 0) return dateCompare;\n\n // Secondary sort by time\n const timeCompare = a.timeOpened.localeCompare(b.timeOpened);\n if (timeCompare !== 0) return timeCompare;\n\n // Tertiary sort by funds_at_close (lower first for simultaneous trades)\n return a.fundsAtClose - b.fundsAtClose;\n });\n\n // Calculate statistics\n const processingTimeMs = Date.now() - startTime;\n const strategies = Array.from(new Set(trades.map((t) => t.strategy))).sort();\n const dates = trades.map((t) => new Date(t.dateOpened));\n const dateRange = {\n start: dates.length > 0 ? new Date(Math.min(...dates.map((d) => d.getTime()))) : null,\n end: dates.length > 0 ? new Date(Math.max(...dates.map((d) => d.getTime()))) : null,\n };\n const totalPL = trades.reduce((sum, t) => sum + t.pl, 0);\n\n // Final progress update\n this.config.progressCallback({\n stage: \"completed\",\n progress: 100,\n rowsProcessed: parseResult.data.length,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validTrades,\n invalidTrades,\n });\n\n return {\n trades,\n totalRows: parseResult.totalRows,\n validTrades,\n invalidTrades,\n errors,\n warnings,\n stats: {\n processingTimeMs,\n strategies,\n dateRange,\n totalPL,\n },\n };\n } catch (error) {\n const processingError: ProcessingError = {\n type: \"parsing\",\n message: `Trade processing failed: ${error instanceof Error ? error.message : String(error)}`,\n details: { fileName: file.name, fileSize: file.size },\n };\n\n return {\n trades: [],\n totalRows: 0,\n validTrades: 0,\n invalidTrades: 0,\n errors: [processingError, ...errors],\n warnings,\n stats: {\n processingTimeMs: Date.now() - startTime,\n strategies: [],\n dateRange: { start: null, end: null },\n totalPL: 0,\n },\n };\n }\n }\n\n /**\n * Validate raw trade data from CSV\n */\n private validateRawTradeData(\n row: Record<string, string>,\n rowIndex: number,\n ): Record<string, string> | null {\n try {\n // Apply column aliases to normalize variations\n const normalizedRow = { ...row };\n Object.entries(TRADE_COLUMN_ALIASES).forEach(([alias, canonical]) => {\n if (normalizedRow[alias] !== undefined) {\n normalizedRow[canonical] = normalizedRow[alias];\n delete normalizedRow[alias];\n }\n });\n\n // OptionOmega sometimes leaves strategy blank; default to Unknown so downstream stats still work\n if (!normalizedRow[\"Strategy\"] || normalizedRow[\"Strategy\"].trim() === \"\") {\n normalizedRow[\"Strategy\"] = \"Unknown\";\n }\n\n // Ensure required columns have values\n const requiredFields = [\"Date Opened\", \"P/L\", \"Strategy\"];\n for (const field of requiredFields) {\n if (!normalizedRow[field] || normalizedRow[field].trim() === \"\") {\n throw new Error(`Missing required field: ${field}`);\n }\n }\n\n // Set default values for missing optional fields\n if (!normalizedRow[\"Opening Commissions + Fees\"]) {\n normalizedRow[\"Opening Commissions + Fees\"] = \"0\";\n }\n if (!normalizedRow[\"Closing Commissions + Fees\"]) {\n normalizedRow[\"Closing Commissions + Fees\"] = \"0\";\n }\n if (!normalizedRow[\"Opening Short/Long Ratio\"]) {\n normalizedRow[\"Opening Short/Long Ratio\"] = \"0\";\n }\n\n const optionalNumericFieldsWithDefaultZero = [\n \"Opening VIX\",\n \"Closing VIX\",\n \"Gap\",\n \"Movement\",\n ] as const;\n optionalNumericFieldsWithDefaultZero.forEach((field) => {\n if (!normalizedRow[field] || normalizedRow[field].trim() === \"\") {\n normalizedRow[field] = \"0\";\n }\n });\n\n // Basic format validation (detailed validation happens in conversion)\n rawTradeDataSchema.parse(normalizedRow);\n\n return normalizedRow;\n } catch (error) {\n // Log validation errors to console for debugging\n console.warn(\n `[TradeProcessor] Row ${rowIndex + 2} validation failed:`,\n error instanceof Error ? error.message : error,\n );\n // Return null for invalid rows - they'll be counted as invalid\n return null;\n }\n }\n\n /**\n * Parse a YYYY-MM-DD date string preserving the calendar date.\n *\n * Option Omega exports dates in Eastern time. JavaScript's new Date('YYYY-MM-DD')\n * parses as UTC midnight, which when converted to local time can shift to the\n * previous day (e.g., Dec 11 UTC → Dec 10 7pm EST).\n *\n * This method creates a Date representing midnight local time on the specified\n * calendar date, so Dec 11 in the CSV becomes Dec 11 in the app regardless of timezone.\n */\n private parseDatePreservingCalendarDay(dateStr: string): Date {\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (match) {\n const [, year, month, day] = match;\n // Create date at midnight local time - this preserves the calendar date\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));\n }\n // Fall back to default parsing for other formats\n return new Date(dateStr);\n }\n\n /**\n * Convert validated CSV row to Trade object\n */\n private convertToTrade(rawData: Record<string, string>): Trade {\n try {\n // Parse dates preserving calendar day\n const dateOpened = this.parseDatePreservingCalendarDay(rawData[\"Date Opened\"]);\n if (isNaN(dateOpened.getTime())) {\n throw new Error(`Invalid Date Opened: ${rawData[\"Date Opened\"]}`);\n }\n\n const dateClosed = rawData[\"Date Closed\"]\n ? this.parseDatePreservingCalendarDay(rawData[\"Date Closed\"])\n : undefined;\n if (dateClosed && isNaN(dateClosed.getTime())) {\n throw new Error(`Invalid Date Closed: ${rawData[\"Date Closed\"]}`);\n }\n\n // Normalize strategy name (handle empty strings)\n const strategy = (rawData[\"Strategy\"] || \"\").trim() || \"Unknown\";\n\n // Parse numeric values with error handling and NaN handling\n const parseNumber = (\n value: string | undefined,\n fieldName: string,\n defaultValue?: number,\n ): number => {\n if (!value || value.trim() === \"\" || value.toLowerCase() === \"nan\") {\n if (defaultValue !== undefined) return defaultValue;\n throw new Error(`Missing required numeric field: ${fieldName}`);\n }\n\n // Remove currency symbols and commas\n const cleaned = value.replace(/[$,]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n\n if (isNaN(parsed)) {\n if (defaultValue !== undefined) return defaultValue;\n throw new Error(`Invalid numeric value for ${fieldName}: ${value}`);\n }\n\n return parsed;\n };\n\n // Build trade object\n const rawPremiumString = (rawData[\"Premium\"] || \"\").replace(/[$,]/g, \"\").trim();\n const premiumPrecision: Trade[\"premiumPrecision\"] =\n rawPremiumString && !rawPremiumString.includes(\".\") ? \"cents\" : \"dollars\";\n\n const trade: Trade = {\n dateOpened,\n timeOpened: rawData[\"Time Opened\"] || \"00:00:00\",\n openingPrice: parseNumber(rawData[\"Opening Price\"], \"Opening Price\"),\n legs: rawData[\"Legs\"] || \"\",\n premium: parseNumber(rawData[\"Premium\"], \"Premium\"),\n premiumPrecision,\n closingPrice: rawData[\"Closing Price\"]\n ? parseNumber(rawData[\"Closing Price\"], \"Closing Price\")\n : undefined,\n dateClosed,\n timeClosed: rawData[\"Time Closed\"] || undefined,\n avgClosingCost: rawData[\"Avg. Closing Cost\"]\n ? parseNumber(rawData[\"Avg. Closing Cost\"], \"Avg. Closing Cost\")\n : undefined,\n reasonForClose: rawData[\"Reason For Close\"] || undefined,\n pl: parseNumber(rawData[\"P/L\"], \"P/L\"),\n numContracts: Math.round(parseNumber(rawData[\"No. of Contracts\"], \"No. of Contracts\")),\n fundsAtClose: parseNumber(rawData[\"Funds at Close\"], \"Funds at Close\"),\n marginReq: parseNumber(rawData[\"Margin Req.\"], \"Margin Req.\"),\n strategy,\n openingCommissionsFees: parseNumber(\n rawData[\"Opening Commissions + Fees\"],\n \"Opening Commissions\",\n 0,\n ),\n closingCommissionsFees: parseNumber(\n rawData[\"Closing Commissions + Fees\"],\n \"Closing Commissions\",\n 0,\n ),\n openingShortLongRatio: parseNumber(\n rawData[\"Opening Short/Long Ratio\"],\n \"Opening Short/Long Ratio\",\n 0,\n ),\n closingShortLongRatio: rawData[\"Closing Short/Long Ratio\"]\n ? parseNumber(rawData[\"Closing Short/Long Ratio\"], \"Closing Short/Long Ratio\")\n : undefined,\n openingVix: rawData[\"Opening VIX\"]\n ? parseNumber(rawData[\"Opening VIX\"], \"Opening VIX\")\n : undefined,\n closingVix: rawData[\"Closing VIX\"]\n ? parseNumber(rawData[\"Closing VIX\"], \"Closing VIX\")\n : undefined,\n gap: rawData[\"Gap\"] ? parseNumber(rawData[\"Gap\"], \"Gap\") : undefined,\n movement: rawData[\"Movement\"] ? parseNumber(rawData[\"Movement\"], \"Movement\") : undefined,\n maxProfit: rawData[\"Max Profit\"]\n ? parseNumber(rawData[\"Max Profit\"], \"Max Profit\")\n : undefined,\n maxLoss: rawData[\"Max Loss\"] ? parseNumber(rawData[\"Max Loss\"], \"Max Loss\") : undefined,\n };\n\n // Extract custom fields (columns not in KNOWN_TRADE_COLUMNS)\n const customFields: Record<string, number | string> = {};\n for (const [key, value] of Object.entries(rawData)) {\n if (!KNOWN_TRADE_COLUMNS.has(key) && value !== undefined && value.trim() !== \"\") {\n // Auto-detect type: try to parse as number\n const cleaned = value.replace(/[$,%]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n if (!isNaN(parsed) && isFinite(parsed)) {\n customFields[key] = parsed;\n } else {\n customFields[key] = value.trim();\n }\n }\n }\n\n // Only add customFields if there are any\n if (Object.keys(customFields).length > 0) {\n trade.customFields = customFields;\n }\n\n // Final validation with Zod schema\n const validatedTrade = tradeSchema.parse(trade);\n return validatedTrade;\n } catch (error) {\n throw new Error(\n `Trade conversion failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Process CSV content directly (for testing)\n */\n async processCSVContent(content: string): Promise<TradeProcessingResult> {\n // Create a mock File object for testing\n const blob = new Blob([content], { type: \"text/csv\" });\n const file = new File([blob], \"test.csv\", { type: \"text/csv\" });\n return this.processFile(file);\n }\n}\n"],"mappings":";;;;;;;;;;AA6FO,IAAM,uBAAuB;AAAA,EAClC,eAAe;AAAA,EACf,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,OAAO;AAAA,EACP,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,UAAU;AAAA,EACV,8BAA8B;AAAA,EAC9B,8BAA8B;AAAA,EAC9B,4BAA4B;AAAA,EAC5B,4BAA4B;AAAA,EAC5B,eAAe;AAAA,EACf,eAAe;AAAA,EACf,KAAK;AAAA,EACL,UAAU;AAAA,EACV,cAAc;AAAA,EACd,YAAY;AACd;AAKO,IAAM,uBAAuB;AAAA,EAClC,wBAAwB;AAAA,EACxB,8BAA8B;AAAA,EAC9B,wBAAwB;AAAA,EACxB,8BAA8B;AAAA,EAC9B,SAAS;AAAA;AACX;AAKO,IAAM,yBAAyB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACjIA,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC,GAAG,OAAO,KAAK,oBAAoB;AAAA,EACnC,GAAG,OAAO,KAAK,oBAAoB;AACrC,CAAC;AAyCM,IAAM,iBAAN,MAAqB;AAAA,EAClB;AAAA,EAER,YAAY,SAAgC,CAAC,GAAG;AAC9C,SAAK,SAAS;AAAA,MACZ,WAAW;AAAA,MACX,kBAAkB;AAAA,MAClB,kBAAkB,MAAM;AAAA,MAAC;AAAA,MACzB,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,MAA4C;AAC5D,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,SAA4B,CAAC;AACnC,UAAM,WAAqB,CAAC;AAE5B,QAAI;AAEF,YAAM,iBAAiB,UAAU,gBAAgB,IAAI;AACrD,UAAI,CAAC,eAAe,OAAO;AACzB,cAAM,IAAI,MAAM,eAAe,KAAK;AAAA,MACtC;AAGA,YAAM,YAAY,IAAI,UAAU;AAAA,QAC9B,SAAS,KAAK,OAAO;AAAA,QACrB,kBAAkB,CAAC,UAAU,kBAAkB;AAC7C,eAAK,OAAO,iBAAiB;AAAA,YAC3B,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA,WAAW;AAAA,YACX,QAAQ;AAAA,YACR,aAAa;AAAA,YACb,eAAe;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAGD,YAAM,cAAc,MAAM,UAAU;AAAA,QAClC;AAAA,QACA,CAAC,KAAK,aAAa,KAAK,qBAAqB,KAAK,QAAQ;AAAA,QAC1D,CAAC,aAAa;AACZ,eAAK,OAAO,iBAAiB;AAAA,YAC3B,GAAG;AAAA,YACH,aAAa;AAAA,YACb,eAAe;AAAA,UACjB,CAAC;AAAA,QACH;AAAA,MACF;AAGA,aAAO,KAAK,GAAG,YAAY,MAAM;AACjC,eAAS,KAAK,GAAG,YAAY,QAAQ;AAGrC,YAAM,oBAAoB,iBAAiB,YAAY,SAAS,oBAAoB;AACpF,YAAM,iBAAiB,mBAAmB,mBAAmB,sBAAsB;AACnF,UAAI,eAAe,SAAS,GAAG;AAC7B,cAAM,IAAI,MAAM,6BAA6B,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,MAC1E;AAGA,WAAK,OAAO,iBAAiB;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU;AAAA,QACV,eAAe;AAAA,QACf,WAAW,YAAY,KAAK;AAAA,QAC5B,QAAQ,OAAO;AAAA,QACf,aAAa;AAAA,QACb,eAAe;AAAA,MACjB,CAAC;AAGD,YAAM,SAAkB,CAAC;AACzB,UAAI,cAAc;AAClB,UAAI,gBAAgB;AAEpB,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK,QAAQ,KAAK;AAChD,YAAI;AACF,gBAAM,QAAQ,KAAK,eAAe,YAAY,KAAK,CAAC,CAAC;AACrD,iBAAO,KAAK,KAAK;AACjB;AAAA,QACF,SAAS,OAAO;AACd;AACA,gBAAM,eAAe,kCAAkC,IAAI,CAAC,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAGvH,kBAAQ,KAAK,oBAAoB,YAAY,EAAE;AAE/C,gBAAM,kBAAmC;AAAA,YACvC,MAAM;AAAA,YACN,SAAS;AAAA,YACT,SAAS,EAAE,KAAK,YAAY,KAAK,CAAC,GAAG,UAAU,IAAI,EAAE;AAAA,YACrD,OAAO;AAAA,YACP,OAAO,YAAY,KAAK,CAAC;AAAA,YACzB,UAAU;AAAA,UACZ;AACA,iBAAO,KAAK,eAAe;AAE3B,cAAI,CAAC,KAAK,OAAO,kBAAkB;AACjC;AAAA,UACF,OAAO;AACL,kBAAM;AAAA,UACR;AAAA,QACF;AAGA,YAAI,IAAI,QAAQ,KAAK,MAAM,YAAY,KAAK,SAAS,GAAG;AACtD,gBAAM,WAAW,KAAK,MAAO,IAAI,YAAY,KAAK,SAAU,GAAG;AAC/D,eAAK,OAAO,iBAAiB;AAAA,YAC3B,OAAO;AAAA,YACP;AAAA,YACA,eAAe,IAAI;AAAA,YACnB,WAAW,YAAY,KAAK;AAAA,YAC5B,QAAQ,OAAO;AAAA,YACf;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAGA,aAAO,KAAK,CAAC,GAAG,MAAM;AACpB,cAAM,cAAc,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AACtF,YAAI,gBAAgB,EAAG,QAAO;AAG9B,cAAM,cAAc,EAAE,WAAW,cAAc,EAAE,UAAU;AAC3D,YAAI,gBAAgB,EAAG,QAAO;AAG9B,eAAO,EAAE,eAAe,EAAE;AAAA,MAC5B,CAAC;AAGD,YAAM,mBAAmB,KAAK,IAAI,IAAI;AACtC,YAAM,aAAa,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,KAAK;AAC3E,YAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC;AACtD,YAAM,YAAY;AAAA,QAChB,OAAO,MAAM,SAAS,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI;AAAA,QACjF,KAAK,MAAM,SAAS,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI;AAAA,MACjF;AACA,YAAM,UAAU,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,IAAI,CAAC;AAGvD,WAAK,OAAO,iBAAiB;AAAA,QAC3B,OAAO;AAAA,QACP,UAAU;AAAA,QACV,eAAe,YAAY,KAAK;AAAA,QAChC,WAAW,YAAY,KAAK;AAAA,QAC5B,QAAQ,OAAO;AAAA,QACf;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL;AAAA,QACA,WAAW,YAAY;AAAA,QACvB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,YAAM,kBAAmC;AAAA,QACvC,MAAM;AAAA,QACN,SAAS,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,QAC3F,SAAS,EAAE,UAAU,KAAK,MAAM,UAAU,KAAK,KAAK;AAAA,MACtD;AAEA,aAAO;AAAA,QACL,QAAQ,CAAC;AAAA,QACT,WAAW;AAAA,QACX,aAAa;AAAA,QACb,eAAe;AAAA,QACf,QAAQ,CAAC,iBAAiB,GAAG,MAAM;AAAA,QACnC;AAAA,QACA,OAAO;AAAA,UACL,kBAAkB,KAAK,IAAI,IAAI;AAAA,UAC/B,YAAY,CAAC;AAAA,UACb,WAAW,EAAE,OAAO,MAAM,KAAK,KAAK;AAAA,UACpC,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,qBACN,KACA,UAC+B;AAC/B,QAAI;AAEF,YAAM,gBAAgB,EAAE,GAAG,IAAI;AAC/B,aAAO,QAAQ,oBAAoB,EAAE,QAAQ,CAAC,CAAC,OAAO,SAAS,MAAM;AACnE,YAAI,cAAc,KAAK,MAAM,QAAW;AACtC,wBAAc,SAAS,IAAI,cAAc,KAAK;AAC9C,iBAAO,cAAc,KAAK;AAAA,QAC5B;AAAA,MACF,CAAC;AAGD,UAAI,CAAC,cAAc,UAAU,KAAK,cAAc,UAAU,EAAE,KAAK,MAAM,IAAI;AACzE,sBAAc,UAAU,IAAI;AAAA,MAC9B;AAGA,YAAM,iBAAiB,CAAC,eAAe,OAAO,UAAU;AACxD,iBAAW,SAAS,gBAAgB;AAClC,YAAI,CAAC,cAAc,KAAK,KAAK,cAAc,KAAK,EAAE,KAAK,MAAM,IAAI;AAC/D,gBAAM,IAAI,MAAM,2BAA2B,KAAK,EAAE;AAAA,QACpD;AAAA,MACF;AAGA,UAAI,CAAC,cAAc,4BAA4B,GAAG;AAChD,sBAAc,4BAA4B,IAAI;AAAA,MAChD;AACA,UAAI,CAAC,cAAc,4BAA4B,GAAG;AAChD,sBAAc,4BAA4B,IAAI;AAAA,MAChD;AACA,UAAI,CAAC,cAAc,0BAA0B,GAAG;AAC9C,sBAAc,0BAA0B,IAAI;AAAA,MAC9C;AAEA,YAAM,uCAAuC;AAAA,QAC3C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,2CAAqC,QAAQ,CAAC,UAAU;AACtD,YAAI,CAAC,cAAc,KAAK,KAAK,cAAc,KAAK,EAAE,KAAK,MAAM,IAAI;AAC/D,wBAAc,KAAK,IAAI;AAAA,QACzB;AAAA,MACF,CAAC;AAGD,yBAAmB,MAAM,aAAa;AAEtC,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,cAAQ;AAAA,QACN,wBAAwB,WAAW,CAAC;AAAA,QACpC,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAC3C;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,+BAA+B,SAAuB;AAC5D,UAAM,QAAQ,QAAQ,MAAM,2BAA2B;AACvD,QAAI,OAAO;AACT,YAAM,CAAC,EAAE,MAAM,OAAO,GAAG,IAAI;AAE7B,aAAO,IAAI,KAAK,SAAS,IAAI,GAAG,SAAS,KAAK,IAAI,GAAG,SAAS,GAAG,CAAC;AAAA,IACpE;AAEA,WAAO,IAAI,KAAK,OAAO;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,SAAwC;AAC7D,QAAI;AAEF,YAAM,aAAa,KAAK,+BAA+B,QAAQ,aAAa,CAAC;AAC7E,UAAI,MAAM,WAAW,QAAQ,CAAC,GAAG;AAC/B,cAAM,IAAI,MAAM,wBAAwB,QAAQ,aAAa,CAAC,EAAE;AAAA,MAClE;AAEA,YAAM,aAAa,QAAQ,aAAa,IACpC,KAAK,+BAA+B,QAAQ,aAAa,CAAC,IAC1D;AACJ,UAAI,cAAc,MAAM,WAAW,QAAQ,CAAC,GAAG;AAC7C,cAAM,IAAI,MAAM,wBAAwB,QAAQ,aAAa,CAAC,EAAE;AAAA,MAClE;AAGA,YAAM,YAAY,QAAQ,UAAU,KAAK,IAAI,KAAK,KAAK;AAGvD,YAAM,cAAc,CAClB,OACA,WACA,iBACW;AACX,YAAI,CAAC,SAAS,MAAM,KAAK,MAAM,MAAM,MAAM,YAAY,MAAM,OAAO;AAClE,cAAI,iBAAiB,OAAW,QAAO;AACvC,gBAAM,IAAI,MAAM,mCAAmC,SAAS,EAAE;AAAA,QAChE;AAGA,cAAM,UAAU,MAAM,QAAQ,SAAS,EAAE,EAAE,KAAK;AAChD,cAAM,SAAS,WAAW,OAAO;AAEjC,YAAI,MAAM,MAAM,GAAG;AACjB,cAAI,iBAAiB,OAAW,QAAO;AACvC,gBAAM,IAAI,MAAM,6BAA6B,SAAS,KAAK,KAAK,EAAE;AAAA,QACpE;AAEA,eAAO;AAAA,MACT;AAGA,YAAM,oBAAoB,QAAQ,SAAS,KAAK,IAAI,QAAQ,SAAS,EAAE,EAAE,KAAK;AAC9E,YAAM,mBACJ,oBAAoB,CAAC,iBAAiB,SAAS,GAAG,IAAI,UAAU;AAElE,YAAM,QAAe;AAAA,QACnB;AAAA,QACA,YAAY,QAAQ,aAAa,KAAK;AAAA,QACtC,cAAc,YAAY,QAAQ,eAAe,GAAG,eAAe;AAAA,QACnE,MAAM,QAAQ,MAAM,KAAK;AAAA,QACzB,SAAS,YAAY,QAAQ,SAAS,GAAG,SAAS;AAAA,QAClD;AAAA,QACA,cAAc,QAAQ,eAAe,IACjC,YAAY,QAAQ,eAAe,GAAG,eAAe,IACrD;AAAA,QACJ;AAAA,QACA,YAAY,QAAQ,aAAa,KAAK;AAAA,QACtC,gBAAgB,QAAQ,mBAAmB,IACvC,YAAY,QAAQ,mBAAmB,GAAG,mBAAmB,IAC7D;AAAA,QACJ,gBAAgB,QAAQ,kBAAkB,KAAK;AAAA,QAC/C,IAAI,YAAY,QAAQ,KAAK,GAAG,KAAK;AAAA,QACrC,cAAc,KAAK,MAAM,YAAY,QAAQ,kBAAkB,GAAG,kBAAkB,CAAC;AAAA,QACrF,cAAc,YAAY,QAAQ,gBAAgB,GAAG,gBAAgB;AAAA,QACrE,WAAW,YAAY,QAAQ,aAAa,GAAG,aAAa;AAAA,QAC5D;AAAA,QACA,wBAAwB;AAAA,UACtB,QAAQ,4BAA4B;AAAA,UACpC;AAAA,UACA;AAAA,QACF;AAAA,QACA,wBAAwB;AAAA,UACtB,QAAQ,4BAA4B;AAAA,UACpC;AAAA,UACA;AAAA,QACF;AAAA,QACA,uBAAuB;AAAA,UACrB,QAAQ,0BAA0B;AAAA,UAClC;AAAA,UACA;AAAA,QACF;AAAA,QACA,uBAAuB,QAAQ,0BAA0B,IACrD,YAAY,QAAQ,0BAA0B,GAAG,0BAA0B,IAC3E;AAAA,QACJ,YAAY,QAAQ,aAAa,IAC7B,YAAY,QAAQ,aAAa,GAAG,aAAa,IACjD;AAAA,QACJ,YAAY,QAAQ,aAAa,IAC7B,YAAY,QAAQ,aAAa,GAAG,aAAa,IACjD;AAAA,QACJ,KAAK,QAAQ,KAAK,IAAI,YAAY,QAAQ,KAAK,GAAG,KAAK,IAAI;AAAA,QAC3D,UAAU,QAAQ,UAAU,IAAI,YAAY,QAAQ,UAAU,GAAG,UAAU,IAAI;AAAA,QAC/E,WAAW,QAAQ,YAAY,IAC3B,YAAY,QAAQ,YAAY,GAAG,YAAY,IAC/C;AAAA,QACJ,SAAS,QAAQ,UAAU,IAAI,YAAY,QAAQ,UAAU,GAAG,UAAU,IAAI;AAAA,MAChF;AAGA,YAAM,eAAgD,CAAC;AACvD,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,YAAI,CAAC,oBAAoB,IAAI,GAAG,KAAK,UAAU,UAAa,MAAM,KAAK,MAAM,IAAI;AAE/E,gBAAM,UAAU,MAAM,QAAQ,UAAU,EAAE,EAAE,KAAK;AACjD,gBAAM,SAAS,WAAW,OAAO;AACjC,cAAI,CAAC,MAAM,MAAM,KAAK,SAAS,MAAM,GAAG;AACtC,yBAAa,GAAG,IAAI;AAAA,UACtB,OAAO;AACL,yBAAa,GAAG,IAAI,MAAM,KAAK;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAGA,UAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,cAAM,eAAe;AAAA,MACvB;AAGA,YAAM,iBAAiB,YAAY,MAAM,KAAK;AAC9C,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,SAAiD;AAEvE,UAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,WAAW,CAAC;AACrD,UAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9D,WAAO,KAAK,YAAY,IAAI;AAAA,EAC9B;AACF;","names":[]}
@@ -4,14 +4,14 @@ import {
4
4
  dailyLogEntrySchema,
5
5
  findMissingHeaders,
6
6
  rawDailyLogDataSchema
7
- } from "./chunk-5EBXHT6C.js";
7
+ } from "./chunk-SY6GDNVI.js";
8
8
 
9
9
  // ../lib/models/daily-log.ts
10
10
  var DAILY_LOG_COLUMN_MAPPING = {
11
- "Date": "date",
11
+ Date: "date",
12
12
  "Net Liquidity": "netLiquidity",
13
13
  "Current Funds": "currentFunds",
14
- "Withdrawn": "withdrawn",
14
+ Withdrawn: "withdrawn",
15
15
  "Trading Funds": "tradingFunds",
16
16
  "P/L": "dailyPl",
17
17
  "P/L %": "dailyPlPct",
@@ -211,7 +211,10 @@ var DailyLogProcessor = class {
211
211
  rawDailyLogDataSchema.parse(normalizedRow);
212
212
  return normalizedRow;
213
213
  } catch (error) {
214
- console.warn(`[DailyLogProcessor] Row ${rowIndex + 2} validation failed:`, error instanceof Error ? error.message : error);
214
+ console.warn(
215
+ `[DailyLogProcessor] Row ${rowIndex + 2} validation failed:`,
216
+ error instanceof Error ? error.message : error
217
+ );
215
218
  return null;
216
219
  }
217
220
  }
@@ -277,7 +280,9 @@ var DailyLogProcessor = class {
277
280
  const validatedEntry = dailyLogEntrySchema.parse(entry);
278
281
  return validatedEntry;
279
282
  } catch (error) {
280
- throw new Error(`Daily log entry conversion failed: ${error instanceof Error ? error.message : String(error)}`);
283
+ throw new Error(
284
+ `Daily log entry conversion failed: ${error instanceof Error ? error.message : String(error)}`
285
+ );
281
286
  }
282
287
  }
283
288
  /**
@@ -294,13 +299,17 @@ var DailyLogProcessor = class {
294
299
  static validateDataConsistency(entries) {
295
300
  const warnings = [];
296
301
  if (entries.length === 0) return warnings;
297
- const sortedEntries = [...entries].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
302
+ const sortedEntries = [...entries].sort(
303
+ (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
304
+ );
298
305
  for (let i = 1; i < sortedEntries.length; i++) {
299
306
  const prevDate = new Date(sortedEntries[i - 1].date);
300
307
  const currentDate = new Date(sortedEntries[i].date);
301
308
  const daysDiff = (currentDate.getTime() - prevDate.getTime()) / (1e3 * 60 * 60 * 24);
302
309
  if (daysDiff > 7) {
303
- warnings.push(`Large date gap detected: ${daysDiff.toFixed(0)} days between ${prevDate.toISOString().split("T")[0]} and ${currentDate.toISOString().split("T")[0]}`);
310
+ warnings.push(
311
+ `Large date gap detected: ${daysDiff.toFixed(0)} days between ${prevDate.toISOString().split("T")[0]} and ${currentDate.toISOString().split("T")[0]}`
312
+ );
304
313
  }
305
314
  }
306
315
  const negativeEntries = sortedEntries.filter((entry) => entry.netLiquidity < 0);
@@ -318,4 +327,4 @@ var DailyLogProcessor = class {
318
327
  export {
319
328
  DailyLogProcessor
320
329
  };
321
- //# sourceMappingURL=chunk-WA5AAPCH.js.map
330
+ //# sourceMappingURL=chunk-A3UKD64A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../lib/models/daily-log.ts","../../lib/processing/daily-log-processor.ts"],"sourcesContent":["/**\n * Daily log model based on legacy Python DailyLogEntry class\n * Represents daily portfolio performance data from OptionOmega\n */\nexport interface DailyLogEntry {\n date: Date;\n netLiquidity: number;\n currentFunds: number;\n withdrawn: number;\n tradingFunds: number;\n dailyPl: number; // P/L for the day\n dailyPlPct: number; // P/L percentage\n drawdownPct: number; // Drawdown percentage\n blockId?: string; // Optional block ID for linking to trades\n\n /**\n * Custom fields from extra columns in the daily log CSV\n * Keys are the original column names, values are auto-detected as number or string\n * These fields can be joined to trades by date for analysis (e.g., dayOpenVix, spyOpen)\n */\n customFields?: Record<string, number | string>;\n}\n\n/**\n * Raw daily log data as it comes from CSV before processing\n */\nexport interface RawDailyLogData {\n Date: string;\n \"Net Liquidity\": string;\n \"Current Funds\": string;\n Withdrawn: string;\n \"Trading Funds\": string;\n \"P/L\": string;\n \"P/L %\": string;\n \"Drawdown %\": string;\n}\n\n/**\n * Processed daily log collection with metadata\n */\nexport interface DailyLog {\n entries: DailyLogEntry[];\n uploadTimestamp: Date;\n filename: string;\n totalEntries: number;\n dateRangeStart: Date;\n dateRangeEnd: Date;\n finalPortfolioValue: number;\n maxDrawdown: number;\n}\n\n/**\n * Column mapping from CSV headers to DailyLogEntry interface properties\n */\nexport const DAILY_LOG_COLUMN_MAPPING = {\n Date: \"date\",\n \"Net Liquidity\": \"netLiquidity\",\n \"Current Funds\": \"currentFunds\",\n Withdrawn: \"withdrawn\",\n \"Trading Funds\": \"tradingFunds\",\n \"P/L\": \"dailyPl\",\n \"P/L %\": \"dailyPlPct\",\n \"Drawdown %\": \"drawdownPct\",\n} as const;\n\n/**\n * Required columns for daily log processing\n */\nexport const REQUIRED_DAILY_LOG_COLUMNS = [\n \"Date\",\n \"Net Liquidity\",\n \"Current Funds\",\n \"Trading Funds\",\n \"P/L\",\n \"P/L %\",\n \"Drawdown %\",\n] as const;\n","/**\n * Daily Log Processor\n *\n * Handles parsing and processing of daily log CSV files from OptionOmega.\n * Converts raw CSV data to validated DailyLogEntry objects.\n */\n\nimport {\n type DailyLogEntry,\n REQUIRED_DAILY_LOG_COLUMNS,\n DAILY_LOG_COLUMN_MAPPING,\n} from \"../models/daily-log.ts\";\n\n/**\n * Set of known daily log column names (canonical names from DAILY_LOG_COLUMN_MAPPING)\n * Used to identify custom columns that should be preserved\n */\nconst KNOWN_DAILY_LOG_COLUMNS = new Set([\n ...Object.keys(DAILY_LOG_COLUMN_MAPPING),\n \"Withdrawn\", // Optional column that may not be in REQUIRED but is known\n]);\nimport type { ValidationError, ProcessingError } from \"../models/index.ts\";\nimport { rawDailyLogDataSchema, dailyLogEntrySchema } from \"../models/validators.ts\";\nimport { CSVParser, type ParseProgress } from \"./csv-parser.ts\";\nimport { findMissingHeaders } from \"../utils/csv-headers.ts\";\n// import { CSVParseResult } from './csv-parser.ts'\n\n/**\n * Daily log processing configuration\n */\nexport interface DailyLogProcessingConfig {\n maxEntries?: number;\n strictValidation?: boolean;\n progressCallback?: (progress: DailyLogProcessingProgress) => void;\n}\n\n/**\n * Daily log processing progress\n */\nexport interface DailyLogProcessingProgress extends ParseProgress {\n stage: \"reading\" | \"parsing\" | \"validating\" | \"converting\" | \"completed\";\n validEntries: number;\n invalidEntries: number;\n}\n\n/**\n * Daily log processing result\n */\nexport interface DailyLogProcessingResult {\n entries: DailyLogEntry[];\n totalRows: number;\n validEntries: number;\n invalidEntries: number;\n errors: ProcessingError[];\n warnings: string[];\n stats: {\n processingTimeMs: number;\n dateRange: { start: Date | null; end: Date | null };\n finalPortfolioValue: number;\n maxDrawdown: number;\n totalPL: number;\n };\n}\n\n/**\n * Daily log processor class\n */\nexport class DailyLogProcessor {\n private config: Required<DailyLogProcessingConfig>;\n\n constructor(config: DailyLogProcessingConfig = {}) {\n this.config = {\n maxEntries: 10000, // Reasonable limit for daily entries\n strictValidation: false,\n progressCallback: () => {},\n ...config,\n };\n }\n\n /**\n * Process daily log file\n */\n async processFile(file: File, blockId?: string): Promise<DailyLogProcessingResult> {\n const startTime = Date.now();\n const errors: ProcessingError[] = [];\n const warnings: string[] = [];\n\n try {\n // Validate file\n const fileValidation = CSVParser.validateCSVFile(file);\n if (!fileValidation.valid) {\n throw new Error(fileValidation.error);\n }\n\n // Configure CSV parser\n const csvParser = new CSVParser({\n maxRows: this.config.maxEntries,\n progressCallback: (progress, rowsProcessed) => {\n this.config.progressCallback({\n stage: \"parsing\",\n progress,\n rowsProcessed,\n totalRows: 0,\n errors: 0,\n validEntries: 0,\n invalidEntries: 0,\n });\n },\n });\n\n // Parse CSV with validation\n const parseResult = await csvParser.parseFileObject(\n file,\n (row, rowIndex) => this.validateRawDailyLogData(row, rowIndex),\n (progress) => {\n this.config.progressCallback({\n ...progress,\n validEntries: 0,\n invalidEntries: 0,\n });\n },\n );\n\n // Collect parsing errors\n errors.push(...parseResult.errors);\n warnings.push(...parseResult.warnings);\n\n // Check for required columns\n const missingColumns = findMissingHeaders(parseResult.headers, REQUIRED_DAILY_LOG_COLUMNS);\n if (missingColumns.length > 0) {\n throw new Error(`Missing required columns: ${missingColumns.join(\", \")}`);\n }\n\n // Update progress for conversion stage\n this.config.progressCallback({\n stage: \"converting\",\n progress: 0,\n rowsProcessed: 0,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries: 0,\n invalidEntries: 0,\n });\n\n // Convert validated data to DailyLogEntry objects\n const entries: DailyLogEntry[] = [];\n let validEntries = 0;\n let invalidEntries = 0;\n\n for (let i = 0; i < parseResult.data.length; i++) {\n try {\n const entry = this.convertToDailyLogEntry(parseResult.data[i], blockId);\n entries.push(entry);\n validEntries++;\n } catch (error) {\n invalidEntries++;\n const errorMessage = `Daily log entry conversion failed at row ${i + 2}: ${error instanceof Error ? error.message : String(error)}`;\n\n // Log conversion errors to console for debugging\n console.warn(`[DailyLogProcessor] ${errorMessage}`);\n\n const validationError: ValidationError = {\n type: \"validation\",\n message: errorMessage,\n details: { row: parseResult.data[i], rowIndex: i + 2 },\n field: \"unknown\",\n value: parseResult.data[i],\n expected: \"Valid daily log entry data\",\n };\n errors.push(validationError);\n\n if (!this.config.strictValidation) {\n continue; // Skip invalid row in non-strict mode\n } else {\n throw error; // Fail fast in strict mode\n }\n }\n\n // Update progress\n if (i % 50 === 0 || i === parseResult.data.length - 1) {\n const progress = Math.round((i / parseResult.data.length) * 100);\n this.config.progressCallback({\n stage: \"converting\",\n progress,\n rowsProcessed: i + 1,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries,\n invalidEntries,\n });\n }\n }\n\n // Sort entries by date\n entries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());\n\n // Calculate statistics\n const processingTimeMs = Date.now() - startTime;\n const dates = entries.map((e) => new Date(e.date));\n const dateRange = {\n start: dates.length > 0 ? new Date(Math.min(...dates.map((d) => d.getTime()))) : null,\n end: dates.length > 0 ? new Date(Math.max(...dates.map((d) => d.getTime()))) : null,\n };\n const finalPortfolioValue = entries.length > 0 ? entries[entries.length - 1].netLiquidity : 0;\n const maxDrawdown = entries.length > 0 ? Math.min(...entries.map((e) => e.drawdownPct)) : 0;\n const totalPL = entries.reduce((sum, e) => sum + e.dailyPl, 0);\n\n // Final progress update\n this.config.progressCallback({\n stage: \"completed\",\n progress: 100,\n rowsProcessed: parseResult.data.length,\n totalRows: parseResult.data.length,\n errors: errors.length,\n validEntries,\n invalidEntries,\n });\n\n return {\n entries,\n totalRows: parseResult.totalRows,\n validEntries,\n invalidEntries,\n errors,\n warnings,\n stats: {\n processingTimeMs,\n dateRange,\n finalPortfolioValue,\n maxDrawdown,\n totalPL,\n },\n };\n } catch (error) {\n const processingError: ProcessingError = {\n type: \"parsing\",\n message: `Daily log processing failed: ${error instanceof Error ? error.message : String(error)}`,\n details: { fileName: file.name, fileSize: file.size },\n };\n\n return {\n entries: [],\n totalRows: 0,\n validEntries: 0,\n invalidEntries: 0,\n errors: [processingError, ...errors],\n warnings,\n stats: {\n processingTimeMs: Date.now() - startTime,\n dateRange: { start: null, end: null },\n finalPortfolioValue: 0,\n maxDrawdown: 0,\n totalPL: 0,\n },\n };\n }\n }\n\n /**\n * Validate raw daily log data from CSV\n */\n private validateRawDailyLogData(\n row: Record<string, string>,\n rowIndex: number,\n ): Record<string, string> | null {\n try {\n // Set default values for missing optional fields\n const normalizedRow = { ...row };\n if (!normalizedRow[\"Withdrawn\"]) {\n normalizedRow[\"Withdrawn\"] = \"0\";\n }\n\n // Ensure required columns have values\n for (const field of REQUIRED_DAILY_LOG_COLUMNS) {\n if (!normalizedRow[field] || normalizedRow[field].trim() === \"\") {\n throw new Error(`Missing required field: ${field}`);\n }\n }\n\n // Basic format validation (detailed validation happens in conversion)\n rawDailyLogDataSchema.parse(normalizedRow);\n\n return normalizedRow;\n } catch (error) {\n // Log validation errors to console for debugging\n console.warn(\n `[DailyLogProcessor] Row ${rowIndex + 2} validation failed:`,\n error instanceof Error ? error.message : error,\n );\n // Return null for invalid rows - they'll be counted as invalid\n return null;\n }\n }\n\n /**\n * Parse a YYYY-MM-DD date string preserving the calendar date.\n * Same approach as trade-processor.ts for consistency.\n */\n private parseDatePreservingCalendarDay(dateStr: string): Date {\n const match = dateStr.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n if (match) {\n const [, year, month, day] = match;\n // Create date at midnight local time - this preserves the calendar date\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));\n }\n // Fall back to default parsing for other formats\n return new Date(dateStr);\n }\n\n /**\n * Convert validated CSV row to DailyLogEntry object\n */\n private convertToDailyLogEntry(rawData: Record<string, string>, blockId?: string): DailyLogEntry {\n try {\n // Parse date preserving calendar day (same as trade processor)\n const date = this.parseDatePreservingCalendarDay(rawData[\"Date\"]);\n if (isNaN(date.getTime())) {\n throw new Error(`Invalid Date: ${rawData[\"Date\"]}`);\n }\n\n // Parse numeric values with error handling\n const parseNumber = (\n value: string | undefined,\n fieldName: string,\n defaultValue?: number,\n ): number => {\n if (!value || value.trim() === \"\") {\n if (defaultValue !== undefined) return defaultValue;\n throw new Error(`Missing required numeric field: ${fieldName}`);\n }\n\n // Remove currency symbols, commas, and percentage signs\n const cleaned = value.replace(/[$,%]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n\n if (isNaN(parsed)) {\n throw new Error(`Invalid numeric value for ${fieldName}: ${value}`);\n }\n\n return parsed;\n };\n\n // Build daily log entry object\n const entry: DailyLogEntry = {\n date,\n netLiquidity: parseNumber(rawData[\"Net Liquidity\"], \"Net Liquidity\"),\n currentFunds: parseNumber(rawData[\"Current Funds\"], \"Current Funds\"),\n withdrawn: parseNumber(rawData[\"Withdrawn\"], \"Withdrawn\", 0),\n tradingFunds: parseNumber(rawData[\"Trading Funds\"], \"Trading Funds\"),\n dailyPl: parseNumber(rawData[\"P/L\"], \"P/L\"),\n dailyPlPct: parseNumber(rawData[\"P/L %\"], \"P/L %\"),\n drawdownPct: parseNumber(rawData[\"Drawdown %\"], \"Drawdown %\"),\n blockId,\n };\n\n // Keep percentage values as they are from CSV to match legacy behavior\n // Legacy Python expects percentage values (e.g., -5.55), not decimals (e.g., -0.0555)\n\n // Extract custom fields (columns not in KNOWN_DAILY_LOG_COLUMNS)\n const customFields: Record<string, number | string> = {};\n for (const [key, value] of Object.entries(rawData)) {\n if (!KNOWN_DAILY_LOG_COLUMNS.has(key) && value !== undefined && value.trim() !== \"\") {\n // Auto-detect type: try to parse as number\n const cleaned = value.replace(/[$,%]/g, \"\").trim();\n const parsed = parseFloat(cleaned);\n if (!isNaN(parsed) && isFinite(parsed)) {\n customFields[key] = parsed;\n } else {\n customFields[key] = value.trim();\n }\n }\n }\n\n // Only add customFields if there are any\n if (Object.keys(customFields).length > 0) {\n entry.customFields = customFields;\n }\n\n // Final validation with Zod schema\n const validatedEntry = dailyLogEntrySchema.parse(entry);\n return validatedEntry;\n } catch (error) {\n throw new Error(\n `Daily log entry conversion failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Process CSV content directly (for testing)\n */\n async processCSVContent(content: string, blockId?: string): Promise<DailyLogProcessingResult> {\n // Create a mock File object for testing\n const blob = new Blob([content], { type: \"text/csv\" });\n const file = new File([blob], \"test-daily.csv\", { type: \"text/csv\" });\n return this.processFile(file, blockId);\n }\n\n /**\n * Validate daily log data consistency\n */\n static validateDataConsistency(entries: DailyLogEntry[]): string[] {\n const warnings: string[] = [];\n\n if (entries.length === 0) return warnings;\n\n // Sort by date for chronological validation\n const sortedEntries = [...entries].sort(\n (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),\n );\n\n // Check for gaps in dates (more than 7 days)\n for (let i = 1; i < sortedEntries.length; i++) {\n const prevDate = new Date(sortedEntries[i - 1].date);\n const currentDate = new Date(sortedEntries[i].date);\n const daysDiff = (currentDate.getTime() - prevDate.getTime()) / (1000 * 60 * 60 * 24);\n\n if (daysDiff > 7) {\n warnings.push(\n `Large date gap detected: ${daysDiff.toFixed(0)} days between ${prevDate.toISOString().split(\"T\")[0]} and ${currentDate.toISOString().split(\"T\")[0]}`,\n );\n }\n }\n\n // Check for negative net liquidity\n const negativeEntries = sortedEntries.filter((entry) => entry.netLiquidity < 0);\n if (negativeEntries.length > 0) {\n warnings.push(`${negativeEntries.length} entries have negative net liquidity`);\n }\n\n // Check for extreme drawdowns (> 50%)\n const extremeDrawdowns = sortedEntries.filter((entry) => entry.drawdownPct < -0.5);\n if (extremeDrawdowns.length > 0) {\n warnings.push(`${extremeDrawdowns.length} entries have extreme drawdowns (> 50%)`);\n }\n\n return warnings;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getSofrRateByKey
4
- } from "./chunk-72GKJE2U.js";
4
+ } from "./chunk-RJDJHZ5Y.js";
5
5
  import {
6
6
  loadMddsProtoRoot
7
- } from "./chunk-FBNDMCT5.js";
7
+ } from "./chunk-T66KH2XH.js";
8
8
 
9
9
  // src/utils/providers/massive.ts
10
10
  import { z } from "zod";
@@ -226,7 +226,15 @@ function computeLegGreeks(optionPrice, underlyingPrice, strike, dte, type, riskF
226
226
  const bsType = type === "C" ? "call" : "put";
227
227
  const nullResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };
228
228
  if (dte < BACHELIER_DTE_THRESHOLD) {
229
- const iv2 = solveNormalIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);
229
+ const iv2 = solveNormalIV(
230
+ bsType,
231
+ optionPrice,
232
+ underlyingPrice,
233
+ strike,
234
+ T,
235
+ riskFreeRate,
236
+ dividendYield
237
+ );
230
238
  if (iv2 === null) return nullResult;
231
239
  return {
232
240
  delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv2),
@@ -425,9 +433,7 @@ function etDateTimeToUtcIso(dateStr, timeStr) {
425
433
  function getApiKey() {
426
434
  const key = process.env.MASSIVE_API_KEY;
427
435
  if (!key) {
428
- throw new Error(
429
- "Set MASSIVE_API_KEY environment variable to use Massive.com data import"
430
- );
436
+ throw new Error("Set MASSIVE_API_KEY environment variable to use Massive.com data import");
431
437
  }
432
438
  return key;
433
439
  }
@@ -439,9 +445,7 @@ async function fetchWithRetry(url, headers, maxRetries = 2) {
439
445
  });
440
446
  if (response.status === 429) {
441
447
  if (attempt === maxRetries) {
442
- throw new Error(
443
- "Massive.com rate limit exceeded \u2014 try again in a few minutes"
444
- );
448
+ throw new Error("Massive.com rate limit exceeded \u2014 try again in a few minutes");
445
449
  }
446
450
  const retryAfter = response.headers.get("Retry-After");
447
451
  const backoffMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : Math.pow(2, attempt + 1) * 1e3;
@@ -452,17 +456,7 @@ async function fetchWithRetry(url, headers, maxRetries = 2) {
452
456
  }
453
457
  throw new Error("Massive.com rate limit exceeded after retries");
454
458
  }
455
- var INDEX_TICKERS = /* @__PURE__ */ new Set([
456
- "SPX",
457
- "NDX",
458
- "RUT",
459
- "DJX",
460
- "VIX",
461
- "VIX9D",
462
- "VIX3M",
463
- "OEX",
464
- "XSP"
465
- ]);
459
+ var INDEX_TICKERS = /* @__PURE__ */ new Set(["SPX", "NDX", "RUT", "DJX", "VIX", "VIX9D", "VIX3M", "OEX", "XSP"]);
466
460
  function detectSnapshotAssetClass(ticker) {
467
461
  return INDEX_TICKERS.has(ticker.toUpperCase()) ? "index" : "stock";
468
462
  }
@@ -574,14 +568,7 @@ var MassiveProvider = class {
574
568
  }
575
569
  async fetchBars(options) {
576
570
  const apiKey = getApiKey();
577
- const {
578
- ticker,
579
- from,
580
- to,
581
- timespan = "day",
582
- multiplier = 1,
583
- assetClass = "stock"
584
- } = options;
571
+ const { ticker, from, to, timespan = "day", multiplier = 1, assetClass = "stock" } = options;
585
572
  const apiTicker = toMassiveTicker(ticker, assetClass);
586
573
  const storageTicker = fromMassiveTicker(apiTicker);
587
574
  const headers = { Authorization: `Bearer ${apiKey}` };
@@ -598,14 +585,10 @@ var MassiveProvider = class {
598
585
  }
599
586
  const response = await fetchWithRetry(url, headers);
600
587
  if (response.status === 401) {
601
- throw new Error(
602
- "MASSIVE_API_KEY rejected by Massive.com \u2014 check your key"
603
- );
588
+ throw new Error("MASSIVE_API_KEY rejected by Massive.com \u2014 check your key");
604
589
  }
605
590
  if (!response.ok) {
606
- throw new Error(
607
- `Massive.com API error: HTTP ${response.status} ${response.statusText}`
608
- );
591
+ throw new Error(`Massive.com API error: HTTP ${response.status} ${response.statusText}`);
609
592
  }
610
593
  const json = await response.json();
611
594
  const parsed = MassiveAggregateResponseSchema.safeParse(json);
@@ -633,9 +616,7 @@ var MassiveProvider = class {
633
616
  const nextUrlObj = new URL(data.next_url);
634
617
  const cursor = nextUrlObj.searchParams.get("cursor") ?? data.next_url;
635
618
  if (seenCursors.has(cursor)) {
636
- throw new Error(
637
- `Pagination loop detected \u2014 cursor repeated: ${cursor.slice(0, 50)}...`
638
- );
619
+ throw new Error(`Pagination loop detected \u2014 cursor repeated: ${cursor.slice(0, 50)}...`);
639
620
  }
640
621
  seenCursors.add(cursor);
641
622
  url = data.next_url;
@@ -848,9 +829,7 @@ var MassiveProvider = class {
848
829
  try {
849
830
  mkdir(dirname(outputPath), { recursive: true });
850
831
  await conn.run(sql);
851
- const countResult = await conn.runAndReadAll(
852
- `SELECT count(*) AS cnt FROM '${outputPath}'`
853
- );
832
+ const countResult = await conn.runAndReadAll(`SELECT count(*) AS cnt FROM '${outputPath}'`);
854
833
  const rowCount = Number(countResult.getRows()[0][0]);
855
834
  return { rowCount, skipped: false };
856
835
  } finally {
@@ -901,14 +880,10 @@ var MassiveProvider = class {
901
880
  }
902
881
  const response = await fetchWithRetry(url, headers);
903
882
  if (response.status === 401) {
904
- throw new Error(
905
- "MASSIVE_API_KEY rejected by Massive.com \u2014 check your key"
906
- );
883
+ throw new Error("MASSIVE_API_KEY rejected by Massive.com \u2014 check your key");
907
884
  }
908
885
  if (!response.ok) {
909
- throw new Error(
910
- `Massive.com API error: HTTP ${response.status} ${response.statusText}`
911
- );
886
+ throw new Error(`Massive.com API error: HTTP ${response.status} ${response.statusText}`);
912
887
  }
913
888
  const json = await response.json();
914
889
  const parsed = MassiveSnapshotResponseSchema.safeParse(json);
@@ -919,9 +894,7 @@ var MassiveProvider = class {
919
894
  const data = parsed.data;
920
895
  if (data.results.length > 0 && underlyingPrice === 0) {
921
896
  underlyingPrice = data.results[0].underlying_asset.price;
922
- underlyingTicker = fromMassiveTicker(
923
- data.results[0].underlying_asset.ticker
924
- );
897
+ underlyingTicker = fromMassiveTicker(data.results[0].underlying_asset.ticker);
925
898
  }
926
899
  for (const contract of data.results) {
927
900
  allContracts.push(mapContract(contract));
@@ -930,9 +903,7 @@ var MassiveProvider = class {
930
903
  const nextUrlObj = new URL(data.next_url);
931
904
  const cursor = nextUrlObj.searchParams.get("cursor") ?? data.next_url;
932
905
  if (seenCursors.has(cursor)) {
933
- throw new Error(
934
- `Pagination loop detected \u2014 cursor repeated: ${cursor.slice(0, 50)}...`
935
- );
906
+ throw new Error(`Pagination loop detected \u2014 cursor repeated: ${cursor.slice(0, 50)}...`);
936
907
  }
937
908
  seenCursors.add(cursor);
938
909
  url = data.next_url;
@@ -1386,7 +1357,7 @@ var ThetaMddsClient = class {
1386
1357
  fetchImpl;
1387
1358
  loadGrpcPackage;
1388
1359
  constructor(env = process.env, fetchImpl = fetch, loadGrpcPackage = async () => {
1389
- const { loadMddsGrpcPackage } = await import("./proto-DWRZJO4E.js");
1360
+ const { loadMddsGrpcPackage } = await import("./proto-YZ4TLX2N.js");
1390
1361
  return loadMddsGrpcPackage();
1391
1362
  }) {
1392
1363
  this.env = env;
@@ -2210,41 +2181,55 @@ var IvSolverPool = class {
2210
2181
  iv: new Float64Array(cols.count),
2211
2182
  ok: new Uint8Array(cols.count)
2212
2183
  };
2213
- const base = Math.floor(cols.count / shardCount);
2214
- const remainder = cols.count % shardCount;
2215
- const shardPromises = [];
2216
- let cursor = 0;
2217
- for (let s = 0; s < shardCount; s++) {
2218
- const size = base + (s < remainder ? 1 : 0);
2219
- const lo = cursor;
2220
- const hi = cursor + size;
2221
- cursor = hi;
2222
- if (size === 0) continue;
2223
- const request = shardRequest(cols, this.nextRequestId++, lo, hi);
2224
- shardPromises.push(
2225
- this.runOnWorker(request).then((reply) => {
2226
- out.delta.set(reply.delta, lo);
2227
- out.gamma.set(reply.gamma, lo);
2228
- out.theta.set(reply.theta, lo);
2229
- out.vega.set(reply.vega, lo);
2230
- out.iv.set(reply.iv, lo);
2231
- out.ok.set(reply.ok, lo);
2232
- })
2233
- );
2184
+ this.refPool();
2185
+ try {
2186
+ const base = Math.floor(cols.count / shardCount);
2187
+ const remainder = cols.count % shardCount;
2188
+ const shardPromises = [];
2189
+ let cursor = 0;
2190
+ for (let s = 0; s < shardCount; s++) {
2191
+ const size = base + (s < remainder ? 1 : 0);
2192
+ const lo = cursor;
2193
+ const hi = cursor + size;
2194
+ cursor = hi;
2195
+ if (size === 0) continue;
2196
+ const request = shardRequest(cols, this.nextRequestId++, lo, hi);
2197
+ shardPromises.push(
2198
+ this.runOnWorker(request).then((reply) => {
2199
+ out.delta.set(reply.delta, lo);
2200
+ out.gamma.set(reply.gamma, lo);
2201
+ out.theta.set(reply.theta, lo);
2202
+ out.vega.set(reply.vega, lo);
2203
+ out.iv.set(reply.iv, lo);
2204
+ out.ok.set(reply.ok, lo);
2205
+ })
2206
+ );
2207
+ }
2208
+ await Promise.all(shardPromises);
2209
+ return out;
2210
+ } finally {
2211
+ this.unrefPool();
2234
2212
  }
2235
- await Promise.all(shardPromises);
2236
- return out;
2237
2213
  }
2238
2214
  ensureWorkers(target) {
2239
2215
  while (this.pool.length < target) {
2240
2216
  const worker = new Worker(this.workerUrl);
2241
2217
  worker.setMaxListeners(0);
2218
+ worker.unref();
2242
2219
  worker.on("error", (err) => {
2243
2220
  throw err;
2244
2221
  });
2245
2222
  this.pool.push({ worker, busy: false });
2246
2223
  }
2247
2224
  }
2225
+ /** Keep the event loop alive while a solve is in flight. */
2226
+ refPool() {
2227
+ for (const { worker } of this.pool) worker.ref();
2228
+ }
2229
+ /** Release the event loop once a solve completes so idle workers don't pin it. */
2230
+ unrefPool() {
2231
+ for (const { worker } of this.pool) worker.unref();
2232
+ }
2248
2233
  runOnWorker(request) {
2249
2234
  return new Promise((resolve, reject) => {
2250
2235
  const slot = this.acquire();
@@ -3033,9 +3018,7 @@ function getProvider() {
3033
3018
  _cached = new ThetaDataProvider();
3034
3019
  break;
3035
3020
  default:
3036
- throw new Error(
3037
- `Unknown MARKET_DATA_PROVIDER: "${name}". Supported: massive, thetadata`
3038
- );
3021
+ throw new Error(`Unknown MARKET_DATA_PROVIDER: "${name}". Supported: massive, thetadata`);
3039
3022
  }
3040
3023
  return _cached;
3041
3024
  }
@@ -3058,4 +3041,4 @@ export {
3058
3041
  getProvider,
3059
3042
  _resetProvider
3060
3043
  };
3061
- //# sourceMappingURL=chunk-PNKG7RY7.js.map
3044
+ //# sourceMappingURL=chunk-OMUDNJBJ.js.map