bulltrackers-module 1.0.281 → 1.0.283

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.
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @fileoverview Analyzes calculation behavior via Monte Carlo simulation
3
+ * to generate "Loose" but mathematically sound contracts.
4
+ */
5
+ const SimRunner = require('../simulation/SimRunner');
6
+ const { MathPrimitives } = require('../layers/mathematics');
7
+ const { normalizeName } = require('../utils/utils');
8
+
9
+ class ContractDiscoverer {
10
+
11
+ static async generateContract(calcManifest, fullManifestMap, iterations = 50) {
12
+ console.log(`[ContractDiscoverer] 🕵️‍♀️ Learning behavior for: ${calcManifest.name}`);
13
+
14
+ const samples = [];
15
+ const errors = [];
16
+
17
+ // 1. Monte Carlo Simulation
18
+ // Run the code against 50 different "universes" of data to see how it behaves.
19
+ for (let i = 0; i < iterations; i++) {
20
+ try {
21
+ // We use your existing SimRunner, which uses Fabricator
22
+ // The SimRunner needs to return the RAW result, not the hash.
23
+ // You might need a small helper in SimRunner or just instantiate directly here:
24
+ const result = await this._runSimulationRaw(calcManifest, fullManifestMap, i);
25
+ if (result) samples.push(result);
26
+ } catch (e) {
27
+ errors.push(e.message);
28
+ }
29
+ }
30
+
31
+ if (samples.length < 5) {
32
+ console.warn(`[ContractDiscoverer] ⚠️ Insufficient samples for ${calcManifest.name}. Skipping.`);
33
+ return null;
34
+ }
35
+
36
+ // 2. Statistical Inference
37
+ // We now analyze the 50 outputs to find "Invariants"
38
+ return this._inferContractFromSamples(samples, calcManifest.type);
39
+ }
40
+
41
+ // Helper to bypass the hashing logic of SimRunner and get raw object
42
+ static async _runSimulationRaw(manifest, map, seed) {
43
+ const Fabricator = require('../simulation/Fabricator');
44
+ const fabricator = new Fabricator(manifest.name + '_seed_' + seed);
45
+ const context = await fabricator.generateContext(manifest, map, seed);
46
+ const instance = new manifest.class();
47
+ await instance.process(context);
48
+ return instance.getResult ? await instance.getResult() : (instance.results || {});
49
+ }
50
+
51
+ static _inferContractFromSamples(samples, type) {
52
+ // Flatten samples if it's a Standard (Batch) calculation
53
+ // We want to analyze "What does A USER result look like?"
54
+ let flattened = samples;
55
+ if (type === 'standard') {
56
+ flattened = [];
57
+ samples.forEach(batch => {
58
+ Object.values(batch).forEach(userResult => flattened.push(userResult));
59
+ });
60
+ }
61
+
62
+ // Initialize Rule Set
63
+ const contract = {
64
+ requiredKeys: new Set(),
65
+ numericBounds: {}, // { min, max, isInteger }
66
+ distributions: {}, // { mean, stdDev }
67
+ enums: {}, // { allowedValues }
68
+ dataTypes: {} // { key: 'number' | 'string' | 'object' }
69
+ };
70
+
71
+ if (flattened.length === 0) return null;
72
+
73
+ // A. Structural Analysis (Keys & Types)
74
+ const first = flattened[0];
75
+ if (typeof first === 'object' && first !== null) {
76
+ Object.keys(first).forEach(key => contract.requiredKeys.add(key));
77
+
78
+ Object.keys(first).forEach(key => {
79
+ contract.dataTypes[key] = typeof first[key];
80
+
81
+ // Track all values for this key to find bounds
82
+ const values = flattened.map(item => item[key]).filter(v => v !== null && v !== undefined);
83
+
84
+ // B. Numeric Analysis (The "Power" part)
85
+ if (typeof first[key] === 'number') {
86
+ this._analyzeNumericField(key, values, contract);
87
+ }
88
+
89
+ // C. Categorical Analysis
90
+ if (typeof first[key] === 'string') {
91
+ const unique = new Set(values);
92
+ if (unique.size < 10) { // If only a few distinct strings, it's an Enum
93
+ contract.enums[key] = Array.from(unique);
94
+ }
95
+ }
96
+ });
97
+ }
98
+
99
+ return {
100
+ ...contract,
101
+ requiredKeys: Array.from(contract.requiredKeys)
102
+ };
103
+ }
104
+
105
+ static _analyzeNumericField(key, values, contract) {
106
+ if (values.length === 0) return;
107
+
108
+ const min = Math.min(...values);
109
+ const max = Math.max(...values);
110
+ const avg = MathPrimitives.average(values);
111
+ const dev = MathPrimitives.standardDeviation(values);
112
+
113
+ // 1. Detect "Hard" Physics Limits (Probability, Ratios)
114
+ // If the value NEVER goes below 0 or above 1 in 50 runs, assume it's a Ratio.
115
+ // We assume "Financial Volatility" creates large numbers, but "Ratios" stay small.
116
+ const isRatio = (min >= 0 && max <= 1.0);
117
+ const isPercentage = (min >= 0 && max <= 100.0 && max > 1.0); // e.g. RSI
118
+ const isPositive = (min >= 0);
119
+
120
+ contract.numericBounds[key] = {
121
+ // We do NOT set strict upper bounds for financial values (Price, Vol, PnL)
122
+ // because crypto/finance can do 1000x.
123
+ // We ONLY set strict bounds for Ratios/Percentages.
124
+ min: isPositive ? 0 : -Infinity,
125
+ max: (isRatio ? 1.0 : (isPercentage ? 100.0 : Infinity))
126
+ };
127
+
128
+ // 2. Detect "Soft" Statistical Envelopes (6 Sigma)
129
+ // This handles the "Ridiculously Volatile" case.
130
+ // 6 Sigma covers 99.9999998% of cases even in non-normal distributions (Chebyshev's inequality).
131
+ // If a value is 20 Sigma away, it's likely a bug (e.g., Unix Timestamp interpreted as Price).
132
+ if (dev > 0) {
133
+ contract.distributions[key] = {
134
+ mean: avg,
135
+ stdDev: dev,
136
+ // "Loose" Envelope: 10 Standard Deviations allowed.
137
+ // This allows for massive volatility but catches data corruption.
138
+ sigmaLimit: 10
139
+ };
140
+ }
141
+ }
142
+ }
143
+
144
+ module.exports = ContractDiscoverer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.281",
3
+ "version": "1.0.283",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [