aiden-shared-calculations-unified 1.0.86 → 1.0.87
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 +36 -106
- package/calculations/core/asset-position-size.js +40 -91
- package/calculations/core/average-daily-pnl-all-users.js +18 -57
- package/calculations/core/average-daily-pnl-per-sector.js +41 -88
- package/calculations/core/average-daily-pnl-per-stock.js +38 -91
- package/calculations/core/average-daily-position-pnl.js +19 -49
- package/calculations/core/holding-duration-per-asset.js +25 -127
- package/calculations/core/instrument-price-change-1d.js +30 -49
- package/calculations/core/instrument-price-momentum-20d.js +50 -60
- package/calculations/core/long-position-per-stock.js +39 -68
- package/calculations/core/overall-holding-duration.js +16 -87
- package/calculations/core/overall-profitability-ratio.js +11 -40
- package/calculations/core/platform-buy-sell-sentiment.js +41 -124
- package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
- package/calculations/core/platform-daily-ownership-delta.js +68 -126
- package/calculations/core/platform-ownership-per-sector.js +45 -96
- package/calculations/core/platform-total-positions-held.js +20 -80
- package/calculations/core/pnl-distribution-per-stock.js +29 -135
- package/calculations/core/price-metrics.js +95 -206
- package/calculations/core/profitability-ratio-per-sector.js +34 -79
- package/calculations/core/profitability-ratio-per-stock.js +32 -88
- package/calculations/core/profitability-skew-per-stock.js +41 -94
- package/calculations/core/profitable-and-unprofitable-status.js +44 -76
- package/calculations/core/sentiment-per-stock.js +24 -77
- package/calculations/core/short-position-per-stock.js +35 -43
- package/calculations/core/social-activity-aggregation.js +26 -49
- package/calculations/core/social-asset-posts-trend.js +38 -94
- package/calculations/core/social-event-correlation.js +26 -93
- package/calculations/core/social-sentiment-aggregation.js +20 -44
- package/calculations/core/social-top-mentioned-words.js +35 -87
- package/calculations/core/social-topic-interest-evolution.js +22 -111
- package/calculations/core/social-topic-sentiment-matrix.js +38 -104
- package/calculations/core/social-word-mentions-trend.js +27 -104
- package/calculations/core/speculator-asset-sentiment.js +31 -72
- package/calculations/core/speculator-danger-zone.js +48 -84
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
- package/calculations/core/speculator-leverage-per-asset.js +25 -64
- package/calculations/core/speculator-leverage-per-sector.js +27 -63
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
- package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
- package/calculations/core/speculator-take-profit-per-asset.js +20 -57
- package/calculations/core/speculator-tsl-per-asset.js +17 -56
- package/calculations/core/total-long-figures.js +16 -31
- package/calculations/core/total-long-per-sector.js +39 -61
- package/calculations/core/total-short-figures.js +13 -32
- package/calculations/core/total-short-per-sector.js +39 -61
- package/calculations/core/users-processed.js +11 -46
- package/calculations/gauss/cohort-capital-flow.js +54 -173
- package/calculations/gauss/cohort-definer.js +77 -163
- package/calculations/gauss/daily-dna-filter.js +29 -83
- package/calculations/gauss/gauss-divergence-signal.js +22 -109
- package/calculations/gem/cohort-momentum-state.js +27 -72
- package/calculations/gem/cohort-skill-definition.js +36 -52
- package/calculations/gem/platform-conviction-divergence.js +18 -60
- package/calculations/gem/quant-skill-alpha-signal.js +25 -98
- package/calculations/gem/skilled-cohort-flow.js +67 -175
- package/calculations/gem/skilled-unskilled-divergence.js +18 -73
- package/calculations/gem/unskilled-cohort-flow.js +64 -172
- package/calculations/helix/helix-contrarian-signal.js +20 -114
- package/calculations/helix/herd-consensus-score.js +42 -124
- package/calculations/helix/winner-loser-flow.js +36 -118
- package/calculations/pyro/risk-appetite-index.js +33 -74
- package/calculations/pyro/squeeze-potential.js +30 -87
- package/calculations/pyro/volatility-signal.js +33 -78
- package/package.json +1 -1
|
@@ -1,136 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
* This 'standard' calculation streams all user portfolios
|
|
5
|
-
* and calculates the average daily P&L (in %) for all
|
|
6
|
-
* users, bucketed by sector.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average daily P&L % per sector.
|
|
3
|
+
* REFACTORED: Aggregates P&L percentages.
|
|
7
4
|
*/
|
|
8
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
9
|
-
|
|
10
5
|
class AverageDailyPnlPerSector {
|
|
11
6
|
constructor() {
|
|
12
|
-
|
|
13
|
-
this.sectorBuckets = new Map();
|
|
14
|
-
|
|
15
|
-
// --- STANDARD 0: RENAMED ---
|
|
7
|
+
this.sectorData = new Map();
|
|
16
8
|
this.sectorMap = null;
|
|
17
9
|
}
|
|
18
10
|
|
|
19
|
-
/** Statically defines metadata */
|
|
20
11
|
static getMetadata() {
|
|
21
12
|
return {
|
|
22
13
|
type: 'standard',
|
|
23
14
|
rootDataDependencies: ['portfolio'],
|
|
24
|
-
isHistorical: false,
|
|
15
|
+
isHistorical: false,
|
|
25
16
|
userType: 'all',
|
|
26
|
-
category: '
|
|
17
|
+
category: 'core_pnl'
|
|
27
18
|
};
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
static getDependencies() {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
34
22
|
|
|
35
|
-
/**
|
|
36
|
-
* Defines the output schema for this calculation.
|
|
37
|
-
*/
|
|
38
23
|
static getSchema() {
|
|
39
|
-
const
|
|
24
|
+
const sectorSchema = {
|
|
40
25
|
"type": "object",
|
|
41
26
|
"properties": {
|
|
42
27
|
"avg_daily_pnl_pct": { "type": "number" },
|
|
43
|
-
"
|
|
28
|
+
"aggregate_pnl_pct_sum": { "type": "number", "description": "Sum of P&L % points for sector." },
|
|
44
29
|
"user_count": { "type": "number" }
|
|
45
30
|
},
|
|
46
|
-
"required": ["avg_daily_pnl_pct", "
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"description": "Calculates the avg. daily P&L % for all users, bucketed by sector.",
|
|
52
|
-
"patternProperties": { "^.*$": schema },
|
|
53
|
-
"additionalProperties": schema
|
|
31
|
+
"required": ["avg_daily_pnl_pct", "aggregate_pnl_pct_sum", "user_count"]
|
|
54
32
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
_init(map, key) {
|
|
58
|
-
if (!map.has(key)) {
|
|
59
|
-
map.set(key, { pnl_sum: 0, count: 0 });
|
|
60
|
-
}
|
|
33
|
+
return { "type": "object", "patternProperties": { "^.*$": sectorSchema } };
|
|
61
34
|
}
|
|
62
35
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.sectorMap = context.sectorMapping;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!this.sectorMap) {
|
|
70
|
-
return; // Failsafe if context is missing map
|
|
36
|
+
_initSector(sector) {
|
|
37
|
+
if (!this.sectorData.has(sector)) {
|
|
38
|
+
this.sectorData.set(sector, { total_pnl_pct: 0, count: 0 });
|
|
71
39
|
}
|
|
72
|
-
|
|
73
|
-
let dailyPnl = 0;
|
|
40
|
+
}
|
|
74
41
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
42
|
+
process(context) {
|
|
43
|
+
const { extract } = context.math;
|
|
44
|
+
const { mappings, user } = context;
|
|
45
|
+
if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
|
|
79
46
|
|
|
80
|
-
|
|
81
|
-
|
|
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) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
47
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
48
|
+
const userSectorPnL = new Map();
|
|
95
49
|
|
|
96
|
-
// We only care about sectors this user *holds*
|
|
97
|
-
const heldSectors = new Set();
|
|
98
50
|
for (const pos of positions) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
51
|
+
const instId = extract.getInstrumentId(pos);
|
|
52
|
+
if (!instId) continue;
|
|
53
|
+
|
|
54
|
+
const sector = this.sectorMap[instId] || 'Unknown';
|
|
55
|
+
// NetProfit is the % profit relative to invested capital for that position
|
|
56
|
+
// We sum these to get the user's "sector performance points"
|
|
57
|
+
const pnlPct = extract.getNetProfit(pos);
|
|
58
|
+
|
|
59
|
+
if (!userSectorPnL.has(sector)) userSectorPnL.set(sector, 0);
|
|
60
|
+
userSectorPnL.set(sector, userSectorPnL.get(sector) + pnlPct);
|
|
105
61
|
}
|
|
106
|
-
|
|
107
|
-
for (const sector of
|
|
108
|
-
this.
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
62
|
+
|
|
63
|
+
for (const [sector, pnl] of userSectorPnL.entries()) {
|
|
64
|
+
this._initSector(sector);
|
|
65
|
+
const data = this.sectorData.get(sector);
|
|
66
|
+
data.total_pnl_pct += pnl;
|
|
67
|
+
data.count++;
|
|
112
68
|
}
|
|
113
69
|
}
|
|
114
70
|
|
|
115
71
|
async getResult() {
|
|
116
72
|
const result = {};
|
|
117
|
-
|
|
118
|
-
for (const [sector, data] of this.sectorBuckets.entries()) {
|
|
73
|
+
for (const [sector, data] of this.sectorData.entries()) {
|
|
119
74
|
if (data.count > 0) {
|
|
120
75
|
result[sector] = {
|
|
121
|
-
avg_daily_pnl_pct:
|
|
122
|
-
|
|
76
|
+
avg_daily_pnl_pct: data.total_pnl_pct / data.count,
|
|
77
|
+
aggregate_pnl_pct_sum: data.total_pnl_pct,
|
|
123
78
|
user_count: data.count
|
|
124
79
|
};
|
|
125
80
|
}
|
|
126
81
|
}
|
|
127
|
-
|
|
128
82
|
return result;
|
|
129
83
|
}
|
|
130
|
-
|
|
84
|
+
|
|
131
85
|
reset() {
|
|
132
|
-
this.
|
|
133
|
-
// --- STANDARD 0: RENAMED ---
|
|
86
|
+
this.sectorData.clear();
|
|
134
87
|
this.sectorMap = null;
|
|
135
88
|
}
|
|
136
89
|
}
|
|
@@ -1,136 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
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.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average daily P&L % per stock.
|
|
3
|
+
* REFACTORED: Aggregates P&L percentages.
|
|
7
4
|
*/
|
|
8
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
9
|
-
|
|
10
5
|
class AverageDailyPnlPerStock {
|
|
11
6
|
constructor() {
|
|
12
|
-
|
|
13
|
-
this.tickerBuckets = new Map();
|
|
14
|
-
|
|
15
|
-
// --- STANDARD 0: RENAMED ---
|
|
7
|
+
this.stockData = new Map();
|
|
16
8
|
this.tickerMap = null;
|
|
17
9
|
}
|
|
18
10
|
|
|
19
|
-
/** Statically defines metadata */
|
|
20
11
|
static getMetadata() {
|
|
21
12
|
return {
|
|
22
13
|
type: 'standard',
|
|
23
14
|
rootDataDependencies: ['portfolio'],
|
|
24
|
-
isHistorical: false,
|
|
15
|
+
isHistorical: false,
|
|
25
16
|
userType: 'all',
|
|
26
|
-
category: '
|
|
17
|
+
category: 'core_pnl'
|
|
27
18
|
};
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
static getDependencies() {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
34
22
|
|
|
35
|
-
/**
|
|
36
|
-
* Defines the output schema for this calculation.
|
|
37
|
-
*/
|
|
38
23
|
static getSchema() {
|
|
39
|
-
const
|
|
24
|
+
const stockSchema = {
|
|
40
25
|
"type": "object",
|
|
41
26
|
"properties": {
|
|
42
27
|
"avg_daily_pnl_pct": { "type": "number" },
|
|
43
|
-
"
|
|
44
|
-
"
|
|
28
|
+
"aggregate_pnl_pct_sum": { "type": "number" },
|
|
29
|
+
"position_count": { "type": "number" }
|
|
45
30
|
},
|
|
46
|
-
"required": ["avg_daily_pnl_pct", "
|
|
31
|
+
"required": ["avg_daily_pnl_pct", "aggregate_pnl_pct_sum", "position_count"]
|
|
47
32
|
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"description": "Calculates the avg. daily P&L % for all users, bucketed by stock.",
|
|
52
|
-
"patternProperties": { "^.*$": schema },
|
|
53
|
-
"additionalProperties": schema
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
_init(map, key) {
|
|
58
|
-
if (!map.has(key)) {
|
|
59
|
-
map.set(key, { pnl_sum: 0, count: 0 });
|
|
60
|
-
}
|
|
33
|
+
return { "type": "object", "patternProperties": { "^.*$": stockSchema } };
|
|
61
34
|
}
|
|
62
35
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.tickerMap = context.instrumentToTicker;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!this.tickerMap) {
|
|
70
|
-
return; // Failsafe if context is missing map
|
|
36
|
+
_initStock(instId) {
|
|
37
|
+
if (!this.stockData.has(instId)) {
|
|
38
|
+
this.stockData.set(instId, { total_pnl_pct: 0, count: 0 });
|
|
71
39
|
}
|
|
72
|
-
|
|
73
|
-
let dailyPnl = 0;
|
|
40
|
+
}
|
|
74
41
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
42
|
+
process(context) {
|
|
43
|
+
const { extract } = context.math;
|
|
44
|
+
const { mappings, user } = context;
|
|
45
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
79
46
|
|
|
80
|
-
|
|
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) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
47
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
95
48
|
|
|
96
|
-
// We only care about tickers this user *holds*
|
|
97
|
-
const heldTickers = new Set();
|
|
98
49
|
for (const pos of positions) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const asset = this.tickerBuckets.get(ticker);
|
|
110
|
-
asset.pnl_sum += dailyPnl;
|
|
111
|
-
asset.count++;
|
|
50
|
+
const instId = extract.getInstrumentId(pos);
|
|
51
|
+
if (!instId) continue;
|
|
52
|
+
|
|
53
|
+
// NetProfit is %
|
|
54
|
+
const pnlPct = extract.getNetProfit(pos);
|
|
55
|
+
|
|
56
|
+
this._initStock(instId);
|
|
57
|
+
const data = this.stockData.get(instId);
|
|
58
|
+
data.total_pnl_pct += pnlPct;
|
|
59
|
+
data.count++;
|
|
112
60
|
}
|
|
113
61
|
}
|
|
114
62
|
|
|
115
63
|
async getResult() {
|
|
64
|
+
if (!this.tickerMap) return {};
|
|
116
65
|
const result = {};
|
|
117
|
-
|
|
118
|
-
|
|
66
|
+
for (const [instId, data] of this.stockData.entries()) {
|
|
67
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
119
68
|
if (data.count > 0) {
|
|
120
69
|
result[ticker] = {
|
|
121
|
-
avg_daily_pnl_pct:
|
|
122
|
-
|
|
123
|
-
|
|
70
|
+
avg_daily_pnl_pct: data.total_pnl_pct / data.count,
|
|
71
|
+
aggregate_pnl_pct_sum: data.total_pnl_pct,
|
|
72
|
+
position_count: data.count
|
|
124
73
|
};
|
|
125
74
|
}
|
|
126
75
|
}
|
|
127
|
-
|
|
128
76
|
return result;
|
|
129
77
|
}
|
|
130
|
-
|
|
78
|
+
|
|
131
79
|
reset() {
|
|
132
|
-
this.
|
|
133
|
-
// --- STANDARD 0: RENAMED ---
|
|
80
|
+
this.stockData.clear();
|
|
134
81
|
this.tickerMap = null;
|
|
135
82
|
}
|
|
136
83
|
}
|
|
@@ -1,90 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for average P&L per position.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What was the average P&L across all
|
|
5
|
-
* individual positions held by the crowd today?"
|
|
6
|
-
*
|
|
7
|
-
* This is different from "average P&L per user" as a user
|
|
8
|
-
* can have multiple positions.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average P&L % per position.
|
|
3
|
+
* REFACTORED: Uses P&L %.
|
|
9
4
|
*/
|
|
10
5
|
class AverageDailyPositionPnl {
|
|
11
6
|
constructor() {
|
|
12
|
-
this.
|
|
7
|
+
this.totalPnlPct = 0;
|
|
13
8
|
this.positionCount = 0;
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
// --- NEW ---
|
|
17
|
-
/**
|
|
18
|
-
* Statically defines all metadata for the manifest builder.
|
|
19
|
-
*/
|
|
20
11
|
static getMetadata() {
|
|
21
12
|
return {
|
|
22
13
|
type: 'standard',
|
|
23
|
-
rootDataDependencies: ['portfolio'],
|
|
14
|
+
rootDataDependencies: ['portfolio'],
|
|
24
15
|
isHistorical: false,
|
|
25
16
|
userType: 'all',
|
|
26
17
|
category: 'core_pnl'
|
|
27
18
|
};
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Statically declare dependencies.
|
|
33
|
-
*/
|
|
34
|
-
static getDependencies() {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
37
22
|
|
|
38
|
-
/**
|
|
39
|
-
* Defines the output schema for this calculation.
|
|
40
|
-
* @returns {object} JSON Schema object
|
|
41
|
-
*/
|
|
42
23
|
static getSchema() {
|
|
43
24
|
return {
|
|
44
25
|
"type": "object",
|
|
45
|
-
"description": "Calculates the average daily P&L across all individual positions.",
|
|
46
26
|
"properties": {
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
"total_pnl": {
|
|
52
|
-
"type": "number",
|
|
53
|
-
"description": "The sum of all daily P&L from all positions."
|
|
54
|
-
},
|
|
55
|
-
"position_count": {
|
|
56
|
-
"type": "number",
|
|
57
|
-
"description": "The total number of positions processed."
|
|
58
|
-
}
|
|
27
|
+
"avg_position_pnl_pct": { "type": "number" },
|
|
28
|
+
"aggregate_pnl_pct_sum": { "type": "number" },
|
|
29
|
+
"total_positions": { "type": "number" }
|
|
59
30
|
},
|
|
60
|
-
"required": ["
|
|
31
|
+
"required": ["avg_position_pnl_pct", "aggregate_pnl_pct_sum", "total_positions"]
|
|
61
32
|
};
|
|
62
33
|
}
|
|
63
34
|
|
|
64
|
-
process(
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
35
|
+
process(context) {
|
|
36
|
+
const { extract } = context.math;
|
|
37
|
+
const { user } = context;
|
|
38
|
+
|
|
39
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
69
40
|
|
|
70
41
|
for (const pos of positions) {
|
|
71
|
-
this.
|
|
42
|
+
this.totalPnlPct += extract.getNetProfit(pos);
|
|
72
43
|
this.positionCount++;
|
|
73
44
|
}
|
|
74
45
|
}
|
|
75
46
|
|
|
76
47
|
getResult() {
|
|
77
48
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
49
|
+
avg_position_pnl_pct: (this.positionCount > 0) ? (this.totalPnlPct / this.positionCount) : 0,
|
|
50
|
+
aggregate_pnl_pct_sum: this.totalPnlPct,
|
|
51
|
+
total_positions: this.positionCount
|
|
81
52
|
};
|
|
82
53
|
}
|
|
83
54
|
|
|
84
55
|
reset() {
|
|
85
|
-
this.
|
|
56
|
+
this.totalPnlPct = 0;
|
|
86
57
|
this.positionCount = 0;
|
|
87
58
|
}
|
|
88
59
|
}
|
|
89
|
-
|
|
90
60
|
module.exports = AverageDailyPositionPnl;
|
|
@@ -1,162 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for user behaviour.
|
|
3
|
-
*
|
|
4
|
-
* REFACTOR: This metric now answers: "What is the average holding duration
|
|
5
|
-
* (in hours) for *closed* positions, averaged across all users, grouped by asset?"
|
|
6
|
-
*
|
|
7
|
-
* This calculation now uses the 'history' data source, not 'portfolio'.
|
|
3
|
+
* REFACTORED: Uses context.math.history.getDailyHistory().
|
|
8
4
|
*/
|
|
9
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
class HoldingDurationPerAsset {
|
|
13
6
|
constructor() {
|
|
14
|
-
// { [instrumentId]: { sum_hours: 0, count: 0 } }
|
|
15
|
-
// 'sum_hours' will be the sum of user-level averages
|
|
16
|
-
// 'count' will be the number of users who traded that asset
|
|
17
7
|
this.assets = new Map();
|
|
18
|
-
// --- STANDARD 0: RENAMED ---
|
|
19
8
|
this.tickerMap = null;
|
|
20
9
|
}
|
|
21
10
|
|
|
22
|
-
/**
|
|
23
|
-
* Statically defines all metadata for the manifest builder.
|
|
24
|
-
*/
|
|
25
11
|
static getMetadata() {
|
|
26
|
-
return {
|
|
27
|
-
type: 'standard',
|
|
28
|
-
rootDataDependencies: ['history'], // It only needs the history doc
|
|
29
|
-
isHistorical: false, // It only needs today's history doc
|
|
30
|
-
userType: 'all',
|
|
31
|
-
category: 'core_metrics'
|
|
32
|
-
};
|
|
12
|
+
return { type: 'standard', rootDataDependencies: ['history'], isHistorical: false, userType: 'all', category: 'core_metrics' };
|
|
33
13
|
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Statically declare dependencies.
|
|
37
|
-
*/
|
|
38
|
-
static getDependencies() {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Defines the output schema for this calculation.
|
|
44
|
-
*/
|
|
14
|
+
static getDependencies() { return []; }
|
|
45
15
|
static getSchema() {
|
|
46
|
-
const tickerSchema = {
|
|
47
|
-
|
|
48
|
-
"properties": {
|
|
49
|
-
"avg_duration_hours": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "Average holding duration in hours (averaged across all users)."
|
|
52
|
-
},
|
|
53
|
-
"count": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "Count of users used in average."
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
"required": ["avg_duration_hours", "count"]
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
"type": "object",
|
|
63
|
-
"description": "Calculates the average holding duration (in hours) for closed positions per asset, averaged across all users.",
|
|
64
|
-
"patternProperties": {
|
|
65
|
-
"^.*$": tickerSchema // Ticker
|
|
66
|
-
},
|
|
67
|
-
"additionalProperties": tickerSchema
|
|
68
|
-
};
|
|
16
|
+
const tickerSchema = { "type": "object", "properties": { "avg_duration_hours": { "type": "number" }, "count": { "type": "number" } }, "required": ["avg_duration_hours", "count"] };
|
|
17
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
69
18
|
}
|
|
70
19
|
|
|
71
20
|
_initAsset(instrumentId) {
|
|
72
|
-
if (!this.assets.has(instrumentId)) {
|
|
73
|
-
this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
|
|
74
|
-
}
|
|
21
|
+
if (!this.assets.has(instrumentId)) this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
|
|
75
22
|
}
|
|
76
23
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
todayPortfolio,
|
|
82
|
-
yesterdayPortfolio,
|
|
83
|
-
userId,
|
|
84
|
-
context,
|
|
85
|
-
todayInsights,
|
|
86
|
-
yesterdayInsights,
|
|
87
|
-
todaySocialPostInsights,
|
|
88
|
-
yesterdaySocialPostInsights,
|
|
89
|
-
todayHistory
|
|
90
|
-
) {
|
|
91
|
-
// --- STANDARD 0: FIXED ---
|
|
92
|
-
if (!this.tickerMap) {
|
|
93
|
-
this.tickerMap = context.instrumentToTicker;
|
|
94
|
-
}
|
|
24
|
+
process(context) {
|
|
25
|
+
const { history } = context.math;
|
|
26
|
+
const { mappings, user } = context;
|
|
27
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
95
28
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// places the history object in the *first* argument (todayPortfolio).
|
|
100
|
-
// ---
|
|
101
|
-
const historyData = todayPortfolio; // NOT todayHistory
|
|
102
|
-
if (!historyData || !Array.isArray(historyData.assets)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
29
|
+
// V4: Strict Access via Math Layer
|
|
30
|
+
const historyDoc = history.getDailyHistory(user);
|
|
31
|
+
const tradedAssets = history.getTradedAssets(historyDoc);
|
|
105
32
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
33
|
+
for (const asset of tradedAssets) {
|
|
34
|
+
const instId = history.getInstrumentId(asset);
|
|
35
|
+
if (!instId || instId === -1) continue;
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const durationMinutes = asset.avgHoldingTimeInMinutes;
|
|
116
|
-
|
|
117
|
-
if (typeof durationMinutes === 'number' && durationMinutes > 0) {
|
|
118
|
-
this._initAsset(instrumentId);
|
|
119
|
-
|
|
120
|
-
const assetData = this.assets.get(instrumentId);
|
|
121
|
-
|
|
122
|
-
// Convert minutes to hours and add to the sum
|
|
37
|
+
const durationMinutes = history.getAvgHoldingTimeMinutes(asset);
|
|
38
|
+
if (durationMinutes > 0) {
|
|
39
|
+
this._initAsset(instId);
|
|
40
|
+
const assetData = this.assets.get(instId);
|
|
123
41
|
assetData.sum_hours += (durationMinutes / 60);
|
|
124
|
-
|
|
125
|
-
// Increment count
|
|
126
42
|
assetData.count++;
|
|
127
43
|
}
|
|
128
44
|
}
|
|
129
45
|
}
|
|
130
46
|
|
|
131
47
|
async getResult() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Failsafe check
|
|
135
|
-
if (!this.tickerMap) {
|
|
136
|
-
return {}; // process() must run first
|
|
137
|
-
}
|
|
138
|
-
|
|
48
|
+
if (!this.tickerMap) return {};
|
|
139
49
|
const result = {};
|
|
140
|
-
for (const [
|
|
141
|
-
|
|
142
|
-
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
143
|
-
|
|
50
|
+
for (const [instId, data] of this.assets.entries()) {
|
|
51
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
144
52
|
if (data.count > 0) {
|
|
145
|
-
result[ticker] = {
|
|
146
|
-
// Calculate the final average (avg of avgs)
|
|
147
|
-
avg_duration_hours: data.sum_hours / data.count,
|
|
148
|
-
count: data.count
|
|
149
|
-
};
|
|
53
|
+
result[ticker] = { avg_duration_hours: data.sum_hours / data.count, count: data.count };
|
|
150
54
|
}
|
|
151
55
|
}
|
|
152
56
|
return result;
|
|
153
57
|
}
|
|
154
|
-
|
|
155
|
-
reset() {
|
|
156
|
-
this.assets.clear();
|
|
157
|
-
// --- STANDARD 0: RENAMED ---
|
|
158
|
-
this.tickerMap = null;
|
|
159
|
-
}
|
|
58
|
+
reset() { this.assets.clear(); this.tickerMap = null; }
|
|
160
59
|
}
|
|
161
|
-
|
|
162
60
|
module.exports = HoldingDurationPerAsset;
|