@startsimpli/api 0.5.18 → 0.5.20
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 +287 -240
- package/package.json +11 -2
- package/src/constants/endpoints.ts +10 -4
- package/src/index.ts +40 -1
- package/src/lib/error-handler.ts +40 -5
- package/src/lib/errors.ts +7 -2
- package/src/lib/markets-api.ts +272 -37
- package/src/lib/sanitize-core.ts +32 -0
- package/src/lib/sanitize.native.ts +17 -0
- package/src/lib/sanitize.ts +6 -26
- package/src/lib/vault-api.test.ts +62 -0
- package/src/lib/vault-api.ts +155 -0
- package/src/types/api.ts +6 -0
|
@@ -77,12 +77,18 @@ export const ENDPOINTS = {
|
|
|
77
77
|
SECTOR_BREADTH: 'api/v1/markets/sector_breadth',
|
|
78
78
|
EARNINGS_CALENDAR: 'api/v1/markets/calendar',
|
|
79
79
|
TRADING_SNAPSHOTS: 'api/v1/markets/trading/snapshots',
|
|
80
|
+
BARS_BULK: 'api/v1/markets/bars/bulk',
|
|
81
|
+
SOCIAL_POSTS: 'api/v1/markets/social_posts',
|
|
80
82
|
|
|
81
|
-
// Options
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Options — schemas confirmed by claude-mac (agent_bridge req 22ac4889).
|
|
84
|
+
// /options/iv/ summary + /options/skew/ intentionally omitted: iv/history covers
|
|
85
|
+
// rank/RV/spread; skew is client-derived from chain (mac req a2f98ba6).
|
|
86
|
+
OPTIONS_CHAIN: (symbol: string) => `api/v1/markets/instruments/${symbol}/options/chain`,
|
|
87
|
+
OPTIONS_IV_HISTORY: (symbol: string) => `api/v1/markets/instruments/${symbol}/options/iv/history`,
|
|
88
|
+
OPTIONS_ATM_IV_HISTORY: (symbol: string) => `api/v1/markets/instruments/${symbol}/options/atm_iv_history`,
|
|
89
|
+
MACRO_CALENDAR: 'api/v1/markets/macro_calendar',
|
|
85
90
|
OPTIONS_GREEKS: 'api/v1/markets/options/greeks',
|
|
91
|
+
OPTIONS_UNUSUAL_ACTIVITY: 'api/v1/markets/options/unusual_activity',
|
|
86
92
|
VIX_TERM: 'api/v1/markets/vix_term',
|
|
87
93
|
|
|
88
94
|
// Sources ops
|
package/src/index.ts
CHANGED
|
@@ -163,6 +163,7 @@ import { EnrichmentApi } from './lib/enrichment-api';
|
|
|
163
163
|
import { TargetListsApi } from './lib/target-lists-api';
|
|
164
164
|
import { MessageTemplatesApi } from './lib/message-templates-api';
|
|
165
165
|
import { MarketsApi } from './lib/markets-api';
|
|
166
|
+
import { VaultApi } from './lib/vault-api';
|
|
166
167
|
|
|
167
168
|
import type { ApiClientConfig } from './lib/api-client';
|
|
168
169
|
|
|
@@ -187,9 +188,25 @@ export function createStartSimpliApi(config: ApiClientConfig = {}) {
|
|
|
187
188
|
targetLists: new TargetListsApi(client),
|
|
188
189
|
messageTemplates: new MessageTemplatesApi(client),
|
|
189
190
|
markets: new MarketsApi(client),
|
|
191
|
+
vault: new VaultApi(client),
|
|
190
192
|
};
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
// Vault API
|
|
196
|
+
export { VaultApi } from './lib/vault-api';
|
|
197
|
+
export type {
|
|
198
|
+
VaultEnvironment,
|
|
199
|
+
VaultEnvironmentInput,
|
|
200
|
+
VaultSecret,
|
|
201
|
+
VaultSecretInput,
|
|
202
|
+
VaultSecretReveal,
|
|
203
|
+
VaultAccessKey,
|
|
204
|
+
VaultAccessKeyCreated,
|
|
205
|
+
VaultAccessKeyInput,
|
|
206
|
+
VaultAuditEntry,
|
|
207
|
+
VaultListParams,
|
|
208
|
+
} from './lib/vault-api';
|
|
209
|
+
|
|
193
210
|
// Markets API
|
|
194
211
|
export { MarketsApi } from './lib/markets-api';
|
|
195
212
|
export type {
|
|
@@ -237,9 +254,31 @@ export type {
|
|
|
237
254
|
OptionsChainResponse,
|
|
238
255
|
OptionsIvPoint,
|
|
239
256
|
OptionsIvResponse,
|
|
257
|
+
OptionsIvHistoryParams,
|
|
240
258
|
OptionsSkewPoint,
|
|
241
|
-
|
|
259
|
+
OptionsChainRaw,
|
|
242
260
|
OptionsGreeks,
|
|
261
|
+
OptionsUnusualActivityPoint,
|
|
262
|
+
OptionsUnusualActivityParams,
|
|
263
|
+
OptionsUnusualActivityResponse,
|
|
264
|
+
AtmIvHistoryPoint,
|
|
265
|
+
AtmIvHistoryParams,
|
|
266
|
+
AtmIvHistoryResponse,
|
|
267
|
+
MacroCategory,
|
|
268
|
+
MacroCalendarRow,
|
|
269
|
+
MacroCalendarParams,
|
|
270
|
+
MacroCalendarResponse,
|
|
271
|
+
VixTenor,
|
|
272
|
+
VixTermState,
|
|
243
273
|
VixTermPoint,
|
|
244
274
|
VixTermResponse,
|
|
275
|
+
BarsBulkParams,
|
|
276
|
+
BarsBulkResponse,
|
|
277
|
+
SocialPlatform,
|
|
278
|
+
SocialDirection,
|
|
279
|
+
SocialPost,
|
|
280
|
+
SocialPostMatchedEntity,
|
|
281
|
+
SocialPostResolvedContact,
|
|
282
|
+
SocialPostsParams,
|
|
283
|
+
SocialPostsResponse,
|
|
245
284
|
} from './lib/markets-api';
|
package/src/lib/error-handler.ts
CHANGED
|
@@ -9,23 +9,34 @@ export class ApiException extends Error {
|
|
|
9
9
|
public statusText?: string;
|
|
10
10
|
public errors?: Record<string, string[]>;
|
|
11
11
|
public detail?: string;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
/** Machine-readable code from the standardized error response (e.g.
|
|
13
|
+
* 'limit_reached', 'no_company'). Set by parseErrorResponse when the
|
|
14
|
+
* backend emits a structured error shape via the core exception handler. */
|
|
15
|
+
public code?: string;
|
|
16
|
+
/** Extra context the backend stapled onto the standardized response —
|
|
17
|
+
* e.g. limit_reached carries { feature_key, limit, current }. */
|
|
18
|
+
public details?: Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
constructor(message: string, options?: Partial<DRFApiError> & { code?: string; details?: Record<string, unknown> }) {
|
|
14
21
|
super(message);
|
|
15
22
|
this.name = 'ApiException';
|
|
16
23
|
this.status = options?.status;
|
|
17
24
|
this.statusText = options?.statusText;
|
|
18
25
|
this.errors = options?.errors;
|
|
19
26
|
this.detail = options?.detail;
|
|
27
|
+
this.code = options?.code;
|
|
28
|
+
this.details = options?.details;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
toJSON(): DRFApiError {
|
|
31
|
+
toJSON(): DRFApiError & { code?: string; details?: Record<string, unknown> } {
|
|
23
32
|
return {
|
|
24
33
|
message: this.message,
|
|
25
34
|
detail: this.detail,
|
|
26
35
|
status: this.status,
|
|
27
36
|
statusText: this.statusText,
|
|
28
37
|
errors: this.errors,
|
|
38
|
+
code: this.code,
|
|
39
|
+
details: this.details,
|
|
29
40
|
};
|
|
30
41
|
}
|
|
31
42
|
}
|
|
@@ -41,7 +52,31 @@ export async function parseErrorResponse(response: Response): Promise<DRFApiErro
|
|
|
41
52
|
try {
|
|
42
53
|
const data = await response.json();
|
|
43
54
|
|
|
44
|
-
//
|
|
55
|
+
// 1) Standardized response shape from apps.core.exceptions:
|
|
56
|
+
// { error, code, statusCode, fieldErrors?, ...details } — the
|
|
57
|
+
// 'error' field carries the human message; 'code' is the machine
|
|
58
|
+
// code (e.g. 'limit_reached'); any extra keys are structured
|
|
59
|
+
// context the view stapled on (feature_key, limit, current, …).
|
|
60
|
+
// Snake_case keys keep through camelCase transform too (already
|
|
61
|
+
// converted by snakeToCamel before we get here when the client
|
|
62
|
+
// applies it; both shapes handled here for safety).
|
|
63
|
+
if (typeof data === 'object' && data !== null && typeof (data.error ?? data.message) === 'string' && data.code) {
|
|
64
|
+
const reservedKeys = new Set(['error', 'code', 'statusCode', 'status_code', 'fieldErrors', 'field_errors', 'timestamp', 'requestId', 'request_id', 'detail']);
|
|
65
|
+
const extras: Record<string, unknown> = {};
|
|
66
|
+
for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
|
|
67
|
+
if (!reservedKeys.has(k)) extras[k] = v;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
message: (data.error as string) ?? (data.message as string),
|
|
71
|
+
detail: (data.detail as string | undefined) ?? (data.error as string),
|
|
72
|
+
code: data.code as string,
|
|
73
|
+
details: Object.keys(extras).length > 0 ? extras : undefined,
|
|
74
|
+
status: response.status,
|
|
75
|
+
statusText: response.statusText,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2) Plain Django REST Framework error: { detail: '...' }
|
|
45
80
|
if (data.detail) {
|
|
46
81
|
return {
|
|
47
82
|
detail: data.detail,
|
|
@@ -51,7 +86,7 @@ export async function parseErrorResponse(response: Response): Promise<DRFApiErro
|
|
|
51
86
|
};
|
|
52
87
|
}
|
|
53
88
|
|
|
54
|
-
//
|
|
89
|
+
// 3) Field-level validation errors: { email: ['...'], password: ['...'] }
|
|
55
90
|
if (typeof data === 'object') {
|
|
56
91
|
return {
|
|
57
92
|
errors: data,
|
package/src/lib/errors.ts
CHANGED
|
@@ -70,8 +70,13 @@ export class AppError extends Error {
|
|
|
70
70
|
this.details = details;
|
|
71
71
|
this.isOperational = isOperational;
|
|
72
72
|
|
|
73
|
-
// Maintains proper stack trace
|
|
74
|
-
|
|
73
|
+
// Maintains proper stack trace where supported (V8/Hermes). Guarded and
|
|
74
|
+
// typed locally so the package stays portable to engines/tsconfigs without
|
|
75
|
+
// the Node-only Error.captureStackTrace global (e.g. React Native).
|
|
76
|
+
const captureStackTrace = (
|
|
77
|
+
Error as { captureStackTrace?: (target: object, ctor?: unknown) => void }
|
|
78
|
+
).captureStackTrace;
|
|
79
|
+
captureStackTrace?.(this, this.constructor);
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
/**
|
package/src/lib/markets-api.ts
CHANGED
|
@@ -257,6 +257,18 @@ export interface OptionContract {
|
|
|
257
257
|
volume: number;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// Server returns separate calls[]/puts[] (confirmed stable, mac req 22ac4889).
|
|
261
|
+
// Client flattens into a single contracts[] with side tag.
|
|
262
|
+
export interface OptionsChainRaw {
|
|
263
|
+
symbol: string;
|
|
264
|
+
expiry: string;
|
|
265
|
+
spot: number | string;
|
|
266
|
+
calls?: Omit<OptionContract, 'side'>[];
|
|
267
|
+
puts?: Omit<OptionContract, 'side'>[];
|
|
268
|
+
expiries?: string[];
|
|
269
|
+
computedAt?: string | null;
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
export interface OptionsChainResponse {
|
|
261
273
|
symbol: string;
|
|
262
274
|
expiry: string;
|
|
@@ -271,32 +283,99 @@ export interface OptionsChainParams {
|
|
|
271
283
|
expiry?: string;
|
|
272
284
|
}
|
|
273
285
|
|
|
286
|
+
// Per brain-trading req b09a6bb6 — atm/skew/put-call ratios as a single snapshot row.
|
|
287
|
+
// realized_vol_30d + iv_rv_spread_30d added in mac req 22ac4889.
|
|
274
288
|
export interface OptionsIvPoint {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
289
|
+
snapshotDate: string;
|
|
290
|
+
atmIv: number;
|
|
291
|
+
atmCallIv: number | null;
|
|
292
|
+
atmPutIv: number | null;
|
|
293
|
+
ivSkew25d: number | null;
|
|
294
|
+
ivSkewOtmPuts: number | null;
|
|
295
|
+
putCallVolumeRatio: number | null;
|
|
296
|
+
putCallOiRatio: number | null;
|
|
297
|
+
// Populated once 20+ days of history accumulate
|
|
298
|
+
ivRank30d: number | null;
|
|
299
|
+
ivRank252d: number | null;
|
|
300
|
+
atmIvPercentile252d: number | null;
|
|
301
|
+
// Annualized 30d stdev of close-to-close log returns (decimal, e.g. 0.42)
|
|
302
|
+
realizedVol30d: number | null;
|
|
303
|
+
// atm_iv - realized_vol_30d. >0 ⇒ options priced rich vs realized.
|
|
304
|
+
ivRvSpread30d: number | null;
|
|
281
305
|
}
|
|
282
306
|
|
|
283
307
|
export interface OptionsIvResponse {
|
|
284
308
|
symbol: string;
|
|
285
|
-
count: number;
|
|
286
309
|
results: OptionsIvPoint[];
|
|
287
310
|
}
|
|
288
311
|
|
|
312
|
+
export interface OptionsIvHistoryParams {
|
|
313
|
+
symbol: string;
|
|
314
|
+
since?: string;
|
|
315
|
+
until?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Skew is derived client-side from chain (per brain-trading guidance);
|
|
319
|
+
// kept as a local UI type for the chart.
|
|
289
320
|
export interface OptionsSkewPoint {
|
|
290
321
|
strike: number;
|
|
291
322
|
iv: number;
|
|
292
323
|
side: OptionSide;
|
|
293
324
|
}
|
|
294
325
|
|
|
295
|
-
export interface
|
|
326
|
+
export interface BarsBulkParams {
|
|
327
|
+
symbols: string[];
|
|
328
|
+
interval?: BarInterval;
|
|
329
|
+
since?: string;
|
|
330
|
+
until?: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface BarsBulkResponse {
|
|
334
|
+
interval: BarInterval;
|
|
335
|
+
count: number;
|
|
336
|
+
results: Record<string, PriceBar[]>;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export type SocialPlatform = 'truth_social' | string;
|
|
340
|
+
export type SocialDirection = 'bullish' | 'bearish' | 'neutral' | 'unclear';
|
|
341
|
+
|
|
342
|
+
export interface SocialPostMatchedEntity {
|
|
296
343
|
symbol: string;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
344
|
+
name?: string | null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface SocialPostResolvedContact {
|
|
348
|
+
id: string;
|
|
349
|
+
name: string;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface SocialPost {
|
|
353
|
+
id: string;
|
|
354
|
+
platform: SocialPlatform;
|
|
355
|
+
postedAt: string;
|
|
356
|
+
url?: string | null;
|
|
357
|
+
text?: string | null;
|
|
358
|
+
resolvedContact: SocialPostResolvedContact | null;
|
|
359
|
+
matchedEntities: SocialPostMatchedEntity[];
|
|
360
|
+
sentiment: number | null;
|
|
361
|
+
sentimentLabel: NewsSentimentLabel | null;
|
|
362
|
+
eventType: string | null;
|
|
363
|
+
marketMovingScore: number | null;
|
|
364
|
+
directionForSymbol: SocialDirection | null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface SocialPostsParams {
|
|
368
|
+
symbol?: string;
|
|
369
|
+
platform?: SocialPlatform;
|
|
370
|
+
since?: string;
|
|
371
|
+
until?: string;
|
|
372
|
+
direction?: SocialDirection;
|
|
373
|
+
limit?: number;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface SocialPostsResponse {
|
|
377
|
+
count: number;
|
|
378
|
+
results: SocialPost[];
|
|
300
379
|
}
|
|
301
380
|
|
|
302
381
|
export interface OptionsGreeks {
|
|
@@ -312,15 +391,97 @@ export interface OptionsGreeks {
|
|
|
312
391
|
computedAt: string | null;
|
|
313
392
|
}
|
|
314
393
|
|
|
394
|
+
// ATM IV history derived from Tradier OptionContractBar via BS inversion (mac req e5b8c299).
|
|
395
|
+
// Deeper history than /options/iv/history/ for symbols where contract listings predate
|
|
396
|
+
// MarketOptionsSnapshot accumulation (UCO has 112 rows back to Sep 2025).
|
|
397
|
+
export interface AtmIvHistoryPoint {
|
|
398
|
+
date: string;
|
|
399
|
+
spot: number;
|
|
400
|
+
atmStrike: number;
|
|
401
|
+
atmCallIv: number | null;
|
|
402
|
+
atmPutIv: number | null;
|
|
403
|
+
atmIv: number | null;
|
|
404
|
+
ivRank30d: number | null;
|
|
405
|
+
ivRank252d: number | null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export interface AtmIvHistoryParams {
|
|
409
|
+
symbol: string;
|
|
410
|
+
since?: string;
|
|
411
|
+
until?: string;
|
|
412
|
+
// Drop rows where both call AND put closest-strike fall outside |strike/spot - 1| > moneynessMax.
|
|
413
|
+
// Omit = full series. Recommended 0.10 to filter early UCO Sep-2025 garbage; tighter (0.05) risks
|
|
414
|
+
// dropping thin-coverage days entirely. (mac req 47ac5ec6)
|
|
415
|
+
moneynessMax?: number;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface AtmIvHistoryResponse {
|
|
419
|
+
symbol: string;
|
|
420
|
+
count: number;
|
|
421
|
+
results: AtmIvHistoryPoint[];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Macro event calendar (mac req e5b8c299). FOMC + CPI + NFP + PCE + EIA petroleum/natgas.
|
|
425
|
+
export type MacroCategory = 'fomc' | 'cpi' | 'nfp' | 'pce' | 'eia_petroleum' | 'eia_natgas' | string;
|
|
426
|
+
|
|
427
|
+
export interface MacroCalendarRow {
|
|
428
|
+
releaseAt: string;
|
|
429
|
+
category: MacroCategory;
|
|
430
|
+
name: string;
|
|
431
|
+
expectedMovers: string[];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface MacroCalendarParams {
|
|
435
|
+
since?: string;
|
|
436
|
+
until?: string;
|
|
437
|
+
categories?: MacroCategory[];
|
|
438
|
+
movers?: string[];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface MacroCalendarResponse {
|
|
442
|
+
count: number;
|
|
443
|
+
results: MacroCalendarRow[];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Unusual options activity z-score per underlying (mac req 748b457e).
|
|
447
|
+
// z_score = (latest_volume - mean) / std over the trailing lookback window.
|
|
448
|
+
// sample_size < 5 ⇒ "collecting"; z_score >= 2 ⇒ unusual.
|
|
449
|
+
export interface OptionsUnusualActivityPoint {
|
|
450
|
+
symbol: string;
|
|
451
|
+
latestDate: string | null;
|
|
452
|
+
latestVolume: number | null;
|
|
453
|
+
mean: number | null;
|
|
454
|
+
std: number | null;
|
|
455
|
+
zScore: number | null;
|
|
456
|
+
sampleSize: number;
|
|
457
|
+
lookback: number;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export interface OptionsUnusualActivityParams {
|
|
461
|
+
symbols: string[];
|
|
462
|
+
lookback?: number;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface OptionsUnusualActivityResponse {
|
|
466
|
+
count: number;
|
|
467
|
+
results: OptionsUnusualActivityPoint[];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export type VixTenor = '9D' | '30D' | '3M' | string;
|
|
471
|
+
// 3-state regime confirmed by mac req 22ac4889 — threshold ±2% spread VIX vs VIX3M.
|
|
472
|
+
export type VixTermState = 'contango_calm' | 'contango_neutral' | 'backwardation_stress';
|
|
473
|
+
|
|
315
474
|
export interface VixTermPoint {
|
|
316
|
-
|
|
475
|
+
tenor: VixTenor;
|
|
476
|
+
symbol: string;
|
|
317
477
|
value: number;
|
|
318
478
|
percentile252d: number | null;
|
|
319
|
-
|
|
479
|
+
delta1d: number | null;
|
|
320
480
|
}
|
|
321
481
|
|
|
322
482
|
export interface VixTermResponse {
|
|
323
483
|
generatedAt: string;
|
|
484
|
+
state: VixTermState;
|
|
324
485
|
points: VixTermPoint[];
|
|
325
486
|
}
|
|
326
487
|
|
|
@@ -440,24 +601,27 @@ function coerceContract(c: OptionContract): OptionContract {
|
|
|
440
601
|
function coerceIvPoint(p: OptionsIvPoint): OptionsIvPoint {
|
|
441
602
|
return {
|
|
442
603
|
...p,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
604
|
+
atmIv: n(p.atmIv),
|
|
605
|
+
atmCallIv: maybeN(p.atmCallIv),
|
|
606
|
+
atmPutIv: maybeN(p.atmPutIv),
|
|
607
|
+
ivSkew25d: maybeN(p.ivSkew25d),
|
|
608
|
+
ivSkewOtmPuts: maybeN(p.ivSkewOtmPuts),
|
|
609
|
+
putCallVolumeRatio: maybeN(p.putCallVolumeRatio),
|
|
610
|
+
putCallOiRatio: maybeN(p.putCallOiRatio),
|
|
611
|
+
ivRank30d: maybeN(p.ivRank30d),
|
|
612
|
+
ivRank252d: maybeN(p.ivRank252d),
|
|
613
|
+
atmIvPercentile252d: maybeN(p.atmIvPercentile252d),
|
|
614
|
+
realizedVol30d: maybeN(p.realizedVol30d),
|
|
615
|
+
ivRvSpread30d: maybeN(p.ivRvSpread30d),
|
|
448
616
|
};
|
|
449
617
|
}
|
|
450
618
|
|
|
451
|
-
function coerceSkewPoint(p: OptionsSkewPoint): OptionsSkewPoint {
|
|
452
|
-
return { ...p, strike: n(p.strike), iv: n(p.iv) };
|
|
453
|
-
}
|
|
454
|
-
|
|
455
619
|
function coerceVixPoint(p: VixTermPoint): VixTermPoint {
|
|
456
620
|
return {
|
|
457
621
|
...p,
|
|
458
622
|
value: n(p.value),
|
|
459
623
|
percentile252d: maybeN(p.percentile252d),
|
|
460
|
-
|
|
624
|
+
delta1d: maybeN(p.delta1d),
|
|
461
625
|
};
|
|
462
626
|
}
|
|
463
627
|
|
|
@@ -529,30 +693,30 @@ export class MarketsApi {
|
|
|
529
693
|
// ===== Options + VIX (tentative — see agent_bridge req 42c763b2) =======
|
|
530
694
|
|
|
531
695
|
async getOptionsChain(params: OptionsChainParams): Promise<OptionsChainResponse> {
|
|
532
|
-
const
|
|
533
|
-
|
|
696
|
+
const { symbol, expiry } = params;
|
|
697
|
+
const res = await this.client.fetch.get<OptionsChainRaw>(ENDPOINTS.OPTIONS_CHAIN(symbol), {
|
|
698
|
+
params: expiry ? { expiry } : undefined,
|
|
534
699
|
});
|
|
700
|
+
const calls = (res.calls ?? []).map((c) => coerceContract({ ...c, side: 'call' as const }));
|
|
701
|
+
const puts = (res.puts ?? []).map((c) => coerceContract({ ...c, side: 'put' as const }));
|
|
535
702
|
return {
|
|
536
|
-
|
|
703
|
+
symbol: res.symbol,
|
|
704
|
+
expiry: res.expiry,
|
|
537
705
|
spot: n(res.spot),
|
|
538
|
-
contracts:
|
|
706
|
+
contracts: [...calls, ...puts],
|
|
707
|
+
expiries: res.expiries,
|
|
708
|
+
computedAt: res.computedAt,
|
|
539
709
|
};
|
|
540
710
|
}
|
|
541
711
|
|
|
542
|
-
async
|
|
543
|
-
const
|
|
544
|
-
|
|
712
|
+
async getOptionsIvHistory(params: OptionsIvHistoryParams): Promise<OptionsIvResponse> {
|
|
713
|
+
const { symbol, ...rest } = params;
|
|
714
|
+
const res = await this.client.fetch.get<OptionsIvResponse>(ENDPOINTS.OPTIONS_IV_HISTORY(symbol), {
|
|
715
|
+
params: rest as Record<string, unknown>,
|
|
545
716
|
});
|
|
546
717
|
return { ...res, results: (res.results ?? []).map(coerceIvPoint) };
|
|
547
718
|
}
|
|
548
719
|
|
|
549
|
-
async getOptionsSkew(symbol: string, expiry: string): Promise<OptionsSkewResponse> {
|
|
550
|
-
const res = await this.client.fetch.get<OptionsSkewResponse>(ENDPOINTS.OPTIONS_SKEW, {
|
|
551
|
-
params: { symbol, expiry },
|
|
552
|
-
});
|
|
553
|
-
return { ...res, spot: n(res.spot), results: (res.results ?? []).map(coerceSkewPoint) };
|
|
554
|
-
}
|
|
555
|
-
|
|
556
720
|
async getOptionsGreeks(symbol: string, expiry: string, strike: number, side: OptionSide): Promise<OptionsGreeks> {
|
|
557
721
|
const res = await this.client.fetch.get<OptionsGreeks>(ENDPOINTS.OPTIONS_GREEKS, {
|
|
558
722
|
params: { symbol, expiry, strike, side },
|
|
@@ -568,6 +732,77 @@ export class MarketsApi {
|
|
|
568
732
|
};
|
|
569
733
|
}
|
|
570
734
|
|
|
735
|
+
async getAtmIvHistory(params: AtmIvHistoryParams): Promise<AtmIvHistoryResponse> {
|
|
736
|
+
const { symbol, moneynessMax, ...rest } = params;
|
|
737
|
+
const res = await this.client.fetch.get<AtmIvHistoryResponse>(ENDPOINTS.OPTIONS_ATM_IV_HISTORY(symbol), {
|
|
738
|
+
params: {
|
|
739
|
+
...rest,
|
|
740
|
+
...(moneynessMax != null ? { moneyness_max: moneynessMax } : {}),
|
|
741
|
+
} as Record<string, unknown>,
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
...res,
|
|
745
|
+
results: (res.results ?? []).map((p) => ({
|
|
746
|
+
...p,
|
|
747
|
+
spot: n(p.spot),
|
|
748
|
+
atmStrike: n(p.atmStrike),
|
|
749
|
+
atmCallIv: maybeN(p.atmCallIv),
|
|
750
|
+
atmPutIv: maybeN(p.atmPutIv),
|
|
751
|
+
atmIv: maybeN(p.atmIv),
|
|
752
|
+
ivRank30d: maybeN(p.ivRank30d),
|
|
753
|
+
ivRank252d: maybeN(p.ivRank252d),
|
|
754
|
+
})),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
async getMacroCalendar(params?: MacroCalendarParams): Promise<MacroCalendarResponse> {
|
|
759
|
+
const { categories, movers, ...rest } = params ?? {};
|
|
760
|
+
return this.client.fetch.get<MacroCalendarResponse>(ENDPOINTS.MACRO_CALENDAR, {
|
|
761
|
+
params: {
|
|
762
|
+
...rest,
|
|
763
|
+
...(categories ? { categories: categories.join(',') } : {}),
|
|
764
|
+
...(movers ? { movers: movers.join(',') } : {}),
|
|
765
|
+
} as Record<string, unknown>,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async getOptionsUnusualActivity(params: OptionsUnusualActivityParams): Promise<OptionsUnusualActivityResponse> {
|
|
770
|
+
const { symbols, lookback } = params;
|
|
771
|
+
const res = await this.client.fetch.get<OptionsUnusualActivityResponse>(ENDPOINTS.OPTIONS_UNUSUAL_ACTIVITY, {
|
|
772
|
+
params: { symbols: symbols.join(','), ...(lookback != null ? { lookback } : {}) } as Record<string, unknown>,
|
|
773
|
+
});
|
|
774
|
+
return {
|
|
775
|
+
...res,
|
|
776
|
+
results: (res.results ?? []).map((p) => ({
|
|
777
|
+
...p,
|
|
778
|
+
latestVolume: maybeN(p.latestVolume),
|
|
779
|
+
mean: maybeN(p.mean),
|
|
780
|
+
std: maybeN(p.std),
|
|
781
|
+
zScore: maybeN(p.zScore),
|
|
782
|
+
sampleSize: n(p.sampleSize),
|
|
783
|
+
lookback: n(p.lookback),
|
|
784
|
+
})),
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
async getBarsBulk(params: BarsBulkParams): Promise<BarsBulkResponse> {
|
|
789
|
+
const { symbols, ...rest } = params;
|
|
790
|
+
const res = await this.client.fetch.get<BarsBulkResponse>(ENDPOINTS.BARS_BULK, {
|
|
791
|
+
params: { ...rest, symbols: symbols.join(','), format: 'json' } as Record<string, unknown>,
|
|
792
|
+
});
|
|
793
|
+
const out: Record<string, PriceBar[]> = {};
|
|
794
|
+
for (const [sym, bars] of Object.entries(res.results ?? {})) {
|
|
795
|
+
out[sym] = (bars ?? []).map(coerceBar);
|
|
796
|
+
}
|
|
797
|
+
return { ...res, results: out };
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async getSocialPosts(params?: SocialPostsParams): Promise<SocialPostsResponse> {
|
|
801
|
+
return this.client.fetch.get<SocialPostsResponse>(ENDPOINTS.SOCIAL_POSTS, {
|
|
802
|
+
params: params as Record<string, unknown> | undefined,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
571
806
|
async getVixTerm(): Promise<VixTermResponse> {
|
|
572
807
|
const res = await this.client.fetch.get<VixTermResponse>(ENDPOINTS.VIX_TERM);
|
|
573
808
|
return { ...res, points: (res.points ?? []).map(coerceVixPoint) };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-neutral sanitize helpers — no DOM, no DOMPurify.
|
|
3
|
+
*
|
|
4
|
+
* Shared by sanitize.ts (web/node, DOMPurify-backed `sanitizeHtml`) and
|
|
5
|
+
* sanitize.native.ts (React Native, DOM-free `sanitizeHtml`). Keeping these
|
|
6
|
+
* pure functions here avoids duplicating them across the two platform entries.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate and sanitize a search query string.
|
|
11
|
+
*
|
|
12
|
+
* Trims whitespace, escapes regex special characters, and limits length to 100 chars.
|
|
13
|
+
*/
|
|
14
|
+
export function sanitizeSearchQuery(query: string): string {
|
|
15
|
+
if (!query || typeof query !== 'string') {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return query
|
|
20
|
+
.trim()
|
|
21
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
22
|
+
.substring(0, 100); // Limit length
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a string is a safe SQL/API identifier.
|
|
27
|
+
*
|
|
28
|
+
* Only allows alphanumeric characters, underscores, and hyphens.
|
|
29
|
+
*/
|
|
30
|
+
export function validateIdentifier(input: string): boolean {
|
|
31
|
+
return /^[a-zA-Z0-9_-]+$/.test(input);
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { sanitizeSearchQuery, validateIdentifier } from './sanitize-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* XSS protection for React Native, where there is no DOM and no DOMPurify
|
|
5
|
+
* (isomorphic-dompurify pulls in jsdom, which cannot be bundled by Metro).
|
|
6
|
+
*
|
|
7
|
+
* React Native renders text through <Text>, never as interpreted HTML, so the
|
|
8
|
+
* safe and correct transform is to strip all markup and return plain text. The
|
|
9
|
+
* web build (sanitize.ts) keeps an allowlist via DOMPurify because the browser
|
|
10
|
+
* actually renders the result as HTML; RN never does.
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeHtml(input: string): string {
|
|
13
|
+
if (!input || typeof input !== 'string') {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
return input.replace(/<[^>]*>/g, '');
|
|
17
|
+
}
|
package/src/lib/sanitize.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import DOMPurify from 'isomorphic-dompurify';
|
|
2
2
|
|
|
3
|
+
export { sanitizeSearchQuery, validateIdentifier } from './sanitize-core';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
|
-
* XSS Protection - sanitize HTML content
|
|
6
|
+
* XSS Protection - sanitize HTML content (web/node, via DOMPurify).
|
|
5
7
|
*
|
|
6
8
|
* Strips all tags except a safe allowlist and removes dangerous attributes.
|
|
9
|
+
*
|
|
10
|
+
* React Native has no DOM, and bundling isomorphic-dompurify drags in jsdom,
|
|
11
|
+
* so Metro resolves the `sanitize.native.ts` sibling on native builds instead.
|
|
7
12
|
*/
|
|
8
13
|
export function sanitizeHtml(input: string): string {
|
|
9
14
|
return DOMPurify.sanitize(input, {
|
|
@@ -12,28 +17,3 @@ export function sanitizeHtml(input: string): string {
|
|
|
12
17
|
ALLOW_DATA_ATTR: false,
|
|
13
18
|
});
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validate and sanitize a search query string.
|
|
18
|
-
*
|
|
19
|
-
* Trims whitespace, escapes regex special characters, and limits length to 100 chars.
|
|
20
|
-
*/
|
|
21
|
-
export function sanitizeSearchQuery(query: string): string {
|
|
22
|
-
if (!query || typeof query !== 'string') {
|
|
23
|
-
return '';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return query
|
|
27
|
-
.trim()
|
|
28
|
-
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
29
|
-
.substring(0, 100); // Limit length
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Validate that a string is a safe SQL/API identifier.
|
|
34
|
-
*
|
|
35
|
-
* Only allows alphanumeric characters, underscores, and hyphens.
|
|
36
|
-
*/
|
|
37
|
-
export function validateIdentifier(input: string): boolean {
|
|
38
|
-
return /^[a-zA-Z0-9_-]+$/.test(input);
|
|
39
|
-
}
|