aiden-shared-calculations-unified 1.0.108 → 1.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
1
  /**
2
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).
3
+ * Uses a Scan-Line algorithm to replay history and generate daily stats.
4
+ * * FEATURES:
5
+ * 1. Auto-Backfill: If running on the EARLIEST available root data date, it generates the full 365-day history.
6
+ * 2. Incremental Mode: If running on any later date, it only saves that specific date to save I/O.
4
7
  */
5
8
 
6
9
  class UserHistoryReconstructor {
@@ -14,16 +17,16 @@ class UserHistoryReconstructor {
14
17
  type: 'standard',
15
18
  category: 'History Reconstruction',
16
19
  userType: 'all',
17
- // False because we generate history internally from the snapshot
18
20
  isHistorical: false,
19
- rootDataDependencies: ['history']
21
+ rootDataDependencies: ['history'],
22
+ // [NEW] Request system injection for Date Boundaries
23
+ requiresEarliestDataDate: true
20
24
  };
21
25
  }
22
26
 
23
27
  static getDependencies() { return []; }
24
28
 
25
29
  static getSchema() {
26
- // Schema for a single Ticker's stats for a single Day
27
30
  const tickerDailyStats = {
28
31
  "type": "object",
29
32
  "properties": {
@@ -40,16 +43,13 @@ class UserHistoryReconstructor {
40
43
  }
41
44
  };
42
45
 
43
- // Output is now: Date -> Ticker -> Stats
44
46
  return {
45
47
  "type": "object",
46
48
  "patternProperties": {
47
- // Key is YYYY-MM-DD
48
- "^\\d{4}-\\d{2}-\\d{2}$": {
49
+ "^\\d{4}-\\d{2}-\\d{2}$": { // Date Key
49
50
  "type": "object",
50
51
  "patternProperties": {
51
- // Key is Ticker
52
- "^.*$": tickerDailyStats
52
+ "^.*$": tickerDailyStats // Ticker Key
53
53
  }
54
54
  }
55
55
  }
@@ -57,16 +57,31 @@ class UserHistoryReconstructor {
57
57
  }
58
58
 
59
59
  async process(context) {
60
- const { user, math, mappings } = context;
60
+ const { user, math, mappings, date, system } = context;
61
+ const currentExecutionDateStr = date.today;
61
62
 
62
- // 1. Get History (Granular V2 Format)
63
+ // 1. Determine Execution Mode (Backfill vs Incremental)
64
+ // We use the INJECTED value from context.system (provided by StandardExecutor)
65
+ let isEarliestRun = false;
66
+
67
+ if (system && system.earliestHistoryDate) {
68
+ const earliestHistoryStr = system.earliestHistoryDate.toISOString().slice(0, 10);
69
+ if (currentExecutionDateStr <= earliestHistoryStr) {
70
+ isEarliestRun = true;
71
+ }
72
+ } else {
73
+ // Fallback: If system context is missing, default to Incremental to prevent explosion
74
+ // console.warn(`[UserHistoryReconstructor] System context missing. Defaulting to Incremental mode.`);
75
+ }
76
+
77
+ // 2. Get History (Granular V2 Format)
63
78
  const history = math.history.getDailyHistory(user);
64
79
  const allTrades = history?.PublicHistoryPositions || [];
65
80
 
66
81
  if (allTrades.length === 0) return;
67
82
 
68
- // 2. Identify all relevant tickers and dates
69
- const events = []; // { time, type, trade, ticker }
83
+ // 3. Identify all relevant tickers and events
84
+ const events = [];
70
85
  const tickerSet = new Set();
71
86
 
72
87
  for (const trade of allTrades) {
@@ -78,42 +93,34 @@ class UserHistoryReconstructor {
78
93
  const openTime = new Date(trade.OpenDateTime).getTime();
79
94
  const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
80
95
 
81
- // Add OPEN event
82
96
  events.push({ time: openTime, type: 'OPEN', trade, ticker });
83
-
84
- // Add CLOSE event (if closed)
85
97
  if (closeTime) { events.push({ time: closeTime, type: 'CLOSE', trade, ticker }); }
86
98
  }
87
99
 
88
- // Sort events by time
89
100
  events.sort((a, b) => a.time - b.time);
90
-
91
101
  if (events.length === 0) return;
92
102
 
93
- // 3. Scan-Line Execution (Replay History)
94
- const userTimeline = {}; // { "2024-01-01": { "AAPL": {...} } }
103
+ // 4. Scan-Line Execution (Replay History)
104
+ const userTimeline = {};
105
+ const activePositions = new Map();
95
106
 
96
- // Tracking State
97
- const activePositions = new Map(); // Map<PositionID, Trade>
98
-
99
- // Determine Start and End dates for the loop
100
107
  const firstEventTime = events[0].time;
101
108
  const lastEventTime = events[events.length - 1].time;
102
109
 
103
110
  const startDate = new Date(firstEventTime); startDate.setUTCHours(0,0,0,0);
104
- const endDate = new Date(lastEventTime); endDate.setUTCHours(0,0,0,0);
111
+ const endDate = new Date(lastEventTime); endDate.setUTCHours(0,0,0,0);
105
112
 
106
113
  let currentEventIdx = 0;
107
114
  const oneDayMs = 86400000;
108
115
 
109
- // Iterate day by day from first trade to last trade
116
+ // Iterate day by day
110
117
  for (let d = startDate.getTime(); d <= endDate.getTime(); d += oneDayMs) {
111
118
  const dateStr = new Date(d).toISOString().slice(0, 10);
112
119
  const dayEnd = d + oneDayMs - 1;
113
120
 
114
- const dayStats = {}; // Ticker -> Stats
121
+ const dayStats = {};
115
122
 
116
- // Process all events that happened TODAY (before EOD)
123
+ // Process events for TODAY
117
124
  while (currentEventIdx < events.length && events[currentEventIdx].time <= dayEnd) {
118
125
  const event = events[currentEventIdx];
119
126
  const { ticker, trade, type } = event;
@@ -135,7 +142,7 @@ class UserHistoryReconstructor {
135
142
  currentEventIdx++;
136
143
  }
137
144
 
138
- // Snapshot the "Held" State at EOD
145
+ // Snapshot "Held" State at EOD
139
146
  for (const [posId, trade] of activePositions) {
140
147
  const ticker = mappings.instrumentToTicker[trade.InstrumentID];
141
148
  if (!ticker) continue;
@@ -143,16 +150,15 @@ class UserHistoryReconstructor {
143
150
  if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
144
151
 
145
152
  const stats = dayStats[ticker];
146
- stats.isHolder = 1; // User is a holder today
153
+ stats.isHolder = 1;
147
154
  stats.holdCount++;
148
155
  stats.sumEntry += (trade.OpenRate || 0);
149
156
  stats.sumLev += (trade.Leverage || 1);
150
157
  }
151
158
 
152
- // Finalize Averages for this Day
159
+ // Finalize Averages
153
160
  for (const ticker in dayStats) {
154
161
  const stats = dayStats[ticker];
155
-
156
162
  if (stats.holdCount > 0) {
157
163
  stats.avgEntry = stats.sumEntry / stats.holdCount;
158
164
  stats.avgLeverage = stats.sumLev / stats.holdCount;
@@ -160,34 +166,27 @@ class UserHistoryReconstructor {
160
166
  if (stats.didBuy > 0) {
161
167
  stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
162
168
  }
163
-
164
- // Cleanup temp sums
165
- delete stats.sumEntry;
166
- delete stats.sumLev;
167
- delete stats.sumBuyLev;
168
- delete stats.holdCount;
169
+ delete stats.sumEntry; delete stats.sumLev; delete stats.sumBuyLev; delete stats.holdCount;
169
170
  }
170
171
 
171
- // Store in timeline if any activity exists for this day
172
+ // [LOGIC UPDATE] Selective Storage
173
+ // 1. If this is the EARLIEST run (Backfill), we save ALL calculated days.
174
+ // 2. If this is a normal run, we ONLY save the stats for the execution date.
172
175
  if (Object.keys(dayStats).length > 0) {
173
- userTimeline[dateStr] = dayStats;
176
+ if (isEarliestRun) {
177
+ userTimeline[dateStr] = dayStats;
178
+ } else if (dateStr === currentExecutionDateStr) {
179
+ userTimeline[dateStr] = dayStats;
180
+ }
174
181
  }
175
182
  }
176
183
 
177
- // 4. Output the full timeline for this user
178
- // The ResultCommitter will handle splitting this into date buckets.
179
184
  this.results[user.id] = userTimeline;
180
185
  }
181
186
 
182
187
  initTickerStats(dayStats, ticker) {
183
188
  dayStats[ticker] = {
184
- isHolder: 0,
185
- didBuy: 0,
186
- didSell: 0,
187
- sumEntry: 0,
188
- sumLev: 0,
189
- holdCount: 0,
190
- sumBuyLev: 0,
189
+ isHolder: 0, didBuy: 0, didSell: 0, sumEntry: 0, sumLev: 0, holdCount: 0, sumBuyLev: 0,
191
190
  closeReasons: { "0": 0, "1": 0, "5": 0 }
192
191
  };
193
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.108",
3
+ "version": "1.0.109",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [