@alphafox/cli 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/clean.d.ts +1 -0
- package/dist/cache/clean.js +12 -0
- package/dist/cache/inspect.d.ts +7 -0
- package/dist/cache/inspect.js +31 -0
- package/dist/cache/paths.d.ts +5 -0
- package/dist/cache/paths.js +41 -0
- package/dist/cache/run-command.d.ts +7 -0
- package/dist/cache/run-command.js +85 -0
- package/dist/commands/run.js +14 -9
- package/dist/engine-backtest/run-command.js +2 -0
- package/dist/engine-backtest/sweep-command.js +2 -0
- package/dist/engine-backtest/types.d.ts +1 -0
- package/dist/skills-manifest.json +49 -37
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/account/SKILL.md +1 -1
- package/skills/admin/SKILL.md +1 -1
- package/skills/alphafox/SKILL.md +14 -3
- package/skills/alphafox-shared/SKILL.md +1 -1
- package/skills/auth/SKILL.md +1 -1
- package/skills/cache/SKILL.md +38 -0
- package/skills/engine-backtest/SKILL.md +2 -1
- package/skills/exchange/SKILL.md +1 -1
- package/skills/market/SKILL.md +1 -1
- package/skills/notification/SKILL.md +1 -1
- package/skills/strategy/SKILL.md +1 -1
- package/skills/trading/SKILL.md +1 -1
- package/vendor/backtest-runner/index.d.ts +15 -0
- package/vendor/backtest-runner/index.mjs +4 -0
- package/vendor/backtest-runner/lib/tape-loader.mjs +127 -53
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function removeCacheRoot(directory: string, env?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeCacheRoot = removeCacheRoot;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const paths_1 = require("./paths");
|
|
6
|
+
function removeCacheRoot(directory, env = process.env) {
|
|
7
|
+
(0, paths_1.assertSafeCacheRoot)(directory, env);
|
|
8
|
+
if (!(0, node_fs_1.existsSync)(directory)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
(0, node_fs_1.rmSync)(directory, { recursive: true, force: true });
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inspectDirectory = inspectDirectory;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function inspectDirectory(path) {
|
|
7
|
+
if (!(0, node_fs_1.existsSync)(path)) {
|
|
8
|
+
return { path, exists: false, bytes: 0, files: 0 };
|
|
9
|
+
}
|
|
10
|
+
const root = (0, node_fs_1.statSync)(path);
|
|
11
|
+
if (root.isFile()) {
|
|
12
|
+
return { path, exists: true, bytes: root.size, files: 1 };
|
|
13
|
+
}
|
|
14
|
+
let bytes = 0;
|
|
15
|
+
let files = 0;
|
|
16
|
+
const walk = (dir) => {
|
|
17
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
|
|
18
|
+
const next = (0, node_path_1.join)(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
walk(next);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (entry.isFile()) {
|
|
24
|
+
files += 1;
|
|
25
|
+
bytes += (0, node_fs_1.statSync)(next).size;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
walk(path);
|
|
30
|
+
return { path, exists: true, bytes, files };
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Remind Agents to offer cleanup at or above this tape-cache size. */
|
|
2
|
+
export declare const TAPE_CACHE_REMIND_BYTES: number;
|
|
3
|
+
export declare function resolveTapeCacheDir(env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
export declare function resolveRuntimeCacheRoot(env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function assertSafeCacheRoot(directory: string, env?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TAPE_CACHE_REMIND_BYTES = void 0;
|
|
4
|
+
exports.resolveTapeCacheDir = resolveTapeCacheDir;
|
|
5
|
+
exports.resolveRuntimeCacheRoot = resolveRuntimeCacheRoot;
|
|
6
|
+
exports.assertSafeCacheRoot = assertSafeCacheRoot;
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
/** Remind Agents to offer cleanup at or above this tape-cache size. */
|
|
10
|
+
exports.TAPE_CACHE_REMIND_BYTES = 512 * 1024 * 1024;
|
|
11
|
+
function resolveTapeCacheDir(env = process.env) {
|
|
12
|
+
const override = env.ALPHAFOX_TAPE_CACHE_DIR?.trim();
|
|
13
|
+
if (override) {
|
|
14
|
+
return (0, node_path_1.resolve)(override);
|
|
15
|
+
}
|
|
16
|
+
return (0, node_path_1.resolve)((0, node_path_1.join)((0, node_os_1.homedir)(), ".alphafox", "cache", "engine-backtest"));
|
|
17
|
+
}
|
|
18
|
+
function resolveRuntimeCacheRoot(env = process.env) {
|
|
19
|
+
const override = env.ALPHAFOX_BACKTEST_RUNTIME_CACHE_DIR?.trim();
|
|
20
|
+
if (override) {
|
|
21
|
+
return (0, node_path_1.resolve)(override);
|
|
22
|
+
}
|
|
23
|
+
const xdg = env.XDG_CACHE_HOME?.trim();
|
|
24
|
+
return (0, node_path_1.resolve)((0, node_path_1.join)(xdg || (0, node_path_1.join)((0, node_os_1.homedir)(), ".cache"), "alphafox", "engine-backtest"));
|
|
25
|
+
}
|
|
26
|
+
function assertSafeCacheRoot(directory, env = process.env) {
|
|
27
|
+
const resolved = (0, node_path_1.resolve)(directory);
|
|
28
|
+
const tape = resolveTapeCacheDir(env);
|
|
29
|
+
const runtime = resolveRuntimeCacheRoot(env);
|
|
30
|
+
if (resolved === tape || resolved === runtime) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if ((0, node_path_1.basename)(resolved) === "engine-backtest") {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
throw Object.assign(new Error(`Refusing to touch cache directory ${resolved}`), {
|
|
37
|
+
type: "usage",
|
|
38
|
+
subtype: "cache_root_unsafe",
|
|
39
|
+
status: 400,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface CacheCliFlags {
|
|
2
|
+
readonly format: "json" | "jsonl" | "text";
|
|
3
|
+
readonly yes: boolean;
|
|
4
|
+
readonly dryRun: boolean;
|
|
5
|
+
readonly jq?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function cmdCache(args: string[], flags: CacheCliFlags, env?: NodeJS.ProcessEnv): Promise<number>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdCache = cmdCache;
|
|
4
|
+
const envelope_1 = require("../envelope");
|
|
5
|
+
const clean_1 = require("./clean");
|
|
6
|
+
const inspect_1 = require("./inspect");
|
|
7
|
+
const paths_1 = require("./paths");
|
|
8
|
+
async function cmdCache(args, flags, env = process.env) {
|
|
9
|
+
const sub = args[0];
|
|
10
|
+
if (sub === "status" || !sub || sub === "help" || sub === "--help" || sub === "-h") {
|
|
11
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
12
|
+
(0, envelope_1.writeSuccess)({
|
|
13
|
+
name: "cache",
|
|
14
|
+
usage: [
|
|
15
|
+
"alphafox cache status",
|
|
16
|
+
"alphafox cache clean [--tape|--runtime|--all] [--yes|--dry-run]",
|
|
17
|
+
],
|
|
18
|
+
}, { format: flags.format, jq: flags.jq });
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
const tape = (0, inspect_1.inspectDirectory)((0, paths_1.resolveTapeCacheDir)(env));
|
|
22
|
+
const runtime = (0, inspect_1.inspectDirectory)((0, paths_1.resolveRuntimeCacheRoot)(env));
|
|
23
|
+
(0, envelope_1.writeSuccess)({
|
|
24
|
+
tape: { ...tape, large: tape.bytes >= paths_1.TAPE_CACHE_REMIND_BYTES },
|
|
25
|
+
runtime,
|
|
26
|
+
remindAfterBytes: paths_1.TAPE_CACHE_REMIND_BYTES,
|
|
27
|
+
totalBytes: tape.bytes + runtime.bytes,
|
|
28
|
+
}, { format: flags.format, jq: flags.jq });
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
if (sub === "clean") {
|
|
32
|
+
const rest = args.slice(1);
|
|
33
|
+
const all = rest.includes("--all");
|
|
34
|
+
const runtimeOnly = rest.includes("--runtime");
|
|
35
|
+
const tapeOnly = rest.includes("--tape") || (!all && !runtimeOnly);
|
|
36
|
+
const clearTape = all || tapeOnly;
|
|
37
|
+
const clearRuntime = all || runtimeOnly;
|
|
38
|
+
const tapePath = (0, paths_1.resolveTapeCacheDir)(env);
|
|
39
|
+
const runtimePath = (0, paths_1.resolveRuntimeCacheRoot)(env);
|
|
40
|
+
const tapeBefore = (0, inspect_1.inspectDirectory)(tapePath);
|
|
41
|
+
const runtimeBefore = (0, inspect_1.inspectDirectory)(runtimePath);
|
|
42
|
+
if (flags.dryRun) {
|
|
43
|
+
(0, envelope_1.writeSuccess)({
|
|
44
|
+
dryRun: true,
|
|
45
|
+
cleared: [
|
|
46
|
+
...(clearTape ? ["tape"] : []),
|
|
47
|
+
...(clearRuntime ? ["runtime"] : []),
|
|
48
|
+
],
|
|
49
|
+
tape: tapeBefore,
|
|
50
|
+
runtime: runtimeBefore,
|
|
51
|
+
}, { format: flags.format, jq: flags.jq });
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
if (!flags.yes) {
|
|
55
|
+
(0, envelope_1.writeError)({
|
|
56
|
+
type: "confirmation",
|
|
57
|
+
subtype: "yes_required",
|
|
58
|
+
message: "Cleaning local backtest cache requires --yes (or --dry-run).",
|
|
59
|
+
status: 400,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (clearTape) {
|
|
63
|
+
(0, clean_1.removeCacheRoot)(tapePath, env);
|
|
64
|
+
}
|
|
65
|
+
if (clearRuntime) {
|
|
66
|
+
(0, clean_1.removeCacheRoot)(runtimePath, env);
|
|
67
|
+
}
|
|
68
|
+
(0, envelope_1.writeSuccess)({
|
|
69
|
+
dryRun: false,
|
|
70
|
+
cleared: [
|
|
71
|
+
...(clearTape ? ["tape"] : []),
|
|
72
|
+
...(clearRuntime ? ["runtime"] : []),
|
|
73
|
+
],
|
|
74
|
+
tape: (0, inspect_1.inspectDirectory)(tapePath),
|
|
75
|
+
runtime: (0, inspect_1.inspectDirectory)(runtimePath),
|
|
76
|
+
bytesFreed: (clearTape ? tapeBefore.bytes : 0) +
|
|
77
|
+
(clearRuntime ? runtimeBefore.bytes : 0),
|
|
78
|
+
}, { format: flags.format, jq: flags.jq });
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
(0, envelope_1.writeError)({
|
|
82
|
+
type: "usage",
|
|
83
|
+
message: "Usage: alphafox cache status|clean",
|
|
84
|
+
});
|
|
85
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -14,11 +14,12 @@ const client_1 = require("../http/client");
|
|
|
14
14
|
const store_1 = require("../keychain/store");
|
|
15
15
|
const confirmation_1 = require("../safety/confirmation");
|
|
16
16
|
const version_1 = require("../version");
|
|
17
|
-
const run_command_1 = require("../
|
|
18
|
-
const run_command_2 = require("../
|
|
19
|
-
const run_command_3 = require("../
|
|
17
|
+
const run_command_1 = require("../cache/run-command");
|
|
18
|
+
const run_command_2 = require("../engine-backtest/run-command");
|
|
19
|
+
const run_command_3 = require("../resolve-symbols/run-command");
|
|
20
|
+
const run_command_4 = require("../skills/run-command");
|
|
20
21
|
const notify_1 = require("../update/notify");
|
|
21
|
-
const
|
|
22
|
+
const run_command_5 = require("../update/run-command");
|
|
22
23
|
const validate_body_1 = require("../catalog/validate-body");
|
|
23
24
|
const types_1 = require("../install/types");
|
|
24
25
|
const wizard_1 = require("../install/wizard");
|
|
@@ -102,6 +103,7 @@ async function runCli(argv, env = process.env) {
|
|
|
102
103
|
"alphafox api METHOD PATH [--body JSON|--config @file]",
|
|
103
104
|
"alphafox engine-backtest run --experiment <uuid> --definition <id> --config @file --exchange <id> --range FROM..TO --initial-equity N",
|
|
104
105
|
"alphafox engine-backtest sweep --experiment <uuid> --definition <id> --config @file --axes @file --exchange <id> --range FROM..TO --initial-equity N --no-persist",
|
|
106
|
+
"alphafox cache status|clean [--tape|--runtime|--all]",
|
|
105
107
|
"alphafox resolve-symbols <query...> [--exchange binance] [--asset-class equity_perp]",
|
|
106
108
|
"alphafox <domain> <resource> <action> [flags]",
|
|
107
109
|
],
|
|
@@ -120,7 +122,8 @@ async function runCli(argv, env = process.env) {
|
|
|
120
122
|
if (cmd !== "version" &&
|
|
121
123
|
cmd !== "install" &&
|
|
122
124
|
cmd !== "update" &&
|
|
123
|
-
cmd !== "skills"
|
|
125
|
+
cmd !== "skills" &&
|
|
126
|
+
cmd !== "cache") {
|
|
124
127
|
assertCatalogCompatible();
|
|
125
128
|
}
|
|
126
129
|
switch (cmd) {
|
|
@@ -129,9 +132,11 @@ async function runCli(argv, env = process.env) {
|
|
|
129
132
|
case "install":
|
|
130
133
|
return await cmdInstall(args, flags, env);
|
|
131
134
|
case "update":
|
|
132
|
-
return await (0,
|
|
135
|
+
return await (0, run_command_5.cmdUpdate)(args, flags, env);
|
|
133
136
|
case "skills":
|
|
134
|
-
return await (0,
|
|
137
|
+
return await (0, run_command_4.cmdSkills)(args, flags, env);
|
|
138
|
+
case "cache":
|
|
139
|
+
return await (0, run_command_1.cmdCache)(args, flags, env);
|
|
135
140
|
case "doctor":
|
|
136
141
|
return cmdDoctor(flags, env);
|
|
137
142
|
case "whoami":
|
|
@@ -154,7 +159,7 @@ async function runCli(argv, env = process.env) {
|
|
|
154
159
|
sub === "help" ||
|
|
155
160
|
sub === "--help" ||
|
|
156
161
|
sub === "-h") {
|
|
157
|
-
return await (0,
|
|
162
|
+
return await (0, run_command_2.cmdEngineBacktest)(args, flags, env);
|
|
158
163
|
}
|
|
159
164
|
// Hyphen built-in owns `run` and `sweep`. Underscore/hyphen catalog CRUD
|
|
160
165
|
// (engine_backtest.experiments.*) still goes through the typed tree.
|
|
@@ -162,7 +167,7 @@ async function runCli(argv, env = process.env) {
|
|
|
162
167
|
}
|
|
163
168
|
case "resolve-symbols":
|
|
164
169
|
case "resolve-symbol":
|
|
165
|
-
return await (0,
|
|
170
|
+
return await (0, run_command_3.cmdResolveSymbols)(args, flags, env);
|
|
166
171
|
default:
|
|
167
172
|
return await cmdTyped(cmd, args, flags, env);
|
|
168
173
|
}
|
|
@@ -14,6 +14,7 @@ const load_config_1 = require("./load-config");
|
|
|
14
14
|
const parse_args_1 = require("./parse-args");
|
|
15
15
|
const sweep_command_1 = require("./sweep-command");
|
|
16
16
|
const persist_1 = require("./persist");
|
|
17
|
+
const paths_1 = require("../cache/paths");
|
|
17
18
|
const replay_timeframe_1 = require("./replay-timeframe");
|
|
18
19
|
const resolve_packages_1 = require("./resolve-packages");
|
|
19
20
|
var load_config_2 = require("./load-config");
|
|
@@ -316,6 +317,7 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
|
|
|
316
317
|
fromMs: args.range.fromMs,
|
|
317
318
|
toMs: args.range.toMs,
|
|
318
319
|
dataQualityMode: args.dataQualityMode,
|
|
320
|
+
cacheDir: (0, paths_1.resolveTapeCacheDir)(env),
|
|
319
321
|
onProgress: (progress) => {
|
|
320
322
|
emitProgress(flags, writeLine, progress.stage || "tape", progress.fraction, progress.detail);
|
|
321
323
|
},
|
|
@@ -7,6 +7,7 @@ exports.executeEngineBacktestSweep = executeEngineBacktestSweep;
|
|
|
7
7
|
const node_crypto_1 = require("node:crypto");
|
|
8
8
|
const profiles_1 = require("../config/profiles");
|
|
9
9
|
const client_1 = require("../http/client");
|
|
10
|
+
const paths_1 = require("../cache/paths");
|
|
10
11
|
const store_1 = require("../keychain/store");
|
|
11
12
|
const errors_1 = require("./errors");
|
|
12
13
|
const load_config_1 = require("./load-config");
|
|
@@ -189,6 +190,7 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
|
|
|
189
190
|
fromMs: args.range.fromMs,
|
|
190
191
|
toMs: args.range.toMs,
|
|
191
192
|
dataQualityMode: args.dataQualityMode,
|
|
193
|
+
cacheDir: (0, paths_1.resolveTapeCacheDir)(env),
|
|
192
194
|
onProgress: (progress) => {
|
|
193
195
|
emitProgress(flags, writeLine, progress.stage || "tape", progress.fraction, progress.detail);
|
|
194
196
|
},
|
|
@@ -238,6 +238,7 @@ export interface BacktestRunnerModule {
|
|
|
238
238
|
readonly fromMs: number;
|
|
239
239
|
readonly toMs: number;
|
|
240
240
|
readonly dataQualityMode?: DataQualityMode;
|
|
241
|
+
readonly cacheDir?: string;
|
|
241
242
|
readonly onProgress?: (progress: TapeLoadProgress) => void;
|
|
242
243
|
}, options?: unknown): Promise<TapeLoadResult>;
|
|
243
244
|
assembleScenario(input: {
|
|
@@ -1,141 +1,153 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "@alphafox/cli",
|
|
4
|
-
"packageVersion": "0.3.
|
|
4
|
+
"packageVersion": "0.3.9",
|
|
5
5
|
"contractVersion": "2026-08-13",
|
|
6
|
-
"bundleHash": "
|
|
6
|
+
"bundleHash": "8693bb9b1b6f0c0bd70f75fe564a09ba829726f6e982d05c90a6595e8c36b715",
|
|
7
7
|
"skills": [
|
|
8
8
|
{
|
|
9
9
|
"name": "alphafox",
|
|
10
|
-
"version": "0.3.
|
|
10
|
+
"version": "0.3.9",
|
|
11
11
|
"files": [
|
|
12
12
|
{
|
|
13
13
|
"path": "SKILL.md",
|
|
14
|
-
"sha256": "
|
|
15
|
-
"size":
|
|
14
|
+
"sha256": "cb9f92b75cce3a03d4f2a9492b6ddd564541828bdb74acaf360cb3f2a0aa8447",
|
|
15
|
+
"size": 4405
|
|
16
16
|
}
|
|
17
17
|
],
|
|
18
|
-
"hash": "
|
|
18
|
+
"hash": "92b330a3f902405235e2a049ce6c98a7cad705f8aeab6ba3c2062df33b222aec"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "alphafox-account",
|
|
22
|
-
"version": "0.3.
|
|
22
|
+
"version": "0.3.9",
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
|
25
25
|
"path": "SKILL.md",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "4ea9c43c579ce7b4a4e5dd0e0ca10eeb793d19fc64e1cf8c4760b6d426524bf6",
|
|
27
27
|
"size": 783
|
|
28
28
|
}
|
|
29
29
|
],
|
|
30
|
-
"hash": "
|
|
30
|
+
"hash": "a94dc7e2e7a958394dbebba5b02e25c7114116b0b1a265be6b544e92f5e4ea65"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"name": "alphafox-admin",
|
|
34
|
-
"version": "0.3.
|
|
34
|
+
"version": "0.3.9",
|
|
35
35
|
"files": [
|
|
36
36
|
{
|
|
37
37
|
"path": "SKILL.md",
|
|
38
|
-
"sha256": "
|
|
38
|
+
"sha256": "6c800c87dd98fc054508ffc179fce19f455cc2da762525accadf2b1fda68e647",
|
|
39
39
|
"size": 787
|
|
40
40
|
}
|
|
41
41
|
],
|
|
42
|
-
"hash": "
|
|
42
|
+
"hash": "534d66e2614677fa7e40d3334f2e6992c632dccffc623c9dfcd963ae49eb66ac"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"name": "alphafox-auth",
|
|
46
|
-
"version": "0.3.
|
|
46
|
+
"version": "0.3.9",
|
|
47
47
|
"files": [
|
|
48
48
|
{
|
|
49
49
|
"path": "SKILL.md",
|
|
50
|
-
"sha256": "
|
|
50
|
+
"sha256": "a12ab3e613b26246ef6eb8d1289307c266d3c82861a8d6567856d63dff47cd6d",
|
|
51
51
|
"size": 2370
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
|
-
"hash": "
|
|
54
|
+
"hash": "e4b6b6f1dc76681e37c1a71ecaa10ab985b4dcc04ca5b2eca8bfcec367076a02"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "alphafox-cache",
|
|
58
|
+
"version": "0.3.9",
|
|
59
|
+
"files": [
|
|
60
|
+
{
|
|
61
|
+
"path": "SKILL.md",
|
|
62
|
+
"sha256": "cceafb6d8d0017090eedb5be82dd13fe2c26963531c5e92f49d1ed7ba948fc5b",
|
|
63
|
+
"size": 1529
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"hash": "f858ee9c3e27c56035cede0f7a6929fc5128573a718762802fba67b7c9d6103a"
|
|
55
67
|
},
|
|
56
68
|
{
|
|
57
69
|
"name": "alphafox-engine-backtest",
|
|
58
|
-
"version": "0.3.
|
|
70
|
+
"version": "0.3.9",
|
|
59
71
|
"files": [
|
|
60
72
|
{
|
|
61
73
|
"path": "SKILL.md",
|
|
62
|
-
"sha256": "
|
|
63
|
-
"size":
|
|
74
|
+
"sha256": "8751aa7218862c44b4635d72a02a31752e400a67b41adec03aea77678a087c48",
|
|
75
|
+
"size": 7802
|
|
64
76
|
}
|
|
65
77
|
],
|
|
66
|
-
"hash": "
|
|
78
|
+
"hash": "2bab573fa07f5c5c1d83eb2e66d96d06abbff723e62f6b84a2de30a6ccf42339"
|
|
67
79
|
},
|
|
68
80
|
{
|
|
69
81
|
"name": "alphafox-exchange",
|
|
70
|
-
"version": "0.3.
|
|
82
|
+
"version": "0.3.9",
|
|
71
83
|
"files": [
|
|
72
84
|
{
|
|
73
85
|
"path": "SKILL.md",
|
|
74
|
-
"sha256": "
|
|
86
|
+
"sha256": "416999214b3e99a9bbb3653286b2eefadbb7721da00762921e2c702de17f112a",
|
|
75
87
|
"size": 743
|
|
76
88
|
}
|
|
77
89
|
],
|
|
78
|
-
"hash": "
|
|
90
|
+
"hash": "988bdad202ba2e522d5d025339ed354063ada77ae56b0d2616963e13df659d83"
|
|
79
91
|
},
|
|
80
92
|
{
|
|
81
93
|
"name": "alphafox-market",
|
|
82
|
-
"version": "0.3.
|
|
94
|
+
"version": "0.3.9",
|
|
83
95
|
"files": [
|
|
84
96
|
{
|
|
85
97
|
"path": "SKILL.md",
|
|
86
|
-
"sha256": "
|
|
98
|
+
"sha256": "5181ca01a222ae17c3e8feb711211c4150f59745b1559da5537a5b42ff4439b9",
|
|
87
99
|
"size": 3078
|
|
88
100
|
}
|
|
89
101
|
],
|
|
90
|
-
"hash": "
|
|
102
|
+
"hash": "29f11a29419e5443593255e07bc6055fe3d15e165ef5e442c550f56845dcb12d"
|
|
91
103
|
},
|
|
92
104
|
{
|
|
93
105
|
"name": "alphafox-notification",
|
|
94
|
-
"version": "0.3.
|
|
106
|
+
"version": "0.3.9",
|
|
95
107
|
"files": [
|
|
96
108
|
{
|
|
97
109
|
"path": "SKILL.md",
|
|
98
|
-
"sha256": "
|
|
110
|
+
"sha256": "0f027ae12e13832a2d267bdf276a59882dc42396d45d31e6a43424d514209906",
|
|
99
111
|
"size": 698
|
|
100
112
|
}
|
|
101
113
|
],
|
|
102
|
-
"hash": "
|
|
114
|
+
"hash": "f8aa26444dd99e3374999086ab2956ce82156de2aab4c40a97a654082f899a29"
|
|
103
115
|
},
|
|
104
116
|
{
|
|
105
117
|
"name": "alphafox-shared",
|
|
106
|
-
"version": "0.3.
|
|
118
|
+
"version": "0.3.9",
|
|
107
119
|
"files": [
|
|
108
120
|
{
|
|
109
121
|
"path": "SKILL.md",
|
|
110
|
-
"sha256": "
|
|
122
|
+
"sha256": "896e71ca6da5f82b9c857486be8225a623a2e3a01be1a807db4d21d209c69a18",
|
|
111
123
|
"size": 5385
|
|
112
124
|
}
|
|
113
125
|
],
|
|
114
|
-
"hash": "
|
|
126
|
+
"hash": "fc69c60e86b01d917f0a513d20643331d29ed2a7baa15577cd19f049b8f8b24a"
|
|
115
127
|
},
|
|
116
128
|
{
|
|
117
129
|
"name": "alphafox-strategy",
|
|
118
|
-
"version": "0.3.
|
|
130
|
+
"version": "0.3.9",
|
|
119
131
|
"files": [
|
|
120
132
|
{
|
|
121
133
|
"path": "SKILL.md",
|
|
122
|
-
"sha256": "
|
|
134
|
+
"sha256": "bbdba03e06c56e5fd35700071841e04a45c9b04d43727ba599bfbecf2fd77e1e",
|
|
123
135
|
"size": 1826
|
|
124
136
|
}
|
|
125
137
|
],
|
|
126
|
-
"hash": "
|
|
138
|
+
"hash": "fb5847c1d8d0229d7b0d3598dbb26e1104be250dfe3a62d1759354c3ecba3e3d"
|
|
127
139
|
},
|
|
128
140
|
{
|
|
129
141
|
"name": "alphafox-trading",
|
|
130
|
-
"version": "0.3.
|
|
142
|
+
"version": "0.3.9",
|
|
131
143
|
"files": [
|
|
132
144
|
{
|
|
133
145
|
"path": "SKILL.md",
|
|
134
|
-
"sha256": "
|
|
146
|
+
"sha256": "454102427cbb7ec119265c0c38adc9acd822881e23fad725b49f693c32ed19e3",
|
|
135
147
|
"size": 3122
|
|
136
148
|
}
|
|
137
149
|
],
|
|
138
|
-
"hash": "
|
|
150
|
+
"hash": "4768ca40fba0d940bf8425dc544f9501c9c5bca5a920fc942247eb7557ec9dcf"
|
|
139
151
|
}
|
|
140
152
|
]
|
|
141
153
|
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -3,6 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CLI_CONTRACT_VERSION = exports.CLI_VERSION = exports.CLI_PACKAGE = exports.CLI_NAME = void 0;
|
|
4
4
|
exports.CLI_NAME = "alphafox";
|
|
5
5
|
exports.CLI_PACKAGE = "@alphafox/cli";
|
|
6
|
-
exports.CLI_VERSION = "0.3.
|
|
6
|
+
exports.CLI_VERSION = "0.3.9";
|
|
7
7
|
var operations_1 = require("./catalog/operations");
|
|
8
8
|
Object.defineProperty(exports, "CLI_CONTRACT_VERSION", { enumerable: true, get: function () { return operations_1.CATALOG_VERSION; } });
|
package/package.json
CHANGED
package/skills/account/SKILL.md
CHANGED
package/skills/admin/SKILL.md
CHANGED
package/skills/alphafox/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: alphafox
|
|
3
|
-
description: AlphaFox CLI entry router. Use for any AlphaFox request — install, update, login, whoami, 回测, engine backtest, strategy definitions, create/list/start/stop a running strategy (trader), ticker/标的 resolve (美股 or crypto), market data, exchange connectors, wallet, subscriptions, notifications, or admin. If a CLI command prints `[alphafox] update available`, ask the user「检测到新的版本,是否需要我帮你升级?」and only then run `alphafox update --format json --no-input`. Start here, then open the routed domain skill. Do not guess alphafox-engine-backtest vs alphafox-strategy vs alphafox-trading from memory.
|
|
4
|
-
version: 0.3.
|
|
3
|
+
description: AlphaFox CLI entry router. Use for any AlphaFox request — install, update, login, whoami, 回测, engine backtest, 清理回测缓存 / 历史数据, strategy definitions, create/list/start/stop a running strategy (trader), ticker/标的 resolve (美股 or crypto), market data, exchange connectors, wallet, subscriptions, notifications, or admin. If a CLI command prints `[alphafox] update available`, ask the user「检测到新的版本,是否需要我帮你升级?」and only then run `alphafox update --format json --no-input`. After a large backtest, if tape cache is large, ask「回测下载的历史数据比较大,要不要我帮你清理本地缓存?」then open `alphafox-cache`. Start here, then open the routed domain skill. Do not guess alphafox-engine-backtest vs alphafox-strategy vs alphafox-trading from memory.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# AlphaFox
|
|
@@ -22,6 +22,7 @@ A **trader** is a running strategy instance (paper or live), not a person. Creat
|
|
|
22
22
|
| Login, logout, whoami, profile, staging vs production | `alphafox-auth` |
|
|
23
23
|
| Ticker / 标的 / 美股 / crypto / resolve a misspelled symbol | `alphafox-market` |
|
|
24
24
|
| Engine WASM backtest, experiment, `engine-backtest run`, persist a local run | `alphafox-engine-backtest` |
|
|
25
|
+
| 清理回测缓存 / 历史 K 线占磁盘 / `alphafox cache` | `alphafox-cache` |
|
|
25
26
|
| Strategy types / definitions / validate config (grid, dca, copy, …) | `alphafox-strategy` |
|
|
26
27
|
| Create, list, start, or stop a running strategy (trader), including copy | `alphafox-trading` |
|
|
27
28
|
| Exchange connectors | `alphafox-exchange` |
|
|
@@ -36,7 +37,7 @@ If several rows apply, load **all** of them (typical: `alphafox-shared` + `alpha
|
|
|
36
37
|
The CLI may print this on **stderr** at most once every 24 hours:
|
|
37
38
|
|
|
38
39
|
```text
|
|
39
|
-
[alphafox] update available: 0.3.
|
|
40
|
+
[alphafox] update available: 0.3.8 -> 0.3.9. After the user confirms, run: alphafox update --format json --no-input,
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
If you see that notice (or `updateAvailable: true` from `alphafox update --check`):
|
|
@@ -60,3 +61,13 @@ Do not install Skills from GitHub. Details and dry-run / check commands live in
|
|
|
60
61
|
- Web `/api/v1/backtests` (`backtests.*`) is **not** a CLI surface. Do not call it via typed commands, `schema`, or `alphafox api`.
|
|
61
62
|
|
|
62
63
|
Ambiguous “帮我回测” → `alphafox-engine-backtest`, after resolving symbols.
|
|
64
|
+
|
|
65
|
+
## Large historical tape
|
|
66
|
+
|
|
67
|
+
`engine-backtest run|sweep` downloads closed OHLCV into the local tape cache. After a long-range or 1m backtest (or whenever the operator mentions disk / 缓存), read `alphafox-cache` and run `alphafox cache status --format json --no-input`.
|
|
68
|
+
|
|
69
|
+
If `data.tape.large` is true (tape ≥ `data.remindAfterBytes`):
|
|
70
|
+
|
|
71
|
+
1. Ask the user: **回测下载的历史数据比较大,要不要我帮你清理本地缓存?**
|
|
72
|
+
2. Wait for an explicit yes. Do not clean on your own.
|
|
73
|
+
3. Follow `alphafox-cache` (`alphafox cache clean --dry-run`, then `--yes`).
|
package/skills/auth/SKILL.md
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: alphafox-cache
|
|
3
|
+
description: Inspect and clean local Engine backtest caches (downloaded OHLCV tape and wasm runtime). Use when the user asks to 清理缓存, free disk, or after a large historical backtest.
|
|
4
|
+
version: 0.3.9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cache
|
|
8
|
+
|
|
9
|
+
Always `--format json --no-input`. Never `--token`. This is local disk only.
|
|
10
|
+
|
|
11
|
+
`engine-backtest run|sweep` writes closed OHLCV into the **tape** cache (`~/.alphafox/cache/engine-backtest`, or `ALPHAFOX_TAPE_CACHE_DIR`). The wasm / Node host lives under the **runtime** cache (`~/.cache/alphafox/engine-backtest/<hash>/`). Tape is the large historical download.
|
|
12
|
+
|
|
13
|
+
## Status first
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
alphafox cache status --format json --no-input
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Read `data.tape.bytes`, `data.tape.files`, `data.tape.large`, `data.remindAfterBytes`. `large` is true when tape bytes ≥ `remindAfterBytes` (512 MiB).
|
|
20
|
+
|
|
21
|
+
If `data.tape.large` is true, ask the user:
|
|
22
|
+
|
|
23
|
+
**回测下载的历史数据比较大,要不要我帮你清理本地缓存?**
|
|
24
|
+
|
|
25
|
+
Wait for an explicit yes. Do not clean on your own.
|
|
26
|
+
|
|
27
|
+
## Clean
|
|
28
|
+
|
|
29
|
+
Default clean is **tape only** (historical bars). Runtime re-downloads on the next run; only add `--runtime` or `--all` when the operator asks.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
alphafox cache clean --dry-run --format json --no-input
|
|
33
|
+
alphafox cache clean --yes --format json --no-input
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`--yes` is required to delete. `--dry-run` reports what would be removed. After clean, `data.bytesFreed` is the space recovered.
|
|
37
|
+
|
|
38
|
+
Do not `rm` cache paths by hand. Do not delete `~/.config/alphafox` (config / skills state / keychain).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: alphafox-engine-backtest
|
|
3
3
|
description: Local Engine WASM backtest (alphafox engine-backtest run|sweep) vs catalog experiment CRUD.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Engine Backtest
|
|
@@ -94,6 +94,7 @@ Owner isolation and 7-day expiry are enforced by the server. Applying a coordina
|
|
|
94
94
|
2. `engine-backtest run` (reuse `--experiment` after the first create).
|
|
95
95
|
3. Read `data.metrics` / `data.engineVersion` / `data.runId` / `data.experimentUrl`.
|
|
96
96
|
4. Adjust parameters and run again. Do not invent a token flag if persist returns 401 — `alphafox auth login`.
|
|
97
|
+
5. After a long-range or 1m run, follow `alphafox-cache`: `alphafox cache status`. If `data.tape.large` is true, ask **回测下载的历史数据比较大,要不要我帮你清理本地缓存?** and wait for yes.
|
|
97
98
|
|
|
98
99
|
## Safety
|
|
99
100
|
|
package/skills/exchange/SKILL.md
CHANGED
package/skills/market/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: alphafox-market
|
|
3
3
|
description: Market data and ticker resolution for US equity perps, RWAs, and crypto on the same perp catalog. Use when the user names 美股, NVDA, AAPL, BTC, or any 标的. Keep the operator's asset class via symbolMetadata — do not rewrite NVDA into a crypto coin.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Market
|
package/skills/strategy/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: alphafox-strategy
|
|
3
3
|
description: Strategy definitions — list types (grid, dca, copy, …) and validate config. Creating a running strategy is creating a trader; use alphafox-trading for that.
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Strategy definitions
|
package/skills/trading/SKILL.md
CHANGED
|
@@ -288,6 +288,8 @@ export interface TapeLoadRequest {
|
|
|
288
288
|
readonly signal?: AbortSignal;
|
|
289
289
|
readonly onProgress?: (progress: TapeLoadProgress) => void;
|
|
290
290
|
readonly nowMs?: number;
|
|
291
|
+
readonly cacheDir?: string;
|
|
292
|
+
readonly seriesConcurrency?: number;
|
|
291
293
|
readonly ohlcvFetcher?: TapeOhlcvFetcher;
|
|
292
294
|
readonly fundingFetcher?: TapeFundingFetcher;
|
|
293
295
|
readonly marketsLoader?: () => Promise<TapeMarketsSnapshot>;
|
|
@@ -302,6 +304,8 @@ export interface TapeProxyOptions {
|
|
|
302
304
|
export interface TapeLoadOptions extends TapeProxyOptions {
|
|
303
305
|
readonly cache?: FileTapeCache | false | "disable";
|
|
304
306
|
readonly cacheDir?: string;
|
|
307
|
+
/** Independent series fetches; default 4, hard cap 8. Pagination stays serial. */
|
|
308
|
+
readonly seriesConcurrency?: number;
|
|
305
309
|
readonly nowMs?: number;
|
|
306
310
|
readonly onProgress?: (progress: TapeLoadProgress) => void;
|
|
307
311
|
readonly createExchange?: (
|
|
@@ -336,6 +340,17 @@ export interface TapeLoadResult {
|
|
|
336
340
|
};
|
|
337
341
|
}
|
|
338
342
|
|
|
343
|
+
export const DEFAULT_TAPE_SERIES_CONCURRENCY: 4;
|
|
344
|
+
export const MAX_TAPE_SERIES_CONCURRENCY: 8;
|
|
345
|
+
|
|
346
|
+
export function resolveTapeSeriesConcurrency(value?: number): number;
|
|
347
|
+
|
|
348
|
+
export function mapWithConcurrency<T, R>(
|
|
349
|
+
items: readonly T[],
|
|
350
|
+
concurrency: number,
|
|
351
|
+
worker: (item: T, index: number) => Promise<R>
|
|
352
|
+
): Promise<R[]>;
|
|
353
|
+
|
|
339
354
|
export function loadTape(
|
|
340
355
|
request: TapeLoadRequest,
|
|
341
356
|
options?: TapeLoadOptions
|
|
@@ -50,8 +50,12 @@ export {
|
|
|
50
50
|
loadSeriesWithCache,
|
|
51
51
|
} from "./lib/series.mjs";
|
|
52
52
|
export {
|
|
53
|
+
DEFAULT_TAPE_SERIES_CONCURRENCY,
|
|
54
|
+
MAX_TAPE_SERIES_CONCURRENCY,
|
|
53
55
|
loadTape,
|
|
56
|
+
mapWithConcurrency,
|
|
54
57
|
resolveTapeCache,
|
|
58
|
+
resolveTapeSeriesConcurrency,
|
|
55
59
|
effectiveTapeEndMs,
|
|
56
60
|
inferFundingIntervals,
|
|
57
61
|
classifyTapeSymbolsForPreflight,
|
|
@@ -39,8 +39,47 @@ const FUNDING_INTERVALS = [
|
|
|
39
39
|
{ interval: "8h", spacingMs: 28_800_000 },
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
+
/** Independent symbol×timeframe (and funding) fetches. Pagination stays serial. */
|
|
43
|
+
export const DEFAULT_TAPE_SERIES_CONCURRENCY = 4;
|
|
44
|
+
export const MAX_TAPE_SERIES_CONCURRENCY = 8;
|
|
45
|
+
|
|
42
46
|
const exchangePromises = new Map();
|
|
43
47
|
|
|
48
|
+
export function resolveTapeSeriesConcurrency(value) {
|
|
49
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 1) {
|
|
50
|
+
return Math.min(
|
|
51
|
+
MAX_TAPE_SERIES_CONCURRENCY,
|
|
52
|
+
Math.max(1, Math.floor(value))
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return DEFAULT_TAPE_SERIES_CONCURRENCY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function mapWithConcurrency(items, concurrency, worker) {
|
|
59
|
+
const list = [...items];
|
|
60
|
+
if (list.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const limit = Math.min(
|
|
64
|
+
list.length,
|
|
65
|
+
Math.max(1, Math.floor(Number(concurrency)) || 1)
|
|
66
|
+
);
|
|
67
|
+
const results = new Array(list.length);
|
|
68
|
+
let nextIndex = 0;
|
|
69
|
+
async function runWorker() {
|
|
70
|
+
while (true) {
|
|
71
|
+
const index = nextIndex;
|
|
72
|
+
nextIndex += 1;
|
|
73
|
+
if (index >= list.length) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
results[index] = await worker(list[index], index);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await Promise.all(Array.from({ length: limit }, () => runWorker()));
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
export function effectiveTapeEndMs(
|
|
45
84
|
requestedToMs,
|
|
46
85
|
nowMs = Date.now(),
|
|
@@ -141,7 +180,13 @@ export async function loadTape(request, options = {}) {
|
|
|
141
180
|
|
|
142
181
|
const exchangeDefinition = resolveRequestExchange(request);
|
|
143
182
|
const onProgress = request.onProgress ?? options.onProgress;
|
|
144
|
-
const cache = resolveTapeCache(
|
|
183
|
+
const cache = resolveTapeCache({
|
|
184
|
+
...options,
|
|
185
|
+
cacheDir: options.cacheDir ?? request.cacheDir,
|
|
186
|
+
});
|
|
187
|
+
const seriesConcurrency = resolveTapeSeriesConcurrency(
|
|
188
|
+
options.seriesConcurrency ?? request.seriesConcurrency
|
|
189
|
+
);
|
|
145
190
|
const dataQualityMode = request.dataQualityMode ?? "strict";
|
|
146
191
|
const baseTimeframe = resolvePlanBaseTimeframe({
|
|
147
192
|
baseTimeframe: request.baseTimeframe,
|
|
@@ -233,17 +278,36 @@ export async function loadTape(request, options = {}) {
|
|
|
233
278
|
const buffers = {};
|
|
234
279
|
const chartSeries = [];
|
|
235
280
|
const series = [];
|
|
236
|
-
const
|
|
281
|
+
const seriesJobs = request.symbols.flatMap((symbol) =>
|
|
282
|
+
timeframes.map((timeframe) => ({ symbol, timeframe }))
|
|
283
|
+
);
|
|
284
|
+
const totalSeries = seriesJobs.length;
|
|
237
285
|
const dataIssues = [];
|
|
238
286
|
const coverageWarnings = [];
|
|
239
|
-
|
|
240
|
-
let
|
|
241
|
-
|
|
242
|
-
|
|
287
|
+
const seriesFractions = new Array(totalSeries).fill(0);
|
|
288
|
+
let lastOhlcvDetail = "";
|
|
289
|
+
const reportOhlcv = (index, fraction, detail) => {
|
|
290
|
+
seriesFractions[index] = fraction;
|
|
291
|
+
lastOhlcvDetail = detail;
|
|
292
|
+
const completed =
|
|
293
|
+
seriesFractions.reduce((sum, value) => sum + value, 0) / totalSeries;
|
|
294
|
+
onProgress?.({
|
|
295
|
+
stage: "ohlcv",
|
|
296
|
+
detail: lastOhlcvDetail,
|
|
297
|
+
fraction:
|
|
298
|
+
MARKETS_PROGRESS_END +
|
|
299
|
+
(OHLCV_PROGRESS_END - MARKETS_PROGRESS_END) * completed,
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const loadedSeries = await mapWithConcurrency(
|
|
303
|
+
seriesJobs,
|
|
304
|
+
seriesConcurrency,
|
|
305
|
+
async (job, index) => {
|
|
243
306
|
request.signal?.throwIfAborted();
|
|
244
|
-
|
|
307
|
+
const { symbol, timeframe } = job;
|
|
308
|
+
const detail = `${symbol} ${timeframe}`;
|
|
245
309
|
try {
|
|
246
|
-
loaded = await loadSeriesWithCache(
|
|
310
|
+
const loaded = await loadSeriesWithCache(
|
|
247
311
|
exchange,
|
|
248
312
|
exchangeDefinition,
|
|
249
313
|
runtimeConfig,
|
|
@@ -255,46 +319,51 @@ export async function loadTape(request, options = {}) {
|
|
|
255
319
|
requirementWarmups.get(`${symbol}\u0000${timeframe}`) ?? 0,
|
|
256
320
|
timeframe === baseTimeframe,
|
|
257
321
|
dataQualityMode,
|
|
258
|
-
(fraction) =>
|
|
259
|
-
onProgress?.({
|
|
260
|
-
stage: "ohlcv",
|
|
261
|
-
detail: `${symbol} ${timeframe}`,
|
|
262
|
-
fraction:
|
|
263
|
-
MARKETS_PROGRESS_END +
|
|
264
|
-
(OHLCV_PROGRESS_END - MARKETS_PROGRESS_END) *
|
|
265
|
-
((seriesDone + fraction) / totalSeries),
|
|
266
|
-
});
|
|
267
|
-
},
|
|
322
|
+
(fraction) => reportOhlcv(index, fraction, detail),
|
|
268
323
|
cacheUntilMs,
|
|
269
324
|
cache,
|
|
270
325
|
request.signal
|
|
271
326
|
);
|
|
327
|
+
reportOhlcv(index, 1, detail);
|
|
328
|
+
return { ok: true, job, loaded };
|
|
272
329
|
} catch (error) {
|
|
273
330
|
request.signal?.throwIfAborted();
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
coverageWarnings.push(
|
|
280
|
-
formatCoverageSoftWarning(loaded.softIssues, loaded.coverageRatio)
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
const { rows } = loaded;
|
|
284
|
-
const bufferKey = `k${bufferSequence++}`;
|
|
285
|
-
const buffer = encodeOhlcvColumns(rows);
|
|
286
|
-
buffers[bufferKey] = buffer;
|
|
287
|
-
if (timeframe === baseTimeframe) {
|
|
288
|
-
chartSeries.push({
|
|
289
|
-
symbol,
|
|
290
|
-
timeframe: baseTimeframe,
|
|
291
|
-
rows: rows.length,
|
|
292
|
-
buffer: buffer.slice(0),
|
|
293
|
-
});
|
|
331
|
+
reportOhlcv(index, 1, detail);
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
issues: toTapeDataIssues(error, symbol, timeframe),
|
|
335
|
+
};
|
|
294
336
|
}
|
|
295
|
-
series.push({ symbol, timeframe, buffer: bufferKey, rows: rows.length });
|
|
296
|
-
seriesDone++;
|
|
297
337
|
}
|
|
338
|
+
);
|
|
339
|
+
let bufferSequence = 0;
|
|
340
|
+
for (const result of loadedSeries) {
|
|
341
|
+
if (!result.ok) {
|
|
342
|
+
dataIssues.push(...result.issues);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (result.loaded.softIssues.length > 0) {
|
|
346
|
+
coverageWarnings.push(
|
|
347
|
+
formatCoverageSoftWarning(
|
|
348
|
+
result.loaded.softIssues,
|
|
349
|
+
result.loaded.coverageRatio
|
|
350
|
+
)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const { symbol, timeframe } = result.job;
|
|
354
|
+
const { rows } = result.loaded;
|
|
355
|
+
const bufferKey = `k${bufferSequence++}`;
|
|
356
|
+
const buffer = encodeOhlcvColumns(rows);
|
|
357
|
+
buffers[bufferKey] = buffer;
|
|
358
|
+
if (timeframe === baseTimeframe) {
|
|
359
|
+
chartSeries.push({
|
|
360
|
+
symbol,
|
|
361
|
+
timeframe: baseTimeframe,
|
|
362
|
+
rows: rows.length,
|
|
363
|
+
buffer: buffer.slice(0),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
series.push({ symbol, timeframe, buffer: bufferKey, rows: rows.length });
|
|
298
367
|
}
|
|
299
368
|
if (dataIssues.length > 0) {
|
|
300
369
|
throw new TapeDataUnavailableError(dataIssues);
|
|
@@ -302,19 +371,24 @@ export async function loadTape(request, options = {}) {
|
|
|
302
371
|
|
|
303
372
|
let fundingRates;
|
|
304
373
|
if (request.needsFunding) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
374
|
+
const fundingEntries = await mapWithConcurrency(
|
|
375
|
+
request.symbols,
|
|
376
|
+
seriesConcurrency,
|
|
377
|
+
async (symbol) => {
|
|
378
|
+
request.signal?.throwIfAborted();
|
|
379
|
+
const samples = await loadFundingHistory(
|
|
380
|
+
exchange,
|
|
381
|
+
exchangeDefinition,
|
|
382
|
+
runtimeConfig,
|
|
383
|
+
symbol,
|
|
384
|
+
request.fromMs,
|
|
385
|
+
tapeToMs,
|
|
386
|
+
request.signal
|
|
387
|
+
);
|
|
388
|
+
return [symbol, samples];
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
fundingRates = Object.fromEntries(fundingEntries);
|
|
318
392
|
}
|
|
319
393
|
request.signal?.throwIfAborted();
|
|
320
394
|
onProgress?.({
|