aiden-shared-calculations-unified 1.0.21 → 1.0.23
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/activity/historical/daily_user_activity_tracker.js +144 -0
- package/calculations/asset_metrics/asset_position_size.js +6 -5
- package/calculations/behavioural/historical/drawdown_response.js +10 -7
- package/calculations/behavioural/historical/gain_response.js +10 -6
- package/calculations/behavioural/historical/position_count_pnl.js +23 -5
- package/calculations/sectors/historical/diversification_pnl.js +24 -6
- package/calculations/speculators/historical/tsl_effectiveness.js +25 -5
- package/package.json +1 -1
- package/calculations/asset_metrics/asset_dollar_metrics.js +0 -51
- package/calculations/sectors/sector_dollar_metrics.js +0 -49
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracks user activity by comparing portfolio snapshots.
|
|
3
|
+
* This is a historical calculation that defines an "active user" as someone
|
|
4
|
+
* who has opened, closed, or reallocated a position within the last 24 hours.
|
|
5
|
+
*
|
|
6
|
+
* This provides the "Daily Active Users" count for the monitored cohort.
|
|
7
|
+
* This depreciates the user activity sampler cloud function which was inefficient for the api usage and now provides this data for free
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class DailyUserActivityTracker {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.activeUserIds = new Set();
|
|
13
|
+
this.activityEvents = {
|
|
14
|
+
new_position: 0,
|
|
15
|
+
closed_position: 0,
|
|
16
|
+
reallocation: 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper to get a simplified map of positions for comparison.
|
|
22
|
+
* @param {object} portfolio - A user's full portfolio object.
|
|
23
|
+
* @returns {object} { posMap: Map<InstrumentID, {invested: number}>, hasAggregated: boolean }
|
|
24
|
+
*/
|
|
25
|
+
_getPortfolioMaps(portfolio) {
|
|
26
|
+
// Prioritize AggregatedPositions, but fall back to PublicPositions
|
|
27
|
+
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
28
|
+
if (!positions || !Array.isArray(positions)) {
|
|
29
|
+
return { posMap: new Map(), hasAggregated: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const posMap = new Map();
|
|
33
|
+
for (const pos of positions) {
|
|
34
|
+
const key = pos.InstrumentID;
|
|
35
|
+
if (key) {
|
|
36
|
+
posMap.set(key, {
|
|
37
|
+
// 'InvestedAmount' or 'Invested' is the portfolio percentage
|
|
38
|
+
// We use this for reallocation logic.
|
|
39
|
+
invested: pos.InvestedAmount || pos.Invested || pos.Amount || 0
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Return the map and a flag indicating if we can trust the 'invested' field
|
|
44
|
+
return { posMap, hasAggregated: !!portfolio.AggregatedPositions };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Processes a single user's daily data.
|
|
49
|
+
*/
|
|
50
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
51
|
+
// This calculation requires both days to find changes.
|
|
52
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { posMap: yPosMap, hasAggregated: yHasAgg } = this._getPortfolioMaps(yesterdayPortfolio);
|
|
57
|
+
const { posMap: tPosMap, hasAggregated: tHasAgg } = this._getPortfolioMaps(todayPortfolio);
|
|
58
|
+
|
|
59
|
+
// Skip if user has no positions on either day
|
|
60
|
+
if (tPosMap.size === 0 && yPosMap.size === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const yIds = new Set(yPosMap.keys());
|
|
65
|
+
const tIds = new Set(tPosMap.keys());
|
|
66
|
+
let isActive = false;
|
|
67
|
+
|
|
68
|
+
// 1. Check for new positions (high-confidence activity)
|
|
69
|
+
for (const tId of tIds) {
|
|
70
|
+
if (!yIds.has(tId)) {
|
|
71
|
+
isActive = true;
|
|
72
|
+
this.activityEvents.new_position++;
|
|
73
|
+
break; // Found activity, no need to check more
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isActive) {
|
|
78
|
+
this.activeUserIds.add(userId);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Check for closed positions (high-confidence activity)
|
|
83
|
+
for (const yId of yIds) {
|
|
84
|
+
if (!tIds.has(yId)) {
|
|
85
|
+
isActive = true;
|
|
86
|
+
this.activityEvents.closed_position++;
|
|
87
|
+
break; // Found activity
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isActive) {
|
|
92
|
+
this.activeUserIds.add(userId);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
|
|
97
|
+
// This checks for changes in the 'Invested' percentage
|
|
98
|
+
if (yHasAgg && tHasAgg) {
|
|
99
|
+
for (const tId of tIds) {
|
|
100
|
+
// We know tId is also in yIds from the checks above
|
|
101
|
+
const tInvested = tPosMap.get(tId).invested;
|
|
102
|
+
const yInvested = yPosMap.get(yId).invested;
|
|
103
|
+
|
|
104
|
+
// Check for a meaningful change (e.g., > 0.01% to avoid float noise)
|
|
105
|
+
if (Math.abs(tInvested - yInvested) > 0.0001) {
|
|
106
|
+
isActive = true;
|
|
107
|
+
this.activityEvents.reallocation++;
|
|
108
|
+
break; // Found activity
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isActive) {
|
|
114
|
+
this.activeUserIds.add(userId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the final aggregated counts for the day.
|
|
120
|
+
*/
|
|
121
|
+
getResult() {
|
|
122
|
+
return {
|
|
123
|
+
// This is the main metric for your graph
|
|
124
|
+
rawActiveUserCount: this.activeUserIds.size,
|
|
125
|
+
|
|
126
|
+
// This is a bonus metric to see *what* users are doing
|
|
127
|
+
activityBreakdown: this.activityEvents
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resets the counters for the next run.
|
|
133
|
+
*/
|
|
134
|
+
reset() {
|
|
135
|
+
this.activeUserIds.clear();
|
|
136
|
+
this.activityEvents = {
|
|
137
|
+
new_position: 0,
|
|
138
|
+
closed_position: 0,
|
|
139
|
+
reallocation: 0
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = DailyUserActivityTracker;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Calculates the average position size for each asset.
|
|
2
|
+
* Calculates the average position size (as a portfolio percentage) for each asset.
|
|
3
3
|
*/
|
|
4
4
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
5
|
|
|
@@ -22,7 +22,8 @@ class AssetPositionSize {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
this.assets[instrumentId].position_count++;
|
|
25
|
-
|
|
25
|
+
// FIX: Use the 'Invested' field, which holds the portfolio percentage
|
|
26
|
+
this.assets[instrumentId].position_value_sum += (position.Invested || 0);
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -36,11 +37,11 @@ class AssetPositionSize {
|
|
|
36
37
|
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
37
38
|
const data = this.assets[instrumentId];
|
|
38
39
|
|
|
39
|
-
// REFACTOR: Perform final calculation and return in standardized format.
|
|
40
40
|
if (data.position_count > 0) {
|
|
41
41
|
result[ticker] = {
|
|
42
|
+
// This is now the average *percentage* size
|
|
42
43
|
average_position_size: data.position_value_sum / data.position_count,
|
|
43
|
-
position_count: data.position_count
|
|
44
|
+
position_count: data.position_count
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -54,4 +55,4 @@ class AssetPositionSize {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
module.exports = AssetPositionSize;
|
|
58
|
+
module.exports = AssetPositionSize;
|
|
@@ -15,7 +15,6 @@ class DrawdownResponse {
|
|
|
15
15
|
return; // Need both days for comparison
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// FIX: Get the correct positions arrays and ensure they are iterable
|
|
19
18
|
const yPositions = yesterdayPortfolio.AggregatedPositions || yesterdayPortfolio.PublicPositions;
|
|
20
19
|
const tPositions = todayPortfolio.AggregatedPositions || todayPortfolio.PublicPositions;
|
|
21
20
|
|
|
@@ -23,20 +22,24 @@ class DrawdownResponse {
|
|
|
23
22
|
return;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
//
|
|
27
|
-
const todayPositions = new Map(tPositions.map(p => [p.PositionID, p]));
|
|
25
|
+
// Use PositionID if available (as in original file), fallback to InstrumentID
|
|
26
|
+
const todayPositions = new Map(tPositions.map(p => [p.PositionID || p.InstrumentID, p]));
|
|
28
27
|
|
|
29
28
|
for (const yPos of yPositions) {
|
|
30
|
-
|
|
29
|
+
// FIX: Use the NetProfit field, which is already a percentage.
|
|
30
|
+
// Your data sample (e.g., -83.6) shows the threshold should be -10.0.
|
|
31
|
+
const drawdownPercent = yPos.NetProfit || 0;
|
|
32
|
+
const yPosId = yPos.PositionID || yPos.InstrumentID;
|
|
31
33
|
|
|
32
34
|
// Check if this position was in a >10% drawdown yesterday
|
|
33
|
-
if (drawdownPercent < -0
|
|
34
|
-
const todayPos = todayPositions.get(
|
|
35
|
+
if (drawdownPercent < -10.0) {
|
|
36
|
+
const todayPos = todayPositions.get(yPosId);
|
|
35
37
|
|
|
36
38
|
if (!todayPos) {
|
|
37
39
|
// Position was closed
|
|
38
40
|
this.drawdown_events.closed_position++;
|
|
39
|
-
} else if (todayPos.
|
|
41
|
+
} else if (todayPos.Invested > yPos.Invested) {
|
|
42
|
+
// FIX: Use 'Invested' (percentage) to check for increase
|
|
40
43
|
// User added money to the losing position
|
|
41
44
|
this.drawdown_events.added_to_position++;
|
|
42
45
|
} else {
|
|
@@ -15,7 +15,6 @@ class GainResponse {
|
|
|
15
15
|
return; // Need both days for comparison
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// FIX: Get the correct positions arrays and ensure they are iterable
|
|
19
18
|
const yPositions = yesterdayPortfolio.AggregatedPositions || yesterdayPortfolio.PublicPositions;
|
|
20
19
|
const tPositions = todayPortfolio.AggregatedPositions || todayPortfolio.PublicPositions;
|
|
21
20
|
|
|
@@ -23,19 +22,24 @@ class GainResponse {
|
|
|
23
22
|
return;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
// Use PositionID if available (as in original file), fallback to InstrumentID
|
|
26
|
+
const todayPositions = new Map(tPositions.map(p => [p.PositionID || p.InstrumentID, p]));
|
|
27
27
|
|
|
28
28
|
for (const yPos of yPositions) {
|
|
29
|
-
|
|
29
|
+
// FIX: Use the NetProfit field, which is already a percentage.
|
|
30
|
+
// Your data sample (e.g., 23.5) shows the threshold should be 10.0.
|
|
31
|
+
const gainPercent = yPos.NetProfit || 0;
|
|
32
|
+
const yPosId = yPos.PositionID || yPos.InstrumentID;
|
|
30
33
|
|
|
31
34
|
// Check if this position was in a >10% gain yesterday
|
|
32
|
-
if (gainPercent > 0
|
|
33
|
-
const todayPos = todayPositions.get(
|
|
35
|
+
if (gainPercent > 10.0) {
|
|
36
|
+
const todayPos = todayPositions.get(yPosId);
|
|
34
37
|
|
|
35
38
|
if (!todayPos) {
|
|
36
39
|
// Position was closed (took full profit)
|
|
37
40
|
this.gain_events.closed_position++;
|
|
38
|
-
} else if (todayPos.
|
|
41
|
+
} else if (todayPos.Invested < yPos.Invested) {
|
|
42
|
+
// FIX: Use 'Invested' (percentage) to check for reduction
|
|
39
43
|
// User reduced the position (took partial profit)
|
|
40
44
|
this.gain_events.reduced_position++;
|
|
41
45
|
} else {
|
|
@@ -14,15 +14,28 @@ class PositionCountPnl {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* FIX: Helper function to calculate total P&L from positions
|
|
19
|
+
* @param {object} portfolio
|
|
20
|
+
* @returns {number|null}
|
|
21
|
+
*/
|
|
22
|
+
_calculateTotalPnl(portfolio) {
|
|
23
|
+
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
24
|
+
if (positions && Array.isArray(positions)) {
|
|
25
|
+
// Sum all NetProfit fields, defaulting to 0 if a position has no NetProfit
|
|
26
|
+
return positions.reduce((sum, pos) => sum + (pos.NetProfit || 0), 0);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
// FIX: Only need todayPortfolio for this logic
|
|
33
|
+
if (!todayPortfolio) {
|
|
34
|
+
return;
|
|
20
35
|
}
|
|
21
36
|
|
|
22
|
-
// FIX: Get the correct positions array
|
|
23
37
|
const positions = todayPortfolio.AggregatedPositions || todayPortfolio.PublicPositions;
|
|
24
38
|
|
|
25
|
-
// FIX: Add check to ensure positions is an iterable array
|
|
26
39
|
if (!positions || !Array.isArray(positions)) {
|
|
27
40
|
return; // Skip users with no positions array
|
|
28
41
|
}
|
|
@@ -32,7 +45,12 @@ class PositionCountPnl {
|
|
|
32
45
|
return; // Skip users with no positions
|
|
33
46
|
}
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
// FIX: Calculate dailyPnl by summing NetProfit from all positions
|
|
49
|
+
const dailyPnl = this._calculateTotalPnl(todayPortfolio);
|
|
50
|
+
|
|
51
|
+
if (dailyPnl === null) {
|
|
52
|
+
return; // Cannot calculate P&L for this user
|
|
53
|
+
}
|
|
36
54
|
|
|
37
55
|
this._initBucket(positionCount);
|
|
38
56
|
this.pnl_by_position_count[positionCount].pnl_sum += dailyPnl;
|
|
@@ -5,11 +5,9 @@ const { getInstrumentSectorMap } = require('../../../utils/sector_mapping_provid
|
|
|
5
5
|
* Aggregates P/L by the number of unique sectors a user is invested in.
|
|
6
6
|
*/
|
|
7
7
|
class DiversificationPnl {
|
|
8
|
-
// ... (rest of the code is unchanged) ...
|
|
9
8
|
constructor() {
|
|
10
9
|
this.pnl_by_sector_count = {};
|
|
11
|
-
|
|
12
|
-
this.sectorMapping = null; // Changed: Load async in process
|
|
10
|
+
this.sectorMapping = null;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
_initBucket(count) {
|
|
@@ -18,11 +16,26 @@ class DiversificationPnl {
|
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
18
|
|
|
19
|
+
/**
|
|
20
|
+
* FIX: Helper function to calculate total P&L from positions
|
|
21
|
+
* @param {object} portfolio
|
|
22
|
+
* @returns {number|null}
|
|
23
|
+
*/
|
|
24
|
+
_calculateTotalPnl(portfolio) {
|
|
25
|
+
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
26
|
+
if (positions && Array.isArray(positions)) {
|
|
27
|
+
// Sum all NetProfit fields, defaulting to 0 if a position has no NetProfit
|
|
28
|
+
return positions.reduce((sum, pos) => sum + (pos.NetProfit || 0), 0);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
async process(todayPortfolio, yesterdayPortfolio, userId) { // Added async
|
|
22
|
-
|
|
34
|
+
// FIX: Only need todayPortfolio for this logic
|
|
35
|
+
if (!todayPortfolio) {
|
|
23
36
|
return;
|
|
24
37
|
}
|
|
25
|
-
|
|
38
|
+
|
|
26
39
|
if(!this.sectorMapping) {
|
|
27
40
|
this.sectorMapping = await getInstrumentSectorMap();
|
|
28
41
|
}
|
|
@@ -44,7 +57,12 @@ class DiversificationPnl {
|
|
|
44
57
|
return;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
// FIX: Calculate dailyPnl by summing NetProfit from all positions
|
|
61
|
+
const dailyPnl = this._calculateTotalPnl(todayPortfolio);
|
|
62
|
+
|
|
63
|
+
if (dailyPnl === null) {
|
|
64
|
+
return; // Cannot calculate P&L for this user
|
|
65
|
+
}
|
|
48
66
|
|
|
49
67
|
this._initBucket(sectorCount);
|
|
50
68
|
this.pnl_by_sector_count[sectorCount].pnl_sum += dailyPnl;
|
|
@@ -8,21 +8,41 @@ class TslEffectiveness {
|
|
|
8
8
|
this.nontsl_group = { pnl_sum: 0, count: 0 };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* FIX: Helper function to calculate total P&L from positions
|
|
13
|
+
* @param {object} portfolio
|
|
14
|
+
* @returns {number|null}
|
|
15
|
+
*/
|
|
16
|
+
_calculateTotalPnl(portfolio) {
|
|
17
|
+
// Speculators use PublicPositions
|
|
18
|
+
const positions = portfolio?.PublicPositions;
|
|
19
|
+
if (positions && Array.isArray(positions)) {
|
|
20
|
+
// Sum all NetProfit fields, defaulting to 0 if a position has no NetProfit
|
|
21
|
+
return positions.reduce((sum, pos) => sum + (pos.NetProfit || 0), 0);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
12
|
-
// Check if user is a speculator and we have
|
|
13
|
-
|
|
27
|
+
// Check if user is a speculator and we have today's data
|
|
28
|
+
// FIX: yesterdayPortfolio is not needed for this logic, only today's P&L
|
|
29
|
+
if (todayPortfolio?.context?.userType !== 'speculator' || !todayPortfolio) {
|
|
14
30
|
return;
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
// FIX: This calculation is for speculators, so we use PublicPositions
|
|
18
33
|
const positions = todayPortfolio.PublicPositions;
|
|
19
34
|
|
|
20
|
-
// FIX: Add check to ensure positions is an iterable array
|
|
21
35
|
if (!positions || !Array.isArray(positions)) {
|
|
22
36
|
return;
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
// FIX: Calculate dailyPnl by summing NetProfit from all positions
|
|
40
|
+
const dailyPnl = this._calculateTotalPnl(todayPortfolio);
|
|
41
|
+
|
|
42
|
+
if (dailyPnl === null) {
|
|
43
|
+
return; // Cannot calculate P&L
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
const usesTSL = positions.some(p => p.IsTslEnabled);
|
|
27
47
|
|
|
28
48
|
if (usesTSL) {
|
package/package.json
CHANGED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aggregates the total dollar amount invested in assets,
|
|
3
|
-
* broken down by profit or loss (from our sample).
|
|
4
|
-
*/
|
|
5
|
-
class AssetDollarMetrics {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.assets = {};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
_initAsset(ticker) {
|
|
11
|
-
if (!this.assets[ticker]) {
|
|
12
|
-
this.assets[ticker] = {
|
|
13
|
-
total_invested_sum: 0,
|
|
14
|
-
profit_invested_sum: 0,
|
|
15
|
-
loss_invested_sum: 0
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
|
|
21
|
-
const { instrumentMappings } = context;
|
|
22
|
-
|
|
23
|
-
// FIX: Use the correct portfolio position properties
|
|
24
|
-
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
25
|
-
if (!positions || !Array.isArray(positions)) return;
|
|
26
|
-
|
|
27
|
-
for (const position of positions) {
|
|
28
|
-
// FIX: Use the correct PascalCase InstrumentID
|
|
29
|
-
const ticker = instrumentMappings[position.InstrumentID];
|
|
30
|
-
if (!ticker) continue;
|
|
31
|
-
|
|
32
|
-
this._initAsset(ticker);
|
|
33
|
-
|
|
34
|
-
// FIX: Use the correct PascalCase InvestedAmount
|
|
35
|
-
this.assets[ticker].total_invested_sum += position.InvestedAmount;
|
|
36
|
-
|
|
37
|
-
// FIX: Use the correct PascalCase NetProfit
|
|
38
|
-
if (position.NetProfit > 0) {
|
|
39
|
-
this.assets[ticker].profit_invested_sum += position.InvestedAmount;
|
|
40
|
-
} else {
|
|
41
|
-
this.assets[ticker].loss_invested_sum += position.InvestedAmount;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getResult() {
|
|
47
|
-
return this.assets;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
module.exports = AssetDollarMetrics;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aggregates the total dollar amount invested in sectors,
|
|
3
|
-
* broken down by profit or loss (from our sample).
|
|
4
|
-
*/
|
|
5
|
-
class SectorDollarMetrics {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.sectors = {};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
_initSector(sector) {
|
|
11
|
-
if (!this.sectors[sector]) {
|
|
12
|
-
this.sectors[sector] = {
|
|
13
|
-
total_invested_sum: 0,
|
|
14
|
-
profit_invested_sum: 0,
|
|
15
|
-
loss_invested_sum: 0
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
|
|
21
|
-
const { sectorMapping } = context; // Assumes sectorMapping is in context
|
|
22
|
-
|
|
23
|
-
// FIX: Use the correct portfolio position properties
|
|
24
|
-
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
25
|
-
if (!positions || !Array.isArray(positions)) return;
|
|
26
|
-
|
|
27
|
-
for (const position of positions) {
|
|
28
|
-
// FIX: Use the correct PascalCase InstrumentID
|
|
29
|
-
const sector = sectorMapping[position.InstrumentID] || 'Other';
|
|
30
|
-
this._initSector(sector);
|
|
31
|
-
|
|
32
|
-
// FIX: Use the correct PascalCase InvestedAmount
|
|
33
|
-
this.sectors[sector].total_invested_sum += position.InvestedAmount;
|
|
34
|
-
|
|
35
|
-
// FIX: Use the correct PascalCase NetProfit
|
|
36
|
-
if (position.NetProfit > 0) {
|
|
37
|
-
this.sectors[sector].profit_invested_sum += position.InvestedAmount;
|
|
38
|
-
} else {
|
|
39
|
-
this.sectors[sector].loss_invested_sum += position.InvestedAmount;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
getResult() {
|
|
45
|
-
return this.sectors;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = SectorDollarMetrics;
|