aiden-shared-calculations-unified 1.0.44 → 1.0.46
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.
- package/calculations/backtests/strategy-performance.js +39 -46
- package/calculations/behavioural/historical/dumb-cohort-flow.js +29 -29
- package/calculations/behavioural/historical/smart-cohort-flow.js +29 -29
- package/calculations/behavioural/historical/smart_money_flow.js +108 -104
- package/calculations/behavioural/historical/user-investment-profile.js +51 -95
- package/calculations/meta/capital_deployment_strategy.js +25 -15
- package/calculations/meta/capital_liquidation_performance.js +18 -4
- package/calculations/meta/capital_vintage_performance.js +18 -4
- package/calculations/meta/cash-flow-deployment.js +42 -16
- package/calculations/meta/cash-flow-liquidation.js +27 -15
- package/calculations/meta/profit_cohort_divergence.js +22 -9
- package/calculations/meta/smart-dumb-divergence-index.js +19 -9
- package/calculations/meta/social_flow_correlation.js +20 -8
- package/calculations/pnl/pnl_distribution_per_stock.js +30 -17
- package/package.json +1 -1
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass
|
|
3
|
-
* following a net deposit signal.
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 2) that analyzes "what" the crowd does
|
|
3
|
+
* following a net deposit signal.
|
|
4
|
+
*
|
|
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*.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
|
-
// Note: This calculation still needs to read *historical* data for the signal.
|
|
8
|
-
// Only same-day dependencies can be passed in-memory.
|
|
9
|
-
// A full refactor would involve the orchestrator passing historical results
|
|
10
|
-
// into the cache, but for now we leave the historical lookback.
|
|
11
|
-
|
|
12
10
|
const { FieldValue } = require('@google-cloud/firestore');
|
|
13
11
|
|
|
14
12
|
class CapitalDeploymentStrategy {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* (NEW) Statically declare dependencies.
|
|
16
|
+
*/
|
|
17
|
+
static getDependencies() {
|
|
18
|
+
return [
|
|
19
|
+
'crowd-cash-flow-proxy',
|
|
20
|
+
'new-allocation-percentage',
|
|
21
|
+
'reallocation-increase-percentage'
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
constructor() {
|
|
16
26
|
this.lookbackDays = 7;
|
|
17
27
|
this.correlationWindow = 3; // How many days after a signal to link behavior
|
|
18
|
-
this.depositSignalThreshold = -0.005;
|
|
28
|
+
this.depositSignalThreshold = -0.005;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
_getDateStr(baseDate, daysAgo) {
|
|
@@ -25,13 +35,14 @@ class CapitalDeploymentStrategy {
|
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
/**
|
|
38
|
+
* REFACTORED PROCESS METHOD
|
|
28
39
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
29
40
|
* @param {object} dependencies The shared dependencies (db, logger).
|
|
30
41
|
* @param {object} config The computation system configuration.
|
|
31
|
-
* @param {object}
|
|
42
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
32
43
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
33
44
|
*/
|
|
34
|
-
async process(dateStr, dependencies, config,
|
|
45
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
35
46
|
const { db, logger } = dependencies;
|
|
36
47
|
const collection = config.resultsCollection;
|
|
37
48
|
const resultsSub = config.resultsSubcollection || 'results';
|
|
@@ -85,9 +96,9 @@ class CapitalDeploymentStrategy {
|
|
|
85
96
|
};
|
|
86
97
|
}
|
|
87
98
|
|
|
88
|
-
// 3. Fetch deployment data for *today* FROM
|
|
89
|
-
const newAllocData =
|
|
90
|
-
const reAllocData =
|
|
99
|
+
// 3. Fetch deployment data for *today* FROM `fetchedDependencies`
|
|
100
|
+
const newAllocData = fetchedDependencies['new-allocation-percentage'];
|
|
101
|
+
const reAllocData = fetchedDependencies['reallocation-increase-percentage'];
|
|
91
102
|
|
|
92
103
|
// 4. Handle missing dependencies
|
|
93
104
|
if (!newAllocData || !reAllocData) {
|
|
@@ -122,7 +133,6 @@ class CapitalDeploymentStrategy {
|
|
|
122
133
|
};
|
|
123
134
|
}
|
|
124
135
|
|
|
125
|
-
// Must exist for the meta-computation runner
|
|
126
136
|
async getResult() { return null; }
|
|
127
137
|
reset() {}
|
|
128
138
|
}
|
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
* @fileoverview Meta-calculation (Pass 3) that tracks the performance
|
|
3
3
|
* of assets that were heavily liquidated (sold) by the crowd to fund
|
|
4
4
|
* a withdrawal event.
|
|
5
|
+
*
|
|
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.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
class CapitalLiquidationPerformance {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* (NEW) Statically declare dependencies.
|
|
16
|
+
*/
|
|
17
|
+
static getDependencies() {
|
|
18
|
+
return ['cash-flow-liquidation'];
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
constructor() {
|
|
9
22
|
this.PERFORMANCE_WINDOW_DAYS = 7;
|
|
10
23
|
this.dependenciesLoaded = false;
|
|
@@ -56,20 +69,21 @@ class CapitalLiquidationPerformance {
|
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
/**
|
|
72
|
+
* REFACTORED PROCESS METHOD
|
|
59
73
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
60
74
|
* @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
|
|
61
75
|
* @param {object} config The computation system configuration.
|
|
62
|
-
* @param {object}
|
|
76
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
63
77
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
64
78
|
*/
|
|
65
|
-
async process(dateStr, dependencies, config,
|
|
79
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
66
80
|
const { db, logger, calculationUtils } = dependencies;
|
|
67
81
|
|
|
68
82
|
// 1. Load all price/mapping data
|
|
69
83
|
await this._loadDependencies(calculationUtils);
|
|
70
84
|
|
|
71
|
-
// 2. Get dependency from
|
|
72
|
-
const data =
|
|
85
|
+
// 2. Get dependency from `fetchedDependencies`
|
|
86
|
+
const data = fetchedDependencies['cash-flow-liquidation'];
|
|
73
87
|
|
|
74
88
|
if (!data || data.status !== 'analysis_complete') {
|
|
75
89
|
logger.log('WARN', `[CapitalLiquidation] Skipping ${dateStr}, no valid 'cash-flow-liquidation' data found.`);
|
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
* @fileoverview Meta-calculation (Pass 3) that tracks the performance
|
|
3
3
|
* of capital "vintages" by analyzing the market returns of assets
|
|
4
4
|
* that were bought following a crowd-wide deposit signal.
|
|
5
|
+
*
|
|
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.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
class CapitalVintagePerformance {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* (NEW) Statically declare dependencies.
|
|
16
|
+
*/
|
|
17
|
+
static getDependencies() {
|
|
18
|
+
return ['cash-flow-deployment'];
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
constructor() {
|
|
9
22
|
this.PERFORMANCE_WINDOW_DAYS = 7;
|
|
10
23
|
this.dependenciesLoaded = false;
|
|
@@ -56,20 +69,21 @@ class CapitalVintagePerformance {
|
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
/**
|
|
72
|
+
* REFACTORED PROCESS METHOD
|
|
59
73
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
60
74
|
* @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
|
|
61
75
|
* @param {object} config The computation system configuration.
|
|
62
|
-
* @param {object}
|
|
76
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
63
77
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
64
78
|
*/
|
|
65
|
-
async process(dateStr, dependencies, config,
|
|
79
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
66
80
|
const { db, logger, calculationUtils } = dependencies;
|
|
67
81
|
|
|
68
82
|
// 1. Load all price/mapping data
|
|
69
83
|
await this._loadDependencies(calculationUtils);
|
|
70
84
|
|
|
71
|
-
// 2. Get dependency from
|
|
72
|
-
const data =
|
|
85
|
+
// 2. Get dependency from `fetchedDependencies`
|
|
86
|
+
const data = fetchedDependencies['cash-flow-deployment'];
|
|
73
87
|
|
|
74
88
|
if (!data || data.status !== 'analysis_complete') {
|
|
75
89
|
logger.log('WARN', `[CapitalVintage] Skipping ${dateStr}, no valid 'cash-flow-deployment' data found.`);
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Correlates a crowd-wide deposit signal with the specific assets
|
|
3
|
+
* that are being bought with that new capital.
|
|
4
|
+
*
|
|
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*.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const { FieldValue } = require('@google-cloud/firestore');
|
|
2
11
|
|
|
3
12
|
class CashFlowDeployment {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* (NEW) Statically declare dependencies.
|
|
16
|
+
*/
|
|
17
|
+
static getDependencies() {
|
|
18
|
+
return ['crowd-cash-flow-proxy', 'asset-crowd-flow'];
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
constructor() {
|
|
5
22
|
this.lookbackDays = 7;
|
|
6
23
|
this.correlationWindow = 3;
|
|
7
|
-
this.depositSignalThreshold = -0.005; //
|
|
24
|
+
this.depositSignalThreshold = -0.005; // Negative value = deposit
|
|
8
25
|
}
|
|
9
26
|
|
|
10
27
|
_getDateStr(baseDate, daysAgo) {
|
|
@@ -13,21 +30,30 @@ class CashFlowDeployment {
|
|
|
13
30
|
return date.toISOString().slice(0, 10);
|
|
14
31
|
}
|
|
15
32
|
|
|
16
|
-
|
|
33
|
+
/**
|
|
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.
|
|
40
|
+
*/
|
|
41
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
17
42
|
const { db, logger } = dependencies;
|
|
18
43
|
const collection = config.resultsCollection;
|
|
44
|
+
const resultsSub = config.resultsSubcollection || 'results';
|
|
45
|
+
const compsSub = config.computationsSubcollection || 'computations';
|
|
19
46
|
|
|
20
|
-
//
|
|
21
|
-
const cashFlowData =
|
|
22
|
-
const assetFlowData =
|
|
47
|
+
// 1. Get same-day dependencies from `fetchedDependencies`
|
|
48
|
+
const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
|
|
49
|
+
const assetFlowData = fetchedDependencies['asset-crowd-flow'];
|
|
23
50
|
|
|
24
51
|
if (!cashFlowData || !assetFlowData) {
|
|
25
52
|
logger.log('WARN', `[CashFlowDeployment] Missing critical in-memory dependency data for ${dateStr}. Skipping.`);
|
|
26
53
|
return null;
|
|
27
54
|
}
|
|
28
|
-
// --- END MODIFICATION ---
|
|
29
55
|
|
|
30
|
-
//
|
|
56
|
+
// 2. Historical lookback for signal (still uses Firestore)
|
|
31
57
|
const refs = [];
|
|
32
58
|
const dates = [];
|
|
33
59
|
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
@@ -36,18 +62,16 @@ class CashFlowDeployment {
|
|
|
36
62
|
}
|
|
37
63
|
const histRefs = dates.map(d =>
|
|
38
64
|
db.collection(collection).doc(d.date)
|
|
39
|
-
.collection(
|
|
40
|
-
.collection(
|
|
65
|
+
.collection(resultsSub).doc(d.category)
|
|
66
|
+
.collection(compsSub).doc(d.computation)
|
|
41
67
|
);
|
|
42
68
|
const snapshots = await db.getAll(...histRefs);
|
|
43
69
|
const dataMap = new Map();
|
|
44
70
|
snapshots.forEach((snap, idx) => {
|
|
45
71
|
if (snap.exists) dataMap.set(idx, snap.data());
|
|
46
72
|
});
|
|
47
|
-
// --- End historical lookback ---
|
|
48
|
-
|
|
49
73
|
|
|
50
|
-
//
|
|
74
|
+
// 3. Find deposit signal
|
|
51
75
|
let depositSignal = null;
|
|
52
76
|
let depositSignalDay = null;
|
|
53
77
|
|
|
@@ -69,6 +93,7 @@ class CashFlowDeployment {
|
|
|
69
93
|
};
|
|
70
94
|
}
|
|
71
95
|
|
|
96
|
+
// 4. Check correlation window
|
|
72
97
|
const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
|
|
73
98
|
|
|
74
99
|
if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
|
|
@@ -79,13 +104,14 @@ class CashFlowDeployment {
|
|
|
79
104
|
};
|
|
80
105
|
}
|
|
81
106
|
|
|
82
|
-
// Use the in-memory data for today
|
|
107
|
+
// 5. Use the in-memory data for today's analysis
|
|
83
108
|
const netSpendPct = cashFlowData.components?.trading_effect || 0;
|
|
84
109
|
const netDepositPct = Math.abs(depositSignal.cash_flow_effect_proxy);
|
|
85
110
|
|
|
111
|
+
// Find top *buys*
|
|
86
112
|
const topBuys = Object.entries(assetFlowData)
|
|
87
|
-
.filter(([ticker, data]) => data.net_crowd_flow_pct > 0)
|
|
88
|
-
.sort(([, a], [, b]) => b.net_crowd_flow_pct - a.net_crowd_flow_pct)
|
|
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
|
|
89
115
|
.slice(0, 10)
|
|
90
116
|
.map(([ticker, data]) => ({
|
|
91
117
|
ticker,
|
|
@@ -98,7 +124,7 @@ class CashFlowDeployment {
|
|
|
98
124
|
signal_date: depositSignalDay,
|
|
99
125
|
days_since_signal: daysSinceSignal,
|
|
100
126
|
signal_deposit_proxy_pct: netDepositPct,
|
|
101
|
-
day_net_spend_pct: netSpendPct,
|
|
127
|
+
day_net_spend_pct: netSpendPct, // This value should be positive
|
|
102
128
|
pct_of_deposit_deployed_today: (netSpendPct / netDepositPct) * 100,
|
|
103
129
|
top_deployment_assets: topBuys
|
|
104
130
|
};
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Correlates a crowd-wide withdrawal signal with the specific assets
|
|
3
3
|
* that are being sold (liquidated) to fund those withdrawals.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
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*.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
const { FieldValue } = require('@google-cloud/firestore');
|
|
8
11
|
|
|
9
12
|
class CashFlowLiquidation {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* (NEW) Statically declare dependencies.
|
|
16
|
+
*/
|
|
17
|
+
static getDependencies() {
|
|
18
|
+
return ['crowd-cash-flow-proxy', 'asset-crowd-flow'];
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
constructor() {
|
|
11
22
|
this.lookbackDays = 7;
|
|
12
23
|
this.correlationWindow = 3;
|
|
13
24
|
// A positive value signals a net crowd withdrawal
|
|
14
|
-
this.withdrawalSignalThreshold = 0.005;
|
|
25
|
+
this.withdrawalSignalThreshold = 0.005;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
_getDateStr(baseDate, daysAgo) {
|
|
@@ -21,27 +32,29 @@ class CashFlowLiquidation {
|
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
/**
|
|
35
|
+
* REFACTORED PROCESS METHOD
|
|
24
36
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
25
37
|
* @param {object} dependencies The shared dependencies (db, logger).
|
|
26
38
|
* @param {object} config The computation system configuration.
|
|
27
|
-
* @param {object}
|
|
39
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
28
40
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
29
41
|
*/
|
|
30
|
-
async process(dateStr, dependencies, config,
|
|
42
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
31
43
|
const { db, logger } = dependencies;
|
|
32
44
|
const collection = config.resultsCollection;
|
|
45
|
+
const resultsSub = config.resultsSubcollection || 'results';
|
|
46
|
+
const compsSub = config.computationsSubcollection || 'computations';
|
|
33
47
|
|
|
34
|
-
//
|
|
35
|
-
const cashFlowData =
|
|
36
|
-
const assetFlowData =
|
|
48
|
+
// 1. Get same-day dependencies from `fetchedDependencies`
|
|
49
|
+
const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
|
|
50
|
+
const assetFlowData = fetchedDependencies['asset-crowd-flow'];
|
|
37
51
|
|
|
38
52
|
if (!cashFlowData || !assetFlowData) {
|
|
39
53
|
logger.log('WARN', `[CashFlowLiquidation] Missing critical in-memory dependency data for ${dateStr}. Skipping.`);
|
|
40
54
|
return null;
|
|
41
55
|
}
|
|
42
|
-
// --- END MODIFICATION ---
|
|
43
56
|
|
|
44
|
-
//
|
|
57
|
+
// 2. Historical lookback for signal (still uses Firestore)
|
|
45
58
|
const dates = [];
|
|
46
59
|
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
47
60
|
const checkDate = this._getDateStr(dateStr, i);
|
|
@@ -49,16 +62,14 @@ class CashFlowLiquidation {
|
|
|
49
62
|
}
|
|
50
63
|
const refs = dates.map(d =>
|
|
51
64
|
db.collection(collection).doc(d.date)
|
|
52
|
-
.collection(
|
|
53
|
-
.collection(
|
|
65
|
+
.collection(resultsSub).doc(d.category)
|
|
66
|
+
.collection(compsSub).doc(d.computation)
|
|
54
67
|
);
|
|
55
68
|
const snapshots = await db.getAll(...refs);
|
|
56
69
|
const dataMap = new Map();
|
|
57
70
|
snapshots.forEach((snap, idx) => {
|
|
58
71
|
if (snap.exists) dataMap.set(idx, snap.data());
|
|
59
72
|
});
|
|
60
|
-
// --- End historical lookback ---
|
|
61
|
-
|
|
62
73
|
|
|
63
74
|
// 2. Find the withdrawal signal
|
|
64
75
|
let withdrawalSignal = null;
|
|
@@ -82,7 +93,8 @@ class CashFlowLiquidation {
|
|
|
82
93
|
signal_threshold: this.withdrawalSignalThreshold
|
|
83
94
|
};
|
|
84
95
|
}
|
|
85
|
-
|
|
96
|
+
|
|
97
|
+
// 3. Check correlation window
|
|
86
98
|
const daysSinceSignal = (new Date(dateStr) - new Date(withdrawalSignalDay)) / (1000 * 60 * 60 * 24);
|
|
87
99
|
|
|
88
100
|
if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
|
|
@@ -93,6 +105,7 @@ class CashFlowLiquidation {
|
|
|
93
105
|
};
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
// 4. Use in-memory data for today's analysis
|
|
96
109
|
// 'trading_effect' will be negative if the crowd is net-selling
|
|
97
110
|
const netSellPct = cashFlowData.components?.trading_effect || 0;
|
|
98
111
|
const netWithdrawalPct = Math.abs(withdrawalSignal.cash_flow_effect_proxy);
|
|
@@ -119,7 +132,6 @@ class CashFlowLiquidation {
|
|
|
119
132
|
};
|
|
120
133
|
}
|
|
121
134
|
|
|
122
|
-
// Must exist for the meta-computation runner
|
|
123
135
|
async getResult() { return null; }
|
|
124
136
|
reset() {}
|
|
125
137
|
}
|
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 2) that correlates the asset flow
|
|
3
3
|
* of the "In Profit" cohort vs. the "In Loss" cohort to find
|
|
4
4
|
* powerful divergence signals (e.g., profit-taking, capitulation).
|
|
5
|
+
*
|
|
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.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
class ProfitCohortDivergence {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* (NEW) Statically declare dependencies.
|
|
15
|
+
*/
|
|
16
|
+
static getDependencies() {
|
|
17
|
+
return ['in-profit-asset-crowd-flow', 'in-loss-asset-crowd-flow'];
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
constructor() {
|
|
9
|
-
this.flowThreshold = 0.005; // Min abs flow %
|
|
21
|
+
this.flowThreshold = 0.005; // Min abs flow %
|
|
10
22
|
}
|
|
11
23
|
|
|
12
24
|
/**
|
|
25
|
+
* REFACTORED PROCESS METHOD
|
|
13
26
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
14
27
|
* @param {object} dependencies The shared dependencies (db, logger).
|
|
15
28
|
* @param {object} config The computation system configuration.
|
|
16
|
-
* @param {object}
|
|
29
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
30
|
+
* e.g., { 'in-profit-asset-crowd-flow': ..., 'in-loss-asset-crowd-flow': ... }
|
|
17
31
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
18
32
|
*/
|
|
19
|
-
async process(dateStr, dependencies, config,
|
|
33
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
20
34
|
const { logger } = dependencies;
|
|
21
35
|
|
|
22
|
-
// 1. Get dependencies
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const lossFlowData = computedDependencies['in-loss-asset-crowd-flow'];
|
|
36
|
+
// 1. Get dependencies
|
|
37
|
+
const profitFlowData = fetchedDependencies['in-profit-asset-crowd-flow'];
|
|
38
|
+
const lossFlowData = fetchedDependencies['in-loss-asset-crowd-flow'];
|
|
26
39
|
|
|
27
40
|
// 2. Handle missing dependencies
|
|
28
41
|
if (!profitFlowData || !lossFlowData) {
|
|
@@ -33,7 +46,7 @@ class ProfitCohortDivergence {
|
|
|
33
46
|
const results = {};
|
|
34
47
|
const allTickers = new Set([...Object.keys(profitFlowData), ...Object.keys(lossFlowData)]);
|
|
35
48
|
|
|
36
|
-
//
|
|
49
|
+
// 3. Correlate
|
|
37
50
|
for (const ticker of allTickers) {
|
|
38
51
|
const profitFlow = profitFlowData[ticker]?.net_crowd_flow_pct || 0;
|
|
39
52
|
const lossFlow = lossFlowData[ticker]?.net_crowd_flow_pct || 0;
|
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 4) that correlates the asset/sector flow
|
|
3
3
|
* of the "Smart Cohort" vs. the "Dumb Cohort" to find divergence signals.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* --- META REFACTOR (v2) ---
|
|
6
|
+
* This calculation is now stateless. It declares its dependencies and
|
|
7
|
+
* expects them to be passed to its `process` method.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
class SmartDumbDivergenceIndex {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* (NEW) Statically declare dependencies.
|
|
14
|
+
*/
|
|
15
|
+
static getDependencies() {
|
|
16
|
+
return ['smart-cohort-flow', 'dumb-cohort-flow'];
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
constructor() {
|
|
12
20
|
// Minimum net flow (as a percentage) to be considered a signal
|
|
13
21
|
this.FLOW_THRESHOLD = 0.005; // Formerly 0.5
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
/**
|
|
25
|
+
* REFACTORED PROCESS METHOD
|
|
17
26
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
18
27
|
* @param {object} dependencies The shared dependencies (db, logger).
|
|
19
28
|
* @param {object} config The computation system configuration.
|
|
20
|
-
* @param {object}
|
|
29
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
30
|
+
* e.g., { 'smart-cohort-flow': ..., 'dumb-cohort-flow': ... }
|
|
21
31
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
22
32
|
*/
|
|
23
|
-
async process(dateStr, dependencies, config,
|
|
33
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
24
34
|
const { logger } = dependencies;
|
|
25
35
|
|
|
26
|
-
// 1. Get dependencies from
|
|
27
|
-
const smartData =
|
|
28
|
-
const dumbData =
|
|
36
|
+
// 1. Get dependencies from the new argument
|
|
37
|
+
const smartData = fetchedDependencies['smart-cohort-flow'];
|
|
38
|
+
const dumbData = fetchedDependencies['dumb-cohort-flow'];
|
|
29
39
|
|
|
30
40
|
// 2. Handle missing dependencies
|
|
31
41
|
if (!smartData || !dumbData) {
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass
|
|
3
|
-
* the actual crowd asset flow.
|
|
4
|
-
*
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 2) to correlate daily social sentiment with
|
|
3
|
+
* the actual crowd asset flow.
|
|
4
|
+
*
|
|
5
|
+
* --- META REFACTOR (v2) ---
|
|
6
|
+
* This calculation is now stateless. It declares its dependencies and
|
|
7
|
+
* expects them to be passed to its `process` method.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
class SocialFlowCorrelation {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* (NEW) Statically declare dependencies.
|
|
14
|
+
*/
|
|
15
|
+
static getDependencies() {
|
|
16
|
+
return ['social-sentiment-aggregation', 'asset-crowd-flow'];
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
constructor() {
|
|
9
20
|
this.bullishSentimentThreshold = 70.0;
|
|
10
21
|
this.bearishSentimentThreshold = 30.0;
|
|
@@ -13,18 +24,20 @@ class SocialFlowCorrelation {
|
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
/**
|
|
27
|
+
* REFACTORED PROCESS METHOD
|
|
16
28
|
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
17
29
|
* @param {object} dependencies The shared dependencies (db, logger).
|
|
18
30
|
* @param {object} config The computation system configuration.
|
|
19
|
-
* @param {object}
|
|
31
|
+
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
32
|
+
* e.g., { 'social-sentiment-aggregation': ..., 'asset-crowd-flow': ... }
|
|
20
33
|
* @returns {Promise<object|null>} The analysis result or null.
|
|
21
34
|
*/
|
|
22
|
-
async process(dateStr, dependencies, config,
|
|
35
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
23
36
|
const { logger } = dependencies;
|
|
24
37
|
|
|
25
38
|
// 1. Get dependencies from in-memory cache
|
|
26
|
-
const socialData =
|
|
27
|
-
const flowData =
|
|
39
|
+
const socialData = fetchedDependencies['social-sentiment-aggregation'];
|
|
40
|
+
const flowData = fetchedDependencies['asset-crowd-flow'];
|
|
28
41
|
|
|
29
42
|
// 2. Handle missing dependencies
|
|
30
43
|
if (!socialData || !flowData) {
|
|
@@ -92,7 +105,6 @@ class SocialFlowCorrelation {
|
|
|
92
105
|
return correlationResults;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
// Must exist for the meta-computation runner
|
|
96
108
|
async getResult() { return null; }
|
|
97
109
|
reset() {}
|
|
98
110
|
}
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Aggregates P&L data points for statistical analysis.
|
|
3
|
+
* This calculation is a dependency for the 'Crowd Sharpe Ratio Proxy'.
|
|
4
|
+
* It gathers the sum, sum of squares, and count of P&L for each stock.
|
|
4
5
|
*/
|
|
5
6
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
7
|
|
|
7
8
|
class PnlDistributionPerStock {
|
|
8
9
|
constructor() {
|
|
9
|
-
this.distributionData = {};
|
|
10
|
+
this.distributionData = {}; // { [instrumentId]: { pnl_sum: 0, pnl_sum_sq: 0, position_count: 0 } }
|
|
10
11
|
this.mappings = null;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
14
15
|
const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
|
|
15
|
-
if (!positions)
|
|
16
|
+
if (!positions) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
16
19
|
|
|
17
20
|
for (const position of positions) {
|
|
18
21
|
const instrumentId = position.InstrumentID;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
// Use NetProfit (which is the P&L value)
|
|
23
|
+
const netProfit = position.NetProfit;
|
|
24
|
+
|
|
25
|
+
// Ensure netProfit is a valid number
|
|
26
|
+
if (instrumentId && typeof netProfit === 'number' && isFinite(netProfit)) {
|
|
27
|
+
if (!this.distributionData[instrumentId]) {
|
|
28
|
+
this.distributionData[instrumentId] = {
|
|
29
|
+
pnl_sum: 0,
|
|
30
|
+
pnl_sum_sq: 0, // Sum of squares
|
|
31
|
+
position_count: 0
|
|
32
|
+
};
|
|
33
|
+
}
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
this.distributionData[instrumentId].pnl_sum += netProfit;
|
|
36
|
+
this.distributionData[instrumentId].pnl_sum_sq += (netProfit * netProfit); // Add the square
|
|
37
|
+
this.distributionData[instrumentId].position_count++;
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -36,12 +43,18 @@ class PnlDistributionPerStock {
|
|
|
36
43
|
if (!this.mappings) {
|
|
37
44
|
this.mappings = await loadInstrumentMappings();
|
|
38
45
|
}
|
|
46
|
+
|
|
39
47
|
const result = {};
|
|
40
48
|
for (const instrumentId in this.distributionData) {
|
|
41
49
|
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
|
|
42
50
|
result[ticker] = this.distributionData[instrumentId];
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
|
|
53
|
+
if (Object.keys(result).length === 0) return {};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
pnl_distribution_by_asset: result
|
|
57
|
+
};
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
reset() {
|