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

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.
@@ -781,7 +781,7 @@ __export(runtimeDashboard_exports, {
781
781
  });
782
782
  module.exports = __toCommonJS(runtimeDashboard_exports);
783
783
  var import_time4 = require("@tradejs/core/time");
784
- var import_logger14 = require("@tradejs/infra/logger");
784
+ var import_logger15 = require("@tradejs/infra/logger");
785
785
  var import_runtimeDeploymentEvents = require("@tradejs/infra/runtimeDeploymentEvents");
786
786
  var import_tradingAccounts3 = require("@tradejs/infra/tradingAccounts");
787
787
  var import_redis4 = require("@tradejs/infra/redis");
@@ -2342,13 +2342,213 @@ var assessSignalWithJev = async ({
2342
2342
  // src/strategyRuntime.ts
2343
2343
  var import_constants7 = require("@tradejs/core/constants");
2344
2344
  var import_strategies6 = require("@tradejs/core/strategies");
2345
- var import_logger13 = require("@tradejs/infra/logger");
2345
+ var import_logger14 = require("@tradejs/infra/logger");
2346
2346
 
2347
2347
  // src/strategyHelpers/runtime.ts
2348
- var import_logger11 = require("@tradejs/infra/logger");
2348
+ var import_logger12 = require("@tradejs/infra/logger");
2349
2349
  var import_constants5 = require("@tradejs/core/constants");
2350
2350
  var import_ml2 = require("@tradejs/infra/ml");
2351
2351
 
2352
+ // src/strategy/entrySubmissionValidation.ts
2353
+ var import_logger5 = require("@tradejs/infra/logger");
2354
+
2355
+ // src/strategy/entryExecutionValidation.ts
2356
+ var EntrySubmissionValidationError = class extends Error {
2357
+ constructor() {
2358
+ super(...arguments);
2359
+ this.name = "EntrySubmissionValidationError";
2360
+ }
2361
+ };
2362
+ var resolveEntryExecutionLimits = (config, direction) => {
2363
+ const side = config[direction];
2364
+ const minimum = side && typeof side === "object" && "minRiskRatio" in side ? side.minRiskRatio : void 0;
2365
+ const maximum = config.MAX_LOSS_VALUE;
2366
+ if (minimum == null && maximum == null) return void 0;
2367
+ const minimumNetRiskRatio = minimum == null ? void 0 : Number(minimum);
2368
+ const maxLossValue = maximum == null ? void 0 : Number(maximum);
2369
+ const feeRate = Number(config.RISK_FEE_RATE ?? 0);
2370
+ const slippage = Number(config.RISK_SLIPPAGE_BPS ?? 0);
2371
+ const impact = Number(config.RISK_MARKET_IMPACT_BPS ?? 0);
2372
+ const slippageBps = slippage + impact;
2373
+ 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)
2374
+ throw new Error("INVALID_ENTRY_EXECUTION_LIMITS");
2375
+ return { minimumNetRiskRatio, maxLossValue, feeRate, slippageBps };
2376
+ };
2377
+ var validateEntryExecution = ({
2378
+ direction,
2379
+ price,
2380
+ qty,
2381
+ stopLossPrice,
2382
+ takeProfitPrice,
2383
+ limits
2384
+ }) => {
2385
+ if (!Number.isFinite(price) || price <= 0 || !Number.isFinite(qty) || qty <= 0 || stopLossPrice == null || !Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || !Number.isFinite(takeProfitPrice) || takeProfitPrice <= 0)
2386
+ throw new EntrySubmissionValidationError(
2387
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
2388
+ );
2389
+ const side = direction === "LONG" ? 1 : -1;
2390
+ if (side * (price - stopLossPrice) <= 0 || side * (takeProfitPrice - price) <= 0)
2391
+ throw new EntrySubmissionValidationError(
2392
+ "INVALID_ENTRY_PROTECTION_AT_SUBMISSION"
2393
+ );
2394
+ const slippage = limits.slippageBps / 1e4;
2395
+ const cost = (value, sign) => value * slippage + value * (1 + sign * slippage) * limits.feeRate;
2396
+ const entryCost = cost(price, side);
2397
+ const lossPerUnit = Math.abs(price - stopLossPrice) + entryCost + cost(stopLossPrice, -side);
2398
+ const rewardPerUnit = Math.max(
2399
+ 0,
2400
+ Math.abs(takeProfitPrice - price) - entryCost - cost(takeProfitPrice, -side)
2401
+ );
2402
+ const netRiskRatio = rewardPerUnit / lossPerUnit;
2403
+ const plannedLossValue = qty * lossPerUnit;
2404
+ if (!Number.isFinite(netRiskRatio) || !Number.isFinite(plannedLossValue))
2405
+ throw new EntrySubmissionValidationError(
2406
+ "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
2407
+ );
2408
+ if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
2409
+ throw new EntrySubmissionValidationError(
2410
+ "ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
2411
+ );
2412
+ if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
2413
+ throw new EntrySubmissionValidationError(
2414
+ "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
2415
+ );
2416
+ return { netRiskRatio, plannedLossValue };
2417
+ };
2418
+
2419
+ // src/strategy/entrySubmissionValidation.ts
2420
+ var MAX_ENTRY_QUOTE_AGE_MS = 5e3;
2421
+ var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
2422
+ var getOrderArrivalSnapshot = async ({
2423
+ connector,
2424
+ symbol,
2425
+ requireTimestamp = false
2426
+ }) => {
2427
+ if (typeof connector.getTopOfBookTicker !== "function") {
2428
+ return {
2429
+ arrivalSnapshotTime: Date.now(),
2430
+ arrivalSource: "unavailable",
2431
+ bid: null,
2432
+ ask: null,
2433
+ arrivalMid: null,
2434
+ spreadBps: null
2435
+ };
2436
+ }
2437
+ try {
2438
+ const ticker = await connector.getTopOfBookTicker(symbol);
2439
+ const arrivalSnapshotTime = toFiniteNumberOrNull(ticker?.timestamp);
2440
+ const timestampUnavailable = requireTimestamp && arrivalSnapshotTime == null;
2441
+ const bid = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.bidPrice);
2442
+ const ask = timestampUnavailable ? null : toFiniteNumberOrNull(ticker?.askPrice);
2443
+ const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
2444
+ const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
2445
+ return {
2446
+ arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
2447
+ arrivalSource: "top_of_book",
2448
+ bid,
2449
+ ask,
2450
+ arrivalMid,
2451
+ spreadBps
2452
+ };
2453
+ } catch (error) {
2454
+ import_logger5.logger.warn(
2455
+ "runtime order arrival snapshot failed: %s %s",
2456
+ symbol,
2457
+ error?.message || String(error)
2458
+ );
2459
+ return {
2460
+ arrivalSnapshotTime: Date.now(),
2461
+ arrivalSource: "top_of_book_error",
2462
+ bid: null,
2463
+ ask: null,
2464
+ arrivalMid: null,
2465
+ spreadBps: null
2466
+ };
2467
+ }
2468
+ };
2469
+ var validateEntryProtectionAtArrival = ({
2470
+ direction,
2471
+ signalPrice,
2472
+ bid,
2473
+ ask,
2474
+ arrivalMid,
2475
+ takeProfits,
2476
+ stopLossPrice
2477
+ }) => {
2478
+ const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
2479
+ const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
2480
+ if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
2481
+ throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
2482
+ }
2483
+ for (const takeProfit of takeProfits) {
2484
+ if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
2485
+ throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
2486
+ }
2487
+ }
2488
+ if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
2489
+ throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
2490
+ }
2491
+ };
2492
+ var createEntrySubmissionValidation = ({
2493
+ connector,
2494
+ symbol,
2495
+ direction,
2496
+ takeProfits,
2497
+ stopLossPrice,
2498
+ limits,
2499
+ onValidated
2500
+ }) => {
2501
+ if (!limits) return void 0;
2502
+ if (!connector.capabilities?.orderSubmissionValidation)
2503
+ throw new EntrySubmissionValidationError(
2504
+ "ENTRY_SUBMISSION_VALIDATION_UNSUPPORTED"
2505
+ );
2506
+ return async ({ qty, executionPrice }) => {
2507
+ const simulated = Reflect.get(connector, "__tradejsTestConnector") === true;
2508
+ const snapshot2 = simulated && executionPrice != null ? {
2509
+ arrivalSnapshotTime: Date.now(),
2510
+ arrivalSource: "simulation",
2511
+ bid: executionPrice,
2512
+ ask: executionPrice,
2513
+ arrivalMid: executionPrice,
2514
+ spreadBps: 0
2515
+ } : await getOrderArrivalSnapshot({
2516
+ connector,
2517
+ symbol,
2518
+ requireTimestamp: true
2519
+ });
2520
+ const age = Date.now() - snapshot2.arrivalSnapshotTime;
2521
+ if (snapshot2.bid == null || snapshot2.ask == null || !Number.isFinite(snapshot2.bid) || !Number.isFinite(snapshot2.ask) || snapshot2.bid <= 0 || snapshot2.ask < snapshot2.bid)
2522
+ throw new EntrySubmissionValidationError(
2523
+ "ENTRY_QUOTE_UNAVAILABLE_AT_SUBMISSION"
2524
+ );
2525
+ if (!Number.isFinite(age) || age < -MAX_ENTRY_QUOTE_AGE_MS || age > MAX_ENTRY_QUOTE_AGE_MS)
2526
+ throw new EntrySubmissionValidationError(
2527
+ "ENTRY_QUOTE_STALE_AT_SUBMISSION"
2528
+ );
2529
+ const price = direction === "LONG" ? snapshot2.ask : snapshot2.bid;
2530
+ validateEntryProtectionAtArrival({
2531
+ direction,
2532
+ signalPrice: price,
2533
+ bid: snapshot2.bid,
2534
+ ask: snapshot2.ask,
2535
+ arrivalMid: snapshot2.arrivalMid,
2536
+ takeProfits,
2537
+ stopLossPrice
2538
+ });
2539
+ const prices = takeProfits.map((tp) => tp.price);
2540
+ const economics2 = validateEntryExecution({
2541
+ direction,
2542
+ price,
2543
+ qty,
2544
+ stopLossPrice,
2545
+ takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
2546
+ limits
2547
+ });
2548
+ onValidated?.(snapshot2, economics2);
2549
+ };
2550
+ };
2551
+
2352
2552
  // src/ai.ts
