@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,607 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncTimesSeries = require('async/timesSeries');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const addDays = require('./../add_days');
|
|
6
|
+
const checkKnownEstimates = require('./check_known_estimates');
|
|
7
|
+
const {confirmationsAfterDay} = require('./../constants');
|
|
8
|
+
const currentEstimates = require('./current_estimates');
|
|
9
|
+
const {dayOfDate} = require('./../../dates');
|
|
10
|
+
const {earliestSupportedDay} = require('./../known');
|
|
11
|
+
const estimateAndRemember = require('./estimate_and_remember');
|
|
12
|
+
const {findCompleteDay} = require('./../blocks');
|
|
13
|
+
const findStartingDay = require('./find_starting_day');
|
|
14
|
+
const {getBlocksForDate} = require('./../blocks');
|
|
15
|
+
const {getMedianTime} = require('./../blocks');
|
|
16
|
+
const getProvisionalPrice = require('./get_provisional_price');
|
|
17
|
+
const {msPerDay} = require('./../constants');
|
|
18
|
+
const {msPerSecond} = require('./../constants');
|
|
19
|
+
const nearestEstimate = require('./nearest_estimate');
|
|
20
|
+
|
|
21
|
+
const {abs} = Math;
|
|
22
|
+
const dayStart = day => Date.parse(`${day}T00:00:00.000Z`);
|
|
23
|
+
const {isArray} = Array;
|
|
24
|
+
const isHeight = n => Number.isInteger(n) && n >= 0;
|
|
25
|
+
const {max} = Math;
|
|
26
|
+
const maxProvisionalAge = 12;
|
|
27
|
+
const maxTipLagMs = 1000 * 60 * 60 * 6;
|
|
28
|
+
const minProvisionalBlocks = 36;
|
|
29
|
+
const minSupportedDay = earliestSupportedDay({}).day;
|
|
30
|
+
const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
|
|
31
|
+
const startSavingDays = 7;
|
|
32
|
+
const yesterday = -1;
|
|
33
|
+
|
|
34
|
+
/** Get the USD price of BTC on the day of a date using only chain data
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
date: <ISO 8601 Date String>
|
|
38
|
+
get_prices: <Get Known Prices Function> ({}, cbk) => (err, {
|
|
39
|
+
[priors]: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
|
|
40
|
+
[provisional]: {
|
|
41
|
+
<Day YYYY-MM-DD String>: {
|
|
42
|
+
[blocks]: [<First Block Height Number>, <Last Block Height Number>]
|
|
43
|
+
cents: <USD Cents Number>
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
[rates]: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
|
|
47
|
+
})
|
|
48
|
+
height: <Current Best Chain Height Number>
|
|
49
|
+
lnd: <Authenticated LND API Object>
|
|
50
|
+
[logger]: <Winston Logger Object>
|
|
51
|
+
put_price: <Remember Price Function> ({
|
|
52
|
+
agreement: <Spike Strengths Agreement With Template Weights Number>
|
|
53
|
+
blocks: [<First Block Height Number>, <Last Block Height Number>]
|
|
54
|
+
cents: <USD Cents Per BTC Number>
|
|
55
|
+
confidence: <Best Position Score Over Best Alias Position Score Number>
|
|
56
|
+
coverage: <Share of Spike Excess At Round USD Positions Number>
|
|
57
|
+
date: <Day YYYY-MM-DD String>
|
|
58
|
+
dispersion: <Peak Width Beyond Smoothing As Price Fraction Number>
|
|
59
|
+
[drift]: <Second Half Price Change From First Half Price Number>
|
|
60
|
+
evidence: <Share of Prior Weight Observable At the Price Number>
|
|
61
|
+
hash: <Last Block Hash Hex String>
|
|
62
|
+
[is_provisional]: <Price Is For An Incomplete Day Bool>
|
|
63
|
+
[is_sample]: <Price Is A Rough Estimate From Sample Blocks Bool>
|
|
64
|
+
[is_widened]: <Price Was Found Outside the Expected Price Window Bool>
|
|
65
|
+
outputs: <Payment Outputs Considered Count Number>
|
|
66
|
+
payments: <Estimated Round USD Payments Count Number>
|
|
67
|
+
rivalry: <Best Patterned Rival Position Score Over Best Score Number>
|
|
68
|
+
round_shift: <Price Change If Round BTC Values Were Counted Number>
|
|
69
|
+
significance: <Payment Peak Beyond Noise In Standard Deviations Number>
|
|
70
|
+
spikes: [{
|
|
71
|
+
strength: <Spike Strength Relative to the Strongest Spike Number>
|
|
72
|
+
usd: <Round USD Amount Number>
|
|
73
|
+
}]
|
|
74
|
+
stability: <Largest Price Change From Perturbed Refinement Number>
|
|
75
|
+
version: <Method Version String>
|
|
76
|
+
[windows]: <Supported Windows Inside the Price Window Count Number>
|
|
77
|
+
}, cbk) => (err)
|
|
78
|
+
[remove_prices]: <Remove Prices Function> ({
|
|
79
|
+
removed: [{
|
|
80
|
+
day: <Day YYYY-MM-DD String>
|
|
81
|
+
hash: <Last Block Hash Hex String>
|
|
82
|
+
}]
|
|
83
|
+
}, cbk) => (err)
|
|
84
|
+
[unsupported]: <Unsupported Estimates Map Object>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@returns via cbk or Promise
|
|
88
|
+
{
|
|
89
|
+
cents: <USD Cents Per BTC Number>
|
|
90
|
+
is_provisional: <Price Is Provisional Bool>
|
|
91
|
+
}
|
|
92
|
+
*/
|
|
93
|
+
module.exports = (args, cbk) => {
|
|
94
|
+
// The blocks of the day priced in full are kept for its other estimates
|
|
95
|
+
const dayBlocks = new Map();
|
|
96
|
+
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
return asyncAuto({
|
|
99
|
+
// Check arguments
|
|
100
|
+
validate: cbk => {
|
|
101
|
+
if (!args.date || typeof args.date !== 'string') {
|
|
102
|
+
return cbk([400, 'ExpectedDateToGetPriceForDate']);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const {day} = dayOfDate({date: args.date});
|
|
106
|
+
|
|
107
|
+
if (!day) {
|
|
108
|
+
return cbk([400, 'ExpectedValidDateToGetPriceForDate']);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (day < minSupportedDay) {
|
|
112
|
+
return cbk([400, 'DateBeforeSupportedRangeToGetPriceForDate', {
|
|
113
|
+
earliest_day: minSupportedDay,
|
|
114
|
+
}]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof args.get_prices !== 'function') {
|
|
118
|
+
return cbk([400, 'ExpectedGetPricesFunctionToGetPriceForDate']);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!isHeight(args.height)) {
|
|
122
|
+
return cbk([400, 'ExpectedCurrentChainHeightToGetPriceForDate']);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!args.lnd) {
|
|
126
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetPriceForDate']);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof args.put_price !== 'function') {
|
|
130
|
+
return cbk([400, 'ExpectedPutPriceFunctionToGetPriceForDate']);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!!args.remove_prices && typeof args.remove_prices !== 'function') {
|
|
134
|
+
return cbk([400, 'ExpectedRemovePricesFunctionToGetPriceForDate']);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return cbk();
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// The day of the date in UTC
|
|
141
|
+
day: ['validate', ({}, cbk) => cbk(null, dayOfDate(args).day)],
|
|
142
|
+
|
|
143
|
+
// Block times are kept across days to avoid retrieving them twice
|
|
144
|
+
times: ['validate', ({}, cbk) => cbk(null, new Map())],
|
|
145
|
+
|
|
146
|
+
// Get the known prices
|
|
147
|
+
getPrices: ['validate', ({}, cbk) => args.get_prices({}, cbk)],
|
|
148
|
+
|
|
149
|
+
// The known prices that were derived by the current method
|
|
150
|
+
estimates: ['getPrices', ({getPrices}, cbk) => {
|
|
151
|
+
if (!getPrices) {
|
|
152
|
+
return cbk([503, 'ExpectedKnownPricesToGetPriceForDate']);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const {priors, provisional, rates} = currentEstimates({
|
|
156
|
+
priors: getPrices.priors,
|
|
157
|
+
provisional: getPrices.provisional,
|
|
158
|
+
rates: getPrices.rates,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return cbk(null, {priors, provisional, rates});
|
|
162
|
+
}],
|
|
163
|
+
|
|
164
|
+
// Check that recently derived known prices are still in the chain
|
|
165
|
+
checkKnown: ['estimates', ({estimates}, cbk) => {
|
|
166
|
+
return checkKnownEstimates({
|
|
167
|
+
estimates,
|
|
168
|
+
height: args.height,
|
|
169
|
+
lnd: args.lnd,
|
|
170
|
+
},
|
|
171
|
+
cbk);
|
|
172
|
+
}],
|
|
173
|
+
|
|
174
|
+
// Prices from replaced blocks are removed where they are kept too
|
|
175
|
+
removeKnown: ['checkKnown', ({checkKnown}, cbk) => {
|
|
176
|
+
const {removed} = checkKnown;
|
|
177
|
+
|
|
178
|
+
// Exit early when there is nothing to remove or nowhere to remove it
|
|
179
|
+
if (!removed.length || !args.remove_prices) {
|
|
180
|
+
return cbk();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return args.remove_prices({removed}, cbk);
|
|
184
|
+
}],
|
|
185
|
+
|
|
186
|
+
// Estimate the price for a day and remember it when it is supported
|
|
187
|
+
estimate: ['estimates', 'times', ({estimates, times}, cbk) => {
|
|
188
|
+
const estimate = (target, cbk) => {
|
|
189
|
+
return estimateAndRemember({
|
|
190
|
+
estimates,
|
|
191
|
+
times,
|
|
192
|
+
blocks: target.blocks,
|
|
193
|
+
day: target.day,
|
|
194
|
+
height: args.height,
|
|
195
|
+
is_sample: target.is_sample,
|
|
196
|
+
is_unguided: target.is_unguided,
|
|
197
|
+
lnd: args.lnd,
|
|
198
|
+
logger: args.logger,
|
|
199
|
+
put_price: args.put_price,
|
|
200
|
+
unsupported: args.unsupported,
|
|
201
|
+
},
|
|
202
|
+
cbk);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return cbk(null, estimate);
|
|
206
|
+
}],
|
|
207
|
+
|
|
208
|
+
// Check if the day is complete and can have a final price
|
|
209
|
+
getBlocks: [
|
|
210
|
+
'day',
|
|
211
|
+
'estimates',
|
|
212
|
+
'removeKnown',
|
|
213
|
+
'times',
|
|
214
|
+
({day, estimates, times}, cbk) =>
|
|
215
|
+
{
|
|
216
|
+
// Exit early when the price is already known
|
|
217
|
+
if (!!estimates.rates[day]) {
|
|
218
|
+
return cbk();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return getBlocksForDate({
|
|
222
|
+
times,
|
|
223
|
+
date: day,
|
|
224
|
+
height: args.height,
|
|
225
|
+
lnd: args.lnd,
|
|
226
|
+
},
|
|
227
|
+
cbk);
|
|
228
|
+
}],
|
|
229
|
+
|
|
230
|
+
// A recent provisional price of an incomplete day is reused
|
|
231
|
+
reuse: [
|
|
232
|
+
'day',
|
|
233
|
+
'estimates',
|
|
234
|
+
'getBlocks',
|
|
235
|
+
({day, estimates, getBlocks}, cbk) =>
|
|
236
|
+
{
|
|
237
|
+
// Exit early when the price is known or the day is complete
|
|
238
|
+
if (!getBlocks || !!getBlocks.is_complete) {
|
|
239
|
+
return cbk();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const {blocks} = estimates.provisional[day] || {};
|
|
243
|
+
|
|
244
|
+
// A provisional price ends six blocks below the tip it was derived
|
|
245
|
+
// at, so it is reused until about six more blocks have been mined
|
|
246
|
+
const age = !isArray(blocks) ? Infinity : args.height - blocks[1];
|
|
247
|
+
|
|
248
|
+
// Exit early when there is no recent provisional price
|
|
249
|
+
if (age > maxProvisionalAge) {
|
|
250
|
+
return cbk();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return cbk(null, {
|
|
254
|
+
cents: estimates.provisional[day].cents,
|
|
255
|
+
is_provisional: true,
|
|
256
|
+
});
|
|
257
|
+
}],
|
|
258
|
+
|
|
259
|
+
// Get the time of the chain tip when the day is not complete
|
|
260
|
+
getTipTime: [
|
|
261
|
+
'getBlocks',
|
|
262
|
+
'reuse',
|
|
263
|
+
'times',
|
|
264
|
+
({getBlocks, reuse, times}, cbk) =>
|
|
265
|
+
{
|
|
266
|
+
// Exit early when the price is known or the day is complete
|
|
267
|
+
if (!getBlocks || !!getBlocks.is_complete || !!reuse) {
|
|
268
|
+
return cbk();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return getMedianTime({
|
|
272
|
+
times,
|
|
273
|
+
height: args.height,
|
|
274
|
+
lnd: args.lnd,
|
|
275
|
+
},
|
|
276
|
+
cbk);
|
|
277
|
+
}],
|
|
278
|
+
|
|
279
|
+
// A day that has not started, or that the chain has not reached, fails
|
|
280
|
+
checkStarted: ['day', 'getTipTime', ({day, getTipTime}, cbk) => {
|
|
281
|
+
// Exit early when there is no incomplete day to check
|
|
282
|
+
if (!getTipTime) {
|
|
283
|
+
return cbk();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const tipTime = getTipTime.time * msPerSecond;
|
|
287
|
+
|
|
288
|
+
// The chain tip can be later than the clock, as in a simulation
|
|
289
|
+
if (dayStart(day) > max(Date.now(), tipTime)) {
|
|
290
|
+
return cbk([400, 'DateAfterCurrentDayToGetPriceForDate', {day}]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// A chain that is far behind the day has none of its blocks yet
|
|
294
|
+
if (dayStart(day) - tipTime > maxTipLagMs) {
|
|
295
|
+
return cbk([503, 'ChainHasNotReachedDayToGetPriceForDate', {day}]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return cbk();
|
|
299
|
+
}],
|
|
300
|
+
|
|
301
|
+
// The latest complete day guides the price of an incomplete day
|
|
302
|
+
getLatest: [
|
|
303
|
+
'checkStarted',
|
|
304
|
+
'getBlocks',
|
|
305
|
+
'reuse',
|
|
306
|
+
'times',
|
|
307
|
+
({getBlocks, reuse, times}, cbk) =>
|
|
308
|
+
{
|
|
309
|
+
// Exit early when the price is known or the day is complete
|
|
310
|
+
if (!getBlocks || !!getBlocks.is_complete || !!reuse) {
|
|
311
|
+
return cbk();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return findCompleteDay({
|
|
315
|
+
times,
|
|
316
|
+
height: args.height,
|
|
317
|
+
lnd: args.lnd,
|
|
318
|
+
},
|
|
319
|
+
cbk);
|
|
320
|
+
}],
|
|
321
|
+
|
|
322
|
+
// A complete day is bridged to directly, and an incomplete day through
|
|
323
|
+
// the latest complete day before it, so the guide of its provisional
|
|
324
|
+
// price is never further back, both from complete day estimates only
|
|
325
|
+
route: [
|
|
326
|
+
'day',
|
|
327
|
+
'getBlocks',
|
|
328
|
+
'getLatest',
|
|
329
|
+
'reuse',
|
|
330
|
+
({day, getBlocks, getLatest, reuse}, cbk) =>
|
|
331
|
+
{
|
|
332
|
+
// Exit early when the price is already known or is reused
|
|
333
|
+
if (!getBlocks || !!reuse) {
|
|
334
|
+
return cbk();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Exit early when the day is complete
|
|
338
|
+
if (!!getBlocks.is_complete) {
|
|
339
|
+
return cbk(null, {is_complete: true, target: day});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return cbk(null, {latest: getLatest.day, target: getLatest.day});
|
|
343
|
+
}],
|
|
344
|
+
|
|
345
|
+
// The nearest estimate that the day can be bridged to from
|
|
346
|
+
nearest: ['estimates', 'route', ({estimates, route}, cbk) => {
|
|
347
|
+
// Exit early when there is nothing to bridge to
|
|
348
|
+
if (!route) {
|
|
349
|
+
return cbk();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return cbk(null, nearestEstimate({
|
|
353
|
+
estimates,
|
|
354
|
+
day: route.target,
|
|
355
|
+
latest: route.latest,
|
|
356
|
+
}));
|
|
357
|
+
}],
|
|
358
|
+
|
|
359
|
+
// A complete day far from any estimate can start from the latest
|
|
360
|
+
// complete day instead, so the latest complete day is found for it
|
|
361
|
+
getStart: [
|
|
362
|
+
'nearest',
|
|
363
|
+
'route',
|
|
364
|
+
'times',
|
|
365
|
+
({nearest, route, times}, cbk) =>
|
|
366
|
+
{
|
|
367
|
+
// Exit early when there is no complete day to bridge to
|
|
368
|
+
if (!route || !route.is_complete) {
|
|
369
|
+
return cbk();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const {estimate} = nearest;
|
|
373
|
+
|
|
374
|
+
// Exit early when starting over cannot save a week of bridging
|
|
375
|
+
if (!!estimate && estimate.distance <= startSavingDays) {
|
|
376
|
+
return cbk();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return findCompleteDay({
|
|
380
|
+
times,
|
|
381
|
+
height: args.height,
|
|
382
|
+
lnd: args.lnd,
|
|
383
|
+
},
|
|
384
|
+
cbk);
|
|
385
|
+
}],
|
|
386
|
+
|
|
387
|
+
// Start from the latest complete day when no estimate is near the day
|
|
388
|
+
coldStart: [
|
|
389
|
+
'estimate',
|
|
390
|
+
'getStart',
|
|
391
|
+
'nearest',
|
|
392
|
+
'route',
|
|
393
|
+
({estimate, getStart, nearest, route}, cbk) =>
|
|
394
|
+
{
|
|
395
|
+
// Exit early when there is nothing to bridge to
|
|
396
|
+
if (!route) {
|
|
397
|
+
return cbk();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const closest = nearest.estimate;
|
|
401
|
+
|
|
402
|
+
// Exit early when starting over cannot save a week of bridging
|
|
403
|
+
if (!!closest && closest.distance <= startSavingDays) {
|
|
404
|
+
return cbk();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Either kind of day starts from the latest complete day
|
|
408
|
+
const latest = !!getStart ? getStart.day : route.latest;
|
|
409
|
+
|
|
410
|
+
const away = abs(dayStart(latest) - dayStart(route.target)) / msPerDay;
|
|
411
|
+
|
|
412
|
+
// Exit early when the latest complete day is not a week nearer
|
|
413
|
+
if (!!closest && closest.distance - away <= startSavingDays) {
|
|
414
|
+
return cbk();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return findStartingDay({
|
|
418
|
+
latest,
|
|
419
|
+
logger: args.logger,
|
|
420
|
+
estimate: (target, cbk) => estimate({
|
|
421
|
+
blocks: target.day === route.target ? dayBlocks : undefined,
|
|
422
|
+
day: target.day,
|
|
423
|
+
is_sample: target.is_sample,
|
|
424
|
+
is_unguided: target.is_unguided,
|
|
425
|
+
},
|
|
426
|
+
cbk),
|
|
427
|
+
},
|
|
428
|
+
cbk);
|
|
429
|
+
}],
|
|
430
|
+
|
|
431
|
+
// Step day by day from the nearest estimate towards the day
|
|
432
|
+
bridge: [
|
|
433
|
+
'coldStart',
|
|
434
|
+
'estimate',
|
|
435
|
+
'estimates',
|
|
436
|
+
'route',
|
|
437
|
+
({estimate, estimates, route}, cbk) =>
|
|
438
|
+
{
|
|
439
|
+
// Exit early when there is nothing to bridge to
|
|
440
|
+
if (!route) {
|
|
441
|
+
return cbk();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const {latest, target} = route;
|
|
445
|
+
|
|
446
|
+
const nearest = nearestEstimate({estimates, latest, day: target});
|
|
447
|
+
|
|
448
|
+
// Exit early when there is no complete day to bridge from
|
|
449
|
+
if (!nearest.estimate) {
|
|
450
|
+
return cbk();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// A complete day is reached when its neighbor has an estimate, and
|
|
454
|
+
// the latest complete day when it has an estimate itself
|
|
455
|
+
const reach = !!route.is_complete ? 1 : 0;
|
|
456
|
+
|
|
457
|
+
// Every step brings the nearest estimate a day closer to the target
|
|
458
|
+
const steps = max(Number(), nearest.estimate.distance - reach);
|
|
459
|
+
|
|
460
|
+
return asyncTimesSeries(steps, (i, cbk) => {
|
|
461
|
+
const {estimate: guide} = nearestEstimate({
|
|
462
|
+
estimates,
|
|
463
|
+
latest,
|
|
464
|
+
day: target,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const days = guide.day < target ? 1 : yesterday;
|
|
468
|
+
|
|
469
|
+
const next = addDays({days, day: guide.day}).day;
|
|
470
|
+
|
|
471
|
+
// The latest complete day keeps its blocks from the start
|
|
472
|
+
const blocks = next === route.latest ? dayBlocks : undefined;
|
|
473
|
+
|
|
474
|
+
return estimate({blocks, day: next, is_sample: true}, (err, res) => {
|
|
475
|
+
if (!!err) {
|
|
476
|
+
return cbk(err);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// An undetermined latest complete day leaves the day before it
|
|
480
|
+
// to guide the provisional price, as it did before the bridge
|
|
481
|
+
// reached the latest complete day itself
|
|
482
|
+
if (!res.is_supported && !route.is_complete && next === target) {
|
|
483
|
+
return cbk();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!res.is_supported) {
|
|
487
|
+
return cbk([503, 'FailedToFindSupportedPriceToBridgeDay', {
|
|
488
|
+
day: next,
|
|
489
|
+
}]);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return cbk();
|
|
493
|
+
});
|
|
494
|
+
},
|
|
495
|
+
cbk);
|
|
496
|
+
}],
|
|
497
|
+
|
|
498
|
+
// Estimate a complete day that has no price yet
|
|
499
|
+
estimateDay: [
|
|
500
|
+
'bridge',
|
|
501
|
+
'day',
|
|
502
|
+
'estimate',
|
|
503
|
+
'estimates',
|
|
504
|
+
'getBlocks',
|
|
505
|
+
'reuse',
|
|
506
|
+
({day, estimate, estimates, getBlocks, reuse}, cbk) =>
|
|
507
|
+
{
|
|
508
|
+
// Exit early when the price is known or reused, or the day is not over
|
|
509
|
+
if (!!estimates.rates[day] || !!reuse || !getBlocks.is_complete) {
|
|
510
|
+
return cbk();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return estimate({day, blocks: dayBlocks, is_sample: false}, cbk);
|
|
514
|
+
}],
|
|
515
|
+
|
|
516
|
+
// The price for the day, or that it needs a provisional price
|
|
517
|
+
getPrice: [
|
|
518
|
+
'day',
|
|
519
|
+
'estimateDay',
|
|
520
|
+
'estimates',
|
|
521
|
+
'reuse',
|
|
522
|
+
({day, estimateDay, estimates, reuse}, cbk) =>
|
|
523
|
+
{
|
|
524
|
+
// Exit early when the data do not support the price of a complete day
|
|
525
|
+
if (!!estimateDay && !estimateDay.is_supported) {
|
|
526
|
+
return cbk([503, 'FailedToFindSupportedPrice', {day}]);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Exit early when a complete day was priced from its blocks
|
|
530
|
+
if (!!estimateDay) {
|
|
531
|
+
return cbk(null, {cents: estimateDay.cents});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Exit early when the price is known
|
|
535
|
+
if (!!estimates.rates[day]) {
|
|
536
|
+
return cbk(null, {cents: estimates.rates[day].cents});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Exit early when a recent provisional price is reused
|
|
540
|
+
if (!!reuse) {
|
|
541
|
+
return cbk(null, reuse);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// The day is not over yet and needs a provisional price
|
|
545
|
+
return cbk(null, {is_provisional: true});
|
|
546
|
+
}],
|
|
547
|
+
|
|
548
|
+
// The blocks to use for a provisional price
|
|
549
|
+
provisionalBlocks: [
|
|
550
|
+
'getBlocks',
|
|
551
|
+
'getPrice',
|
|
552
|
+
({getBlocks, getPrice}, cbk) =>
|
|
553
|
+
{
|
|
554
|
+
// Exit early when there is no provisional price to derive
|
|
555
|
+
if (!getPrice.is_provisional || !!getPrice.cents) {
|
|
556
|
+
return cbk();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const end = args.height - confirmationsAfterDay + 1;
|
|
560
|
+
|
|
561
|
+
// The confirmed blocks of the day so far
|
|
562
|
+
const confirmed = getBlocks.heights.filter(height => height < end);
|
|
563
|
+
|
|
564
|
+
const first = !confirmed.length ? end : confirmed[Number()];
|
|
565
|
+
const missing = minProvisionalBlocks - confirmed.length;
|
|
566
|
+
|
|
567
|
+
// Pad with the blocks before the day when the day has just begun
|
|
568
|
+
const start = max(Number(), first - max(Number(), missing));
|
|
569
|
+
|
|
570
|
+
return cbk(null, range(start, first).concat(confirmed));
|
|
571
|
+
}],
|
|
572
|
+
|
|
573
|
+
// Derive a provisional price for an incomplete day
|
|
574
|
+
provisional: [
|
|
575
|
+
'day',
|
|
576
|
+
'estimates',
|
|
577
|
+
'getPrice',
|
|
578
|
+
'provisionalBlocks',
|
|
579
|
+
({day, estimates, getPrice, provisionalBlocks}, cbk) =>
|
|
580
|
+
{
|
|
581
|
+
// Exit early when there is no provisional price to derive
|
|
582
|
+
if (!provisionalBlocks) {
|
|
583
|
+
return cbk(null, getPrice);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return getProvisionalPrice({
|
|
587
|
+
day,
|
|
588
|
+
estimates,
|
|
589
|
+
heights: provisionalBlocks,
|
|
590
|
+
lnd: args.lnd,
|
|
591
|
+
logger: args.logger,
|
|
592
|
+
put_price: args.put_price,
|
|
593
|
+
},
|
|
594
|
+
cbk);
|
|
595
|
+
}],
|
|
596
|
+
|
|
597
|
+
// Final price
|
|
598
|
+
price: ['provisional', ({provisional}, cbk) => {
|
|
599
|
+
return cbk(null, {
|
|
600
|
+
cents: provisional.cents,
|
|
601
|
+
is_provisional: !!provisional.is_provisional,
|
|
602
|
+
});
|
|
603
|
+
}],
|
|
604
|
+
},
|
|
605
|
+
returnResult({reject, resolve, of: 'price'}, cbk));
|
|
606
|
+
});
|
|
607
|
+
};
|