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,20 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* - Added defensive checks in '_loadDependencies' to prevent crash
|
|
5
|
-
* when dependencies fail to load (e.g., due to worker bug).
|
|
6
|
-
* - **FIXED:** Restored logic to calculate 'avg_position_change_pct'
|
|
7
|
-
* to resolve the hardcoded '0' value. This involves
|
|
8
|
-
* re-adding fields to '_initFlowData' and 'process'.
|
|
3
|
+
* REFACTORED: Uses context.computed and context.math.extract.
|
|
9
4
|
*/
|
|
10
|
-
|
|
11
5
|
class UnskilledCohortFlow {
|
|
12
6
|
constructor() {
|
|
13
7
|
this.assetFlows = new Map();
|
|
14
8
|
this.cohortMap = new Map();
|
|
15
|
-
this.tickerMap = null;
|
|
16
9
|
this.dependenciesLoaded = false;
|
|
17
|
-
this.
|
|
10
|
+
this.tickerMap = null;
|
|
18
11
|
}
|
|
19
12
|
|
|
20
13
|
static getMetadata() {
|
|
@@ -28,10 +21,7 @@ class UnskilledCohortFlow {
|
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
static getDependencies() {
|
|
31
|
-
return [
|
|
32
|
-
'cohort-skill-definition', // from gem (Pass 1)
|
|
33
|
-
'instrument-price-change-1d' // from core (Pass 1)
|
|
34
|
-
];
|
|
24
|
+
return ['cohort-skill-definition', 'instrument-price-change-1d'];
|
|
35
25
|
}
|
|
36
26
|
|
|
37
27
|
static getSchema() {
|
|
@@ -45,206 +35,108 @@ class UnskilledCohortFlow {
|
|
|
45
35
|
},
|
|
46
36
|
"required": ["net_flow_pct", "net_flow_contribution", "avg_position_change_pct", "user_count"]
|
|
47
37
|
};
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"description": "Calculates capital flow and conviction change for the 'Unskilled' cohort.",
|
|
52
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
53
|
-
"additionalProperties": tickerSchema
|
|
54
|
-
};
|
|
38
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
55
39
|
}
|
|
56
40
|
|
|
57
|
-
_getPortfolioPositions(portfolio) {
|
|
58
|
-
// --- FIX: Support both Normal (Aggregated) and Speculator (Public) ---
|
|
59
|
-
return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// --- THIS IS THE FIX (Part 1) ---
|
|
63
41
|
_initFlowData(instrumentId) {
|
|
64
42
|
if (!this.assetFlows.has(instrumentId)) {
|
|
65
43
|
this.assetFlows.set(instrumentId, {
|
|
66
|
-
total_invested_yesterday: 0,
|
|
67
|
-
|
|
68
|
-
price_change_yesterday: 0,
|
|
69
|
-
// Restore fields needed for avg_position_change_pct
|
|
70
|
-
total_pos_size_yesterday: 0,
|
|
71
|
-
total_pos_size_today: 0,
|
|
72
|
-
user_count_yesterday: 0,
|
|
73
|
-
user_count_today: 0
|
|
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
|
|
74
46
|
});
|
|
75
47
|
}
|
|
76
48
|
}
|
|
77
|
-
// --- END FIX (Part 1) ---
|
|
78
49
|
|
|
79
|
-
_loadDependencies(
|
|
50
|
+
_loadDependencies(computed) {
|
|
80
51
|
if (this.dependenciesLoaded) return;
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
throw new Error(
|
|
84
|
-
`[unskilled-cohort-flow] CRITICAL ERROR: fetchedDependencies object was UNDEFINED.
|
|
85
|
-
This means a dependency test (like 'cohort-skill-definition' or 'instrument-price-change-1d')
|
|
86
|
-
failed to run or produced an invalid result.`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const cohortData = fetchedDependencies['cohort-skill-definition'];
|
|
91
|
-
if (!cohortData) {
|
|
92
|
-
throw new Error(
|
|
93
|
-
`[unskilled-cohort-flow] DEPENDENCY ERROR: 'cohort-skill-definition' was missing.
|
|
94
|
-
Check logs for errors in that calculation.`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// This logic is correct for 'cohort-skill-definition' output
|
|
99
|
-
if (cohortData && cohortData.unskilled_user_ids) {
|
|
52
|
+
const cohortData = computed['cohort-skill-definition'];
|
|
53
|
+
if (cohortData) {
|
|
100
54
|
(cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
|
|
101
55
|
}
|
|
102
|
-
|
|
103
|
-
this.priceChangeMap = fetchedDependencies['instrument-price-change-1d'];
|
|
104
|
-
if (!this.priceChangeMap) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`[unskilled-cohort-flow] DEPENDENCY ERROR: 'instrument-price-change-1d' was missing.
|
|
107
|
-
This is likely due to the worker bug for 'price' dependencies.`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
56
|
this.dependenciesLoaded = true;
|
|
112
57
|
}
|
|
113
58
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const cohortName = this.cohortMap.get(String(userId));
|
|
123
|
-
if (cohortName !== 'unskilled') {
|
|
124
|
-
return; // Not in unskilled cohort
|
|
125
|
-
}
|
|
59
|
+
process(context) {
|
|
60
|
+
const { user, computed, mappings, math } = context;
|
|
61
|
+
const { extract } = math;
|
|
62
|
+
|
|
63
|
+
this._loadDependencies(computed);
|
|
64
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
126
65
|
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
66
|
+
if (this.cohortMap.get(user.id) !== 'unskilled') return;
|
|
67
|
+
|
|
68
|
+
const priceChangeMap = computed['instrument-price-change-1d'];
|
|
69
|
+
if (!priceChangeMap) return;
|
|
130
70
|
|
|
131
|
-
const yPos =
|
|
132
|
-
const tPos =
|
|
133
|
-
if (!yPos || !tPos) return;
|
|
71
|
+
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
72
|
+
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
134
73
|
|
|
135
|
-
const yPosMap = new Map(yPos.map(p => [p
|
|
136
|
-
const tPosMap = new Map(tPos.map(p => [p
|
|
74
|
+
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
75
|
+
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
137
76
|
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
138
77
|
|
|
139
|
-
for (const
|
|
140
|
-
if (!
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
const asset = this.assetFlows.get(instrumentId);
|
|
144
|
-
|
|
145
|
-
const yP = yPosMap.get(instrumentId);
|
|
146
|
-
const tP = tPosMap.get(instrumentId);
|
|
147
|
-
|
|
148
|
-
// Get $ flow
|
|
149
|
-
const yInvested = yP?.Invested || yP?.Amount || 0;
|
|
150
|
-
const tInvested = tP?.Invested || tP?.Amount || 0;
|
|
151
|
-
|
|
152
|
-
// Get position size (same as $ flow for these schemas)
|
|
153
|
-
const ySize = yP?.Invested || yP?.Amount || 0;
|
|
154
|
-
const tSize = tP?.Invested || tP?.Amount || 0;
|
|
78
|
+
for (const instId of allInstrumentIds) {
|
|
79
|
+
if (!instId) continue;
|
|
80
|
+
this._initFlowData(instId);
|
|
81
|
+
const asset = this.assetFlows.get(instId);
|
|
155
82
|
|
|
156
|
-
|
|
157
|
-
|
|
83
|
+
const yWeight = extract.getPositionWeight(yPosMap.get(instId), user.type);
|
|
84
|
+
const tWeight = extract.getPositionWeight(tPosMap.get(instId), user.type);
|
|
158
85
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
asset.price_change_yesterday += yPriceChange_decimal * yInvested;
|
|
166
|
-
|
|
167
|
-
// Track yesterday's size and user count
|
|
168
|
-
asset.total_pos_size_yesterday += ySize;
|
|
86
|
+
if (yWeight > 0) {
|
|
87
|
+
asset.total_invested_yesterday += yWeight;
|
|
88
|
+
const ticker = mappings.instrumentToTicker[instId];
|
|
89
|
+
const priceChange = (ticker && priceChangeMap[ticker]) ? priceChangeMap[ticker].change_1d_pct : 0;
|
|
90
|
+
asset.price_change_yesterday += (priceChange / 100.0) * yWeight;
|
|
91
|
+
asset.total_pos_size_yesterday += yWeight;
|
|
169
92
|
asset.user_count_yesterday++;
|
|
170
93
|
}
|
|
171
|
-
if (
|
|
172
|
-
asset.total_invested_today +=
|
|
94
|
+
if (tWeight > 0) {
|
|
95
|
+
asset.total_invested_today += tWeight;
|
|
173
96
|
asset.user_count_today++;
|
|
174
|
-
|
|
175
|
-
// Track today's size
|
|
176
|
-
asset.total_pos_size_today += tSize;
|
|
97
|
+
asset.total_pos_size_today += tWeight;
|
|
177
98
|
}
|
|
178
99
|
}
|
|
179
100
|
}
|
|
180
|
-
// --- END FIX (Part 2) ---
|
|
181
101
|
|
|
182
|
-
// --- THIS IS THE FIX (Part 3) ---
|
|
183
102
|
async getResult() {
|
|
184
|
-
if (!this.tickerMap) {
|
|
185
|
-
return {};
|
|
186
|
-
}
|
|
187
|
-
|
|
103
|
+
if (!this.tickerMap) return {};
|
|
188
104
|
const finalResult = {};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const ticker = this.tickerMap[instrumentId];
|
|
105
|
+
for (const [instId, data] of this.assetFlows.entries()) {
|
|
106
|
+
const ticker = this.tickerMap[instId];
|
|
192
107
|
if (!ticker) continue;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let flow_contribution = 0;
|
|
203
|
-
|
|
204
|
-
if (total_invested_yesterday > 0) {
|
|
205
|
-
const avg_price_change_decimal = (price_change_yesterday === 0) ? 0 : (price_change_yesterday / total_invested_yesterday);
|
|
206
|
-
const price_adjusted_yesterday_value = total_invested_yesterday * (1 + avg_price_change_decimal);
|
|
207
|
-
|
|
208
|
-
flow_contribution = total_invested_today - price_adjusted_yesterday_value;
|
|
209
|
-
net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
210
|
-
} else if (total_invested_today > 0) {
|
|
211
|
-
flow_contribution = total_invested_today;
|
|
212
|
-
net_flow_percentage = Infinity;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Restore the calculation logic for conviction
|
|
216
|
-
let avg_position_change_pct = 0;
|
|
217
|
-
if (user_count_yesterday > 0 && user_count_today > 0) {
|
|
218
|
-
const avg_pos_y = total_pos_size_yesterday / user_count_yesterday;
|
|
219
|
-
const avg_pos_t = total_pos_size_today / user_count_today;
|
|
220
|
-
if (avg_pos_y > 0) {
|
|
221
|
-
avg_position_change_pct = ((avg_pos_t - avg_pos_y) / avg_pos_y) * 100;
|
|
222
|
-
}
|
|
223
|
-
} else if (user_count_today > 0) {
|
|
224
|
-
// Pure inflow, conviction is max
|
|
225
|
-
avg_position_change_pct = Infinity;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (isFinite(net_flow_percentage) && isFinite(flow_contribution)) {
|
|
229
|
-
finalResult[ticker] = {
|
|
230
|
-
net_flow_pct: net_flow_percentage,
|
|
231
|
-
net_flow_contribution: flow_contribution,
|
|
232
|
-
avg_position_change_pct: avg_position_change_pct, // Now correctly calculated
|
|
233
|
-
user_count: user_count_today
|
|
234
|
-
};
|
|
108
|
+
let net_flow = 0, flow_contrib = 0;
|
|
109
|
+
if (data.total_invested_yesterday > 0) {
|
|
110
|
+
const avg_change = data.price_change_yesterday / data.total_invested_yesterday;
|
|
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;
|
|
235
117
|
}
|
|
118
|
+
let avg_pos_change = 0;
|
|
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
|
+
};
|
|
236
131
|
}
|
|
237
|
-
|
|
238
132
|
return finalResult;
|
|
239
133
|
}
|
|
240
|
-
// --- END FIX (Part 3) ---
|
|
241
134
|
|
|
242
135
|
reset() {
|
|
243
136
|
this.assetFlows.clear();
|
|
244
137
|
this.cohortMap.clear();
|
|
245
|
-
this.tickerMap = null;
|
|
246
138
|
this.dependenciesLoaded = false;
|
|
247
|
-
this.
|
|
139
|
+
this.tickerMap = null;
|
|
248
140
|
}
|
|
249
141
|
}
|
|
250
142
|
module.exports = UnskilledCohortFlow;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ghost Book: Cost-Basis Density Estimation
|
|
3
|
+
* Identifies "Invisible Walls" of Support/Resistance based on holder break-even psychology.
|
|
4
|
+
*/
|
|
5
|
+
class CostBasisDensity {
|
|
6
|
+
constructor() { this.walls = {}; }
|
|
7
|
+
|
|
8
|
+
static getMetadata() {
|
|
9
|
+
return {
|
|
10
|
+
type: 'meta',
|
|
11
|
+
dependencies: ['asset-cost-basis-profile'],
|
|
12
|
+
category: 'ghost_book'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static getDependencies() { return ['asset-cost-basis-profile']; }
|
|
17
|
+
|
|
18
|
+
static getSchema() {
|
|
19
|
+
return {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"patternProperties": {
|
|
22
|
+
"^.*$": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"resistance_zones": { "type": "array", "items": { "type": "number" } },
|
|
26
|
+
"support_zones": { "type": "array", "items": { "type": "number" } },
|
|
27
|
+
"nearest_wall_strength": { "type": "number" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
process(context) {
|
|
35
|
+
const { computed, math } = context;
|
|
36
|
+
const { signals: SignalPrimitives } = math;
|
|
37
|
+
|
|
38
|
+
const tickers = SignalPrimitives.getUnionKeys(computed, ['asset-cost-basis-profile']);
|
|
39
|
+
|
|
40
|
+
for (const ticker of tickers) {
|
|
41
|
+
const data = computed['asset-cost-basis-profile'][ticker];
|
|
42
|
+
if (!data || !data.profile) continue;
|
|
43
|
+
|
|
44
|
+
const profile = data.profile; // Array of {price, density}
|
|
45
|
+
const currentPrice = data.current_price;
|
|
46
|
+
|
|
47
|
+
const resistance = [];
|
|
48
|
+
const support = [];
|
|
49
|
+
let maxDensity = 0;
|
|
50
|
+
|
|
51
|
+
// Find Local Maxima in the Density Curve
|
|
52
|
+
for (let i = 1; i < profile.length - 1; i++) {
|
|
53
|
+
const prev = profile[i-1].density;
|
|
54
|
+
const curr = profile[i].density;
|
|
55
|
+
const next = profile[i+1].density;
|
|
56
|
+
|
|
57
|
+
if (curr > prev && curr > next) {
|
|
58
|
+
// We have a peak (Wall)
|
|
59
|
+
if (profile[i].price > currentPrice) {
|
|
60
|
+
resistance.push(Number(profile[i].price.toFixed(2)));
|
|
61
|
+
} else {
|
|
62
|
+
support.push(Number(profile[i].price.toFixed(2)));
|
|
63
|
+
}
|
|
64
|
+
if (curr > maxDensity) maxDensity = curr;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.walls[ticker] = {
|
|
69
|
+
resistance_zones: resistance.slice(0, 3), // Top 3
|
|
70
|
+
support_zones: support.slice(0, 3),
|
|
71
|
+
nearest_wall_strength: Number(maxDensity.toFixed(4))
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getResult() { return this.walls; }
|
|
77
|
+
reset() { this.walls = {}; }
|
|
78
|
+
}
|
|
79
|
+
module.exports = CostBasisDensity;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class LiquidityVacuum {
|
|
2
|
+
constructor() { this.vacuumResults = {}; }
|
|
3
|
+
|
|
4
|
+
static getMetadata() {
|
|
5
|
+
return {
|
|
6
|
+
type: 'meta',
|
|
7
|
+
dependencies: ['asset-cost-basis-profile'],
|
|
8
|
+
category: 'ghost_book'
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static getDependencies() { return ['asset-cost-basis-profile']; }
|
|
13
|
+
|
|
14
|
+
static getSchema() {
|
|
15
|
+
return { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "crash_probability": {"type":"number"}, "liquidity_ratio": {"type":"number"} } } } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
process(context) {
|
|
19
|
+
const { computed, math } = context;
|
|
20
|
+
const { distribution } = math; // Correctly accesses 'distribution' from context
|
|
21
|
+
|
|
22
|
+
const tickers = Object.keys(computed['asset-cost-basis-profile'] || {});
|
|
23
|
+
|
|
24
|
+
for (const ticker of tickers) {
|
|
25
|
+
const data = computed['asset-cost-basis-profile'][ticker];
|
|
26
|
+
if (!data) continue;
|
|
27
|
+
|
|
28
|
+
const current = data.current_price;
|
|
29
|
+
const totalInv = data.total_inventory_weight;
|
|
30
|
+
|
|
31
|
+
// Integrate Danger Zone (0% to -5% drop)
|
|
32
|
+
const riskVol = distribution.integrateProfile(data.profile, current * 0.95, current);
|
|
33
|
+
|
|
34
|
+
// Ratio: At-Risk Inventory / Total Inventory
|
|
35
|
+
const ratio = (totalInv > 0) ? (riskVol / totalInv) * 10 : 0;
|
|
36
|
+
|
|
37
|
+
let status = "STABLE";
|
|
38
|
+
if (ratio > 0.5) status = "FRAGILE";
|
|
39
|
+
if (ratio > 1.0) status = "CRITICAL_VACUUM";
|
|
40
|
+
|
|
41
|
+
this.vacuumResults[ticker] = {
|
|
42
|
+
crash_probability: Number(Math.min(ratio * 0.5, 0.99).toFixed(2)),
|
|
43
|
+
liquidity_ratio: Number(ratio.toFixed(4)),
|
|
44
|
+
status: status
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getResult() { return this.vacuumResults; }
|
|
50
|
+
reset() { this.vacuumResults = {}; }
|
|
51
|
+
}
|
|
52
|
+
module.exports = LiquidityVacuum;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Ghost Book: Retail Gamma Exposure
|
|
3
|
+
* Measures the "Elasticity of Intent" (Beta) between Price Change and Net Flow.
|
|
4
|
+
*/
|
|
5
|
+
class RetailGammaExposure {
|
|
6
|
+
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
|
+
category: 'ghost_book'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static getDependencies() { return ['skilled-cohort-flow', 'instrument-price-change-1d']; }
|
|
18
|
+
|
|
19
|
+
static getSchema() {
|
|
20
|
+
return {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"patternProperties": {
|
|
23
|
+
"^.*$": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"gamma_beta": { "type": "number" },
|
|
27
|
+
"regime": { "type": "string", "enum": ["ACCELERANT", "STABILIZER", "NEUTRAL"] },
|
|
28
|
+
"_state": { "type": "object" } // Rolling buffer
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process(context) {
|
|
36
|
+
const { computed, previousComputed, math } = context;
|
|
37
|
+
// FIX: Destructure correct keys from context.math
|
|
38
|
+
// 'signals' maps to SignalPrimitives, 'distribution' maps to DistributionAnalytics
|
|
39
|
+
const { signals, distribution } = math;
|
|
40
|
+
|
|
41
|
+
const tickers = signals.getUnionKeys(computed, ['skilled-cohort-flow']);
|
|
42
|
+
|
|
43
|
+
for (const ticker of tickers) {
|
|
44
|
+
// 1. Inputs
|
|
45
|
+
const flow = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
|
|
46
|
+
const priceChange = signals.getMetric(computed, 'instrument-price-change-1d', ticker, 'change_1d_pct', 0);
|
|
47
|
+
|
|
48
|
+
// 2. State Management (Rolling Window)
|
|
49
|
+
const prevResult = signals.getPreviousState(previousComputed, 'retail-gamma-exposure', ticker);
|
|
50
|
+
const bufferSize = 14; // 2-week regression window
|
|
51
|
+
|
|
52
|
+
let flowBuffer = prevResult?._state?.flow_buffer || [];
|
|
53
|
+
let priceBuffer = prevResult?._state?.price_buffer || [];
|
|
54
|
+
|
|
55
|
+
flowBuffer.push(flow);
|
|
56
|
+
priceBuffer.push(priceChange);
|
|
57
|
+
|
|
58
|
+
if (flowBuffer.length > bufferSize) {
|
|
59
|
+
flowBuffer.shift();
|
|
60
|
+
priceBuffer.shift();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. Regression: Flow ~ Alpha + Beta * PriceChange
|
|
64
|
+
const regression = distribution.linearRegression(priceBuffer, flowBuffer);
|
|
65
|
+
const beta = regression.slope;
|
|
66
|
+
|
|
67
|
+
// 4. Regime Logic
|
|
68
|
+
let regime = "NEUTRAL";
|
|
69
|
+
if (beta > 0.5) regime = "ACCELERANT"; // Volatility Explosion Watch
|
|
70
|
+
else if (beta < -0.5) regime = "STABILIZER"; // Chop Zone
|
|
71
|
+
|
|
72
|
+
this.gammaResults[ticker] = {
|
|
73
|
+
gamma_beta: Number(beta.toFixed(4)),
|
|
74
|
+
regime: regime,
|
|
75
|
+
_state: {
|
|
76
|
+
flow_buffer: flowBuffer,
|
|
77
|
+
price_buffer: priceBuffer
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getResult() { return this.gammaResults; }
|
|
84
|
+
reset() { this.gammaResults = {}; }
|
|
85
|
+
}
|
|
86
|
+
module.exports = RetailGammaExposure;
|