@tradejs/cli 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2606 -2171
- package/dist/lib/aiPocketSearch.js +10 -0
- package/dist/lib/aiTrainResearch.js +24 -15
- package/dist/lib/researchAgent.js +16 -2
- package/dist/lib/runtimeLineage.js +24 -15
- package/dist/lib/strategyGateFingerprint.js +26 -15
- package/dist/lib/strategyRelease.js +266 -253
- package/dist/scripts/agentRun.js +50 -45
- package/dist/scripts/aiPocketSearch.js +2269 -1972
- package/dist/scripts/aiTrain.js +24 -15
- package/dist/scripts/backtest.js +2 -2
- package/dist/scripts/replay.js +24 -15
- package/dist/scripts/replayRunner.js +24 -15
- package/dist/scripts/researchAuto.js +2 -2
- package/dist/scripts/runtimeEvidence.js +1 -0
- package/dist/scripts/runtimeScorecard.js +108 -103
- package/dist/scripts/signals.js +35 -17
- package/dist/scripts/signalsDaemon.js +35 -17
- package/dist/scripts/strategyRelease.js +222 -209
- package/dist/scripts/test-ml.js +8 -2
- package/package.json +8 -9
|
@@ -44,11 +44,191 @@ var import_chalk = __toESM(require("chalk"));
|
|
|
44
44
|
var import_node_crypto = require("crypto");
|
|
45
45
|
var import_promises = __toESM(require("fs/promises"));
|
|
46
46
|
var import_node_path = __toESM(require("path"));
|
|
47
|
-
var
|
|
47
|
+
var import_types2 = require("@tradejs/types");
|
|
48
48
|
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
49
|
+
|
|
50
|
+
// src/lib/strategyRelease/liveDiagnosis.ts
|
|
51
|
+
var import_types = require("@tradejs/types");
|
|
52
|
+
var buildStrategyLiveDiagnosis = (input) => {
|
|
53
|
+
const minimumClosedTrades = input.minimumClosedTrades ?? 20;
|
|
54
|
+
const minimumParityRatio = input.minimumParityRatio ?? 0.95;
|
|
55
|
+
const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
|
|
56
|
+
const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
|
|
57
|
+
const hasExplicitRiskScale = input.riskScaleComparable != null;
|
|
58
|
+
const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
|
|
59
|
+
const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
|
|
60
|
+
const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
|
|
61
|
+
const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
|
|
62
|
+
const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
|
|
63
|
+
const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
|
|
64
|
+
const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
|
|
65
|
+
const subtype = attributedVerdict !== "GENERALIZATION_FAILURE" ? null : input.rawCoreExpectancyDelta < 0 && input.aiGateAddedValue >= 0 ? "RAW_CORE_DECAY" : input.aiGateAddedValue < 0 ? "AI_GATE_FAILURE" : input.regimeCoverage < minimumRegimeCoverage ? "REGIME_SHIFT" : (input.overfitProbability ?? 0) >= 0.5 ? "SUSPECTED_HISTORICAL_OVERFIT" : "RAW_CORE_DECAY";
|
|
66
|
+
const explanation = attributedVerdict === "RUNTIME_DIVERGENCE" ? "Runtime cannot be compared economically until lineage, replay parity, and order execution agree with the frozen composition." : attributedVerdict === "EXPECTED_DRAWDOWN" ? "The observed drawdown is within the preregistered equal-length historical drawdown envelope." : attributedVerdict === "GENERALIZATION_FAILURE" ? `Runtime is comparable, but the observed drawdown exceeds the historical envelope (${subtype}).` : attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "The drawdown breaches its historical envelope, but matched shadow raw-core, deterministic-gate, or causal regime evidence is missing for attribution." : "There are not enough comparable closed trades and historical drawdown observations for attribution.";
|
|
67
|
+
const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
|
|
68
|
+
"Resolve lineage and replay parity mismatches before evaluating strategy economics.",
|
|
69
|
+
"Inspect order failures and execution residuals against the frozen execution model."
|
|
70
|
+
] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
|
|
71
|
+
"Keep the frozen composition unchanged and continue prospective evidence collection."
|
|
72
|
+
] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
|
|
73
|
+
"Do not retune the live cohort; open a new immutable research lineage.",
|
|
74
|
+
"Compare raw-core decay, AI-gate added value, and causal regime coverage."
|
|
75
|
+
] : [
|
|
76
|
+
attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "Collect matched shadow raw-core, deterministic-gate, and causal regime evidence without changing the composition." : "Collect more verified prospective outcomes without changing the composition."
|
|
77
|
+
];
|
|
78
|
+
return {
|
|
79
|
+
schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
|
|
80
|
+
strategy: input.strategy,
|
|
81
|
+
compositionId: input.compositionId,
|
|
82
|
+
createdAt: input.createdAt,
|
|
83
|
+
verdict: attributedVerdict,
|
|
84
|
+
subtype,
|
|
85
|
+
confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
|
|
86
|
+
evidence: {
|
|
87
|
+
lineageComparable: input.lineageComparable,
|
|
88
|
+
riskScaleComparable: input.riskScaleComparable ?? false,
|
|
89
|
+
releaseMaxLossValue: input.releaseMaxLossValue ?? null,
|
|
90
|
+
runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
|
|
91
|
+
riskScaleRatio: input.riskScaleRatio ?? null,
|
|
92
|
+
parityRatio: input.parityRatio,
|
|
93
|
+
orderFailureRate: input.orderFailureRate,
|
|
94
|
+
observedDrawdown: input.observedDrawdown,
|
|
95
|
+
normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
|
|
96
|
+
historicalDrawdownP95: input.historicalDrawdownP95,
|
|
97
|
+
historicalDrawdownMaximum: input.historicalDrawdownMaximum,
|
|
98
|
+
closedTrades: input.closedTrades,
|
|
99
|
+
rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
|
|
100
|
+
aiGateAddedValue: input.aiGateAddedValue,
|
|
101
|
+
regimeCoverage: input.regimeCoverage,
|
|
102
|
+
overfitProbability: input.overfitProbability
|
|
103
|
+
},
|
|
104
|
+
explanation,
|
|
105
|
+
recommendations
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/lib/strategyRelease/monitoringProfile.ts
|
|
110
|
+
var DAY_MS = 864e5;
|
|
111
|
+
function assert(condition, message) {
|
|
112
|
+
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
113
|
+
}
|
|
114
|
+
var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
|
|
115
|
+
let equity = 0;
|
|
116
|
+
let peak = 0;
|
|
117
|
+
let maximum = 0;
|
|
118
|
+
for (let index = startIndex; index < endIndex; index += 1) {
|
|
119
|
+
const trade = trades[index];
|
|
120
|
+
equity += trade.netProfit;
|
|
121
|
+
peak = Math.max(peak, equity);
|
|
122
|
+
maximum = Math.max(maximum, peak - equity);
|
|
123
|
+
}
|
|
124
|
+
return maximum;
|
|
125
|
+
};
|
|
126
|
+
var lowerBoundExitTimestamp = (trades, timestamp) => {
|
|
127
|
+
let left = 0;
|
|
128
|
+
let right = trades.length;
|
|
129
|
+
while (left < right) {
|
|
130
|
+
const middle = Math.floor((left + right) / 2);
|
|
131
|
+
if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
|
|
132
|
+
else right = middle;
|
|
133
|
+
}
|
|
134
|
+
return left;
|
|
135
|
+
};
|
|
136
|
+
var percentile = (values, probability) => {
|
|
137
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
138
|
+
const position = (sorted.length - 1) * probability;
|
|
139
|
+
const lower = Math.floor(position);
|
|
140
|
+
const upper = Math.ceil(position);
|
|
141
|
+
if (lower === upper) return sorted[lower];
|
|
142
|
+
return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
|
|
143
|
+
};
|
|
144
|
+
var buildStrategyMonitoringProfile = ({
|
|
145
|
+
trades,
|
|
146
|
+
startTime,
|
|
147
|
+
endTime,
|
|
148
|
+
days,
|
|
149
|
+
minimumProspectiveClosedTrades = 20,
|
|
150
|
+
minimumParityRatio = 0.95,
|
|
151
|
+
maximumOrderFailureRate = 0.05,
|
|
152
|
+
minimumRegimeCoverage = 0.5,
|
|
153
|
+
aiGateExpectancy = null,
|
|
154
|
+
overfitProbability = null
|
|
155
|
+
}) => {
|
|
156
|
+
assert(endTime > startTime, "monitoring profile window is invalid");
|
|
157
|
+
assert(days.length > 0, "monitoring profile days are required");
|
|
158
|
+
const ordered = trades.filter(
|
|
159
|
+
(trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
|
|
160
|
+
).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
|
|
161
|
+
const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
|
|
162
|
+
assert(
|
|
163
|
+
Number.isInteger(windowDays) && windowDays > 0,
|
|
164
|
+
"monitoring profile days must be positive integers"
|
|
165
|
+
);
|
|
166
|
+
const duration = windowDays * DAY_MS;
|
|
167
|
+
assert(
|
|
168
|
+
endTime - startTime >= duration,
|
|
169
|
+
`${windowDays}d monitoring window exceeds historical coverage`
|
|
170
|
+
);
|
|
171
|
+
const drawdowns = [];
|
|
172
|
+
for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
|
|
173
|
+
const windowEnd = windowStart + duration;
|
|
174
|
+
drawdowns.push(
|
|
175
|
+
realizedDrawdown(
|
|
176
|
+
ordered,
|
|
177
|
+
lowerBoundExitTimestamp(ordered, windowStart),
|
|
178
|
+
lowerBoundExitTimestamp(ordered, windowEnd)
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
days: windowDays,
|
|
184
|
+
p95: percentile(drawdowns, 0.95),
|
|
185
|
+
maximum: Math.max(...drawdowns)
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
minimumProspectiveClosedTrades,
|
|
190
|
+
minimumParityRatio,
|
|
191
|
+
maximumOrderFailureRate,
|
|
192
|
+
minimumRegimeCoverage,
|
|
193
|
+
drawdownEnvelopes,
|
|
194
|
+
rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
|
|
195
|
+
aiGateExpectancy,
|
|
196
|
+
overfitProbability
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/lib/strategyRelease/evidenceRetention.ts
|
|
201
|
+
var DAY_MS2 = 864e5;
|
|
202
|
+
var planStrategyEvidenceRetention = ({
|
|
203
|
+
now,
|
|
204
|
+
entries,
|
|
205
|
+
retentionDays = {
|
|
206
|
+
operational_redis: 3,
|
|
207
|
+
verbose_payload: 14,
|
|
208
|
+
verified_runtime_bundle: 90,
|
|
209
|
+
compact_ledger: null
|
|
210
|
+
}
|
|
211
|
+
}) => {
|
|
212
|
+
const keep = [];
|
|
213
|
+
const remove = [];
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
const days = retentionDays[entry.kind];
|
|
216
|
+
if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS2) {
|
|
217
|
+
keep.push(entry);
|
|
218
|
+
} else {
|
|
219
|
+
remove.push(entry);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
keep,
|
|
224
|
+
delete: remove,
|
|
225
|
+
bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/lib/strategyRelease.ts
|
|
49
230
|
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
50
231
|
var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
|
|
51
|
-
var DAY_MS = 864e5;
|
|
52
232
|
var REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
|
|
53
233
|
var REQUIRED_STABLE_TERMINAL_WINDOWS = [365, 180, 90];
|
|
54
234
|
var MIN_RECENT_DIRECTION_REPAIR_TRADES = 20;
|
|
@@ -187,11 +367,11 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
|
187
367
|
};
|
|
188
368
|
var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(value);
|
|
189
369
|
var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence.strategyEvidenceSha256)(value);
|
|
190
|
-
function
|
|
370
|
+
function assert2(condition, message) {
|
|
191
371
|
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
192
372
|
}
|
|
193
|
-
var assertFingerprint = (value, label) =>
|
|
194
|
-
var assertLineageFingerprint = (value, label) =>
|
|
373
|
+
var assertFingerprint = (value, label) => assert2(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
|
|
374
|
+
var assertLineageFingerprint = (value, label) => assert2(
|
|
195
375
|
LINEAGE_FINGERPRINT_RE.test(value),
|
|
196
376
|
`${label} must be a 16-character lowercase lineage fingerprint`
|
|
197
377
|
);
|
|
@@ -239,7 +419,7 @@ var deriveReleaseGates = (evidence) => {
|
|
|
239
419
|
};
|
|
240
420
|
var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
241
421
|
const directionPolicy = composition.directionPolicy ?? "both";
|
|
242
|
-
const requireEqual = (actual, expected, label, artifactId) =>
|
|
422
|
+
const requireEqual = (actual, expected, label, artifactId) => assert2(
|
|
243
423
|
actual === expected,
|
|
244
424
|
`evidence ${artifactId} ${label} does not match the frozen composition`
|
|
245
425
|
);
|
|
@@ -271,7 +451,7 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
271
451
|
"MAX_LOSS_VALUE",
|
|
272
452
|
reference.artifactId
|
|
273
453
|
);
|
|
274
|
-
|
|
454
|
+
assert2(
|
|
275
455
|
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
276
456
|
`evidence ${reference.artifactId} does not include the frozen core export`
|
|
277
457
|
);
|
|
@@ -317,7 +497,7 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
317
497
|
reference.artifactId
|
|
318
498
|
);
|
|
319
499
|
}
|
|
320
|
-
|
|
500
|
+
assert2(
|
|
321
501
|
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
322
502
|
`evidence ${reference.artifactId} does not use the frozen core export`
|
|
323
503
|
);
|
|
@@ -421,60 +601,60 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
421
601
|
}
|
|
422
602
|
};
|
|
423
603
|
var createStrategyReleaseManifest = (input) => {
|
|
424
|
-
|
|
425
|
-
|
|
604
|
+
assert2(input.strategy.trim(), "strategy is required");
|
|
605
|
+
assert2(
|
|
426
606
|
Number.isFinite(input.createdAt),
|
|
427
607
|
"createdAt must be a finite timestamp"
|
|
428
608
|
);
|
|
429
|
-
|
|
609
|
+
assert2(
|
|
430
610
|
input.researchBudget.hypothesisFamilies <= 3,
|
|
431
611
|
"at most 3 hypothesis families are allowed"
|
|
432
612
|
);
|
|
433
|
-
|
|
613
|
+
assert2(
|
|
434
614
|
input.researchBudget.maximumVariantsPerFamily <= 5,
|
|
435
615
|
"at most 5 variants per family are allowed"
|
|
436
616
|
);
|
|
437
|
-
|
|
617
|
+
assert2(
|
|
438
618
|
input.researchBudget.isolatedLongFinalists <= 1,
|
|
439
619
|
"at most one isolated-long finalist is allowed"
|
|
440
620
|
);
|
|
441
|
-
|
|
621
|
+
assert2(
|
|
442
622
|
input.researchBudget.aiGateTuningRounds <= 1,
|
|
443
623
|
"at most one AI-gate tuning round is allowed"
|
|
444
624
|
);
|
|
445
|
-
|
|
446
|
-
|
|
625
|
+
assert2(input.marketWindow.cacheOnly, "market window must be cache-only");
|
|
626
|
+
assert2(
|
|
447
627
|
input.marketWindow.endTime > input.marketWindow.startTime,
|
|
448
628
|
"market window is invalid"
|
|
449
629
|
);
|
|
450
|
-
|
|
451
|
-
|
|
630
|
+
assert2(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
|
|
631
|
+
assert2(
|
|
452
632
|
input.composition.longEnabled && input.composition.shortEnabled,
|
|
453
633
|
"LONG and SHORT must both remain enabled"
|
|
454
634
|
);
|
|
455
|
-
|
|
635
|
+
assert2(
|
|
456
636
|
input.composition.directionPolicy == null || ["both", "long_only", "short_only", "direction_aware"].includes(
|
|
457
637
|
input.composition.directionPolicy
|
|
458
638
|
),
|
|
459
639
|
"composition.directionPolicy is invalid"
|
|
460
640
|
);
|
|
461
|
-
|
|
641
|
+
assert2(
|
|
462
642
|
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
463
643
|
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
464
644
|
);
|
|
465
|
-
|
|
645
|
+
assert2(
|
|
466
646
|
Number.isFinite(input.monitoring.minimumParityRatio) && input.monitoring.minimumParityRatio >= 0 && input.monitoring.minimumParityRatio <= 1,
|
|
467
647
|
"monitoring.minimumParityRatio must be between 0 and 1"
|
|
468
648
|
);
|
|
469
|
-
|
|
649
|
+
assert2(
|
|
470
650
|
Number.isFinite(input.monitoring.maximumOrderFailureRate) && input.monitoring.maximumOrderFailureRate >= 0 && input.monitoring.maximumOrderFailureRate <= 1,
|
|
471
651
|
"monitoring.maximumOrderFailureRate must be between 0 and 1"
|
|
472
652
|
);
|
|
473
|
-
|
|
653
|
+
assert2(
|
|
474
654
|
Number.isFinite(input.monitoring.minimumRegimeCoverage) && input.monitoring.minimumRegimeCoverage >= 0 && input.monitoring.minimumRegimeCoverage <= 1,
|
|
475
655
|
"monitoring.minimumRegimeCoverage must be between 0 and 1"
|
|
476
656
|
);
|
|
477
|
-
|
|
657
|
+
assert2(
|
|
478
658
|
input.monitoring.drawdownEnvelopes.length > 0 && input.monitoring.drawdownEnvelopes.every(
|
|
479
659
|
(entry) => Number.isInteger(entry.days) && entry.days > 0 && Number.isFinite(entry.p95) && Number.isFinite(entry.maximum) && entry.p95 >= 0 && entry.maximum >= entry.p95
|
|
480
660
|
),
|
|
@@ -508,7 +688,7 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
508
688
|
}
|
|
509
689
|
verifyEvidenceComposition(input.strategy, input.composition, input.evidence);
|
|
510
690
|
const evidenceGates = deriveReleaseGates(input.evidence);
|
|
511
|
-
|
|
691
|
+
assert2(
|
|
512
692
|
canonicalStrategyReleaseJson(input.gates) === canonicalStrategyReleaseJson(evidenceGates),
|
|
513
693
|
"release gates must be derived from verified evidence assertions"
|
|
514
694
|
);
|
|
@@ -555,7 +735,7 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
555
735
|
verdict
|
|
556
736
|
};
|
|
557
737
|
return {
|
|
558
|
-
schema:
|
|
738
|
+
schema: import_types2.STRATEGY_RELEASE_SCHEMA,
|
|
559
739
|
releaseId: `${safeSegment(input.strategy, "strategy")}_${compactTimestamp(input.createdAt)}_${strategyReleaseSha256(releaseIdentity).slice(0, 16)}`,
|
|
560
740
|
strategy: input.strategy,
|
|
561
741
|
createdAt: input.createdAt,
|
|
@@ -582,21 +762,21 @@ var createStrategyReleaseEnvelope = (manifest) => ({
|
|
|
582
762
|
var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
583
763
|
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
584
764
|
const envelope = value;
|
|
585
|
-
|
|
765
|
+
assert2(
|
|
586
766
|
envelope?.schema === "tradejs-strategy-release-envelope/v1",
|
|
587
767
|
"release envelope schema mismatch"
|
|
588
768
|
);
|
|
589
|
-
|
|
769
|
+
assert2(
|
|
590
770
|
envelope.releaseId === envelope.manifest?.releaseId,
|
|
591
771
|
"releaseId mismatch"
|
|
592
772
|
);
|
|
593
|
-
|
|
773
|
+
assert2(
|
|
594
774
|
strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
|
|
595
775
|
"release manifest checksum mismatch"
|
|
596
776
|
);
|
|
597
777
|
const manifest = envelope.manifest;
|
|
598
|
-
|
|
599
|
-
manifest.schema ===
|
|
778
|
+
assert2(
|
|
779
|
+
manifest.schema === import_types2.STRATEGY_RELEASE_SCHEMA,
|
|
600
780
|
"manifest schema mismatch"
|
|
601
781
|
);
|
|
602
782
|
const rebuilt = createStrategyReleaseManifest({
|
|
@@ -624,7 +804,7 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
|
624
804
|
summary: manifest.verdict.summary,
|
|
625
805
|
prospective: manifest.prospective
|
|
626
806
|
});
|
|
627
|
-
|
|
807
|
+
assert2(
|
|
628
808
|
canonicalStrategyReleaseJson(rebuilt) === canonicalStrategyReleaseJson(manifest),
|
|
629
809
|
"release manifest contains self-declared or inconsistent derived fields"
|
|
630
810
|
);
|
|
@@ -739,147 +919,6 @@ var publishStrategyRelease = async ({
|
|
|
739
919
|
]);
|
|
740
920
|
return { releasePath, markerPath, envelope, markerEnvelope };
|
|
741
921
|
};
|
|
742
|
-
var buildStrategyLiveDiagnosis = (input) => {
|
|
743
|
-
const minimumClosedTrades = input.minimumClosedTrades ?? 20;
|
|
744
|
-
const minimumParityRatio = input.minimumParityRatio ?? 0.95;
|
|
745
|
-
const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
|
|
746
|
-
const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
|
|
747
|
-
const hasExplicitRiskScale = input.riskScaleComparable != null;
|
|
748
|
-
const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
|
|
749
|
-
const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
|
|
750
|
-
const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
|
|
751
|
-
const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
|
|
752
|
-
const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
|
|
753
|
-
const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
|
|
754
|
-
const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
|
|
755
|
-
const subtype = attributedVerdict !== "GENERALIZATION_FAILURE" ? null : input.rawCoreExpectancyDelta < 0 && input.aiGateAddedValue >= 0 ? "RAW_CORE_DECAY" : input.aiGateAddedValue < 0 ? "AI_GATE_FAILURE" : input.regimeCoverage < minimumRegimeCoverage ? "REGIME_SHIFT" : (input.overfitProbability ?? 0) >= 0.5 ? "SUSPECTED_HISTORICAL_OVERFIT" : "RAW_CORE_DECAY";
|
|
756
|
-
const explanation = attributedVerdict === "RUNTIME_DIVERGENCE" ? "Runtime cannot be compared economically until lineage, replay parity, and order execution agree with the frozen composition." : attributedVerdict === "EXPECTED_DRAWDOWN" ? "The observed drawdown is within the preregistered equal-length historical drawdown envelope." : attributedVerdict === "GENERALIZATION_FAILURE" ? `Runtime is comparable, but the observed drawdown exceeds the historical envelope (${subtype}).` : attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "The drawdown breaches its historical envelope, but matched shadow raw-core, deterministic-gate, or causal regime evidence is missing for attribution." : "There are not enough comparable closed trades and historical drawdown observations for attribution.";
|
|
757
|
-
const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
|
|
758
|
-
"Resolve lineage and replay parity mismatches before evaluating strategy economics.",
|
|
759
|
-
"Inspect order failures and execution residuals against the frozen execution model."
|
|
760
|
-
] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
|
|
761
|
-
"Keep the frozen composition unchanged and continue prospective evidence collection."
|
|
762
|
-
] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
|
|
763
|
-
"Do not retune the live cohort; open a new immutable research lineage.",
|
|
764
|
-
"Compare raw-core decay, AI-gate added value, and causal regime coverage."
|
|
765
|
-
] : [
|
|
766
|
-
attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "Collect matched shadow raw-core, deterministic-gate, and causal regime evidence without changing the composition." : "Collect more verified prospective outcomes without changing the composition."
|
|
767
|
-
];
|
|
768
|
-
return {
|
|
769
|
-
schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
|
|
770
|
-
strategy: input.strategy,
|
|
771
|
-
compositionId: input.compositionId,
|
|
772
|
-
createdAt: input.createdAt,
|
|
773
|
-
verdict: attributedVerdict,
|
|
774
|
-
subtype,
|
|
775
|
-
confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
|
|
776
|
-
evidence: {
|
|
777
|
-
lineageComparable: input.lineageComparable,
|
|
778
|
-
riskScaleComparable: input.riskScaleComparable ?? false,
|
|
779
|
-
releaseMaxLossValue: input.releaseMaxLossValue ?? null,
|
|
780
|
-
runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
|
|
781
|
-
riskScaleRatio: input.riskScaleRatio ?? null,
|
|
782
|
-
parityRatio: input.parityRatio,
|
|
783
|
-
orderFailureRate: input.orderFailureRate,
|
|
784
|
-
observedDrawdown: input.observedDrawdown,
|
|
785
|
-
normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
|
|
786
|
-
historicalDrawdownP95: input.historicalDrawdownP95,
|
|
787
|
-
historicalDrawdownMaximum: input.historicalDrawdownMaximum,
|
|
788
|
-
closedTrades: input.closedTrades,
|
|
789
|
-
rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
|
|
790
|
-
aiGateAddedValue: input.aiGateAddedValue,
|
|
791
|
-
regimeCoverage: input.regimeCoverage,
|
|
792
|
-
overfitProbability: input.overfitProbability
|
|
793
|
-
},
|
|
794
|
-
explanation,
|
|
795
|
-
recommendations
|
|
796
|
-
};
|
|
797
|
-
};
|
|
798
|
-
var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
|
|
799
|
-
let equity = 0;
|
|
800
|
-
let peak = 0;
|
|
801
|
-
let maximum = 0;
|
|
802
|
-
for (let index = startIndex; index < endIndex; index += 1) {
|
|
803
|
-
const trade = trades[index];
|
|
804
|
-
equity += trade.netProfit;
|
|
805
|
-
peak = Math.max(peak, equity);
|
|
806
|
-
maximum = Math.max(maximum, peak - equity);
|
|
807
|
-
}
|
|
808
|
-
return maximum;
|
|
809
|
-
};
|
|
810
|
-
var lowerBoundExitTimestamp = (trades, timestamp) => {
|
|
811
|
-
let left = 0;
|
|
812
|
-
let right = trades.length;
|
|
813
|
-
while (left < right) {
|
|
814
|
-
const middle = Math.floor((left + right) / 2);
|
|
815
|
-
if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
|
|
816
|
-
else right = middle;
|
|
817
|
-
}
|
|
818
|
-
return left;
|
|
819
|
-
};
|
|
820
|
-
var percentile = (values, probability) => {
|
|
821
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
822
|
-
const position = (sorted.length - 1) * probability;
|
|
823
|
-
const lower = Math.floor(position);
|
|
824
|
-
const upper = Math.ceil(position);
|
|
825
|
-
if (lower === upper) return sorted[lower];
|
|
826
|
-
return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
|
|
827
|
-
};
|
|
828
|
-
var buildStrategyMonitoringProfile = ({
|
|
829
|
-
trades,
|
|
830
|
-
startTime,
|
|
831
|
-
endTime,
|
|
832
|
-
days,
|
|
833
|
-
minimumProspectiveClosedTrades = 20,
|
|
834
|
-
minimumParityRatio = 0.95,
|
|
835
|
-
maximumOrderFailureRate = 0.05,
|
|
836
|
-
minimumRegimeCoverage = 0.5,
|
|
837
|
-
aiGateExpectancy = null,
|
|
838
|
-
overfitProbability = null
|
|
839
|
-
}) => {
|
|
840
|
-
assert(endTime > startTime, "monitoring profile window is invalid");
|
|
841
|
-
assert(days.length > 0, "monitoring profile days are required");
|
|
842
|
-
const ordered = trades.filter(
|
|
843
|
-
(trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
|
|
844
|
-
).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
|
|
845
|
-
const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
|
|
846
|
-
assert(
|
|
847
|
-
Number.isInteger(windowDays) && windowDays > 0,
|
|
848
|
-
"monitoring profile days must be positive integers"
|
|
849
|
-
);
|
|
850
|
-
const duration = windowDays * DAY_MS;
|
|
851
|
-
assert(
|
|
852
|
-
endTime - startTime >= duration,
|
|
853
|
-
`${windowDays}d monitoring window exceeds historical coverage`
|
|
854
|
-
);
|
|
855
|
-
const drawdowns = [];
|
|
856
|
-
for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
|
|
857
|
-
const windowEnd = windowStart + duration;
|
|
858
|
-
drawdowns.push(
|
|
859
|
-
realizedDrawdown(
|
|
860
|
-
ordered,
|
|
861
|
-
lowerBoundExitTimestamp(ordered, windowStart),
|
|
862
|
-
lowerBoundExitTimestamp(ordered, windowEnd)
|
|
863
|
-
)
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
return {
|
|
867
|
-
days: windowDays,
|
|
868
|
-
p95: percentile(drawdowns, 0.95),
|
|
869
|
-
maximum: Math.max(...drawdowns)
|
|
870
|
-
};
|
|
871
|
-
});
|
|
872
|
-
return {
|
|
873
|
-
minimumProspectiveClosedTrades,
|
|
874
|
-
minimumParityRatio,
|
|
875
|
-
maximumOrderFailureRate,
|
|
876
|
-
minimumRegimeCoverage,
|
|
877
|
-
drawdownEnvelopes,
|
|
878
|
-
rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
|
|
879
|
-
aiGateExpectancy,
|
|
880
|
-
overfitProbability
|
|
881
|
-
};
|
|
882
|
-
};
|
|
883
922
|
var publishStrategyLiveDiagnosis = async ({
|
|
884
923
|
rootDir,
|
|
885
924
|
diagnosis,
|
|
@@ -963,32 +1002,6 @@ var publishStrategyLiveDiagnosis = async ({
|
|
|
963
1002
|
await verifyStrategyEvidenceMarkerEnvelope(markerPath);
|
|
964
1003
|
return { diagnosisPath, markerPath, envelope, markerEnvelope };
|
|
965
1004
|
};
|
|
966
|
-
var planStrategyEvidenceRetention = ({
|
|
967
|
-
now,
|
|
968
|
-
entries,
|
|
969
|
-
retentionDays = {
|
|
970
|
-
operational_redis: 3,
|
|
971
|
-
verbose_payload: 14,
|
|
972
|
-
verified_runtime_bundle: 90,
|
|
973
|
-
compact_ledger: null
|
|
974
|
-
}
|
|
975
|
-
}) => {
|
|
976
|
-
const keep = [];
|
|
977
|
-
const remove = [];
|
|
978
|
-
for (const entry of entries) {
|
|
979
|
-
const days = retentionDays[entry.kind];
|
|
980
|
-
if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS) {
|
|
981
|
-
keep.push(entry);
|
|
982
|
-
} else {
|
|
983
|
-
remove.push(entry);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
keep,
|
|
988
|
-
delete: remove,
|
|
989
|
-
bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
|
|
990
|
-
};
|
|
991
|
-
};
|
|
992
1005
|
var finiteString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
993
1006
|
var finiteNumber = (value) => {
|
|
994
1007
|
const number = Number(value);
|
|
@@ -1164,39 +1177,39 @@ var verifyCompletedCoreResearchBundle = async ({
|
|
|
1164
1177
|
}) => {
|
|
1165
1178
|
const researchId = finiteString(artifact.researchId);
|
|
1166
1179
|
const specSha256 = finiteString(artifact.specSha256);
|
|
1167
|
-
|
|
1168
|
-
|
|
1180
|
+
assert2(researchId != null, "core result researchId is required");
|
|
1181
|
+
assert2(specSha256 != null, "core result specSha256 is required");
|
|
1169
1182
|
const researchDir = import_node_path.default.dirname(resultPath);
|
|
1170
1183
|
const manifestPath = import_node_path.default.join(researchDir, "manifest.json");
|
|
1171
1184
|
const specPath = import_node_path.default.join(researchDir, "spec.json");
|
|
1172
1185
|
const manifestBytes = await import_promises.default.readFile(manifestPath).catch(() => null);
|
|
1173
1186
|
const specBytes = await import_promises.default.readFile(specPath).catch(() => null);
|
|
1174
|
-
|
|
1187
|
+
assert2(
|
|
1175
1188
|
manifestBytes != null && specBytes != null,
|
|
1176
1189
|
"core result must belong to a completed core research bundle"
|
|
1177
1190
|
);
|
|
1178
1191
|
const manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
1179
1192
|
const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
|
|
1180
1193
|
const parsedSpec = JSON.parse(specBytes.toString("utf8"));
|
|
1181
|
-
|
|
1194
|
+
assert2(
|
|
1182
1195
|
manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
|
|
1183
1196
|
"core result is not bound by its completed core research manifest"
|
|
1184
1197
|
);
|
|
1185
1198
|
for (const [relativePath, expectedSha256] of Object.entries(
|
|
1186
1199
|
artifactHashes ?? {}
|
|
1187
1200
|
)) {
|
|
1188
|
-
|
|
1201
|
+
assert2(
|
|
1189
1202
|
!import_node_path.default.isAbsolute(relativePath) && !relativePath.split(/[\\/]/).includes(".."),
|
|
1190
1203
|
`core bundle artifact ${relativePath} escapes its research directory`
|
|
1191
1204
|
);
|
|
1192
|
-
|
|
1205
|
+
assert2(
|
|
1193
1206
|
typeof expectedSha256 === "string" && SHA256_RE.test(expectedSha256),
|
|
1194
1207
|
`core bundle artifact ${relativePath} has an invalid checksum`
|
|
1195
1208
|
);
|
|
1196
1209
|
const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(
|
|
1197
1210
|
import_node_path.default.join(researchDir, relativePath)
|
|
1198
1211
|
);
|
|
1199
|
-
|
|
1212
|
+
assert2(
|
|
1200
1213
|
actualSha256 === expectedSha256,
|
|
1201
1214
|
`core bundle artifact ${relativePath} checksum mismatch`
|
|
1202
1215
|
);
|
|
@@ -1206,7 +1219,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1206
1219
|
return Promise.all(
|
|
1207
1220
|
references.map(async (reference) => {
|
|
1208
1221
|
const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(reference.path);
|
|
1209
|
-
|
|
1222
|
+
assert2(
|
|
1210
1223
|
actualSha256 === reference.sha256,
|
|
1211
1224
|
`evidence ${reference.artifactId} checksum mismatch`
|
|
1212
1225
|
);
|
|
@@ -1215,7 +1228,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1215
1228
|
const parsed = JSON.parse(
|
|
1216
1229
|
await import_promises.default.readFile(reference.path, "utf8")
|
|
1217
1230
|
);
|
|
1218
|
-
|
|
1231
|
+
assert2(
|
|
1219
1232
|
parsed != null && typeof parsed === "object" && !Array.isArray(parsed),
|
|
1220
1233
|
`evidence ${reference.artifactId} must be a JSON object`
|
|
1221
1234
|
);
|
|
@@ -1228,7 +1241,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1228
1241
|
const nested = (value) => value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1229
1242
|
const array = (value) => Array.isArray(value) ? value : [];
|
|
1230
1243
|
const semanticallyValid = reference.kind === "core_research" ? artifact.schema === "tradejs-core-research-result/v1" || artifact.schema === "tradejs-core-research-manifest/v1" && artifact.status === "completed" : reference.kind === "ai_gate" ? nested(artifact.run)?.mode === "local-deterministic" && nested(artifact.research)?.lineage != null && nested(artifact.outcome) != null : reference.kind === "runtime_parity" ? artifact.reportType === "replay-runtime-evidence" && nested(nested(artifact.replay)?.runtimeComparison) != null : reference.kind === "execution_calibration" ? artifact.reportType === "execution-calibration" && nested(artifact.summary) != null : reference.kind === "runtime_evidence" ? artifact.reportType === "runtime-evidence" : artifact.reportType === "runtime-scorecard";
|
|
1231
|
-
|
|
1244
|
+
assert2(
|
|
1232
1245
|
semanticallyValid,
|
|
1233
1246
|
`evidence ${reference.artifactId} does not match ${reference.kind}`
|
|
1234
1247
|
);
|
package/dist/scripts/test-ml.js
CHANGED
|
@@ -26,7 +26,7 @@ module.exports = __toCommonJS(test_ml_exports);
|
|
|
26
26
|
var import_config = require("dotenv/config");
|
|
27
27
|
var import_constants = require("@tradejs/core/constants");
|
|
28
28
|
var import_ml = require("@tradejs/infra/ml");
|
|
29
|
-
var
|
|
29
|
+
var import_registry = require("@tradejs/node/registry");
|
|
30
30
|
var now = Date.now();
|
|
31
31
|
var INTERVAL_MIN = 15;
|
|
32
32
|
var CANDLES = import_constants.ML_BASE_CANDLES_WINDOW;
|
|
@@ -70,6 +70,12 @@ var buildTrendline = (entryTs, price) => ({
|
|
|
70
70
|
]
|
|
71
71
|
});
|
|
72
72
|
var main = async () => {
|
|
73
|
+
const defaultConfig = await (0, import_registry.getStrategyDefaults)("TrendLine", process.cwd());
|
|
74
|
+
if (!defaultConfig) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
"TrendLine is not registered by the current tradejs.config.ts"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
73
79
|
const startTs = now - CANDLES * INTERVAL_MIN * 6e4;
|
|
74
80
|
const candles = makeCandles(120, startTs);
|
|
75
81
|
const btcCandles = makeCandles(42e3, startTs);
|
|
@@ -120,7 +126,7 @@ var main = async () => {
|
|
|
120
126
|
volume24h: makeSeries(10, 8e3, 200)
|
|
121
127
|
}
|
|
122
128
|
};
|
|
123
|
-
const { TRENDLINE, HIGHS, LOWS, ML_THRESHOLD } =
|
|
129
|
+
const { TRENDLINE, HIGHS, LOWS, ML_THRESHOLD } = defaultConfig;
|
|
124
130
|
const fullRow = (0, import_ml.buildMlTrainingRow)(
|
|
125
131
|
{
|
|
126
132
|
signal,
|