bulltrackers-module 1.0.26 → 1.0.28

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/README.MD ADDED
@@ -0,0 +1,153 @@
1
+ # BullTrackers Module (`bulltrackers-module`)
2
+
3
+ **Version:** 1.0.27
4
+
5
+ ## Overview
6
+
7
+ This package encapsulates the core logic and shared utilities for the BullTrackers cloud function architecture. It promotes code reuse and separation of concerns by providing abstracted handlers and helper functions for various backend services.
8
+
9
+ ## Core Concepts (Based on your Documentation.md)
10
+
11
+ * **eToro User IDs (CIDs):** Sequential integers assigned upon registration.
12
+
13
+ eToro assigns a hidden UID to each users' account in order for their backend configurations to identify users without needing to reference a username.
14
+ This is a common fact across most modern sites, but eToro is slightly unique in that their UIDs are not randomised, they are sequential.
15
+ This means that UID 1 is the first account ever made, and UID 1 million is the 1 millionth account made.
16
+
17
+ * **Block Sampling:** Randomly sampling users within 1M ID blocks to ensure representative data across different registration cohorts.
18
+
19
+ Given how the UIDs are assigned on eToro, we can group users by their UID into 1m block ranges, and then from this we can sample users randomly to produce data across each Block.
20
+ Now there is a slight caveat to the current implementation, where our current implementation is designed to fill up a set capacity of users, per user type, per block range.
21
+ This is statistically biased, because there will be more public users in certain blocks than other blocks.
22
+ The future resolution to this, is to weight the block capacity by that blocks' ratio of public to private users, ensuring representative sampling.
23
+ For now, this is not implemented.
24
+
25
+ * **User Types:** Differentiating between 'normal' users and 'speculators' based on portfolio activity and specific asset holdings.
26
+
27
+ This system attempts to categorise user portfolios, and the users themselves, by types and (in the future, sub types).
28
+ These are based on the response of the rankings API call, which returns enough detail to figure out whether a user is speculative, normal, inactive or private.
29
+ In the future the plan is to create sub categories that state the users' risk group alongside their main category.
30
+ Private is not a category, it is rather the lack of a category. Users who are private, are implicitely removed from the response of the API, auto-filtering for the system.
31
+ Inactive users are a future implementation of a user type, to allow us to track users who have not logged into the platformm recently but hold positions.
32
+
33
+ * **Data Flow:** An architecture primarily using Google Cloud Pub/Sub and Cloud Functions.
34
+
35
+ The flow of data is, in part, event-driven, and in part scheduler driven.
36
+ The system begins by searching for users to fill up the capacity of the block counts for normal and speculator users. This is the discovery process.
37
+ These users are then verified, and if they meet the criteria of a normal or speculator user, then their UID is stored.
38
+ The update task triggers once a day and finds all the users it needs to update the data of, this will include those users the discovery process located and then verified.
39
+ The update task will look at their portfolios, store the contents.
40
+
41
+ The submitting of the task type and UIDs is done by the Orchestrators, either the update or discovery orchestrator.
42
+ In discovery mode, the tasks are submitted directly to the task engine through a pub/sub
43
+ In update mode, the tasks are passed to the dispatcher, to then slowly release to the task engine, an intentional choice to ensure the task engine can scale gradually to handle the incoming requests.
44
+ The task engine is event-driven, triggered by pub/sub messages.
45
+ The portfolio data is then stored, and once a day the computation system is run on a cron schedule trigger, fetches all the portfolio data and applies computations to the data, returning results that are stored.
46
+ The API is then able to read these results.
47
+
48
+ ## Module Structure
49
+
50
+ The module is organized by function, mirroring the cloud function deployment structure.
51
+
52
+ ### `/core`
53
+
54
+ Contains utilities shared across multiple functions within the module.
55
+
56
+ * **`/utils`:**
57
+ * `firestore_utils.js`: Helpers for common Firestore operations (batching, querying blocks, resetting proxies, getting IDs).
58
+ * `pubsub_utils.js`: Helpers for publishing messages to Pub/Sub topics in batches.
59
+ * `intelligent_header_manager.js`: Manages a pool of browser headers, selecting them based on past success rates to mimic real user traffic. Stores performance data in Firestore.
60
+ * `intelligent_proxy_manager.js`: Manages a pool of Google Apps Script proxies, selecting available ones and handling failures/locking.
61
+
62
+ ### `/orchestrator`
63
+
64
+ Handles the triggering and coordination of data fetching tasks (Discovery and Update).
65
+
66
+ * **`index.js`:** Exports factory functions (`createDiscoveryOrchestrator`, `createUpdateOrchestrator`).
67
+ * **`/helpers`:**
68
+ * `discovery_helpers.js`: Logic for checking if discovery is needed, finding candidate users (prioritizing known users, then random generation with exclusions), managing pending lists, and dispatching tasks.
69
+ * `update_helpers.js`: Logic for identifying users needing updates based on timestamps and dispatching update tasks.
70
+
71
+ ### `/dispatcher`
72
+
73
+ Acts as an intermediary buffer, receiving tasks from the Orchestrator and slowly releasing them to the Task Engine to prevent overwhelming it.
74
+
75
+ * **`index.js`:** Exports the factory function `createDispatcherHandler`.
76
+ * **`/helpers/dispatch_helpers.js`:** Contains the core logic for batching and delaying task publishing.
77
+
78
+ ### `/task-engine`
79
+
80
+ The core processing unit. Receives tasks (discover, verify, update) for different user types (normal, speculator) and executes the corresponding logic using eToro APIs via the proxy manager.
81
+
82
+ * **`index.js`:** Exports the factory function `createTaskEngineHandler`.
83
+ * **`handler_creator.js`:** Sets up the handler, initializes clients (Firestore, PubSub, managers), and defines the main processing flow.
84
+ * **`/helpers`:**
85
+ * `discover_helpers.js`: Handles fetching user ranking/activity data, filtering private/inactive users, applying heuristics (especially for speculators), and chaining to the 'verify' task. Also handles logging invalid speculator IDs.
86
+ * `verify_helpers.js`: Handles fetching portfolio details, checking for specific assets (for speculators) or any positions (for normal users), storing bronze state, updating block counts, and storing verified users.
87
+ * `update_helpers.js`: Handles fetching the latest portfolio or position data for already verified users, handling private users, and storing the updated data.
88
+ * **`/utils`:**
89
+ * `firestore_batch_manager.js`: Manages batch writes to Firestore for portfolios (sharded), timestamps, and removing processed speculators from the pending list.
90
+ ---
91
+
92
+ ### `/computation-system`
93
+
94
+ Handles the orchestration and execution of the **unified computation system**. This system runs scheduled calculations (both daily and historical comparisons) using calculation logic imported from the `aiden-shared-calculations-unified` package. It identifies date ranges needing processing or backfilling, loads the necessary portfolio data, runs the calculations, and stores the results.
95
+
96
+ * **`index.js`:** Exports the factory function `createComputationSystemHandler` and the main orchestration logic `runComputationOrchestrator`.
97
+ * **`handler_creator.js`:** Creates the HTTP-triggered Cloud Function handler that initiates the computation orchestration.
98
+ * **`/helpers/orchestration_helpers.js`:** Contains the core logic for determining dates to process, identifying missing computations, loading data (using `data_loader.js`), initializing calculators from the unified package, processing data across dates, and committing results to Firestore.
99
+ * **`/utils`:**
100
+ * `data_loader.js`: Functions specifically for loading portfolio data in parts/shards from Firestore for the computation system.
101
+ * `utils.js`: Helper functions for categorizing calculations (historical vs. daily), managing Firestore batches, handling date ranges, parallel processing, and finding the earliest data dates.
102
+
103
+
104
+ ### `/generic-api`
105
+
106
+ Provides a flexible, cached API endpoint (intended for frontend use) to query computed data stored in Firestore.
107
+
108
+ * **`index.js`:** Exports the factory function `createApiApp` to set up the Express app.
109
+ * **`/helpers/api_helpers.js`:** Contains logic for request validation, date range generation, data fetching from the unified insights collection, and building the calculation map.
110
+
111
+ ### `/invalid-speculator-handler`
112
+
113
+ Listens to a Pub/Sub topic for messages containing IDs of users identified as private or inactive speculators and stores them in Firestore for future exclusion during discovery.
114
+
115
+ * **`index.js`:** Exports helpers.
116
+ * **`/helpers/handler_helpers.js`:** Core logic for finding/creating a Firestore document and adding the invalid IDs, managing document size limits.
117
+
118
+ ### `/speculator-cleanup-orchestrator`
119
+
120
+ A scheduled function to remove stale speculators from the active blocks based on inactivity and clean up old entries from the pending speculator list.
121
+
122
+ * **`index.js`:** Exports the factory function `createSpeculatorCleanupHandler`.
123
+ * **`/helpers/cleanup_helpers.js`:** Contains the core logic for querying blocks/pending lists based on grace periods and batching deletions/count updates.
124
+
125
+ ### `/fetch-insights`
126
+
127
+ Scheduled function to fetch daily instrument ownership insights (buy/sell percentages, total owners) from the eToro API.
128
+
129
+ * **`index.js`:** Exports the factory function `createFetchInsightsHandler`.
130
+ * **`/helpers/handler_helpers.js`:** Core logic for making the API call (via proxy manager) and storing the results in Firestore.
131
+
132
+ ### `/etoro-price-fetcher`
133
+
134
+ Scheduled function to fetch daily closing prices for instruments from the eToro API.
135
+
136
+ * **`index.js`:** Exports the factory function `createPriceFetcherHandler`.
137
+ * **`/helpers/handler_helpers.js`:** Core logic for making the API call (via proxy manager) and batch-writing price updates to Firestore.
138
+
139
+ ### `/appscript-api`
140
+
141
+ Contains the helper logic intended to be deployed as a Google Apps Script web app, acting as the HTTP proxy layer.
142
+
143
+ * **`index.js`:** Exports the `handlePost` function.
144
+ * **`/helpers/errors.js`:** Utility for creating standardized error responses.
145
+
146
+ ## Setup & Usage
147
+
148
+ ```javascript
149
+ // Example
150
+ const { Orchestrator } = require('bulltrackers-module');
151
+ const cfg = require('./config/orchestrator_config'); // Load specific config
152
+
153
+ exports.discoveryOrchestrator = Orchestrator.createDiscoveryOrchestrator(cfg);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Creates a structured JSON error response.
3
+ * @param {string} message
4
+ * @param {number} statusCode
5
+ * @returns {GoogleAppsScript.Content.TextOutput}
6
+ */
7
+ function createErrorResponse(message, statusCode) {
8
+ const error = {
9
+ error: {
10
+ message,
11
+ code: statusCode,
12
+ },
13
+ };
14
+
15
+ return ContentService.createTextOutput(JSON.stringify(error))
16
+ .setMimeType(ContentService.MimeType.JSON);
17
+ }
18
+
19
+ module.exports = { createErrorResponse };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Google Apps Script proxy logic (v2) as a module.
3
+ * Can be imported and used in a doPost or other GAS handlers.
4
+ */
5
+
6
+ const { createErrorResponse } = require('./helpers/error');
7
+
8
+ /**
9
+ * Handles the incoming POST request object from Apps Script.
10
+ * @param {GoogleAppsScript.Events.DoPost} e
11
+ * @returns {GoogleAppsScript.Content.TextOutput}
12
+ */
13
+ function handlePost(e) {
14
+ try {
15
+ // Parse the request details from the POST body
16
+ const requestDetails = JSON.parse(e.postData.contents);
17
+ const { url, headers, method, body } = requestDetails;
18
+
19
+ if (!url || !headers) {
20
+ return createErrorResponse("Invalid request: 'url' and 'headers' are required.", 400);
21
+ }
22
+
23
+ // Prepare the external request options
24
+ const options = {
25
+ headers: headers,
26
+ muteHttpExceptions: true,
27
+ method: method || 'GET',
28
+ };
29
+
30
+ if (body) {
31
+ options.payload = body;
32
+ if (!headers['Content-Type']) {
33
+ headers['Content-Type'] = 'application/json';
34
+ }
35
+ }
36
+
37
+ // Make the external request using UrlFetchApp
38
+ const response = UrlFetchApp.fetch(url, options);
39
+
40
+ // Package the response to send back
41
+ const responseData = {
42
+ statusCode: response.getResponseCode(),
43
+ headers: response.getHeaders(),
44
+ body: response.getContentText(),
45
+ };
46
+
47
+ return ContentService.createTextOutput(JSON.stringify(responseData))
48
+ .setMimeType(ContentService.MimeType.JSON);
49
+
50
+ } catch (error) {
51
+ return createErrorResponse(error.toString(), 500);
52
+ }
53
+ }
54
+
55
+ module.exports = {
56
+ handlePost,
57
+ };
package/index.js CHANGED
@@ -14,6 +14,7 @@ const InvalidSpeculatorHandler = require('./functions/invalid-speculator-ha
14
14
  const SpeculatorCleanupOrchestrator = require('./functions/speculator-cleanup-orchestrator');
15
15
  const FetchInsights = require('./functions/fetch-insights');
16
16
  const EtoroPriceFetcher = require('./functions/etoro-price-fetcher');
17
+ const { handlePost } = require('./functions/appscript-api');
17
18
 
18
19
  module.exports = {
19
20
  core,
@@ -25,5 +26,6 @@ module.exports = {
25
26
  InvalidSpeculatorHandler,
26
27
  SpeculatorCleanupOrchestrator,
27
28
  FetchInsights,
28
- EtoroPriceFetcher
29
+ EtoroPriceFetcher,
30
+ handlePost
29
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -14,7 +14,8 @@
14
14
  "functions/invalid-speculator-handler/",
15
15
  "functions/speculator-cleanup-orchestrator/",
16
16
  "functions/fetch-insights/",
17
- "functions/etoro-price-fetcher/"
17
+ "functions/etoro-price-fetcher/",
18
+ "functions/appscript-api/"
18
19
  ],
19
20
  "scripts": {
20
21
  "test": "echo \"Error: no test specified\" && exit 1"