aiden-shared-calculations-unified 1.0.86 → 1.0.88
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 +96 -0
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
- package/calculations/core/asset-cost-basis-profile.js +127 -0
- package/calculations/core/asset-pnl-status.js +36 -106
- package/calculations/core/asset-position-size.js +40 -91
- package/calculations/core/average-daily-pnl-all-users.js +18 -57
- package/calculations/core/average-daily-pnl-per-sector.js +41 -88
- package/calculations/core/average-daily-pnl-per-stock.js +38 -91
- package/calculations/core/average-daily-position-pnl.js +19 -49
- package/calculations/core/holding-duration-per-asset.js +25 -127
- package/calculations/core/instrument-price-change-1d.js +30 -49
- package/calculations/core/instrument-price-momentum-20d.js +50 -60
- package/calculations/core/long-position-per-stock.js +39 -68
- package/calculations/core/overall-holding-duration.js +16 -87
- package/calculations/core/overall-profitability-ratio.js +11 -40
- package/calculations/core/platform-buy-sell-sentiment.js +41 -124
- package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
- package/calculations/core/platform-daily-ownership-delta.js +68 -126
- package/calculations/core/platform-ownership-per-sector.js +45 -96
- package/calculations/core/platform-total-positions-held.js +20 -80
- package/calculations/core/pnl-distribution-per-stock.js +29 -135
- package/calculations/core/price-metrics.js +95 -206
- package/calculations/core/profitability-ratio-per-sector.js +34 -79
- package/calculations/core/profitability-ratio-per-stock.js +32 -88
- package/calculations/core/profitability-skew-per-stock.js +41 -94
- package/calculations/core/profitable-and-unprofitable-status.js +44 -76
- package/calculations/core/sentiment-per-stock.js +24 -77
- package/calculations/core/short-position-per-stock.js +35 -43
- package/calculations/core/social-activity-aggregation.js +26 -49
- package/calculations/core/social-asset-posts-trend.js +38 -94
- package/calculations/core/social-event-correlation.js +26 -93
- package/calculations/core/social-sentiment-aggregation.js +20 -44
- package/calculations/core/social-top-mentioned-words.js +35 -87
- package/calculations/core/social-topic-interest-evolution.js +22 -111
- package/calculations/core/social-topic-sentiment-matrix.js +38 -104
- package/calculations/core/social-word-mentions-trend.js +27 -104
- package/calculations/core/speculator-asset-sentiment.js +31 -72
- package/calculations/core/speculator-danger-zone.js +48 -84
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
- package/calculations/core/speculator-leverage-per-asset.js +25 -64
- package/calculations/core/speculator-leverage-per-sector.js +27 -63
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
- package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
- package/calculations/core/speculator-take-profit-per-asset.js +20 -57
- package/calculations/core/speculator-tsl-per-asset.js +17 -56
- package/calculations/core/test..js +0 -0
- package/calculations/core/total-long-figures.js +16 -31
- package/calculations/core/total-long-per-sector.js +39 -61
- package/calculations/core/total-short-figures.js +13 -32
- package/calculations/core/total-short-per-sector.js +39 -61
- package/calculations/core/users-processed.js +11 -46
- package/calculations/gauss/cohort-capital-flow.js +54 -173
- package/calculations/gauss/cohort-definer.js +77 -163
- package/calculations/gauss/daily-dna-filter.js +29 -83
- package/calculations/gauss/gauss-divergence-signal.js +22 -109
- package/calculations/gem/cohort-momentum-state.js +27 -72
- package/calculations/gem/cohort-skill-definition.js +36 -52
- package/calculations/gem/platform-conviction-divergence.js +18 -60
- package/calculations/gem/quant-skill-alpha-signal.js +25 -98
- package/calculations/gem/skilled-cohort-flow.js +67 -175
- package/calculations/gem/skilled-unskilled-divergence.js +18 -73
- package/calculations/gem/unskilled-cohort-flow.js +64 -172
- package/calculations/ghost-book/cost-basis-density.js +79 -0
- package/calculations/ghost-book/liquidity-vacuum.js +52 -0
- package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
- package/calculations/helix/helix-contrarian-signal.js +20 -114
- package/calculations/helix/herd-consensus-score.js +42 -124
- package/calculations/helix/winner-loser-flow.js +36 -118
- package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
- package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
- package/calculations/predicative-alpha/mimetic-latency.js +124 -0
- package/calculations/pyro/risk-appetite-index.js +33 -74
- package/calculations/pyro/squeeze-potential.js +30 -87
- package/calculations/pyro/volatility-signal.js +33 -78
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CORE Product Line (Pass 1 - Meta)
|
|
3
|
+
* Calculates annualized volatility using the new priceExtractor.
|
|
4
|
+
*/
|
|
5
|
+
class AssetVolatilityEstimator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.result = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static getMetadata() {
|
|
11
|
+
return {
|
|
12
|
+
type: 'meta',
|
|
13
|
+
rootDataDependencies: ['price'],
|
|
14
|
+
isHistorical: false,
|
|
15
|
+
userType: 'n/a',
|
|
16
|
+
category: 'market_stats'
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getDependencies() { return []; }
|
|
21
|
+
|
|
22
|
+
static getSchema() {
|
|
23
|
+
const tickerSchema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"volatility_30d": { "type": "number" },
|
|
27
|
+
"last_price": { "type": "number" },
|
|
28
|
+
"data_points": { "type": "number" }
|
|
29
|
+
},
|
|
30
|
+
"required": ["volatility_30d"]
|
|
31
|
+
};
|
|
32
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process(context) {
|
|
36
|
+
// FIXED: Destructure 'mappings' to resolve real tickers
|
|
37
|
+
const { math, prices, mappings } = context;
|
|
38
|
+
|
|
39
|
+
// FIXED: Destructure 'priceExtractor' directly (matches worker.js injection)
|
|
40
|
+
const { compute, priceExtractor } = math;
|
|
41
|
+
|
|
42
|
+
// 1. Get ALL histories properly sorted and parsed
|
|
43
|
+
const allHistories = priceExtractor.getAllHistories(prices);
|
|
44
|
+
|
|
45
|
+
for (const [key, candles] of allHistories.entries()) {
|
|
46
|
+
// RESOLUTION FIX:
|
|
47
|
+
// 'key' is likely an index string ("0") because mock data is an array.
|
|
48
|
+
// We must resolve this to the real Ticker Symbol for the result map.
|
|
49
|
+
let ticker = key;
|
|
50
|
+
|
|
51
|
+
if (prices.history && prices.history[key] && prices.history[key].instrumentId) {
|
|
52
|
+
const instId = prices.history[key].instrumentId;
|
|
53
|
+
if (mappings && mappings.instrumentToTicker && mappings.instrumentToTicker[instId]) {
|
|
54
|
+
ticker = mappings.instrumentToTicker[instId];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (candles.length < 10) continue;
|
|
59
|
+
|
|
60
|
+
// 2. Calculate Log Returns
|
|
61
|
+
const logReturns = [];
|
|
62
|
+
let lastPrice = 0;
|
|
63
|
+
|
|
64
|
+
for (let i = 1; i < candles.length; i++) {
|
|
65
|
+
const prev = candles[i-1].price;
|
|
66
|
+
const curr = candles[i].price;
|
|
67
|
+
|
|
68
|
+
if (prev > 0 && curr > 0) {
|
|
69
|
+
logReturns.push(Math.log(curr / prev));
|
|
70
|
+
lastPrice = curr;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. Filter 30-Day Lookback
|
|
75
|
+
const LOOKBACK = 30;
|
|
76
|
+
const relevantReturns = logReturns.slice(-LOOKBACK);
|
|
77
|
+
|
|
78
|
+
if (relevantReturns.length < 5) continue;
|
|
79
|
+
|
|
80
|
+
// 4. Calculate Stats
|
|
81
|
+
const stdDev = compute.standardDeviation(relevantReturns);
|
|
82
|
+
const annualizedVol = stdDev * Math.sqrt(365);
|
|
83
|
+
|
|
84
|
+
this.result[ticker] = {
|
|
85
|
+
volatility_30d: annualizedVol,
|
|
86
|
+
last_price: lastPrice,
|
|
87
|
+
data_points: relevantReturns.length
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getResult() { return this.result; }
|
|
93
|
+
reset() { this.result = {}; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = AssetVolatilityEstimator;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CORE Product Line (Pass 2 - Standard)
|
|
3
|
+
* Calculates capitulation risk using DYNAMIC volatility from asset-volatility-estimator.
|
|
4
|
+
* Relies on 'schema.md' definitions for portfolio extraction via MathPrimitives.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class RetailCapitulationRiskForecast {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.assetRiskProfiles = new Map();
|
|
10
|
+
this.tickerMap = null;
|
|
11
|
+
|
|
12
|
+
// FIX: Use a container object (like the Maps in other calculations)
|
|
13
|
+
// This allows us to MUTATE 'deps.mathLib' instead of RE-ASSIGNING 'this.mathLib'
|
|
14
|
+
this.deps = { mathLib: null };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static getMetadata() {
|
|
18
|
+
return {
|
|
19
|
+
type: 'standard',
|
|
20
|
+
rootDataDependencies: ['portfolio', 'history'],
|
|
21
|
+
isHistorical: false,
|
|
22
|
+
userType: 'all',
|
|
23
|
+
category: 'risk_models'
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static getDependencies() {
|
|
28
|
+
return ['asset-volatility-estimator'];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static getSchema() {
|
|
32
|
+
const tickerSchema = {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"capitulation_probability": { "type": "number" },
|
|
36
|
+
"at_risk_user_count": { "type": "number" },
|
|
37
|
+
"average_pain_threshold_pct": { "type": "number" },
|
|
38
|
+
"used_volatility": { "type": "number" }
|
|
39
|
+
},
|
|
40
|
+
"required": ["capitulation_probability", "at_risk_user_count"]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_initAsset(ticker, currentPrice, volatility) {
|
|
47
|
+
if (!this.assetRiskProfiles.has(ticker)) {
|
|
48
|
+
this.assetRiskProfiles.set(ticker, {
|
|
49
|
+
currentPrice: currentPrice,
|
|
50
|
+
volatility: volatility,
|
|
51
|
+
profiles: []
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process(context) {
|
|
57
|
+
const { user, mappings, math, computed } = context;
|
|
58
|
+
const { extract, history, compute, signals } = math;
|
|
59
|
+
|
|
60
|
+
// 1. Capture Libraries via MUTATION
|
|
61
|
+
// We are retrieving the 'deps' object (GET) and mutating its property.
|
|
62
|
+
// This bypasses the Proxy SET trap entirely.
|
|
63
|
+
if (!this.deps.mathLib && compute) {
|
|
64
|
+
this.deps.mathLib = compute;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!this.tickerMap) {
|
|
68
|
+
this.tickerMap = mappings.instrumentToTicker;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Determine User's "Pain Threshold"
|
|
72
|
+
const historyDoc = history.getDailyHistory(user);
|
|
73
|
+
const summary = history.getSummary(historyDoc);
|
|
74
|
+
let personalPainThreshold = (summary && summary.avgLossPct < 0)
|
|
75
|
+
? summary.avgLossPct
|
|
76
|
+
: -25.0;
|
|
77
|
+
|
|
78
|
+
// 3. Analyze Positions
|
|
79
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
80
|
+
|
|
81
|
+
for (const pos of positions) {
|
|
82
|
+
const instId = extract.getInstrumentId(pos);
|
|
83
|
+
|
|
84
|
+
if (!this.tickerMap) continue;
|
|
85
|
+
|
|
86
|
+
const ticker = this.tickerMap[instId];
|
|
87
|
+
|
|
88
|
+
if (!ticker) continue;
|
|
89
|
+
|
|
90
|
+
// Fetch Dependency
|
|
91
|
+
const assetStats = signals.getPreviousState(computed, 'asset-volatility-estimator', ticker);
|
|
92
|
+
|
|
93
|
+
const dynamicVol = assetStats ? assetStats.volatility_30d : 0.60;
|
|
94
|
+
const currentPrice = assetStats ? assetStats.last_price : 0;
|
|
95
|
+
|
|
96
|
+
if (currentPrice <= 0) continue;
|
|
97
|
+
|
|
98
|
+
// Get P&L from Position Schema
|
|
99
|
+
const netProfit = extract.getNetProfit(pos);
|
|
100
|
+
|
|
101
|
+
// Calculate Entry Price using the Dependency Price
|
|
102
|
+
const entryPrice = extract.deriveEntryPrice(currentPrice, netProfit);
|
|
103
|
+
|
|
104
|
+
if (entryPrice > 0) {
|
|
105
|
+
this._initAsset(ticker, currentPrice, dynamicVol);
|
|
106
|
+
this.assetRiskProfiles.get(ticker).profiles.push({
|
|
107
|
+
entryPrice: entryPrice,
|
|
108
|
+
thresholdPct: personalPainThreshold
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getResult() {
|
|
115
|
+
const result = {};
|
|
116
|
+
const TIME_HORIZON_DAYS = 3;
|
|
117
|
+
const SIMULATION_COUNT = 1000;
|
|
118
|
+
|
|
119
|
+
// Access the library from the container
|
|
120
|
+
const mathLib = this.deps.mathLib;
|
|
121
|
+
|
|
122
|
+
if (!mathLib || !mathLib.simulateGBM) {
|
|
123
|
+
console.log('[DEBUG RCRF] MathLib missing in deps container!');
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [ticker, data] of this.assetRiskProfiles.entries()) {
|
|
128
|
+
if (data.profiles.length < 1) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// 1. Generate Price Paths
|
|
134
|
+
const pricePaths = mathLib.simulateGBM(
|
|
135
|
+
data.currentPrice,
|
|
136
|
+
data.volatility,
|
|
137
|
+
TIME_HORIZON_DAYS,
|
|
138
|
+
SIMULATION_COUNT
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!pricePaths || pricePaths.length === 0) continue;
|
|
142
|
+
|
|
143
|
+
// 2. Run Population Breakdown
|
|
144
|
+
const capitulationProb = mathLib.simulatePopulationBreakdown(
|
|
145
|
+
pricePaths,
|
|
146
|
+
data.profiles
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const totalThreshold = data.profiles.reduce((acc, p) => acc + p.thresholdPct, 0);
|
|
150
|
+
const avgThreshold = totalThreshold / data.profiles.length;
|
|
151
|
+
|
|
152
|
+
result[ticker] = {
|
|
153
|
+
capitulation_probability: parseFloat(capitulationProb.toFixed(4)),
|
|
154
|
+
at_risk_user_count: data.profiles.length,
|
|
155
|
+
average_pain_threshold_pct: parseFloat(avgThreshold.toFixed(2)),
|
|
156
|
+
used_volatility: parseFloat(data.volatility.toFixed(4))
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.log(`[DEBUG RCRF] Error processing ${ticker}: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
reset() {
|
|
167
|
+
this.assetRiskProfiles.clear();
|
|
168
|
+
this.tickerMap = null;
|
|
169
|
+
this.deps.mathLib = null; // Reset the container
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = RetailCapitulationRiskForecast;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core Calculation (Pass 2)
|
|
3
|
+
* Generates the "Ghost Book" - KDE of Holder Cost Basis.
|
|
4
|
+
* ARCHITECTURE COMPLIANT: Uses Context for Math & Prices.
|
|
5
|
+
*/
|
|
6
|
+
class AssetCostBasisProfile {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Map<Ticker, Map<PriceKey, Weight>>
|
|
9
|
+
this.assetBins = new Map();
|
|
10
|
+
this.tickerMap = null;
|
|
11
|
+
// FIX 1: Change state name to clearly indicate storage of the function pointer
|
|
12
|
+
this.computeKDE_func = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getMetadata() {
|
|
16
|
+
return {
|
|
17
|
+
type: 'standard',
|
|
18
|
+
rootDataDependencies: ['portfolio'],
|
|
19
|
+
dependencies: [],
|
|
20
|
+
isHistorical: false,
|
|
21
|
+
userType: 'all',
|
|
22
|
+
category: 'core_distribution'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getDependencies() { return []; }
|
|
27
|
+
|
|
28
|
+
static getSchema() {
|
|
29
|
+
const profileSchema = {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"profile": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "type": "object", "properties": { "price": {"type": "number"}, "density": {"type": "number"} } }
|
|
35
|
+
},
|
|
36
|
+
"current_price": { "type": "number" },
|
|
37
|
+
"total_inventory_weight": { "type": "number" }
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return { "type": "object", "patternProperties": { "^.*$": profileSchema } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process(context) {
|
|
44
|
+
const { user, mappings, math } = context;
|
|
45
|
+
|
|
46
|
+
// FIX 2 (CRITICAL): Store the *function pointer* itself from math.distribution.computeKDE.
|
|
47
|
+
// This is necessary because storing the class object (math.distribution) breaks the static method reference when serialized/deserialized by worker threads.
|
|
48
|
+
if (!this.computeKDE_func) {
|
|
49
|
+
this.computeKDE_func = math.distribution.computeKDE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
53
|
+
|
|
54
|
+
const { extract } = math;
|
|
55
|
+
|
|
56
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
57
|
+
|
|
58
|
+
for (const pos of positions) {
|
|
59
|
+
const instId = extract.getInstrumentId(pos);
|
|
60
|
+
const ticker = this.tickerMap[instId];
|
|
61
|
+
|
|
62
|
+
if (!ticker) continue;
|
|
63
|
+
|
|
64
|
+
const currentPrice = extract.getCurrentRate(pos);
|
|
65
|
+
if (currentPrice <= 0) continue;
|
|
66
|
+
|
|
67
|
+
const netProfitPct = extract.getNetProfit(pos);
|
|
68
|
+
const weight = extract.getPositionWeight(pos, user.type);
|
|
69
|
+
|
|
70
|
+
if (netProfitPct <= -99.9) continue;
|
|
71
|
+
|
|
72
|
+
const entryPrice = currentPrice / (1 + (netProfitPct / 100.0));
|
|
73
|
+
|
|
74
|
+
const priceKey = Number(entryPrice.toPrecision(6));
|
|
75
|
+
|
|
76
|
+
if (!this.assetBins.has(ticker)) {
|
|
77
|
+
this.assetBins.set(ticker, { bins: new Map(), currentPrice });
|
|
78
|
+
}
|
|
79
|
+
const asset = this.assetBins.get(ticker);
|
|
80
|
+
|
|
81
|
+
asset.currentPrice = currentPrice;
|
|
82
|
+
|
|
83
|
+
const currentWeight = asset.bins.get(priceKey) || 0;
|
|
84
|
+
asset.bins.set(priceKey, currentWeight + weight);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getResult() {
|
|
89
|
+
const result = {};
|
|
90
|
+
|
|
91
|
+
// Use the function pointer stored in state. No longer need the corrupted 'DistributionAnalytics' variable.
|
|
92
|
+
const computeKDE = this.computeKDE_func;
|
|
93
|
+
|
|
94
|
+
if (!computeKDE) return {}; // Safety guard
|
|
95
|
+
|
|
96
|
+
for (const [ticker, data] of this.assetBins.entries()) {
|
|
97
|
+
const points = [];
|
|
98
|
+
let totalWeight = 0;
|
|
99
|
+
for (const [price, weight] of data.bins.entries()) {
|
|
100
|
+
points.push({ value: price, weight: weight });
|
|
101
|
+
totalWeight += weight;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (points.length < 2) continue;
|
|
105
|
+
|
|
106
|
+
const bandwidth = data.currentPrice * 0.02;
|
|
107
|
+
|
|
108
|
+
// FIX 3: Call the stored function directly
|
|
109
|
+
const profile = computeKDE(points, bandwidth, 60);
|
|
110
|
+
|
|
111
|
+
result[ticker] = {
|
|
112
|
+
profile: profile,
|
|
113
|
+
current_price: data.currentPrice,
|
|
114
|
+
total_inventory_weight: totalWeight
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
reset() {
|
|
121
|
+
this.assetBins.clear();
|
|
122
|
+
this.tickerMap = null;
|
|
123
|
+
this.computeKDE_func = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = AssetCostBasisProfile;
|
|
@@ -1,170 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Core Metric (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* This 'standard' calculation streams all user portfolios
|
|
5
|
-
* and, for each asset, buckets users into "Winner" (in-profit)
|
|
6
|
-
* and "Loser" (in-loss) cohorts.
|
|
7
|
-
*
|
|
8
|
-
* It is a dependency for calculations that need to know
|
|
9
|
-
* the full list of users in each cohort (e.g., 'helix-contrarian-signal')
|
|
10
|
-
* but is too large to be a dependency for 'winner-loser-flow'.
|
|
3
|
+
* REFACTORED: Buckets users by P&L status (Win/Loss).
|
|
11
4
|
*/
|
|
12
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
13
|
-
|
|
14
5
|
class AssetPnlStatus {
|
|
15
6
|
constructor() {
|
|
16
|
-
// { [ticker]: { winners: [uid1, uid2], losers: [uid1] } }
|
|
17
7
|
this.tickerBuckets = new Map();
|
|
18
|
-
// { [sector]: { winners: [uid1, uid2], losers: [uid1] } }
|
|
19
8
|
this.sectorBuckets = new Map();
|
|
20
|
-
|
|
21
|
-
// --- STANDARD 0: RENAMED ---
|
|
22
|
-
this.tickerMap = null;
|
|
23
|
-
this.sectorMap = null;
|
|
24
|
-
|
|
25
9
|
}
|
|
26
10
|
|
|
27
|
-
/** Statically defines metadata */
|
|
28
11
|
static getMetadata() {
|
|
29
12
|
return {
|
|
30
13
|
type: 'standard',
|
|
31
14
|
rootDataDependencies: ['portfolio'],
|
|
32
|
-
isHistorical: false,
|
|
15
|
+
isHistorical: false,
|
|
33
16
|
userType: 'all',
|
|
34
17
|
category: 'core'
|
|
35
18
|
};
|
|
36
19
|
}
|
|
37
20
|
|
|
38
|
-
|
|
39
|
-
static getDependencies() {
|
|
40
|
-
return []; // This is a Pass 2 calculation
|
|
41
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
42
22
|
|
|
43
|
-
/**
|
|
44
|
-
* Defines the output schema for this calculation.
|
|
45
|
-
*/
|
|
46
23
|
static getSchema() {
|
|
47
24
|
const cohortSchema = {
|
|
48
25
|
"type": "object",
|
|
49
26
|
"properties": {
|
|
50
|
-
"winners": {
|
|
51
|
-
|
|
52
|
-
"items": { "type": "string" },
|
|
53
|
-
"description": "List of User IDs in the 'Winner' (in-profit) cohort."
|
|
54
|
-
},
|
|
55
|
-
"losers": {
|
|
56
|
-
"type": "array",
|
|
57
|
-
"items": { "type": "string" },
|
|
58
|
-
"description": "List of User IDs in the 'Loser' (in-loss) cohort."
|
|
59
|
-
}
|
|
27
|
+
"winners": { "type": "array", "items": { "type": "string" } },
|
|
28
|
+
"losers": { "type": "array", "items": { "type": "string" } }
|
|
60
29
|
},
|
|
61
30
|
"required": ["winners", "losers"]
|
|
62
31
|
};
|
|
63
|
-
|
|
64
32
|
return {
|
|
65
33
|
"type": "object",
|
|
66
|
-
"description": "Buckets all users into 'Winner' or 'Loser' cohorts per asset and sector.",
|
|
67
34
|
"properties": {
|
|
68
|
-
"by_ticker": {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"additionalProperties": cohortSchema
|
|
72
|
-
},
|
|
73
|
-
"by_sector": {
|
|
74
|
-
"type": "object",
|
|
75
|
-
"patternProperties": { "^.*$": cohortSchema },
|
|
76
|
-
"additionalProperties": cohortSchema
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"required": ["by_ticker", "by_sector"]
|
|
35
|
+
"by_ticker": { "type": "object", "patternProperties": { "^.*$": cohortSchema } },
|
|
36
|
+
"by_sector": { "type": "object", "patternProperties": { "^.*$": cohortSchema } }
|
|
37
|
+
}
|
|
80
38
|
};
|
|
81
39
|
}
|
|
82
40
|
|
|
83
41
|
_init(map, key) {
|
|
84
|
-
if (!map.has(key)) {
|
|
85
|
-
map.set(key, { winners: new Set(), losers: new Set() });
|
|
86
|
-
}
|
|
42
|
+
if (!map.has(key)) map.set(key, { winners: new Set(), losers: new Set() });
|
|
87
43
|
}
|
|
88
44
|
|
|
89
|
-
process(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!this.tickerMap || !this.sectorMap) {
|
|
97
|
-
return; // Failsafe if context is missing maps
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
|
|
101
|
-
if (!positions || positions.length === 0) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
45
|
+
process(context) {
|
|
46
|
+
const { extract } = context.math;
|
|
47
|
+
const { mappings, user } = context;
|
|
48
|
+
|
|
49
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
104
50
|
|
|
105
51
|
for (const pos of positions) {
|
|
106
|
-
|
|
52
|
+
const instId = extract.getInstrumentId(pos);
|
|
53
|
+
if (!instId) continue;
|
|
107
54
|
|
|
108
|
-
const pnl = pos
|
|
109
|
-
if (pnl === 0) continue;
|
|
55
|
+
const pnl = extract.getNetProfit(pos);
|
|
56
|
+
if (pnl === 0) continue;
|
|
110
57
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const sector = this.sectorMap[pos.InstrumentID];
|
|
58
|
+
const ticker = mappings.instrumentToTicker[instId];
|
|
59
|
+
const sector = mappings.sectorMapping[instId];
|
|
114
60
|
const isWinner = pnl > 0;
|
|
115
61
|
|
|
116
62
|
if (ticker) {
|
|
117
63
|
this._init(this.tickerBuckets, ticker);
|
|
118
64
|
const asset = this.tickerBuckets.get(ticker);
|
|
119
|
-
if (isWinner) asset.winners.add(
|
|
120
|
-
else asset.losers.add(
|
|
65
|
+
if (isWinner) asset.winners.add(user.id);
|
|
66
|
+
else asset.losers.add(user.id);
|
|
121
67
|
}
|
|
122
68
|
|
|
123
69
|
if (sector) {
|
|
124
70
|
this._init(this.sectorBuckets, sector);
|
|
125
71
|
const sec = this.sectorBuckets.get(sector);
|
|
126
|
-
if (isWinner) sec.winners.add(
|
|
127
|
-
else sec.losers.add(
|
|
72
|
+
if (isWinner) sec.winners.add(user.id);
|
|
73
|
+
else sec.losers.add(user.id);
|
|
128
74
|
}
|
|
129
75
|
}
|
|
130
76
|
}
|
|
131
77
|
|
|
132
78
|
async getResult() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// Failsafe check
|
|
136
|
-
if (!this.tickerMap) {
|
|
137
|
-
return { by_ticker: {}, by_sector: {} };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const result = {
|
|
141
|
-
by_ticker: {},
|
|
142
|
-
by_sector: {}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
for (const [ticker, data] of this.tickerBuckets.entries()) {
|
|
146
|
-
result.by_ticker[ticker] = {
|
|
147
|
-
winners: Array.from(data.winners),
|
|
148
|
-
losers: Array.from(data.losers)
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
for (const [sector, data] of this.sectorBuckets.entries()) {
|
|
153
|
-
result.by_sector[sector] = {
|
|
154
|
-
winners: Array.from(data.winners),
|
|
155
|
-
losers: Array.from(data.losers)
|
|
156
|
-
};
|
|
157
|
-
}
|
|
79
|
+
const result = { by_ticker: {}, by_sector: {} };
|
|
158
80
|
|
|
81
|
+
const buildResult = (map, out) => {
|
|
82
|
+
for (const [key, data] of map.entries()) {
|
|
83
|
+
out[key] = {
|
|
84
|
+
winners: Array.from(data.winners),
|
|
85
|
+
losers: Array.from(data.losers)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
buildResult(this.tickerBuckets, result.by_ticker);
|
|
91
|
+
buildResult(this.sectorBuckets, result.by_sector);
|
|
159
92
|
return result;
|
|
160
93
|
}
|
|
161
94
|
|
|
162
95
|
reset() {
|
|
163
96
|
this.tickerBuckets.clear();
|
|
164
97
|
this.sectorBuckets.clear();
|
|
165
|
-
// --- STANDARD 0: RENAMED ---
|
|
166
|
-
this.tickerMap = null;
|
|
167
|
-
this.sectorMap = null;
|
|
168
98
|
}
|
|
169
99
|
}
|
|
170
100
|
module.exports = AssetPnlStatus;
|