aiden-shared-calculations-unified 1.0.82 → 1.0.84
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 +122 -104
- package/calculations/core/asset-position-size.js +110 -73
- package/calculations/core/average-daily-pnl-all-users.js +17 -3
- package/calculations/core/average-daily-pnl-per-sector.js +83 -75
- package/calculations/core/average-daily-pnl-per-stock.js +84 -73
- package/calculations/core/average-daily-position-pnl.js +2 -2
- package/calculations/core/holding-duration-per-asset.js +24 -23
- package/calculations/core/instrument-price-change-1d.js +72 -82
- package/calculations/core/instrument-price-momentum-20d.js +66 -100
- package/calculations/core/long-position-per-stock.js +21 -13
- package/calculations/core/overall-holding-duration.js +8 -3
- package/calculations/core/overall-profitability-ratio.js +2 -2
- package/calculations/core/platform-buy-sell-sentiment.js +75 -22
- package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
- package/calculations/core/platform-daily-ownership-delta.js +39 -15
- package/calculations/core/platform-ownership-per-sector.js +38 -18
- package/calculations/core/platform-total-positions-held.js +36 -14
- package/calculations/core/pnl-distribution-per-stock.js +39 -36
- package/calculations/core/price-metrics.js +70 -172
- package/calculations/core/profitability-ratio-per-sector.js +23 -29
- package/calculations/core/profitability-ratio-per-stock.js +20 -13
- package/calculations/core/profitability-skew-per-stock.js +20 -13
- package/calculations/core/profitable-and-unprofitable-status.js +34 -10
- package/calculations/core/sentiment-per-stock.js +20 -9
- package/calculations/core/short-position-per-stock.js +23 -37
- package/calculations/core/social-activity-aggregation.js +41 -115
- package/calculations/core/social-asset-posts-trend.js +77 -94
- package/calculations/core/social-event-correlation.js +87 -106
- package/calculations/core/social-sentiment-aggregation.js +56 -138
- package/calculations/core/social-top-mentioned-words.js +74 -106
- package/calculations/core/social-topic-interest-evolution.js +94 -94
- package/calculations/core/social-topic-sentiment-matrix.js +90 -74
- package/calculations/core/social-word-mentions-trend.js +92 -106
- package/calculations/core/speculator-asset-sentiment.js +63 -92
- package/calculations/core/speculator-danger-zone.js +77 -90
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
- package/calculations/core/speculator-leverage-per-asset.js +62 -57
- package/calculations/core/speculator-leverage-per-sector.js +53 -65
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
- package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
- package/calculations/core/speculator-take-profit-per-asset.js +45 -69
- package/calculations/core/speculator-tsl-per-asset.js +42 -49
- package/calculations/core/total-long-figures.js +19 -19
- package/calculations/core/total-long-per-sector.js +39 -36
- package/calculations/core/total-short-figures.js +19 -19
- package/calculations/core/total-short-per-sector.js +39 -36
- package/calculations/core/users-processed.js +52 -25
- package/calculations/gauss/cohort-capital-flow.js +38 -29
- package/calculations/gauss/cohort-definer.js +17 -25
- package/calculations/gauss/daily-dna-filter.js +10 -4
- package/calculations/gauss/gauss-divergence-signal.js +28 -6
- package/calculations/gem/cohort-momentum-state.js +113 -92
- package/calculations/gem/cohort-skill-definition.js +23 -53
- package/calculations/gem/platform-conviction-divergence.js +62 -116
- package/calculations/gem/quant-skill-alpha-signal.js +107 -123
- package/calculations/gem/skilled-cohort-flow.js +178 -167
- package/calculations/gem/skilled-unskilled-divergence.js +73 -113
- package/calculations/gem/unskilled-cohort-flow.js +176 -166
- package/calculations/helix/helix-contrarian-signal.js +91 -83
- package/calculations/helix/herd-consensus-score.js +135 -97
- package/calculations/helix/winner-loser-flow.js +14 -14
- package/calculations/pyro/risk-appetite-index.js +121 -123
- package/calculations/pyro/squeeze-potential.js +93 -125
- package/calculations/pyro/volatility-signal.js +109 -97
- package/package.json +9 -9
- package/README.MD +0 -78
|
@@ -1,129 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Core Metric (Pass 2)
|
|
3
3
|
*
|
|
4
|
-
* This
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* is performing well or poorly in.
|
|
4
|
+
* This 'standard' calculation streams all user portfolios
|
|
5
|
+
* and calculates the average daily P&L (in %) for all
|
|
6
|
+
* users, bucketed by sector.
|
|
8
7
|
*/
|
|
9
|
-
|
|
8
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
10
9
|
|
|
11
10
|
class AverageDailyPnlPerSector {
|
|
12
11
|
constructor() {
|
|
13
|
-
//
|
|
14
|
-
this.
|
|
15
|
-
|
|
12
|
+
// { [sector]: { pnl_sum: 0, count: 0 } }
|
|
13
|
+
this.sectorBuckets = new Map();
|
|
14
|
+
|
|
15
|
+
// --- STANDARD 0: RENAMED ---
|
|
16
|
+
this.sectorMap = null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Statically defines all metadata for the manifest builder.
|
|
21
|
-
*/
|
|
19
|
+
/** Statically defines metadata */
|
|
22
20
|
static getMetadata() {
|
|
23
21
|
return {
|
|
24
22
|
type: 'standard',
|
|
25
|
-
rootDataDependencies: ['portfolio'],
|
|
26
|
-
isHistorical: false,
|
|
23
|
+
rootDataDependencies: ['portfolio'],
|
|
24
|
+
isHistorical: false, // Reads today's portfolio
|
|
27
25
|
userType: 'all',
|
|
28
|
-
category: '
|
|
26
|
+
category: 'core'
|
|
29
27
|
};
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Statically declare dependencies.
|
|
35
|
-
*/
|
|
30
|
+
/** Statically declare dependencies */
|
|
36
31
|
static getDependencies() {
|
|
37
32
|
return [];
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
/**
|
|
41
36
|
* Defines the output schema for this calculation.
|
|
42
|
-
* @returns {object} JSON Schema object
|
|
43
37
|
*/
|
|
44
38
|
static getSchema() {
|
|
45
|
-
const
|
|
39
|
+
const schema = {
|
|
46
40
|
"type": "object",
|
|
47
|
-
"description": "P&L metrics for a specific sector.",
|
|
48
41
|
"properties": {
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
"pnl_sum": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "Sum of all P&L for this sector."
|
|
56
|
-
},
|
|
57
|
-
"user_count": {
|
|
58
|
-
"type": "number",
|
|
59
|
-
"description": "Count of users with positions in this sector."
|
|
60
|
-
}
|
|
42
|
+
"avg_daily_pnl_pct": { "type": "number" },
|
|
43
|
+
"total_pnl_pct": { "type": "number" },
|
|
44
|
+
"user_count": { "type": "number" }
|
|
61
45
|
},
|
|
62
|
-
"required": ["
|
|
46
|
+
"required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
|
|
63
47
|
};
|
|
64
|
-
|
|
48
|
+
|
|
65
49
|
return {
|
|
66
50
|
"type": "object",
|
|
67
|
-
"description": "Calculates the
|
|
68
|
-
"patternProperties": {
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
"additionalProperties": sectorSchema
|
|
51
|
+
"description": "Calculates the avg. daily P&L % for all users, bucketed by sector.",
|
|
52
|
+
"patternProperties": { "^.*$": schema },
|
|
53
|
+
"additionalProperties": schema
|
|
72
54
|
};
|
|
73
55
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!
|
|
77
|
-
|
|
78
|
-
pnl_sum: 0,
|
|
79
|
-
users: new Set() // Use Set to count unique users
|
|
80
|
-
});
|
|
56
|
+
|
|
57
|
+
_init(map, key) {
|
|
58
|
+
if (!map.has(key)) {
|
|
59
|
+
map.set(key, { pnl_sum: 0, count: 0 });
|
|
81
60
|
}
|
|
82
61
|
}
|
|
83
62
|
|
|
84
|
-
process(
|
|
85
|
-
//
|
|
86
|
-
if (!this.
|
|
87
|
-
|
|
63
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
64
|
+
// --- STANDARD 0: FIXED ---
|
|
65
|
+
if (!this.sectorMap) {
|
|
66
|
+
this.sectorMap = context.sectorMapping;
|
|
88
67
|
}
|
|
68
|
+
|
|
69
|
+
if (!this.sectorMap) {
|
|
70
|
+
return; // Failsafe if context is missing map
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let dailyPnl = 0;
|
|
74
|
+
|
|
75
|
+
// Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
|
|
76
|
+
if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
|
|
77
|
+
|
|
78
|
+
dailyPnl = todayPortfolio.NetProfit;
|
|
89
79
|
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
// Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
|
|
81
|
+
} else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
|
|
82
|
+
|
|
83
|
+
// Sum the P&L by calculating (Value - Invested) from the aggregates
|
|
84
|
+
dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
|
|
85
|
+
return sum + (agg.Value - agg.Invested);
|
|
86
|
+
}, 0);
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
if (dailyPnl === 0) return; // No P&L
|
|
90
|
+
|
|
91
|
+
const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
|
|
92
|
+
if (!positions || positions.length === 0) {
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// We only care about sectors this user *holds*
|
|
97
|
+
const heldSectors = new Set();
|
|
95
98
|
for (const pos of positions) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
if (!pos.InstrumentID) continue;
|
|
100
|
+
// --- STANDARD 0: SIMPLIFIED ---
|
|
101
|
+
const sector = this.sectorMap[pos.InstrumentID];
|
|
102
|
+
if (sector) {
|
|
103
|
+
heldSectors.add(sector);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const sector of heldSectors) {
|
|
108
|
+
this._init(this.sectorBuckets, sector);
|
|
109
|
+
const sec = this.sectorBuckets.get(sector);
|
|
110
|
+
sec.pnl_sum += dailyPnl;
|
|
111
|
+
sec.count++;
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
getResult() {
|
|
115
|
+
async getResult() {
|
|
109
116
|
const result = {};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
117
|
+
|
|
118
|
+
for (const [sector, data] of this.sectorBuckets.entries()) {
|
|
119
|
+
if (data.count > 0) {
|
|
113
120
|
result[sector] = {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
user_count:
|
|
121
|
+
avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
|
|
122
|
+
total_pnl_pct: data.pnl_sum * 100,
|
|
123
|
+
user_count: data.count
|
|
117
124
|
};
|
|
118
125
|
}
|
|
119
126
|
}
|
|
127
|
+
|
|
120
128
|
return result;
|
|
121
129
|
}
|
|
122
|
-
|
|
130
|
+
|
|
123
131
|
reset() {
|
|
124
|
-
this.
|
|
125
|
-
|
|
132
|
+
this.sectorBuckets.clear();
|
|
133
|
+
// --- STANDARD 0: RENAMED ---
|
|
134
|
+
this.sectorMap = null;
|
|
126
135
|
}
|
|
127
136
|
}
|
|
128
|
-
|
|
129
137
|
module.exports = AverageDailyPnlPerSector;
|
|
@@ -1,126 +1,137 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Core Metric (Pass 2)
|
|
3
3
|
*
|
|
4
|
-
* This
|
|
5
|
-
*
|
|
4
|
+
* This 'standard' calculation streams all user portfolios
|
|
5
|
+
* and calculates the average daily P&L (in %) for all
|
|
6
|
+
* users, bucketed by stock ticker.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
8
9
|
|
|
9
10
|
class AverageDailyPnlPerStock {
|
|
10
11
|
constructor() {
|
|
11
|
-
//
|
|
12
|
-
this.
|
|
13
|
-
|
|
12
|
+
// { [ticker]: { pnl_sum: 0, count: 0 } }
|
|
13
|
+
this.tickerBuckets = new Map();
|
|
14
|
+
|
|
15
|
+
// --- STANDARD 0: RENAMED ---
|
|
16
|
+
this.tickerMap = null;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Statically defines all metadata for the manifest builder.
|
|
19
|
-
*/
|
|
19
|
+
/** Statically defines metadata */
|
|
20
20
|
static getMetadata() {
|
|
21
21
|
return {
|
|
22
22
|
type: 'standard',
|
|
23
|
-
rootDataDependencies: ['portfolio'],
|
|
24
|
-
isHistorical: false,
|
|
23
|
+
rootDataDependencies: ['portfolio'],
|
|
24
|
+
isHistorical: false, // Reads today's portfolio
|
|
25
25
|
userType: 'all',
|
|
26
|
-
category: '
|
|
26
|
+
category: 'core'
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Statically declare dependencies.
|
|
33
|
-
*/
|
|
30
|
+
/** Statically declare dependencies */
|
|
34
31
|
static getDependencies() {
|
|
35
32
|
return [];
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
/**
|
|
39
36
|
* Defines the output schema for this calculation.
|
|
40
|
-
* @returns {object} JSON Schema object
|
|
41
37
|
*/
|
|
42
38
|
static getSchema() {
|
|
43
|
-
const
|
|
39
|
+
const schema = {
|
|
44
40
|
"type": "object",
|
|
45
|
-
"description": "P&L metrics for a specific asset ticker.",
|
|
46
41
|
"properties": {
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
"pnl_sum": {
|
|
52
|
-
"type": "number",
|
|
53
|
-
"description": "Sum of all P&L for this asset."
|
|
54
|
-
},
|
|
55
|
-
"position_count": {
|
|
56
|
-
"type": "number",
|
|
57
|
-
"description": "Count of positions in this asset."
|
|
58
|
-
}
|
|
42
|
+
"avg_daily_pnl_pct": { "type": "number" },
|
|
43
|
+
"total_pnl_pct": { "type": "number" },
|
|
44
|
+
"user_count": { "type": "number" }
|
|
59
45
|
},
|
|
60
|
-
"required": ["
|
|
46
|
+
"required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
|
|
61
47
|
};
|
|
62
|
-
|
|
48
|
+
|
|
63
49
|
return {
|
|
64
50
|
"type": "object",
|
|
65
|
-
"description": "Calculates the
|
|
66
|
-
"patternProperties": {
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
"additionalProperties": tickerSchema
|
|
51
|
+
"description": "Calculates the avg. daily P&L % for all users, bucketed by stock.",
|
|
52
|
+
"patternProperties": { "^.*$": schema },
|
|
53
|
+
"additionalProperties": schema
|
|
70
54
|
};
|
|
71
55
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!
|
|
75
|
-
|
|
76
|
-
pnl_sum: 0,
|
|
77
|
-
position_count: 0
|
|
78
|
-
});
|
|
56
|
+
|
|
57
|
+
_init(map, key) {
|
|
58
|
+
if (!map.has(key)) {
|
|
59
|
+
map.set(key, { pnl_sum: 0, count: 0 });
|
|
79
60
|
}
|
|
80
61
|
}
|
|
81
62
|
|
|
82
|
-
process(
|
|
83
|
-
|
|
84
|
-
if (!
|
|
85
|
-
|
|
63
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
64
|
+
// --- STANDARD 0: FIXED ---
|
|
65
|
+
if (!this.tickerMap) {
|
|
66
|
+
this.tickerMap = context.instrumentToTicker;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!this.tickerMap) {
|
|
70
|
+
return; // Failsafe if context is missing map
|
|
86
71
|
}
|
|
72
|
+
|
|
73
|
+
let dailyPnl = 0;
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
// Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
|
|
76
|
+
if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
|
|
77
|
+
|
|
78
|
+
dailyPnl = todayPortfolio.NetProfit;
|
|
79
|
+
|
|
80
|
+
// Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
|
|
81
|
+
} else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
// Sum the P&L by calculating (Value - Invested) from the aggregates
|
|
84
|
+
dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
|
|
85
|
+
return sum + (agg.Value - agg.Invested);
|
|
86
|
+
}, 0);
|
|
93
87
|
|
|
94
|
-
const assetData = this.assets.get(instrumentId);
|
|
95
|
-
assetData.pnl_sum += pos.NetProfit || 0;
|
|
96
|
-
assetData.position_count++;
|
|
97
88
|
}
|
|
98
|
-
|
|
89
|
+
if (dailyPnl === 0) return; // No P&L
|
|
90
|
+
|
|
91
|
+
const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
|
|
92
|
+
if (!positions || positions.length === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
// We only care about tickers this user *holds*
|
|
97
|
+
const heldTickers = new Set();
|
|
98
|
+
for (const pos of positions) {
|
|
99
|
+
if (!pos.InstrumentID) continue;
|
|
100
|
+
// --- STANDARD 0: SIMPLIFIED ---
|
|
101
|
+
const ticker = this.tickerMap[pos.InstrumentID];
|
|
102
|
+
if (ticker) {
|
|
103
|
+
heldTickers.add(ticker);
|
|
104
|
+
}
|
|
103
105
|
}
|
|
106
|
+
|
|
107
|
+
for (const ticker of heldTickers) {
|
|
108
|
+
this._init(this.tickerBuckets, ticker);
|
|
109
|
+
const asset = this.tickerBuckets.get(ticker);
|
|
110
|
+
asset.pnl_sum += dailyPnl;
|
|
111
|
+
asset.count++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
104
114
|
|
|
115
|
+
async getResult() {
|
|
105
116
|
const result = {};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (data.position_count > 0) {
|
|
117
|
+
|
|
118
|
+
for (const [ticker, data] of this.tickerBuckets.entries()) {
|
|
119
|
+
if (data.count > 0) {
|
|
110
120
|
result[ticker] = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
|
|
122
|
+
total_pnl_pct: data.pnl_sum * 100,
|
|
123
|
+
user_count: data.count
|
|
114
124
|
};
|
|
115
125
|
}
|
|
116
126
|
}
|
|
127
|
+
|
|
117
128
|
return result;
|
|
118
129
|
}
|
|
119
|
-
|
|
130
|
+
|
|
120
131
|
reset() {
|
|
121
|
-
this.
|
|
122
|
-
|
|
132
|
+
this.tickerBuckets.clear();
|
|
133
|
+
// --- STANDARD 0: RENAMED ---
|
|
134
|
+
this.tickerMap = null;
|
|
123
135
|
}
|
|
124
136
|
}
|
|
125
|
-
|
|
126
137
|
module.exports = AverageDailyPnlPerStock;
|
|
@@ -61,8 +61,8 @@ class AverageDailyPositionPnl {
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
process(
|
|
65
|
-
const positions =
|
|
64
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
65
|
+
const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
|
|
66
66
|
if (!positions || !Array.isArray(positions)) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This calculation now uses the 'history' data source, not 'portfolio'.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class HoldingDurationPerAsset {
|
|
@@ -15,10 +15,10 @@ class HoldingDurationPerAsset {
|
|
|
15
15
|
// 'sum_hours' will be the sum of user-level averages
|
|
16
16
|
// 'count' will be the number of users who traded that asset
|
|
17
17
|
this.assets = new Map();
|
|
18
|
-
|
|
18
|
+
// --- STANDARD 0: RENAMED ---
|
|
19
|
+
this.tickerMap = null;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
// --- NEW ---
|
|
22
22
|
/**
|
|
23
23
|
* Statically defines all metadata for the manifest builder.
|
|
24
24
|
*/
|
|
@@ -32,7 +32,6 @@ class HoldingDurationPerAsset {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// --- NEW ---
|
|
36
35
|
/**
|
|
37
36
|
* Statically declare dependencies.
|
|
38
37
|
*/
|
|
@@ -42,7 +41,6 @@ class HoldingDurationPerAsset {
|
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
43
|
* Defines the output schema for this calculation.
|
|
45
|
-
* @returns {object} JSON Schema object
|
|
46
44
|
*/
|
|
47
45
|
static getSchema() {
|
|
48
46
|
const tickerSchema = {
|
|
@@ -76,18 +74,8 @@ class HoldingDurationPerAsset {
|
|
|
76
74
|
}
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
// --- REFACTORED ---
|
|
80
77
|
/**
|
|
81
78
|
* Process data from the 'history' root data source.
|
|
82
|
-
* @param {object} todayPortfolio - (Not used)
|
|
83
|
-
* @param {object} yesterdayPortfolio - (Not used)
|
|
84
|
-
* @param {string} userId - The user ID.
|
|
85
|
-
* @param {object} context - (Not used)
|
|
86
|
-
* @param {object} todayInsights - (Not used)
|
|
87
|
-
* @param {object} yesterdayInsights - (Not used)
|
|
88
|
-
* @param {object} todaySocialPostInsights - (Not used)
|
|
89
|
-
* @param {object} yesterdaySocialPostInsights - (Not used)
|
|
90
|
-
* @param {object} todayHistory - The user's history doc for today.
|
|
91
79
|
*/
|
|
92
80
|
process(
|
|
93
81
|
todayPortfolio,
|
|
@@ -100,8 +88,17 @@ class HoldingDurationPerAsset {
|
|
|
100
88
|
yesterdaySocialPostInsights,
|
|
101
89
|
todayHistory
|
|
102
90
|
) {
|
|
103
|
-
//
|
|
104
|
-
|
|
91
|
+
// --- STANDARD 0: FIXED ---
|
|
92
|
+
if (!this.tickerMap) {
|
|
93
|
+
this.tickerMap = context.instrumentToTicker;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---
|
|
97
|
+
// FIX: The test harness injects data based on getMetadata().
|
|
98
|
+
// Because rootDataDependencies is ONLY ['history'], the test harness
|
|
99
|
+
// places the history object in the *first* argument (todayPortfolio).
|
|
100
|
+
// ---
|
|
101
|
+
const historyData = todayPortfolio; // NOT todayHistory
|
|
105
102
|
if (!historyData || !Array.isArray(historyData.assets)) {
|
|
106
103
|
return;
|
|
107
104
|
}
|
|
@@ -125,21 +122,24 @@ class HoldingDurationPerAsset {
|
|
|
125
122
|
// Convert minutes to hours and add to the sum
|
|
126
123
|
assetData.sum_hours += (durationMinutes / 60);
|
|
127
124
|
|
|
128
|
-
// Increment count
|
|
129
|
-
// an average for this asset, not individual positions)
|
|
125
|
+
// Increment count
|
|
130
126
|
assetData.count++;
|
|
131
127
|
}
|
|
132
128
|
}
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
async getResult() {
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
// --- STANDARD 0: REMOVED forbidden data load ---
|
|
133
|
+
|
|
134
|
+
// Failsafe check
|
|
135
|
+
if (!this.tickerMap) {
|
|
136
|
+
return {}; // process() must run first
|
|
138
137
|
}
|
|
139
138
|
|
|
140
139
|
const result = {};
|
|
141
140
|
for (const [instrumentId, data] of this.assets.entries()) {
|
|
142
|
-
|
|
141
|
+
// --- STANDARD 0: SIMPLIFIED ---
|
|
142
|
+
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
143
143
|
|
|
144
144
|
if (data.count > 0) {
|
|
145
145
|
result[ticker] = {
|
|
@@ -154,7 +154,8 @@ class HoldingDurationPerAsset {
|
|
|
154
154
|
|
|
155
155
|
reset() {
|
|
156
156
|
this.assets.clear();
|
|
157
|
-
|
|
157
|
+
// --- STANDARD 0: RENAMED ---
|
|
158
|
+
this.tickerMap = null;
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|