aiden-shared-calculations-unified 1.0.104 → 1.0.106
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-total-long-per-stock.js +1 -1
- package/calculations/core/crowd-cost-basis.js +31 -23
- package/calculations/core/leverage-divergence.js +35 -23
- package/calculations/core/liquidation-cascade.js +31 -18
- package/calculations/core/ownership-vs-performance-ytd.js +48 -13
- package/calculations/core/ownership-vs-volatility.js +25 -8
- package/calculations/core/user-history-reconstructor.js +100 -55
- package/package.json +1 -1
|
@@ -28,7 +28,7 @@ class InsightsTotalPositionsHeld {
|
|
|
28
28
|
if (insights.length === 0) return;
|
|
29
29
|
|
|
30
30
|
for (const insight of insights) {
|
|
31
|
-
this.totalPositions += insightsHelper.
|
|
31
|
+
this.totalPositions += insightsHelper.getLongCount(insight); // Corrected to use getLongCount
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -1,57 +1,65 @@
|
|
|
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',
|
|
9
|
+
userType: 'n/a',
|
|
12
10
|
isHistorical: false,
|
|
13
11
|
rootDataDependencies: ['price']
|
|
14
12
|
};
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
static getDependencies() {
|
|
18
|
-
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
16
|
+
|
|
17
|
+
static getSchema() {
|
|
18
|
+
const schema = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
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." }
|
|
25
|
+
},
|
|
26
|
+
"required": ["avgEntry", "holderCount", "profitabilityPct", "state"]
|
|
27
|
+
};
|
|
28
|
+
// The result is a Map of Ticker -> Schema
|
|
29
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
async process(context) {
|
|
22
33
|
const { computed, prices, math } = context;
|
|
23
|
-
|
|
24
|
-
// 1. Access the output of the Standard calculation
|
|
25
34
|
const userReconstructions = computed['user-history-reconstructor'];
|
|
26
35
|
if (!userReconstructions) return;
|
|
27
36
|
|
|
28
|
-
const aggregator = {};
|
|
37
|
+
const aggregator = {};
|
|
29
38
|
|
|
30
|
-
//
|
|
39
|
+
// 1. Iterate Users
|
|
31
40
|
for (const userId in userReconstructions) {
|
|
32
41
|
const userPortfolio = userReconstructions[userId];
|
|
33
|
-
|
|
42
|
+
|
|
34
43
|
for (const ticker in userPortfolio) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const stats = userPortfolio[ticker];
|
|
45
|
+
|
|
46
|
+
if (stats.isHolder === 1) {
|
|
47
|
+
if (!aggregator[ticker]) aggregator[ticker] = { sumEntry: 0, count: 0 };
|
|
48
|
+
aggregator[ticker].sumEntry += stats.avgEntry;
|
|
49
|
+
aggregator[ticker].count++;
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
//
|
|
54
|
+
// 2. Compute Global Cost Basis
|
|
46
55
|
for (const ticker in aggregator) {
|
|
47
56
|
const data = aggregator[ticker];
|
|
48
|
-
if (data.count <
|
|
57
|
+
if (data.count < 5) continue;
|
|
49
58
|
|
|
50
59
|
const globalAvgEntry = data.sumEntry / data.count;
|
|
51
|
-
|
|
52
|
-
// Get
|
|
60
|
+
|
|
61
|
+
// Get Price for Context Date
|
|
53
62
|
const priceHistory = math.priceExtractor.getHistory(prices, ticker);
|
|
54
|
-
// The last item in price history for this context is "Today"
|
|
55
63
|
const lastPriceObj = priceHistory[priceHistory.length - 1];
|
|
56
64
|
const currentPrice = lastPriceObj ? lastPriceObj.price : globalAvgEntry;
|
|
57
65
|
|
|
@@ -7,42 +7,56 @@ class LeverageDivergence {
|
|
|
7
7
|
type: 'meta',
|
|
8
8
|
category: 'History Reconstruction',
|
|
9
9
|
userType: 'n/a',
|
|
10
|
-
isHistorical: true,
|
|
10
|
+
isHistorical: true,
|
|
11
11
|
rootDataDependencies: []
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
static getDependencies() {
|
|
16
|
-
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
16
|
+
|
|
17
|
+
static getSchema() {
|
|
18
|
+
const schema = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"levHolders": { "type": "number", "description": "Count of holders with leverage > 1.1x." },
|
|
22
|
+
"spotHolders": { "type": "number", "description": "Count of holders with leverage <= 1.1x." },
|
|
23
|
+
"levDelta": { "type": "number", "description": "Change in leveraged holders vs yesterday." },
|
|
24
|
+
"spotDelta": { "type": "number", "description": "Change in spot holders vs yesterday." },
|
|
25
|
+
"signal": { "type": "string", "description": "Divergence signal: NEUTRAL, SMART_ACCUMULATION, SPECULATIVE_PUMP, SMART_EXIT." }
|
|
26
|
+
},
|
|
27
|
+
"required": ["levHolders", "spotHolders", "levDelta", "spotDelta", "signal"]
|
|
28
|
+
};
|
|
29
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
async process(context) {
|
|
20
33
|
const { computed, previousComputed } = context;
|
|
21
|
-
|
|
34
|
+
|
|
22
35
|
const currentReconstruction = computed['user-history-reconstructor'];
|
|
23
|
-
|
|
24
|
-
const previousResult = previousComputed['leverage-divergence'];
|
|
36
|
+
const previousResult = previousComputed['leverage-divergence'];
|
|
25
37
|
|
|
26
|
-
const currentAgg = {};
|
|
38
|
+
const currentAgg = {};
|
|
27
39
|
|
|
28
40
|
// 1. Build Today's Aggregates
|
|
29
41
|
for (const userId in currentReconstruction) {
|
|
30
42
|
const userPortfolio = currentReconstruction[userId];
|
|
43
|
+
|
|
31
44
|
for (const ticker in userPortfolio) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
const stats = userPortfolio[ticker];
|
|
46
|
+
|
|
47
|
+
if (stats.isHolder === 1) {
|
|
48
|
+
if (!currentAgg[ticker]) currentAgg[ticker] = { levHolders: 0, spotHolders: 0 };
|
|
49
|
+
|
|
50
|
+
if (stats.avgLeverage > 1.1) {
|
|
51
|
+
currentAgg[ticker].levHolders++;
|
|
52
|
+
} else {
|
|
53
|
+
currentAgg[ticker].spotHolders++;
|
|
54
|
+
}
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
// 2. Compare with
|
|
59
|
+
// 2. Compare with Previous Day
|
|
46
60
|
for (const ticker in currentAgg) {
|
|
47
61
|
const curr = currentAgg[ticker];
|
|
48
62
|
const prev = previousResult ? previousResult[ticker] : null;
|
|
@@ -56,12 +70,10 @@ class LeverageDivergence {
|
|
|
56
70
|
const spotDelta = curr.spotHolders - prev.spotHolders;
|
|
57
71
|
|
|
58
72
|
let signal = 'NEUTRAL';
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (spotDelta > 0 && levDelta
|
|
62
|
-
|
|
63
|
-
// Retail (Spot) Selling + Speculators (Lev) Buying = High Conviction Pump
|
|
64
|
-
if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
|
|
73
|
+
if (spotDelta > 0 && levDelta < 0) signal = 'SMART_ACCUMULATION';
|
|
74
|
+
else if (spotDelta < 0 && levDelta > 0) signal = 'SPECULATIVE_PUMP';
|
|
75
|
+
else if (spotDelta > 0 && levDelta > 0) signal = 'BROAD_ACCUMULATION';
|
|
76
|
+
else if (spotDelta < 0 && levDelta < 0) signal = 'BROAD_EXIT';
|
|
65
77
|
|
|
66
78
|
this.results[ticker] = {
|
|
67
79
|
...curr,
|
|
@@ -8,12 +8,24 @@ class LiquidationCascade {
|
|
|
8
8
|
category: 'History Reconstruction',
|
|
9
9
|
userType: 'n/a',
|
|
10
10
|
isHistorical: false,
|
|
11
|
-
rootDataDependencies: []
|
|
11
|
+
rootDataDependencies: []
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
static getDependencies() {
|
|
16
|
-
|
|
15
|
+
static getDependencies() { return ['user-history-reconstructor']; }
|
|
16
|
+
|
|
17
|
+
static getSchema() {
|
|
18
|
+
const schema = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
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
|
+
"painIndex": { "type": "number", "description": "Ratio of forced closures to total closures (0.0 - 1.0)." },
|
|
24
|
+
"isFlushEvent": { "type": "boolean", "description": "True if painIndex > 0.3." }
|
|
25
|
+
},
|
|
26
|
+
"required": ["totalClosures", "forcedClosures", "painIndex", "isFlushEvent"]
|
|
27
|
+
};
|
|
28
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
async process(context) {
|
|
@@ -21,36 +33,37 @@ class LiquidationCascade {
|
|
|
21
33
|
const userReconstructions = computed['user-history-reconstructor'];
|
|
22
34
|
if (!userReconstructions) return;
|
|
23
35
|
|
|
24
|
-
const aggregator = {};
|
|
36
|
+
const aggregator = {};
|
|
25
37
|
|
|
26
|
-
// 1. Aggregate Forced Exits
|
|
27
38
|
for (const userId in userReconstructions) {
|
|
28
39
|
const userPortfolio = userReconstructions[userId];
|
|
29
|
-
|
|
40
|
+
|
|
30
41
|
for (const ticker in userPortfolio) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
if (!aggregator[ticker]) aggregator[ticker] = {
|
|
42
|
+
const stats = userPortfolio[ticker];
|
|
43
|
+
|
|
44
|
+
if (stats.didSell > 0) {
|
|
45
|
+
if (!aggregator[ticker]) aggregator[ticker] = { totalClosed: 0, forced: 0 };
|
|
46
|
+
|
|
47
|
+
aggregator[ticker].totalClosed += stats.didSell;
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
if (stats.closeReasons && stats.closeReasons["1"]) {
|
|
50
|
+
aggregator[ticker].forced += stats.closeReasons["1"];
|
|
51
|
+
}
|
|
38
52
|
}
|
|
39
53
|
}
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
// 2. Calculate Pain Index
|
|
43
56
|
for (const ticker in aggregator) {
|
|
44
57
|
const data = aggregator[ticker];
|
|
45
|
-
if (data.
|
|
58
|
+
if (data.totalClosed < 5) continue;
|
|
46
59
|
|
|
47
|
-
const forcedRatio = data.forced / data.
|
|
60
|
+
const forcedRatio = data.forced / data.totalClosed;
|
|
48
61
|
|
|
49
62
|
this.results[ticker] = {
|
|
50
|
-
totalClosures: data.
|
|
63
|
+
totalClosures: data.totalClosed,
|
|
51
64
|
forcedClosures: data.forced,
|
|
52
|
-
painIndex: forcedRatio,
|
|
53
|
-
isFlushEvent: forcedRatio > 0.3
|
|
65
|
+
painIndex: forcedRatio,
|
|
66
|
+
isFlushEvent: forcedRatio > 0.3
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
69
|
}
|
|
@@ -14,17 +14,52 @@ class OwnershipVsPerformanceYTD {
|
|
|
14
14
|
|
|
15
15
|
static getDependencies() { return []; }
|
|
16
16
|
|
|
17
|
+
static getSchema() {
|
|
18
|
+
const metricSchema = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"priceYtd": { "type": "number" },
|
|
22
|
+
"ownersYtd": { "type": "number" },
|
|
23
|
+
"currentPrice": { "type": "number" },
|
|
24
|
+
"currentOwners": { "type": "number" }
|
|
25
|
+
},
|
|
26
|
+
"required": ["priceYtd", "ownersYtd", "currentPrice", "currentOwners"]
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const baselineSchema = {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"patternProperties": {
|
|
32
|
+
"^.*$": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"startPrice": { "type": "number" },
|
|
36
|
+
"startOwners": { "type": "number" },
|
|
37
|
+
"date": { "type": "string" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"baselines": baselineSchema
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": metricSchema
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
17
52
|
async process(context) {
|
|
18
53
|
const { insights: insightsHelper, priceExtractor } = context.math;
|
|
19
54
|
const { previousComputed, mappings, prices, date } = context;
|
|
20
|
-
|
|
55
|
+
|
|
21
56
|
const dailyInsights = insightsHelper.getInsights(context, 'today');
|
|
22
|
-
|
|
57
|
+
|
|
23
58
|
// 1. Manage Baseline State (Jan 1st snapshot)
|
|
24
59
|
// If today is Jan 1st (or first run), we reset. Otherwise, we load from yesterday.
|
|
25
60
|
let baselineState = previousComputed['ownership-vs-performance-ytd']?.baselines || {};
|
|
26
61
|
const isStartOfYear = date.today.endsWith('-01-01') || date.today.endsWith('-01-02');
|
|
27
|
-
|
|
62
|
+
|
|
28
63
|
if (isStartOfYear) {
|
|
29
64
|
baselineState = {}; // Reset for new year
|
|
30
65
|
}
|
|
@@ -37,11 +72,11 @@ class OwnershipVsPerformanceYTD {
|
|
|
37
72
|
if (!ticker) continue;
|
|
38
73
|
|
|
39
74
|
const currentOwners = insightsHelper.getTotalOwners(insight);
|
|
40
|
-
|
|
75
|
+
|
|
41
76
|
// Get Price History
|
|
42
77
|
const priceHist = priceExtractor.getHistory(prices, ticker);
|
|
43
78
|
if (!priceHist.length) continue;
|
|
44
|
-
|
|
79
|
+
|
|
45
80
|
const currentPrice = priceHist[priceHist.length - 1].price;
|
|
46
81
|
|
|
47
82
|
// 2. Set Baseline if missing
|
|
@@ -56,12 +91,12 @@ class OwnershipVsPerformanceYTD {
|
|
|
56
91
|
const base = baselineState[ticker];
|
|
57
92
|
|
|
58
93
|
// 3. Calculate Deltas
|
|
59
|
-
const priceYtd = base.startPrice > 0
|
|
60
|
-
? ((currentPrice - base.startPrice) / base.startPrice) * 100
|
|
94
|
+
const priceYtd = base.startPrice > 0
|
|
95
|
+
? ((currentPrice - base.startPrice) / base.startPrice) * 100
|
|
61
96
|
: 0;
|
|
62
|
-
|
|
63
|
-
const ownersYtd = base.startOwners > 0
|
|
64
|
-
? ((currentOwners - base.startOwners) / base.startOwners) * 100
|
|
97
|
+
|
|
98
|
+
const ownersYtd = base.startOwners > 0
|
|
99
|
+
? ((currentOwners - base.startOwners) / base.startOwners) * 100
|
|
65
100
|
: 0;
|
|
66
101
|
|
|
67
102
|
currentMetrics[ticker] = {
|
|
@@ -75,15 +110,15 @@ class OwnershipVsPerformanceYTD {
|
|
|
75
110
|
// 4. Output structure includes the baselines so they are saved for tomorrow
|
|
76
111
|
this.results = {
|
|
77
112
|
metrics: currentMetrics,
|
|
78
|
-
baselines: baselineState
|
|
113
|
+
baselines: baselineState
|
|
79
114
|
};
|
|
80
115
|
}
|
|
81
116
|
|
|
82
|
-
async getResult() {
|
|
117
|
+
async getResult() {
|
|
83
118
|
// We flatten the 'metrics' for easy API consumption,
|
|
84
119
|
// but we must ensure 'baselines' is preserved in the stored document
|
|
85
120
|
// so 'previousComputed' picks it up tomorrow.
|
|
86
|
-
return { ...this.results.metrics, baselines: this.results.baselines };
|
|
121
|
+
return { ...this.results.metrics, baselines: this.results.baselines };
|
|
87
122
|
}
|
|
88
123
|
}
|
|
89
124
|
|
|
@@ -14,12 +14,29 @@ class OwnershipVsVolatility {
|
|
|
14
14
|
|
|
15
15
|
static getDependencies() { return []; }
|
|
16
16
|
|
|
17
|
+
static getSchema() {
|
|
18
|
+
const schema = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"volatilityScore": { "type": "number" },
|
|
22
|
+
"ownerChange7d": { "type": "number" },
|
|
23
|
+
"laggedOwners": { "type": "number" },
|
|
24
|
+
"_history": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": { "type": "number" }
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": ["volatilityScore", "ownerChange7d", "laggedOwners"]
|
|
30
|
+
};
|
|
31
|
+
return { "type": "object", "patternProperties": { "^.*$": schema } };
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
async process(context) {
|
|
18
35
|
const { insights: insightsHelper, priceExtractor, compute } = context.math;
|
|
19
36
|
const { previousComputed, mappings, prices } = context;
|
|
20
|
-
|
|
37
|
+
|
|
21
38
|
const dailyInsights = insightsHelper.getInsights(context, 'today');
|
|
22
|
-
|
|
39
|
+
|
|
23
40
|
// Use 7-day lookback for ownership change
|
|
24
41
|
const prevState = previousComputed['ownership-vs-volatility'] || {};
|
|
25
42
|
|
|
@@ -35,17 +52,17 @@ class OwnershipVsVolatility {
|
|
|
35
52
|
const recentPrices = priceHist.slice(-14);
|
|
36
53
|
const returns = [];
|
|
37
54
|
for (let i = 1; i < recentPrices.length; i++) {
|
|
38
|
-
returns.push((recentPrices[i].price - recentPrices[i-1].price) / recentPrices[i-1].price);
|
|
55
|
+
returns.push((recentPrices[i].price - recentPrices[i - 1].price) / recentPrices[i - 1].price);
|
|
39
56
|
}
|
|
40
|
-
|
|
57
|
+
|
|
41
58
|
const volatility = compute.standardDeviation(returns); // Raw std dev
|
|
42
59
|
|
|
43
60
|
// 2. Calculate Ownership Change (Current vs 7 days ago stored state)
|
|
44
61
|
const currentOwners = insightsHelper.getTotalOwners(insight);
|
|
45
62
|
const prevOwners = prevState[ticker]?.laggedOwners || currentOwners;
|
|
46
|
-
|
|
47
|
-
const ownerChangePct = prevOwners > 0
|
|
48
|
-
? ((currentOwners - prevOwners) / prevOwners) * 100
|
|
63
|
+
|
|
64
|
+
const ownerChangePct = prevOwners > 0
|
|
65
|
+
? ((currentOwners - prevOwners) / prevOwners) * 100
|
|
49
66
|
: 0;
|
|
50
67
|
|
|
51
68
|
this.results[ticker] = {
|
|
@@ -56,7 +73,7 @@ class OwnershipVsVolatility {
|
|
|
56
73
|
// Simplified: We store today's value, and the API/UI compares.
|
|
57
74
|
// Better: Use a simple rolling queue like the Momentum calc.
|
|
58
75
|
_history: [...(prevState[ticker]?._history || []), currentOwners].slice(-7),
|
|
59
|
-
|
|
76
|
+
|
|
60
77
|
// For the output: Calculate change vs the oldest in history (approx 7d ago)
|
|
61
78
|
laggedOwners: (prevState[ticker]?._history?.[0] || currentOwners)
|
|
62
79
|
};
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Reconstructs a user's portfolio state for a specific date from their trade history.
|
|
3
|
-
* Acts as the "Map" phase for downstream Meta aggregations.
|
|
4
|
-
*/
|
|
5
1
|
class UserHistoryReconstructor {
|
|
6
2
|
constructor() {
|
|
7
|
-
this.results = {};
|
|
3
|
+
this.results = {};
|
|
8
4
|
}
|
|
9
5
|
|
|
10
6
|
static getMetadata() {
|
|
11
7
|
return {
|
|
12
8
|
name: 'user-history-reconstructor',
|
|
13
9
|
type: 'standard',
|
|
14
|
-
category: 'History Reconstruction',
|
|
15
|
-
userType: 'all',
|
|
10
|
+
category: 'History Reconstruction',
|
|
11
|
+
userType: 'all',
|
|
16
12
|
isHistorical: false,
|
|
17
13
|
rootDataDependencies: ['history']
|
|
18
14
|
};
|
|
@@ -21,15 +17,29 @@ class UserHistoryReconstructor {
|
|
|
21
17
|
static getDependencies() { return []; }
|
|
22
18
|
|
|
23
19
|
static getSchema() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
// Schema for a single Ticker's stats
|
|
21
|
+
const tickerSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"isHolder": { "type": "number", "description": "1 if the user holds the asset at EOD, 0 otherwise." },
|
|
25
|
+
"didBuy": { "type": "number", "description": "Count of buy trades executed today." },
|
|
26
|
+
"didSell": { "type": "number", "description": "Count of sell trades executed today." },
|
|
27
|
+
"avgEntry": { "type": "number", "description": "Average entry price of held positions." },
|
|
28
|
+
"avgLeverage": { "type": "number", "description": "Average leverage of held positions." },
|
|
29
|
+
"buyLeverage": { "type": "number", "description": "Average leverage of new buys executed today." },
|
|
30
|
+
"closeReasons": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"description": "Map of close reason codes to counts (0=Manual, 1=Stop/Liq, 5=TP)."
|
|
32
33
|
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["isHolder", "didBuy", "didSell", "avgEntry", "avgLeverage"]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// The result for a user is a Map of Ticker -> TickerSchema
|
|
39
|
+
return {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"patternProperties": {
|
|
42
|
+
"^.*$": tickerSchema
|
|
33
43
|
}
|
|
34
44
|
};
|
|
35
45
|
}
|
|
@@ -37,72 +47,107 @@ class UserHistoryReconstructor {
|
|
|
37
47
|
async process(context) {
|
|
38
48
|
const { user, math, mappings, date } = context;
|
|
39
49
|
|
|
40
|
-
// 1. Get History
|
|
50
|
+
// 1. Get History (V2 Format Only)
|
|
41
51
|
const history = math.history.getDailyHistory(user);
|
|
42
52
|
const allTrades = history?.PublicHistoryPositions || [];
|
|
43
53
|
|
|
44
54
|
if (allTrades.length === 0) return;
|
|
45
55
|
|
|
46
|
-
// 2.
|
|
47
|
-
const
|
|
48
|
-
|
|
56
|
+
// 2. Define Time Boundaries for "Yesterday" (Target Date)
|
|
57
|
+
const dayStart = new Date(date.today + "T00:00:00.000Z").getTime();
|
|
58
|
+
const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
|
|
49
59
|
|
|
50
|
-
// 3. Initialize User State
|
|
51
60
|
this.results[user.id] = {};
|
|
52
61
|
const userState = this.results[user.id];
|
|
53
62
|
|
|
54
|
-
const
|
|
55
|
-
const dayEnd = new Date(date.today + "T23:59:59.999Z").getTime();
|
|
56
|
-
|
|
57
|
-
for (const trade of activeTrades) {
|
|
63
|
+
for (const trade of allTrades) {
|
|
58
64
|
const instId = trade.InstrumentID;
|
|
59
65
|
const ticker = mappings.instrumentToTicker[instId];
|
|
60
66
|
if (!ticker) continue;
|
|
61
67
|
|
|
68
|
+
// Parse Dates
|
|
69
|
+
const openTime = new Date(trade.OpenDateTime).getTime();
|
|
70
|
+
const closeTime = trade.CloseDateTime ? new Date(trade.CloseDateTime).getTime() : null;
|
|
71
|
+
|
|
72
|
+
// 3. Determine States
|
|
73
|
+
const isOpenBeforeEOD = openTime <= dayEnd;
|
|
74
|
+
// Active if it wasn't closed before the day started
|
|
75
|
+
const isClosedAfterSOD = closeTime === null || closeTime >= dayStart;
|
|
76
|
+
|
|
77
|
+
const wasActiveToday = isOpenBeforeEOD && isClosedAfterSOD;
|
|
78
|
+
|
|
79
|
+
// "Held" means active at End of Day
|
|
80
|
+
const isHeldAtEOD = isOpenBeforeEOD && (closeTime === null || closeTime > dayEnd);
|
|
81
|
+
|
|
82
|
+
// "Opened" means OpenTime is inside today
|
|
83
|
+
const isOpenedToday = openTime >= dayStart && openTime <= dayEnd;
|
|
84
|
+
|
|
85
|
+
// "Closed" means CloseTime is inside today
|
|
86
|
+
const isClosedToday = closeTime !== null && closeTime >= dayStart && closeTime <= dayEnd;
|
|
87
|
+
|
|
88
|
+
if (!wasActiveToday) continue;
|
|
89
|
+
|
|
62
90
|
if (!userState[ticker]) {
|
|
63
|
-
userState[ticker] = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
userState[ticker] = {
|
|
92
|
+
isHolder: 0,
|
|
93
|
+
didBuy: 0,
|
|
94
|
+
didSell: 0,
|
|
95
|
+
sumEntry: 0,
|
|
96
|
+
sumLev: 0,
|
|
97
|
+
holdCount: 0,
|
|
98
|
+
sumBuyLev: 0,
|
|
99
|
+
closeReasons: { "0": 0, "1": 0, "5": 0 }
|
|
69
100
|
};
|
|
70
101
|
}
|
|
71
|
-
|
|
72
102
|
const stats = userState[ticker];
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
|
|
104
|
+
// 4. Populate Metrics
|
|
105
|
+
if (isHeldAtEOD) {
|
|
106
|
+
stats.isHolder = 1;
|
|
107
|
+
stats.holdCount++;
|
|
108
|
+
stats.sumEntry += (trade.OpenRate || 0);
|
|
109
|
+
stats.sumLev += (trade.Leverage || 1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isOpenedToday) {
|
|
113
|
+
stats.didBuy++;
|
|
114
|
+
stats.sumBuyLev += (trade.Leverage || 1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (isClosedToday) {
|
|
118
|
+
stats.didSell++;
|
|
119
|
+
const reason = String(trade.CloseReason || 0);
|
|
120
|
+
if (stats.closeReasons[reason] === undefined) stats.closeReasons[reason] = 0;
|
|
121
|
+
stats.closeReasons[reason]++;
|
|
87
122
|
}
|
|
88
123
|
}
|
|
89
124
|
|
|
90
|
-
//
|
|
125
|
+
// 5. Cleanup / Average
|
|
91
126
|
for (const ticker in userState) {
|
|
92
127
|
const stats = userState[ticker];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
stats.
|
|
128
|
+
|
|
129
|
+
if (stats.holdCount > 0) {
|
|
130
|
+
stats.avgEntry = stats.sumEntry / stats.holdCount;
|
|
131
|
+
stats.avgLeverage = stats.sumLev / stats.holdCount;
|
|
132
|
+
} else {
|
|
133
|
+
stats.avgEntry = 0;
|
|
134
|
+
stats.avgLeverage = 0;
|
|
96
135
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
136
|
+
|
|
137
|
+
if (stats.didBuy > 0) {
|
|
138
|
+
stats.buyLeverage = stats.sumBuyLev / stats.didBuy;
|
|
139
|
+
} else {
|
|
140
|
+
stats.buyLeverage = 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
delete stats.sumEntry;
|
|
144
|
+
delete stats.sumLev;
|
|
145
|
+
delete stats.sumBuyLev;
|
|
146
|
+
delete stats.holdCount;
|
|
100
147
|
}
|
|
101
148
|
}
|
|
102
149
|
|
|
103
|
-
async getResult() {
|
|
104
|
-
return this.results;
|
|
105
|
-
}
|
|
150
|
+
async getResult() { return this.results; }
|
|
106
151
|
}
|
|
107
152
|
|
|
108
153
|
module.exports = UserHistoryReconstructor;
|