@tradejs/cli 1.0.10 → 1.0.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.
package/README.md CHANGED
@@ -19,7 +19,16 @@ Official CLI for the TradeJS open-source framework.
19
19
 
20
20
  ## Standard External Install Flow
21
21
 
22
- For a normal external project with CLI + runtime + UI:
22
+ For a new external project with CLI + runtime + UI:
23
+
24
+ ```bash
25
+ npx create-tradejs
26
+ ```
27
+
28
+ The generator installs the packages, starts local infra, and opens the install
29
+ page. The user chooses the local `root` password before entering the dashboard.
30
+
31
+ For manual integration into an existing project:
23
32
 
24
33
  ```bash
25
34
  npm i @tradejs/app @tradejs/core @tradejs/node @tradejs/types @tradejs/base @tradejs/cli
@@ -34,7 +43,7 @@ import { basePreset } from '@tradejs/base';
34
43
  export default defineConfig(basePreset);
35
44
  ```
36
45
 
37
- ## First Commands In A Fresh Project
46
+ ## Manual Setup Commands
38
47
 
39
48
  ```bash
40
49
  npx @tradejs/cli infra-init
@@ -43,10 +52,10 @@ npx @tradejs/cli doctor
43
52
  npx @tradejs/cli user-add -u root -p 'StrongPassword123!'
44
53
  ```
45
54
 
46
- After that you can run:
55
+ After saving a backtest config, you can run:
47
56
 
