@tradejs/node 3.1.11 → 3.1.12-beta.218

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,552 @@
1
+ import {
2
+ getStrategyCreator,
3
+ getStrategyEntry,
4
+ getStrategyPluginSource
5
+ } from "./chunk-IKRSNRP6.mjs";
6
+ import {
7
+ loadTradejsConfig
8
+ } from "./chunk-LZDXRXIU.mjs";
9
+
10
+ // src/runtimeStrategies.ts
11
+ import { createHash } from "crypto";
12
+ import { readFile } from "fs/promises";
13
+ import path from "path";
14
+ import { getRuntimeControls } from "@tradejs/infra/runtimeControls";
15
+ import { resolveTradingAccount } from "@tradejs/infra/tradingAccounts";
16
+ var RUNTIME_PACKAGE_MANIFEST_SCHEMA = "tradejs-runtime-package-manifest/v1";
17
+ var INTERVALS = /* @__PURE__ */ new Set([
18
+ "1",
19
+ "3",
20
+ "5",
21
+ "15",
22
+ "30",
23
+ "60",
24
+ "120",
25
+ "240",
26
+ "360",
27
+ "720",
28
+ "D",
29
+ "W",
30
+ "M"
31
+ ]);
32
+ var RUNTIME_KEYS = /* @__PURE__ */ new Set(["deployments"]);
33
+ var DEPLOYMENT_KEYS = /* @__PURE__ */ new Set([
34
+ "label",
35
+ "connectorName",
36
+ "provider",
37
+ "accountId",
38
+ "enabled",
39
+ "strategies",
40
+ "assetClasses",
41
+ "tickers"
42
+ ]);
43
+ var STRATEGY_KEYS = /* @__PURE__ */ new Set(["generation", "enabled", "selection", "config"]);
44
+ var SELECTION_KEYS = /* @__PURE__ */ new Set(["tickers"]);
45
+ var FORBIDDEN_CONFIG_KEYS = /* @__PURE__ */ new Set([
46
+ "ACCOUNT_ID",
47
+ "DEPLOYMENT_ID",
48
+ "CONNECTOR_NAME",
49
+ "ENABLE"
50
+ ]);
51
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
52
+ var normalizeForCanonicalJson = (value) => {
53
+ if (Array.isArray(value)) return value.map(normalizeForCanonicalJson);
54
+ if (isRecord(value)) {
55
+ return Object.fromEntries(
56
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nestedValue]) => [
57
+ key,
58
+ normalizeForCanonicalJson(nestedValue)
59
+ ])
60
+ );
61
+ }
62
+ return value;
63
+ };
64
+ var revision = (prefix, value) => `${prefix}:${createHash("sha256").update(JSON.stringify(normalizeForCanonicalJson(value))).digest("hex").slice(0, 16)}`;
65
+ var computeStrategyRevision = ({
66
+ strategyName,
67
+ strategyPackage,
68
+ strategyPackageVersion,
69
+ strategyDependencyVersions,
70
+ runtimePackageVersion,
71
+ strategyConfig
72
+ }) => revision("sr1", {
73
+ schema: "tradejs-strategy-revision/v1",
74
+ strategyName,
75
+ strategyPackage,
76
+ strategyPackageVersion,
77
+ strategyDependencyVersions,
78
+ runtimePackageVersion,
79
+ strategyConfig
80
+ });
81
+ var computeDeploymentCompositionId = (value) => revision("dc1", {
82
+ schema: "tradejs-deployment-composition/v1",
83
+ ...value,
84
+ assetClasses: value.assetClasses ? [...value.assetClasses].sort((left, right) => left.localeCompare(right)) : void 0,
85
+ strategies: [...value.strategies].sort(
86
+ (left, right) => left.strategyName.localeCompare(right.strategyName)
87
+ ).map((strategy) => ({
88
+ ...strategy,
89
+ selection: strategy.selection ? {
90
+ tickers: [...strategy.selection.tickers].sort(
91
+ (left, right) => left.localeCompare(right)
92
+ )
93
+ } : void 0
94
+ }))
95
+ });
96
+ var readRuntimePackageManifest = async (projectRoot) => {
97
+ const manifestPath = process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST?.trim() || path.join(projectRoot, "runtime-package-manifest.json");
98
+ let value;
99
+ try {
100
+ value = JSON.parse(await readFile(manifestPath, "utf8"));
101
+ } catch (error) {
102
+ throw new Error(
103
+ `Unable to read runtime package manifest ${manifestPath}: ${String(error)}`
104
+ );
105
+ }
106
+ if (!isRecord(value) || Object.keys(value).some(
107
+ (key) => !["schema", "projectSha", "packages"].includes(key)
108
+ ) || value.schema !== RUNTIME_PACKAGE_MANIFEST_SCHEMA || typeof value.projectSha !== "string" || !/^[a-f0-9]{40}$/.test(value.projectSha) || !isRecord(value.packages) || Object.keys(value.packages).length === 0 || Object.entries(value.packages).some(
109
+ ([packageName, version]) => !packageName.trim() || typeof version !== "string" || !version.trim()
110
+ )) {
111
+ throw new Error(`Invalid runtime package manifest: ${manifestPath}`);
112
+ }
113
+ return value;
114
+ };
115
+ var readInstalledPackageMetadata = async ({
116
+ projectRoot,
117
+ packageName,
118
+ projectPackage
119
+ }) => {
120
+ const packageJsonPath = projectPackage ? path.join(projectRoot, "package.json") : path.join(
121
+ projectRoot,
122
+ "node_modules",
123
+ ...packageName.split("/"),
124
+ "package.json"
125
+ );
126
+ try {
127
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
128
+ if (packageJson.name !== void 0 && packageJson.name !== packageName || typeof packageJson.version !== "string" || !packageJson.version.trim()) {
129
+ throw new Error("name or version is invalid");
130
+ }
131
+ const dependencyNames = [
132
+ ...isRecord(packageJson.dependencies) ? Object.keys(packageJson.dependencies) : [],
133
+ ...isRecord(packageJson.peerDependencies) ? Object.keys(packageJson.peerDependencies) : []
134
+ ];
135
+ return {
136
+ version: packageJson.version,
137
+ runtimeDependencies: [...new Set(dependencyNames)].filter((name) => name.startsWith("@tradejs/")).sort((left, right) => left.localeCompare(right))
138
+ };
139
+ } catch (error) {
140
+ throw new Error(
141
+ `Installed package manifest not found for ${packageName}: ${String(error)}`
142
+ );
143
+ }
144
+ };
145
+ var resolveVerifiedPackageVersion = async ({
146
+ projectRoot,
147
+ packageName,
148
+ manifest,
149
+ projectPackage = false
150
+ }) => {
151
+ const declaredVersion = manifest.packages[packageName];
152
+ if (!declaredVersion) {
153
+ throw new Error(`Runtime package manifest is missing ${packageName}`);
154
+ }
155
+ const installed = await readInstalledPackageMetadata({
156
+ projectRoot,
157
+ packageName,
158
+ projectPackage
159
+ });
160
+ if (declaredVersion !== installed.version) {
161
+ throw new Error(
162
+ `Runtime package manifest mismatch for ${packageName}: declared=${declaredVersion} installed=${installed.version}`
163
+ );
164
+ }
165
+ return installed.version;
166
+ };
167
+ var resolveVerifiedStrategyDependencyVersions = async ({
168
+ projectRoot,
169
+ strategyPackage,
170
+ manifest
171
+ }) => {
172
+ const metadata = await readInstalledPackageMetadata({
173
+ projectRoot,
174
+ packageName: strategyPackage.name,
175
+ projectPackage: strategyPackage.projectPackage
176
+ });
177
+ return Object.fromEntries(
178
+ await Promise.all(
179
+ metadata.runtimeDependencies.map(async (packageName) => [
180
+ packageName,
181
+ await resolveVerifiedPackageVersion({
182
+ projectRoot,
183
+ packageName,
184
+ manifest
185
+ })
186
+ ])
187
+ )
188
+ );
189
+ };
190
+ var resolveStrategyPackage = async ({
191
+ pluginSource,
192
+ projectRoot
193
+ }) => {
194
+ if (!pluginSource) return null;
195
+ if (!pluginSource.startsWith(".") && !path.isAbsolute(pluginSource)) {
196
+ return { name: pluginSource, projectPackage: false };
197
+ }
198
+ try {
199
+ const packageJson = JSON.parse(
200
+ await readFile(path.join(projectRoot, "package.json"), "utf8")
201
+ );
202
+ return typeof packageJson.name === "string" && packageJson.name.trim() ? { name: packageJson.name, projectPackage: true } : null;
203
+ } catch {
204
+ return null;
205
+ }
206
+ };
207
+ var verifyStringSet = (value) => value === void 0 || Array.isArray(value) && value.length > 0 && value.every(
208
+ (item) => typeof item === "string" && item.length > 0 && item === item.trim()
209
+ ) && new Set(value).size === value.length;
210
+ var verifyStrategySelection = (value) => value === void 0 || isRecord(value) && Object.keys(value).length === 1 && Object.keys(value).every((key) => SELECTION_KEYS.has(key)) && value.tickers !== void 0 && verifyStringSet(value.tickers);
211
+ var cloneSelection = (selection) => selection ? { tickers: [...selection.tickers] } : void 0;
212
+ var resolveStrategySelection = ({
213
+ deployment,
214
+ strategy
215
+ }) => cloneSelection(
216
+ strategy.selection ?? (deployment.tickers ? { tickers: deployment.tickers } : void 0)
217
+ );
218
+ var verifyDeploymentDeclaration = (deploymentId, value) => {
219
+ if (!deploymentId.trim() || !isRecord(value) || Object.keys(value).some((key) => !DEPLOYMENT_KEYS.has(key)) || typeof value.connectorName !== "string" || !value.connectorName.trim() || typeof value.accountId !== "string" || !value.accountId.trim() || value.label !== void 0 && typeof value.label !== "string" || value.provider !== void 0 && typeof value.provider !== "string" || value.enabled !== void 0 && typeof value.enabled !== "boolean" || !verifyStringSet(value.assetClasses) || !verifyStringSet(value.tickers) || !isRecord(value.strategies) || !Object.keys(value.strategies).length) {
220
+ throw new Error(`Invalid runtime deployment declaration: ${deploymentId}`);
221
+ }
222
+ for (const [strategyName, strategyValue] of Object.entries(
223
+ value.strategies
224
+ )) {
225
+ if (!strategyName.trim() || !isRecord(strategyValue) || Object.keys(strategyValue).some((key) => !STRATEGY_KEYS.has(key)) || strategyValue.generation !== void 0 && (typeof strategyValue.generation !== "string" || !strategyValue.generation.trim()) || typeof strategyValue.enabled !== "boolean" || !verifyStrategySelection(strategyValue.selection) || !isRecord(strategyValue.config) || Object.keys(strategyValue.config).some(
226
+ (key) => FORBIDDEN_CONFIG_KEYS.has(key)
227
+ ) || !INTERVALS.has(String(strategyValue.config.INTERVAL)) || !["crypto", "tradfi"].includes(String(strategyValue.config.UNIVERSE))) {
228
+ throw new Error(
229
+ `Invalid runtime strategy declaration: ${deploymentId}/${strategyName}`
230
+ );
231
+ }
232
+ }
233
+ return value;
234
+ };
235
+ var verifyRuntimeDeclaration = (value) => {
236
+ if (!isRecord(value) || Object.keys(value).some((key) => !RUNTIME_KEYS.has(key)) || !isRecord(value.deployments) || !Object.keys(value.deployments).length) {
237
+ throw new Error("Invalid runtime declaration");
238
+ }
239
+ for (const [deploymentId, deployment] of Object.entries(value.deployments)) {
240
+ verifyDeploymentDeclaration(deploymentId, deployment);
241
+ }
242
+ return value;
243
+ };
244
+ var loadRuntimeDeclaration = async (projectRoot) => {
245
+ const projectConfig = await loadTradejsConfig(projectRoot);
246
+ if (!projectConfig.runtime) {
247
+ throw new Error("Runtime declaration is required in tradejs.config.ts");
248
+ }
249
+ return verifyRuntimeDeclaration(projectConfig.runtime);
250
+ };
251
+ var resolveStrategyComposition = async ({
252
+ strategyName,
253
+ declaration,
254
+ deployment,
255
+ projectRoot,
256
+ packageManifest
257
+ }) => {
258
+ const [strategyEntry, strategyCreator, pluginSource] = await Promise.all([
259
+ getStrategyEntry(strategyName, projectRoot),
260
+ getStrategyCreator(strategyName, projectRoot),
261
+ getStrategyPluginSource(strategyName, projectRoot)
262
+ ]);
263
+ if (!strategyEntry || !strategyCreator) {
264
+ throw new Error(`Unknown strategy: ${strategyName}`);
265
+ }
266
+ if (typeof strategyEntry.parseConfig !== "function") {
267
+ throw new Error(`Strategy config parser is missing: ${strategyName}`);
268
+ }
269
+ const strategyPackage = await resolveStrategyPackage({
270
+ pluginSource: pluginSource ?? null,
271
+ projectRoot
272
+ });
273
+ if (!strategyPackage) {
274
+ throw new Error(`Installed strategy package not found: ${strategyName}`);
275
+ }
276
+ const [
277
+ strategyPackageVersion,
278
+ strategyDependencyVersions,
279
+ runtimePackageVersion
280
+ ] = await Promise.all([
281
+ resolveVerifiedPackageVersion({
282
+ projectRoot,
283
+ packageName: strategyPackage.name,
284
+ projectPackage: strategyPackage.projectPackage,
285
+ manifest: packageManifest
286
+ }),
287
+ resolveVerifiedStrategyDependencyVersions({
288
+ projectRoot,
289
+ strategyPackage,
290
+ manifest: packageManifest
291
+ }),
292
+ resolveVerifiedPackageVersion({
293
+ projectRoot,
294
+ packageName: "@tradejs/node",
295
+ manifest: packageManifest
296
+ })
297
+ ]);
298
+ const parsedConfig = strategyEntry.parseConfig(declaration.config);
299
+ if (!isRecord(parsedConfig)) {
300
+ throw new Error(
301
+ `Strategy config parser returned a non-object: ${strategyName}`
302
+ );
303
+ }
304
+ const strategyConfig = parsedConfig;
305
+ const selection = resolveStrategySelection({
306
+ deployment,
307
+ strategy: declaration
308
+ });
309
+ return {
310
+ strategyName,
311
+ strategyRevision: computeStrategyRevision({
312
+ strategyName,
313
+ strategyPackage: strategyPackage.name,
314
+ strategyPackageVersion,
315
+ strategyDependencyVersions,
316
+ runtimePackageVersion,
317
+ strategyConfig
318
+ }),
319
+ ...declaration.generation ? { generation: declaration.generation } : {},
320
+ enabled: declaration.enabled,
321
+ interval: String(strategyConfig.INTERVAL),
322
+ universe: strategyConfig.UNIVERSE,
323
+ strategyPackage: strategyPackage.name,
324
+ strategyPackageVersion,
325
+ strategyDependencyVersions,
326
+ runtimePackageVersion,
327
+ strategyCreator,
328
+ sourceStrategyConfig: declaration.config,
329
+ strategyConfig,
330
+ ...selection ? { selection } : {}
331
+ };
332
+ };
333
+ var resolveRuntimeComposition = async ({
334
+ projectRoot
335
+ }) => {
336
+ const [runtime, packageManifest] = await Promise.all([
337
+ loadRuntimeDeclaration(projectRoot),
338
+ readRuntimePackageManifest(projectRoot)
339
+ ]);
340
+ const deployments = await Promise.all(
341
+ Object.entries(runtime.deployments).map(
342
+ async ([deploymentId, declaration]) => {
343
+ const strategies = await Promise.all(
344
+ Object.entries(declaration.strategies).map(
345
+ ([strategyName, strategyDeclaration]) => resolveStrategyComposition({
346
+ strategyName,
347
+ declaration: strategyDeclaration,
348
+ deployment: declaration,
349
+ projectRoot,
350
+ packageManifest
351
+ })
352
+ )
353
+ );
354
+ const provider = (declaration.provider || declaration.connectorName).trim().toLowerCase();
355
+ return {
356
+ deploymentId,
357
+ deploymentCompositionId: computeDeploymentCompositionId({
358
+ deploymentId,
359
+ connectorName: declaration.connectorName.trim(),
360
+ provider,
361
+ accountId: declaration.accountId.trim(),
362
+ enabled: declaration.enabled ?? true,
363
+ ...declaration.assetClasses ? { assetClasses: declaration.assetClasses } : {},
364
+ strategies: strategies.map((strategy) => ({
365
+ strategyName: strategy.strategyName,
366
+ strategyRevision: strategy.strategyRevision,
367
+ enabled: strategy.enabled,
368
+ ...strategy.selection ? { selection: strategy.selection } : {}
369
+ }))
370
+ }),
371
+ declaration,
372
+ strategies
373
+ };
374
+ }
375
+ )
376
+ );
377
+ return {
378
+ deployments: deployments.sort(
379
+ (left, right) => left.deploymentId.localeCompare(right.deploymentId)
380
+ )
381
+ };
382
+ };
383
+ var toRuntimeDeployment = ({
384
+ composition,
385
+ controls
386
+ }) => {
387
+ const { declaration, deploymentId } = composition;
388
+ const deploymentEnabled = declaration.enabled ?? true;
389
+ return {
390
+ id: deploymentId,
391
+ deploymentCompositionId: composition.deploymentCompositionId,
392
+ label: declaration.label?.trim() || deploymentId,
393
+ connectorName: declaration.connectorName.trim(),
394
+ provider: (declaration.provider || declaration.connectorName).trim().toLowerCase(),
395
+ accountId: declaration.accountId.trim(),
396
+ enabled: deploymentEnabled,
397
+ strategies: composition.strategies.map((strategy) => ({
398
+ strategyName: strategy.strategyName,
399
+ strategyRevision: strategy.strategyRevision,
400
+ enabled: strategy.enabled,
401
+ controlState: deploymentEnabled && strategy.enabled && !controls.deployments[deploymentId]?.[strategy.strategyName]?.entriesPaused ? "active" : "entries_paused",
402
+ ...strategy.selection ? { selection: strategy.selection } : {}
403
+ })),
404
+ ...declaration.assetClasses ? { assetClasses: declaration.assetClasses } : {},
405
+ ...declaration.tickers ? { tickers: declaration.tickers } : {}
406
+ };
407
+ };
408
+ var listRuntimeDeployments = async ({
409
+ userName,
410
+ projectRoot
411
+ }) => {
412
+ const [composition, controls] = await Promise.all([
413
+ resolveRuntimeComposition({ projectRoot }),
414
+ getRuntimeControls(userName)
415
+ ]);
416
+ return composition.deployments.map(
417
+ (deploymentComposition) => toRuntimeDeployment({ composition: deploymentComposition, controls })
418
+ ).sort((left, right) => left.label.localeCompare(right.label));
419
+ };
420
+ var getRuntimeDeployment = async ({
421
+ userName,
422
+ projectRoot,
423
+ deploymentId
424
+ }) => {
425
+ const deployments = await listRuntimeDeployments({ userName, projectRoot });
426
+ return deployments.find((deployment) => deployment.id === deploymentId) ?? null;
427
+ };
428
+ var resolveAccountId = async ({
429
+ userName,
430
+ deployment,
431
+ universe
432
+ }) => {
433
+ const account = await resolveTradingAccount({
434
+ userName,
435
+ accountId: deployment.accountId,
436
+ provider: deployment.provider,
437
+ universe
438
+ });
439
+ if (!account) {
440
+ throw new Error(`Trading account not found: ${deployment.accountId}`);
441
+ }
442
+ return account.id;
443
+ };
444
+ var loadResolvedRuntimeStrategies = async ({
445
+ userName,
446
+ projectRoot,
447
+ deploymentId,
448
+ universe,
449
+ accountId,
450
+ interval
451
+ }) => {
452
+ const [composition, controls] = await Promise.all([
453
+ resolveRuntimeComposition({ projectRoot }),
454
+ getRuntimeControls(userName)
455
+ ]);
456
+ const deploymentComposition = composition.deployments.find(
457
+ (candidate) => candidate.deploymentId === deploymentId
458
+ );
459
+ if (!deploymentComposition) {
460
+ throw new Error(`Runtime deployment not found: ${deploymentId}`);
461
+ }
462
+ const deployment = toRuntimeDeployment({
463
+ composition: deploymentComposition,
464
+ controls
465
+ });
466
+ const strategies = await Promise.all(
467
+ deploymentComposition.strategies.map(async (strategy) => {
468
+ const strategyView = deployment.strategies.find(
469
+ (candidate) => candidate.strategyName === strategy.strategyName
470
+ );
471
+ const resolvedAccountId = await resolveAccountId({
472
+ userName,
473
+ deployment,
474
+ universe: strategy.universe
475
+ });
476
+ return {
477
+ ...strategy,
478
+ deploymentCompositionId: deploymentComposition.deploymentCompositionId,
479
+ accountId: resolvedAccountId,
480
+ controlState: strategyView?.controlState ?? "entries_paused"
481
+ };
482
+ })
483
+ );
484
+ const filtered = strategies.filter(
485
+ (candidate) => (!universe || candidate.universe === universe) && (!interval || String(candidate.interval) === String(interval)) && (!accountId || candidate.accountId === accountId)
486
+ );
487
+ const identities = /* @__PURE__ */ new Set();
488
+ for (const candidate of filtered) {
489
+ const identity = `${candidate.strategyName}:${candidate.accountId ?? "default"}`;
490
+ if (identities.has(identity)) {
491
+ throw new Error(`Runtime strategy conflict: ${identity}`);
492
+ }
493
+ identities.add(identity);
494
+ }
495
+ return filtered;
496
+ };
497
+ var getRuntimeStrategyPackageMetadata = async ({
498
+ strategyName,
499
+ projectRoot
500
+ }) => {
501
+ const [packageManifest, pluginSource] = await Promise.all([
502
+ readRuntimePackageManifest(projectRoot),
503
+ getStrategyPluginSource(strategyName, projectRoot)
504
+ ]);
505
+ const strategyPackage = await resolveStrategyPackage({
506
+ pluginSource: pluginSource ?? null,
507
+ projectRoot
508
+ });
509
+ if (!strategyPackage) {
510
+ throw new Error(`Installed strategy package not found: ${strategyName}`);
511
+ }
512
+ const [
513
+ strategyPackageVersion,
514
+ strategyDependencyVersions,
515
+ runtimePackageVersion
516
+ ] = await Promise.all([
517
+ resolveVerifiedPackageVersion({
518
+ projectRoot,
519
+ packageName: strategyPackage.name,
520
+ projectPackage: strategyPackage.projectPackage,
521
+ manifest: packageManifest
522
+ }),
523
+ resolveVerifiedStrategyDependencyVersions({
524
+ projectRoot,
525
+ strategyPackage,
526
+ manifest: packageManifest
527
+ }),
528
+ resolveVerifiedPackageVersion({
529
+ projectRoot,
530
+ packageName: "@tradejs/node",
531
+ manifest: packageManifest
532
+ })
533
+ ]);
534
+ return {
535
+ strategyPackage: strategyPackage.name,
536
+ strategyPackageVersion,
537
+ strategyDependencyVersions,
538
+ runtimePackageVersion
539
+ };
540
+ };
541
+
542
+ export {
543
+ RUNTIME_PACKAGE_MANIFEST_SCHEMA,
544
+ computeStrategyRevision,
545
+ computeDeploymentCompositionId,
546
+ verifyRuntimeDeclaration,
547
+ resolveRuntimeComposition,
548
+ listRuntimeDeployments,
549
+ getRuntimeDeployment,
550
+ loadResolvedRuntimeStrategies,
551
+ getRuntimeStrategyPackageMetadata
552
+ };