aiden-shared-calculations-unified 1.0.102 → 1.0.103
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,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;
|