aiden-shared-calculations-unified 1.0.105 → 1.0.107
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/crowd-cost-basis.js +63 -36
- package/calculations/core/leverage-divergence.js +86 -51
- package/calculations/core/liquidation-cascade.js +57 -26
- package/calculations/core/user-history-reconstructor.js +153 -61
- package/calculations/gauss/daily-dna-filter.js +13 -24
- package/calculations/gem/cohort-skill-definition.js +13 -19
- package/package.json +1 -1
- package/calculations/audit_results.txt +0 -97
- package/calculations/audit_schemas.js +0 -47
|
@@ -1,83 +1,110 @@
|
|
|
1
1
|
class CrowdCostBasis {
|
|
2
|
-
constructor() {
|
|
3
|
-
this.results = {};
|
|
4
|
-
}
|
|
2
|
+
constructor() { this.results = {}; }
|
|
5
3
|
|
|
6
4
|
static getMetadata() {
|
|
7
5
|
return {
|
|
8
6
|
name: 'crowd-cost-basis',
|
|
9
7
|
type: 'meta',
|
|
10
8
|
category: 'History Reconstruction',
|
|
11
|
-
userType: 'n/a',
|
|
12
|
-
isHistorical: false,
|
|
9
|
+
userType: 'n/a',
|
|
10
|
+
isHistorical: false, // Self-contained history generation
|
|
13
11
|
rootDataDependencies: ['price']
|
|
14
12
|
};
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
static getDependencies() {
|
|
18
|
-
return ['user-history-reconstructor'];
|
|
19
|
-
}
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
20
16
|
|
|
21
17
|
static getSchema() {
|
|
22
|
-
const
|
|
18
|
+
const dailySchema = {
|
|
23
19
|
"type": "object",
|
|
24
20
|
"properties": {
|
|
25
|
-
"avgEntry": { "type": "number" },
|
|
26
|
-
"holderCount": { "type": "number" },
|
|
27
|
-
"profitabilityPct": { "type": "number" },
|
|
28
|
-
"state": { "type": "string" }
|
|
21
|
+
"avgEntry": { "type": "number", "description": "Global average entry price for all holders." },
|
|
22
|
+
"holderCount": { "type": "number", "description": "Total number of users holding the asset." },
|
|
23
|
+
"profitabilityPct": { "type": "number", "description": "Percentage distance between current price and avg entry." },
|
|
24
|
+
"state": { "type": "string", "description": "Profitability state: PROFIT_SUPPORT or LOSS_RESISTANCE." }
|
|
29
25
|
},
|
|
30
26
|
"required": ["avgEntry", "holderCount", "profitabilityPct", "state"]
|
|
31
27
|
};
|
|
32
|
-
|
|
28
|
+
|
|
29
|
+
// Output: Date -> Ticker -> Schema
|
|
30
|
+
return {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"patternProperties": {
|
|
33
|
+
"^\\d{4}-\\d{2}-\\d{2}$": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"patternProperties": { "^.*$": dailySchema }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
async process(context) {
|
|
36
42
|
const { computed, prices, math } = context;
|
|
43
|
+
const historyData = computed['user-history-reconstructor'];
|
|
44
|
+
if (!historyData) return;
|
|
45
|
+
|
|
46
|
+
// Detect Structure: Date-First (Time Machine) or User-First (Legacy)
|
|
47
|
+
// With the new StandardExecutor refactor, it should be Date -> User -> Ticker
|
|
48
|
+
const keys = Object.keys(historyData);
|
|
49
|
+
const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
|
|
50
|
+
|
|
51
|
+
if (isDateKeyed) {
|
|
52
|
+
// Process All Dates in the Map
|
|
53
|
+
for (const dateStr of keys) {
|
|
54
|
+
this.results[dateStr] = this.processSingleDate(dateStr, historyData[dateStr], prices, math);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Fallback for single-date/legacy context
|
|
58
|
+
const dateStr = context.date.today;
|
|
59
|
+
this.results[dateStr] = this.processSingleDate(dateStr, historyData, prices, math);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const aggregator = {}; // { AAPL: { sumEntry: 0, count: 0 } }
|
|
63
|
+
processSingleDate(dateStr, usersMap, prices, math) {
|
|
64
|
+
const aggregator = {};
|
|
65
|
+
const dailyResults = {};
|
|
43
66
|
|
|
44
|
-
//
|
|
45
|
-
for (const userId in
|
|
46
|
-
const userPortfolio =
|
|
67
|
+
// 1. Iterate Users for this Date
|
|
68
|
+
for (const userId in usersMap) {
|
|
69
|
+
const userPortfolio = usersMap[userId];
|
|
47
70
|
|
|
48
71
|
for (const ticker in userPortfolio) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
|
|
72
|
+
const stats = userPortfolio[ticker];
|
|
52
73
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
if (stats.isHolder === 1) {
|
|
75
|
+
if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
|
|
76
|
+
aggregator[ticker].sumEntry += stats.avgEntry;
|
|
77
|
+
aggregator[ticker].count++;
|
|
78
|
+
}
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
//
|
|
82
|
+
// 2. Compute Global Cost Basis
|
|
60
83
|
for (const ticker in aggregator) {
|
|
61
84
|
const data = aggregator[ticker];
|
|
62
|
-
if (data.count <
|
|
85
|
+
if (data.count < 3) continue; // Noise filter
|
|
63
86
|
|
|
64
87
|
const globalAvgEntry = data.sumEntry / data.count;
|
|
65
88
|
|
|
66
|
-
// Get
|
|
89
|
+
// Get Price for this specific date
|
|
90
|
+
// Note: prices.history might be sharded, assuming ContextFactory loaded relevant shards
|
|
67
91
|
const priceHistory = math.priceExtractor.getHistory(prices, ticker);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
|
|
93
|
+
// Find price closest to this date
|
|
94
|
+
// Optimization: In a real "Time Machine" run, priceExtractor should probably be a map lookup
|
|
95
|
+
const dayPriceObj = priceHistory.find(p => p.date === dateStr);
|
|
96
|
+
const currentPrice = dayPriceObj ? dayPriceObj.price : globalAvgEntry;
|
|
71
97
|
|
|
72
|
-
const diffPct = ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100;
|
|
98
|
+
const diffPct = globalAvgEntry > 0 ? ((currentPrice - globalAvgEntry) / globalAvgEntry) * 100 : 0;
|
|
73
99
|
|
|
74
|
-
|
|
100
|
+
dailyResults[ticker] = {
|
|
75
101
|
avgEntry: globalAvgEntry,
|
|
76
102
|
holderCount: data.count,
|
|
77
103
|
profitabilityPct: diffPct,
|
|
78
104
|
state: diffPct > 0 ? 'PROFIT_SUPPORT' : 'LOSS_RESISTANCE'
|
|
79
105
|
};
|
|
80
106
|
}
|
|
107
|
+
return dailyResults;
|
|
81
108
|
}
|
|
82
109
|
|
|
83
110
|
async getResult() { return this.results; }
|
|
@@ -7,84 +7,119 @@ class LeverageDivergence {
|
|
|
7
7
|
type: 'meta',
|
|
8
8
|
category: 'History Reconstruction',
|
|
9
9
|
userType: 'n/a',
|
|
10
|
-
isHistorical:
|
|
11
|
-
rootDataDependencies: []
|
|
10
|
+
isHistorical: false, // We handle history internally now
|
|
11
|
+
rootDataDependencies: ['price'] // Needed for Smart/Dumb PnL calc
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
static getDependencies() {
|
|
16
|
-
return ['user-history-reconstructor'];
|
|
17
|
-
}
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
18
16
|
|
|
19
17
|
static getSchema() {
|
|
20
|
-
const
|
|
18
|
+
const dailySchema = {
|
|
21
19
|
"type": "object",
|
|
22
20
|
"properties": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"signal": { "type": "string" }
|
|
21
|
+
"smartAccumulation": { "type": "number", "description": "Buy volume from profitable users." },
|
|
22
|
+
"dumbAccumulation": { "type": "number", "description": "Buy volume from unprofitable/high-lev users." },
|
|
23
|
+
"netFlow": { "type": "number", "description": "Net buy/sell activity." },
|
|
24
|
+
"signal": { "type": "string", "description": "Divergence signal: NEUTRAL, SMART_ACCUMULATION, SPECULATIVE_PUMP, BROAD_EXIT." }
|
|
28
25
|
},
|
|
29
|
-
"required": ["
|
|
26
|
+
"required": ["smartAccumulation", "dumbAccumulation", "netFlow", "signal"]
|
|
27
|
+
};
|
|
28
|
+
// Output: Date -> Ticker -> Schema
|
|
29
|
+
return {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"patternProperties": {
|
|
32
|
+
"^\\d{4}-\\d{2}-\\d{2}$": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"patternProperties": { "^.*$": dailySchema }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
30
37
|
};
|
|
31
|
-
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
async process(context) {
|
|
35
|
-
const { computed,
|
|
41
|
+
const { computed, prices, math } = context;
|
|
42
|
+
const historyData = computed['user-history-reconstructor'];
|
|
43
|
+
if (!historyData) return;
|
|
44
|
+
|
|
45
|
+
const keys = Object.keys(historyData);
|
|
46
|
+
const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
|
|
47
|
+
const dateList = isDateKeyed ? keys.sort() : [context.date.today];
|
|
48
|
+
|
|
49
|
+
// Process sequentially to allow day-over-day comparison if needed (though we use raw flows here)
|
|
50
|
+
for (const dateStr of dateList) {
|
|
51
|
+
const usersMap = isDateKeyed ? historyData[dateStr] : historyData;
|
|
52
|
+
this.results[dateStr] = this.processSingleDate(dateStr, usersMap, prices, math);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
36
55
|
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
const previousResult = previousComputed['leverage-divergence'];
|
|
56
|
+
processSingleDate(dateStr, usersMap, prices, math) {
|
|
57
|
+
const tickerAgg = {}; // Ticker -> { smartBuys, dumbBuys, sells }
|
|
40
58
|
|
|
41
|
-
const
|
|
59
|
+
for (const userId in usersMap) {
|
|
60
|
+
const userPortfolio = usersMap[userId];
|
|
42
61
|
|
|
43
|
-
// 1. Build Today's Aggregates
|
|
44
|
-
for (const userId in currentReconstruction) {
|
|
45
|
-
const userPortfolio = currentReconstruction[userId];
|
|
46
62
|
for (const ticker in userPortfolio) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
const stats = userPortfolio[ticker];
|
|
64
|
+
|
|
65
|
+
// We only care about Activity (Buys/Sells)
|
|
66
|
+
if (stats.didBuy === 0 && stats.didSell === 0) continue;
|
|
67
|
+
|
|
68
|
+
if (!tickerAgg[ticker]) tickerAgg[ticker] = { smartBuys: 0, dumbBuys: 0, sells: 0 };
|
|
69
|
+
|
|
70
|
+
// 1. Classify User "Smartness" for this Ticker/Date
|
|
71
|
+
// Heuristic: Are they profitable on their holdings relative to TODAY's price?
|
|
72
|
+
const priceHistory = math.priceExtractor.getHistory(prices, ticker);
|
|
73
|
+
const dayPriceObj = priceHistory.find(p => p.date === dateStr);
|
|
74
|
+
const currentPrice = dayPriceObj ? dayPriceObj.price : stats.avgEntry;
|
|
75
|
+
|
|
76
|
+
let isSmart = false;
|
|
77
|
+
if (stats.avgEntry > 0 && currentPrice > 0) {
|
|
78
|
+
const pnlPct = ((currentPrice - stats.avgEntry) / stats.avgEntry) * 100;
|
|
79
|
+
// Smart = Profitable (>2%) OR (Small Loss > -2% AND Low Leverage < 2x)
|
|
80
|
+
// Dumb = Unprofitable (< -5%) OR High Leverage (> 5x)
|
|
81
|
+
if (pnlPct > 2) isSmart = true;
|
|
82
|
+
else if (pnlPct > -2 && stats.avgLeverage < 2) isSmart = true;
|
|
83
|
+
}
|
|
50
84
|
|
|
51
|
-
//
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
// 2. Accumulate Flows
|
|
86
|
+
if (stats.didBuy > 0) {
|
|
87
|
+
if (isSmart) tickerAgg[ticker].smartBuys += stats.didBuy;
|
|
88
|
+
else tickerAgg[ticker].dumbBuys += stats.didBuy;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (stats.didSell > 0) {
|
|
92
|
+
tickerAgg[ticker].sells += stats.didSell;
|
|
56
93
|
}
|
|
57
94
|
}
|
|
58
95
|
}
|
|
59
96
|
|
|
60
|
-
|
|
61
|
-
for (const ticker in
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (!prev) {
|
|
66
|
-
this.results[ticker] = { ...curr, levDelta: 0, spotDelta: 0, signal: 'NEW' };
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
97
|
+
const dailyResult = {};
|
|
98
|
+
for (const ticker in tickerAgg) {
|
|
99
|
+
const { smartBuys, dumbBuys, sells } = tickerAgg[ticker];
|
|
100
|
+
const netFlow = (smartBuys + dumbBuys) - sells;
|
|
101
|
+
const totalActivity = smartBuys + dumbBuys + sells;
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
const spotDelta = curr.spotHolders - prev.spotHolders;
|
|
103
|
+
if (totalActivity < 3) continue;
|
|
72
104
|
|
|
73
105
|
let signal = 'NEUTRAL';
|
|
106
|
+
|
|
107
|
+
if (netFlow > 0) {
|
|
108
|
+
if (smartBuys > dumbBuys) signal = 'SMART_ACCUMULATION';
|
|
109
|
+
else signal = 'SPECULATIVE_PUMP'; // Mostly dumb/lev money buying
|
|
110
|
+
} else if (netFlow < 0) {
|
|
111
|
+
signal = 'BROAD_EXIT';
|
|
112
|
+
}
|
|
74
113
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.results[ticker] = {
|
|
82
|
-
...curr,
|
|
83
|
-
levDelta,
|
|
84
|
-
spotDelta,
|
|
85
|
-
signal
|
|
114
|
+
dailyResult[ticker] = {
|
|
115
|
+
smartAccumulation: smartBuys,
|
|
116
|
+
dumbAccumulation: dumbBuys,
|
|
117
|
+
netFlow: netFlow,
|
|
118
|
+
signal: signal
|
|
86
119
|
};
|
|
87
120
|
}
|
|
121
|
+
|
|
122
|
+
return dailyResult;
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
async getResult() { return this.results; }
|
|
@@ -12,61 +12,92 @@ class LiquidationCascade {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
static getDependencies() {
|
|
16
|
-
return ['user-history-reconstructor'];
|
|
17
|
-
}
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
18
16
|
|
|
19
17
|
static getSchema() {
|
|
20
|
-
const
|
|
18
|
+
const dailySchema = {
|
|
21
19
|
"type": "object",
|
|
22
20
|
"properties": {
|
|
23
|
-
"totalClosures": { "type": "number" },
|
|
24
|
-
"forcedClosures": { "type": "number" },
|
|
25
|
-
"
|
|
26
|
-
"
|
|
21
|
+
"totalClosures": { "type": "number", "description": "Total number of positions closed today." },
|
|
22
|
+
"forcedClosures": { "type": "number", "description": "Number of positions closed via Stop Loss (Reason 1)." },
|
|
23
|
+
"avgClosedLeverage": { "type": "number", "description": "Average leverage of the closed positions." },
|
|
24
|
+
"painIndex": { "type": "number", "description": "Ratio of forced closures to total closures (0.0 - 1.0)." },
|
|
25
|
+
"isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3 AND avgClosedLeverage > 3." }
|
|
27
26
|
},
|
|
28
27
|
"required": ["totalClosures", "forcedClosures", "painIndex", "isFlushEvent"]
|
|
29
28
|
};
|
|
30
|
-
|
|
29
|
+
// Output: Date -> Ticker -> Schema
|
|
30
|
+
return {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"patternProperties": {
|
|
33
|
+
"^\\d{4}-\\d{2}-\\d{2}$": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"patternProperties": { "^.*$": dailySchema }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
async process(context) {
|
|
34
42
|
const { computed } = context;
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
43
|
+
const historyData = computed['user-history-reconstructor'];
|
|
44
|
+
if (!historyData) return;
|
|
37
45
|
|
|
46
|
+
const keys = Object.keys(historyData);
|
|
47
|
+
const isDateKeyed = keys.length > 0 && /^\d{4}-\d{2}-\d{2}$/.test(keys[0]);
|
|
48
|
+
|
|
49
|
+
if (isDateKeyed) {
|
|
50
|
+
for (const dateStr of keys) {
|
|
51
|
+
this.results[dateStr] = this.processSingleDate(historyData[dateStr]);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
this.results[context.date.today] = this.processSingleDate(historyData);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
processSingleDate(usersMap) {
|
|
38
59
|
const aggregator = {};
|
|
60
|
+
const dailyResults = {};
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const userPortfolio = userReconstructions[userId];
|
|
62
|
+
for (const userId in usersMap) {
|
|
63
|
+
const userPortfolio = usersMap[userId];
|
|
43
64
|
|
|
44
65
|
for (const ticker in userPortfolio) {
|
|
45
|
-
const
|
|
66
|
+
const stats = userPortfolio[ticker];
|
|
67
|
+
|
|
68
|
+
if (stats.didSell > 0) {
|
|
69
|
+
if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0, sumLev: 0 };
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
aggregator[ticker].totalClosed += stats.didSell;
|
|
72
|
+
|
|
73
|
+
// Reason 1 is usually Stop Loss / Liquidation in cTrader/MetaTrader schemas
|
|
74
|
+
if (stats.closeReasons && stats.closeReasons["1"]) {
|
|
75
|
+
aggregator[ticker].forced += stats.closeReasons["1"];
|
|
76
|
+
}
|
|
49
77
|
|
|
50
|
-
|
|
51
|
-
aggregator[ticker].
|
|
78
|
+
// We use avgLeverage of the holding as a proxy for the closed leverage
|
|
79
|
+
aggregator[ticker].sumLev += (stats.avgLeverage * stats.didSell);
|
|
52
80
|
}
|
|
53
81
|
}
|
|
54
82
|
}
|
|
55
83
|
|
|
56
|
-
// 2. Calculate Pain Index
|
|
57
84
|
for (const ticker in aggregator) {
|
|
58
85
|
const data = aggregator[ticker];
|
|
59
|
-
if (data.
|
|
86
|
+
if (data.totalClosed < 3) continue;
|
|
60
87
|
|
|
61
|
-
const forcedRatio = data.forced / data.
|
|
88
|
+
const forcedRatio = data.forced / data.totalClosed;
|
|
89
|
+
const avgLev = data.sumLev / data.totalClosed;
|
|
62
90
|
|
|
63
|
-
|
|
64
|
-
totalClosures: data.
|
|
91
|
+
dailyResults[ticker] = {
|
|
92
|
+
totalClosures: data.totalClosed,
|
|
65
93
|
forcedClosures: data.forced,
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
avgClosedLeverage: avgLev,
|
|
95
|
+
painIndex: forcedRatio,
|
|
96
|
+
// Flush = High Pain AND Significant Leverage involved
|
|
97
|
+
isFlushEvent: forcedRatio > 0.3 && avgLev > 3
|
|
68
98
|
};
|
|
69
99
|
}
|
|
100
|
+
return dailyResults;
|
|
70
101
|
}
|
|
71
102
|
|
|
72
103
|
async getResult() { return this.results; }
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Reconstructs a user's
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Reconstructs a user's full trading history timeline from a single snapshot.
|
|
3
|
+
* Uses a Scan-Line algorithm to replay history and generate daily stats (Holders, Buys, Sells).
|
|
4
4
|
*/
|
|
5
|
+
|
|
5
6
|
class UserHistoryReconstructor {
|
|
6
7
|
constructor() {
|
|
7
|
-
this.results = {};
|
|
8
|
+
this.results = {};
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
static getMetadata() {
|
|
11
12
|
return {
|
|
12
13
|
name: 'user-history-reconstructor',
|
|
13
14
|
type: 'standard',
|
|
14
|
-
category: 'History Reconstruction',
|
|
15
|
-
userType: 'all',
|
|
16
|
-
|
|
15
|
+
category: 'History Reconstruction',
|
|
16
|
+
userType: 'all',
|
|
17
|
+
// False because we generate history internally from the snapshot
|
|
18
|
+
isHistorical: false,
|
|
17
19
|
rootDataDependencies: ['history']
|
|
18
20
|
};
|
|
19
21
|
}
|
|
@@ -21,88 +23,178 @@ class UserHistoryReconstructor {
|
|
|
21
23
|
static getDependencies() { return []; }
|
|
22
24
|
|
|
23
25
|
static getSchema() {
|
|
26
|
+
// Schema for a single Ticker's stats for a single Day
|
|
27
|
+
const tickerDailyStats = {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"isHolder": { "type": "number", "description": "1 if the user holds the asset at EOD, 0 otherwise." },
|
|
31
|
+
"didBuy": { "type": "number", "description": "Count of buy trades executed this day." },
|
|
32
|
+
"didSell": { "type": "number", "description": "Count of sell trades executed this day." },
|
|
33
|
+
"avgEntry": { "type": "number", "description": "Average entry price of held positions." },
|
|
34
|
+
"avgLeverage": { "type": "number", "description": "Average leverage of held positions." },
|
|
35
|
+
"buyLeverage": { "type": "number", "description": "Average leverage of new buys executed this day." },
|
|
36
|
+
"closeReasons": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"description": "Map of close reason codes to counts (0=Manual, 1=Stop/Liq, 5=TP)."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Output is now: Date -> Ticker -> Stats
|
|
24
44
|
return {
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
45
|
+
"type": "object",
|
|
46
|
+
"patternProperties": {
|
|
47
|
+
// Key is YYYY-MM-DD
|
|
48
|
+
"^\\d{4}-\\d{2}-\\d{2}$": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"patternProperties": {
|
|
51
|
+
// Key is Ticker
|
|
52
|
+
"^.*$": tickerDailyStats
|
|
53
|
+
}
|
|
32
54
|
}
|
|
33
55
|
}
|
|
34
56
|
};
|
|
35
57
|
}
|
|
36
58
|
|
|
37
59
|
async process(context) {
|
|
38
|
-
const { user, math, mappings
|
|
60
|
+
const { user, math, mappings } = context;
|
|
39
61
|
|
|
40
|
-
// 1. Get History
|
|
62
|
+
// 1. Get History (Granular V2 Format)
|
|
41
63
|
const history = math.history.getDailyHistory(user);
|
|
42
64
|
const allTrades = history?.PublicHistoryPositions || [];
|
|
43
65
|
|
|
44
66
|
if (allTrades.length === 0) return;
|
|
45
67
|
|
|
46
|
-
// 2.
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.results[user.id] = {};
|
|
52
|
-
const userState = this.results[user.id];
|
|
53
|
-
|
|
54
|
-
const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
|
|
55
|
-
const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
|
|
56
|
-
|
|
57
|
-
for (const trade of activeTrades) {
|
|
68
|
+
// 2. Identify all relevant tickers and dates
|
|
69
|
+
const events = []; // { time, type, trade, ticker }
|
|
70
|
+
const tickerSet = new Set();
|
|
71
|
+
|
|
72
|
+
for (const trade of allTrades) {
|
|
58
73
|
const instId = trade.InstrumentID;
|
|
59
74
|
const ticker = mappings.instrumentToTicker[instId];
|
|
60
75
|
if (!ticker) continue;
|
|
76
|
+
tickerSet.add(ticker);
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
const openTime = new Date(trade.OpenDateTime).getTime();
|
|
79
|
+
const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
|
|
80
|
+
|
|
81
|
+
// Add OPEN event
|
|
82
|
+
events.push({ time: openTime, type: 'OPEN', trade, ticker });
|
|
83
|
+
|
|
84
|
+
// Add CLOSE event (if closed)
|
|
85
|
+
if (closeTime) {
|
|
86
|
+
events.push({ time: closeTime, type: 'CLOSE', trade, ticker });
|
|
70
87
|
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Sort events by time
|
|
91
|
+
events.sort((a, b) => a.time - b.time);
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
if (events.length === 0) return;
|
|
94
|
+
|
|
95
|
+
// 3. Scan-Line Execution (Replay History)
|
|
96
|
+
const userTimeline = {}; // { "2024-01-01": { "AAPL": {...} } }
|
|
97
|
+
|
|
98
|
+
// Tracking State
|
|
99
|
+
const activePositions = new Map(); // Map<PositionID, Trade>
|
|
100
|
+
|
|
101
|
+
// Determine Start and End dates for the loop
|
|
102
|
+
const firstEventTime = events[0].time;
|
|
103
|
+
const lastEventTime = events[events.length - 1].time;
|
|
104
|
+
|
|
105
|
+
const startDate = new Date(firstEventTime); startDate.setUTCHours(0,0,0,0);
|
|
106
|
+
const endDate = new Date(lastEventTime); endDate.setUTCHours(0,0,0,0);
|
|
107
|
+
|
|
108
|
+
let currentEventIdx = 0;
|
|
109
|
+
const oneDayMs = 86400000;
|
|
110
|
+
|
|
111
|
+
// Iterate day by day from first trade to last trade
|
|
112
|
+
for (let d = startDate.getTime(); d <= endDate.getTime(); d += oneDayMs) {
|
|
113
|
+
const dateStr = new Date(d).toISOString().slice(0, 10);
|
|
114
|
+
const dayEnd = d + oneDayMs - 1;
|
|
115
|
+
|
|
116
|
+
const dayStats = {}; // Ticker -> Stats
|
|
117
|
+
|
|
118
|
+
// Process all events that happened TODAY (before EOD)
|
|
119
|
+
while (currentEventIdx < events.length && events[currentEventIdx].time <= dayEnd) {
|
|
120
|
+
const event = events[currentEventIdx];
|
|
121
|
+
const { ticker, trade, type } = event;
|
|
122
|
+
|
|
123
|
+
if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
|
|
124
|
+
|
|
125
|
+
if (type === 'OPEN') {
|
|
126
|
+
activePositions.set(trade.PositionID, trade);
|
|
127
|
+
dayStats[ticker].didBuy++;
|
|
128
|
+
dayStats[ticker].sumBuyLev += (trade.Leverage || 1);
|
|
129
|
+
} else if (type === 'CLOSE') {
|
|
130
|
+
activePositions.delete(trade.PositionID);
|
|
131
|
+
dayStats[ticker].didSell++;
|
|
132
|
+
|
|
133
|
+
const reason = String(trade.CloseReason || 0);
|
|
134
|
+
if (dayStats[ticker].closeReasons[reason] === undefined) dayStats[ticker].closeReasons[reason] = 0;
|
|
135
|
+
dayStats[ticker].closeReasons[reason]++;
|
|
86
136
|
}
|
|
137
|
+
currentEventIdx++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Snapshot the "Held" State at EOD
|
|
141
|
+
for (const [posId, trade] of activePositions) {
|
|
142
|
+
const ticker = mappings.instrumentToTicker[trade.InstrumentID];
|
|
143
|
+
if (!ticker) continue;
|
|
144
|
+
|
|
145
|
+
if (!dayStats[ticker]) this.initTickerStats(dayStats, ticker);
|
|
146
|
+
|
|
147
|
+
const stats = dayStats[ticker];
|
|
148
|
+
stats.isHolder = 1; // User is a holder today
|
|
149
|
+
stats.holdCount++;
|
|
150
|
+
stats.sumEntry += (trade.OpenRate || 0);
|
|
151
|
+
stats.sumLev += (trade.Leverage || 1);
|
|
87
152
|
}
|
|
88
|
-
}
|
|
89
153
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
stats.
|
|
95
|
-
|
|
154
|
+
// Finalize Averages for this Day
|
|
155
|
+
for (const ticker in dayStats) {
|
|
156
|
+
const stats = dayStats[ticker];
|
|
157
|
+
|
|
158
|
+
if (stats.holdCount > 0) {
|
|
159
|
+
stats.avgEntry = stats.sumEntry / stats.holdCount;
|
|
160
|
+
stats.avgLeverage = stats.sumLev / stats.holdCount;
|
|
161
|
+
}
|
|
162
|
+
if (stats.didBuy > 0) {
|
|
163
|
+
stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Cleanup temp sums
|
|
167
|
+
delete stats.sumEntry;
|
|
168
|
+
delete stats.sumLev;
|
|
169
|
+
delete stats.sumBuyLev;
|
|
170
|
+
delete stats.holdCount;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Store in timeline if any activity exists for this day
|
|
174
|
+
if (Object.keys(dayStats).length > 0) {
|
|
175
|
+
userTimeline[dateStr] = dayStats;
|
|
96
176
|
}
|
|
97
|
-
// Cleanup intermediate sums
|
|
98
|
-
delete stats.totalEntry;
|
|
99
|
-
delete stats.totalLev;
|
|
100
177
|
}
|
|
178
|
+
|
|
179
|
+
// 4. Output the full timeline for this user
|
|
180
|
+
// The ResultCommitter will handle splitting this into date buckets.
|
|
181
|
+
this.results[user.id] = userTimeline;
|
|
101
182
|
}
|
|
102
183
|
|
|
103
|
-
|
|
104
|
-
|
|
184
|
+
initTickerStats(dayStats, ticker) {
|
|
185
|
+
dayStats[ticker] = {
|
|
186
|
+
isHolder: 0,
|
|
187
|
+
didBuy: 0,
|
|
188
|
+
didSell: 0,
|
|
189
|
+
sumEntry: 0,
|
|
190
|
+
sumLev: 0,
|
|
191
|
+
holdCount: 0,
|
|
192
|
+
sumBuyLev: 0,
|
|
193
|
+
closeReasons: { "0": 0, "1": 0, "5": 0 }
|
|
194
|
+
};
|
|
105
195
|
}
|
|
196
|
+
|
|
197
|
+
async getResult() { return this.results; }
|
|
106
198
|
}
|
|
107
199
|
|
|
108
200
|
module.exports = UserHistoryReconstructor;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GAUSS Product Line (Pass 1)
|
|
3
|
-
* REFACTORED: Uses
|
|
3
|
+
* REFACTORED: Uses the centralized SmartMoneyScorer for consistent classification.
|
|
4
4
|
*/
|
|
5
5
|
class DailyDnaFilter {
|
|
6
6
|
constructor() {
|
|
@@ -10,7 +10,7 @@ class DailyDnaFilter {
|
|
|
10
10
|
static getMetadata() {
|
|
11
11
|
return {
|
|
12
12
|
type: 'standard',
|
|
13
|
-
rootDataDependencies: ['history'],
|
|
13
|
+
rootDataDependencies: ['history', 'portfolio', 'price'], // Added portfolio/price for hybrid scoring
|
|
14
14
|
isHistorical: false,
|
|
15
15
|
userType: 'all',
|
|
16
16
|
category: 'gauss'
|
|
@@ -33,30 +33,17 @@ class DailyDnaFilter {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
process(context) {
|
|
36
|
-
|
|
37
|
-
const {
|
|
38
|
-
|
|
39
|
-
// 1. Get strict daily history data
|
|
40
|
-
const historyDoc = history.getDailyHistory(user);
|
|
41
|
-
|
|
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;
|
|
36
|
+
// Use the centralized Intelligence Engine injected via Context
|
|
37
|
+
const { SmartMoneyScorer } = context.math;
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
// CORRECTED LINE: uses summary.avgProfitPct
|
|
53
|
-
const avgWin = summary.avgProfitPct;
|
|
54
|
-
const avgLoss = Math.abs(summary.avgLossPct);
|
|
55
|
-
|
|
56
|
-
const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
|
|
39
|
+
if (!SmartMoneyScorer) return;
|
|
40
|
+
|
|
41
|
+
const result = SmartMoneyScorer.scoreHybrid(context);
|
|
42
|
+
const score = result.totalScore;
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
// Filter out users with insufficient data (Score 0 or Neutral with no method)
|
|
45
|
+
if (score > 0) {
|
|
46
|
+
this.allUserSkills.push([context.user.id, score]);
|
|
60
47
|
}
|
|
61
48
|
}
|
|
62
49
|
|
|
@@ -64,8 +51,10 @@ class DailyDnaFilter {
|
|
|
64
51
|
const totalUsers = this.allUserSkills.length;
|
|
65
52
|
if (totalUsers === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: 0, cohort_size: 0 };
|
|
66
53
|
|
|
54
|
+
// Sort Descending (Highest Score First)
|
|
67
55
|
this.allUserSkills.sort((a, b) => b[1] - a[1]);
|
|
68
56
|
|
|
57
|
+
// Top 20% Smart, Bottom 20% Dumb
|
|
69
58
|
const cohortSize = Math.floor(totalUsers * 0.20);
|
|
70
59
|
if (cohortSize === 0) return { smart_cohort_ids: [], dumb_cohort_ids: [], total_users_analyzed: totalUsers, cohort_size: 0 };
|
|
71
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GEM Product Line (Pass 1)
|
|
3
|
-
* REFACTORED:
|
|
3
|
+
* REFACTORED: Uses SmartMoneyScorer for advanced hybrid classification.
|
|
4
4
|
*/
|
|
5
5
|
class CohortSkillDefinition {
|
|
6
6
|
constructor() { this.userScores = new Map(); }
|
|
@@ -21,7 +21,8 @@ class CohortSkillDefinition {
|
|
|
21
21
|
static getMetadata() {
|
|
22
22
|
return {
|
|
23
23
|
type: 'standard',
|
|
24
|
-
|
|
24
|
+
// Updated dependencies to support hybrid scoring
|
|
25
|
+
rootDataDependencies: ['history', 'portfolio', 'price'],
|
|
25
26
|
isHistorical: false,
|
|
26
27
|
userType: 'all',
|
|
27
28
|
category: 'gem'
|
|
@@ -31,26 +32,19 @@ class CohortSkillDefinition {
|
|
|
31
32
|
static getDependencies() { return []; }
|
|
32
33
|
|
|
33
34
|
process(context) {
|
|
34
|
-
|
|
35
|
-
const {
|
|
35
|
+
// Use the centralized engine injected via context
|
|
36
|
+
const { SmartMoneyScorer } = context.math;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
const historyDoc = history.getDailyHistory(user);
|
|
39
|
-
|
|
40
|
-
// 2. Get strict summary DTO
|
|
41
|
-
const summary = history.getSummary(historyDoc);
|
|
38
|
+
if (!SmartMoneyScorer) return;
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
const result = SmartMoneyScorer.scoreHybrid(context);
|
|
41
|
+
const score = result.totalScore;
|
|
44
42
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const skillScore = expectancy * Math.log10(Math.max(1, summary.totalTrades));
|
|
52
|
-
|
|
53
|
-
if (isFinite(skillScore)) this.userScores.set(user.id, skillScore);
|
|
43
|
+
// GEM requires a minimum threshold of activity usually,
|
|
44
|
+
// but SmartMoneyScorer handles low-data cases by returning neutral/0.
|
|
45
|
+
if (score > 0) {
|
|
46
|
+
this.userScores.set(context.user.id, score);
|
|
47
|
+
}
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
getResult() {
|
package/package.json
CHANGED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
Scanning 92 files...
|
|
2
|
-
|
|
3
|
-
--- Files MISSING getSchema() ---
|
|
4
|
-
|
|
5
|
-
--- Files WITH getSchema() ---
|
|
6
|
-
capitulation\asset-volatility-estimator.js
|
|
7
|
-
capitulation\retail-capitulation-risk-forecast.js
|
|
8
|
-
core\asset-cost-basis-profile.js
|
|
9
|
-
core\asset-pnl-status.js
|
|
10
|
-
core\asset-position-size.js
|
|
11
|
-
core\average-daily-pnl-all-users.js
|
|
12
|
-
core\average-daily-pnl-per-sector.js
|
|
13
|
-
core\average-daily-pnl-per-stock.js
|
|
14
|
-
core\average-daily-position-pnl.js
|
|
15
|
-
core\crowd-cost-basis.js
|
|
16
|
-
core\holding-duration-per-asset.js
|
|
17
|
-
core\insights-daily-bought-vs-sold-count.js
|
|
18
|
-
core\insights-daily-ownership-delta.js
|
|
19
|
-
core\insights-sentimet-per-stock.js
|
|
20
|
-
core\insights-total-long-per-sector.js
|
|
21
|
-
core\Insights-total-long-per-stock.js
|
|
22
|
-
core\insights-total-positions-held.js
|
|
23
|
-
core\instrument-price-change-1d.js
|
|
24
|
-
core\instrument-price-momentum-20d.js
|
|
25
|
-
core\leverage-divergence.js
|
|
26
|
-
core\liquidation-cascade.js
|
|
27
|
-
core\long-position-per-stock.js
|
|
28
|
-
core\overall-holding-duration.js
|
|
29
|
-
core\overall-profitability-ratio.js
|
|
30
|
-
core\ownership-vs-performance-ytd.js
|
|
31
|
-
core\ownership-vs-volatility.js
|
|
32
|
-
core\platform-buy-sell-sentiment.js
|
|
33
|
-
core\platform-daily-bought-vs-sold-count.js
|
|
34
|
-
core\platform-daily-ownership-delta.js
|
|
35
|
-
core\platform-ownership-per-sector.js
|
|
36
|
-
core\platform-total-positions-held.js
|
|
37
|
-
core\pnl-distribution-per-stock.js
|
|
38
|
-
core\price-metrics.js
|
|
39
|
-
core\profitability-ratio-per-sector.js
|
|
40
|
-
core\profitability-ratio-per-stock.js
|
|
41
|
-
core\profitability-skew-per-stock.js
|
|
42
|
-
core\profitable-and-unprofitable-status.js
|
|
43
|
-
core\sentiment-per-stock.js
|
|
44
|
-
core\short-interest-growth.js
|
|
45
|
-
core\short-position-per-stock.js
|
|
46
|
-
core\social-activity-aggregation.js
|
|
47
|
-
core\social-asset-posts-trend.js
|
|
48
|
-
core\social-event-correlation.js
|
|
49
|
-
core\social-sentiment-aggregation.js
|
|
50
|
-
core\social-top-mentioned-words.js
|
|
51
|
-
core\social-topic-interest-evolution.js
|
|
52
|
-
core\social-topic-sentiment-matrix.js
|
|
53
|
-
core\social-word-mentions-trend.js
|
|
54
|
-
core\speculator-asset-sentiment.js
|
|
55
|
-
core\speculator-danger-zone.js
|
|
56
|
-
core\speculator-distance-to-stop-loss-per-leverage.js
|
|
57
|
-
core\speculator-distance-to-tp-per-leverage.js
|
|
58
|
-
core\speculator-entry-distance-to-sl-per-leverage.js
|
|
59
|
-
core\speculator-entry-distance-to-tp-per-leverage.js
|
|
60
|
-
core\speculator-leverage-per-asset.js
|
|
61
|
-
core\speculator-leverage-per-sector.js
|
|
62
|
-
core\speculator-risk-reward-ratio-per-asset.js
|
|
63
|
-
core\speculator-stop-loss-distance-by-sector-short-long-breakdown.js
|
|
64
|
-
core\speculator-stop-loss-distance-by-ticker-short-long-breakdown.js
|
|
65
|
-
core\speculator-stop-loss-per-asset.js
|
|
66
|
-
core\speculator-take-profit-per-asset.js
|
|
67
|
-
core\speculator-tsl-per-asset.js
|
|
68
|
-
core\total-long-figures.js
|
|
69
|
-
core\total-long-per-sector.js
|
|
70
|
-
core\total-short-figures.js
|
|
71
|
-
core\total-short-per-sector.js
|
|
72
|
-
core\trending-ownership-momentum.js
|
|
73
|
-
core\user-history-reconstructor.js
|
|
74
|
-
core\users-processed.js
|
|
75
|
-
gauss\cohort-capital-flow.js
|
|
76
|
-
gauss\cohort-definer.js
|
|
77
|
-
gauss\daily-dna-filter.js
|
|
78
|
-
gauss\gauss-divergence-signal.js
|
|
79
|
-
gem\cohort-momentum-state.js
|
|
80
|
-
gem\cohort-skill-definition.js
|
|
81
|
-
gem\platform-conviction-divergence.js
|
|
82
|
-
gem\quant-skill-alpha-signal.js
|
|
83
|
-
gem\skilled-cohort-flow.js
|
|
84
|
-
gem\skilled-unskilled-divergence.js
|
|
85
|
-
gem\unskilled-cohort-flow.js
|
|
86
|
-
ghost-book\cost-basis-density.js
|
|
87
|
-
ghost-book\liquidity-vacuum.js
|
|
88
|
-
ghost-book\retail-gamma-exposure.js
|
|
89
|
-
helix\helix-contrarian-signal.js
|
|
90
|
-
helix\herd-consensus-score.js
|
|
91
|
-
helix\winner-loser-flow.js
|
|
92
|
-
predicative-alpha\cognitive-dissonance.js
|
|
93
|
-
predicative-alpha\diamond-hand-fracture.js
|
|
94
|
-
predicative-alpha\mimetic-latency.js
|
|
95
|
-
pyro\risk-appetite-index.js
|
|
96
|
-
pyro\squeeze-potential.js
|
|
97
|
-
pyro\volatility-signal.js
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const rootDir = __dirname;
|
|
5
|
-
|
|
6
|
-
function getAllFiles(dirPath, arrayOfFiles) {
|
|
7
|
-
const files = fs.readdirSync(dirPath);
|
|
8
|
-
|
|
9
|
-
arrayOfFiles = arrayOfFiles || [];
|
|
10
|
-
|
|
11
|
-
files.forEach(function (file) {
|
|
12
|
-
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
|
|
13
|
-
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
|
|
14
|
-
} else {
|
|
15
|
-
if (file.endsWith('.js') && file !== 'audit_schemas.js') {
|
|
16
|
-
arrayOfFiles.push(path.join(dirPath, "/", file));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return arrayOfFiles;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const files = getAllFiles(rootDir);
|
|
25
|
-
let missingSchema = [];
|
|
26
|
-
let hasSchema = [];
|
|
27
|
-
|
|
28
|
-
const output = [];
|
|
29
|
-
output.push(`Scanning ${files.length} files...`);
|
|
30
|
-
|
|
31
|
-
files.forEach(file => {
|
|
32
|
-
const content = fs.readFileSync(file, 'utf8');
|
|
33
|
-
if (content.includes('static getSchema()')) {
|
|
34
|
-
hasSchema.push(file);
|
|
35
|
-
} else {
|
|
36
|
-
missingSchema.push(file);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
output.push('\n--- Files MISSING getSchema() ---');
|
|
41
|
-
missingSchema.forEach(f => output.push(path.relative(rootDir, f)));
|
|
42
|
-
|
|
43
|
-
output.push('\n--- Files WITH getSchema() ---');
|
|
44
|
-
hasSchema.forEach(f => output.push(path.relative(rootDir, f)));
|
|
45
|
-
|
|
46
|
-
fs.writeFileSync(path.join(rootDir, 'audit_results.txt'), output.join('\n'));
|
|
47
|
-
console.log('Audit complete. Results written to audit_results.txt');
|