create-tradejs 3.1.24 → 3.1.25

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.
@@ -0,0 +1,223 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdtemp, readFile, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import test from 'node:test';
7
+ import {
8
+ generateFinalCompositionBoard,
9
+ validateBoardSpec,
10
+ } from './final-composition-board.mjs';
11
+
12
+ const hash = (value) => createHash('sha256').update(value).digest('hex');
13
+ const sha = (character) => character.repeat(64);
14
+
15
+ const makeCandidate = ({
16
+ id,
17
+ role,
18
+ label,
19
+ color,
20
+ pnl,
21
+ trades,
22
+ drawdown,
23
+ artifactSha,
24
+ }) => ({
25
+ id,
26
+ role,
27
+ label,
28
+ status: 'eligible',
29
+ color,
30
+ riskUnit: 10,
31
+ composition: {
32
+ kind: 'core+deterministic-gate',
33
+ gateSource: role === 'baseline' ? 'current' : 'variant',
34
+ coreResearchId: `${id}-core`,
35
+ coreConfigSha256: sha('a'),
36
+ coreResult: { path: `${id}-core.json`, sha256: artifactSha },
37
+ coreExport: { path: `${id}-export.jsonl`, sha256: artifactSha },
38
+ gateReport: { path: `${id}-gate.json`, sha256: artifactSha },
39
+ ...(role === 'baseline'
40
+ ? {
41
+ gateAuthority: {
42
+ path: `${id}-authority.json`,
43
+ sha256: artifactSha,
44
+ },
45
+ }
46
+ : {}),
47
+ gateFingerprint: sha('b'),
48
+ configFingerprint: sha('c'),
49
+ contextFingerprint: sha('d'),
50
+ directionPolicy: 'both',
51
+ minQuality: 4,
52
+ },
53
+ metrics: {
54
+ trades,
55
+ pnl,
56
+ profitFactor: 1.5,
57
+ maxDrawdown: drawdown,
58
+ },
59
+ terminal: [
60
+ { days: 365, trades: Math.min(trades, 20), pnl: pnl * 0.5 },
61
+ { days: 180, trades: Math.min(trades, 12), pnl: pnl * 0.3 },
62
+ { days: 90, trades: Math.min(trades, 8), pnl: pnl * 0.2 },
63
+ { days: 30, trades: Math.min(trades, 3), pnl: pnl * 0.1 },
64
+ { days: 7, trades: Math.min(trades, 1), pnl: pnl * 0.05 },
65
+ ],
66
+ equity: [
67
+ [1_700_000_000_000, 0],
68
+ [1_710_000_000_000, pnl],
69
+ ],
70
+ });
71
+
72
+ const makeSpec = (artifactSha) => ({
73
+ schema: 'tradejs-final-composition-board/v1',
74
+ strategy: 'ExampleStrategy',
75
+ researchId: 'example-20260825-v1',
76
+ title: 'ExampleStrategy final compositions',
77
+ subtitle: 'Common cache-only comparison window',
78
+ baselineId: 'baseline',
79
+ selectedId: 'candidate',
80
+ comparisonWindow: { start: 1_699_000_000_000, end: 1_711_000_000_000 },
81
+ normalization: { pnlUnit: 'research PnL', maxLossValue: 10 },
82
+ limitations: ['Untouched test support is small'],
83
+ candidates: [
84
+ makeCandidate({
85
+ id: 'baseline',
86
+ role: 'baseline',
87
+ label: 'baseline + own gate',
88
+ color: '#315f7d',
89
+ pnl: 100,
90
+ trades: 20,
91
+ drawdown: 30,
92
+ artifactSha,
93
+ }),
94
+ makeCandidate({
95
+ id: 'candidate',
96
+ role: 'candidate',
97
+ label: 'candidate + own gate',
98
+ color: '#d36b2c',
99
+ pnl: 160,
100
+ trades: 28,
101
+ drawdown: 26,
102
+ artifactSha,
103
+ }),
104
+ ],
105
+ });
106
+
107
+ test('rejects a raw-core row without a candidate-specific deterministic gate', () => {
108
+ const spec = makeSpec(sha('e'));
109
+ spec.candidates[1].composition.kind = 'raw-core';
110
+ assert.throws(
111
+ () => validateBoardSpec(spec),
112
+ /composition\.kind must be core\+deterministic-gate/u,
113
+ );
114
+ });
115
+
116
+ test('requires production core + current AI-gate as the baseline', () => {
117
+ const spec = makeSpec(sha('e'));
118
+ spec.candidates[0].composition.gateSource = 'variant';
119
+ delete spec.candidates[0].composition.gateAuthority;
120
+ assert.throws(
121
+ () => validateBoardSpec(spec),
122
+ /baseline must use the current AI-gate/u,
123
+ );
124
+ });
125
+
126
+ test('requires the fixed terminal chart windows', () => {
127
+ const spec = makeSpec(sha('e'));
128
+ spec.candidates[1].terminal.pop();
129
+ assert.throws(
130
+ () => validateBoardSpec(spec),
131
+ /must contain exactly 365d, 180d, 90d, 30d, and 7d/u,
132
+ );
133
+ });
134
+
135
+ test('allows the current production baseline to remain selected', () => {
136
+ const spec = makeSpec(sha('e'));
137
+ spec.selectedId = 'baseline';
138
+ spec.terminalComparisonIds = ['candidate'];
139
+ const board = validateBoardSpec(spec);
140
+ assert.equal(board.selectedId, 'baseline');
141
+ assert.deepEqual(board.terminalComparisonIds, ['candidate']);
142
+ });
143
+
144
+ test('verifies candidate artifacts and renders the dashboard and equity board', async () => {
145
+ const root = await mkdtemp(path.join(os.tmpdir(), 'tradejs-final-board-'));
146
+ const contents = 'immutable-evidence\n';
147
+ const artifactSha = hash(contents);
148
+ for (const id of ['baseline', 'candidate']) {
149
+ for (const suffix of ['core.json', 'export.jsonl', 'gate.json']) {
150
+ await writeFile(path.join(root, `${id}-${suffix}`), contents, 'utf8');
151
+ }
152
+ }
153
+ await writeFile(path.join(root, 'baseline-authority.json'), contents, 'utf8');
154
+ const { summary } = await generateFinalCompositionBoard({
155
+ spec: makeSpec(artifactSha),
156
+ artifactRoot: root,
157
+ outDir: path.join(root, 'charts'),
158
+ });
159
+ assert.equal(summary.candidates.length, 2);
160
+ assert.match(summary.candidates[1].compositionFingerprint, /^[a-f0-9]{64}$/u);
161
+ assert.deepEqual(Object.keys(summary.artifacts).sort(), [
162
+ 'final-composition-dashboard.png',
163
+ 'final-composition-dashboard.svg',
164
+ 'final-composition-equity.png',
165
+ 'final-composition-equity.svg',
166
+ ]);
167
+ const dashboard = await readFile(
168
+ path.join(root, 'charts', 'final-composition-dashboard.svg'),
169
+ 'utf8',
170
+ );
171
+ const equity = await readFile(
172
+ path.join(root, 'charts', 'final-composition-equity.svg'),
173
+ 'utf8',
174
+ );
175
+ assert.match(dashboard, /PnL in terminal windows/u);
176
+ assert.match(dashboard, /Final compositions: PnL ↔ drawdown/u);
177
+ assert.match(equity, /production core \+ current AI-gate/u);
178
+ assert.match(equity, /candidate \+ own gate/u);
179
+ });
180
+
181
+ test('renders an additional terminal comparison without changing selectedId', async () => {
182
+ const root = await mkdtemp(path.join(os.tmpdir(), 'tradejs-final-board-'));
183
+ const contents = 'immutable-evidence\n';
184
+ const artifactSha = hash(contents);
185
+ const spec = makeSpec(artifactSha);
186
+ spec.candidates.push(
187
+ makeCandidate({
188
+ id: 'transition',
189
+ role: 'candidate',
190
+ label: 'Transition breakout + own gate',
191
+ color: '#a82f2f',
192
+ pnl: 155,
193
+ trades: 24,
194
+ drawdown: 22,
195
+ artifactSha,
196
+ }),
197
+ );
198
+ spec.terminalComparisonIds = ['candidate', 'transition'];
199
+ for (const id of ['baseline', 'candidate', 'transition']) {
200
+ for (const suffix of ['core.json', 'export.jsonl', 'gate.json']) {
201
+ await writeFile(path.join(root, `${id}-${suffix}`), contents, 'utf8');
202
+ }
203
+ }
204
+ await writeFile(path.join(root, 'baseline-authority.json'), contents, 'utf8');
205
+
206
+ const { summary } = await generateFinalCompositionBoard({
207
+ spec,
208
+ artifactRoot: root,
209
+ outDir: path.join(root, 'charts'),
210
+ png: false,
211
+ });
212
+ const dashboard = await readFile(
213
+ path.join(root, 'charts', 'final-composition-dashboard.svg'),
214
+ 'utf8',
215
+ );
216
+
217
+ assert.equal(summary.selectedId, 'candidate');
218
+ assert.deepEqual(summary.terminalComparisonIds, ['candidate', 'transition']);
219
+ assert.match(dashboard, /data-terminal-series="baseline"/u);
220
+ assert.match(dashboard, /data-terminal-series="candidate"/u);
221
+ assert.match(dashboard, /data-terminal-series="transition"/u);
222
+ assert.match(dashboard, /Transition breakout \+ own gate/u);
223
+ });
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import fsp from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+
8
+ const usage = `Usage:
9
+ node freeze-gate-variants.mjs --pocket <report.json> --candidateId <id> --output <spec.json> [--limit <1..5>]
10
+ `;
11
+
12
+ const parseArgs = (argv) => {
13
+ const options = { limit: 5 };
14
+ for (let index = 0; index < argv.length; index += 1) {
15
+ const argument = argv[index];
16
+ if (argument === '--help') return { help: true };
17
+ if (!argument.startsWith('--')) throw new Error(`Unexpected argument: ${argument}`);
18
+ const name = argument.slice(2);
19
+ const value = argv[++index];
20
+ if (!value || value.startsWith('--')) throw new Error(`Missing value for ${argument}`);
21
+ if (name === 'limit') options.limit = Number(value);
22
+ else if (['pocket', 'candidateId', 'output'].includes(name)) options[name] = value;
23
+ else throw new Error(`Unknown option: ${argument}`);
24
+ }
25
+ if (!options.pocket || !options.candidateId || !options.output) {
26
+ throw new Error('Required: --pocket, --candidateId, and --output');
27
+ }
28
+ if (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 5) {
29
+ throw new Error('--limit must be an integer from 1 through 5');
30
+ }
31
+ return options;
32
+ };
33
+
34
+ const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
35
+
36
+ export const buildFrozenGateSpec = ({ report, candidateId, limit, sourcePath, sourceSha256 }) => {
37
+ if (report?.run?.featurePolicy !== 'causal-stationary') {
38
+ throw new Error('Pocket report must use featurePolicy=causal-stationary');
39
+ }
40
+ const pockets = report?.pocketSearch?.positivePockets;
41
+ if (!Array.isArray(pockets) || pockets.length === 0) {
42
+ throw new Error('Pocket report has no positive pockets to freeze');
43
+ }
44
+ const variants = pockets.slice(0, limit).map((pocket, index) => {
45
+ const predicates = pocket?.predicates;
46
+ if (!Array.isArray(predicates) || predicates.length === 0) {
47
+ throw new Error(`Pocket ${index + 1} has no predicates`);
48
+ }
49
+ return {
50
+ name: `${candidateId}-own-gate-${index + 1}`,
51
+ mode: 'replace',
52
+ quality: 4,
53
+ expression: predicates.map((predicate) => predicate.label).join(' && '),
54
+ discovery: {
55
+ rank: index + 1,
56
+ support: pocket.summary?.support ?? null,
57
+ events: pocket.summary?.events ?? null,
58
+ },
59
+ };
60
+ });
61
+ return {
62
+ schema: 'tradejs-candidate-gate-variants/v1',
63
+ candidateId,
64
+ sourcePocketReport: { path: sourcePath, sha256: sourceSha256 },
65
+ discovery: {
66
+ featurePolicy: report.run.featurePolicy,
67
+ until: report.run.until,
68
+ trainRows: report.run.trainRows,
69
+ validationRows: report.run.validationRows,
70
+ testRows: report.run.testRows,
71
+ },
72
+ variants,
73
+ };
74
+ };
75
+
76
+ export const main = async (argv = process.argv.slice(2)) => {
77
+ const options = parseArgs(argv);
78
+ if (options.help) {
79
+ process.stdout.write(usage);
80
+ return;
81
+ }
82
+ const pocketPath = path.resolve(options.pocket);
83
+ const outputPath = path.resolve(options.output);
84
+ const pocketText = await fsp.readFile(pocketPath, 'utf8');
85
+ const spec = buildFrozenGateSpec({
86
+ report: JSON.parse(pocketText),
87
+ candidateId: options.candidateId,
88
+ limit: options.limit,
89
+ sourcePath: options.pocket,
90
+ sourceSha256: sha256(pocketText),
91
+ });
92
+ await fsp.mkdir(path.dirname(outputPath), { recursive: true });
93
+ const output = `${JSON.stringify(spec, null, 2)}\n`;
94
+ await fsp.writeFile(outputPath, output, 'utf8');
95
+ process.stdout.write(`${sha256(output)} ${options.output}\n`);
96
+ };
97
+
98
+ if (pathToFileURL(path.resolve(process.argv[1] ?? '')).href === import.meta.url) {
99
+ main().catch((error) => {
100
+ console.error(error instanceof Error ? error.stack : String(error));
101
+ process.exitCode = 1;
102
+ });
103
+ }
@@ -0,0 +1,52 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { buildFrozenGateSpec } from './freeze-gate-variants.mjs';
5
+
6
+ test('freezes no more than five causal replacement gates', () => {
7
+ const pocket = (index) => ({
8
+ predicates: [
9
+ { label: `feature.value >= ${index}` },
10
+ { label: 'derived.direction == SHORT' },
11
+ ],
12
+ summary: { support: 25 + index, events: 25 },
13
+ });
14
+ const spec = buildFrozenGateSpec({
15
+ report: {
16
+ run: {
17
+ featurePolicy: 'causal-stationary',
18
+ until: 1,
19
+ trainRows: 100,
20
+ validationRows: 0,
21
+ testRows: 0,
22
+ },
23
+ pocketSearch: { positivePockets: Array.from({ length: 6 }, (_, index) => pocket(index)) },
24
+ },
25
+ candidateId: 'candidate-a',
26
+ limit: 5,
27
+ sourcePath: 'pocket.json',
28
+ sourceSha256: 'a'.repeat(64),
29
+ });
30
+
31
+ assert.equal(spec.variants.length, 5);
32
+ assert.equal(spec.variants[0].mode, 'replace');
33
+ assert.equal(spec.variants[0].quality, 4);
34
+ assert.equal(
35
+ spec.variants[0].expression,
36
+ 'feature.value >= 0 && derived.direction == SHORT',
37
+ );
38
+ });
39
+
40
+ test('rejects pocket reports with a non-causal feature policy', () => {
41
+ assert.throws(
42
+ () =>
43
+ buildFrozenGateSpec({
44
+ report: { run: { featurePolicy: 'all' }, pocketSearch: { positivePockets: [{}] } },
45
+ candidateId: 'candidate-a',
46
+ limit: 1,
47
+ sourcePath: 'pocket.json',
48
+ sourceSha256: 'a'.repeat(64),
49
+ }),
50
+ /causal-stationary/,
51
+ );
52
+ });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "tradejs-skill-bundle/v1",
3
3
  "source": "TradeJS-Dev/TradeJS:.codex/skills",
