aiden-shared-calculations-unified 1.0.94 → 1.0.96

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.
Files changed (55) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +1 -2
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +1 -2
  3. package/calculations/gauss/cohort-definer.js +24 -2
  4. package/calculations/ghost-book/cost-basis-density.js +1 -2
  5. package/calculations/ghost-book/liquidity-vacuum.js +1 -2
  6. package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
  7. package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
  8. package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
  9. package/calculations/predicative-alpha/mimetic-latency.js +1 -2
  10. package/package.json +1 -1
  11. package/calculations/legacy/activity_by_pnl_status.js +0 -119
  12. package/calculations/legacy/asset_crowd_flow.js +0 -163
  13. package/calculations/legacy/capital_deployment_strategy.js +0 -108
  14. package/calculations/legacy/capital_liquidation_performance.js +0 -139
  15. package/calculations/legacy/capital_vintage_performance.js +0 -136
  16. package/calculations/legacy/cash-flow-deployment.js +0 -144
  17. package/calculations/legacy/cash-flow-liquidation.js +0 -146
  18. package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
  19. package/calculations/legacy/crowd_conviction_score.js +0 -261
  20. package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
  21. package/calculations/legacy/daily_asset_activity.js +0 -128
  22. package/calculations/legacy/daily_user_activity_tracker.js +0 -182
  23. package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
  24. package/calculations/legacy/diversification_pnl.js +0 -115
  25. package/calculations/legacy/drawdown_response.js +0 -137
  26. package/calculations/legacy/dumb-cohort-flow.js +0 -238
  27. package/calculations/legacy/gain_response.js +0 -137
  28. package/calculations/legacy/historical_performance_aggregator.js +0 -85
  29. package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
  30. package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
  31. package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
  32. package/calculations/legacy/new_allocation_percentage.js +0 -98
  33. package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
  34. package/calculations/legacy/position_count_pnl.js +0 -120
  35. package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
  36. package/calculations/legacy/profit_cohort_divergence.js +0 -115
  37. package/calculations/legacy/profitability_migration.js +0 -104
  38. package/calculations/legacy/reallocation_increase_percentage.js +0 -104
  39. package/calculations/legacy/risk_appetite_change.js +0 -97
  40. package/calculations/legacy/sector_rotation.js +0 -117
  41. package/calculations/legacy/shark_attack_signal.js +0 -112
  42. package/calculations/legacy/smart-cohort-flow.js +0 -238
  43. package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
  44. package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
  45. package/calculations/legacy/smart_money_flow.js +0 -198
  46. package/calculations/legacy/social-predictive-regime-state.js +0 -102
  47. package/calculations/legacy/social-topic-driver-index.js +0 -147
  48. package/calculations/legacy/social-topic-predictive-potential.js +0 -461
  49. package/calculations/legacy/social_flow_correlation.js +0 -112
  50. package/calculations/legacy/speculator_adjustment_activity.js +0 -103
  51. package/calculations/legacy/strategy-performance.js +0 -265
  52. package/calculations/legacy/tsl_effectiveness.js +0 -85
  53. package/calculations/legacy/user-investment-profile.js +0 -313
  54. package/calculations/legacy/user_expectancy_score.js +0 -106
  55. package/calculations/legacy/user_profitability_tracker.js +0 -131
@@ -13,8 +13,7 @@ class AssetVolatilityEstimator {
13
13
  type: 'meta',
14
14
  rootDataDependencies: ['price'],
15
15
  isHistorical: false,
16
- userType: 'n/a',
17
- category: 'market_stats'
16
+ userType: 'n/a'
18
17
  };
19
18
  }
20
19
 
@@ -21,8 +21,7 @@ class RetailCapitulationRiskForecast {
21
21
  type: 'standard',
22
22
  rootDataDependencies: ['portfolio', 'history'],
23
23
  isHistorical: false,
24
- userType: 'all',
25
- category: 'risk_models'
24
+ userType: 'all'
26
25
  };
