aiden-shared-calculations-unified 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,78 @@
1
+ # Unified Calculations Package (`aiden-shared-calculations-unified`)
2
+
3
+ **Version:** 1.0.0
4
+
5
+ ## Overview
6
+
7
+ This package centralizes all data calculation logic for the BullTrackers project. It provides a standardized structure for calculations run by the unified Computation System and includes shared utility functions. Calculations are dynamically loaded and categorized.
8
+
9
+ ## Package Structure
10
+
11
+ ### `/utils`
12
+
13
+ Shared utility functions used by calculations or the systems consuming them.
14
+
15
+ * `firestore_utils.js`: Provides a resilient `withRetry` wrapper for Firestore operations using exponential backoff.
16
+ * `sector_mapping_provider.js`: Provides functions (`loadInstrumentMappings`, `getInstrumentSectorMap`) to fetch and cache instrument-to-ticker and instrument-to-sector mappings from Firestore.
17
+
18
+ ### `/calculations`
19
+
20
+ Contains the core calculation logic, organized into subdirectories representing categories.
21
+
22
+ * **Calculation Class Structure:** Each `.js` file defines a class responsible for a specific metric. Every class **must** implement:
23
+ * `constructor()`: Initializes any internal state needed for aggregation.
24
+ * `process(portfolioData, userId, context)` OR `process(todayPortfolio, yesterdayPortfolio, userId)`: Processes a single user's data. The signature depends on whether the calculation requires historical comparison. `context` provides shared data like mappings.
25
+ * `getResult()`: Returns the final, calculated result for the aggregation period. **Crucially, this method must perform any final averaging (e.g., sum/count) itself.** It should return the final value or object ready for storage, not raw components.
26
+ * `reset()`: (Optional but recommended) Resets the internal state, often used by the calling system between processing batches or days.
27
+
28
+ * **Categories (Examples based on your files):**
29
+ * `/asset_metrics`: Calculations focused on individual assets (e.g., `asset_dollar_metrics`, `asset_position_size`).
30
+ * `/behavioural`: Calculations analyzing user trading patterns (e.g., `drawdown_response`, `gain_response`, `paper_vs_diamond_hands`, `position_count_pnl`, `smart_money_flow`).
31
+ * `/pnl`: Profit and Loss related calculations (e.g., `asset_pnl_status`, `average_daily_pnl_all_users`, `average_daily_pnl_per_sector`, `average_daily_pnl_per_stock`, `average_daily_position_pnl`, `pnl_distribution_per_stock`, `profitability_migration`, `profitability_ratio_per_stock`, `profitability_skew_per_stock`, `user_profitability_tracker`).
32
+ * `/sanity`: Basic checks and counts (e.g., `users_processed`).
33
+ * `/sectors`: Calculations aggregated by market sector (e.g., `diversification_pnl`, `sector_dollar_metrics`, `sector_rotation`, `total_long_per_sector`, `total_short_per_sector`).
34
+ * `/sentiment`: Calculations related to market sentiment (e.g., `crowd_conviction_score`).
35
+ * `/short_and_long_stats`: Specific counts for short and long positions (e.g., `long_position_per_stock`, `sentiment_per_stock`, `short_position_per_stock`, `total_long_figures`, `total_short_figures`).
36
+ * `/speculators`: Calculations **specifically** for the 'speculator' user type, often involving leverage, stop-loss, or take-profit data (e.g., `distance_to_stop_loss_per_leverage`, `distance_to_tp_per_leverage`, `entry_distance_to_sl_per_leverage`, `entry_distance_to_tp_per_leverage`, `holding_duration_per_asset`, `leverage_per_asset`, `leverage_per_sector`, `risk_appetite_change`, `risk_reward_ratio_per_asset`, `speculator_asset_sentiment`, `speculator_danger_zone`, `stop_loss_distance_by_sector_short_long_breakdown`, `stop_loss_distance_by_ticker_short_long_breakdown`, `stop_loss_per_asset`, `take_profit_per_asset`, `tsl_effectiveness`, `tsl_per_asset`).
37
+
38
+ * **Output Formats:** Calculations should adhere to the standardized output formats defined in `docs/Notes/output_formats.md`.
39
+
40
+ ## Usage
41
+
42
+ This package is intended to be consumed primarily by the unified Computation System.
43
+
44
+ ```javascript
45
+ // Example usage within Computation System
46
+ const { calculations, utils } = require('aiden-shared-calculations-unified');
47
+
48
+ const CalculationClass = calculations.pnl.average_daily_pnl_per_stock;
49
+ const calculator = new CalculationClass();
50
+
51
+ // ... load data ...
52
+
53
+ // In a loop for each user:
54
+ calculator.process(portfolioData, userId, context);
55
+
56
+ // After processing all users:
57
+ const results = await calculator.getResult();
58
+
59
+ // ... store results ...
60
+ ````
61
+
62
+ ## Contributing
63
+
64
+ *(Outline the process for adding new calculations)*
65
+
66
+ 1. **Determine Category:** Decide which subdirectory (`/calculations/<category>`) the new metric belongs to. Create a new category if necessary.
67
+ 2. **Create File:** Create a new `.js` file using kebab-case (e.g., `my-new-metric.js`).
68
+ 3. **Implement Class:** Define the calculation class, ensuring it has `constructor`, `process`, and `getResult` methods adhering to the standards. Remember `getResult` must return the *final* computed value.
69
+ 4. **Add to Manifest:** The main `index.js` uses `require-all`, so the new calculation should be automatically included in the exports upon the next package build/publish, assuming the file naming and structure are correct.
70
+ 5. **Publish:** Bump the package version (`npm version patch` or minor/major as appropriate) and publish (`npm publish --access public`).
71
+ 6. **Update Consumer:** Update the version dependency in the Computation System's `package.json`.
72
+
73
+ <!-- end list -->
74
+
75
+ ```
76
+
77
+ ---
78
+ ```
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Estimates the average percentage deposited into accounts between two days.
3
+ */
4
+
5
+ class DepositPercentage {
6
+ constructor() {
7
+ this.totalDepositPercentage = 0;
8
+ this.userCount = 0;
9
+ }
10
+
11
+ /**
12
+ * Calculates total PnL from aggregated or public positions.
13
+ * @param {object} portfolio - Portfolio data object.
14
+ * @returns {number|null} Total PnL or null if positions are missing.
15
+ */
16
+ calculateTotalPnl(portfolio) {
17
+ // Use the same logic as in ProfitabilityMigration
18
+ if (portfolio && portfolio.AggregatedPositions) {
19
+ return portfolio.AggregatedPositions.reduce((sum, pos) => sum + (pos.NetProfit || 0), 0);
20
+ } else if (portfolio && portfolio.PublicPositions) {
21
+ // PublicPositions might lack NetProfit, adjust if necessary based on your data
22
+ return portfolio.PublicPositions.reduce((sum, pos) => sum + (pos.NetProfit || 0), 0);
23
+ }
24
+ return null;
25
+ }
26
+
27
+ process(todayPortfolio, yesterdayPortfolio, userId) {
28
+ if (!todayPortfolio || !yesterdayPortfolio || !yesterdayPortfolio.PortfolioValue || yesterdayPortfolio.PortfolioValue === 0) {
29
+ // Need both portfolios and a non-zero yesterday value for calculation
30
+ return;
31
+ }
32
+
33
+ const valueChange = (todayPortfolio.PortfolioValue || 0) - yesterdayPortfolio.PortfolioValue;
34
+
35
+ const todayPnl = this.calculateTotalPnl(todayPortfolio);
36
+ const yesterdayPnl = this.calculateTotalPnl(yesterdayPortfolio);
37
+
38
+ // If PnL can't be calculated for either day, we can't reliably estimate cash flow
39
+ if (todayPnl === null || yesterdayPnl === null) {
40
+ return;
41
+ }
42
+
43
+ const pnlChange = todayPnl - yesterdayPnl;
44
+ const cashFlow = valueChange - pnlChange;
45
+
46
+ // Check for deposit (positive cash flow)
47
+ if (cashFlow > 0) {
48
+ const depositPercentage = (cashFlow / yesterdayPortfolio.PortfolioValue) * 100;
49
+ this.totalDepositPercentage += depositPercentage;
50
+ this.userCount++;
51
+ }
52
+ }
53
+
54
+ getResult() {
55
+ if (this.userCount === 0) {
56
+ return {}; // Return empty object if no users contributed
57
+ }
58
+
59
+ return {
60
+ // Calculate the final average directly
61
+ average_deposit_percentage: this.totalDepositPercentage / this.userCount
62
+ };
63
+ }
64
+
65
+ reset() {
66
+ this.totalDepositPercentage = 0;
67
+ this.userCount = 0;
68
+ }
69
+ }
70
+
71
+ module.exports = DepositPercentage;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Calculates the average percentage of total portfolio value
3
+ * newly allocated to assets not held on the previous day.
4
+ */
5
+
6
+ class NewAllocationPercentage {
7
+ constructor() {
8
+ this.accumulatedNewAllocationPercentage = 0;
9
+ this.userCount = 0;
10
+ }
11
+
12
+ process(todayPortfolio, yesterdayPortfolio, userId) {
13
+ const todayPositions = todayPortfolio?.AggregatedPositions;
14
+ const yesterdayPositions = yesterdayPortfolio?.AggregatedPositions || [];
15
+
16
+ if (!todayPositions || todayPositions.length === 0) return;
17
+
18
+ const yesterdayIds = new Set(yesterdayPositions.map(p => p.InstrumentID));
19
+ let userNewAllocation = 0;
20
+
21
+ for (const pos of todayPositions) {
22
+ if (!yesterdayIds.has(pos.InstrumentID)) {
23
+ const invested = typeof pos.Invested === 'number' ? pos.Invested : 0;
24
+ userNewAllocation += invested;
25
+ }
26
+ }
27
+
28
+ // Guard against data rounding or eToro API drift
29
+ if (userNewAllocation > 100) userNewAllocation = 100;
30
+
31
+ this.accumulatedNewAllocationPercentage += userNewAllocation;
32
+ this.userCount++;
33
+ }
34
+
35
+ getResult() {
36
+ if (this.userCount === 0) return {};
37
+ return {
38
+ average_new_allocation_percentage:
39
+ this.accumulatedNewAllocationPercentage / this.userCount
40
+ };
41
+ }
42
+
43
+ reset() {
44
+ this.accumulatedNewAllocationPercentage = 0;
45
+ this.userCount = 0;
46
+ }
47
+ }
48
+
49
+ module.exports = NewAllocationPercentage;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @fileoverview Calculates the average percentage increase in allocation
3
+ * specifically towards assets already held on the previous day.
4
+ */
5
+
6
+ class ReallocationIncreasePercentage {
7
+ constructor() {
8
+ this.accumulatedIncreasePercentage = 0;
9
+ this.userCount = 0;
10
+ }
11
+
12
+ process(todayPortfolio, yesterdayPortfolio, userId) {
13
+ if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
14
+ // Requires AggregatedPositions which contain the 'Invested' percentage
15
+ return;
16
+ }
17
+
18
+ const yesterdayPositions = new Map(yesterdayPortfolio.AggregatedPositions.map(p => [p.InstrumentID, p]));
19
+ let userTotalIncreasePercentage = 0;
20
+
21
+ for (const todayPos of todayPortfolio.AggregatedPositions) {
22
+ const yesterdayPos = yesterdayPositions.get(todayPos.InstrumentID);
23
+
24
+ // Check if the asset was held yesterday
25
+ if (yesterdayPos) {
26
+ // Ensure 'Invested' property exists and is a number
27
+ const todayInvested = typeof todayPos.Invested === 'number' ? todayPos.Invested : 0;
28
+ const yesterdayInvested = typeof yesterdayPos.Invested === 'number' ? yesterdayPos.Invested : 0;
29
+
30
+ const deltaInvested = todayInvested - yesterdayInvested;
31
+
32
+ // Accumulate only the increases
33
+ if (deltaInvested > 0) {
34
+ userTotalIncreasePercentage += deltaInvested;
35
+ }
36
+ }
37
+ }
38
+
39
+ // Only count users who had positions on both days for this metric
40
+ if (yesterdayPortfolio.AggregatedPositions.length > 0 && todayPortfolio.AggregatedPositions.length > 0) {
41
+ this.accumulatedIncreasePercentage += userTotalIncreasePercentage;
42
+ this.userCount++;
43
+ }
44
+ }
45
+
46
+ getResult() {
47
+ if (this.userCount === 0) {
48
+ return {};
49
+ }
50
+
51
+ return {
52
+ // Calculate the final average directly
53
+ average_reallocation_increase_percentage: this.accumulatedIncreasePercentage / this.userCount
54
+ };
55
+ }
56
+
57
+ reset() {
58
+ this.accumulatedIncreasePercentage = 0;
59
+ this.userCount = 0;
60
+ }
61
+ }
62
+
63
+ module.exports = ReallocationIncreasePercentage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [