@tradejs/node 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ai.d.mts +4 -1
- package/dist/ai.d.ts +4 -1
- package/dist/ai.js +601 -93
- package/dist/ai.mjs +5 -3
- package/dist/backtest.d.mts +26 -2
- package/dist/backtest.d.ts +26 -2
- package/dist/backtest.js +3367 -481
- package/dist/backtest.mjs +1845 -269
- package/dist/chunk-37VNDZVX.mjs +1040 -0
- package/dist/chunk-IUZML4RK.mjs +1136 -0
- package/dist/{chunk-WGOYR6AB.mjs → chunk-QVSMINLG.mjs} +1 -1
- package/dist/{chunk-JMDYEKIO.mjs → chunk-V3YMKE4I.mjs} +1 -1
- package/dist/{chunk-JU77QVJ3.mjs → chunk-WS5DYEVZ.mjs} +59 -5
- package/dist/cli.d.mts +13 -3
- package/dist/cli.d.ts +13 -3
- package/dist/cli.js +1016 -234
- package/dist/cli.mjs +338 -65
- package/dist/connectors.js +59 -5
- package/dist/connectors.mjs +2 -2
- package/dist/registry.js +59 -5
- package/dist/registry.mjs +2 -2
- package/dist/strategies.d.mts +29 -8
- package/dist/strategies.d.ts +29 -8
- package/dist/strategies.js +3428 -1285
- package/dist/strategies.mjs +904 -112
- package/package.json +7 -7
- package/dist/chunk-2JKX3DM7.mjs +0 -619
- package/dist/chunk-JRRG3YQG.mjs +0 -154
|
@@ -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
|
+
};
|