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.
- package/README.MD +77 -77
- package/calculations/activity/historical/activity_by_pnl_status.js +85 -85
- package/calculations/activity/historical/daily_asset_activity.js +85 -85
- package/calculations/activity/historical/daily_user_activity_tracker.js +144 -144
- package/calculations/activity/historical/speculator_adjustment_activity.js +76 -76
- package/calculations/asset_metrics/asset_position_size.js +57 -57
- package/calculations/backtests/strategy-performance.js +229 -245
- package/calculations/behavioural/historical/asset_crowd_flow.js +165 -170
- package/calculations/behavioural/historical/drawdown_response.js +58 -58
- package/calculations/behavioural/historical/dumb-cohort-flow.js +249 -249
- package/calculations/behavioural/historical/gain_response.js +57 -57
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +98 -98
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +99 -99
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +39 -39
- package/calculations/behavioural/historical/position_count_pnl.js +67 -67
- package/calculations/behavioural/historical/smart-cohort-flow.js +250 -250
- package/calculations/behavioural/historical/smart_money_flow.js +165 -165
- package/calculations/behavioural/historical/user-investment-profile.js +412 -412
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +121 -121
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +117 -117
- package/calculations/capital_flow/historical/new_allocation_percentage.js +49 -49
- package/calculations/insights/daily_bought_vs_sold_count.js +55 -55
- package/calculations/insights/daily_buy_sell_sentiment_count.js +49 -49
- package/calculations/insights/daily_ownership_delta.js +55 -55
- package/calculations/insights/daily_total_positions_held.js +39 -39
- package/calculations/meta/capital_deployment_strategy.js +129 -137
- package/calculations/meta/capital_liquidation_performance.js +121 -163
- package/calculations/meta/capital_vintage_performance.js +121 -158
- package/calculations/meta/cash-flow-deployment.js +110 -124
- package/calculations/meta/cash-flow-liquidation.js +126 -142
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +83 -91
- package/calculations/meta/profit_cohort_divergence.js +77 -91
- package/calculations/meta/smart-dumb-divergence-index.js +116 -138
- package/calculations/meta/social_flow_correlation.js +99 -125
- package/calculations/pnl/asset_pnl_status.js +46 -46
- package/calculations/pnl/historical/profitability_migration.js +57 -57
- package/calculations/pnl/historical/user_profitability_tracker.js +117 -117
- package/calculations/pnl/profitable_and_unprofitable_status.js +64 -64
- package/calculations/sectors/historical/diversification_pnl.js +76 -76
- package/calculations/sectors/historical/sector_rotation.js +67 -67
- package/calculations/sentiment/historical/crowd_conviction_score.js +80 -80
- package/calculations/socialPosts/social-asset-posts-trend.js +52 -52
- package/calculations/socialPosts/social-top-mentioned-words.js +102 -102
- package/calculations/socialPosts/social-topic-interest-evolution.js +53 -53
- package/calculations/socialPosts/social-word-mentions-trend.js +62 -62
- package/calculations/socialPosts/social_activity_aggregation.js +103 -103
- package/calculations/socialPosts/social_event_correlation.js +121 -121
- package/calculations/socialPosts/social_sentiment_aggregation.js +114 -114
- package/calculations/speculators/historical/risk_appetite_change.js +54 -54
- package/calculations/speculators/historical/tsl_effectiveness.js +74 -74
- package/index.js +33 -33
- package/package.json +32 -32
- package/utils/firestore_utils.js +76 -76
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +0 -63
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +0 -91
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +0 -73
|
@@ -1,166 +1,166 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tracks the investment flow of "smart money".
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { Firestore } = require('@google-cloud/firestore');
|
|
6
|
-
const firestore = new Firestore();
|
|
7
|
-
// CORRECTED PATH: ../utils/ instead of ../../utils/
|
|
8
|
-
const { getInstrumentSectorMap } = require('../../../utils/sector_mapping_provider');
|
|
9
|
-
|
|
10
|
-
class SmartMoneyFlow {
|
|
11
|
-
// ... (rest of the code is unchanged) ...
|
|
12
|
-
constructor() {
|
|
13
|
-
this.smartMoneyUsers = new Set();
|
|
14
|
-
this.sectorFlow = {};
|
|
15
|
-
this.sectorMap = null; // Cache for the sector map
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
19
|
-
// Load smart money users only if the set is empty
|
|
20
|
-
if (this.smartMoneyUsers.size === 0) {
|
|
21
|
-
await this.identifySmartMoney();
|
|
22
|
-
// Log after identification attempt
|
|
23
|
-
console.log(`Identified ${this.smartMoneyUsers.size} smart money users.`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Load sector map if not already loaded
|
|
27
|
-
if (!this.sectorMap) {
|
|
28
|
-
try {
|
|
29
|
-
this.sectorMap = await getInstrumentSectorMap();
|
|
30
|
-
if (!this.sectorMap || Object.keys(this.sectorMap).length === 0) {
|
|
31
|
-
console.warn('Sector map loaded but is empty.');
|
|
32
|
-
} else {
|
|
33
|
-
// console.log('Sector map loaded successfully.'); // Optional: remove if too verbose
|
|
34
|
-
}
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error('Failed to load sector map:', error);
|
|
37
|
-
return; // Stop processing if sector map fails to load
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Check if the current user is considered "smart money"
|
|
42
|
-
if (this.smartMoneyUsers.has(userId) && todayPortfolio && yesterdayPortfolio) {
|
|
43
|
-
// Ensure sectorMap is available before proceeding
|
|
44
|
-
if (!this.sectorMap) {
|
|
45
|
-
console.warn(`Skipping user ${userId}: Sector map not available.`);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const todaySectorInvestment = this.calculateSectorInvestment(todayPortfolio);
|
|
49
|
-
const yesterdaySectorInvestment = this.calculateSectorInvestment(yesterdayPortfolio);
|
|
50
|
-
|
|
51
|
-
// Calculate change in investment per sector
|
|
52
|
-
const allSectors = new Set([...Object.keys(todaySectorInvestment), ...Object.keys(yesterdaySectorInvestment)]);
|
|
53
|
-
for (const sector of allSectors) {
|
|
54
|
-
const todayAmount = todaySectorInvestment[sector] || 0;
|
|
55
|
-
const yesterdayAmount = yesterdaySectorInvestment[sector] || 0;
|
|
56
|
-
const change = todayAmount - yesterdayAmount;
|
|
57
|
-
|
|
58
|
-
// Only record if there is a change
|
|
59
|
-
if (change !== 0) {
|
|
60
|
-
if (!this.sectorFlow[sector]) {
|
|
61
|
-
this.sectorFlow[sector] = 0;
|
|
62
|
-
}
|
|
63
|
-
this.sectorFlow[sector] += change;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
// Optional: Log why processing is skipped for a user
|
|
68
|
-
// if (!this.smartMoneyUsers.has(userId)) console.log(`User ${userId} is not smart money.`);
|
|
69
|
-
// if (!todayPortfolio) console.log(`User ${userId}: Missing today's portfolio.`);
|
|
70
|
-
// if (!yesterdayPortfolio) console.log(`User ${userId}: Missing yesterday's portfolio.`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async identifySmartMoney() {
|
|
75
|
-
console.log("Attempting to identify smart money users...");
|
|
76
|
-
try {
|
|
77
|
-
// Fetching sharded profitability data
|
|
78
|
-
const shardPromises = [];
|
|
79
|
-
const NUM_SHARDS = 50; // Ensure this matches the value used in user_profitability_tracker
|
|
80
|
-
for (let i = 0; i < NUM_SHARDS; i++) {
|
|
81
|
-
// Corrected document path for shards
|
|
82
|
-
const docRef = firestore.collection('historical_insights').doc(`user_profitability_shard_${i}`);
|
|
83
|
-
shardPromises.push(docRef.get());
|
|
84
|
-
}
|
|
85
|
-
const shardSnapshots = await Promise.all(shardPromises);
|
|
86
|
-
|
|
87
|
-
let profitableUsersFound = 0;
|
|
88
|
-
shardSnapshots.forEach((snap, index) => {
|
|
89
|
-
if (snap.exists) {
|
|
90
|
-
const shardData = snap.data().profits || {}; // Assuming data structure { profits: { userId: [...] } }
|
|
91
|
-
for (const userId in shardData) {
|
|
92
|
-
const history = shardData[userId];
|
|
93
|
-
// Check if history is an array and has enough entries
|
|
94
|
-
if (Array.isArray(history) && history.length >= 5) {
|
|
95
|
-
// "Smart money" definition: profitable for at least 5 of the last 7 days
|
|
96
|
-
// Filter based on the 'pnl' property of each history entry
|
|
97
|
-
const profitableDays = history.slice(-7).filter(d => d && typeof d.pnl === 'number' && d.pnl > 0).length;
|
|
98
|
-
if (profitableDays >= 5) {
|
|
99
|
-
this.smartMoneyUsers.add(userId);
|
|
100
|
-
profitableUsersFound++;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
console.warn(`Profitability shard user_profitability_shard_${index} does not exist.`);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
console.log(`Found ${profitableUsersFound} potentially smart money users across all shards.`);
|
|
109
|
-
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.error('Error identifying smart money users:', error);
|
|
112
|
-
// Decide how to handle error: maybe retry, or proceed without smart money data
|
|
113
|
-
this.smartMoneyUsers.clear(); // Ensure set is empty on error
|
|
114
|
-
}
|
|
115
|
-
// Final check after attempt
|
|
116
|
-
if (this.smartMoneyUsers.size === 0) {
|
|
117
|
-
console.warn("No smart money users identified. Smart money flow calculation might be empty.");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
calculateSectorInvestment(portfolio) {
|
|
123
|
-
const sectorInvestment = {};
|
|
124
|
-
// Ensure sectorMap is loaded
|
|
125
|
-
if (!this.sectorMap) {
|
|
126
|
-
console.warn("Cannot calculate sector investment: Sector map not loaded.");
|
|
127
|
-
return sectorInvestment; // Return empty object
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Use AggregatedPositions as it contains 'Invested' amount
|
|
131
|
-
if (portfolio && portfolio.AggregatedPositions && Array.isArray(portfolio.AggregatedPositions)) {
|
|
132
|
-
for (const pos of portfolio.AggregatedPositions) {
|
|
133
|
-
if (pos && typeof pos.InstrumentID !== 'undefined' && typeof pos.Invested === 'number') {
|
|
134
|
-
const sector = this.sectorMap[pos.InstrumentID] || 'N/A';
|
|
135
|
-
if (!sectorInvestment[sector]) {
|
|
136
|
-
sectorInvestment[sector] = 0;
|
|
137
|
-
}
|
|
138
|
-
sectorInvestment[sector] += pos.Invested;
|
|
139
|
-
} else {
|
|
140
|
-
// Log potentially malformed position data
|
|
141
|
-
// console.warn('Skipping position due to missing InstrumentID or Invested amount:', pos);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
} else {
|
|
145
|
-
// Log if AggregatedPositions are missing or not an array
|
|
146
|
-
// console.warn('AggregatedPositions missing or invalid in portfolio for sector investment calculation.');
|
|
147
|
-
}
|
|
148
|
-
return sectorInvestment;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
getResult() {
|
|
153
|
-
// Return only sectors with non-zero flow if desired, or the full object
|
|
154
|
-
const filteredFlow = {};
|
|
155
|
-
for (const sector in this.sectorFlow) {
|
|
156
|
-
if (this.sectorFlow[sector] !== 0) {
|
|
157
|
-
filteredFlow[sector] = this.sectorFlow[sector];
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// console.log("Final Smart Money Flow:", filteredFlow); // Optional: Log final result
|
|
161
|
-
return { smart_money_flow: filteredFlow };
|
|
162
|
-
// Or return the full object: return { smart_money_flow: this.sectorFlow };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracks the investment flow of "smart money".
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Firestore } = require('@google-cloud/firestore');
|
|
6
|
+
const firestore = new Firestore();
|
|
7
|
+
// CORRECTED PATH: ../utils/ instead of ../../utils/
|
|
8
|
+
const { getInstrumentSectorMap } = require('../../../utils/sector_mapping_provider');
|
|
9
|
+
|
|
10
|
+
class SmartMoneyFlow {
|
|
11
|
+
// ... (rest of the code is unchanged) ...
|
|
12
|
+
constructor() {
|
|
13
|
+
this.smartMoneyUsers = new Set();
|
|
14
|
+
this.sectorFlow = {};
|
|
15
|
+
this.sectorMap = null; // Cache for the sector map
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
19
|
+
// Load smart money users only if the set is empty
|
|
20
|
+
if (this.smartMoneyUsers.size === 0) {
|
|
21
|
+
await this.identifySmartMoney();
|
|
22
|
+
// Log after identification attempt
|
|
23
|
+
console.log(`Identified ${this.smartMoneyUsers.size} smart money users.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Load sector map if not already loaded
|
|
27
|
+
if (!this.sectorMap) {
|
|
28
|
+
try {
|
|
29
|
+
this.sectorMap = await getInstrumentSectorMap();
|
|
30
|
+
if (!this.sectorMap || Object.keys(this.sectorMap).length === 0) {
|
|
31
|
+
console.warn('Sector map loaded but is empty.');
|
|
32
|
+
} else {
|
|
33
|
+
// console.log('Sector map loaded successfully.'); // Optional: remove if too verbose
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Failed to load sector map:', error);
|
|
37
|
+
return; // Stop processing if sector map fails to load
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if the current user is considered "smart money"
|
|
42
|
+
if (this.smartMoneyUsers.has(userId) && todayPortfolio && yesterdayPortfolio) {
|
|
43
|
+
// Ensure sectorMap is available before proceeding
|
|
44
|
+
if (!this.sectorMap) {
|
|
45
|
+
console.warn(`Skipping user ${userId}: Sector map not available.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const todaySectorInvestment = this.calculateSectorInvestment(todayPortfolio);
|
|
49
|
+
const yesterdaySectorInvestment = this.calculateSectorInvestment(yesterdayPortfolio);
|
|
50
|
+
|
|
51
|
+
// Calculate change in investment per sector
|
|
52
|
+
const allSectors = new Set([...Object.keys(todaySectorInvestment), ...Object.keys(yesterdaySectorInvestment)]);
|
|
53
|
+
for (const sector of allSectors) {
|
|
54
|
+
const todayAmount = todaySectorInvestment[sector] || 0;
|
|
55
|
+
const yesterdayAmount = yesterdaySectorInvestment[sector] || 0;
|
|
56
|
+
const change = todayAmount - yesterdayAmount;
|
|
57
|
+
|
|
58
|
+
// Only record if there is a change
|
|
59
|
+
if (change !== 0) {
|
|
60
|
+
if (!this.sectorFlow[sector]) {
|
|
61
|
+
this.sectorFlow[sector] = 0;
|
|
62
|
+
}
|
|
63
|
+
this.sectorFlow[sector] += change;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// Optional: Log why processing is skipped for a user
|
|
68
|
+
// if (!this.smartMoneyUsers.has(userId)) console.log(`User ${userId} is not smart money.`);
|
|
69
|
+
// if (!todayPortfolio) console.log(`User ${userId}: Missing today's portfolio.`);
|
|
70
|
+
// if (!yesterdayPortfolio) console.log(`User ${userId}: Missing yesterday's portfolio.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async identifySmartMoney() {
|
|
75
|
+
console.log("Attempting to identify smart money users...");
|
|
76
|
+
try {
|
|
77
|
+
// Fetching sharded profitability data
|
|
78
|
+
const shardPromises = [];
|
|
79
|
+
const NUM_SHARDS = 50; // Ensure this matches the value used in user_profitability_tracker
|
|
80
|
+
for (let i = 0; i < NUM_SHARDS; i++) {
|
|
81
|
+
// Corrected document path for shards
|
|
82
|
+
const docRef = firestore.collection('historical_insights').doc(`user_profitability_shard_${i}`);
|
|
83
|
+
shardPromises.push(docRef.get());
|
|
84
|
+
}
|
|
85
|
+
const shardSnapshots = await Promise.all(shardPromises);
|
|
86
|
+
|
|
87
|
+
let profitableUsersFound = 0;
|
|
88
|
+
shardSnapshots.forEach((snap, index) => {
|
|
89
|
+
if (snap.exists) {
|
|
90
|
+
const shardData = snap.data().profits || {}; // Assuming data structure { profits: { userId: [...] } }
|
|
91
|
+
for (const userId in shardData) {
|
|
92
|
+
const history = shardData[userId];
|
|
93
|
+
// Check if history is an array and has enough entries
|
|
94
|
+
if (Array.isArray(history) && history.length >= 5) {
|
|
95
|
+
// "Smart money" definition: profitable for at least 5 of the last 7 days
|
|
96
|
+
// Filter based on the 'pnl' property of each history entry
|
|
97
|
+
const profitableDays = history.slice(-7).filter(d => d && typeof d.pnl === 'number' && d.pnl > 0).length;
|
|
98
|
+
if (profitableDays >= 5) {
|
|
99
|
+
this.smartMoneyUsers.add(userId);
|
|
100
|
+
profitableUsersFound++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
console.warn(`Profitability shard user_profitability_shard_${index} does not exist.`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
console.log(`Found ${profitableUsersFound} potentially smart money users across all shards.`);
|
|
109
|
+
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('Error identifying smart money users:', error);
|
|
112
|
+
// Decide how to handle error: maybe retry, or proceed without smart money data
|
|
113
|
+
this.smartMoneyUsers.clear(); // Ensure set is empty on error
|
|
114
|
+
}
|
|
115
|
+
// Final check after attempt
|
|
116
|
+
if (this.smartMoneyUsers.size === 0) {
|
|
117
|
+
console.warn("No smart money users identified. Smart money flow calculation might be empty.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
calculateSectorInvestment(portfolio) {
|
|
123
|
+
const sectorInvestment = {};
|
|
124
|
+
// Ensure sectorMap is loaded
|
|
125
|
+
if (!this.sectorMap) {
|
|
126
|
+
console.warn("Cannot calculate sector investment: Sector map not loaded.");
|
|
127
|
+
return sectorInvestment; // Return empty object
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Use AggregatedPositions as it contains 'Invested' amount
|
|
131
|
+
if (portfolio && portfolio.AggregatedPositions && Array.isArray(portfolio.AggregatedPositions)) {
|
|
132
|
+
for (const pos of portfolio.AggregatedPositions) {
|
|
133
|
+
if (pos && typeof pos.InstrumentID !== 'undefined' && typeof pos.Invested === 'number') {
|
|
134
|
+
const sector = this.sectorMap[pos.InstrumentID] || 'N/A';
|
|
135
|
+
if (!sectorInvestment[sector]) {
|
|
136
|
+
sectorInvestment[sector] = 0;
|
|
137
|
+
}
|
|
138
|
+
sectorInvestment[sector] += pos.Invested;
|
|
139
|
+
} else {
|
|
140
|
+
// Log potentially malformed position data
|
|
141
|
+
// console.warn('Skipping position due to missing InstrumentID or Invested amount:', pos);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// Log if AggregatedPositions are missing or not an array
|
|
146
|
+
// console.warn('AggregatedPositions missing or invalid in portfolio for sector investment calculation.');
|
|
147
|
+
}
|
|
148
|
+
return sectorInvestment;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
getResult() {
|
|
153
|
+
// Return only sectors with non-zero flow if desired, or the full object
|
|
154
|
+
const filteredFlow = {};
|
|
155
|
+
for (const sector in this.sectorFlow) {
|
|
156
|
+
if (this.sectorFlow[sector] !== 0) {
|
|
157
|
+
filteredFlow[sector] = this.sectorFlow[sector];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// console.log("Final Smart Money Flow:", filteredFlow); // Optional: Log final result
|
|
161
|
+
return { smart_money_flow: filteredFlow };
|
|
162
|
+
// Or return the full object: return { smart_money_flow: this.sectorFlow };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
166
|
module.exports = SmartMoneyFlow;
|