@alphafox/cli 0.3.2 → 0.3.4

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.
Files changed (69) hide show
  1. package/README.md +4 -3
  2. package/dist/auth/loopback-callback.js +6 -6
  3. package/dist/catalog/allowlist.d.ts +1 -1
  4. package/dist/catalog/allowlist.js +1 -1
  5. package/dist/catalog/generated/registry.json +338 -49
  6. package/dist/catalog/generated/schemas.json +1880 -779
  7. package/dist/catalog/omit.d.ts +6 -0
  8. package/dist/catalog/omit.js +11 -0
  9. package/dist/catalog/operations.d.ts +1 -1
  10. package/dist/catalog/operations.js +5 -2
  11. package/dist/commands/run.js +10 -3
  12. package/dist/engine-backtest/load-config.d.ts +4 -0
  13. package/dist/engine-backtest/load-config.js +44 -0
  14. package/dist/engine-backtest/parse-args.d.ts +3 -1
  15. package/dist/engine-backtest/parse-args.js +87 -3
  16. package/dist/engine-backtest/parse-axes.d.ts +3 -0
  17. package/dist/engine-backtest/parse-axes.js +167 -0
  18. package/dist/engine-backtest/persist.d.ts +60 -1
  19. package/dist/engine-backtest/persist.js +196 -1
  20. package/dist/engine-backtest/return-curve.d.ts +27 -0
  21. package/dist/engine-backtest/return-curve.js +80 -0
  22. package/dist/engine-backtest/run-command.d.ts +1 -4
  23. package/dist/engine-backtest/run-command.js +61 -45
  24. package/dist/engine-backtest/sweep-command.d.ts +13 -0
  25. package/dist/engine-backtest/sweep-command.js +749 -0
  26. package/dist/engine-backtest/sweep-kernel/caps.d.ts +26 -0
  27. package/dist/engine-backtest/sweep-kernel/caps.js +48 -0
  28. package/dist/engine-backtest/sweep-kernel/index.d.ts +13 -0
  29. package/dist/engine-backtest/sweep-kernel/index.js +34 -0
  30. package/dist/engine-backtest/sweep-kernel/plan.d.ts +22 -0
  31. package/dist/engine-backtest/sweep-kernel/plan.js +320 -0
  32. package/dist/engine-backtest/sweep-kernel/results.d.ts +72 -0
  33. package/dist/engine-backtest/sweep-kernel/results.js +150 -0
  34. package/dist/engine-backtest/sweep-kernel/session.d.ts +55 -0
  35. package/dist/engine-backtest/sweep-kernel/session.js +56 -0
  36. package/dist/engine-backtest/sweep-kernel/types.d.ts +36 -0
  37. package/dist/engine-backtest/sweep-kernel/types.js +2 -0
  38. package/dist/engine-backtest/types.d.ts +130 -0
  39. package/dist/install/exec.js +5 -1
  40. package/dist/install/wizard.js +26 -29
  41. package/dist/resolve-symbols/catalog.d.ts +3 -0
  42. package/dist/resolve-symbols/catalog.js +50 -0
  43. package/dist/resolve-symbols/errors.d.ts +18 -0
  44. package/dist/resolve-symbols/errors.js +26 -0
  45. package/dist/resolve-symbols/exchanges.d.ts +8 -0
  46. package/dist/resolve-symbols/exchanges.js +53 -0
  47. package/dist/resolve-symbols/match.d.ts +6 -0
  48. package/dist/resolve-symbols/match.js +250 -0
  49. package/dist/resolve-symbols/parse-args.d.ts +5 -0
  50. package/dist/resolve-symbols/parse-args.js +106 -0
  51. package/dist/resolve-symbols/run-command.d.ts +21 -0
  52. package/dist/resolve-symbols/run-command.js +149 -0
  53. package/dist/resolve-symbols/types.d.ts +37 -0
  54. package/dist/resolve-symbols/types.js +2 -0
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/docs/alphafox-cli-installation-guide.md +2 -2
  58. package/package.json +1 -1
  59. package/skills/account/SKILL.md +1 -1
  60. package/skills/admin/SKILL.md +1 -1
  61. package/skills/alphafox/SKILL.md +38 -0
  62. package/skills/alphafox-shared/SKILL.md +5 -1
  63. package/skills/auth/SKILL.md +1 -1
  64. package/skills/engine-backtest/SKILL.md +48 -6
  65. package/skills/exchange/SKILL.md +1 -1
  66. package/skills/market/SKILL.md +27 -4
  67. package/skills/notification/SKILL.md +1 -1
  68. package/skills/strategy/SKILL.md +7 -20
  69. 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/backtests/{backtestId}. */
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/backtests/{backtestId}. */
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];
@@ -14,6 +14,7 @@ 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");
17
18
  const validate_body_1 = require("../catalog/validate-body");
