aiden-shared-calculations-unified 1.0.109 → 1.0.110
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/core/insights-daily-bought-vs-sold-count.js +20 -15
- package/calculations/core/insights-daily-ownership-delta.js +12 -10
- package/calculations/core/instrument-price-change-1d.js +16 -31
- package/calculations/core/instrument-price-momentum-20d.js +16 -26
- package/calculations/core/ownership-vs-performance-ytd.js +15 -52
- package/calculations/core/ownership-vs-volatility.js +27 -36
- package/calculations/core/platform-daily-bought-vs-sold-count.js +28 -26
- package/calculations/core/platform-daily-ownership-delta.js +28 -31
- package/calculations/core/price-metrics.js +15 -54
- package/calculations/core/short-interest-growth.js +6 -13
- package/calculations/core/trending-ownership-momentum.js +16 -28
- package/calculations/gauss/cohort-capital-flow.js +34 -71
- package/calculations/gauss/cohort-definer.js +61 -142
- package/calculations/gem/cohort-momentum-state.js +27 -77
- package/calculations/gem/skilled-cohort-flow.js +36 -114
- package/calculations/gem/unskilled-cohort-flow.js +36 -112
- package/calculations/ghost-book/retail-gamma-exposure.js +14 -61
- package/calculations/helix/herd-consensus-score.js +27 -90
- package/calculations/helix/winner-loser-flow.js +21 -90
- package/calculations/predicative-alpha/cognitive-dissonance.js +25 -91
- package/calculations/predicative-alpha/diamond-hand-fracture.js +17 -72
- package/calculations/predicative-alpha/mimetic-latency.js +21 -100
- package/package.json +1 -1
|
@@ -17,37 +17,42 @@ class InsightsDailyBoughtVsSoldCount {
|
|
|
17
17
|
const tickerSchema = {
|
|
18
18
|
"type": "object",
|
|
19
19
|
"properties": {
|
|
20
|
-
"positions_bought": { "type": "number" },
|
|
21
|
-
"positions_sold": { "type": "number" },
|
|
22
|
-
"net_change": { "type": "number" }
|
|
20
|
+
"positions_bought": { "type": ["number", "null"] },
|
|
21
|
+
"positions_sold": { "type": ["number", "null"] },
|
|
22
|
+
"net_change": { "type": ["number", "null"] },
|
|
23
|
+
"total_positions": { "type": "number" } // Baseline
|
|
23
24
|
},
|
|
24
|
-
"required": ["positions_bought", "positions_sold", "net_change"]
|
|
25
|
+
"required": ["positions_bought", "positions_sold", "net_change", "total_positions"]
|
|
25
26
|
};
|
|
26
27
|
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
process(context) {
|
|
30
31
|
const { insights: insightsHelper } = context.math;
|
|
31
|
-
const insights = insightsHelper.getInsights(context);
|
|
32
|
+
const insights = insightsHelper.getInsights(context);
|
|
32
33
|
const { mappings } = context;
|
|
33
34
|
const tickerMap = mappings.instrumentToTicker || {};
|
|
34
35
|
|
|
35
|
-
if (insights.length === 0) return;
|
|
36
|
+
if (!insights || insights.length === 0) return;
|
|
36
37
|
|
|
37
38
|
for (const insight of insights) {
|
|
38
39
|
const instId = insight.instrumentId;
|
|
39
40
|
const ticker = tickerMap[instId] || `id_${instId}`;
|
|
41
|
+
const total = insightsHelper.getTotalOwners(insight);
|
|
42
|
+
|
|
43
|
+
// netChange requires yesterday's data to be accurate
|
|
40
44
|
const netChange = insightsHelper.getNetOwnershipChange(insight);
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
// If netChange is null (missing history), we still save total_positions
|
|
47
|
+
const bought = (netChange !== null && netChange > 0) ? netChange : 0;
|
|
48
|
+
const sold = (netChange !== null && netChange < 0) ? Math.abs(netChange) : 0;
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
50
|
+
this.results[ticker] = {
|
|
51
|
+
positions_bought: bought,
|
|
52
|
+
positions_sold: sold,
|
|
53
|
+
net_change: netChange,
|
|
54
|
+
total_positions: total
|
|
55
|
+
};
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -17,36 +17,38 @@ class InsightsDailyOwnershipDelta {
|
|
|
17
17
|
const tickerSchema = {
|
|
18
18
|
"type": "object",
|
|
19
19
|
"properties": {
|
|
20
|
-
"owners_added": { "type": "number" },
|
|
21
|
-
"owners_removed": { "type": "number" },
|
|
22
|
-
"net_ownership_change": { "type": "number" }
|
|
20
|
+
"owners_added": { "type": ["number", "null"] },
|
|
21
|
+
"owners_removed": { "type": ["number", "null"] },
|
|
22
|
+
"net_ownership_change": { "type": ["number", "null"] },
|
|
23
|
+
"total_owners": { "type": "number" } // Baseline
|
|
23
24
|
},
|
|
24
|
-
"required": ["owners_added", "owners_removed", "net_ownership_change"]
|
|
25
|
+
"required": ["owners_added", "owners_removed", "net_ownership_change", "total_owners"]
|
|
25
26
|
};
|
|
26
27
|
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
process(context) {
|
|
30
31
|
const { insights: insightsHelper } = context.math;
|
|
31
|
-
const insights = insightsHelper.getInsights(context);
|
|
32
|
+
const insights = insightsHelper.getInsights(context);
|
|
32
33
|
const { mappings } = context;
|
|
33
34
|
const tickerMap = mappings.instrumentToTicker || {};
|
|
34
35
|
|
|
35
|
-
if (insights.length === 0) return;
|
|
36
|
+
if (!insights || insights.length === 0) return;
|
|
36
37
|
|
|
37
38
|
for (const insight of insights) {
|
|
39
|
+
const total = insightsHelper.getTotalOwners(insight);
|
|
38
40
|
const netChange = insightsHelper.getNetOwnershipChange(insight);
|
|
39
|
-
if (netChange === 0) continue;
|
|
40
41
|
|
|
41
42
|
const instId = insight.instrumentId;
|
|
42
43
|
const ticker = tickerMap[instId] || `id_${instId}`;
|
|
43
|
-
const added = netChange > 0 ? netChange : 0;
|
|
44
|
-
const removed = netChange < 0 ? Math.abs(netChange) : 0;
|
|
44
|
+
const added = (netChange !== null && netChange > 0) ? netChange : 0;
|
|
45
|
+
const removed = (netChange !== null && netChange < 0) ? Math.abs(netChange) : 0;
|
|
45
46
|
|
|
46
47
|
this.results[ticker] = {
|
|
47
48
|
owners_added: added,
|
|
48
49
|
owners_removed: removed,
|
|
49
|
-
net_ownership_change: netChange
|
|
50
|
+
net_ownership_change: netChange,
|
|
51
|
+
total_owners: total
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
|
|
3
|
-
* FIXED: Supports Sharded/Batched Execution (Accumulates results).
|
|
4
|
-
*/
|
|
5
1
|
class InstrumentPriceChange1D {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.result = {};
|
|
8
|
-
}
|
|
2
|
+
constructor() { this.result = {}; }
|
|
9
3
|
|
|
10
4
|
static getMetadata() {
|
|
11
5
|
return {
|
|
@@ -23,9 +17,10 @@ class InstrumentPriceChange1D {
|
|
|
23
17
|
const tickerSchema = {
|
|
24
18
|
"type": "object",
|
|
25
19
|
"properties": {
|
|
26
|
-
"change_1d_pct": { "type": "number" }
|
|
20
|
+
"change_1d_pct": { "type": ["number", "null"] },
|
|
21
|
+
"closing_price": { "type": "number" } // Baseline
|
|
27
22
|
},
|
|
28
|
-
"required": ["change_1d_pct"]
|
|
23
|
+
"required": ["change_1d_pct", "closing_price"]
|
|
29
24
|
};
|
|
30
25
|
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
31
26
|
}
|
|
@@ -35,48 +30,38 @@ class InstrumentPriceChange1D {
|
|
|
35
30
|
const { instrumentToTicker } = mappings;
|
|
36
31
|
const { priceExtractor } = math;
|
|
37
32
|
|
|
38
|
-
if (!prices || !prices.history)
|
|
39
|
-
// Do NOT reset result here, just return if this shard is empty
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
33
|
+
if (!prices || !prices.history) return;
|
|
42
34
|
|
|
43
35
|
const todayStr = date.today;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
|
|
36
|
+
const prevDate = new Date(todayStr + 'T00:00:00Z');
|
|
37
|
+
prevDate.setUTCDate(prevDate.getUTCDate() - 1);
|
|
38
|
+
const yesterdayStr = prevDate.toISOString().slice(0, 10);
|
|
48
39
|
|
|
49
40
|
const batchResults = {};
|
|
50
41
|
|
|
51
|
-
// Iterate over THIS SHARD'S instruments
|
|
52
42
|
for (const [instrumentId, priceData] of Object.entries(prices.history)) {
|
|
53
43
|
const ticker = instrumentToTicker[instrumentId];
|
|
54
44
|
if (!ticker) continue;
|
|
55
45
|
|
|
56
|
-
// Note: getHistory works on the partial 'prices' object just fine
|
|
57
|
-
const history = priceExtractor.getHistory(prices, instrumentId);
|
|
58
|
-
if (history.length === 0) continue;
|
|
59
|
-
|
|
60
46
|
const todayPrice = priceData.prices?.[todayStr];
|
|
61
47
|
const yesterdayPrice = priceData.prices?.[yesterdayStr];
|
|
62
48
|
|
|
63
|
-
if (todayPrice
|
|
64
|
-
|
|
49
|
+
if (todayPrice) {
|
|
50
|
+
let changePct = null;
|
|
51
|
+
if (yesterdayPrice && yesterdayPrice > 0) {
|
|
52
|
+
changePct = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
|
|
53
|
+
}
|
|
54
|
+
|
|
65
55
|
batchResults[ticker] = {
|
|
66
|
-
change_1d_pct: isFinite(changePct) ? changePct :
|
|
56
|
+
change_1d_pct: (changePct !== null && isFinite(changePct)) ? changePct : null,
|
|
57
|
+
closing_price: todayPrice
|
|
67
58
|
};
|
|
68
|
-
} else {
|
|
69
|
-
batchResults[ticker] = { change_1d_pct: 0 };
|
|
70
59
|
}
|
|
71
60
|
}
|
|
72
|
-
|
|
73
|
-
// Merge batch results into main result
|
|
74
61
|
Object.assign(this.result, batchResults);
|
|
75
62
|
}
|
|
76
63
|
|
|
77
64
|
async getResult() { return this.result; }
|
|
78
|
-
|
|
79
|
-
// Explicit reset only called by system between full runs
|
|
80
65
|
reset() { this.result = {}; }
|
|
81
66
|
}
|
|
82
67
|
module.exports = InstrumentPriceChange1D;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
|
|
3
|
-
* FIXED: Supports Sharded/Batched Execution (Accumulates results).
|
|
4
|
-
*/
|
|
5
1
|
class InstrumentPriceMomentum20D {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.result = {};
|
|
8
|
-
}
|
|
2
|
+
constructor() { this.result = {}; }
|
|
9
3
|
|
|
10
4
|
static getMetadata() {
|
|
11
5
|
return {
|
|
@@ -23,9 +17,10 @@ class InstrumentPriceMomentum20D {
|
|
|
23
17
|
const tickerSchema = {
|
|
24
18
|
"type": "object",
|
|
25
19
|
"properties": {
|
|
26
|
-
"momentum_20d_pct": { "type": "number" }
|
|
20
|
+
"momentum_20d_pct": { "type": ["number", "null"] },
|
|
21
|
+
"current_price": { "type": "number" }
|
|
27
22
|
},
|
|
28
|
-
"required": ["momentum_20d_pct"]
|
|
23
|
+
"required": ["momentum_20d_pct", "current_price"]
|
|
29
24
|
};
|
|
30
25
|
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
31
26
|
}
|
|
@@ -33,7 +28,6 @@ class InstrumentPriceMomentum20D {
|
|
|
33
28
|
_findPriceOnOrBefore(prices, instrumentId, targetDateStr) {
|
|
34
29
|
const priceData = prices.history[instrumentId];
|
|
35
30
|
if (!priceData || !priceData.prices) return null;
|
|
36
|
-
|
|
37
31
|
let checkDate = new Date(targetDateStr + 'T00:00:00Z');
|
|
38
32
|
for (let i = 0; i < 5; i++) {
|
|
39
33
|
const str = checkDate.toISOString().slice(0, 10);
|
|
@@ -47,20 +41,15 @@ class InstrumentPriceMomentum20D {
|
|
|
47
41
|
process(context) {
|
|
48
42
|
const { mappings, prices, date } = context;
|
|
49
43
|
const { instrumentToTicker } = mappings;
|
|
50
|
-
|
|
51
|
-
if (!prices || !prices.history) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
44
|
+
if (!prices || !prices.history) return;
|
|
54
45
|
|
|
55
46
|
const todayStr = date.today;
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
|
|
47
|
+
const oldDate = new Date(todayStr + 'T00:00:00Z');
|
|
48
|
+
oldDate.setUTCDate(oldDate.getUTCDate() - 20);
|
|
49
|
+
const oldStr = oldDate.toISOString().slice(0, 10);
|
|
60
50
|
|
|
61
51
|
const batchResults = {};
|
|
62
52
|
|
|
63
|
-
// Iterate over THIS SHARD'S instruments
|
|
64
53
|
for (const [instrumentId, priceData] of Object.entries(prices.history)) {
|
|
65
54
|
const ticker = instrumentToTicker[instrumentId];
|
|
66
55
|
if (!ticker) continue;
|
|
@@ -68,17 +57,18 @@ class InstrumentPriceMomentum20D {
|
|
|
68
57
|
const currentPrice = this._findPriceOnOrBefore(prices, instrumentId, todayStr);
|
|
69
58
|
const oldPrice = this._findPriceOnOrBefore(prices, instrumentId, oldStr);
|
|
70
59
|
|
|
71
|
-
if (currentPrice
|
|
72
|
-
|
|
60
|
+
if (currentPrice) {
|
|
61
|
+
let momPct = null;
|
|
62
|
+
if (oldPrice && oldPrice > 0) {
|
|
63
|
+
momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
|
|
64
|
+
}
|
|
65
|
+
|
|
73
66
|
batchResults[ticker] = {
|
|
74
|
-
momentum_20d_pct: isFinite(momPct) ? momPct :
|
|
67
|
+
momentum_20d_pct: (momPct !== null && isFinite(momPct)) ? momPct : null,
|
|
68
|
+
current_price: currentPrice
|
|
75
69
|
};
|
|
76
|
-
} else {
|
|
77
|
-
batchResults[ticker] = { momentum_20d_pct: 0 };
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
|
-
|
|
81
|
-
// Merge
|
|
82
72
|
Object.assign(this.result, batchResults);
|
|
83
73
|
}
|
|
84
74
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class OwnershipVsPerformanceYTD {
|
|
2
|
-
constructor() { this.results = {}; }
|
|
2
|
+
constructor() { this.results = { metrics: {}, baselines: {} }; }
|
|
3
3
|
|
|
4
4
|
static getMetadata() {
|
|
5
5
|
return {
|
|
@@ -7,7 +7,7 @@ class OwnershipVsPerformanceYTD {
|
|
|
7
7
|
type: 'meta',
|
|
8
8
|
category: 'core',
|
|
9
9
|
userType: 'n/a',
|
|
10
|
-
isHistorical: true,
|
|
10
|
+
isHistorical: true,
|
|
11
11
|
rootDataDependencies: ['insights', 'price']
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -18,33 +18,17 @@ class OwnershipVsPerformanceYTD {
|
|
|
18
18
|
const metricSchema = {
|
|
19
19
|
"type": "object",
|
|
20
20
|
"properties": {
|
|
21
|
-
"priceYtd": { "type": "number" },
|
|
22
|
-
"ownersYtd": { "type": "number" },
|
|
21
|
+
"priceYtd": { "type": ["number", "null"] },
|
|
22
|
+
"ownersYtd": { "type": ["number", "null"] },
|
|
23
23
|
"currentPrice": { "type": "number" },
|
|
24
24
|
"currentOwners": { "type": "number" }
|
|
25
25
|
},
|
|
26
26
|
"required": ["priceYtd", "ownersYtd", "currentPrice", "currentOwners"]
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const baselineSchema = {
|
|
30
|
-
"type": "object",
|
|
31
|
-
"patternProperties": {
|
|
32
|
-
"^.*$": {
|
|
33
|
-
"type": "object",
|
|
34
|
-
"properties": {
|
|
35
|
-
"startPrice": { "type": "number" },
|
|
36
|
-
"startOwners": { "type": "number" },
|
|
37
|
-
"date": { "type": "string" }
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
29
|
return {
|
|
44
30
|
"type": "object",
|
|
45
|
-
"properties": {
|
|
46
|
-
"baselines": baselineSchema
|
|
47
|
-
},
|
|
31
|
+
"properties": { "baselines": { "type": "object" } },
|
|
48
32
|
"additionalProperties": metricSchema
|
|
49
33
|
};
|
|
50
34
|
}
|
|
@@ -54,15 +38,11 @@ class OwnershipVsPerformanceYTD {
|
|
|
54
38
|
const { previousComputed, mappings, prices, date } = context;
|
|
55
39
|
|
|
56
40
|
const dailyInsights = insightsHelper.getInsights(context, 'today');
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
let baselineState = previousComputed['ownership-vs-performance-ytd']?.baselines || {};
|
|
41
|
+
const prevRes = previousComputed['ownership-vs-performance-ytd'];
|
|
42
|
+
let baselineState = prevRes?.baselines || {};
|
|
43
|
+
|
|
61
44
|
const isStartOfYear = date.today.endsWith('-01-01') || date.today.endsWith('-01-02');
|
|
62
|
-
|
|
63
|
-
if (isStartOfYear) {
|
|
64
|
-
baselineState = {}; // Reset for new year
|
|
65
|
-
}
|
|
45
|
+
if (isStartOfYear) baselineState = {};
|
|
66
46
|
|
|
67
47
|
const currentMetrics = {};
|
|
68
48
|
|
|
@@ -72,14 +52,11 @@ class OwnershipVsPerformanceYTD {
|
|
|
72
52
|
if (!ticker) continue;
|
|
73
53
|
|
|
74
54
|
const currentOwners = insightsHelper.getTotalOwners(insight);
|
|
75
|
-
|
|
76
|
-
// Get Price History
|
|
77
55
|
const priceHist = priceExtractor.getHistory(prices, ticker);
|
|
78
56
|
if (!priceHist.length) continue;
|
|
79
57
|
|
|
80
58
|
const currentPrice = priceHist[priceHist.length - 1].price;
|
|
81
59
|
|
|
82
|
-
// 2. Set Baseline if missing
|
|
83
60
|
if (!baselineState[ticker]) {
|
|
84
61
|
baselineState[ticker] = {
|
|
85
62
|
startPrice: currentPrice,
|
|
@@ -89,37 +66,23 @@ class OwnershipVsPerformanceYTD {
|
|
|
89
66
|
}
|
|
90
67
|
|
|
91
68
|
const base = baselineState[ticker];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const priceYtd = base.startPrice > 0
|
|
95
|
-
? ((currentPrice - base.startPrice) / base.startPrice) * 100
|
|
96
|
-
: 0;
|
|
97
|
-
|
|
98
|
-
const ownersYtd = base.startOwners > 0
|
|
99
|
-
? ((currentOwners - base.startOwners) / base.startOwners) * 100
|
|
100
|
-
: 0;
|
|
69
|
+
const priceYtd = base.startPrice > 0 ? ((currentPrice - base.startPrice) / base.startPrice) * 100 : 0;
|
|
70
|
+
const ownersYtd = base.startOwners > 0 ? ((currentOwners - base.startOwners) / base.startOwners) * 100 : 0;
|
|
101
71
|
|
|
102
72
|
currentMetrics[ticker] = {
|
|
103
|
-
priceYtd,
|
|
104
|
-
ownersYtd,
|
|
73
|
+
priceYtd: isFinite(priceYtd) ? priceYtd : null,
|
|
74
|
+
ownersYtd: isFinite(ownersYtd) ? ownersYtd : null,
|
|
105
75
|
currentPrice,
|
|
106
76
|
currentOwners
|
|
107
77
|
};
|
|
108
78
|
}
|
|
109
79
|
|
|
110
|
-
|
|
111
|
-
this.results = {
|
|
112
|
-
metrics: currentMetrics,
|
|
113
|
-
baselines: baselineState
|
|
114
|
-
};
|
|
80
|
+
this.results = { metrics: currentMetrics, baselines: baselineState };
|
|
115
81
|
}
|
|
116
82
|
|
|
117
83
|
async getResult() {
|
|
118
|
-
// We flatten the 'metrics' for easy API consumption,
|
|
119
|
-
// but we must ensure 'baselines' is preserved in the stored document
|
|
120
|
-
// so 'previousComputed' picks it up tomorrow.
|
|
121
84
|
return { ...this.results.metrics, baselines: this.results.baselines };
|
|
122
85
|
}
|
|
86
|
+
reset() { this.results = { metrics: {}, baselines: {} }; }
|
|
123
87
|
}
|
|
124
|
-
|
|
125
88
|
module.exports = OwnershipVsPerformanceYTD;
|
|
@@ -7,7 +7,7 @@ class OwnershipVsVolatility {
|
|
|
7
7
|
type: 'meta',
|
|
8
8
|
category: 'core',
|
|
9
9
|
userType: 'n/a',
|
|
10
|
-
isHistorical: true,
|
|
10
|
+
isHistorical: true,
|
|
11
11
|
rootDataDependencies: ['insights', 'price']
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -18,13 +18,10 @@ class OwnershipVsVolatility {
|
|
|
18
18
|
const schema = {
|
|
19
19
|
"type": "object",
|
|
20
20
|
"properties": {
|
|
21
|
-
"volatilityScore": { "type": "number" },
|
|
22
|
-
"ownerChange7d": { "type": "number" },
|
|
21
|
+
"volatilityScore": { "type": ["number", "null"] },
|
|
22
|
+
"ownerChange7d": { "type": ["number", "null"] },
|
|
23
23
|
"laggedOwners": { "type": "number" },
|
|
24
|
-
"_history": {
|
|
25
|
-
"type": "array",
|
|
26
|
-
"items": { "type": "number" }
|
|
27
|
-
}
|
|
24
|
+
"_history": { "type": "array", "items": { "type": "number" } }
|
|
28
25
|
},
|
|
29
26
|
"required": ["volatilityScore", "ownerChange7d", "laggedOwners"]
|
|
30
27
|
};
|
|
@@ -34,10 +31,7 @@ class OwnershipVsVolatility {
|
|
|
34
31
|
async process(context) {
|
|
35
32
|
const { insights: insightsHelper, priceExtractor, compute } = context.math;
|
|
36
33
|
const { previousComputed, mappings, prices } = context;
|
|
37
|
-
|
|
38
34
|
const dailyInsights = insightsHelper.getInsights(context, 'today');
|
|
39
|
-
|
|
40
|
-
// Use 7-day lookback for ownership change
|
|
41
35
|
const prevState = previousComputed['ownership-vs-volatility'] || {};
|
|
42
36
|
|
|
43
37
|
for (const insight of dailyInsights) {
|
|
@@ -45,42 +39,39 @@ class OwnershipVsVolatility {
|
|
|
45
39
|
const ticker = mappings.instrumentToTicker[instId];
|
|
46
40
|
if (!ticker) continue;
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
const currentOwners = insightsHelper.getTotalOwners(insight);
|
|
49
43
|
const priceHist = priceExtractor.getHistory(prices, ticker);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
|
|
45
|
+
let volatility = null;
|
|
46
|
+
if (priceHist.length >= 14) {
|
|
47
|
+
const recentPrices = priceHist.slice(-14);
|
|
48
|
+
const returns = [];
|
|
49
|
+
for (let i = 1; i < recentPrices.length; i++) {
|
|
50
|
+
returns.push((recentPrices[i].price - recentPrices[i - 1].price) / recentPrices[i - 1].price);
|
|
51
|
+
}
|
|
52
|
+
volatility = compute.standardDeviation(returns);
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
const
|
|
55
|
+
const history = [...(prevState[ticker]?._history || [])];
|
|
56
|
+
history.push(currentOwners);
|
|
57
|
+
if (history.length > 7) history.shift();
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
? ((currentOwners - prevOwners) / prevOwners) * 100
|
|
66
|
-
: 0;
|
|
59
|
+
const laggedOwners = history[0];
|
|
60
|
+
let ownerChangePct = null;
|
|
61
|
+
if (laggedOwners > 0 && history.length >= 7) {
|
|
62
|
+
ownerChangePct = ((currentOwners - laggedOwners) / laggedOwners) * 100;
|
|
63
|
+
}
|
|
67
64
|
|
|
68
65
|
this.results[ticker] = {
|
|
69
|
-
volatilityScore: volatility,
|
|
70
|
-
ownerChange7d: ownerChangePct,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Simplified: We store today's value, and the API/UI compares.
|
|
74
|
-
// Better: Use a simple rolling queue like the Momentum calc.
|
|
75
|
-
_history: [...(prevState[ticker]?._history || []), currentOwners].slice(-7),
|
|
76
|
-
|
|
77
|
-
// For the output: Calculate change vs the oldest in history (approx 7d ago)
|
|
78
|
-
laggedOwners: (prevState[ticker]?._history?.[0] || currentOwners)
|
|
66
|
+
volatilityScore: (volatility !== null && isFinite(volatility)) ? volatility : null,
|
|
67
|
+
ownerChange7d: (ownerChangePct !== null && isFinite(ownerChangePct)) ? ownerChangePct : null,
|
|
68
|
+
_history: history,
|
|
69
|
+
laggedOwners: laggedOwners
|
|
79
70
|
};
|
|
80
71
|
}
|
|
81
72
|
}
|
|
82
73
|
|
|
83
74
|
async getResult() { return this.results; }
|
|
75
|
+
reset() { this.results = {}; }
|
|
84
76
|
}
|
|
85
|
-
|
|
86
77
|
module.exports = OwnershipVsVolatility;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for daily bought vs. sold count.
|
|
3
|
-
* REFACTORED: Uses context.math.extract on today vs yesterday.
|
|
4
|
-
*/
|
|
5
1
|
class DailyBoughtVsSoldCount {
|
|
6
2
|
constructor() {
|
|
7
3
|
this.assetActivity = new Map();
|
|
@@ -26,16 +22,17 @@ class DailyBoughtVsSoldCount {
|
|
|
26
22
|
"properties": {
|
|
27
23
|
"positions_bought": { "type": "number" },
|
|
28
24
|
"positions_sold": { "type": "number" },
|
|
29
|
-
"net_change": { "type": "number" }
|
|
25
|
+
"net_change": { "type": "number" },
|
|
26
|
+
"aggregate_portfolio_size": { "type": "number" } // Baseline
|
|
30
27
|
},
|
|
31
|
-
"required": ["positions_bought", "positions_sold", "net_change"]
|
|
28
|
+
"required": ["positions_bought", "positions_sold", "net_change", "aggregate_portfolio_size"]
|
|
32
29
|
};
|
|
33
30
|
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
_initAsset(instrumentId) {
|
|
37
34
|
if (!this.assetActivity.has(instrumentId)) {
|
|
38
|
-
this.assetActivity.set(instrumentId, { new: 0, closed: 0 });
|
|
35
|
+
this.assetActivity.set(instrumentId, { new: 0, closed: 0, currentSize: 0 });
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
@@ -55,24 +52,30 @@ class DailyBoughtVsSoldCount {
|
|
|
55
52
|
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
56
53
|
|
|
57
54
|
const todayPositions = extract.getPositions(user.portfolio.today, user.type);
|
|
58
|
-
const yesterdayPositions = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
55
|
+
const yesterdayPositions = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
|
|
59
56
|
|
|
60
57
|
const tPosMap = this._getInstrumentIds(todayPositions, extract);
|
|
61
58
|
const yPosMap = this._getInstrumentIds(yesterdayPositions, extract);
|
|
62
59
|
|
|
63
|
-
//
|
|
64
|
-
for (const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.assetActivity.get(instId).new++;
|
|
68
|
-
}
|
|
60
|
+
// Track current sizes for Baseline
|
|
61
|
+
for (const instId of tPosMap.values()) {
|
|
62
|
+
this._initAsset(instId);
|
|
63
|
+
this.assetActivity.get(instId).currentSize++;
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
// Only calculate bought/sold if yesterday exists
|
|
67
|
+
if (user.portfolio.yesterday) {
|
|
68
|
+
for (const [posId, instId] of tPosMap.entries()) {
|
|
69
|
+
if (!yPosMap.has(posId)) {
|
|
70
|
+
this._initAsset(instId);
|
|
71
|
+
this.assetActivity.get(instId).new++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const [posId, instId] of yPosMap.entries()) {
|
|
75
|
+
if (!tPosMap.has(posId)) {
|
|
76
|
+
this._initAsset(instId);
|
|
77
|
+
this.assetActivity.get(instId).closed++;
|
|
78
|
+
}
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
}
|
|
@@ -82,13 +85,12 @@ class DailyBoughtVsSoldCount {
|
|
|
82
85
|
const result = {};
|
|
83
86
|
for (const [instId, data] of this.assetActivity.entries()) {
|
|
84
87
|
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
88
|
+
result[ticker] = {
|
|
89
|
+
positions_bought: data.new,
|
|
90
|
+
positions_sold: data.closed,
|
|
91
|
+
net_change: data.new - data.closed,
|
|
92
|
+
aggregate_portfolio_size: data.currentSize
|
|
93
|
+
};
|
|
92
94
|
}
|
|
93
95
|
return result;
|
|
94
96
|
}
|