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,43 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total long positions per sector.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the total number of long ('buy')
|
|
5
|
+
* positions for each sector?"
|
|
3
6
|
*/
|
|
4
|
-
const {
|
|
7
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class TotalLongPerSector {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// We will store { [sector]: count }
|
|
12
|
+
this.sectors = new Map();
|
|
13
|
+
this.mappings = null;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Calculates the total count of long ('buy') positions per sector.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Sector name
|
|
26
|
+
"^.*$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "The total count of long positions for this sector."
|
|
16
29
|
}
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "number"
|
|
17
33
|
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_initSector(sector) {
|
|
38
|
+
if (!this.sectors.has(sector)) {
|
|
39
|
+
this.sectors.set(sector, 0);
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
44
|
+
// This calculation needs the sector mappings from Pass 1 context
|
|
45
|
+
if (!this.mappings) {
|
|
46
|
+
this.mappings = context.mappings;
|
|
47
|
+
}
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
longCountBySector[sector] = (longCountBySector[sector] || 0) + 1;
|
|
49
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
50
|
+
if (!positions || !Array.isArray(positions) || !this.mappings) {
|
|
51
|
+
return;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
for (const pos of positions) {
|
|
55
|
+
// Only count 'buy' (long) positions
|
|
56
|
+
if (pos.IsBuy) {
|
|
57
|
+
const instrumentId = pos.InstrumentID;
|
|
58
|
+
if (!instrumentId) continue;
|
|
59
|
+
|
|
60
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
61
|
+
this._initSector(sector);
|
|
62
|
+
|
|
63
|
+
this.sectors.set(sector, this.sectors.get(sector) + 1);
|
|
64
|
+
}
|
|
34
65
|
}
|
|
66
|
+
}
|
|
35
67
|
|
|
36
|
-
|
|
68
|
+
getResult() {
|
|
69
|
+
// Convert Map to plain object
|
|
70
|
+
return Object.fromEntries(this.sectors);
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
reset() {
|
|
40
|
-
this.
|
|
74
|
+
this.sectors.clear();
|
|
75
|
+
this.mappings = null;
|
|
41
76
|
}
|
|
42
77
|
}
|
|
43
78
|
|
|
@@ -1,43 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total short positions per sector.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the total number of short ('sell')
|
|
5
|
+
* positions for each sector?"
|
|
3
6
|
*/
|
|
4
|
-
const {
|
|
7
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class TotalShortPerSector {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// We will store { [sector]: count }
|
|
12
|
+
this.sectors = new Map();
|
|
13
|
+
this.mappings = null;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Calculates the total count of short ('sell') positions per sector.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Sector name
|
|
26
|
+
"^.*$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "The total count of short positions for this sector."
|
|
16
29
|
}
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "number"
|
|
17
33
|
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_initSector(sector) {
|
|
38
|
+
if (!this.sectors.has(sector)) {
|
|
39
|
+
this.sectors.set(sector, 0);
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
44
|
+
// This calculation needs the sector mappings from Pass 1 context
|
|
45
|
+
if (!this.mappings) {
|
|
46
|
+
this.mappings = context.mappings;
|
|
47
|
+
}
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
shortCountBySector[sector] = (shortCountBySector[sector] || 0) + 1;
|
|
49
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
50
|
+
if (!positions || !Array.isArray(positions) || !this.mappings) {
|
|
51
|
+
return;
|
|
29
52
|
}
|
|
30
53
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
for (const pos of positions) {
|
|
55
|
+
// Only count 'sell' (short) positions
|
|
56
|
+
if (!pos.IsBuy) {
|
|
57
|
+
const instrumentId = pos.InstrumentID;
|
|
58
|
+
if (!instrumentId) continue;
|
|
59
|
+
|
|
60
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
61
|
+
this._initSector(sector);
|
|
62
|
+
|
|
63
|
+
this.sectors.set(sector, this.sectors.get(sector) + 1);
|
|
64
|
+
}
|
|
34
65
|
}
|
|
66
|
+
}
|
|
35
67
|
|
|
36
|
-
|
|
68
|
+
getResult() {
|
|
69
|
+
// Convert Map to plain object
|
|
70
|
+
return Object.fromEntries(this.sectors);
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
reset() {
|
|
40
|
-
this.
|
|
74
|
+
this.sectors.clear();
|
|
75
|
+
this.mappings = null;
|
|
41
76
|
}
|
|
42
77
|
}
|
|
43
78
|
|
|
@@ -1,80 +1,260 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for crowd conviction.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the 'Crowd Conviction Score'
|
|
5
|
+
* for each instrument?"
|
|
6
|
+
*
|
|
7
|
+
* It's based on factors like:
|
|
8
|
+
* 1. Holding Duration (longer = more conviction)
|
|
9
|
+
* 2. P&L % (positive = more conviction)
|
|
10
|
+
* 3. Risk/Reward Ratio (higher = more conviction)
|
|
11
|
+
* 4. Leverage (lower = more conviction)
|
|
12
|
+
*
|
|
13
|
+
* This is a *stateful* calculation that computes a 30-day
|
|
14
|
+
* rolling average of these metrics to build the score.
|
|
3
15
|
*/
|
|
4
|
-
// CORRECTED PATH: ../utils/ instead of ../../utils/
|
|
5
16
|
const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
|
|
6
17
|
|
|
7
|
-
|
|
8
18
|
class CrowdConvictionScore {
|
|
9
|
-
// ... (rest of the code is unchanged) ...
|
|
10
19
|
constructor() {
|
|
11
|
-
|
|
20
|
+
// { [instrumentId]: { metrics: [], history: [] } }
|
|
21
|
+
this.assetData = new Map();
|
|
12
22
|
this.mappings = null;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Defines the output schema for this calculation.
|
|
27
|
+
* @returns {object} JSON Schema object
|
|
28
|
+
*/
|
|
29
|
+
static getSchema() {
|
|
30
|
+
const historyItemSchema = {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"avg_holding_duration": { "type": "number" },
|
|
34
|
+
"avg_pnl": { "type": "number" },
|
|
35
|
+
"avg_rr": { "type": "number" },
|
|
36
|
+
"avg_leverage": { "type": "number" }
|
|
37
|
+
},
|
|
38
|
+
"required": ["avg_holding_duration", "avg_pnl", "avg_rr", "avg_leverage"]
|
|
39
|
+
};
|
|
17
40
|
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
const tickerSchema = {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Crowd conviction score and its components for a specific asset.",
|
|
44
|
+
"properties": {
|
|
45
|
+
"conviction_score": {
|
|
46
|
+
"type": "number",
|
|
47
|
+
"description": "Final conviction score (0-100), based on 30-day rolling averages."
|
|
48
|
+
},
|
|
49
|
+
"roll_avg_holding_duration": {
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "30-day rolling average of holding duration (hours)."
|
|
52
|
+
},
|
|
53
|
+
"roll_avg_pnl": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"description": "30-day rolling average of P&L percentage."
|
|
56
|
+
},
|
|
57
|
+
"roll_avg_rr": {
|
|
58
|
+
"type": "number",
|
|
59
|
+
"description": "30-day rolling average of risk/reward ratio."
|
|
60
|
+
},
|
|
61
|
+
"roll_avg_leverage": {
|
|
62
|
+
"type": "number",
|
|
63
|
+
"description": "30-day rolling average of leverage."
|
|
64
|
+
},
|
|
65
|
+
"avg_holding_duration": {
|
|
66
|
+
"type": "number",
|
|
67
|
+
"description": "Today's average holding duration."
|
|
68
|
+
},
|
|
69
|
+
"avg_pnl": {
|
|
70
|
+
"type": "number",
|
|
71
|
+
"description": "Today's average P&L percentage."
|
|
72
|
+
},
|
|
73
|
+
"avg_rr": {
|
|
74
|
+
"type": "number",
|
|
75
|
+
"description": "Today's average risk/reward ratio."
|
|
76
|
+
},
|
|
77
|
+
"avg_leverage": {
|
|
78
|
+
"type": "number",
|
|
79
|
+
"description": "Today's average leverage."
|
|
80
|
+
},
|
|
81
|
+
"history": {
|
|
82
|
+
"type": "array",
|
|
83
|
+
"description": "30-day history of daily average metrics.",
|
|
84
|
+
"items": historyItemSchema
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"required": [
|
|
88
|
+
"conviction_score", "roll_avg_holding_duration", "roll_avg_pnl",
|
|
89
|
+
"roll_avg_rr", "roll_avg_leverage", "avg_holding_duration",
|
|
90
|
+
"avg_pnl", "avg_rr", "avg_leverage", "history"
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"description": "Calculates a 30-day rolling 'Crowd Conviction Score' (0-100) for each asset.",
|
|
97
|
+
"patternProperties": {
|
|
98
|
+
"^.*$": tickerSchema // Ticker
|
|
99
|
+
},
|
|
100
|
+
"additionalProperties": tickerSchema
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_initAsset(instrumentId) {
|
|
105
|
+
if (!this.assetData.has(instrumentId)) {
|
|
106
|
+
// metrics: Stores *today's* raw values before averaging
|
|
107
|
+
// history: Stores *yesterday's* 30-day history
|
|
108
|
+
this.assetData.set(instrumentId, {
|
|
109
|
+
metrics: [],
|
|
110
|
+
history: []
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_getHoldingDurationHours(openDateStr) {
|
|
116
|
+
if (!openDateStr) return 0;
|
|
117
|
+
try {
|
|
118
|
+
const openDate = new Date(openDateStr);
|
|
119
|
+
const diffMs = new Date().getTime() - openDate.getTime();
|
|
120
|
+
return diffMs / (1000 * 60 * 60);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Simple min-max normalization helper
|
|
127
|
+
_normalize(value, min, max) {
|
|
128
|
+
if (max === min) return 0.5; // Avoid division by zero
|
|
129
|
+
const normalized = (value - min) / (max - min);
|
|
130
|
+
return Math.max(0, Math.min(1, normalized)); // Clamp between 0 and 1
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
134
|
+
// This score is based on speculator actions
|
|
135
|
+
if (portfolioData?.context?.userType !== 'speculator') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 1. Get this metric's history from yesterday (pre-loaded)
|
|
140
|
+
if (this.assetData.size === 0) { // Only run once
|
|
141
|
+
const yHistoryData = context.yesterdaysDependencyData['crowd_conviction_score'];
|
|
142
|
+
if (yHistoryData) {
|
|
143
|
+
if (!this.mappings) {
|
|
144
|
+
// We need mappings to convert *yesterday's* tickers back to IDs
|
|
145
|
+
this.mappings = context.mappings;
|
|
146
|
+
}
|
|
147
|
+
for (const [ticker, data] of Object.entries(yHistoryData)) {
|
|
148
|
+
const instrumentId = this.mappings.tickerToInstrument[ticker];
|
|
149
|
+
if (instrumentId) {
|
|
150
|
+
this._initAsset(instrumentId);
|
|
151
|
+
this.assetData.get(instrumentId).history = data.history || [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
20
155
|
}
|
|
21
156
|
|
|
22
|
-
const positions =
|
|
23
|
-
if (!positions)
|
|
157
|
+
const positions = portfolioData.PublicPositions;
|
|
158
|
+
if (!positions || !Array.isArray(positions)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
24
161
|
|
|
25
162
|
for (const pos of positions) {
|
|
26
163
|
const instrumentId = pos.InstrumentID;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
164
|
+
if (!instrumentId) continue;
|
|
165
|
+
|
|
166
|
+
this._initAsset(instrumentId);
|
|
167
|
+
|
|
168
|
+
const pnl_percent = (pos.NetProfit || 0) / (pos.Amount || 1);
|
|
169
|
+
const holding_duration = this._getHoldingDurationHours(pos.OpenDateTime);
|
|
170
|
+
const leverage = pos.Leverage || 1;
|
|
171
|
+
|
|
172
|
+
const sl_rate = pos.StopLossRate || 0;
|
|
173
|
+
const tp_rate = pos.TakeProfitRate || 0;
|
|
174
|
+
const open_rate = pos.OpenRate || 1;
|
|
175
|
+
|
|
176
|
+
let rr_ratio = 0;
|
|
177
|
+
if (sl_rate > 0 && tp_rate > 0) {
|
|
178
|
+
const risk = Math.abs(open_rate - sl_rate);
|
|
179
|
+
const reward = Math.abs(tp_rate - open_rate);
|
|
180
|
+
if (risk > 0) {
|
|
181
|
+
rr_ratio = reward / risk;
|
|
182
|
+
}
|
|
34
183
|
}
|
|
184
|
+
|
|
185
|
+
this.assetData.get(instrumentId).metrics.push({
|
|
186
|
+
holding_duration,
|
|
187
|
+
pnl_percent,
|
|
188
|
+
rr_ratio,
|
|
189
|
+
leverage
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
35
193
|
|
|
36
|
-
|
|
37
|
-
|
|
194
|
+
async getResult() {
|
|
195
|
+
if (!this.mappings) {
|
|
196
|
+
this.mappings = await loadInstrumentMappings();
|
|
197
|
+
}
|
|
38
198
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
199
|
+
const result = {};
|
|
200
|
+
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
201
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
202
|
+
const metrics = data.metrics;
|
|
203
|
+
const yHistory = data.history; // Yesterday's 30-day history
|
|
45
204
|
|
|
46
|
-
if (
|
|
47
|
-
const risk = Math.abs(pos.OpenRate - pos.StopLossRate);
|
|
48
|
-
const reward = Math.abs(pos.TakeProfitRate - pos.OpenRate);
|
|
49
|
-
if (risk > 0) {
|
|
50
|
-
const ratio = reward / risk;
|
|
51
|
-
score += ratio * 0.5;
|
|
52
|
-
weights += 0.5;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
205
|
+
if (metrics.length === 0) continue;
|
|
55
206
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
207
|
+
// 1. Calculate today's averages
|
|
208
|
+
const count = metrics.length;
|
|
209
|
+
const avg_holding_duration = metrics.reduce((s, m) => s + m.holding_duration, 0) / count;
|
|
210
|
+
const avg_pnl = metrics.reduce((s, m) => s + m.pnl_percent, 0) / count;
|
|
211
|
+
const avg_rr = metrics.reduce((s, m) => s + m.rr_ratio, 0) / count;
|
|
212
|
+
const avg_leverage = metrics.reduce((s, m) => s + m.leverage, 0) / count;
|
|
213
|
+
|
|
214
|
+
const todayMetrics = { avg_holding_duration, avg_pnl, avg_rr, avg_leverage };
|
|
60
215
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
216
|
+
// 2. Create new 30-day history
|
|
217
|
+
const newHistory = [todayMetrics, ...yHistory].slice(0, 30);
|
|
218
|
+
|
|
219
|
+
// 3. Calculate 30-day rolling averages
|
|
220
|
+
const historyCount = newHistory.length;
|
|
221
|
+
const roll_avg_duration = newHistory.reduce((s, m) => s + m.avg_holding_duration, 0) / historyCount;
|
|
222
|
+
const roll_avg_pnl = newHistory.reduce((s, m) => s + m.avg_pnl, 0) / historyCount;
|
|
223
|
+
const roll_avg_rr = newHistory.reduce((s, m) => s + m.avg_rr, 0) / historyCount;
|
|
224
|
+
const roll_avg_leverage = newHistory.reduce((s, m) => s + m.avg_leverage, 0) / historyCount;
|
|
225
|
+
|
|
226
|
+
// 4. Calculate Conviction Score (example normalization)
|
|
227
|
+
// (Assumes a 0-1 range for normalization, then scales to 0-100)
|
|
228
|
+
const norm_duration = this._normalize(roll_avg_duration, 0, 240); // 0-10 days
|
|
229
|
+
const norm_pnl = this._normalize(roll_avg_pnl, -0.5, 0.5); // -50% to +50%
|
|
230
|
+
const norm_rr = this._normalize(roll_avg_rr, 0, 3); // 0 to 3 R:R
|
|
231
|
+
const norm_leverage = 1 - this._normalize(roll_avg_leverage, 1, 10); // 1x to 10x (inverted)
|
|
232
|
+
|
|
233
|
+
// Combine scores (equal weight for this example)
|
|
234
|
+
const score_pct = (norm_duration + norm_pnl + norm_rr + norm_leverage) / 4;
|
|
235
|
+
|
|
236
|
+
result[ticker] = {
|
|
237
|
+
conviction_score: score_pct * 100, // Final score 0-100
|
|
238
|
+
roll_avg_holding_duration,
|
|
239
|
+
roll_avg_pnl,
|
|
240
|
+
roll_avg_rr,
|
|
241
|
+
roll_avg_leverage,
|
|
242
|
+
|
|
243
|
+
// Also include today's raw averages
|
|
244
|
+
avg_holding_duration,
|
|
245
|
+
avg_pnl,
|
|
246
|
+
avg_rr,
|
|
247
|
+
avg_leverage,
|
|
248
|
+
|
|
249
|
+
history: newHistory
|
|
250
|
+
};
|
|
66
251
|
}
|
|
252
|
+
return result;
|
|
67
253
|
}
|
|
68
254
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const data = this.convictionData[ticker];
|
|
73
|
-
if (data.count > 0) {
|
|
74
|
-
result[ticker] = data.totalScore / data.count;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { crowd_conviction_score: result };
|
|
255
|
+
reset() {
|
|
256
|
+
this.assetData.clear();
|
|
257
|
+
this.mappings = null;
|
|
78
258
|
}
|
|
79
259
|
}
|
|
80
260
|
|
|
@@ -1,24 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for long positions per stock.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "How many long ('buy') positions
|
|
5
|
+
* are there for each stock?"
|
|
4
6
|
*/
|
|
5
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
8
|
|
|
7
9
|
class LongPositionPerStock {
|
|
8
10
|
constructor() {
|
|
9
|
-
|
|
11
|
+
// We will store { [instrumentId]: count }
|
|
12
|
+
this.assets = new Map();
|
|
10
13
|
this.mappings = null;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Calculates the total count of long ('buy') positions for each asset.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Ticker
|
|
26
|
+
"^.*$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "The total count of long positions for this asset."
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "number"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_initAsset(instrumentId) {
|
|
38
|
+
if (!this.assets.has(instrumentId)) {
|
|
39
|
+
this.assets.set(instrumentId, 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process(portfolioData) {
|
|
44
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
45
|
+
if (!positions || !Array.isArray(positions)) {
|
|
15
46
|
return;
|
|
16
47
|
}
|
|
17
48
|
|
|
18
|
-
for (const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
for (const pos of positions) {
|
|
50
|
+
// Only count 'buy' (long) positions
|
|
51
|
+
if (pos.IsBuy) {
|
|
52
|
+
const instrumentId = pos.InstrumentID;
|
|
53
|
+
if (!instrumentId) continue;
|
|
54
|
+
|
|
55
|
+
this._initAsset(instrumentId);
|
|
56
|
+
this.assets.set(instrumentId, this.assets.get(instrumentId) + 1);
|
|
22
57
|
}
|
|
23
58
|
}
|
|
24
59
|
}
|
|
@@ -27,16 +62,17 @@ class LongPositionPerStock {
|
|
|
27
62
|
if (!this.mappings) {
|
|
28
63
|
this.mappings = await loadInstrumentMappings();
|
|
29
64
|
}
|
|
65
|
+
|
|
30
66
|
const result = {};
|
|
31
|
-
for (const instrumentId
|
|
32
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
33
|
-
result[ticker] =
|
|
67
|
+
for (const [instrumentId, count] of this.assets.entries()) {
|
|
68
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
69
|
+
result[ticker] = count;
|
|
34
70
|
}
|
|
35
|
-
return
|
|
71
|
+
return result;
|
|
36
72
|
}
|
|
37
73
|
|
|
38
74
|
reset() {
|
|
39
|
-
this.
|
|
75
|
+
this.assets.clear();
|
|
40
76
|
this.mappings = null;
|
|
41
77
|
}
|
|
42
78
|
}
|