@ckirg/corelib-markets 0.1.22

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Costas Kirgoussios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # @ckirg/corelib-markets
2
+
3
+ Financial market utilities and data providers for the Corelib monorepo, featuring high-resilience wrappers for Nasdaq and real-time streaming via Alpaca, Finnhub, and Yahoo Finance.
4
+
5
+ > [!IMPORTANT]
6
+ > Not on the public npm registry — install from GitHub Releases. See the [root install guide](../README.md#-installation-for-external-projects).
7
+
8
+ ## Features
9
+
10
+ - **Nasdaq API Wrapper**: Resilient requests with custom headers and error handling.
11
+ - **Market Status & Scheduling**: Intelligent pollers and sleep-calculators based on market phases.
12
+ - **Market Monitor**: Adaptive poller with heuristic fallback during API failures.
13
+ - **CNN Fear & Greed Index**: Retrieval and filtering of the popular sentiment indicator.
14
+ - **Nasdaq 100 Symbols**: Fast, cached access to the Nasdaq 100 constituent symbols.
15
+ - **Real-Time Streaming**: Zero event-loop-blocking live feeds via Alpaca, Finnhub, and Yahoo Finance — the Rust WebSocket engine delivers data to JS via napi `ThreadsafeFunction`.
16
+ - **Market Symbols**: Persistent symbol database with auto-refresh and environment-aware search sequencing.
17
+
18
+ ## Installation
19
+
20
+ Install from the GitHub Release — see the [root install guide](../README.md#-installation-for-external-projects) for package manager overrides (required because `@ckirg/corelib` is a peer dependency also not on npm).
21
+
22
+ ```bash
23
+ # pnpm
24
+ pnpm add https://github.com/ckir/corelib/releases/download/v0.1.17/ckirg-corelib-markets-0.1.17.tgz
25
+ ```
26
+
27
+ ## Usage Examples
28
+
29
+ ### 1. Market Monitor (Resilient Status Poller)
30
+ Intelligent long-running task that adapts to market hours and handles failures gracefully. It emits events only on market phase changes or after the first successful poll.
31
+
32
+ ```typescript
33
+ import { MarketMonitor, type MarketPhase } from '@ckirg/corelib-markets';
34
+
35
+ const monitor = new MarketMonitor({
36
+ liveIntervalSec: 15, // Frequency when market is open
37
+ closedIntervalSec: 1800, // Frequency when market is closed
38
+ warnIntervalSec: 60 // Warning throttle during failures
39
+ });
40
+
41
+ // Emits on first successful poll and whenever the market phase (open/closed/pre/after) changes
42
+ monitor.on("status-change", (phase: MarketPhase, data, heuristic) => {
43
+ console.log(`Current phase: ${phase}`);
44
+ console.log(`Is heuristic (using fallback data due to fetch failure): ${!!heuristic}`);
45
+ console.log('Full Market Data:', data);
46
+ });
47
+
48
+ monitor.on("stopped", () => {
49
+ console.log("Monitor gracefully stopped.");
50
+ });
51
+
52
+ monitor.start();
53
+
54
+ // Check current state
55
+ console.log('Is Running:', monitor.isRunningState);
56
+ console.log('Current Phase:', monitor.currentPhase);
57
+
58
+ // Later...
59
+ // monitor.stop();
60
+ ```
61
+
62
+ ### 2. Nasdaq Market Status & Sleep Calculation
63
+ Direct retrieval and wait-time calculation.
64
+
65
+ ```typescript
66
+ import { MarketStatus } from '@ckirg/corelib-markets';
67
+
68
+ const result = await MarketStatus.getStatus();
69
+
70
+ if (result.status === 'success') {
71
+ const info = result.value;
72
+ console.log(`Nasdaq Status: ${info.mrktStatus}`);
73
+
74
+ // Calculate milliseconds until next open or pre-market (returns 0 if already open)
75
+ const sleepMs = MarketStatus.getSleepDuration(info);
76
+ console.log(`Sleeping ${sleepMs}ms until next event.`);
77
+ }
78
+ ```
79
+
80
+ ### 3. CNN Fear & Greed Index
81
+ Retrieve sentiment data with optional historical filtering.
82
+
83
+ ```typescript
84
+ import { CnnFearAndGreed, CnnFearAndGreedFilter } from '@ckirg/corelib-markets';
85
+
86
+ // Fetch current Fear & Greed Index (returns the 'fear_and_greed' sub-object by default)
87
+ const current = await CnnFearAndGreed.getFearAndGreed();
88
+
89
+ // Fetch historical scores (full 1-year data)
90
+ const historical = await CnnFearAndGreed.getFearAndGreed("Historical", "full");
91
+
92
+ // Fetch specific metric for a specific date
93
+ const vix = await CnnFearAndGreed.getFearAndGreed(
94
+ "2026-03-15",
95
+ CnnFearAndGreedFilter.MarketVolatilityVix
96
+ );
97
+
98
+ if (current.status === 'success') {
99
+ console.log(`Score: ${current.value.score} (${current.value.rating})`);
100
+ }
101
+ ```
102
+
103
+ ### 4. Historical Data (Yahoo Finance v3)
104
+ Retrieve standardized historical OHLCV data using a resilient Yahoo Finance v3 integration.
105
+
106
+ ```typescript
107
+ import { Historical } from '@ckirg/corelib-markets';
108
+
109
+ // Fetch daily historical data for the last year
110
+ const result = await Historical.getData("AAPL", {
111
+ period1: "2023-01-01",
112
+ interval: "1d"
113
+ });
114
+
115
+ if (result.status === 'success') {
116
+ console.log(`Retrieved ${result.value.length} quotes for ${result.value[0].symbol}`);
117
+ const latest = result.value[result.value.length - 1];
118
+ console.log(`Latest Close (${latest.date}): ${latest.close}`);
119
+ }
120
+ ```
121
+
122
+ ### 5. Real-Time Streaming (Rust WS Engine)
123
+
124
+ The flagship feature of `@ckirg/corelib-markets`. All three streaming providers share the same architecture:
125
+
126
+ **Architecture:** The Rust crate owns the WebSocket connection. Data arrives on a Rust worker thread and is forwarded to JS via napi `ThreadsafeFunction` — the JS event loop is never blocked. Each wrapper is a standard Node.js `EventEmitter`. Construction is synchronous (calls into the native addon); `init()` + `start()` are async. Wrap `new XxxStreaming()` in `try/catch`.
127
+
128
+ #### Alpaca (authenticated; IEX real-time feed)
129
+
130
+ ```typescript
131
+ import { AlpacaStreaming } from '@ckirg/corelib-markets';
132
+
133
+ const stream = new AlpacaStreaming();
134
+
135
+ // Credentials via init() or APCA_API_KEY_ID / APCA_API_SECRET_KEY env vars
136
+ await stream.init({
137
+ keyId: 'YOUR_KEY',
138
+ secretKey: 'YOUR_SECRET',
139
+ silenceSeconds: 60, // auto-reconnect after N seconds of silence (default: 60)
140
+ // dbPath: '/tmp/alpaca.redb' // optional: override persistence DB path
141
+ });
142
+
143
+ await stream.start();
144
+
145
+ // Simple array → mapped to quotes channel; or pass { trades?, quotes?, bars? }
146
+ stream.subscribe(['AAPL', 'TSLA', 'NVDA']);
147
+
148
+ stream.on('pricing', (data) => {
149
+ console.log(`${data.symbol}: $${data.price}`);
150
+ });
151
+ stream.on('market', (event) => console.log('market event', event));
152
+ stream.on('connected', () => console.log('connected'));
153
+ stream.on('disconnected', () => console.log('disconnected'));
154
+ stream.on('reconnecting', () => console.log('reconnecting'));
155
+ stream.on('log', (r) => console.log(`[${r.level}] ${r.msg}`));
156
+ stream.on('error', (e) => console.error('error', e));
157
+
158
+ // stream.unsubscribe(['AAPL']);
159
+ // stream.clean(); // wipe persistence DB (auto-runs in development mode)
160
+ stream.stop();
161
+ ```
162
+
163
+ #### Finnhub (authenticated; token required)
164
+
165
+ ```typescript
166
+ import { FinnhubStreaming, type FinnhubPricingData } from '@ckirg/corelib-markets';
167
+
168
+ const stream = new FinnhubStreaming();
169
+
170
+ // Token via init() or FINNHUB_API_KEY env var
171
+ await stream.init({
172
+ token: 'YOUR_FINNHUB_API_KEY',
173
+ // name?: string — optional label
174
+ // baseUrl?: string — override WS endpoint
175
+ });
176
+
177
+ await stream.start();
178
+
179
+ await stream.subscribe(['AAPL', 'MSFT', 'NVDA']);
180
+
181
+ stream.on('pricing', (data: FinnhubPricingData) => {
182
+ // Payload fields are camelCase in JS (napi convention): messageType, not message_type.
183
+ // data.timestamp is a numeric epoch in milliseconds.
184
+ console.log(`[${data.messageType}] ${data.symbol}: $${data.price} @ ${data.timestamp}`);
185
+ });
186
+ stream.on('connected', () => console.log('connected'));
187
+ stream.on('disconnected', (r) => console.log('disconnected', r));
188
+ stream.on('reconnecting', (d) => console.log('reconnecting', d));
189
+ stream.on('log', (r) => console.log(`[${r.level}] ${r.msg}`));
190
+ stream.on('error', (e) => console.error('error', e));
191
+
192
+ await stream.stop();
193
+ ```
194
+
195
+ #### Yahoo Finance (unauthenticated)
196
+
197
+ ```typescript
198
+ import { YahooStreaming } from '@ckirg/corelib-markets';
199
+
200
+ const stream = new YahooStreaming();
201
+
202
+ await stream.init({
203
+ silenceSeconds: 45, // auto-reconnect threshold (default: 60)
204
+ // dbPath: '/tmp/yahoo_streaming.redb'
205
+ });
206
+
207
+ await stream.start();
208
+
209
+ stream.subscribe(['AAPL', 'TSLA', 'NVDA', 'BTC-USD']);
210
+
211
+ // Yahoo's raw payload is the proto-decoded JsPricingData: the ticker is `id` (not `symbol`).
212
+ stream.on('pricing', (data) => console.log(`${data.id}: $${data.price}`));
213
+ stream.on('silence-reconnect', () => console.log('silent too long, reconnecting'));
214
+ stream.on('connected', () => console.log('connected'));
215
+ stream.on('disconnected', () => console.log('disconnected'));
216
+ stream.on('error', (e) => console.error('error', e));
217
+
218
+ // stream.clean(); // wipe persistence DB (auto-runs in development mode)
219
+ stream.stop();
220
+ ```
221
+
222
+ **Events emitted by all three:** `pricing`, `log` (`{level, msg, extras?}`), `connected`, `disconnected`, `reconnecting`, `error`. Alpaca/Yahoo also emit `market` (unified market event, parsed JSON object) and `silence-reconnect`.
223
+
224
+ ### 6. Nasdaq 100 Symbols
225
+ Fast, cached retrieval of the Nasdaq 100 constituent symbols.
226
+
227
+ ```typescript
228
+ import { getSymbolsTop100 } from '@ckirg/corelib-markets';
229
+
230
+ const symbols = await getSymbolsTop100();
231
+ console.log(`Nasdaq 100 constituents (${symbols.length}):`, symbols);
232
+ ```
233
+
234
+ ### 7. Persistent Symbol Database (MarketSymbols)
235
+ Automated Nasdaq symbol directory with auto-refresh and environment-aware search sequencing.
236
+
237
+ #### Features
238
+ - **Auto-Refresh**: Synchronizes with official Nasdaq directories (`nasdaqlisted.txt`, `otherlisted.txt`) if data is missing or outdated (older than today NY time).
239
+ - **Environment-Aware**: Automatically optimizes search sequence based on the runtime:
240
+ - **Standard (Node/Bun)**: `SQLite -> Nasdaq API -> Ingestors` (Prioritizes local speed).
241
+ - **Edge (Cloudflare/Lambda)**: `Nasdaq API -> Ingestors -> SQLite` (Prioritizes fresh API data over cold storage).
242
+ - **Turso Support**: Supports both local SQLite and remote Turso/LibSQL databases.
243
+
244
+ #### Basic Usage
245
+ ```typescript
246
+ import { MarketSymbols } from '@ckirg/corelib-markets';
247
+
248
+ // Initialize (defaults to local SQLite: ./tmp/NasdaqSymbols.sqlite)
249
+ const symbols = new MarketSymbols();
250
+
251
+ // Get details (Sequence depends on runtime)
252
+ const aapl = await symbols.get("AAPL");
253
+ ```
254
+
255
+ #### Cloud Usage (Turso)
256
+ ```typescript
257
+ const symbols = new MarketSymbols({
258
+ dbUrl: "libsql://your-db.turso.io",
259
+ dbToken: "your-auth-token"
260
+ });
261
+ ```
262
+
263
+ #### Custom Ingestors (e.g., Google Apps Script)
264
+ The ingestor should return a `MarketSymbolRow` JSON structure.
265
+
266
+ ```typescript
267
+ const ingestorUrl = "https://script.google.com/macros/s/.../exec";
268
+ const symbols = new MarketSymbols(undefined, [ingestorUrl]);
269
+
270
+ // Fallback search will hit the GAS endpoint if not found in DB or Nasdaq API
271
+ const custom = await symbols.get("PRIVATE_TICKER");
272
+ ```
273
+
274
+ #### Manual Maintenance
275
+ ```typescript
276
+ // Force a full directory refresh from Nasdaq sources
277
+ await symbols.refresh();
278
+
279
+ // Graceful shutdown
280
+ await symbols.close();
281
+ ```
282
+
283
+ ### 8. Resilient Nasdaq API
284
+ Low-level wrapper for custom Nasdaq API interactions.
285
+
286
+ ```typescript
287
+ import { ApiNasdaqUnlimited } from '@ckirg/corelib-markets';
288
+
289
+ // Execute single high-resilience request
290
+ const result = await ApiNasdaqUnlimited.endPoint('https://api.nasdaq.com/api/quote/AAPL/info');
291
+
292
+ if (result.status === 'success') {
293
+ console.log('AAPL Info:', result.value);
294
+ }
295
+ ```
296
+
297
+ ### 9. Integration with Core (Logging & Config)
298
+ `ts-markets` is designed to seamlessly use the logging and configuration systems provided by `@ckirg/corelib`.
299
+
300
+ ```typescript
301
+ import { logger, ConfigManager } from '@ckirg/corelib';
302
+ import { MarketMonitor } from '@ckirg/corelib-markets';
303
+
304
+ // The monitor automatically uses the global logger
305
+ const monitor = new MarketMonitor();
306
+
307
+ // You can override default headers via ConfigManager
308
+ // markets.nasdaq.headers
309
+ // markets.cnn.headers
310
+
311
+ logger.info("Starting market services...");
312
+ monitor.start();
313
+ ```