aiden-shared-calculations-unified 1.0.34 → 1.0.36

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.
Files changed (58) hide show
  1. package/README.MD +77 -77
  2. package/calculations/activity/historical/activity_by_pnl_status.js +85 -85
  3. package/calculations/activity/historical/daily_asset_activity.js +85 -85
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +144 -144
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +76 -76
  6. package/calculations/asset_metrics/asset_position_size.js +57 -57
  7. package/calculations/backtests/strategy-performance.js +229 -245
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +165 -170
  9. package/calculations/behavioural/historical/drawdown_response.js +58 -58
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +249 -249
  11. package/calculations/behavioural/historical/gain_response.js +57 -57
  12. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +98 -98
  13. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +99 -99
  14. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +39 -39
  15. package/calculations/behavioural/historical/position_count_pnl.js +67 -67
  16. package/calculations/behavioural/historical/smart-cohort-flow.js +250 -250
  17. package/calculations/behavioural/historical/smart_money_flow.js +165 -165
  18. package/calculations/behavioural/historical/user-investment-profile.js +412 -412
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +121 -121
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +117 -117
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +49 -49
  22. package/calculations/insights/daily_bought_vs_sold_count.js +55 -55
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +49 -49
  24. package/calculations/insights/daily_ownership_delta.js +55 -55
  25. package/calculations/insights/daily_total_positions_held.js +39 -39
  26. package/calculations/meta/capital_deployment_strategy.js +129 -137
  27. package/calculations/meta/capital_liquidation_performance.js +121 -163
  28. package/calculations/meta/capital_vintage_performance.js +121 -158
  29. package/calculations/meta/cash-flow-deployment.js +110 -124
  30. package/calculations/meta/cash-flow-liquidation.js +126 -142
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +83 -91
  32. package/calculations/meta/profit_cohort_divergence.js +77 -91
  33. package/calculations/meta/smart-dumb-divergence-index.js +116 -138
  34. package/calculations/meta/social_flow_correlation.js +99 -125
  35. package/calculations/pnl/asset_pnl_status.js +46 -46
  36. package/calculations/pnl/historical/profitability_migration.js +57 -57
  37. package/calculations/pnl/historical/user_profitability_tracker.js +117 -117
  38. package/calculations/pnl/profitable_and_unprofitable_status.js +64 -64
  39. package/calculations/sectors/historical/diversification_pnl.js +76 -76
  40. package/calculations/sectors/historical/sector_rotation.js +67 -67
  41. package/calculations/sentiment/historical/crowd_conviction_score.js +80 -80
  42. package/calculations/socialPosts/social-asset-posts-trend.js +52 -52
  43. package/calculations/socialPosts/social-top-mentioned-words.js +102 -102
  44. package/calculations/socialPosts/social-topic-interest-evolution.js +53 -53
  45. package/calculations/socialPosts/social-word-mentions-trend.js +62 -62
  46. package/calculations/socialPosts/social_activity_aggregation.js +103 -103
  47. package/calculations/socialPosts/social_event_correlation.js +121 -121
  48. package/calculations/socialPosts/social_sentiment_aggregation.js +114 -114
  49. package/calculations/speculators/historical/risk_appetite_change.js +54 -54
  50. package/calculations/speculators/historical/tsl_effectiveness.js +74 -74
  51. package/index.js +33 -33
  52. package/package.json +32 -32
  53. package/utils/firestore_utils.js +76 -76
  54. package/utils/price_data_provider.js +142 -142
  55. package/utils/sector_mapping_provider.js +74 -74
  56. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +0 -63
  57. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +0 -91
  58. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +0 -73
