@gearbox-protocol/sdk 18.2.0-next.13 → 18.2.0-next.14
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/dist/cjs/permissionless/api/index.js +2 -1
- package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +40 -5
- package/dist/cjs/permissionless/api/oracles/index.js +2 -1
- package/dist/cjs/permissionless/api/oracles/score/formula.js +19 -24
- package/dist/cjs/permissionless/api/oracles/score/index.js +1 -1
- package/dist/cjs/permissionless/api/oracles/score/run.js +186 -105
- package/dist/cjs/permissionless/api/oracles/stress/index.js +1 -0
- package/dist/cjs/permissionless/api/oracles/stress/run.js +63 -13
- package/dist/cjs/permissionless/index.js +2 -1
- package/dist/esm/permissionless/api/index.js +4 -4
- package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +41 -6
- package/dist/esm/permissionless/api/oracles/index.js +4 -4
- package/dist/esm/permissionless/api/oracles/score/formula.js +20 -24
- package/dist/esm/permissionless/api/oracles/score/index.js +3 -3
- package/dist/esm/permissionless/api/oracles/score/run.js +187 -107
- package/dist/esm/permissionless/api/oracles/stress/index.js +2 -2
- package/dist/esm/permissionless/api/oracles/stress/run.js +64 -15
- package/dist/esm/permissionless/index.js +4 -4
- package/dist/types/permissionless/api/index.d.ts +5 -5
- package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +30 -6
- package/dist/types/permissionless/api/oracles/index.d.ts +5 -5
- package/dist/types/permissionless/api/oracles/score/formula.d.ts +14 -17
- package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -4
- package/dist/types/permissionless/api/oracles/score/run.d.ts +28 -10
- package/dist/types/permissionless/api/oracles/score/types.d.ts +33 -28
- package/dist/types/permissionless/api/oracles/stress/index.d.ts +2 -2
- package/dist/types/permissionless/api/oracles/stress/run.d.ts +11 -0
- package/dist/types/permissionless/index.d.ts +5 -5
- package/package.json +1 -1
|
@@ -72,18 +72,7 @@ async function readForest(client, compressor, oracles) {
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
const { priceFeedTree } = state.result;
|
|
75
|
-
|
|
76
|
-
const address = node.baseParams.addr.toLowerCase();
|
|
77
|
-
if (nodes.has(address)) continue;
|
|
78
|
-
nodes.set(address, {
|
|
79
|
-
address,
|
|
80
|
-
contractType: typeName(node.baseParams.contractType),
|
|
81
|
-
version: Number(node.baseParams.version),
|
|
82
|
-
description: node.description,
|
|
83
|
-
underlyingFeeds: node.underlyingFeeds.map((feed) => feed.toLowerCase())
|
|
84
|
-
});
|
|
85
|
-
answers.set(address, node.answer.success ? node.answer.price : null);
|
|
86
|
-
}
|
|
75
|
+
addTree(priceFeedTree, nodes, answers);
|
|
87
76
|
});
|
|
88
77
|
return {
|
|
89
78
|
nodes,
|
|
@@ -92,6 +81,43 @@ async function readForest(client, compressor, oracles) {
|
|
|
92
81
|
};
|
|
93
82
|
}
|
|
94
83
|
/**
|
|
84
|
+
* The trees under a handful of feeds, flattened, whether or not any oracle
|
|
85
|
+
* holds them.
|
|
86
|
+
*
|
|
87
|
+
* One call for all of them: the compressor walks the feeds it is given the
|
|
88
|
+
* way it walks an oracle's, and answers with the same nodes.
|
|
89
|
+
*/
|
|
90
|
+
async function readFeedTrees(client, compressor, feeds) {
|
|
91
|
+
const tree = await client.readContract({
|
|
92
|
+
address: compressor,
|
|
93
|
+
abi: require_abi_compressors_priceFeedCompressor.priceFeedCompressorAbi,
|
|
94
|
+
functionName: "loadPriceFeedTree",
|
|
95
|
+
args: [feeds]
|
|
96
|
+
});
|
|
97
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
98
|
+
const answers = /* @__PURE__ */ new Map();
|
|
99
|
+
addTree(tree, nodes, answers);
|
|
100
|
+
return {
|
|
101
|
+
nodes,
|
|
102
|
+
answers
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** A tree's nodes into the flattened forest, the first copy of each kept. */
|
|
106
|
+
function addTree(tree, nodes, answers) {
|
|
107
|
+
for (const node of tree) {
|
|
108
|
+
const address = node.baseParams.addr.toLowerCase();
|
|
109
|
+
if (nodes.has(address)) continue;
|
|
110
|
+
nodes.set(address, {
|
|
111
|
+
address,
|
|
112
|
+
contractType: typeName(node.baseParams.contractType),
|
|
113
|
+
version: Number(node.baseParams.version),
|
|
114
|
+
description: node.description,
|
|
115
|
+
underlyingFeeds: node.underlyingFeeds.map((feed) => feed.toLowerCase())
|
|
116
|
+
});
|
|
117
|
+
answers.set(address, node.answer.success ? node.answer.price : null);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
95
121
|
* Where each rate-bearing feed reads its rate, and what it will accept.
|
|
96
122
|
*
|
|
97
123
|
* The baseline a rate source is perturbed from is the feed's own `lowerBound`
|
|
@@ -153,6 +179,18 @@ async function readRateTargets(client, feeds) {
|
|
|
153
179
|
bounds
|
|
154
180
|
};
|
|
155
181
|
}
|
|
182
|
+
/** Tries after the first before a refused read is given up on. */
|
|
183
|
+
const READ_RETRIES = 3;
|
|
184
|
+
/**
|
|
185
|
+
* A multicall failure that is the node's and not the feed's.
|
|
186
|
+
*
|
|
187
|
+
* A call that reverted inside the multicall is decoded from its own return
|
|
188
|
+
* data and carries no error of the `eth_call` around it; a chunk the node
|
|
189
|
+
* refused carries exactly that, for every call in the chunk.
|
|
190
|
+
*/
|
|
191
|
+
function isRefusal(error) {
|
|
192
|
+
return error instanceof viem.BaseError && error.walk((cause) => cause instanceof viem.CallExecutionError) !== null;
|
|
193
|
+
}
|
|
156
194
|
/**
|
|
157
195
|
* What every feed answers with one group's sources overridden.
|
|
158
196
|
*
|
|
@@ -165,7 +203,7 @@ async function readUnder(client, feeds, sources, multiplier, updatedAt, freshen
|
|
|
165
203
|
const driven = require_permissionless_api_oracles_stress_override.groupOverride(sources, multiplier, updatedAt);
|
|
166
204
|
const taken = new Set(driven.map((override) => override.address.toLowerCase()));
|
|
167
205
|
const overrides = [...require_permissionless_api_oracles_stress_override.groupOverride(freshen, 1, updatedAt).filter((override) => !taken.has(override.address.toLowerCase())), ...driven];
|
|
168
|
-
const
|
|
206
|
+
const readOnce = () => client.multicall({
|
|
169
207
|
contracts: feeds.map((address) => ({
|
|
170
208
|
address,
|
|
171
209
|
abi: aggregatorAbi,
|
|
@@ -174,6 +212,17 @@ async function readUnder(client, feeds, sources, multiplier, updatedAt, freshen
|
|
|
174
212
|
allowFailure: true,
|
|
175
213
|
stateOverride: overrides.length > 0 ? overrides : void 0
|
|
176
214
|
});
|
|
215
|
+
let results = [];
|
|
216
|
+
for (let attempt = 0;; attempt++) {
|
|
217
|
+
results = await readOnce();
|
|
218
|
+
const refused = results.find((result) => result.status === "failure" && isRefusal(result.error));
|
|
219
|
+
if (!refused) break;
|
|
220
|
+
if (attempt >= READ_RETRIES) {
|
|
221
|
+
const reason = refused.error instanceof viem.BaseError ? refused.error.walk().shortMessage : String(refused.error);
|
|
222
|
+
throw new Error(`the node would not answer a read of ${feeds.length} feeds after 4 tries: ${reason}`, { cause: refused.error });
|
|
223
|
+
}
|
|
224
|
+
await new Promise((resolve) => setTimeout(resolve, 500 * 2 ** attempt));
|
|
225
|
+
}
|
|
177
226
|
return new Map(feeds.map((feed, i) => {
|
|
178
227
|
const result = results[i];
|
|
179
228
|
return [feed, result?.status === "success" ? result.result[1] ?? null : null];
|
|
@@ -429,6 +478,7 @@ async function runStressTest({ client, chainId, slots, compressor, scenarios = D
|
|
|
429
478
|
//#endregion
|
|
430
479
|
exports.DEFAULT_SCENARIOS = DEFAULT_SCENARIOS;
|
|
431
480
|
exports.RATE_FEED_TYPE = RATE_FEED_TYPE;
|
|
481
|
+
exports.readFeedTrees = readFeedTrees;
|
|
432
482
|
exports.readForest = readForest;
|
|
433
483
|
exports.readRateTargets = readRateTargets;
|
|
434
484
|
exports.readUnder = readUnder;
|
|
@@ -91,7 +91,6 @@ exports.PriceFeedSaltMissingError = require_permissionless_api_errors.PriceFeedS
|
|
|
91
91
|
exports.PriceFeedStalenessUnknownError = require_permissionless_api_errors.PriceFeedStalenessUnknownError;
|
|
92
92
|
exports.PriceFeedStoreContract = require_permissionless_bindings_price_feed_store.PriceFeedStoreContract;
|
|
93
93
|
exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
|
|
94
|
-
exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
|
|
95
94
|
exports.RoutingManagerContract = require_permissionless_bindings_router_routing_manager.RoutingManagerContract;
|
|
96
95
|
exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
|
|
97
96
|
exports.TOKEN_COMPRESSOR = require_permissionless_utils_literals.TOKEN_COMPRESSOR;
|
|
@@ -137,11 +136,13 @@ exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
|
|
|
137
136
|
exports.priceFeedSalt = require_permissionless_api_deploy_PermissionlessDeploy.priceFeedSalt;
|
|
138
137
|
exports.priceFeedSetupParams = require_permissionless_bindings_pricefeeds_index.priceFeedSetupParams;
|
|
139
138
|
exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
|
|
139
|
+
exports.readFeedTrees = require_permissionless_api_oracles_stress_run.readFeedTrees;
|
|
140
140
|
exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
|
|
141
141
|
exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
|
|
142
142
|
exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
|
|
143
143
|
exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
|
|
144
144
|
exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
|
|
145
|
+
exports.runFeedPairScore = require_permissionless_api_oracles_score_run.runFeedPairScore;
|
|
145
146
|
exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
|
|
146
147
|
exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
|
|
147
148
|
exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
|
|
@@ -9,13 +9,13 @@ import "./deploy/index.js";
|
|
|
9
9
|
import { classifySources, sourcesByFeed } from "./oracles/stress/classify.js";
|
|
10
10
|
import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./oracles/stress/override.js";
|
|
11
11
|
import { scoreGroup, slotHarm, verdictOf } from "./oracles/stress/score.js";
|
|
12
|
-
import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, readUnder, runStressTest } from "./oracles/stress/run.js";
|
|
13
|
-
import {
|
|
14
|
-
import { runAssetScores } from "./oracles/score/run.js";
|
|
12
|
+
import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readFeedTrees, readForest, readRateTargets, readUnder, runStressTest } from "./oracles/stress/run.js";
|
|
13
|
+
import { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./oracles/score/formula.js";
|
|
14
|
+
import { runAssetScores, runFeedPairScore } from "./oracles/score/run.js";
|
|
15
15
|
import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./oracles/PermissionlessOracles.js";
|
|
16
16
|
import { InstanceOwnerTransactions } from "./transactions/InstanceOwnerTransactions.js";
|
|
17
17
|
import { GearboxPermissionless } from "./GearboxPermissionless.js";
|
|
18
18
|
import "./oracles/index.js";
|
|
19
19
|
import "./transactions/index.js";
|
|
20
20
|
import "./types.js";
|
|
21
|
-
export { ChainsNamespace, DEFAULT_DEPLOY_SALT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, GearboxPermissionless, InstanceOwnerTransactions, PermissionlessAnalytics, PermissionlessChainUnreachableError, PermissionlessDeploy, PermissionlessNotAttachedError, PermissionlessOracles, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError, RATE_FEED_TYPE,
|
|
21
|
+
export { ChainsNamespace, DEFAULT_DEPLOY_SALT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, GearboxPermissionless, InstanceOwnerTransactions, PermissionlessAnalytics, PermissionlessChainUnreachableError, PermissionlessDeploy, PermissionlessNotAttachedError, PermissionlessOracles, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError, RATE_FEED_TYPE, SCORE_MULTIPLIER, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, deriveEffectiveStalenessPeriod, factorOf, finalPrice, groupOverride, perturb, priceFeedSalt, rateStub, readFeedTrees, readForest, readRateTargets, readUnder, roundDataStub, runAssetScores, runFeedPairScore, runStressTest, scoreGroup, scoreOfFactor, slotHarm, sourcesByFeed, verdictOf, worstOf };
|
|
@@ -10,7 +10,7 @@ import { getFeedDecimals, getLatestPrices, getPrices } from "../../utils/price-u
|
|
|
10
10
|
import { byCentrality, feedKey, pricesByFeed, toCap, totalQuotas } from "./enrich.js";
|
|
11
11
|
import { assetDetailsSchema, assetSummariesSchema, marketSlotsSchema, priceFeedDetailsSchema, priceFeedStoresSchema } from "./schemas.js";
|
|
12
12
|
import { runStressTest } from "./stress/run.js";
|
|
13
|
-
import { runAssetScores } from "./score/run.js";
|
|
13
|
+
import { runAssetScores, runFeedPairScore } from "./score/run.js";
|
|
14
14
|
import { erc20Abi } from "viem";
|
|
15
15
|
//#region src/permissionless/api/oracles/PermissionlessOracles.ts
|
|
16
16
|
const GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = { 143: 100000000n };
|
|
@@ -359,14 +359,16 @@ var PermissionlessOracles = class extends AbstractOffchainNamespace {
|
|
|
359
359
|
* three leaves anything to simulate.
|
|
360
360
|
**/
|
|
361
361
|
/**
|
|
362
|
-
* Every asset's
|
|
362
|
+
* Every asset's manipulation resistance: how far, from 1 to 10 times, a
|
|
363
|
+
* lying source can push the price its markets hold for it.
|
|
363
364
|
*
|
|
364
365
|
* Each source group behind an asset's feeds — Chainlink, Redstone, Lido and
|
|
365
|
-
* so on — is made in turn to answer
|
|
366
|
+
* so on — is made in turn to answer ten times what it answers now, and a
|
|
366
367
|
* market scores by how far that moves its final price, `min(main,
|
|
367
|
-
* reserve)
|
|
368
|
-
*
|
|
369
|
-
*
|
|
368
|
+
* reserve)`, as a multiple either way: 1 for not at all, 1.04 for 4%, 10
|
|
369
|
+
* for the whole lie. A group under which the price stops answering moves
|
|
370
|
+
* nothing and is listed rather than scored. An asset scores its worst
|
|
371
|
+
* running market, underlying or collateral.
|
|
370
372
|
*
|
|
371
373
|
* Simulated on the chain as it stands, through state overrides in
|
|
372
374
|
* `eth_call`: one call per source group per chain, so the node behind the
|
|
@@ -408,6 +410,39 @@ var PermissionlessOracles = class extends AbstractOffchainNamespace {
|
|
|
408
410
|
}
|
|
409
411
|
}))).flat();
|
|
410
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* The manipulation resistance of any main feed and reserve on a chain,
|
|
415
|
+
* scored the way {@link assetScores} scores a market holding them — for
|
|
416
|
+
* trying a pair before any market does.
|
|
417
|
+
*
|
|
418
|
+
* ```ts
|
|
419
|
+
* const pair = await permissionless.oracles.feedPairScore({
|
|
420
|
+
* chainId: 1,
|
|
421
|
+
* main: "0x…",
|
|
422
|
+
* reserve: "0x…",
|
|
423
|
+
* });
|
|
424
|
+
* ```
|
|
425
|
+
*
|
|
426
|
+
* Reads the chain directly, through state overrides in `eth_call`, so the
|
|
427
|
+
* client has to have attached and the node behind it has to accept them.
|
|
428
|
+
*
|
|
429
|
+
* @throws When the client has not attached, when the chain has no client or
|
|
430
|
+
* no PriceFeedCompressor, or when there is nothing to score: a feed the
|
|
431
|
+
* compressor cannot read, a `ZeroPriceFeed` main, or a final price that does
|
|
432
|
+
* not answer.
|
|
433
|
+
**/
|
|
434
|
+
async feedPairScore({ chainId, main, reserve }) {
|
|
435
|
+
const client = this.#chains.client(chainId);
|
|
436
|
+
const compressor = await new AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(AP_PRICE_FEED_COMPRESSOR);
|
|
437
|
+
return runFeedPairScore({
|
|
438
|
+
client,
|
|
439
|
+
chainId,
|
|
440
|
+
compressor,
|
|
441
|
+
main,
|
|
442
|
+
reserve,
|
|
443
|
+
logger: this.logger ?? void 0
|
|
444
|
+
});
|
|
445
|
+
}
|
|
411
446
|
async stressTest({ chainId, ...options }) {
|
|
412
447
|
const client = this.#chains.client(chainId);
|
|
413
448
|
const compressor = await new AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(AP_PRICE_FEED_COMPRESSOR);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { classifySources, sourcesByFeed } from "./stress/classify.js";
|
|
2
2
|
import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./stress/override.js";
|
|
3
3
|
import { scoreGroup, slotHarm, verdictOf } from "./stress/score.js";
|
|
4
|
-
import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, readUnder, runStressTest } from "./stress/run.js";
|
|
5
|
-
import {
|
|
6
|
-
import { runAssetScores } from "./score/run.js";
|
|
4
|
+
import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readFeedTrees, readForest, readRateTargets, readUnder, runStressTest } from "./stress/run.js";
|
|
5
|
+
import { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./score/formula.js";
|
|
6
|
+
import { runAssetScores, runFeedPairScore } from "./score/run.js";
|
|
7
7
|
import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
|
|
8
8
|
import "./score/index.js";
|
|
9
9
|
import "./stress/index.js";
|
|
10
10
|
import "./types.js";
|
|
11
|
-
export { DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles, RATE_FEED_TYPE,
|
|
11
|
+
export { DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles, RATE_FEED_TYPE, SCORE_MULTIPLIER, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, factorOf, finalPrice, groupOverride, perturb, rateStub, readFeedTrees, readForest, readRateTargets, readUnder, roundDataStub, runAssetScores, runFeedPairScore, runStressTest, scoreGroup, scoreOfFactor, slotHarm, sourcesByFeed, verdictOf, worstOf };
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
//#region src/permissionless/api/oracles/score/formula.ts
|
|
2
2
|
/** What every source of a group is made to answer, as a multiple of now. */
|
|
3
|
-
const SCORE_MULTIPLIER =
|
|
3
|
+
const SCORE_MULTIPLIER = 10;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* How far the final price moved, as a multiple of where it was, either way:
|
|
6
|
+
* 1 for a price that did not move, 1.04 for one 4% off, and
|
|
7
|
+
* {@link SCORE_MULTIPLIER} for one that took the whole lie — the most it can
|
|
8
|
+
* take from one leg, and where a price over two legs of one group, moved
|
|
9
|
+
* further, is held too.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
* liquidated either until the feed is replaced.
|
|
9
|
-
*/
|
|
10
|
-
const REVERT_SCORE = 5;
|
|
11
|
-
/**
|
|
12
|
-
* 0 to 10 by how far the final price moved, on a log scale against the lie
|
|
13
|
-
* itself: 10 for a price that did not move, 0 for one that moved by the full
|
|
14
|
-
* {@link SCORE_MULTIPLIER} or more, either way. Rounded to a half point.
|
|
15
|
-
*
|
|
16
|
-
* A bound holding a price within a few percent keeps a 10; a price that let
|
|
17
|
-
* half the lie through, ×√5, scores 5.
|
|
11
|
+
* `null` for a price that stopped answering, which is not a move.
|
|
18
12
|
*/
|
|
19
13
|
function scoreOfFactor(factor) {
|
|
20
|
-
if (factor === null || !Number.isFinite(factor) || factor <= 0) return
|
|
21
|
-
const
|
|
22
|
-
return Math.
|
|
14
|
+
if (factor === null || !Number.isFinite(factor) || factor <= 0) return null;
|
|
15
|
+
const move = factor >= 1 ? factor : 1 / factor;
|
|
16
|
+
return Math.min(10, move);
|
|
23
17
|
}
|
|
24
18
|
/**
|
|
25
19
|
* The price a market settles on: the lower of main and reserve, or the main
|
|
@@ -37,19 +31,21 @@ function factorOf(base, stressed) {
|
|
|
37
31
|
return Number(stressed) / Number(base);
|
|
38
32
|
}
|
|
39
33
|
/**
|
|
40
|
-
* The market's score and the group it comes from: the
|
|
41
|
-
*
|
|
42
|
-
* feeds the price at all
|
|
34
|
+
* The market's score and the group it comes from: the furthest any group
|
|
35
|
+
* moved its price. 1 where nothing moved it — no group did, or no source
|
|
36
|
+
* feeds the price at all.
|
|
37
|
+
*
|
|
38
|
+
* A group under which the price stops answering moves nothing and does not
|
|
39
|
+
* set the score; it is listed after those that do, for a reader to see.
|
|
43
40
|
*/
|
|
44
41
|
function worstOf(impacts) {
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const worst = sorted[0] ?? null;
|
|
42
|
+
const sorted = [...impacts].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
43
|
+
const worst = sorted.find((impact) => impact.score !== null) ?? null;
|
|
48
44
|
return {
|
|
49
|
-
score: worst?.score ??
|
|
45
|
+
score: worst?.score ?? 1,
|
|
50
46
|
impacts: sorted,
|
|
51
47
|
worst
|
|
52
48
|
};
|
|
53
49
|
}
|
|
54
50
|
//#endregion
|
|
55
|
-
export {
|
|
51
|
+
export { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { runAssetScores } from "./run.js";
|
|
1
|
+
import { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./formula.js";
|
|
2
|
+
import { runAssetScores, runFeedPairScore } from "./run.js";
|
|
3
3
|
import "./types.js";
|
|
4
|
-
export {
|
|
4
|
+
export { SCORE_MULTIPLIER, factorOf, finalPrice, runAssetScores, runFeedPairScore, scoreOfFactor, worstOf };
|