27
26
  }
28
27
 
@@ -24,10 +24,32 @@ class CohortDefiner {
24
24
  }
25
25
 
26
26
  static getSchema() {
27
- const cohortSchema = { "type": "array", "items": { "type": "string" } };
27
+ const cohortList = {
28
+ "type": "array",
29
+ "items": { "type": "string" },
30
+ "description": "List of User IDs belonging to this behavioral cohort"
31
+ };
32
+
28
33
  return {
29
34
  "type": "object",
30
- "patternProperties": { "^.*$": cohortSchema }
35
+ "required": [
36
+ "smart_investors",
37
+ "smart_scalpers",
38
+ "uncategorized_smart",
39
+ "fomo_chasers",
40
+ "patient_losers",
41
+ "fomo_bagholders",
42
+ "uncategorized_dumb"
43
+ ],
44
+ "properties": {
45
+ "smart_investors": cohortList,
46
+ "smart_scalpers": cohortList,
47
+ "uncategorized_smart": cohortList,
48
+ "fomo_chasers": cohortList,
49
+ "patient_losers": cohortList,
50
+ "fomo_bagholders": cohortList,
51
+ "uncategorized_dumb": cohortList
52
+ }
31
53
  };
32
54
  }
33
55
 
@@ -8,8 +8,7 @@ class CostBasisDensity {
8
8
  static getMetadata() {
9
9
  return {
10
10
  type: 'meta', // Runs ONCE per day
11
- dependencies: ['asset-cost-basis-profile'],
12
- category: 'ghost_book'
11
+ dependencies: ['asset-cost-basis-profile']
13
12
  };
14
13
  }
15
14
 
@@ -4,8 +4,7 @@ class LiquidityVacuum {
4
4
  static getMetadata() {
5
5
  return {
6
6
  type: 'meta',
7
- dependencies: ['asset-cost-basis-profile'],
8
- category: 'ghost_book'
7
+ dependencies: ['asset-cost-basis-profile']
9
8
  };
10
9
  }
11
10
 
@@ -10,7 +10,6 @@ class RetailGammaExposure {
10
10
  type: 'meta',
11
11
  dependencies: ['skilled-cohort-flow', 'instrument-price-change-1d'],
12
12
  isHistorical: true, // Requires t-1, t-2... for rolling regression
13
- category: 'ghost_book'
14
13
  };
15
14
  }
16
15
 
@@ -13,8 +13,7 @@ class CognitiveDissonance {
13
13
  type: 'meta', // Runs after standard calculations
14
14
  rootDataDependencies: [],
15
15
  isHistorical: true, // Requires t-1 state
16
- userType: 'aggregate',
17
- category: 'predictive_alpha'
16
+ userType: 'aggregate'
18
17
  };
19
18
  }
20
19
 
@@ -10,8 +10,7 @@ class DiamondHandFracture {
10
10
  type: 'meta',
11
11
  rootDataDependencies: [],
12
12
  isHistorical: true,
13
- userType: 'aggregate',
14
- category: 'predictive_alpha'
13
+ userType: 'aggregate'
15
14
  };
16
15
  }
17
16
 
@@ -15,8 +15,7 @@ class MimeticLatencyOscillator {
15
15
  type: 'meta',
16
16
  rootDataDependencies: [],
17
17
  isHistorical: true,
18
- userType: 'aggregate',
19
- category: 'predictive_alpha'
18
+ userType: 'aggregate'
20
19
  };
21
20
  }
