@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 +13 -4
- package/dist/cli.js +69 -18
- package/dist/lib/coinMarketCapContextBackfill.js +41 -3
- package/dist/lib/executionCalibration.js +1 -1
- package/dist/lib/marketContextPrepare.js +41 -3
- package/dist/lib/runEnvironment.js +6 -1
- package/dist/scripts/backtest.js +49 -9
- package/dist/scripts/executionCalibration.js +1 -1
- package/dist/scripts/infraCommon.js +8 -5
- package/dist/scripts/infraInit.js +8 -5
- package/dist/scripts/infraUp.js +10 -2
- package/dist/scripts/replay.js +47 -4
- package/dist/scripts/replayRunner.js +47 -4
- package/dist/scripts/runtimeParity.js +41 -3
- package/dist/scripts/signals.js +41 -3
- package/dist/scripts/signalsDaemon.js +41 -3
- package/package.json +9 -9
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
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
-
|
|
12087
|
-
|
|
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
|
|
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, [
|
|
20446
|
-
|
|
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
|
|
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
|
|
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
|
|
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)(
|
package/dist/scripts/backtest.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
5940
|
-
|
|
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
|
|
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:
|
package/dist/scripts/infraUp.js
CHANGED
|
@@ -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, [
|
|
87
|
-
|
|
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 = {
|
package/dist/scripts/replay.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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) => {
|
package/dist/scripts/signals.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
33
|
-
"@tradejs/connectors": "^1.0.
|
|
34
|
-
"@tradejs/core": "^1.0.
|
|
35
|
-
"@tradejs/indicators": "^1.0.
|
|
36
|
-
"@tradejs/infra": "^1.0.
|
|
37
|
-
"@tradejs/node": "^1.0.
|
|
38
|
-
"@tradejs/strategies": "^1.0.
|
|
39
|
-
"@tradejs/types": "^1.0.
|
|
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",
|