aiden-shared-calculations-unified 1.0.104 → 1.0.106

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.
@@ -28,7 +28,7 @@ class InsightsTotalPositionsHeld {
28
28
  if (insights.length === 0) return;
29
29
 
30
30
  for (const insight of insights) {
31
- this.totalPositions += insightsHelper.getTotalOwners(insight);
31
+ this.totalPositions += insightsHelper.getLongCount(insight); // Corrected to use getLongCount
32
32
  }
33
33
  }
34
34
 
@@ -1,57 +1,65 @@
1
1
  class CrowdCostBasis {
2
- constructor() {
3
- this.results = {};
4
- }
2
+ constructor() { this.results = {}; }
5
3
 
6
4
  static getMetadata() {
7
5
  return {
8
6
  name: 'crowd-cost-basis',
9
7
  type: 'meta',
10
8
  category: 'History Reconstruction',
11
- userType: 'n/a', // <--- Works on dependency results
9
+ userType: 'n/a',
12
10
  isHistorical: false,
13
11
  rootDataDependencies: ['price']
14
12
  };
15
13
  }
16
14
 
17
- static getDependencies() {
18
- return ['user-history-reconstructor'];
15
+ static getDependencies() { return ['user-history-reconstructor']; }
16
+
17
+ static getSchema() {
18
+ const schema = {
19
+ "type": "object",
20
+ "properties": {
21
+ "avgEntry": { "type": "number", "description": "Global average entry price for all holders." },
22
+ "holderCount": { "type": "number", "description": "Total number of users holding the asset." },
23
+ "profitabilityPct": { "type": "number", "description": "Percentage distance between current price and avg entry." },
24
+ "state": { "type": "string", "description": "Profitability state: PROFIT_SUPPORT or LOSS_RESISTANCE." }
25
+ },
26
+ "required": ["avgEntry", "holderCount", "profitabilityPct", "state"]
27
+ };
28
+ // The result is a Map of Ticker -> Schema
29
+ return { "type": "object", "patternProperties": { "^.*$": schema } };
19
30
  }
20
31
 
21
32
  async process(context) {
22
33
  const { computed, prices, math } = context;
23
-
24
- // 1. Access the output of the Standard calculation
25
34
  const userReconstructions = computed['user-history-reconstructor'];
26
35
  if (!userReconstructions) return;
27
36
 
28
- const aggregator = {}; // { AAPL: { sumEntry: 0, count: 0 } }
37
+ const aggregator = {};
29
38
 
30
- // 2. Iterate over all users' reconstructed states
39
+ // 1. Iterate Users
31
40
  for (const userId in userReconstructions) {
32
41
  const userPortfolio = userReconstructions[userId];
33
-
42
+
34
43
  for (const ticker in userPortfolio) {
35
- const position = userPortfolio[ticker];
36
-
37
- if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
38
-
39
- // Aggregating global average entry
40
- aggregator[ticker].sumEntry += position.avgEntry;
41
- aggregator[ticker].count++;
44
+ const stats = userPortfolio[ticker];
45
+
46
+ if (stats.isHolder === 1) {
47
+ if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
48
+ aggregator[ticker].sumEntry += stats.avgEntry;
49
+ aggregator[ticker].count++;
50
+ }
42
51
  }
43
52
  }
44
53
 
45
- // 3. Compute Global Average vs Current Price
54
+ // 2. Compute Global Cost Basis
46
55
  for (const ticker in aggregator) {
47
56
  const data = aggregator[ticker];
48
- if (data.count < 10) continue; // Noise filter
57
+ if (data.count < 5) continue;
49
58
 
50
59
  const globalAvgEntry = data.sumEntry / data.count;
51
-
52
- // Get today's closing price
60
+
61
+ // Get Price for Context Date
53
62
  const priceHistory = math.priceExtractor.getHistory(prices, ticker);
54
- // The last item in price history for this context is "Today"
55
63
  const lastPriceObj = priceHistory[priceHistory.length - 1];
56
64
  const currentPrice = lastPriceObj ? lastPriceObj.price : globalAvgEntry;
57
65
 
@@ -7,42 +7,56 @@ class LeverageDivergence {
7
7
  type: 'meta',
8
8
  category: 'History Reconstruction',
9
9
  userType: 'n/a',
10
- isHistorical: true, // <--- Needs yesterday's result
10
+ isHistorical: true,
11
11
  rootDataDependencies: []
12
12
  };
13
13
  }
14
14
 
15
- static getDependencies() {
16
- return ['user-history-reconstructor'];
15
+ static getDependencies() { return ['user-history-reconstructor']; }
16
+
17
+ static getSchema() {
18
+ const schema = {
19
+ "type": "object",
20
+ "properties": {
21
+ "levHolders": { "type": "number", "description": "Count of holders with leverage > 1.1x." },
22
+ "spotHolders": { "type": "number", "description": "Count of holders with leverage <= 1.1x." },
23
+ "levDelta": { "type": "number", "description": "Change in leveraged holders vs yesterday." },
24
+ "spotDelta": { "type": "number", "description": "Change in spot holders vs yesterday." },
25
+ "signal": { "type": "string", "description": "Divergence signal: NEUTRAL, SMART_ACCUMULATION, SPECULATIVE_PUMP, SMART_EXIT." }
26
+ },
27
+ "required": ["levHolders", "spotHolders", "levDelta", "spotDelta", "signal"]
28
+ };
29
+ return { "type": "object", "patternProperties": { "^.*$": schema } };
17
30
  }
18
31
 
19
32
  async process(context) {
20
33
  const { computed, previousComputed } = context;
21
-
34
+
22
35
  const currentReconstruction = computed['user-history-reconstructor'];
23
- // Access SELF from yesterday
24
- const previousResult = previousComputed['leverage-divergence'];
36
+ const previousResult = previousComputed['leverage-divergence'];
25
37
 
26
- const currentAgg = {}; // { AAPL: { levHolders: 0, spotHolders: 0 } }
38
+ const currentAgg = {};
27
39
 
28
40
  // 1. Build Today's Aggregates
29
41
  for (const userId in currentReconstruction) {
30
42
  const userPortfolio = currentReconstruction[userId];
43
+
31
44
  for (const ticker in userPortfolio) {
32
- const pos = userPortfolio[ticker];
33
-
34
- if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
35
-
36
- // If avg leverage > 1.1, count as "Leveraged Holder"
37
- if (pos.avgLeverage > 1.1) {
38
- currentAgg[ticker].levHolders++;
39
- } else {
40
- currentAgg[ticker].spotHolders++;
45
+ const stats = userPortfolio[ticker];
46
+
47
+ if (stats.isHolder === 1) {
48
+ if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
49
+
50
+ if (stats.avgLeverage > 1.1) {
51
+ currentAgg[ticker].levHolders++;
52
+ } else {
53
+ currentAgg[ticker].spotHolders++;
54
+ }
41
55
  }
42
56
  }
43
57
  }
44
58
 
45
- // 2. Compare with Yesterday
59
+ // 2. Compare with Previous Day
46
60
  for (const ticker in currentAgg) {
47
61
  const curr = currentAgg[ticker];
48
62
  const prev = previousResult ? previousResult[ticker] : null;
@@ -56,12 +70,10 @@ class LeverageDivergence {
56
70
  const spotDelta = curr.spotHolders - prev.spotHolders;
57
71
 
58
72
  let signal = 'NEUTRAL';
59
-
60
- // Retail (Spot) Buying + Speculators (Lev) Selling = Smart Money Exit
61
- if (spotDelta > 0 && levDelta < 0) signal = 'SMART_EXIT';
62
-
63
- // Retail (Spot) Selling + Speculators (Lev) Buying = High Conviction Pump
64
- if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
73
+ if (spotDelta > 0 && levDelta < 0) signal = 'SMART_ACCUMULATION';
74
+ else if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
75
+ else if (spotDelta > 0 && levDelta > 0) signal = 'BROAD_ACCUMULATION';
76
+ else if (spotDelta < 0 && levDelta < 0) signal = 'BROAD_EXIT';
65
77
 
66
78
  this.results[ticker] = {
67
79
  ...curr,
@@ -8,12 +8,24 @@ class LiquidationCascade {
8
8
  category: 'History Reconstruction',
9
9
  userType: 'n/a',
10
10
  isHistorical: false,
11
- rootDataDependencies: []
11
+ rootDataDependencies: []
12
12
  };
13
13
  }
14
14
 
15
- static getDependencies() {
16
- return ['user-history-reconstructor'];
15
+ static getDependencies() { return ['user-history-reconstructor']; }
16
+
17
+ static getSchema() {
18
+ const schema = {
19
+ "type": "object",
20
+ "properties": {
21
+ "totalClosures": { "type": "number", "description": "Total number of positions closed today." },
22
+ "forcedClosures": { "type": "number", "description": "Number of positions closed via Stop Loss (Reason 1)." },
23
+ "painIndex": { "type": "number", "description": "Ratio of forced closures to total closures (0.0 - 1.0)." },
24
+ "isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3." }
25
+ },
26
+ "required": ["totalClosures", "forcedClosures", "painIndex", "isFlushEvent"]
27
+ };
28
+ return { "type": "object", "patternProperties": { "^.*$": schema } };
17
29
  }
18
30
 
19
31
  async process(context) {
@@ -21,36 +33,37 @@ class LiquidationCascade {
21
33
  const userReconstructions = computed['user-history-reconstructor'];
22
34
  if (!userReconstructions) return;
23
35
 
24
- const aggregator = {};
36
+ const aggregator = {};
25
37
 
26
- // 1. Aggregate Forced Exits
27
38
  for (const userId in userReconstructions) {
28
39
  const userPortfolio = userReconstructions[userId];
29
-
40
+
30
41
  for (const ticker in userPortfolio) {
31
- const position = userPortfolio[ticker];
32
-
33
- if (position.closedToday > 0) {
34
- if (!aggregator[ticker]) aggregator[ticker] = { closed: 0, forced: 0 };
42
+ const stats = userPortfolio[ticker];
43
+
44
+ if (stats.didSell > 0) {
45
+ if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0 };
46
+
47
+ aggregator[ticker].totalClosed += stats.didSell;
35
48
 
36
- aggregator[ticker].closed += position.closedToday;
37
- aggregator[ticker].forced += position.forcedExits;
49
+ if (stats.closeReasons && stats.closeReasons["1"]) {
50
+ aggregator[ticker].forced += stats.closeReasons["1"];
51
+ }
38
52
  }
39
53
  }
40
54
  }
41
55
 
42
- // 2. Calculate Pain Index
43
56
  for (const ticker in aggregator) {
44
57
  const data = aggregator[ticker];
45
- if (data.closed < 5) continue;
58
+ if (data.totalClosed < 5) continue;
46
59
 
47
- const forcedRatio = data.forced / data.closed;
60
+ const forcedRatio = data.forced / data.totalClosed;
48
61
 
49
62
  this.results[ticker] = {
50
- totalClosures: data.closed,
63
+ totalClosures: data.totalClosed,
51
64
  forcedClosures: data.forced,
52
- painIndex: forcedRatio, // 0.0 to 1.0
53
- isFlushEvent: forcedRatio > 0.3 // Flag if >30% of selling was forced
65
+ painIndex: forcedRatio,
66
+ isFlushEvent: forcedRatio > 0.3
54
67
  };
55
68
  }
56
69
  }
@@ -14,17 +14,52 @@ class OwnershipVsPerformanceYTD {
14
14
 
15
15
  static getDependencies() { return []; }
16
16
 
17
+ static getSchema() {
18
+ const metricSchema = {
19
+ "type": "object",
20
+ "properties": {
21
+ "priceYtd": { "type": "number" },
22
+ "ownersYtd": { "type": "number" },
23
+ "currentPrice": { "type": "number" },
24
+ "currentOwners": { "type": "number" }
25
+ },
26
+ "required": ["priceYtd", "ownersYtd", "currentPrice", "currentOwners"]
27
+ };
28
+
29
+ const baselineSchema = {
30
+ "type": "object",
31
+ "patternProperties": {
32
+ "^.*$": {
33
+ "type": "object",
34
+ "properties": {
35
+ "startPrice": { "type": "number" },
36
+ "startOwners": { "type": "number" },
37
+ "date": { "type": "string" }
38
+ }
39
+ }
40
+ }
41
+ };
42
+
43
+ return {
44
+ "type": "object",
45
+ "properties": {
46
+ "baselines": baselineSchema
47
+ },
48
+ "additionalProperties": metricSchema
49
+ };
50
+ }
51
+
17
52
  async process(context) {
18
53
  const { insights: insightsHelper, priceExtractor } = context.math;
19
54
  const { previousComputed, mappings, prices, date } = context;
20
-
55
+
21
56
  const dailyInsights = insightsHelper.getInsights(context, 'today');
22
-
57
+
23
58
  // 1. Manage Baseline State (Jan 1st snapshot)
24
59
  // If today is Jan 1st (or first run), we reset. Otherwise, we load from yesterday.
25
60
  let baselineState = previousComputed['ownership-vs-performance-ytd']?.baselines || {};
26
61
  const isStartOfYear = date.today.endsWith('-01-01') || date.today.endsWith('-01-02');
27
-
62
+
28
63
  if (isStartOfYear) {
29
64
  baselineState = {}; // Reset for new year
30
65
  }
@@ -37,11 +72,11 @@ class OwnershipVsPerformanceYTD {
37
72
  if (!ticker) continue;
38
73
 
39
74
  const currentOwners = insightsHelper.getTotalOwners(insight);
40
-
75
+
41
76
  // Get Price History
42
77
  const priceHist = priceExtractor.getHistory(prices, ticker);
43
78
  if (!priceHist.length) continue;
44
-
79
+
45
80
  const currentPrice = priceHist[priceHist.length - 1].price;
46
81
 
47
82
  // 2. Set Baseline if missing
@@ -56,12 +91,12 @@ class OwnershipVsPerformanceYTD {
56
91
  const base = baselineState[ticker];
57
92
 
58
93
  // 3. Calculate Deltas
59
- const priceYtd = base.startPrice > 0
60
- ? ((currentPrice - base.startPrice) / base.startPrice) * 100
94
+ const priceYtd = base.startPrice > 0
95
+ ? ((currentPrice - base.startPrice) / base.startPrice) * 100
61
96
  : 0;
62
-
63
- const ownersYtd = base.startOwners > 0
64
- ? ((currentOwners - base.startOwners) / base.startOwners) * 100
97
+
98
+ const ownersYtd = base.startOwners > 0
99
+ ? ((currentOwners - base.startOwners) / base.startOwners) * 100
65
100
  : 0;
66
101
 
67
102
  currentMetrics[ticker] = {
@@ -75,15 +110,15 @@ class OwnershipVsPerformanceYTD {
75
110
  // 4. Output structure includes the baselines so they are saved for tomorrow
76
111
  this.results = {
77
112
  metrics: currentMetrics,
78
- baselines: baselineState
113
+ baselines: baselineState
79
114
  };
80
115
  }
81
116
 
82
- async getResult() {
117
+ async getResult() {
83
118
  // We flatten the 'metrics' for easy API consumption,
84
119
  // but we must ensure 'baselines' is preserved in the stored document
85
120
  // so 'previousComputed' picks it up tomorrow.
86
- return { ...this.results.metrics, baselines: this.results.baselines };
121
+ return { ...this.results.metrics, baselines: this.results.baselines };
87
122
  }
88
123
  }
89
124
 
@@ -14,12 +14,29 @@ class OwnershipVsVolatility {
14
14
 
15
15
  static getDependencies() { return []; }
16
16
 
17
+ static getSchema() {
18
+ const schema = {
19
+ "type": "object",
20
+ "properties": {
21
+ "volatilityScore": { "type": "number" },
22
+ "ownerChange7d": { "type": "number" },
23
+ "laggedOwners": { "type": "number" },
24
+ "_history": {
25
+ "type": "array",
26
+ "items": { "type": "number" }
27
+ }
28
+ },
29
+ "required": ["volatilityScore", "ownerChange7d", "laggedOwners"]
30
+ };
31
+ return { "type": "object", "patternProperties": { "^.*$": schema } };
32
+ }
33
+
17
34
  async process(context) {
18
35
  const { insights: insightsHelper, priceExtractor, compute } = context.math;
19
36
  const { previousComputed, mappings, prices } = context;
20
-
37
+
21
38
  const dailyInsights = insightsHelper.getInsights(context, 'today');
22
-
39
+
23
40
  // Use 7-day lookback for ownership change
24
41
  const prevState = previousComputed['ownership-vs-volatility'] || {};
25
42
 
@@ -35,17 +52,17 @@ class OwnershipVsVolatility {
35
52
  const recentPrices = priceHist.slice(-14);
36
53
  const returns = [];
37
54
  for (let i = 1; i < recentPrices.length; i++) {
38
- returns.push((recentPrices[i].price - recentPrices[i-1].price) / recentPrices[i-1].price);
55
+ returns.push((recentPrices[i].price - recentPrices[i - 1].price) / recentPrices[i - 1].price);
39
56
  }
40
-
57
+
41
58
  const volatility = compute.standardDeviation(returns); // Raw std dev
42
59
 
43
60
  // 2. Calculate Ownership Change (Current vs 7 days ago stored state)
44
61
  const currentOwners = insightsHelper.getTotalOwners(insight);
45
62
  const prevOwners = prevState[ticker]?.laggedOwners || currentOwners;
46
-
47
- const ownerChangePct = prevOwners > 0
48
- ? ((currentOwners - prevOwners) / prevOwners) * 100
63
+
64
+ const ownerChangePct = prevOwners > 0
65
+ ? ((currentOwners - prevOwners) / prevOwners) * 100
49
66
  : 0;
50
67
 
51
68
  this.results[ticker] = {
@@ -56,7 +73,7 @@ class OwnershipVsVolatility {
56
73
  // Simplified: We store today's value, and the API/UI compares.
57
74
  // Better: Use a simple rolling queue like the Momentum calc.
58
75
  _history: [...(prevState[ticker]?._history || []), currentOwners].slice(-7),
59
-
76
+
60
77
  // For the output: Calculate change vs the oldest in history (approx 7d ago)
61
78
  laggedOwners: (prevState[ticker]?._history?.[0] || currentOwners)
62
79
  };
@@ -1,18 +1,14 @@
1
- /**
2
- * @fileoverview Reconstructs a user's portfolio state for a specific date from their trade history.
3
- * Acts as the "Map" phase for downstream Meta aggregations.
4
- */
5
1
  class UserHistoryReconstructor {
6
2
  constructor() {
7
- this.results = {}; // Output: { "user_id": { "AAPL": { ...stats... } } }
3
+ this.results = {};
8
4
  }
9
5
 
10
6
  static getMetadata() {
11
7
  return {
12
8
  name: 'user-history-reconstructor',
13
9
  type: 'standard',
14
- category: 'History Reconstruction', // <--- Specific Category
15
- userType: 'all', // <--- Processes User Data
10
+ category: 'History Reconstruction',
11
+ userType: 'all',
16
12
  isHistorical: false,
17
13
  rootDataDependencies: ['history']
18
14
  };
@@ -21,15 +17,29 @@ class UserHistoryReconstructor {
21
17
  static getDependencies() { return []; }
22
18
 
23
19
  static getSchema() {
24
- return {
25
- "USER_ID": {
26
- "TICKER": {
27
- "activeCount": 1,
28
- "avgEntry": 0.0,
29
- "avgLeverage": 1.0,
30
- "closedToday": 0,
31
- "forcedExits": 0
20
+ // Schema for a single Ticker's stats
21
+ const tickerSchema = {
22
+ "type": "object",
23
+ "properties": {
24
+ "isHolder": { "type": "number", "description": "1 if the user holds the asset at EOD, 0 otherwise." },
25
+ "didBuy": { "type": "number", "description": "Count of buy trades executed today." },
26
+ "didSell": { "type": "number", "description": "Count of sell trades executed today." },
27
+ "avgEntry": { "type": "number", "description": "Average entry price of held positions." },
28
+ "avgLeverage": { "type": "number", "description": "Average leverage of held positions." },
29
+ "buyLeverage": { "type": "number", "description": "Average leverage of new buys executed today." },
30
+ "closeReasons": {
31
+ "type": "object",
32
+ "description": "Map of close reason codes to counts (0=Manual, 1=Stop/Liq, 5=TP)."
32
33
  }
34
+ },
35
+ "required": ["isHolder", "didBuy", "didSell", "avgEntry", "avgLeverage"]
36
+ };
37
+
38
+ // The result for a user is a Map of Ticker -> TickerSchema
39
+ return {
40
+ "type": "object",
41
+ "patternProperties": {
42
+ "^.*$": tickerSchema
33
43
  }
34
44
  };
35
45
  }
@@ -37,72 +47,107 @@ class UserHistoryReconstructor {
37
47
  async process(context) {
38
48
  const { user, math, mappings, date } = context;
39
49
 
40
- // 1. Get History
50
+ // 1. Get History (V2 Format Only)
41
51
  const history = math.history.getDailyHistory(user);
42
52
  const allTrades = history?.PublicHistoryPositions || [];
43
53
 
44
54
  if (allTrades.length === 0) return;
45
55
 
46
- // 2. Filter for Active Trades on this Date
47
- const activeTrades = math.history.getActiveTradesForDate(allTrades, date.today);
48
- if (activeTrades.length === 0) return;
56
+ // 2. Define Time Boundaries for "Yesterday" (Target Date)
57
+ const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
58
+ const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
49
59
 
50
- // 3. Initialize User State
51
60
  this.results[user.id] = {};
52
61
  const userState = this.results[user.id];
53
62
 
54
- const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
55
- const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
56
-
57
- for (const trade of activeTrades) {
63
+ for (const trade of allTrades) {
58
64
  const instId = trade.InstrumentID;
59
65
  const ticker = mappings.instrumentToTicker[instId];
60
66
  if (!ticker) continue;
61
67
 
68
+ // Parse Dates
69
+ const openTime = new Date(trade.OpenDateTime).getTime();
70
+ const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
71
+
72
+ // 3. Determine States
73
+ const isOpenBeforeEOD = openTime <= dayEnd;
74
+ // Active if it wasn't closed before the day started
75
+ const isClosedAfterSOD = closeTime === null || closeTime >= dayStart;
76
+
77
+ const wasActiveToday = isOpenBeforeEOD && isClosedAfterSOD;
78
+
79
+ // "Held" means active at End of Day
80
+ const isHeldAtEOD = isOpenBeforeEOD && (closeTime === null || closeTime > dayEnd);
81
+
82
+ // "Opened" means OpenTime is inside today
83
+ const isOpenedToday = openTime >= dayStart && openTime <= dayEnd;
84
+
85
+ // "Closed" means CloseTime is inside today
86
+ const isClosedToday = closeTime !== null && closeTime >= dayStart && closeTime <= dayEnd;
87
+
88
+ if (!wasActiveToday) continue;
89
+
62
90
  if (!userState[ticker]) {
63
- userState[ticker] = {
64
- activeCount: 0,
65
- totalEntry: 0,
66
- totalLev: 0,
67
- closedToday: 0,
68
- forcedExits: 0
91
+ userState[ticker] = {
92
+ isHolder: 0,
93
+ didBuy: 0,
94
+ didSell: 0,
95
+ sumEntry: 0,
96
+ sumLev: 0,
97
+ holdCount: 0,
98
+ sumBuyLev: 0,
99
+ closeReasons: { "0": 0, "1": 0, "5": 0 }
69
100
  };
70
101
  }
71
-
72
102
  const stats = userState[ticker];
73
-
74
- // Accumulate Holding State
75
- stats.activeCount++;
76
- stats.totalEntry += (trade.OpenRate || 0);
77
- stats.totalLev += (trade.Leverage || 1);
78
-
79
- // Check for Closure Events happening TODAY
80
- if (trade.CloseDateTime) {
81
- const closeTime = new Date(trade.CloseDateTime).getTime();
82
- if (closeTime >= dayStart && closeTime <= dayEnd) {
83
- stats.closedToday++;
84
- // CloseReason: 1 = Stop Loss, 5 = Take Profit, 0 = Manual
85
- if (trade.CloseReason === 1) stats.forcedExits++;
86
- }
103
+
104
+ // 4. Populate Metrics
105
+ if (isHeldAtEOD) {
106
+ stats.isHolder = 1;
107
+ stats.holdCount++;
108
+ stats.sumEntry += (trade.OpenRate || 0);
109
+ stats.sumLev += (trade.Leverage || 1);
110
+ }
111
+
112
+ if (isOpenedToday) {
113
+ stats.didBuy++;
114
+ stats.sumBuyLev += (trade.Leverage || 1);
115
+ }
116
+
117
+ if (isClosedToday) {
118
+ stats.didSell++;
119
+ const reason = String(trade.CloseReason || 0);
120
+ if (stats.closeReasons[reason] === undefined) stats.closeReasons[reason] = 0;
121
+ stats.closeReasons[reason]++;
87
122
  }
88
123
  }
89
124
 
90
- // 4. Finalize Averages for this User
125
+ // 5. Cleanup / Average
91
126
  for (const ticker in userState) {
92
127
  const stats = userState[ticker];
93
- if (stats.activeCount > 0) {
94
- stats.avgEntry = stats.totalEntry / stats.activeCount;
95
- stats.avgLeverage = stats.totalLev / stats.activeCount;
128
+
129
+ if (stats.holdCount > 0) {
130
+ stats.avgEntry = stats.sumEntry / stats.holdCount;
131
+ stats.avgLeverage = stats.sumLev / stats.holdCount;
132
+ } else {
133
+ stats.avgEntry = 0;
134
+ stats.avgLeverage = 0;
96
135
  }
97
- // Cleanup intermediate sums
98
- delete stats.totalEntry;
99
- delete stats.totalLev;
136
+
137
+ if (stats.didBuy > 0) {
138
+ stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
139
+ } else {
140
+ stats.buyLeverage = 0;
141
+ }
142
+
143
+ delete stats.sumEntry;
144
+ delete stats.sumLev;
145
+ delete stats.sumBuyLev;
146
+ delete stats.holdCount;
100
147
  }
101
148
  }
102
149
 
103
- async getResult() {
104
- return this.results;
105
- }
150
+ async getResult() { return this.results; }
106
151
  }
107
152
 
108
153
  module.exports = UserHistoryReconstructor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.104",
3
+ "version": "1.0.106",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [