tradeblocks-mcp 0.4.2 → 0.4.8

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.
@@ -20454,8 +20454,17 @@ async function discoverCsvFiles(folderPath) {
20454
20454
  if (csvFiles.includes("reportinglog.csv")) {
20455
20455
  mappings.reportinglog = "reportinglog.csv";
20456
20456
  }
20457
+ if (!mappings.reportinglog) {
20458
+ const strategyLogFile = csvFiles.find((f) => {
20459
+ const lower = f.toLowerCase();
20460
+ return lower.includes("strategy-trade-log") || lower.includes("strategylog") || lower.startsWith("strategy-log");
20461
+ });
20462
+ if (strategyLogFile) {
20463
+ mappings.reportinglog = strategyLogFile;
20464
+ }
20465
+ }
20457
20466
  for (const csvFile of csvFiles) {
20458
- if (csvFile === "tradelog.csv" || csvFile === "dailylog.csv" || csvFile === "reportinglog.csv") {
20467
+ if (csvFile === "tradelog.csv" || csvFile === "dailylog.csv" || csvFile === "reportinglog.csv" || csvFile === mappings.reportinglog) {
20459
20468
  continue;
20460
20469
  }
20461
20470
  const csvPath = path.join(folderPath, csvFile);
@@ -20761,6 +20770,19 @@ async function listBlocks(baseDir) {
20761
20770
  continue;
20762
20771
  }
20763
20772
  const hasDailyLog = !!dailylogFilename;
20773
+ const reportingLogFilename = await findReportingLogFile(blockPath, metadata);
20774
+ const hasReportingLog = !!reportingLogFilename;
20775
+ let reportingLogSummary;
20776
+ if (hasReportingLog && metadata?.reportingLogStats) {
20777
+ const reportingLogStale = reportingLogFilename ? !await isReportingLogCacheValid(blockPath, metadata, reportingLogFilename) : false;
20778
+ reportingLogSummary = {
20779
+ tradeCount: metadata.reportingLogStats.totalTrades,
20780
+ strategyCount: metadata.reportingLogStats.strategies.length,
20781
+ totalPL: metadata.reportingLogStats.totalPL,
20782
+ dateRange: metadata.reportingLogStats.dateRange,
20783
+ stale: reportingLogStale
20784
+ };
20785
+ }
20764
20786
  const cacheValid = metadata?.cachedStats && !needsMetadataUpdate && await isCacheValid(blockPath, metadata);
20765
20787
  if (cacheValid && metadata?.cachedStats) {
20766
20788
  blocks.push({
@@ -20768,13 +20790,15 @@ async function listBlocks(baseDir) {
20768
20790
  name: metadata.name || entry.name,
20769
20791
  tradeCount: metadata.tradeCount,
20770
20792
  hasDailyLog,
20793
+ hasReportingLog,
20771
20794
  dateRange: {
20772
20795
  start: metadata.dateRange.start ? new Date(metadata.dateRange.start) : null,
20773
20796
  end: metadata.dateRange.end ? new Date(metadata.dateRange.end) : null
20774
20797
  },
20775
20798
  strategies: metadata.strategies,
20776
20799
  totalPl: metadata.cachedStats.totalPl,
20777
- netPl: metadata.cachedStats.netPl
20800
+ netPl: metadata.cachedStats.netPl,
20801
+ reportingLog: reportingLogSummary
20778
20802
  });
20779
20803
  } else {
20780
20804
  try {
@@ -20800,13 +20824,15 @@ async function listBlocks(baseDir) {
20800
20824
  name: updatedMetadata.name,
20801
20825
  tradeCount: trades.length,
20802
20826
  hasDailyLog,
20827
+ hasReportingLog,
20803
20828
  dateRange: {
20804
20829
  start: updatedMetadata.dateRange.start ? new Date(updatedMetadata.dateRange.start) : null,
20805
20830
  end: updatedMetadata.dateRange.end ? new Date(updatedMetadata.dateRange.end) : null
20806
20831
  },
20807
20832
  strategies: updatedMetadata.strategies,
20808
20833
  totalPl,
20809
- netPl: totalPl - totalCommissions
20834
+ netPl: totalPl - totalCommissions,
20835
+ reportingLog: reportingLogSummary
20810
20836
  });
20811
20837
  await saveMetadata(blockPath, updatedMetadata);
20812
20838
  } catch (error) {
@@ -20866,22 +20892,20 @@ async function loadReportingLog(baseDir, blockId) {
20866
20892
  try {
20867
20893
  await fs.access(reportingLogPath);
20868
20894
  } catch {
20869
- if (filename === "reportinglog.csv") {
20870
- const discovered = await discoverCsvFiles(blockPath);
20871
- if (discovered.mappings.reportinglog) {
20872
- const altPath = path.join(blockPath, discovered.mappings.reportinglog);
20873
- const content2 = await fs.readFile(altPath, "utf-8");
20874
- const records2 = parseCSV(content2);
20875
- const trades2 = [];
20876
- for (const record of records2) {
20877
- const trade = convertToReportingTrade(record);
20878
- if (trade) trades2.push(trade);
20879
- }
20880
- trades2.sort(
20881
- (a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
20882
- );
20883
- return trades2;
20884
- }
20895
+ const discovered = await discoverCsvFiles(blockPath);
20896
+ if (discovered.mappings.reportinglog) {
20897
+ const altPath = path.join(blockPath, discovered.mappings.reportinglog);
20898
+ const content2 = await fs.readFile(altPath, "utf-8");
20899
+ const records2 = parseCSV(content2);
20900
+ const trades2 = [];
20901
+ for (const record of records2) {
20902
+ const trade = convertToReportingTrade(record);
20903
+ if (trade) trades2.push(trade);
20904
+ }
20905
+ trades2.sort(
20906
+ (a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
20907
+ );
20908
+ return trades2;
20885
20909
  }
20886
20910
  throw new Error(`reportinglog.csv not found in block: ${blockId}`);
20887
20911
  }
@@ -20899,6 +20923,148 @@ async function loadReportingLog(baseDir, blockId) {
20899
20923
  );
20900
20924
  return trades;
20901
20925
  }
20926
+ function computeReportingLogStats(trades, invalidCount = 0) {
20927
+ const byStrategy = {};
20928
+ const strategyTrades = /* @__PURE__ */ new Map();
20929
+ for (const trade of trades) {
20930
+ const strategyKey = trade.strategy.trim();
20931
+ if (!strategyTrades.has(strategyKey)) {
20932
+ strategyTrades.set(strategyKey, []);
20933
+ }
20934
+ strategyTrades.get(strategyKey).push(trade);
20935
+ }
20936
+ for (const [strategy, strategyTradeList] of strategyTrades) {
20937
+ const tradeCount = strategyTradeList.length;
20938
+ const winningTrades = strategyTradeList.filter((t) => t.pl > 0).length;
20939
+ const winRate = tradeCount > 0 ? winningTrades / tradeCount : 0;
20940
+ const totalPL = strategyTradeList.reduce((sum2, t) => sum2 + t.pl, 0);
20941
+ const avgPL = tradeCount > 0 ? totalPL / tradeCount : 0;
20942
+ const contractCount = strategyTradeList.reduce(
20943
+ (sum2, t) => sum2 + t.numContracts,
20944
+ 0
20945
+ );
20946
+ byStrategy[strategy] = {
20947
+ tradeCount,
20948
+ winRate,
20949
+ totalPL,
20950
+ avgPL,
20951
+ contractCount
20952
+ };
20953
+ }
20954
+ const dates = trades.map((t) => new Date(t.dateOpened).getTime());
20955
+ const dateRange = {
20956
+ start: dates.length > 0 ? new Date(Math.min(...dates)).toISOString() : null,
20957
+ end: dates.length > 0 ? new Date(Math.max(...dates)).toISOString() : null
20958
+ };
20959
+ return {
20960
+ totalTrades: trades.length,
20961
+ totalPL: trades.reduce((sum2, t) => sum2 + t.pl, 0),
20962
+ dateRange,
20963
+ strategies: Array.from(strategyTrades.keys()).sort(),
20964
+ byStrategy,
20965
+ invalidTrades: invalidCount,
20966
+ calculatedAt: (/* @__PURE__ */ new Date()).toISOString()
20967
+ };
20968
+ }
20969
+ async function findReportingLogFile(blockPath, metadata) {
20970
+ if (metadata?.csvMappings?.reportinglog) {
20971
+ const mappedPath = path.join(blockPath, metadata.csvMappings.reportinglog);
20972
+ try {
20973
+ await fs.access(mappedPath);
20974
+ return metadata.csvMappings.reportinglog;
20975
+ } catch {
20976
+ }
20977
+ }
20978
+ const standardPath = path.join(blockPath, "reportinglog.csv");
20979
+ try {
20980
+ await fs.access(standardPath);
20981
+ return "reportinglog.csv";
20982
+ } catch {
20983
+ }
20984
+ const discovered = await discoverCsvFiles(blockPath);
20985
+ return discovered.mappings.reportinglog;
20986
+ }
20987
+ async function isReportingLogCacheValid(blockPath, metadata, reportingLogFilename) {
20988
+ if (!metadata.reportingLogStats) {
20989
+ return false;
20990
+ }
20991
+ if (!metadata.csvFileMtimes?.reportinglog) {
20992
+ return false;
20993
+ }
20994
+ try {
20995
+ const filePath = path.join(blockPath, reportingLogFilename);
20996
+ const stat2 = await fs.stat(filePath);
20997
+ return stat2.mtimeMs === metadata.csvFileMtimes.reportinglog;
20998
+ } catch {
20999
+ return false;
21000
+ }
21001
+ }
21002
+ async function loadReportingLogStats(baseDir, blockId) {
21003
+ const blockPath = path.join(baseDir, blockId);
21004
+ const metadata = await loadMetadata(blockPath);
21005
+ const reportingLogFilename = await findReportingLogFile(blockPath, metadata);
21006
+ if (!reportingLogFilename) {
21007
+ return void 0;
21008
+ }
21009
+ if (metadata && await isReportingLogCacheValid(blockPath, metadata, reportingLogFilename)) {
21010
+ return {
21011
+ stats: metadata.reportingLogStats,
21012
+ stale: false
21013
+ };
21014
+ }
21015
+ try {
21016
+ const reportingLogPath = path.join(blockPath, reportingLogFilename);
21017
+ const content = await fs.readFile(reportingLogPath, "utf-8");
21018
+ const records = parseCSV(content);
21019
+ const trades = [];
21020
+ let invalidCount = 0;
21021
+ for (const record of records) {
21022
+ const trade = convertToReportingTrade(record);
21023
+ if (trade) {
21024
+ trades.push(trade);
21025
+ } else {
21026
+ invalidCount++;
21027
+ }
21028
+ }
21029
+ const stats = computeReportingLogStats(trades, invalidCount);
21030
+ const fileStat = await fs.stat(reportingLogPath);
21031
+ const updatedMetadata = {
21032
+ ...metadata || {
21033
+ blockId,
21034
+ name: blockId,
21035
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
21036
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
21037
+ tradeCount: 0,
21038
+ dailyLogCount: 0,
21039
+ dateRange: { start: null, end: null },
21040
+ strategies: []
21041
+ },
21042
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
21043
+ csvMappings: {
21044
+ ...metadata?.csvMappings || {},
21045
+ reportinglog: reportingLogFilename
21046
+ },
21047
+ csvFileMtimes: {
21048
+ ...metadata?.csvFileMtimes || {},
21049
+ reportinglog: fileStat.mtimeMs
21050
+ },
21051
+ reportingLogStats: stats
21052
+ };
21053
+ await saveMetadata(blockPath, updatedMetadata);
21054
+ return {
21055
+ stats,
21056
+ stale: false
21057
+ };
21058
+ } catch (error) {
21059
+ if (metadata?.reportingLogStats) {
21060
+ return {
21061
+ stats: metadata.reportingLogStats,
21062
+ stale: true
21063
+ };
21064
+ }
21065
+ throw error;
21066
+ }
21067
+ }
20902
21068
  function toKebabCase(str) {
20903
21069
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "").toLowerCase().replace(/-+/g, "-").replace(/^-|-$/g, "");
20904
21070
  }
@@ -21055,6 +21221,7 @@ export {
21055
21221
  loadBlock,
21056
21222
  loadMetadata,
21057
21223
  loadReportingLog,
21224
+ loadReportingLogStats,
21058
21225
  performTailRiskAnalysis,
21059
21226
  saveMetadata
21060
21227
  };