48
57
  ```bash
49
- npx @tradejs/cli backtest
58
+ npx @tradejs/cli backtest --config MaStrategy:base
50
59
  npx @tradejs/cli backtest --ai
51
60
  npx @tradejs/cli ai-export
52
61
  npx @tradejs/cli ai-train -n 50 --minQuality 4
package/dist/cli.js CHANGED
@@ -9425,7 +9425,7 @@ var init_derivativesContextBackfill = __esm({
9425
9425
  });
9426
9426
 
9427
9427
  // src/lib/coinMarketCapContextBackfill.ts
9428
- var import_chalk8, import_async2, import_timescale3, import_userSettings3, DAY_MS7, SOURCE_GLOBAL_DAILY, SOURCE_REFERENCE, SOURCE_EXCHANGE_LIQUIDITY, SOURCE_FEAR_GREED, SOURCE_INDEX, COVERAGE_SCOPE_ALL, REFERENCE_ASSETS, CMC_INDEXES, DEFAULT_EXCHANGE_SLUGS, lastRequestTs2, asInt3, parseEnabledFlag2, getBaseUrl, getRequestDelayMs2, getMaxRetries, getMaxBackfillDays, getHistoricalAccessMonths, isExchangeLiquidityBackfillEnabled, isFearGreedBackfillEnabled, isVerboseRequestLoggingEnabled, getFearGreedPageSize, getExchangeSlugs, toFiniteNumberOrNull, toIntOrNull, getRecord, getNestedRecord, safeDivide2, sumFinite, toExchangeLiquidityRegime, normalizeFearGreedClassification, toFearGreedRegime, dayStartUtc, toIsoDate, subtractUtcMonths, nextUtcDayStart, resolveCoinMarketCapBackfillWindow, hasIntervalCoverage, hasDailyCoverage, coverageKey2, isMarketContextBackfillCoverageDataBearing, coverageRowsToKeySet, hasBackfillCoverage, formatCoverageRange, formatReferenceCoverage, resolveBackfillStatus, formatSourceStatus, createCoinMarketCapFetchStats, getCoinMarketCapPathLabel, recordCoinMarketCapFetchStats, formatCoinMarketCapFetchStats, formatRowTimeRange, logBackfillSourceDone, markBackfillCoverage, coinMarketCapFetch, fetchCoinMarketCapIndexRows, coinMarketCapGlobalPayloadToRows, normalizeAssetDataItems, coinMarketCapHistoricalQuotesPayloadToRows, coinMarketCapExchangeQuotesPayloadToLiquidityRows, coinMarketCapFearGreedPayloadToRows, toCmcIndexWeightPct, coinMarketCapIndexPayloadToRows, shouldBackfillCoinMarketCapContextForMode, shouldBackfillCoinMarketCapContextForBacktest, shouldBackfillCoinMarketCapContextForReplay, shouldBackfillCoinMarketCapContextForSignals, skippedResult, backfillCoinMarketCapContext, backfillCoinMarketCapContextForBacktest, backfillCoinMarketCapContextForReplay, backfillCoinMarketCapContextForSignals;
9428
+ var import_chalk8, import_async2, import_timescale3, import_userSettings3, DAY_MS7, DEFAULT_CONTEXT_MAX_AGE_MS, DEFAULT_FEAR_GREED_STALE_RETRY_MS, SOURCE_GLOBAL_DAILY, SOURCE_REFERENCE, SOURCE_EXCHANGE_LIQUIDITY, SOURCE_FEAR_GREED, SOURCE_INDEX, COVERAGE_SCOPE_ALL, REFERENCE_ASSETS, CMC_INDEXES, DEFAULT_EXCHANGE_SLUGS, lastRequestTs2, asInt3, parseEnabledFlag2, getBaseUrl, getRequestDelayMs2, getMaxRetries, getMaxBackfillDays, getHistoricalAccessMonths, isExchangeLiquidityBackfillEnabled, isFearGreedBackfillEnabled, isVerboseRequestLoggingEnabled, getFearGreedPageSize, getContextMaxAgeMs, getFearGreedStaleRetryMs, getExchangeSlugs, toFiniteNumberOrNull, toIntOrNull, getRecord, getNestedRecord, safeDivide2, sumFinite, toExchangeLiquidityRegime, normalizeFearGreedClassification, toFearGreedRegime, dayStartUtc, toIsoDate, subtractUtcMonths, nextUtcDayStart, resolveCoinMarketCapBackfillWindow, hasIntervalCoverage, hasDailyCoverage, coverageKey2, isMarketContextBackfillCoverageDataBearing, coverageRowsToKeySet, hasBackfillCoverage, hasRecentBackfillCoverage, formatCoverageRange, formatReferenceCoverage, resolveBackfillStatus, formatSourceStatus, createCoinMarketCapFetchStats, getCoinMarketCapPathLabel, recordCoinMarketCapFetchStats, formatCoinMarketCapFetchStats, formatRowTimeRange, logBackfillSourceDone, markBackfillCoverage, coinMarketCapFetch, fetchCoinMarketCapIndexRows, coinMarketCapGlobalPayloadToRows, normalizeAssetDataItems, coinMarketCapHistoricalQuotesPayloadToRows, coinMarketCapExchangeQuotesPayloadToLiquidityRows, coinMarketCapFearGreedPayloadToRows, toCmcIndexWeightPct, coinMarketCapIndexPayloadToRows, shouldBackfillCoinMarketCapContextForMode, shouldBackfillCoinMarketCapContextForBacktest, shouldBackfillCoinMarketCapContextForReplay, shouldBackfillCoinMarketCapContextForSignals, skippedResult, backfillCoinMarketCapContext, backfillCoinMarketCapContextForBacktest, backfillCoinMarketCapContextForReplay, backfillCoinMarketCapContextForSignals;
9429
9429
  var init_coinMarketCapContextBackfill = __esm({
9430
9430
  "src/lib/coinMarketCapContextBackfill.ts"() {
9431
9431
  "use strict";
@@ -9434,6 +9434,8 @@ var init_coinMarketCapContextBackfill = __esm({
9434
9434
  import_timescale3 = require("@tradejs/infra/timescale");
9435
9435
  import_userSettings3 = require("@tradejs/infra/userSettings");
9436
9436
  DAY_MS7 = 864e5;
9437
+ DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
9438
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
9437
9439
  SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
9438
9440
  SOURCE_REFERENCE = "coinmarketcap_reference_asset";
9439
9441
  SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -9479,6 +9481,14 @@ var init_coinMarketCapContextBackfill = __esm({
9479
9481
  isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
9480
9482
  isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
9481
9483
  getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
9484
+ getContextMaxAgeMs = () => asInt3(
9485
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
9486
+ DEFAULT_CONTEXT_MAX_AGE_MS
9487
+ );
9488
+ getFearGreedStaleRetryMs = () => asInt3(
9489
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
9490
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
9491
+ );
9482
9492
  getExchangeSlugs = () => {
9483
9493
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
9484
9494
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -9599,6 +9609,23 @@ var init_coinMarketCapContextBackfill = __esm({
9599
9609
  )
9600
9610
  );
9601
9611
  hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
9612
+ hasRecentBackfillCoverage = (rows, params) => {
9613
+ const key = coverageKey2(params);
9614
+ return rows.some((row) => {
9615
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
9616
+ if (coverageKey2({
9617
+ source: row.source,
9618
+ scope: row.scope,
9619
+ interval: row.interval,
9620
+ fromMs: row.fromMs,
9621
+ toMs: row.toMs
9622
+ }) !== key) {
9623
+ return false;
9624
+ }
9625
+ const checkedAtMs = Number(row.checkedAtMs);
9626
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
9627
+ });
9628
+ };
9602
9629
  formatCoverageRange = (coverage) => {
9603
9630
  if (!coverage) return "coverage=none";
9604
9631
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -10126,13 +10153,24 @@ var init_coinMarketCapContextBackfill = __esm({
10126
10153
  toMs
10127
10154
  }
10128
10155
  );
10129
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
10156
+ const fearGreedBackfillParams = {
10130
10157
  source: SOURCE_FEAR_GREED,
10131
10158
  scope: COVERAGE_SCOPE_ALL,
10132
10159
  interval: "1d",
10133
10160
  fromMs,
10134
10161
  toMs
10135
- });
10162
+ };
10163
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
10164
+ fearGreedBackfillKeys,
10165
+ fearGreedBackfillParams
10166
+ );
10167
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
10168
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
10169
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
10170
+ ...fearGreedBackfillParams,
10171
+ atMs: params.endMs,
10172
+ maxAgeMs: getFearGreedStaleRetryMs()
10173
+ }));
10136
10174
  const indexesBackfillCovered = CMC_INDEXES.every(
10137
10175
  (index) => hasBackfillCoverage(indexBackfillKeys, {
10138
10176
  source: SOURCE_INDEX,
@@ -10449,7 +10487,7 @@ var init_coinMarketCapContextBackfill = __esm({
10449
10487
  };
10450
10488
  backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
10451
10489
  backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
10452
- backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
10490
+ backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
10453
10491
  }
10454
10492
  });
10455
10493
 
@@ -11173,6 +11211,11 @@ var init_runEnvironment = __esm({
11173
11211
  assetClasses
11174
11212
  })
11175
11213
  );
11214
+ if (!loadedTickers.length) {
11215
+ throw new Error(
11216
+ `No tickers available for ${connectorName}. Check connector market-data access or select tickers explicitly.`
11217
+ );
11218
+ }
11176
11219
  if (showTickersList) {
11177
11220
  console.log(import_chalk12.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
11178
11221
  return null;
@@ -11224,7 +11267,7 @@ var init_runEnvironment = __esm({
11224
11267
  log: (message) => console.log(import_chalk12.default.gray(message))
11225
11268
  });
11226
11269
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval5);
11227
- if (backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval5)) {
11270
+ if (import_constants6.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval5)) {
11228
11271
  await timeOperation2(
11229
11272
  `update ${connectorName} execution`,
11230
11273
  () => (0, import_cli4.update)(
@@ -12083,12 +12126,9 @@ var init_runnerCore = __esm({
12083
12126
  Number.isFinite(requestedTestsLimit) ? testsSkip + requestedTestsLimit : void 0
12084
12127
  );
12085
12128
  if (!preparedSuite.length) {
12086
- console.log(
12087
- import_chalk14.default.yellow(
12088
- `No tests selected (skip=${testsSkip}, limit=${Number.isFinite(requestedTestsLimit) ? requestedTestsLimit : "all"}).`
12089
- )
12129
+ throw new Error(
12130
+ `No backtest tests selected (available=${testSuite.length}, skip=${testsSkip}, limit=${Number.isFinite(requestedTestsLimit) ? requestedTestsLimit : "all"}).`
12090
12131
  );
12091
- return null;
12092
12132
  }
12093
12133
  await prepareMarketContextForRun({
12094
12134
  mode: "backtest",
@@ -20114,7 +20154,7 @@ var init_executionCalibration = __esm({
20114
20154
  );
20115
20155
  }
20116
20156
  notes.push(
20117
- "Delay risk bps is disabled; signal-to-arrival latency is modeled by delayed lower-timeframe backtest fills."
20157
+ "Delay risk bps is disabled; signal-to-arrival latency is modeled by next-bar-open backtest fills."
20118
20158
  );
20119
20159
  return {
20120
20160
  confidence: fullTelemetrySamples.length >= 50 ? "high" : fullTelemetrySamples.length >= 20 ? "medium" : "low",
@@ -20311,13 +20351,12 @@ var init_infraCommon = __esm({
20311
20351
  DEV_COMPOSE_TEMPLATE = `services:
