@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.
@@ -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
+ });