bulltrackers-module 1.0.733 → 1.0.734
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.
- package/functions/computation-system-v2/README.md +152 -0
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +720 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +176 -0
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +294 -0
- package/functions/computation-system-v2/computations/TestComputation.js +46 -0
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +172 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +317 -0
- package/functions/computation-system-v2/framework/core/Computation.js +73 -0
- package/functions/computation-system-v2/framework/core/Manifest.js +223 -0
- package/functions/computation-system-v2/framework/core/RuleInjector.js +53 -0
- package/functions/computation-system-v2/framework/core/Rules.js +231 -0
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +163 -0
- package/functions/computation-system-v2/framework/cost/CostTracker.js +154 -0
- package/functions/computation-system-v2/framework/data/DataFetcher.js +399 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +232 -0
- package/functions/computation-system-v2/framework/data/SchemaRegistry.js +287 -0
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +498 -0
- package/functions/computation-system-v2/framework/execution/TaskRunner.js +35 -0
- package/functions/computation-system-v2/framework/execution/middleware/CostTrackerMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +32 -0
- package/functions/computation-system-v2/framework/execution/middleware/Middleware.js +14 -0
- package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +47 -0
- package/functions/computation-system-v2/framework/index.js +45 -0
- package/functions/computation-system-v2/framework/lineage/LineageTracker.js +147 -0
- package/functions/computation-system-v2/framework/monitoring/Profiler.js +80 -0
- package/functions/computation-system-v2/framework/resilience/Checkpointer.js +66 -0
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +327 -0
- package/functions/computation-system-v2/framework/storage/StateRepository.js +286 -0
- package/functions/computation-system-v2/framework/storage/StorageManager.js +469 -0
- package/functions/computation-system-v2/framework/storage/index.js +9 -0
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +86 -0
- package/functions/computation-system-v2/framework/utils/Graph.js +205 -0
- package/functions/computation-system-v2/handlers/dispatcher.js +109 -0
- package/functions/computation-system-v2/handlers/index.js +23 -0
- package/functions/computation-system-v2/handlers/onDemand.js +289 -0
- package/functions/computation-system-v2/handlers/scheduler.js +327 -0
- package/functions/computation-system-v2/index.js +163 -0
- package/functions/computation-system-v2/rules/index.js +49 -0
- package/functions/computation-system-v2/rules/instruments.js +465 -0
- package/functions/computation-system-v2/rules/metrics.js +304 -0
- package/functions/computation-system-v2/rules/portfolio.js +534 -0
- package/functions/computation-system-v2/rules/rankings.js +655 -0
- package/functions/computation-system-v2/rules/social.js +562 -0
- package/functions/computation-system-v2/rules/trades.js +545 -0
- package/functions/computation-system-v2/scripts/migrate-sectors.js +73 -0
- package/functions/computation-system-v2/test/test-dispatcher.js +317 -0
- package/functions/computation-system-v2/test/test-framework.js +500 -0
- package/functions/computation-system-v2/test/test-real-execution.js +166 -0
- package/functions/computation-system-v2/test/test-real-integration.js +194 -0
- package/functions/computation-system-v2/test/test-refactor-e2e.js +131 -0
- package/functions/computation-system-v2/test/test-results.json +31 -0
- package/functions/computation-system-v2/test/test-risk-metrics-computation.js +329 -0
- package/functions/computation-system-v2/test/test-scheduler.js +204 -0
- package/functions/computation-system-v2/test/test-storage.js +449 -0
- package/functions/orchestrator/index.js +18 -26
- package/package.json +3 -2
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unified Computation Scheduler
|
|
3
|
+
*
|
|
4
|
+
* Single Cloud Function triggered every minute by Cloud Scheduler.
|
|
5
|
+
* Checks all computations, dispatches those that are due to Cloud Tasks.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
*
|
|
9
|
+
* Cloud Scheduler (every minute, * * * * *)
|
|
10
|
+
* │
|
|
11
|
+
* ▼
|
|
12
|
+
* ┌─────────────────────────────────────────────┐
|
|
13
|
+
* │ Scheduler Cloud Function (this file) │
|
|
14
|
+
* │ 1. Floor current time to minute boundary │
|
|
15
|
+
* │ 2. Check each computation's schedule │
|
|
16
|
+
* │ 3. Enqueue due computations to Cloud Tasks │
|
|
17
|
+
* └─────────────────────────────────────────────┘
|
|
18
|
+
* │
|
|
19
|
+
* ▼ (via Cloud Tasks queue)
|
|
20
|
+
* ┌─────────────────────────────────────────────┐
|
|
21
|
+
* │ Dispatcher Cloud Function │
|
|
22
|
+
* │ - Validates dependencies │
|
|
23
|
+
* │ - Executes computation │
|
|
24
|
+
* │ - Returns 503 if blocked (Cloud Tasks │
|
|
25
|
+
* │ will retry with backoff) │
|
|
26
|
+
* └─────────────────────────────────────────────┘
|
|
27
|
+
*
|
|
28
|
+
* Clock Drift Handling:
|
|
29
|
+
* - Scheduler might run at 14:00:58 instead of 14:00:00
|
|
30
|
+
* - We floor to minute boundary: 14:00:58 → 14:00
|
|
31
|
+
* - Schedule check uses 14:00, payload uses 14:00
|
|
32
|
+
* - System behaves as if it ran exactly on time
|
|
33
|
+
*
|
|
34
|
+
* Rate Limiting:
|
|
35
|
+
* - Uses p-limit to control concurrent Cloud Tasks API calls
|
|
36
|
+
* - Prevents hitting GCP API quotas
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const { CloudTasksClient } = require('@google-cloud/tasks');
|
|
40
|
+
const pLimit = require('p-limit');
|
|
41
|
+
const { ManifestBuilder, ScheduleValidator } = require('../framework');
|
|
42
|
+
const config = require('../config/bulltrackers.config');
|
|
43
|
+
|
|
44
|
+
// Concurrency limit for Cloud Tasks API calls
|
|
45
|
+
const CLOUD_TASKS_CONCURRENCY = 10;
|
|
46
|
+
|
|
47
|
+
// Singleton instances
|
|
48
|
+
let manifest = null;
|
|
49
|
+
let scheduleValidator = null;
|
|
50
|
+
let tasksClient = null;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Initialize manifest and schedule validator.
|
|
54
|
+
*/
|
|
55
|
+
async function initialize() {
|
|
56
|
+
if (manifest) return;
|
|
57
|
+
|
|
58
|
+
console.log('[Scheduler] Initializing...');
|
|
59
|
+
|
|
60
|
+
const builder = new ManifestBuilder(config, { log: (l, m) => console.log(`[${l}] ${m}`) });
|
|
61
|
+
manifest = builder.build(config.computations || []);
|
|
62
|
+
scheduleValidator = builder.getScheduleValidator();
|
|
63
|
+
tasksClient = new CloudTasksClient();
|
|
64
|
+
|
|
65
|
+
console.log(`[Scheduler] Initialized with ${manifest.length} computations`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Main scheduler handler.
|
|
70
|
+
* Triggered by Cloud Scheduler every minute.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} req - HTTP request
|
|
73
|
+
* @param {Object} res - HTTP response
|
|
74
|
+
*/
|
|
75
|
+
async function schedulerHandler(req, res) {
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await initialize();
|
|
80
|
+
|
|
81
|
+
// Get current time, floored to minute boundary
|
|
82
|
+
// This handles clock drift - if we run at 14:00:58, we treat it as 14:00:00
|
|
83
|
+
const now = floorToMinute(new Date());
|
|
84
|
+
const targetDate = formatDate(now);
|
|
85
|
+
const currentTime = formatTime(now);
|
|
86
|
+
|
|
87
|
+
console.log(`[Scheduler] Running for ${targetDate} ${currentTime}`);
|
|
88
|
+
|
|
89
|
+
// Find computations due at this time
|
|
90
|
+
const dueComputations = findDueComputations(now);
|
|
91
|
+
|
|
92
|
+
if (dueComputations.length === 0) {
|
|
93
|
+
console.log(`[Scheduler] No computations due at ${currentTime}`);
|
|
94
|
+
return res.status(200).json({
|
|
95
|
+
status: 'ok',
|
|
96
|
+
time: currentTime,
|
|
97
|
+
dispatched: 0,
|
|
98
|
+
message: 'No computations due'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`[Scheduler] ${dueComputations.length} computations due: ${dueComputations.map(c => c.name).join(', ')}`);
|
|
103
|
+
|
|
104
|
+
// Dispatch to Cloud Tasks with rate limiting
|
|
105
|
+
// Pass 'now' for idempotent task naming (retry-safe)
|
|
106
|
+
const results = await dispatchComputations(dueComputations, targetDate, now);
|
|
107
|
+
|
|
108
|
+
const duration = Date.now() - startTime;
|
|
109
|
+
const succeeded = results.filter(r => r.status === 'dispatched').length;
|
|
110
|
+
const failed = results.filter(r => r.status === 'error').length;
|
|
111
|
+
|
|
112
|
+
console.log(`[Scheduler] Dispatched ${succeeded}/${dueComputations.length} in ${duration}ms`);
|
|
113
|
+
|
|
114
|
+
return res.status(200).json({
|
|
115
|
+
status: 'ok',
|
|
116
|
+
time: currentTime,
|
|
117
|
+
date: targetDate,
|
|
118
|
+
dispatched: succeeded,
|
|
119
|
+
failed,
|
|
120
|
+
duration,
|
|
121
|
+
results
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[Scheduler] Error:', error);
|
|
126
|
+
return res.status(500).json({
|
|
127
|
+
status: 'error',
|
|
128
|
+
message: error.message
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Find all computations that are due at the given time.
|
|
135
|
+
*
|
|
136
|
+
* @param {Date} now - Current time (floored to minute)
|
|
137
|
+
* @returns {Array} Array of manifest entries that are due
|
|
138
|
+
*/
|
|
139
|
+
function findDueComputations(now) {
|
|
140
|
+
const due = [];
|
|
141
|
+
const currentHour = now.getUTCHours();
|
|
142
|
+
const currentMinute = now.getUTCMinutes();
|
|
143
|
+
const currentTime = `${String(currentHour).padStart(2, '0')}:${String(currentMinute).padStart(2, '0')}`;
|
|
144
|
+
|
|
145
|
+
const dayOfWeek = now.getUTCDay(); // 0 = Sunday
|
|
146
|
+
const dayOfMonth = now.getUTCDate(); // 1-31
|
|
147
|
+
|
|
148
|
+
for (const entry of manifest) {
|
|
149
|
+
const schedule = entry.schedule;
|
|
150
|
+
|
|
151
|
+
// Check if this computation is due now
|
|
152
|
+
if (isScheduleDue(schedule, currentTime, dayOfWeek, dayOfMonth)) {
|
|
153
|
+
due.push(entry);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return due;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if a schedule is due at the given time.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} schedule - Schedule object
|
|
164
|
+
* @param {string} currentTime - Current time in HH:MM format
|
|
165
|
+
* @param {number} dayOfWeek - Day of week (0-6, Sunday=0)
|
|
166
|
+
* @param {number} dayOfMonth - Day of month (1-31)
|
|
167
|
+
* @returns {boolean}
|
|
168
|
+
*/
|
|
169
|
+
function isScheduleDue(schedule, currentTime, dayOfWeek, dayOfMonth) {
|
|
170
|
+
const scheduleTime = schedule.time || '02:00';
|
|
171
|
+
const [scheduleHour, scheduleMinute] = scheduleTime.split(':').map(Number);
|
|
172
|
+
const [currentHour, currentMinuteNum] = currentTime.split(':').map(Number);
|
|
173
|
+
|
|
174
|
+
// Check frequency-specific conditions
|
|
175
|
+
switch (schedule.frequency) {
|
|
176
|
+
case 'hourly':
|
|
177
|
+
// Hourly runs every hour at the specified minute
|
|
178
|
+
// e.g., time: '00:30' means run at XX:30
|
|
179
|
+
// Only check the minute portion matches
|
|
180
|
+
return scheduleMinute === currentMinuteNum;
|
|
181
|
+
|
|
182
|
+
case 'daily':
|
|
183
|
+
// Daily runs at exact time (hour:minute must match)
|
|
184
|
+
return scheduleTime === currentTime;
|
|
185
|
+
|
|
186
|
+
case 'weekly':
|
|
187
|
+
// Weekly runs at exact time on specified day
|
|
188
|
+
if (scheduleTime !== currentTime) return false;
|
|
189
|
+
const targetDay = schedule.dayOfWeek ?? 0; // Default Sunday
|
|
190
|
+
return dayOfWeek === targetDay;
|
|
191
|
+
|
|
192
|
+
case 'monthly':
|
|
193
|
+
// Monthly runs at exact time on specified day of month
|
|
194
|
+
if (scheduleTime !== currentTime) return false;
|
|
195
|
+
const targetDayOfMonth = schedule.dayOfMonth ?? 1; // Default 1st
|
|
196
|
+
return dayOfMonth === targetDayOfMonth;
|
|
197
|
+
|
|
198
|
+
default:
|
|
199
|
+
// Unknown frequency, default to daily behavior
|
|
200
|
+
return scheduleTime === currentTime;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Dispatch computations to Cloud Tasks queue.
|
|
206
|
+
* Uses p-limit for rate limiting.
|
|
207
|
+
*
|
|
208
|
+
* @param {Array} computations - Array of manifest entries
|
|
209
|
+
* @param {string} targetDate - Target date (YYYY-MM-DD)
|
|
210
|
+
* @param {Date} scheduledTime - The floored time this scheduler run represents (for idempotent task names)
|
|
211
|
+
* @returns {Promise<Array>} Results for each dispatch
|
|
212
|
+
*/
|
|
213
|
+
async function dispatchComputations(computations, targetDate, scheduledTime) {
|
|
214
|
+
const limit = pLimit(CLOUD_TASKS_CONCURRENCY);
|
|
215
|
+
|
|
216
|
+
const { projectId, location, queueName, dispatcherUrl } = config.cloudTasks;
|
|
217
|
+
const queuePath = tasksClient.queuePath(projectId, location, queueName);
|
|
218
|
+
|
|
219
|
+
// Use the floored scheduledTime for idempotent task naming
|
|
220
|
+
// This ensures retries or slow loops don't create duplicate tasks
|
|
221
|
+
const timeSlot = formatTimeCompact(scheduledTime);
|
|
222
|
+
|
|
223
|
+
const tasks = computations.map(entry => limit(async () => {
|
|
224
|
+
try {
|
|
225
|
+
const taskPayload = {
|
|
226
|
+
computationName: entry.originalName,
|
|
227
|
+
targetDate,
|
|
228
|
+
source: 'scheduled',
|
|
229
|
+
scheduledAt: scheduledTime.toISOString()
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const task = {
|
|
233
|
+
httpRequest: {
|
|
234
|
+
httpMethod: 'POST',
|
|
235
|
+
url: dispatcherUrl,
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/json'
|
|
238
|
+
},
|
|
239
|
+
body: Buffer.from(JSON.stringify(taskPayload)).toString('base64'),
|
|
240
|
+
// OIDC token for authenticated Cloud Function invocation
|
|
241
|
+
// The Dispatcher should be deployed with "Require authentication"
|
|
242
|
+
oidcToken: {
|
|
243
|
+
serviceAccountEmail: config.cloudTasks.serviceAccountEmail,
|
|
244
|
+
audience: dispatcherUrl
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
// Task name uses the floored time slot - idempotent across retries
|
|
248
|
+
// If scheduler runs twice for the same minute, Cloud Tasks deduplicates
|
|
249
|
+
name: `${queuePath}/tasks/${entry.name}-${targetDate}-${timeSlot}`
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
await tasksClient.createTask({ parent: queuePath, task });
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
computation: entry.originalName,
|
|
256
|
+
status: 'dispatched',
|
|
257
|
+
targetDate
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
} catch (error) {
|
|
261
|
+
// Handle "already exists" gracefully (duplicate prevention)
|
|
262
|
+
if (error.code === 6) { // ALREADY_EXISTS
|
|
263
|
+
return {
|
|
264
|
+
computation: entry.originalName,
|
|
265
|
+
status: 'skipped',
|
|
266
|
+
reason: 'Task already exists (duplicate prevention)'
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.error(`[Scheduler] Failed to dispatch ${entry.originalName}:`, error.message);
|
|
271
|
+
return {
|
|
272
|
+
computation: entry.originalName,
|
|
273
|
+
status: 'error',
|
|
274
|
+
error: error.message
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
return Promise.all(tasks);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Floor a date to the nearest minute boundary.
|
|
284
|
+
* 14:00:58 → 14:00:00
|
|
285
|
+
*/
|
|
286
|
+
function floorToMinute(date) {
|
|
287
|
+
const floored = new Date(date);
|
|
288
|
+
floored.setUTCSeconds(0);
|
|
289
|
+
floored.setUTCMilliseconds(0);
|
|
290
|
+
return floored;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Format date as YYYY-MM-DD.
|
|
295
|
+
*/
|
|
296
|
+
function formatDate(date) {
|
|
297
|
+
return date.toISOString().split('T')[0];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Format time as HH:MM.
|
|
302
|
+
*/
|
|
303
|
+
function formatTime(date) {
|
|
304
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
305
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
306
|
+
return `${hours}:${minutes}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Format time as HHMM (compact, for task names).
|
|
311
|
+
*/
|
|
312
|
+
function formatTimeCompact(date) {
|
|
313
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
314
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
315
|
+
return `${hours}${minutes}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Export for Cloud Functions
|
|
319
|
+
module.exports = {
|
|
320
|
+
schedulerHandler,
|
|
321
|
+
initialize,
|
|
322
|
+
|
|
323
|
+
// For testing
|
|
324
|
+
_findDueComputations: findDueComputations,
|
|
325
|
+
_isScheduleDue: isScheduleDue,
|
|
326
|
+
_floorToMinute: floorToMinute
|
|
327
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Computation System v2 Entry Point
|
|
3
|
+
* * Usage:
|
|
4
|
+
* const { execute, analyze, runComputation } = require('./computation-system-v2');
|
|
5
|
+
* * // Analyze what can run
|
|
6
|
+
* const report = await analyze({ date: '2026-01-24' });
|
|
7
|
+
* * // Execute computations (Monolith/Scheduler mode)
|
|
8
|
+
* const result = await execute({ date: '2026-01-24' });
|
|
9
|
+
* * // Run single computation (Worker mode)
|
|
10
|
+
* const task = await runComputation({
|
|
11
|
+
* date: '2026-01-24',
|
|
12
|
+
* computation: 'UserPortfolioSummary'
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { Orchestrator } = require('./framework');
|
|
17
|
+
const config = require('./config/bulltrackers.config');
|
|
18
|
+
|
|
19
|
+
// Add computations to config
|
|
20
|
+
config.computations = [
|
|
21
|
+
require('./computations/UserPortfolioSummary'),
|
|
22
|
+
require('./computations/PopularInvestorProfileMetrics'),
|
|
23
|
+
require('./computations/PopularInvestorRiskAssessment'),
|
|
24
|
+
// Add more computations here as they're migrated
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Singleton orchestrator instance
|
|
28
|
+
let orchestrator = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get or create the orchestrator instance.
|
|
32
|
+
* @param {Object} [customConfig] - Override default config
|
|
33
|
+
* @param {Object} [logger] - Custom logger
|
|
34
|
+
* @returns {Promise<Orchestrator>}
|
|
35
|
+
*/
|
|
36
|
+
async function getOrchestrator(customConfig = null, logger = null) {
|
|
37
|
+
if (!orchestrator || customConfig) {
|
|
38
|
+
const cfg = customConfig || config;
|
|
39
|
+
orchestrator = new Orchestrator(cfg, logger);
|
|
40
|
+
// Note: Orchestrator initializes lazily on first execute/analyze call,
|
|
41
|
+
// but we can force it here if needed.
|
|
42
|
+
}
|
|
43
|
+
return orchestrator;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Analyze what can run for a given date.
|
|
48
|
+
* @param {Object} options
|
|
49
|
+
* @param {string} options.date - Target date (YYYY-MM-DD)
|
|
50
|
+
* @param {Object} [options.config] - Override config
|
|
51
|
+
* @param {Object} [options.logger] - Custom logger
|
|
52
|
+
* @returns {Promise<Object>} Analysis report
|
|
53
|
+
*/
|
|
54
|
+
async function analyze(options) {
|
|
55
|
+
const { date, config: customConfig = null, logger = null } = options;
|
|
56
|
+
const orch = await getOrchestrator(customConfig, logger);
|
|
57
|
+
return orch.analyze({ date });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute computations for a given date.
|
|
62
|
+
* (Used by Scheduler or Monolithic runs)
|
|
63
|
+
*/
|
|
64
|
+
async function execute(options) {
|
|
65
|
+
const {
|
|
66
|
+
date,
|
|
67
|
+
pass = null,
|
|
68
|
+
computation = null,
|
|
69
|
+
dryRun = false,
|
|
70
|
+
entities = null,
|
|
71
|
+
config: customConfig = null,
|
|
72
|
+
logger = null
|
|
73
|
+
} = options;
|
|
74
|
+
|
|
75
|
+
const orch = await getOrchestrator(customConfig, logger);
|
|
76
|
+
return orch.execute({ date, pass, computation, dryRun, entities });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* WORKER ENTRY POINT: Run a single computation.
|
|
81
|
+
* (Used by Cloud Functions / Dispatcher)
|
|
82
|
+
*/
|
|
83
|
+
async function runComputation(options) {
|
|
84
|
+
const {
|
|
85
|
+
date,
|
|
86
|
+
computation,
|
|
87
|
+
entityIds = null,
|
|
88
|
+
dryRun = false,
|
|
89
|
+
config: customConfig = null,
|
|
90
|
+
logger = null
|
|
91
|
+
} = options;
|
|
92
|
+
|
|
93
|
+
const orch = await getOrchestrator(customConfig, logger);
|
|
94
|
+
|
|
95
|
+
// 1. Ensure manifest is built
|
|
96
|
+
if (!orch.manifest) await orch.initialize();
|
|
97
|
+
|
|
98
|
+
// 2. Find the specific entry
|
|
99
|
+
// We normalize to handle case-insensitivity
|
|
100
|
+
const normalizedName = computation.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
101
|
+
const entry = orch.manifest.find(c => c.name === normalizedName);
|
|
102
|
+
|
|
103
|
+
if (!entry) {
|
|
104
|
+
throw new Error(`Computation not found: ${computation}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. Delegate to Orchestrator's runSingle
|
|
108
|
+
// This handles dependencies, data fetching, middleware, etc.
|
|
109
|
+
return orch.runSingle(entry, date, {
|
|
110
|
+
entityIds,
|
|
111
|
+
dryRun
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get the manifest (list of all computations with metadata).
|
|
117
|
+
*/
|
|
118
|
+
async function getManifest(options = {}) {
|
|
119
|
+
const { config: customConfig = null, logger = null } = options;
|
|
120
|
+
const orch = await getOrchestrator(customConfig, logger);
|
|
121
|
+
if (!orch.manifest) await orch.initialize();
|
|
122
|
+
return orch.manifest;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Warm the schema cache for all tables.
|
|
127
|
+
*/
|
|
128
|
+
async function warmCache(options = {}) {
|
|
129
|
+
const { config: customConfig = null, logger = null } = options;
|
|
130
|
+
const orch = await getOrchestrator(customConfig, logger);
|
|
131
|
+
const allTables = Object.keys(orch.config.tables);
|
|
132
|
+
return orch.schemaRegistry.warmCache(allTables);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Reset the singleton (useful for testing).
|
|
137
|
+
*/
|
|
138
|
+
function reset() {
|
|
139
|
+
orchestrator = null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Import handlers for Cloud Functions
|
|
143
|
+
const handlers = require('./handlers');
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
// Main API
|
|
147
|
+
analyze,
|
|
148
|
+
execute,
|
|
149
|
+
runComputation, // <--- New Method
|
|
150
|
+
getManifest,
|
|
151
|
+
warmCache,
|
|
152
|
+
reset,
|
|
153
|
+
|
|
154
|
+
// For advanced usage
|
|
155
|
+
getOrchestrator,
|
|
156
|
+
config,
|
|
157
|
+
|
|
158
|
+
// Cloud Function handlers
|
|
159
|
+
...handlers,
|
|
160
|
+
|
|
161
|
+
// Re-export framework components
|
|
162
|
+
...require('./framework')
|
|
163
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Business Rules Index
|
|
3
|
+
*
|
|
4
|
+
* Central registry of all business rules.
|
|
5
|
+
* These are automatically injected into computations.
|
|
6
|
+
*
|
|
7
|
+
* Available rule modules:
|
|
8
|
+
* - portfolio: Portfolio snapshots (positions, value, exposure)
|
|
9
|
+
* - metrics: Financial metrics (Sharpe, Sortino, VaR, etc.)
|
|
10
|
+
* - rankings: PI rankings data (AUM, copiers, risk scores)
|
|
11
|
+
* - trades: Trade history (closed positions, stats)
|
|
12
|
+
* - social: Social data (posts, ratings, page views)
|
|
13
|
+
* - instruments: Instrument data (prices, sentiment, tickers)
|
|
14
|
+
*
|
|
15
|
+
* Rules are available in computations as:
|
|
16
|
+
* rules.portfolio.extractPositions(...)
|
|
17
|
+
* rules.metrics.calculateSharpeRatio(...)
|
|
18
|
+
* rules.rankings.getRiskScore(...)
|
|
19
|
+
* rules.trades.calculateTradeStats(...)
|
|
20
|
+
* rules.social.calculateEngagement(...)
|
|
21
|
+
* rules.instruments.getPrice(...)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const portfolio = require('./portfolio');
|
|
25
|
+
const metrics = require('./metrics');
|
|
26
|
+
const rankings = require('./rankings');
|
|
27
|
+
const trades = require('./trades');
|
|
28
|
+
const social = require('./social');
|
|
29
|
+
const instruments = require('./instruments');
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
// Portfolio snapshots - positions, value, exposure, direction
|
|
33
|
+
portfolio,
|
|
34
|
+
|
|
35
|
+
// Financial metrics - Sharpe, Sortino, drawdown, VaR
|
|
36
|
+
metrics,
|
|
37
|
+
|
|
38
|
+
// PI rankings - AUM, copiers, risk scores, trading style
|
|
39
|
+
rankings,
|
|
40
|
+
|
|
41
|
+
// Trade history - closed trades, win rate, P&L stats
|
|
42
|
+
trades,
|
|
43
|
+
|
|
44
|
+
// Social data - posts, ratings, page views, engagement
|
|
45
|
+
social,
|
|
46
|
+
|
|
47
|
+
// Instruments - prices, sentiment, tickers
|
|
48
|
+
instruments
|
|
49
|
+
};
|