aiden-shared-calculations-unified 1.0.37 → 1.0.38
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/capital_flow/historical/reallocation_increase_percentage.js +63 -0
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +91 -0
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +73 -0
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the average percentage increase in allocation
|
|
3
|
+
* specifically towards assets already held on the previous day.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ReallocationIncreasePercentage {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.accumulatedIncreasePercentage = 0;
|
|
9
|
+
this.userCount = 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
13
|
+
if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
|
|
14
|
+
// Requires AggregatedPositions which contain the 'Invested' percentage
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const yesterdayPositions = new Map(yesterdayPortfolio.AggregatedPositions.map(p => [p.InstrumentID, p]));
|
|
19
|
+
let userTotalIncreasePercentage = 0;
|
|
20
|
+
|
|
21
|
+
for (const todayPos of todayPortfolio.AggregatedPositions) {
|
|
22
|
+
const yesterdayPos = yesterdayPositions.get(todayPos.InstrumentID);
|
|
23
|
+
|
|
24
|
+
// Check if the asset was held yesterday
|
|
25
|
+
if (yesterdayPos) {
|
|
26
|
+
// Ensure 'Invested' property exists and is a number
|
|
27
|
+
const todayInvested = typeof todayPos.Invested === 'number' ? todayPos.Invested : 0;
|
|
28
|
+
const yesterdayInvested = typeof yesterdayPos.Invested === 'number' ? yesterdayPos.Invested : 0;
|
|
29
|
+
|
|
30
|
+
const deltaInvested = todayInvested - yesterdayInvested;
|
|
31
|
+
|
|
32
|
+
// Accumulate only the increases
|
|
33
|
+
if (deltaInvested > 0) {
|
|
34
|
+
userTotalIncreasePercentage += deltaInvested;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Only count users who had positions on both days for this metric
|
|
40
|
+
if (yesterdayPortfolio.AggregatedPositions.length > 0 && todayPortfolio.AggregatedPositions.length > 0) {
|
|
41
|
+
this.accumulatedIncreasePercentage += userTotalIncreasePercentage;
|
|
42
|
+
this.userCount++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getResult() {
|
|
47
|
+
if (this.userCount === 0) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
// Calculate the final average directly
|
|
53
|
+
average_reallocation_increase_percentage: this.accumulatedIncreasePercentage / this.userCount
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
reset() {
|
|
58
|
+
this.accumulatedIncreasePercentage = 0;
|
|
59
|
+
this.userCount = 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = ReallocationIncreasePercentage;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the average stop loss distance (percent and value)
|
|
3
|
+
* for long and short positions, grouped by SECTOR.
|
|
4
|
+
*/
|
|
5
|
+
const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
|
|
6
|
+
|
|
7
|
+
class StopLossDistanceBySector {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.instrumentData = {};
|
|
10
|
+
this.instrumentToSector = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
14
|
+
if (!portfolioData || !portfolioData.PublicPositions) return;
|
|
15
|
+
|
|
16
|
+
for (const position of portfolioData.PublicPositions) {
|
|
17
|
+
const { InstrumentID, Leverage, StopLossRate, CurrentRate, IsBuy } = position;
|
|
18
|
+
if (Leverage <= 1 || StopLossRate <= 0.0001 || CurrentRate <= 0) continue;
|
|
19
|
+
|
|
20
|
+
const distance_value = IsBuy ? CurrentRate - StopLossRate : StopLossRate - CurrentRate;
|
|
21
|
+
const distance_percent = (distance_value / CurrentRate) * 100;
|
|
22
|
+
|
|
23
|
+
if (distance_percent > 0) {
|
|
24
|
+
const posType = IsBuy ? 'long' : 'short';
|
|
25
|
+
if (!this.instrumentData[InstrumentID]) this.instrumentData[InstrumentID] = {};
|
|
26
|
+
if (!this.instrumentData[InstrumentID][posType]) {
|
|
27
|
+
this.instrumentData[InstrumentID][posType] = {
|
|
28
|
+
distance_percent_sum: 0,
|
|
29
|
+
distance_value_sum: 0,
|
|
30
|
+
count: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const agg = this.instrumentData[InstrumentID][posType];
|
|
34
|
+
agg.distance_percent_sum += distance_percent;
|
|
35
|
+
agg.distance_value_sum += distance_value;
|
|
36
|
+
agg.count++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getResult() {
|
|
42
|
+
if (Object.keys(this.instrumentData).length === 0) return {};
|
|
43
|
+
if (!this.instrumentToSector) {
|
|
44
|
+
this.instrumentToSector = await getInstrumentSectorMap();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const sectorData = {};
|
|
48
|
+
for (const instrumentId in this.instrumentData) {
|
|
49
|
+
const sector = this.instrumentToSector[instrumentId] || 'N/A';
|
|
50
|
+
if (!sectorData[sector]) sectorData[sector] = {};
|
|
51
|
+
|
|
52
|
+
for (const posType in this.instrumentData[instrumentId]) {
|
|
53
|
+
if (!sectorData[sector][posType]) {
|
|
54
|
+
sectorData[sector][posType] = {
|
|
55
|
+
distance_percent_sum: 0,
|
|
56
|
+
distance_value_sum: 0,
|
|
57
|
+
count: 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const source = this.instrumentData[instrumentId][posType];
|
|
61
|
+
const target = sectorData[sector][posType];
|
|
62
|
+
target.distance_percent_sum += source.distance_percent_sum;
|
|
63
|
+
target.distance_value_sum += source.distance_value_sum;
|
|
64
|
+
target.count += source.count;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const sector in sectorData) {
|
|
70
|
+
result[sector] = {};
|
|
71
|
+
for (const posType in sectorData[sector]) {
|
|
72
|
+
const data = sectorData[sector][posType];
|
|
73
|
+
// REFACTOR: Perform final calculation and return in standardized format.
|
|
74
|
+
if (data.count > 0) {
|
|
75
|
+
result[sector][posType] = {
|
|
76
|
+
average_distance_percent: data.distance_percent_sum / data.count,
|
|
77
|
+
average_distance_value: data.distance_value_sum / data.count,
|
|
78
|
+
count: data.count
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
reset() {
|
|
87
|
+
this.instrumentData = {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = StopLossDistanceBySector;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the average stop loss distance (percent and value)
|
|
3
|
+
* for long and short positions, grouped by TICKER.
|
|
4
|
+
*/
|
|
5
|
+
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
|
+
|
|
7
|
+
class StopLossDistanceByTicker {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.instrumentData = {};
|
|
10
|
+
this.instrumentToTicker = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
14
|
+
if (!portfolioData || !portfolioData.PublicPositions) return;
|
|
15
|
+
|
|
16
|
+
for (const position of portfolioData.PublicPositions) {
|
|
17
|
+
const { InstrumentID, Leverage, StopLossRate, CurrentRate, IsBuy } = position;
|
|
18
|
+
if (Leverage <= 1 || StopLossRate <= 0.0001 || CurrentRate <= 0) continue;
|
|
19
|
+
|
|
20
|
+
const distance_value = IsBuy ? CurrentRate - StopLossRate : StopLossRate - CurrentRate;
|
|
21
|
+
const distance_percent = (distance_value / CurrentRate) * 100;
|
|
22
|
+
|
|
23
|
+
if (distance_percent > 0) {
|
|
24
|
+
const posType = IsBuy ? 'long' : 'short';
|
|
25
|
+
if (!this.instrumentData[InstrumentID]) this.instrumentData[InstrumentID] = {};
|
|
26
|
+
if (!this.instrumentData[InstrumentID][posType]) {
|
|
27
|
+
this.instrumentData[InstrumentID][posType] = {
|
|
28
|
+
distance_percent_sum: 0,
|
|
29
|
+
distance_value_sum: 0,
|
|
30
|
+
count: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const agg = this.instrumentData[InstrumentID][posType];
|
|
34
|
+
agg.distance_percent_sum += distance_percent;
|
|
35
|
+
agg.distance_value_sum += distance_value;
|
|
36
|
+
agg.count++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getResult() {
|
|
42
|
+
if (Object.keys(this.instrumentData).length === 0) return {};
|
|
43
|
+
if (!this.instrumentToTicker) {
|
|
44
|
+
const mappings = await loadInstrumentMappings();
|
|
45
|
+
this.instrumentToTicker = mappings.instrumentToTicker;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const instrumentId in this.instrumentData) {
|
|
50
|
+
const ticker = this.instrumentToTicker[instrumentId] || `unknown_${instrumentId}`;
|
|
51
|
+
if (!result[ticker]) result[ticker] = {};
|
|
52
|
+
|
|
53
|
+
for (const posType in this.instrumentData[instrumentId]) {
|
|
54
|
+
const data = this.instrumentData[instrumentId][posType];
|
|
55
|
+
// REFACTOR: Perform final calculation and return in standardized format.
|
|
56
|
+
if (data.count > 0) {
|
|
57
|
+
result[ticker][posType] = {
|
|
58
|
+
average_distance_percent: data.distance_percent_sum / data.count,
|
|
59
|
+
average_distance_value: data.distance_value_sum / data.count,
|
|
60
|
+
count: data.count
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
reset() {
|
|
69
|
+
this.instrumentData = {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = StopLossDistanceByTicker;
|