aiden-shared-calculations-unified 1.0.105 → 1.0.107

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,83 +1,110 @@
1
1
  class CrowdCostBasis {
2
- constructor() {
3
- this.results = {};
4
- }
2
+ constructor() { this.results = {}; }
5
3
 
6
4
  static getMetadata() {
7
5
  return {
8
6
  name: 'crowd-cost-basis',
9
7
  type: 'meta',
10
8
  category: 'History Reconstruction',
11
- userType: 'n/a', // <--- Works on dependency results
12
- isHistorical: false,
9
+ userType: 'n/a',
10
+ isHistorical: false, // Self-contained history generation
13
11
  rootDataDependencies: ['price']
14
12
  };
15
13
  }
16
14
 
17
- static getDependencies() {
18
- return ['user-history-reconstructor'];
19
- }
15
+ static getDependencies() { return ['user-history-reconstructor']; }
20
16
 
21
17
  static getSchema() {
22
- const schema = {
18
+ const dailySchema = {
23
19
  "type": "object",
24
20
  "properties": {
25
- "avgEntry": { "type": "number" },
26
- "holderCount": { "type": "number" },
27
- "profitabilityPct": { "type": "number" },
28
- "state": { "type": "string" }
21
+ "avgEntry": { "type": "number", "description": "Global average entry price for all holders." },
22
+ "holderCount": { "type": "number", "description": "Total number of users holding the asset." },
23
+ "profitabilityPct": { "type": "number", "description": "Percentage distance between current price and avg entry." },
24
+ "state": { "type": "string", "description": "Profitability state: PROFIT_SUPPORT or LOSS_RESISTANCE." }
29
25
  },
30
26
  "required": ["avgEntry", "holderCount", "profitabilityPct", "state"]
31
27
  };
32
- return { "type": "object", "patternProperties": { "^.*$": schema } };
28
+
29
+ // Output: Date -> Ticker -> Schema
30
+ return {
31
+ "type": "object",
32
+ "patternProperties": {
33
+ "^\\d{4}-\\d{2}-\\d{2}$": {
34
+ "type": "object",
35
+ "patternProperties": { "^.*$": dailySchema }
36
+ }
37
+ }
38
+ };
33
39
  }
34
40
 
35
41
  async process(context) {
36
42
  const { computed, prices, math } = context;
43
+ const historyData = computed['user-history-reconstructor'];
44
+ if (!historyData) return;
45
+
46
+ // Detect Structure: Date-First (Time Machine) or User-First (Legacy)
47
+ // With the new StandardExecutor refactor, it should be Date -> User -> Ticker
48
+ const keys = Object.keys(historyData);
49
+ const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
50
+
51
+ if (isDateKeyed) {
52
+ // Process All Dates in the Map
53
+ for (const dateStr of keys) {
54
+ this.results[dateStr] = this.processSingleDate(dateStr, historyData[dateStr], prices, math);
55
+ }
56
+ } else {
57
+ // Fallback for single-date/legacy context
58
+ const dateStr = context.date.today;
59
+ this.results[dateStr] = this.processSingleDate(dateStr, historyData, prices, math);
60
+ }
61
+ }
37
62
 
38
- // 1. Access the output of the Standard calculation
39
- const userReconstructions = computed['user-history-reconstructor'];
40
- if (!userReconstructions) return;
41
-
42
- const aggregator = {}; // { AAPL: { sumEntry: 0, count: 0 } }
63
+ processSingleDate(dateStr, usersMap, prices, math) {
64
+ const aggregator = {};
65
+ const dailyResults = {};
43
66
 
44
- // 2. Iterate over all users' reconstructed states
45
- for (const userId in userReconstructions) {
46
- const userPortfolio = userReconstructions[userId];
67
+ // 1. Iterate Users for this Date
68
+ for (const userId in usersMap) {
69
+ const userPortfolio = usersMap[userId];
47
70
 
48
71
  for (const ticker in userPortfolio) {
49
- const position = userPortfolio[ticker];
50
-
51
- if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
72
+ const stats = userPortfolio[ticker];
52
73
 
53
- // Aggregating global average entry
54
- aggregator[ticker].sumEntry += position.avgEntry;
55
- aggregator[ticker].count++;
74
+ if (stats.isHolder === 1) {
75
+ if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
76
+ aggregator[ticker].sumEntry += stats.avgEntry;
77
+ aggregator[ticker].count++;
78
+ }
56
79
  }
57
80
  }
58
81
 
59
- // 3. Compute Global Average vs Current Price
82
+ // 2. Compute Global Cost Basis
60
83
  for (const ticker in aggregator) {
61
84
  const data = aggregator[ticker];
62
- if (data.count < 10) continue; // Noise filter
85
+ if (data.count < 3) continue; // Noise filter
63
86
 
64
87
  const globalAvgEntry = data.sumEntry / data.count;
65
88
 
66
- // Get today's closing price
89
+ // Get Price for this specific date
90
+ // Note: prices.history might be sharded, assuming ContextFactory loaded relevant shards
67
91
  const priceHistory = math.priceExtractor.getHistory(prices, ticker);
68
- // The last item in price history for this context is "Today"
69
- const lastPriceObj = priceHistory[priceHistory.length - 1];
70
- const currentPrice = lastPriceObj ? lastPriceObj.price : globalAvgEntry;
92
+
93
+ // Find price closest to this date
94
+ // Optimization: In a real "Time Machine" run, priceExtractor should probably be a map lookup
95
+ const dayPriceObj = priceHistory.find(p => p.date === dateStr);
96
+ const currentPrice = dayPriceObj ? dayPriceObj.price : globalAvgEntry;
71
97
 
72
- const diffPct = ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100;
98
+ const diffPct = globalAvgEntry > 0 ? ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100 : 0;
73
99
 
74
- this.results[ticker] = {
100
+ dailyResults[ticker] = {
75
101
  avgEntry: globalAvgEntry,
76
102
  holderCount: data.count,
77
103
  profitabilityPct: diffPct,
78
104
  state: diffPct > 0 ? 'PROFIT_SUPPORT' : 'LOSS_RESISTANCE'
79
105
  };
80
106
  }
107
+ return dailyResults;
81
108
  }
82
109
 
83
110
  async getResult() { return this.results; }
@@ -7,84 +7,119 @@ class LeverageDivergence {
7
7
  type: 'meta',
8
8
  category: 'History Reconstruction',
9
9
  userType: 'n/a',
10
- isHistorical: true, // <--- Needs yesterday's result
11
- rootDataDependencies: []
10
+ isHistorical: false, // We handle history internally now
11
+ rootDataDependencies: ['price'] // Needed for Smart/Dumb PnL calc
12
12
  };
13
13
  }
14
14
 
15
- static getDependencies() {
16
- return ['user-history-reconstructor'];
17
- }
15
+ static getDependencies() { return ['user-history-reconstructor']; }
18
16
 
19
17
  static getSchema() {
20
- const schema = {
18
+ const dailySchema = {
21
19
  "type": "object",
22
20
  "properties": {
23
- "levHolders": { "type": "number" },
24
- "spotHolders": { "type": "number" },
25
- "levDelta": { "type": "number" },
26
- "spotDelta": { "type": "number" },
27
- "signal": { "type": "string" }
21
+ "smartAccumulation": { "type": "number", "description": "Buy volume from profitable users." },
22
+ "dumbAccumulation": { "type": "number", "description": "Buy volume from unprofitable/high-lev users." },
23
+ "netFlow": { "type": "number", "description": "Net buy/sell activity." },
24
+ "signal": { "type": "string", "description": "Divergence signal: NEUTRAL, SMART_ACCUMULATION, SPECULATIVE_PUMP, BROAD_EXIT." }
28
25
  },
29
- "required": ["levHolders", "spotHolders", "levDelta", "spotDelta", "signal"]
26
+ "required": ["smartAccumulation", "dumbAccumulation", "netFlow", "signal"]
27
+ };
28
+ // Output: Date -> Ticker -> Schema
29
+ return {
30
+ "type": "object",
31
+ "patternProperties": {
32
+ "^\\d{4}-\\d{2}-\\d{2}$": {
33
+ "type": "object",
34
+ "patternProperties": { "^.*$": dailySchema }
35
+ }
36
+ }
30
37
  };
31
- return { "type": "object", "patternProperties": { "^.*$": schema } };
32
38
  }
33
39
 
34
40
  async process(context) {
35
- const { computed, previousComputed } = context;
41
+ const { computed, prices, math } = context;
42
+ const historyData = computed['user-history-reconstructor'];
43
+ if (!historyData) return;
44
+
45
+ const keys = Object.keys(historyData);
46
+ const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
47
+ const dateList = isDateKeyed ? keys.sort() : [context.date.today];
48
+
49
+ // Process sequentially to allow day-over-day comparison if needed (though we use raw flows here)
50
+ for (const dateStr of dateList) {
51
+ const usersMap = isDateKeyed ? historyData[dateStr] : historyData;
52
+ this.results[dateStr] = this.processSingleDate(dateStr, usersMap, prices, math);
53
+ }
54
+ }
36
55
 
37
- const currentReconstruction = computed['user-history-reconstructor'];
38
- // Access SELF from yesterday
39
- const previousResult = previousComputed['leverage-divergence'];
56
+ processSingleDate(dateStr, usersMap, prices, math) {
57
+ const tickerAgg = {}; // Ticker -> { smartBuys, dumbBuys, sells }
40
58
 
41
- const currentAgg = {}; // { AAPL: { levHolders: 0, spotHolders: 0 } }
59
+ for (const userId in usersMap) {
60
+ const userPortfolio = usersMap[userId];
42
61
 
43
- // 1. Build Today's Aggregates
44
- for (const userId in currentReconstruction) {
45
- const userPortfolio = currentReconstruction[userId];
46
62
  for (const ticker in userPortfolio) {
47
- const pos = userPortfolio[ticker];
48
-
49
- if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
63
+ const stats = userPortfolio[ticker];
64
+
65
+ // We only care about Activity (Buys/Sells)
66
+ if (stats.didBuy === 0 && stats.didSell === 0) continue;
67
+
68
+ if (!tickerAgg[ticker]) tickerAgg[ticker] = { smartBuys: 0, dumbBuys: 0, sells: 0 };
69
+
70
+ // 1. Classify User "Smartness" for this Ticker/Date
71
+ // Heuristic: Are they profitable on their holdings relative to TODAY's price?
72
+ const priceHistory = math.priceExtractor.getHistory(prices, ticker);
73
+ const dayPriceObj = priceHistory.find(p => p.date === dateStr);
74
+ const currentPrice = dayPriceObj ? dayPriceObj.price : stats.avgEntry;
75
+
76
+ let isSmart = false;
77
+ if (stats.avgEntry > 0 && currentPrice > 0) {
78
+ const pnlPct = ((currentPrice - stats.avgEntry) / stats.avgEntry) * 100;
79
+ // Smart = Profitable (>2%) OR (Small Loss > -2% AND Low Leverage < 2x)
80
+ // Dumb = Unprofitable (< -5%) OR High Leverage (> 5x)
81
+ if (pnlPct > 2) isSmart = true;
82
+ else if (pnlPct > -2 && stats.avgLeverage < 2) isSmart = true;
83
+ }
50
84
 
51
- // If avg leverage > 1.1, count as "Leveraged Holder"
52
- if (pos.avgLeverage > 1.1) {
53
- currentAgg[ticker].levHolders++;
54
- } else {
55
- currentAgg[ticker].spotHolders++;
85
+ // 2. Accumulate Flows
86
+ if (stats.didBuy > 0) {
87
+ if (isSmart) tickerAgg[ticker].smartBuys += stats.didBuy;
88
+ else tickerAgg[ticker].dumbBuys += stats.didBuy;
89
+ }
90
+
91
+ if (stats.didSell > 0) {
92
+ tickerAgg[ticker].sells += stats.didSell;
56
93
  }
57
94
  }
58
95
  }
59
96
 
60
- // 2. Compare with Yesterday
61
- for (const ticker in currentAgg) {
62
- const curr = currentAgg[ticker];
63
- const prev = previousResult ? previousResult[ticker] : null;
64
-
65
- if (!prev) {
66
- this.results[ticker] = { ...curr, levDelta: 0, spotDelta: 0, signal: 'NEW' };
67
- continue;
68
- }
97
+ const dailyResult = {};
98
+ for (const ticker in tickerAgg) {
99
+ const { smartBuys, dumbBuys, sells } = tickerAgg[ticker];
100
+ const netFlow = (smartBuys + dumbBuys) - sells;
101
+ const totalActivity = smartBuys + dumbBuys + sells;
69
102
 
70
- const levDelta = curr.levHolders - prev.levHolders;
71
- const spotDelta = curr.spotHolders - prev.spotHolders;
103
+ if (totalActivity < 3) continue;
72
104
 
73
105
  let signal = 'NEUTRAL';
106
+
107
+ if (netFlow > 0) {
108
+ if (smartBuys > dumbBuys) signal = 'SMART_ACCUMULATION';
109
+ else signal = 'SPECULATIVE_PUMP'; // Mostly dumb/lev money buying
110
+ } else if (netFlow < 0) {
111
+ signal = 'BROAD_EXIT';
112
+ }
74
113
 
75
- // Retail (Spot) Buying + Speculators (Lev) Selling = Smart Money Exit
76
- if (spotDelta > 0 && levDelta < 0) signal = 'SMART_EXIT';
77
-
78
- // Retail (Spot) Selling + Speculators (Lev) Buying = High Conviction Pump
79
- if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
80
-
81
- this.results[ticker] = {
82
- ...curr,
83
- levDelta,
84
- spotDelta,
85
- signal
114
+ dailyResult[ticker] = {
115
+ smartAccumulation: smartBuys,
116
+ dumbAccumulation: dumbBuys,
117
+ netFlow: netFlow,
118
+ signal: signal
86
119
  };
87
120
  }
121
+
122
+ return dailyResult;
88
123
  }
89
124
 
90
125
  async getResult() { return this.results; }
@@ -12,61 +12,92 @@ class LiquidationCascade {
12
12
  };
13
13
  }
14
14
 
15
- static getDependencies() {
16
- return ['user-history-reconstructor'];
17
- }
15
+ static getDependencies() { return ['user-history-reconstructor']; }
18
16
 
19
17
  static getSchema() {
20
- const schema = {
18
+ const dailySchema = {
21
19
  "type": "object",
22
20
  "properties": {
23
- "totalClosures": { "type": "number" },
24
- "forcedClosures": { "type": "number" },
25
- "painIndex": { "type": "number" },
26
- "isFlushEvent": { "type": "boolean" }
21
+ "totalClosures": { "type": "number", "description": "Total number of positions closed today." },
22
+ "forcedClosures": { "type": "number", "description": "Number of positions closed via Stop Loss (Reason 1)." },
23
+ "avgClosedLeverage": { "type": "number", "description": "Average leverage of the closed positions." },
24
+ "painIndex": { "type": "number", "description": "Ratio of forced closures to total closures (0.0 - 1.0)." },
25
+ "isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3 AND avgClosedLeverage > 3." }
27
26
  },
28
27
  "required": ["totalClosures", "forcedClosures", "painIndex", "isFlushEvent"]
29
28
  };
30
- return { "type": "object", "patternProperties": { "^.*$": schema } };
29
+ // Output: Date -> Ticker -> Schema
30
+ return {
31
+ "type": "object",
32
+ "patternProperties": {
33
+ "^\\d{4}-\\d{2}-\\d{2}$": {
34
+ "type": "object",
35
+ "patternProperties": { "^.*$": dailySchema }
36
+ }
37
+ }
38
+ };
31
39
  }
32
40
 
33
41
  async process(context) {
34
42
  const { computed } = context;
35
- const userReconstructions = computed['user-history-reconstructor'];
36
- if (!userReconstructions) return;
43
+ const historyData = computed['user-history-reconstructor'];
44
+ if (!historyData) return;
37
45
 
46
+ const keys = Object.keys(historyData);
47
+ const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
48
+
49
+ if (isDateKeyed) {
50
+ for (const dateStr of keys) {
51
+ this.results[dateStr] = this.processSingleDate(historyData[dateStr]);
52
+ }
53
+ } else {
54
+ this.results[context.date.today] = this.processSingleDate(historyData);
55
+ }
56
+ }
57
+
58
+ processSingleDate(usersMap) {
38
59
  const aggregator = {};
60
+ const dailyResults = {};
39
61
 
40
- // 1. Aggregate Forced Exits
41
- for (const userId in userReconstructions) {
42
- const userPortfolio = userReconstructions[userId];
62
+ for (const userId in usersMap) {
63
+ const userPortfolio = usersMap[userId];
43
64
 
44
65
  for (const ticker in userPortfolio) {
45
- const position = userPortfolio[ticker];
66
+ const stats = userPortfolio[ticker];
67
+
68
+ if (stats.didSell > 0) {
69
+ if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0, sumLev: 0 };
46
70
 
47
- if (position.closedToday > 0) {
48
- if (!aggregator[ticker]) aggregator[ticker] = { closed: 0, forced: 0 };
71
+ aggregator[ticker].totalClosed += stats.didSell;
72
+
73
+ // Reason 1 is usually Stop Loss / Liquidation in cTrader/MetaTrader schemas
74
+ if (stats.closeReasons && stats.closeReasons["1"]) {
75
+ aggregator[ticker].forced += stats.closeReasons["1"];
76
+ }
49
77
 
50
- aggregator[ticker].closed += position.closedToday;
51
- aggregator[ticker].forced += position.forcedExits;
78
+ // We use avgLeverage of the holding as a proxy for the closed leverage
79
+ aggregator[ticker].sumLev += (stats.avgLeverage * stats.didSell);
52
80
  }
53
81
  }
54
82
  }
55
83
 
56
- // 2. Calculate Pain Index
57
84
  for (const ticker in aggregator) {
58
85
  const data = aggregator[ticker];
59
- if (data.closed < 5) continue;
86
+ if (data.totalClosed < 3) continue;
60
87
 
61
- const forcedRatio = data.forced / data.closed;
88
+ const forcedRatio = data.forced / data.totalClosed;
89
+ const avgLev = data.sumLev / data.totalClosed;
62
90
 
63
- this.results[ticker] = {
64
- totalClosures: data.closed,
91
+ dailyResults[ticker] = {
92
+ totalClosures: data.totalClosed,
65
93
  forcedClosures: data.forced,
66
- painIndex: forcedRatio, // 0.0 to 1.0
67
- isFlushEvent: forcedRatio > 0.3 // Flag if >30% of selling was forced
94
+ avgClosedLeverage: avgLev,
95
+ painIndex: forcedRatio,
96
+ // Flush = High Pain AND Significant Leverage involved
97
+ isFlushEvent: forcedRatio > 0.3 && avgLev > 3
68
98
  };
69
99
  }
100
+ return dailyResults;
70
101
  }
71
102
 
72
103
  async getResult() { return this.results; }
@@ -1,19 +1,21 @@
1
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.
2
+ * @fileoverview Reconstructs a user's full trading history timeline from a single snapshot.
3
+ * Uses a Scan-Line algorithm to replay history and generate daily stats (Holders, Buys, Sells).
4
4
  */
5
+
5
6
  class UserHistoryReconstructor {
6
7
  constructor() {
7
- this.results = {}; // Output: { "user_id": { "AAPL": { ...stats... } } }
8
+ this.results = {};
8
9
  }
9
10
 
10
11
  static getMetadata() {
11
12
  return {
12
13
  name: 'user-history-reconstructor',
13
14
  type: 'standard',
14
- category: 'History Reconstruction', // <--- Specific Category
15
- userType: 'all', // <--- Processes User Data
16
- isHistorical: false,
15
+ category: 'History Reconstruction',
16
+ userType: 'all',
17
+ // False because we generate history internally from the snapshot
18
+ isHistorical: false,
17
19
  rootDataDependencies: ['history']
18
20
  };
19
21
  }
@@ -21,88 +23,178 @@ class UserHistoryReconstructor {
21
23
  static getDependencies() { return []; }
22
24
 
23
25
  static getSchema() {
26
+ // Schema for a single Ticker's stats for a single Day
27
+ const tickerDailyStats = {
28
+ "type": "object",
29
+ "properties": {
30
+ "isHolder": { "type": "number", "description": "1 if the user holds the asset at EOD, 0 otherwise." },
31
+ "didBuy": { "type": "number", "description": "Count of buy trades executed this day." },
32
+ "didSell": { "type": "number", "description": "Count of sell trades executed this day." },
33
+ "avgEntry": { "type": "number", "description": "Average entry price of held positions." },
34
+ "avgLeverage": { "type": "number", "description": "Average leverage of held positions." },
35
+ "buyLeverage": { "type": "number", "description": "Average leverage of new buys executed this day." },
36
+ "closeReasons": {
37
+ "type": "object",
38
+ "description": "Map of close reason codes to counts (0=Manual, 1=Stop/Liq, 5=TP)."
39
+ }
40
+ }
41
+ };
42
+
43
+ // Output is now: Date -> Ticker -> Stats
24
44
  return {
25
- "USER_ID": {
26
- "TICKER": {
27
- "activeCount": 1,
28
- "avgEntry": 0.0,
29
- "avgLeverage": 1.0,
30
- "closedToday": 0,
31
- "forcedExits": 0
45
+ "type": "object",
46
+ "patternProperties": {
47
+ // Key is YYYY-MM-DD
48
+ "^\\d{4}-\\d{2}-\\d{2}$": {
49
+ "type": "object",
50
+ "patternProperties": {
51
+ // Key is Ticker
52
+ "^.*$": tickerDailyStats
53
+ }
32
54
  }
33
55
  }
34
56
  };
35
57
  }
36
58
 
37
59
  async process(context) {
38
- const { user, math, mappings, date } = context;
60
+ const { user, math, mappings } = context;
39
61
 
40
- // 1. Get History
62
+ // 1. Get History (Granular V2 Format)
41
63
  const history = math.history.getDailyHistory(user);
42
64
  const allTrades = history?.PublicHistoryPositions || [];
43
65
 
44
66
  if (allTrades.length === 0) return;
45
67
 
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) {
68
+ // 2. Identify all relevant tickers and dates
69
+ const events = []; // { time, type, trade, ticker }
70
+ const tickerSet = new Set();
71
+
72
+ for (const trade of allTrades) {
58
73
  const instId = trade.InstrumentID;
59
74
  const ticker = mappings.instrumentToTicker[instId];
60
75
  if (!ticker) continue;
76
+ tickerSet.add(ticker);
61
77
 
62
- if (!userState[ticker]) {
63
- userState[ticker] = {
64
- activeCount: 0,
65
- totalEntry: 0,
66
- totalLev: 0,
67
- closedToday: 0,
68
- forcedExits: 0
69
- };
78
+ const openTime = new Date(trade.OpenDateTime).getTime();
79
+ const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
80
+
81
+ // Add OPEN event
82
+ events.push({ time: openTime, type: 'OPEN', trade, ticker });
83
+
84
+ // Add CLOSE event (if closed)
85
+ if (closeTime) {
86
+ events.push({ time: closeTime, type: 'CLOSE', trade, ticker });
70
87
  }
88
+ }
89
+
90
+ // Sort events by time
91
+ events.sort((a, b) => a.time - b.time);
71
92
 
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++;
93
+ if (events.length === 0) return;
94
+
95
+ // 3. Scan-Line Execution (Replay History)
96
+ const userTimeline = {}; // { "2024-01-01": { "AAPL": {...} } }
97
+
98
+ // Tracking State
99
+ const activePositions = new Map(); // Map<PositionID, Trade>
100
+
101
+ // Determine Start and End dates for the loop
102
+ const firstEventTime = events[0].time;
103
+ const lastEventTime = events[events.length - 1].time;
104
+
105
+ const startDate = new Date(firstEventTime); startDate.setUTCHours(0,0,0,0);
106
+ const endDate = new Date(lastEventTime); endDate.setUTCHours(0,0,0,0);
107
+
108
+ let currentEventIdx = 0;
109
+ const oneDayMs = 86400000;
110
+
111
+ // Iterate day by day from first trade to last trade
112
+ for (let d = startDate.getTime(); d <= endDate.getTime(); d += oneDayMs) {
113
+ const dateStr = new Date(d).toISOString().slice(0, 10);
114
+ const dayEnd = d + oneDayMs - 1;
115
+
116
+ const dayStats = {}; // Ticker -> Stats
117
+
118
+ // Process all events that happened TODAY (before EOD)
119
+ while (currentEventIdx < events.length && events[currentEventIdx].time <= dayEnd) {
120
+ const event = events[currentEventIdx];
121
+ const { ticker, trade, type } = event;
122
+
123
+ if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
124
+
125
+ if (type === 'OPEN') {
126
+ activePositions.set(trade.PositionID, trade);
127
+ dayStats[ticker].didBuy++;
128
+ dayStats[ticker].sumBuyLev += (trade.Leverage || 1);
129
+ } else if (type === 'CLOSE') {
130
+ activePositions.delete(trade.PositionID);
131
+ dayStats[ticker].didSell++;
132
+
133
+ const reason = String(trade.CloseReason || 0);
134
+ if (dayStats[ticker].closeReasons[reason] === undefined) dayStats[ticker].closeReasons[reason] = 0;
135
+ dayStats[ticker].closeReasons[reason]++;
86
136
  }
137
+ currentEventIdx++;
138
+ }
139
+
140
+ // Snapshot the "Held" State at EOD
141
+ for (const [posId, trade] of activePositions) {
142
+ const ticker = mappings.instrumentToTicker[trade.InstrumentID];
143
+ if (!ticker) continue;
144
+
145
+ if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
146
+
147
+ const stats = dayStats[ticker];
148
+ stats.isHolder = 1; // User is a holder today
149
+ stats.holdCount++;
150
+ stats.sumEntry += (trade.OpenRate || 0);
151
+ stats.sumLev += (trade.Leverage || 1);
87
152
  }
88
- }
89
153
 
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;
154
+ // Finalize Averages for this Day
155
+ for (const ticker in dayStats) {
156
+ const stats = dayStats[ticker];
157
+
158
+ if (stats.holdCount > 0) {
159
+ stats.avgEntry = stats.sumEntry / stats.holdCount;
160
+ stats.avgLeverage = stats.sumLev / stats.holdCount;
161
+ }
162
+ if (stats.didBuy > 0) {
163
+ stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
164
+ }
165
+
166
+ // Cleanup temp sums
167
+ delete stats.sumEntry;
168
+ delete stats.sumLev;
169
+ delete stats.sumBuyLev;
170
+ delete stats.holdCount;
171
+ }
172
+
173
+ // Store in timeline if any activity exists for this day
174
+ if (Object.keys(dayStats).length > 0) {
175
+ userTimeline[dateStr] = dayStats;
96
176
  }
97
- // Cleanup intermediate sums
98
- delete stats.totalEntry;
99
- delete stats.totalLev;
100
177
  }
178
+
179
+ // 4. Output the full timeline for this user
180
+ // The ResultCommitter will handle splitting this into date buckets.
181
+ this.results[user.id] = userTimeline;
101
182
  }
102
183
 
103
- async getResult() {
104
- return this.results;
184
+ initTickerStats(dayStats, ticker) {
185
+ dayStats[ticker] = {
186
+ isHolder: 0,
187
+ didBuy: 0,
188
+ didSell: 0,
189
+ sumEntry: 0,
190
+ sumLev: 0,
191
+ holdCount: 0,
192
+ sumBuyLev: 0,
193
+ closeReasons: { "0": 0, "1": 0, "5": 0 }
194
+ };
105
195
  }
196
+
197
+ async getResult() { return this.results; }
106
198
  }
107
199
 
108
200
  module.exports = UserHistoryReconstructor;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview GAUSS Product Line (Pass 1)
3
- * REFACTORED: Uses context.math.history.getSummary().
3
+ * REFACTORED: Uses the centralized SmartMoneyScorer for consistent classification.
4
4
  */
5
5
  class DailyDnaFilter {
6
6
  constructor() {
@@ -10,7 +10,7 @@ class DailyDnaFilter {
10
10
  static getMetadata() {
11
11
  return {
12
12
  type: 'standard',
13
- rootDataDependencies: ['history'],
13
+ rootDataDependencies: ['history', 'portfolio', 'price'], // Added portfolio/price for hybrid scoring
14
14
  isHistorical: false,
15
15
  userType: 'all',
16
16
  category: 'gauss'
@@ -33,30 +33,17 @@ class DailyDnaFilter {
33
33
  }
34
34
 
35
35
  process(context) {
36
- const { user, math } = context;
37
- const { history } = math; // 'history' is the TOOL
38
-
39
- // 1. Get strict daily history data
40
- const historyDoc = history.getDailyHistory(user);
41
-
42
- // 2. Get strict summary object using the tool
43
- const summary = history.getSummary(historyDoc);
44
-
45
- // Validation
46
- if (!summary || summary.totalTrades < 20) return;
36
+ // Use the centralized Intelligence Engine injected via Context
37
+ const { SmartMoneyScorer } = context.math;
47
38
 
48
- // 3. Use properties from the SUMMARY object, not the history tool
49
- const winRate = summary.winRatio / 100.0;
50
- const lossRate = 1.0 - winRate;
51
-
52
- // CORRECTED LINE: uses summary.avgProfitPct
53
- const avgWin = summary.avgProfitPct;
54
- const avgLoss = Math.abs(summary.avgLossPct);
55
-
56
- const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
39
+ if (!SmartMoneyScorer) return;
40
+
41
+ const result = SmartMoneyScorer.scoreHybrid(context);
42
+ const score = result.totalScore;
57
43
 
58
- if (isFinite(lt_skill)) {
59
- this.allUserSkills.push([user.id, lt_skill]);
44
+ // Filter out users with insufficient data (Score 0 or Neutral with no method)
45
+ if (score > 0) {
46
+ this.allUserSkills.push([context.user.id, score]);
60
47
  }
61
48
  }
62
49
 
@@ -64,8 +51,10 @@ class DailyDnaFilter {
64
51
  const totalUsers = this.allUserSkills.length;
65
52
  if (totalUsers === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
66
53
 
54
+ // Sort Descending (Highest Score First)
67
55
  this.allUserSkills.sort((a, b) => b[1] - a[1]);
68
56
 
57
+ // Top 20% Smart, Bottom 20% Dumb
69
58
  const cohortSize = Math.floor(totalUsers * 0.20);
70
59
  if (cohortSize === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
71
60
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview GEM Product Line (Pass 1)
3
- * REFACTORED: Strictly uses context.math.history to access user history.
3
+ * REFACTORED: Uses SmartMoneyScorer for advanced hybrid classification.
4
4
  */
5
5
  class CohortSkillDefinition {
6
6
  constructor() { this.userScores = new Map(); }
@@ -21,7 +21,8 @@ class CohortSkillDefinition {
21
21
  static getMetadata() {
22
22
  return {
23
23
  type: 'standard',
24
- rootDataDependencies: ['history'],
24
+ // Updated dependencies to support hybrid scoring
25
+ rootDataDependencies: ['history', 'portfolio', 'price'],
25
26
  isHistorical: false,
26
27
  userType: 'all',
27
28
  category: 'gem'
@@ -31,26 +32,19 @@ class CohortSkillDefinition {
31
32
  static getDependencies() { return []; }
32
33
 
33
34
  process(context) {
34
- const { user, math } = context;
35
- const { history } = math;
35
+ // Use the centralized engine injected via context
36
+ const { SmartMoneyScorer } = context.math;
36
37
 
37
- // 1. Get strict daily history
38
- const historyDoc = history.getDailyHistory(user);
39
-
40
- // 2. Get strict summary DTO
41
- const summary = history.getSummary(historyDoc);
38
+ if (!SmartMoneyScorer) return;
42
39
 
43
- if (!summary || summary.totalTrades < 10) return;
40
+ const result = SmartMoneyScorer.scoreHybrid(context);
41
+ const score = result.totalScore;
44
42
 
45
- // 3. Valid DTO usage
46
- const winRate = summary.winRatio / 100.0;
47
- const lossRate = 1.0 - winRate;
48
-
49
- const expectancy = (winRate * summary.avgProfitPct) - (lossRate * Math.abs(summary.avgLossPct));
50
-
51
- const skillScore = expectancy * Math.log10(Math.max(1, summary.totalTrades));
52
-
53
- if (isFinite(skillScore)) this.userScores.set(user.id, skillScore);
43
+ // GEM requires a minimum threshold of activity usually,
44
+ // but SmartMoneyScorer handles low-data cases by returning neutral/0.
45
+ if (score > 0) {
46
+ this.userScores.set(context.user.id, score);
47
+ }
54
48
  }
55
49
 
56
50
  getResult() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.105",
3
+ "version": "1.0.107",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,97 +0,0 @@
1
- Scanning 92 files...
2
-
3
- --- Files MISSING getSchema() ---
4
-
5
- --- Files WITH getSchema() ---
6
- capitulation\asset-volatility-estimator.js
7
- capitulation\retail-capitulation-risk-forecast.js
8
- core\asset-cost-basis-profile.js
9
- core\asset-pnl-status.js
10
- core\asset-position-size.js
11
- core\average-daily-pnl-all-users.js
12
- core\average-daily-pnl-per-sector.js
13
- core\average-daily-pnl-per-stock.js
14
- core\average-daily-position-pnl.js
15
- core\crowd-cost-basis.js
16
- core\holding-duration-per-asset.js
17
- core\insights-daily-bought-vs-sold-count.js
18
- core\insights-daily-ownership-delta.js
19
- core\insights-sentimet-per-stock.js
20
- core\insights-total-long-per-sector.js
21
- core\Insights-total-long-per-stock.js
22
- core\insights-total-positions-held.js
23
- core\instrument-price-change-1d.js
24
- core\instrument-price-momentum-20d.js
25
- core\leverage-divergence.js
26
- core\liquidation-cascade.js
27
- core\long-position-per-stock.js
28
- core\overall-holding-duration.js
29
- core\overall-profitability-ratio.js
30
- core\ownership-vs-performance-ytd.js
31
- core\ownership-vs-volatility.js
32
- core\platform-buy-sell-sentiment.js
33
- core\platform-daily-bought-vs-sold-count.js
34
- core\platform-daily-ownership-delta.js
35
- core\platform-ownership-per-sector.js
36
- core\platform-total-positions-held.js
37
- core\pnl-distribution-per-stock.js
38
- core\price-metrics.js
39
- core\profitability-ratio-per-sector.js
40
- core\profitability-ratio-per-stock.js
41
- core\profitability-skew-per-stock.js
42
- core\profitable-and-unprofitable-status.js
43
- core\sentiment-per-stock.js
44
- core\short-interest-growth.js
45
- core\short-position-per-stock.js
46
- core\social-activity-aggregation.js
47
- core\social-asset-posts-trend.js
48
- core\social-event-correlation.js
49
- core\social-sentiment-aggregation.js
50
- core\social-top-mentioned-words.js
51
- core\social-topic-interest-evolution.js
52
- core\social-topic-sentiment-matrix.js
53
- core\social-word-mentions-trend.js
54
- core\speculator-asset-sentiment.js
55
- core\speculator-danger-zone.js
56
- core\speculator-distance-to-stop-loss-per-leverage.js
57
- core\speculator-distance-to-tp-per-leverage.js
58
- core\speculator-entry-distance-to-sl-per-leverage.js
59
- core\speculator-entry-distance-to-tp-per-leverage.js
60
- core\speculator-leverage-per-asset.js
61
- core\speculator-leverage-per-sector.js
62
- core\speculator-risk-reward-ratio-per-asset.js
63
- core\speculator-stop-loss-distance-by-sector-short-long-breakdown.js
64
- core\speculator-stop-loss-distance-by-ticker-short-long-breakdown.js
65
- core\speculator-stop-loss-per-asset.js
66
- core\speculator-take-profit-per-asset.js
67
- core\speculator-tsl-per-asset.js
68
- core\total-long-figures.js
69
- core\total-long-per-sector.js
70
- core\total-short-figures.js
71
- core\total-short-per-sector.js
72
- core\trending-ownership-momentum.js
73
- core\user-history-reconstructor.js
74
- core\users-processed.js
75
- gauss\cohort-capital-flow.js
76
- gauss\cohort-definer.js
77
- gauss\daily-dna-filter.js
78
- gauss\gauss-divergence-signal.js
79
- gem\cohort-momentum-state.js
80
- gem\cohort-skill-definition.js
81
- gem\platform-conviction-divergence.js
82
- gem\quant-skill-alpha-signal.js
83
- gem\skilled-cohort-flow.js
84
- gem\skilled-unskilled-divergence.js
85
- gem\unskilled-cohort-flow.js
86
- ghost-book\cost-basis-density.js
87
- ghost-book\liquidity-vacuum.js
88
- ghost-book\retail-gamma-exposure.js
89
- helix\helix-contrarian-signal.js
90
- helix\herd-consensus-score.js
91
- helix\winner-loser-flow.js
92
- predicative-alpha\cognitive-dissonance.js
93
- predicative-alpha\diamond-hand-fracture.js
94
- predicative-alpha\mimetic-latency.js
95
- pyro\risk-appetite-index.js
96
- pyro\squeeze-potential.js
97
- pyro\volatility-signal.js
@@ -1,47 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- const rootDir = __dirname;
5
-
6
- function getAllFiles(dirPath, arrayOfFiles) {
7
- const files = fs.readdirSync(dirPath);
8
-
9
- arrayOfFiles = arrayOfFiles || [];
10
-
11
- files.forEach(function (file) {
12
- if (fs.statSync(dirPath + "/" + file).isDirectory()) {
13
- arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
14
- } else {
15
- if (file.endsWith('.js') && file !== 'audit_schemas.js') {
16
- arrayOfFiles.push(path.join(dirPath, "/", file));
17
- }
18
- }
19
- });
20
-
21
- return arrayOfFiles;
22
- }
23
-
24
- const files = getAllFiles(rootDir);
25
- let missingSchema = [];
26
- let hasSchema = [];
27
-
28
- const output = [];
29
- output.push(`Scanning ${files.length} files...`);
30
-
31
- files.forEach(file => {
32
- const content = fs.readFileSync(file, 'utf8');
33
- if (content.includes('static getSchema()')) {
34
- hasSchema.push(file);
35
- } else {
36
- missingSchema.push(file);
37
- }
38
- });
39
-
40
- output.push('\n--- Files MISSING getSchema() ---');
41
- missingSchema.forEach(f => output.push(path.relative(rootDir, f)));
42
-
43
- output.push('\n--- Files WITH getSchema() ---');
44
- hasSchema.forEach(f => output.push(path.relative(rootDir, f)));
45
-
46
- fs.writeFileSync(path.join(rootDir, 'audit_results.txt'), output.join('\n'));
47
- console.log('Audit complete. Results written to audit_results.txt');