aiden-shared-calculations-unified 1.0.92 → 1.0.94

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview CORE Product Line (Pass 1 - Meta)
3
- * Calculates annualized volatility using the new priceExtractor.
3
+ * Calculates annualized volatility.
4
+ * Safe for sharded execution (accumulates results).
4
5
  */
5
6
  class AssetVolatilityEstimator {
6
7
  constructor() {
@@ -33,31 +34,30 @@ class AssetVolatilityEstimator {
33
34
  }
34
35
 
35
36
  process(context) {
36
- // FIXED: Destructure 'mappings' to resolve real tickers
37
37
  const { math, prices, mappings } = context;
38
-
39
- // FIXED: Destructure 'priceExtractor' directly (matches worker.js injection)
40
38
  const { compute, priceExtractor } = math;
39
+
40
+ if (!prices || !prices.history) return;
41
41
 
42
- // 1. Get ALL histories properly sorted and parsed
42
+ // Efficiently extract only histories present in this shard/context
43
43
  const allHistories = priceExtractor.getAllHistories(prices);
44
44
 
45
+ const batchResult = {};
46
+
45
47
  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
48
  let ticker = key;
50
49
 
51
- if (prices.history && prices.history[key] && prices.history[key].instrumentId) {
50
+ // Resolve ticker logic
51
+ if (prices.history[key] && prices.history[key].instrumentId) {
52
52
  const instId = prices.history[key].instrumentId;
53
53
  if (mappings && mappings.instrumentToTicker && mappings.instrumentToTicker[instId]) {
54
54
  ticker = mappings.instrumentToTicker[instId];
55
55
  }
56
56
  }
57
57
 
58
- if (candles.length < 10) continue;
58
+ // Guard: Need enough data for a trend
59
+ if (!candles || candles.length < 10) continue;
59
60
 
60
- // 2. Calculate Log Returns
61
61
  const logReturns = [];
62
62
  let lastPrice = 0;
63
63
 
@@ -65,28 +65,31 @@ class AssetVolatilityEstimator {
65
65
  const prev = candles[i-1].price;
66
66
  const curr = candles[i].price;
67
67
 
68
+ // Guard: Prevent log(0) or log(negative) errors
68
69
  if (prev > 0 && curr > 0) {
69
70
  logReturns.push(Math.log(curr / prev));
70
71
  lastPrice = curr;
71
72
  }
72
73
  }
73
74
 
74
- // 3. Filter 30-Day Lookback
75
75
  const LOOKBACK = 30;
76
76
  const relevantReturns = logReturns.slice(-LOOKBACK);
77
77
 
78
+ // Guard: Need enough returns for Standard Deviation
78
79
  if (relevantReturns.length < 5) continue;
79
80
 
80
- // 4. Calculate Stats
81
81
  const stdDev = compute.standardDeviation(relevantReturns);
82
82
  const annualizedVol = stdDev * Math.sqrt(365);
83
83
 
84
- this.result[ticker] = {
85
- volatility_30d: annualizedVol,
84
+ batchResult[ticker] = {
85
+ volatility_30d: Number(annualizedVol.toFixed(4)),
86
86
  last_price: lastPrice,
87
87
  data_points: relevantReturns.length
88
88
  };
89
89
  }
90
+
91
+ // Accumulate results (handling batched execution)
92
+ Object.assign(this.result, batchResult);
90
93
  }
91
94
 
92
95
  async getResult() { return this.result; }
@@ -1,16 +1,18 @@
1
1
  /**
2
2
  * @fileoverview CORE Product Line (Pass 2 - Standard)
3
3
  * Calculates capitulation risk using DYNAMIC volatility from asset-volatility-estimator.
4
- * Relies on 'schema.md' definitions for portfolio extraction via MathPrimitives.
4
+ * MEMORY OPTIMIZED: Uses Cohort Aggregation instead of raw user arrays.
5
5
  */
6
6
 
7
7
  class RetailCapitulationRiskForecast {
8
8
  constructor() {
9
- this.assetRiskProfiles = new Map();
9
+ // Map<Ticker, Map<CohortKey, { entryPrice, thresholdPct, weight }>>
10
+ this.assetCohorts = new Map();
11
+
12
+ // Cache for Ticker Map
10
13
  this.tickerMap = null;
11
14
 
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'
15
+ // Container for dependency injection (MathLib)
14
16
  this.deps = { mathLib: null };
15
17
  }
16
18
 
@@ -43,23 +45,11 @@ class RetailCapitulationRiskForecast {
43
45
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
44
46
  }
45
47
 
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
48
  process(context) {
57
49
  const { user, mappings, math, computed } = context;
58
50
  const { extract, history, compute, signals } = math;
59
51
 
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.
52
+ // 1. Capture MathLib (Mutation Pattern)
63
53
  if (!this.deps.mathLib && compute) {
64
54
  this.deps.mathLib = compute;
65
55
  }
@@ -69,68 +59,123 @@ class RetailCapitulationRiskForecast {
69
59
  }
70
60
 
71
61
  // 2. Determine User's "Pain Threshold"
62
+ // We bucket this to the nearest 5% to reduce cardinality
72
63
  const historyDoc = history.getDailyHistory(user);
73
64
  const summary = history.getSummary(historyDoc);
74
- let personalPainThreshold = (summary && summary.avgLossPct < 0)
75
- ? summary.avgLossPct
76
- : -25.0;
65
+ let rawThreshold = (summary && summary.avgLossPct < 0) ? summary.avgLossPct : -25.0;
66
+ const bucketedThreshold = Math.round(rawThreshold / 5) * 5;
77
67
 
78
68
  // 3. Analyze Positions
79
69
  const positions = extract.getPositions(user.portfolio.today, user.type);
80
70
 
81
71
  for (const pos of positions) {
82
72
  const instId = extract.getInstrumentId(pos);
83
-
84
73
  if (!this.tickerMap) continue;
85
-
86
74
  const ticker = this.tickerMap[instId];
87
-
88
75
  if (!ticker) continue;
89
76
 
90
- // Fetch Dependency
77
+ // Fetch Volatility & Price from Dependency
78
+ // Note: We access this to normalize Entry Price, but we store Volatility
79
+ // at the aggregation stage to keep it consistent.
91
80
  const assetStats = signals.getPreviousState(computed, 'asset-volatility-estimator', ticker);
92
-
93
- const dynamicVol = assetStats ? assetStats.volatility_30d : 0.60;
94
81
  const currentPrice = assetStats ? assetStats.last_price : 0;
82
+ const dynamicVol = assetStats ? assetStats.volatility_30d : 0.60;
95
83
 
96
84
  if (currentPrice <= 0) continue;
97
85
 
98
- // Get P&L from Position Schema
99
86
  const netProfit = extract.getNetProfit(pos);
100
-
101
- // Calculate Entry Price using the Dependency Price
102
87
  const entryPrice = extract.deriveEntryPrice(currentPrice, netProfit);
103
88
 
104
89
  if (entryPrice > 0) {
105
- this._initAsset(ticker, currentPrice, dynamicVol);
106
- this.assetRiskProfiles.get(ticker).profiles.push({
107
- entryPrice: entryPrice,
108
- thresholdPct: personalPainThreshold
109
- });
90
+ this._aggregateCohort(ticker, entryPrice, bucketedThreshold, currentPrice, dynamicVol);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Aggregates a user into a memory-efficient cohort.
97
+ */
98
+ _aggregateCohort(ticker, entryPrice, thresholdPct, currentPrice, volatility) {
99
+ if (!this.assetCohorts.has(ticker)) {
100
+ this.assetCohorts.set(ticker, {
101
+ currentPrice,
102
+ volatility,
103
+ cohorts: new Map() // Key: "Entry_Threshold" -> Value: { count }
104
+ });
105
+ }
106
+
107
+ const assetData = this.assetCohorts.get(ticker);
108
+
109
+ // Update volatility/price (last write wins is fine for streaming updates of same asset)
110
+ assetData.currentPrice = currentPrice;
111
+ assetData.volatility = volatility;
112
+
113
+ // Create Cohort Key
114
+ // Round Entry Price to 2 significant digits to bucket users
115
+ const entryKey = entryPrice.toPrecision(3);
116
+ const cohortKey = `${entryKey}_${thresholdPct}`;
117
+
118
+ if (!assetData.cohorts.has(cohortKey)) {
119
+ assetData.cohorts.set(cohortKey, {
120
+ entryPrice: parseFloat(entryKey),
121
+ thresholdPct: thresholdPct,
122
+ count: 0
123
+ });
124
+ }
125
+
126
+ // Increment count (Weighting)
127
+ assetData.cohorts.get(cohortKey).count++;
128
+ }
129
+
130
+ /**
131
+ * Local implementation of Population Breakdown that handles WEIGHTED cohorts.
132
+ * Replaces math.simulatePopulationBreakdown for this specific optimized calculation.
133
+ */
134
+ _simulateWeightedBreakdown(pricePaths, cohorts) {
135
+ if (!pricePaths.length || cohorts.length === 0) return 0;
136
+
137
+ let totalBreakdownEvents = 0;
138
+ const totalSims = pricePaths.length;
139
+
140
+ // Calculate total users (sum of all cohort counts)
141
+ const totalUsers = cohorts.reduce((sum, c) => sum + c.count, 0);
142
+ if (totalUsers === 0) return 0;
143
+
144
+ for (let i = 0; i < totalSims; i++) {
145
+ const simPrice = pricePaths[i];
146
+ let capitulatedCount = 0;
147
+
148
+ for (const cohort of cohorts) {
149
+ // P&L% = (CurrentValue - EntryValue) / EntryValue
150
+ const hypotheticalPnL = ((simPrice - cohort.entryPrice) / cohort.entryPrice) * 100;
151
+
152
+ if (hypotheticalPnL < cohort.thresholdPct) {
153
+ capitulatedCount += cohort.count; // Add the WEIGHT of the cohort
154
+ }
110
155
  }
156
+
157
+ totalBreakdownEvents += (capitulatedCount / totalUsers);
111
158
  }
159
+
160
+ return totalBreakdownEvents / totalSims;
112
161
  }
113
162
 
114
163
  async getResult() {
115
164
  const result = {};
116
165
  const TIME_HORIZON_DAYS = 3;
117
166
  const SIMULATION_COUNT = 1000;
118
-
119
- // Access the library from the container
120
167
  const mathLib = this.deps.mathLib;
121
168
 
122
- if (!mathLib || !mathLib.simulateGBM) {
123
- console.log('[DEBUG RCRF] MathLib missing in deps container!');
124
- return {};
125
- }
169
+ if (!mathLib || !mathLib.simulateGBM) return {};
126
170
 
127
- for (const [ticker, data] of this.assetRiskProfiles.entries()) {
128
- if (data.profiles.length < 1) {
129
- continue;
130
- }
171
+ for (const [ticker, data] of this.assetCohorts.entries()) {
172
+ // Flatten Map values to Array for iteration
173
+ const cohortArray = Array.from(data.cohorts.values());
174
+
175
+ if (cohortArray.length === 0) continue;
131
176
 
132
177
  try {
133
- // 1. Generate Price Paths
178
+ // 1. Generate Price Paths (O(Sims))
134
179
  const pricePaths = mathLib.simulateGBM(
135
180
  data.currentPrice,
136
181
  data.volatility,
@@ -140,23 +185,26 @@ class RetailCapitulationRiskForecast {
140
185
 
141
186
  if (!pricePaths || pricePaths.length === 0) continue;
142
187
 
143
- // 2. Run Population Breakdown
144
- const capitulationProb = mathLib.simulatePopulationBreakdown(
188
+ // 2. Run Weighted Population Breakdown (O(Sims * Cohorts))
189
+ // This is much faster than O(Sims * Users)
190
+ const capitulationProb = this._simulateWeightedBreakdown(
145
191
  pricePaths,
146
- data.profiles
192
+ cohortArray
147
193
  );
148
194
 
149
- const totalThreshold = data.profiles.reduce((acc, p) => acc + p.thresholdPct, 0);
150
- const avgThreshold = totalThreshold / data.profiles.length;
195
+ // Calculate weighted average threshold for reporting
196
+ const totalUsers = cohortArray.reduce((sum, c) => sum + c.count, 0);
197
+ const weightedThresholdSum = cohortArray.reduce((sum, c) => sum + (c.thresholdPct * c.count), 0);
198
+ const avgThreshold = totalUsers > 0 ? weightedThresholdSum / totalUsers : -25;
151
199
 
152
200
  result[ticker] = {
153
201
  capitulation_probability: parseFloat(capitulationProb.toFixed(4)),
154
- at_risk_user_count: data.profiles.length,
202
+ at_risk_user_count: totalUsers,
155
203
  average_pain_threshold_pct: parseFloat(avgThreshold.toFixed(2)),
156
204
  used_volatility: parseFloat(data.volatility.toFixed(4))
157
205
  };
158
206
  } catch (err) {
159
- console.log(`[DEBUG RCRF] Error processing ${ticker}: ${err.message}`);
207
+ // Silent catch to prevent one bad ticker from stopping the batch
160
208
  }
161
209
  }
162
210
 
@@ -164,9 +212,9 @@ class RetailCapitulationRiskForecast {
164
212
  }
165
213
 
166
214
  reset() {
167
- this.assetRiskProfiles.clear();
215
+ this.assetCohorts.clear();
168
216
  this.tickerMap = null;
169
- this.deps.mathLib = null; // Reset the container
217
+ this.deps.mathLib = null;
170
218
  }
171
219
  }
172
220
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
3
- * FIXED: Uses priceExtractor properly
3
+ * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
4
  */
5
5
  class InstrumentPriceChange1D {
6
6
  constructor() {
@@ -36,7 +36,7 @@ class InstrumentPriceChange1D {
36
36
  const { priceExtractor } = math;
37
37
 
38
38
  if (!prices || !prices.history) {
39
- this.result = {};
39
+ // Do NOT reset result here, just return if this shard is empty
40
40
  return;
41
41
  }
42
42
 
@@ -46,34 +46,37 @@ class InstrumentPriceChange1D {
46
46
  yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
47
47
  const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
48
48
 
49
- const results = {};
49
+ const batchResults = {};
50
50
 
51
- // Iterate over all instruments in price history
51
+ // Iterate over THIS SHARD'S instruments
52
52
  for (const [instrumentId, priceData] of Object.entries(prices.history)) {
53
53
  const ticker = instrumentToTicker[instrumentId];
54
54
  if (!ticker) continue;
55
55
 
56
+ // Note: getHistory works on the partial 'prices' object just fine
56
57
  const history = priceExtractor.getHistory(prices, instrumentId);
57
58
  if (history.length === 0) continue;
58
59
 
59
- // Find today's and yesterday's prices
60
60
  const todayPrice = priceData.prices?.[todayStr];
61
61
  const yesterdayPrice = priceData.prices?.[yesterdayStr];
62
62
 
63
63
  if (todayPrice && yesterdayPrice && yesterdayPrice > 0) {
64
64
  const changePct = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
65
- results[ticker] = {
65
+ batchResults[ticker] = {
66
66
  change_1d_pct: isFinite(changePct) ? changePct : 0
67
67
  };
68
68
  } else {
69
- results[ticker] = { change_1d_pct: 0 };
69
+ batchResults[ticker] = { change_1d_pct: 0 };
70
70
  }
71
71
  }
72
72
 
73
- this.result = results;
73
+ // Merge batch results into main result
74
+ Object.assign(this.result, batchResults);
74
75
  }
75
76
 
76
77
  async getResult() { return this.result; }
78
+
79
+ // Explicit reset only called by system between full runs
77
80
  reset() { this.result = {}; }
78
81
  }
79
82
  module.exports = InstrumentPriceChange1D;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
3
- * FIXED: Uses priceExtractor properly
3
+ * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
4
  */
5
5
  class InstrumentPriceMomentum20D {
6
6
  constructor() {
@@ -49,7 +49,6 @@ class InstrumentPriceMomentum20D {
49
49
  const { instrumentToTicker } = mappings;
50
50
 
51
51
  if (!prices || !prices.history) {
52
- this.result = {};
53
52
  return;
54
53
  }
55
54
 
@@ -59,9 +58,9 @@ class InstrumentPriceMomentum20D {
59
58
  twentyDaysAgo.setUTCDate(todayDate.getUTCDate() - 20);
60
59
  const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
61
60
 
62
- const results = {};
61
+ const batchResults = {};
63
62
 
64
- // Iterate over all instruments in price history
63
+ // Iterate over THIS SHARD'S instruments
65
64
  for (const [instrumentId, priceData] of Object.entries(prices.history)) {
66
65
  const ticker = instrumentToTicker[instrumentId];
67
66
  if (!ticker) continue;
@@ -71,15 +70,16 @@ class InstrumentPriceMomentum20D {
71
70
 
72
71
  if (currentPrice && oldPrice && oldPrice > 0) {
73
72
  const momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
74
- results[ticker] = {
73
+ batchResults[ticker] = {
75
74
  momentum_20d_pct: isFinite(momPct) ? momPct : 0
76
75
  };
77
76
  } else {
78
- results[ticker] = { momentum_20d_pct: 0 };
77
+ batchResults[ticker] = { momentum_20d_pct: 0 };
79
78
  }
80
79
  }
81
80
 
82
- this.result = results;
81
+ // Merge
82
+ Object.assign(this.result, batchResults);
83
83
  }
84
84
 
85
85
  async getResult() { return this.result; }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
3
- * REFACTORED: Uses process(context) solely.
3
+ * FIXED: Supports Sharded/Batched Execution.
4
+ * MOVED: Sector aggregation logic moved to getResult() to handle sharding correctly.
4
5
  */
5
6
  const RANGES = [7, 30, 90, 365];
6
7
  const TRADING_DAYS_PER_YEAR = 252;
@@ -8,6 +9,8 @@ const TRADING_DAYS_PER_YEAR = 252;
8
9
  class CorePriceMetrics {
9
10
  constructor() {
10
11
  this.result = { by_instrument: {}, by_sector: {} };
12
+ // We persist mappings here because we process shard-by-shard
13
+ this.mappings = null;
11
14
  }
12
15
 
13
16
  static getMetadata() {
@@ -87,25 +90,22 @@ class CorePriceMetrics {
87
90
 
88
91
  process(context) {
89
92
  const { mappings, prices, date } = context;
90
- // FIX 1: Use the correct property name 'instrumentToSector'
91
- const { instrumentToTicker, instrumentToSector } = mappings;
93
+ const { instrumentToTicker } = mappings;
94
+
95
+ // Save mappings for the final aggregation step
96
+ if (!this.mappings) this.mappings = mappings;
92
97
 
93
98
  const priceData = prices?.history;
94
99
  const todayDateStr = date.today;
95
100
 
96
101
  if (!priceData || !todayDateStr) {
97
- this.result = { by_instrument: {}, by_sector: {} };
98
102
  return;
99
103
  }
100
104
 
101
- const by_instrument = {};
102
- const tickerToInstrument = {};
103
-
104
- // FIX 2: Iterate over Object.values() since priceData is a map, not an array
105
+ // Iterate over THIS SHARD'S data
105
106
  for (const p of Object.values(priceData)) {
106
107
  const ticker = instrumentToTicker[p.instrumentId];
107
108
  if (!ticker) continue;
108
- tickerToInstrument[ticker] = p.instrumentId;
109
109
 
110
110
  const metrics = {};
111
111
  for (const range of RANGES) {
@@ -119,14 +119,26 @@ class CorePriceMetrics {
119
119
  ? (stats.mean / stats.stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR)
120
120
  : 0;
121
121
  }
122
- by_instrument[ticker] = metrics;
122
+ // Accumulate into by_instrument
123
+ this.result.by_instrument[ticker] = metrics;
124
+ }
125
+ }
126
+
127
+ async getResult() {
128
+ // Perform Sector Aggregation HERE (after all shards are processed)
129
+ const by_instrument = this.result.by_instrument;
130
+ const instrumentToSector = this.mappings?.instrumentToSector || {};
131
+ const instrumentToTicker = this.mappings?.instrumentToTicker || {};
132
+
133
+ // Reverse map ticker -> instrumentId
134
+ const tickerToInstrument = {};
135
+ for(const [id, tick] of Object.entries(instrumentToTicker)) {
136
+ tickerToInstrument[tick] = id;
123
137
  }
124
138
 
125
- // Sector Aggregation
126
139
  const sectorAggs = {};
127
140
  for (const ticker in by_instrument) {
128
141
  const instId = tickerToInstrument[ticker];
129
- // FIX 1 (Usage): Use the corrected variable name
130
142
  const sector = instrumentToSector[instId] || "Unknown";
131
143
 
132
144
  if (!sectorAggs[sector]) sectorAggs[sector] = { metrics: {}, counts: {} };
@@ -152,11 +164,14 @@ class CorePriceMetrics {
152
164
  by_sector[sector][`average_${key}`] = count > 0 ? sectorAggs[sector].metrics[key] / count : null;
153
165
  }
154
166
  }
155
-
156
- this.result = { by_instrument, by_sector };
167
+
168
+ this.result.by_sector = by_sector;
169
+ return this.result;
157
170
  }
158
171
 
159
- async getResult() { return this.result; }
160
- reset() { this.result = { by_instrument: {}, by_sector: {} }; }
172
+ reset() {
173
+ this.result = { by_instrument: {}, by_sector: {} };
174
+ this.mappings = null;
175
+ }
161
176
  }
162
177
  module.exports = CorePriceMetrics;
@@ -7,7 +7,7 @@ class CostBasisDensity {
7
7
 
8
8
  static getMetadata() {
9
9
  return {
10
- type: 'meta',
10
+ type: 'meta', // Runs ONCE per day
11
11
  dependencies: ['asset-cost-basis-profile'],
12
12
  category: 'ghost_book'
13
13
  };
@@ -35,11 +35,17 @@ class CostBasisDensity {
35
35
  const { computed, math } = context;
36
36
  const { signals: SignalPrimitives } = math;
37
37
 
38
+ // 1. Get Union of Tickers (Safe Iteration)
38
39
  const tickers = SignalPrimitives.getUnionKeys(computed, ['asset-cost-basis-profile']);
39
40
 
40
41
  for (const ticker of tickers) {
42
+ // 2. Safe Data Access
41
43
  const data = computed['asset-cost-basis-profile'][ticker];
42
- if (!data || !data.profile) continue;
44
+
45
+ // Check for 'profile' specifically as it contains the density curve
46
+ if (!data || !Array.isArray(data.profile) || data.profile.length < 3) {
47
+ continue;
48
+ }
43
49
 
44
50
  const profile = data.profile; // Array of {price, density}
45
51
  const currentPrice = data.current_price;
@@ -48,26 +54,42 @@ class CostBasisDensity {
48
54
  const support = [];
49
55
  let maxDensity = 0;
50
56
 
51
- // Find Local Maxima in the Density Curve
57
+ // 3. Peak Detection Algorithm
58
+ // Iterate through the KDE curve to find local maxima
52
59
  for (let i = 1; i < profile.length - 1; i++) {
53
60
  const prev = profile[i-1].density;
54
61
  const curr = profile[i].density;
55
62
  const next = profile[i+1].density;
56
63
 
64
+ // Simple Peak Check
57
65
  if (curr > prev && curr > next) {
58
- // We have a peak (Wall)
66
+ const priceVal = Number(profile[i].price.toFixed(2));
67
+
68
+ // Classify as Resistance (Overhead Supply) or Support (Underlying Demand)
59
69
  if (profile[i].price > currentPrice) {
60
- resistance.push(Number(profile[i].price.toFixed(2)));
70
+ resistance.push(priceVal);
61
71
  } else {
62
- support.push(Number(profile[i].price.toFixed(2)));
72
+ support.push(priceVal);
63
73
  }
74
+
64
75
  if (curr > maxDensity) maxDensity = curr;
65
76
  }
66
77
  }
67
78
 
79
+ // 4. Sort Walls by proximity to current price?
80
+ // Currently slice(0,3) takes the first found, which are lower prices in a sorted KDE.
81
+ // Support: We want HIGHEST prices below current (closest to current).
82
+ // Resistance: We want LOWEST prices above current (closest to current).
83
+
84
+ // Sort Descending (Highest Price First)
85
+ support.sort((a, b) => b - a);
86
+
87
+ // Sort Ascending (Lowest Price First)
88
+ resistance.sort((a, b) => a - b);
89
+
68
90
  this.walls[ticker] = {
69
- resistance_zones: resistance.slice(0, 3), // Top 3
70
- support_zones: support.slice(0, 3),
91
+ resistance_zones: resistance.slice(0, 3), // Closest 3 resistance levels
92
+ support_zones: support.slice(0, 3), // Closest 3 support levels
71
93
  nearest_wall_strength: Number(maxDensity.toFixed(4))
72
94
  };
73
95
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
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.
3
+ * Measures the lag between "Smart Money" flow and "Herd" conviction.
4
+ * Self-healing state: Warms up over 15-30 days automatically.
4
5
  */
5
6
  class MimeticLatencyOscillator {
6
7
  constructor() {
@@ -44,12 +45,10 @@ class MimeticLatencyOscillator {
44
45
 
45
46
  for (const ticker of tickers) {
46
47
  // 1. Inputs
47
- // 'skilled-cohort-flow' -> net_flow_pct
48
48
  const rawFlow = SignalPrimitives.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
49
- // 'herd-consensus-score' -> herd_conviction_score
50
49
  const rawHerd = SignalPrimitives.getMetric(computed, 'herd-consensus-score', ticker, 'herd_conviction_score', 0);
51
50
 
52
- // 2. Restore State
51
+ // 2. Restore State (Handle Cold Start)
53
52
  const prevResult = SignalPrimitives.getPreviousState(previousComputed, 'mimetic-latency', ticker);
54
53
  const prevState = prevResult ? prevResult._state : { flow_buffer: [], herd_buffer: [], last_flow: 0, last_herd: 0 };
55
54
 
@@ -67,20 +66,20 @@ class MimeticLatencyOscillator {
67
66
  flowBuffer.push(flowDelta);
68
67
  herdBuffer.push(herdDelta);
69
68
 
69
+ // Cap memory usage
70
70
  if (flowBuffer.length > this.windowSize) flowBuffer.shift();
71
71
  if (herdBuffer.length > this.windowSize) herdBuffer.shift();
72
72
 
73
73
  // 5. Lagged Cross-Correlation
74
74
  let maxCorr = -1.0;
75
75
  let bestLag = 0;
76
+ let regime = "WARM_UP";
76
77
 
78
+ // Only compute if we have statistical significance (>= 15 samples)
77
79
  if (flowBuffer.length >= 15) {
78
80
  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
81
  const len = flowBuffer.length;
83
- if (len - k < 5) continue; // Min sample check
82
+ if (len - k < 5) continue;
84
83
 
85
84
  const slicedFlow = flowBuffer.slice(0, len - k);
86
85
  const slicedHerd = herdBuffer.slice(k, len);
@@ -92,16 +91,15 @@ class MimeticLatencyOscillator {
92
91
  bestLag = k;
93
92
  }
94
93
  }
95
- }
96
94
 
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";
95
+ // 6. Regime Classification
96
+ if (maxCorr > 0.3) {
97
+ if (bestLag >= 3) regime = "EARLY_ALPHA";
98
+ else if (bestLag >= 1) regime = "MARKUP";
99
+ else regime = "FOMO_TRAP";
100
+ } else {
101
+ regime = "DECOUPLING";
102
+ }
105
103
  }
106
104
 
107
105
  this.mloResults[ticker] = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.92",
3
+ "version": "1.0.94",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [