@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/dist/cli.js CHANGED
@@ -1361,11 +1361,11 @@ var require_lodash = __commonJS({
1361
1361
  return isFunction(object[key]);
1362
1362
  });
1363
1363
  }
1364
- function baseGet(object, path3) {
1365
- path3 = castPath(path3, object);
1366
- var index = 0, length = path3.length;
1364
+ function baseGet(object, path4) {
1365
+ path4 = castPath(path4, object);
1366
+ var index = 0, length = path4.length;
1367
1367
  while (object != null && index < length) {
1368
- object = object[toKey(path3[index++])];
1368
+ object = object[toKey(path4[index++])];
1369
1369
  }
1370
1370
  return index && index == length ? object : undefined2;
1371
1371
  }
@@ -1429,10 +1429,10 @@ var require_lodash = __commonJS({
1429
1429
  });
1430
1430
  return accumulator;
1431
1431
  }
1432
- function baseInvoke(object, path3, args) {
1433
- path3 = castPath(path3, object);
1434
- object = parent(object, path3);
1435
- var func = object == null ? object : object[toKey(last(path3))];
1432
+ function baseInvoke(object, path4, args) {
1433
+ path4 = castPath(path4, object);
1434
+ object = parent(object, path4);
1435
+ var func = object == null ? object : object[toKey(last(path4))];
1436
1436
  return func == null ? undefined2 : apply(func, object, args);
1437
1437
  }
1438
1438
  function baseIsArguments(value) {
@@ -1588,13 +1588,13 @@ var require_lodash = __commonJS({
1588
1588
  return object === source || baseIsMatch(object, source, matchData);
1589
1589
  };
1590
1590
  }
1591
- function baseMatchesProperty(path3, srcValue) {
1592
- if (isKey(path3) && isStrictComparable(srcValue)) {
1593
- return matchesStrictComparable(toKey(path3), srcValue);
1591
+ function baseMatchesProperty(path4, srcValue) {
1592
+ if (isKey(path4) && isStrictComparable(srcValue)) {
1593
+ return matchesStrictComparable(toKey(path4), srcValue);
1594
1594
  }
1595
1595
  return function(object) {
1596
- var objValue = get(object, path3);
1597
- return objValue === undefined2 && objValue === srcValue ? hasIn(object, path3) : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
1596
+ var objValue = get(object, path4);
1597
+ return objValue === undefined2 && objValue === srcValue ? hasIn(object, path4) : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
1598
1598
  };
1599
1599
  }
1600
1600
  function baseMerge(object, source, srcIndex, customizer, stack) {
@@ -1691,23 +1691,23 @@ var require_lodash = __commonJS({
1691
1691
  });
1692
1692
  }
1693
1693
  function basePick(object, paths) {
1694
- return basePickBy(object, paths, function(value, path3) {
1695
- return hasIn(object, path3);
1694
+ return basePickBy(object, paths, function(value, path4) {
1695
+ return hasIn(object, path4);
1696
1696
  });
1697
1697
  }
1698
1698
  function basePickBy(object, paths, predicate) {
1699
1699
  var index = -1, length = paths.length, result2 = {};
1700
1700
  while (++index < length) {
1701
- var path3 = paths[index], value = baseGet(object, path3);
1702
- if (predicate(value, path3)) {
1703
- baseSet(result2, castPath(path3, object), value);
1701
+ var path4 = paths[index], value = baseGet(object, path4);
1702
+ if (predicate(value, path4)) {
1703
+ baseSet(result2, castPath(path4, object), value);
1704
1704
  }
1705
1705
  }
1706
1706
  return result2;
1707
1707
  }
1708
- function basePropertyDeep(path3) {
1708
+ function basePropertyDeep(path4) {
1709
1709
  return function(object) {
1710
- return baseGet(object, path3);
1710
+ return baseGet(object, path4);
1711
1711
  };
1712
1712
  }
1713
1713
  function basePullAll(array, values2, iteratee2, comparator) {
@@ -1781,14 +1781,14 @@ var require_lodash = __commonJS({
1781
1781
  var array = values(collection);
1782
1782
  return shuffleSelf(array, baseClamp(n, 0, array.length));
1783
1783
  }
1784
- function baseSet(object, path3, value, customizer) {
1784
+ function baseSet(object, path4, value, customizer) {
1785
1785
  if (!isObject(object)) {
1786
1786
  return object;
1787
1787
  }
1788
- path3 = castPath(path3, object);
1789
- var index = -1, length = path3.length, lastIndex = length - 1, nested = object;
1788
+ path4 = castPath(path4, object);
1789
+ var index = -1, length = path4.length, lastIndex = length - 1, nested = object;
1790
1790
  while (nested != null && ++index < length) {
1791
- var key = toKey(path3[index]), newValue = value;
1791
+ var key = toKey(path4[index]), newValue = value;
1792
1792
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
1793
1793
  return object;
1794
1794
  }
@@ -1796,7 +1796,7 @@ var require_lodash = __commonJS({
1796
1796
  var objValue = nested[key];
1797
1797
  newValue = customizer ? customizer(objValue, key, nested) : undefined2;
1798
1798
  if (newValue === undefined2) {
1799
- newValue = isObject(objValue) ? objValue : isIndex(path3[index + 1]) ? [] : {};
1799
+ newValue = isObject(objValue) ? objValue : isIndex(path4[index + 1]) ? [] : {};
1800
1800
  }
1801
1801
  }
1802
1802
  assignValue(nested, key, newValue);
@@ -1962,14 +1962,14 @@ var require_lodash = __commonJS({
1962
1962
  }
1963
1963
  return result2;
1964
1964
  }
1965
- function baseUnset(object, path3) {
1966
- path3 = castPath(path3, object);
1967
- var index = -1, length = path3.length;
1965
+ function baseUnset(object, path4) {
1966
+ path4 = castPath(path4, object);
1967
+ var index = -1, length = path4.length;
1968
1968
  if (!length) {
1969
1969
  return true;
1970
1970
  }
1971
1971
  while (++index < length) {
1972
- var key = toKey(path3[index]);
1972
+ var key = toKey(path4[index]);
1973
1973
  if (key === "__proto__" && !hasOwnProperty.call(object, "__proto__")) {
1974
1974
  return false;
1975
1975
  }
@@ -1977,11 +1977,11 @@ var require_lodash = __commonJS({
1977
1977
  return false;
1978
1978
  }
1979
1979
  }
1980
- var obj = parent(object, path3);
1981
- return obj == null || delete obj[toKey(last(path3))];
1980
+ var obj = parent(object, path4);
1981
+ return obj == null || delete obj[toKey(last(path4))];
1982
1982
  }
1983
- function baseUpdate(object, path3, updater, customizer) {
1984
- return baseSet(object, path3, updater(baseGet(object, path3)), customizer);
1983
+ function baseUpdate(object, path4, updater, customizer) {
1984
+ return baseSet(object, path4, updater(baseGet(object, path4)), customizer);
1985
1985
  }
1986
1986
  function baseWhile(array, predicate, isDrop, fromRight) {
1987
1987
  var length = array.length, index = fromRight ? length : -1;
@@ -2864,11 +2864,11 @@ var require_lodash = __commonJS({
2864
2864
  var match = source.match(reWrapDetails);
2865
2865
  return match ? match[1].split(reSplitDetails) : [];
2866
2866
  }
2867
- function hasPath(object, path3, hasFunc) {
2868
- path3 = castPath(path3, object);
2869
- var index = -1, length = path3.length, result2 = false;
2867
+ function hasPath(object, path4, hasFunc) {
2868
+ path4 = castPath(path4, object);
2869
+ var index = -1, length = path4.length, result2 = false;
2870
2870
  while (++index < length) {
2871
- var key = toKey(path3[index]);
2871
+ var key = toKey(path4[index]);
2872
2872
  if (!(result2 = object != null && hasFunc(object, key))) {
2873
2873
  break;
2874
2874
  }
@@ -3070,8 +3070,8 @@ var require_lodash = __commonJS({
3070
3070
  return apply(func, this, otherArgs);
3071
3071
  };
3072
3072
  }
3073
- function parent(object, path3) {
3074
- return path3.length < 2 ? object : baseGet(object, baseSlice(path3, 0, -1));
3073
+ function parent(object, path4) {
3074
+ return path4.length < 2 ? object : baseGet(object, baseSlice(path4, 0, -1));
3075
3075
  }
3076
3076
  function reorder(array, indexes) {
3077
3077
  var arrLength = array.length, length = nativeMin(indexes.length, arrLength), oldArray = copyArray(array);
@@ -3706,10 +3706,10 @@ var require_lodash = __commonJS({
3706
3706
  }
3707
3707
  return isString(collection) ? fromIndex <= length && collection.indexOf(value, fromIndex) > -1 : !!length && baseIndexOf(collection, value, fromIndex) > -1;
3708
3708
  }
3709
- var invokeMap = baseRest(function(collection, path3, args) {
3710
- var index = -1, isFunc = typeof path3 == "function", result2 = isArrayLike(collection) ? Array2(collection.length) : [];
3709
+ var invokeMap = baseRest(function(collection, path4, args) {
3710
+ var index = -1, isFunc = typeof path4 == "function", result2 = isArrayLike(collection) ? Array2(collection.length) : [];
3711
3711
  baseEach(collection, function(value) {
3712
- result2[++index] = isFunc ? apply(path3, value, args) : baseInvoke(value, path3, args);
3712
+ result2[++index] = isFunc ? apply(path4, value, args) : baseInvoke(value, path4, args);
3713
3713
  });
3714
3714
  return result2;
3715
3715
  });
@@ -4361,15 +4361,15 @@ var require_lodash = __commonJS({
4361
4361
  function functionsIn(object) {
4362
4362
  return object == null ? [] : baseFunctions(object, keysIn(object));
4363
4363
  }
4364
- function get(object, path3, defaultValue) {
4365
- var result2 = object == null ? undefined2 : baseGet(object, path3);
4364
+ function get(object, path4, defaultValue) {
4365
+ var result2 = object == null ? undefined2 : baseGet(object, path4);
4366
4366
  return result2 === undefined2 ? defaultValue : result2;
4367
4367
  }
4368
- function has(object, path3) {
4369
- return object != null && hasPath(object, path3, baseHas);
4368
+ function has(object, path4) {
4369
+ return object != null && hasPath(object, path4, baseHas);
4370
4370
  }
4371
- function hasIn(object, path3) {
4372
- return object != null && hasPath(object, path3, baseHasIn);
4371
+ function hasIn(object, path4) {
4372
+ return object != null && hasPath(object, path4, baseHasIn);
4373
4373
  }
4374
4374
  var invert = createInverter(function(result2, value, key) {
4375
4375
  if (value != null && typeof value.toString != "function") {
@@ -4422,10 +4422,10 @@ var require_lodash = __commonJS({
4422
4422
  return result2;
4423
4423
  }
4424
4424
  var isDeep = false;
4425
- paths = arrayMap(paths, function(path3) {
4426
- path3 = castPath(path3, object);
4427
- isDeep || (isDeep = path3.length > 1);
4428
- return path3;
4425
+ paths = arrayMap(paths, function(path4) {
4426
+ path4 = castPath(path4, object);
4427
+ isDeep || (isDeep = path4.length > 1);
4428
+ return path4;
4429
4429
  });
4430
4430
  copyObject(object, getAllKeysIn(object), result2);
4431
4431
  if (isDeep) {
@@ -4451,19 +4451,19 @@ var require_lodash = __commonJS({
4451
4451
  return [prop];
4452
4452
  });
4453
4453
  predicate = getIteratee(predicate);
4454
- return basePickBy(object, props, function(value, path3) {
4455
- return predicate(value, path3[0]);
4454
+ return basePickBy(object, props, function(value, path4) {
4455
+ return predicate(value, path4[0]);
4456
4456
  });
4457
4457
  }
4458
- function result(object, path3, defaultValue) {
4459
- path3 = castPath(path3, object);
4460
- var index = -1, length = path3.length;
4458
+ function result(object, path4, defaultValue) {
4459
+ path4 = castPath(path4, object);
4460
+ var index = -1, length = path4.length;
4461
4461
  if (!length) {
4462
4462
  length = 1;
4463
4463
  object = undefined2;
4464
4464
  }
4465
4465
  while (++index < length) {
4466
- var value = object == null ? undefined2 : object[toKey(path3[index])];
4466
+ var value = object == null ? undefined2 : object[toKey(path4[index])];
4467
4467
  if (value === undefined2) {
4468
4468
  index = length;
4469
4469
  value = defaultValue;
@@ -4472,12 +4472,12 @@ var require_lodash = __commonJS({
4472
4472
  }
4473
4473
  return object;
4474
4474
  }
4475
- function set(object, path3, value) {
4476
- return object == null ? object : baseSet(object, path3, value);
4475
+ function set(object, path4, value) {
4476
+ return object == null ? object : baseSet(object, path4, value);
4477
4477
  }
4478
- function setWith(object, path3, value, customizer) {
4478
+ function setWith(object, path4, value, customizer) {
4479
4479
  customizer = typeof customizer == "function" ? customizer : undefined2;
4480
- return object == null ? object : baseSet(object, path3, value, customizer);
4480
+ return object == null ? object : baseSet(object, path4, value, customizer);
4481
4481
  }
4482
4482
  var toPairs = createToPairs(keys);
4483
4483
  var toPairsIn = createToPairs(keysIn);
@@ -4499,15 +4499,15 @@ var require_lodash = __commonJS({
4499
4499
  });
4500
4500
  return accumulator;
4501
4501
  }
4502
- function unset(object, path3) {
4503
- return object == null ? true : baseUnset(object, path3);
4502
+ function unset(object, path4) {
4503
+ return object == null ? true : baseUnset(object, path4);
4504
4504
  }
4505
- function update2(object, path3, updater) {
4506
- return object == null ? object : baseUpdate(object, path3, castFunction(updater));
4505
+ function update2(object, path4, updater) {
4506
+ return object == null ? object : baseUpdate(object, path4, castFunction(updater));
4507
4507
  }
4508
- function updateWith(object, path3, updater, customizer) {
4508
+ function updateWith(object, path4, updater, customizer) {
4509
4509
  customizer = typeof customizer == "function" ? customizer : undefined2;
4510
- return object == null ? object : baseUpdate(object, path3, castFunction(updater), customizer);
4510
+ return object == null ? object : baseUpdate(object, path4, castFunction(updater), customizer);
4511
4511
  }
4512
4512
  function values(object) {
4513
4513
  return object == null ? [] : baseValues(object, keys(object));
@@ -4893,17 +4893,17 @@ var require_lodash = __commonJS({
4893
4893
  function matches(source) {
4894
4894
  return baseMatches(baseClone(source, CLONE_DEEP_FLAG));
4895
4895
  }
4896
- function matchesProperty(path3, srcValue) {
4897
- return baseMatchesProperty(path3, baseClone(srcValue, CLONE_DEEP_FLAG));
4896
+ function matchesProperty(path4, srcValue) {
4897
+ return baseMatchesProperty(path4, baseClone(srcValue, CLONE_DEEP_FLAG));
4898
4898
  }
4899
- var method = baseRest(function(path3, args) {
4899
+ var method = baseRest(function(path4, args) {
4900
4900
  return function(object) {
4901
- return baseInvoke(object, path3, args);
4901
+ return baseInvoke(object, path4, args);
4902
4902
  };
4903
4903
  });
4904
4904
  var methodOf = baseRest(function(object, args) {
4905
- return function(path3) {
4906
- return baseInvoke(object, path3, args);
4905
+ return function(path4) {
4906
+ return baseInvoke(object, path4, args);
4907
4907
  };
4908
4908
  });
4909
4909
  function mixin(object, source, options) {
@@ -4950,12 +4950,12 @@ var require_lodash = __commonJS({
4950
4950
  var over = createOver(arrayMap);
4951
4951
  var overEvery = createOver(arrayEvery);
4952
4952
  var overSome = createOver(arraySome);
4953
- function property(path3) {
4954
- return isKey(path3) ? baseProperty(toKey(path3)) : basePropertyDeep(path3);
4953
+ function property(path4) {
4954
+ return isKey(path4) ? baseProperty(toKey(path4)) : basePropertyDeep(path4);
4955
4955
  }
4956
4956
  function propertyOf(object) {
4957
- return function(path3) {
4958
- return object == null ? undefined2 : baseGet(object, path3);
4957
+ return function(path4) {
4958
+ return object == null ? undefined2 : baseGet(object, path4);
4959
4959
  };
4960
4960
  }
4961
4961
  var range = createRange();
@@ -5408,12 +5408,12 @@ var require_lodash = __commonJS({
5408
5408
  LazyWrapper.prototype.findLast = function(predicate) {
5409
5409
  return this.reverse().find(predicate);
5410
5410
  };
5411
- LazyWrapper.prototype.invokeMap = baseRest(function(path3, args) {
5412
- if (typeof path3 == "function") {
5411
+ LazyWrapper.prototype.invokeMap = baseRest(function(path4, args) {
5412
+ if (typeof path4 == "function") {
5413
5413
  return new LazyWrapper(this);
5414
5414
  }
5415
5415
  return this.map(function(value) {
5416
- return baseInvoke(value, path3, args);
5416
+ return baseInvoke(value, path4, args);
5417
5417
  });
5418
5418
  });
5419
5419
  LazyWrapper.prototype.reject = function(predicate) {
@@ -5535,9 +5535,12 @@ __export(cli_exports, {
5535
5535
  cleanFiles: () => cleanFiles,
5536
5536
  cleanRedis: () => cleanRedis,
5537
5537
  drawStatInCLI: () => drawStatInCLI,
5538
+ formatRuntimeCloseNotification: () => formatRuntimeCloseNotification,
5538
5539
  getTickers: () => getTickers,
5539
5540
  loadTradejsConfig: () => loadTradejsConfig,
5540
5541
  makeScreenshots: () => makeScreenshots,
5542
+ sendDocumentToTG: () => sendDocumentToTG,
5543
+ sendRuntimeCloseNotificationsToTG: () => sendRuntimeCloseNotificationsToTG,
5541
5544
  sendTextToTG: () => sendTextToTG,
5542
5545
  sendToAI: () => sendToAI,
5543
5546
  sendToTG: () => sendToTG,
@@ -5545,6 +5548,7 @@ __export(cli_exports, {
5545
5548
  });
5546
5549
  module.exports = __toCommonJS(cli_exports);
5547
5550
  var import_promises2 = __toESM(require("fs/promises"));
5551
+ var import_path3 = __toESM(require("path"));
5548
5552
  var import_lodash = __toESM(require_lodash());
5549
5553
  var import_progress = __toESM(require("progress"));
5550
5554
  var import_chalk = __toESM(require("chalk"));
@@ -5573,6 +5577,7 @@ var SCREENSHOT_CONCURRENCY_LIMIT = NODE_ENV === "production" ? 1 : 2;
5573
5577
  var import_files = require("@tradejs/infra/files");
5574
5578
  var import_redis3 = require("@tradejs/infra/redis");
5575
5579
  var import_logger5 = require("@tradejs/infra/logger");
5580
+ var import_timescale = require("@tradejs/infra/timescale");
5576
5581
 
5577
5582
  // src/ai.ts
5578
5583
  var import_aiLanguages = require("@tradejs/infra/aiLanguages");
@@ -5581,6 +5586,10 @@ var import_userSettings = require("@tradejs/infra/userSettings");
5581
5586
 
5582
5587
  // src/aiShared.ts
5583
5588
  var MAX_AI_SERIES_POINTS = 5;
5589
+ var COMPACT_INDICATORS_SNAPSHOT_SYMBOL = /* @__PURE__ */ Symbol.for(
5590
+ "tradejs.indicators.compactSnapshot"
5591
+ );
5592
+ var COMPACT_INDICATORS_SNAPSHOT_KEY = "__tradejsCompactIndicatorsSnapshot";
5584
5593
  var trimSeriesDeep = (value) => {
5585
5594
  if (Array.isArray(value)) {
5586
5595
  const trimmed = value.slice(-MAX_AI_SERIES_POINTS);
@@ -5602,13 +5611,15 @@ var trimSeriesDeep = (value) => {
5602
5611
  }
5603
5612
  return value;
5604
5613
  };
5614
+ var buildCompactAiIndicatorsSnapshot = (value) => {
5615
+ const compactSnapshot = value && typeof value === "object" ? value[COMPACT_INDICATORS_SNAPSHOT_SYMBOL] ?? value[COMPACT_INDICATORS_SNAPSHOT_KEY] : void 0;
5616
+ if (typeof compactSnapshot === "function") {
5617
+ return compactSnapshot({ limit: MAX_AI_SERIES_POINTS });
5618
+ }
5619
+ return trimSeriesDeep(value);
5620
+ };
5605
5621
 
5606
5622
  // src/aiMarketContext.ts
5607
- var SESSION_WINDOWS = [
5608
- { name: "asia", startMinuteUtc: 0, endMinuteUtc: 8 * 60 },
5609
- { name: "europe", startMinuteUtc: 7 * 60, endMinuteUtc: 16 * 60 },
5610
- { name: "us", startMinuteUtc: 13 * 60, endMinuteUtc: 22 * 60 }
5611
- ];
5612
5623
  var toRecord = (value) => {
5613
5624
  if (!value || typeof value !== "object" || Array.isArray(value)) {
5614
5625
  return null;
@@ -5619,92 +5630,485 @@ var toFiniteNumber = (value) => {
5619
5630
  const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
5620
5631
  return Number.isFinite(numeric) ? numeric : null;
5621
5632
  };
5622
- var getLastFiniteNumber = (value) => {
5623
- const numeric = toFiniteNumber(value);
5624
- if (numeric != null) {
5625
- return numeric;
5626
- }
5627
- if (!Array.isArray(value)) {
5628
- return null;
5629
- }
5630
- for (let i = value.length - 1; i >= 0; i -= 1) {
5631
- const nested = getLastFiniteNumber(value[i]);
5632
- if (nested != null) {
5633
- return nested;
5634
- }
5635
- }
5636
- return null;
5637
- };
5638
5633
  var roundTo = (value, decimals) => {
5639
5634
  const factor = 10 ** decimals;
5640
5635
  return Math.round(value * factor) / factor;
5641
5636
  };
5642
- var isInsideSession = (minuteUtc, startMinuteUtc, endMinuteUtc) => startMinuteUtc <= endMinuteUtc ? minuteUtc >= startMinuteUtc && minuteUtc < endMinuteUtc : minuteUtc >= startMinuteUtc || minuteUtc < endMinuteUtc;
5643
- var buildTradingSessionContext = (timestamp) => {
5644
- const date = new Date(timestamp);
5645
- const utcHour = date.getUTCHours();
5646
- const utcMinute = date.getUTCMinutes();
5647
- const minuteUtc = utcHour * 60 + utcMinute;
5648
- const activeSessions = SESSION_WINDOWS.filter(
5649
- (session) => isInsideSession(minuteUtc, session.startMinuteUtc, session.endMinuteUtc)
5650
- ).map((session) => session.name);
5651
- const primarySession = activeSessions.includes("us") ? "us" : activeSessions.includes("europe") ? "europe" : activeSessions.includes("asia") ? "asia" : "off_hours";
5652
- return {
5653
- timezone: "UTC",
5654
- utcHour,
5655
- utcMinute,
5656
- primarySession,
5657
- activeSessions,
5658
- isOverlap: activeSessions.length > 1,
5659
- overlap: activeSessions.length > 1 ? `${activeSessions.join("_")}_overlap` : null
5660
- };
5661
- };
5662
5637
  var buildMissingSpreadContext = () => ({
5663
- source: "binance_coinbase_btc",
5664
- indicatorKey: "payload.indicators.spread",
5638
+ source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
5639
+ indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
5665
5640
  available: false,
5666
5641
  value: null,
5642
+ zScore: null,
5667
5643
  bps: null,
5668
5644
  absBps: null,
5669
5645
  bias: null,
5670
5646
  severity: null
5671
5647
  });
5672
- var buildSpreadContextFromValue = (spread) => {
5648
+ var buildSpreadContextFromSignal = (signal) => {
5649
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5650
+ const relative = toRecord(baseContext?.relative);
5651
+ const execution = toRecord(relative?.execution);
5652
+ const spread = toFiniteNumber(execution?.venueSpread);
5653
+ const zScore = toFiniteNumber(execution?.venueSpreadZScore);
5654
+ if (spread == null) {
5655
+ return buildMissingSpreadContext();
5656
+ }
5673
5657
  const value = roundTo(spread, 8);
5674
5658
  const bps = roundTo(value * 1e4, 2);
5675
5659
  const absBps = Math.abs(bps);
5676
- const bias = Math.abs(bps) < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
5660
+ const bias = absBps < 1 ? "flat" : bps > 0 ? "coinbase_premium" : "binance_premium";
5677
5661
  const severity = absBps >= 20 ? "wide" : absBps >= 5 ? "elevated" : "normal";
5678
5662
  return {
5679
- source: "binance_coinbase_btc",
5680
- indicatorKey: "payload.indicators.spread",
5663
+ source: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
5664
+ indicatorKey: "payload.additionalIndicators.baseContext.relative.execution.venueSpread",
5681
5665
  available: true,
5682
5666
  value,
5667
+ zScore,
5683
5668
  bps,
5684
5669
  absBps,
5685
5670
  bias,
5686
5671
  severity
5687
5672
  };
5688
5673
  };
5689
- var readSpreadFromSignal = (signal) => {
5690
- const indicatorSpread = getLastFiniteNumber(signal.indicators?.spread);
5691
- if (indicatorSpread != null) {
5692
- return indicatorSpread;
5674
+ var buildTrueDeltaContextFromSignal = (signal) => {
5675
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5676
+ const participation = toRecord(baseContext?.participation);
5677
+ const delta = toRecord(participation?.delta);
5678
+ const source = String(delta?.source ?? "");
5679
+ const isTrueDeltaSource = source === "kline_taker_volume" || source === "agg_trades" || source === "trades";
5680
+ if (!delta || !isTrueDeltaSource) {
5681
+ return {
5682
+ source: source || null,
5683
+ available: false,
5684
+ buyPressurePct: null,
5685
+ buyVolume: null,
5686
+ sellVolume: null,
5687
+ netDelta: null,
5688
+ deltaPct: null,
5689
+ signedVolumeZScore: null
5690
+ };
5693
5691
  }
5694
- return getLastFiniteNumber(signal.additionalIndicators?.spread);
5692
+ return {
5693
+ source,
5694
+ available: true,
5695
+ buyPressurePct: toFiniteNumber(delta.buyPressurePct),
5696
+ buyVolume: toFiniteNumber(delta.buyVolume),
5697
+ sellVolume: toFiniteNumber(delta.sellVolume),
5698
+ netDelta: toFiniteNumber(delta.netDelta),
5699
+ deltaPct: toFiniteNumber(delta.deltaPct),
5700
+ signedVolumeZScore: toFiniteNumber(delta.signedVolumeZScore)
5701
+ };
5695
5702
  };
5696
- var buildAiMarketContext = (signal) => {
5697
- const existingMarketContext = toRecord(
5698
- signal.additionalIndicators?.marketContext
5699
- );
5700
- const existingSpread = toRecord(existingMarketContext?.binanceCoinbaseSpread);
5701
- const spread = readSpreadFromSignal(signal);
5703
+ var buildTradeFlowContextFromSignal = (signal) => {
5704
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5705
+ const participation = toRecord(baseContext?.participation);
5706
+ const tradeFlow = toRecord(participation?.tradeFlow);
5707
+ if (!tradeFlow) {
5708
+ return {
5709
+ source: null,
5710
+ available: false,
5711
+ interval: null,
5712
+ stale: null,
5713
+ trades: null,
5714
+ buyPressurePct: null,
5715
+ netBaseDelta: null,
5716
+ netQuoteDelta: null
5717
+ };
5718
+ }
5719
+ return {
5720
+ source: String(tradeFlow.source ?? ""),
5721
+ available: true,
5722
+ interval: String(tradeFlow.interval ?? ""),
5723
+ stale: typeof tradeFlow.stale === "boolean" ? tradeFlow.stale : null,
5724
+ trades: toFiniteNumber(tradeFlow.trades),
5725
+ buyPressurePct: toFiniteNumber(tradeFlow.buyPressurePct),
5726
+ netBaseDelta: toFiniteNumber(tradeFlow.netBaseDelta),
5727
+ netQuoteDelta: toFiniteNumber(tradeFlow.netQuoteDelta)
5728
+ };
5729
+ };
5730
+ var buildMarketBreadthContextFromSignal = (signal) => {
5731
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5732
+ const relative = toRecord(baseContext?.relative);
5733
+ const breadth = toRecord(relative?.marketBreadth);
5734
+ if (!breadth) {
5735
+ return {
5736
+ source: null,
5737
+ available: false,
5738
+ universe: null,
5739
+ interval: null,
5740
+ stale: null,
5741
+ symbolsCount: null,
5742
+ advanceDeclineRatio: null,
5743
+ pctAboveMa20: null,
5744
+ pctAboveMa50: null,
5745
+ equalWeightedReturn: null,
5746
+ volumeWeightedReturn: null,
5747
+ dispersion: null
5748
+ };
5749
+ }
5750
+ return {
5751
+ source: String(breadth.source ?? ""),
5752
+ available: true,
5753
+ universe: String(breadth.universe ?? ""),
5754
+ interval: String(breadth.interval ?? ""),
5755
+ stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
5756
+ symbolsCount: toFiniteNumber(breadth.symbolsCount),
5757
+ advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
5758
+ pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
5759
+ pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
5760
+ equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
5761
+ volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
5762
+ dispersion: toFiniteNumber(breadth.dispersion)
5763
+ };
5764
+ };
5765
+ var buildTargetVsBtcContextFromSignal = (signal) => {
5766
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5767
+ const relative = toRecord(baseContext?.relative);
5768
+ const targetVsBtc = toRecord(relative?.targetVsBtc);
5769
+ if (!targetVsBtc) {
5770
+ return {
5771
+ source: null,
5772
+ available: false,
5773
+ ratioReturn1h: null,
5774
+ ratioReturn4h: null,
5775
+ ratioReturn24h: null,
5776
+ alphaVsBtc1h: null,
5777
+ alphaVsBtc4h: null,
5778
+ alphaVsBtc24h: null,
5779
+ betaToBtc20: null,
5780
+ correlationToBtc20: null,
5781
+ ratioTrend: null
5782
+ };
5783
+ }
5784
+ return {
5785
+ source: String(targetVsBtc.source ?? ""),
5786
+ available: true,
5787
+ ratioReturn1h: toFiniteNumber(targetVsBtc.ratioReturn1h),
5788
+ ratioReturn4h: toFiniteNumber(targetVsBtc.ratioReturn4h),
5789
+ ratioReturn24h: toFiniteNumber(targetVsBtc.ratioReturn24h),
5790
+ alphaVsBtc1h: toFiniteNumber(targetVsBtc.alphaVsBtc1h),
5791
+ alphaVsBtc4h: toFiniteNumber(targetVsBtc.alphaVsBtc4h),
5792
+ alphaVsBtc24h: toFiniteNumber(targetVsBtc.alphaVsBtc24h),
5793
+ betaToBtc20: toFiniteNumber(targetVsBtc.betaToBtc20),
5794
+ correlationToBtc20: toFiniteNumber(targetVsBtc.correlationToBtc20),
5795
+ ratioTrend: typeof targetVsBtc.ratioTrend === "string" ? targetVsBtc.ratioTrend : null
5796
+ };
5797
+ };
5798
+ var buildBtcAltRegimeContextFromSignal = (signal) => {
5799
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5800
+ const relative = toRecord(baseContext?.relative);
5801
+ const btcAltRegime = toRecord(relative?.btcAltRegime);
5802
+ if (!btcAltRegime) {
5803
+ return {
5804
+ source: null,
5805
+ available: false,
5806
+ universe: null,
5807
+ interval: null,
5808
+ stale: null,
5809
+ regime: null,
5810
+ btcReturn24h: null,
5811
+ altBasketReturn24h: null,
5812
+ btcVsAltReturn24h: null,
5813
+ btcTurnoverShare24h: null,
5814
+ btcTurnoverShareChange24h: null,
5815
+ altVolToBtcVol24h: null,
5816
+ altDispersion24h: null
5817
+ };
5818
+ }
5819
+ return {
5820
+ source: String(btcAltRegime.source ?? ""),
5821
+ available: true,
5822
+ universe: String(btcAltRegime.universe ?? ""),
5823
+ interval: String(btcAltRegime.interval ?? ""),
5824
+ stale: typeof btcAltRegime.stale === "boolean" ? btcAltRegime.stale : null,
5825
+ regime: typeof btcAltRegime.regime === "string" ? btcAltRegime.regime : null,
5826
+ btcReturn24h: toFiniteNumber(btcAltRegime.btcReturn24h),
5827
+ altBasketReturn24h: toFiniteNumber(btcAltRegime.altBasketReturn24h),
5828
+ btcVsAltReturn24h: toFiniteNumber(btcAltRegime.btcVsAltReturn24h),
5829
+ btcTurnoverShare24h: toFiniteNumber(btcAltRegime.btcTurnoverShare24h),
5830
+ btcTurnoverShareChange24h: toFiniteNumber(
5831
+ btcAltRegime.btcTurnoverShareChange24h
5832
+ ),
5833
+ altVolToBtcVol24h: toFiniteNumber(btcAltRegime.altVolToBtcVol24h),
5834
+ altDispersion24h: toFiniteNumber(btcAltRegime.altDispersion24h)
5835
+ };
5836
+ };
5837
+ var buildCmcGlobalContextFromSignal = (signal) => {
5838
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5839
+ const relative = toRecord(baseContext?.relative);
5840
+ const cmcGlobal = toRecord(relative?.cmcGlobal);
5841
+ if (!cmcGlobal) {
5842
+ return {
5843
+ source: null,
5844
+ available: false,
5845
+ interval: null,
5846
+ asOfTs: null,
5847
+ stale: null,
5848
+ totalMarketCapUsd: null,
5849
+ totalVolumeUsd: null,
5850
+ totalVolumeReportedUsd: null,
5851
+ altMarketCapUsd: null,
5852
+ altVolumeUsd: null,
5853
+ altVolumeReportedUsd: null,
5854
+ btcDominancePct: null,
5855
+ ethDominancePct: null,
5856
+ btcDominanceChange24hPct: null,
5857
+ ethDominanceChange24hPct: null,
5858
+ altMarketCapChange24hPct: null,
5859
+ altVolumeChange24hPct: null,
5860
+ activeCryptocurrencies: null,
5861
+ activeExchanges: null,
5862
+ activeMarketPairs: null,
5863
+ altLiquidityRegime: null
5864
+ };
5865
+ }
5866
+ return {
5867
+ source: String(cmcGlobal.source ?? ""),
5868
+ available: true,
5869
+ interval: typeof cmcGlobal.interval === "string" ? cmcGlobal.interval : null,
5870
+ asOfTs: toFiniteNumber(cmcGlobal.asOfTs),
5871
+ stale: typeof cmcGlobal.stale === "boolean" ? cmcGlobal.stale : null,
5872
+ totalMarketCapUsd: toFiniteNumber(cmcGlobal.totalMarketCapUsd),
5873
+ totalVolumeUsd: toFiniteNumber(cmcGlobal.totalVolumeUsd),
5874
+ totalVolumeReportedUsd: toFiniteNumber(cmcGlobal.totalVolumeReportedUsd),
5875
+ altMarketCapUsd: toFiniteNumber(cmcGlobal.altMarketCapUsd),
5876
+ altVolumeUsd: toFiniteNumber(cmcGlobal.altVolumeUsd),
5877
+ altVolumeReportedUsd: toFiniteNumber(cmcGlobal.altVolumeReportedUsd),
5878
+ btcDominancePct: toFiniteNumber(cmcGlobal.btcDominancePct),
5879
+ ethDominancePct: toFiniteNumber(cmcGlobal.ethDominancePct),
5880
+ btcDominanceChange24hPct: toFiniteNumber(
5881
+ cmcGlobal.btcDominanceChange24hPct
5882
+ ),
5883
+ ethDominanceChange24hPct: toFiniteNumber(
5884
+ cmcGlobal.ethDominanceChange24hPct
5885
+ ),
5886
+ altMarketCapChange24hPct: toFiniteNumber(
5887
+ cmcGlobal.altMarketCapChange24hPct
5888
+ ),
5889
+ altVolumeChange24hPct: toFiniteNumber(cmcGlobal.altVolumeChange24hPct),
5890
+ activeCryptocurrencies: toFiniteNumber(cmcGlobal.activeCryptocurrencies),
5891
+ activeExchanges: toFiniteNumber(cmcGlobal.activeExchanges),
5892
+ activeMarketPairs: toFiniteNumber(cmcGlobal.activeMarketPairs),
5893
+ altLiquidityRegime: typeof cmcGlobal.altLiquidityRegime === "string" ? cmcGlobal.altLiquidityRegime : null
5894
+ };
5895
+ };
5896
+ var buildCmcReferenceAssetsContextFromSignal = (signal) => {
5897
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5898
+ const relative = toRecord(baseContext?.relative);
5899
+ const cmcReferenceAssets = toRecord(relative?.cmcReferenceAssets);
5900
+ if (!cmcReferenceAssets) {
5901
+ return {
5902
+ source: null,
5903
+ available: false,
5904
+ interval: null,
5905
+ asOfTs: null,
5906
+ stale: null,
5907
+ btcMarketCapUsd: null,
5908
+ ethMarketCapUsd: null,
5909
+ btcVolumeUsd: null,
5910
+ ethVolumeUsd: null,
5911
+ btcVolumeToMarketCap: null,
5912
+ ethVolumeToMarketCap: null,
5913
+ ethBtcMarketCapRatio: null,
5914
+ ethBtcMarketCapRatioChange24hPct: null,
5915
+ ethVsBtcVolumeRatio: null,
5916
+ referenceLiquidityRegime: null
5917
+ };
5918
+ }
5919
+ return {
5920
+ source: String(cmcReferenceAssets.source ?? ""),
5921
+ available: true,
5922
+ interval: typeof cmcReferenceAssets.interval === "string" ? cmcReferenceAssets.interval : null,
5923
+ asOfTs: toFiniteNumber(cmcReferenceAssets.asOfTs),
5924
+ stale: typeof cmcReferenceAssets.stale === "boolean" ? cmcReferenceAssets.stale : null,
5925
+ btcMarketCapUsd: toFiniteNumber(cmcReferenceAssets.btcMarketCapUsd),
5926
+ ethMarketCapUsd: toFiniteNumber(cmcReferenceAssets.ethMarketCapUsd),
5927
+ btcVolumeUsd: toFiniteNumber(cmcReferenceAssets.btcVolumeUsd),
5928
+ ethVolumeUsd: toFiniteNumber(cmcReferenceAssets.ethVolumeUsd),
5929
+ btcVolumeToMarketCap: toFiniteNumber(
5930
+ cmcReferenceAssets.btcVolumeToMarketCap
5931
+ ),
5932
+ ethVolumeToMarketCap: toFiniteNumber(
5933
+ cmcReferenceAssets.ethVolumeToMarketCap
5934
+ ),
5935
+ ethBtcMarketCapRatio: toFiniteNumber(
5936
+ cmcReferenceAssets.ethBtcMarketCapRatio
5937
+ ),
5938
+ ethBtcMarketCapRatioChange24hPct: toFiniteNumber(
5939
+ cmcReferenceAssets.ethBtcMarketCapRatioChange24hPct
5940
+ ),
5941
+ ethVsBtcVolumeRatio: toFiniteNumber(cmcReferenceAssets.ethVsBtcVolumeRatio),
5942
+ referenceLiquidityRegime: typeof cmcReferenceAssets.referenceLiquidityRegime === "string" ? cmcReferenceAssets.referenceLiquidityRegime : null
5943
+ };
5944
+ };
5945
+ var buildCmcExchangeLiquidityContextFromSignal = (signal) => {
5946
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5947
+ const relative = toRecord(baseContext?.relative);
5948
+ const cmcExchangeLiquidity = toRecord(relative?.cmcExchangeLiquidity);
5949
+ if (!cmcExchangeLiquidity) {
5950
+ return {
5951
+ source: null,
5952
+ available: false,
5953
+ interval: null,
5954
+ asOfTs: null,
5955
+ stale: null,
5956
+ exchangesCount: null,
5957
+ totalVolumeUsd: null,
5958
+ totalVolumeChange24hPct: null,
5959
+ binanceVolumeUsd: null,
5960
+ binanceVolumeShare: null,
5961
+ topExchangeVolumeShare: null,
5962
+ liquidityRegime: null
5963
+ };
5964
+ }
5965
+ return {
5966
+ source: String(cmcExchangeLiquidity.source ?? ""),
5967
+ available: true,
5968
+ interval: typeof cmcExchangeLiquidity.interval === "string" ? cmcExchangeLiquidity.interval : null,
5969
+ asOfTs: toFiniteNumber(cmcExchangeLiquidity.asOfTs),
5970
+ stale: typeof cmcExchangeLiquidity.stale === "boolean" ? cmcExchangeLiquidity.stale : null,
5971
+ exchangesCount: toFiniteNumber(cmcExchangeLiquidity.exchangesCount),
5972
+ totalVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.totalVolumeUsd),
5973
+ totalVolumeChange24hPct: toFiniteNumber(
5974
+ cmcExchangeLiquidity.totalVolumeChange24hPct
5975
+ ),
5976
+ binanceVolumeUsd: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeUsd),
5977
+ binanceVolumeShare: toFiniteNumber(cmcExchangeLiquidity.binanceVolumeShare),
5978
+ topExchangeVolumeShare: toFiniteNumber(
5979
+ cmcExchangeLiquidity.topExchangeVolumeShare
5980
+ ),
5981
+ liquidityRegime: typeof cmcExchangeLiquidity.liquidityRegime === "string" ? cmcExchangeLiquidity.liquidityRegime : null
5982
+ };
5983
+ };
5984
+ var buildCmcFearGreedContextFromSignal = (signal) => {
5985
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5986
+ const relative = toRecord(baseContext?.relative);
5987
+ const cmcFearGreed = toRecord(relative?.cmcFearGreed);
5988
+ if (!cmcFearGreed) {
5989
+ return {
5990
+ source: null,
5991
+ available: false,
5992
+ interval: null,
5993
+ asOfTs: null,
5994
+ stale: null,
5995
+ value: null,
5996
+ valueChange24h: null,
5997
+ valueChange7d: null,
5998
+ classification: null,
5999
+ sentimentRegime: null
6000
+ };
6001
+ }
5702
6002
  return {
5703
- ...existingMarketContext ?? {},
5704
- tradingSession: buildTradingSessionContext(signal.timestamp),
5705
- binanceCoinbaseSpread: spread != null ? buildSpreadContextFromValue(spread) : existingSpread ?? buildMissingSpreadContext()
6003
+ source: String(cmcFearGreed.source ?? ""),
6004
+ available: true,
6005
+ interval: typeof cmcFearGreed.interval === "string" ? cmcFearGreed.interval : null,
6006
+ asOfTs: toFiniteNumber(cmcFearGreed.asOfTs),
6007
+ stale: typeof cmcFearGreed.stale === "boolean" ? cmcFearGreed.stale : null,
6008
+ value: toFiniteNumber(cmcFearGreed.value),
6009
+ valueChange24h: toFiniteNumber(cmcFearGreed.valueChange24h),
6010
+ valueChange7d: toFiniteNumber(cmcFearGreed.valueChange7d),
6011
+ classification: typeof cmcFearGreed.classification === "string" ? cmcFearGreed.classification : null,
6012
+ sentimentRegime: typeof cmcFearGreed.sentimentRegime === "string" ? cmcFearGreed.sentimentRegime : null
5706
6013
  };
5707
6014
  };
6015
+ var buildCmcIndexesContextFromSignal = (signal) => {
6016
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
6017
+ const relative = toRecord(baseContext?.relative);
6018
+ const cmcIndexes = toRecord(relative?.cmcIndexes);
6019
+ if (!cmcIndexes) {
6020
+ return {
6021
+ source: null,
6022
+ available: false,
6023
+ interval: null,
6024
+ asOfTs: null,
6025
+ stale: null,
6026
+ cmc100Value: null,
6027
+ cmc100Change24hPct: null,
6028
+ cmc100TopConstituentSymbol: null,
6029
+ cmc100TopConstituentWeightPct: null,
6030
+ cmc20Value: null,
6031
+ cmc20Change24hPct: null,
6032
+ cmc20TopConstituentSymbol: null,
6033
+ cmc20TopConstituentWeightPct: null,
6034
+ cmc20ToCmc100Ratio: null,
6035
+ cmc20ToCmc100RatioChange24hPct: null,
6036
+ indexRegime: null
6037
+ };
6038
+ }
6039
+ return {
6040
+ source: String(cmcIndexes.source ?? ""),
6041
+ available: true,
6042
+ interval: typeof cmcIndexes.interval === "string" ? cmcIndexes.interval : null,
6043
+ asOfTs: toFiniteNumber(cmcIndexes.asOfTs),
6044
+ stale: typeof cmcIndexes.stale === "boolean" ? cmcIndexes.stale : null,
6045
+ cmc100Value: toFiniteNumber(cmcIndexes.cmc100Value),
6046
+ cmc100Change24hPct: toFiniteNumber(cmcIndexes.cmc100Change24hPct),
6047
+ cmc100TopConstituentSymbol: typeof cmcIndexes.cmc100TopConstituentSymbol === "string" ? cmcIndexes.cmc100TopConstituentSymbol : null,
6048
+ cmc100TopConstituentWeightPct: toFiniteNumber(
6049
+ cmcIndexes.cmc100TopConstituentWeightPct
6050
+ ),
6051
+ cmc20Value: toFiniteNumber(cmcIndexes.cmc20Value),
6052
+ cmc20Change24hPct: toFiniteNumber(cmcIndexes.cmc20Change24hPct),
6053
+ cmc20TopConstituentSymbol: typeof cmcIndexes.cmc20TopConstituentSymbol === "string" ? cmcIndexes.cmc20TopConstituentSymbol : null,
6054
+ cmc20TopConstituentWeightPct: toFiniteNumber(
6055
+ cmcIndexes.cmc20TopConstituentWeightPct
6056
+ ),
6057
+ cmc20ToCmc100Ratio: toFiniteNumber(cmcIndexes.cmc20ToCmc100Ratio),
6058
+ cmc20ToCmc100RatioChange24hPct: toFiniteNumber(
6059
+ cmcIndexes.cmc20ToCmc100RatioChange24hPct
6060
+ ),
6061
+ indexRegime: typeof cmcIndexes.indexRegime === "string" ? cmcIndexes.indexRegime : null
6062
+ };
6063
+ };
6064
+ var buildReferenceTradeFlowContextFromSignal = (signal) => {
6065
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
6066
+ const relative = toRecord(baseContext?.relative);
6067
+ const refs = toRecord(relative?.referenceTradeFlow);
6068
+ const primaryReferenceSymbol = typeof refs?.primaryReferenceSymbol === "string" ? refs.primaryReferenceSymbol : null;
6069
+ const tradeFlowBySymbol = toRecord(refs?.tradeFlowBySymbol);
6070
+ const primaryTradeFlow = primaryReferenceSymbol != null ? toRecord(tradeFlowBySymbol?.[primaryReferenceSymbol]) : null;
6071
+ if (!refs) {
6072
+ return {
6073
+ source: null,
6074
+ available: false,
6075
+ primaryReferenceSymbol: null,
6076
+ referenceSymbols: [],
6077
+ primaryTradeFlowBuyPressurePct: null,
6078
+ primaryTradeFlowStale: null
6079
+ };
6080
+ }
6081
+ return {
6082
+ source: String(refs.source ?? ""),
6083
+ available: true,
6084
+ primaryReferenceSymbol,
6085
+ referenceSymbols: Array.isArray(refs.referenceSymbols) ? refs.referenceSymbols.map(String) : [],
6086
+ primaryTradeFlowBuyPressurePct: toFiniteNumber(
6087
+ primaryTradeFlow?.buyPressurePct
6088
+ ),
6089
+ primaryTradeFlowStale: typeof primaryTradeFlow?.stale === "boolean" ? primaryTradeFlow.stale : null
6090
+ };
6091
+ };
6092
+ var buildAiMarketContext = (signal) => ({
6093
+ execution: {
6094
+ binanceCoinbaseSpread: buildSpreadContextFromSignal(signal)
6095
+ },
6096
+ participation: {
6097
+ trueDelta: buildTrueDeltaContextFromSignal(signal),
6098
+ tradeFlow: buildTradeFlowContextFromSignal(signal)
6099
+ },
6100
+ relative: {
6101
+ marketBreadth: buildMarketBreadthContextFromSignal(signal),
6102
+ targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
6103
+ btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
6104
+ cmcGlobal: buildCmcGlobalContextFromSignal(signal),
6105
+ cmcReferenceAssets: buildCmcReferenceAssetsContextFromSignal(signal),
6106
+ cmcExchangeLiquidity: buildCmcExchangeLiquidityContextFromSignal(signal),
6107
+ cmcFearGreed: buildCmcFearGreedContextFromSignal(signal),
6108
+ cmcIndexes: buildCmcIndexesContextFromSignal(signal),
6109
+ referenceTradeFlow: buildReferenceTradeFlowContextFromSignal(signal)
6110
+ }
6111
+ });
5708
6112
 
5709
6113
  // src/strategy/manifests.ts
5710
6114
  var import_indicators = require("@tradejs/core/indicators");
@@ -5713,7 +6117,6 @@ var import_logger2 = require("@tradejs/infra/logger");
5713
6117
  // src/tradejsConfig.ts
5714
6118
  var import_fs = __toESM(require("fs"));
5715
6119
  var import_path = __toESM(require("path"));
5716
- var import_module = require("module");
5717
6120
  var import_url = require("url");
5718
6121
  var import_config = require("@tradejs/core/config");
5719
6122
  var import_logger = require("@tradejs/infra/logger");
@@ -5729,6 +6132,7 @@ var cachedByCwd = /* @__PURE__ */ new Map();
5729
6132
  var announcedConfigFile = /* @__PURE__ */ new Set();
5730
6133
  var tsNodeRegistered = false;
5731
6134
  var tsconfigPathsRegisteredByCwd = /* @__PURE__ */ new Set();
6135
+ var tsconfigPathMatchersByCwd = /* @__PURE__ */ new Map();
5732
6136
  var getTradejsProjectCwd = (cwd) => {
5733
6137
  const explicit = String(cwd ?? "").trim();
5734
6138
  if (explicit) {
@@ -5758,7 +6162,14 @@ var normalizeConfig = (rawConfig) => {
5758
6162
  ...hooks ? { hooks } : {}
5759
6163
  };
5760
6164
  };
5761
- var getRequireFn = (cwd = getTradejsProjectCwd()) => (0, import_module.createRequire)(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
6165
+ var getNodeCreateRequire = () => {
6166
+ const builtinModule = process.getBuiltinModule?.("module");
6167
+ if (typeof builtinModule?.createRequire === "function") {
6168
+ return builtinModule.createRequire;
6169
+ }
6170
+ throw new TypeError("module.createRequire is not available");
6171
+ };
6172
+ var getRequireFn = (cwd = getTradejsProjectCwd()) => getNodeCreateRequire()(import_path.default.join(import_path.default.resolve(cwd), "__tradejs_loader__.js"));
5762
6173
  var ensureTsNodeRegistered = async () => {
5763
6174
  if (tsNodeRegistered) {
5764
6175
  return;
@@ -5768,8 +6179,8 @@ var ensureTsNodeRegistered = async () => {
5768
6179
  tsNode.register?.({
5769
6180
  transpileOnly: true,
5770
6181
  compilerOptions: {
5771
- module: "commonjs",
5772
- moduleResolution: "node"
6182
+ module: "Node16",
6183
+ moduleResolution: "node16"
5773
6184
  }
5774
6185
  });
5775
6186
  tsNodeRegistered = true;
@@ -5796,6 +6207,42 @@ var ensureTsconfigPathsRegistered = async (cwd = getTradejsProjectCwd()) => {
5796
6207
  });
5797
6208
  tsconfigPathsRegisteredByCwd.add(projectRoot);
5798
6209
  };
6210
+ var resolveTsconfigPathModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
6211
+ const projectRoot = getTradejsProjectCwd(cwd);
6212
+ const cachedMatcher = tsconfigPathMatchersByCwd.get(projectRoot);
6213
+ if (cachedMatcher) {
6214
+ const resolved2 = cachedMatcher(moduleName);
6215
+ return resolved2 || null;
6216
+ }
6217
+ const tsconfigPathsModule = await import("tsconfig-paths");
6218
+ const loadConfig = tsconfigPathsModule.loadConfig;
6219
+ const createMatchPath = tsconfigPathsModule.createMatchPath;
6220
+ if (typeof loadConfig !== "function" || typeof createMatchPath !== "function") {
6221
+ return null;
6222
+ }
6223
+ const loadedConfig = loadConfig(projectRoot);
6224
+ if (loadedConfig.resultType !== "success") {
6225
+ return null;
6226
+ }
6227
+ const matchPath = createMatchPath(
6228
+ loadedConfig.absoluteBaseUrl,
6229
+ loadedConfig.paths
6230
+ );
6231
+ const matcher = (requestedModule) => matchPath(requestedModule, void 0, import_fs.default.existsSync, [
6232
+ ".ts",
6233
+ ".tsx",
6234
+ ".mts",
6235
+ ".cts",
6236
+ ".js",
6237
+ ".jsx",
6238
+ ".mjs",
6239
+ ".cjs",
6240
+ ".json"
6241
+ ]) || "";
6242
+ tsconfigPathMatchersByCwd.set(projectRoot, matcher);
6243
+ const resolved = matcher(moduleName);
6244
+ return resolved || null;
6245
+ };
5799
6246
  var toImportSpecifier = (moduleName) => {
5800
6247
  if (moduleName.startsWith("file://")) {
5801
6248
  return moduleName;
@@ -5854,7 +6301,18 @@ var importTradejsModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
5854
6301
  }
5855
6302
  if (isBareModuleSpecifier(normalized)) {
5856
6303
  await ensureTsconfigPathsRegistered(cwd);
5857
- return requireFn(normalized);
6304
+ try {
6305
+ return requireFn(normalized);
6306
+ } catch (error) {
6307
+ const resolvedByTsconfig = await resolveTsconfigPathModule(
6308
+ normalized,
6309
+ cwd
6310
+ );
6311
+ if (resolvedByTsconfig && resolvedByTsconfig !== normalized) {
6312
+ return requireFn(resolvedByTsconfig);
6313
+ }
6314
+ throw error;
6315
+ }
5858
6316
  }
5859
6317
  try {
5860
6318
  return await import(
@@ -6124,6 +6582,9 @@ var strategies = new Proxy(
6124
6582
  }
6125
6583
  );
6126
6584
 
6585
+ // src/strategy/policyProfiles.ts
6586
+ var getStrategyProfileAiAdapter = (manifest, profileId) => manifest?.policyProfiles?.find(({ id }) => id === profileId)?.aiAdapter ?? manifest?.aiAdapter;
6587
+
6127
6588
  // src/strategyAdapters/ai.ts
6128
6589
  var toRecord2 = (value) => {
6129
6590
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -6151,13 +6612,13 @@ var buildBaseAiPayload = (signal) => {
6151
6612
  }
6152
6613
  },
6153
6614
  figures: trimSeriesDeep(signal.figures ?? {}),
6154
- indicators: trimSeriesDeep(signal.indicators),
6615
+ indicators: buildCompactAiIndicatorsSnapshot(signal.indicators),
6155
6616
  additionalIndicators: trimSeriesDeep(additionalIndicators)
6156
6617
  };
6157
6618
  };
6158
6619
  var defaultAiAdapter = {};
6159
- var getStrategyAiAdapter = (strategy) => getStrategyManifest(strategy)?.aiAdapter ?? defaultAiAdapter;
6160
- var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy);
6620
+ var getStrategyAiAdapter = (strategy, profileId) => getStrategyProfileAiAdapter(getStrategyManifest(strategy), profileId) ?? defaultAiAdapter;
6621
+ var getSignalAiAdapter = (signal) => getStrategyAiAdapter(signal.strategy, signal.policyProfileId);
6161
6622
  var buildAiPayloadByStrategy = (signal) => {
6162
6623
  const basePayload = buildBaseAiPayload(signal);
6163
6624
  const adapter = getSignalAiAdapter(signal);
@@ -6290,24 +6751,44 @@ Input payload structure:
6290
6751
  - payload.figures:
6291
6752
  strategy-specific figures or geometry when available. Fields vary by strategy.
6292
6753
  - payload.indicators:
6293
- indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values.
6754
+ historical indicator dictionaries and series for the coin and BTC; all series are already trimmed to the latest 5 values. Treat this block as recent-history transport, not as the primary source of the current shared context.
6294
6755
  - payload.additionalIndicators:
6295
- strategy-specific summary/context fields. This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
6296
- Examples: helperFlags, structureContext, spread, correlation, volatilitySummary.
6756
+ strategy-specific summary/context fields plus the canonical current shared context snapshot.
6757
+ This is not noise; it contains derived fields deliberately passed by the strategy to help the decision.
6758
+ Examples: baseContext, helperFlags, structureContext, volatilitySummary.
6759
+ Always inspect \`payload.additionalIndicators.baseContext\` first for the current shared state:
6760
+ \u2022 \`baseContext.raw\`: current MA, ATR, BB, OBV, price stats, levels, BTC correlation.
6761
+ \u2022 \`baseContext.regime\`: derived trend / volatility / momentum / session regime fields.
6762
+ \u2022 \`baseContext.structure\`: local range position, breakout freshness/quality, level-touch counts, rejection wick context.
6763
+ \u2022 \`baseContext.participation\`: volume/turnover participation, effort-vs-result context, and Binance aggTrades trade-flow when available.
6764
+ \u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
6765
+ \u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
6766
+ \u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
6767
+ \u2022 \`baseContext.gateFeatures\`: direction-aware, normalized fields derived from baseContext; prefer \`setup\`, \`scores\`, \`confirmations\`, \`conflicts\`, \`risk\`, and \`decisionHints\` for quick gate checks before inspecting raw nested context.
6297
6768
  Always inspect \`payload.additionalIndicators.marketContext\` when present:
6298
- \u2022 \`marketContext.tradingSession\`: UTC session at signal time: asia / europe / us / overlap / off_hours.
6299
- \u2022 \`marketContext.binanceCoinbaseSpread\`: BTC spread between Coinbase and Binance from \`payload.indicators.spread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6769
+ \u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6770
+ \u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
6771
+ \u2022 \`marketContext.participation.tradeFlow\`: Binance aggTrades buy/sell pressure buckets when available.
6772
+ \u2022 \`marketContext.relative.marketBreadth\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the configured Binance breadth universe.
6773
+ \u2022 \`marketContext.relative.targetVsBtc\`: target/BTC ratio returns, alpha, beta, and short-window correlation; use it to decide whether the target is leading or lagging BTC in the signal direction.
6774
+ \u2022 \`marketContext.relative.btcAltRegime\`: Binance-derived BTC-vs-alt basket regime, BTC/alt 24h returns, BTC turnover share, and alt dispersion; use it as a broad alt-market risk pocket.
6775
+ \u2022 \`marketContext.relative.cmcGlobal\`: historical CoinMarketCap global market metrics: total/alt market cap, total/alt volume, BTC/ETH dominance and 24h changes, active markets, \`interval\`, and \`altLiquidityRegime\`.
6776
+ \u2022 \`marketContext.relative.cmcReferenceAssets\`: historical CoinMarketCap BTC/ETH market-cap and volume context, ETH/BTC market-cap ratio, ETH-vs-BTC volume ratio, \`interval\`, and \`referenceLiquidityRegime\`.
6777
+ \u2022 \`marketContext.relative.cmcExchangeLiquidity\`: historical CoinMarketCap major-exchange liquidity aggregate: total volume, 24h volume change, Binance share, concentration, and \`liquidityRegime\`.
6778
+ \u2022 \`marketContext.relative.cmcFearGreed\`: historical daily CoinMarketCap Fear & Greed sentiment index: value, classification, 24h/7d value changes, and \`sentimentRegime\`.
6779
+ \u2022 \`marketContext.relative.cmcIndexes\`: historical daily CoinMarketCap CMC100/CMC20 index values, 24h changes, top constituents, CMC20/CMC100 ratio, and \`indexRegime\`.
6780
+ \u2022 \`marketContext.relative.referenceTradeFlow\`: BTC/ETH reference trade-flow summary used for broad market pressure when the target symbol itself is not BTC/ETH.
6300
6781
  If those fields exist, use them as a more explicit hint instead of trying to re-derive the same idea from raw lines or points.
6301
- If \`derivativesContext\` exists, it is a derived Coinalyze summary for the time of the signal. Coinalyze context is built only from \`BTCUSDT\` and \`ETHUSDT\` reference symbols, not for every target coin. \`targetSymbol\` is just the source signal coin. Use BTC/ETH open interest, funding, liquidations, and pressure/riskFlags as positioning context, not as an independent trade idea.
6782
+ If \`baseContext.derivatives\` exists, its top-level \`summary\` and \`intervals\` are the primary BTCUSDT Coinalyze benchmark context for the time of the signal. \`secondaryReferenceSymbol\` identifies the ETHUSDT secondary benchmark, and \`referenceContexts\` contains BTCUSDT/ETHUSDT plus configured extra reference symbols such as BNBUSDT/SOLUSDT/TRXUSDT/XRPUSDT. If \`targetContext\` or \`targetDerived\` exists, those fields are the Coinalyze context for the actual target coin; use them as target-specific positioning evidence, but do not infer target-coin derivatives when they are absent.
6302
6783
  Key patterns:
6303
- \u2022 coin: \`maFast\`, \`atrPct\`, \`macd...\`, \`candles15m/candles1h/candles4h/candles1d\`, and \`*1h/*4h/*1d\`
6304
- \u2022 BTC: \`btcMaFast\`, \`btcAtr\`, \`btcMacd...\`, \`btcCandles*\`, and \`btc*1h/*4h/*1d\`
6305
- \u2022 strategy service keys are possible as well, for example \`correlation\`, \`spread\`, \`touches\`, \`distance\`
6784
+ \u2022 current shared state: prefer \`payload.additionalIndicators.baseContext\`
6785
+ \u2022 recent historical series: \`payload.indicators\`
6786
+ \u2022 strategy service keys are possible as well, for example \`touches\`, \`distance\`, timing flags, and other setup-specific summaries
6306
6787
 
6307
6788
  How to analyze, in order:
6308
6789
  1. Start with price structure and the setup geometry or context in \`payload.figures\`. This has higher priority than indicators.
6309
- 2. Then use \`payload.additionalIndicators\` when it contains explicit strategy-specific context such as line state, spread, correlation, and similar fields.
6310
- 3. Then assess confirmation or conflict from the current coin indicators.
6790
+ 2. Then use \`payload.additionalIndicators.baseContext\` and other explicit strategy-specific context fields.
6791
+ 3. Then assess confirmation or conflict from the current shared state and recent coin indicator history.
6311
6792
  4. Then evaluate BTC context.
6312
6793
  5. Only after that choose \`direction\`, \`quality\`, and whether an extra confirmation level is required.
6313
6794
  6. If strong conflicts exist, reduce quality or set direction to \`null\`.
@@ -6316,13 +6797,24 @@ Explicit conflict rules:
6316
6797
  - If the figure or price structure is invalid or doubtful, indicators must not rescue the setup.
6317
6798
  - If strategy-specific helper fields explicitly say the signal is not confirmed yet, lacks margin, or requires waiting, do not overstate quality.
6318
6799
  - If the structure is acceptable but BTC or key indicators noticeably conflict, quality is usually \`<= 3\`.
6319
- - If \`derivativesContext.referenceContexts\` exists, check \`primaryReferenceSymbol\` first, then compare \`BTCUSDT\` and \`ETHUSDT\` as broad-market derivatives context. Do not search for Coinalyze data for \`targetSymbol\` unless \`targetSymbol\` itself is \`BTCUSDT\` or \`ETHUSDT\`.
6320
- - If \`derivativesContext.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as crowded positioning and do not overstate quality without strong structural confirmation.
6321
- - If \`derivativesContext.summary.directionAligned=false\`, explicitly mention the derivatives conflict in \`confirmations\` or \`qualityReason\`.
6322
- - If \`derivativesContext\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
6323
- - If \`marketContext.tradingSession\` exists, treat the session as a liquidity and volatility regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
6324
- - If \`marketContext.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
6325
- - If \`marketContext.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
6800
+ - If \`baseContext.derivatives.referenceContexts\` exists, check \`primaryReferenceSymbol\` first as the BTC benchmark, then compare \`secondaryReferenceSymbol\`/ETHUSDT and any target-specific \`targetDerived\`. If \`targetDerived\` exists, compare it to the primary reference instead of treating reference pressure as the target coin's own pressure.
6801
+ - If top-level \`baseContext.derivatives.summary.riskFlags\` contains \`crowded_long\` for a LONG or \`crowded_short\` for a SHORT, treat that as broad-market crowded positioning. If \`targetDerived.riskFlags\` contains the same directional crowding, treat that as target-specific crowded positioning.
6802
+ - If top-level \`baseContext.derivatives.summary.directionAligned=false\`, explicitly mention the broad-market derivatives conflict in \`confirmations\` or \`qualityReason\`. If \`targetDerived.directionAligned=false\`, explicitly mention the target-specific derivatives conflict.
6803
+ - If \`baseContext.derivatives\` is absent, stale, or \`missing_derivatives\`, do not infer Coinalyze conclusions and do not penalize the signal just because that data is missing.
6804
+ - Use \`baseContext.regime.session\` directly as the canonical session/liquidity regime: asia is often thinner, europe/us are more active, and overlaps can amplify both momentum and noise. Do not reject a signal solely because of session, but mention clear session support or conflict in \`confirmations\` or \`qualityReason\`.
6805
+ - If \`marketContext.execution.binanceCoinbaseSpread.available=true\` and \`severity=elevated/wide\`, treat it as cross-exchange divergence or BTC liquidity risk. Do not use the spread as a standalone long/short signal, but reduce confidence or require more confirmation when the rest of the structure is weak or BTC context conflicts.
6806
+ - If \`marketContext.execution.binanceCoinbaseSpread\` is missing or \`available=false\`, do not infer anything from Binance/Coinbase spread and do not penalize the signal just because it is absent.
6807
+ - If \`marketContext.participation.trueDelta.available=true\`, use it as better participation evidence than OHLCV-derived proxy delta; still do not let delta override invalid price structure.
6808
+ - If \`marketContext.participation.tradeFlow.available=true\` and \`stale=false\`, use it as direct lower-timeframe participation evidence. Treat stale or missing tradeFlow as absent, not as negative evidence.
6809
+ - If \`marketContext.relative.marketBreadth.available=true\` and \`stale=false\`, use it as broad alt-market support/conflict. Breadth is contextual; do not let it override the target symbol structure.
6810
+ - If \`marketContext.relative.targetVsBtc.available=true\`, treat positive target/BTC ratio trend as support for alt LONGs and negative ratio trend as support for alt SHORTs; ignore it when the target structure is stronger and clearly explains the setup.
6811
+ - If \`marketContext.relative.btcAltRegime.available=true\` and \`stale=false\`, treat \`alt_lead\`/\`risk_on\` as broad support for alt LONGs and \`btc_lead\`/\`risk_off\` as pressure against alt LONGs or support for cautious alt SHORTs. Do not use it as a standalone entry reason.
6812
+ - If \`marketContext.relative.cmcGlobal.available=true\` and \`stale=false\`, use falling alt market cap/volume or rising BTC dominance as broad risk pressure for alt LONGs. Treat missing CMC history as absent context, not a bearish signal.
6813
+ - If \`marketContext.relative.cmcReferenceAssets.available=true\` and \`stale=false\`, use \`eth_led\` as broad support for ETH/high-beta alt strength and \`btc_led\`/\`thin\` as broad caution. Do not describe BTC/ETH reference history as target-symbol flow.
6814
+ - If \`marketContext.relative.cmcExchangeLiquidity.available=true\` and \`stale=false\`, treat \`contracting\`, \`thin\`, or \`concentrated\` as broad liquidity risk; \`expanding\` or \`balanced\` supports cleaner execution context but is not a standalone entry reason.
6815
+ - If \`marketContext.relative.cmcFearGreed.available=true\` and \`stale=false\`, use \`risk_on\` as broad support for LONGs and \`risk_off\`/\`capitulation\` as broad pressure. Treat \`euphoric\` as overheating/chase caution, not as standalone SHORT proof.
6816
+ - If \`marketContext.relative.cmcIndexes.available=true\` and \`stale=false\`, use \`top20_led\` as broad support for mega-cap leadership, \`large_cap_led\` as broader CMC100 participation, and \`risk_off\` as broad pressure. Do not use CMC index history as a standalone entry reason.
6817
+ - If \`marketContext.relative.referenceTradeFlow.available=true\`, treat BTC/ETH trade-flow as broad market context only. For alt symbols, do not describe it as the target coin's own flow.
6326
6818
  - If the current signal is not confirmed (\`direction=null\`), name the main reason briefly in \`comment\`.
6327
6819
  If you use the structured fields, include the main reason in \`qualityReason\` or \`triggerInvalidation\`.
6328
6820
 
@@ -6373,6 +6865,18 @@ Trade payload:
6373
6865
  ${JSON.stringify(payload)}
6374
6866
  ${buildAiHumanPromptAddonByStrategy(signal, payload)}
6375
6867
  `;
6868
+ var getAiInvocationError = (error) => {
6869
+ const details = error instanceof Error && error.message.trim() ? error.message.trim() : String(error);
6870
+ const isEmptyCompletion = error instanceof TypeError && /Cannot read properties of undefined \(reading ['"]message['"]\)/.test(
6871
+ details
6872
+ );
6873
+ const wrapped = new Error(
6874
+ isEmptyCompletion ? "AI provider returned an empty chat completion" : `AI model invocation failed: ${details}`
6875
+ );
6876
+ wrapped.cause = error;
6877
+ return wrapped;
6878
+ };
6879
+ var isEmptyResponseContent = (content) => typeof content === "string" ? content.trim().length === 0 : Object.keys(content).length === 0;
6376
6880
  var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
6377
6881
  var userSettingsCache = /* @__PURE__ */ new Map();
6378
6882
  var aiModelCache = /* @__PURE__ */ new Map();
@@ -6493,10 +6997,17 @@ var runAiPrompt = async ({ systemPrompt, humanPrompt }, options = {}) => {
6493
6997
  ]
6494
6998
  })
6495
6999
  );
6496
- const response = await model.invoke(messages);
6497
- const parsed = parseAIResponse(
6498
- normalizeResponseContent(response.content)
6499
- );
7000
+ let response;
7001
+ try {
7002
+ response = await model.invoke(messages);
7003
+ } catch (error) {
7004
+ throw getAiInvocationError(error);
7005
+ }
7006
+ const responseContent = normalizeResponseContent(response?.content);
7007
+ if (isEmptyResponseContent(responseContent)) {
7008
+ throw new Error("AI provider returned an empty chat completion");
7009
+ }
7010
+ const parsed = parseAIResponse(responseContent);
6500
7011
  const normalized = normalizeAnalysis(parsed);
6501
7012
  if (!options.signal) {
6502
7013
  return normalized;
@@ -6533,6 +7044,27 @@ var import_puppeteer = __toESM(require("puppeteer"));
6533
7044
  var import_async = require("@tradejs/core/async");
6534
7045
  var import_logger3 = require("@tradejs/infra/logger");
6535
7046
  var import_redis2 = require("@tradejs/infra/redis");
7047
+
7048
+ // src/dashboardUrl.ts
7049
+ var buildDashboardUrl = ({
7050
+ baseUrl,
7051
+ provider = "bybit",
7052
+ universe = "crypto",
7053
+ symbol,
7054
+ interval,
7055
+ searchParams = {}
7056
+ }) => {
7057
+ const url = new URL(
7058
+ `/routes/dashboard/${provider}/${universe}/${symbol}/${interval}`,
7059
+ baseUrl
7060
+ );
7061
+ for (const [key, value] of Object.entries(searchParams)) {
7062
+ url.searchParams.set(key, value);
7063
+ }
7064
+ return url.toString();
7065
+ };
7066
+
7067
+ // src/screenshot.ts
6536
7068
  var { APP_URL } = process.env;
6537
7069
  var SCREENSHOT_NAVIGATION_ATTEMPTS = 3;
6538
7070
  var SCREENSHOT_NAVIGATION_RETRY_DELAY_MS = 2e3;
@@ -6641,7 +7173,18 @@ var screenDashboard = async (signal, projectRoot, userName = "root") => {
6641
7173
  `Failed to create screenshot session token for ${userName}`
6642
7174
  );
6643
7175
  }
6644
- const dashboardUrl = `${screenshotBaseUrl}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}&autoZoom=true&screenshot=1&screenshotToken=${encodeURIComponent(screenshotToken)}`;
7176
+ const dashboardUrl = buildDashboardUrl({
7177
+ baseUrl: screenshotBaseUrl,
7178
+ universe: signal.universe ?? "crypto",
7179
+ symbol,
7180
+ interval,
7181
+ searchParams: {
7182
+ signalId,
7183
+ autoZoom: "true",
7184
+ screenshot: "1",
7185
+ screenshotToken
7186
+ }
7187
+ });
6645
7188
  const maskedDashboardUrl = maskTokenInUrl(dashboardUrl);
6646
7189
  import_logger3.logger.info(
6647
7190
  "screenshot start: %s %sm url=%s path=%s",
@@ -6949,6 +7492,25 @@ var describeErrorValue2 = (value) => {
6949
7492
  return String(value);
6950
7493
  };
6951
7494
  var normalizeQuality = (value) => typeof value === "number" ? Math.max(1, Math.min(5, Math.round(value))) : null;
7495
+ var formatOrderValue = (value) => {
7496
+ const rounded = Number(value.toFixed(2));
7497
+ return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(2);
7498
+ };
7499
+ var getDisplayDecision = (signalDirection, analysis, fallback) => {
7500
+ if (!analysis && !fallback) {
7501
+ return void 0;
7502
+ }
7503
+ if (analysis?.direction != null && analysis.direction !== signalDirection) {
7504
+ return "rejected";
7505
+ }
7506
+ if (analysis?.needRetest === true) {
7507
+ return "pending";
7508
+ }
7509
+ if (analysis?.direction != null && analysis.direction === signalDirection) {
7510
+ return "approved";
7511
+ }
7512
+ return fallback;
7513
+ };
6952
7514
  var formatAnalysisLevel = (value) => {
6953
7515
  if (value == null || !Number.isFinite(value)) return null;
6954
7516
  if (Math.abs(value) >= 1e3) {
@@ -6996,24 +7558,17 @@ var formatOrderSkipReason = (reason) => {
6996
7558
  }
6997
7559
  return reason;
6998
7560
  };
6999
- var getLastNumber = (value) => {
7000
- if (Array.isArray(value)) {
7001
- const last = value[value.length - 1];
7002
- return typeof last === "number" ? last : void 0;
7003
- }
7004
- return typeof value === "number" ? value : void 0;
7005
- };
7006
- var getAiQualityLine = (analysis) => {
7561
+ var getAiQualityLine = (analysis, label = "AI Quality") => {
7007
7562
  const quality = normalizeQuality(analysis?.quality);
7008
7563
  if (!quality) return null;
7009
7564
  const approvedCurrentDirection = analysis?.direction != null && analysis.direction !== null;
7010
7565
  if (quality >= 4 && approvedCurrentDirection) {
7011
- return `\u{1F7E2} AI Quality: ${quality}/5`;
7566
+ return `\u{1F7E2} ${label}: ${quality}/5`;
7012
7567
  }
7013
7568
  if (quality === 3 && approvedCurrentDirection) {
7014
- return `\u{1F7E1} AI Quality: ${quality}/5`;
7569
+ return `\u{1F7E1} ${label}: ${quality}/5`;
7015
7570
  }
7016
- return `\u{1F534} AI Quality: ${quality}/5`;
7571
+ return `\u{1F534} ${label}: ${quality}/5`;
7017
7572
  };
7018
7573
  var getTelegramErrorReason = (data) => {
7019
7574
  if (data && typeof data === "object") {
@@ -7139,26 +7694,59 @@ var sendTextToTG = async (message, options = {}) => {
7139
7694
  chatId
7140
7695
  });
7141
7696
  };
7142
- var formatMessage = (signal, analysis) => {
7697
+ var sendDocumentToTG = async (document, options = {}) => {
7698
+ const { token, chatId } = await getTelegramSettings(options.userName);
7699
+ const body = new FormData();
7700
+ const fileContent = typeof document.content === "string" ? document.content : (() => {
7701
+ const bytes = new Uint8Array(document.content.byteLength);
7702
+ bytes.set(document.content);
7703
+ return bytes;
7704
+ })();
7705
+ body.set("chat_id", String(chatId || ""));
7706
+ body.set(
7707
+ "document",
7708
+ new File([fileContent], document.filename, {
7709
+ type: "application/json"
7710
+ })
7711
+ );
7712
+ if (document.caption?.trim()) {
7713
+ body.set("caption", document.caption.trim());
7714
+ body.set("parse_mode", "HTML");
7715
+ }
7716
+ const data = await requestTelegram({
7717
+ method: "sendDocument",
7718
+ token,
7719
+ init: {
7720
+ method: "POST",
7721
+ body
7722
+ }
7723
+ });
7724
+ import_logger4.logger.info(
7725
+ "tg sendDocument: %s",
7726
+ data?.ok ? "sent" : getTelegramErrorReason(data)
7727
+ );
7728
+ return data;
7729
+ };
7730
+ var formatMessage = (signal, analysis, options = {}) => {
7143
7731
  const {
7144
7732
  symbol,
7145
7733
  direction,
7146
7734
  strategy,
7147
7735
  orderStatus,
7148
7736
  orderSkipReason,
7737
+ orderFailureReason,
7738
+ orderQty,
7739
+ orderValue,
7149
7740
  isConfigFromBacktest,
7150
7741
  ml,
7151
7742
  prices: { currentPrice, takeProfitPrice, stopLossPrice, riskRatio },
7152
- indicators,
7153
7743
  additionalIndicators
7154
7744
  } = signal;
7745
+ const userName = options.userName || "root";
7155
7746
  try {
7156
7747
  const lines = [];
7157
7748
  const distance = additionalIndicators?.distance;
7158
7749
  const touches = additionalIndicators?.touches;
7159
- const correlation = getLastNumber(indicators.correlation);
7160
- const atrPct = getLastNumber(indicators.atrPct);
7161
- const spread = getLastNumber(indicators.spread);
7162
7750
  const formatPrices = () => {
7163
7751
  const tpPercent = Math.abs(
7164
7752
  (takeProfitPrice - currentPrice) / currentPrice * 100
@@ -7166,8 +7754,10 @@ var formatMessage = (signal, analysis) => {
7166
7754
  const slPercent = Math.abs((stopLossPrice - currentPrice) / currentPrice * 100).toFixed(
7167
7755
  2
7168
7756
  ) + "%";
7757
+ const resolvedOrderValue = typeof orderValue === "number" && Number.isFinite(orderValue) ? orderValue : typeof orderQty === "number" && Number.isFinite(orderQty) ? orderQty * currentPrice : null;
7169
7758
  const prices = [
7170
7759
  `Price: <b>${(0, import_math.formatNumber)(currentPrice)}</b>`,
7760
+ resolvedOrderValue != null ? `Value: <b>${formatOrderValue(resolvedOrderValue)}$</b>` : null,
7171
7761
  `TP: <b>${(0, import_math.formatNumber)(takeProfitPrice)}</b> (${tpPercent})`,
7172
7762
  `SL: <b>${(0, import_math.formatNumber)(stopLossPrice)}</b> (${slPercent})`,
7173
7763
  `R:R = <b>${riskRatio.toFixed(2)}</b>`
@@ -7193,6 +7783,8 @@ var formatMessage = (signal, analysis) => {
7193
7783
  lines.push(
7194
7784
  `Skip reason: <b>${escapeHtml(formatOrderSkipReason(orderSkipReason))}</b>`
7195
7785
  );
7786
+ } else if (orderStatus === "failed" && orderFailureReason) {
7787
+ lines.push(`Reason: <code>${escapeHtml(orderFailureReason)}</code>`);
7196
7788
  }
7197
7789
  }
7198
7790
  if (isConfigFromBacktest) {
@@ -7205,17 +7797,25 @@ var formatMessage = (signal, analysis) => {
7205
7797
  `${ml.passed ? "\u{1F7E2} ML: PASS" : "\u{1F534} ML: FAIL"} (${ml.probability.toFixed(3)} / ${ml.threshold.toFixed(2)})`
7206
7798
  );
7207
7799
  }
7208
- const aiQualityLine = getAiQualityLine(analysis);
7209
- if (aiQualityLine) {
7210
- lines.push(aiQualityLine);
7800
+ const gateAnalysis = analysis?.gateAnalysis && typeof analysis.gateAnalysis === "object" && !Array.isArray(analysis.gateAnalysis) ? analysis.gateAnalysis : null;
7801
+ if (gateAnalysis) {
7802
+ const gateQualityLine = getAiQualityLine(gateAnalysis, "Gate Quality");
7803
+ if (gateQualityLine) {
7804
+ lines.push(gateQualityLine);
7805
+ }
7806
+ const llmQualityLine = getAiQualityLine(analysis, "LLM Quality");
7807
+ if (llmQualityLine) {
7808
+ lines.push(llmQualityLine);
7809
+ }
7810
+ } else {
7811
+ const aiQualityLine = getAiQualityLine(analysis);
7812
+ if (aiQualityLine) {
7813
+ lines.push(aiQualityLine);
7814
+ }
7211
7815
  }
7212
7816
  lines.push("");
7213
- if (correlation) {
7214
- lines.push(`BTC correlation: ${correlation}`);
7215
- }
7216
7817
  const prices = formatPrices();
7217
7818
  if (prices) {
7218
- lines.push("");
7219
7819
  lines.push(prices);
7220
7820
  }
7221
7821
  };
@@ -7229,9 +7829,17 @@ var formatMessage = (signal, analysis) => {
7229
7829
  var sendSignal = async (signal, imgInterval, analysis, options = {}) => {
7230
7830
  const { symbol, signalId, interval } = signal;
7231
7831
  const { token, chatId } = await getTelegramSettings(options.userName);
7232
- const message = formatMessage(signal, analysis);
7832
+ const message = formatMessage(signal, analysis, {
7833
+ userName: options.userName
7834
+ });
7233
7835
  const publicAppUrl = APP_URL2?.startsWith("https") ? APP_URL2 : null;
7234
- const dashboardUrl = publicAppUrl ? `${APP_URL2}/routes/dashboard/bybit/${symbol}/${interval}/?signalId=${signalId}` : null;
7836
+ const dashboardUrl = publicAppUrl ? buildDashboardUrl({
7837
+ baseUrl: publicAppUrl,
7838
+ universe: signal.universe ?? "crypto",
7839
+ symbol,
7840
+ interval,
7841
+ searchParams: { signalId }
7842
+ }) : null;
7235
7843
  const actionButtons = [
7236
7844
  dashboardUrl ? { text: "Dashboard", url: dashboardUrl } : null
7237
7845
  ].filter(Boolean);
@@ -7324,9 +7932,19 @@ var formatAnalysisMessage = (signal, analysis) => {
7324
7932
  if (quality) {
7325
7933
  lines.push(`Quality: <b>${quality}/5</b>`);
7326
7934
  }
7327
- if (analysis.gateDecision && analysis.llmDecision) {
7935
+ const gateDecision = getDisplayDecision(
7936
+ signal.direction,
7937
+ analysis.gateAnalysis,
7938
+ analysis.gateDecision
7939
+ );
7940
+ const llmDecision = getDisplayDecision(
7941
+ signal.direction,
7942
+ analysis,
7943
+ analysis.llmDecision
7944
+ );
7945
+ if (gateDecision && llmDecision) {
7328
7946
  lines.push(
7329
- `Gate vs LLM: <b>${analysis.gateContradictsLlm ? "conflict" : "aligned"}</b> (gate ${analysis.gateDecision}, LLM ${analysis.llmDecision})`
7947
+ `Gate vs LLM: <b>${gateDecision === llmDecision ? "aligned" : "conflict"}</b> (gate ${gateDecision}, LLM ${llmDecision})`
7330
7948
  );
7331
7949
  }
7332
7950
  const happeningText = takeUniqueAnalysisText(
@@ -7371,19 +7989,6 @@ var formatAnalysisMessage = (signal, analysis) => {
7371
7989
  if (btcText) {
7372
7990
  lines.push(`BTC context: ${escapeHtml(btcText)}`);
7373
7991
  }
7374
- const levels = [];
7375
- if (typeof analysis.takeProfitPrice === "number") {
7376
- levels.push(`TP <b>${formatAnalysisLevel(analysis.takeProfitPrice)}</b>`);
7377
- }
7378
- if (typeof analysis.stopLossPrice === "number") {
7379
- levels.push(`SL <b>${formatAnalysisLevel(analysis.stopLossPrice)}</b>`);
7380
- }
7381
- if (typeof analysis.retestPrice === "number") {
7382
- levels.push(`Retest <b>${formatAnalysisLevel(analysis.retestPrice)}</b>`);
7383
- }
7384
- if (levels.length > 0) {
7385
- lines.push(`Levels: ${levels.join(" | ")}`);
7386
- }
7387
7992
  return lines.join("\n");
7388
7993
  };
7389
7994
  var sendSignalAnalysis = async (signal, analysis, options = {}) => {
@@ -7410,10 +8015,35 @@ var sendSignalAnalysis = async (signal, analysis, options = {}) => {
7410
8015
 
7411
8016
  // src/cli.ts
7412
8017
  var getProjectRoot2 = () => getTradejsProjectCwd();
8018
+ var escapeHtml2 = (value) => value == null ? "" : String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8019
+ var formatOptionalNumber = (value) => {
8020
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8021
+ return "n/a";
8022
+ }
8023
+ const normalized = Number(value.toFixed(8));
8024
+ return Number.isInteger(normalized) ? String(normalized) : String(normalized);
8025
+ };
8026
+ var formatOptionalTimestamp = (value) => {
8027
+ if (typeof value !== "number" || !Number.isFinite(value)) {
8028
+ return "n/a";
8029
+ }
8030
+ return new Date(value).toISOString();
8031
+ };
8032
+ var isEnoentError = (error) => error?.code === "ENOENT";
7413
8033
  var cleanFiles = async (dir) => {
7414
8034
  let completed = 0;
7415
8035
  const projectRoot = getProjectRoot2();
7416
- const files = await (0, import_files.getFiles)(dir, projectRoot);
8036
+ let files = [];
8037
+ try {
8038
+ files = await (0, import_files.getFiles)(dir, projectRoot);
8039
+ } catch (error) {
8040
+ if (isEnoentError(error)) {
8041
+ import_logger5.logger.info(import_chalk.default.yellow("clean:", dir));
8042
+ import_logger5.logger.info("");
8043
+ return;
8044
+ }
8045
+ throw error;
8046
+ }
7417
8047
  const bar = new import_progress.default(":current/:total [:bar][:percent] :eta(s)", {
7418
8048
  total: files.length,
7419
8049
  width: 30
@@ -7421,12 +8051,18 @@ var cleanFiles = async (dir) => {
7421
8051
  import_logger5.logger.info(import_chalk.default.yellow("clean:", dir));
7422
8052
  for await (const file of files) {
7423
8053
  completed++;
7424
- const fullPath = `${dir}/${file}`;
7425
- const stat = await import_promises2.default.lstat(fullPath);
7426
- if (stat.isDirectory()) {
7427
- await import_promises2.default.rm(fullPath, { recursive: true, force: true });
7428
- } else {
7429
- await import_promises2.default.unlink(fullPath);
8054
+ const fullPath = import_path3.default.join(projectRoot, dir, file);
8055
+ try {
8056
+ const stat = await import_promises2.default.lstat(fullPath);
8057
+ if (stat.isDirectory()) {
8058
+ await import_promises2.default.rm(fullPath, { recursive: true, force: true });
8059
+ } else {
8060
+ await import_promises2.default.unlink(fullPath);
8061
+ }
8062
+ } catch (error) {
8063
+ if (!isEnoentError(error)) {
8064
+ throw error;
8065
+ }
7430
8066
  }
7431
8067
  if (completed % 100 === 0 || completed === files.length) {
7432
8068
  bar.tick(completed === files.length ? completed % 100 : 100);
@@ -7471,28 +8107,58 @@ var update = async (connector, interval, tickers, preloadDays = import_constants
7471
8107
  );
7472
8108
  const preloadEnd = Math.trunc(options.preloadEnd ?? (0, import_time.getTimestamp)());
7473
8109
  const connectorLabel = String(options.connectorLabel || "").trim();
7474
- const preloadLabel = options.preloadStart != null || options.preloadEnd != null ? `preloadStart=${preloadStart}, preloadEnd=${preloadEnd}` : `preloadDays=${preloadDays}`;
8110
+ const preloadLabel = options.preloadStart != null || options.preloadEnd != null ? `preloadStart=${formatOptionalTimestamp(preloadStart)}, preloadEnd=${formatOptionalTimestamp(preloadEnd)}` : `preloadDays=${preloadDays}`;
7475
8111
  if (preloadStart >= preloadEnd) {
7476
8112
  throw new Error(
7477
8113
  `Invalid update preload window: start (${preloadStart}) must be less than end (${preloadEnd})`
7478
8114
  );
7479
8115
  }
8116
+ let queue = [...new Set(tickers.slice())];
8117
+ if (!queue.includes("BTCUSDT")) {
8118
+ queue.unshift("BTCUSDT");
8119
+ }
8120
+ const intervalMinutes = Number(interval);
8121
+ if (options.skipCovered && connectorLabel && Number.isFinite(intervalMinutes) && intervalMinutes > 0) {
8122
+ const edges = await (0, import_timescale.getDataEdgesForSymbols)(
8123
+ connectorLabel,
8124
+ queue,
8125
+ Math.floor(intervalMinutes)
8126
+ );
8127
+ const initialCount = queue.length;
8128
+ queue = queue.filter((symbol) => {
8129
+ const edge = edges.get(String(symbol).toUpperCase());
8130
+ return !edge || edge.min === void 0 || edge.max === void 0 || edge.min > preloadStart || edge.max < preloadEnd;
8131
+ });
8132
+ const skippedCount = initialCount - queue.length;
8133
+ if (skippedCount > 0) {
8134
+ import_logger5.logger.info(
8135
+ import_chalk.default.gray(
8136
+ `update ${connectorLabel}: skip ${skippedCount}/${initialCount} cached symbols for interval=${interval}`
8137
+ )
8138
+ );
8139
+ }
8140
+ }
8141
+ if (queue.length === 0) {
8142
+ import_logger5.logger.info(
8143
+ import_chalk.default.yellow(
8144
+ `update: 0 (connector=${connectorLabel || "unknown"}, interval=${interval}, klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, ${preloadLabel})`
8145
+ )
8146
+ );
8147
+ import_logger5.logger.info("");
8148
+ return;
8149
+ }
7480
8150
  const bar = new import_progress.default(
7481
8151
  ":current/:total [:bar][:percent] :eta(s) :symbol",
7482
8152
  {
7483
- total: tickers.length,
8153
+ total: queue.length,
7484
8154
  width: 30
7485
8155
  }
7486
8156
  );
7487
8157
  import_logger5.logger.info(
7488
8158
  import_chalk.default.yellow(
7489
- `update: ${tickers.length} (connector=${connectorLabel || "unknown"}, klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, ${preloadLabel})`
8159
+ `update: ${queue.length} (connector=${connectorLabel || "unknown"}, interval=${interval}, klineConcurrency=${KLINE_CONCURRENCY_LIMIT}, ${preloadLabel})`
7490
8160
  )
7491
8161
  );
7492
- const queue = tickers.slice();
7493
- if (!queue.includes("BTCUSDT")) {
7494
- queue.unshift("BTCUSDT");
7495
- }
7496
8162
  await (0, import_async3.runWithConcurrency)(queue, KLINE_CONCURRENCY_LIMIT, async (symbol) => {
7497
8163
  try {
7498
8164
  await connector.kline({
@@ -7521,6 +8187,8 @@ var getCLILevelColor = (level) => {
7521
8187
  return import_chalk.default.green;
7522
8188
  case "warning":
7523
8189
  return import_chalk.default.yellow;
8190
+ case "neutral":
8191
+ return import_chalk.default.gray;
7524
8192
  case "error":
7525
8193
  return import_chalk.default.red;
7526
8194
  }
@@ -7532,18 +8200,18 @@ var drawStatInCLI = (stat, keys) => {
7532
8200
  return color(formatted);
7533
8201
  });
7534
8202
  };
7535
- var scanner = async (connector, limit) => {
7536
- const data = await connector.getTickers();
8203
+ var scanner = async (connector, limit, query) => {
8204
+ const data = await connector.getTickers(query);
7537
8205
  const tickers = (0, import_tickers.getTopTickers)(data, limit);
7538
8206
  return tickers.map(({ value }) => value);
7539
8207
  };
7540
- var getTickers = async (connector, include = "", exclude = "", limit, chunk) => {
8208
+ var getTickers = async (connector, include = "", exclude = "", limit, chunk, query) => {
7541
8209
  let tickers;
7542
8210
  const excludeTickers = parseSymbolsFromCLI(exclude);
7543
8211
  if (include) {
7544
8212
  tickers = parseSymbolsFromCLI(include);
7545
8213
  } else {
7546
- tickers = await scanner(connector, limit);
8214
+ tickers = await scanner(connector, limit, query);
7547
8215
  }
7548
8216
  if (chunk) {
7549
8217
  const [currentChunk, chunksCount] = chunk.split("/").map((c) => parseInt(c));
@@ -7603,7 +8271,79 @@ var sendToAI = async (signals, userName = "root") => {
7603
8271
  });
7604
8272
  import_logger5.logger.info("");
7605
8273
  };
8274
+ var formatRuntimeCloseNotification = (event, _userName = event.userName ?? "root") => {
8275
+ const ownership = event.openedByStrategy === event.strategy ? "matched" : `mismatch: opened by ${event.openedByStrategy}`;
8276
+ return [
8277
+ "<b>Strategy self-close</b>",
8278
+ `Symbol: <b>${escapeHtml2(event.symbol)}</b>`,
8279
+ `Strategy: <b>${escapeHtml2(event.strategy)}</b>`,
8280
+ `Direction: <b>${escapeHtml2(event.direction)}</b>`,
8281
+ `Reason: <code>${escapeHtml2(event.code)}</code>`,
8282
+ "",
8283
+ `Opened by journal: <b>${escapeHtml2(event.openedByStrategy)}</b>`,
8284
+ `Ownership: <b>${escapeHtml2(ownership)}</b>`,
8285
+ "",
8286
+ `Entry: <b>${escapeHtml2(formatOptionalNumber(event.entryPrice))}</b> at <code>${escapeHtml2(formatOptionalTimestamp(event.entryTimestamp))}</code>`,
8287
+ `Exit: <b>${escapeHtml2(formatOptionalNumber(event.exitPrice))}</b> at <code>${escapeHtml2(formatOptionalTimestamp(event.exitTimestamp))}</code>`,
8288
+ `Qty: <b>${escapeHtml2(formatOptionalNumber(event.qty))}</b>`,
8289
+ `Closed PnL: <b>${escapeHtml2(formatOptionalNumber(event.closedPnl))}</b>`
8290
+ ].join("\n");
8291
+ };
8292
+ var sendRuntimeCloseNotificationsToTG = async (events, userName = "root") => {
8293
+ import_logger5.logger.info(import_chalk.default.yellow("close messages:", events.length));
8294
+ if (!events.length) {
8295
+ import_logger5.logger.info("");
8296
+ return;
8297
+ }
8298
+ const bar = new import_progress.default(
8299
+ ":current/:total [:bar][:percent] :eta(s) :symbol",
8300
+ {
8301
+ total: events.length,
8302
+ width: 30
8303
+ }
8304
+ );
8305
+ await (0, import_async3.runWithConcurrency)(events, 1, async (event) => {
8306
+ try {
8307
+ await sendTextToTG(formatRuntimeCloseNotification(event, userName), {
8308
+ userName
8309
+ });
8310
+ } catch (err) {
8311
+ import_logger5.logger.error(
8312
+ "Failed close notification: %s %s",
8313
+ event.symbol,
8314
+ err?.message || String(err)
8315
+ );
8316
+ } finally {
8317
+ bar.tick(1, { symbol: import_chalk.default.gray(event.symbol) });
8318
+ }
8319
+ });
8320
+ import_logger5.logger.info("");
8321
+ };
7606
8322
  var sendToTG = async (signals, imgInterval, userName = "root") => {
8323
+ const strategyConfigCache = /* @__PURE__ */ new Map();
8324
+ const resolveStrategyConfig = async (strategyName) => {
8325
+ if (strategyConfigCache.has(strategyName)) {
8326
+ return strategyConfigCache.get(strategyName);
8327
+ }
8328
+ const config = await (0, import_redis3.getData)(
8329
+ `users:${userName}:strategies:${strategyName}:config`,
8330
+ null
8331
+ );
8332
+ strategyConfigCache.set(strategyName, config);
8333
+ return config;
8334
+ };
8335
+ const resolveDecision = (analysis, direction, minQuality = 4) => {
8336
+ if (analysis?.needRetest === true) {
8337
+ return "rejected";
8338
+ }
8339
+ const quality = Number(analysis?.quality);
8340
+ if (!Number.isFinite(quality)) {
8341
+ return "rejected";
8342
+ }
8343
+ const normalized = Math.round(quality);
8344
+ const resolvedQuality = analysis?.direction === direction ? normalized : 0;
8345
+ return resolvedQuality >= minQuality ? "approved" : "rejected";
8346
+ };
7607
8347
  const deliverableSignals = signals.filter(
7608
8348
  (signal) => signal.orderStatus !== "skipped" && signal.orderStatus !== "canceled"
7609
8349
  );
@@ -7621,17 +8361,56 @@ var sendToTG = async (signals, imgInterval, userName = "root") => {
7621
8361
  );
7622
8362
  await (0, import_async3.runWithConcurrency)(deliverableSignals, 1, async (signal) => {
7623
8363
  try {
7624
- const analysis = await (0, import_redis3.getData)(
8364
+ let analysis = await (0, import_redis3.getData)(
7625
8365
  import_redis3.redisKeys.analysis(signal.symbol, signal.signalId),
7626
8366
  null
7627
8367
  );
8368
+ let shouldSendSignalAnalysis = analysis && typeof analysis === "object" && Object.keys(analysis).length > 0;
8369
+ const strategyConfig = await resolveStrategyConfig(signal.strategy);
8370
+ const aiMode = strategyConfig?.AI_MODE;
8371
+ const gateAnalysis = signal.aiAnalysis;
8372
+ if (aiMode === "gate" && gateAnalysis) {
8373
+ const minQuality = Number(strategyConfig?.MIN_AI_QUALITY) || 4;
8374
+ const gateDecision = resolveDecision(
8375
+ gateAnalysis,
8376
+ signal.direction,
8377
+ minQuality
8378
+ );
8379
+ try {
8380
+ const llmAnalysis = await askAI(signal, { userName });
8381
+ const llmDecision = resolveDecision(
8382
+ llmAnalysis,
8383
+ signal.direction,
8384
+ minQuality
8385
+ );
8386
+ analysis = {
8387
+ ...llmAnalysis ?? {},
8388
+ gateAnalysis,
8389
+ gateDecision,
8390
+ llmDecision,
8391
+ gateContradictsLlm: gateDecision !== llmDecision
8392
+ };
8393
+ shouldSendSignalAnalysis = true;
8394
+ } catch (error) {
8395
+ import_logger5.logger.error(
8396
+ "LLM commentary failed: %s (%s)",
8397
+ signal.symbol,
8398
+ error?.message || String(error)
8399
+ );
8400
+ analysis = {
8401
+ gateAnalysis,
8402
+ gateDecision
8403
+ };
8404
+ shouldSendSignalAnalysis = false;
8405
+ }
8406
+ }
7628
8407
  await sendSignal(signal, imgInterval, analysis, { userName });
7629
- if (analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
8408
+ if (shouldSendSignalAnalysis && analysis && typeof analysis === "object" && Object.keys(analysis).length > 0) {
7630
8409
  await sendSignalAnalysis(signal, analysis, { userName });
7631
8410
  }
7632
8411
  } catch (err) {
7633
8412
  import_logger5.logger.error(
7634
- "Failed sent: %s %s",
8413
+ "Signal notification failed: %s (%s)",
7635
8414
  signal.symbol,
7636
8415
  err?.message || String(err)
7637
8416
  );
@@ -7646,9 +8425,12 @@ var sendToTG = async (signals, imgInterval, userName = "root") => {
7646
8425
  cleanFiles,
7647
8426
  cleanRedis,
7648
8427
  drawStatInCLI,
8428
+ formatRuntimeCloseNotification,
7649
8429
  getTickers,
7650
8430
  loadTradejsConfig,
7651
8431
  makeScreenshots,
8432
+ sendDocumentToTG,
8433
+ sendRuntimeCloseNotificationsToTG,
7652
8434
  sendTextToTG,
7653
8435
  sendToAI,
7654
8436
  sendToTG,