aiden-shared-calculations-unified 1.0.84 → 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,129 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for
|
|
3
|
-
*
|
|
4
|
-
* This is a 'type: "meta"' calculation. It runs ONCE.
|
|
5
|
-
* It reads the pre-aggregated 'insights' data source (/daily_instrument_insights/)
|
|
6
|
-
* and uses the sector mapping provider to aggregate the total number
|
|
7
|
-
* of owners for each sector.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for sector ownership.
|
|
3
|
+
* REFACTORED: Uses exposure weight.
|
|
8
4
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DailyOwnershipPerSector {
|
|
12
|
-
|
|
13
|
-
// --- STANDARD 2: ADDED ---
|
|
5
|
+
class PlatformOwnershipPerSector {
|
|
14
6
|
constructor() {
|
|
15
|
-
this.
|
|
7
|
+
this.sectorData = new Map();
|
|
8
|
+
this.sectorMap = null;
|
|
16
9
|
}
|
|
17
10
|
|
|
18
|
-
/**
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
21
11
|
static getMetadata() {
|
|
22
12
|
return {
|
|
23
|
-
type: '
|
|
24
|
-
rootDataDependencies: ['
|
|
13
|
+
type: 'standard',
|
|
14
|
+
rootDataDependencies: ['portfolio'],
|
|
25
15
|
isHistorical: false,
|
|
26
|
-
userType: '
|
|
27
|
-
category: '
|
|
16
|
+
userType: 'all',
|
|
17
|
+
category: 'core'
|
|
28
18
|
};
|
|
29
19
|
}
|
|
30
20
|
|
|
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
|
-
*/
|
|
41
23
|
static getSchema() {
|
|
42
|
-
const
|
|
24
|
+
const schema = {
|
|
43
25
|
"type": "object",
|
|
44
|
-
"description": "Aggregated ownership for a single sector.",
|
|
45
26
|
"properties": {
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
"description": "The total number of unique owners for all assets in this sector."
|
|
49
|
-
}
|
|
27
|
+
"total_exposure_weight": { "type": "number" },
|
|
28
|
+
"position_count": { "type": "number" }
|
|
50
29
|
},
|
|
51
|
-
"required": ["
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
"type": "object",
|
|
56
|
-
"description": "Calculates the total unique owners per sector based on the 'insights' data source.",
|
|
57
|
-
"patternProperties": {
|
|
58
|
-
"^.*$": sectorSchema // Matches any string key (sector name)
|
|
59
|
-
},
|
|
60
|
-
"additionalProperties": sectorSchema
|
|
30
|
+
"required": ["total_exposure_weight", "position_count"]
|
|
61
31
|
};
|
|
32
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
62
33
|
}
|
|
63
34
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// --- STANDARD 1: UPDATED SIGNATURE ---
|
|
68
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
69
|
-
// { [sectorName]: { total_owners: 0 } }
|
|
70
|
-
const sectorOwners = new Map();
|
|
71
|
-
|
|
72
|
-
// ---
|
|
73
|
-
// FIX: The test harness ('worker.js') is our "ground truth".
|
|
74
|
-
//
|
|
75
|
-
// 1. Get Logger & Mappings:
|
|
76
|
-
// 'worker.js' passes the context object as ARGUMENT 4 (named 'config').
|
|
77
|
-
// This context *already contains* the sector map.
|
|
78
|
-
// ---
|
|
79
|
-
const { logger, sectorMapping } = config; // 'config' is Param 4
|
|
80
|
-
|
|
81
|
-
// 2. Get the insights document
|
|
82
|
-
// 'worker.js' passes the root data object as ARGUMENT 1 (named 'dateStr').
|
|
83
|
-
// ---
|
|
84
|
-
const rootDataToday = dateStr; // 'dateStr' is Param 1
|
|
85
|
-
const insightsDoc = rootDataToday.insights;
|
|
86
|
-
|
|
87
|
-
if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
|
|
88
|
-
logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found.`);
|
|
89
|
-
this.result = {};
|
|
90
|
-
return;
|
|
35
|
+
_initSector(sector) {
|
|
36
|
+
if (!this.sectorData.has(sector)) {
|
|
37
|
+
this.sectorData.set(sector, { weight: 0, count: 0 });
|
|
91
38
|
}
|
|
39
|
+
}
|
|
92
40
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
41
|
+
process(context) {
|
|
42
|
+
const { extract } = context.math;
|
|
43
|
+
const { mappings, user } = context;
|
|
44
|
+
if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
|
|
97
45
|
|
|
98
|
-
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
46
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
101
47
|
|
|
102
|
-
|
|
103
|
-
const
|
|
48
|
+
for (const pos of positions) {
|
|
49
|
+
const instId = extract.getInstrumentId(pos);
|
|
50
|
+
if (!instId) continue;
|
|
104
51
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
sectorOwners.set(sectorName, { total_owners: 0 });
|
|
108
|
-
}
|
|
52
|
+
const sector = this.sectorMap[instId] || 'Unknown';
|
|
53
|
+
const weight = extract.getPositionWeight(pos, user.type);
|
|
109
54
|
|
|
110
|
-
|
|
111
|
-
|
|
55
|
+
this._initSector(sector);
|
|
56
|
+
const data = this.sectorData.get(sector);
|
|
57
|
+
data.weight += weight;
|
|
58
|
+
data.count++;
|
|
112
59
|
}
|
|
113
|
-
|
|
114
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
115
|
-
this.result = Object.fromEntries(sectorOwners);
|
|
116
60
|
}
|
|
117
61
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
62
|
+
async getResult() {
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const [sector, data] of this.sectorData.entries()) {
|
|
65
|
+
result[sector] = {
|
|
66
|
+
total_exposure_weight: data.weight,
|
|
67
|
+
position_count: data.count
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
121
71
|
}
|
|
122
72
|
|
|
123
|
-
// --- STANDARD 2: ADDED ---
|
|
124
73
|
reset() {
|
|
125
|
-
this.
|
|
74
|
+
this.sectorData.clear();
|
|
75
|
+
this.sectorMap = null;
|
|
126
76
|
}
|
|
127
77
|
}
|
|
128
|
-
|
|
129
|
-
module.exports = DailyOwnershipPerSector;
|
|
78
|
+
module.exports = PlatformOwnershipPerSector;
|
|
@@ -1,105 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for total positions
|
|
3
|
-
*
|
|
4
|
-
* REFACTOR: This is now a 'type: "meta"' calculation. It runs ONCE.
|
|
5
|
-
* It reads the pre-aggregated 'insights' data source and sums the
|
|
6
|
-
* 'total' field from all instruments to get the platform-wide total.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total positions count.
|
|
3
|
+
* REFACTORED: Uses context.math.extract.
|
|
7
4
|
*/
|
|
8
|
-
class
|
|
9
|
-
|
|
10
|
-
// --- STANDARD 2: ADDED ---
|
|
5
|
+
class PlatformTotalPositionsHeld {
|
|
11
6
|
constructor() {
|
|
12
|
-
this.
|
|
7
|
+
this.totalPositions = 0;
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
/**
|
|
16
|
-
* Statically defines all metadata for the manifest builder.
|
|
17
|
-
*/
|
|
18
10
|
static getMetadata() {
|
|
19
11
|
return {
|
|
20
|
-
type: '
|
|
21
|
-
rootDataDependencies: ['
|
|
12
|
+
type: 'standard',
|
|
13
|
+
rootDataDependencies: ['portfolio'],
|
|
22
14
|
isHistorical: false,
|
|
23
|
-
userType: '
|
|
15
|
+
userType: 'all',
|
|
24
16
|
category: 'core_metrics'
|
|
25
17
|
};
|
|
26
18
|
}
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
* Statically declare dependencies.
|
|
30
|
-
*/
|
|
31
|
-
static getDependencies() {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
20
|
+
static getDependencies() { return []; }
|
|
34
21
|
|
|
35
|
-
/**
|
|
36
|
-
* Defines the output schema for this calculation.
|
|
37
|
-
*/
|
|
38
22
|
static getSchema() {
|
|
39
23
|
return {
|
|
40
24
|
"type": "object",
|
|
41
|
-
"description": "Calculates the total number of positions held across all users and instruments.",
|
|
42
25
|
"properties": {
|
|
43
|
-
"
|
|
44
|
-
"type": "number",
|
|
45
|
-
"description": "The total aggregated count of all positions."
|
|
46
|
-
}
|
|
26
|
+
"total_positions_count": { "type": "number" }
|
|
47
27
|
},
|
|
48
|
-
"required": ["
|
|
28
|
+
"required": ["total_positions_count"]
|
|
49
29
|
};
|
|
50
30
|
}
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let totalPositions = 0;
|
|
58
|
-
|
|
59
|
-
// ---
|
|
60
|
-
// FIX: The test harness ('worker.js') is our "ground truth".
|
|
61
|
-
//
|
|
62
|
-
// 1. Get Logger:
|
|
63
|
-
// 'worker.js' passes the context object as ARGUMENT 4 (named 'config').
|
|
64
|
-
// ---
|
|
65
|
-
const { logger } = config; // 'config' is Param 4
|
|
66
|
-
|
|
67
|
-
// ---
|
|
68
|
-
// 2. Get Data:
|
|
69
|
-
// 'worker.js' passes the root data object as ARGUMENT 1 (named 'dateStr').
|
|
70
|
-
// ---
|
|
71
|
-
const rootDataToday = dateStr; // 'dateStr' is Param 1
|
|
72
|
-
const insightsDoc = rootDataToday.insights;
|
|
73
|
-
|
|
74
|
-
if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
|
|
75
|
-
logger.log('WARN', `[daily-total-positions-held] No 'insights' data found.`);
|
|
76
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
77
|
-
this.result = { totalPositions: 0 };
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const instrument of insightsDoc.insights) {
|
|
82
|
-
// The 'total' field from the doc is the total # of positions for that instrument
|
|
83
|
-
if (typeof instrument.total === 'number') {
|
|
84
|
-
totalPositions += instrument.total;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
89
|
-
this.result = {
|
|
90
|
-
totalPositions: totalPositions
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// --- STANDARD 2: ADDED ---
|
|
95
|
-
async getResult(fetchedDependencies) {
|
|
96
|
-
return this.result;
|
|
32
|
+
process(context) {
|
|
33
|
+
const { extract } = context.math;
|
|
34
|
+
const { user } = context;
|
|
35
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
36
|
+
this.totalPositions += positions.length;
|
|
97
37
|
}
|
|
98
38
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.result = {};
|
|
39
|
+
getResult() {
|
|
40
|
+
return { total_positions_count: this.totalPositions };
|
|
102
41
|
}
|
|
103
|
-
}
|
|
104
42
|
|
|
105
|
-
|
|
43
|
+
reset() { this.totalPositions = 0; }
|
|
44
|
+
}
|
|
45
|
+
module.exports = PlatformTotalPositionsHeld;
|
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for P&L distribution per stock.
|
|
3
|
-
*
|
|
4
|
-
* This metric tracks the distribution of P&L percentages for all open
|
|
5
|
-
* positions, grouped by instrument.
|
|
6
|
-
*
|
|
7
|
-
* REFACTOR: This calculation now aggregates the distribution into
|
|
8
|
-
* predefined buckets on the server-side, returning a chart-ready
|
|
9
|
-
* histogram object instead of raw arrays.
|
|
10
|
-
*
|
|
11
|
-
* --- FIX: 2025-11-12 ---
|
|
12
|
-
* This calculation is a dependency for crowd_sharpe_ratio_proxy,
|
|
13
|
-
* which requires sum, sumSq, and count for variance calculations.
|
|
14
|
-
* This file has been updated to provide *both* the histogram
|
|
15
|
-
* and a 'stats' object containing these required values.
|
|
16
|
-
* ---------------------
|
|
3
|
+
* REFACTORED: Aggregates distribution of P&L Percentages.
|
|
17
4
|
*/
|
|
18
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
19
|
-
|
|
20
|
-
// Define the P&L percentage buckets for the histogram
|
|
21
5
|
const BUCKETS = [
|
|
22
|
-
{ label: 'loss_heavy', min: -Infinity, max: -50 },
|
|
23
|
-
{ label: 'loss_medium', min: -50, max: -25 },
|
|
24
|
-
{ label: 'loss_light', min: -25, max: 0 },
|
|
25
|
-
{ label: 'gain_light', min: 0, max: 25 },
|
|
26
|
-
{ label: 'gain_medium', min: 25, max: 50 },
|
|
27
|
-
{ label: 'gain_heavy', min: 50, max: 100 },
|
|
28
|
-
{ label: 'gain_extreme', min: 100, max: Infinity }
|
|
6
|
+
{ label: 'loss_heavy', min: -Infinity, max: -50 },
|
|
7
|
+
{ label: 'loss_medium', min: -50, max: -25 },
|
|
8
|
+
{ label: 'loss_light', min: -25, max: 0 },
|
|
9
|
+
{ label: 'gain_light', min: 0, max: 25 },
|
|
10
|
+
{ label: 'gain_medium', min: 25, max: 50 },
|
|
11
|
+
{ label: 'gain_heavy', min: 50, max: 100 },
|
|
12
|
+
{ label: 'gain_extreme', min: 100, max: Infinity }
|
|
29
13
|
];
|
|
30
14
|
|
|
31
15
|
class PnlDistributionPerStock {
|
|
32
16
|
constructor() {
|
|
33
|
-
// We will store { [instrumentId]: [pnlPercent1, pnlPercent2, ...] }
|
|
34
17
|
this.pnlMap = new Map();
|
|
35
|
-
// --- STANDARD 0: RENAMED ---
|
|
36
18
|
this.tickerMap = null;
|
|
37
19
|
}
|
|
38
20
|
|
|
39
|
-
/**
|
|
40
|
-
* Statically defines all metadata for the manifest builder.
|
|
41
|
-
*/
|
|
42
21
|
static getMetadata() {
|
|
43
22
|
return {
|
|
44
23
|
type: 'standard',
|
|
@@ -49,58 +28,18 @@ class PnlDistributionPerStock {
|
|
|
49
28
|
};
|
|
50
29
|
}
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
* Statically declare dependencies.
|
|
54
|
-
*/
|
|
55
|
-
static getDependencies() {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
31
|
+
static getDependencies() { return []; }
|
|
58
32
|
|
|
59
|
-
/**
|
|
60
|
-
* Defines the output schema for this calculation.
|
|
61
|
-
*/
|
|
62
33
|
static getSchema() {
|
|
63
34
|
const bucketSchema = {
|
|
64
35
|
"type": "object",
|
|
65
|
-
"description": "Histogram and stats of P&L distribution for a single asset.",
|
|
66
36
|
"properties": {
|
|
67
|
-
"histogram": {
|
|
68
|
-
|
|
69
|
-
"description": "Histogram of P&L distribution.",
|
|
70
|
-
"properties": {
|
|
71
|
-
"loss_heavy": { "type": "number", "description": "Count of positions with > 50% loss" },
|
|
72
|
-
"loss_medium": { "type": "number", "description": "Count of positions with 25-50% loss" },
|
|
73
|
-
"loss_light": { "type": "number", "description": "Count of positions with 0-25% loss" },
|
|
74
|
-
"gain_light": { "type": "number", "description": "Count of positions with 0-25% gain" },
|
|
75
|
-
"gain_medium": { "type": "number", "description": "Count of positions with 25-50% gain" },
|
|
76
|
-
"gain_heavy": { "type": "number", "description": "Count of positions with 50-100% gain" },
|
|
77
|
-
"gain_extreme": { "type": "number", "description": "Count of positions with > 100% gain" },
|
|
78
|
-
"total_positions": { "type": "number", "description": "Total positions counted" }
|
|
79
|
-
},
|
|
80
|
-
"required": ["total_positions"]
|
|
81
|
-
},
|
|
82
|
-
"stats": {
|
|
83
|
-
"type": "object",
|
|
84
|
-
"description": "Raw statistics needed for variance/Sharpe calculations.",
|
|
85
|
-
"properties": {
|
|
86
|
-
"sum": { "type": "number", "description": "Sum of all P&L percentages" },
|
|
87
|
-
"sumSq": { "type": "number", "description": "Sum of all squared P&L percentages" },
|
|
88
|
-
"count": { "type": "number", "description": "Total count of positions" }
|
|
89
|
-
},
|
|
90
|
-
"required": ["sum", "sumSq", "count"]
|
|
91
|
-
}
|
|
37
|
+
"histogram": { "type": "object" },
|
|
38
|
+
"stats": { "type": "object", "required": ["sum", "sumSq", "count"] }
|
|
92
39
|
},
|
|
93
40
|
"required": ["histogram", "stats"]
|
|
94
41
|
};
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
"type": "object",
|
|
98
|
-
"description": "Calculates a histogram and raw stats of P&L percentage distribution for all open positions, per asset.",
|
|
99
|
-
"patternProperties": {
|
|
100
|
-
"^.*$": bucketSchema // Ticker
|
|
101
|
-
},
|
|
102
|
-
"additionalProperties": bucketSchema
|
|
103
|
-
};
|
|
42
|
+
return { "type": "object", "patternProperties": { "^.*$": bucketSchema } };
|
|
104
43
|
}
|
|
105
44
|
|
|
106
45
|
_initAsset(instrumentId) {
|
|
@@ -109,74 +48,37 @@ class PnlDistributionPerStock {
|
|
|
109
48
|
}
|
|
110
49
|
}
|
|
111
50
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!this.tickerMap)
|
|
116
|
-
this.tickerMap = context.instrumentToTicker;
|
|
117
|
-
}
|
|
51
|
+
process(context) {
|
|
52
|
+
const { extract } = context.math;
|
|
53
|
+
const { mappings, user } = context;
|
|
54
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
118
55
|
|
|
119
|
-
const positions =
|
|
120
|
-
if (!positions || !Array.isArray(positions)) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
56
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
123
57
|
|
|
124
58
|
for (const pos of positions) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
// ---
|
|
128
|
-
// FIX 1: The schema file (schema.md) shows the P&L field
|
|
129
|
-
// is 'NetProfit', not 'ProfitRate'.
|
|
130
|
-
// ---
|
|
131
|
-
const pnlPercent = pos.NetProfit;
|
|
132
|
-
|
|
133
|
-
if (!instrumentId || typeof pnlPercent !== 'number') {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
59
|
+
const instId = extract.getInstrumentId(pos);
|
|
60
|
+
if (!instId) continue;
|
|
136
61
|
|
|
137
|
-
|
|
62
|
+
const pnlPercent = extract.getNetProfit(pos);
|
|
138
63
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// (e.g., 34.09), not a decimal (e.g., 0.34).
|
|
142
|
-
// Do not multiply by 100.
|
|
143
|
-
// ---
|
|
144
|
-
this.pnlMap.get(instrumentId).push(pnlPercent);
|
|
64
|
+
this._initAsset(instId);
|
|
65
|
+
this.pnlMap.get(instId).push(pnlPercent);
|
|
145
66
|
}
|
|
146
67
|
}
|
|
147
68
|
|
|
148
|
-
/**
|
|
149
|
-
* REFACTOR: This method now calculates the distribution on the server.
|
|
150
|
-
* --- FIX: 2025-11-12 ---
|
|
151
|
-
* Also calculates and returns sum, sumSq, and count.
|
|
152
|
-
*/
|
|
153
69
|
async getResult() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// Failsafe check
|
|
157
|
-
if (!this.tickerMap) {
|
|
158
|
-
return {}; // process() must run first
|
|
159
|
-
}
|
|
70
|
+
if (!this.tickerMap) return {};
|
|
160
71
|
|
|
161
72
|
const result = {};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// --- STANDARD 0: SIMPLIFIED ---
|
|
165
|
-
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
73
|
+
for (const [instId, pnlValues] of this.pnlMap.entries()) {
|
|
74
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
166
75
|
const count = pnlValues.length;
|
|
167
76
|
|
|
168
|
-
if (count === 0)
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
77
|
+
if (count === 0) continue;
|
|
171
78
|
|
|
172
79
|
const histogram = {
|
|
173
|
-
loss_heavy: 0,
|
|
174
|
-
|
|
175
|
-
loss_light: 0,
|
|
176
|
-
gain_light: 0,
|
|
177
|
-
gain_medium: 0,
|
|
178
|
-
gain_heavy: 0,
|
|
179
|
-
gain_extreme: 0,
|
|
80
|
+
loss_heavy: 0, loss_medium: 0, loss_light: 0,
|
|
81
|
+
gain_light: 0, gain_medium: 0, gain_heavy: 0, gain_extreme: 0,
|
|
180
82
|
total_positions: count
|
|
181
83
|
};
|
|
182
84
|
|
|
@@ -195,15 +97,9 @@ class PnlDistributionPerStock {
|
|
|
195
97
|
}
|
|
196
98
|
}
|
|
197
99
|
|
|
198
|
-
const stats = {
|
|
199
|
-
sum: sum,
|
|
200
|
-
sumSq: sumSq,
|
|
201
|
-
count: count
|
|
202
|
-
};
|
|
203
|
-
|
|
204
100
|
result[ticker] = {
|
|
205
101
|
histogram: histogram,
|
|
206
|
-
stats:
|
|
102
|
+
stats: { sum, sumSq, count }
|
|
207
103
|
};
|
|
208
104
|
}
|
|
209
105
|
return result;
|
|
@@ -211,9 +107,7 @@ class PnlDistributionPerStock {
|
|
|
211
107
|
|
|
212
108
|
reset() {
|
|
213
109
|
this.pnlMap.clear();
|
|
214
|
-
// --- STANDARD 0: RENAMED ---
|
|
215
110
|
this.tickerMap = null;
|
|
216
111
|
}
|
|
217
112
|
}
|
|
218
|
-
|
|
219
113
|
module.exports = PnlDistributionPerStock;
|