@tradejs/node 1.0.8 → 1.0.10

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,1040 @@
1
+ import {
2
+ getStrategyProfileMlAdapter
3
+ } from "./chunk-IUZML4RK.mjs";
4
+ import {
5
+ getStrategyManifest
6
+ } from "./chunk-QVSMINLG.mjs";
7
+
8
+ // src/strategyHelpers/binanceMarketContext.ts
9
+ import {
10
+ getLatestMarketBreadth,
11
+ getLatestMarketTradeFlow
12
+ } from "@tradejs/infra/timescale";
13
+ import { logger } from "@tradejs/infra/logger";
14
+ import { refreshSignalBaseContextGateFeatures } from "@tradejs/core/strategies";
15
+ var DEFAULT_MAX_AGE_BY_INTERVAL = {
16
+ "1m": 3 * 6e4,
17
+ "5m": 10 * 6e4,
18
+ "15m": 30 * 6e4,
19
+ "1h": 2 * 60 * 6e4
20
+ };
21
+ var binanceMarketContextUnavailable = false;
22
+ var referenceRowsCache = /* @__PURE__ */ new Map();
23
+ var breadthCache = /* @__PURE__ */ new Map();
24
+ var parseEnabledFlag = (value, env) => {
25
+ const normalized = String(value ?? "").trim().toLowerCase();
26
+ if (!normalized)
27
+ return env === "BACKTEST" || env === "CRON" || env === "PARITY";
28
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
29
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
30
+ if (normalized === "backtest") return env === "BACKTEST";
31
+ if (normalized === "live") return env !== "BACKTEST";
32
+ return false;
33
+ };
34
+ var toFiniteNumberOrNull = (value) => {
35
+ const numeric = typeof value === "number" ? value : Number(value);
36
+ return Number.isFinite(numeric) ? numeric : null;
37
+ };
38
+ var signalIntervalToMarketInterval = (value) => {
39
+ const normalized = String(value).trim().toLowerCase();
40
+ if (normalized === "1" || normalized === "1m") return "1m";
41
+ if (normalized === "5" || normalized === "5m") return "5m";
42
+ if (normalized === "60" || normalized === "1h") return "1h";
43
+ return "15m";
44
+ };
45
+ var resolveMarketInterval = (signal, override) => override ?? signalIntervalToMarketInterval(signal.interval);
46
+ var resolveBreadthUniverse = () => (process.env.BINANCE_MARKET_CONTEXT_BREADTH_UNIVERSE || "binance_top30_usdt").trim().toLowerCase();
47
+ var getReferenceSymbols = () => {
48
+ const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
49
+ return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
50
+ };
51
+ var resolvePrimaryReferenceSymbol = (signalSymbol) => {
52
+ const symbol = signalSymbol.trim().toUpperCase();
53
+ const referenceSymbols = getReferenceSymbols();
54
+ return referenceSymbols.includes(symbol) ? symbol : referenceSymbols[0];
55
+ };
56
+ var hasBaseContext = (signal) => Boolean(
57
+ signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
58
+ );
59
+ var isBinanceMarketContextEnabled = (env) => parseEnabledFlag(process.env.BINANCE_MARKET_CONTEXT_ENABLED, env);
60
+ var toTradeFlowContext = (row, interval) => row ? {
61
+ source: "binance_agg_trades",
62
+ interval,
63
+ asOfTs: row.ts.getTime(),
64
+ ageMs: row.ageMs,
65
+ stale: row.stale,
66
+ trades: toFiniteNumberOrNull(row.trades),
67
+ buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
68
+ buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
69
+ sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
70
+ buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
71
+ sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
72
+ netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
73
+ netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
74
+ } : null;
75
+ var getCachedReferenceRows = ({
76
+ referenceSymbols,
77
+ interval,
78
+ timestamp,
79
+ maxAgeMs
80
+ }) => {
81
+ const key = `${referenceSymbols.join(",")}:${interval}:${timestamp}:${maxAgeMs}`;
82
+ const cached = referenceRowsCache.get(key);
83
+ if (cached) return cached;
84
+ const promise = Promise.all(
85
+ referenceSymbols.map(async (symbol) => {
86
+ const tradeFlow = await getLatestMarketTradeFlow({
87
+ symbol,
88
+ interval,
89
+ atMs: timestamp,
90
+ maxAgeMs
91
+ });
92
+ return {
93
+ symbol,
94
+ tradeFlow: toTradeFlowContext(tradeFlow, interval)
95
+ };
96
+ })
97
+ );
98
+ referenceRowsCache.set(key, promise);
99
+ return promise;
100
+ };
101
+ var getCachedBreadth = ({
102
+ breadthUniverse,
103
+ interval,
104
+ timestamp,
105
+ maxAgeMs
106
+ }) => {
107
+ const key = `${breadthUniverse}:${interval}:${timestamp}:${maxAgeMs}`;
108
+ const cached = breadthCache.get(key);
109
+ if (cached) return cached;
110
+ const promise = getLatestMarketBreadth({
111
+ universe: breadthUniverse,
112
+ interval,
113
+ atMs: timestamp,
114
+ maxAgeMs
115
+ });
116
+ breadthCache.set(key, promise);
117
+ return promise;
118
+ };
119
+ var enrichSignalWithBinanceMarketContext = async (params) => {
120
+ const {
121
+ signal,
122
+ env,
123
+ enabled = isBinanceMarketContextEnabled(env),
124
+ interval = resolveMarketInterval(signal, params.interval),
125
+ breadthUniverse = resolveBreadthUniverse(),
126
+ maxAgeMs = DEFAULT_MAX_AGE_BY_INTERVAL[interval]
127
+ } = params;
128
+ if (signal.universe === "tradfi" || !enabled || binanceMarketContextUnavailable || !hasBaseContext(signal)) {
129
+ return false;
130
+ }
131
+ try {
132
+ const referenceSymbols = getReferenceSymbols();
133
+ const primaryReferenceSymbol = resolvePrimaryReferenceSymbol(signal.symbol);
134
+ const [referenceRows, breadth] = await Promise.all([
135
+ getCachedReferenceRows({
136
+ referenceSymbols,
137
+ interval,
138
+ timestamp: signal.timestamp,
139
+ maxAgeMs
140
+ }),
141
+ getCachedBreadth({
142
+ breadthUniverse,
143
+ interval,
144
+ timestamp: signal.timestamp,
145
+ maxAgeMs
146
+ })
147
+ ]);
148
+ const tradeFlowBySymbol = Object.fromEntries(
149
+ referenceRows.filter((row) => row.tradeFlow).map((row) => [row.symbol, row.tradeFlow])
150
+ );
151
+ const targetReferenceSymbol = signal.symbol.trim().toUpperCase();
152
+ const targetTradeFlow = tradeFlowBySymbol[targetReferenceSymbol];
153
+ if (!Object.keys(tradeFlowBySymbol).length && !breadth) {
154
+ return false;
155
+ }
156
+ const baseContext = signal.additionalIndicators.baseContext;
157
+ signal.additionalIndicators = {
158
+ ...signal.additionalIndicators,
159
+ baseContext: {
160
+ ...baseContext,
161
+ participation: {
162
+ ...baseContext.participation,
163
+ ...targetTradeFlow ? {
164
+ tradeFlow: targetTradeFlow
165
+ } : {}
166
+ },
167
+ relative: {
168
+ ...baseContext.relative,
169
+ execution: {
170
+ ...baseContext.relative.execution
171
+ },
172
+ ...Object.keys(tradeFlowBySymbol).length ? {
173
+ referenceTradeFlow: {
174
+ source: "binance_reference_market",
175
+ primaryReferenceSymbol,
176
+ referenceSymbols,
177
+ tradeFlowBySymbol
178
+ }
179
+ } : {},
180
+ ...breadth ? {
181
+ marketBreadth: {
182
+ source: "binance_klines",
183
+ universe: breadth.universe,
184
+ interval,
185
+ asOfTs: breadth.ts.getTime(),
186
+ ageMs: breadth.ageMs,
187
+ stale: breadth.stale,
188
+ symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
189
+ advancers: toFiniteNumberOrNull(breadth.advancers),
190
+ decliners: toFiniteNumberOrNull(breadth.decliners),
191
+ unchanged: toFiniteNumberOrNull(breadth.unchanged),
192
+ advanceDeclineRatio: toFiniteNumberOrNull(
193
+ breadth.advanceDeclineRatio
194
+ ),
195
+ pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
196
+ pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
197
+ equalWeightedReturn: toFiniteNumberOrNull(
198
+ breadth.equalWeightedReturn
199
+ ),
200
+ volumeWeightedReturn: toFiniteNumberOrNull(
201
+ breadth.volumeWeightedReturn
202
+ ),
203
+ dispersion: toFiniteNumberOrNull(breadth.dispersion)
204
+ },
205
+ btcAltRegime: {
206
+ source: "binance_klines",
207
+ universe: breadth.universe,
208
+ interval,
209
+ asOfTs: breadth.ts.getTime(),
210
+ ageMs: breadth.ageMs,
211
+ stale: breadth.stale,
212
+ btcReturn1h: toFiniteNumberOrNull(breadth.btcReturn1h),
213
+ btcReturn4h: toFiniteNumberOrNull(breadth.btcReturn4h),
214
+ btcReturn24h: toFiniteNumberOrNull(breadth.btcReturn24h),
215
+ altBasketReturn1h: toFiniteNumberOrNull(
216
+ breadth.altBasketReturn1h
217
+ ),
218
+ altBasketReturn4h: toFiniteNumberOrNull(
219
+ breadth.altBasketReturn4h
220
+ ),
221
+ altBasketReturn24h: toFiniteNumberOrNull(
222
+ breadth.altBasketReturn24h
223
+ ),
224
+ btcVsAltReturn1h: toFiniteNumberOrNull(
225
+ breadth.btcVsAltReturn1h
226
+ ),
227
+ btcVsAltReturn4h: toFiniteNumberOrNull(
228
+ breadth.btcVsAltReturn4h
229
+ ),
230
+ btcVsAltReturn24h: toFiniteNumberOrNull(
231
+ breadth.btcVsAltReturn24h
232
+ ),
233
+ btcTurnoverShare1h: toFiniteNumberOrNull(
234
+ breadth.btcTurnoverShare1h
235
+ ),
236
+ btcTurnoverShare24h: toFiniteNumberOrNull(
237
+ breadth.btcTurnoverShare24h
238
+ ),
239
+ btcTurnoverShareChange24h: toFiniteNumberOrNull(
240
+ breadth.btcTurnoverShareChange24h
241
+ ),
242
+ altVolToBtcVol24h: toFiniteNumberOrNull(
243
+ breadth.altVolToBtcVol24h
244
+ ),
245
+ altDispersion24h: toFiniteNumberOrNull(
246
+ breadth.altDispersion24h
247
+ ),
248
+ regime: breadth.btcAltRegime ?? "unknown"
249
+ }
250
+ } : {}
251
+ }
252
+ }
253
+ };
254
+ refreshSignalBaseContextGateFeatures(signal);
255
+ return true;
256
+ } catch (error) {
257
+ binanceMarketContextUnavailable = true;
258
+ logger.warn(
259
+ "Binance market context disabled after Timescale read failure: %s",
260
+ String(error)
261
+ );
262
+ return false;
263
+ }
264
+ };
265
+
266
+ // src/strategyHelpers/coinMarketCapContext.ts
267
+ import { refreshSignalBaseContextGateFeatures as refreshSignalBaseContextGateFeatures2 } from "@tradejs/core/strategies";
268
+ import { logger as logger2 } from "@tradejs/infra/logger";
269
+ import {
270
+ getLatestMarketCmcExchangeLiquidityContext,
271
+ getLatestMarketCmcFearGreedContext,
272
+ getLatestMarketCmcIndexContexts,
273
+ getLatestMarketGlobalContext,
274
+ getLatestMarketReferenceAssetContexts
275
+ } from "@tradejs/infra/timescale";
276
+ var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
277
+ var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
278
+ var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
279
+ var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
280
+ var SOURCE_FEAR_GREED = "coinmarketcap_fear_greed";
281
+ var SOURCE_INDEX = "coinmarketcap_index";
282
+ var DAY_MS = 864e5;
283
+ var coinMarketCapContextUnavailable = false;
284
+ var globalContextCache = /* @__PURE__ */ new Map();
285
+ var referenceContextCache = /* @__PURE__ */ new Map();
286
+ var exchangeLiquidityContextCache = /* @__PURE__ */ new Map();
287
+ var fearGreedContextCache = /* @__PURE__ */ new Map();
288
+ var indexContextCache = /* @__PURE__ */ new Map();
289
+ var parseEnabledFlag2 = (value, env) => {
290
+ const normalized = String(value ?? "").trim().toLowerCase();
291
+ if (!normalized) {
292
+ return env === "BACKTEST" || env === "PARITY" || env === "CRON";
293
+ }
294
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
295
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
296
+ if (normalized === "backtest") return env === "BACKTEST";
297
+ if (normalized === "live") return env !== "BACKTEST";
298
+ return false;
299
+ };
300
+ var asInt = (value, fallback) => {
301
+ const parsed = Number.parseInt(String(value ?? ""), 10);
302
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
303
+ };
304
+ var toFiniteNumberOrNull2 = (value) => {
305
+ const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
306
+ return Number.isFinite(numeric) ? numeric : null;
307
+ };
308
+ var safeDivide = (numerator, denominator) => numerator != null && denominator != null && denominator > 0 ? numerator / denominator : null;
309
+ var hasBaseContext2 = (signal) => Boolean(
310
+ signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
311
+ );
312
+ var resolveMaxAgeMs = () => asInt(process.env.COINMARKETCAP_CONTEXT_MAX_AGE_MS, DEFAULT_MAX_AGE_MS);
313
+ var toAltLiquidityRegime = ({
314
+ stale,
315
+ btcDominanceChange24hPct,
316
+ altMarketCapChange24hPct,
317
+ altVolumeChange24hPct
318
+ }) => {
319
+ if (stale) return "unknown";
320
+ if (altMarketCapChange24hPct != null && altMarketCapChange24hPct <= -0.03 || altVolumeChange24hPct != null && altVolumeChange24hPct <= -0.15) {
321
+ return "risk_off";
322
+ }
323
+ if (btcDominanceChange24hPct != null && btcDominanceChange24hPct >= 0.25) {
324
+ return "btc_favored";
325
+ }
326
+ if (btcDominanceChange24hPct != null && btcDominanceChange24hPct <= -0.25 && (altMarketCapChange24hPct == null || altMarketCapChange24hPct >= 0)) {
327
+ return "alt_friendly";
328
+ }
329
+ return "neutral";
330
+ };
331
+ var toReferenceLiquidityRegime = ({
332
+ stale,
333
+ ethBtcMarketCapRatioChange24hPct,
334
+ ethVsBtcVolumeRatio
335
+ }) => {
336
+ if (stale) return "unknown";
337
+ if (ethVsBtcVolumeRatio != null && ethVsBtcVolumeRatio < 0.15) return "thin";
338
+ if (ethBtcMarketCapRatioChange24hPct != null && ethBtcMarketCapRatioChange24hPct >= 0.01) {
339
+ return "eth_led";
340
+ }
341
+ if (ethBtcMarketCapRatioChange24hPct != null && ethBtcMarketCapRatioChange24hPct <= -0.01) {
342
+ return "btc_led";
343
+ }
344
+ return "balanced";
345
+ };
346
+ var toExchangeLiquidityRegime = ({
347
+ stale,
348
+ totalVolumeChange24hPct,
349
+ fallback
350
+ }) => {
351
+ if (stale) return "unknown";
352
+ if (totalVolumeChange24hPct != null && totalVolumeChange24hPct >= 0.15) {
353
+ return "expanding";
354
+ }
355
+ if (totalVolumeChange24hPct != null && totalVolumeChange24hPct <= -0.15) {
356
+ return "contracting";
357
+ }
358
+ return fallback;
359
+ };
360
+ var toIndexRegime = ({
361
+ stale,
362
+ cmc100Change24hPct,
363
+ cmc20Change24hPct,
364
+ cmc20ToCmc100RatioChange24hPct
365
+ }) => {
366
+ if (stale) return "unknown";
367
+ if (cmc100Change24hPct == null && cmc20Change24hPct == null) {
368
+ return "unknown";
369
+ }
370
+ if ((cmc100Change24hPct ?? 0) <= -0.02 && (cmc20Change24hPct ?? 0) <= -0.02) {
371
+ return "risk_off";
372
+ }
373
+ if ((cmc20ToCmc100RatioChange24hPct ?? 0) >= 5e-3) {
374
+ return "top20_led";
375
+ }
376
+ if ((cmc20ToCmc100RatioChange24hPct ?? 0) <= -5e-3) {
377
+ return "large_cap_led";
378
+ }
379
+ return "balanced";
380
+ };
381
+ var getCachedGlobalContext = ({
382
+ timestamp,
383
+ maxAgeMs
384
+ }) => {
385
+ const key = `${SOURCE_GLOBAL_DAILY}:${timestamp}:${maxAgeMs}`;
386
+ const cached = globalContextCache.get(key);
387
+ if (cached) return cached;
388
+ const promise = getLatestMarketGlobalContext({
389
+ source: SOURCE_GLOBAL_DAILY,
390
+ atMs: timestamp,
391
+ maxAgeMs
392
+ });
393
+ globalContextCache.set(key, promise);
394
+ return promise;
395
+ };
396
+ var getCachedReferenceContexts = ({
397
+ timestamp,
398
+ maxAgeMs
399
+ }) => {
400
+ const key = `${SOURCE_REFERENCE}:1d:${timestamp}:${maxAgeMs}`;
401
+ const cached = referenceContextCache.get(key);
402
+ if (cached) return cached;
403
+ const promise = getLatestMarketReferenceAssetContexts({
404
+ source: SOURCE_REFERENCE,
405
+ symbols: ["BTCUSDT", "ETHUSDT"],
406
+ interval: "1d",
407
+ atMs: timestamp,
408
+ maxAgeMs
409
+ });
410
+ referenceContextCache.set(key, promise);
411
+ return promise;
412
+ };
413
+ var getCachedExchangeLiquidityContext = ({
414
+ timestamp,
415
+ maxAgeMs
416
+ }) => {
417
+ const key = `${SOURCE_EXCHANGE_LIQUIDITY}:1d:${timestamp}:${maxAgeMs}`;
418
+ const cached = exchangeLiquidityContextCache.get(key);
419
+ if (cached) return cached;
420
+ const promise = getLatestMarketCmcExchangeLiquidityContext({
421
+ source: SOURCE_EXCHANGE_LIQUIDITY,
422
+ interval: "1d",
423
+ atMs: timestamp,
424
+ maxAgeMs
425
+ });
426
+ exchangeLiquidityContextCache.set(key, promise);
427
+ return promise;
428
+ };
429
+ var getCachedFearGreedContext = ({
430
+ timestamp,
431
+ maxAgeMs
432
+ }) => {
433
+ const key = `${SOURCE_FEAR_GREED}:1d:${timestamp}:${maxAgeMs}`;
434
+ const cached = fearGreedContextCache.get(key);
435
+ if (cached) return cached;
436
+ const promise = getLatestMarketCmcFearGreedContext({
437
+ source: SOURCE_FEAR_GREED,
438
+ interval: "1d",
439
+ atMs: timestamp,
440
+ maxAgeMs
441
+ });
442
+ fearGreedContextCache.set(key, promise);
443
+ return promise;
444
+ };
445
+ var getCachedIndexContexts = ({
446
+ timestamp,
447
+ maxAgeMs
448
+ }) => {
449
+ const key = `${SOURCE_INDEX}:1d:${timestamp}:${maxAgeMs}`;
450
+ const cached = indexContextCache.get(key);
451
+ if (cached) return cached;
452
+ const promise = getLatestMarketCmcIndexContexts({
453
+ source: SOURCE_INDEX,
454
+ indexSlugs: ["cmc100", "cmc20"],
455
+ interval: "1d",
456
+ atMs: timestamp,
457
+ maxAgeMs
458
+ });
459
+ indexContextCache.set(key, promise);
460
+ return promise;
461
+ };
462
+ var isCoinMarketCapContextEnabled = (env) => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_ENABLED, env);
463
+ var enrichSignalWithCoinMarketCapContext = async (params) => {
464
+ const {
465
+ signal,
466
+ env,
467
+ enabled = isCoinMarketCapContextEnabled(env),
468
+ maxAgeMs = resolveMaxAgeMs()
469
+ } = params;
470
+ if (signal.universe === "tradfi" || !enabled || coinMarketCapContextUnavailable || !hasBaseContext2(signal)) {
471
+ return false;
472
+ }
473
+ try {
474
+ const [
475
+ globalDailyRow,
476
+ dailyReferences,
477
+ previousDailyReferences,
478
+ exchangeLiquidityRow,
479
+ fearGreedRow,
480
+ indexRows
481
+ ] = await Promise.all([
482
+ getCachedGlobalContext({
483
+ timestamp: signal.timestamp,
484
+ maxAgeMs
485
+ }),
486
+ getCachedReferenceContexts({
487
+ timestamp: signal.timestamp,
488
+ maxAgeMs
489
+ }),
490
+ getCachedReferenceContexts({
491
+ timestamp: signal.timestamp - DAY_MS,
492
+ maxAgeMs: maxAgeMs + DAY_MS
493
+ }),
494
+ getCachedExchangeLiquidityContext({
495
+ timestamp: signal.timestamp,
496
+ maxAgeMs
497
+ }),
498
+ getCachedFearGreedContext({
499
+ timestamp: signal.timestamp,
500
+ maxAgeMs
501
+ }),
502
+ getCachedIndexContexts({
503
+ timestamp: signal.timestamp,
504
+ maxAgeMs
505
+ })
506
+ ]);
507
+ const globalRow = globalDailyRow;
508
+ const references = dailyReferences;
509
+ const previousReferences = previousDailyReferences;
510
+ if (!globalRow && !references.size && !exchangeLiquidityRow && !fearGreedRow && !indexRows.size) {
511
+ return false;
512
+ }
513
+ const btcRow = references.get("BTCUSDT") ?? null;
514
+ const ethRow = references.get("ETHUSDT") ?? null;
515
+ const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
516
+ const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
517
+ const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
518
+ const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
519
+ const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
520
+ previousBtcRow?.marketCapUsd
521
+ );
522
+ const previousEthMarketCapUsd = toFiniteNumberOrNull2(
523
+ previousEthRow?.marketCapUsd
524
+ );
525
+ const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
526
+ const previousEthBtcMarketCapRatio = safeDivide(
527
+ previousEthMarketCapUsd,
528
+ previousBtcMarketCapUsd
529
+ );
530
+ const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
531
+ const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
532
+ const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
533
+ const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
534
+ const btcDominanceChange24hPct = toFiniteNumberOrNull2(
535
+ globalRow?.btcDominanceChange24hPct
536
+ );
537
+ const altMarketCapChange24hPct = toFiniteNumberOrNull2(
538
+ globalRow?.altMarketCapChange24hPct
539
+ );
540
+ const altVolumeChange24hPct = toFiniteNumberOrNull2(
541
+ globalRow?.altVolumeChange24hPct
542
+ );
543
+ const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
544
+ stale: globalRow.stale,
545
+ btcDominanceChange24hPct,
546
+ altMarketCapChange24hPct,
547
+ altVolumeChange24hPct
548
+ }) : "unknown";
549
+ const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
550
+ stale: exchangeLiquidityRow.stale,
551
+ totalVolumeChange24hPct: toFiniteNumberOrNull2(
552
+ exchangeLiquidityRow.totalVolumeChange24hPct
553
+ ),
554
+ fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
555
+ }) : "unknown";
556
+ const cmc100Row = indexRows.get("cmc100") ?? null;
557
+ const cmc20Row = indexRows.get("cmc20") ?? null;
558
+ const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
559
+ const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
560
+ const cmc100Change24hPct = toFiniteNumberOrNull2(
561
+ cmc100Row?.valueChange24hPct
562
+ );
563
+ const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
564
+ const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
565
+ const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
566
+ const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
567
+ const indexRegime = toIndexRegime({
568
+ stale: indexStale,
569
+ cmc100Change24hPct,
570
+ cmc20Change24hPct,
571
+ cmc20ToCmc100RatioChange24hPct
572
+ });
573
+ const baseContext = signal.additionalIndicators.baseContext;
574
+ signal.additionalIndicators = {
575
+ ...signal.additionalIndicators,
576
+ baseContext: {
577
+ ...baseContext,
578
+ relative: {
579
+ ...baseContext.relative,
580
+ ...globalRow ? {
581
+ cmcGlobal: {
582
+ source: globalRow.source,
583
+ interval: "1d",
584
+ asOfTs: globalRow.ts.getTime(),
585
+ ageMs: globalRow.ageMs,
586
+ stale: globalRow.stale,
587
+ totalMarketCapUsd: toFiniteNumberOrNull2(
588
+ globalRow.totalMarketCapUsd
589
+ ),
590
+ totalVolumeUsd: toFiniteNumberOrNull2(
591
+ globalRow.totalVolumeUsd
592
+ ),
593
+ totalVolumeReportedUsd: toFiniteNumberOrNull2(
594
+ globalRow.totalVolumeReportedUsd
595
+ ),
596
+ altMarketCapUsd: toFiniteNumberOrNull2(
597
+ globalRow.altMarketCapUsd
598
+ ),
599
+ altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
600
+ altVolumeReportedUsd: toFiniteNumberOrNull2(
601
+ globalRow.altVolumeReportedUsd
602
+ ),
603
+ btcDominancePct: toFiniteNumberOrNull2(
604
+ globalRow.btcDominancePct
605
+ ),
606
+ ethDominancePct: toFiniteNumberOrNull2(
607
+ globalRow.ethDominancePct
608
+ ),
609
+ btcDominanceChange24hPct,
610
+ ethDominanceChange24hPct: toFiniteNumberOrNull2(
611
+ globalRow.ethDominanceChange24hPct
612
+ ),
613
+ altMarketCapChange24hPct,
614
+ altVolumeChange24hPct,
615
+ activeCryptocurrencies: toFiniteNumberOrNull2(
616
+ globalRow.activeCryptocurrencies
617
+ ),
618
+ activeExchanges: toFiniteNumberOrNull2(
619
+ globalRow.activeExchanges
620
+ ),
621
+ activeMarketPairs: toFiniteNumberOrNull2(
622
+ globalRow.activeMarketPairs
623
+ ),
624
+ altLiquidityRegime
625
+ }
626
+ } : {},
627
+ ...btcRow || ethRow ? {
628
+ cmcReferenceAssets: {
629
+ source: SOURCE_REFERENCE,
630
+ interval: "1d",
631
+ asOfTs: Math.max(
632
+ btcRow?.ts.getTime() ?? 0,
633
+ ethRow?.ts.getTime() ?? 0
634
+ ),
635
+ ageMs: btcRow?.ageMs != null && ethRow?.ageMs != null ? Math.max(btcRow.ageMs, ethRow.ageMs) : btcRow?.ageMs ?? ethRow?.ageMs ?? null,
636
+ stale: referenceStale,
637
+ btcMarketCapUsd,
638
+ ethMarketCapUsd,
639
+ btcVolumeUsd,
640
+ ethVolumeUsd,
641
+ btcVolumeToMarketCap: safeDivide(
642
+ btcVolumeUsd,
643
+ btcMarketCapUsd
644
+ ),
645
+ ethVolumeToMarketCap: safeDivide(
646
+ ethVolumeUsd,
647
+ ethMarketCapUsd
648
+ ),
649
+ ethBtcMarketCapRatio,
650
+ ethBtcMarketCapRatioChange24hPct,
651
+ ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd),
652
+ referenceLiquidityRegime: toReferenceLiquidityRegime({
653
+ stale: referenceStale,
654
+ ethBtcMarketCapRatioChange24hPct,
655
+ ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd)
656
+ })
657
+ }
658
+ } : {},
659
+ ...exchangeLiquidityRow ? {
660
+ cmcExchangeLiquidity: {
661
+ source: SOURCE_EXCHANGE_LIQUIDITY,
662
+ interval: exchangeLiquidityRow.interval,
663
+ asOfTs: exchangeLiquidityRow.ts.getTime(),
664
+ ageMs: exchangeLiquidityRow.ageMs,
665
+ stale: exchangeLiquidityRow.stale,
666
+ exchangesCount: toFiniteNumberOrNull2(
667
+ exchangeLiquidityRow.exchangesCount
668
+ ),
669
+ totalVolumeUsd: toFiniteNumberOrNull2(
670
+ exchangeLiquidityRow.totalVolumeUsd
671
+ ),
672
+ totalVolumeChange24hPct: toFiniteNumberOrNull2(
673
+ exchangeLiquidityRow.totalVolumeChange24hPct
674
+ ),
675
+ binanceVolumeUsd: toFiniteNumberOrNull2(
676
+ exchangeLiquidityRow.binanceVolumeUsd
677
+ ),
678
+ binanceVolumeShare: toFiniteNumberOrNull2(
679
+ exchangeLiquidityRow.binanceVolumeShare
680
+ ),
681
+ topExchangeVolumeShare: toFiniteNumberOrNull2(
682
+ exchangeLiquidityRow.topExchangeVolumeShare
683
+ ),
684
+ liquidityRegime: exchangeLiquidityRegime
685
+ }
686
+ } : {},
687
+ ...fearGreedRow ? {
688
+ cmcFearGreed: {
689
+ source: SOURCE_FEAR_GREED,
690
+ interval: "1d",
691
+ asOfTs: fearGreedRow.ts.getTime(),
692
+ ageMs: fearGreedRow.ageMs,
693
+ stale: fearGreedRow.stale,
694
+ value: toFiniteNumberOrNull2(fearGreedRow.value),
695
+ valueChange24h: toFiniteNumberOrNull2(
696
+ fearGreedRow.valueChange24h
697
+ ),
698
+ valueChange7d: toFiniteNumberOrNull2(
699
+ fearGreedRow.valueChange7d
700
+ ),
701
+ classification: fearGreedRow.classification ?? "Unknown",
702
+ sentimentRegime: fearGreedRow.sentimentRegime ?? "unknown"
703
+ }
704
+ } : {},
705
+ ...cmc100Row || cmc20Row ? {
706
+ cmcIndexes: {
707
+ source: SOURCE_INDEX,
708
+ interval: "1d",
709
+ asOfTs: Math.max(
710
+ cmc100Row?.ts.getTime() ?? 0,
711
+ cmc20Row?.ts.getTime() ?? 0
712
+ ),
713
+ ageMs: cmc100Row?.ageMs != null && cmc20Row?.ageMs != null ? Math.max(cmc100Row.ageMs, cmc20Row.ageMs) : cmc100Row?.ageMs ?? cmc20Row?.ageMs ?? null,
714
+ stale: indexStale,
715
+ cmc100Value,
716
+ cmc100Change24hPct,
717
+ cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
718
+ cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
719
+ cmc100Row?.topConstituentWeightPct
720
+ ),
721
+ cmc20Value,
722
+ cmc20Change24hPct,
723
+ cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
724
+ cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
725
+ cmc20Row?.topConstituentWeightPct
726
+ ),
727
+ cmc20ToCmc100Ratio,
728
+ cmc20ToCmc100RatioChange24hPct,
729
+ indexRegime
730
+ }
731
+ } : {}
732
+ }
733
+ }
734
+ };
735
+ refreshSignalBaseContextGateFeatures2(signal);
736
+ return true;
737
+ } catch (error) {
738
+ coinMarketCapContextUnavailable = true;
739
+ logger2.warn(
740
+ "CoinMarketCap context disabled after Timescale read failure: %s",
741
+ String(error)
742
+ );
743
+ return false;
744
+ }
745
+ };
746
+
747
+ // src/strategyHelpers/derivativesContext.ts
748
+ import {
749
+ buildDerivativesContext,
750
+ normalizeDerivativesIntervals
751
+ } from "@tradejs/core/indicators";
752
+ import { refreshSignalBaseContextGateFeatures as refreshSignalBaseContextGateFeatures3 } from "@tradejs/core/strategies";
753
+ import {
754
+ DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS,
755
+ resolveDerivativesContextReferenceSymbols
756
+ } from "@tradejs/core/constants";
757
+ import { getDerivativesWindow } from "@tradejs/infra/timescale";
758
+ import { logger as logger3 } from "@tradejs/infra/logger";
759
+ var DEFAULT_INTERVALS = ["15m", "1h"];
760
+ var DEFAULT_LOOKBACK_HOURS = 48;
761
+ var PRIMARY_DERIVATIVES_REFERENCE_SYMBOL = DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[0];
762
+ var SECONDARY_DERIVATIVES_REFERENCE_SYMBOL = DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[1];
763
+ var derivativesContextUnavailable = false;
764
+ var parseEnabledFlag3 = (value, env) => {
765
+ const normalized = String(value ?? "").trim().toLowerCase();
766
+ if (!normalized) return true;
767
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
768
+ if (normalized === "backtest") return env === "BACKTEST";
769
+ if (normalized === "live") return env !== "BACKTEST";
770
+ return false;
771
+ };
772
+ var parseBooleanFlag = (value, fallback = false) => {
773
+ const normalized = String(value ?? "").trim().toLowerCase();
774
+ if (!normalized) return fallback;
775
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
776
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
777
+ return fallback;
778
+ };
779
+ var parseLookbackMs = () => {
780
+ const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
781
+ const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
782
+ return normalizedHours * 60 * 60 * 1e3;
783
+ };
784
+ var parseIntervals = () => {
785
+ const fromEnv = normalizeDerivativesIntervals(
786
+ process.env.DERIVATIVES_CONTEXT_INTERVALS
787
+ );
788
+ return fromEnv.length ? fromEnv : DEFAULT_INTERVALS;
789
+ };
790
+ var getDerivativesContextReferenceSymbols = () => [
791
+ ...resolveDerivativesContextReferenceSymbols(
792
+ process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
793
+ )
794
+ ];
795
+ var normalizeSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
796
+ var getSignalPriceChangePct1h = (signal) => {
797
+ const baseContext = signal.additionalIndicators?.baseContext;
798
+ if (!baseContext || typeof baseContext !== "object" || Array.isArray(baseContext)) {
799
+ return null;
800
+ }
801
+ const raw = typeof baseContext.raw === "object" && baseContext.raw && !Array.isArray(baseContext.raw) ? baseContext.raw : null;
802
+ const price = raw && typeof raw.price === "object" && raw.price && !Array.isArray(raw.price) ? raw.price : null;
803
+ const value = price?.price1hPct;
804
+ const numeric = typeof value === "number" ? value : Number(value);
805
+ return Number.isFinite(numeric) ? numeric : null;
806
+ };
807
+ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
808
+ var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
809
+ var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
810
+ var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
811
+ var toFiniteNumberOrNull3 = (value) => {
812
+ if (typeof value === "number" && Number.isFinite(value)) return value;
813
+ if (typeof value === "string" && value.trim()) {
814
+ const parsed = Number(value);
815
+ return Number.isFinite(parsed) ? parsed : null;
816
+ }
817
+ return null;
818
+ };
819
+ var roundNullable = (value, digits = 4) => {
820
+ if (value == null || !Number.isFinite(value)) return null;
821
+ const multiplier = 10 ** digits;
822
+ return Math.round(value * multiplier) / multiplier;
823
+ };
824
+ var deltaNullable = (targetValue, referenceValue) => {
825
+ const target = toFiniteNumberOrNull3(targetValue);
826
+ const reference = toFiniteNumberOrNull3(referenceValue);
827
+ return target == null || reference == null ? null : roundNullable(target - reference);
828
+ };
829
+ var buildTargetDerivedContext = (params) => {
830
+ const { targetContext, primaryReferenceContext } = params;
831
+ const targetPrimary = getPrimaryIntervalContext(targetContext);
832
+ const referencePrimary = getPrimaryIntervalContext(primaryReferenceContext);
833
+ const targetDirectionAligned = targetContext.summary.directionAligned;
834
+ const referenceDirectionAligned = primaryReferenceContext?.summary.directionAligned ?? null;
835
+ return {
836
+ available: hasDerivativesSymbolData(targetContext),
837
+ stale: targetContext.summary.riskFlags.includes("stale_derivatives") || targetPrimary?.stale === true ? true : targetPrimary == null ? null : false,
838
+ sourceSymbol: targetContext.symbol,
839
+ referenceSymbol: primaryReferenceContext?.symbol ?? null,
840
+ directionAligned: targetDirectionAligned,
841
+ referenceDirectionAligned,
842
+ pressure: targetContext.summary.pressure ?? null,
843
+ referencePressure: primaryReferenceContext?.summary.pressure ?? null,
844
+ riskFlags: targetContext.summary.riskFlags,
845
+ oiChangePct1h: targetPrimary?.oiChangePct1h ?? null,
846
+ oiAcceleration: targetContext.summary.oiAcceleration ?? null,
847
+ fundingRate: targetPrimary?.fundingRate ?? null,
848
+ fundingZScore: targetPrimary?.fundingZScore ?? null,
849
+ fundingChange1h: targetContext.summary.fundingChange1h ?? null,
850
+ liqSpikeRatio: targetPrimary?.liqSpikeRatio ?? null,
851
+ liqImbalance: targetPrimary?.liqImbalance ?? null,
852
+ targetVsPrimaryOiChangePct1hDelta: deltaNullable(
853
+ targetPrimary?.oiChangePct1h,
854
+ referencePrimary?.oiChangePct1h
855
+ ),
856
+ targetVsPrimaryFundingZScoreDelta: deltaNullable(
857
+ targetPrimary?.fundingZScore,
858
+ referencePrimary?.fundingZScore
859
+ ),
860
+ targetReferenceConflict: targetDirectionAligned == null || referenceDirectionAligned == null ? null : targetDirectionAligned !== referenceDirectionAligned
861
+ };
862
+ };
863
+ var buildReferenceDerivativesContext = (params) => {
864
+ const {
865
+ targetSymbol,
866
+ primaryReferenceSymbol,
867
+ secondaryReferenceSymbol,
868
+ referenceSymbols,
869
+ referenceContexts,
870
+ targetContext
871
+ } = params;
872
+ const primaryContext = referenceContexts[primaryReferenceSymbol] ?? referenceContexts[referenceSymbols[0]];
873
+ if (!primaryContext) {
874
+ throw new Error("No derivatives reference contexts built");
875
+ }
876
+ const referenceSymbolsMetadata = [
877
+ .../* @__PURE__ */ new Set([
878
+ primaryReferenceSymbol,
879
+ secondaryReferenceSymbol,
880
+ ...referenceSymbols,
881
+ ...Object.keys(referenceContexts)
882
+ ])
883
+ ];
884
+ const targetDerived = targetContext && hasDerivativesSymbolData(targetContext) ? buildTargetDerivedContext({
885
+ targetContext,
886
+ primaryReferenceContext: primaryContext
887
+ }) : void 0;
888
+ return {
889
+ ...primaryContext,
890
+ targetSymbol,
891
+ primaryReferenceSymbol: primaryContext.symbol,
892
+ secondaryReferenceSymbol: referenceContexts[secondaryReferenceSymbol]?.symbol ?? secondaryReferenceSymbol,
893
+ referenceSymbols: referenceSymbolsMetadata,
894
+ referenceContexts,
895
+ ...targetContext && targetDerived ? {
896
+ targetContext,
897
+ targetDerived
898
+ } : {}
899
+ };
900
+ };
901
+ var isDerivativesContextEnabled = (env) => parseEnabledFlag3(process.env.DERIVATIVES_CONTEXT_ENABLED, env);
902
+ var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
903
+ var enrichSignalWithDerivativesContext = async (params) => {
904
+ const { signal, env, enabled = isDerivativesContextEnabled(env) } = params;
905
+ if (signal.universe === "tradfi" || !enabled || derivativesContextUnavailable) {
906
+ return false;
907
+ }
908
+ try {
909
+ const intervals = parseIntervals();
910
+ const referenceSymbols = getDerivativesContextReferenceSymbols();
911
+ const targetSymbol = normalizeSymbol(signal.symbol);
912
+ const lookbackMs = parseLookbackMs();
913
+ const contexts = await Promise.all(
914
+ referenceSymbols.map(async (symbol) => {
915
+ const rowsByInterval = await getDerivativesWindow({
916
+ symbol,
917
+ intervals,
918
+ endMs: signal.timestamp,
919
+ lookbackMs
920
+ });
921
+ return [
922
+ symbol,
923
+ buildDerivativesContext({
924
+ symbol,
925
+ direction: signal.direction,
926
+ timestamp: signal.timestamp,
927
+ rowsByInterval,
928
+ priceChangePct1h: getSignalPriceChangePct1h(signal),
929
+ intervals
930
+ })
931
+ ];
932
+ })
933
+ );
934
+ const referenceContexts = Object.fromEntries(contexts);
935
+ const primaryReferenceSymbol = resolvePrimaryReferenceSymbol2();
936
+ const secondaryReferenceSymbol = resolveSecondaryReferenceSymbol();
937
+ const targetContextEnabled = isDerivativesTargetContextEnabled();
938
+ const referenceTargetContext = targetContextEnabled && targetSymbol !== primaryReferenceSymbol ? referenceContexts[targetSymbol] : void 0;
939
+ const shouldFetchTargetContext = targetContextEnabled && targetSymbol.length > 0 && !referenceSymbols.some(
940
+ (referenceSymbol) => referenceSymbol === targetSymbol
941
+ );
942
+ const fetchedTargetContext = shouldFetchTargetContext ? await (async () => {
943
+ const rowsByInterval = await getDerivativesWindow({
944
+ symbol: targetSymbol,
945
+ intervals,
946
+ endMs: signal.timestamp,
947
+ lookbackMs
948
+ });
949
+ const context = buildDerivativesContext({
950
+ symbol: targetSymbol,
951
+ direction: signal.direction,
952
+ timestamp: signal.timestamp,
953
+ rowsByInterval,
954
+ priceChangePct1h: getSignalPriceChangePct1h(signal),
955
+ intervals
956
+ });
957
+ return hasDerivativesSymbolData(context) ? context : void 0;
958
+ })() : void 0;
959
+ const targetContext = referenceTargetContext && hasDerivativesSymbolData(referenceTargetContext) ? referenceTargetContext : fetchedTargetContext;
960
+ const derivativesContext = buildReferenceDerivativesContext({
961
+ targetSymbol: targetSymbol || signal.symbol,
962
+ primaryReferenceSymbol,
963
+ secondaryReferenceSymbol,
964
+ referenceSymbols,
965
+ referenceContexts,
966
+ targetContext
967
+ });
968
+ signal.additionalIndicators = {
969
+ ...signal.additionalIndicators ?? {},
970
+ baseContext: signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext) ? {
971
+ ...signal.additionalIndicators.baseContext,
972
+ derivatives: derivativesContext
973
+ } : signal.additionalIndicators?.baseContext
974
+ };
975
+ refreshSignalBaseContextGateFeatures3(signal);
976
+ return true;
977
+ } catch (error) {
978
+ derivativesContextUnavailable = true;
979
+ logger3.warn(
980
+ "Derivatives context disabled after Timescale read failure: %s",
981
+ String(error)
982
+ );
983
+ return false;
984
+ }
985
+ };
986
+
987
+ // src/strategyAdapters/ml.ts
988
+ var defaultMlAdapter = {
989
+ normalizeStrategyConfig: (strategyConfig) => strategyConfig
990
+ };
991
+ var getStrategyMlAdapter = (strategy, profileId) => {
992
+ const strategyAdapter = getStrategyProfileMlAdapter(
993
+ getStrategyManifest(strategy),
994
+ profileId
995
+ );
996
+ if (!strategyAdapter) return defaultMlAdapter;
997
+ return {
998
+ ...defaultMlAdapter,
999
+ ...strategyAdapter
1000
+ };
1001
+ };
1002
+
1003
+ // src/mlPayload.ts
1004
+ var normalizeStrategyConfig = (strategyConfig, strategyName, profileId) => {
1005
+ return getStrategyMlAdapter(
1006
+ strategyName,
1007
+ profileId
1008
+ ).normalizeStrategyConfig?.(strategyConfig);
1009
+ };
1010
+ var buildMlPayload = (payload) => {
1011
+ const strategyName = payload.signal?.strategy ?? payload.context?.strategyName;
1012
+ const profileId = payload.signal?.policyProfileId;
1013
+ const mlAdapter = getStrategyMlAdapter(strategyName, profileId);
1014
+ const normalizedSignal = mlAdapter.normalizeSignal?.(payload.signal) ?? payload.signal;
1015
+ const nextSignal = {
1016
+ ...normalizedSignal,
1017
+ indicators: {
1018
+ ...normalizedSignal?.indicators ?? {}
1019
+ }
1020
+ };
1021
+ const nextContext = payload.context ? {
1022
+ ...payload.context,
1023
+ strategyConfig: normalizeStrategyConfig(
1024
+ payload.context.strategyConfig,
1025
+ strategyName,
1026
+ profileId
1027
+ )
1028
+ } : void 0;
1029
+ return {
1030
+ signal: nextSignal,
1031
+ context: nextContext
1032
+ };
1033
+ };
1034
+
1035
+ export {
1036
+ enrichSignalWithBinanceMarketContext,
1037
+ enrichSignalWithCoinMarketCapContext,
1038
+ enrichSignalWithDerivativesContext,
1039
+ buildMlPayload
1040
+ };