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,136 +1,139 @@
1
1
  /**
2
- * @fileoverview Meta-calculation (Pass 3) that tracks the performance
3
- * of assets that were heavily liquidated (sold) by the crowd to fund
4
- * a withdrawal event.
2
+ * @fileoverview Calculation (Pass 4) for capital liquidation performance.
5
3
  *
6
- * --- META REFACTOR (v2) ---
7
- * This calculation is now stateless. It declares its dependencies and
8
- * expects them to be passed to its `process` method.
9
- * It still loads its own price data.
4
+ * This metric answers: "When users liquidate assets (net withdrawal),
5
+ * do they tend to sell winners or losers?"
6
+ *
7
+ * It *depends* on 'cash-flow-liquidation' (to get the list of
8
+ * liquidated assets) and 'asset_pnl_status' (to check the P&L
9
+ * status of those assets).
10
10
  */
11
-
12
11
  class CapitalLiquidationPerformance {
13
-
14
- /**
15
- * (NEW) Statically declare dependencies.
16
- */
17
- static getDependencies() {
18
- return ['cash-flow-liquidation'];
19
- }
20
-
21
12
  constructor() {
22
- this.PERFORMANCE_WINDOW_DAYS = 7;
23
- this.dependenciesLoaded = false;
24
- this.priceMap = null;
25
- this.tickerToIdMap = null;
26
- }
27
-
28
- async _loadDependencies(calculationUtils) {
29
- if (this.dependenciesLoaded) return;
30
- const { loadAllPriceData, loadInstrumentMappings } = calculationUtils;
31
- const [priceData, mappings] = await Promise.all([
32
- loadAllPriceData(),
33
- loadInstrumentMappings()
34
- ]);
35
- this.priceMap = priceData;
36
- this.tickerToIdMap = {};
37
- if (mappings && mappings.instrumentToTicker) {
38
- for (const [id, ticker] of Object.entries(mappings.instrumentToTicker)) {
39
- this.tickerToIdMap[ticker] = id;
40
- }
41
- }
42
- this.dependenciesLoaded = true;
43
- }
44
-
45
- _getDateStr(baseDateStr, daysOffset) {
46
- const date = new Date(baseDateStr + 'T00:00:00Z');
47
- date.setUTCDate(date.getUTCDate() + daysOffset);
48
- return date.toISOString().slice(0, 10);
13
+ // No per-user processing needed
49
14
  }
50
15
 
51
- _calculateBasketPerformance(assets, startDateStr, endDateStr) {
52
- if (!assets || assets.length === 0) return 0;
53
- let totalReturn = 0;
54
- let validAssets = 0;
55
- for (const asset of assets) {
56
- const ticker = asset.ticker;
57
- const instrumentId = this.tickerToIdMap[ticker];
58
- if (!instrumentId || !this.priceMap[instrumentId]) continue;
59
- const startPrice = this.priceMap[instrumentId][startDateStr];
60
- const endPrice = this.priceMap[instrumentId][endDateStr];
61
- if (startPrice && endPrice && startPrice > 0) {
62
- const assetReturn = (endPrice - startPrice) / startPrice;
63
- totalReturn += assetReturn;
64
- validAssets++;
65
- }
66
- }
67
- if (validAssets === 0) return 0;
68
- return (totalReturn / validAssets) * 100;
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 users sell winners or losers during net withdrawal events.",
24
+ "properties": {
25
+ "is_net_withdrawal_day": {
26
+ "type": "boolean",
27
+ "description": "True if today was a net withdrawal day."
28
+ },
29
+ "total_assets_liquidated": {
30
+ "type": "number",
31
+ "description": "The number of assets that saw net liquidation."
32
+ },
33
+ "assets_sold_at_profit_count": {
34
+ "type": "number",
35
+ "description": "Count of liquidated assets where the majority of users were in profit."
36
+ },
37
+ "assets_sold_at_loss_count": {
38
+ "type": "number",
39
+ "description": "Count of liquidated assets where the majority of users were in loss."
40
+ },
41
+ "winner_loser_sell_ratio": {
42
+ "type": ["number", "null"],
43
+ "description": "Ratio of (Assets Sold at Profit / Assets Sold at Loss). Null if no assets sold at loss."
44
+ },
45
+ "details": {
46
+ "type": "array",
47
+ "description": "List of liquidated assets and their P&L status.",
48
+ "items": {
49
+ "type": "object",
50
+ "properties": {
51
+ "ticker": { "type": "string" },
52
+ "net_flow_contribution": { "type": "number" },
53
+ "status": { "type": "string", "enum": ["profit", "loss", "neutral"] },
54
+ "profit_ratio": { "type": "number" }
55
+ },
56
+ "required": ["ticker", "net_flow_contribution", "status", "profit_ratio"]
57
+ }
58
+ }
59
+ },
60
+ "required": ["is_net_withdrawal_day", "total_assets_liquidated", "assets_sold_at_profit_count", "assets_sold_at_loss_count", "winner_loser_sell_ratio", "details"]
61
+ };
69
62
  }
