@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.
- package/README.md +4 -3
- package/dist/auth/loopback-callback.js +6 -6
- 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 +10 -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/install/exec.js +5 -1
- package/dist/install/wizard.js +26 -29
- 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/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/alphafox-cli-installation-guide.md +2 -2
- package/package.json +1 -1
- 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 +5 -1
- package/skills/auth/SKILL.md +1 -1
- 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,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractSweepMetrics = extractSweepMetrics;
|
|
4
|
+
exports.selectBestNonLiquidatedPoint = selectBestNonLiquidatedPoint;
|
|
5
|
+
exports.resolveIslandRiskLevel = resolveIslandRiskLevel;
|
|
6
|
+
exports.analyzeSweepIsland = analyzeSweepIsland;
|
|
7
|
+
function extractSweepMetrics(source) {
|
|
8
|
+
return {
|
|
9
|
+
returnPct: source.returnPct,
|
|
10
|
+
maxDrawdownPct: source.maxDrawdownPct,
|
|
11
|
+
sharpeRatio: source.sharpeRatio,
|
|
12
|
+
winRatePct: source.winRatePct,
|
|
13
|
+
...(source.maxLeverage === undefined
|
|
14
|
+
? {}
|
|
15
|
+
: { maxLeverage: source.maxLeverage }),
|
|
16
|
+
liquidationCount: typeof source.liquidationCount === "number" &&
|
|
17
|
+
Number.isFinite(source.liquidationCount)
|
|
18
|
+
? source.liquidationCount
|
|
19
|
+
: source.liquidated
|
|
20
|
+
? 1
|
|
21
|
+
: 0,
|
|
22
|
+
netPnl: source.netPnl,
|
|
23
|
+
finalEquity: source.finalEquity,
|
|
24
|
+
tradeCount: source.tradeCount,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function selectBestNonLiquidatedPoint(points) {
|
|
28
|
+
const candidates = points.filter(isOkNonLiquidatedPoint);
|
|
29
|
+
if (candidates.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const best = candidates.reduce((leader, point) => point.metrics.returnPct > leader.metrics.returnPct ? point : leader);
|
|
33
|
+
return {
|
|
34
|
+
coordinate: best.coordinate,
|
|
35
|
+
returnPct: best.metrics.returnPct,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function resolveIslandRiskLevel(score) {
|
|
39
|
+
if (score >= 80) {
|
|
40
|
+
return "high";
|
|
41
|
+
}
|
|
42
|
+
if (score >= 50) {
|
|
43
|
+
return "medium";
|
|
44
|
+
}
|
|
45
|
+
if (score >= 30) {
|
|
46
|
+
return "low_medium";
|
|
47
|
+
}
|
|
48
|
+
return "low";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Score each swept point by how sharply it deviates from its neighbors, then
|
|
52
|
+
* flag the point closest to `centerValue` when it sits on a fragile island.
|
|
53
|
+
*/
|
|
54
|
+
function analyzeSweepIsland(points, centerValue) {
|
|
55
|
+
const rows = [...points].sort((left, right) => left.value - right.value);
|
|
56
|
+
if (rows.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
points: [],
|
|
59
|
+
warning: null,
|
|
60
|
+
overallScore: 0,
|
|
61
|
+
overallRiskLevel: "low",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const returnStd = populationStd(rows.map((row) => row.returnPct));
|
|
65
|
+
const drawdownStd = populationStd(rows.map((row) => row.maxDrawdownPct));
|
|
66
|
+
const centerIndex = rows.reduce((bestIndex, row, index) => Math.abs(row.value - centerValue) <
|
|
67
|
+
Math.abs(rows[bestIndex].value - centerValue)
|
|
68
|
+
? index
|
|
69
|
+
: bestIndex, 0);
|
|
70
|
+
const scored = rows.map((row, index) => {
|
|
71
|
+
const neighbors = [index - 1, index + 1]
|
|
72
|
+
.filter((i) => i >= 0 && i < rows.length)
|
|
73
|
+
.map((i) => rows[i]);
|
|
74
|
+
const score = scoreNeighborhood(row, neighbors, returnStd, drawdownStd);
|
|
75
|
+
return {
|
|
76
|
+
...row,
|
|
77
|
+
isCenter: index === centerIndex,
|
|
78
|
+
islandScore: Number(score.toFixed(2)),
|
|
79
|
+
riskLevel: resolveIslandRiskLevel(score),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
const center = scored[centerIndex];
|
|
83
|
+
const centerNeighbors = [centerIndex - 1, centerIndex + 1]
|
|
84
|
+
.filter((i) => i >= 0 && i < scored.length)
|
|
85
|
+
.map((i) => scored[i]);
|
|
86
|
+
const hasLiquidationJump = center.liquidationCount === 0 &&
|
|
87
|
+
centerNeighbors.some((row) => row.liquidationCount > 0);
|
|
88
|
+
const warning = hasLiquidationJump || center.islandScore >= 50
|
|
89
|
+
? {
|
|
90
|
+
centerValue: center.value,
|
|
91
|
+
islandScore: center.islandScore,
|
|
92
|
+
riskLevel: center.riskLevel,
|
|
93
|
+
counterexamples: centerNeighbors
|
|
94
|
+
.filter((row) => row.liquidationCount > center.liquidationCount)
|
|
95
|
+
.map((row) => ({
|
|
96
|
+
counterValue: row.value,
|
|
97
|
+
centerReturnPct: center.returnPct,
|
|
98
|
+
counterReturnPct: row.returnPct,
|
|
99
|
+
centerLiquidationCount: center.liquidationCount,
|
|
100
|
+
counterLiquidationCount: row.liquidationCount,
|
|
101
|
+
})),
|
|
102
|
+
}
|
|
103
|
+
: null;
|
|
104
|
+
const overallScore = scored.reduce((highest, row) => Math.max(highest, row.islandScore), 0);
|
|
105
|
+
return {
|
|
106
|
+
points: scored,
|
|
107
|
+
warning,
|
|
108
|
+
overallScore,
|
|
109
|
+
overallRiskLevel: resolveIslandRiskLevel(overallScore),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function isOkNonLiquidatedPoint(point) {
|
|
113
|
+
return (point.status === "ok" &&
|
|
114
|
+
point.metrics !== undefined &&
|
|
115
|
+
Number.isFinite(point.metrics.returnPct) &&
|
|
116
|
+
Number.isFinite(point.metrics.liquidationCount) &&
|
|
117
|
+
point.metrics.liquidationCount === 0);
|
|
118
|
+
}
|
|
119
|
+
function scoreNeighborhood(row, neighbors, returnStd, drawdownStd) {
|
|
120
|
+
if (neighbors.length === 0) {
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
const neighborReturn = average(neighbors.map((n) => n.returnPct));
|
|
124
|
+
const neighborDrawdown = average(neighbors.map((n) => n.maxDrawdownPct));
|
|
125
|
+
const neighborLiquidations = average(neighbors.map((n) => n.liquidationCount));
|
|
126
|
+
const returnAdvantage = (Math.min(Math.max(row.returnPct - neighborReturn, 0) / Math.max(returnStd, 0.5), 2) /
|
|
127
|
+
2) *
|
|
128
|
+
100;
|
|
129
|
+
const drawdownAdvantage = (Math.min(Math.max(neighborDrawdown - row.maxDrawdownPct, 0) /
|
|
130
|
+
Math.max(drawdownStd, 0.1), 2) /
|
|
131
|
+
2) *
|
|
132
|
+
100;
|
|
133
|
+
const liquidationJump = (Math.min(Math.max(neighborLiquidations - row.liquidationCount, 0), 3) /
|
|
134
|
+
3) *
|
|
135
|
+
100;
|
|
136
|
+
return (0.35 * returnAdvantage + 0.3 * drawdownAdvantage + 0.25 * liquidationJump);
|
|
137
|
+
}
|
|
138
|
+
function average(values) {
|
|
139
|
+
if (values.length === 0) {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
143
|
+
}
|
|
144
|
+
function populationStd(values) {
|
|
145
|
+
if (values.length < 2) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
const mean = values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
149
|
+
return Math.sqrt(values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / values.length);
|
|
150
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type SweepExecutionState = {
|
|
2
|
+
readonly status: "idle";
|
|
3
|
+
} | {
|
|
4
|
+
readonly status: "running";
|
|
5
|
+
readonly total: number;
|
|
6
|
+
} | {
|
|
7
|
+
readonly status: "succeeded";
|
|
8
|
+
readonly resultId: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly status: "failed";
|
|
11
|
+
readonly message: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly status: "cancelled";
|
|
14
|
+
};
|
|
15
|
+
export type SweepCloudSaveState = {
|
|
16
|
+
readonly status: "idle";
|
|
17
|
+
} | {
|
|
18
|
+
readonly status: "saving";
|
|
19
|
+
} | {
|
|
20
|
+
readonly status: "saved";
|
|
21
|
+
readonly sweepId: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly status: "failed";
|
|
24
|
+
readonly message: string;
|
|
25
|
+
};
|
|
26
|
+
export interface SweepSessionState {
|
|
27
|
+
readonly execution: SweepExecutionState;
|
|
28
|
+
readonly cloudSave: SweepCloudSaveState;
|
|
29
|
+
}
|
|
30
|
+
export type SweepSessionEvent = {
|
|
31
|
+
readonly type: "execution-started";
|
|
32
|
+
readonly total: number;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: "execution-succeeded";
|
|
35
|
+
readonly resultId: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly type: "execution-failed";
|
|
38
|
+
readonly message: string;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "execution-cancelled";
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: "execution-reset";
|
|
43
|
+
} | {
|
|
44
|
+
readonly type: "cloud-save-started";
|
|
45
|
+
} | {
|
|
46
|
+
readonly type: "cloud-save-succeeded";
|
|
47
|
+
readonly sweepId: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly type: "cloud-save-failed";
|
|
50
|
+
readonly message: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly type: "cloud-save-retry";
|
|
53
|
+
};
|
|
54
|
+
export declare function createSweepSessionState(): SweepSessionState;
|
|
55
|
+
export declare function reduceSweepSession(state: SweepSessionState, event: SweepSessionEvent): SweepSessionState;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSweepSessionState = createSweepSessionState;
|
|
4
|
+
exports.reduceSweepSession = reduceSweepSession;
|
|
5
|
+
const IDLE_CLOUD_SAVE = { status: "idle" };
|
|
6
|
+
function createSweepSessionState() {
|
|
7
|
+
return {
|
|
8
|
+
execution: { status: "idle" },
|
|
9
|
+
cloudSave: IDLE_CLOUD_SAVE,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function reduceSweepSession(state, event) {
|
|
13
|
+
switch (event.type) {
|
|
14
|
+
case "execution-started":
|
|
15
|
+
return {
|
|
16
|
+
execution: { status: "running", total: event.total },
|
|
17
|
+
cloudSave: IDLE_CLOUD_SAVE,
|
|
18
|
+
};
|
|
19
|
+
case "execution-succeeded":
|
|
20
|
+
return {
|
|
21
|
+
execution: { status: "succeeded", resultId: event.resultId },
|
|
22
|
+
cloudSave: IDLE_CLOUD_SAVE,
|
|
23
|
+
};
|
|
24
|
+
case "execution-failed":
|
|
25
|
+
return {
|
|
26
|
+
execution: { status: "failed", message: event.message },
|
|
27
|
+
cloudSave: IDLE_CLOUD_SAVE,
|
|
28
|
+
};
|
|
29
|
+
case "execution-cancelled":
|
|
30
|
+
return {
|
|
31
|
+
execution: { status: "cancelled" },
|
|
32
|
+
cloudSave: IDLE_CLOUD_SAVE,
|
|
33
|
+
};
|
|
34
|
+
case "execution-reset":
|
|
35
|
+
return createSweepSessionState();
|
|
36
|
+
case "cloud-save-started":
|
|
37
|
+
case "cloud-save-retry":
|
|
38
|
+
return applyCloudSave(state, { status: "saving" });
|
|
39
|
+
case "cloud-save-succeeded":
|
|
40
|
+
return applyCloudSave(state, {
|
|
41
|
+
status: "saved",
|
|
42
|
+
sweepId: event.sweepId,
|
|
43
|
+
});
|
|
44
|
+
case "cloud-save-failed":
|
|
45
|
+
return applyCloudSave(state, {
|
|
46
|
+
status: "failed",
|
|
47
|
+
message: event.message,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function applyCloudSave(state, cloudSave) {
|
|
52
|
+
if (state.execution.status !== "succeeded") {
|
|
53
|
+
return state;
|
|
54
|
+
}
|
|
55
|
+
return { ...state, cloudSave };
|
|
56
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type SweepSubscriptionTier = "free" | "pro" | "pro_max";
|
|
2
|
+
export type SweepSearchMode = "standard" | "fast";
|
|
3
|
+
export interface SweepCoordinate {
|
|
4
|
+
readonly values: readonly number[];
|
|
5
|
+
}
|
|
6
|
+
export interface SweepAxisWindow {
|
|
7
|
+
readonly min: number;
|
|
8
|
+
readonly max: number;
|
|
9
|
+
readonly step: number;
|
|
10
|
+
}
|
|
11
|
+
/** Explicit values, or a min/max/step window around the current value. */
|
|
12
|
+
export interface SweepAxisInput {
|
|
13
|
+
readonly path: readonly string[];
|
|
14
|
+
readonly current: number;
|
|
15
|
+
readonly isInteger: boolean;
|
|
16
|
+
readonly min?: number;
|
|
17
|
+
readonly max?: number;
|
|
18
|
+
readonly minExclusive?: boolean;
|
|
19
|
+
readonly maxExclusive?: boolean;
|
|
20
|
+
readonly values?: readonly number[];
|
|
21
|
+
readonly window?: SweepAxisWindow;
|
|
22
|
+
}
|
|
23
|
+
export interface SweepPlanAxis {
|
|
24
|
+
readonly path: readonly string[];
|
|
25
|
+
readonly current: number;
|
|
26
|
+
readonly values: readonly number[];
|
|
27
|
+
}
|
|
28
|
+
export interface SweepPlan {
|
|
29
|
+
readonly axes: readonly SweepPlanAxis[];
|
|
30
|
+
readonly coordinates: readonly SweepCoordinate[];
|
|
31
|
+
readonly requestedCombinationCount: number;
|
|
32
|
+
readonly sampled: boolean;
|
|
33
|
+
}
|
|
34
|
+
export type SweepConfigRecord = {
|
|
35
|
+
readonly [key: string]: unknown;
|
|
36
|
+
};
|
|
@@ -34,6 +34,14 @@ export interface EngineBacktestRunArgs {
|
|
|
34
34
|
/** Replay/download bar. Defaults to 1m; never finer. */
|
|
35
35
|
readonly replayTimeframe: string;
|
|
36
36
|
}
|
|
37
|
+
export type SweepMode = "neighborhood" | "range";
|
|
38
|
+
export type SweepSearchMode = "standard" | "fast";
|
|
39
|
+
export interface EngineBacktestSweepArgs extends EngineBacktestRunArgs {
|
|
40
|
+
readonly axesRaw?: string;
|
|
41
|
+
readonly mode: SweepMode;
|
|
42
|
+
readonly searchMode: SweepSearchMode;
|
|
43
|
+
readonly concurrency: number;
|
|
44
|
+
}
|
|
37
45
|
export interface EngineBacktestSeriesRequirement {
|
|
38
46
|
readonly symbol: string;
|
|
39
47
|
readonly timeframe: string;
|
|
@@ -105,6 +113,37 @@ export interface EngineBacktestResult {
|
|
|
105
113
|
}>;
|
|
106
114
|
readonly warnings?: string[];
|
|
107
115
|
}
|
|
116
|
+
export interface EngineBacktestBatchVariant {
|
|
117
|
+
readonly runId: string;
|
|
118
|
+
readonly config: unknown;
|
|
119
|
+
}
|
|
120
|
+
export interface EngineBacktestBatchRequest {
|
|
121
|
+
readonly version: 1;
|
|
122
|
+
readonly batchId: string;
|
|
123
|
+
readonly baseScenario: Omit<EngineBacktestScenario, "tape">;
|
|
124
|
+
readonly variants: readonly EngineBacktestBatchVariant[];
|
|
125
|
+
readonly tape: EngineBacktestTapeInput;
|
|
126
|
+
}
|
|
127
|
+
export interface EngineBacktestBatchPointResult {
|
|
128
|
+
readonly runId: string;
|
|
129
|
+
readonly status: "completed" | "failed";
|
|
130
|
+
readonly metrics: EngineBacktestMetrics;
|
|
131
|
+
readonly errors?: Array<{
|
|
132
|
+
code: string;
|
|
133
|
+
message: string;
|
|
134
|
+
path?: string;
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
137
|
+
export interface EngineBacktestBatchResult {
|
|
138
|
+
readonly batchId: string;
|
|
139
|
+
readonly status: "completed" | "failed";
|
|
140
|
+
readonly results: readonly EngineBacktestBatchPointResult[];
|
|
141
|
+
readonly errors?: Array<{
|
|
142
|
+
code: string;
|
|
143
|
+
message: string;
|
|
144
|
+
path?: string;
|
|
145
|
+
}>;
|
|
146
|
+
}
|
|
108
147
|
export interface EngineBacktestSupportReason {
|
|
109
148
|
readonly code: string;
|
|
110
149
|
readonly message: string;
|
|
@@ -180,6 +219,7 @@ export interface BacktestClientLike {
|
|
|
180
219
|
readonly config: unknown;
|
|
181
220
|
}): Promise<EngineBacktestPlan | EngineBacktestFailure>;
|
|
182
221
|
runBacktest(scenario: EngineBacktestScenario, buffers: Record<string, ArrayBuffer>, onProgress?: (fraction: number) => void): Promise<EngineBacktestResult>;
|
|
222
|
+
runBacktestBatch(batch: EngineBacktestBatchRequest, buffers: Record<string, ArrayBuffer>, onProgress?: (fraction: number) => void): Promise<EngineBacktestBatchResult>;
|
|
183
223
|
terminate(): void;
|
|
184
224
|
}
|
|
185
225
|
export interface BacktestWasmModule {
|
|
@@ -237,6 +277,8 @@ export interface CreateRunRequestBody {
|
|
|
237
277
|
readonly metrics: EngineBacktestMetrics;
|
|
238
278
|
readonly engineVersion: string;
|
|
239
279
|
readonly configSchemaVersion: number;
|
|
280
|
+
/** Compact [[unix_ms, cumulative_return]]; omitted when the local run has no series. */
|
|
281
|
+
readonly returnCurve?: ReadonlyArray<readonly [number, number]>;
|
|
240
282
|
}
|
|
241
283
|
export interface EngineBacktestRunSuccess {
|
|
242
284
|
readonly metrics: EngineBacktestMetrics;
|
|
@@ -247,3 +289,91 @@ export interface EngineBacktestRunSuccess {
|
|
|
247
289
|
readonly persisted: boolean;
|
|
248
290
|
readonly coverageWarnings: readonly string[];
|
|
249
291
|
}
|
|
292
|
+
export interface EngineBacktestSweepPointRow {
|
|
293
|
+
readonly coordinate: {
|
|
294
|
+
readonly values: readonly number[];
|
|
295
|
+
};
|
|
296
|
+
readonly status: "ok" | "failed";
|
|
297
|
+
readonly metrics?: {
|
|
298
|
+
readonly returnPct: number;
|
|
299
|
+
readonly maxDrawdownPct: number;
|
|
300
|
+
readonly sharpeRatio: number;
|
|
301
|
+
readonly winRatePct: number;
|
|
302
|
+
readonly maxLeverage?: number;
|
|
303
|
+
readonly liquidationCount: number;
|
|
304
|
+
readonly netPnl: number;
|
|
305
|
+
readonly finalEquity: number;
|
|
306
|
+
readonly tradeCount: number;
|
|
307
|
+
};
|
|
308
|
+
readonly error?: string;
|
|
309
|
+
}
|
|
310
|
+
export interface CreateSweepPointSummary {
|
|
311
|
+
readonly coordinate: {
|
|
312
|
+
readonly values: readonly number[];
|
|
313
|
+
};
|
|
314
|
+
readonly status: "ok" | "failed";
|
|
315
|
+
readonly error?: string;
|
|
316
|
+
readonly metrics?: EngineBacktestSweepPointRow["metrics"];
|
|
317
|
+
}
|
|
318
|
+
export interface CreateSweepRequestBody {
|
|
319
|
+
readonly clientSweepId: string;
|
|
320
|
+
readonly baseInputSnapshot: PersistedSnapshot;
|
|
321
|
+
readonly axes: ReadonlyArray<{
|
|
322
|
+
readonly path: readonly string[];
|
|
323
|
+
readonly current: number;
|
|
324
|
+
readonly values: readonly number[];
|
|
325
|
+
}>;
|
|
326
|
+
readonly searchMetadata: {
|
|
327
|
+
readonly mode: SweepMode;
|
|
328
|
+
readonly searchMode: SweepSearchMode;
|
|
329
|
+
readonly concurrency: number;
|
|
330
|
+
readonly requestedCombinationCount: number;
|
|
331
|
+
readonly sampled: boolean;
|
|
332
|
+
};
|
|
333
|
+
readonly aggregateSummary: {
|
|
334
|
+
readonly successfulCount: number;
|
|
335
|
+
readonly failedCount: number;
|
|
336
|
+
readonly liquidatedCount: number;
|
|
337
|
+
readonly elapsedMs: number;
|
|
338
|
+
readonly best: {
|
|
339
|
+
readonly coordinate: {
|
|
340
|
+
readonly values: readonly number[];
|
|
341
|
+
};
|
|
342
|
+
readonly returnPct: number;
|
|
343
|
+
} | null;
|
|
344
|
+
};
|
|
345
|
+
readonly pointSummaries: readonly CreateSweepPointSummary[];
|
|
346
|
+
readonly engineVersion: string;
|
|
347
|
+
readonly configSchemaVersion: number;
|
|
348
|
+
}
|
|
349
|
+
export interface EngineBacktestSweepSuccess {
|
|
350
|
+
readonly persisted: boolean;
|
|
351
|
+
readonly sweepId?: string;
|
|
352
|
+
readonly clientSweepId?: string;
|
|
353
|
+
readonly mode: SweepMode;
|
|
354
|
+
readonly searchMode: SweepSearchMode;
|
|
355
|
+
readonly requestedCombinationCount: number;
|
|
356
|
+
readonly sampled: boolean;
|
|
357
|
+
readonly combinationCount: number;
|
|
358
|
+
readonly successfulCount: number;
|
|
359
|
+
readonly failedCount: number;
|
|
360
|
+
readonly liquidatedCount: number;
|
|
361
|
+
readonly elapsedMs: number;
|
|
362
|
+
readonly best: {
|
|
363
|
+
readonly coordinate: {
|
|
364
|
+
readonly values: readonly number[];
|
|
365
|
+
};
|
|
366
|
+
readonly returnPct: number;
|
|
367
|
+
readonly config: unknown;
|
|
368
|
+
} | null;
|
|
369
|
+
readonly points: readonly EngineBacktestSweepPointRow[];
|
|
370
|
+
readonly engineVersion: string;
|
|
371
|
+
readonly experimentId?: string;
|
|
372
|
+
readonly experimentUrl?: string;
|
|
373
|
+
readonly coverageWarnings: readonly string[];
|
|
374
|
+
readonly axes: ReadonlyArray<{
|
|
375
|
+
readonly path: readonly string[];
|
|
376
|
+
readonly current: number;
|
|
377
|
+
readonly values: readonly number[];
|
|
378
|
+
}>;
|
|
379
|
+
}
|
package/dist/install/exec.js
CHANGED
|
@@ -50,7 +50,11 @@ async function confirmTty(message) {
|
|
|
50
50
|
try {
|
|
51
51
|
const answer = await rl.question(`${message} [Y/n] `);
|
|
52
52
|
const token = answer.trim().toLowerCase();
|
|
53
|
-
return token === "" ||
|
|
53
|
+
return (token === "" ||
|
|
54
|
+
token === "y" ||
|
|
55
|
+
token === "yes" ||
|
|
56
|
+
token === "是" ||
|
|
57
|
+
token === "好");
|
|
54
58
|
}
|
|
55
59
|
finally {
|
|
56
60
|
rl.close();
|
package/dist/install/wizard.js
CHANGED
|
@@ -49,24 +49,21 @@ function parseNpmListVersion(output) {
|
|
|
49
49
|
return match?.[1] ?? null;
|
|
50
50
|
}
|
|
51
51
|
function skillsListHasAlphafox(output) {
|
|
52
|
-
|
|
53
|
-
return new RegExp(`(^|\\s)${prefix}[\\w-]+`, "m").test(output);
|
|
52
|
+
return /(^|\s)alphafox(?:-[\w-]+)?(?=\s|$)/m.test(output);
|
|
54
53
|
}
|
|
55
54
|
function nextSteps(input) {
|
|
56
|
-
const steps = [
|
|
57
|
-
"Restart the AI tool so newly installed Skills are loaded.",
|
|
58
|
-
];
|
|
55
|
+
const steps = ["请重启 AI 工具,以便加载刚安装的 Skills。"];
|
|
59
56
|
if (input.auth.action === "skipped" || input.auth.action === "planned") {
|
|
60
57
|
steps.push("alphafox auth login --browser --format json --no-input", "alphafox auth login --no-wait --format json --no-input");
|
|
61
58
|
}
|
|
62
|
-
steps.push("alphafox doctor --format json --no-input", `Agent
|
|
59
|
+
steps.push("alphafox doctor --format json --no-input", `Agent 安装指南:${types_1.AGENT_INSTALL_GUIDE_BLOB_URL}`);
|
|
63
60
|
if (input.dryRun) {
|
|
64
|
-
steps.unshift("
|
|
61
|
+
steps.unshift("这是 --dry-run,去掉该参数再运行才会真正安装。");
|
|
65
62
|
}
|
|
66
63
|
return steps;
|
|
67
64
|
}
|
|
68
65
|
async function runInstallWizard(flags, env = process.env, runner = (0, exec_1.createDefaultInstallRunner)(env, defaultSearchDirs())) {
|
|
69
|
-
runner.log("
|
|
66
|
+
runner.log("正在安装 Alphafox CLI…");
|
|
70
67
|
const installedVer = await readGloballyInstalledVersion(runner);
|
|
71
68
|
const latestVer = await readLatestVersion(runner);
|
|
72
69
|
const needsUpgrade = Boolean(installedVer) &&
|
|
@@ -140,7 +137,7 @@ async function npmPrefixGlobal(runner) {
|
|
|
140
137
|
async function stepInstallCli(flags, runner, versions) {
|
|
141
138
|
const { installedVer, latestVer, needsUpgrade } = versions;
|
|
142
139
|
if (installedVer && !needsUpgrade) {
|
|
143
|
-
runner.log(
|
|
140
|
+
runner.log(`已安装(${installedVer}),已跳过`);
|
|
144
141
|
return {
|
|
145
142
|
action: flags.dryRun ? "planned" : "skipped",
|
|
146
143
|
version: installedVer,
|
|
@@ -149,8 +146,8 @@ async function stepInstallCli(flags, runner, versions) {
|
|
|
149
146
|
}
|
|
150
147
|
if (flags.dryRun) {
|
|
151
148
|
runner.log(needsUpgrade
|
|
152
|
-
?
|
|
153
|
-
:
|
|
149
|
+
? `将升级 ${version_1.CLI_PACKAGE}(${installedVer} → ${latestVer ?? "latest"})`
|
|
150
|
+
: `将全局安装 ${version_1.CLI_PACKAGE}`);
|
|
154
151
|
return {
|
|
155
152
|
action: "planned",
|
|
156
153
|
previousVersion: installedVer ?? undefined,
|
|
@@ -158,8 +155,8 @@ async function stepInstallCli(flags, runner, versions) {
|
|
|
158
155
|
};
|
|
159
156
|
}
|
|
160
157
|
runner.log(needsUpgrade
|
|
161
|
-
?
|
|
162
|
-
:
|
|
158
|
+
? `正在升级 ${version_1.CLI_PACKAGE}(${installedVer} → ${latestVer})…`
|
|
159
|
+
: `正在全局安装 ${version_1.CLI_PACKAGE}…`);
|
|
163
160
|
try {
|
|
164
161
|
await runner.exec("npm", ["install", "-g", version_1.CLI_PACKAGE], {
|
|
165
162
|
timeoutMs: NPM_TIMEOUT_MS,
|
|
@@ -169,13 +166,13 @@ async function stepInstallCli(flags, runner, versions) {
|
|
|
169
166
|
throw new types_1.InstallError({
|
|
170
167
|
type: "install",
|
|
171
168
|
subtype: "npm_global_failed",
|
|
172
|
-
message:
|
|
169
|
+
message: `全局安装 ${version_1.CLI_PACKAGE} 失败。`,
|
|
173
170
|
hint: `npm install -g ${version_1.CLI_PACKAGE}`,
|
|
174
171
|
details: err instanceof Error ? err.message : String(err),
|
|
175
172
|
});
|
|
176
173
|
}
|
|
177
174
|
const after = (await readGloballyInstalledVersion(runner)) ?? latestVer ?? version_1.CLI_VERSION;
|
|
178
|
-
runner.log(needsUpgrade ?
|
|
175
|
+
runner.log(needsUpgrade ? `已升级到 ${after}` : "已全局安装");
|
|
179
176
|
return {
|
|
180
177
|
action: needsUpgrade ? "upgraded" : "installed",
|
|
181
178
|
version: after,
|
|
@@ -214,7 +211,7 @@ async function stepInstallSkills(flags, runner) {
|
|
|
214
211
|
const sources = await resolveSkillsSources(runner);
|
|
215
212
|
const already = flags.dryRun ? false : await skillsAlreadyInstalled(runner);
|
|
216
213
|
if (already) {
|
|
217
|
-
runner.log("Skills
|
|
214
|
+
runner.log("Skills 已安装,已跳过");
|
|
218
215
|
return {
|
|
219
216
|
action: "skipped",
|
|
220
217
|
source: sources[0],
|
|
@@ -223,19 +220,19 @@ async function stepInstallSkills(flags, runner) {
|
|
|
223
220
|
};
|
|
224
221
|
}
|
|
225
222
|
if (flags.dryRun) {
|
|
226
|
-
runner.log(
|
|
223
|
+
runner.log(`将从 ${sources[0] ?? types_1.SKILLS_GITHUB_SOURCE} 安装 Skills`);
|
|
227
224
|
return {
|
|
228
225
|
action: "planned",
|
|
229
226
|
source: sources[0] ?? types_1.SKILLS_GITHUB_SOURCE,
|
|
230
227
|
scope: "global",
|
|
231
228
|
};
|
|
232
229
|
}
|
|
233
|
-
runner.log("
|
|
230
|
+
runner.log("正在安装 AI Skills…");
|
|
234
231
|
let lastError;
|
|
235
232
|
for (const source of sources) {
|
|
236
233
|
try {
|
|
237
234
|
await runner.exec("npx", ["-y", "skills", "add", source, "-y", "-g"], { timeoutMs: SKILLS_TIMEOUT_MS });
|
|
238
|
-
runner.log(`Skills
|
|
235
|
+
runner.log(`Skills 已安装(${source})`);
|
|
239
236
|
return { action: "installed", source, scope: "global" };
|
|
240
237
|
}
|
|
241
238
|
catch (err) {
|
|
@@ -245,7 +242,7 @@ async function stepInstallSkills(flags, runner) {
|
|
|
245
242
|
throw new types_1.InstallError({
|
|
246
243
|
type: "install",
|
|
247
244
|
subtype: "skills_add_failed",
|
|
248
|
-
message: "
|
|
245
|
+
message: "安装 Agent Skills 失败。",
|
|
249
246
|
hint: `npx skills add ${types_1.SKILLS_GITHUB_SOURCE} -y -g`,
|
|
250
247
|
details: lastError instanceof Error ? lastError.message : String(lastError),
|
|
251
248
|
});
|
|
@@ -274,16 +271,16 @@ async function stepAuth(flags, runner) {
|
|
|
274
271
|
if (flags.dryRun) {
|
|
275
272
|
return { action: "planned", reason: "non-interactive" };
|
|
276
273
|
}
|
|
277
|
-
runner.log("
|
|
274
|
+
runner.log("要完成安装,请运行:\n alphafox auth login --browser\n alphafox auth login --no-wait --format json --no-input");
|
|
278
275
|
return { action: "skipped", reason: "non-interactive" };
|
|
279
276
|
}
|
|
280
277
|
if (flags.dryRun) {
|
|
281
|
-
runner.log("
|
|
278
|
+
runner.log("将提示运行 alphafox auth login --browser");
|
|
282
279
|
return { action: "planned", reason: "tty" };
|
|
283
280
|
}
|
|
284
|
-
const yes = await runner.confirm("
|
|
281
|
+
const yes = await runner.confirm("现在登录,以便 Agent 可以调用公开 Application API?");
|
|
285
282
|
if (!yes) {
|
|
286
|
-
runner.log("
|
|
283
|
+
runner.log("已跳过登录。之后可运行 alphafox auth login。");
|
|
287
284
|
return { action: "skipped", reason: "declined" };
|
|
288
285
|
}
|
|
289
286
|
const bin = await resolveAlphafoxBin(runner);
|
|
@@ -291,19 +288,19 @@ async function stepAuth(flags, runner) {
|
|
|
291
288
|
throw new types_1.InstallError({
|
|
292
289
|
type: "install",
|
|
293
290
|
subtype: "cli_bin_missing",
|
|
294
|
-
message: "alphafox
|
|
295
|
-
hint: "
|
|
291
|
+
message: "安装完成后找不到 alphafox 可执行文件。",
|
|
292
|
+
hint: "请确认 npm 全局 bin 目录在 PATH 中,然后运行 alphafox auth login --browser。",
|
|
296
293
|
});
|
|
297
294
|
}
|
|
298
295
|
try {
|
|
299
296
|
await runner.execInherit(bin, ["auth", "login", "--browser"], {
|
|
300
297
|
timeoutMs: 10 * 60_000,
|
|
301
298
|
});
|
|
302
|
-
runner.log("
|
|
299
|
+
runner.log("登录完成");
|
|
303
300
|
return { action: "completed" };
|
|
304
301
|
}
|
|
305
302
|
catch (err) {
|
|
306
|
-
runner.log("
|
|
303
|
+
runner.log("登录失败。请运行 alphafox auth login --browser 重试。");
|
|
307
304
|
return {
|
|
308
305
|
action: "failed",
|
|
309
306
|
reason: err instanceof Error ? err.message : String(err),
|
|
@@ -319,7 +316,7 @@ function installHelpData() {
|
|
|
319
316
|
"alphafox install --no-auth",
|
|
320
317
|
"alphafox install --dry-run",
|
|
321
318
|
],
|
|
322
|
-
description: "
|
|
319
|
+
description: "全局安装 @alphafox/cli,并通过 npx skills add 把同版本 Agent Skills 写入本机已检测到的 Agent(Cursor、Claude Code、Codex 等)的用户级目录。",
|
|
323
320
|
agentGuide: types_1.AGENT_INSTALL_GUIDE_BLOB_URL,
|
|
324
321
|
};
|
|
325
322
|
}
|