aiden-shared-calculations-unified 1.0.76 → 1.0.77
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/{pnl/asset_pnl_status.js → core/asset-pnl-status.js} +20 -0
- package/calculations/{asset_metrics/asset_position_size.js → core/asset-position-size.js} +25 -1
- package/calculations/{pnl/average_daily_pnl_all_users.js → core/average-daily-pnl-all-users.js} +22 -0
- package/calculations/{pnl/average_daily_pnl_per_sector.js → core/average-daily-pnl-per-sector.js} +22 -0
- package/calculations/{pnl/average_daily_pnl_per_stock.js → core/average-daily-pnl-per-stock.js} +22 -0
- package/calculations/{pnl/average_daily_position_pnl.js → core/average-daily-position-pnl.js} +22 -0
- package/calculations/{behavioural/historical/holding_duration_per_asset.js → core/holding-duration-per-asset.js} +44 -4
- package/calculations/{meta/gem_instrument-price-momentum.js → core/instrument-price-momentum-20d.js} +17 -4
- package/calculations/{short_and_long_stats/long_position_per_stock.js → core/long-position-per-stock.js} +24 -0
- package/calculations/{behavioural/overall_holding_duration.js → core/overall-holding-duration.js} +27 -3
- package/calculations/{pnl/overall_profitability_ratio.js → core/overall-profitability-ratio.js} +24 -0
- package/calculations/{insights/daily_buy_sell_sentiment_count.js → core/platform-buy-sell-sentiment.js} +22 -0
- package/calculations/{insights/historical/daily_bought_vs_sold_count.js → core/platform-daily-bought-vs-sold-count.js} +22 -0
- package/calculations/{insights/historical/daily_ownership_delta.js → core/platform-daily-ownership-delta.js} +22 -0
- package/calculations/{insights/daily_ownership_per_sector.js → core/platform-ownership-per-sector.js} +22 -0
- package/calculations/{insights/daily_total_positions_held.js → core/platform-total-positions-held.js} +22 -0
- package/calculations/{pnl/pnl_distribution_per_stock.js → core/pnl-distribution-per-stock.js} +24 -0
- package/calculations/{pnl/profitability_ratio_per_sector,js → core/profitability-ratio-per-sector.js} +35 -5
- package/calculations/{pnl/profitability_ratio_per_stock.js → core/profitability-ratio-per-stock.js} +24 -0
- package/calculations/{pnl/profitability_skew_per_stock.js → core/profitability-skew-per-stock.js} +24 -0
- package/calculations/{pnl/profitable_and_unprofitable_status.js → core/profitable-and-unprofitable-status.js} +24 -0
- package/calculations/{short_and_long_stats/sentiment_per_stock.js → core/sentiment-per-stock.js} +20 -0
- package/calculations/{short_and_long_stats/short_position_per_stock.js → core/short-position-per-stock.js} +24 -0
- package/calculations/{socialPosts/social_activity_aggregation.js → core/social-activity-aggregation.js} +32 -8
- package/calculations/{socialPosts → core}/social-asset-posts-trend.js +30 -8
- package/calculations/{socialPosts/social_event_correlation.js → core/social-event-correlation.js} +24 -2
- package/calculations/{socialPosts/social_sentiment_aggregation.js → core/social-sentiment-aggregation.js} +23 -0
- package/calculations/{socialPosts → core}/social-top-mentioned-words.js +31 -8
- package/calculations/{socialPosts → core}/social-topic-interest-evolution.js +35 -11
- package/calculations/{socialPosts → core}/social-topic-sentiment-matrix.js +34 -10
- package/calculations/{socialPosts → core}/social-word-mentions-trend.js +36 -11
- package/calculations/{speculators/speculator_asset_sentiment.js → core/speculator-asset-sentiment.js} +20 -0
- package/calculations/{speculators/speculator_danger_zone.js → core/speculator-danger-zone.js} +22 -0
- package/calculations/{speculators/distance_to_stop_loss_per_leverage.js → core/speculator-distance-to-stop-loss-per-leverage.js} +24 -0
- package/calculations/{speculators/distance_to_tp_per_leverage.js → core/speculator-distance-to-tp-per-leverage.js} +24 -0
- package/calculations/{speculators/entry_distance_to_sl_per_leverage.js → core/speculator-entry-distance-to-sl-per-leverage.js} +24 -0
- package/calculations/{speculators/entry_distance_to_tp_per_leverage.js → core/speculator-entry-distance-to-tp-per-leverage.js} +24 -0
- package/calculations/{speculators/leverage_per_asset.js → core/speculator-leverage-per-asset.js} +20 -0
- package/calculations/{speculators/leverage_per_sector.js → core/speculator-leverage-per-sector.js} +22 -0
- package/calculations/{speculators/risk_reward_ratio_per_asset.js → core/speculator-risk-reward-ratio-per-asset.js} +20 -0
- package/calculations/{speculators/stop_loss_distance_by_sector_short_long_breakdown.js → core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js} +22 -0
- package/calculations/{speculators/stop_loss_distance_by_ticker_short_long_breakdown.js → core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js} +24 -0
- package/calculations/{speculators/stop_loss_per_asset.js → core/speculator-stop-loss-per-asset.js} +24 -0
- package/calculations/{speculators/take_profit_per_asset.js → core/speculator-take-profit-per-asset.js} +24 -0
- package/calculations/{speculators/tsl_per_asset.js → core/speculator-tsl-per-asset.js} +24 -0
- package/calculations/{short_and_long_stats/total_long_figures.js → core/total-long-figures.js} +24 -0
- package/calculations/{sectors/total_long_per_sector.js → core/total-long-per-sector.js} +22 -0
- package/calculations/{short_and_long_stats/total_short_figures.js → core/total-short-figures.js} +24 -0
- package/calculations/{sectors/total_short_per_sector.js → core/total-short-per-sector.js} +22 -0
- package/calculations/{sanity/users_processed.js → core/users-processed.js} +22 -0
- package/calculations/gauss/cohort-capital-flow.js +216 -0
- package/calculations/gauss/cohort-definer.js +211 -0
- package/calculations/gauss/daily-dna-filter.js +130 -0
- package/calculations/gauss/gauss-divergence-signal.js +160 -0
- package/calculations/{meta/gem_cohort-momentum-state.js → gem/cohort-momentum-state.js} +22 -7
- package/calculations/{behavioural/historical/gem_cohort-skill-definition.js → gem/cohort-skill-definition.js} +18 -1
- package/calculations/{sentiment/gem_platform-conviction-divergence.js → gem/platform-conviction-divergence.js} +13 -0
- package/calculations/{meta/gem_quant-skill-alpha-signal.js → gem/quant-skill-alpha-signal.js} +25 -8
- package/calculations/{behavioural/historical/gem_skilled-cohort-flow.js → gem/skilled-cohort-flow.js} +16 -2
- package/calculations/{meta/gem_skilled-unskilled-divergence.js → gem/skilled-unskilled-divergence.js} +18 -4
- package/calculations/{behavioural/historical/gem_unskilled-cohort-flow.js → gem/unskilled-cohort-flow.js} +16 -2
- package/calculations/helix/helix-contrarian-signal.js +154 -0
- package/calculations/helix/herd-consensus-score.js +152 -0
- package/calculations/helix/winner-loser-flow.js +206 -0
- package/calculations/{behavioural/historical → legacy}/asset_crowd_flow.js +1 -1
- package/calculations/{sentiment/historical → legacy}/crowd_conviction_score.js +1 -1
- package/calculations/{activity/historical → legacy}/daily_asset_activity.js +1 -1
- package/calculations/{behavioural/historical → legacy}/dumb-cohort-flow.js +1 -1
- package/calculations/{behavioural/historical → legacy}/in_loss_asset_crowd_flow.js +1 -1
- package/calculations/{behavioural/historical → legacy}/in_profit_asset_crowd_flow.js +1 -1
- package/calculations/{speculators/historical → legacy}/risk_appetite_change.js +3 -1
- package/calculations/{sectors/historical → legacy}/sector_rotation.js +1 -1
- package/calculations/{behavioural/historical → legacy}/smart-cohort-flow.js +1 -1
- package/calculations/{behavioural/historical → legacy}/smart_money_flow.js +1 -1
- package/calculations/{behavioural/historical → legacy}/user-investment-profile.js +2 -2
- package/calculations/pyro/risk-appetite-index.js +153 -0
- package/calculations/pyro/squeeze-potential.js +158 -0
- package/calculations/pyro/volatility-signal.js +133 -0
- package/package.json +1 -1
- package/calculations/socialPosts/gem_social_sentiment_aggregation.js +0 -146
- /package/calculations/{activity/historical → legacy}/activity_by_pnl_status.js +0 -0
- /package/calculations/{meta → legacy}/capital_deployment_strategy.js +0 -0
- /package/calculations/{meta → legacy}/capital_liquidation_performance.js +0 -0
- /package/calculations/{meta → legacy}/capital_vintage_performance.js +0 -0
- /package/calculations/{meta → legacy}/cash-flow-deployment.js +0 -0
- /package/calculations/{meta → legacy}/cash-flow-liquidation.js +0 -0
- /package/calculations/{capital_flow/historical → legacy}/crowd-cash-flow-proxy.js +0 -0
- /package/calculations/{meta → legacy}/crowd_sharpe_ratio_proxy.js +0 -0
- /package/calculations/{activity/historical → legacy}/daily_user_activity_tracker.js +0 -0
- /package/calculations/{capital_flow/historical → legacy}/deposit_withdrawal_percentage.js +0 -0
- /package/calculations/{sectors/historical → legacy}/diversification_pnl.js +0 -0
- /package/calculations/{behavioural/historical → legacy}/drawdown_response.js +0 -0
- /package/calculations/{behavioural/historical → legacy}/gain_response.js +0 -0
- /package/calculations/{behavioural/historical → legacy}/historical_performance_aggregator.js +0 -0
- /package/calculations/{meta → legacy}/negative_expectancy_cohort_flow.js +0 -0
- /package/calculations/{capital_flow/historical → legacy}/new_allocation_percentage.js +0 -0
- /package/calculations/{behavioural/historical → legacy}/paper_vs_diamond_hands.js +0 -0
- /package/calculations/{behavioural/historical → legacy}/position_count_pnl.js +0 -0
- /package/calculations/{meta → legacy}/positive_expectancy_cohort_flow.js +0 -0
- /package/calculations/{meta → legacy}/profit_cohort_divergence.js +0 -0
- /package/calculations/{pnl/historical → legacy}/profitability_migration.js +0 -0
- /package/calculations/{capital_flow/historical → legacy}/reallocation_increase_percentage.js +0 -0
- /package/calculations/{meta → legacy}/shark_attack_signal.js +0 -0
- /package/calculations/{meta → legacy}/smart-dumb-divergence-index.js +0 -0
- /package/calculations/{meta → legacy}/smart_dumb_divergence_index_v2.js +0 -0
- /package/calculations/{meta → legacy}/social-predictive-regime-state.js +0 -0
- /package/calculations/{meta → legacy}/social-topic-driver-index.js +0 -0
- /package/calculations/{meta → legacy}/social-topic-predictive-potential.js +0 -0
- /package/calculations/{meta → legacy}/social_flow_correlation.js +0 -0
- /package/calculations/{activity/historical → legacy}/speculator_adjustment_activity.js +0 -0
- /package/calculations/{backtests → legacy}/strategy-performance.js +0 -0
- /package/calculations/{speculators/historical → legacy}/tsl_effectiveness.js +0 -0
- /package/calculations/{meta → legacy}/user_expectancy_score.js +0 -0
- /package/calculations/{pnl/historical → legacy}/user_profitability_tracker.js +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HELIX Product Line (Pass 3)
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the 'Herd Consensus Score' for
|
|
5
|
+
* each asset?"
|
|
6
|
+
*
|
|
7
|
+
* It blends sentiment from three different views of the herd:
|
|
8
|
+
* 1. The entire Platform (from gem/platform-conviction-divergence)
|
|
9
|
+
* 2. Our 20k User Sample (from core/sentiment-per-stock)
|
|
10
|
+
* 3. Social Media (from core/social-sentiment-aggregation)
|
|
11
|
+
*
|
|
12
|
+
* The score is normalized from -10 (Extreme Bearish Consensus)
|
|
13
|
+
* to +10 (Extreme Bullish Consensus).
|
|
14
|
+
*/
|
|
15
|
+
class HerdConsensusScore {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Defines the output schema for this calculation.
|
|
19
|
+
* @returns {object} JSON Schema object
|
|
20
|
+
*/
|
|
21
|
+
static getSchema() {
|
|
22
|
+
const tickerSchema = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"consensus_score": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Blended score (-10 to +10) of platform, sample, and social sentiment."
|
|
28
|
+
},
|
|
29
|
+
"platform_sentiment": { "type": "number" },
|
|
30
|
+
"sample_sentiment": { "type": "number" },
|
|
31
|
+
"social_sentiment": { "type": "number" }
|
|
32
|
+
},
|
|
33
|
+
"required": ["consensus_score"]
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Calculates a blended 'Herd Consensus Score' (-10 to +10) per asset.",
|
|
39
|
+
"patternProperties": {
|
|
40
|
+
"^.*$": tickerSchema // Ticker
|
|
41
|
+
},
|
|
42
|
+
"additionalProperties": tickerSchema
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Statically defines all metadata for the manifest builder.
|
|
48
|
+
*/
|
|
49
|
+
static getMetadata() {
|
|
50
|
+
return {
|
|
51
|
+
type: 'meta',
|
|
52
|
+
rootDataDependencies: [],
|
|
53
|
+
isHistorical: false,
|
|
54
|
+
userType: 'n/a',
|
|
55
|
+
category: 'helix'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Statically declare dependencies.
|
|
61
|
+
*/
|
|
62
|
+
static getDependencies() {
|
|
63
|
+
return [
|
|
64
|
+
// This is the only source of *per-ticker* platform sentiment
|
|
65
|
+
'platform-conviction-divergence', // from gem
|
|
66
|
+
'sentiment-per-stock', // from core
|
|
67
|
+
'social-sentiment-aggregation' // from core
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalizes a 0-100 value (like platform_long_pct) to -1.0 to +1.0
|
|
73
|
+
*/
|
|
74
|
+
_normalizePercent(v) {
|
|
75
|
+
if (v === null || v === undefined) return 0;
|
|
76
|
+
const score = (v - 50) / 50; // 50 -> 0, 100 -> 1, 0 -> -1
|
|
77
|
+
return Math.max(-1, Math.min(1, score));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Normalizes a net percent value (-100 to 100) to -1.0 to +1.0
|
|
82
|
+
*/
|
|
83
|
+
_normalizeNetPercent(v) {
|
|
84
|
+
if (v === null || v === undefined) return 0;
|
|
85
|
+
const score = v / 100;
|
|
86
|
+
return Math.max(-1, Math.min(1, score));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalizes a ratio (0 to Inf) to -1.0 to +1.0
|
|
91
|
+
* 1.0 -> 0.0 (Neutral)
|
|
92
|
+
* 0.5 -> -1.0 (Bearish)
|
|
93
|
+
* 2.0 -> +1.0 (Bullish)
|
|
94
|
+
*/
|
|
95
|
+
_normalizeRatio(v) {
|
|
96
|
+
if (v === null || v === undefined || v <= 0) return -1.0;
|
|
97
|
+
const score = Math.log2(v); // log2(1)=0, log2(0.5)=-1, log2(2)=1
|
|
98
|
+
return Math.max(-1, Math.min(1, score));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* This is a 'meta' calculation. It runs once.
|
|
103
|
+
*/
|
|
104
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
105
|
+
const { logger } = dependencies;
|
|
106
|
+
|
|
107
|
+
const platformData = fetchedDependencies['platform-conviction-divergence'];
|
|
108
|
+
const sampleData = fetchedDependencies['sentiment-per-stock'];
|
|
109
|
+
const socialData = fetchedDependencies['social-sentiment-aggregation']?.per_ticker;
|
|
110
|
+
|
|
111
|
+
if (!platformData || !sampleData || !socialData) {
|
|
112
|
+
logger.log('WARN', `[helix/herd-consensus-score] Missing dependencies for ${dateStr}. Skipping.`);
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const allTickers = new Set([
|
|
117
|
+
...Object.keys(platformData),
|
|
118
|
+
...Object.keys(sampleData),
|
|
119
|
+
...Object.keys(socialData)
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const result = {};
|
|
123
|
+
|
|
124
|
+
for (const ticker of allTickers) {
|
|
125
|
+
// 1. Platform Sentiment (0-100) -> -1 to +1
|
|
126
|
+
const plat_long_pct = platformData[ticker]?.platform_long_pct;
|
|
127
|
+
const s_plat = this._normalizePercent(plat_long_pct);
|
|
128
|
+
|
|
129
|
+
// 2. Sample Sentiment (Ratio) -> -1 to +1
|
|
130
|
+
const sample_ratio = sampleData[ticker]?.sentiment_ratio;
|
|
131
|
+
const s_samp = this._normalizeRatio(sample_ratio);
|
|
132
|
+
|
|
133
|
+
// 3. Social Sentiment (-100 to 100) -> -1 to +1
|
|
134
|
+
const social_pct = socialData[ticker]?.net_sentiment_pct;
|
|
135
|
+
const s_soc = this._normalizeNetPercent(social_pct);
|
|
136
|
+
|
|
137
|
+
// Blend scores (equal weight) and scale to -10 to +10
|
|
138
|
+
const consensus_score = ((s_plat + s_samp + s_soc) / 3) * 10;
|
|
139
|
+
|
|
140
|
+
result[ticker] = {
|
|
141
|
+
consensus_score: consensus_score,
|
|
142
|
+
platform_sentiment: s_plat,
|
|
143
|
+
sample_sentiment: s_samp,
|
|
144
|
+
social_sentiment: s_soc
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = HerdConsensusScore;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what is the net flow
|
|
5
|
+
* of *unique users* into the 'Winner' (in-profit) and 'Loser'
|
|
6
|
+
* (in-loss) cohorts?"
|
|
7
|
+
*
|
|
8
|
+
* e.g., "Today, 150 losers capitulated (closed) on AAPL,
|
|
9
|
+
* while 25 winners joined (opened)."
|
|
10
|
+
*
|
|
11
|
+
* This is a 'standard' calc because it must iterate over all
|
|
12
|
+
* users to compare their T-1 vs T portfolios.
|
|
13
|
+
*/
|
|
14
|
+
const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
|
|
15
|
+
|
|
16
|
+
class WinnerLoserFlow {
|
|
17
|
+
constructor() {
|
|
18
|
+
// We will store { [ticker]: { winners_joined: 0, ... } }
|
|
19
|
+
this.assetFlows = new Map();
|
|
20
|
+
this.cohorts = null; // Caches cohort data
|
|
21
|
+
this.mappings = null; // Caches mappings
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Defines the output schema for this calculation.
|
|
26
|
+
* @returns {object} JSON Schema object
|
|
27
|
+
*/
|
|
28
|
+
static getSchema() {
|
|
29
|
+
const tickerSchema = {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"net_winner_flow": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Net users joining the 'Winner' cohort (Joined - Left)."
|
|
35
|
+
},
|
|
36
|
+
"net_loser_flow": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"description": "Net users joining the 'Loser' cohort (Joined - Left)."
|
|
39
|
+
},
|
|
40
|
+
"raw_counts": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"winners_joined": { "type": "number" },
|
|
44
|
+
"winners_left": { "type": "number" },
|
|
45
|
+
"losers_joined": { "type": "number" },
|
|
46
|
+
"losers_left": { "type": "number" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "Tracks the net flow of unique users into/out of the Winner (in-profit) and Loser (in-loss) cohorts per asset.",
|
|
56
|
+
"patternProperties": {
|
|
57
|
+
"^.*$": tickerSchema // Ticker
|
|
58
|
+
},
|
|
59
|
+
"additionalProperties": tickerSchema
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Statically defines all metadata for the manifest builder.
|
|
65
|
+
*/
|
|
66
|
+
static getMetadata() {
|
|
67
|
+
return {
|
|
68
|
+
type: 'standard',
|
|
69
|
+
rootDataDependencies: ['portfolio'],
|
|
70
|
+
isHistorical: true, // Needs T-1 portfolio
|
|
71
|
+
userType: 'all',
|
|
72
|
+
category: 'helix'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Statically declare dependencies.
|
|
78
|
+
*/
|
|
79
|
+
static getDependencies() {
|
|
80
|
+
return [
|
|
81
|
+
'asset-pnl-status' // from core
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Helper to load cohort data from the dependency (runs once).
|
|
87
|
+
*/
|
|
88
|
+
_loadCohorts(fetchedDependencies) {
|
|
89
|
+
if (this.cohorts) return;
|
|
90
|
+
|
|
91
|
+
const pnlStatusData = fetchedDependencies['asset-pnl-status'];
|
|
92
|
+
if (!pnlStatusData) {
|
|
93
|
+
this.cohorts = { winnerMap: new Map(), loserMap: new Map() };
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const winnerMap = new Map();
|
|
98
|
+
const loserMap = new Map();
|
|
99
|
+
|
|
100
|
+
for (const [ticker, data] of Object.entries(pnlStatusData)) {
|
|
101
|
+
// 'users_in_profit' and 'users_in_loss' are arrays of user IDs
|
|
102
|
+
winnerMap.set(ticker, new Set(data.users_in_profit || []));
|
|
103
|
+
loserMap.set(ticker, new Set(data.users_in_loss || []));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.cohorts = { winnerMap, loserMap };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Helper to get a user's holdings (Instrument IDs)
|
|
111
|
+
*/
|
|
112
|
+
_getHoldings(portfolio) {
|
|
113
|
+
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
114
|
+
if (!positions || !Array.isArray(positions)) {
|
|
115
|
+
return new Set();
|
|
116
|
+
}
|
|
117
|
+
return new Set(positions.map(p => p.InstrumentID).filter(Boolean));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Initialize asset flow counters
|
|
122
|
+
*/
|
|
123
|
+
_initAsset(ticker) {
|
|
124
|
+
if (!this.assetFlows.has(ticker)) {
|
|
125
|
+
this.assetFlows.set(ticker, {
|
|
126
|
+
winners_joined: 0,
|
|
127
|
+
winners_left: 0,
|
|
128
|
+
losers_joined: 0,
|
|
129
|
+
losers_left: 0
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
135
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Load mappings and cohort data on the first user
|
|
140
|
+
if (!this.mappings) {
|
|
141
|
+
this.mappings = context.mappings;
|
|
142
|
+
}
|
|
143
|
+
this._loadCohorts(fetchedDependencies);
|
|
144
|
+
|
|
145
|
+
const yHoldings = this._getHoldings(yesterdayPortfolio); // Set<InstrumentID>
|
|
146
|
+
const tHoldings = this._getHoldings(todayPortfolio); // Set<InstrumentID>
|
|
147
|
+
|
|
148
|
+
const allInstrumentIds = new Set([...yHoldings, ...tHoldings]);
|
|
149
|
+
if (allInstrumentIds.size === 0) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const instrumentId of allInstrumentIds) {
|
|
154
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId];
|
|
155
|
+
if (!ticker) continue;
|
|
156
|
+
|
|
157
|
+
const isWinner = this.cohorts.winnerMap.get(ticker)?.has(userId) || false;
|
|
158
|
+
const isLoser = this.cohorts.loserMap.get(ticker)?.has(userId) || false;
|
|
159
|
+
|
|
160
|
+
// We only care about users who are *currently* in a cohort
|
|
161
|
+
if (!isWinner && !isLoser) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const heldYesterday = yHoldings.has(instrumentId);
|
|
166
|
+
const holdsToday = tHoldings.has(instrumentId);
|
|
167
|
+
|
|
168
|
+
// Skip if no change in holding status
|
|
169
|
+
if (heldYesterday === holdsToday) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._initAsset(ticker);
|
|
174
|
+
const stats = this.assetFlows.get(ticker);
|
|
175
|
+
|
|
176
|
+
if (isWinner) {
|
|
177
|
+
if (holdsToday && !heldYesterday) stats.winners_joined++;
|
|
178
|
+
if (!holdsToday && heldYesterday) stats.winners_left++;
|
|
179
|
+
}
|
|
180
|
+
if (isLoser) {
|
|
181
|
+
if (holdsToday && !heldYesterday) stats.losers_joined++;
|
|
182
|
+
if (!holdsToday && heldYesterday) stats.losers_left++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async getResult() {
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const [ticker, data] of this.assetFlows.entries()) {
|
|
190
|
+
result[ticker] = {
|
|
191
|
+
net_winner_flow: data.winners_joined - data.winners_left,
|
|
192
|
+
net_loser_flow: data.losers_joined - data.losers_left,
|
|
193
|
+
raw_counts: data
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
reset() {
|
|
200
|
+
this.assetFlows.clear();
|
|
201
|
+
this.cohorts = null;
|
|
202
|
+
this.mappings = null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = WinnerLoserFlow;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This isolates true buying/selling pressure from simple value changes.
|
|
9
9
|
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('
|
|
10
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
11
|
|
|
12
12
|
class AssetCrowdFlow {
|
|
13
13
|
constructor() {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* This is a *stateful* calculation that computes a 30-day
|
|
14
14
|
* rolling average of these metrics to build the score.
|
|
15
15
|
*/
|
|
16
|
-
const { loadInstrumentMappings } = require('
|
|
16
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
17
17
|
|
|
18
18
|
class CrowdConvictionScore {
|
|
19
19
|
constructor() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Tracks the flow of unique users opening or closing positions
|
|
3
3
|
* on a per-asset basis. This measures the "focus" of the crowd's activity.
|
|
4
4
|
*/
|
|
5
|
-
const { loadInstrumentMappings } = require('
|
|
5
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
6
|
|
|
7
7
|
class DailyAssetActivity {
|
|
8
8
|
constructor() {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* This calculation *depends* on 'user_profitability_tracker'
|
|
8
8
|
* to identify the cohort.
|
|
9
9
|
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('
|
|
10
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
11
|
|
|
12
12
|
class DumbCohortFlow {
|
|
13
13
|
constructor() {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This calculation *depends* on 'asset_pnl_status' to identify the cohort.
|
|
11
11
|
*/
|
|
12
|
-
const { loadInstrumentMappings } = require('
|
|
12
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
13
13
|
|
|
14
14
|
class InLossAssetCrowdFlow {
|
|
15
15
|
constructor() {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This calculation *depends* on 'asset_pnl_status' to identify the cohort.
|
|
11
11
|
*/
|
|
12
|
-
const { loadInstrumentMappings } = require('
|
|
12
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
13
13
|
|
|
14
14
|
class InProfitAssetCrowdFlow {
|
|
15
15
|
constructor() {
|
|
@@ -61,9 +61,11 @@ class RiskAppetiteChange {
|
|
|
61
61
|
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
62
62
|
// --- FIX ---
|
|
63
63
|
// Add null check to prevent crash on the first day of processing
|
|
64
|
-
if (!todayPortfolio || !
|
|
64
|
+
if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.Portfolio || !yesterdayPortfolio.Portfolio) {
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
|
|
67
69
|
// --- END FIX ---
|
|
68
70
|
|
|
69
71
|
// 1. Get portfolio snapshots
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This measures which sectors the crowd is rotating into or out of.
|
|
8
8
|
*/
|
|
9
|
-
const { loadInstrumentMappings } = require('
|
|
9
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
10
10
|
|
|
11
11
|
class SectorRotation {
|
|
12
12
|
constructor() {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* This calculation *depends* on 'user_profitability_tracker'
|
|
8
8
|
* to identify the cohort.
|
|
9
9
|
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('
|
|
10
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
11
|
|
|
12
12
|
class SmartCohortFlow {
|
|
13
13
|
constructor() {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This calculation *depends* on 'user_profitability_tracker'
|
|
9
9
|
* to identify the cohort.
|
|
10
10
|
*/
|
|
11
|
-
const { loadInstrumentMappings } = require('
|
|
11
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
12
12
|
|
|
13
13
|
class SmartMoneyFlow {
|
|
14
14
|
constructor() {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
const { Firestore } = require('@google-cloud/firestore');
|
|
11
11
|
const firestore = new Firestore();
|
|
12
12
|
// NOTE: Corrected relative path for data_loader
|
|
13
|
-
const { loadDataByRefs, getPortfolioPartRefs, loadFullDayMap } = require('
|
|
14
|
-
const { loadAllPriceData, getInstrumentSectorMap } = require('
|
|
13
|
+
const { loadDataByRefs, getPortfolioPartRefs, loadFullDayMap } = require('../../../bulltrackers-module/functions/computation-system/utils/data_loader');
|
|
14
|
+
const { loadAllPriceData, getInstrumentSectorMap } = require('../../utils/price_data_provider');
|
|
15
15
|
|
|
16
16
|
// Config
|
|
17
17
|
const NUM_SHARDS = 50;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview PYRO Product Line (Pass 2)
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each asset, what is the 'Risk-On'
|
|
5
|
+
* score from speculators?"
|
|
6
|
+
*
|
|
7
|
+
* It combines the average leverage being used with the
|
|
8
|
+
* average Risk/Reward ratio being set. A high score means
|
|
9
|
+
* speculators are confident: using high leverage *and*
|
|
10
|
+
* setting wide R/R targets.
|
|
11
|
+
*/
|
|
12
|
+
class RiskAppetiteIndex {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Defines the output schema for this calculation.
|
|
16
|
+
* @returns {object} JSON Schema object
|
|
17
|
+
*/
|
|
18
|
+
static getSchema() {
|
|
19
|
+
const tickerSchema = {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"risk_appetite_score": {
|
|
23
|
+
"type": "number",
|
|
24
|
+
"description": "Blended score (0-10) of leverage use and R/R conviction."
|
|
25
|
+
},
|
|
26
|
+
"avg_leverage": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Weighted average leverage used on this asset."
|
|
29
|
+
},
|
|
30
|
+
"avg_rr_ratio": {
|
|
31
|
+
"type": "number",
|
|
32
|
+
"description": "Average R/R ratio set on this asset."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["risk_appetite_score", "avg_leverage", "avg_rr_ratio"]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"description": "Calculates a 'Risk-On' score for speculators per asset.",
|
|
41
|
+
"patternProperties": {
|
|
42
|
+
"^.*$": tickerSchema // Ticker
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": tickerSchema
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Statically defines all metadata for the manifest builder.
|
|
50
|
+
*/
|
|
51
|
+
static getMetadata() {
|
|
52
|
+
return {
|
|
53
|
+
type: 'meta',
|
|
54
|
+
rootDataDependencies: [],
|
|
55
|
+
isHistorical: false,
|
|
56
|
+
userType: 'n/a',
|
|
57
|
+
category: 'pyro'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Statically declare dependencies.
|
|
63
|
+
*/
|
|
64
|
+
static getDependencies() {
|
|
65
|
+
return [
|
|
66
|
+
'speculator-leverage-per-asset', // from core
|
|
67
|
+
'speculator-risk-reward-ratio-per-asset' // from core
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Helper to calculate weighted average leverage from buckets.
|
|
73
|
+
* e.g., { "1x": 10, "5x": 5 }
|
|
74
|
+
*/
|
|
75
|
+
_calculateAvgLeverage(levBuckets) {
|
|
76
|
+
if (!levBuckets || typeof levBuckets !== 'object') {
|
|
77
|
+
return 1; // Default to 1x leverage
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let totalCount = 0;
|
|
81
|
+
let weightedSum = 0;
|
|
82
|
+
|
|
83
|
+
for (const [key, count] of Object.entries(levBuckets)) {
|
|
84
|
+
const leverage = parseInt(key, 10); // "5x" -> 5
|
|
85
|
+
if (!isNaN(leverage) && count > 0) {
|
|
86
|
+
totalCount += count;
|
|
87
|
+
weightedSum += (leverage * count);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (totalCount === 0) {
|
|
92
|
+
return 1; // Default to 1x if no positions
|
|
93
|
+
}
|
|
94
|
+
return weightedSum / totalCount;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Simple min-max normalization, clamped 0-1.
|
|
99
|
+
*/
|
|
100
|
+
_normalize(value, min, max) {
|
|
101
|
+
const normalized = (value - min) / (max - min);
|
|
102
|
+
return Math.max(0, Math.min(1, normalized));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* This is a 'meta' calculation. It runs once.
|
|
107
|
+
* @param {string} dateStr - The date string 'YYYY-MM-DD'.
|
|
108
|
+
* @param {object} dependencies - The shared dependencies (e.g., logger).
|
|
109
|
+
* @param {object} config - The computation system configuration.
|
|
110
|
+
* @param {object} fetchedDependencies - Results from Pass 1.
|
|
111
|
+
* @returns {Promise<object>} The calculation result.
|
|
112
|
+
*/
|
|
113
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
114
|
+
const { logger } = dependencies;
|
|
115
|
+
|
|
116
|
+
const leverageData = fetchedDependencies['speculator-leverage-per-asset'];
|
|
117
|
+
const rrData = fetchedDependencies['speculator-risk-reward-ratio-per-asset'];
|
|
118
|
+
|
|
119
|
+
if (!leverageData || !rrData) {
|
|
120
|
+
logger.log('WARN', `[pyro/risk-appetite-index] Missing core dependencies for ${dateStr}. Skipping.`);
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const allTickers = new Set([
|
|
125
|
+
...Object.keys(leverageData),
|
|
126
|
+
...Object.keys(rrData)
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const result = {};
|
|
130
|
+
|
|
131
|
+
for (const ticker of allTickers) {
|
|
132
|
+
const avg_leverage = this._calculateAvgLeverage(leverageData[ticker]);
|
|
133
|
+
const avg_rr_ratio = rrData[ticker]?.avg_rr_ratio || 0;
|
|
134
|
+
|
|
135
|
+
// Normalize: Leverage from 1x-10x, R/R from 0-5
|
|
136
|
+
const norm_lev = this._normalize(avg_leverage, 1, 10);
|
|
137
|
+
const norm_rr = this._normalize(avg_rr_ratio, 0, 5);
|
|
138
|
+
|
|
139
|
+
// Combine scores and scale to 0-10
|
|
140
|
+
const risk_appetite_score = (norm_lev * 0.6 + norm_rr * 0.4) * 10;
|
|
141
|
+
|
|
142
|
+
result[ticker] = {
|
|
143
|
+
risk_appetite_score: risk_appetite_score,
|
|
144
|
+
avg_leverage: avg_leverage,
|
|
145
|
+
avg_rr_ratio: avg_rr_ratio
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = RiskAppetiteIndex;
|