aiden-shared-calculations-unified 1.0.79 → 1.0.81
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 +21 -42
- package/calculations/core/social-sentiment-aggregation.js +12 -6
- package/calculations/gauss/cohort-capital-flow.js +2 -1
- package/calculations/gauss/cohort-definer.js +2 -1
- package/calculations/helix/winner-loser-flow.js +29 -46
- package/calculations/legacy/in_loss_asset_crowd_flow.js +19 -49
- package/calculations/legacy/in_profit_asset_crowd_flow.js +20 -50
- package/package.json +1 -1
|
@@ -7,68 +7,46 @@
|
|
|
7
7
|
* This provides a crowd-wide P&L status for each instrument.
|
|
8
8
|
*
|
|
9
9
|
* --- FIX ---
|
|
10
|
-
* This version is modified to only store
|
|
11
|
-
*
|
|
12
|
-
* 1 MiB Firestore document size limit.
|
|
10
|
+
* This version is modified to *only* store counts. The
|
|
11
|
+
* `users_in_profit` and `users_in_loss` arrays are removed
|
|
12
|
+
* to prevent exceeding the 1 MiB Firestore document size limit.
|
|
13
13
|
*/
|
|
14
14
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
15
15
|
|
|
16
16
|
class AssetPnlStatus {
|
|
17
17
|
constructor() {
|
|
18
|
-
// We will store { [instrumentId]: {
|
|
19
|
-
//
|
|
18
|
+
// We will store { [instrumentId]: { in_profit_count: 0, in_loss_count: 0 } }
|
|
19
|
+
// We no longer store the user maps, just the counts.
|
|
20
20
|
this.assets = new Map();
|
|
21
21
|
this.mappings = null;
|
|
22
|
+
this.seenUsers = new Map(); // Map<instrumentId, Set<userId>>
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Defines the output schema for this calculation.
|
|
26
27
|
* --- MODIFIED ---
|
|
27
|
-
*
|
|
28
|
-
* from `items: userSchema` to `items: { "type": "string" }`.
|
|
28
|
+
* Removed the `users_in_profit` and `users_in_loss` arrays.
|
|
29
29
|
* @returns {object} JSON Schema object
|
|
30
30
|
*/
|
|
31
31
|
static getSchema() {
|
|
32
|
-
/*
|
|
33
|
-
// The userSchema is no longer needed in the output
|
|
34
|
-
const userSchema = {
|
|
35
|
-
"type": "object",
|
|
36
|
-
"properties": {
|
|
37
|
-
"userId": { "type": "string" },
|
|
38
|
-
"pnl": { "type": "number" }
|
|
39
|
-
},
|
|
40
|
-
"required": ["userId", "pnl"]
|
|
41
|
-
};
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
32
|
const tickerSchema = {
|
|
45
33
|
"type": "object",
|
|
46
34
|
"description": "P&L status for a specific asset.",
|
|
47
35
|
"properties": {
|
|
48
36
|
"in_profit_count": {
|
|
49
37
|
"type": "number",
|
|
50
|
-
"description": "Count of users currently in profit on this asset."
|
|
38
|
+
"description": "Count of unique users currently in profit on this asset."
|
|
51
39
|
},
|
|
52
40
|
"in_loss_count": {
|
|
53
41
|
"type": "number",
|
|
54
|
-
"description": "Count of users currently in loss on this asset."
|
|
42
|
+
"description": "Count of unique users currently in loss on this asset."
|
|
55
43
|
},
|
|
56
44
|
"profit_ratio": {
|
|
57
45
|
"type": "number",
|
|
58
46
|
"description": "Percentage of users in profit (In Profit / Total)."
|
|
59
|
-
},
|
|
60
|
-
"users_in_profit": {
|
|
61
|
-
"type": "array",
|
|
62
|
-
"description": "List of user IDs in profit.",
|
|
63
|
-
"items": { "type": "string" } // <-- MODIFIED
|
|
64
|
-
},
|
|
65
|
-
"users_in_loss": {
|
|
66
|
-
"type": "array",
|
|
67
|
-
"description": "List of user IDs in loss.",
|
|
68
|
-
"items": { "type": "string" } // <-- MODIFIED
|
|
69
47
|
}
|
|
70
48
|
},
|
|
71
|
-
"required": ["in_profit_count", "in_loss_count", "profit_ratio"
|
|
49
|
+
"required": ["in_profit_count", "in_loss_count", "profit_ratio"]
|
|
72
50
|
};
|
|
73
51
|
|
|
74
52
|
return {
|
|
@@ -104,8 +82,8 @@ class AssetPnlStatus {
|
|
|
104
82
|
_initAsset(instrumentId) {
|
|
105
83
|
if (!this.assets.has(instrumentId)) {
|
|
106
84
|
this.assets.set(instrumentId, {
|
|
107
|
-
in_profit: new
|
|
108
|
-
in_loss: new
|
|
85
|
+
in_profit: new Set(),
|
|
86
|
+
in_loss: new Set()
|
|
109
87
|
});
|
|
110
88
|
}
|
|
111
89
|
}
|
|
@@ -124,18 +102,20 @@ class AssetPnlStatus {
|
|
|
124
102
|
const asset = this.assets.get(instrumentId);
|
|
125
103
|
const pnl = pos.NetProfit || 0;
|
|
126
104
|
|
|
105
|
+
// Only count one user once per asset
|
|
127
106
|
if (pnl > 0) {
|
|
128
|
-
asset.in_profit.
|
|
107
|
+
asset.in_profit.add(userId);
|
|
108
|
+
asset.in_loss.delete(userId); // Ensure user isn't in both
|
|
129
109
|
} else if (pnl < 0) {
|
|
130
|
-
asset.in_loss.
|
|
110
|
+
asset.in_loss.add(userId);
|
|
111
|
+
asset.in_profit.delete(userId); // Ensure user isn't in both
|
|
131
112
|
}
|
|
132
113
|
}
|
|
133
114
|
}
|
|
134
115
|
|
|
135
116
|
/**
|
|
136
117
|
* --- MODIFIED ---
|
|
137
|
-
* This now saves
|
|
138
|
-
* an array of {userId, pnl} objects to save space.
|
|
118
|
+
* This now saves only counts.
|
|
139
119
|
*/
|
|
140
120
|
async getResult() {
|
|
141
121
|
if (!this.mappings) {
|
|
@@ -154,10 +134,8 @@ class AssetPnlStatus {
|
|
|
154
134
|
result[ticker] = {
|
|
155
135
|
in_profit_count: profitCount,
|
|
156
136
|
in_loss_count: lossCount,
|
|
157
|
-
profit_ratio: (profitCount / total) * 100
|
|
158
|
-
//
|
|
159
|
-
users_in_profit: Array.from(data.in_profit.keys()), // <-- MODIFIED
|
|
160
|
-
users_in_loss: Array.from(data.in_loss.keys()) // <-- MODIFIED
|
|
137
|
+
profit_ratio: (profitCount / total) * 100
|
|
138
|
+
// Removed the user arrays
|
|
161
139
|
};
|
|
162
140
|
}
|
|
163
141
|
}
|
|
@@ -167,6 +145,7 @@ class AssetPnlStatus {
|
|
|
167
145
|
reset() {
|
|
168
146
|
this.assets.clear();
|
|
169
147
|
this.mappings = null;
|
|
148
|
+
this.seenUsers.clear();
|
|
170
149
|
}
|
|
171
150
|
}
|
|
172
151
|
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* This metric answers: "What is the aggregated social media
|
|
5
5
|
* sentiment (bullish, bearish, neutral) globally and
|
|
6
6
|
* for each ticker?"
|
|
7
|
+
*
|
|
8
|
+
* --- MODIFIED ---
|
|
9
|
+
* Changed `process` method to read from `dependencies.rootData.todaySocialPostInsights`
|
|
10
|
+
* instead of a non-existent `calculationUtils.loadSocialData`.
|
|
7
11
|
*/
|
|
8
12
|
class SocialSentimentAggregation {
|
|
9
13
|
constructor() {
|
|
@@ -90,19 +94,21 @@ class SocialSentimentAggregation {
|
|
|
90
94
|
|
|
91
95
|
/**
|
|
92
96
|
* @param {string} dateStr - Today's date.
|
|
93
|
-
* @param {object} dependencies - db, logger.
|
|
97
|
+
* @param {object} dependencies - db, logger, calculationUtils, AND rootData.
|
|
94
98
|
* @param {object} config - Computation config.
|
|
95
99
|
* @param {object} fetchedDependencies - (UNUSED) In-memory results.
|
|
96
100
|
*/
|
|
97
101
|
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
98
|
-
|
|
99
|
-
//
|
|
100
|
-
const todaySocialPosts =
|
|
102
|
+
// --- MODIFIED ---
|
|
103
|
+
// 'meta' calcs get rootData injected into the dependencies object.
|
|
104
|
+
const todaySocialPosts = dependencies.rootData?.todaySocialPostInsights || {};
|
|
105
|
+
// --- END MODIFIED ---
|
|
101
106
|
|
|
102
|
-
for (const post of todaySocialPosts) {
|
|
107
|
+
for (const post of Object.values(todaySocialPosts)) {
|
|
103
108
|
// This logic assumes 'sentiment' is a simple string,
|
|
104
109
|
// not the object from gemini. This is correct for this calc.
|
|
105
|
-
|
|
110
|
+
// --- MODIFIED: Read from structured sentiment object ---
|
|
111
|
+
const sentiment = post.sentiment?.overallSentiment?.toLowerCase() || 'neutral';
|
|
106
112
|
|
|
107
113
|
// 1. Aggregate global sentiment
|
|
108
114
|
if (sentiment === 'bullish') {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* from the new dependency.
|
|
15
15
|
* --------------------------
|
|
16
16
|
*/
|
|
17
|
-
const { loadInstrumentMappings } = require('
|
|
17
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
18
|
+
|
|
18
19
|
|
|
19
20
|
class CohortCapitalFlow {
|
|
20
21
|
constructor() {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* It then buckets these users into our final, named sub-cohorts
|
|
9
9
|
* (e.g., "Smart Investors", "FOMO Chasers").
|
|
10
10
|
*/
|
|
11
|
-
const { loadInstrumentMappings } = require('
|
|
11
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
class CohortDefiner {
|
|
14
15
|
constructor() {
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
* of *unique users* into the 'Winner' (in-profit) and 'Loser'
|
|
6
6
|
* (in-loss) cohorts?"
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* users to compare their T-1 vs T portfolios.
|
|
8
|
+
* --- MODIFIED ---
|
|
9
|
+
* Removed dependency on 'asset-pnl-status' to fix 1MiB limit.
|
|
10
|
+
* This calculation now determines "winner/loser" status internally
|
|
11
|
+
* by reading the user's portfolio P&L directly.
|
|
13
12
|
*/
|
|
14
13
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
15
14
|
|
|
@@ -18,7 +17,6 @@ class WinnerLoserFlow {
|
|
|
18
17
|
constructor() {
|
|
19
18
|
// We will store { [ticker]: { winners_joined: 0, ... } }
|
|
20
19
|
this.assetFlows = new Map();
|
|
21
|
-
this.cohorts = null; // Caches cohort data
|
|
22
20
|
this.mappings = null; // Caches mappings
|
|
23
21
|
}
|
|
24
22
|
|
|
@@ -76,46 +74,30 @@ class WinnerLoserFlow {
|
|
|
76
74
|
|
|
77
75
|
/**
|
|
78
76
|
* Statically declare dependencies.
|
|
77
|
+
* --- MODIFIED ---
|
|
78
|
+
* Removed 'asset-pnl-status' dependency.
|
|
79
79
|
*/
|
|
80
80
|
static getDependencies() {
|
|
81
|
-
return [
|
|
82
|
-
'asset-pnl-status' // from core
|
|
83
|
-
];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Helper to load cohort data from the dependency (runs once).
|
|
88
|
-
*/
|
|
89
|
-
_loadCohorts(fetchedDependencies) {
|
|
90
|
-
if (this.cohorts) return;
|
|
91
|
-
|
|
92
|
-
const pnlStatusData = fetchedDependencies['asset-pnl-status'];
|
|
93
|
-
if (!pnlStatusData) {
|
|
94
|
-
this.cohorts = { winnerMap: new Map(), loserMap: new Map() };
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const winnerMap = new Map();
|
|
99
|
-
const loserMap = new Map();
|
|
100
|
-
|
|
101
|
-
for (const [ticker, data] of Object.entries(pnlStatusData)) {
|
|
102
|
-
// 'users_in_profit' and 'users_in_loss' are arrays of user IDs
|
|
103
|
-
winnerMap.set(ticker, new Set(data.users_in_profit || []));
|
|
104
|
-
loserMap.set(ticker, new Set(data.users_in_loss || []));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.cohorts = { winnerMap, loserMap };
|
|
81
|
+
return [];
|
|
108
82
|
}
|
|
109
83
|
|
|
110
84
|
/**
|
|
111
|
-
*
|
|
85
|
+
* --- MODIFIED ---
|
|
86
|
+
* Helper to get a user's holdings (Instrument IDs) AND their P&L.
|
|
87
|
+
* @returns {Map<InstrumentID, {pnl: number}>}
|
|
112
88
|
*/
|
|
113
|
-
|
|
89
|
+
_getHoldingsWithPnl(portfolio) {
|
|
114
90
|
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
115
91
|
if (!positions || !Array.isArray(positions)) {
|
|
116
|
-
return new
|
|
92
|
+
return new Map();
|
|
93
|
+
}
|
|
94
|
+
const map = new Map();
|
|
95
|
+
for (const pos of positions) {
|
|
96
|
+
if (pos.InstrumentID) {
|
|
97
|
+
map.set(pos.InstrumentID, { pnl: pos.NetProfit || 0 });
|
|
98
|
+
}
|
|
117
99
|
}
|
|
118
|
-
return
|
|
100
|
+
return map;
|
|
119
101
|
}
|
|
120
102
|
|
|
121
103
|
/**
|
|
@@ -132,21 +114,19 @@ class WinnerLoserFlow {
|
|
|
132
114
|
}
|
|
133
115
|
}
|
|
134
116
|
|
|
135
|
-
process(todayPortfolio, yesterdayPortfolio, userId, context
|
|
117
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
136
118
|
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
137
119
|
return;
|
|
138
120
|
}
|
|
139
121
|
|
|
140
|
-
// Load mappings and cohort data on the first user
|
|
141
122
|
if (!this.mappings) {
|
|
142
123
|
this.mappings = context.mappings;
|
|
143
124
|
}
|
|
144
|
-
this._loadCohorts(fetchedDependencies);
|
|
145
125
|
|
|
146
|
-
const yHoldings = this.
|
|
147
|
-
const tHoldings = this.
|
|
126
|
+
const yHoldings = this._getHoldingsWithPnl(yesterdayPortfolio); // Map<InstID, {pnl}>
|
|
127
|
+
const tHoldings = this._getHoldingsWithPnl(todayPortfolio); // Map<InstID, {pnl}>
|
|
148
128
|
|
|
149
|
-
const allInstrumentIds = new Set([...yHoldings, ...tHoldings]);
|
|
129
|
+
const allInstrumentIds = new Set([...yHoldings.keys(), ...tHoldings.keys()]);
|
|
150
130
|
if (allInstrumentIds.size === 0) {
|
|
151
131
|
return;
|
|
152
132
|
}
|
|
@@ -155,8 +135,12 @@ class WinnerLoserFlow {
|
|
|
155
135
|
const ticker = this.mappings.instrumentToTicker[instrumentId];
|
|
156
136
|
if (!ticker) continue;
|
|
157
137
|
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
// --- MODIFIED ---
|
|
139
|
+
// Determine cohort status from today's portfolio.
|
|
140
|
+
const tPnl = tHoldings.get(instrumentId)?.pnl;
|
|
141
|
+
const isWinner = tPnl > 0;
|
|
142
|
+
const isLoser = tPnl < 0;
|
|
143
|
+
// --- END MODIFIED ---
|
|
160
144
|
|
|
161
145
|
// We only care about users who are *currently* in a cohort
|
|
162
146
|
if (!isWinner && !isLoser) {
|
|
@@ -199,7 +183,6 @@ class WinnerLoserFlow {
|
|
|
199
183
|
|
|
200
184
|
reset() {
|
|
201
185
|
this.assetFlows.clear();
|
|
202
|
-
this.cohorts = null;
|
|
203
186
|
this.mappings = null;
|
|
204
187
|
}
|
|
205
188
|
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 3) for "In Loss" cohort asset flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This helps identify if losers are capitulating or doubling down.
|
|
9
|
-
*
|
|
10
|
-
* This calculation *depends* on 'asset_pnl_status' to identify the cohort.
|
|
4
|
+
* --- MODIFIED ---
|
|
5
|
+
* Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
|
|
6
|
+
* This calculation now determines "in loss" status internally
|
|
7
|
+
* by reading the *yesterday's* portfolio P&L directly.
|
|
11
8
|
*/
|
|
12
9
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
13
10
|
|
|
@@ -15,7 +12,8 @@ class InLossAssetCrowdFlow {
|
|
|
15
12
|
constructor() {
|
|
16
13
|
this.assetData = new Map();
|
|
17
14
|
this.mappings = null;
|
|
18
|
-
|
|
15
|
+
// No longer need this:
|
|
16
|
+
// this.inLossCohorts = null;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/**
|
|
@@ -57,9 +55,11 @@ class InLossAssetCrowdFlow {
|
|
|
57
55
|
|
|
58
56
|
/**
|
|
59
57
|
* Statically declare dependencies.
|
|
58
|
+
* --- MODIFIED ---
|
|
59
|
+
* Removed 'asset_pnl-status'
|
|
60
60
|
*/
|
|
61
61
|
static getDependencies() {
|
|
62
|
-
return [
|
|
62
|
+
return [];
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
_getPortfolioPositions(portfolio) {
|
|
@@ -77,31 +77,8 @@ class InLossAssetCrowdFlow {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* --- MODIFIED ---
|
|
83
|
-
* Reads `data.users_in_loss` as a string array, not an object array.
|
|
84
|
-
*/
|
|
85
|
-
_getInLossCohorts(fetchedDependencies) {
|
|
86
|
-
if (this.inLossCohorts) {
|
|
87
|
-
return this.inLossCohorts;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const pnlStatusData = fetchedDependencies['asset_pnl_status'];
|
|
91
|
-
if (!pnlStatusData) {
|
|
92
|
-
return new Map();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Re-structure the data for efficient lookup
|
|
96
|
-
// Map<ticker, Set<userId>>
|
|
97
|
-
this.inLossCohorts = new Map();
|
|
98
|
-
for (const [ticker, data] of Object.entries(pnlStatusData)) {
|
|
99
|
-
// `data.users_in_loss` is now a string[], so no .map() is needed.
|
|
100
|
-
const userSet = new Set(data.users_in_loss || []); // <-- MODIFIED
|
|
101
|
-
this.inLossCohorts.set(ticker, userSet);
|
|
102
|
-
}
|
|
103
|
-
return this.inLossCohorts;
|
|
104
|
-
}
|
|
80
|
+
// --- MODIFIED ---
|
|
81
|
+
// Removed _getInLossCohorts helper
|
|
105
82
|
|
|
106
83
|
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
107
84
|
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
@@ -109,15 +86,9 @@ class InLossAssetCrowdFlow {
|
|
|
109
86
|
}
|
|
110
87
|
|
|
111
88
|
if (!this.mappings) {
|
|
112
|
-
// Context contains the mappings loaded in Pass 1
|
|
113
89
|
this.mappings = context.mappings;
|
|
114
90
|
}
|
|
115
91
|
|
|
116
|
-
const cohorts = this._getInLossCohorts(fetchedDependencies);
|
|
117
|
-
if (cohorts.size === 0) {
|
|
118
|
-
return; // No dependency data
|
|
119
|
-
}
|
|
120
|
-
|
|
121
92
|
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
122
93
|
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
123
94
|
|
|
@@ -129,22 +100,22 @@ class InLossAssetCrowdFlow {
|
|
|
129
100
|
for (const instrumentId of allInstrumentIds) {
|
|
130
101
|
if (!instrumentId) continue;
|
|
131
102
|
|
|
132
|
-
const
|
|
133
|
-
const
|
|
103
|
+
const yP = yPosMap.get(instrumentId);
|
|
104
|
+
const tP = tPosMap.get(instrumentId);
|
|
134
105
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
106
|
+
// --- MODIFIED ---
|
|
107
|
+
// Check P&L from YESTERDAY's position to define cohort
|
|
108
|
+
const yPnl = yP?.NetProfit || 0;
|
|
109
|
+
if (yPnl >= 0) {
|
|
110
|
+
continue; // User was not in loss yesterday, skip.
|
|
138
111
|
}
|
|
112
|
+
// --- END MODIFIED ---
|
|
139
113
|
|
|
140
114
|
// User *is* in the cohort, process their data
|
|
141
115
|
this._initAsset(instrumentId);
|
|
142
116
|
const asset = this.assetData.get(instrumentId);
|
|
143
117
|
asset.cohort.add(userId); // Track cohort size
|
|
144
118
|
|
|
145
|
-
const yP = yPosMap.get(instrumentId);
|
|
146
|
-
const tP = tPosMap.get(instrumentId);
|
|
147
|
-
|
|
148
119
|
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
149
120
|
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
150
121
|
|
|
@@ -191,7 +162,6 @@ class InLossAssetCrowdFlow {
|
|
|
191
162
|
reset() {
|
|
192
163
|
this.assetData.clear();
|
|
193
164
|
this.mappings = null;
|
|
194
|
-
this.inLossCohorts = null;
|
|
195
165
|
}
|
|
196
166
|
}
|
|
197
167
|
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 3) for "In Profit" cohort asset flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This helps identify if winners are taking profit or adding to positions.
|
|
9
|
-
*
|
|
10
|
-
* This calculation *depends* on 'asset_pnl_status' to identify the cohort.
|
|
4
|
+
* --- MODIFIED ---
|
|
5
|
+
* Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
|
|
6
|
+
* This calculation now determines "in profit" status internally
|
|
7
|
+
* by reading the *yesterday's* portfolio P&L directly.
|
|
11
8
|
*/
|
|
12
9
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
13
10
|
|
|
@@ -15,7 +12,8 @@ class InProfitAssetCrowdFlow {
|
|
|
15
12
|
constructor() {
|
|
16
13
|
this.assetData = new Map();
|
|
17
14
|
this.mappings = null;
|
|
18
|
-
|
|
15
|
+
// No longer need this:
|
|
16
|
+
// this.inProfitCohorts = null;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/**
|
|
@@ -57,9 +55,11 @@ class InProfitAssetCrowdFlow {
|
|
|
57
55
|
|
|
58
56
|
/**
|
|
59
57
|
* Statically declare dependencies.
|
|
58
|
+
* --- MODIFIED ---
|
|
59
|
+
* Removed 'asset_pnl-status'
|
|
60
60
|
*/
|
|
61
61
|
static getDependencies() {
|
|
62
|
-
return [
|
|
62
|
+
return [];
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
_getPortfolioPositions(portfolio) {
|
|
@@ -77,31 +77,8 @@ class InProfitAssetCrowdFlow {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* --- MODIFIED ---
|
|
83
|
-
* Reads `data.users_in_profit` as a string array, not an object array.
|
|
84
|
-
*/
|
|
85
|
-
_getInProfitCohorts(fetchedDependencies) {
|
|
86
|
-
if (this.inProfitCohorts) {
|
|
87
|
-
return this.inProfitCohorts;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const pnlStatusData = fetchedDependencies['asset_pnl_status'];
|
|
91
|
-
if (!pnlStatusData) {
|
|
92
|
-
return new Map();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Re-structure the data for efficient lookup
|
|
96
|
-
// Map<ticker, Set<userId>>
|
|
97
|
-
this.inProfitCohorts = new Map();
|
|
98
|
-
for (const [ticker, data] of Object.entries(pnlStatusData)) {
|
|
99
|
-
// `data.users_in_profit` is now a string[], so no .map() is needed.
|
|
100
|
-
const userSet = new Set(data.users_in_profit || []); // <-- MODIFIED
|
|
101
|
-
this.inProfitCohorts.set(ticker, userSet);
|
|
102
|
-
}
|
|
103
|
-
return this.inProfitCohorts;
|
|
104
|
-
}
|
|
80
|
+
// --- MODIFIED ---
|
|
81
|
+
// Removed _getInProfitCohorts helper
|
|
105
82
|
|
|
106
83
|
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
107
84
|
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
@@ -109,15 +86,9 @@ class InProfitAssetCrowdFlow {
|
|
|
109
86
|
}
|
|
110
87
|
|
|
111
88
|
if (!this.mappings) {
|
|
112
|
-
// Context contains the mappings loaded in Pass 1
|
|
113
89
|
this.mappings = context.mappings;
|
|
114
90
|
}
|
|
115
91
|
|
|
116
|
-
const cohorts = this._getInProfitCohorts(fetchedDependencies);
|
|
117
|
-
if (cohorts.size === 0) {
|
|
118
|
-
return; // No dependency data
|
|
119
|
-
}
|
|
120
|
-
|
|
121
92
|
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
122
93
|
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
123
94
|
|
|
@@ -129,22 +100,22 @@ class InProfitAssetCrowdFlow {
|
|
|
129
100
|
for (const instrumentId of allInstrumentIds) {
|
|
130
101
|
if (!instrumentId) continue;
|
|
131
102
|
|
|
132
|
-
const
|
|
133
|
-
const
|
|
103
|
+
const yP = yPosMap.get(instrumentId);
|
|
104
|
+
const tP = tPosMap.get(instrumentId);
|
|
134
105
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
106
|
+
// --- MODIFIED ---
|
|
107
|
+
// Check P&L from YESTERDAY's position to define cohort
|
|
108
|
+
const yPnl = yP?.NetProfit || 0;
|
|
109
|
+
if (yPnl <= 0) {
|
|
110
|
+
continue; // User was not in profit yesterday, skip.
|
|
138
111
|
}
|
|
139
|
-
|
|
112
|
+
// --- END MODIFIED ---
|
|
113
|
+
|
|
140
114
|
// User *is* in the cohort, process their data
|
|
141
115
|
this._initAsset(instrumentId);
|
|
142
116
|
const asset = this.assetData.get(instrumentId);
|
|
143
117
|
asset.cohort.add(userId); // Track cohort size
|
|
144
118
|
|
|
145
|
-
const yP = yPosMap.get(instrumentId);
|
|
146
|
-
const tP = tPosMap.get(instrumentId);
|
|
147
|
-
|
|
148
119
|
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
149
120
|
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
150
121
|
|
|
@@ -191,7 +162,6 @@ class InProfitAssetCrowdFlow {
|
|
|
191
162
|
reset() {
|
|
192
163
|
this.assetData.clear();
|
|
193
164
|
this.mappings = null;
|
|
194
|
-
this.inProfitCohorts = null;
|
|
195
165
|
}
|
|
196
166
|
}
|
|
197
167
|
|