@tradejs/node 3.1.29-beta.288 → 3.1.29-beta.290

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/mcp.js CHANGED
@@ -781,6 +781,7 @@ __export(mcp_exports, {
781
781
  McpCursorError: () => McpCursorError,
782
782
  cancelMcpJob: () => cancelMcpJob,
783
783
  enqueueMcpJob: () => enqueueMcpJob,
784
+ formatMcpArtifactLogLine: () => formatMcpArtifactLogLine,
784
785
  isMcpRecord: () => isMcpRecord,
785
786
  mcpId: () => mcpId,
786
787
  mcpJobDirectory: () => mcpJobDirectory,
@@ -1066,13 +1067,210 @@ var assessSignalWithJev = async ({
1066
1067
  // src/strategyRuntime.ts
1067
1068
  var import_constants5 = require("@tradejs/core/constants");
1068
1069
  var import_strategies6 = require("@tradejs/core/strategies");
1069
- var import_logger10 = require("@tradejs/infra/logger");
1070
+ var import_logger11 = require("@tradejs/infra/logger");
1070
1071
 
1071
1072
  // src/strategyHelpers/runtime.ts
1072
- var import_logger8 = require("@tradejs/infra/logger");
1073
+ var import_logger9 = require("@tradejs/infra/logger");
1073
1074
  var import_constants3 = require("@tradejs/core/constants");
1074
1075
  var import_ml2 = require("@tradejs/infra/ml");
1075
1076
 
1077
+ // src/strategy/entrySubmissionValidation.ts
1078
+ var import_logger = require("@tradejs/infra/logger");
1079
+
1080
+ // src/strategy/entryExecutionValidation.ts
1081
+ var EntrySubmissionValidationError = class extends Error {
1082
+ constructor() {
1083
+ super(...arguments);
1084
+ this.name = "EntrySubmissionValidationError";
1085
+ }
1086
+ };
1087
+ var resolveEntryExecutionLimits = (config, direction) => {
1088
+ const side = config[direction];
1089
+ const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
1090
+ const maximum = config.MAX_LOSS_VALUE;
1091
+ if (minimum == null && maximum == null) return void 0;
1092
+ const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
1093
+ const maxLossValue = maximum == null ? void 0 : Number(maximum);
1094
+ const feeRate = Number(config.RISK_FEE_RATE ?? 0);
1095
+ const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
1096
+ const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
1097
+ const slippageBps = slippage + impact;
1098
+ if (minimumNetRiskRatio != null && (!Number.isFinite(minimumNetRiskRatio) || minimumNetRiskRatio < 0) || maxLossValue != null && (!Number.isFinite(maxLossValue) || maxLossValue <= 0) || !Number.isFinite(feeRate) || feeRate < 0 || feeRate >= 1 || !Number.isFinite(slippage) || slippage < 0 || !Number.isFinite(impact) || impact < 0 || !Number.isFinite(slippageBps) || slippageBps < 0 || slippageBps >= 1e4)
1099
+ throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
1100
+ return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
1101
+ };
1102
+ var validateEntryExecution = ({
1103
+ direction,
1104
+ price,
1105
+ qty,
1106
+ stopLossPrice,
1107
+ takeProfitPrice,
1108
+ limits
1109
+ }) => {
1110
+ if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
1111
+ throw new EntrySubmissionValidationError(
1112
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1113
+ );
1114
+ const side = direction === "LONG" ? 1 : -1;
1115
+ if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
1116
+ throw new EntrySubmissionValidationError(
1117
+ "INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
1118
+ );
1119
+ const slippage = limits.slippageBps / 1e4;
1120
+ const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
1121
+ const entryCost = cost(price, side);
1122
+ const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
1123
+ const rewardPerUnit = Math.max(
1124
+ 0,
1125
+ Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
1126
+ );
1127
+ const netRiskRatio = rewardPerUnit / lossPerUnit;
1128
+ const plannedLossValue = qty * lossPerUnit;
1129
+ if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
1130
+ throw new EntrySubmissionValidationError(
1131
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1132
+ );
1133
+ if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
1134
+ throw new EntrySubmissionValidationError(
1135
+ "ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
1136
+ );
1137
+ const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
1138
+ return { netRiskRatio, plannedLossValue, riskExceeded };
1139
+ };
1140
+
1141
+ // src/strategy/entrySubmissionValidation.ts
1142
+ var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
1143
+ var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1144
+ var getOrderArrivalSnapshot = async ({
1145
+ connector,
1146
+ symbol,
1147
+ requireTimestamp = false
1148
+ }) => {
1149
+ if (typeof connector.getTopOfBookTicker !== "function") {
1150
+ return {
1151
+ arrivalSnapshotTime: Date.now(),
1152
+ arrivalSource: "unavailable",
1153
+ bid: null,
1154
+ ask: null,
1155
+ arrivalMid: null,
1156
+ spreadBps: null
1157
+ };
1158
+ }
1159
+ try {
1160
+ const ticker = await connector.getTopOfBookTicker(symbol);
1161
+ const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
1162
+ const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
1163
+ const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
1164
+ const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
1165
+ const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
1166
+ const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
1167
+ return {
1168
+ arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
1169
+ arrivalSource: "top_of_book",
1170
+ bid,
1171
+ ask,
1172
+ arrivalMid,
1173
+ spreadBps
1174
+ };
1175
+ } catch (error) {
1176
+ import_logger.logger.warn(
1177
+ "runtime order arrival snapshot failed: %s %s",
1178
+ symbol,
1179
+ error?.message || String(error)
1180
+ );
1181
+ return {
1182
+ arrivalSnapshotTime: Date.now(),
1183
+ arrivalSource: "top_of_book_error",
1184
+ bid: null,
1185
+ ask: null,
1186
+ arrivalMid: null,
1187
+ spreadBps: null
1188
+ };
1189
+ }
1190
+ };
1191
+ var validateEntryProtectionAtArrival = ({
1192
+ direction,
1193
+ signalPrice,
1194
+ bid,
1195
+ ask,
1196
+ arrivalMid,
1197
+ takeProfits,
1198
+ stopLossPrice
1199
+ }) => {
1200
+ const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
1201
+ const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
1202
+ if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
1203
+ throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
1204
+ }
1205
+ for (const takeProfit of takeProfits) {
1206
+ if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
1207
+ throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
1208
+ }
1209
+ }
1210
+ if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
1211
+ throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
1212
+ }
1213
+ };
1214
+ var createEntrySubmissionValidation = ({
1215
+ connector,
1216
+ symbol,
1217
+ direction,
1218
+ takeProfits,
1219
+ stopLossPrice,
1220
+ limits,
1221
+ onValidated
1222
+ }) => {
1223
+ if (!limits) return void 0;
1224
+ if (!connector.capabilities?.orderSubmissionValidation)
1225
+ throw new EntrySubmissionValidationError(
1226
+ "ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
1227
+ );
1228
+ return async ({ qty, executionPrice }) => {
1229
+ const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
1230
+ const snapshot2 = simulated && executionPrice != null ? {
1231
+ arrivalSnapshotTime: Date.now(),
1232
+ arrivalSource: "simulation",
1233
+ bid: executionPrice,
1234
+ ask: executionPrice,
1235
+ arrivalMid: executionPrice,
1236
+ spreadBps: 0
1237
+ } : await getOrderArrivalSnapshot({
1238
+ connector,
1239
+ symbol,
1240
+ requireTimestamp: true
1241
+ });
1242
+ const age = Date.now() - snapshot2.arrivalSnapshotTime;
1243
+ if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
1244
+ throw new EntrySubmissionValidationError(
1245
+ "ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
1246
+ );
1247
+ if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
1248
+ throw new EntrySubmissionValidationError(
1249
+ "ENTRY_QUOTE_STALE_AT_SUBMISSION"
1250
+ );
1251
+ const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
1252
+ validateEntryProtectionAtArrival({
1253
+ direction,
1254
+ signalPrice: price,
1255
+ bid: snapshot2.bid,
1256
+ ask: snapshot2.ask,
1257
+ arrivalMid: snapshot2.arrivalMid,
1258
+ takeProfits,
1259
+ stopLossPrice
1260
+ });
1261
+ const prices = takeProfits.map((tp) => tp.price);
1262
+ const economics2 = validateEntryExecution({
1263
+ direction,
1264
+ price,
1265
+ qty,
1266
+ stopLossPrice,
1267
+ takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
1268
+ limits
1269
+ });
1270
+ onValidated?.(snapshot2, economics2);
1271
+ };
1272
+ };
1273
+
1076
1274
  // src/ai.ts
1077
1275
  var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
1078
1276
  var import_redis = require("@tradejs/infra/redis");
@@ -1657,7 +1855,7 @@ var import_fs = __toESM(require("fs"));
1657
1855
  var import_path = __toESM(require("path"));
1658
1856
  var import_url = require("url");
1659
1857
  var import_config = require("@tradejs/core/config");
1660
- var import_logger = require("@tradejs/infra/logger");
1858
+ var import_logger2 = require("@tradejs/infra/logger");
1661
1859
  var CONFIG_FILE_NAMES = [
1662
1860
  "tradejs.config.ts",
1663
1861
  "tradejs.config.mts",
@@ -1924,11 +2122,11 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
1924
2122
  cachedByCwd.set(cwd, config);
1925
2123
  if (!announcedConfigFile.has(configFilePath)) {
1926
2124
  announcedConfigFile.add(configFilePath);
1927
- import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
2125
+ import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
1928
2126
  }
1929
2127
  return config;
1930
2128
  } catch (error) {
1931
- import_logger.logger.log(
2129
+ import_logger2.logger.log(
1932
2130
  "warn",
1933
2131
  "Failed to load TradeJS config from %s: %s",
1934
2132
  configFilePath,
@@ -2895,7 +3093,7 @@ var import_node_crypto = require("crypto");
2895
3093
  var import_constants = require("@tradejs/core/constants");
2896
3094
  var import_time = require("@tradejs/core/time");
2897
3095
  var import_trade = require("@tradejs/core/trade");
2898
- var import_logger2 = require("@tradejs/infra/logger");
3096
+ var import_logger3 = require("@tradejs/infra/logger");
2899
3097
  var import_redis2 = require("@tradejs/infra/redis");
2900
3098
  var now = () => Date.now();
2901
3099
  var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
@@ -2954,7 +3152,7 @@ var recordRuntimeTradeOpen = async (params) => {
2954
3152
  )
2955
3153
  ]);
2956
3154
  } catch (error) {
2957
- import_logger2.logger.error(
3155
+ import_logger3.logger.error(
2958
3156
  "runtime trade open journal failed: %s %s",
2959
3157
  record.symbol,
2960
3158
  error?.message || String(error)
@@ -3022,7 +3220,7 @@ var recordRuntimeTradeIncrease = async (params) => {
3022
3220
  )
3023
3221
  ]);
3024
3222
  } catch (error) {
3025
- import_logger2.logger.error(
3223
+ import_logger3.logger.error(
3026
3224
  "runtime trade increase journal failed: %s %s",
3027
3225
  symbol,
3028
3226
  error?.message || String(error)
@@ -3129,7 +3327,7 @@ var markRuntimeTradeClosed = async (params) => {
3129
3327
  )
3130
3328
  ]);
3131
3329
  } catch (error) {
3132
- import_logger2.logger.error(
3330
+ import_logger3.logger.error(
3133
3331
  "runtime trade close journal failed: %s %s",
3134
3332
  symbol,
3135
3333
  error?.message || String(error)
@@ -3139,11 +3337,11 @@ var markRuntimeTradeClosed = async (params) => {
3139
3337
  };
3140
3338
 
3141
3339
  // src/strategyHelpers/marketContextStages.ts
3142
- var import_logger7 = require("@tradejs/infra/logger");
3340
+ var import_logger8 = require("@tradejs/infra/logger");
3143
3341
 
3144
3342
  // src/strategyHelpers/binanceMarketContext.ts
3145
3343
  var import_marketContext = require("@tradejs/infra/timescale/marketContext");
3146
- var import_logger3 = require("@tradejs/infra/logger");
3344
+ var import_logger4 = require("@tradejs/infra/logger");
3147
3345
  var import_strategies = require("@tradejs/core/strategies");
3148
3346
 
3149
3347
  // src/binanceBreadthUniverses.ts
@@ -3507,7 +3705,7 @@ var parseEnabledFlag = (value, env) => {
3507
3705
  if (normalized === "live") return env !== "BACKTEST";
3508
3706
  return false;
3509
3707
  };
3510
- var toFiniteNumberOrNull = (value) => {
3708
+ var toFiniteNumberOrNull2 = (value) => {
3511
3709
  const numeric = typeof value === "number" ? value : Number(value);
3512
3710
  return Number.isFinite(numeric) ? numeric : null;
3513
3711
  };
@@ -3632,14 +3830,14 @@ var toTradeFlowContext = (row, interval) => row ? {
3632
3830
  asOfTs: row.ts.getTime(),
3633
3831
  ageMs: row.ageMs,
3634
3832
  stale: row.stale,
3635
- trades: toFiniteNumberOrNull(row.trades),
3636
- buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
3637
- buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
3638
- sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
3639
- buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
3640
- sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
3641
- netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
3642
- netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
3833
+ trades: toFiniteNumberOrNull2(row.trades),
3834
+ buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
3835
+ buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
3836
+ sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
3837
+ buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
3838
+ sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
3839
+ netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
3840
+ netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
3643
3841
  } : null;
3644
3842
  var getCachedReferenceRows = ({
3645
3843
  referenceSymbols,
@@ -3728,16 +3926,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
3728
3926
  asOfTs: breadth.ts.getTime(),
3729
3927
  ageMs: breadth.ageMs,
3730
3928
  stale: breadth.stale,
3731
- symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
3732
- advancers: toFiniteNumberOrNull(breadth.advancers),
3733
- decliners: toFiniteNumberOrNull(breadth.decliners),
3734
- unchanged: toFiniteNumberOrNull(breadth.unchanged),
3735
- advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
3736
- pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
3737
- pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
3738
- equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
3739
- volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
3740
- dispersion: toFiniteNumberOrNull(breadth.dispersion)
3929
+ symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
3930
+ advancers: toFiniteNumberOrNull2(breadth.advancers),
3931
+ decliners: toFiniteNumberOrNull2(breadth.decliners),
3932
+ unchanged: toFiniteNumberOrNull2(breadth.unchanged),
3933
+ advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
3934
+ pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
3935
+ pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
3936
+ equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
3937
+ volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
3938
+ dispersion: toFiniteNumberOrNull2(breadth.dispersion)
3741
3939
  });
3742
3940
  var enrichSignalWithBinanceMarketContext = async (params) => {
3743
3941
  const {
@@ -3838,46 +4036,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3838
4036
  asOfTs: primaryBreadth.ts.getTime(),
3839
4037
  ageMs: primaryBreadth.ageMs,
3840
4038
  stale: primaryBreadth.stale,
3841
- btcReturn1h: toFiniteNumberOrNull(
4039
+ btcReturn1h: toFiniteNumberOrNull2(
3842
4040
  primaryBreadth.btcReturn1h
3843
4041
  ),
3844
- btcReturn4h: toFiniteNumberOrNull(
4042
+ btcReturn4h: toFiniteNumberOrNull2(
3845
4043
  primaryBreadth.btcReturn4h
3846
4044
  ),
3847
- btcReturn24h: toFiniteNumberOrNull(
4045
+ btcReturn24h: toFiniteNumberOrNull2(
3848
4046
  primaryBreadth.btcReturn24h
3849
4047
  ),
3850
- altBasketReturn1h: toFiniteNumberOrNull(
4048
+ altBasketReturn1h: toFiniteNumberOrNull2(
3851
4049
  primaryBreadth.altBasketReturn1h
3852
4050
  ),
3853
- altBasketReturn4h: toFiniteNumberOrNull(
4051
+ altBasketReturn4h: toFiniteNumberOrNull2(
3854
4052
  primaryBreadth.altBasketReturn4h
3855
4053
  ),
3856
- altBasketReturn24h: toFiniteNumberOrNull(
4054
+ altBasketReturn24h: toFiniteNumberOrNull2(
3857
4055
  primaryBreadth.altBasketReturn24h
3858
4056
  ),
3859
- btcVsAltReturn1h: toFiniteNumberOrNull(
4057
+ btcVsAltReturn1h: toFiniteNumberOrNull2(
3860
4058
  primaryBreadth.btcVsAltReturn1h
3861
4059
  ),
3862
- btcVsAltReturn4h: toFiniteNumberOrNull(
4060
+ btcVsAltReturn4h: toFiniteNumberOrNull2(
3863
4061
  primaryBreadth.btcVsAltReturn4h
3864
4062
  ),
3865
- btcVsAltReturn24h: toFiniteNumberOrNull(
4063
+ btcVsAltReturn24h: toFiniteNumberOrNull2(
3866
4064
  primaryBreadth.btcVsAltReturn24h
3867
4065
  ),
3868
- btcTurnoverShare1h: toFiniteNumberOrNull(
4066
+ btcTurnoverShare1h: toFiniteNumberOrNull2(
3869
4067
  primaryBreadth.btcTurnoverShare1h
3870
4068
  ),
3871
- btcTurnoverShare24h: toFiniteNumberOrNull(
4069
+ btcTurnoverShare24h: toFiniteNumberOrNull2(
3872
4070
  primaryBreadth.btcTurnoverShare24h
3873
4071
  ),
3874
- btcTurnoverShareChange24h: toFiniteNumberOrNull(
4072
+ btcTurnoverShareChange24h: toFiniteNumberOrNull2(
3875
4073
  primaryBreadth.btcTurnoverShareChange24h
3876
4074
  ),
3877
- altVolToBtcVol24h: toFiniteNumberOrNull(
4075
+ altVolToBtcVol24h: toFiniteNumberOrNull2(
3878
4076
  primaryBreadth.altVolToBtcVol24h
3879
4077
  ),
3880
- altDispersion24h: toFiniteNumberOrNull(
4078
+ altDispersion24h: toFiniteNumberOrNull2(
3881
4079
  primaryBreadth.altDispersion24h
3882
4080
  ),
3883
4081
  regime: primaryBreadth.btcAltRegime ?? "unknown"
@@ -3894,7 +4092,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3894
4092
  throw error;
3895
4093
  }
3896
4094
  binanceMarketContextUnavailable = true;
3897
- import_logger3.logger.warn(
4095
+ import_logger4.logger.warn(
3898
4096
  "Binance market context disabled after Timescale read failure: %s",
3899
4097
  String(error)
3900
4098
  );
@@ -3904,7 +4102,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3904
4102
 
3905
4103
  // src/strategyHelpers/coinMarketCapContext.ts
3906
4104
  var import_strategies2 = require("@tradejs/core/strategies");
3907
- var import_logger4 = require("@tradejs/infra/logger");
4105
+ var import_logger5 = require("@tradejs/infra/logger");
3908
4106
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
3909
4107
  var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
3910
4108
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
@@ -3942,7 +4140,7 @@ var asInt = (value, fallback) => {
3942
4140
  const parsed = Number.parseInt(String(value ?? ""), 10);
3943
4141
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
3944
4142
  };
3945
- var toFiniteNumberOrNull2 = (value) => {
4143
+ var toFiniteNumberOrNull3 = (value) => {
3946
4144
  const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
3947
4145
  return Number.isFinite(numeric) ? numeric : null;
3948
4146
  };
@@ -4176,12 +4374,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4176
4374
  const ethRow = references.get("ETHUSDT") ?? null;
4177
4375
  const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
4178
4376
  const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
4179
- const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
4180
- const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
4181
- const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
4377
+ const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
4378
+ const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
4379
+ const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
4182
4380
  previousBtcRow?.marketCapUsd
4183
4381
  );
4184
- const previousEthMarketCapUsd = toFiniteNumberOrNull2(
4382
+ const previousEthMarketCapUsd = toFiniteNumberOrNull3(
4185
4383
  previousEthRow?.marketCapUsd
4186
4384
  );
4187
4385
  const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
@@ -4190,16 +4388,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4190
4388
  previousBtcMarketCapUsd
4191
4389
  );
4192
4390
  const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
4193
- const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
4194
- const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
4391
+ const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
4392
+ const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
4195
4393
  const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
4196
- const btcDominanceChange24hPct = toFiniteNumberOrNull2(
4394
+ const btcDominanceChange24hPct = toFiniteNumberOrNull3(
4197
4395
  globalRow?.btcDominanceChange24hPct
4198
4396
  );
4199
- const altMarketCapChange24hPct = toFiniteNumberOrNull2(
4397
+ const altMarketCapChange24hPct = toFiniteNumberOrNull3(
4200
4398
  globalRow?.altMarketCapChange24hPct
4201
4399
  );
4202
- const altVolumeChange24hPct = toFiniteNumberOrNull2(
4400
+ const altVolumeChange24hPct = toFiniteNumberOrNull3(
4203
4401
  globalRow?.altVolumeChange24hPct
4204
4402
  );
4205
4403
  const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
@@ -4210,19 +4408,19 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4210
4408
  }) : "unknown";
4211
4409
  const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
4212
4410
  stale: exchangeLiquidityRow.stale,
4213
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
4411
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
4214
4412
  exchangeLiquidityRow.totalVolumeChange24hPct
4215
4413
  ),
4216
4414
  fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
4217
4415
  }) : "unknown";
4218
4416
  const cmc100Row = indexRows.get("cmc100") ?? null;
4219
4417
  const cmc20Row = indexRows.get("cmc20") ?? null;
4220
- const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
4221
- const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
4222
- const cmc100Change24hPct = toFiniteNumberOrNull2(
4418
+ const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
4419
+ const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
4420
+ const cmc100Change24hPct = toFiniteNumberOrNull3(
4223
4421
  cmc100Row?.valueChange24hPct
4224
4422
  );
4225
- const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
4423
+ const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
4226
4424
  const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
4227
4425
  const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
4228
4426
  const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
@@ -4246,41 +4444,41 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4246
4444
  asOfTs: globalRow.ts.getTime(),
4247
4445
  ageMs: globalRow.ageMs,
4248
4446
  stale: globalRow.stale,
4249
- totalMarketCapUsd: toFiniteNumberOrNull2(
4447
+ totalMarketCapUsd: toFiniteNumberOrNull3(
4250
4448
  globalRow.totalMarketCapUsd
4251
4449
  ),
4252
- totalVolumeUsd: toFiniteNumberOrNull2(
4450
+ totalVolumeUsd: toFiniteNumberOrNull3(
4253
4451
  globalRow.totalVolumeUsd
4254
4452
  ),
4255
- totalVolumeReportedUsd: toFiniteNumberOrNull2(
4453
+ totalVolumeReportedUsd: toFiniteNumberOrNull3(
4256
4454
  globalRow.totalVolumeReportedUsd
4257
4455
  ),
4258
- altMarketCapUsd: toFiniteNumberOrNull2(
4456
+ altMarketCapUsd: toFiniteNumberOrNull3(
4259
4457
  globalRow.altMarketCapUsd
4260
4458
  ),
4261
- altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
4262
- altVolumeReportedUsd: toFiniteNumberOrNull2(
4459
+ altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
4460
+ altVolumeReportedUsd: toFiniteNumberOrNull3(
4263
4461
  globalRow.altVolumeReportedUsd
4264
4462
  ),
4265
- btcDominancePct: toFiniteNumberOrNull2(
4463
+ btcDominancePct: toFiniteNumberOrNull3(
4266
4464
  globalRow.btcDominancePct
4267
4465
  ),
4268
- ethDominancePct: toFiniteNumberOrNull2(
4466
+ ethDominancePct: toFiniteNumberOrNull3(
4269
4467
  globalRow.ethDominancePct
4270
4468
  ),
4271
4469
  btcDominanceChange24hPct,
4272
- ethDominanceChange24hPct: toFiniteNumberOrNull2(
4470
+ ethDominanceChange24hPct: toFiniteNumberOrNull3(
4273
4471
  globalRow.ethDominanceChange24hPct
4274
4472
  ),
4275
4473
  altMarketCapChange24hPct,
4276
4474
  altVolumeChange24hPct,
4277
- activeCryptocurrencies: toFiniteNumberOrNull2(
4475
+ activeCryptocurrencies: toFiniteNumberOrNull3(
4278
4476
  globalRow.activeCryptocurrencies
4279
4477
  ),
4280
- activeExchanges: toFiniteNumberOrNull2(
4478
+ activeExchanges: toFiniteNumberOrNull3(
4281
4479
  globalRow.activeExchanges
4282
4480
  ),
4283
- activeMarketPairs: toFiniteNumberOrNull2(
4481
+ activeMarketPairs: toFiniteNumberOrNull3(
4284
4482
  globalRow.activeMarketPairs
4285
4483
  ),
4286
4484
  altLiquidityRegime
@@ -4325,22 +4523,22 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4325
4523
  asOfTs: exchangeLiquidityRow.ts.getTime(),
4326
4524
  ageMs: exchangeLiquidityRow.ageMs,
4327
4525
  stale: exchangeLiquidityRow.stale,
4328
- exchangesCount: toFiniteNumberOrNull2(
4526
+ exchangesCount: toFiniteNumberOrNull3(
4329
4527
  exchangeLiquidityRow.exchangesCount
4330
4528
  ),
4331
- totalVolumeUsd: toFiniteNumberOrNull2(
4529
+ totalVolumeUsd: toFiniteNumberOrNull3(
4332
4530
  exchangeLiquidityRow.totalVolumeUsd
4333
4531
  ),
4334
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
4532
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
4335
4533
  exchangeLiquidityRow.totalVolumeChange24hPct
4336
4534
  ),
4337
- binanceVolumeUsd: toFiniteNumberOrNull2(
4535
+ binanceVolumeUsd: toFiniteNumberOrNull3(
4338
4536
  exchangeLiquidityRow.binanceVolumeUsd
4339
4537
  ),
4340
- binanceVolumeShare: toFiniteNumberOrNull2(
4538
+ binanceVolumeShare: toFiniteNumberOrNull3(
4341
4539
  exchangeLiquidityRow.binanceVolumeShare
4342
4540
  ),
4343
- topExchangeVolumeShare: toFiniteNumberOrNull2(
4541
+ topExchangeVolumeShare: toFiniteNumberOrNull3(
4344
4542
  exchangeLiquidityRow.topExchangeVolumeShare
4345
4543
  ),
4346
4544
  liquidityRegime: exchangeLiquidityRegime
@@ -4353,11 +4551,11 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4353
4551
  asOfTs: fearGreedRow.ts.getTime(),
4354
4552
  ageMs: fearGreedRow.ageMs,
4355
4553
  stale: fearGreedRow.stale,
4356
- value: toFiniteNumberOrNull2(fearGreedRow.value),
4357
- valueChange24h: toFiniteNumberOrNull2(
4554
+ value: toFiniteNumberOrNull3(fearGreedRow.value),
4555
+ valueChange24h: toFiniteNumberOrNull3(
4358
4556
  fearGreedRow.valueChange24h
4359
4557
  ),
4360
- valueChange7d: toFiniteNumberOrNull2(
4558
+ valueChange7d: toFiniteNumberOrNull3(
4361
4559
  fearGreedRow.valueChange7d
4362
4560
  ),
4363
4561
  classification: fearGreedRow.classification ?? "Unknown",
@@ -4377,13 +4575,13 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4377
4575
  cmc100Value,
4378
4576
  cmc100Change24hPct,
4379
4577
  cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
4380
- cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
4578
+ cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
4381
4579
  cmc100Row?.topConstituentWeightPct
4382
4580
  ),
4383
4581
  cmc20Value,
4384
4582
  cmc20Change24hPct,
4385
4583
  cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
4386
- cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
4584
+ cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
4387
4585
  cmc20Row?.topConstituentWeightPct
4388
4586
  ),
4389
4587
  cmc20ToCmc100Ratio,
@@ -4401,7 +4599,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4401
4599
  throw error;
4402
4600
  }
4403
4601
  coinMarketCapContextUnavailable = true;
4404
- import_logger4.logger.warn(
4602
+ import_logger5.logger.warn(
4405
4603
  "CoinMarketCap context disabled after Timescale read failure: %s",
4406
4604
  String(error)
4407
4605
  );
@@ -4415,7 +4613,7 @@ var import_data2 = require("@tradejs/core/data");
4415
4613
  var import_strategies3 = require("@tradejs/core/strategies");
4416
4614
  var import_constants2 = require("@tradejs/core/constants");
4417
4615
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
4418
- var import_logger5 = require("@tradejs/infra/logger");
4616
+ var import_logger6 = require("@tradejs/infra/logger");
4419
4617
  var STORED_INTERVALS = ["15m", "1h"];
4420
4618
  var CONTEXT_INTERVALS = ["15m", "1h"];
4421
4619
  var DEFAULT_LOOKBACK_HOURS = 48;
@@ -4470,7 +4668,7 @@ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
4470
4668
  var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
4471
4669
  var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
4472
4670
  var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
4473
- var toFiniteNumberOrNull3 = (value) => {
4671
+ var toFiniteNumberOrNull4 = (value) => {
4474
4672
  if (typeof value === "number" && Number.isFinite(value)) return value;
4475
4673
  if (typeof value === "string" && value.trim()) {
4476
4674
  const parsed = Number(value);
@@ -4484,8 +4682,8 @@ var roundNullable = (value, digits = 4) => {
4484
4682
  return Math.round(value * multiplier) / multiplier;
4485
4683
  };
4486
4684
  var deltaNullable = (targetValue, referenceValue) => {
4487
- const target = toFiniteNumberOrNull3(targetValue);
4488
- const reference = toFiniteNumberOrNull3(referenceValue);
4685
+ const target = toFiniteNumberOrNull4(targetValue);
4686
+ const reference = toFiniteNumberOrNull4(referenceValue);
4489
4687
  return target == null || reference == null ? null : roundNullable(target - reference);
4490
4688
  };
4491
4689
  var buildTargetDerivedContext = (params) => {
@@ -4647,7 +4845,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4647
4845
  throw error;
4648
4846
  }
4649
4847
  derivativesContextUnavailable = true;
4650
- import_logger5.logger.warn(
4848
+ import_logger6.logger.warn(
4651
4849
  "Derivatives context disabled after Timescale read failure: %s",
4652
4850
  String(error)
4653
4851
  );
@@ -4659,7 +4857,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4659
4857
  var import_data3 = require("@tradejs/core/data");
4660
4858
  var import_strategies4 = require("@tradejs/core/strategies");
4661
4859
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
4662
- var import_logger6 = require("@tradejs/infra/logger");
4860
+ var import_logger7 = require("@tradejs/infra/logger");
4663
4861
 
4664
4862
  // src/hyperliquidWhaleUniverse.ts
4665
4863
  var import_node_crypto3 = require("crypto");
@@ -5179,7 +5377,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
5179
5377
  throw error;
5180
5378
  }
5181
5379
  hyperliquidWhaleContextUnavailable = true;
5182
- import_logger6.logger.warn(
5380
+ import_logger7.logger.warn(
5183
5381
  "Hyperliquid whale context disabled after Timescale read failure: %s",
5184
5382
  String(error)
5185
5383
  );
@@ -5262,7 +5460,7 @@ var runMarketContextStage = async ({
5262
5460
  elapsedMs: Date.now() - startedAt
5263
5461
  };
5264
5462
  if (status === "timed_out") {
5265
- import_logger7.logger.warn(
5463
+ import_logger8.logger.warn(
5266
5464
  "Market context stage timed out: %s after %sms",
5267
5465
  stage,
5268
5466
  result.elapsedMs
@@ -5452,79 +5650,10 @@ var enrichSignalWithAi = async ({
5452
5650
  signal.aiAnalysis = analysis;
5453
5651
  return resolveAiQuality(analysis, direction);
5454
5652
  } catch (err) {
5455
- import_logger8.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
5653
+ import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
5456
5654
  }
5457
5655
  return void 0;
5458
5656
  };
5459
- var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
5460
- var getOrderArrivalSnapshot = async ({
5461
- connector,
5462
- symbol
5463
- }) => {
5464
- if (typeof connector.getTopOfBookTicker !== "function") {
5465
- return {
5466
- arrivalSnapshotTime: Date.now(),
5467
- arrivalSource: "unavailable",
5468
- bid: null,
5469
- ask: null,
5470
- arrivalMid: null,
5471
- spreadBps: null
5472
- };
5473
- }
5474
- try {
5475
- const ticker = await connector.getTopOfBookTicker(symbol);
5476
- const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
5477
- const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
5478
- const ask = toFiniteNumberOrNull4(ticker?.askPrice);
5479
- const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
5480
- const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
5481
- return {
5482
- arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
5483
- arrivalSource: "top_of_book",
5484
- bid,
5485
- ask,
5486
- arrivalMid,
5487
- spreadBps
5488
- };
5489
- } catch (error) {
5490
- import_logger8.logger.warn(
5491
- "runtime order arrival snapshot failed: %s %s",
5492
- symbol,
5493
- error?.message || String(error)
5494
- );
5495
- return {
5496
- arrivalSnapshotTime: Date.now(),
5497
- arrivalSource: "top_of_book_error",
5498
- bid: null,
5499
- ask: null,
5500
- arrivalMid: null,
5501
- spreadBps: null
5502
- };
5503
- }
5504
- };
5505
- var validateEntryProtectionAtArrival = ({
5506
- direction,
5507
- signalPrice,
5508
- bid,
5509
- ask,
5510
- arrivalMid,
5511
- takeProfits,
5512
- stopLossPrice
5513
- }) => {
5514
- const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
5515
- const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
5516
- if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
5517
- throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
5518
- }
5519
- for (const takeProfit of takeProfits) {
5520
- if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
5521
- throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
5522
- }
5523
- }
5524
- if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
5525
- throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
5526
- }
5527
- };
5528
5657
  var resolveRuntimeTelemetryQuality = ({
5529
5658
  signalClosePrice,
5530
5659
  arrivalMid,
@@ -5601,7 +5730,8 @@ var executeEntryOrder = async ({
5601
5730
  signal,
5602
5731
  beforePlaceOrder,
5603
5732
  recordRuntimeTrade = true,
5604
- leverage
5733
+ leverage,
5734
+ executionLimits
5605
5735
  }) => {
5606
5736
  await beforePlaceOrder?.();
5607
5737
  const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
@@ -5612,23 +5742,57 @@ var executeEntryOrder = async ({
5612
5742
  signal.orderQty = qty;
5613
5743
  signal.orderValue = qty * currentPrice;
5614
5744
  signal.orderFailureReason = void 0;
5615
- const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
5616
- try {
5617
- validateEntryProtectionAtArrival({
5618
- direction,
5619
- signalPrice: currentPrice,
5620
- bid: arrivalSnapshot.bid,
5621
- ask: arrivalSnapshot.ask,
5622
- arrivalMid: arrivalSnapshot.arrivalMid,
5623
- takeProfits,
5624
- stopLossPrice
5625
- });
5626
- } catch (error) {
5627
- signal.orderStatus = "failed";
5628
- signal.orderFailureReason = error.message;
5629
- throw error;
5745
+ let arrivalSnapshot = {
5746
+ arrivalSnapshotTime: Date.now(),
5747
+ arrivalSource: "unavailable",
5748
+ bid: null,
5749
+ ask: null,
5750
+ arrivalMid: null,
5751
+ spreadBps: null
5752
+ };
5753
+ let orderSubmitTime = Date.now();
5754
+ const beforeSubmit = createEntrySubmissionValidation({
5755
+ connector,
5756
+ symbol,
5757
+ direction,
5758
+ takeProfits,
5759
+ stopLossPrice,
5760
+ limits: executionLimits,
5761
+ onValidated: (snapshot2, economics2) => {
5762
+ arrivalSnapshot = snapshot2;
5763
+ orderSubmitTime = Date.now();
5764
+ if (executionLimits?.maxLossValue != null) {
5765
+ signal.riskDecision = {
5766
+ stage: "order_plan",
5767
+ status: economics2.riskExceeded ? "warning" : "approved",
5768
+ reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
5769
+ plannedLossValue: economics2.plannedLossValue,
5770
+ maxLossValue: executionLimits.maxLossValue,
5771
+ limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
5772
+ enforced: false
5773
+ };
5774
+ }
5775
+ }
5776
+ });
5777
+ if (!beforeSubmit) {
5778
+ arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
5779
+ try {
5780
+ validateEntryProtectionAtArrival({
5781
+ direction,
5782
+ signalPrice: currentPrice,
5783
+ bid: arrivalSnapshot.bid,
5784
+ ask: arrivalSnapshot.ask,
5785
+ arrivalMid: arrivalSnapshot.arrivalMid,
5786
+ takeProfits,
5787
+ stopLossPrice
5788
+ });
5789
+ } catch (error) {
5790
+ signal.orderStatus = "failed";
5791
+ signal.orderFailureReason = error.message;
5792
+ throw error;
5793
+ }
5794
+ orderSubmitTime = Date.now();
5630
5795
  }
5631
- const orderSubmitTime = Date.now();
5632
5796
  const orderPlaced = await connector.placeOrder({
5633
5797
  symbol,
5634
5798
  qty,
@@ -5639,7 +5803,8 @@ var executeEntryOrder = async ({
5639
5803
  direction,
5640
5804
  ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
5641
5805
  orderId,
5642
- signal
5806
+ signal,
5807
+ ...beforeSubmit ? { beforeSubmit } : {}
5643
5808
  });
5644
5809
  const orderAckTime = Date.now();
5645
5810
  const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
@@ -6468,7 +6633,7 @@ var canUseSharedReplayState = ({
6468
6633
  }) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
6469
6634
 
6470
6635
  // src/strategy/runtimeExecution.ts
6471
- var import_logger9 = require("@tradejs/infra/logger");
6636
+ var import_logger10 = require("@tradejs/infra/logger");
6472
6637
  var import_types = require("@tradejs/types");
6473
6638
  var buildExitOrderSignal = ({
6474
6639
  strategyName,
@@ -6520,7 +6685,7 @@ var handleExitDecision = async ({
6520
6685
  deploymentId: connector.deploymentId
6521
6686
  });
6522
6687
  if (!activeTrade) {
6523
- import_logger9.logger.warn(
6688
+ import_logger10.logger.warn(
6524
6689
  "[%s] blocked closePosition for untracked runtime position: %s",
6525
6690
  strategyName ?? "unknown",
6526
6691
  symbol
@@ -6528,7 +6693,7 @@ var handleExitDecision = async ({
6528
6693
  return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
6529
6694
  }
6530
6695
  if (!strategyName || activeTrade.strategy !== strategyName) {
6531
- import_logger9.logger.warn(
6696
+ import_logger10.logger.warn(
6532
6697
  "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
6533
6698
  strategyName ?? "unknown",
6534
6699
  symbol,
@@ -6581,7 +6746,7 @@ var handleExitDecision = async ({
6581
6746
  exitType: closedTrade?.exitType ?? "exit"
6582
6747
  });
6583
6748
  } catch (notificationError) {
6584
- import_logger9.logger.error(
6749
+ import_logger10.logger.error(
6585
6750
  "runtime close notification error: %s %s",
6586
6751
  symbol,
6587
6752
  notificationError
@@ -6595,7 +6760,7 @@ var handleExitDecision = async ({
6595
6760
  decision,
6596
6761
  market
6597
6762
  });
6598
- import_logger9.logger.error("close order error: %s %s", symbol, err);
6763
+ import_logger10.logger.error("close order error: %s %s", symbol, err);
6599
6764
  return "ORDER_ERROR";
6600
6765
  }
6601
6766
  return decision.code;
@@ -6622,7 +6787,7 @@ var handleProtectDecision = async ({
6622
6787
  decision,
6623
6788
  market
6624
6789
  });
6625
- import_logger9.logger.error("protect position error: %s %s", symbol, err);
6790
+ import_logger10.logger.error("protect position error: %s %s", symbol, err);
6626
6791
  return "ORDER_ERROR";
6627
6792
  }
6628
6793
  return decision.code;
@@ -6688,7 +6853,11 @@ var executeEntryDecision = async ({
6688
6853
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
6689
6854
  signal,
6690
6855
  beforePlaceOrder,
6691
- recordRuntimeTrade: recordRuntimeJournal
6856
+ recordRuntimeTrade: recordRuntimeJournal,
6857
+ executionLimits: resolveEntryExecutionLimits(
6858
+ hookCtx.strategyConfig,
6859
+ decision.entryContext.direction
6860
+ )
6692
6861
  });
6693
6862
  await invokeStageHooks(
6694
6863
  "afterPlaceOrder",
@@ -6710,19 +6879,32 @@ var executeEntryDecision = async ({
6710
6879
  return signal;
6711
6880
  }
6712
6881
  await beforePlaceOrder();
6713
- const arrivalSnapshot = await getOrderArrivalSnapshot({
6882
+ const beforeSubmit = createEntrySubmissionValidation({
6714
6883
  connector,
6715
- symbol
6716
- });
6717
- validateEntryProtectionAtArrival({
6884
+ symbol,
6718
6885
  direction: decision.entryContext.direction,
6719
- signalPrice: decision.entryContext.prices.currentPrice,
6720
- bid: arrivalSnapshot.bid,
6721
- ask: arrivalSnapshot.ask,
6722
- arrivalMid: arrivalSnapshot.arrivalMid,
6723
6886
  takeProfits: decision.orderPlan.takeProfits,
6724
- stopLossPrice: decision.orderPlan.stopLossPrice
6887
+ stopLossPrice: decision.orderPlan.stopLossPrice,
6888
+ limits: resolveEntryExecutionLimits(
6889
+ hookCtx.strategyConfig,
6890
+ decision.entryContext.direction
6891
+ )
6725
6892
  });
6893
+ if (!beforeSubmit) {
6894
+ const arrivalSnapshot = await getOrderArrivalSnapshot({
6895
+ connector,
6896
+ symbol
6897
+ });
6898
+ validateEntryProtectionAtArrival({
6899
+ direction: decision.entryContext.direction,
6900
+ signalPrice: decision.entryContext.prices.currentPrice,
6901
+ bid: arrivalSnapshot.bid,
6902
+ ask: arrivalSnapshot.ask,
6903
+ arrivalMid: arrivalSnapshot.arrivalMid,
6904
+ takeProfits: decision.orderPlan.takeProfits,
6905
+ stopLossPrice: decision.orderPlan.stopLossPrice
6906
+ });
6907
+ }
6726
6908
  const orderPlaced = await connector.placeOrder({
6727
6909
  symbol,
6728
6910
  qty: decision.orderPlan.qty,
@@ -6730,6 +6912,7 @@ var executeEntryDecision = async ({
6730
6912
  timestamp: decision.entryContext.timestamp,
6731
6913
  direction: decision.entryContext.direction,
6732
6914
  positionIntent: decision.orderPlan.positionIntent,
6915
+ ...beforeSubmit ? { beforeSubmit } : {},
6733
6916
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
6734
6917
  });
6735
6918
  if (!orderPlaced) {
@@ -6771,6 +6954,23 @@ var executeEntryDecision = async ({
6771
6954
  { decision, entry, market }
6772
6955
  );
6773
6956
  } catch (err) {
6957
+ if (err instanceof EntrySubmissionValidationError) {
6958
+ if (signal) {
6959
+ signal.orderStatus = "skipped";
6960
+ signal.orderSkipReason = err.message;
6961
+ signal.allocatorDecision = {
6962
+ stage: "runtime_admission",
6963
+ status: "rejected",
6964
+ reason: err.message
6965
+ };
6966
+ }
6967
+ import_logger10.logger.warn(
6968
+ "entry rejected before submission: %s %s",
6969
+ symbol,
6970
+ err.message
6971
+ );
6972
+ return signal ?? err.message;
6973
+ }
6774
6974
  if (signal) {
6775
6975
  signal.orderStatus = "failed";
6776
6976
  if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
@@ -6785,9 +6985,9 @@ var executeEntryDecision = async ({
6785
6985
  market
6786
6986
  });
6787
6987
  if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
6788
- import_logger9.logger.warn("order warning: %s %s", symbol, err);
6988
+ import_logger10.logger.warn("order warning: %s %s", symbol, err);
6789
6989
  } else {
6790
- import_logger9.logger.error("order error: %s %s", symbol, err);
6990
+ import_logger10.logger.error("order error: %s %s", symbol, err);
6791
6991
  }
6792
6992
  return signal ?? "ORDER_ERROR";
6793
6993
  }
@@ -6895,6 +7095,9 @@ var createStrategyRuntime = ({
6895
7095
  });
6896
7096
  const strategyPolicyProfile = getPolicyProfile();
6897
7097
  const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
7098
+ const strategyConfig = { ...config };
7099
+ delete strategyConfig.RECORD_RUNTIME_TRADES;
7100
+ delete strategyConfig.SIMULATE_ORDERS;
6898
7101
  const hookBase = {
6899
7102
  connector,
6900
7103
  strategyName,
@@ -6905,7 +7108,7 @@ var createStrategyRuntime = ({
6905
7108
  accountId,
6906
7109
  deploymentId,
6907
7110
  policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
6908
- strategyConfig: config,
7111
+ strategyConfig,
6909
7112
  env,
6910
7113
  isConfigFromBacktest
6911
7114
  };
@@ -6952,7 +7155,7 @@ var createStrategyRuntime = ({
6952
7155
  try {
6953
7156
  await projectHook(errorParams);
6954
7157
  } catch (hookError) {
6955
- import_logger10.logger.error(
7158
+ import_logger11.logger.error(
6956
7159
  "project hook onRuntimeError failed: %s %s",
6957
7160
  strategyName,
6958
7161
  hookError
@@ -6966,21 +7169,21 @@ var createStrategyRuntime = ({
6966
7169
  try {
6967
7170
  await onRuntimeError(errorParams);
6968
7171
  } catch (hookError) {
6969
- import_logger10.logger.error(
7172
+ import_logger11.logger.error(
6970
7173
  "runtime hook onRuntimeError failed: %s %s",
6971
7174
  strategyName,
6972
7175
  hookError
6973
7176
  );
6974
7177
  }
6975
7178
  };
6976
- const invokeHook = async (stage, hook, params, errorContext = {}) => {
7179
+ const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
6977
7180
  if (!hook) {
6978
7181
  return void 0;
6979
7182
  }
6980
7183
  try {
6981
7184
  return await hook(params);
6982
7185
  } catch (error) {
6983
- import_logger10.logger.error(
7186
+ import_logger11.logger.error(
6984
7187
  'strategy hook "%s" failed for %s: %s',
6985
7188
  stage,
6986
7189
  strategyName,
@@ -6993,17 +7196,18 @@ var createStrategyRuntime = ({
6993
7196
  entry: errorContext.entry,
6994
7197
  market: errorContext.market
6995
7198
  });
6996
- return void 0;
7199
+ return failureResult;
6997
7200
  }
6998
7201
  };
6999
- const invokeProjectHooks = async (stage, params, errorContext = {}) => {
7202
+ const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
7000
7203
  const results = [];
7001
7204
  for (const hook of getProjectHookList(stage)) {
7002
7205
  const result = await invokeHook(
7003
7206
  stage,
7004
7207
  hook,
7005
7208
  params,
7006
- errorContext
7209
+ errorContext,
7210
+ failureResult
7007
7211
  );
7008
7212
  if (result !== void 0) {
7009
7213
  results.push(result);
@@ -7018,10 +7222,12 @@ var createStrategyRuntime = ({
7018
7222
  return invokeHook(stage, hook, params, errorContext);
7019
7223
  };
7020
7224
  const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
7225
+ const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
7021
7226
  const projectResults = await invokeProjectHooks(
7022
7227
  stage,
7023
7228
  params,
7024
- errorContext
7229
+ errorContext,
7230
+ failureResult
7025
7231
  );
7026
7232
  const projectBlock = projectResults.find(
7027
7233
  (result) => result?.allow === false
@@ -7033,7 +7239,8 @@ var createStrategyRuntime = ({
7033
7239
  stage,
7034
7240
  hook,
7035
7241
  params,
7036
- errorContext
7242
+ errorContext,
7243
+ failureResult
7037
7244
  );
7038
7245
  };
7039
7246
  const applyProjectAfterCoreDecisionHooks = async ({
@@ -7179,11 +7386,8 @@ var createStrategyRuntime = ({
7179
7386
  return enrichedBaseContext;
7180
7387
  }
7181
7388
  });
7182
- const coreConfig = { ...config };
7183
- delete coreConfig.RECORD_RUNTIME_TRADES;
7184
- delete coreConfig.SIMULATE_ORDERS;
7185
7389
  const core = await createCore({
7186
- config: coreConfig,
7390
+ config: strategyConfig,
7187
7391
  data,
7188
7392
  strategyApi,
7189
7393
  indicatorsState
@@ -7802,10 +8006,10 @@ var createStrategyRuntime = ({
7802
8006
  setStrategyRuntimeFactory(createStrategyRuntime);
7803
8007
 
7804
8008
  // src/connectorsRegistry.ts
7805
- var import_logger13 = require("@tradejs/infra/logger");
8009
+ var import_logger14 = require("@tradejs/infra/logger");
7806
8010
 
7807
8011
  // src/connectorRuntime.ts
7808
- var import_logger12 = require("@tradejs/infra/logger");
8012
+ var import_logger13 = require("@tradejs/infra/logger");
7809
8013
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
7810
8014
 
7811
8015
  // src/timescaleCachedKline.ts
@@ -7813,7 +8017,7 @@ var import_constants6 = require("@tradejs/core/constants");
7813
8017
  var import_data5 = require("@tradejs/core/data");
7814
8018
  var import_time2 = require("@tradejs/core/time");
7815
8019
  var import_async = require("@tradejs/core/async");
7816
- var import_logger11 = require("@tradejs/infra/logger");
8020
+ var import_logger12 = require("@tradejs/infra/logger");
7817
8021
  var import_candles = require("@tradejs/infra/timescale/candles");
7818
8022
  var DEFAULT_LIMIT = 1e3;
7819
8023
  var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
@@ -7923,7 +8127,7 @@ var createTimescaleCachedKline = ({
7923
8127
  }) => {
7924
8128
  const intMinutes = intervalToMinutes(interval);
7925
8129
  if (!intMinutes) {
7926
- import_logger11.logger.log("error", "refreshTail: invalid interval %s", interval);
8130
+ import_logger12.logger.log("error", "refreshTail: invalid interval %s", interval);
7927
8131
  return;
7928
8132
  }
7929
8133
  const intervalMs = intervalMsOf(intMinutes);
@@ -7954,7 +8158,7 @@ var createTimescaleCachedKline = ({
7954
8158
  }) => {
7955
8159
  const intMinutes = intervalToMinutes(interval);
7956
8160
  if (!intMinutes) {
7957
- import_logger11.logger.log("error", "kline: invalid interval %s", interval);
8161
+ import_logger12.logger.log("error", "kline: invalid interval %s", interval);
7958
8162
  return [];
7959
8163
  }
7960
8164
  if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
@@ -8063,7 +8267,7 @@ var createTimescaleCachedKline = ({
8063
8267
  if (warmOnly) {
8064
8268
  if (isTimescaleFallbackMode) {
8065
8269
  isTimescaleFallbackMode = false;
8066
- import_logger11.logger.log("info", "TimescaleDB connection restored for kline cache");
8270
+ import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
8067
8271
  }
8068
8272
  return [];
8069
8273
  }
@@ -8081,13 +8285,13 @@ var createTimescaleCachedKline = ({
8081
8285
  );
8082
8286
  if (isTimescaleFallbackMode) {
8083
8287
  isTimescaleFallbackMode = false;
8084
- import_logger11.logger.log("info", "TimescaleDB connection restored for kline cache");
8288
+ import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
8085
8289
  }
8086
8290
  return dbData;
8087
8291
  } catch (error) {
8088
8292
  if (!isTimescaleFallbackMode) {
8089
8293
  isTimescaleFallbackMode = true;
8090
- import_logger11.logger.log(
8294
+ import_logger12.logger.log(
8091
8295
  "warn",
8092
8296
  "TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
8093
8297
  symbol,
@@ -8111,7 +8315,7 @@ var createTimescaleCachedKline = ({
8111
8315
 
8112
8316
  // src/connectorRuntime.ts
8113
8317
  var connectorRuntime = {
8114
- logger: import_logger12.logger,
8318
+ logger: import_logger13.logger,
8115
8319
  resolveTradingAccount: import_tradingAccounts.resolveTradingAccount,
8116
8320
  createCachedKline: createTimescaleCachedKline
8117
8321
  };
@@ -8172,7 +8376,7 @@ var normalizeProviders = (providers, connectorName) => {
8172
8376
  var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
8173
8377
  const existing = providerToConnectorName.get(provider);
8174
8378
  if (existing && existing !== connectorName) {
8175
- import_logger13.logger.warn(
8379
+ import_logger14.logger.warn(
8176
8380
  'Skip duplicate connector provider "%s" from %s: already mapped to %s',
8177
8381
  provider,
8178
8382
  source,
@@ -8185,11 +8389,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
8185
8389
  var registerEntry = (entry, source, state) => {
8186
8390
  const connectorName = String(entry?.name ?? "").trim();
8187
8391
  if (!connectorName) {
8188
- import_logger13.logger.warn("Skip connector entry without name from %s", source);
8392
+ import_logger14.logger.warn("Skip connector entry without name from %s", source);
8189
8393
  return;
8190
8394
  }
8191
8395
  if (typeof entry.creator !== "function") {
8192
- import_logger13.logger.warn(
8396
+ import_logger14.logger.warn(
8193
8397
  'Skip connector entry "%s" from %s: creator must be a function',
8194
8398
  connectorName,
8195
8399
  source
@@ -8201,7 +8405,7 @@ var registerEntry = (entry, source, state) => {
8201
8405
  state.connectorCreators
8202
8406
  );
8203
8407
  if (existingByName) {
8204
- import_logger13.logger.warn(
8408
+ import_logger14.logger.warn(
8205
8409
  'Skip duplicate connector "%s" from %s: already registered as %s',
8206
8410
  connectorName,
8207
8411
  source,
@@ -8276,7 +8480,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
8276
8480
  );
8277
8481
  const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
8278
8482
  if (!pluginDefinition) {
8279
- import_logger13.logger.warn(
8483
+ import_logger14.logger.warn(
8280
8484
  'Skip connector plugin "%s": export { connectorEntries } is missing',
8281
8485
  moduleName
8282
8486
  );
@@ -8284,7 +8488,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
8284
8488
  }
8285
8489
  registerEntries2(pluginDefinition.connectorEntries, moduleName, state);
8286
8490
  } catch (error) {
8287
- import_logger13.logger.warn(
8491
+ import_logger14.logger.warn(
8288
8492
  'Failed to load connector plugin "%s": %s',
8289
8493
  moduleName,
8290
8494
  String(error)
@@ -8850,6 +9054,7 @@ var loadResolvedRuntimeStrategies = async ({
8850
9054
  var import_node_crypto5 = require("crypto");
8851
9055
  var import_promises3 = require("fs/promises");
8852
9056
  var import_node_path3 = __toESM(require("path"));
9057
+ var import_node_util = require("util");
8853
9058
  var import_redis3 = require("@tradejs/infra/redis");
8854
9059
  var MCP_JOB_TTL = 7 * 86400;
8855
9060
  var hash = (value) => (0, import_node_crypto5.createHash)("sha256").update(value).digest("hex");
@@ -9015,10 +9220,29 @@ var readAuthorizedMcpJob = async (principal, id) => {
9015
9220
  throw new Error("Insufficient scope");
9016
9221
  return job;
9017
9222
  };
9018
- var redactMcpJobLog = (line) => line.replace(/\u001b\[[0-9;]*m/g, "").replace(/\b(Bearer|Basic)\s+\S+/gi, "$1 [redacted]").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]*:[^\s/@]+@/gi, "$1[redacted]@").replace(/((?:authorization|cookie)\s*:\s*)[^\r\n]+/gi, "$1[redacted]").replace(
9223
+ var redactMcpLogLine = (line) => line.replace(/\u001b\[[0-9;]*m/g, "").replace(/\b(Bearer|Basic)\s+\S+/gi, "$1 [redacted]").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]*:[^\s/@]+@/gi, "$1[redacted]@").replace(/((?:authorization|cookie)\s*:\s*)[^\r\n]+/gi, "$1[redacted]").replace(
9019
9224
  /((?:[\w.-]*(?:secret|password|token|api[_-]?key|private[_-]?key|authorization|cookie|credential)[\w.-]*)["']?\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;&}]+)/gi,
9020
9225
  "$1[redacted]"
9021
- ).slice(0, 1e3);
9226
+ );
9227
+ var MCP_LOG_LINE_LIMIT = 1e3;
9228
+ var redactMcpJobLog = (line) => redactMcpLogLine(line).slice(0, MCP_LOG_LINE_LIMIT);
9229
+ var formatMcpArtifactLogLine = (line) => {
9230
+ const text = redactMcpLogLine(
9231
+ (0, import_node_util.stripVTControlCharacters)(line).replace(/\r/g, "")
9232
+ );
9233
+ const fragments = [];
9234
+ let start = 0;
9235
+ while (start < text.length) {
9236
+ let end = Math.min(start + MCP_LOG_LINE_LIMIT, text.length);
9237
+ const lastCodeUnit = text.charCodeAt(end - 1);
9238
+ if (end < text.length && lastCodeUnit >= 55296 && lastCodeUnit <= 56319) {
9239
+ end -= 1;
9240
+ }
9241
+ fragments.push(redactMcpJobLog(text.slice(start, end)));
9242
+ start = end;
9243
+ }
9244
+ return fragments.join("\n") + "\n";
9245
+ };
9022
9246
  var readMcpJobLogs = async (principal, id, offset = 0, limit = 50) => {
9023
9247
  if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 100)
9024
9248
  throw new Error("Select a non-negative log offset and a limit of 1\u2013100");
@@ -9481,6 +9705,7 @@ var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
9481
9705
  McpCursorError,
9482
9706
  cancelMcpJob,
9483
9707
  enqueueMcpJob,
9708
+ formatMcpArtifactLogLine,
9484
9709
  isMcpRecord,
9485
9710
  mcpId,
9486
9711
  mcpJobDirectory,