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,29 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GAUSS Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
* This 'standard' calculation re-streams all users, filters for
|
|
5
|
-
* the "Smart" and "Dumb" cohorts (from Pass 1), and then calculates
|
|
6
|
-
* their full 4D "Trader DNA".
|
|
7
|
-
*
|
|
8
|
-
* It then buckets these users into our final, named sub-cohorts
|
|
9
|
-
* (e.g., "Smart Investors", "FOMO Chasers").
|
|
3
|
+
* REFACTORED: Uses context.computed and context.math.
|
|
10
4
|
*/
|
|
11
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
12
|
-
|
|
13
|
-
|
|
14
5
|
class CohortDefiner {
|
|
15
6
|
constructor() {
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
this.momentumData = null;
|
|
21
|
-
// --- STANDARD 0: RENAMED ---
|
|
22
|
-
this.tickerMap = null;
|
|
23
|
-
this.cohortIdSets = null; // { smart: Set, dumb: Set }
|
|
7
|
+
this.smartVectors = [];
|
|
8
|
+
this.dumbVectors = [];
|
|
9
|
+
this.cohortIdSets = null;
|
|
24
10
|
}
|
|
25
11
|
|
|
26
|
-
/** Statically defines metadata */
|
|
27
12
|
static getMetadata() {
|
|
28
13
|
return {
|
|
29
14
|
type: 'standard',
|
|
@@ -34,197 +19,129 @@ class CohortDefiner {
|
|
|
34
19
|
};
|
|
35
20
|
}
|
|
36
21
|
|
|
37
|
-
/** Statically declare dependencies */
|
|
38
22
|
static getDependencies() {
|
|
39
|
-
return [
|
|
40
|
-
'daily-dna-filter', // from gauss (Pass 1)
|
|
41
|
-
'instrument-price-momentum-20d' // from core (Pass 1)
|
|
42
|
-
];
|
|
23
|
+
return ['daily-dna-filter', 'instrument-price-momentum-20d'];
|
|
43
24
|
}
|
|
44
25
|
|
|
45
|
-
/**
|
|
46
|
-
* Defines the output schema for this calculation.
|
|
47
|
-
*/
|
|
48
26
|
static getSchema() {
|
|
49
|
-
const cohortSchema = {
|
|
50
|
-
"type": "array",
|
|
51
|
-
"description": "A list of User IDs belonging to this cohort.",
|
|
52
|
-
"items": { "type": "string" }
|
|
53
|
-
};
|
|
54
|
-
|
|
27
|
+
const cohortSchema = { "type": "array", "items": { "type": "string" } };
|
|
55
28
|
return {
|
|
56
29
|
"type": "object",
|
|
57
|
-
"
|
|
58
|
-
"properties": {
|
|
59
|
-
"smart_investors": cohortSchema,
|
|
60
|
-
"smart_scalpers": cohortSchema,
|
|
61
|
-
"fomo_chasers": cohortSchema,
|
|
62
|
-
"patient_losers": cohortSchema,
|
|
63
|
-
"fomo_bagholders": cohortSchema,
|
|
64
|
-
"uncategorized_smart": cohortSchema,
|
|
65
|
-
"uncategorized_dumb": cohortSchema
|
|
66
|
-
},
|
|
67
|
-
"additionalProperties": cohortSchema
|
|
30
|
+
"patternProperties": { "^.*$": cohortSchema }
|
|
68
31
|
};
|
|
69
32
|
}
|
|
70
33
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
_loadDependencies(fetchedDependencies) {
|
|
75
|
-
if (this.cohortIdSets) return; // Run once
|
|
76
|
-
|
|
77
|
-
const dnaFilterData = fetchedDependencies['daily-dna-filter'];
|
|
78
|
-
this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
|
|
34
|
+
_loadDependencies(computed) {
|
|
35
|
+
if (this.cohortIdSets) return;
|
|
36
|
+
const dnaFilterData = computed['daily-dna-filter'];
|
|
79
37
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
} else {
|
|
86
|
-
this.cohortIdSets = {
|
|
87
|
-
smart: new Set(),
|
|
88
|
-
dumb: new Set()
|
|
89
|
-
};
|
|
90
|
-
}
|
|
38
|
+
this.cohortIdSets = {
|
|
39
|
+
smart: new Set(dnaFilterData?.smart_cohort_ids || []),
|
|
40
|
+
dumb: new Set(dnaFilterData?.dumb_cohort_ids || [])
|
|
41
|
+
};
|
|
91
42
|
}
|
|
92
43
|
|
|
93
|
-
_getFomoScore(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
44
|
+
_getFomoScore(extract, mappings, today, yesterday, momentumData, userType) {
|
|
45
|
+
if (!momentumData) return 0;
|
|
46
|
+
|
|
47
|
+
const tPos = extract.getPositions(today, userType);
|
|
48
|
+
const yPos = extract.getPositions(yesterday, userType);
|
|
49
|
+
|
|
50
|
+
const yIds = new Set(yPos.map(p => extract.getInstrumentId(p)));
|
|
51
|
+
const newPositions = tPos.filter(p => !yIds.has(extract.getInstrumentId(p)));
|
|
100
52
|
|
|
101
|
-
const yIds = new Set((yesterdayPortfolio?.AggregatedPositions || []).map(p => p.InstrumentID));
|
|
102
|
-
const newPositions = (todayPortfolio?.AggregatedPositions || []).filter(p => p.InstrumentID && !yIds.has(p.InstrumentID));
|
|
103
53
|
if (newPositions.length === 0) return 0;
|
|
104
54
|
|
|
105
|
-
let fomoSum = 0;
|
|
106
|
-
let count = 0;
|
|
55
|
+
let fomoSum = 0, count = 0;
|
|
107
56
|
for (const pos of newPositions) {
|
|
108
|
-
|
|
109
|
-
const ticker =
|
|
110
|
-
if (ticker &&
|
|
111
|
-
fomoSum +=
|
|
57
|
+
const instId = extract.getInstrumentId(pos);
|
|
58
|
+
const ticker = mappings.instrumentToTicker[instId];
|
|
59
|
+
if (ticker && momentumData[ticker]) {
|
|
60
|
+
fomoSum += momentumData[ticker].momentum_20d_pct || 0;
|
|
112
61
|
count++;
|
|
113
62
|
}
|
|
114
63
|
}
|
|
115
|
-
return
|
|
64
|
+
return count > 0 ? fomoSum / count : 0;
|
|
116
65
|
}
|
|
117
66
|
|
|
118
|
-
_getBagholderScore(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (openPositions.length === 0) return 0;
|
|
67
|
+
_getBagholderScore(extract, today, userType) {
|
|
68
|
+
const positions = extract.getPositions(today, userType);
|
|
69
|
+
if (positions.length === 0) return 0;
|
|
122
70
|
|
|
123
|
-
let durationSum = 0;
|
|
124
|
-
let count = 0;
|
|
71
|
+
let durationSum = 0, count = 0;
|
|
125
72
|
const now = new Date();
|
|
126
73
|
|
|
127
|
-
for (const pos of
|
|
128
|
-
if (pos
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (isFinite(durationDays)) {
|
|
74
|
+
for (const pos of positions) {
|
|
75
|
+
if (extract.getNetProfit(pos) < -20) {
|
|
76
|
+
const openDate = extract.getOpenDateTime(pos);
|
|
77
|
+
if (openDate) {
|
|
78
|
+
try {
|
|
79
|
+
const durationDays = (now - openDate) / (86400000);
|
|
134
80
|
durationSum += durationDays;
|
|
135
81
|
count++;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
82
|
+
} catch (e) {}
|
|
83
|
+
}
|
|
138
84
|
}
|
|
139
85
|
}
|
|
140
|
-
return
|
|
86
|
+
return count > 0 ? durationSum / count : 0;
|
|
141
87
|
}
|
|
142
88
|
|
|
143
|
-
process(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// --- STANDARD 0: FIXED ---
|
|
147
|
-
if (!this.tickerMap) {
|
|
148
|
-
this.tickerMap = context.instrumentToTicker;
|
|
149
|
-
}
|
|
89
|
+
process(context) {
|
|
90
|
+
const { user, computed, mappings, math } = context;
|
|
91
|
+
const { extract, history } = math;
|
|
150
92
|
|
|
151
|
-
|
|
152
|
-
const isDumb = this.cohortIdSets.dumb.has(userId);
|
|
93
|
+
this._loadDependencies(computed);
|
|
153
94
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
95
|
+
const isSmart = this.cohortIdSets.smart.has(user.id);
|
|
96
|
+
const isDumb = this.cohortIdSets.dumb.has(user.id);
|
|
97
|
+
|
|
98
|
+
if (!isSmart && !isDumb) return;
|
|
157
99
|
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
|
|
100
|
+
// 1. Strict History Access
|
|
101
|
+
const historyDoc = history.getDailyHistory(user);
|
|
102
|
+
const summary = history.getSummary(historyDoc);
|
|
161
103
|
|
|
162
|
-
|
|
104
|
+
if (!summary) return;
|
|
105
|
+
|
|
106
|
+
// 2. Valid usage of Summary DTO properties
|
|
107
|
+
const winRate = summary.winRatio / 100.0;
|
|
163
108
|
const lossRate = 1.0 - winRate;
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
|
|
167
|
-
const lt_time = history.avgHoldingTimeInMinutes || 0;
|
|
109
|
+
const lt_skill = (winRate * summary.avgProfitPct) - (lossRate * Math.abs(summary.avgLossPct));
|
|
110
|
+
const lt_time = summary.avgHoldingTimeInMinutes;
|
|
168
111
|
|
|
169
|
-
const
|
|
170
|
-
const
|
|
112
|
+
const momentumData = computed['instrument-price-momentum-20d'];
|
|
113
|
+
const st_fomo = this._getFomoScore(extract, mappings, user.portfolio.today, user.portfolio.yesterday, momentumData, user.type);
|
|
114
|
+
const st_bagholder = this._getBagholderScore(extract, user.portfolio.today, user.type);
|
|
171
115
|
|
|
172
|
-
const vector = { userId, skill: lt_skill, time: lt_time, fomo: st_fomo, bagholder: st_bagholder };
|
|
116
|
+
const vector = { userId: user.id, skill: lt_skill, time: lt_time, fomo: st_fomo, bagholder: st_bagholder };
|
|
173
117
|
|
|
174
|
-
if (isSmart)
|
|
175
|
-
|
|
176
|
-
} else if (isDumb) {
|
|
177
|
-
this.dumbVectors.push(vector);
|
|
178
|
-
}
|
|
118
|
+
if (isSmart) this.smartVectors.push(vector);
|
|
119
|
+
else this.dumbVectors.push(vector);
|
|
179
120
|
}
|
|
180
121
|
|
|
181
122
|
_getMedian(vectors, key) {
|
|
182
123
|
if (vectors.length === 0) return 0;
|
|
183
124
|
const sorted = vectors.map(v => v[key]).sort((a, b) => a - b);
|
|
184
125
|
const mid = Math.floor(sorted.length / 2);
|
|
185
|
-
|
|
186
|
-
return (sorted[mid - 1] + sorted[mid]) / 2;
|
|
187
|
-
}
|
|
188
|
-
return sorted[mid];
|
|
126
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
189
127
|
}
|
|
190
128
|
|
|
191
|
-
getResult() {
|
|
129
|
+
async getResult() {
|
|
192
130
|
const cohorts = {};
|
|
193
|
-
const assignedSmart = new Set();
|
|
194
|
-
const assignedDumb = new Set();
|
|
131
|
+
const assignedSmart = new Set(), assignedDumb = new Set();
|
|
195
132
|
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
cohorts['
|
|
199
|
-
|
|
200
|
-
.map(u => { assignedSmart.add(u.userId); return u.userId; });
|
|
201
|
-
|
|
202
|
-
cohorts['smart_scalpers'] = this.smartVectors
|
|
203
|
-
.filter(u => u.time < smart_median_time)
|
|
204
|
-
.map(u => { assignedSmart.add(u.userId); return u.userId; });
|
|
205
|
-
|
|
206
|
-
cohorts['uncategorized_smart'] = this.smartVectors
|
|
207
|
-
.filter(u => !assignedSmart.has(u.userId))
|
|
208
|
-
.map(u => u.userId);
|
|
133
|
+
const smart_time = this._getMedian(this.smartVectors, 'time');
|
|
134
|
+
cohorts['smart_investors'] = this.smartVectors.filter(u => u.time >= smart_time).map(u => { assignedSmart.add(u.userId); return u.userId; });
|
|
135
|
+
cohorts['smart_scalpers'] = this.smartVectors.filter(u => u.time < smart_time).map(u => { assignedSmart.add(u.userId); return u.userId; });
|
|
136
|
+
cohorts['uncategorized_smart'] = this.smartVectors.filter(u => !assignedSmart.has(u.userId)).map(u => u.userId);
|
|
209
137
|
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
cohorts['fomo_chasers'] = this.dumbVectors
|
|
214
|
-
.filter(u => u.fomo >= dumb_median_fomo && u.bagholder < dumb_median_bag)
|
|
215
|
-
.map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
216
|
-
|
|
217
|
-
cohorts['patient_losers'] = this.dumbVectors
|
|
218
|
-
.filter(u => u.fomo < dumb_median_fomo && u.bagholder >= dumb_median_bag)
|
|
219
|
-
.map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
220
|
-
|
|
221
|
-
cohorts['fomo_bagholders'] = this.dumbVectors
|
|
222
|
-
.filter(u => u.fomo >= dumb_median_fomo && u.bagholder >= dumb_median_bag)
|
|
223
|
-
.map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
138
|
+
const dumb_fomo = this._getMedian(this.dumbVectors, 'fomo');
|
|
139
|
+
const dumb_bag = this._getMedian(this.dumbVectors, 'bagholder');
|
|
224
140
|
|
|
225
|
-
cohorts['
|
|
226
|
-
|
|
227
|
-
|
|
141
|
+
cohorts['fomo_chasers'] = this.dumbVectors.filter(u => u.fomo >= dumb_fomo && u.bagholder < dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
142
|
+
cohorts['patient_losers'] = this.dumbVectors.filter(u => u.fomo < dumb_fomo && u.bagholder >= dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
143
|
+
cohorts['fomo_bagholders'] = this.dumbVectors.filter(u => u.fomo >= dumb_fomo && u.bagholder >= dumb_bag).map(u => { assignedDumb.add(u.userId); return u.userId; });
|
|
144
|
+
cohorts['uncategorized_dumb'] = this.dumbVectors.filter(u => !assignedDumb.has(u.userId)).map(u => u.userId);
|
|
228
145
|
|
|
229
146
|
return cohorts;
|
|
230
147
|
}
|
|
@@ -232,9 +149,6 @@ class CohortDefiner {
|
|
|
232
149
|
reset() {
|
|
233
150
|
this.smartVectors = [];
|
|
234
151
|
this.dumbVectors = [];
|
|
235
|
-
this.momentumData = null;
|
|
236
|
-
// --- STANDARD 0: RENAMED ---
|
|
237
|
-
this.tickerMap = null;
|
|
238
152
|
this.cohortIdSets = null;
|
|
239
153
|
}
|
|
240
154
|
}
|
|
@@ -1,61 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GAUSS Product Line (Pass 1)
|
|
3
|
-
*
|
|
4
|
-
* This metric is a *filter*. It processes all users and calculates
|
|
5
|
-
* their "Long-Term Skill" (Expectancy) from their 'history' doc.
|
|
6
|
-
*
|
|
7
|
-
* It then filters this list to *only* return the User IDs
|
|
8
|
-
* of the top 20% (Smart) and bottom 20% (Dumb) of users,
|
|
9
|
-
* solving the 1MB document limit for downstream processing.
|
|
3
|
+
* REFACTORED: Uses context.math.history.getSummary().
|
|
10
4
|
*/
|
|
11
5
|
class DailyDnaFilter {
|
|
12
6
|
constructor() {
|
|
13
|
-
// We will store all users and their primary skill score
|
|
14
|
-
// [ [userId, skillScore], ... ]
|
|
15
7
|
this.allUserSkills = [];
|
|
16
8
|
}
|
|
17
9
|
|
|
18
|
-
/**
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
21
10
|
static getMetadata() {
|
|
22
11
|
return {
|
|
23
12
|
type: 'standard',
|
|
24
|
-
// This calc only needs the 'history' doc.
|
|
25
|
-
// As per the test harness, this means 'todayPortfolio'
|
|
26
|
-
// in process() *is* the history object.
|
|
27
13
|
rootDataDependencies: ['history'],
|
|
28
|
-
isHistorical: false,
|
|
14
|
+
isHistorical: false,
|
|
29
15
|
userType: 'all',
|
|
30
16
|
category: 'gauss'
|
|
31
17
|
};
|
|
32
18
|
}
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
* Statically declare dependencies.
|
|
36
|
-
*/
|
|
37
|
-
static getDependencies() {
|
|
38
|
-
return []; // This is a Pass 1 calculation
|
|
39
|
-
}
|
|
20
|
+
static getDependencies() { return []; }
|
|
40
21
|
|
|
41
|
-
/**
|
|
42
|
-
* Defines the output schema for this calculation.
|
|
43
|
-
*/
|
|
44
22
|
static getSchema() {
|
|
45
23
|
return {
|
|
46
24
|
"type": "object",
|
|
47
|
-
"description": "Provides the User ID lists for the 'Smart' (top 20%) and 'Dumb' (bottom 20%) cohorts based on long-term Expectancy.",
|
|
48
25
|
"properties": {
|
|
49
|
-
"smart_cohort_ids": {
|
|
50
|
-
|
|
51
|
-
"description": "List of user IDs in the top 20% 'Smart' cohort.",
|
|
52
|
-
"items": { "type": "string" }
|
|
53
|
-
},
|
|
54
|
-
"dumb_cohort_ids": {
|
|
55
|
-
"type": "array",
|
|
56
|
-
"description": "List of user IDs in the bottom 20% 'Dumb' cohort.",
|
|
57
|
-
"items": { "type": "string" }
|
|
58
|
-
},
|
|
26
|
+
"smart_cohort_ids": { "type": "array", "items": { "type": "string" } },
|
|
27
|
+
"dumb_cohort_ids": { "type": "array", "items": { "type": "string" } },
|
|
59
28
|
"total_users_analyzed": { "type": "number" },
|
|
60
29
|
"cohort_size": { "type": "number" }
|
|
61
30
|
},
|
|
@@ -63,74 +32,51 @@ class DailyDnaFilter {
|
|
|
63
32
|
};
|
|
64
33
|
}
|
|
65
34
|
|
|
35
|
+
process(context) {
|
|
36
|
+
const { user, math } = context;
|
|
37
|
+
const { history } = math; // 'history' is the TOOL
|
|
66
38
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* @param {object} todayPortfolio - Today's portfolio snapshot.
|
|
70
|
-
* @param {object} yesterdayPortfolio - Yesterday's portfolio snapshot.
|
|
71
|
-
* @param {string} userId - The user's ID.
|
|
72
|
-
*/
|
|
73
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
39
|
+
// 1. Get strict daily history data
|
|
40
|
+
const historyDoc = history.getDailyHistory(user);
|
|
74
41
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
const history = todayPortfolio?.all;
|
|
81
|
-
if (!history || !history.totalTrades || history.totalTrades < 20) {
|
|
82
|
-
return; // Not enough history for a reliable score
|
|
83
|
-
}
|
|
42
|
+
// 2. Get strict summary object using the tool
|
|
43
|
+
const summary = history.getSummary(historyDoc);
|
|
44
|
+
|
|
45
|
+
// Validation
|
|
46
|
+
if (!summary || summary.totalTrades < 20) return;
|
|
84
47
|
|
|
85
|
-
|
|
48
|
+
// 3. Use properties from the SUMMARY object, not the history tool
|
|
49
|
+
const winRate = summary.winRatio / 100.0;
|
|
86
50
|
const lossRate = 1.0 - winRate;
|
|
87
|
-
const avgWin = history.avgProfitPct || 0;
|
|
88
|
-
// Use Math.abs because avgLossPct is stored as a negative number
|
|
89
|
-
const avgLoss = Math.abs(history.avgLossPct || 0);
|
|
90
51
|
|
|
91
|
-
//
|
|
52
|
+
// CORRECTED LINE: uses summary.avgProfitPct
|
|
53
|
+
const avgWin = summary.avgProfitPct;
|
|
54
|
+
const avgLoss = Math.abs(summary.avgLossPct);
|
|
55
|
+
|
|
92
56
|
const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
|
|
93
57
|
|
|
94
|
-
if (
|
|
95
|
-
|
|
58
|
+
if (isFinite(lt_skill)) {
|
|
59
|
+
this.allUserSkills.push([user.id, lt_skill]);
|
|
96
60
|
}
|
|
97
|
-
|
|
98
|
-
this.allUserSkills.push([userId, lt_skill]);
|
|
99
61
|
}
|
|
100
62
|
|
|
101
|
-
/**
|
|
102
|
-
* Returns the final filtered list of user IDs.
|
|
103
|
-
*/
|
|
104
63
|
getResult() {
|
|
105
64
|
const totalUsers = this.allUserSkills.length;
|
|
106
|
-
if (totalUsers === 0) {
|
|
107
|
-
return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
|
|
108
|
-
}
|
|
65
|
+
if (totalUsers === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
|
|
109
66
|
|
|
110
|
-
// Sort by Skill (Expectancy) in descending order (highest skill first)
|
|
111
67
|
this.allUserSkills.sort((a, b) => b[1] - a[1]);
|
|
112
68
|
|
|
113
|
-
const cohortSize = Math.floor(totalUsers * 0.20);
|
|
114
|
-
if (cohortSize === 0) {
|
|
115
|
-
return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Get the top 20% (smartest)
|
|
119
|
-
const smart_cohort_ids = this.allUserSkills.slice(0, cohortSize).map(u => u[0]);
|
|
120
|
-
// Get the bottom 20% (dumbest)
|
|
121
|
-
const dumb_cohort_ids = this.allUserSkills.slice(-cohortSize).map(u => u[0]);
|
|
69
|
+
const cohortSize = Math.floor(totalUsers * 0.20);
|
|
70
|
+
if (cohortSize === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
|
|
122
71
|
|
|
123
|
-
// Output is now small and compact, solving the 1MB limit.
|
|
124
72
|
return {
|
|
125
|
-
smart_cohort_ids:
|
|
126
|
-
dumb_cohort_ids:
|
|
73
|
+
smart_cohort_ids: this.allUserSkills.slice(0, cohortSize).map(u => u[0]),
|
|
74
|
+
dumb_cohort_ids: this.allUserSkills.slice(-cohortSize).map(u => u[0]),
|
|
127
75
|
total_users_analyzed: totalUsers,
|
|
128
76
|
cohort_size: cohortSize
|
|
129
77
|
};
|
|
130
78
|
}
|
|
131
79
|
|
|
132
|
-
reset() {
|
|
133
|
-
this.allUserSkills = [];
|
|
134
|
-
}
|
|
80
|
+
reset() { this.allUserSkills = []; }
|
|
135
81
|
}
|
|
136
82
|
module.exports = DailyDnaFilter;
|
|
@@ -1,66 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GAUSS Product Line (Pass 4)
|
|
3
|
-
*
|
|
4
|
-
* This is the final, stateless signal generator for the Gauss line.
|
|
5
|
-
* It answers: "What is the net 'Smart vs. Dumb' flow divergence?"
|
|
6
|
-
*
|
|
7
|
-
* It consumes the daily flows from all defined cohorts (Pass 3)
|
|
8
|
-
* and aggregates them into a final, actionable signal.
|
|
9
|
-
*
|
|
10
|
-
* --- FIX ---
|
|
11
|
-
* - Aligned data access with the corrected schema of its
|
|
12
|
-
* dependency, 'cohort-capital-flow'.
|
|
13
|
-
* - It now reads 'cohortFlows[cohortName]' directly, instead of
|
|
14
|
-
* the non-existent 'cohortFlows[cohortName].assets'.
|
|
3
|
+
* REFACTORED: Uses context.math.signals and process(context).
|
|
15
4
|
*/
|
|
16
5
|
class GaussDivergenceSignal {
|
|
6
|
+
constructor() { this.result = {}; }
|
|
17
7
|
|
|
18
|
-
// --- STANDARD 2: ADDED ---
|
|
19
|
-
constructor() {
|
|
20
|
-
this.result = {};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Defines the output schema for this calculation.
|
|
25
|
-
* @returns {object} JSON Schema object
|
|
26
|
-
*/
|
|
27
8
|
static getSchema() {
|
|
28
9
|
const tickerSchema = {
|
|
29
10
|
"type": "object",
|
|
30
11
|
"properties": {
|
|
31
|
-
"signal": {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
"gauss_score": {
|
|
36
|
-
"type": "number",
|
|
37
|
-
"description": "Final divergence score (-10 to +10). Positive = Smart Buying, Negative = Smart Selling."
|
|
38
|
-
},
|
|
39
|
-
"smart_flow_total_pct": {
|
|
40
|
-
"type": "number",
|
|
41
|
-
"description": "The total %-point flow from all 'Smart' cohorts."
|
|
42
|
-
},
|
|
43
|
-
"dumb_flow_total_pct": {
|
|
44
|
-
"type": "number",
|
|
45
|
-
"description": "The total %-point flow from all 'Dumb' cohorts."
|
|
46
|
-
}
|
|
12
|
+
"signal": { "type": "string" },
|
|
13
|
+
"gauss_score": { "type": "number" },
|
|
14
|
+
"smart_flow_total_pct": { "type": "number" },
|
|
15
|
+
"dumb_flow_total_pct": { "type": "number" }
|
|
47
16
|
},
|
|
48
|
-
"required": ["signal", "gauss_score"
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
"type": "object",
|
|
53
|
-
"description": "Generates a final divergence signal by aggregating flow from Smart vs. Dumb cohorts.",
|
|
54
|
-
"patternProperties": {
|
|
55
|
-
"^.*$": tickerSchema // Ticker
|
|
56
|
-
},
|
|
57
|
-
"additionalProperties": tickerSchema
|
|
17
|
+
"required": ["signal", "gauss_score"]
|
|
58
18
|
};
|
|
19
|
+
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
59
20
|
}
|
|
60
21
|
|
|
61
|
-
/**
|
|
62
|
-
* Statically defines all metadata for the manifest builder.
|
|
63
|
-
*/
|
|
64
22
|
static getMetadata() {
|
|
65
23
|
return {
|
|
66
24
|
type: 'meta',
|
|
@@ -71,79 +29,41 @@ class GaussDivergenceSignal {
|
|
|
71
29
|
};
|
|
72
30
|
}
|
|
73
31
|
|
|
74
|
-
|
|
75
|
-
* Statically declare dependencies.
|
|
76
|
-
*/
|
|
77
|
-
static getDependencies() {
|
|
78
|
-
return [
|
|
79
|
-
'cohort-capital-flow' // from gauss (Pass 3)
|
|
80
|
-
];
|
|
81
|
-
}
|
|
32
|
+
static getDependencies() { return ['cohort-capital-flow']; }
|
|
82
33
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* 0 -> 0, 1 -> 7.6, 2 -> 9.6, 3 -> 9.95
|
|
87
|
-
* We scale the input to tune sensitivity.
|
|
88
|
-
*/
|
|
89
|
-
_normalize(score) {
|
|
90
|
-
// A score of 2.0 (%-point flow) will be ~9.6
|
|
91
|
-
// A score of 1.0 (%-point flow) will be ~7.6
|
|
92
|
-
return Math.tanh(score / 2.0) * 10;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* This is a 'meta' calculation. It runs once.
|
|
97
|
-
*/
|
|
98
|
-
// --- THIS IS THE FIX ---
|
|
99
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
100
|
-
const { logger } = dependencies;
|
|
34
|
+
process(context) {
|
|
35
|
+
const { computed, math } = context;
|
|
36
|
+
const { signals } = math;
|
|
101
37
|
|
|
102
|
-
const cohortFlows =
|
|
103
|
-
|
|
104
|
-
if (!cohortFlows) {
|
|
105
|
-
logger.log('WARN', `[gauss/gauss-divergence-signal] Missing dependency 'cohort-capital-flow' for ${dateStr}.`);
|
|
106
|
-
this.result = {};
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
38
|
+
const cohortFlows = computed['cohort-capital-flow'];
|
|
39
|
+
if (!cohortFlows) return;
|
|
109
40
|
|
|
110
41
|
const SMART_COHORTS = ['smart_investors', 'smart_scalpers'];
|
|
111
42
|
const DUMB_COHORTS = ['fomo_chasers', 'patient_losers', 'fomo_bagholders'];
|
|
112
43
|
|
|
113
|
-
const blendedFlows = new Map();
|
|
44
|
+
const blendedFlows = new Map();
|
|
114
45
|
|
|
115
|
-
// 1. Blend all cohort flows
|
|
116
46
|
for (const cohortName in cohortFlows) {
|
|
117
47
|
const isSmart = SMART_COHORTS.includes(cohortName);
|
|
118
48
|
const isDumb = DUMB_COHORTS.includes(cohortName);
|
|
119
49
|
if (!isSmart && !isDumb) continue;
|
|
120
50
|
|
|
121
|
-
|
|
122
|
-
const assets = cohortFlows[cohortName]; // This is now the ticker map
|
|
51
|
+
const assets = cohortFlows[cohortName];
|
|
123
52
|
if (!assets) continue;
|
|
124
53
|
|
|
125
54
|
for (const [ticker, data] of Object.entries(assets)) {
|
|
126
|
-
if (!blendedFlows.has(ticker)) {
|
|
127
|
-
blendedFlows.set(ticker, { smart: 0, dumb: 0 });
|
|
128
|
-
}
|
|
55
|
+
if (!blendedFlows.has(ticker)) blendedFlows.set(ticker, { smart: 0, dumb: 0 });
|
|
129
56
|
|
|
130
|
-
// This logic remains correct, as it reads the inner flow object
|
|
131
57
|
const flow = data.net_flow_contribution || 0;
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
blendedFlows.get(ticker).smart += flow;
|
|
135
|
-
} else if (isDumb) {
|
|
136
|
-
blendedFlows.get(ticker).dumb += flow;
|
|
137
|
-
}
|
|
58
|
+
if (isSmart) blendedFlows.get(ticker).smart += flow;
|
|
59
|
+
else if (isDumb) blendedFlows.get(ticker).dumb += flow;
|
|
138
60
|
}
|
|
139
61
|
}
|
|
140
|
-
// --- END FIX ---
|
|
141
62
|
|
|
142
|
-
// 2. Calculate final signal (logic unchanged)
|
|
143
63
|
const result = {};
|
|
144
64
|
for (const [ticker, data] of blendedFlows.entries()) {
|
|
145
65
|
const divergence = data.smart - data.dumb;
|
|
146
|
-
const gauss_score =
|
|
66
|
+
const gauss_score = signals.normalizeTanh(divergence, 10, 2.0);
|
|
147
67
|
|
|
148
68
|
let signal = "Neutral";
|
|
149
69
|
if (gauss_score > 7.0) signal = "Strong Buy";
|
|
@@ -158,17 +78,10 @@ class GaussDivergenceSignal {
|
|
|
158
78
|
dumb_flow_total_pct: data.dumb
|
|
159
79
|
};
|
|
160
80
|
}
|
|
161
|
-
|
|
162
81
|
this.result = result;
|
|
163
82
|
}
|
|
164
83
|
|
|
165
|
-
async getResult(
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
reset() {
|
|
170
|
-
this.result = {};
|
|
171
|
-
}
|
|
84
|
+
async getResult() { return this.result; }
|
|
85
|
+
reset() { this.result = {}; }
|
|
172
86
|
}
|
|
173
|
-
|
|
174
87
|
module.exports = GaussDivergenceSignal;
|