aiden-shared-calculations-unified 1.0.12 → 1.0.13

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.
@@ -0,0 +1,145 @@
1
+ const { FieldValue } = require('@google-cloud/firestore');
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
+ class CashFlowDeployment {
9
+ 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
15
+ }
16
+
17
+ /**
18
+ * Helper to get a YYYY-MM-DD string for N days ago.
19
+ */
20
+ _getDateStr(baseDate, daysAgo) {
21
+ const date = new Date(baseDate + 'T00:00:00Z');
22
+ date.setUTCDate(date.getUTCDate() - daysAgo);
23
+ return date.toISOString().slice(0, 10);
24
+ }
25
+
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
+ async process(dateStr, dependencies, config) {
52
+ const { db, logger } = dependencies;
53
+ const collection = config.resultsCollection;
54
+
55
+ let depositSignal = null;
56
+ let depositSignalDay = null;
57
+
58
+ // 1. Look back for a deposit signal
59
+ for (let i = 1; i <= this.lookbackDays; i++) {
60
+ const checkDate = this._getDateStr(dateStr, i);
61
+ const flowData = await this._fetchCalc(db, collection, checkDate, 'capital_flow', 'crowd-cash-flow-proxy');
62
+
63
+ if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
64
+ depositSignal = flowData;
65
+ depositSignalDay = checkDate;
66
+ break; // Found the most recent strong signal
67
+ }
68
+ }
69
+
70
+ // If no signal was found in the lookback window, stop.
71
+ if (!depositSignal) {
72
+ return {
73
+ status: 'no_signal_found',
74
+ lookback_days: this.lookbackDays,
75
+ signal_threshold: this.depositSignalThreshold
76
+ };
77
+ }
78
+
79
+ // 2. A signal was found. Now, track the "spend" in the following days.
80
+ const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
81
+
82
+ // Only run this analysis for the N days *after* the signal
83
+ if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
84
+ return {
85
+ status: 'outside_correlation_window',
86
+ signal_day: depositSignalDay,
87
+ days_since_signal: daysSinceSignal
88
+ };
89
+ }
90
+
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
+ ]);
96
+
97
+ if (!cashFlowData || !assetFlowData) {
98
+ logger.log('WARN', `[CashFlowDeployment] Missing dependency data for ${dateStr}. Skipping.`);
99
+ return null; // Missing data, can't run
100
+ }
101
+
102
+ // This is the "spend" (net move from cash to assets) on this day
103
+ const netSpendPct = cashFlowData.components?.trading_effect || 0;
104
+
105
+ // This is the total deposit signal
106
+ const netDepositPct = Math.abs(depositSignal.cash_flow_effect_proxy);
107
+
108
+ // Filter asset flow to find the top 10 "buys"
109
+ const topBuys = Object.entries(assetFlowData)
110
+ .filter(([ticker, data]) => data.net_crowd_flow_pct > 0)
111
+ .sort(([, a], [, b]) => b.net_crowd_flow_pct - a.net_crowd_flow_pct)
112
+ .slice(0, 10)
113
+ .map(([ticker, data]) => ({
114
+ ticker: ticker,
115
+ net_flow_pct: data.net_crowd_flow_pct
116
+ }));
117
+
118
+ // 4. Return the final result
119
+ return {
120
+ status: 'analysis_complete',
121
+ analysis_date: dateStr,
122
+ signal_date: depositSignalDay,
123
+ days_since_signal: daysSinceSignal,
124
+ signal_deposit_proxy_pct: netDepositPct,
125
+ day_net_spend_pct: netSpendPct,
126
+ // Calculate what percentage of the *total* deposit was spent *today*
127
+ pct_of_deposit_deployed_today: (netSpendPct / netDepositPct) * 100,
128
+ top_deployment_assets: topBuys
129
+ };
130
+ }
131
+
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
+ }
143
+ }
144
+
145
+ module.exports = CashFlowDeployment;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [