aiden-shared-calculations-unified 1.0.95 → 1.0.97
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/capitulation/asset-volatility-estimator.js +1 -2
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +1 -2
- package/calculations/core/Insights-total-long-per-stock +56 -0
- package/calculations/core/insights-daily-bought-vs-sold-count.js +74 -0
- package/calculations/core/insights-daily-ownership-delta.js +70 -0
- package/calculations/core/insights-sentimet-per-stock.js +68 -0
- package/calculations/core/insights-total-long-per-sector +73 -0
- package/calculations/core/insights-total-positions-held.js +49 -0
- package/calculations/ghost-book/cost-basis-density.js +1 -2
- package/calculations/ghost-book/liquidity-vacuum.js +4 -4
- package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
- package/calculations/helix/winner-loser-flow.js +1 -1
- package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
- package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
- package/calculations/predicative-alpha/mimetic-latency.js +1 -2
- package/package.json +1 -1
- package/calculations/legacy/activity_by_pnl_status.js +0 -119
- package/calculations/legacy/asset_crowd_flow.js +0 -163
- package/calculations/legacy/capital_deployment_strategy.js +0 -108
- package/calculations/legacy/capital_liquidation_performance.js +0 -139
- package/calculations/legacy/capital_vintage_performance.js +0 -136
- package/calculations/legacy/cash-flow-deployment.js +0 -144
- package/calculations/legacy/cash-flow-liquidation.js +0 -146
- package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
- package/calculations/legacy/crowd_conviction_score.js +0 -261
- package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
- package/calculations/legacy/daily_asset_activity.js +0 -128
- package/calculations/legacy/daily_user_activity_tracker.js +0 -182
- package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
- package/calculations/legacy/diversification_pnl.js +0 -115
- package/calculations/legacy/drawdown_response.js +0 -137
- package/calculations/legacy/dumb-cohort-flow.js +0 -238
- package/calculations/legacy/gain_response.js +0 -137
- package/calculations/legacy/historical_performance_aggregator.js +0 -85
- package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
- package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
- package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/new_allocation_percentage.js +0 -98
- package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
- package/calculations/legacy/position_count_pnl.js +0 -120
- package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/profit_cohort_divergence.js +0 -115
- package/calculations/legacy/profitability_migration.js +0 -104
- package/calculations/legacy/reallocation_increase_percentage.js +0 -104
- package/calculations/legacy/risk_appetite_change.js +0 -97
- package/calculations/legacy/sector_rotation.js +0 -117
- package/calculations/legacy/shark_attack_signal.js +0 -112
- package/calculations/legacy/smart-cohort-flow.js +0 -238
- package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
- package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
- package/calculations/legacy/smart_money_flow.js +0 -198
- package/calculations/legacy/social-predictive-regime-state.js +0 -102
- package/calculations/legacy/social-topic-driver-index.js +0 -147
- package/calculations/legacy/social-topic-predictive-potential.js +0 -461
- package/calculations/legacy/social_flow_correlation.js +0 -112
- package/calculations/legacy/speculator_adjustment_activity.js +0 -103
- package/calculations/legacy/strategy-performance.js +0 -265
- package/calculations/legacy/tsl_effectiveness.js +0 -85
- package/calculations/legacy/user-investment-profile.js +0 -313
- package/calculations/legacy/user_expectancy_score.js +0 -106
- package/calculations/legacy/user_profitability_tracker.js +0 -131
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for asset crowd flow.
|
|
3
|
-
*
|
|
4
|
-
* This metric calculates the "Net Crowd Flow Percentage" for each asset.
|
|
5
|
-
* It's defined as the net percentage of the crowd's capital flowing in or out
|
|
6
|
-
* of an asset, *adjusted for the asset's own price movement*.
|
|
7
|
-
*
|
|
8
|
-
* This isolates true buying/selling pressure from simple value changes.
|
|
9
|
-
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
|
-
|
|
12
|
-
class AssetCrowdFlow {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.assetData = new Map();
|
|
15
|
-
this.mappings = null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Defines the output schema for this calculation.
|
|
20
|
-
* @returns {object} JSON Schema object
|
|
21
|
-
*/
|
|
22
|
-
static getSchema() {
|
|
23
|
-
return {
|
|
24
|
-
"type": "object",
|
|
25
|
-
"description": "Calculates the net capital flow % per asset, adjusted for price movements.",
|
|
26
|
-
"patternProperties": {
|
|
27
|
-
// Ticker
|
|
28
|
-
"^.*$": {
|
|
29
|
-
"type": "object",
|
|
30
|
-
"description": "Net crowd flow metrics for a specific asset ticker.",
|
|
31
|
-
"properties": {
|
|
32
|
-
"net_flow_percentage": {
|
|
33
|
-
"type": "number",
|
|
34
|
-
"description": "Net capital flow (buying/selling) as a percentage of total holdings, adjusted for price changes."
|
|
35
|
-
},
|
|
36
|
-
"total_invested_today": {
|
|
37
|
-
"type": "number",
|
|
38
|
-
"description": "Total USD value invested in this asset today by the sample."
|
|
39
|
-
},
|
|
40
|
-
"total_invested_yesterday": {
|
|
41
|
-
"type": "number",
|
|
42
|
-
"description": "Total USD value invested in this asset yesterday by the sample."
|
|
43
|
-
},
|
|
44
|
-
"price_change_contribution": {
|
|
45
|
-
"type": "number",
|
|
46
|
-
"description": "The portion of the value change attributable to the asset's price movement."
|
|
47
|
-
},
|
|
48
|
-
"net_flow_contribution": {
|
|
49
|
-
"type": "number",
|
|
50
|
-
"description": "The portion of the value change attributable to net buying or selling."
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
"required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"additionalProperties": {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"properties": {
|
|
59
|
-
"net_flow_percentage": { "type": "number" },
|
|
60
|
-
"total_invested_today": { "type": "number" },
|
|
61
|
-
"total_invested_yesterday": { "type": "number" },
|
|
62
|
-
"price_change_contribution": { "type": "number" },
|
|
63
|
-
"net_flow_contribution": { "type": "number" }
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
_getPortfolioPositions(portfolio) {
|
|
70
|
-
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
_initAsset(instrumentId) {
|
|
74
|
-
if (!this.assetData.has(instrumentId)) {
|
|
75
|
-
this.assetData.set(instrumentId, {
|
|
76
|
-
total_invested_yesterday: 0,
|
|
77
|
-
total_invested_today: 0,
|
|
78
|
-
price_change_yesterday: 0, // Used to calculate weighted avg price change
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
process(todayPortfolio, yesterdayPortfolio) {
|
|
84
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
89
|
-
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
90
|
-
|
|
91
|
-
const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
|
|
92
|
-
const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
|
|
93
|
-
|
|
94
|
-
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
95
|
-
|
|
96
|
-
for (const instrumentId of allInstrumentIds) {
|
|
97
|
-
if (!instrumentId) continue;
|
|
98
|
-
|
|
99
|
-
this._initAsset(instrumentId);
|
|
100
|
-
const asset = this.assetData.get(instrumentId);
|
|
101
|
-
|
|
102
|
-
const yP = yPosMap.get(instrumentId);
|
|
103
|
-
const tP = tPosMap.get(instrumentId);
|
|
104
|
-
|
|
105
|
-
const yInvested = yP?.Invested || 0;
|
|
106
|
-
const tInvested = tP?.Invested || 0;
|
|
107
|
-
|
|
108
|
-
if (yInvested > 0) {
|
|
109
|
-
asset.total_invested_yesterday += yInvested;
|
|
110
|
-
// Get price change from *yesterday's* portfolio
|
|
111
|
-
const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1); // PipsRate holds 1-day change
|
|
112
|
-
asset.price_change_yesterday += yPriceChange * yInvested; // Weighted sum
|
|
113
|
-
}
|
|
114
|
-
if (tInvested > 0) {
|
|
115
|
-
asset.total_invested_today += tInvested;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async getResult() {
|
|
121
|
-
if (!this.mappings) {
|
|
122
|
-
this.mappings = await loadInstrumentMappings();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const result = {};
|
|
126
|
-
|
|
127
|
-
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
128
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
129
|
-
|
|
130
|
-
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
131
|
-
|
|
132
|
-
if (total_invested_yesterday > 0) {
|
|
133
|
-
// Calculate the weighted average price change %
|
|
134
|
-
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
135
|
-
|
|
136
|
-
// Estimate the value change *due to price*
|
|
137
|
-
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
138
|
-
|
|
139
|
-
// Estimate the value change *due to net flow* (buy/sell)
|
|
140
|
-
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
141
|
-
|
|
142
|
-
// Calculate Net Flow as a percentage of yesterday's holdings
|
|
143
|
-
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
144
|
-
|
|
145
|
-
result[ticker] = {
|
|
146
|
-
net_flow_percentage: net_flow_percentage,
|
|
147
|
-
total_invested_today: total_invested_today,
|
|
148
|
-
total_invested_yesterday: total_invested_yesterday,
|
|
149
|
-
price_change_contribution: price_contribution,
|
|
150
|
-
net_flow_contribution: flow_contribution
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
reset() {
|
|
158
|
-
this.assetData.clear();
|
|
159
|
-
this.mappings = null;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
module.exports = AssetCrowdFlow;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 3) for capital deployment strategy.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "Following a net deposit event, does the crowd
|
|
5
|
-
* tend to deploy new capital into new assets or add to existing ones?"
|
|
6
|
-
*
|
|
7
|
-
* It *depends* on 'crowd-cash-flow-proxy', 'new_allocation_percentage',
|
|
8
|
-
* and 'reallocation_increase_percentage'.
|
|
9
|
-
*/
|
|
10
|
-
class CapitalDeploymentStrategy {
|
|
11
|
-
constructor() {
|
|
12
|
-
// This class only aggregates results from other calculations.
|
|
13
|
-
// No per-user processing is needed.
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Defines the output schema for this calculation.
|
|
18
|
-
* @returns {object} JSON Schema object
|
|
19
|
-
*/
|
|
20
|
-
static getSchema() {
|
|
21
|
-
return {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"description": "Analyzes if the crowd deploys new capital into new or existing assets, triggered by a net deposit event.",
|
|
24
|
-
"properties": {
|
|
25
|
-
"is_net_deposit_day": {
|
|
26
|
-
"type": "boolean",
|
|
27
|
-
"description": "True if today was a net deposit day for the crowd."
|
|
28
|
-
},
|
|
29
|
-
"net_cash_flow_proxy": {
|
|
30
|
-
"type": "number",
|
|
31
|
-
"description": "The estimated net cash flow (from 'crowd-cash-flow-proxy')."
|
|
32
|
-
},
|
|
33
|
-
"total_deployed_to_new_positions_pct": {
|
|
34
|
-
"type": "number",
|
|
35
|
-
"description": "The total % portfolio allocation deployed to new positions (from 'new_allocation_percentage')."
|
|
36
|
-
},
|
|
37
|
-
"total_deployed_to_existing_positions_pct": {
|
|
38
|
-
"type": "number",
|
|
39
|
-
"description": "The total % portfolio allocation added to existing positions (from 'reallocation_increase_percentage')."
|
|
40
|
-
},
|
|
41
|
-
"deployment_ratio_new_vs_existing": {
|
|
42
|
-
"type": ["number", "null"],
|
|
43
|
-
"description": "Ratio of (New / Existing) deployment. Null if no deployment to existing positions."
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
"required": ["is_net_deposit_day", "net_cash_flow_proxy", "total_deployed_to_new_positions_pct", "total_deployed_to_existing_positions_pct", "deployment_ratio_new_vs_existing"]
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Statically declare dependencies.
|
|
52
|
-
*/
|
|
53
|
-
static getDependencies() {
|
|
54
|
-
return [
|
|
55
|
-
'crowd-cash-flow-proxy',
|
|
56
|
-
'new_allocation_percentage',
|
|
57
|
-
'reallocation_increase_percentage'
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* process() is a no-op. All logic is in getResult().
|
|
63
|
-
*/
|
|
64
|
-
process() {
|
|
65
|
-
// No-op
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Aggregates results from dependencies.
|
|
70
|
-
*/
|
|
71
|
-
getResult(fetchedDependencies) {
|
|
72
|
-
const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
|
|
73
|
-
const newAllocData = fetchedDependencies['new_allocation_percentage'];
|
|
74
|
-
const reallocData = fetchedDependencies['reallocation_increase_percentage'];
|
|
75
|
-
|
|
76
|
-
if (!cashFlowData || !newAllocData || !reallocData) {
|
|
77
|
-
// Return a default "non-event" state if dependencies are missing
|
|
78
|
-
return {
|
|
79
|
-
is_net_deposit_day: false,
|
|
80
|
-
net_cash_flow_proxy: 0,
|
|
81
|
-
total_deployed_to_new_positions_pct: 0,
|
|
82
|
-
total_deployed_to_existing_positions_pct: 0,
|
|
83
|
-
deployment_ratio_new_vs_existing: null
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const netCashFlow = cashFlowData.net_cash_flow_proxy || 0;
|
|
88
|
-
const isNetDepositDay = netCashFlow > 0;
|
|
89
|
-
|
|
90
|
-
// We only report deployment stats IF it was a deposit day.
|
|
91
|
-
const newAllocSum = isNetDepositDay ? newAllocData.total_new_allocation_pct_sum : 0;
|
|
92
|
-
const reallocSum = isNetDepositDay ? reallocData.total_reallocation_increase_pct_sum : 0;
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
is_net_deposit_day: isNetDepositDay,
|
|
96
|
-
net_cash_flow_proxy: netCashFlow,
|
|
97
|
-
total_deployed_to_new_positions_pct: newAllocSum,
|
|
98
|
-
total_deployed_to_existing_positions_pct: reallocSum,
|
|
99
|
-
deployment_ratio_new_vs_existing: (reallocSum > 0) ? (newAllocSum / reallocSum) : null
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
reset() {
|
|
104
|
-
// No state to reset
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = CapitalDeploymentStrategy;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 4) for capital liquidation performance.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "When users liquidate assets (net withdrawal),
|
|
5
|
-
* do they tend to sell winners or losers?"
|
|
6
|
-
*
|
|
7
|
-
* It *depends* on 'cash-flow-liquidation' (to get the list of
|
|
8
|
-
* liquidated assets) and 'asset_pnl_status' (to check the P&L
|
|
9
|
-
* status of those assets).
|
|
10
|
-
*/
|
|
11
|
-
class CapitalLiquidationPerformance {
|
|
12
|
-
constructor() {
|
|
13
|
-
// No per-user processing needed
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Defines the output schema for this calculation.
|
|
18
|
-
* @returns {object} JSON Schema object
|
|
19
|
-
*/
|
|
20
|
-
static getSchema() {
|
|
21
|
-
return {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"description": "Analyzes if users sell winners or losers during net withdrawal events.",
|
|
24
|
-
"properties": {
|
|
25
|
-
"is_net_withdrawal_day": {
|
|
26
|
-
"type": "boolean",
|
|
27
|
-
"description": "True if today was a net withdrawal day."
|
|
28
|
-
},
|
|
29
|
-
"total_assets_liquidated": {
|
|
30
|
-
"type": "number",
|
|
31
|
-
"description": "The number of assets that saw net liquidation."
|
|
32
|
-
},
|
|
33
|
-
"assets_sold_at_profit_count": {
|
|
34
|
-
"type": "number",
|
|
35
|
-
"description": "Count of liquidated assets where the majority of users were in profit."
|
|
36
|
-
},
|
|
37
|
-
"assets_sold_at_loss_count": {
|
|
38
|
-
"type": "number",
|
|
39
|
-
"description": "Count of liquidated assets where the majority of users were in loss."
|
|
40
|
-
},
|
|
41
|
-
"winner_loser_sell_ratio": {
|
|
42
|
-
"type": ["number", "null"],
|
|
43
|
-
"description": "Ratio of (Assets Sold at Profit / Assets Sold at Loss). Null if no assets sold at loss."
|
|
44
|
-
},
|
|
45
|
-
"details": {
|
|
46
|
-
"type": "array",
|
|
47
|
-
"description": "List of liquidated assets and their P&L status.",
|
|
48
|
-
"items": {
|
|
49
|
-
"type": "object",
|
|
50
|
-
"properties": {
|
|
51
|
-
"ticker": { "type": "string" },
|
|
52
|
-
"net_flow_contribution": { "type": "number" },
|
|
53
|
-
"status": { "type": "string", "enum": ["profit", "loss", "neutral"] },
|
|
54
|
-
"profit_ratio": { "type": "number" }
|
|
55
|
-
},
|
|
56
|
-
"required": ["ticker", "net_flow_contribution", "status", "profit_ratio"]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
"required": ["is_net_withdrawal_day", "total_assets_liquidated", "assets_sold_at_profit_count", "assets_sold_at_loss_count", "winner_loser_sell_ratio", "details"]
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Statically declare dependencies.
|
|
66
|
-
*/
|
|
67
|
-
static getDependencies() {
|
|
68
|
-
return [
|
|
69
|
-
'cash-flow-liquidation', // Pass 3
|
|
70
|
-
'asset_pnl_status' // Pass 1
|
|
71
|
-
];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
process() {
|
|
75
|
-
// No-op
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getResult(fetchedDependencies) {
|
|
79
|
-
const liquidationData = fetchedDependencies['cash-flow-liquidation'];
|
|
80
|
-
const pnlStatusData = fetchedDependencies['asset_pnl_status'];
|
|
81
|
-
|
|
82
|
-
const defaults = {
|
|
83
|
-
is_net_withdrawal_day: false,
|
|
84
|
-
total_assets_liquidated: 0,
|
|
85
|
-
assets_sold_at_profit_count: 0,
|
|
86
|
-
assets_sold_at_loss_count: 0,
|
|
87
|
-
winner_loser_sell_ratio: null,
|
|
88
|
-
details: []
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
if (!liquidationData || !pnlStatusData || !liquidationData.is_net_withdrawal_day) {
|
|
92
|
-
return defaults;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let profitCount = 0;
|
|
96
|
-
let lossCount = 0;
|
|
97
|
-
const details = [];
|
|
98
|
-
|
|
99
|
-
for (const asset of liquidationData.asset_flow_details) {
|
|
100
|
-
// We only care about assets that were *sold* (negative flow)
|
|
101
|
-
if (asset.net_flow_contribution < 0) {
|
|
102
|
-
const ticker = asset.ticker;
|
|
103
|
-
const pnlInfo = pnlStatusData[ticker];
|
|
104
|
-
|
|
105
|
-
if (pnlInfo) {
|
|
106
|
-
let status = 'neutral';
|
|
107
|
-
if (pnlInfo.profit_ratio > 50) {
|
|
108
|
-
status = 'profit';
|
|
109
|
-
profitCount++;
|
|
110
|
-
} else if (pnlInfo.profit_ratio < 50) {
|
|
111
|
-
status = 'loss';
|
|
112
|
-
lossCount++;
|
|
113
|
-
}
|
|
114
|
-
details.push({
|
|
115
|
-
ticker: ticker,
|
|
116
|
-
net_flow_contribution: asset.net_flow_contribution,
|
|
117
|
-
status: status,
|
|
118
|
-
profit_ratio: pnlInfo.profit_ratio
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
is_net_withdrawal_day: true,
|
|
126
|
-
total_assets_liquidated: details.length,
|
|
127
|
-
assets_sold_at_profit_count: profitCount,
|
|
128
|
-
assets_sold_at_loss_count: lossCount,
|
|
129
|
-
winner_loser_sell_ratio: (lossCount > 0) ? (profitCount / lossCount) : null,
|
|
130
|
-
details: details
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
reset() {
|
|
135
|
-
// No state
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
module.exports = CapitalLiquidationPerformance;
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 3) that tracks the performance
|
|
3
|
-
* of capital "vintages" by analyzing the market returns of assets
|
|
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.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
class CapitalVintagePerformance {
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* (NEW) Statically declare dependencies.
|
|
16
|
-
*/
|
|
17
|
-
static getDependencies() {
|
|
18
|
-
return ['cash-flow-deployment'];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
constructor() {
|
|
22
|
-
this.PERFORMANCE_WINDOW_DAYS = 7;
|
|
23
|
-
this.dependenciesLoaded = false;
|
|
24
|
-
this.priceMap = null;
|
|
25
|
-
this.tickerToIdMap = null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async _loadDependencies(calculationUtils) {
|
|
29
|
-
if (this.dependenciesLoaded) return;
|
|
30
|
-
const { loadAllPriceData, loadInstrumentMappings } = calculationUtils;
|
|
31
|
-
const [priceData, mappings] = await Promise.all([
|
|
32
|
-
loadAllPriceData(),
|
|
33
|
-
loadInstrumentMappings()
|
|
34
|
-
]);
|
|
35
|
-
this.priceMap = priceData;
|
|
36
|
-
this.tickerToIdMap = {};
|
|
37
|
-
if (mappings && mappings.instrumentToTicker) {
|
|
38
|
-
for (const [id, ticker] of Object.entries(mappings.instrumentToTicker)) {
|
|
39
|
-
this.tickerToIdMap[ticker] = id;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
this.dependenciesLoaded = true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
_getDateStr(baseDateStr, daysOffset) {
|
|
46
|
-
const date = new Date(baseDateStr + 'T00:00:00Z');
|
|
47
|
-
date.setUTCDate(date.getUTCDate() + daysOffset);
|
|
48
|
-
return date.toISOString().slice(0, 10);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
_calculateBasketPerformance(assets, startDateStr, endDateStr) {
|
|
52
|
-
if (!assets || assets.length === 0) return 0;
|
|
53
|
-
let totalReturn = 0;
|
|
54
|
-
let validAssets = 0;
|
|
55
|
-
for (const asset of assets) {
|
|
56
|
-
const ticker = asset.ticker;
|
|
57
|
-
const instrumentId = this.tickerToIdMap[ticker];
|
|
58
|
-
if (!instrumentId || !this.priceMap[instrumentId]) continue;
|
|
59
|
-
const startPrice = this.priceMap[instrumentId][startDateStr];
|
|
60
|
-
const endPrice = this.priceMap[instrumentId][endDateStr];
|
|
61
|
-
if (startPrice && endPrice && startPrice > 0) {
|
|
62
|
-
const assetReturn = (endPrice - startPrice) / startPrice;
|
|
63
|
-
totalReturn += assetReturn;
|
|
64
|
-
validAssets++;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (validAssets === 0) return 0;
|
|
68
|
-
return (totalReturn / validAssets) * 100;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* REFACTORED PROCESS METHOD
|
|
73
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
74
|
-
* @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
|
|
75
|
-
* @param {object} config The computation system configuration.
|
|
76
|
-
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
77
|
-
* @returns {Promise<object|null>} The analysis result or null.
|
|
78
|
-
*/
|
|
79
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
80
|
-
const { db, logger, calculationUtils } = dependencies;
|
|
81
|
-
|
|
82
|
-
// 1. Load all price/mapping data
|
|
83
|
-
await this._loadDependencies(calculationUtils);
|
|
84
|
-
|
|
85
|
-
// 2. Get dependency from `fetchedDependencies`
|
|
86
|
-
const data = fetchedDependencies['cash-flow-deployment'];
|
|
87
|
-
|
|
88
|
-
if (!data || data.status !== 'analysis_complete') {
|
|
89
|
-
logger.log('WARN', `[CapitalVintage] Skipping ${dateStr}, no valid 'cash-flow-deployment' data found.`);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const topAssets = data.top_deployment_assets; // [{ ticker: 'AAPL', ... }]
|
|
94
|
-
const signalDate = data.signal_date; // The day the deposit signal occurred
|
|
95
|
-
const deploymentDate = data.analysis_date; // The day the capital was spent (dateStr)
|
|
96
|
-
|
|
97
|
-
if (!topAssets || topAssets.length === 0) {
|
|
98
|
-
logger.log('INFO', `[CapitalVintage] No top assets deployed on ${dateStr}.`);
|
|
99
|
-
return { status: 'no_deployment' };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 3. Define performance windows
|
|
103
|
-
const preSignalStart = this._getDateStr(signalDate, -this.PERFORMANCE_WINDOW_DAYS);
|
|
104
|
-
const preSignalEnd = signalDate;
|
|
105
|
-
const postDeployStart = deploymentDate;
|
|
106
|
-
const postDeployEnd = this._getDateStr(deploymentDate, this.PERFORMANCE_WINDOW_DAYS);
|
|
107
|
-
|
|
108
|
-
// 4. Calculate performance
|
|
109
|
-
const preSignalReturnPct = this._calculateBasketPerformance(
|
|
110
|
-
topAssets, preSignalStart, preSignalEnd
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const postDeploymentReturnPct = this._calculateBasketPerformance(
|
|
114
|
-
topAssets, postDeployStart, postDeployEnd
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const momentum = postDeploymentReturnPct - preSignalReturnPct;
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
status: 'analysis_complete',
|
|
121
|
-
signal_date: signalDate,
|
|
122
|
-
deployment_date: deploymentDate,
|
|
123
|
-
performance_window_days: this.PERFORMANCE_WINDOW_DAYS,
|
|
124
|
-
deployed_assets: topAssets.map(a => a.ticker),
|
|
125
|
-
pre_signal_return_pct: preSignalReturnPct,
|
|
126
|
-
post_deployment_return_pct: postDeploymentReturnPct,
|
|
127
|
-
return_momentum: momentum,
|
|
128
|
-
interpretation: "Measures the 7-day return of deployed assets *after* deployment vs. 7-days *before* the signal."
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async getResult() { return null; }
|
|
133
|
-
reset() {}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
module.exports = CapitalVintagePerformance;
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 3) for cash flow deployment.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "Following a net deposit event, what percentage
|
|
5
|
-
* of that new capital is deployed *today*, and which assets are
|
|
6
|
-
* receiving the most inflow?"
|
|
7
|
-
*
|
|
8
|
-
* It *depends* on 'crowd-cash-flow-proxy' (to know if it's a
|
|
9
|
-
* deposit day) and 'asset_crowd_flow' (to see where flow went).
|
|
10
|
-
*/
|
|
11
|
-
class CashFlowDeployment {
|
|
12
|
-
constructor() {
|
|
13
|
-
// No per-user processing
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Defines the output schema for this calculation.
|
|
18
|
-
* @returns {object} JSON Schema object
|
|
19
|
-
*/
|
|
20
|
-
static getSchema() {
|
|
21
|
-
const assetFlowSchema = {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"properties": {
|
|
24
|
-
"ticker": { "type": "string" },
|
|
25
|
-
"net_flow_contribution": { "type": "number" },
|
|
26
|
-
"percent_of_total_inflow": { "type": "number" }
|
|
27
|
-
},
|
|
28
|
-
"required": ["ticker", "net_flow_contribution", "percent_of_total_inflow"]
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
"type": "object",
|
|
33
|
-
"description": "On net deposit days, tracks % of new capital deployed and which assets received it.",
|
|
34
|
-
"properties": {
|
|
35
|
-
"is_net_deposit_day": {
|
|
36
|
-
"type": "boolean",
|
|
37
|
-
"description": "True if today was a net deposit day."
|
|
38
|
-
},
|
|
39
|
-
"net_cash_flow_proxy": {
|
|
40
|
-
"type": "number",
|
|
41
|
-
"description": "The total estimated net cash flow (positive)."
|
|
42
|
-
},
|
|
43
|
-
"total_net_capital_flow": {
|
|
44
|
-
"type": "number",
|
|
45
|
-
"description": "The sum of all *positive* net capital flows into assets."
|
|
46
|
-
},
|
|
47
|
-
"deployment_percentage": {
|
|
48
|
-
"type": ["number", "null"],
|
|
49
|
-
"description": "Percentage of net cash flow that was deployed (Total Net Flow / Net Cash Flow). Null if no cash flow."
|
|
50
|
-
},
|
|
51
|
-
"top_inflow_assets": {
|
|
52
|
-
"type": "array",
|
|
53
|
-
"description": "Top 5 assets receiving the most inflow.",
|
|
54
|
-
"items": assetFlowSchema
|
|
55
|
-
},
|
|
56
|
-
"asset_flow_details": {
|
|
57
|
-
"type": "array",
|
|
58
|
-
"description": "Full list of all assets and their inflows.",
|
|
59
|
-
"items": assetFlowSchema
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"required": ["is_net_deposit_day", "net_cash_flow_proxy", "total_net_capital_flow", "deployment_percentage", "top_inflow_assets", "asset_flow_details"]
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Statically declare dependencies.
|
|
68
|
-
*/
|
|
69
|
-
static getDependencies() {
|
|
70
|
-
return [
|
|
71
|
-
'crowd-cash-flow-proxy', // Pass 2
|
|
72
|
-
'asset_crowd_flow' // Pass 2
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
process() {
|
|
77
|
-
// No-op
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getResult(fetchedDependencies) {
|
|
81
|
-
const cashFlowData = fetchedDependencies['crowd-cash-flow-proxy'];
|
|
82
|
-
const assetFlowData = fetchedDependencies['asset_crowd_flow'];
|
|
83
|
-
|
|
84
|
-
const defaults = {
|
|
85
|
-
is_net_deposit_day: false,
|
|
86
|
-
net_cash_flow_proxy: 0,
|
|
87
|
-
total_net_capital_flow: 0,
|
|
88
|
-
deployment_percentage: null,
|
|
89
|
-
top_inflow_assets: [],
|
|
90
|
-
asset_flow_details: []
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (!cashFlowData || !assetFlowData || cashFlowData.net_cash_flow_proxy <= 0) {
|
|
94
|
-
// Not a net deposit day
|
|
95
|
-
return defaults;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const netCashFlow = cashFlowData.net_cash_flow_proxy;
|
|
99
|
-
let totalNetInflow = 0;
|
|
100
|
-
const allFlows = [];
|
|
101
|
-
|
|
102
|
-
for (const [ticker, data] of Object.entries(assetFlowData)) {
|
|
103
|
-
// We only care about *positive* flow (deployment)
|
|
104
|
-
if (data.net_flow_contribution > 0) {
|
|
105
|
-
totalNetInflow += data.net_flow_contribution;
|
|
106
|
-
allFlows.push({
|
|
107
|
-
ticker: ticker,
|
|
108
|
-
net_flow_contribution: data.net_flow_contribution
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (totalNetInflow === 0) {
|
|
114
|
-
// Net deposit day, but no positive flow detected
|
|
115
|
-
return {
|
|
116
|
-
...defaults,
|
|
117
|
-
is_net_deposit_day: true,
|
|
118
|
-
net_cash_flow_proxy: netCashFlow,
|
|
119
|
-
deployment_percentage: 0
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Calculate percent_of_total_inflow for each
|
|
124
|
-
const asset_flow_details = allFlows.map(flow => ({
|
|
125
|
-
...flow,
|
|
126
|
-
percent_of_total_inflow: (flow.net_flow_contribution / totalNetInflow) * 100
|
|
127
|
-
})).sort((a, b) => b.net_flow_contribution - a.net_flow_contribution);
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
is_net_deposit_day: true,
|
|
131
|
-
net_cash_flow_proxy: netCashFlow,
|
|
132
|
-
total_net_capital_flow: totalNetInflow,
|
|
133
|
-
deployment_percentage: (netCashFlow > 0) ? (totalNetInflow / netCashFlow) * 100 : null,
|
|
134
|
-
top_inflow_assets: asset_flow_details.slice(0, 5),
|
|
135
|
-
asset_flow_details: asset_flow_details
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
reset() {
|
|
140
|
-
// No state
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
module.exports = CashFlowDeployment;
|