18
19
  const types_1 = require("../install/types");
19
20
  const wizard_1 = require("../install/wizard");
@@ -94,6 +95,8 @@ async function runCli(argv, env = process.env) {
94
95
  "alphafox catalog",
95
96
  "alphafox api METHOD PATH [--body JSON|--config @file]",
96
97
  "alphafox engine-backtest run --experiment <uuid> --definition <id> --config @file --exchange <id> --range FROM..TO --initial-equity N",
98
+ "alphafox engine-backtest sweep --experiment <uuid> --definition <id> --config @file --axes @file --exchange <id> --range FROM..TO --initial-equity N --no-persist",
99
+ "alphafox resolve-symbols <query...> [--exchange binance]",
97
100
  "alphafox <domain> <resource> <action> [flags]",
98
101
  ],
99
102
  }, { format: flags.format, jq: flags.jq });
@@ -126,15 +129,19 @@ async function runCli(argv, env = process.env) {
126
129
  const sub = args[0];
127
130
  if (!sub ||
128
131
  sub === "run" ||
132
+ sub === "sweep" ||
129
133
  sub === "help" ||
130
134
  sub === "--help" ||
131
135
  sub === "-h") {
132
136
  return await (0, run_command_1.cmdEngineBacktest)(args, flags, env);
133
137
  }
134
- // Hyphen built-in only owns `run`. Underscore/hyphen catalog CRUD
138
+ // Hyphen built-in owns `run` and `sweep`. Underscore/hyphen catalog CRUD
135
139
  // (engine_backtest.experiments.*) still goes through the typed tree.
136
140
  return await cmdTyped(cmd, args, flags, env);
137
141
  }
142
+ case "resolve-symbols":
143
+ case "resolve-symbol":
144
+ return await (0, run_command_2.cmdResolveSymbols)(args, flags, env);
138
145
  default:
139
146
  return await cmdTyped(cmd, args, flags, env);
140
147
  }
@@ -174,8 +181,8 @@ async function cmdInstall(args, flags, env) {
174
181
  (0, envelope_1.writeError)({
175
182
  type: "usage",
176
183
  subtype: "unknown_install_flag",
177
- message: `Unknown install flag: ${parsed.unknown.join(" ")}`,
178
- hint: "Usage: alphafox install [--no-auth] [--dry-run]",
184
+ message: `未知的 install 参数:${parsed.unknown.join(" ")}`,
185
+ hint: "用法:alphafox install [--no-auth] [--dry-run]",
179
186
  });
180
187
  }
181
188
  if (parsed.help) {
@@ -0,0 +1,4 @@
1
+ export declare function loadConfigValue(raw: string, options?: {
2
+ readonly cwd?: string;
3
+ readonly readFile?: (path: string) => string;
4
+ }): unknown;
@@ -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
- function usage(message, subtype = "invalid_args") {
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: exports.ENGINE_BACKTEST_RUN_USAGE[0],
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,3 @@
1
+ import type { SweepAxisInput } from "./sweep-kernel";
2
+ import type { SweepMode } from "./types";
3
+ export declare function parseSweepAxesDocument(raw: unknown, config: unknown, mode: SweepMode): SweepAxisInput[];
@@ -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;