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

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.
@@ -1,3 +1,7 @@
1
+ import {
2
+ createMemoryPortfolioStore,
3
+ createPortfolioCoordinator
4
+ } from "./chunk-UUNEWAT4.mjs";
1
5
  import {
2
6
  askAI,
3
7
  buildAiPayload,
@@ -7,7 +11,7 @@ import {
7
11
  resolveStrategyPolicyProfile,
8
12
  runAiPromptLocal,
9
13
  setStrategyRuntimeFactory
10
- } from "./chunk-UL5Z46JL.mjs";
14
+ } from "./chunk-PYJWXAYR.mjs";
11
15
  import {
12
16
  getTradejsProjectCwd,
13
17
  loadTradejsConfig
@@ -72,15 +76,14 @@ var validateEntryExecution = ({
72
76
  throw new EntrySubmissionValidationError(
73
77
  "ENTRY_ECONOMICS_INPUTS_UNAVAILABLE"
74
78
  );
75
- if (limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio)
76
- throw new EntrySubmissionValidationError(
77
- "ENTRY_RR_BELOW_MIN_AT_SUBMISSION"
78
- );
79
- if (limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9))
80
- throw new EntrySubmissionValidationError(
81
- "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION"
82
- );
83
- return { netRiskRatio, plannedLossValue };
79
+ const belowMinimumNetRiskRatio = limits.minimumNetRiskRatio != null && netRiskRatio <= limits.minimumNetRiskRatio;
80
+ const riskExceeded = limits.maxLossValue != null && plannedLossValue > limits.maxLossValue * (1 + 1e-9);
81
+ return {
82
+ netRiskRatio,
83
+ plannedLossValue,
84
+ riskExceeded,
85
+ belowMinimumNetRiskRatio
86
+ };
84
87
  };
85
88
 
86
89
  // src/strategy/entrySubmissionValidation.ts
@@ -212,6 +215,15 @@ var createEntrySubmissionValidation = ({
212
215
  takeProfitPrice: direction === "LONG" ? Math.max(...prices) : Math.min(...prices),
213
216
  limits
214
217
  });
218
+ if (economics.belowMinimumNetRiskRatio) {
219
+ logger.warn(
220
+ "entry submission warning: %s %s ENTRY_RR_BELOW_MIN_AT_SUBMISSION (RR %s, threshold %s)",
221
+ symbol,
222
+ direction,
223
+ economics.netRiskRatio,
224
+ limits.minimumNetRiskRatio
225
+ );
226
+ }
215
227
  onValidated?.(snapshot2, economics);
216
228
  };
217
229
  };
@@ -2281,6 +2293,10 @@ import { createRuntimeOrderLinkPrefix } from "@tradejs/core/trade";
2281
2293
  import { logger as logger5 } from "@tradejs/infra/logger";
