@alexbosworth/fiat 2.0.0 → 3.0.0
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/CHANGELOG.md +22 -0
- package/README.md +93 -1
- package/arrays/index.js +3 -0
- package/arrays/sort_by.js +44 -0
- package/chain/add_days.js +22 -0
- package/chain/blocks/block_header_hash.js +29 -0
- package/chain/blocks/block_header_time.js +25 -0
- package/chain/blocks/find_complete_day.js +90 -0
- package/chain/blocks/find_height_for_time.js +93 -0
- package/chain/blocks/get_block_hash.js +60 -0
- package/chain/blocks/get_block_header.js +45 -0
- package/chain/blocks/get_block_payments.js +100 -0
- package/chain/blocks/get_blocks_for_date.js +110 -0
- package/chain/blocks/get_median_time.js +92 -0
- package/chain/blocks/index.js +19 -0
- package/chain/blocks/payment_outputs_from_block.js +178 -0
- package/chain/blocks/sample_heights.js +33 -0
- package/chain/blocks/window_heights.js +34 -0
- package/chain/chain_rates.js +129 -0
- package/chain/constants.json +26 -0
- package/chain/estimates/check_known_estimates.js +119 -0
- package/chain/estimates/current_estimates.js +66 -0
- package/chain/estimates/estimate_and_remember.js +199 -0
- package/chain/estimates/estimate_day_price.js +361 -0
- package/chain/estimates/estimate_record.js +107 -0
- package/chain/estimates/estimate_windows.js +246 -0
- package/chain/estimates/find_starting_day.js +86 -0
- package/chain/estimates/forget_estimates.js +113 -0
- package/chain/estimates/get_chain_price.js +167 -0
- package/chain/estimates/get_price_for_date.js +607 -0
- package/chain/estimates/get_price_from_blocks.js +281 -0
- package/chain/estimates/get_provisional_price.js +163 -0
- package/chain/estimates/index.js +25 -0
- package/chain/estimates/is_alias_ratio.js +38 -0
- package/chain/estimates/is_supported_estimate.js +81 -0
- package/chain/estimates/nearest_estimate.js +49 -0
- package/chain/estimates/price_window.js +25 -0
- package/chain/estimates/remember_estimate.js +83 -0
- package/chain/estimates/store_estimate.js +198 -0
- package/chain/histogram/best_position.js +63 -0
- package/chain/histogram/histogram_excess.js +50 -0
- package/chain/histogram/histogram_peak.js +87 -0
- package/chain/histogram/implied_prices_histogram.js +53 -0
- package/chain/histogram/index.js +33 -0
- package/chain/histogram/is_round_tokens.js +26 -0
- package/chain/histogram/kernel_width.js +25 -0
- package/chain/histogram/lower_bound.js +28 -0
- package/chain/histogram/noise_of_neighbors.js +28 -0
- package/chain/histogram/noise_strength.js +35 -0
- package/chain/histogram/noise_strengths.js +29 -0
- package/chain/histogram/outputs_histogram.js +80 -0
- package/chain/histogram/parabola_peak.js +28 -0
- package/chain/histogram/payments_near_position.js +31 -0
- package/chain/histogram/peak_width.js +37 -0
- package/chain/histogram/position_score.js +38 -0
- package/chain/histogram/score_position.js +94 -0
- package/chain/histogram/smooth_bins.js +29 -0
- package/chain/histogram/sorted_neighbors.js +89 -0
- package/chain/histogram/spike_strengths.js +33 -0
- package/chain/histogram/template_agreement.js +27 -0
- package/chain/index.js +3 -0
- package/chain/known/earliest_supported_day.js +12 -0
- package/chain/known/index.js +11 -0
- package/chain/known/known_prices.js +43 -0
- package/chain/known/known_prices.json +1836 -0
- package/chain/known/method_version.js +26 -0
- package/chain/known/parse_known_prices.js +67 -0
- package/chain/outputs/alias_shifts.js +31 -0
- package/chain/outputs/cap_near_round_values.js +83 -0
- package/chain/outputs/cap_repeated_values.js +33 -0
- package/chain/outputs/dollar_bin_for_price.js +19 -0
- package/chain/outputs/estimate_from_outputs.js +321 -0
- package/chain/outputs/index.js +11 -0
- package/chain/outputs/peak_around_price.js +86 -0
- package/chain/outputs/price_for_dollar_bin.js +21 -0
- package/chain/outputs/price_from_outputs.js +98 -0
- package/chain/outputs/refine_price.js +73 -0
- package/chain/outputs/round_amounts.js +49 -0
- package/chain/outputs/template.json +22 -0
- package/coinbase/constants.json +5 -0
- package/coinbase/get_coinbase_day_candles.js +155 -0
- package/coinbase/get_coinbase_historic_price.js +135 -0
- package/coinbase/index.js +7 -1
- package/dates/constants.json +3 -0
- package/dates/day_of_date.js +26 -0
- package/dates/index.js +5 -0
- package/dates/is_calendar_day.js +16 -0
- package/dates/time_of_date.js +61 -0
- package/docs/METHODOLOGY.md +984 -0
- package/index.js +2 -1
- package/package.json +29 -8
- package/rates/get_prices.js +51 -8
- package/storage/constants.json +4 -0
- package/storage/get_prices_file.js +169 -0
- package/storage/index.js +4 -0
- package/storage/is_prices.js +31 -0
- package/storage/put_prices_file.js +252 -0
- package/storage/temp_path.js +22 -0
- package/.gitattributes +0 -1
- package/.tm_properties +0 -1
- package/test/coinbase/test_get_coinbase_current_price.js +0 -77
- package/test/coindesk/test_get_coindesk_current_price.js +0 -95
- package/test/coingecko/test_get_coingecko_rates.js +0 -93
- package/test/rates/test_get_current_price.js +0 -74
- package/test/rates/test_get_prices.js +0 -78
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const {kernelBins} = require('./../constants');
|
|
2
|
+
const noiseOfNeighbors = require('./noise_of_neighbors');
|
|
3
|
+
const {noiseRadius} = require('./../constants');
|
|
4
|
+
|
|
5
|
+
const {max} = Math;
|
|
6
|
+
const {min} = Math;
|
|
7
|
+
|
|
8
|
+
/** Get the strength that noise alone would give a spike at a center
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
center: <Spike Center Bin Number>
|
|
12
|
+
excess: [<Share of Outputs Above Local Background Number>]
|
|
13
|
+
min_bin: <Lowest Observable Bin Number>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns
|
|
17
|
+
{
|
|
18
|
+
noise: <Spike Strength From Noise Alone Number>
|
|
19
|
+
}
|
|
20
|
+
*/
|
|
21
|
+
module.exports = args => {
|
|
22
|
+
const {center, excess} = args;
|
|
23
|
+
|
|
24
|
+
const start = max(Number(), args.min_bin, center - noiseRadius);
|
|
25
|
+
|
|
26
|
+
const end = max(start, min(excess.length, center + noiseRadius + 1));
|
|
27
|
+
|
|
28
|
+
// The neighbors on each side, leaving out the spike itself
|
|
29
|
+
const below = excess.slice(start, max(start, center - kernelBins));
|
|
30
|
+
const above = excess.slice(max(start, center + kernelBins + 1), end);
|
|
31
|
+
|
|
32
|
+
const sorted = below.concat(above).sort((a, b) => a - b);
|
|
33
|
+
|
|
34
|
+
return noiseOfNeighbors({sorted});
|
|
35
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const {kernelBins} = require('./../constants');
|
|
2
|
+
const noiseOfNeighbors = require('./noise_of_neighbors');
|
|
3
|
+
const {noiseRadius} = require('./../constants');
|
|
4
|
+
const sortedNeighbors = require('./sorted_neighbors');
|
|
5
|
+
|
|
6
|
+
/** Get the strength that noise alone would give a spike at each bin
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
excess: [<Share of Outputs Above Local Background Number>]
|
|
10
|
+
min_bin: <Lowest Observable Bin Number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@returns
|
|
14
|
+
{
|
|
15
|
+
noise: [<Spike Strength From Noise Alone Number>]
|
|
16
|
+
}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = args => {
|
|
19
|
+
// The neighbors of a bin are near it, leaving out the spike itself
|
|
20
|
+
const {values} = sortedNeighbors({
|
|
21
|
+
bins: args.excess,
|
|
22
|
+
exclude: kernelBins,
|
|
23
|
+
min_bin: args.min_bin,
|
|
24
|
+
radius: noiseRadius,
|
|
25
|
+
value: sorted => noiseOfNeighbors({sorted}).noise,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {noise: values};
|
|
29
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const isRoundTokens = require('./is_round_tokens');
|
|
2
|
+
const lowerBound = require('./lower_bound');
|
|
3
|
+
const {maxTokens} = require('./../constants');
|
|
4
|
+
const {minTokens} = require('./../constants');
|
|
5
|
+
const {roundTokensMultiple} = require('./../constants');
|
|
6
|
+
|
|
7
|
+
const binsPerDecade = 200;
|
|
8
|
+
const {ceil} = Math;
|
|
9
|
+
const {floor} = Math;
|
|
10
|
+
const {log10} = Math;
|
|
11
|
+
const {max} = Math;
|
|
12
|
+
const {min} = Math;
|
|
13
|
+
const minNeighborhood = 20;
|
|
14
|
+
const neighborhoodShare = 0.001;
|
|
15
|
+
const {round} = Math;
|
|
16
|
+
|
|
17
|
+
/** Make a normalized log scale histogram of output values
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
[is_counting_round]: <Count Round Values In Full Bool>
|
|
21
|
+
outputs: [<Output Tokens Number>]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@returns
|
|
25
|
+
{
|
|
26
|
+
bins: [<Share of Outputs in Bin Number>]
|
|
27
|
+
bins_per_decade: <Bins Per Factor of Ten Number>
|
|
28
|
+
}
|
|
29
|
+
*/
|
|
30
|
+
module.exports = args => {
|
|
31
|
+
const {outputs} = args;
|
|
32
|
+
|
|
33
|
+
const binFor = tokens => round(log10(tokens) * binsPerDecade);
|
|
34
|
+
const counts = Array(round(log10(maxTokens) * binsPerDecade) + 1).fill(0);
|
|
35
|
+
const rounded = new Map();
|
|
36
|
+
|
|
37
|
+
const included = outputs.filter(n => n >= minTokens && n <= maxTokens);
|
|
38
|
+
|
|
39
|
+
const plain = included.filter(tokens => {
|
|
40
|
+
// Exit early when the value is not round or round values count in full
|
|
41
|
+
if (!!args.is_counting_round || !isRoundTokens({tokens}).is_round) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
rounded.set(tokens, (rounded.get(tokens) || Number()) + 1);
|
|
46
|
+
|
|
47
|
+
return false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
plain.forEach(tokens => counts[binFor(tokens)]++);
|
|
51
|
+
|
|
52
|
+
const sorted = Float64Array.from(plain).sort();
|
|
53
|
+
|
|
54
|
+
// A round value counts for no more than its neighboring values suggest
|
|
55
|
+
rounded.forEach((count, tokens) => {
|
|
56
|
+
const reach = max(minNeighborhood, round(tokens * neighborhoodShare));
|
|
57
|
+
|
|
58
|
+
const high = tokens + reach;
|
|
59
|
+
const low = tokens - reach;
|
|
60
|
+
|
|
61
|
+
const start = lowerBound({sorted, value: low}).index;
|
|
62
|
+
const end = lowerBound({sorted, value: high + 1}).index;
|
|
63
|
+
|
|
64
|
+
const highRound = floor(high / roundTokensMultiple);
|
|
65
|
+
const lowRound = ceil(low / roundTokensMultiple);
|
|
66
|
+
|
|
67
|
+
const values = reach * 2 + 1 - (highRound - lowRound + 1);
|
|
68
|
+
|
|
69
|
+
const expected = (end - start) / max(1, values);
|
|
70
|
+
|
|
71
|
+
counts[binFor(tokens)] += min(count, expected);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const total = counts.reduce((sum, count) => sum + count, Number());
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
bins: counts.map(count => !total ? 0 : count / total),
|
|
78
|
+
bins_per_decade: binsPerDecade,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const {half} = require('./../constants');
|
|
2
|
+
|
|
3
|
+
const twice = 2;
|
|
4
|
+
|
|
5
|
+
/** Find the peak of a parabola fit through three evenly spaced points
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
above: <Value After Center Number>
|
|
9
|
+
below: <Value Before Center Number>
|
|
10
|
+
center: <Center Value Number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@returns
|
|
14
|
+
{
|
|
15
|
+
delta: <Peak Offset From Center In Units of Point Spacing Number>
|
|
16
|
+
}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = ({above, below, center}) => {
|
|
19
|
+
const curvature = below - twice * center + above;
|
|
20
|
+
|
|
21
|
+
// Exit early when the points do not form a peak
|
|
22
|
+
if (curvature >= 0) {
|
|
23
|
+
return {delta: 0};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// A negative zero is normalized to zero
|
|
27
|
+
return {delta: half * (below - above) / curvature || 0};
|
|
28
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const {max} = Math;
|
|
2
|
+
const {min} = Math;
|
|
3
|
+
const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
|
|
4
|
+
|
|
5
|
+
/** Count the implied prices above the background around a position
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
background: [<Background Count In Bin Number>]
|
|
9
|
+
counts: [<Implied Prices In Bin Count Number>]
|
|
10
|
+
kernel: <Smoothing Kernel Half Width in Bins Number>
|
|
11
|
+
position: <Bin Index Number>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@returns
|
|
15
|
+
{
|
|
16
|
+
above: <Counts Above the Background Number>
|
|
17
|
+
observed: <Counts Observed Number>
|
|
18
|
+
}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = ({background, counts, kernel, position}) => {
|
|
21
|
+
const start = max(Number(), position - kernel);
|
|
22
|
+
const end = min(counts.length, position + kernel + 1);
|
|
23
|
+
|
|
24
|
+
return range(start, end).reduce((sum, bin) => {
|
|
25
|
+
return {
|
|
26
|
+
above: sum.above + counts[bin] - background[bin],
|
|
27
|
+
observed: sum.observed + counts[bin],
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
{above: Number(), observed: Number()});
|
|
31
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const {half} = require('./../constants');
|
|
2
|
+
|
|
3
|
+
/** Measure the width of a peak in histogram bins at half of its height
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
bins: [<Bin Value Number>]
|
|
7
|
+
peak: <Peak Bin Index Number>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@returns
|
|
11
|
+
{
|
|
12
|
+
width: <Contiguous Bins At or Above Half the Peak Height Count Number>
|
|
13
|
+
}
|
|
14
|
+
*/
|
|
15
|
+
module.exports = ({bins, peak}) => {
|
|
16
|
+
const threshold = (bins[peak] || 0) * half;
|
|
17
|
+
|
|
18
|
+
// Exit early when there is no peak
|
|
19
|
+
if (!threshold) {
|
|
20
|
+
return {width: Number()};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isHigh = bin => bins[bin] !== undefined && bins[bin] >= threshold;
|
|
24
|
+
|
|
25
|
+
const lower = bins.slice(Number(), peak).reverse();
|
|
26
|
+
|
|
27
|
+
const left = lower.findIndex((value, gap) => !isHigh(peak - gap - 1));
|
|
28
|
+
|
|
29
|
+
const higher = bins.slice(peak + 1);
|
|
30
|
+
|
|
31
|
+
const right = higher.findIndex((value, gap) => !isHigh(peak + gap + 1));
|
|
32
|
+
|
|
33
|
+
const before = left === -1 ? peak : left;
|
|
34
|
+
const after = right === -1 ? bins.length - peak - 1 : right;
|
|
35
|
+
|
|
36
|
+
return {width: before + after + 1};
|
|
37
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const {kernelBins} = require('./../constants');
|
|
2
|
+
|
|
3
|
+
/** Score a dollar position by the strength of its spikes above their noise
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
dollar_bin: <Bin Where One Dollar Lands Number>
|
|
7
|
+
min_bin: <Lowest Observable Bin Number>
|
|
8
|
+
noise: [<Spike Strength From Noise Alone At Bin Number>]
|
|
9
|
+
shifts: [<Bins Above the Dollar Bin of Round USD Amount Number>]
|
|
10
|
+
strengths: [<Spike Strength At Bin Number>]
|
|
11
|
+
weights: [<Amount Weight Number>]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@returns
|
|
15
|
+
{
|
|
16
|
+
score: <Weighted Strength of Observable Spikes Above Noise Number>
|
|
17
|
+
}
|
|
18
|
+
*/
|
|
19
|
+
module.exports = args => {
|
|
20
|
+
const {noise, shifts, strengths, weights} = args;
|
|
21
|
+
|
|
22
|
+
const score = shifts.reduce((sum, shift, index) => {
|
|
23
|
+
const center = args.dollar_bin + shift;
|
|
24
|
+
|
|
25
|
+
const isAbove = center + kernelBins >= strengths.length;
|
|
26
|
+
const isBelow = center - kernelBins < args.min_bin;
|
|
27
|
+
|
|
28
|
+
// Exit early when the whole spike of the amount is not in the histogram
|
|
29
|
+
if (isAbove || isBelow) {
|
|
30
|
+
return sum;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return sum + (strengths[center] - noise[center]) * weights[index];
|
|
34
|
+
},
|
|
35
|
+
Number());
|
|
36
|
+
|
|
37
|
+
return {score};
|
|
38
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const {kernelBins} = require('./../constants');
|
|
2
|
+
|
|
3
|
+
const {isArray} = Array;
|
|
4
|
+
const {log10} = Math;
|
|
5
|
+
const {max} = Math;
|
|
6
|
+
const minPatternSpikes = 3;
|
|
7
|
+
const minPatternStrength = 0.2;
|
|
8
|
+
const {round} = Math;
|
|
9
|
+
|
|
10
|
+
/** Score a dollar position of the round USD pattern against histogram excess
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
amounts: [<Round USD Amount Number>]
|
|
14
|
+
bins_per_decade: <Bins Per Factor of Ten Number>
|
|
15
|
+
dollar_bin: <Bin Where One Dollar Lands Number>
|
|
16
|
+
min_bin: <Lowest Observable Bin Number>
|
|
17
|
+
noise: [<Spike Strength From Noise Alone At Bin Number>]
|
|
18
|
+
strengths: [<Spike Strength At Bin Number>]
|
|
19
|
+
weights: [<Amount Weight Number>]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@throws
|
|
23
|
+
<Error>
|
|
24
|
+
|
|
25
|
+
@returns
|
|
26
|
+
{
|
|
27
|
+
evidence: <Share of Weight That Was Observable Number>
|
|
28
|
+
is_pattern: <Position Shows a Multi-Denomination Pattern Bool>
|
|
29
|
+
observable: [<Amount Is Observable Bool>]
|
|
30
|
+
score: <Weighted Strength of Observable Spikes Above Noise Number>
|
|
31
|
+
strengths: [<Observable Spike Strength Above Noise or Zero Number>]
|
|
32
|
+
}
|
|
33
|
+
*/
|
|
34
|
+
module.exports = args => {
|
|
35
|
+
if (!isArray(args.strengths)) {
|
|
36
|
+
throw new Error('ExpectedSpikeStrengthsToScorePosition');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!isArray(args.noise)) {
|
|
40
|
+
throw new Error('ExpectedNoiseStrengthsToScorePosition');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (args.noise.length !== args.strengths.length) {
|
|
44
|
+
throw new Error('ExpectedNoiseStrengthOfEveryBinToScorePosition');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const {amounts, strengths, weights} = args;
|
|
48
|
+
|
|
49
|
+
const binsPerDecade = args.bins_per_decade;
|
|
50
|
+
const dollarBin = args.dollar_bin;
|
|
51
|
+
|
|
52
|
+
const centers = amounts.map(usd => {
|
|
53
|
+
return dollarBin + round(log10(usd) * binsPerDecade);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// An amount is observable when its whole spike is within the histogram
|
|
57
|
+
const observable = centers.map(center => {
|
|
58
|
+
const high = center + kernelBins < strengths.length;
|
|
59
|
+
const low = center - kernelBins >= args.min_bin;
|
|
60
|
+
|
|
61
|
+
return high && low;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Spike strengths above the strength that noise alone would give them
|
|
65
|
+
const above = centers.map((center, index) => {
|
|
66
|
+
// Exit early when the amount cannot be seen at this position
|
|
67
|
+
if (!observable[index]) {
|
|
68
|
+
return Number();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return strengths[center] - args.noise[center];
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const observedWeight = weights.reduce((sum, weight, index) => {
|
|
75
|
+
return sum + (!observable[index] ? 0 : weight);
|
|
76
|
+
},
|
|
77
|
+
Number());
|
|
78
|
+
|
|
79
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, Number());
|
|
80
|
+
|
|
81
|
+
const observed = above.map(n => max(Number(), n));
|
|
82
|
+
|
|
83
|
+
const strongest = max(...observed);
|
|
84
|
+
|
|
85
|
+
const spikes = observed.filter(n => n >= strongest * minPatternStrength);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
observable,
|
|
89
|
+
evidence: !totalWeight ? 0 : observedWeight / totalWeight,
|
|
90
|
+
is_pattern: !!strongest && spikes.length >= minPatternSpikes,
|
|
91
|
+
score: above.reduce((sum, n, i) => sum + n * weights[i], Number()),
|
|
92
|
+
strengths: observed,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const {abs} = Math;
|
|
2
|
+
const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
|
|
3
|
+
|
|
4
|
+
/** Smooth histogram bins with a triangular kernel
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
bins: [<Bin Value Number>]
|
|
8
|
+
half: <Kernel Half Width in Bins Number>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@returns
|
|
12
|
+
{
|
|
13
|
+
smoothed: [<Smoothed Bin Value Number>]
|
|
14
|
+
}
|
|
15
|
+
*/
|
|
16
|
+
module.exports = ({bins, half}) => {
|
|
17
|
+
const offsets = range(-half, half + 1);
|
|
18
|
+
|
|
19
|
+
const smoothed = bins.map((value, bin) => {
|
|
20
|
+
return offsets.reduce((sum, offset) => {
|
|
21
|
+
const weight = 1 - abs(offset) / (half + 1);
|
|
22
|
+
|
|
23
|
+
return sum + weight * (bins[bin + offset] || 0);
|
|
24
|
+
},
|
|
25
|
+
Number());
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {smoothed};
|
|
29
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const lowerBound = require('./lower_bound');
|
|
2
|
+
|
|
3
|
+
const {abs} = Math;
|
|
4
|
+
const {max} = Math;
|
|
5
|
+
const {min} = Math;
|
|
6
|
+
const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
|
|
7
|
+
|
|
8
|
+
/** Get a value from the sorted shares of the neighbors of each bin
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
bins: [<Share Number>]
|
|
12
|
+
exclude: <Bins Next to a Bin to Leave Out of Its Neighbors Number>
|
|
13
|
+
min_bin: <Lowest Bin That Can Be a Neighbor Number>
|
|
14
|
+
radius: <Bins On Either Side to Take Neighbors From Number>
|
|
15
|
+
value: <Value From Sorted Neighbor Shares Function> (sorted) => <Value>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@returns
|
|
19
|
+
{
|
|
20
|
+
values: [<Value From the Sorted Shares of the Neighbors of the Bin>]
|
|
21
|
+
}
|
|
22
|
+
*/
|
|
23
|
+
module.exports = args => {
|
|
24
|
+
const {bins, exclude, radius, value} = args;
|
|
25
|
+
|
|
26
|
+
const first = max(Number(), args.min_bin);
|
|
27
|
+
|
|
28
|
+
// The shares of the neighbors of a bin, kept in order from bin to bin
|
|
29
|
+
const sorted = [];
|
|
30
|
+
|
|
31
|
+
// Moving on to a bin only changes the bins at the edges of its reach and at
|
|
32
|
+
// the edges of the bins left out around it
|
|
33
|
+
const edges = [-1 - radius, -1 - exclude, exclude, radius]
|
|
34
|
+
.filter((offset, i, offsets) => offsets.indexOf(offset) === i);
|
|
35
|
+
|
|
36
|
+
// A neighbor of a bin is within its reach and beyond the bins left out
|
|
37
|
+
const isNeighbor = (index, bin) => {
|
|
38
|
+
const distance = abs(index - bin);
|
|
39
|
+
|
|
40
|
+
const isBin = index >= first && index < bins.length;
|
|
41
|
+
|
|
42
|
+
return isBin && distance > exclude && distance <= radius;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Put the share of a bin in its place among the sorted shares
|
|
46
|
+
const add = index => {
|
|
47
|
+
const at = lowerBound({sorted, value: bins[index]}).index;
|
|
48
|
+
|
|
49
|
+
return sorted.splice(at, Number(), bins[index]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Take the share of a bin out of the sorted shares
|
|
53
|
+
const drop = index => {
|
|
54
|
+
const at = lowerBound({sorted, value: bins[index]}).index;
|
|
55
|
+
|
|
56
|
+
return sorted.splice(at, 1);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Move the neighbors on from the bin before a bin to the bin
|
|
60
|
+
const step = bin => {
|
|
61
|
+
return edges.map(offset => bin + offset).forEach(index => {
|
|
62
|
+
const isNeighborNow = isNeighbor(index, bin);
|
|
63
|
+
const wasNeighbor = isNeighbor(index, bin - 1);
|
|
64
|
+
|
|
65
|
+
// Exit early when the bin is still a neighbor, or still is not one
|
|
66
|
+
if (isNeighborNow === wasNeighbor) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return isNeighborNow ? add(index) : drop(index);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// The first bin starts out with all of its neighbors
|
|
75
|
+
range(Number(), min(bins.length, radius + 1))
|
|
76
|
+
.filter(index => isNeighbor(index, Number()))
|
|
77
|
+
.forEach(add);
|
|
78
|
+
|
|
79
|
+
const values = bins.map((share, bin) => {
|
|
80
|
+
// A bin after the first moves the neighbors on from the bin before it
|
|
81
|
+
if (!!bin) {
|
|
82
|
+
step(bin);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value(sorted);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return {values};
|
|
89
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const {spikeKernel} = require('./../constants');
|
|
2
|
+
|
|
3
|
+
const half = (spikeKernel.length - 1) / 2;
|
|
4
|
+
const {isArray} = Array;
|
|
5
|
+
|
|
6
|
+
/** Measure the strength of a spike centered at each bin of a histogram
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
excess: [<Share of Outputs Above Local Background Number>]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@throws
|
|
13
|
+
<Error>
|
|
14
|
+
|
|
15
|
+
@returns
|
|
16
|
+
{
|
|
17
|
+
strengths: [<Spike Strength At Bin Number>]
|
|
18
|
+
}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = ({excess}) => {
|
|
21
|
+
if (!isArray(excess)) {
|
|
22
|
+
throw new Error('ExpectedHistogramExcessToMeasureSpikeStrengths');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const strengths = excess.map((share, center) => {
|
|
26
|
+
return spikeKernel.reduce((sum, weight, offset) => {
|
|
27
|
+
return sum + weight * (excess[center + offset - half] || 0);
|
|
28
|
+
},
|
|
29
|
+
0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {strengths};
|
|
33
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const dotProduct = (a, b) => a.reduce((sum, n, i) => sum + n * b[i], Number());
|
|
2
|
+
const magnitude = arr => Math.sqrt(arr.reduce((sum, n) => sum + n * n, 0));
|
|
3
|
+
|
|
4
|
+
/** Measure how well measured spike strengths agree with the template weights
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
strengths: [<Spike Strength Number>]
|
|
8
|
+
weights: [<Template Weight Number>]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@returns
|
|
12
|
+
{
|
|
13
|
+
agreement: <Cosine Similarity Number>
|
|
14
|
+
}
|
|
15
|
+
*/
|
|
16
|
+
module.exports = ({strengths, weights}) => {
|
|
17
|
+
const dot = dotProduct(strengths, weights);
|
|
18
|
+
const strengthsNorm = magnitude(strengths);
|
|
19
|
+
const weightsNorm = magnitude(weights);
|
|
20
|
+
|
|
21
|
+
// Exit early when there are no spikes to compare
|
|
22
|
+
if (!strengthsNorm || !weightsNorm) {
|
|
23
|
+
return {agreement: Number()};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {agreement: dot / (strengthsNorm * weightsNorm)};
|
|
27
|
+
};
|
package/chain/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const earliestSupportedDay = require('./earliest_supported_day');
|
|
2
|
+
const knownPrices = require('./known_prices');
|
|
3
|
+
const methodVersion = require('./method_version');
|
|
4
|
+
const parseKnownPrices = require('./parse_known_prices');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
earliestSupportedDay,
|
|
8
|
+
knownPrices,
|
|
9
|
+
methodVersion,
|
|
10
|
+
parseKnownPrices,
|
|
11
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const {join} = require('path');
|
|
2
|
+
const {readFileSync} = require('fs');
|
|
3
|
+
|
|
4
|
+
const methodVersion = require('./method_version');
|
|
5
|
+
const parseKnownPrices = require('./parse_known_prices');
|
|
6
|
+
|
|
7
|
+
const fileName = 'known_prices.json';
|
|
8
|
+
const loaded = new Map();
|
|
9
|
+
|
|
10
|
+
/** Get the prices that were shipped with the method
|
|
11
|
+
|
|
12
|
+
{}
|
|
13
|
+
|
|
14
|
+
@throws
|
|
15
|
+
<Error>
|
|
16
|
+
|
|
17
|
+
@returns
|
|
18
|
+
{
|
|
19
|
+
rates: {
|
|
20
|
+
<Day YYYY-MM-DD String>: {
|
|
21
|
+
cents: <USD Cents Per BTC Number>
|
|
22
|
+
version: <Method Version String>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
*/
|
|
27
|
+
module.exports = ({}) => {
|
|
28
|
+
// Exit early when the prices were already read
|
|
29
|
+
if (loaded.has(fileName)) {
|
|
30
|
+
return {rates: loaded.get(fileName)};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const text = readFileSync(join(__dirname, fileName), 'utf8');
|
|
34
|
+
|
|
35
|
+
const {rates, version} = parseKnownPrices({text});
|
|
36
|
+
|
|
37
|
+
// Prices derived by another version of the method are not used
|
|
38
|
+
const current = version === methodVersion({}).version ? rates : {};
|
|
39
|
+
|
|
40
|
+
loaded.set(fileName, current);
|
|
41
|
+
|
|
42
|
+
return {rates: current};
|
|
43
|
+
};
|