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
@@ -27,7 +27,7 @@ export interface GreeksResult {
27
27
  theta: number | null; // per day
28
28
  vega: number | null; // per 1% IV move
29
29
  iv: number | null; // annualized implied volatility (0-N, not percentage)
30
- model?: 'bs' | 'bachelier'; // which pricing model was used; undefined if IV solve failed
30
+ model?: "bs" | "bachelier"; // which pricing model was used; undefined if IV solve failed
31
31
  }
32
32
 
33
33
  // --- Internal helpers (exported for Bachelier model and testing) ---
@@ -50,7 +50,7 @@ export function cdf(x: number): number {
50
50
  const absX = Math.abs(x);
51
51
 
52
52
  const p = 0.2316419;
53
- const b1 = 0.319381530;
53
+ const b1 = 0.31938153;
54
54
  const b2 = -0.356563782;
55
55
  const b3 = 1.781477937;
56
56
  const b4 = -1.821255978;
@@ -98,7 +98,7 @@ function d1d2(
98
98
  * @returns Option price
99
99
  */
100
100
  export function bsPrice(
101
- type: 'call' | 'put',
101
+ type: "call" | "put",
102
102
  S: number,
103
103
  K: number,
104
104
  T: number,
@@ -108,13 +108,13 @@ export function bsPrice(
108
108
  ): number {
109
109
  // Edge case: at or past expiry
110
110
  if (T <= 0) {
111
- return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);
111
+ return type === "call" ? Math.max(S - K, 0) : Math.max(K - S, 0);
112
112
  }
113
113
 
114
114
  // Edge case: zero volatility — return discounted intrinsic
115
115
  if (sigma <= 0) {
116
116
  const forward = S * Math.exp((r - q) * T);
117
- if (type === 'call') {
117
+ if (type === "call") {
118
118
  return Math.max(forward - K, 0) * Math.exp(-r * T);
119
119
  } else {
120
120
  return Math.max(K - forward, 0) * Math.exp(-r * T);
@@ -123,7 +123,7 @@ export function bsPrice(
123
123
 
124
124
  const { d1, d2 } = d1d2(S, K, T, r, q, sigma);
125
125
 
126
- if (type === 'call') {
126
+ if (type === "call") {
127
127
  return S * Math.exp(-q * T) * cdf(d1) - K * Math.exp(-r * T) * cdf(d2);
128
128
  } else {
129
129
  return K * Math.exp(-r * T) * cdf(-d2) - S * Math.exp(-q * T) * cdf(-d1);
@@ -136,7 +136,7 @@ export function bsPrice(
136
136
  * Put: (N(d1) - 1) * e^(-qT)
137
137
  */
138
138
  export function bsDelta(
139
- type: 'call' | 'put',
139
+ type: "call" | "put",
140
140
  S: number,
141
141
  K: number,
142
142
  T: number,
@@ -145,14 +145,14 @@ export function bsDelta(
145
145
  sigma: number,
146
146
  ): number {
147
147
  if (T <= 0 || sigma <= 0) {
148
- if (type === 'call') return S > K ? 1 : 0;
148
+ if (type === "call") return S > K ? 1 : 0;
149
149
  return S < K ? -1 : 0;
150
150
  }
151
151
 
152
152
  const { d1 } = d1d2(S, K, T, r, q, sigma);
153
153
  const eqT = Math.exp(-q * T);
154
154
 
155
- if (type === 'call') {
155
+ if (type === "call") {
156
156
  return cdf(d1) * eqT;
157
157
  } else {
158
158
  return (cdf(d1) - 1) * eqT;
@@ -182,7 +182,7 @@ export function bsGamma(
182
182
  * Returns the daily time decay (negative for long options).
183
183
  */
184
184
  export function bsTheta(
185
- type: 'call' | 'put',
185
+ type: "call" | "put",
186
186
  S: number,
187
187
  K: number,
188
188
  T: number,
@@ -200,7 +200,7 @@ export function bsTheta(
200
200
  // First term: -(S * e^(-qT) * N'(d1) * sigma) / (2 * sqrt(T))
201
201
  const term1 = -(S * eqT * pdf(d1) * sigma) / (2 * sqrtT);
202
202
 
203
- if (type === 'call') {
203
+ if (type === "call") {
204
204
  const term2 = q * S * eqT * cdf(d1);
205
205
  const term3 = -r * K * erT * cdf(d2);
206
206
  return (term1 - term2 - term3) / 365;
@@ -244,7 +244,7 @@ export function bsVega(
244
244
  * @returns Implied volatility or null if convergence fails
245
245
  */
246
246
  export function solveIV(
247
- type: 'call' | 'put',
247
+ type: "call" | "put",
248
248
  marketPrice: number,
249
249
  S: number,
250
250
  K: number,
@@ -323,7 +323,7 @@ export function solveIV(
323
323
  * @returns Option price
324
324
  */
325
325
  export function bachelierPrice(
326
- type: 'call' | 'put',
326
+ type: "call" | "put",
327
327
  S: number,
328
328
  K: number,
329
329
  T: number,
@@ -331,16 +331,18 @@ export function bachelierPrice(
331
331
  q: number,
332
332
  sigma_n: number,
333
333
  ): number {
334
- if (T <= 0) return type === 'call' ? Math.max(S - K, 0) : Math.max(K - S, 0);
334
+ if (T <= 0) return type === "call" ? Math.max(S - K, 0) : Math.max(K - S, 0);
335
335
  if (sigma_n <= 0) {
336
336
  const forward = S * Math.exp((r - q) * T);
337
- return Math.exp(-r * T) * (type === 'call' ? Math.max(forward - K, 0) : Math.max(K - forward, 0));
337
+ return (
338
+ Math.exp(-r * T) * (type === "call" ? Math.max(forward - K, 0) : Math.max(K - forward, 0))
339
+ );
338
340
  }
339
341
  const forward = S * Math.exp((r - q) * T);
340
342
  const sqrtT = Math.sqrt(T);
341
343
  const d = (forward - K) / (sigma_n * sqrtT);
342
344
  const discount = Math.exp(-r * T);
343
- if (type === 'call') {
345
+ if (type === "call") {
344
346
  return discount * ((forward - K) * cdf(d) + sigma_n * sqrtT * pdf(d));
345
347
  } else {
346
348
  return discount * ((K - forward) * cdf(-d) + sigma_n * sqrtT * pdf(d));
@@ -353,7 +355,7 @@ export function bachelierPrice(
353
355
  * Put: -e^(-rT) * N(-d)
354
356
  */
355
357
  export function bachelierDelta(
356
- type: 'call' | 'put',
358
+ type: "call" | "put",
357
359
  S: number,
358
360
  K: number,
359
361
  T: number,
@@ -362,13 +364,13 @@ export function bachelierDelta(
362
364
  sigma_n: number,
363
365
  ): number {
364
366
  if (T <= 0 || sigma_n <= 0) {
365
- if (type === 'call') return S > K ? 1 : 0;
367
+ if (type === "call") return S > K ? 1 : 0;
366
368
  return S < K ? -1 : 0;
367
369
  }
368
370
  const forward = S * Math.exp((r - q) * T);
369
371
  const d = (forward - K) / (sigma_n * Math.sqrt(T));
370
372
  const discount = Math.exp(-r * T);
371
- return type === 'call' ? discount * cdf(d) : -discount * cdf(-d);
373
+ return type === "call" ? discount * cdf(d) : -discount * cdf(-d);
372
374
  }
373
375
 
374
376
  /**
@@ -387,7 +389,7 @@ export function bachelierGamma(
387
389
  const forward = S * Math.exp((r - q) * T);
388
390
  const sqrtT = Math.sqrt(T);
389
391
  const d = (forward - K) / (sigma_n * sqrtT);
390
- return Math.exp(-r * T) * pdf(d) / (sigma_n * sqrtT);
392
+ return (Math.exp(-r * T) * pdf(d)) / (sigma_n * sqrtT);
391
393
  }
392
394
 
393
395
  /**
@@ -396,7 +398,7 @@ export function bachelierGamma(
396
398
  * Formula: -(e^(-rT) * sigma_n * n(d) / (2 * sqrt(T)) - r * price) / 365
397
399
  */
398
400
  export function bachelierTheta(
399
- type: 'call' | 'put',
401
+ type: "call" | "put",
400
402
  S: number,
401
403
  K: number,
402
404
  T: number,
@@ -413,7 +415,7 @@ export function bachelierTheta(
413
415
  // dV/dT = -e^(-rT) * sigma_n * n(d) / (2*sqrt(T)) + r * price (positive = more time = more value)
414
416
  // theta = -dV/dT per year, then / 365 for per-calendar-day
415
417
  // annualTheta is negative for long options (time decay)
416
- const annualTheta = -discount * sigma_n * pdf(d) / (2 * sqrtT) + r * price;
418
+ const annualTheta = (-discount * sigma_n * pdf(d)) / (2 * sqrtT) + r * price;
417
419
  return annualTheta / 365;
418
420
  }
419
421
 
@@ -432,7 +434,7 @@ export function bachelierVega(
432
434
  if (T <= 0 || sigma_n <= 0) return 0;
433
435
  const forward = S * Math.exp((r - q) * T);
434
436
  const d = (forward - K) / (sigma_n * Math.sqrt(T));
435
- return Math.exp(-r * T) * Math.sqrt(T) * pdf(d) / 100;
437
+ return (Math.exp(-r * T) * Math.sqrt(T) * pdf(d)) / 100;
436
438
  }
437
439
 
438
440
  /**
@@ -450,7 +452,7 @@ export function bachelierVega(
450
452
  * @returns Normal implied volatility (dollar vol, e.g., ~800 for SPX) or null
451
453
  */
452
454
  export function solveNormalIV(
453
- type: 'call' | 'put',
455
+ type: "call" | "put",
454
456
  marketPrice: number,
455
457
  S: number,
456
458
  K: number,
@@ -527,18 +529,26 @@ export function computeLegGreeks(
527
529
  underlyingPrice: number,
528
530
  strike: number,
529
531
  dte: number,
530
- type: 'C' | 'P',
532
+ type: "C" | "P",
531
533
  riskFreeRate: number,
532
534
  dividendYield: number,
533
535
  ): GreeksResult {
534
536
  const T = dte / 365;
535
- const bsType = type === 'C' ? 'call' : 'put';
537
+ const bsType = type === "C" ? "call" : "put";
536
538
  const nullResult: GreeksResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };
537
539
 
538
540
  if (dte < BACHELIER_DTE_THRESHOLD) {
539
541
  // Bachelier normal model for short-dated options (dte < 0.1 days / ~2.4 hours).
540
542
  // iv field stores normal (dollar) volatility, not log-normal vol.
541
- const iv = solveNormalIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);
543
+ const iv = solveNormalIV(
544
+ bsType,
545
+ optionPrice,
546
+ underlyingPrice,
547
+ strike,
548
+ T,
549
+ riskFreeRate,
550
+ dividendYield,
551
+ );
542
552
  if (iv === null) return nullResult;
543
553
  return {
544
554
  delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
@@ -546,7 +556,7 @@ export function computeLegGreeks(
546
556
  theta: bachelierTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
547
557
  vega: bachelierVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
548
558
  iv,
549
- model: 'bachelier',
559
+ model: "bachelier",
550
560
  };
551
561
  }
552
562
 
@@ -560,6 +570,6 @@ export function computeLegGreeks(
560
570
  theta: bsTheta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
561
571
  vega: bsVega(underlyingPrice, strike, T, riskFreeRate, dividendYield, iv),
562
572
  iv,
563
- model: 'bs',
573
+ model: "bs",
564
574
  };
565
575
  }
@@ -23,7 +23,13 @@ import { getSyncMetadataJson } from "../db/json-adapters.ts";
23
23
  import { getBlocksDir } from "../sync/index.ts";
24
24
 
25
25
  // Re-export CSV discovery types and functions from shared module
26
- export { type CsvMappings, type CsvType, detectCsvType, discoverCsvFiles, logCsvDiscoveryWarning } from "./csv-discovery.ts";
26
+ export {
27
+ type CsvMappings,
28
+ type CsvType,
29
+ detectCsvType,
30
+ discoverCsvFiles,
31
+ logCsvDiscoveryWarning,
32
+ } from "./csv-discovery.ts";
27
33
  import { type CsvType, discoverCsvFiles } from "./csv-discovery.ts";
28
34
 
29
35
  function resolveBlocksBaseDir(baseDir: string): string {
@@ -81,17 +87,14 @@ function parseDatePreservingCalendarDay(dateStr: string): Date {
81
87
  /**
82
88
  * Parse numeric value from CSV string
83
89
  */
84
- function parseNumber(
85
- value: string | undefined,
86
- defaultValue?: number
87
- ): number {
90
+ function parseNumber(value: string | undefined, defaultValue?: number): number {
88
91
  if (!value || value.trim() === "" || value.toLowerCase() === "nan") {
89
92
  if (defaultValue !== undefined) return defaultValue;
90
93
  return 0;
91
94
  }
92
95
  const cleaned = value.replace(/[$,%]/g, "").trim();
93
96
  const parsed = parseFloat(cleaned);
94
- return isNaN(parsed) ? defaultValue ?? 0 : parsed;
97
+ return isNaN(parsed) ? (defaultValue ?? 0) : parsed;
95
98
  }
96
99
 
97
100
  const KNOWN_TRADE_COLUMNS = new Set([
@@ -129,7 +132,10 @@ const KNOWN_TRADE_COLUMNS = new Set([
129
132
  */
130
133
  function parseCSV(content: string): Record<string, string>[] {
131
134
  // Strip UTF-8 BOM if present (common in Windows/Excel CSV exports)
132
- const lines = content.replace(/^\uFEFF/, "").trim().split("\n");
135
+ const lines = content
136
+ .replace(/^\uFEFF/, "")
137
+ .trim()
138
+ .split("\n");
133
139
  if (lines.length < 2) return [];
134
140
 
135
141
  const headers = parseCSVLine(lines[0]);
@@ -205,14 +211,10 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
205
211
  legs,
206
212
  premium: parseNumber(raw["Premium"]),
207
213
  premiumPrecision,
208
- closingPrice: raw["Closing Price"]
209
- ? parseNumber(raw["Closing Price"])
210
- : undefined,
214
+ closingPrice: raw["Closing Price"] ? parseNumber(raw["Closing Price"]) : undefined,
211
215
  dateClosed,
212
216
  timeClosed: raw["Time Closed"] || undefined,
213
- avgClosingCost: raw["Avg. Closing Cost"]
214
- ? parseNumber(raw["Avg. Closing Cost"])
215
- : undefined,
217
+ avgClosingCost: raw["Avg. Closing Cost"] ? parseNumber(raw["Avg. Closing Cost"]) : undefined,
216
218
  reasonForClose: raw["Reason For Close"] || undefined,
217
219
  pl: parseNumber(raw["P/L"]),
218
220
  numContracts: Math.round(parseNumber(raw["No. of Contracts"], 1)),
@@ -221,27 +223,21 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
221
223
  strategy,
222
224
  openingCommissionsFees: parseNumber(
223
225
  raw["Opening Commissions + Fees"] || raw["Opening comms & fees"],
224
- 0
226
+ 0,
225
227
  ),
226
228
  closingCommissionsFees: parseNumber(
227
229
  raw["Closing Commissions + Fees"] || raw["Closing comms & fees"],
228
- 0
230
+ 0,
229
231
  ),
230
232
  openingShortLongRatio: parseNumber(raw["Opening Short/Long Ratio"], 0),
231
233
  closingShortLongRatio: raw["Closing Short/Long Ratio"]
232
234
  ? parseNumber(raw["Closing Short/Long Ratio"])
233
235
  : undefined,
234
- openingVix: raw["Opening VIX"]
235
- ? parseNumber(raw["Opening VIX"])
236
- : undefined,
237
- closingVix: raw["Closing VIX"]
238
- ? parseNumber(raw["Closing VIX"])
239
- : undefined,
236
+ openingVix: raw["Opening VIX"] ? parseNumber(raw["Opening VIX"]) : undefined,
237
+ closingVix: raw["Closing VIX"] ? parseNumber(raw["Closing VIX"]) : undefined,
240
238
  gap: raw["Gap"] ? parseNumber(raw["Gap"]) : undefined,
241
239
  movement: raw["Movement"] ? parseNumber(raw["Movement"]) : undefined,
242
- maxProfit: raw["Max Profit"]
243
- ? parseNumber(raw["Max Profit"])
244
- : undefined,
240
+ maxProfit: raw["Max Profit"] ? parseNumber(raw["Max Profit"]) : undefined,
245
241
  maxLoss: raw["Max Loss"] ? parseNumber(raw["Max Loss"]) : undefined,
246
242
  };
247
243
 
@@ -268,7 +264,7 @@ function convertToTrade(raw: Record<string, string>, blockId?: string): Trade |
268
264
  */
269
265
  function convertToDailyLogEntry(
270
266
  raw: Record<string, string>,
271
- blockId?: string
267
+ blockId?: string,
272
268
  ): DailyLogEntry | null {
273
269
  try {
274
270
  const date = parseDatePreservingCalendarDay(raw["Date"]);
@@ -298,7 +294,7 @@ function convertToDailyLogEntry(
298
294
  async function loadTrades(
299
295
  blockPath: string,
300
296
  filename: string = "tradelog.csv",
301
- blockId?: string
297
+ blockId?: string,
302
298
  ): Promise<Trade[]> {
303
299
  const tradelogPath = path.join(blockPath, filename);
304
300
  const content = await fs.readFile(tradelogPath, "utf-8");
@@ -314,8 +310,7 @@ async function loadTrades(
314
310
 
315
311
  // Sort by date and time
316
312
  trades.sort((a, b) => {
317
- const dateCompare =
318
- new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();
313
+ const dateCompare = new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime();
319
314
  if (dateCompare !== 0) return dateCompare;
320
315
  return a.timeOpened.localeCompare(b.timeOpened);
321
316
  });
@@ -332,7 +327,7 @@ async function loadTrades(
332
327
  async function loadDailyLogs(
333
328
  blockPath: string,
334
329
  blockId: string,
335
- filename: string = "dailylog.csv"
330
+ filename: string = "dailylog.csv",
336
331
  ): Promise<DailyLogEntry[] | undefined> {
337
332
  const dailylogPath = path.join(blockPath, filename);
338
333
 
@@ -350,9 +345,7 @@ async function loadDailyLogs(
350
345
  }
351
346
 
352
347
  // Sort by date
353
- entries.sort(
354
- (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
355
- );
348
+ entries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
356
349
 
357
350
  return entries.length > 0 ? entries : undefined;
358
351
  } catch {
@@ -365,10 +358,7 @@ async function loadDailyLogs(
365
358
  * Load a complete block (trades + optional daily logs).
366
359
  * Uses csv-discovery header sniffing for file resolution.
367
360
  */
368
- export async function loadBlock(
369
- baseDir: string,
370
- blockId: string
371
- ): Promise<LoadedBlock> {
361
+ export async function loadBlock(baseDir: string, blockId: string): Promise<LoadedBlock> {
372
362
  const blocksDir = resolveBlocksBaseDir(baseDir);
373
363
  const blockPath = path.join(blocksDir, blockId);
374
364
 
@@ -469,14 +459,17 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
469
459
  }
470
460
 
471
461
  // Build a map of block_id -> trade stats
472
- const tradeStats = new Map<string, {
473
- tradeCount: number;
474
- strategies: string[];
475
- minDate: Date | null;
476
- maxDate: Date | null;
477
- totalPl: number;
478
- netPl: number;
479
- }>();
462
+ const tradeStats = new Map<
463
+ string,
464
+ {
465
+ tradeCount: number;
466
+ strategies: string[];
467
+ minDate: Date | null;
468
+ maxDate: Date | null;
469
+ totalPl: number;
470
+ netPl: number;
471
+ }
472
+ >();
480
473
 
481
474
  for (const row of tradeStatsReader.getRows()) {
482
475
  const blockId = row[0] as string;
@@ -510,13 +503,16 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
510
503
  GROUP BY r.block_id
511
504
  `);
512
505
 
513
- const reportingStats = new Map<string, {
514
- tradeCount: number;
515
- strategyCount: number;
516
- totalPL: number;
517
- minDate: string | null;
518
- maxDate: string | null;
519
- }>();
506
+ const reportingStats = new Map<
507
+ string,
508
+ {
509
+ tradeCount: number;
510
+ strategyCount: number;
511
+ totalPL: number;
512
+ minDate: string | null;
513
+ maxDate: string | null;
514
+ }
515
+ >();
520
516
 
521
517
  for (const row of reportingReader.getRows()) {
522
518
  const blockId = row[0] as string;
@@ -530,10 +526,13 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
530
526
  }
531
527
 
532
528
  // Query 3: Sync metadata to determine hasDailyLog/hasReportingLog
533
- const syncMeta = new Map<string, {
534
- hasDailyLog: boolean;
535
- hasReportingLog: boolean;
536
- }>();
529
+ const syncMeta = new Map<
530
+ string,
531
+ {
532
+ hasDailyLog: boolean;
533
+ hasReportingLog: boolean;
534
+ }
535
+ >();
537
536
 
538
537
  if (isParquetMode()) {
539
538
  // In Parquet mode, read .sync-meta.json files from blocksDir
@@ -654,27 +653,21 @@ export async function listBlocks(baseDir: string): Promise<BlockInfo[]> {
654
653
  /**
655
654
  * Normalize header names using column aliases
656
655
  */
657
- function normalizeRecordHeaders(
658
- raw: Record<string, string>
659
- ): Record<string, string> {
656
+ function normalizeRecordHeaders(raw: Record<string, string>): Record<string, string> {
660
657
  const normalized: Record<string, string> = { ...raw };
661
- Object.entries(REPORTING_TRADE_COLUMN_ALIASES).forEach(
662
- ([alias, canonical]) => {
663
- if (normalized[alias] !== undefined) {
664
- normalized[canonical] = normalized[alias];
665
- delete normalized[alias];
666
- }
658
+ Object.entries(REPORTING_TRADE_COLUMN_ALIASES).forEach(([alias, canonical]) => {
659
+ if (normalized[alias] !== undefined) {
660
+ normalized[canonical] = normalized[alias];
661
+ delete normalized[alias];
667
662
  }
668
- );
663
+ });
669
664
  return normalized;
670
665
  }
671
666
 
672
667
  /**
673
668
  * Convert raw CSV record to ReportingTrade object
674
669
  */
675
- export function convertToReportingTrade(
676
- raw: Record<string, string>
677
- ): ReportingTrade | null {
670
+ export function convertToReportingTrade(raw: Record<string, string>): ReportingTrade | null {
678
671
  // Check if this is a TAT format row
679
672
  const keys = Object.keys(raw);
680
673
  if (isTatFormat(keys)) {
@@ -725,7 +718,7 @@ export function convertToReportingTrade(
725
718
  */
726
719
  export async function loadReportingLog(
727
720
  baseDir: string,
728
- blockId: string
721
+ blockId: string,
729
722
  ): Promise<ReportingTrade[]> {
730
723
  const blocksDir = resolveBlocksBaseDir(baseDir);
731
724
  const blockPath = path.join(blocksDir, blockId);
@@ -754,10 +747,7 @@ export async function loadReportingLog(
754
747
  }
755
748
 
756
749
  // Sort by date
757
- trades.sort(
758
- (a, b) =>
759
- new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
760
- );
750
+ trades.sort((a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime());
761
751
 
762
752
  return trades;
763
753
  }
@@ -808,7 +798,7 @@ function toKebabCase(str: string): string {
808
798
  */
809
799
  function validateCsvColumns(
810
800
  records: Record<string, string>[],
811
- csvType: "tradelog" | "dailylog" | "reportinglog"
801
+ csvType: "tradelog" | "dailylog" | "reportinglog",
812
802
  ): { valid: boolean; error?: string } {
813
803
  if (records.length === 0) {
814
804
  return { valid: false, error: "CSV file is empty or has no data rows" };
@@ -849,9 +839,7 @@ function validateCsvColumns(
849
839
  // Required columns for OO reporting log (with aliases)
850
840
  const dateOpenedAliases = ["Date Opened", "date_opened"];
851
841
  const plAliases = ["P/L", "pl"];
852
- const hasDateOpened = dateOpenedAliases.some((col) =>
853
- headers.includes(col)
854
- );
842
+ const hasDateOpened = dateOpenedAliases.some((col) => headers.includes(col));
855
843
  const hasPl = plAliases.some((col) => headers.includes(col));
856
844
  const missing: string[] = [];
857
845
  if (!hasDateOpened) missing.push("Date Opened");
@@ -873,7 +861,7 @@ function validateCsvColumns(
873
861
  * Import a CSV file into the blocks directory
874
862
  *
875
863
  * Requires local filesystem access. The MCP server must be running locally
876
- * (via npx tradeblocks-mcp or mcpb desktop extension) to access files.
864
+ * (via npx tradeblocks-mcp) to access files.
877
865
  *
878
866
  * @param baseDir - Base directory for blocks
879
867
  * @param options - Import options: csvPath, blockName, csvType
@@ -881,7 +869,7 @@ function validateCsvColumns(
881
869
  */
882
870
  export async function importCsv(
883
871
  baseDir: string,
884
- options: ImportCsvOptions
872
+ options: ImportCsvOptions,
885
873
  ): Promise<ImportCsvResult> {
886
874
  const { csvPath, blockName } = options;
887
875
  let { csvType = "tradelog" } = options;
@@ -918,9 +906,7 @@ export async function importCsv(
918
906
  const blockId = toKebabCase(name);
919
907
 
920
908
  if (!blockId) {
921
- throw new Error(
922
- "Could not derive a valid block ID from the filename or provided name"
923
- );
909
+ throw new Error("Could not derive a valid block ID from the filename or provided name");
924
910
  }
925
911
 
926
912
  // Check if block already exists
@@ -928,7 +914,7 @@ export async function importCsv(
928
914
  try {
929
915
  await fs.access(blockPath);
930
916
  throw new Error(
931
- `Block "${blockId}" already exists. Use a different blockName or delete the existing block first.`
917
+ `Block "${blockId}" already exists. Use a different blockName or delete the existing block first.`,
932
918
  );
933
919
  } catch (error) {
934
920
  // Directory doesn't exist - good, we can create it
@@ -16,9 +16,9 @@
16
16
  * Manual-only — purposefully side-effectful (opens a live DuckDB, calls the
17
17
  * singleton provider). No unit test; exists for operator inspection.
18
18
  */
19
- import * as path from "path";
20
19
  import { DuckDBInstance } from "@duckdb/node-api";
21
20
  import { getProvider } from "./market-provider.ts";
21
+ import { resolveDbPath } from "../db/data-root.ts";
22
22
 
23
23
  /** Per-date probe result returned alongside the summary deltas. */
24
24
  export interface CalibrationProbeDateResult {
@@ -59,7 +59,7 @@ export async function calibrateProviderFetch(
59
59
  dataRoot: string,
60
60
  ): Promise<CalibrationProbeResult> {
61
61
  const provider = getProvider();
62
- const dbPath = path.join(dataRoot, "database", "market.duckdb");
62
+ const dbPath = resolveDbPath(dataRoot, "market");
63
63
  const instance = await DuckDBInstance.create(dbPath);
64
64
  const conn = await instance.connect();
65
65
 
@@ -117,8 +117,7 @@ export async function calibrateProviderFetch(
117
117
  }
118
118
  }
119
119
 
120
- const avgCloseDelta =
121
- deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;
120
+ const avgCloseDelta = deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0;
122
121
  const maxCloseDelta = deltas.length > 0 ? Math.max(...deltas) : 0;
123
122
  return { avgCloseDelta, maxCloseDelta, dateResults };
124
123
  }
@@ -83,7 +83,7 @@ export function deduplicateContracts(contracts: ContractRow[]): ContractRow[] {
83
83
  const existing = map.get(key);
84
84
  if (!existing) {
85
85
  map.set(key, c);
86
- } else if (c.ticker.startsWith('SPXW') && !existing.ticker.startsWith('SPXW')) {
86
+ } else if (c.ticker.startsWith("SPXW") && !existing.ticker.startsWith("SPXW")) {
87
87
  map.set(key, c);
88
88
  }
89
89
  }
@@ -132,7 +132,7 @@ export async function loadChain(
132
132
  ): Promise<ChainLoadResult> {
133
133
  throw new Error(
134
134
  "chain-loader.loadChain has been removed. " +
135
- "Use stores.chain.readChain(underlying, date) instead — empty array is the new skip signal.",
135
+ "Use stores.chain.readChain(underlying, date) instead — empty array is the new skip signal.",
136
136
  );
137
137
  }
138
138
 
@@ -150,6 +150,6 @@ export async function loadChainsBulk(
150
150
  ): Promise<Map<string, ChainLoadResult>> {
151
151
  throw new Error(
152
152
  "chain-loader.loadChainsBulk has been removed. " +
153
- "Use a per-date loop with stores.chain.readChain(underlying, date) instead.",
153
+ "Use a per-date loop with stores.chain.readChain(underlying, date) instead.",
154
154
  );
155
155
  }