2282
2294
  import {
2283
2295
  delKey,
2296
+ deleteRuntimeTradeReference,
2297
+ getDataStrict,
2298
+ setDataStrict,
2299
+ setHashJsonFieldStrict,
2284
2300
  getData,
2285
2301
  redisKeys,
2286
2302
  setData,
@@ -2447,6 +2463,7 @@ var getActiveRuntimeTrade = async (params) => {
2447
2463
  var markRuntimeTradeClosed = async (params) => {
2448
2464
  const {
2449
2465
  userName,
2466
+ expectedOrderId,
2450
2467
  symbol,
2451
2468
  strategy,
2452
2469
  exitPrice,
@@ -2459,13 +2476,15 @@ var markRuntimeTradeClosed = async (params) => {
2459
2476
  if (!userName) {
2460
2477
  return null;
2461
2478
  }
2462
- const existing = await getActiveRuntimeTrade({
2479
+ const existing = expectedOrderId ? await getDataStrict(
2480
+ redisKeys.runtimeTrade(userName, expectedOrderId)
2481
+ ) : await getActiveRuntimeTrade({
2463
2482
  userName,
2464
2483
  symbol,
2465
2484
  accountId,
2466
2485
  deploymentId
2467
2486
  });
2468
- if (!existing) {
2487
+ if (!existing || expectedOrderId && existing.orderId !== expectedOrderId) {
2469
2488
  return null;
2470
2489
  }
2471
2490
  const orderId = existing.orderId;
@@ -2479,7 +2498,7 @@ var markRuntimeTradeClosed = async (params) => {
2479
2498
  exitPrice: resolvedExitPrice,
2480
2499
  qty: existing.qty
2481
2500
  }) : existing.closedPnl ?? existing.currentPnl ?? null;
2482
- const next = {
2501
+ const next = expectedOrderId && existing.status === "closed" ? existing : {
2483
2502
  ...existing,
2484
2503
  status: "closed",
2485
2504
  currentPrice: resolvedExitPrice,
@@ -2494,22 +2513,33 @@ var markRuntimeTradeClosed = async (params) => {
2494
2513
  const closeDayKey = getRuntimeStorageDayKey(next.exitTimestamp);
2495
2514
  try {
2496
2515
  await Promise.all([
2497
- setData(redisKeys.runtimeTrade(userName, orderId), next, {
2498
- expire: TTL_1M
2499
- }),
2500
- setHashJsonField(
2516
+ (expectedOrderId ? setDataStrict : setData)(
2517
+ redisKeys.runtimeTrade(userName, orderId),
2518
+ next,
2519
+ {
2520
+ expire: TTL_1M
2521
+ }
2522
+ ),
2523
+ (expectedOrderId ? setHashJsonFieldStrict : setHashJsonField)(
2501
2524
  redisKeys.runtimeTradeBucket(userName, dayKey),
2502
2525
  orderId,
2503
2526
  next,
2504
2527
  { expire: TTL_1M }
2505
2528
  ),
2506
- setHashJsonField(
2529
+ (expectedOrderId ? setHashJsonFieldStrict : setHashJsonField)(
2507
2530
  redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
2508
2531
  orderId,
2509
2532
  next,
2510
2533
  { expire: TTL_1M }
2511
2534
  ),
2512
- delKey(
2535
+ expectedOrderId ? deleteRuntimeTradeReference(
2536
+ redisKeys.runtimeActiveTrade(
2537
+ userName,
2538
+ symbol,
2539
+ deploymentId ?? accountId
2540
+ ),
2541
+ expectedOrderId
2542
+ ) : delKey(
2513
2543
  redisKeys.runtimeActiveTrade(
2514
2544
  userName,
2515
2545
  symbol,
@@ -2518,6 +2548,7 @@ var markRuntimeTradeClosed = async (params) => {
2518
2548
  )
2519
2549
  ]);
2520
2550
  } catch (error) {
2551
+ if (expectedOrderId) throw error;
2521
2552
  logger5.error(
2522
2553
  "runtime trade close journal failed: %s %s",
2523
2554
  symbol,
@@ -2527,6 +2558,93 @@ var markRuntimeTradeClosed = async (params) => {
2527
2558
  return next;
2528
2559
  };
2529
2560
 
2561
+ // src/strategyHelpers/tickerProfiles.ts
2562
+ import { readTickerProfiles } from "@tradejs/infra/tickerProfiles";
2563
+ import {
2564
+ isTickerProfileSnapshot,
2565
+ tickerProfileMonth
2566
+ } from "@tradejs/core/tickerProfiles";
2567
+ var snapshots = /* @__PURE__ */ new Map();
2568
+ var resolveTickerProfileSource = (strategy) => {
2569
+ const declared = getStrategyManifest(strategy)?.contextRequirements?.tickerProfiles;
2570
+ if (declared) return declared;
2571
+ const provider = process.env.TICKER_PROFILES_PROVIDER?.trim();
2572
+ const universeId = process.env.TICKER_PROFILES_UNIVERSE?.trim();
2573
+ if (!provider && !universeId) return void 0;
2574
+ if (!provider || !universeId)
2575
+ throw new Error(
2576
+ "Set both TICKER_PROFILES_PROVIDER and TICKER_PROFILES_UNIVERSE"
2577
+ );
2578
+ return { provider, universeId };
2579
+ };
2580
+ var loadTickerProfileContext = async ({
2581
+ projectRoot,
2582
+ provider,
2583
+ universeId,
2584
+ symbol,
2585
+ timestamp
2586
+ }) => {
2587
+ const asOf = tickerProfileMonth(timestamp), key = JSON.stringify([projectRoot, provider, universeId, asOf]);
2588
+ let cached = snapshots.get(key);
2589
+ if (!cached || cached.expires < Date.now()) {
2590
+ const value = readTickerProfiles(projectRoot, provider, universeId, asOf).then((envelope) => {
2591
+ if (!envelope) return null;
2592
+ const s = envelope.snapshot;
2593
+ if (!isTickerProfileSnapshot(s) || s.asOf !== asOf || s.provider !== provider || s.universeId !== universeId)
2594
+ throw new Error("Ticker profile identity/schema mismatch");
2595
+ const entry = snapshots.get(key);
2596
+ if (entry) entry.expires = Infinity;
2597
+ return new Map(
2598
+ s.profiles.map((p) => [
2599
+ p.symbol,
2600
+ Object.freeze({
2601
+ ...p,
2602
+ definition: s.definition,
2603
+ provider,
2604
+ universeId,
2605
+ snapshotId: envelope.snapshotId
2606
+ })
2607
+ ])
2608
+ );
2609
+ }).catch((error) => {
2610
+ snapshots.delete(key);
2611
+ throw error;
2612
+ });
2613
+ cached = { expires: Date.now() + 6e4, value };
2614
+ snapshots.set(key, cached);
2615
+ if (snapshots.size > 24) {
2616
+ const oldest = snapshots.keys().next().value;
2617
+ if (oldest) snapshots.delete(oldest);
2618
+ }
2619
+ }
2620
+ return (await cached.value)?.get(symbol);
2621
+ };
2622
+ var withTickerProfile = (baseContext, profile) => Object.defineProperty(
2623
+ Object.create(
2624
+ Object.getPrototypeOf(baseContext),
2625
+ Object.getOwnPropertyDescriptors(baseContext)
2626
+ ),
2627
+ "tickerProfile",
2628
+ { enumerable: true, configurable: true, value: profile }
2629
+ );
2630
+ var enrichSignalWithTickerProfile = async (signal) => {
2631
+ const source = resolveTickerProfileSource(signal.strategy);
2632
+ if (!source || signal.universe === "tradfi" || !signal.symbol.endsWith("USDT"))
2633
+ return;
2634
+ const profile = await loadTickerProfileContext({
2635
+ ...source,
2636
+ projectRoot: getTradejsProjectCwd(),
2637
+ symbol: signal.symbol,
2638
+ timestamp: signal.timestamp
2639
+ });
2640
+ if (profile) {
2641
+ signal.additionalIndicators = {
2642
+ ...signal.additionalIndicators,
2643
+ tickerProfile: profile
2644
+ };
2645
+ }
2646
+ };
2647
+
2530
2648
  // src/strategyHelpers/marketContextStages.ts
2531
2649
  import { logger as logger7 } from "@tradejs/infra/logger";
2532
2650
 
@@ -2887,6 +3005,7 @@ var enrichSignalWithMarketContextStages = async ({
2887
3005
  })
2888
3006
  });
2889
3007
  }
3008
+ await enrichSignalWithTickerProfile(signal);
2890
3009
  const results = [];
2891
3010
  for (const stage of stages) {
2892
3011
  if (abortSignal?.aborted) break;
@@ -2934,12 +3053,12 @@ var findReplayAiAnalysis = ({
2934
3053
  direction,
2935
3054
  ai
2936
3055
  }) => {
2937
- const snapshots = ai?.replayAnalyses;
2938
- if (!Array.isArray(snapshots) || !snapshots.length) {
3056
+ const snapshots2 = ai?.replayAnalyses;
3057
+ if (!Array.isArray(snapshots2) || !snapshots2.length) {
2939
3058
  return void 0;
2940
3059
  }
2941
3060
  let best = null;
2942
- for (const snapshot2 of snapshots) {
3061
+ for (const snapshot2 of snapshots2) {
2943
3062
  if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
2944
3063
  continue;
2945
3064
  }
@@ -3112,7 +3231,9 @@ var executeEntryOrder = async ({
3112
3231
  beforePlaceOrder,
3113
3232
  recordRuntimeTrade = true,
3114
3233
  leverage,
3115
- executionLimits
3234
+ executionLimits,
3235
+ markPortfolioSubmitted,
3236
+ rejectPortfolioSubmission
3116
3237
  }) => {
3117
3238
  await beforePlaceOrder?.();
3118
3239
  const previousPosition = positionIntent === "increase" ? await connector.getPosition(symbol) : null;
@@ -3145,12 +3266,12 @@ var executeEntryOrder = async ({
3145
3266
  if (executionLimits?.maxLossValue != null) {
3146
3267
  signal.riskDecision = {
3147
3268
  stage: "order_plan",
3148
- status: "approved",
3149
- reason: "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
3269
+ status: economics.riskExceeded ? "warning" : "approved",
3270
+ reason: economics.riskExceeded ? "ENTRY_RISK_EXCEEDS_MAX_LOSS_AT_SUBMISSION" : "PLANNED_LOSS_WITHIN_MAX_LOSS_VALUE",
3150
3271
  plannedLossValue: economics.plannedLossValue,
3151
3272
  maxLossValue: executionLimits.maxLossValue,
3152
3273
  limitUtilization: economics.plannedLossValue / executionLimits.maxLossValue,
3153
- enforced: true
3274
+ enforced: false
3154
3275
  };
3155
3276
  }
3156
3277
  }
@@ -3185,7 +3306,13 @@ var executeEntryOrder = async ({
3185
3306
  ...typeof leverage === "number" && Number.isFinite(leverage) ? { leverage } : {},
3186
3307
  orderId,
3187
3308
  signal,
3188
- ...beforeSubmit ? { beforeSubmit } : {}
3309
+ ...rejectPortfolioSubmission ? { onRejected: rejectPortfolioSubmission } : {},
3310
+ ...beforeSubmit || markPortfolioSubmitted ? {
3311
+ beforeSubmit: async (context) => {
3312
+ await beforeSubmit?.(context);
3313
+ await markPortfolioSubmitted?.(context.qty);
3314
+ }
3315
+ } : {}
3189
3316
  });
3190
3317
  const orderAckTime = Date.now();
3191
3318
  const placedQty = typeof signal.orderQty === "number" && Number.isFinite(signal.orderQty) && signal.orderQty > 0 ? signal.orderQty : qty;
@@ -3460,6 +3587,191 @@ var evaluateResearchEntryPolicy = async (policy, signal, onDecision) => {
3460
3587
  return allowed;
3461
3588
  };
3462
3589
 
3590
+ // src/strategy/portfolioRuntime.ts
3591
+ import { createHash as createHash3 } from "crypto";
3592
+ import {
3593
+ parsePortfolioPolicy,
3594
+ assertPortfolioState,
3595
+ portfolioFingerprint
3596
+ } from "@tradejs/core/portfolio";
3597
+ import { portfolioStateStorage } from "@tradejs/infra/redis";
3598
+ var coordinators = /* @__PURE__ */ new WeakMap();
3599
+ async function observeRuntimePortfolio(connector, binding, asOf, reservations, observedNow = Date.now) {
3600
+ if (!connector.getOpenPositionPnl || !connector.getEntryExecutions)
3601
+ throw new Error(
3602
+ "Portfolio policies require position marks and entry executions"
3603
+ );
3604
+ const orders = await Promise.all(
3605
+ reservations.map(async (r) => {
3606
+ if (r.submitted && connector.getEntryOrderObservation) {
3607
+ const fact = await connector.getEntryOrderObservation({
3608
+ symbol: r.symbol,
3609
+ orderId: r.id
3610
+ });
3611
+ if (fact) return fact;
3612
+ }
3613
+ const executions = r.submitted ? await connector.getEntryExecutions({
3614
+ symbol: r.symbol,
3615
+ startTime: r.timestamp,
3616
+ endTime: asOf
3617
+ }) : [];
3618
+ const filledQty = executions.filter(
3619
+ (e) => (e.orderLinkId === r.id || e.orderId === r.id) && e.direction === r.direction
3620
+ ).reduce((sum, e) => sum + e.qty, 0);
3621
+ return {
3622
+ id: r.id,
3623
+ status: filledQty >= r.qty ? "filled" : filledQty > 0 ? "partial" : "pending",
3624
+ filledQty,
3625
+ positionId: r.id
3626
+ };
3627
+ })
3628
+ );
3629
+ const snapshots2 = await connector.getOpenPositionPnl();
3630
+ const positions = await Promise.all(
3631
+ snapshots2.map(async (p) => {
3632
+ const reserved = reservations.find(
3633
+ (r) => r.symbol === p.symbol && r.direction === p.direction && orders.some(
3634
+ (o) => o.id === r.id && o.filledQty > 0 && p.qty <= o.filledQty * (1 + 1e-8)
3635
+ )
3636
+ );
3637
+ const trade = await getActiveRuntimeTrade({
3638
+ ...binding,
3639
+ symbol: p.symbol
3640
+ });
3641
+ const owner = trade && trade.direction === p.direction && p.qty <= trade.qty * (1 + 1e-8) && Math.abs(p.price - (trade.actualEntryPrice ?? trade.entryPrice)) <= p.price * 1e-8 ? trade : null;
3642
+ return {
3643
+ id: reserved?.id ?? owner?.orderId ?? `unknown:${p.symbol}:${p.direction}`,
3644
+ strategy: reserved?.strategy ?? owner?.strategy ?? null,
3645
+ symbol: p.symbol,
3646
+ direction: p.direction,
3647
+ qty: p.qty,
3648
+ entryPrice: p.price,
3649
+ markPrice: p.currentPrice,
3650
+ markedAt: p.observedAt ?? null,
3651
+ stopLossPrice: p.slPrice ?? p.stopLossPrice ?? null
3652
+ };
3653
+ })
3654
+ );
3655
+ return { asOf: Math.max(asOf, observedNow()), positions, orders };
3656
+ }
3657
+ function persistentStore(key) {
3658
+ const raw = /* @__PURE__ */ new WeakMap();
3659
+ return {
3660
+ async read() {
3661
+ const value = await portfolioStateStorage.read(key);
3662
+ if (value === null) return null;
3663
+ const state = JSON.parse(value);
3664
+ assertPortfolioState(state);
3665
+ raw.set(state, value);
3666
+ return state;
3667
+ },
3668
+ async compare(previous, next) {
3669
+ const expected = previous === null ? null : raw.get(previous);
3670
+ if (expected === void 0)
3671
+ throw new Error("Portfolio CAS requires a storage snapshot");
3672
+ return portfolioStateStorage.compare(key, expected, JSON.stringify(next));
3673
+ }
3674
+ };
3675
+ }
3676
+ function getPortfolioRuntime({
3677
+ value,
3678
+ connectorName,
3679
+ strategy,
3680
+ connector,
3681
+ env,
3682
+ userName,
3683
+ accountId,
3684
+ deploymentId
3685
+ }) {
3686
+ const policy = parsePortfolioPolicy(value);
3687
+ if (!policy) return void 0;
3688
+ const replay = (env === "BACKTEST" || env === "PARITY") && Reflect.get(connector, "__tradejsReplayConnector") === true;
3689
+ if ((env === "BACKTEST" || env === "PARITY") && (!replay || !connector.getPortfolioObservation))
3690
+ throw new Error(
3691
+ "Portfolio policies require shared portfolio replay; isolated symbol backtests are unsupported"
3692
+ );
3693
+ if (!replay && (!userName || !accountId || !deploymentId || !connector.getOpenPositionPnl || !connector.getEntryExecutions))
3694
+ throw new Error(
3695
+ "Portfolio runtime requires exact user/account/deployment binding, marks and executions"
3696
+ );
3697
+ const binding = {
3698
+ userName,
3699
+ accountId,
3700
+ deploymentId
3701
+ };
3702
+ const scope = policy.scope === "strategy" ? strategy : policy.scope === "account" ? "account" : [...policy.scope.strategies].sort();
3703
+ const key = createHash3("sha256").update(JSON.stringify([binding, connectorName, connector.universe, scope])).digest("hex");
3704
+ const fingerprint3 = portfolioFingerprint(policy, strategy);
3705
+ const cacheKey = key;
3706
+ let registry = coordinators.get(connector);
3707
+ if (!registry) {
3708
+ registry = /* @__PURE__ */ new Map();
3709
+ coordinators.set(connector, registry);
3710
+ }
3711
+ const existing = registry.get(cacheKey);
3712
+ if (existing) {
3713
+ if (existing.fingerprint !== fingerprint3)
3714
+ throw new Error("Conflicting portfolio configuration for the same scope");
3715
+ return existing.coordinator;
3716
+ }
3717
+ const coordinator = createPortfolioCoordinator({
3718
+ policy,
3719
+ strategy,
3720
+ store: replay ? createMemoryPortfolioStore() : persistentStore(key),
3721
+ execution: {
3722
+ async confirmClosed(intent, asOf) {
3723
+ if (replay) return;
3724
+ const position = intent.position;
3725
+ await markRuntimeTradeClosed({
3726
+ ...binding,
3727
+ strategy: position.strategy,
3728
+ expectedOrderId: position.id,
3729
+ symbol: position.symbol,
3730
+ exitPrice: position.markPrice,
3731
+ exitTimestamp: asOf,
3732
+ exitType: "exit"
3733
+ });
3734
+ },
3735
+ observe: (asOf, reservations) => replay ? connector.getPortfolioObservation(asOf, reservations) : observeRuntimePortfolio(connector, binding, asOf, reservations),
3736
+ async close(position, intent, asOf) {
3737
+ if (!replay) {
3738
+ const trade = await getActiveRuntimeTrade({
3739
+ ...binding,
3740
+ symbol: position.symbol
3741
+ });
3742
+ if (!trade || trade.orderId !== position.id)
3743
+ throw new Error("Portfolio close ownership changed");
3744
+ }
3745
+ await connector.closePosition({
3746
+ symbol: position.symbol,
3747
+ direction: position.direction,
3748
+ price: position.markPrice,
3749
+ timestamp: asOf,
3750
+ signal: {
3751
+ signalId: `${position.id}:${intent.reason}`,
3752
+ strategy: position.strategy,
3753
+ symbol: position.symbol,
3754
+ direction: position.direction,
3755
+ interval: "1",
3756
+ timestamp: asOf,
3757
+ prices: {
3758
+ riskRatio: 0,
3759
+ currentPrice: position.markPrice,
3760
+ takeProfitPrice: position.markPrice,
3761
+ stopLossPrice: position.markPrice
3762
+ },
3763
+ figures: {},
3764
+ indicators: {},
3765
+ additionalIndicators: { portfolioExitReason: intent.reason }
3766
+ }
3767
+ });
3768
+ }
3769
+ }
3770
+ });
3771
+ registry.set(cacheKey, { fingerprint: fingerprint3, coordinator });
3772
+ return coordinator;
3773
+ }
3774
+
3463
3775
  // src/strategyRuntime.ts
3464
3776
  import { parseJevConfig } from "@tradejs/core/jev";
3465
3777
  import {
@@ -4195,7 +4507,7 @@ var handleProtectDecision = async ({
4195
4507
  }
4196
4508
  return decision.code;
4197
4509
  };
4198
- var executeEntryDecision = async ({
4510
+ var executeAdmittedEntryDecision = async ({
4199
4511
  connector,
4200
4512
  symbol,
4201
4513
  decision,
@@ -4209,7 +4521,9 @@ var executeEntryDecision = async ({
4209
4521
  ai,
4210
4522
  recordRuntimeJournal,
4211
4523
  invokeStageHooks,
4212
- notifyRuntimeError
4524
+ notifyRuntimeError,
4525
+ markPortfolioSubmitted,
4526
+ rejectPortfolioSubmission
4213
4527
  }) => {
4214
4528
  const signal = decision.signal;
4215
4529
  const beforePlaceOrder = async () => {
@@ -4256,6 +4570,8 @@ var executeEntryDecision = async ({
4256
4570
  ...Number.isFinite(Number(hookCtx.strategyConfig.LEVERAGE)) ? { leverage: Number(hookCtx.strategyConfig.LEVERAGE) } : {},
4257
4571
  signal,
4258
4572
  beforePlaceOrder,
4573
+ markPortfolioSubmitted,
4574
+ rejectPortfolioSubmission,
4259
4575
  recordRuntimeTrade: recordRuntimeJournal,
4260
4576
  executionLimits: resolveEntryExecutionLimits(
4261
4577
  hookCtx.strategyConfig,
@@ -4396,6 +4712,37 @@ var executeEntryDecision = async ({
4396
4712
  }
4397
4713
  return signal ?? decision.code;
4398
4714
  };
4715
+ async function executeEntryDecision(params) {
4716
+ if (!params.portfolio) return executeAdmittedEntryDecision(params);
4717
+ const { decision, portfolio } = params;
4718
+ const signal = decision.signal;
4719
+ if (!signal || decision.orderPlan.positionIntent === "increase")
4720
+ return "PORTFOLIO_REQUIRES_NEW_SIGNAL_POSITION";
4721
+ signal.orderId ??= createRuntimeOrderId(signal.strategy);
4722
+ const result = await portfolio.submit(
4723
+ params.portfolioTime ?? decision.entryContext.timestamp,
4724
+ {
4725
+ id: signal.orderId,
4726
+ signalId: signal.signalId,
4727
+ strategy: signal.strategy,
4728
+ symbol: params.symbol,
4729
+ direction: decision.entryContext.direction,
4730
+ qty: decision.orderPlan.qty,
4731
+ timestamp: decision.entryContext.timestamp
4732
+ },
4733
+ (markPortfolioSubmitted, rejectPortfolioSubmission) => executeAdmittedEntryDecision({
4734
+ ...params,
4735
+ markPortfolioSubmitted,
4736
+ rejectPortfolioSubmission
4737
+ })
4738
+ );
4739
+ if (result.reason) {
4740
+ signal.orderStatus = "failed";
4741
+ signal.orderFailureReason = result.reason;
4742
+ return signal;
4743
+ }
4744
+ return result.value;
4745
+ }
4399
4746
 
4400
4747
  // src/strategyRuntime.ts
4401
4748
  var cloneWithPropertyDescriptors = (value) => Object.create(
@@ -4423,6 +4770,7 @@ var createStrategyRuntime = ({
4423
4770
  };
4424
4771
  const creator = async ({
4425
4772
  userName,
4773
+ connectorName,
4426
4774
  config: baseConfig,
4427
4775
  symbol,
4428
4776
  universe: requestedUniverse,
@@ -4461,6 +4809,17 @@ var createStrategyRuntime = ({
4461
4809
  const projectConfig = await loadTradejsConfig(projectRoot);
4462
4810
  const projectHooks = projectConfig.hooks;
4463
4811
  const env = String(config.ENV ?? "BACKTEST");
4812
+ const portfolio = getPortfolioRuntime({
4813
+ connectorName,
4814
+ value: baseConfig?.PORTFOLIO_POLICY ?? config.PORTFOLIO_POLICY,
4815
+ strategy: strategyName,
4816
+ connector,
4817
+ env,
4818
+ userName,
4819
+ accountId,
4820
+ deploymentId
4821
+ });
4822
+ const portfolioContext = portfolio ? (now2) => isRuntimeOrderExecutionEnabled({ config, env, connector }) ? portfolio.tick(now2) : portfolio.context(now2) : void 0;
4464
4823
  const researchPolicyValue = baseConfig?.RESEARCH_ENTRY_POLICY ?? config.RESEARCH_ENTRY_POLICY;
4465
4824
  const researchEntryPolicy = researchPolicyValue == null ? void 0 : parseResearchEntryPolicy(researchPolicyValue);
4466
4825
  if (researchEntryPolicy && (env !== "BACKTEST" || Reflect.get(connector, "__tradejsTestConnector") !== true))
@@ -4498,6 +4857,9 @@ var createStrategyRuntime = ({
4498
4857
  });
4499
4858
  const strategyPolicyProfile = getPolicyProfile();
4500
4859
  const indicatorPeriods = buildDefaultIndicatorPeriods(config);
4860
+ const strategyConfig = { ...config };
4861
+ delete strategyConfig.RECORD_RUNTIME_TRADES;
4862
+ delete strategyConfig.SIMULATE_ORDERS;
4501
4863
  const hookBase = {
4502
4864
  connector,
4503
4865
  strategyName,
@@ -4508,7 +4870,7 @@ var createStrategyRuntime = ({
4508
4870
  accountId,
4509
4871
  deploymentId,
4510
4872
  policyProfileId: strategyPolicyProfile?.id ?? requestedPolicyProfileId,
4511
- strategyConfig: config,
4873
+ strategyConfig,
4512
4874
  env,
4513
4875
  isConfigFromBacktest
4514
4876
  };
@@ -4576,7 +4938,7 @@ var createStrategyRuntime = ({
4576
4938
  );
4577
4939
  }
4578
4940
  };
4579
- const invokeHook = async (stage, hook, params, errorContext = {}) => {
4941
+ const invokeHook = async (stage, hook, params, errorContext = {}, failureResult) => {
4580
4942
  if (!hook) {
4581
4943
  return void 0;
4582
4944
  }
@@ -4596,17 +4958,18 @@ var createStrategyRuntime = ({
4596
4958
  entry: errorContext.entry,
4597
4959
  market: errorContext.market
4598
4960
  });
4599
- return void 0;
4961
+ return failureResult;
4600
4962
  }
4601
4963
  };
4602
- const invokeProjectHooks = async (stage, params, errorContext = {}) => {
4964
+ const invokeProjectHooks = async (stage, params, errorContext = {}, failureResult) => {
4603
4965
  const results = [];
4604
4966
  for (const hook of getProjectHookList(stage)) {
4605
4967
  const result = await invokeHook(
4606
4968
  stage,
4607
4969
  hook,
4608
4970
  params,
4609
- errorContext
4971
+ errorContext,
4972
+ failureResult
4610
4973
  );
4611
4974
  if (result !== void 0) {
4612
4975
  results.push(result);
@@ -4621,10 +4984,12 @@ var createStrategyRuntime = ({
4621
4984
  return invokeHook(stage, hook, params, errorContext);
4622
4985
  };
4623
4986
  const invokeGateHooks = async (stage, hook, params, errorContext = {}) => {
4987
+ const failureResult = stage === "beforeEntryGate" ? { allow: false, reason: "ERROR" } : void 0;
4624
4988
  const projectResults = await invokeProjectHooks(
4625
4989
  stage,
4626
4990
  params,
4627
- errorContext
4991
+ errorContext,
4992
+ failureResult
4628
4993
  );
4629
4994
  const projectBlock = projectResults.find(
4630
4995
  (result) => result?.allow === false
@@ -4636,7 +5001,8 @@ var createStrategyRuntime = ({
4636
5001
  stage,
4637
5002
  hook,
4638
5003
  params,
4639
- errorContext
5004
+ errorContext,
5005
+ failureResult
4640
5006
  );
4641
5007
  };
4642
5008
  const applyProjectAfterCoreDecisionHooks = async ({
@@ -4752,6 +5118,20 @@ var createStrategyRuntime = ({
4752
5118
  interval: decisionInterval
4753
5119
  }) => {
4754
5120
  if (!baseContext) return void 0;
5121
+ const profileSource = strategyManifest?.contextRequirements?.tickerProfiles;
5122
+ if (profileSource) {
5123
+ const profile = await loadTickerProfileContext({
5124
+ ...profileSource,
5125
+ projectRoot,
5126
+ symbol: decisionSymbol,
5127
+ timestamp: candle.timestamp
5128
+ });
5129
+ if (!profile)
5130
+ throw new Error(
5131
+ "Required ticker profile is unavailable at decision time"
5132
+ );
5133
+ baseContext = withTickerProfile(baseContext, profile);
5134
+ }
4755
5135
  if (!coreContextRequirements.has("hyperliquidWhales")) {
4756
5136
  return baseContext;
4757
5137
  }
@@ -4782,11 +5162,8 @@ var createStrategyRuntime = ({
4782
5162
  return enrichedBaseContext;
4783
5163
  }
4784
5164
  });
4785
- const coreConfig = { ...config };
4786
- delete coreConfig.RECORD_RUNTIME_TRADES;
4787
- delete coreConfig.SIMULATE_ORDERS;
4788
5165
  const core = await createCore({
4789
- config: coreConfig,
5166
+ config: strategyConfig,
4790
5167
  data,
4791
5168
  strategyApi,
4792
5169
  indicatorsState
@@ -4922,6 +5299,8 @@ var createStrategyRuntime = ({
4922
5299
  runtime: pending.runtime
4923
5300
  });
4924
5301
  return executeEntryDecision({
5302
+ portfolio,
5303
+ portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
4925
5304
  connector,
4926
5305
  symbol,
4927
5306
  decision: pending.decision,
@@ -4939,6 +5318,10 @@ var createStrategyRuntime = ({
4939
5318
  });
4940
5319
  };
4941
5320
  const runWithDecisionOverride = async (candle, btcCandle, options = {}) => {
5321
+ if (portfolioContext)
5322
+ await portfolioContext(
5323
+ env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now()
5324
+ );
4942
5325
  appendCurrentMarketData(candle, btcCandle, options.ethCandle);
4943
5326
  const ethCandle = resolveEthCandle(candle, options.ethCandle);
4944
5327
  indicatorsState.setCurrentBar(candle, btcCandle, ethCandle);
@@ -5109,6 +5492,13 @@ var createStrategyRuntime = ({
5109
5492
  });
5110
5493
  const signal = decision.signal;
5111
5494
  if (signal) {
5495
+ if (portfolio)
5496
+ signal.additionalIndicators = {
5497
+ ...signal.additionalIndicators,
5498
+ portfolioContext: await portfolioContext(
5499
+ env === "BACKTEST" || env === "PARITY" ? decision.entryContext.timestamp : Date.now()
5500
+ )
5501
+ };
5112
5502
  if (runtimeLineage) signal.runtimeLineage = runtimeLineage;
5113
5503
  if (universe) signal.universe = universe;
5114
5504
  if (assetClass) signal.assetClass = assetClass;
@@ -5348,6 +5738,8 @@ var createStrategyRuntime = ({
5348
5738
  });
5349
5739
  }
5350
5740
  return executeEntryDecision({
5741
+ portfolio,
5742
+ portfolioTime: env === "BACKTEST" || env === "PARITY" ? candle.timestamp : Date.now(),
5351
5743
  connector,
5352
5744
  symbol,
5353
5745
  decision,