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
|
@@ -1,90 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for average P&L per position.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What was the average P&L across all
|
|
5
|
-
* individual positions held by the crowd today?"
|
|
6
|
-
*
|
|
7
|
-
* This is different from "average P&L per user" as a user
|
|
8
|
-
* can have multiple positions.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for average P&L % per position.
|
|
3
|
+
* REFACTORED: Uses P&L %.
|
|
9
4
|
*/
|
|
10
5
|
class AverageDailyPositionPnl {
|
|
11
6
|
constructor() {
|
|
12
|
-
this.
|
|
7
|
+
this.totalPnlPct = 0;
|
|
13
8
|
this.positionCount = 0;
|
|
14
9
|
}
|
|
15
10
|
|
|
16
|
-
// --- NEW ---
|
|
17
|
-
/**
|
|
18
|
-
* Statically defines all metadata for the manifest builder.
|
|
19
|
-
*/
|
|
20
11
|
static getMetadata() {
|
|
21
12
|
return {
|
|
22
13
|
type: 'standard',
|
|
23
|
-
rootDataDependencies: ['portfolio'],
|
|
14
|
+
rootDataDependencies: ['portfolio'],
|
|
24
15
|
isHistorical: false,
|
|
25
16
|
userType: 'all',
|
|
26
17
|
category: 'core_pnl'
|
|
27
18
|
};
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Statically declare dependencies.
|
|
33
|
-
*/
|
|
34
|
-
static getDependencies() {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
37
22
|
|
|
38
|
-
/**
|
|
39
|
-
* Defines the output schema for this calculation.
|
|
40
|
-
* @returns {object} JSON Schema object
|
|
41
|
-
*/
|
|
42
23
|
static getSchema() {
|
|
43
24
|
return {
|
|
44
25
|
"type": "object",
|
|
45
|
-
"description": "Calculates the average daily P&L across all individual positions.",
|
|
46
26
|
"properties": {
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
"total_pnl": {
|
|
52
|
-
"type": "number",
|
|
53
|
-
"description": "The sum of all daily P&L from all positions."
|
|
54
|
-
},
|
|
55
|
-
"position_count": {
|
|
56
|
-
"type": "number",
|
|
57
|
-
"description": "The total number of positions processed."
|
|
58
|
-
}
|
|
27
|
+
"avg_position_pnl_pct": { "type": "number" },
|
|
28
|
+
"aggregate_pnl_pct_sum": { "type": "number" },
|
|
29
|
+
"total_positions": { "type": "number" }
|
|
59
30
|
},
|
|
60
|
-
"required": ["
|
|
31
|
+
"required": ["avg_position_pnl_pct", "aggregate_pnl_pct_sum", "total_positions"]
|
|
61
32
|
};
|
|
62
33
|
}
|
|
63
34
|
|
|
64
|
-
process(
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
35
|
+
process(context) {
|
|
36
|
+
const { extract } = context.math;
|
|
37
|
+
const { user } = context;
|
|
38
|
+
|
|
39
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
69
40
|
|
|
70
41
|
for (const pos of positions) {
|
|
71
|
-
this.
|
|
42
|
+
this.totalPnlPct += extract.getNetProfit(pos);
|
|
72
43
|
this.positionCount++;
|
|
73
44
|
}
|
|
74
45
|
}
|
|
75
46
|
|
|
76
47
|
getResult() {
|
|
77
48
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
49
|
+
avg_position_pnl_pct: (this.positionCount > 0) ? (this.totalPnlPct / this.positionCount) : 0,
|
|
50
|
+
aggregate_pnl_pct_sum: this.totalPnlPct,
|
|
51
|
+
total_positions: this.positionCount
|
|
81
52
|
};
|
|
82
53
|
}
|
|
83
54
|
|
|
84
55
|
reset() {
|
|
85
|
-
this.
|
|
56
|
+
this.totalPnlPct = 0;
|
|
86
57
|
this.positionCount = 0;
|
|
87
58
|
}
|
|
88
59
|
}
|
|
89
|
-
|
|
90
60
|
module.exports = AverageDailyPositionPnl;
|
|
@@ -1,162 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for user behaviour.
|
|
3
|
-
*
|
|
4
|
-
* REFACTOR: This metric now answers: "What is the average holding duration
|
|
5
|
-
* (in hours) for *closed* positions, averaged across all users, grouped by asset?"
|
|
6
|
-
*
|
|
7
|
-
* This calculation now uses the 'history' data source, not 'portfolio'.
|
|
3
|
+
* REFACTORED: Uses context.math.history.getDailyHistory().
|
|
8
4
|
*/
|
|
9
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
class HoldingDurationPerAsset {
|
|
13
6
|
constructor() {
|
|
14
|
-
// { [instrumentId]: { sum_hours: 0, count: 0 } }
|
|
15
|
-
// 'sum_hours' will be the sum of user-level averages
|
|
16
|
-
// 'count' will be the number of users who traded that asset
|
|
17
7
|
this.assets = new Map();
|
|
18
|
-
// --- STANDARD 0: RENAMED ---
|
|
19
8
|
this.tickerMap = null;
|
|
20
9
|
}
|
|
21
10
|
|
|
22
|
-
/**
|
|
23
|
-
* Statically defines all metadata for the manifest builder.
|
|
24
|
-
*/
|
|
25
11
|
static getMetadata() {
|
|
26
|
-
return {
|
|
27
|
-
type: 'standard',
|
|
28
|
-
rootDataDependencies: ['history'], // It only needs the history doc
|
|
29
|
-
isHistorical: false, // It only needs today's history doc
|
|
30
|
-
userType: 'all',
|
|
31
|
-
category: 'core_metrics'
|
|
32
|
-
};
|
|
12
|
+
return { type: 'standard', rootDataDependencies: ['history'], isHistorical: false, userType: 'all', category: 'core_metrics' };
|
|
33
13
|
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Statically declare dependencies.
|
|
37
|
-
*/
|
|
38
|
-
static getDependencies() {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Defines the output schema for this calculation.
|
|
44
|
-
*/
|
|
14
|
+
static getDependencies() { return []; }
|
|
45
15
|
static getSchema() {
|
|
46
|
-
const tickerSchema = {
|
|
47
|
-
|
|
48
|
-
"properties": {
|
|
49
|
-
"avg_duration_hours": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "Average holding duration in hours (averaged across all users)."
|
|
52
|
-
},
|
|
53
|
-
"count": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "Count of users used in average."
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
"required": ["avg_duration_hours", "count"]
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
"type": "object",
|
|
63
|
-
"description": "Calculates the average holding duration (in hours) for closed positions per asset, averaged across all users.",
|
|
64
|
-
"patternProperties": {
|
|
65
|
-
"^.*$": tickerSchema // Ticker
|
|
66
|
-
},
|
|
67
|
-
"additionalProperties": tickerSchema
|
|
68
|
-
};
|
|
16
|
+
const tickerSchema = { "type": "object", "properties": { "avg_duration_hours": { "type": "number" }, "count": { "type": "number" } }, "required": ["avg_duration_hours", "count"] };
|
|
17
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
69
18
|
}
|
|
70
19
|
|
|
71
20
|
_initAsset(instrumentId) {
|
|
72
|
-
if (!this.assets.has(instrumentId)) {
|
|
73
|
-
this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
|
|
74
|
-
}
|
|
21
|
+
if (!this.assets.has(instrumentId)) this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
|
|
75
22
|
}
|
|
76
23
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
todayPortfolio,
|
|
82
|
-
yesterdayPortfolio,
|
|
83
|
-
userId,
|
|
84
|
-
context,
|
|
85
|
-
todayInsights,
|
|
86
|
-
yesterdayInsights,
|
|
87
|
-
todaySocialPostInsights,
|
|
88
|
-
yesterdaySocialPostInsights,
|
|
89
|
-
todayHistory
|
|
90
|
-
) {
|
|
91
|
-
// --- STANDARD 0: FIXED ---
|
|
92
|
-
if (!this.tickerMap) {
|
|
93
|
-
this.tickerMap = context.instrumentToTicker;
|
|
94
|
-
}
|
|
24
|
+
process(context) {
|
|
25
|
+
const { history } = context.math;
|
|
26
|
+
const { mappings, user } = context;
|
|
27
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
95
28
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// places the history object in the *first* argument (todayPortfolio).
|
|
100
|
-
// ---
|
|
101
|
-
const historyData = todayPortfolio; // NOT todayHistory
|
|
102
|
-
if (!historyData || !Array.isArray(historyData.assets)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
29
|
+
// V4: Strict Access via Math Layer
|
|
30
|
+
const historyDoc = history.getDailyHistory(user);
|
|
31
|
+
const tradedAssets = history.getTradedAssets(historyDoc);
|
|
105
32
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
33
|
+
for (const asset of tradedAssets) {
|
|
34
|
+
const instId = history.getInstrumentId(asset);
|
|
35
|
+
if (!instId || instId === -1) continue;
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const durationMinutes = asset.avgHoldingTimeInMinutes;
|
|
116
|
-
|
|
117
|
-
if (typeof durationMinutes === 'number' && durationMinutes > 0) {
|
|
118
|
-
this._initAsset(instrumentId);
|
|
119
|
-
|
|
120
|
-
const assetData = this.assets.get(instrumentId);
|
|
121
|
-
|
|
122
|
-
// Convert minutes to hours and add to the sum
|
|
37
|
+
const durationMinutes = history.getAvgHoldingTimeMinutes(asset);
|
|
38
|
+
if (durationMinutes > 0) {
|
|
39
|
+
this._initAsset(instId);
|
|
40
|
+
const assetData = this.assets.get(instId);
|
|
123
41
|
assetData.sum_hours += (durationMinutes / 60);
|
|
124
|
-
|
|
125
|
-
// Increment count
|
|
126
42
|
assetData.count++;
|
|
127
43
|
}
|
|
128
44
|
}
|
|
129
45
|
}
|
|
130
46
|
|
|
131
47
|
async getResult() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Failsafe check
|
|
135
|
-
if (!this.tickerMap) {
|
|
136
|
-
return {}; // process() must run first
|
|
137
|
-
}
|
|
138
|
-
|
|
48
|
+
if (!this.tickerMap) return {};
|
|
139
49
|
const result = {};
|
|
140
|
-
for (const [
|
|
141
|
-
|
|
142
|
-
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
143
|
-
|
|
50
|
+
for (const [instId, data] of this.assets.entries()) {
|
|
51
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
144
52
|
if (data.count > 0) {
|
|
145
|
-
result[ticker] = {
|
|
146
|
-
// Calculate the final average (avg of avgs)
|
|
147
|
-
avg_duration_hours: data.sum_hours / data.count,
|
|
148
|
-
count: data.count
|
|
149
|
-
};
|
|
53
|
+
result[ticker] = { avg_duration_hours: data.sum_hours / data.count, count: data.count };
|
|
150
54
|
}
|
|
151
55
|
}
|
|
152
56
|
return result;
|
|
153
57
|
}
|
|
154
|
-
|
|
155
|
-
reset() {
|
|
156
|
-
this.assets.clear();
|
|
157
|
-
// --- STANDARD 0: RENAMED ---
|
|
158
|
-
this.tickerMap = null;
|
|
159
|
-
}
|
|
58
|
+
reset() { this.assets.clear(); this.tickerMap = null; }
|
|
160
59
|
}
|
|
161
|
-
|
|
162
60
|
module.exports = HoldingDurationPerAsset;
|
|
@@ -1,75 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
* This 'meta' calculation computes the 1-day percentage price
|
|
5
|
-
* change for all instruments.
|
|
2
|
+
* @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
|
|
3
|
+
* REFACTORED: Uses process(context) solely.
|
|
6
4
|
*/
|
|
7
|
-
|
|
8
5
|
class InstrumentPriceChange1D {
|
|
9
6
|
constructor() {
|
|
10
7
|
this.result = {};
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/** Statically defines metadata */
|
|
14
10
|
static getMetadata() {
|
|
15
11
|
return {
|
|
16
12
|
type: 'meta',
|
|
17
13
|
rootDataDependencies: ['price'],
|
|
18
14
|
isHistorical: true,
|
|
19
|
-
userType:
|
|
15
|
+
userType: 'n/a',
|
|
20
16
|
category: 'core'
|
|
21
17
|
};
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
static getDependencies() { return []; }
|
|
21
|
+
|
|
22
|
+
static getSchema() {
|
|
23
|
+
const tickerSchema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"change_1d_pct": { "type": "number" }
|
|
27
|
+
},
|
|
28
|
+
"required": ["change_1d_pct"]
|
|
29
|
+
};
|
|
30
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
/**
|
|
30
|
-
* Helper to parse price data from the { instrumentId, prices: { '2025-10-01': 123 } } format
|
|
31
|
-
* and return a sorted list.
|
|
32
|
-
*/
|
|
33
33
|
_getPrices(priceData) {
|
|
34
|
-
if (!priceData || !priceData.prices)
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
// FIX: Correctly parse and sort price data
|
|
34
|
+
if (!priceData || !priceData.prices) return [];
|
|
38
35
|
return Object.entries(priceData.prices)
|
|
39
36
|
.map(([date, price]) => ({ date, price }))
|
|
40
37
|
.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* Arg 2: metaRootData (production compliance, unused in test)
|
|
47
|
-
* Arg 3: dependencies (logger, etc., unused in test)
|
|
48
|
-
* Arg 4: config (mappings)
|
|
49
|
-
* Arg 5: fetchedDependencies (unused)
|
|
50
|
-
*/
|
|
51
|
-
process(metaPayload, metaRootData, dependencies, config, fetchedDependencies) {
|
|
52
|
-
const results = {};
|
|
40
|
+
process(context) {
|
|
41
|
+
const { mappings, prices } = context;
|
|
42
|
+
const { instrumentToTicker } = mappings;
|
|
53
43
|
|
|
54
|
-
//
|
|
55
|
-
const todayPrices =
|
|
56
|
-
const yesterdayPrices =
|
|
44
|
+
// Expecting context.prices to be populated by the controller for 'meta' + 'price' dependency
|
|
45
|
+
const todayPrices = prices?.today || [];
|
|
46
|
+
const yesterdayPrices = prices?.yesterday || [];
|
|
47
|
+
|
|
48
|
+
const results = {};
|
|
57
49
|
|
|
58
|
-
// Create lookup maps for today's and yesterday's prices
|
|
59
50
|
const todayPriceMap = new Map(todayPrices.map(p => {
|
|
60
|
-
const
|
|
61
|
-
const lastPrice =
|
|
51
|
+
const pList = this._getPrices(p);
|
|
52
|
+
const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
|
|
62
53
|
return [p.instrumentId, lastPrice];
|
|
63
54
|
}));
|
|
64
55
|
|
|
65
56
|
const yesterdayPriceMap = new Map(yesterdayPrices.map(p => {
|
|
66
|
-
const
|
|
67
|
-
const lastPrice =
|
|
57
|
+
const pList = this._getPrices(p);
|
|
58
|
+
const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
|
|
68
59
|
return [p.instrumentId, lastPrice];
|
|
69
60
|
}));
|
|
70
61
|
|
|
71
|
-
const { instrumentToTicker } = config; // Read from Arg 4
|
|
72
|
-
|
|
73
62
|
for (const [instrumentId, todayPrice] of todayPriceMap) {
|
|
74
63
|
const yesterdayPrice = yesterdayPriceMap.get(instrumentId);
|
|
75
64
|
const ticker = instrumentToTicker[instrumentId];
|
|
@@ -81,21 +70,13 @@ class InstrumentPriceChange1D {
|
|
|
81
70
|
change_1d_pct: isFinite(changePct) ? changePct : 0
|
|
82
71
|
};
|
|
83
72
|
} else {
|
|
84
|
-
results[ticker] = {
|
|
85
|
-
change_1d_pct: 0
|
|
86
|
-
};
|
|
73
|
+
results[ticker] = { change_1d_pct: 0 };
|
|
87
74
|
}
|
|
88
75
|
}
|
|
89
76
|
this.result = results;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
|
-
async getResult() {
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
reset() {
|
|
97
|
-
this.result = {};
|
|
98
|
-
}
|
|
79
|
+
async getResult() { return this.result; }
|
|
80
|
+
reset() { this.result = {}; }
|
|
99
81
|
}
|
|
100
|
-
|
|
101
82
|
module.exports = InstrumentPriceChange1D;
|
|
@@ -1,94 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
* This 'meta' calculation computes the 20-day percentage price
|
|
5
|
-
* change (momentum) for all instruments.
|
|
2
|
+
* @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
|
|
3
|
+
* REFACTORED: Uses process(context) solely.
|
|
6
4
|
*/
|
|
7
|
-
|
|
8
5
|
class InstrumentPriceMomentum20D {
|
|
9
6
|
constructor() {
|
|
10
7
|
this.result = {};
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
/** Statically defines metadata */
|
|
14
10
|
static getMetadata() {
|
|
15
11
|
return {
|
|
16
12
|
type: 'meta',
|
|
17
13
|
rootDataDependencies: ['price'],
|
|
18
|
-
isHistorical: true,
|
|
19
|
-
userType:
|
|
14
|
+
isHistorical: true,
|
|
15
|
+
userType: 'n/a',
|
|
20
16
|
category: 'core'
|
|
21
17
|
};
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
static getDependencies() { return []; }
|
|
21
|
+
|
|
22
|
+
static getSchema() {
|
|
23
|
+
const tickerSchema = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"momentum_20d_pct": { "type": "number" }
|
|
27
|
+
},
|
|
28
|
+
"required": ["momentum_20d_pct"]
|
|
29
|
+
};
|
|
30
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
_findPriceOnOrBefore(priceHistoryObj, targetDateStr) {
|
|
34
|
+
if (!priceHistoryObj || !priceHistoryObj.prices) return null;
|
|
35
|
+
|
|
36
|
+
let checkDate = new Date(targetDateStr + 'T00:00:00Z');
|
|
37
|
+
for (let i = 0; i < 5; i++) {
|
|
38
|
+
const str = checkDate.toISOString().slice(0, 10);
|
|
39
|
+
const price = priceHistoryObj.prices[str];
|
|
40
|
+
if (price !== undefined && price !== null && price > 0) return price;
|
|
41
|
+
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
|
36
42
|
}
|
|
37
|
-
|
|
38
|
-
return Object.entries(priceData.prices)
|
|
39
|
-
.map(([date, price]) => ({ date, price }))
|
|
40
|
-
.sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
43
|
+
return null;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* Arg 5: fetchedDependencies (unused)
|
|
50
|
-
*/
|
|
51
|
-
process(metaPayload, metaRootData, dependencies, config, fetchedDependencies) {
|
|
52
|
-
const results = {};
|
|
46
|
+
process(context) {
|
|
47
|
+
const { mappings, prices, date } = context;
|
|
48
|
+
const { instrumentToTicker } = mappings;
|
|
49
|
+
|
|
50
|
+
// Use historical price data available in context
|
|
51
|
+
const priceData = prices?.history || [];
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
53
|
+
const todayStr = date.today;
|
|
54
|
+
const todayDate = new Date(todayStr + 'T00:00:00Z');
|
|
55
|
+
const twentyDaysAgo = new Date(todayDate);
|
|
56
|
+
twentyDaysAgo.setUTCDate(todayDate.getUTCDate() - 20);
|
|
57
|
+
const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const results = {};
|
|
60
|
+
|
|
61
|
+
for (const p of priceData) {
|
|
62
|
+
const instrumentId = p.instrumentId;
|
|
61
63
|
const ticker = instrumentToTicker[instrumentId];
|
|
62
64
|
if (!ticker) continue;
|
|
63
|
-
|
|
64
|
-
const prices = this._getPrices(instrumentData);
|
|
65
|
-
|
|
66
|
-
if (prices.length >= 20) {
|
|
67
|
-
const currentPrice = prices[prices.length - 1].price;
|
|
68
|
-
const price20DaysAgo = prices[prices.length - 20].price;
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
66
|
+
const currentPrice = this._findPriceOnOrBefore(p, todayStr);
|
|
67
|
+
const oldPrice = this._findPriceOnOrBefore(p, oldStr);
|
|
68
|
+
|
|
69
|
+
if (currentPrice && oldPrice && oldPrice > 0) {
|
|
70
|
+
const momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
|
|
71
|
+
results[ticker] = {
|
|
72
|
+
momentum_20d_pct: isFinite(momPct) ? momPct : 0
|
|
73
|
+
};
|
|
78
74
|
} else {
|
|
79
75
|
results[ticker] = { momentum_20d_pct: 0 };
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
this.result = results;
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
async getResult() {
|
|
86
|
-
return this.result;
|
|
87
|
-
}
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
81
|
+
async getResult() { return this.result; }
|
|
82
|
+
reset() { this.result = {}; }
|
|
92
83
|
}
|
|
93
|
-
|
|
94
84
|
module.exports = InstrumentPriceMomentum20D;
|