aiden-shared-calculations-unified 1.0.82 → 1.0.84
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 +122 -104
- package/calculations/core/asset-position-size.js +110 -73
- package/calculations/core/average-daily-pnl-all-users.js +17 -3
- package/calculations/core/average-daily-pnl-per-sector.js +83 -75
- package/calculations/core/average-daily-pnl-per-stock.js +84 -73
- package/calculations/core/average-daily-position-pnl.js +2 -2
- package/calculations/core/holding-duration-per-asset.js +24 -23
- package/calculations/core/instrument-price-change-1d.js +72 -82
- package/calculations/core/instrument-price-momentum-20d.js +66 -100
- package/calculations/core/long-position-per-stock.js +21 -13
- package/calculations/core/overall-holding-duration.js +8 -3
- package/calculations/core/overall-profitability-ratio.js +2 -2
- package/calculations/core/platform-buy-sell-sentiment.js +75 -22
- package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
- package/calculations/core/platform-daily-ownership-delta.js +39 -15
- package/calculations/core/platform-ownership-per-sector.js +38 -18
- package/calculations/core/platform-total-positions-held.js +36 -14
- package/calculations/core/pnl-distribution-per-stock.js +39 -36
- package/calculations/core/price-metrics.js +70 -172
- package/calculations/core/profitability-ratio-per-sector.js +23 -29
- package/calculations/core/profitability-ratio-per-stock.js +20 -13
- package/calculations/core/profitability-skew-per-stock.js +20 -13
- package/calculations/core/profitable-and-unprofitable-status.js +34 -10
- package/calculations/core/sentiment-per-stock.js +20 -9
- package/calculations/core/short-position-per-stock.js +23 -37
- package/calculations/core/social-activity-aggregation.js +41 -115
- package/calculations/core/social-asset-posts-trend.js +77 -94
- package/calculations/core/social-event-correlation.js +87 -106
- package/calculations/core/social-sentiment-aggregation.js +56 -138
- package/calculations/core/social-top-mentioned-words.js +74 -106
- package/calculations/core/social-topic-interest-evolution.js +94 -94
- package/calculations/core/social-topic-sentiment-matrix.js +90 -74
- package/calculations/core/social-word-mentions-trend.js +92 -106
- package/calculations/core/speculator-asset-sentiment.js +63 -92
- package/calculations/core/speculator-danger-zone.js +77 -90
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
- package/calculations/core/speculator-leverage-per-asset.js +62 -57
- package/calculations/core/speculator-leverage-per-sector.js +53 -65
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
- package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
- package/calculations/core/speculator-take-profit-per-asset.js +45 -69
- package/calculations/core/speculator-tsl-per-asset.js +42 -49
- package/calculations/core/total-long-figures.js +19 -19
- package/calculations/core/total-long-per-sector.js +39 -36
- package/calculations/core/total-short-figures.js +19 -19
- package/calculations/core/total-short-per-sector.js +39 -36
- package/calculations/core/users-processed.js +52 -25
- package/calculations/gauss/cohort-capital-flow.js +38 -29
- package/calculations/gauss/cohort-definer.js +17 -25
- package/calculations/gauss/daily-dna-filter.js +10 -4
- package/calculations/gauss/gauss-divergence-signal.js +28 -6
- package/calculations/gem/cohort-momentum-state.js +113 -92
- package/calculations/gem/cohort-skill-definition.js +23 -53
- package/calculations/gem/platform-conviction-divergence.js +62 -116
- package/calculations/gem/quant-skill-alpha-signal.js +107 -123
- package/calculations/gem/skilled-cohort-flow.js +178 -167
- package/calculations/gem/skilled-unskilled-divergence.js +73 -113
- package/calculations/gem/unskilled-cohort-flow.js +176 -166
- package/calculations/helix/helix-contrarian-signal.js +91 -83
- package/calculations/helix/herd-consensus-score.js +135 -97
- package/calculations/helix/winner-loser-flow.js +14 -14
- package/calculations/pyro/risk-appetite-index.js +121 -123
- package/calculations/pyro/squeeze-potential.js +93 -125
- package/calculations/pyro/volatility-signal.js +109 -97
- package/package.json +9 -9
- package/README.MD +0 -78
|
@@ -1,241 +1,252 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* to
|
|
2
|
+
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
+
* --- FIX ---
|
|
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'.
|
|
9
9
|
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
|
-
|
|
12
10
|
|
|
13
11
|
class SkilledCohortFlow {
|
|
14
12
|
constructor() {
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Defines the output schema for this calculation.
|
|
23
|
-
* @returns {object} JSON Schema object
|
|
24
|
-
*/
|
|
25
|
-
static getSchema() {
|
|
26
|
-
const flowSchema = {
|
|
27
|
-
"type": "object",
|
|
28
|
-
"properties": {
|
|
29
|
-
"net_flow_percentage": { "type": "number" },
|
|
30
|
-
"total_invested_today": { "type": "number" },
|
|
31
|
-
"total_invested_yesterday": { "type": "number" }
|
|
32
|
-
},
|
|
33
|
-
"required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
"type": "object",
|
|
38
|
-
"description": "Calculates net capital flow % (price-adjusted) for the 'Skilled Cohort' (top 20% by skill score), aggregated by asset and sector.",
|
|
39
|
-
"properties": {
|
|
40
|
-
"cohort_size": {
|
|
41
|
-
"type": "number",
|
|
42
|
-
"description": "The number of users identified as being in the Skilled Cohort."
|
|
43
|
-
},
|
|
44
|
-
"assets": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"description": "Price-adjusted net flow per asset.",
|
|
47
|
-
"patternProperties": { "^.*$": flowSchema }, // Ticker
|
|
48
|
-
"additionalProperties": flowSchema
|
|
49
|
-
},
|
|
50
|
-
"sectors": {
|
|
51
|
-
"type": "object",
|
|
52
|
-
"description": "Price-adjusted net flow per sector.",
|
|
53
|
-
"patternProperties": { "^.*$": flowSchema }, // Sector
|
|
54
|
-
"additionalProperties": flowSchema
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"required": ["cohort_size", "assets", "sectors"]
|
|
58
|
-
};
|
|
13
|
+
this.assetFlows = new Map();
|
|
14
|
+
this.cohortMap = new Map();
|
|
15
|
+
this.tickerMap = null;
|
|
16
|
+
this.dependenciesLoaded = false;
|
|
17
|
+
this.priceChangeMap = null;
|
|
59
18
|
}
|
|
60
19
|
|
|
61
|
-
/**
|
|
62
|
-
* Statically defines all metadata for the manifest builder.
|
|
63
|
-
*/
|
|
64
20
|
static getMetadata() {
|
|
65
21
|
return {
|
|
66
22
|
type: 'standard',
|
|
67
23
|
rootDataDependencies: ['portfolio'],
|
|
68
|
-
isHistorical: true,
|
|
24
|
+
isHistorical: true,
|
|
69
25
|
userType: 'all',
|
|
70
26
|
category: 'gem'
|
|
71
27
|
};
|
|
72
28
|
}
|
|
73
29
|
|
|
74
|
-
/**
|
|
75
|
-
* Statically declare dependencies.
|
|
76
|
-
*/
|
|
77
30
|
static getDependencies() {
|
|
78
|
-
return [
|
|
31
|
+
return [
|
|
32
|
+
'cohort-skill-definition', // from gem (Pass 1)
|
|
33
|
+
'instrument-price-change-1d' // from core (Pass 1)
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static getSchema() {
|
|
38
|
+
const tickerSchema = {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"net_flow_pct": { "type": "number" },
|
|
42
|
+
"net_flow_contribution": { "type": "number" },
|
|
43
|
+
"avg_position_change_pct": { "type": "number" },
|
|
44
|
+
"user_count": { "type": "number" }
|
|
45
|
+
},
|
|
46
|
+
"required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count"]
|
|
47
|
+
};
|
|
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
|
+
};
|
|
79
55
|
}
|
|
80
56
|
|
|
81
57
|
_getPortfolioPositions(portfolio) {
|
|
82
|
-
|
|
58
|
+
// --- FIX: Support both Normal (Aggregated) and Speculator (Public) ---
|
|
59
|
+
return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
83
60
|
}
|
|
84
61
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
62
|
+
// --- THIS IS THE FIX (Part 1) ---
|
|
63
|
+
_initFlowData(instrumentId) {
|
|
64
|
+
if (!this.assetFlows.has(instrumentId)) {
|
|
65
|
+
this.assetFlows.set(instrumentId, {
|
|
88
66
|
total_invested_yesterday: 0,
|
|
89
67
|
total_invested_today: 0,
|
|
90
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
|
|
91
74
|
});
|
|
92
75
|
}
|
|
93
76
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
// --- END FIX (Part 1) ---
|
|
78
|
+
|
|
79
|
+
_loadDependencies(fetchedDependencies) {
|
|
80
|
+
if (this.dependenciesLoaded) return;
|
|
81
|
+
|
|
82
|
+
if (!fetchedDependencies) {
|
|
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
|
+
);
|
|
102
89
|
}
|
|
103
|
-
}
|
|
104
90
|
|
|
105
|
-
_getSkilledCohort(fetchedDependencies) {
|
|
106
|
-
if (this.skilledCohortUserIds) {
|
|
107
|
-
return this.skilledCohortUserIds;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// FIX: Use normalized dependency name
|
|
111
91
|
const cohortData = fetchedDependencies['cohort-skill-definition'];
|
|
112
|
-
if (!cohortData
|
|
113
|
-
|
|
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) {
|
|
101
|
+
(cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
102
|
+
}
|
|
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
|
+
);
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
this.
|
|
117
|
-
return this.skilledCohortUserIds;
|
|
112
|
+
this.dependenciesLoaded = true;
|
|
118
113
|
}
|
|
119
114
|
|
|
115
|
+
// --- THIS IS THE FIX (Part 2) ---
|
|
120
116
|
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
// Load dependencies *before* checking cohort
|
|
118
|
+
this._loadDependencies(fetchedDependencies);
|
|
119
|
+
|
|
120
|
+
if (!this.tickerMap) {
|
|
121
|
+
this.tickerMap = context.instrumentToTicker;
|
|
125
122
|
}
|
|
126
123
|
|
|
127
|
-
|
|
124
|
+
const cohortName = this.cohortMap.get(String(userId)); // Ensure string ID
|
|
125
|
+
if (cohortName !== 'skilled') {
|
|
126
|
+
return; // Not in skilled cohort
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!todayPortfolio || !yesterdayPortfolio || !this.priceChangeMap || !this.tickerMap) {
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
// Logic from here is identical to smart-cohort-flow.js
|
|
132
133
|
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
133
134
|
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
if (!yPos || !tPos) return;
|
|
136
|
+
|
|
137
|
+
const yPosMap = new Map(yPos.map(p => [p.InstrumentID, p]));
|
|
138
|
+
const tPosMap = new Map(tPos.map(p => [p.InstrumentID, p]));
|
|
136
139
|
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
137
|
-
|
|
138
|
-
if (!this.mappings) {
|
|
139
|
-
this.mappings = context.mappings;
|
|
140
|
-
}
|
|
141
140
|
|
|
142
141
|
for (const instrumentId of allInstrumentIds) {
|
|
143
142
|
if (!instrumentId) continue;
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
const asset = this.
|
|
143
|
+
|
|
144
|
+
this._initFlowData(instrumentId);
|
|
145
|
+
const asset = this.assetFlows.get(instrumentId);
|
|
146
|
+
|
|
147
147
|
const yP = yPosMap.get(instrumentId);
|
|
148
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;
|
|
149
153
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
155
|
-
this._initSector(sector);
|
|
156
|
-
const sectorAsset = this.sectorData.get(sector);
|
|
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
157
|
|
|
158
158
|
if (yInvested > 0) {
|
|
159
|
-
// Use P&L % from portfolio 'NetProfit'
|
|
160
|
-
const yPriceChange = (yP?.NetProfit || 0) / (yP?.Invested || 1);
|
|
161
|
-
|
|
162
159
|
asset.total_invested_yesterday += yInvested;
|
|
163
|
-
|
|
160
|
+
|
|
161
|
+
const ticker = this.tickerMap[instrumentId];
|
|
162
|
+
const yPriceChange_pct = (ticker && this.priceChangeMap[ticker])
|
|
163
|
+
? this.priceChangeMap[ticker].price_change_1d_pct
|
|
164
|
+
: 0;
|
|
165
|
+
|
|
166
|
+
const yPriceChange_decimal = (yPriceChange_pct || 0) / 100.0;
|
|
167
|
+
asset.price_change_yesterday += yPriceChange_decimal * yInvested;
|
|
164
168
|
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
// Track yesterday's size and user count
|
|
170
|
+
asset.total_pos_size_yesterday += ySize;
|
|
171
|
+
asset.user_count_yesterday++;
|
|
167
172
|
}
|
|
168
173
|
if (tInvested > 0) {
|
|
169
174
|
asset.total_invested_today += tInvested;
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
_calculateFlow(dataMap) {
|
|
176
|
-
const result = {};
|
|
177
|
-
for (const [key, data] of dataMap.entries()) {
|
|
178
|
-
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
179
|
-
|
|
180
|
-
if (total_invested_yesterday > 0) {
|
|
181
|
-
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
182
|
-
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
183
|
-
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
184
|
-
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
175
|
+
asset.user_count_today++; // Count users holding today
|
|
185
176
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
total_invested_today: total_invested_today,
|
|
189
|
-
total_invested_yesterday: total_invested_yesterday
|
|
190
|
-
};
|
|
177
|
+
// Track today's size
|
|
178
|
+
asset.total_pos_size_today += tSize;
|
|
191
179
|
}
|
|
192
180
|
}
|
|
193
|
-
return result;
|
|
194
181
|
}
|
|
182
|
+
// --- END FIX (Part 2) ---
|
|
195
183
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
// --- THIS IS THE FIX (Part 3) ---
|
|
185
|
+
async getResult() {
|
|
186
|
+
if (!this.tickerMap) {
|
|
187
|
+
return {};
|
|
199
188
|
}
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const {
|
|
208
|
-
|
|
189
|
+
|
|
190
|
+
const finalResult = {};
|
|
191
|
+
|
|
192
|
+
for (const [instrumentId, data] of this.assetFlows.entries()) {
|
|
193
|
+
const ticker = this.tickerMap[instrumentId];
|
|
194
|
+
if (!ticker) continue;
|
|
195
|
+
|
|
196
|
+
const {
|
|
197
|
+
total_invested_yesterday, total_invested_today, price_change_yesterday,
|
|
198
|
+
// Destructure the restored fields
|
|
199
|
+
total_pos_size_yesterday, total_pos_size_today,
|
|
200
|
+
user_count_yesterday, user_count_today
|
|
201
|
+
} = data;
|
|
202
|
+
|
|
203
|
+
let net_flow_percentage = 0;
|
|
204
|
+
let flow_contribution = 0;
|
|
205
|
+
|
|
209
206
|
if (total_invested_yesterday > 0) {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
219
236
|
};
|
|
220
237
|
}
|
|
221
238
|
}
|
|
222
|
-
|
|
223
|
-
// 2. Calculate Sector Flow
|
|
224
|
-
const sectorResult = this._calculateFlow(this.sectorData);
|
|
225
239
|
|
|
226
|
-
return
|
|
227
|
-
cohort_size: skilledCohort.size,
|
|
228
|
-
assets: assetResult,
|
|
229
|
-
sectors: sectorResult
|
|
230
|
-
};
|
|
240
|
+
return finalResult;
|
|
231
241
|
}
|
|
242
|
+
// --- END FIX (Part 3) ---
|
|
232
243
|
|
|
233
244
|
reset() {
|
|
234
|
-
this.
|
|
235
|
-
this.
|
|
236
|
-
this.
|
|
237
|
-
this.
|
|
245
|
+
this.assetFlows.clear();
|
|
246
|
+
this.cohortMap.clear();
|
|
247
|
+
this.tickerMap = null;
|
|
248
|
+
this.dependenciesLoaded = false;
|
|
249
|
+
this.priceChangeMap = null;
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
|
-
|
|
241
252
|
module.exports = SkilledCohortFlow;
|
|
@@ -1,153 +1,113 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview GEM Product Line (Pass 3)
|
|
3
3
|
*
|
|
4
|
-
* This
|
|
5
|
-
*
|
|
6
|
-
* of the 'Skilled Cohort' vs. the 'Unskilled Cohort'?"
|
|
7
|
-
*
|
|
8
|
-
* It *depends* on 'skilled-cohort-flow' and 'unskilled-cohort-flow'.
|
|
4
|
+
* This 'meta' calculation answers: "What is the net flow
|
|
5
|
+
* divergence between Skilled and Unskilled cohorts?"
|
|
9
6
|
*/
|
|
10
7
|
class SkilledUnskilledDivergence {
|
|
8
|
+
|
|
9
|
+
// --- STANDARD 2: ADDED ---
|
|
11
10
|
constructor() {
|
|
12
|
-
|
|
11
|
+
this.result = {};
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
/** Statically defines metadata */
|
|
15
|
+
static getMetadata() {
|
|
16
|
+
return {
|
|
17
|
+
type: 'meta',
|
|
18
|
+
rootDataDependencies: [],
|
|
19
|
+
isHistorical: false,
|
|
20
|
+
userType: 'n/a',
|
|
21
|
+
category: 'gem'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Statically declare dependencies */
|
|
26
|
+
static getDependencies() {
|
|
27
|
+
return [
|
|
28
|
+
'skilled-cohort-flow', // from gem (Pass 2)
|
|
29
|
+
'unskilled-cohort-flow' // from gem (Pass 2)
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
/**
|
|
16
34
|
* Defines the output schema for this calculation.
|
|
17
|
-
* @returns {object} JSON Schema object
|
|
18
35
|
*/
|
|
19
36
|
static getSchema() {
|
|
20
|
-
const
|
|
37
|
+
const tickerSchema = {
|
|
21
38
|
"type": "object",
|
|
22
39
|
"properties": {
|
|
23
|
-
"status": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"enum": ["Capitulation", "Euphoria", "Confirmation (Buy)", "Confirmation (Sell)", "Divergence (Skilled Buy)", "Divergence (Skilled Sell)", "Neutral"]
|
|
26
|
-
},
|
|
27
40
|
"skilled_flow_pct": { "type": "number" },
|
|
28
|
-
"unskilled_flow_pct": { "type": "number" }
|
|
41
|
+
"unskilled_flow_pct": { "type": "number" },
|
|
42
|
+
"flow_divergence_score": { "type": "number" }
|
|
29
43
|
},
|
|
30
|
-
"required": ["
|
|
44
|
+
"required": ["skilled_flow_pct", "unskilled_flow_pct", "flow_divergence_score"]
|
|
31
45
|
};
|
|
32
|
-
|
|
46
|
+
|
|
33
47
|
return {
|
|
34
48
|
"type": "object",
|
|
35
|
-
"description": "
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"type": "object",
|
|
39
|
-
"description": "Divergence signals per asset.",
|
|
40
|
-
"patternProperties": { "^.*$": signalSchema }, // Ticker
|
|
41
|
-
"additionalProperties": signalSchema
|
|
42
|
-
},
|
|
43
|
-
"sectors": {
|
|
44
|
-
"type": "object",
|
|
45
|
-
"description": "Divergence signals per sector.",
|
|
46
|
-
"patternProperties": { "^.*$": signalSchema }, // Sector
|
|
47
|
-
"additionalProperties": signalSchema
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"required": ["assets", "sectors"]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Statically defines all metadata for the manifest builder.
|
|
56
|
-
*/
|
|
57
|
-
static getMetadata() {
|
|
58
|
-
return {
|
|
59
|
-
type: 'meta',
|
|
60
|
-
rootDataDependencies: [],
|
|
61
|
-
isHistorical: false,
|
|
62
|
-
userType: 'n/a',
|
|
63
|
-
category: 'gem'
|
|
49
|
+
"description": "Tracks the net % flow divergence between skilled and unskilled cohorts.",
|
|
50
|
+
"patternProperties": { "^.*$": tickerSchema },
|
|
51
|
+
"additionalProperties": tickerSchema
|
|
64
52
|
};
|
|
65
53
|
}
|
|
66
54
|
|
|
67
55
|
/**
|
|
68
|
-
*
|
|
56
|
+
* This is a 'meta' calculation. It runs once.
|
|
69
57
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
];
|
|
75
|
-
|
|
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'];
|
|
76
64
|
|
|
77
|
-
process() {
|
|
78
|
-
// No-op
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
_calculateDivergence(skilledFlow, unskilledFlow) {
|
|
82
|
-
const result = {};
|
|
83
65
|
if (!skilledFlow || !unskilledFlow) {
|
|
84
|
-
|
|
66
|
+
logger.log('WARN', `[gem/skilled-unskilled-divergence] Missing dependencies for ${dateStr}.`);
|
|
67
|
+
// --- STANDARD 2: DO NOT RETURN ---
|
|
68
|
+
this.result = {};
|
|
69
|
+
return;
|
|
85
70
|
}
|
|
86
|
-
|
|
87
|
-
const allKeys = new Set([...Object.keys(skilledFlow), ...Object.keys(unskilledFlow)]);
|
|
88
|
-
const THRESHOLD = 1.0; // Min flow % to register as a signal
|
|
89
71
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
72
|
+
const allTickers = new Set([
|
|
73
|
+
...Object.keys(skilledFlow),
|
|
74
|
+
...Object.keys(unskilledFlow)
|
|
75
|
+
]);
|
|
93
76
|
|
|
94
|
-
|
|
77
|
+
const result = {};
|
|
95
78
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
// Both selling
|
|
101
|
-
else if (sFlow < -THRESHOLD && dFlow < -THRESHOLD) {
|
|
102
|
-
status = 'Confirmation (Sell)';
|
|
103
|
-
}
|
|
104
|
-
// Skilled buying, Unskilled selling
|
|
105
|
-
else if (sFlow > THRESHOLD && dFlow < -THRESHOLD) {
|
|
106
|
-
status = 'Capitulation'; // Skilled buying the dip from unskilled
|
|
107
|
-
}
|
|
108
|
-
// Skilled selling, Unskilled buying
|
|
109
|
-
else if (sFlow < -THRESHOLD && dFlow > THRESHOLD) {
|
|
110
|
-
status = 'Euphoria'; // Skilled selling into unskilled fomo
|
|
111
|
-
}
|
|
112
|
-
// Skilled buying, Unskilled neutral
|
|
113
|
-
else if (sFlow > THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
|
|
114
|
-
status = 'Divergence (Skilled Buy)';
|
|
115
|
-
}
|
|
116
|
-
// Skilled selling, Unskilled neutral
|
|
117
|
-
else if (sFlow < -THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
|
|
118
|
-
status = 'Divergence (Skilled Sell)';
|
|
119
|
-
}
|
|
79
|
+
for (const ticker of allTickers) {
|
|
80
|
+
const skilledData = skilledFlow[ticker];
|
|
81
|
+
const unskilledData = unskilledFlow[ticker];
|
|
120
82
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
|
86
|
+
|
|
87
|
+
// Only include assets that are actively being traded
|
|
88
|
+
if (skilled_flow_pct === 0 && unskilled_flow_pct === 0) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
result[ticker] = {
|
|
93
|
+
skilled_flow_pct: skilled_flow_pct,
|
|
94
|
+
unskilled_flow_pct: unskilled_flow_pct,
|
|
95
|
+
flow_divergence_score: skilled_flow_pct - unskilled_flow_pct
|
|
125
96
|
};
|
|
126
97
|
}
|
|
127
|
-
|
|
98
|
+
|
|
99
|
+
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
100
|
+
this.result = result;
|
|
128
101
|
}
|
|
129
102
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
*/
|
|
134
|
-
getResult(fetchedDependencies) {
|
|
135
|
-
// FIX: Use normalized dependency names
|
|
136
|
-
const skilledFlowData = fetchedDependencies['skilled-cohort-flow'];
|
|
137
|
-
const unskilledFlowData = fetchedDependencies['unskilled-cohort-flow'];
|
|
138
|
-
|
|
139
|
-
const assetResult = this._calculateDivergence(skilledFlowData?.assets, unskilledFlowData?.assets);
|
|
140
|
-
const sectorResult = this._calculateDivergence(skilledFlowData?.sectors, unskilledFlowData?.sectors);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
assets: assetResult,
|
|
144
|
-
sectors: sectorResult
|
|
145
|
-
};
|
|
103
|
+
// --- STANDARD 2: ADDED ---
|
|
104
|
+
async getResult(fetchedDependencies) {
|
|
105
|
+
return this.result;
|
|
146
106
|
}
|
|
147
107
|
|
|
108
|
+
// --- STANDARD 2: ADDED ---
|
|
148
109
|
reset() {
|
|
149
|
-
|
|
110
|
+
this.result = {};
|
|
150
111
|
}
|
|
151
112
|
}
|
|
152
|
-
|
|
153
113
|
module.exports = SkilledUnskilledDivergence;
|