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
@@ -43,10 +43,8 @@ export const replayTradeSchema = z.object({
43
43
  type: z.enum(["C", "P"]).describe("Call or Put"),
44
44
  expiry: z.string().describe("Expiration date YYYY-MM-DD"),
45
45
  quantity: z.number().describe("Positive = long, negative = short"),
46
- entry_price: z
47
- .number()
48
- .describe("Per-contract entry price (premium paid/received)"),
49
- })
46
+ entry_price: z.number().describe("Per-contract entry price (premium paid/received)"),
47
+ }),
50
48
  )
51
49
  .optional()
52
50
  .describe("Explicit leg definitions for hypothetical replay"),
@@ -56,22 +54,20 @@ export const replayTradeSchema = z.object({
56
54
  trade_index: z
57
55
  .number()
58
56
  .optional()
59
- .describe(
60
- "0-based index of trade in block's tradelog (ordered by date_opened)"
61
- ),
57
+ .describe("0-based index of trade in block's tradelog (ordered by date_opened)"),
62
58
 
63
59
  // Common fields
64
60
  open_date: z
65
61
  .string()
66
62
  .optional()
67
63
  .describe(
68
- "Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)"
64
+ "Trade open date YYYY-MM-DD (required for hypothetical mode, auto-resolved for tradelog mode)",
69
65
  ),
70
66
  close_date: z
71
67
  .string()
72
68
  .optional()
73
69
  .describe(
74
- "Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)"
70
+ "Trade close date YYYY-MM-DD (required for hypothetical, auto-resolved for tradelog)",
75
71
  ),
76
72
  multiplier: z
77
73
  .number()
@@ -82,21 +78,21 @@ export const replayTradeSchema = z.object({
82
78
  .default("sampled")
83
79
  .describe(
84
80
  "Output format: 'sampled' returns path sampled at ~15min intervals (default), " +
85
- "'full' returns complete minute-by-minute P&L path, " +
86
- "'summary' returns MFE/MAE/P&L without minute-level path"
81
+ "'full' returns complete minute-by-minute P&L path, " +
82
+ "'summary' returns MFE/MAE/P&L without minute-level path",
87
83
  ),
88
84
  close_at: z
89
85
  .enum(["trade", "expiry"])
90
86
  .default("trade")
91
87
  .describe(
92
88
  "When to end the P&L path: 'trade' (default) truncates at the trade's actual close time, " +
93
- "'expiry' shows full path through option expiry. Only applies to tradelog mode."
89
+ "'expiry' shows full path through option expiry. Only applies to tradelog mode.",
94
90
  ),
95
91
  skip_quotes: z
96
92
  .boolean()
97
93
  .default(false)
98
94
  .describe(
99
- "Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks."
95
+ "Skip NBBO quote enrichment for option bars. Faster, but uses cached trade bars / HL2 marks.",
100
96
  ),
101
97
  });
102
98
 
@@ -105,15 +101,25 @@ export const replayTradeSchema = z.object({
105
101
  // ---------------------------------------------------------------------------
106
102
 
107
103
  const MONTH_MAP: Record<string, string> = {
108
- Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06',
109
- Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12',
104
+ Jan: "01",
105
+ Feb: "02",
106
+ Mar: "03",
107
+ Apr: "04",
108
+ May: "05",
109
+ Jun: "06",
110
+ Jul: "07",
111
+ Aug: "08",
112
+ Sep: "09",
113
+ Oct: "10",
114
+ Nov: "11",
115
+ Dec: "12",
110
116
  };
111
117
 
112
118
  /** Convert OO expiry hint "Mar 13" + year "2026" → "2026-03-13" */
113
119
  function resolveOOExpiryHint(hint: string, year: string): string {
114
- const [mon, day] = hint.split(' ');
115
- const mm = MONTH_MAP[mon] ?? '01';
116
- const dd = day.padStart(2, '0');
120
+ const [mon, day] = hint.split(" ");
121
+ const mm = MONTH_MAP[mon] ?? "01";
122
+ const dd = day.padStart(2, "0");
117
123
  return `${year}-${mm}-${dd}`;
118
124
  }
119
125
 
@@ -130,8 +136,8 @@ export function resolveOODateRange(
130
136
  tradeOpenDate: string,
131
137
  ): { from: string; to: string } | null {
132
138
  const hints = parsedLegs
133
- .filter(l => l.expiryHint)
134
- .map(l => resolveOOExpiryHint(l.expiryHint!, tradeYear));
139
+ .filter((l) => l.expiryHint)
140
+ .map((l) => resolveOOExpiryHint(l.expiryHint!, tradeYear));
135
141
 
136
142
  if (hints.length === 0) return null;
137
143
 
@@ -151,16 +157,9 @@ export async function handleReplayTrade(
151
157
  params: z.infer<typeof replayTradeSchema>,
152
158
  baseDir: string,
153
159
  stores: MarketStores,
154
- injectedConn?: import("@duckdb/node-api").DuckDBConnection
160
+ injectedConn?: import("@duckdb/node-api").DuckDBConnection,
155
161
  ): Promise<ReplayResult> {
156
- const {
157
- legs: inputLegs,
158
- block_id,
159
- trade_index,
160
- multiplier,
161
- close_at,
162
- skip_quotes,
163
- } = params;
162
+ const { legs: inputLegs, block_id, trade_index, multiplier, close_at, skip_quotes } = params;
164
163
  let { open_date, close_date } = params;
165
164
  let tradeCloseTimestamp: string | undefined; // "YYYY-MM-DD HH:MM" when trade actually closed
166
165
 
@@ -169,9 +168,7 @@ export async function handleReplayTrade(
169
168
  if (inputLegs && inputLegs.length > 0) {
170
169
  // ----- Mode A: Hypothetical replay -----
171
170
  if (!open_date || !close_date) {
172
- throw new Error(
173
- "open_date and close_date are required for hypothetical replay mode"
174
- );
171
+ throw new Error("open_date and close_date are required for hypothetical replay mode");
175
172
  }
176
173
 
177
174
  replayLegs = inputLegs.map((leg) => ({
@@ -182,21 +179,19 @@ export async function handleReplayTrade(
182
179
  }));
183
180
  } else if (block_id !== undefined && trade_index !== undefined) {
184
181
  // ----- Mode B: Tradelog replay -----
185
- const conn = injectedConn ?? await getConnection(baseDir);
182
+ const conn = injectedConn ?? (await getConnection(baseDir));
186
183
 
187
184
  const result = await conn.runAndReadAll(
188
185
  `SELECT legs, premium, date_opened, date_closed, ticker, num_contracts, time_closed
189
186
  FROM trades.trade_data
190
187
  WHERE block_id = '${block_id.replace(/'/g, "''")}'
191
188
  ORDER BY date_opened, rowid
192
- LIMIT 1 OFFSET ${trade_index}`
189
+ LIMIT 1 OFFSET ${trade_index}`,
193
190
  );
194
191
 
195
192
  const rows = result.getRows();
196
193
  if (rows.length === 0) {
197
- throw new Error(
198
- `No trade found at index ${trade_index} in block "${block_id}"`
199
- );
194
+ throw new Error(`No trade found at index ${trade_index} in block "${block_id}"`);
200
195
  }
201
196
 
202
197
  const row = rows[0];
@@ -225,20 +220,19 @@ export async function handleReplayTrade(
225
220
  parsedLegs = parseLegsString(legsStr);
226
221
  } catch {
227
222
  throw new Error(
228
- `Cannot parse legs "${legsStr}" from tradelog — use hypothetical mode with explicit strikes`
223
+ `Cannot parse legs "${legsStr}" from tradelog — use hypothetical mode with explicit strikes`,
229
224
  );
230
225
  }
231
226
 
232
227
  // Build ReplayLeg[] from parsed legs
233
228
  const root = ticker || parsedLegs[0].root;
234
- const perContractPremium =
235
- numContracts > 0 ? premium / numContracts : premium;
229
+ const perContractPremium = numContracts > 0 ? premium / numContracts : premium;
236
230
 
237
231
  // OO format provides per-leg entry price, contract count, and expiry hint
238
- const hasOOData = parsedLegs.some(l => l.entryPrice !== undefined);
232
+ const hasOOData = parsedLegs.some((l) => l.entryPrice !== undefined);
239
233
 
240
234
  // Resolve per-leg expiry: OO expiryHint ("Mar 13") + year from trade date
241
- const tradeYear = (open_date || dateOpened).split('-')[0];
235
+ const tradeYear = (open_date || dateOpened).split("-")[0];
242
236
 
243
237
  // Override fetch date range from OO expiryHints when available
244
238
  if (hasOOData) {
@@ -259,15 +253,13 @@ export async function handleReplayTrade(
259
253
  quantity: hasOOData
260
254
  ? leg.quantity * (leg.contracts ?? 1)
261
255
  : leg.quantity * (numContracts > 0 ? numContracts : 1),
262
- entryPrice: hasOOData
263
- ? leg.entryPrice!
264
- : perContractPremium / parsedLegs.length,
256
+ entryPrice: hasOOData ? leg.entryPrice! : perContractPremium / parsedLegs.length,
265
257
  multiplier,
266
258
  };
267
259
  });
268
260
  } else {
269
261
  throw new Error(
270
- "Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode"
262
+ "Provide either legs[] for hypothetical mode or block_id + trade_index for tradelog mode",
271
263
  );
272
264
  }
273
265
 
@@ -313,7 +305,7 @@ export async function handleReplayTrade(
313
305
  const barsByLeg: BarRow[][] = replayLegs.map((leg) => {
314
306
  const quotes = quotesByOcc.get(leg.occTicker) ?? [];
315
307
  return quotes.map((q) => {
316
- const [date, time] = q.timestamp.split(' ');
308
+ const [date, time] = q.timestamp.split(" ");
317
309
  const mid = (q.bid + q.ask) / 2;
318
310
  return {
319
311
  ticker: q.occ_ticker,
@@ -333,15 +325,20 @@ export async function handleReplayTrade(
333
325
  // ----- Fetch underlying bars + build greeks config -----
334
326
  // Reverse-map weekly roots back to standard root for underlying fetch
335
327
  const REVERSE_ROOT_MAP: Record<string, string> = {
336
- SPXW: 'SPX', NDXP: 'NDX', RUTW: 'RUT',
328
+ SPXW: "SPX",
329
+ NDXP: "NDX",
330
+ RUTW: "RUT",
337
331
  };
338
332
  const DIVIDEND_YIELDS: Record<string, number> = {
339
- SPX: 0.015, SPXW: 0.015, NDX: 0.015, NDXP: 0.015,
333
+ SPX: 0.015,
334
+ SPXW: 0.015,
335
+ NDX: 0.015,
336
+ NDXP: 0.015,
340
337
  };
341
338
 
342
339
  // Extract root from first leg's OCC ticker
343
340
  const firstRootMatch = replayLegs[0]?.occTicker.match(/^([A-Z]+)/);
344
- const rawRoot = firstRootMatch ? firstRootMatch[1] : '';
341
+ const rawRoot = firstRootMatch ? firstRootMatch[1] : "";
345
342
  const underlyingTicker = REVERSE_ROOT_MAP[rawRoot] ?? rawRoot;
346
343
  const dividendYield = DIVIDEND_YIELDS[rawRoot] ?? 0;
347
344
 
@@ -356,11 +353,7 @@ export async function handleReplayTrade(
356
353
  );
357
354
  if (underlyingBars.length === 0) {
358
355
  try {
359
- underlyingBars = await stores.spot.readDailyBars(
360
- underlyingTicker,
361
- open_date!,
362
- close_date!,
363
- );
356
+ underlyingBars = await stores.spot.readDailyBars(underlyingTicker, open_date!, close_date!);
364
357
  } catch {
365
358
  // No fallback available — greeks will be omitted
366
359
  }
@@ -375,17 +368,21 @@ export async function handleReplayTrade(
375
368
  if (underlyingBars.length > 0) {
376
369
  underlyingBars = underlyingBars.filter(
377
370
  (b) =>
378
- Number.isFinite(b.open) && b.open > 0 &&
379
- Number.isFinite(b.high) && b.high > 0 &&
380
- Number.isFinite(b.low) && b.low > 0 &&
381
- Number.isFinite(b.close)&& b.close> 0,
371
+ Number.isFinite(b.open) &&
372
+ b.open > 0 &&
373
+ Number.isFinite(b.high) &&
374
+ b.high > 0 &&
375
+ Number.isFinite(b.low) &&
376
+ b.low > 0 &&
377
+ Number.isFinite(b.close) &&
378
+ b.close > 0,
382
379
  );
383
380
  }
384
381
 
385
382
  // Build underlying price map for greeks config
386
383
  const underlyingPrices = new Map<string, number>();
387
384
  for (const b of underlyingBars) {
388
- const ts = `${b.date} ${b.time ?? ''}`.trim();
385
+ const ts = `${b.date} ${b.time ?? ""}`.trim();
389
386
  underlyingPrices.set(ts, markPrice(b));
390
387
  }
391
388
 
@@ -394,7 +391,7 @@ export async function handleReplayTrade(
394
391
  // (e.g. one source skipped a minute), greeks computation falls back to
395
392
  // the nearest underlying timestamp within tolerance.
396
393
  const sortedTimestamps = Array.from(underlyingPrices.keys())
397
- .filter(k => k.includes(' ')) // Only intraday timestamps, not date-only keys
394
+ .filter((k) => k.includes(" ")) // Only intraday timestamps, not date-only keys
398
395
  .sort();
399
396
 
400
397
  // VIX IVP lookup via EnrichedStore.read — used as an optional input to
@@ -427,12 +424,12 @@ export async function handleReplayTrade(
427
424
  greeksConfig = {
428
425
  underlyingPrices,
429
426
  sortedTimestamps,
430
- legs: replayLegs.map(leg => {
427
+ legs: replayLegs.map((leg) => {
431
428
  // Extract strike, type, expiry from OCC ticker: ROOT{YYMMDD}{C|P}{strike*1000}
432
429
  const occMatch = leg.occTicker.match(/^[A-Z]+(\d{6})([CP])(\d{8})$/);
433
- if (!occMatch) return { strike: 0, type: 'C' as const, expiryDate: '' };
430
+ if (!occMatch) return { strike: 0, type: "C" as const, expiryDate: "" };
434
431
  const yymmdd = occMatch[1];
435
- const type = occMatch[2] as 'C' | 'P';
432
+ const type = occMatch[2] as "C" | "P";
436
433
  const strike = parseInt(occMatch[3], 10) / 1000;
437
434
  const expiryDate = `20${yymmdd.slice(0, 2)}-${yymmdd.slice(2, 4)}-${yymmdd.slice(4, 6)}`;
438
435
  return { strike, type, expiryDate };
@@ -445,8 +442,7 @@ export async function handleReplayTrade(
445
442
 
446
443
  // ----- Compute P&L path + MFE/MAE -----
447
444
  let fullPath = computeStrategyPnlPath(replayLegs, barsByLeg, greeksConfig);
448
- let { mfe, mae, mfeTimestamp, maeTimestamp } =
449
- computeReplayMfeMae(fullPath);
445
+ let { mfe, mae, mfeTimestamp, maeTimestamp } = computeReplayMfeMae(fullPath);
450
446
  let totalPnl = fullPath.length > 0 ? fullPath[fullPath.length - 1].strategyPnl : 0;
451
447
 
452
448
  // Surface a warning when >50% of leg-timestamps have null greeks — the
@@ -462,23 +458,30 @@ export async function handleReplayTrade(
462
458
  }
463
459
  }
464
460
  }
465
- const greeksWarning = greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5
466
- ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`
467
- : null;
461
+ const greeksWarning =
462
+ greeksTotalCount > 0 && greeksNullCount / greeksTotalCount > 0.5
463
+ ? `Greeks unavailable for ${greeksNullCount} of ${greeksTotalCount} leg-timestamps (0DTE options use Bachelier model; some legs may have insufficient time value for IV computation)`
464
+ : null;
468
465
 
469
466
  // Apply format filter
470
467
  // Truncate path at trade close timestamp when close_at === "trade" (default)
471
468
  // This ensures decompose_greeks and exit triggers only analyze the actual holding period
472
469
  if (close_at === "trade" && tradeCloseTimestamp && fullPath.length > 0) {
473
- const truncIdx = fullPath.findIndex(p => p.timestamp > tradeCloseTimestamp!);
470
+ const truncIdx = fullPath.findIndex((p) => p.timestamp > tradeCloseTimestamp!);
474
471
  if (truncIdx > 0) {
475
472
  fullPath = fullPath.slice(0, truncIdx);
476
473
  // Recompute MFE/MAE/totalPnl on truncated path
477
474
  mfe = -Infinity;
478
475
  mae = Infinity;
479
476
  for (const p of fullPath) {
480
- if (p.strategyPnl > mfe) { mfe = p.strategyPnl; mfeTimestamp = p.timestamp; }
481
- if (p.strategyPnl < mae) { mae = p.strategyPnl; maeTimestamp = p.timestamp; }
477
+ if (p.strategyPnl > mfe) {
478
+ mfe = p.strategyPnl;
479
+ mfeTimestamp = p.timestamp;
480
+ }
481
+ if (p.strategyPnl < mae) {
482
+ mae = p.strategyPnl;
483
+ maeTimestamp = p.timestamp;
484
+ }
482
485
  }
483
486
  totalPnl = fullPath[fullPath.length - 1].strategyPnl;
484
487
  }
@@ -494,12 +497,13 @@ export async function handleReplayTrade(
494
497
  mfeTimestamp,
495
498
  maeTimestamp,
496
499
  ]);
497
- pnlPath = fullPath.filter(p => keyTimestamps.has(p.timestamp));
500
+ pnlPath = fullPath.filter((p) => keyTimestamps.has(p.timestamp));
498
501
  } else if (format === "sampled") {
499
502
  // Sample at ~15min intervals (keep every 15th bar, plus first/last/MFE/MAE)
500
503
  const keyTimestamps = new Set([mfeTimestamp, maeTimestamp]);
501
- pnlPath = fullPath.filter((p, i) =>
502
- i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp)
504
+ pnlPath = fullPath.filter(
505
+ (p, i) =>
506
+ i === 0 || i === fullPath.length - 1 || i % 15 === 0 || keyTimestamps.has(p.timestamp),
503
507
  );
504
508
  } else {
505
509
  pnlPath = fullPath;
@@ -546,7 +550,7 @@ export function registerReplayTools(
546
550
  const summary =
547
551
  `Replayed ${result.legs.length}-leg strategy from ${params.open_date ?? "trade dates"} to ${params.close_date ?? "trade dates"}: ` +
548
552
  `$${result.totalPnl.toFixed(2)} P&L, MFE=$${result.mfe.toFixed(2)}, MAE=$${result.mae.toFixed(2)}, ` +
549
- `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? 'yes' : 'no'}`;
553
+ `${result.pnlPath.length} minute bars, greeks=${result.pnlPath[0]?.legGreeks ? "yes" : "no"}`;
550
554
 
551
555
  return createToolOutput(summary, result);
552
556
  } catch (error) {
@@ -560,6 +564,6 @@ export function registerReplayTools(
560
564
  isError: true,
561
565
  };
562
566
  }
563
- }
567
+ },
564
568
  );
565
569
  }