22
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,119 +0,0 @@
1
- /**
2
- * @fileoverview Analyzes *why* users were active by checking the P&L status
3
- * of positions just before they were closed.
4
- * This measures "Profit Taking" vs. "Capitulation".
5
- */
6
- class ActivityByPnlStatus {
7
- constructor() {
8
- this.total_positions_yesterday = {
9
- in_profit: 0,
10
- in_loss: 0
11
- };
12
- this.closed_positions_today = {
13
- profit_taken: 0,
14
- loss_realized: 0
15
- };
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": "Measures profit-taking vs. capitulation by analyzing the P&L of closed positions.",
26
- "properties": {
27
- "profit_taking_rate_pct": {
28
- "type": "number",
29
- "description": "The percentage of profitable positions (from yesterday) that were closed today."
30
- },
31
- "capitulation_rate_pct": {
32
- "type": "number",
33
- "description": "The percentage of losing positions (from yesterday) that were closed today."
34
- },
35
- "raw_counts": {
36
- "type": "object",
37
- "description": "Raw counts used to calculate the rates.",
38
- "properties": {
39
- "profit_positions_closed": { "type": "number" },
40
- "loss_positions_closed": { "type": "number" },
41
- "total_profit_positions_available": { "type": "number" },
42
- "total_loss_positions_available": { "type": "number" }
43
- },
44
- "required": ["profit_positions_closed", "loss_positions_closed", "total_profit_positions_available", "total_loss_positions_available"]
45
- }
46
- },
47
- "required": ["profit_taking_rate_pct", "capitulation_rate_pct", "raw_counts"]
48
- };
49
- }
50
-
51
- _getPortfolioMap(portfolio) {
52
- // We MUST use PositionID here to track specific trades, not just the asset
53
- const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
54
- if (!positions || !Array.isArray(positions)) {
55
- return new Map();
56
- }
57
- // Map<PositionID, NetProfit>
58
- return new Map(positions.map(p => [p.PositionID, p.NetProfit || 0]));
59
- }
60
-
61
- process(todayPortfolio, yesterdayPortfolio, userId) {
62
- if (!todayPortfolio || !yesterdayPortfolio) {
63
- return;
64
- }
65
-
66
- const yPosMap = this._getPortfolioMap(yesterdayPortfolio);
67
- const tPosMap = this._getPortfolioMap(todayPortfolio);
68
-
69
- if (yPosMap.size === 0) {
70
- return; // No positions yesterday to analyze
71
- }
72
-
73
- for (const [yPosId, yNetProfit] of yPosMap.entries()) {
74
- // 1. Bucket yesterday's P&L state
75
- if (yNetProfit > 0) {
76
- this.total_positions_yesterday.in_profit++;
77
- } else if (yNetProfit < 0) {
78
- this.total_positions_yesterday.in_loss++;
79
- }
80
-
81
- // 2. Check if this position was closed
82
- if (!tPosMap.has(yPosId)) {
83
- // Position was closed. Check its P&L from yesterday.
84
- if (yNetProfit > 0) {
85
- this.closed_positions_today.profit_taken++;
86
- } else if (yNetProfit < 0) {
87
- this.closed_positions_today.loss_realized++;
88
- }
89
- }
90
- }
91
- }
92
-
93
- getResult() {
94
- const { in_profit, in_loss } = this.total_positions_yesterday;
95
- const { profit_taken, loss_realized } = this.closed_positions_today;
96
-
97
- // Calculate rates to normalize the data
98
- const profit_taking_rate = (in_profit > 0) ? (profit_taken / in_profit) * 100 : 0;
99
- const capitulation_rate = (in_loss > 0) ? (loss_realized / in_loss) * 100 : 0;
100
-
101
- return {
102
- profit_taking_rate_pct: profit_taking_rate, // % of profitable positions that were closed
103
- capitulation_rate_pct: capitulation_rate, // % of losing positions that were closed
104
- raw_counts: {
105
- profit_positions_closed: profit_taken,
106
- loss_positions_closed: loss_realized,
107
- total_profit_positions_available: in_profit,
108
- total_loss_positions_available: in_loss
109
- }
110
- };
111
- }
112
-
113
- reset() {
114
- this.total_positions_yesterday = { in_profit: 0, in_loss: 0 };
115
- this.closed_positions_today = { profit_taken: 0, loss_realized: 0 };
116
- }
117
- }
118
-
119
- module.exports = ActivityByPnlStatus;
@@ -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;