bulltrackers-module 1.0.211 → 1.0.213
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/functions/computation-system/controllers/computation_controller.js +199 -188
- package/functions/computation-system/helpers/computation_dispatcher.js +90 -90
- package/functions/computation-system/helpers/computation_manifest_builder.js +327 -283
- package/functions/computation-system/helpers/computation_pass_runner.js +168 -157
- package/functions/computation-system/helpers/computation_worker.js +85 -85
- package/functions/computation-system/helpers/orchestration_helpers.js +542 -558
- package/functions/computation-system/layers/extractors.js +279 -0
- package/functions/computation-system/layers/index.js +40 -0
- package/functions/computation-system/layers/math_primitives.js +743 -743
- package/functions/computation-system/layers/mathematics.js +397 -0
- package/functions/computation-system/layers/profiling.js +287 -0
- package/functions/computation-system/layers/validators.js +170 -0
- package/functions/computation-system/utils/schema_capture.js +63 -63
- package/functions/computation-system/utils/utils.js +22 -1
- package/functions/task-engine/helpers/update_helpers.js +17 -49
- package/package.json +1 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Extractors Layer
|
|
3
|
+
* Core access methods to raw data.
|
|
4
|
+
* FIX: HistoryExtractor handles both Legacy and Modern (Granular) schemas.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { SCHEMAS } = require('./profiling');
|
|
8
|
+
|
|
9
|
+
class TradeSeriesBuilder {
|
|
10
|
+
static buildReturnSeries(historyTrades) {
|
|
11
|
+
if (!historyTrades || !Array.isArray(historyTrades)) return [];
|
|
12
|
+
const closedTrades = historyTrades.filter(t => t.CloseDateTime && typeof t.NetProfit === 'number');
|
|
13
|
+
closedTrades.sort((a, b) => new Date(a.CloseDateTime) - new Date(b.CloseDateTime));
|
|
14
|
+
return closedTrades.map(t => t.NetProfit);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static buildCumulativeCurve(returnSeries, startValue = 100) {
|
|
18
|
+
const curve = [startValue];
|
|
19
|
+
let current = startValue;
|
|
20
|
+
for (const ret of returnSeries) {
|
|
21
|
+
current = current * (1 + (ret / 100));
|
|
22
|
+
curve.push(current);
|
|
23
|
+
}
|
|
24
|
+
return curve;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class DataExtractor {
|
|
29
|
+
static getPositions(portfolio, userType) {
|
|
30
|
+
if (!portfolio) return [];
|
|
31
|
+
if (userType === SCHEMAS.USER_TYPES.SPECULATOR) {
|
|
32
|
+
return portfolio.PublicPositions || [];
|
|
33
|
+
}
|
|
34
|
+
return portfolio.AggregatedPositions || [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static getInstrumentId(position) {
|
|
38
|
+
if (!position) return null;
|
|
39
|
+
return position.InstrumentID || position.instrumentId || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static getPositionId(position) {
|
|
43
|
+
if (!position) return null;
|
|
44
|
+
if (position.PositionID) return String(position.PositionID);
|
|
45
|
+
if (position.PositionId) return String(position.PositionId);
|
|
46
|
+
const instId = this.getInstrumentId(position);
|
|
47
|
+
const dir = this.getDirection(position);
|
|
48
|
+
if (instId) return `${instId}_${dir}`;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static getNetProfit(position) {
|
|
53
|
+
return position ? (position.NetProfit || 0) : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static getPositionWeight(position, userType) {
|
|
57
|
+
if (!position) return 0;
|
|
58
|
+
return position.Invested || position.Amount || 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static getPositionValuePct(position) {
|
|
62
|
+
return position ? (position.Value || 0) : 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static deriveEntryPrice(currentPrice, netProfitPct) {
|
|
66
|
+
if (!currentPrice || currentPrice <= 0) return 0;
|
|
67
|
+
if (netProfitPct <= -100) return Number.MAX_SAFE_INTEGER;
|
|
68
|
+
return currentPrice / (1 + (netProfitPct / 100.0));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static getPortfolioDailyPnl(portfolio, userType) {
|
|
72
|
+
if (!portfolio) return 0;
|
|
73
|
+
if (userType === SCHEMAS.USER_TYPES.SPECULATOR) {
|
|
74
|
+
return portfolio.NetProfit || 0;
|
|
75
|
+
}
|
|
76
|
+
if (portfolio.AggregatedPositionsByInstrumentTypeID) {
|
|
77
|
+
return portfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
|
|
78
|
+
return sum + ((agg.Value || 0) - (agg.Invested || 0));
|
|
79
|
+
}, 0);
|
|
80
|
+
}
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static getDirection(position) {
|
|
85
|
+
if (!position) return "Buy";
|
|
86
|
+
if (position.Direction) return position.Direction;
|
|
87
|
+
if (typeof position.IsBuy === 'boolean') return position.IsBuy ? "Buy" : "Sell";
|
|
88
|
+
return "Buy";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static getLeverage(position) { return position ? (position.Leverage || 1) : 1; }
|
|
92
|
+
static getOpenRate(position) { return position ? (position.OpenRate || 0) : 0; }
|
|
93
|
+
static getCurrentRate(position) { return position ? (position.CurrentRate || 0) : 0; }
|
|
94
|
+
static getStopLossRate(position) {
|
|
95
|
+
const rate = position ? (position.StopLossRate || 0) : 0;
|
|
96
|
+
if (rate > 0 && rate <= 0.01) return 0;
|
|
97
|
+
if (rate < 0) return 0;
|
|
98
|
+
return rate;
|
|
99
|
+
}
|
|
100
|
+
static getTakeProfitRate(position) {
|
|
101
|
+
const rate = position ? (position.TakeProfitRate || 0) : 0;
|
|
102
|
+
if (rate > 0 && rate <= 0.01) return 0;
|
|
103
|
+
return rate;
|
|
104
|
+
}
|
|
105
|
+
static getHasTSL(position) { return position ? (position.HasTrailingStopLoss === true) : false; }
|
|
106
|
+
static getOpenDateTime(position) { return (!position || !position.OpenDateTime) ? null : new Date(position.OpenDateTime); }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
class priceExtractor {
|
|
110
|
+
static getHistory(pricesContext, tickerOrId) {
|
|
111
|
+
if (!pricesContext || !pricesContext.history) return [];
|
|
112
|
+
let assetData = pricesContext.history[tickerOrId];
|
|
113
|
+
|
|
114
|
+
if (!assetData) {
|
|
115
|
+
const id = Object.keys(pricesContext.history).find(key => {
|
|
116
|
+
const data = pricesContext.history[key];
|
|
117
|
+
return data.ticker === tickerOrId;
|
|
118
|
+
});
|
|
119
|
+
if (id) assetData = pricesContext.history[id];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!assetData || !assetData.prices) return [];
|
|
123
|
+
const priceMap = assetData.prices;
|
|
124
|
+
const sortedDates = Object.keys(priceMap).sort((a, b) => a.localeCompare(b));
|
|
125
|
+
|
|
126
|
+
return sortedDates.map(date => ({
|
|
127
|
+
date: date,
|
|
128
|
+
price: priceMap[date]
|
|
129
|
+
})).filter(item => item.price > 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static getAllHistories(pricesContext) {
|
|
133
|
+
if (!pricesContext || !pricesContext.history) return new Map();
|
|
134
|
+
const results = new Map();
|
|
135
|
+
for (const [id, data] of Object.entries(pricesContext.history)) {
|
|
136
|
+
const ticker = data.ticker || id;
|
|
137
|
+
const history = this.getHistory(pricesContext, id);
|
|
138
|
+
if (history.length > 0) results.set(ticker, history);
|
|
139
|
+
}
|
|
140
|
+
return results;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class HistoryExtractor {
|
|
145
|
+
static getDailyHistory(user) {
|
|
146
|
+
return user?.history?.today || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static getTradedAssets(historyDoc) {
|
|
150
|
+
// 1. Try Modern Granular Data (Derive Assets from Trades)
|
|
151
|
+
if (historyDoc?.PublicHistoryPositions?.length) {
|
|
152
|
+
const trades = historyDoc.PublicHistoryPositions;
|
|
153
|
+
const assetsMap = new Map();
|
|
154
|
+
|
|
155
|
+
for (const t of trades) {
|
|
156
|
+
const instId = t.InstrumentID;
|
|
157
|
+
if (!instId) continue;
|
|
158
|
+
|
|
159
|
+
if (!assetsMap.has(instId)) {
|
|
160
|
+
assetsMap.set(instId, {
|
|
161
|
+
instrumentId: instId,
|
|
162
|
+
totalDuration: 0,
|
|
163
|
+
count: 0
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const asset = assetsMap.get(instId);
|
|
168
|
+
const open = new Date(t.OpenDateTime);
|
|
169
|
+
const close = new Date(t.CloseDateTime);
|
|
170
|
+
const durationMins = (close - open) / 60000;
|
|
171
|
+
|
|
172
|
+
if (durationMins > 0) {
|
|
173
|
+
asset.totalDuration += durationMins;
|
|
174
|
+
asset.count++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return Array.from(assetsMap.values()).map(a => ({
|
|
178
|
+
instrumentId: a.instrumentId,
|
|
179
|
+
avgHoldingTimeInMinutes: a.count > 0 ? (a.totalDuration / a.count) : 0
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Fallback to Legacy 'assets' array
|
|
184
|
+
if (historyDoc?.assets && Array.isArray(historyDoc.assets)) {
|
|
185
|
+
return historyDoc.assets;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static getInstrumentId(asset) {
|
|
192
|
+
return asset ? asset.instrumentId : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static getAvgHoldingTimeMinutes(asset) {
|
|
196
|
+
return asset ? (asset.avgHoldingTimeInMinutes || 0) : 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static getSummary(historyDoc) {
|
|
200
|
+
// 1. Try Modern Granular Data (Derive Summary)
|
|
201
|
+
if (historyDoc?.PublicHistoryPositions?.length) {
|
|
202
|
+
const trades = historyDoc.PublicHistoryPositions;
|
|
203
|
+
let totalTrades = trades.length;
|
|
204
|
+
let wins = 0;
|
|
205
|
+
let totalProf = 0;
|
|
206
|
+
let totalLoss = 0;
|
|
207
|
+
let profCount = 0;
|
|
208
|
+
let lossCount = 0;
|
|
209
|
+
let totalDur = 0;
|
|
210
|
+
|
|
211
|
+
for (const t of trades) {
|
|
212
|
+
if (t.NetProfit > 0) {
|
|
213
|
+
wins++;
|
|
214
|
+
totalProf += t.NetProfit;
|
|
215
|
+
profCount++;
|
|
216
|
+
} else if (t.NetProfit < 0) {
|
|
217
|
+
totalLoss += t.NetProfit;
|
|
218
|
+
lossCount++;
|
|
219
|
+
}
|
|
220
|
+
const open = new Date(t.OpenDateTime);
|
|
221
|
+
const close = new Date(t.CloseDateTime);
|
|
222
|
+
totalDur += (close - open) / 60000;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
totalTrades: totalTrades,
|
|
227
|
+
winRatio: totalTrades > 0 ? (wins / totalTrades) * 100 : 0,
|
|
228
|
+
avgProfitPct: profCount > 0 ? totalProf / profCount : 0,
|
|
229
|
+
avgLossPct: lossCount > 0 ? totalLoss / lossCount : 0,
|
|
230
|
+
avgHoldingTimeInMinutes: totalTrades > 0 ? totalDur / totalTrades : 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 2. Fallback to Legacy 'all' object
|
|
235
|
+
if (historyDoc?.all) {
|
|
236
|
+
return historyDoc.all;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
class InsightsExtractor {
|
|
244
|
+
static getInsights(context) {
|
|
245
|
+
return context.insights || context.daily_instrument_insights || [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static getInsightForInstrument(insights, instrumentId) {
|
|
249
|
+
if (!insights || !Array.isArray(insights)) return null;
|
|
250
|
+
return insights.find(i => i.instrumentId === instrumentId) || null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static getTotalOwners(insight) { return insight ? (insight.total || 0) : 0; }
|
|
254
|
+
static getLongPercent(insight) { return insight ? (insight.buy || 0) : 0; }
|
|
255
|
+
static getShortPercent(insight) { return insight ? (insight.sell || 0) : 0; }
|
|
256
|
+
static getGrowthPercent(insight) { return insight ? (insight.growth || 0) : 0; }
|
|
257
|
+
|
|
258
|
+
static getLongCount(insight) {
|
|
259
|
+
const total = this.getTotalOwners(insight);
|
|
260
|
+
const buyPct = this.getLongPercent(insight);
|
|
261
|
+
return Math.floor(total * (buyPct / 100));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static getShortCount(insight) {
|
|
265
|
+
const total = this.getTotalOwners(insight);
|
|
266
|
+
const sellPct = this.getShortPercent(insight);
|
|
267
|
+
return Math.floor(total * (sellPct / 100));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static getNetOwnershipChange(insight) {
|
|
271
|
+
const total = this.getTotalOwners(insight);
|
|
272
|
+
const growth = this.getGrowthPercent(insight);
|
|
273
|
+
if (total === 0) return 0;
|
|
274
|
+
const prevTotal = total / (1 + (growth / 100));
|
|
275
|
+
return Math.round(total - prevTotal);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = { DataExtractor, priceExtractor, HistoryExtractor, InsightsExtractor, TradeSeriesBuilder };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Layers Barrel File
|
|
3
|
+
* Aggregates all mathematical, extractor, and profiling primitives.
|
|
4
|
+
* Utilizes require-all to dynamically load new modules added to this directory.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const requireAll = require('require-all');
|
|
8
|
+
|
|
9
|
+
// 1. Manually Require Core Modules to ensure order and presence
|
|
10
|
+
const profiling = require('./profiling');
|
|
11
|
+
const extractors = require('./extractors');
|
|
12
|
+
const mathematics = require('./mathematics');
|
|
13
|
+
const validators = require('./validators');
|
|
14
|
+
|
|
15
|
+
// 2. Aggregate explicit exports
|
|
16
|
+
const coreExports = {
|
|
17
|
+
...profiling,
|
|
18
|
+
...extractors,
|
|
19
|
+
...mathematics,
|
|
20
|
+
...validators
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 3. Dynamic Loading (Optional/Future-Proofing)
|
|
24
|
+
// This loads any OTHER file in this directory that wasn't manually required above.
|
|
25
|
+
const dynamicModules = requireAll({
|
|
26
|
+
dirname: __dirname,
|
|
27
|
+
filter: /^(?!index\.js$|profiling\.js$|extractors\.js$|mathematics\.js$|validators\.js$).+\.js$/,
|
|
28
|
+
resolve: (mod) => mod
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Flatten dynamic modules into the export object
|
|
32
|
+
const dynamicExports = {};
|
|
33
|
+
Object.values(dynamicModules).forEach(moduleExports => {
|
|
34
|
+
Object.assign(dynamicExports, moduleExports);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
...coreExports,
|
|
39
|
+
...dynamicExports
|
|
40
|
+
};
|