tardis-dev 12.5.22 → 12.6.7
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/dist/apikeyaccessinfo.js +1 -1
- package/dist/apikeyaccessinfo.js.map +1 -1
- package/dist/clearcache.js +12 -12
- package/dist/clearcache.js.map +1 -1
- package/dist/combine.js +1 -1
- package/dist/combine.js.map +1 -1
- package/dist/computable/booksnapshot.d.ts.map +1 -1
- package/dist/computable/booksnapshot.js +1 -1
- package/dist/computable/booksnapshot.js.map +1 -1
- package/dist/computable/tradebar.d.ts.map +1 -1
- package/dist/computable/tradebar.js.map +1 -1
- package/dist/debug.js +1 -1
- package/dist/debug.js.map +1 -1
- package/dist/downloaddatasets.js +13 -13
- package/dist/downloaddatasets.js.map +1 -1
- package/dist/exchangedetails.js +1 -1
- package/dist/exchangedetails.js.map +1 -1
- package/dist/filter.js.map +1 -1
- package/dist/handy.js +10 -10
- package/dist/handy.js.map +1 -1
- package/dist/instrumentinfo.js +1 -1
- package/dist/instrumentinfo.js.map +1 -1
- package/dist/mappers/binance.d.ts.map +1 -1
- package/dist/mappers/binance.js +2 -2
- package/dist/mappers/binance.js.map +1 -1
- package/dist/mappers/binanceoptions.js +10 -10
- package/dist/mappers/binanceoptions.js.map +1 -1
- package/dist/mappers/bitflyer.js +1 -1
- package/dist/mappers/bitflyer.js.map +1 -1
- package/dist/mappers/bybit.d.ts +12 -1
- package/dist/mappers/bybit.d.ts.map +1 -1
- package/dist/mappers/bybit.js +21 -3
- package/dist/mappers/bybit.js.map +1 -1
- package/dist/mappers/coinbase.js +2 -2
- package/dist/mappers/coinbase.js.map +1 -1
- package/dist/mappers/dydx.d.ts +6 -14
- package/dist/mappers/dydx.d.ts.map +1 -1
- package/dist/mappers/dydx.js +1 -1
- package/dist/mappers/dydx.js.map +1 -1
- package/dist/mappers/ftx.js +2 -2
- package/dist/mappers/ftx.js.map +1 -1
- package/dist/mappers/huobi.d.ts.map +1 -1
- package/dist/mappers/huobi.js +11 -11
- package/dist/mappers/huobi.js.map +1 -1
- package/dist/mappers/okex.js +14 -14
- package/dist/mappers/okex.js.map +1 -1
- package/dist/mappers/phemex.d.ts.map +1 -1
- package/dist/mappers/phemex.js.map +1 -1
- package/dist/realtimefeeds/ascendex.js +2 -2
- package/dist/realtimefeeds/ascendex.js.map +1 -1
- package/dist/realtimefeeds/binance.js +1 -1
- package/dist/realtimefeeds/binance.js.map +1 -1
- package/dist/realtimefeeds/bitmex.js +1 -1
- package/dist/realtimefeeds/bitmex.js.map +1 -1
- package/dist/realtimefeeds/ftx.js +1 -1
- package/dist/realtimefeeds/ftx.js.map +1 -1
- package/dist/realtimefeeds/huobi.js +7 -7
- package/dist/realtimefeeds/huobi.js.map +1 -1
- package/dist/realtimefeeds/okex.js +1 -1
- package/dist/realtimefeeds/okex.js.map +1 -1
- package/dist/realtimefeeds/realtimefeed.js +9 -9
- package/dist/realtimefeeds/realtimefeed.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +75 -40
- package/dist/replay.js.map +1 -1
- package/dist/stream.js +4 -4
- package/dist/stream.js.map +1 -1
- package/dist/worker.js +12 -12
- package/dist/worker.js.map +1 -1
- package/package.json +19 -18
- package/src/computable/booksnapshot.ts +4 -2
- package/src/computable/tradebar.ts +4 -1
- package/src/downloaddatasets.ts +188 -188
- package/src/filter.ts +69 -69
- package/src/handy.ts +2 -2
- package/src/instrumentinfo.ts +1 -1
- package/src/mappers/ascendex.ts +156 -156
- package/src/mappers/binance.ts +6 -3
- package/src/mappers/bybit.ts +28 -4
- package/src/mappers/coinflex.ts +159 -159
- package/src/mappers/delta.ts +175 -175
- package/src/mappers/dydx.ts +303 -306
- package/src/mappers/gateio.ts +117 -117
- package/src/mappers/gateiofutures.ts +185 -185
- package/src/mappers/huobi.ts +4 -2
- package/src/mappers/phemex.ts +179 -177
- package/src/mappers/poloniex.ts +150 -150
- package/src/mappers/serum.ts +103 -103
- package/src/mappers/upbit.ts +104 -104
- package/src/realtimefeeds/ascendex.ts +65 -65
- package/src/realtimefeeds/coinflex.ts +29 -29
- package/src/realtimefeeds/delta.ts +27 -27
- package/src/realtimefeeds/dydx.ts +40 -40
- package/src/realtimefeeds/gateio.ts +41 -41
- package/src/realtimefeeds/gateiofutures.ts +90 -90
- package/src/realtimefeeds/poloniex.ts +28 -28
- package/src/realtimefeeds/realtimefeed.ts +2 -2
- package/src/realtimefeeds/upbit.ts +35 -35
- package/src/replay.ts +61 -18
- package/src/stream.ts +1 -1
package/src/downloaddatasets.ts
CHANGED
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import { existsSync } from 'fs-extra'
|
|
2
|
-
import pMap from 'p-map'
|
|
3
|
-
import { debug } from './debug'
|
|
4
|
-
import { DatasetType } from './exchangedetails'
|
|
5
|
-
import { addDays, doubleDigit, download, parseAsUTCDate, sequence } from './handy'
|
|
6
|
-
import { getOptions } from './options'
|
|
7
|
-
import { Exchange } from './types'
|
|
8
|
-
|
|
9
|
-
const CONCURRENCY_LIMIT = 20
|
|
10
|
-
const MILLISECONDS_IN_SINGLE_DAY = 24 * 60 * 60 * 1000
|
|
11
|
-
const DEFAULT_DOWNLOAD_DIR = './datasets'
|
|
12
|
-
|
|
13
|
-
const options = getOptions()
|
|
14
|
-
|
|
15
|
-
export async function downloadDatasets(downloadDatasetsOptions: DownloadDatasetsOptions) {
|
|
16
|
-
const { exchange, dataTypes, from, to, symbols } = downloadDatasetsOptions
|
|
17
|
-
const apiKey = downloadDatasetsOptions.apiKey !== undefined ? downloadDatasetsOptions.apiKey : options.apiKey
|
|
18
|
-
const downloadDir = downloadDatasetsOptions.downloadDir !== undefined ? downloadDatasetsOptions.downloadDir : DEFAULT_DOWNLOAD_DIR
|
|
19
|
-
const format = downloadDatasetsOptions.format !== undefined ? downloadDatasetsOptions.format : 'csv'
|
|
20
|
-
const getFilename = downloadDatasetsOptions.getFilename !== undefined ? downloadDatasetsOptions.getFilename : getFilenameDefault
|
|
21
|
-
const skipIfExists = downloadDatasetsOptions.skipIfExists === undefined ? true : downloadDatasetsOptions.skipIfExists
|
|
22
|
-
|
|
23
|
-
// in case someone provided 'api/exchange' symbol, transform it to symbol that is accepted by datasets API
|
|
24
|
-
const datasetsSymbols = symbols.map((s) => s.replace(/\/|:/g, '-').toUpperCase())
|
|
25
|
-
|
|
26
|
-
for (const symbol of datasetsSymbols) {
|
|
27
|
-
for (const dataType of dataTypes) {
|
|
28
|
-
const { daysCountToFetch, startDate } = getDownloadDateRange(downloadDatasetsOptions)
|
|
29
|
-
const startTimestamp = new Date().valueOf()
|
|
30
|
-
debug('dataset download started for %s %s %s from %s to %s', exchange, dataType, symbol, from, to)
|
|
31
|
-
|
|
32
|
-
if (daysCountToFetch > 1) {
|
|
33
|
-
// start with downloading last day of the range, validates is API key has access to the end range of requested data
|
|
34
|
-
await downloadDataSet(
|
|
35
|
-
getDownloadOptions({
|
|
36
|
-
exchange,
|
|
37
|
-
symbol,
|
|
38
|
-
apiKey,
|
|
39
|
-
downloadDir,
|
|
40
|
-
dataType,
|
|
41
|
-
format,
|
|
42
|
-
getFilename,
|
|
43
|
-
date: addDays(startDate, daysCountToFetch - 1)
|
|
44
|
-
}),
|
|
45
|
-
skipIfExists
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// then download the first day of the range, validates is API key has access to the start range of requested data
|
|
50
|
-
await downloadDataSet(
|
|
51
|
-
getDownloadOptions({
|
|
52
|
-
exchange,
|
|
53
|
-
symbol,
|
|
54
|
-
apiKey,
|
|
55
|
-
downloadDir,
|
|
56
|
-
dataType,
|
|
57
|
-
format,
|
|
58
|
-
getFilename,
|
|
59
|
-
date: startDate
|
|
60
|
-
}),
|
|
61
|
-
skipIfExists
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
// download the rest concurrently up to the CONCURRENCY_LIMIT
|
|
65
|
-
await pMap(
|
|
66
|
-
sequence(daysCountToFetch - 1, 1), // this will produce Iterable sequence from 1 to daysCountToFetch - 1 (as we already downloaded data for the first and last day)
|
|
67
|
-
(offset) =>
|
|
68
|
-
downloadDataSet(
|
|
69
|
-
getDownloadOptions({
|
|
70
|
-
exchange,
|
|
71
|
-
symbol,
|
|
72
|
-
apiKey,
|
|
73
|
-
downloadDir,
|
|
74
|
-
dataType,
|
|
75
|
-
format,
|
|
76
|
-
getFilename,
|
|
77
|
-
date: addDays(startDate, offset)
|
|
78
|
-
}),
|
|
79
|
-
skipIfExists
|
|
80
|
-
),
|
|
81
|
-
{ concurrency: CONCURRENCY_LIMIT }
|
|
82
|
-
)
|
|
83
|
-
const elapsedSeconds = (new Date().valueOf() - startTimestamp) / 1000
|
|
84
|
-
|
|
85
|
-
debug('dataset download finished for %s %s %s from %s to %s, time: %s seconds', exchange, dataType, symbol, from, to, elapsedSeconds)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function downloadDataSet(downloadOptions: DownloadOptions, skipIfExists: boolean) {
|
|
91
|
-
if (skipIfExists && existsSync(downloadOptions.downloadPath)) {
|
|
92
|
-
debug('dataset %s already exists, skipping download', downloadOptions.downloadPath)
|
|
93
|
-
} else {
|
|
94
|
-
return await download(downloadOptions)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function getDownloadOptions({
|
|
99
|
-
apiKey,
|
|
100
|
-
exchange,
|
|
101
|
-
dataType,
|
|
102
|
-
date,
|
|
103
|
-
symbol,
|
|
104
|
-
format,
|
|
105
|
-
downloadDir,
|
|
106
|
-
getFilename
|
|
107
|
-
}: {
|
|
108
|
-
exchange: Exchange
|
|
109
|
-
dataType: DatasetType
|
|
110
|
-
symbol: string
|
|
111
|
-
date: Date
|
|
112
|
-
format: string
|
|
113
|
-
apiKey: string
|
|
114
|
-
downloadDir: string
|
|
115
|
-
getFilename: (options: GetFilenameOptions) => string
|
|
116
|
-
}): DownloadOptions {
|
|
117
|
-
const year = date.getUTCFullYear()
|
|
118
|
-
const month = doubleDigit(date.getUTCMonth() + 1)
|
|
119
|
-
const day = doubleDigit(date.getUTCDate())
|
|
120
|
-
|
|
121
|
-
const url = `${options.datasetsEndpoint}/${exchange}/${dataType}/${year}/${month}/${day}/${symbol}.${format}.gz`
|
|
122
|
-
const filename = getFilename({
|
|
123
|
-
dataType,
|
|
124
|
-
date,
|
|
125
|
-
exchange,
|
|
126
|
-
format,
|
|
127
|
-
symbol
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const downloadPath = `${downloadDir}/${filename}`
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
url,
|
|
134
|
-
downloadPath,
|
|
135
|
-
userAgent: options._userAgent,
|
|
136
|
-
apiKey
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
type DownloadOptions = Parameters<typeof download>[0]
|
|
141
|
-
|
|
142
|
-
function getFilenameDefault({ exchange, dataType, format, date, symbol }: GetFilenameOptions) {
|
|
143
|
-
return `${exchange}_${dataType}_${date.toISOString().split('T')[0]}_${symbol}.${format}.gz`
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function getDownloadDateRange({ from, to }: DownloadDatasetsOptions) {
|
|
147
|
-
if (!from || isNaN(Date.parse(from))) {
|
|
148
|
-
throw new Error(`Invalid "from" argument: ${from}. Please provide valid date string.`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (!to || isNaN(Date.parse(to))) {
|
|
152
|
-
throw new Error(`Invalid "to" argument: ${to}. Please provide valid date string.`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const toDate = parseAsUTCDate(to)
|
|
156
|
-
const fromDate = parseAsUTCDate(from)
|
|
157
|
-
const daysCountToFetch = Math.floor((toDate.getTime() - fromDate.getTime()) / MILLISECONDS_IN_SINGLE_DAY)
|
|
158
|
-
|
|
159
|
-
if (daysCountToFetch < 1) {
|
|
160
|
-
throw new Error(`Invalid "to" and "from" arguments combination. Please provide "to" day that is later than "from" day.`)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
startDate: fromDate,
|
|
165
|
-
daysCountToFetch
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
type GetFilenameOptions = {
|
|
170
|
-
exchange: Exchange
|
|
171
|
-
dataType: DatasetType
|
|
172
|
-
symbol: string
|
|
173
|
-
date: Date
|
|
174
|
-
format: string
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
type DownloadDatasetsOptions = {
|
|
178
|
-
exchange: Exchange
|
|
179
|
-
dataTypes: DatasetType[]
|
|
180
|
-
symbols: string[]
|
|
181
|
-
from: string
|
|
182
|
-
to: string
|
|
183
|
-
format?: 'csv'
|
|
184
|
-
apiKey?: string
|
|
185
|
-
downloadDir?: string
|
|
186
|
-
getFilename?: (options: GetFilenameOptions) => string
|
|
187
|
-
skipIfExists?: boolean
|
|
188
|
-
}
|
|
1
|
+
import { existsSync } from 'fs-extra'
|
|
2
|
+
import pMap from 'p-map'
|
|
3
|
+
import { debug } from './debug'
|
|
4
|
+
import { DatasetType } from './exchangedetails'
|
|
5
|
+
import { addDays, doubleDigit, download, parseAsUTCDate, sequence } from './handy'
|
|
6
|
+
import { getOptions } from './options'
|
|
7
|
+
import { Exchange } from './types'
|
|
8
|
+
|
|
9
|
+
const CONCURRENCY_LIMIT = 20
|
|
10
|
+
const MILLISECONDS_IN_SINGLE_DAY = 24 * 60 * 60 * 1000
|
|
11
|
+
const DEFAULT_DOWNLOAD_DIR = './datasets'
|
|
12
|
+
|
|
13
|
+
const options = getOptions()
|
|
14
|
+
|
|
15
|
+
export async function downloadDatasets(downloadDatasetsOptions: DownloadDatasetsOptions) {
|
|
16
|
+
const { exchange, dataTypes, from, to, symbols } = downloadDatasetsOptions
|
|
17
|
+
const apiKey = downloadDatasetsOptions.apiKey !== undefined ? downloadDatasetsOptions.apiKey : options.apiKey
|
|
18
|
+
const downloadDir = downloadDatasetsOptions.downloadDir !== undefined ? downloadDatasetsOptions.downloadDir : DEFAULT_DOWNLOAD_DIR
|
|
19
|
+
const format = downloadDatasetsOptions.format !== undefined ? downloadDatasetsOptions.format : 'csv'
|
|
20
|
+
const getFilename = downloadDatasetsOptions.getFilename !== undefined ? downloadDatasetsOptions.getFilename : getFilenameDefault
|
|
21
|
+
const skipIfExists = downloadDatasetsOptions.skipIfExists === undefined ? true : downloadDatasetsOptions.skipIfExists
|
|
22
|
+
|
|
23
|
+
// in case someone provided 'api/exchange' symbol, transform it to symbol that is accepted by datasets API
|
|
24
|
+
const datasetsSymbols = symbols.map((s) => s.replace(/\/|:/g, '-').toUpperCase())
|
|
25
|
+
|
|
26
|
+
for (const symbol of datasetsSymbols) {
|
|
27
|
+
for (const dataType of dataTypes) {
|
|
28
|
+
const { daysCountToFetch, startDate } = getDownloadDateRange(downloadDatasetsOptions)
|
|
29
|
+
const startTimestamp = new Date().valueOf()
|
|
30
|
+
debug('dataset download started for %s %s %s from %s to %s', exchange, dataType, symbol, from, to)
|
|
31
|
+
|
|
32
|
+
if (daysCountToFetch > 1) {
|
|
33
|
+
// start with downloading last day of the range, validates is API key has access to the end range of requested data
|
|
34
|
+
await downloadDataSet(
|
|
35
|
+
getDownloadOptions({
|
|
36
|
+
exchange,
|
|
37
|
+
symbol,
|
|
38
|
+
apiKey,
|
|
39
|
+
downloadDir,
|
|
40
|
+
dataType,
|
|
41
|
+
format,
|
|
42
|
+
getFilename,
|
|
43
|
+
date: addDays(startDate, daysCountToFetch - 1)
|
|
44
|
+
}),
|
|
45
|
+
skipIfExists
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// then download the first day of the range, validates is API key has access to the start range of requested data
|
|
50
|
+
await downloadDataSet(
|
|
51
|
+
getDownloadOptions({
|
|
52
|
+
exchange,
|
|
53
|
+
symbol,
|
|
54
|
+
apiKey,
|
|
55
|
+
downloadDir,
|
|
56
|
+
dataType,
|
|
57
|
+
format,
|
|
58
|
+
getFilename,
|
|
59
|
+
date: startDate
|
|
60
|
+
}),
|
|
61
|
+
skipIfExists
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// download the rest concurrently up to the CONCURRENCY_LIMIT
|
|
65
|
+
await pMap(
|
|
66
|
+
sequence(daysCountToFetch - 1, 1), // this will produce Iterable sequence from 1 to daysCountToFetch - 1 (as we already downloaded data for the first and last day)
|
|
67
|
+
(offset) =>
|
|
68
|
+
downloadDataSet(
|
|
69
|
+
getDownloadOptions({
|
|
70
|
+
exchange,
|
|
71
|
+
symbol,
|
|
72
|
+
apiKey,
|
|
73
|
+
downloadDir,
|
|
74
|
+
dataType,
|
|
75
|
+
format,
|
|
76
|
+
getFilename,
|
|
77
|
+
date: addDays(startDate, offset)
|
|
78
|
+
}),
|
|
79
|
+
skipIfExists
|
|
80
|
+
),
|
|
81
|
+
{ concurrency: CONCURRENCY_LIMIT }
|
|
82
|
+
)
|
|
83
|
+
const elapsedSeconds = (new Date().valueOf() - startTimestamp) / 1000
|
|
84
|
+
|
|
85
|
+
debug('dataset download finished for %s %s %s from %s to %s, time: %s seconds', exchange, dataType, symbol, from, to, elapsedSeconds)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function downloadDataSet(downloadOptions: DownloadOptions, skipIfExists: boolean) {
|
|
91
|
+
if (skipIfExists && existsSync(downloadOptions.downloadPath)) {
|
|
92
|
+
debug('dataset %s already exists, skipping download', downloadOptions.downloadPath)
|
|
93
|
+
} else {
|
|
94
|
+
return await download(downloadOptions)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getDownloadOptions({
|
|
99
|
+
apiKey,
|
|
100
|
+
exchange,
|
|
101
|
+
dataType,
|
|
102
|
+
date,
|
|
103
|
+
symbol,
|
|
104
|
+
format,
|
|
105
|
+
downloadDir,
|
|
106
|
+
getFilename
|
|
107
|
+
}: {
|
|
108
|
+
exchange: Exchange
|
|
109
|
+
dataType: DatasetType
|
|
110
|
+
symbol: string
|
|
111
|
+
date: Date
|
|
112
|
+
format: string
|
|
113
|
+
apiKey: string
|
|
114
|
+
downloadDir: string
|
|
115
|
+
getFilename: (options: GetFilenameOptions) => string
|
|
116
|
+
}): DownloadOptions {
|
|
117
|
+
const year = date.getUTCFullYear()
|
|
118
|
+
const month = doubleDigit(date.getUTCMonth() + 1)
|
|
119
|
+
const day = doubleDigit(date.getUTCDate())
|
|
120
|
+
|
|
121
|
+
const url = `${options.datasetsEndpoint}/${exchange}/${dataType}/${year}/${month}/${day}/${symbol}.${format}.gz`
|
|
122
|
+
const filename = getFilename({
|
|
123
|
+
dataType,
|
|
124
|
+
date,
|
|
125
|
+
exchange,
|
|
126
|
+
format,
|
|
127
|
+
symbol
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const downloadPath = `${downloadDir}/${filename}`
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
url,
|
|
134
|
+
downloadPath,
|
|
135
|
+
userAgent: options._userAgent,
|
|
136
|
+
apiKey
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type DownloadOptions = Parameters<typeof download>[0]
|
|
141
|
+
|
|
142
|
+
function getFilenameDefault({ exchange, dataType, format, date, symbol }: GetFilenameOptions) {
|
|
143
|
+
return `${exchange}_${dataType}_${date.toISOString().split('T')[0]}_${symbol}.${format}.gz`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getDownloadDateRange({ from, to }: DownloadDatasetsOptions) {
|
|
147
|
+
if (!from || isNaN(Date.parse(from))) {
|
|
148
|
+
throw new Error(`Invalid "from" argument: ${from}. Please provide valid date string.`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!to || isNaN(Date.parse(to))) {
|
|
152
|
+
throw new Error(`Invalid "to" argument: ${to}. Please provide valid date string.`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const toDate = parseAsUTCDate(to)
|
|
156
|
+
const fromDate = parseAsUTCDate(from)
|
|
157
|
+
const daysCountToFetch = Math.floor((toDate.getTime() - fromDate.getTime()) / MILLISECONDS_IN_SINGLE_DAY)
|
|
158
|
+
|
|
159
|
+
if (daysCountToFetch < 1) {
|
|
160
|
+
throw new Error(`Invalid "to" and "from" arguments combination. Please provide "to" day that is later than "from" day.`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
startDate: fromDate,
|
|
165
|
+
daysCountToFetch
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type GetFilenameOptions = {
|
|
170
|
+
exchange: Exchange
|
|
171
|
+
dataType: DatasetType
|
|
172
|
+
symbol: string
|
|
173
|
+
date: Date
|
|
174
|
+
format: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
type DownloadDatasetsOptions = {
|
|
178
|
+
exchange: Exchange
|
|
179
|
+
dataTypes: DatasetType[]
|
|
180
|
+
symbols: string[]
|
|
181
|
+
from: string
|
|
182
|
+
to: string
|
|
183
|
+
format?: 'csv'
|
|
184
|
+
apiKey?: string
|
|
185
|
+
downloadDir?: string
|
|
186
|
+
getFilename?: (options: GetFilenameOptions) => string
|
|
187
|
+
skipIfExists?: boolean
|
|
188
|
+
}
|
package/src/filter.ts
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { NormalizedData, Disconnect, Trade } from './types'
|
|
2
|
-
import { CappedSet } from './handy'
|
|
3
|
-
|
|
4
|
-
export async function* filter<T extends NormalizedData | Disconnect>(messages: AsyncIterableIterator<T>, filter: (message: T) => boolean) {
|
|
5
|
-
for await (const message of messages) {
|
|
6
|
-
if (filter(message)) {
|
|
7
|
-
yield message
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function uniqueTradesOnly<T extends NormalizedData | Disconnect>(
|
|
13
|
-
{
|
|
14
|
-
maxWindow,
|
|
15
|
-
onDuplicateFound,
|
|
16
|
-
skipStaleOlderThanSeconds
|
|
17
|
-
}: {
|
|
18
|
-
maxWindow: number
|
|
19
|
-
skipStaleOlderThanSeconds?: number
|
|
20
|
-
onDuplicateFound?: (trade: Trade) => void
|
|
21
|
-
} = {
|
|
22
|
-
maxWindow: 500
|
|
23
|
-
}
|
|
24
|
-
) {
|
|
25
|
-
const perSymbolQueues = {} as {
|
|
26
|
-
[key: string]: CappedSet<string>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (message: T) => {
|
|
30
|
-
// pass trough any message that is not a trade
|
|
31
|
-
if (message.type !== 'trade') {
|
|
32
|
-
return true
|
|
33
|
-
} else {
|
|
34
|
-
const trade =
|
|
35
|
-
// pass trough trades that can't be uniquely identified
|
|
36
|
-
// ignore index trades
|
|
37
|
-
if (trade.id === undefined || trade.symbol.startsWith('.')) {
|
|
38
|
-
return true
|
|
39
|
-
} else {
|
|
40
|
-
let alreadySeenTrades = perSymbolQueues[trade.symbol]
|
|
41
|
-
|
|
42
|
-
if (alreadySeenTrades === undefined) {
|
|
43
|
-
perSymbolQueues[trade.symbol] = new CappedSet<string>(maxWindow)
|
|
44
|
-
alreadySeenTrades = perSymbolQueues[trade.symbol]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const isDuplicate = alreadySeenTrades.has(trade.id)
|
|
48
|
-
const isStale =
|
|
49
|
-
skipStaleOlderThanSeconds !== undefined &&
|
|
50
|
-
trade.localTimestamp.valueOf() - trade.timestamp.valueOf() > skipStaleOlderThanSeconds * 1000
|
|
51
|
-
|
|
52
|
-
if (isDuplicate || isStale) {
|
|
53
|
-
if (onDuplicateFound !== undefined) {
|
|
54
|
-
onDuplicateFound(trade)
|
|
55
|
-
}
|
|
56
|
-
// refresh duplicated key position so it's added back at the beginning of the queue
|
|
57
|
-
alreadySeenTrades.remove(trade.id)
|
|
58
|
-
alreadySeenTrades.add(trade.id)
|
|
59
|
-
|
|
60
|
-
return false
|
|
61
|
-
} else {
|
|
62
|
-
alreadySeenTrades.add(trade.id)
|
|
63
|
-
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
import { NormalizedData, Disconnect, Trade } from './types'
|
|
2
|
+
import { CappedSet } from './handy'
|
|
3
|
+
|
|
4
|
+
export async function* filter<T extends NormalizedData | Disconnect>(messages: AsyncIterableIterator<T>, filter: (message: T) => boolean) {
|
|
5
|
+
for await (const message of messages) {
|
|
6
|
+
if (filter(message)) {
|
|
7
|
+
yield message
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function uniqueTradesOnly<T extends NormalizedData | Disconnect>(
|
|
13
|
+
{
|
|
14
|
+
maxWindow,
|
|
15
|
+
onDuplicateFound,
|
|
16
|
+
skipStaleOlderThanSeconds
|
|
17
|
+
}: {
|
|
18
|
+
maxWindow: number
|
|
19
|
+
skipStaleOlderThanSeconds?: number
|
|
20
|
+
onDuplicateFound?: (trade: Trade) => void
|
|
21
|
+
} = {
|
|
22
|
+
maxWindow: 500
|
|
23
|
+
}
|
|
24
|
+
) {
|
|
25
|
+
const perSymbolQueues = {} as {
|
|
26
|
+
[key: string]: CappedSet<string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (message: T) => {
|
|
30
|
+
// pass trough any message that is not a trade
|
|
31
|
+
if (message.type !== 'trade') {
|
|
32
|
+
return true
|
|
33
|
+
} else {
|
|
34
|
+
const trade = message as unknown as Trade
|
|
35
|
+
// pass trough trades that can't be uniquely identified
|
|
36
|
+
// ignore index trades
|
|
37
|
+
if (trade.id === undefined || trade.symbol.startsWith('.')) {
|
|
38
|
+
return true
|
|
39
|
+
} else {
|
|
40
|
+
let alreadySeenTrades = perSymbolQueues[trade.symbol]
|
|
41
|
+
|
|
42
|
+
if (alreadySeenTrades === undefined) {
|
|
43
|
+
perSymbolQueues[trade.symbol] = new CappedSet<string>(maxWindow)
|
|
44
|
+
alreadySeenTrades = perSymbolQueues[trade.symbol]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isDuplicate = alreadySeenTrades.has(trade.id)
|
|
48
|
+
const isStale =
|
|
49
|
+
skipStaleOlderThanSeconds !== undefined &&
|
|
50
|
+
trade.localTimestamp.valueOf() - trade.timestamp.valueOf() > skipStaleOlderThanSeconds * 1000
|
|
51
|
+
|
|
52
|
+
if (isDuplicate || isStale) {
|
|
53
|
+
if (onDuplicateFound !== undefined) {
|
|
54
|
+
onDuplicateFound(trade)
|
|
55
|
+
}
|
|
56
|
+
// refresh duplicated key position so it's added back at the beginning of the queue
|
|
57
|
+
alreadySeenTrades.remove(trade.id)
|
|
58
|
+
alreadySeenTrades.add(trade.id)
|
|
59
|
+
|
|
60
|
+
return false
|
|
61
|
+
} else {
|
|
62
|
+
alreadySeenTrades.add(trade.id)
|
|
63
|
+
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/handy.ts
CHANGED
|
@@ -253,7 +253,7 @@ export async function download({
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
const MAX_ATTEMPTS =
|
|
256
|
+
const MAX_ATTEMPTS = 30
|
|
257
257
|
let attempts = 0
|
|
258
258
|
|
|
259
259
|
while (true) {
|
|
@@ -270,7 +270,7 @@ export async function download({
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
const randomIngridient = Math.random() * 500
|
|
273
|
-
const attemptsDelayMS = Math.pow(2, attempts) * ONE_SEC_IN_MS
|
|
273
|
+
const attemptsDelayMS = Math.min(Math.pow(2, attempts) * ONE_SEC_IN_MS, 120 * ONE_SEC_IN_MS)
|
|
274
274
|
let nextAttemptDelayMS = randomIngridient + attemptsDelayMS
|
|
275
275
|
|
|
276
276
|
if (tooManyRequests) {
|
package/src/instrumentinfo.ts
CHANGED
|
@@ -34,7 +34,7 @@ async function getInstrumentInfoForExchange(exchange: Exchange, filterOrSymbol?:
|
|
|
34
34
|
headers: { Authorization: `Bearer ${options.apiKey}` }
|
|
35
35
|
})
|
|
36
36
|
.json()
|
|
37
|
-
} catch (e) {
|
|
37
|
+
} catch (e: any) {
|
|
38
38
|
// expose 400 error message from server
|
|
39
39
|
if (e.response?.statusCode === 400) {
|
|
40
40
|
let err: { code: Number; message: string }
|