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.
- package/README.md +85 -80
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
- package/dist/chunk-2I3S2ZLT.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
- package/dist/chunk-4HZLVUOZ.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
- package/dist/chunk-NWNSKALN.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
- package/dist/chunk-WNTYA7ER.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
- package/dist/chunk-YZA4RS76.js.map +1 -0
- package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
- package/dist/chunk-ZTJXC3LS.js.map +1 -0
- package/dist/daily-log-processor-MZW2XBZY.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-6U33TQUT.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
- package/dist/test-exports.js +876 -483
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-DKDCWGKD.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/package.json +2 -6
- package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
- package/server/chunk-536NSFED.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
- package/server/chunk-A3UKD64A.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
- package/server/chunk-OMUDNJBJ.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
- package/server/chunk-RJDJHZ5Y.js.map +1 -0
- package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
- package/server/chunk-RM4B2VKS.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
- package/server/chunk-SY6GDNVI.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-B2VPU2FQ.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +2497 -2029
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-L56RFHAC.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
- package/server/trade-processor-3PI4LHZI.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +38 -8
- package/src/db/connection.ts +107 -40
- package/src/db/data-root.ts +22 -0
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +139 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/index.ts +2 -0
- package/src/tools/blocks/paired-comparison.ts +425 -0
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +11 -14
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +69 -83
- package/src/utils/calibration-probe.ts +3 -4
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-QTTR7AAW.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/manifest.json +0 -141
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
- /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
|
@@ -15420,12 +15420,7 @@ function normalQuantile(p) {
|
|
|
15420
15420
|
4.374664141464968,
|
|
15421
15421
|
2.938163982698783
|
|
15422
15422
|
];
|
|
15423
|
-
const d = [
|
|
15424
|
-
0.007784695709041462,
|
|
15425
|
-
0.3224671290700398,
|
|
15426
|
-
2.445134137142996,
|
|
15427
|
-
3.754408661907416
|
|
15428
|
-
];
|
|
15423
|
+
const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416];
|
|
15429
15424
|
const pLow = 0.02425;
|
|
15430
15425
|
const pHigh = 1 - pLow;
|
|
15431
15426
|
let q;
|
|
@@ -15589,10 +15584,7 @@ function calculateCorrelationMatrix(trades, options = {}) {
|
|
|
15589
15584
|
}
|
|
15590
15585
|
const strategyReturns = {};
|
|
15591
15586
|
for (const strategy of Object.keys(strategyDailyReturns)) {
|
|
15592
|
-
strategyReturns[strategy] = aggregateByPeriod(
|
|
15593
|
-
strategyDailyReturns[strategy],
|
|
15594
|
-
timePeriod
|
|
15595
|
-
);
|
|
15587
|
+
strategyReturns[strategy] = aggregateByPeriod(strategyDailyReturns[strategy], timePeriod);
|
|
15596
15588
|
}
|
|
15597
15589
|
const allDates = /* @__PURE__ */ new Set();
|
|
15598
15590
|
for (const returns of Object.values(strategyReturns)) {
|
|
@@ -15727,9 +15719,7 @@ function normalizeReturn(trade, mode) {
|
|
|
15727
15719
|
return trade.pl / trade.marginReq;
|
|
15728
15720
|
}
|
|
15729
15721
|
case "notional": {
|
|
15730
|
-
const notional = Math.abs(
|
|
15731
|
-
(trade.openingPrice || 0) * (trade.numContracts || 0)
|
|
15732
|
-
);
|
|
15722
|
+
const notional = Math.abs((trade.openingPrice || 0) * (trade.numContracts || 0));
|
|
15733
15723
|
if (!notional) {
|
|
15734
15724
|
return null;
|
|
15735
15725
|
}
|
|
@@ -15742,9 +15732,7 @@ function normalizeReturn(trade, mode) {
|
|
|
15742
15732
|
function getTradeDateKey(trade, basis) {
|
|
15743
15733
|
const date = basis === "closed" ? trade.dateClosed : trade.dateOpened;
|
|
15744
15734
|
if (!date) {
|
|
15745
|
-
throw new Error(
|
|
15746
|
-
"Trade is missing required date information for correlation calculation"
|
|
15747
|
-
);
|
|
15735
|
+
throw new Error("Trade is missing required date information for correlation calculation");
|
|
15748
15736
|
}
|
|
15749
15737
|
const year = date.getFullYear();
|
|
15750
15738
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
@@ -15761,9 +15749,7 @@ function getIsoWeekKey(dateStr) {
|
|
|
15761
15749
|
const dayOfWeek = thursday.getUTCDay() || 7;
|
|
15762
15750
|
thursday.setUTCDate(thursday.getUTCDate() + (4 - dayOfWeek));
|
|
15763
15751
|
const yearStart = new Date(Date.UTC(thursday.getUTCFullYear(), 0, 1));
|
|
15764
|
-
const weekNum = Math.ceil(
|
|
15765
|
-
((thursday.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
|
|
15766
|
-
);
|
|
15752
|
+
const weekNum = Math.ceil(((thursday.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
15767
15753
|
return `${thursday.getUTCFullYear()}-W${String(weekNum).padStart(2, "0")}`;
|
|
15768
15754
|
}
|
|
15769
15755
|
function getMonthKey(dateStr) {
|
|
@@ -15850,9 +15836,7 @@ function performTailRiskAnalysis(trades, options = {}) {
|
|
|
15850
15836
|
}
|
|
15851
15837
|
if (strategyFilter && strategyFilter.length > 0) {
|
|
15852
15838
|
const filterSet = new Set(strategyFilter);
|
|
15853
|
-
filteredTrades = filteredTrades.filter(
|
|
15854
|
-
(t) => t.strategy && filterSet.has(t.strategy)
|
|
15855
|
-
);
|
|
15839
|
+
filteredTrades = filteredTrades.filter((t) => t.strategy && filterSet.has(t.strategy));
|
|
15856
15840
|
}
|
|
15857
15841
|
if (dateRange?.from || dateRange?.to) {
|
|
15858
15842
|
filteredTrades = filteredTrades.filter((t) => {
|
|
@@ -15867,41 +15851,27 @@ function performTailRiskAnalysis(trades, options = {}) {
|
|
|
15867
15851
|
return true;
|
|
15868
15852
|
});
|
|
15869
15853
|
}
|
|
15870
|
-
const aligned = aggregateAndAlignReturns(
|
|
15871
|
-
filteredTrades,
|
|
15872
|
-
normalization,
|
|
15873
|
-
dateBasis
|
|
15874
|
-
);
|
|
15854
|
+
const aligned = aggregateAndAlignReturns(filteredTrades, normalization, dateBasis);
|
|
15875
15855
|
if (aligned.strategies.length < 2) {
|
|
15876
|
-
return createEmptyResult(
|
|
15877
|
-
aligned,
|
|
15878
|
-
tailThreshold,
|
|
15879
|
-
varianceThreshold,
|
|
15880
|
-
startTime
|
|
15881
|
-
);
|
|
15856
|
+
return createEmptyResult(aligned, tailThreshold, varianceThreshold, startTime);
|
|
15882
15857
|
}
|
|
15883
15858
|
if (aligned.dates.length < minTradingDays) {
|
|
15884
|
-
return createEmptyResult(
|
|
15885
|
-
aligned,
|
|
15886
|
-
tailThreshold,
|
|
15887
|
-
varianceThreshold,
|
|
15888
|
-
startTime
|
|
15889
|
-
);
|
|
15859
|
+
return createEmptyResult(aligned, tailThreshold, varianceThreshold, startTime);
|
|
15890
15860
|
}
|
|
15891
15861
|
const transformedReturns = aligned.returns.map(
|
|
15892
15862
|
(strategyReturns) => probabilityIntegralTransform(strategyReturns)
|
|
15893
15863
|
);
|
|
15894
15864
|
const copulaCorrelationMatrix = computeCorrelationMatrix(transformedReturns);
|
|
15895
|
-
const { eigenvalues, eigenvectors, explainedVariance, effectiveFactors } = performEigenAnalysis(
|
|
15865
|
+
const { eigenvalues, eigenvectors, explainedVariance, effectiveFactors } = performEigenAnalysis(
|
|
15866
|
+
copulaCorrelationMatrix,
|
|
15867
|
+
varianceThreshold
|
|
15868
|
+
);
|
|
15896
15869
|
const jointTailRiskResult = estimateJointTailRisk(
|
|
15897
15870
|
transformedReturns,
|
|
15898
15871
|
aligned.tradedMask,
|
|
15899
15872
|
tailThreshold
|
|
15900
15873
|
);
|
|
15901
|
-
const analytics = calculateTailRiskAnalytics(
|
|
15902
|
-
jointTailRiskResult.matrix,
|
|
15903
|
-
aligned.strategies
|
|
15904
|
-
);
|
|
15874
|
+
const analytics = calculateTailRiskAnalytics(jointTailRiskResult.matrix, aligned.strategies);
|
|
15905
15875
|
const marginalContributions = calculateMarginalContributions(
|
|
15906
15876
|
copulaCorrelationMatrix,
|
|
15907
15877
|
jointTailRiskResult.matrix,
|
|
@@ -15998,9 +15968,7 @@ function normalizeReturn2(trade, mode) {
|
|
|
15998
15968
|
break;
|
|
15999
15969
|
}
|
|
16000
15970
|
case "notional": {
|
|
16001
|
-
const notional = Math.abs(
|
|
16002
|
-
(trade.openingPrice || 0) * (trade.numContracts || 0)
|
|
16003
|
-
);
|
|
15971
|
+
const notional = Math.abs((trade.openingPrice || 0) * (trade.numContracts || 0));
|
|
16004
15972
|
if (!notional || notional === 0) {
|
|
16005
15973
|
return null;
|
|
16006
15974
|
}
|
|
@@ -16084,10 +16052,7 @@ function performEigenAnalysis(correlationMatrix, varianceThreshold = 0.8) {
|
|
|
16084
16052
|
effectiveFactors
|
|
16085
16053
|
};
|
|
16086
16054
|
} catch (error) {
|
|
16087
|
-
console.warn(
|
|
16088
|
-
"Eigenvalue decomposition failed, using identity fallback:",
|
|
16089
|
-
error
|
|
16090
|
-
);
|
|
16055
|
+
console.warn("Eigenvalue decomposition failed, using identity fallback:", error);
|
|
16091
16056
|
return {
|
|
16092
16057
|
eigenvalues: new Array(n).fill(1),
|
|
16093
16058
|
eigenvectors: correlationMatrix.map(
|
|
@@ -16146,10 +16111,7 @@ function estimateJointTailRisk(transformedReturns, tradedMask, tailThreshold) {
|
|
|
16146
16111
|
}
|
|
16147
16112
|
}
|
|
16148
16113
|
}
|
|
16149
|
-
const minTailObs = getMinTailObservations(
|
|
16150
|
-
tailThreshold,
|
|
16151
|
-
sharedTradingDays
|
|
16152
|
-
);
|
|
16114
|
+
const minTailObs = getMinTailObservations(tailThreshold, sharedTradingDays);
|
|
16153
16115
|
if (iInTailAndBothTraded < minTailObs) {
|
|
16154
16116
|
row.push(NaN);
|
|
16155
16117
|
insufficientPairs++;
|
|
@@ -16220,10 +16182,7 @@ function calculateMarginalContributions(_copulaCorrelationMatrix, jointTailRiskM
|
|
|
16220
16182
|
}
|
|
16221
16183
|
const contributions = [];
|
|
16222
16184
|
const firstEigenvector = eigenvectors[0] || new Array(n).fill(0);
|
|
16223
|
-
const sumAbsLoadings = firstEigenvector.reduce(
|
|
16224
|
-
(sum2, val) => sum2 + Math.abs(val),
|
|
16225
|
-
0
|
|
16226
|
-
);
|
|
16185
|
+
const sumAbsLoadings = firstEigenvector.reduce((sum2, val) => sum2 + Math.abs(val), 0);
|
|
16227
16186
|
for (let i = 0; i < n; i++) {
|
|
16228
16187
|
const concentrationScore = sumAbsLoadings > 0 ? Math.abs(firstEigenvector[i]) / sumAbsLoadings : 1 / n;
|
|
16229
16188
|
let sumJointRisk = 0;
|
|
@@ -20837,8 +20796,10 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
20837
20796
|
if (!trade.dateOpened) return false;
|
|
20838
20797
|
const date = new Date(trade.dateOpened);
|
|
20839
20798
|
if (isNaN(date.getTime())) return false;
|
|
20840
|
-
if (typeof trade.openingCommissionsFees !== "number" || isNaN(trade.openingCommissionsFees))
|
|
20841
|
-
|
|
20799
|
+
if (typeof trade.openingCommissionsFees !== "number" || isNaN(trade.openingCommissionsFees))
|
|
20800
|
+
return false;
|
|
20801
|
+
if (typeof trade.closingCommissionsFees !== "number" || isNaN(trade.closingCommissionsFees))
|
|
20802
|
+
return false;
|
|
20842
20803
|
return true;
|
|
20843
20804
|
} catch {
|
|
20844
20805
|
return false;
|
|
@@ -20877,7 +20838,10 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
20877
20838
|
const streaks = this.calculateStreaks(validTrades);
|
|
20878
20839
|
const timeInDrawdown = this.calculateTimeInDrawdown(validTrades, adjustedDailyLogs);
|
|
20879
20840
|
const periodicWinRates = this.calculatePeriodicWinRates(validTrades);
|
|
20880
|
-
const initialCapital = _PortfolioStatsCalculator.calculateInitialCapital(
|
|
20841
|
+
const initialCapital = _PortfolioStatsCalculator.calculateInitialCapital(
|
|
20842
|
+
validTrades,
|
|
20843
|
+
adjustedDailyLogs
|
|
20844
|
+
);
|
|
20881
20845
|
return {
|
|
20882
20846
|
totalTrades,
|
|
20883
20847
|
totalPl,
|
|
@@ -20915,14 +20879,17 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
20915
20879
|
if (trades.length === 0) {
|
|
20916
20880
|
return {};
|
|
20917
20881
|
}
|
|
20918
|
-
const tradesByStrategy = trades.reduce(
|
|
20919
|
-
|
|
20920
|
-
|
|
20921
|
-
acc[strategy]
|
|
20922
|
-
|
|
20923
|
-
|
|
20924
|
-
|
|
20925
|
-
|
|
20882
|
+
const tradesByStrategy = trades.reduce(
|
|
20883
|
+
(acc, trade) => {
|
|
20884
|
+
const strategy = trade.strategy || "Unknown";
|
|
20885
|
+
if (!acc[strategy]) {
|
|
20886
|
+
acc[strategy] = [];
|
|
20887
|
+
}
|
|
20888
|
+
acc[strategy].push(trade);
|
|
20889
|
+
return acc;
|
|
20890
|
+
},
|
|
20891
|
+
{}
|
|
20892
|
+
);
|
|
20926
20893
|
const strategyStats = {};
|
|
20927
20894
|
Object.entries(tradesByStrategy).forEach(([strategyName, strategyTrades]) => {
|
|
20928
20895
|
const portfolioStats = this.calculatePortfolioStats(strategyTrades);
|
|
@@ -20957,7 +20924,9 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
20957
20924
|
return maxDrawdown2;
|
|
20958
20925
|
}
|
|
20959
20926
|
if (trades.length === 0) return 0;
|
|
20960
|
-
const closedTrades = trades.filter(
|
|
20927
|
+
const closedTrades = trades.filter(
|
|
20928
|
+
(trade) => trade.dateClosed && trade.fundsAtClose !== void 0
|
|
20929
|
+
);
|
|
20961
20930
|
if (closedTrades.length === 0) return 0;
|
|
20962
20931
|
const sortedTrades = [...closedTrades].sort((a, b) => {
|
|
20963
20932
|
try {
|
|
@@ -21058,9 +21027,7 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
21058
21027
|
* Calculate average days to expiration (DTE)
|
|
21059
21028
|
*/
|
|
21060
21029
|
calculateAvgDTE(trades) {
|
|
21061
|
-
const tradesWithDTE = trades.filter(
|
|
21062
|
-
(trade) => trade.dateClosed && trade.dateOpened
|
|
21063
|
-
);
|
|
21030
|
+
const tradesWithDTE = trades.filter((trade) => trade.dateClosed && trade.dateOpened);
|
|
21064
21031
|
if (tradesWithDTE.length === 0) return void 0;
|
|
21065
21032
|
const totalDTE = tradesWithDTE.reduce((sum2, trade) => {
|
|
21066
21033
|
const openDate = new Date(trade.dateOpened);
|
|
@@ -21081,7 +21048,9 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
21081
21048
|
return a.timeOpened.localeCompare(b.timeOpened);
|
|
21082
21049
|
});
|
|
21083
21050
|
const startDate = new Date(sortedTrades[0].dateOpened);
|
|
21084
|
-
const endDate = new Date(
|
|
21051
|
+
const endDate = new Date(
|
|
21052
|
+
sortedTrades[sortedTrades.length - 1].dateClosed || sortedTrades[sortedTrades.length - 1].dateOpened
|
|
21053
|
+
);
|
|
21085
21054
|
const totalYears = (endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24 * 365.25);
|
|
21086
21055
|
if (totalYears <= 0) return void 0;
|
|
21087
21056
|
const initialCapital = _PortfolioStatsCalculator.calculateInitialCapital(trades);
|
|
@@ -21148,7 +21117,9 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
21148
21117
|
if (winningTrades.length === 0 || losingTrades.length === 0) return void 0;
|
|
21149
21118
|
const winRate = winningTrades.length / trades.length;
|
|
21150
21119
|
const avgWin = winningTrades.reduce((sum2, trade) => sum2 + trade.pl, 0) / winningTrades.length;
|
|
21151
|
-
const avgLoss = Math.abs(
|
|
21120
|
+
const avgLoss = Math.abs(
|
|
21121
|
+
losingTrades.reduce((sum2, trade) => sum2 + trade.pl, 0) / losingTrades.length
|
|
21122
|
+
);
|
|
21152
21123
|
if (avgLoss === 0) return void 0;
|
|
21153
21124
|
const winLossRatio = avgWin / avgLoss;
|
|
21154
21125
|
const kellyPercentage = (winRate * winLossRatio - (1 - winRate)) / winLossRatio;
|
|
@@ -21194,7 +21165,9 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
21194
21165
|
return daysInDrawdown / dailyLogEntries.length * 100;
|
|
21195
21166
|
}
|
|
21196
21167
|
if (trades.length === 0) return void 0;
|
|
21197
|
-
const closedTrades = trades.filter(
|
|
21168
|
+
const closedTrades = trades.filter(
|
|
21169
|
+
(trade) => trade.dateClosed && trade.fundsAtClose !== void 0
|
|
21170
|
+
);
|
|
21198
21171
|
if (closedTrades.length === 0) return void 0;
|
|
21199
21172
|
const sortedTrades = [...closedTrades].sort((a, b) => {
|
|
21200
21173
|
try {
|
|
@@ -21243,7 +21216,9 @@ var PortfolioStatsCalculator = class _PortfolioStatsCalculator {
|
|
|
21243
21216
|
}
|
|
21244
21217
|
monthlyTrades.get(monthKey).push(trade);
|
|
21245
21218
|
const startOfYear = new Date(date.getFullYear(), 0, 1);
|
|
21246
|
-
const weekNumber = Math.ceil(
|
|
21219
|
+
const weekNumber = Math.ceil(
|
|
21220
|
+
((date.getTime() - startOfYear.getTime()) / 864e5 + startOfYear.getDay() + 1) / 7
|
|
21221
|
+
);
|
|
21247
21222
|
const weekKey = `${date.getFullYear()}-${String(weekNumber).padStart(2, "0")}`;
|
|
21248
21223
|
if (!weeklyTrades.has(weekKey)) {
|
|
21249
21224
|
weeklyTrades.set(weekKey, []);
|
|
@@ -21420,7 +21395,14 @@ var WalkForwardAnalyzer = class {
|
|
|
21420
21395
|
const calculator = new PortfolioStatsCalculator();
|
|
21421
21396
|
const startedAt = /* @__PURE__ */ new Date();
|
|
21422
21397
|
if (sortedTrades.length === 0) {
|
|
21423
|
-
const emptyResults = this.buildResults(
|
|
21398
|
+
const emptyResults = this.buildResults(
|
|
21399
|
+
[],
|
|
21400
|
+
options.config,
|
|
21401
|
+
0,
|
|
21402
|
+
0,
|
|
21403
|
+
sortedTrades.length,
|
|
21404
|
+
startedAt
|
|
21405
|
+
);
|
|
21424
21406
|
return {
|
|
21425
21407
|
config: options.config,
|
|
21426
21408
|
results: emptyResults,
|
|
@@ -21441,8 +21423,16 @@ var WalkForwardAnalyzer = class {
|
|
|
21441
21423
|
for (let index = 0; index < windows.length; index++) {
|
|
21442
21424
|
this.throwIfAborted(options.signal);
|
|
21443
21425
|
const window2 = windows[index];
|
|
21444
|
-
const inSampleTrades = this.filterTrades(
|
|
21445
|
-
|
|
21426
|
+
const inSampleTrades = this.filterTrades(
|
|
21427
|
+
sortedTrades,
|
|
21428
|
+
window2.inSampleStart,
|
|
21429
|
+
window2.inSampleEnd
|
|
21430
|
+
);
|
|
21431
|
+
const outSampleTrades = this.filterTrades(
|
|
21432
|
+
sortedTrades,
|
|
21433
|
+
window2.outOfSampleStart,
|
|
21434
|
+
window2.outOfSampleEnd
|
|
21435
|
+
);
|
|
21446
21436
|
const minInSample = options.config.minInSampleTrades ?? DEFAULT_MIN_IN_SAMPLE_TRADES;
|
|
21447
21437
|
const minOutSample = options.config.minOutOfSampleTrades ?? DEFAULT_MIN_OUT_SAMPLE_TRADES;
|
|
21448
21438
|
if (inSampleTrades.length < minInSample || outSampleTrades.length < minOutSample) {
|
|
@@ -21451,7 +21441,9 @@ var WalkForwardAnalyzer = class {
|
|
|
21451
21441
|
skippedWindows.push({ ...window2, reason, detail });
|
|
21452
21442
|
continue;
|
|
21453
21443
|
}
|
|
21454
|
-
const combinationIterator = this.buildCombinationIterator(
|
|
21444
|
+
const combinationIterator = this.buildCombinationIterator(
|
|
21445
|
+
options.config.parameterRanges || {}
|
|
21446
|
+
);
|
|
21455
21447
|
if (combinationIterator.count > MAX_PARAMETER_COMBINATIONS) {
|
|
21456
21448
|
throw new Error(
|
|
21457
21449
|
`Walk-forward parameter grid too large (${combinationIterator.count.toLocaleString()} combinations). Reduce ranges or increase step sizes.`
|
|
@@ -21482,7 +21474,12 @@ var WalkForwardAnalyzer = class {
|
|
|
21482
21474
|
inSampleInitialCapital
|
|
21483
21475
|
);
|
|
21484
21476
|
const inSampleStats = calculator.calculatePortfolioStats(scaledInSampleTrades);
|
|
21485
|
-
if (!this.isRiskAcceptable(
|
|
21477
|
+
if (!this.isRiskAcceptable(
|
|
21478
|
+
params,
|
|
21479
|
+
inSampleStats,
|
|
21480
|
+
scaledInSampleTrades,
|
|
21481
|
+
options.config.performanceFloor
|
|
21482
|
+
)) {
|
|
21486
21483
|
continue;
|
|
21487
21484
|
}
|
|
21488
21485
|
if (enforceDiversificationConstraints && diversificationConfig) {
|
|
@@ -21494,7 +21491,10 @@ var WalkForwardAnalyzer = class {
|
|
|
21494
21491
|
continue;
|
|
21495
21492
|
}
|
|
21496
21493
|
}
|
|
21497
|
-
const targetValue = this.getTargetMetricValue(
|
|
21494
|
+
const targetValue = this.getTargetMetricValue(
|
|
21495
|
+
inSampleStats,
|
|
21496
|
+
options.config.optimizationTarget
|
|
21497
|
+
);
|
|
21498
21498
|
if (!Number.isFinite(targetValue)) {
|
|
21499
21499
|
continue;
|
|
21500
21500
|
}
|
|
@@ -21549,7 +21549,10 @@ var WalkForwardAnalyzer = class {
|
|
|
21549
21549
|
inSampleMetrics: bestCombo.inSampleStats,
|
|
21550
21550
|
outOfSampleMetrics: outSampleStats,
|
|
21551
21551
|
targetMetricInSample: bestCombo.score,
|
|
21552
|
-
targetMetricOutOfSample: this.getTargetMetricValue(
|
|
21552
|
+
targetMetricOutOfSample: this.getTargetMetricValue(
|
|
21553
|
+
outSampleStats,
|
|
21554
|
+
options.config.optimizationTarget
|
|
21555
|
+
),
|
|
21553
21556
|
diversificationMetrics
|
|
21554
21557
|
};
|
|
21555
21558
|
periods.push(period);
|
|
@@ -21623,7 +21626,9 @@ var WalkForwardAnalyzer = class {
|
|
|
21623
21626
|
const inSampleStart = new Date(cursor);
|
|
21624
21627
|
const inSampleEnd = new Date(cursor + (config4.inSampleDays - 1) * DAY_MS);
|
|
21625
21628
|
const outOfSampleStart = new Date(inSampleEnd.getTime() + DAY_MS);
|
|
21626
|
-
const outOfSampleEnd = new Date(
|
|
21629
|
+
const outOfSampleEnd = new Date(
|
|
21630
|
+
outOfSampleStart.getTime() + (config4.outOfSampleDays - 1) * DAY_MS
|
|
21631
|
+
);
|
|
21627
21632
|
if (outOfSampleStart > lastDate) {
|
|
21628
21633
|
break;
|
|
21629
21634
|
}
|
|
@@ -21638,7 +21643,9 @@ var WalkForwardAnalyzer = class {
|
|
|
21638
21643
|
return windows;
|
|
21639
21644
|
}
|
|
21640
21645
|
floorToUTCDate(date) {
|
|
21641
|
-
const floored = new Date(
|
|
21646
|
+
const floored = new Date(
|
|
21647
|
+
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
|
|
21648
|
+
);
|
|
21642
21649
|
return floored;
|
|
21643
21650
|
}
|
|
21644
21651
|
buildCombinationIterator(parameterRanges) {
|
|
@@ -21824,10 +21831,7 @@ var WalkForwardAnalyzer = class {
|
|
|
21824
21831
|
maxCorrelationPair,
|
|
21825
21832
|
avgTailDependence: tailRiskResult?.analytics.averageJointTailRisk ?? 0,
|
|
21826
21833
|
maxTailDependence: tailRiskResult?.analytics.highestJointTailRisk.value ?? 0,
|
|
21827
|
-
maxTailDependencePair: tailRiskResult?.analytics.highestJointTailRisk.pair ?? [
|
|
21828
|
-
"",
|
|
21829
|
-
""
|
|
21830
|
-
],
|
|
21834
|
+
maxTailDependencePair: tailRiskResult?.analytics.highestJointTailRisk.pair ?? ["", ""],
|
|
21831
21835
|
effectiveFactors: tailRiskResult?.effectiveFactors ?? correlationMatrix.strategies.length,
|
|
21832
21836
|
highRiskPairsPct: tailRiskResult?.analytics.highRiskPairsPct ?? 0,
|
|
21833
21837
|
// Track insufficient tail data for UI display
|
|
@@ -22198,10 +22202,7 @@ function createSyntheticMaxLossTrades(trades, percentage, simulationLength, base
|
|
|
22198
22202
|
return [];
|
|
22199
22203
|
}
|
|
22200
22204
|
const requestedBudget = Math.ceil(simulationLength * percentage / 100);
|
|
22201
|
-
const cappedBudget = Math.min(
|
|
22202
|
-
simulationLength,
|
|
22203
|
-
Math.max(1, requestedBudget)
|
|
22204
|
-
);
|
|
22205
|
+
const cappedBudget = Math.min(simulationLength, Math.max(1, requestedBudget));
|
|
22205
22206
|
if (cappedBudget <= 0) {
|
|
22206
22207
|
return [];
|
|
22207
22208
|
}
|
|
@@ -22397,9 +22398,7 @@ function calculatePercentageReturns(trades, normalizeTo1Lot, initialCapital) {
|
|
|
22397
22398
|
if (trades.length === 0) {
|
|
22398
22399
|
return [];
|
|
22399
22400
|
}
|
|
22400
|
-
const sortedTrades = [...trades].sort(
|
|
22401
|
-
(a, b) => a.dateOpened.getTime() - b.dateOpened.getTime()
|
|
22402
|
-
);
|
|
22401
|
+
const sortedTrades = [...trades].sort((a, b) => a.dateOpened.getTime() - b.dateOpened.getTime());
|
|
22403
22402
|
const percentageReturns = [];
|
|
22404
22403
|
let capital;
|
|
22405
22404
|
if (initialCapital !== void 0 && initialCapital > 0) {
|
|
@@ -22424,9 +22423,7 @@ function calculateMarginReturns(trades) {
|
|
|
22424
22423
|
if (trades.length === 0) {
|
|
22425
22424
|
return [];
|
|
22426
22425
|
}
|
|
22427
|
-
const sortedTrades = [...trades].sort(
|
|
22428
|
-
(a, b) => a.dateOpened.getTime() - b.dateOpened.getTime()
|
|
22429
|
-
);
|
|
22426
|
+
const sortedTrades = [...trades].sort((a, b) => a.dateOpened.getTime() - b.dateOpened.getTime());
|
|
22430
22427
|
const returns = [];
|
|
22431
22428
|
for (const trade of sortedTrades) {
|
|
22432
22429
|
if (trade.marginReq > 0) {
|
|
@@ -22520,25 +22517,13 @@ function runMonteCarloSimulation(trades, params) {
|
|
|
22520
22517
|
let actualResamplePoolSize;
|
|
22521
22518
|
const isPercentageMode = params.resampleMethod === "percentage";
|
|
22522
22519
|
if (params.resampleMethod === "trades") {
|
|
22523
|
-
const tradePool = getTradeResamplePool(
|
|
22524
|
-
trades,
|
|
22525
|
-
params.resampleWindow,
|
|
22526
|
-
params.strategy
|
|
22527
|
-
);
|
|
22520
|
+
const tradePool = getTradeResamplePool(trades, params.resampleWindow, params.strategy);
|
|
22528
22521
|
actualResamplePoolSize = tradePool.length;
|
|
22529
|
-
resamplePool = tradePool.map(
|
|
22530
|
-
(t) => params.normalizeTo1Lot ? scaleTradeToOneLot(t) : t.pl
|
|
22531
|
-
);
|
|
22522
|
+
resamplePool = tradePool.map((t) => params.normalizeTo1Lot ? scaleTradeToOneLot(t) : t.pl);
|
|
22532
22523
|
} else if (params.resampleMethod === "daily") {
|
|
22533
22524
|
const filteredTrades = params.strategy && params.strategy !== "all" ? trades.filter((t) => t.strategy === params.strategy) : trades;
|
|
22534
|
-
const dailyReturns = calculateDailyReturns(
|
|
22535
|
-
|
|
22536
|
-
params.normalizeTo1Lot
|
|
22537
|
-
);
|
|
22538
|
-
const dailyPLs = getDailyResamplePool(
|
|
22539
|
-
dailyReturns,
|
|
22540
|
-
params.resampleWindow
|
|
22541
|
-
);
|
|
22525
|
+
const dailyReturns = calculateDailyReturns(filteredTrades, params.normalizeTo1Lot);
|
|
22526
|
+
const dailyPLs = getDailyResamplePool(dailyReturns, params.resampleWindow);
|
|
22542
22527
|
actualResamplePoolSize = dailyPLs.length;
|
|
22543
22528
|
resamplePool = dailyPLs;
|
|
22544
22529
|
} else {
|
|
@@ -22557,10 +22542,7 @@ function runMonteCarloSimulation(trades, params) {
|
|
|
22557
22542
|
params.historicalInitialCapital
|
|
22558
22543
|
// Use historical capital (if provided) to reconstruct trajectory
|
|
22559
22544
|
);
|
|
22560
|
-
const percentagePool = getPercentageResamplePool(
|
|
22561
|
-
percentageReturns,
|
|
22562
|
-
params.resampleWindow
|
|
22563
|
-
);
|
|
22545
|
+
const percentagePool = getPercentageResamplePool(percentageReturns, params.resampleWindow);
|
|
22564
22546
|
actualResamplePoolSize = percentagePool.length;
|
|
22565
22547
|
resamplePool = percentagePool;
|
|
22566
22548
|
}
|
|
@@ -22611,20 +22593,13 @@ function runMonteCarloSimulation(trades, params) {
|
|
|
22611
22593
|
resamplePool = [...resamplePool, ...worstCaseTrades];
|
|
22612
22594
|
}
|
|
22613
22595
|
}
|
|
22614
|
-
const enforcedGuaranteeTrades = params.worstCaseEnabled && params.worstCaseMode === "guarantee" && params.simulationLength > 0 ? worstCaseTrades.slice(
|
|
22615
|
-
0,
|
|
22616
|
-
Math.min(worstCaseTrades.length, params.simulationLength)
|
|
22617
|
-
) : [];
|
|
22596
|
+
const enforcedGuaranteeTrades = params.worstCaseEnabled && params.worstCaseMode === "guarantee" && params.simulationLength > 0 ? worstCaseTrades.slice(0, Math.min(worstCaseTrades.length, params.simulationLength)) : [];
|
|
22618
22597
|
const simulations = [];
|
|
22619
22598
|
for (let i = 0; i < params.numSimulations; i++) {
|
|
22620
22599
|
const seed = params.randomSeed !== void 0 ? params.randomSeed + i : void 0;
|
|
22621
22600
|
const guaranteeActive = enforcedGuaranteeTrades.length > 0;
|
|
22622
22601
|
const baselineSampleSize = guaranteeActive ? Math.max(0, params.simulationLength - enforcedGuaranteeTrades.length) : params.simulationLength;
|
|
22623
|
-
let resampledPLs = resampleWithReplacement(
|
|
22624
|
-
resamplePool,
|
|
22625
|
-
baselineSampleSize,
|
|
22626
|
-
seed
|
|
22627
|
-
);
|
|
22602
|
+
let resampledPLs = resampleWithReplacement(resamplePool, baselineSampleSize, seed);
|
|
22628
22603
|
if (guaranteeActive) {
|
|
22629
22604
|
const combined = [...resampledPLs];
|
|
22630
22605
|
const rng = seed !== void 0 ? createSeededRandom(seed + 999999) : Math.random;
|
|
@@ -22714,10 +22689,7 @@ function calculateStatistics(simulations) {
|
|
|
22714
22689
|
50
|
|
22715
22690
|
);
|
|
22716
22691
|
const meanSharpeRatio = sharpeRatios.reduce((sum2, s) => sum2 + s, 0) / sharpeRatios.length;
|
|
22717
|
-
const variance2 = finalValues.reduce(
|
|
22718
|
-
(sum2, v) => sum2 + Math.pow(v - meanFinalValue, 2),
|
|
22719
|
-
0
|
|
22720
|
-
) / (finalValues.length - 1);
|
|
22692
|
+
const variance2 = finalValues.reduce((sum2, v) => sum2 + Math.pow(v - meanFinalValue, 2), 0) / (finalValues.length - 1);
|
|
22721
22693
|
const stdFinalValue = Math.sqrt(variance2);
|
|
22722
22694
|
const profitableSimulations = totalReturns.filter((r) => r > 0).length;
|
|
22723
22695
|
const probabilityOfProfit = profitableSimulations / totalReturns.length;
|
|
@@ -22908,10 +22880,7 @@ function linearRegression(y) {
|
|
|
22908
22880
|
const slope = sumX2 > 0 ? sumXY / sumX2 : 0;
|
|
22909
22881
|
const intercept = meanY - slope * meanX;
|
|
22910
22882
|
const predicted = x.map((xi) => slope * xi + intercept);
|
|
22911
|
-
const ssRes = y.reduce(
|
|
22912
|
-
(sum2, yi, i) => sum2 + (yi - predicted[i]) ** 2,
|
|
22913
|
-
0
|
|
22914
|
-
);
|
|
22883
|
+
const ssRes = y.reduce((sum2, yi, i) => sum2 + (yi - predicted[i]) ** 2, 0);
|
|
22915
22884
|
const ssTot = y.reduce((sum2, yi) => sum2 + (yi - meanY) ** 2, 0);
|
|
22916
22885
|
const rSquared = ssTot > 0 ? 1 - ssRes / ssTot : 0;
|
|
22917
22886
|
const mse = n > 2 ? ssRes / (n - 2) : 0;
|
|
@@ -23152,7 +23121,13 @@ function segmentByPeriod(trades) {
|
|
|
23152
23121
|
const monthDate = parseMonthKey(m.periodKey);
|
|
23153
23122
|
return monthDate !== null && getQuarterKey(monthDate) === qKey;
|
|
23154
23123
|
}).map((m) => m.avgMonthlyReturnPct);
|
|
23155
|
-
return computePeriodMetrics(
|
|
23124
|
+
return computePeriodMetrics(
|
|
23125
|
+
qKey,
|
|
23126
|
+
quarterTrades,
|
|
23127
|
+
isPartial,
|
|
23128
|
+
partialNote,
|
|
23129
|
+
constituentMonthlyReturns
|
|
23130
|
+
);
|
|
23156
23131
|
});
|
|
23157
23132
|
const yearlyGroups = groupTradesByKey(sortedTrades, getYearKey);
|
|
23158
23133
|
const yearKeys = Array.from(yearlyGroups.keys()).sort();
|
|
@@ -23165,7 +23140,13 @@ function segmentByPeriod(trades) {
|
|
|
23165
23140
|
index === yearKeys.length - 1
|
|
23166
23141
|
);
|
|
23167
23142
|
const constituentMonthlyReturns = monthlyMetrics.filter((m) => m.periodKey.startsWith(yKey + "-")).map((m) => m.avgMonthlyReturnPct);
|
|
23168
|
-
return computePeriodMetrics(
|
|
23143
|
+
return computePeriodMetrics(
|
|
23144
|
+
yKey,
|
|
23145
|
+
yearTrades,
|
|
23146
|
+
isPartial,
|
|
23147
|
+
partialNote,
|
|
23148
|
+
constituentMonthlyReturns
|
|
23149
|
+
);
|
|
23169
23150
|
});
|
|
23170
23151
|
const yearlyTrends = computePeriodTrends(yearlyMetrics);
|
|
23171
23152
|
const quarterlyTrends = computePeriodTrends(quarterlyMetrics);
|
|
@@ -23362,7 +23343,14 @@ function computeRollingMetrics(trades, options) {
|
|
|
23362
23343
|
};
|
|
23363
23344
|
}
|
|
23364
23345
|
function computeSeasonalAverages(series) {
|
|
23365
|
-
const metricNames = [
|
|
23346
|
+
const metricNames = [
|
|
23347
|
+
"winRate",
|
|
23348
|
+
"profitFactor",
|
|
23349
|
+
"kellyPercent",
|
|
23350
|
+
"sharpeRatio",
|
|
23351
|
+
"avgReturn",
|
|
23352
|
+
"netPl"
|
|
23353
|
+
];
|
|
23366
23354
|
const buckets = {};
|
|
23367
23355
|
for (const metric of metricNames) {
|
|
23368
23356
|
buckets[metric] = { Q1: [], Q2: [], Q3: [], Q4: [] };
|
|
@@ -23598,9 +23586,17 @@ function runRegimeComparison(trades, options) {
|
|
|
23598
23586
|
};
|
|
23599
23587
|
const recentResult = runMonteCarloSimulation(recentPool, recentParams);
|
|
23600
23588
|
const metricPairs = [
|
|
23601
|
-
[
|
|
23589
|
+
[
|
|
23590
|
+
"probabilityOfProfit",
|
|
23591
|
+
fullResult.statistics.probabilityOfProfit,
|
|
23592
|
+
recentResult.statistics.probabilityOfProfit
|
|
23593
|
+
],
|
|
23602
23594
|
["sharpeRatio", fullResult.statistics.meanSharpeRatio, recentResult.statistics.meanSharpeRatio],
|
|
23603
|
-
[
|
|
23595
|
+
[
|
|
23596
|
+
"medianMaxDrawdown",
|
|
23597
|
+
fullResult.statistics.medianMaxDrawdown,
|
|
23598
|
+
recentResult.statistics.medianMaxDrawdown
|
|
23599
|
+
]
|
|
23604
23600
|
];
|
|
23605
23601
|
const comparison = metricPairs.map(([metric, fullValue, recentValue]) => {
|
|
23606
23602
|
const delta = recentValue - fullValue;
|
|
@@ -23773,7 +23769,6 @@ function analyzeWalkForwardDegradation(trades, options) {
|
|
|
23773
23769
|
if (options) {
|
|
23774
23770
|
for (const [key, value] of Object.entries(options)) {
|
|
23775
23771
|
if (value !== void 0) {
|
|
23776
|
-
;
|
|
23777
23772
|
definedOptions[key] = value;
|
|
23778
23773
|
}
|
|
23779
23774
|
}
|
|
@@ -23862,7 +23857,11 @@ function analyzeWalkForwardDegradation(trades, options) {
|
|
|
23862
23857
|
const oosMetrics = computeMetrics(oosTrades);
|
|
23863
23858
|
const sharpeEfficiency = computeEfficiency(oosMetrics.sharpe, isMetrics.sharpe, "sharpe");
|
|
23864
23859
|
const winRateEfficiency = computeEfficiency(oosMetrics.winRate, isMetrics.winRate, "winRate");
|
|
23865
|
-
const pfEfficiency = computeEfficiency(
|
|
23860
|
+
const pfEfficiency = computeEfficiency(
|
|
23861
|
+
oosMetrics.profitFactor,
|
|
23862
|
+
isMetrics.profitFactor,
|
|
23863
|
+
"profitFactor"
|
|
23864
|
+
);
|
|
23866
23865
|
if (isMetrics.sharpe !== null && isMetrics.sharpe < 0) {
|
|
23867
23866
|
warnings.push(
|
|
23868
23867
|
`Negative IS Sharpe in period ${window2.periodIndex} -- efficiency ratio may be misleading`
|
|
@@ -23871,9 +23870,21 @@ function analyzeWalkForwardDegradation(trades, options) {
|
|
|
23871
23870
|
periods.push({
|
|
23872
23871
|
window: window2,
|
|
23873
23872
|
metrics: {
|
|
23874
|
-
sharpe: {
|
|
23875
|
-
|
|
23876
|
-
|
|
23873
|
+
sharpe: {
|
|
23874
|
+
inSample: isMetrics.sharpe,
|
|
23875
|
+
outOfSample: oosMetrics.sharpe,
|
|
23876
|
+
efficiency: sharpeEfficiency
|
|
23877
|
+
},
|
|
23878
|
+
winRate: {
|
|
23879
|
+
inSample: isMetrics.winRate,
|
|
23880
|
+
outOfSample: oosMetrics.winRate,
|
|
23881
|
+
efficiency: winRateEfficiency
|
|
23882
|
+
},
|
|
23883
|
+
profitFactor: {
|
|
23884
|
+
inSample: isMetrics.profitFactor,
|
|
23885
|
+
outOfSample: oosMetrics.profitFactor,
|
|
23886
|
+
efficiency: pfEfficiency
|
|
23887
|
+
}
|
|
23877
23888
|
},
|
|
23878
23889
|
sufficient: true,
|
|
23879
23890
|
warnings
|
|
@@ -23917,7 +23928,10 @@ function analyzeWalkForwardDegradation(trades, options) {
|
|
|
23917
23928
|
const qualifyingCount = qualifyingPeriods.length;
|
|
23918
23929
|
const recentCount = Math.min(config4.recentPeriodCount, qualifyingCount);
|
|
23919
23930
|
const recentPeriodSlice = qualifyingPeriods.slice(-recentCount);
|
|
23920
|
-
const historicalPeriodSlice = qualifyingPeriods.slice(
|
|
23931
|
+
const historicalPeriodSlice = qualifyingPeriods.slice(
|
|
23932
|
+
0,
|
|
23933
|
+
Math.max(0, qualifyingCount - recentCount)
|
|
23934
|
+
);
|
|
23921
23935
|
const recentData = getEfficiencyData(recentPeriodSlice);
|
|
23922
23936
|
const historicalData = getEfficiencyData(historicalPeriodSlice);
|
|
23923
23937
|
const recentAvgEfficiency = {
|
|
@@ -23971,6 +23985,69 @@ function analyzeWalkForwardDegradation(trades, options) {
|
|
|
23971
23985
|
};
|
|
23972
23986
|
}
|
|
23973
23987
|
|
|
23988
|
+
// ../lib/calculations/trade-set-alignment.ts
|
|
23989
|
+
var KEY_SEPARATOR = " ";
|
|
23990
|
+
var REASON_EMPTY_STRATEGY = "empty or non-string strategy";
|
|
23991
|
+
var REASON_INVALID_DATE = "invalid dateOpened";
|
|
23992
|
+
function computeMatchKey(strategy, dateOpened, time) {
|
|
23993
|
+
if (typeof strategy !== "string" || strategy.length === 0) {
|
|
23994
|
+
return { key: null, reason: REASON_EMPTY_STRATEGY };
|
|
23995
|
+
}
|
|
23996
|
+
const dateKey = formatDateKey2(new Date(dateOpened));
|
|
23997
|
+
if (dateKey.includes("NaN")) {
|
|
23998
|
+
return { key: null, reason: REASON_INVALID_DATE };
|
|
23999
|
+
}
|
|
24000
|
+
const timeKey = truncateTimeToMinute(time);
|
|
24001
|
+
return { key: `${dateKey}${KEY_SEPARATOR}${strategy}${KEY_SEPARATOR}${timeKey}`, reason: null };
|
|
24002
|
+
}
|
|
24003
|
+
function matchTradeSets(backtestTrades, actualTrades) {
|
|
24004
|
+
const actualByKey = /* @__PURE__ */ new Map();
|
|
24005
|
+
const unusableActual = [];
|
|
24006
|
+
actualTrades.forEach((trade, index) => {
|
|
24007
|
+
const { key, reason } = computeMatchKey(
|
|
24008
|
+
trade.strategy,
|
|
24009
|
+
trade.dateOpened,
|
|
24010
|
+
trade.rawTimeOpened ?? trade.timeOpened
|
|
24011
|
+
);
|
|
24012
|
+
if (key === null) {
|
|
24013
|
+
unusableActual.push({ index, reason });
|
|
24014
|
+
return;
|
|
24015
|
+
}
|
|
24016
|
+
const queue = actualByKey.get(key);
|
|
24017
|
+
if (queue) queue.push(index);
|
|
24018
|
+
else actualByKey.set(key, [index]);
|
|
24019
|
+
});
|
|
24020
|
+
const matched = [];
|
|
24021
|
+
const unmatchedBacktestIndices = [];
|
|
24022
|
+
const unusableBacktest = [];
|
|
24023
|
+
backtestTrades.forEach((trade, index) => {
|
|
24024
|
+
const { key, reason } = computeMatchKey(trade.strategy, trade.dateOpened, trade.timeOpened);
|
|
24025
|
+
if (key === null) {
|
|
24026
|
+
unusableBacktest.push({ index, reason });
|
|
24027
|
+
return;
|
|
24028
|
+
}
|
|
24029
|
+
const queue = actualByKey.get(key);
|
|
24030
|
+
if (queue && queue.length > 0) {
|
|
24031
|
+
const actualIndex = queue.shift();
|
|
24032
|
+
matched.push({ backtestIndex: index, actualIndex });
|
|
24033
|
+
} else {
|
|
24034
|
+
unmatchedBacktestIndices.push(index);
|
|
24035
|
+
}
|
|
24036
|
+
});
|
|
24037
|
+
const unmatchedActualIndices = [];
|
|
24038
|
+
for (const queue of actualByKey.values()) {
|
|
24039
|
+
for (const idx of queue) unmatchedActualIndices.push(idx);
|
|
24040
|
+
}
|
|
24041
|
+
unmatchedActualIndices.sort((a, b) => a - b);
|
|
24042
|
+
return {
|
|
24043
|
+
matched,
|
|
24044
|
+
unmatchedBacktestIndices,
|
|
24045
|
+
unmatchedActualIndices,
|
|
24046
|
+
unusableBacktest,
|
|
24047
|
+
unusableActual
|
|
24048
|
+
};
|
|
24049
|
+
}
|
|
24050
|
+
|
|
23974
24051
|
// ../lib/calculations/trade-matching.ts
|
|
23975
24052
|
function formatDateKey2(d) {
|
|
23976
24053
|
const year = d.getFullYear();
|
|
@@ -23980,11 +24057,17 @@ function formatDateKey2(d) {
|
|
|
23980
24057
|
}
|
|
23981
24058
|
function truncateTimeToMinute(time) {
|
|
23982
24059
|
if (!time) return "00:00";
|
|
23983
|
-
const
|
|
23984
|
-
if (
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
24060
|
+
const match = time.trim().match(/^(\d{1,2}):(\d{2})(?::\d{2}(?:\.\d+)?)?\s*([AP]M)?$/i);
|
|
24061
|
+
if (!match) return "00:00";
|
|
24062
|
+
let hour = Number(match[1]);
|
|
24063
|
+
const period = match[3]?.toUpperCase();
|
|
24064
|
+
if (period) {
|
|
24065
|
+
if (hour < 1 || hour > 12) return "00:00";
|
|
24066
|
+
hour = hour % 12 + (period === "PM" ? 12 : 0);
|
|
24067
|
+
} else if (hour > 23) {
|
|
24068
|
+
return "00:00";
|
|
24069
|
+
}
|
|
24070
|
+
return `${String(hour).padStart(2, "0")}:${match[2]}`;
|
|
23988
24071
|
}
|
|
23989
24072
|
function parseHourFromTime(timeOpened) {
|
|
23990
24073
|
if (!timeOpened || typeof timeOpened !== "string") return null;
|
|
@@ -24004,9 +24087,7 @@ function getIsoWeekKey2(dateStr) {
|
|
|
24004
24087
|
const dayOfWeek = thursday.getUTCDay() || 7;
|
|
24005
24088
|
thursday.setUTCDate(thursday.getUTCDate() + (4 - dayOfWeek));
|
|
24006
24089
|
const yearStart = new Date(Date.UTC(thursday.getUTCFullYear(), 0, 1));
|
|
24007
|
-
const weekNum = Math.ceil(
|
|
24008
|
-
((thursday.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
|
|
24009
|
-
);
|
|
24090
|
+
const weekNum = Math.ceil(((thursday.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
24010
24091
|
return `${thursday.getUTCFullYear()}-W${String(weekNum).padStart(2, "0")}`;
|
|
24011
24092
|
}
|
|
24012
24093
|
function getMonthKey3(dateStr) {
|
|
@@ -24025,9 +24106,7 @@ function applyDateRangeFilter(trades, dateRange) {
|
|
|
24025
24106
|
}
|
|
24026
24107
|
function applyStrategyFilter(trades, strategy) {
|
|
24027
24108
|
if (!strategy) return trades;
|
|
24028
|
-
return trades.filter(
|
|
24029
|
-
(t) => t.strategy.toLowerCase() === strategy.toLowerCase()
|
|
24030
|
-
);
|
|
24109
|
+
return trades.filter((t) => t.strategy.toLowerCase() === strategy.toLowerCase());
|
|
24031
24110
|
}
|
|
24032
24111
|
function calculateScaledPl(btPl, actualPl, btContracts, actualContracts, scaling) {
|
|
24033
24112
|
let scaledBtPl = btPl;
|
|
@@ -24046,111 +24125,88 @@ function calculateScaledPl(btPl, actualPl, btContracts, actualContracts, scaling
|
|
|
24046
24125
|
return { scaledBtPl, scaledActualPl };
|
|
24047
24126
|
}
|
|
24048
24127
|
function matchTrades(backtestTrades, actualTrades, scaling) {
|
|
24049
|
-
const
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24128
|
+
const {
|
|
24129
|
+
matched,
|
|
24130
|
+
unmatchedBacktestIndices,
|
|
24131
|
+
unmatchedActualIndices,
|
|
24132
|
+
unusableBacktest,
|
|
24133
|
+
unusableActual
|
|
24134
|
+
} = matchTradeSets(backtestTrades, actualTrades);
|
|
24135
|
+
const matchedTrades = matched.map(({ backtestIndex, actualIndex }) => {
|
|
24136
|
+
const btTrade = backtestTrades[backtestIndex];
|
|
24137
|
+
const actualTrade = actualTrades[actualIndex];
|
|
24138
|
+
const { scaledBtPl, scaledActualPl } = calculateScaledPl(
|
|
24139
|
+
btTrade.pl,
|
|
24140
|
+
actualTrade.pl,
|
|
24141
|
+
btTrade.numContracts,
|
|
24142
|
+
actualTrade.numContracts,
|
|
24143
|
+
scaling
|
|
24144
|
+
);
|
|
24145
|
+
return {
|
|
24146
|
+
date: formatDateKey2(new Date(btTrade.dateOpened)),
|
|
24147
|
+
strategy: btTrade.strategy,
|
|
24148
|
+
timeOpened: truncateTimeToMinute(btTrade.timeOpened),
|
|
24149
|
+
// Total slippage = actual P/L - backtest P/L (after scaling)
|
|
24150
|
+
totalSlippage: scaledActualPl - scaledBtPl,
|
|
24151
|
+
openingVix: btTrade.openingVix,
|
|
24152
|
+
closingVix: btTrade.closingVix,
|
|
24153
|
+
gap: btTrade.gap,
|
|
24154
|
+
movement: btTrade.movement,
|
|
24155
|
+
hourOfDay: parseHourFromTime(btTrade.timeOpened),
|
|
24156
|
+
contracts: actualTrade.numContracts
|
|
24157
|
+
};
|
|
24057
24158
|
});
|
|
24058
|
-
const
|
|
24059
|
-
|
|
24060
|
-
let unmatchedActualCount = actualTrades.length;
|
|
24061
|
-
for (const btTrade of backtestTrades) {
|
|
24062
|
-
const dateKey = formatDateKey2(new Date(btTrade.dateOpened));
|
|
24063
|
-
const timeKey = truncateTimeToMinute(btTrade.timeOpened);
|
|
24064
|
-
const key = `${dateKey}|${btTrade.strategy}|${timeKey}`;
|
|
24065
|
-
const actualMatches = actualByKey.get(key);
|
|
24066
|
-
const actualTrade = actualMatches?.[0];
|
|
24067
|
-
if (actualTrade) {
|
|
24068
|
-
unmatchedActualCount--;
|
|
24069
|
-
if (actualMatches && actualMatches.length > 1) {
|
|
24070
|
-
actualByKey.set(key, actualMatches.slice(1));
|
|
24071
|
-
} else {
|
|
24072
|
-
actualByKey.delete(key);
|
|
24073
|
-
}
|
|
24074
|
-
const { scaledBtPl, scaledActualPl } = calculateScaledPl(
|
|
24075
|
-
btTrade.pl,
|
|
24076
|
-
actualTrade.pl,
|
|
24077
|
-
btTrade.numContracts,
|
|
24078
|
-
actualTrade.numContracts,
|
|
24079
|
-
scaling
|
|
24080
|
-
);
|
|
24081
|
-
const totalSlippage = scaledActualPl - scaledBtPl;
|
|
24082
|
-
matchedTrades.push({
|
|
24083
|
-
date: dateKey,
|
|
24084
|
-
strategy: btTrade.strategy,
|
|
24085
|
-
timeOpened: timeKey,
|
|
24086
|
-
totalSlippage,
|
|
24087
|
-
openingVix: btTrade.openingVix,
|
|
24088
|
-
closingVix: btTrade.closingVix,
|
|
24089
|
-
gap: btTrade.gap,
|
|
24090
|
-
movement: btTrade.movement,
|
|
24091
|
-
hourOfDay: parseHourFromTime(btTrade.timeOpened),
|
|
24092
|
-
contracts: actualTrade.numContracts
|
|
24093
|
-
});
|
|
24094
|
-
} else {
|
|
24095
|
-
unmatchedBacktestCount++;
|
|
24096
|
-
}
|
|
24097
|
-
}
|
|
24159
|
+
const unmatchedBacktestCount = unmatchedBacktestIndices.length + unusableBacktest.length;
|
|
24160
|
+
const unmatchedActualCount = unmatchedActualIndices.length + unusableActual.length;
|
|
24098
24161
|
return { matchedTrades, unmatchedBacktestCount, unmatchedActualCount };
|
|
24099
24162
|
}
|
|
24100
24163
|
|
|
24101
24164
|
// ../lib/calculations/live-alignment.ts
|
|
24102
24165
|
function matchTradesWithScaledPl(backtestTrades, actualTrades, scaling) {
|
|
24103
|
-
const
|
|
24104
|
-
|
|
24105
|
-
|
|
24106
|
-
|
|
24107
|
-
|
|
24108
|
-
|
|
24109
|
-
|
|
24110
|
-
|
|
24111
|
-
|
|
24112
|
-
|
|
24166
|
+
const {
|
|
24167
|
+
matched,
|
|
24168
|
+
unmatchedBacktestIndices,
|
|
24169
|
+
unmatchedActualIndices,
|
|
24170
|
+
unusableBacktest,
|
|
24171
|
+
unusableActual
|
|
24172
|
+
} = matchTradeSets(backtestTrades, actualTrades);
|
|
24173
|
+
const pairs = matched.map(({ backtestIndex, actualIndex }) => {
|
|
24174
|
+
const btTrade = backtestTrades[backtestIndex];
|
|
24175
|
+
const actualTrade = actualTrades[actualIndex];
|
|
24176
|
+
const { scaledBtPl, scaledActualPl } = calculateScaledPl(
|
|
24177
|
+
btTrade.pl,
|
|
24178
|
+
actualTrade.pl,
|
|
24179
|
+
btTrade.numContracts,
|
|
24180
|
+
actualTrade.numContracts,
|
|
24181
|
+
scaling
|
|
24182
|
+
);
|
|
24183
|
+
return {
|
|
24184
|
+
date: formatDateKey2(new Date(btTrade.dateOpened)),
|
|
24185
|
+
strategy: btTrade.strategy,
|
|
24186
|
+
scaledBtPl,
|
|
24187
|
+
scaledActualPl,
|
|
24188
|
+
slippage: scaledActualPl - scaledBtPl,
|
|
24189
|
+
btContracts: btTrade.numContracts,
|
|
24190
|
+
actualContracts: actualTrade.numContracts
|
|
24191
|
+
};
|
|
24192
|
+
});
|
|
24113
24193
|
const unmatchedBacktestByStrategy = /* @__PURE__ */ new Map();
|
|
24194
|
+
for (const index of unmatchedBacktestIndices) {
|
|
24195
|
+
const strat = backtestTrades[index].strategy;
|
|
24196
|
+
unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
|
|
24197
|
+
}
|
|
24198
|
+
for (const { index } of unusableBacktest) {
|
|
24199
|
+
const strat = backtestTrades[index].strategy;
|
|
24200
|
+
unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
|
|
24201
|
+
}
|
|
24114
24202
|
const unmatchedActualByStrategy = /* @__PURE__ */ new Map();
|
|
24115
|
-
for (const
|
|
24116
|
-
const strat =
|
|
24203
|
+
for (const index of unmatchedActualIndices) {
|
|
24204
|
+
const strat = actualTrades[index].strategy;
|
|
24117
24205
|
unmatchedActualByStrategy.set(strat, (unmatchedActualByStrategy.get(strat) || 0) + 1);
|
|
24118
24206
|
}
|
|
24119
|
-
for (const
|
|
24120
|
-
const
|
|
24121
|
-
|
|
24122
|
-
const key = `${dateKey} ${btTrade.strategy} ${timeKey}`;
|
|
24123
|
-
const actualMatches = actualByKey.get(key);
|
|
24124
|
-
const actualTrade = actualMatches?.[0];
|
|
24125
|
-
if (actualTrade) {
|
|
24126
|
-
const strat = actualTrade.strategy;
|
|
24127
|
-
const remaining = (unmatchedActualByStrategy.get(strat) || 1) - 1;
|
|
24128
|
-
unmatchedActualByStrategy.set(strat, remaining);
|
|
24129
|
-
if (actualMatches && actualMatches.length > 1) {
|
|
24130
|
-
actualByKey.set(key, actualMatches.slice(1));
|
|
24131
|
-
} else {
|
|
24132
|
-
actualByKey.delete(key);
|
|
24133
|
-
}
|
|
24134
|
-
const { scaledBtPl, scaledActualPl } = calculateScaledPl(
|
|
24135
|
-
btTrade.pl,
|
|
24136
|
-
actualTrade.pl,
|
|
24137
|
-
btTrade.numContracts,
|
|
24138
|
-
actualTrade.numContracts,
|
|
24139
|
-
scaling
|
|
24140
|
-
);
|
|
24141
|
-
pairs.push({
|
|
24142
|
-
date: dateKey,
|
|
24143
|
-
strategy: btTrade.strategy,
|
|
24144
|
-
scaledBtPl,
|
|
24145
|
-
scaledActualPl,
|
|
24146
|
-
slippage: scaledActualPl - scaledBtPl,
|
|
24147
|
-
btContracts: btTrade.numContracts,
|
|
24148
|
-
actualContracts: actualTrade.numContracts
|
|
24149
|
-
});
|
|
24150
|
-
} else {
|
|
24151
|
-
const strat = btTrade.strategy;
|
|
24152
|
-
unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
|
|
24153
|
-
}
|
|
24207
|
+
for (const { index } of unusableActual) {
|
|
24208
|
+
const strat = actualTrades[index].strategy;
|
|
24209
|
+
unmatchedActualByStrategy.set(strat, (unmatchedActualByStrategy.get(strat) || 0) + 1);
|
|
24154
24210
|
}
|
|
24155
24211
|
return { pairs, unmatchedBacktestByStrategy, unmatchedActualByStrategy };
|
|
24156
24212
|
}
|
|
@@ -24200,20 +24256,33 @@ function analyzeLiveAlignment(backtestTrades, actualTrades, options) {
|
|
|
24200
24256
|
filteredBacktest = [];
|
|
24201
24257
|
filteredActual = [];
|
|
24202
24258
|
}
|
|
24259
|
+
const outsideOverlapBacktestCount = backtestTrades.length - filteredBacktest.length;
|
|
24260
|
+
const outsideOverlapActualCount = actualTrades.length - filteredActual.length;
|
|
24261
|
+
if (overlapRange && (outsideOverlapBacktestCount > 0 || outsideOverlapActualCount > 0)) {
|
|
24262
|
+
warnings.push(
|
|
24263
|
+
`${outsideOverlapBacktestCount} backtest trade(s) and ${outsideOverlapActualCount} actual trade(s) fall outside the shared overlap window and are excluded from alignment metrics`
|
|
24264
|
+
);
|
|
24265
|
+
}
|
|
24203
24266
|
if (backtestTrades.length === 0) {
|
|
24204
24267
|
warnings.push("No backtest trades provided");
|
|
24205
24268
|
}
|
|
24206
24269
|
if (actualTrades.length === 0) {
|
|
24207
24270
|
warnings.push("No actual trades provided");
|
|
24208
24271
|
}
|
|
24209
|
-
const { pairs, unmatchedBacktestByStrategy, unmatchedActualByStrategy } = matchTradesWithScaledPl(
|
|
24272
|
+
const { pairs, unmatchedBacktestByStrategy, unmatchedActualByStrategy } = matchTradesWithScaledPl(
|
|
24273
|
+
filteredBacktest,
|
|
24274
|
+
filteredActual,
|
|
24275
|
+
scaling
|
|
24276
|
+
);
|
|
24210
24277
|
if (pairs.length === 0 && (backtestTrades.length > 0 || actualTrades.length > 0)) {
|
|
24211
24278
|
warnings.push("No matched trade pairs found");
|
|
24212
24279
|
}
|
|
24213
24280
|
const minOverlapTrades = Math.min(filteredBacktest.length, filteredActual.length);
|
|
24214
24281
|
const matchRate = minOverlapTrades > 0 ? pairs.length / minOverlapTrades : 0;
|
|
24215
24282
|
if (matchRate > 0 && matchRate < 0.5) {
|
|
24216
|
-
warnings.push(
|
|
24283
|
+
warnings.push(
|
|
24284
|
+
`Low match rate (${(matchRate * 100).toFixed(1)}%) - trade matching may be unreliable`
|
|
24285
|
+
);
|
|
24217
24286
|
}
|
|
24218
24287
|
const dayStrategyMap = /* @__PURE__ */ new Map();
|
|
24219
24288
|
for (const pair of pairs) {
|
|
@@ -24236,12 +24305,14 @@ function analyzeLiveAlignment(backtestTrades, actualTrades, options) {
|
|
|
24236
24305
|
if (agreed) stratData.agreed++;
|
|
24237
24306
|
strategyDayAgreement.set(strategy, stratData);
|
|
24238
24307
|
}
|
|
24239
|
-
const directionByStrategy = Array.from(strategyDayAgreement.entries()).map(
|
|
24240
|
-
strategy,
|
|
24241
|
-
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24308
|
+
const directionByStrategy = Array.from(strategyDayAgreement.entries()).map(
|
|
24309
|
+
([strategy, data]) => ({
|
|
24310
|
+
strategy,
|
|
24311
|
+
rate: data.total > 0 ? data.agreed / data.total : 0,
|
|
24312
|
+
totalDays: data.total,
|
|
24313
|
+
agreementDays: data.agreed
|
|
24314
|
+
})
|
|
24315
|
+
);
|
|
24245
24316
|
const directionAgreement = {
|
|
24246
24317
|
overallRate: totalDays > 0 ? agreementDays / totalDays : 0,
|
|
24247
24318
|
totalDays,
|
|
@@ -24355,6 +24426,8 @@ function analyzeLiveAlignment(backtestTrades, actualTrades, options) {
|
|
|
24355
24426
|
const dataQuality = {
|
|
24356
24427
|
backtestTradeCount: backtestTrades.length,
|
|
24357
24428
|
actualTradeCount: actualTrades.length,
|
|
24429
|
+
outsideOverlapBacktestCount,
|
|
24430
|
+
outsideOverlapActualCount,
|
|
24358
24431
|
matchedTradeCount: pairs.length,
|
|
24359
24432
|
matchRate,
|
|
24360
24433
|
overlapMonths: monthlySeries.length,
|
|
@@ -24395,20 +24468,9 @@ function safePercentChange(current, comparison) {
|
|
|
24395
24468
|
if (comparison === 0) return null;
|
|
24396
24469
|
return (current - comparison) / Math.abs(comparison) * 100;
|
|
24397
24470
|
}
|
|
24398
|
-
var DOLLAR_METRICS = /* @__PURE__ */ new Set([
|
|
24399
|
-
|
|
24400
|
-
|
|
24401
|
-
"avgReturn",
|
|
24402
|
-
"netPl"
|
|
24403
|
-
]);
|
|
24404
|
-
var DOLLAR_TREND_METRICS = /* @__PURE__ */ new Set([
|
|
24405
|
-
"netPl",
|
|
24406
|
-
"tradeCount"
|
|
24407
|
-
]);
|
|
24408
|
-
var HIGHER_IS_WORSE_METRICS = /* @__PURE__ */ new Set([
|
|
24409
|
-
"maxDrawdownPercent",
|
|
24410
|
-
"medianMaxDrawdown"
|
|
24411
|
-
]);
|
|
24471
|
+
var DOLLAR_METRICS = /* @__PURE__ */ new Set(["avgWin", "avgLoss", "avgReturn", "netPl"]);
|
|
24472
|
+
var DOLLAR_TREND_METRICS = /* @__PURE__ */ new Set(["netPl", "tradeCount"]);
|
|
24473
|
+
var HIGHER_IS_WORSE_METRICS = /* @__PURE__ */ new Set(["maxDrawdownPercent", "medianMaxDrawdown"]);
|
|
24412
24474
|
function extractObservations(periodResult, rollingResult, regimeResult, wfResult, liveResult) {
|
|
24413
24475
|
const observations = [];
|
|
24414
24476
|
for (const m of rollingResult.recentVsHistorical.metrics) {
|
|
@@ -24666,14 +24728,18 @@ function synthesizeEdgeDecay(trades, actualTrades, options) {
|
|
|
24666
24728
|
const winRateComp = findMetric(rollingResult.recentVsHistorical.metrics, "winRate");
|
|
24667
24729
|
const pfComp = findMetric(rollingResult.recentVsHistorical.metrics, "profitFactor");
|
|
24668
24730
|
const sharpeComp = findMetric(rollingResult.recentVsHistorical.metrics, "sharpeRatio");
|
|
24669
|
-
const rateObsWithPct = observations.filter(
|
|
24731
|
+
const rateObsWithPct = observations.filter(
|
|
24732
|
+
(o) => o.percentChange !== null && o.metricType === "rate"
|
|
24733
|
+
);
|
|
24670
24734
|
let decayCount = 0;
|
|
24671
24735
|
for (const o of rateObsWithPct) {
|
|
24672
24736
|
const isDecay = HIGHER_IS_WORSE_METRICS.has(o.metric) ? o.percentChange > 0 : o.percentChange < 0;
|
|
24673
24737
|
if (isDecay) decayCount++;
|
|
24674
24738
|
}
|
|
24675
24739
|
const decayFraction = rateObsWithPct.length > 0 ? decayCount / rateObsWithPct.length : 0;
|
|
24676
|
-
const rateObsWithAbsPct = observations.filter(
|
|
24740
|
+
const rateObsWithAbsPct = observations.filter(
|
|
24741
|
+
(o) => o.absPercentChange !== null && o.metricType === "rate"
|
|
24742
|
+
);
|
|
24677
24743
|
const meanAbsPctValue = rateObsWithAbsPct.length > 0 ? rateObsWithAbsPct.reduce((sum2, o) => sum2 + o.absPercentChange, 0) / rateObsWithAbsPct.length : 0;
|
|
24678
24744
|
const meanAbsPctNormalized = Math.min(meanAbsPctValue / 50, 1) * decayFraction;
|
|
24679
24745
|
const mcDivergenceValue = regimeResult?.divergence.compositeScore ?? null;
|
|
@@ -24708,15 +24774,35 @@ function synthesizeEdgeDecay(trades, actualTrades, options) {
|
|
|
24708
24774
|
structuralFlagRatio: BASE_WEIGHTS.structuralFlagRatio / otherSum
|
|
24709
24775
|
};
|
|
24710
24776
|
}
|
|
24711
|
-
const compositeDecayScore = Math.max(
|
|
24712
|
-
|
|
24713
|
-
|
|
24714
|
-
|
|
24777
|
+
const compositeDecayScore = Math.max(
|
|
24778
|
+
0,
|
|
24779
|
+
Math.min(
|
|
24780
|
+
1,
|
|
24781
|
+
meanAbsPctNormalized * weights.meanAbsPercentChange + mcDivergenceNormalized * mcDecayDirection * weights.mcRegimeDivergence + wfEffDeltaNormalized * weights.wfEfficiencyDelta + structuralFlagRatioNormalized * weights.structuralFlagRatio
|
|
24782
|
+
)
|
|
24783
|
+
);
|
|
24715
24784
|
const compositeDecayScoreComponents = {
|
|
24716
|
-
meanAbsPercentChange: {
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24785
|
+
meanAbsPercentChange: {
|
|
24786
|
+
value: meanAbsPctValue,
|
|
24787
|
+
normalized: meanAbsPctNormalized,
|
|
24788
|
+
weight: weights.meanAbsPercentChange,
|
|
24789
|
+
decayFraction
|
|
24790
|
+
},
|
|
24791
|
+
mcRegimeDivergence: {
|
|
24792
|
+
value: mcDivergenceValue,
|
|
24793
|
+
normalized: mcDivergenceNormalized * mcDecayDirection,
|
|
24794
|
+
weight: weights.mcRegimeDivergence
|
|
24795
|
+
},
|
|
24796
|
+
wfEfficiencyDelta: {
|
|
24797
|
+
value: wfEffDeltaValue,
|
|
24798
|
+
normalized: wfEffDeltaNormalized,
|
|
24799
|
+
weight: weights.wfEfficiencyDelta
|
|
24800
|
+
},
|
|
24801
|
+
structuralFlagRatio: {
|
|
24802
|
+
value: structuralFlagRatioValue,
|
|
24803
|
+
normalized: structuralFlagRatioNormalized,
|
|
24804
|
+
weight: weights.structuralFlagRatio
|
|
24805
|
+
}
|
|
24720
24806
|
};
|
|
24721
24807
|
const summary = {
|
|
24722
24808
|
totalTrades,
|
|
@@ -24969,7 +25055,9 @@ var PerformanceCalculator = class {
|
|
|
24969
25055
|
static calculateMonthlyReturns(trades, initialCapital) {
|
|
24970
25056
|
if (trades.length === 0) return {};
|
|
24971
25057
|
if (!initialCapital) {
|
|
24972
|
-
const firstTrade = trades.sort(
|
|
25058
|
+
const firstTrade = trades.sort(
|
|
25059
|
+
(a, b) => new Date(a.dateOpened).getTime() - new Date(b.dateOpened).getTime()
|
|
25060
|
+
)[0];
|
|
24973
25061
|
initialCapital = firstTrade.fundsAtClose - firstTrade.pl;
|
|
24974
25062
|
}
|
|
24975
25063
|
const monthlyPl = this.aggregatePLByPeriod(trades, "monthly");
|
|
@@ -25102,55 +25190,558 @@ var PerformanceCalculator = class {
|
|
|
25102
25190
|
// ../lib/models/report-config.ts
|
|
25103
25191
|
var REPORT_FIELDS = [
|
|
25104
25192
|
// Market conditions
|
|
25105
|
-
{
|
|
25106
|
-
|
|
25107
|
-
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
|
|
25111
|
-
{
|
|
25112
|
-
|
|
25113
|
-
|
|
25114
|
-
|
|
25193
|
+
{
|
|
25194
|
+
field: "openingVix",
|
|
25195
|
+
label: "Opening VIX",
|
|
25196
|
+
category: "market",
|
|
25197
|
+
description: "VIX level when the trade was opened"
|
|
25198
|
+
},
|
|
25199
|
+
{
|
|
25200
|
+
field: "closingVix",
|
|
25201
|
+
label: "Closing VIX",
|
|
25202
|
+
category: "market",
|
|
25203
|
+
description: "VIX level when the trade was closed"
|
|
25204
|
+
},
|
|
25205
|
+
{
|
|
25206
|
+
field: "vixChange",
|
|
25207
|
+
label: "VIX Change",
|
|
25208
|
+
category: "market",
|
|
25209
|
+
description: "How much VIX moved during the trade",
|
|
25210
|
+
formula: "Closing VIX - Opening VIX"
|
|
25211
|
+
},
|
|
25212
|
+
{
|
|
25213
|
+
field: "vixChangePct",
|
|
25214
|
+
label: "VIX Change %",
|
|
25215
|
+
category: "market",
|
|
25216
|
+
unit: "%",
|
|
25217
|
+
description: "Percentage change in VIX during the trade",
|
|
25218
|
+
formula: "((Closing VIX - Opening VIX) / Opening VIX) \xD7 100"
|
|
25219
|
+
},
|
|
25220
|
+
{
|
|
25221
|
+
field: "openingShortLongRatio",
|
|
25222
|
+
label: "Opening S/L Ratio",
|
|
25223
|
+
category: "market",
|
|
25224
|
+
description: "Short/Long ratio at trade entry - measures market sentiment"
|
|
25225
|
+
},
|
|
25226
|
+
{
|
|
25227
|
+
field: "closingShortLongRatio",
|
|
25228
|
+
label: "Closing S/L Ratio",
|
|
25229
|
+
category: "market",
|
|
25230
|
+
description: "Short/Long ratio at trade exit"
|
|
25231
|
+
},
|
|
25232
|
+
{
|
|
25233
|
+
field: "shortLongRatioChange",
|
|
25234
|
+
label: "S/L Ratio Change",
|
|
25235
|
+
category: "market",
|
|
25236
|
+
description: "How S/L ratio changed during the trade",
|
|
25237
|
+
formula: "Closing S/L Ratio / Opening S/L Ratio"
|
|
25238
|
+
},
|
|
25239
|
+
{
|
|
25240
|
+
field: "shortLongRatioChangePct",
|
|
25241
|
+
label: "S/L Ratio Change %",
|
|
25242
|
+
category: "market",
|
|
25243
|
+
unit: "%",
|
|
25244
|
+
description: "Percentage change in S/L ratio",
|
|
25245
|
+
formula: "((Closing - Opening) / Opening) \xD7 100"
|
|
25246
|
+
},
|
|
25247
|
+
{
|
|
25248
|
+
field: "gap",
|
|
25249
|
+
label: "Gap",
|
|
25250
|
+
category: "market",
|
|
25251
|
+
description: "Opening gap in points from previous close"
|
|
25252
|
+
},
|
|
25253
|
+
{
|
|
25254
|
+
field: "movement",
|
|
25255
|
+
label: "Movement",
|
|
25256
|
+
category: "market",
|
|
25257
|
+
description: "Underlying price movement during the trade"
|
|
25258
|
+
},
|
|
25115
25259
|
// Return metrics
|
|
25116
|
-
{
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
|
|
25120
|
-
|
|
25121
|
-
|
|
25260
|
+
{
|
|
25261
|
+
field: "pl",
|
|
25262
|
+
label: "Profit/Loss",
|
|
25263
|
+
category: "returns",
|
|
25264
|
+
unit: "$",
|
|
25265
|
+
description: "Trade profit or loss in dollars (before fees)"
|
|
25266
|
+
},
|
|
25267
|
+
{
|
|
25268
|
+
field: "netPl",
|
|
25269
|
+
label: "Net P/L",
|
|
25270
|
+
category: "returns",
|
|
25271
|
+
unit: "$",
|
|
25272
|
+
description: "Profit/loss after subtracting all fees",
|
|
25273
|
+
formula: "P/L - Total Fees"
|
|
25274
|
+
},
|
|
25275
|
+
{
|
|
25276
|
+
field: "plPct",
|
|
25277
|
+
label: "P/L %",
|
|
25278
|
+
category: "returns",
|
|
25279
|
+
unit: "%",
|
|
25280
|
+
description: "Return as a percentage of premium collected",
|
|
25281
|
+
formula: "(P/L / Premium) \xD7 100"
|
|
25282
|
+
},
|
|
25283
|
+
{
|
|
25284
|
+
field: "netPlPct",
|
|
25285
|
+
label: "Net P/L %",
|
|
25286
|
+
category: "returns",
|
|
25287
|
+
unit: "%",
|
|
25288
|
+
description: "Net return as a percentage of premium",
|
|
25289
|
+
formula: "(Net P/L / Premium) \xD7 100"
|
|
25290
|
+
},
|
|
25291
|
+
{
|
|
25292
|
+
field: "rom",
|
|
25293
|
+
label: "Return on Margin",
|
|
25294
|
+
category: "returns",
|
|
25295
|
+
unit: "%",
|
|
25296
|
+
description: "Return relative to margin required - measures capital efficiency",
|
|
25297
|
+
formula: "(P/L / Margin Required) \xD7 100"
|
|
25298
|
+
},
|
|
25299
|
+
{
|
|
25300
|
+
field: "isWinner",
|
|
25301
|
+
label: "Is Winner",
|
|
25302
|
+
category: "returns",
|
|
25303
|
+
description: "Binary flag: 1 if trade was profitable, 0 if it was a loss"
|
|
25304
|
+
},
|
|
25122
25305
|
// Risk metrics (MFE/MAE)
|
|
25123
|
-
{
|
|
25124
|
-
|
|
25125
|
-
|
|
25126
|
-
|
|
25127
|
-
|
|
25306
|
+
{
|
|
25307
|
+
field: "mfePercent",
|
|
25308
|
+
label: "MFE %",
|
|
25309
|
+
category: "risk",
|
|
25310
|
+
unit: "%",
|
|
25311
|
+
description: "Maximum Favorable Excursion - the best unrealized profit during the trade as % of premium",
|
|
25312
|
+
formula: "(Max Unrealized Profit / Premium) \xD7 100"
|
|
25313
|
+
},
|
|
25314
|
+
{
|
|
25315
|
+
field: "maePercent",
|
|
25316
|
+
label: "MAE %",
|
|
25317
|
+
category: "risk",
|
|
25318
|
+
unit: "%",
|
|
25319
|
+
description: "Maximum Adverse Excursion - the worst unrealized loss during the trade as % of premium",
|
|
25320
|
+
formula: "(Max Unrealized Loss / Premium) \xD7 100"
|
|
25321
|
+
},
|
|
25322
|
+
{
|
|
25323
|
+
field: "profitCapturePercent",
|
|
25324
|
+
label: "Profit Capture %",
|
|
25325
|
+
category: "risk",
|
|
25326
|
+
unit: "%",
|
|
25327
|
+
description: "How much of the peak profit was captured at exit",
|
|
25328
|
+
formula: "(P/L / MFE) \xD7 100"
|
|
25329
|
+
},
|
|
25330
|
+
{
|
|
25331
|
+
field: "excursionRatio",
|
|
25332
|
+
label: "Excursion Ratio",
|
|
25333
|
+
category: "risk",
|
|
25334
|
+
description: "Reward/risk ratio - how much upside vs downside the trade experienced",
|
|
25335
|
+
formula: "MFE / MAE"
|
|
25336
|
+
},
|
|
25337
|
+
{
|
|
25338
|
+
field: "rMultiple",
|
|
25339
|
+
label: "R-Multiple",
|
|
25340
|
+
category: "risk",
|
|
25341
|
+
description: 'Risk-adjusted return - how many "R" (risk units) were won or lost',
|
|
25342
|
+
formula: "P/L / MAE"
|
|
25343
|
+
},
|
|
25128
25344
|
// Trade details
|
|
25129
|
-
{
|
|
25130
|
-
|
|
25131
|
-
|
|
25132
|
-
|
|
25133
|
-
|
|
25134
|
-
|
|
25135
|
-
|
|
25136
|
-
{
|
|
25137
|
-
|
|
25138
|
-
|
|
25345
|
+
{
|
|
25346
|
+
field: "premium",
|
|
25347
|
+
label: "Premium",
|
|
25348
|
+
category: "trade",
|
|
25349
|
+
unit: "$",
|
|
25350
|
+
description: "Premium collected per contract when opening the trade"
|
|
25351
|
+
},
|
|
25352
|
+
{
|
|
25353
|
+
field: "marginReq",
|
|
25354
|
+
label: "Margin Required",
|
|
25355
|
+
category: "trade",
|
|
25356
|
+
unit: "$",
|
|
25357
|
+
description: "Margin/buying power required to hold the position"
|
|
25358
|
+
},
|
|
25359
|
+
{
|
|
25360
|
+
field: "openingPrice",
|
|
25361
|
+
label: "Opening Price",
|
|
25362
|
+
category: "trade",
|
|
25363
|
+
unit: "$",
|
|
25364
|
+
description: "Price of the position when opened"
|
|
25365
|
+
},
|
|
25366
|
+
{
|
|
25367
|
+
field: "closingPrice",
|
|
25368
|
+
label: "Closing Price",
|
|
25369
|
+
category: "trade",
|
|
25370
|
+
unit: "$",
|
|
25371
|
+
description: "Price of the position when closed"
|
|
25372
|
+
},
|
|
25373
|
+
{
|
|
25374
|
+
field: "numContracts",
|
|
25375
|
+
label: "Contracts",
|
|
25376
|
+
category: "trade",
|
|
25377
|
+
description: "Number of contracts traded"
|
|
25378
|
+
},
|
|
25379
|
+
{
|
|
25380
|
+
field: "totalFees",
|
|
25381
|
+
label: "Total Fees",
|
|
25382
|
+
category: "trade",
|
|
25383
|
+
unit: "$",
|
|
25384
|
+
description: "All commissions and fees paid",
|
|
25385
|
+
formula: "Opening Fees + Closing Fees"
|
|
25386
|
+
},
|
|
25387
|
+
{
|
|
25388
|
+
field: "openingCommissionsFees",
|
|
25389
|
+
label: "Opening Fees",
|
|
25390
|
+
category: "trade",
|
|
25391
|
+
unit: "$",
|
|
25392
|
+
description: "Commissions and fees paid when opening"
|
|
25393
|
+
},
|
|
25394
|
+
{
|
|
25395
|
+
field: "closingCommissionsFees",
|
|
25396
|
+
label: "Closing Fees",
|
|
25397
|
+
category: "trade",
|
|
25398
|
+
unit: "$",
|
|
25399
|
+
description: "Commissions and fees paid when closing"
|
|
25400
|
+
},
|
|
25401
|
+
{
|
|
25402
|
+
field: "maxProfit",
|
|
25403
|
+
label: "Max Profit",
|
|
25404
|
+
category: "trade",
|
|
25405
|
+
unit: "%",
|
|
25406
|
+
description: "Maximum unrealized profit as % of premium during the trade"
|
|
25407
|
+
},
|
|
25408
|
+
{
|
|
25409
|
+
field: "maxLoss",
|
|
25410
|
+
label: "Max Loss",
|
|
25411
|
+
category: "trade",
|
|
25412
|
+
unit: "%",
|
|
25413
|
+
description: "Maximum unrealized loss as % of premium during the trade"
|
|
25414
|
+
},
|
|
25139
25415
|
// Timing
|
|
25140
|
-
{
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
{
|
|
25147
|
-
|
|
25148
|
-
|
|
25416
|
+
{
|
|
25417
|
+
field: "tradeNumber",
|
|
25418
|
+
label: "Trade #",
|
|
25419
|
+
category: "timing",
|
|
25420
|
+
description: "Sequential trade number (1 = first trade)"
|
|
25421
|
+
},
|
|
25422
|
+
{
|
|
25423
|
+
field: "dateOpenedTimestamp",
|
|
25424
|
+
label: "Date Opened",
|
|
25425
|
+
category: "timing",
|
|
25426
|
+
description: "When the trade was opened (useful for time-series charts)"
|
|
25427
|
+
},
|
|
25428
|
+
{
|
|
25429
|
+
field: "durationHours",
|
|
25430
|
+
label: "Duration (hrs)",
|
|
25431
|
+
category: "timing",
|
|
25432
|
+
unit: "hrs",
|
|
25433
|
+
description: "How long the position was held",
|
|
25434
|
+
formula: "Close Time - Open Time"
|
|
25435
|
+
},
|
|
25436
|
+
{
|
|
25437
|
+
field: "dayOfWeek",
|
|
25438
|
+
label: "Day of Week",
|
|
25439
|
+
category: "timing",
|
|
25440
|
+
description: "Day of week when opened: 0=Sunday through 6=Saturday"
|
|
25441
|
+
},
|
|
25442
|
+
{
|
|
25443
|
+
field: "hourOfDay",
|
|
25444
|
+
label: "Hour of Day",
|
|
25445
|
+
category: "timing",
|
|
25446
|
+
description: "Hour of day when opened (0-23 in Eastern Time)"
|
|
25447
|
+
},
|
|
25448
|
+
{
|
|
25449
|
+
field: "timeOfDayMinutes",
|
|
25450
|
+
label: "Time of Day",
|
|
25451
|
+
category: "timing",
|
|
25452
|
+
description: "Exact time when opened as minutes since midnight (e.g., 11:45 = 705). Useful for scatter plots to analyze floating-time or multiple-entry trades"
|
|
25453
|
+
},
|
|
25454
|
+
{
|
|
25455
|
+
field: "dayOfMonth",
|
|
25456
|
+
label: "Day of Month",
|
|
25457
|
+
category: "timing",
|
|
25458
|
+
description: "Day of month when opened (1-31)"
|
|
25459
|
+
},
|
|
25460
|
+
{
|
|
25461
|
+
field: "monthOfYear",
|
|
25462
|
+
label: "Month of Year",
|
|
25463
|
+
category: "timing",
|
|
25464
|
+
description: "Month when opened (1=January through 12=December)"
|
|
25465
|
+
},
|
|
25466
|
+
{
|
|
25467
|
+
field: "weekOfYear",
|
|
25468
|
+
label: "Week of Year",
|
|
25469
|
+
category: "timing",
|
|
25470
|
+
description: "ISO week number when opened (1-52)"
|
|
25471
|
+
},
|
|
25149
25472
|
// Portfolio context
|
|
25150
|
-
{
|
|
25151
|
-
|
|
25473
|
+
{
|
|
25474
|
+
field: "exposureOnOpen",
|
|
25475
|
+
label: "Portfolio Exposure %",
|
|
25476
|
+
category: "risk",
|
|
25477
|
+
unit: "%",
|
|
25478
|
+
description: "Portfolio margin exposure as % of equity at the exact moment this trade opened - shows how much risk was deployed when entering"
|
|
25479
|
+
},
|
|
25480
|
+
{
|
|
25481
|
+
field: "exposureOnOpenDollars",
|
|
25482
|
+
label: "Portfolio Exposure $",
|
|
25483
|
+
category: "risk",
|
|
25484
|
+
unit: "$",
|
|
25485
|
+
description: "Portfolio margin exposure in dollars at the exact moment this trade opened - shows total margin at risk when entering"
|
|
25486
|
+
}
|
|
25152
25487
|
];
|
|
25153
25488
|
|
|
25489
|
+
// ../lib/calculations/paired-block-bootstrap.ts
|
|
25490
|
+
function mulberry32(seed) {
|
|
25491
|
+
let state = seed >>> 0;
|
|
25492
|
+
return function() {
|
|
25493
|
+
state = state + 1831565813 >>> 0;
|
|
25494
|
+
let t = state;
|
|
25495
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
25496
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
25497
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
25498
|
+
};
|
|
25499
|
+
}
|
|
25500
|
+
function deriveSeed(seed, blockDays) {
|
|
25501
|
+
return seed + Math.imul(blockDays, 2654435761) >>> 0;
|
|
25502
|
+
}
|
|
25503
|
+
function percentileOf(sorted, p) {
|
|
25504
|
+
const n = sorted.length;
|
|
25505
|
+
if (n === 1) return sorted[0];
|
|
25506
|
+
const rank = p * (n - 1);
|
|
25507
|
+
const lo = Math.floor(rank);
|
|
25508
|
+
const hi = Math.ceil(rank);
|
|
25509
|
+
if (lo === hi) return sorted[lo];
|
|
25510
|
+
const weight = rank - lo;
|
|
25511
|
+
return sorted[lo] * (1 - weight) + sorted[hi] * weight;
|
|
25512
|
+
}
|
|
25513
|
+
function observedValueMap(arm) {
|
|
25514
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
25515
|
+
for (let i = 0; i < arm.index.length; i++) {
|
|
25516
|
+
if (arm.observedMask[i]) {
|
|
25517
|
+
map2.set(arm.index[i], arm.values[i]);
|
|
25518
|
+
}
|
|
25519
|
+
}
|
|
25520
|
+
return map2;
|
|
25521
|
+
}
|
|
25522
|
+
function isConstantArm(arm) {
|
|
25523
|
+
return arm !== void 0 && "constant" in arm;
|
|
25524
|
+
}
|
|
25525
|
+
function unionDays(a, b) {
|
|
25526
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25527
|
+
for (const d of a) seen.add(d);
|
|
25528
|
+
for (const d of b) seen.add(d);
|
|
25529
|
+
return Array.from(seen).sort();
|
|
25530
|
+
}
|
|
25531
|
+
function buildOverlap(ambient, isObserved, deltaOf) {
|
|
25532
|
+
const days = [];
|
|
25533
|
+
const delta = [];
|
|
25534
|
+
const runs = [];
|
|
25535
|
+
let runStart = -1;
|
|
25536
|
+
for (const day of ambient) {
|
|
25537
|
+
if (isObserved(day)) {
|
|
25538
|
+
if (runStart === -1) runStart = days.length;
|
|
25539
|
+
days.push(day);
|
|
25540
|
+
delta.push(deltaOf(day));
|
|
25541
|
+
} else if (runStart !== -1) {
|
|
25542
|
+
runs.push({ start: runStart, length: days.length - runStart });
|
|
25543
|
+
runStart = -1;
|
|
25544
|
+
}
|
|
25545
|
+
}
|
|
25546
|
+
if (runStart !== -1) {
|
|
25547
|
+
runs.push({ start: runStart, length: days.length - runStart });
|
|
25548
|
+
}
|
|
25549
|
+
return { days, delta, runs };
|
|
25550
|
+
}
|
|
25551
|
+
function buildComparisonOverlap(armA, armB) {
|
|
25552
|
+
const mapA = observedValueMap(armA);
|
|
25553
|
+
if (armB === void 0 || isConstantArm(armB)) {
|
|
25554
|
+
const c = armB === void 0 ? 0 : armB.constant;
|
|
25555
|
+
return buildOverlap(
|
|
25556
|
+
armA.index,
|
|
25557
|
+
(day) => mapA.has(day),
|
|
25558
|
+
(day) => mapA.get(day) - c
|
|
25559
|
+
);
|
|
25560
|
+
}
|
|
25561
|
+
const mapB = observedValueMap(armB);
|
|
25562
|
+
const ambient = unionDays(armA.index, armB.index);
|
|
25563
|
+
return buildOverlap(
|
|
25564
|
+
ambient,
|
|
25565
|
+
(day) => mapA.has(day) && mapB.has(day),
|
|
25566
|
+
(day) => mapA.get(day) - mapB.get(day)
|
|
25567
|
+
);
|
|
25568
|
+
}
|
|
25569
|
+
function enumerateBlockStarts(runs, blockDays) {
|
|
25570
|
+
const starts = [];
|
|
25571
|
+
for (const run of runs) {
|
|
25572
|
+
const lastStart = run.length - blockDays;
|
|
25573
|
+
for (let offset = 0; offset <= lastStart; offset++) {
|
|
25574
|
+
starts.push(run.start + offset);
|
|
25575
|
+
}
|
|
25576
|
+
}
|
|
25577
|
+
return starts;
|
|
25578
|
+
}
|
|
25579
|
+
function drawResampleIndices(rng, blockStarts, blockDays, n) {
|
|
25580
|
+
const indices = [];
|
|
25581
|
+
while (indices.length < n) {
|
|
25582
|
+
const start = blockStarts[Math.floor(rng() * blockStarts.length)];
|
|
25583
|
+
for (let k = 0; k < blockDays && indices.length < n; k++) {
|
|
25584
|
+
indices.push(start + k);
|
|
25585
|
+
}
|
|
25586
|
+
}
|
|
25587
|
+
return indices;
|
|
25588
|
+
}
|
|
25589
|
+
function computeCore(params) {
|
|
25590
|
+
const {
|
|
25591
|
+
overlapDays,
|
|
25592
|
+
runs,
|
|
25593
|
+
deltaSeries,
|
|
25594
|
+
memberDeltas,
|
|
25595
|
+
extremum,
|
|
25596
|
+
blockDays,
|
|
25597
|
+
resamples,
|
|
25598
|
+
seed,
|
|
25599
|
+
ciLevel,
|
|
25600
|
+
statistic,
|
|
25601
|
+
effectiveNFloorBlocks
|
|
25602
|
+
} = params;
|
|
25603
|
+
const n = overlapDays.length;
|
|
25604
|
+
const effectiveN = n / blockDays;
|
|
25605
|
+
const takeExtremum = (values) => extremum === "min" ? Math.min(...values) : Math.max(...values);
|
|
25606
|
+
let point;
|
|
25607
|
+
if (n === 0) {
|
|
25608
|
+
point = null;
|
|
25609
|
+
} else if (memberDeltas) {
|
|
25610
|
+
point = takeExtremum(memberDeltas.map((d) => statistic(d)));
|
|
25611
|
+
} else {
|
|
25612
|
+
point = statistic(deltaSeries);
|
|
25613
|
+
}
|
|
25614
|
+
const blockStarts = enumerateBlockStarts(runs, blockDays);
|
|
25615
|
+
if (effectiveNFloorBlocks !== void 0 && effectiveN < effectiveNFloorBlocks) {
|
|
25616
|
+
return { point, low: null, high: null, effectiveN, status: "notComparable" };
|
|
25617
|
+
}
|
|
25618
|
+
if (blockStarts.length < 2) {
|
|
25619
|
+
return { point, low: null, high: null, effectiveN, status: "underpowered" };
|
|
25620
|
+
}
|
|
25621
|
+
const rng = mulberry32(deriveSeed(seed, blockDays));
|
|
25622
|
+
const distribution = new Array(resamples);
|
|
25623
|
+
for (let r = 0; r < resamples; r++) {
|
|
25624
|
+
const idx = drawResampleIndices(rng, blockStarts, blockDays, n);
|
|
25625
|
+
if (memberDeltas) {
|
|
25626
|
+
const stats = memberDeltas.map((d) => statistic(idx.map((i) => d[i])));
|
|
25627
|
+
distribution[r] = takeExtremum(stats);
|
|
25628
|
+
} else {
|
|
25629
|
+
distribution[r] = statistic(idx.map((i) => deltaSeries[i]));
|
|
25630
|
+
}
|
|
25631
|
+
}
|
|
25632
|
+
distribution.sort((a, b) => a - b);
|
|
25633
|
+
const alpha = 1 - ciLevel;
|
|
25634
|
+
const low = percentileOf(distribution, alpha / 2);
|
|
25635
|
+
const high = percentileOf(distribution, 1 - alpha / 2);
|
|
25636
|
+
return { point, low, high, effectiveN, status: "resolved" };
|
|
25637
|
+
}
|
|
25638
|
+
function buildSelectionOverlap(members) {
|
|
25639
|
+
const memberMaps = members.map((m) => {
|
|
25640
|
+
const overlap2 = buildComparisonOverlap(m.armA, m.armB);
|
|
25641
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
25642
|
+
for (let i = 0; i < overlap2.days.length; i++) {
|
|
25643
|
+
map2.set(overlap2.days[i], overlap2.delta[i]);
|
|
25644
|
+
}
|
|
25645
|
+
return map2;
|
|
25646
|
+
});
|
|
25647
|
+
let ambient = [];
|
|
25648
|
+
for (const m of members) {
|
|
25649
|
+
const armBDays = isConstantArm(m.armB) || m.armB === void 0 ? [] : m.armB.index;
|
|
25650
|
+
ambient = unionDays(ambient, unionDays(m.armA.index, armBDays));
|
|
25651
|
+
}
|
|
25652
|
+
const overlap = buildOverlap(
|
|
25653
|
+
ambient,
|
|
25654
|
+
(day) => memberMaps.every((map2) => map2.has(day)),
|
|
25655
|
+
() => 0
|
|
25656
|
+
);
|
|
25657
|
+
const memberDeltas = memberMaps.map((map2) => overlap.days.map((day) => map2.get(day)));
|
|
25658
|
+
return { days: overlap.days, runs: overlap.runs, memberDeltas };
|
|
25659
|
+
}
|
|
25660
|
+
function holdingPeriodBlockDays(holdingPeriodsTradingDays, percentile2 = 0.95) {
|
|
25661
|
+
if (holdingPeriodsTradingDays.length === 0) return 1;
|
|
25662
|
+
const sorted = [...holdingPeriodsTradingDays].sort((a, b) => a - b);
|
|
25663
|
+
return Math.max(1, Math.round(percentileOf(sorted, percentile2)));
|
|
25664
|
+
}
|
|
25665
|
+
function pairedBlockBootstrap(input) {
|
|
25666
|
+
const blockDays = Math.max(1, Math.round(input.holdingRule.blockDays));
|
|
25667
|
+
const { ciLevel, resamples, seed, statistic, effectiveNFloorBlocks } = input;
|
|
25668
|
+
let overlapDays;
|
|
25669
|
+
let runs;
|
|
25670
|
+
let deltaSeries;
|
|
25671
|
+
let memberDeltas;
|
|
25672
|
+
let extremum;
|
|
25673
|
+
let selection = null;
|
|
25674
|
+
if (input.selectionSet) {
|
|
25675
|
+
const sel = buildSelectionOverlap(input.selectionSet.members);
|
|
25676
|
+
overlapDays = sel.days;
|
|
25677
|
+
runs = sel.runs;
|
|
25678
|
+
memberDeltas = sel.memberDeltas;
|
|
25679
|
+
extremum = input.selectionSet.extremum;
|
|
25680
|
+
deltaSeries = [];
|
|
25681
|
+
const memberPoints = sel.memberDeltas.map((d) => statistic(d));
|
|
25682
|
+
const kConsidered = memberPoints.length;
|
|
25683
|
+
const centroid = kConsidered > 0 ? memberPoints.reduce((s, v) => s + v, 0) / kConsidered : 0;
|
|
25684
|
+
let maxGap = 0;
|
|
25685
|
+
if (kConsidered >= 2) {
|
|
25686
|
+
const ordered = [...memberPoints].sort((a, b) => a - b);
|
|
25687
|
+
maxGap = extremum === "min" ? ordered[1] - ordered[0] : ordered[kConsidered - 1] - ordered[kConsidered - 2];
|
|
25688
|
+
}
|
|
25689
|
+
selection = { centroid, maxGap, kConsidered };
|
|
25690
|
+
} else {
|
|
25691
|
+
const overlap = buildComparisonOverlap(input.armA, input.armB);
|
|
25692
|
+
overlapDays = overlap.days;
|
|
25693
|
+
runs = overlap.runs;
|
|
25694
|
+
deltaSeries = overlap.delta;
|
|
25695
|
+
}
|
|
25696
|
+
const overlapWindow = overlapDays.length > 0 ? { start: overlapDays[0], end: overlapDays[overlapDays.length - 1] } : null;
|
|
25697
|
+
const primary = computeCore({
|
|
25698
|
+
overlapDays,
|
|
25699
|
+
runs,
|
|
25700
|
+
deltaSeries,
|
|
25701
|
+
memberDeltas,
|
|
25702
|
+
extremum,
|
|
25703
|
+
blockDays,
|
|
25704
|
+
resamples,
|
|
25705
|
+
seed,
|
|
25706
|
+
ciLevel,
|
|
25707
|
+
statistic,
|
|
25708
|
+
effectiveNFloorBlocks
|
|
25709
|
+
});
|
|
25710
|
+
const sensitivity = [];
|
|
25711
|
+
for (const multiplier of input.holdingRule.sensitivity ?? []) {
|
|
25712
|
+
const sensBlockDays = Math.max(1, Math.round(multiplier * blockDays));
|
|
25713
|
+
const sens = computeCore({
|
|
25714
|
+
overlapDays,
|
|
25715
|
+
runs,
|
|
25716
|
+
deltaSeries,
|
|
25717
|
+
memberDeltas,
|
|
25718
|
+
extremum,
|
|
25719
|
+
blockDays: sensBlockDays,
|
|
25720
|
+
resamples,
|
|
25721
|
+
seed,
|
|
25722
|
+
ciLevel,
|
|
25723
|
+
statistic,
|
|
25724
|
+
effectiveNFloorBlocks
|
|
25725
|
+
});
|
|
25726
|
+
sensitivity.push({
|
|
25727
|
+
blockDays: sensBlockDays,
|
|
25728
|
+
ci: { low: sens.low, high: sens.high },
|
|
25729
|
+
status: sens.status
|
|
25730
|
+
});
|
|
25731
|
+
}
|
|
25732
|
+
return {
|
|
25733
|
+
point: primary.point,
|
|
25734
|
+
ci: { level: ciLevel, low: primary.low, high: primary.high, method: "paired-day-block" },
|
|
25735
|
+
effectiveN: primary.effectiveN,
|
|
25736
|
+
overlapWindow,
|
|
25737
|
+
blockDays,
|
|
25738
|
+
sensitivity,
|
|
25739
|
+
selection,
|
|
25740
|
+
status: primary.status,
|
|
25741
|
+
seed
|
|
25742
|
+
};
|
|
25743
|
+
}
|
|
25744
|
+
|
|
25154
25745
|
// ../lib/calculations/index.ts
|
|
25155
25746
|
function generateDataHash(trades, dailyLogs) {
|
|
25156
25747
|
const data = {
|
|
@@ -25179,9 +25770,15 @@ var CalculationOrchestrator = class {
|
|
|
25179
25770
|
};
|
|
25180
25771
|
}
|
|
25181
25772
|
const calculator = new PortfolioStatsCalculator(config4);
|
|
25182
|
-
const portfolioStats = calculator.calculatePortfolioStats(
|
|
25773
|
+
const portfolioStats = calculator.calculatePortfolioStats(
|
|
25774
|
+
trades,
|
|
25775
|
+
dailyLogs
|
|
25776
|
+
);
|
|
25183
25777
|
const strategyStats = calculator.calculateStrategyStats(trades);
|
|
25184
|
-
const performanceMetrics = PerformanceCalculator.calculatePerformanceMetrics(
|
|
25778
|
+
const performanceMetrics = PerformanceCalculator.calculatePerformanceMetrics(
|
|
25779
|
+
trades,
|
|
25780
|
+
dailyLogs
|
|
25781
|
+
);
|
|
25185
25782
|
this.cache.set(blockId, {
|
|
25186
25783
|
portfolioStats,
|
|
25187
25784
|
strategyStats,
|
|
@@ -25221,9 +25818,9 @@ var calculationOrchestrator = new CalculationOrchestrator();
|
|
|
25221
25818
|
var REPORTING_TRADE_COLUMN_ALIASES = {
|
|
25222
25819
|
"Initial Premium ($)": "Initial Premium",
|
|
25223
25820
|
"Initial Credit": "Initial Premium",
|
|
25224
|
-
|
|
25821
|
+
Contracts: "No. of Contracts",
|
|
25225
25822
|
"Contracts Traded": "No. of Contracts",
|
|
25226
|
-
|
|
25823
|
+
PL: "P/L"
|
|
25227
25824
|
};
|
|
25228
25825
|
|
|
25229
25826
|
// ../lib/processing/tat-adapter.ts
|
|
@@ -25393,6 +25990,8 @@ export {
|
|
|
25393
25990
|
matchTrades,
|
|
25394
25991
|
analyzeLiveAlignment,
|
|
25395
25992
|
synthesizeEdgeDecay,
|
|
25993
|
+
holdingPeriodBlockDays,
|
|
25994
|
+
pairedBlockBootstrap,
|
|
25396
25995
|
REPORTING_TRADE_COLUMN_ALIASES,
|
|
25397
25996
|
isTatFormat,
|
|
25398
25997
|
convertTatRowToReportingTrade,
|
|
@@ -25409,4 +26008,4 @@ decimal.js/decimal.mjs:
|
|
|
25409
26008
|
* MIT Licence
|
|
25410
26009
|
*)
|
|
25411
26010
|
*/
|
|
25412
|
-
//# sourceMappingURL=chunk-
|
|
26011
|
+
//# sourceMappingURL=chunk-RJDJHZ5Y.js.map
|