aiden-shared-calculations-unified 1.0.86 → 1.0.88

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 (80) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +96 -0
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
  3. package/calculations/core/asset-cost-basis-profile.js +127 -0
  4. package/calculations/core/asset-pnl-status.js +36 -106
  5. package/calculations/core/asset-position-size.js +40 -91
  6. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  7. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  8. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  9. package/calculations/core/average-daily-position-pnl.js +19 -49
  10. package/calculations/core/holding-duration-per-asset.js +25 -127
  11. package/calculations/core/instrument-price-change-1d.js +30 -49
  12. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  13. package/calculations/core/long-position-per-stock.js +39 -68
  14. package/calculations/core/overall-holding-duration.js +16 -87
  15. package/calculations/core/overall-profitability-ratio.js +11 -40
  16. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  17. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  18. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  19. package/calculations/core/platform-ownership-per-sector.js +45 -96
  20. package/calculations/core/platform-total-positions-held.js +20 -80
  21. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  22. package/calculations/core/price-metrics.js +95 -206
  23. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  24. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  25. package/calculations/core/profitability-skew-per-stock.js +41 -94
  26. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  27. package/calculations/core/sentiment-per-stock.js +24 -77
  28. package/calculations/core/short-position-per-stock.js +35 -43
  29. package/calculations/core/social-activity-aggregation.js +26 -49
  30. package/calculations/core/social-asset-posts-trend.js +38 -94
  31. package/calculations/core/social-event-correlation.js +26 -93
  32. package/calculations/core/social-sentiment-aggregation.js +20 -44
  33. package/calculations/core/social-top-mentioned-words.js +35 -87
  34. package/calculations/core/social-topic-interest-evolution.js +22 -111
  35. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  36. package/calculations/core/social-word-mentions-trend.js +27 -104
  37. package/calculations/core/speculator-asset-sentiment.js +31 -72
  38. package/calculations/core/speculator-danger-zone.js +48 -84
  39. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  40. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  41. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  42. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  43. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  44. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  45. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  46. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  47. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  48. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  49. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  50. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  51. package/calculations/core/test..js +0 -0
  52. package/calculations/core/total-long-figures.js +16 -31
  53. package/calculations/core/total-long-per-sector.js +39 -61
  54. package/calculations/core/total-short-figures.js +13 -32
  55. package/calculations/core/total-short-per-sector.js +39 -61
  56. package/calculations/core/users-processed.js +11 -46
  57. package/calculations/gauss/cohort-capital-flow.js +54 -173
  58. package/calculations/gauss/cohort-definer.js +77 -163
  59. package/calculations/gauss/daily-dna-filter.js +29 -83
  60. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  61. package/calculations/gem/cohort-momentum-state.js +27 -72
  62. package/calculations/gem/cohort-skill-definition.js +36 -52
  63. package/calculations/gem/platform-conviction-divergence.js +18 -60
  64. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  65. package/calculations/gem/skilled-cohort-flow.js +67 -175
  66. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  67. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  68. package/calculations/ghost-book/cost-basis-density.js +79 -0
  69. package/calculations/ghost-book/liquidity-vacuum.js +52 -0
  70. package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
  71. package/calculations/helix/helix-contrarian-signal.js +20 -114
  72. package/calculations/helix/herd-consensus-score.js +42 -124
  73. package/calculations/helix/winner-loser-flow.js +36 -118
  74. package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
  75. package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
  76. package/calculations/predicative-alpha/mimetic-latency.js +124 -0
  77. package/calculations/pyro/risk-appetite-index.js +33 -74
  78. package/calculations/pyro/squeeze-potential.js +30 -87
  79. package/calculations/pyro/volatility-signal.js +33 -78
  80. package/package.json +1 -1
@@ -1,29 +1,14 @@
1
1
  /**
2
2
  * @fileoverview GAUSS Product Line (Pass 2)
3
- *
4
- * This 'standard' calculation re-streams all users, filters for
5
- * the "Smart" and "Dumb" cohorts (from Pass 1), and then calculates
6
- * their full 4D "Trader DNA".
7
- *
8
- * It then buckets these users into our final, named sub-cohorts
9
- * (e.g., "Smart Investors", "FOMO Chasers").
3
+ * REFACTORED: Uses context.computed and context.math.
10
4
  */
