aiden-shared-calculations-unified 1.0.108 → 1.0.110

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.
Files changed (24) hide show
  1. package/calculations/core/insights-daily-bought-vs-sold-count.js +20 -15
  2. package/calculations/core/insights-daily-ownership-delta.js +12 -10
  3. package/calculations/core/instrument-price-change-1d.js +16 -31
  4. package/calculations/core/instrument-price-momentum-20d.js +16 -26
  5. package/calculations/core/ownership-vs-performance-ytd.js +15 -52
  6. package/calculations/core/ownership-vs-volatility.js +27 -36
  7. package/calculations/core/platform-daily-bought-vs-sold-count.js +28 -26
  8. package/calculations/core/platform-daily-ownership-delta.js +28 -31
  9. package/calculations/core/price-metrics.js +15 -54
  10. package/calculations/core/short-interest-growth.js +6 -13
  11. package/calculations/core/trending-ownership-momentum.js +16 -28
  12. package/calculations/core/user-history-reconstructor.js +48 -49
  13. package/calculations/gauss/cohort-capital-flow.js +34 -71
  14. package/calculations/gauss/cohort-definer.js +61 -142
  15. package/calculations/gem/cohort-momentum-state.js +27 -77
  16. package/calculations/gem/skilled-cohort-flow.js +36 -114
  17. package/calculations/gem/unskilled-cohort-flow.js +36 -112
  18. package/calculations/ghost-book/retail-gamma-exposure.js +14 -61
  19. package/calculations/helix/herd-consensus-score.js +27 -90
  20. package/calculations/helix/winner-loser-flow.js +21 -90
  21. package/calculations/predicative-alpha/cognitive-dissonance.js +25 -91
  22. package/calculations/predicative-alpha/diamond-hand-fracture.js +17 -72
  23. package/calculations/predicative-alpha/mimetic-latency.js +21 -100
  24. package/package.json +1 -1
@@ -17,37 +17,42 @@ class InsightsDailyBoughtVsSoldCount {
17
17
  const tickerSchema = {
18
18
  "type": "object",
19
19
  "properties": {
20
- "positions_bought": { "type": "number" },
21
- "positions_sold": { "type": "number" },
22
- "net_change": { "type": "number" }
20
+ "positions_bought": { "type": ["number", "null"] },
21
+ "positions_sold": { "type": ["number", "null"] },
22
+ "net_change": { "type": ["number", "null"] },
23
+ "total_positions": { "type": "number" } // Baseline
23
24
  },
24
- "required": ["positions_bought", "positions_sold", "net_change"]
25
+ "required": ["positions_bought", "positions_sold", "net_change", "total_positions"]
25
26
  };
26
27
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
27
28
  }
28
29
 
29
30
  process(context) {
30
31
  const { insights: insightsHelper } = context.math;
31
- const insights = insightsHelper.getInsights(context); // FIXED: Direct call
32
+ const insights = insightsHelper.getInsights(context);
32
33
  const { mappings } = context;
33
34
  const tickerMap = mappings.instrumentToTicker || {};
34
35
 
35
- if (insights.length === 0) return;
36
+ if (!insights || insights.length === 0) return;
36
37
 
37
38
  for (const insight of insights) {
38
39
  const instId = insight.instrumentId;
39
40
  const ticker = tickerMap[instId] || `id_${instId}`;
41
+ const total = insightsHelper.getTotalOwners(insight);
42
+
43
+ // netChange requires yesterday's data to be accurate
40
44
  const netChange = insightsHelper.getNetOwnershipChange(insight);
41
- const bought = netChange > 0 ? netChange : 0;
42
- const sold = netChange < 0 ? Math.abs(netChange) : 0;
45
+
46
+ // If netChange is null (missing history), we still save total_positions
47
+ const bought = (netChange !== null && netChange > 0) ? netChange : 0;
48
+ const sold = (netChange !== null && netChange < 0) ? Math.abs(netChange) : 0;
43
49
 
44
- if (bought > 0 || sold > 0) {
45
- this.results[ticker] = {
46
- positions_bought: bought,
47
- positions_sold: sold,
48
- net_change: netChange
49
- };
50
- }
50
+ this.results[ticker] = {
51
+ positions_bought: bought,
52
+ positions_sold: sold,
53
+ net_change: netChange,
54
+ total_positions: total
55
+ };
51
56
  }
52
57
  }
