aiden-shared-calculations-unified 1.0.63 → 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,73 +1,120 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each ticker, what is the
|
|
5
|
+
* average stop loss distance (in % and value) for
|
|
6
|
+
* long and short positions?"
|
|
4
7
|
*/
|
|
5
8
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
9
|
|
|
7
|
-
class
|
|
10
|
+
class StopLossDistanceByTickerShortLongBreakdown {
|
|
8
11
|
constructor() {
|
|
9
|
-
|
|
10
|
-
this.
|
|
12
|
+
// { [instrumentId]: { long_pct: [], long_val: [], short_pct: [], short_val: [] } }
|
|
13
|
+
this.assets = new Map();
|
|
14
|
+
this.mappings = null;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const tickerSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"long_avg_dist_pct": { "type": "number" },
|
|
26
|
+
"long_avg_dist_val": { "type": "number" },
|
|
27
|
+
"long_count": { "type": "number" },
|
|
28
|
+
"short_avg_dist_pct": { "type": "number" },
|
|
29
|
+
"short_avg_dist_val": { "type": "number" },
|
|
30
|
+
"short_count": { "type": "number" }
|
|
31
|
+
},
|
|
32
|
+
"required": ["long_avg_dist_pct", "long_avg_dist_val", "long_count", "short_avg_dist_pct", "short_avg_dist_val", "short_count"]
|
|
33
|
+
};
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
return {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Calculates avg SL distance (% and value) for long/short positions, grouped by asset.",
|
|
38
|
+
"patternProperties": {
|
|
39
|
+
"^.*$": tickerSchema // Ticker
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": tickerSchema
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_initAsset(instrumentId) {
|
|
46
|
+
if (!this.assets.has(instrumentId)) {
|
|
47
|
+
this.assets.set(instrumentId, {
|
|
48
|
+
long_pct: [], long_val: [], short_pct: [], short_val: []
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_avg(arr) {
|
|
54
|
+
if (arr.length === 0) return 0;
|
|
55
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
56
|
+
}
|
|
19
57
|
|
|
20
|
-
|
|
21
|
-
|
|
58
|
+
process(portfolioData) {
|
|
59
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const positions = portfolioData.PublicPositions;
|
|
64
|
+
if (!positions || !Array.isArray(positions)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
22
67
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
for (const pos of positions) {
|
|
69
|
+
const instrumentId = pos.InstrumentID;
|
|
70
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
71
|
+
const open_rate = pos.OpenRate || 0;
|
|
72
|
+
|
|
73
|
+
if (!instrumentId || sl_rate === 0 || open_rate === 0) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this._initAsset(instrumentId);
|
|
78
|
+
const assetData = this.assets.get(instrumentId);
|
|
79
|
+
|
|
80
|
+
const distance_val = Math.abs(open_rate - sl_rate);
|
|
81
|
+
const distance_pct = (distance_val / open_rate) * 100;
|
|
82
|
+
|
|
83
|
+
if (pos.IsBuy) {
|
|
84
|
+
assetData.long_pct.push(distance_pct);
|
|
85
|
+
assetData.long_val.push(distance_val);
|
|
86
|
+
} else {
|
|
87
|
+
assetData.short_pct.push(distance_pct);
|
|
88
|
+
assetData.short_val.push(distance_val);
|
|
37
89
|
}
|
|
38
90
|
}
|
|
39
91
|
}
|
|
40
92
|
|
|
41
93
|
async getResult() {
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
const mappings = await loadInstrumentMappings();
|
|
45
|
-
this.instrumentToTicker = mappings.instrumentToTicker;
|
|
94
|
+
if (!this.mappings) {
|
|
95
|
+
this.mappings = await loadInstrumentMappings();
|
|
46
96
|
}
|
|
47
97
|
|
|
48
98
|
const result = {};
|
|
49
|
-
for (const instrumentId
|
|
50
|
-
const ticker = this.instrumentToTicker[instrumentId] || `
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
count: data.count
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
99
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
100
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
101
|
+
|
|
102
|
+
result[ticker] = {
|
|
103
|
+
long_avg_dist_pct: this._avg(data.long_pct),
|
|
104
|
+
long_avg_dist_val: this._avg(data.long_val),
|
|
105
|
+
long_count: data.long_pct.length,
|
|
106
|
+
short_avg_dist_pct: this._avg(data.short_pct),
|
|
107
|
+
short_avg_dist_val: this._avg(data.short_val),
|
|
108
|
+
short_count: data.short_pct.length
|
|
109
|
+
};
|
|
64
110
|
}
|
|
65
111
|
return result;
|
|
66
112
|
}
|
|
67
113
|
|
|
68
114
|
reset() {
|
|
69
|
-
this.
|
|
115
|
+
this.assets.clear();
|
|
116
|
+
this.mappings = null;
|
|
70
117
|
}
|
|
71
118
|
}
|
|
72
119
|
|
|
73
|
-
module.exports =
|
|
120
|
+
module.exports = StopLossDistanceByTickerShortLongBreakdown;
|
|
@@ -1,26 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what is the
|
|
5
|
+
* average stop loss level and the percentage of
|
|
6
|
+
* positions that have a stop loss set?"
|
|
3
7
|
*/
|
|
4
8
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
9
|
|
|
6
10
|
class StopLossPerAsset {
|
|
7
11
|
constructor() {
|
|
8
|
-
|
|
12
|
+
// { [instrumentId]: { sl_rate_sum: 0, sl_dist_sum: 0, sl_set_count: 0, total_count: 0 } }
|
|
13
|
+
this.assets = new Map();
|
|
9
14
|
this.mappings = null;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const tickerSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"avg_sl_rate": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Average SL price level (for positions with SL)."
|
|
28
|
+
},
|
|
29
|
+
"avg_sl_pct_dist": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Average SL distance from open price % (for positions with SL)."
|
|
32
|
+
},
|
|
33
|
+
"sl_set_count": { "type": "number" },
|
|
34
|
+
"total_count": { "type": "number" },
|
|
35
|
+
"sl_set_rate_pct": {
|
|
36
|
+
"type": "number",
|
|
37
|
+
"description": "Percentage of positions that have a SL set."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["avg_sl_rate", "avg_sl_pct_dist", "sl_set_count", "total_count", "sl_set_rate_pct"]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Calculates the average SL level and usage rate per asset for speculators.",
|
|
46
|
+
"patternProperties": {
|
|
47
|
+
"^.*$": tickerSchema // Ticker
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": tickerSchema
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_initAsset(instrumentId) {
|
|
54
|
+
if (!this.assets.has(instrumentId)) {
|
|
55
|
+
this.assets.set(instrumentId, {
|
|
56
|
+
sl_rate_sum: 0,
|
|
57
|
+
sl_dist_sum: 0,
|
|
58
|
+
sl_set_count: 0,
|
|
59
|
+
total_count: 0
|
|
60
|
+
});
|
|
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
|
+
const assetData = this.assets.get(instrumentId);
|
|
80
|
+
assetData.total_count++;
|
|
81
|
+
|
|
82
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
83
|
+
if (sl_rate > 0) {
|
|
84
|
+
assetData.sl_set_count++;
|
|
85
|
+
assetData.sl_rate_sum += sl_rate;
|
|
86
|
+
|
|
87
|
+
const open_rate = pos.OpenRate || 0;
|
|
88
|
+
if (open_rate > 0) {
|
|
89
|
+
const distance = Math.abs(open_rate - sl_rate);
|
|
90
|
+
assetData.sl_dist_sum += (distance / open_rate);
|
|
24
91
|
}
|
|
25
92
|
}
|
|
26
93
|
}
|
|
@@ -30,26 +97,27 @@ class StopLossPerAsset {
|
|
|
30
97
|
if (!this.mappings) {
|
|
31
98
|
this.mappings = await loadInstrumentMappings();
|
|
32
99
|
}
|
|
100
|
+
|
|
33
101
|
const result = {};
|
|
34
|
-
for (const instrumentId
|
|
35
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
36
|
-
const
|
|
102
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
103
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
104
|
+
const count = data.sl_set_count;
|
|
37
105
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
106
|
+
result[ticker] = {
|
|
107
|
+
avg_sl_rate: (count > 0) ? (data.sl_rate_sum / count) : 0,
|
|
108
|
+
avg_sl_pct_dist: (count > 0) ? (data.sl_dist_sum / count) * 100 : 0,
|
|
109
|
+
sl_set_count: data.sl_set_count,
|
|
110
|
+
total_count: data.total_count,
|
|
111
|
+
sl_set_rate_pct: (data.total_count > 0) ? (data.sl_set_count / data.total_count) * 100 : 0
|
|
112
|
+
};
|
|
44
113
|
}
|
|
45
|
-
|
|
46
114
|
return result;
|
|
47
115
|
}
|
|
48
116
|
|
|
49
117
|
reset() {
|
|
50
|
-
this.
|
|
118
|
+
this.assets.clear();
|
|
51
119
|
this.mappings = null;
|
|
52
120
|
}
|
|
53
121
|
}
|
|
54
122
|
|
|
55
|
-
module.exports = StopLossPerAsset;
|
|
123
|
+
module.exports = StopLossPerAsset;
|
|
@@ -1,26 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what is the
|
|
5
|
+
* average take profit level and the percentage of
|
|
6
|
+
* positions that have a take profit set?"
|
|
3
7
|
*/
|
|
4
8
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
9
|
|
|
6
10
|
class TakeProfitPerAsset {
|
|
7
11
|
constructor() {
|
|
8
|
-
|
|
12
|
+
// { [instrumentId]: { tp_rate_sum: 0, tp_dist_sum: 0, tp_set_count: 0, total_count: 0 } }
|
|
13
|
+
this.assets = new Map();
|
|
9
14
|
this.mappings = null;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const tickerSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"avg_tp_rate": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Average TP price level (for positions with TP)."
|
|
28
|
+
},
|
|
29
|
+
"avg_tp_pct_dist": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Average TP distance from open price % (for positions with TP)."
|
|
32
|
+
},
|
|
33
|
+
"tp_set_count": { "type": "number" },
|
|
34
|
+
"total_count": { "type": "number" },
|
|
35
|
+
"tp_set_rate_pct": {
|
|
36
|
+
"type": "number",
|
|
37
|
+
"description": "Percentage of positions that have a TP set."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["avg_tp_rate", "avg_tp_pct_dist", "tp_set_count", "total_count", "tp_set_rate_pct"]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Calculates the average TP level and usage rate per asset for speculators.",
|
|
46
|
+
"patternProperties": {
|
|
47
|
+
"^.*$": tickerSchema // Ticker
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": tickerSchema
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_initAsset(instrumentId) {
|
|
54
|
+
if (!this.assets.has(instrumentId)) {
|
|
55
|
+
this.assets.set(instrumentId, {
|
|
56
|
+
tp_rate_sum: 0,
|
|
57
|
+
tp_dist_sum: 0,
|
|
58
|
+
tp_set_count: 0,
|
|
59
|
+
total_count: 0
|
|
60
|
+
});
|
|
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
|
+
const assetData = this.assets.get(instrumentId);
|
|
80
|
+
assetData.total_count++;
|
|
81
|
+
|
|
82
|
+
const tp_rate = pos.TakeProfitRate || 0;
|
|
83
|
+
if (tp_rate > 0) {
|
|
84
|
+
assetData.tp_set_count++;
|
|
85
|
+
assetData.tp_rate_sum += tp_rate;
|
|
86
|
+
|
|
87
|
+
const open_rate = pos.OpenRate || 0;
|
|
88
|
+
if (open_rate > 0) {
|
|
89
|
+
const distance = Math.abs(tp_rate - open_rate);
|
|
90
|
+
assetData.tp_dist_sum += (distance / open_rate);
|
|
24
91
|
}
|
|
25
92
|
}
|
|
26
93
|
}
|
|
@@ -30,26 +97,27 @@ class TakeProfitPerAsset {
|
|
|
30
97
|
if (!this.mappings) {
|
|
31
98
|
this.mappings = await loadInstrumentMappings();
|
|
32
99
|
}
|
|
100
|
+
|
|
33
101
|
const result = {};
|
|
34
|
-
for (const instrumentId
|
|
35
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
102
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
103
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
104
|
+
const count = data.tp_set_count;
|
|
105
|
+
|
|
106
|
+
result[ticker] = {
|
|
107
|
+
avg_tp_rate: (count > 0) ? (data.tp_rate_sum / count) : 0,
|
|
108
|
+
avg_tp_pct_dist: (count > 0) ? (data.tp_dist_sum / count) * 100 : 0,
|
|
109
|
+
tp_set_count: data.tp_set_count,
|
|
110
|
+
total_count: data.total_count,
|
|
111
|
+
tp_set_rate_pct: (data.total_count > 0) ? (data.tp_set_count / data.total_count) * 100 : 0
|
|
112
|
+
};
|
|
44
113
|
}
|
|
45
|
-
|
|
46
114
|
return result;
|
|
47
115
|
}
|
|
48
116
|
|
|
49
117
|
reset() {
|
|
50
|
-
this.
|
|
118
|
+
this.assets.clear();
|
|
51
119
|
this.mappings = null;
|
|
52
120
|
}
|
|
53
121
|
}
|
|
54
122
|
|
|
55
|
-
module.exports = TakeProfitPerAsset;
|
|
123
|
+
module.exports = TakeProfitPerAsset;
|
|
@@ -1,29 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, how many
|
|
5
|
+
* speculators have Trailing Stop Loss (TSL) enabled
|
|
6
|
+
* versus disabled?"
|
|
3
7
|
*/
|
|
4
8
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
9
|
|
|
6
10
|
class TslPerAsset {
|
|
7
11
|
constructor() {
|
|
8
|
-
|
|
12
|
+
// { [instrumentId]: { enabled: 0, disabled: 0 } }
|
|
13
|
+
this.assets = new Map();
|
|
9
14
|
this.mappings = null;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const tickerSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"tsl_enabled_count": { "type": "number" },
|
|
26
|
+
"tsl_disabled_count": { "type": "number" },
|
|
27
|
+
"tsl_enabled_pct": {
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "Percentage of positions with TSL enabled."
|
|
20
30
|
}
|
|
31
|
+
},
|
|
32
|
+
"required": ["tsl_enabled_count", "tsl_disabled_count", "tsl_enabled_pct"]
|
|
33
|
+
};
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
return {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Calculates the TSL (Trailing Stop Loss) enabled vs. disabled rate per asset.",
|
|
38
|
+
"patternProperties": {
|
|
39
|
+
"^.*$": tickerSchema // Ticker
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": tickerSchema
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_initAsset(instrumentId) {
|
|
46
|
+
if (!this.assets.has(instrumentId)) {
|
|
47
|
+
this.assets.set(instrumentId, {
|
|
48
|
+
enabled: 0,
|
|
49
|
+
disabled: 0
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process(portfolioData) {
|
|
55
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const positions = portfolioData.PublicPositions;
|
|
60
|
+
if (!positions || !Array.isArray(positions)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const pos of positions) {
|
|
65
|
+
const instrumentId = pos.InstrumentID;
|
|
66
|
+
if (!instrumentId) continue;
|
|
67
|
+
|
|
68
|
+
this._initAsset(instrumentId);
|
|
69
|
+
const assetData = this.assets.get(instrumentId);
|
|
70
|
+
|
|
71
|
+
if (pos.IsTslEnabled) {
|
|
72
|
+
assetData.enabled++;
|
|
73
|
+
} else {
|
|
74
|
+
assetData.disabled++;
|
|
27
75
|
}
|
|
28
76
|
}
|
|
29
77
|
}
|
|
@@ -32,19 +80,25 @@ class TslPerAsset {
|
|
|
32
80
|
if (!this.mappings) {
|
|
33
81
|
this.mappings = await loadInstrumentMappings();
|
|
34
82
|
}
|
|
83
|
+
|
|
35
84
|
const result = {};
|
|
36
|
-
for (const instrumentId
|
|
37
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
38
|
-
|
|
85
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
86
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
87
|
+
const total = data.enabled + data.disabled;
|
|
88
|
+
|
|
89
|
+
if (total > 0) {
|
|
90
|
+
result[ticker] = {
|
|
91
|
+
tsl_enabled_count: data.enabled,
|
|
92
|
+
tsl_disabled_count: data.disabled,
|
|
93
|
+
tsl_enabled_pct: (data.enabled / total) * 100
|
|
94
|
+
};
|
|
95
|
+
}
|
|
39
96
|
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
tsl_per_asset: result
|
|
43
|
-
};
|
|
97
|
+
return result;
|
|
44
98
|
}
|
|
45
99
|
|
|
46
100
|
reset() {
|
|
47
|
-
this.
|
|
101
|
+
this.assets.clear();
|
|
48
102
|
this.mappings = null;
|
|
49
103
|
}
|
|
50
104
|
}
|