bulltrackers-module 1.0.732 → 1.0.733
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/functions/orchestrator/index.js +19 -17
- package/index.js +8 -29
- package/package.json +1 -1
- package/functions/computation-system/WorkflowOrchestrator.js +0 -213
- package/functions/computation-system/config/monitoring_config.js +0 -31
- package/functions/computation-system/config/validation_overrides.js +0 -10
- package/functions/computation-system/context/ContextFactory.js +0 -143
- package/functions/computation-system/context/ManifestBuilder.js +0 -379
- package/functions/computation-system/data/AvailabilityChecker.js +0 -236
- package/functions/computation-system/data/CachedDataLoader.js +0 -325
- package/functions/computation-system/data/DependencyFetcher.js +0 -455
- package/functions/computation-system/executors/MetaExecutor.js +0 -279
- package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
- package/functions/computation-system/executors/StandardExecutor.js +0 -465
- package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
- package/functions/computation-system/helpers/computation_worker.js +0 -375
- package/functions/computation-system/helpers/monitor.js +0 -64
- package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
- package/functions/computation-system/layers/extractors.js +0 -1097
- package/functions/computation-system/layers/index.js +0 -40
- package/functions/computation-system/layers/mathematics.js +0 -522
- package/functions/computation-system/layers/profiling.js +0 -537
- package/functions/computation-system/layers/validators.js +0 -170
- package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
- package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
- package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
- package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
- package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
- package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
- package/functions/computation-system/logger/logger.js +0 -297
- package/functions/computation-system/persistence/ContractValidator.js +0 -81
- package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
- package/functions/computation-system/persistence/ResultCommitter.js +0 -283
- package/functions/computation-system/persistence/ResultsValidator.js +0 -130
- package/functions/computation-system/persistence/RunRecorder.js +0 -142
- package/functions/computation-system/persistence/StatusRepository.js +0 -52
- package/functions/computation-system/reporter_epoch.js +0 -6
- package/functions/computation-system/scripts/UpdateContracts.js +0 -128
- package/functions/computation-system/services/SnapshotService.js +0 -148
- package/functions/computation-system/simulation/Fabricator.js +0 -285
- package/functions/computation-system/simulation/SeededRandom.js +0 -41
- package/functions/computation-system/simulation/SimRunner.js +0 -51
- package/functions/computation-system/system_epoch.js +0 -2
- package/functions/computation-system/tools/BuildReporter.js +0 -531
- package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
- package/functions/computation-system/tools/DeploymentValidator.js +0 -536
- package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
- package/functions/computation-system/topology/HashManager.js +0 -55
- package/functions/computation-system/topology/ManifestLoader.js +0 -47
- package/functions/computation-system/utils/data_loader.js +0 -675
- package/functions/computation-system/utils/schema_capture.js +0 -121
- package/functions/computation-system/utils/utils.js +0 -188
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Profiling Layer - Intelligence Engine (V6)
|
|
3
|
-
* Encapsulates advanced behavioral profiling, psychological scoring, and classification schemas.
|
|
4
|
-
* UPDATED: Added AnomalyDetector, SimilarityEngine, and ContentAnalysis.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const SCHEMAS = {
|
|
8
|
-
USER_TYPES: { NORMAL: 'normal', SPECULATOR: 'speculator', SIGNED_IN: 'SIGNED_IN_USER', POPULAR: 'POPULAR_INVESTOR' },
|
|
9
|
-
STYLES: {
|
|
10
|
-
INVESTOR: 'Investor',
|
|
11
|
-
SWING_TRADER: 'Swing Trader',
|
|
12
|
-
DAY_TRADER: 'Day Trader',
|
|
13
|
-
SCALPER: 'Scalper'
|
|
14
|
-
},
|
|
15
|
-
LABELS: {
|
|
16
|
-
ELITE: 'Elite',
|
|
17
|
-
SMART: 'Smart Money',
|
|
18
|
-
NEUTRAL: 'Neutral',
|
|
19
|
-
DUMB: 'Dumb Money',
|
|
20
|
-
GAMBLER: 'Gambler'
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
class AnomalyDetector {
|
|
25
|
-
/**
|
|
26
|
-
* Detects unusual behavior by comparing current portfolio state to historical averages (30D).
|
|
27
|
-
* @param {Array} historyTrades - Full trade history
|
|
28
|
-
* @param {Array} currentPositions - Current portfolio positions
|
|
29
|
-
* @param {Object} mappings - Sector mappings
|
|
30
|
-
* @returns {Object} { anomalies: [], warnings: [] }
|
|
31
|
-
*/
|
|
32
|
-
static detect(historyTrades, currentPositions, mappings) {
|
|
33
|
-
const anomalies = [];
|
|
34
|
-
const warnings = [];
|
|
35
|
-
|
|
36
|
-
if (!historyTrades || historyTrades.length < 10) return { anomalies, warnings };
|
|
37
|
-
|
|
38
|
-
// 1. Calculate Historical Baselines (last 30 days vs older)
|
|
39
|
-
const now = new Date();
|
|
40
|
-
const thirtyDaysAgo = new Date(now.getTime() - (30 * 86400000));
|
|
41
|
-
|
|
42
|
-
const recentTrades = historyTrades.filter(t => new Date(t.OpenDateTime) > thirtyDaysAgo);
|
|
43
|
-
const allLeverage = historyTrades.map(t => t.Leverage || 1);
|
|
44
|
-
const avgLeverage = allLeverage.reduce((a,b) => a+b, 0) / allLeverage.length;
|
|
45
|
-
|
|
46
|
-
// 2. Check for Leverage Anomalies in Current Positions
|
|
47
|
-
for (const pos of currentPositions) {
|
|
48
|
-
const currentLev = pos.Leverage || 1;
|
|
49
|
-
if (currentLev > avgLeverage * 1.5 && currentLev > 2) {
|
|
50
|
-
warnings.push(`High Leverage Alert: Position ${pos.InstrumentID} has ${currentLev}x leverage (Avg: ${avgLeverage.toFixed(1)}x)`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 3. Check for New Sector Exposure
|
|
55
|
-
const historicalSectors = new Set();
|
|
56
|
-
historyTrades.forEach(t => {
|
|
57
|
-
const sector = mappings.instrumentToSector[t.InstrumentID];
|
|
58
|
-
if (sector) historicalSectors.add(sector);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
for (const pos of currentPositions) {
|
|
62
|
-
const sector = mappings.instrumentToSector[pos.InstrumentID];
|
|
63
|
-
if (sector && !historicalSectors.has(sector)) {
|
|
64
|
-
anomalies.push(`New Sector Entry: User entered ${sector} sector for the first time.`);
|
|
65
|
-
historicalSectors.add(sector); // suppress duplicate alerts
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { anomalies, warnings };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
class SimilarityEngine {
|
|
74
|
-
/**
|
|
75
|
-
* Finds similar investors based on portfolio composition and risk profile.
|
|
76
|
-
* @param {Object} targetUserContext - The user to match
|
|
77
|
-
* @param {Array<Object>} candidateContexts - List of other users to compare against
|
|
78
|
-
* @param {number} limit - Max results
|
|
79
|
-
*/
|
|
80
|
-
static findPeers(targetUserContext, candidateContexts, limit = 5) {
|
|
81
|
-
const targetVector = this._buildFeatureVector(targetUserContext);
|
|
82
|
-
const scores = [];
|
|
83
|
-
|
|
84
|
-
for (const candidate of candidateContexts) {
|
|
85
|
-
// Skip self
|
|
86
|
-
if (candidate.user.id === targetUserContext.user.id) continue;
|
|
87
|
-
|
|
88
|
-
const candidateVector = this._buildFeatureVector(candidate);
|
|
89
|
-
const similarity = this._cosineSimilarity(targetVector, candidateVector);
|
|
90
|
-
|
|
91
|
-
scores.push({
|
|
92
|
-
userId: candidate.user.id,
|
|
93
|
-
score: similarity,
|
|
94
|
-
matches: this._explainMatch(targetVector, candidateVector)
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return scores.sort((a,b) => b.score - a.score).slice(0, limit);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
static _buildFeatureVector(ctx) {
|
|
102
|
-
// Simplified feature extraction
|
|
103
|
-
const portfolio = ctx.user.portfolio.today;
|
|
104
|
-
const positions = portfolio.AggregatedPositions || portfolio.PublicPositions || [];
|
|
105
|
-
|
|
106
|
-
// 1. Sector Weights
|
|
107
|
-
const sectorMap = {};
|
|
108
|
-
let totalVal = 0;
|
|
109
|
-
positions.forEach(p => {
|
|
110
|
-
// Assume mappings are available in ctx or extracted beforehand
|
|
111
|
-
// For this snippet, we use InstrumentID as a proxy for asset overlap if sector missing
|
|
112
|
-
const val = p.Value || 0;
|
|
113
|
-
totalVal += val;
|
|
114
|
-
sectorMap[p.InstrumentID] = (sectorMap[p.InstrumentID] || 0) + val;
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Normalize
|
|
118
|
-
const vector = {};
|
|
119
|
-
if (totalVal > 0) {
|
|
120
|
-
Object.keys(sectorMap).forEach(k => vector[k] = sectorMap[k] / totalVal);
|
|
121
|
-
}
|
|
122
|
-
return vector;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
static _cosineSimilarity(vecA, vecB) {
|
|
126
|
-
const keys = new Set([...Object.keys(vecA), ...Object.keys(vecB)]);
|
|
127
|
-
let dot = 0, magA = 0, magB = 0;
|
|
128
|
-
keys.forEach(k => {
|
|
129
|
-
const valA = vecA[k] || 0;
|
|
130
|
-
const valB = vecB[k] || 0;
|
|
131
|
-
dot += valA * valB;
|
|
132
|
-
magA += valA * valA;
|
|
133
|
-
magB += valB * valB;
|
|
134
|
-
});
|
|
135
|
-
if (magA === 0 || magB === 0) return 0;
|
|
136
|
-
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
static _explainMatch(vecA, vecB) {
|
|
140
|
-
// Find top overlapping keys
|
|
141
|
-
const overlaps = [];
|
|
142
|
-
Object.keys(vecA).forEach(k => {
|
|
143
|
-
if (vecB[k]) overlaps.push(k);
|
|
144
|
-
});
|
|
145
|
-
return overlaps.slice(0, 3); // Top 3 common instruments
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
class SmartMoneyScorer {
|
|
150
|
-
static _correlation(x, y) {
|
|
151
|
-
if (!x || !y || x.length !== y.length || x.length < 2) return 0;
|
|
152
|
-
const n = x.length;
|
|
153
|
-
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
|
|
154
|
-
for (let i = 0; i < n; i++) {
|
|
155
|
-
sumX += x[i]; sumY += y[i];
|
|
156
|
-
sumXY += x[i] * y[i];
|
|
157
|
-
sumX2 += x[i] * x[i]; sumY2 += y[i] * y[i];
|
|
158
|
-
}
|
|
159
|
-
const numerator = (n * sumXY) - (sumX * sumY);
|
|
160
|
-
const denominator = Math.sqrt(((n * sumX2) - (sumX * sumX)) * ((n * sumY2) - (sumY * sumY)));
|
|
161
|
-
return (denominator === 0) ? 0 : numerator / denominator;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
static scorePortfolio(portfolio, userType, prices, mappings, math) {
|
|
165
|
-
const positions = math.extract.getPositions(portfolio, userType);
|
|
166
|
-
if (!positions || positions.length === 0) return { score: 0, label: SCHEMAS.LABELS.NEUTRAL };
|
|
167
|
-
|
|
168
|
-
let totalInvested = 0;
|
|
169
|
-
let weightedPnL = 0;
|
|
170
|
-
let shortInvested = 0;
|
|
171
|
-
let shortPnL = 0;
|
|
172
|
-
|
|
173
|
-
const weights = [];
|
|
174
|
-
const pnls = [];
|
|
175
|
-
const sectors = new Set();
|
|
176
|
-
const tickers = new Set();
|
|
177
|
-
|
|
178
|
-
for (const pos of positions) {
|
|
179
|
-
const invested = math.extract.getPositionWeight(pos, userType);
|
|
180
|
-
const pnl = math.extract.getNetProfit(pos);
|
|
181
|
-
const instId = math.extract.getInstrumentId(pos);
|
|
182
|
-
const isShort = math.extract.getDirection(pos) === 'Sell';
|
|
183
|
-
|
|
184
|
-
const sector = mappings.instrumentToSector[instId];
|
|
185
|
-
const ticker = mappings.instrumentToTicker[instId];
|
|
186
|
-
|
|
187
|
-
if (invested > 0) {
|
|
188
|
-
totalInvested += invested;
|
|
189
|
-
weightedPnL += (pnl * invested);
|
|
190
|
-
weights.push(invested);
|
|
191
|
-
pnls.push(pnl);
|
|
192
|
-
|
|
193
|
-
if (sector) sectors.add(sector);
|
|
194
|
-
if (ticker) tickers.add(ticker);
|
|
195
|
-
|
|
196
|
-
if (isShort) {
|
|
197
|
-
shortInvested += invested;
|
|
198
|
-
shortPnL += (pnl * invested);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (totalInvested === 0) return { score: 0, label: SCHEMAS.LABELS.NEUTRAL };
|
|
204
|
-
|
|
205
|
-
const avgPnL = weightedPnL / totalInvested;
|
|
206
|
-
const allocEfficiency = this._correlation(weights, pnls);
|
|
207
|
-
|
|
208
|
-
let hhi = 0;
|
|
209
|
-
for (const w of weights) {
|
|
210
|
-
const share = w / totalInvested;
|
|
211
|
-
hhi += (share * share);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const shortRatio = shortInvested / totalInvested;
|
|
215
|
-
const avgShortPnL = shortInvested > 0 ? shortPnL / shortInvested : 0;
|
|
216
|
-
|
|
217
|
-
let score = 50;
|
|
218
|
-
|
|
219
|
-
if (allocEfficiency > 0.5) score += 20;
|
|
220
|
-
else if (allocEfficiency < -0.3) score -= 15;
|
|
221
|
-
|
|
222
|
-
if (avgPnL > 5) score += 10;
|
|
223
|
-
if (avgPnL > 20) score += 10;
|
|
224
|
-
if (avgPnL < -10) score -= 10;
|
|
225
|
-
if (avgPnL < -25) score -= 15;
|
|
226
|
-
|
|
227
|
-
if (hhi > 0.3) {
|
|
228
|
-
if (avgPnL > 5) score += 10;
|
|
229
|
-
else if (avgPnL < -5) score -= 10;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (sectors.size >= 4) score += 5;
|
|
233
|
-
|
|
234
|
-
if (shortRatio > 0.1) {
|
|
235
|
-
if (avgShortPnL > 0) score += 10;
|
|
236
|
-
else score -= 10;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return {
|
|
240
|
-
score: Math.max(0, Math.min(100, score)),
|
|
241
|
-
metrics: { allocEfficiency, hhi, avgPnL, shortRatio, sectorCount: sectors.size }
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
static scoreHistory(historyDoc, prices, mappings, math) {
|
|
246
|
-
const trades = historyDoc?.PublicHistoryPositions || [];
|
|
247
|
-
|
|
248
|
-
if (trades.length < 5) return { score: 0, label: SCHEMAS.LABELS.NEUTRAL };
|
|
249
|
-
const validTrades = trades.filter(t => t.OpenDateTime && t.CloseDateTime && t.InstrumentID);
|
|
250
|
-
if (validTrades.length < 5) return { score: 0, label: SCHEMAS.LABELS.NEUTRAL };
|
|
251
|
-
|
|
252
|
-
let wins = 0, losses = 0;
|
|
253
|
-
let totalWinPct = 0, totalLossPct = 0;
|
|
254
|
-
let entryScores = [];
|
|
255
|
-
const assetsTraded = new Map();
|
|
256
|
-
|
|
257
|
-
validTrades.sort((a, b) => new Date(a.OpenDateTime) - new Date(b.OpenDateTime));
|
|
258
|
-
const firstDate = new Date(validTrades[0].OpenDateTime);
|
|
259
|
-
const lastDate = new Date(validTrades[validTrades.length-1].OpenDateTime);
|
|
260
|
-
const daysActive = Math.max(1, (lastDate - firstDate) / 86400000);
|
|
261
|
-
|
|
262
|
-
for (const t of validTrades) {
|
|
263
|
-
const ticker = mappings.instrumentToTicker[t.InstrumentID];
|
|
264
|
-
|
|
265
|
-
if (!assetsTraded.has(t.InstrumentID)) assetsTraded.set(t.InstrumentID, { count: 0, pnl: 0 });
|
|
266
|
-
const assetStat = assetsTraded.get(t.InstrumentID);
|
|
267
|
-
assetStat.count++;
|
|
268
|
-
assetStat.pnl += t.NetProfit;
|
|
269
|
-
|
|
270
|
-
if (t.NetProfit > 0) { wins++; totalWinPct += t.NetProfit; }
|
|
271
|
-
else { losses++; totalLossPct += Math.abs(t.NetProfit); }
|
|
272
|
-
|
|
273
|
-
if (ticker && prices) {
|
|
274
|
-
const priceHist = math.priceExtractor.getHistory(prices, ticker);
|
|
275
|
-
if (priceHist && priceHist.length > 0) {
|
|
276
|
-
const eff = ExecutionAnalytics.calculateEfficiency(t.OpenRate, priceHist, t.OpenDateTime, t.IsBuy ? 'Buy' : 'Sell');
|
|
277
|
-
entryScores.push(eff);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const avgWin = wins > 0 ? totalWinPct / wins : 0;
|
|
283
|
-
const avgLoss = losses > 0 ? totalLossPct / losses : 1;
|
|
284
|
-
const profitFactor = (wins * avgWin) / Math.max(1, (losses * avgLoss));
|
|
285
|
-
|
|
286
|
-
const avgEntrySkill = entryScores.length > 0 ? math.compute.average(entryScores) : 0.5;
|
|
287
|
-
|
|
288
|
-
const totalTrades = validTrades.length;
|
|
289
|
-
const uniqueAssets = assetsTraded.size;
|
|
290
|
-
const specializationRatio = 1 - (uniqueAssets / totalTrades);
|
|
291
|
-
|
|
292
|
-
const tradesPerDay = totalTrades / daysActive;
|
|
293
|
-
|
|
294
|
-
let revengeScore = 0;
|
|
295
|
-
for (const [id, stat] of assetsTraded.entries()) {
|
|
296
|
-
if (stat.pnl < -20 && stat.count > 5) revengeScore += 1;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
let score = 50;
|
|
300
|
-
|
|
301
|
-
if (profitFactor > 1.2) score += 10;
|
|
302
|
-
if (profitFactor > 2.0) score += 15;
|
|
303
|
-
if (profitFactor < 0.8) score -= 15;
|
|
304
|
-
|
|
305
|
-
if (avgEntrySkill > 0.7) score += 10;
|
|
306
|
-
if (avgEntrySkill < 0.3) score -= 10;
|
|
307
|
-
|
|
308
|
-
if (specializationRatio > 0.6) score += 5;
|
|
309
|
-
if (specializationRatio < 0.1 && totalTrades > 20) score -= 5;
|
|
310
|
-
|
|
311
|
-
if (tradesPerDay > 10 && profitFactor < 1.0) score -= 10;
|
|
312
|
-
|
|
313
|
-
if (revengeScore > 0) score -= (revengeScore * 5);
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
score: Math.max(0, Math.min(100, score)),
|
|
317
|
-
metrics: { profitFactor, avgEntrySkill, specializationRatio, tradesPerDay, revengeScore }
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
static scoreHybrid(context) {
|
|
322
|
-
const { user, prices, mappings, math } = context;
|
|
323
|
-
|
|
324
|
-
const pScore = this.scorePortfolio(user.portfolio.today, user.type, prices, mappings, math);
|
|
325
|
-
const hScore = this.scoreHistory(user.history.today, prices, mappings, math);
|
|
326
|
-
|
|
327
|
-
let finalScore = 50;
|
|
328
|
-
let method = 'Neutral';
|
|
329
|
-
|
|
330
|
-
const hasHistory = hScore && hScore.score > 0;
|
|
331
|
-
const hasPortfolio = pScore && pScore.score > 0;
|
|
332
|
-
|
|
333
|
-
if (hasHistory && hasPortfolio) {
|
|
334
|
-
finalScore = (hScore.score * 0.6) + (pScore.score * 0.4);
|
|
335
|
-
method = 'Hybrid';
|
|
336
|
-
} else if (hasHistory) {
|
|
337
|
-
finalScore = hScore.score;
|
|
338
|
-
method = 'HistoryOnly';
|
|
339
|
-
} else if (hasPortfolio) {
|
|
340
|
-
finalScore = pScore.score;
|
|
341
|
-
method = 'PortfolioOnly';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
let label = SCHEMAS.LABELS.NEUTRAL;
|
|
345
|
-
if (finalScore >= 80) label = SCHEMAS.LABELS.ELITE;
|
|
346
|
-
else if (finalScore >= 65) label = SCHEMAS.LABELS.SMART;
|
|
347
|
-
else if (finalScore <= 35) label = SCHEMAS.LABELS.GAMBLER;
|
|
348
|
-
else if (finalScore <= 50) label = SCHEMAS.LABELS.DUMB;
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
totalScore: Math.round(finalScore),
|
|
352
|
-
label: label,
|
|
353
|
-
method: method,
|
|
354
|
-
components: {
|
|
355
|
-
portfolio: pScore,
|
|
356
|
-
history: hScore
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
class CognitiveBiases {
|
|
363
|
-
static calculateAnchoringScore(openPositions, thresholdPct = 2.0, minDaysHeld = 14) {
|
|
364
|
-
if (!openPositions || openPositions.length === 0) return 0;
|
|
365
|
-
let anchoredCount = 0, validPositions = 0;
|
|
366
|
-
const now = Date.now(), msPerDay = 86400000;
|
|
367
|
-
for (const pos of openPositions) {
|
|
368
|
-
if (pos.OpenDateTime) {
|
|
369
|
-
validPositions++;
|
|
370
|
-
const ageDays = (now - new Date(pos.OpenDateTime).getTime()) / msPerDay;
|
|
371
|
-
if (ageDays > minDaysHeld && Math.abs(pos.NetProfit) < thresholdPct) { anchoredCount++; }
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return validPositions > 0 ? (anchoredCount / validPositions) : 0;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
static calculateDispositionEffect(historyTrades) {
|
|
378
|
-
let winDur = 0, winCount = 0, lossDur = 0, lossCount = 0;
|
|
379
|
-
for (const t of historyTrades) {
|
|
380
|
-
if (!t.OpenDateTime || !t.CloseDateTime) continue;
|
|
381
|
-
const dur = (new Date(t.CloseDateTime) - new Date(t.OpenDateTime)) / 3600000;
|
|
382
|
-
if (t.NetProfit > 0) { winDur += dur; winCount++; } else if (t.NetProfit < 0) { lossDur += dur; lossCount++; }
|
|
383
|
-
}
|
|
384
|
-
const avgWinHold = winCount > 0 ? winDur / winCount : 0;
|
|
385
|
-
const avgLossHold = lossCount > 0 ? lossDur / lossCount : 0;
|
|
386
|
-
if (avgWinHold === 0) return 2.0;
|
|
387
|
-
return avgLossHold / avgWinHold;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
class SkillAttribution {
|
|
392
|
-
static calculateSelectionAlpha(userPositions, dailyInsights) {
|
|
393
|
-
let totalAlpha = 0, count = 0;
|
|
394
|
-
for (const pos of userPositions) {
|
|
395
|
-
const instrumentId = pos.InstrumentID;
|
|
396
|
-
let insight = Array.isArray(dailyInsights) ? dailyInsights.find(i => i.instrumentId === instrumentId) : null;
|
|
397
|
-
if (insight && typeof insight.growth === 'number') {
|
|
398
|
-
totalAlpha += (pos.NetProfit - insight.growth);
|
|
399
|
-
count++;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return count > 0 ? totalAlpha / count : 0;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
class ExecutionAnalytics {
|
|
407
|
-
static calculateEfficiency(price, priceHistory, date, direction, windowDays = 7) {
|
|
408
|
-
if (!priceHistory || priceHistory.length === 0) return 0.5;
|
|
409
|
-
const targetDate = new Date(date);
|
|
410
|
-
const start = new Date(targetDate); start.setDate(start.getDate() - windowDays);
|
|
411
|
-
const end = new Date(targetDate); end.setDate(end.getDate() + windowDays);
|
|
412
|
-
const pricesInWindow = priceHistory.filter(p => { const d = new Date(p.date); return d >= start && d <= end; }).map(p => p.price);
|
|
413
|
-
if (pricesInWindow.length === 0) return 0.5;
|
|
414
|
-
const minP = Math.min(...pricesInWindow);
|
|
415
|
-
const maxP = Math.max(...pricesInWindow);
|
|
416
|
-
const range = maxP - minP;
|
|
417
|
-
if (range === 0) return 0.5;
|
|
418
|
-
const location = (price - minP) / range;
|
|
419
|
-
if (direction === 'Buy') { return 1 - location; } else { return location; }
|
|
420
|
-
}
|
|
421
|
-
static calculateLossTolerance(realizedPnL, maxDrawdown) {
|
|
422
|
-
if (maxDrawdown === 0) return 1;
|
|
423
|
-
return (realizedPnL - maxDrawdown) / Math.abs(maxDrawdown);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* UPGRADE: Risk Geometry Class
|
|
429
|
-
* Contains Convex Hull (Monotone Chain) for Efficient Frontier analysis.
|
|
430
|
-
*/
|
|
431
|
-
class RiskGeometry {
|
|
432
|
-
static crossProduct(o, a, b) {
|
|
433
|
-
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Computes the Convex Hull of a set of 2D points using Monotone Chain algorithm.
|
|
438
|
-
* @param {Array<{x: number, y: number}>} points
|
|
439
|
-
* @returns {Array<{x: number, y: number}>} Points on the hull
|
|
440
|
-
*/
|
|
441
|
-
static computeConvexHull(points) {
|
|
442
|
-
if (points.length <= 1) return points;
|
|
443
|
-
|
|
444
|
-
// Sort by x coordinate (and y if x is same)
|
|
445
|
-
points.sort((a, b) => a.x === b.x ? a.y - b.y : a.x - b.x);
|
|
446
|
-
|
|
447
|
-
const lower = [];
|
|
448
|
-
for (const p of points) {
|
|
449
|
-
while (lower.length >= 2 && this.crossProduct(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
|
|
450
|
-
lower.pop();
|
|
451
|
-
}
|
|
452
|
-
lower.push(p);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const upper = [];
|
|
456
|
-
for (let i = points.length - 1; i >= 0; i--) {
|
|
457
|
-
const p = points[i];
|
|
458
|
-
while (upper.length >= 2 && this.crossProduct(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
|
|
459
|
-
upper.pop();
|
|
460
|
-
}
|
|
461
|
-
upper.push(p);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
upper.pop();
|
|
465
|
-
lower.pop();
|
|
466
|
-
return lower.concat(upper);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
class Psychometrics {
|
|
471
|
-
static computeDispositionSkew(historyTrades, currentPositions) {
|
|
472
|
-
const getMedian = (arr) => { if (!arr.length) return 0; const sorted = [...arr].sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; };
|
|
473
|
-
const realized = historyTrades.map(t => t.NetProfit);
|
|
474
|
-
const unrealized = currentPositions.map(p => p.NetProfit);
|
|
475
|
-
if (realized.length < 5 || unrealized.length < 5) return 0;
|
|
476
|
-
return getMedian(realized) - getMedian(unrealized);
|
|
477
|
-
}
|
|
478
|
-
static detectRevengeTrading(historyTrades) {
|
|
479
|
-
let riskSpikes = 0; let losses = 0; if (historyTrades.length === 0) return 0;
|
|
480
|
-
const avgLev = historyTrades.reduce((sum, t) => sum + (t.Leverage || 1), 0) / historyTrades.length;
|
|
481
|
-
for (let i = 1; i < historyTrades.length; i++) {
|
|
482
|
-
const prev = historyTrades[i-1]; const curr = historyTrades[i];
|
|
483
|
-
if (prev.NetProfit < 0) { losses++; if ((curr.Leverage || 1) > (avgLev * 1.5)) { riskSpikes++; } }
|
|
484
|
-
}
|
|
485
|
-
return losses > 0 ? (riskSpikes / losses) : 0;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
class AdaptiveAnalytics {
|
|
490
|
-
static analyzeDrawdownAdaptation(tradeHistory, drawdownThreshold = -15) {
|
|
491
|
-
let adaptationScore = 0; let eventCount = 0;
|
|
492
|
-
for (let i = 0; i < tradeHistory.length - 3; i++) {
|
|
493
|
-
if (tradeHistory[i].NetProfit < drawdownThreshold) {
|
|
494
|
-
eventCount++; const nextTrades = tradeHistory.slice(i+1, i+4);
|
|
495
|
-
const prevLev = tradeHistory[i].Leverage || 1;
|
|
496
|
-
const nextLevAvg = nextTrades.reduce((s, t) => s + (t.Leverage||1), 0) / 3;
|
|
497
|
-
if (nextLevAvg < prevLev) adaptationScore += 1; else if (nextLevAvg > prevLev * 1.5) adaptationScore -= 2;
|
|
498
|
-
const lostInstrument = tradeHistory[i].InstrumentID;
|
|
499
|
-
const stuckToSame = nextTrades.every(t => t.InstrumentID === lostInstrument);
|
|
500
|
-
if (!stuckToSame) adaptationScore += 0.5;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return eventCount > 0 ? (adaptationScore / eventCount) : 0;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
class UserClassifier {
|
|
508
|
-
static classify(context) {
|
|
509
|
-
const result = SmartMoneyScorer.scoreHybrid(context);
|
|
510
|
-
return {
|
|
511
|
-
intelligence: {
|
|
512
|
-
label: result.label,
|
|
513
|
-
score: result.totalScore,
|
|
514
|
-
isSmart: result.totalScore >= 65
|
|
515
|
-
},
|
|
516
|
-
style: { primary: SCHEMAS.STYLES.INVESTOR },
|
|
517
|
-
metrics: {
|
|
518
|
-
profitFactor: result.components.history?.metrics?.profitFactor || 0,
|
|
519
|
-
allocEfficiency: result.components.portfolio?.metrics?.allocEfficiency || 0
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
module.exports = {
|
|
526
|
-
SCHEMAS,
|
|
527
|
-
UserClassifier,
|
|
528
|
-
SmartMoneyScorer,
|
|
529
|
-
ExecutionAnalytics,
|
|
530
|
-
Psychometrics,
|
|
531
|
-
AdaptiveAnalytics,
|
|
532
|
-
CognitiveBiases,
|
|
533
|
-
SkillAttribution,
|
|
534
|
-
RiskGeometry,
|
|
535
|
-
AnomalyDetector,
|
|
536
|
-
SimilarityEngine
|
|
537
|
-
};
|