aiden-shared-calculations-unified 1.0.35 → 1.0.37
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 -165
- package/calculations/behavioural/historical/drawdown_response.js +58 -58
- package/calculations/behavioural/historical/dumb-cohort-flow.js +217 -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 +217 -250
- package/calculations/behavioural/historical/smart_money_flow.js +165 -165
- package/calculations/behavioural/historical/user-investment-profile.js +358 -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,145 +1,145 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tracks user activity by comparing portfolio snapshots.
|
|
3
|
-
* This is a historical calculation that defines an "active user" as someone
|
|
4
|
-
* who has opened, closed, or reallocated a position within the last 24 hours.
|
|
5
|
-
*
|
|
6
|
-
* This provides the "Daily Active Users" count for the monitored cohort.
|
|
7
|
-
* This depreciates the user activity sampler cloud function which was inefficient for the api usage and now provides this data for free
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
class DailyUserActivityTracker {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.activeUserIds = new Set();
|
|
13
|
-
this.activityEvents = {
|
|
14
|
-
new_position: 0,
|
|
15
|
-
closed_position: 0,
|
|
16
|
-
reallocation: 0
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Helper to get a simplified map of positions for comparison.
|
|
22
|
-
* @param {object} portfolio - A user's full portfolio object.
|
|
23
|
-
* @returns {object} { posMap: Map<InstrumentID, {invested: number}>, hasAggregated: boolean }
|
|
24
|
-
*/
|
|
25
|
-
_getPortfolioMaps(portfolio) {
|
|
26
|
-
// Prioritize AggregatedPositions, but fall back to PublicPositions
|
|
27
|
-
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
28
|
-
if (!positions || !Array.isArray(positions)) {
|
|
29
|
-
return { posMap: new Map(), hasAggregated: false };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const posMap = new Map();
|
|
33
|
-
for (const pos of positions) {
|
|
34
|
-
const key = pos.InstrumentID;
|
|
35
|
-
if (key) {
|
|
36
|
-
posMap.set(key, {
|
|
37
|
-
// 'InvestedAmount' or 'Invested' is the portfolio percentage
|
|
38
|
-
// We use this for reallocation logic.
|
|
39
|
-
invested: pos.InvestedAmount || pos.Invested || pos.Amount || 0
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// Return the map and a flag indicating if we can trust the 'invested' field
|
|
44
|
-
return { posMap, hasAggregated: !!portfolio.AggregatedPositions };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Processes a single user's daily data.
|
|
49
|
-
*/
|
|
50
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
51
|
-
// This calculation requires both days to find changes.
|
|
52
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const { posMap: yPosMap, hasAggregated: yHasAgg } = this._getPortfolioMaps(yesterdayPortfolio);
|
|
57
|
-
const { posMap: tPosMap, hasAggregated: tHasAgg } = this._getPortfolioMaps(todayPortfolio);
|
|
58
|
-
|
|
59
|
-
// Skip if user has no positions on either day
|
|
60
|
-
if (tPosMap.size === 0 && yPosMap.size === 0) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const yIds = new Set(yPosMap.keys());
|
|
65
|
-
const tIds = new Set(tPosMap.keys());
|
|
66
|
-
let isActive = false;
|
|
67
|
-
|
|
68
|
-
// 1. Check for new positions (high-confidence activity)
|
|
69
|
-
for (const tId of tIds) {
|
|
70
|
-
if (!yIds.has(tId)) {
|
|
71
|
-
isActive = true;
|
|
72
|
-
this.activityEvents.new_position++;
|
|
73
|
-
break; // Found activity, no need to check more
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (isActive) {
|
|
78
|
-
this.activeUserIds.add(userId);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 2. Check for closed positions (high-confidence activity)
|
|
83
|
-
for (const yId of yIds) {
|
|
84
|
-
if (!tIds.has(yId)) {
|
|
85
|
-
isActive = true;
|
|
86
|
-
this.activityEvents.closed_position++;
|
|
87
|
-
break; // Found activity
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (isActive) {
|
|
92
|
-
this.activeUserIds.add(userId);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
|
|
97
|
-
// This checks for changes in the 'Invested' percentage
|
|
98
|
-
// 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
|
|
99
|
-
if (yHasAgg && tHasAgg) {
|
|
100
|
-
for (const tId of tIds) {
|
|
101
|
-
const tInvested = tPosMap.get(tId).invested;
|
|
102
|
-
const yInvested = yPosMap.get(tId)?.invested ?? 0;
|
|
103
|
-
|
|
104
|
-
// Check for a meaningful change (e.g., > 0.01% to avoid float noise)
|
|
105
|
-
if (Math.abs(tInvested - yInvested) > 0.0001) {
|
|
106
|
-
isActive = true;
|
|
107
|
-
this.activityEvents.reallocation++;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (isActive) {
|
|
115
|
-
this.activeUserIds.add(userId);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Returns the final aggregated counts for the day.
|
|
121
|
-
*/
|
|
122
|
-
getResult() {
|
|
123
|
-
return {
|
|
124
|
-
// This is the main metric for your graph
|
|
125
|
-
rawActiveUserCount: this.activeUserIds.size,
|
|
126
|
-
|
|
127
|
-
// This is a bonus metric to see *what* users are doing
|
|
128
|
-
activityBreakdown: this.activityEvents
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Resets the counters for the next run.
|
|
134
|
-
*/
|
|
135
|
-
reset() {
|
|
136
|
-
this.activeUserIds.clear();
|
|
137
|
-
this.activityEvents = {
|
|
138
|
-
new_position: 0,
|
|
139
|
-
closed_position: 0,
|
|
140
|
-
reallocation: 0
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracks user activity by comparing portfolio snapshots.
|
|
3
|
+
* This is a historical calculation that defines an "active user" as someone
|
|
4
|
+
* who has opened, closed, or reallocated a position within the last 24 hours.
|
|
5
|
+
*
|
|
6
|
+
* This provides the "Daily Active Users" count for the monitored cohort.
|
|
7
|
+
* This depreciates the user activity sampler cloud function which was inefficient for the api usage and now provides this data for free
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class DailyUserActivityTracker {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.activeUserIds = new Set();
|
|
13
|
+
this.activityEvents = {
|
|
14
|
+
new_position: 0,
|
|
15
|
+
closed_position: 0,
|
|
16
|
+
reallocation: 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper to get a simplified map of positions for comparison.
|
|
22
|
+
* @param {object} portfolio - A user's full portfolio object.
|
|
23
|
+
* @returns {object} { posMap: Map<InstrumentID, {invested: number}>, hasAggregated: boolean }
|
|
24
|
+
*/
|
|
25
|
+
_getPortfolioMaps(portfolio) {
|
|
26
|
+
// Prioritize AggregatedPositions, but fall back to PublicPositions
|
|
27
|
+
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
28
|
+
if (!positions || !Array.isArray(positions)) {
|
|
29
|
+
return { posMap: new Map(), hasAggregated: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const posMap = new Map();
|
|
33
|
+
for (const pos of positions) {
|
|
34
|
+
const key = pos.InstrumentID;
|
|
35
|
+
if (key) {
|
|
36
|
+
posMap.set(key, {
|
|
37
|
+
// 'InvestedAmount' or 'Invested' is the portfolio percentage
|
|
38
|
+
// We use this for reallocation logic.
|
|
39
|
+
invested: pos.InvestedAmount || pos.Invested || pos.Amount || 0
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Return the map and a flag indicating if we can trust the 'invested' field
|
|
44
|
+
return { posMap, hasAggregated: !!portfolio.AggregatedPositions };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Processes a single user's daily data.
|
|
49
|
+
*/
|
|
50
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
51
|
+
// This calculation requires both days to find changes.
|
|
52
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { posMap: yPosMap, hasAggregated: yHasAgg } = this._getPortfolioMaps(yesterdayPortfolio);
|
|
57
|
+
const { posMap: tPosMap, hasAggregated: tHasAgg } = this._getPortfolioMaps(todayPortfolio);
|
|
58
|
+
|
|
59
|
+
// Skip if user has no positions on either day
|
|
60
|
+
if (tPosMap.size === 0 && yPosMap.size === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const yIds = new Set(yPosMap.keys());
|
|
65
|
+
const tIds = new Set(tPosMap.keys());
|
|
66
|
+
let isActive = false;
|
|
67
|
+
|
|
68
|
+
// 1. Check for new positions (high-confidence activity)
|
|
69
|
+
for (const tId of tIds) {
|
|
70
|
+
if (!yIds.has(tId)) {
|
|
71
|
+
isActive = true;
|
|
72
|
+
this.activityEvents.new_position++;
|
|
73
|
+
break; // Found activity, no need to check more
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isActive) {
|
|
78
|
+
this.activeUserIds.add(userId);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Check for closed positions (high-confidence activity)
|
|
83
|
+
for (const yId of yIds) {
|
|
84
|
+
if (!tIds.has(yId)) {
|
|
85
|
+
isActive = true;
|
|
86
|
+
this.activityEvents.closed_position++;
|
|
87
|
+
break; // Found activity
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isActive) {
|
|
92
|
+
this.activeUserIds.add(userId);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
|
|
97
|
+
// This checks for changes in the 'Invested' percentage
|
|
98
|
+
// 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
|
|
99
|
+
if (yHasAgg && tHasAgg) {
|
|
100
|
+
for (const tId of tIds) {
|
|
101
|
+
const tInvested = tPosMap.get(tId).invested;
|
|
102
|
+
const yInvested = yPosMap.get(tId)?.invested ?? 0;
|
|
103
|
+
|
|
104
|
+
// Check for a meaningful change (e.g., > 0.01% to avoid float noise)
|
|
105
|
+
if (Math.abs(tInvested - yInvested) > 0.0001) {
|
|
106
|
+
isActive = true;
|
|
107
|
+
this.activityEvents.reallocation++;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if (isActive) {
|
|
115
|
+
this.activeUserIds.add(userId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns the final aggregated counts for the day.
|
|
121
|
+
*/
|
|
122
|
+
getResult() {
|
|
123
|
+
return {
|
|
124
|
+
// This is the main metric for your graph
|
|
125
|
+
rawActiveUserCount: this.activeUserIds.size,
|
|
126
|
+
|
|
127
|
+
// This is a bonus metric to see *what* users are doing
|
|
128
|
+
activityBreakdown: this.activityEvents
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resets the counters for the next run.
|
|
134
|
+
*/
|
|
135
|
+
reset() {
|
|
136
|
+
this.activeUserIds.clear();
|
|
137
|
+
this.activityEvents = {
|
|
138
|
+
new_position: 0,
|
|
139
|
+
closed_position: 0,
|
|
140
|
+
reallocation: 0
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
145
|
module.exports = DailyUserActivityTracker;
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tracks "tinkering" activity from speculators.
|
|
3
|
-
* Instead of just opening/closing, this counts how many users
|
|
4
|
-
* actively *adjusted* the SL, TP, or TSL on existing trades.
|
|
5
|
-
*/
|
|
6
|
-
class SpeculatorAdjustmentActivity {
|
|
7
|
-
constructor() {
|
|
8
|
-
// Use Sets to count unique users
|
|
9
|
-
this.sl_adjusted_users = new Set();
|
|
10
|
-
this.tp_adjusted_users = new Set();
|
|
11
|
-
this.tsl_toggled_users = new Set();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
_getPublicPositionsMap(portfolio) {
|
|
15
|
-
const positions = portfolio?.PublicPositions;
|
|
16
|
-
if (!positions || !Array.isArray(positions)) {
|
|
17
|
-
return new Map();
|
|
18
|
-
}
|
|
19
|
-
// Map<PositionID, PositionObject>
|
|
20
|
-
return new Map(positions.map(p => [p.PositionID, p]));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
24
|
-
// This calculation is only for speculators
|
|
25
|
-
if (todayPortfolio?.context?.userType !== 'speculator' || !yesterdayPortfolio) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const yPosMap = this._getPublicPositionsMap(yesterdayPortfolio);
|
|
30
|
-
const tPosMap = this._getPublicPositionsMap(todayPortfolio);
|
|
31
|
-
|
|
32
|
-
if (yPosMap.size === 0 || tPosMap.size === 0) {
|
|
33
|
-
return; // No positions to compare
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const [tPosId, tPos] of tPosMap.entries()) {
|
|
37
|
-
// Check if this position existed yesterday
|
|
38
|
-
if (yPosMap.has(tPosId)) {
|
|
39
|
-
const yPos = yPosMap.get(tPosId);
|
|
40
|
-
|
|
41
|
-
// 1. Check for Stop Loss adjustment
|
|
42
|
-
if (tPos.StopLossRate !== yPos.StopLossRate) {
|
|
43
|
-
this.sl_adjusted_users.add(userId);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2. Check for Take Profit adjustment
|
|
47
|
-
if (tPos.TakeProfitRate !== yPos.TakeProfitRate) {
|
|
48
|
-
this.tp_adjusted_users.add(userId);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 3. Check if TSL was toggled on or off
|
|
52
|
-
if (tPos.IsTslEnabled !== yPos.IsTslEnabled) {
|
|
53
|
-
this.tsl_toggled_users.add(userId);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getResult() {
|
|
60
|
-
return {
|
|
61
|
-
// Count of unique users who adjusted at least one trade's SL
|
|
62
|
-
unique_users_adjusted_sl: this.sl_adjusted_users.size,
|
|
63
|
-
// Count of unique users who adjusted at least one trade's TP
|
|
64
|
-
unique_users_adjusted_tp: this.tp_adjusted_users.size,
|
|
65
|
-
// Count of unique users who toggled TSL on or off
|
|
66
|
-
unique_users_toggled_tsl: this.tsl_toggled_users.size
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
reset() {
|
|
71
|
-
this.sl_adjusted_users.clear();
|
|
72
|
-
this.tp_adjusted_users.clear();
|
|
73
|
-
this.tsl_toggled_users.clear();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracks "tinkering" activity from speculators.
|
|
3
|
+
* Instead of just opening/closing, this counts how many users
|
|
4
|
+
* actively *adjusted* the SL, TP, or TSL on existing trades.
|
|
5
|
+
*/
|
|
6
|
+
class SpeculatorAdjustmentActivity {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Use Sets to count unique users
|
|
9
|
+
this.sl_adjusted_users = new Set();
|
|
10
|
+
this.tp_adjusted_users = new Set();
|
|
11
|
+
this.tsl_toggled_users = new Set();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_getPublicPositionsMap(portfolio) {
|
|
15
|
+
const positions = portfolio?.PublicPositions;
|
|
16
|
+
if (!positions || !Array.isArray(positions)) {
|
|
17
|
+
return new Map();
|
|
18
|
+
}
|
|
19
|
+
// Map<PositionID, PositionObject>
|
|
20
|
+
return new Map(positions.map(p => [p.PositionID, p]));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
24
|
+
// This calculation is only for speculators
|
|
25
|
+
if (todayPortfolio?.context?.userType !== 'speculator' || !yesterdayPortfolio) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const yPosMap = this._getPublicPositionsMap(yesterdayPortfolio);
|
|
30
|
+
const tPosMap = this._getPublicPositionsMap(todayPortfolio);
|
|
31
|
+
|
|
32
|
+
if (yPosMap.size === 0 || tPosMap.size === 0) {
|
|
33
|
+
return; // No positions to compare
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const [tPosId, tPos] of tPosMap.entries()) {
|
|
37
|
+
// Check if this position existed yesterday
|
|
38
|
+
if (yPosMap.has(tPosId)) {
|
|
39
|
+
const yPos = yPosMap.get(tPosId);
|
|
40
|
+
|
|
41
|
+
// 1. Check for Stop Loss adjustment
|
|
42
|
+
if (tPos.StopLossRate !== yPos.StopLossRate) {
|
|
43
|
+
this.sl_adjusted_users.add(userId);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Check for Take Profit adjustment
|
|
47
|
+
if (tPos.TakeProfitRate !== yPos.TakeProfitRate) {
|
|
48
|
+
this.tp_adjusted_users.add(userId);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Check if TSL was toggled on or off
|
|
52
|
+
if (tPos.IsTslEnabled !== yPos.IsTslEnabled) {
|
|
53
|
+
this.tsl_toggled_users.add(userId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getResult() {
|
|
60
|
+
return {
|
|
61
|
+
// Count of unique users who adjusted at least one trade's SL
|
|
62
|
+
unique_users_adjusted_sl: this.sl_adjusted_users.size,
|
|
63
|
+
// Count of unique users who adjusted at least one trade's TP
|
|
64
|
+
unique_users_adjusted_tp: this.tp_adjusted_users.size,
|
|
65
|
+
// Count of unique users who toggled TSL on or off
|
|
66
|
+
unique_users_toggled_tsl: this.tsl_toggled_users.size
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
reset() {
|
|
71
|
+
this.sl_adjusted_users.clear();
|
|
72
|
+
this.tp_adjusted_users.clear();
|
|
73
|
+
this.tsl_toggled_users.clear();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
77
|
module.exports = SpeculatorAdjustmentActivity;
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calculates the average position size (as a portfolio percentage) for each asset.
|
|
3
|
-
*/
|
|
4
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
|
-
|
|
6
|
-
class AssetPositionSize {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.assets = {};
|
|
9
|
-
this.mappings = null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
|
|
13
|
-
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
14
|
-
if (!positions || !Array.isArray(positions)) return;
|
|
15
|
-
|
|
16
|
-
for (const position of positions) {
|
|
17
|
-
const instrumentId = position.InstrumentID;
|
|
18
|
-
if (!instrumentId) continue;
|
|
19
|
-
|
|
20
|
-
if (!this.assets[instrumentId]) {
|
|
21
|
-
this.assets[instrumentId] = { position_count: 0, position_value_sum: 0 };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.assets[instrumentId].position_count++;
|
|
25
|
-
// FIX: Use the 'Invested' field, which holds the portfolio percentage
|
|
26
|
-
this.assets[instrumentId].position_value_sum += (position.Invested || 0);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async getResult() {
|
|
31
|
-
if (!this.mappings) {
|
|
32
|
-
this.mappings = await loadInstrumentMappings();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const result = {};
|
|
36
|
-
for (const instrumentId in this.assets) {
|
|
37
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
38
|
-
const data = this.assets[instrumentId];
|
|
39
|
-
|
|
40
|
-
if (data.position_count > 0) {
|
|
41
|
-
result[ticker] = {
|
|
42
|
-
// This is now the average *percentage* size
|
|
43
|
-
average_position_size: data.position_value_sum / data.position_count,
|
|
44
|
-
position_count: data.position_count
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
reset() {
|
|
53
|
-
this.assets = {};
|
|
54
|
-
this.mappings = null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the average position size (as a portfolio percentage) for each asset.
|
|
3
|
+
*/
|
|
4
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
|
+
|
|
6
|
+
class AssetPositionSize {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.assets = {};
|
|
9
|
+
this.mappings = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
|
|
13
|
+
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
14
|
+
if (!positions || !Array.isArray(positions)) return;
|
|
15
|
+
|
|
16
|
+
for (const position of positions) {
|
|
17
|
+
const instrumentId = position.InstrumentID;
|
|
18
|
+
if (!instrumentId) continue;
|
|
19
|
+
|
|
20
|
+
if (!this.assets[instrumentId]) {
|
|
21
|
+
this.assets[instrumentId] = { position_count: 0, position_value_sum: 0 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.assets[instrumentId].position_count++;
|
|
25
|
+
// FIX: Use the 'Invested' field, which holds the portfolio percentage
|
|
26
|
+
this.assets[instrumentId].position_value_sum += (position.Invested || 0);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getResult() {
|
|
31
|
+
if (!this.mappings) {
|
|
32
|
+
this.mappings = await loadInstrumentMappings();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const instrumentId in this.assets) {
|
|
37
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
38
|
+
const data = this.assets[instrumentId];
|
|
39
|
+
|
|
40
|
+
if (data.position_count > 0) {
|
|
41
|
+
result[ticker] = {
|
|
42
|
+
// This is now the average *percentage* size
|
|
43
|
+
average_position_size: data.position_value_sum / data.position_count,
|
|
44
|
+
position_count: data.position_count
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
reset() {
|
|
53
|
+
this.assets = {};
|
|
54
|
+
this.mappings = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
58
|
module.exports = AssetPositionSize;
|