@@ -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 };
@@ -1,63 +0,0 @@
1
- /**
2
- * @fileoverview Calculates the average percentage increase in allocation
3
- * specifically towards assets already held on the previous day.
4
- */
5
-
6
- class ReallocationIncreasePercentage {
7
- constructor() {
8
- this.accumulatedIncreasePercentage = 0;
9
- this.userCount = 0;
10
- }
11
-
12
- process(todayPortfolio, yesterdayPortfolio, userId) {
13
- if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
14
- // Requires AggregatedPositions which contain the 'Invested' percentage
15
- return;
16
- }
17
-
18
- const yesterdayPositions = new Map(yesterdayPortfolio.AggregatedPositions.map(p => [p.InstrumentID, p]));
19
- let userTotalIncreasePercentage = 0;
20
-
21
- for (const todayPos of todayPortfolio.AggregatedPositions) {
22
- const yesterdayPos = yesterdayPositions.get(todayPos.InstrumentID);
23
-
24
- // Check if the asset was held yesterday
25
- if (yesterdayPos) {
26
- // Ensure 'Invested' property exists and is a number
27
- const todayInvested = typeof todayPos.Invested === 'number' ? todayPos.Invested : 0;
28
- const yesterdayInvested = typeof yesterdayPos.Invested === 'number' ? yesterdayPos.Invested : 0;
29
-
30
- const deltaInvested = todayInvested - yesterdayInvested;
31
-
32
- // Accumulate only the increases
33
- if (deltaInvested > 0) {
34
- userTotalIncreasePercentage += deltaInvested;
35
- }
36
- }
37
- }
38
-
39
- // Only count users who had positions on both days for this metric
40
- if (yesterdayPortfolio.AggregatedPositions.length > 0 && todayPortfolio.AggregatedPositions.length > 0) {
41
- this.accumulatedIncreasePercentage += userTotalIncreasePercentage;
42
- this.userCount++;
43
- }
44
- }
45
-
46
- getResult() {
47
- if (this.userCount === 0) {
48
- return {};
49
- }
50
-
51
- return {
52
- // Calculate the final average directly
53
- average_reallocation_increase_percentage: this.accumulatedIncreasePercentage / this.userCount
54
- };
55
- }
56
-
57
- reset() {
58
- this.accumulatedIncreasePercentage = 0;
59
- this.userCount = 0;
60
- }
61
- }
62
-
63
- module.exports = ReallocationIncreasePercentage;
@@ -1,91 +0,0 @@
1
- /**
2
- * @fileoverview Calculates the average stop loss distance (percent and value)
3
- * for long and short positions, grouped by SECTOR.
4
- */
5
- const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
6
-
7
- class StopLossDistanceBySector {
8
- constructor() {
9
- this.instrumentData = {};
10
- this.instrumentToSector = null;
11
- }
12
-
13
- process(portfolioData, yesterdayPortfolio, userId, context) {
14
- if (!portfolioData || !portfolioData.PublicPositions) return;
15
-
16
- for (const position of portfolioData.PublicPositions) {
17
- const { InstrumentID, Leverage, StopLossRate, CurrentRate, IsBuy } = position;
18
- if (Leverage <= 1 || StopLossRate <= 0.0001 || CurrentRate <= 0) continue;
19
-
20
- const distance_value = IsBuy ? CurrentRate - StopLossRate : StopLossRate - CurrentRate;
21
- const distance_percent = (distance_value / CurrentRate) * 100;
22
-
23
- if (distance_percent > 0) {
24
- const posType = IsBuy ? 'long' : 'short';
25
- if (!this.instrumentData[InstrumentID]) this.instrumentData[InstrumentID] = {};
26
- if (!this.instrumentData[InstrumentID][posType]) {
27
- this.instrumentData[InstrumentID][posType] = {
28
- distance_percent_sum: 0,
29
- distance_value_sum: 0,
30
- count: 0
31
- };
32
- }
33
- const agg = this.instrumentData[InstrumentID][posType];
34
- agg.distance_percent_sum += distance_percent;
35
- agg.distance_value_sum += distance_value;
36
- agg.count++;
37
- }
38
- }
39
- }
40
-
41
- async getResult() {
42
- if (Object.keys(this.instrumentData).length === 0) return {};
43
- if (!this.instrumentToSector) {
44
- this.instrumentToSector = await getInstrumentSectorMap();
45
- }
46
-
47
- const sectorData = {};
48
- for (const instrumentId in this.instrumentData) {
49
- const sector = this.instrumentToSector[instrumentId] || 'N/A';
50
- if (!sectorData[sector]) sectorData[sector] = {};
51
-
52
- for (const posType in this.instrumentData[instrumentId]) {
53
- if (!sectorData[sector][posType]) {
54
- sectorData[sector][posType] = {
55
- distance_percent_sum: 0,
56
- distance_value_sum: 0,
57
- count: 0
58
- };
59
- }
60
- const source = this.instrumentData[instrumentId][posType];
61
- const target = sectorData[sector][posType];
62
- target.distance_percent_sum += source.distance_percent_sum;
63
- target.distance_value_sum += source.distance_value_sum;
64
- target.count += source.count;
65
- }
66
- }
67
-
68
- const result = {};
69
- for (const sector in sectorData) {
70
- result[sector] = {};
71
- for (const posType in sectorData[sector]) {
72
- const data = sectorData[sector][posType];
73
- // REFACTOR: Perform final calculation and return in standardized format.
74
- if (data.count > 0) {
75
- result[sector][posType] = {
76
- average_distance_percent: data.distance_percent_sum / data.count,
77
- average_distance_value: data.distance_value_sum / data.count,
78
- count: data.count
79
- };
80
- }
81
- }
82
- }
83
- return result;
84
- }
85
-
86
- reset() {
87
- this.instrumentData = {};
88
- }
89
- }
90
-
91
- module.exports = StopLossDistanceBySector;
@@ -1,73 +0,0 @@
1
- /**
2
- * @fileoverview Calculates the average stop loss distance (percent and value)
3
- * for long and short positions, grouped by TICKER.
4
- */
5
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
-
7
- class StopLossDistanceByTicker {
8
- constructor() {
9
- this.instrumentData = {};
10
- this.instrumentToTicker = null;
11
- }
12
-
13
- process(portfolioData, yesterdayPortfolio, userId, context) {
14
- if (!portfolioData || !portfolioData.PublicPositions) return;
15
-
16
- for (const position of portfolioData.PublicPositions) {
17
- const { InstrumentID, Leverage, StopLossRate, CurrentRate, IsBuy } = position;
18
- if (Leverage <= 1 || StopLossRate <= 0.0001 || CurrentRate <= 0) continue;
19
-
20
- const distance_value = IsBuy ? CurrentRate - StopLossRate : StopLossRate - CurrentRate;
21
- const distance_percent = (distance_value / CurrentRate) * 100;
22
-
23
- if (distance_percent > 0) {
24
- const posType = IsBuy ? 'long' : 'short';
25
- if (!this.instrumentData[InstrumentID]) this.instrumentData[InstrumentID] = {};
26
- if (!this.instrumentData[InstrumentID][posType]) {
27
- this.instrumentData[InstrumentID][posType] = {
28
- distance_percent_sum: 0,
29
- distance_value_sum: 0,
30
- count: 0
31
- };
32
- }
33
- const agg = this.instrumentData[InstrumentID][posType];
34
- agg.distance_percent_sum += distance_percent;
35
- agg.distance_value_sum += distance_value;
36
- agg.count++;
37
- }
38
- }
39
- }
40
-
41
- async getResult() {
42
- if (Object.keys(this.instrumentData).length === 0) return {};
43
- if (!this.instrumentToTicker) {
44
- const mappings = await loadInstrumentMappings();
45
- this.instrumentToTicker = mappings.instrumentToTicker;
46
- }
47
-
48
- const result = {};
49
- for (const instrumentId in this.instrumentData) {
50
- const ticker = this.instrumentToTicker[instrumentId] || `unknown_${instrumentId}`;
51
- if (!result[ticker]) result[ticker] = {};
52
-
53
- for (const posType in this.instrumentData[instrumentId]) {
54
- const data = this.instrumentData[instrumentId][posType];
55
- // REFACTOR: Perform final calculation and return in standardized format.
56
- if (data.count > 0) {
57
- result[ticker][posType] = {
58
- average_distance_percent: data.distance_percent_sum / data.count,
59
- average_distance_value: data.distance_value_sum / data.count,
60
- count: data.count
61
- };
62
- }
63
- }
64
- }
65
- return result;
66
- }
67
-
68
- reset() {
69
- this.instrumentData = {};
70
- }
71
- }
72
-
73
- module.exports = StopLossDistanceByTicker;