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.
Files changed (100) hide show
  1. package/dist/apikeyaccessinfo.js +1 -1
  2. package/dist/apikeyaccessinfo.js.map +1 -1
  3. package/dist/clearcache.js +12 -12
  4. package/dist/clearcache.js.map +1 -1
  5. package/dist/combine.js +1 -1
  6. package/dist/combine.js.map +1 -1
  7. package/dist/computable/booksnapshot.d.ts.map +1 -1
  8. package/dist/computable/booksnapshot.js +1 -1
  9. package/dist/computable/booksnapshot.js.map +1 -1
  10. package/dist/computable/tradebar.d.ts.map +1 -1
  11. package/dist/computable/tradebar.js.map +1 -1
  12. package/dist/debug.js +1 -1
  13. package/dist/debug.js.map +1 -1
  14. package/dist/downloaddatasets.js +13 -13
  15. package/dist/downloaddatasets.js.map +1 -1
  16. package/dist/exchangedetails.js +1 -1
  17. package/dist/exchangedetails.js.map +1 -1
  18. package/dist/filter.js.map +1 -1
  19. package/dist/handy.js +10 -10
  20. package/dist/handy.js.map +1 -1
  21. package/dist/instrumentinfo.js +1 -1
  22. package/dist/instrumentinfo.js.map +1 -1
  23. package/dist/mappers/binance.d.ts.map +1 -1
  24. package/dist/mappers/binance.js +2 -2
  25. package/dist/mappers/binance.js.map +1 -1
  26. package/dist/mappers/binanceoptions.js +10 -10
  27. package/dist/mappers/binanceoptions.js.map +1 -1
  28. package/dist/mappers/bitflyer.js +1 -1
  29. package/dist/mappers/bitflyer.js.map +1 -1
  30. package/dist/mappers/bybit.d.ts +12 -1
  31. package/dist/mappers/bybit.d.ts.map +1 -1
  32. package/dist/mappers/bybit.js +21 -3
  33. package/dist/mappers/bybit.js.map +1 -1
  34. package/dist/mappers/coinbase.js +2 -2
  35. package/dist/mappers/coinbase.js.map +1 -1
  36. package/dist/mappers/dydx.d.ts +6 -14
  37. package/dist/mappers/dydx.d.ts.map +1 -1
  38. package/dist/mappers/dydx.js +1 -1
  39. package/dist/mappers/dydx.js.map +1 -1
  40. package/dist/mappers/ftx.js +2 -2
  41. package/dist/mappers/ftx.js.map +1 -1
  42. package/dist/mappers/huobi.d.ts.map +1 -1
  43. package/dist/mappers/huobi.js +11 -11
  44. package/dist/mappers/huobi.js.map +1 -1
  45. package/dist/mappers/okex.js +14 -14
  46. package/dist/mappers/okex.js.map +1 -1
  47. package/dist/mappers/phemex.d.ts.map +1 -1
  48. package/dist/mappers/phemex.js.map +1 -1
  49. package/dist/realtimefeeds/ascendex.js +2 -2
  50. package/dist/realtimefeeds/ascendex.js.map +1 -1
  51. package/dist/realtimefeeds/binance.js +1 -1
  52. package/dist/realtimefeeds/binance.js.map +1 -1
  53. package/dist/realtimefeeds/bitmex.js +1 -1
  54. package/dist/realtimefeeds/bitmex.js.map +1 -1
  55. package/dist/realtimefeeds/ftx.js +1 -1
  56. package/dist/realtimefeeds/ftx.js.map +1 -1
  57. package/dist/realtimefeeds/huobi.js +7 -7
  58. package/dist/realtimefeeds/huobi.js.map +1 -1
  59. package/dist/realtimefeeds/okex.js +1 -1
  60. package/dist/realtimefeeds/okex.js.map +1 -1
  61. package/dist/realtimefeeds/realtimefeed.js +9 -9
  62. package/dist/realtimefeeds/realtimefeed.js.map +1 -1
  63. package/dist/replay.d.ts.map +1 -1
  64. package/dist/replay.js +75 -40
  65. package/dist/replay.js.map +1 -1
  66. package/dist/stream.js +4 -4
  67. package/dist/stream.js.map +1 -1
  68. package/dist/worker.js +12 -12
  69. package/dist/worker.js.map +1 -1
  70. package/package.json +19 -18
  71. package/src/computable/booksnapshot.ts +4 -2
  72. package/src/computable/tradebar.ts +4 -1
  73. package/src/downloaddatasets.ts +188 -188
  74. package/src/filter.ts +69 -69
  75. package/src/handy.ts +2 -2
  76. package/src/instrumentinfo.ts +1 -1
  77. package/src/mappers/ascendex.ts +156 -156
  78. package/src/mappers/binance.ts +6 -3
  79. package/src/mappers/bybit.ts +28 -4
  80. package/src/mappers/coinflex.ts +159 -159
  81. package/src/mappers/delta.ts +175 -175
  82. package/src/mappers/dydx.ts +303 -306
  83. package/src/mappers/gateio.ts +117 -117
  84. package/src/mappers/gateiofutures.ts +185 -185
  85. package/src/mappers/huobi.ts +4 -2
  86. package/src/mappers/phemex.ts +179 -177
  87. package/src/mappers/poloniex.ts +150 -150
  88. package/src/mappers/serum.ts +103 -103
  89. package/src/mappers/upbit.ts +104 -104
  90. package/src/realtimefeeds/ascendex.ts +65 -65
  91. package/src/realtimefeeds/coinflex.ts +29 -29
  92. package/src/realtimefeeds/delta.ts +27 -27
  93. package/src/realtimefeeds/dydx.ts +40 -40
  94. package/src/realtimefeeds/gateio.ts +41 -41
  95. package/src/realtimefeeds/gateiofutures.ts +90 -90
  96. package/src/realtimefeeds/poloniex.ts +28 -28
  97. package/src/realtimefeeds/realtimefeed.ts +2 -2
  98. package/src/realtimefeeds/upbit.ts +35 -35
  99. package/src/replay.ts +61 -18
  100. package/src/stream.ts +1 -1
@@ -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 = (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
- }
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 = 8
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) {
@@ -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 }