bulltrackers-module 1.0.733 → 1.0.734
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/functions/computation-system-v2/README.md +152 -0
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +720 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +176 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +294 -0
- package/functions/computation-system-v2/computations/TestComputation.js +46 -0
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +172 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +317 -0
- package/functions/computation-system-v2/framework/core/Computation.js +73 -0
- package/functions/computation-system-v2/framework/core/Manifest.js +223 -0
- package/functions/computation-system-v2/framework/core/RuleInjector.js +53 -0
- package/functions/computation-system-v2/framework/core/Rules.js +231 -0
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +163 -0
- package/functions/computation-system-v2/framework/cost/CostTracker.js +154 -0
- package/functions/computation-system-v2/framework/data/DataFetcher.js +399 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +232 -0
- package/functions/computation-system-v2/framework/data/SchemaRegistry.js +287 -0
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +498 -0
- package/functions/computation-system-v2/framework/execution/TaskRunner.js +35 -0
- package/functions/computation-system-v2/framework/execution/middleware/CostTrackerMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/Middleware.js +14 -0
- package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +47 -0
- package/functions/computation-system-v2/framework/index.js +45 -0
- package/functions/computation-system-v2/framework/lineage/LineageTracker.js +147 -0
- package/functions/computation-system-v2/framework/monitoring/Profiler.js +80 -0
- package/functions/computation-system-v2/framework/resilience/Checkpointer.js +66 -0
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +327 -0
- package/functions/computation-system-v2/framework/storage/StateRepository.js +286 -0
- package/functions/computation-system-v2/framework/storage/StorageManager.js +469 -0
- package/functions/computation-system-v2/framework/storage/index.js +9 -0
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +86 -0
- package/functions/computation-system-v2/framework/utils/Graph.js +205 -0
- package/functions/computation-system-v2/handlers/dispatcher.js +109 -0
- package/functions/computation-system-v2/handlers/index.js +23 -0
- package/functions/computation-system-v2/handlers/onDemand.js +289 -0
- package/functions/computation-system-v2/handlers/scheduler.js +327 -0
- package/functions/computation-system-v2/index.js +163 -0
- package/functions/computation-system-v2/rules/index.js +49 -0
- package/functions/computation-system-v2/rules/instruments.js +465 -0
- package/functions/computation-system-v2/rules/metrics.js +304 -0
- package/functions/computation-system-v2/rules/portfolio.js +534 -0
- package/functions/computation-system-v2/rules/rankings.js +655 -0
- package/functions/computation-system-v2/rules/social.js +562 -0
- package/functions/computation-system-v2/rules/trades.js +545 -0
- package/functions/computation-system-v2/scripts/migrate-sectors.js +73 -0
- package/functions/computation-system-v2/test/test-dispatcher.js +317 -0
- package/functions/computation-system-v2/test/test-framework.js +500 -0
- package/functions/computation-system-v2/test/test-real-execution.js +166 -0
- package/functions/computation-system-v2/test/test-real-integration.js +194 -0
- package/functions/computation-system-v2/test/test-refactor-e2e.js +131 -0
- package/functions/computation-system-v2/test/test-results.json +31 -0
- package/functions/computation-system-v2/test/test-risk-metrics-computation.js +329 -0
- package/functions/computation-system-v2/test/test-scheduler.js +204 -0
- package/functions/computation-system-v2/test/test-storage.js +449 -0
- package/functions/orchestrator/index.js +18 -26
- package/package.json +3 -2
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rankings Business Rules
|
|
3
|
+
*
|
|
4
|
+
* For extracting and calculating metrics from pi_rankings data.
|
|
5
|
+
* The rankings_data JSON contains comprehensive investor metrics from eToro.
|
|
6
|
+
*
|
|
7
|
+
* Usage in computation:
|
|
8
|
+
* ```javascript
|
|
9
|
+
* const rankingsData = rules.rankings.extractRankingsData(row);
|
|
10
|
+
* const riskScore = rules.rankings.getRiskScore(rankingsData);
|
|
11
|
+
* const tier = rules.rankings.getAUMTier(rankingsData);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// DATA EXTRACTION
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract rankings_data from a pi_rankings row.
|
|
21
|
+
* Handles both string JSON and parsed object.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} row - pi_rankings row
|
|
24
|
+
* @returns {Object|null} Parsed rankings data
|
|
25
|
+
*/
|
|
26
|
+
function extractRankingsData(row) {
|
|
27
|
+
if (!row) return null;
|
|
28
|
+
|
|
29
|
+
let data = row.rankings_data;
|
|
30
|
+
|
|
31
|
+
if (typeof data === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
data = JSON.parse(data);
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return data || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the PI ID from a rankings row.
|
|
44
|
+
* @param {Object} row - pi_rankings row
|
|
45
|
+
* @returns {string|number|null}
|
|
46
|
+
*/
|
|
47
|
+
function getPiId(row) {
|
|
48
|
+
return row?.pi_id || row?.CustomerId || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the username from a rankings row.
|
|
53
|
+
* @param {Object} row - pi_rankings row
|
|
54
|
+
* @returns {string|null}
|
|
55
|
+
*/
|
|
56
|
+
function getUsername(row) {
|
|
57
|
+
return row?.username || row?.UserName || extractRankingsData(row)?.UserName || null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// RISK METRICS
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the risk score (0-10 integer).
|
|
66
|
+
* @param {Object} data - rankings_data object
|
|
67
|
+
* @returns {number}
|
|
68
|
+
*/
|
|
69
|
+
function getRiskScore(data) {
|
|
70
|
+
return data?.RiskScore ?? 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get max daily risk score.
|
|
75
|
+
* @param {Object} data - rankings_data object
|
|
76
|
+
* @returns {number}
|
|
77
|
+
*/
|
|
78
|
+
function getMaxDailyRiskScore(data) {
|
|
79
|
+
return data?.MaxDailyRiskScore ?? 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get max monthly risk score.
|
|
84
|
+
* @param {Object} data - rankings_data object
|
|
85
|
+
* @returns {number}
|
|
86
|
+
*/
|
|
87
|
+
function getMaxMonthlyRiskScore(data) {
|
|
88
|
+
return data?.MaxMonthlyRiskScore ?? 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get daily drawdown percentage (negative number).
|
|
93
|
+
* @param {Object} data - rankings_data object
|
|
94
|
+
* @returns {number}
|
|
95
|
+
*/
|
|
96
|
+
function getDailyDrawdown(data) {
|
|
97
|
+
return data?.DailyDD ?? 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get weekly drawdown percentage (negative number).
|
|
102
|
+
* @param {Object} data - rankings_data object
|
|
103
|
+
* @returns {number}
|
|
104
|
+
*/
|
|
105
|
+
function getWeeklyDrawdown(data) {
|
|
106
|
+
return data?.WeeklyDD ?? 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get peak to valley (max drawdown).
|
|
111
|
+
* @param {Object} data - rankings_data object
|
|
112
|
+
* @returns {number}
|
|
113
|
+
*/
|
|
114
|
+
function getPeakToValley(data) {
|
|
115
|
+
return data?.PeakToValley ?? 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Categorize risk score into a tier.
|
|
120
|
+
* @param {number} riskScore - Risk score 0-10
|
|
121
|
+
* @returns {string} 'low' | 'medium' | 'high' | 'very_high'
|
|
122
|
+
*/
|
|
123
|
+
function getRiskTier(riskScore) {
|
|
124
|
+
if (riskScore <= 3) return 'low';
|
|
125
|
+
if (riskScore <= 5) return 'medium';
|
|
126
|
+
if (riskScore <= 7) return 'high';
|
|
127
|
+
return 'very_high';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// PERFORMANCE METRICS
|
|
132
|
+
// =============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get total gain percentage (2 full years + current year).
|
|
136
|
+
* @param {Object} data - rankings_data object
|
|
137
|
+
* @returns {number}
|
|
138
|
+
*/
|
|
139
|
+
function getTotalGain(data) {
|
|
140
|
+
return data?.Gain ?? 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get daily gain percentage.
|
|
145
|
+
* @param {Object} data - rankings_data object
|
|
146
|
+
* @returns {number}
|
|
147
|
+
*/
|
|
148
|
+
function getDailyGain(data) {
|
|
149
|
+
return data?.DailyGain ?? 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get this week's gain percentage.
|
|
154
|
+
* @param {Object} data - rankings_data object
|
|
155
|
+
* @returns {number}
|
|
156
|
+
*/
|
|
157
|
+
function getWeeklyGain(data) {
|
|
158
|
+
return data?.ThisWeekGain ?? 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get win ratio (0-100).
|
|
163
|
+
* @param {Object} data - rankings_data object
|
|
164
|
+
* @returns {number}
|
|
165
|
+
*/
|
|
166
|
+
function getWinRatio(data) {
|
|
167
|
+
return data?.WinRatio ?? 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get profitable weeks percentage.
|
|
172
|
+
* @param {Object} data - rankings_data object
|
|
173
|
+
* @returns {number}
|
|
174
|
+
*/
|
|
175
|
+
function getProfitableWeeksPct(data) {
|
|
176
|
+
return data?.ProfitableWeeksPct ?? 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get profitable months percentage.
|
|
181
|
+
* @param {Object} data - rankings_data object
|
|
182
|
+
* @returns {number}
|
|
183
|
+
*/
|
|
184
|
+
function getProfitableMonthsPct(data) {
|
|
185
|
+
return data?.ProfitableMonthsPct ?? 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get copiers gain (performance of copiers).
|
|
190
|
+
* @param {Object} data - rankings_data object
|
|
191
|
+
* @returns {number}
|
|
192
|
+
*/
|
|
193
|
+
function getCopiersGain(data) {
|
|
194
|
+
return data?.CopiersGain ?? 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// AUM & COPIERS
|
|
199
|
+
// =============================================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get AUM (Assets Under Management) in USD.
|
|
203
|
+
* @param {Object} data - rankings_data object
|
|
204
|
+
* @returns {number}
|
|
205
|
+
*/
|
|
206
|
+
function getAUM(data) {
|
|
207
|
+
return data?.AUMValue ?? 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get AUM tier (1-5).
|
|
212
|
+
* @param {Object} data - rankings_data object
|
|
213
|
+
* @returns {number}
|
|
214
|
+
*/
|
|
215
|
+
function getAUMTier(data) {
|
|
216
|
+
return data?.AUMTier ?? 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get AUM tier description.
|
|
221
|
+
* @param {Object} data - rankings_data object
|
|
222
|
+
* @returns {string}
|
|
223
|
+
*/
|
|
224
|
+
function getAUMTierDesc(data) {
|
|
225
|
+
return data?.AUMTierDesc || 'Unknown';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get current copiers count.
|
|
230
|
+
* @param {Object} data - rankings_data object
|
|
231
|
+
* @returns {number}
|
|
232
|
+
*/
|
|
233
|
+
function getCopiers(data) {
|
|
234
|
+
return data?.Copiers ?? 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get baseline copiers count.
|
|
239
|
+
* @param {Object} data - rankings_data object
|
|
240
|
+
* @returns {number}
|
|
241
|
+
*/
|
|
242
|
+
function getBaselineCopiers(data) {
|
|
243
|
+
return data?.BaseLineCopiers ?? 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Calculate copier change from baseline.
|
|
248
|
+
* @param {Object} data - rankings_data object
|
|
249
|
+
* @returns {number}
|
|
250
|
+
*/
|
|
251
|
+
function getCopierChange(data) {
|
|
252
|
+
const current = getCopiers(data);
|
|
253
|
+
const baseline = getBaselineCopiers(data);
|
|
254
|
+
return current - baseline;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Calculate copier change percentage.
|
|
259
|
+
* @param {Object} data - rankings_data object
|
|
260
|
+
* @returns {number}
|
|
261
|
+
*/
|
|
262
|
+
function getCopierChangePct(data) {
|
|
263
|
+
const baseline = getBaselineCopiers(data);
|
|
264
|
+
if (baseline === 0) return 0;
|
|
265
|
+
return ((getCopiers(data) - baseline) / baseline) * 100;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// TRADING STYLE
|
|
270
|
+
// =============================================================================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get exposure percentage.
|
|
274
|
+
* @param {Object} data - rankings_data object
|
|
275
|
+
* @returns {number}
|
|
276
|
+
*/
|
|
277
|
+
function getExposure(data) {
|
|
278
|
+
return data?.Exposure ?? 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get long position percentage.
|
|
283
|
+
* @param {Object} data - rankings_data object
|
|
284
|
+
* @returns {number}
|
|
285
|
+
*/
|
|
286
|
+
function getLongPosPct(data) {
|
|
287
|
+
return data?.LongPosPct ?? 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get short position percentage.
|
|
292
|
+
* @param {Object} data - rankings_data object
|
|
293
|
+
* @returns {number}
|
|
294
|
+
*/
|
|
295
|
+
function getShortPosPct(data) {
|
|
296
|
+
return 100 - (data?.LongPosPct ?? 100);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get average position size percentage.
|
|
301
|
+
* @param {Object} data - rankings_data object
|
|
302
|
+
* @returns {number}
|
|
303
|
+
*/
|
|
304
|
+
function getAvgPosSize(data) {
|
|
305
|
+
return data?.AvgPosSize ?? 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get total trades count.
|
|
310
|
+
* @param {Object} data - rankings_data object
|
|
311
|
+
* @returns {number}
|
|
312
|
+
*/
|
|
313
|
+
function getTotalTrades(data) {
|
|
314
|
+
return data?.Trades ?? 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get total traded instruments count.
|
|
319
|
+
* @param {Object} data - rankings_data object
|
|
320
|
+
* @returns {number}
|
|
321
|
+
*/
|
|
322
|
+
function getTotalInstruments(data) {
|
|
323
|
+
return data?.TotalTradedInstruments ?? 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get leverage breakdown.
|
|
328
|
+
* @param {Object} data - rankings_data object
|
|
329
|
+
* @returns {Object} { low, medium, high }
|
|
330
|
+
*/
|
|
331
|
+
function getLeverageBreakdown(data) {
|
|
332
|
+
return {
|
|
333
|
+
low: data?.LowLeveragePct ?? 0,
|
|
334
|
+
medium: data?.MediumLeveragePct ?? 0,
|
|
335
|
+
high: data?.HighLeveragePct ?? 0
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Determine primary trading style.
|
|
341
|
+
* @param {Object} data - rankings_data object
|
|
342
|
+
* @returns {string}
|
|
343
|
+
*/
|
|
344
|
+
function getTradingStyle(data) {
|
|
345
|
+
const longPct = getLongPosPct(data);
|
|
346
|
+
const leverage = getLeverageBreakdown(data);
|
|
347
|
+
const trades = getTotalTrades(data);
|
|
348
|
+
const instruments = getTotalInstruments(data);
|
|
349
|
+
|
|
350
|
+
// Determine direction bias
|
|
351
|
+
let directionStyle = 'balanced';
|
|
352
|
+
if (longPct >= 80) directionStyle = 'long-only';
|
|
353
|
+
else if (longPct <= 20) directionStyle = 'short-focused';
|
|
354
|
+
|
|
355
|
+
// Determine leverage style
|
|
356
|
+
let leverageStyle = 'conservative';
|
|
357
|
+
if (leverage.high > 30) leverageStyle = 'aggressive';
|
|
358
|
+
else if (leverage.medium > 40) leverageStyle = 'moderate';
|
|
359
|
+
|
|
360
|
+
// Determine diversification
|
|
361
|
+
let diversification = 'concentrated';
|
|
362
|
+
if (instruments >= 20) diversification = 'diversified';
|
|
363
|
+
else if (instruments >= 10) diversification = 'moderate';
|
|
364
|
+
|
|
365
|
+
return `${directionStyle}, ${leverageStyle}, ${diversification}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// =============================================================================
|
|
369
|
+
// TOP ASSET INFO
|
|
370
|
+
// =============================================================================
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get top traded instrument ID.
|
|
374
|
+
* @param {Object} data - rankings_data object
|
|
375
|
+
* @returns {number|null}
|
|
376
|
+
*/
|
|
377
|
+
function getTopInstrumentId(data) {
|
|
378
|
+
return data?.TopTradedInstrumentId || null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get top traded instrument percentage.
|
|
383
|
+
* @param {Object} data - rankings_data object
|
|
384
|
+
* @returns {number}
|
|
385
|
+
*/
|
|
386
|
+
function getTopInstrumentPct(data) {
|
|
387
|
+
return data?.TopTradedInstrumentPct ?? 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get top traded asset class ID.
|
|
392
|
+
* @param {Object} data - rankings_data object
|
|
393
|
+
* @returns {number|null}
|
|
394
|
+
*/
|
|
395
|
+
function getTopAssetClassId(data) {
|
|
396
|
+
return data?.TopTradedAssetClassId || null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get tags array.
|
|
401
|
+
* @param {Object} data - rankings_data object
|
|
402
|
+
* @returns {number[]}
|
|
403
|
+
*/
|
|
404
|
+
function getTags(data) {
|
|
405
|
+
return data?.Tags || [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// =============================================================================
|
|
409
|
+
// ACTIVITY & STATUS
|
|
410
|
+
// =============================================================================
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get active weeks count.
|
|
414
|
+
* @param {Object} data - rankings_data object
|
|
415
|
+
* @returns {number}
|
|
416
|
+
*/
|
|
417
|
+
function getActiveWeeks(data) {
|
|
418
|
+
return data?.ActiveWeeks ?? 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get active weeks percentage.
|
|
423
|
+
* @param {Object} data - rankings_data object
|
|
424
|
+
* @returns {number}
|
|
425
|
+
*/
|
|
426
|
+
function getActiveWeeksPct(data) {
|
|
427
|
+
return data?.ActiveWeeksPct ?? 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get weeks since registration.
|
|
432
|
+
* @param {Object} data - rankings_data object
|
|
433
|
+
* @returns {number}
|
|
434
|
+
*/
|
|
435
|
+
function getWeeksSinceRegistration(data) {
|
|
436
|
+
return data?.WeeksSinceRegistration ?? 0;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Get first activity date.
|
|
441
|
+
* @param {Object} data - rankings_data object
|
|
442
|
+
* @returns {Date|null}
|
|
443
|
+
*/
|
|
444
|
+
function getFirstActivity(data) {
|
|
445
|
+
return data?.FirstActivity ? new Date(data.FirstActivity) : null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get last activity date.
|
|
450
|
+
* @param {Object} data - rankings_data object
|
|
451
|
+
* @returns {Date|null}
|
|
452
|
+
*/
|
|
453
|
+
function getLastActivity(data) {
|
|
454
|
+
return data?.LastActivity ? new Date(data.LastActivity) : null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check if investor is verified.
|
|
459
|
+
* @param {Object} data - rankings_data object
|
|
460
|
+
* @returns {boolean}
|
|
461
|
+
*/
|
|
462
|
+
function isVerified(data) {
|
|
463
|
+
return data?.Verified === true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Check if investor is blocked.
|
|
468
|
+
* @param {Object} data - rankings_data object
|
|
469
|
+
* @returns {boolean}
|
|
470
|
+
*/
|
|
471
|
+
function isBlocked(data) {
|
|
472
|
+
return data?.Blocked === true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Check if investor is a Popular Investor.
|
|
477
|
+
* @param {Object} data - rankings_data object
|
|
478
|
+
* @returns {boolean}
|
|
479
|
+
*/
|
|
480
|
+
function isPopularInvestor(data) {
|
|
481
|
+
return data?.PopularInvestor === true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get country.
|
|
486
|
+
* @param {Object} data - rankings_data object
|
|
487
|
+
* @returns {string}
|
|
488
|
+
*/
|
|
489
|
+
function getCountry(data) {
|
|
490
|
+
return data?.Country || 'Unknown';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Get full name (if displayed).
|
|
495
|
+
* @param {Object} data - rankings_data object
|
|
496
|
+
* @returns {string|null}
|
|
497
|
+
*/
|
|
498
|
+
function getFullName(data) {
|
|
499
|
+
return data?.DisplayFullName ? data.FullName : null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// =============================================================================
|
|
503
|
+
// CALCULATED METRICS
|
|
504
|
+
// =============================================================================
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Calculate a simple PI quality score (0-100).
|
|
508
|
+
* @param {Object} data - rankings_data object
|
|
509
|
+
* @returns {number}
|
|
510
|
+
*/
|
|
511
|
+
function calculateQualityScore(data) {
|
|
512
|
+
let score = 50;
|
|
513
|
+
|
|
514
|
+
// Performance (+/- 20)
|
|
515
|
+
const gain = getTotalGain(data);
|
|
516
|
+
if (gain > 100) score += 20;
|
|
517
|
+
else if (gain > 50) score += 10;
|
|
518
|
+
else if (gain < 0) score -= 20;
|
|
519
|
+
else if (gain < 20) score -= 10;
|
|
520
|
+
|
|
521
|
+
// Risk (+/- 15)
|
|
522
|
+
const risk = getRiskScore(data);
|
|
523
|
+
if (risk <= 3) score += 15;
|
|
524
|
+
else if (risk <= 5) score += 5;
|
|
525
|
+
else if (risk >= 8) score -= 15;
|
|
526
|
+
else if (risk >= 7) score -= 5;
|
|
527
|
+
|
|
528
|
+
// Drawdown (+/- 10)
|
|
529
|
+
const drawdown = Math.abs(getPeakToValley(data));
|
|
530
|
+
if (drawdown < 15) score += 10;
|
|
531
|
+
else if (drawdown > 40) score -= 10;
|
|
532
|
+
|
|
533
|
+
// Win ratio (+/- 10)
|
|
534
|
+
const winRatio = getWinRatio(data);
|
|
535
|
+
if (winRatio >= 70) score += 10;
|
|
536
|
+
else if (winRatio < 50) score -= 10;
|
|
537
|
+
|
|
538
|
+
// Copiers (+/- 5)
|
|
539
|
+
const copierChange = getCopierChangePct(data);
|
|
540
|
+
if (copierChange > 10) score += 5;
|
|
541
|
+
else if (copierChange < -10) score -= 5;
|
|
542
|
+
|
|
543
|
+
return Math.max(0, Math.min(100, score));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Summarize a PI's key metrics.
|
|
548
|
+
* @param {Object} row - pi_rankings row
|
|
549
|
+
* @returns {Object}
|
|
550
|
+
*/
|
|
551
|
+
function summarize(row) {
|
|
552
|
+
const data = extractRankingsData(row);
|
|
553
|
+
if (!data) return null;
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
piId: getPiId(row),
|
|
557
|
+
username: getUsername(row),
|
|
558
|
+
rank: row?.rank,
|
|
559
|
+
|
|
560
|
+
// Performance
|
|
561
|
+
totalGain: getTotalGain(data),
|
|
562
|
+
weeklyGain: getWeeklyGain(data),
|
|
563
|
+
winRatio: getWinRatio(data),
|
|
564
|
+
profitableWeeksPct: getProfitableWeeksPct(data),
|
|
565
|
+
|
|
566
|
+
// Risk
|
|
567
|
+
riskScore: getRiskScore(data),
|
|
568
|
+
riskTier: getRiskTier(getRiskScore(data)),
|
|
569
|
+
maxDrawdown: getPeakToValley(data),
|
|
570
|
+
|
|
571
|
+
// AUM & Copiers
|
|
572
|
+
aum: getAUM(data),
|
|
573
|
+
aumTier: getAUMTierDesc(data),
|
|
574
|
+
copiers: getCopiers(data),
|
|
575
|
+
copierChange: getCopierChange(data),
|
|
576
|
+
|
|
577
|
+
// Trading style
|
|
578
|
+
exposure: getExposure(data),
|
|
579
|
+
longPct: getLongPosPct(data),
|
|
580
|
+
avgPosSize: getAvgPosSize(data),
|
|
581
|
+
|
|
582
|
+
// Quality
|
|
583
|
+
qualityScore: calculateQualityScore(data),
|
|
584
|
+
|
|
585
|
+
// Status
|
|
586
|
+
isVerified: isVerified(data),
|
|
587
|
+
country: getCountry(data)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
module.exports = {
|
|
592
|
+
// Data extraction
|
|
593
|
+
extractRankingsData,
|
|
594
|
+
getPiId,
|
|
595
|
+
getUsername,
|
|
596
|
+
|
|
597
|
+
// Risk metrics
|
|
598
|
+
getRiskScore,
|
|
599
|
+
getMaxDailyRiskScore,
|
|
600
|
+
getMaxMonthlyRiskScore,
|
|
601
|
+
getDailyDrawdown,
|
|
602
|
+
getWeeklyDrawdown,
|
|
603
|
+
getPeakToValley,
|
|
604
|
+
getRiskTier,
|
|
605
|
+
|
|
606
|
+
// Performance metrics
|
|
607
|
+
getTotalGain,
|
|
608
|
+
getDailyGain,
|
|
609
|
+
getWeeklyGain,
|
|
610
|
+
getWinRatio,
|
|
611
|
+
getProfitableWeeksPct,
|
|
612
|
+
getProfitableMonthsPct,
|
|
613
|
+
getCopiersGain,
|
|
614
|
+
|
|
615
|
+
// AUM & Copiers
|
|
616
|
+
getAUM,
|
|
617
|
+
getAUMTier,
|
|
618
|
+
getAUMTierDesc,
|
|
619
|
+
getCopiers,
|
|
620
|
+
getBaselineCopiers,
|
|
621
|
+
getCopierChange,
|
|
622
|
+
getCopierChangePct,
|
|
623
|
+
|
|
624
|
+
// Trading style
|
|
625
|
+
getExposure,
|
|
626
|
+
getLongPosPct,
|
|
627
|
+
getShortPosPct,
|
|
628
|
+
getAvgPosSize,
|
|
629
|
+
getTotalTrades,
|
|
630
|
+
getTotalInstruments,
|
|
631
|
+
getLeverageBreakdown,
|
|
632
|
+
getTradingStyle,
|
|
633
|
+
|
|
634
|
+
// Top asset
|
|
635
|
+
getTopInstrumentId,
|
|
636
|
+
getTopInstrumentPct,
|
|
637
|
+
getTopAssetClassId,
|
|
638
|
+
getTags,
|
|
639
|
+
|
|
640
|
+
// Activity & Status
|
|
641
|
+
getActiveWeeks,
|
|
642
|
+
getActiveWeeksPct,
|
|
643
|
+
getWeeksSinceRegistration,
|
|
644
|
+
getFirstActivity,
|
|
645
|
+
getLastActivity,
|
|
646
|
+
isVerified,
|
|
647
|
+
isBlocked,
|
|
648
|
+
isPopularInvestor,
|
|
649
|
+
getCountry,
|
|
650
|
+
getFullName,
|
|
651
|
+
|
|
652
|
+
// Calculated
|
|
653
|
+
calculateQualityScore,
|
|
654
|
+
summarize
|
|
655
|
+
};
|