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,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++;
|
|
@@ -1,156 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for
|
|
3
|
-
*
|
|
4
|
-
* REFACTOR: This is now a 'type: "meta"' calculation. It runs ONCE.
|
|
5
|
-
* It reads the pre-aggregated 'insights' data source and sums the
|
|
6
|
-
* 'buy' and 'sell' counts from all instruments.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for platform-wide Buy/Sell ratio.
|
|
3
|
+
* REFACTORED: Uses exposure weights.
|
|
7
4
|
*/
|
|
8
|
-
class
|
|
9
|
-
|
|
10
|
-
// --- STANDARD 2: ADDED ---
|
|
5
|
+
class PlatformBuySellSentiment {
|
|
11
6
|
constructor() {
|
|
12
|
-
this.
|
|
7
|
+
this.longWeight = 0;
|
|
8
|
+
this.shortWeight = 0;
|
|
9
|
+
this.longCount = 0;
|
|
10
|
+
this.shortCount = 0;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
/**
|
|
16
|
-
* Statically defines all metadata for the manifest builder.
|
|
17
|
-
*/
|
|
18
13
|
static getMetadata() {
|
|
19
14
|
return {
|
|
20
|
-
type: '
|
|
21
|
-
rootDataDependencies: ['
|
|
15
|
+
type: 'standard',
|
|
16
|
+
rootDataDependencies: ['portfolio'],
|
|
22
17
|
isHistorical: false,
|
|
23
|
-
userType: '
|
|
18
|
+
userType: 'all',
|
|
24
19
|
category: 'core_sentiment'
|
|
25
20
|
};
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
* Statically declare dependencies.
|
|
30
|
-
*/
|
|
31
|
-
static getDependencies() {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
23
|
+
static getDependencies() { return []; }
|
|
34
24
|
|
|
35
|
-
/**
|
|
36
|
-
* Defines the output schema for this calculation.
|
|
37
|
-
*/
|
|
38
25
|
static getSchema() {
|
|
39
26
|
return {
|
|
40
27
|
"type": "object",
|
|
41
|
-
"description": "Total count of 'buy' (long) vs 'sell' (short) positions and the sentiment ratio.",
|
|
42
28
|
"properties": {
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
"totalSellPositions": {
|
|
48
|
-
"type": "number",
|
|
49
|
-
"description": "Total count of all 'sell' (short) positions."
|
|
50
|
-
},
|
|
51
|
-
"sentimentRatio": {
|
|
52
|
-
"type": ["number", "null"],
|
|
53
|
-
"description": "Ratio of buy to sell positions (buy/sell). Null if no sell positions."
|
|
54
|
-
}
|
|
29
|
+
"long_exposure_weight": { "type": "number" },
|
|
30
|
+
"short_exposure_weight": { "type": "number" },
|
|
31
|
+
"sentiment_ratio_weight": { "type": ["number", "null"] },
|
|
32
|
+
"sentiment_ratio_count": { "type": ["number", "null"] }
|
|
55
33
|
},
|
|
56
|
-
"required": ["
|
|
34
|
+
"required": ["long_exposure_weight", "short_exposure_weight", "sentiment_ratio_weight", "sentiment_ratio_count"]
|
|
57
35
|
};
|
|
58
36
|
}
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
65
|
-
let totalBuyPositions = 0;
|
|
66
|
-
let totalSellPositions = 0;
|
|
67
|
-
|
|
68
|
-
// ---
|
|
69
|
-
// FIX: The test harness injects the rootData object as the *first*
|
|
70
|
-
// argument (named 'dateStr' here) for 'meta' calcs.
|
|
71
|
-
// 'rootData' (the 2nd arg) is used for yesterday's data and is null.
|
|
72
|
-
// ---
|
|
73
|
-
const rootDataToday = dateStr;
|
|
74
|
-
const insightsDoc = rootDataToday.insights;
|
|
75
|
-
|
|
76
|
-
if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
|
|
77
|
-
dependencies.logger.log('WARN', `[daily-buy-sell-sentiment-count] No 'insights' data found.`);
|
|
78
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
79
|
-
this.result = {
|
|
80
|
-
totalBuyPositions: 0,
|
|
81
|
-
totalSellPositions: 0,
|
|
82
|
-
sentimentRatio: null
|
|
83
|
-
};
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
38
|
+
process(context) {
|
|
39
|
+
const { extract } = context.math;
|
|
40
|
+
const { user } = context;
|
|
41
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
86
42
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// ---
|
|
91
|
-
// FIX: schema.md shows 'buy' and 'sell' are percentages (e.g., 51, 49).
|
|
92
|
-
// To get the *count*, we must use 'total' and the percentage.
|
|
93
|
-
// total * (buy / 100)
|
|
94
|
-
// But 'total' is the *total raw owners count*, not the total positions.
|
|
95
|
-
// The prompt says "percentage of owners in long/short", so
|
|
96
|
-
// 'buy' and 'sell' ARE the sentiment, not counts.
|
|
97
|
-
//
|
|
98
|
-
// Rereading schema.md:
|
|
99
|
-
// "buy": 51, ---> percentage of owners in long
|
|
100
|
-
// "sell": 49, ---> percentage of owners in short
|
|
101
|
-
// "total": 6149, ---> total raw owners count today
|
|
102
|
-
//
|
|
103
|
-
// The description says "Total count of 'buy' (long) vs 'sell' (short) positions".
|
|
104
|
-
// The old code `totalBuyPositions += instrument.buy;` assumes 'buy' is a count.
|
|
105
|
-
// Based on the schema, 'buy' is a *percentage*.
|
|
106
|
-
//
|
|
107
|
-
// Let's assume the intent is to calculate the weighted average sentiment.
|
|
108
|
-
// No, the schema *output* says "totalBuyPositions" and "totalSellPositions".
|
|
109
|
-
//
|
|
110
|
-
// THIS IS THE REAL BUG.
|
|
111
|
-
// The schema 'insights' does not provide *counts* of buy/sell, only *percentages*.
|
|
112
|
-
// The calculation *assumes* 'instrument.buy' is a count.
|
|
113
|
-
//
|
|
114
|
-
// Let's look at the schema again:
|
|
115
|
-
// "buy": 51
|
|
116
|
-
// "sell": 49
|
|
117
|
-
// "total": 6149
|
|
118
|
-
//
|
|
119
|
-
// The only logical interpretation is:
|
|
120
|
-
// Buy Count = total * (buy / 100)
|
|
121
|
-
// Sell Count = total * (sell / 100)
|
|
122
|
-
// ---
|
|
43
|
+
for (const pos of positions) {
|
|
44
|
+
const weight = extract.getPositionWeight(pos, user.type);
|
|
45
|
+
const direction = extract.getDirection(pos);
|
|
123
46
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
totalSellPositions += totalOwners * (instrument.sell / 100);
|
|
131
|
-
}
|
|
47
|
+
if (direction === 'Buy') {
|
|
48
|
+
this.longWeight += weight;
|
|
49
|
+
this.longCount++;
|
|
50
|
+
} else {
|
|
51
|
+
this.shortWeight += weight;
|
|
52
|
+
this.shortCount++;
|
|
132
53
|
}
|
|
133
54
|
}
|
|
134
|
-
|
|
135
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
136
|
-
this.result = {
|
|
137
|
-
// Round the counts, as they are now derived from percentages
|
|
138
|
-
totalBuyPositions: Math.round(totalBuyPositions),
|
|
139
|
-
totalSellPositions: Math.round(totalSellPositions),
|
|
140
|
-
// Calculate ratio: Buy / Sell
|
|
141
|
-
sentimentRatio: (totalSellPositions > 0) ? (totalBuyPositions / totalSellPositions) : null
|
|
142
|
-
};
|
|
143
55
|
}
|
|
144
56
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
57
|
+
getResult() {
|
|
58
|
+
return {
|
|
59
|
+
long_exposure_weight: this.longWeight,
|
|
60
|
+
short_exposure_weight: this.shortWeight,
|
|
61
|
+
sentiment_ratio_weight: this.shortWeight > 0 ? this.longWeight / this.shortWeight : null,
|
|
62
|
+
sentiment_ratio_count: this.shortCount > 0 ? this.longCount / this.shortCount : null
|
|
63
|
+
};
|
|
148
64
|
}
|
|
149
65
|
|
|
150
|
-
// --- STANDARD 2: ADDED ---
|
|
151
66
|
reset() {
|
|
152
|
-
this.
|
|
67
|
+
this.longWeight = 0;
|
|
68
|
+
this.shortWeight = 0;
|
|
69
|
+
this.longCount = 0;
|
|
70
|
+
this.shortCount = 0;
|
|
153
71
|
}
|
|
154
72
|
}
|
|
155
|
-
|
|
156
|
-
module.exports = DailyBuySellSentimentCount;
|
|
73
|
+
module.exports = PlatformBuySellSentiment;
|