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
|
-
|
|
11
|
-
|
|
12
|
-
this.
|
|
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
|
-
//
|
|
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
|
-
|
|
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 =
|
|
66
|
-
break;
|
|
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
|
-
//
|
|
92
|
-
const
|
|
93
|
-
|
|
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;
|
|
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
|
|
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
|
-
|
|
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;
|