tradeblocks-mcp 3.0.2 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +82 -68
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +127 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -37,7 +37,7 @@ export function normalizeTicker(value: string | null | undefined): string | null
37
37
  * Resolve ticker from arbitrary object fields (case/alias tolerant).
38
38
  */
39
39
  export function resolveTickerFromFields(
40
- fields: Record<string, unknown> | null | undefined
40
+ fields: Record<string, unknown> | null | undefined,
41
41
  ): string | null {
42
42
  if (!fields) return null;
43
43
  for (const field of TICKER_FIELD_CANDIDATES) {
@@ -55,11 +55,10 @@ export function resolveTickerFromFields(
55
55
  */
56
56
  export function resolveTradeTicker(
57
57
  trade: Pick<Trade, "customFields">,
58
- fallback: string = DEFAULT_MARKET_TICKER
58
+ fallback: string = DEFAULT_MARKET_TICKER,
59
59
  ): string {
60
60
  return (
61
- resolveTickerFromFields(trade.customFields as Record<string, unknown> | undefined) ??
62
- fallback
61
+ resolveTickerFromFields(trade.customFields as Record<string, unknown> | undefined) ?? fallback
63
62
  );
64
63
  }
65
64
 
@@ -68,12 +67,9 @@ export function resolveTradeTicker(
68
67
  */
69
68
  export function resolveTickerFromCsvRow(
70
69
  row: Record<string, string>,
71
- fallback: string = DEFAULT_MARKET_TICKER
70
+ fallback: string = DEFAULT_MARKET_TICKER,
72
71
  ): string {
73
- return (
74
- resolveTickerFromFields(row as Record<string, unknown>) ??
75
- fallback
76
- );
72
+ return resolveTickerFromFields(row as Record<string, unknown>) ?? fallback;
77
73
  }
78
74
 
79
75
  /**
@@ -7,8 +7,8 @@
7
7
  * All functions are pure — no fetch, no DuckDB.
8
8
  */
9
9
 
10
- import type { BarRow } from './market-provider.ts';
11
- import { computeLegGreeks, type GreeksResult } from './black-scholes.ts';
10
+ import type { BarRow } from "./market-provider.ts";
11
+ import { computeLegGreeks, type GreeksResult } from "./black-scholes.ts";
12
12
 
13
13
  // ---------------------------------------------------------------------------
14
14
  // Types
@@ -16,25 +16,25 @@ import { computeLegGreeks, type GreeksResult } from './black-scholes.ts';
16
16
 
17
17
  /** A parsed leg from a tradelog "legs" string (before OCC ticker resolution). */
18
18
  export interface ParsedLeg {
19
- root: string; // "SPY", "SPX", "SPXW"
20
- strike: number; // Numeric strike price
21
- type: 'C' | 'P'; // Call or Put
22
- quantity: number; // +1 or -1 (direction derived from position in spread)
19
+ root: string; // "SPY", "SPX", "SPXW"
20
+ strike: number; // Numeric strike price
21
+ type: "C" | "P"; // Call or Put
22
+ quantity: number; // +1 or -1 (direction derived from position in spread)
23
23
  }
24
24
 
25
25
  /** A fully resolved leg ready for replay (after OCC ticker construction). */
26
26
  export interface ReplayLeg {
27
- occTicker: string; // Full OCC ticker for Massive API fetch
28
- quantity: number; // Positive = long, negative = short
29
- entryPrice: number; // Per-contract entry price
30
- multiplier: number; // 100 for standard equity/index options
27
+ occTicker: string; // Full OCC ticker for Massive API fetch
28
+ quantity: number; // Positive = long, negative = short
29
+ entryPrice: number; // Per-contract entry price
30
+ multiplier: number; // 100 for standard equity/index options
31
31
  }
32
32
 
33
33
  /** A single point on the strategy P&L path. */
34
34
  export interface PnlPoint {
35
- timestamp: string; // "YYYY-MM-DD HH:MM" ET
36
- strategyPnl: number; // Combined P&L across all legs at this minute
37
- legPrices: number[]; // Mark price for each leg at this minute (bid/ask mid or HL2 fallback)
35
+ timestamp: string; // "YYYY-MM-DD HH:MM" ET
36
+ strategyPnl: number; // Combined P&L across all legs at this minute
37
+ legPrices: number[]; // Mark price for each leg at this minute (bid/ask mid or HL2 fallback)
38
38
  underlyingPrice?: number; // Underlying price used for greeks / decomposition at this timestamp
39
39
  // Per-leg greeks (Phase 69) — array parallel to legPrices
40
40
  legGreeks?: GreeksResult[];
@@ -55,11 +55,11 @@ export interface PnlPoint {
55
55
 
56
56
  /** Configuration for greeks computation in P&L path. */
57
57
  export interface GreeksConfig {
58
- underlyingPrices: Map<string, number>; // timestamp -> underlying price
59
- legs: Array<{ strike: number; type: 'C' | 'P'; expiryDate: string }>; // per-leg BS inputs
60
- riskFreeRate: number; // e.g. 0.045
61
- dividendYield: number; // e.g. 0.015 for SPX, 0 otherwise
62
- ivpByDate?: Map<string, number>; // date -> IVP value
58
+ underlyingPrices: Map<string, number>; // timestamp -> underlying price
59
+ legs: Array<{ strike: number; type: "C" | "P"; expiryDate: string }>; // per-leg BS inputs
60
+ riskFreeRate: number; // e.g. 0.045
61
+ dividendYield: number; // e.g. 0.015 for SPX, 0 otherwise
62
+ ivpByDate?: Map<string, number>; // date -> IVP value
63
63
  /** Sorted intraday timestamps from underlyingPrices for nearest-timestamp binary search. */
64
64
  sortedTimestamps?: string[];
65
65
  }
@@ -67,14 +67,14 @@ export interface GreeksConfig {
67
67
  /** Complete replay result with P&L path, MFE/MAE, and metadata. */
68
68
  export interface ReplayResult {
69
69
  pnlPath: PnlPoint[];
70
- mfe: number; // Max of strategyPnl series
71
- mae: number; // Min of strategyPnl series
72
- mfeTimestamp: string; // When MFE occurred
73
- maeTimestamp: string; // When MAE occurred
74
- totalPnl: number; // Final P&L at last bar
75
- totalBars?: number; // Total minute bars before format filtering
76
- legs: ReplayLeg[]; // The legs that were replayed
77
- greeksWarning?: string | null; // D-12: warning when >50% of leg-timestamps have null greeks
70
+ mfe: number; // Max of strategyPnl series
71
+ mae: number; // Min of strategyPnl series
72
+ mfeTimestamp: string; // When MFE occurred
73
+ maeTimestamp: string; // When MAE occurred
74
+ totalPnl: number; // Final P&L at last bar
75
+ totalBars?: number; // Total minute bars before format filtering
76
+ legs: ReplayLeg[]; // The legs that were replayed
77
+ greeksWarning?: string | null; // D-12: warning when >50% of leg-timestamps have null greeks
78
78
  }
79
79
 
80
80
  // ---------------------------------------------------------------------------
@@ -91,7 +91,7 @@ export interface ReplayResult {
91
91
  * Guards against broken exchange quotes (crossed bid>ask; blown ask>10×bid
92
92
  * with mid>$1) by falling back to HL2.
93
93
  */
94
- export function markPrice(bar: Pick<BarRow, 'high' | 'low' | 'bid' | 'ask'>): number {
94
+ export function markPrice(bar: Pick<BarRow, "high" | "low" | "bid" | "ask">): number {
95
95
  const { bid, ask } = bar;
96
96
  const hl2 = (bar.high + bar.low) / 2;
97
97
  if (bid != null && ask != null && (bid > 0 || ask > 0)) {
@@ -127,17 +127,24 @@ export function findNearestTimestamp(
127
127
  const targetMin = timestampToMinutes(target);
128
128
  if (targetMin === null) return undefined;
129
129
 
130
- let lo = 0, hi = sortedTimestamps.length - 1;
130
+ let lo = 0,
131
+ hi = sortedTimestamps.length - 1;
131
132
  let bestIdx = 0;
132
133
  let bestDiff = Infinity;
133
134
 
134
135
  while (lo <= hi) {
135
136
  const mid = (lo + hi) >>> 1;
136
137
  const midMin = timestampToMinutes(sortedTimestamps[mid]);
137
- if (midMin === null) { lo = mid + 1; continue; }
138
+ if (midMin === null) {
139
+ lo = mid + 1;
140
+ continue;
141
+ }
138
142
 
139
143
  const diff = Math.abs(midMin - targetMin);
140
- if (diff < bestDiff) { bestDiff = diff; bestIdx = mid; }
144
+ if (diff < bestDiff) {
145
+ bestDiff = diff;
146
+ bestIdx = mid;
147
+ }
141
148
  if (midMin < targetMin) lo = mid + 1;
142
149
  else if (midMin > targetMin) hi = mid - 1;
143
150
  else break; // exact match
@@ -148,9 +155,9 @@ export function findNearestTimestamp(
148
155
  }
149
156
 
150
157
  function timestampToMinutes(ts: string): number | null {
151
- const timePart = ts.split(' ')[1];
158
+ const timePart = ts.split(" ")[1];
152
159
  if (!timePart) return null;
153
- const [h, m] = timePart.split(':').map(Number);
160
+ const [h, m] = timePart.split(":").map(Number);
154
161
  if (isNaN(h) || isNaN(m)) return null;
155
162
  return h * 60 + m;
156
163
  }
@@ -171,13 +178,14 @@ const VERBOSE_LEG_RE = /^([A-Z]+)\s+\w+\s+(\d+(?:\.\d+)?)\s+(Call|Put)$/i;
171
178
  // Option Omega format: "{contracts} {Mon} {day} {strike} {P|C} {STO|BTO} {price}"
172
179
  // Example: "397 Mar 12 6610 P STO 35.85"
173
180
  // Captures: (1)contracts (2)month (3)day (4)strike (5)C|P (6)STO|BTO (7)price
174
- const OO_LEG_RE = /^(\d+)\s+(\w+)\s+(\d+)\s+(\d+(?:\.\d+)?)\s+(C|P)\s+(STO|BTO|STC|BTC)\s+(\d+(?:\.\d+)?)$/i;
181
+ const OO_LEG_RE =
182
+ /^(\d+)\s+(\w+)\s+(\d+)\s+(\d+(?:\.\d+)?)\s+(C|P)\s+(STO|BTO|STC|BTC)\s+(\d+(?:\.\d+)?)$/i;
175
183
 
176
184
  /** Extended parsed leg with entry price from OO format. */
177
185
  export interface ParsedLegOO extends ParsedLeg {
178
- entryPrice?: number; // Fill price from OO leg (e.g., 35.85)
179
- contracts?: number; // Contract count from OO leg
180
- expiryHint?: string; // "Mon DD" from OO format (e.g., "Mar 12") for multi-expiry strategies
186
+ entryPrice?: number; // Fill price from OO leg (e.g., 35.85)
187
+ contracts?: number; // Contract count from OO leg
188
+ expiryHint?: string; // "Mon DD" from OO format (e.g., "Mar 12") for multi-expiry strategies
181
189
  }
182
190
 
183
191
  /**
@@ -196,46 +204,46 @@ export interface ParsedLegOO extends ParsedLeg {
196
204
  * @throws Error if legs string is empty or cannot be parsed
197
205
  */
198
206
  export function parseLegsString(legsStr: string): ParsedLegOO[] {
199
- if (!legsStr || legsStr.trim() === '') {
207
+ if (!legsStr || legsStr.trim() === "") {
200
208
  throw new Error('Cannot parse legs "" — use hypothetical mode with explicit strikes');
201
209
  }
202
210
 
203
211
  // Detect Option Omega pipe-delimited format
204
- if (legsStr.includes('|')) {
212
+ if (legsStr.includes("|")) {
205
213
  return parseOOLegs(legsStr);
206
214
  }
207
215
 
208
- const parts = legsStr.includes('/') ? legsStr.split('/') : [legsStr];
216
+ const parts = legsStr.includes("/") ? legsStr.split("/") : [legsStr];
209
217
  const legs: ParsedLegOO[] = [];
210
- let inheritedRoot = '';
218
+ let inheritedRoot = "";
211
219
 
212
220
  for (let i = 0; i < parts.length; i++) {
213
221
  const raw = parts[i].trim();
214
222
  let root: string;
215
223
  let strike: number;
216
- let type: 'C' | 'P';
224
+ let type: "C" | "P";
217
225
 
218
226
  const compactMatch = raw.match(COMPACT_LEG_RE);
219
227
  if (compactMatch) {
220
228
  root = compactMatch[1].toUpperCase();
221
229
  strike = parseFloat(compactMatch[2]);
222
- type = compactMatch[3].toUpperCase() as 'C' | 'P';
230
+ type = compactMatch[3].toUpperCase() as "C" | "P";
223
231
  } else {
224
232
  // Try compact without root (e.g., "465C" in "SPY 470C/465C")
225
233
  const noRootMatch = raw.match(COMPACT_NO_ROOT_RE);
226
234
  if (noRootMatch && inheritedRoot) {
227
235
  root = inheritedRoot;
228
236
  strike = parseFloat(noRootMatch[1]);
229
- type = noRootMatch[2].toUpperCase() as 'C' | 'P';
237
+ type = noRootMatch[2].toUpperCase() as "C" | "P";
230
238
  } else {
231
239
  const verboseMatch = raw.match(VERBOSE_LEG_RE);
232
240
  if (verboseMatch) {
233
241
  root = verboseMatch[1].toUpperCase();
234
242
  strike = parseFloat(verboseMatch[2]);
235
- type = verboseMatch[3].toLowerCase() === 'call' ? 'C' : 'P';
243
+ type = verboseMatch[3].toLowerCase() === "call" ? "C" : "P";
236
244
  } else {
237
245
  throw new Error(
238
- `Cannot parse legs "${legsStr}" — use hypothetical mode with explicit strikes`
246
+ `Cannot parse legs "${legsStr}" — use hypothetical mode with explicit strikes`,
239
247
  );
240
248
  }
241
249
  }
@@ -245,7 +253,7 @@ export function parseLegsString(legsStr: string): ParsedLegOO[] {
245
253
  if (i === 0) inheritedRoot = root;
246
254
 
247
255
  // First leg is bought (+1), subsequent alternate -1, +1, -1...
248
- const quantity = i === 0 ? 1 : (i % 2 === 0 ? 1 : -1);
256
+ const quantity = i === 0 ? 1 : i % 2 === 0 ? 1 : -1;
249
257
 
250
258
  legs.push({ root, strike, type, quantity });
251
259
  }
@@ -264,7 +272,7 @@ export function parseLegsString(legsStr: string): ParsedLegOO[] {
264
272
  * - Open+close fills: same strike, same date, opposite direction (close dropped)
265
273
  */
266
274
  function parseOOLegs(legsStr: string): ParsedLegOO[] {
267
- const segments = legsStr.split('|').map(s => s.trim());
275
+ const segments = legsStr.split("|").map((s) => s.trim());
268
276
  const legs: ParsedLegOO[] = [];
269
277
  const seen = new Set<string>();
270
278
 
@@ -272,7 +280,7 @@ function parseOOLegs(legsStr: string): ParsedLegOO[] {
272
280
  const match = seg.match(OO_LEG_RE);
273
281
  if (!match) {
274
282
  throw new Error(
275
- `Cannot parse OO leg segment "${seg}" — use hypothetical mode with explicit strikes`
283
+ `Cannot parse OO leg segment "${seg}" — use hypothetical mode with explicit strikes`,
276
284
  );
277
285
  }
278
286
 
@@ -280,7 +288,7 @@ function parseOOLegs(legsStr: string): ParsedLegOO[] {
280
288
  const month = match[2];
281
289
  const day = match[3];
282
290
  const strike = parseFloat(match[4]);
283
- const type = match[5].toUpperCase() as 'C' | 'P';
291
+ const type = match[5].toUpperCase() as "C" | "P";
284
292
  const direction = match[6].toUpperCase();
285
293
  const price = parseFloat(match[7]);
286
294
 
@@ -291,10 +299,10 @@ function parseOOLegs(legsStr: string): ParsedLegOO[] {
291
299
  seen.add(key);
292
300
 
293
301
  legs.push({
294
- root: '', // OO format doesn't include root — caller provides via trade's ticker field
302
+ root: "", // OO format doesn't include root — caller provides via trade's ticker field
295
303
  strike,
296
304
  type,
297
- quantity: direction === 'BTO' ? 1 : -1,
305
+ quantity: direction === "BTO" ? 1 : -1,
298
306
  entryPrice: price,
299
307
  contracts,
300
308
  expiryHint: `${month} ${day}`,
@@ -318,16 +326,16 @@ function parseOOLegs(legsStr: string): ParsedLegOO[] {
318
326
  export function buildOccTicker(
319
327
  root: string,
320
328
  expiry: string,
321
- type: 'C' | 'P',
329
+ type: "C" | "P",
322
330
  strike: number,
323
331
  ): string {
324
332
  // Extract YYMMDD from "YYYY-MM-DD"
325
- const [yyyy, mm, dd] = expiry.split('-');
333
+ const [yyyy, mm, dd] = expiry.split("-");
326
334
  const yy = yyyy.slice(2);
327
335
 
328
336
  // Strike * 1000 padded to 8 digits
329
337
  const strikeInt = Math.round(strike * 1000);
330
- const strikePadded = String(strikeInt).padStart(8, '0');
338
+ const strikePadded = String(strikeInt).padStart(8, "0");
331
339
 
332
340
  return `${root}${yy}${mm}${dd}${type}${strikePadded}`;
333
341
  }
@@ -361,7 +369,7 @@ export function computeStrategyPnlPath(
361
369
  const legMaps: Map<string, BarRow>[] = barsByLeg.map((bars) => {
362
370
  const map = new Map<string, BarRow>();
363
371
  for (const bar of bars) {
364
- const ts = `${bar.date} ${bar.time ?? ''}`.trim();
372
+ const ts = `${bar.date} ${bar.time ?? ""}`.trim();
365
373
  map.set(ts, bar);
366
374
  }
367
375
  return map;
@@ -371,7 +379,7 @@ export function computeStrategyPnlPath(
371
379
  const allTimestamps = new Set<string>();
372
380
  for (const bars of barsByLeg) {
373
381
  for (const bar of bars) {
374
- allTimestamps.add(`${bar.date} ${bar.time ?? ''}`.trim());
382
+ allTimestamps.add(`${bar.date} ${bar.time ?? ""}`.trim());
375
383
  }
376
384
  }
377
385
  const sortedTimestamps = [...allTimestamps].sort();
@@ -380,7 +388,6 @@ export function computeStrategyPnlPath(
380
388
  const path: PnlPoint[] = [];
381
389
  const lastBar: (BarRow | undefined)[] = new Array(legs.length).fill(undefined);
382
390
 
383
-
384
391
  for (const ts of sortedTimestamps) {
385
392
  let complete = true;
386
393
  const legPrices: number[] = [];
@@ -413,14 +420,17 @@ export function computeStrategyPnlPath(
413
420
  if (nearest) underlyingPrice = greeksConfig.underlyingPrices.get(nearest);
414
421
  }
415
422
  if (underlyingPrice === undefined) {
416
- const dateOnly = ts.split(' ')[0];
423
+ const dateOnly = ts.split(" ")[0];
417
424
  underlyingPrice = greeksConfig.underlyingPrices.get(dateOnly);
418
425
  }
419
426
 
420
427
  if (underlyingPrice !== undefined) {
421
428
  point.underlyingPrice = underlyingPrice;
422
429
  const legGreeksArr: GreeksResult[] = [];
423
- let netDelta = 0, netGamma = 0, netTheta = 0, netVega = 0;
430
+ let netDelta = 0,
431
+ netGamma = 0,
432
+ netTheta = 0,
433
+ netVega = 0;
424
434
  let allNull = true;
425
435
 
426
436
  for (let j = 0; j < legs.length; j++) {
@@ -431,11 +441,11 @@ export function computeStrategyPnlPath(
431
441
  }
432
442
 
433
443
  // Compute fractional DTE from bar timestamp to leg expiry
434
- const dateStr = ts.split(' ')[0];
435
- const timePart = ts.split(' ')[1] ?? '09:30';
436
- const [eyy, emm, edd] = legCfg.expiryDate.split('-').map(Number);
437
- const [byy, bmm, bdd] = dateStr.split('-').map(Number);
438
- const [hh, min] = timePart.split(':').map(Number);
444
+ const dateStr = ts.split(" ")[0];
445
+ const timePart = ts.split(" ")[1] ?? "09:30";
446
+ const [eyy, emm, edd] = legCfg.expiryDate.split("-").map(Number);
447
+ const [byy, bmm, bdd] = dateStr.split("-").map(Number);
448
+ const [hh, min] = timePart.split(":").map(Number);
439
449
 
440
450
  const expiryMs = new Date(eyy, emm - 1, edd).getTime() + 16 * 60 * 60 * 1000; // 4:00 PM ET
441
451
  const barMs = new Date(byy, bmm - 1, bdd).getTime() + (hh * 60 + min) * 60 * 1000;
@@ -459,7 +469,7 @@ export function computeStrategyPnlPath(
459
469
 
460
470
  if (g.delta !== null) {
461
471
  allNull = false;
462
- const weight = legs[j].quantity * legs[j].multiplier / 100;
472
+ const weight = (legs[j].quantity * legs[j].multiplier) / 100;
463
473
  netDelta += g.delta * weight;
464
474
  netGamma += g.gamma! * weight;
465
475
  netTheta += g.theta! * weight;
@@ -473,9 +483,8 @@ export function computeStrategyPnlPath(
473
483
  point.netTheta = allNull ? null : netTheta;
474
484
  point.netVega = allNull ? null : netVega;
475
485
 
476
-
477
486
  // IVP lookup by date
478
- const ivpDate = ts.split(' ')[0];
487
+ const ivpDate = ts.split(" ")[0];
479
488
  point.ivp = greeksConfig.ivpByDate?.get(ivpDate) ?? null;
480
489
  }
481
490
  }
@@ -505,7 +514,7 @@ export function computeReplayMfeMae(pnlPath: PnlPoint[]): {
505
514
  maeTimestamp: string;
506
515
  } {
507
516
  if (pnlPath.length === 0) {
508
- return { mfe: 0, mae: 0, mfeTimestamp: '', maeTimestamp: '' };
517
+ return { mfe: 0, mae: 0, mfeTimestamp: "", maeTimestamp: "" };
509
518
  }
510
519
 
511
520
  let mfe = pnlPath[0].strategyPnl;