@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.
- package/dist/ai.js +109 -48
- package/dist/ai.mjs +1 -1
- package/dist/backtest.js +2504 -1883
- package/dist/backtest.mjs +3 -2
- package/dist/{chunk-KYGJBMKE.mjs → chunk-HRON2EAI.mjs} +432 -40
- package/dist/{chunk-UL5Z46JL.mjs → chunk-PYJWXAYR.mjs} +97 -30
- package/dist/chunk-UUNEWAT4.mjs +193 -0
- package/dist/{chunk-BO42QWAB.mjs → chunk-W6PPAADK.mjs} +1 -1
- package/dist/cli.js +837 -775
- package/dist/cli.mjs +4 -3
- package/dist/diagnostics.js +2532 -1911
- package/dist/diagnostics.mjs +4 -3
- package/dist/mcp.d.mts +4 -2
- package/dist/mcp.d.ts +4 -2
- package/dist/mcp.js +2614 -1971
- package/dist/mcp.mjs +27 -5
- package/dist/portfolio.d.mts +28 -0
- package/dist/portfolio.d.ts +28 -0
- package/dist/portfolio.js +210 -0
- package/dist/portfolio.mjs +9 -0
- package/dist/registry.js +2091 -1470
- package/dist/registry.mjs +3 -2
- package/dist/runtimeDashboard.js +2255 -1632
- package/dist/runtimeDashboard.mjs +4 -3
- package/dist/runtimeStrategies.js +2417 -1796
- package/dist/runtimeStrategies.mjs +4 -3
- package/dist/strategies.d.mts +3 -1
- package/dist/strategies.d.ts +3 -1
- package/dist/strategies.js +2106 -1485
- package/dist/strategies.mjs +3 -2
- package/package.json +10 -5
|
@@ -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-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
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
|
-
|
|
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
|
|
2938
|
-
if (!Array.isArray(
|
|
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
|
|
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:
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|