aiden-shared-calculations-unified 1.0.0
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/calculations/asset_metrics/asset_dollar_metrics.js +51 -0
- package/calculations/asset_metrics/asset_position_size.js +57 -0
- package/calculations/behavioural/drawdown_response.js +56 -0
- package/calculations/behavioural/gain_response.js +54 -0
- package/calculations/behavioural/paper_vs_diamond_hands.js +40 -0
- package/calculations/behavioural/position_count_pnl.js +50 -0
- package/calculations/behavioural/smart_money_flow.js +166 -0
- package/calculations/pnl/asset_pnl_status.js +47 -0
- package/calculations/pnl/average_daily_pnl_all_users.js +48 -0
- package/calculations/pnl/average_daily_pnl_per_sector.js +49 -0
- package/calculations/pnl/average_daily_pnl_per_stock.js +62 -0
- package/calculations/pnl/average_daily_position_pnl.js +40 -0
- package/calculations/pnl/pnl_distribution_per_stock.js +53 -0
- package/calculations/pnl/profitability_migration.js +58 -0
- package/calculations/pnl/profitability_ratio_per_stock.js +51 -0
- package/calculations/pnl/profitability_skew_per_stock.js +59 -0
- package/calculations/pnl/user_profitability_tracker.js +68 -0
- package/calculations/sanity/users_processed.js +27 -0
- package/calculations/sectors/diversification_pnl.js +59 -0
- package/calculations/sectors/sector_dollar_metrics.js +49 -0
- package/calculations/sectors/sector_rotation.js +68 -0
- package/calculations/sectors/total_long_per_sector.js +44 -0
- package/calculations/sectors/total_short_per_sector.js +44 -0
- package/calculations/sentiment/crowd_conviction_score.js +81 -0
- package/calculations/short_and_long_stats/long_position_per_stock.js +44 -0
- package/calculations/short_and_long_stats/sentiment_per_stock.js +50 -0
- package/calculations/short_and_long_stats/short_position_per_stock.js +43 -0
- package/calculations/short_and_long_stats/total_long_figures.js +33 -0
- package/calculations/short_and_long_stats/total_short_figures.js +34 -0
- package/calculations/speculators/distance_to_stop_loss_per_leverage.js +78 -0
- package/calculations/speculators/distance_to_tp_per_leverage.js +76 -0
- package/calculations/speculators/entry_distance_to_sl_per_leverage.js +78 -0
- package/calculations/speculators/entry_distance_to_tp_per_leverage.js +77 -0
- package/calculations/speculators/holding_duration_per_asset.js +55 -0
- package/calculations/speculators/leverage_per_asset.js +47 -0
- package/calculations/speculators/leverage_per_sector.js +45 -0
- package/calculations/speculators/risk_appetite_change.js +55 -0
- package/calculations/speculators/risk_reward_ratio_per_asset.js +60 -0
- package/calculations/speculators/speculator_asset_sentiment.js +81 -0
- package/calculations/speculators/speculator_danger_zone.js +58 -0
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +91 -0
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +73 -0
- package/calculations/speculators/stop_loss_per_asset.js +55 -0
- package/calculations/speculators/take_profit_per_asset.js +55 -0
- package/calculations/speculators/tsl_effectiveness.js +55 -0
- package/calculations/speculators/tsl_per_asset.js +52 -0
- package/index.js +31 -0
- package/package.json +32 -0
- package/utils/firestore_utils.js +77 -0
- package/utils/sector_mapping_provider.js +75 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the overall average PnL (NetProfit %) across every single position held by the crowd.
|
|
3
|
+
* This gives a high-level view of whether the average position is in profit or loss.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class AverageDailyPositionPnl {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.totalNetProfit = 0;
|
|
9
|
+
this.totalPositions = 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process(portfolioData, userId, context) {
|
|
13
|
+
if (portfolioData && portfolioData.AggregatedPositions) {
|
|
14
|
+
for (const position of portfolioData.AggregatedPositions) {
|
|
15
|
+
this.totalNetProfit += position.NetProfit;
|
|
16
|
+
this.totalPositions++;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getResult() {
|
|
22
|
+
if (this.totalPositions === 0) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// REFACTOR: Perform the final calculation directly.
|
|
27
|
+
const average_daily_position_pnl = this.totalNetProfit / this.totalPositions;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
average_daily_position_pnl
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reset() {
|
|
35
|
+
this.totalNetProfit = 0;
|
|
36
|
+
this.totalPositions = 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = AverageDailyPositionPnl;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the necessary components to determine the statistical variance of PnL for each stock,
|
|
3
|
+
* serving as a proxy for crowd volatility.
|
|
4
|
+
*/
|
|
5
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
|
+
|
|
7
|
+
class PnlDistributionPerStock {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.distributionData = {};
|
|
10
|
+
this.mappings = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
process(portfolioData, userId, context) {
|
|
14
|
+
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
15
|
+
if (!positions) return;
|
|
16
|
+
|
|
17
|
+
for (const position of positions) {
|
|
18
|
+
const instrumentId = position.InstrumentID;
|
|
19
|
+
const netProfit = position.NetProfit;
|
|
20
|
+
|
|
21
|
+
if (!this.distributionData[instrumentId]) {
|
|
22
|
+
this.distributionData[instrumentId] = {
|
|
23
|
+
pnl_sum: 0,
|
|
24
|
+
pnl_sum_sq: 0,
|
|
25
|
+
position_count: 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.distributionData[instrumentId].pnl_sum += netProfit;
|
|
30
|
+
this.distributionData[instrumentId].pnl_sum_sq += netProfit * netProfit;
|
|
31
|
+
this.distributionData[instrumentId].position_count++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getResult() {
|
|
36
|
+
if (!this.mappings) {
|
|
37
|
+
this.mappings = await loadInstrumentMappings();
|
|
38
|
+
}
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const instrumentId in this.distributionData) {
|
|
41
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
42
|
+
result[ticker] = this.distributionData[instrumentId];
|
|
43
|
+
}
|
|
44
|
+
return { pnl_distribution_by_asset: result };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
reset() {
|
|
48
|
+
this.distributionData = {};
|
|
49
|
+
this.mappings = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = PnlDistributionPerStock;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the migration of users between profitability states.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
class ProfitabilityMigration {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.profitableToUnprofitable = 0;
|
|
8
|
+
this.unprofitableToProfitable = 0;
|
|
9
|
+
this.remainedProfitable = 0;
|
|
10
|
+
this.remainedUnprofitable = 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
14
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const todayProfit = this.calculateTotalPnl(todayPortfolio);
|
|
19
|
+
const yesterdayProfit = this.calculateTotalPnl(yesterdayPortfolio);
|
|
20
|
+
|
|
21
|
+
if (todayProfit === null || yesterdayProfit === null) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const wasProfitable = yesterdayProfit > 0;
|
|
26
|
+
const isProfitable = todayProfit > 0;
|
|
27
|
+
|
|
28
|
+
if (wasProfitable && !isProfitable) {
|
|
29
|
+
this.profitableToUnprofitable++;
|
|
30
|
+
} else if (!wasProfitable && isProfitable) {
|
|
31
|
+
this.unprofitableToProfitable++;
|
|
32
|
+
} else if (wasProfitable && isProfitable) {
|
|
33
|
+
this.remainedProfitable++;
|
|
34
|
+
} else {
|
|
35
|
+
this.remainedUnprofitable++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
calculateTotalPnl(portfolio) {
|
|
40
|
+
if (portfolio && portfolio.AggregatedPositions) {
|
|
41
|
+
return portfolio.AggregatedPositions.reduce((sum, pos) => sum + pos.NetProfit, 0);
|
|
42
|
+
} else if (portfolio && portfolio.PublicPositions) {
|
|
43
|
+
return portfolio.PublicPositions.reduce((sum, pos) => sum + pos.NetProfit, 0);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getResult() {
|
|
49
|
+
return {
|
|
50
|
+
profitable_to_unprofitable: this.profitableToUnprofitable,
|
|
51
|
+
unprofitable_to_profitable: this.unprofitableToProfitable,
|
|
52
|
+
remained_profitable: this.remainedProfitable,
|
|
53
|
+
remained_unprofitable: this.remainedUnprofitable,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = ProfitabilityMigration;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the ratio of profitable vs. unprofitable positions for each stock.
|
|
3
|
+
*/
|
|
4
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
|
+
|
|
6
|
+
class ProfitabilityRatioPerStock {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.profitabilityData = {};
|
|
9
|
+
this.mappings = null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process(portfolioData, userId, context) {
|
|
13
|
+
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
14
|
+
if (!positions) return;
|
|
15
|
+
|
|
16
|
+
for (const position of positions) {
|
|
17
|
+
const instrumentId = position.InstrumentID;
|
|
18
|
+
const netProfit = position.NetProfit;
|
|
19
|
+
|
|
20
|
+
if (!this.profitabilityData[instrumentId]) {
|
|
21
|
+
this.profitabilityData[instrumentId] = { profitable: 0, unprofitable: 0 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (netProfit > 0) {
|
|
25
|
+
this.profitabilityData[instrumentId].profitable++;
|
|
26
|
+
} else if (netProfit < 0) {
|
|
27
|
+
this.profitabilityData[instrumentId].unprofitable++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getResult() {
|
|
33
|
+
if (!this.mappings) {
|
|
34
|
+
this.mappings = await loadInstrumentMappings();
|
|
35
|
+
}
|
|
36
|
+
const result = {};
|
|
37
|
+
for (const instrumentId in this.profitabilityData) {
|
|
38
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
39
|
+
result[ticker] = this.profitabilityData[instrumentId];
|
|
40
|
+
}
|
|
41
|
+
if (Object.keys(result).length === 0) return {};
|
|
42
|
+
return { profitability_by_asset: result };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
reset() {
|
|
46
|
+
this.profitabilityData = {};
|
|
47
|
+
this.mappings = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = ProfitabilityRatioPerStock;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the magnitude of profitable vs. unprofitable positions for each stock,
|
|
3
|
+
* providing insight into the skew of returns.
|
|
4
|
+
*/
|
|
5
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
|
+
|
|
7
|
+
class ProfitabilitySkewPerStock {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.skewData = {};
|
|
10
|
+
this.mappings = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
process(portfolioData, userId, context) {
|
|
14
|
+
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
15
|
+
if (!positions) return;
|
|
16
|
+
|
|
17
|
+
for (const position of positions) {
|
|
18
|
+
const instrumentId = position.InstrumentID;
|
|
19
|
+
const netProfit = position.NetProfit;
|
|
20
|
+
|
|
21
|
+
if (!this.skewData[instrumentId]) {
|
|
22
|
+
this.skewData[instrumentId] = {
|
|
23
|
+
profit_sum: 0,
|
|
24
|
+
loss_sum: 0,
|
|
25
|
+
profitable_count: 0,
|
|
26
|
+
unprofitable_count: 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (netProfit > 0) {
|
|
31
|
+
this.skewData[instrumentId].profit_sum += netProfit;
|
|
32
|
+
this.skewData[instrumentId].profitable_count++;
|
|
33
|
+
} else if (netProfit < 0) {
|
|
34
|
+
this.skewData[instrumentId].loss_sum += netProfit; // Keep it negative to sum up losses
|
|
35
|
+
this.skewData[instrumentId].unprofitable_count++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getResult() {
|
|
41
|
+
if (!this.mappings) {
|
|
42
|
+
this.mappings = await loadInstrumentMappings();
|
|
43
|
+
}
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const instrumentId in this.skewData) {
|
|
46
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
47
|
+
result[ticker] = this.skewData[instrumentId];
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(result).length === 0) return {};
|
|
50
|
+
return { profitability_skew_by_asset: result };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
reset() {
|
|
54
|
+
this.skewData = {};
|
|
55
|
+
this.mappings = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = ProfitabilitySkewPerStock;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracks user profitability over a 7-day rolling window.
|
|
3
|
+
* This version shards the output to avoid Firestore's "too many index entries" error.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Firestore } = require('@google-cloud/firestore');
|
|
7
|
+
const firestore = new Firestore();
|
|
8
|
+
|
|
9
|
+
// Define a constant for the number of shards
|
|
10
|
+
const NUM_SHARDS = 50;
|
|
11
|
+
|
|
12
|
+
class UserProfitabilityTracker {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.dailyProfits = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
18
|
+
if (!todayPortfolio) return;
|
|
19
|
+
|
|
20
|
+
const todayPnl = this.calculateTotalPnl(todayPortfolio);
|
|
21
|
+
if (todayPnl !== null) {
|
|
22
|
+
this.dailyProfits[userId] = todayPnl;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
calculateTotalPnl(portfolio) {
|
|
27
|
+
if (portfolio && portfolio.AggregatedPositions) {
|
|
28
|
+
return portfolio.AggregatedPositions.reduce((sum, pos) => sum + pos.NetProfit, 0);
|
|
29
|
+
} else if (portfolio && portfolio.PublicPositions) {
|
|
30
|
+
return portfolio.PublicPositions.reduce((sum, pos) => sum + pos.NetProfit, 0);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getResult() {
|
|
36
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
37
|
+
const results = {};
|
|
38
|
+
|
|
39
|
+
// Prepare sharded data structure
|
|
40
|
+
for (let i = 0; i < NUM_SHARDS; i++) {
|
|
41
|
+
results[`user_profitability_shard_${i}`] = {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Fetch all existing shards in parallel for efficiency
|
|
45
|
+
const shardPromises = [];
|
|
46
|
+
for (let i = 0; i < NUM_SHARDS; i++) {
|
|
47
|
+
const docRef = firestore.collection('historical_insights').doc(`user_profitability_shard_${i}`);
|
|
48
|
+
shardPromises.push(docRef.get());
|
|
49
|
+
}
|
|
50
|
+
const shardSnapshots = await Promise.all(shardPromises);
|
|
51
|
+
const existingData = shardSnapshots.map(snap => (snap.exists ? snap.data().profits : {}));
|
|
52
|
+
|
|
53
|
+
// Process users and assign them to shards
|
|
54
|
+
for (const userId in this.dailyProfits) {
|
|
55
|
+
const shardIndex = parseInt(userId, 10) % NUM_SHARDS;
|
|
56
|
+
const userHistory = existingData[shardIndex][userId] || [];
|
|
57
|
+
|
|
58
|
+
userHistory.push({ date: today, pnl: this.dailyProfits[userId] });
|
|
59
|
+
|
|
60
|
+
// Ensure we only keep the last 7 days of data
|
|
61
|
+
results[`user_profitability_shard_${shardIndex}`][userId] = userHistory.slice(-7);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { sharded_user_profitability: results };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = UserProfitabilityTracker;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview A simple counter that increments for each user processed.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
class UsersProcessed {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.userCount = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
process(portfolioData, userId, context) {
|
|
11
|
+
this.userCount++;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getResult() {
|
|
15
|
+
if (this.userCount === 0) return {};
|
|
16
|
+
// FIX: Rename the key to use the 'raw' prefix for correct aggregation.
|
|
17
|
+
return {
|
|
18
|
+
rawTotalUsersProcessed: this.userCount
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
reset() {
|
|
23
|
+
this.userCount = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = UsersProcessed;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// CORRECTED PATH: ../utils/ instead of ../../utils/
|
|
2
|
+
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Aggregates P/L by the number of unique sectors a user is invested in.
|
|
6
|
+
*/
|
|
7
|
+
class DiversificationPnl {
|
|
8
|
+
// ... (rest of the code is unchanged) ...
|
|
9
|
+
constructor() {
|
|
10
|
+
this.pnl_by_sector_count = {};
|
|
11
|
+
// Load the mapping on initialization
|
|
12
|
+
this.sectorMapping = null; // Changed: Load async in process
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_initBucket(count) {
|
|
16
|
+
if (!this.pnl_by_sector_count[count]) {
|
|
17
|
+
this.pnl_by_sector_count[count] = { pnl_sum: 0, count: 0 };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async process(todayPortfolio, yesterdayPortfolio, userId) { // Added async
|
|
22
|
+
if (!yesterdayPortfolio || !todayPortfolio) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Load mapping if not already loaded
|
|
26
|
+
if(!this.sectorMapping) {
|
|
27
|
+
this.sectorMapping = await getInstrumentSectorMap();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const positions = todayPortfolio.AggregatedPositions || todayPortfolio.PublicPositions;
|
|
31
|
+
|
|
32
|
+
if (!positions || !Array.isArray(positions)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const uniqueSectors = new Set();
|
|
37
|
+
for (const position of positions) {
|
|
38
|
+
const sector = this.sectorMapping[position.InstrumentID] || 'Other';
|
|
39
|
+
uniqueSectors.add(sector);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sectorCount = uniqueSectors.size;
|
|
43
|
+
if (sectorCount === 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const dailyPnl = todayPortfolio.PortfolioValue - yesterdayPortfolio.PortfolioValue;
|
|
48
|
+
|
|
49
|
+
this._initBucket(sectorCount);
|
|
50
|
+
this.pnl_by_sector_count[sectorCount].pnl_sum += dailyPnl;
|
|
51
|
+
this.pnl_by_sector_count[sectorCount].count++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getResult() {
|
|
55
|
+
return this.pnl_by_sector_count;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = DiversificationPnl;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregates the total dollar amount invested in sectors,
|
|
3
|
+
* broken down by profit or loss (from our sample).
|
|
4
|
+
*/
|
|
5
|
+
class SectorDollarMetrics {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.sectors = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_initSector(sector) {
|
|
11
|
+
if (!this.sectors[sector]) {
|
|
12
|
+
this.sectors[sector] = {
|
|
13
|
+
total_invested_sum: 0,
|
|
14
|
+
profit_invested_sum: 0,
|
|
15
|
+
loss_invested_sum: 0
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
process(portfolioData, userId, context) {
|
|
21
|
+
const { sectorMapping } = context; // Assumes sectorMapping is in context
|
|
22
|
+
|
|
23
|
+
// FIX: Use the correct portfolio position properties
|
|
24
|
+
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
25
|
+
if (!positions || !Array.isArray(positions)) return;
|
|
26
|
+
|
|
27
|
+
for (const position of positions) {
|
|
28
|
+
// FIX: Use the correct PascalCase InstrumentID
|
|
29
|
+
const sector = sectorMapping[position.InstrumentID] || 'Other';
|
|
30
|
+
this._initSector(sector);
|
|
31
|
+
|
|
32
|
+
// FIX: Use the correct PascalCase InvestedAmount
|
|
33
|
+
this.sectors[sector].total_invested_sum += position.InvestedAmount;
|
|
34
|
+
|
|
35
|
+
// FIX: Use the correct PascalCase NetProfit
|
|
36
|
+
if (position.NetProfit > 0) {
|
|
37
|
+
this.sectors[sector].profit_invested_sum += position.InvestedAmount;
|
|
38
|
+
} else {
|
|
39
|
+
this.sectors[sector].loss_invested_sum += position.InvestedAmount;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getResult() {
|
|
45
|
+
return this.sectors;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = SectorDollarMetrics;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Analyzes sector rotation by comparing investment amounts between two days.
|
|
3
|
+
*/
|
|
4
|
+
// CORRECTED PATH: ../utils/ instead of ../../utils/
|
|
5
|
+
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
|
|
6
|
+
|
|
7
|
+
class SectorRotation {
|
|
8
|
+
// ... (rest of the code is unchanged) ...
|
|
9
|
+
constructor() {
|
|
10
|
+
this.todaySectorInvestment = {};
|
|
11
|
+
this.yesterdaySectorInvestment = {};
|
|
12
|
+
this.sectorMap = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
16
|
+
if (!this.sectorMap) {
|
|
17
|
+
this.sectorMap = await getInstrumentSectorMap();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (todayPortfolio) {
|
|
21
|
+
this.accumulateSectorInvestment(todayPortfolio, this.todaySectorInvestment);
|
|
22
|
+
}
|
|
23
|
+
if (yesterdayPortfolio) {
|
|
24
|
+
this.accumulateSectorInvestment(yesterdayPortfolio, this.yesterdaySectorInvestment);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
accumulateSectorInvestment(portfolio, target) {
|
|
29
|
+
// Use AggregatedPositions, as PublicPositions don't have Invested amount
|
|
30
|
+
if (portfolio && portfolio.AggregatedPositions) {
|
|
31
|
+
for (const pos of portfolio.AggregatedPositions) {
|
|
32
|
+
// Check if sectorMap is loaded before accessing it
|
|
33
|
+
if (!this.sectorMap) {
|
|
34
|
+
console.warn('Sector map not loaded in accumulateSectorInvestment');
|
|
35
|
+
continue; // or handle appropriately
|
|
36
|
+
}
|
|
37
|
+
const sector = this.sectorMap[pos.InstrumentID] || 'N/A';
|
|
38
|
+
if (!target[sector]) {
|
|
39
|
+
target[sector] = 0;
|
|
40
|
+
}
|
|
41
|
+
// Use Invested amount if available, otherwise fallback might be needed
|
|
42
|
+
target[sector] += (pos.Invested || pos.Amount || 0); // Added fallback
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Optional: Handle PublicPositions if needed, though they lack 'Invested'
|
|
46
|
+
else if (portfolio && portfolio.PublicPositions) {
|
|
47
|
+
// Logic to handle PublicPositions if necessary, e.g., using 'Amount'
|
|
48
|
+
// console.warn("Using PublicPositions for sector rotation, 'Invested' amount missing.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
getResult() {
|
|
54
|
+
const sectorRotationData = {};
|
|
55
|
+
const allSectors = new Set([...Object.keys(this.todaySectorInvestment), ...Object.keys(this.yesterdaySectorInvestment)]);
|
|
56
|
+
|
|
57
|
+
for (const sector of allSectors) {
|
|
58
|
+
const todayAmount = this.todaySectorInvestment[sector] || 0;
|
|
59
|
+
const yesterdayAmount = this.yesterdaySectorInvestment[sector] || 0;
|
|
60
|
+
sectorRotationData[sector] = todayAmount - yesterdayAmount;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { sector_rotation: sectorRotationData };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
module.exports = SectorRotation;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Counts the total number of 'long' (Buy) positions for each sector.
|
|
3
|
+
*/
|
|
4
|
+
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider'); // Correctly import the new function
|
|
5
|
+
|
|
6
|
+
class TotalLongPerSector {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.longPositions = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
process(portfolioData, userId, context) {
|
|
12
|
+
if (portfolioData && portfolioData.AggregatedPositions) {
|
|
13
|
+
for (const position of portfolioData.AggregatedPositions) {
|
|
14
|
+
if (position.Direction === 'Buy') {
|
|
15
|
+
this.longPositions.push(position);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getResult() {
|
|
22
|
+
const longCountBySector = {};
|
|
23
|
+
const sectorMap = await getInstrumentSectorMap(); // Use the new function
|
|
24
|
+
|
|
25
|
+
for (const position of this.longPositions) {
|
|
26
|
+
const instrumentId = position.InstrumentID;
|
|
27
|
+
const sector = sectorMap[instrumentId] || 'N/A';
|
|
28
|
+
longCountBySector[sector] = (longCountBySector[sector] || 0) + 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const sector in longCountBySector) {
|
|
33
|
+
result[`raw_${sector}_long_count`] = longCountBySector[sector];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
reset() {
|
|
40
|
+
this.longPositions = [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = TotalLongPerSector;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Counts the total number of 'short' (Sell) positions for each sector.
|
|
3
|
+
*/
|
|
4
|
+
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider'); // Correctly import the new function
|
|
5
|
+
|
|
6
|
+
class TotalShortPerSector {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.shortPositions = [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
process(portfolioData, userId, context) {
|
|
12
|
+
if (portfolioData && portfolioData.AggregatedPositions) {
|
|
13
|
+
for (const position of portfolioData.AggregatedPositions) {
|
|
14
|
+
if (position.Direction === 'Sell') {
|
|
15
|
+
this.shortPositions.push(position);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getResult() {
|
|
22
|
+
const shortCountBySector = {};
|
|
23
|
+
const sectorMap = await getInstrumentSectorMap(); // Use the new function
|
|
24
|
+
|
|
25
|
+
for (const position of this.shortPositions) {
|
|
26
|
+
const instrumentId = position.InstrumentID;
|
|
27
|
+
const sector = sectorMap[instrumentId] || 'N/A';
|
|
28
|
+
shortCountBySector[sector] = (shortCountBySector[sector] || 0) + 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const sector in shortCountBySector) {
|
|
33
|
+
result[`raw_${sector}_short_count`] = shortCountBySector[sector];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
reset() {
|
|
40
|
+
this.shortPositions = [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = TotalShortPerSector;
|