bulltrackers-module 1.0.950 → 1.0.951
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.
|
@@ -29,7 +29,7 @@ exports.config = {
|
|
|
29
29
|
filter: { user_type: 'POPULAR_INVESTOR' }
|
|
30
30
|
},
|
|
31
31
|
'portfolio_snapshots': {
|
|
32
|
-
lookback:
|
|
32
|
+
lookback: 7, // FIX: Changed to 7 to allow historical diffing of positions
|
|
33
33
|
mandatory: false,
|
|
34
34
|
fields: ['user_id', 'portfolio_data', 'date'],
|
|
35
35
|
filter: { user_type: 'POPULAR_INVESTOR' }
|
|
@@ -122,21 +122,99 @@ exports.process = (ctx) => {
|
|
|
122
122
|
const topPIRiskChange7dSlice = topPIRiskChange7d.slice(0, TOP_RISK_CHANGE_N);
|
|
123
123
|
|
|
124
124
|
// -------------------------------------------------------------------------
|
|
125
|
-
//
|
|
125
|
+
// Define the 7-day boundary for asset trading tracking
|
|
126
126
|
// -------------------------------------------------------------------------
|
|
127
|
+
const targetDate = new Date(date);
|
|
128
|
+
const sevenDaysAgo = new Date(targetDate.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
129
|
+
|
|
127
130
|
const purchaseCount = new Map();
|
|
128
131
|
const saleCount = new Map();
|
|
129
132
|
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
// 2A. Most purchased / most sold assets (from trade_history_snapshots)
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
130
136
|
tradeHistory.forEach(row => {
|
|
131
137
|
const trades = lib.trades.extractTrades(row);
|
|
132
138
|
trades.forEach(trade => {
|
|
133
139
|
const instrumentId = lib.trades.getInstrumentId(trade);
|
|
134
140
|
if (instrumentId == null) return;
|
|
135
141
|
const ticker = resolveTicker(instrumentId);
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
|
|
143
|
+
const openDate = lib.trades.getOpenDate(trade);
|
|
144
|
+
const closeDate = lib.trades.getCloseDate(trade);
|
|
145
|
+
const isBuy = lib.trades.isBuy(trade); // True if Long position
|
|
146
|
+
|
|
147
|
+
if (isBuy) {
|
|
148
|
+
// If it's a Long position and was opened recently -> Purchase
|
|
149
|
+
if (openDate && openDate >= sevenDaysAgo && openDate <= targetDate) {
|
|
150
|
+
purchaseCount.set(ticker, (purchaseCount.get(ticker) || 0) + 1);
|
|
151
|
+
}
|
|
152
|
+
// If it's a Long position and was closed recently -> Sale
|
|
153
|
+
if (closeDate && closeDate >= sevenDaysAgo && closeDate <= targetDate) {
|
|
154
|
+
saleCount.set(ticker, (saleCount.get(ticker) || 0) + 1);
|
|
155
|
+
}
|
|
138
156
|
} else {
|
|
139
|
-
|
|
157
|
+
// For Short positions, opening is a Sale, closing is a Purchase
|
|
158
|
+
if (openDate && openDate >= sevenDaysAgo && openDate <= targetDate) {
|
|
159
|
+
saleCount.set(ticker, (saleCount.get(ticker) || 0) + 1);
|
|
160
|
+
}
|
|
161
|
+
if (closeDate && closeDate >= sevenDaysAgo && closeDate <= targetDate) {
|
|
162
|
+
purchaseCount.set(ticker, (purchaseCount.get(ticker) || 0) + 1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// -------------------------------------------------------------------------
|
|
169
|
+
// 2B. Most purchased assets (from diffing portfolio_snapshots)
|
|
170
|
+
// -------------------------------------------------------------------------
|
|
171
|
+
const portfolioByUser = new Map();
|
|
172
|
+
const portfolioRows = toArray(data['portfolio_snapshots'] || []);
|
|
173
|
+
|
|
174
|
+
// Group snapshots by user
|
|
175
|
+
portfolioRows.forEach(row => {
|
|
176
|
+
const userId = lib.portfolio.getUserId(row);
|
|
177
|
+
if (!userId) return;
|
|
178
|
+
|
|
179
|
+
const rowDateStr = row.date && (typeof row.date.value === 'string' ? row.date.value : String(row.date).slice(0, 10));
|
|
180
|
+
if (!rowDateStr) return;
|
|
181
|
+
|
|
182
|
+
const rowDate = new Date(rowDateStr);
|
|
183
|
+
if (rowDate < sevenDaysAgo || rowDate > targetDate) return;
|
|
184
|
+
|
|
185
|
+
if (!portfolioByUser.has(userId)) portfolioByUser.set(userId, []);
|
|
186
|
+
portfolioByUser.get(userId).push({ date: rowDateStr, row });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Diff the oldest and newest snapshot for each user in the 7-day window
|
|
190
|
+
portfolioByUser.forEach((snapshots) => {
|
|
191
|
+
if (snapshots.length < 2) return; // Need at least 2 days to diff
|
|
192
|
+
|
|
193
|
+
snapshots.sort((a, b) => a.date.localeCompare(b.date));
|
|
194
|
+
|
|
195
|
+
const oldestPositions = lib.portfolio.extractPositions(lib.portfolio.extractPortfolioData(snapshots[0].row));
|
|
196
|
+
const newestPositions = lib.portfolio.extractPositions(lib.portfolio.extractPortfolioData(snapshots[snapshots.length - 1].row));
|
|
197
|
+
|
|
198
|
+
// Helper to sum invested amount per asset
|
|
199
|
+
const getInvestedMap = (positions) => {
|
|
200
|
+
const map = new Map();
|
|
201
|
+
positions.forEach(p => {
|
|
202
|
+
const id = lib.portfolio.getInstrumentId(p);
|
|
203
|
+
if (id != null) map.set(id, (map.get(id) || 0) + lib.portfolio.getInvested(p));
|
|
204
|
+
});
|
|
205
|
+
return map;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const oldMap = getInvestedMap(oldestPositions);
|
|
209
|
+
const newMap = getInvestedMap(newestPositions);
|
|
210
|
+
|
|
211
|
+
newMap.forEach((newInvested, instrumentId) => {
|
|
212
|
+
const oldInvested = oldMap.get(instrumentId) || 0;
|
|
213
|
+
|
|
214
|
+
// If the total invested amount in an asset increased, they purchased more of it
|
|
215
|
+
if (newInvested > oldInvested) {
|
|
216
|
+
const ticker = resolveTicker(instrumentId);
|
|
217
|
+
purchaseCount.set(ticker, (purchaseCount.get(ticker) || 0) + 1);
|
|
140
218
|
}
|
|
141
219
|
});
|
|
142
220
|
});
|
|
@@ -226,4 +304,4 @@ exports.process = (ctx) => {
|
|
|
226
304
|
recentAlertEvents
|
|
227
305
|
}
|
|
228
306
|
};
|
|
229
|
-
};
|
|
307
|
+
};
|