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.
- package/functions/computation-system/onboarding.md +154 -869
- package/functions/computation-system/persistence/ContractValidator.js +81 -0
- package/functions/computation-system/persistence/ResultCommitter.js +73 -13
- package/functions/computation-system/scripts/UpdateContracts.js +128 -0
- package/functions/computation-system/simulation/Fabricator.js +285 -0
- package/functions/computation-system/simulation/SeededRandom.js +41 -0
- package/functions/computation-system/simulation/SimRunner.js +51 -0
- package/functions/computation-system/tools/BuildReporter.js +199 -159
- package/functions/computation-system/tools/ContractDiscoverer.js +144 -0
- package/package.json +1 -1
|
@@ -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;
|