@tradejs/cli 1.0.6 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10049 -4860
- package/dist/lib/aiTrainMetrics.js +23 -0
- package/dist/lib/candlesProviderMigration.js +266 -0
- package/dist/lib/cliArgs.js +40 -0
- package/dist/lib/derivativesContextBackfill.js +487 -0
- package/dist/lib/researchAgent.js +195 -0
- package/dist/lib/runtimeParity.js +234 -0
- package/dist/lib/runtimeSignalsLoader.js +153 -0
- package/dist/lib/runtimeSignalsStorage.js +162 -0
- package/dist/lib/telegramReports.js +91 -0
- package/dist/lib/timeWindow.js +101 -0
- package/dist/scripts/agentRun.js +909 -0
- package/dist/scripts/aiTrain.js +113 -31
- package/dist/scripts/backtest.js +794 -93
- package/dist/scripts/candlesMigrateProvider.js +5302 -0
- package/dist/scripts/continuity.js +1 -1
- package/dist/scripts/migration.js +1 -1
- package/dist/scripts/researchAuto.js +847 -0
- package/dist/scripts/results.js +46 -22
- package/dist/scripts/runtimeParity.js +1688 -0
- package/dist/scripts/signals.js +774 -63
- package/dist/scripts/signalsSummary.js +712 -0
- package/dist/scripts/user-add.js +0 -9
- package/package.json +16 -12
|
@@ -30,6 +30,20 @@ var divideOrNull = (num, denom) => {
|
|
|
30
30
|
}
|
|
31
31
|
return num / denom;
|
|
32
32
|
};
|
|
33
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
34
|
+
var DAYS_PER_WEEK = 7;
|
|
35
|
+
var DAYS_PER_MONTH = 30.4375;
|
|
36
|
+
var getEvaluationPeriodDays = (evaluations) => {
|
|
37
|
+
const timestamps = evaluations.map(
|
|
38
|
+
(evaluation) => typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp) ? evaluation.timestamp : null
|
|
39
|
+
).filter((timestamp) => timestamp != null);
|
|
40
|
+
if (!timestamps.length) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const minTimestamp = Math.min(...timestamps);
|
|
44
|
+
const maxTimestamp = Math.max(...timestamps);
|
|
45
|
+
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
46
|
+
};
|
|
33
47
|
var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
34
48
|
var summarizeAiTrainEvaluations = (evaluations) => {
|
|
35
49
|
let approved = 0;
|
|
@@ -96,6 +110,11 @@ var summarizeAiTrainEvaluations = (evaluations) => {
|
|
|
96
110
|
}
|
|
97
111
|
const avgProfitAll = divideOrNull(totalProfitAll, evaluations.length);
|
|
98
112
|
const avgProfitApproved = divideOrNull(totalProfitApproved, approved);
|
|
113
|
+
const periodDays = getEvaluationPeriodDays(evaluations);
|
|
114
|
+
const avgProfitApprovedPerDay = periodDays == null ? null : divideOrNull(totalProfitApproved, periodDays);
|
|
115
|
+
const avgApprovedTradesPerDay = periodDays == null ? null : divideOrNull(approved, periodDays);
|
|
116
|
+
const avgProfitApprovedPerMonth = avgProfitApprovedPerDay == null ? null : avgProfitApprovedPerDay * DAYS_PER_MONTH;
|
|
117
|
+
const avgApprovedTradesPerWeek = avgApprovedTradesPerDay == null ? null : avgApprovedTradesPerDay * DAYS_PER_WEEK;
|
|
99
118
|
const expectancyDelta = avgProfitAll == null || avgProfitApproved == null ? null : avgProfitApproved - avgProfitAll;
|
|
100
119
|
return {
|
|
101
120
|
approved,
|
|
@@ -113,6 +132,10 @@ var summarizeAiTrainEvaluations = (evaluations) => {
|
|
|
113
132
|
recallWinners: divideOrNull(truePositive, profitable),
|
|
114
133
|
avgProfitAll,
|
|
115
134
|
avgProfitApproved,
|
|
135
|
+
avgProfitApprovedPerDay,
|
|
136
|
+
avgProfitApprovedPerMonth,
|
|
137
|
+
avgApprovedTradesPerDay,
|
|
138
|
+
avgApprovedTradesPerWeek,
|
|
116
139
|
expectancyDelta,
|
|
117
140
|
qualityBuckets: [...bucketMap.values()].sort(
|
|
118
141
|
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/candlesProviderMigration.ts
|
|
21
|
+
var candlesProviderMigration_exports = {};
|
|
22
|
+
__export(candlesProviderMigration_exports, {
|
|
23
|
+
runCandlesProviderMigration: () => runCandlesProviderMigration
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(candlesProviderMigration_exports);
|
|
26
|
+
var LOCK_KEY = "tradejs:candles-provider-migration";
|
|
27
|
+
var assertIdentifier = (value, label) => {
|
|
28
|
+
const normalized = String(value || "").trim();
|
|
29
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(normalized)) {
|
|
30
|
+
throw new Error(`Invalid ${label}: ${value}`);
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
};
|
|
34
|
+
var quoteIdentifier = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
35
|
+
var quoteQualifiedName = (schema, table) => `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
36
|
+
var toQualifiedRegclass = (schema, table) => `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
37
|
+
var getProviderColumnExists = async (db, schema, table) => {
|
|
38
|
+
const res = await db.query(
|
|
39
|
+
`
|
|
40
|
+
SELECT EXISTS (
|
|
41
|
+
SELECT 1
|
|
42
|
+
FROM information_schema.columns
|
|
43
|
+
WHERE table_schema = $1
|
|
44
|
+
AND table_name = $2
|
|
45
|
+
AND column_name = 'provider'
|
|
46
|
+
) AS exists
|
|
47
|
+
`,
|
|
48
|
+
[schema, table]
|
|
49
|
+
);
|
|
50
|
+
return Boolean(res.rows[0]?.exists);
|
|
51
|
+
};
|
|
52
|
+
var getPrimaryKeyColumns = async (db, schema, table) => {
|
|
53
|
+
const res = await db.query(
|
|
54
|
+
`
|
|
55
|
+
SELECT a.attname AS column_name
|
|
56
|
+
FROM pg_index i
|
|
57
|
+
JOIN pg_class t
|
|
58
|
+
ON t.oid = i.indrelid
|
|
59
|
+
JOIN pg_namespace n
|
|
60
|
+
ON n.oid = t.relnamespace
|
|
61
|
+
JOIN unnest(i.indkey) WITH ORDINALITY AS keys(attnum, ord)
|
|
62
|
+
ON true
|
|
63
|
+
JOIN pg_attribute a
|
|
64
|
+
ON a.attrelid = t.oid
|
|
65
|
+
AND a.attnum = keys.attnum
|
|
66
|
+
WHERE i.indisprimary
|
|
67
|
+
AND n.nspname = $1
|
|
68
|
+
AND t.relname = $2
|
|
69
|
+
ORDER BY keys.ord ASC
|
|
70
|
+
`,
|
|
71
|
+
[schema, table]
|
|
72
|
+
);
|
|
73
|
+
return res.rows.map((row) => String(row.column_name));
|
|
74
|
+
};
|
|
75
|
+
var getCompressionJobs = async (db, schema, table) => {
|
|
76
|
+
const res = await db.query(
|
|
77
|
+
`
|
|
78
|
+
SELECT job_id, scheduled
|
|
79
|
+
FROM timescaledb_information.jobs
|
|
80
|
+
WHERE proc_name = 'policy_compression'
|
|
81
|
+
AND hypertable_schema = $1
|
|
82
|
+
AND hypertable_name = $2
|
|
83
|
+
ORDER BY job_id ASC
|
|
84
|
+
`,
|
|
85
|
+
[schema, table]
|
|
86
|
+
);
|
|
87
|
+
return res.rows.map((row) => ({
|
|
88
|
+
jobId: Number(row.job_id),
|
|
89
|
+
scheduled: Boolean(row.scheduled)
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
92
|
+
var getCompressedChunks = async (db, schema, table) => {
|
|
93
|
+
const res = await db.query(
|
|
94
|
+
`
|
|
95
|
+
SELECT chunk::text AS chunk_name
|
|
96
|
+
FROM timescaledb_information.chunk_compression_settings
|
|
97
|
+
WHERE hypertable = $1::regclass
|
|
98
|
+
ORDER BY chunk::text ASC
|
|
99
|
+
`,
|
|
100
|
+
[toQualifiedRegclass(schema, table)]
|
|
101
|
+
);
|
|
102
|
+
return res.rows.map((row) => String(row.chunk_name));
|
|
103
|
+
};
|
|
104
|
+
var runCandlesProviderMigration = async (db, options = {}) => {
|
|
105
|
+
const schema = assertIdentifier(options.schema || "public", "schema");
|
|
106
|
+
const table = assertIdentifier(options.table || "candles", "table");
|
|
107
|
+
const dryRun = Boolean(options.dryRun);
|
|
108
|
+
const recompress = options.recompress !== false;
|
|
109
|
+
const keepPolicyPaused = Boolean(options.keepPolicyPaused);
|
|
110
|
+
const quotedTable = quoteQualifiedName(schema, table);
|
|
111
|
+
const onProgress = typeof options.onProgress === "function" ? options.onProgress : void 0;
|
|
112
|
+
const progress = (message) => {
|
|
113
|
+
onProgress?.(message);
|
|
114
|
+
};
|
|
115
|
+
const summary = {
|
|
116
|
+
schema,
|
|
117
|
+
table,
|
|
118
|
+
dryRun,
|
|
119
|
+
providerColumnExistsBefore: false,
|
|
120
|
+
providerBackfilledRows: 0,
|
|
121
|
+
primaryKeyColumnsBefore: [],
|
|
122
|
+
primaryKeyChanged: false,
|
|
123
|
+
compressionJobIds: [],
|
|
124
|
+
pausedJobIds: [],
|
|
125
|
+
resumedJobIds: [],
|
|
126
|
+
compressedChunksBefore: [],
|
|
127
|
+
decompressedChunks: [],
|
|
128
|
+
recompressedChunks: [],
|
|
129
|
+
indexEnsured: false,
|
|
130
|
+
compressionSettingsUpdated: false
|
|
131
|
+
};
|
|
132
|
+
const jobsToResume = /* @__PURE__ */ new Set();
|
|
133
|
+
progress(`Acquire migration lock for ${schema}.${table}`);
|
|
134
|
+
await db.query("SELECT pg_advisory_lock(hashtext($1))", [LOCK_KEY]);
|
|
135
|
+
try {
|
|
136
|
+
progress("Inspect current candles schema");
|
|
137
|
+
summary.providerColumnExistsBefore = await getProviderColumnExists(
|
|
138
|
+
db,
|
|
139
|
+
schema,
|
|
140
|
+
table
|
|
141
|
+
);
|
|
142
|
+
summary.primaryKeyColumnsBefore = await getPrimaryKeyColumns(
|
|
143
|
+
db,
|
|
144
|
+
schema,
|
|
145
|
+
table
|
|
146
|
+
);
|
|
147
|
+
const compressionJobs = await getCompressionJobs(db, schema, table);
|
|
148
|
+
summary.compressionJobIds = compressionJobs.map((job) => job.jobId);
|
|
149
|
+
progress(
|
|
150
|
+
`Found ${summary.compressionJobIds.length} compression job(s) for ${schema}.${table}`
|
|
151
|
+
);
|
|
152
|
+
summary.compressedChunksBefore = await getCompressedChunks(
|
|
153
|
+
db,
|
|
154
|
+
schema,
|
|
155
|
+
table
|
|
156
|
+
);
|
|
157
|
+
progress(
|
|
158
|
+
`Found ${summary.compressedChunksBefore.length} compressed chunk(s) to process`
|
|
159
|
+
);
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
progress("Dry run finished");
|
|
162
|
+
return summary;
|
|
163
|
+
}
|
|
164
|
+
for (const [index, job] of compressionJobs.entries()) {
|
|
165
|
+
if (!job.scheduled) continue;
|
|
166
|
+
progress(
|
|
167
|
+
`Pause compression job ${job.jobId} (${index + 1}/${compressionJobs.length})`
|
|
168
|
+
);
|
|
169
|
+
await db.query("SELECT alter_job($1, scheduled => false)", [job.jobId]);
|
|
170
|
+
summary.pausedJobIds.push(job.jobId);
|
|
171
|
+
if (!keepPolicyPaused) {
|
|
172
|
+
jobsToResume.add(job.jobId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const [index, chunkName] of summary.compressedChunksBefore.entries()) {
|
|
176
|
+
progress(
|
|
177
|
+
`Decompress chunk ${index + 1}/${summary.compressedChunksBefore.length}: ${chunkName}`
|
|
178
|
+
);
|
|
179
|
+
await db.query("SELECT decompress_chunk($1::regclass, true)", [
|
|
180
|
+
chunkName
|
|
181
|
+
]);
|
|
182
|
+
summary.decompressedChunks.push(chunkName);
|
|
183
|
+
}
|
|
184
|
+
progress("Ensure provider column exists");
|
|
185
|
+
await db.query(
|
|
186
|
+
`ALTER TABLE ${quotedTable} ADD COLUMN IF NOT EXISTS provider text`
|
|
187
|
+
);
|
|
188
|
+
progress("Backfill provider='bybit' for existing rows");
|
|
189
|
+
const updateResult = await db.query(
|
|
190
|
+
`UPDATE ${quotedTable} SET provider = 'bybit' WHERE provider IS NULL`
|
|
191
|
+
);
|
|
192
|
+
summary.providerBackfilledRows = Number(updateResult.rowCount ?? 0);
|
|
193
|
+
progress("Set provider column default and NOT NULL");
|
|
194
|
+
await db.query(
|
|
195
|
+
`ALTER TABLE ${quotedTable}
|
|
196
|
+
ALTER COLUMN provider SET DEFAULT 'bybit',
|
|
197
|
+
ALTER COLUMN provider SET NOT NULL`
|
|
198
|
+
);
|
|
199
|
+
const expectedPrimaryKey = ["provider", "symbol", "interval", "ts"];
|
|
200
|
+
if (summary.primaryKeyColumnsBefore.join(",") !== expectedPrimaryKey.join(",")) {
|
|
201
|
+
progress("Replace primary key with provider-aware key");
|
|
202
|
+
await db.query(
|
|
203
|
+
`ALTER TABLE ${quotedTable} DROP CONSTRAINT IF EXISTS candles_pkey`
|
|
204
|
+
);
|
|
205
|
+
await db.query(
|
|
206
|
+
`ALTER TABLE ${quotedTable}
|
|
207
|
+
ADD CONSTRAINT candles_pkey
|
|
208
|
+
PRIMARY KEY (provider, symbol, interval, ts)`
|
|
209
|
+
);
|
|
210
|
+
summary.primaryKeyChanged = true;
|
|
211
|
+
}
|
|
212
|
+
progress("Recreate provider-aware candle index");
|
|
213
|
+
await db.query("DROP INDEX IF EXISTS candles_symbol_interval_ts_idx");
|
|
214
|
+
await db.query(
|
|
215
|
+
`CREATE INDEX IF NOT EXISTS candles_provider_symbol_interval_ts_idx
|
|
216
|
+
ON ${quotedTable} (provider, symbol, interval, ts DESC)`
|
|
217
|
+
);
|
|
218
|
+
summary.indexEnsured = true;
|
|
219
|
+
progress("Update Timescale compression settings");
|
|
220
|
+
await db.query(
|
|
221
|
+
`ALTER TABLE ${quotedTable} SET (
|
|
222
|
+
timescaledb.compress,
|
|
223
|
+
timescaledb.compress_segmentby = 'provider, symbol, interval'
|
|
224
|
+
)`
|
|
225
|
+
);
|
|
226
|
+
summary.compressionSettingsUpdated = true;
|
|
227
|
+
if (recompress) {
|
|
228
|
+
for (const [index, chunkName] of summary.decompressedChunks.entries()) {
|
|
229
|
+
progress(
|
|
230
|
+
`Recompress chunk ${index + 1}/${summary.decompressedChunks.length}: ${chunkName}`
|
|
231
|
+
);
|
|
232
|
+
await db.query("SELECT compress_chunk($1::regclass, true)", [
|
|
233
|
+
chunkName
|
|
234
|
+
]);
|
|
235
|
+
summary.recompressedChunks.push(chunkName);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!keepPolicyPaused) {
|
|
239
|
+
const jobIds = [...jobsToResume];
|
|
240
|
+
for (const [index, jobId] of jobIds.entries()) {
|
|
241
|
+
progress(
|
|
242
|
+
`Resume compression job ${jobId} (${index + 1}/${jobIds.length})`
|
|
243
|
+
);
|
|
244
|
+
await db.query("SELECT alter_job($1, scheduled => true)", [jobId]);
|
|
245
|
+
summary.resumedJobIds.push(jobId);
|
|
246
|
+
}
|
|
247
|
+
jobsToResume.clear();
|
|
248
|
+
}
|
|
249
|
+
progress("Migration finished");
|
|
250
|
+
return summary;
|
|
251
|
+
} finally {
|
|
252
|
+
for (const jobId of jobsToResume) {
|
|
253
|
+
try {
|
|
254
|
+
progress(`Best-effort resume of compression job ${jobId}`);
|
|
255
|
+
await db.query("SELECT alter_job($1, scheduled => true)", [jobId]);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
progress(`Release migration lock for ${schema}.${table}`);
|
|
260
|
+
await db.query("SELECT pg_advisory_unlock(hashtext($1))", [LOCK_KEY]);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
264
|
+
0 && (module.exports = {
|
|
265
|
+
runCandlesProviderMigration
|
|
266
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/cliArgs.ts
|
|
21
|
+
var cliArgs_exports = {};
|
|
22
|
+
__export(cliArgs_exports, {
|
|
23
|
+
normalizeCliArgv: () => normalizeCliArgv
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(cliArgs_exports);
|
|
26
|
+
var normalizeCliArgv = (argv, legacyFlagMap) => argv.map((arg) => {
|
|
27
|
+
const [flag, ...rest] = String(arg).split("=");
|
|
28
|
+
const normalizedFlag = legacyFlagMap[flag];
|
|
29
|
+
if (!normalizedFlag) {
|
|
30
|
+
return arg;
|
|
31
|
+
}
|
|
32
|
+
if (!rest.length) {
|
|
33
|
+
return normalizedFlag;
|
|
34
|
+
}
|
|
35
|
+
return `${normalizedFlag}=${rest.join("=")}`;
|
|
36
|
+
});
|
|
37
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
38
|
+
0 && (module.exports = {
|
|
39
|
+
normalizeCliArgv
|
|
40
|
+
});
|