aiden-shared-calculations-unified 1.0.21 → 1.0.22
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/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
|
@@ -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;
|