aiden-shared-calculations-unified 1.0.82 → 1.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/calculations/core/asset-pnl-status.js +122 -104
- package/calculations/core/asset-position-size.js +110 -73
- package/calculations/core/average-daily-pnl-all-users.js +17 -3
- package/calculations/core/average-daily-pnl-per-sector.js +83 -75
- package/calculations/core/average-daily-pnl-per-stock.js +84 -73
- package/calculations/core/average-daily-position-pnl.js +2 -2
- package/calculations/core/holding-duration-per-asset.js +24 -23
- package/calculations/core/instrument-price-change-1d.js +72 -82
- package/calculations/core/instrument-price-momentum-20d.js +66 -100
- package/calculations/core/long-position-per-stock.js +21 -13
- package/calculations/core/overall-holding-duration.js +8 -3
- package/calculations/core/overall-profitability-ratio.js +2 -2
- package/calculations/core/platform-buy-sell-sentiment.js +75 -22
- package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
- package/calculations/core/platform-daily-ownership-delta.js +39 -15
- package/calculations/core/platform-ownership-per-sector.js +38 -18
- package/calculations/core/platform-total-positions-held.js +36 -14
- package/calculations/core/pnl-distribution-per-stock.js +39 -36
- package/calculations/core/price-metrics.js +70 -172
- package/calculations/core/profitability-ratio-per-sector.js +23 -29
- package/calculations/core/profitability-ratio-per-stock.js +20 -13
- package/calculations/core/profitability-skew-per-stock.js +20 -13
- package/calculations/core/profitable-and-unprofitable-status.js +34 -10
- package/calculations/core/sentiment-per-stock.js +20 -9
- package/calculations/core/short-position-per-stock.js +23 -37
- package/calculations/core/social-activity-aggregation.js +41 -115
- package/calculations/core/social-asset-posts-trend.js +77 -94
- package/calculations/core/social-event-correlation.js +87 -106
- package/calculations/core/social-sentiment-aggregation.js +56 -138
- package/calculations/core/social-top-mentioned-words.js +74 -106
- package/calculations/core/social-topic-interest-evolution.js +94 -94
- package/calculations/core/social-topic-sentiment-matrix.js +90 -74
- package/calculations/core/social-word-mentions-trend.js +92 -106
- package/calculations/core/speculator-asset-sentiment.js +63 -92
- package/calculations/core/speculator-danger-zone.js +77 -90
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
- package/calculations/core/speculator-leverage-per-asset.js +62 -57
- package/calculations/core/speculator-leverage-per-sector.js +53 -65
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
- package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
- package/calculations/core/speculator-take-profit-per-asset.js +45 -69
- package/calculations/core/speculator-tsl-per-asset.js +42 -49
- package/calculations/core/total-long-figures.js +19 -19
- package/calculations/core/total-long-per-sector.js +39 -36
- package/calculations/core/total-short-figures.js +19 -19
- package/calculations/core/total-short-per-sector.js +39 -36
- package/calculations/core/users-processed.js +52 -25
- package/calculations/gauss/cohort-capital-flow.js +38 -29
- package/calculations/gauss/cohort-definer.js +17 -25
- package/calculations/gauss/daily-dna-filter.js +10 -4
- package/calculations/gauss/gauss-divergence-signal.js +28 -6
- package/calculations/gem/cohort-momentum-state.js +113 -92
- package/calculations/gem/cohort-skill-definition.js +23 -53
- package/calculations/gem/platform-conviction-divergence.js +62 -116
- package/calculations/gem/quant-skill-alpha-signal.js +107 -123
- package/calculations/gem/skilled-cohort-flow.js +178 -167
- package/calculations/gem/skilled-unskilled-divergence.js +73 -113
- package/calculations/gem/unskilled-cohort-flow.js +176 -166
- package/calculations/helix/helix-contrarian-signal.js +91 -83
- package/calculations/helix/herd-consensus-score.js +135 -97
- package/calculations/helix/winner-loser-flow.js +14 -14
- package/calculations/pyro/risk-appetite-index.js +121 -123
- package/calculations/pyro/squeeze-potential.js +93 -125
- package/calculations/pyro/volatility-signal.js +109 -97
- package/package.json +9 -9
- package/README.MD +0 -78
|
@@ -1,147 +1,134 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for speculator
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator "danger zone".
|
|
3
|
+
* --- FIX ---
|
|
4
|
+
* - Rewritten logic to calculate SL distance from raw schema fields
|
|
5
|
+
* (StopLossRate, CurrentRate, OpenRate) instead of 'PctToStopLoss'.
|
|
6
|
+
* - "Danger Zone" is defined as being >= 90% of the way to the SL.
|
|
6
7
|
*/
|
|
7
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
8
8
|
|
|
9
9
|
class SpeculatorDangerZone {
|
|
10
10
|
constructor() {
|
|
11
|
-
// { [instrumentId]: { long_in_danger: 0, short_in_danger: 0, long_total: 0, short_total: 0 } }
|
|
12
11
|
this.assets = new Map();
|
|
13
|
-
this.
|
|
12
|
+
this.tickerMap = null;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
/**
|
|
17
|
-
* Defines the output schema for this calculation.
|
|
18
|
-
* @returns {object} JSON Schema object
|
|
19
|
-
*/
|
|
20
|
-
static getSchema() {
|
|
21
|
-
const tickerSchema = {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"properties": {
|
|
24
|
-
"long_in_danger": { "type": "number" },
|
|
25
|
-
"short_in_danger": { "type": "number" },
|
|
26
|
-
"long_total": { "type": "number" },
|
|
27
|
-
"short_total": { "type": "number" },
|
|
28
|
-
"long_danger_pct": {
|
|
29
|
-
"type": "number",
|
|
30
|
-
"description": "Percentage of long positions in the danger zone."
|
|
31
|
-
},
|
|
32
|
-
"short_danger_pct": {
|
|
33
|
-
"type": "number",
|
|
34
|
-
"description": "Percentage of short positions in the danger zone."
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"required": ["long_in_danger", "short_in_danger", "long_total", "short_total", "long_danger_pct", "short_danger_pct"]
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
"type": "object",
|
|
42
|
-
"description": "Tracks speculator positions within 5% of their Stop Loss.",
|
|
43
|
-
"patternProperties": {
|
|
44
|
-
"^.*$": tickerSchema // Ticker
|
|
45
|
-
},
|
|
46
|
-
"additionalProperties": tickerSchema
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Statically defines all metadata for the manifest builder.
|
|
52
|
-
*/
|
|
53
15
|
static getMetadata() {
|
|
54
16
|
return {
|
|
55
17
|
type: 'standard',
|
|
56
18
|
rootDataDependencies: ['portfolio'],
|
|
57
19
|
isHistorical: false,
|
|
58
|
-
userType: 'speculator',
|
|
20
|
+
userType: 'speculator',
|
|
59
21
|
category: 'core_speculator'
|
|
60
22
|
};
|
|
61
23
|
}
|
|
62
24
|
|
|
63
|
-
/**
|
|
64
|
-
* Statically declare dependencies.
|
|
65
|
-
*/
|
|
66
25
|
static getDependencies() {
|
|
67
26
|
return [];
|
|
68
27
|
}
|
|
69
28
|
|
|
29
|
+
static getSchema() {
|
|
30
|
+
const tickerSchema = {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"danger_zone_pct": { "type": "number" },
|
|
34
|
+
"danger_zone_count": { "type": "number" },
|
|
35
|
+
"total_count": { "type": "number" }
|
|
36
|
+
},
|
|
37
|
+
"required": ["danger_zone_pct", "danger_zone_count", "total_count"]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Calculates the percentage of speculators in the 'danger zone' (90-100% to stop-loss).",
|
|
43
|
+
"patternProperties": { "^.*$": tickerSchema },
|
|
44
|
+
"additionalProperties": tickerSchema
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
70
48
|
_initAsset(instrumentId) {
|
|
71
49
|
if (!this.assets.has(instrumentId)) {
|
|
72
|
-
this.assets.set(instrumentId, {
|
|
73
|
-
long_in_danger: 0,
|
|
74
|
-
short_in_danger: 0,
|
|
75
|
-
long_total: 0,
|
|
76
|
-
short_total: 0
|
|
77
|
-
});
|
|
50
|
+
this.assets.set(instrumentId, { danger_zone: 0, total: 0 });
|
|
78
51
|
}
|
|
79
52
|
}
|
|
80
53
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return;
|
|
54
|
+
// --- THIS IS THE FIX ---
|
|
55
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
56
|
+
if (!this.tickerMap) {
|
|
57
|
+
this.tickerMap = context.instrumentToTicker;
|
|
86
58
|
}
|
|
87
|
-
|
|
88
|
-
const positions =
|
|
59
|
+
|
|
60
|
+
const positions = todayPortfolio.PublicPositions;
|
|
89
61
|
if (!positions || !Array.isArray(positions)) {
|
|
90
62
|
return;
|
|
91
63
|
}
|
|
92
64
|
|
|
93
65
|
for (const pos of positions) {
|
|
94
66
|
const instrumentId = pos.InstrumentID;
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
const slRate = pos.StopLossRate;
|
|
68
|
+
const openRate = pos.OpenRate;
|
|
69
|
+
const currentRate = pos.CurrentRate;
|
|
70
|
+
|
|
71
|
+
if (!instrumentId || !slRate || slRate <= 0 || !openRate || openRate <= 0 || !currentRate || currentRate <= 0) {
|
|
72
|
+
continue; // No SL or invalid data
|
|
100
73
|
}
|
|
101
|
-
|
|
74
|
+
|
|
102
75
|
this._initAsset(instrumentId);
|
|
103
76
|
const assetData = this.assets.get(instrumentId);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const isInDanger = distance_pct <= 0.05; // 5% danger zone
|
|
77
|
+
assetData.total++;
|
|
78
|
+
|
|
79
|
+
let totalRiskDistance = 0;
|
|
80
|
+
let currentDistance = 0;
|
|
109
81
|
|
|
110
82
|
if (pos.IsBuy) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
83
|
+
// Long
|
|
84
|
+
totalRiskDistance = openRate - slRate;
|
|
85
|
+
currentDistance = currentRate - slRate;
|
|
115
86
|
} else {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
// Short
|
|
88
|
+
totalRiskDistance = slRate - openRate;
|
|
89
|
+
currentDistance = slRate - currentRate;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (totalRiskDistance <= 0) {
|
|
93
|
+
continue; // Invalid SL (at or beyond open price)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// This is the percentage of the "risk buffer" that has been used up.
|
|
97
|
+
// e.g., Open=100, SL=80. RiskDist=20.
|
|
98
|
+
// Current=82. CurrentDist=2. PctUsed = (20-2)/20 = 18/20 = 90%.
|
|
99
|
+
const percentOfRiskUsed = (totalRiskDistance - currentDistance) / totalRiskDistance;
|
|
100
|
+
|
|
101
|
+
// "Danger Zone" = 90% or more of the way to the SL
|
|
102
|
+
if (percentOfRiskUsed >= 0.90) {
|
|
103
|
+
assetData.danger_zone++;
|
|
120
104
|
}
|
|
121
105
|
}
|
|
122
106
|
}
|
|
107
|
+
// --- END FIX ---
|
|
123
108
|
|
|
124
109
|
async getResult() {
|
|
125
|
-
if (!this.
|
|
126
|
-
|
|
110
|
+
if (!this.tickerMap) {
|
|
111
|
+
return {};
|
|
127
112
|
}
|
|
128
113
|
|
|
129
114
|
const result = {};
|
|
130
115
|
for (const [instrumentId, data] of this.assets.entries()) {
|
|
131
|
-
const ticker = this.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
116
|
+
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
117
|
+
|
|
118
|
+
if (data.total > 0) {
|
|
119
|
+
result[ticker] = {
|
|
120
|
+
danger_zone_pct: (data.danger_zone / data.total) * 100,
|
|
121
|
+
danger_zone_count: data.danger_zone,
|
|
122
|
+
total_count: data.total
|
|
123
|
+
};
|
|
124
|
+
}
|
|
138
125
|
}
|
|
139
126
|
return result;
|
|
140
127
|
}
|
|
141
128
|
|
|
142
129
|
reset() {
|
|
143
130
|
this.assets.clear();
|
|
144
|
-
this.
|
|
131
|
+
this.tickerMap = null;
|
|
145
132
|
}
|
|
146
133
|
}
|
|
147
134
|
|
|
@@ -1,141 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for speculator
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator SL distance.
|
|
3
|
+
* --- FIX ---
|
|
4
|
+
* - Rewritten logic to calculate SL distance from raw schema fields
|
|
5
|
+
* (StopLossRate, CurrentRate) instead of the non-existent
|
|
6
|
+
* 'PctToStopLoss' field.
|
|
7
|
+
* - Updated process signature to match worker.
|
|
7
8
|
*/
|
|
8
|
-
const
|
|
9
|
+
const BUCKETS = [1, 2, 5, 10, 20, 30]; // Leverage buckets
|
|
9
10
|
|
|
10
|
-
class
|
|
11
|
+
class SpeculatorDistanceToStopLossPerLeverage {
|
|
11
12
|
constructor() {
|
|
12
|
-
|
|
13
|
-
this.
|
|
14
|
-
|
|
13
|
+
this.buckets = new Map();
|
|
14
|
+
this._initBuckets();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_initBuckets() {
|
|
18
|
+
this.buckets.clear();
|
|
19
|
+
for (const b of BUCKETS) {
|
|
20
|
+
this.buckets.set(b, { sum: 0, count: 0 });
|
|
21
|
+
}
|
|
22
|
+
this.buckets.set('other', { sum: 0, count: 0 });
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
// --- NEW ---
|
|
18
|
-
/**
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
21
25
|
static getMetadata() {
|
|
22
26
|
return {
|
|
23
27
|
type: 'standard',
|
|
24
28
|
rootDataDependencies: ['portfolio'],
|
|
25
29
|
isHistorical: false,
|
|
26
|
-
userType: 'speculator',
|
|
30
|
+
userType: 'speculator', // <-- KEY: Only runs for speculators
|
|
27
31
|
category: 'core_speculator'
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
// --- NEW ---
|
|
32
|
-
/**
|
|
33
|
-
* Statically declare dependencies.
|
|
34
|
-
*/
|
|
35
35
|
static getDependencies() {
|
|
36
36
|
return [];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Defines the output schema for this calculation.
|
|
41
|
-
* @returns {object} JSON Schema object
|
|
42
|
-
*/
|
|
43
39
|
static getSchema() {
|
|
44
|
-
const
|
|
40
|
+
const bucketSchema = {
|
|
45
41
|
"type": "object",
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "The average percentage distance from current price to Stop Loss for this leverage level."
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
"additionalProperties": { "type": "number" }
|
|
42
|
+
"properties": {
|
|
43
|
+
"avg_distance_pct": { "type": "number" },
|
|
44
|
+
"position_count": { "type": "number" }
|
|
45
|
+
}
|
|
55
46
|
};
|
|
56
47
|
|
|
57
48
|
return {
|
|
58
49
|
"type": "object",
|
|
59
|
-
"description": "Calculates
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
"description": "Calculates the average distance to stop-loss, bucketed by leverage.",
|
|
51
|
+
"properties": {
|
|
52
|
+
"x1": bucketSchema,
|
|
53
|
+
"x2": bucketSchema,
|
|
54
|
+
"x5": bucketSchema,
|
|
55
|
+
"x10": bucketSchema,
|
|
56
|
+
"x20": bucketSchema,
|
|
57
|
+
"x30": bucketSchema,
|
|
58
|
+
"other": bucketSchema
|
|
59
|
+
}
|
|
64
60
|
};
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
assetData[key] = { sum_dist: 0, count: 0 };
|
|
63
|
+
_getBucket(leverage) {
|
|
64
|
+
if (this.buckets.has(leverage)) {
|
|
65
|
+
return this.buckets.get(leverage);
|
|
71
66
|
}
|
|
72
|
-
return
|
|
67
|
+
return this.buckets.get('other');
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
// ---
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// This calculation is only for speculators
|
|
79
|
-
if (portfolioData?.context?.userType !== 'speculator') {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const positions = portfolioData.PublicPositions;
|
|
70
|
+
// --- THIS IS THE FIX ---
|
|
71
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
72
|
+
const positions = todayPortfolio.PublicPositions;
|
|
84
73
|
if (!positions || !Array.isArray(positions)) {
|
|
85
74
|
return;
|
|
86
75
|
}
|
|
87
76
|
|
|
88
77
|
for (const pos of positions) {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
if
|
|
94
|
-
|
|
78
|
+
const leverage = pos.Leverage;
|
|
79
|
+
const slRate = pos.StopLossRate;
|
|
80
|
+
const currentRate = pos.CurrentRate;
|
|
81
|
+
|
|
82
|
+
// Check if SL is set (slRate > 0) and we have valid prices
|
|
83
|
+
if (!leverage || !slRate || slRate <= 0 || !currentRate || currentRate <= 0) {
|
|
84
|
+
continue;
|
|
95
85
|
}
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
// Manually calculate the percentage distance to SL
|
|
88
|
+
let pctToSL = 0;
|
|
89
|
+
if (pos.IsBuy) {
|
|
90
|
+
// Long: (Current - SL) / Current
|
|
91
|
+
pctToSL = (currentRate - slRate) / currentRate;
|
|
92
|
+
} else {
|
|
93
|
+
// Short: (SL - Current) / Current
|
|
94
|
+
pctToSL = (slRate - currentRate) / currentRate;
|
|
99
95
|
}
|
|
100
|
-
const assetData = this.assets.get(instrumentId);
|
|
101
|
-
|
|
102
|
-
const leverage = pos.Leverage || 1;
|
|
103
|
-
const key = this._initLeverage(assetData, leverage);
|
|
104
|
-
|
|
105
|
-
// Calculate distance
|
|
106
|
-
const distance = Math.abs(current_price - sl_rate);
|
|
107
|
-
const distance_pct = (distance / current_price);
|
|
108
|
-
|
|
109
|
-
assetData[key].sum_dist += distance_pct;
|
|
110
|
-
assetData[key].count++;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
96
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
97
|
+
// We only care about open SLs (distance > 0)
|
|
98
|
+
if (pctToSL <= 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const bucket = this._getBucket(leverage);
|
|
103
|
+
bucket.sum += (pctToSL * 100); // Convert to percentage
|
|
104
|
+
bucket.count++;
|
|
117
105
|
}
|
|
106
|
+
}
|
|
107
|
+
// --- END FIX ---
|
|
118
108
|
|
|
109
|
+
getResult() {
|
|
119
110
|
const result = {};
|
|
120
|
-
for (const [
|
|
121
|
-
const
|
|
122
|
-
result[
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (data.count > 0) {
|
|
127
|
-
// Store the final average distance * 100 for percentage
|
|
128
|
-
result[ticker][leverageKey] = (data.sum_dist / data.count) * 100;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
111
|
+
for (const [key, data] of this.buckets.entries()) {
|
|
112
|
+
const label = (key === 'other') ? 'other' : `x${key}`;
|
|
113
|
+
result[label] = {
|
|
114
|
+
avg_distance_pct: (data.count > 0) ? (data.sum / data.count) : 0,
|
|
115
|
+
position_count: data.count
|
|
116
|
+
};
|
|
131
117
|
}
|
|
132
118
|
return result;
|
|
133
119
|
}
|
|
134
120
|
|
|
135
121
|
reset() {
|
|
136
|
-
this.
|
|
137
|
-
this.mappings = null;
|
|
122
|
+
this._initBuckets();
|
|
138
123
|
}
|
|
139
124
|
}
|
|
140
125
|
|
|
141
|
-
module.exports =
|
|
126
|
+
module.exports = SpeculatorDistanceToStopLossPerLeverage;
|
|
@@ -1,139 +1,126 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for speculator
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for speculator TP distance.
|
|
3
|
+
* --- FIX ---
|
|
4
|
+
* - Rewritten logic to calculate TP distance from raw schema fields
|
|
5
|
+
* (TakeProfitRate, CurrentRate) instead of the non-existent
|
|
6
|
+
* 'PctToTakeProfit' field.
|
|
7
|
+
* - Updated process signature to match worker.
|
|
7
8
|
*/
|
|
8
|
-
const
|
|
9
|
+
const BUCKETS = [1, 2, 5, 10, 20, 30]; // Leverage buckets
|
|
9
10
|
|
|
10
|
-
class
|
|
11
|
+
class SpeculatorDistanceToTakeProfitPerLeverage {
|
|
11
12
|
constructor() {
|
|
12
|
-
|
|
13
|
-
this.
|
|
14
|
-
|
|
13
|
+
this.buckets = new Map();
|
|
14
|
+
this._initBuckets();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_initBuckets() {
|
|
18
|
+
this.buckets.clear();
|
|
19
|
+
for (const b of BUCKETS) {
|
|
20
|
+
this.buckets.set(b, { sum: 0, count: 0 });
|
|
21
|
+
}
|
|
22
|
+
this.buckets.set('other', { sum: 0, count: 0 });
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
// --- NEW ---
|
|
18
|
-
/**
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
21
25
|
static getMetadata() {
|
|
22
26
|
return {
|
|
23
27
|
type: 'standard',
|
|
24
28
|
rootDataDependencies: ['portfolio'],
|
|
25
29
|
isHistorical: false,
|
|
26
|
-
userType: 'speculator',
|
|
30
|
+
userType: 'speculator', // <-- KEY: Only runs for speculators
|
|
27
31
|
category: 'core_speculator'
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
// --- NEW ---
|
|
32
|
-
/**
|
|
33
|
-
* Statically declare dependencies.
|
|
34
|
-
*/
|
|
35
35
|
static getDependencies() {
|
|
36
36
|
return [];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
* Defines the output schema for this calculation.
|
|
41
|
-
* @returns {object} JSON Schema object
|
|
42
|
-
*/
|
|
43
39
|
static getSchema() {
|
|
44
|
-
const
|
|
40
|
+
const bucketSchema = {
|
|
45
41
|
"type": "object",
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "The average percentage distance from current price to Take Profit for this leverage level."
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
"additionalProperties": { "type": "number" }
|
|
42
|
+
"properties": {
|
|
43
|
+
"avg_distance_pct": { "type": "number" },
|
|
44
|
+
"position_count": { "type": "number" }
|
|
45
|
+
}
|
|
55
46
|
};
|
|
56
47
|
|
|
57
48
|
return {
|
|
58
49
|
"type": "object",
|
|
59
|
-
"description": "Calculates
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
"description": "Calculates the average distance to take-profit, bucketed by leverage.",
|
|
51
|
+
"properties": {
|
|
52
|
+
"x1": bucketSchema,
|
|
53
|
+
"x2": bucketSchema,
|
|
54
|
+
"x5": bucketSchema,
|
|
55
|
+
"x10": bucketSchema,
|
|
56
|
+
"x20": bucketSchema,
|
|
57
|
+
"x30": bucketSchema,
|
|
58
|
+
"other": bucketSchema
|
|
59
|
+
}
|
|
64
60
|
};
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
assetData[key] = { sum_dist: 0, count: 0 };
|
|
63
|
+
_getBucket(leverage) {
|
|
64
|
+
if (this.buckets.has(leverage)) {
|
|
65
|
+
return this.buckets.get(leverage);
|
|
71
66
|
}
|
|
72
|
-
return
|
|
67
|
+
return this.buckets.get('other');
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
// ---
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// This calculation is only for speculators
|
|
79
|
-
if (portfolioData?.context?.userType !== 'speculator') {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const positions = portfolioData.PublicPositions;
|
|
70
|
+
// --- THIS IS THE FIX ---
|
|
71
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
72
|
+
const positions = todayPortfolio.PublicPositions;
|
|
84
73
|
if (!positions || !Array.isArray(positions)) {
|
|
85
74
|
return;
|
|
86
75
|
}
|
|
87
76
|
|
|
88
77
|
for (const pos of positions) {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
if
|
|
94
|
-
|
|
78
|
+
const leverage = pos.Leverage;
|
|
79
|
+
const tpRate = pos.TakeProfitRate;
|
|
80
|
+
const currentRate = pos.CurrentRate;
|
|
81
|
+
|
|
82
|
+
// Check if TP is set (tpRate > 0) and we have valid prices
|
|
83
|
+
if (!leverage || !tpRate || tpRate <= 0 || !currentRate || currentRate <= 0) {
|
|
84
|
+
continue;
|
|
95
85
|
}
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
// Manually calculate the percentage distance to TP
|
|
88
|
+
let pctToTP = 0;
|
|
89
|
+
if (pos.IsBuy) {
|
|
90
|
+
// Long: (TP - Current) / Current
|
|
91
|
+
pctToTP = (tpRate - currentRate) / currentRate;
|
|
92
|
+
} else {
|
|
93
|
+
// Short: (Current - TP) / Current
|
|
94
|
+
pctToTP = (currentRate - tpRate) / currentRate;
|
|
99
95
|
}
|
|
100
|
-
const assetData = this.assets.get(instrumentId);
|
|
101
|
-
|
|
102
|
-
const leverage = pos.Leverage || 1;
|
|
103
|
-
const key = this._initLeverage(assetData, leverage);
|
|
104
|
-
|
|
105
|
-
const distance = Math.abs(tp_rate - current_price);
|
|
106
|
-
const distance_pct = (distance / current_price);
|
|
107
|
-
|
|
108
|
-
assetData[key].sum_dist += distance_pct;
|
|
109
|
-
assetData[key].count++;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
96
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
97
|
+
// We only care about open TPs (distance > 0)
|
|
98
|
+
if (pctToTP <= 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const bucket = this._getBucket(leverage);
|
|
103
|
+
bucket.sum += (pctToTP * 100); // Convert to percentage
|
|
104
|
+
bucket.count++;
|
|
116
105
|
}
|
|
106
|
+
}
|
|
107
|
+
// --- END FIX ---
|
|
117
108
|
|
|
109
|
+
getResult() {
|
|
118
110
|
const result = {};
|
|
119
|
-
for (const [
|
|
120
|
-
const
|
|
121
|
-
result[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (data.count > 0) {
|
|
126
|
-
result[ticker][leverageKey] = (data.sum_dist / data.count) * 100;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
111
|
+
for (const [key, data] of this.buckets.entries()) {
|
|
112
|
+
const label = (key === 'other') ? 'other' : `x${key}`;
|
|
113
|
+
result[label] = {
|
|
114
|
+
avg_distance_pct: (data.count > 0) ? (data.sum / data.count) : 0,
|
|
115
|
+
position_count: data.count
|
|
116
|
+
};
|
|
129
117
|
}
|
|
130
118
|
return result;
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
reset() {
|
|
134
|
-
this.
|
|
135
|
-
this.mappings = null;
|
|
122
|
+
this._initBuckets();
|
|
136
123
|
}
|
|
137
124
|
}
|
|
138
125
|
|
|
139
|
-
module.exports =
|
|
126
|
+
module.exports = SpeculatorDistanceToTakeProfitPerLeverage;
|