@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,246 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncMapSeries = require('async/mapSeries');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const {getMedianTime} = require('./../blocks');
|
|
6
|
+
const getPriceFromBlocks = require('./get_price_from_blocks');
|
|
7
|
+
const {half} = require('./../constants');
|
|
8
|
+
const isSupportedEstimate = require('./is_supported_estimate');
|
|
9
|
+
const {sortBy} = require('./../../arrays');
|
|
10
|
+
const {unpricedErrors} = require('./../constants');
|
|
11
|
+
const {windowHeights} = require('./../blocks');
|
|
12
|
+
|
|
13
|
+
const {isArray} = Array;
|
|
14
|
+
const isHeight = n => Number.isInteger(n) && n >= 0;
|
|
15
|
+
const {isInteger} = Number;
|
|
16
|
+
const {max} = Math;
|
|
17
|
+
const sum = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
18
|
+
const windowBlocksCount = 12;
|
|
19
|
+
|
|
20
|
+
/** Estimate the USD price of BTC on a day from windows of its blocks
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
blocks: <Retrieved Blocks By Height Map Object>
|
|
24
|
+
heights: [<Block Height Number>]
|
|
25
|
+
[is_capping_near_round]: <Cap Values Just Off Round BTC Amounts Too Bool>
|
|
26
|
+
lnd: <Authenticated LND API Object>
|
|
27
|
+
[max_price]: <Maximum Possible USD Price Number>
|
|
28
|
+
[min_price]: <Minimum Possible USD Price Number>
|
|
29
|
+
times: <Block Times By Height Map Object>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@returns via cbk or Promise
|
|
33
|
+
{
|
|
34
|
+
[agreement]: <Spike Strengths Agreement With Template Weights Number>
|
|
35
|
+
[cents]: <USD Cents Per BTC Number>
|
|
36
|
+
[confidence]: <Best Position Score Over Best Alias Position Score Number>
|
|
37
|
+
[coverage]: <Share of Spike Excess At Round USD Positions Number>
|
|
38
|
+
[dispersion]: <Peak Width Beyond Smoothing As Price Fraction Number>
|
|
39
|
+
[evidence]: <Share of Prior Weight Observable At the Price Number>
|
|
40
|
+
is_supported: <Supported Windows Cover Enough of the Day Bool>
|
|
41
|
+
[is_unsettled]: <Refinement Was Still Moving After Its Last Pass Bool>
|
|
42
|
+
[outputs]: <Payment Outputs Considered Count Number>
|
|
43
|
+
[payments]: <Estimated Round USD Payments Count Number>
|
|
44
|
+
[rivalry]: <Best Patterned Rival Position Score Over Best Score Number>
|
|
45
|
+
[round_shift]: <Price Change If Round BTC Values Were Counted Number>
|
|
46
|
+
[significance]: <Payment Peak Beyond Noise In Standard Deviations Number>
|
|
47
|
+
[spikes]: [{
|
|
48
|
+
strength: <Spike Strength Relative to the Strongest Spike Number>
|
|
49
|
+
usd: <Round USD Amount Number>
|
|
50
|
+
}]
|
|
51
|
+
[stability]: <Largest Price Change From Perturbed Refinement Number>
|
|
52
|
+
windows: [{
|
|
53
|
+
blocks: [<First Block Height Number>, <Last Block Height Number>]
|
|
54
|
+
[cents]: <USD Cents Per BTC Number>
|
|
55
|
+
is_supported: <Window Price Is Supported By the Data Bool>
|
|
56
|
+
[is_widened]: <Search Was Widened Beyond the Price Window Bool>
|
|
57
|
+
seconds: <Time Covered By the Window Seconds Number>
|
|
58
|
+
}]
|
|
59
|
+
}
|
|
60
|
+
*/
|
|
61
|
+
module.exports = (args, cbk) => {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
return asyncAuto({
|
|
64
|
+
// Check arguments
|
|
65
|
+
validate: cbk => {
|
|
66
|
+
if (!args.blocks) {
|
|
67
|
+
return cbk([400, 'ExpectedRetrievedBlocksMapToEstimateWindows']);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!isArray(args.heights) || !args.heights.length) {
|
|
71
|
+
return cbk([400, 'ExpectedBlockHeightsToEstimateWindows']);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!args.heights.every(isHeight)) {
|
|
75
|
+
return cbk([400, 'ExpectedBlockHeightNumbersToEstimateWindows']);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!args.lnd) {
|
|
79
|
+
return cbk([400, 'ExpectedAuthenticatedLndToEstimateWindows']);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!args.times) {
|
|
83
|
+
return cbk([400, 'ExpectedBlockTimesMapToEstimateWindows']);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return cbk();
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Split the blocks into windows
|
|
90
|
+
windows: ['validate', ({}, cbk) => {
|
|
91
|
+
const heights = args.heights.slice().sort((a, b) => a - b);
|
|
92
|
+
|
|
93
|
+
return cbk(null, windowHeights({heights, count: windowBlocksCount}));
|
|
94
|
+
}],
|
|
95
|
+
|
|
96
|
+
// Estimate each window on its own
|
|
97
|
+
estimates: ['windows', ({windows}, cbk) => {
|
|
98
|
+
return asyncMapSeries(windows.windows, (heights, cbk) => {
|
|
99
|
+
return getPriceFromBlocks({
|
|
100
|
+
heights,
|
|
101
|
+
blocks: args.blocks,
|
|
102
|
+
is_capping_near_round: args.is_capping_near_round,
|
|
103
|
+
lnd: args.lnd,
|
|
104
|
+
max_price: args.max_price,
|
|
105
|
+
min_price: args.min_price,
|
|
106
|
+
},
|
|
107
|
+
(err, res) => {
|
|
108
|
+
// A window whose outputs cannot be priced is not supported
|
|
109
|
+
if (!!err && unpricedErrors.includes(err[1])) {
|
|
110
|
+
return cbk(null, {heights});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!!err) {
|
|
114
|
+
return cbk(err);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const support = isSupportedEstimate({
|
|
118
|
+
confidence: res.confidence,
|
|
119
|
+
evidence: res.evidence,
|
|
120
|
+
is_unconstrained: !args.min_price || !!res.is_widened,
|
|
121
|
+
payments: res.payments,
|
|
122
|
+
rivalry: res.rivalry,
|
|
123
|
+
significance: res.significance,
|
|
124
|
+
spikes: res.spikes,
|
|
125
|
+
stability: res.stability,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return cbk(null, {
|
|
129
|
+
heights,
|
|
130
|
+
estimate: res,
|
|
131
|
+
is_supported: support.is_supported,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
cbk);
|
|
136
|
+
}],
|
|
137
|
+
|
|
138
|
+
// Get the median times past bounding the windows, from the block before
|
|
139
|
+
getTimes: ['estimates', 'windows', ({windows}, cbk) => {
|
|
140
|
+
const [[first]] = windows.windows;
|
|
141
|
+
|
|
142
|
+
// The first block of the chain has no block before it
|
|
143
|
+
const start = max(Number(), first - 1);
|
|
144
|
+
|
|
145
|
+
const bounds = [start].concat(windows.windows.map(heights => {
|
|
146
|
+
return heights[heights.length - 1];
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
// The retrieved blocks already have their timestamps
|
|
150
|
+
args.blocks.forEach((block, height) => {
|
|
151
|
+
// Exit early when the time is known or the block has no time
|
|
152
|
+
if (args.times.has(height) || !isInteger(block.time)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return args.times.set(height, block.time);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return asyncMapSeries(bounds, (height, cbk) => {
|
|
160
|
+
return getMedianTime({
|
|
161
|
+
height,
|
|
162
|
+
lnd: args.lnd,
|
|
163
|
+
times: args.times,
|
|
164
|
+
},
|
|
165
|
+
cbk);
|
|
166
|
+
},
|
|
167
|
+
cbk);
|
|
168
|
+
}],
|
|
169
|
+
|
|
170
|
+
// Price the day from the windows that are supported
|
|
171
|
+
price: ['estimates', 'getTimes', ({estimates, getTimes}, cbk) => {
|
|
172
|
+
// Each window covers the time since the end of the window before it
|
|
173
|
+
const windows = estimates.map((window, index) => {
|
|
174
|
+
const {estimate} = window;
|
|
175
|
+
const [first] = window.heights;
|
|
176
|
+
const [last] = window.heights.slice(-1);
|
|
177
|
+
|
|
178
|
+
const elapsed = getTimes[index + 1].time - getTimes[index].time;
|
|
179
|
+
|
|
180
|
+
// A window whose search had to be widened is not counted
|
|
181
|
+
const isWidened = !!estimate && !!estimate.is_widened;
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
estimate,
|
|
185
|
+
blocks: [first, last],
|
|
186
|
+
cents: !estimate ? undefined : estimate.cents,
|
|
187
|
+
is_counted: !!window.is_supported && !isWidened,
|
|
188
|
+
is_supported: !!window.is_supported,
|
|
189
|
+
is_widened: isWidened || undefined,
|
|
190
|
+
seconds: max(Number(), elapsed),
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const counted = windows.filter(n => n.is_counted);
|
|
195
|
+
|
|
196
|
+
const coveredSeconds = sum(counted.map(n => n.seconds));
|
|
197
|
+
const totalSeconds = sum(windows.map(n => n.seconds));
|
|
198
|
+
|
|
199
|
+
const reported = windows.map(window => ({
|
|
200
|
+
blocks: window.blocks,
|
|
201
|
+
cents: window.cents,
|
|
202
|
+
is_supported: window.is_supported,
|
|
203
|
+
is_widened: window.is_widened,
|
|
204
|
+
seconds: window.seconds,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
// Exit early when the supported windows cover too little of the day
|
|
208
|
+
if (!totalSeconds || coveredSeconds < totalSeconds * half) {
|
|
209
|
+
return cbk(null, {is_supported: false, windows: reported});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// The median is the price with half of the covered time below it
|
|
213
|
+
const {sorted} = sortBy({array: counted, attribute: 'cents'});
|
|
214
|
+
|
|
215
|
+
const below = sorted.map((window, position) => {
|
|
216
|
+
return sum(sorted.slice(Number(), position + 1).map(n => n.seconds));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const middle = below.findIndex(n => n >= coveredSeconds * half);
|
|
220
|
+
|
|
221
|
+
const {estimate} = sorted[middle];
|
|
222
|
+
|
|
223
|
+
// Measures are the median window's, outputs and payments are totals
|
|
224
|
+
return cbk(null, {
|
|
225
|
+
agreement: estimate.agreement,
|
|
226
|
+
cents: estimate.cents,
|
|
227
|
+
confidence: estimate.confidence,
|
|
228
|
+
coverage: estimate.coverage,
|
|
229
|
+
dispersion: estimate.dispersion,
|
|
230
|
+
evidence: estimate.evidence,
|
|
231
|
+
is_supported: true,
|
|
232
|
+
is_unsettled: estimate.is_unsettled,
|
|
233
|
+
outputs: sum(counted.map(n => n.estimate.outputs)),
|
|
234
|
+
payments: sum(counted.map(n => n.estimate.payments)),
|
|
235
|
+
rivalry: estimate.rivalry,
|
|
236
|
+
round_shift: estimate.round_shift,
|
|
237
|
+
significance: estimate.significance,
|
|
238
|
+
spikes: estimate.spikes,
|
|
239
|
+
stability: estimate.stability,
|
|
240
|
+
windows: reported,
|
|
241
|
+
});
|
|
242
|
+
}],
|
|
243
|
+
},
|
|
244
|
+
returnResult({reject, resolve, of: 'price'}, cbk));
|
|
245
|
+
});
|
|
246
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
|
|
7
|
+
const maxStartDays = 7;
|
|
8
|
+
|
|
9
|
+
/** Find a starting day whose price its own blocks clearly identify
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
estimate: <Estimate Day Price Function> ({
|
|
13
|
+
day: <Day YYYY-MM-DD String>
|
|
14
|
+
is_sample: <Estimate From a Sample of Blocks Bool>
|
|
15
|
+
is_unguided: <Estimate Without a Neighboring Guide Bool>
|
|
16
|
+
}, cbk) => (err, {is_supported: <Price Is Supported Bool>})
|
|
17
|
+
latest: <Latest Complete Day YYYY-MM-DD String>
|
|
18
|
+
[logger]: <Winston Logger Object>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@returns via cbk or Promise
|
|
22
|
+
{
|
|
23
|
+
day: <Supported Starting Day YYYY-MM-DD String>
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = ({estimate, latest, logger}, cbk) => {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
return asyncAuto({
|
|
29
|
+
// Try the latest complete day, then the days before it
|
|
30
|
+
findStart: cbk => {
|
|
31
|
+
const start = {};
|
|
32
|
+
const tried = [];
|
|
33
|
+
|
|
34
|
+
return asyncTimesSeries(maxStartDays, (i, cbk) => {
|
|
35
|
+
// Exit early when a supported starting day was already found
|
|
36
|
+
if (!!start.day) {
|
|
37
|
+
return cbk();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const {day} = addDays({day: latest, days: -i});
|
|
41
|
+
|
|
42
|
+
tried.push(day);
|
|
43
|
+
|
|
44
|
+
return estimate({
|
|
45
|
+
day,
|
|
46
|
+
is_sample: false,
|
|
47
|
+
is_unguided: true,
|
|
48
|
+
},
|
|
49
|
+
(err, res) => {
|
|
50
|
+
if (!!err) {
|
|
51
|
+
return cbk(err);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Exit early when the day clearly identifies its price
|
|
55
|
+
if (!!res.is_supported) {
|
|
56
|
+
start.day = day;
|
|
57
|
+
|
|
58
|
+
return cbk();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!!logger) {
|
|
62
|
+
logger.info({unsupported_starting_day: day});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return cbk();
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
err => {
|
|
69
|
+
if (!!err) {
|
|
70
|
+
return cbk(err);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Exit early when no starting price is clearly identified
|
|
74
|
+
if (!start.day) {
|
|
75
|
+
return cbk([503, 'FailedToFindSupportedStartingPrice', {
|
|
76
|
+
days: tried,
|
|
77
|
+
}]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return cbk(null, {day: start.day});
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
returnResult({reject, resolve, of: 'findStart'}, cbk));
|
|
85
|
+
});
|
|
86
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const currentEstimates = require('./current_estimates');
|
|
5
|
+
const {putPricesFile} = require('./../../storage');
|
|
6
|
+
|
|
7
|
+
const {isArray} = Array;
|
|
8
|
+
const types = ['priors', 'provisional', 'rates'];
|
|
9
|
+
|
|
10
|
+
/** Forget estimates whose last blocks are no longer in the chain
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
estimates: {
|
|
14
|
+
priors: {<Day YYYY-MM-DD String>: <Estimate Object>}
|
|
15
|
+
[provisional]: {<Day YYYY-MM-DD String>: <Estimate Object>}
|
|
16
|
+
rates: {<Day YYYY-MM-DD String>: <Estimate Object>}
|
|
17
|
+
}
|
|
18
|
+
fs: {
|
|
19
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
20
|
+
makeDirectory: <Make Directory Function> (path, cbk) => {}
|
|
21
|
+
renameFile: <Rename File Function> (from, to, cbk) => {}
|
|
22
|
+
writeFile: <Write File Contents Function> (path, contents, cbk) => {}
|
|
23
|
+
}
|
|
24
|
+
path: <Prices File Path String>
|
|
25
|
+
removed: [{
|
|
26
|
+
day: <Day YYYY-MM-DD String>
|
|
27
|
+
hash: <Last Block Hash Hex String>
|
|
28
|
+
}]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@returns via cbk or Promise
|
|
32
|
+
*/
|
|
33
|
+
module.exports = ({estimates, fs, path, removed}, cbk) => {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
return asyncAuto({
|
|
36
|
+
// Check arguments
|
|
37
|
+
validate: cbk => {
|
|
38
|
+
if (!estimates || !estimates.priors || !estimates.rates) {
|
|
39
|
+
return cbk([400, 'ExpectedKnownEstimatesToForgetEstimates']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!fs) {
|
|
43
|
+
return cbk([400, 'ExpectedFileSystemMethodsToForgetEstimates']);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!path) {
|
|
47
|
+
return cbk([400, 'ExpectedPricesFilePathToForgetEstimates']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!isArray(removed)) {
|
|
51
|
+
return cbk([400, 'ExpectedRemovedEstimatesToForgetEstimates']);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return cbk();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Forget the estimates that are kept in memory
|
|
58
|
+
forget: ['validate', ({}, cbk) => {
|
|
59
|
+
removed.forEach(({day, hash}) => {
|
|
60
|
+
types.filter(type => !!estimates[type]).forEach(type => {
|
|
61
|
+
const known = estimates[type][day];
|
|
62
|
+
|
|
63
|
+
if (!!known && known.hash === hash) {
|
|
64
|
+
delete estimates[type][day];
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return cbk();
|
|
70
|
+
}],
|
|
71
|
+
|
|
72
|
+
// Remove the estimates from the prices file
|
|
73
|
+
write: ['forget', ({}, cbk) => {
|
|
74
|
+
// Exit early when there is nothing to remove
|
|
75
|
+
if (!removed.length) {
|
|
76
|
+
return cbk();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return putPricesFile({
|
|
80
|
+
fs,
|
|
81
|
+
path,
|
|
82
|
+
removed,
|
|
83
|
+
priors: estimates.priors,
|
|
84
|
+
provisional: estimates.provisional,
|
|
85
|
+
rates: estimates.rates,
|
|
86
|
+
},
|
|
87
|
+
cbk);
|
|
88
|
+
}],
|
|
89
|
+
|
|
90
|
+
// Keep the current prices that are now in the file
|
|
91
|
+
update: ['write', ({write}, cbk) => {
|
|
92
|
+
// Exit early when the file was not written
|
|
93
|
+
if (!write) {
|
|
94
|
+
return cbk();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Prices in the file from other versions of the method are not used
|
|
98
|
+
const current = currentEstimates({
|
|
99
|
+
priors: write.priors,
|
|
100
|
+
provisional: write.provisional,
|
|
101
|
+
rates: write.rates,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
estimates.priors = current.priors;
|
|
105
|
+
estimates.provisional = current.provisional;
|
|
106
|
+
estimates.rates = current.rates;
|
|
107
|
+
|
|
108
|
+
return cbk();
|
|
109
|
+
}],
|
|
110
|
+
},
|
|
111
|
+
returnResult({reject, resolve}, cbk));
|
|
112
|
+
});
|
|
113
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getHeight} = require('ln-service');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const currentEstimates = require('./current_estimates');
|
|
6
|
+
const {dayOfDate} = require('./../../dates');
|
|
7
|
+
const forgetEstimates = require('./forget_estimates');
|
|
8
|
+
const getPriceForDate = require('./get_price_for_date');
|
|
9
|
+
const {getPricesFile} = require('./../../storage');
|
|
10
|
+
const storeEstimate = require('./store_estimate');
|
|
11
|
+
|
|
12
|
+
const {assign} = Object;
|
|
13
|
+
|
|
14
|
+
/** Get the USD price of BTC on a date derived from chain data
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
date: <Day YYYY-MM-DD String>
|
|
18
|
+
estimates: {
|
|
19
|
+
[priors]: {<Day YYYY-MM-DD String>: <Estimate Object>}
|
|
20
|
+
provisional: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
|
|
21
|
+
[rates]: {<Day YYYY-MM-DD String>: <Estimate Object>}
|
|
22
|
+
samples: <Count of Sample Estimates Stored Number>
|
|
23
|
+
}
|
|
24
|
+
fs: {
|
|
25
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
26
|
+
makeDirectory: <Make Directory Function> (path, cbk) => {}
|
|
27
|
+
renameFile: <Rename File Function> (from, to, cbk) => {}
|
|
28
|
+
writeFile: <Write File Contents Function> (path, contents, cbk) => {}
|
|
29
|
+
}
|
|
30
|
+
[known]: {
|
|
31
|
+
<Day YYYY-MM-DD String>: {
|
|
32
|
+
cents: <USD Cents Per BTC Number>
|
|
33
|
+
version: <Method Version String>
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lnd: <Authenticated LND API Object>
|
|
37
|
+
[logger]: <Winston Logger Object>
|
|
38
|
+
path: <Prices File Path String>
|
|
39
|
+
[unsupported]: <Unsupported Estimates Map Object>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@returns via cbk or Promise
|
|
43
|
+
{
|
|
44
|
+
cents: <USD Cents Per BTC Number>
|
|
45
|
+
}
|
|
46
|
+
*/
|
|
47
|
+
module.exports = (args, cbk) => {
|
|
48
|
+
const {date, estimates, fs, lnd, logger, path, unsupported} = args;
|
|
49
|
+
|
|
50
|
+
const known = args.known || {};
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
return asyncAuto({
|
|
54
|
+
// Check arguments
|
|
55
|
+
validate: cbk => {
|
|
56
|
+
if (!date) {
|
|
57
|
+
return cbk([400, 'ExpectedDateToGetChainPrice']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!dayOfDate({date}).day) {
|
|
61
|
+
return cbk([400, 'ExpectedValidDateToGetChainPrice']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!estimates) {
|
|
65
|
+
return cbk([400, 'ExpectedKnownEstimatesToGetChainPrice']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!fs) {
|
|
69
|
+
return cbk([400, 'ExpectedFileSystemMethodsToGetChainPrice']);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!lnd) {
|
|
73
|
+
return cbk([400, 'ExpectedAuthenticatedLndToGetChainPrice']);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!path) {
|
|
77
|
+
return cbk([400, 'ExpectedPricesFilePathToGetChainPrice']);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return cbk();
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// A price shipped with the method answers for its day
|
|
84
|
+
shipped: ['validate', ({}, cbk) => {
|
|
85
|
+
const shipped = known[dayOfDate({date}).day];
|
|
86
|
+
|
|
87
|
+
return cbk(null, !shipped ? undefined : {cents: shipped.cents});
|
|
88
|
+
}],
|
|
89
|
+
|
|
90
|
+
// Get the previously derived prices
|
|
91
|
+
getPrices: ['shipped', ({shipped}, cbk) => {
|
|
92
|
+
// Exit early when the price was shipped or the prices are loaded
|
|
93
|
+
if (!!shipped || !!estimates.rates) {
|
|
94
|
+
return cbk();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return getPricesFile({fs, path}, cbk);
|
|
98
|
+
}],
|
|
99
|
+
|
|
100
|
+
// Keep the previously derived prices of the current method
|
|
101
|
+
loadPrices: ['getPrices', ({getPrices}, cbk) => {
|
|
102
|
+
// Exit early when there are no prices to load
|
|
103
|
+
if (!getPrices) {
|
|
104
|
+
return cbk();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Prices derived by another version of the method are not used
|
|
108
|
+
const current = currentEstimates({
|
|
109
|
+
priors: getPrices.priors,
|
|
110
|
+
provisional: getPrices.provisional,
|
|
111
|
+
rates: getPrices.rates,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
estimates.priors = current.priors;
|
|
115
|
+
estimates.provisional = current.provisional;
|
|
116
|
+
estimates.rates = current.rates;
|
|
117
|
+
|
|
118
|
+
return cbk();
|
|
119
|
+
}],
|
|
120
|
+
|
|
121
|
+
// Get the current chain height
|
|
122
|
+
getHeight: ['shipped', ({shipped}, cbk) => {
|
|
123
|
+
// Exit early when the price was shipped with the method
|
|
124
|
+
if (!!shipped) {
|
|
125
|
+
return cbk();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return getHeight({lnd}, cbk);
|
|
129
|
+
}],
|
|
130
|
+
|
|
131
|
+
// Derive the price for the date
|
|
132
|
+
getPrice: [
|
|
133
|
+
'getHeight',
|
|
134
|
+
'loadPrices',
|
|
135
|
+
'shipped',
|
|
136
|
+
({getHeight, shipped}, cbk) =>
|
|
137
|
+
{
|
|
138
|
+
// Exit early when the price was shipped with the method
|
|
139
|
+
if (!!shipped) {
|
|
140
|
+
return cbk(null, shipped);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Shipped prices guide the days after them but are never stored
|
|
144
|
+
return getPriceForDate({
|
|
145
|
+
date,
|
|
146
|
+
lnd,
|
|
147
|
+
logger,
|
|
148
|
+
unsupported,
|
|
149
|
+
get_prices: ({}, cbk) => cbk(null, {
|
|
150
|
+
priors: estimates.priors,
|
|
151
|
+
provisional: estimates.provisional,
|
|
152
|
+
rates: assign({}, known, estimates.rates),
|
|
153
|
+
}),
|
|
154
|
+
height: getHeight.current_block_height,
|
|
155
|
+
put_price: (estimate, cbk) => {
|
|
156
|
+
return storeEstimate({estimate, estimates, fs, logger, path}, cbk);
|
|
157
|
+
},
|
|
158
|
+
remove_prices: ({removed}, cbk) => {
|
|
159
|
+
return forgetEstimates({estimates, fs, path, removed}, cbk);
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
cbk);
|
|
163
|
+
}],
|
|
164
|
+
},
|
|
165
|
+
returnResult({reject, resolve, of: 'getPrice'}, cbk));
|
|
166
|
+
});
|
|
167
|
+
};
|