@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/backtest.js CHANGED
@@ -799,7 +799,7 @@ var import_indicators3 = require("@tradejs/core/indicators");
799
799
  var import_constants9 = require("@tradejs/core/constants");
800
800
  var import_strategies7 = require("@tradejs/core/strategies");
801
801
  var import_time3 = require("@tradejs/core/time");
802
- var import_logger14 = require("@tradejs/infra/logger");
802
+ var import_logger15 = require("@tradejs/infra/logger");
803
803
 
804
804
  // src/strategyRuntime.ts
805
805
  var import_jev6 = require("@tradejs/core/jev");
@@ -1054,13 +1054,210 @@ var assessSignalWithJev = async ({
1054
1054
  // src/strategyRuntime.ts
1055
1055
  var import_constants5 = require("@tradejs/core/constants");
1056
1056
  var import_strategies6 = require("@tradejs/core/strategies");
1057
- var import_logger10 = require("@tradejs/infra/logger");
1057
+ var import_logger11 = require("@tradejs/infra/logger");
1058
1058
 
1059
1059
  // src/strategyHelpers/runtime.ts
1060
- var import_logger8 = require("@tradejs/infra/logger");
1060
+ var import_logger9 = require("@tradejs/infra/logger");
1061
1061
  var import_constants3 = require("@tradejs/core/constants");
1062
1062
  var import_ml2 = require("@tradejs/infra/ml");
1063
1063
 
1064
+ // src/strategy/entrySubmissionValidation.ts
1065
+ var import_logger = require("@tradejs/infra/logger");
1066
+
1067
+ // src/strategy/entryExecutionValidation.ts
1068
+ var EntrySubmissionValidationError = class extends Error {
1069
+ constructor() {
1070
+ super(...arguments);
1071
+ this.name = "EntrySubmissionValidationError";
1072
+ }
1073
+ };
1074
+ var resolveEntryExecutionLimits = (config, direction) => {
1075
+ const side = config[direction];
1076
+ const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
1077
+ const maximum = config.MAX_LOSS_VALUE;
1078
+ if (minimum == null && maximum == null) return void 0;
1079
+ const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
1080
+ const maxLossValue = maximum == null ? void 0 : Number(maximum);
1081
+ const feeRate = Number(config.RISK_FEE_RATE ?? 0);
1082
+ const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
1083
+ const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
1084
+ const slippageBps = slippage + impact;
1085
+ 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)
1086
+ throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
1087
+ return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
1088
+ };
1089
+ var validateEntryExecution = ({
1090
+ direction,
1091
+ price,
1092
+ qty,
1093
+ stopLossPrice,
1094
+ takeProfitPrice,
1095
+ limits
1096
+ }) => {
1097
+ if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
1098
+ throw new EntrySubmissionValidationError(
1099
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1100
+ );
1101
+ const side = direction === "LONG" ? 1 : -1;
1102
+ if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
1103
+ throw new EntrySubmissionValidationError(
1104
+ "INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
1105
+ );
1106
+ const slippage = limits.slippageBps / 1e4;
1107
+ const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
1108
+ const entryCost = cost(price, side);
1109
+ const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
1110
+ const rewardPerUnit = Math.max(
1111
+ 0,
1112
+ Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
1113
+ );
1114
+ const netRiskRatio = rewardPerUnit / lossPerUnit;
1115
+ const plannedLossValue = qty * lossPerUnit;
1116
+ if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
1117
+ throw new EntrySubmissionValidationError(
1118
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
1119
+ );
1120
+ if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
1121
+ throw new EntrySubmissionValidationError(
1122
+ "ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
1123
+ );
1124
+ const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
1125
+ return { netRiskRatio, plannedLossValue, riskExceeded };
1126
+ };
1127
+
1128
+ // src/strategy/entrySubmissionValidation.ts
1129
+ var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
1130
+ var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1131
+ var getOrderArrivalSnapshot = async ({
1132
+ connector,
1133
+ symbol,
1134
+ requireTimestamp = false
1135
+ }) => {
1136
+ if (typeof connector.getTopOfBookTicker !== "function") {
1137
+ return {
1138
+ arrivalSnapshotTime: Date.now(),
1139
+ arrivalSource: "unavailable",
1140
+ bid: null,
1141
+ ask: null,
1142
+ arrivalMid: null,
1143
+ spreadBps: null
1144
+ };
1145
+ }
1146
+ try {
1147
+ const ticker = await connector.getTopOfBookTicker(symbol);
1148
+ const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
1149
+ const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
1150
+ const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
1151
+ const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
1152
+ const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
1153
+ const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
1154
+ return {
1155
+ arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
1156
+ arrivalSource: "top_of_book",
1157
+ bid,
1158
+ ask,
1159
+ arrivalMid,
1160
+ spreadBps
1161
+ };
1162
+ } catch (error) {
1163
+ import_logger.logger.warn(
1164
+ "runtime order arrival snapshot failed: %s %s",
1165
+ symbol,
1166
+ error?.message || String(error)
1167
+ );
1168
+ return {
1169
+ arrivalSnapshotTime: Date.now(),
1170
+ arrivalSource: "top_of_book_error",
1171
+ bid: null,
1172
+ ask: null,
1173
+ arrivalMid: null,
1174
+ spreadBps: null
1175
+ };
1176
+ }
1177
+ };
1178
+ var validateEntryProtectionAtArrival = ({
1179
+ direction,
1180
+ signalPrice,
1181
+ bid,
1182
+ ask,
1183
+ arrivalMid,
1184
+ takeProfits,
1185
+ stopLossPrice
1186
+ }) => {
1187
+ const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
1188
+ const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
1189
+ if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
1190
+ throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
1191
+ }
1192
+ for (const takeProfit of takeProfits) {
1193
+ if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
1194
+ throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
1195
+ }
1196
+ }
1197
+ if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
1198
+ throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
1199
+ }
1200
+ };
1201
+ var createEntrySubmissionValidation = ({
1202
+ connector,
1203
+ symbol,
1204
+ direction,
1205
+ takeProfits,
1206
+ stopLossPrice,
1207
+ limits,
1208
+ onValidated
1209
+ }) => {
1210
+ if (!limits) return void 0;
1211
+ if (!connector.capabilities?.orderSubmissionValidation)
1212
+ throw new EntrySubmissionValidationError(
1213
+ "ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
1214
+ );
1215
+ return async ({ qty, executionPrice }) => {
1216
+ const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
1217
+ const snapshot2 = simulated && executionPrice != null ? {
1218
+ arrivalSnapshotTime: Date.now(),
1219
+ arrivalSource: "simulation",
1220
+ bid: executionPrice,
1221
+ ask: executionPrice,
1222
+ arrivalMid: executionPrice,
1223
+ spreadBps: 0
1224
+ } : await getOrderArrivalSnapshot({
1225
+ connector,
1226
+ symbol,
1227
+ requireTimestamp: true
1228
+ });
1229
+ const age = Date.now() - snapshot2.arrivalSnapshotTime;
1230
+ if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
1231
+ throw new EntrySubmissionValidationError(
1232
+ "ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
1233
+ );
1234
+ if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
1235
+ throw new EntrySubmissionValidationError(
1236
+ "ENTRY_QUOTE_STALE_AT_SUBMISSION"
1237
+ );
1238
+ const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
1239
+ validateEntryProtectionAtArrival({
1240
+ direction,
1241
+ signalPrice: price,
1242
+ bid: snapshot2.bid,
1243
+ ask: snapshot2.ask,
1244
+ arrivalMid: snapshot2.arrivalMid,
1245
+ takeProfits,
1246
+ stopLossPrice
1247
+ });
1248
+ const prices = takeProfits.map((tp) => tp.price);
1249
+ const economics2 = validateEntryExecution({
1250
+ direction,
1251
+ price,
1252
+ qty,
1253
+ stopLossPrice,
1254
+ takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
1255
+ limits
1256
+ });
1257
+ onValidated?.(snapshot2, economics2);
1258
+ };
1259
+ };
1260
+
1064
1261
  // src/ai.ts
