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
|
|
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
|
|
48
|
-
"^\\d{4}-\\d{2}-\\d{2}$": {
|
|
49
|
+
"^\\d{4}-\\d{2}-\\d{2}$": { // Date Key
|
|
49
50
|
"type": "object",
|
|
50
51
|
"patternProperties": {
|
|
51
|
-
|
|
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.
|
|
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
|
-
//
|
|
69
|
-
const events = [];
|
|
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
|
-
//
|
|
94
|
-
const userTimeline = {};
|
|
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);
|
|
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
|
|
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 = {};
|
|
121
|
+
const dayStats = {};
|
|
115
122
|
|
|
116
|
-
// Process
|
|
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
|
|
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;
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
}
|