@alexbosworth/fiat 1.0.4 → 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 +26 -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 -11
  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 -75
  102. package/test/coindesk/test_get_coindesk_current_price.js +0 -93
  103. package/test/coingecko/test_get_coingecko_rates.js +0 -91
  104. package/test/rates/test_get_current_price.js +0 -72
  105. package/test/rates/test_get_prices.js +0 -76
@@ -0,0 +1,119 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncEachLimit = require('async/eachLimit');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const {getBlockHash} = require('./../blocks');
6
+
7
+ const checkLimit = 4;
8
+ const flatten = arr => [].concat(...arr);
9
+ const {isArray} = Array;
10
+ const isHeight = n => Number.isInteger(n) && n >= 0;
11
+ const {keys} = Object;
12
+ const recentBlocks = 1008;
13
+ const sortByDay = (a, b) => a.day.localeCompare(b.day);
14
+
15
+ /** Check that recently derived known estimates are still in the chain
16
+
17
+ {
18
+ estimates: {
19
+ priors: {<Day YYYY-MM-DD String>: <Estimate Object>}
20
+ [provisional]: {<Day YYYY-MM-DD String>: <Estimate Object>}
21
+ rates: {<Day YYYY-MM-DD String>: <Estimate Object>}
22
+ }
23
+ height: <Current Best Chain Height Number>
24
+ lnd: <Authenticated LND API Object>
25
+ }
26
+
27
+ @returns via cbk or Promise
28
+ {
29
+ removed: [{
30
+ day: <Removed Estimate Day YYYY-MM-DD String>
31
+ hash: <Last Block Hash Of the Removed Estimate Hex String>
32
+ }]
33
+ }
34
+ */
35
+ module.exports = ({estimates, height, lnd}, cbk) => {
36
+ return new Promise((resolve, reject) => {
37
+ return asyncAuto({
38
+ // Check arguments
39
+ validate: cbk => {
40
+ if (!estimates || !estimates.priors || !estimates.rates) {
41
+ return cbk([400, 'ExpectedKnownEstimatesToCheckKnownEstimates']);
42
+ }
43
+
44
+ if (!isHeight(height)) {
45
+ return cbk([400, 'ExpectedChainHeightToCheckKnownEstimates']);
46
+ }
47
+
48
+ if (!lnd) {
49
+ return cbk([400, 'ExpectedAuthenticatedLndToCheckKnownEstimates']);
50
+ }
51
+
52
+ return cbk();
53
+ },
54
+
55
+ // Collect the estimates that have a recent block to check
56
+ recent: ['validate', ({}, cbk) => {
57
+ const {priors, provisional, rates} = estimates;
58
+
59
+ const sources = [priors, provisional || {}, rates];
60
+
61
+ const known = flatten(sources.map(source => {
62
+ return keys(source).map(day => {
63
+ return {day, source, estimate: source[day]};
64
+ });
65
+ }));
66
+
67
+ const checks = known.filter(({estimate}) => {
68
+ // Exit early when there is no block to check against
69
+ if (!estimate.hash || !isArray(estimate.blocks)) {
70
+ return false;
71
+ }
72
+
73
+ const [, last] = estimate.blocks;
74
+
75
+ // Deeply buried blocks do not get reorganized so skip checking them
76
+ return isHeight(last) && height - last < recentBlocks;
77
+ });
78
+
79
+ return cbk(null, checks);
80
+ }],
81
+
82
+ // Check the hashes of the last blocks against the chain
83
+ check: ['recent', ({recent}, cbk) => {
84
+ const removed = [];
85
+
86
+ return asyncEachLimit(recent, checkLimit, (known, cbk) => {
87
+ const {day, estimate, source} = known;
88
+
89
+ const [, last] = estimate.blocks;
90
+
91
+ // Exit early when the chain has not reached the block yet, since
92
+ // the estimate cannot be checked it is only set aside
93
+ if (last > height) {
94
+ delete source[day];
95
+
96
+ return cbk();
97
+ }
98
+
99
+ return getBlockHash({lnd, height: last}, (err, res) => {
100
+ if (!!err) {
101
+ return cbk(err);
102
+ }
103
+
104
+ // The chain was reorganized past the block the estimate used
105
+ if (res.hash !== estimate.hash) {
106
+ removed.push({day, hash: estimate.hash});
107
+
108
+ delete source[day];
109
+ }
110
+
111
+ return cbk();
112
+ });
113
+ },
114
+ err => cbk(err, {removed: removed.sort(sortByDay)}));
115
+ }],
116
+ },
117
+ returnResult({reject, resolve, of: 'check'}, cbk));
118
+ });
119
+ };
@@ -0,0 +1,66 @@
1
+ const {isCalendarDay} = require('./../../dates');
2
+ const {methodVersion} = require('./../known');
3
+
4
+ const isObject = n => !!n && typeof n === 'object' && !Array.isArray(n);
5
+ const isPrice = n => Number.isInteger(n) && n >= 1e4 && n <= 1e9;
6
+ const isHeight = n => Number.isInteger(n) && n >= 0;
7
+ const {keys} = Object;
8
+
9
+ /** Keep only the known estimates that were derived by the current method
10
+
11
+ {
12
+ [priors]: {<Day YYYY-MM-DD String>: {[version]: <Method Version String>}}
13
+ [provisional]: {
14
+ <Day YYYY-MM-DD String>: {[version]: <Method Version String>}
15
+ }
16
+ [rates]: {<Day YYYY-MM-DD String>: {[version]: <Method Version String>}}
17
+ }
18
+
19
+ @returns
20
+ {
21
+ priors: {<Day YYYY-MM-DD String>: <Estimate Object>}
22
+ provisional: {<Day YYYY-MM-DD String>: <Estimate Object>}
23
+ rates: {<Day YYYY-MM-DD String>: <Estimate Object>}
24
+ }
25
+ */
26
+ module.exports = ({priors, provisional, rates}) => {
27
+ // The version covers both the derivation method and the round USD template
28
+ const {version} = methodVersion({});
29
+
30
+ const current = ({estimates, is_provisional}) => {
31
+ return keys(estimates || {}).reduce((sum, day) => {
32
+ const estimate = estimates[day];
33
+
34
+ // Exit early when the entry is not an estimate for a day of the calendar
35
+ if (!isCalendarDay({day}).is_calendar_day || !isObject(estimate)) {
36
+ return sum;
37
+ }
38
+
39
+ const {blocks} = estimate;
40
+
41
+ const [first, last] = Array.isArray(blocks) ? blocks : [];
42
+
43
+ // A block range is two heights in order
44
+ const isRange = isHeight(first) && isHeight(last) && first <= last;
45
+
46
+ // Exit early when the range is damaged or a provisional price has none
47
+ if ((blocks !== undefined || !!is_provisional) && !isRange) {
48
+ return sum;
49
+ }
50
+
51
+ // Estimates left out here will be derived again
52
+ if (estimate.version === version && isPrice(estimate.cents)) {
53
+ sum[day] = estimate;
54
+ }
55
+
56
+ return sum;
57
+ },
58
+ {});
59
+ };
60
+
61
+ return {
62
+ priors: current({estimates: priors}),
63
+ provisional: current({estimates: provisional, is_provisional: true}),
64
+ rates: current({estimates: rates}),
65
+ };
66
+ };
@@ -0,0 +1,199 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const estimateDayPrice = require('./estimate_day_price');
5
+ const estimateRecord = require('./estimate_record');
6
+ const {getBlockHash} = require('./../blocks');
7
+ const isAliasRatio = require('./is_alias_ratio');
8
+ const nearestEstimate = require('./nearest_estimate');
9
+ const priceWindow = require('./price_window');
10
+ const rememberEstimate = require('./remember_estimate');
11
+
12
+ const estimateKey = ({cents, day}) => [day, cents].join(' ');
13
+
14
+ /** Estimate the price for a day and remember it when it is supported
15
+
16
+ {
17
+ [blocks]: <Retrieved Blocks By Height Map Object>
18
+ day: <Day YYYY-MM-DD String>
19
+ estimates: {
20
+ priors: {<Day YYYY-MM-DD String>: <Estimate Object>}
21
+ provisional: {<Day YYYY-MM-DD String>: <Estimate Object>}
22
+ rates: {<Day YYYY-MM-DD String>: <Estimate Object>}
23
+ }
24
+ height: <Current Best Chain Height Number>
25
+ [is_sample]: <Estimate From a Sample of Blocks Bool>
26
+ [is_unguided]: <Estimate Without a Neighboring Guide Bool>
27
+ lnd: <Authenticated LND API Object>
28
+ [logger]: <Winston Logger Object>
29
+ put_price: <Remember Price Function> (record, cbk) => (err)
30
+ times: <Block Times Map Object>
31
+ [unsupported]: <Unsupported Estimates Map Object>
32
+ }
33
+
34
+ @returns via cbk or Promise
35
+ <Day Price Estimate Object>
36
+ */
37
+ module.exports = (args, cbk) => {
38
+ return new Promise((resolve, reject) => {
39
+ return asyncAuto({
40
+ // The nearest estimate guides the search unless the day is unguided
41
+ guide: cbk => {
42
+ // Exit early when a starting day is estimated on its own
43
+ if (!!args.is_unguided) {
44
+ return cbk(null, {});
45
+ }
46
+
47
+ const {estimate} = nearestEstimate({
48
+ day: args.day,
49
+ estimates: args.estimates,
50
+ });
51
+
52
+ return cbk(null, {estimate});
53
+ },
54
+
55
+ // The key that identifies the estimate among unsupported estimates
56
+ key: ['guide', ({guide}, cbk) => {
57
+ return cbk(null, estimateKey({
58
+ cents: !guide.estimate ? undefined : guide.estimate.cents,
59
+ day: args.day,
60
+ }));
61
+ }],
62
+
63
+ // Get the estimate of the day that was found unsupported before
64
+ getUnsupported: ['key', ({key}, cbk) => {
65
+ // Exit early when unsupported estimates are not kept
66
+ if (!args.unsupported) {
67
+ return cbk();
68
+ }
69
+
70
+ return cbk(null, args.unsupported.get(key));
71
+ }],
72
+
73
+ // Get the hash of the last block of the estimate found unsupported
74
+ getHash: ['getUnsupported', ({getUnsupported}, cbk) => {
75
+ // Exit early when the day was not found unsupported before
76
+ if (!getUnsupported) {
77
+ return cbk();
78
+ }
79
+
80
+ const [, last] = getUnsupported.blocks;
81
+
82
+ // Exit early when the chain no longer reaches the blocks
83
+ if (last > args.height) {
84
+ return cbk();
85
+ }
86
+
87
+ return getBlockHash({height: last, lnd: args.lnd}, cbk);
88
+ }],
89
+
90
+ // An estimate found unsupported holds while its blocks are in the chain
91
+ cached: [
92
+ 'getHash',
93
+ 'getUnsupported',
94
+ 'key',
95
+ ({getHash, getUnsupported, key}, cbk) =>
96
+ {
97
+ // Exit early when the day was not found unsupported before
98
+ if (!getUnsupported) {
99
+ return cbk();
100
+ }
101
+
102
+ // Exit early when the blocks are gone, so the day is estimated again
103
+ if (!getHash || getHash.hash !== getUnsupported.hash) {
104
+ args.unsupported.delete(key);
105
+
106
+ return cbk();
107
+ }
108
+
109
+ return cbk(null, getUnsupported);
110
+ }],
111
+
112
+ // Estimate the price of the day
113
+ getEstimate: ['cached', 'guide', ({cached, guide}, cbk) => {
114
+ // Exit early when the same estimate was already found unsupported
115
+ if (!!cached) {
116
+ return cbk(null, cached);
117
+ }
118
+
119
+ const {estimate} = guide;
120
+
121
+ const bounds = !estimate ? {} : priceWindow({cents: estimate.cents});
122
+
123
+ return estimateDayPrice({
124
+ blocks: args.blocks,
125
+ date: args.day,
126
+ height: args.height,
127
+ is_sample: args.is_sample || undefined,
128
+ lnd: args.lnd,
129
+ logger: args.logger,
130
+ max_price: bounds.max_price,
131
+ min_price: bounds.min_price,
132
+ times: args.times,
133
+ },
134
+ cbk);
135
+ }],
136
+
137
+ // A price at an alias of its guide means one of them is wrong
138
+ checkAlias: ['getEstimate', 'guide', ({getEstimate, guide}, cbk) => {
139
+ const {estimate} = guide;
140
+
141
+ // Exit early when the price was not found beyond a guide window
142
+ if (!getEstimate.is_widened || !estimate) {
143
+ return cbk();
144
+ }
145
+
146
+ // Exit early when the price is not supported anyway
147
+ if (!getEstimate.is_supported) {
148
+ return cbk();
149
+ }
150
+
151
+ const ratio = isAliasRatio({
152
+ cents: getEstimate.cents,
153
+ other: estimate.cents,
154
+ });
155
+
156
+ if (!!ratio.is_alias) {
157
+ return cbk([503, 'FoundPriceAtAliasOfNeighboringDayPrice', {
158
+ cents: getEstimate.cents,
159
+ day: args.day,
160
+ neighbor: estimate.day,
161
+ neighbor_cents: estimate.cents,
162
+ }]);
163
+ }
164
+
165
+ return cbk();
166
+ }],
167
+
168
+ // Remember a supported estimate
169
+ remember: [
170
+ 'checkAlias',
171
+ 'getEstimate',
172
+ 'key',
173
+ ({getEstimate, key}, cbk) =>
174
+ {
175
+ // Exit early when the estimate is not supported by the data
176
+ if (!getEstimate.is_supported) {
177
+ // Keep the result so that the estimate is not made again
178
+ if (!!args.unsupported) {
179
+ args.unsupported.set(key, getEstimate);
180
+ }
181
+
182
+ return cbk(null, getEstimate);
183
+ }
184
+
185
+ const {record} = estimateRecord({
186
+ date: args.day,
187
+ estimate: getEstimate,
188
+ is_sample: args.is_sample,
189
+ is_unguided: args.is_unguided,
190
+ });
191
+
192
+ rememberEstimate({estimate: record, estimates: args.estimates});
193
+
194
+ return args.put_price(record, err => cbk(err, getEstimate));
195
+ }],
196
+ },
197
+ returnResult({reject, resolve, of: 'remember'}, cbk));
198
+ });
199
+ };