aiden-shared-calculations-unified 1.0.87 → 1.0.89

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.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview CORE Product Line (Pass 1 - Meta)
3
+ * Calculates annualized volatility using the new priceExtractor.
4
+ */
5
+ class AssetVolatilityEstimator {
6
+ constructor() {
7
+ this.result = {};
8
+ }
9
+
10
+ static getMetadata() {
11
+ return {
12
+ type: 'meta',
13
+ rootDataDependencies: ['price'],
14
+ isHistorical: false,
15
+ userType: 'n/a',
16
+ category: 'market_stats'
17
+ };
18
+ }
19
+
20
+ static getDependencies() { return []; }
21
+
22
+ static getSchema() {
23
+ const tickerSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "volatility_30d": { "type": "number" },
27
+ "last_price": { "type": "number" },
28
+ "data_points": { "type": "number" }
29
+ },
30
+ "required": ["volatility_30d"]
31
+ };
32
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
33
+ }
34
+
35
+ process(context) {
36
+ // FIXED: Destructure 'mappings' to resolve real tickers
37
+ const { math, prices, mappings } = context;
38
+
39
+ // FIXED: Destructure 'priceExtractor' directly (matches worker.js injection)
40
+ const { compute, priceExtractor } = math;
41
+
42
+ // 1. Get ALL histories properly sorted and parsed
43
+ const allHistories = priceExtractor.getAllHistories(prices);
44
+
45
+ for (const [key, candles] of allHistories.entries()) {
46
+ // RESOLUTION FIX:
47
+ // 'key' is likely an index string ("0") because mock data is an array.
48
+ // We must resolve this to the real Ticker Symbol for the result map.
49
+ let ticker = key;
50
+
51
+ if (prices.history && prices.history[key] && prices.history[key].instrumentId) {
52
+ const instId = prices.history[key].instrumentId;
53
+ if (mappings && mappings.instrumentToTicker && mappings.instrumentToTicker[instId]) {
54
+ ticker = mappings.instrumentToTicker[instId];
55
+ }
56
+ }
57
+
58
+ if (candles.length < 10) continue;
59
+
60
+ // 2. Calculate Log Returns
61
+ const logReturns = [];
62
+ let lastPrice = 0;
63
+
64
+ for (let i = 1; i < candles.length; i++) {
65
+ const prev = candles[i-1].price;
66
+ const curr = candles[i].price;
67
+
68
+ if (prev > 0 && curr > 0) {
69
+ logReturns.push(Math.log(curr / prev));
70
+ lastPrice = curr;
71
+ }
72
+ }
73
+
74
+ // 3. Filter 30-Day Lookback
75
+ const LOOKBACK = 30;
76
+ const relevantReturns = logReturns.slice(-LOOKBACK);
77
+
78
+ if (relevantReturns.length < 5) continue;
79
+
80
+ // 4. Calculate Stats
81
+ const stdDev = compute.standardDeviation(relevantReturns);
82
+ const annualizedVol = stdDev * Math.sqrt(365);
83
+
84
+ this.result[ticker] = {
85
+ volatility_30d: annualizedVol,
86
+ last_price: lastPrice,
87
+ data_points: relevantReturns.length
88
+ };
89
+ }
90
+ }
91
+
92
+ async getResult() { return this.result; }
93
+ reset() { this.result = {}; }
94
+ }
95
+
96
+ module.exports = AssetVolatilityEstimator;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @fileoverview CORE Product Line (Pass 2 - Standard)
3
+ * Calculates capitulation risk using DYNAMIC volatility from asset-volatility-estimator.
4
+ * Relies on 'schema.md' definitions for portfolio extraction via MathPrimitives.
5
+ */
6
+
7
+ class RetailCapitulationRiskForecast {
8
+ constructor() {
9
+ this.assetRiskProfiles = new Map();
10
+ this.tickerMap = null;
11
+
12
+ // FIX: Use a container object (like the Maps in other calculations)
13
+ // This allows us to MUTATE 'deps.mathLib' instead of RE-ASSIGNING 'this.mathLib'
14
+ this.deps = { mathLib: null };
15
+ }
16
+
17
+ static getMetadata() {
18
+ return {
19
+ type: 'standard',
20
+ rootDataDependencies: ['portfolio', 'history'],
21
+ isHistorical: false,
22
+ userType: 'all',
23
+ category: 'risk_models'
24
+ };
25
+ }
26
+
27
+ static getDependencies() {
28
+ return ['asset-volatility-estimator'];
29
+ }
30
+
31
+ static getSchema() {
32
+ const tickerSchema = {
33
+ "type": "object",
34
+ "properties": {
35
+ "capitulation_probability": { "type": "number" },
36
+ "at_risk_user_count": { "type": "number" },
37
+ "average_pain_threshold_pct": { "type": "number" },
38
+ "used_volatility": { "type": "number" }
39
+ },
40
+ "required": ["capitulation_probability", "at_risk_user_count"]
41
+ };
42
+
43
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
44
+ }
45
+
46
+ _initAsset(ticker, currentPrice, volatility) {
47
+ if (!this.assetRiskProfiles.has(ticker)) {
48
+ this.assetRiskProfiles.set(ticker, {
49
+ currentPrice: currentPrice,
50
+ volatility: volatility,
51
+ profiles: []
52
+ });
53
+ }
54
+ }
55
+
56
+ process(context) {
57
+ const { user, mappings, math, computed } = context;
58
+ const { extract, history, compute, signals } = math;
59
+
60
+ // 1. Capture Libraries via MUTATION
61
+ // We are retrieving the 'deps' object (GET) and mutating its property.
62
+ // This bypasses the Proxy SET trap entirely.
63
+ if (!this.deps.mathLib && compute) {
64
+ this.deps.mathLib = compute;
65
+ }
66
+
67
+ if (!this.tickerMap) {
68
+ this.tickerMap = mappings.instrumentToTicker;
69
+ }
70
+
71
+ // 2. Determine User's "Pain Threshold"
72
+ const historyDoc = history.getDailyHistory(user);
73
+ const summary = history.getSummary(historyDoc);
74
+ let personalPainThreshold = (summary && summary.avgLossPct < 0)
75
+ ? summary.avgLossPct
76
+ : -25.0;
77
+
78
+ // 3. Analyze Positions
79
+ const positions = extract.getPositions(user.portfolio.today, user.type);
80
+
81
+ for (const pos of positions) {
82
+ const instId = extract.getInstrumentId(pos);
83
+
84
+ if (!this.tickerMap) continue;
85
+
86
+ const ticker = this.tickerMap[instId];
87
+
88
+ if (!ticker) continue;
89
+
90
+ // Fetch Dependency
91
+ const assetStats = signals.getPreviousState(computed, 'asset-volatility-estimator', ticker);
92
+
93
+ const dynamicVol = assetStats ? assetStats.volatility_30d : 0.60;
94
+ const currentPrice = assetStats ? assetStats.last_price : 0;
95
+
96
+ if (currentPrice <= 0) continue;
97
+
98
+ // Get P&L from Position Schema
99
+ const netProfit = extract.getNetProfit(pos);
100
+
101
+ // Calculate Entry Price using the Dependency Price
102
+ const entryPrice = extract.deriveEntryPrice(currentPrice, netProfit);
103
+
104
+ if (entryPrice > 0) {
105
+ this._initAsset(ticker, currentPrice, dynamicVol);
106
+ this.assetRiskProfiles.get(ticker).profiles.push({
107
+ entryPrice: entryPrice,
108
+ thresholdPct: personalPainThreshold
109
+ });
110
+ }
111
+ }
112
+ }
113
+
114
+ async getResult() {
115
+ const result = {};
116
+ const TIME_HORIZON_DAYS = 3;
117
+ const SIMULATION_COUNT = 1000;
118
+
119
+ // Access the library from the container
120
+ const mathLib = this.deps.mathLib;
121
+
122
+ if (!mathLib || !mathLib.simulateGBM) {
123
+ console.log('[DEBUG RCRF] MathLib missing in deps container!');
124
+ return {};
125
+ }
126
+
127
+ for (const [ticker, data] of this.assetRiskProfiles.entries()) {
128
+ if (data.profiles.length < 1) {
129
+ continue;
130
+ }
131
+
132
+ try {
133
+ // 1. Generate Price Paths
134
+ const pricePaths = mathLib.simulateGBM(
135
+ data.currentPrice,
136
+ data.volatility,
137
+ TIME_HORIZON_DAYS,
138
+ SIMULATION_COUNT
139
+ );
140
+
141
+ if (!pricePaths || pricePaths.length === 0) continue;
142
+
143
+ // 2. Run Population Breakdown
144
+ const capitulationProb = mathLib.simulatePopulationBreakdown(
145
+ pricePaths,
146
+ data.profiles
147
+ );
148
+
149
+ const totalThreshold = data.profiles.reduce((acc, p) => acc + p.thresholdPct, 0);
150
+ const avgThreshold = totalThreshold / data.profiles.length;
151
+
152
+ result[ticker] = {
153
+ capitulation_probability: parseFloat(capitulationProb.toFixed(4)),
154
+ at_risk_user_count: data.profiles.length,
155
+ average_pain_threshold_pct: parseFloat(avgThreshold.toFixed(2)),
156
+ used_volatility: parseFloat(data.volatility.toFixed(4))
157
+ };
158
+ } catch (err) {
159
+ console.log(`[DEBUG RCRF] Error processing ${ticker}: ${err.message}`);
160
+ }
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ reset() {
167
+ this.assetRiskProfiles.clear();
168
+ this.tickerMap = null;
169
+ this.deps.mathLib = null; // Reset the container
170
+ }
171
+ }
172
+
173
+ module.exports = RetailCapitulationRiskForecast;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Core Calculation (Pass 2)
3
+ * Generates the "Ghost Book" - KDE of Holder Cost Basis.
4
+ * ARCHITECTURE COMPLIANT: Uses Context for Math & Prices.
5
+ */
6
+ class AssetCostBasisProfile {
7
+ constructor() {
8
+ // Map<Ticker, Map<PriceKey, Weight>>
9
+ this.assetBins = new Map();
10
+ this.tickerMap = null;
11
+ // FIX 1: Change state name to clearly indicate storage of the function pointer
12
+ this.computeKDE_func = null;
13
+ }
14
+
15
+ static getMetadata() {
16
+ return {
17
+ type: 'standard',
18
+ rootDataDependencies: ['portfolio'],
19
+ dependencies: [],
20
+ isHistorical: false,
21
+ userType: 'all',
22
+ category: 'core_distribution'
23
+ };
24
+ }
25
+
26
+ static getDependencies() { return []; }
27
+
28
+ static getSchema() {
29
+ const profileSchema = {
30
+ "type": "object",
31
+ "properties": {
32
+ "profile": {
33
+ "type": "array",
34
+ "items": { "type": "object", "properties": { "price": {"type": "number"}, "density": {"type": "number"} } }
35
+ },
36
+ "current_price": { "type": "number" },
37
+ "total_inventory_weight": { "type": "number" }
38
+ }
39
+ };
40
+ return { "type": "object", "patternProperties": { "^.*$": profileSchema } };
41
+ }
42
+
43
+ process(context) {
44
+ const { user, mappings, math } = context;
45
+
46
+ // FIX 2 (CRITICAL): Store the *function pointer* itself from math.distribution.computeKDE.
47
+ // This is necessary because storing the class object (math.distribution) breaks the static method reference when serialized/deserialized by worker threads.
48
+ if (!this.computeKDE_func) {
49
+ this.computeKDE_func = math.distribution.computeKDE;
50
+ }
51
+
52
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
53
+
54
+ const { extract } = math;
55
+
56
+ const positions = extract.getPositions(user.portfolio.today, user.type);
57
+
58
+ for (const pos of positions) {
59
+ const instId = extract.getInstrumentId(pos);
60
+ const ticker = this.tickerMap[instId];
61
+
62
+ if (!ticker) continue;
63
+
64
+ const currentPrice = extract.getCurrentRate(pos);
65
+ if (currentPrice <= 0) continue;
66
+
67
+ const netProfitPct = extract.getNetProfit(pos);
68
+ const weight = extract.getPositionWeight(pos, user.type);
69
+
70
+ if (netProfitPct <= -99.9) continue;
71
+
72
+ const entryPrice = currentPrice / (1 + (netProfitPct / 100.0));
73
+
74
+ const priceKey = Number(entryPrice.toPrecision(6));
75
+
76
+ if (!this.assetBins.has(ticker)) {
77
+ this.assetBins.set(ticker, { bins: new Map(), currentPrice });
78
+ }
79
+ const asset = this.assetBins.get(ticker);
80
+
81
+ asset.currentPrice = currentPrice;
82
+
83
+ const currentWeight = asset.bins.get(priceKey) || 0;
84
+ asset.bins.set(priceKey, currentWeight + weight);
85
+ }
86
+ }
87
+
88
+ async getResult() {
89
+ const result = {};
90
+
91
+ // Use the function pointer stored in state. No longer need the corrupted 'DistributionAnalytics' variable.
92
+ const computeKDE = this.computeKDE_func;
93
+
94
+ if (!computeKDE) return {}; // Safety guard
95
+
96
+ for (const [ticker, data] of this.assetBins.entries()) {
97
+ const points = [];
98
+ let totalWeight = 0;
99
+ for (const [price, weight] of data.bins.entries()) {
100
+ points.push({ value: price, weight: weight });
101
+ totalWeight += weight;
102
+ }
103
+
104
+ if (points.length < 2) continue;
105
+
106
+ const bandwidth = data.currentPrice * 0.02;
107
+
108
+ // FIX 3: Call the stored function directly
109
+ const profile = computeKDE(points, bandwidth, 60);
110
+
111
+ result[ticker] = {
112
+ profile: profile,
113
+ current_price: data.currentPrice,
114
+ total_inventory_weight: totalWeight
115
+ };
116
+ }
117
+ return result;
118
+ }
119
+
120
+ reset() {
121
+ this.assetBins.clear();
122
+ this.tickerMap = null;
123
+ this.computeKDE_func = null;
124
+ }
125
+ }
126
+
127
+ module.exports = AssetCostBasisProfile;
@@ -56,7 +56,7 @@ class AssetPnlStatus {
56
56
  if (pnl === 0) continue;
57
57
 
58
58
  const ticker = mappings.instrumentToTicker[instId];
59
- const sector = mappings.sectorMapping[instId];
59
+ const sector = mappings.instrumentToSector[instId];
60
60
  const isWinner = pnl > 0;
61
61
 
62
62
  if (ticker) {
@@ -57,7 +57,7 @@ class AssetPositionSize {
57
57
  if (!instId) continue;
58
58
 
59
59
  const ticker = mappings.instrumentToTicker[instId];
60
- const sector = mappings.sectorMapping[instId];
60
+ const sector = mappings.instrumentToSector[instId];
61
61
 
62
62
  if (ticker) {
63
63
  this._init(this.tickerBuckets, ticker);
@@ -42,7 +42,7 @@ class AverageDailyPnlPerSector {
42
42
  process(context) {
43
43
  const { extract } = context.math;
44
44
  const { mappings, user } = context;
45
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
45
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
46
46
 
47
47
  const positions = extract.getPositions(user.portfolio.today, user.type);
48
48
  const userSectorPnL = new Map();
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1) for ownership delta (users added/lost per asset).
3
- * REFACTORED: Tracks net change in number of owners.
3
+ * REFACTORED: Tracks net change in number of owners. ---> TODO IN TEST ENVIRONMENT THE VALUE OF NUMBER OF CLOSURES ALWAYS RETURNS 0, IS THIS A COMPUTATION BUG OR A TEST HARNESS BUG?
4
4
  */
5
5
  class PlatformDailyOwnershipDelta {
6
6
  constructor() {
@@ -41,7 +41,7 @@ class PlatformOwnershipPerSector {
41
41
  process(context) {
42
42
  const { extract } = context.math;
43
43
  const { mappings, user } = context;
44
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
44
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
45
45
 
46
46
  const positions = extract.getPositions(user.portfolio.today, user.type);
47
47
 
@@ -42,7 +42,7 @@ class ProfitabilityRatioPerSector {
42
42
  process(context) {
43
43
  const { extract } = context.math;
44
44
  const { mappings, user } = context;
45
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
45
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
46
46
 
47
47
  const positions = extract.getPositions(user.portfolio.today, user.type);
48
48
 
@@ -41,7 +41,7 @@ class SpeculatorLeveragePerSector {
41
41
  process(context) {
42
42
  const { extract } = context.math;
43
43
  const { mappings, user } = context;
44
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
44
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
45
45
 
46
46
  const positions = extract.getPositions(user.portfolio.today, user.type);
47
47
 
@@ -46,7 +46,7 @@ class SpeculatorSLDistanceSectorBreakdown {
46
46
  process(context) {
47
47
  const { extract } = context.math;
48
48
  const { mappings, user } = context;
49
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
49
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
50
50
 
51
51
  const positions = extract.getPositions(user.portfolio.today, user.type);
52
52
 
File without changes
@@ -41,7 +41,7 @@ class TotalLongPerSector {
41
41
  process(context) {
42
42
  const { extract } = context.math;
43
43
  const { mappings, user } = context;
44
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
44
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
45
45
 
46
46
  const positions = extract.getPositions(user.portfolio.today, user.type);
47
47
 
@@ -41,7 +41,7 @@ class TotalShortPerSector {
41
41
  process(context) {
42
42
  const { extract } = context.math;
43
43
  const { mappings, user } = context;
44
- if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
44
+ if (!this.sectorMap) this.sectorMap = mappings.instrumentToSector;
45
45
 
46
46
  const positions = extract.getPositions(user.portfolio.today, user.type);
47
47
 
@@ -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;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @fileoverview Cognitive Dissonance Arbitrage (CDA) v2.2
3
+ * REFACTORED: Adheres to System Architecture and Schema v1.
4
+ */
5
+ class CognitiveDissonance {
6
+ constructor() {
7
+ this.cdaResults = {};
8
+ this.alpha = 2 / (20 + 1); // EMA Alpha
9
+ }
10
+
11
+ static getMetadata() {
12
+ return {
13
+ type: 'meta', // Runs after standard calculations
14
+ rootDataDependencies: [],
15
+ isHistorical: true, // Requires t-1 state
16
+ userType: 'aggregate',
17
+ category: 'predictive_alpha'
18
+ };
19
+ }
20
+
21
+ static getDependencies() {
22
+ return ['social-topic-sentiment-matrix', 'skilled-cohort-flow', 'instrument-price-change-1d'];
23
+ }
24
+
25
+ static getSchema() {
26
+ // Schema remains strictly compliant with user definition
27
+ const metricSchema = {
28
+ "type": "object",
29
+ "properties": {
30
+ "cda_score": { "type": "number" },
31
+ "regime": { "type": "string" },
32
+ "z_sentiment": { "type": "number" },
33
+ "z_flow": { "type": "number" },
34
+ "price_confirmation": { "type": "boolean" },
35
+ "_state": { "type": "object" } // Opaque state object
36
+ }
37
+ };
38
+ return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
39
+ }
40
+
41
+ process(context) {
42
+ const { computed, previousComputed, math } = context;
43
+ const { signals: SignalPrimitives, TimeSeries } = math;
44
+
45
+ const tickers = SignalPrimitives.getUnionKeys(computed, CognitiveDissonance.getDependencies());
46
+
47
+ for (const ticker of tickers) {
48
+ // 1. Get Metrics (Safe Access)
49
+ // MAP: 'social-topic-sentiment-matrix' uses 'net_sentiment' NOT 'sentiment_score'
50
+ const rawSentiment = SignalPrimitives.getMetric(computed, 'social-topic-sentiment-matrix', ticker, 'net_sentiment', 0);
51
+ const rawFlow = SignalPrimitives.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
52
+ const priceChange = SignalPrimitives.getMetric(computed, 'instrument-price-change-1d', ticker, 'change_1d_pct', 0); // Map to correct field
53
+
54
+ // 2. Get Previous State
55
+ const prevResult = SignalPrimitives.getPreviousState(previousComputed, 'cognitive-dissonance', ticker);
56
+ const prevState = prevResult ? prevResult._state : { sent_mean: 0, sent_var: 1, flow_mean: 0, flow_var: 1 };
57
+
58
+ // 3. Update Statistics (Math Layer)
59
+ const sentStats = TimeSeries.updateEMAState(rawSentiment, { mean: prevState.sent_mean, variance: prevState.sent_var }, this.alpha);
60
+ const flowStats = TimeSeries.updateEMAState(rawFlow, { mean: prevState.flow_mean, variance: prevState.flow_var }, this.alpha);
61
+
62
+ const sentStdDev = Math.sqrt(sentStats.variance);
63
+ const flowStdDev = Math.sqrt(flowStats.variance);
64
+
65
+ // 4. Compute Z-Scores
66
+ const zSentiment = (sentStdDev > 0.001) ? (rawSentiment - sentStats.mean) / sentStdDev : 0;
67
+ const zFlow = (flowStdDev > 0.001) ? (rawFlow - flowStats.mean) / flowStdDev : 0;
68
+
69
+ // 5. Logic (Voice vs Hands)
70
+ const interaction = zSentiment * zFlow;
71
+ let cdaScore = 0;
72
+ let regime = "NEUTRAL";
73
+ let priceConfirmation = false;
74
+
75
+ const disagree = (Math.sign(zSentiment) !== Math.sign(zFlow));
76
+ const loudVoice = Math.abs(zSentiment) > 1.65;
77
+ const activeHands = Math.abs(zFlow) > 0.5;
78
+
79
+ if (disagree && loudVoice && activeHands) {
80
+ cdaScore = -interaction; // Positive score = Bearish Dissonance (Euphoria + Selling)
81
+
82
+ const priceAgreesWithSentiment = (Math.sign(priceChange) === Math.sign(zSentiment));
83
+ const priceIsFlat = Math.abs(priceChange) < 0.5;
84
+
85
+ if (priceAgreesWithSentiment && !priceIsFlat) {
86
+ priceConfirmation = true;
87
+ regime = "CONVERGENT";
88
+ cdaScore = 0;
89
+ } else {
90
+ regime = (zSentiment > 0) ? "BEARISH_DISSONANCE" : "BULLISH_DISSONANCE";
91
+ }
92
+ } else if (!disagree && loudVoice) {
93
+ regime = "CONVERGENT";
94
+ }
95
+
96
+ this.cdaResults[ticker] = {
97
+ cda_score: Number(cdaScore.toFixed(4)),
98
+ regime: regime,
99
+ z_sentiment: Number(zSentiment.toFixed(4)),
100
+ z_flow: Number(zFlow.toFixed(4)),
101
+ price_confirmation: priceConfirmation,
102
+ _state: {
103
+ sent_mean: sentStats.mean, sent_var: sentStats.variance,
104
+ flow_mean: flowStats.mean, flow_var: flowStats.variance
105
+ }
106
+ };
107
+ }
108
+ }
109
+
110
+ getResult() { return this.cdaResults; }
111
+ reset() { this.cdaResults = {}; }
112
+ }
113
+ module.exports = CognitiveDissonance;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Diamond Hand Fracture Coefficient (DHFC) v2.2
3
+ * REFACTORED: Adheres to System Architecture.
4
+ */
5
+ class DiamondHandFracture {
6
+ constructor() { this.fractureResults = {}; }
7
+
8
+ static getMetadata() {
9
+ return {
10
+ type: 'meta',
11
+ rootDataDependencies: [],
12
+ isHistorical: true,
13
+ userType: 'aggregate',
14
+ category: 'predictive_alpha'
15
+ };
16
+ }
17
+
18
+ static getDependencies() {
19
+ return ['holding-duration-per-asset', 'average-daily-pnl-per-stock'];
20
+ }
21
+
22
+ static getSchema() {
23
+ const metricSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "fracture_velocity": { "type": "number" },
27
+ "capitulation_score": { "type": "number" },
28
+ "regime": { "type": "string" },
29
+ "expected_duration_hours": { "type": "number" },
30
+ "actual_duration_hours": { "type": "number" }
31
+ }
32
+ };
33
+ return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
34
+ }
35
+
36
+ process(context) {
37
+ const { computed, previousComputed, math } = context;
38
+ const { signals: SignalPrimitives } = math; // fixed?
39
+
40
+ const tickers = SignalPrimitives.getUnionKeys(computed, DiamondHandFracture.getDependencies());
41
+
42
+ for (const ticker of tickers) {
43
+ // 1. Current State
44
+ const H_t = SignalPrimitives.getMetric(computed, 'holding-duration-per-asset', ticker, 'avg_duration_hours', 0);
45
+ const OI_t = SignalPrimitives.getMetric(computed, 'holding-duration-per-asset', ticker, 'count', 0);
46
+ const avgPnl = SignalPrimitives.getMetric(computed, 'average-daily-pnl-per-stock', ticker, 'avg_daily_pnl_pct', 0);
47
+
48
+ // 2. Previous State (T-1)
49
+ // Note: We fetch 'holding-duration-per-asset' from *previous* computed results
50
+ const prevData = SignalPrimitives.getPreviousState(previousComputed, 'holding-duration-per-asset', ticker);
51
+ const H_prev = prevData ? (prevData.avg_duration_hours || 0) : 0;
52
+ const OI_prev = prevData ? (prevData.count || 0) : 0;
53
+
54
+ // Init Check
55
+ if (H_prev === 0 || OI_prev === 0) {
56
+ this.fractureResults[ticker] = { fracture_velocity: 0, capitulation_score: 0, regime: "STABLE", expected_duration_hours: H_t, actual_duration_hours: H_t };
57
+ continue;
58
+ }
59
+
60
+ // 3. Logic: Inventory Dilution
61
+ let dilutionFactor = (OI_t > 0) ? (OI_prev / OI_t) : 1.0;
62
+ if (dilutionFactor > 2.0) dilutionFactor = 1.0; // Clamp extreme dilution
63
+
64
+ const H_expected = H_prev * dilutionFactor;
65
+ const phi = H_expected - H_t;
66
+ const phi_normalized = (H_prev > 0) ? (phi / H_prev) * 100 : 0;
67
+
68
+ // 4. Capitulation Score
69
+ const painFactor = (avgPnl < 0) ? Math.abs(avgPnl) : 0;
70
+ const capitulationScore = phi_normalized * painFactor;
71
+
72
+ let regime = "STABLE";
73
+ if (phi_normalized > 5.0) regime = "FRACTURE";
74
+ if (capitulationScore > 20.0) regime = "CAPITULATION";
75
+ if (phi_normalized < -5.0) regime = "ACCUMULATION";
76
+
77
+ this.fractureResults[ticker] = {
78
+ fracture_velocity: Number(phi_normalized.toFixed(4)),
79
+ capitulation_score: Number(capitulationScore.toFixed(4)),
80
+ regime: regime,
81
+ expected_duration_hours: Number(H_expected.toFixed(2)),
82
+ actual_duration_hours: Number(H_t.toFixed(2))
83
+ };
84
+ }
85
+ }
86
+
87
+ getResult() { return this.fractureResults; }
88
+ reset() { this.fractureResults = {}; }
89
+ }
90
+ module.exports = DiamondHandFracture;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @fileoverview Mimetic Latency Oscillator (MLO) v2.2
3
+ * REFACTORED: Offloaded math to primitives, corrected dependencies. TODO - HOW IS THIS GOING TO WORK GIVEN IT REQUIRES ITS OWN YESTERDAY DATA, BUT IT WILL BACKFILL IMMEDIATELY ON ITS FIRST PASS, AND THUS NOT FIND ANY YESTERDAY DATA? CONFUSED.
4
+ */
5
+ class MimeticLatencyOscillator {
6
+ constructor() {
7
+ this.mloResults = {};
8
+ this.windowSize = 30;
9
+ this.maxLag = 10;
10
+ }
11
+
12
+ static getMetadata() {
13
+ return {
14
+ type: 'meta',
15
+ rootDataDependencies: [],
16
+ isHistorical: true,
17
+ userType: 'aggregate',
18
+ category: 'predictive_alpha'
19
+ };
20
+ }
21
+
22
+ static getDependencies() {
23
+ return ['skilled-cohort-flow', 'herd-consensus-score'];
24
+ }
25
+
26
+ static getSchema() {
27
+ const metricSchema = {
28
+ "type": "object",
29
+ "properties": {
30
+ "lag_days": { "type": "integer" },
31
+ "correlation_strength": { "type": "number" },
32
+ "regime": { "type": "string" },
33
+ "_state": { "type": "object" }
34
+ }
35
+ };
36
+ return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
37
+ }
38
+
39
+ process(context) {
40
+ const { computed, previousComputed, math } = context;
41
+ const { signals: SignalPrimitives, TimeSeries } = math;
42
+
43
+ const tickers = SignalPrimitives.getUnionKeys(computed, MimeticLatencyOscillator.getDependencies());
44
+
45
+ for (const ticker of tickers) {
46
+ // 1. Inputs
47
+ // 'skilled-cohort-flow' -> net_flow_pct
48
+ const rawFlow = SignalPrimitives.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
49
+ // 'herd-consensus-score' -> herd_conviction_score
50
+ const rawHerd = SignalPrimitives.getMetric(computed, 'herd-consensus-score', ticker, 'herd_conviction_score', 0);
51
+
52
+ // 2. Restore State
53
+ const prevResult = SignalPrimitives.getPreviousState(previousComputed, 'mimetic-latency', ticker);
54
+ const prevState = prevResult ? prevResult._state : { flow_buffer: [], herd_buffer: [], last_flow: 0, last_herd: 0 };
55
+
56
+ const prevFlow = (prevState.last_flow !== undefined) ? prevState.last_flow : 0;
57
+ const prevHerd = (prevState.last_herd !== undefined) ? prevState.last_herd : 0;
58
+
59
+ // 3. Calculate Detrended Delta
60
+ const flowDelta = rawFlow - prevFlow;
61
+ const herdDelta = rawHerd - prevHerd;
62
+
63
+ // 4. Update Buffers
64
+ let flowBuffer = [...(prevState.flow_buffer || [])];
65
+ let herdBuffer = [...(prevState.herd_buffer || [])];
66
+
67
+ flowBuffer.push(flowDelta);
68
+ herdBuffer.push(herdDelta);
69
+
70
+ if (flowBuffer.length > this.windowSize) flowBuffer.shift();
71
+ if (herdBuffer.length > this.windowSize) herdBuffer.shift();
72
+
73
+ // 5. Lagged Cross-Correlation
74
+ let maxCorr = -1.0;
75
+ let bestLag = 0;
76
+
77
+ if (flowBuffer.length >= 15) {
78
+ for (let k = 0; k <= this.maxLag; k++) {
79
+ // Check if Flow[t-k] predicts Herd[t]
80
+ // Slice Flow: 0 to End-k
81
+ // Slice Herd: k to End
82
+ const len = flowBuffer.length;
83
+ if (len - k < 5) continue; // Min sample check
84
+
85
+ const slicedFlow = flowBuffer.slice(0, len - k);
86
+ const slicedHerd = herdBuffer.slice(k, len);
87
+
88
+ const r = TimeSeries.pearsonCorrelation(slicedFlow, slicedHerd);
89
+
90
+ if (r > maxCorr) {
91
+ maxCorr = r;
92
+ bestLag = k;
93
+ }
94
+ }
95
+ }
96
+
97
+ // 6. Regime
98
+ let regime = "NO_SIGNAL";
99
+ if (maxCorr > 0.3) {
100
+ if (bestLag >= 3) regime = "EARLY_ALPHA";
101
+ else if (bestLag >= 1) regime = "MARKUP";
102
+ else regime = "FOMO_TRAP";
103
+ } else {
104
+ regime = "DECOUPLING";
105
+ }
106
+
107
+ this.mloResults[ticker] = {
108
+ lag_days: bestLag,
109
+ correlation_strength: Number(maxCorr.toFixed(4)),
110
+ regime: regime,
111
+ _state: {
112
+ flow_buffer: flowBuffer,
113
+ herd_buffer: herdBuffer,
114
+ last_flow: rawFlow,
115
+ last_herd: rawHerd
116
+ }
117
+ };
118
+ }
119
+ }
120
+
121
+ getResult() { return this.mloResults; }
122
+ reset() { this.mloResults = {}; }
123
+ }
124
+ module.exports = MimeticLatencyOscillator;
@@ -50,7 +50,7 @@ class RiskAppetiteIndex {
50
50
 
51
51
  if (!this.tickerMap) {
52
52
  this.tickerMap = mappings.instrumentToTicker;
53
- this.sectorMap = mappings.sectorMapping;
53
+ this.sectorMap = mappings.instrumentToSector;
54
54
  }
55
55
 
56
56
  const positions = extract.getPositions(user.portfolio.today, user.type);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.87",
3
+ "version": "1.0.89",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [