aiden-shared-calculations-unified 1.0.107 → 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.
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
class AssetPnlStatus {
|
|
6
6
|
constructor() {
|
|
7
|
-
this.
|
|
7
|
+
// [FIX 1] Use 'this.results' directly.
|
|
8
|
+
// The StandardExecutor monitors this property to perform Batch Flushing.
|
|
9
|
+
this.results = {};
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
static getMetadata() {
|
|
@@ -22,29 +24,26 @@ class AssetPnlStatus {
|
|
|
22
24
|
static getSchema() {
|
|
23
25
|
return {
|
|
24
26
|
"type": "object",
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
+
// [FIX 2] Flattened Schema (removed "by_user" wrapper)
|
|
28
|
+
"patternProperties": {
|
|
29
|
+
"^[0-9]+$": {
|
|
27
30
|
"type": "object",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"losers": { "type": "array", "items": { "type": "string" } }
|
|
34
|
-
},
|
|
35
|
-
"required": ["winners", "losers"]
|
|
36
|
-
}
|
|
37
|
-
}
|
|
31
|
+
"properties": {
|
|
32
|
+
"winners": { "type": "array", "items": { "type": "string" } },
|
|
33
|
+
"losers": { "type": "array", "items": { "type": "string" } }
|
|
34
|
+
},
|
|
35
|
+
"required": ["winners", "losers"]
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
};
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
_initUser(userId) {
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// [FIX 3] Direct access to this.results
|
|
43
|
+
if (!this.results[userId]) {
|
|
44
|
+
this.results[userId] = { winners: [], losers: [] };
|
|
46
45
|
}
|
|
47
|
-
return this.
|
|
46
|
+
return this.results[userId];
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
process(context) {
|
|
@@ -55,7 +54,9 @@ class AssetPnlStatus {
|
|
|
55
54
|
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
56
55
|
if (!positions || positions.length === 0) return;
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
// Use temporary sets for processing to keep logic clean, then convert to array for storage
|
|
58
|
+
const winners = new Set();
|
|
59
|
+
const losers = new Set();
|
|
59
60
|
|
|
60
61
|
for (const pos of positions) {
|
|
61
62
|
const instId = extract.getInstrumentId(pos);
|
|
@@ -68,35 +69,26 @@ class AssetPnlStatus {
|
|
|
68
69
|
const sector = mappings.instrumentToSector[instId];
|
|
69
70
|
const isWinner = pnl > 0;
|
|
70
71
|
|
|
71
|
-
if (ticker
|
|
72
|
-
|
|
73
|
-
if (ticker) {
|
|
74
|
-
if (isWinner) stats.winners.add(ticker); else stats.losers.add(ticker);
|
|
75
|
-
}
|
|
76
|
-
if (sector) {
|
|
77
|
-
if (isWinner) stats.winners.add(sector); else stats.losers.add(sector);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
72
|
+
if (ticker) { isWinner ? winners.add(ticker) : losers.add(ticker); }
|
|
73
|
+
if (sector) { isWinner ? winners.add(sector) : losers.add(sector); }
|
|
80
74
|
}
|
|
81
|
-
}
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
byUser[userId] = {
|
|
89
|
-
winners: Array.from(stats.winners),
|
|
90
|
-
losers: Array.from(stats.losers)
|
|
91
|
-
};
|
|
92
|
-
}
|
|
76
|
+
if (winners.size > 0 || losers.size > 0) {
|
|
77
|
+
this.results[userId] = {
|
|
78
|
+
winners: Array.from(winners),
|
|
79
|
+
losers: Array.from(losers)
|
|
80
|
+
};
|
|
93
81
|
}
|
|
82
|
+
}
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
async getResult() {
|
|
85
|
+
// [FIX 4] Return 'this.results' directly.
|
|
86
|
+
// This is a FLAT object { "user1": {...}, "user2": {...} }
|
|
87
|
+
// The ResultCommitter can now shard this into 50-user chunks if needed.
|
|
88
|
+
return this.results;
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
reset
|
|
91
|
+
// [FIX 5] No manual reset needed, StandardExecutor does it.
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
module.exports = AssetPnlStatus;
|
|
@@ -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,44 +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
|
-
if (closeTime) {
|
|
86
|
-
events.push({ time: closeTime, type: 'CLOSE', trade, ticker });
|
|
87
|
-
}
|
|
97
|
+
if (closeTime) { events.push({ time: closeTime, type: 'CLOSE', trade, ticker }); }
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
// Sort events by time
|
|
91
100
|
events.sort((a, b) => a.time - b.time);
|
|
92
|
-
|
|
93
101
|
if (events.length === 0) return;
|
|
94
102
|
|
|
95
|
-
//
|
|
96
|
-
const userTimeline = {};
|
|
97
|
-
|
|
98
|
-
// Tracking State
|
|
99
|
-
const activePositions = new Map(); // Map<PositionID, Trade>
|
|
103
|
+
// 4. Scan-Line Execution (Replay History)
|
|
104
|
+
const userTimeline = {};
|
|
105
|
+
const activePositions = new Map();
|
|
100
106
|
|
|
101
|
-
// Determine Start and End dates for the loop
|
|
102
107
|
const firstEventTime = events[0].time;
|
|
103
108
|
const lastEventTime = events[events.length - 1].time;
|
|
104
109
|
|
|
105
110
|
const startDate = new Date(firstEventTime); startDate.setUTCHours(0,0,0,0);
|
|
106
|
-
const endDate = new Date(lastEventTime);
|
|
111
|
+
const endDate = new Date(lastEventTime); endDate.setUTCHours(0,0,0,0);
|
|
107
112
|
|
|
108
113
|
let currentEventIdx = 0;
|
|
109
114
|
const oneDayMs = 86400000;
|
|
110
115
|
|
|
111
|
-
// Iterate day by day
|
|
116
|
+
// Iterate day by day
|
|
112
117
|
for (let d = startDate.getTime(); d <= endDate.getTime(); d += oneDayMs) {
|
|
113
118
|
const dateStr = new Date(d).toISOString().slice(0, 10);
|
|
114
119
|
const dayEnd = d + oneDayMs - 1;
|
|
115
120
|
|
|
116
|
-
const dayStats = {};
|
|
121
|
+
const dayStats = {};
|
|
117
122
|
|
|
118
|
-
// Process
|
|
123
|
+
// Process events for TODAY
|
|
119
124
|
while (currentEventIdx < events.length && events[currentEventIdx].time <= dayEnd) {
|
|
120
125
|
const event = events[currentEventIdx];
|
|
121
126
|
const { ticker, trade, type } = event;
|
|
@@ -137,7 +142,7 @@ class UserHistoryReconstructor {
|
|
|
137
142
|
currentEventIdx++;
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
// Snapshot
|
|
145
|
+
// Snapshot "Held" State at EOD
|
|
141
146
|
for (const [posId, trade] of activePositions) {
|
|
142
147
|
const ticker = mappings.instrumentToTicker[trade.InstrumentID];
|
|
143
148
|
if (!ticker) continue;
|
|
@@ -145,16 +150,15 @@ class UserHistoryReconstructor {
|
|
|
145
150
|
if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
|
|
146
151
|
|
|
147
152
|
const stats = dayStats[ticker];
|
|
148
|
-
stats.isHolder = 1;
|
|
153
|
+
stats.isHolder = 1;
|
|
149
154
|
stats.holdCount++;
|
|
150
155
|
stats.sumEntry += (trade.OpenRate || 0);
|
|
151
156
|
stats.sumLev += (trade.Leverage || 1);
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
// Finalize Averages
|
|
159
|
+
// Finalize Averages
|
|
155
160
|
for (const ticker in dayStats) {
|
|
156
161
|
const stats = dayStats[ticker];
|
|
157
|
-
|
|
158
162
|
if (stats.holdCount > 0) {
|
|
159
163
|
stats.avgEntry = stats.sumEntry / stats.holdCount;
|
|
160
164
|
stats.avgLeverage = stats.sumLev / stats.holdCount;
|
|
@@ -162,34 +166,27 @@ class UserHistoryReconstructor {
|
|
|
162
166
|
if (stats.didBuy > 0) {
|
|
163
167
|
stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
|
|
164
168
|
}
|
|
165
|
-
|
|
166
|
-
// Cleanup temp sums
|
|
167
|
-
delete stats.sumEntry;
|
|
168
|
-
delete stats.sumLev;
|
|
169
|
-
delete stats.sumBuyLev;
|
|
170
|
-
delete stats.holdCount;
|
|
169
|
+
delete stats.sumEntry; delete stats.sumLev; delete stats.sumBuyLev; delete stats.holdCount;
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
//
|
|
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.
|
|
174
175
|
if (Object.keys(dayStats).length > 0) {
|
|
175
|
-
|
|
176
|
+
if (isEarliestRun) {
|
|
177
|
+
userTimeline[dateStr] = dayStats;
|
|
178
|
+
} else if (dateStr === currentExecutionDateStr) {
|
|
179
|
+
userTimeline[dateStr] = dayStats;
|
|
180
|
+
}
|
|
176
181
|
}
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
// 4. Output the full timeline for this user
|
|
180
|
-
// The ResultCommitter will handle splitting this into date buckets.
|
|
181
184
|
this.results[user.id] = userTimeline;
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
initTickerStats(dayStats, ticker) {
|
|
185
188
|
dayStats[ticker] = {
|
|
186
|
-
isHolder: 0,
|
|
187
|
-
didBuy: 0,
|
|
188
|
-
didSell: 0,
|
|
189
|
-
sumEntry: 0,
|
|
190
|
-
sumLev: 0,
|
|
191
|
-
holdCount: 0,
|
|
192
|
-
sumBuyLev: 0,
|
|
189
|
+
isHolder: 0, didBuy: 0, didSell: 0, sumEntry: 0, sumLev: 0, holdCount: 0, sumBuyLev: 0,
|
|
193
190
|
closeReasons: { "0": 0, "1": 0, "5": 0 }
|
|
194
191
|
};
|
|
195
192
|
}
|
|
@@ -49,8 +49,8 @@ class SqueezePotential {
|
|
|
49
49
|
|
|
50
50
|
for (const ticker of tickers) {
|
|
51
51
|
const shortSlAvg = signals.getMetric(computed, 'speculator-stop-loss-distance-by-ticker-short-long-breakdown', ticker, 'avg_short_sl_distance_pct');
|
|
52
|
-
const userCount
|
|
53
|
-
const weight
|
|
52
|
+
const userCount = signals.getMetric(computed, 'short-position-per-stock', ticker, 'short_count');
|
|
53
|
+
const weight = signals.getMetric(computed, 'short-position-per-stock', ticker, 'total_short_exposure_weight');
|
|
54
54
|
|
|
55
55
|
if (userCount > 0) {
|
|
56
56
|
result[ticker] = {
|