tradeblocks-mcp 4.3.0 → 4.4.0

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.
@@ -8,7 +8,7 @@ import {
8
8
  syncAllBlocks,
9
9
  syncBlock,
10
10
  upsertSyncMetadata
11
- } from "./chunk-TLX7IBCM.js";
11
+ } from "./chunk-CZNPQ574.js";
12
12
  import "./chunk-D4AWELQU.js";
13
13
  import "./chunk-IW75IC6I.js";
14
14
  import "./chunk-ZSVYX5PP.js";
@@ -26,4 +26,4 @@ export {
26
26
  syncBlock,
27
27
  upsertSyncMetadata
28
28
  };
29
- //# sourceMappingURL=sync-EW33QZPD.js.map
29
+ //# sourceMappingURL=sync-P6QUZFSF.js.map
@@ -131,7 +131,7 @@ import {
131
131
  writeParquetPartition,
132
132
  writeQuoteMinutesPartition,
133
133
  writeSpotPartition
134
- } from "./chunk-TLX7IBCM.js";
134
+ } from "./chunk-CZNPQ574.js";
135
135
  import "./chunk-D4AWELQU.js";
136
136
  import "./chunk-IW75IC6I.js";
137
137
  import "./chunk-ZSVYX5PP.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tradeblocks-mcp",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "MCP server for options trade analysis",
5
5
  "author": "David Romeo <davidmromeo@gmail.com>",
6
6
  "type": "module",