53
58
 
@@ -17,36 +17,38 @@ class InsightsDailyOwnershipDelta {
17
17
  const tickerSchema = {
18
18
  "type": "object",
19
19
  "properties": {
20
- "owners_added": { "type": "number" },
21
- "owners_removed": { "type": "number" },
22
- "net_ownership_change": { "type": "number" }
20
+ "owners_added": { "type": ["number", "null"] },
21
+ "owners_removed": { "type": ["number", "null"] },
22
+ "net_ownership_change": { "type": ["number", "null"] },
23
+ "total_owners": { "type": "number" } // Baseline
23
24
  },
24
- "required": ["owners_added", "owners_removed", "net_ownership_change"]
25
+ "required": ["owners_added", "owners_removed", "net_ownership_change", "total_owners"]
25
26
  };
26
27
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
27
28
  }
28
29
 
29
30
  process(context) {
30
31
  const { insights: insightsHelper } = context.math;
31
- const insights = insightsHelper.getInsights(context); // FIXED: Direct call
32
+ const insights = insightsHelper.getInsights(context);
32
33
  const { mappings } = context;
33
34
  const tickerMap = mappings.instrumentToTicker || {};
34
35
 
35
- if (insights.length === 0) return;
36
+ if (!insights || insights.length === 0) return;
36
37
 
37
38
  for (const insight of insights) {
39
+ const total = insightsHelper.getTotalOwners(insight);
38
40
  const netChange = insightsHelper.getNetOwnershipChange(insight);
39
- if (netChange === 0) continue;
40
41
 
41
42
  const instId = insight.instrumentId;
42
43
  const ticker = tickerMap[instId] || `id_${instId}`;
43
- const added = netChange > 0 ? netChange : 0;
44
- const removed = netChange < 0 ? Math.abs(netChange) : 0;
44
+ const added = (netChange !== null && netChange > 0) ? netChange : 0;
45
+ const removed = (netChange !== null && netChange < 0) ? Math.abs(netChange) : 0;
45
46
 
46
47
  this.results[ticker] = {
47
48
  owners_added: added,
48
49
  owners_removed: removed,
49
- net_ownership_change: netChange
50
+ net_ownership_change: netChange,
51
+ total_owners: total
50
52
  };
51
53
  }
52
54
  }
