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,46 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for asset P&L status.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, how many users in our
|
|
5
|
+
* sample are in profit versus in loss?"
|
|
6
|
+
*
|
|
7
|
+
* This provides a crowd-wide P&L status for each instrument.
|
|
4
8
|
*/
|
|
9
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
10
|
+
|
|
5
11
|
class AssetPnlStatus {
|
|
6
12
|
constructor() {
|
|
7
|
-
|
|
13
|
+
// We will store { [instrumentId]: { in_profit: Set(), in_loss: Set() } }
|
|
14
|
+
this.assets = new Map();
|
|
15
|
+
this.mappings = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Defines the output schema for this calculation.
|
|
20
|
+
* @returns {object} JSON Schema object
|
|
21
|
+
*/
|
|
22
|
+
static getSchema() {
|
|
23
|
+
const userSchema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"userId": { "type": "string" },
|
|
27
|
+
"pnl": { "type": "number" }
|
|
28
|
+
},
|
|
29
|
+
"required": ["userId", "pnl"]
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const tickerSchema = {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "P&L status for a specific asset.",
|
|
35
|
+
"properties": {
|
|
36
|
+
"in_profit_count": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"description": "Count of users currently in profit on this asset."
|
|
39
|
+
},
|
|
40
|
+
"in_loss_count": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"description": "Count of users currently in loss on this asset."
|
|
43
|
+
},
|
|
44
|
+
"profit_ratio": {
|
|
45
|
+
"type": "number",
|
|
46
|
+
"description": "Percentage of users in profit (In Profit / Total)."
|
|
47
|
+
},
|
|
48
|
+
"users_in_profit": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"description": "List of users in profit.",
|
|
51
|
+
"items": userSchema
|
|
52
|
+
},
|
|
53
|
+
"users_in_loss": {
|
|
54
|
+
"type": "array",
|
|
55
|
+
"description": "List of users in loss.",
|
|
56
|
+
"items": userSchema
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": ["in_profit_count", "in_loss_count", "profit_ratio", "users_in_profit", "users_in_loss"]
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"description": "Tracks the number of users in profit vs. in loss for each asset.",
|
|
65
|
+
"patternProperties": {
|
|
66
|
+
"^.*$": tickerSchema // Ticker
|
|
67
|
+
},
|
|
68
|
+
"additionalProperties": tickerSchema
|
|
69
|
+
};
|
|
8
70
|
}
|
|
9
71
|
|
|
10
|
-
_initAsset(
|
|
11
|
-
if (!this.assets
|
|
12
|
-
this.assets
|
|
72
|
+
_initAsset(instrumentId) {
|
|
73
|
+
if (!this.assets.has(instrumentId)) {
|
|
74
|
+
this.assets.set(instrumentId, {
|
|
75
|
+
in_profit: new Map(), // Map<userId, pnl>
|
|
76
|
+
in_loss: new Map() // Map<userId, pnl>
|
|
77
|
+
});
|
|
13
78
|
}
|
|
14
79
|
}
|
|
15
|
-
|
|
16
|
-
process(portfolioData, yesterdayPortfolio, userId
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
80
|
+
|
|
81
|
+
process(portfolioData, yesterdayPortfolio, userId) {
|
|
82
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
83
|
+
if (!positions || !Array.isArray(positions)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const pos of positions) {
|
|
88
|
+
const instrumentId = pos.InstrumentID;
|
|
89
|
+
if (!instrumentId) continue;
|
|
90
|
+
|
|
91
|
+
this._initAsset(instrumentId);
|
|
92
|
+
const asset = this.assets.get(instrumentId);
|
|
93
|
+
const pnl = pos.NetProfit || 0;
|
|
94
|
+
|
|
95
|
+
if (pnl > 0) {
|
|
96
|
+
asset.in_profit.set(userId, pnl);
|
|
97
|
+
} else if (pnl < 0) {
|
|
98
|
+
asset.in_loss.set(userId, pnl);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getResult() {
|
|
104
|
+
if (!this.mappings) {
|
|
105
|
+
this.mappings = await loadInstrumentMappings();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const result = {};
|
|
109
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
110
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
111
|
+
|
|
112
|
+
const profitCount = data.in_profit.size;
|
|
113
|
+
const lossCount = data.in_loss.size;
|
|
114
|
+
const total = profitCount + lossCount;
|
|
115
|
+
|
|
116
|
+
if (total > 0) {
|
|
117
|
+
result[ticker] = {
|
|
118
|
+
in_profit_count: profitCount,
|
|
119
|
+
in_loss_count: lossCount,
|
|
120
|
+
profit_ratio: (profitCount / total) * 100,
|
|
121
|
+
// Convert Maps to arrays of objects for the final result
|
|
122
|
+
users_in_profit: Array.from(data.in_profit, ([userId, pnl]) => ({ userId, pnl })),
|
|
123
|
+
users_in_loss: Array.from(data.in_loss, ([userId, pnl]) => ({ userId, pnl }))
|
|
124
|
+
};
|
|
36
125
|
}
|
|
37
|
-
processedTickers.add(ticker);
|
|
38
126
|
}
|
|
127
|
+
return result;
|
|
39
128
|
}
|
|
40
129
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
130
|
+
reset() {
|
|
131
|
+
this.assets.clear();
|
|
132
|
+
this.mappings = null;
|
|
44
133
|
}
|
|
45
134
|
}
|
|
46
135
|
|
|
@@ -1,48 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average daily P&L.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What was the average daily P&L
|
|
5
|
+
* for the average user?"
|
|
6
|
+
*
|
|
7
|
+
* This is a global health metric for the crowd.
|
|
4
8
|
*/
|
|
5
|
-
|
|
6
9
|
class AverageDailyPnlAllUsers {
|
|
7
10
|
constructor() {
|
|
8
|
-
this.
|
|
11
|
+
this.totalPnl = 0;
|
|
9
12
|
this.userCount = 0;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Defines the output schema for this calculation.
|
|
17
|
+
* @returns {object} JSON Schema object
|
|
18
|
+
*/
|
|
19
|
+
static getSchema() {
|
|
20
|
+
return {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"description": "Calculates the average daily P&L across all users.",
|
|
23
|
+
"properties": {
|
|
24
|
+
"average_daily_pnl": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"description": "The average daily P&L per user (Total P&L / User Count)."
|
|
27
|
+
},
|
|
28
|
+
"total_pnl": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "The sum of all daily P&L from all users."
|
|
31
|
+
},
|
|
32
|
+
"user_count": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "The total number of users processed."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["average_daily_pnl", "total_pnl", "user_count"]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
process(portfolioData) {
|
|
42
|
+
// Use the P&L from the summary, which is for the *day*
|
|
43
|
+
const dailyPnl = portfolioData.Summary?.NetProfit || 0;
|
|
44
|
+
|
|
45
|
+
this.totalPnl += dailyPnl;
|
|
26
46
|
this.userCount++;
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
getResult() {
|
|
30
|
-
if (this.userCount === 0) {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// REFACTOR: Perform the final calculation directly.
|
|
35
|
-
const average_daily_pnl = this.totalOfUserAveragePnls / this.userCount;
|
|
36
|
-
|
|
37
50
|
return {
|
|
38
|
-
average_daily_pnl:
|
|
51
|
+
average_daily_pnl: (this.userCount > 0) ? (this.totalPnl / this.userCount) : 0,
|
|
52
|
+
total_pnl: this.totalPnl,
|
|
53
|
+
user_count: this.userCount
|
|
39
54
|
};
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
reset() {
|
|
43
|
-
this.
|
|
58
|
+
this.totalPnl = 0;
|
|
44
59
|
this.userCount = 0;
|
|
45
60
|
}
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
module.exports = AverageDailyPnlAllUsers;
|
|
63
|
+
module.exports = AverageDailyPnlAllUsers;
|
|
@@ -1,49 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average daily P&L per sector.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What was the average daily P&L for each sector?"
|
|
5
|
+
*
|
|
6
|
+
* This helps identify which parts of the market the crowd
|
|
7
|
+
* is performing well or poorly in.
|
|
4
8
|
*/
|
|
5
|
-
const {
|
|
9
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
10
|
|
|
7
11
|
class AverageDailyPnlPerSector {
|
|
8
12
|
constructor() {
|
|
9
|
-
|
|
13
|
+
// We will store { [sector]: { pnl_sum: 0, user_count: 0 } }
|
|
14
|
+
this.sectors = new Map();
|
|
15
|
+
this.mappings = null;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Defines the output schema for this calculation.
|
|
20
|
+
* @returns {object} JSON Schema object
|
|
21
|
+
*/
|
|
22
|
+
static getSchema() {
|
|
23
|
+
const sectorSchema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"description": "P&L metrics for a specific sector.",
|
|
26
|
+
"properties": {
|
|
27
|
+
"average_pnl": {
|
|
28
|
+
"type": "number",
|
|
29
|
+
"description": "Average P&L for this sector (Sum / User Count)."
|
|
30
|
+
},
|
|
31
|
+
"pnl_sum": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"description": "Sum of all P&L for this sector."
|
|
34
|
+
},
|
|
35
|
+
"user_count": {
|
|
36
|
+
"type": "number",
|
|
37
|
+
"description": "Count of users with positions in this sector."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["average_pnl", "pnl_sum", "user_count"]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Calculates the average daily P&L per sector.",
|
|
46
|
+
"patternProperties": {
|
|
47
|
+
"^.*$": sectorSchema // Sector name
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": sectorSchema
|
|
50
|
+
};
|
|
16
51
|
}
|
|
17
52
|
|
|
18
|
-
|
|
19
|
-
if (this.
|
|
53
|
+
_initSector(sector) {
|
|
54
|
+
if (!this.sectors.has(sector)) {
|
|
55
|
+
this.sectors.set(sector, {
|
|
56
|
+
pnl_sum: 0,
|
|
57
|
+
users: new Set() // Use Set to count unique users
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
20
61
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
62
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
63
|
+
// This calculation needs the sector mappings from Pass 1 context
|
|
64
|
+
if (!this.mappings) {
|
|
65
|
+
this.mappings = context.mappings;
|
|
66
|
+
}
|
|
24
67
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
68
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
69
|
+
if (!positions || !Array.isArray(positions) || !this.mappings) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
28
72
|
|
|
29
|
-
|
|
30
|
-
|
|
73
|
+
for (const pos of positions) {
|
|
74
|
+
const instrumentId = pos.InstrumentID;
|
|
75
|
+
if (!instrumentId) continue;
|
|
76
|
+
|
|
77
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
78
|
+
this._initSector(sector);
|
|
79
|
+
|
|
80
|
+
const sectorData = this.sectors.get(sector);
|
|
81
|
+
sectorData.pnl_sum += pos.NetProfit || 0;
|
|
82
|
+
sectorData.users.add(userId);
|
|
31
83
|
}
|
|
84
|
+
}
|
|
32
85
|
|
|
86
|
+
getResult() {
|
|
33
87
|
const result = {};
|
|
34
|
-
for (const sector
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
88
|
+
for (const [sector, data] of this.sectors.entries()) {
|
|
89
|
+
const userCount = data.users.size;
|
|
90
|
+
if (userCount > 0) {
|
|
91
|
+
result[sector] = {
|
|
92
|
+
average_pnl: data.pnl_sum / userCount,
|
|
93
|
+
pnl_sum: data.pnl_sum,
|
|
94
|
+
user_count: userCount
|
|
95
|
+
};
|
|
96
|
+
}
|
|
39
97
|
}
|
|
40
|
-
|
|
41
98
|
return result;
|
|
42
99
|
}
|
|
43
100
|
|
|
44
101
|
reset() {
|
|
45
|
-
this.
|
|
102
|
+
this.sectors.clear();
|
|
103
|
+
this.mappings = null;
|
|
46
104
|
}
|
|
47
105
|
}
|
|
48
106
|
|
|
49
|
-
module.exports = AverageDailyPnlPerSector;
|
|
107
|
+
module.exports = AverageDailyPnlPerSector;
|
|
@@ -1,34 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average daily P&L per stock.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What was the average daily P&L for a
|
|
5
|
+
* position in each specific stock?"
|
|
4
6
|
*/
|
|
5
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
8
|
|
|
7
9
|
class AverageDailyPnlPerStock {
|
|
8
10
|
constructor() {
|
|
9
|
-
|
|
11
|
+
// We will store { [instrumentId]: { pnl_sum: 0, position_count: 0 } }
|
|
12
|
+
this.assets = new Map();
|
|
10
13
|
this.mappings = null;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
"description": "P&L metrics for a specific asset ticker.",
|
|
24
|
+
"properties": {
|
|
25
|
+
"average_pnl": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Average P&L per position (Sum / Count)."
|
|
28
|
+
},
|
|
29
|
+
"pnl_sum": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Sum of all P&L for this asset."
|
|
32
|
+
},
|
|
33
|
+
"position_count": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "Count of positions in this asset."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": ["average_pnl", "pnl_sum", "position_count"]
|
|
39
|
+
};
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
return {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Calculates the average daily P&L per stock/asset.",
|
|
44
|
+
"patternProperties": {
|
|
45
|
+
"^.*$": tickerSchema // Ticker
|
|
46
|
+
},
|
|
47
|
+
"additionalProperties": tickerSchema
|
|
48
|
+
};
|
|
49
|
+
}
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
51
|
+
_initAsset(instrumentId) {
|
|
52
|
+
if (!this.assets.has(instrumentId)) {
|
|
53
|
+
this.assets.set(instrumentId, {
|
|
54
|
+
pnl_sum: 0,
|
|
55
|
+
position_count: 0
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
process(portfolioData) {
|
|
61
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
62
|
+
if (!positions || !Array.isArray(positions)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const pos of positions) {
|
|
67
|
+
const instrumentId = pos.InstrumentID;
|
|
68
|
+
if (!instrumentId) continue;
|
|
69
|
+
|
|
70
|
+
this._initAsset(instrumentId);
|
|
71
|
+
|
|
72
|
+
const assetData = this.assets.get(instrumentId);
|
|
73
|
+
assetData.pnl_sum += pos.NetProfit || 0;
|
|
74
|
+
assetData.position_count++;
|
|
32
75
|
}
|
|
33
76
|
}
|
|
34
77
|
|
|
@@ -38,25 +81,24 @@ class AverageDailyPnlPerStock {
|
|
|
38
81
|
}
|
|
39
82
|
|
|
40
83
|
const result = {};
|
|
41
|
-
for (const instrumentId
|
|
42
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
43
|
-
const data = this.pnlData[instrumentId];
|
|
84
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
85
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
44
86
|
|
|
45
|
-
// REFACTOR: Perform the final calculation directly.
|
|
46
87
|
if (data.position_count > 0) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
88
|
+
result[ticker] = {
|
|
89
|
+
average_pnl: data.pnl_sum / data.position_count,
|
|
90
|
+
pnl_sum: data.pnl_sum,
|
|
91
|
+
position_count: data.position_count
|
|
92
|
+
};
|
|
50
93
|
}
|
|
51
94
|
}
|
|
52
|
-
|
|
53
95
|
return result;
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
reset() {
|
|
57
|
-
this.
|
|
99
|
+
this.assets.clear();
|
|
58
100
|
this.mappings = null;
|
|
59
101
|
}
|
|
60
102
|
}
|
|
61
103
|
|
|
62
|
-
module.exports = AverageDailyPnlPerStock;
|
|
104
|
+
module.exports = AverageDailyPnlPerStock;
|
|
@@ -1,40 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average P&L per position.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What was the average P&L across all
|
|
5
|
+
* individual positions held by the crowd today?"
|
|
6
|
+
*
|
|
7
|
+
* This is different from "average P&L per user" as a user
|
|
8
|
+
* can have multiple positions.
|
|
4
9
|
*/
|
|
5
|
-
|
|
6
10
|
class AverageDailyPositionPnl {
|
|
7
11
|
constructor() {
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
12
|
+
this.totalPnl = 0;
|
|
13
|
+
this.positionCount = 0;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 average daily P&L across all individual positions.",
|
|
24
|
+
"properties": {
|
|
25
|
+
"average_pnl": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "The average daily P&L per position (Total P&L / Position Count)."
|
|
28
|
+
},
|
|
29
|
+
"total_pnl": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "The sum of all daily P&L from all positions."
|
|
32
|
+
},
|
|
33
|
+
"position_count": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "The total number of positions processed."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": ["average_pnl", "total_pnl", "position_count"]
|
|
39
|
+
};
|
|
19
40
|
}
|
|
20
41
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
process(portfolioData) {
|
|
43
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
44
|
+
if (!positions || !Array.isArray(positions)) {
|
|
45
|
+
return;
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
for (const pos of positions) {
|
|
49
|
+
this.totalPnl += pos.NetProfit || 0;
|
|
50
|
+
this.positionCount++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
28
53
|
|
|
54
|
+
getResult() {
|
|
29
55
|
return {
|
|
30
|
-
|
|
56
|
+
average_pnl: (this.positionCount > 0) ? (this.totalPnl / this.positionCount) : 0,
|
|
57
|
+
total_pnl: this.totalPnl,
|
|
58
|
+
position_count: this.positionCount
|
|
31
59
|
};
|
|
32
60
|
}
|
|
33
61
|
|
|
34
62
|
reset() {
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
63
|
+
this.totalPnl = 0;
|
|
64
|
+
this.positionCount = 0;
|
|
37
65
|
}
|
|
38
66
|
}
|
|
39
67
|
|
|
40
|
-
module.exports = AverageDailyPositionPnl;
|
|
68
|
+
module.exports = AverageDailyPositionPnl;
|