aiden-shared-calculations-unified 1.0.12 → 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.
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
2
|
+
|
|
3
|
+
class CashFlowDeployment {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.lookbackDays = 7;
|
|
6
|
+
this.correlationWindow = 3;
|
|
7
|
+
this.depositSignalThreshold = -1.0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_getDateStr(baseDate, daysAgo) {
|
|
11
|
+
const date = new Date(baseDate + 'T00:00:00Z');
|
|
12
|
+
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
13
|
+
return date.toISOString().slice(0, 10);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async process(dateStr, dependencies, config) {
|
|
17
|
+
const { db, logger } = dependencies;
|
|
18
|
+
const collection = config.resultsCollection;
|
|
19
|
+
|
|
20
|
+
// build all needed refs in advance for this day
|
|
21
|
+
const dateRefs = [];
|
|
22
|
+
const dates = [];
|
|
23
|
+
|
|
24
|
+
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
25
|
+
const checkDate = this._getDateStr(dateStr, i);
|
|
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;
|
|
55
|
+
if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
|
|
56
|
+
depositSignal = flowData;
|
|
57
|
+
depositSignalDay = dateUsed;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!depositSignal) {
|
|
63
|
+
return {
|
|
64
|
+
status: 'no_signal_found',
|
|
65
|
+
lookback_days: this.lookbackDays,
|
|
66
|
+
signal_threshold: this.depositSignalThreshold
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
|
|
71
|
+
|
|
72
|
+
if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
|
|
73
|
+
return {
|
|
74
|
+
status: 'outside_correlation_window',
|
|
75
|
+
signal_day: depositSignalDay,
|
|
76
|
+
days_since_signal: daysSinceSignal
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
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);
|
|
83
|
+
|
|
84
|
+
if (!cashFlowData || !assetFlowData) {
|
|
85
|
+
logger.log('WARN', `[CashFlowDeployment] Missing dependency data for ${dateStr}. Skipping.`);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const netSpendPct = cashFlowData.components?.trading_effect || 0;
|
|
90
|
+
const netDepositPct = Math.abs(depositSignal.cash_flow_effect_proxy);
|
|
91
|
+
|
|
92
|
+
const topBuys = Object.entries(assetFlowData)
|
|
93
|
+
.filter(([ticker, data]) => data.net_crowd_flow_pct > 0)
|
|
94
|
+
.sort(([, a], [, b]) => b.net_crowd_flow_pct - a.net_crowd_flow_pct)
|
|
95
|
+
.slice(0, 10)
|
|
96
|
+
.map(([ticker, data]) => ({
|
|
97
|
+
ticker,
|
|
98
|
+
net_flow_pct: data.net_crowd_flow_pct
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
status: 'analysis_complete',
|
|
103
|
+
analysis_date: dateStr,
|
|
104
|
+
signal_date: depositSignalDay,
|
|
105
|
+
days_since_signal: daysSinceSignal,
|
|
106
|
+
signal_deposit_proxy_pct: netDepositPct,
|
|
107
|
+
day_net_spend_pct: netSpendPct,
|
|
108
|
+
pct_of_deposit_deployed_today: (netSpendPct / netDepositPct) * 100,
|
|
109
|
+
top_deployment_assets: topBuys
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getResult() { return null; }
|
|
114
|
+
reset() {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = CashFlowDeployment;
|