spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/services/financialDataService.ts6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import axios from 'axios';1819const FINANCIAL_DATA_API_KEY = process.env.FINANCIAL_DATA_API_KEY || 'a44ec329c25a58c0bff4947cb649eec3';20const FINANCIAL_DATA_BASE_URL = 'https://financialdata.net/api/v1';2122// Option Chain Interface23export interface OptionContract {24 identifier: string;25 option_type: string; // 'call' or 'put'26 strike: number;27 expiration_date: string;28 last_price?: number;29 bid?: number;30 ask?: number;31 volume?: number;32 open_interest?: number;33 implied_volatility?: number;34}3536export interface OptionChain {37 symbol: string;38 options: OptionContract[];39 timestamp?: string;40}4142// Option Prices Interface43export interface OptionPrice {44 identifier: string;45 symbol: string;46 option_type: string;47 strike: number;48 expiration_date: string;49 last_price: number;50 bid: number;51 ask: number;52 bid_size?: number;53 ask_size?: number;54 volume: number;55 open_interest: number;56 implied_volatility: number;57 in_the_money: boolean;58 intrinsic_value: number;59 time_value: number;60 last_trade_date?: string;61 timestamp?: string;62}6364// Option Greeks Interface65export interface OptionGreeks {66 identifier: string;67 symbol: string;68 option_type: string;69 strike: number;70 expiration_date: string;71 delta: number;72 gamma: number;73 theta: number;74 vega: number;75 rho: number;76 implied_volatility: number;77 theoretical_price?: number;78 timestamp?: string;79}8081/**82 * Generic request function for Financial Data API83 */84async function financialDataRequest<T>(endpoint: string, params: Record<string, any> = {}): Promise<T> {85 if (!FINANCIAL_DATA_API_KEY) {86 throw new Error('FINANCIAL_DATA_API_KEY not configured');87 }8889 try {90 console.log(`[Financial Data API] Requesting ${endpoint} with params:`, JSON.stringify(params));9192 const response = await axios.get(`${FINANCIAL_DATA_BASE_URL}${endpoint}`, {93 params: {94 ...params,95 key: FINANCIAL_DATA_API_KEY,96 },97 timeout: 15000,98 });99100 console.log(`[Financial Data API] Response for ${endpoint}:`,101 Array.isArray(response.data) ? `Array with ${response.data.length} items` : typeof response.data102 );103104 return response.data;105 } catch (error) {106 console.error(`[Financial Data API] ERROR for ${endpoint}:`, error);107 if (axios.isAxiosError(error) && error.response) {108 console.error(`[Financial Data API] Response status: ${error.response.status}`);109 console.error(`[Financial Data API] Response data:`, JSON.stringify(error.response.data));110 throw new Error(`Financial Data API error: ${error.response.status} - ${error.response.statusText}. Details: ${JSON.stringify(error.response.data)}`);111 }112 throw new Error(`Failed to fetch from Financial Data API: ${(error as Error).message}`);113 }114}115116/**117 * Get option chain for a stock symbol118 * Returns all available option contracts (calls and puts) with their identifiers119 *120 * @param symbol - Stock ticker symbol (e.g., "AAPL", "MSFT")121 * @returns Option chain with all available contracts122 */123export async function getOptionChain(symbol: string): Promise<OptionChain> {124 console.log(`[getOptionChain] Fetching option chain for symbol: ${symbol}`);125126 const data = await financialDataRequest<any>('/option-chain', { identifier: symbol });127128 // Map API response to our interface129 // API returns: contract_name, put_or_call, strike_price, expiration_date130 const rawOptions = data.options || data;131132 console.log(`[getOptionChain] Processing ${rawOptions.length} option contracts`);133134 const options = rawOptions.map((opt: any, index: number) => {135 // Log first 3 identifiers to understand the format136 if (index < 3) {137 console.log(`[getOptionChain] Sample identifier #${index + 1}:`, opt.contract_name);138 }139140 return {141 identifier: opt.contract_name, // Map contract_name to identifier142 option_type: opt.put_or_call?.toLowerCase() || 'call', // Convert "Call"/"Put" to "call"/"put"143 strike: opt.strike_price || 0,144 expiration_date: opt.expiration_date || '',145 // Optional fields that may not be in the chain response146 last_price: opt.last_price,147 bid: opt.bid,148 ask: opt.ask,149 volume: opt.volume,150 open_interest: opt.open_interest,151 implied_volatility: opt.implied_volatility152 };153 });154155 console.log(`[getOptionChain] Successfully mapped ${options.length} option contracts`);156157 return {158 symbol: symbol,159 options: options,160 timestamp: data.timestamp || new Date().toISOString()161 };162}163164/**165 * Get detailed option prices for a specific option contract166 *167 * @param identifier - Option contract identifier (e.g., "MSFT250417C00400000")168 * Format: SYMBOL + YYMMDD + C/P + Strike*1000169 * @returns Detailed pricing information for the option170 */171export async function getOptionPrices(identifier: string): Promise<OptionPrice> {172 console.log(`[getOptionPrices] Fetching prices for identifier: ${identifier}`);173174 // Validate identifier format before making request175 const match = identifier.match(/^([A-Z]+)(\d{6})([CP])(\d{8})$/);176 if (!match) {177 console.warn(`[getOptionPrices] Invalid identifier format: ${identifier}`);178 console.warn(`[getOptionPrices] Expected format: SYMBOL + YYMMDD + C/P + Strike*1000 (e.g., "AAPL280121C00005000")`);179 }180181 const data = await financialDataRequest<any>('/option-prices', { identifier });182183 // API returns an array of historical prices, get the most recent one184 let latestData: any;185 if (Array.isArray(data)) {186 console.log(`[getOptionPrices] Received array with ${data.length} items`);187 if (data.length === 0) {188 throw new Error(189 `No price data found for option contract: ${identifier}. ` +190 `This may indicate that the contract identifier is invalid or the option has no recent trading data. ` +191 `Please verify the identifier format and try again.`192 );193 }194 // Sort by date descending and take the most recent195 latestData = data.sort((a: any, b: any) => {196 const dateA = new Date(a.date || 0).getTime();197 const dateB = new Date(b.date || 0).getTime();198 return dateB - dateA; // Descending order (most recent first)199 })[0];200 console.log(`[getOptionPrices] Using most recent data from: ${latestData.date}`);201 } else {202 console.log(`[getOptionPrices] Received single object`);203 latestData = data;204 }205206 // Parse identifier to extract symbol, option_type, strike, and expiration207 // Format: SYMBOL + YYMMDD + C/P + Strike*1000 (e.g., "AAPL280121C00005000")208 // Note: match was already computed above during validation209 const symbol = match ? match[1] : identifier.substring(0, 4);210 const optionType = match ? (match[3] === 'C' ? 'call' : 'put') : 'call';211 const strike = match ? parseInt(match[4]) / 1000 : 0;212 const expirationDate = match ? `20${match[2].substring(0, 2)}-${match[2].substring(2, 4)}-${match[2].substring(4, 6)}` : '';213214 // Calculate derived fields if not present215 const lastPrice = latestData.close || latestData.last_price || 0;216 const bid = latestData.bid || lastPrice * 0.99; // Estimate if not available217 const ask = latestData.ask || lastPrice * 1.01; // Estimate if not available218 const intrinsicValue = optionType === 'call'219 ? Math.max(0, lastPrice - strike)220 : Math.max(0, strike - lastPrice);221 const timeValue = Math.max(0, lastPrice - intrinsicValue);222223 return {224 identifier: latestData.contract_name || identifier,225 symbol: symbol,226 option_type: optionType,227 strike: strike,228 expiration_date: expirationDate,229 last_price: lastPrice,230 bid: bid,231 ask: ask,232 bid_size: latestData.bid_size,233 ask_size: latestData.ask_size,234 volume: latestData.volume || 0,235 open_interest: latestData.open_interest || 0,236 implied_volatility: latestData.implied_volatility || 0,237 in_the_money: (optionType === 'call' && lastPrice > strike) || (optionType === 'put' && lastPrice < strike),238 intrinsic_value: intrinsicValue,239 time_value: timeValue,240 last_trade_date: latestData.date,241 timestamp: latestData.timestamp || new Date().toISOString()242 };243}244245/**246 * Get option Greeks for a specific option contract247 * Greeks measure different risk sensitivities of options248 *249 * @param identifier - Option contract identifier (e.g., "MSFT250417C00400000")250 * @returns Greek values (Delta, Gamma, Theta, Vega, Rho) for the option251 */252export async function getOptionGreeks(identifier: string): Promise<OptionGreeks> {253 console.log(`[getOptionGreeks] Fetching Greeks for identifier: ${identifier}`);254255 // Validate identifier format before making request256 const match = identifier.match(/^([A-Z]+)(\d{6})([CP])(\d{8})$/);257 if (!match) {258 console.warn(`[getOptionGreeks] Invalid identifier format: ${identifier}`);259 console.warn(`[getOptionGreeks] Expected format: SYMBOL + YYMMDD + C/P + Strike*1000 (e.g., "AAPL280121C00005000")`);260 }261262 const data = await financialDataRequest<any>('/option-greeks', { identifier });263264 // API returns an array of historical Greeks, get the most recent one265 let latestData: any;266 if (Array.isArray(data)) {267 console.log(`[getOptionGreeks] Received array with ${data.length} items`);268 if (data.length === 0) {269 throw new Error(270 `No Greeks data found for option contract: ${identifier}. ` +271 `This may indicate that the contract identifier is invalid or the option has no recent trading data. ` +272 `Please verify the identifier format and try again.`273 );274 }275 // Sort by date descending and take the most recent276 latestData = data.sort((a: any, b: any) => {277 const dateA = new Date(a.date || 0).getTime();278 const dateB = new Date(b.date || 0).getTime();279 return dateB - dateA; // Descending order (most recent first)280 })[0];281 console.log(`[getOptionGreeks] Using most recent data from: ${latestData.date}`);282 } else {283 console.log(`[getOptionGreeks] Received single object`);284 latestData = data;285 }286287 // Parse identifier to extract symbol, option_type, strike, and expiration288 // Format: SYMBOL + YYMMDD + C/P + Strike*1000 (e.g., "AAPL280121C00005000")289 // Note: match was already computed above during validation290 const symbol = match ? match[1] : identifier.substring(0, 4);291 const optionType = match ? (match[3] === 'C' ? 'call' : 'put') : 'call';292 const strike = match ? parseInt(match[4]) / 1000 : 0;293 const expirationDate = match ? `20${match[2].substring(0, 2)}-${match[2].substring(2, 4)}-${match[2].substring(4, 6)}` : '';294295 return {296 identifier: latestData.contract_name || identifier,297 symbol: symbol,298 option_type: optionType,299 strike: strike,300 expiration_date: expirationDate,301 delta: latestData.delta || 0,302 gamma: latestData.gamma || 0,303 theta: latestData.theta || 0,304 vega: latestData.vega || 0,305 rho: latestData.rho || 0,306 implied_volatility: latestData.implied_volatility || 0,307 theoretical_price: latestData.theoretical_price,308 timestamp: latestData.date || new Date().toISOString()309 };310}311