2353
2553
  var import_aiLanguages2 = require("@tradejs/core/aiLanguages");
2354
2554
  var import_redis2 = require("@tradejs/infra/redis");
@@ -3881,7 +4081,7 @@ var import_node_crypto = require("crypto");
3881
4081
  var import_constants3 = require("@tradejs/core/constants");
3882
4082
  var import_time3 = require("@tradejs/core/time");
3883
4083
  var import_trade = require("@tradejs/core/trade");
3884
- var import_logger5 = require("@tradejs/infra/logger");
4084
+ var import_logger6 = require("@tradejs/infra/logger");
3885
4085
  var import_redis3 = require("@tradejs/infra/redis");
3886
4086
  var now = () => Date.now();
3887
4087
  var toRandomOrderSuffix = () => (0, import_node_crypto.randomUUID)().replace(/-/g, "").slice(0, 12).toLowerCase();
@@ -3940,7 +4140,7 @@ var recordRuntimeTradeOpen = async (params) => {
3940
4140
  )
3941
4141
  ]);
3942
4142
  } catch (error) {
3943
- import_logger5.logger.error(
4143
+ import_logger6.logger.error(
3944
4144
  "runtime trade open journal failed: %s %s",
3945
4145
  record.symbol,
3946
4146
  error?.message || String(error)
@@ -4008,7 +4208,7 @@ var recordRuntimeTradeIncrease = async (params) => {
4008
4208
  )
4009
4209
  ]);
4010
4210
  } catch (error) {
4011
- import_logger5.logger.error(
4211
+ import_logger6.logger.error(
4012
4212
  "runtime trade increase journal failed: %s %s",
4013
4213
  symbol,
4014
4214
  error?.message || String(error)
@@ -4115,7 +4315,7 @@ var markRuntimeTradeClosed = async (params) => {
4115
4315
  )
4116
4316
  ]);
4117
4317
  } catch (error) {
4118
- import_logger5.logger.error(
4318
+ import_logger6.logger.error(
4119
4319
  "runtime trade close journal failed: %s %s",
4120
4320
  symbol,
4121
4321
  error?.message || String(error)
@@ -4125,11 +4325,11 @@ var markRuntimeTradeClosed = async (params) => {
4125
4325
  };
4126
4326
 
4127
4327
  // src/strategyHelpers/marketContextStages.ts
4128
- var import_logger10 = require("@tradejs/infra/logger");
4328
+ var import_logger11 = require("@tradejs/infra/logger");
4129
4329
 
4130
4330
  // src/strategyHelpers/binanceMarketContext.ts
4131
4331
  var import_marketContext = require("@tradejs/infra/timescale/marketContext");
4132
- var import_logger6 = require("@tradejs/infra/logger");
4332
+ var import_logger7 = require("@tradejs/infra/logger");
4133
4333
  var import_strategies = require("@tradejs/core/strategies");
4134
4334
 
4135
4335
  // src/binanceBreadthUniverses.ts
@@ -4493,7 +4693,7 @@ var parseEnabledFlag = (value, env) => {
4493
4693
  if (normalized === "live") return env !== "BACKTEST";
4494
4694
  return false;
4495
4695
  };
4496
- var toFiniteNumberOrNull = (value) => {
4696
+ var toFiniteNumberOrNull2 = (value) => {
4497
4697
  const numeric = typeof value === "number" ? value : Number(value);
4498
4698
  return Number.isFinite(numeric) ? numeric : null;
4499
4699
  };
@@ -4618,14 +4818,14 @@ var toTradeFlowContext = (row, interval) => row ? {
4618
4818
  asOfTs: row.ts.getTime(),
4619
4819
  ageMs: row.ageMs,
4620
4820
  stale: row.stale,
4621
- trades: toFiniteNumberOrNull(row.trades),
4622
- buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
4623
- buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
4624
- sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
4625
- buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
4626
- sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
4627
- netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
4628
- netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
4821
+ trades: toFiniteNumberOrNull2(row.trades),
4822
+ buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
4823
+ buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
4824
+ sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
4825
+ buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
4826
+ sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
4827
+ netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
4828
+ netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
4629
4829
  } : null;
4630
4830
  var getCachedReferenceRows = ({
4631
4831
  referenceSymbols,
@@ -4714,16 +4914,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
4714
4914
  asOfTs: breadth.ts.getTime(),
4715
4915
  ageMs: breadth.ageMs,
4716
4916
  stale: breadth.stale,
4717
- symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
4718
- advancers: toFiniteNumberOrNull(breadth.advancers),
4719
- decliners: toFiniteNumberOrNull(breadth.decliners),
4720
- unchanged: toFiniteNumberOrNull(breadth.unchanged),
4721
- advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
4722
- pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
4723
- pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
4724
- equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
4725
- volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
4726
- dispersion: toFiniteNumberOrNull(breadth.dispersion)
4917
+ symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
4918
+ advancers: toFiniteNumberOrNull2(breadth.advancers),
4919
+ decliners: toFiniteNumberOrNull2(breadth.decliners),
4920
+ unchanged: toFiniteNumberOrNull2(breadth.unchanged),
4921
+ advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
4922
+ pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
4923
+ pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
4924
+ equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
4925
+ volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
4926
+ dispersion: toFiniteNumberOrNull2(breadth.dispersion)
4727
4927
  });
4728
4928
  var enrichSignalWithBinanceMarketContext = async (params) => {
4729
4929
  const {
@@ -4824,46 +5024,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
4824
5024
  asOfTs: primaryBreadth.ts.getTime(),
4825
5025
  ageMs: primaryBreadth.ageMs,
4826
5026
  stale: primaryBreadth.stale,
4827
- btcReturn1h: toFiniteNumberOrNull(
5027
+ btcReturn1h: toFiniteNumberOrNull2(
4828
5028
  primaryBreadth.btcReturn1h
4829
5029
  ),
4830
- btcReturn4h: toFiniteNumberOrNull(
5030
+ btcReturn4h: toFiniteNumberOrNull2(
4831
5031
  primaryBreadth.btcReturn4h
4832
5032
  ),
4833
- btcReturn24h: toFiniteNumberOrNull(
5033
+ btcReturn24h: toFiniteNumberOrNull2(
4834
5034
  primaryBreadth.btcReturn24h
4835
5035
  ),
4836
- altBasketReturn1h: toFiniteNumberOrNull(
5036
+ altBasketReturn1h: toFiniteNumberOrNull2(
4837
5037
  primaryBreadth.altBasketReturn1h
4838
5038
  ),
4839
- altBasketReturn4h: toFiniteNumberOrNull(
5039
+ altBasketReturn4h: toFiniteNumberOrNull2(
4840
5040
  primaryBreadth.altBasketReturn4h
4841
5041
  ),
4842
- altBasketReturn24h: toFiniteNumberOrNull(
5042
+ altBasketReturn24h: toFiniteNumberOrNull2(
4843
5043
  primaryBreadth.altBasketReturn24h
4844
5044
  ),
4845
- btcVsAltReturn1h: toFiniteNumberOrNull(
5045
+ btcVsAltReturn1h: toFiniteNumberOrNull2(
4846
5046
  primaryBreadth.btcVsAltReturn1h
4847
5047
  ),
4848
- btcVsAltReturn4h: toFiniteNumberOrNull(
5048
+ btcVsAltReturn4h: toFiniteNumberOrNull2(
4849
5049
  primaryBreadth.btcVsAltReturn4h
4850
5050
  ),
4851
- btcVsAltReturn24h: toFiniteNumberOrNull(
5051
+ btcVsAltReturn24h: toFiniteNumberOrNull2(
4852
5052
  primaryBreadth.btcVsAltReturn24h
4853
5053
  ),
4854
- btcTurnoverShare1h: toFiniteNumberOrNull(
5054
+ btcTurnoverShare1h: toFiniteNumberOrNull2(
4855
5055
  primaryBreadth.btcTurnoverShare1h
4856
5056
  ),
4857
- btcTurnoverShare24h: toFiniteNumberOrNull(
5057
+ btcTurnoverShare24h: toFiniteNumberOrNull2(
4858
5058
  primaryBreadth.btcTurnoverShare24h
4859
5059
  ),
4860
- btcTurnoverShareChange24h: toFiniteNumberOrNull(
5060
+ btcTurnoverShareChange24h: toFiniteNumberOrNull2(
4861
5061
  primaryBreadth.btcTurnoverShareChange24h
4862
5062
  ),
4863
- altVolToBtcVol24h: toFiniteNumberOrNull(
5063
+ altVolToBtcVol24h: toFiniteNumberOrNull2(
4864
5064
  primaryBreadth.altVolToBtcVol24h
4865
5065
  ),
4866
- altDispersion24h: toFiniteNumberOrNull(
5066
+ altDispersion24h: toFiniteNumberOrNull2(
4867
5067
  primaryBreadth.altDispersion24h
4868
5068
  ),
4869
5069
  regime: primaryBreadth.btcAltRegime ?? "unknown"
@@ -4880,7 +5080,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
4880
5080
  throw error;
4881
5081
  }
4882
5082
  binanceMarketContextUnavailable = true;
4883
- import_logger6.logger.warn(
5083
+ import_logger7.logger.warn(
4884
5084
  "Binance market context disabled after Timescale read failure: %s",
4885
5085
  String(error)
4886
5086
  );
@@ -4890,7 +5090,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
4890
5090
 
4891
5091
  // src/strategyHelpers/coinMarketCapContext.ts
4892
5092
  var import_strategies2 = require("@tradejs/core/strategies");
4893
- var import_logger7 = require("@tradejs/infra/logger");
5093
+ var import_logger8 = require("@tradejs/infra/logger");
4894
5094
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
4895
5095
  var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
4896
5096
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
@@ -4928,7 +5128,7 @@ var asInt = (value, fallback) => {
4928
5128
  const parsed = Number.parseInt(String(value ?? ""), 10);
4929
5129
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
4930
5130
  };
4931
- var toFiniteNumberOrNull2 = (value) => {
5131
+ var toFiniteNumberOrNull3 = (value) => {
4932
5132
  const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
4933
5133
  return Number.isFinite(numeric) ? numeric : null;
4934
5134
  };
@@ -5162,12 +5362,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5162
5362
  const ethRow = references.get("ETHUSDT") ?? null;
5163
5363
  const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
5164
5364
  const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
5165
- const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
5166
- const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
5167
- const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
5365
+ const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
5366
+ const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
5367
+ const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
5168
5368
  previousBtcRow?.marketCapUsd
5169
5369
  );
5170
- const previousEthMarketCapUsd = toFiniteNumberOrNull2(
5370
+ const previousEthMarketCapUsd = toFiniteNumberOrNull3(
5171
5371
  previousEthRow?.marketCapUsd
5172
5372
  );
5173
5373
  const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
@@ -5176,16 +5376,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5176
5376
  previousBtcMarketCapUsd
5177
5377
  );
5178
5378
  const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
5179
- const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
5180
- const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
5379
+ const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
5380
+ const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
5181
5381
  const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
5182
- const btcDominanceChange24hPct = toFiniteNumberOrNull2(
5382
+ const btcDominanceChange24hPct = toFiniteNumberOrNull3(
5183
5383
  globalRow?.btcDominanceChange24hPct
5184
5384
  );
5185
- const altMarketCapChange24hPct = toFiniteNumberOrNull2(
5385
+ const altMarketCapChange24hPct = toFiniteNumberOrNull3(
5186
5386
  globalRow?.altMarketCapChange24hPct
5187
5387
  );
5188
- const altVolumeChange24hPct = toFiniteNumberOrNull2(
5388
+ const altVolumeChange24hPct = toFiniteNumberOrNull3(
5189
5389
  globalRow?.altVolumeChange24hPct
5190
5390
  );
5191
5391
  const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
@@ -5196,19 +5396,19 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5196
5396
  }) : "unknown";
5197
5397
  const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
5198
5398
  stale: exchangeLiquidityRow.stale,
5199
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
5399
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
5200
5400
  exchangeLiquidityRow.totalVolumeChange24hPct
5201
5401
  ),
5202
5402
  fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
5203
5403
  }) : "unknown";
5204
5404
  const cmc100Row = indexRows.get("cmc100") ?? null;
5205
5405
  const cmc20Row = indexRows.get("cmc20") ?? null;
5206
- const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
5207
- const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
5208
- const cmc100Change24hPct = toFiniteNumberOrNull2(
5406
+ const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
5407
+ const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
5408
+ const cmc100Change24hPct = toFiniteNumberOrNull3(
5209
5409
  cmc100Row?.valueChange24hPct
5210
5410
  );
5211
- const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
5411
+ const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
5212
5412
  const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
5213
5413
  const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
5214
5414
  const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
@@ -5232,41 +5432,41 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5232
5432
  asOfTs: globalRow.ts.getTime(),
5233
5433
  ageMs: globalRow.ageMs,
5234
5434
  stale: globalRow.stale,
5235
- totalMarketCapUsd: toFiniteNumberOrNull2(
5435
+ totalMarketCapUsd: toFiniteNumberOrNull3(
5236
5436
  globalRow.totalMarketCapUsd
5237
5437
  ),
5238
- totalVolumeUsd: toFiniteNumberOrNull2(
5438
+ totalVolumeUsd: toFiniteNumberOrNull3(
5239
5439
  globalRow.totalVolumeUsd
5240
5440
  ),
5241
- totalVolumeReportedUsd: toFiniteNumberOrNull2(
5441
+ totalVolumeReportedUsd: toFiniteNumberOrNull3(
5242
5442
  globalRow.totalVolumeReportedUsd
5243
5443
  ),
5244
- altMarketCapUsd: toFiniteNumberOrNull2(
5444
+ altMarketCapUsd: toFiniteNumberOrNull3(
5245
5445
  globalRow.altMarketCapUsd
5246
5446
  ),
5247
- altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
5248
- altVolumeReportedUsd: toFiniteNumberOrNull2(
5447
+ altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
5448
+ altVolumeReportedUsd: toFiniteNumberOrNull3(
5249
5449
  globalRow.altVolumeReportedUsd
5250
5450
  ),
5251
- btcDominancePct: toFiniteNumberOrNull2(
5451
+ btcDominancePct: toFiniteNumberOrNull3(
5252
5452
  globalRow.btcDominancePct
5253
5453
  ),
5254
- ethDominancePct: toFiniteNumberOrNull2(
5454
+ ethDominancePct: toFiniteNumberOrNull3(
5255
5455
  globalRow.ethDominancePct
5256
5456
  ),
5257
5457
  btcDominanceChange24hPct,
5258
- ethDominanceChange24hPct: toFiniteNumberOrNull2(
5458
+ ethDominanceChange24hPct: toFiniteNumberOrNull3(
5259
5459
  globalRow.ethDominanceChange24hPct
5260
5460
  ),
5261
5461
  altMarketCapChange24hPct,
5262
5462
  altVolumeChange24hPct,
5263
- activeCryptocurrencies: toFiniteNumberOrNull2(
5463
+ activeCryptocurrencies: toFiniteNumberOrNull3(
5264
5464
  globalRow.activeCryptocurrencies
5265
5465
  ),
5266
- activeExchanges: toFiniteNumberOrNull2(
5466
+ activeExchanges: toFiniteNumberOrNull3(
5267
5467
  globalRow.activeExchanges
5268
5468
  ),
5269
- activeMarketPairs: toFiniteNumberOrNull2(
5469
+ activeMarketPairs: toFiniteNumberOrNull3(
5270
5470
  globalRow.activeMarketPairs
5271
5471
  ),
5272
5472
  altLiquidityRegime
@@ -5311,22 +5511,22 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5311
5511
  asOfTs: exchangeLiquidityRow.ts.getTime(),
5312
5512
  ageMs: exchangeLiquidityRow.ageMs,
5313
5513
  stale: exchangeLiquidityRow.stale,
5314
- exchangesCount: toFiniteNumberOrNull2(
5514
+ exchangesCount: toFiniteNumberOrNull3(
5315
5515
  exchangeLiquidityRow.exchangesCount
5316
5516
  ),
5317
- totalVolumeUsd: toFiniteNumberOrNull2(
5517
+ totalVolumeUsd: toFiniteNumberOrNull3(
5318
5518
  exchangeLiquidityRow.totalVolumeUsd
5319
5519
  ),
5320
- totalVolumeChange24hPct: toFiniteNumberOrNull2(
5520
+ totalVolumeChange24hPct: toFiniteNumberOrNull3(
5321
5521
  exchangeLiquidityRow.totalVolumeChange24hPct
5322
5522
  ),
5323
- binanceVolumeUsd: toFiniteNumberOrNull2(
5523
+ binanceVolumeUsd: toFiniteNumberOrNull3(
5324
5524
  exchangeLiquidityRow.binanceVolumeUsd
5325
5525
  ),
5326
- binanceVolumeShare: toFiniteNumberOrNull2(
5526
+ binanceVolumeShare: toFiniteNumberOrNull3(
5327
5527
  exchangeLiquidityRow.binanceVolumeShare
5328
5528
  ),
5329
- topExchangeVolumeShare: toFiniteNumberOrNull2(
5529
+ topExchangeVolumeShare: toFiniteNumberOrNull3(
5330
5530
  exchangeLiquidityRow.topExchangeVolumeShare
5331
5531
  ),
5332
5532
  liquidityRegime: exchangeLiquidityRegime
@@ -5339,11 +5539,11 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5339
5539
  asOfTs: fearGreedRow.ts.getTime(),
5340
5540
  ageMs: fearGreedRow.ageMs,
5341
5541
  stale: fearGreedRow.stale,
5342
- value: toFiniteNumberOrNull2(fearGreedRow.value),
5343
- valueChange24h: toFiniteNumberOrNull2(
5542
+ value: toFiniteNumberOrNull3(fearGreedRow.value),
5543
+ valueChange24h: toFiniteNumberOrNull3(
5344
5544
  fearGreedRow.valueChange24h
5345
5545
  ),
5346
- valueChange7d: toFiniteNumberOrNull2(
5546
+ valueChange7d: toFiniteNumberOrNull3(
5347
5547
  fearGreedRow.valueChange7d
5348
5548
  ),
5349
5549
  classification: fearGreedRow.classification ?? "Unknown",
@@ -5363,13 +5563,13 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5363
5563
  cmc100Value,
5364
5564
  cmc100Change24hPct,
5365
5565
  cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
5366
- cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
5566
+ cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
5367
5567
  cmc100Row?.topConstituentWeightPct
5368
5568
  ),
5369
5569
  cmc20Value,
5370
5570
  cmc20Change24hPct,
5371
5571
  cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
5372
- cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
5572
+ cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
5373
5573
  cmc20Row?.topConstituentWeightPct
5374
5574
  ),
5375
5575
  cmc20ToCmc100Ratio,
@@ -5387,7 +5587,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
5387
5587
  throw error;
5388
5588
  }
5389
5589
  coinMarketCapContextUnavailable = true;
5390
- import_logger7.logger.warn(
5590
+ import_logger8.logger.warn(
5391
5591
  "CoinMarketCap context disabled after Timescale read failure: %s",
5392
5592
  String(error)
5393
5593
  );
@@ -5401,7 +5601,7 @@ var import_data3 = require("@tradejs/core/data");
5401
5601
  var import_strategies3 = require("@tradejs/core/strategies");
5402
5602
  var import_constants4 = require("@tradejs/core/constants");
5403
5603
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
5404
- var import_logger8 = require("@tradejs/infra/logger");
5604
+ var import_logger9 = require("@tradejs/infra/logger");
5405
5605
  var STORED_INTERVALS = ["15m", "1h"];
5406
5606
  var CONTEXT_INTERVALS = ["15m", "1h"];
5407
5607
  var DEFAULT_LOOKBACK_HOURS = 48;
@@ -5456,7 +5656,7 @@ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
5456
5656
  var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
5457
5657
  var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
5458
5658
  var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
5459
- var toFiniteNumberOrNull3 = (value) => {
5659
+ var toFiniteNumberOrNull4 = (value) => {
5460
5660
  if (typeof value === "number" && Number.isFinite(value)) return value;
5461
5661
  if (typeof value === "string" && value.trim()) {
5462
5662
  const parsed = Number(value);
@@ -5470,8 +5670,8 @@ var roundNullable = (value, digits = 4) => {
5470
5670
  return Math.round(value * multiplier) / multiplier;
5471
5671
  };
5472
5672
  var deltaNullable = (targetValue, referenceValue) => {
5473
- const target = toFiniteNumberOrNull3(targetValue);
5474
- const reference = toFiniteNumberOrNull3(referenceValue);
5673
+ const target = toFiniteNumberOrNull4(targetValue);
5674
+ const reference = toFiniteNumberOrNull4(referenceValue);
5475
5675
  return target == null || reference == null ? null : roundNullable(target - reference);
5476
5676
  };
5477
5677
  var buildTargetDerivedContext = (params) => {
@@ -5633,7 +5833,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
5633
5833
  throw error;
5634
5834
  }
5635
5835
  derivativesContextUnavailable = true;
5636
- import_logger8.logger.warn(
5836
+ import_logger9.logger.warn(
5637
5837
  "Derivatives context disabled after Timescale read failure: %s",
5638
5838
  String(error)
5639
5839
  );
@@ -5645,7 +5845,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
5645
5845
  var import_data4 = require("@tradejs/core/data");
5646
5846
  var import_strategies4 = require("@tradejs/core/strategies");
5647
5847
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
5648
- var import_logger9 = require("@tradejs/infra/logger");
5848
+ var import_logger10 = require("@tradejs/infra/logger");
5649
5849
 
5650
5850
  // src/hyperliquidWhaleUniverse.ts
5651
5851
  var import_node_crypto3 = require("crypto");
@@ -6165,7 +6365,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
6165
6365
  throw error;
6166
6366
  }
6167
6367
  hyperliquidWhaleContextUnavailable = true;
6168
- import_logger9.logger.warn(
6368
+ import_logger10.logger.warn(
6169
6369
  "Hyperliquid whale context disabled after Timescale read failure: %s",
6170
6370
  String(error)
6171
6371
  );
@@ -6248,7 +6448,7 @@ var runMarketContextStage = async ({
6248
6448
  elapsedMs: Date.now() - startedAt
6249
6449
  };
6250
6450
  if (status === "timed_out") {
6251
- import_logger10.logger.warn(
6451
+ import_logger11.logger.warn(
6252
6452
  "Market context stage timed out: %s after %sms",
6253
6453
  stage,
6254
6454
  result.elapsedMs
@@ -6438,79 +6638,10 @@ var enrichSignalWithAi = async ({
6438
6638
  signal.aiAnalysis = analysis;
6439
6639
  return resolveAiQuality(analysis, direction);
6440
6640
  } catch (err) {
6441
- import_logger11.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
6641
+ import_logger12.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
6442
6642
  }
6443
6643
  return void 0;
6444
6644
  };
6445
- var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
6446
- var getOrderArrivalSnapshot = async ({
6447
- connector,
6448
- symbol
6449
- }) => {
6450
- if (typeof connector.getTopOfBookTicker !== "function") {
6451
- return {
6452
- arrivalSnapshotTime: Date.now(),
6453
- arrivalSource: "unavailable",
6454
- bid: null,
6455
- ask: null,
6456
- arrivalMid: null,
6457
- spreadBps: null
6458
- };
6459
- }
6460
- try {
6461
- const ticker = await connector.getTopOfBookTicker(symbol);
6462
- const arrivalSnapshotTime = toFiniteNumberOrNull4(ticker?.timestamp);
6463
- const bid = toFiniteNumberOrNull4(ticker?.bidPrice);
6464
- const ask = toFiniteNumberOrNull4(ticker?.askPrice);
6465
- const arrivalMid = bid != null && ask != null ? (bid + ask) / 2 : null;
6466
- const spreadBps = bid != null && ask != null && arrivalMid != null && arrivalMid > 0 ? (ask - bid) / arrivalMid * 1e4 : null;
6467
- return {
6468
- arrivalSnapshotTime: arrivalSnapshotTime ?? Date.now(),
6469
- arrivalSource: "top_of_book",
6470
- bid,
6471
- ask,
6472
- arrivalMid,
6473
- spreadBps
6474
- };
6475
- } catch (error) {
6476
- import_logger11.logger.warn(
6477
- "runtime order arrival snapshot failed: %s %s",
6478
- symbol,
6479
- error?.message || String(error)
6480
- );
6481
- return {
6482
- arrivalSnapshotTime: Date.now(),
6483
- arrivalSource: "top_of_book_error",
6484
- bid: null,
6485
- ask: null,
6486
- arrivalMid: null,
6487
- spreadBps: null
6488
- };
6489
- }
6490
- };
6491
- var validateEntryProtectionAtArrival = ({
6492
- direction,
6493
- signalPrice,
6494
- bid,
6495
- ask,
6496
- arrivalMid,
6497
- takeProfits,
6498
- stopLossPrice
6499
- }) => {
6500
- const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
6501
- const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
6502
- if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
6503
- throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
6504
- }
6505
- for (const takeProfit of takeProfits) {
6506
- if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
6507
- throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
6508
- }
6509
- }
6510
- if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
6511
- throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
6512
- }
6513
- };
6514
6645
  var resolveRuntimeTelemetryQuality = ({
6515
6646
  signalClosePrice,
6516
6647
  arrivalMid,
@@ -6587,7 +6718,8 @@ var executeEntryOrder = async ({
6587
6718
  signal,
6588
6719
  beforePlaceOrder,
6589
6720
  recordRuntimeTrade = true,
6590
- leverage
6721
+ leverage,
6722
+ executionLimits
6591
6723
  }) => {
6592
6724
  await beforePlaceOrder?.();
6593
6725
  const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
@@ -6598,23 +6730,57 @@ var executeEntryOrder = async ({
6598
6730
  signal.orderQty = qty;
6599
6731
  signal.orderValue = qty * currentPrice;
6600
6732
  signal.orderFailureReason = void 0;
6601
- const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
6602
- try {
6603
- validateEntryProtectionAtArrival({
6604
- direction,
6605
- signalPrice: currentPrice,
6606
- bid: arrivalSnapshot.bid,
6607
- ask: arrivalSnapshot.ask,
6608
- arrivalMid: arrivalSnapshot.arrivalMid,
6609
- takeProfits,
6610
- stopLossPrice
6611
- });
6612
- } catch (error) {
6613
- signal.orderStatus = "failed";
6614
- signal.orderFailureReason = error.message;
6615
- throw error;
6733
+ let arrivalSnapshot = {
6734
+ arrivalSnapshotTime: Date.now(),
6735
+ arrivalSource: "unavailable",
6736
+ bid: null,
6737
+ ask: null,
6738
+ arrivalMid: null,
6739
+ spreadBps: null
6740
+ };
6741
+ let orderSubmitTime = Date.now();
6742
+ const beforeSubmit = createEntrySubmissionValidation({
6743
+ connector,
6744
+ symbol,
6745
+ direction,
6746
+ takeProfits,
6747
+ stopLossPrice,
6748
+ limits: executionLimits,
6749
+ onValidated: (snapshot2, economics2) => {
6750
+ arrivalSnapshot = snapshot2;
6751
+ orderSubmitTime = Date.now();
6752
+ if (executionLimits?.maxLossValue != null) {
6753
+ signal.riskDecision = {
6754
+ stage: "order_plan",
6755
+ status: "approved",
6756
+ reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
6757
+ plannedLossValue: economics2.plannedLossValue,
6758
+ maxLossValue: executionLimits.maxLossValue,
6759
+ limitUtilization: economics2.plannedLossValue / executionLimits.maxLossValue,
6760
+ enforced: true
6761
+ };
6762
+ }
6763
+ }
6764
+ });
6765
+ if (!beforeSubmit) {
6766
+ arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
6767
+ try {
6768
+ validateEntryProtectionAtArrival({
6769
+ direction,
6770
+ signalPrice: currentPrice,
6771
+ bid: arrivalSnapshot.bid,
6772
+ ask: arrivalSnapshot.ask,
6773
+ arrivalMid: arrivalSnapshot.arrivalMid,
6774
+ takeProfits,
6775
+ stopLossPrice
6776
+ });
6777
+ } catch (error) {
6778
+ signal.orderStatus = "failed";
6779
+ signal.orderFailureReason = error.message;
6780
+ throw error;
6781
+ }
6782
+ orderSubmitTime = Date.now();
6616
6783
  }
6617
- const orderSubmitTime = Date.now();
6618
6784
  const orderPlaced = await connector.placeOrder({
6619
6785
  symbol,
6620
6786
  qty,
@@ -6625,7 +6791,8 @@ var executeEntryOrder = async ({
6625
6791
  direction,
6626
6792
  ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
6627
6793
  orderId,
6628
- signal
6794
+ signal,
6795
+ ...beforeSubmit ? { beforeSubmit } : {}
6629
6796
  });
6630
6797
  const orderAckTime = Date.now();
6631
6798
  const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
@@ -7454,7 +7621,7 @@ var canUseSharedReplayState = ({
7454
7621
  }) => (env === "BACKTEST" || env === "PARITY") && Boolean(sharedReplayKey);
7455
7622
 
7456
7623
  // src/strategy/runtimeExecution.ts
7457
- var import_logger12 = require("@tradejs/infra/logger");
7624
+ var import_logger13 = require("@tradejs/infra/logger");
7458
7625
  var import_types = require("@tradejs/types");
7459
7626
  var buildExitOrderSignal = ({
7460
7627
  strategyName,
@@ -7506,7 +7673,7 @@ var handleExitDecision = async ({
7506
7673
  deploymentId: connector.deploymentId
7507
7674
  });
7508
7675
  if (!activeTrade) {
7509
- import_logger12.logger.warn(
7676
+ import_logger13.logger.warn(
7510
7677
  "[%s] blocked closePosition for untracked runtime position: %s",
7511
7678
  strategyName ?? "unknown",
7512
7679
  symbol
@@ -7514,7 +7681,7 @@ var handleExitDecision = async ({
7514
7681
  return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
7515
7682
  }
7516
7683
  if (!strategyName || activeTrade.strategy !== strategyName) {
7517
- import_logger12.logger.warn(
7684
+ import_logger13.logger.warn(
7518
7685
  "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
7519
7686
  strategyName ?? "unknown",
7520
7687
  symbol,
@@ -7567,7 +7734,7 @@ var handleExitDecision = async ({
7567
7734
  exitType: closedTrade?.exitType ?? "exit"
7568
7735
  });
7569
7736
  } catch (notificationError) {
7570
- import_logger12.logger.error(
7737
+ import_logger13.logger.error(
7571
7738
  "runtime close notification error: %s %s",
7572
7739
  symbol,
7573
7740
  notificationError
@@ -7581,7 +7748,7 @@ var handleExitDecision = async ({
7581
7748
  decision,
7582
7749
  market
7583
7750
  });
7584
- import_logger12.logger.error("close order error: %s %s", symbol, err);
7751
+ import_logger13.logger.error("close order error: %s %s", symbol, err);
7585
7752
  return "ORDER_ERROR";
7586
7753
  }
7587
7754
  return decision.code;
@@ -7608,7 +7775,7 @@ var handleProtectDecision = async ({
7608
7775
  decision,
7609
7776
  market
7610
7777
  });
7611
- import_logger12.logger.error("protect position error: %s %s", symbol, err);
7778
+ import_logger13.logger.error("protect position error: %s %s", symbol, err);
7612
7779
  return "ORDER_ERROR";
7613
7780
  }
7614
7781
  return decision.code;
@@ -7674,7 +7841,11 @@ var executeEntryDecision = async ({
7674
7841
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
7675
7842
  signal,
7676
7843
  beforePlaceOrder,
7677
- recordRuntimeTrade: recordRuntimeJournal
7844
+ recordRuntimeTrade: recordRuntimeJournal,
7845
+ executionLimits: resolveEntryExecutionLimits(
7846
+ hookCtx.strategyConfig,
7847
+ decision.entryContext.direction
7848
+ )
7678
7849
  });
7679
7850
  await invokeStageHooks(
7680
7851
  "afterPlaceOrder",
@@ -7696,19 +7867,32 @@ var executeEntryDecision = async ({
7696
7867
  return signal;
7697
7868
  }
7698
7869
  await beforePlaceOrder();
7699
- const arrivalSnapshot = await getOrderArrivalSnapshot({
7870
+ const beforeSubmit = createEntrySubmissionValidation({
7700
7871
  connector,
7701
- symbol
7702
- });
7703
- validateEntryProtectionAtArrival({
7872
+ symbol,
7704
7873
  direction: decision.entryContext.direction,
7705
- signalPrice: decision.entryContext.prices.currentPrice,
7706
- bid: arrivalSnapshot.bid,
7707
- ask: arrivalSnapshot.ask,
7708
- arrivalMid: arrivalSnapshot.arrivalMid,
7709
7874
  takeProfits: decision.orderPlan.takeProfits,
7710
- stopLossPrice: decision.orderPlan.stopLossPrice
7875
+ stopLossPrice: decision.orderPlan.stopLossPrice,
7876
+ limits: resolveEntryExecutionLimits(
7877
+ hookCtx.strategyConfig,
7878
+ decision.entryContext.direction
7879
+ )
7711
7880
  });
7881
+ if (!beforeSubmit) {
7882
+ const arrivalSnapshot = await getOrderArrivalSnapshot({
7883
+ connector,
7884
+ symbol
7885
+ });
7886
+ validateEntryProtectionAtArrival({
7887
+ direction: decision.entryContext.direction,
7888
+ signalPrice: decision.entryContext.prices.currentPrice,
7889
+ bid: arrivalSnapshot.bid,
7890
+ ask: arrivalSnapshot.ask,
7891
+ arrivalMid: arrivalSnapshot.arrivalMid,
7892
+ takeProfits: decision.orderPlan.takeProfits,
7893
+ stopLossPrice: decision.orderPlan.stopLossPrice
7894
+ });
7895
+ }
7712
7896
  const orderPlaced = await connector.placeOrder({
7713
7897
  symbol,
7714
7898
  qty: decision.orderPlan.qty,
@@ -7716,6 +7900,7 @@ var executeEntryDecision = async ({
7716
7900
  timestamp: decision.entryContext.timestamp,
7717
7901
  direction: decision.entryContext.direction,
7718
7902
  positionIntent: decision.orderPlan.positionIntent,
7903
+ ...beforeSubmit ? { beforeSubmit } : {},
7719
7904
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {}
7720
7905
  });
7721
7906
  if (!orderPlaced) {
@@ -7757,6 +7942,23 @@ var executeEntryDecision = async ({
7757
7942
  { decision, entry, market }
7758
7943
  );
7759
7944
  } catch (err) {
7945
+ if (err instanceof EntrySubmissionValidationError) {
7946
+ if (signal) {
7947
+ signal.orderStatus = "skipped";
7948
+ signal.orderSkipReason = err.message;
7949
+ signal.allocatorDecision = {
7950
+ stage: "runtime_admission",
7951
+ status: "rejected",
7952
+ reason: err.message
7953
+ };
7954
+ }
7955
+ import_logger13.logger.warn(
7956
+ "entry rejected before submission: %s %s",
7957
+ symbol,
7958
+ err.message
7959
+ );
7960
+ return signal ?? err.message;
7961
+ }
7760
7962
  if (signal) {
7761
7963
  signal.orderStatus = "failed";
7762
7964
  if (typeof signal.orderFailureReason !== "string" || !signal.orderFailureReason.trim()) {
@@ -7771,9 +7973,9 @@ var executeEntryDecision = async ({
7771
7973
  market
7772
7974
  });
7773
7975
  if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
7774
- import_logger12.logger.warn("order warning: %s %s", symbol, err);
7976
+ import_logger13.logger.warn("order warning: %s %s", symbol, err);
7775
7977
  } else {
7776
- import_logger12.logger.error("order error: %s %s", symbol, err);
7978
+ import_logger13.logger.error("order error: %s %s", symbol, err);
7777
7979
  }
7778
7980
  return signal ?? "ORDER_ERROR";
7779
7981
  }
@@ -7938,7 +8140,7 @@ var createStrategyRuntime = ({
7938
8140
  try {
7939
8141
  await projectHook(errorParams);
7940
8142
  } catch (hookError) {
7941
- import_logger13.logger.error(
8143
+ import_logger14.logger.error(
7942
8144
  "project hook onRuntimeError failed: %s %s",
7943
8145
  strategyName,
7944
8146
  hookError
@@ -7952,7 +8154,7 @@ var createStrategyRuntime = ({
7952
8154
  try {
7953
8155
  await onRuntimeError(errorParams);
7954
8156
  } catch (hookError) {
7955
- import_logger13.logger.error(
8157
+ import_logger14.logger.error(
7956
8158
  "runtime hook onRuntimeError failed: %s %s",
7957
8159
  strategyName,
7958
8160
  hookError
@@ -7966,7 +8168,7 @@ var createStrategyRuntime = ({
7966
8168
  try {
7967
8169
  return await hook(params);
7968
8170
  } catch (error) {
7969
- import_logger13.logger.error(
8171
+ import_logger14.logger.error(
7970
8172
  'strategy hook "%s" failed for %s: %s',
7971
8173
  stage,
7972
8174
  strategyName,
@@ -9520,7 +9722,7 @@ var loadExchangeRange = async ({
9520
9722
  } catch (error) {
9521
9723
  const message = error?.message || String(error);
9522
9724
  errors?.push(`${label}: ${message}`);
9523
- import_logger14.logger.warn("strategies runtime: %s failed: %s", label, message);
9725
+ import_logger15.logger.warn("strategies runtime: %s failed: %s", label, message);
9524
9726
  return [];
9525
9727
  }
9526
9728
  };
@@ -9560,7 +9762,7 @@ var loadClosedPnlRows = async ({
9560
9762
  } catch (error) {
9561
9763
  const message = error?.message || String(error);
9562
9764
  errors?.push(`getClosedPnl: ${message}`);
9563
- import_logger14.logger.warn("strategies runtime: getClosedPnl failed: %s", message);
9765
+ import_logger15.logger.warn("strategies runtime: getClosedPnl failed: %s", message);
9564
9766
  return [];
9565
9767
  }
9566
9768
  };
@@ -9593,7 +9795,7 @@ var loadExchangeEntryRows = async ({
9593
9795
  } catch (error) {
9594
9796
  const message = error?.message || String(error);
9595
9797
  errors?.push(`getEntryExecutions: ${message}`);
9596
- import_logger14.logger.warn("strategies runtime: getEntryExecutions failed: %s", message);
9798
+ import_logger15.logger.warn("strategies runtime: getEntryExecutions failed: %s", message);
9597
9799
  return [];
9598
9800
  }
9599
9801
  };
@@ -9609,7 +9811,7 @@ var loadOpenPositions = async (connector, errors) => {
9609
9811
  } catch (error) {
9610
9812
  const message = error?.message || String(error);
9611
9813
  errors?.push(`getOpenPositionPnl: ${message}`);
9612
- import_logger14.logger.warn("strategies runtime: getOpenPositionPnl failed: %s", message);
9814
+ import_logger15.logger.warn("strategies runtime: getOpenPositionPnl failed: %s", message);
9613
9815
  return { positions: [], reliable: false };
9614
9816
  }
9615
9817
  };