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.
- package/README.md +32 -0
- package/dist/{chunk-TLX7IBCM.js → chunk-CZNPQ574.js} +5 -5
- package/dist/{chunk-TLX7IBCM.js.map → chunk-CZNPQ574.js.map} +1 -1
- package/dist/{sync-EW33QZPD.js → sync-P6QUZFSF.js} +2 -2
- package/dist/test-exports.js +1 -1
- package/package.json +1 -1
- package/server/{chunk-YIWLVX7Z.js → chunk-5JTQEV7P.js} +307 -252
- package/server/chunk-5JTQEV7P.js.map +1 -0
- package/server/index.js +374 -168
- package/server/index.js.map +1 -1
- package/server/{sync-2POG6KA6.js → sync-336543PJ.js} +2 -2
- package/src/tools/reports/predictive.ts +170 -195
- package/src/utils/filter-curve.ts +257 -0
- package/server/chunk-YIWLVX7Z.js.map +0 -1
- /package/dist/{sync-EW33QZPD.js.map → sync-P6QUZFSF.js.map} +0 -0
- /package/server/{sync-2POG6KA6.js.map → sync-336543PJ.js.map} +0 -0
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
syncAllBlocks,
|
|
9
9
|
syncBlock,
|
|
10
10
|
upsertSyncMetadata
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
29
|
+
//# sourceMappingURL=sync-P6QUZFSF.js.map
|
package/dist/test-exports.js
CHANGED
|
@@ -131,7 +131,7 @@ import {
|
|
|
131
131
|
writeParquetPartition,
|
|
132
132
|
writeQuoteMinutesPartition,
|
|
133
133
|
writeSpotPartition
|
|
134
|
-
} from "./chunk-
|
|
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
|
@@ -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-
|
|
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-
|
|
28060
|
+
//# sourceMappingURL=chunk-5JTQEV7P.js.map
|