aiden-shared-calculations-unified 1.0.64 → 1.0.66
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 +1 -1
- package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
- package/calculations/activity/historical/daily_asset_activity.js +42 -0
- package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
- package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
- package/calculations/asset_metrics/asset_position_size.js +36 -0
- package/calculations/backtests/strategy-performance.js +41 -0
- package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
- package/calculations/behavioural/historical/drawdown_response.js +113 -35
- package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
- package/calculations/behavioural/historical/gain_response.js +113 -34
- package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
- package/calculations/behavioural/historical/position_count_pnl.js +91 -39
- package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
- package/calculations/behavioural/historical/smart_money_flow.js +160 -151
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
- package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
- package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
- package/calculations/insights/daily_total_positions_held.js +28 -24
- package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
- package/calculations/insights/historical/daily_ownership_delta.js +95 -32
- package/calculations/meta/capital_deployment_strategy.js +78 -110
- package/calculations/meta/capital_liquidation_performance.js +114 -111
- package/calculations/meta/cash-flow-deployment.js +114 -107
- package/calculations/meta/cash-flow-liquidation.js +114 -107
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
- package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
- package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
- package/calculations/meta/profit_cohort_divergence.js +83 -59
- package/calculations/meta/shark_attack_signal.js +91 -39
- package/calculations/meta/smart-dumb-divergence-index.js +114 -98
- package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
- package/calculations/meta/social-predictive-regime-state.js +76 -155
- package/calculations/meta/social-topic-driver-index.js +74 -127
- package/calculations/meta/user_expectancy_score.js +83 -31
- package/calculations/pnl/asset_pnl_status.js +120 -31
- package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
- package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
- package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
- package/calculations/pnl/average_daily_position_pnl.js +49 -21
- package/calculations/pnl/historical/profitability_migration.js +81 -35
- package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
- package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
- package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
- package/calculations/pnl/profitability_skew_per_stock.js +86 -31
- package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
- package/calculations/sanity/users_processed.js +24 -1
- package/calculations/sectors/historical/diversification_pnl.js +104 -42
- package/calculations/sectors/historical/sector_rotation.js +94 -45
- package/calculations/sectors/total_long_per_sector.js +55 -20
- package/calculations/sectors/total_short_per_sector.js +55 -20
- package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
- package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
- package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
- package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
- package/calculations/short_and_long_stats/total_long_figures.js +34 -13
- package/calculations/short_and_long_stats/total_short_figures.js +34 -14
- package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
- package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
- package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
- package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
- package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
- package/calculations/socialPosts/social_activity_aggregation.js +106 -77
- package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
- package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
- package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
- package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
- package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
- package/calculations/speculators/historical/risk_appetite_change.js +89 -32
- package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
- package/calculations/speculators/holding_duration_per_asset.js +83 -23
- package/calculations/speculators/leverage_per_asset.js +68 -19
- package/calculations/speculators/leverage_per_sector.js +86 -25
- package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
- package/calculations/speculators/speculator_asset_sentiment.js +100 -48
- package/calculations/speculators/speculator_danger_zone.js +101 -33
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
- package/calculations/speculators/stop_loss_per_asset.js +94 -26
- package/calculations/speculators/take_profit_per_asset.js +95 -27
- package/calculations/speculators/tsl_per_asset.js +77 -23
- package/package.json +1 -1
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
const { Firestore } = require('@google-cloud/firestore');
|
|
2
|
-
const firestore = new Firestore();
|
|
3
|
-
|
|
4
|
-
// Config
|
|
5
|
-
const PRICE_COLLECTION = 'asset_prices';
|
|
6
|
-
const CACHE_DURATION_MS = 3600000; // 1 hour
|
|
7
|
-
|
|
8
|
-
// Cache
|
|
9
|
-
let cache = {
|
|
10
|
-
timestamp: null,
|
|
11
|
-
priceMap: null, // Will be { instrumentId: { "YYYY-MM-DD": price, ... } }
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// In-progress fetch promise
|
|
15
|
-
let fetchPromise = null;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Finds the most recent available price on or before a given date.
|
|
19
|
-
* @param {object} priceHistory - The map of { "YYYY-MM-DD": price }
|
|
20
|
-
* @param {string} dateStr - The target date string to start searching from.
|
|
21
|
-
* @param {number} [maxLookback=5] - Max days to look back (to skip weekends/holidays).
|
|
22
|
-
* @returns {number|null} The price, or null if not found.
|
|
23
|
-
*/
|
|
24
|
-
function _findPreviousAvailablePrice(priceHistory, dateStr, maxLookback = 5) {
|
|
25
|
-
if (!priceHistory) return null;
|
|
26
|
-
|
|
27
|
-
let checkDate = new Date(dateStr + 'T00:00:00Z');
|
|
28
|
-
|
|
29
|
-
for (let i = 0; i < maxLookback; i++) {
|
|
30
|
-
const checkDateStr = checkDate.toISOString().slice(0, 10);
|
|
31
|
-
const price = priceHistory[checkDateStr];
|
|
32
|
-
|
|
33
|
-
if (price !== undefined && price !== null && price > 0) {
|
|
34
|
-
return price; // Found it
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// If not found, look back one more day
|
|
38
|
-
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// console.warn(`No price found for instrument within ${maxLookback} days of ${dateStr}`);
|
|
42
|
-
return null; // No price found within lookback
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Loads all sharded price data from the `asset_prices` collection.
|
|
47
|
-
* This is a heavy operation and should be cached.
|
|
48
|
-
*/
|
|
49
|
-
async function loadAllPriceData() {
|
|
50
|
-
const now = Date.now();
|
|
51
|
-
if (cache.timestamp && (now - cache.timestamp < CACHE_DURATION_MS)) {
|
|
52
|
-
return cache.priceMap;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (fetchPromise) {
|
|
56
|
-
return fetchPromise;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
fetchPromise = (async () => {
|
|
60
|
-
console.log('Fetching and caching all asset price data...');
|
|
61
|
-
const masterPriceMap = {};
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const snapshot = await firestore.collection(PRICE_COLLECTION).get();
|
|
65
|
-
|
|
66
|
-
if (snapshot.empty) {
|
|
67
|
-
throw new Error(`Price collection '${PRICE_COLLECTION}' is empty.`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Loop through each shard document (e.g., "shard_0", "shard_1")
|
|
71
|
-
snapshot.forEach(doc => {
|
|
72
|
-
const shardData = doc.data();
|
|
73
|
-
|
|
74
|
-
// Loop through each instrumentId in the shard
|
|
75
|
-
for (const instrumentId in shardData) {
|
|
76
|
-
// Check if it's a valid instrument entry
|
|
77
|
-
if (shardData[instrumentId] && shardData[instrumentId].prices) {
|
|
78
|
-
masterPriceMap[instrumentId] = shardData[instrumentId].prices;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
cache = {
|
|
84
|
-
timestamp: now,
|
|
85
|
-
priceMap: masterPriceMap,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
console.log(`Successfully cached prices for ${Object.keys(masterPriceMap).length} instruments.`);
|
|
89
|
-
return masterPriceMap;
|
|
90
|
-
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.error('CRITICAL: Error loading price data:', err);
|
|
93
|
-
// On error, return an empty map but don't cache, so a future call can retry.
|
|
94
|
-
return {};
|
|
95
|
-
} finally {
|
|
96
|
-
// Clear the promise so the next call (if cache is stale) triggers a new fetch
|
|
97
|
-
fetchPromise = null;
|
|
98
|
-
}
|
|
99
|
-
})();
|
|
100
|
-
|
|
101
|
-
return fetchPromise;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* A helper to safely get the price change percentage between two dates.
|
|
106
|
-
* --- THIS IS THE MODIFIED FUNCTION ---
|
|
107
|
-
* @param {string} instrumentId - The instrument ID.
|
|
108
|
-
* @param {string} yesterdayStr - YYYY-MM-DD date string for yesterday.
|
|
109
|
-
* @param {string} todayStr - YYYY-MM-DD date string for today.
|
|
110
|
-
* @param {object} priceMap - The master price map from loadAllPriceData().
|
|
111
|
-
* @returns {number|null} The percentage change (e.g., 0.10 for +10%), or null if data is missing.
|
|
112
|
-
*/
|
|
113
|
-
function getDailyPriceChange(instrumentId, yesterdayStr, todayStr, priceMap) {
|
|
114
|
-
if (!priceMap || !priceMap[instrumentId]) {
|
|
115
|
-
return null; // No price data for this instrument
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const instrumentPrices = priceMap[instrumentId];
|
|
119
|
-
|
|
120
|
-
// Find the most recent available price *on or before* the target dates
|
|
121
|
-
const priceDay1 = _findPreviousAvailablePrice(instrumentPrices, yesterdayStr, 5);
|
|
122
|
-
const priceDay2 = _findPreviousAvailablePrice(instrumentPrices, todayStr, 5);
|
|
123
|
-
|
|
124
|
-
if (priceDay1 && priceDay2) {
|
|
125
|
-
// We found prices, now check if they are the *same* price
|
|
126
|
-
// (e.g., if today is Sunday, priceDay2 would be Friday's price.
|
|
127
|
-
// If yesterday was Saturday, priceDay1 would *also* be Friday's price).
|
|
128
|
-
if (priceDay1 === priceDay2) {
|
|
129
|
-
return 0.0; // No change between the two most recent *available* dates
|
|
130
|
-
}
|
|
131
|
-
return (priceDay2 - priceDay1) / priceDay1;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return null; // Missing one or both dates, even after lookback
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
module.exports = {
|
|
139
|
-
loadAllPriceData,
|
|
140
|
-
getDailyPriceChange
|
|
141
|
-
// Add the helper to exports if you want, but it's not required
|
|
142
|
-
};
|
|
1
|
+
const { Firestore } = require('@google-cloud/firestore');
|
|
2
|
+
const firestore = new Firestore();
|
|
3
|
+
|
|
4
|
+
// Config
|
|
5
|
+
const PRICE_COLLECTION = 'asset_prices';
|
|
6
|
+
const CACHE_DURATION_MS = 3600000; // 1 hour
|
|
7
|
+
|
|
8
|
+
// Cache
|
|
9
|
+
let cache = {
|
|
10
|
+
timestamp: null,
|
|
11
|
+
priceMap: null, // Will be { instrumentId: { "YYYY-MM-DD": price, ... } }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// In-progress fetch promise
|
|
15
|
+
let fetchPromise = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Finds the most recent available price on or before a given date.
|
|
19
|
+
* @param {object} priceHistory - The map of { "YYYY-MM-DD": price }
|
|
20
|
+
* @param {string} dateStr - The target date string to start searching from.
|
|
21
|
+
* @param {number} [maxLookback=5] - Max days to look back (to skip weekends/holidays).
|
|
22
|
+
* @returns {number|null} The price, or null if not found.
|
|
23
|
+
*/
|
|
24
|
+
function _findPreviousAvailablePrice(priceHistory, dateStr, maxLookback = 5) {
|
|
25
|
+
if (!priceHistory) return null;
|
|
26
|
+
|
|
27
|
+
let checkDate = new Date(dateStr + 'T00:00:00Z');
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < maxLookback; i++) {
|
|
30
|
+
const checkDateStr = checkDate.toISOString().slice(0, 10);
|
|
31
|
+
const price = priceHistory[checkDateStr];
|
|
32
|
+
|
|
33
|
+
if (price !== undefined && price !== null && price > 0) {
|
|
34
|
+
return price; // Found it
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If not found, look back one more day
|
|
38
|
+
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// console.warn(`No price found for instrument within ${maxLookback} days of ${dateStr}`);
|
|
42
|
+
return null; // No price found within lookback
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Loads all sharded price data from the `asset_prices` collection.
|
|
47
|
+
* This is a heavy operation and should be cached.
|
|
48
|
+
*/
|
|
49
|
+
async function loadAllPriceData() {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
if (cache.timestamp && (now - cache.timestamp < CACHE_DURATION_MS)) {
|
|
52
|
+
return cache.priceMap;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (fetchPromise) {
|
|
56
|
+
return fetchPromise;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fetchPromise = (async () => {
|
|
60
|
+
console.log('Fetching and caching all asset price data...');
|
|
61
|
+
const masterPriceMap = {};
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const snapshot = await firestore.collection(PRICE_COLLECTION).get();
|
|
65
|
+
|
|
66
|
+
if (snapshot.empty) {
|
|
67
|
+
throw new Error(`Price collection '${PRICE_COLLECTION}' is empty.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Loop through each shard document (e.g., "shard_0", "shard_1")
|
|
71
|
+
snapshot.forEach(doc => {
|
|
72
|
+
const shardData = doc.data();
|
|
73
|
+
|
|
74
|
+
// Loop through each instrumentId in the shard
|
|
75
|
+
for (const instrumentId in shardData) {
|
|
76
|
+
// Check if it's a valid instrument entry
|
|
77
|
+
if (shardData[instrumentId] && shardData[instrumentId].prices) {
|
|
78
|
+
masterPriceMap[instrumentId] = shardData[instrumentId].prices;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
cache = {
|
|
84
|
+
timestamp: now,
|
|
85
|
+
priceMap: masterPriceMap,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
console.log(`Successfully cached prices for ${Object.keys(masterPriceMap).length} instruments.`);
|
|
89
|
+
return masterPriceMap;
|
|
90
|
+
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error('CRITICAL: Error loading price data:', err);
|
|
93
|
+
// On error, return an empty map but don't cache, so a future call can retry.
|
|
94
|
+
return {};
|
|
95
|
+
} finally {
|
|
96
|
+
// Clear the promise so the next call (if cache is stale) triggers a new fetch
|
|
97
|
+
fetchPromise = null;
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
|
|
101
|
+
return fetchPromise;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A helper to safely get the price change percentage between two dates.
|
|
106
|
+
* --- THIS IS THE MODIFIED FUNCTION ---
|
|
107
|
+
* @param {string} instrumentId - The instrument ID.
|
|
108
|
+
* @param {string} yesterdayStr - YYYY-MM-DD date string for yesterday.
|
|
109
|
+
* @param {string} todayStr - YYYY-MM-DD date string for today.
|
|
110
|
+
* @param {object} priceMap - The master price map from loadAllPriceData().
|
|
111
|
+
* @returns {number|null} The percentage change (e.g., 0.10 for +10%), or null if data is missing.
|
|
112
|
+
*/
|
|
113
|
+
function getDailyPriceChange(instrumentId, yesterdayStr, todayStr, priceMap) {
|
|
114
|
+
if (!priceMap || !priceMap[instrumentId]) {
|
|
115
|
+
return null; // No price data for this instrument
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const instrumentPrices = priceMap[instrumentId];
|
|
119
|
+
|
|
120
|
+
// Find the most recent available price *on or before* the target dates
|
|
121
|
+
const priceDay1 = _findPreviousAvailablePrice(instrumentPrices, yesterdayStr, 5);
|
|
122
|
+
const priceDay2 = _findPreviousAvailablePrice(instrumentPrices, todayStr, 5);
|
|
123
|
+
|
|
124
|
+
if (priceDay1 && priceDay2) {
|
|
125
|
+
// We found prices, now check if they are the *same* price
|
|
126
|
+
// (e.g., if today is Sunday, priceDay2 would be Friday's price.
|
|
127
|
+
// If yesterday was Saturday, priceDay1 would *also* be Friday's price).
|
|
128
|
+
if (priceDay1 === priceDay2) {
|
|
129
|
+
return 0.0; // No change between the two most recent *available* dates
|
|
130
|
+
}
|
|
131
|
+
return (priceDay2 - priceDay1) / priceDay1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null; // Missing one or both dates, even after lookback
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
loadAllPriceData,
|
|
140
|
+
getDailyPriceChange
|
|
141
|
+
// Add the helper to exports if you want, but it's not required
|
|
142
|
+
};
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
const { Firestore } = require('@google-cloud/firestore');
|
|
2
|
-
const firestore = new Firestore();
|
|
3
|
-
|
|
4
|
-
let cache = {
|
|
5
|
-
timestamp: null,
|
|
6
|
-
instrumentToSector: null,
|
|
7
|
-
instrumentToTicker: null,
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// This variable will hold the promise for the in-progress fetch operation.
|
|
11
|
-
let fetchPromise = null;
|
|
12
|
-
|
|
13
|
-
async function loadInstrumentMappings() {
|
|
14
|
-
const now = Date.now();
|
|
15
|
-
// If the cache is recent, return it immediately.
|
|
16
|
-
if (cache.timestamp && (now - cache.timestamp < 3600000)) {
|
|
17
|
-
return cache;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// If a fetch is not already in progress, start one.
|
|
21
|
-
if (!fetchPromise) {
|
|
22
|
-
fetchPromise = (async () => {
|
|
23
|
-
try {
|
|
24
|
-
console.log('Fetching instrument mappings...');
|
|
25
|
-
const [tickerToIdDoc, tickerToSectorDoc] = await Promise.all([
|
|
26
|
-
firestore.collection('instrument_mappings').doc('etoro_to_ticker').get(),
|
|
27
|
-
firestore.collection('instrument_sector_mappings').doc('sector_mappings').get()
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
if (!tickerToIdDoc.exists || !tickerToSectorDoc.exists) {
|
|
31
|
-
throw new Error('Instrument mapping documents missing.');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const tickerToId = tickerToIdDoc.data();
|
|
35
|
-
const tickerToSector = tickerToSectorDoc.data();
|
|
36
|
-
|
|
37
|
-
const idToTicker = Object.fromEntries(Object.entries(tickerToId).map(([id, ticker]) => [id, ticker]));
|
|
38
|
-
|
|
39
|
-
const idToSector = Object.fromEntries(
|
|
40
|
-
Object.entries(idToTicker).map(([id, ticker]) => [id, tickerToSector[ticker] || 'N/A'])
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Update the cache with the new data.
|
|
44
|
-
cache = {
|
|
45
|
-
timestamp: now,
|
|
46
|
-
instrumentToTicker: idToTicker,
|
|
47
|
-
instrumentToSector: idToSector,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
console.log('Mappings cached successfully.');
|
|
51
|
-
return cache;
|
|
52
|
-
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error('Error loading instrument mappings:', err);
|
|
55
|
-
// On error, don't cache, so a future call can retry.
|
|
56
|
-
return { instrumentToTicker: {}, instrumentToSector: {} };
|
|
57
|
-
} finally {
|
|
58
|
-
// IMPORTANT: Reset the promise so that if the cache expires,
|
|
59
|
-
// a new fetch can be initiated later.
|
|
60
|
-
fetchPromise = null;
|
|
61
|
-
}
|
|
62
|
-
})();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Return the promise for the in-progress fetch.
|
|
66
|
-
// Subsequent calls will wait for this single promise to resolve.
|
|
67
|
-
return fetchPromise;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function getInstrumentSectorMap() {
|
|
71
|
-
const mappings = await loadInstrumentMappings();
|
|
72
|
-
return mappings.instrumentToSector;
|
|
73
|
-
}
|
|
74
|
-
|
|
1
|
+
const { Firestore } = require('@google-cloud/firestore');
|
|
2
|
+
const firestore = new Firestore();
|
|
3
|
+
|
|
4
|
+
let cache = {
|
|
5
|
+
timestamp: null,
|
|
6
|
+
instrumentToSector: null,
|
|
7
|
+
instrumentToTicker: null,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// This variable will hold the promise for the in-progress fetch operation.
|
|
11
|
+
let fetchPromise = null;
|
|
12
|
+
|
|
13
|
+
async function loadInstrumentMappings() {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
// If the cache is recent, return it immediately.
|
|
16
|
+
if (cache.timestamp && (now - cache.timestamp < 3600000)) {
|
|
17
|
+
return cache;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// If a fetch is not already in progress, start one.
|
|
21
|
+
if (!fetchPromise) {
|
|
22
|
+
fetchPromise = (async () => {
|
|
23
|
+
try {
|
|
24
|
+
console.log('Fetching instrument mappings...');
|
|
25
|
+
const [tickerToIdDoc, tickerToSectorDoc] = await Promise.all([
|
|
26
|
+
firestore.collection('instrument_mappings').doc('etoro_to_ticker').get(),
|
|
27
|
+
firestore.collection('instrument_sector_mappings').doc('sector_mappings').get()
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
if (!tickerToIdDoc.exists || !tickerToSectorDoc.exists) {
|
|
31
|
+
throw new Error('Instrument mapping documents missing.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tickerToId = tickerToIdDoc.data();
|
|
35
|
+
const tickerToSector = tickerToSectorDoc.data();
|
|
36
|
+
|
|
37
|
+
const idToTicker = Object.fromEntries(Object.entries(tickerToId).map(([id, ticker]) => [id, ticker]));
|
|
38
|
+
|
|
39
|
+
const idToSector = Object.fromEntries(
|
|
40
|
+
Object.entries(idToTicker).map(([id, ticker]) => [id, tickerToSector[ticker] || 'N/A'])
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Update the cache with the new data.
|
|
44
|
+
cache = {
|
|
45
|
+
timestamp: now,
|
|
46
|
+
instrumentToTicker: idToTicker,
|
|
47
|
+
instrumentToSector: idToSector,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
console.log('Mappings cached successfully.');
|
|
51
|
+
return cache;
|
|
52
|
+
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('Error loading instrument mappings:', err);
|
|
55
|
+
// On error, don't cache, so a future call can retry.
|
|
56
|
+
return { instrumentToTicker: {}, instrumentToSector: {} };
|
|
57
|
+
} finally {
|
|
58
|
+
// IMPORTANT: Reset the promise so that if the cache expires,
|
|
59
|
+
// a new fetch can be initiated later.
|
|
60
|
+
fetchPromise = null;
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Return the promise for the in-progress fetch.
|
|
66
|
+
// Subsequent calls will wait for this single promise to resolve.
|
|
67
|
+
return fetchPromise;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getInstrumentSectorMap() {
|
|
71
|
+
const mappings = await loadInstrumentMappings();
|
|
72
|
+
return mappings.instrumentToSector;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
75
|
module.exports = { loadInstrumentMappings, getInstrumentSectorMap };
|