aiden-shared-calculations-unified 1.0.86 → 1.0.88
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/capitulation/asset-volatility-estimator.js +96 -0
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
- package/calculations/core/asset-cost-basis-profile.js +127 -0
- 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/test..js +0 -0
- 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/ghost-book/cost-basis-density.js +79 -0
- package/calculations/ghost-book/liquidity-vacuum.js +52 -0
- package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
- 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/predicative-alpha/cognitive-dissonance.js +113 -0
- package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
- package/calculations/predicative-alpha/mimetic-latency.js +124 -0
- 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,49 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview HELIX Product Line (Pass 4)
|
|
3
|
-
*
|
|
4
|
-
* This is the final, stateless signal generator for the HELIX line.
|
|
5
|
-
* It answers: "Are 'Winners' (in-profit) and 'Losers'
|
|
6
|
-
* (in-loss) cohorts flowing in opposite directions?"
|
|
3
|
+
* REFACTORED: Uses context.math.signals and process(context).
|
|
7
4
|
*/
|
|
8
5
|
class HelixContrarianSignal {
|
|
6
|
+
constructor() { this.result = {}; }
|
|
9
7
|
|
|
10
|
-
// --- STANDARD 2: ADDED ---
|
|
11
|
-
constructor() {
|
|
12
|
-
this.result = {};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Defines the output schema for this calculation.
|
|
17
|
-
*/
|
|
18
8
|
static getSchema() {
|
|
19
9
|
const tickerSchema = {
|
|
20
10
|
"type": "object",
|
|
21
11
|
"properties": {
|
|
22
|
-
"signal": {
|
|
23
|
-
|
|
24
|
-
"enum": ["Strong Contrarian", "Contrarian", "Neutral", "Consensus"]
|
|
25
|
-
},
|
|
26
|
-
"helix_score": {
|
|
27
|
-
"type": "number",
|
|
28
|
-
"description": "Divergence score (-10 to +10). Positive = Contrarian (Winners sell, Losers buy), Negative = Consensus (Both buy/sell)."
|
|
29
|
-
},
|
|
12
|
+
"signal": { "type": "string" },
|
|
13
|
+
"helix_score": { "type": "number" },
|
|
30
14
|
"winner_net_flow": { "type": "number" },
|
|
31
15
|
"loser_net_flow": { "type": "number" }
|
|
32
16
|
},
|
|
33
|
-
"required": ["signal", "helix_score"
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
"type": "object",
|
|
38
|
-
"description": "Generates a final contrarian signal based on Winner vs. Loser flow.",
|
|
39
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
40
|
-
"additionalProperties": tickerSchema
|
|
17
|
+
"required": ["signal", "helix_score"]
|
|
41
18
|
};
|
|
19
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
42
20
|
}
|
|
43
21
|
|
|
44
|
-
/**
|
|
45
|
-
* Statically defines all metadata for the manifest builder.
|
|
46
|
-
*/
|
|
47
22
|
static getMetadata() {
|
|
48
23
|
return {
|
|
49
24
|
type: 'meta',
|
|
@@ -54,109 +29,40 @@ class HelixContrarianSignal {
|
|
|
54
29
|
};
|
|
55
30
|
}
|
|
56
31
|
|
|
57
|
-
/**
|
|
58
|
-
* Statically declare dependencies.
|
|
59
|
-
*/
|
|
60
32
|
static getDependencies() {
|
|
61
|
-
return [
|
|
62
|
-
'winner-loser-flow', // from helix (Pass 2)
|
|
63
|
-
'herd-consensus-score' // from helix (Pass 2)
|
|
64
|
-
];
|
|
33
|
+
return ['winner-loser-flow', 'herd-consensus-score'];
|
|
65
34
|
}
|
|
66
35
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
_normalize(score) {
|
|
71
|
-
// Scale input score to tune sensitivity
|
|
72
|
-
// A score of 50 will be ~9.9 (strong signal)
|
|
73
|
-
// A score of 20 will be ~9.6
|
|
74
|
-
// A score of 10 will be ~7.6
|
|
75
|
-
return Math.tanh(score / 10.0) * 10;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* This is a 'meta' calculation. It runs once.
|
|
80
|
-
*/
|
|
81
|
-
// --- STANDARD 1: UPDATED SIGNATURE (added rootData) ---
|
|
82
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
83
|
-
const { logger } = dependencies;
|
|
36
|
+
process(context) {
|
|
37
|
+
const { computed, math } = context;
|
|
38
|
+
const { signals } = math;
|
|
84
39
|
|
|
85
|
-
const
|
|
86
|
-
const herdConviction = fetchedDependencies['herd-consensus-score'];
|
|
87
|
-
|
|
88
|
-
if (!cohortFlows || !herdConviction) {
|
|
89
|
-
logger.log('WARN', `[helix/helix-contrarian-signal] Missing dependencies for ${dateStr}.`);
|
|
90
|
-
// --- STANDARD 2: DO NOT RETURN ---
|
|
91
|
-
this.result = {};
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const allTickers = new Set([
|
|
96
|
-
...Object.keys(cohortFlows),
|
|
97
|
-
...Object.keys(herdConviction)
|
|
98
|
-
]);
|
|
99
|
-
|
|
40
|
+
const tickers = signals.getUnionKeys(computed, ['winner-loser-flow', 'herd-consensus-score']);
|
|
100
41
|
const result = {};
|
|
101
42
|
|
|
102
|
-
for (const ticker of
|
|
103
|
-
const
|
|
104
|
-
const
|
|
43
|
+
for (const ticker of tickers) {
|
|
44
|
+
const winner_flow = signals.getMetric(computed, 'winner-loser-flow', ticker, 'net_winner_flow');
|
|
45
|
+
const loser_flow = signals.getMetric(computed, 'herd-consensus-score', ticker, 'herd_conviction_score');
|
|
105
46
|
|
|
106
|
-
// 1. Get Winner Flow (using net_winner_flow)
|
|
107
|
-
const winner_flow = flow?.net_winner_flow || 0;
|
|
108
|
-
|
|
109
|
-
// 2. Get Loser Flow (using herd_conviction_score)
|
|
110
|
-
// This is a proxy for "Loser Flow".
|
|
111
|
-
// If conviction > 0, losers are buying.
|
|
112
|
-
// If conviction < 0, losers are selling.
|
|
113
|
-
const loser_flow = conviction?.herd_conviction_score || 0;
|
|
114
|
-
|
|
115
|
-
// --- Signal Logic ---
|
|
116
|
-
// A contrarian signal (positive score) happens when:
|
|
117
|
-
// 1. Winners SELL (flow < 0) and Losers BUY (flow > 0)
|
|
118
|
-
//
|
|
119
|
-
// A consensus signal (negative score) happens when:
|
|
120
|
-
// 2. Winners BUY (flow > 0) and Losers BUY (flow > 0)
|
|
121
|
-
// 3. Winners SELL (flow < 0) and Losers SELL (flow < 0)
|
|
122
|
-
|
|
123
|
-
// We can simplify this:
|
|
124
|
-
// Score = Loser Flow - Winner Flow
|
|
125
|
-
//
|
|
126
|
-
// Case 1: Loser (Buy, +50) - Winner (Sell, -20) = +70 (Strong Contrarian)
|
|
127
|
-
// Case 2: Loser (Buy, +50) - Winner (Buy, +20) = +30 (Weak Contrarian)
|
|
128
|
-
// Case 3: Loser (Sell, -50) - Winner (Sell, -20) = -30 (Consensus)
|
|
129
|
-
// Case 4: Loser (Sell, -50) - Winner (Buy, +20) = -70 (Strong Consensus)
|
|
130
|
-
|
|
131
47
|
const divergence = loser_flow - winner_flow;
|
|
132
|
-
const helix_score =
|
|
48
|
+
const helix_score = signals.normalizeTanh(divergence, 10, 10.0);
|
|
133
49
|
|
|
134
50
|
let signal = "Neutral";
|
|
135
|
-
if (helix_score > 7.5) signal = "Strong Contrarian";
|
|
51
|
+
if (helix_score > 7.5) signal = "Strong Contrarian";
|
|
136
52
|
else if (helix_score > 2.5) signal = "Contrarian";
|
|
137
|
-
else if (helix_score < -2.5) signal = "Consensus";
|
|
53
|
+
else if (helix_score < -2.5) signal = "Consensus";
|
|
138
54
|
|
|
139
55
|
result[ticker] = {
|
|
140
56
|
signal: signal,
|
|
141
57
|
helix_score: helix_score,
|
|
142
58
|
winner_net_flow: winner_flow,
|
|
143
|
-
loser_net_flow: loser_flow
|
|
59
|
+
loser_net_flow: loser_flow
|
|
144
60
|
};
|
|
145
61
|
}
|
|
146
|
-
|
|
147
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
148
62
|
this.result = result;
|
|
149
63
|
}
|
|
150
64
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return this.result;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// --- STANDARD 2: ADDED ---
|
|
157
|
-
reset() {
|
|
158
|
-
this.result = {};
|
|
159
|
-
}
|
|
65
|
+
async getResult() { return this.result; }
|
|
66
|
+
reset() { this.result = {}; }
|
|
160
67
|
}
|
|
161
|
-
|
|
162
68
|
module.exports = HelixContrarianSignal;
|
|
@@ -1,179 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "For each asset, what is the
|
|
5
|
-
* net 'conviction' (avg. position change) of the
|
|
6
|
-
* 'Herd' (in-loss) cohort?"
|
|
3
|
+
* REFACTORED: Uses context.math.extract.
|
|
7
4
|
*/
|
|
8
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
9
|
-
|
|
10
|
-
|
|
11
5
|
class HerdConsensusScore {
|
|
12
6
|
constructor() {
|
|
13
|
-
// { [instrumentId]: { ... } }
|
|
14
7
|
this.assetConviction = new Map();
|
|
15
|
-
|
|
16
|
-
// --- STANDARD 0: RENAMED ---
|
|
17
|
-
this.tickerMap = null;
|
|
8
|
+
this.tickerMap = null;
|
|
18
9
|
}
|
|
19
10
|
|
|
20
|
-
/**
|
|
21
|
-
* Defines the output schema for this calculation.
|
|
22
|
-
*/
|
|
23
11
|
static getSchema() {
|
|
24
12
|
const tickerSchema = {
|
|
25
13
|
"type": "object",
|
|
26
14
|
"properties": {
|
|
27
|
-
"herd_conviction_score": {
|
|
28
|
-
"type": "number",
|
|
29
|
-
"description": "Avg. % change in position size for 'Loser' cohort. Positive = Buying the Dip, Negative = Capitulating."
|
|
30
|
-
},
|
|
15
|
+
"herd_conviction_score": { "type": "number" },
|
|
31
16
|
"user_count": { "type": "number" }
|
|
32
17
|
},
|
|
33
18
|
"required": ["herd_conviction_score", "user_count"]
|
|
34
19
|
};
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
"type": "object",
|
|
38
|
-
"description": "Tracks the change in avg. position size for the 'Loser' (in-loss) cohort.",
|
|
39
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
40
|
-
"additionalProperties": tickerSchema
|
|
41
|
-
};
|
|
20
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
42
21
|
}
|
|
43
22
|
|
|
44
|
-
/**
|
|
45
|
-
* Statically defines all metadata for the manifest builder.
|
|
46
|
-
*/
|
|
47
23
|
static getMetadata() {
|
|
48
24
|
return {
|
|
49
25
|
type: 'standard',
|
|
50
26
|
rootDataDependencies: ['portfolio'],
|
|
51
|
-
isHistorical: true,
|
|
27
|
+
isHistorical: true,
|
|
52
28
|
userType: 'all',
|
|
53
29
|
category: 'helix'
|
|
54
30
|
};
|
|
55
31
|
}
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
* Statically declare dependencies.
|
|
59
|
-
*/
|
|
60
|
-
static getDependencies() {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
33
|
+
static getDependencies() { return []; }
|
|
63
34
|
|
|
64
|
-
_getPortfolioPositions(portfolio) {
|
|
65
|
-
return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
35
|
_initAsset(instrumentId) {
|
|
69
36
|
if (!this.assetConviction.has(instrumentId)) {
|
|
70
37
|
this.assetConviction.set(instrumentId, {
|
|
71
|
-
total_pos_size_yesterday: 0,
|
|
72
|
-
|
|
73
|
-
user_count_yesterday: 0,
|
|
74
|
-
user_count_today: 0
|
|
38
|
+
total_pos_size_yesterday: 0, total_pos_size_today: 0,
|
|
39
|
+
user_count_yesterday: 0, user_count_today: 0
|
|
75
40
|
});
|
|
76
41
|
}
|
|
77
42
|
}
|
|
78
43
|
|
|
79
|
-
process(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// --- STANDARD 0: FIXED ---
|
|
85
|
-
if (!this.tickerMap) {
|
|
86
|
-
this.tickerMap = context.instrumentToTicker;
|
|
87
|
-
}
|
|
44
|
+
process(context) {
|
|
45
|
+
const { user, mappings, math } = context;
|
|
46
|
+
const { extract } = math;
|
|
47
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
88
48
|
|
|
89
|
-
const
|
|
90
|
-
|
|
49
|
+
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
50
|
+
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
91
51
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// 'Loser' cohort for any given asset.
|
|
95
|
-
const tPosMap = new Map(tPos.map(p => [p.InstrumentID, p]));
|
|
96
|
-
const yPosMap = new Map((this._getPortfolioPositions(yesterdayPortfolio) || []).map(p => [p.InstrumentID, p]));
|
|
52
|
+
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
53
|
+
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
97
54
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for (const instrumentId of allInstrumentIds) {
|
|
104
|
-
const tP = tPosMap.get(instrumentId);
|
|
105
|
-
const yP = yPosMap.get(instrumentId);
|
|
55
|
+
const allIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
56
|
+
|
|
57
|
+
for (const instId of allIds) {
|
|
58
|
+
const tP = tPosMap.get(instId);
|
|
59
|
+
const yP = yPosMap.get(instId);
|
|
106
60
|
|
|
107
|
-
|
|
108
|
-
const tPnl = tP?.NetProfit || 0;
|
|
109
|
-
const isLoser = tPnl < 0;
|
|
61
|
+
if (extract.getNetProfit(tP) >= 0) continue; // Only Losers (Herd)
|
|
110
62
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// --- This user is in the 'Herd' for this asset ---
|
|
118
|
-
this._initAsset(instrumentId);
|
|
119
|
-
const asset = this.assetConviction.get(instrumentId);
|
|
120
|
-
|
|
121
|
-
// ---
|
|
122
|
-
// FIX: The field 'PositionSizeInUnits' does not exist in schema.md.
|
|
123
|
-
// For Normal users, the field is 'Invested'.
|
|
124
|
-
// For Speculator users, the field is 'Amount'.
|
|
125
|
-
// This line now correctly checks for either.
|
|
126
|
-
// ---
|
|
127
|
-
const ySize = yP?.Invested || yP?.Amount || 0;
|
|
128
|
-
const tSize = tP?.Invested || tP?.Amount || 0;
|
|
63
|
+
this._initAsset(instId);
|
|
64
|
+
const asset = this.assetConviction.get(instId);
|
|
65
|
+
const yWeight = extract.getPositionWeight(yP, user.type);
|
|
66
|
+
const tWeight = extract.getPositionWeight(tP, user.type);
|
|
129
67
|
|
|
130
|
-
if (
|
|
131
|
-
asset.total_pos_size_yesterday +=
|
|
68
|
+
if (yWeight > 0) {
|
|
69
|
+
asset.total_pos_size_yesterday += yWeight;
|
|
132
70
|
asset.user_count_yesterday++;
|
|
133
71
|
}
|
|
134
|
-
if (
|
|
135
|
-
asset.total_pos_size_today +=
|
|
72
|
+
if (tWeight > 0) {
|
|
73
|
+
asset.total_pos_size_today += tWeight;
|
|
136
74
|
asset.user_count_today++;
|
|
137
75
|
}
|
|
138
76
|
}
|
|
139
77
|
}
|
|
140
78
|
|
|
141
79
|
async getResult() {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Failsafe check
|
|
145
|
-
if (!this.tickerMap) {
|
|
146
|
-
return {}; // process() must run first
|
|
147
|
-
}
|
|
148
|
-
|
|
80
|
+
if (!this.tickerMap) return {};
|
|
149
81
|
const result = {};
|
|
150
|
-
for (const [
|
|
151
|
-
|
|
152
|
-
const ticker = this.tickerMap[instrumentId];
|
|
82
|
+
for (const [instId, data] of this.assetConviction.entries()) {
|
|
83
|
+
const ticker = this.tickerMap[instId];
|
|
153
84
|
if (!ticker) continue;
|
|
154
85
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
user_count_yesterday
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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;
|
|
161
92
|
|
|
162
|
-
if (
|
|
163
|
-
const avg_pos_y = total_pos_size_yesterday / user_count_yesterday;
|
|
164
|
-
const avg_pos_t = total_pos_size_today / user_count_today;
|
|
165
|
-
if (avg_pos_y > 0) {
|
|
166
|
-
avg_position_change_pct = ((avg_pos_t - avg_pos_y) / avg_pos_y) * 100;
|
|
167
|
-
}
|
|
168
|
-
} else if (user_count_today > 0) {
|
|
169
|
-
// Pure inflow, conviction is max
|
|
170
|
-
avg_position_change_pct = Infinity;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (isFinite(avg_position_change_pct) && user_count_today > 0) {
|
|
93
|
+
if (isFinite(avg_pos_change) && data.user_count_today > 0) {
|
|
174
94
|
result[ticker] = {
|
|
175
|
-
herd_conviction_score:
|
|
176
|
-
user_count: user_count_today
|
|
95
|
+
herd_conviction_score: avg_pos_change,
|
|
96
|
+
user_count: data.user_count_today
|
|
177
97
|
};
|
|
178
98
|
}
|
|
179
99
|
}
|
|
@@ -182,9 +102,7 @@ class HerdConsensusScore {
|
|
|
182
102
|
|
|
183
103
|
reset() {
|
|
184
104
|
this.assetConviction.clear();
|
|
185
|
-
// --- STANDARD 0: RENAMED ---
|
|
186
105
|
this.tickerMap = null;
|
|
187
106
|
}
|
|
188
107
|
}
|
|
189
|
-
|
|
190
108
|
module.exports = HerdConsensusScore;
|
|
@@ -1,158 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "For each asset, what is the net flow
|
|
5
|
-
* of *unique users* into the 'Winner' (in-profit) and 'Loser'
|
|
6
|
-
* (in-loss) cohorts?"
|
|
7
|
-
*
|
|
8
|
-
* --- MODIFIED ---
|
|
9
|
-
* Removed dependency on 'asset-pnl-status' to fix 1MiB limit.
|
|
10
|
-
* This calculation now determines "winner/loser" status internally
|
|
11
|
-
* by reading the user's portfolio P&L directly.
|
|
3
|
+
* REFACTORED: Uses context.math.extract.
|
|
12
4
|
*/
|
|
13
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
14
|
-
|
|
15
|
-
|
|
16
5
|
class WinnerLoserFlow {
|
|
17
6
|
constructor() {
|
|
18
|
-
// We will store { [ticker]: { winners_joined: 0, ... } }
|
|
19
7
|
this.assetFlows = new Map();
|
|
20
|
-
|
|
21
|
-
this.tickerMap = null; // Caches mappings
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Defines the output schema for this calculation.
|
|
26
|
-
* @returns {object} JSON Schema object
|
|
27
|
-
*/
|
|
28
|
-
static getSchema() {
|
|
29
|
-
const tickerSchema = {
|
|
30
|
-
"type": "object",
|
|
31
|
-
"properties": {
|
|
32
|
-
"net_winner_flow": {
|
|
33
|
-
"type": "number",
|
|
34
|
-
"description": "Net users joining the 'Winner' cohort (Joined - Left)."
|
|
35
|
-
},
|
|
36
|
-
"net_loser_flow": {
|
|
37
|
-
"type": "number",
|
|
38
|
-
"description": "Net users joining the 'Loser' cohort (Joined - Left)."
|
|
39
|
-
},
|
|
40
|
-
"raw_counts": {
|
|
41
|
-
"type": "object",
|
|
42
|
-
"properties": {
|
|
43
|
-
"winners_joined": { "type": "number" },
|
|
44
|
-
"winners_left": { "type": "number" },
|
|
45
|
-
"losers_joined": { "type": "number" },
|
|
46
|
-
"losers_left": { "type": "number" }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
"type": "object",
|
|
55
|
-
"description": "Tracks the net flow of unique users into/out of the Winner (in-profit) and Loser (in-loss) cohorts per asset.",
|
|
56
|
-
"patternProperties": {
|
|
57
|
-
"^.*$": tickerSchema // Ticker
|
|
58
|
-
},
|
|
59
|
-
"additionalProperties": tickerSchema
|
|
60
|
-
};
|
|
8
|
+
this.tickerMap = null;
|
|
61
9
|
}
|
|
62
10
|
|
|
63
|
-
/**
|
|
64
|
-
* Statically defines all metadata for the manifest builder.
|
|
65
|
-
*/
|
|
66
11
|
static getMetadata() {
|
|
67
12
|
return {
|
|
68
13
|
type: 'standard',
|
|
69
14
|
rootDataDependencies: ['portfolio'],
|
|
70
|
-
isHistorical: true,
|
|
15
|
+
isHistorical: true,
|
|
71
16
|
userType: 'all',
|
|
72
17
|
category: 'helix'
|
|
73
18
|
};
|
|
74
19
|
}
|
|
75
20
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
21
|
+
static getDependencies() { return []; }
|
|
22
|
+
|
|
23
|
+
static getSchema() {
|
|
24
|
+
const tickerSchema = {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"net_winner_flow": { "type": "number" },
|
|
28
|
+
"net_loser_flow": { "type": "number" },
|
|
29
|
+
"raw_counts": { "type": "object" }
|
|
30
|
+
},
|
|
31
|
+
"required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
|
|
32
|
+
};
|
|
33
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
81
34
|
}
|
|
82
35
|
|
|
83
|
-
|
|
84
|
-
* Helper to get a user's holdings (Instrument IDs) AND their P&L.
|
|
85
|
-
* @returns {Map<InstrumentID, {pnl: number}>}
|
|
86
|
-
*/
|
|
87
|
-
_getHoldingsWithPnl(portfolio) {
|
|
88
|
-
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
89
|
-
if (!positions || !Array.isArray(positions)) {
|
|
90
|
-
return new Map();
|
|
91
|
-
}
|
|
36
|
+
_getHoldings(positions, extract) {
|
|
92
37
|
const map = new Map();
|
|
93
38
|
for (const pos of positions) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
39
|
+
const id = extract.getInstrumentId(pos);
|
|
40
|
+
if (id) map.set(id, { pnl: extract.getNetProfit(pos) });
|
|
97
41
|
}
|
|
98
42
|
return map;
|
|
99
43
|
}
|
|
100
44
|
|
|
101
|
-
/**
|
|
102
|
-
* Initialize asset flow counters
|
|
103
|
-
*/
|
|
104
45
|
_initAsset(ticker) {
|
|
105
46
|
if (!this.assetFlows.has(ticker)) {
|
|
106
|
-
this.assetFlows.set(ticker, {
|
|
107
|
-
winners_joined: 0,
|
|
108
|
-
winners_left: 0,
|
|
109
|
-
losers_joined: 0,
|
|
110
|
-
losers_left: 0
|
|
111
|
-
});
|
|
47
|
+
this.assetFlows.set(ticker, { winners_joined: 0, winners_left: 0, losers_joined: 0, losers_left: 0 });
|
|
112
48
|
}
|
|
113
49
|
}
|
|
114
50
|
|
|
115
|
-
process(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
51
|
+
process(context) {
|
|
52
|
+
const { user, mappings, math } = context;
|
|
53
|
+
const { extract } = math;
|
|
54
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
119
55
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.tickerMap = context.instrumentToTicker;
|
|
123
|
-
}
|
|
56
|
+
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
57
|
+
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
124
58
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
59
|
+
const yHoldings = this._getHoldings(yPos, extract);
|
|
60
|
+
const tHoldings = this._getHoldings(tPos, extract);
|
|
128
61
|
|
|
129
|
-
const
|
|
130
|
-
const tHoldings = this._getHoldingsWithPnl(todayPortfolio); // Map<InstID, {pnl}>
|
|
62
|
+
const allIds = new Set([...yHoldings.keys(), ...tHoldings.keys()]);
|
|
131
63
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const instrumentId of allInstrumentIds) {
|
|
138
|
-
// --- STANDARD 0: FIXED ---
|
|
139
|
-
const ticker = this.tickerMap[instrumentId];
|
|
64
|
+
for (const instId of allIds) {
|
|
65
|
+
const ticker = this.tickerMap[instId];
|
|
140
66
|
if (!ticker) continue;
|
|
141
67
|
|
|
142
|
-
const tPnl = tHoldings.get(
|
|
68
|
+
const tPnl = tHoldings.get(instId)?.pnl || 0;
|
|
143
69
|
const isWinner = tPnl > 0;
|
|
144
70
|
const isLoser = tPnl < 0;
|
|
145
|
-
|
|
146
|
-
if (!isWinner && !isLoser) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const heldYesterday = yHoldings.has(instrumentId);
|
|
151
|
-
const holdsToday = tHoldings.has(instrumentId);
|
|
71
|
+
if (!isWinner && !isLoser) continue;
|
|
152
72
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
73
|
+
const heldYesterday = yHoldings.has(instId);
|
|
74
|
+
const holdsToday = tHoldings.has(instId);
|
|
75
|
+
if (heldYesterday === holdsToday) continue;
|
|
156
76
|
|
|
157
77
|
this._initAsset(ticker);
|
|
158
78
|
const stats = this.assetFlows.get(ticker);
|
|
@@ -182,9 +102,7 @@ class WinnerLoserFlow {
|
|
|
182
102
|
|
|
183
103
|
reset() {
|
|
184
104
|
this.assetFlows.clear();
|
|
185
|
-
// --- STANDARD 0: RENAMED ---
|
|
186
105
|
this.tickerMap = null;
|
|
187
106
|
}
|
|
188
107
|
}
|
|
189
|
-
|
|
190
108
|
module.exports = WinnerLoserFlow;
|