11
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
12
-
13
-
14
5
  class CohortDefiner {
15
6
  constructor() {
16
- // We will store the full DNA for our filtered cohorts
17
- this.smartVectors = []; // [ { userId, skill, time, fomo, bagholder }, ... ]
18
- this.dumbVectors = []; // [ { userId, skill, time, fomo, bagholder }, ... ]
19
-
20
- this.momentumData = null;
21
- // --- STANDARD 0: RENAMED ---
22
- this.tickerMap = null;
23
- this.cohortIdSets = null; // { smart: Set, dumb: Set }
7
+ this.smartVectors = [];
8
+ this.dumbVectors = [];
9
+ this.cohortIdSets = null;
24
10
  }
25
11
 
26
- /** Statically defines metadata */
27
12
  static getMetadata() {
28
13
  return {
29
14
  type: 'standard',
@@ -34,197 +19,129 @@ class CohortDefiner {
34
19
  };
35
20
  }
36
21
 
37
- /** Statically declare dependencies */
38
22
  static getDependencies() {
39
- return [
40
- 'daily-dna-filter', // from gauss (Pass 1)
41
- 'instrument-price-momentum-20d' // from core (Pass 1)
42
- ];
23
+ return ['daily-dna-filter', 'instrument-price-momentum-20d'];
43
24
  }
44
25
 
45
- /**
46
- * Defines the output schema for this calculation.
47
- */
48
26
  static getSchema() {
49
- const cohortSchema = {
50
- "type": "array",
51
- "description": "A list of User IDs belonging to this cohort.",
52
- "items": { "type": "string" }
53
- };
54
-
27
+ const cohortSchema = { "type": "array", "items": { "type": "string" } };
55
28
  return {
56
29
  "type": "object",
57
- "description": "A map of named cohorts to the list of user IDs in each.",
58
- "properties": {
59
- "smart_investors": cohortSchema,
60
- "smart_scalpers": cohortSchema,
61
- "fomo_chasers": cohortSchema,
62
- "patient_losers": cohortSchema,
63
- "fomo_bagholders": cohortSchema,
64
- "uncategorized_smart": cohortSchema,
65
- "uncategorized_dumb": cohortSchema
66
- },
67
- "additionalProperties": cohortSchema
30
+ "patternProperties": { "^.*$": cohortSchema }
68
31
  };
69
32
  }
70
33
 
71
- /**
72
- * Loads dependencies into memory on the first process() call.
73
- */
74
- _loadDependencies(fetchedDependencies) {
75
- if (this.cohortIdSets) return; // Run once
76
-
77
- const dnaFilterData = fetchedDependencies['daily-dna-filter'];
78
- this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
34
+ _loadDependencies(computed) {
35
+ if (this.cohortIdSets) return;
36
+ const dnaFilterData = computed['daily-dna-filter'];
79
37
 
80
- if (dnaFilterData && dnaFilterData.smart_cohort_ids && dnaFilterData.dumb_cohort_ids) {
81
- this.cohortIdSets = {
82
- smart: new Set(dnaFilterData.smart_cohort_ids),
83
- dumb: new Set(dnaFilterData.dumb_cohort_ids)
84
- };
85
- } else {
86
- this.cohortIdSets = {
87
- smart: new Set(),
88
- dumb: new Set()
89
- };
90
- }
38
+ this.cohortIdSets = {
39
+ smart: new Set(dnaFilterData?.smart_cohort_ids || []),
40
+ dumb: new Set(dnaFilterData?.dumb_cohort_ids || [])
41
+ };
91
42
  }
92
43
 
93
- _getFomoScore(todayPortfolio, yesterdayPortfolio) {
94
- // --- STANDARD 0: UPDATED CHECK ---
95
- if (!this.tickerMap) return 0;
96
-
97
- if (!this.momentumData) {
98
- return 0; // Cannot calculate FOMO without momentum data
99
- }
44
+ _getFomoScore(extract, mappings, today, yesterday, momentumData, userType) {
45
+ if (!momentumData) return 0;
46
+
47
+ const tPos = extract.getPositions(today, userType);
48
+ const yPos = extract.getPositions(yesterday, userType);
49
+
50
+ const yIds = new Set(yPos.map(p => extract.getInstrumentId(p)));
51
+ const newPositions = tPos.filter(p => !yIds.has(extract.getInstrumentId(p)));
100
52
 
101
- const yIds = new Set((yesterdayPortfolio?.AggregatedPositions || []).map(p => p.InstrumentID));
102
- const newPositions = (todayPortfolio?.AggregatedPositions || []).filter(p => p.InstrumentID && !yIds.has(p.InstrumentID));
103
53
  if (newPositions.length === 0) return 0;
104
54
 
105
- let fomoSum = 0;
106
- let count = 0;
55
+ let fomoSum = 0, count = 0;
107
56
  for (const pos of newPositions) {
108
- // --- STANDARD 0: SIMPLIFIED ---
109
- const ticker = this.tickerMap[pos.InstrumentID];
110
- if (ticker && this.momentumData[ticker]) {
111
- fomoSum += this.momentumData[ticker].momentum_20d_pct || 0;
57
+ const instId = extract.getInstrumentId(pos);
58
+ const ticker = mappings.instrumentToTicker[instId];
59
+ if (ticker && momentumData[ticker]) {
60
+ fomoSum += momentumData[ticker].momentum_20d_pct || 0;
112
61
  count++;
113
62
  }
114
63
  }
115
- return (count > 0) ? fomoSum / count : 0;
64
+ return count > 0 ? fomoSum / count : 0;
116
65
  }
117
66
 
118
- _getBagholderScore(todayPortfolio) {
119
- // FIX: Only Speculator data (PublicPositions) has the OpenDateTime field needed for this calculation.
120
- const openPositions = todayPortfolio?.PublicPositions || [];
121
- if (openPositions.length === 0) return 0;
67
+ _getBagholderScore(extract, today, userType) {
68
+ const positions = extract.getPositions(today, userType);
69
+ if (positions.length === 0) return 0;
122
70
 
123
- let durationSum = 0;
124
- let count = 0;
71
+ let durationSum = 0, count = 0;
125
72
  const now = new Date();
126
73
 
127
- for (const pos of openPositions) {
128
- if (pos.NetProfit < -20) {
129
- try {
130
- const openDate = new Date(pos.OpenDateTime);
131
- const durationMs = now.getTime() - openDate.getTime();
132
- const durationDays = durationMs / (1000 * 60 * 60 * 24);
133
- if (isFinite(durationDays)) {
74
+ for (const pos of positions) {
75
+ if (extract.getNetProfit(pos) < -20) {
76
+ const openDate = extract.getOpenDateTime(pos);
77
+ if (openDate) {
78
+ try {
79
+ const durationDays = (now - openDate) / (86400000);
134
80
  durationSum += durationDays;
135
81
  count++;
136
- }
137
- } catch (e) { /* ignore invalid date */ }
82
+ } catch (e) {}
83
+ }
138
84
  }
139
85
  }
140
- return (count > 0) ? durationSum / count : 0;
86
+ return count > 0 ? durationSum / count : 0;
141
87
  }
142
88
 
143
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
144
- this._loadDependencies(fetchedDependencies);
145
-
146
- // --- STANDARD 0: FIXED ---
147
- if (!this.tickerMap) {
148
- this.tickerMap = context.instrumentToTicker;
149
- }
89
+ process(context) {
90
+ const { user, computed, mappings, math } = context;
91
+ const { extract, history } = math;
150
92
 
151
- const isSmart = this.cohortIdSets.smart.has(userId);
152
- const isDumb = this.cohortIdSets.dumb.has(userId);
93
+ this._loadDependencies(computed);
153
94
 
154
- if (!isSmart && !isDumb) {
155
- return; // Not in our extreme cohorts
156
- }
95
+ const isSmart = this.cohortIdSets.smart.has(user.id);
96
+ const isDumb = this.cohortIdSets.dumb.has(user.id);
97
+
98
+ if (!isSmart && !isDumb) return;
157
99
 
158
- // FIX: Access history data from the nested property provided by the fixed worker.js
159
- const history = todayPortfolio?.history?.all;
160
- if (!history) return;
100
+ // 1. Strict History Access
101
+ const historyDoc = history.getDailyHistory(user);
102
+ const summary = history.getSummary(historyDoc);
161
103
 
162
- const winRate = (history.winRatio || 0) / 100.0;
104
+ if (!summary) return;
105
+
106
+ // 2. Valid usage of Summary DTO properties
107
+ const winRate = summary.winRatio / 100.0;
163
108
  const lossRate = 1.0 - winRate;
164
- const avgWin = history.avgProfitPct || 0;
165
- const avgLoss = Math.abs(history.avgLossPct || 0);
166
- const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
167
- const lt_time = history.avgHoldingTimeInMinutes || 0;
109
+ const lt_skill = (winRate * summary.avgProfitPct) - (lossRate * Math.abs(summary.avgLossPct));
110
+ const lt_time = summary.avgHoldingTimeInMinutes;
168
111
 
169
- const st_fomo = this._getFomoScore(todayPortfolio, yesterdayPortfolio);
170
- const st_bagholder = this._getBagholderScore(todayPortfolio);
112
+ const momentumData = computed['instrument-price-momentum-20d'];
113
+ const st_fomo = this._getFomoScore(extract, mappings, user.portfolio.today, user.portfolio.yesterday, momentumData, user.type);
114
+ const st_bagholder = this._getBagholderScore(extract, user.portfolio.today, user.type);
171
115
 
172
- const vector = { userId, skill: lt_skill, time: lt_time, fomo: st_fomo, bagholder: st_bagholder };
116
+ const vector = { userId: user.id, skill: lt_skill, time: lt_time, fomo: st_fomo, bagholder: st_bagholder };
173
117
 
174
- if (isSmart) {
175
- this.smartVectors.push(vector);
176
- } else if (isDumb) {
177
- this.dumbVectors.push(vector);
178
- }
118
+ if (isSmart) this.smartVectors.push(vector);
119
+ else this.dumbVectors.push(vector);
179
120
  }
180
121
 
181
122
  _getMedian(vectors, key) {
182
123
  if (vectors.length === 0) return 0;
183
124
  const sorted = vectors.map(v => v[key]).sort((a, b) => a - b);
184
125
  const mid = Math.floor(sorted.length / 2);
185
- if (sorted.length % 2 === 0 && sorted.length > 0) {
186
- return (sorted[mid - 1] + sorted[mid]) / 2;
187
- }
188
- return sorted[mid];
126
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
189
127
  }
190
128
 
191
- getResult() {
129
+ async getResult() {
192
130
  const cohorts = {};
193
- const assignedSmart = new Set();
194
- const assignedDumb = new Set();
131
+ const assignedSmart = new Set(), assignedDumb = new Set();
195
132
 
196
- const smart_median_time = this._getMedian(this.smartVectors, 'time');
197
-
198
- cohorts['smart_investors'] = this.smartVectors
199
- .filter(u => u.time >= smart_median_time)
200
- .map(u => { assignedSmart.add(u.userId); return u.userId; });
201
-
202
- cohorts['smart_scalpers'] = this.smartVectors
203
- .filter(u => u.time < smart_median_time)
204
- .map(u => { assignedSmart.add(u.userId); return u.userId; });
205
-
206
- cohorts['uncategorized_smart'] = this.smartVectors
207
- .filter(u => !assignedSmart.has(u.userId))
208
- .map(u => u.userId);
133
+ const smart_time = this._getMedian(this.smartVectors, 'time');
134
+ cohorts['smart_investors'] = this.smartVectors.filter(u => u.time >= smart_time).map(u => { assignedSmart.add(u.userId); return u.userId; });
135
+ cohorts['smart_scalpers'] = this.smartVectors.filter(u => u.time < smart_time).map(u => { assignedSmart.add(u.userId); return u.userId; });
136
+ cohorts['uncategorized_smart'] = this.smartVectors.filter(u => !assignedSmart.has(u.userId)).map(u => u.userId);
209
137
 
210
- const dumb_median_fomo = this._getMedian(this.dumbVectors, 'fomo');
211
- const dumb_median_bag = this._getMedian(this.dumbVectors, 'bagholder');
212
-
213
- cohorts['fomo_chasers'] = this.dumbVectors
214
- .filter(u => u.fomo >= dumb_median_fomo && u.bagholder < dumb_median_bag)
215
- .map(u => { assignedDumb.add(u.userId); return u.userId; });
216
-
217
- cohorts['patient_losers'] = this.dumbVectors
218
- .filter(u => u.fomo < dumb_median_fomo && u.bagholder >= dumb_median_bag)
219
- .map(u => { assignedDumb.add(u.userId); return u.userId; });
220
-
221
- cohorts['fomo_bagholders'] = this.dumbVectors
222
- .filter(u => u.fomo >= dumb_median_fomo && u.bagholder >= dumb_median_bag)
223
- .map(u => { assignedDumb.add(u.userId); return u.userId; });
138
+ const dumb_fomo = this._getMedian(this.dumbVectors, 'fomo');
139
+ const dumb_bag = this._getMedian(this.dumbVectors, 'bagholder');
224
140
 
225
- cohorts['uncategorized_dumb'] = this.dumbVectors
226
- .filter(u => !assignedDumb.has(u.userId))
227
- .map(u => u.userId);
141
+ cohorts['fomo_chasers'] = this.dumbVectors.filter(u => u.fomo >= dumb_fomo && u.bagholder < dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
142
+ cohorts['patient_losers'] = this.dumbVectors.filter(u => u.fomo < dumb_fomo && u.bagholder >= dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
143
+ cohorts['fomo_bagholders'] = this.dumbVectors.filter(u => u.fomo >= dumb_fomo && u.bagholder >= dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
144
+ cohorts['uncategorized_dumb'] = this.dumbVectors.filter(u => !assignedDumb.has(u.userId)).map(u => u.userId);
228
145
 
229
146
  return cohorts;
230
147
  }
@@ -232,9 +149,6 @@ class CohortDefiner {
232
149
  reset() {
233
150
  this.smartVectors = [];
234
151
  this.dumbVectors = [];
235
- this.momentumData = null;
236
- // --- STANDARD 0: RENAMED ---
237
- this.tickerMap = null;
238
152
  this.cohortIdSets = null;
239
153
  }
240
154
  }
@@ -1,61 +1,30 @@
1
1
  /**
2
2
  * @fileoverview GAUSS Product Line (Pass 1)
3
- *
4
- * This metric is a *filter*. It processes all users and calculates
5
- * their "Long-Term Skill" (Expectancy) from their 'history' doc.
6
- *
7
- * It then filters this list to *only* return the User IDs
8
- * of the top 20% (Smart) and bottom 20% (Dumb) of users,
9
- * solving the 1MB document limit for downstream processing.
3
+ * REFACTORED: Uses context.math.history.getSummary().
10
4
  */
11
5
  class DailyDnaFilter {
12
6
  constructor() {
13
- // We will store all users and their primary skill score
14
- // [ [userId, skillScore], ... ]
15
7
  this.allUserSkills = [];
16
8
  }
17
9
 
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
21
10
  static getMetadata() {
22
11
  return {
23
12
  type: 'standard',
24
- // This calc only needs the 'history' doc.
25
- // As per the test harness, this means 'todayPortfolio'
26
- // in process() *is* the history object.
27
13
  rootDataDependencies: ['history'],
28
- isHistorical: false, // Reads today's history snapshot
14
+ isHistorical: false,
29
15
  userType: 'all',
30
16
  category: 'gauss'
31
17
  };
32
18
  }
33
19
 
34
- /**
35
- * Statically declare dependencies.
36
- */
37
- static getDependencies() {
38
- return []; // This is a Pass 1 calculation
39
- }
20
+ static getDependencies() { return []; }
40
21
 
41
- /**
42
- * Defines the output schema for this calculation.
43
- */
44
22
  static getSchema() {
45
23
  return {
46
24
  "type": "object",
47
- "description": "Provides the User ID lists for the 'Smart' (top 20%) and 'Dumb' (bottom 20%) cohorts based on long-term Expectancy.",
48
25
  "properties": {
49
- "smart_cohort_ids": {
50
- "type": "array",
51
- "description": "List of user IDs in the top 20% 'Smart' cohort.",
52
- "items": { "type": "string" }
53
- },
54
- "dumb_cohort_ids": {
55
- "type": "array",
56
- "description": "List of user IDs in the bottom 20% 'Dumb' cohort.",
57
- "items": { "type": "string" }
58
- },
26
+ "smart_cohort_ids": { "type": "array", "items": { "type": "string" } },
27
+ "dumb_cohort_ids": { "type": "array", "items": { "type": "string" } },
59
28
  "total_users_analyzed": { "type": "number" },
60
29
  "cohort_size": { "type": "number" }
61
30
  },
@@ -63,74 +32,51 @@ class DailyDnaFilter {
63
32
  };
64
33
  }
65
34
 
35
+ process(context) {
36
+ const { user, math } = context;
37
+ const { history } = math; // 'history' is the TOOL
66
38
 
67
- /**
68
- * Processes a single user's daily data.
69
- * @param {object} todayPortfolio - Today's portfolio snapshot.
70
- * @param {object} yesterdayPortfolio - Yesterday's portfolio snapshot.
71
- * @param {string} userId - The user's ID.
72
- */
73
- process(todayPortfolio, yesterdayPortfolio, userId) {
39
+ // 1. Get strict daily history data
40
+ const historyDoc = history.getDailyHistory(user);
74
41
 
75
- // ---
76
- // FIX: 'todayPortfolio' *is* the history object because
77
- // getMetadata() only requests ['history'].
78
- // The path is 'todayPortfolio.all', not 'todayPortfolio.history.all'.
79
- // ---
80
- const history = todayPortfolio?.all;
81
- if (!history || !history.totalTrades || history.totalTrades < 20) {
82
- return; // Not enough history for a reliable score
83
- }
42
+ // 2. Get strict summary object using the tool
43
+ const summary = history.getSummary(historyDoc);
44
+
45
+ // Validation
46
+ if (!summary || summary.totalTrades < 20) return;
84
47
 
85
- const winRate = (history.winRatio || 0) / 100.0;
48
+ // 3. Use properties from the SUMMARY object, not the history tool
49
+ const winRate = summary.winRatio / 100.0;
86
50
  const lossRate = 1.0 - winRate;
87
- const avgWin = history.avgProfitPct || 0;
88
- // Use Math.abs because avgLossPct is stored as a negative number
89
- const avgLoss = Math.abs(history.avgLossPct || 0);
90
51
 
91
- // Expectancy = (Win % * Avg Win %) - (Loss % * Avg Loss %)
52
+ // CORRECTED LINE: uses summary.avgProfitPct
53
+ const avgWin = summary.avgProfitPct;
54
+ const avgLoss = Math.abs(summary.avgLossPct);
55
+
92
56
  const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
93
57
 
94
- if (!isFinite(lt_skill)) {
95
- return; // Skip bad data (e.g., division by zero)
58
+ if (isFinite(lt_skill)) {
59
+ this.allUserSkills.push([user.id, lt_skill]);
96
60
  }
97
-
98
- this.allUserSkills.push([userId, lt_skill]);
99
61
  }
100
62
 
101
- /**
102
- * Returns the final filtered list of user IDs.
103
- */
104
63
  getResult() {
105
64
  const totalUsers = this.allUserSkills.length;
106
- if (totalUsers === 0) {
107
- return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
108
- }
65
+ if (totalUsers === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
109
66
 
110
- // Sort by Skill (Expectancy) in descending order (highest skill first)
111
67
  this.allUserSkills.sort((a, b) => b[1] - a[1]);
112
68
 
113
- const cohortSize = Math.floor(totalUsers * 0.20); // Top/Bottom 20%
114
- if (cohortSize === 0) {
115
- return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
116
- }
117
-
118
- // Get the top 20% (smartest)
119
- const smart_cohort_ids = this.allUserSkills.slice(0, cohortSize).map(u => u[0]);
120
- // Get the bottom 20% (dumbest)
121
- const dumb_cohort_ids = this.allUserSkills.slice(-cohortSize).map(u => u[0]);
69
+ const cohortSize = Math.floor(totalUsers * 0.20);
70
+ if (cohortSize === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
122
71
 
123
- // Output is now small and compact, solving the 1MB limit.
124
72
  return {
125
- smart_cohort_ids: smart_cohort_ids,
126
- dumb_cohort_ids: dumb_cohort_ids,
73
+ smart_cohort_ids: this.allUserSkills.slice(0, cohortSize).map(u => u[0]),
74
+ dumb_cohort_ids: this.allUserSkills.slice(-cohortSize).map(u => u[0]),
127
75
  total_users_analyzed: totalUsers,
128
76
  cohort_size: cohortSize
129
77
  };
130
78
  }
131
79
 
132
- reset() {
133
- this.allUserSkills = [];
134
- }
80
+ reset() { this.allUserSkills = []; }
135
81
  }
136
82
  module.exports = DailyDnaFilter;
@@ -1,66 +1,24 @@
1
1
  /**
2
2
  * @fileoverview GAUSS Product Line (Pass 4)
3
- *
4
- * This is the final, stateless signal generator for the Gauss line.
5
- * It answers: "What is the net 'Smart vs. Dumb' flow divergence?"
6
- *
7
- * It consumes the daily flows from all defined cohorts (Pass 3)
8
- * and aggregates them into a final, actionable signal.
9
- *
10
- * --- FIX ---
11
- * - Aligned data access with the corrected schema of its
12
- * dependency, 'cohort-capital-flow'.
13
- * - It now reads 'cohortFlows[cohortName]' directly, instead of
14
- * the non-existent 'cohortFlows[cohortName].assets'.
3
+ * REFACTORED: Uses context.math.signals and process(context).
15
4
  */
16
5
  class GaussDivergenceSignal {
6
+ constructor() { this.result = {}; }
17
7
 
18
- // --- STANDARD 2: ADDED ---
19
- constructor() {
20
- this.result = {};
21
- }
22
-
23
- /**
24
- * Defines the output schema for this calculation.
25
- * @returns {object} JSON Schema object
26
- */
27
8
  static getSchema() {
28
9
  const tickerSchema = {
29
10
  "type": "object",
30
11
  "properties": {
31
- "signal": {
32
- "type": "string",
33
- "enum": ["Strong Buy", "Buy", "Neutral", "Sell", "Strong Sell"]
34
- },
35
- "gauss_score": {
36
- "type": "number",
37
- "description": "Final divergence score (-10 to +10). Positive = Smart Buying, Negative = Smart Selling."
38
- },
39
- "smart_flow_total_pct": {
40
- "type": "number",
41
- "description": "The total %-point flow from all 'Smart' cohorts."
42
- },
43
- "dumb_flow_total_pct": {
44
- "type": "number",
45
- "description": "The total %-point flow from all 'Dumb' cohorts."
46
- }
12
+ "signal": { "type": "string" },
13
+ "gauss_score": { "type": "number" },
14
+ "smart_flow_total_pct": { "type": "number" },
15
+ "dumb_flow_total_pct": { "type": "number" }
47
16
  },
48
- "required": ["signal", "gauss_score", "smart_flow_total_pct", "dumb_flow_total_pct"]
49
- };
50
-
51
- return {
52
- "type": "object",
53
- "description": "Generates a final divergence signal by aggregating flow from Smart vs. Dumb cohorts.",
54
- "patternProperties": {
55
- "^.*$": tickerSchema // Ticker
56
- },
57
- "additionalProperties": tickerSchema
17
+ "required": ["signal", "gauss_score"]
58
18
  };
19
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
59
20
  }
60
21
 
61
- /**
62
- * Statically defines all metadata for the manifest builder.
63
- */
64
22
  static getMetadata() {
65
23
  return {
66
24
  type: 'meta',
@@ -71,79 +29,41 @@ class GaussDivergenceSignal {
71
29
  };
72
30
  }
73
31
 
74
- /**
75
- * Statically declare dependencies.
76
- */
77
- static getDependencies() {
78
- return [
79
- 'cohort-capital-flow' // from gauss (Pass 3)
80
- ];
81
- }
32
+ static getDependencies() { return ['cohort-capital-flow']; }
82
33
 
83
- /**
84
- * Simple tanh normalization. Scales any number to a -10 to +10 range,
85
- * with diminishing returns for extreme values.
86
- * 0 -> 0, 1 -> 7.6, 2 -> 9.6, 3 -> 9.95
87
- * We scale the input to tune sensitivity.
88
- */
89
- _normalize(score) {
90
- // A score of 2.0 (%-point flow) will be ~9.6
91
- // A score of 1.0 (%-point flow) will be ~7.6
92
- return Math.tanh(score / 2.0) * 10;
93
- }
94
-
95
- /**
96
- * This is a 'meta' calculation. It runs once.
97
- */
98
- // --- THIS IS THE FIX ---
99
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
100
- const { logger } = dependencies;
34
+ process(context) {
35
+ const { computed, math } = context;
36
+ const { signals } = math;
101
37
 
102
- const cohortFlows = fetchedDependencies['cohort-capital-flow'];
103
-
104
- if (!cohortFlows) {
105
- logger.log('WARN', `[gauss/gauss-divergence-signal] Missing dependency 'cohort-capital-flow' for ${dateStr}.`);
106
- this.result = {};
107
- return;
108
- }
38
+ const cohortFlows = computed['cohort-capital-flow'];
39
+ if (!cohortFlows) return;
109
40
 
110
41
  const SMART_COHORTS = ['smart_investors', 'smart_scalpers'];
111
42
  const DUMB_COHORTS = ['fomo_chasers', 'patient_losers', 'fomo_bagholders'];
112
43
 
113
- const blendedFlows = new Map(); // Map<ticker, { smart: 0, dumb: 0 }>
44
+ const blendedFlows = new Map();
114
45
 
115
- // 1. Blend all cohort flows
116
46
  for (const cohortName in cohortFlows) {
117
47
  const isSmart = SMART_COHORTS.includes(cohortName);
118
48
  const isDumb = DUMB_COHORTS.includes(cohortName);
119
49
  if (!isSmart && !isDumb) continue;
120
50
 
121
- // Was: const assets = cohortFlows[cohortName]?.assets;
122
- const assets = cohortFlows[cohortName]; // This is now the ticker map
51
+ const assets = cohortFlows[cohortName];
123
52
  if (!assets) continue;
124
53
 
125
54
  for (const [ticker, data] of Object.entries(assets)) {
126
- if (!blendedFlows.has(ticker)) {
127
- blendedFlows.set(ticker, { smart: 0, dumb: 0 });
128
- }
55
+ if (!blendedFlows.has(ticker)) blendedFlows.set(ticker, { smart: 0, dumb: 0 });
129
56
 
130
- // This logic remains correct, as it reads the inner flow object
131
57
  const flow = data.net_flow_contribution || 0;
132
-
133
- if (isSmart) {
134
- blendedFlows.get(ticker).smart += flow;
135
- } else if (isDumb) {
136
- blendedFlows.get(ticker).dumb += flow;
137
- }
58
+ if (isSmart) blendedFlows.get(ticker).smart += flow;
59
+ else if (isDumb) blendedFlows.get(ticker).dumb += flow;
138
60
  }
139
61
  }
140
- // --- END FIX ---
141
62
 
142
- // 2. Calculate final signal (logic unchanged)
143
63
  const result = {};
144
64
  for (const [ticker, data] of blendedFlows.entries()) {
145
65
  const divergence = data.smart - data.dumb;
146
- const gauss_score = this._normalize(divergence);
66
+ const gauss_score = signals.normalizeTanh(divergence, 10, 2.0);
147
67
 
148
68
  let signal = "Neutral";
149
69
  if (gauss_score > 7.0) signal = "Strong Buy";
@@ -158,17 +78,10 @@ class GaussDivergenceSignal {
158
78
  dumb_flow_total_pct: data.dumb
159
79
  };
160
80
  }
161
-
162
81
  this.result = result;
163
82
  }
164
83
 
165
- async getResult(fetchedDependencies) {
166
- return this.result;
167
- }
168
-
169
- reset() {
170
- this.result = {};
171
- }
84
+ async getResult() { return this.result; }
85
+ reset() { this.result = {}; }
172
86
  }
173
-
174
87
  module.exports = GaussDivergenceSignal;