@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,361 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncTimesSeries = require('async/timesSeries');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const estimateWindows = require('./estimate_windows');
6
+ const {getBlockHash} = require('./../blocks');
7
+ const {getBlocksForDate} = require('./../blocks');
8
+ const getPriceFromBlocks = require('./get_price_from_blocks');
9
+ const {isCalendarDay} = require('./../../dates');
10
+ const isSupportedEstimate = require('./is_supported_estimate');
11
+ const {sampleHeights} = require('./../blocks');
12
+ const {unpricedErrors} = require('./../constants');
13
+
14
+ const {assign} = Object;
15
+ const {ceil} = Math;
16
+ const isHeight = n => Number.isInteger(n) && n >= 0;
17
+ const {log2} = Math;
18
+ const {max} = Math;
19
+ const {min} = Math;
20
+ const minSamplePayments = 500;
21
+ const minWindowsBlocks = 24;
22
+ const {pow} = Math;
23
+ const sampleBlocksCount = 12;
24
+
25
+ /** Estimate the USD price of BTC on a complete day
26
+
27
+ {
28
+ [blocks]: <Retrieved Blocks By Height Map Object>
29
+ date: <Day YYYY-MM-DD String>
30
+ height: <Current Best Chain Height Number>
31
+ [is_capping_near_round]: <Cap Values Just Off Round BTC Amounts Too Bool>
32
+ [is_sample]: <Estimate From A Sample of the Blocks Bool>
33
+ lnd: <Authenticated LND API Object>
34
+ [logger]: <Winston Logger Object>
35
+ [max_price]: <Maximum Possible USD Price Number>
36
+ [min_price]: <Minimum Possible USD Price Number>
37
+ [times]: <Block Times By Height Map Object>
38
+ }
39
+
40
+ @returns via cbk or Promise
41
+ {
42
+ [agreement]: <Spike Strengths Agreement With Template Weights Number>
43
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
44
+ [cents]: <USD Cents Per BTC Number>
45
+ [confidence]: <Best Position Score Over Best Alias Position Score Number>
46
+ [coverage]: <Share of Spike Excess At Round USD Positions Number>
47
+ [dispersion]: <Peak Width Beyond Smoothing As Price Fraction Number>
48
+ [drift]: <Second Half Price Change From First Half Price Number>
49
+ [evidence]: <Share of Prior Weight Observable At the Price Number>
50
+ hash: <Last Block Hash Hex String>
51
+ is_supported: <Estimate Is Supported By the Data Bool>
52
+ [is_unpriced]: <Blocks Give No Price At All Bool>
53
+ [is_unsettled]: <Refinement Was Still Moving After Its Last Pass Bool>
54
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
55
+ [outputs]: <Payment Outputs Considered Count Number>
56
+ [payments]: <Estimated Round USD Payments Count Number>
57
+ [rivalry]: <Best Patterned Rival Position Score Over Best Score Number>
58
+ [round_shift]: <Price Change If Round BTC Values Were Counted Number>
59
+ [sample_blocks]: <Blocks In the Sample Count Number>
60
+ [significance]: <Payment Peak Beyond Noise In Standard Deviations Number>
61
+ [spikes]: [{
62
+ strength: <Spike Strength Relative to the Strongest Spike Number>
63
+ usd: <Round USD Amount Number>
64
+ }]
65
+ [stability]: <Largest Price Change From Perturbed Refinement Number>
66
+ [windows]: [{
67
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
68
+ [cents]: <USD Cents Per BTC Number>
69
+ is_supported: <Window Price Is Supported By the Data Bool>
70
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
71
+ seconds: <Time Covered By the Window Seconds Number>
72
+ }]
73
+ }
74
+ */
75
+ module.exports = (args, cbk) => {
76
+ return new Promise((resolve, reject) => {
77
+ return asyncAuto({
78
+ // Check arguments
79
+ validate: cbk => {
80
+ if (!isCalendarDay({day: args.date}).is_calendar_day) {
81
+ return cbk([400, 'ExpectedYyyyMmDdDateToEstimateDayPrice']);
82
+ }
83
+
84
+ if (!isHeight(args.height)) {
85
+ return cbk([400, 'ExpectedCurrentChainHeightToEstimateDayPrice']);
86
+ }
87
+
88
+ if (!args.lnd) {
89
+ return cbk([400, 'ExpectedAuthenticatedLndToEstimateDayPrice']);
90
+ }
91
+
92
+ return cbk();
93
+ },
94
+
95
+ // Blocks are kept so that each one is retrieved once for all estimates
96
+ blocks: ['validate', ({}, cbk) => cbk(null, args.blocks || new Map())],
97
+
98
+ // Block times are kept to avoid retrieving them twice
99
+ times: ['validate', ({}, cbk) => cbk(null, args.times || new Map())],
100
+
101
+ // Get the blocks on the day
102
+ getBlocks: ['times', ({times}, cbk) => {
103
+ return getBlocksForDate({
104
+ times,
105
+ date: args.date,
106
+ height: args.height,
107
+ lnd: args.lnd,
108
+ },
109
+ cbk);
110
+ }],
111
+
112
+ // Only a complete day is estimated
113
+ checkComplete: ['getBlocks', ({getBlocks}, cbk) => {
114
+ if (!getBlocks.is_complete) {
115
+ return cbk([503, 'ExpectedCompleteDayToEstimateDayPrice']);
116
+ }
117
+
118
+ return cbk();
119
+ }],
120
+
121
+ // Get the hash of the last block on the day
122
+ getHash: ['checkComplete', 'getBlocks', ({getBlocks}, cbk) => {
123
+ const {heights} = getBlocks;
124
+
125
+ return getBlockHash({
126
+ height: heights[heights.length - 1],
127
+ lnd: args.lnd,
128
+ },
129
+ cbk);
130
+ }],
131
+
132
+ // Derive the price from the blocks on the day
133
+ getPrice: [
134
+ 'blocks',
135
+ 'checkComplete',
136
+ 'getBlocks',
137
+ ({blocks, getBlocks}, cbk) =>
138
+ {
139
+ const {heights} = getBlocks;
140
+
141
+ // Exit early when using all of the blocks on the day
142
+ if (!args.is_sample) {
143
+ return getPriceFromBlocks({
144
+ blocks,
145
+ heights,
146
+ date: args.date,
147
+ is_capping_near_round: args.is_capping_near_round,
148
+ lnd: args.lnd,
149
+ logger: args.logger,
150
+ max_price: args.max_price,
151
+ min_price: args.min_price,
152
+ },
153
+ (err, res) => {
154
+ // Exit early when the blocks give no price at all
155
+ if (!!err && unpricedErrors.includes(err[1])) {
156
+ return cbk(null, {is_unpriced: true});
157
+ }
158
+
159
+ return cbk(err, res);
160
+ });
161
+ }
162
+
163
+ const estimates = [];
164
+
165
+ // Each attempt doubles the sample size up to all the blocks
166
+ const doublings = ceil(log2(heights.length / sampleBlocksCount));
167
+
168
+ const attempts = max(1, doublings + 1);
169
+
170
+ return asyncTimesSeries(attempts, (i, cbk) => {
171
+ const [latest] = estimates;
172
+
173
+ // Exit early when the sample already supports the price well
174
+ if (!!latest && latest.is_enough) {
175
+ return cbk();
176
+ }
177
+
178
+ // Exit early when all the blocks were already sampled
179
+ if (!!latest && latest.count >= heights.length) {
180
+ return cbk();
181
+ }
182
+
183
+ const count = sampleBlocksCount * pow(2, i);
184
+
185
+ // Only a sample that is expanded to the whole day shows progress
186
+ const logger = count >= heights.length ? args.logger : undefined;
187
+
188
+ // A doubled sample keeps the blocks of the smaller samples
189
+ const sample = sampleHeights({
190
+ count,
191
+ heights,
192
+ initial: sampleBlocksCount,
193
+ });
194
+
195
+ return getPriceFromBlocks({
196
+ blocks,
197
+ logger,
198
+ date: args.date,
199
+ heights: sample.heights,
200
+ is_capping_near_round: args.is_capping_near_round,
201
+ lnd: args.lnd,
202
+ max_price: args.max_price,
203
+ min_price: args.min_price,
204
+ },
205
+ (err, res) => {
206
+ // A sample whose blocks give no price at all is not supported
207
+ if (!!err && unpricedErrors.includes(err[1])) {
208
+ estimates.unshift({count, price: {is_unpriced: true}});
209
+
210
+ return cbk();
211
+ }
212
+
213
+ if (!!err) {
214
+ return cbk(err);
215
+ }
216
+
217
+ const hasPayments = res.payments >= minSamplePayments;
218
+
219
+ const support = isSupportedEstimate({
220
+ confidence: res.confidence,
221
+ evidence: res.evidence,
222
+ is_unconstrained: !args.min_price || !!res.is_widened,
223
+ payments: res.payments,
224
+ rivalry: res.rivalry,
225
+ significance: res.significance,
226
+ spikes: res.spikes,
227
+ stability: res.stability,
228
+ });
229
+
230
+ estimates.unshift({
231
+ count,
232
+ is_enough: support.is_supported && hasPayments,
233
+ price: res,
234
+ });
235
+
236
+ return cbk();
237
+ });
238
+ },
239
+ err => {
240
+ if (!!err) {
241
+ return cbk(err);
242
+ }
243
+
244
+ const [latest] = estimates;
245
+
246
+ // A sample that was expanded to the whole day used all its blocks
247
+ const sampled = min(latest.count, heights.length);
248
+
249
+ return cbk(null, assign({}, latest.price, {
250
+ sample_blocks: sampled,
251
+ }));
252
+ });
253
+ }],
254
+
255
+ // Determine if the data support the price of the day as a whole
256
+ isSupported: ['getPrice', ({getPrice}, cbk) => {
257
+ // Exit early when the blocks gave no price at all
258
+ if (!!getPrice.is_unpriced) {
259
+ return cbk(null, false);
260
+ }
261
+
262
+ return cbk(null, isSupportedEstimate({
263
+ confidence: getPrice.confidence,
264
+ evidence: getPrice.evidence,
265
+ is_unconstrained: !args.min_price || !!getPrice.is_widened,
266
+ payments: getPrice.payments,
267
+ rivalry: getPrice.rivalry,
268
+ significance: getPrice.significance,
269
+ spikes: getPrice.spikes,
270
+ stability: getPrice.stability,
271
+ }).is_supported);
272
+ }],
273
+
274
+ // Estimate a full day in windows, and a sample that is not supported
275
+ getWindows: [
276
+ 'blocks',
277
+ 'getBlocks',
278
+ 'getPrice',
279
+ 'isSupported',
280
+ 'times',
281
+ ({blocks, getBlocks, getPrice, isSupported, times}, cbk) =>
282
+ {
283
+ const {heights} = getBlocks;
284
+
285
+ // Exit early when a sample is supported, since it only bridges
286
+ if (!!args.is_sample && !!isSupported) {
287
+ return cbk();
288
+ }
289
+
290
+ // Exit early when the blocks of the day gave no price at all
291
+ if (!!getPrice.is_unpriced) {
292
+ return cbk();
293
+ }
294
+
295
+ // Exit early when the estimate did not use every block on the day
296
+ if (getPrice.sample_blocks < heights.length) {
297
+ return cbk();
298
+ }
299
+
300
+ // Exit early when the day is too short to split into windows
301
+ if (heights.length < minWindowsBlocks) {
302
+ return cbk();
303
+ }
304
+
305
+ return estimateWindows({
306
+ blocks,
307
+ heights,
308
+ times,
309
+ is_capping_near_round: args.is_capping_near_round,
310
+ lnd: args.lnd,
311
+ max_price: args.max_price,
312
+ min_price: args.min_price,
313
+ },
314
+ cbk);
315
+ }],
316
+
317
+ // The estimated price for the day
318
+ price: [
319
+ 'getBlocks',
320
+ 'getHash',
321
+ 'getPrice',
322
+ 'getWindows',
323
+ 'isSupported',
324
+ ({getBlocks, getHash, getPrice, getWindows, isSupported}, cbk) =>
325
+ {
326
+ const {heights} = getBlocks;
327
+
328
+ const isWindowed = !!getWindows && getWindows.is_supported;
329
+
330
+ // A day priced from its windows takes the measures of the windows
331
+ const price = isWindowed ? getWindows : getPrice;
332
+
333
+ return cbk(null, {
334
+ agreement: price.agreement,
335
+ blocks: [heights[Number()], heights[heights.length - 1]],
336
+ cents: price.cents,
337
+ confidence: price.confidence,
338
+ coverage: price.coverage,
339
+ dispersion: price.dispersion,
340
+ drift: price.drift,
341
+ evidence: price.evidence,
342
+ hash: getHash.hash,
343
+ is_supported: isSupported || isWindowed,
344
+ is_unpriced: getPrice.is_unpriced,
345
+ is_unsettled: price.is_unsettled,
346
+ is_widened: price.is_widened,
347
+ outputs: price.outputs,
348
+ payments: price.payments,
349
+ rivalry: price.rivalry,
350
+ round_shift: price.round_shift,
351
+ sample_blocks: getPrice.sample_blocks,
352
+ significance: price.significance,
353
+ spikes: price.spikes,
354
+ stability: price.stability,
355
+ windows: !getWindows ? undefined : getWindows.windows,
356
+ });
357
+ }],
358
+ },
359
+ returnResult({reject, resolve, of: 'price'}, cbk));
360
+ });
361
+ };
@@ -0,0 +1,107 @@
1
+ const {methodVersion} = require('./../known');
2
+
3
+ const isCounted = n => !!n.is_supported && !n.is_widened;
4
+
5
+ /** Make the record of a price estimate that is reported for remembering
6
+
7
+ {
8
+ date: <Day YYYY-MM-DD String>
9
+ estimate: {
10
+ agreement: <Spike Strengths Agreement With Template Weights Number>
11
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
12
+ cents: <USD Cents Per BTC Number>
13
+ confidence: <Best Position Score Over Best Alias Position Score Number>
14
+ coverage: <Share of Spike Excess At Round USD Positions Number>
15
+ dispersion: <Peak Width Beyond Smoothing As Price Fraction Number>
16
+ [drift]: <Second Half Price Change From First Half Price Number>
17
+ evidence: <Share of Prior Weight Observable At the Price Number>
18
+ hash: <Last Block Hash Hex String>
19
+ [is_unsettled]: <Refinement Was Still Moving After Its Last Pass Bool>
20
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
21
+ outputs: <Payment Outputs Considered Count Number>
22
+ payments: <Estimated Round USD Payments Count Number>
23
+ rivalry: <Best Patterned Rival Position Score Over Best Score Number>
24
+ round_shift: <Price Change If Round BTC Values Were Counted Number>
25
+ significance: <Payment Peak Beyond Noise In Standard Deviations Number>
26
+ spikes: [{
27
+ strength: <Spike Strength Relative to the Strongest Spike Number>
28
+ usd: <Round USD Amount Number>
29
+ }]
30
+ stability: <Largest Price Change From Perturbed Refinement Number>
31
+ [windows]: [{
32
+ is_supported: <Window Price Is Supported By the Data Bool>
33
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
34
+ }]
35
+ }
36
+ [is_provisional]: <Price Is For An Incomplete Day Bool>
37
+ [is_sample]: <Price Is A Rough Estimate From Sample Blocks Bool>
38
+ [is_unguided]: <Price Was Estimated Without a Neighboring Guide Bool>
39
+ }
40
+
41
+ @returns
42
+ {
43
+ record: {
44
+ agreement: <Spike Strengths Agreement With Template Weights Number>
45
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
46
+ cents: <USD Cents Per BTC Number>
47
+ confidence: <Best Position Score Over Best Alias Position Score Number>
48
+ coverage: <Share of Spike Excess At Round USD Positions Number>
49
+ date: <Day YYYY-MM-DD String>
50
+ dispersion: <Peak Width Beyond Smoothing As Price Fraction Number>
51
+ [drift]: <Second Half Price Change From First Half Price Number>
52
+ evidence: <Share of Prior Weight Observable At the Price Number>
53
+ hash: <Last Block Hash Hex String>
54
+ [is_provisional]: <Price Is For An Incomplete Day Bool>
55
+ [is_sample]: <Price Is A Rough Estimate From Sample Blocks Bool>
56
+ [is_unguided]: <Price Was Estimated Without a Neighboring Guide Bool>
57
+ [is_unsettled]: <Refinement Was Still Moving After Its Last Pass Bool>
58
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
59
+ outputs: <Payment Outputs Considered Count Number>
60
+ payments: <Estimated Round USD Payments Count Number>
61
+ rivalry: <Best Patterned Rival Position Score Over Best Score Number>
62
+ round_shift: <Price Change If Round BTC Values Were Counted Number>
63
+ significance: <Payment Peak Beyond Noise In Standard Deviations Number>
64
+ spikes: [{
65
+ strength: <Spike Strength Relative to the Strongest Spike Number>
66
+ usd: <Round USD Amount Number>
67
+ }]
68
+ stability: <Largest Price Change From Perturbed Refinement Number>
69
+ version: <Method Version String>
70
+ [windows]: <Supported Windows Inside the Price Window Count Number>
71
+ }
72
+ }
73
+ */
74
+ module.exports = args => {
75
+ const {date, estimate} = args;
76
+
77
+ const counted = (estimate.windows || []).filter(isCounted);
78
+
79
+ return {
80
+ record: {
81
+ date,
82
+ agreement: estimate.agreement,
83
+ blocks: estimate.blocks,
84
+ cents: estimate.cents,
85
+ confidence: estimate.confidence,
86
+ coverage: estimate.coverage,
87
+ dispersion: estimate.dispersion,
88
+ drift: estimate.drift,
89
+ evidence: estimate.evidence,
90
+ hash: estimate.hash,
91
+ is_provisional: args.is_provisional || undefined,
92
+ is_sample: args.is_sample || undefined,
93
+ is_unguided: args.is_unguided || undefined,
94
+ is_unsettled: estimate.is_unsettled || undefined,
95
+ is_widened: estimate.is_widened || undefined,
96
+ outputs: estimate.outputs,
97
+ payments: estimate.payments,
98
+ rivalry: estimate.rivalry,
99
+ round_shift: estimate.round_shift,
100
+ significance: estimate.significance,
101
+ spikes: estimate.spikes,
102
+ stability: estimate.stability,
103
+ version: methodVersion({}).version,
104
+ windows: !estimate.windows ? undefined : counted.length,
105
+ },
106
+ };
107
+ };