bulltrackers-module 1.0.206 → 1.0.208
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.
|
@@ -12,18 +12,30 @@
|
|
|
12
12
|
*/
|
|
13
13
|
async function checkDiscoveryNeed(userType, config, dependencies) {
|
|
14
14
|
const { logger, firestoreUtils } = dependencies;
|
|
15
|
+
const { targetUsersPerBlock, normalBlockCountsDocPath, allHighValueBlocks } = config;
|
|
16
|
+
|
|
17
|
+
// --- REFACTOR: ORGANIC SPECULATOR DISCOVERY ---
|
|
18
|
+
// We no longer "hunt" for speculators using block targets.
|
|
19
|
+
// Speculators are now discovered organically via the Normal User Update Loop (Feedback Loop).
|
|
20
|
+
if (userType === 'speculator') {
|
|
21
|
+
logger.log('INFO', '[Orchestrator Helpers] Speculator discovery is Organic/Feedback-Driven. Skipping active discovery checks.');
|
|
22
|
+
return { needsDiscovery: false, blocksToFill: [] };
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
logger.log('INFO', `[Orchestrator Helpers] Checking discovery need for ${userType}...`);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const blockCounts = await firestoreUtils.getBlockCapacities(dependencies, {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// Logic for Normal Users remains (fill blocks to target)
|
|
28
|
+
const blockCounts = await firestoreUtils.getBlockCapacities(dependencies, { normalBlockCountsDocPath }, userType);
|
|
29
|
+
|
|
30
|
+
const underPopulatedBlocks = allHighValueBlocks.filter(block => (blockCounts[block.startId] || 0) < targetUsersPerBlock);
|
|
31
|
+
const needsDiscovery = underPopulatedBlocks.length > 0;
|
|
32
|
+
|
|
33
|
+
if (!needsDiscovery) {
|
|
34
|
+
logger.log('SUCCESS', `✅ All blocks are at target capacity for ${userType} users.`);
|
|
35
|
+
} else {
|
|
36
|
+
logger.log('INFO', `Found ${underPopulatedBlocks.length} underpopulated blocks for ${userType}.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
return { needsDiscovery, blocksToFill: underPopulatedBlocks };
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -38,29 +50,70 @@ async function checkDiscoveryNeed(userType, config, dependencies) {
|
|
|
38
50
|
async function getDiscoveryCandidates(userType, blocksToFill, config, dependencies) {
|
|
39
51
|
const { logger, firestoreUtils } = dependencies;
|
|
40
52
|
logger.log('INFO', `[Orchestrator Helpers] Getting discovery candidates for ${userType}...`);
|
|
41
|
-
|
|
42
|
-
const
|
|
53
|
+
|
|
54
|
+
const {
|
|
55
|
+
maxTasksPerRun,
|
|
56
|
+
discoveryBatchSize,
|
|
57
|
+
maxRandomCidsToDiscover,
|
|
58
|
+
specBlocksCollection,
|
|
59
|
+
pendingSpecCollection,
|
|
60
|
+
normalUserCollection,
|
|
61
|
+
invalidSpecCollection,
|
|
62
|
+
snapshotsSubCollectionName,
|
|
63
|
+
partsSubCollectionName
|
|
64
|
+
} = config;
|
|
65
|
+
|
|
43
66
|
const dispatchedCids = new Set();
|
|
44
|
-
|
|
67
|
+
|
|
68
|
+
// Fetch exclusion list to avoid re-discovering existing users
|
|
69
|
+
// Note: For Normal users, we check against existing Normal users.
|
|
70
|
+
const latestNormalPortfolios = await firestoreUtils.getLatestNormalUserPortfolios(dependencies, {
|
|
71
|
+
normalUserCollectionName: normalUserCollection,
|
|
72
|
+
snapshotsSubCollectionName,
|
|
73
|
+
partsSubCollectionName
|
|
74
|
+
});
|
|
75
|
+
|
|
45
76
|
const existingNormalUserIds = new Set(Object.keys(latestNormalPortfolios));
|
|
46
|
-
const exclusionIds = await firestoreUtils.getExclusionIds(dependencies, {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
const exclusionIds = await firestoreUtils.getExclusionIds(dependencies, {
|
|
78
|
+
specBlocksCollection,
|
|
79
|
+
pendingSpecCollection,
|
|
80
|
+
normalUserCollection,
|
|
81
|
+
invalidSpecCollection,
|
|
82
|
+
existingNormalUserIds
|
|
83
|
+
}, userType);
|
|
84
|
+
|
|
85
|
+
// --- RANDOM DISCOVERY LOOP (For Normal Users) ---
|
|
51
86
|
let dispatchedRandomCidCount = 0;
|
|
52
|
-
|
|
53
|
-
while ((dispatchedRandomCidCount +
|
|
87
|
+
|
|
88
|
+
while ((dispatchedRandomCidCount + dispatchedCids.size) < maxRandomCidsToDiscover &&
|
|
89
|
+
dispatchedCids.size < maxTasksPerRun &&
|
|
90
|
+
blocksToFill.length > 0) {
|
|
91
|
+
|
|
54
92
|
const blockIndex = dispatchedCids.size % blocksToFill.length;
|
|
55
93
|
const block = blocksToFill[blockIndex];
|
|
56
94
|
if (!block) break;
|
|
95
|
+
|
|
57
96
|
for (let j = 0; j < discoveryBatchSize; j++) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
if ((dispatchedRandomCidCount + dispatchedCids.size) >= maxRandomCidsToDiscover ||
|
|
98
|
+
dispatchedCids.size >= maxTasksPerRun) break;
|
|
99
|
+
|
|
100
|
+
let randomId;
|
|
101
|
+
let retryCount = 0;
|
|
102
|
+
const MAX_RETRIES = 50;
|
|
103
|
+
|
|
104
|
+
do {
|
|
105
|
+
if (++retryCount > MAX_RETRIES) break;
|
|
106
|
+
// Generate random CID within the block range
|
|
107
|
+
randomId = String(Math.floor(Math.random() * 1000000) + block.startId);
|
|
108
|
+
} while (exclusionIds.has(randomId) || dispatchedCids.has(randomId));
|
|
109
|
+
|
|
110
|
+
if (retryCount <= MAX_RETRIES) {
|
|
111
|
+
dispatchedCids.add(randomId);
|
|
112
|
+
dispatchedRandomCidCount++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
64
117
|
logger.log('INFO', `Generated ${dispatchedRandomCidCount} random CIDs for ${userType} discovery.`);
|
|
65
118
|
logger.log('INFO', `Total candidates for dispatch: ${dispatchedCids.size}`);
|
|
66
119
|
return dispatchedCids;
|
|
@@ -75,34 +128,54 @@ async function getDiscoveryCandidates(userType, blocksToFill, config, dependenci
|
|
|
75
128
|
* @returns {Promise<void>}
|
|
76
129
|
*/
|
|
77
130
|
async function dispatchDiscovery(userType, candidates, config, dependencies) {
|
|
78
|
-
const { logger,
|
|
79
|
-
const {topicName,dispatchBatchSize
|
|
80
|
-
|
|
131
|
+
const { logger, pubsub } = dependencies;
|
|
132
|
+
const { topicName, dispatchBatchSize } = config;
|
|
133
|
+
|
|
134
|
+
if (candidates.size === 0) {
|
|
135
|
+
logger.log('INFO', `[Orchestrator Helpers] No ${userType} candidates to dispatch.`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
81
139
|
logger.log('INFO', `[Orchestrator Helpers] Dispatching ${candidates.size} discovery tasks for ${userType}...`);
|
|
82
|
-
|
|
140
|
+
|
|
83
141
|
const cidsArray = Array.from(candidates);
|
|
84
|
-
if (isSpeculator)
|
|
85
|
-
{await firestoreUtils.clearCollection(dependencies, pendingSpecCollection);
|
|
86
|
-
await firestoreUtils.batchWriteShardedIds(dependencies, {collectionPath: pendingSpecCollection,items: cidsArray, timestamp: new Date(),maxFieldsPerDoc: pendingMaxFieldsPerDoc,maxWritesPerBatch: pendingMaxWritesPerBatch});}
|
|
87
142
|
const tasks = [];
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < cidsArray.length; i += dispatchBatchSize) {
|
|
145
|
+
const batchCids = cidsArray.slice(i, i + dispatchBatchSize).map(cid => parseInt(cid));
|
|
146
|
+
if (batchCids.length > 0) {
|
|
147
|
+
const blockId = Math.floor(batchCids[0] / 1000000) * 1000000;
|
|
148
|
+
tasks.push({
|
|
149
|
+
type: 'discover',
|
|
150
|
+
cids: batchCids,
|
|
151
|
+
blockId,
|
|
152
|
+
userType
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
92
157
|
const topic = pubsub.topic(topicName);
|
|
93
158
|
let totalCidsPublished = 0;
|
|
94
159
|
let messagesPublished = 0;
|
|
160
|
+
|
|
95
161
|
for (let i = 0; i < tasks.length; i += dispatchBatchSize) {
|
|
96
162
|
const batchOfTasks = tasks.slice(i, i + dispatchBatchSize);
|
|
97
163
|
const messagePayload = { tasks: batchOfTasks };
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await topic.publishMessage({ json: messagePayload });
|
|
167
|
+
|
|
168
|
+
const cidsInThisMessage = batchOfTasks.reduce((acc, task) => acc + task.cids.length, 0);
|
|
169
|
+
totalCidsPublished += cidsInThisMessage;
|
|
170
|
+
messagesPublished++;
|
|
171
|
+
|
|
172
|
+
logger.log('INFO', `[Orchestrator Helpers] Dispatched batch ${messagesPublished} with ${batchOfTasks.length} discover tasks (${cidsInThisMessage} CIDs) as 1 Pub/Sub message.`);
|
|
173
|
+
} catch (publishError) {
|
|
174
|
+
logger.log('ERROR', `[Orchestrator Helpers] Failed to publish discover batch ${messagesPublished + 1}.`, { error: publishError.message });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
logger.log('SUCCESS', `[Orchestrator Helpers] Dispatched ${totalCidsPublished} CIDs in ${tasks.length} tasks for ${userType} discovery.`);
|
|
106
179
|
}
|
|
107
180
|
|
|
108
181
|
module.exports = { checkDiscoveryNeed, getDiscoveryCandidates, dispatchDiscovery };
|
|
@@ -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 };
|