1065
1262
  var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
1066
1263
  var import_redis = require("@tradejs/infra/redis");
@@ -1645,7 +1842,7 @@ var import_fs = __toESM(require("fs"));
1645
1842
  var import_path = __toESM(require("path"));
1646
1843
  var import_url = require("url");
1647
1844
  var import_config = require("@tradejs/core/config");
1648
- var import_logger = require("@tradejs/infra/logger");
1845
+ var import_logger2 = require("@tradejs/infra/logger");
1649
1846
  var CONFIG_FILE_NAMES = [
1650
1847
  "tradejs.config.ts",
1651
1848
  "tradejs.config.mts",
@@ -1912,11 +2109,11 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
1912
2109
  cachedByCwd.set(cwd, config);
1913
2110
  if (!announcedConfigFile.has(configFilePath)) {
1914
2111
  announcedConfigFile.add(configFilePath);
1915
- import_logger.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
2112
+ import_logger2.logger.log("debug", "Loaded TradeJS config: %s", configFilePath);
1916
2113
  }
1917
2114
  return config;
1918
2115
  } catch (error) {
1919
- import_logger.logger.log(
2116
+ import_logger2.logger.log(
1920
2117
  "warn",
1921
2118
  "Failed to load TradeJS config from %s: %s",
1922
2119
  configFilePath,
@@ -2864,7 +3061,7 @@ var import_node_crypto = require("crypto");
2864
3061
  var import_constants = require("@tradejs/core/constants");
2865
3062
  var import_time = require("@tradejs/core/time");
2866
3063
  var import_trade = require("@tradejs/core/trade");
2867
- var import_logger2 = require("@tradejs/infra/logger");
3064
+ var import_logger3 = require("@tradejs/infra/logger");
2868
3065
  var import_redis2 = require("@tradejs/infra/redis");
2869
3066
  var now = () => Date.now();
2870
3067
  var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
@@ -2923,7 +3120,7 @@ var recordRuntimeTradeOpen = async (params) => {
2923
3120
  )
2924
3121
  ]);
2925
3122
  } catch (error) {
2926
- import_logger2.logger.error(
3123
+ import_logger3.logger.error(
2927
3124
  "runtime trade open journal failed: %s %s",
2928
3125
  record.symbol,
2929
3126
  error?.message || String(error)
@@ -2991,7 +3188,7 @@ var recordRuntimeTradeIncrease = async (params) => {
2991
3188
  )
2992
3189
  ]);
2993
3190
  } catch (error) {
2994
- import_logger2.logger.error(
3191
+ import_logger3.logger.error(
2995
3192
  "runtime trade increase journal failed: %s %s",
2996
3193
  symbol,
2997
3194
  error?.message || String(error)
@@ -3098,7 +3295,7 @@ var markRuntimeTradeClosed = async (params) => {
3098
3295
  )
3099
3296
  ]);
3100
3297
  } catch (error) {
3101
- import_logger2.logger.error(
3298
+ import_logger3.logger.error(
3102
3299
  "runtime trade close journal failed: %s %s",
3103
3300
  symbol,
3104
3301
  error?.message || String(error)
@@ -3108,11 +3305,11 @@ var markRuntimeTradeClosed = async (params) => {
3108
3305
  };
3109
3306
 
3110
3307
  // src/strategyHelpers/marketContextStages.ts
3111
- var import_logger7 = require("@tradejs/infra/logger");
3308
+ var import_logger8 = require("@tradejs/infra/logger");
3112
3309
 
3113
3310
  // src/strategyHelpers/binanceMarketContext.ts
3114
3311
  var import_marketContext = require("@tradejs/infra/timescale/marketContext");
3115
- var import_logger3 = require("@tradejs/infra/logger");
3312
+ var import_logger4 = require("@tradejs/infra/logger");
3116
3313
  var import_strategies = require("@tradejs/core/strategies");
3117
3314
 
3118
3315
  // src/binanceBreadthUniverses.ts
@@ -3476,7 +3673,7 @@ var parseEnabledFlag = (value, env) => {
3476
3673
  if (normalized === "live") return env !== "BACKTEST";
3477
3674
  return false;
3478
3675
  };
3479
- var toFiniteNumberOrNull = (value) => {
3676
+ var toFiniteNumberOrNull2 = (value) => {
3480
3677
  const numeric = typeof value === "number" ? value : Number(value);
3481
3678
  return Number.isFinite(numeric) ? numeric : null;
3482
3679
  };
@@ -3601,14 +3798,14 @@ var toTradeFlowContext = (row, interval) => row ? {
3601
3798
  asOfTs: row.ts.getTime(),
3602
3799
  ageMs: row.ageMs,
3603
3800
  stale: row.stale,
3604
- trades: toFiniteNumberOrNull(row.trades),
3605
- buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
3606
- buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
3607
- sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
3608
- buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
3609
- sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
3610
- netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
3611
- netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
3801
+ trades: toFiniteNumberOrNull2(row.trades),
3802
+ buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
3803
+ buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
3804
+ sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
3805
+ buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
3806
+ sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
3807
+ netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
3808
+ netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
3612
3809
  } : null;
3613
3810
  var getCachedReferenceRows = ({
3614
3811
  referenceSymbols,
@@ -3697,16 +3894,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
3697
3894
  asOfTs: breadth.ts.getTime(),
3698
3895
  ageMs: breadth.ageMs,
3699
3896
  stale: breadth.stale,
3700
- symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
3701
- advancers: toFiniteNumberOrNull(breadth.advancers),
3702
- decliners: toFiniteNumberOrNull(breadth.decliners),
3703
- unchanged: toFiniteNumberOrNull(breadth.unchanged),
3704
- advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
3705
- pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
3706
- pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
3707
- equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
3708
- volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
3709
- dispersion: toFiniteNumberOrNull(breadth.dispersion)
3897
+ symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
3898
+ advancers: toFiniteNumberOrNull2(breadth.advancers),
3899
+ decliners: toFiniteNumberOrNull2(breadth.decliners),
3900
+ unchanged: toFiniteNumberOrNull2(breadth.unchanged),
3901
+ advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
3902
+ pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
3903
+ pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
3904
+ equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
3905
+ volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
3906
+ dispersion: toFiniteNumberOrNull2(breadth.dispersion)
3710
3907
  });
3711
3908
  var enrichSignalWithBinanceMarketContext = async (params) => {
3712
3909
  const {
@@ -3807,46 +4004,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3807
4004
  asOfTs: primaryBreadth.ts.getTime(),
3808
4005
  ageMs: primaryBreadth.ageMs,
3809
4006
  stale: primaryBreadth.stale,
3810
- btcReturn1h: toFiniteNumberOrNull(
4007
+ btcReturn1h: toFiniteNumberOrNull2(
3811
4008
  primaryBreadth.btcReturn1h
3812
4009
  ),
3813
- btcReturn4h: toFiniteNumberOrNull(
4010
+ btcReturn4h: toFiniteNumberOrNull2(
3814
4011
  primaryBreadth.btcReturn4h
3815
4012
  ),
3816
- btcReturn24h: toFiniteNumberOrNull(
4013
+ btcReturn24h: toFiniteNumberOrNull2(
3817
4014
  primaryBreadth.btcReturn24h
3818
4015
  ),
3819
- altBasketReturn1h: toFiniteNumberOrNull(
4016
+ altBasketReturn1h: toFiniteNumberOrNull2(
3820
4017
  primaryBreadth.altBasketReturn1h
3821
4018
  ),
3822
- altBasketReturn4h: toFiniteNumberOrNull(
4019
+ altBasketReturn4h: toFiniteNumberOrNull2(
3823
4020
  primaryBreadth.altBasketReturn4h
3824
4021
  ),
3825
- altBasketReturn24h: toFiniteNumberOrNull(
4022
+ altBasketReturn24h: toFiniteNumberOrNull2(
3826
4023
  primaryBreadth.altBasketReturn24h
3827
4024
  ),
3828
- btcVsAltReturn1h: toFiniteNumberOrNull(
4025
+ btcVsAltReturn1h: toFiniteNumberOrNull2(
3829
4026
  primaryBreadth.btcVsAltReturn1h
3830
4027
  ),
3831
- btcVsAltReturn4h: toFiniteNumberOrNull(
4028
+ btcVsAltReturn4h: toFiniteNumberOrNull2(
3832
4029
  primaryBreadth.btcVsAltReturn4h
3833
4030
  ),
3834
- btcVsAltReturn24h: toFiniteNumberOrNull(
4031
+ btcVsAltReturn24h: toFiniteNumberOrNull2(
3835
4032
  primaryBreadth.btcVsAltReturn24h
3836
4033
  ),
3837
- btcTurnoverShare1h: toFiniteNumberOrNull(
4034
+ btcTurnoverShare1h: toFiniteNumberOrNull2(
3838
4035
  primaryBreadth.btcTurnoverShare1h
3839
4036
  ),
3840
- btcTurnoverShare24h: toFiniteNumberOrNull(
4037
+ btcTurnoverShare24h: toFiniteNumberOrNull2(
3841
4038
  primaryBreadth.btcTurnoverShare24h
3842
4039
  ),
3843
- btcTurnoverShareChange24h: toFiniteNumberOrNull(
4040
+ btcTurnoverShareChange24h: toFiniteNumberOrNull2(
3844
4041
  primaryBreadth.btcTurnoverShareChange24h
3845
4042
  ),
3846
- altVolToBtcVol24h: toFiniteNumberOrNull(
4043
+ altVolToBtcVol24h: toFiniteNumberOrNull2(
3847
4044
  primaryBreadth.altVolToBtcVol24h
3848
4045
  ),
3849
- altDispersion24h: toFiniteNumberOrNull(
4046
+ altDispersion24h: toFiniteNumberOrNull2(
3850
4047
  primaryBreadth.altDispersion24h
3851
4048
  ),
3852
4049
  regime: primaryBreadth.btcAltRegime ?? "unknown"
@@ -3863,7 +4060,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3863
4060
  throw error;
3864
4061
  }
3865
4062
  binanceMarketContextUnavailable = true;
3866
- import_logger3.logger.warn(
4063
+ import_logger4.logger.warn(
3867
4064
  "Binance market context disabled after Timescale read failure: %s",
3868
4065
  String(error)
3869
4066
  );
@@ -3873,7 +4070,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
3873
4070
 
3874
4071
  // src/strategyHelpers/coinMarketCapContext.ts
3875
4072
  var import_strategies2 = require("@tradejs/core/strategies");
3876
- var import_logger4 = require("@tradejs/infra/logger");
4073
+ var import_logger5 = require("@tradejs/infra/logger");
3877
4074
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
3878
4075
  var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
3879
4076
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
@@ -3911,7 +4108,7 @@ var asInt = (value, fallback) => {
3911
4108
  const parsed = Number.parseInt(String(value ?? ""), 10);
3912
4109
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
3913
4110
  };
3914
- var toFiniteNumberOrNull2 = (value) => {
4111
+ var toFiniteNumberOrNull3 = (value) => {
3915
4112
  const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
3916
4113
  return Number.isFinite(numeric) ? numeric : null;
3917
4114
  };
@@ -4145,12 +4342,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4145
4342
  const ethRow = references.get("ETHUSDT") ?? null;
4146
4343
  const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
4147
4344
  const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
4148
- const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
4149
- const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
4150
- const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
4345
+ const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
4346
+ const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
4347
+ const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
4151
4348
  previousBtcRow?.marketCapUsd
4152
4349
  );
4153
- const previousEthMarketCapUsd = toFiniteNumberOrNull2(
4350
+ const previousEthMarketCapUsd = toFiniteNumberOrNull3(
4154
4351
  previousEthRow?.marketCapUsd
4155
4352
  );
4156
4353
  const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
@@ -4159,16 +4356,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4159
4356
  previousBtcMarketCapUsd
4160
4357
  );
4161
4358
  const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
4162
- const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
4163
- const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
4359
+ const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
4360
+ const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
4164
4361
  const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
4165
- const btcDominanceChange24hPct = toFiniteNumberOrNull2(
4362
+ const btcDominanceChange24hPct = toFiniteNumberOrNull3(
4166
4363
  globalRow?.btcDominanceChange24hPct
4167
4364
  );
4168
- const altMarketCapChange24hPct = toFiniteNumberOrNull2(
4365
+ const altMarketCapChange24hPct = toFiniteNumberOrNull3(
4169
4366
  globalRow?.altMarketCapChange24hPct
4170
4367
  );
4171
- const altVolumeChange24hPct = toFiniteNumberOrNull2(
4368
+ const altVolumeChange24hPct = toFiniteNumberOrNull3(
4172
4369
  globalRow?.altVolumeChange24hPct
4173
4370
  );
4174
4371
  const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
@@ -4179,19 +4376,19 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4179
4376
  }) : "unknown";
4180
4377
  const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
4181
4378
  stale: exchangeLiquidityRow.stale,
4182
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
4379
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
4183
4380
  exchangeLiquidityRow.totalVolumeChange24hPct
4184
4381
  ),
4185
4382
  fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
4186
4383
  }) : "unknown";
4187
4384
  const cmc100Row = indexRows.get("cmc100") ?? null;
4188
4385
  const cmc20Row = indexRows.get("cmc20") ?? null;
4189
- const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
4190
- const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
4191
- const cmc100Change24hPct = toFiniteNumberOrNull2(
4386
+ const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
4387
+ const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
4388
+ const cmc100Change24hPct = toFiniteNumberOrNull3(
4192
4389
  cmc100Row?.valueChange24hPct
4193
4390
  );
4194
- const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
4391
+ const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
4195
4392
  const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
4196
4393
  const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
4197
4394
  const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
@@ -4215,41 +4412,41 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4215
4412
  asOfTs: globalRow.ts.getTime(),
4216
4413
  ageMs: globalRow.ageMs,
4217
4414
  stale: globalRow.stale,
4218
- totalMarketCapUsd: toFiniteNumberOrNull2(
4415
+ totalMarketCapUsd: toFiniteNumberOrNull3(
4219
4416
  globalRow.totalMarketCapUsd
4220
4417
  ),
4221
- totalVolumeUsd: toFiniteNumberOrNull2(
4418
+ totalVolumeUsd: toFiniteNumberOrNull3(
4222
4419
  globalRow.totalVolumeUsd
4223
4420
  ),
4224
- totalVolumeReportedUsd: toFiniteNumberOrNull2(
4421
+ totalVolumeReportedUsd: toFiniteNumberOrNull3(
4225
4422
  globalRow.totalVolumeReportedUsd
4226
4423
  ),
4227
- altMarketCapUsd: toFiniteNumberOrNull2(
4424
+ altMarketCapUsd: toFiniteNumberOrNull3(
4228
4425
  globalRow.altMarketCapUsd
4229
4426
  ),
4230
- altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
4231
- altVolumeReportedUsd: toFiniteNumberOrNull2(
4427
+ altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
4428
+ altVolumeReportedUsd: toFiniteNumberOrNull3(
4232
4429
  globalRow.altVolumeReportedUsd
4233
4430
  ),
4234
- btcDominancePct: toFiniteNumberOrNull2(
4431
+ btcDominancePct: toFiniteNumberOrNull3(
4235
4432
  globalRow.btcDominancePct
4236
4433
  ),
4237
- ethDominancePct: toFiniteNumberOrNull2(
4434
+ ethDominancePct: toFiniteNumberOrNull3(
4238
4435
  globalRow.ethDominancePct
4239
4436
  ),
4240
4437
  btcDominanceChange24hPct,
4241
- ethDominanceChange24hPct: toFiniteNumberOrNull2(
4438
+ ethDominanceChange24hPct: toFiniteNumberOrNull3(
4242
4439
  globalRow.ethDominanceChange24hPct
4243
4440
  ),
4244
4441
  altMarketCapChange24hPct,
4245
4442
  altVolumeChange24hPct,
4246
- activeCryptocurrencies: toFiniteNumberOrNull2(
4443
+ activeCryptocurrencies: toFiniteNumberOrNull3(
4247
4444
  globalRow.activeCryptocurrencies
4248
4445
  ),
4249
- activeExchanges: toFiniteNumberOrNull2(
4446
+ activeExchanges: toFiniteNumberOrNull3(
4250
4447
  globalRow.activeExchanges
4251
4448
  ),
4252
- activeMarketPairs: toFiniteNumberOrNull2(
4449
+ activeMarketPairs: toFiniteNumberOrNull3(
4253
4450
  globalRow.activeMarketPairs
4254
4451
  ),
4255
4452
  altLiquidityRegime
@@ -4294,22 +4491,22 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4294
4491
  asOfTs: exchangeLiquidityRow.ts.getTime(),
4295
4492
  ageMs: exchangeLiquidityRow.ageMs,
4296
4493
  stale: exchangeLiquidityRow.stale,
4297
- exchangesCount: toFiniteNumberOrNull2(
4494
+ exchangesCount: toFiniteNumberOrNull3(
4298
4495
  exchangeLiquidityRow.exchangesCount
4299
4496
  ),
4300
- totalVolumeUsd: toFiniteNumberOrNull2(
4497
+ totalVolumeUsd: toFiniteNumberOrNull3(
4301
4498
  exchangeLiquidityRow.totalVolumeUsd
4302
4499
  ),
4303
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
4500
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
4304
4501
  exchangeLiquidityRow.totalVolumeChange24hPct
4305
4502
  ),
4306
- binanceVolumeUsd: toFiniteNumberOrNull2(
4503
+ binanceVolumeUsd: toFiniteNumberOrNull3(
4307
4504
  exchangeLiquidityRow.binanceVolumeUsd
4308
4505
  ),
4309
- binanceVolumeShare: toFiniteNumberOrNull2(
4506
+ binanceVolumeShare: toFiniteNumberOrNull3(
4310
4507
  exchangeLiquidityRow.binanceVolumeShare
4311
4508
  ),
4312
- topExchangeVolumeShare: toFiniteNumberOrNull2(
4509
+ topExchangeVolumeShare: toFiniteNumberOrNull3(
4313
4510
  exchangeLiquidityRow.topExchangeVolumeShare
4314
4511
  ),
4315
4512
  liquidityRegime: exchangeLiquidityRegime
@@ -4322,11 +4519,11 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4322
4519
  asOfTs: fearGreedRow.ts.getTime(),
4323
4520
  ageMs: fearGreedRow.ageMs,
4324
4521
  stale: fearGreedRow.stale,
4325
- value: toFiniteNumberOrNull2(fearGreedRow.value),
4326
- valueChange24h: toFiniteNumberOrNull2(
4522
+ value: toFiniteNumberOrNull3(fearGreedRow.value),
4523
+ valueChange24h: toFiniteNumberOrNull3(
4327
4524
  fearGreedRow.valueChange24h
4328
4525
  ),
4329
- valueChange7d: toFiniteNumberOrNull2(
4526
+ valueChange7d: toFiniteNumberOrNull3(
4330
4527
  fearGreedRow.valueChange7d
4331
4528
  ),
4332
4529
  classification: fearGreedRow.classification ?? "Unknown",
@@ -4346,13 +4543,13 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4346
4543
  cmc100Value,
4347
4544
  cmc100Change24hPct,
4348
4545
  cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
4349
- cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
4546
+ cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
4350
4547
  cmc100Row?.topConstituentWeightPct
4351
4548
  ),
4352
4549
  cmc20Value,
4353
4550
  cmc20Change24hPct,
4354
4551
  cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
4355
- cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
4552
+ cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
4356
4553
  cmc20Row?.topConstituentWeightPct
4357
4554
  ),
4358
4555
  cmc20ToCmc100Ratio,
@@ -4370,7 +4567,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4370
4567
  throw error;
4371
4568
  }
4372
4569
  coinMarketCapContextUnavailable = true;
4373
- import_logger4.logger.warn(
4570
+ import_logger5.logger.warn(
4374
4571
  "CoinMarketCap context disabled after Timescale read failure: %s",
4375
4572
  String(error)
4376
4573
  );
@@ -4384,7 +4581,7 @@ var import_data2 = require("@tradejs/core/data");
4384
4581
  var import_strategies3 = require("@tradejs/core/strategies");
4385
4582
  var import_constants2 = require("@tradejs/core/constants");
4386
4583
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
4387
- var import_logger5 = require("@tradejs/infra/logger");
4584
+ var import_logger6 = require("@tradejs/infra/logger");
4388
4585
  var STORED_INTERVALS = ["15m", "1h"];
4389
4586
  var CONTEXT_INTERVALS = ["15m", "1h"];
4390
4587
  var DEFAULT_LOOKBACK_HOURS = 48;
@@ -4439,7 +4636,7 @@ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
4439
4636
  var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
4440
4637
  var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
4441
4638
  var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
4442
- var toFiniteNumberOrNull3 = (value) => {
4639
+ var toFiniteNumberOrNull4 = (value) => {
4443
4640
  if (typeof value === "number" && Number.isFinite(value)) return value;
4444
4641
  if (typeof value === "string" && value.trim()) {
4445
4642
  const parsed = Number(value);
@@ -4453,8 +4650,8 @@ var roundNullable = (value, digits = 4) => {
4453
4650
  return Math.round(value * multiplier) / multiplier;
4454
4651
  };
4455
4652
  var deltaNullable = (targetValue, referenceValue) => {
4456
- const target = toFiniteNumberOrNull3(targetValue);
4457
- const reference = toFiniteNumberOrNull3(referenceValue);
4653
+ const target = toFiniteNumberOrNull4(targetValue);
4654
+ const reference = toFiniteNumberOrNull4(referenceValue);
4458
4655
  return target == null || reference == null ? null : roundNullable(target - reference);
4459
4656
  };
4460
4657
  var buildTargetDerivedContext = (params) => {
@@ -4616,7 +4813,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4616
4813
  throw error;
4617
4814
  }
4618
4815
  derivativesContextUnavailable = true;
4619
- import_logger5.logger.warn(
4816
+ import_logger6.logger.warn(
4620
4817
  "Derivatives context disabled after Timescale read failure: %s",
4621
4818
  String(error)
4622
4819
  );
@@ -4628,7 +4825,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4628
4825
  var import_data3 = require("@tradejs/core/data");
4629
4826
  var import_strategies4 = require("@tradejs/core/strategies");
4630
4827
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
4631
- var import_logger6 = require("@tradejs/infra/logger");
4828
+ var import_logger7 = require("@tradejs/infra/logger");
4632
4829
 
4633
4830
  // src/hyperliquidWhaleUniverse.ts
4634
4831
  var import_node_crypto3 = require("crypto");
@@ -5148,7 +5345,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
5148
5345
  throw error;
5149
5346
  }
5150
5347
  hyperliquidWhaleContextUnavailable = true;
5151
- import_logger6.logger.warn(
5348
+ import_logger7.logger.warn(
5152
5349
  "Hyperliquid whale context disabled after Timescale read failure: %s",
5153
5350
  String(error)
5154
5351
  );
@@ -5231,7 +5428,7 @@ var runMarketContextStage = async ({
5231
5428
  elapsedMs: Date.now() - startedAt
5232
5429
  };
5233
5430
  if (status === "timed_out") {
5234
- import_logger7.logger.warn(
5431
+ import_logger8.logger.warn(
5235
5432
  "Market context stage timed out: %s after %sms",
5236
5433
  stage,
5237
5434
  result.elapsedMs
@@ -5421,79 +5618,10 @@ var enrichSignalWithAi = async ({
5421
5618
  signal.aiAnalysis = analysis;
5422
5619
  return resolveAiQuality(analysis, direction);
5423
5620
  } catch (err) {
5424
- import_logger8.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
5621
+ import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
5425
5622
  }
5426
5623
  return void 0;
5427
5624
  };
5428
- var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
5429
- var getOrderArrivalSnapshot = async ({
5430
- connector,
5431
- symbol
5432
- }) => {
5433
- if (typeof connector.getTopOfBookTicker !== "function") {
5434
- return {
5435
- arrivalSnapshotTime: Date.now(),
5436
- arrivalSource: "unavailable",
5437
- bid: null,
5438
- ask: null,
5439
- arrivalMid: null,
5440
- spreadBps: null
5441
- };
5442
- }
5443
- try {
5444
- const ticker = await connector.getTopOfBookTicker(symbol);
5445
- const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
5446
- const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
5447
- const ask = toFiniteNumberOrNull4(ticker?.askPrice);
5448
- const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
5449
- const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
5450
- return {
5451
- arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
5452
- arrivalSource: "top_of_book",
5453
- bid,
5454
- ask,
5455
- arrivalMid,
5456
- spreadBps
5457
- };
5458
- } catch (error) {
5459
- import_logger8.logger.warn(
5460
- "runtime order arrival snapshot failed: %s %s",
5461
- symbol,
5462
- error?.message || String(error)
5463
- );
5464
- return {
5465
- arrivalSnapshotTime: Date.now(),
5466
- arrivalSource: "top_of_book_error",
5467
- bid: null,
5468
- ask: null,
5469
- arrivalMid: null,
5470
- spreadBps: null
5471
- };
5472
- }
5473
- };
5474
- var validateEntryProtectionAtArrival = ({
5475
- direction,
5476
- signalPrice,
5477
- bid,
5478
- ask,
5479
- arrivalMid,
5480
- takeProfits,
5481
- stopLossPrice
5482
- }) => {
5483
- const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
5484
- const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
5485
- if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
5486
- throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
5487
- }
5488
- for (const takeProfit of takeProfits) {
5489
- if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
5490
- throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
5491
- }
5492
- }
5493
- if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
5494
- throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
5495
- }
5496
- };
5497
5625
  var resolveRuntimeTelemetryQuality = ({
5498
5626
  signalClosePrice,
5499
5627
  arrivalMid,
@@ -5570,7 +5698,8 @@ var executeEntryOrder = async ({
5570
5698
  signal,
5571
5699
  beforePlaceOrder,
5572
5700
  recordRuntimeTrade = true,
5573
- leverage
5701
+ leverage,
5702
+ executionLimits
5574
5703
  }) => {
5575
5704
  await beforePlaceOrder?.();
5576
5705
  const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
@@ -5581,23 +5710,57 @@ var executeEntryOrder = async ({
5581
5710
  signal.orderQty = qty;
5582
5711
  signal.orderValue = qty * currentPrice;
5583
5712
  signal.orderFailureReason = void 0;
5584
- const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
5585
- try {
5586
- validateEntryProtectionAtArrival({
5587
- direction,
5588
- signalPrice: currentPrice,
5589
- bid: arrivalSnapshot.bid,
5590
- ask: arrivalSnapshot.ask,
5591
- arrivalMid: arrivalSnapshot.arrivalMid,
5592
- takeProfits,
5593
- stopLossPrice
5594
- });
5595
- } catch (error) {
5596
- signal.orderStatus = "failed";
5597
- signal.orderFailureReason = error.message;
5598
- throw error;
5713
+ let arrivalSnapshot = {
5714
+ arrivalSnapshotTime: Date.now(),
5715
+ arrivalSource: "unavailable",
5716
+ bid: null,
5717
+ ask: null,
5718
+ arrivalMid: null,
5719
+ spreadBps: null
5720
+ };
5721
+ let orderSubmitTime = Date.now();
5722
+ const beforeSubmit = createEntrySubmissionValidation({
5723
+ connector,
5724
+ symbol,
5725
+ direction,
5726
+ takeProfits,
5727
+ stopLossPrice,
5728
+ limits: executionLimits,
5729
+ onValidated: (snapshot2, economics2) => {
5730
+ arrivalSnapshot = snapshot2;
5731
+ orderSubmitTime = Date.now();
5732
+ if (executionLimits?.maxLossValue != null) {
5733
+ signal.riskDecision = {
5734
+ stage: "order_plan",
5735
+ status: economics2.riskExceeded ? "warning" : "approved",
5736
+ reason: economics2.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
5737
+ plannedLossValue: economics2.plannedLossValue,
5738
+ maxLossValue: executionLimits.maxLossValue,
5739
+ limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
5740
+ enforced: false
5741
+ };
5742
+ }
5743
+ }
5744
+ });
5745
+ if (!beforeSubmit) {
5746
+ arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
5747
+ try {
5748
+ validateEntryProtectionAtArrival({
5749
+ direction,
5750
+ signalPrice: currentPrice,
5751
+ bid: arrivalSnapshot.bid,
5752
+ ask: arrivalSnapshot.ask,
5753
+ arrivalMid: arrivalSnapshot.arrivalMid,
5754
+ takeProfits,
5755
+ stopLossPrice
5756
+ });
5757
+ } catch (error) {
5758
+ signal.orderStatus = "failed";
5759
+ signal.orderFailureReason = error.message;
5760
+ throw error;
5761
+ }
5762
+ orderSubmitTime = Date.now();
5599
5763
  }
5600
- const orderSubmitTime = Date.now();
5601
5764
  const orderPlaced = await connector.placeOrder({
5602
5765
  symbol,
5603
5766
  qty,
@@ -5608,7 +5771,8 @@ var executeEntryOrder = async ({
5608
5771
  direction,
5609
5772
  ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
5610
5773
  orderId,
5611
- signal
5774
+ signal,
5775
+ ...beforeSubmit ? { beforeSubmit } : {}
5612
5776
  });
5613
5777
  const orderAckTime = Date.now();
5614
5778
  const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
@@ -6440,7 +6604,7 @@ var canUseSharedReplayState = ({
6440
6604
  }) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
6441
6605
 
6442
6606
  // src/strategy/runtimeExecution.ts
6443
- var import_logger9 = require("@tradejs/infra/logger");
6607
+ var import_logger10 = require("@tradejs/infra/logger");
6444
6608
  var import_types = require("@tradejs/types");
6445
6609
  var buildExitOrderSignal = ({
6446
6610
  strategyName,
@@ -6492,7 +6656,7 @@ var handleExitDecision = async ({
6492
6656
  deploymentId: connector.deploymentId
6493
6657
  });
6494
6658
  if (!activeTrade) {
6495
- import_logger9.logger.warn(
6659
+ import_logger10.logger.warn(
6496
6660
  "[%s] blocked closePosition for untracked runtime position: %s",
6497
6661
  strategyName ?? "unknown",
6498
6662
  symbol
@@ -6500,7 +6664,7 @@ var handleExitDecision = async ({
6500
6664
  return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
6501
6665
  }
6502
6666
  if (!strategyName || activeTrade.strategy !== strategyName) {
6503
- import_logger9.logger.warn(
6667
+ import_logger10.logger.warn(
6504
6668
  "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
6505
6669
  strategyName ?? "unknown",
6506
6670
  symbol,
@@ -6553,7 +6717,7 @@ var handleExitDecision = async ({
6553
6717
  exitType: closedTrade?.exitType ?? "exit"
6554
6718
  });
6555
6719
  } catch (notificationError) {
6556
- import_logger9.logger.error(
6720
+ import_logger10.logger.error(
6557
6721
  "runtime close notification error: %s %s",
6558
6722
  symbol,
6559
6723
  notificationError
@@ -6567,7 +6731,7 @@ var handleExitDecision = async ({
6567
6731
  decision,
6568
6732
  market
6569
6733
  });
6570
- import_logger9.logger.error("close order error: %s %s", symbol, err);
6734
+ import_logger10.logger.error("close order error: %s %s", symbol, err);
6571
6735
  return "ORDER_ERROR";
6572
6736
  }
6573
6737
  return decision.code;
@@ -6594,7 +6758,7 @@ var handleProtectDecision = async ({
6594
6758
  decision,
6595
6759
  market
6596
6760
  });
6597
- import_logger9.logger.error("protect position error: %s %s", symbol, err);
6761
+ import_logger10.logger.error("protect position error: %s %s", symbol, err);
6598
6762
  return "ORDER_ERROR";
6599
6763
  }
6600
6764
  return decision.code;
@@ -6660,7 +6824,11 @@ var executeEntryDecision = async ({
6660
6824
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
6661
6825
  signal,
6662
6826
  beforePlaceOrder,
6663
- recordRuntimeTrade: recordRuntimeJournal
6827
+ recordRuntimeTrade: recordRuntimeJournal,
6828
+ executionLimits: resolveEntryExecutionLimits(
6829
+ hookCtx.strategyConfig,
6830
+ decision.entryContext.direction
6831
+ )
6664
6832
  });
6665
6833
  await invokeStageHooks(
6666
6834
  "afterPlaceOrder",
@@ -6682,19 +6850,32 @@ var executeEntryDecision = async ({
6682
6850
  return signal;
6683
6851
  }
6684
6852
  await beforePlaceOrder();
6685
- const arrivalSnapshot = await getOrderArrivalSnapshot({
6853
+ const beforeSubmit = createEntrySubmissionValidation({
6686
6854
  connector,
6687
- symbol
6688
- });
6689
- validateEntryProtectionAtArrival({
6855
+ symbol,
6690
6856
  direction: decision.entryContext.direction,
6691
- signalPrice: decision.entryContext.prices.currentPrice,
6692
- bid: arrivalSnapshot.bid,
6693
- ask: arrivalSnapshot.ask,
6694
- arrivalMid: arrivalSnapshot.arrivalMid,
6695
6857
  takeProfits: decision.orderPlan.takeProfits,
6696
- stopLossPrice: decision.orderPlan.stopLossPrice
6858
+ stopLossPrice: decision.orderPlan.stopLossPrice,
6859
+ limits: resolveEntryExecutionLimits(
6860
+ hookCtx.strategyConfig,
6861
+ decision.entryContext.direction
6862
+ )
6697
6863
  });
6864
+ if (!beforeSubmit) {
6865
+ const arrivalSnapshot = await getOrderArrivalSnapshot({
6866
+ connector,
6867
+ symbol
6868
+ });
6869
+ validateEntryProtectionAtArrival({
6870
+ direction: decision.entryContext.direction,
6871
+ signalPrice: decision.entryContext.prices.currentPrice,
6872
+ bid: arrivalSnapshot.bid,
6873
+ ask: arrivalSnapshot.ask,
6874
+ arrivalMid: arrivalSnapshot.arrivalMid,
6875
+ takeProfits: decision.orderPlan.takeProfits,
6876
+ stopLossPrice: decision.orderPlan.stopLossPrice
6877
+ });
6878
+ }
6698
6879
  const orderPlaced = await connector.placeOrder({
6699
6880
  symbol,
6700
6881
  qty: decision.orderPlan.qty,
@@ -6702,6 +6883,7 @@ var executeEntryDecision = async ({
6702
6883
  timestamp: decision.entryContext.timestamp,
6703
6884
  direction: decision.entryContext.direction,
6704
6885
  positionIntent: decision.orderPlan.positionIntent,
6886
+ ...beforeSubmit ? { beforeSubmit } : {},
6705
6887
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
6706
6888
  });
6707
6889
  if (!orderPlaced) {
@@ -6743,6 +6925,23 @@ var executeEntryDecision = async ({
6743
6925
  { decision, entry, market }
6744
6926
  );
6745
6927
  } catch (err) {
6928
+ if (err instanceof EntrySubmissionValidationError) {
6929
+ if (signal) {
6930
+ signal.orderStatus = "skipped";
6931
+ signal.orderSkipReason = err.message;
6932
+ signal.allocatorDecision = {
6933
+ stage: "runtime_admission",
6934
+ status: "rejected",
6935
+ reason: err.message
6936
+ };
6937
+ }
6938
+ import_logger10.logger.warn(
6939
+ "entry rejected before submission: %s %s",
6940
+ symbol,
6941
+ err.message
6942
+ );
6943
+ return signal ?? err.message;
6944
+ }
6746
6945
  if (signal) {
6747
6946
  signal.orderStatus = "failed";
6748
6947
  if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
@@ -6757,9 +6956,9 @@ var executeEntryDecision = async ({
6757
6956
  market
6758
6957
  });
6759
6958
  if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
6760
- import_logger9.logger.warn("order warning: %s %s", symbol, err);
6959
+ import_logger10.logger.warn("order warning: %s %s", symbol, err);
6761
6960
  } else {
6762
- import_logger9.logger.error("order error: %s %s", symbol, err);
6961
+ import_logger10.logger.error("order error: %s %s", symbol, err);
6763
6962
  }
6764
6963
  return signal ?? "ORDER_ERROR";
6765
6964
  }
@@ -6867,6 +7066,9 @@ var createStrategyRuntime = ({
6867
7066
  });
6868
7067
  const strategyPolicyProfile = getPolicyProfile();
6869
7068
  const indicatorPeriods = (0, import_strategies6.buildDefaultIndicatorPeriods)(config);
7069
+ const strategyConfig = { ...config };
7070
+ delete strategyConfig.RECORD_RUNTIME_TRADES;
7071
+ delete strategyConfig.SIMULATE_ORDERS;
6870
7072
  const hookBase = {
6871
7073
  connector,
6872
7074
  strategyName,
@@ -6877,7 +7079,7 @@ var createStrategyRuntime = ({
6877
7079
  accountId,
6878
7080
  deploymentId,
6879
7081
  policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
6880
- strategyConfig: config,
7082
+ strategyConfig,
6881
7083
  env,
6882
7084
  isConfigFromBacktest
6883
7085
  };
@@ -6924,7 +7126,7 @@ var createStrategyRuntime = ({
6924
7126
  try {
6925
7127
  await projectHook(errorParams);
6926
7128
  } catch (hookError) {
6927
- import_logger10.logger.error(
7129
+ import_logger11.logger.error(
6928
7130
  "project hook onRuntimeError failed: %s %s",
6929
7131
  strategyName,
6930
7132
  hookError
@@ -6938,21 +7140,21 @@ var createStrategyRuntime = ({
6938
7140
  try {
6939
7141
  await onRuntimeError(errorParams);
6940
7142
  } catch (hookError) {
6941
- import_logger10.logger.error(
7143
+ import_logger11.logger.error(
6942
7144
  "runtime hook onRuntimeError failed: %s %s",
6943
7145
  strategyName,
6944
7146
  hookError
6945
7147
  );
6946
7148
  }
6947
7149
  };
6948
- const invokeHook = async (stage, hook, params, errorContext = {}) => {
7150
+ const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
6949
7151
  if (!hook) {
6950
7152
  return void 0;
6951
7153
  }
6952
7154
  try {
6953
7155
  return await hook(params);
6954
7156
  } catch (error) {
6955
- import_logger10.logger.error(
7157
+ import_logger11.logger.error(
6956
7158
  'strategy hook "%s" failed for %s: %s',
6957
7159
  stage,
6958
7160
  strategyName,
@@ -6965,17 +7167,18 @@ var createStrategyRuntime = ({
6965
7167
  entry: errorContext.entry,
6966
7168
  market: errorContext.market
6967
7169
  });
6968
- return void 0;
7170
+ return failureResult;
6969
7171
  }
6970
7172
  };
6971
- const invokeProjectHooks = async (stage, params, errorContext = {}) => {
7173
+ const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
6972
7174
  const results = [];
6973
7175
  for (const hook of getProjectHookList(stage)) {
6974
7176
  const result = await invokeHook(
6975
7177
  stage,
6976
7178
  hook,
6977
7179
  params,
6978
- errorContext
7180
+ errorContext,
7181
+ failureResult
6979
7182
  );
6980
7183
  if (result !== void 0) {
6981
7184
  results.push(result);
@@ -6990,10 +7193,12 @@ var createStrategyRuntime = ({
6990
7193
  return invokeHook(stage, hook, params, errorContext);
6991
7194
  };
6992
7195
  const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
7196
+ const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
6993
7197
  const projectResults = await invokeProjectHooks(
6994
7198
  stage,
6995
7199
  params,
6996
- errorContext
7200
+ errorContext,
7201
+ failureResult
6997
7202
  );
6998
7203
  const projectBlock = projectResults.find(
6999
7204
  (result) => result?.allow === false
@@ -7005,7 +7210,8 @@ var createStrategyRuntime = ({
7005
7210
  stage,
7006
7211
  hook,
7007
7212
  params,
7008
- errorContext
7213
+ errorContext,
7214
+ failureResult
7009
7215
  );
7010
7216
  };
7011
7217
  const applyProjectAfterCoreDecisionHooks = async ({
@@ -7151,11 +7357,8 @@ var createStrategyRuntime = ({
7151
7357
  return enrichedBaseContext;
7152
7358
  }
7153
7359
  });
7154
- const coreConfig = { ...config };
7155
- delete coreConfig.RECORD_RUNTIME_TRADES;
7156
- delete coreConfig.SIMULATE_ORDERS;
7157
7360
  const core = await createCore({
7158
- config: coreConfig,
7361
+ config: strategyConfig,
7159
7362
  data,
7160
7363
  strategyApi,
7161
7364
  indicatorsState
@@ -7774,10 +7977,10 @@ var createStrategyRuntime = ({
7774
7977
  setStrategyRuntimeFactory(createStrategyRuntime);
7775
7978
 
7776
7979
  // src/connectorsRegistry.ts
7777
- var import_logger13 = require("@tradejs/infra/logger");
7980
+ var import_logger14 = require("@tradejs/infra/logger");
7778
7981
 
7779
7982
  // src/connectorRuntime.ts
7780
- var import_logger12 = require("@tradejs/infra/logger");
7983
+ var import_logger13 = require("@tradejs/infra/logger");
7781
7984
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
7782
7985
 
7783
7986
  // src/timescaleCachedKline.ts
@@ -7785,7 +7988,7 @@ var import_constants6 = require("@tradejs/core/constants");
7785
7988
  var import_data5 = require("@tradejs/core/data");
7786
7989
  var import_time2 = require("@tradejs/core/time");
7787
7990
  var import_async = require("@tradejs/core/async");
7788
- var import_logger11 = require("@tradejs/infra/logger");
7991
+ var import_logger12 = require("@tradejs/infra/logger");
7789
7992
  var import_candles = require("@tradejs/infra/timescale/candles");
7790
7993
  var DEFAULT_LIMIT = 1e3;
7791
7994
  var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
@@ -7895,7 +8098,7 @@ var createTimescaleCachedKline = ({
7895
8098
  }) => {
7896
8099
  const intMinutes = intervalToMinutes(interval);
7897
8100
  if (!intMinutes) {
7898
- import_logger11.logger.log("error", "refreshTail: invalid interval %s", interval);
8101
+ import_logger12.logger.log("error", "refreshTail: invalid interval %s", interval);
7899
8102
  return;
7900
8103
  }
7901
8104
  const intervalMs = intervalMsOf(intMinutes);
@@ -7926,7 +8129,7 @@ var createTimescaleCachedKline = ({
7926
8129
  }) => {
7927
8130
  const intMinutes = intervalToMinutes(interval);
7928
8131
  if (!intMinutes) {
7929
- import_logger11.logger.log("error", "kline: invalid interval %s", interval);
8132
+ import_logger12.logger.log("error", "kline: invalid interval %s", interval);
7930
8133
  return [];
7931
8134
  }
7932
8135
  if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
@@ -8035,7 +8238,7 @@ var createTimescaleCachedKline = ({
8035
8238
  if (warmOnly) {
8036
8239
  if (isTimescaleFallbackMode) {
8037
8240
  isTimescaleFallbackMode = false;
8038
- import_logger11.logger.log("info", "TimescaleDB connection restored for kline cache");
8241
+ import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
8039
8242
  }
8040
8243
  return [];
8041
8244
  }
@@ -8053,13 +8256,13 @@ var createTimescaleCachedKline = ({
8053
8256
  );
8054
8257
  if (isTimescaleFallbackMode) {
8055
8258
  isTimescaleFallbackMode = false;
8056
- import_logger11.logger.log("info", "TimescaleDB connection restored for kline cache");
8259
+ import_logger12.logger.log("info", "TimescaleDB connection restored for kline cache");
8057
8260
  }
8058
8261
  return dbData;
8059
8262
  } catch (error) {
8060
8263
  if (!isTimescaleFallbackMode) {
8061
8264
  isTimescaleFallbackMode = true;
8062
- import_logger11.logger.log(
8265
+ import_logger12.logger.log(
8063
8266
  "warn",
8064
8267
  "TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
8065
8268
  symbol,
@@ -8083,7 +8286,7 @@ var createTimescaleCachedKline = ({
8083
8286
 
8084
8287
  // src/connectorRuntime.ts
8085
8288
  var connectorRuntime = {
8086
- logger: import_logger12.logger,
8289
+ logger: import_logger13.logger,
8087
8290
  resolveTradingAccount: import_tradingAccounts.resolveTradingAccount,
8088
8291
  createCachedKline: createTimescaleCachedKline
8089
8292
  };
@@ -8144,7 +8347,7 @@ var normalizeProviders = (providers, connectorName) => {
8144
8347
  var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
8145
8348
  const existing = providerToConnectorName.get(provider);
8146
8349
  if (existing && existing !== connectorName) {
8147
- import_logger13.logger.warn(
8350
+ import_logger14.logger.warn(
8148
8351
  'Skip duplicate connector provider "%s" from %s: already mapped to %s',
8149
8352
  provider,
8150
8353
  source,
@@ -8157,11 +8360,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
8157
8360
  var registerEntry = (entry, source, state) => {
8158
8361
  const connectorName = String(entry?.name ?? "").trim();
8159
8362
  if (!connectorName) {
8160
- import_logger13.logger.warn("Skip connector entry without name from %s", source);
8363
+ import_logger14.logger.warn("Skip connector entry without name from %s", source);
8161
8364
  return;
8162
8365
  }
8163
8366
  if (typeof entry.creator !== "function") {
8164
- import_logger13.logger.warn(
8367
+ import_logger14.logger.warn(
8165
8368
  'Skip connector entry "%s" from %s: creator must be a function',
8166
8369
  connectorName,
8167
8370
  source
@@ -8173,7 +8376,7 @@ var registerEntry = (entry, source, state) => {
8173
8376
  state.connectorCreators
8174
8377
  );
8175
8378
  if (existingByName) {
8176
- import_logger13.logger.warn(
8379
+ import_logger14.logger.warn(
8177
8380
  'Skip duplicate connector "%s" from %s: already registered as %s',
8178
8381
  connectorName,
8179
8382
  source,
@@ -8248,7 +8451,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
8248
8451
  );
8249
8452
  const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
8250
8453
  if (!pluginDefinition) {
8251
- import_logger13.logger.warn(
8454
+ import_logger14.logger.warn(
8252
8455
  'Skip connector plugin "%s": export { connectorEntries } is missing',
8253
8456
  moduleName
8254
8457
  );
@@ -8256,7 +8459,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
8256
8459
  }
8257
8460
  registerEntries2(pluginDefinition.connectorEntries, moduleName, state);
8258
8461
  } catch (error) {
8259
- import_logger13.logger.warn(
8462
+ import_logger14.logger.warn(
8260
8463
  'Failed to load connector plugin "%s": %s',
8261
8464
  moduleName,
8262
8465
  String(error)
@@ -8953,7 +9156,10 @@ var createTestConnector = (connector, context) => {
8953
9156
  });
8954
9157
  return {
8955
9158
  __tradejsTestConnector: true,
8956
- capabilities: connector.capabilities,
9159
+ capabilities: {
9160
+ ...connector.capabilities,
9161
+ orderSubmissionValidation: true
9162
+ },
8957
9163
  universe: connector.universe,
8958
9164
  accountId: connector.accountId,
8959
9165
  deploymentId: connector.deploymentId,
@@ -9235,6 +9441,7 @@ var createTestConnector = (connector, context) => {
9235
9441
  }
9236
9442
  },
9237
9443
  placeOrder: async (order) => {
9444
+ const { beforeSubmit, ...submittedOrder } = order;
9238
9445
  const isPositionIncrease = Boolean(
9239
9446
  currentPosition && order.positionIntent === "increase" && currentPosition.direction === order.direction
9240
9447
  );
@@ -9271,6 +9478,7 @@ var createTestConnector = (connector, context) => {
9271
9478
  signal: order.signal
9272
9479
  });
9273
9480
  const previousPosition = currentPosition;
9481
+ await beforeSubmit?.({ qty: orderQty, executionPrice: entryPrice });
9274
9482
  const previousQty = previousPosition?.qty ?? 0;
9275
9483
  const resultingQty = previousQty + orderQty;
9276
9484
  const resultingEntryPrice = previousPosition ? getWeightedAverage(
@@ -9283,7 +9491,7 @@ var createTestConnector = (connector, context) => {
9283
9491
  ...previousPosition,
9284
9492
  qty: resultingQty,
9285
9493
  price: resultingEntryPrice
9286
- } : { ...order, qty: orderQty, price: entryPrice, amount };
9494
+ } : { ...submittedOrder, qty: orderQty, price: entryPrice, amount };
9287
9495
  originalQty = resultingQty;
9288
9496
  if (isPositionIncrease) {
9289
9497
  const { fee: fee2, profit: profit2 } = getNetProfit({
@@ -9383,7 +9591,7 @@ var createTestConnector = (connector, context) => {
9383
9591
  };
9384
9592
  }
9385
9593
  logOrder({
9386
- ...order,
9594
+ ...submittedOrder,
9387
9595
  qty: orderQty,
9388
9596
  price: entryPrice,
9389
9597
  profit: profit2,
@@ -9424,7 +9632,7 @@ var createTestConnector = (connector, context) => {
9424
9632
  currentEntryLegResults = [openTradeResult];
9425
9633
  currentTradeResult = currentSignalId ? { ...openTradeResult, signalId: currentSignalId } : null;
9426
9634
  logOrder({
9427
- ...order,
9635
+ ...submittedOrder,
9428
9636
  qty: orderQty,
9429
9637
  price: entryPrice,
9430
9638
  profit,
@@ -10180,7 +10388,7 @@ var prepareTestingData = async (params) => {
10180
10388
  throw new Error(`Unknown connector: ${connectorName}`);
10181
10389
  }
10182
10390
  if (!binanceConnector || !coinbaseConnector) {
10183
- import_logger14.logger.warn(
10391
+ import_logger15.logger.warn(
10184
10392
  "Binance/Coinbase connectors are unavailable. Reusing %s for BTC references.",
10185
10393
  connectorName
10186
10394
  );