aiden-shared-calculations-unified 1.0.71 → 1.0.73
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/behavioural/historical/asset_crowd_flow.js +2 -2
- package/calculations/behavioural/historical/dumb-cohort-flow.js +1 -1
- package/calculations/behavioural/historical/gem_cohort-skill-definition.js +109 -0
- package/calculations/behavioural/historical/gem_skilled-cohort-flow.js +226 -0
- package/calculations/behavioural/historical/gem_unskilled-cohort-flow.js +225 -0
- package/calculations/insights/daily_ownership_per_sector.js +1 -1
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +31 -18
- package/calculations/meta/gem_cohort-momentum-state.js +110 -0
- package/calculations/meta/gem_instrument-price-momentum.js +115 -0
- package/calculations/meta/gem_skilled-unskilled-divergence.js +139 -0
- package/calculations/meta/quant-skill-alpha-signal.js +152 -0
- package/calculations/meta/social-topic-driver-index.js +56 -9
- package/calculations/meta/social-topic-predictive-potential.js +9 -3
- package/calculations/pnl/pnl_distribution_per_stock.js +70 -16
- package/calculations/sentiment/gem_platform-conviction-divergence.js +141 -0
- package/package.json +3 -2
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* REFACTOR: This calculation now aggregates the distribution into
|
|
8
8
|
* predefined buckets on the server-side, returning a chart-ready
|
|
9
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
|
+
* ---------------------
|
|
10
17
|
*/
|
|
11
18
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
12
19
|
|
|
@@ -31,28 +38,48 @@ class PnlDistributionPerStock {
|
|
|
31
38
|
/**
|
|
32
39
|
* Defines the output schema for this calculation.
|
|
33
40
|
* REFACTOR: Schema now describes the server-calculated histogram.
|
|
34
|
-
*
|
|
41
|
+
*
|
|
42
|
+
* --- FIX: 2025-11-12 ---
|
|
43
|
+
* Added 'stats' object to the schema to support downstream
|
|
44
|
+
* meta-calculations like crowd_sharpe_ratio_proxy.
|
|
35
45
|
*/
|
|
36
46
|
static getSchema() {
|
|
37
47
|
const bucketSchema = {
|
|
38
48
|
"type": "object",
|
|
39
|
-
"description": "Histogram of P&L distribution for a single asset.",
|
|
49
|
+
"description": "Histogram and stats of P&L distribution for a single asset.",
|
|
40
50
|
"properties": {
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
"histogram": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "Histogram of P&L distribution.",
|
|
54
|
+
"properties": {
|
|
55
|
+
"loss_heavy": { "type": "number", "description": "Count of positions with > 50% loss" },
|
|
56
|
+
"loss_medium": { "type": "number", "description": "Count of positions with 25-50% loss" },
|
|
57
|
+
"loss_light": { "type": "number", "description": "Count of positions with 0-25% loss" },
|
|
58
|
+
"gain_light": { "type": "number", "description": "Count of positions with 0-25% gain" },
|
|
59
|
+
"gain_medium": { "type": "number", "description": "Count of positions with 25-50% gain" },
|
|
60
|
+
"gain_heavy": { "type": "number", "description": "Count of positions with 50-100% gain" },
|
|
61
|
+
"gain_extreme": { "type": "number", "description": "Count of positions with > 100% gain" },
|
|
62
|
+
"total_positions": { "type": "number", "description": "Total positions counted" }
|
|
63
|
+
},
|
|
64
|
+
"required": ["total_positions"]
|
|
65
|
+
},
|
|
66
|
+
"stats": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"description": "Raw statistics needed for variance/Sharpe calculations.",
|
|
69
|
+
"properties": {
|
|
70
|
+
"sum": { "type": "number", "description": "Sum of all P&L percentages" },
|
|
71
|
+
"sumSq": { "type": "number", "description": "Sum of all squared P&L percentages" },
|
|
72
|
+
"count": { "type": "number", "description": "Total count of positions" }
|
|
73
|
+
},
|
|
74
|
+
"required": ["sum", "sumSq", "count"]
|
|
75
|
+
}
|
|
49
76
|
},
|
|
50
|
-
"required": ["
|
|
77
|
+
"required": ["histogram", "stats"]
|
|
51
78
|
};
|
|
52
79
|
|
|
53
80
|
return {
|
|
54
81
|
"type": "object",
|
|
55
|
-
"description": "Calculates a histogram of P&L percentage distribution for all open positions, per asset.",
|
|
82
|
+
"description": "Calculates a histogram and raw stats of P&L percentage distribution for all open positions, per asset.",
|
|
56
83
|
"patternProperties": {
|
|
57
84
|
"^.*$": bucketSchema // Ticker
|
|
58
85
|
},
|
|
@@ -90,6 +117,9 @@ class PnlDistributionPerStock {
|
|
|
90
117
|
/**
|
|
91
118
|
* REFACTOR: This method now calculates the distribution on the server.
|
|
92
119
|
* It transforms the raw P&L arrays into histogram bucket counts.
|
|
120
|
+
*
|
|
121
|
+
* --- FIX: 2025-11-12 ---
|
|
122
|
+
* Also calculates and returns sum, sumSq, and count.
|
|
93
123
|
*/
|
|
94
124
|
async getResult() {
|
|
95
125
|
if (!this.mappings) {
|
|
@@ -100,6 +130,11 @@ class PnlDistributionPerStock {
|
|
|
100
130
|
|
|
101
131
|
for (const [instrumentId, pnlValues] of this.pnlMap.entries()) {
|
|
102
132
|
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
133
|
+
const count = pnlValues.length;
|
|
134
|
+
|
|
135
|
+
if (count === 0) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
103
138
|
|
|
104
139
|
// 1. Initialize the histogram object for this ticker
|
|
105
140
|
const histogram = {
|
|
@@ -110,11 +145,20 @@ class PnlDistributionPerStock {
|
|
|
110
145
|
gain_medium: 0,
|
|
111
146
|
gain_heavy: 0,
|
|
112
147
|
gain_extreme: 0,
|
|
113
|
-
total_positions:
|
|
148
|
+
total_positions: count
|
|
114
149
|
};
|
|
115
150
|
|
|
116
|
-
//
|
|
151
|
+
// --- FIX: Initialize stats ---
|
|
152
|
+
let sum = 0;
|
|
153
|
+
let sumSq = 0;
|
|
154
|
+
|
|
155
|
+
// 2. Process all P&L values into buckets and calculate stats
|
|
117
156
|
for (const pnl of pnlValues) {
|
|
157
|
+
// --- FIX: Add to stats ---
|
|
158
|
+
sum += pnl;
|
|
159
|
+
sumSq += (pnl * pnl);
|
|
160
|
+
|
|
161
|
+
// Add to histogram
|
|
118
162
|
for (const bucket of BUCKETS) {
|
|
119
163
|
if (pnl >= bucket.min && pnl < bucket.max) {
|
|
120
164
|
histogram[bucket.label]++;
|
|
@@ -122,9 +166,19 @@ class PnlDistributionPerStock {
|
|
|
122
166
|
}
|
|
123
167
|
}
|
|
124
168
|
}
|
|
169
|
+
|
|
170
|
+
// --- FIX: Create stats object ---
|
|
171
|
+
const stats = {
|
|
172
|
+
sum: sum,
|
|
173
|
+
sumSq: sumSq,
|
|
174
|
+
count: count
|
|
175
|
+
};
|
|
125
176
|
|
|
126
|
-
// 3. Add the aggregated histogram to the final result
|
|
127
|
-
result[ticker] =
|
|
177
|
+
// 3. Add the aggregated histogram and stats to the final result
|
|
178
|
+
result[ticker] = {
|
|
179
|
+
histogram: histogram,
|
|
180
|
+
stats: stats
|
|
181
|
+
};
|
|
128
182
|
}
|
|
129
183
|
return result;
|
|
130
184
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for platform conviction divergence.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "Is our 20,000-user sample more bullish or bearish
|
|
5
|
+
* on an asset than the entire eToro platform?"
|
|
6
|
+
*
|
|
7
|
+
* It compares the long/short ratio of our sample (from 'portfolio' data)
|
|
8
|
+
* against the platform-wide 'buy'/'sell' % (from 'insights' data).
|
|
9
|
+
*/
|
|
10
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
|
+
|
|
12
|
+
class PlatformConvictionDivergence {
|
|
13
|
+
constructor() {
|
|
14
|
+
// { [instrumentId]: { long: 0, short: 0 } }
|
|
15
|
+
this.sampledCounts = new Map();
|
|
16
|
+
this.mappings = null;
|
|
17
|
+
this.todayInsightsData = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Defines the output schema for this calculation.
|
|
22
|
+
* @returns {object} JSON Schema object
|
|
23
|
+
*/
|
|
24
|
+
static getSchema() {
|
|
25
|
+
const tickerSchema = {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"platform_long_pct": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Percentage of holders on the entire platform who are long."
|
|
31
|
+
},
|
|
32
|
+
"sampled_long_pct": {
|
|
33
|
+
"type": ["number", "null"],
|
|
34
|
+
"description": "Percentage of holders in our sample who are long. Null if no sample."
|
|
35
|
+
},
|
|
36
|
+
"divergence": {
|
|
37
|
+
"type": ["number", "null"],
|
|
38
|
+
"description": "The difference (Sampled % - Platform %). Positive means our sample is more bullish."
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": ["platform_long_pct", "sampled_long_pct", "divergence"]
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"description": "Calculates the divergence between the sample's long/short ratio and the platform's.",
|
|
47
|
+
"patternProperties": {
|
|
48
|
+
"^.*$": tickerSchema // Ticker
|
|
49
|
+
},
|
|
50
|
+
"additionalProperties": tickerSchema
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* This is a Pass 1 calculation and has no dependencies.
|
|
56
|
+
*/
|
|
57
|
+
static getDependencies() {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_initAsset(instrumentId) {
|
|
62
|
+
if (!this.sampledCounts.has(instrumentId)) {
|
|
63
|
+
this.sampledCounts.set(instrumentId, { long: 0, short: 0 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process(portfolioData, yesterdayPortfolio, userId, context, todayInsights) {
|
|
68
|
+
// Capture insights data on the first user processed
|
|
69
|
+
if (!this.todayInsightsData && todayInsights) {
|
|
70
|
+
this.todayInsightsData = todayInsights;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
74
|
+
if (!positions || !Array.isArray(positions)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const pos of positions) {
|
|
79
|
+
const instrumentId = pos.InstrumentID;
|
|
80
|
+
if (!instrumentId) continue;
|
|
81
|
+
|
|
82
|
+
this._initAsset(instrumentId);
|
|
83
|
+
const assetData = this.sampledCounts.get(instrumentId);
|
|
84
|
+
|
|
85
|
+
if (pos.IsBuy) {
|
|
86
|
+
assetData.long++;
|
|
87
|
+
} else {
|
|
88
|
+
assetData.short++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getResult() {
|
|
94
|
+
if (!this.mappings) {
|
|
95
|
+
this.mappings = await loadInstrumentMappings();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = {};
|
|
99
|
+
const insights = this.todayInsightsData?.insights;
|
|
100
|
+
|
|
101
|
+
if (!insights || !Array.isArray(insights)) {
|
|
102
|
+
return {}; // No platform data to compare against
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const instrument of insights) {
|
|
106
|
+
const instrumentId = instrument.instrumentId;
|
|
107
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId];
|
|
108
|
+
if (!ticker) continue;
|
|
109
|
+
|
|
110
|
+
const platform_long_pct = instrument.buy || 0; // e.g., 51
|
|
111
|
+
|
|
112
|
+
const sampledData = this.sampledCounts.get(instrumentId);
|
|
113
|
+
const sampled_long = sampledData?.long || 0;
|
|
114
|
+
const sampled_short = sampledData?.short || 0;
|
|
115
|
+
const totalSampled = sampled_long + sampled_short;
|
|
116
|
+
|
|
117
|
+
let sampled_long_pct = null;
|
|
118
|
+
let divergence = null;
|
|
119
|
+
|
|
120
|
+
if (totalSampled > 0) {
|
|
121
|
+
sampled_long_pct = (sampled_long / totalSampled) * 100;
|
|
122
|
+
divergence = sampled_long_pct - platform_long_pct;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
result[ticker] = {
|
|
126
|
+
platform_long_pct: platform_long_pct,
|
|
127
|
+
sampled_long_pct: sampled_long_pct,
|
|
128
|
+
divergence: divergence
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
reset() {
|
|
135
|
+
this.sampledCounts.clear();
|
|
136
|
+
this.mappings = null;
|
|
137
|
+
this.todayInsightsData = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = PlatformConvictionDivergence;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiden-shared-calculations-unified",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.73",
|
|
4
4
|
"description": "Shared calculation modules for the BullTrackers Computation System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"@google-cloud/firestore": "^7.11.3",
|
|
25
25
|
"sharedsetup": "latest",
|
|
26
26
|
"require-all": "^3.0.0",
|
|
27
|
-
"dotenv": "latest"
|
|
27
|
+
"dotenv": "latest",
|
|
28
|
+
"viz.js": "^2.1.2"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"bulltracker-deployer": "file:../bulltracker-deployer"
|