aiden-shared-calculations-unified 1.0.108 → 1.0.110
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/insights-daily-bought-vs-sold-count.js +20 -15
- package/calculations/core/insights-daily-ownership-delta.js +12 -10
- package/calculations/core/instrument-price-change-1d.js +16 -31
- package/calculations/core/instrument-price-momentum-20d.js +16 -26
- package/calculations/core/ownership-vs-performance-ytd.js +15 -52
- package/calculations/core/ownership-vs-volatility.js +27 -36
- package/calculations/core/platform-daily-bought-vs-sold-count.js +28 -26
- package/calculations/core/platform-daily-ownership-delta.js +28 -31
- package/calculations/core/price-metrics.js +15 -54
- package/calculations/core/short-interest-growth.js +6 -13
- package/calculations/core/trending-ownership-momentum.js +16 -28
- package/calculations/core/user-history-reconstructor.js +48 -49
- package/calculations/gauss/cohort-capital-flow.js +34 -71
- package/calculations/gauss/cohort-definer.js +61 -142
- package/calculations/gem/cohort-momentum-state.js +27 -77
- package/calculations/gem/skilled-cohort-flow.js +36 -114
- package/calculations/gem/unskilled-cohort-flow.js +36 -112
- package/calculations/ghost-book/retail-gamma-exposure.js +14 -61
- package/calculations/helix/herd-consensus-score.js +27 -90
- package/calculations/helix/winner-loser-flow.js +21 -90
- package/calculations/predicative-alpha/cognitive-dissonance.js +25 -91
- package/calculations/predicative-alpha/diamond-hand-fracture.js +17 -72
- package/calculations/predicative-alpha/mimetic-latency.js +21 -100
- package/package.json +1 -1
|
@@ -1,144 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
-
* REFACTORED: Uses context.computed and context.math.extract.
|
|
4
|
-
*/
|
|
5
1
|
class SkilledCohortFlow {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.dependenciesLoaded = false;
|
|
10
|
-
this.tickerMap = null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
static getMetadata() {
|
|
14
|
-
return {
|
|
15
|
-
type: 'standard',
|
|
16
|
-
rootDataDependencies: ['portfolio'],
|
|
17
|
-
isHistorical: true,
|
|
18
|
-
userType: 'all',
|
|
19
|
-
category: 'gem'
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
static getDependencies() {
|
|
24
|
-
return ['cohort-skill-definition', 'instrument-price-change-1d'];
|
|
25
|
-
}
|
|
2
|
+
constructor() { this.assetFlows = new Map(); this.cohortMap = new Map(); this.dependenciesLoaded = false; this.tickerMap = null; }
|
|
3
|
+
static getMetadata() { return { type: 'standard', rootDataDependencies: ['portfolio'], isHistorical: true, userType: 'all', category: 'gem' }; }
|
|
4
|
+
static getDependencies() { return ['cohort-skill-definition', 'instrument-price-change-1d']; }
|
|
26
5
|
|
|
27
6
|
static getSchema() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
"properties": {
|
|
31
|
-
"net_flow_pct": { "type": "number" },
|
|
32
|
-
"net_flow_contribution": { "type": "number" },
|
|
33
|
-
"avg_position_change_pct": { "type": "number" },
|
|
34
|
-
"user_count": { "type": "number" }
|
|
35
|
-
},
|
|
36
|
-
"required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count"]
|
|
37
|
-
};
|
|
38
|
-
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
_initFlowData(instrumentId) {
|
|
42
|
-
if (!this.assetFlows.has(instrumentId)) {
|
|
43
|
-
this.assetFlows.set(instrumentId, {
|
|
44
|
-
total_invested_yesterday: 0, total_invested_today: 0, price_change_yesterday: 0,
|
|
45
|
-
total_pos_size_yesterday: 0, total_pos_size_today: 0, user_count_yesterday: 0, user_count_today: 0
|
|
46
|
-
});
|
|
47
|
-
}
|
|
7
|
+
const s = { "type": "object", "properties": { "net_flow_pct": { "type": ["number", "null"] }, "net_flow_contribution": { "type": ["number", "null"] }, "avg_position_change_pct": { "type": ["number", "null"] }, "user_count": { "type": "number" }, "total_invested_today": { "type": "number" } }, "required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count", "total_invested_today"] };
|
|
8
|
+
return { "type": "object", "patternProperties": { "^.*$": s } };
|
|
48
9
|
}
|
|
49
10
|
|
|
50
11
|
_loadDependencies(computed) {
|
|
51
12
|
if (this.dependenciesLoaded) return;
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
(cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
55
|
-
}
|
|
13
|
+
const d = computed['cohort-skill-definition'];
|
|
14
|
+
if (d) (d.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
56
15
|
this.dependenciesLoaded = true;
|
|
57
16
|
}
|
|
58
17
|
|
|
59
18
|
process(context) {
|
|
60
19
|
const { user, computed, mappings, math } = context;
|
|
61
20
|
const { extract } = math;
|
|
62
|
-
|
|
63
21
|
this._loadDependencies(computed);
|
|
64
|
-
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
65
|
-
|
|
66
22
|
if (this.cohortMap.get(user.id) !== 'skilled') return;
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
if (!priceChangeMap) return;
|
|
23
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
24
|
+
const priceMap = computed['instrument-price-change-1d'];
|
|
70
25
|
|
|
71
|
-
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
72
26
|
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
73
|
-
|
|
27
|
+
const yPos = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
|
|
74
28
|
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
75
29
|
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
for (const instId of allInstrumentIds) {
|
|
79
|
-
if (!instId) continue;
|
|
80
|
-
this._initFlowData(instId);
|
|
81
|
-
const asset = this.assetFlows.get(instId);
|
|
82
|
-
|
|
83
|
-
const yWeight = extract.getPositionWeight(yPosMap.get(instId), user.type);
|
|
84
|
-
const tWeight = extract.getPositionWeight(tPosMap.get(instId), user.type);
|
|
30
|
+
const ids = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
85
31
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
asset.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
32
|
+
for (const id of ids) {
|
|
33
|
+
if (!this.assetFlows.has(id)) this.assetFlows.set(id, { yInv: 0, tInv: 0, pChg: 0, ySize: 0, tSize: 0, yCount: 0, tCount: 0, hasY: false });
|
|
34
|
+
const asset = this.assetFlows.get(id);
|
|
35
|
+
const tW = extract.getPositionWeight(tPosMap.get(id), user.type);
|
|
36
|
+
if (tW > 0) { asset.tInv += tW; asset.tCount++; asset.tSize += tW; }
|
|
37
|
+
if (user.portfolio.yesterday) {
|
|
38
|
+
asset.hasY = true;
|
|
39
|
+
const yW = extract.getPositionWeight(yPosMap.get(id), user.type);
|
|
40
|
+
if (yW > 0) {
|
|
41
|
+
asset.yInv += yW; asset.yCount++; asset.ySize += yW;
|
|
42
|
+
const tick = mappings.instrumentToTicker[id];
|
|
43
|
+
const chg = (tick && priceMap?.[tick]) ? priceMap[tick].change_1d_pct : 0;
|
|
44
|
+
asset.pChg += ((chg || 0) / 100.0) * yW;
|
|
45
|
+
}
|
|
98
46
|
}
|
|
99
47
|
}
|
|
100
48
|
}
|
|
101
49
|
|
|
102
50
|
async getResult() {
|
|
103
|
-
if (!this.tickerMap) return {};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const avg_change = data.price_change_yesterday / data.total_invested_yesterday;
|
|
112
|
-
const adjusted_y = data.total_invested_yesterday * (1 + avg_change);
|
|
113
|
-
flow_contrib = data.total_invested_today - adjusted_y;
|
|
114
|
-
net_flow = (flow_contrib / data.total_invested_yesterday) * 100;
|
|
115
|
-
} else if (data.total_invested_today > 0) {
|
|
116
|
-
flow_contrib = data.total_invested_today;
|
|
117
|
-
net_flow = Infinity;
|
|
51
|
+
if (!this.tickerMap) return {}; const res = {};
|
|
52
|
+
for (const [id, d] of this.assetFlows.entries()) {
|
|
53
|
+
const tick = this.tickerMap[id]; if (!tick) continue;
|
|
54
|
+
let net = null, contrib = null, avgPos = null;
|
|
55
|
+
if (d.hasY) {
|
|
56
|
+
const adjY = d.yInv + d.pChg; contrib = d.tInv - adjY;
|
|
57
|
+
net = d.yInv > 0 ? (contrib / d.yInv) * 100 : (d.tInv > 0 ? Infinity : 0);
|
|
58
|
+
if (d.yCount > 0 && d.tCount > 0) avgPos = (((d.tSize / d.tCount) - (d.ySize / d.yCount)) / (d.ySize / d.yCount)) * 100;
|
|
118
59
|
}
|
|
119
|
-
|
|
120
|
-
let avg_pos_change = 0;
|
|
121
|
-
if (data.user_count_yesterday > 0 && data.user_count_today > 0) {
|
|
122
|
-
const avg_y = data.total_pos_size_yesterday / data.user_count_yesterday;
|
|
123
|
-
const avg_t = data.total_pos_size_today / data.user_count_today;
|
|
124
|
-
if (avg_y > 0) avg_pos_change = ((avg_t - avg_y) / avg_y) * 100;
|
|
125
|
-
} else if (data.user_count_today > 0) avg_pos_change = Infinity;
|
|
126
|
-
|
|
127
|
-
finalResult[ticker] = {
|
|
128
|
-
net_flow_pct: isFinite(net_flow) ? net_flow : 0,
|
|
129
|
-
net_flow_contribution: isFinite(flow_contrib) ? flow_contrib : 0,
|
|
130
|
-
avg_position_change_pct: isFinite(avg_pos_change) ? avg_pos_change : 0,
|
|
131
|
-
user_count: data.user_count_today
|
|
132
|
-
};
|
|
60
|
+
res[tick] = { net_flow_pct: isFinite(net) ? net : null, net_flow_contribution: isFinite(contrib) ? contrib : null, avg_position_change_pct: isFinite(avgPos) ? avgPos : null, user_count: d.tCount, total_invested_today: d.tInv };
|
|
133
61
|
}
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
reset() {
|
|
138
|
-
this.assetFlows.clear();
|
|
139
|
-
this.cohortMap.clear();
|
|
140
|
-
this.dependenciesLoaded = false;
|
|
141
|
-
this.tickerMap = null;
|
|
62
|
+
return res;
|
|
142
63
|
}
|
|
64
|
+
reset() { this.assetFlows.clear(); this.cohortMap.clear(); this.dependenciesLoaded = false; this.tickerMap = null; }
|
|
143
65
|
}
|
|
144
66
|
module.exports = SkilledCohortFlow;
|
|
@@ -1,142 +1,66 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
-
* REFACTORED: Uses context.computed and context.math.extract.
|
|
4
|
-
*/
|
|
5
1
|
class UnskilledCohortFlow {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.dependenciesLoaded = false;
|
|
10
|
-
this.tickerMap = null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
static getMetadata() {
|
|
14
|
-
return {
|
|
15
|
-
type: 'standard',
|
|
16
|
-
rootDataDependencies: ['portfolio'],
|
|
17
|
-
isHistorical: true,
|
|
18
|
-
userType: 'all',
|
|
19
|
-
category: 'gem'
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
static getDependencies() {
|
|
24
|
-
return ['cohort-skill-definition', 'instrument-price-change-1d'];
|
|
25
|
-
}
|
|
2
|
+
constructor() { this.assetFlows = new Map(); this.cohortMap = new Map(); this.dependenciesLoaded = false; this.tickerMap = null; }
|
|
3
|
+
static getMetadata() { return { type: 'standard', rootDataDependencies: ['portfolio'], isHistorical: true, userType: 'all', category: 'gem' }; }
|
|
4
|
+
static getDependencies() { return ['cohort-skill-definition', 'instrument-price-change-1d']; }
|
|
26
5
|
|
|
27
6
|
static getSchema() {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
"properties": {
|
|
31
|
-
"net_flow_pct": { "type": "number" },
|
|
32
|
-
"net_flow_contribution": { "type": "number" },
|
|
33
|
-
"avg_position_change_pct": { "type": "number" },
|
|
34
|
-
"user_count": { "type": "number" }
|
|
35
|
-
},
|
|
36
|
-
"required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count"]
|
|
37
|
-
};
|
|
38
|
-
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
_initFlowData(instrumentId) {
|
|
42
|
-
if (!this.assetFlows.has(instrumentId)) {
|
|
43
|
-
this.assetFlows.set(instrumentId, {
|
|
44
|
-
total_invested_yesterday: 0, total_invested_today: 0, price_change_yesterday: 0,
|
|
45
|
-
total_pos_size_yesterday: 0, total_pos_size_today: 0, user_count_yesterday: 0, user_count_today: 0
|
|
46
|
-
});
|
|
47
|
-
}
|
|
7
|
+
const s = { "type": "object", "properties": { "net_flow_pct": { "type": ["number", "null"] }, "net_flow_contribution": { "type": ["number", "null"] }, "avg_position_change_pct": { "type": ["number", "null"] }, "user_count": { "type": "number" }, "total_invested_today": { "type": "number" } }, "required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count", "total_invested_today"] };
|
|
8
|
+
return { "type": "object", "patternProperties": { "^.*$": s } };
|
|
48
9
|
}
|
|
49
10
|
|
|
50
11
|
_loadDependencies(computed) {
|
|
51
12
|
if (this.dependenciesLoaded) return;
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
54
|
-
(cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
|
|
55
|
-
}
|
|
13
|
+
const d = computed['cohort-skill-definition'];
|
|
14
|
+
if (d) (d.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
|
|
56
15
|
this.dependenciesLoaded = true;
|
|
57
16
|
}
|
|
58
17
|
|
|
59
18
|
process(context) {
|
|
60
19
|
const { user, computed, mappings, math } = context;
|
|
61
20
|
const { extract } = math;
|
|
62
|
-
|
|
63
21
|
this._loadDependencies(computed);
|
|
64
|
-
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
65
|
-
|
|
66
22
|
if (this.cohortMap.get(user.id) !== 'unskilled') return;
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
if (!priceChangeMap) return;
|
|
23
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
24
|
+
const priceMap = computed['instrument-price-change-1d'];
|
|
70
25
|
|
|
71
|
-
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
72
26
|
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
73
|
-
|
|
27
|
+
const yPos = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
|
|
74
28
|
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
75
29
|
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
for (const instId of allInstrumentIds) {
|
|
79
|
-
if (!instId) continue;
|
|
80
|
-
this._initFlowData(instId);
|
|
81
|
-
const asset = this.assetFlows.get(instId);
|
|
82
|
-
|
|
83
|
-
const yWeight = extract.getPositionWeight(yPosMap.get(instId), user.type);
|
|
84
|
-
const tWeight = extract.getPositionWeight(tPosMap.get(instId), user.type);
|
|
30
|
+
const ids = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
85
31
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
asset.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
32
|
+
for (const id of ids) {
|
|
33
|
+
if (!this.assetFlows.has(id)) this.assetFlows.set(id, { yInv: 0, tInv: 0, pChg: 0, ySize: 0, tSize: 0, yCount: 0, tCount: 0, hasY: false });
|
|
34
|
+
const asset = this.assetFlows.get(id);
|
|
35
|
+
const tW = extract.getPositionWeight(tPosMap.get(id), user.type);
|
|
36
|
+
if (tW > 0) { asset.tInv += tW; asset.tCount++; asset.tSize += tW; }
|
|
37
|
+
if (user.portfolio.yesterday) {
|
|
38
|
+
asset.hasY = true;
|
|
39
|
+
const yW = extract.getPositionWeight(yPosMap.get(id), user.type);
|
|
40
|
+
if (yW > 0) {
|
|
41
|
+
asset.yInv += yW; asset.yCount++; asset.ySize += yW;
|
|
42
|
+
const tick = mappings.instrumentToTicker[id];
|
|
43
|
+
const chg = (tick && priceMap?.[tick]) ? priceMap[tick].change_1d_pct : 0;
|
|
44
|
+
asset.pChg += ((chg || 0) / 100.0) * yW;
|
|
45
|
+
}
|
|
98
46
|
}
|
|
99
47
|
}
|
|
100
48
|
}
|
|
101
49
|
|
|
102
50
|
async getResult() {
|
|
103
|
-
if (!this.tickerMap) return {};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const adjusted_y = data.total_invested_yesterday * (1 + avg_change);
|
|
112
|
-
flow_contrib = data.total_invested_today - adjusted_y;
|
|
113
|
-
net_flow = (flow_contrib / data.total_invested_yesterday) * 100;
|
|
114
|
-
} else if (data.total_invested_today > 0) {
|
|
115
|
-
flow_contrib = data.total_invested_today;
|
|
116
|
-
net_flow = Infinity;
|
|
51
|
+
if (!this.tickerMap) return {}; const res = {};
|
|
52
|
+
for (const [id, d] of this.assetFlows.entries()) {
|
|
53
|
+
const tick = this.tickerMap[id]; if (!tick) continue;
|
|
54
|
+
let net = null, contrib = null, avgPos = null;
|
|
55
|
+
if (d.hasY) {
|
|
56
|
+
const adjY = d.yInv + d.pChg; contrib = d.tInv - adjY;
|
|
57
|
+
net = d.yInv > 0 ? (contrib / d.yInv) * 100 : (d.tInv > 0 ? Infinity : 0);
|
|
58
|
+
if (d.yCount > 0 && d.tCount > 0) avgPos = (((d.tSize / d.tCount) - (d.ySize / d.yCount)) / (d.ySize / d.yCount)) * 100;
|
|
117
59
|
}
|
|
118
|
-
|
|
119
|
-
if (data.user_count_yesterday > 0 && data.user_count_today > 0) {
|
|
120
|
-
const avg_y = data.total_pos_size_yesterday / data.user_count_yesterday;
|
|
121
|
-
const avg_t = data.total_pos_size_today / data.user_count_today;
|
|
122
|
-
if (avg_y > 0) avg_pos_change = ((avg_t - avg_y) / avg_y) * 100;
|
|
123
|
-
} else if (data.user_count_today > 0) avg_pos_change = Infinity;
|
|
124
|
-
|
|
125
|
-
finalResult[ticker] = {
|
|
126
|
-
net_flow_pct: isFinite(net_flow) ? net_flow : 0,
|
|
127
|
-
net_flow_contribution: isFinite(flow_contrib) ? flow_contrib : 0,
|
|
128
|
-
avg_position_change_pct: isFinite(avg_pos_change) ? avg_pos_change : 0,
|
|
129
|
-
user_count: data.user_count_today
|
|
130
|
-
};
|
|
60
|
+
res[tick] = { net_flow_pct: isFinite(net) ? net : null, net_flow_contribution: isFinite(contrib) ? contrib : null, avg_position_change_pct: isFinite(avgPos) ? avgPos : null, user_count: d.tCount, total_invested_today: d.tInv };
|
|
131
61
|
}
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
reset() {
|
|
136
|
-
this.assetFlows.clear();
|
|
137
|
-
this.cohortMap.clear();
|
|
138
|
-
this.dependenciesLoaded = false;
|
|
139
|
-
this.tickerMap = null;
|
|
62
|
+
return res;
|
|
140
63
|
}
|
|
64
|
+
reset() { this.assetFlows.clear(); this.cohortMap.clear(); this.dependenciesLoaded = false; this.tickerMap = null; }
|
|
141
65
|
}
|
|
142
66
|
module.exports = UnskilledCohortFlow;
|
|
@@ -1,80 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Ghost Book: Retail Gamma Exposure
|
|
3
|
-
* Measures the "Elasticity of Intent" (Beta) between Price Change and Net Flow.
|
|
4
|
-
*/
|
|
5
1
|
class RetailGammaExposure {
|
|
6
2
|
constructor() { this.gammaResults = {}; }
|
|
7
|
-
|
|
8
|
-
static getMetadata() {
|
|
9
|
-
return {
|
|
10
|
-
type: 'meta',
|
|
11
|
-
dependencies: ['skilled-cohort-flow', 'instrument-price-change-1d'],
|
|
12
|
-
isHistorical: true, // Requires t-1, t-2... for rolling regression
|
|
13
|
-
userType: 'n/a', // FIXED: Added missing field
|
|
14
|
-
category: 'ghost_book'
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
3
|
+
static getMetadata() { return { type: 'meta', dependencies: ['skilled-cohort-flow', 'instrument-price-change-1d'], isHistorical: true, userType: 'n/a', category: 'ghost_book' }; }
|
|
18
4
|
static getDependencies() { return ['skilled-cohort-flow', 'instrument-price-change-1d']; }
|
|
19
5
|
|
|
20
6
|
static getSchema() {
|
|
21
|
-
return {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"patternProperties": {
|
|
24
|
-
"^.*$": {
|
|
25
|
-
"type": "object",
|
|
26
|
-
"properties": {
|
|
27
|
-
"gamma_beta": { "type": "number" },
|
|
28
|
-
"regime": { "type": "string", "enum": ["ACCELERANT", "STABILIZER", "NEUTRAL"] },
|
|
29
|
-
"_state": { "type": "object" } // Rolling buffer
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
7
|
+
return { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "gamma_beta": { "type": ["number", "null"] }, "regime": { "type": "string" }, "_state": { "type": "object" } } } } };
|
|
34
8
|
}
|
|
35
9
|
|
|
36
10
|
process(context) {
|
|
37
11
|
const { computed, previousComputed, math } = context;
|
|
38
12
|
const { signals, distribution } = math;
|
|
39
|
-
|
|
40
13
|
const tickers = signals.getUnionKeys(computed, ['skilled-cohort-flow']);
|
|
41
|
-
|
|
42
14
|
for (const ticker of tickers) {
|
|
43
15
|
const flow = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (flowBuffer.length > bufferSize) {
|
|
56
|
-
flowBuffer.shift();
|
|
57
|
-
priceBuffer.shift();
|
|
16
|
+
const price = signals.getMetric(computed, 'instrument-price-change-1d', ticker, 'change_1d_pct', 0);
|
|
17
|
+
const prev = signals.getPreviousState(previousComputed, 'retail-gamma-exposure', ticker);
|
|
18
|
+
let fBuf = [...(prev?._state?.flow_buffer || [])], pBuf = [...(prev?._state?.price_buffer || [])];
|
|
19
|
+
fBuf.push(flow); pBuf.push(price);
|
|
20
|
+
if (fBuf.length > 14) { fBuf.shift(); pBuf.shift(); }
|
|
21
|
+
|
|
22
|
+
let beta = null, regime = "WARM_UP";
|
|
23
|
+
if (fBuf.length >= 7) {
|
|
24
|
+
const reg = distribution.linearRegression(pBuf, fBuf);
|
|
25
|
+
beta = reg.slope;
|
|
26
|
+
regime = beta > 0.5 ? "ACCELERANT" : (beta < -0.5 ? "STABILIZER" : "NEUTRAL");
|
|
58
27
|
}
|
|
59
|
-
|
|
60
|
-
const regression = distribution.linearRegression(priceBuffer, flowBuffer);
|
|
61
|
-
const beta = regression.slope;
|
|
62
|
-
|
|
63
|
-
let regime = "NEUTRAL";
|
|
64
|
-
if (beta > 0.5) regime = "ACCELERANT";
|
|
65
|
-
else if (beta < -0.5) regime = "STABILIZER";
|
|
66
|
-
|
|
67
|
-
this.gammaResults[ticker] = {
|
|
68
|
-
gamma_beta: Number(beta.toFixed(4)),
|
|
69
|
-
regime: regime,
|
|
70
|
-
_state: {
|
|
71
|
-
flow_buffer: flowBuffer,
|
|
72
|
-
price_buffer: priceBuffer
|
|
73
|
-
}
|
|
74
|
-
};
|
|
28
|
+
this.gammaResults[ticker] = { gamma_beta: (beta !== null && isFinite(beta)) ? Number(beta.toFixed(4)) : null, regime, _state: { flow_buffer: fBuf, price_buffer: pBuf } };
|
|
75
29
|
}
|
|
76
30
|
}
|
|
77
|
-
|
|
78
31
|
getResult() { return this.gammaResults; }
|
|
79
32
|
reset() { this.gammaResults = {}; }
|
|
80
33
|
}
|
|
@@ -1,108 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
|
-
* REFACTORED: Uses context.math.extract.
|
|
4
|
-
*/
|
|
5
1
|
class HerdConsensusScore {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
2
|
+
constructor() { this.assetConviction = new Map(); this.tickerMap = null; }
|
|
3
|
+
static getMetadata() { return { type: 'standard', rootDataDependencies: ['portfolio'], isHistorical: true, userType: 'all', category: 'helix' }; }
|
|
4
|
+
static getDependencies() { return []; }
|
|
10
5
|
|
|
11
6
|
static getSchema() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
"properties": {
|
|
15
|
-
"herd_conviction_score": { "type": "number" },
|
|
16
|
-
"user_count": { "type": "number" }
|
|
17
|
-
},
|
|
18
|
-
"required": ["herd_conviction_score", "user_count"]
|
|
19
|
-
};
|
|
20
|
-
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
7
|
+
const s = { "type": "object", "properties": { "herd_conviction_score": { "type": ["number", "null"] }, "user_count": { "type": "number" }, "total_abs_size_today": { "type": "number" } }, "required": ["herd_conviction_score", "user_count", "total_abs_size_today"] };
|
|
8
|
+
return { "type": "object", "patternProperties": { "^.*$": s } };
|
|
21
9
|
}
|
|
22
10
|
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
type: 'standard',
|
|
26
|
-
rootDataDependencies: ['portfolio'],
|
|
27
|
-
isHistorical: true,
|
|
28
|
-
userType: 'all',
|
|
29
|
-
category: 'helix'
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static getDependencies() { return []; }
|
|
34
|
-
|
|
35
|
-
_initAsset(instrumentId) {
|
|
36
|
-
if (!this.assetConviction.has(instrumentId)) {
|
|
37
|
-
this.assetConviction.set(instrumentId, {
|
|
38
|
-
total_pos_size_yesterday: 0, total_pos_size_today: 0,
|
|
39
|
-
user_count_yesterday: 0, user_count_today: 0
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
11
|
+
_init(id) { if (!this.assetConviction.has(id)) this.assetConviction.set(id, { ySize: 0, tSize: 0, yCount: 0, tCount: 0, hasY: false }); }
|
|
43
12
|
|
|
44
13
|
process(context) {
|
|
45
|
-
const { user, mappings, math } = context;
|
|
46
|
-
const { extract } = math;
|
|
14
|
+
const { user, mappings, math } = context; const { extract } = math;
|
|
47
15
|
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
48
|
-
|
|
49
|
-
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
50
16
|
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const asset = this.assetConviction.get(instId);
|
|
65
|
-
const yWeight = extract.getPositionWeight(yP, user.type);
|
|
66
|
-
const tWeight = extract.getPositionWeight(tP, user.type);
|
|
67
|
-
|
|
68
|
-
if (yWeight > 0) {
|
|
69
|
-
asset.total_pos_size_yesterday += yWeight;
|
|
70
|
-
asset.user_count_yesterday++;
|
|
71
|
-
}
|
|
72
|
-
if (tWeight > 0) {
|
|
73
|
-
asset.total_pos_size_today += tWeight;
|
|
74
|
-
asset.user_count_today++;
|
|
17
|
+
const yPos = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
|
|
18
|
+
const yMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
19
|
+
const tMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
20
|
+
const ids = new Set([...yMap.keys(), ...tMap.keys()]);
|
|
21
|
+
for (const id of ids) {
|
|
22
|
+
const tP = tMap.get(id), yP = yMap.get(id);
|
|
23
|
+
if (extract.getNetProfit(tP) >= 0) continue;
|
|
24
|
+
this._init(id); const a = this.assetConviction.get(id);
|
|
25
|
+
const tW = extract.getPositionWeight(tP, user.type);
|
|
26
|
+
if (tW > 0) { a.tSize += tW; a.tCount++; }
|
|
27
|
+
if (user.portfolio.yesterday) {
|
|
28
|
+
a.hasY = true; const yW = extract.getPositionWeight(yP, user.type);
|
|
29
|
+
if (yW > 0) { a.ySize += yW; a.yCount++; }
|
|
75
30
|
}
|
|
76
31
|
}
|
|
77
32
|
}
|
|
78
33
|
|
|
79
34
|
async getResult() {
|
|
80
|
-
if (!this.tickerMap) return {};
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let avg_pos_change = 0;
|
|
87
|
-
if (data.user_count_yesterday > 0 && data.user_count_today > 0) {
|
|
88
|
-
const avg_y = data.total_pos_size_yesterday / data.user_count_yesterday;
|
|
89
|
-
const avg_t = data.total_pos_size_today / data.user_count_today;
|
|
90
|
-
if (avg_y > 0) avg_pos_change = ((avg_t - avg_y) / avg_y) * 100;
|
|
91
|
-
} else if (data.user_count_today > 0) avg_pos_change = Infinity;
|
|
92
|
-
|
|
93
|
-
if (isFinite(avg_pos_change) && data.user_count_today > 0) {
|
|
94
|
-
result[ticker] = {
|
|
95
|
-
herd_conviction_score: avg_pos_change,
|
|
96
|
-
user_count: data.user_count_today
|
|
97
|
-
};
|
|
98
|
-
}
|
|
35
|
+
if (!this.tickerMap) return {}; const res = {};
|
|
36
|
+
for (const [id, d] of this.assetConviction.entries()) {
|
|
37
|
+
const tick = this.tickerMap[id]; if (!tick) continue;
|
|
38
|
+
let score = null; if (d.hasY && d.yCount > 0 && d.tCount > 0) score = (((d.tSize / d.tCount) - (d.ySize / d.yCount)) / (d.ySize / d.yCount)) * 100;
|
|
39
|
+
res[tick] = { herd_conviction_score: isFinite(score) ? score : null, user_count: d.tCount, total_abs_size_today: d.tSize };
|
|
99
40
|
}
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
reset() {
|
|
104
|
-
this.assetConviction.clear();
|
|
105
|
-
this.tickerMap = null;
|
|
41
|
+
return res;
|
|
106
42
|
}
|
|
43
|
+
reset() { this.assetConviction.clear(); this.tickerMap = null; }
|
|
107
44
|
}
|
|
108
45
|
module.exports = HerdConsensusScore;
|