aiden-shared-calculations-unified 1.0.102 → 1.0.104

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,72 @@
1
+ class CrowdCostBasis {
2
+ constructor() {
3
+ this.results = {};
4
+ }
5
+
6
+ static getMetadata() {
7
+ return {
8
+ name: 'crowd-cost-basis',
9
+ type: 'meta',
10
+ category: 'History Reconstruction',
11
+ userType: 'n/a', // <--- Works on dependency results
12
+ isHistorical: false,
13
+ rootDataDependencies: ['price']
14
+ };
15
+ }
16
+
17
+ static getDependencies() {
18
+ return ['user-history-reconstructor'];
19
+ }
20
+
21
+ async process(context) {
22
+ const { computed, prices, math } = context;
23
+
24
+ // 1. Access the output of the Standard calculation
25
+ const userReconstructions = computed['user-history-reconstructor'];
26
+ if (!userReconstructions) return;
27
+
28
+ const aggregator = {}; // { AAPL: { sumEntry: 0, count: 0 } }
29
+
30
+ // 2. Iterate over all users' reconstructed states
31
+ for (const userId in userReconstructions) {
32
+ const userPortfolio = userReconstructions[userId];
33
+
34
+ for (const ticker in userPortfolio) {
35
+ const position = userPortfolio[ticker];
36
+
37
+ if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
38
+
39
+ // Aggregating global average entry
40
+ aggregator[ticker].sumEntry += position.avgEntry;
41
+ aggregator[ticker].count++;
42
+ }
43
+ }
44
+
45
+ // 3. Compute Global Average vs Current Price
46
+ for (const ticker in aggregator) {
47
+ const data = aggregator[ticker];
48
+ if (data.count < 10) continue; // Noise filter
49
+
50
+ const globalAvgEntry = data.sumEntry / data.count;
51
+
52
+ // Get today's closing price
53
+ const priceHistory = math.priceExtractor.getHistory(prices, ticker);
54
+ // The last item in price history for this context is "Today"
55
+ const lastPriceObj = priceHistory[priceHistory.length - 1];
56
+ const currentPrice = lastPriceObj ? lastPriceObj.price : globalAvgEntry;
57
+
58
+ const diffPct = ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100;
59
+
60
+ this.results[ticker] = {
61
+ avgEntry: globalAvgEntry,
62
+ holderCount: data.count,
63
+ profitabilityPct: diffPct,
64
+ state: diffPct > 0 ? 'PROFIT_SUPPORT' : 'LOSS_RESISTANCE'
65
+ };
66
+ }
67
+ }
68
+
69
+ async getResult() { return this.results; }
70
+ }
71
+
72
+ module.exports = CrowdCostBasis;
@@ -0,0 +1,78 @@
1
+ class LeverageDivergence {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'leverage-divergence',
7
+ type: 'meta',
8
+ category: 'History Reconstruction',
9
+ userType: 'n/a',
10
+ isHistorical: true, // <--- Needs yesterday's result
11
+ rootDataDependencies: []
12
+ };
13
+ }
14
+
15
+ static getDependencies() {
16
+ return ['user-history-reconstructor'];
17
+ }
18
+
19
+ async process(context) {
20
+ const { computed, previousComputed } = context;
21
+
22
+ const currentReconstruction = computed['user-history-reconstructor'];
23
+ // Access SELF from yesterday
24
+ const previousResult = previousComputed['leverage-divergence'];
25
+
26
+ const currentAgg = {}; // { AAPL: { levHolders: 0, spotHolders: 0 } }
27
+
28
+ // 1. Build Today's Aggregates
29
+ for (const userId in currentReconstruction) {
30
+ const userPortfolio = currentReconstruction[userId];
31
+ for (const ticker in userPortfolio) {
32
+ const pos = userPortfolio[ticker];
33
+
34
+ if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
35
+
36
+ // If avg leverage > 1.1, count as "Leveraged Holder"
37
+ if (pos.avgLeverage > 1.1) {
38
+ currentAgg[ticker].levHolders++;
39
+ } else {
40
+ currentAgg[ticker].spotHolders++;
41
+ }
42
+ }
43
+ }
44
+
45
+ // 2. Compare with Yesterday
46
+ for (const ticker in currentAgg) {
47
+ const curr = currentAgg[ticker];
48
+ const prev = previousResult ? previousResult[ticker] : null;
49
+
50
+ if (!prev) {
51
+ this.results[ticker] = { ...curr, levDelta: 0, spotDelta: 0, signal: 'NEW' };
52
+ continue;
53
+ }
54
+
55
+ const levDelta = curr.levHolders - prev.levHolders;
56
+ const spotDelta = curr.spotHolders - prev.spotHolders;
57
+
58
+ let signal = 'NEUTRAL';
59
+
60
+ // Retail (Spot) Buying + Speculators (Lev) Selling = Smart Money Exit
61
+ if (spotDelta > 0 && levDelta < 0) signal = 'SMART_EXIT';
62
+
63
+ // Retail (Spot) Selling + Speculators (Lev) Buying = High Conviction Pump
64
+ if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
65
+
66
+ this.results[ticker] = {
67
+ ...curr,
68
+ levDelta,
69
+ spotDelta,
70
+ signal
71
+ };
72
+ }
73
+ }
74
+
75
+ async getResult() { return this.results; }
76
+ }
77
+
78
+ module.exports = LeverageDivergence;
@@ -0,0 +1,61 @@
1
+ class LiquidationCascade {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'liquidation-cascade',
7
+ type: 'meta',
8
+ category: 'History Reconstruction',
9
+ userType: 'n/a',
10
+ isHistorical: false,
11
+ rootDataDependencies: []
12
+ };
13
+ }
14
+
15
+ static getDependencies() {
16
+ return ['user-history-reconstructor'];
17
+ }
18
+
19
+ async process(context) {
20
+ const { computed } = context;
21
+ const userReconstructions = computed['user-history-reconstructor'];
22
+ if (!userReconstructions) return;
23
+
24
+ const aggregator = {};
25
+
26
+ // 1. Aggregate Forced Exits
27
+ for (const userId in userReconstructions) {
28
+ const userPortfolio = userReconstructions[userId];
29
+
30
+ for (const ticker in userPortfolio) {
31
+ const position = userPortfolio[ticker];
32
+
33
+ if (position.closedToday > 0) {
34
+ if (!aggregator[ticker]) aggregator[ticker] = { closed: 0, forced: 0 };
35
+
36
+ aggregator[ticker].closed += position.closedToday;
37
+ aggregator[ticker].forced += position.forcedExits;
38
+ }
39
+ }
40
+ }
41
+
42
+ // 2. Calculate Pain Index
43
+ for (const ticker in aggregator) {
44
+ const data = aggregator[ticker];
45
+ if (data.closed < 5) continue;
46
+
47
+ const forcedRatio = data.forced / data.closed;
48
+
49
+ this.results[ticker] = {
50
+ totalClosures: data.closed,
51
+ forcedClosures: data.forced,
52
+ painIndex: forcedRatio, // 0.0 to 1.0
53
+ isFlushEvent: forcedRatio > 0.3 // Flag if >30% of selling was forced
54
+ };
55
+ }
56
+ }
57
+
58
+ async getResult() { return this.results; }
59
+ }
60
+
61
+ module.exports = LiquidationCascade;
@@ -0,0 +1,90 @@
1
+ class OwnershipVsPerformanceYTD {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'ownership-vs-performance-ytd',
7
+ type: 'meta',
8
+ category: 'core',
9
+ userType: 'n/a',
10
+ isHistorical: true, // Required to carry forward Jan 1st baseline
11
+ rootDataDependencies: ['insights', 'price']
12
+ };
13
+ }
14
+
15
+ static getDependencies() { return []; }
16
+
17
+ async process(context) {
18
+ const { insights: insightsHelper, priceExtractor } = context.math;
19
+ const { previousComputed, mappings, prices, date } = context;
20
+
21
+ const dailyInsights = insightsHelper.getInsights(context, 'today');
22
+
23
+ // 1. Manage Baseline State (Jan 1st snapshot)
24
+ // If today is Jan 1st (or first run), we reset. Otherwise, we load from yesterday.
25
+ let baselineState = previousComputed['ownership-vs-performance-ytd']?.baselines || {};
26
+ const isStartOfYear = date.today.endsWith('-01-01') || date.today.endsWith('-01-02');
27
+
28
+ if (isStartOfYear) {
29
+ baselineState = {}; // Reset for new year
30
+ }
31
+
32
+ const currentMetrics = {};
33
+
34
+ for (const insight of dailyInsights) {
35
+ const instId = insight.instrumentId;
36
+ const ticker = mappings.instrumentToTicker[instId];
37
+ if (!ticker) continue;
38
+
39
+ const currentOwners = insightsHelper.getTotalOwners(insight);
40
+
41
+ // Get Price History
42
+ const priceHist = priceExtractor.getHistory(prices, ticker);
43
+ if (!priceHist.length) continue;
44
+
45
+ const currentPrice = priceHist[priceHist.length - 1].price;
46
+
47
+ // 2. Set Baseline if missing
48
+ if (!baselineState[ticker]) {
49
+ baselineState[ticker] = {
50
+ startPrice: currentPrice,
51
+ startOwners: currentOwners,
52
+ date: date.today
53
+ };
54
+ }
55
+
56
+ const base = baselineState[ticker];
57
+
58
+ // 3. Calculate Deltas
59
+ const priceYtd = base.startPrice > 0
60
+ ? ((currentPrice - base.startPrice) / base.startPrice) * 100
61
+ : 0;
62
+
63
+ const ownersYtd = base.startOwners > 0
64
+ ? ((currentOwners - base.startOwners) / base.startOwners) * 100
65
+ : 0;
66
+
67
+ currentMetrics[ticker] = {
68
+ priceYtd,
69
+ ownersYtd,
70
+ currentPrice,
71
+ currentOwners
72
+ };
73
+ }
74
+
75
+ // 4. Output structure includes the baselines so they are saved for tomorrow
76
+ this.results = {
77
+ metrics: currentMetrics,
78
+ baselines: baselineState
79
+ };
80
+ }
81
+
82
+ async getResult() {
83
+ // We flatten the 'metrics' for easy API consumption,
84
+ // but we must ensure 'baselines' is preserved in the stored document
85
+ // so 'previousComputed' picks it up tomorrow.
86
+ return { ...this.results.metrics, baselines: this.results.baselines };
87
+ }
88
+ }
89
+
90
+ module.exports = OwnershipVsPerformanceYTD;
@@ -0,0 +1,69 @@
1
+ class OwnershipVsVolatility {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'ownership-vs-volatility',
7
+ type: 'meta',
8
+ category: 'core',
9
+ userType: 'n/a',
10
+ isHistorical: true, // To calculate ownership change over window
11
+ rootDataDependencies: ['insights', 'price']
12
+ };
13
+ }
14
+
15
+ static getDependencies() { return []; }
16
+
17
+ async process(context) {
18
+ const { insights: insightsHelper, priceExtractor, compute } = context.math;
19
+ const { previousComputed, mappings, prices } = context;
20
+
21
+ const dailyInsights = insightsHelper.getInsights(context, 'today');
22
+
23
+ // Use 7-day lookback for ownership change
24
+ const prevState = previousComputed['ownership-vs-volatility'] || {};
25
+
26
+ for (const insight of dailyInsights) {
27
+ const instId = insight.instrumentId;
28
+ const ticker = mappings.instrumentToTicker[instId];
29
+ if (!ticker) continue;
30
+
31
+ // 1. Calculate Recent Volatility (14 Day StdDev of Returns)
32
+ const priceHist = priceExtractor.getHistory(prices, ticker);
33
+ if (priceHist.length < 14) continue;
34
+
35
+ const recentPrices = priceHist.slice(-14);
36
+ const returns = [];
37
+ for (let i = 1; i < recentPrices.length; i++) {
38
+ returns.push((recentPrices[i].price - recentPrices[i-1].price) / recentPrices[i-1].price);
39
+ }
40
+
41
+ const volatility = compute.standardDeviation(returns); // Raw std dev
42
+
43
+ // 2. Calculate Ownership Change (Current vs 7 days ago stored state)
44
+ const currentOwners = insightsHelper.getTotalOwners(insight);
45
+ const prevOwners = prevState[ticker]?.laggedOwners || currentOwners;
46
+
47
+ const ownerChangePct = prevOwners > 0
48
+ ? ((currentOwners - prevOwners) / prevOwners) * 100
49
+ : 0;
50
+
51
+ this.results[ticker] = {
52
+ volatilityScore: volatility,
53
+ ownerChange7d: ownerChangePct,
54
+ // Store current owners as 'laggedOwners' for next week?
55
+ // Actually, for daily rolling 7d change, we need a queue.
56
+ // Simplified: We store today's value, and the API/UI compares.
57
+ // Better: Use a simple rolling queue like the Momentum calc.
58
+ _history: [...(prevState[ticker]?._history || []), currentOwners].slice(-7),
59
+
60
+ // For the output: Calculate change vs the oldest in history (approx 7d ago)
61
+ laggedOwners: (prevState[ticker]?._history?.[0] || currentOwners)
62
+ };
63
+ }
64
+ }
65
+
66
+ async getResult() { return this.results; }
67
+ }
68
+
69
+ module.exports = OwnershipVsVolatility;
@@ -0,0 +1,69 @@
1
+ class ShortInterestGrowth {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'short-interest-growth',
7
+ type: 'meta',
8
+ category: 'core',
9
+ userType: 'n/a',
10
+ isHistorical: true,
11
+ rootDataDependencies: ['insights']
12
+ };
13
+ }
14
+
15
+ static getDependencies() { return []; }
16
+
17
+ static getSchema() {
18
+ return {
19
+ "TICKER": {
20
+ "shortCount": 0,
21
+ "shortSparkline": [0, 0, 0, 0, 0, 0, 0],
22
+ "growth7d": 0.0
23
+ }
24
+ };
25
+ }
26
+
27
+ async process(context) {
28
+ const { insights: insightsHelper } = context.math;
29
+ const { previousComputed, mappings } = context;
30
+
31
+ const dailyInsights = insightsHelper.getInsights(context, 'today');
32
+ const previousState = previousComputed['short-interest-growth'] || {};
33
+
34
+ for (const insight of dailyInsights) {
35
+ const instId = insight.instrumentId;
36
+ const ticker = mappings.instrumentToTicker[instId];
37
+ if (!ticker) continue;
38
+
39
+ // Calculate Short Count: Total Owners * (Sell % / 100)
40
+ const shortCount = insightsHelper.getShortCount(insight);
41
+
42
+ const prevState = previousState[ticker] || { shortSparkline: [] };
43
+ const sparkline = [...(prevState.shortSparkline || [])];
44
+
45
+ sparkline.push(shortCount);
46
+ if (sparkline.length > 7) sparkline.shift();
47
+
48
+ // Calculate simple 7-day growth %
49
+ let growth7d = 0;
50
+ if (sparkline.length > 1) {
51
+ const start = sparkline[0];
52
+ const end = sparkline[sparkline.length - 1];
53
+ if (start > 0) {
54
+ growth7d = ((end - start) / start) * 100;
55
+ }
56
+ }
57
+
58
+ this.results[ticker] = {
59
+ shortCount,
60
+ shortSparkline: sparkline,
61
+ growth7d
62
+ };
63
+ }
64
+ }
65
+
66
+ async getResult() { return this.results; }
67
+ }
68
+
69
+ module.exports = ShortInterestGrowth;
@@ -0,0 +1,87 @@
1
+ class TrendingOwnershipMomentum {
2
+ constructor() { this.results = {}; }
3
+
4
+ static getMetadata() {
5
+ return {
6
+ name: 'trending-ownership-momentum',
7
+ type: 'meta',
8
+ category: 'core',
9
+ userType: 'n/a',
10
+ isHistorical: true, // Needed for rolling history
11
+ rootDataDependencies: ['insights']
12
+ };
13
+ }
14
+
15
+ static getDependencies() { return []; }
16
+
17
+ static getSchema() {
18
+ return {
19
+ "TICKER": {
20
+ "currentOwners": 0,
21
+ "sparkline": [0, 0, 0, 0, 0, 0, 0], // 7-Day History
22
+ "momentumScore": 0.0, // Slope of the trend
23
+ "trend": "string" // 'RISING', 'FALLING', 'STABLE'
24
+ }
25
+ };
26
+ }
27
+
28
+ async process(context) {
29
+ const { insights: insightsHelper } = context.math;
30
+ const { previousComputed, mappings } = context;
31
+
32
+ // 1. Get Today's Data
33
+ const dailyInsights = insightsHelper.getInsights(context, 'today');
34
+ if (!dailyInsights.length) return;
35
+
36
+ // 2. Get Yesterday's State (for rolling history)
37
+ const previousState = previousComputed['trending-ownership-momentum'] || {};
38
+
39
+ for (const insight of dailyInsights) {
40
+ const instId = insight.instrumentId;
41
+ const ticker = mappings.instrumentToTicker[instId];
42
+ if (!ticker) continue;
43
+
44
+ const currentCount = insightsHelper.getTotalOwners(insight);
45
+ const prevState = previousState[ticker] || { sparkline: [] };
46
+
47
+ // 3. Update Rolling Window (Last 7 Days)
48
+ // Create new array copy to avoid mutation issues
49
+ const sparkline = [...(prevState.sparkline || [])];
50
+ sparkline.push(currentCount);
51
+
52
+ // Keep only last 7 entries
53
+ if (sparkline.length > 7) sparkline.shift();
54
+
55
+ // 4. Calculate Momentum Score (Linear Regression Slope)
56
+ let momentumScore = 0;
57
+ if (sparkline.length >= 2) {
58
+ const n = sparkline.length;
59
+ let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
60
+ for (let i = 0; i < n; i++) {
61
+ sumX += i;
62
+ sumY += sparkline[i];
63
+ sumXY += i * sparkline[i];
64
+ sumXX += i * i;
65
+ }
66
+ const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
67
+ // Normalize slope by current count to get percentage-like momentum
68
+ momentumScore = currentCount > 0 ? (slope / currentCount) * 100 : 0;
69
+ }
70
+
71
+ let trend = 'STABLE';
72
+ if (momentumScore > 0.5) trend = 'RISING';
73
+ if (momentumScore < -0.5) trend = 'FALLING';
74
+
75
+ this.results[ticker] = {
76
+ currentOwners: currentCount,
77
+ sparkline,
78
+ momentumScore,
79
+ trend
80
+ };
81
+ }
82
+ }
83
+
84
+ async getResult() { return this.results; }
85
+ }
86
+
87
+ module.exports = TrendingOwnershipMomentum;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @fileoverview Reconstructs a user's portfolio state for a specific date from their trade history.
3
+ * Acts as the "Map" phase for downstream Meta aggregations.
4
+ */
5
+ class UserHistoryReconstructor {
6
+ constructor() {
7
+ this.results = {}; // Output: { "user_id": { "AAPL": { ...stats... } } }
8
+ }
9
+
10
+ static getMetadata() {
11
+ return {
12
+ name: 'user-history-reconstructor',
13
+ type: 'standard',
14
+ category: 'History Reconstruction', // <--- Specific Category
15
+ userType: 'all', // <--- Processes User Data
16
+ isHistorical: false,
17
+ rootDataDependencies: ['history']
18
+ };
19
+ }
20
+
21
+ static getDependencies() { return []; }
22
+
23
+ static getSchema() {
24
+ return {
25
+ "USER_ID": {
26
+ "TICKER": {
27
+ "activeCount": 1,
28
+ "avgEntry": 0.0,
29
+ "avgLeverage": 1.0,
30
+ "closedToday": 0,
31
+ "forcedExits": 0
32
+ }
33
+ }
34
+ };
35
+ }
36
+
37
+ async process(context) {
38
+ const { user, math, mappings, date } = context;
39
+
40
+ // 1. Get History
41
+ const history = math.history.getDailyHistory(user);
42
+ const allTrades = history?.PublicHistoryPositions || [];
43
+
44
+ if (allTrades.length === 0) return;
45
+
46
+ // 2. Filter for Active Trades on this Date
47
+ const activeTrades = math.history.getActiveTradesForDate(allTrades, date.today);
48
+ if (activeTrades.length === 0) return;
49
+
50
+ // 3. Initialize User State
51
+ this.results[user.id] = {};
52
+ const userState = this.results[user.id];
53
+
54
+ const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
55
+ const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
56
+
57
+ for (const trade of activeTrades) {
58
+ const instId = trade.InstrumentID;
59
+ const ticker = mappings.instrumentToTicker[instId];
60
+ if (!ticker) continue;
61
+
62
+ if (!userState[ticker]) {
63
+ userState[ticker] = {
64
+ activeCount: 0,
65
+ totalEntry: 0,
66
+ totalLev: 0,
67
+ closedToday: 0,
68
+ forcedExits: 0
69
+ };
70
+ }
71
+
72
+ const stats = userState[ticker];
73
+
74
+ // Accumulate Holding State
75
+ stats.activeCount++;
76
+ stats.totalEntry += (trade.OpenRate || 0);
77
+ stats.totalLev += (trade.Leverage || 1);
78
+
79
+ // Check for Closure Events happening TODAY
80
+ if (trade.CloseDateTime) {
81
+ const closeTime = new Date(trade.CloseDateTime).getTime();
82
+ if (closeTime >= dayStart && closeTime <= dayEnd) {
83
+ stats.closedToday++;
84
+ // CloseReason: 1 = Stop Loss, 5 = Take Profit, 0 = Manual
85
+ if (trade.CloseReason === 1) stats.forcedExits++;
86
+ }
87
+ }
88
+ }
89
+
90
+ // 4. Finalize Averages for this User
91
+ for (const ticker in userState) {
92
+ const stats = userState[ticker];
93
+ if (stats.activeCount > 0) {
94
+ stats.avgEntry = stats.totalEntry / stats.activeCount;
95
+ stats.avgLeverage = stats.totalLev / stats.activeCount;
96
+ }
97
+ // Cleanup intermediate sums
98
+ delete stats.totalEntry;
99
+ delete stats.totalLev;
100
+ }
101
+ }
102
+
103
+ async getResult() {
104
+ return this.results;
105
+ }
106
+ }
107
+
108
+ module.exports = UserHistoryReconstructor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.102",
3
+ "version": "1.0.104",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [