@alphafox/cli 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ import type { TapeCoverageIssue, TapeCoverageNotice } from "./types";
2
+ export declare function summarizeTapeCoverageNotice(issues: readonly TapeCoverageIssue[]): TapeCoverageNotice;
3
+ /** Runtime guard: a missing field must not look like a clean tape. */
4
+ export declare function requireTapeCoverageIssues(value: unknown): readonly TapeCoverageIssue[];
5
+ export declare function snapshotCoverageIssues(issues: readonly TapeCoverageIssue[] | undefined): TapeCoverageIssue[] | undefined;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeTapeCoverageNotice = summarizeTapeCoverageNotice;
4
+ exports.requireTapeCoverageIssues = requireTapeCoverageIssues;
5
+ exports.snapshotCoverageIssues = snapshotCoverageIssues;
6
+ const errors_1 = require("./errors");
7
+ function symbolLabel(issue) {
8
+ const timeframe = issue.timeframe && issue.timeframe !== "*" ? ` ${issue.timeframe}` : "";
9
+ return `${issue.symbol}${timeframe}`;
10
+ }
11
+ function uniqueIssues(issues) {
12
+ const seen = new Set();
13
+ const unique = [];
14
+ for (const issue of issues) {
15
+ const key = `${issue.code}\u0000${issue.symbol}\u0000${issue.timeframe}`;
16
+ if (seen.has(key))
17
+ continue;
18
+ seen.add(key);
19
+ unique.push(issue);
20
+ }
21
+ return unique;
22
+ }
23
+ function summarizeTapeCoverageNotice(issues) {
24
+ const prefix = [];
25
+ const internal = [];
26
+ const other = [];
27
+ for (const item of issues) {
28
+ if (item.code === "prefix_gap") {
29
+ prefix.push(item);
30
+ }
31
+ else if (item.code === "internal_gap") {
32
+ internal.push(item);
33
+ }
34
+ else {
35
+ other.push(item);
36
+ }
37
+ }
38
+ const uniquePrefix = uniqueIssues(prefix);
39
+ const uniqueInternal = uniqueIssues(internal);
40
+ const uniqueOther = uniqueIssues(other);
41
+ const severity = uniqueInternal.length > 0
42
+ ? "warning"
43
+ : uniquePrefix.length + uniqueOther.length > 0
44
+ ? "notice"
45
+ : "none";
46
+ const messages = [];
47
+ if (uniqueInternal.length > 0) {
48
+ messages.push(`WARNING: missing mid-range candles (more severe): ${uniqueInternal
49
+ .map(symbolLabel)
50
+ .join(", ")}`);
51
+ }
52
+ if (uniquePrefix.length > 0) {
53
+ messages.push(`NOTICE: missing start candles (less severe): ${uniquePrefix
54
+ .map(symbolLabel)
55
+ .join(", ")}`);
56
+ }
57
+ if (uniqueOther.length > 0) {
58
+ messages.push(`NOTICE: other soft coverage gaps: ${uniqueOther
59
+ .map((issue) => `${symbolLabel(issue)} (${issue.code})`)
60
+ .join(", ")}`);
61
+ }
62
+ return {
63
+ severity,
64
+ prefix: uniquePrefix,
65
+ internal: uniqueInternal,
66
+ other: uniqueOther,
67
+ messages,
68
+ };
69
+ }
70
+ /** Must match alphafox-web `engineBacktestPersistedSnapshotSchema.coverageIssues`. */
71
+ const PERSISTED_COVERAGE_ISSUE_CODES = new Set([
72
+ "market_missing",
73
+ "ohlcv_missing",
74
+ "invalid_ohlcv",
75
+ "non_monotonic",
76
+ "internal_gap",
77
+ "prefix_gap",
78
+ "suffix_gap",
79
+ "warmup_insufficient",
80
+ "load_failed",
81
+ "coverage_insufficient",
82
+ ]);
83
+ /** Runtime guard: a missing field must not look like a clean tape. */
84
+ function requireTapeCoverageIssues(value) {
85
+ if (!Array.isArray(value)) {
86
+ throw new errors_1.EngineBacktestError({
87
+ type: "runtime",
88
+ subtype: "missing_coverage_issues",
89
+ message: "Tape loader omitted coverageIssues; refusing to treat the tape as clean.",
90
+ });
91
+ }
92
+ return value;
93
+ }
94
+ function snapshotCoverageIssues(issues) {
95
+ if (!issues || issues.length === 0)
96
+ return undefined;
97
+ return issues.map((issue) => {
98
+ if (!PERSISTED_COVERAGE_ISSUE_CODES.has(issue.code)) {
99
+ throw new errors_1.EngineBacktestError({
100
+ type: "runtime",
101
+ subtype: "invalid_coverage_issue",
102
+ message: `Cannot persist unknown tape coverage issue code: ${issue.code}`,
103
+ });
104
+ }
105
+ return {
106
+ code: issue.code,
107
+ symbol: issue.symbol,
108
+ timeframe: issue.timeframe,
109
+ ...(issue.expected === undefined ? {} : { expected: issue.expected }),
110
+ ...(issue.actual === undefined ? {} : { actual: issue.actual }),
111
+ ...(issue.timestamp === undefined ? {} : { timestamp: issue.timestamp }),
112
+ ...(issue.message === undefined ? {} : { message: issue.message }),
113
+ };
114
+ });
115
+ }
@@ -1,4 +1,4 @@
1
- import type { EngineBacktestRunArgs, EngineBacktestSweepArgs, ExecutionModel, InclusiveUtcDateRange } from "./types";
1
+ import { type EngineBacktestRunArgs, type EngineBacktestSweepArgs, type ExecutionModel, type InclusiveUtcDateRange } from "./types";
2
2
  export declare const ENGINE_BACKTEST_RUN_USAGE: string[];
3
3
  export declare const ENGINE_BACKTEST_SWEEP_USAGE: string[];
4
4
  export declare function parseInclusiveUtcDateRange(rangeStart: string, rangeEnd: string): InclusiveUtcDateRange;
@@ -9,6 +9,7 @@ exports.parseEngineBacktestSweepArgs = parseEngineBacktestSweepArgs;
9
9
  const errors_1 = require("./errors");
10
10
  const replay_timeframe_1 = require("./replay-timeframe");
11
11
  const sweep_kernel_1 = require("./sweep-kernel");
12
+ const types_1 = require("./types");
12
13
  const DAY_MS = 86_400_000;
13
14
  const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
14
15
  const TIERS = new Set(["free", "pro", "pro_max"]);
@@ -107,7 +108,7 @@ function parseEngineBacktestRunArgs(args) {
107
108
  let to;
108
109
  let initialEquity;
109
110
  let tier;
110
- let dataQualityMode = "strict";
111
+ let dataQualityMode = types_1.DEFAULT_DATA_QUALITY_MODE;
111
112
  let configSchemaVersion;
112
113
  let executionModelOverride;
113
114
  let persist = true;
@@ -343,7 +344,7 @@ function parseEngineBacktestSweepArgs(args) {
343
344
  return {
344
345
  help: true,
345
346
  createExperiment: false,
346
- dataQualityMode: "strict",
347
+ dataQualityMode: types_1.DEFAULT_DATA_QUALITY_MODE,
347
348
  persist: true,
348
349
  replayTimeframe: replay_timeframe_1.ENGINE_BACKTEST_DEFAULT_REPLAY_TIMEFRAME,
349
350
  axesRaw,
@@ -1,5 +1,5 @@
1
1
  import type { ProfileName } from "../config/profiles";
2
- import type { CreateRunRequestBody, CreateSweepRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, EngineBacktestSweepPointRow, ExecutionModel, PersistedSnapshot, SweepMode, SweepSearchMode } from "./types";
2
+ import type { CreateRunRequestBody, CreateSweepRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, EngineBacktestSweepPointRow, ExecutionModel, PersistedSnapshot, SweepMode, SweepSearchMode, TapeCoverageIssue } 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;
@@ -24,6 +24,7 @@ export declare function buildCreateRunRequest(input: {
24
24
  readonly dataQualityMode: DataQualityMode;
25
25
  readonly engineVersion: string;
26
26
  readonly equityCurve?: unknown;
27
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
27
28
  }): CreateRunRequestBody;
28
29
  /**
29
30
  * Experiment detail URL. Locale prefix (`/zh`, `/en`) is optional — web
@@ -51,6 +52,7 @@ export declare function buildSweepBaseSnapshot(input: {
51
52
  readonly initialEquity: number;
52
53
  readonly subscriptionTier: PersistedSnapshot["subscriptionTier"];
53
54
  readonly dataQualityMode: DataQualityMode;
55
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
54
56
  readonly symbols: readonly string[];
55
57
  readonly timeframes: readonly string[];
56
58
  readonly baseTimeframe: string;
@@ -14,6 +14,7 @@ exports.buildCreateSweepRequest = buildCreateSweepRequest;
14
14
  exports.assertSweepCreateRequestSize = assertSweepCreateRequestSize;
15
15
  const errors_1 = require("./errors");
16
16
  const return_curve_1 = require("./return-curve");
17
+ const coverage_notice_1 = require("./coverage-notice");
17
18
  /** Aligned with `@alphafoxai/backtest-runner` DEFAULT_EXECUTION_MODEL. */
18
19
  exports.DEFAULT_EXECUTION_MODEL = {
19
20
  pricePath: "ohlc_path_4",
@@ -59,6 +60,7 @@ function buildCreateRunRequest(input) {
59
60
  }
60
61
  const timeframes = uniqueSeriesField(scenario.tape.series, "timeframe");
61
62
  const engineVersion = input.engineVersion.trim() || "node-wasm";
63
+ const coverageIssues = (0, coverage_notice_1.snapshotCoverageIssues)(input.coverageIssues);
62
64
  const snapshot = {
63
65
  snapshotSchemaVersion: exports.SNAPSHOT_SCHEMA_VERSION,
64
66
  strategyDefinitionId: scenario.trader.strategyDefinitionId,
@@ -70,6 +72,7 @@ function buildCreateRunRequest(input) {
70
72
  initialEquity: scenario.exchange.initialEquity,
71
73
  subscriptionTier: scenario.trader.subscriptionTier,
72
74
  dataQualityMode: input.dataQualityMode,
75
+ ...(coverageIssues ? { coverageIssues } : {}),
73
76
  symbols,
74
77
  timeframes: timeframes.length > 0 ? timeframes : ["1m"],
75
78
  baseTimeframe: scenario.tape.baseTimeframe || "1m",
@@ -141,6 +144,7 @@ function buildSweepBaseSnapshot(input) {
141
144
  const timeframes = [
142
145
  ...new Set(input.timeframes.map((item) => item.trim()).filter(Boolean)),
143
146
  ];
147
+ const coverageIssues = (0, coverage_notice_1.snapshotCoverageIssues)(input.coverageIssues);
144
148
  return {
145
149
  snapshotSchemaVersion: exports.SNAPSHOT_SCHEMA_VERSION,
146
150
  strategyDefinitionId: input.definitionId,
@@ -152,6 +156,7 @@ function buildSweepBaseSnapshot(input) {
152
156
  initialEquity: input.initialEquity,
153
157
  subscriptionTier: input.subscriptionTier,
154
158
  dataQualityMode: input.dataQualityMode,
159
+ ...(coverageIssues ? { coverageIssues } : {}),
155
160
  symbols,
156
161
  timeframes: timeframes.length > 0 ? timeframes : ["1m"],
157
162
  baseTimeframe: input.baseTimeframe.trim() || "1m",
@@ -9,6 +9,7 @@ const profiles_1 = require("../config/profiles");
9
9
  const envelope_1 = require("../envelope");
10
10
  const client_1 = require("../http/client");
11
11
  const store_1 = require("../keychain/store");
12
+ const coverage_notice_1 = require("./coverage-notice");
12
13
  const errors_1 = require("./errors");
13
14
  const load_config_1 = require("./load-config");
14
15
  const parse_args_1 = require("./parse-args");
@@ -336,6 +337,10 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
336
337
  details: issues ? { issues } : undefined,
337
338
  });
338
339
  }
340
+ tapeResult = {
341
+ ...tapeResult,
342
+ coverageIssues: (0, coverage_notice_1.requireTapeCoverageIssues)(tapeResult.coverageIssues),
343
+ };
339
344
  const runId = mintId();
340
345
  const executionModel = {
341
346
  ...(runner.DEFAULT_EXECUTION_MODEL ?? persist_1.DEFAULT_EXECUTION_MODEL),
@@ -400,6 +405,7 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
400
405
  dataQualityMode: args.dataQualityMode,
401
406
  engineVersion,
402
407
  equityCurve: result.equityCurve,
408
+ coverageIssues: tapeResult.coverageIssues,
403
409
  });
404
410
  const res = await postJson(api, profile, env, runsPath(experimentId), body, mintId);
405
411
  persistedRunId = extractEntityId(res.json);
@@ -419,7 +425,9 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
419
425
  runId: persistedRunId,
420
426
  experimentUrl: (0, persist_1.experimentPageUrl)(profile.name, experimentId),
421
427
  persisted: Boolean(persistedRunId),
422
- coverageWarnings: tapeResult.coverageWarnings ?? [],
428
+ coverageWarnings: tapeResult.coverageWarnings,
429
+ coverageIssues: tapeResult.coverageIssues,
430
+ coverageNotice: (0, coverage_notice_1.summarizeTapeCoverageNotice)(tapeResult.coverageIssues),
423
431
  };
424
432
  }
425
433
  finally {
@@ -441,6 +449,7 @@ function engineBacktestHelpData() {
441
449
  "runs.create is write (not high-risk-write); --yes is not required",
442
450
  "Do not pass --token; use alphafox auth login",
443
451
  "--replay-timeframe defaults to 1m (min 1m). Indicator series still download their native plan timeframes.",
452
+ "--data-quality defaults to basic: hard tape failures still stop, soft gaps finish the run and appear as coverageNotice (prefix_gap less severe, internal_gap more severe). Use --data-quality strict to fail on any gap.",
444
453
  ],
445
454
  };
446
455
  }
@@ -9,6 +9,7 @@ const profiles_1 = require("../config/profiles");
9
9
  const client_1 = require("../http/client");
10
10
  const paths_1 = require("../cache/paths");
11
11
  const store_1 = require("../keychain/store");
12
+ const coverage_notice_1 = require("./coverage-notice");
12
13
  const errors_1 = require("./errors");
13
14
  const load_config_1 = require("./load-config");
14
15
  const parse_axes_1 = require("./parse-axes");
@@ -175,6 +176,7 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
175
176
  },
176
177
  buffers: {},
177
178
  coverageWarnings: [],
179
+ coverageIssues: [],
178
180
  };
179
181
  if (tapePlans.length > 0) {
180
182
  try {
@@ -209,6 +211,10 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
209
211
  details: issues ? { issues } : undefined,
210
212
  });
211
213
  }
214
+ tapeResult = {
215
+ ...tapeResult,
216
+ coverageIssues: (0, coverage_notice_1.requireTapeCoverageIssues)(tapeResult.coverageIssues),
217
+ };
212
218
  }
213
219
  emitProgress(flags, writeLine, "tape", 1);
214
220
  while (clients.length < concurrency) {
@@ -353,6 +359,7 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
353
359
  initialEquity: args.initialEquity,
354
360
  subscriptionTier,
355
361
  dataQualityMode: args.dataQualityMode,
362
+ coverageIssues: tapeResult.coverageIssues,
356
363
  symbols: tapeSymbols.length > 0 ? tapeSymbols : coverage.symbols,
357
364
  timeframes: (0, persist_1.uniqueSeriesField)(tapeResult.tape.series, "timeframe"),
358
365
  baseTimeframe: tapeResult.tape.baseTimeframe || args.replayTimeframe,
@@ -417,7 +424,9 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
417
424
  experimentUrl: experimentId
418
425
  ? (0, persist_1.experimentSweepPageUrl)(profile.name, experimentId)
419
426
  : undefined,
420
- coverageWarnings: tapeResult.coverageWarnings ?? [],
427
+ coverageWarnings: tapeResult.coverageWarnings,
428
+ coverageIssues: tapeResult.coverageIssues,
429
+ coverageNotice: (0, coverage_notice_1.summarizeTapeCoverageNotice)(tapeResult.coverageIssues),
421
430
  axes: coarsePlan.axes,
422
431
  };
423
432
  }
@@ -1,5 +1,23 @@
1
1
  export type SubscriptionTier = "free" | "pro" | "pro_max";
2
2
  export type DataQualityMode = "strict" | "basic";
3
+ /** New runs default to basic; `--data-quality strict` remains an explicit override. */
4
+ export declare const DEFAULT_DATA_QUALITY_MODE: DataQualityMode;
5
+ export interface TapeCoverageIssue {
6
+ readonly code: string;
7
+ readonly symbol: string;
8
+ readonly timeframe: string;
9
+ readonly expected?: number;
10
+ readonly actual?: number;
11
+ readonly timestamp?: number;
12
+ readonly message?: string;
13
+ }
14
+ export interface TapeCoverageNotice {
15
+ readonly severity: "none" | "notice" | "warning";
16
+ readonly prefix: readonly TapeCoverageIssue[];
17
+ readonly internal: readonly TapeCoverageIssue[];
18
+ readonly other: readonly TapeCoverageIssue[];
19
+ readonly messages: readonly string[];
20
+ }
3
21
  export type PricePath = "ohlc_path_4" | "close_only";
4
22
  export interface ExecutionModel {
5
23
  readonly pricePath: PricePath;
@@ -202,6 +220,7 @@ export interface TapeLoadResult {
202
220
  readonly tape: EngineBacktestTapeInput;
203
221
  readonly buffers: Record<string, ArrayBuffer>;
204
222
  readonly coverageWarnings: readonly string[];
223
+ readonly coverageIssues: readonly TapeCoverageIssue[];
205
224
  }
206
225
  export interface BacktestClientLike {
207
226
  init(): Promise<string>;
@@ -265,6 +284,7 @@ export interface PersistedSnapshot {
265
284
  readonly initialEquity: number;
266
285
  readonly subscriptionTier: SubscriptionTier;
267
286
  readonly dataQualityMode: DataQualityMode;
287
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
268
288
  readonly symbols: string[];
269
289
  readonly timeframes: string[];
270
290
  readonly baseTimeframe: string;
@@ -289,6 +309,8 @@ export interface EngineBacktestRunSuccess {
289
309
  readonly experimentUrl: string;
290
310
  readonly persisted: boolean;
291
311
  readonly coverageWarnings: readonly string[];
312
+ readonly coverageIssues: readonly TapeCoverageIssue[];
313
+ readonly coverageNotice: TapeCoverageNotice;
292
314
  }
293
315
  export interface EngineBacktestSweepPointRow {
294
316
  readonly coordinate: {
@@ -372,6 +394,8 @@ export interface EngineBacktestSweepSuccess {
372
394
  readonly experimentId?: string;
373
395
  readonly experimentUrl?: string;
374
396
  readonly coverageWarnings: readonly string[];
397
+ readonly coverageIssues: readonly TapeCoverageIssue[];
398
+ readonly coverageNotice: TapeCoverageNotice;
375
399
  readonly axes: ReadonlyArray<{
376
400
  readonly path: readonly string[];
377
401
  readonly current: number;
@@ -1,2 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_DATA_QUALITY_MODE = void 0;
4
+ /** New runs default to basic; `--data-quality strict` remains an explicit override. */
5
+ exports.DEFAULT_DATA_QUALITY_MODE = "basic";
@@ -1,153 +1,153 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "@alphafox/cli",
4
- "packageVersion": "0.3.10",
4
+ "packageVersion": "0.3.11",
5
5
  "contractVersion": "2026-08-13",
6
- "bundleHash": "e03966174d892d1205e0f4a5ec69c11d7cd78e9f5bd2f95fc3e145b7dfea14cd",
6
+ "bundleHash": "10abd3fc0d5bbd4a260f42a02bff35899e829543419cf0f678077546a7bebcc6",
7
7
  "skills": [
8
8
  {
9
9
  "name": "alphafox",
10
- "version": "0.3.10",
10
+ "version": "0.3.11",
11
11
  "files": [
12
12
  {
13
13
  "path": "SKILL.md",
14
- "sha256": "58466ff1f9d42d6c64b1a4b2d52bafd40857dd7e0ca928f987fc926c358bd850",
15
- "size": 4407
14
+ "sha256": "ebc6138b666514ef543cf5b444d3ff293bd439eae5c39a231fada5b4dae27b11",
15
+ "size": 4408
16
16
  }
17
17
  ],
18
- "hash": "363bf0b3746b8cd8b291956b58dd9ac3e001bc1bfb80f0fa9c471aec4bf0021d"
18
+ "hash": "c4c73d98a0ad828e66fccb406294ab3533530b1d003c71eebd9e569895cbf305"
19
19
  },
20
20
  {
21
21
  "name": "alphafox-account",
22
- "version": "0.3.10",
22
+ "version": "0.3.11",
23
23
  "files": [
24
24
  {
25
25
  "path": "SKILL.md",
26
- "sha256": "4fcc7608817c900055dd605e0e71cd5b05e0945516701d0a281ebefb0407fb7b",
26
+ "sha256": "714b3f0094a67bc08764ae26f900e3f61463950b2a031f4b46a478a968deea74",
27
27
  "size": 784
28
28
  }
29
29
  ],
30
- "hash": "273fdb4dd81103fc214b85ab7d59fb9dd65ebf2ec6d5d0d889837573bd8e3dae"
30
+ "hash": "94085eb4ec63d15a217df97878282c7f0c1e59c93dfd34e26279113cdcc7b625"
31
31
  },
32
32
  {
33
33
  "name": "alphafox-admin",
34
- "version": "0.3.10",
34
+ "version": "0.3.11",
35
35
  "files": [
36
36
  {
37
37
  "path": "SKILL.md",
38
- "sha256": "0639573e3d59f42d3d718bdf07d719b01d802cbbdc04806c14882ebf8cd7c081",
38
+ "sha256": "dcdd0cdd11e6c4160e314766b7150b70fb0b0b6b52b4ad65723853c0ccefdd93",
39
39
  "size": 788
40
40
  }
41
41
  ],
42
- "hash": "bcd9d41096e65472eebc0981cc9f614c755f28ae307dffdb15117ad8e22c954c"
42
+ "hash": "38f9a8eff99035a55e40e96bd156d407282aa09adcf0c08b7713b18508c112dc"
43
43
  },
44
44
  {
45
45
  "name": "alphafox-auth",
46
- "version": "0.3.10",
46
+ "version": "0.3.11",
47
47
  "files": [
48
48
  {
49
49
  "path": "SKILL.md",
50
- "sha256": "d4edec17dd89e9e2e5d1759657837456f60d0b062d0a332334d9e6df6bfe003e",
50
+ "sha256": "1cc9b91b9f3994d3f0fde453fba4c08c0ff18849692518c1a73182cfc6267066",
51
51
  "size": 2371
52
52
  }
53
53
  ],
54
- "hash": "01c80f4fa33736e85b845576e5d026e7ec32869a8c0f806119625d44e43fcbaa"
54
+ "hash": "f14d148e80c7c25b9306e57039e10b341b04c06fbe8c0602b60d49033436a149"
55
55
  },
56
56
  {
57
57
  "name": "alphafox-cache",
58
- "version": "0.3.10",
58
+ "version": "0.3.11",
59
59
  "files": [
60
60
  {
61
61
  "path": "SKILL.md",
62
- "sha256": "c7f36174e5bb1ac97ca55e89b6720863d3d05a52a522ff5498a4c5ea4a31adaa",
62
+ "sha256": "292e0075bd55f5036a6bcb3afbc6184f5e010e4840b80c1940bbaa51bf8f2a9e",
63
63
  "size": 1530
64
64
  }
65
65
  ],
66
- "hash": "a5a68c82de33aa8e09b12a65de1c45c522490d1b807c34a9616792cc71a13350"
66
+ "hash": "4baa604211122515fa189bb5830d4b4b0633a92b585fd91b918879828d221740"
67
67
  },
68
68
  {
69
69
  "name": "alphafox-engine-backtest",
70
- "version": "0.3.10",
70
+ "version": "0.3.11",
71
71
  "files": [
72
72
  {
73
73
  "path": "SKILL.md",
74
- "sha256": "dbcb6dc047150bdf3988da112691868787be4a51bda95e60c63628269b06a8d7",
75
- "size": 7803
74
+ "sha256": "a0e00cdd48c4c4da1bbbe0d54f2b08dda273e6b6d7160e5a37485e7002218d05",
75
+ "size": 8278
76
76
  }
77
77
  ],
78
- "hash": "1e85862e3ea104968061a0d778b836a6475b1d676dfbbb80a074851cd4e386fb"
78
+ "hash": "49f17cdd45fef3c600ee954bb089b239609228dd205f12edad8973c6e2a13fba"
79
79
  },
80
80
  {
81
81
  "name": "alphafox-exchange",
82
- "version": "0.3.10",
82
+ "version": "0.3.11",
83
83
  "files": [
84
84
  {
85
85
  "path": "SKILL.md",
86
- "sha256": "100f74f3240d86e73531a981585c9a236d3d5fd3ea31ecb54a74074d4e256403",
86
+ "sha256": "e883d53ce237ecec682555c9171519bf16139fee0db968825a47d8cfc4ccb25b",
87
87
  "size": 744
88
88
  }
89
89
  ],
90
- "hash": "9230a196316900e2cf6bd15659d2bf47f5332d855663f4dd116744139cfcf968"
90
+ "hash": "087f26e565876d66709fb303cc6e379648a1b7de6f9f54c4015e973b8398fcb8"
91
91
  },
92
92
  {
93
93
  "name": "alphafox-market",
94
- "version": "0.3.10",
94
+ "version": "0.3.11",
95
95
  "files": [
96
96
  {
97
97
  "path": "SKILL.md",
98
- "sha256": "e750c23aaad020221a6cfdf2b273886c8367de63ca68df6a95cb31492c447f0d",
98
+ "sha256": "d37df5d1498d201abd9206f5536ffbbf35fa20ec7f47aa20da40f07abcd3f847",
99
99
  "size": 3079
100
100
  }
101
101
  ],
102
- "hash": "34283bcd2403e671ba5dcd4da7d3ad65f73a46ca3a5d29d28c3464038dfb074c"
102
+ "hash": "dc57e16172f577410f80922436593ab5ac5d1d2ab62402fb2c8ee7d8bac84cbd"
103
103
  },
104
104
  {
105
105
  "name": "alphafox-notification",
106
- "version": "0.3.10",
106
+ "version": "0.3.11",
107
107
  "files": [
108
108
  {
109
109
  "path": "SKILL.md",
110
- "sha256": "4d6e705ff6aff33af729bca184dcb716cd97d31b4fe188173b07423eba0d94d4",
110
+ "sha256": "0fdcc37a21deaee5195c2576ce563b895dc7aac2af2e269a38b2506860fbb5fc",
111
111
  "size": 699
112
112
  }
113
113
  ],
114
- "hash": "a36312725ffa86ed9e629b056375b68ae6955d56a304c5895e4ab24666a53763"
114
+ "hash": "c4acd5b245e6c1f21f1d35d96a33ffe8c1a4754c8405c92c54180765f14072ea"
115
115
  },
116
116
  {
117
117
  "name": "alphafox-shared",
118
- "version": "0.3.10",
118
+ "version": "0.3.11",
119
119
  "files": [
120
120
  {
121
121
  "path": "SKILL.md",
122
- "sha256": "4019aba3820aa1cf16dcb49202f251e4074829a130a7608caf6c67c3118a799c",
122
+ "sha256": "cfd0716870fb6bcadbb20c7cad1999f32f8f237cfebb1fa143f6a49687256568",
123
123
  "size": 5386
124
124
  }
125
125
  ],
126
- "hash": "ae7dbd98f00f8f6162d5dc77bd35ca13303895f7d6c91d910af1f46f03d2869e"
126
+ "hash": "939e048cd4243a13a341d0405a13f40d82dd6553a2db5e42765d90dae909331d"
127
127
  },
128
128
  {
129
129
  "name": "alphafox-strategy",
130
- "version": "0.3.10",
130
+ "version": "0.3.11",
131
131
  "files": [
132
132
  {
133
133
  "path": "SKILL.md",
134
- "sha256": "85c78855f0a312f5093642668606197cc68569e9203f5b85a6e9c4bd3b9c9c21",
134
+ "sha256": "be35901e6d5c3766f73274552e5403e172a9c3ce9d560fbfd78cb68fd1c894c1",
135
135
  "size": 1827
136
136
  }
137
137
  ],
138
- "hash": "cb79e63d42a2559d2ec69b684c7d13d3351b49e117ca3256f462f0f04ba7d657"
138
+ "hash": "e031e199ee2c1013e2bc39d2fc2a72b9f734ede86b3ca93d2a3ca53ace1c65cf"
139
139
  },
140
140
  {
141
141
  "name": "alphafox-trading",
142
- "version": "0.3.10",
142
+ "version": "0.3.11",
143
143
  "files": [
144
144
  {
145
145
  "path": "SKILL.md",
146
- "sha256": "b580088309ddaeb0b460b572c9861393fe8d9b1129fca2f9dd2e9caa4b568368",
146
+ "sha256": "2bac318aae54820edf6454c98e8ce4a16b350fce8e0e29c936ea434a72a0857d",
147
147
  "size": 3123
148
148
  }
149
149
  ],
150
- "hash": "c215ba2c61b5d4487046bb7f56145eabdc942c70c6c36f2d79cc2d7da887463f"
150
+ "hash": "2da4c6f7a381472f1d82118d5e44e3c52a1a6e7185b2f9e5a16181aa5f1c89bd"
151
151
  }
152
152
  ]
153
153
  }
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export declare const CLI_NAME = "alphafox";
2
2
  export declare const CLI_PACKAGE = "@alphafox/cli";
3
- export declare const CLI_VERSION = "0.3.10";
3
+ export declare const CLI_VERSION = "0.3.11";
4
4
  export { CATALOG_VERSION as CLI_CONTRACT_VERSION } from "./catalog/operations";
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.10";
6
+ exports.CLI_VERSION = "0.3.11";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphafox/cli",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "AlphaFox CLI — Agent/Human entry for the public Application API.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-account
3
3
  description: Account, wallet, and subscription read paths.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Account / wallet
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-admin
3
3
  description: Admin-only operations reusing Web role authorization.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Admin
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox
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.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # AlphaFox
@@ -37,7 +37,7 @@ If several rows apply, load **all** of them (typical: `alphafox-shared` + `alpha
37
37
  The CLI may print this on **stderr** at most once every 24 hours:
38
38
 
39
39
  ```text
40
- [alphafox] update available: 0.3.9 -> 0.3.10. After the user confirms, run: alphafox update --format json --no-input,
40
+ [alphafox] update available: 0.3.10 -> 0.3.11. After the user confirms, run: alphafox update --format json --no-input,
41
41
  ```
42
42
 
43
43
  If you see that notice (or `updateAvailable: true` from `alphafox update --check`):
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-shared
3
3
  description: Shared AlphaFox CLI rules for Agents — auth, profiles, envelopes, risk gates, and public operationIds only.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # AlphaFox shared Agent contract
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-auth
3
3
  description: Login, status, logout, whoami, and environment isolation for AlphaFox CLI.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Auth Skill
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-cache
3
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.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Cache
@@ -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.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Engine Backtest
@@ -52,7 +52,7 @@ alphafox engine-backtest run \
52
52
  --format jsonl --no-input
53
53
  ```
54
54
 
55
- Also valid: `--from` / `--to` instead of `--range`. `--create-experiment --name "..."` when there is no `--experiment` (needs `strategyDefinitionId` + `strategyDefinitionDisplay` `{zh,en}`; pass `--definition-label-zh` / `--definition-label-en` or the CLI falls back to the definition id). Persisted runs use the account tier from `subscriptions.me.get`; if `--tier` is supplied, it must match. With `--no-persist`, `runs.create` is skipped and `--tier` may simulate `free|pro|pro_max` (default `pro`). `--data-quality` defaults to `strict`. `--replay-timeframe` defaults to `1m` (allowed `1m|3m|5m|15m|30m|1h|4h`); this is the replay/download bar and is merged with plan indicator timeframes so a 4h RSI grid still replays on 1m. `runs.create` is `write`, not `high-risk-write` — do not add `--yes`. Do not update or delete experiments through this command.
55
+ Also valid: `--from` / `--to` instead of `--range`. `--create-experiment --name "..."` when there is no `--experiment` (needs `strategyDefinitionId` + `strategyDefinitionDisplay` `{zh,en}`; pass `--definition-label-zh` / `--definition-label-en` or the CLI falls back to the definition id). Persisted runs use the account tier from `subscriptions.me.get`; if `--tier` is supplied, it must match. With `--no-persist`, `runs.create` is skipped and `--tier` may simulate `free|pro|pro_max` (default `pro`). `--data-quality` defaults to `basic` (soft gaps finish the run and appear as `coverageNotice`; `prefix_gap` is less severe than `internal_gap`). `--data-quality strict` still fails on any gap. `--replay-timeframe` defaults to `1m` (allowed `1m|3m|5m|15m|30m|1h|4h`); this is the replay/download bar and is merged with plan indicator timeframes so a 4h RSI grid still replays on 1m. `runs.create` is `write`, not `high-risk-write` — do not add `--yes`. Do not update or delete experiments through this command.
56
56
 
57
57
  `--format jsonl` writes one JSON object per progress line (`{event:"progress",stage,fraction}`), then a final `{ok:true,data:{...}}` envelope.
58
58
 
@@ -92,13 +92,14 @@ Owner isolation and 7-day expiry are enforced by the server. Applying a coordina
92
92
 
93
93
  1. Edit config JSON.
94
94
  2. `engine-backtest run` (reuse `--experiment` after the first create).
95
- 3. Read `data.metrics` / `data.engineVersion` / `data.runId` / `data.experimentUrl`.
95
+ 3. Read `data.metrics` / `data.engineVersion` / `data.runId` / `data.experimentUrl`. After the run, also read `data.coverageNotice` (`warning` = mid-range candle gaps; `notice` = start / other soft gaps).
96
96
  4. Adjust parameters and run again. Do not invent a token flag if persist returns 401 — `alphafox auth login`.
97
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.
98
98
 
99
99
  ## Safety
100
100
 
101
101
  - Unauthenticated persist/create fails like `whoami` (HTTP 401, exit 77).
102
+ - Default `basic` data quality: missing market / empty / corrupted tape **stops**; soft gaps complete the run and surface as `coverageNotice`. Do not silently switch to `strict` or hide those notices.
102
103
  - `strict` data quality: missing/gapped tape **stops**. Do not retry as `basic` unless the operator asks.
103
104
  - `planBacktest` unsupported → fail with the plan reason. Do not degrade to a guessed universe.
104
105
  - Catalog `engine_backtest.experiments.byId.update` / `.delete` / `.sweeps.byId.delete` are high-risk-write and are **not** this command's run/sweep path.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-exchange
3
3
  description: Exchange connectors list and connection management via Public API.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Exchange connectors
@@ -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.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Market
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-notification
3
3
  description: Notification channels and subscriptions.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Notification
@@ -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.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Strategy definitions
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-trading
3
3
  description: Running strategies (traders) — create, list, start, and stop. A trader is a live or paper strategy instance (grid, dca, copy, …), not a person.
4
- version: 0.3.10
4
+ version: 0.3.11
5
5
  ---
6
6
 
7
7
  # Running strategies (traders)
@@ -33,13 +33,13 @@ import {
33
33
  assembleScenario,
34
34
  } from "@alphafoxai/backtest-runner";
35
35
 
36
- const { tape, buffers, coverageWarnings } = await loadTape({
36
+ const { tape, buffers, coverageWarnings, coverageIssues } = await loadTape({
37
37
  exchangeId: "binance_perp_usdt",
38
38
  symbols: ["BTC/USDT:USDT"],
39
39
  timeframes: ["1m", "1h"],
40
40
  fromMs,
41
41
  toMs,
42
- dataQualityMode: "strict",
42
+ dataQualityMode: "basic",
43
43
  });
44
44
 
45
45
  const scenario = assembleScenario({
@@ -82,8 +82,8 @@ const scenario = assembleScenario({
82
82
 
83
83
  ## 数据质量
84
84
 
85
- - `strict`(默认):任何缺口 / 缺数 / 非法 K 线都抛 `TapeDataUnavailableError`。
86
- - `basic`:硬失败(缺市场、空序列、非法 K 线、拉数失败)仍抛错;软缺口进入 `coverageWarnings`。
85
+ - `basic`(默认):硬失败(缺市场、空序列、非法 K 线、拉数失败)仍抛错;软缺口进入 `coverageIssues` `coverageWarnings`。起始缺口(`prefix_gap`)较轻,中间缺口(`internal_gap`)较重。
86
+ - `strict`:任何缺口 / 缺数 / 非法 K 线都抛 `TapeDataUnavailableError`。
87
87
  - 禁止 mock 成功或静默降级。
88
88
 
89
89
  ## 已知限制(相对 alphafox-web)
@@ -109,6 +109,7 @@ export function createFileTapeCache(
109
109
  export function createDisabledTapeCache(): FileTapeCache;
110
110
 
111
111
  export type TapeDataQualityMode = "strict" | "basic";
112
+ export const DEFAULT_TAPE_DATA_QUALITY_MODE: TapeDataQualityMode;
112
113
  export type TapeDataIssueCode =
113
114
  | "market_missing"
114
115
  | "ohlcv_missing"
@@ -140,6 +141,17 @@ export function isTapeDataUnavailableError(
140
141
  value: unknown
141
142
  ): value is TapeDataUnavailableError;
142
143
 
144
+ export function summarizeTapeCoverageIssues(issues: readonly TapeDataIssue[]): {
145
+ readonly prefix: TapeDataIssue[];
146
+ readonly internal: TapeDataIssue[];
147
+ readonly other: TapeDataIssue[];
148
+ };
149
+
150
+ export function formatCoverageSoftWarning(
151
+ issues: readonly TapeDataIssue[],
152
+ coverageRatio: number
153
+ ): string;
154
+
143
155
  export type EngineBacktestPrecisionMode = "DECIMAL_PLACES" | "TICK_SIZE";
144
156
  export type EngineBacktestSubscriptionTier = "free" | "pro" | "pro_max";
145
157
  export type EngineBacktestPricePath = "ohlc_path_4" | "close_only";
@@ -334,6 +346,7 @@ export interface TapeLoadResult {
334
346
  readonly tape: EngineBacktestTapeInput;
335
347
  readonly buffers: Record<string, ArrayBuffer>;
336
348
  readonly coverageWarnings: readonly string[];
349
+ readonly coverageIssues: readonly TapeDataIssue[];
337
350
  readonly chartData?: {
338
351
  readonly exchangeId: string;
339
352
  readonly series: readonly TapeChartSeries[];
@@ -27,8 +27,11 @@ export {
27
27
  export {
28
28
  TapeDataUnavailableError,
29
29
  isTapeDataUnavailableError,
30
+ DEFAULT_TAPE_DATA_QUALITY_MODE,
30
31
  evaluateOhlcvCoverage,
31
32
  analyzeOhlcvCoverage,
33
+ formatCoverageSoftWarning,
34
+ summarizeTapeCoverageIssues,
32
35
  } from "./lib/coverage.mjs";
33
36
  export {
34
37
  TIMEFRAME_MS,
@@ -178,6 +178,8 @@ export function analyzeOhlcvCoverage(input) {
178
178
  };
179
179
  }
180
180
 
181
+ export const DEFAULT_TAPE_DATA_QUALITY_MODE = "basic";
182
+
181
183
  export function evaluateOhlcvCoverage(input) {
182
184
  const report = analyzeOhlcvCoverage(input);
183
185
 
@@ -210,12 +212,45 @@ export function evaluateOhlcvCoverage(input) {
210
212
  };
211
213
  }
212
214
 
215
+ export function summarizeTapeCoverageIssues(issues) {
216
+ const prefix = [];
217
+ const internal = [];
218
+ const other = [];
219
+ for (const item of issues) {
220
+ if (item.code === "prefix_gap") {
221
+ prefix.push(item);
222
+ } else if (item.code === "internal_gap") {
223
+ internal.push(item);
224
+ } else {
225
+ other.push(item);
226
+ }
227
+ }
228
+ return { prefix, internal, other };
229
+ }
230
+
213
231
  export function formatCoverageSoftWarning(issues, coverageRatio) {
214
- const codes = [...new Set(issues.map((item) => item.code))].join(", ");
215
232
  const sample = issues[0];
216
233
  const symbol = sample?.symbol ?? "unknown";
217
234
  const timeframe = sample?.timeframe ?? "*";
218
- return `${symbol} ${timeframe}: accepted soft data issues (${codes}) with ${(coverageRatio * 100).toFixed(1)}% replay coverage`;
235
+ const codes = [...new Set(issues.map((item) => item.code))];
236
+ const summary = summarizeTapeCoverageIssues(issues);
237
+ const parts = [];
238
+ if (summary.prefix.length > 0) {
239
+ parts.push("missing start candles (less severe)");
240
+ }
241
+ if (summary.internal.length > 0) {
242
+ parts.push("missing mid-range candles (more severe)");
243
+ }
244
+ if (summary.other.length > 0) {
245
+ parts.push(
246
+ `other soft gaps (${summary.other.map((item) => item.code).join(", ")})`
247
+ );
248
+ }
249
+ const detail =
250
+ parts.length > 0
251
+ ? parts.join("; ")
252
+ : `accepted soft data issues (${codes.join(", ")})`;
253
+ return `${symbol} ${timeframe}: ${detail} with ${(coverageRatio * 100).toFixed(1)}% replay coverage`;
219
254
  }
220
255
 
221
256
  function issue(input, code, detail = {}) {
@@ -5,6 +5,7 @@ import {
5
5
  DEFAULT_TAPE_CACHE_DIR,
6
6
  } from "./cache.mjs";
7
7
  import {
8
+ DEFAULT_TAPE_DATA_QUALITY_MODE,
8
9
  formatCoverageSoftWarning,
9
10
  isTapeDataUnavailableError,
10
11
  TapeDataUnavailableError,
@@ -187,7 +188,8 @@ export async function loadTape(request, options = {}) {
187
188
  const seriesConcurrency = resolveTapeSeriesConcurrency(
188
189
  options.seriesConcurrency ?? request.seriesConcurrency
189
190
  );
190
- const dataQualityMode = request.dataQualityMode ?? "strict";
191
+ const dataQualityMode =
192
+ request.dataQualityMode ?? DEFAULT_TAPE_DATA_QUALITY_MODE;
191
193
  const baseTimeframe = resolvePlanBaseTimeframe({
192
194
  baseTimeframe: request.baseTimeframe,
193
195
  timeframes: [
@@ -284,6 +286,7 @@ export async function loadTape(request, options = {}) {
284
286
  const totalSeries = seriesJobs.length;
285
287
  const dataIssues = [];
286
288
  const coverageWarnings = [];
289
+ const coverageIssues = [];
287
290
  const seriesFractions = new Array(totalSeries).fill(0);
288
291
  let lastOhlcvDetail = "";
289
292
  const reportOhlcv = (index, fraction, detail) => {
@@ -343,6 +346,7 @@ export async function loadTape(request, options = {}) {
343
346
  continue;
344
347
  }
345
348
  if (result.loaded.softIssues.length > 0) {
349
+ coverageIssues.push(...result.loaded.softIssues);
346
350
  coverageWarnings.push(
347
351
  formatCoverageSoftWarning(
348
352
  result.loaded.softIssues,
@@ -409,6 +413,7 @@ export async function loadTape(request, options = {}) {
409
413
  },
410
414
  buffers,
411
415
  coverageWarnings,
416
+ coverageIssues,
412
417
  chartData: {
413
418
  exchangeId: exchangeDefinition.id,
414
419
  series: chartSeries,