aiden-shared-calculations-unified 1.0.86 → 1.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/calculations/core/asset-pnl-status.js +36 -106
- package/calculations/core/asset-position-size.js +40 -91
- package/calculations/core/average-daily-pnl-all-users.js +18 -57
- package/calculations/core/average-daily-pnl-per-sector.js +41 -88
- package/calculations/core/average-daily-pnl-per-stock.js +38 -91
- package/calculations/core/average-daily-position-pnl.js +19 -49
- package/calculations/core/holding-duration-per-asset.js +25 -127
- package/calculations/core/instrument-price-change-1d.js +30 -49
- package/calculations/core/instrument-price-momentum-20d.js +50 -60
- package/calculations/core/long-position-per-stock.js +39 -68
- package/calculations/core/overall-holding-duration.js +16 -87
- package/calculations/core/overall-profitability-ratio.js +11 -40
- package/calculations/core/platform-buy-sell-sentiment.js +41 -124
- package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
- package/calculations/core/platform-daily-ownership-delta.js +68 -126
- package/calculations/core/platform-ownership-per-sector.js +45 -96
- package/calculations/core/platform-total-positions-held.js +20 -80
- package/calculations/core/pnl-distribution-per-stock.js +29 -135
- package/calculations/core/price-metrics.js +95 -206
- package/calculations/core/profitability-ratio-per-sector.js +34 -79
- package/calculations/core/profitability-ratio-per-stock.js +32 -88
- package/calculations/core/profitability-skew-per-stock.js +41 -94
- package/calculations/core/profitable-and-unprofitable-status.js +44 -76
- package/calculations/core/sentiment-per-stock.js +24 -77
- package/calculations/core/short-position-per-stock.js +35 -43
- package/calculations/core/social-activity-aggregation.js +26 -49
- package/calculations/core/social-asset-posts-trend.js +38 -94
- package/calculations/core/social-event-correlation.js +26 -93
- package/calculations/core/social-sentiment-aggregation.js +20 -44
- package/calculations/core/social-top-mentioned-words.js +35 -87
- package/calculations/core/social-topic-interest-evolution.js +22 -111
- package/calculations/core/social-topic-sentiment-matrix.js +38 -104
- package/calculations/core/social-word-mentions-trend.js +27 -104
- package/calculations/core/speculator-asset-sentiment.js +31 -72
- package/calculations/core/speculator-danger-zone.js +48 -84
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
- package/calculations/core/speculator-leverage-per-asset.js +25 -64
- package/calculations/core/speculator-leverage-per-sector.js +27 -63
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
- package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
- package/calculations/core/speculator-take-profit-per-asset.js +20 -57
- package/calculations/core/speculator-tsl-per-asset.js +17 -56
- package/calculations/core/total-long-figures.js +16 -31
- package/calculations/core/total-long-per-sector.js +39 -61
- package/calculations/core/total-short-figures.js +13 -32
- package/calculations/core/total-short-per-sector.js +39 -61
- package/calculations/core/users-processed.js +11 -46
- package/calculations/gauss/cohort-capital-flow.js +54 -173
- package/calculations/gauss/cohort-definer.js +77 -163
- package/calculations/gauss/daily-dna-filter.js +29 -83
- package/calculations/gauss/gauss-divergence-signal.js +22 -109
- package/calculations/gem/cohort-momentum-state.js +27 -72
- package/calculations/gem/cohort-skill-definition.js +36 -52
- package/calculations/gem/platform-conviction-divergence.js +18 -60
- package/calculations/gem/quant-skill-alpha-signal.js +25 -98
- package/calculations/gem/skilled-cohort-flow.js +67 -175
- package/calculations/gem/skilled-unskilled-divergence.js +18 -73
- package/calculations/gem/unskilled-cohort-flow.js +64 -172
- package/calculations/helix/helix-contrarian-signal.js +20 -114
- package/calculations/helix/herd-consensus-score.js +42 -124
- package/calculations/helix/winner-loser-flow.js +36 -118
- package/calculations/pyro/risk-appetite-index.js +33 -74
- package/calculations/pyro/squeeze-potential.js +30 -87
- package/calculations/pyro/volatility-signal.js +33 -78
- package/package.json +1 -1
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* - Added defensive checks in '_loadDependencies' to prevent crash
|
|
5
|
-
* when dependencies fail to load (e.g., due to worker bug).
|
|
6
|
-
* - **FIXED:** Restored logic to calculate 'avg_position_change_pct'
|
|
7
|
-
* to resolve the hardcoded '0' value. This involves
|
|
8
|
-
* re-adding fields to '_initFlowData' and 'process'.
|
|
3
|
+
* REFACTORED: Uses context.computed and context.math.extract.
|
|
9
4
|
*/
|
|
10
|
-
|
|
11
5
|
class SkilledCohortFlow {
|
|
12
6
|
constructor() {
|
|
13
7
|
this.assetFlows = new Map();
|
|
14
8
|
this.cohortMap = new Map();
|
|
15
|
-
this.tickerMap = null;
|
|
16
9
|
this.dependenciesLoaded = false;
|
|
17
|
-
this.
|
|
10
|
+
this.tickerMap = null;
|
|
18
11
|
}
|
|
19
12
|
|
|
20
13
|
static getMetadata() {
|
|
@@ -28,10 +21,7 @@ class SkilledCohortFlow {
|
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
static getDependencies() {
|
|
31
|
-
return [
|
|
32
|
-
'cohort-skill-definition', // from gem (Pass 1)
|
|
33
|
-
'instrument-price-change-1d' // from core (Pass 1)
|
|
34
|
-
];
|
|
24
|
+
return ['cohort-skill-definition', 'instrument-price-change-1d'];
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
static getSchema() {
|
|
@@ -45,208 +35,110 @@ class SkilledCohortFlow {
|
|
|
45
35
|
},
|
|
46
36
|
"required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count"]
|
|
47
37
|
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"description": "Calculates capital flow and conviction change for the 'Skilled' cohort.",
|
|
52
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
53
|
-
"additionalProperties": tickerSchema
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
_getPortfolioPositions(portfolio) {
|
|
58
|
-
// --- FIX: Support both Normal (Aggregated) and Speculator (Public) ---
|
|
59
|
-
return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
38
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
60
39
|
}
|
|
61
40
|
|
|
62
|
-
// --- THIS IS THE FIX (Part 1) ---
|
|
63
41
|
_initFlowData(instrumentId) {
|
|
64
42
|
if (!this.assetFlows.has(instrumentId)) {
|
|
65
43
|
this.assetFlows.set(instrumentId, {
|
|
66
|
-
total_invested_yesterday: 0,
|
|
67
|
-
|
|
68
|
-
price_change_yesterday: 0,
|
|
69
|
-
// Restore fields needed for avg_position_change_pct
|
|
70
|
-
total_pos_size_yesterday: 0,
|
|
71
|
-
total_pos_size_today: 0,
|
|
72
|
-
user_count_yesterday: 0,
|
|
73
|
-
user_count_today: 0
|
|
44
|
+
total_invested_yesterday: 0, total_invested_today: 0, price_change_yesterday: 0,
|
|
45
|
+
total_pos_size_yesterday: 0, total_pos_size_today: 0, user_count_yesterday: 0, user_count_today: 0
|
|
74
46
|
});
|
|
75
47
|
}
|
|
76
48
|
}
|
|
77
|
-
// --- END FIX (Part 1) ---
|
|
78
49
|
|
|
79
|
-
_loadDependencies(
|
|
50
|
+
_loadDependencies(computed) {
|
|
80
51
|
if (this.dependenciesLoaded) return;
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
// This will stop the test and explain the root cause.
|
|
84
|
-
throw new Error(
|
|
85
|
-
`[skilled-cohort-flow] CRITICAL ERROR: fetchedDependencies object was UNDEFINED.
|
|
86
|
-
This means a dependency test (like 'cohort-skill-definition' or 'instrument-price-change-1d')
|
|
87
|
-
failed to run or produced an invalid result.`
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const cohortData = fetchedDependencies['cohort-skill-definition'];
|
|
92
|
-
if (!cohortData) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`[skilled-cohort-flow] DEPENDENCY ERROR: 'cohort-skill-definition' was missing.
|
|
95
|
-
Check logs for errors in that calculation.`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// This logic is correct for 'cohort-skill-definition' output
|
|
100
|
-
if (cohortData && cohortData.skilled_user_ids) {
|
|
52
|
+
const cohortData = computed['cohort-skill-definition'];
|
|
53
|
+
if (cohortData) {
|
|
101
54
|
(cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
102
55
|
}
|
|
103
|
-
|
|
104
|
-
this.priceChangeMap = fetchedDependencies['instrument-price-change-1d'];
|
|
105
|
-
if (!this.priceChangeMap) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`[skilled-cohort-flow] DEPENDENCY ERROR: 'instrument-price-change-1d' was missing.
|
|
108
|
-
This is likely due to the worker bug for 'price' dependencies.`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
56
|
this.dependenciesLoaded = true;
|
|
113
57
|
}
|
|
114
58
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!this.tickerMap)
|
|
121
|
-
this.tickerMap = context.instrumentToTicker;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const cohortName = this.cohortMap.get(String(userId)); // Ensure string ID
|
|
125
|
-
if (cohortName !== 'skilled') {
|
|
126
|
-
return; // Not in skilled cohort
|
|
127
|
-
}
|
|
59
|
+
process(context) {
|
|
60
|
+
const { user, computed, mappings, math } = context;
|
|
61
|
+
const { extract } = math;
|
|
62
|
+
|
|
63
|
+
this._loadDependencies(computed);
|
|
64
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
128
65
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
66
|
+
if (this.cohortMap.get(user.id) !== 'skilled') return;
|
|
67
|
+
|
|
68
|
+
const priceChangeMap = computed['instrument-price-change-1d'];
|
|
69
|
+
if (!priceChangeMap) return;
|
|
132
70
|
|
|
133
|
-
const yPos =
|
|
134
|
-
const tPos =
|
|
135
|
-
if (!yPos || !tPos) return;
|
|
71
|
+
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
72
|
+
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
136
73
|
|
|
137
|
-
const yPosMap = new Map(yPos.map(p => [p
|
|
138
|
-
const tPosMap = new Map(tPos.map(p => [p
|
|
74
|
+
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
75
|
+
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
139
76
|
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
140
77
|
|
|
141
|
-
for (const
|
|
142
|
-
if (!
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
const asset = this.assetFlows.get(instrumentId);
|
|
146
|
-
|
|
147
|
-
const yP = yPosMap.get(instrumentId);
|
|
148
|
-
const tP = tPosMap.get(instrumentId);
|
|
149
|
-
|
|
150
|
-
// Get $ flow
|
|
151
|
-
const yInvested = yP?.Invested || yP?.Amount || 0;
|
|
152
|
-
const tInvested = tP?.Invested || tP?.Amount || 0;
|
|
153
|
-
|
|
154
|
-
// Get position size (same as $ flow for these schemas)
|
|
155
|
-
const ySize = yP?.Invested || yP?.Amount || 0;
|
|
156
|
-
const tSize = tP?.Invested || tP?.Amount || 0;
|
|
157
|
-
|
|
158
|
-
if (yInvested > 0) {
|
|
159
|
-
asset.total_invested_yesterday += yInvested;
|
|
78
|
+
for (const instId of allInstrumentIds) {
|
|
79
|
+
if (!instId) continue;
|
|
80
|
+
this._initFlowData(instId);
|
|
81
|
+
const asset = this.assetFlows.get(instId);
|
|
160
82
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
? this.priceChangeMap[ticker].price_change_1d_pct
|
|
164
|
-
: 0;
|
|
83
|
+
const yWeight = extract.getPositionWeight(yPosMap.get(instId), user.type);
|
|
84
|
+
const tWeight = extract.getPositionWeight(tPosMap.get(instId), user.type);
|
|
165
85
|
|
|
166
|
-
|
|
167
|
-
asset.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
asset.
|
|
86
|
+
if (yWeight > 0) {
|
|
87
|
+
asset.total_invested_yesterday += yWeight;
|
|
88
|
+
const ticker = mappings.instrumentToTicker[instId];
|
|
89
|
+
const priceChange = (ticker && priceChangeMap[ticker]) ? priceChangeMap[ticker].change_1d_pct : 0;
|
|
90
|
+
asset.price_change_yesterday += (priceChange / 100.0) * yWeight;
|
|
91
|
+
asset.total_pos_size_yesterday += yWeight;
|
|
171
92
|
asset.user_count_yesterday++;
|
|
172
93
|
}
|
|
173
|
-
if (
|
|
174
|
-
asset.total_invested_today +=
|
|
175
|
-
asset.user_count_today++;
|
|
176
|
-
|
|
177
|
-
// Track today's size
|
|
178
|
-
asset.total_pos_size_today += tSize;
|
|
94
|
+
if (tWeight > 0) {
|
|
95
|
+
asset.total_invested_today += tWeight;
|
|
96
|
+
asset.user_count_today++;
|
|
97
|
+
asset.total_pos_size_today += tWeight;
|
|
179
98
|
}
|
|
180
99
|
}
|
|
181
100
|
}
|
|
182
|
-
// --- END FIX (Part 2) ---
|
|
183
101
|
|
|
184
|
-
// --- THIS IS THE FIX (Part 3) ---
|
|
185
102
|
async getResult() {
|
|
186
|
-
if (!this.tickerMap) {
|
|
187
|
-
return {};
|
|
188
|
-
}
|
|
189
|
-
|
|
103
|
+
if (!this.tickerMap) return {};
|
|
190
104
|
const finalResult = {};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const ticker = this.tickerMap[instrumentId];
|
|
105
|
+
for (const [instId, data] of this.assetFlows.entries()) {
|
|
106
|
+
const ticker = this.tickerMap[instId];
|
|
194
107
|
if (!ticker) continue;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (total_invested_yesterday > 0) {
|
|
207
|
-
const avg_price_change_decimal = (price_change_yesterday === 0) ? 0 : (price_change_yesterday / total_invested_yesterday);
|
|
208
|
-
const price_adjusted_yesterday_value = total_invested_yesterday * (1 + avg_price_change_decimal);
|
|
209
|
-
|
|
210
|
-
flow_contribution = total_invested_today - price_adjusted_yesterday_value;
|
|
211
|
-
net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
212
|
-
} else if (total_invested_today > 0) {
|
|
213
|
-
flow_contribution = total_invested_today;
|
|
214
|
-
net_flow_percentage = Infinity;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Restore the calculation logic for conviction
|
|
218
|
-
let avg_position_change_pct = 0;
|
|
219
|
-
if (user_count_yesterday > 0 && user_count_today > 0) {
|
|
220
|
-
const avg_pos_y = total_pos_size_yesterday / user_count_yesterday;
|
|
221
|
-
const avg_pos_t = total_pos_size_today / user_count_today;
|
|
222
|
-
if (avg_pos_y > 0) {
|
|
223
|
-
avg_position_change_pct = ((avg_pos_t - avg_pos_y) / avg_pos_y) * 100;
|
|
224
|
-
}
|
|
225
|
-
} else if (user_count_today > 0) {
|
|
226
|
-
// Pure inflow, conviction is max
|
|
227
|
-
avg_position_change_pct = Infinity;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (isFinite(net_flow_percentage) && isFinite(flow_contribution)) {
|
|
231
|
-
finalResult[ticker] = {
|
|
232
|
-
net_flow_pct: net_flow_percentage,
|
|
233
|
-
net_flow_contribution: flow_contribution,
|
|
234
|
-
avg_position_change_pct: avg_position_change_pct, // Now correctly calculated
|
|
235
|
-
user_count: user_count_today
|
|
236
|
-
};
|
|
108
|
+
// ... (Same calculation logic as before, just boilerplate math)
|
|
109
|
+
let net_flow = 0, flow_contrib = 0;
|
|
110
|
+
if (data.total_invested_yesterday > 0) {
|
|
111
|
+
const avg_change = data.price_change_yesterday / data.total_invested_yesterday;
|
|
112
|
+
const adjusted_y = data.total_invested_yesterday * (1 + avg_change);
|
|
113
|
+
flow_contrib = data.total_invested_today - adjusted_y;
|
|
114
|
+
net_flow = (flow_contrib / data.total_invested_yesterday) * 100;
|
|
115
|
+
} else if (data.total_invested_today > 0) {
|
|
116
|
+
flow_contrib = data.total_invested_today;
|
|
117
|
+
net_flow = Infinity;
|
|
237
118
|
}
|
|
119
|
+
|
|
120
|
+
let avg_pos_change = 0;
|
|
121
|
+
if (data.user_count_yesterday > 0 && data.user_count_today > 0) {
|
|
122
|
+
const avg_y = data.total_pos_size_yesterday / data.user_count_yesterday;
|
|
123
|
+
const avg_t = data.total_pos_size_today / data.user_count_today;
|
|
124
|
+
if (avg_y > 0) avg_pos_change = ((avg_t - avg_y) / avg_y) * 100;
|
|
125
|
+
} else if (data.user_count_today > 0) avg_pos_change = Infinity;
|
|
126
|
+
|
|
127
|
+
finalResult[ticker] = {
|
|
128
|
+
net_flow_pct: isFinite(net_flow) ? net_flow : 0,
|
|
129
|
+
net_flow_contribution: isFinite(flow_contrib) ? flow_contrib : 0,
|
|
130
|
+
avg_position_change_pct: isFinite(avg_pos_change) ? avg_pos_change : 0,
|
|
131
|
+
user_count: data.user_count_today
|
|
132
|
+
};
|
|
238
133
|
}
|
|
239
|
-
|
|
240
134
|
return finalResult;
|
|
241
135
|
}
|
|
242
|
-
// --- END FIX (Part 3) ---
|
|
243
136
|
|
|
244
137
|
reset() {
|
|
245
138
|
this.assetFlows.clear();
|
|
246
139
|
this.cohortMap.clear();
|
|
247
|
-
this.tickerMap = null;
|
|
248
140
|
this.dependenciesLoaded = false;
|
|
249
|
-
this.
|
|
141
|
+
this.tickerMap = null;
|
|
250
142
|
}
|
|
251
143
|
}
|
|
252
144
|
module.exports = SkilledCohortFlow;
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 3)
|
|
3
|
-
*
|
|
4
|
-
* This 'meta' calculation answers: "What is the net flow
|
|
5
|
-
* divergence between Skilled and Unskilled cohorts?"
|
|
3
|
+
* REFACTORED: Uses context.math.signals and process(context).
|
|
6
4
|
*/
|
|
7
5
|
class SkilledUnskilledDivergence {
|
|
6
|
+
constructor() { this.result = {}; }
|
|
8
7
|
|
|
9
|
-
// --- STANDARD 2: ADDED ---
|
|
10
|
-
constructor() {
|
|
11
|
-
this.result = {};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Statically defines metadata */
|
|
15
8
|
static getMetadata() {
|
|
16
9
|
return {
|
|
17
10
|
type: 'meta',
|
|
@@ -22,17 +15,8 @@ class SkilledUnskilledDivergence {
|
|
|
22
15
|
};
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return [
|
|
28
|
-
'skilled-cohort-flow', // from gem (Pass 2)
|
|
29
|
-
'unskilled-cohort-flow' // from gem (Pass 2)
|
|
30
|
-
];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Defines the output schema for this calculation.
|
|
35
|
-
*/
|
|
18
|
+
static getDependencies() { return ['skilled-cohort-flow', 'unskilled-cohort-flow']; }
|
|
19
|
+
|
|
36
20
|
static getSchema() {
|
|
37
21
|
const tickerSchema = {
|
|
38
22
|
"type": "object",
|
|
@@ -43,71 +27,32 @@ class SkilledUnskilledDivergence {
|
|
|
43
27
|
},
|
|
44
28
|
"required": ["skilled_flow_pct", "unskilled_flow_pct", "flow_divergence_score"]
|
|
45
29
|
};
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
"type": "object",
|
|
49
|
-
"description": "Tracks the net % flow divergence between skilled and unskilled cohorts.",
|
|
50
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
51
|
-
"additionalProperties": tickerSchema
|
|
52
|
-
};
|
|
30
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
53
31
|
}
|
|
54
32
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// --- STANDARD 1: UPDATED SIGNATURE (added rootData) ---
|
|
59
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
60
|
-
const { logger } = dependencies;
|
|
61
|
-
|
|
62
|
-
const skilledFlow = fetchedDependencies['skilled-cohort-flow'];
|
|
63
|
-
const unskilledFlow = fetchedDependencies['unskilled-cohort-flow'];
|
|
64
|
-
|
|
65
|
-
if (!skilledFlow || !unskilledFlow) {
|
|
66
|
-
logger.log('WARN', `[gem/skilled-unskilled-divergence] Missing dependencies for ${dateStr}.`);
|
|
67
|
-
// --- STANDARD 2: DO NOT RETURN ---
|
|
68
|
-
this.result = {};
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const allTickers = new Set([
|
|
73
|
-
...Object.keys(skilledFlow),
|
|
74
|
-
...Object.keys(unskilledFlow)
|
|
75
|
-
]);
|
|
33
|
+
process(context) {
|
|
34
|
+
const { computed, math } = context;
|
|
35
|
+
const { signals } = math;
|
|
76
36
|
|
|
37
|
+
const tickers = signals.getUnionKeys(computed, ['skilled-cohort-flow', 'unskilled-cohort-flow']);
|
|
77
38
|
const result = {};
|
|
78
39
|
|
|
79
|
-
for (const ticker of
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
// Get net_flow_pct
|
|
84
|
-
const skilled_flow_pct = skilledData?.net_flow_pct || 0;
|
|
85
|
-
const unskilled_flow_pct = unskilledData?.net_flow_pct || 0;
|
|
40
|
+
for (const ticker of tickers) {
|
|
41
|
+
const skilled = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct');
|
|
42
|
+
const unskilled = signals.getMetric(computed, 'unskilled-cohort-flow', ticker, 'net_flow_pct');
|
|
86
43
|
|
|
87
|
-
|
|
88
|
-
if (skilled_flow_pct === 0 && unskilled_flow_pct === 0) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
44
|
+
if (skilled === 0 && unskilled === 0) continue;
|
|
91
45
|
|
|
92
46
|
result[ticker] = {
|
|
93
|
-
skilled_flow_pct:
|
|
94
|
-
unskilled_flow_pct:
|
|
95
|
-
flow_divergence_score:
|
|
47
|
+
skilled_flow_pct: skilled,
|
|
48
|
+
unskilled_flow_pct: unskilled,
|
|
49
|
+
flow_divergence_score: signals.divergence(skilled, unskilled)
|
|
96
50
|
};
|
|
97
51
|
}
|
|
98
|
-
|
|
99
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
100
52
|
this.result = result;
|
|
101
53
|
}
|
|
102
54
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return this.result;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// --- STANDARD 2: ADDED ---
|
|
109
|
-
reset() {
|
|
110
|
-
this.result = {};
|
|
111
|
-
}
|
|
55
|
+
async getResult() { return this.result; }
|
|
56
|
+
reset() { this.result = {}; }
|
|
112
57
|
}
|
|
113
58
|
module.exports = SkilledUnskilledDivergence;
|