aiden-shared-calculations-unified 1.0.106 → 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.
@@ -7,7 +7,7 @@ class CrowdCostBasis {
7
7
  type: 'meta',
8
8
  category: 'History Reconstruction',
9
9
  userType: 'n/a',
10
- isHistorical: false,
10
+ isHistorical: false, // Self-contained history generation
11
11
  rootDataDependencies: ['price']
12
12
  };
13
13
  }
@@ -15,7 +15,7 @@ class CrowdCostBasis {
15
15
  static getDependencies() { return ['user-history-reconstructor']; }
16
16
 
17
17
  static getSchema() {
18
- const schema = {
18
+ const dailySchema = {
19
19
  "type": "object",
20
20
  "properties": {
21
21
  "avgEntry": { "type": "number", "description": "Global average entry price for all holders." },
@@ -25,20 +25,48 @@ class CrowdCostBasis {
25
25
  },
26
26
  "required": ["avgEntry", "holderCount", "profitabilityPct", "state"]
27
27
  };
28
- // The result is a Map of Ticker -> Schema
29
- 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
+ };
30
39
  }
31
40
 
32
41
  async process(context) {
33
42
  const { computed, prices, math } = context;
34
- const userReconstructions = computed['user-history-reconstructor'];
35
- if (!userReconstructions) return;
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
+ }
36
62
 
63
+ processSingleDate(dateStr, usersMap, prices, math) {
37
64
  const aggregator = {};
65
+ const dailyResults = {};
38
66
 
39
- // 1. Iterate Users
40
- for (const userId in userReconstructions) {
41
- const userPortfolio = userReconstructions[userId];
67
+ // 1. Iterate Users for this Date
68
+ for (const userId in usersMap) {
69
+ const userPortfolio = usersMap[userId];
42
70
 
43
71
  for (const ticker in userPortfolio) {
44
72
  const stats = userPortfolio[ticker];
@@ -54,24 +82,29 @@ class CrowdCostBasis {
54
82
  // 2. Compute Global Cost Basis
55
83
  for (const ticker in aggregator) {
56
84
  const data = aggregator[ticker];
57
- if (data.count < 5) continue;
85
+ if (data.count < 3) continue; // Noise filter
58
86
 
59
87
  const globalAvgEntry = data.sumEntry / data.count;
60
88
 
61
- // Get Price for Context Date
89
+ // Get Price for this specific date
90
+ // Note: prices.history might be sharded, assuming ContextFactory loaded relevant shards
62
91
  const priceHistory = math.priceExtractor.getHistory(prices, ticker);
63
- const lastPriceObj = priceHistory[priceHistory.length - 1];
64
- 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;
65
97
 
66
- const diffPct = ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100;
98
+ const diffPct = globalAvgEntry > 0 ? ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100 : 0;
67
99
 
68
- this.results[ticker] = {
100
+ dailyResults[ticker] = {
69
101
  avgEntry: globalAvgEntry,
70
102
  holderCount: data.count,
71
103
  profitabilityPct: diffPct,
72
104
  state: diffPct > 0 ? 'PROFIT_SUPPORT' : 'LOSS_RESISTANCE'
73
105
  };
74
106
  }
107
+ return dailyResults;
75
108
  }
76
109
 
77
110
  async getResult() { return this.results; }
@@ -7,81 +7,119 @@ class LeverageDivergence {
7
7
  type: 'meta',
8
8
  category: 'History Reconstruction',
9
9
  userType: 'n/a',
10
- isHistorical: true,
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
15
  static getDependencies() { return ['user-history-reconstructor']; }
16
16
 
17
17
  static getSchema() {
18
- const schema = {
18
+ const dailySchema = {
19
19
  "type": "object",
20
20
  "properties": {
21
- "levHolders": { "type": "number", "description": "Count of holders with leverage > 1.1x." },
22
- "spotHolders": { "type": "number", "description": "Count of holders with leverage <= 1.1x." },
23
- "levDelta": { "type": "number", "description": "Change in leveraged holders vs yesterday." },
24
- "spotDelta": { "type": "number", "description": "Change in spot holders vs yesterday." },
25
- "signal": { "type": "string", "description": "Divergence signal: NEUTRAL, SMART_ACCUMULATION, SPECULATIVE_PUMP, SMART_EXIT." }
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." }
26
25
  },
27
- "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
+ }
28
37
  };
29
- return { "type": "object", "patternProperties": { "^.*$": schema } };
30
38
  }
31
39
 
32
40
  async process(context) {
33
- const { computed, previousComputed } = context;
34
-
35
- const currentReconstruction = computed['user-history-reconstructor'];
36
- const previousResult = previousComputed['leverage-divergence'];
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
+ }
37
55
 
38
- const currentAgg = {};
56
+ processSingleDate(dateStr, usersMap, prices, math) {
57
+ const tickerAgg = {}; // Ticker -> { smartBuys, dumbBuys, sells }
39
58
 
40
- // 1. Build Today's Aggregates
41
- for (const userId in currentReconstruction) {
42
- const userPortfolio = currentReconstruction[userId];
59
+ for (const userId in usersMap) {
60
+ const userPortfolio = usersMap[userId];
43
61
 
44
62
  for (const ticker in userPortfolio) {
45
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
+ }
46
84
 
47
- if (stats.isHolder === 1) {
48
- if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
49
-
50
- if (stats.avgLeverage > 1.1) {
51
- currentAgg[ticker].levHolders++;
52
- } else {
53
- currentAgg[ticker].spotHolders++;
54
- }
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;
55
93
  }
56
94
  }
57
95
  }
58
96
 
59
- // 2. Compare with Previous Day
60
- for (const ticker in currentAgg) {
61
- const curr = currentAgg[ticker];
62
- const prev = previousResult ? previousResult[ticker] : null;
63
-
64
- if (!prev) {
65
- this.results[ticker] = { ...curr, levDelta: 0, spotDelta: 0, signal: 'NEW' };
66
- continue;
67
- }
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;
68
102
 
69
- const levDelta = curr.levHolders - prev.levHolders;
70
- const spotDelta = curr.spotHolders - prev.spotHolders;
103
+ if (totalActivity < 3) continue;
71
104
 
72
105
  let signal = 'NEUTRAL';
73
- if (spotDelta > 0 && levDelta < 0) signal = 'SMART_ACCUMULATION';
74
- else if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
75
- else if (spotDelta > 0 && levDelta > 0) signal = 'BROAD_ACCUMULATION';
76
- else if (spotDelta < 0 && levDelta < 0) signal = 'BROAD_EXIT';
77
-
78
- this.results[ticker] = {
79
- ...curr,
80
- levDelta,
81
- spotDelta,
82
- signal
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
+ }
113
+
114
+ dailyResult[ticker] = {
115
+ smartAccumulation: smartBuys,
116
+ dumbAccumulation: dumbBuys,
117
+ netFlow: netFlow,
118
+ signal: signal
83
119
  };
84
120
  }
121
+
122
+ return dailyResult;
85
123
  }
86
124
 
87
125
  async getResult() { return this.results; }
@@ -15,57 +15,89 @@ class LiquidationCascade {
15
15
  static getDependencies() { return ['user-history-reconstructor']; }
16
16
 
17
17
  static getSchema() {
18
- const schema = {
18
+ const dailySchema = {
19
19
  "type": "object",
20
20
  "properties": {
21
21
  "totalClosures": { "type": "number", "description": "Total number of positions closed today." },
22
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." },
23
24
  "painIndex": { "type": "number", "description": "Ratio of forced closures to total closures (0.0 - 1.0)." },
24
- "isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3." }
25
+ "isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3 AND avgClosedLeverage > 3." }
25
26
  },
26
27
  "required": ["totalClosures", "forcedClosures", "painIndex", "isFlushEvent"]
27
28
  };
28
- 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
+ };
29
39
  }
30
40
 
31
41
  async process(context) {
32
42
  const { computed } = context;
33
- const userReconstructions = computed['user-history-reconstructor'];
34
- if (!userReconstructions) return;
43
+ const historyData = computed['user-history-reconstructor'];
44
+ if (!historyData) return;
45
+
46
+ const keys = Object.keys(historyData);
47
+ const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
35
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) {
36
59
  const aggregator = {};
60
+ const dailyResults = {};
37
61
 
38
- for (const userId in userReconstructions) {
39
- const userPortfolio = userReconstructions[userId];
62
+ for (const userId in usersMap) {
63
+ const userPortfolio = usersMap[userId];
40
64
 
41
65
  for (const ticker in userPortfolio) {
42
66
  const stats = userPortfolio[ticker];
43
67
 
44
68
  if (stats.didSell > 0) {
45
- if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0 };
69
+ if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0, sumLev: 0 };
46
70
 
47
71
  aggregator[ticker].totalClosed += stats.didSell;
48
72
 
73
+ // Reason 1 is usually Stop Loss / Liquidation in cTrader/MetaTrader schemas
49
74
  if (stats.closeReasons && stats.closeReasons["1"]) {
50
75
  aggregator[ticker].forced += stats.closeReasons["1"];
51
76
  }
77
+
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
84
  for (const ticker in aggregator) {
57
85
  const data = aggregator[ticker];
58
- if (data.totalClosed < 5) continue;
86
+ if (data.totalClosed < 3) continue;
59
87
 
60
88
  const forcedRatio = data.forced / data.totalClosed;
89
+ const avgLev = data.sumLev / data.totalClosed;
61
90
 
62
- this.results[ticker] = {
91
+ dailyResults[ticker] = {
63
92
  totalClosures: data.totalClosed,
64
93
  forcedClosures: data.forced,
94
+ avgClosedLeverage: avgLev,
65
95
  painIndex: forcedRatio,
66
- isFlushEvent: forcedRatio > 0.3
96
+ // Flush = High Pain AND Significant Leverage involved
97
+ isFlushEvent: forcedRatio > 0.3 && avgLev > 3
67
98
  };
68
99
  }
100
+ return dailyResults;
69
101
  }
70
102
 
71
103
  async getResult() { return this.results; }
@@ -1,3 +1,8 @@
1
+ /**
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
+ */
5
+
1
6
  class UserHistoryReconstructor {
2
7
  constructor() {
3
8
  this.results = {};
@@ -9,7 +14,8 @@ class UserHistoryReconstructor {
9
14
  type: 'standard',
10
15
  category: 'History Reconstruction',
11
16
  userType: 'all',
12
- isHistorical: false,
17
+ // False because we generate history internally from the snapshot
18
+ isHistorical: false,
13
19
  rootDataDependencies: ['history']
14
20
  };
15
21
  }
@@ -17,134 +23,175 @@ class UserHistoryReconstructor {
17
23
  static getDependencies() { return []; }
18
24
 
19
25
  static getSchema() {
20
- // Schema for a single Ticker's stats
21
- const tickerSchema = {
26
+ // Schema for a single Ticker's stats for a single Day
27
+ const tickerDailyStats = {
22
28
  "type": "object",
23
29
  "properties": {
24
30
  "isHolder": { "type": "number", "description": "1 if the user holds the asset at EOD, 0 otherwise." },
25
- "didBuy": { "type": "number", "description": "Count of buy trades executed today." },
26
- "didSell": { "type": "number", "description": "Count of sell trades executed today." },
31
+ "didBuy": { "type": "number", "description": "Count of buy trades executed this day." },
32
+ "didSell": { "type": "number", "description": "Count of sell trades executed this day." },
27
33
  "avgEntry": { "type": "number", "description": "Average entry price of held positions." },
28
34
  "avgLeverage": { "type": "number", "description": "Average leverage of held positions." },
29
- "buyLeverage": { "type": "number", "description": "Average leverage of new buys executed today." },
35
+ "buyLeverage": { "type": "number", "description": "Average leverage of new buys executed this day." },
30
36
  "closeReasons": {
31
37
  "type": "object",
32
38
  "description": "Map of close reason codes to counts (0=Manual, 1=Stop/Liq, 5=TP)."
33
39
  }
34
- },
35
- "required": ["isHolder", "didBuy", "didSell", "avgEntry", "avgLeverage"]
40
+ }
36
41
  };
37
42
 
38
- // The result for a user is a Map of Ticker -> TickerSchema
43
+ // Output is now: Date -> Ticker -> Stats
39
44
  return {
40
45
  "type": "object",
41
46
  "patternProperties": {
42
- "^.*$": tickerSchema
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
+ }
54
+ }
43
55
  }
44
56
  };
45
57
  }
46
58
 
47
59
  async process(context) {
48
- const { user, math, mappings, date } = context;
60
+ const { user, math, mappings } = context;
49
61
 
50
- // 1. Get History (V2 Format Only)
62
+ // 1. Get History (Granular V2 Format)
51
63
  const history = math.history.getDailyHistory(user);
52
64
  const allTrades = history?.PublicHistoryPositions || [];
53
65
 
54
66
  if (allTrades.length === 0) return;
55
67
 
56
- // 2. Define Time Boundaries for "Yesterday" (Target Date)
57
- const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
58
- const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
59
-
60
- this.results[user.id] = {};
61
- const userState = this.results[user.id];
62
-
68
+ // 2. Identify all relevant tickers and dates
69
+ const events = []; // { time, type, trade, ticker }
70
+ const tickerSet = new Set();
71
+
63
72
  for (const trade of allTrades) {
64
73
  const instId = trade.InstrumentID;
65
74
  const ticker = mappings.instrumentToTicker[instId];
66
75
  if (!ticker) continue;
76
+ tickerSet.add(ticker);
67
77
 
68
- // Parse Dates
69
78
  const openTime = new Date(trade.OpenDateTime).getTime();
70
79
  const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
71
80
 
72
- // 3. Determine States
73
- const isOpenBeforeEOD = openTime <= dayEnd;
74
- // Active if it wasn't closed before the day started
75
- const isClosedAfterSOD = closeTime === null || closeTime >= dayStart;
76
-
77
- const wasActiveToday = isOpenBeforeEOD && isClosedAfterSOD;
78
-
79
- // "Held" means active at End of Day
80
- const isHeldAtEOD = isOpenBeforeEOD && (closeTime === null || closeTime > dayEnd);
81
-
82
- // "Opened" means OpenTime is inside today
83
- const isOpenedToday = openTime >= dayStart && openTime <= dayEnd;
84
-
85
- // "Closed" means CloseTime is inside today
86
- const isClosedToday = closeTime !== null && closeTime >= dayStart && closeTime <= dayEnd;
87
-
88
- if (!wasActiveToday) continue;
89
-
90
- if (!userState[ticker]) {
91
- userState[ticker] = {
92
- isHolder: 0,
93
- didBuy: 0,
94
- didSell: 0,
95
- sumEntry: 0,
96
- sumLev: 0,
97
- holdCount: 0,
98
- sumBuyLev: 0,
99
- closeReasons: { "0": 0, "1": 0, "5": 0 }
100
- };
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 });
101
87
  }
102
- const stats = userState[ticker];
88
+ }
89
+
90
+ // Sort events by time
91
+ events.sort((a, b) => a.time - b.time);
92
+
93
+ if (events.length === 0) return;
103
94
 
104
- // 4. Populate Metrics
105
- if (isHeldAtEOD) {
106
- stats.isHolder = 1;
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]++;
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
107
149
  stats.holdCount++;
108
150
  stats.sumEntry += (trade.OpenRate || 0);
109
151
  stats.sumLev += (trade.Leverage || 1);
110
152
  }
111
153
 
112
- if (isOpenedToday) {
113
- stats.didBuy++;
114
- stats.sumBuyLev += (trade.Leverage || 1);
115
- }
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
+ }
116
165
 
117
- if (isClosedToday) {
118
- stats.didSell++;
119
- const reason = String(trade.CloseReason || 0);
120
- if (stats.closeReasons[reason] === undefined) stats.closeReasons[reason] = 0;
121
- stats.closeReasons[reason]++;
166
+ // Cleanup temp sums
167
+ delete stats.sumEntry;
168
+ delete stats.sumLev;
169
+ delete stats.sumBuyLev;
170
+ delete stats.holdCount;
122
171
  }
123
- }
124
172
 
125
- // 5. Cleanup / Average
126
- for (const ticker in userState) {
127
- const stats = userState[ticker];
128
-
129
- if (stats.holdCount > 0) {
130
- stats.avgEntry = stats.sumEntry / stats.holdCount;
131
- stats.avgLeverage = stats.sumLev / stats.holdCount;
132
- } else {
133
- stats.avgEntry = 0;
134
- stats.avgLeverage = 0;
173
+ // Store in timeline if any activity exists for this day
174
+ if (Object.keys(dayStats).length > 0) {
175
+ userTimeline[dateStr] = dayStats;
135
176
  }
177
+ }
136
178
 
137
- if (stats.didBuy > 0) {
138
- stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
139
- } else {
140
- stats.buyLeverage = 0;
141
- }
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;
182
+ }
142
183
 
143
- delete stats.sumEntry;
144
- delete stats.sumLev;
145
- delete stats.sumBuyLev;
146
- delete stats.holdCount;
147
- }
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
+ };
148
195
  }
149
196
 
150
197
  async getResult() { return this.results; }
@@ -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.106",
3
+ "version": "1.0.107",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [