aiden-shared-calculations-unified 1.0.13 → 1.0.14

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.
@@ -1,73 +1,64 @@
1
1
  const { FieldValue } = require('@google-cloud/firestore');
2
2
 
3
- /**
4
- * @fileoverview A "meta-calculation" that analyzes the results of
5
- * 'crowd-cash-flow-proxy' and 'asset-crowd-flow' to correlate
6
- * deposit events with subsequent asset purchases.
7
- */
8
3
  class CashFlowDeployment {
9
4
  constructor() {
10
- // This calculation is stateless for `process()`, but `getResult()` is not used.
11
- // All logic happens in `process()` which returns the result directly.
12
- this.lookbackDays = 7; // How many days to look back for a signal
13
- this.correlationWindow = 3; // How many days after the signal to track deployment
14
- this.depositSignalThreshold = -1.0; // A -1.0% proxy value is a strong deposit signal
5
+ this.lookbackDays = 7;
6
+ this.correlationWindow = 3;
7
+ this.depositSignalThreshold = -1.0;
15
8
  }
16
9
 
17
- /**
18
- * Helper to get a YYYY-MM-DD string for N days ago.
19
- */
20
10
  _getDateStr(baseDate, daysAgo) {
21
11
  const date = new Date(baseDate + 'T00:00:00Z');
22
12
  date.setUTCDate(date.getUTCDate() - daysAgo);
23
13
  return date.toISOString().slice(0, 10);
24
14
  }
25
15
 
26
- /**
27
- * Fetches a single calculation result from Firestore.
28
- */
29
- async _fetchCalc(db, collection, dateStr, category, computation) {
30
- try {
31
- const docRef = db.collection(collection).doc(dateStr)
32
- .collection('results').doc(category)
33
- .collection('computations').doc(computation);
34
- const doc = await docRef.get();
35
- if (!doc.exists) return null;
36
- return doc.data();
37
- } catch (e) {
38
- return null;
39
- }
40
- }
41
-
42
- /**
43
- * This special `process` method is called by the computation orchestrator *after*
44
- * all user-data-based calculations are complete.
45
- *
46
- * @param {string} dateStr - The "current" day being processed (e.g., "2025-10-30").
47
- * @param {object} dependencies - The master dependencies object containing { db, logger }.
48
- * @param {object} config - The computation system config.
49
- * @returns {Promise<object|null>} The final result object for this day, or null.
50
- */
51
16
  async process(dateStr, dependencies, config) {
52
17
  const { db, logger } = dependencies;
53
18
  const collection = config.resultsCollection;
54
-
55
- let depositSignal = null;
56
- let depositSignalDay = null;
57
19
 
58
- // 1. Look back for a deposit signal
20
+ // build all needed refs in advance for this day
21
+ const dateRefs = [];
22
+ const dates = [];
23
+
59
24
  for (let i = 1; i <= this.lookbackDays; i++) {
60
25
  const checkDate = this._getDateStr(dateStr, i);
61
- const flowData = await this._fetchCalc(db, collection, checkDate, 'capital_flow', 'crowd-cash-flow-proxy');
62
-
26
+ dates.push({ date: checkDate, category: 'capital_flow', computation: 'crowd-cash-flow-proxy' });
27
+ }
28
+
29
+ // add refs for today's 2 dependencies
30
+ dates.push({ date: dateStr, category: 'capital_flow', computation: 'crowd-cash-flow-proxy' });
31
+ dates.push({ date: dateStr, category: 'behavioural', computation: 'asset-crowd-flow' });
32
+
33
+ // build refs array
34
+ const refs = dates.map(d =>
35
+ db.collection(collection).doc(d.date)
36
+ .collection('results').doc(d.category)
37
+ .collection('computations').doc(d.computation)
38
+ );
39
+
40
+ const snapshots = await db.getAll(...refs);
41
+
42
+ // build map(path -> data)
43
+ const dataMap = new Map();
44
+ snapshots.forEach((snap, idx) => {
45
+ if (snap.exists) dataMap.set(idx, snap.data());
46
+ });
47
+
48
+ // find deposit signal
49
+ let depositSignal = null;
50
+ let depositSignalDay = null;
51
+
52
+ for (let i = 0; i < this.lookbackDays; i++) {
53
+ const flowData = dataMap.get(i);
54
+ const dateUsed = dates[i].date;
63
55
  if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
64
56
  depositSignal = flowData;
65
- depositSignalDay = checkDate;
66
- break; // Found the most recent strong signal
57
+ depositSignalDay = dateUsed;
58
+ break;
67
59
  }
68
60
  }
69
61
 
70
- // If no signal was found in the lookback window, stop.
71
62
  if (!depositSignal) {
72
63
  return {
73
64
  status: 'no_signal_found',
@@ -76,10 +67,8 @@ class CashFlowDeployment {
76
67
  };
77
68
  }
78
69
 
79
- // 2. A signal was found. Now, track the "spend" in the following days.
80
70
  const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
81
71
 
82
- // Only run this analysis for the N days *after* the signal
83
72
  if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
84
73
  return {
85
74
  status: 'outside_correlation_window',
@@ -88,34 +77,27 @@ class CashFlowDeployment {
88
77
  };
89
78
  }
90
79
 
91
- // 3. We are INSIDE the correlation window. Let's analyze the deployment.
92
- const [cashFlowData, assetFlowData] = await Promise.all([
93
- this._fetchCalc(db, collection, dateStr, 'capital_flow', 'crowd-cash-flow-proxy'),
94
- this._fetchCalc(db, collection, dateStr, 'behavioural', 'asset-crowd-flow')
95
- ]);
80
+ // today's two dependencies are the last 2 refs
81
+ const cashFlowData = dataMap.get(this.lookbackDays);
82
+ const assetFlowData = dataMap.get(this.lookbackDays + 1);
96
83
 
97
84
  if (!cashFlowData || !assetFlowData) {
98
85
  logger.log('WARN', `[CashFlowDeployment] Missing dependency data for ${dateStr}. Skipping.`);
99
- return null; // Missing data, can't run
86
+ return null;
100
87
  }
101
88
 
102
- // This is the "spend" (net move from cash to assets) on this day
103
89
  const netSpendPct = cashFlowData.components?.trading_effect || 0;
104
-
105
- // This is the total deposit signal
106
90
  const netDepositPct = Math.abs(depositSignal.cash_flow_effect_proxy);
107
91
 
108
- // Filter asset flow to find the top 10 "buys"
109
92
  const topBuys = Object.entries(assetFlowData)
110
93
  .filter(([ticker, data]) => data.net_crowd_flow_pct > 0)
111
94
  .sort(([, a], [, b]) => b.net_crowd_flow_pct - a.net_crowd_flow_pct)
112
95
  .slice(0, 10)
113
96
  .map(([ticker, data]) => ({
114
- ticker: ticker,
97
+ ticker,
115
98
  net_flow_pct: data.net_crowd_flow_pct
116
99
  }));
117
100
 
118
- // 4. Return the final result
119
101
  return {
120
102
  status: 'analysis_complete',
121
103
  analysis_date: dateStr,
@@ -123,23 +105,13 @@ class CashFlowDeployment {
123
105
  days_since_signal: daysSinceSignal,
124
106
  signal_deposit_proxy_pct: netDepositPct,
125
107
  day_net_spend_pct: netSpendPct,
126
- // Calculate what percentage of the *total* deposit was spent *today*
127
108
  pct_of_deposit_deployed_today: (netSpendPct / netDepositPct) * 100,
128
109
  top_deployment_assets: topBuys
129
110
  };
130
111
  }
131
112
 
132
- /**
133
- * This calculation is stateless per-run, so getResult is not used.
134
- * The orchestrator will call `process` and commit the result directly.
135
- */
136
- async getResult() {
137
- return null;
138
- }
139
-
140
- reset() {
141
- // Nothing to reset, as state is not carried
142
- }
113
+ async getResult() { return null; }
114
+ reset() {}
143
115
  }
144
116
 
145
117
  module.exports = CashFlowDeployment;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [