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,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 3) for cash flow liquidation.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "Following a net withdrawal event, what percentage
|
|
5
|
-
* of that withdrawal was funded by selling *today*, and which assets
|
|
6
|
-
* are being liquidated the most?"
|
|
7
|
-
*
|
|
8
|
-
* It *depends* on 'crowd-cash-flow-proxy' (to know if it's a
|
|
9
|
-
* withdrawal day) and 'asset_crowd_flow' (to see where flow went).
|
|
10
|
-
*/
|
|
11
|
-
class CashFlowLiquidation {
|
|
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_outflow": { "type": "number" }
|
|
27
|
-
},
|
|
28
|
-
"required": ["ticker", "net_flow_contribution", "percent_of_total_outflow"]
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
"type": "object",
|
|
33
|
-
"description": "On net withdrawal days, tracks % of withdrawal funded by selling and which assets were sold.",
|
|
34
|
-
"properties": {
|
|
35
|
-
"is_net_withdrawal_day": {
|
|
36
|
-
"type": "boolean",
|
|
37
|
-
"description": "True if today was a net withdrawal day."
|
|
38
|
-
},
|
|
39
|
-
"net_cash_flow_proxy": {
|
|
40
|
-
"type": "number",
|
|
41
|
-
"description": "The total estimated net cash flow (negative)."
|
|
42
|
-
},
|
|
43
|
-
"total_net_capital_flow": {
|
|
44
|
-
"type": "number",
|
|
45
|
-
"description": "The sum of all *negative* net capital flows from assets (total liquidation)."
|
|
46
|
-
},
|
|
47
|
-
"funding_percentage": {
|
|
48
|
-
"type": ["number", "null"],
|
|
49
|
-
"description": "Percentage of net cash flow funded by selling (Total Net Flow / Net Cash Flow). Null if no cash flow."
|
|
50
|
-
},
|
|
51
|
-
"top_outflow_assets": {
|
|
52
|
-
"type": "array",
|
|
53
|
-
"description": "Top 5 assets being liquidated.",
|
|
54
|
-
"items": assetFlowSchema
|
|
55
|
-
},
|
|
56
|
-
"asset_flow_details": {
|
|
57
|
-
"type": "array",
|
|
58
|
-
"description": "Full list of all assets and their outflows.",
|
|
59
|
-
"items": assetFlowSchema
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"required": ["is_net_withdrawal_day", "net_cash_flow_proxy", "total_net_capital_flow", "funding_percentage", "top_outflow_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_withdrawal_day: false,
|
|
86
|
-
net_cash_flow_proxy: 0,
|
|
87
|
-
total_net_capital_flow: 0,
|
|
88
|
-
funding_percentage: null,
|
|
89
|
-
top_outflow_assets: [],
|
|
90
|
-
asset_flow_details: []
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (!cashFlowData || !assetFlowData || cashFlowData.net_cash_flow_proxy >= 0) {
|
|
94
|
-
// Not a net withdrawal day
|
|
95
|
-
return defaults;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const netCashFlow = cashFlowData.net_cash_flow_proxy; // This is a negative number
|
|
99
|
-
let totalNetOutflow = 0;
|
|
100
|
-
const allFlows = [];
|
|
101
|
-
|
|
102
|
-
for (const [ticker, data] of Object.entries(assetFlowData)) {
|
|
103
|
-
// We only care about *negative* flow (liquidation)
|
|
104
|
-
if (data.net_flow_contribution < 0) {
|
|
105
|
-
totalNetOutflow += data.net_flow_contribution; // Summing negative numbers
|
|
106
|
-
allFlows.push({
|
|
107
|
-
ticker: ticker,
|
|
108
|
-
net_flow_contribution: data.net_flow_contribution
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (totalNetOutflow === 0) {
|
|
114
|
-
// Net withdrawal day, but no negative flow detected
|
|
115
|
-
return {
|
|
116
|
-
...defaults,
|
|
117
|
-
is_net_withdrawal_day: true,
|
|
118
|
-
net_cash_flow_proxy: netCashFlow,
|
|
119
|
-
funding_percentage: 0
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Calculate percent_of_total_outflow for each
|
|
124
|
-
const asset_flow_details = allFlows.map(flow => ({
|
|
125
|
-
...flow,
|
|
126
|
-
// (Negative / Negative) * 100 = Positive %
|
|
127
|
-
percent_of_total_outflow: (flow.net_flow_contribution / totalNetOutflow) * 100
|
|
128
|
-
})).sort((a, b) => a.net_flow_contribution - b.net_flow_contribution); // Sort ascending (most negative first)
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
is_net_withdrawal_day: true,
|
|
132
|
-
net_cash_flow_proxy: netCashFlow,
|
|
133
|
-
total_net_capital_flow: totalNetOutflow,
|
|
134
|
-
// (Negative / Negative) * 100 = Positive %
|
|
135
|
-
funding_percentage: (netCashFlow < 0) ? (totalNetOutflow / netCashFlow) * 100 : null,
|
|
136
|
-
top_outflow_assets: asset_flow_details.slice(0, 5),
|
|
137
|
-
asset_flow_details: asset_flow_details
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
reset() {
|
|
142
|
-
// No state
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
module.exports = CashFlowLiquidation;
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for crowd cash flow proxy.
|
|
3
|
-
*
|
|
4
|
-
* This metric estimates the net cash flow (deposits vs. withdrawals)
|
|
5
|
-
* of the crowd by analyzing changes in 'Invested' vs. 'Cash' balances
|
|
6
|
-
* in user portfolios.
|
|
7
|
-
*
|
|
8
|
-
* It's a proxy because it doesn't see real bank transactions, but
|
|
9
|
-
* infers from the total portfolio value vs. the invested amount.
|
|
10
|
-
*/
|
|
11
|
-
class CrowdCashFlowProxy {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.total_portfolio_value_yesterday = 0;
|
|
14
|
-
this.total_invested_yesterday = 0;
|
|
15
|
-
|
|
16
|
-
this.total_portfolio_value_today = 0;
|
|
17
|
-
this.total_invested_today = 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Defines the output schema for this calculation.
|
|
22
|
-
* @returns {object} JSON Schema object
|
|
23
|
-
*/
|
|
24
|
-
static getSchema() {
|
|
25
|
-
return {
|
|
26
|
-
"type": "object",
|
|
27
|
-
"description": "Estimates net cash flow (deposits/withdrawals) by comparing changes in total portfolio value vs. invested capital.",
|
|
28
|
-
"properties": {
|
|
29
|
-
"total_portfolio_value_yesterday": {
|
|
30
|
-
"type": "number",
|
|
31
|
-
"description": "Sum of all users' total portfolio values from yesterday."
|
|
32
|
-
},
|
|
33
|
-
"total_invested_yesterday": {
|
|
34
|
-
"type": "number",
|
|
35
|
-
"description": "Sum of all users' invested capital from yesterday."
|
|
36
|
-
},
|
|
37
|
-
"total_portfolio_value_today": {
|
|
38
|
-
"type": "number",
|
|
39
|
-
"description": "Sum of all users' total portfolio values from today."
|
|
40
|
-
},
|
|
41
|
-
"total_invested_today": {
|
|
42
|
-
"type": "number",
|
|
43
|
-
"description": "Sum of all users' invested capital from today."
|
|
44
|
-
},
|
|
45
|
-
"total_pnl_contribution": {
|
|
46
|
-
"type": "number",
|
|
47
|
-
"description": "The estimated change in portfolio value attributable to P&L."
|
|
48
|
-
},
|
|
49
|
-
"net_cash_flow_proxy": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "The estimated net cash flow (Deposits - Withdrawals). Positive indicates net deposits."
|
|
52
|
-
},
|
|
53
|
-
"net_cash_flow_proxy_pct": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "The net cash flow proxy as a percentage of yesterday's total portfolio value."
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
"required": [
|
|
59
|
-
"total_portfolio_value_yesterday", "total_invested_yesterday",
|
|
60
|
-
"total_portfolio_value_today", "total_invested_today",
|
|
61
|
-
"total_pnl_contribution", "net_cash_flow_proxy", "net_cash_flow_proxy_pct"
|
|
62
|
-
]
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
_getPortfolioValues(portfolio) {
|
|
67
|
-
if (!portfolio || !portfolio.Summary) {
|
|
68
|
-
return { portfolioValue: 0, invested: 0 };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Total value (Invested + Cash)
|
|
72
|
-
const portfolioValue = portfolio.Summary.PortfolioValue || 0;
|
|
73
|
-
// Total invested in positions
|
|
74
|
-
const invested = portfolio.Summary.Invested || 0;
|
|
75
|
-
|
|
76
|
-
return { portfolioValue, invested };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
process(todayPortfolio, yesterdayPortfolio) {
|
|
80
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const yValues = this._getPortfolioValues(yesterdayPortfolio);
|
|
85
|
-
const tValues = this._getPortfolioValues(todayPortfolio);
|
|
86
|
-
|
|
87
|
-
this.total_portfolio_value_yesterday += yValues.portfolioValue;
|
|
88
|
-
this.total_invested_yesterday += yValues.invested;
|
|
89
|
-
|
|
90
|
-
this.total_portfolio_value_today += tValues.portfolioValue;
|
|
91
|
-
this.total_invested_today += tValues.invested;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
getResult() {
|
|
95
|
-
// P&L is the change in *invested* value, not total value
|
|
96
|
-
const totalPnl = this.total_invested_today - this.total_invested_yesterday;
|
|
97
|
-
|
|
98
|
-
// The change in total value is (P&L + Net Cash Flow)
|
|
99
|
-
// So, Net Cash Flow = (Total Value Change) - P&L
|
|
100
|
-
const totalValueChange = this.total_portfolio_value_today - this.total_portfolio_value_yesterday;
|
|
101
|
-
|
|
102
|
-
const netCashFlowProxy = totalValueChange - totalPnl;
|
|
103
|
-
|
|
104
|
-
const netCashFlowProxyPct = (this.total_portfolio_value_yesterday > 0)
|
|
105
|
-
? (netCashFlowProxy / this.total_portfolio_value_yesterday) * 100
|
|
106
|
-
: 0;
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
total_portfolio_value_yesterday: this.total_portfolio_value_yesterday,
|
|
110
|
-
total_invested_yesterday: this.total_invested_yesterday,
|
|
111
|
-
total_portfolio_value_today: this.total_portfolio_value_today,
|
|
112
|
-
total_invested_today: this.total_invested_today,
|
|
113
|
-
|
|
114
|
-
total_pnl_contribution: totalPnl,
|
|
115
|
-
net_cash_flow_proxy: netCashFlowProxy,
|
|
116
|
-
net_cash_flow_proxy_pct: netCashFlowProxyPct
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
reset() {
|
|
121
|
-
this.total_portfolio_value_yesterday = 0;
|
|
122
|
-
this.total_invested_yesterday = 0;
|
|
123
|
-
this.total_portfolio_value_today = 0;
|
|
124
|
-
this.total_invested_today = 0;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = CrowdCashFlowProxy;
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for crowd conviction.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the 'Crowd Conviction Score'
|
|
5
|
-
* for each instrument?"
|
|
6
|
-
*
|
|
7
|
-
* It's based on factors like:
|
|
8
|
-
* 1. Holding Duration (longer = more conviction)
|
|
9
|
-
* 2. P&L % (positive = more conviction)
|
|
10
|
-
* 3. Risk/Reward Ratio (higher = more conviction)
|
|
11
|
-
* 4. Leverage (lower = more conviction)
|
|
12
|
-
*
|
|
13
|
-
* This is a *stateful* calculation that computes a 30-day
|
|
14
|
-
* rolling average of these metrics to build the score.
|
|
15
|
-
*/
|
|
16
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
17
|
-
|
|
18
|
-
class CrowdConvictionScore {
|
|
19
|
-
constructor() {
|
|
20
|
-
// { [instrumentId]: { metrics: [], history: [] } }
|
|
21
|
-
this.assetData = new Map();
|
|
22
|
-
this.mappings = null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Defines the output schema for this calculation.
|
|
27
|
-
* @returns {object} JSON Schema object
|
|
28
|
-
*/
|
|
29
|
-
static getSchema() {
|
|
30
|
-
const historyItemSchema = {
|
|
31
|
-
"type": "object",
|
|
32
|
-
"properties": {
|
|
33
|
-
"avg_holding_duration": { "type": "number" },
|
|
34
|
-
"avg_pnl": { "type": "number" },
|
|
35
|
-
"avg_rr": { "type": "number" },
|
|
36
|
-
"avg_leverage": { "type": "number" }
|
|
37
|
-
},
|
|
38
|
-
"required": ["avg_holding_duration", "avg_pnl", "avg_rr", "avg_leverage"]
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const tickerSchema = {
|
|
42
|
-
"type": "object",
|
|
43
|
-
"description": "Crowd conviction score and its components for a specific asset.",
|
|
44
|
-
"properties": {
|
|
45
|
-
"conviction_score": {
|
|
46
|
-
"type": "number",
|
|
47
|
-
"description": "Final conviction score (0-100), based on 30-day rolling averages."
|
|
48
|
-
},
|
|
49
|
-
"roll_avg_holding_duration": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "30-day rolling average of holding duration (hours)."
|
|
52
|
-
},
|
|
53
|
-
"roll_avg_pnl": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "30-day rolling average of P&L percentage."
|
|
56
|
-
},
|
|
57
|
-
"roll_avg_rr": {
|
|
58
|
-
"type": "number",
|
|
59
|
-
"description": "30-day rolling average of risk/reward ratio."
|
|
60
|
-
},
|
|
61
|
-
"roll_avg_leverage": {
|
|
62
|
-
"type": "number",
|
|
63
|
-
"description": "30-day rolling average of leverage."
|
|
64
|
-
},
|
|
65
|
-
"avg_holding_duration": {
|
|
66
|
-
"type": "number",
|
|
67
|
-
"description": "Today's average holding duration."
|
|
68
|
-
},
|
|
69
|
-
"avg_pnl": {
|
|
70
|
-
"type": "number",
|
|
71
|
-
"description": "Today's average P&L percentage."
|
|
72
|
-
},
|
|
73
|
-
"avg_rr": {
|
|
74
|
-
"type": "number",
|
|
75
|
-
"description": "Today's average risk/reward ratio."
|
|
76
|
-
},
|
|
77
|
-
"avg_leverage": {
|
|
78
|
-
"type": "number",
|
|
79
|
-
"description": "Today's average leverage."
|
|
80
|
-
},
|
|
81
|
-
"history": {
|
|
82
|
-
"type": "array",
|
|
83
|
-
"description": "30-day history of daily average metrics.",
|
|
84
|
-
"items": historyItemSchema
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"required": [
|
|
88
|
-
"conviction_score", "roll_avg_holding_duration", "roll_avg_pnl",
|
|
89
|
-
"roll_avg_rr", "roll_avg_leverage", "avg_holding_duration",
|
|
90
|
-
"avg_pnl", "avg_rr", "avg_leverage", "history"
|
|
91
|
-
]
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
"type": "object",
|
|
96
|
-
"description": "Calculates a 30-day rolling 'Crowd Conviction Score' (0-100) for each asset.",
|
|
97
|
-
"patternProperties": {
|
|
98
|
-
"^.*$": tickerSchema // Ticker
|
|
99
|
-
},
|
|
100
|
-
"additionalProperties": tickerSchema
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_initAsset(instrumentId) {
|
|
105
|
-
if (!this.assetData.has(instrumentId)) {
|
|
106
|
-
// metrics: Stores *today's* raw values before averaging
|
|
107
|
-
// history: Stores *yesterday's* 30-day history
|
|
108
|
-
this.assetData.set(instrumentId, {
|
|
109
|
-
metrics: [],
|
|
110
|
-
history: []
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
_getHoldingDurationHours(openDateStr) {
|
|
116
|
-
if (!openDateStr) return 0;
|
|
117
|
-
try {
|
|
118
|
-
const openDate = new Date(openDateStr);
|
|
119
|
-
const diffMs = new Date().getTime() - openDate.getTime();
|
|
120
|
-
return diffMs / (1000 * 60 * 60);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Simple min-max normalization helper
|
|
127
|
-
_normalize(value, min, max) {
|
|
128
|
-
if (max === min) return 0.5; // Avoid division by zero
|
|
129
|
-
const normalized = (value - min) / (max - min);
|
|
130
|
-
return Math.max(0, Math.min(1, normalized)); // Clamp between 0 and 1
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
134
|
-
// This score is based on speculator actions
|
|
135
|
-
if (portfolioData?.context?.userType !== 'speculator') {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 1. Get this metric's history from yesterday (pre-loaded)
|
|
140
|
-
if (this.assetData.size === 0) { // Only run once
|
|
141
|
-
const yHistoryData = context.yesterdaysDependencyData['crowd_conviction_score'];
|
|
142
|
-
if (yHistoryData) {
|
|
143
|
-
if (!this.mappings) {
|
|
144
|
-
// We need mappings to convert *yesterday's* tickers back to IDs
|
|
145
|
-
this.mappings = context.mappings;
|
|
146
|
-
}
|
|
147
|
-
for (const [ticker, data] of Object.entries(yHistoryData)) {
|
|
148
|
-
const instrumentId = this.mappings.tickerToInstrument[ticker];
|
|
149
|
-
if (instrumentId) {
|
|
150
|
-
this._initAsset(instrumentId);
|
|
151
|
-
this.assetData.get(instrumentId).history = data.history || [];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const positions = portfolioData.PublicPositions;
|
|
158
|
-
if (!positions || !Array.isArray(positions)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
for (const pos of positions) {
|
|
163
|
-
const instrumentId = pos.InstrumentID;
|
|
164
|
-
if (!instrumentId) continue;
|
|
165
|
-
|
|
166
|
-
this._initAsset(instrumentId);
|
|
167
|
-
|
|
168
|
-
const pnl_percent = (pos.NetProfit || 0) / (pos.Amount || 1);
|
|
169
|
-
const holding_duration = this._getHoldingDurationHours(pos.OpenDateTime);
|
|
170
|
-
const leverage = pos.Leverage || 1;
|
|
171
|
-
|
|
172
|
-
const sl_rate = pos.StopLossRate || 0;
|
|
173
|
-
const tp_rate = pos.TakeProfitRate || 0;
|
|
174
|
-
const open_rate = pos.OpenRate || 1;
|
|
175
|
-
|
|
176
|
-
let rr_ratio = 0;
|
|
177
|
-
if (sl_rate > 0 && tp_rate > 0) {
|
|
178
|
-
const risk = Math.abs(open_rate - sl_rate);
|
|
179
|
-
const reward = Math.abs(tp_rate - open_rate);
|
|
180
|
-
if (risk > 0) {
|
|
181
|
-
rr_ratio = reward / risk;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this.assetData.get(instrumentId).metrics.push({
|
|
186
|
-
holding_duration,
|
|
187
|
-
pnl_percent,
|
|
188
|
-
rr_ratio,
|
|
189
|
-
leverage
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async getResult() {
|
|
195
|
-
if (!this.mappings) {
|
|
196
|
-
this.mappings = await loadInstrumentMappings();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const result = {};
|
|
200
|
-
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
201
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
202
|
-
const metrics = data.metrics;
|
|
203
|
-
const yHistory = data.history; // Yesterday's 30-day history
|
|
204
|
-
|
|
205
|
-
if (metrics.length === 0) continue;
|
|
206
|
-
|
|
207
|
-
// 1. Calculate today's averages
|
|
208
|
-
const count = metrics.length;
|
|
209
|
-
const avg_holding_duration = metrics.reduce((s, m) => s + m.holding_duration, 0) / count;
|
|
210
|
-
const avg_pnl = metrics.reduce((s, m) => s + m.pnl_percent, 0) / count;
|
|
211
|
-
const avg_rr = metrics.reduce((s, m) => s + m.rr_ratio, 0) / count;
|
|
212
|
-
const avg_leverage = metrics.reduce((s, m) => s + m.leverage, 0) / count;
|
|
213
|
-
|
|
214
|
-
const todayMetrics = { avg_holding_duration, avg_pnl, avg_rr, avg_leverage };
|
|
215
|
-
|
|
216
|
-
// 2. Create new 30-day history
|
|
217
|
-
const newHistory = [todayMetrics, ...yHistory].slice(0, 30);
|
|
218
|
-
|
|
219
|
-
// 3. Calculate 30-day rolling averages
|
|
220
|
-
const historyCount = newHistory.length;
|
|
221
|
-
const roll_avg_duration = newHistory.reduce((s, m) => s + m.avg_holding_duration, 0) / historyCount;
|
|
222
|
-
const roll_avg_pnl = newHistory.reduce((s, m) => s + m.avg_pnl, 0) / historyCount;
|
|
223
|
-
const roll_avg_rr = newHistory.reduce((s, m) => s + m.avg_rr, 0) / historyCount;
|
|
224
|
-
const roll_avg_leverage = newHistory.reduce((s, m) => s + m.avg_leverage, 0) / historyCount;
|
|
225
|
-
|
|
226
|
-
// 4. Calculate Conviction Score (example normalization)
|
|
227
|
-
// (Assumes a 0-1 range for normalization, then scales to 0-100)
|
|
228
|
-
const norm_duration = this._normalize(roll_avg_duration, 0, 240); // 0-10 days
|
|
229
|
-
const norm_pnl = this._normalize(roll_avg_pnl, -0.5, 0.5); // -50% to +50%
|
|
230
|
-
const norm_rr = this._normalize(roll_avg_rr, 0, 3); // 0 to 3 R:R
|
|
231
|
-
const norm_leverage = 1 - this._normalize(roll_avg_leverage, 1, 10); // 1x to 10x (inverted)
|
|
232
|
-
|
|
233
|
-
// Combine scores (equal weight for this example)
|
|
234
|
-
const score_pct = (norm_duration + norm_pnl + norm_rr + norm_leverage) / 4;
|
|
235
|
-
|
|
236
|
-
result[ticker] = {
|
|
237
|
-
conviction_score: score_pct * 100, // Final score 0-100
|
|
238
|
-
roll_avg_holding_duration,
|
|
239
|
-
roll_avg_pnl,
|
|
240
|
-
roll_avg_rr,
|
|
241
|
-
roll_avg_leverage,
|
|
242
|
-
|
|
243
|
-
// Also include today's raw averages
|
|
244
|
-
avg_holding_duration,
|
|
245
|
-
avg_pnl,
|
|
246
|
-
avg_rr,
|
|
247
|
-
avg_leverage,
|
|
248
|
-
|
|
249
|
-
history: newHistory
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
reset() {
|
|
256
|
-
this.assetData.clear();
|
|
257
|
-
this.mappings = null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
module.exports = CrowdConvictionScore;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for crowd sharpe ratio proxy.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the crowd's risk-adjusted return
|
|
5
|
-
* (Sharpe Ratio proxy) for each asset?"
|
|
6
|
-
*
|
|
7
|
-
* It uses the distribution of P&L from 'pnl_distribution_per_stock'
|
|
8
|
-
* to calculate variance (risk).
|
|
9
|
-
*
|
|
10
|
-
* --- FIX: 2025-11-12 ---
|
|
11
|
-
* Refactored this file to be a "meta" calculation.
|
|
12
|
-
* 1. Removed constructor, getResult, reset, and the no-op 'process'.
|
|
13
|
-
* 2. Added the required `async process(dStr, deps, config, fetchedDeps)` method.
|
|
14
|
-
* 3. Moved all logic into `process`.
|
|
15
|
-
* 4. Updated logic to read from `fetchedDeps['pnl_distribution_per_stock']`.
|
|
16
|
-
* 5. Updated data access to read from the new `data.stats` object
|
|
17
|
-
* provided by the fixed dependency.
|
|
18
|
-
*/
|
|
19
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
20
|
-
|
|
21
|
-
class CrowdSharpeRatioProxy {
|
|
22
|
-
/**
|
|
23
|
-
* Defines the output schema for this calculation.
|
|
24
|
-
* @returns {object} JSON Schema object
|
|
25
|
-
*/
|
|
26
|
-
static getSchema() {
|
|
27
|
-
const tickerSchema = {
|
|
28
|
-
"type": "object",
|
|
29
|
-
"description": "Sharpe Ratio proxy metrics for a specific asset.",
|
|
30
|
-
"properties": {
|
|
31
|
-
"sharpe_ratio_proxy": {
|
|
32
|
-
"type": "number",
|
|
33
|
-
"description": "Risk-adjusted return proxy (Mean P&L / StdDev P&L). Assumes risk-free rate is 0."
|
|
34
|
-
},
|
|
35
|
-
"average_pnl": { "type": "number" },
|
|
36
|
-
"std_dev_pnl": { "type": "number" },
|
|
37
|
-
"variance_pnl": { "type": "number" },
|
|
38
|
-
"position_count": { "type": "number" }
|
|
39
|
-
},
|
|
40
|
-
"required": ["sharpe_ratio_proxy", "average_pnl", "std_dev_pnl", "position_count"]
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
"type": "object",
|
|
45
|
-
"description": "Calculates a risk-adjusted return (Sharpe Ratio proxy) for each asset based on P&L distribution.",
|
|
46
|
-
"patternProperties": {
|
|
47
|
-
"^.*$": tickerSchema // Matches any string key (ticker)
|
|
48
|
-
},
|
|
49
|
-
"additionalProperties": tickerSchema
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Statically declare dependencies.
|
|
55
|
-
*/
|
|
56
|
-
static getDependencies() {
|
|
57
|
-
return [
|
|
58
|
-
'pnl_distribution_per_stock' // Pass 1
|
|
59
|
-
];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* --- FIX: This is the new main execution method for meta-calcs ---
|
|
64
|
-
* It receives all dependencies from the orchestrator.
|
|
65
|
-
*/
|
|
66
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
67
|
-
// --- FIX: Load dependency data from the argument ---
|
|
68
|
-
const pnlDistData = fetchedDependencies['pnl_distribution_per_stock'];
|
|
69
|
-
|
|
70
|
-
if (!pnlDistData) {
|
|
71
|
-
dependencies.logger.log('WARN', `[crowd_sharpe_ratio_proxy] Missing dependency 'pnl_distribution_per_stock' for ${dateStr}. Skipping.`);
|
|
72
|
-
return {};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// --- FIX: Load mappings inside the process method ---
|
|
76
|
-
const mappings = await loadInstrumentMappings();
|
|
77
|
-
if (!mappings || !mappings.instrumentToTicker) {
|
|
78
|
-
dependencies.logger.log('ERROR', `[crowd_sharpe_ratio_proxy] Failed to load instrument mappings.`);
|
|
79
|
-
return {};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const result = {};
|
|
83
|
-
|
|
84
|
-
for (const [ticker, data] of Object.entries(pnlDistData)) {
|
|
85
|
-
|
|
86
|
-
// --- FIX: Read from the new 'stats' sub-object ---
|
|
87
|
-
if (!data.stats) {
|
|
88
|
-
continue; // Skip if data is malformed
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const { sum, sumSq, count } = data.stats;
|
|
92
|
-
|
|
93
|
-
if (count < 2) {
|
|
94
|
-
continue; // Need at least 2 data points for variance
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Calculate Mean (Average P&L)
|
|
98
|
-
const mean = sum / count;
|
|
99
|
-
|
|
100
|
-
// Calculate Variance
|
|
101
|
-
// Var(X) = E[X^2] - (E[X])^2
|
|
102
|
-
const meanSq = sumSq / count;
|
|
103
|
-
const variance = meanSq - (mean * mean);
|
|
104
|
-
|
|
105
|
-
// Handle potential float precision errors
|
|
106
|
-
if (variance < 0) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Calculate Standard Deviation (Risk)
|
|
111
|
-
const stdDev = Math.sqrt(variance);
|
|
112
|
-
|
|
113
|
-
if (stdDev === 0) {
|
|
114
|
-
continue; // No risk, Sharpe ratio is infinite/undefined
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Calculate Sharpe Ratio Proxy (assuming risk-free rate = 0)
|
|
118
|
-
// Sharpe = Mean(Return) / StdDev(Return)
|
|
119
|
-
const sharpeProxy = mean / stdDev;
|
|
120
|
-
|
|
121
|
-
// --- FIX: Data is already keyed by ticker, no mapping needed ---
|
|
122
|
-
// const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
123
|
-
|
|
124
|
-
result[ticker] = {
|
|
125
|
-
sharpe_ratio_proxy: sharpeProxy,
|
|
126
|
-
average_pnl: mean,
|
|
127
|
-
std_dev_pnl: stdDev,
|
|
128
|
-
variance_pnl: variance,
|
|
129
|
-
position_count: count
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
module.exports = CrowdSharpeRatioProxy;
|