aiden-shared-calculations-unified 1.0.84 → 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,17 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* - Added defensive check in _loadDependencies for 'instrument-price-momentum-20d'.
|
|
5
|
-
* - This calc fails because its dependency fails. The logic is correct.
|
|
3
|
+
* REFACTORED: Uses context.computed and context.math.
|
|
6
4
|
*/
|
|
7
|
-
|
|
8
5
|
class CohortMomentumState {
|
|
9
6
|
constructor() {
|
|
10
7
|
this.cohortMomentum = new Map();
|
|
11
8
|
this.cohortMap = new Map();
|
|
12
|
-
this.tickerMap = null;
|
|
13
9
|
this.dependenciesLoaded = false;
|
|
14
|
-
this.
|
|
10
|
+
this.tickerMap = null; // Need to store this
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
static getMetadata() {
|
|
@@ -25,10 +21,7 @@ class CohortMomentumState {
|
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
static getDependencies() {
|
|
28
|
-
return [
|
|
29
|
-
'cohort-skill-definition',
|
|
30
|
-
'instrument-price-momentum-20d'
|
|
31
|
-
];
|
|
24
|
+
return ['cohort-skill-definition', 'instrument-price-momentum-20d'];
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
static getSchema() {
|
|
@@ -40,107 +33,69 @@ class CohortMomentumState {
|
|
|
40
33
|
},
|
|
41
34
|
"required": ["average_momentum_exposure_pct", "trade_count"]
|
|
42
35
|
};
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"description": "Calculates the average 20D momentum % for all *new* trades, bucketed by skill cohort.",
|
|
47
|
-
"properties": {
|
|
48
|
-
"skilled": cohortSchema,
|
|
49
|
-
"unskilled": cohortSchema
|
|
50
|
-
}
|
|
51
|
-
};
|
|
36
|
+
return { "type": "object", "properties": { "skilled": cohortSchema, "unskilled": cohortSchema } };
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
_loadDependencies(
|
|
39
|
+
_loadDependencies(computed) {
|
|
55
40
|
if (this.dependenciesLoaded) return;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
const cohortData = fetchedDependencies['cohort-skill-definition'];
|
|
62
|
-
if (!cohortData) {
|
|
63
|
-
throw new Error("[cohort-momentum-state] Dependency Error: 'cohort-skill-definition' was missing.");
|
|
41
|
+
const cohortData = computed['cohort-skill-definition'];
|
|
42
|
+
if (cohortData) {
|
|
43
|
+
(cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
44
|
+
(cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
|
|
64
45
|
}
|
|
65
|
-
this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
|
|
66
|
-
if (!this.momentumData) {
|
|
67
|
-
throw new Error("[cohort-momentum-state] Dependency Error: 'instrument-price-momentum-20d' was missing. This is the root cause of failure.");
|
|
68
|
-
}
|
|
69
|
-
// --- END FIX ---
|
|
70
|
-
|
|
71
|
-
// Cohort data structure is correct
|
|
72
|
-
(cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
|
|
73
|
-
(cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
|
|
74
|
-
|
|
75
46
|
this.dependenciesLoaded = true;
|
|
76
47
|
}
|
|
77
48
|
|
|
78
49
|
_initCohort(cohortName) {
|
|
79
|
-
if (!this.cohortMomentum.has(cohortName)) {
|
|
80
|
-
this.cohortMomentum.set(cohortName, { momentum_sum: 0, count: 0 });
|
|
81
|
-
}
|
|
50
|
+
if (!this.cohortMomentum.has(cohortName)) this.cohortMomentum.set(cohortName, { momentum_sum: 0, count: 0 });
|
|
82
51
|
}
|
|
83
52
|
|
|
84
|
-
process(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
53
|
+
process(context) {
|
|
54
|
+
const { user, computed, mappings, math } = context;
|
|
55
|
+
const { extract } = math;
|
|
56
|
+
|
|
57
|
+
this._loadDependencies(computed);
|
|
58
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
91
59
|
|
|
92
|
-
const cohortName = this.cohortMap.get(
|
|
93
|
-
if (!cohortName)
|
|
94
|
-
return; // Not in a defined cohort
|
|
95
|
-
}
|
|
60
|
+
const cohortName = this.cohortMap.get(user.id);
|
|
61
|
+
if (!cohortName) return;
|
|
96
62
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
63
|
+
const momentumData = computed['instrument-price-momentum-20d'];
|
|
64
|
+
if (!momentumData) return;
|
|
100
65
|
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
const newPositions = (todayPortfolio?.AggregatedPositions || []).filter(p => p.InstrumentID && !yIds.has(p.InstrumentID));
|
|
66
|
+
const yIds = new Set(extract.getPositions(user.portfolio.yesterday, user.type).map(p => extract.getInstrumentId(p)));
|
|
67
|
+
const newPositions = extract.getPositions(user.portfolio.today, user.type).filter(p => !yIds.has(extract.getInstrumentId(p)));
|
|
104
68
|
|
|
105
|
-
if (newPositions.length === 0)
|
|
106
|
-
return; // No new positions
|
|
107
|
-
}
|
|
69
|
+
if (newPositions.length === 0) return;
|
|
108
70
|
|
|
109
71
|
this._initCohort(cohortName);
|
|
110
72
|
const asset = this.cohortMomentum.get(cohortName);
|
|
111
73
|
|
|
112
74
|
for (const pos of newPositions) {
|
|
113
|
-
const ticker =
|
|
114
|
-
if (ticker &&
|
|
115
|
-
asset.momentum_sum +=
|
|
75
|
+
const ticker = mappings.instrumentToTicker[extract.getInstrumentId(pos)];
|
|
76
|
+
if (ticker && momentumData[ticker]) {
|
|
77
|
+
asset.momentum_sum += momentumData[ticker].momentum_20d_pct || 0;
|
|
116
78
|
asset.count++;
|
|
117
79
|
}
|
|
118
80
|
}
|
|
119
81
|
}
|
|
120
82
|
|
|
121
83
|
async getResult() {
|
|
122
|
-
if (!this.tickerMap) {
|
|
123
|
-
return {};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
84
|
const finalResult = {};
|
|
127
|
-
|
|
128
85
|
for (const [cohortName, data] of this.cohortMomentum.entries()) {
|
|
129
86
|
finalResult[cohortName] = {
|
|
130
87
|
average_momentum_exposure_pct: (data.count > 0) ? data.momentum_sum / data.count : 0,
|
|
131
88
|
trade_count: data.count
|
|
132
89
|
};
|
|
133
90
|
}
|
|
134
|
-
|
|
135
91
|
return finalResult;
|
|
136
92
|
}
|
|
137
93
|
|
|
138
94
|
reset() {
|
|
139
95
|
this.cohortMomentum.clear();
|
|
140
96
|
this.cohortMap.clear();
|
|
141
|
-
this.tickerMap = null;
|
|
142
97
|
this.dependenciesLoaded = false;
|
|
143
|
-
this.
|
|
98
|
+
this.tickerMap = null;
|
|
144
99
|
}
|
|
145
100
|
}
|
|
146
101
|
module.exports = CohortMomentumState;
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
* - 'todayPortfolio' IS the history object.
|
|
5
|
-
* - Changed 'todayPortfolio?.all' to 'historyData?.all' for clarity.
|
|
6
|
-
* - This calculation is already correct based on the worker 'hack'.
|
|
2
|
+
* @fileoverview GEM Product Line (Pass 1)
|
|
3
|
+
* REFACTORED: Strictly uses context.math.history to access user history.
|
|
7
4
|
*/
|
|
8
5
|
class CohortSkillDefinition {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.userScores = new Map();
|
|
11
|
-
}
|
|
6
|
+
constructor() { this.userScores = new Map(); }
|
|
12
7
|
|
|
13
8
|
static getSchema() {
|
|
14
9
|
return {
|
|
15
10
|
"type": "object",
|
|
16
|
-
"description": "Provides the user ID lists for the 'Skilled' (top 20%) and 'Unskilled' (bottom 20%) cohorts based on historical trade performance.",
|
|
17
11
|
"properties": {
|
|
18
12
|
"skilled_user_ids": { "type": "array", "items": { "type": "string" } },
|
|
19
13
|
"unskilled_user_ids": { "type": "array", "items": { "type": "string" } },
|
|
20
14
|
"skilled_cohort_size": { "type": "number" },
|
|
21
15
|
"unskilled_cohort_size": { "type": "number" }
|
|
22
16
|
},
|
|
23
|
-
"required": ["skilled_user_ids", "unskilled_user_ids"
|
|
17
|
+
"required": ["skilled_user_ids", "unskilled_user_ids"]
|
|
24
18
|
};
|
|
25
19
|
}
|
|
26
20
|
|
|
@@ -34,63 +28,53 @@ class CohortSkillDefinition {
|
|
|
34
28
|
};
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
static getDependencies() {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// --- THIS IS THE FIX ---
|
|
42
|
-
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
31
|
+
static getDependencies() { return []; }
|
|
43
32
|
|
|
33
|
+
process(context) {
|
|
34
|
+
const { user, math } = context;
|
|
35
|
+
const { history } = math;
|
|
36
|
+
|
|
37
|
+
// 1. Get strict daily history
|
|
38
|
+
const historyDoc = history.getDailyHistory(user);
|
|
39
|
+
|
|
40
|
+
// 2. Get strict summary DTO
|
|
41
|
+
const summary = history.getSummary(historyDoc);
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
const history = historyData?.all;
|
|
43
|
+
if (!summary || summary.totalTrades < 10) return;
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const { winRatio, avgProfitPct, avgLossPct, totalTrades } = history;
|
|
53
|
-
|
|
54
|
-
if (!totalTrades || totalTrades < 10) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const winRate = winRatio / 100.0;
|
|
45
|
+
// 3. Valid DTO usage
|
|
46
|
+
const winRate = summary.winRatio / 100.0;
|
|
59
47
|
const lossRate = 1.0 - winRate;
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const skillScore = expectancy * Math.log10(Math.max(1, totalTrades));
|
|
48
|
+
|
|
49
|
+
const expectancy = (winRate * summary.avgProfitPct) - (lossRate * Math.abs(summary.avgLossPct));
|
|
50
|
+
|
|
51
|
+
const skillScore = expectancy * Math.log10(Math.max(1, summary.totalTrades));
|
|
65
52
|
|
|
66
|
-
if (isFinite(skillScore))
|
|
67
|
-
this.userScores.set(userId, skillScore);
|
|
68
|
-
}
|
|
53
|
+
if (isFinite(skillScore)) this.userScores.set(user.id, skillScore);
|
|
69
54
|
}
|
|
70
55
|
|
|
71
56
|
getResult() {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const cohortSize = Math.floor(sortedUsers.length * 0.20);
|
|
57
|
+
const sorted = Array.from(this.userScores.entries()).sort((a, b) => b[1] - a[1]);
|
|
58
|
+
const cohortSize = Math.floor(sorted.length * 0.20);
|
|
59
|
+
|
|
76
60
|
if (cohortSize === 0) {
|
|
77
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
skilled_user_ids: [], unskilled_user_ids: [],
|
|
63
|
+
skilled_cohort_size: 0, unskilled_cohort_size: 0
|
|
64
|
+
};
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
const
|
|
81
|
-
const
|
|
67
|
+
const skilled = sorted.slice(0, cohortSize).map(u => String(u[0]));
|
|
68
|
+
const unskilled = sorted.slice(-cohortSize).map(u => String(u[0]));
|
|
82
69
|
|
|
83
70
|
return {
|
|
84
|
-
skilled_user_ids:
|
|
85
|
-
unskilled_user_ids:
|
|
86
|
-
skilled_cohort_size:
|
|
87
|
-
unskilled_cohort_size:
|
|
71
|
+
skilled_user_ids: skilled,
|
|
72
|
+
unskilled_user_ids: unskilled,
|
|
73
|
+
skilled_cohort_size: skilled.length,
|
|
74
|
+
unskilled_cohort_size: unskilled.length
|
|
88
75
|
};
|
|
89
76
|
}
|
|
90
77
|
|
|
91
|
-
reset() {
|
|
92
|
-
this.userScores.clear();
|
|
93
|
-
}
|
|
78
|
+
reset() { this.userScores.clear(); }
|
|
94
79
|
}
|
|
95
|
-
|
|
96
80
|
module.exports = CohortSkillDefinition;
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 3)
|
|
3
|
-
*
|
|
4
|
-
* - This calc is failing because its dependencies are failing.
|
|
5
|
-
* - Added defensive checks for missing dependencies.
|
|
6
|
-
* - Updated process signature to 5-arg meta standard.
|
|
3
|
+
* REFACTORED: Uses context.math.signals and process(context).
|
|
7
4
|
*/
|
|
8
5
|
class PlatformConvictionDivergence {
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
this.result = {};
|
|
12
|
-
}
|
|
6
|
+
constructor() { this.result = {}; }
|
|
13
7
|
|
|
14
8
|
static getMetadata() {
|
|
15
9
|
return {
|
|
@@ -21,12 +15,7 @@ class PlatformConvictionDivergence {
|
|
|
21
15
|
};
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
static getDependencies() {
|
|
25
|
-
return [
|
|
26
|
-
'skilled-cohort-flow',
|
|
27
|
-
'unskilled-cohort-flow'
|
|
28
|
-
];
|
|
29
|
-
}
|
|
18
|
+
static getDependencies() { return ['skilled-cohort-flow', 'unskilled-cohort-flow']; }
|
|
30
19
|
|
|
31
20
|
static getSchema() {
|
|
32
21
|
const tickerSchema = {
|
|
@@ -38,63 +27,32 @@ class PlatformConvictionDivergence {
|
|
|
38
27
|
},
|
|
39
28
|
"required": ["skilled_conviction_change_pct", "unskilled_conviction_change_pct", "conviction_divergence_score"]
|
|
40
29
|
};
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
"type": "object",
|
|
44
|
-
"description": "Tracks the divergence in 'conviction' (change in avg. position size) between skilled and unskilled cohorts.",
|
|
45
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
46
|
-
"additionalProperties": tickerSchema
|
|
47
|
-
};
|
|
30
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
48
31
|
}
|
|
49
32
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const skilledFlow = fetchedDependencies['skilled-cohort-flow'];
|
|
54
|
-
const unskilledFlow = fetchedDependencies['unskilled-cohort-flow'];
|
|
55
|
-
|
|
56
|
-
if (!skilledFlow || !unskilledFlow) {
|
|
57
|
-
// This is expected until the worker bug is fixed
|
|
58
|
-
this.result = {};
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const allTickers = new Set([
|
|
63
|
-
...Object.keys(skilledFlow),
|
|
64
|
-
...Object.keys(unskilledFlow)
|
|
65
|
-
]);
|
|
33
|
+
process(context) {
|
|
34
|
+
const { computed, math } = context;
|
|
35
|
+
const { signals } = math;
|
|
66
36
|
|
|
37
|
+
const tickers = signals.getUnionKeys(computed, ['skilled-cohort-flow', 'unskilled-cohort-flow']);
|
|
67
38
|
const result = {};
|
|
68
39
|
|
|
69
|
-
for (const ticker of
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const skilled_conviction = skilledData?.avg_position_change_pct || 0;
|
|
75
|
-
const unskilled_conviction = unskilledData?.avg_position_change_pct || 0;
|
|
76
|
-
|
|
77
|
-
if (skilled_conviction === 0 && unskilled_conviction === 0) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
40
|
+
for (const ticker of tickers) {
|
|
41
|
+
const skilled = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'avg_position_change_pct');
|
|
42
|
+
const unskilled = signals.getMetric(computed, 'unskilled-cohort-flow', ticker, 'avg_position_change_pct');
|
|
43
|
+
|
|
44
|
+
if (skilled === 0 && unskilled === 0) continue;
|
|
80
45
|
|
|
81
46
|
result[ticker] = {
|
|
82
|
-
skilled_conviction_change_pct:
|
|
83
|
-
unskilled_conviction_change_pct:
|
|
84
|
-
conviction_divergence_score:
|
|
47
|
+
skilled_conviction_change_pct: skilled,
|
|
48
|
+
unskilled_conviction_change_pct: unskilled,
|
|
49
|
+
conviction_divergence_score: signals.divergence(skilled, unskilled)
|
|
85
50
|
};
|
|
86
51
|
}
|
|
87
|
-
|
|
88
52
|
this.result = result;
|
|
89
53
|
}
|
|
90
|
-
// --- END FIX ---
|
|
91
|
-
|
|
92
|
-
async getResult(fetchedDependencies) {
|
|
93
|
-
return this.result;
|
|
94
|
-
}
|
|
95
54
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
55
|
+
async getResult() { return this.result; }
|
|
56
|
+
reset() { this.result = {}; }
|
|
99
57
|
}
|
|
100
58
|
module.exports = PlatformConvictionDivergence;
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 4)
|
|
3
|
-
*
|
|
4
|
-
* This is the final, stateless signal generator for the GEM line.
|
|
5
|
-
* It answers: "Are skilled users buying into *positive* momentum
|
|
6
|
-
* while unskilled users are buying into *negative* momentum?"
|
|
3
|
+
* REFACTORED: Uses context.math.signals and process(context).
|
|
7
4
|
*/
|
|
8
5
|
class QuantSkillAlphaSignal {
|
|
6
|
+
constructor() { this.result = {}; }
|
|
9
7
|
|
|
10
|
-
// --- STANDARD 2: ADDED ---
|
|
11
|
-
constructor() {
|
|
12
|
-
this.result = {};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Statically defines metadata */
|
|
16
8
|
static getMetadata() {
|
|
17
9
|
return {
|
|
18
10
|
type: 'meta',
|
|
@@ -23,69 +15,36 @@ class QuantSkillAlphaSignal {
|
|
|
23
15
|
};
|
|
24
16
|
}
|
|
25
17
|
|
|
26
|
-
/** Statically declare dependencies */
|
|
27
18
|
static getDependencies() {
|
|
28
|
-
return [
|
|
29
|
-
'skilled-unskilled-divergence', // from gem (Pass 3)
|
|
30
|
-
'cohort-momentum-state', // from gem (Pass 2)
|
|
31
|
-
'instrument-price-momentum-20d'// from core (Pass 1)
|
|
32
|
-
];
|
|
19
|
+
return ['skilled-unskilled-divergence', 'cohort-momentum-state', 'instrument-price-momentum-20d'];
|
|
33
20
|
}
|
|
34
21
|
|
|
35
|
-
/**
|
|
36
|
-
* Defines the output schema for this calculation.
|
|
37
|
-
*/
|
|
38
22
|
static getSchema() {
|
|
39
23
|
const tickerSchema = {
|
|
40
24
|
"type": "object",
|
|
41
25
|
"properties": {
|
|
42
|
-
"signal": { "type": "string"
|
|
26
|
+
"signal": { "type": "string" },
|
|
43
27
|
"gem_score": { "type": "number" },
|
|
44
|
-
"components": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"properties": {
|
|
47
|
-
"flow_divergence": { "type": "number" },
|
|
48
|
-
"momentum_divergence": { "type": "number" },
|
|
49
|
-
"asset_momentum": { "type": "number" }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
28
|
+
"components": { "type": "object" }
|
|
52
29
|
},
|
|
53
|
-
"required": ["signal", "gem_score"
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"description": "Generates a final skill-based alpha signal.",
|
|
59
|
-
"patternProperties": { "^.*$": tickerSchema },
|
|
60
|
-
"additionalProperties": tickerSchema
|
|
30
|
+
"required": ["signal", "gem_score"]
|
|
61
31
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
_normalize(score) {
|
|
65
|
-
return Math.tanh(score / 5.0) * 10;
|
|
32
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
66
33
|
}
|
|
67
34
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// --- STANDARD 1: UPDATED SIGNATURE (added rootData) ---
|
|
72
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
73
|
-
const { logger, calculationUtils } = dependencies;
|
|
74
|
-
|
|
75
|
-
const flowDivergence = fetchedDependencies['skilled-unskilled-divergence'];
|
|
76
|
-
const momentumExposure = fetchedDependencies['cohort-momentum-state'];
|
|
77
|
-
const assetMomentum = fetchedDependencies['instrument-price-momentum-20d'];
|
|
35
|
+
process(context) {
|
|
36
|
+
const { computed, math } = context;
|
|
37
|
+
const { signals } = math;
|
|
78
38
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
39
|
+
const flowDivergence = computed['skilled-unskilled-divergence'];
|
|
40
|
+
const momentumExposure = computed['cohort-momentum-state'];
|
|
41
|
+
const assetMomentum = computed['instrument-price-momentum-20d'];
|
|
42
|
+
|
|
43
|
+
if (!flowDivergence || !momentumExposure || !assetMomentum) return;
|
|
85
44
|
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const momentumDivergence =
|
|
45
|
+
const skilledMom = momentumExposure.skilled?.average_momentum_exposure_pct || 0;
|
|
46
|
+
const unskilledMom = momentumExposure.unskilled?.average_momentum_exposure_pct || 0;
|
|
47
|
+
const momentumDivergence = skilledMom - unskilledMom;
|
|
89
48
|
|
|
90
49
|
const result = {};
|
|
91
50
|
const allTickers = Object.keys(flowDivergence);
|
|
@@ -93,32 +52,13 @@ class QuantSkillAlphaSignal {
|
|
|
93
52
|
for (const ticker of allTickers) {
|
|
94
53
|
const flowData = flowDivergence[ticker];
|
|
95
54
|
const assetMom = assetMomentum[ticker]?.momentum_20d_pct || 0;
|
|
96
|
-
|
|
97
|
-
// 1. Flow Divergence (Skilled buying, Unskilled selling)
|
|
98
|
-
const flowScore = flowData.flow_divergence_score; // e.g., +1.5
|
|
99
|
-
|
|
100
|
-
// 2. Momentum Divergence (Skilled buying high-mom, Unskilled buying low-mom)
|
|
101
|
-
const momScore = momentumDivergence; // e.g., +20.0
|
|
102
|
-
|
|
103
|
-
// 3. Asset Momentum (Is the asset *actually* in an uptrend?)
|
|
104
|
-
const assetScore = assetMom; // e.g., +15.0
|
|
105
|
-
|
|
106
|
-
// --- Signal Logic ---
|
|
107
|
-
// We want all three to be positive.
|
|
108
|
-
// 1. Skilled are buying, Unskilled are selling (flowScore > 0)
|
|
109
|
-
// 2. Skilled are buying *more* momentum than Unskilled (momScore > 0)
|
|
110
|
-
// 3. The asset itself has positive momentum (assetScore > 0)
|
|
55
|
+
const flowScore = flowData.flow_divergence_score;
|
|
111
56
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const divergenceFactor = (flowScore > 0 && momScore > 0) ? (flowScore * momScore) : 0;
|
|
57
|
+
const scaledFlow = flowScore * 10;
|
|
58
|
+
const finalScore = (scaledFlow + momentumDivergence + assetMom) / 3.0;
|
|
115
59
|
|
|
116
|
-
|
|
117
|
-
// We scale flowScore (small) and momScore (large) to be comparable
|
|
118
|
-
const scaledFlow = flowScore * 10; // (e.g., 1.5 -> 15)
|
|
119
|
-
const finalScore = (scaledFlow + momScore + assetScore) / 3.0;
|
|
60
|
+
const gem_score = signals.normalizeTanh(finalScore, 10, 5.0);
|
|
120
61
|
|
|
121
|
-
const gem_score = this._normalize(finalScore);
|
|
122
62
|
let signal = "Neutral";
|
|
123
63
|
if (gem_score > 7.0) signal = "Strong Buy";
|
|
124
64
|
else if (gem_score > 2.5) signal = "Buy";
|
|
@@ -128,26 +68,13 @@ class QuantSkillAlphaSignal {
|
|
|
128
68
|
result[ticker] = {
|
|
129
69
|
signal: signal,
|
|
130
70
|
gem_score: gem_score,
|
|
131
|
-
components: {
|
|
132
|
-
flow_divergence: flowScore,
|
|
133
|
-
momentum_divergence: momScore,
|
|
134
|
-
asset_momentum: assetScore
|
|
135
|
-
}
|
|
71
|
+
components: { flow_divergence: flowScore, momentum_divergence: momentumDivergence, asset_momentum: assetMom }
|
|
136
72
|
};
|
|
137
73
|
}
|
|
138
|
-
|
|
139
|
-
// --- STANDARD 2: SET STATE, DO NOT RETURN ---
|
|
140
74
|
this.result = result;
|
|
141
75
|
}
|
|
142
76
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return this.result;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// --- STANDARD 2: ADDED ---
|
|
149
|
-
reset() {
|
|
150
|
-
this.result = {};
|
|
151
|
-
}
|
|
77
|
+
async getResult() { return this.result; }
|
|
78
|
+
reset() { this.result = {}; }
|
|
152
79
|
}
|
|
153
80
|
module.exports = QuantSkillAlphaSignal;
|