aiden-shared-calculations-unified 1.0.86 → 1.0.87
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/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/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/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/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,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;
|
|
@@ -1,112 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for long positions per stock.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "How many long ('buy') positions
|
|
5
|
-
* are there for each stock?"
|
|
3
|
+
* REFACTORED: Uses context.math.extract. No USD.
|
|
6
4
|
*/
|
|
7
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
8
|
-
|
|
9
5
|
class LongPositionPerStock {
|
|
10
6
|
constructor() {
|
|
11
|
-
|
|
12
|
-
this.assets = new Map();
|
|
13
|
-
// --- STANDARD 0: RENAMED ---
|
|
7
|
+
this.stockData = new Map();
|
|
14
8
|
this.tickerMap = null;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
/**
|
|
18
|
-
* Statically defines all metadata for the manifest builder.
|
|
19
|
-
*/
|
|
20
11
|
static getMetadata() {
|
|
21
12
|
return {
|
|
22
13
|
type: 'standard',
|
|
23
14
|
rootDataDependencies: ['portfolio'],
|
|
24
15
|
isHistorical: false,
|
|
25
16
|
userType: 'all',
|
|
26
|
-
category: 'core_sentiment'
|
|
17
|
+
category: 'core_sentiment'
|
|
27
18
|
};
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
* Statically declare dependencies.
|
|
32
|
-
*/
|
|
33
|
-
static getDependencies() {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
36
22
|
|
|
37
|
-
/**
|
|
38
|
-
* Defines the output schema for this calculation.
|
|
39
|
-
*/
|
|
40
23
|
static getSchema() {
|
|
41
|
-
|
|
24
|
+
const schema = {
|
|
42
25
|
"type": "object",
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"^.*$": {
|
|
47
|
-
"type": "number",
|
|
48
|
-
"description": "The total count of long positions for this asset."
|
|
49
|
-
}
|
|
26
|
+
"properties": {
|
|
27
|
+
"long_count": { "type": "number" },
|
|
28
|
+
"total_long_exposure_weight": { "type": "number" }
|
|
50
29
|
},
|
|
51
|
-
"
|
|
52
|
-
"type": "number"
|
|
53
|
-
}
|
|
30
|
+
"required": ["long_count", "total_long_exposure_weight"]
|
|
54
31
|
};
|
|
32
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
55
33
|
}
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
if (!this.
|
|
59
|
-
this.
|
|
35
|
+
_initStock(instId) {
|
|
36
|
+
if (!this.stockData.has(instId)) {
|
|
37
|
+
this.stockData.set(instId, { count: 0, weight: 0 });
|
|
60
38
|
}
|
|
61
39
|
}
|
|
62
40
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (!this.tickerMap)
|
|
67
|
-
this.tickerMap = context.instrumentToTicker;
|
|
68
|
-
}
|
|
41
|
+
process(context) {
|
|
42
|
+
const { extract } = context.math;
|
|
43
|
+
const { mappings, user } = context;
|
|
44
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
69
45
|
|
|
70
|
-
|
|
71
|
-
const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
|
|
72
|
-
if (!positions || !Array.isArray(positions)) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
46
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
75
47
|
|
|
76
48
|
for (const pos of positions) {
|
|
77
|
-
|
|
78
|
-
if (pos.IsBuy) {
|
|
79
|
-
const instrumentId = pos.InstrumentID;
|
|
80
|
-
if (!instrumentId) continue;
|
|
81
|
-
|
|
82
|
-
this._initAsset(instrumentId);
|
|
83
|
-
this.assets.set(instrumentId, this.assets.get(instrumentId) + 1);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
49
|
+
if (extract.getDirection(pos) !== 'Buy') continue;
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
51
|
+
const instId = extract.getInstrumentId(pos);
|
|
52
|
+
if (!instId) continue;
|
|
90
53
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
54
|
+
const weight = extract.getPositionWeight(pos, user.type);
|
|
55
|
+
|
|
56
|
+
this._initStock(instId);
|
|
57
|
+
const data = this.stockData.get(instId);
|
|
58
|
+
data.count++;
|
|
59
|
+
data.weight += weight;
|
|
94
60
|
}
|
|
61
|
+
}
|
|
95
62
|
|
|
63
|
+
async getResult() {
|
|
64
|
+
if (!this.tickerMap) return {};
|
|
96
65
|
const result = {};
|
|
97
|
-
for (const [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
66
|
+
for (const [instId, data] of this.stockData.entries()) {
|
|
67
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
68
|
+
if (data.count > 0) {
|
|
69
|
+
result[ticker] = {
|
|
70
|
+
long_count: data.count,
|
|
71
|
+
total_long_exposure_weight: data.weight
|
|
72
|
+
};
|
|
73
|
+
}
|
|
101
74
|
}
|
|
102
75
|
return result;
|
|
103
76
|
}
|
|
104
77
|
|
|
105
78
|
reset() {
|
|
106
|
-
this.
|
|
107
|
-
// --- STANDARD 0: RENAMED ---
|
|
79
|
+
this.stockData.clear();
|
|
108
80
|
this.tickerMap = null;
|
|
109
81
|
}
|
|
110
82
|
}
|
|
111
|
-
|
|
112
83
|
module.exports = LongPositionPerStock;
|
|
@@ -1,108 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for user behaviour.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the average holding duration (in hours)
|
|
5
|
-
* for *closed* positions, averaged across all users?"
|
|
6
|
-
*
|
|
7
|
-
* It uses the 'history' data source's 'all' object.
|
|
3
|
+
* REFACTORED: Uses context.math.history.getDailyHistory().
|
|
8
4
|
*/
|
|
9
5
|
class AverageHoldingDurationOverall {
|
|
10
|
-
constructor() {
|
|
11
|
-
// Stores all the user-level average durations
|
|
12
|
-
this.durationsHours = [];
|
|
13
|
-
}
|
|
6
|
+
constructor() { this.durationsHours = []; }
|
|
14
7
|
|
|
15
|
-
// --- NEW ---
|
|
16
|
-
/**
|
|
17
|
-
* Statically defines all metadata for the manifest builder.
|
|
18
|
-
*/
|
|
19
8
|
static getMetadata() {
|
|
20
|
-
return {
|
|
21
|
-
type: 'standard',
|
|
22
|
-
rootDataDependencies: ['history'], // Needs the history doc
|
|
23
|
-
isHistorical: false, // Needs today's history doc
|
|
24
|
-
userType: 'all',
|
|
25
|
-
category: 'core_metrics'
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// --- NEW ---
|
|
30
|
-
/**
|
|
31
|
-
* Statically declare dependencies.
|
|
32
|
-
*/
|
|
33
|
-
static getDependencies() {
|
|
34
|
-
return [];
|
|
9
|
+
return { type: 'standard', rootDataDependencies: ['history'], isHistorical: false, userType: 'all', category: 'core_metrics' };
|
|
35
10
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Defines the output schema for this calculation.
|
|
39
|
-
* @returns {object} JSON Schema object
|
|
40
|
-
*/
|
|
11
|
+
static getDependencies() { return []; }
|
|
41
12
|
static getSchema() {
|
|
42
|
-
return {
|
|
43
|
-
"type": "object",
|
|
44
|
-
"description": "Calculates the platform-wide average holding duration (in hours) for closed positions.",
|
|
45
|
-
"properties": {
|
|
46
|
-
"average_duration_hours": {
|
|
47
|
-
"type": "number",
|
|
48
|
-
"description": "The average holding duration in hours, averaged across all users."
|
|
49
|
-
},
|
|
50
|
-
"user_count": {
|
|
51
|
-
"type": "number",
|
|
52
|
-
"description": "The number of users included in the average."
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"required": ["average_duration_hours", "user_count"]
|
|
56
|
-
};
|
|
13
|
+
return { "type": "object", "properties": { "average_duration_hours": { "type": "number" }, "user_count": { "type": "number" } }, "required": ["average_duration_hours", "user_count"] };
|
|
57
14
|
}
|
|
58
15
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* @param {object} todayPortfolio - Contains today's history doc
|
|
63
|
-
* @param {object} yesterdayPortfolio - (Not used)
|
|
64
|
-
* @param {string} userId - The user ID.
|
|
65
|
-
*/
|
|
66
|
-
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
67
|
-
|
|
68
|
-
// ---
|
|
69
|
-
// FIX: The test harness injects data based on getMetadata().
|
|
70
|
-
// Because rootDataDependencies is ONLY ['history'], the test harness
|
|
71
|
-
// places the history object in the *first* argument (todayPortfolio).
|
|
72
|
-
// ---
|
|
73
|
-
const historyData = todayPortfolio; // NOT todayPortfolio?.history
|
|
74
|
-
if (!historyData || !historyData.all) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
16
|
+
process(context) {
|
|
17
|
+
const { history } = context.math;
|
|
18
|
+
const { user } = context;
|
|
77
19
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
20
|
+
// V4: Strict Access via Math Layer
|
|
21
|
+
const historyDoc = history.getDailyHistory(user);
|
|
22
|
+
const summary = history.getSummary(historyDoc);
|
|
80
23
|
|
|
81
|
-
if (
|
|
82
|
-
this.durationsHours.push(
|
|
24
|
+
if (summary && summary.avgHoldingTimeInMinutes > 0) {
|
|
25
|
+
this.durationsHours.push(summary.avgHoldingTimeInMinutes / 60);
|
|
83
26
|
}
|
|
84
27
|
}
|
|
85
28
|
|
|
86
29
|
getResult() {
|
|
87
30
|
const count = this.durationsHours.length;
|
|
88
|
-
if (count === 0) {
|
|
89
|
-
return {
|
|
90
|
-
average_duration_hours: 0,
|
|
91
|
-
user_count: 0
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
31
|
+
if (count === 0) return { average_duration_hours: 0, user_count: 0 };
|
|
95
32
|
const sum = this.durationsHours.reduce((a, b) => a + b, 0);
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
average_duration_hours: sum / count,
|
|
99
|
-
user_count: count
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
reset() {
|
|
104
|
-
this.durationsHours = [];
|
|
33
|
+
return { average_duration_hours: sum / count, user_count: count };
|
|
105
34
|
}
|
|
35
|
+
reset() { this.durationsHours = []; }
|
|
106
36
|
}
|
|
107
|
-
|
|
108
37
|
module.exports = AverageHoldingDurationOverall;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for P&L.
|
|
3
|
-
*
|
|
4
|
-
* This metric provides the *overall* platform-wide profitability ratio
|
|
5
|
-
* (total profitable positions / total unprofitable positions)
|
|
6
|
-
* across all users and all open positions.
|
|
3
|
+
* REFACTORED: Standardized extraction.
|
|
7
4
|
*/
|
|
8
5
|
class ProfitabilityRatioOverall {
|
|
9
6
|
constructor() {
|
|
@@ -11,10 +8,6 @@ class ProfitabilityRatioOverall {
|
|
|
11
8
|
this.unprofitable = 0;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
// --- NEW ---
|
|
15
|
-
/**
|
|
16
|
-
* Statically defines all metadata for the manifest builder.
|
|
17
|
-
*/
|
|
18
11
|
static getMetadata() {
|
|
19
12
|
return {
|
|
20
13
|
type: 'standard',
|
|
@@ -25,50 +18,28 @@ class ProfitabilityRatioOverall {
|
|
|
25
18
|
};
|
|
26
19
|
}
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Statically declare dependencies.
|
|
31
|
-
*/
|
|
32
|
-
static getDependencies() {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
35
22
|
|
|
36
|
-
/**
|
|
37
|
-
* Defines the output schema for this calculation.
|
|
38
|
-
* @returns {object} JSON Schema object
|
|
39
|
-
*/
|
|
40
23
|
static getSchema() {
|
|
41
24
|
return {
|
|
42
25
|
"type": "object",
|
|
43
|
-
"description": "Calculates the overall profitability ratio (profitable/unprofitable positions) for the entire platform.",
|
|
44
26
|
"properties": {
|
|
45
|
-
"overall_ratio": {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
"total_profitable": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "Total count of all profitable positions."
|
|
52
|
-
},
|
|
53
|
-
"total_unprofitable": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "Total count of all unprofitable positions."
|
|
56
|
-
}
|
|
27
|
+
"overall_ratio": { "type": ["number", "null"] },
|
|
28
|
+
"total_profitable": { "type": "number" },
|
|
29
|
+
"total_unprofitable": { "type": "number" }
|
|
57
30
|
},
|
|
58
31
|
"required": ["overall_ratio", "total_profitable", "total_unprofitable"]
|
|
59
32
|
};
|
|
60
33
|
}
|
|
61
34
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
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
|
-
const pnl = pos
|
|
42
|
+
const pnl = extract.getNetProfit(pos); // This is a %
|
|
72
43
|
|
|
73
44
|
if (pnl > 0) {
|
|
74
45
|
this.profitable++;
|