@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
package/dist/cli.js
CHANGED
|
@@ -3005,7 +3005,7 @@ var init_aiTrainDataset = __esm({
|
|
|
3005
3005
|
});
|
|
3006
3006
|
|
|
3007
3007
|
// src/lib/aiTrainMetrics.ts
|
|
3008
|
-
var divideOrNull, DAY_MS, DAYS_PER_WEEK, DAYS_PER_MONTH, getEvaluationPeriodDays, qualitySortKey, emptyRiskSummary, summarizeApprovedRisk, summarizeAiTrainEvaluations, getDirectionSortKey, getMonthKey, isDirectionMatchAtThreshold, summarizeAiTrainEvaluationsByDirection, summarizeAiTrainEvaluationsByMonth, summarizeAiTrainEvaluationsByQualityThreshold;
|
|
3008
|
+
var divideOrNull, DAY_MS, DAYS_PER_WEEK, DAYS_PER_MONTH, DAYS_PER_YEAR, getEvaluationPeriodDays, qualitySortKey, calculateTradePnlRiskRatios, emptyRiskSummary, summarizeApprovedRisk, summarizeAiTrainEvaluations, getDirectionSortKey, getMonthKey, isDirectionMatchAtThreshold, summarizeAiTrainEvaluationsByDirection, summarizeAiTrainEvaluationsByMonth, summarizeAiTrainEvaluationsByQualityThreshold;
|
|
3009
3009
|
var init_aiTrainMetrics = __esm({
|
|
3010
3010
|
"src/lib/aiTrainMetrics.ts"() {
|
|
3011
3011
|
"use strict";
|
|
@@ -3018,6 +3018,7 @@ var init_aiTrainMetrics = __esm({
|
|
|
3018
3018
|
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3019
3019
|
DAYS_PER_WEEK = 7;
|
|
3020
3020
|
DAYS_PER_MONTH = 30.4375;
|
|
3021
|
+
DAYS_PER_YEAR = 365;
|
|
3021
3022
|
getEvaluationPeriodDays = (evaluations) => {
|
|
3022
3023
|
let minTimestamp = null;
|
|
3023
3024
|
let maxTimestamp = null;
|
|
@@ -3039,6 +3040,40 @@ var init_aiTrainMetrics = __esm({
|
|
|
3039
3040
|
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
3040
3041
|
};
|
|
3041
3042
|
qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
3043
|
+
calculateTradePnlRiskRatios = ({
|
|
3044
|
+
profits,
|
|
3045
|
+
totalProfit,
|
|
3046
|
+
maxDrawdown,
|
|
3047
|
+
periodDays
|
|
3048
|
+
}) => {
|
|
3049
|
+
if (!profits.length || periodDays == null || periodDays <= 0) {
|
|
3050
|
+
return {
|
|
3051
|
+
sharpeRatio: null,
|
|
3052
|
+
sortinoRatio: null,
|
|
3053
|
+
calmarRatio: null
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
const meanProfit = totalProfit / profits.length;
|
|
3057
|
+
const variance = profits.reduce((sum2, profit) => {
|
|
3058
|
+
const diff = profit - meanProfit;
|
|
3059
|
+
return sum2 + diff * diff;
|
|
3060
|
+
}, 0) / profits.length;
|
|
3061
|
+
const stdDev = Math.sqrt(variance);
|
|
3062
|
+
const downsideDeviation = Math.sqrt(
|
|
3063
|
+
profits.reduce(
|
|
3064
|
+
(sum2, profit) => profit < 0 ? sum2 + profit * profit : sum2,
|
|
3065
|
+
0
|
|
3066
|
+
) / profits.length
|
|
3067
|
+
);
|
|
3068
|
+
const annualizationScale = Math.sqrt(
|
|
3069
|
+
profits.length / periodDays * DAYS_PER_YEAR
|
|
3070
|
+
);
|
|
3071
|
+
return {
|
|
3072
|
+
sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
|
|
3073
|
+
sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
|
|
3074
|
+
calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
|
|
3075
|
+
};
|
|
3076
|
+
};
|
|
3042
3077
|
emptyRiskSummary = () => ({
|
|
3043
3078
|
trades: 0,
|
|
3044
3079
|
totalProfit: 0,
|
|
@@ -3054,12 +3089,15 @@ var init_aiTrainMetrics = __esm({
|
|
|
3054
3089
|
maxDrawdown: 0,
|
|
3055
3090
|
maxDrawdownPctOfGrossProfit: null,
|
|
3056
3091
|
maxDrawdownPctOfTotalProfit: null,
|
|
3092
|
+
sharpeRatio: null,
|
|
3093
|
+
sortinoRatio: null,
|
|
3094
|
+
calmarRatio: null,
|
|
3057
3095
|
recoveryFactor: null,
|
|
3058
3096
|
ulcerIndex: null,
|
|
3059
3097
|
maxConsecutiveWins: 0,
|
|
3060
3098
|
maxConsecutiveLosses: 0
|
|
3061
3099
|
});
|
|
3062
|
-
summarizeApprovedRisk = (evaluations) => {
|
|
3100
|
+
summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
3063
3101
|
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
3064
3102
|
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
3065
3103
|
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
@@ -3082,8 +3120,10 @@ var init_aiTrainMetrics = __esm({
|
|
|
3082
3120
|
let currentLossStreak = 0;
|
|
3083
3121
|
let maxConsecutiveWins = 0;
|
|
3084
3122
|
let maxConsecutiveLosses = 0;
|
|
3123
|
+
const approvedProfits = [];
|
|
3085
3124
|
for (const evaluation of approvedEvaluations) {
|
|
3086
3125
|
const profit = evaluation.profit;
|
|
3126
|
+
approvedProfits.push(profit);
|
|
3087
3127
|
if (profit > 0) {
|
|
3088
3128
|
grossProfit += profit;
|
|
3089
3129
|
wins += 1;
|
|
@@ -3111,6 +3151,12 @@ var init_aiTrainMetrics = __esm({
|
|
|
3111
3151
|
const totalProfit = grossProfit - grossLoss;
|
|
3112
3152
|
const avgWin = divideOrNull(grossProfit, wins);
|
|
3113
3153
|
const avgLoss = divideOrNull(grossLoss, losses);
|
|
3154
|
+
const riskRatios = calculateTradePnlRiskRatios({
|
|
3155
|
+
profits: approvedProfits,
|
|
3156
|
+
totalProfit,
|
|
3157
|
+
maxDrawdown,
|
|
3158
|
+
periodDays
|
|
3159
|
+
});
|
|
3114
3160
|
return {
|
|
3115
3161
|
trades: approvedEvaluations.length,
|
|
3116
3162
|
totalProfit,
|
|
@@ -3126,6 +3172,7 @@ var init_aiTrainMetrics = __esm({
|
|
|
3126
3172
|
maxDrawdown,
|
|
3127
3173
|
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
3128
3174
|
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
3175
|
+
...riskRatios,
|
|
3129
3176
|
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
3130
3177
|
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
3131
3178
|
maxConsecutiveWins,
|
|
@@ -3224,7 +3271,7 @@ var init_aiTrainMetrics = __esm({
|
|
|
3224
3271
|
avgApprovedTradesPerDay,
|
|
3225
3272
|
avgApprovedTradesPerWeek,
|
|
3226
3273
|
expectancyDelta,
|
|
3227
|
-
approvedRisk: summarizeApprovedRisk(evaluations),
|
|
3274
|
+
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
3228
3275
|
qualityBuckets: [...bucketMap.values()].sort(
|
|
3229
3276
|
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
3230
3277
|
)
|
|
@@ -6135,7 +6182,7 @@ var init_aiTrainEvaluationDump = __esm({
|
|
|
6135
6182
|
});
|
|
6136
6183
|
|
|
6137
6184
|
// src/lib/aiTrainResearch.ts
|
|
6138
|
-
var import_node_child_process2, import_node_crypto2, import_promises4, import_node_path3, DAY_MS3, RESEARCH_CONTEXT_ENV_KEYS, normalizeForStableJson, fingerprintResearchValue, getTimestampRange, summarizeAiTrainCoverage, summarizeAiTrainRejectReasons, summarizeAiTrainTerminalWindows, readOptionalFile, getGitLineage, resolveGateFingerprint, buildAiTrainLineage;
|
|
6185
|
+
var import_node_child_process2, import_node_crypto2, import_promises4, import_node_path3, DAY_MS3, RESEARCH_CONTEXT_ENV_KEYS, DERIVATIVES_CONTEXT_DATA_MODEL, normalizeForStableJson, fingerprintResearchValue, getTimestampRange, summarizeAiTrainCoverage, summarizeAiTrainRejectReasons, summarizeAiTrainTerminalWindows, readOptionalFile, getGitLineage, resolveGateFingerprint, buildAiTrainLineage;
|
|
6139
6186
|
var init_aiTrainResearch = __esm({
|
|
6140
6187
|
"src/lib/aiTrainResearch.ts"() {
|
|
6141
6188
|
"use strict";
|
|
@@ -6164,6 +6211,12 @@ var init_aiTrainResearch = __esm({
|
|
|
6164
6211
|
"COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
|
|
6165
6212
|
"COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
|
|
6166
6213
|
];
|
|
6214
|
+
DERIVATIVES_CONTEXT_DATA_MODEL = {
|
|
6215
|
+
derivativesSourceIntervals: "15m",
|
|
6216
|
+
derivativesDerivedIntervals: "1h",
|
|
6217
|
+
derivativesHourlyFallback: "stored-1h",
|
|
6218
|
+
derivativesDataModelVersion: 2
|
|
6219
|
+
};
|
|
6167
6220
|
normalizeForStableJson = (value) => {
|
|
6168
6221
|
if (Array.isArray(value)) {
|
|
6169
6222
|
return value.map(normalizeForStableJson);
|
|
@@ -6276,6 +6329,7 @@ var init_aiTrainResearch = __esm({
|
|
|
6276
6329
|
const relativeCandidates = [
|
|
6277
6330
|
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
6278
6331
|
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
6332
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
6279
6333
|
`packages/strategies/src/${strategyName}/config.ts`,
|
|
6280
6334
|
"packages/node/src/ai.ts"
|
|
6281
6335
|
];
|
|
@@ -6322,7 +6376,8 @@ var init_aiTrainResearch = __esm({
|
|
|
6322
6376
|
...Object.fromEntries(
|
|
6323
6377
|
RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
6324
6378
|
),
|
|
6325
|
-
...runContext
|
|
6379
|
+
...runContext,
|
|
6380
|
+
...DERIVATIVES_CONTEXT_DATA_MODEL
|
|
6326
6381
|
};
|
|
6327
6382
|
return {
|
|
6328
6383
|
gitSha,
|
|
@@ -7402,6 +7457,9 @@ var init_aiTrain = __esm({
|
|
|
7402
7457
|
import_chalk5.default.gray("CALENDAR/D"),
|
|
7403
7458
|
import_chalk5.default.gray("WR"),
|
|
7404
7459
|
import_chalk5.default.gray("PF"),
|
|
7460
|
+
import_chalk5.default.gray("SHARPE"),
|
|
7461
|
+
import_chalk5.default.gray("SORTINO"),
|
|
7462
|
+
import_chalk5.default.gray("CALMAR"),
|
|
7405
7463
|
import_chalk5.default.gray("PNL"),
|
|
7406
7464
|
import_chalk5.default.gray("MAX_DD"),
|
|
7407
7465
|
import_chalk5.default.gray("TOP REJECT")
|
|
@@ -7416,6 +7474,9 @@ var init_aiTrain = __esm({
|
|
|
7416
7474
|
colorizeMetricNumber(window2.approvedPerCalendarDay),
|
|
7417
7475
|
colorizeRatio2(window2.outcome.approvedRisk.winRate),
|
|
7418
7476
|
colorizeMetricNumber(window2.outcome.approvedRisk.profitFactor),
|
|
7477
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.sharpeRatio),
|
|
7478
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.sortinoRatio),
|
|
7479
|
+
colorizeMetricNumber(window2.outcome.approvedRisk.calmarRatio),
|
|
7419
7480
|
colorizeProfit2(window2.outcome.approvedRisk.totalProfit),
|
|
7420
7481
|
colorizeProfit2(-window2.outcome.approvedRisk.maxDrawdown),
|
|
7421
7482
|
import_chalk5.default.gray(
|
|
@@ -7464,6 +7525,18 @@ var init_aiTrain = __esm({
|
|
|
7464
7525
|
"profit_factor",
|
|
7465
7526
|
colorizeMetricNumber(summary.approvedRisk.profitFactor)
|
|
7466
7527
|
],
|
|
7528
|
+
[
|
|
7529
|
+
"sharpe_ratio",
|
|
7530
|
+
colorizeMetricNumber(summary.approvedRisk.sharpeRatio)
|
|
7531
|
+
],
|
|
7532
|
+
[
|
|
7533
|
+
"sortino_ratio",
|
|
7534
|
+
colorizeMetricNumber(summary.approvedRisk.sortinoRatio)
|
|
7535
|
+
],
|
|
7536
|
+
[
|
|
7537
|
+
"calmar_ratio",
|
|
7538
|
+
colorizeMetricNumber(summary.approvedRisk.calmarRatio)
|
|
7539
|
+
],
|
|
7467
7540
|
[
|
|
7468
7541
|
"payoff_ratio",
|
|
7469
7542
|
colorizeMetricNumber(summary.approvedRisk.payoffRatio)
|
|
@@ -8590,7 +8663,7 @@ var init_binanceMarketContextBackfill = __esm({
|
|
|
8590
8663
|
});
|
|
8591
8664
|
|
|
8592
8665
|
// src/lib/derivativesContextBackfill.ts
|
|
8593
|
-
var import_chalk7, import_progress4, import_async, import_constants5, import_indicators, import_timescale2, import_userSettings2, HOUR_MS, DAY_MS6, DEFAULT_LOOKBACK_HOURS, SOURCE_INTERVALS, coinalyzeIntervalMap, derivativesIntervalMs, lastRequestTs, asInt2, parseList, parseBooleanFlag, normalizeSymbols, isDerivativesTargetContextEnabled, resolveDerivativesContextBackfillSymbols, chunkArray, isBacktestDerivativesContextEnabled, isSignalsDerivativesContextEnabled, isDerivativesContextBackfillEnabled, shouldBackfillDerivativesContextForBacktest, shouldBackfillDerivativesContextForSignals, resolveDerivativesContextIntervals, resolveDerivativesContextLookbackMs, resolveDerivativesContextBackfillWindow, resolveDerivativesContextIntervalBackfillWindow, resolveDerivativesContextMissingFetchFromMs, resolveDerivativesContextFetchFromMs, countBackfillWindows, buildBackfillWindows, hasDerivativesWindowCoverage, resolveDerivativesContextMissingCoverageFetchFromMs, groupDerivativesContextMissingFetchRanges, getMissingClosedDerivativesSymbols, coverageKey, extendEdges, getCoinalyzeApiKey, getCoinalyzeBaseUrl, getRequestDelayMs, getRequestTimeoutMs, networkErrorCodes, getNestedErrorValue, getCoinalyzeErrorCause, isRetryableCoinalyzeFetchError, formatCoinalyzeRequestError, fetchJsonWithRateLimit, fetchCoinalyzeMarkets, selectBestMarket, buildMatches, normalizeMetricPoint, toSeriesMap, fetchMetricBatch, skippedBackfillResult2, backfillDerivativesContext, backfillDerivativesContextForBacktest, backfillDerivativesContextForSignals;
|
|
8666
|
+
var import_chalk7, import_progress4, import_async, import_constants5, import_indicators, import_timescale2, import_userSettings2, HOUR_MS, DAY_MS6, DEFAULT_LOOKBACK_HOURS, SOURCE_INTERVALS, coinalyzeIntervalMap, derivativesIntervalMs, lastRequestTs, asInt2, parseList, parseBooleanFlag, normalizeSymbols, isDerivativesTargetContextEnabled, resolveDerivativesContextBackfillSymbols, chunkArray, isBacktestDerivativesContextEnabled, isSignalsDerivativesContextEnabled, isDerivativesContextBackfillEnabled, shouldBackfillDerivativesContextForBacktest, shouldBackfillDerivativesContextForSignals, resolveDerivativesContextIntervals, resolveDerivativesContextLookbackMs, resolveDerivativesContextBackfillWindow, resolveDerivativesContextIntervalBackfillWindow, resolveDerivativesContextMissingFetchFromMs, resolveDerivativesContextFetchFromMs, resolveDerivativesContextRequiredFetchFromMs, countBackfillWindows, buildBackfillWindows, hasDerivativesWindowCoverage, resolveDerivativesContextMissingCoverageFetchFromMs, groupDerivativesContextMissingFetchRanges, getMissingClosedDerivativesSymbols, coverageKey, extendEdges, getCoinalyzeApiKey, getCoinalyzeBaseUrl, getRequestDelayMs, getRequestTimeoutMs, networkErrorCodes, getNestedErrorValue, getCoinalyzeErrorCause, isRetryableCoinalyzeFetchError, formatCoinalyzeRequestError, fetchJsonWithRateLimit, fetchCoinalyzeMarkets, selectBestMarket, buildMatches, normalizeMetricPoint, toSeriesMap, fetchMetricBatch, skippedBackfillResult2, backfillDerivativesContext, backfillDerivativesContextForBacktest, backfillDerivativesContextForSignals;
|
|
8594
8667
|
var init_derivativesContextBackfill = __esm({
|
|
8595
8668
|
"src/lib/derivativesContextBackfill.ts"() {
|
|
8596
8669
|
"use strict";
|
|
@@ -8722,6 +8795,38 @@ var init_derivativesContextBackfill = __esm({
|
|
|
8722
8795
|
return null;
|
|
8723
8796
|
};
|
|
8724
8797
|
resolveDerivativesContextFetchFromMs = (params) => resolveDerivativesContextMissingFetchFromMs(params) ?? (params.refreshClosedTail ? params.toMs : null);
|
|
8798
|
+
resolveDerivativesContextRequiredFetchFromMs = (params) => {
|
|
8799
|
+
const dataCoverageFromMs = params.dataCoverageKeyExists ? null : params.mode === "signals" ? params.fromMs : resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
8800
|
+
ranges: params.dataCoverageRanges,
|
|
8801
|
+
fromMs: params.fromMs,
|
|
8802
|
+
toMs: params.toMs,
|
|
8803
|
+
intervalMs: params.intervalMs
|
|
8804
|
+
});
|
|
8805
|
+
const edgesFromMs = dataCoverageFromMs == null ? null : resolveDerivativesContextFetchFromMs({
|
|
8806
|
+
edges: params.edges,
|
|
8807
|
+
fromMs: params.fromMs,
|
|
8808
|
+
toMs: params.toMs,
|
|
8809
|
+
intervalMs: params.intervalMs,
|
|
8810
|
+
refreshClosedTail: params.mode === "signals"
|
|
8811
|
+
});
|
|
8812
|
+
const dataFetchFromMs = dataCoverageFromMs != null && edgesFromMs != null ? Math.max(dataCoverageFromMs, edgesFromMs) : null;
|
|
8813
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
8814
|
+
interval: params.interval,
|
|
8815
|
+
fromMs: params.fromMs,
|
|
8816
|
+
toMs: params.toMs,
|
|
8817
|
+
nowMs: params.nowMs
|
|
8818
|
+
});
|
|
8819
|
+
const liquidationFetchFromMs = confirmedLiquidationWindow ? resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
8820
|
+
ranges: params.liquidationCoverageRanges,
|
|
8821
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
8822
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
8823
|
+
intervalMs: params.intervalMs
|
|
8824
|
+
}) : null;
|
|
8825
|
+
const fetchStarts = [dataFetchFromMs, liquidationFetchFromMs].filter(
|
|
8826
|
+
(value) => value != null
|
|
8827
|
+
);
|
|
8828
|
+
return fetchStarts.length ? Math.min(...fetchStarts) : null;
|
|
8829
|
+
};
|
|
8725
8830
|
countBackfillWindows = (params) => {
|
|
8726
8831
|
let count = 0;
|
|
8727
8832
|
let cursor = params.fromMs;
|
|
@@ -9057,7 +9162,8 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9057
9162
|
mode,
|
|
9058
9163
|
startMs,
|
|
9059
9164
|
endMs,
|
|
9060
|
-
preloadStartMs: params.preloadStartMs
|
|
9165
|
+
preloadStartMs: params.preloadStartMs,
|
|
9166
|
+
nowMs: Date.now()
|
|
9061
9167
|
});
|
|
9062
9168
|
if (safeEndMs <= fromMs) {
|
|
9063
9169
|
return skippedBackfillResult2();
|
|
@@ -9091,15 +9197,33 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9091
9197
|
);
|
|
9092
9198
|
const coverageKeysByInterval = /* @__PURE__ */ new Map();
|
|
9093
9199
|
const coverageRangesByInterval = /* @__PURE__ */ new Map();
|
|
9200
|
+
const liquidationCoverageRangesByInterval = /* @__PURE__ */ new Map();
|
|
9201
|
+
const coverageNowMs = Date.now();
|
|
9094
9202
|
await Promise.all(
|
|
9095
9203
|
intervalWindows.map(async (window2) => {
|
|
9096
|
-
const
|
|
9097
|
-
source: "coinalyze",
|
|
9098
|
-
symbols,
|
|
9204
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
9099
9205
|
interval: window2.interval,
|
|
9100
9206
|
fromMs: window2.fromMs,
|
|
9101
|
-
toMs: window2.toMs
|
|
9207
|
+
toMs: window2.toMs,
|
|
9208
|
+
nowMs: coverageNowMs
|
|
9102
9209
|
});
|
|
9210
|
+
const [coverageRows, liquidationCoverageRows] = await Promise.all([
|
|
9211
|
+
(0, import_timescale2.getDerivativesBackfillCoverage)({
|
|
9212
|
+
source: "coinalyze",
|
|
9213
|
+
symbols,
|
|
9214
|
+
interval: window2.interval,
|
|
9215
|
+
fromMs: window2.fromMs,
|
|
9216
|
+
toMs: window2.toMs
|
|
9217
|
+
}),
|
|
9218
|
+
confirmedLiquidationWindow ? (0, import_timescale2.getDerivativesMetricCoverage)({
|
|
9219
|
+
source: "coinalyze",
|
|
9220
|
+
metric: "liquidation",
|
|
9221
|
+
symbols,
|
|
9222
|
+
interval: window2.interval,
|
|
9223
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
9224
|
+
toMs: confirmedLiquidationWindow.toMs
|
|
9225
|
+
}) : Promise.resolve([])
|
|
9226
|
+
]);
|
|
9103
9227
|
coverageKeysByInterval.set(
|
|
9104
9228
|
window2.interval,
|
|
9105
9229
|
new Set(
|
|
@@ -9121,6 +9245,17 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9121
9245
|
rangesBySymbol.set(symbol, ranges);
|
|
9122
9246
|
}
|
|
9123
9247
|
coverageRangesByInterval.set(window2.interval, rangesBySymbol);
|
|
9248
|
+
const liquidationRangesBySymbol = /* @__PURE__ */ new Map();
|
|
9249
|
+
for (const row of liquidationCoverageRows) {
|
|
9250
|
+
const symbol = row.symbol.toUpperCase();
|
|
9251
|
+
const ranges = liquidationRangesBySymbol.get(symbol) ?? [];
|
|
9252
|
+
ranges.push({ fromMs: row.fromMs, toMs: row.toMs });
|
|
9253
|
+
liquidationRangesBySymbol.set(symbol, ranges);
|
|
9254
|
+
}
|
|
9255
|
+
liquidationCoverageRangesByInterval.set(
|
|
9256
|
+
window2.interval,
|
|
9257
|
+
liquidationRangesBySymbol
|
|
9258
|
+
);
|
|
9124
9259
|
})
|
|
9125
9260
|
);
|
|
9126
9261
|
const cachedWindows = intervalWindows.reduce(
|
|
@@ -9145,7 +9280,7 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9145
9280
|
(symbol) => backfillWindows.every((backfillWindow) => {
|
|
9146
9281
|
const normalizedSymbol = symbol.toUpperCase();
|
|
9147
9282
|
const coverageRanges = coverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [];
|
|
9148
|
-
|
|
9283
|
+
const dataCovered = hasDerivativesWindowCoverage({
|
|
9149
9284
|
edges: edgesBySymbol?.get(normalizedSymbol),
|
|
9150
9285
|
fromMs: backfillWindow.fromMs,
|
|
9151
9286
|
toMs: backfillWindow.toMs
|
|
@@ -9162,6 +9297,19 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9162
9297
|
toMs: backfillWindow.toMs
|
|
9163
9298
|
})
|
|
9164
9299
|
);
|
|
9300
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
9301
|
+
interval: window2.interval,
|
|
9302
|
+
fromMs: backfillWindow.fromMs,
|
|
9303
|
+
toMs: backfillWindow.toMs,
|
|
9304
|
+
nowMs: coverageNowMs
|
|
9305
|
+
});
|
|
9306
|
+
const liquidationCovered = confirmedLiquidationWindow == null || resolveDerivativesContextMissingCoverageFetchFromMs({
|
|
9307
|
+
ranges: liquidationCoverageRangesByInterval.get(window2.interval)?.get(normalizedSymbol) ?? [],
|
|
9308
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
9309
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
9310
|
+
intervalMs: window2.intervalMs
|
|
9311
|
+
}) == null;
|
|
9312
|
+
return dataCovered && liquidationCovered;
|
|
9165
9313
|
})
|
|
9166
9314
|
);
|
|
9167
9315
|
});
|
|
@@ -9246,6 +9394,11 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9246
9394
|
coverageKeysByInterval.set(interval5, coverageKeys);
|
|
9247
9395
|
const coverageRangesBySymbol = coverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
|
|
9248
9396
|
coverageRangesByInterval.set(interval5, coverageRangesBySymbol);
|
|
9397
|
+
const liquidationCoverageRangesBySymbol = liquidationCoverageRangesByInterval.get(interval5) ?? /* @__PURE__ */ new Map();
|
|
9398
|
+
liquidationCoverageRangesByInterval.set(
|
|
9399
|
+
interval5,
|
|
9400
|
+
liquidationCoverageRangesBySymbol
|
|
9401
|
+
);
|
|
9249
9402
|
for (let batchIdx = 0; batchIdx < symbolBatches.length; batchIdx += 1) {
|
|
9250
9403
|
const batch = symbolBatches[batchIdx];
|
|
9251
9404
|
let cursor = window2.fromMs;
|
|
@@ -9258,33 +9411,25 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9258
9411
|
fromMs: cursor,
|
|
9259
9412
|
toMs
|
|
9260
9413
|
});
|
|
9261
|
-
if (coverageKeys.has(key)) {
|
|
9262
|
-
return null;
|
|
9263
|
-
}
|
|
9264
9414
|
const normalizedSymbol = item.symbol.toUpperCase();
|
|
9265
|
-
const
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
refreshClosedTail: mode === "signals"
|
|
9281
|
-
});
|
|
9282
|
-
if (edgesFromMs == null) {
|
|
9283
|
-
return null;
|
|
9284
|
-
}
|
|
9415
|
+
const requiredFromMs = resolveDerivativesContextRequiredFetchFromMs(
|
|
9416
|
+
{
|
|
9417
|
+
mode,
|
|
9418
|
+
interval: interval5,
|
|
9419
|
+
intervalMs: intervalMs2,
|
|
9420
|
+
fromMs: cursor,
|
|
9421
|
+
toMs,
|
|
9422
|
+
nowMs: coverageNowMs,
|
|
9423
|
+
dataCoverageKeyExists: coverageKeys.has(key),
|
|
9424
|
+
dataCoverageRanges: coverageRangesBySymbol.get(normalizedSymbol),
|
|
9425
|
+
liquidationCoverageRanges: liquidationCoverageRangesBySymbol.get(normalizedSymbol),
|
|
9426
|
+
edges: edgesBySymbol.get(normalizedSymbol)
|
|
9427
|
+
}
|
|
9428
|
+
);
|
|
9429
|
+
if (requiredFromMs == null) return null;
|
|
9285
9430
|
return {
|
|
9286
9431
|
item,
|
|
9287
|
-
fromMs:
|
|
9432
|
+
fromMs: requiredFromMs
|
|
9288
9433
|
};
|
|
9289
9434
|
}).filter(
|
|
9290
9435
|
(item) => item != null
|
|
@@ -9309,6 +9454,7 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9309
9454
|
);
|
|
9310
9455
|
let rows = [];
|
|
9311
9456
|
let missingClosedSymbols = [];
|
|
9457
|
+
let liquidationRowsByMarket = /* @__PURE__ */ new Map();
|
|
9312
9458
|
for (let attempt = 1; attempt <= maxClosedBarAttempts; attempt += 1) {
|
|
9313
9459
|
const oiMap = await fetchMetricBatch({
|
|
9314
9460
|
endpoint: oiPath,
|
|
@@ -9328,7 +9474,7 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9328
9474
|
fromMs: group.fromMs,
|
|
9329
9475
|
toMs
|
|
9330
9476
|
});
|
|
9331
|
-
|
|
9477
|
+
liquidationRowsByMarket = await fetchMetricBatch({
|
|
9332
9478
|
endpoint: liqPath,
|
|
9333
9479
|
metric: "liq",
|
|
9334
9480
|
marketSymbols,
|
|
@@ -9343,7 +9489,7 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9343
9489
|
symbol: item.symbol,
|
|
9344
9490
|
oiRaw: oiMap.get(marketSymbol) ?? [],
|
|
9345
9491
|
fundingRaw: fundingMap.get(marketSymbol) ?? [],
|
|
9346
|
-
liqRaw:
|
|
9492
|
+
liqRaw: liquidationRowsByMarket.get(marketSymbol) ?? []
|
|
9347
9493
|
});
|
|
9348
9494
|
return (0, import_indicators.coinalyzePointsToRows)(points, interval5, "coinalyze");
|
|
9349
9495
|
});
|
|
@@ -9366,6 +9512,42 @@ var init_derivativesContextBackfill = __esm({
|
|
|
9366
9512
|
await (0, import_timescale2.upsertDerivatives)(rows);
|
|
9367
9513
|
totalRows += rows.length;
|
|
9368
9514
|
}
|
|
9515
|
+
const confirmedLiquidationWindow = (0, import_indicators.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
9516
|
+
interval: interval5,
|
|
9517
|
+
fromMs: group.fromMs,
|
|
9518
|
+
toMs,
|
|
9519
|
+
nowMs: coverageNowMs
|
|
9520
|
+
});
|
|
9521
|
+
if (confirmedLiquidationWindow) {
|
|
9522
|
+
const metricCoverageRows = missingBatch.map((item) => {
|
|
9523
|
+
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
9524
|
+
const eventRowsCount = (liquidationRowsByMarket.get(marketSymbol) ?? []).filter((point) => {
|
|
9525
|
+
const timestamp = (0, import_indicators.toCoinalyzeTimestampMs)(
|
|
9526
|
+
point.t ?? point.ts ?? point.time ?? point.timestamp
|
|
9527
|
+
);
|
|
9528
|
+
return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
|
|
9529
|
+
}).length;
|
|
9530
|
+
return {
|
|
9531
|
+
source: "coinalyze",
|
|
9532
|
+
metric: "liquidation",
|
|
9533
|
+
symbol: item.symbol,
|
|
9534
|
+
interval: interval5,
|
|
9535
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
9536
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
9537
|
+
eventRowsCount
|
|
9538
|
+
};
|
|
9539
|
+
});
|
|
9540
|
+
await (0, import_timescale2.applyDerivativesMetricCoverage)(metricCoverageRows);
|
|
9541
|
+
for (const coverageRow of metricCoverageRows) {
|
|
9542
|
+
const symbol = coverageRow.symbol.toUpperCase();
|
|
9543
|
+
const ranges = liquidationCoverageRangesBySymbol.get(symbol) ?? [];
|
|
9544
|
+
ranges.push({
|
|
9545
|
+
fromMs: coverageRow.fromMs,
|
|
9546
|
+
toMs: coverageRow.toMs
|
|
9547
|
+
});
|
|
9548
|
+
liquidationCoverageRangesBySymbol.set(symbol, ranges);
|
|
9549
|
+
}
|
|
9550
|
+
}
|
|
9369
9551
|
if (mode === "backtest") {
|
|
9370
9552
|
const rowsCountBySymbol = /* @__PURE__ */ new Map();
|
|
9371
9553
|
for (const row of rows) {
|
|
@@ -19393,6 +19575,36 @@ var init_derivativesIngestCoinalyzeAll = __esm({
|
|
|
19393
19575
|
await (0, import_timescale7.upsertDerivatives)(rows);
|
|
19394
19576
|
totalRows += rows.length;
|
|
19395
19577
|
}
|
|
19578
|
+
const confirmedLiquidationWindow = (0, import_indicators3.resolveCoinalyzeConfirmedIntradayCoverage)({
|
|
19579
|
+
interval: interval5,
|
|
19580
|
+
fromMs: cursor,
|
|
19581
|
+
toMs,
|
|
19582
|
+
nowMs: now2
|
|
19583
|
+
});
|
|
19584
|
+
if (confirmedLiquidationWindow) {
|
|
19585
|
+
await (0, import_timescale7.applyDerivativesMetricCoverage)(
|
|
19586
|
+
batch.map((item) => {
|
|
19587
|
+
const marketSymbol = item.marketSymbol.toUpperCase();
|
|
19588
|
+
const eventRowsCount = (liqMap.get(marketSymbol) ?? []).filter(
|
|
19589
|
+
(point) => {
|
|
19590
|
+
const timestamp = (0, import_indicators3.toCoinalyzeTimestampMs)(
|
|
19591
|
+
point.t ?? point.ts ?? point.time ?? point.timestamp
|
|
19592
|
+
);
|
|
19593
|
+
return timestamp != null && timestamp >= confirmedLiquidationWindow.fromMs && timestamp <= confirmedLiquidationWindow.toMs;
|
|
19594
|
+
}
|
|
19595
|
+
).length;
|
|
19596
|
+
return {
|
|
19597
|
+
source: "coinalyze",
|
|
19598
|
+
metric: "liquidation",
|
|
19599
|
+
symbol: item.symbol,
|
|
19600
|
+
interval: interval5,
|
|
19601
|
+
fromMs: confirmedLiquidationWindow.fromMs,
|
|
19602
|
+
toMs: confirmedLiquidationWindow.toMs,
|
|
19603
|
+
eventRowsCount
|
|
19604
|
+
};
|
|
19605
|
+
})
|
|
19606
|
+
);
|
|
19607
|
+
}
|
|
19396
19608
|
} catch (error) {
|
|
19397
19609
|
failedWindows += 1;
|
|
19398
19610
|
console.error(
|
|
@@ -34,6 +34,7 @@ var divideOrNull = (num, denom) => {
|
|
|
34
34
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
35
35
|
var DAYS_PER_WEEK = 7;
|
|
36
36
|
var DAYS_PER_MONTH = 30.4375;
|
|
37
|
+
var DAYS_PER_YEAR = 365;
|
|
37
38
|
var getEvaluationPeriodDays = (evaluations) => {
|
|
38
39
|
let minTimestamp = null;
|
|
39
40
|
let maxTimestamp = null;
|
|
@@ -55,6 +56,40 @@ var getEvaluationPeriodDays = (evaluations) => {
|
|
|
55
56
|
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
56
57
|
};
|
|
57
58
|
var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
59
|
+
var calculateTradePnlRiskRatios = ({
|
|
60
|
+
profits,
|
|
61
|
+
totalProfit,
|
|
62
|
+
maxDrawdown,
|
|
63
|
+
periodDays
|
|
64
|
+
}) => {
|
|
65
|
+
if (!profits.length || periodDays == null || periodDays <= 0) {
|
|
66
|
+
return {
|
|
67
|
+
sharpeRatio: null,
|
|
68
|
+
sortinoRatio: null,
|
|
69
|
+
calmarRatio: null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const meanProfit = totalProfit / profits.length;
|
|
73
|
+
const variance = profits.reduce((sum, profit) => {
|
|
74
|
+
const diff = profit - meanProfit;
|
|
75
|
+
return sum + diff * diff;
|
|
76
|
+
}, 0) / profits.length;
|
|
77
|
+
const stdDev = Math.sqrt(variance);
|
|
78
|
+
const downsideDeviation = Math.sqrt(
|
|
79
|
+
profits.reduce(
|
|
80
|
+
(sum, profit) => profit < 0 ? sum + profit * profit : sum,
|
|
81
|
+
0
|
|
82
|
+
) / profits.length
|
|
83
|
+
);
|
|
84
|
+
const annualizationScale = Math.sqrt(
|
|
85
|
+
profits.length / periodDays * DAYS_PER_YEAR
|
|
86
|
+
);
|
|
87
|
+
return {
|
|
88
|
+
sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
|
|
89
|
+
sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
|
|
90
|
+
calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
|
|
91
|
+
};
|
|
92
|
+
};
|
|
58
93
|
var emptyRiskSummary = () => ({
|
|
59
94
|
trades: 0,
|
|
60
95
|
totalProfit: 0,
|
|
@@ -70,12 +105,15 @@ var emptyRiskSummary = () => ({
|
|
|
70
105
|
maxDrawdown: 0,
|
|
71
106
|
maxDrawdownPctOfGrossProfit: null,
|
|
72
107
|
maxDrawdownPctOfTotalProfit: null,
|
|
108
|
+
sharpeRatio: null,
|
|
109
|
+
sortinoRatio: null,
|
|
110
|
+
calmarRatio: null,
|
|
73
111
|
recoveryFactor: null,
|
|
74
112
|
ulcerIndex: null,
|
|
75
113
|
maxConsecutiveWins: 0,
|
|
76
114
|
maxConsecutiveLosses: 0
|
|
77
115
|
});
|
|
78
|
-
var summarizeApprovedRisk = (evaluations) => {
|
|
116
|
+
var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
79
117
|
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
80
118
|
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
81
119
|
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
@@ -98,8 +136,10 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
98
136
|
let currentLossStreak = 0;
|
|
99
137
|
let maxConsecutiveWins = 0;
|
|
100
138
|
let maxConsecutiveLosses = 0;
|
|
139
|
+
const approvedProfits = [];
|
|
101
140
|
for (const evaluation of approvedEvaluations) {
|
|
102
141
|
const profit = evaluation.profit;
|
|
142
|
+
approvedProfits.push(profit);
|
|
103
143
|
if (profit > 0) {
|
|
104
144
|
grossProfit += profit;
|
|
105
145
|
wins += 1;
|
|
@@ -127,6 +167,12 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
127
167
|
const totalProfit = grossProfit - grossLoss;
|
|
128
168
|
const avgWin = divideOrNull(grossProfit, wins);
|
|
129
169
|
const avgLoss = divideOrNull(grossLoss, losses);
|
|
170
|
+
const riskRatios = calculateTradePnlRiskRatios({
|
|
171
|
+
profits: approvedProfits,
|
|
172
|
+
totalProfit,
|
|
173
|
+
maxDrawdown,
|
|
174
|
+
periodDays
|
|
175
|
+
});
|
|
130
176
|
return {
|
|
131
177
|
trades: approvedEvaluations.length,
|
|
132
178
|
totalProfit,
|
|
@@ -142,6 +188,7 @@ var summarizeApprovedRisk = (evaluations) => {
|
|
|
142
188
|
maxDrawdown,
|
|
143
189
|
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
144
190
|
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
191
|
+
...riskRatios,
|
|
145
192
|
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
146
193
|
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
147
194
|
maxConsecutiveWins,
|
|
@@ -240,7 +287,7 @@ var summarizeAiTrainEvaluations = (evaluations) => {
|
|
|
240
287
|
avgApprovedTradesPerDay,
|
|
241
288
|
avgApprovedTradesPerWeek,
|
|
242
289
|
expectancyDelta,
|
|
243
|
-
approvedRisk: summarizeApprovedRisk(evaluations),
|
|
290
|
+
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
244
291
|
qualityBuckets: [...bucketMap.values()].sort(
|
|
245
292
|
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
246
293
|
)
|