@tradejs/core 3.0.0 → 3.1.0

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.
@@ -0,0 +1,478 @@
1
+ import {
2
+ normalizeStrategyOrderLinkKey,
3
+ parseStrategyOrderLinkKey
4
+ } from "./chunk-NYJU3J7Y.mjs";
5
+ import "./chunk-M7QGVZ3J.mjs";
6
+ import {
7
+ INITIAL_BACKTEST_AMOUNT
8
+ } from "./chunk-MKCQSB4H.mjs";
9
+
10
+ // src/utils/runtimeTradeAnalytics.ts
11
+ import { addMonths, endOfMonth, startOfMonth } from "date-fns";
12
+ var MS_IN_DAY = 24 * 60 * 60 * 1e3;
13
+ var AVG_DAYS_IN_MONTH = 30.4375;
14
+ var roundValue = (value, digits = 2) => {
15
+ if (!Number.isFinite(value)) return 0;
16
+ const factor = 10 ** digits;
17
+ return Math.round(value * factor) / factor;
18
+ };
19
+ var toFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
20
+ var getTradePnl = (trade) => trade.status === "closed" ? trade.closedPnl ?? trade.currentPnl ?? null : trade.currentPnl ?? null;
21
+ var getTradeResolvedTimestamp = (trade, endTime) => typeof trade.exitTimestamp === "number" && Number.isFinite(trade.exitTimestamp) ? trade.exitTimestamp : endTime;
22
+ var resolveTradesWithKnownPnl = (trades, endTime) => trades.map((trade) => {
23
+ const resolvedPnl = getTradePnl(trade);
24
+ const resolvedTimestamp = getTradeResolvedTimestamp(trade, endTime);
25
+ if (typeof resolvedPnl !== "number" || !Number.isFinite(resolvedPnl) || !Number.isFinite(resolvedTimestamp)) {
26
+ return null;
27
+ }
28
+ return {
29
+ ...trade,
30
+ resolvedPnl,
31
+ resolvedTimestamp: Math.max(trade.entryTimestamp, resolvedTimestamp)
32
+ };
33
+ }).filter((trade) => trade != null).sort(
34
+ (left, right) => left.resolvedTimestamp - right.resolvedTimestamp || left.entryTimestamp - right.entryTimestamp
35
+ );
36
+ var calculateMaxDrawdown = (amounts) => {
37
+ let peak = amounts[0] ?? 0;
38
+ let maxDrawdown = 0;
39
+ for (const amount of amounts) {
40
+ peak = Math.max(peak, amount);
41
+ if (peak > 0) {
42
+ maxDrawdown = Math.max(maxDrawdown, (peak - amount) / peak * 100);
43
+ }
44
+ }
45
+ return roundValue(maxDrawdown);
46
+ };
47
+ var calculateSharpeRatio = (orderLog, startTime, endTime) => {
48
+ if (!orderLog.length || endTime <= startTime) return null;
49
+ const points = [...orderLog].map(([ts, amount]) => ({ ts, amount })).sort((left, right) => left.ts - right.ts);
50
+ const eomSeries = [];
51
+ let pointIndex = 0;
52
+ let monthCursor = startOfMonth(new Date(startTime));
53
+ const lastMonth = endOfMonth(new Date(endTime));
54
+ let lastAmount = points[0]?.amount ?? INITIAL_BACKTEST_AMOUNT;
55
+ while (monthCursor <= lastMonth) {
56
+ const eomTs = endOfMonth(monthCursor).getTime();
57
+ while (pointIndex < points.length && points[pointIndex].ts <= eomTs) {
58
+ lastAmount = points[pointIndex].amount;
59
+ pointIndex += 1;
60
+ }
61
+ eomSeries.push(lastAmount);
62
+ monthCursor = addMonths(monthCursor, 1);
63
+ }
64
+ if (eomSeries.length < 2) return null;
65
+ const monthlyReturns = eomSeries.slice(1).map((amount, index) => {
66
+ const previous = eomSeries[index];
67
+ return previous > 0 ? amount / previous - 1 : 0;
68
+ });
69
+ const mean = monthlyReturns.reduce((sum, value) => sum + value, 0) / monthlyReturns.length;
70
+ const variance = monthlyReturns.reduce((sum, value) => sum + (value - mean) ** 2, 0) / monthlyReturns.length;
71
+ const standardDeviation = Math.sqrt(variance);
72
+ return standardDeviation > 0 && Number.isFinite(standardDeviation) ? roundValue(mean / standardDeviation * Math.sqrt(12)) : null;
73
+ };
74
+ var calculateExposurePercent = (trades, startTime, endTime) => {
75
+ if (endTime <= startTime) return 0;
76
+ const intervals = trades.map((trade) => ({
77
+ start: Math.max(startTime, trade.entryTimestamp),
78
+ end: Math.min(endTime, getTradeResolvedTimestamp(trade, endTime))
79
+ })).filter(({ start, end }) => end > start).sort((left, right) => left.start - right.start);
80
+ const merged = [];
81
+ for (const interval of intervals) {
82
+ const last = merged[merged.length - 1];
83
+ if (!last || interval.start > last.end) {
84
+ merged.push({ ...interval });
85
+ } else {
86
+ last.end = Math.max(last.end, interval.end);
87
+ }
88
+ }
89
+ const coveredMs = merged.reduce(
90
+ (sum, interval) => sum + interval.end - interval.start,
91
+ 0
92
+ );
93
+ return roundValue(coveredMs / (endTime - startTime) * 100);
94
+ };
95
+ var calculateStreaks = (pnls) => {
96
+ let currentWins = 0;
97
+ let currentLosses = 0;
98
+ let maxConsecutiveWins = 0;
99
+ let maxConsecutiveLosses = 0;
100
+ for (const pnl of pnls) {
101
+ if (pnl > 0) {
102
+ currentWins += 1;
103
+ currentLosses = 0;
104
+ } else if (pnl < 0) {
105
+ currentLosses += 1;
106
+ currentWins = 0;
107
+ } else {
108
+ currentWins = 0;
109
+ currentLosses = 0;
110
+ }
111
+ maxConsecutiveWins = Math.max(maxConsecutiveWins, currentWins);
112
+ maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentLosses);
113
+ }
114
+ return { maxConsecutiveWins, maxConsecutiveLosses };
115
+ };
116
+ var calculateSymbolConcentration = (trades, limit) => {
117
+ const pnlBySymbol = /* @__PURE__ */ new Map();
118
+ for (const trade of trades) {
119
+ pnlBySymbol.set(
120
+ trade.symbol,
121
+ (pnlBySymbol.get(trade.symbol) ?? 0) + Math.abs(trade.resolvedPnl)
122
+ );
123
+ }
124
+ const values = [...pnlBySymbol.values()].sort((left, right) => right - left);
125
+ const total = values.reduce((sum, value) => sum + value, 0);
126
+ return total > 0 ? roundValue(
127
+ values.slice(0, limit).reduce((sum, value) => sum + value, 0) / total * 100
128
+ ) : null;
129
+ };
130
+ var createEmptyStat = (startTime, endTime) => {
131
+ const periodDays = Math.max(0, (endTime - startTime) / MS_IN_DAY);
132
+ return {
133
+ periodDays: roundValue(periodDays),
134
+ periodMonths: roundValue(periodDays / AVG_DAYS_IN_MONTH),
135
+ orders: 0,
136
+ wins: 0,
137
+ losses: 0,
138
+ ordersPerMonth: 0,
139
+ exposure: 0,
140
+ amount: INITIAL_BACKTEST_AMOUNT,
141
+ maxAmount: INITIAL_BACKTEST_AMOUNT,
142
+ minAmount: INITIAL_BACKTEST_AMOUNT,
143
+ netProfit: 0,
144
+ totalReturn: 0,
145
+ cagr: 0,
146
+ maxDrawdown: 0,
147
+ calmar: null,
148
+ winRate: 0,
149
+ riskRewardRatio: null,
150
+ expectancy: 0,
151
+ maxConsecutiveWins: 0,
152
+ maxConsecutiveLosses: 0,
153
+ sharpeRatio: null,
154
+ score: 0
155
+ };
156
+ };
157
+ var resolveStrategyNameByOrderLinkId = ({
158
+ orderLinkId,
159
+ strategyNames
160
+ }) => {
161
+ const strategyKey = parseStrategyOrderLinkKey(orderLinkId);
162
+ if (!strategyKey) return null;
163
+ const strategyByKey = /* @__PURE__ */ new Map();
164
+ for (const strategyName of strategyNames) {
165
+ const key = normalizeStrategyOrderLinkKey(strategyName);
166
+ if (key && !strategyByKey.has(key)) strategyByKey.set(key, strategyName);
167
+ }
168
+ return strategyByKey.get(strategyKey) ?? null;
169
+ };
170
+ var isRuntimeTradeRecord = (value) => {
171
+ if (!value || typeof value !== "object") return false;
172
+ const record = value;
173
+ return typeof record.orderId === "string" && typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.entryTimestamp === "number" && typeof record.entryPrice === "number" && typeof record.qty === "number";
174
+ };
175
+ var selectTradesForWindow = (trades, startTime, activeOrderIds = /* @__PURE__ */ new Set()) => trades.filter((trade) => {
176
+ if (trade.status === "active") {
177
+ return activeOrderIds.has(trade.orderId) || trade.entryTimestamp >= startTime;
178
+ }
179
+ return trade.entryTimestamp >= startTime || typeof trade.exitTimestamp === "number" && trade.exitTimestamp >= startTime;
180
+ });
181
+ var buildRuntimeStrategyAnalytics = ({
182
+ trades,
183
+ startTime,
184
+ endTime
185
+ }) => {
186
+ const resolvedTrades = resolveTradesWithKnownPnl(trades, endTime);
187
+ const orderLog = [[startTime, INITIAL_BACKTEST_AMOUNT]];
188
+ let runningAmount = INITIAL_BACKTEST_AMOUNT;
189
+ for (const trade of resolvedTrades) {
190
+ runningAmount = roundValue(runningAmount + trade.resolvedPnl);
191
+ orderLog.push([trade.resolvedTimestamp, runningAmount]);
192
+ }
193
+ if (orderLog[orderLog.length - 1]?.[0] !== endTime) {
194
+ orderLog.push([endTime, runningAmount]);
195
+ }
196
+ const pnls = resolvedTrades.map(({ resolvedPnl }) => resolvedPnl);
197
+ const wins = pnls.filter((pnl) => pnl > 0).length;
198
+ const losses = pnls.filter((pnl) => pnl < 0).length;
199
+ const averageWin = wins ? pnls.filter((pnl) => pnl > 0).reduce((sum, pnl) => sum + pnl, 0) / wins : 0;
200
+ const averageLoss = losses ? Math.abs(
201
+ pnls.filter((pnl) => pnl < 0).reduce((sum, pnl) => sum + pnl, 0) / losses
202
+ ) : 0;
203
+ let amountBeforeTrade = INITIAL_BACKTEST_AMOUNT;
204
+ const returnSeries = resolvedTrades.map((trade) => {
205
+ const value = amountBeforeTrade > 0 ? trade.resolvedPnl / amountBeforeTrade : 0;
206
+ amountBeforeTrade += trade.resolvedPnl;
207
+ return value;
208
+ });
209
+ const periodDays = Math.max(0, (endTime - startTime) / MS_IN_DAY);
210
+ const periodMonths = periodDays / AVG_DAYS_IN_MONTH;
211
+ const amount = orderLog[orderLog.length - 1]?.[1] ?? INITIAL_BACKTEST_AMOUNT;
212
+ const netProfit = amount - INITIAL_BACKTEST_AMOUNT;
213
+ const totalReturn = netProfit / INITIAL_BACKTEST_AMOUNT * 100;
214
+ const cagr = periodMonths > 0 ? (Math.pow(amount / INITIAL_BACKTEST_AMOUNT, 12 / periodMonths) - 1) * 100 : 0;
215
+ const amounts = orderLog.map(([, value]) => value);
216
+ const maxDrawdown = calculateMaxDrawdown(amounts);
217
+ const streaks = calculateStreaks(pnls);
218
+ const stat = trades.length ? {
219
+ periodDays: roundValue(periodDays),
220
+ periodMonths: roundValue(periodMonths),
221
+ orders: trades.length,
222
+ wins,
223
+ losses,
224
+ ordersPerMonth: periodMonths ? roundValue(trades.length / periodMonths) : 0,
225
+ exposure: calculateExposurePercent(trades, startTime, endTime),
226
+ amount: roundValue(amount),
227
+ maxAmount: roundValue(Math.max(...amounts)),
228
+ minAmount: roundValue(Math.min(...amounts)),
229
+ netProfit: roundValue(netProfit),
230
+ totalReturn: roundValue(totalReturn),
231
+ cagr: roundValue(cagr),
232
+ maxDrawdown,
233
+ calmar: maxDrawdown > 0 ? roundValue(cagr / maxDrawdown) : null,
234
+ winRate: roundValue(wins / trades.length * 100),
235
+ riskRewardRatio: averageLoss > 0 ? roundValue(averageWin / averageLoss) : null,
236
+ expectancy: returnSeries.length ? roundValue(
237
+ returnSeries.reduce((sum, value) => sum + value, 0) / returnSeries.length * 100
238
+ ) : 0,
239
+ ...streaks,
240
+ sharpeRatio: calculateSharpeRatio(orderLog, startTime, endTime),
241
+ score: 0
242
+ } : createEmptyStat(startTime, endTime);
243
+ const activeTrades = trades.filter(({ status }) => status === "active");
244
+ const closedTrades = trades.filter(({ status }) => status === "closed");
245
+ const sumPnl = (rows, primary) => roundValue(
246
+ rows.reduce((sum, trade) => {
247
+ const value = trade[primary] ?? trade.currentPnl;
248
+ return sum + (typeof value === "number" && Number.isFinite(value) ? value : 0);
249
+ }, 0)
250
+ );
251
+ const activePnl = sumPnl(activeTrades, "currentPnl");
252
+ const closedPnl = sumPnl(closedTrades, "closedPnl");
253
+ const summary = {
254
+ totalTrades: trades.length,
255
+ activeTrades: activeTrades.length,
256
+ closedTrades: closedTrades.length,
257
+ wins,
258
+ losses,
259
+ activePnl,
260
+ closedPnl,
261
+ totalPnl: roundValue(activePnl + closedPnl),
262
+ symbolConcentrationTop1: calculateSymbolConcentration(resolvedTrades, 1),
263
+ symbolConcentrationTop5: calculateSymbolConcentration(resolvedTrades, 5)
264
+ };
265
+ return { orderLog, stat, summary };
266
+ };
267
+ var getLevelPercent = (trade, levelPrice, kind) => {
268
+ if (typeof levelPrice !== "number" || !Number.isFinite(levelPrice) || !Number.isFinite(trade.entryPrice) || trade.entryPrice <= 0) {
269
+ return null;
270
+ }
271
+ const raw = trade.direction === "LONG" ? (levelPrice - trade.entryPrice) / trade.entryPrice * 100 : (trade.entryPrice - levelPrice) / trade.entryPrice * 100;
272
+ return roundValue(kind === "stopLoss" ? Math.abs(raw) : raw);
273
+ };
274
+ var getSlippagePercent = (expectedPrice, actualPrice) => typeof expectedPrice === "number" && Number.isFinite(expectedPrice) && expectedPrice > 0 && typeof actualPrice === "number" && Number.isFinite(actualPrice) ? roundValue((actualPrice - expectedPrice) / expectedPrice * 100, 4) : null;
275
+ var getTotalFee = (trade) => {
276
+ const explicit = toFiniteNumberOrNull(trade.totalFee);
277
+ if (explicit != null) return explicit;
278
+ const fees = [trade.openFee, trade.closeFee, trade.fundingFee].map(toFiniteNumberOrNull).filter((value) => value != null);
279
+ return fees.length ? Number(fees.reduce((sum, value) => sum + value, 0).toFixed(12)) : null;
280
+ };
281
+ var toRuntimeTradeView = (trade, endTime = Date.now()) => {
282
+ const resolvedTimestamp = getTradeResolvedTimestamp(trade, endTime);
283
+ const durationHours = Number.isFinite(resolvedTimestamp) && resolvedTimestamp >= trade.entryTimestamp ? roundValue((resolvedTimestamp - trade.entryTimestamp) / 36e5) : null;
284
+ const expectedExitPrice = trade.exitType === "tp" ? trade.aiAnalysis?.takeProfitPrice : trade.exitType === "sl" ? trade.aiAnalysis?.stopLossPrice : null;
285
+ return {
286
+ orderId: trade.orderId,
287
+ symbol: trade.symbol,
288
+ direction: trade.direction,
289
+ status: trade.status,
290
+ qty: trade.qty,
291
+ entryTimestamp: trade.entryTimestamp,
292
+ entryPrice: trade.entryPrice,
293
+ actualEntryPrice: toFiniteNumberOrNull(trade.actualEntryPrice),
294
+ exitTimestamp: toFiniteNumberOrNull(trade.exitTimestamp),
295
+ exitPrice: toFiniteNumberOrNull(trade.exitPrice),
296
+ actualExitPrice: toFiniteNumberOrNull(trade.actualExitPrice),
297
+ currentPrice: toFiniteNumberOrNull(trade.currentPrice),
298
+ pnl: getTradePnl(trade),
299
+ durationHours,
300
+ entrySlippagePercent: getSlippagePercent(
301
+ trade.entryPrice,
302
+ trade.actualEntryPrice
303
+ ),
304
+ exitSlippagePercent: getSlippagePercent(
305
+ expectedExitPrice,
306
+ trade.actualExitPrice ?? trade.exitPrice
307
+ ),
308
+ exitType: trade.exitType ?? null,
309
+ takeProfitPrice: toFiniteNumberOrNull(trade.aiAnalysis?.takeProfitPrice),
310
+ stopLossPrice: toFiniteNumberOrNull(trade.aiAnalysis?.stopLossPrice),
311
+ takeProfitPercent: getLevelPercent(
312
+ trade,
313
+ trade.aiAnalysis?.takeProfitPrice,
314
+ "takeProfit"
315
+ ),
316
+ stopLossPercent: getLevelPercent(
317
+ trade,
318
+ trade.aiAnalysis?.stopLossPrice,
319
+ "stopLoss"
320
+ ),
321
+ openFee: toFiniteNumberOrNull(trade.openFee),
322
+ closeFee: toFiniteNumberOrNull(trade.closeFee),
323
+ fundingFee: toFiniteNumberOrNull(trade.fundingFee),
324
+ totalFee: getTotalFee(trade),
325
+ lastSyncedAt: toFiniteNumberOrNull(trade.lastSyncedAt)
326
+ };
327
+ };
328
+
329
+ // src/utils/runtimeTradeLineage.ts
330
+ var buildRuntimeStrategyIdentityKey = ({
331
+ strategyName,
332
+ configId,
333
+ universe,
334
+ accountId,
335
+ deploymentId,
336
+ policyProfileId
337
+ }) => [
338
+ strategyName,
339
+ configId ?? "config",
340
+ universe ?? "crypto",
341
+ accountId ?? "default",
342
+ deploymentId ?? "default",
343
+ policyProfileId ?? "default"
344
+ ].join(":");
345
+ var assignLegacyRuntimeTradeAccountScopes = (trades, scopes) => trades.map((trade) => {
346
+ if (trade.accountId || trade.deploymentId) return trade;
347
+ const matchingAccountIds = new Set(
348
+ scopes.filter(
349
+ (scope) => scope.strategyName === trade.strategy && scope.configId === (trade.runtimeConfigId ?? "config") && scope.universe === (trade.universe ?? "crypto")
350
+ ).map((scope) => scope.accountId).filter((accountId) => Boolean(accountId))
351
+ );
352
+ return matchingAccountIds.size === 1 ? { ...trade, accountId: [...matchingAccountIds][0] } : trade;
353
+ });
354
+ var getRuntimeStrategyAiGateObservedFrom = ({
355
+ scopes,
356
+ strategyName,
357
+ configId,
358
+ endTime
359
+ }) => {
360
+ const normalizedConfigId = configId ?? "config";
361
+ let observedFrom = null;
362
+ for (const scope of scopes) {
363
+ if (scope.strategy !== strategyName || (scope.runtimeConfigId ?? "config") !== normalizedConfigId || scope.firstTimestamp > endTime) {
364
+ continue;
365
+ }
366
+ observedFrom = observedFrom == null ? scope.firstTimestamp : Math.min(observedFrom, scope.firstTimestamp);
367
+ }
368
+ return observedFrom;
369
+ };
370
+ var buildRuntimeStrategyMaxLossValueTimeline = ({
371
+ scopes,
372
+ strategyName,
373
+ configId,
374
+ startTime,
375
+ endTime
376
+ }) => {
377
+ const normalizedConfigId = configId ?? "config";
378
+ const observationsByTimestamp = /* @__PURE__ */ new Map();
379
+ for (const scope of scopes) {
380
+ const value = scope.lineage.maxLossValue;
381
+ if (scope.strategy !== strategyName || (scope.runtimeConfigId ?? "config") !== normalizedConfigId || scope.firstTimestamp > endTime || typeof value !== "number" || !Number.isFinite(value)) {
382
+ continue;
383
+ }
384
+ const existing = observationsByTimestamp.get(scope.firstTimestamp);
385
+ if (!existing || scope.lastTimestamp > existing.lastTimestamp || scope.lastTimestamp === existing.lastTimestamp && value > existing.value) {
386
+ observationsByTimestamp.set(scope.firstTimestamp, {
387
+ value,
388
+ lastTimestamp: scope.lastTimestamp
389
+ });
390
+ }
391
+ }
392
+ const changes = [];
393
+ let observedFrom = null;
394
+ let initialValue = null;
395
+ let currentValue = null;
396
+ for (const [timestamp, observation] of [
397
+ ...observationsByTimestamp.entries()
398
+ ].sort(([left], [right]) => left - right)) {
399
+ if (currentValue == null) {
400
+ observedFrom = timestamp;
401
+ initialValue = observation.value;
402
+ currentValue = observation.value;
403
+ continue;
404
+ }
405
+ if (observation.value === currentValue) continue;
406
+ if (timestamp >= startTime) {
407
+ changes.push({
408
+ timestamp,
409
+ previousValue: currentValue,
410
+ value: observation.value
411
+ });
412
+ }
413
+ currentValue = observation.value;
414
+ }
415
+ return { observedFrom, initialValue, changes };
416
+ };
417
+ var isRuntimeStrategyLineageScope = (value) => {
418
+ if (!value || typeof value !== "object") return false;
419
+ const record = value;
420
+ const lineage = record.lineage;
421
+ return typeof record.strategy === "string" && typeof record.symbol === "string" && typeof record.firstTimestamp === "number" && Number.isFinite(record.firstTimestamp) && typeof record.lastTimestamp === "number" && Number.isFinite(record.lastTimestamp) && lineage != null && typeof lineage.gateFingerprint === "string" && lineage.gateFingerprint.trim().length > 0;
422
+ };
423
+ var buildRuntimeStrategyAiGateChanges = ({
424
+ scopes,
425
+ strategyName,
426
+ configId,
427
+ startTime,
428
+ endTime
429
+ }) => {
430
+ const normalizedConfigId = configId ?? "config";
431
+ const observationsByTimestamp = /* @__PURE__ */ new Map();
432
+ for (const scope of scopes) {
433
+ if (scope.strategy !== strategyName || (scope.runtimeConfigId ?? "config") !== normalizedConfigId || scope.firstTimestamp > endTime) {
434
+ continue;
435
+ }
436
+ const fingerprint = scope.lineage.gateFingerprint.trim();
437
+ const existing = observationsByTimestamp.get(scope.firstTimestamp);
438
+ if (!existing || scope.lastTimestamp > existing.lastTimestamp || scope.lastTimestamp === existing.lastTimestamp && fingerprint > existing.fingerprint) {
439
+ observationsByTimestamp.set(scope.firstTimestamp, {
440
+ fingerprint,
441
+ lastTimestamp: scope.lastTimestamp
442
+ });
443
+ }
444
+ }
445
+ const changes = [];
446
+ let currentFingerprint = null;
447
+ for (const [timestamp, observation] of [
448
+ ...observationsByTimestamp.entries()
449
+ ].sort(([left], [right]) => left - right)) {
450
+ if (currentFingerprint == null) {
451
+ currentFingerprint = observation.fingerprint;
452
+ continue;
453
+ }
454
+ if (observation.fingerprint === currentFingerprint) continue;
455
+ if (timestamp >= startTime) {
456
+ changes.push({
457
+ timestamp,
458
+ previousFingerprint: currentFingerprint,
459
+ fingerprint: observation.fingerprint
460
+ });
461
+ }
462
+ currentFingerprint = observation.fingerprint;
463
+ }
464
+ return changes;
465
+ };
466
+ export {
467
+ assignLegacyRuntimeTradeAccountScopes,
468
+ buildRuntimeStrategyAiGateChanges,
469
+ buildRuntimeStrategyAnalytics,
470
+ buildRuntimeStrategyIdentityKey,
471
+ buildRuntimeStrategyMaxLossValueTimeline,
472
+ getRuntimeStrategyAiGateObservedFrom,
473
+ isRuntimeStrategyLineageScope,
474
+ isRuntimeTradeRecord,
475
+ resolveStrategyNameByOrderLinkId,
476
+ selectTradesForWindow,
477
+ toRuntimeTradeView
478
+ };
@@ -4,12 +4,12 @@ import {
4
4
  import {
5
5
  createIndicators,
6
6
  getRequiredControllerSeedWindow
7
- } from "./chunk-QRRQA4TU.mjs";
7
+ } from "./chunk-OLHUGX7X.mjs";
8
8
  import "./chunk-M7QGVZ3J.mjs";
9
- import "./chunk-AYC2QVKI.mjs";
10
9
  import {
11
10
  getTimestamp
12
11
  } from "./chunk-S4KHOAXM.mjs";
12
+ import "./chunk-AYC2QVKI.mjs";
13
13
  import {
14
14
  FEE_PERCENT
15
15
  } from "./chunk-MKCQSB4H.mjs";