@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +93 -1
  3. package/arrays/index.js +3 -0
  4. package/arrays/sort_by.js +44 -0
  5. package/chain/add_days.js +22 -0
  6. package/chain/blocks/block_header_hash.js +29 -0
  7. package/chain/blocks/block_header_time.js +25 -0
  8. package/chain/blocks/find_complete_day.js +90 -0
  9. package/chain/blocks/find_height_for_time.js +93 -0
  10. package/chain/blocks/get_block_hash.js +60 -0
  11. package/chain/blocks/get_block_header.js +45 -0
  12. package/chain/blocks/get_block_payments.js +100 -0
  13. package/chain/blocks/get_blocks_for_date.js +110 -0
  14. package/chain/blocks/get_median_time.js +92 -0
  15. package/chain/blocks/index.js +19 -0
  16. package/chain/blocks/payment_outputs_from_block.js +178 -0
  17. package/chain/blocks/sample_heights.js +33 -0
  18. package/chain/blocks/window_heights.js +34 -0
  19. package/chain/chain_rates.js +129 -0
  20. package/chain/constants.json +26 -0
  21. package/chain/estimates/check_known_estimates.js +119 -0
  22. package/chain/estimates/current_estimates.js +66 -0
  23. package/chain/estimates/estimate_and_remember.js +199 -0
  24. package/chain/estimates/estimate_day_price.js +361 -0
  25. package/chain/estimates/estimate_record.js +107 -0
  26. package/chain/estimates/estimate_windows.js +246 -0
  27. package/chain/estimates/find_starting_day.js +86 -0
  28. package/chain/estimates/forget_estimates.js +113 -0
  29. package/chain/estimates/get_chain_price.js +167 -0
  30. package/chain/estimates/get_price_for_date.js +607 -0
  31. package/chain/estimates/get_price_from_blocks.js +281 -0
  32. package/chain/estimates/get_provisional_price.js +163 -0
  33. package/chain/estimates/index.js +25 -0
  34. package/chain/estimates/is_alias_ratio.js +38 -0
  35. package/chain/estimates/is_supported_estimate.js +81 -0
  36. package/chain/estimates/nearest_estimate.js +49 -0
  37. package/chain/estimates/price_window.js +25 -0
  38. package/chain/estimates/remember_estimate.js +83 -0
  39. package/chain/estimates/store_estimate.js +198 -0
  40. package/chain/histogram/best_position.js +63 -0
  41. package/chain/histogram/histogram_excess.js +50 -0
  42. package/chain/histogram/histogram_peak.js +87 -0
  43. package/chain/histogram/implied_prices_histogram.js +53 -0
  44. package/chain/histogram/index.js +33 -0
  45. package/chain/histogram/is_round_tokens.js +26 -0
  46. package/chain/histogram/kernel_width.js +25 -0
  47. package/chain/histogram/lower_bound.js +28 -0
  48. package/chain/histogram/noise_of_neighbors.js +28 -0
  49. package/chain/histogram/noise_strength.js +35 -0
  50. package/chain/histogram/noise_strengths.js +29 -0
  51. package/chain/histogram/outputs_histogram.js +80 -0
  52. package/chain/histogram/parabola_peak.js +28 -0
  53. package/chain/histogram/payments_near_position.js +31 -0
  54. package/chain/histogram/peak_width.js +37 -0
  55. package/chain/histogram/position_score.js +38 -0
  56. package/chain/histogram/score_position.js +94 -0
  57. package/chain/histogram/smooth_bins.js +29 -0
  58. package/chain/histogram/sorted_neighbors.js +89 -0
  59. package/chain/histogram/spike_strengths.js +33 -0
  60. package/chain/histogram/template_agreement.js +27 -0
  61. package/chain/index.js +3 -0
  62. package/chain/known/earliest_supported_day.js +12 -0
  63. package/chain/known/index.js +11 -0
  64. package/chain/known/known_prices.js +43 -0
  65. package/chain/known/known_prices.json +1836 -0
  66. package/chain/known/method_version.js +26 -0
  67. package/chain/known/parse_known_prices.js +67 -0
  68. package/chain/outputs/alias_shifts.js +31 -0
  69. package/chain/outputs/cap_near_round_values.js +83 -0
  70. package/chain/outputs/cap_repeated_values.js +33 -0
  71. package/chain/outputs/dollar_bin_for_price.js +19 -0
  72. package/chain/outputs/estimate_from_outputs.js +321 -0
  73. package/chain/outputs/index.js +11 -0
  74. package/chain/outputs/peak_around_price.js +86 -0
  75. package/chain/outputs/price_for_dollar_bin.js +21 -0
  76. package/chain/outputs/price_from_outputs.js +98 -0
  77. package/chain/outputs/refine_price.js +73 -0
  78. package/chain/outputs/round_amounts.js +49 -0
  79. package/chain/outputs/template.json +22 -0
  80. package/coinbase/constants.json +5 -0
  81. package/coinbase/get_coinbase_day_candles.js +155 -0
  82. package/coinbase/get_coinbase_historic_price.js +135 -0
  83. package/coinbase/index.js +7 -1
  84. package/dates/constants.json +3 -0
  85. package/dates/day_of_date.js +26 -0
  86. package/dates/index.js +5 -0
  87. package/dates/is_calendar_day.js +16 -0
  88. package/dates/time_of_date.js +61 -0
  89. package/docs/METHODOLOGY.md +984 -0
  90. package/index.js +2 -1
  91. package/package.json +29 -8
  92. package/rates/get_prices.js +51 -8
  93. package/storage/constants.json +4 -0
  94. package/storage/get_prices_file.js +169 -0
  95. package/storage/index.js +4 -0
  96. package/storage/is_prices.js +31 -0
  97. package/storage/put_prices_file.js +252 -0
  98. package/storage/temp_path.js +22 -0
  99. package/.gitattributes +0 -1
  100. package/.tm_properties +0 -1
  101. package/test/coinbase/test_get_coinbase_current_price.js +0 -77
  102. package/test/coindesk/test_get_coindesk_current_price.js +0 -95
  103. package/test/coingecko/test_get_coingecko_rates.js +0 -93
  104. package/test/rates/test_get_current_price.js +0 -74
  105. 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,3 @@
1
+ const chainRates = require('./chain_rates');
2
+
3
+ module.exports = {chainRates};
@@ -0,0 +1,12 @@
1
+ const earliestDay = '2021-10-20';
2
+
3
+ /** Get the earliest day that prices are derived for
4
+
5
+ {}
6
+
7
+ @returns
8
+ {
9
+ day: <Earliest Supported Day YYYY-MM-DD String>
10
+ }
11
+ */
12
+ module.exports = ({}) => ({day: earliestDay});
@@ -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
+ };