@@ -1,11 +1,5 @@
1
- /**
2
- * @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
3
- * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
- */
5
1
  class InstrumentPriceChange1D {
6
- constructor() {
7
- this.result = {};
8
- }
2
+ constructor() { this.result = {}; }
9
3
 
10
4
  static getMetadata() {
11
5
  return {
@@ -23,9 +17,10 @@ class InstrumentPriceChange1D {
23
17
  const tickerSchema = {
24
18
  "type": "object",
25
19
  "properties": {
26
- "change_1d_pct": { "type": "number" }
20
+ "change_1d_pct": { "type": ["number", "null"] },
21
+ "closing_price": { "type": "number" } // Baseline
27
22
  },
28
- "required": ["change_1d_pct"]
23
+ "required": ["change_1d_pct", "closing_price"]
29
24
  };
30
25
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
31
26
  }
@@ -35,48 +30,38 @@ class InstrumentPriceChange1D {
35
30
  const { instrumentToTicker } = mappings;
36
31
  const { priceExtractor } = math;
37
32
 
38
- if (!prices || !prices.history) {
39
- // Do NOT reset result here, just return if this shard is empty
40
- return;
41
- }
33
+ if (!prices || !prices.history) return;
42
34
 
43
35
  const todayStr = date.today;
44
- const todayDate = new Date(todayStr + 'T00:00:00Z');
45
- const yesterdayDate = new Date(todayDate);
46
- yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
47
- const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
36
+ const prevDate = new Date(todayStr + 'T00:00:00Z');
37
+ prevDate.setUTCDate(prevDate.getUTCDate() - 1);
38
+ const yesterdayStr = prevDate.toISOString().slice(0, 10);
48
39
 
49
40
  const batchResults = {};
50
41
 
51
- // Iterate over THIS SHARD'S instruments
52
42
  for (const [instrumentId, priceData] of Object.entries(prices.history)) {
53
43
  const ticker = instrumentToTicker[instrumentId];
54
44
  if (!ticker) continue;
55
45
 
56
- // Note: getHistory works on the partial 'prices' object just fine
57
- const history = priceExtractor.getHistory(prices, instrumentId);
58
- if (history.length === 0) continue;
59
-
60
46
  const todayPrice = priceData.prices?.[todayStr];
61
47
  const yesterdayPrice = priceData.prices?.[yesterdayStr];
62
48
 
63
- if (todayPrice && yesterdayPrice && yesterdayPrice > 0) {
64
- const changePct = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
49
+ if (todayPrice) {
50
+ let changePct = null;
51
+ if (yesterdayPrice && yesterdayPrice > 0) {
52
+ changePct = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
53
+ }
54
+
65
55
  batchResults[ticker] = {
66
- change_1d_pct: isFinite(changePct) ? changePct : 0
56
+ change_1d_pct: (changePct !== null && isFinite(changePct)) ? changePct : null,
57
+ closing_price: todayPrice
67
58
  };
68
- } else {
69
- batchResults[ticker] = { change_1d_pct: 0 };
70
59
  }
71
60
  }
72
-
73
- // Merge batch results into main result
74
61
  Object.assign(this.result, batchResults);
75
62
  }
76
63
 
77
64
  async getResult() { return this.result; }
78
-
79
- // Explicit reset only called by system between full runs
80
65
  reset() { this.result = {}; }
81
66
  }
82
67
  module.exports = InstrumentPriceChange1D;
@@ -1,11 +1,5 @@
1
- /**
2
- * @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
3
- * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
- */
5
1
  class InstrumentPriceMomentum20D {
6
- constructor() {
7
- this.result = {};
8
- }
2
+ constructor() { this.result = {}; }
9
3
 
10
4
  static getMetadata() {
11
5
  return {
@@ -23,9 +17,10 @@ class InstrumentPriceMomentum20D {
23
17
  const tickerSchema = {
24
18
  "type": "object",
25
19
  "properties": {
26
- "momentum_20d_pct": { "type": "number" }
20
+ "momentum_20d_pct": { "type": ["number", "null"] },
21
+ "current_price": { "type": "number" }
27
22
  },
28
- "required": ["momentum_20d_pct"]
23
+ "required": ["momentum_20d_pct", "current_price"]
29
24
  };
30
25
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
31
26
  }
@@ -33,7 +28,6 @@ class InstrumentPriceMomentum20D {
33
28
  _findPriceOnOrBefore(prices, instrumentId, targetDateStr) {
34
29
  const priceData = prices.history[instrumentId];
35
30
  if (!priceData || !priceData.prices) return null;
36
-
37
31
  let checkDate = new Date(targetDateStr + 'T00:00:00Z');
38
32
  for (let i = 0; i < 5; i++) {
39
33
  const str = checkDate.toISOString().slice(0, 10);
@@ -47,20 +41,15 @@ class InstrumentPriceMomentum20D {
47
41
  process(context) {
48
42
  const { mappings, prices, date } = context;
49
43
  const { instrumentToTicker } = mappings;
50
-
51
- if (!prices || !prices.history) {
52
- return;
53
- }
44
+ if (!prices || !prices.history) return;
54
45
 
55
46
  const todayStr = date.today;
56
- const todayDate = new Date(todayStr + 'T00:00:00Z');
57
- const twentyDaysAgo = new Date(todayDate);
58
- twentyDaysAgo.setUTCDate(todayDate.getUTCDate() - 20);
59
- const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
47
+ const oldDate = new Date(todayStr + 'T00:00:00Z');
48
+ oldDate.setUTCDate(oldDate.getUTCDate() - 20);
49
+ const oldStr = oldDate.toISOString().slice(0, 10);
60
50
 
61
51
  const batchResults = {};
62
52
 
63
- // Iterate over THIS SHARD'S instruments
64
53
  for (const [instrumentId, priceData] of Object.entries(prices.history)) {
65
54
  const ticker = instrumentToTicker[instrumentId];
66
55
  if (!ticker) continue;
@@ -68,17 +57,18 @@ class InstrumentPriceMomentum20D {
68
57
  const currentPrice = this._findPriceOnOrBefore(prices, instrumentId, todayStr);
69
58
  const oldPrice = this._findPriceOnOrBefore(prices, instrumentId, oldStr);
70
59
 
71
- if (currentPrice && oldPrice && oldPrice > 0) {
72
- const momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
60
+ if (currentPrice) {
61
+ let momPct = null;
62
+ if (oldPrice && oldPrice > 0) {
63
+ momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
64
+ }
65
+
73
66
  batchResults[ticker] = {
74
- momentum_20d_pct: isFinite(momPct) ? momPct : 0
67
+ momentum_20d_pct: (momPct !== null && isFinite(momPct)) ? momPct : null,
68
+ current_price: currentPrice
75
69
  };
76
- } else {
77
- batchResults[ticker] = { momentum_20d_pct: 0 };
78
70
  }
79
71
  }
80
-
81
- // Merge
82
72
  Object.assign(this.result, batchResults);
83
73
  }
84
74
 
@@ -1,5 +1,5 @@
1
1
  class OwnershipVsPerformanceYTD {
2
- constructor() { this.results = {}; }
2
+ constructor() { this.results = { metrics: {}, baselines: {} }; }
3
3
 
4
4
  static getMetadata() {
5
5
  return {
@@ -7,7 +7,7 @@ class OwnershipVsPerformanceYTD {
7
7
  type: 'meta',
8
8
  category: 'core',
9
9
  userType: 'n/a',
10
- isHistorical: true, // Required to carry forward Jan 1st baseline
10
+ isHistorical: true,
11
11
  rootDataDependencies: ['insights', 'price']
12
12
  };
13
13
  }
@@ -18,33 +18,17 @@ class OwnershipVsPerformanceYTD {
18
18
  const metricSchema = {
19
19
  "type": "object",
20
20
  "properties": {
21
- "priceYtd": { "type": "number" },
22
- "ownersYtd": { "type": "number" },
21
+ "priceYtd": { "type": ["number", "null"] },
22
+ "ownersYtd": { "type": ["number", "null"] },
23
23
  "currentPrice": { "type": "number" },
24
24
  "currentOwners": { "type": "number" }
25
25
  },
26
26
  "required": ["priceYtd", "ownersYtd", "currentPrice", "currentOwners"]
27
27
  };
28
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
29
  return {
44
30
  "type": "object",
45
- "properties": {
46
- "baselines": baselineSchema
47
- },
31
+ "properties": { "baselines": { "type": "object" } },
48
32
  "additionalProperties": metricSchema
49
33
  };
50
34
  }
@@ -54,15 +38,11 @@ class OwnershipVsPerformanceYTD {
54
38
  const { previousComputed, mappings, prices, date } = context;
55
39
 
56
40
  const dailyInsights = insightsHelper.getInsights(context, 'today');
57
-
58
- // 1. Manage Baseline State (Jan 1st snapshot)
59
- // If today is Jan 1st (or first run), we reset. Otherwise, we load from yesterday.
60
- let baselineState = previousComputed['ownership-vs-performance-ytd']?.baselines || {};
41
+ const prevRes = previousComputed['ownership-vs-performance-ytd'];
42
+ let baselineState = prevRes?.baselines || {};
43
+
61
44
  const isStartOfYear = date.today.endsWith('-01-01') || date.today.endsWith('-01-02');
62
-
63
- if (isStartOfYear) {
64
- baselineState = {}; // Reset for new year
65
- }
45
+ if (isStartOfYear) baselineState = {};
66
46
 
67
47
  const currentMetrics = {};
68
48
 
@@ -72,14 +52,11 @@ class OwnershipVsPerformanceYTD {
72
52
  if (!ticker) continue;
73
53
 
74
54
  const currentOwners = insightsHelper.getTotalOwners(insight);
75
-
76
- // Get Price History
77
55
  const priceHist = priceExtractor.getHistory(prices, ticker);
78
56
  if (!priceHist.length) continue;
79
57
 
80
58
  const currentPrice = priceHist[priceHist.length - 1].price;
81
59
 
82
- // 2. Set Baseline if missing
83
60
  if (!baselineState[ticker]) {
84
61
  baselineState[ticker] = {
85
62
  startPrice: currentPrice,
@@ -89,37 +66,23 @@ class OwnershipVsPerformanceYTD {
89
66
  }
90
67
 
91
68
  const base = baselineState[ticker];
92
-
93
- // 3. Calculate Deltas
94
- const priceYtd = base.startPrice > 0
95
- ? ((currentPrice - base.startPrice) / base.startPrice) * 100
96
- : 0;
97
-
98
- const ownersYtd = base.startOwners > 0
99
- ? ((currentOwners - base.startOwners) / base.startOwners) * 100
100
- : 0;
69
+ const priceYtd = base.startPrice > 0 ? ((currentPrice - base.startPrice) / base.startPrice) * 100 : 0;
70
+ const ownersYtd = base.startOwners > 0 ? ((currentOwners - base.startOwners) / base.startOwners) * 100 : 0;
101
71
 
102
72
  currentMetrics[ticker] = {
103
- priceYtd,
104
- ownersYtd,
73
+ priceYtd: isFinite(priceYtd) ? priceYtd : null,
74
+ ownersYtd: isFinite(ownersYtd) ? ownersYtd : null,
105
75
  currentPrice,
106
76
  currentOwners
107
77
  };
108
78
  }
109
79
 
110
- // 4. Output structure includes the baselines so they are saved for tomorrow
111
- this.results = {
112
- metrics: currentMetrics,
113
- baselines: baselineState
114
- };
80
+ this.results = { metrics: currentMetrics, baselines: baselineState };
115
81
  }
116
82
 
117
83
  async getResult() {
118
- // We flatten the 'metrics' for easy API consumption,
119
- // but we must ensure 'baselines' is preserved in the stored document
120
- // so 'previousComputed' picks it up tomorrow.
121
84
  return { ...this.results.metrics, baselines: this.results.baselines };
122
85
  }
86
+ reset() { this.results = { metrics: {}, baselines: {} }; }
123
87
  }
124
-
125
88
  module.exports = OwnershipVsPerformanceYTD;
@@ -7,7 +7,7 @@ class OwnershipVsVolatility {
7
7
  type: 'meta',
8
8
  category: 'core',
9
9
  userType: 'n/a',
10
- isHistorical: true, // To calculate ownership change over window
10
+ isHistorical: true,
11
11
  rootDataDependencies: ['insights', 'price']
12
12
  };
13
13
  }
@@ -18,13 +18,10 @@ class OwnershipVsVolatility {
18
18
  const schema = {
19
19
  "type": "object",
20
20
  "properties": {
21
- "volatilityScore": { "type": "number" },
22
- "ownerChange7d": { "type": "number" },
21
+ "volatilityScore": { "type": ["number", "null"] },
22
+ "ownerChange7d": { "type": ["number", "null"] },
23
23
  "laggedOwners": { "type": "number" },
24
- "_history": {
25
- "type": "array",
26
- "items": { "type": "number" }
27
- }
24
+ "_history": { "type": "array", "items": { "type": "number" } }
28
25
  },
29
26
  "required": ["volatilityScore", "ownerChange7d", "laggedOwners"]
30
27
  };
@@ -34,10 +31,7 @@ class OwnershipVsVolatility {
34
31
  async process(context) {
35
32
  const { insights: insightsHelper, priceExtractor, compute } = context.math;
36
33
  const { previousComputed, mappings, prices } = context;
37
-
38
34
  const dailyInsights = insightsHelper.getInsights(context, 'today');
39
-
40
- // Use 7-day lookback for ownership change
41
35
  const prevState = previousComputed['ownership-vs-volatility'] || {};
42
36
 
43
37
  for (const insight of dailyInsights) {
@@ -45,42 +39,39 @@ class OwnershipVsVolatility {
45
39
  const ticker = mappings.instrumentToTicker[instId];
46
40
  if (!ticker) continue;
47
41
 
48
- // 1. Calculate Recent Volatility (14 Day StdDev of Returns)
42
+ const currentOwners = insightsHelper.getTotalOwners(insight);
49
43
  const priceHist = priceExtractor.getHistory(prices, ticker);
50
- if (priceHist.length < 14) continue;
51
-
52
- const recentPrices = priceHist.slice(-14);
53
- const returns = [];
54
- for (let i = 1; i < recentPrices.length; i++) {
55
- returns.push((recentPrices[i].price - recentPrices[i - 1].price) / recentPrices[i - 1].price);
44
+
45
+ let volatility = null;
46
+ if (priceHist.length >= 14) {
47
+ const recentPrices = priceHist.slice(-14);
48
+ const returns = [];
49
+ for (let i = 1; i < recentPrices.length; i++) {
50
+ returns.push((recentPrices[i].price - recentPrices[i - 1].price) / recentPrices[i - 1].price);
51
+ }
52
+ volatility = compute.standardDeviation(returns);
56
53
  }
57
54
 
58
- const volatility = compute.standardDeviation(returns); // Raw std dev
55
+ const history = [...(prevState[ticker]?._history || [])];
56
+ history.push(currentOwners);
57
+ if (history.length > 7) history.shift();
59
58
 
60
- // 2. Calculate Ownership Change (Current vs 7 days ago stored state)
61
- const currentOwners = insightsHelper.getTotalOwners(insight);
62
- const prevOwners = prevState[ticker]?.laggedOwners || currentOwners;
63
-
64
- const ownerChangePct = prevOwners > 0
65
- ? ((currentOwners - prevOwners) / prevOwners) * 100
66
- : 0;
59
+ const laggedOwners = history[0];
60
+ let ownerChangePct = null;
61
+ if (laggedOwners > 0 && history.length >= 7) {
62
+ ownerChangePct = ((currentOwners - laggedOwners) / laggedOwners) * 100;
63
+ }
67
64
 
68
65
  this.results[ticker] = {
69
- volatilityScore: volatility,
70
- ownerChange7d: ownerChangePct,
71
- // Store current owners as 'laggedOwners' for next week?
72
- // Actually, for daily rolling 7d change, we need a queue.
73
- // Simplified: We store today's value, and the API/UI compares.
74
- // Better: Use a simple rolling queue like the Momentum calc.
75
- _history: [...(prevState[ticker]?._history || []), currentOwners].slice(-7),
76
-
77
- // For the output: Calculate change vs the oldest in history (approx 7d ago)
78
- laggedOwners: (prevState[ticker]?._history?.[0] || currentOwners)
66
+ volatilityScore: (volatility !== null && isFinite(volatility)) ? volatility : null,
67
+ ownerChange7d: (ownerChangePct !== null && isFinite(ownerChangePct)) ? ownerChangePct : null,
68
+ _history: history,
69
+ laggedOwners: laggedOwners
79
70
  };
80
71
  }
81
72
  }
82
73
 
83
74
  async getResult() { return this.results; }
75
+ reset() { this.results = {}; }
84
76
  }
85
-
86
77
  module.exports = OwnershipVsVolatility;
@@ -1,7 +1,3 @@
1
- /**
2
- * @fileoverview Calculation (Pass 1) for daily bought vs. sold count.
3
- * REFACTORED: Uses context.math.extract on today vs yesterday.
4
- */
5
1
  class DailyBoughtVsSoldCount {
6
2
  constructor() {
7
3
  this.assetActivity = new Map();
@@ -26,16 +22,17 @@ class DailyBoughtVsSoldCount {
26
22
  "properties": {
27
23
  "positions_bought": { "type": "number" },
28
24
  "positions_sold": { "type": "number" },
29
- "net_change": { "type": "number" }
25
+ "net_change": { "type": "number" },
26
+ "aggregate_portfolio_size": { "type": "number" } // Baseline
30
27
  },
31
- "required": ["positions_bought", "positions_sold", "net_change"]
28
+ "required": ["positions_bought", "positions_sold", "net_change", "aggregate_portfolio_size"]
32
29
  };
33
30
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
34
31
  }
35
32
 
36
33
  _initAsset(instrumentId) {
37
34
  if (!this.assetActivity.has(instrumentId)) {
38
- this.assetActivity.set(instrumentId, { new: 0, closed: 0 });
35
+ this.assetActivity.set(instrumentId, { new: 0, closed: 0, currentSize: 0 });
39
36
  }
40
37
  }
41
38
 
@@ -55,24 +52,30 @@ class DailyBoughtVsSoldCount {
55
52
  if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
56
53
 
57
54
  const todayPositions = extract.getPositions(user.portfolio.today, user.type);
58
- const yesterdayPositions = extract.getPositions(user.portfolio.yesterday, user.type);
55
+ const yesterdayPositions = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
59
56
 
60
57
  const tPosMap = this._getInstrumentIds(todayPositions, extract);
61
58
  const yPosMap = this._getInstrumentIds(yesterdayPositions, extract);
62
59
 
63
- // New positions (in today, not yesterday)
64
- for (const [posId, instId] of tPosMap.entries()) {
65
- if (!yPosMap.has(posId)) {
66
- this._initAsset(instId);
67
- this.assetActivity.get(instId).new++;
68
- }
60
+ // Track current sizes for Baseline
61
+ for (const instId of tPosMap.values()) {
62
+ this._initAsset(instId);
63
+ this.assetActivity.get(instId).currentSize++;
69
64
  }
70
65
 
71
- // Closed positions (in yesterday, not today)
72
- for (const [posId, instId] of yPosMap.entries()) {
73
- if (!tPosMap.has(posId)) {
74
- this._initAsset(instId);
75
- this.assetActivity.get(instId).closed++;
66
+ // Only calculate bought/sold if yesterday exists
67
+ if (user.portfolio.yesterday) {
68
+ for (const [posId, instId] of tPosMap.entries()) {
69
+ if (!yPosMap.has(posId)) {
70
+ this._initAsset(instId);
71
+ this.assetActivity.get(instId).new++;
72
+ }
73
+ }
74
+ for (const [posId, instId] of yPosMap.entries()) {
75
+ if (!tPosMap.has(posId)) {
76
+ this._initAsset(instId);
77
+ this.assetActivity.get(instId).closed++;
78
+ }
76
79
  }
77
80
  }
78
81
  }
@@ -82,13 +85,12 @@ class DailyBoughtVsSoldCount {
82
85
  const result = {};
83
86
  for (const [instId, data] of this.assetActivity.entries()) {
84
87
  const ticker = this.tickerMap[instId] || `id_${instId}`;
85
- if (data.new > 0 || data.closed > 0) {
86
- result[ticker] = {
87
- positions_bought: data.new,
88
- positions_sold: data.closed,
89
- net_change: data.new - data.closed
90
- };
91
- }
88
+ result[ticker] = {
89
+ positions_bought: data.new,
90
+ positions_sold: data.closed,
91
+ net_change: data.new - data.closed,
92
+ aggregate_portfolio_size: data.currentSize
93
+ };
92
94
  }
93
95
  return result;
94
96
  }