aiden-shared-calculations-unified 1.0.64 → 1.0.65
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,118 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* by aggregating portfolio percentage changes across all users.
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for deposit/withdrawal percentage.
|
|
4
3
|
*
|
|
5
|
-
* This
|
|
6
|
-
*
|
|
4
|
+
* This metric estimates the net cash flow (deposits vs. withdrawals)
|
|
5
|
+
* by analyzing changes in portfolio percentages.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - P/L_Effect = avg_value_day1 - avg_invested_day1
|
|
11
|
-
* - Trading_Effect = avg_invested_day2 - avg_invested_day1
|
|
7
|
+
* It's a proxy based on the assumption that changes in the *sum* of
|
|
8
|
+
* position percentages not accounted for by P&L represent new cash.
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
* A negative value indicates a net DEPOSIT (denominator grew, shrinking all %s).
|
|
15
|
-
* A positive value indicates a net WITHDRAWAL (denominator shrank, inflating all %s).
|
|
10
|
+
* (DEPRECIATED by crowd-cash-flow-proxy, but kept for reference)
|
|
16
11
|
*/
|
|
17
|
-
|
|
18
|
-
class CrowdCashFlowProxy {
|
|
12
|
+
class DepositWithdrawalPercentage {
|
|
19
13
|
constructor() {
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.total_value_day2 = 0;
|
|
14
|
+
this.invested_pct_yesterday_sum = 0;
|
|
15
|
+
this.invested_pct_today_sum = 0;
|
|
16
|
+
this.pnl_pct_today_sum = 0;
|
|
24
17
|
this.user_count = 0;
|
|
25
18
|
}
|
|
26
19
|
|
|
27
20
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @
|
|
30
|
-
* @param {string} field - The field to sum ('Invested' or 'Value').
|
|
31
|
-
* @returns {number} The total sum of that field.
|
|
21
|
+
* Defines the output schema for this calculation.
|
|
22
|
+
* @returns {object} JSON Schema object
|
|
32
23
|
*/
|
|
33
|
-
|
|
24
|
+
static getSchema() {
|
|
25
|
+
return {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"description": "(DEPRECIATED) Estimates net cash flow by analyzing changes in portfolio position percentages.",
|
|
28
|
+
"properties": {
|
|
29
|
+
"estimated_net_flow_pct": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "The estimated net cash flow (deposit/withdrawal) as an average percentage change across all users."
|
|
32
|
+
},
|
|
33
|
+
"user_count": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "Number of users processed."
|
|
36
|
+
},
|
|
37
|
+
"debug": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"invested_pct_yesterday_sum": { "type": "number" },
|
|
41
|
+
"invested_pct_today_sum": { "type": "number" },
|
|
42
|
+
"pnl_pct_today_sum": { "type": "number" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": ["estimated_net_flow_pct", "user_count"]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_getPortfolioSums(portfolio) {
|
|
51
|
+
const positions = portfolio?.AggregatedPositions;
|
|
34
52
|
if (!positions || !Array.isArray(positions)) {
|
|
35
|
-
return 0;
|
|
53
|
+
return { investedPct: 0, pnlPct: 0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let investedPct = 0;
|
|
57
|
+
let pnlPct = 0;
|
|
58
|
+
|
|
59
|
+
for (const pos of positions) {
|
|
60
|
+
investedPct += pos.InvestedAmount || pos.Invested || 0;
|
|
61
|
+
pnlPct += (pos.NetProfit || 0) / 100; // Assuming NetProfit is in %
|
|
36
62
|
}
|
|
37
|
-
|
|
63
|
+
|
|
64
|
+
return { investedPct, pnlPct };
|
|
38
65
|
}
|
|
39
66
|
|
|
40
|
-
process(todayPortfolio, yesterdayPortfolio
|
|
41
|
-
|
|
42
|
-
if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
|
|
67
|
+
process(todayPortfolio, yesterdayPortfolio) {
|
|
68
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
43
69
|
return;
|
|
44
70
|
}
|
|
45
71
|
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// Only include users who have some form of positions
|
|
52
|
-
if (invested_day1 === 0 && invested_day2 === 0 && value_day1 === 0 && value_day2 === 0) {
|
|
53
|
-
return;
|
|
72
|
+
const ySums = this._getPortfolioSums(yesterdayPortfolio);
|
|
73
|
+
const tSums = this._getPortfolioSums(todayPortfolio);
|
|
74
|
+
|
|
75
|
+
if (ySums.investedPct === 0 && tSums.investedPct === 0) {
|
|
76
|
+
return; // Skip users with no positions
|
|
54
77
|
}
|
|
55
78
|
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this.total_value_day2 += value_day2;
|
|
79
|
+
this.invested_pct_yesterday_sum += ySums.investedPct;
|
|
80
|
+
this.invested_pct_today_sum += tSums.investedPct;
|
|
81
|
+
this.pnl_pct_today_sum += tSums.pnlPct; // Use today's P&L
|
|
60
82
|
this.user_count++;
|
|
61
83
|
}
|
|
62
84
|
|
|
63
85
|
getResult() {
|
|
64
86
|
if (this.user_count === 0) {
|
|
65
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
estimated_net_flow_pct: 0,
|
|
89
|
+
user_count: 0,
|
|
90
|
+
debug: {
|
|
91
|
+
invested_pct_yesterday_sum: 0,
|
|
92
|
+
invested_pct_today_sum: 0,
|
|
93
|
+
pnl_pct_today_sum: 0
|
|
94
|
+
}
|
|
95
|
+
};
|
|
66
96
|
}
|
|
97
|
+
|
|
98
|
+
// Avg % change in invested capital
|
|
99
|
+
const avg_invested_change = (this.invested_pct_today_sum - this.invested_pct_yesterday_sum) / this.user_count;
|
|
100
|
+
// Avg P&L
|
|
101
|
+
const avg_pnl = this.pnl_pct_today_sum / this.user_count;
|
|
67
102
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
const avg_value_day1 = this.total_value_day1 / this.user_count;
|
|
71
|
-
const avg_invested_day2 = this.total_invested_day2 / this.user_count;
|
|
72
|
-
const avg_value_day2 = this.total_value_day2 / this.user_count;
|
|
73
|
-
|
|
74
|
-
// 2. Isolate the three effects
|
|
75
|
-
const total_value_change = avg_value_day2 - avg_value_day1;
|
|
76
|
-
const pl_effect = avg_value_day1 - avg_invested_day1;
|
|
77
|
-
const trading_effect = avg_invested_day2 - avg_invested_day1;
|
|
78
|
-
|
|
79
|
-
// 3. Solve for the Cash Flow Effect
|
|
80
|
-
// Total_Change = pl_effect + trading_effect + cash_flow_effect
|
|
81
|
-
const cash_flow_effect = total_value_change - pl_effect - trading_effect;
|
|
103
|
+
// The change not accounted for by P&L is the proxy flow
|
|
104
|
+
const net_flow_pct = avg_invested_change - avg_pnl;
|
|
82
105
|
|
|
83
106
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Debug components
|
|
93
|
-
components: {
|
|
94
|
-
total_value_change: total_value_change,
|
|
95
|
-
pl_effect: pl_effect,
|
|
96
|
-
trading_effect: trading_effect
|
|
97
|
-
},
|
|
98
|
-
// Debug averages
|
|
99
|
-
averages: {
|
|
100
|
-
avg_invested_day1: avg_invested_day1,
|
|
101
|
-
avg_value_day1: avg_value_day1,
|
|
102
|
-
avg_invested_day2: avg_invested_day2,
|
|
103
|
-
avg_value_day2: avg_value_day2
|
|
104
|
-
},
|
|
105
|
-
user_sample_size: this.user_count
|
|
107
|
+
estimated_net_flow_pct: net_flow_pct,
|
|
108
|
+
user_count: this.user_count,
|
|
109
|
+
debug: {
|
|
110
|
+
invested_pct_yesterday_sum: this.invested_pct_yesterday_sum,
|
|
111
|
+
invested_pct_today_sum: this.invested_pct_today_sum,
|
|
112
|
+
pnl_pct_today_sum: this.pnl_pct_today_sum
|
|
113
|
+
}
|
|
106
114
|
};
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
reset() {
|
|
110
|
-
this.
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.total_value_day2 = 0;
|
|
118
|
+
this.invested_pct_yesterday_sum = 0;
|
|
119
|
+
this.invested_pct_today_sum = 0;
|
|
120
|
+
this.pnl_pct_today_sum = 0;
|
|
114
121
|
this.user_count = 0;
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
module.exports =
|
|
125
|
+
module.exports = DepositWithdrawalPercentage;
|
|
@@ -1,49 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for new allocation percentage.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "On average, what percentage of a user's
|
|
5
|
+
* portfolio was allocated to *new* positions today?"
|
|
6
|
+
*
|
|
7
|
+
* This measures the flow of capital into new ideas vs. existing ones.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
9
|
class NewAllocationPercentage {
|
|
7
10
|
constructor() {
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
11
|
+
this.total_new_allocation_pct = 0;
|
|
12
|
+
this.users_with_new_positions = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Defines the output schema for this calculation.
|
|
17
|
+
* @returns {object} JSON Schema object
|
|
18
|
+
*/
|
|
19
|
+
static getSchema() {
|
|
20
|
+
return {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Calculates the average portfolio percentage allocated to *new* positions today.",
|
|
23
|
+
"properties": {
|
|
24
|
+
"average_new_allocation_pct": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "The average percentage of a portfolio allocated to new positions, for users who opened new positions."
|
|
27
|
+
},
|
|
28
|
+
"total_new_allocation_pct_sum": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "The sum of all new allocation percentages."
|
|
31
|
+
},
|
|
32
|
+
"user_count_with_new_positions": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "The count of users who opened at least one new position."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["average_new_allocation_pct", "total_new_allocation_pct_sum", "user_count_with_new_positions"]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_getPortfolioPositionMap(portfolio) {
|
|
42
|
+
// We MUST use AggregatedPositions for the 'Invested' %
|
|
43
|
+
const positions = portfolio?.AggregatedPositions;
|
|
44
|
+
if (!positions || !Array.isArray(positions)) {
|
|
45
|
+
return new Map();
|
|
46
|
+
}
|
|
47
|
+
// Map<InstrumentID, { invested: number }>
|
|
48
|
+
return new Map(positions.map(p => [p.InstrumentID, {
|
|
49
|
+
invested: p.InvestedAmount || p.Invested || 0
|
|
50
|
+
}]));
|
|
10
51
|
}
|
|
11
52
|
|
|
12
|
-
process(todayPortfolio, yesterdayPortfolio
|
|
13
|
-
|
|
14
|
-
|
|
53
|
+
process(todayPortfolio, yesterdayPortfolio) {
|
|
54
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const yPosMap = this._getPortfolioPositionMap(yesterdayPortfolio);
|
|
59
|
+
const tPosMap = this._getPortfolioPositionMap(todayPortfolio);
|
|
15
60
|
|
|
16
|
-
if (
|
|
61
|
+
if (tPosMap.size === 0) {
|
|
62
|
+
return; // No positions today
|
|
63
|
+
}
|
|
17
64
|
|
|
18
|
-
|
|
19
|
-
let userNewAllocation = 0;
|
|
65
|
+
let userNewAllocationPct = 0;
|
|
20
66
|
|
|
21
|
-
for (const
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
67
|
+
for (const [tId, tPosData] of tPosMap.entries()) {
|
|
68
|
+
// Check if this position is new (in today but not yesterday)
|
|
69
|
+
if (!yPosMap.has(tId)) {
|
|
70
|
+
userNewAllocationPct += tPosData.invested;
|
|
25
71
|
}
|
|
26
72
|
}
|
|
27
73
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.userCount++;
|
|
74
|
+
if (userNewAllocationPct > 0) {
|
|
75
|
+
this.total_new_allocation_pct += userNewAllocationPct;
|
|
76
|
+
this.users_with_new_positions++;
|
|
77
|
+
}
|
|
33
78
|
}
|
|
34
79
|
|
|
35
80
|
getResult() {
|
|
36
|
-
|
|
81
|
+
const avg_pct = (this.users_with_new_positions > 0)
|
|
82
|
+
? (this.total_new_allocation_pct / this.users_with_new_positions)
|
|
83
|
+
: 0;
|
|
84
|
+
|
|
37
85
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
86
|
+
average_new_allocation_pct: avg_pct,
|
|
87
|
+
total_new_allocation_pct_sum: this.total_new_allocation_pct,
|
|
88
|
+
user_count_with_new_positions: this.users_with_new_positions
|
|
40
89
|
};
|
|
41
90
|
}
|
|
42
91
|
|
|
43
92
|
reset() {
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
93
|
+
this.total_new_allocation_pct = 0;
|
|
94
|
+
this.users_with_new_positions = 0;
|
|
46
95
|
}
|
|
47
96
|
}
|
|
48
97
|
|
|
49
|
-
module.exports = NewAllocationPercentage;
|
|
98
|
+
module.exports = NewAllocationPercentage;
|
|
@@ -1,62 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for reallocation increase percentage.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "On average, what was the total percentage
|
|
5
|
+
* increase in allocation to *existing* positions today?"
|
|
6
|
+
*
|
|
7
|
+
* This measures "doubling down" or adding to winners/losers.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
9
|
class ReallocationIncreasePercentage {
|
|
7
10
|
constructor() {
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
11
|
+
this.total_reallocation_increase_pct = 0;
|
|
12
|
+
this.users_with_reallocations = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Defines the output schema for this calculation.
|
|
17
|
+
* @returns {object} JSON Schema object
|
|
18
|
+
*/
|
|
19
|
+
static getSchema() {
|
|
20
|
+
return {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Calculates the average portfolio percentage *added* to *existing* positions today.",
|
|
23
|
+
"properties": {
|
|
24
|
+
"average_reallocation_increase_pct": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "The average percentage of a portfolio added to existing positions, for users who reallocated."
|
|
27
|
+
},
|
|
28
|
+
"total_reallocation_increase_pct_sum": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "The sum of all reallocation increase percentages."
|
|
31
|
+
},
|
|
32
|
+
"user_count_with_reallocations": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "The count of users who added to at least one existing position."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["average_reallocation_increase_pct", "total_reallocation_increase_pct_sum", "user_count_with_reallocations"]
|
|
38
|
+
};
|
|
10
39
|
}
|
|
11
40
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
_getPortfolioPositionMap(portfolio) {
|
|
42
|
+
// We MUST use AggregatedPositions for the 'Invested' %
|
|
43
|
+
const positions = portfolio?.AggregatedPositions;
|
|
44
|
+
if (!positions || !Array.isArray(positions)) {
|
|
45
|
+
return new Map();
|
|
46
|
+
}
|
|
47
|
+
// Map<InstrumentID, { invested: number }>
|
|
48
|
+
return new Map(positions.map(p => [p.InstrumentID, {
|
|
49
|
+
invested: p.InvestedAmount || p.Invested || 0
|
|
50
|
+
}]));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process(todayPortfolio, yesterdayPortfolio) {
|
|
54
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
15
55
|
return;
|
|
16
56
|
}
|
|
17
57
|
|
|
18
|
-
const
|
|
19
|
-
|
|
58
|
+
const yPosMap = this._getPortfolioPositionMap(yesterdayPortfolio);
|
|
59
|
+
const tPosMap = this._getPortfolioPositionMap(todayPortfolio);
|
|
20
60
|
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
if (tPosMap.size === 0 || yPosMap.size === 0) {
|
|
62
|
+
return; // No positions to compare
|
|
63
|
+
}
|
|
23
64
|
|
|
24
|
-
|
|
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;
|
|
65
|
+
let userReallocationIncrease = 0;
|
|
29
66
|
|
|
30
|
-
|
|
67
|
+
for (const [tId, tPosData] of tPosMap.entries()) {
|
|
68
|
+
// Check if this position also existed yesterday
|
|
69
|
+
if (yPosMap.has(tId)) {
|
|
70
|
+
const yPosData = yPosMap.get(tId);
|
|
71
|
+
const increase = tPosData.invested - yPosData.invested;
|
|
31
72
|
|
|
32
|
-
//
|
|
33
|
-
if (
|
|
34
|
-
|
|
73
|
+
// We only care about *increases*
|
|
74
|
+
if (increase > 0) {
|
|
75
|
+
userReallocationIncrease += increase;
|
|
35
76
|
}
|
|
36
77
|
}
|
|
37
78
|
}
|
|
38
79
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.userCount++;
|
|
80
|
+
if (userReallocationIncrease > 0) {
|
|
81
|
+
this.total_reallocation_increase_pct += userReallocationIncrease;
|
|
82
|
+
this.users_with_reallocations++;
|
|
43
83
|
}
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
getResult() {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
const avg_pct = (this.users_with_reallocations > 0)
|
|
88
|
+
? (this.total_reallocation_increase_pct / this.users_with_reallocations)
|
|
89
|
+
: 0;
|
|
50
90
|
|
|
51
91
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
average_reallocation_increase_pct: avg_pct,
|
|
93
|
+
total_reallocation_increase_pct_sum: this.total_reallocation_increase_pct,
|
|
94
|
+
user_count_with_reallocations: this.users_with_reallocations
|
|
54
95
|
};
|
|
55
96
|
}
|
|
56
97
|
|
|
57
98
|
reset() {
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
99
|
+
this.total_reallocation_increase_pct = 0;
|
|
100
|
+
this.users_with_reallocations = 0;
|
|
60
101
|
}
|
|
61
102
|
}
|
|
62
103
|
|
|
@@ -1,55 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for daily buy/sell sentiment.
|
|
3
|
+
*
|
|
4
|
+
* This metric tracks the total number of 'buy' (long) vs 'sell' (short)
|
|
5
|
+
* positions held across all instruments.
|
|
6
|
+
*
|
|
7
|
+
* This provides a raw, global sentiment reading.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
9
|
class DailyBuySellSentimentCount {
|
|
7
10
|
constructor() {
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.processedDay = false; // Flag to run logic only once per day
|
|
11
|
+
this.buyPositions = 0;
|
|
12
|
+
this.sellPositions = 0;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Defines the output schema for this calculation.
|
|
17
|
+
* @returns {object} JSON Schema object
|
|
18
|
+
*/
|
|
19
|
+
static getSchema() {
|
|
20
|
+
return {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Total count of 'buy' (long) vs 'sell' (short) positions and the sentiment ratio.",
|
|
23
|
+
"properties": {
|
|
24
|
+
"totalBuyPositions": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "Total count of all 'buy' (long) positions."
|
|
27
|
+
},
|
|
28
|
+
"totalSellPositions": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Total count of all 'sell' (short) positions."
|
|
31
|
+
},
|
|
32
|
+
"sentimentRatio": {
|
|
33
|
+
"type": ["number", "null"],
|
|
34
|
+
"description": "Ratio of buy to sell positions (buy/sell). Null if no sell positions."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["totalBuyPositions", "totalSellPositions", "sentimentRatio"]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
17
40
|
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
process(portfolioData) {
|
|
42
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
43
|
+
if (!positions || !Array.isArray(positions)) {
|
|
20
44
|
return;
|
|
21
45
|
}
|
|
22
46
|
|
|
23
|
-
for (const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.totalBuyPositions += (buyPercent * totalOwners);
|
|
30
|
-
this.totalSellPositions += (sellPercent * totalOwners);
|
|
47
|
+
for (const pos of positions) {
|
|
48
|
+
if (pos.IsBuy) {
|
|
49
|
+
this.buyPositions++;
|
|
50
|
+
} else {
|
|
51
|
+
this.sellPositions++;
|
|
31
52
|
}
|
|
32
53
|
}
|
|
33
54
|
}
|
|
34
55
|
|
|
35
56
|
getResult() {
|
|
36
|
-
// --- ADD THIS CHECK ---
|
|
37
|
-
if (!this.processedDay) {
|
|
38
|
-
console.warn('[DailyBuySellSentimentCount] getResult called but process never ran (missing insights). Returning null for backfill.');
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
// --- END ADD ---
|
|
42
|
-
|
|
43
57
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
totalBuyPositions: this.buyPositions,
|
|
59
|
+
totalSellPositions: this.sellPositions,
|
|
60
|
+
// Calculate ratio: Buy / Sell
|
|
61
|
+
sentimentRatio: (this.sellPositions > 0) ? (this.buyPositions / this.sellPositions) : null
|
|
46
62
|
};
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
reset() {
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
52
|
-
this.processedDay = false;
|
|
66
|
+
this.buyPositions = 0;
|
|
67
|
+
this.sellPositions = 0;
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
|
|
@@ -1,45 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total positions held.
|
|
3
|
+
*
|
|
4
|
+
* This is a simple counter that calculates the total number of positions
|
|
5
|
+
* held across all users and all instruments today.
|
|
6
|
+
*
|
|
7
|
+
* It provides a measure of overall market participation.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
9
|
class DailyTotalPositionsHeld {
|
|
7
10
|
constructor() {
|
|
8
11
|
this.totalPositions = 0;
|
|
9
|
-
this.processedDay = false; // Flag to run logic only once per day
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Defines the output schema for this calculation.
|
|
16
|
+
* @returns {object} JSON Schema object
|
|
17
|
+
*/
|
|
18
|
+
static getSchema() {
|
|
19
|
+
return {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Calculates the total number of positions held across all users and instruments.",
|
|
22
|
+
"properties": {
|
|
23
|
+
"totalPositions": {
|
|
24
|
+
"type": "number",
|
|
25
|
+
"description": "The total aggregated count of all positions."
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["totalPositions"]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
process(portfolioData) {
|
|
33
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
34
|
+
if (positions && Array.isArray(positions)) {
|
|
35
|
+
this.totalPositions += positions.length;
|
|
24
36
|
}
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
getResult() {
|
|
28
|
-
// --- ADD THIS CHECK ---
|
|
29
|
-
if (!this.processedDay) {
|
|
30
|
-
console.warn('[DailyTotalPositionsHeld] getResult called but process never ran (missing insights). Returning null for backfill.');
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
// --- END ADD ---
|
|
34
|
-
|
|
35
40
|
return {
|
|
36
|
-
|
|
41
|
+
totalPositions: this.totalPositions
|
|
37
42
|
};
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
reset() {
|
|
41
46
|
this.totalPositions = 0;
|
|
42
|
-
this.processedDay = false;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|