4
- "bundleSha256": "f058077d43fc342e82062908fa7512b74981c2bee878901508dd8d3fdba32e23",
4
+ "bundleSha256": "30e3254a3cc0e82d8625b754897f226d23861d2357e23196197f69bfa6a568ee",
5
5
  "skills": [
6
6
  "ai-train-local-research",
7
7
  "backtest-config-redis",
@@ -19,10 +19,10 @@
19
19
  "strategy-release"
20
20
  ],
21
21
  "files": {
22
- ".codex/skills/ai-train-local-research/references/gate-ablation.md": "5bd50a225864b518c1ca3400716c384f289899f8b81dd33df6aee3dc2998ff0b",
22
+ ".codex/skills/ai-train-local-research/references/gate-ablation.md": "a1011bf0c9fcccf1692983364da168a1bac6d044b24ed3bfd7cb44e4b0ca9ed5",
23
23
  ".codex/skills/ai-train-local-research/references/reporting.md": "a5a5cc6438a8cc228560e302e944f1ff320f4c963988af83e1ba1443728f7149",
24
- ".codex/skills/ai-train-local-research/scripts/ai-gate-ablation.mjs": "b3a4bceff25219644e96d5309d3c11c59761b9cd7d7096ee701f95654853cfa8",
25
- ".codex/skills/ai-train-local-research/scripts/ai-gate-ablation.test.mjs": "dc5128b8743f67cc496a296d52a21ec3cc139308ffc80eac07cf3a77dc4c78bf",
24
+ ".codex/skills/ai-train-local-research/scripts/ai-gate-ablation.mjs": "494dea5fc2ef02e4ab07a9aa82b8d531e576975b12bb0723f6fa0403db2e3a56",
25
+ ".codex/skills/ai-train-local-research/scripts/ai-gate-ablation.test.mjs": "c7e61f5f1a607e88a9e371fade9822fa68503575cbc718c7d437b0d681691ed8",
26
26
  ".codex/skills/ai-train-local-research/SKILL.md": "479e44aad9cb6ab6b5c8931c807c63cf2a1938a59d01f553c20f44645716a23a",
27
27
  ".codex/skills/backtest-config-redis/scripts/get_backtest_config.sh": "833e950d6348c5b7a4bd3f00d25af60f6394190bff744bd5ab64db9e43d826d5",
28
28
  ".codex/skills/backtest-config-redis/SKILL.md": "85e7b1fa425aa23dfa7950de05c1d3ce046d044dc3872f3665705e9e2f2a1977",
@@ -35,13 +35,20 @@
35
35
  ".codex/skills/strategy-backtest-research/scripts/fast-ai-export-metrics.mjs": "1d1747d34269ad15a610dcd687334c3d47ed9460d891285ad1dd851c7658d1af",
36
36
  ".codex/skills/strategy-backtest-research/scripts/fast-ai-export-metrics.test.mjs": "4caab52646504afd2a6f1e4ba9f43d6b07d714baca89d2a2010545fb4210a9da",
37
37
  ".codex/skills/strategy-backtest-research/scripts/research-notes-check.mjs": "435aa6560abcf4e8a60fb86230d8d8de2f2dc9e4c52acc7a272ce30287d40325",
38
- ".codex/skills/strategy-backtest-research/SKILL.md": "4acc57b255cfe8b16a2b2bf0af0c8ee50e79e2bcea3f8045d422342007b8a779",
38
+ ".codex/skills/strategy-backtest-research/SKILL.md": "526197e5f196d80dc4f4cc3688d2e0d232eb010267c53d2a71589e4ddddac4e4",
39
39
  ".codex/skills/strategy-candidate-compare/SKILL.md": "34dbca2c77ce74836f5c264131cefb751313147128ab1c3f947002f975be051c",
40
40
  ".codex/skills/strategy-candidate-report/SKILL.md": "3cad1241ce2107d7e30a5d78eafd6429514a10d1a8e9cf3d0e97ffef9d691ab6",
41
- ".codex/skills/strategy-forward-start/SKILL.md": "1591bd1c64864283320793b3c0bb4e1677aab960951ef8b487a1101ca7b6f82c",
41
+ ".codex/skills/strategy-forward-start/SKILL.md": "a86311aeb4c5b46e43011806ffa0f665c4ec0fddce8d7f8c1232184e4a14bfa7",
42
42
  ".codex/skills/strategy-forward-status/SKILL.md": "b41f117680259fd7b4d5cbe3c0a1be75a42f08c8cf78f9ad7ccb0d90bfc66269",
43
43
  ".codex/skills/strategy-improvement-plan/SKILL.md": "f5f0f390941dd3332890a2f0ffae1248abde4925db2cf49a41c0c0c1b47e7864",
44
- ".codex/skills/strategy-improvement-research/SKILL.md": "634b6d4d7e9e8e22db9c073bef0410ae90ab1b68d0fc2610d8def48b5fc687c4",
44
+ ".codex/skills/strategy-improvement-research/references/final-composition-board.md": "05b49ea4aba85792ccea0aeaf4ee2637b808157962f0a38631d6468c342349d6",
45
+ ".codex/skills/strategy-improvement-research/scripts/build-final-composition-spec.mjs": "b51873691fafbcf2dd70d8c9a6da842fec5f8358346c632cfe79211db08b18ba",
46
+ ".codex/skills/strategy-improvement-research/scripts/build-final-composition-spec.test.mjs": "85d06b8fe09ee7611a3fb989173c361ba32f0e9364422925ff221bfe127b2bfa",
47
+ ".codex/skills/strategy-improvement-research/scripts/final-composition-board.mjs": "e5c5a1f91467f25e76f4f294b803c2602221d62fa5bd2be454a4345c3097773d",
48
+ ".codex/skills/strategy-improvement-research/scripts/final-composition-board.test.mjs": "240c4cf13208a9fbbfcade622789dc9f142b68e79b490da7583ab793795d2f35",
49
+ ".codex/skills/strategy-improvement-research/scripts/freeze-gate-variants.mjs": "17b3c868880ddd5b326d309e6e42eaeef1ea22d625a5db947997ac73b221ad6d",
50
+ ".codex/skills/strategy-improvement-research/scripts/freeze-gate-variants.test.mjs": "963e1c64ab3deef11fd48a96e10f3827af38036dfe2eb28daf7849f19dd414d8",
51
+ ".codex/skills/strategy-improvement-research/SKILL.md": "6964eb154e40b0b775aa2735b2ebbe998984479f46333ed03eab88d2ee150d48",
45
52
  ".codex/skills/strategy-period-revalidate/SKILL.md": "f367ba1c632506a7d9bf92662a40b61df55f7a56ad04f3600a475b3c365bbbcb",
46
53
  ".codex/skills/strategy-release/agents/openai.yaml": "46ddb09119b08ae3be3b9509121b57492342012e774789b615767b24270dba17",
47
54
  ".codex/skills/strategy-release/references/diagnose-live.md": "dc2effa6eba34e3cfcc1eaf4d2fd1c96d6ccdda971a0edec76c9eed1ec293a3d",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tradejs",
3
- "version": "3.1.24",
3
+ "version": "3.1.25",
4
4
  "description": "Create a ready-to-run TradeJS project with local infrastructure and the Web UI.",
5
5
  "keywords": [
6
6
  "tradejs",