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,33 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the average Risk/Reward
|
|
5
|
+
* ratio (based on SL/TP) for speculator positions on each asset?"
|
|
3
6
|
*/
|
|
4
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class RiskRewardRatioPerAsset {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// { [instrumentId]: { sum_rr: 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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.rrData[instrumentId].count++;
|
|
28
|
-
}
|
|
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_rr_ratio": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "Average Risk/Reward ratio (Reward / Risk)."
|
|
27
|
+
},
|
|
28
|
+
"count": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Count of positions with both SL and TP set."
|
|
29
31
|
}
|
|
32
|
+
},
|
|
33
|
+
"required": ["avg_rr_ratio", "count"]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Calculates the average Risk/Reward ratio from SL/TP settings for each 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_rr: 0, count: 0 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process(portfolioData) {
|
|
53
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const positions = portfolioData.PublicPositions;
|
|
58
|
+
if (!positions || !Array.isArray(positions)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const pos of positions) {
|
|
63
|
+
const instrumentId = pos.InstrumentID;
|
|
64
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
65
|
+
const tp_rate = pos.TakeProfitRate || 0;
|
|
66
|
+
const open_rate = pos.OpenRate || 0;
|
|
67
|
+
|
|
68
|
+
// Need all three to calculate R/R
|
|
69
|
+
if (!instrumentId || sl_rate === 0 || tp_rate === 0 || open_rate === 0) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const risk = Math.abs(open_rate - sl_rate);
|
|
74
|
+
const reward = Math.abs(tp_rate - open_rate);
|
|
75
|
+
|
|
76
|
+
if (risk === 0) {
|
|
77
|
+
continue; // Cannot divide by zero
|
|
30
78
|
}
|
|
79
|
+
|
|
80
|
+
const rr_ratio = reward / risk;
|
|
81
|
+
|
|
82
|
+
this._initAsset(instrumentId);
|
|
83
|
+
const assetData = this.assets.get(instrumentId);
|
|
84
|
+
assetData.sum_rr += rr_ratio;
|
|
85
|
+
assetData.count++;
|
|
31
86
|
}
|
|
32
87
|
}
|
|
33
88
|
|
|
@@ -35,26 +90,25 @@ class RiskRewardRatioPerAsset {
|
|
|
35
90
|
if (!this.mappings) {
|
|
36
91
|
this.mappings = await loadInstrumentMappings();
|
|
37
92
|
}
|
|
38
|
-
const result = {};
|
|
39
|
-
for (const instrumentId in this.rrData) {
|
|
40
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
41
|
-
const data = this.rrData[instrumentId];
|
|
42
93
|
|
|
43
|
-
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
96
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
97
|
+
|
|
44
98
|
if (data.count > 0) {
|
|
45
99
|
result[ticker] = {
|
|
46
|
-
|
|
100
|
+
avg_rr_ratio: data.sum_rr / data.count,
|
|
101
|
+
count: data.count
|
|
47
102
|
};
|
|
48
103
|
}
|
|
49
104
|
}
|
|
50
|
-
|
|
51
105
|
return result;
|
|
52
106
|
}
|
|
53
107
|
|
|
54
108
|
reset() {
|
|
55
|
-
this.
|
|
109
|
+
this.assets.clear();
|
|
56
110
|
this.mappings = null;
|
|
57
111
|
}
|
|
58
112
|
}
|
|
59
113
|
|
|
60
|
-
module.exports = RiskRewardRatioPerAsset;
|
|
114
|
+
module.exports = RiskRewardRatioPerAsset;
|
|
@@ -1,48 +1,110 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what are the average
|
|
5
|
+
* P&L, leverage, SL rate, and TP rate for *long* and *short*
|
|
6
|
+
* speculator positions?"
|
|
4
7
|
*/
|
|
5
8
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
9
|
|
|
7
10
|
class SpeculatorAssetSentiment {
|
|
8
11
|
constructor() {
|
|
9
|
-
|
|
12
|
+
// { [instrumentId]: { long: {...}, short: {...} } }
|
|
13
|
+
this.assets = new Map();
|
|
10
14
|
this.mappings = null;
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const sentimentDetailSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"avg_pnl_pct": { "type": "number" },
|
|
26
|
+
"avg_leverage": { "type": "number" },
|
|
27
|
+
"sl_rate_pct": { "type": "number" },
|
|
28
|
+
"tp_rate_pct": { "type": "number" },
|
|
29
|
+
"count": { "type": "number" }
|
|
30
|
+
},
|
|
31
|
+
"required": ["avg_pnl_pct", "avg_leverage", "sl_rate_pct", "tp_rate_pct", "count"]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const tickerSchema = {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"long": sentimentDetailSchema,
|
|
38
|
+
"short": sentimentDetailSchema
|
|
39
|
+
},
|
|
40
|
+
"required": ["long", "short"]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Aggregates P&L, leverage, and SL/TP usage for speculators, split by long/short positions per asset.",
|
|
46
|
+
"patternProperties": {
|
|
47
|
+
"^.*$": tickerSchema // Ticker
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": tickerSchema
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
13
53
|
_initAsset(instrumentId) {
|
|
14
|
-
if (!this.assets
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
54
|
+
if (!this.assets.has(instrumentId)) {
|
|
55
|
+
const createSide = () => ({
|
|
56
|
+
pnl_sum: 0,
|
|
57
|
+
leverage_sum: 0,
|
|
58
|
+
sl_set_count: 0,
|
|
59
|
+
tp_set_count: 0,
|
|
60
|
+
count: 0
|
|
61
|
+
});
|
|
62
|
+
this.assets.set(instrumentId, {
|
|
63
|
+
long: createSide(),
|
|
64
|
+
short: createSide()
|
|
65
|
+
});
|
|
19
66
|
}
|
|
20
67
|
}
|
|
21
68
|
|
|
22
|
-
process(portfolioData
|
|
69
|
+
process(portfolioData) {
|
|
70
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
23
74
|
const positions = portfolioData.PublicPositions;
|
|
24
|
-
if (!positions || !Array.isArray(positions))
|
|
75
|
+
if (!positions || !Array.isArray(positions)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
25
78
|
|
|
26
|
-
for (const
|
|
27
|
-
const instrumentId =
|
|
79
|
+
for (const pos of positions) {
|
|
80
|
+
const instrumentId = pos.InstrumentID;
|
|
28
81
|
if (!instrumentId) continue;
|
|
29
|
-
|
|
82
|
+
|
|
30
83
|
this._initAsset(instrumentId);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
const sideData = pos.IsBuy ? this.assets.get(instrumentId).long : this.assets.get(instrumentId).short;
|
|
85
|
+
|
|
86
|
+
const pnl_percent = (pos.NetProfit || 0) / (pos.Amount || 1);
|
|
87
|
+
|
|
88
|
+
sideData.pnl_sum += pnl_percent;
|
|
89
|
+
sideData.leverage_sum += pos.Leverage || 1;
|
|
90
|
+
if (pos.StopLossRate) sideData.sl_set_count++;
|
|
91
|
+
if (pos.TakeProfitRate) sideData.tp_set_count++;
|
|
92
|
+
sideData.count++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_calculateAverages(data) {
|
|
97
|
+
const count = data.count;
|
|
98
|
+
if (count === 0) {
|
|
99
|
+
return { avg_pnl_pct: 0, avg_leverage: 0, sl_rate_pct: 0, tp_rate_pct: 0, count: 0 };
|
|
45
100
|
}
|
|
101
|
+
return {
|
|
102
|
+
avg_pnl_pct: data.pnl_sum / count,
|
|
103
|
+
avg_leverage: data.leverage_sum / count,
|
|
104
|
+
sl_rate_pct: (data.sl_set_count / count) * 100,
|
|
105
|
+
tp_rate_pct: (data.tp_set_count / count) * 100,
|
|
106
|
+
count: count
|
|
107
|
+
};
|
|
46
108
|
}
|
|
47
109
|
|
|
48
110
|
async getResult() {
|
|
@@ -50,32 +112,22 @@ class SpeculatorAssetSentiment {
|
|
|
50
112
|
this.mappings = await loadInstrumentMappings();
|
|
51
113
|
}
|
|
52
114
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// REFACTOR: Perform final calculations.
|
|
62
|
-
finalResult[ticker][direction] = {
|
|
63
|
-
count: stats.count,
|
|
64
|
-
average_pnl: stats.count > 0 ? stats.pnl_sum / stats.count : 0,
|
|
65
|
-
average_leverage: stats.count > 0 ? stats.leverage_sum / stats.count : 0,
|
|
66
|
-
average_sl_rate: stats.count > 0 ? stats.sl_rate_sum / stats.count : 0,
|
|
67
|
-
average_tp_rate: stats.count > 0 ? stats.tp_rate_sum / stats.count : 0,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
115
|
+
const result = {};
|
|
116
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
117
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
118
|
+
|
|
119
|
+
result[ticker] = {
|
|
120
|
+
long: this._calculateAverages(data.long),
|
|
121
|
+
short: this._calculateAverages(data.short)
|
|
122
|
+
};
|
|
70
123
|
}
|
|
71
|
-
|
|
72
|
-
return finalResult;
|
|
124
|
+
return result;
|
|
73
125
|
}
|
|
74
126
|
|
|
75
127
|
reset() {
|
|
76
|
-
this.assets
|
|
128
|
+
this.assets.clear();
|
|
77
129
|
this.mappings = null;
|
|
78
130
|
}
|
|
79
131
|
}
|
|
80
132
|
|
|
81
|
-
module.exports = SpeculatorAssetSentiment;
|
|
133
|
+
module.exports = SpeculatorAssetSentiment;
|
|
@@ -1,57 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, how many long and
|
|
5
|
+
* short speculator positions are within 5% of their stop loss?"
|
|
4
6
|
*/
|
|
7
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
8
|
+
|
|
5
9
|
class SpeculatorDangerZone {
|
|
6
10
|
constructor() {
|
|
7
|
-
|
|
11
|
+
// { [instrumentId]: { long_in_danger: 0, short_in_danger: 0, long_total: 0, short_total: 0 } }
|
|
12
|
+
this.assets = new Map();
|
|
13
|
+
this.mappings = null;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
"long_in_danger": { "type": "number" },
|
|
25
|
+
"short_in_danger": { "type": "number" },
|
|
26
|
+
"long_total": { "type": "number" },
|
|
27
|
+
"short_total": { "type": "number" },
|
|
28
|
+
"long_danger_pct": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "Percentage of long positions in the danger zone."
|
|
31
|
+
},
|
|
32
|
+
"short_danger_pct": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Percentage of short positions in the danger zone."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["long_in_danger", "short_in_danger", "long_total", "short_total", "long_danger_pct", "short_danger_pct"]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Tracks speculator positions within 5% of their Stop Loss.",
|
|
43
|
+
"patternProperties": {
|
|
44
|
+
"^.*$": tickerSchema // Ticker
|
|
45
|
+
},
|
|
46
|
+
"additionalProperties": tickerSchema
|
|
47
|
+
};
|
|
14
48
|
}
|
|
15
49
|
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
_initAsset(instrumentId) {
|
|
51
|
+
if (!this.assets.has(instrumentId)) {
|
|
52
|
+
this.assets.set(instrumentId, {
|
|
53
|
+
long_in_danger: 0,
|
|
54
|
+
short_in_danger: 0,
|
|
55
|
+
long_total: 0,
|
|
56
|
+
short_total: 0
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
18
60
|
|
|
19
|
-
|
|
61
|
+
process(portfolioData) {
|
|
62
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
20
66
|
const positions = portfolioData.PublicPositions;
|
|
21
|
-
if (!positions || !Array.isArray(positions))
|
|
67
|
+
if (!positions || !Array.isArray(positions)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
22
70
|
|
|
23
|
-
for (const
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
for (const pos of positions) {
|
|
72
|
+
const instrumentId = pos.InstrumentID;
|
|
73
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
74
|
+
const current_price = pos.LastCloseRate || 0;
|
|
75
|
+
|
|
76
|
+
if (!instrumentId || sl_rate === 0 || current_price === 0) {
|
|
26
77
|
continue;
|
|
27
78
|
}
|
|
79
|
+
|
|
80
|
+
this._initAsset(instrumentId);
|
|
81
|
+
const assetData = this.assets.get(instrumentId);
|
|
82
|
+
|
|
83
|
+
const distance = Math.abs(current_price - sl_rate);
|
|
84
|
+
const distance_pct = (distance / current_price);
|
|
85
|
+
|
|
86
|
+
const isInDanger = distance_pct <= 0.05; // 5% danger zone
|
|
28
87
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this._initAsset(ticker);
|
|
34
|
-
|
|
35
|
-
let distance_percent = 0;
|
|
36
|
-
// FIX: Use the correct PascalCase IsBuy
|
|
37
|
-
if (position.IsBuy) {
|
|
38
|
-
// Long position: Danger if SL is just below current rate
|
|
39
|
-
distance_percent = (position.CurrentRate - position.StopLossRate) / position.CurrentRate;
|
|
40
|
-
if (distance_percent > 0 && distance_percent < 0.05) {
|
|
41
|
-
this.danger_zone[ticker].long_danger_count++;
|
|
88
|
+
if (pos.IsBuy) {
|
|
89
|
+
assetData.long_total++;
|
|
90
|
+
if (isInDanger) {
|
|
91
|
+
assetData.long_in_danger++;
|
|
42
92
|
}
|
|
43
93
|
} else {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.danger_zone[ticker].short_danger_count++;
|
|
94
|
+
assetData.short_total++;
|
|
95
|
+
if (isInDanger) {
|
|
96
|
+
assetData.short_in_danger++;
|
|
48
97
|
}
|
|
49
98
|
}
|
|
50
99
|
}
|
|
51
100
|
}
|
|
52
101
|
|
|
53
|
-
getResult() {
|
|
54
|
-
|
|
102
|
+
async getResult() {
|
|
103
|
+
if (!this.mappings) {
|
|
104
|
+
this.mappings = await loadInstrumentMappings();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
109
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
110
|
+
|
|
111
|
+
result[ticker] = {
|
|
112
|
+
...data,
|
|
113
|
+
long_danger_pct: (data.long_total > 0) ? (data.long_in_danger / data.long_total) * 100 : 0,
|
|
114
|
+
short_danger_pct: (data.short_total > 0) ? (data.short_in_danger / data.short_total) * 100 : 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
reset() {
|
|
121
|
+
this.assets.clear();
|
|
122
|
+
this.mappings = null;
|
|
55
123
|
}
|
|
56
124
|
}
|
|
57
125
|
|
|
@@ -1,91 +1,118 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator metric.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each sector, what is the
|
|
5
|
+
* average stop loss distance (in % and value) for
|
|
6
|
+
* long and short positions?"
|
|
4
7
|
*/
|
|
5
|
-
const {
|
|
8
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
9
|
|
|
7
|
-
class
|
|
10
|
+
class StopLossDistanceBySectorShortLongBreakdown {
|
|
8
11
|
constructor() {
|
|
9
|
-
|
|
10
|
-
this.
|
|
12
|
+
// { [sector]: { long_pct: [], long_val: [], short_pct: [], short_val: [] } }
|
|
13
|
+
this.sectors = new Map();
|
|
14
|
+
this.mappings = null;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const sectorSchema = {
|
|
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
|
+
};
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
return {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Calculates avg SL distance (% and value) for long/short positions, grouped by sector.",
|
|
38
|
+
"patternProperties": {
|
|
39
|
+
"^.*$": sectorSchema // Sector
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": sectorSchema
|
|
42
|
+
};
|
|
43
|
+
}
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
distance_percent_sum: 0,
|
|
29
|
-
distance_value_sum: 0,
|
|
30
|
-
count: 0
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
const agg = this.instrumentData[InstrumentID][posType];
|
|
34
|
-
agg.distance_percent_sum += distance_percent;
|
|
35
|
-
agg.distance_value_sum += distance_value;
|
|
36
|
-
agg.count++;
|
|
37
|
-
}
|
|
45
|
+
_initSector(sector) {
|
|
46
|
+
if (!this.sectors.has(sector)) {
|
|
47
|
+
this.sectors.set(sector, {
|
|
48
|
+
long_pct: [], long_val: [], short_pct: [], short_val: []
|
|
49
|
+
});
|
|
38
50
|
}
|
|
39
51
|
}
|
|
52
|
+
|
|
53
|
+
_avg(arr) {
|
|
54
|
+
if (arr.length === 0) return 0;
|
|
55
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
56
|
+
}
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
58
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
59
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!this.mappings) {
|
|
63
|
+
this.mappings = context.mappings;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const positions = portfolioData.PublicPositions;
|
|
67
|
+
if (!positions || !Array.isArray(positions) || !this.mappings) {
|
|
68
|
+
return;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
for (const pos of positions) {
|
|
72
|
+
const instrumentId = pos.InstrumentID;
|
|
73
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
74
|
+
const open_rate = pos.OpenRate || 0;
|
|
75
|
+
|
|
76
|
+
if (!instrumentId || sl_rate === 0 || open_rate === 0) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
81
|
+
this._initSector(sector);
|
|
82
|
+
const sectorData = this.sectors.get(sector);
|
|
83
|
+
|
|
84
|
+
const distance_val = Math.abs(open_rate - sl_rate);
|
|
85
|
+
const distance_pct = (distance_val / open_rate) * 100;
|
|
86
|
+
|
|
87
|
+
if (pos.IsBuy) {
|
|
88
|
+
sectorData.long_pct.push(distance_pct);
|
|
89
|
+
sectorData.long_val.push(distance_val);
|
|
90
|
+
} else {
|
|
91
|
+
sectorData.short_pct.push(distance_pct);
|
|
92
|
+
sectorData.short_val.push(distance_val);
|
|
65
93
|
}
|
|
66
94
|
}
|
|
95
|
+
}
|
|
67
96
|
|
|
97
|
+
async getResult() {
|
|
68
98
|
const result = {};
|
|
69
|
-
for (const sector
|
|
70
|
-
result[sector] = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
count: data.count
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
99
|
+
for (const [sector, data] of this.sectors.entries()) {
|
|
100
|
+
result[sector] = {
|
|
101
|
+
long_avg_dist_pct: this._avg(data.long_pct),
|
|
102
|
+
long_avg_dist_val: this._avg(data.long_val),
|
|
103
|
+
long_count: data.long_pct.length,
|
|
104
|
+
short_avg_dist_pct: this._avg(data.short_pct),
|
|
105
|
+
short_avg_dist_val: this._avg(data.short_val),
|
|
106
|
+
short_count: data.short_pct.length
|
|
107
|
+
};
|
|
82
108
|
}
|
|
83
109
|
return result;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
reset() {
|
|
87
|
-
this.
|
|
113
|
+
this.sectors.clear();
|
|
114
|
+
this.mappings = null;
|
|
88
115
|
}
|
|
89
116
|
}
|
|
90
117
|
|
|
91
|
-
module.exports =
|
|
118
|
+
module.exports = StopLossDistanceBySectorShortLongBreakdown;
|