tradeblocks-mcp 1.2.1 → 1.3.0-beta.2

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.
@@ -20348,6 +20348,138 @@ var REPORTING_TRADE_COLUMN_ALIASES = {
20348
20348
  "PL": "P/L"
20349
20349
  };
20350
20350
 
20351
+ // ../lib/processing/tat-adapter.ts
20352
+ var TAT_SIGNATURE_HEADERS = ["tradeid", "profitloss", "buyingpower"];
20353
+ function isTatFormat(headers) {
20354
+ const lower = headers.map((h) => h.toLowerCase().trim());
20355
+ return TAT_SIGNATURE_HEADERS.every((sig) => lower.includes(sig));
20356
+ }
20357
+ function parseTatDate(dateStr) {
20358
+ if (!dateStr || dateStr.trim() === "") return null;
20359
+ const isoMatch = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})/);
20360
+ if (isoMatch) {
20361
+ const [, year, month, day] = isoMatch;
20362
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
20363
+ }
20364
+ const usMatch = dateStr.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
20365
+ if (usMatch) {
20366
+ const [, month, day, year] = usMatch;
20367
+ return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
20368
+ }
20369
+ return null;
20370
+ }
20371
+ function formatTatTime(timeStr) {
20372
+ if (!timeStr || timeStr.trim() === "") return void 0;
20373
+ if (/[AP]M$/i.test(timeStr.trim())) return timeStr.trim();
20374
+ const match = timeStr.match(/^(\d{1,2}):(\d{2})/);
20375
+ if (!match) return void 0;
20376
+ const hours = parseInt(match[1], 10);
20377
+ const minutes = match[2];
20378
+ const period = hours >= 12 ? "PM" : "AM";
20379
+ const displayHours = hours % 12 || 12;
20380
+ return `${displayHours}:${minutes} ${period}`;
20381
+ }
20382
+ function buildTatLegsString(row) {
20383
+ const symbol = (row.UnderlyingSymbol || "").trim();
20384
+ const tradeType = (row.TradeType || "").trim();
20385
+ const shortPut = (row.ShortPut || "").trim();
20386
+ const shortCall = (row.ShortCall || "").trim();
20387
+ const longPut = (row.LongPut || "").trim();
20388
+ const longCall = (row.LongCall || "").trim();
20389
+ const isPresent = (v) => v !== "" && v !== "0";
20390
+ const shortParts = [];
20391
+ if (isPresent(shortPut)) shortParts.push(`${shortPut}P`);
20392
+ if (isPresent(shortCall)) shortParts.push(`${shortCall}C`);
20393
+ const shortLeg = shortParts.join("/");
20394
+ const longParts = [];
20395
+ if (isPresent(longPut)) longParts.push(`${longPut}P`);
20396
+ if (isPresent(longCall)) longParts.push(`${longCall}C`);
20397
+ const longLeg = longParts.join("/");
20398
+ const prefix = symbol ? `${symbol} ` : "";
20399
+ const suffix = tradeType ? ` ${tradeType}` : "";
20400
+ if (shortLeg && longLeg) {
20401
+ return `${prefix}${shortLeg} | ${longLeg}${suffix}`;
20402
+ }
20403
+ if (shortLeg) return `${prefix}${shortLeg}${suffix}`;
20404
+ if (longLeg) return `${prefix}${longLeg}${suffix}`;
20405
+ return tradeType || "Unknown";
20406
+ }
20407
+ function parseNumber(value, defaultValue) {
20408
+ if (!value || value.trim() === "" || value.toLowerCase() === "nan") {
20409
+ return defaultValue ?? 0;
20410
+ }
20411
+ const cleaned = value.replace(/[$,%]/g, "").trim();
20412
+ const parsed = parseFloat(cleaned);
20413
+ return isNaN(parsed) ? defaultValue ?? 0 : parsed;
20414
+ }
20415
+ function normalizeRowKeys(row) {
20416
+ const keyMap = {};
20417
+ for (const key of Object.keys(row)) {
20418
+ keyMap[key.toLowerCase()] = key;
20419
+ }
20420
+ const canonicalKeys = [
20421
+ "OpenDate",
20422
+ "Date",
20423
+ "CloseDate",
20424
+ "OpenTime",
20425
+ "CloseTime",
20426
+ "TimeOpened",
20427
+ "TimeClosed",
20428
+ "ProfitLoss",
20429
+ "PriceOpen",
20430
+ "PriceClose",
20431
+ "TotalPremium",
20432
+ "Qty",
20433
+ "Strategy",
20434
+ "Template",
20435
+ "Status",
20436
+ "UnderlyingSymbol",
20437
+ "TradeType",
20438
+ "ShortPut",
20439
+ "ShortCall",
20440
+ "LongPut",
20441
+ "LongCall"
20442
+ ];
20443
+ const normalized = { ...row };
20444
+ for (const canonical of canonicalKeys) {
20445
+ if (!(canonical in normalized)) {
20446
+ const actual = keyMap[canonical.toLowerCase()];
20447
+ if (actual) normalized[canonical] = row[actual];
20448
+ }
20449
+ }
20450
+ return normalized;
20451
+ }
20452
+ function convertTatRowToReportingTrade(row) {
20453
+ row = normalizeRowKeys(row);
20454
+ const dateOpened = parseTatDate(row.OpenDate) ?? parseTatDate(row.Date);
20455
+ if (!dateOpened || isNaN(dateOpened.getTime())) return null;
20456
+ const plStr = (row.ProfitLoss || "").trim();
20457
+ if (!plStr) return null;
20458
+ const dateClosed = parseTatDate(row.CloseDate) ?? void 0;
20459
+ const timeOpened = formatTatTime(row.OpenTime) ?? formatTatTime(row.TimeOpened);
20460
+ const timeClosed = formatTatTime(row.CloseTime) ?? formatTatTime(row.TimeClosed);
20461
+ const legs = buildTatLegsString(row);
20462
+ const qty = parseNumber(row.Qty, 1);
20463
+ return {
20464
+ // Template = strategy name (matches OO's Strategy); Strategy = user-defined grouping
20465
+ strategy: (row.Template || row.Strategy || "").trim() || "Unknown",
20466
+ dateOpened,
20467
+ timeOpened,
20468
+ openingPrice: 0,
20469
+ // OO reports underlying price level; TAT does not provide this
20470
+ legs,
20471
+ initialPremium: qty !== 0 ? parseNumber(row.TotalPremium) / qty : parseNumber(row.TotalPremium),
20472
+ // Per-spread premium (matches OO semantics)
20473
+ numContracts: qty,
20474
+ // Qty = spreads (matches OO semantics)
20475
+ pl: parseNumber(row.ProfitLoss),
20476
+ closingPrice: row.PriceClose ? parseNumber(row.PriceClose) : void 0,
20477
+ dateClosed,
20478
+ timeClosed,
20479
+ reasonForClose: (row.Status || "").trim() || void 0
20480
+ };
20481
+ }
20482
+
20351
20483
  // ../lib/utils/trade-frequency.ts
