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.
Files changed (89) hide show
  1. package/README.MD +1 -1
  2. package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
  3. package/calculations/activity/historical/daily_asset_activity.js +42 -0
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
  6. package/calculations/asset_metrics/asset_position_size.js +36 -0
  7. package/calculations/backtests/strategy-performance.js +41 -0
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
  9. package/calculations/behavioural/historical/drawdown_response.js +113 -35
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
  11. package/calculations/behavioural/historical/gain_response.js +113 -34
  12. package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
  13. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
  14. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
  15. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
  16. package/calculations/behavioural/historical/position_count_pnl.js +91 -39
  17. package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
  18. package/calculations/behavioural/historical/smart_money_flow.js +160 -151
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
  22. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
  24. package/calculations/insights/daily_total_positions_held.js +28 -24
  25. package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
  26. package/calculations/insights/historical/daily_ownership_delta.js +95 -32
  27. package/calculations/meta/capital_deployment_strategy.js +78 -110
  28. package/calculations/meta/capital_liquidation_performance.js +114 -111
  29. package/calculations/meta/cash-flow-deployment.js +114 -107
  30. package/calculations/meta/cash-flow-liquidation.js +114 -107
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
  32. package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
  33. package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
  34. package/calculations/meta/profit_cohort_divergence.js +83 -59
  35. package/calculations/meta/shark_attack_signal.js +91 -39
  36. package/calculations/meta/smart-dumb-divergence-index.js +114 -98
  37. package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
  38. package/calculations/meta/social-predictive-regime-state.js +76 -155
  39. package/calculations/meta/social-topic-driver-index.js +74 -127
  40. package/calculations/meta/user_expectancy_score.js +83 -31
  41. package/calculations/pnl/asset_pnl_status.js +120 -31
  42. package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
  43. package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
  44. package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
  45. package/calculations/pnl/average_daily_position_pnl.js +49 -21
  46. package/calculations/pnl/historical/profitability_migration.js +81 -35
  47. package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
  48. package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
  49. package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
  50. package/calculations/pnl/profitability_skew_per_stock.js +86 -31
  51. package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
  52. package/calculations/sanity/users_processed.js +24 -1
  53. package/calculations/sectors/historical/diversification_pnl.js +104 -42
  54. package/calculations/sectors/historical/sector_rotation.js +94 -45
  55. package/calculations/sectors/total_long_per_sector.js +55 -20
  56. package/calculations/sectors/total_short_per_sector.js +55 -20
  57. package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
  58. package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
  59. package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
  60. package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
  61. package/calculations/short_and_long_stats/total_long_figures.js +34 -13
  62. package/calculations/short_and_long_stats/total_short_figures.js +34 -14
  63. package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
  64. package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
  65. package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
  66. package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
  67. package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
  68. package/calculations/socialPosts/social_activity_aggregation.js +106 -77
  69. package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
  70. package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
  71. package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
  72. package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
  73. package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
  74. package/calculations/speculators/historical/risk_appetite_change.js +89 -32
  75. package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
  76. package/calculations/speculators/holding_duration_per_asset.js +83 -23
  77. package/calculations/speculators/leverage_per_asset.js +68 -19
  78. package/calculations/speculators/leverage_per_sector.js +86 -25
  79. package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
  80. package/calculations/speculators/speculator_asset_sentiment.js +100 -48
  81. package/calculations/speculators/speculator_danger_zone.js +101 -33
  82. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
  83. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
  84. package/calculations/speculators/stop_loss_per_asset.js +94 -26
  85. package/calculations/speculators/take_profit_per_asset.js +95 -27
  86. package/calculations/speculators/tsl_per_asset.js +77 -23
  87. package/package.json +1 -1
  88. package/utils/price_data_provider.js +142 -142
  89. package/utils/sector_mapping_provider.js +74 -74
@@ -1,61 +1,126 @@
1
1
  /**
2
- * @fileoverview Calculates the total number of positions 'bought' vs 'sold' based on daily owner delta.
3
- * Uses the 'daily_instrument_insights' collection (truth of source).
4
- * 'Bought' = sum of positive deltas, 'Sold' = sum of absolute negative deltas.
2
+ * @fileoverview Calculation (Pass 2) for daily bought vs. sold count.
3
+ *
4
+ * This metric tracks the total number of positions 'bought' (new)
5
+ * and 'sold' (closed) today, based on daily ownership change.
6
+ *
7
+ * This is different from 'daily_asset_activity' because it counts
8
+ * *positions*, not *unique users*.
5
9
  */
10
+ const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
6
11
 
7
12
  class DailyBoughtVsSoldCount {
8
13
  constructor() {
9
- this.totalBought = 0;
10
- this.totalSold = 0;
11
- this.processedDay = false; // Flag to run logic only once per day
14
+ // We will store { [instrumentId]: { new: 0, closed: 0 } }
15
+ this.assetActivity = new Map();
16
+ this.mappings = null;
12
17
  }
13
18
 
14
- // Needs insights from today and yesterday
15
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
16
- if (this.processedDay) return;
17
- this.processedDay = true;
19
+ /**
20
+ * Defines the output schema for this calculation.
21
+ * @returns {object} JSON Schema object
22
+ */
23
+ static getSchema() {
24
+ const tickerSchema = {
25
+ "type": "object",
26
+ "description": "Daily trade activity for a specific asset.",
27
+ "properties": {
28
+ "positions_bought": {
29
+ "type": "number",
30
+ "description": "Total new positions opened for this asset."
31
+ },
32
+ "positions_sold": {
33
+ "type": "number",
34
+ "description": "Total positions closed for this asset."
35
+ },
36
+ "net_change": {
37
+ "type": "number",
38
+ "description": "Net change in positions (bought - sold)."
39
+ }
40
+ },
41
+ "required": ["positions_bought", "positions_sold", "net_change"]
42
+ };
18
43
 
19
- if (!todayInsights || !todayInsights.insights || !yesterdayInsights || !yesterdayInsights.insights) {
20
- console.warn('[DailyBoughtVsSoldCount] Missing insights data for today or yesterday.');
21
- return;
44
+ return {
45
+ "type": "object",
46
+ "description": "Tracks new positions (bought) and closed positions (sold) per asset.",
47
+ "patternProperties": {
48
+ "^.*$": tickerSchema // Matches any string key (ticker)
49
+ },
50
+ "additionalProperties": tickerSchema
51
+ };
52
+ }
53
+
54
+ _initAsset(instrumentId) {
55
+ if (!this.assetActivity.has(instrumentId)) {
56
+ this.assetActivity.set(instrumentId, {
57
+ new: 0,
58
+ closed: 0
59
+ });
60
+ }
61
+ }
62
+
63
+ _getInstrumentIds(portfolio) {
64
+ // This MUST use PositionID to be accurate
65
+ const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
66
+ if (!positions || !Array.isArray(positions)) {
67
+ return new Map();
22
68
  }
69
+ // Map<PositionID, InstrumentID>
70
+ return new Map(positions.map(p => [p.PositionID, p.InstrumentID]).filter(p => p[0] && p[1]));
71
+ }
23
72
 
24
- const yesterdayTotals = new Map(yesterdayInsights.insights.map(i => [i.instrumentId, i.total]));
73
+ process(todayPortfolio, yesterdayPortfolio) {
74
+ if (!todayPortfolio || !yesterdayPortfolio) {
75
+ return;
76
+ }
25
77
 
26
- for (const instrument of todayInsights.insights) {
27
- const instrumentId = instrument.instrumentId;
28
- const todayTotal = instrument.total || 0;
29
- const yesterdayTotal = yesterdayTotals.get(instrumentId) || 0;
78
+ const yPosMap = this._getInstrumentIds(yesterdayPortfolio); // Map<PosID, InstID>
79
+ const tPosMap = this._getInstrumentIds(todayPortfolio); // Map<PosID, InstID>
30
80
 
31
- const delta = todayTotal - yesterdayTotal;
81
+ // Find new positions (in today but not yesterday)
82
+ for (const [tPosId, tInstId] of tPosMap.entries()) {
83
+ if (!yPosMap.has(tPosId)) {
84
+ this._initAsset(tInstId);
85
+ this.assetActivity.get(tInstId).new++;
86
+ }
87
+ }
32
88
 
33
- if (delta > 0) {
34
- this.totalBought += delta;
35
- } else if (delta < 0) {
36
- this.totalSold += Math.abs(delta); // Sum of absolute decreases
89
+ // Find closed positions (in yesterday but not today)
90
+ for (const [yPosId, yInstId] of yPosMap.entries()) {
91
+ if (!tPosMap.has(yPosId)) {
92
+ this._initAsset(yInstId);
93
+ this.assetActivity.get(yInstId).closed++;
37
94
  }
38
95
  }
39
96
  }
40
97
 
41
- getResult() {
42
- // --- ADD THIS CHECK ---
43
- if (!this.processedDay) {
44
- console.warn('[DailyBoughtVsSoldCount] getResult called but process never ran (missing insights). Returning null for backfill.');
45
- return null;
98
+ async getResult() {
99
+ if (!this.mappings) {
100
+ this.mappings = await loadInstrumentMappings();
46
101
  }
47
- // --- END ADD ---
48
102
 
49
- return {
50
- total_positions_bought_delta: this.totalBought,
51
- total_positions_sold_delta: this.totalSold
52
- };
103
+ const result = {};
104
+ for (const [instrumentId, data] of this.assetActivity.entries()) {
105
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
106
+
107
+ const openCount = data.new;
108
+ const closeCount = data.closed;
109
+
110
+ if (openCount > 0 || closeCount > 0) {
111
+ result[ticker] = {
112
+ positions_bought: openCount,
113
+ positions_sold: closeCount,
114
+ net_change: openCount - closeCount
115
+ };
116
+ }
117
+ }
118
+ return result;
53
119
  }
54
120
 
55
121
  reset() {
56
- this.totalBought = 0;
57
- this.totalSold = 0;
58
- this.processedDay = false;
122
+ this.assetActivity.clear();
123
+ this.mappings = null;
59
124
  }
60
125
  }
61
126
 
@@ -1,60 +1,123 @@
1
1
  /**
2
- * @fileoverview Calculates the daily change (delta) in total owners for each instrument.
3
- * Uses the 'daily_instrument_insights' collection (truth of source).
2
+ * @fileoverview Calculation (Pass 2) for daily ownership delta.
3
+ *
4
+ * This metric calculates the daily change in the total number of *owners*
5
+ * (unique users) for each instrument.
6
+ *
7
+ * This measures the broadening or narrowing of an asset's holder base.
4
8
  */
5
9
  const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
6
10
 
7
11
  class DailyOwnershipDelta {
8
12
  constructor() {
9
- this.deltaByTicker = {};
13
+ // We will store { [instrumentId]: { owners_yesterday: Set(), owners_today: Set() } }
14
+ this.assetOwnership = new Map();
10
15
  this.mappings = null;
11
16
  }
12
17
 
13
- // This calculation only needs insights data, not portfolio data.
14
- // It runs daily but needs 'yesterday's insights to calculate the delta.
15
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
16
- // We only need to run the core logic once per day, not per user.
17
- // Use a flag to ensure it runs only on the first user processed for the day.
18
- if (this.processedDay) return;
19
- this.processedDay = true; // Set flag
18
+ /**
19
+ * Defines the output schema for this calculation.
20
+ * @returns {object} JSON Schema object
21
+ */
22
+ static getSchema() {
23
+ const tickerSchema = {
24
+ "type": "object",
25
+ "description": "Daily change in unique owners for a specific asset.",
26
+ "properties": {
27
+ "owners_today": {
28
+ "type": "number",
29
+ "description": "Total unique users holding this asset today."
30
+ },
31
+ "owners_yesterday": {
32
+ "type": "number",
33
+ "description": "Total unique users holding this asset yesterday."
34
+ },
35
+ "owner_delta": {
36
+ "type": "number",
37
+ "description": "The net change in unique owners (today - yesterday)."
38
+ },
39
+ "owner_delta_percent": {
40
+ "type": ["number", "null"],
41
+ "description": "Percentage change in unique owners. Null if yesterday had 0 owners."
42
+ }
43
+ },
44
+ "required": ["owners_today", "owners_yesterday", "owner_delta", "owner_delta_percent"]
45
+ };
20
46
 
21
- if (!todayInsights || !todayInsights.insights || !yesterdayInsights || !yesterdayInsights.insights) {
22
- console.warn('[DailyOwnershipDelta] Missing insights data for today or yesterday.');
23
- return;
47
+ return {
48
+ "type": "object",
49
+ "description": "Calculates the daily change in the total number of unique owners for each asset.",
50
+ "patternProperties": {
51
+ "^.*$": tickerSchema // Matches any string key (ticker)
52
+ },
53
+ "additionalProperties": tickerSchema
54
+ };
55
+ }
56
+
57
+ _initAsset(instrumentId) {
58
+ if (!this.assetOwnership.has(instrumentId)) {
59
+ this.assetOwnership.set(instrumentId, {
60
+ owners_yesterday: new Set(),
61
+ owners_today: new Set()
62
+ });
24
63
  }
64
+ }
25
65
 
26
- if (!this.mappings) {
27
- this.mappings = await loadInstrumentMappings();
66
+ _getInstrumentIds(portfolio) {
67
+ const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
68
+ if (!positions || !Array.isArray(positions)) {
69
+ return new Set();
28
70
  }
71
+ return new Set(positions.map(p => p.InstrumentID).filter(Boolean));
72
+ }
29
73
 
30
- const yesterdayTotals = new Map(yesterdayInsights.insights.map(i => [i.instrumentId, i.total]));
74
+ process(todayPortfolio, yesterdayPortfolio, userId) {
75
+ if (!todayPortfolio || !yesterdayPortfolio) {
76
+ return;
77
+ }
31
78
 
32
- for (const instrument of todayInsights.insights) {
33
- const instrumentId = instrument.instrumentId;
34
- const todayTotal = instrument.total || 0;
35
- const yesterdayTotal = yesterdayTotals.get(instrumentId) || 0; // Default to 0 if not found yesterday
79
+ const yIds = this._getInstrumentIds(yesterdayPortfolio);
80
+ const tIds = this._getInstrumentIds(todayPortfolio);
36
81
 
37
- const delta = todayTotal - yesterdayTotal;
38
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
82
+ // Add user to yesterday's owner sets
83
+ for (const yId of yIds) {
84
+ this._initAsset(yId);
85
+ this.assetOwnership.get(yId).owners_yesterday.add(userId);
86
+ }
39
87
 
40
- this.deltaByTicker[ticker] = delta;
88
+ // Add user to today's owner sets
89
+ for (const tId of tIds) {
90
+ this._initAsset(tId);
91
+ this.assetOwnership.get(tId).owners_today.add(userId);
41
92
  }
42
93
  }
43
94
 
44
- getResult() {
45
- // --- ADD THIS CHECK ---
46
- if (!this.processedDay) {
47
- console.warn('[DailyOwnershipDelta] getResult called but process never ran (missing insights). Returning null for backfill.');
48
- return null;
95
+ async getResult() {
96
+ if (!this.mappings) {
97
+ this.mappings = await loadInstrumentMappings();
49
98
  }
50
- // --- END ADD ---
51
99
 
52
- return this.deltaByTicker;
100
+ const result = {};
101
+ for (const [instrumentId, data] of this.assetOwnership.entries()) {
102
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
103
+
104
+ const yOwners = data.owners_yesterday.size;
105
+ const tOwners = data.owners_today.size;
106
+
107
+ if (yOwners > 0 || tOwners > 0) {
108
+ result[ticker] = {
109
+ owners_today: tOwners,
110
+ owners_yesterday: yOwners,
111
+ owner_delta: tOwners - yOwners,
112
+ owner_delta_percent: (yOwners > 0) ? ((tOwners - yOwners) / yOwners) * 100 : null
113
+ };
114
+ }
115
+ }
116
+ return result;
53
117
  }
54
118
 
55
119
  reset() {
56
- this.deltaByTicker = {};
57
- this.processedDay = false; // Reset the flag for the next day
120
+ this.assetOwnership.clear();
58
121
  this.mappings = null;
59
122
  }
60
123
  }
@@ -1,140 +1,108 @@
1
1
  /**
2
- * @fileoverview Meta-calculation (Pass 2) that analyzes "what" the crowd does
3
- * following a net deposit signal.
2
+ * @fileoverview Calculation (Pass 3) for capital deployment strategy.
4
3
  *
5
- * --- META REFACTOR (v2) ---
6
- * This calc fetches *today's* dependencies from the runner, but still
7
- * performs its own historical lookback for the *signal*.
4
+ * This metric answers: "Following a net deposit event, does the crowd
5
+ * tend to deploy new capital into new assets or add to existing ones?"
6
+ *
7
+ * It *depends* on 'crowd-cash-flow-proxy', 'new_allocation_percentage',
8
+ * and 'reallocation_increase_percentage'.
8
9
  */
9
-
10
- const { FieldValue } = require('@google-cloud/firestore');
11
-
12
10
  class CapitalDeploymentStrategy {
11
+ constructor() {
12
+ // This class only aggregates results from other calculations.
13
+ // No per-user processing is needed.
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": "Analyzes if the crowd deploys new capital into new or existing assets, triggered by a net deposit event.",
24
+ "properties": {
25
+ "is_net_deposit_day": {
26
+ "type": "boolean",
27
+ "description": "True if today was a net deposit day for the crowd."
28
+ },
29
+ "net_cash_flow_proxy": {
30
+ "type": "number",
31
+ "description": "The estimated net cash flow (from 'crowd-cash-flow-proxy')."
32
+ },
33
+ "total_deployed_to_new_positions_pct": {
34
+ "type": "number",
35
+ "description": "The total % portfolio allocation deployed to new positions (from 'new_allocation_percentage')."
36
+ },
37
+ "total_deployed_to_existing_positions_pct": {
38
+ "type": "number",
39
+ "description": "The total % portfolio allocation added to existing positions (from 'reallocation_increase_percentage')."
40
+ },
41
+ "deployment_ratio_new_vs_existing": {
42
+ "type": ["number", "null"],
43
+ "description": "Ratio of (New / Existing) deployment. Null if no deployment to existing positions."
44
+ }
45
+ },
46
+ "required": ["is_net_deposit_day", "net_cash_flow_proxy", "total_deployed_to_new_positions_pct", "total_deployed_to_existing_positions_pct", "deployment_ratio_new_vs_existing"]
47
+ };
48
+ }
13
49
 
14
50
  /**
15
- * (NEW) Statically declare dependencies.
51
+ * Statically declare dependencies.
16
52
  */
17
53
  static getDependencies() {
18
54
  return [
19
- 'crowd-cash-flow-proxy',
20
- 'new-allocation-percentage',
21
- 'reallocation-increase-percentage'
55
+ 'crowd-cash-flow-proxy',
56
+ 'new_allocation_percentage',
57
+ 'reallocation_increase_percentage'
22
58
  ];
23
59
  }
24
-
25
- constructor() {
26
- this.lookbackDays = 7;
27
- this.correlationWindow = 3; // How many days after a signal to link behavior
28
- this.depositSignalThreshold = -0.005;
29
- }
30
60
 
31
- _getDateStr(baseDate, daysAgo) {
32
- const date = new Date(baseDate + 'T00:00:00Z');
33
- date.setUTCDate(date.getUTCDate() - daysAgo);
34
- return date.toISOString().slice(0, 10);
61
+ /**
62
+ * process() is a no-op. All logic is in getResult().
63
+ */
64
+ process() {
65
+ // No-op
35
66
  }
36
67
 
37
68
  /**
38
- * REFACTORED PROCESS METHOD
39
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
40
- * @param {object} dependencies The shared dependencies (db, logger).
41
- * @param {object} config The computation system configuration.
42
- * @param {object} fetchedDependencies In-memory results from previous passes.
43
- * @returns {Promise<object|null>} The analysis result or null.
69
+ * Aggregates results from dependencies.
44
70
  */
45
- async process(dateStr, dependencies, config, fetchedDependencies) {
46
- const { db, logger } = dependencies;
47
- const collection = config.resultsCollection;
48
- const resultsSub = config.resultsSubcollection || 'results';
49
- const compsSub = config.computationsSubcollection || 'computations';
50
-
51
- // 1. Find the most recent deposit signal (Still requires historical lookback)
52
- let depositSignal = null;
53
- let depositSignalDay = null;
54
- let refsToGet = [];
71
+ getResult(fetchedDependencies) {
72
+ const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
73
+ const newAllocData = fetchedDependencies['new_allocation_percentage'];
74
+ const reallocData = fetchedDependencies['reallocation_increase_percentage'];
55
75
 
56
- for (let i = 1; i <= this.lookbackDays; i++) {
57
- const checkDate = this._getDateStr(dateStr, i);
58
- refsToGet.push({
59
- date: checkDate,
60
- key: `signal_${checkDate}`,
61
- ref: db.collection(collection).doc(checkDate).collection(resultsSub).doc('capital_flow').collection(compsSub).doc('crowd-cash-flow-proxy')
62
- });
63
- }
64
-
65
- const signalSnapshots = await db.getAll(...refsToGet.map(r => r.ref));
66
- const dataMap = new Map();
67
- signalSnapshots.forEach((snap, idx) => {
68
- if (snap.exists) dataMap.set(refsToGet[idx].key, snap.data());
69
- });
70
-
71
- for (let i = 1; i <= this.lookbackDays; i++) {
72
- const checkDate = this._getDateStr(dateStr, i);
73
- const flowData = dataMap.get(`signal_${checkDate}`);
74
- if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
75
- depositSignal = flowData;
76
- depositSignalDay = checkDate;
77
- break; // Found the most recent signal
78
- }
79
- }
80
-
81
- if (!depositSignal) {
82
- return {
83
- status: 'no_deposit_signal_found',
84
- lookback_days: this.lookbackDays
85
- };
86
- }
87
-
88
- // 2. Check if today is within the correlation window
89
- const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
90
-
91
- if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
76
+ if (!cashFlowData || !newAllocData || !reallocData) {
77
+ // Return a default "non-event" state if dependencies are missing
92
78
  return {
93
- status: 'outside_correlation_window',
94
- signal_day: depositSignalDay,
95
- days_since_signal: daysSinceSignal
79
+ is_net_deposit_day: false,
80
+ net_cash_flow_proxy: 0,
81
+ total_deployed_to_new_positions_pct: 0,
82
+ total_deployed_to_existing_positions_pct: 0,
83
+ deployment_ratio_new_vs_existing: null
96
84
  };
97
85
  }
98
86
 
99
- // 3. Fetch deployment data for *today* FROM `fetchedDependencies`
100
- const newAllocData = fetchedDependencies['new-allocation-percentage'];
101
- const reAllocData = fetchedDependencies['reallocation-increase-percentage'];
102
-
103
- // 4. Handle missing dependencies
104
- if (!newAllocData || !reAllocData) {
105
- logger.log('WARN', `[CapitalDeploymentStrategy] Missing in-memory deployment data for ${dateStr}. Skipping.`);
106
- return null;
107
- }
108
-
109
- // 5. Calculate deployment bias
110
- const newAlloc = newAllocData.average_new_allocation_percentage || 0;
111
- const reAlloc = reAllocData.average_reallocation_increase_percentage || 0;
112
-
113
- const totalDeployment = newAlloc + reAlloc;
114
- let newAssetBias = 0;
115
- let existingAssetBias = 0;
87
+ const netCashFlow = cashFlowData.net_cash_flow_proxy || 0;
88
+ const isNetDepositDay = netCashFlow > 0;
116
89
 
117
- if (totalDeployment > 0) {
118
- newAssetBias = (newAlloc / totalDeployment) * 100;
119
- existingAssetBias = (reAlloc / totalDeployment) * 100;
120
- }
90
+ // We only report deployment stats IF it was a deposit day.
91
+ const newAllocSum = isNetDepositDay ? newAllocData.total_new_allocation_pct_sum : 0;
92
+ const reallocSum = isNetDepositDay ? reallocData.total_reallocation_increase_pct_sum : 0;
121
93
 
122
94
  return {
123
- status: 'analysis_complete',
124
- analysis_date: dateStr,
125
- signal_date: depositSignalDay,
126
- days_since_signal: daysSinceSignal,
127
- signal_deposit_proxy_pct: Math.abs(depositSignal.cash_flow_effect_proxy),
128
- deployment_new_alloc_pct: newAlloc,
129
- deployment_existing_alloc_pct: reAlloc,
130
- total_deployment_pct: totalDeployment,
131
- new_asset_bias: newAssetBias,
132
- existing_asset_bias: existingAssetBias
95
+ is_net_deposit_day: isNetDepositDay,
96
+ net_cash_flow_proxy: netCashFlow,
97
+ total_deployed_to_new_positions_pct: newAllocSum,
98
+ total_deployed_to_existing_positions_pct: reallocSum,
99
+ deployment_ratio_new_vs_existing: (reallocSum > 0) ? (newAllocSum / reallocSum) : null
133
100
  };
134
101
  }
135
102
 
136
- async getResult() { return null; }
137
- reset() {}
103
+ reset() {
104
+ // No state to reset
105
+ }
138
106
  }
139
107
 
140
108
  module.exports = CapitalDeploymentStrategy;