bulltrackers-module 1.0.206 → 1.0.207
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,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* FILENAME: CloudFunctions/NpmWrappers/bulltrackers-module/functions/task-engine/helpers/update_helpers.js
|
|
3
|
+
* (OPTIMIZED V4: Auto-Speculator Detection via History/Portfolio Intersection)
|
|
3
4
|
* (OPTIMIZED V3: Removed obsolete username lookup logic)
|
|
4
5
|
* (OPTIMIZED V2: Added "Circuit Breaker" for Proxy failures)
|
|
5
6
|
* (REFACTORED: Concurrency set to 1, added fallback and verbose logging)
|
|
@@ -26,27 +27,54 @@ function shouldTryProxy() {
|
|
|
26
27
|
*/
|
|
27
28
|
function recordProxyOutcome(success) {
|
|
28
29
|
if (success) {
|
|
29
|
-
if (_consecutiveProxyFailures > 0) {
|
|
30
|
-
// Optional: Only log recovery to reduce noise
|
|
31
|
-
}
|
|
32
30
|
_consecutiveProxyFailures = 0;
|
|
33
31
|
} else {
|
|
34
32
|
_consecutiveProxyFailures++;
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
/**
|
|
37
|
+
* --- NEW HELPER: Speculator Detector ---
|
|
38
|
+
* intersections: (History: Leverage > 1) AND (Portfolio: Currently Owned)
|
|
39
|
+
*/
|
|
40
|
+
function detectSpeculatorTargets(historyData, portfolioData) {
|
|
41
|
+
if (!historyData?.PublicHistoryPositions || !portfolioData?.AggregatedPositions) return [];
|
|
42
|
+
|
|
43
|
+
// 1. Identify assets that have EVER been traded with leverage > 1
|
|
44
|
+
const leveragedAssets = new Set();
|
|
45
|
+
for (const pos of historyData.PublicHistoryPositions) {
|
|
46
|
+
if (pos.Leverage > 1 && pos.InstrumentID) {
|
|
47
|
+
leveragedAssets.add(pos.InstrumentID);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (leveragedAssets.size === 0) return [];
|
|
52
|
+
|
|
53
|
+
// 2. Check if the user CURRENTLY owns any of these assets
|
|
54
|
+
const targets = [];
|
|
55
|
+
for (const pos of portfolioData.AggregatedPositions) {
|
|
56
|
+
if (leveragedAssets.has(pos.InstrumentID)) {
|
|
57
|
+
targets.push(pos.InstrumentID);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return targets;
|
|
62
|
+
}
|
|
63
|
+
|
|
38
64
|
/**
|
|
39
65
|
* (REFACTORED: Fully sequential, verbose logging, node-fetch fallback)
|
|
40
66
|
*/
|
|
41
|
-
async function handleUpdate(task, taskId, { logger, headerManager, proxyManager, db, batchManager }, config) {
|
|
42
|
-
// Note: 'username' param removed from signature as it is no longer needed.
|
|
67
|
+
async function handleUpdate(task, taskId, { logger, headerManager, proxyManager, db, batchManager, pubsub }, config) {
|
|
43
68
|
const { userId, instruments, instrumentId, userType } = task;
|
|
44
69
|
const instrumentsToProcess = userType === 'speculator' ? (instruments || [instrumentId]) : [undefined];
|
|
45
70
|
const today = new Date().toISOString().slice(0, 10);
|
|
46
71
|
const portfolioBlockId = `${Math.floor(parseInt(userId) / 1000000)}M`;
|
|
47
72
|
let isPrivate = false;
|
|
48
73
|
|
|
49
|
-
//
|
|
74
|
+
// Captured data for detection logic
|
|
75
|
+
let capturedHistory = null;
|
|
76
|
+
let capturedPortfolio = null;
|
|
77
|
+
|
|
50
78
|
logger.log('TRACE', `[handleUpdate/${userId}] Starting update task. Type: ${userType}. Instruments: ${instrumentsToProcess.join(', ')}`);
|
|
51
79
|
|
|
52
80
|
// --- 1. Process History Fetch (Sequentially) ---
|
|
@@ -63,11 +91,10 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
63
91
|
} else {
|
|
64
92
|
|
|
65
93
|
// --- REFACTOR: New Granular API Logic ---
|
|
66
|
-
// No username required. Uses CID (userId) directly.
|
|
67
94
|
const d = new Date();
|
|
68
95
|
d.setFullYear(d.getFullYear() - 1);
|
|
69
96
|
const oneYearAgoStr = d.toISOString();
|
|
70
|
-
const uuid = crypto.randomUUID ? crypto.randomUUID() : '0205aca7-bd37-4884-8455-f28ce1add2de';
|
|
97
|
+
const uuid = crypto.randomUUID ? crypto.randomUUID() : '0205aca7-bd37-4884-8455-f28ce1add2de';
|
|
71
98
|
|
|
72
99
|
const historyUrl = `https://www.etoro.com/sapi/trade-data-real/history/public/credit/flat?StartTime=${oneYearAgoStr}&PageNumber=1&ItemsPerPage=30000&PublicHistoryPortfolioFilter=&CID=${userId}&client_request_id=${uuid}`;
|
|
73
100
|
const options = { headers: historyHeader.header };
|
|
@@ -79,13 +106,12 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
79
106
|
logger.log('TRACE', `[handleUpdate/${userId}] Attempting history fetch via AppScript proxy...`);
|
|
80
107
|
response = await proxyManager.fetch(historyUrl, options);
|
|
81
108
|
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
82
|
-
|
|
83
109
|
wasHistorySuccess = true;
|
|
84
110
|
proxyUsedForHistory = true;
|
|
85
|
-
recordProxyOutcome(true);
|
|
111
|
+
recordProxyOutcome(true);
|
|
86
112
|
|
|
87
113
|
} catch (proxyError) {
|
|
88
|
-
recordProxyOutcome(false);
|
|
114
|
+
recordProxyOutcome(false);
|
|
89
115
|
logger.log('WARN', `[handleUpdate/${userId}] History fetch via AppScript proxy FAILED. Error: ${proxyError.message}. Failures: ${_consecutiveProxyFailures}/${MAX_PROXY_FAILURES}.`, { error: proxyError.message, source: 'AppScript' });
|
|
90
116
|
}
|
|
91
117
|
}
|
|
@@ -105,6 +131,7 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
105
131
|
|
|
106
132
|
if (wasHistorySuccess) {
|
|
107
133
|
const data = await response.json();
|
|
134
|
+
capturedHistory = data; // Capture for later
|
|
108
135
|
await batchManager.addToTradingHistoryBatch(userId, portfolioBlockId, today, data, userType);
|
|
109
136
|
}
|
|
110
137
|
}
|
|
@@ -118,7 +145,7 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
// --- 2. Process Portfolio Fetches (Sequentially) ---
|
|
121
|
-
logger.log('TRACE', `[handleUpdate/${userId}] Starting ${instrumentsToProcess.length} sequential portfolio fetches.`);
|
|
148
|
+
logger.log('TRACE', `[handleUpdate/${userId}] Starting ${instrumentsToProcess.length} sequential portfolio fetches.`);
|
|
122
149
|
|
|
123
150
|
for (const instId of instrumentsToProcess) {
|
|
124
151
|
if (isPrivate) {
|
|
@@ -141,13 +168,12 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
141
168
|
logger.log('TRACE', `[handleUpdate/${userId}] Attempting portfolio fetch via AppScript proxy...`);
|
|
142
169
|
response = await proxyManager.fetch(portfolioUrl, options);
|
|
143
170
|
if (!response.ok) throw new Error(`AppScript proxy failed with status ${response.status}`);
|
|
144
|
-
|
|
145
171
|
wasPortfolioSuccess = true;
|
|
146
172
|
proxyUsedForPortfolio = true;
|
|
147
|
-
recordProxyOutcome(true);
|
|
173
|
+
recordProxyOutcome(true);
|
|
148
174
|
|
|
149
175
|
} catch (proxyError) {
|
|
150
|
-
recordProxyOutcome(false);
|
|
176
|
+
recordProxyOutcome(false);
|
|
151
177
|
logger.log('WARN', `[handleUpdate/${userId}] Portfolio fetch via Proxy FAILED. Error: ${proxyError.message}. Failures: ${_consecutiveProxyFailures}/${MAX_PROXY_FAILURES}.`, { error: proxyError.message, source: 'AppScript' });
|
|
152
178
|
}
|
|
153
179
|
}
|
|
@@ -172,8 +198,9 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
172
198
|
|
|
173
199
|
try {
|
|
174
200
|
const portfolioJson = JSON.parse(body);
|
|
201
|
+
capturedPortfolio = portfolioJson; // Capture for detection
|
|
175
202
|
await batchManager.addToPortfolioBatch(userId, portfolioBlockId, today, portfolioJson, userType, instId);
|
|
176
|
-
logger.log('TRACE', `[handleUpdate/${userId}] Portfolio processed successfully.`);
|
|
203
|
+
logger.log('TRACE', `[handleUpdate/${userId}] Portfolio processed successfully.`);
|
|
177
204
|
|
|
178
205
|
} catch (parseError) {
|
|
179
206
|
wasPortfolioSuccess = false;
|
|
@@ -186,7 +213,34 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
186
213
|
if (proxyUsedForPortfolio) { headerManager.updatePerformance(portfolioHeader.id, wasPortfolioSuccess); }
|
|
187
214
|
}
|
|
188
215
|
|
|
189
|
-
// --- 5.
|
|
216
|
+
// --- 5. SPECULATOR DETECTION & QUEUEING (NEW) ---
|
|
217
|
+
// Only run detection if:
|
|
218
|
+
// 1. We are processing a Normal User (userType !== 'speculator')
|
|
219
|
+
// 2. We successfully fetched both history and portfolio
|
|
220
|
+
// 3. We have PubSub available to queue new tasks
|
|
221
|
+
if (userType !== 'speculator' && capturedHistory && capturedPortfolio && pubsub && config.PUBSUB_TOPIC_TASK_ENGINE) {
|
|
222
|
+
try {
|
|
223
|
+
const speculatorAssets = detectSpeculatorTargets(capturedHistory, capturedPortfolio);
|
|
224
|
+
if (speculatorAssets.length > 0) {
|
|
225
|
+
logger.log('INFO', `[handleUpdate/${userId}] DETECTED SPECULATOR BEHAVIOR. Queuing ${speculatorAssets.length} targeted updates.`);
|
|
226
|
+
|
|
227
|
+
const newTasks = speculatorAssets.map(assetId => ({
|
|
228
|
+
type: 'update',
|
|
229
|
+
userType: 'speculator',
|
|
230
|
+
userId: userId,
|
|
231
|
+
instrumentId: assetId
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
// Publish to Task Engine (Tasks are wrapped in a 'tasks' array payload)
|
|
235
|
+
const dataBuffer = Buffer.from(JSON.stringify({ tasks: newTasks }));
|
|
236
|
+
await pubsub.topic(config.PUBSUB_TOPIC_TASK_ENGINE).publishMessage({ data: dataBuffer });
|
|
237
|
+
}
|
|
238
|
+
} catch (detectionError) {
|
|
239
|
+
logger.log('ERROR', `[handleUpdate/${userId}] Error during Speculator Detection.`, { error: detectionError.message });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// --- 6. Handle Private Users & Timestamps ---
|
|
190
244
|
if (isPrivate) {
|
|
191
245
|
logger.log('WARN', `[handleUpdate/${userId}] Removing private user from updates.`);
|
|
192
246
|
for (const instrumentId of instrumentsToProcess) {
|
|
@@ -207,7 +261,7 @@ async function handleUpdate(task, taskId, { logger, headerManager, proxyManager,
|
|
|
207
261
|
|
|
208
262
|
if (userType === 'speculator') { await batchManager.addSpeculatorTimestampFix(userId, String(Math.floor(userId/1e6)*1e6)); }
|
|
209
263
|
|
|
210
|
-
logger.log('TRACE', `[handleUpdate/${userId}] Update task finished successfully.`);
|
|
264
|
+
logger.log('TRACE', `[handleUpdate/${userId}] Update task finished successfully.`);
|
|
211
265
|
}
|
|
212
266
|
|
|
213
267
|
module.exports = { handleUpdate };
|