@alphafox/cli 0.3.3 → 0.3.5
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/LICENSE +1 -1
- package/README.md +28 -9
- package/dist/auth/loopback-callback.js +3 -3
- package/dist/catalog/allowlist.d.ts +1 -1
- package/dist/catalog/allowlist.js +1 -1
- package/dist/catalog/generated/registry.json +338 -49
- package/dist/catalog/generated/schemas.json +1880 -779
- package/dist/catalog/omit.d.ts +6 -0
- package/dist/catalog/omit.js +11 -0
- package/dist/catalog/operations.d.ts +1 -1
- package/dist/catalog/operations.js +5 -2
- package/dist/commands/run.js +25 -3
- package/dist/engine-backtest/load-config.d.ts +4 -0
- package/dist/engine-backtest/load-config.js +44 -0
- package/dist/engine-backtest/parse-args.d.ts +3 -1
- package/dist/engine-backtest/parse-args.js +87 -3
- package/dist/engine-backtest/parse-axes.d.ts +3 -0
- package/dist/engine-backtest/parse-axes.js +167 -0
- package/dist/engine-backtest/persist.d.ts +60 -1
- package/dist/engine-backtest/persist.js +196 -1
- package/dist/engine-backtest/return-curve.d.ts +27 -0
- package/dist/engine-backtest/return-curve.js +80 -0
- package/dist/engine-backtest/run-command.d.ts +1 -4
- package/dist/engine-backtest/run-command.js +61 -45
- package/dist/engine-backtest/sweep-command.d.ts +13 -0
- package/dist/engine-backtest/sweep-command.js +749 -0
- package/dist/engine-backtest/sweep-kernel/caps.d.ts +26 -0
- package/dist/engine-backtest/sweep-kernel/caps.js +48 -0
- package/dist/engine-backtest/sweep-kernel/index.d.ts +13 -0
- package/dist/engine-backtest/sweep-kernel/index.js +34 -0
- package/dist/engine-backtest/sweep-kernel/plan.d.ts +22 -0
- package/dist/engine-backtest/sweep-kernel/plan.js +320 -0
- package/dist/engine-backtest/sweep-kernel/results.d.ts +72 -0
- package/dist/engine-backtest/sweep-kernel/results.js +150 -0
- package/dist/engine-backtest/sweep-kernel/session.d.ts +55 -0
- package/dist/engine-backtest/sweep-kernel/session.js +56 -0
- package/dist/engine-backtest/sweep-kernel/types.d.ts +36 -0
- package/dist/engine-backtest/sweep-kernel/types.js +2 -0
- package/dist/engine-backtest/types.d.ts +130 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -1
- package/dist/install/types.d.ts +8 -1
- package/dist/install/types.js +1 -0
- package/dist/install/wizard.js +92 -55
- package/dist/keychain/windows-credential.d.ts +1 -1
- package/dist/keychain/windows-credential.js +1 -1
- package/dist/resolve-symbols/catalog.d.ts +3 -0
- package/dist/resolve-symbols/catalog.js +50 -0
- package/dist/resolve-symbols/errors.d.ts +18 -0
- package/dist/resolve-symbols/errors.js +26 -0
- package/dist/resolve-symbols/exchanges.d.ts +8 -0
- package/dist/resolve-symbols/exchanges.js +53 -0
- package/dist/resolve-symbols/match.d.ts +6 -0
- package/dist/resolve-symbols/match.js +250 -0
- package/dist/resolve-symbols/parse-args.d.ts +5 -0
- package/dist/resolve-symbols/parse-args.js +106 -0
- package/dist/resolve-symbols/run-command.d.ts +21 -0
- package/dist/resolve-symbols/run-command.js +149 -0
- package/dist/resolve-symbols/types.d.ts +37 -0
- package/dist/resolve-symbols/types.js +2 -0
- package/dist/skills/manager.d.ts +96 -0
- package/dist/skills/manager.js +445 -0
- package/dist/skills/run-command.d.ts +22 -0
- package/dist/skills/run-command.js +143 -0
- package/dist/skills-manifest.json +141 -0
- package/dist/update/run-command.d.ts +41 -0
- package/dist/update/run-command.js +204 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/agents/issue-tracker.md +2 -2
- package/docs/agents/triage-labels.md +1 -1
- package/docs/alphafox-cli-installation-guide.md +16 -17
- package/docs/index.html +11 -11
- package/docs/release-supply-chain.md +11 -6
- package/package.json +2 -2
- package/skills/account/SKILL.md +1 -1
- package/skills/admin/SKILL.md +1 -1
- package/skills/alphafox/SKILL.md +38 -0
- package/skills/alphafox-shared/SKILL.md +20 -4
- package/skills/auth/SKILL.md +2 -2
- package/skills/engine-backtest/SKILL.md +48 -6
- package/skills/exchange/SKILL.md +1 -1
- package/skills/market/SKILL.md +27 -4
- package/skills/notification/SKILL.md +1 -1
- package/skills/strategy/SKILL.md +7 -20
- package/skills/trading/SKILL.md +3 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat-attached `/api/v1/backtests` jobs are not a CLI surface.
|
|
3
|
+
* Local Engine WASM (`engine-backtest run`) and `engine_backtest.*` stay.
|
|
4
|
+
* Match `backtests` / `backtests.*` only — never `engine_backtest.*`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isOmittedCatalogOperation(operationId: string): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isOmittedCatalogOperation = isOmittedCatalogOperation;
|
|
4
|
+
/**
|
|
5
|
+
* Chat-attached `/api/v1/backtests` jobs are not a CLI surface.
|
|
6
|
+
* Local Engine WASM (`engine-backtest run`) and `engine_backtest.*` stay.
|
|
7
|
+
* Match `backtests` / `backtests.*` only — never `engine_backtest.*`.
|
|
8
|
+
*/
|
|
9
|
+
function isOmittedCatalogOperation(operationId) {
|
|
10
|
+
return operationId === "backtests" || operationId.startsWith("backtests.");
|
|
11
|
+
}
|
|
@@ -102,6 +102,6 @@ export declare function buildCapabilityManifest(): {
|
|
|
102
102
|
errorSchema: string;
|
|
103
103
|
}[];
|
|
104
104
|
};
|
|
105
|
-
/** Resolve path templates like /api/v1/
|
|
105
|
+
/** Resolve path templates like /api/v1/engine-backtest/experiments/{experimentId}. */
|
|
106
106
|
export declare function resolveOperationPath(template: string, params: Record<string, string>): string;
|
|
107
107
|
export declare function checkGeneratedCatalogCompatibility(cliVersion: string): import("./compatibility").CompatibilityResult;
|
|
@@ -19,11 +19,12 @@ exports.checkGeneratedCatalogCompatibility = checkGeneratedCatalogCompatibility;
|
|
|
19
19
|
const compatibility_1 = require("./compatibility");
|
|
20
20
|
const registry_json_1 = __importDefault(require("./generated/registry.json"));
|
|
21
21
|
const schemas_json_1 = __importDefault(require("./generated/schemas.json"));
|
|
22
|
+
const omit_1 = require("./omit");
|
|
22
23
|
const generated = registry_json_1.default;
|
|
23
24
|
const generatedSchemas = schemas_json_1.default;
|
|
24
25
|
exports.CATALOG_SOURCE = generated.source;
|
|
25
26
|
exports.CATALOG_VERSION = generated.compatibility.contractVersion;
|
|
26
|
-
exports.CATALOG_OPERATIONS = generated.operations;
|
|
27
|
+
exports.CATALOG_OPERATIONS = generated.operations.filter((op) => !(0, omit_1.isOmittedCatalogOperation)(op.operationId));
|
|
27
28
|
exports.COMPATIBILITY_RANGE = generated.compatibility;
|
|
28
29
|
function getCompatibilityRange() {
|
|
29
30
|
return exports.COMPATIBILITY_RANGE;
|
|
@@ -63,6 +64,8 @@ function pathTemplateMatchesOp(template, actual, catchAll) {
|
|
|
63
64
|
return true;
|
|
64
65
|
}
|
|
65
66
|
function getOperationSchemaDocument(operationId) {
|
|
67
|
+
if ((0, omit_1.isOmittedCatalogOperation)(operationId))
|
|
68
|
+
return undefined;
|
|
66
69
|
return generatedSchemas[operationId];
|
|
67
70
|
}
|
|
68
71
|
function extractPathParamNames(template) {
|
|
@@ -103,7 +106,7 @@ function buildCapabilityManifest() {
|
|
|
103
106
|
})),
|
|
104
107
|
};
|
|
105
108
|
}
|
|
106
|
-
/** Resolve path templates like /api/v1/
|
|
109
|
+
/** Resolve path templates like /api/v1/engine-backtest/experiments/{experimentId}. */
|
|
107
110
|
function resolveOperationPath(template, params) {
|
|
108
111
|
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, key) => {
|
|
109
112
|
const value = params[key];
|
package/dist/commands/run.js
CHANGED
|
@@ -14,6 +14,9 @@ const store_1 = require("../keychain/store");
|
|
|
14
14
|
const confirmation_1 = require("../safety/confirmation");
|
|
15
15
|
const version_1 = require("../version");
|
|
16
16
|
const run_command_1 = require("../engine-backtest/run-command");
|
|
17
|
+
const run_command_2 = require("../resolve-symbols/run-command");
|
|
18
|
+
const run_command_3 = require("../skills/run-command");
|
|
19
|
+
const run_command_4 = require("../update/run-command");
|
|
17
20
|
const validate_body_1 = require("../catalog/validate-body");
|
|
18
21
|
const types_1 = require("../install/types");
|
|
19
22
|
const wizard_1 = require("../install/wizard");
|
|
@@ -85,6 +88,8 @@ async function runCli(argv, env = process.env) {
|
|
|
85
88
|
"alphafox version",
|
|
86
89
|
"alphafox doctor",
|
|
87
90
|
"alphafox install [--no-auth|--dry-run]",
|
|
91
|
+
"alphafox update [--check|--version VERSION]",
|
|
92
|
+
"alphafox skills status|sync [--force --yes]",
|
|
88
93
|
"alphafox auth login [--no-wait|--device-code CODE|--browser]",
|
|
89
94
|
"alphafox auth status [--verify]",
|
|
90
95
|
"alphafox auth logout",
|
|
@@ -94,13 +99,18 @@ async function runCli(argv, env = process.env) {
|
|
|
94
99
|
"alphafox catalog",
|
|
95
100
|
"alphafox api METHOD PATH [--body JSON|--config @file]",
|
|
96
101
|
"alphafox engine-backtest run --experiment <uuid> --definition <id> --config @file --exchange <id> --range FROM..TO --initial-equity N",
|
|
102
|
+
"alphafox engine-backtest sweep --experiment <uuid> --definition <id> --config @file --axes @file --exchange <id> --range FROM..TO --initial-equity N --no-persist",
|
|
103
|
+
"alphafox resolve-symbols <query...> [--exchange binance]",
|
|
97
104
|
"alphafox <domain> <resource> <action> [flags]",
|
|
98
105
|
],
|
|
99
106
|
}, { format: flags.format, jq: flags.jq });
|
|
100
107
|
return 0;
|
|
101
108
|
}
|
|
102
109
|
try {
|
|
103
|
-
if (cmd !== "version" &&
|
|
110
|
+
if (cmd !== "version" &&
|
|
111
|
+
cmd !== "install" &&
|
|
112
|
+
cmd !== "update" &&
|
|
113
|
+
cmd !== "skills") {
|
|
104
114
|
assertCatalogCompatible();
|
|
105
115
|
}
|
|
106
116
|
switch (cmd) {
|
|
@@ -108,6 +118,10 @@ async function runCli(argv, env = process.env) {
|
|
|
108
118
|
return cmdVersion(flags);
|
|
109
119
|
case "install":
|
|
110
120
|
return await cmdInstall(args, flags, env);
|
|
121
|
+
case "update":
|
|
122
|
+
return await (0, run_command_4.cmdUpdate)(args, flags, env);
|
|
123
|
+
case "skills":
|
|
124
|
+
return await (0, run_command_3.cmdSkills)(args, flags, env);
|
|
111
125
|
case "doctor":
|
|
112
126
|
return cmdDoctor(flags, env);
|
|
113
127
|
case "whoami":
|
|
@@ -126,15 +140,19 @@ async function runCli(argv, env = process.env) {
|
|
|
126
140
|
const sub = args[0];
|
|
127
141
|
if (!sub ||
|
|
128
142
|
sub === "run" ||
|
|
143
|
+
sub === "sweep" ||
|
|
129
144
|
sub === "help" ||
|
|
130
145
|
sub === "--help" ||
|
|
131
146
|
sub === "-h") {
|
|
132
147
|
return await (0, run_command_1.cmdEngineBacktest)(args, flags, env);
|
|
133
148
|
}
|
|
134
|
-
// Hyphen built-in
|
|
149
|
+
// Hyphen built-in owns `run` and `sweep`. Underscore/hyphen catalog CRUD
|
|
135
150
|
// (engine_backtest.experiments.*) still goes through the typed tree.
|
|
136
151
|
return await cmdTyped(cmd, args, flags, env);
|
|
137
152
|
}
|
|
153
|
+
case "resolve-symbols":
|
|
154
|
+
case "resolve-symbol":
|
|
155
|
+
return await (0, run_command_2.cmdResolveSymbols)(args, flags, env);
|
|
138
156
|
default:
|
|
139
157
|
return await cmdTyped(cmd, args, flags, env);
|
|
140
158
|
}
|
|
@@ -193,7 +211,11 @@ async function cmdInstall(args, flags, env) {
|
|
|
193
211
|
help: false,
|
|
194
212
|
}, env);
|
|
195
213
|
(0, envelope_1.writeSuccess)(data, { format: flags.format, jq: flags.jq });
|
|
196
|
-
return data.auth.action === "failed"
|
|
214
|
+
return data.auth.action === "failed" ||
|
|
215
|
+
data.skills.action === "blocked" ||
|
|
216
|
+
(data.skills.blocked?.length ?? 0) > 0
|
|
217
|
+
? 1
|
|
218
|
+
: 0;
|
|
197
219
|
}
|
|
198
220
|
catch (err) {
|
|
199
221
|
if ((0, types_1.isInstallError)(err)) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfigValue = loadConfigValue;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
function loadConfigValue(raw, options = {}) {
|
|
8
|
+
const cwd = options.cwd ?? process.cwd();
|
|
9
|
+
const read = options.readFile ?? ((p) => (0, node_fs_1.readFileSync)(p, "utf8"));
|
|
10
|
+
if (raw.startsWith("@")) {
|
|
11
|
+
const rel = raw.slice(1);
|
|
12
|
+
if (!rel) {
|
|
13
|
+
throw new errors_1.EngineBacktestError({
|
|
14
|
+
type: "usage",
|
|
15
|
+
subtype: "missing_config",
|
|
16
|
+
message: "--config @path is empty",
|
|
17
|
+
status: 400,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const abs = (0, node_path_1.isAbsolute)(rel) ? rel : (0, node_path_1.resolve)(cwd, rel);
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(read(abs));
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
throw new errors_1.EngineBacktestError({
|
|
26
|
+
type: "usage",
|
|
27
|
+
subtype: "invalid_config",
|
|
28
|
+
message: `Cannot read --config file ${abs}: ${err instanceof Error ? err.message : String(err)}`,
|
|
29
|
+
status: 400,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new errors_1.EngineBacktestError({
|
|
38
|
+
type: "usage",
|
|
39
|
+
subtype: "invalid_config",
|
|
40
|
+
message: "--config must be JSON or @path-to.json",
|
|
41
|
+
status: 400,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { EngineBacktestRunArgs, ExecutionModel, InclusiveUtcDateRange } from "./types";
|
|
1
|
+
import type { EngineBacktestRunArgs, EngineBacktestSweepArgs, ExecutionModel, InclusiveUtcDateRange } from "./types";
|
|
2
2
|
export declare const ENGINE_BACKTEST_RUN_USAGE: string[];
|
|
3
|
+
export declare const ENGINE_BACKTEST_SWEEP_USAGE: string[];
|
|
3
4
|
export declare function parseInclusiveUtcDateRange(rangeStart: string, rangeEnd: string): InclusiveUtcDateRange;
|
|
4
5
|
export declare function parseRangeFlag(raw: string): InclusiveUtcDateRange;
|
|
5
6
|
export declare function parseExecutionModelJson(raw: string): Partial<ExecutionModel>;
|
|
6
7
|
export declare function parseEngineBacktestRunArgs(args: readonly string[]): EngineBacktestRunArgs;
|
|
8
|
+
export declare function parseEngineBacktestSweepArgs(args: readonly string[]): EngineBacktestSweepArgs;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ENGINE_BACKTEST_RUN_USAGE = void 0;
|
|
3
|
+
exports.ENGINE_BACKTEST_SWEEP_USAGE = exports.ENGINE_BACKTEST_RUN_USAGE = void 0;
|
|
4
4
|
exports.parseInclusiveUtcDateRange = parseInclusiveUtcDateRange;
|
|
5
5
|
exports.parseRangeFlag = parseRangeFlag;
|
|
6
6
|
exports.parseExecutionModelJson = parseExecutionModelJson;
|
|
7
7
|
exports.parseEngineBacktestRunArgs = parseEngineBacktestRunArgs;
|
|
8
|
+
exports.parseEngineBacktestSweepArgs = parseEngineBacktestSweepArgs;
|
|
8
9
|
const errors_1 = require("./errors");
|
|
9
10
|
const replay_timeframe_1 = require("./replay-timeframe");
|
|
11
|
+
const sweep_kernel_1 = require("./sweep-kernel");
|
|
10
12
|
const DAY_MS = 86_400_000;
|
|
11
13
|
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
12
14
|
const TIERS = new Set(["free", "pro", "pro_max"]);
|
|
@@ -16,12 +18,17 @@ exports.ENGINE_BACKTEST_RUN_USAGE = [
|
|
|
16
18
|
"alphafox engine-backtest run --experiment <uuid> --definition <id> --config @file.json --exchange <id> --range YYYY-MM-DD..YYYY-MM-DD --initial-equity N [--replay-timeframe 1m]",
|
|
17
19
|
"alphafox engine-backtest run --create-experiment --name <name> --definition <id> --config @file.json --exchange <id> --from YYYY-MM-DD --to YYYY-MM-DD --initial-equity N",
|
|
18
20
|
];
|
|
19
|
-
|
|
21
|
+
exports.ENGINE_BACKTEST_SWEEP_USAGE = [
|
|
22
|
+
"alphafox engine-backtest sweep --experiment <uuid> --definition <id> --config @file.json --axes @axes.json --exchange <id> --range YYYY-MM-DD..YYYY-MM-DD --initial-equity N [--no-persist] [--mode neighborhood|range] [--search-mode standard|fast] [--concurrency N]",
|
|
23
|
+
];
|
|
24
|
+
const SWEEP_MODES = new Set(["neighborhood", "range"]);
|
|
25
|
+
const SEARCH_MODES = new Set(["standard", "fast"]);
|
|
26
|
+
function usage(message, subtype = "invalid_args", hint = exports.ENGINE_BACKTEST_RUN_USAGE[0]) {
|
|
20
27
|
throw new errors_1.EngineBacktestError({
|
|
21
28
|
type: "usage",
|
|
22
29
|
subtype,
|
|
23
30
|
message,
|
|
24
|
-
hint
|
|
31
|
+
hint,
|
|
25
32
|
status: 400,
|
|
26
33
|
});
|
|
27
34
|
}
|
|
@@ -279,3 +286,80 @@ function parseEngineBacktestRunArgs(args) {
|
|
|
279
286
|
replayTimeframe,
|
|
280
287
|
};
|
|
281
288
|
}
|
|
289
|
+
function parseEngineBacktestSweepArgs(args) {
|
|
290
|
+
let axesRaw;
|
|
291
|
+
let mode = "neighborhood";
|
|
292
|
+
let searchMode = "standard";
|
|
293
|
+
let concurrency = sweep_kernel_1.DEFAULT_SWEEP_CONCURRENCY;
|
|
294
|
+
let help = false;
|
|
295
|
+
const rest = [];
|
|
296
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
297
|
+
const a = args[i];
|
|
298
|
+
if (a === "--help" || a === "-h") {
|
|
299
|
+
help = true;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const eq = a.indexOf("=");
|
|
303
|
+
const flag = eq >= 0 ? a.slice(0, eq) : a;
|
|
304
|
+
const inline = eq >= 0 ? a.slice(eq + 1) : undefined;
|
|
305
|
+
const read = () => {
|
|
306
|
+
if (inline !== undefined)
|
|
307
|
+
return inline;
|
|
308
|
+
const taken = takeValue(args, i, flag);
|
|
309
|
+
i = taken.next;
|
|
310
|
+
return taken.value;
|
|
311
|
+
};
|
|
312
|
+
if (flag === "--axes") {
|
|
313
|
+
axesRaw = read();
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (flag === "--mode") {
|
|
317
|
+
const raw = read().trim();
|
|
318
|
+
if (!SWEEP_MODES.has(raw)) {
|
|
319
|
+
usage("--mode must be neighborhood|range", "invalid_sweep_mode", exports.ENGINE_BACKTEST_SWEEP_USAGE[0]);
|
|
320
|
+
}
|
|
321
|
+
mode = raw;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (flag === "--search-mode") {
|
|
325
|
+
const raw = read().trim();
|
|
326
|
+
if (!SEARCH_MODES.has(raw)) {
|
|
327
|
+
usage("--search-mode must be standard|fast", "invalid_search_mode", exports.ENGINE_BACKTEST_SWEEP_USAGE[0]);
|
|
328
|
+
}
|
|
329
|
+
searchMode = raw;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (flag === "--concurrency") {
|
|
333
|
+
const n = Number(read());
|
|
334
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
335
|
+
usage("--concurrency must be a positive integer", "invalid_concurrency", exports.ENGINE_BACKTEST_SWEEP_USAGE[0]);
|
|
336
|
+
}
|
|
337
|
+
concurrency = n;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
rest.push(a);
|
|
341
|
+
}
|
|
342
|
+
if (help) {
|
|
343
|
+
return {
|
|
344
|
+
help: true,
|
|
345
|
+
createExperiment: false,
|
|
346
|
+
dataQualityMode: "strict",
|
|
347
|
+
persist: true,
|
|
348
|
+
replayTimeframe: replay_timeframe_1.ENGINE_BACKTEST_DEFAULT_REPLAY_TIMEFRAME,
|
|
349
|
+
axesRaw,
|
|
350
|
+
mode,
|
|
351
|
+
searchMode,
|
|
352
|
+
concurrency,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (!axesRaw) {
|
|
356
|
+
usage("--axes is required (@file.json or JSON)", "missing_axes", exports.ENGINE_BACKTEST_SWEEP_USAGE[0]);
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
...parseEngineBacktestRunArgs(rest),
|
|
360
|
+
axesRaw,
|
|
361
|
+
mode,
|
|
362
|
+
searchMode,
|
|
363
|
+
concurrency,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseSweepAxesDocument = parseSweepAxesDocument;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const NEIGHBORHOOD_STEPS = 3;
|
|
6
|
+
function usage(message, subtype) {
|
|
7
|
+
throw new errors_1.EngineBacktestError({
|
|
8
|
+
type: "usage",
|
|
9
|
+
subtype,
|
|
10
|
+
message,
|
|
11
|
+
hint: "Each --axes entry needs an explicit config path and either values or min/max/step.",
|
|
12
|
+
status: 400,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function parseSweepAxesDocument(raw, config, mode) {
|
|
16
|
+
const list = listAxes(raw);
|
|
17
|
+
if (list.length === 0) {
|
|
18
|
+
usage("--axes must contain at least one axis", "invalid_axes");
|
|
19
|
+
}
|
|
20
|
+
return list.map((item, index) => parseSweepAxis(item, config, mode, index));
|
|
21
|
+
}
|
|
22
|
+
function listAxes(raw) {
|
|
23
|
+
if (Array.isArray(raw)) {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
27
|
+
const axes = raw.axes;
|
|
28
|
+
if (Array.isArray(axes)) {
|
|
29
|
+
return axes;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
usage("--axes must be a JSON array or {\"axes\": [...]}", "invalid_axes");
|
|
33
|
+
}
|
|
34
|
+
function parseSweepAxis(raw, config, mode, index) {
|
|
35
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
36
|
+
usage(`Axis ${index} must be an object`, "invalid_axis");
|
|
37
|
+
}
|
|
38
|
+
const row = raw;
|
|
39
|
+
const path = parseAxisPath(row.path, index);
|
|
40
|
+
const current = readNumericCurrent(config, path, index);
|
|
41
|
+
const values = parseOptionalNumberList(row.values, `Axis ${index} values`);
|
|
42
|
+
const min = optionalFiniteNumber(row.min, `Axis ${index} min`);
|
|
43
|
+
const max = optionalFiniteNumber(row.max, `Axis ${index} max`);
|
|
44
|
+
const step = optionalFiniteNumber(row.step, `Axis ${index} step`);
|
|
45
|
+
const hasWindow = min !== undefined && max !== undefined && step !== undefined;
|
|
46
|
+
if (!values && !hasWindow) {
|
|
47
|
+
usage(`Axis ${index} must set values or min/max/step`, "invalid_axis_window");
|
|
48
|
+
}
|
|
49
|
+
if (hasWindow && !(step > 0)) {
|
|
50
|
+
usage(`Axis ${index} step must be > 0`, "invalid_axis_window");
|
|
51
|
+
}
|
|
52
|
+
const isInteger = typeof row.isInteger === "boolean"
|
|
53
|
+
? row.isInteger
|
|
54
|
+
: inferInteger(current, values, step);
|
|
55
|
+
const axis = {
|
|
56
|
+
path,
|
|
57
|
+
current,
|
|
58
|
+
isInteger,
|
|
59
|
+
...(min === undefined ? {} : { min }),
|
|
60
|
+
...(max === undefined ? {} : { max }),
|
|
61
|
+
...(typeof row.minExclusive === "boolean"
|
|
62
|
+
? { minExclusive: row.minExclusive }
|
|
63
|
+
: {}),
|
|
64
|
+
...(typeof row.maxExclusive === "boolean"
|
|
65
|
+
? { maxExclusive: row.maxExclusive }
|
|
66
|
+
: {}),
|
|
67
|
+
...(values ? { values } : {}),
|
|
68
|
+
...(values || step === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: {
|
|
71
|
+
window: mode === "neighborhood"
|
|
72
|
+
? {
|
|
73
|
+
min: current - NEIGHBORHOOD_STEPS * step,
|
|
74
|
+
max: current + NEIGHBORHOOD_STEPS * step,
|
|
75
|
+
step,
|
|
76
|
+
}
|
|
77
|
+
: { min: min, max: max, step },
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
return axis;
|
|
81
|
+
}
|
|
82
|
+
function parseAxisPath(raw, index) {
|
|
83
|
+
if (typeof raw === "string") {
|
|
84
|
+
const path = raw.split(".").filter((segment) => segment.length > 0);
|
|
85
|
+
if (path.length === 0) {
|
|
86
|
+
usage(`Axis ${index} path must not be empty`, "invalid_axis_path");
|
|
87
|
+
}
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(raw)) {
|
|
91
|
+
if (raw.length === 0 ||
|
|
92
|
+
raw.some((segment) => typeof segment !== "string" || segment.length === 0)) {
|
|
93
|
+
usage(`Axis ${index} path must be a non-empty array of segments`, "invalid_axis_path");
|
|
94
|
+
}
|
|
95
|
+
return raw;
|
|
96
|
+
}
|
|
97
|
+
usage(`Axis ${index} must identify an explicit config path`, "invalid_axis_path");
|
|
98
|
+
}
|
|
99
|
+
function readNumericCurrent(config, path, index) {
|
|
100
|
+
const value = getValueAtPath(config, path);
|
|
101
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
102
|
+
usage(`Axis ${index} current value at ${path.join(".")} must be a finite number in --config`, "invalid_axis_current");
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function parseOptionalNumberList(raw, label) {
|
|
107
|
+
if (raw === undefined) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
111
|
+
usage(`${label} must be a non-empty number array`, "invalid_axis_values");
|
|
112
|
+
}
|
|
113
|
+
const values = raw.map((value, valueIndex) => {
|
|
114
|
+
const n = Number(value);
|
|
115
|
+
if (!Number.isFinite(n)) {
|
|
116
|
+
usage(`${label}[${valueIndex}] must be a finite number`, "invalid_axis_values");
|
|
117
|
+
}
|
|
118
|
+
return n;
|
|
119
|
+
});
|
|
120
|
+
return values;
|
|
121
|
+
}
|
|
122
|
+
function optionalFiniteNumber(raw, label) {
|
|
123
|
+
if (raw === undefined) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const n = Number(raw);
|
|
127
|
+
if (!Number.isFinite(n)) {
|
|
128
|
+
usage(`${label} must be a finite number`, "invalid_axis_window");
|
|
129
|
+
}
|
|
130
|
+
return n;
|
|
131
|
+
}
|
|
132
|
+
function inferInteger(current, values, step) {
|
|
133
|
+
const candidates = [
|
|
134
|
+
current,
|
|
135
|
+
...(values ?? []),
|
|
136
|
+
...(step === undefined ? [] : [step]),
|
|
137
|
+
];
|
|
138
|
+
return candidates.every((value) => Number.isInteger(value));
|
|
139
|
+
}
|
|
140
|
+
function isPlainRecord(value) {
|
|
141
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
142
|
+
}
|
|
143
|
+
function parseArrayIndex(segment) {
|
|
144
|
+
if (!/^\d+$/.test(segment)) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const index = Number(segment);
|
|
148
|
+
return Number.isInteger(index) && index >= 0 ? index : null;
|
|
149
|
+
}
|
|
150
|
+
function getValueAtPath(root, path) {
|
|
151
|
+
let current = root;
|
|
152
|
+
for (const segment of path) {
|
|
153
|
+
if (Array.isArray(current)) {
|
|
154
|
+
const index = parseArrayIndex(segment);
|
|
155
|
+
if (index === null) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
current = current[index];
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (!isPlainRecord(current)) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
current = current[segment];
|
|
165
|
+
}
|
|
166
|
+
return current;
|
|
167
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProfileName } from "../config/profiles";
|
|
2
|
-
import type { CreateRunRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, ExecutionModel } from "./types";
|
|
2
|
+
import type { CreateRunRequestBody, CreateSweepRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, EngineBacktestSweepPointRow, ExecutionModel, PersistedSnapshot, SweepMode, SweepSearchMode } from "./types";
|
|
3
3
|
/** Aligned with `@alphafoxai/backtest-runner` DEFAULT_EXECUTION_MODEL. */
|
|
4
4
|
export declare const DEFAULT_EXECUTION_MODEL: ExecutionModel;
|
|
5
5
|
export declare const SNAPSHOT_SCHEMA_VERSION: 1;
|
|
@@ -23,6 +23,7 @@ export declare function buildCreateRunRequest(input: {
|
|
|
23
23
|
readonly exchangeId: string;
|
|
24
24
|
readonly dataQualityMode: DataQualityMode;
|
|
25
25
|
readonly engineVersion: string;
|
|
26
|
+
readonly equityCurve?: unknown;
|
|
26
27
|
}): CreateRunRequestBody;
|
|
27
28
|
/**
|
|
28
29
|
* Experiment detail URL. Locale prefix (`/zh`, `/en`) is optional — web
|
|
@@ -30,3 +31,61 @@ export declare function buildCreateRunRequest(input: {
|
|
|
30
31
|
* unprefixed `/dashboard/traders/backtest/{id}` path.
|
|
31
32
|
*/
|
|
32
33
|
export declare function experimentPageUrl(profile: ProfileName, experimentId: string): string;
|
|
34
|
+
/** Web experiment workspace opens the Sweep tab with `?tab=sweep`. */
|
|
35
|
+
export declare function experimentSweepPageUrl(profile: ProfileName, experimentId: string): string;
|
|
36
|
+
export declare const ENGINE_BACKTEST_SWEEP_MAX_UTF8_BYTES: number;
|
|
37
|
+
export declare const ENGINE_BACKTEST_SWEEP_ERROR_MAX_UTF8_BYTES: number;
|
|
38
|
+
export declare function shouldPersistSweepCompletion(input: {
|
|
39
|
+
readonly completed: boolean;
|
|
40
|
+
readonly cancelled: boolean;
|
|
41
|
+
}): boolean;
|
|
42
|
+
export declare function mintClientSweepId(randomUuid?: () => string): string;
|
|
43
|
+
export declare function sweepsPath(experimentId: string): string;
|
|
44
|
+
export declare function buildSweepBaseSnapshot(input: {
|
|
45
|
+
readonly definitionId: string;
|
|
46
|
+
readonly configSchemaVersion: number;
|
|
47
|
+
readonly config: unknown;
|
|
48
|
+
readonly exchangeId: string;
|
|
49
|
+
readonly rangeStart: string;
|
|
50
|
+
readonly rangeEnd: string;
|
|
51
|
+
readonly initialEquity: number;
|
|
52
|
+
readonly subscriptionTier: PersistedSnapshot["subscriptionTier"];
|
|
53
|
+
readonly dataQualityMode: DataQualityMode;
|
|
54
|
+
readonly symbols: readonly string[];
|
|
55
|
+
readonly timeframes: readonly string[];
|
|
56
|
+
readonly baseTimeframe: string;
|
|
57
|
+
readonly executionModel: ExecutionModel;
|
|
58
|
+
readonly positionSideDual: boolean;
|
|
59
|
+
readonly engineVersion: string;
|
|
60
|
+
}): PersistedSnapshot;
|
|
61
|
+
/**
|
|
62
|
+
* Build POST .../sweeps body from a completed local search.
|
|
63
|
+
* Field set matches web `buildCreateSweepRequestFromCompleted`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildCreateSweepRequest(input: {
|
|
66
|
+
readonly clientSweepId: string;
|
|
67
|
+
readonly snapshot: PersistedSnapshot;
|
|
68
|
+
readonly axes: ReadonlyArray<{
|
|
69
|
+
readonly path: readonly string[];
|
|
70
|
+
readonly current: number;
|
|
71
|
+
readonly values: readonly number[];
|
|
72
|
+
}>;
|
|
73
|
+
readonly mode: SweepMode;
|
|
74
|
+
readonly searchMode: SweepSearchMode;
|
|
75
|
+
readonly concurrency: number;
|
|
76
|
+
readonly requestedCombinationCount: number;
|
|
77
|
+
readonly sampled: boolean;
|
|
78
|
+
readonly points: readonly EngineBacktestSweepPointRow[];
|
|
79
|
+
readonly successfulCount: number;
|
|
80
|
+
readonly failedCount: number;
|
|
81
|
+
readonly liquidatedCount: number;
|
|
82
|
+
readonly elapsedMs: number;
|
|
83
|
+
readonly best: {
|
|
84
|
+
readonly coordinate: {
|
|
85
|
+
readonly values: readonly number[];
|
|
86
|
+
};
|
|
87
|
+
readonly returnPct: number;
|
|
88
|
+
} | null;
|
|
89
|
+
readonly engineVersion: string;
|
|
90
|
+
}): CreateSweepRequestBody;
|
|
91
|
+
export declare function assertSweepCreateRequestSize(body: CreateSweepRequestBody): void;
|