@tradejs/cli 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +250 -38
- package/dist/lib/aiTrainCharts.js +49 -2
- package/dist/lib/aiTrainMetrics.js +49 -2
- package/dist/lib/aiTrainResearch.js +58 -3
- package/dist/lib/derivativesContextBackfill.js +143 -32
- package/dist/lib/marketContextPrepare.js +141 -32
- package/dist/scripts/aiPocketSearch.js +49 -2
- package/dist/scripts/aiTrain.js +76 -3
- package/dist/scripts/backtest.js +141 -32
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +30 -0
- package/dist/scripts/replay.js +141 -32
- package/dist/scripts/replayRunner.js +141 -32
- package/dist/scripts/runtimeParity.js +141 -32
- package/dist/scripts/signals.js +141 -32
- package/dist/scripts/signalsDaemon.js +141 -32
- package/package.json +9 -9
|
@@ -2552,6 +2552,38 @@ var resolveDerivativesContextMissingFetchFromMs = (params) => {
|
|
|
2552
2552
|
return null;
|
|
2553
2553
|
};
|
|
2554
2554
|
var resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
|
|
2555
|
+
var resolveDerivativesContextRequiredFetchFromMs = (params) => {
|
|
2556
|
+
const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
2557
|
+
ranges: params.dataCoverageRanges,
|
|
2558
|
+
fromMs: params.fromMs,
|
|
2559
|
+
toMs: params.toMs,
|
|
2560
|
+
intervalMs: params.intervalMs
|
|
2561
|
+
});
|
|
2562
|
+
const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
|
|
2563
|
+
edges: params.edges,
|
|
2564
|
+
fromMs: params.fromMs,
|
|
2565
|
+
toMs: params.toMs,
|
|
2566
|
+
intervalMs: params.intervalMs,
|
|
2567
|
+
refreshClosedTail: params.mode === "signals"
|
|
2568
|
+
});
|
|
2569
|
+
const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
|
|
2570
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
2571
|
+
interval: params.interval,
|
|
2572
|
+
fromMs: params.fromMs,
|
|
2573
|
+
toMs: params.toMs,
|
|
2574
|
+
nowMs: params.nowMs
|
|
2575
|
+
});
|
|
2576
|
+
const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
2577
|
+
ranges: params.liquidationCoverageRanges,
|
|
2578
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
2579
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
2580
|
+
intervalMs: params.intervalMs
|
|
2581
|
+
}) : null;
|
|
2582
|
+
const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
|
|
2583
|
+
(value) => value != null
|
|
2584
|
+
);
|
|
2585
|
+
return fetchStarts.length ? Math.min(...fetchStarts) : null;
|
|
2586
|
+
};
|
|
2555
2587
|
var countBackfillWindows = (params) => {
|
|
2556
2588
|
let count = 0;
|
|
2557
2589
|
let cursor = params.fromMs;
|
|
@@ -2887,7 +2919,8 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
2887
2919
|
mode,
|
|
2888
2920
|
startMs,
|
|
2889
2921
|
endMs,
|
|
2890
|
-
preloadStartMs: params.preloadStartMs
|
|
2922
|
+
preloadStartMs: params.preloadStartMs,
|
|
2923
|
+
nowMs: Date.now()
|
|
2891
2924
|
});
|
|
2892
2925
|
if (safeEndMs <= fromMs) {
|
|
2893
2926
|
return skippedBackfillResult2();
|
|
@@ -2921,15 +2954,33 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
2921
2954
|
);
|
|
2922
2955
|
const coverageKeysByInterval = /* @__PURE__ */ new Map();
|
|
2923
2956
|
const coverageRangesByInterval = /* @__PURE__ */ new Map();
|
|
2957
|
+
const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
|
|
2958
|
+
const coverageNowMs = Date.now();
|
|
2924
2959
|
await Promise.all(
|
|
2925
2960
|
intervalWindows.map(async (window2) => {
|
|
2926
|
-
const
|
|
2927
|
-
source: "coinalyze",
|
|
2928
|
-
symbols,
|
|
2961
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
2929
2962
|
interval: window2.interval,
|
|
2930
2963
|
fromMs: window2.fromMs,
|
|
2931
|
-
toMs: window2.toMs
|
|
2964
|
+
toMs: window2.toMs,
|
|
2965
|
+
nowMs: coverageNowMs
|
|
2932
2966
|
});
|
|
2967
|
+
const [coverageRows, liquidationCoverageRows] = await Promise.all([
|
|
2968
|
+
(0, import_timescale2.getDerivativesBackfillCoverage)({
|
|
2969
|
+
source: "coinalyze",
|
|
2970
|
+
symbols,
|
|
2971
|
+
interval: window2.interval,
|
|
2972
|
+
fromMs: window2.fromMs,
|
|
2973
|
+
toMs: window2.toMs
|
|
2974
|
+
}),
|
|
2975
|
+
confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
|
|
2976
|
+
source: "coinalyze",
|
|
2977
|
+
metric: "liquidation",
|
|
2978
|
+
symbols,
|
|
2979
|
+
interval: window2.interval,
|
|
2980
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
2981
|
+
toMs: confirmedLiquidationWindow.toMs
|
|
2982
|
+
}) : Promise.resolve([])
|
|
2983
|
+
]);
|
|
2933
2984
|
coverageKeysByInterval.set(
|
|
2934
2985
|
window2.interval,
|
|
2935
2986
|
new Set(
|
|
@@ -2951,6 +3002,17 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
2951
3002
|
rangesBySymbol.set(symbol, ranges);
|
|
2952
3003
|
}
|
|
2953
3004
|
coverageRangesByInterval.set(window2.interval, rangesBySymbol);
|
|
3005
|
+
const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
|
|
3006
|
+
for (const row of liquidationCoverageRows) {
|
|
3007
|
+
const symbol = row.symbol.toUpperCase();
|
|
3008
|
+
const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
|
|
3009
|
+
ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
|
|
3010
|
+
liquidationRangesBySymbol.set(symbol, ranges);
|
|
3011
|
+
}
|
|
3012
|
+
liquidationCoverageRangesByInterval.set(
|
|
3013
|
+
window2.interval,
|
|
3014
|
+
liquidationRangesBySymbol
|
|
3015
|
+
);
|
|
2954
3016
|
})
|
|
2955
3017
|
);
|
|
2956
3018
|
const cachedWindows = intervalWindows.reduce(
|
|
@@ -2975,7 +3037,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
2975
3037
|
(symbol) => backfillWindows.every((backfillWindow) => {
|
|
2976
3038
|
const normalizedSymbol = symbol.toUpperCase();
|
|
2977
3039
|
const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
|
|
2978
|
-
|
|
3040
|
+
const dataCovered = hasDerivativesWindowCoverage({
|
|
2979
3041
|
edges: edgesBySymbol?.get(normalizedSymbol),
|
|
2980
3042
|
fromMs: backfillWindow.fromMs,
|
|
2981
3043
|
toMs: backfillWindow.toMs
|
|
@@ -2992,6 +3054,19 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
2992
3054
|
toMs: backfillWindow.toMs
|
|
2993
3055
|
})
|
|
2994
3056
|
);
|
|
3057
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
3058
|
+
interval: window2.interval,
|
|
3059
|
+
fromMs: backfillWindow.fromMs,
|
|
3060
|
+
toMs: backfillWindow.toMs,
|
|
3061
|
+
nowMs: coverageNowMs
|
|
3062
|
+
});
|
|
3063
|
+
const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
3064
|
+
ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
|
|
3065
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
3066
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
3067
|
+
intervalMs: window2.intervalMs
|
|
3068
|
+
}) == null;
|
|
3069
|
+
return dataCovered && liquidationCovered;
|
|
2995
3070
|
})
|
|
2996
3071
|
);
|
|
2997
3072
|
});
|
|
@@ -3076,6 +3151,11 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3076
3151
|
coverageKeysByInterval.set(interval, coverageKeys);
|
|
3077
3152
|
const coverageRangesBySymbol = coverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
|
|
3078
3153
|
coverageRangesByInterval.set(interval, coverageRangesBySymbol);
|
|
3154
|
+
const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval) ?? /* @__PURE__ */ new Map();
|
|
3155
|
+
liquidationCoverageRangesByInterval.set(
|
|
3156
|
+
interval,
|
|
3157
|
+
liquidationCoverageRangesBySymbol
|
|
3158
|
+
);
|
|
3079
3159
|
for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
|
|
3080
3160
|
const batch = symbolBatches[batchIdx];
|
|
3081
3161
|
let cursor = window2.fromMs;
|
|
@@ -3088,33 +3168,25 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3088
3168
|
fromMs: cursor,
|
|
3089
3169
|
toMs
|
|
3090
3170
|
});
|
|
3091
|
-
if (coverageKeys.has(key)) {
|
|
3092
|
-
return null;
|
|
3093
|
-
}
|
|
3094
3171
|
const normalizedSymbol = item.symbol.toUpperCase();
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
refreshClosedTail: mode === "signals"
|
|
3111
|
-
});
|
|
3112
|
-
if (edgesFromMs == null) {
|
|
3113
|
-
return null;
|
|
3114
|
-
}
|
|
3172
|
+
const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
|
|
3173
|
+
{
|
|
3174
|
+
mode,
|
|
3175
|
+
interval,
|
|
3176
|
+
intervalMs,
|
|
3177
|
+
fromMs: cursor,
|
|
3178
|
+
toMs,
|
|
3179
|
+
nowMs: coverageNowMs,
|
|
3180
|
+
dataCoverageKeyExists: coverageKeys.has(key),
|
|
3181
|
+
dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
|
|
3182
|
+
liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
|
|
3183
|
+
edges: edgesBySymbol.get(normalizedSymbol)
|
|
3184
|
+
}
|
|
3185
|
+
);
|
|
3186
|
+
if (requiredFromMs == null) return null;
|
|
3115
3187
|
return {
|
|
3116
3188
|
item,
|
|
3117
|
-
fromMs:
|
|
3189
|
+
fromMs: requiredFromMs
|
|
3118
3190
|
};
|
|
3119
3191
|
}).filter(
|
|
3120
3192
|
(item) => item != null
|
|
@@ -3139,6 +3211,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3139
3211
|
);
|
|
3140
3212
|
let rows = [];
|
|
3141
3213
|
let missingClosedSymbols = [];
|
|
3214
|
+
let liquidationRowsByMarket = /* @__PURE__ */ new Map();
|
|
3142
3215
|
for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
|
|
3143
3216
|
const oiMap = await fetchMetricBatch({
|
|
3144
3217
|
endpoint: oiPath,
|
|
@@ -3158,7 +3231,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3158
3231
|
fromMs: group.fromMs,
|
|
3159
3232
|
toMs
|
|
3160
3233
|
});
|
|
3161
|
-
|
|
3234
|
+
liquidationRowsByMarket = await fetchMetricBatch({
|
|
3162
3235
|
endpoint: liqPath,
|
|
3163
3236
|
metric: "liq",
|
|
3164
3237
|
marketSymbols,
|
|
@@ -3173,7 +3246,7 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3173
3246
|
symbol: item.symbol,
|
|
3174
3247
|
oiRaw: oiMap.get(marketSymbol) ?? [],
|
|
3175
3248
|
fundingRaw: fundingMap.get(marketSymbol) ?? [],
|
|
3176
|
-
liqRaw:
|
|
3249
|
+
liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
|
|
3177
3250
|
});
|
|
3178
3251
|
return (0, import_indicators.coinalyzePointsToRows)(points, interval, "coinalyze");
|
|
3179
3252
|
});
|
|
@@ -3196,6 +3269,42 @@ var backfillDerivativesContext = async (params, enabled, mode) => {
|
|
|
3196
3269
|
await (0, import_timescale2.upsertDerivatives)(rows);
|
|
3197
3270
|
totalRows += rows.length;
|
|
3198
3271
|
}
|
|
3272
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
3273
|
+
interval,
|
|
3274
|
+
fromMs: group.fromMs,
|
|
3275
|
+
toMs,
|
|
3276
|
+
nowMs: coverageNowMs
|
|
3277
|
+
});
|
|
3278
|
+
if (confirmedLiquidationWindow) {
|
|
3279
|
+
const metricCoverageRows = missingBatch.map((item) => {
|
|
3280
|
+
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
3281
|
+
const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
|
|
3282
|
+
const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
|
|
3283
|
+
point.t ?? point.ts ?? point.time ?? point.timestamp
|
|
3284
|
+
);
|
|
3285
|
+
return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
|
|
3286
|
+
}).length;
|
|
3287
|
+
return {
|
|
3288
|
+
source: "coinalyze",
|
|
3289
|
+
metric: "liquidation",
|
|
3290
|
+
symbol: item.symbol,
|
|
3291
|
+
interval,
|
|
3292
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
3293
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
3294
|
+
eventRowsCount
|
|
3295
|
+
};
|
|
3296
|
+
});
|
|
3297
|
+
await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
|
|
3298
|
+
for (const coverageRow of metricCoverageRows) {
|
|
3299
|
+
const symbol = coverageRow.symbol.toUpperCase();
|
|
3300
|
+
const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
|
|
3301
|
+
ranges.push({
|
|
3302
|
+
fromMs: coverageRow.fromMs,
|
|
3303
|
+
toMs: coverageRow.toMs
|
|
3304
|
+
});
|
|
3305
|
+
liquidationCoverageRangesBySymbol.set(symbol, ranges);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3199
3308
|
if (mode === "backtest") {
|
|
3200
3309
|
const rowsCountBySymbol = /* @__PURE__ */ new Map();
|
|
3201
3310
|
for (const row of rows) {
|
|
@@ -1663,6 +1663,7 @@ var divideOrNull = (num, denom) => {
|
|
|
1663
1663
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1664
1664
|
var DAYS_PER_WEEK = 7;
|
|
1665
1665
|
var DAYS_PER_MONTH = 30.4375;
|
|
1666
|
+
var DAYS_PER_YEAR = 365;
|
|
1666
1667
|
var getEvaluationPeriodDays = (evaluations) => {
|
|
1667
1668
|
let minTimestamp = null;
|
|
1668
1669
|
let maxTimestamp = null;
|
|
@@ -1684,6 +1685,40 @@ var getEvaluationPeriodDays = (evaluations) => {
|
|
|
1684
1685
|
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
1685
1686
|
};
|
|
1686
1687
|
var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
1688
|
+
var calculateTradePnlRiskRatios = ({
|
|
1689
|
+
profits,
|
|
1690
|
+
totalProfit,
|
|
1691
|
+
maxDrawdown,
|
|
1692
|
+
periodDays
|
|
1693
|
+
}) => {
|
|
1694
|
+
if (!profits.length || periodDays == null || periodDays <= 0) {
|
|
1695
|
+
return {
|
|
1696
|
+
sharpeRatio: null,
|
|
1697
|
+
sortinoRatio: null,
|
|
1698
|
+
calmarRatio: null
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
const meanProfit = totalProfit / profits.length;
|
|
1702
|
+
const variance = profits.reduce((sum, profit) => {
|
|
1703
|
+
const diff = profit - meanProfit;
|
|
1704
|
+
return sum + diff * diff;
|
|
1705
|
+
}, 0) / profits.length;
|
|
1706
|
+
const stdDev = Math.sqrt(variance);
|
|
1707
|
+
const downsideDeviation = Math.sqrt(
|
|
1708
|
+
profits.reduce(
|
|
1709
|
+
(sum, profit) => profit < 0 ? sum + profit * profit : sum,
|
|
1710
|
+
0
|
|
1711
|
+
) / profits.length
|
|
1712
|
+
);
|
|
1713
|
+
const annualizationScale = Math.sqrt(
|
|
1714
|
+
profits.length / periodDays * DAYS_PER_YEAR
|
|
1715
|
+
);
|
|
1716
|
+
return {
|
|
1717
|
+
sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
|
|
1718
|
+
sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
|
|
1719
|
+
calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
|
|
1720
|
+
};
|
|
1721
|
+
};
|
|
1687
1722
|
var emptyRiskSummary = () => ({
|
|
1688
1723
|
trades: 0,
|
|
1689
1724
|
totalProfit: 0,
|
|
@@ -1699,12 +1734,15 @@ var emptyRiskSummary = () => ({
|
|
|
1699
1734
|
maxDrawdown: 0,
|
|
1700
1735
|
maxDrawdownPctOfGrossProfit: null,
|
|
1701
1736
|
maxDrawdownPctOfTotalProfit: null,
|
|
1737
|
+
sharpeRatio: null,
|
|
1738
|
+
sortinoRatio: null,
|
|
1739
|
+
calmarRatio: null,
|
|
1702
1740
|
recoveryFactor: null,
|
|
1703
1741
|
ulcerIndex: null,
|
|
1704
1742
|
maxConsecutiveWins: 0,
|
|
1705
1743
|
maxConsecutiveLosses: 0
|
|
1706
1744
|
});
|
|
1707
|
-
var summarizeApprovedRisk = (evaluations) => {
|
|
1745
|
+
var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
1708
1746
|
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
1709
1747
|
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
1710
1748
|
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
@@ -1727,8 +1765,10 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1727
1765
|
let currentLossStreak = 0;
|
|
1728
1766
|
let maxConsecutiveWins = 0;
|
|
1729
1767
|
let maxConsecutiveLosses = 0;
|
|
1768
|
+
const approvedProfits = [];
|
|
1730
1769
|
for (const evaluation of approvedEvaluations) {
|
|
1731
1770
|
const profit = evaluation.profit;
|
|
1771
|
+
approvedProfits.push(profit);
|
|
1732
1772
|
if (profit > 0) {
|
|
1733
1773
|
grossProfit += profit;
|
|
1734
1774
|
wins += 1;
|
|
@@ -1756,6 +1796,12 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1756
1796
|
const totalProfit = grossProfit - grossLoss;
|
|
1757
1797
|
const avgWin = divideOrNull(grossProfit, wins);
|
|
1758
1798
|
const avgLoss = divideOrNull(grossLoss, losses);
|
|
1799
|
+
const riskRatios = calculateTradePnlRiskRatios({
|
|
1800
|
+
profits: approvedProfits,
|
|
1801
|
+
totalProfit,
|
|
1802
|
+
maxDrawdown,
|
|
1803
|
+
periodDays
|
|
1804
|
+
});
|
|
1759
1805
|
return {
|
|
1760
1806
|
trades: approvedEvaluations.length,
|
|
1761
1807
|
totalProfit,
|
|
@@ -1771,6 +1817,7 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1771
1817
|
maxDrawdown,
|
|
1772
1818
|
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
1773
1819
|
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
1820
|
+
...riskRatios,
|
|
1774
1821
|
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
1775
1822
|
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
1776
1823
|
maxConsecutiveWins,
|
|
@@ -1869,7 +1916,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
|
|
|
1869
1916
|
avgApprovedTradesPerDay,
|
|
1870
1917
|
avgApprovedTradesPerWeek,
|
|
1871
1918
|
expectancyDelta,
|
|
1872
|
-
approvedRisk: summarizeApprovedRisk(evaluations),
|
|
1919
|
+
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
1873
1920
|
qualityBuckets: [...bucketMap.values()].sort(
|
|
1874
1921
|
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
1875
1922
|
)
|
package/dist/scripts/aiTrain.js
CHANGED
|
@@ -1653,6 +1653,7 @@ var divideOrNull = (num, denom) => {
|
|
|
1653
1653
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1654
1654
|
var DAYS_PER_WEEK = 7;
|
|
1655
1655
|
var DAYS_PER_MONTH = 30.4375;
|
|
1656
|
+
var DAYS_PER_YEAR = 365;
|
|
1656
1657
|
var getEvaluationPeriodDays = (evaluations) => {
|
|
1657
1658
|
let minTimestamp = null;
|
|
1658
1659
|
let maxTimestamp = null;
|
|
@@ -1674,6 +1675,40 @@ var getEvaluationPeriodDays = (evaluations) => {
|
|
|
1674
1675
|
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
1675
1676
|
};
|
|
1676
1677
|
var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
1678
|
+
var calculateTradePnlRiskRatios = ({
|
|
1679
|
+
profits,
|
|
1680
|
+
totalProfit,
|
|
1681
|
+
maxDrawdown,
|
|
1682
|
+
periodDays
|
|
1683
|
+
}) => {
|
|
1684
|
+
if (!profits.length || periodDays == null || periodDays <= 0) {
|
|
1685
|
+
return {
|
|
1686
|
+
sharpeRatio: null,
|
|
1687
|
+
sortinoRatio: null,
|
|
1688
|
+
calmarRatio: null
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
const meanProfit = totalProfit / profits.length;
|
|
1692
|
+
const variance = profits.reduce((sum, profit) => {
|
|
1693
|
+
const diff = profit - meanProfit;
|
|
1694
|
+
return sum + diff * diff;
|
|
1695
|
+
}, 0) / profits.length;
|
|
1696
|
+
const stdDev = Math.sqrt(variance);
|
|
1697
|
+
const downsideDeviation = Math.sqrt(
|
|
1698
|
+
profits.reduce(
|
|
1699
|
+
(sum, profit) => profit < 0 ? sum + profit * profit : sum,
|
|
1700
|
+
0
|
|
1701
|
+
) / profits.length
|
|
1702
|
+
);
|
|
1703
|
+
const annualizationScale = Math.sqrt(
|
|
1704
|
+
profits.length / periodDays * DAYS_PER_YEAR
|
|
1705
|
+
);
|
|
1706
|
+
return {
|
|
1707
|
+
sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
|
|
1708
|
+
sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
|
|
1709
|
+
calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
|
|
1710
|
+
};
|
|
1711
|
+
};
|
|
1677
1712
|
var emptyRiskSummary = () => ({
|
|
1678
1713
|
trades: 0,
|
|
1679
1714
|
totalProfit: 0,
|
|
@@ -1689,12 +1724,15 @@ var emptyRiskSummary = () => ({
|
|
|
1689
1724
|
maxDrawdown: 0,
|
|
1690
1725
|
maxDrawdownPctOfGrossProfit: null,
|
|
1691
1726
|
maxDrawdownPctOfTotalProfit: null,
|
|
1727
|
+
sharpeRatio: null,
|
|
1728
|
+
sortinoRatio: null,
|
|
1729
|
+
calmarRatio: null,
|
|
1692
1730
|
recoveryFactor: null,
|
|
1693
1731
|
ulcerIndex: null,
|
|
1694
1732
|
maxConsecutiveWins: 0,
|
|
1695
1733
|
maxConsecutiveLosses: 0
|
|
1696
1734
|
});
|
|
1697
|
-
var summarizeApprovedRisk = (evaluations) => {
|
|
1735
|
+
var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
1698
1736
|
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
1699
1737
|
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
1700
1738
|
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
@@ -1717,8 +1755,10 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1717
1755
|
let currentLossStreak = 0;
|
|
1718
1756
|
let maxConsecutiveWins = 0;
|
|
1719
1757
|
let maxConsecutiveLosses = 0;
|
|
1758
|
+
const approvedProfits = [];
|
|
1720
1759
|
for (const evaluation of approvedEvaluations) {
|
|
1721
1760
|
const profit = evaluation.profit;
|
|
1761
|
+
approvedProfits.push(profit);
|
|
1722
1762
|
if (profit > 0) {
|
|
1723
1763
|
grossProfit += profit;
|
|
1724
1764
|
wins += 1;
|
|
@@ -1746,6 +1786,12 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1746
1786
|
const totalProfit = grossProfit - grossLoss;
|
|
1747
1787
|
const avgWin = divideOrNull(grossProfit, wins);
|
|
1748
1788
|
const avgLoss = divideOrNull(grossLoss, losses);
|
|
1789
|
+
const riskRatios = calculateTradePnlRiskRatios({
|
|
1790
|
+
profits: approvedProfits,
|
|
1791
|
+
totalProfit,
|
|
1792
|
+
maxDrawdown,
|
|
1793
|
+
periodDays
|
|
1794
|
+
});
|
|
1749
1795
|
return {
|
|
1750
1796
|
trades: approvedEvaluations.length,
|
|
1751
1797
|
totalProfit,
|
|
@@ -1761,6 +1807,7 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
1761
1807
|
maxDrawdown,
|
|
1762
1808
|
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
1763
1809
|
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
1810
|
+
...riskRatios,
|
|
1764
1811
|
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
1765
1812
|
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
1766
1813
|
maxConsecutiveWins,
|
|
@@ -1859,7 +1906,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
|
|
|
1859
1906
|
avgApprovedTradesPerDay,
|
|
1860
1907
|
avgApprovedTradesPerWeek,
|
|
1861
1908
|
expectancyDelta,
|
|
1862
|
-
approvedRisk: summarizeApprovedRisk(evaluations),
|
|
1909
|
+
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
1863
1910
|
qualityBuckets: [...bucketMap.values()].sort(
|
|
1864
1911
|
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
1865
1912
|
)
|
|
@@ -2523,6 +2570,12 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
|
|
|
2523
2570
|
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
2524
2571
|
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
|
|
2525
2572
|
];
|
|
2573
|
+
var DERIVATIVES_CONTEXT_DATA_MODEL = {
|
|
2574
|
+
derivativesSourceIntervals: "15m",
|
|
2575
|
+
derivativesDerivedIntervals: "1h",
|
|
2576
|
+
derivativesHourlyFallback: "stored-1h",
|
|
2577
|
+
derivativesDataModelVersion: 2
|
|
2578
|
+
};
|
|
2526
2579
|
var normalizeForStableJson = (value) => {
|
|
2527
2580
|
if (Array.isArray(value)) {
|
|
2528
2581
|
return value.map(normalizeForStableJson);
|
|
@@ -2635,6 +2688,7 @@ var resolveGateFingerprint = async (projectRoot, strategyName, gitSha) => {
|
|
|
2635
2688
|
const relativeCandidates = [
|
|
2636
2689
|
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
2637
2690
|
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
2691
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
2638
2692
|
`packages/strategies/src/${strategyName}/config.ts`,
|
|
2639
2693
|
"packages/node/src/ai.ts"
|
|
2640
2694
|
];
|
|
@@ -2681,7 +2735,8 @@ var buildAiTrainLineage = async ({
|
|
|
2681
2735
|
...Object.fromEntries(
|
|
2682
2736
|
RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
2683
2737
|
),
|
|
2684
|
-
...runContext
|
|
2738
|
+
...runContext,
|
|
2739
|
+
...DERIVATIVES_CONTEXT_DATA_MODEL
|
|
2685
2740
|
};
|
|
2686
2741
|
return {
|
|
2687
2742
|
gitSha,
|
|
@@ -3741,6 +3796,9 @@ var main = async () => {
|
|
|
3741
3796
|
import_chalk.default.gray("CALENDAR/D"),
|
|
3742
3797
|
import_chalk.default.gray("WR"),
|
|
3743
3798
|
import_chalk.default.gray("PF"),
|
|
3799
|
+
import_chalk.default.gray("SHARPE"),
|
|
3800
|
+
import_chalk.default.gray("SORTINO"),
|
|
3801
|
+
import_chalk.default.gray("CALMAR"),
|
|
3744
3802
|
import_chalk.default.gray("PNL"),
|
|
3745
3803
|
import_chalk.default.gray("MAX_DD"),
|
|
3746
3804
|
import_chalk.default.gray("TOP REJECT")
|
|
@@ -3755,6 +3813,9 @@ var main = async () => {
|
|
|
3755
3813
|
colorizeMetricNumber(window2.approvedPerCalendarDay),
|
|
3756
3814
|
colorizeRatio(window2.outcome.approvedRisk.winRate),
|
|
3757
3815
|
colorizeMetricNumber(window2.outcome.approvedRisk.profitFactor),
|
|
3816
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.sharpeRatio),
|
|
3817
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.sortinoRatio),
|
|
3818
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.calmarRatio),
|
|
3758
3819
|
colorizeProfit(window2.outcome.approvedRisk.totalProfit),
|
|
3759
3820
|
colorizeProfit(-window2.outcome.approvedRisk.maxDrawdown),
|
|
3760
3821
|
import_chalk.default.gray(
|
|
@@ -3803,6 +3864,18 @@ var main = async () => {
|
|
|
3803
3864
|
"profit_factor",
|
|
3804
3865
|
colorizeMetricNumber(summary.approvedRisk.profitFactor)
|
|
3805
3866
|
],
|
|
3867
|
+
[
|
|
3868
|
+
"sharpe_ratio",
|
|
3869
|
+
colorizeMetricNumber(summary.approvedRisk.sharpeRatio)
|
|
3870
|
+
],
|
|
3871
|
+
[
|
|
3872
|
+
"sortino_ratio",
|
|
3873
|
+
colorizeMetricNumber(summary.approvedRisk.sortinoRatio)
|
|
3874
|
+
],
|
|
3875
|
+
[
|
|
3876
|
+
"calmar_ratio",
|
|
3877
|
+
colorizeMetricNumber(summary.approvedRisk.calmarRatio)
|
|
3878
|
+
],
|
|
3806
3879
|
[
|
|
3807
3880
|
"payoff_ratio",
|
|
3808
3881
|
colorizeMetricNumber(summary.approvedRisk.payoffRatio)
|