20352
20484
  var MS_PER_DAY = 1e3 * 60 * 60 * 24;
20353
20485
 
@@ -20360,7 +20492,7 @@ function parseDatePreservingCalendarDay(dateStr) {
20360
20492
  }
20361
20493
  return new Date(dateStr);
20362
20494
  }
20363
- function parseNumber(value, defaultValue) {
20495
+ function parseNumber2(value, defaultValue) {
20364
20496
  if (!value || value.trim() === "" || value.toLowerCase() === "nan") {
20365
20497
  if (defaultValue !== void 0) return defaultValue;
20366
20498
  return 0;
@@ -20479,6 +20611,11 @@ async function detectCsvType(filePath) {
20479
20611
  if (hasSimpleDate && hasValue && matchedTradeColumns.length < 2) {
20480
20612
  return "dailylog";
20481
20613
  }
20614
+ const tatSignature = ["tradeid", "profitloss", "buyingpower"];
20615
+ const isTat = tatSignature.every((sig) => headers.includes(sig));
20616
+ if (isTat) {
20617
+ return "reportinglog";
20618
+ }
20482
20619
  const reportingAliases = Object.keys(REPORTING_TRADE_COLUMN_ALIASES).map(
20483
20620
  (k) => k.toLowerCase()
20484
20621
  );
@@ -20609,36 +20746,36 @@ function convertToTrade(raw) {
20609
20746
  const trade = {
20610
20747
  dateOpened,
20611
20748
  timeOpened: raw["Time Opened"] || "00:00:00",
20612
- openingPrice: parseNumber(raw["Opening Price"]),
20749
+ openingPrice: parseNumber2(raw["Opening Price"]),
20613
20750
  legs,
20614
- premium: parseNumber(raw["Premium"]),
20751
+ premium: parseNumber2(raw["Premium"]),
20615
20752
  premiumPrecision,
20616
- closingPrice: raw["Closing Price"] ? parseNumber(raw["Closing Price"]) : void 0,
20753
+ closingPrice: raw["Closing Price"] ? parseNumber2(raw["Closing Price"]) : void 0,
20617
20754
  dateClosed,
20618
20755
  timeClosed: raw["Time Closed"] || void 0,
20619
- avgClosingCost: raw["Avg. Closing Cost"] ? parseNumber(raw["Avg. Closing Cost"]) : void 0,
20756
+ avgClosingCost: raw["Avg. Closing Cost"] ? parseNumber2(raw["Avg. Closing Cost"]) : void 0,
20620
20757
  reasonForClose: raw["Reason For Close"] || void 0,
20621
- pl: parseNumber(raw["P/L"]),
20622
- numContracts: Math.round(parseNumber(raw["No. of Contracts"], 1)),
20623
- fundsAtClose: parseNumber(raw["Funds at Close"]),
20624
- marginReq: parseNumber(raw["Margin Req."]),
20758
+ pl: parseNumber2(raw["P/L"]),
20759
+ numContracts: Math.round(parseNumber2(raw["No. of Contracts"], 1)),
20760
+ fundsAtClose: parseNumber2(raw["Funds at Close"]),
20761
+ marginReq: parseNumber2(raw["Margin Req."]),
20625
20762
  strategy,
20626
- openingCommissionsFees: parseNumber(
20763
+ openingCommissionsFees: parseNumber2(
20627
20764
  raw["Opening Commissions + Fees"] || raw["Opening comms & fees"],
20628
20765
  0
20629
20766
  ),
20630
- closingCommissionsFees: parseNumber(
20767
+ closingCommissionsFees: parseNumber2(
20631
20768
  raw["Closing Commissions + Fees"] || raw["Closing comms & fees"],
20632
20769
  0
20633
20770
  ),
20634
- openingShortLongRatio: parseNumber(raw["Opening Short/Long Ratio"], 0),
20635
- closingShortLongRatio: raw["Closing Short/Long Ratio"] ? parseNumber(raw["Closing Short/Long Ratio"]) : void 0,
20636
- openingVix: raw["Opening VIX"] ? parseNumber(raw["Opening VIX"]) : void 0,
20637
- closingVix: raw["Closing VIX"] ? parseNumber(raw["Closing VIX"]) : void 0,
20638
- gap: raw["Gap"] ? parseNumber(raw["Gap"]) : void 0,
20639
- movement: raw["Movement"] ? parseNumber(raw["Movement"]) : void 0,
20640
- maxProfit: raw["Max Profit"] ? parseNumber(raw["Max Profit"]) : void 0,
20641
- maxLoss: raw["Max Loss"] ? parseNumber(raw["Max Loss"]) : void 0
20771
+ openingShortLongRatio: parseNumber2(raw["Opening Short/Long Ratio"], 0),
20772
+ closingShortLongRatio: raw["Closing Short/Long Ratio"] ? parseNumber2(raw["Closing Short/Long Ratio"]) : void 0,
20773
+ openingVix: raw["Opening VIX"] ? parseNumber2(raw["Opening VIX"]) : void 0,
20774
+ closingVix: raw["Closing VIX"] ? parseNumber2(raw["Closing VIX"]) : void 0,
20775
+ gap: raw["Gap"] ? parseNumber2(raw["Gap"]) : void 0,
20776
+ movement: raw["Movement"] ? parseNumber2(raw["Movement"]) : void 0,
20777
+ maxProfit: raw["Max Profit"] ? parseNumber2(raw["Max Profit"]) : void 0,
20778
+ maxLoss: raw["Max Loss"] ? parseNumber2(raw["Max Loss"]) : void 0
20642
20779
  };
20643
20780
  const customFields = {};
20644
20781
  for (const [key, value] of Object.entries(raw)) {
@@ -20662,13 +20799,13 @@ function convertToDailyLogEntry(raw, blockId) {
20662
20799
  if (isNaN(date.getTime())) return null;
20663
20800
  return {
20664
20801
  date,
20665
- netLiquidity: parseNumber(raw["Net Liquidity"]),
20666
- currentFunds: parseNumber(raw["Current Funds"]),
20667
- withdrawn: parseNumber(raw["Withdrawn"], 0),
20668
- tradingFunds: parseNumber(raw["Trading Funds"]),
20669
- dailyPl: parseNumber(raw["P/L"]),
20670
- dailyPlPct: parseNumber(raw["P/L %"]),
20671
- drawdownPct: parseNumber(raw["Drawdown %"]),
20802
+ netLiquidity: parseNumber2(raw["Net Liquidity"]),
20803
+ currentFunds: parseNumber2(raw["Current Funds"]),
20804
+ withdrawn: parseNumber2(raw["Withdrawn"], 0),
20805
+ tradingFunds: parseNumber2(raw["Trading Funds"]),
20806
+ dailyPl: parseNumber2(raw["P/L"]),
20807
+ dailyPlPct: parseNumber2(raw["P/L %"]),
20808
+ drawdownPct: parseNumber2(raw["Drawdown %"]),
20672
20809
  blockId
20673
20810
  };
20674
20811
  } catch {
@@ -20935,6 +21072,10 @@ function normalizeRecordHeaders(raw) {
20935
21072
  return normalized;
20936
21073
  }
20937
21074
  function convertToReportingTrade(raw) {
21075
+ const keys = Object.keys(raw);
21076
+ if (isTatFormat(keys)) {
21077
+ return convertTatRowToReportingTrade(raw);
21078
+ }
20938
21079
  try {
20939
21080
  const normalized = normalizeRecordHeaders(raw);
20940
21081
  const dateOpened = parseDatePreservingCalendarDay(normalized["Date Opened"]);
@@ -20945,15 +21086,15 @@ function convertToReportingTrade(raw) {
20945
21086
  strategy,
20946
21087
  dateOpened,
20947
21088
  timeOpened: normalized["Time Opened"] || void 0,
20948
- openingPrice: parseNumber(normalized["Opening Price"]),
21089
+ openingPrice: parseNumber2(normalized["Opening Price"]),
20949
21090
  legs: normalized["Legs"] || "",
20950
- initialPremium: parseNumber(normalized["Initial Premium"]),
20951
- numContracts: parseNumber(normalized["No. of Contracts"], 1),
20952
- pl: parseNumber(normalized["P/L"]),
20953
- closingPrice: normalized["Closing Price"] ? parseNumber(normalized["Closing Price"]) : void 0,
21091
+ initialPremium: parseNumber2(normalized["Initial Premium"]),
21092
+ numContracts: parseNumber2(normalized["No. of Contracts"], 1),
21093
+ pl: parseNumber2(normalized["P/L"]),
21094
+ closingPrice: normalized["Closing Price"] ? parseNumber2(normalized["Closing Price"]) : void 0,
20954
21095
  dateClosed,
20955
21096
  timeClosed: normalized["Time Closed"] || void 0,
20956
- avgClosingCost: normalized["Avg. Closing Cost"] ? parseNumber(normalized["Avg. Closing Cost"]) : void 0,
21097
+ avgClosingCost: normalized["Avg. Closing Cost"] ? parseNumber2(normalized["Avg. Closing Cost"]) : void 0,
20957
21098
  reasonForClose: normalized["Reason For Close"] || void 0
20958
21099
  };
20959
21100
  } catch {
@@ -21173,6 +21314,9 @@ function validateCsvColumns(records, csvType) {
21173
21314
  break;
21174
21315
  }
21175
21316
  case "reportinglog": {
21317
+ if (isTatFormat(headers)) {
21318
+ break;
21319
+ }
21176
21320
  const dateOpenedAliases = ["Date Opened", "date_opened"];
21177
21321
  const plAliases = ["P/L", "pl"];
21178
21322
  const hasDateOpened = dateOpenedAliases.some(
@@ -21194,7 +21338,8 @@ function validateCsvColumns(records, csvType) {
21194
21338
  return { valid: true };
21195
21339
  }
21196
21340
  async function importCsv(baseDir, options) {
21197
- const { csvPath, blockName, csvType = "tradelog" } = options;
21341
+ const { csvPath, blockName } = options;
21342
+ let { csvType = "tradelog" } = options;
21198
21343
  try {
21199
21344
  await fs.access(csvPath);
21200
21345
  } catch {
@@ -21202,6 +21347,12 @@ async function importCsv(baseDir, options) {
21202
21347
  }
21203
21348
  const content = await fs.readFile(csvPath, "utf-8");
21204
21349
  const records = parseCSV(content);
21350
+ if (csvType === "tradelog" && records.length > 0) {
21351
+ const headers = Object.keys(records[0]);
21352
+ if (isTatFormat(headers)) {
21353
+ csvType = "reportinglog";
21354
+ }
21355
+ }
21205
21356
  const validation = validateCsvColumns(records, csvType);
21206
21357
  if (!validation.valid) {
21207
21358
  throw new Error(validation.error);
@@ -21283,6 +21434,7 @@ async function importCsv(baseDir, options) {
21283
21434
  return {
21284
21435
  blockId,
21285
21436
  name: name79,
21437
+ csvType,
21286
21438
  recordCount: records.length,
21287
21439
  dateRange,
21288
21440
  strategies,
@@ -21837,6 +21989,7 @@ var TICKER_FIELD_CANDIDATES = [
21837
21989
  "underlying",
21838
21990
  "Underlying",
21839
21991
  "underlyingSymbol",
21992
+ "UnderlyingSymbol",
21840
21993
  "Underlying Symbol"
21841
21994
  ];
21842
21995
  function normalizeTicker(value) {
@@ -22063,58 +22216,44 @@ async function insertTradeBatch(conn, blockId, records, startIdx, batchSize) {
22063
22216
  `;
22064
22217
  await conn.run(sql, params);
22065
22218
  }
22066
- async function insertReportingBatch(conn, blockId, records, startIdx, batchSize) {
22067
- const batch = records.slice(startIdx, startIdx + batchSize);
22219
+ function formatDateForDb(date) {
22220
+ if (!date || isNaN(date.getTime())) return null;
22221
+ const year = date.getFullYear();
22222
+ const month = String(date.getMonth() + 1).padStart(2, "0");
22223
+ const day = String(date.getDate()).padStart(2, "0");
22224
+ return `${year}-${month}-${day}`;
22225
+ }
22226
+ async function insertReportingBatch(conn, blockId, trades, tickers, startIdx, batchSize) {
22227
+ const batch = trades.slice(startIdx, startIdx + batchSize);
22228
+ const batchTickers = tickers.slice(startIdx, startIdx + batchSize);
22068
22229
  if (batch.length === 0) return;
22069
22230
  const columnsPerRow = 15;
22070
22231
  const placeholders = [];
22071
22232
  const params = [];
22072
22233
  for (let rowIdx = 0; rowIdx < batch.length; rowIdx++) {
22073
- const record = batch[rowIdx];
22234
+ const trade = batch[rowIdx];
22074
22235
  const baseParam = rowIdx * columnsPerRow + 1;
22075
22236
  const rowPlaceholders = Array.from(
22076
22237
  { length: columnsPerRow },
22077
22238
  (_, i) => `$${baseParam + i}`
22078
22239
  );
22079
22240
  placeholders.push(`(${rowPlaceholders.join(", ")})`);
22080
- const initialPremium = parseFloat(record["Initial Premium"]);
22081
- const numContracts = parseInt(record["No. of Contracts"], 10);
22082
- const pl = parseFloat(record["P/L"]);
22083
- const closingPrice = parseFloat(record["Closing Price"]);
22084
- const avgClosingCost = parseFloat(record["Avg. Closing Cost"]);
22085
- const openingPrice = parseFloat(record["Opening Price"]);
22086
- const ticker = resolveTickerFromCsvRow(record);
22087
22241
  params.push(
22088
22242
  blockId,
22089
- // block_id
22090
- normalizeCsvDate(record["Date Opened"]),
22091
- // date_opened
22092
- record["Time Opened"] || null,
22093
- // time_opened
22094
- record["Strategy"] || null,
22095
- // strategy
22096
- record["Legs"] || null,
22097
- // legs
22098
- isNaN(initialPremium) ? null : initialPremium,
22099
- // initial_premium
22100
- isNaN(numContracts) ? 1 : numContracts,
22101
- // num_contracts
22102
- isNaN(pl) ? 0 : pl,
22103
- // pl
22104
- normalizeCsvDate(record["Date Closed"]),
22105
- // date_closed
22106
- record["Time Closed"] || null,
22107
- // time_closed
22108
- isNaN(closingPrice) ? null : closingPrice,
22109
- // closing_price
22110
- isNaN(avgClosingCost) ? null : avgClosingCost,
22111
- // avg_closing_cost
22112
- record["Reason For Close"] || null,
22113
- // reason_for_close
22114
- isNaN(openingPrice) ? null : openingPrice,
22115
- // opening_price
22116
- ticker
22117
- // ticker
22243
+ formatDateForDb(trade.dateOpened),
22244
+ trade.timeOpened || null,
22245
+ trade.strategy || null,
22246
+ trade.legs || null,
22247
+ trade.initialPremium ?? null,
22248
+ trade.numContracts ?? 1,
22249
+ trade.pl ?? 0,
22250
+ formatDateForDb(trade.dateClosed),
22251
+ trade.timeClosed || null,
22252
+ trade.closingPrice ?? null,
22253
+ trade.avgClosingCost ?? null,
22254
+ trade.reasonForClose || null,
22255
+ trade.openingPrice ?? null,
22256
+ batchTickers[rowIdx]
22118
22257
  );
22119
22258
  }
22120
22259
  const sql = `
@@ -22214,8 +22353,17 @@ async function syncBlockInternal(conn, blockId, blockPath) {
22214
22353
  const reportingPath = path3.join(blockPath, optionalLogs.reportinglog);
22215
22354
  const reportingContent = await fs3.readFile(reportingPath, "utf-8");
22216
22355
  const reportingRecords = parseCSV2(reportingContent);
22217
- for (let i = 0; i < reportingRecords.length; i += batchSize) {
22218
- await insertReportingBatch(conn, blockId, reportingRecords, i, batchSize);
22356
+ const reportingTrades = [];
22357
+ const reportingTickers = [];
22358
+ for (const record of reportingRecords) {
22359
+ const trade = convertToReportingTrade(record);
22360
+ if (trade) {
22361
+ reportingTrades.push(trade);
22362
+ reportingTickers.push(resolveTickerFromCsvRow(record));
22363
+ }
22364
+ }
22365
+ for (let i = 0; i < reportingTrades.length; i += batchSize) {
22366
+ await insertReportingBatch(conn, blockId, reportingTrades, reportingTickers, i, batchSize);
22219
22367
  }
22220
22368
  }
22221
22369
  const newMetadata = {