20312
20352
  timescale:
20313
20353
  image: timescale/timescaledb:latest-pg16
20314
- container_name: tradejs-timescale
20315
20354
  environment:
20316
20355
  POSTGRES_USER: \${PG_USER:-app}
20317
20356
  POSTGRES_PASSWORD: \${PG_PASSWORD:-app}
20318
20357
  POSTGRES_DB: \${PG_DB:-app}
20319
20358
  ports:
20320
- - "5432:5432"
20359
+ - "\${PG_PORT:-5432}:5432"
20321
20360
  command:
20322
20361
  - postgres
20323
20362
  - -c
@@ -20337,14 +20376,18 @@ var init_infraCommon = __esm({
20337
20376
 
20338
20377
  redis:
20339
20378
  image: redis/redis-stack:latest
20340
- container_name: tradejs-redis
20341
20379
  environment:
20342
20380
  REDIS_ARGS: --save "" --appendonly no --stop-writes-on-bgsave-error no
20343
20381
  ports:
20344
- - "6379:6379"
20345
- - "5540:8001"
20382
+ - "\${REDIS_PORT:-6379}:6379"
20383
+ - "\${REDIS_INSIGHT_PORT:-5540}:8001"
20346
20384
  volumes:
20347
20385
  - tradejs_redisdata:/data
20386
+ healthcheck:
20387
+ test: ["CMD", "redis-cli", "ping"]
20388
+ interval: 5s
20389
+ timeout: 3s
20390
+ retries: 10
20348
20391
  restart: unless-stopped
20349
20392
 
20350
20393
  volumes:
@@ -20442,8 +20485,16 @@ var init_infraUp = __esm({
20442
20485
  main19 = async () => {
20443
20486
  const composePath = requireDevComposeFile();
20444
20487
  console.log(import_chalk27.default.cyan("Starting dev infra (timescale, redis)..."));
20445
- runDockerCompose(composePath, ["up", "-d", "timescale", "redis"]);
20446
- console.log(import_chalk27.default.green("Dev infra is up."));
20488
+ runDockerCompose(composePath, [
20489
+ "up",
20490
+ "-d",
20491
+ "--wait",
20492
+ "--wait-timeout",
20493
+ "120",
20494
+ "timescale",
20495
+ "redis"
20496
+ ]);
20497
+ console.log(import_chalk27.default.green("Dev infra is ready."));
20447
20498
  };
20448
20499
  }
20449
20500
  });
@@ -52,6 +52,8 @@ var import_async = require("@tradejs/core/async");
52
52
  var import_timescale = require("@tradejs/infra/timescale");
53
53
  var import_userSettings = require("@tradejs/infra/userSettings");
54
54
  var DAY_MS = 864e5;
55
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
56
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
55
57
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
56
58
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
57
59
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -97,6 +99,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag(
97
99
  var isFearGreedBackfillEnabled = () => parseEnabledFlag(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
98
100
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
99
101
  var getFearGreedPageSize = () => asInt(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
102
+ var getContextMaxAgeMs = () => asInt(
103
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
104
+ DEFAULT_CONTEXT_MAX_AGE_MS
105
+ );
106
+ var getFearGreedStaleRetryMs = () => asInt(
107
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
108
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
109
+ );
100
110
  var getExchangeSlugs = () => {
101
111
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
102
112
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -217,6 +227,23 @@ var coverageRowsToKeySet = (rows) => new Set(
217
227
  )
218
228
  );
219
229
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey(params));
230
+ var hasRecentBackfillCoverage = (rows, params) => {
231
+ const key = coverageKey(params);
232
+ return rows.some((row) => {
233
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
234
+ if (coverageKey({
235
+ source: row.source,
236
+ scope: row.scope,
237
+ interval: row.interval,
238
+ fromMs: row.fromMs,
239
+ toMs: row.toMs
240
+ }) !== key) {
241
+ return false;
242
+ }
243
+ const checkedAtMs = Number(row.checkedAtMs);
244
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
245
+ });
246
+ };
220
247
  var formatCoverageRange = (coverage) => {
221
248
  if (!coverage) return "coverage=none";
222
249
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -744,13 +771,24 @@ var backfillCoinMarketCapContext = async (params) => {
744
771
  toMs
745
772
  }
746
773
  );
747
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
774
+ const fearGreedBackfillParams = {
748
775
  source: SOURCE_FEAR_GREED,
749
776
  scope: COVERAGE_SCOPE_ALL,
750
777
  interval: "1d",
751
778
  fromMs,
752
779
  toMs
753
- });
780
+ };
781
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
782
+ fearGreedBackfillKeys,
783
+ fearGreedBackfillParams
784
+ );
785
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
786
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
787
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
788
+ ...fearGreedBackfillParams,
789
+ atMs: params.endMs,
790
+ maxAgeMs: getFearGreedStaleRetryMs()
791
+ }));
754
792
  const indexesBackfillCovered = CMC_INDEXES.every(
755
793
  (index) => hasBackfillCoverage(indexBackfillKeys, {
756
794
  source: SOURCE_INDEX,
@@ -1067,7 +1105,7 @@ var backfillCoinMarketCapContext = async (params) => {
1067
1105
  };
1068
1106
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
1069
1107
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
1070
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
1108
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
1071
1109
  // Annotate the CommonJS export names for ESM import in node:
1072
1110
  0 && (module.exports = {
1073
1111
  backfillCoinMarketCapContext,
@@ -619,7 +619,7 @@ var buildRecommendation = (samples) => {
619
619
  );
620
620
  }
621
621
  notes.push(
622
- "Delay risk bps is disabled; signal-to-arrival latency is modeled by delayed lower-timeframe backtest fills."
622
+ "Delay risk bps is disabled; signal-to-arrival latency is modeled by next-bar-open backtest fills."
623
623
  );
624
624
  return {
625
625
  confidence: fullTelemetrySamples.length >= 50 ? "high" : fullTelemetrySamples.length >= 20 ? "medium" : "low",
@@ -3257,6 +3257,8 @@ var import_async2 = require("@tradejs/core/async");
3257
3257
  var import_timescale3 = require("@tradejs/infra/timescale");
3258
3258
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3259
3259
  var DAY_MS3 = 864e5;
3260
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3261
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3260
3262
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3261
3263
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3262
3264
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3302,6 +3304,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3302
3304
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3303
3305
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3304
3306
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3307
+ var getContextMaxAgeMs = () => asInt3(
3308
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3309
+ DEFAULT_CONTEXT_MAX_AGE_MS
3310
+ );
3311
+ var getFearGreedStaleRetryMs = () => asInt3(
3312
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3313
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3314
+ );
3305
3315
  var getExchangeSlugs = () => {
3306
3316
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3307
3317
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -3422,6 +3432,23 @@ var coverageRowsToKeySet = (rows) => new Set(
3422
3432
  )
3423
3433
  );
3424
3434
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
3435
+ var hasRecentBackfillCoverage = (rows, params) => {
3436
+ const key = coverageKey2(params);
3437
+ return rows.some((row) => {
3438
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
3439
+ if (coverageKey2({
3440
+ source: row.source,
3441
+ scope: row.scope,
3442
+ interval: row.interval,
3443
+ fromMs: row.fromMs,
3444
+ toMs: row.toMs
3445
+ }) !== key) {
3446
+ return false;
3447
+ }
3448
+ const checkedAtMs = Number(row.checkedAtMs);
3449
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
3450
+ });
3451
+ };
3425
3452
  var formatCoverageRange = (coverage) => {
3426
3453
  if (!coverage) return "coverage=none";
3427
3454
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -3949,13 +3976,24 @@ var backfillCoinMarketCapContext = async (params) => {
3949
3976
  toMs
3950
3977
  }
3951
3978
  );
3952
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
3979
+ const fearGreedBackfillParams = {
3953
3980
  source: SOURCE_FEAR_GREED,
3954
3981
  scope: COVERAGE_SCOPE_ALL,
3955
3982
  interval: "1d",
3956
3983
  fromMs,
3957
3984
  toMs
3958
- });
3985
+ };
3986
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
3987
+ fearGreedBackfillKeys,
3988
+ fearGreedBackfillParams
3989
+ );
3990
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
3991
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
3992
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
3993
+ ...fearGreedBackfillParams,
3994
+ atMs: params.endMs,
3995
+ maxAgeMs: getFearGreedStaleRetryMs()
3996
+ }));
3959
3997
  const indexesBackfillCovered = CMC_INDEXES.every(
3960
3998
  (index) => hasBackfillCoverage(indexBackfillKeys, {
3961
3999
  source: SOURCE_INDEX,
@@ -4272,7 +4310,7 @@ var backfillCoinMarketCapContext = async (params) => {
4272
4310
  };
4273
4311
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4274
4312
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4275
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4313
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4276
4314
 
4277
4315
  // src/lib/runFormatting.ts
4278
4316
  var import_date_fns = require("date-fns");
@@ -2163,6 +2163,11 @@ var prepareRunEnvironment = async ({
2163
2163
  assetClasses
2164
2164
  })
2165
2165
  );
2166
+ if (!loadedTickers.length) {
2167
+ throw new Error(
2168
+ `No tickers available for ${connectorName}. Check connector market-data access or select tickers explicitly.`
2169
+ );
2170
+ }
2166
2171
  if (showTickersList) {
2167
2172
  console.log(import_chalk3.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
2168
2173
  return null;
@@ -2214,7 +2219,7 @@ var prepareRunEnvironment = async ({
2214
2219
  log: (message) => console.log(import_chalk3.default.gray(message))
2215
2220
  });
2216
2221
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval);
2217
- if (backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2222
+ if (import_constants.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2218
2223
  await timeOperation2(
2219
2224
  `update ${connectorName} execution`,
2220
2225
  () => (0, import_cli3.update)(
@@ -3318,6 +3318,8 @@ var import_async2 = require("@tradejs/core/async");
3318
3318
  var import_timescale3 = require("@tradejs/infra/timescale");
3319
3319
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3320
3320
  var DAY_MS3 = 864e5;
3321
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3322
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3321
3323
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3322
3324
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3323
3325
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3363,6 +3365,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3363
3365
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3364
3366
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3365
3367
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3368
+ var getContextMaxAgeMs = () => asInt3(
3369
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3370
+ DEFAULT_CONTEXT_MAX_AGE_MS
3371
+ );
3372
+ var getFearGreedStaleRetryMs = () => asInt3(
3373
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3374
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3375
+ );
3366
3376
  var getExchangeSlugs = () => {
3367
3377
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3368
3378
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -3483,6 +3493,23 @@ var coverageRowsToKeySet = (rows) => new Set(
3483
3493
  )
3484
3494
  );
3485
3495
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
3496
+ var hasRecentBackfillCoverage = (rows, params) => {
3497
+ const key = coverageKey2(params);
3498
+ return rows.some((row) => {
3499
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
3500
+ if (coverageKey2({
3501
+ source: row.source,
3502
+ scope: row.scope,
3503
+ interval: row.interval,
3504
+ fromMs: row.fromMs,
3505
+ toMs: row.toMs
3506
+ }) !== key) {
3507
+ return false;
3508
+ }
3509
+ const checkedAtMs = Number(row.checkedAtMs);
3510
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
3511
+ });
3512
+ };
3486
3513
  var formatCoverageRange = (coverage) => {
3487
3514
  if (!coverage) return "coverage=none";
3488
3515
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4010,13 +4037,24 @@ var backfillCoinMarketCapContext = async (params) => {
4010
4037
  toMs
4011
4038
  }
4012
4039
  );
4013
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4040
+ const fearGreedBackfillParams = {
4014
4041
  source: SOURCE_FEAR_GREED,
4015
4042
  scope: COVERAGE_SCOPE_ALL,
4016
4043
  interval: "1d",
4017
4044
  fromMs,
4018
4045
  toMs
4019
- });
4046
+ };
4047
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4048
+ fearGreedBackfillKeys,
4049
+ fearGreedBackfillParams
4050
+ );
4051
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4052
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4053
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4054
+ ...fearGreedBackfillParams,
4055
+ atMs: params.endMs,
4056
+ maxAgeMs: getFearGreedStaleRetryMs()
4057
+ }));
4020
4058
  const indexesBackfillCovered = CMC_INDEXES.every(
4021
4059
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4022
4060
  source: SOURCE_INDEX,
@@ -4333,7 +4371,7 @@ var backfillCoinMarketCapContext = async (params) => {
4333
4371
  };
4334
4372
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4335
4373
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4336
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4374
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4337
4375
 
4338
4376
  // src/lib/marketContextPrepare.ts
4339
4377
  var shouldPrepareDerivativesContextForRun = (params) => {
@@ -4936,6 +4974,11 @@ var prepareRunEnvironment = async ({
4936
4974
  assetClasses
4937
4975
  })
4938
4976
  );
4977
+ if (!loadedTickers.length) {
4978
+ throw new Error(
4979
+ `No tickers available for ${connectorName}. Check connector market-data access or select tickers explicitly.`
4980
+ );
4981
+ }
4939
4982
  if (showTickersList) {
4940
4983
  console.log(import_chalk7.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
4941
4984
  return null;
@@ -4987,7 +5030,7 @@ var prepareRunEnvironment = async ({
4987
5030
  log: (message) => console.log(import_chalk7.default.gray(message))
4988
5031
  });
4989
5032
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval2);
4990
- if (backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval2)) {
5033
+ if (import_constants2.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval2)) {
4991
5034
  await timeOperation2(
4992
5035
  `update ${connectorName} execution`,
4993
5036
  () => (0, import_cli3.update)(
@@ -5936,12 +5979,9 @@ var buildPreparedTestSuite = async ({
5936
5979
  Number.isFinite(requestedTestsLimit) ? testsSkip + requestedTestsLimit : void 0
5937
5980
  );
5938
5981
  if (!preparedSuite.length) {
5939
- console.log(
5940
- import_chalk9.default.yellow(
5941
- `No tests selected (skip=${testsSkip}, limit=${Number.isFinite(requestedTestsLimit) ? requestedTestsLimit : "all"}).`
5942
- )
5982
+ throw new Error(
5983
+ `No backtest tests selected (available=${testSuite.length}, skip=${testsSkip}, limit=${Number.isFinite(requestedTestsLimit) ? requestedTestsLimit : "all"}).`
5943
5984
  );
5944
- return null;
5945
5985
  }
5946
5986
  await prepareMarketContextForRun({
5947
5987
  mode: "backtest",
@@ -636,7 +636,7 @@ var buildRecommendation = (samples) => {
636
636
  );
637
637
  }
638
638
  notes.push(
639
- "Delay risk bps is disabled; signal-to-arrival latency is modeled by delayed lower-timeframe backtest fills."
639
+ "Delay risk bps is disabled; signal-to-arrival latency is modeled by next-bar-open backtest fills."
640
640
  );
641
641
  return {
642
642
  confidence: fullTelemetrySamples.length >= 50 ? "high" : fullTelemetrySamples.length >= 20 ? "medium" : "low",
@@ -60,13 +60,12 @@ var resolveProjectRoot = () => {
60
60
  var DEV_COMPOSE_TEMPLATE = `services:
61
61
  timescale:
62
62
  image: timescale/timescaledb:latest-pg16
63
- container_name: tradejs-timescale
64
63
  environment:
65
64
  POSTGRES_USER: \${PG_USER:-app}
66
65
  POSTGRES_PASSWORD: \${PG_PASSWORD:-app}
67
66
  POSTGRES_DB: \${PG_DB:-app}
68
67
  ports:
69
- - "5432:5432"
68
+ - "\${PG_PORT:-5432}:5432"
70
69
  command:
71
70
  - postgres
72
71
  - -c
@@ -86,14 +85,18 @@ var DEV_COMPOSE_TEMPLATE = `services:
86
85
 
87
86
  redis:
88
87
  image: redis/redis-stack:latest
89
- container_name: tradejs-redis
90
88
  environment:
91
89
  REDIS_ARGS: --save "" --appendonly no --stop-writes-on-bgsave-error no
92
90
  ports:
93
- - "6379:6379"
94
- - "5540:8001"
91
+ - "\${REDIS_PORT:-6379}:6379"
92
+ - "\${REDIS_INSIGHT_PORT:-5540}:8001"
95
93
  volumes:
96
94
  - tradejs_redisdata:/data
95
+ healthcheck:
96
+ test: ["CMD", "redis-cli", "ping"]
97
+ interval: 5s
98
+ timeout: 3s
99
+ retries: 10
97
100
  restart: unless-stopped
98
101
 
99
102
  volumes:
@@ -60,13 +60,12 @@ var resolveProjectRoot = () => {
60
60
  var DEV_COMPOSE_TEMPLATE = `services:
61
61
  timescale:
62
62
  image: timescale/timescaledb:latest-pg16
63
- container_name: tradejs-timescale
64
63
  environment:
65
64
  POSTGRES_USER: \${PG_USER:-app}
66
65
  POSTGRES_PASSWORD: \${PG_PASSWORD:-app}
67
66
  POSTGRES_DB: \${PG_DB:-app}
68
67
  ports:
69
- - "5432:5432"
68
+ - "\${PG_PORT:-5432}:5432"
70
69
  command:
71
70
  - postgres
72
71
  - -c
@@ -86,14 +85,18 @@ var DEV_COMPOSE_TEMPLATE = `services:
86
85
 
87
86
  redis:
88
87
  image: redis/redis-stack:latest
89
- container_name: tradejs-redis
90
88
  environment:
91
89
  REDIS_ARGS: --save "" --appendonly no --stop-writes-on-bgsave-error no
92
90
  ports:
93
- - "6379:6379"
94
- - "5540:8001"
91
+ - "\${REDIS_PORT:-6379}:6379"
92
+ - "\${REDIS_INSIGHT_PORT:-5540}:8001"
95
93
  volumes:
96
94
  - tradejs_redisdata:/data
95
+ healthcheck:
96
+ test: ["CMD", "redis-cli", "ping"]
97
+ interval: 5s
98
+ timeout: 3s
99
+ retries: 10
97
100
  restart: unless-stopped
98
101
 
99
102
  volumes:
@@ -83,8 +83,16 @@ var runDockerCompose = (composePath, args) => {
83
83
  var main = async () => {
84
84
  const composePath = requireDevComposeFile();
85
85
  console.log(import_chalk2.default.cyan("Starting dev infra (timescale, redis)..."));
86
- runDockerCompose(composePath, ["up", "-d", "timescale", "redis"]);
87
- console.log(import_chalk2.default.green("Dev infra is up."));
86
+ runDockerCompose(composePath, [
87
+ "up",
88
+ "-d",
89
+ "--wait",
90
+ "--wait-timeout",
91
+ "120",
92
+ "timescale",
93
+ "redis"
94
+ ]);
95
+ console.log(import_chalk2.default.green("Dev infra is ready."));
88
96
  };
89
97
  // Annotate the CommonJS export names for ESM import in node:
90
98
  0 && (module.exports = {
@@ -2217,6 +2217,11 @@ var prepareRunEnvironment = async ({
2217
2217
  assetClasses
2218
2218
  })
2219
2219
  );
2220
+ if (!loadedTickers.length) {
2221
+ throw new Error(
2222
+ `No tickers available for ${connectorName}. Check connector market-data access or select tickers explicitly.`
2223
+ );
2224
+ }
2220
2225
  if (showTickersList) {
2221
2226
  console.log(import_chalk3.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
2222
2227
  return null;
@@ -2268,7 +2273,7 @@ var prepareRunEnvironment = async ({
2268
2273
  log: (message) => console.log(import_chalk3.default.gray(message))
2269
2274
  });
2270
2275
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval);
2271
- if (backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2276
+ if (import_constants.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2272
2277
  await timeOperation2(
2273
2278
  `update ${connectorName} execution`,
2274
2279
  () => (0, import_cli3.update)(
@@ -3924,6 +3929,8 @@ var import_async2 = require("@tradejs/core/async");
3924
3929
  var import_timescale3 = require("@tradejs/infra/timescale");
3925
3930
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3926
3931
  var DAY_MS3 = 864e5;
3932
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3933
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3927
3934
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3928
3935
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3929
3936
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3969,6 +3976,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3969
3976
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3970
3977
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3971
3978
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3979
+ var getContextMaxAgeMs = () => asInt3(
3980
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3981
+ DEFAULT_CONTEXT_MAX_AGE_MS
3982
+ );
3983
+ var getFearGreedStaleRetryMs = () => asInt3(
3984
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3985
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3986
+ );
3972
3987
  var getExchangeSlugs = () => {
3973
3988
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3974
3989
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -4089,6 +4104,23 @@ var coverageRowsToKeySet = (rows) => new Set(
4089
4104
  )
4090
4105
  );
4091
4106
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
4107
+ var hasRecentBackfillCoverage = (rows, params) => {
4108
+ const key = coverageKey2(params);
4109
+ return rows.some((row) => {
4110
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
4111
+ if (coverageKey2({
4112
+ source: row.source,
4113
+ scope: row.scope,
4114
+ interval: row.interval,
4115
+ fromMs: row.fromMs,
4116
+ toMs: row.toMs
4117
+ }) !== key) {
4118
+ return false;
4119
+ }
4120
+ const checkedAtMs = Number(row.checkedAtMs);
4121
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
4122
+ });
4123
+ };
4092
4124
  var formatCoverageRange = (coverage) => {
4093
4125
  if (!coverage) return "coverage=none";
4094
4126
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4616,13 +4648,24 @@ var backfillCoinMarketCapContext = async (params) => {
4616
4648
  toMs
4617
4649
  }
4618
4650
  );
4619
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4651
+ const fearGreedBackfillParams = {
4620
4652
  source: SOURCE_FEAR_GREED,
4621
4653
  scope: COVERAGE_SCOPE_ALL,
4622
4654
  interval: "1d",
4623
4655
  fromMs,
4624
4656
  toMs
4625
- });
4657
+ };
4658
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4659
+ fearGreedBackfillKeys,
4660
+ fearGreedBackfillParams
4661
+ );
4662
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4663
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4664
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4665
+ ...fearGreedBackfillParams,
4666
+ atMs: params.endMs,
4667
+ maxAgeMs: getFearGreedStaleRetryMs()
4668
+ }));
4626
4669
  const indexesBackfillCovered = CMC_INDEXES.every(
4627
4670
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4628
4671
  source: SOURCE_INDEX,
@@ -4939,7 +4982,7 @@ var backfillCoinMarketCapContext = async (params) => {
4939
4982
  };
4940
4983
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4941
4984
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4942
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4985
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4943
4986
 
4944
4987
  // src/lib/marketContextPrepare.ts
4945
4988
  var shouldPrepareDerivativesContextForRun = (params) => {
@@ -2220,6 +2220,11 @@ var prepareRunEnvironment = async ({
2220
2220
  assetClasses
2221
2221
  })
2222
2222
  );
2223
+ if (!loadedTickers.length) {
2224
+ throw new Error(
2225
+ `No tickers available for ${connectorName}. Check connector market-data access or select tickers explicitly.`
2226
+ );
2227
+ }
2223
2228
  if (showTickersList) {
2224
2229
  console.log(import_chalk3.default.gray(JSON.stringify(loadedTickers.sort(), null, 2)));
2225
2230
  return null;
@@ -2271,7 +2276,7 @@ var prepareRunEnvironment = async ({
2271
2276
  log: (message) => console.log(import_chalk3.default.gray(message))
2272
2277
  });
2273
2278
  const backtestExecutionInterval = resolveBacktestExecutionPreloadInterval(interval);
2274
- if (backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2279
+ if (import_constants.BACKTEST_LOWER_TIMEFRAME_EXECUTION_ENABLED && backtestExecutionInterval && String(backtestExecutionInterval) !== String(interval)) {
2275
2280
  await timeOperation2(
2276
2281
  `update ${connectorName} execution`,
2277
2282
  () => (0, import_cli3.update)(
@@ -3927,6 +3932,8 @@ var import_async2 = require("@tradejs/core/async");
3927
3932
  var import_timescale3 = require("@tradejs/infra/timescale");
3928
3933
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3929
3934
  var DAY_MS3 = 864e5;
3935
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3936
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3930
3937
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3931
3938
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3932
3939
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3972,6 +3979,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3972
3979
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3973
3980
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3974
3981
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3982
+ var getContextMaxAgeMs = () => asInt3(
3983
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3984
+ DEFAULT_CONTEXT_MAX_AGE_MS
3985
+ );
3986
+ var getFearGreedStaleRetryMs = () => asInt3(
3987
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3988
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3989
+ );
3975
3990
  var getExchangeSlugs = () => {
3976
3991
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3977
3992
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -4092,6 +4107,23 @@ var coverageRowsToKeySet = (rows) => new Set(
4092
4107
  )
4093
4108
  );
4094
4109
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
4110
+ var hasRecentBackfillCoverage = (rows, params) => {
4111
+ const key = coverageKey2(params);
4112
+ return rows.some((row) => {
4113
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
4114
+ if (coverageKey2({
4115
+ source: row.source,
4116
+ scope: row.scope,
4117
+ interval: row.interval,
4118
+ fromMs: row.fromMs,
4119
+ toMs: row.toMs
4120
+ }) !== key) {
4121
+ return false;
4122
+ }
4123
+ const checkedAtMs = Number(row.checkedAtMs);
4124
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
4125
+ });
4126
+ };
4095
4127
  var formatCoverageRange = (coverage) => {
4096
4128
  if (!coverage) return "coverage=none";
4097
4129
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4619,13 +4651,24 @@ var backfillCoinMarketCapContext = async (params) => {
4619
4651
  toMs
4620
4652
  }
4621
4653
  );
4622
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4654
+ const fearGreedBackfillParams = {
4623
4655
  source: SOURCE_FEAR_GREED,
4624
4656
  scope: COVERAGE_SCOPE_ALL,
4625
4657
  interval: "1d",
4626
4658
  fromMs,
4627
4659
  toMs
4628
- });
4660
+ };
4661
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4662
+ fearGreedBackfillKeys,
4663
+ fearGreedBackfillParams
4664
+ );
4665
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4666
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4667
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4668
+ ...fearGreedBackfillParams,
4669
+ atMs: params.endMs,
4670
+ maxAgeMs: getFearGreedStaleRetryMs()
4671
+ }));
4629
4672
  const indexesBackfillCovered = CMC_INDEXES.every(
4630
4673
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4631
4674
  source: SOURCE_INDEX,
@@ -4942,7 +4985,7 @@ var backfillCoinMarketCapContext = async (params) => {
4942
4985
  };
4943
4986
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4944
4987
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4945
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4988
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4946
4989
 
4947
4990
  // src/lib/marketContextPrepare.ts
4948
4991
  var shouldPrepareDerivativesContextForRun = (params) => {
@@ -3937,6 +3937,8 @@ var import_async2 = require("@tradejs/core/async");
3937
3937
  var import_timescale3 = require("@tradejs/infra/timescale");
3938
3938
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3939
3939
  var DAY_MS3 = 864e5;
3940
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3941
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3940
3942
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3941
3943
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3942
3944
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3982,6 +3984,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3982
3984
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3983
3985
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3984
3986
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3987
+ var getContextMaxAgeMs = () => asInt3(
3988
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3989
+ DEFAULT_CONTEXT_MAX_AGE_MS
3990
+ );
3991
+ var getFearGreedStaleRetryMs = () => asInt3(
3992
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3993
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3994
+ );
3985
3995
  var getExchangeSlugs = () => {
3986
3996
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3987
3997
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -4102,6 +4112,23 @@ var coverageRowsToKeySet = (rows) => new Set(
4102
4112
  )
4103
4113
  );
4104
4114
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
4115
+ var hasRecentBackfillCoverage = (rows, params) => {
4116
+ const key = coverageKey2(params);
4117
+ return rows.some((row) => {
4118
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
4119
+ if (coverageKey2({
4120
+ source: row.source,
4121
+ scope: row.scope,
4122
+ interval: row.interval,
4123
+ fromMs: row.fromMs,
4124
+ toMs: row.toMs
4125
+ }) !== key) {
4126
+ return false;
4127
+ }
4128
+ const checkedAtMs = Number(row.checkedAtMs);
4129
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
4130
+ });
4131
+ };
4105
4132
  var formatCoverageRange = (coverage) => {
4106
4133
  if (!coverage) return "coverage=none";
4107
4134
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4629,13 +4656,24 @@ var backfillCoinMarketCapContext = async (params) => {
4629
4656
  toMs
4630
4657
  }
4631
4658
  );
4632
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4659
+ const fearGreedBackfillParams = {
4633
4660
  source: SOURCE_FEAR_GREED,
4634
4661
  scope: COVERAGE_SCOPE_ALL,
4635
4662
  interval: "1d",
4636
4663
  fromMs,
4637
4664
  toMs
4638
- });
4665
+ };
4666
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4667
+ fearGreedBackfillKeys,
4668
+ fearGreedBackfillParams
4669
+ );
4670
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4671
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4672
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4673
+ ...fearGreedBackfillParams,
4674
+ atMs: params.endMs,
4675
+ maxAgeMs: getFearGreedStaleRetryMs()
4676
+ }));
4639
4677
  const indexesBackfillCovered = CMC_INDEXES.every(
4640
4678
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4641
4679
  source: SOURCE_INDEX,
@@ -4952,7 +4990,7 @@ var backfillCoinMarketCapContext = async (params) => {
4952
4990
  };
4953
4991
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4954
4992
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4955
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4993
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4956
4994
 
4957
4995
  // src/lib/marketContextPrepare.ts
4958
4996
  var shouldPrepareDerivativesContextForRun = (params) => {
@@ -3433,6 +3433,8 @@ var import_async2 = require("@tradejs/core/async");
3433
3433
  var import_timescale3 = require("@tradejs/infra/timescale");
3434
3434
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3435
3435
  var DAY_MS3 = 864e5;
3436
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3437
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3436
3438
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3437
3439
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3438
3440
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3478,6 +3480,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3478
3480
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3479
3481
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3480
3482
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3483
+ var getContextMaxAgeMs = () => asInt3(
3484
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3485
+ DEFAULT_CONTEXT_MAX_AGE_MS
3486
+ );
3487
+ var getFearGreedStaleRetryMs = () => asInt3(
3488
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3489
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3490
+ );
3481
3491
  var getExchangeSlugs = () => {
3482
3492
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3483
3493
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -3598,6 +3608,23 @@ var coverageRowsToKeySet = (rows) => new Set(
3598
3608
  )
3599
3609
  );
3600
3610
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
3611
+ var hasRecentBackfillCoverage = (rows, params) => {
3612
+ const key = coverageKey2(params);
3613
+ return rows.some((row) => {
3614
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
3615
+ if (coverageKey2({
3616
+ source: row.source,
3617
+ scope: row.scope,
3618
+ interval: row.interval,
3619
+ fromMs: row.fromMs,
3620
+ toMs: row.toMs
3621
+ }) !== key) {
3622
+ return false;
3623
+ }
3624
+ const checkedAtMs = Number(row.checkedAtMs);
3625
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
3626
+ });
3627
+ };
3601
3628
  var formatCoverageRange = (coverage) => {
3602
3629
  if (!coverage) return "coverage=none";
3603
3630
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4125,13 +4152,24 @@ var backfillCoinMarketCapContext = async (params) => {
4125
4152
  toMs
4126
4153
  }
4127
4154
  );
4128
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4155
+ const fearGreedBackfillParams = {
4129
4156
  source: SOURCE_FEAR_GREED,
4130
4157
  scope: COVERAGE_SCOPE_ALL,
4131
4158
  interval: "1d",
4132
4159
  fromMs,
4133
4160
  toMs
4134
- });
4161
+ };
4162
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4163
+ fearGreedBackfillKeys,
4164
+ fearGreedBackfillParams
4165
+ );
4166
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4167
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4168
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4169
+ ...fearGreedBackfillParams,
4170
+ atMs: params.endMs,
4171
+ maxAgeMs: getFearGreedStaleRetryMs()
4172
+ }));
4135
4173
  const indexesBackfillCovered = CMC_INDEXES.every(
4136
4174
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4137
4175
  source: SOURCE_INDEX,
@@ -4448,7 +4486,7 @@ var backfillCoinMarketCapContext = async (params) => {
4448
4486
  };
4449
4487
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4450
4488
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4451
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4489
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4452
4490
 
4453
4491
  // src/lib/marketContextPrepare.ts
4454
4492
  var shouldPrepareDerivativesContextForRun = (params) => {
@@ -3431,6 +3431,8 @@ var import_async2 = require("@tradejs/core/async");
3431
3431
  var import_timescale3 = require("@tradejs/infra/timescale");
3432
3432
  var import_userSettings2 = require("@tradejs/infra/userSettings");
3433
3433
  var DAY_MS3 = 864e5;
3434
+ var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
3435
+ var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
3434
3436
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
3435
3437
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
3436
3438
  var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
@@ -3476,6 +3478,14 @@ var isExchangeLiquidityBackfillEnabled = () => parseEnabledFlag2(
3476
3478
  var isFearGreedBackfillEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED, true);
3477
3479
  var isVerboseRequestLoggingEnabled = () => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_VERBOSE_REQUESTS, false);
3478
3480
  var getFearGreedPageSize = () => asInt3(process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_PAGE_SIZE, 500);
3481
+ var getContextMaxAgeMs = () => asInt3(
3482
+ process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS,
3483
+ DEFAULT_CONTEXT_MAX_AGE_MS
3484
+ );
3485
+ var getFearGreedStaleRetryMs = () => asInt3(
3486
+ process.env.COINMARKETCAP_CONTEXT_FEAR_GREED_STALE_RETRY_MS,
3487
+ DEFAULT_FEAR_GREED_STALE_RETRY_MS
3488
+ );
3479
3489
  var getExchangeSlugs = () => {
3480
3490
  const raw = process.env.COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS;
3481
3491
  const values = String(raw ?? "").split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
@@ -3596,6 +3606,23 @@ var coverageRowsToKeySet = (rows) => new Set(
3596
3606
  )
3597
3607
  );
3598
3608
  var hasBackfillCoverage = (coverageKeys, params) => coverageKeys.has(coverageKey2(params));
3609
+ var hasRecentBackfillCoverage = (rows, params) => {
3610
+ const key = coverageKey2(params);
3611
+ return rows.some((row) => {
3612
+ if (!isMarketContextBackfillCoverageDataBearing(row)) return false;
3613
+ if (coverageKey2({
3614
+ source: row.source,
3615
+ scope: row.scope,
3616
+ interval: row.interval,
3617
+ fromMs: row.fromMs,
3618
+ toMs: row.toMs
3619
+ }) !== key) {
3620
+ return false;
3621
+ }
3622
+ const checkedAtMs = Number(row.checkedAtMs);
3623
+ return Number.isFinite(checkedAtMs) && Math.max(0, params.atMs - checkedAtMs) <= params.maxAgeMs;
3624
+ });
3625
+ };
3599
3626
  var formatCoverageRange = (coverage) => {
3600
3627
  if (!coverage) return "coverage=none";
3601
3628
  return `coverage=${new Date(coverage.firstMs).toISOString()}..${new Date(
@@ -4123,13 +4150,24 @@ var backfillCoinMarketCapContext = async (params) => {
4123
4150
  toMs
4124
4151
  }
4125
4152
  );
4126
- const fearGreedBackfillCovered = hasBackfillCoverage(fearGreedBackfillKeys, {
4153
+ const fearGreedBackfillParams = {
4127
4154
  source: SOURCE_FEAR_GREED,
4128
4155
  scope: COVERAGE_SCOPE_ALL,
4129
4156
  interval: "1d",
4130
4157
  fromMs,
4131
4158
  toMs
4132
- });
4159
+ };
4160
+ const fearGreedBackfillWasCovered = hasBackfillCoverage(
4161
+ fearGreedBackfillKeys,
4162
+ fearGreedBackfillParams
4163
+ );
4164
+ const fearGreedLatestAgeMs = fearGreedCoverage == null ? Number.POSITIVE_INFINITY : Math.max(0, params.endMs - fearGreedCoverage.lastMs);
4165
+ const refreshStaleFearGreed = params.refreshStaleFearGreed === true && fearGreedLatestAgeMs > getContextMaxAgeMs();
4166
+ const fearGreedBackfillCovered = fearGreedBackfillWasCovered && (!refreshStaleFearGreed || hasRecentBackfillCoverage(fearGreedBackfillCoverage, {
4167
+ ...fearGreedBackfillParams,
4168
+ atMs: params.endMs,
4169
+ maxAgeMs: getFearGreedStaleRetryMs()
4170
+ }));
4133
4171
  const indexesBackfillCovered = CMC_INDEXES.every(
4134
4172
  (index) => hasBackfillCoverage(indexBackfillKeys, {
4135
4173
  source: SOURCE_INDEX,
@@ -4446,7 +4484,7 @@ var backfillCoinMarketCapContext = async (params) => {
4446
4484
  };
4447
4485
  var backfillCoinMarketCapContextForBacktest = backfillCoinMarketCapContext;
4448
4486
  var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4449
- var backfillCoinMarketCapContextForSignals = backfillCoinMarketCapContext;
4487
+ var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4450
4488
 
4451
4489
  // src/lib/marketContextPrepare.ts
4452
4490
  var shouldPrepareDerivativesContextForRun = (params) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Official CLI for the TradeJS open-source framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -29,14 +29,14 @@
29
29
  "tradejs": "dist/cli.js"
30
30
  },
31
31
  "dependencies": {
32
- "@tradejs/base": "^1.0.10",
33
- "@tradejs/connectors": "^1.0.10",
34
- "@tradejs/core": "^1.0.10",
35
- "@tradejs/indicators": "^1.0.10",
36
- "@tradejs/infra": "^1.0.10",
37
- "@tradejs/node": "^1.0.10",
38
- "@tradejs/strategies": "^1.0.10",
39
- "@tradejs/types": "^1.0.10",
32
+ "@tradejs/base": "^1.0.11",
33
+ "@tradejs/connectors": "^1.0.11",
34
+ "@tradejs/core": "^1.0.11",
35
+ "@tradejs/indicators": "^1.0.11",
36
+ "@tradejs/infra": "^1.0.11",
37
+ "@tradejs/node": "^1.0.11",
38
+ "@tradejs/strategies": "^1.0.11",
39
+ "@tradejs/types": "^1.0.11",
40
40
  "args": "^5.0.3",
41
41
  "bcryptjs": "^2.4.3",
42
42
  "chalk": "4.1.2",