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,55 +1,112 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the daily change in
|
|
5
|
+
* speculators' risk appetite, as measured by the
|
|
6
|
+
* average distance of their stop-loss orders?"
|
|
7
|
+
*
|
|
8
|
+
* This is a *stateful* calculation that computes a 30-day
|
|
9
|
+
* rolling average.
|
|
3
10
|
*/
|
|
4
|
-
|
|
5
11
|
class RiskAppetiteChange {
|
|
6
12
|
constructor() {
|
|
7
|
-
|
|
8
|
-
this.
|
|
9
|
-
|
|
13
|
+
// Stores *today's* raw values
|
|
14
|
+
this.sl_distances = [];
|
|
15
|
+
// Stores *yesterday's* 30-day history
|
|
16
|
+
this.history = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Defines the output schema for this calculation.
|
|
21
|
+
* @returns {object} JSON Schema object
|
|
22
|
+
*/
|
|
23
|
+
static getSchema() {
|
|
24
|
+
return {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"description": "Tracks the 30-day rolling average of speculator stop loss distance as a proxy for risk appetite.",
|
|
27
|
+
"properties": {
|
|
28
|
+
"risk_appetite_score": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Today's average SL distance %."
|
|
31
|
+
},
|
|
32
|
+
"daily_change_pct": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Percentage change from yesterday's average."
|
|
35
|
+
},
|
|
36
|
+
"avg_sl_distance_30d": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"description": "30-day rolling average of the SL distance."
|
|
39
|
+
},
|
|
40
|
+
"history_30d": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"description": "30-day history of daily average SL distance.",
|
|
43
|
+
"items": { "type": "number" }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": ["risk_appetite_score", "daily_change_pct", "avg_sl_distance_30d", "history_30d"]
|
|
47
|
+
};
|
|
10
48
|
}
|
|
11
49
|
|
|
12
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
13
|
-
|
|
50
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
51
|
+
// 1. Get this metric's history from yesterday (pre-loaded)
|
|
52
|
+
if (this.history.length === 0) { // Only run once
|
|
53
|
+
const yHistoryData = context.yesterdaysDependencyData['risk_appetite_change'];
|
|
54
|
+
if (yHistoryData) {
|
|
55
|
+
this.history = yHistoryData.history_30d || [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (todayPortfolio?.context?.userType !== 'speculator') {
|
|
14
60
|
return;
|
|
15
61
|
}
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (yesterdayAvgDistance !== null && todayAvgDistance !== null) {
|
|
21
|
-
this.totalYesterdaySLDistance += yesterdayAvgDistance;
|
|
22
|
-
this.totalTodaySLDistance += todayAvgDistance;
|
|
23
|
-
this.userCount++;
|
|
62
|
+
|
|
63
|
+
const positions = todayPortfolio.PublicPositions;
|
|
64
|
+
if (!positions || !Array.isArray(positions)) {
|
|
65
|
+
return;
|
|
24
66
|
}
|
|
25
|
-
}
|
|
26
67
|
|
|
27
|
-
calculateAverageSLDistance(positions) {
|
|
28
|
-
let totalDistance = 0;
|
|
29
|
-
let count = 0;
|
|
30
68
|
for (const pos of positions) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
70
|
+
const open_rate = pos.OpenRate || 0;
|
|
71
|
+
|
|
72
|
+
if (sl_rate > 0 && open_rate > 0) {
|
|
73
|
+
const distance = Math.abs(open_rate - sl_rate);
|
|
74
|
+
const distance_pct = (distance / open_rate);
|
|
75
|
+
this.sl_distances.push(distance_pct);
|
|
37
76
|
}
|
|
38
77
|
}
|
|
39
|
-
return count > 0 ? totalDistance / count : null;
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
getResult() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
81
|
+
const yHistory = this.history;
|
|
82
|
+
|
|
83
|
+
let today_avg_dist = 0;
|
|
84
|
+
if (this.sl_distances.length > 0) {
|
|
85
|
+
today_avg_dist = (this.sl_distances.reduce((a, b) => a + b, 0) / this.sl_distances.length) * 100;
|
|
86
|
+
}
|
|
46
87
|
|
|
88
|
+
const newHistory = [today_avg_dist, ...yHistory].slice(0, 30);
|
|
89
|
+
|
|
90
|
+
const yesterday_avg = yHistory[0] || 0;
|
|
91
|
+
const avg_30d = newHistory.reduce((a, b) => a + b, 0) / newHistory.length;
|
|
92
|
+
|
|
93
|
+
let daily_change = 0;
|
|
94
|
+
if (yesterday_avg > 0) {
|
|
95
|
+
daily_change = ((today_avg_dist - yesterday_avg) / yesterday_avg) * 100;
|
|
96
|
+
}
|
|
97
|
+
|
|
47
98
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
99
|
+
risk_appetite_score: today_avg_dist, // Today's score
|
|
100
|
+
daily_change_pct: daily_change,
|
|
101
|
+
avg_sl_distance_30d: avg_30d,
|
|
102
|
+
history_30d: newHistory
|
|
51
103
|
};
|
|
52
104
|
}
|
|
105
|
+
|
|
106
|
+
reset() {
|
|
107
|
+
this.sl_distances = [];
|
|
108
|
+
this.history = [];
|
|
109
|
+
}
|
|
53
110
|
}
|
|
54
111
|
|
|
55
112
|
module.exports = RiskAppetiteChange;
|
|
@@ -1,75 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for TSL effectiveness.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the difference in average
|
|
5
|
+
* P&L between speculators who use a Trailing Stop Loss (TSL)
|
|
6
|
+
* versus those who do not?"
|
|
4
7
|
*/
|
|
5
8
|
class TslEffectiveness {
|
|
6
9
|
constructor() {
|
|
7
|
-
this.
|
|
8
|
-
this.
|
|
10
|
+
this.with_tsl = { pnl_sum: 0, count: 0 };
|
|
11
|
+
this.without_tsl = { pnl_sum: 0, count: 0 };
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
* @returns {number|null}
|
|
15
|
+
* Defines the output schema for this calculation.
|
|
16
|
+
* @returns {object} JSON Schema object
|
|
15
17
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
static getSchema() {
|
|
19
|
+
return {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"description": "Compares the average P&L of speculators who use TSL vs. those who do not.",
|
|
22
|
+
"properties": {
|
|
23
|
+
"with_tsl_avg_pnl": {
|
|
24
|
+
"type": "number",
|
|
25
|
+
"description": "Average P&L for positions with TSL enabled."
|
|
26
|
+
},
|
|
27
|
+
"without_tsl_avg_pnl": {
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "Average P&L for positions without TSL enabled."
|
|
30
|
+
},
|
|
31
|
+
"effectiveness_delta": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"description": "The difference in P&L (With TSL - Without TSL)."
|
|
34
|
+
},
|
|
35
|
+
"with_tsl_count": { "type": "number" },
|
|
36
|
+
"without_tsl_count": { "type": "number" }
|
|
37
|
+
},
|
|
38
|
+
"required": ["with_tsl_avg_pnl", "without_tsl_avg_pnl", "effectiveness_delta", "with_tsl_count", "without_tsl_count"]
|
|
39
|
+
};
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
if (todayPortfolio?.context?.userType !== 'speculator' || !todayPortfolio) {
|
|
43
|
+
// This calculation is only for speculators
|
|
44
|
+
if (todayPortfolio?.context?.userType !== 'speculator') {
|
|
30
45
|
return;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
const positions = todayPortfolio.PublicPositions;
|
|
34
|
-
|
|
35
49
|
if (!positions || !Array.isArray(positions)) {
|
|
36
|
-
return;
|
|
50
|
+
return;
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.tsl_group.pnl_sum += dailyPnl;
|
|
50
|
-
this.tsl_group.count++;
|
|
51
|
-
} else {
|
|
52
|
-
this.nontsl_group.pnl_sum += dailyPnl;
|
|
53
|
-
this.nontsl_group.count++;
|
|
53
|
+
for (const pos of positions) {
|
|
54
|
+
const pnl = pos.NetProfit || 0;
|
|
55
|
+
|
|
56
|
+
if (pos.IsTslEnabled) {
|
|
57
|
+
this.with_tsl.pnl_sum += pnl;
|
|
58
|
+
this.with_tsl.count++;
|
|
59
|
+
} else {
|
|
60
|
+
this.without_tsl.pnl_sum += pnl;
|
|
61
|
+
this.without_tsl.count++;
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
getResult() {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const with_avg = (this.with_tsl.count > 0) ? (this.with_tsl.pnl_sum / this.with_tsl.count) : 0;
|
|
68
|
+
const without_avg = (this.without_tsl.count > 0) ? (this.without_tsl.pnl_sum / this.without_tsl.count) : 0;
|
|
69
|
+
|
|
62
70
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
avg_pnl: nontsl_avg_pnl,
|
|
69
|
-
count: this.nontsl_group.count
|
|
70
|
-
}
|
|
71
|
+
with_tsl_avg_pnl: with_avg,
|
|
72
|
+
without_tsl_avg_pnl: without_avg,
|
|
73
|
+
effectiveness_delta: with_avg - without_avg,
|
|
74
|
+
with_tsl_count: this.with_tsl.count,
|
|
75
|
+
without_tsl_count: this.without_tsl.count
|
|
71
76
|
};
|
|
72
77
|
}
|
|
78
|
+
|
|
79
|
+
reset() {
|
|
80
|
+
this.with_tsl = { pnl_sum: 0, count: 0 };
|
|
81
|
+
this.without_tsl = { pnl_sum: 0, count: 0 };
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
module.exports = TslEffectiveness;
|
|
@@ -1,27 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the average holding duration
|
|
5
|
+
* (in hours) for open speculator positions, grouped by asset?"
|
|
3
6
|
*/
|
|
4
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class HoldingDurationPerAsset {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// { [instrumentId]: { sum_hours: 0, count: 0 } }
|
|
12
|
+
this.assets = new Map();
|
|
9
13
|
this.mappings = null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
const tickerSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"avg_duration_hours": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "Average holding duration in hours."
|
|
27
|
+
},
|
|
28
|
+
"count": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Count of positions used in average."
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
},
|
|
33
|
+
"required": ["avg_duration_hours", "count"]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Calculates the average holding duration (in hours) for open speculator positions per asset.",
|
|
39
|
+
"patternProperties": {
|
|
40
|
+
"^.*$": tickerSchema // Ticker
|
|
41
|
+
},
|
|
42
|
+
"additionalProperties": tickerSchema
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_initAsset(instrumentId) {
|
|
47
|
+
if (!this.assets.has(instrumentId)) {
|
|
48
|
+
this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_getHoldingDurationHours(openDateStr) {
|
|
53
|
+
if (!openDateStr) return 0;
|
|
54
|
+
try {
|
|
55
|
+
const openDate = new Date(openDateStr);
|
|
56
|
+
// Get difference from 'now' (or the snapshot time)
|
|
57
|
+
const diffMs = new Date().getTime() - openDate.getTime();
|
|
58
|
+
return diffMs / (1000 * 60 * 60); // Convert ms to hours
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process(portfolioData) {
|
|
65
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const positions = portfolioData.PublicPositions;
|
|
70
|
+
if (!positions || !Array.isArray(positions)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const pos of positions) {
|
|
75
|
+
const instrumentId = pos.InstrumentID;
|
|
76
|
+
if (!instrumentId) continue;
|
|
77
|
+
|
|
78
|
+
this._initAsset(instrumentId);
|
|
79
|
+
|
|
80
|
+
const duration = this._getHoldingDurationHours(pos.OpenDateTime);
|
|
81
|
+
|
|
82
|
+
if (duration > 0) {
|
|
83
|
+
const assetData = this.assets.get(instrumentId);
|
|
84
|
+
assetData.sum_hours += duration;
|
|
85
|
+
assetData.count++;
|
|
25
86
|
}
|
|
26
87
|
}
|
|
27
88
|
}
|
|
@@ -30,26 +91,25 @@ class HoldingDurationPerAsset {
|
|
|
30
91
|
if (!this.mappings) {
|
|
31
92
|
this.mappings = await loadInstrumentMappings();
|
|
32
93
|
}
|
|
33
|
-
const result = {};
|
|
34
|
-
for (const instrumentId in this.durationData) {
|
|
35
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
36
|
-
const data = this.durationData[instrumentId];
|
|
37
94
|
|
|
38
|
-
|
|
95
|
+
const result = {};
|
|
96
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
97
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
98
|
+
|
|
39
99
|
if (data.count > 0) {
|
|
40
100
|
result[ticker] = {
|
|
41
|
-
|
|
101
|
+
avg_duration_hours: data.sum_hours / data.count,
|
|
102
|
+
count: data.count
|
|
42
103
|
};
|
|
43
104
|
}
|
|
44
105
|
}
|
|
45
|
-
|
|
46
106
|
return result;
|
|
47
107
|
}
|
|
48
108
|
|
|
49
109
|
reset() {
|
|
50
|
-
this.
|
|
110
|
+
this.assets.clear();
|
|
51
111
|
this.mappings = null;
|
|
52
112
|
}
|
|
53
113
|
}
|
|
54
114
|
|
|
55
|
-
module.exports = HoldingDurationPerAsset;
|
|
115
|
+
module.exports = HoldingDurationPerAsset;
|
|
@@ -1,25 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what is the
|
|
5
|
+
* distribution of leverage levels used by speculators?"
|
|
3
6
|
*/
|
|
4
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class LeveragePerAsset {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// { [instrumentId]: { [leverage]: count } }
|
|
12
|
+
this.assets = new Map();
|
|
9
13
|
this.mappings = null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
const leverageSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Distribution of leverage levels for this asset.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Leverage level, e.g., "1x", "5x"
|
|
26
|
+
"^[0-9]+x$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Count of positions at this leverage."
|
|
20
29
|
}
|
|
21
|
-
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": { "type": "number" }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "Calculates the distribution of leverage levels used by speculators for each asset.",
|
|
37
|
+
"patternProperties": {
|
|
38
|
+
"^.*$": leverageSchema // Ticker
|
|
39
|
+
},
|
|
40
|
+
"additionalProperties": leverageSchema
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_initAsset(instrumentId) {
|
|
45
|
+
if (!this.assets.has(instrumentId)) {
|
|
46
|
+
this.assets.set(instrumentId, {});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process(portfolioData) {
|
|
51
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const positions = portfolioData.PublicPositions;
|
|
56
|
+
if (!positions || !Array.isArray(positions)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const pos of positions) {
|
|
61
|
+
const instrumentId = pos.InstrumentID;
|
|
62
|
+
if (!instrumentId) continue;
|
|
63
|
+
|
|
64
|
+
this._initAsset(instrumentId);
|
|
65
|
+
const assetData = this.assets.get(instrumentId);
|
|
66
|
+
|
|
67
|
+
const leverage = pos.Leverage || 1;
|
|
68
|
+
const key = `${leverage}x`;
|
|
69
|
+
|
|
70
|
+
if (!assetData[key]) {
|
|
71
|
+
assetData[key] = 0;
|
|
22
72
|
}
|
|
73
|
+
assetData[key]++;
|
|
23
74
|
}
|
|
24
75
|
}
|
|
25
76
|
|
|
@@ -27,19 +78,17 @@ class LeveragePerAsset {
|
|
|
27
78
|
if (!this.mappings) {
|
|
28
79
|
this.mappings = await loadInstrumentMappings();
|
|
29
80
|
}
|
|
81
|
+
|
|
30
82
|
const result = {};
|
|
31
|
-
for (const instrumentId
|
|
32
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
33
|
-
result[ticker] =
|
|
83
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
84
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
85
|
+
result[ticker] = data;
|
|
34
86
|
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
leverage_per_asset: result
|
|
38
|
-
};
|
|
87
|
+
return result;
|
|
39
88
|
}
|
|
40
89
|
|
|
41
90
|
reset() {
|
|
42
|
-
this.
|
|
91
|
+
this.assets.clear();
|
|
43
92
|
this.mappings = null;
|
|
44
93
|
}
|
|
45
94
|
}
|
|
@@ -1,44 +1,105 @@
|
|
|
1
|
-
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each sector, what is the
|
|
5
|
+
* distribution of leverage levels used by speculators?"
|
|
5
6
|
*/
|
|
7
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
8
|
|
|
7
9
|
class LeveragePerSector {
|
|
8
10
|
constructor() {
|
|
9
|
-
|
|
11
|
+
// { [sector]: { [leverage]: count } }
|
|
12
|
+
this.sectors = new Map();
|
|
13
|
+
this.mappings = null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
const leverageSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Distribution of leverage levels for this sector.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Leverage level, e.g., "1x", "5x", "inf"
|
|
26
|
+
"^[0-9inf]+x$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Count of positions at this leverage."
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": { "type": "number" }
|
|
32
|
+
};
|
|
17
33
|
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
return {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "Calculates the distribution of leverage levels used by speculators for each sector.",
|
|
37
|
+
"patternProperties": {
|
|
38
|
+
"^.*$": leverageSchema // Sector
|
|
39
|
+
},
|
|
40
|
+
"additionalProperties": leverageSchema
|
|
41
|
+
};
|
|
42
|
+
}
|
|
20
43
|
|
|
21
|
-
|
|
22
|
-
|
|
44
|
+
_initSector(sector) {
|
|
45
|
+
if (!this.sectors.has(sector)) {
|
|
46
|
+
this.sectors.set(sector, {
|
|
47
|
+
'1x': 0,
|
|
48
|
+
'2x': 0,
|
|
49
|
+
'5x': 0,
|
|
50
|
+
'10x': 0,
|
|
51
|
+
'inf': 0 // For other leverages
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_getLeverageBucket(leverage) {
|
|
57
|
+
switch (leverage) {
|
|
58
|
+
case 1: return '1x';
|
|
59
|
+
case 2: return '2x';
|
|
60
|
+
case 5: return '5x';
|
|
61
|
+
case 10: return '10x';
|
|
62
|
+
default: return 'inf';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
23
65
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
66
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
67
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.mappings) {
|
|
72
|
+
this.mappings = context.mappings;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const positions = portfolioData.PublicPositions;
|
|
76
|
+
if (!positions || !Array.isArray(positions) || !this.mappings) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
28
79
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
80
|
+
for (const pos of positions) {
|
|
81
|
+
const instrumentId = pos.InstrumentID;
|
|
82
|
+
if (!instrumentId) continue;
|
|
83
|
+
|
|
84
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
85
|
+
this._initSector(sector);
|
|
86
|
+
const sectorData = this.sectors.get(sector);
|
|
87
|
+
|
|
88
|
+
const leverage = pos.Leverage || 1;
|
|
89
|
+
const key = this._getLeverageBucket(leverage);
|
|
90
|
+
|
|
91
|
+
sectorData[key]++;
|
|
33
92
|
}
|
|
93
|
+
}
|
|
34
94
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
async getResult() {
|
|
96
|
+
// Convert Map to plain object
|
|
97
|
+
return Object.fromEntries(this.sectors);
|
|
38
98
|
}
|
|
39
99
|
|
|
40
100
|
reset() {
|
|
41
|
-
this.
|
|
101
|
+
this.sectors.clear();
|
|
102
|
+
this.mappings = null;
|
|
42
103
|
}
|
|
43
104
|
}
|
|
44
105
|
|