@@ -3717,7 +3717,7 @@ async function openReadWriteConnection(dbPath, threads, memoryLimit) {
3717
3717
  await createMarketParquetViews(connection, dataRoot);
3718
3718
  await ensureMarketDataTables(connection);
3719
3719
  try {
3720
- const blocksDir = (await import("./sync-2POG6KA6.js")).getBlocksDir(dataRoot);
3720
+ const blocksDir = (await import("./sync-336543PJ.js")).getBlocksDir(dataRoot);
3721
3721
  await migrateMetadataToJson(connection, dataRoot, blocksDir);
3722
3722
  } catch (err) {
3723
3723
  console.warn(
@@ -21776,254 +21776,6 @@ function pearsonCorrelation(x, y) {
21776
21776
  return result;
21777
21777
  }
21778
21778
 
21779
- // ../lib/calculations/field-analysis.ts
21780
- function getNumericTradeFieldValue(trade, field) {
21781
- if (typeof field !== "string") return null;
21782
- let value;
21783
- if (field.startsWith("custom.")) value = trade.customFields?.[field.slice(7)];
21784
- else if (field.startsWith("daily.")) value = trade.dailyCustomFields?.[field.slice(6)];
21785
- else if (field.includes(".")) {
21786
- const dot2 = field.indexOf(".");
21787
- value = trade.staticDatasetFields?.[field.slice(0, dot2)]?.[field.slice(dot2 + 1)];
21788
- } else value = trade[field];
21789
- return typeof value === "number" && Number.isFinite(value) ? value : null;
21790
- }
21791
- function percentile2(sorted, p) {
21792
- const index = p / 100 * (sorted.length - 1);
21793
- const lower = Math.floor(index);
21794
- const upper = Math.ceil(index);
21795
- return lower === upper ? sorted[lower] : sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower);
21796
- }
21797
- function fieldStatisticsFromValues(values, bucketCount = 10) {
21798
- if (!values.length) return null;
21799
- const sorted = [...values].sort((a, b) => a - b);
21800
- const min3 = sorted[0];
21801
- const max3 = sorted[sorted.length - 1];
21802
- const sum2 = values.reduce((a, b) => a + b, 0);
21803
- const avg = sum2 / values.length;
21804
- const median2 = percentile2(sorted, 50);
21805
- const stdDev = values.length < 2 ? 0 : Math.sqrt(values.reduce((sum3, value) => sum3 + (value - avg) ** 2, 0) / (values.length - 1));
21806
- const percentiles = {
21807
- p5: percentile2(sorted, 5),
21808
- p10: percentile2(sorted, 10),
21809
- p25: percentile2(sorted, 25),
21810
- p50: median2,
21811
- p75: percentile2(sorted, 75),
21812
- p90: percentile2(sorted, 90),
21813
- p95: percentile2(sorted, 95)
21814
- };
21815
- const bucketSize = (max3 - min3) / bucketCount || 1;
21816
- const histogram = Array.from({ length: bucketCount }, (_, i) => ({
21817
- min: min3 + i * bucketSize,
21818
- max: i === bucketCount - 1 ? max3 + 1e-3 : min3 + (i + 1) * bucketSize,
21819
- count: 0
21820
- }));
21821
- for (const value of values) {
21822
- const index = Math.min(Math.floor((value - min3) / bucketSize), bucketCount - 1);
21823
- if (histogram[index]) histogram[index].count++;
21824
- }
21825
- return {
21826
- statistics: { count: values.length, min: min3, max: max3, sum: sum2, avg, median: median2, stdDev },
21827
- percentiles,
21828
- histogram
21829
- };
21830
- }
21831
- function rankPredictiveTradeFields(trades, targetField = "pl", minSamples = 30, includeCustomFields = true, additionalFields = []) {
21832
- const fields = REPORT_FIELDS.filter(
21833
- (info) => info?.field && info.field !== targetField
21834
- ).map(({ field, label }) => ({ field, label }));
21835
- for (const entry of additionalFields) {
21836
- if (entry.field !== targetField && !fields.some(({ field }) => field === entry.field))
21837
- fields.push(entry);
21838
- }
21839
- if (includeCustomFields) {
21840
- const names = /* @__PURE__ */ new Set();
21841
- for (const trade of trades)
21842
- for (const key of Object.keys(trade.customFields ?? {})) names.add(key);
21843
- for (const name79 of names) {
21844
- const field = `custom.${name79}`;
21845
- if (field !== targetField) fields.push({ field, label: `Custom: ${name79}` });
21846
- }
21847
- }
21848
- const rankedFields = [];
21849
- const fieldsSkipped = [];
21850
- for (const { field, label } of fields) {
21851
- const pairs = [];
21852
- for (const trade of trades) {
21853
- const x = getNumericTradeFieldValue(trade, field);
21854
- const y = getNumericTradeFieldValue(trade, targetField);
21855
- if (x !== null && y !== null) pairs.push({ x, y });
21856
- }
21857
- if (pairs.length < minSamples) {
21858
- fieldsSkipped.push({
21859
- field,
21860
- label,
21861
- reason: "insufficient_samples",
21862
- sampleSize: pairs.length
21863
- });
21864
- continue;
21865
- }
21866
- const xValues = pairs.map((pair) => pair.x);
21867
- if (Math.min(...xValues) === Math.max(...xValues)) {
21868
- fieldsSkipped.push({ field, label, reason: "no_variance", sampleSize: pairs.length });
21869
- continue;
21870
- }
21871
- const correlation = pearsonCorrelation(
21872
- xValues,
21873
- pairs.map((pair) => pair.y)
21874
- );
21875
- const absCorrelation = Math.abs(correlation);
21876
- rankedFields.push({
21877
- field,
21878
- label,
21879
- correlation: Math.round(correlation * 1e4) / 1e4,
21880
- absCorrelation: Math.round(absCorrelation * 1e4) / 1e4,
21881
- sampleSize: pairs.length,
21882
- direction: correlation >= 0 ? "positive" : "negative"
21883
- });
21884
- }
21885
- rankedFields.sort((a, b) => b.absCorrelation - a.absCorrelation);
21886
- return {
21887
- totalFieldsAnalyzed: fields.length,
21888
- fieldsWithSufficientData: rankedFields.length,
21889
- rankedFields,
21890
- fieldsSkipped
21891
- };
21892
- }
21893
-
21894
- // ../lib/calculations/kelly.ts
21895
- var ZERO_METRICS = {
21896
- fraction: 0,
21897
- percent: 0,
21898
- winRate: 0,
21899
- payoffRatio: 0,
21900
- avgWin: 0,
21901
- avgLoss: 0,
21902
- hasValidKelly: false
21903
- };
21904
- function hasUnrealisticAbsoluteValues(avgWin, avgLoss, startingCapital) {
21905
- if (!startingCapital) {
21906
- return avgWin > 1e7 || avgLoss > 1e7;
21907
- }
21908
- return avgWin > startingCapital * 100 || avgLoss > startingCapital * 100;
21909
- }
21910
- function calculateKellyFromReturns(trades) {
21911
- const winReturns = [];
21912
- const lossReturns = [];
21913
- for (const trade of trades) {
21914
- const pl = trade.pl || 0;
21915
- const margin = trade.marginReq || 0;
21916
- if (margin <= 0) continue;
21917
- const returnPct = pl / margin * 100;
21918
- if (pl > 0) {
21919
- winReturns.push(returnPct);
21920
- } else if (pl < 0) {
21921
- lossReturns.push(Math.abs(returnPct));
21922
- }
21923
- }
21924
- const totalTrades = winReturns.length + lossReturns.length;
21925
- const winRate = totalTrades > 0 ? winReturns.length / totalTrades : 0;
21926
- const avgWinPct = winReturns.length > 0 ? winReturns.reduce((sum2, val) => sum2 + val, 0) / winReturns.length : 0;
21927
- const avgLossPct = lossReturns.length > 0 ? lossReturns.reduce((sum2, val) => sum2 + val, 0) / lossReturns.length : 0;
21928
- const hasValidKelly = winReturns.length > 0 && lossReturns.length > 0 && avgLossPct > 0;
21929
- if (!hasValidKelly) {
21930
- return {
21931
- fraction: 0,
21932
- avgWinPct,
21933
- avgLossPct,
21934
- payoffRatio: avgLossPct > 0 ? avgWinPct / avgLossPct : 0,
21935
- winRate,
21936
- hasValidKelly: false
21937
- };
21938
- }
21939
- const payoffRatio = avgWinPct / avgLossPct;
21940
- const lossRate = 1 - winRate;
21941
- const kellyFraction = (payoffRatio * winRate - lossRate) / payoffRatio;
21942
- return {
21943
- fraction: kellyFraction,
21944
- avgWinPct,
21945
- avgLossPct,
21946
- payoffRatio,
21947
- winRate,
21948
- hasValidKelly: true
21949
- };
21950
- }
21951
- function calculateKellyMetrics(trades, startingCapital) {
21952
- if (trades.length === 0) {
21953
- return ZERO_METRICS;
21954
- }
21955
- const wins = [];
21956
- const losses = [];
21957
- for (const trade of trades) {
21958
- const pl = trade.pl || 0;
21959
- if (pl > 0) {
21960
- wins.push(pl);
21961
- } else if (pl < 0) {
21962
- losses.push(Math.abs(pl));
21963
- }
21964
- }
21965
- const totalTrades = trades.length;
21966
- const winRate = wins.length / totalTrades;
21967
- const avgWin = wins.length > 0 ? wins.reduce((sum2, val) => sum2 + val, 0) / wins.length : 0;
21968
- const avgLoss = losses.length > 0 ? losses.reduce((sum2, val) => sum2 + val, 0) / losses.length : 0;
21969
- const hasValidKelly = wins.length > 0 && losses.length > 0 && avgLoss > 0;
21970
- const unrealistic = hasUnrealisticAbsoluteValues(avgWin, avgLoss, startingCapital);
21971
- let normalizedMetrics = null;
21972
- const hasMarginData = trades.some((t) => (t.marginReq || 0) > 0);
21973
- if (hasMarginData) {
21974
- normalizedMetrics = calculateKellyFromReturns(trades);
21975
- }
21976
- if (!hasValidKelly) {
21977
- return {
21978
- fraction: 0,
21979
- percent: 0,
21980
- winRate,
21981
- payoffRatio: avgLoss > 0 ? avgWin / avgLoss : 0,
21982
- avgWin,
21983
- avgLoss,
21984
- hasValidKelly: false,
21985
- calculationMethod: "absolute",
21986
- hasUnrealisticValues: unrealistic,
21987
- avgWinPct: normalizedMetrics?.avgWinPct,
21988
- avgLossPct: normalizedMetrics?.avgLossPct,
21989
- normalizedKellyPct: normalizedMetrics?.hasValidKelly ? normalizedMetrics.fraction * 100 : void 0
21990
- };
21991
- }
21992
- const payoffRatio = avgWin / avgLoss;
21993
- const lossRate = 1 - winRate;
21994
- const kellyFraction = (payoffRatio * winRate - lossRate) / payoffRatio;
21995
- const kellyPercent = kellyFraction * 100;
21996
- return {
21997
- fraction: kellyFraction,
21998
- percent: kellyPercent,
21999
- winRate,
22000
- payoffRatio,
22001
- avgWin,
22002
- avgLoss,
22003
- hasValidKelly: true,
22004
- calculationMethod: normalizedMetrics && normalizedMetrics.hasValidKelly ? "percentage" : "absolute",
22005
- hasUnrealisticValues: unrealistic,
22006
- avgWinPct: normalizedMetrics?.avgWinPct,
22007
- avgLossPct: normalizedMetrics?.avgLossPct,
22008
- normalizedKellyPct: normalizedMetrics?.hasValidKelly ? normalizedMetrics.fraction * 100 : void 0
22009
- };
22010
- }
22011
- function calculateStrategyKellyMetrics(trades, startingCapital) {
22012
- const strategyMap = /* @__PURE__ */ new Map();
22013
- for (const trade of trades) {
22014
- const strategy = trade.strategy || "Uncategorized";
22015
- if (!strategyMap.has(strategy)) {
22016
- strategyMap.set(strategy, []);
22017
- }
22018
- strategyMap.get(strategy).push(trade);
22019
- }
22020
- const metricsMap = /* @__PURE__ */ new Map();
22021
- for (const [strategy, strategyTrades] of strategyMap.entries()) {
22022
- metricsMap.set(strategy, calculateKellyMetrics(strategyTrades, startingCapital));
22023
- }
22024
- return metricsMap;
22025
- }
22026
-
22027
21779
  // ../lib/calculations/correlation.ts
22028
21780
  function calculateCorrelationMatrix(trades, options = {}) {
22029
21781
  const {
@@ -22273,6 +22025,288 @@ function calculateCorrelationAnalytics(matrix2, minSamples = 2) {
22273
22025
  };
22274
22026
  }
22275
22027
 
22028
+ // ../lib/calculations/field-analysis.ts
22029
+ var KENDALL_PAIR_LIMIT = 75e6;
22030
+ var KendallPairWorkLimitError = class extends RangeError {
22031
+ pairLimit = KENDALL_PAIR_LIMIT;
22032
+ estimatedPairs;
22033
+ fieldCount;
22034
+ tradeCount;
22035
+ constructor(estimatedPairs, fieldCount, tradeCount) {
22036
+ super(
22037
+ `kendall_pair_work_limit: ${estimatedPairs} estimated pairs exceed ${KENDALL_PAIR_LIMIT}`
22038
+ );
22039
+ this.name = "KendallPairWorkLimitError";
22040
+ this.estimatedPairs = estimatedPairs;
22041
+ this.fieldCount = fieldCount;
22042
+ this.tradeCount = tradeCount;
22043
+ }
22044
+ };
22045
+ function getNumericTradeFieldValue(trade, field) {
22046
+ if (typeof field !== "string") return null;
22047
+ let value;
22048
+ if (field.startsWith("custom.")) value = trade.customFields?.[field.slice(7)];
22049
+ else if (field.startsWith("daily.")) value = trade.dailyCustomFields?.[field.slice(6)];
22050
+ else if (field.includes(".")) {
22051
+ const dot2 = field.indexOf(".");
22052
+ value = trade.staticDatasetFields?.[field.slice(0, dot2)]?.[field.slice(dot2 + 1)];
22053
+ } else value = trade[field];
22054
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
22055
+ }
22056
+ function percentile2(sorted, p) {
22057
+ const index = p / 100 * (sorted.length - 1);
22058
+ const lower = Math.floor(index);
22059
+ const upper = Math.ceil(index);
22060
+ return lower === upper ? sorted[lower] : sorted[lower] + (sorted[upper] - sorted[lower]) * (index - lower);
22061
+ }
22062
+ function fieldStatisticsFromValues(values, bucketCount = 10) {
22063
+ if (!values.length) return null;
22064
+ const sorted = [...values].sort((a, b) => a - b);
22065
+ const min3 = sorted[0];
22066
+ const max3 = sorted[sorted.length - 1];
22067
+ const sum2 = values.reduce((a, b) => a + b, 0);
22068
+ const avg = sum2 / values.length;
22069
+ const median2 = percentile2(sorted, 50);
22070
+ const stdDev = values.length < 2 ? 0 : Math.sqrt(values.reduce((sum3, value) => sum3 + (value - avg) ** 2, 0) / (values.length - 1));
22071
+ const percentiles = {
22072
+ p5: percentile2(sorted, 5),
22073
+ p10: percentile2(sorted, 10),
22074
+ p25: percentile2(sorted, 25),
22075
+ p50: median2,
22076
+ p75: percentile2(sorted, 75),
22077
+ p90: percentile2(sorted, 90),
22078
+ p95: percentile2(sorted, 95)
22079
+ };
22080
+ const bucketSize = (max3 - min3) / bucketCount || 1;
22081
+ const histogram = Array.from({ length: bucketCount }, (_, i) => ({
22082
+ min: min3 + i * bucketSize,
22083
+ max: i === bucketCount - 1 ? max3 + 1e-3 : min3 + (i + 1) * bucketSize,
22084
+ count: 0
22085
+ }));
22086
+ for (const value of values) {
22087
+ const index = Math.min(Math.floor((value - min3) / bucketSize), bucketCount - 1);
22088
+ if (histogram[index]) histogram[index].count++;
22089
+ }
22090
+ return {
22091
+ statistics: { count: values.length, min: min3, max: max3, sum: sum2, avg, median: median2, stdDev },
22092
+ percentiles,
22093
+ histogram
22094
+ };
22095
+ }
22096
+ function rankPredictiveTradeFields(trades, targetField = "pl", minSamples = 30, includeCustomFields = true, additionalFields = [], method = "pearson") {
22097
+ const fields = REPORT_FIELDS.filter(
22098
+ (info) => info?.field && info.field !== targetField
22099
+ ).map(({ field, label }) => ({ field, label }));
22100
+ for (const entry of additionalFields) {
22101
+ if (entry.field !== targetField && !fields.some(({ field }) => field === entry.field))
22102
+ fields.push(entry);
22103
+ }
22104
+ if (includeCustomFields) {
22105
+ const names = /* @__PURE__ */ new Set();
22106
+ for (const trade of trades)
22107
+ for (const key of Object.keys(trade.customFields ?? {})) names.add(key);
22108
+ for (const name79 of names) {
22109
+ const field = `custom.${name79}`;
22110
+ if (field !== targetField) fields.push({ field, label: `Custom: ${name79}` });
22111
+ }
22112
+ }
22113
+ if (method === "kendall") {
22114
+ let estimatedPairs = 0;
22115
+ let fieldCount = 0;
22116
+ for (const { field } of fields) {
22117
+ let count = 0;
22118
+ for (const trade of trades) {
22119
+ if (getNumericTradeFieldValue(trade, field) !== null && getNumericTradeFieldValue(trade, targetField) !== null)
22120
+ count++;
22121
+ }
22122
+ if (count >= minSamples) {
22123
+ fieldCount++;
22124
+ estimatedPairs += count * (count - 1) / 2;
22125
+ }
22126
+ }
22127
+ if (estimatedPairs > KENDALL_PAIR_LIMIT)
22128
+ throw new KendallPairWorkLimitError(estimatedPairs, fieldCount, trades.length);
22129
+ }
22130
+ const rankedFields = [];
22131
+ const fieldsSkipped = [];
22132
+ for (const { field, label } of fields) {
22133
+ const pairs = [];
22134
+ for (const trade of trades) {
22135
+ const x = getNumericTradeFieldValue(trade, field);
22136
+ const y = getNumericTradeFieldValue(trade, targetField);
22137
+ if (x !== null && y !== null) pairs.push({ x, y });
22138
+ }
22139
+ if (pairs.length < minSamples) {
22140
+ fieldsSkipped.push({
22141
+ field,
22142
+ label,
22143
+ reason: "insufficient_samples",
22144
+ sampleSize: pairs.length
22145
+ });
22146
+ continue;
22147
+ }
22148
+ const xValues = pairs.map((pair) => pair.x);
22149
+ if (Math.min(...xValues) === Math.max(...xValues)) {
22150
+ fieldsSkipped.push({ field, label, reason: "no_variance", sampleSize: pairs.length });
22151
+ continue;
22152
+ }
22153
+ const correlate = method === "spearman" ? spearmanCorrelation : method === "kendall" ? kendallTau : pearsonCorrelation;
22154
+ const correlation = correlate(
22155
+ xValues,
22156
+ pairs.map((pair) => pair.y)
22157
+ );
22158
+ const absCorrelation = Math.abs(correlation);
22159
+ rankedFields.push({
22160
+ field,
22161
+ label,
22162
+ correlation: Math.round(correlation * 1e4) / 1e4,
22163
+ absCorrelation: Math.round(absCorrelation * 1e4) / 1e4,
22164
+ sampleSize: pairs.length,
22165
+ direction: correlation >= 0 ? "positive" : "negative"
22166
+ });
22167
+ }
22168
+ rankedFields.sort((a, b) => b.absCorrelation - a.absCorrelation);
22169
+ return {
22170
+ totalFieldsAnalyzed: fields.length,
22171
+ fieldsWithSufficientData: rankedFields.length,
22172
+ rankedFields,
22173
+ fieldsSkipped
22174
+ };
22175
+ }
22176
+
22177
+ // ../lib/calculations/kelly.ts
22178
+ var ZERO_METRICS = {
22179
+ fraction: 0,
22180
+ percent: 0,
22181
+ winRate: 0,
22182
+ payoffRatio: 0,
22183
+ avgWin: 0,
22184
+ avgLoss: 0,
22185
+ hasValidKelly: false
22186
+ };
22187
+ function hasUnrealisticAbsoluteValues(avgWin, avgLoss, startingCapital) {
22188
+ if (!startingCapital) {
22189
+ return avgWin > 1e7 || avgLoss > 1e7;
22190
+ }
22191
+ return avgWin > startingCapital * 100 || avgLoss > startingCapital * 100;
22192
+ }
22193
+ function calculateKellyFromReturns(trades) {
22194
+ const winReturns = [];
22195
+ const lossReturns = [];
22196
+ for (const trade of trades) {
22197
+ const pl = trade.pl || 0;
22198
+ const margin = trade.marginReq || 0;
22199
+ if (margin <= 0) continue;
22200
+ const returnPct = pl / margin * 100;
22201
+ if (pl > 0) {
22202
+ winReturns.push(returnPct);
22203
+ } else if (pl < 0) {
22204
+ lossReturns.push(Math.abs(returnPct));
22205
+ }
22206
+ }
22207
+ const totalTrades = winReturns.length + lossReturns.length;
22208
+ const winRate = totalTrades > 0 ? winReturns.length / totalTrades : 0;
22209
+ const avgWinPct = winReturns.length > 0 ? winReturns.reduce((sum2, val) => sum2 + val, 0) / winReturns.length : 0;
22210
+ const avgLossPct = lossReturns.length > 0 ? lossReturns.reduce((sum2, val) => sum2 + val, 0) / lossReturns.length : 0;
22211
+ const hasValidKelly = winReturns.length > 0 && lossReturns.length > 0 && avgLossPct > 0;
22212
+ if (!hasValidKelly) {
22213
+ return {
22214
+ fraction: 0,
22215
+ avgWinPct,
22216
+ avgLossPct,
22217
+ payoffRatio: avgLossPct > 0 ? avgWinPct / avgLossPct : 0,
22218
+ winRate,
22219
+ hasValidKelly: false
22220
+ };
22221
+ }
22222
+ const payoffRatio = avgWinPct / avgLossPct;
22223
+ const lossRate = 1 - winRate;
22224
+ const kellyFraction = (payoffRatio * winRate - lossRate) / payoffRatio;
22225
+ return {
22226
+ fraction: kellyFraction,
22227
+ avgWinPct,
22228
+ avgLossPct,
22229
+ payoffRatio,
22230
+ winRate,
22231
+ hasValidKelly: true
22232
+ };
22233
+ }
22234
+ function calculateKellyMetrics(trades, startingCapital) {
22235
+ if (trades.length === 0) {
22236
+ return ZERO_METRICS;
22237
+ }
22238
+ const wins = [];
22239
+ const losses = [];
22240
+ for (const trade of trades) {
22241
+ const pl = trade.pl || 0;
22242
+ if (pl > 0) {
22243
+ wins.push(pl);
22244
+ } else if (pl < 0) {
22245
+ losses.push(Math.abs(pl));
22246
+ }
22247
+ }
22248
+ const totalTrades = trades.length;
22249
+ const winRate = wins.length / totalTrades;
22250
+ const avgWin = wins.length > 0 ? wins.reduce((sum2, val) => sum2 + val, 0) / wins.length : 0;
22251
+ const avgLoss = losses.length > 0 ? losses.reduce((sum2, val) => sum2 + val, 0) / losses.length : 0;
22252
+ const hasValidKelly = wins.length > 0 && losses.length > 0 && avgLoss > 0;
22253
+ const unrealistic = hasUnrealisticAbsoluteValues(avgWin, avgLoss, startingCapital);
22254
+ let normalizedMetrics = null;
22255
+ const hasMarginData = trades.some((t) => (t.marginReq || 0) > 0);
22256
+ if (hasMarginData) {
22257
+ normalizedMetrics = calculateKellyFromReturns(trades);
22258
+ }
22259
+ if (!hasValidKelly) {
22260
+ return {
22261
+ fraction: 0,
22262
+ percent: 0,
22263
+ winRate,
22264
+ payoffRatio: avgLoss > 0 ? avgWin / avgLoss : 0,
22265
+ avgWin,
22266
+ avgLoss,
22267
+ hasValidKelly: false,
22268
+ calculationMethod: "absolute",
22269
+ hasUnrealisticValues: unrealistic,
22270
+ avgWinPct: normalizedMetrics?.avgWinPct,
22271
+ avgLossPct: normalizedMetrics?.avgLossPct,
22272
+ normalizedKellyPct: normalizedMetrics?.hasValidKelly ? normalizedMetrics.fraction * 100 : void 0
22273
+ };
22274
+ }
22275
+ const payoffRatio = avgWin / avgLoss;
22276
+ const lossRate = 1 - winRate;
22277
+ const kellyFraction = (payoffRatio * winRate - lossRate) / payoffRatio;
22278
+ const kellyPercent = kellyFraction * 100;
22279
+ return {
22280
+ fraction: kellyFraction,
22281
+ percent: kellyPercent,
22282
+ winRate,
22283
+ payoffRatio,
22284
+ avgWin,
22285
+ avgLoss,
22286
+ hasValidKelly: true,
22287
+ calculationMethod: normalizedMetrics && normalizedMetrics.hasValidKelly ? "percentage" : "absolute",
22288
+ hasUnrealisticValues: unrealistic,
22289
+ avgWinPct: normalizedMetrics?.avgWinPct,
22290
+ avgLossPct: normalizedMetrics?.avgLossPct,
22291
+ normalizedKellyPct: normalizedMetrics?.hasValidKelly ? normalizedMetrics.fraction * 100 : void 0
22292
+ };
22293
+ }
22294
+ function calculateStrategyKellyMetrics(trades, startingCapital) {
22295
+ const strategyMap = /* @__PURE__ */ new Map();
22296
+ for (const trade of trades) {
22297
+ const strategy = trade.strategy || "Uncategorized";
22298
+ if (!strategyMap.has(strategy)) {
22299
+ strategyMap.set(strategy, []);
22300
+ }
22301
+ strategyMap.get(strategy).push(trade);
22302
+ }
22303
+ const metricsMap = /* @__PURE__ */ new Map();
22304
+ for (const [strategy, strategyTrades] of strategyMap.entries()) {
22305
+ metricsMap.set(strategy, calculateKellyMetrics(strategyTrades, startingCapital));
22306
+ }
22307
+ return metricsMap;
22308
+ }
22309
+
22276
22310
  // ../lib/calculations/tail-risk-analysis.ts
22277
22311
  var HIGH_DEPENDENCE_THRESHOLD = 0.5;
22278
22312
  var CONCENTRATION_WEIGHT = 0.5;
@@ -26062,6 +26096,25 @@ function pairedBlockBootstrap(input) {
26062
26096
  };
26063
26097
  }
26064
26098
 
26099
+ // ../lib/calculations/holm-adjust.ts
26100
+ function holmAdjust(pValues) {
26101
+ const ranked = pValues.map((pValue, index) => {
26102
+ if (!Number.isFinite(pValue) || pValue < 0 || pValue > 1) {
26103
+ throw new RangeError(`pValues[${index}] must be a finite number in [0, 1]`);
26104
+ }
26105
+ return { pValue, index };
26106
+ });
26107
+ ranked.sort((a, b) => a.pValue - b.pValue || a.index - b.index);
26108
+ const adjusted = new Array(pValues.length);
26109
+ let running = 0;
26110
+ for (let rank = 0; rank < ranked.length; rank++) {
26111
+ const { pValue, index } = ranked[rank];
26112
+ running = Math.max(running, Math.min(1, (ranked.length - rank) * pValue));
26113
+ adjusted[index] = running;
26114
+ }
26115
+ return adjusted;
26116
+ }
26117
+
26065
26118
  // ../lib/processing/tat-adapter.ts
26066
26119
  var TAT_SIGNATURE_HEADERS = ["tradeid", "profitloss", "buyingpower"];
26067
26120
  function isTatFormat(headers) {
@@ -27896,13 +27949,14 @@ export {
27896
27949
  getRanks,
27897
27950
  kendallTau,
27898
27951
  pearsonCorrelation,
27952
+ calculateCorrelationMatrix,
27953
+ calculateCorrelationAnalytics,
27954
+ KendallPairWorkLimitError,
27899
27955
  getNumericTradeFieldValue,
27900
27956
  fieldStatisticsFromValues,
27901
27957
  rankPredictiveTradeFields,
27902
27958
  calculateKellyMetrics,
27903
27959
  calculateStrategyKellyMetrics,
27904
- calculateCorrelationMatrix,
27905
- calculateCorrelationAnalytics,
27906
27960
  performTailRiskAnalysis,
27907
27961
  singleTapeWalkForwardByTrades,
27908
27962
  STRESS_SCENARIOS,
@@ -27917,6 +27971,7 @@ export {
27917
27971
  synthesizeEdgeDecay,
27918
27972
  holdingPeriodBlockDays,
27919
27973
  pairedBlockBootstrap,
27974
+ holmAdjust,
27920
27975
  processStaticDatasetContent,
27921
27976
  validateDatasetName,
27922
27977
  canonicalJson,
@@ -28002,4 +28057,4 @@ decimal.js/decimal.mjs:
28002
28057
  * MIT Licence
28003
28058
  *)
28004
28059
  */
28005
- //# sourceMappingURL=chunk-YIWLVX7Z.js.map
28060
+ //# sourceMappingURL=chunk-5JTQEV7P.js.map