70
63
 
71
64
  /**
72
- * REFACTORED PROCESS METHOD
73
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
74
- * @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
75
- * @param {object} config The computation system configuration.
76
- * @param {object} fetchedDependencies In-memory results from previous passes.
77
- * @returns {Promise<object|null>} The analysis result or null.
65
+ * Statically declare dependencies.
78
66
  */
79
- async process(dateStr, dependencies, config, fetchedDependencies) {
80
- const { db, logger, calculationUtils } = dependencies;
67
+ static getDependencies() {
68
+ return [
69
+ 'cash-flow-liquidation', // Pass 3
70
+ 'asset_pnl_status' // Pass 1
71
+ ];
72
+ }
81
73
 
82
- // 1. Load all price/mapping data
83
- await this._loadDependencies(calculationUtils);
74
+ process() {
75
+ // No-op
76
+ }
84
77
 
85
- // 2. Get dependency from `fetchedDependencies`
86
- const data = fetchedDependencies['cash-flow-liquidation'];
78
+ getResult(fetchedDependencies) {
79
+ const liquidationData = fetchedDependencies['cash-flow-liquidation'];
80
+ const pnlStatusData = fetchedDependencies['asset_pnl_status'];
81
+
82
+ const defaults = {
83
+ is_net_withdrawal_day: false,
84
+ total_assets_liquidated: 0,
85
+ assets_sold_at_profit_count: 0,
86
+ assets_sold_at_loss_count: 0,
87
+ winner_loser_sell_ratio: null,
88
+ details: []
89
+ };
87
90
 
88
- if (!data || data.status !== 'analysis_complete') {
89
- logger.log('WARN', `[CapitalLiquidation] Skipping ${dateStr}, no valid 'cash-flow-liquidation' data found.`);
90
- return null;
91
+ if (!liquidationData || !pnlStatusData || !liquidationData.is_net_withdrawal_day) {
92
+ return defaults;
91
93
  }
92
-
93
- const topAssets = data.top_liquidation_assets; // [{ ticker: 'MSFT', ... }]
94
- const signalDate = data.signal_date;
95
- const liquidationDate = data.analysis_date;
96
94
 
97
- if (!topAssets || topAssets.length === 0) {
98
- logger.log('INFO', `[CapitalLiquidation] No top assets liquidated on ${dateStr}.`);
99
- return { status: 'no_liquidation' };
95
+ let profitCount = 0;
96
+ let lossCount = 0;
97
+ const details = [];
98
+
99
+ for (const asset of liquidationData.asset_flow_details) {
100
+ // We only care about assets that were *sold* (negative flow)
101
+ if (asset.net_flow_contribution < 0) {
102
+ const ticker = asset.ticker;
103
+ const pnlInfo = pnlStatusData[ticker];
104
+
105
+ if (pnlInfo) {
106
+ let status = 'neutral';
107
+ if (pnlInfo.profit_ratio > 50) {
108
+ status = 'profit';
109
+ profitCount++;
110
+ } else if (pnlInfo.profit_ratio < 50) {
111
+ status = 'loss';
112
+ lossCount++;
113
+ }
114
+ details.push({
115
+ ticker: ticker,
116
+ net_flow_contribution: asset.net_flow_contribution,
117
+ status: status,
118
+ profit_ratio: pnlInfo.profit_ratio
119
+ });
120
+ }
121
+ }
100
122
  }
101
123
 
102
- // 3. Define performance windows
103
- const preSignalStart = this._getDateStr(signalDate, -this.PERFORMANCE_WINDOW_DAYS);
104
- const preSignalEnd = signalDate;
105
- const postLiquidationStart = liquidationDate;
106
- const postLiquidationEnd = this._getDateStr(liquidationDate, this.PERFORMANCE_WINDOW_DAYS);
107
-
108
- // 4. Calculate performance
109
- const preSignalReturnPct = this._calculateBasketPerformance(
110
- topAssets, preSignalStart, preSignalEnd
111
- );
112
-
113
- const postLiquidationReturnPct = this._calculateBasketPerformance(
114
- topAssets, postLiquidationStart, postLiquidationEnd
115
- );
116
-
117
- const crowdTimingError = postLiquidationReturnPct;
118
-
119
124
  return {
120
- status: 'analysis_complete',
121
- signal_date: signalDate,
122
- liquidation_date: liquidationDate,
123
- performance_window_days: this.PERFORMANCE_WINDOW_DAYS,
124
- liquidated_assets: topAssets.map(a => a.ticker),
125
- pre_signal_return_pct: preSignalReturnPct,
126
- post_liquidation_return_pct: postLiquidationReturnPct,
127
- crowd_timing_error: crowdTimingError,
128
- interpretation: "Measures the 7-day return of liquidated assets *after* being sold. Positive = asset recovered (bad timing). Negative = asset kept falling (good timing)."
125
+ is_net_withdrawal_day: true,
126
+ total_assets_liquidated: details.length,
127
+ assets_sold_at_profit_count: profitCount,
128
+ assets_sold_at_loss_count: lossCount,
129
+ winner_loser_sell_ratio: (lossCount > 0) ? (profitCount / lossCount) : null,
130
+ details: details
129
131
  };
130
132
  }
131
133
 
132
- async getResult() { return null; }
133
- reset() {}
134
+ reset() {
135
+ // No state
136
+ }
134
137
  }
135
138
 
136
139
  module.exports = CapitalLiquidationPerformance;
@@ -1,137 +1,144 @@
1
1
  /**
2
- * @fileoverview Correlates a crowd-wide deposit signal with the specific assets
3
- * that are being bought with that new capital.
2
+ * @fileoverview Calculation (Pass 3) for cash flow deployment.
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, what percentage
5
+ * of that new capital is deployed *today*, and which assets are
6
+ * receiving the most inflow?"
7
+ *
8
+ * It *depends* on 'crowd-cash-flow-proxy' (to know if it's a
9
+ * deposit day) and 'asset_crowd_flow' (to see where flow went).
8
10
  */
9
-
10
- const { FieldValue } = require('@google-cloud/firestore');
11
-
12
11
  class CashFlowDeployment {
13
-
14
- /**
15
- * (NEW) Statically declare dependencies.
16
- */
17
- static getDependencies() {
18
- return ['crowd-cash-flow-proxy', 'asset-crowd-flow'];
19
- }
20
-
21
12
  constructor() {
22
- this.lookbackDays = 7;
23
- this.correlationWindow = 3;
24
- this.depositSignalThreshold = -0.005; // Negative value = deposit
13
+ // No per-user processing
25
14
  }
26
15
 
27
- _getDateStr(baseDate, daysAgo) {
28
- const date = new Date(baseDate + 'T00:00:00Z');
29
- date.setUTCDate(date.getUTCDate() - daysAgo);
30
- return date.toISOString().slice(0, 10);
16
+ /**
17
+ * Defines the output schema for this calculation.
18
+ * @returns {object} JSON Schema object
19
+ */
20
+ static getSchema() {
21
+ const assetFlowSchema = {
22
+ "type": "object",
23
+ "properties": {
24
+ "ticker": { "type": "string" },
25
+ "net_flow_contribution": { "type": "number" },
26
+ "percent_of_total_inflow": { "type": "number" }
27
+ },
28
+ "required": ["ticker", "net_flow_contribution", "percent_of_total_inflow"]
29
+ };
30
+
31
+ return {
32
+ "type": "object",
33
+ "description": "On net deposit days, tracks % of new capital deployed and which assets received it.",
34
+ "properties": {
35
+ "is_net_deposit_day": {
36
+ "type": "boolean",
37
+ "description": "True if today was a net deposit day."
38
+ },
39
+ "net_cash_flow_proxy": {
40
+ "type": "number",
41
+ "description": "The total estimated net cash flow (positive)."
42
+ },
43
+ "total_net_capital_flow": {
44
+ "type": "number",
45
+ "description": "The sum of all *positive* net capital flows into assets."
46
+ },
47
+ "deployment_percentage": {
48
+ "type": ["number", "null"],
49
+ "description": "Percentage of net cash flow that was deployed (Total Net Flow / Net Cash Flow). Null if no cash flow."
50
+ },
51
+ "top_inflow_assets": {
52
+ "type": "array",
53
+ "description": "Top 5 assets receiving the most inflow.",
54
+ "items": assetFlowSchema
55
+ },
56
+ "asset_flow_details": {
57
+ "type": "array",
58
+ "description": "Full list of all assets and their inflows.",
59
+ "items": assetFlowSchema
60
+ }
61
+ },
62
+ "required": ["is_net_deposit_day", "net_cash_flow_proxy", "total_net_capital_flow", "deployment_percentage", "top_inflow_assets", "asset_flow_details"]
63
+ };
31
64
  }
32
65
 
33
66
  /**
34
- * REFACTORED PROCESS METHOD
35
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
36
- * @param {object} dependencies The shared dependencies (db, logger).
37
- * @param {object} config The computation system configuration.
38
- * @param {object} fetchedDependencies In-memory results from previous passes.
39
- * @returns {Promise<object|null>} The analysis result or null.
67
+ * Statically declare dependencies.
40
68
  */
41
- async process(dateStr, dependencies, config, fetchedDependencies) {
42
- const { db, logger } = dependencies;
43
- const collection = config.resultsCollection;
44
- const resultsSub = config.resultsSubcollection || 'results';
45
- const compsSub = config.computationsSubcollection || 'computations';
69
+ static getDependencies() {
70
+ return [
71
+ 'crowd-cash-flow-proxy', // Pass 2
72
+ 'asset_crowd_flow' // Pass 2
73
+ ];
74
+ }
46
75
 
47
- // 1. Get same-day dependencies from `fetchedDependencies`
48
- const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
49
- const assetFlowData = fetchedDependencies['asset-crowd-flow'];
76
+ process() {
77
+ // No-op
78
+ }
50
79
 
51
- if (!cashFlowData || !assetFlowData) {
52
- logger.log('WARN', `[CashFlowDeployment] Missing critical in-memory dependency data for ${dateStr}. Skipping.`);
53
- return null;
54
- }
80
+ getResult(fetchedDependencies) {
81
+ const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
82
+ const assetFlowData = fetchedDependencies['asset_crowd_flow'];
83
+
84
+ const defaults = {
85
+ is_net_deposit_day: false,
86
+ net_cash_flow_proxy: 0,
87
+ total_net_capital_flow: 0,
88
+ deployment_percentage: null,
89
+ top_inflow_assets: [],
90
+ asset_flow_details: []
91
+ };
55
92
 
56
- // 2. Historical lookback for signal (still uses Firestore)
57
- const refs = [];
58
- const dates = [];
59
- for (let i = 1; i <= this.lookbackDays; i++) {
60
- const checkDate = this._getDateStr(dateStr, i);
61
- dates.push({ date: checkDate, category: 'capital_flow', computation: 'crowd-cash-flow-proxy' });
93
+ if (!cashFlowData || !assetFlowData || cashFlowData.net_cash_flow_proxy <= 0) {
94
+ // Not a net deposit day
95
+ return defaults;
62
96
  }
63
- const histRefs = dates.map(d =>
64
- db.collection(collection).doc(d.date)
65
- .collection(resultsSub).doc(d.category)
66
- .collection(compsSub).doc(d.computation)
67
- );
68
- const snapshots = await db.getAll(...histRefs);
69
- const dataMap = new Map();
70
- snapshots.forEach((snap, idx) => {
71
- if (snap.exists) dataMap.set(idx, snap.data());
72
- });
73
97
 
74
- // 3. Find deposit signal
75
- let depositSignal = null;
76
- let depositSignalDay = null;
77
-
78
- for (let i = 0; i < this.lookbackDays; i++) {
79
- const flowData = dataMap.get(i);
80
- const dateUsed = dates[i].date;
81
- if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
82
- depositSignal = flowData;
83
- depositSignalDay = dateUsed;
84
- break;
98
+ const netCashFlow = cashFlowData.net_cash_flow_proxy;
99
+ let totalNetInflow = 0;
100
+ const allFlows = [];
101
+
102
+ for (const [ticker, data] of Object.entries(assetFlowData)) {
103
+ // We only care about *positive* flow (deployment)
104
+ if (data.net_flow_contribution > 0) {
105
+ totalNetInflow += data.net_flow_contribution;
106
+ allFlows.push({
107
+ ticker: ticker,
108
+ net_flow_contribution: data.net_flow_contribution
109
+ });
85
110
  }
86
111
  }
87
112
 
88
- if (!depositSignal) {
113
+ if (totalNetInflow === 0) {
114
+ // Net deposit day, but no positive flow detected
89
115
  return {
90
- status: 'no_signal_found',
91
- lookback_days: this.lookbackDays,
92
- signal_threshold: this.depositSignalThreshold
116
+ ...defaults,
117
+ is_net_deposit_day: true,
118
+ net_cash_flow_proxy: netCashFlow,
119
+ deployment_percentage: 0
93
120
  };
94
121
  }
95
-
96
- // 4. Check correlation window
97
- const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
98
-
99
- if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
100
- return {
101
- status: 'outside_correlation_window',
102
- signal_day: depositSignalDay,
103
- days_since_signal: daysSinceSignal
104
- };
105
- }
106
-
107
- // 5. Use the in-memory data for today's analysis
108
- const netSpendPct = cashFlowData.components?.trading_effect || 0;
109
- const netDepositPct = Math.abs(depositSignal.cash_flow_effect_proxy);
110
-
111
- // Find top *buys*
112
- const topBuys = Object.entries(assetFlowData)
113
- .filter(([ticker, data]) => data.net_crowd_flow_pct > 0) // Find positive flow
114
- .sort(([, a], [, b]) => b.net_crowd_flow_pct - a.net_crowd_flow_pct) // Sort descending
115
- .slice(0, 10)
116
- .map(([ticker, data]) => ({
117
- ticker,
118
- net_flow_pct: data.net_crowd_flow_pct
119
- }));
122
+
123
+ // Calculate percent_of_total_inflow for each
124
+ const asset_flow_details = allFlows.map(flow => ({
125
+ ...flow,
126
+ percent_of_total_inflow: (flow.net_flow_contribution / totalNetInflow) * 100
127
+ })).sort((a, b) => b.net_flow_contribution - a.net_flow_contribution);
120
128
 
121
129
  return {
122
- status: 'analysis_complete',
123
- analysis_date: dateStr,
124
- signal_date: depositSignalDay,
125
- days_since_signal: daysSinceSignal,
126
- signal_deposit_proxy_pct: netDepositPct,
127
- day_net_spend_pct: netSpendPct, // This value should be positive
128
- pct_of_deposit_deployed_today: (netSpendPct / netDepositPct) * 100,
129
- top_deployment_assets: topBuys
130
+ is_net_deposit_day: true,
131
+ net_cash_flow_proxy: netCashFlow,
132
+ total_net_capital_flow: totalNetInflow,
133
+ deployment_percentage: (netCashFlow > 0) ? (totalNetInflow / netCashFlow) * 100 : null,
134
+ top_inflow_assets: asset_flow_details.slice(0, 5),
135
+ asset_flow_details: asset_flow_details
130
136
  };
131
137
  }
132
138
 
133
- async getResult() { return null; }
134
- reset() {}
139
+ reset() {
140
+ // No state
141
+ }
135
142
  }
136
143
 
137
144
  module.exports = CashFlowDeployment;