aiden-shared-calculations-unified 1.0.108 → 1.0.110
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/insights-daily-bought-vs-sold-count.js +20 -15
- package/calculations/core/insights-daily-ownership-delta.js +12 -10
- package/calculations/core/instrument-price-change-1d.js +16 -31
- package/calculations/core/instrument-price-momentum-20d.js +16 -26
- package/calculations/core/ownership-vs-performance-ytd.js +15 -52
- package/calculations/core/ownership-vs-volatility.js +27 -36
- package/calculations/core/platform-daily-bought-vs-sold-count.js +28 -26
- package/calculations/core/platform-daily-ownership-delta.js +28 -31
- package/calculations/core/price-metrics.js +15 -54
- package/calculations/core/short-interest-growth.js +6 -13
- package/calculations/core/trending-ownership-momentum.js +16 -28
- package/calculations/core/user-history-reconstructor.js +48 -49
- package/calculations/gauss/cohort-capital-flow.js +34 -71
- package/calculations/gauss/cohort-definer.js +61 -142
- package/calculations/gem/cohort-momentum-state.js +27 -77
- package/calculations/gem/skilled-cohort-flow.js +36 -114
- package/calculations/gem/unskilled-cohort-flow.js +36 -112
- package/calculations/ghost-book/retail-gamma-exposure.js +14 -61
- package/calculations/helix/herd-consensus-score.js +27 -90
- package/calculations/helix/winner-loser-flow.js +21 -90
- package/calculations/predicative-alpha/cognitive-dissonance.js +25 -91
- package/calculations/predicative-alpha/diamond-hand-fracture.js +17 -72
- package/calculations/predicative-alpha/mimetic-latency.js +21 -100
- package/package.json +1 -1
|
@@ -1,108 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
|
-
* REFACTORED: Uses context.math.extract.
|
|
4
|
-
*/
|
|
5
1
|
class WinnerLoserFlow {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
this.tickerMap = null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static getMetadata() {
|
|
12
|
-
return {
|
|
13
|
-
type: 'standard',
|
|
14
|
-
rootDataDependencies: ['portfolio'],
|
|
15
|
-
isHistorical: true,
|
|
16
|
-
userType: 'all',
|
|
17
|
-
category: 'helix'
|
|
18
|
-
};
|
|
19
|
-
}s
|
|
20
|
-
|
|
2
|
+
constructor() { this.assetFlows = new Map(); this.tickerMap = null; }
|
|
3
|
+
static getMetadata() { return { type: 'standard', rootDataDependencies: ['portfolio'], isHistorical: true, userType: 'all', category: 'helix' }; }
|
|
21
4
|
static getDependencies() { return []; }
|
|
22
5
|
|
|
23
6
|
static getSchema() {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
"properties": {
|
|
27
|
-
"net_winner_flow": { "type": "number" },
|
|
28
|
-
"net_loser_flow": { "type": "number" },
|
|
29
|
-
"raw_counts": { "type": "object" }
|
|
30
|
-
},
|
|
31
|
-
"required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
|
|
32
|
-
};
|
|
33
|
-
return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
_getHoldings(positions, extract) {
|
|
37
|
-
const map = new Map();
|
|
38
|
-
for (const pos of positions) {
|
|
39
|
-
const id = extract.getInstrumentId(pos);
|
|
40
|
-
if (id) map.set(id, { pnl: extract.getNetProfit(pos) });
|
|
41
|
-
}
|
|
42
|
-
return map;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
_initAsset(ticker) {
|
|
46
|
-
if (!this.assetFlows.has(ticker)) {
|
|
47
|
-
this.assetFlows.set(ticker, { winners_joined: 0, winners_left: 0, losers_joined: 0, losers_left: 0 });
|
|
48
|
-
}
|
|
7
|
+
const s = { "type": "object", "properties": { "net_winner_flow": { "type": ["number", "null"] }, "net_loser_flow": { "type": ["number", "null"] }, "total_winners_today": { "type": "number" }, "total_losers_today": { "type": "number" } }, "required": ["net_winner_flow", "net_loser_flow", "total_winners_today", "total_losers_today"] };
|
|
8
|
+
return { "type": "object", "patternProperties": { "^.*$": s } };
|
|
49
9
|
}
|
|
50
10
|
|
|
51
11
|
process(context) {
|
|
52
|
-
const { user, mappings, math } = context;
|
|
53
|
-
const { extract } = math;
|
|
12
|
+
const { user, mappings, math } = context; const { extract } = math;
|
|
54
13
|
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
55
|
-
|
|
56
|
-
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
57
14
|
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const isLoser = tPnl < 0;
|
|
71
|
-
if (!isWinner && !isLoser) continue;
|
|
72
|
-
|
|
73
|
-
const heldYesterday = yHoldings.has(instId);
|
|
74
|
-
const holdsToday = tHoldings.has(instId);
|
|
75
|
-
if (heldYesterday === holdsToday) continue;
|
|
76
|
-
|
|
77
|
-
this._initAsset(ticker);
|
|
78
|
-
const stats = this.assetFlows.get(ticker);
|
|
79
|
-
|
|
80
|
-
if (isWinner) {
|
|
81
|
-
if (holdsToday && !heldYesterday) stats.winners_joined++;
|
|
82
|
-
if (!holdsToday && heldYesterday) stats.winners_left++;
|
|
83
|
-
}
|
|
84
|
-
if (isLoser) {
|
|
85
|
-
if (holdsToday && !heldYesterday) stats.losers_joined++;
|
|
86
|
-
if (!holdsToday && heldYesterday) stats.losers_left++;
|
|
15
|
+
const yPos = user.portfolio.yesterday ? extract.getPositions(user.portfolio.yesterday, user.type) : [];
|
|
16
|
+
const yH = new Map(yPos.map(p => [extract.getInstrumentId(p), extract.getNetProfit(p)]));
|
|
17
|
+
const tH = new Map(tPos.map(p => [extract.getInstrumentId(p), extract.getNetProfit(p)]));
|
|
18
|
+
const ids = new Set([...yH.keys(), ...tH.keys()]);
|
|
19
|
+
for (const id of ids) {
|
|
20
|
+
const tick = this.tickerMap[id]; if (!tick) continue;
|
|
21
|
+
if (!this.assetFlows.has(tick)) this.assetFlows.set(tick, { wJ: 0, wL: 0, lJ: 0, lL: 0, wT: 0, lT: 0, hasY: !!user.portfolio.yesterday });
|
|
22
|
+
const s = this.assetFlows.get(tick), tPnl = tH.get(id), yPnl = yH.get(id);
|
|
23
|
+
if (tH.has(id)) { if (tPnl > 0) s.wT++; if (tPnl < 0) s.lT++; }
|
|
24
|
+
if (user.portfolio.yesterday) {
|
|
25
|
+
if (tH.has(id) && !yH.has(id)) { if (tPnl > 0) s.wJ++; if (tPnl < 0) s.lJ++; }
|
|
26
|
+
if (!tH.has(id) && yH.has(id)) { if (yPnl > 0) s.wL++; if (yPnl < 0) s.lL++; }
|
|
87
27
|
}
|
|
88
28
|
}
|
|
89
29
|
}
|
|
90
30
|
|
|
91
31
|
async getResult() {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
result[ticker] = {
|
|
95
|
-
net_winner_flow: data.winners_joined - data.winners_left,
|
|
96
|
-
net_loser_flow: data.losers_joined - data.losers_left,
|
|
97
|
-
raw_counts: data
|
|
98
|
-
};
|
|
32
|
+
const res = {}; for (const [tick, d] of this.assetFlows.entries()) {
|
|
33
|
+
res[tick] = { net_winner_flow: d.hasY ? d.wJ - d.wL : null, net_loser_flow: d.hasY ? d.lJ - d.lL : null, total_winners_today: d.wT, total_losers_today: d.lT };
|
|
99
34
|
}
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
reset() {
|
|
104
|
-
this.assetFlows.clear();
|
|
105
|
-
this.tickerMap = null;
|
|
35
|
+
return res;
|
|
106
36
|
}
|
|
37
|
+
reset() { this.assetFlows.clear(); this.tickerMap = null; }
|
|
107
38
|
}
|
|
108
39
|
module.exports = WinnerLoserFlow;
|
|
@@ -1,105 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Cognitive Dissonance Arbitrage (CDA) v2.2
|
|
3
|
-
* Clean: System automatically handles sharding.
|
|
4
|
-
*/
|
|
5
1
|
class CognitiveDissonance {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static getMetadata() {
|
|
12
|
-
return {
|
|
13
|
-
type: 'meta',
|
|
14
|
-
rootDataDependencies: [],
|
|
15
|
-
isHistorical: true,
|
|
16
|
-
userType: 'aggregate'
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static getDependencies() {
|
|
21
|
-
return ['social-topic-sentiment-matrix', 'skilled-cohort-flow', 'instrument-price-change-1d'];
|
|
22
|
-
}
|
|
2
|
+
constructor() { this.cdaResults = {}; this.alpha = 2 / (20 + 1); }
|
|
3
|
+
static getMetadata() { return { type: 'meta', rootDataDependencies: [], isHistorical: true, userType: 'aggregate' }; }
|
|
4
|
+
static getDependencies() { return ['social-topic-sentiment-matrix', 'skilled-cohort-flow', 'instrument-price-change-1d']; }
|
|
23
5
|
|
|
24
6
|
static getSchema() {
|
|
25
|
-
|
|
26
|
-
"type": "object",
|
|
27
|
-
"properties": {
|
|
28
|
-
"cda_score": { "type": "number" },
|
|
29
|
-
"regime": { "type": "string" },
|
|
30
|
-
"z_sentiment": { "type": "number" },
|
|
31
|
-
"z_flow": { "type": "number" },
|
|
32
|
-
"price_confirmation": { "type": "boolean" },
|
|
33
|
-
"_state": { "type": "object" }
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
|
|
7
|
+
return { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "cda_score": { "type": ["number", "null"] }, "regime": { "type": "string" }, "_state": { "type": "object" } } } } };
|
|
37
8
|
}
|
|
38
9
|
|
|
39
10
|
process(context) {
|
|
40
11
|
const { computed, previousComputed, math } = context;
|
|
41
|
-
const { signals
|
|
42
|
-
|
|
43
|
-
const tickers = SignalPrimitives.getUnionKeys(computed, CognitiveDissonance.getDependencies());
|
|
44
|
-
|
|
12
|
+
const { signals, TimeSeries } = math;
|
|
13
|
+
const tickers = signals.getUnionKeys(computed, CognitiveDissonance.getDependencies());
|
|
45
14
|
for (const ticker of tickers) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const interaction = zSentiment * zFlow;
|
|
65
|
-
let cdaScore = 0;
|
|
66
|
-
let regime = "NEUTRAL";
|
|
67
|
-
let priceConfirmation = false;
|
|
68
|
-
|
|
69
|
-
const disagree = (Math.sign(zSentiment) !== Math.sign(zFlow));
|
|
70
|
-
const loudVoice = Math.abs(zSentiment) > 1.65;
|
|
71
|
-
const activeHands = Math.abs(zFlow) > 0.5;
|
|
72
|
-
|
|
73
|
-
if (disagree && loudVoice && activeHands) {
|
|
74
|
-
cdaScore = -interaction;
|
|
75
|
-
const priceAgreesWithSentiment = (Math.sign(priceChange) === Math.sign(zSentiment));
|
|
76
|
-
const priceIsFlat = Math.abs(priceChange) < 0.5;
|
|
77
|
-
|
|
78
|
-
if (priceAgreesWithSentiment && !priceIsFlat) {
|
|
79
|
-
priceConfirmation = true;
|
|
80
|
-
regime = "CONVERGENT";
|
|
81
|
-
cdaScore = 0;
|
|
82
|
-
} else {
|
|
83
|
-
regime = (zSentiment > 0) ? "BEARISH_DISSONANCE" : "BULLISH_DISSONANCE";
|
|
84
|
-
}
|
|
85
|
-
} else if (!disagree && loudVoice) {
|
|
86
|
-
regime = "CONVERGENT";
|
|
15
|
+
const sent = signals.getMetric(computed, 'social-topic-sentiment-matrix', ticker, 'net_sentiment', 0);
|
|
16
|
+
const flow = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
|
|
17
|
+
const pChg = signals.getMetric(computed, 'instrument-price-change-1d', ticker, 'change_1d_pct', 0);
|
|
18
|
+
const prev = signals.getPreviousState(previousComputed, 'cognitive-dissonance', ticker);
|
|
19
|
+
const pS = prev ? prev._state : { sent_mean: sent, sent_var: 1, flow_mean: flow, flow_var: 1 };
|
|
20
|
+
const sS = TimeSeries.updateEMAState(sent, { mean: pS.sent_mean, variance: pS.sent_var }, this.alpha);
|
|
21
|
+
const fS = TimeSeries.updateEMAState(flow, { mean: pS.flow_mean, variance: pS.flow_var }, this.alpha);
|
|
22
|
+
const sDev = Math.sqrt(sS.variance), fDev = Math.sqrt(fS.variance);
|
|
23
|
+
const zS = sDev > 0.001 ? (sent - sS.mean) / sDev : 0, zF = fDev > 0.001 ? (flow - fS.mean) / fDev : 0;
|
|
24
|
+
let cda = null, regime = "WARM_UP";
|
|
25
|
+
if (prev) {
|
|
26
|
+
const interact = zS * zF; cda = 0; regime = "NEUTRAL";
|
|
27
|
+
const disagree = Math.sign(zS) !== Math.sign(zF), loud = Math.abs(zS) > 1.65, active = Math.abs(zF) > 0.5;
|
|
28
|
+
if (disagree && loud && active) {
|
|
29
|
+
cda = -interact;
|
|
30
|
+
if (Math.sign(pChg) === Math.sign(zS) && Math.abs(pChg) > 0.5) { cda = 0; regime = "CONVERGENT"; }
|
|
31
|
+
else regime = zS > 0 ? "BEARISH_DISSONANCE" : "BULLISH_DISSONANCE";
|
|
32
|
+
} else if (!disagree && loud) regime = "CONVERGENT";
|
|
87
33
|
}
|
|
88
|
-
|
|
89
|
-
this.cdaResults[ticker] = {
|
|
90
|
-
cda_score: Number(cdaScore.toFixed(4)),
|
|
91
|
-
regime: regime,
|
|
92
|
-
z_sentiment: Number(zSentiment.toFixed(4)),
|
|
93
|
-
z_flow: Number(zFlow.toFixed(4)),
|
|
94
|
-
price_confirmation: priceConfirmation,
|
|
95
|
-
_state: {
|
|
96
|
-
sent_mean: sentStats.mean, sent_var: sentStats.variance,
|
|
97
|
-
flow_mean: flowStats.mean, flow_var: flowStats.variance
|
|
98
|
-
}
|
|
99
|
-
};
|
|
34
|
+
this.cdaResults[ticker] = { cda_score: (cda !== null && isFinite(cda)) ? Number(cda.toFixed(4)) : null, regime, _state: { sent_mean: sS.mean, sent_var: sS.variance, flow_mean: fS.mean, flow_var: fS.variance } };
|
|
100
35
|
}
|
|
101
36
|
}
|
|
102
|
-
|
|
103
37
|
async getResult() { return this.cdaResults; }
|
|
104
38
|
reset() { this.cdaResults = {}; }
|
|
105
39
|
}
|
|
@@ -1,88 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Diamond Hand Fracture Coefficient (DHFC) v2.2
|
|
3
|
-
* REFACTORED: Adheres to System Architecture.
|
|
4
|
-
*/
|
|
5
1
|
class DiamondHandFracture {
|
|
6
2
|
constructor() { this.fractureResults = {}; }
|
|
7
|
-
|
|
8
|
-
static
|
|
9
|
-
return {
|
|
10
|
-
type: 'meta',
|
|
11
|
-
rootDataDependencies: [],
|
|
12
|
-
isHistorical: true,
|
|
13
|
-
userType: 'aggregate'
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
static getDependencies() {
|
|
18
|
-
return ['holding-duration-per-asset', 'average-daily-pnl-per-stock'];
|
|
19
|
-
}
|
|
3
|
+
static getMetadata() { return { type: 'meta', rootDataDependencies: [], isHistorical: true, userType: 'aggregate' }; }
|
|
4
|
+
static getDependencies() { return ['holding-duration-per-asset', 'average-daily-pnl-per-stock']; }
|
|
20
5
|
|
|
21
6
|
static getSchema() {
|
|
22
|
-
|
|
23
|
-
"type": "object",
|
|
24
|
-
"properties": {
|
|
25
|
-
"fracture_velocity": { "type": "number" },
|
|
26
|
-
"capitulation_score": { "type": "number" },
|
|
27
|
-
"regime": { "type": "string" },
|
|
28
|
-
"expected_duration_hours": { "type": "number" },
|
|
29
|
-
"actual_duration_hours": { "type": "number" }
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
|
|
7
|
+
return { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "fracture_velocity": { "type": ["number", "null"] }, "regime": { "type": "string" }, "actual_duration_hours": { "type": "number" } } } } };
|
|
33
8
|
}
|
|
34
9
|
|
|
35
10
|
process(context) {
|
|
36
11
|
const { computed, previousComputed, math } = context;
|
|
37
|
-
const { signals
|
|
38
|
-
|
|
39
|
-
const tickers = SignalPrimitives.getUnionKeys(computed, DiamondHandFracture.getDependencies());
|
|
40
|
-
|
|
12
|
+
const { signals } = math;
|
|
13
|
+
const tickers = signals.getUnionKeys(computed, DiamondHandFracture.getDependencies());
|
|
41
14
|
for (const ticker of tickers) {
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const prevData = SignalPrimitives.getPreviousState(previousComputed, 'holding-duration-per-asset', ticker);
|
|
50
|
-
const H_prev = prevData ? (prevData.avg_duration_hours || 0) : 0;
|
|
51
|
-
const OI_prev = prevData ? (prevData.count || 0) : 0;
|
|
52
|
-
|
|
53
|
-
// Init Check
|
|
54
|
-
if (H_prev === 0 || OI_prev === 0) {
|
|
55
|
-
this.fractureResults[ticker] = { fracture_velocity: 0, capitulation_score: 0, regime: "STABLE", expected_duration_hours: H_t, actual_duration_hours: H_t };
|
|
15
|
+
const H_t = signals.getMetric(computed, 'holding-duration-per-asset', ticker, 'avg_duration_hours', 0);
|
|
16
|
+
const OI_t = signals.getMetric(computed, 'holding-duration-per-asset', ticker, 'count', 0);
|
|
17
|
+
const pnl = signals.getMetric(computed, 'average-daily-pnl-per-stock', ticker, 'avg_daily_pnl_pct', 0);
|
|
18
|
+
const prev = signals.getPreviousState(previousComputed, 'holding-duration-per-asset', ticker);
|
|
19
|
+
const H_p = prev?.avg_duration_hours || 0, OI_p = prev?.count || 0;
|
|
20
|
+
if (H_p === 0 || OI_p === 0) {
|
|
21
|
+
this.fractureResults[ticker] = { fracture_velocity: null, capitulation_score: 0, regime: "COLD_START", expected_duration_hours: H_t, actual_duration_hours: H_t };
|
|
56
22
|
continue;
|
|
57
23
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
const H_expected = H_prev * dilutionFactor;
|
|
64
|
-
const phi = H_expected - H_t;
|
|
65
|
-
const phi_normalized = (H_prev > 0) ? (phi / H_prev) * 100 : 0;
|
|
66
|
-
|
|
67
|
-
// 4. Capitulation Score
|
|
68
|
-
const painFactor = (avgPnl < 0) ? Math.abs(avgPnl) : 0;
|
|
69
|
-
const capitulationScore = phi_normalized * painFactor;
|
|
70
|
-
|
|
71
|
-
let regime = "STABLE";
|
|
72
|
-
if (phi_normalized > 5.0) regime = "FRACTURE";
|
|
73
|
-
if (capitulationScore > 20.0) regime = "CAPITULATION";
|
|
74
|
-
if (phi_normalized < -5.0) regime = "ACCUMULATION";
|
|
75
|
-
|
|
76
|
-
this.fractureResults[ticker] = {
|
|
77
|
-
fracture_velocity: Number(phi_normalized.toFixed(4)),
|
|
78
|
-
capitulation_score: Number(capitulationScore.toFixed(4)),
|
|
79
|
-
regime: regime,
|
|
80
|
-
expected_duration_hours: Number(H_expected.toFixed(2)),
|
|
81
|
-
actual_duration_hours: Number(H_t.toFixed(2))
|
|
82
|
-
};
|
|
24
|
+
let dil = (OI_t > 0) ? (OI_p / OI_t) : 1.0; if (dil > 2.0) dil = 1.0;
|
|
25
|
+
const H_exp = H_p * dil, phi = H_exp - H_t, phi_n = (H_p > 0) ? (phi / H_p) * 100 : 0;
|
|
26
|
+
const cap = (pnl < 0) ? Math.abs(pnl) * phi_n : 0;
|
|
27
|
+
let reg = "STABLE"; if (phi_n > 5.0) reg = "FRACTURE"; if (cap > 20.0) reg = "CAPITULATION"; if (phi_n < -5.0) reg = "ACCUMULATION";
|
|
28
|
+
this.fractureResults[ticker] = { fracture_velocity: Number(phi_n.toFixed(4)), capitulation_score: Number(cap.toFixed(4)), regime: reg, expected_duration_hours: Number(H_exp.toFixed(2)), actual_duration_hours: Number(H_t.toFixed(2)) };
|
|
83
29
|
}
|
|
84
30
|
}
|
|
85
|
-
|
|
86
31
|
getResult() { return this.fractureResults; }
|
|
87
32
|
reset() { this.fractureResults = {}; }
|
|
88
33
|
}
|
|
@@ -1,116 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Mimetic Latency Oscillator (MLO) v2.3
|
|
3
|
-
* FIXED: Safe state access to prevent 'undefined' property errors.
|
|
4
|
-
*/
|
|
5
1
|
class MimeticLatencyOscillator {
|
|
6
|
-
constructor() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.maxLag = 10;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
static getMetadata() {
|
|
13
|
-
return {
|
|
14
|
-
type: 'meta',
|
|
15
|
-
rootDataDependencies: [],
|
|
16
|
-
isHistorical: true,
|
|
17
|
-
userType: 'aggregate'
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static getDependencies() {
|
|
22
|
-
return ['skilled-cohort-flow', 'herd-consensus-score'];
|
|
23
|
-
}
|
|
2
|
+
constructor() { this.mloResults = {}; this.windowSize = 30; this.maxLag = 10; }
|
|
3
|
+
static getMetadata() { return { type: 'meta', rootDataDependencies: [], isHistorical: true, userType: 'aggregate' }; }
|
|
4
|
+
static getDependencies() { return ['skilled-cohort-flow', 'herd-consensus-score']; }
|
|
24
5
|
|
|
25
6
|
static getSchema() {
|
|
26
|
-
|
|
27
|
-
"type": "object",
|
|
28
|
-
"properties": {
|
|
29
|
-
"lag_days": { "type": "integer" },
|
|
30
|
-
"correlation_strength": { "type": "number" },
|
|
31
|
-
"regime": { "type": "string" },
|
|
32
|
-
"_state": { "type": "object" }
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
return { "type": "object", "patternProperties": { "^.*$": metricSchema } };
|
|
7
|
+
return { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "lag_days": { "type": ["number", "null"] }, "regime": { "type": "string" }, "_state": { "type": "object" } } } } };
|
|
36
8
|
}
|
|
37
9
|
|
|
38
10
|
process(context) {
|
|
39
11
|
const { computed, previousComputed, math } = context;
|
|
40
|
-
const { signals
|
|
41
|
-
|
|
42
|
-
const tickers = SignalPrimitives.getUnionKeys(computed, MimeticLatencyOscillator.getDependencies());
|
|
43
|
-
|
|
12
|
+
const { signals, TimeSeries } = math;
|
|
13
|
+
const tickers = signals.getUnionKeys(computed, MimeticLatencyOscillator.getDependencies());
|
|
44
14
|
for (const ticker of tickers) {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const prevFlow = (prevState && prevState.last_flow !== undefined) ? prevState.last_flow : 0;
|
|
55
|
-
const prevHerd = (prevState && prevState.last_herd !== undefined) ? prevState.last_herd : 0;
|
|
56
|
-
const flowBuffer = (prevState && Array.isArray(prevState.flow_buffer)) ? [...prevState.flow_buffer] : [];
|
|
57
|
-
const herdBuffer = (prevState && Array.isArray(prevState.herd_buffer)) ? [...prevState.herd_buffer] : [];
|
|
58
|
-
|
|
59
|
-
// 3. Calculate Detrended Delta
|
|
60
|
-
const flowDelta = rawFlow - prevFlow;
|
|
61
|
-
const herdDelta = rawHerd - prevHerd;
|
|
62
|
-
|
|
63
|
-
// 4. Update Buffers
|
|
64
|
-
flowBuffer.push(flowDelta);
|
|
65
|
-
herdBuffer.push(herdDelta);
|
|
66
|
-
|
|
67
|
-
if (flowBuffer.length > this.windowSize) flowBuffer.shift();
|
|
68
|
-
if (herdBuffer.length > this.windowSize) herdBuffer.shift();
|
|
69
|
-
|
|
70
|
-
// 5. Lagged Cross-Correlation
|
|
71
|
-
let maxCorr = -1.0;
|
|
72
|
-
let bestLag = 0;
|
|
73
|
-
let regime = "WARM_UP";
|
|
74
|
-
|
|
75
|
-
if (flowBuffer.length >= 15) {
|
|
15
|
+
const flow = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'net_flow_pct', 0);
|
|
16
|
+
const herd = signals.getMetric(computed, 'herd-consensus-score', ticker, 'herd_conviction_score', 0);
|
|
17
|
+
const prev = signals.getPreviousState(previousComputed, 'mimetic-latency', ticker);
|
|
18
|
+
const pS = prev?._state || null;
|
|
19
|
+
const pF = pS?.last_flow || 0, pH = pS?.last_herd || 0, fBuf = [...(pS?.flow_buffer || [])], hBuf = [...(pS?.herd_buffer || [])];
|
|
20
|
+
fBuf.push(flow - pF); hBuf.push(herd - pH);
|
|
21
|
+
if (fBuf.length > this.windowSize) { fBuf.shift(); hBuf.shift(); }
|
|
22
|
+
let maxCorr = -1.0, bestLag = null, regime = "WARM_UP";
|
|
23
|
+
if (fBuf.length >= 15) {
|
|
76
24
|
for (let k = 0; k <= this.maxLag; k++) {
|
|
77
|
-
const len =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const slicedFlow = flowBuffer.slice(0, len - k);
|
|
81
|
-
const slicedHerd = herdBuffer.slice(k, len);
|
|
82
|
-
|
|
83
|
-
const r = TimeSeries.pearsonCorrelation(slicedFlow, slicedHerd);
|
|
84
|
-
|
|
85
|
-
if (r > maxCorr) {
|
|
86
|
-
maxCorr = r;
|
|
87
|
-
bestLag = k;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (maxCorr > 0.3) {
|
|
92
|
-
if (bestLag >= 3) regime = "EARLY_ALPHA";
|
|
93
|
-
else if (bestLag >= 1) regime = "MARKUP";
|
|
94
|
-
else regime = "FOMO_TRAP";
|
|
95
|
-
} else {
|
|
96
|
-
regime = "DECOUPLING";
|
|
25
|
+
const len = fBuf.length; if (len - k < 5) continue;
|
|
26
|
+
const r = TimeSeries.pearsonCorrelation(fBuf.slice(0, len - k), hBuf.slice(k, len));
|
|
27
|
+
if (r > maxCorr) { maxCorr = r; bestLag = k; }
|
|
97
28
|
}
|
|
29
|
+
if (maxCorr > 0.3) regime = bestLag >= 3 ? "EARLY_ALPHA" : (bestLag >= 1 ? "MARKUP" : "FOMO_TRAP");
|
|
30
|
+
else regime = "DECOUPLING";
|
|
98
31
|
}
|
|
99
|
-
|
|
100
|
-
this.mloResults[ticker] = {
|
|
101
|
-
lag_days: bestLag,
|
|
102
|
-
correlation_strength: Number(maxCorr.toFixed(4)),
|
|
103
|
-
regime: regime,
|
|
104
|
-
_state: {
|
|
105
|
-
flow_buffer: flowBuffer,
|
|
106
|
-
herd_buffer: herdBuffer,
|
|
107
|
-
last_flow: rawFlow,
|
|
108
|
-
last_herd: rawHerd
|
|
109
|
-
}
|
|
110
|
-
};
|
|
32
|
+
this.mloResults[ticker] = { lag_days: bestLag, correlation_strength: Number(maxCorr.toFixed(4)), regime, _state: { flow_buffer: fBuf, herd_buffer: hBuf, last_flow: flow, last_herd: herd } };
|
|
111
33
|
}
|
|
112
34
|
}
|
|
113
|
-
|
|
114
35
|
getResult() { return this.mloResults; }
|
|
115
36
|
reset() { this.mloResults = {}; }
|
|
116
37
|
}
|