@deeptick/client 0.1.1 → 0.2.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/README.md +12 -218
- package/dist/index.d.ts +58 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +250 -20
- package/dist/index.js.map +1 -1
- package/package.json +15 -29
- package/dist/cache.d.ts +0 -48
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -140
- package/dist/cache.js.map +0 -1
- package/dist/feed.d.ts +0 -89
- package/dist/feed.d.ts.map +0 -1
- package/dist/feed.js +0 -232
- package/dist/feed.js.map +0 -1
- package/dist/streaming.d.ts +0 -179
- package/dist/streaming.d.ts.map +0 -1
- package/dist/streaming.js +0 -318
- package/dist/streaming.js.map +0 -1
- package/dist/types.d.ts +0 -142
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -152
- package/dist/types.js.map +0 -1
- package/src/cache.ts +0 -206
- package/src/feed.ts +0 -290
- package/src/index.ts +0 -45
- package/src/streaming.ts +0 -468
- package/src/types.ts +0 -246
package/dist/types.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepTick TypeScript Client — Derived Data Computations
|
|
3
|
-
*
|
|
4
|
-
* All derived data types are computed client-side from raw Phase 1 data.
|
|
5
|
-
* Uses Apache Arrow JS for zero-copy Parquet reads.
|
|
6
|
-
*
|
|
7
|
-
* Install: npm install @deeptick/client apache-arrow parquet-wasm
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* import { DeepTick, derive } from '@deeptick/client';
|
|
11
|
-
*
|
|
12
|
-
* const dt = new DeepTick({ dataDir: './data/merged' });
|
|
13
|
-
* const trades = await dt.loadTrades('hyperliquid', 'BTC', '2026-05-01');
|
|
14
|
-
* const candles = derive.candles(trades, { intervalMs: 60_000 });
|
|
15
|
-
* const bbo = derive.bbo(await dt.loadBookDeltas('hyperliquid', 'BTC', '2026-05-01'));
|
|
16
|
-
*/
|
|
17
|
-
// ─── Derived Data Functions ─────────────────────────────────────────
|
|
18
|
-
// All functions are pure: raw data in → derived data out. Nothing stored.
|
|
19
|
-
export var derive;
|
|
20
|
-
(function (derive) {
|
|
21
|
-
/**
|
|
22
|
-
* Derive BBO time series from L2 book deltas.
|
|
23
|
-
* Replays delta stream maintaining full book state.
|
|
24
|
-
*/
|
|
25
|
-
function bbo(deltas) {
|
|
26
|
-
const bids = new Map();
|
|
27
|
-
const asks = new Map();
|
|
28
|
-
const result = [];
|
|
29
|
-
for (const d of deltas) {
|
|
30
|
-
const bidsRaw = unpackLevels(d.bids_packed);
|
|
31
|
-
const asksRaw = unpackLevels(d.asks_packed);
|
|
32
|
-
if (d.is_snapshot) {
|
|
33
|
-
bids.clear();
|
|
34
|
-
asks.clear();
|
|
35
|
-
for (const [p, s] of bidsRaw) {
|
|
36
|
-
if (s > 0)
|
|
37
|
-
bids.set(p, s);
|
|
38
|
-
}
|
|
39
|
-
for (const [p, s] of asksRaw) {
|
|
40
|
-
if (s > 0)
|
|
41
|
-
asks.set(p, s);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
for (const [p, s] of bidsRaw) {
|
|
46
|
-
if (s > 0) {
|
|
47
|
-
bids.set(p, s);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
bids.delete(p);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
for (const [p, s] of asksRaw) {
|
|
54
|
-
if (s > 0) {
|
|
55
|
-
asks.set(p, s);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
asks.delete(p);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (bids.size > 0 && asks.size > 0) {
|
|
63
|
-
let bestBidPx = -Infinity;
|
|
64
|
-
let bestBidSz = 0;
|
|
65
|
-
for (const [p, s] of bids.entries()) {
|
|
66
|
-
if (p > bestBidPx) {
|
|
67
|
-
bestBidPx = p;
|
|
68
|
-
bestBidSz = s;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
let bestAskPx = Infinity;
|
|
72
|
-
let bestAskSz = 0;
|
|
73
|
-
for (const [p, s] of asks.entries()) {
|
|
74
|
-
if (p < bestAskPx) {
|
|
75
|
-
bestAskPx = p;
|
|
76
|
-
bestAskSz = s;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (bestBidPx !== -Infinity && bestAskPx !== Infinity) {
|
|
80
|
-
result.push({
|
|
81
|
-
timestamp: d.timestamp,
|
|
82
|
-
local_timestamp: d.local_timestamp,
|
|
83
|
-
bid_price: bestBidPx,
|
|
84
|
-
bid_amount: bestBidSz,
|
|
85
|
-
ask_price: bestAskPx,
|
|
86
|
-
ask_amount: bestAskSz,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
derive.bbo = bbo;
|
|
94
|
-
/**
|
|
95
|
-
* Derive periodic L2 snapshots from delta stream.
|
|
96
|
-
* @param depth - 20 for standard, 1000 for deep book
|
|
97
|
-
* @param intervalMs - snapshot interval (default: 60000 = 1 min)
|
|
98
|
-
*/
|
|
99
|
-
function bookSnapshots(deltas, options) {
|
|
100
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
101
|
-
}
|
|
102
|
-
derive.bookSnapshots = bookSnapshots;
|
|
103
|
-
/**
|
|
104
|
-
* Derive OHLCV candles from trades at any interval.
|
|
105
|
-
* Includes VWAP and buy/sell volume split.
|
|
106
|
-
*/
|
|
107
|
-
function candles(trades, options) {
|
|
108
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
109
|
-
}
|
|
110
|
-
derive.candles = candles;
|
|
111
|
-
/**
|
|
112
|
-
* Derive volume profile from trades.
|
|
113
|
-
* @param priceBucketSize - auto-calculated if not provided
|
|
114
|
-
*/
|
|
115
|
-
function volumeProfile(trades, options) {
|
|
116
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
117
|
-
}
|
|
118
|
-
derive.volumeProfile = volumeProfile;
|
|
119
|
-
/**
|
|
120
|
-
* Derive order book imbalance from L2 deltas.
|
|
121
|
-
* imbalance = (bid_vol - ask_vol) / (bid_vol + ask_vol) at top N levels
|
|
122
|
-
*/
|
|
123
|
-
function bookImbalance(deltas, options) {
|
|
124
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
125
|
-
}
|
|
126
|
-
derive.bookImbalance = bookImbalance;
|
|
127
|
-
/**
|
|
128
|
-
* Derive cross-exchange funding rate spreads.
|
|
129
|
-
* Takes funding data from multiple exchanges for same underlying.
|
|
130
|
-
*/
|
|
131
|
-
function fundingCross(fundingByExchange, options) {
|
|
132
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
133
|
-
}
|
|
134
|
-
derive.fundingCross = fundingCross;
|
|
135
|
-
/**
|
|
136
|
-
* Derive cross-exchange basis (price spread in bps).
|
|
137
|
-
*/
|
|
138
|
-
function crossExchangeBasis(tradesByExchange, options) {
|
|
139
|
-
throw new Error("Not implemented yet. Feel free to contribute!");
|
|
140
|
-
}
|
|
141
|
-
derive.crossExchangeBasis = crossExchangeBasis;
|
|
142
|
-
})(derive || (derive = {}));
|
|
143
|
-
// ─── Utility: Unpack binary-packed book levels ──────────────────────
|
|
144
|
-
export function unpackLevels(packed) {
|
|
145
|
-
const view = new DataView(packed.buffer, packed.byteOffset, packed.byteLength);
|
|
146
|
-
const pairs = [];
|
|
147
|
-
for (let i = 0; i < packed.byteLength; i += 16) {
|
|
148
|
-
pairs.push([view.getFloat64(i, true), view.getFloat64(i + 8, true)]);
|
|
149
|
-
}
|
|
150
|
-
return pairs;
|
|
151
|
-
}
|
|
152
|
-
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/src/cache.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepTick TypeScript Client — Caching + Parallel Downloads.
|
|
3
|
-
*
|
|
4
|
-
* Disk-based cache for downloaded data so repeat queries are instant.
|
|
5
|
-
* Parallel multi-day fetches via Promise.all.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import { CachedClient } from '@deeptick/client';
|
|
9
|
-
*
|
|
10
|
-
* const client = new CachedClient('https://deeptick.lacertalabs.xyz');
|
|
11
|
-
*
|
|
12
|
-
* // First call: downloads from server, caches locally
|
|
13
|
-
* const trades = await client.loadTrades('hyperliquid', 'BTC', '2026-05-01', '2026-05-07');
|
|
14
|
-
*
|
|
15
|
-
* // Second call: instant from disk cache
|
|
16
|
-
* const trades2 = await client.loadTrades('hyperliquid', 'BTC', '2026-05-01', '2026-05-07');
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import * as fs from 'fs';
|
|
20
|
-
import * as path from 'path';
|
|
21
|
-
import * as os from 'os';
|
|
22
|
-
import * as crypto from 'crypto';
|
|
23
|
-
|
|
24
|
-
export interface CacheStats {
|
|
25
|
-
cacheDir: string;
|
|
26
|
-
cacheHits: number;
|
|
27
|
-
cacheMisses: number;
|
|
28
|
-
hitRatePct: number;
|
|
29
|
-
bytesDownloaded: number;
|
|
30
|
-
bytesFromCache: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class CachedClient {
|
|
34
|
-
private baseUrl: string;
|
|
35
|
-
private cacheDir: string;
|
|
36
|
-
private maxConcurrency: number;
|
|
37
|
-
private apiKey: string;
|
|
38
|
-
|
|
39
|
-
cacheHits = 0;
|
|
40
|
-
cacheMisses = 0;
|
|
41
|
-
bytesDownloaded = 0;
|
|
42
|
-
bytesFromCache = 0;
|
|
43
|
-
|
|
44
|
-
constructor(
|
|
45
|
-
baseUrl: string = 'https://deeptick.lacertalabs.xyz',
|
|
46
|
-
cacheDir?: string,
|
|
47
|
-
maxConcurrency: number = 8,
|
|
48
|
-
apiKey: string = process.env.DEEPTICK_API_KEY ?? '',
|
|
49
|
-
) {
|
|
50
|
-
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
51
|
-
this.cacheDir = cacheDir || path.join(os.homedir(), '.deeptick', 'cache');
|
|
52
|
-
this.maxConcurrency = maxConcurrency;
|
|
53
|
-
this.apiKey = apiKey;
|
|
54
|
-
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ─── Cache Key ────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
private cachePath(
|
|
60
|
-
exchange: string, dataType: string, symbol: string, date: string,
|
|
61
|
-
columns?: string[],
|
|
62
|
-
): string {
|
|
63
|
-
const safeSymbol = symbol.replace(/\//g, '-').replace(/:/g, '-');
|
|
64
|
-
let colHash = '';
|
|
65
|
-
if (columns && columns.length > 0) {
|
|
66
|
-
const sorted = [...columns].sort().join(',');
|
|
67
|
-
colHash = '_' + crypto.createHash('md5').update(sorted).digest('hex').slice(0, 8);
|
|
68
|
-
}
|
|
69
|
-
return path.join(
|
|
70
|
-
this.cacheDir, exchange, dataType, safeSymbol,
|
|
71
|
-
`${date}${colHash}.bin`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private isCached(filePath: string): boolean {
|
|
76
|
-
return fs.existsSync(filePath);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─── Download ─────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
private async downloadDate(
|
|
82
|
-
exchange: string, dataType: string, symbol: string, date: string,
|
|
83
|
-
columns?: string[], format: string = 'csv',
|
|
84
|
-
): Promise<string | null> {
|
|
85
|
-
const cp = this.cachePath(exchange, dataType, symbol, date, columns);
|
|
86
|
-
|
|
87
|
-
if (this.isCached(cp)) {
|
|
88
|
-
this.cacheHits++;
|
|
89
|
-
this.bytesFromCache += fs.statSync(cp).size;
|
|
90
|
-
return fs.readFileSync(cp, 'utf-8');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.cacheMisses++;
|
|
94
|
-
|
|
95
|
-
let url = `${this.baseUrl}/v1/data/${exchange}/${dataType}/${symbol}/${date}?format=${format}`;
|
|
96
|
-
if (columns && columns.length > 0) {
|
|
97
|
-
url += `&columns=${columns.join(',')}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const headers: Record<string, string> = {};
|
|
102
|
-
if (this.apiKey) {
|
|
103
|
-
headers['X-API-Key'] = this.apiKey;
|
|
104
|
-
}
|
|
105
|
-
const resp = await fetch(url, { headers });
|
|
106
|
-
if (!resp.ok) {
|
|
107
|
-
if (resp.status === 404) return null;
|
|
108
|
-
throw new Error(`HTTP ${resp.status}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const data = await resp.text();
|
|
112
|
-
this.bytesDownloaded += Buffer.byteLength(data);
|
|
113
|
-
|
|
114
|
-
// Cache to disk
|
|
115
|
-
const dir = path.dirname(cp);
|
|
116
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
117
|
-
fs.writeFileSync(cp, data);
|
|
118
|
-
|
|
119
|
-
return data;
|
|
120
|
-
} catch (err) {
|
|
121
|
-
console.error(`[cache] Download error for ${exchange}/${dataType}/${symbol}/${date}:`, err);
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ─── Parallel Multi-Day Loader ────────────────────────────────────
|
|
127
|
-
|
|
128
|
-
private dateRange(start: string, end: string): string[] {
|
|
129
|
-
const dates: string[] = [];
|
|
130
|
-
const current = new Date(start + 'T00:00:00Z');
|
|
131
|
-
const endDate = new Date(end + 'T00:00:00Z');
|
|
132
|
-
|
|
133
|
-
while (current <= endDate) {
|
|
134
|
-
dates.push(current.toISOString().split('T')[0]);
|
|
135
|
-
current.setUTCDate(current.getUTCDate() + 1);
|
|
136
|
-
}
|
|
137
|
-
return dates;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async load(
|
|
141
|
-
dataType: string,
|
|
142
|
-
exchange: string,
|
|
143
|
-
symbol: string,
|
|
144
|
-
startDate: string,
|
|
145
|
-
endDate?: string,
|
|
146
|
-
columns?: string[],
|
|
147
|
-
): Promise<string[]> {
|
|
148
|
-
const end = endDate || startDate;
|
|
149
|
-
const dates = this.dateRange(startDate, end);
|
|
150
|
-
|
|
151
|
-
// Parallel download with concurrency limit
|
|
152
|
-
const results: (string | null)[] = [];
|
|
153
|
-
for (let i = 0; i < dates.length; i += this.maxConcurrency) {
|
|
154
|
-
const batch = dates.slice(i, i + this.maxConcurrency);
|
|
155
|
-
const batchResults = await Promise.all(
|
|
156
|
-
batch.map(d => this.downloadDate(exchange, dataType, symbol, d, columns)),
|
|
157
|
-
);
|
|
158
|
-
results.push(...batchResults);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return results.filter((r): r is string => r !== null);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async loadTrades(
|
|
165
|
-
exchange: string, symbol: string, startDate: string, endDate?: string,
|
|
166
|
-
): Promise<string[]> {
|
|
167
|
-
return this.load('trades', exchange, symbol, startDate, endDate);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async loadBookDeltas(
|
|
171
|
-
exchange: string, symbol: string, startDate: string, endDate?: string,
|
|
172
|
-
): Promise<string[]> {
|
|
173
|
-
return this.load('book_l2_delta', exchange, symbol, startDate, endDate);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async loadDerivativeTicker(
|
|
177
|
-
exchange: string, symbol: string, startDate: string, endDate?: string,
|
|
178
|
-
): Promise<string[]> {
|
|
179
|
-
return this.load('derivative_ticker', exchange, symbol, startDate, endDate);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async loadFunding(
|
|
183
|
-
exchange: string, symbol: string, startDate: string, endDate?: string,
|
|
184
|
-
): Promise<string[]> {
|
|
185
|
-
return this.loadDerivativeTicker(exchange, symbol, startDate, endDate);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ─── Cache Management ────────────────────────────────────────────
|
|
189
|
-
|
|
190
|
-
getStats(): CacheStats {
|
|
191
|
-
const total = this.cacheHits + this.cacheMisses;
|
|
192
|
-
return {
|
|
193
|
-
cacheDir: this.cacheDir,
|
|
194
|
-
cacheHits: this.cacheHits,
|
|
195
|
-
cacheMisses: this.cacheMisses,
|
|
196
|
-
hitRatePct: total > 0 ? Math.round((this.cacheHits / total) * 1000) / 10 : 0,
|
|
197
|
-
bytesDownloaded: this.bytesDownloaded,
|
|
198
|
-
bytesFromCache: this.bytesFromCache,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
clearCache(): void {
|
|
203
|
-
fs.rmSync(this.cacheDir, { recursive: true, force: true });
|
|
204
|
-
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
205
|
-
}
|
|
206
|
-
}
|
package/src/feed.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepTick TypeScript Unified Feed — Seamless Live ↔ Replay switching.
|
|
3
|
-
*
|
|
4
|
-
* Same interface for both live streaming and historical replay.
|
|
5
|
-
* Backtest code = production code.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import { DeepTickFeed, FeedMode } from '@deeptick/client';
|
|
9
|
-
*
|
|
10
|
-
* // Live mode
|
|
11
|
-
* const feed = DeepTickFeed.live('wss://deeptick.lacertalabs.xyz/v1/stream');
|
|
12
|
-
* feed.onData((topic, data) => console.log(topic, data));
|
|
13
|
-
* await feed.connect();
|
|
14
|
-
* await feed.subscribe(['hyperliquid.trades.BTC']);
|
|
15
|
-
*
|
|
16
|
-
* // Replay mode — same callback, historical data
|
|
17
|
-
* const feed = DeepTickFeed.replay('wss://deeptick.lacertalabs.xyz/v1/replay/ws', {
|
|
18
|
-
* sources: ['hyperliquid:trades:BTC'],
|
|
19
|
-
* from: '2026-05-01',
|
|
20
|
-
* to: '2026-05-03',
|
|
21
|
-
* speed: 10,
|
|
22
|
-
* });
|
|
23
|
-
* feed.onData((topic, data) => console.log(topic, data)); // Same handler!
|
|
24
|
-
* await feed.connect();
|
|
25
|
-
*
|
|
26
|
-
* // Auto mode — replay then switch to live
|
|
27
|
-
* const feed = DeepTickFeed.auto('wss://deeptick.lacertalabs.xyz', {
|
|
28
|
-
* sources: ['hyperliquid:trades:BTC'],
|
|
29
|
-
* from: '2026-05-01',
|
|
30
|
-
* });
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import WebSocket from 'ws';
|
|
34
|
-
|
|
35
|
-
export enum FeedMode {
|
|
36
|
-
LIVE = 'live',
|
|
37
|
-
REPLAY = 'replay',
|
|
38
|
-
AUTO = 'auto',
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface FeedOptions {
|
|
42
|
-
channels?: string[];
|
|
43
|
-
sources?: string[];
|
|
44
|
-
from?: string;
|
|
45
|
-
to?: string;
|
|
46
|
-
speed?: number;
|
|
47
|
-
reconnect?: boolean;
|
|
48
|
-
apiKey?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface FeedStats {
|
|
52
|
-
mode: FeedMode;
|
|
53
|
-
currentMode: FeedMode;
|
|
54
|
-
messagesReceived: number;
|
|
55
|
-
connected: boolean;
|
|
56
|
-
url: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type DataCallback = (topic: string, data: Record<string, any>) => void;
|
|
60
|
-
type ReplayEndCallback = (stats: { totalEvents: number; durationS: number }) => void;
|
|
61
|
-
type ModeChangeCallback = (from: FeedMode, to: FeedMode) => void;
|
|
62
|
-
|
|
63
|
-
export class DeepTickFeed {
|
|
64
|
-
private mode: FeedMode;
|
|
65
|
-
private currentMode: FeedMode;
|
|
66
|
-
private url: string;
|
|
67
|
-
private options: FeedOptions;
|
|
68
|
-
private ws: WebSocket | null = null;
|
|
69
|
-
private messagesReceived = 0;
|
|
70
|
-
private connected = false;
|
|
71
|
-
|
|
72
|
-
private dataCallbacks: DataCallback[] = [];
|
|
73
|
-
private replayEndCallbacks: ReplayEndCallback[] = [];
|
|
74
|
-
private modeChangeCallbacks: ModeChangeCallback[] = [];
|
|
75
|
-
|
|
76
|
-
private constructor(mode: FeedMode, url: string, options: FeedOptions = {}) {
|
|
77
|
-
this.mode = mode;
|
|
78
|
-
this.currentMode = mode;
|
|
79
|
-
this.url = url;
|
|
80
|
-
this.options = options;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ─── Factory Methods ─────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
static live(url: string = 'wss://deeptick.lacertalabs.xyz/v1/stream', options: FeedOptions = {}): DeepTickFeed {
|
|
86
|
-
return new DeepTickFeed(FeedMode.LIVE, url, options);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
static replay(url: string = 'wss://deeptick.lacertalabs.xyz/v1/replay/ws', options: FeedOptions = {}): DeepTickFeed {
|
|
90
|
-
return new DeepTickFeed(FeedMode.REPLAY, url, options);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
static auto(baseUrl: string = 'wss://deeptick.lacertalabs.xyz', options: FeedOptions = {}): DeepTickFeed {
|
|
94
|
-
const today = new Date().toISOString().split('T')[0];
|
|
95
|
-
return new DeepTickFeed(FeedMode.AUTO, `${baseUrl}/v1/replay/ws`, {
|
|
96
|
-
...options,
|
|
97
|
-
to: today,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ─── Callbacks ───────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
/** Register a callback for data events (works identically for live and replay) */
|
|
104
|
-
onData(cb: DataCallback): this {
|
|
105
|
-
this.dataCallbacks.push(cb);
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Register a callback for replay completion */
|
|
110
|
-
onReplayEnd(cb: ReplayEndCallback): this {
|
|
111
|
-
this.replayEndCallbacks.push(cb);
|
|
112
|
-
return this;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Register a callback for mode changes (replay → live in auto mode) */
|
|
116
|
-
onModeChange(cb: ModeChangeCallback): this {
|
|
117
|
-
this.modeChangeCallbacks.push(cb);
|
|
118
|
-
return this;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ─── Connection ──────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
async connect(): Promise<void> {
|
|
124
|
-
return new Promise((resolve, reject) => {
|
|
125
|
-
const urlWithKey = this.options.apiKey
|
|
126
|
-
? `${this.url}?api_key=${this.options.apiKey}`
|
|
127
|
-
: this.url;
|
|
128
|
-
|
|
129
|
-
this.ws = new WebSocket(urlWithKey);
|
|
130
|
-
|
|
131
|
-
this.ws.on('open', () => {
|
|
132
|
-
this.connected = true;
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
this.ws.on('message', (raw: WebSocket.Data) => {
|
|
136
|
-
const frame = JSON.parse(raw.toString());
|
|
137
|
-
this.messagesReceived++;
|
|
138
|
-
|
|
139
|
-
switch (frame.type) {
|
|
140
|
-
case 'welcome':
|
|
141
|
-
// Start feed based on mode
|
|
142
|
-
if (this.mode === FeedMode.LIVE) {
|
|
143
|
-
this.startLive();
|
|
144
|
-
} else {
|
|
145
|
-
this.startReplay();
|
|
146
|
-
}
|
|
147
|
-
resolve();
|
|
148
|
-
break;
|
|
149
|
-
|
|
150
|
-
case 'data':
|
|
151
|
-
for (const cb of this.dataCallbacks) {
|
|
152
|
-
cb(frame.topic, frame.data);
|
|
153
|
-
}
|
|
154
|
-
break;
|
|
155
|
-
|
|
156
|
-
case 'replay_end':
|
|
157
|
-
for (const cb of this.replayEndCallbacks) {
|
|
158
|
-
cb({
|
|
159
|
-
totalEvents: frame.total_events,
|
|
160
|
-
durationS: frame.duration_s,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (this.currentMode === FeedMode.AUTO) {
|
|
165
|
-
// Switch to live
|
|
166
|
-
this.switchToLive();
|
|
167
|
-
}
|
|
168
|
-
break;
|
|
169
|
-
|
|
170
|
-
case 'subscribed':
|
|
171
|
-
case 'replay_start':
|
|
172
|
-
case 'heartbeat':
|
|
173
|
-
break;
|
|
174
|
-
|
|
175
|
-
case 'error':
|
|
176
|
-
console.error('[feed] Server error:', frame.message);
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
this.ws.on('close', () => {
|
|
182
|
-
this.connected = false;
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
this.ws.on('error', (err) => {
|
|
186
|
-
if (!this.connected) {
|
|
187
|
-
reject(err);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async disconnect(): Promise<void> {
|
|
194
|
-
this.connected = false;
|
|
195
|
-
if (this.ws) {
|
|
196
|
-
this.ws.close();
|
|
197
|
-
this.ws = null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async subscribe(channels: string[]): Promise<void> {
|
|
202
|
-
this.options.channels = channels;
|
|
203
|
-
if (this.connected && this.currentMode === FeedMode.LIVE && this.ws) {
|
|
204
|
-
this.ws.send(JSON.stringify({ action: 'subscribe', channels }));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// ─── Internal ────────────────────────────────────────────────────
|
|
209
|
-
|
|
210
|
-
private startLive(): void {
|
|
211
|
-
if (this.options.channels && this.ws) {
|
|
212
|
-
this.ws.send(JSON.stringify({
|
|
213
|
-
action: 'subscribe',
|
|
214
|
-
channels: this.options.channels,
|
|
215
|
-
}));
|
|
216
|
-
}
|
|
217
|
-
this.currentMode = FeedMode.LIVE;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private startReplay(): void {
|
|
221
|
-
if (this.ws) {
|
|
222
|
-
this.ws.send(JSON.stringify({
|
|
223
|
-
action: 'replay',
|
|
224
|
-
sources: this.options.sources || [],
|
|
225
|
-
from: this.options.from || '',
|
|
226
|
-
to: this.options.to || '',
|
|
227
|
-
speed: this.options.speed || 0,
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
this.currentMode = FeedMode.REPLAY;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private async switchToLive(): Promise<void> {
|
|
234
|
-
const oldMode = this.currentMode;
|
|
235
|
-
|
|
236
|
-
// Close replay connection
|
|
237
|
-
if (this.ws) {
|
|
238
|
-
this.ws.close();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Connect to live endpoint
|
|
242
|
-
const liveUrl = this.url.replace('/v1/replay/ws', '/v1/stream');
|
|
243
|
-
const urlWithKey = this.options.apiKey
|
|
244
|
-
? `${liveUrl}?api_key=${this.options.apiKey}`
|
|
245
|
-
: liveUrl;
|
|
246
|
-
|
|
247
|
-
return new Promise((resolve) => {
|
|
248
|
-
this.ws = new WebSocket(urlWithKey);
|
|
249
|
-
|
|
250
|
-
this.ws.on('open', () => {
|
|
251
|
-
this.connected = true;
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
this.ws.on('message', (raw: WebSocket.Data) => {
|
|
255
|
-
const frame = JSON.parse(raw.toString());
|
|
256
|
-
this.messagesReceived++;
|
|
257
|
-
|
|
258
|
-
if (frame.type === 'welcome') {
|
|
259
|
-
// Build channels from sources
|
|
260
|
-
if (!this.options.channels && this.options.sources) {
|
|
261
|
-
this.options.channels = this.options.sources.map(s => s.replace(/:/g, '.'));
|
|
262
|
-
}
|
|
263
|
-
this.startLive();
|
|
264
|
-
for (const cb of this.modeChangeCallbacks) {
|
|
265
|
-
cb(oldMode, FeedMode.LIVE);
|
|
266
|
-
}
|
|
267
|
-
resolve();
|
|
268
|
-
} else if (frame.type === 'data') {
|
|
269
|
-
for (const cb of this.dataCallbacks) {
|
|
270
|
-
cb(frame.topic, frame.data);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
this.ws.on('close', () => {
|
|
276
|
-
this.connected = false;
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
getStats(): FeedStats {
|
|
282
|
-
return {
|
|
283
|
-
mode: this.mode,
|
|
284
|
-
currentMode: this.currentMode,
|
|
285
|
-
messagesReceived: this.messagesReceived,
|
|
286
|
-
connected: this.connected,
|
|
287
|
-
url: this.url,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deeptick/client — TypeScript SDK for DeepTick market data
|
|
3
|
-
*
|
|
4
|
-
* Three layers:
|
|
5
|
-
* 1. CachedClient — Download + cache daily archives
|
|
6
|
-
* 2. DeepTickStream — Real-time WebSocket streaming
|
|
7
|
-
* 3. DeepTickFeed — Unified live/replay feed
|
|
8
|
-
* 4. derive — Client-side derived computations
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* import { DeepTickStream } from '@deeptick/client';
|
|
12
|
-
* import { CachedClient } from '@deeptick/client/cache';
|
|
13
|
-
* import { DeepTickFeed } from '@deeptick/client/feed';
|
|
14
|
-
* import { derive } from '@deeptick/client/types';
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// Re-export everything
|
|
18
|
-
export { DeepTickStream } from './streaming.js';
|
|
19
|
-
export type {
|
|
20
|
-
StreamFrame,
|
|
21
|
-
StreamTrade,
|
|
22
|
-
StreamBookTicker,
|
|
23
|
-
StreamBookDelta,
|
|
24
|
-
StreamDerivativeTicker,
|
|
25
|
-
DataCallback,
|
|
26
|
-
DeepTickStreamOptions,
|
|
27
|
-
} from './streaming.js';
|
|
28
|
-
|
|
29
|
-
export { DeepTickFeed, FeedMode } from './feed.js';
|
|
30
|
-
export type { FeedOptions, FeedStats } from './feed.js';
|
|
31
|
-
|
|
32
|
-
export { CachedClient } from './cache.js';
|
|
33
|
-
export type { CacheStats } from './cache.js';
|
|
34
|
-
|
|
35
|
-
export { derive, unpackLevels } from './types.js';
|
|
36
|
-
export type {
|
|
37
|
-
TradeRecord,
|
|
38
|
-
BookDeltaRecord,
|
|
39
|
-
BBORecord,
|
|
40
|
-
CandleRecord,
|
|
41
|
-
BookSnapshot,
|
|
42
|
-
VolumeProfileBucket,
|
|
43
|
-
ImbalanceRecord,
|
|
44
|
-
FundingCrossRecord,
|
|
45
|
-
} from './types.js';
|