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#!/usr/bin/env python32# =============================================================================3# VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4# -----------------------------------------------------------------------------5# File: server/services/dataDownloadService.py6#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# =============================================================================1516"""17FMP Data Download Service18Downloads financial data from FMP API and exports to various formats (CSV, XLSX, JSON, TXT)19"""2021import sys22import json23import os24import pandas as pd25import requests26from datetime import datetime27from typing import Dict, Any, Optional, List2829# Get API key from environment30FMP_API_KEY = os.environ.get('FMP_API_KEY')31FMP_BASE_URL = 'https://financialmodelingprep.com/stable'323334def call_fmp_api(endpoint: str, params: Dict[str, Any] = None) -> Any:35 """36 Make a call to the FMP API37 """38 if not FMP_API_KEY:39 raise ValueError("FMP_API_KEY environment variable not set")4041 url = f"{FMP_BASE_URL}/{endpoint}"42 api_params = {'apikey': FMP_API_KEY}4344 if params:45 api_params.update(params)4647 try:48 response = requests.get(url, params=api_params, timeout=30)49 response.raise_for_status()50 return response.json()51 except requests.exceptions.RequestException as e:52 raise Exception(f"Failed to fetch data from FMP API: {str(e)}")535455def fetch_data(data_type, params):56 """57 Fetch data from FMP API based on data_type and params58 Returns a tuple of (data, metadata)59 """60 symbol = params.get('symbol')61 period = params.get('period', 'annual')62 limit = params.get('limit', 10)63 from_date = params.get('from_date')64 to_date = params.get('to_date')6566 metadata = {67 'data_type': data_type,68 'symbol': symbol,69 'fetched_at': datetime.now().isoformat(),70 'params': params71 }7273 try:74 # Fundamental Data75 if data_type == 'company_profile':76 data = call_fmp_api('profile', {'symbol': symbol})77 metadata['description'] = f'Company Profile for {symbol}'7879 elif data_type == 'income_statement':80 data = call_fmp_api('income-statement', {'symbol': symbol, 'period': period, 'limit': limit})81 metadata['description'] = f'Income Statement for {symbol} ({period}, limit: {limit})'8283 elif data_type == 'balance_sheet':84 data = call_fmp_api('balance-sheet-statement', {'symbol': symbol, 'period': period, 'limit': limit})85 metadata['description'] = f'Balance Sheet for {symbol} ({period}, limit: {limit})'8687 elif data_type == 'cash_flow':88 data = call_fmp_api('cash-flow-statement', {'symbol': symbol, 'period': period, 'limit': limit})89 metadata['description'] = f'Cash Flow Statement for {symbol} ({period}, limit: {limit})'9091 elif data_type == 'key_metrics':92 data = call_fmp_api('key-metrics', {'symbol': symbol, 'period': period, 'limit': limit})93 metadata['description'] = f'Key Metrics for {symbol} ({period}, limit: {limit})'9495 elif data_type == 'financial_ratios':96 data = call_fmp_api('ratios', {'symbol': symbol, 'period': period, 'limit': limit})97 metadata['description'] = f'Financial Ratios for {symbol} ({period}, limit: {limit})'9899 elif data_type == 'financial_growth':100 data = call_fmp_api('financial-growth', {'symbol': symbol, 'period': period, 'limit': limit})101 metadata['description'] = f'Financial Growth for {symbol} ({period}, limit: {limit})'102103 # Market Data104 elif data_type == 'stock_quote':105 result = call_fmp_api('quote', {'symbol': symbol})106 data = result if isinstance(result, list) else [result]107 metadata['description'] = f'Stock Quote for {symbol}'108109 elif data_type == 'historical_price':110 api_params = {'symbol': symbol}111 if from_date:112 api_params['from'] = from_date113 if to_date:114 api_params['to'] = to_date115 result = call_fmp_api('historical-price-eod/full', api_params)116 # Stable API returns flat array117 data = result if isinstance(result, list) else result.get('historical', [])118 # Reverse to get chronological order119 data = list(reversed(data))120 metadata['description'] = f'Historical Prices for {symbol}'121 if from_date:122 metadata['description'] += f' from {from_date}'123 if to_date:124 metadata['description'] += f' to {to_date}'125126 elif data_type == 'intraday_price':127 interval = params.get('interval', '15min')128 result = call_fmp_api(f'historical-chart/{interval}', {'symbol': symbol})129 data = result if isinstance(result, list) else []130 metadata['description'] = f'Intraday Prices for {symbol} ({interval} interval)'131132 # Technical Indicators133 elif data_type == 'rsi':134 period_val = params.get('indicator_period', 14)135 time_period = params.get('time_period', 'daily')136 data = call_fmp_api('technical-indicators/rsi', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})137 metadata['description'] = f'RSI for {symbol} (period: {period_val}, {time_period})'138139 elif data_type == 'macd':140 time_period = params.get('time_period', 'daily')141 data = call_fmp_api('technical-indicators/macd', {'symbol': symbol, 'timeframe': time_period})142 metadata['description'] = f'MACD for {symbol} ({time_period})'143144 elif data_type == 'ema':145 period_val = params.get('indicator_period', 50)146 time_period = params.get('time_period', 'daily')147 data = call_fmp_api('technical-indicators/ema', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})148 metadata['description'] = f'EMA for {symbol} (period: {period_val}, {time_period})'149150 elif data_type == 'sma':151 period_val = params.get('indicator_period', 50)152 time_period = params.get('time_period', 'daily')153 data = call_fmp_api('technical-indicators/sma', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})154 metadata['description'] = f'SMA for {symbol} (period: {period_val}, {time_period})'155156 elif data_type == 'adx':157 period_val = params.get('indicator_period', 14)158 time_period = params.get('time_period', 'daily')159 data = call_fmp_api('technical-indicators/adx', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})160 metadata['description'] = f'ADX for {symbol} (period: {period_val}, {time_period})'161162 elif data_type == 'williams_r':163 period_val = params.get('indicator_period', 14)164 time_period = params.get('time_period', 'daily')165 data = call_fmp_api('technical-indicators/williams', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})166 metadata['description'] = f'Williams %R for {symbol} (period: {period_val}, {time_period})'167168 elif data_type == 'cci':169 period_val = params.get('indicator_period', 20)170 time_period = params.get('time_period', 'daily')171 data = call_fmp_api('technical-indicators/cci', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})172 metadata['description'] = f'CCI for {symbol} (period: {period_val}, {time_period})'173174 elif data_type == 'stochastic':175 period_val = params.get('indicator_period', 14)176 time_period = params.get('time_period', 'daily')177 data = call_fmp_api('technical-indicators/stoch', {'symbol': symbol, 'timeframe': time_period, 'periodLength': period_val})178 metadata['description'] = f'Stochastic Oscillator for {symbol} (period: {period_val}, {time_period})'179180 # News & Events181 elif data_type == 'financial_news':182 news_limit = params.get('news_limit', 20)183 data = call_fmp_api('news/stock', {'symbols': symbol, 'limit': news_limit})184 metadata['description'] = f'Financial News for {symbol} (limit: {news_limit})'185186 elif data_type == 'earnings_calendar':187 api_params = {}188 if from_date:189 api_params['from'] = from_date190 if to_date:191 api_params['to'] = to_date192 data = call_fmp_api('earnings-calendar', api_params)193 metadata['description'] = f'Earnings Calendar'194 if from_date:195 metadata['description'] += f' from {from_date}'196 if to_date:197 metadata['description'] += f' to {to_date}'198199 elif data_type == 'earnings_surprises':200 data = call_fmp_api('earnings-surprises', {'symbol': symbol})201 metadata['description'] = f'Earnings Surprises for {symbol}'202203 elif data_type == 'analyst_estimates':204 data = call_fmp_api('analyst-estimates', {'symbol': symbol, 'period': period, 'limit': limit})205 metadata['description'] = f'Analyst Estimates for {symbol} ({period})'206207 elif data_type == 'price_target':208 data = call_fmp_api('price-target', {'symbol': symbol})209 metadata['description'] = f'Analyst Price Targets for {symbol}'210211 elif data_type == 'upgrades_downgrades':212 data = call_fmp_api('grades', {'symbol': symbol})213 metadata['description'] = f'Analyst Upgrades & Downgrades for {symbol}'214215 # Dividends & Corporate Actions216 elif data_type == 'dividend_history':217 data = call_fmp_api('dividends', {'symbol': symbol})218 if isinstance(data, dict) and 'historical' in data:219 data = data['historical']220 metadata['description'] = f'Dividend History for {symbol}'221222 elif data_type == 'stock_splits':223 data = call_fmp_api('splits', {'symbol': symbol})224 if isinstance(data, dict) and 'historical' in data:225 data = data['historical']226 metadata['description'] = f'Stock Split History for {symbol}'227228 # Institutional & Insider Data229 elif data_type == 'insider_trading':230 data = call_fmp_api('insider-trading/search', {'symbol': symbol, 'limit': limit})231 metadata['description'] = f'Insider Trading for {symbol} (limit: {limit})'232233 elif data_type == 'institutional_holders':234 data = call_fmp_api('institutional-ownership/latest', {'symbol': symbol})235 metadata['description'] = f'Institutional Holders for {symbol}'236237 elif data_type == 'congressional_trading':238 api_params = {'symbol': symbol} if symbol else {}239 data = call_fmp_api('senate-trading', api_params)240 metadata['description'] = f'Congressional Trading{" for " + symbol if symbol else ""}'241242 # ESG Data243 elif data_type == 'esg_score':244 data = call_fmp_api('esg-environmental-social-governance-data', {'symbol': symbol})245 metadata['description'] = f'ESG Score for {symbol}'246247 # Macro Data248 elif data_type == 'treasury_rates':249 api_params = {}250 if from_date:251 api_params['from'] = from_date252 if to_date:253 api_params['to'] = to_date254 data = call_fmp_api('treasury', api_params)255 metadata['description'] = f'US Treasury Rates'256257 elif data_type == 'economic_calendar':258 api_params = {}259 if from_date:260 api_params['from'] = from_date261 if to_date:262 api_params['to'] = to_date263 data = call_fmp_api('economic-calendar', api_params)264 metadata['description'] = f'Economic Calendar'265266 # Forex & Commodities267 elif data_type == 'forex_quote':268 pair = params.get('pair', 'EURUSD')269 data = call_fmp_api('fx', {'symbol': pair})270 metadata['description'] = f'Forex Quote for {pair}'271272 elif data_type == 'commodity_quotes':273 data = call_fmp_api('batch-commodity-quote')274 metadata['description'] = f'Commodity Quotes'275276 else:277 return None, {'error': f'Unknown data_type: {data_type}'}278279 metadata['record_count'] = len(data) if isinstance(data, list) else 1280 return data, metadata281282 except Exception as e:283 return None, {'error': str(e)}284285286def convert_to_dataframe(data):287 """288 Convert data to pandas DataFrame289 """290 if isinstance(data, list):291 if len(data) > 0:292 return pd.DataFrame(data)293 else:294 return pd.DataFrame()295 elif isinstance(data, dict):296 return pd.DataFrame([data])297 else:298 return pd.DataFrame()299300301def export_data(data, output_format, output_path):302 """303 Export data to specified format304 """305 df = convert_to_dataframe(data)306307 if df.empty:308 raise ValueError('No data to export')309310 if output_format == 'csv':311 df.to_csv(output_path, index=False, encoding='utf-8')312313 elif output_format == 'xlsx':314 # Use openpyxl engine for better compatibility315 df.to_excel(output_path, index=False, engine='openpyxl')316317 elif output_format == 'json':318 # Export as pretty JSON319 df.to_json(output_path, orient='records', indent=2)320321 elif output_format == 'txt':322 # Export as formatted text table323 with open(output_path, 'w', encoding='utf-8') as f:324 f.write(df.to_string(index=False))325326 else:327 raise ValueError(f'Unsupported format: {output_format}')328329330def main():331 """332 Main execution function333 """334 try:335 # Read input from stdin336 input_data = sys.stdin.read()337 params = json.loads(input_data)338339 data_type = params.get('data_type')340 output_format = params.get('format', 'csv').lower()341342 if not data_type:343 result = {344 'success': False,345 'error': 'data_type is required'346 }347 print(json.dumps(result))348 sys.exit(1)349350 # Validate format351 valid_formats = ['csv', 'xlsx', 'json', 'txt']352 if output_format not in valid_formats:353 result = {354 'success': False,355 'error': f'Invalid format. Must be one of: {", ".join(valid_formats)}'356 }357 print(json.dumps(result))358 sys.exit(1)359360 # Fetch data361 data, metadata = fetch_data(data_type, params)362363 if data is None:364 result = {365 'success': False,366 'error': metadata.get('error', 'Failed to fetch data')367 }368 print(json.dumps(result))369 sys.exit(1)370371 # Generate filename372 timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')373 symbol = params.get('symbol', 'data')374 filename = f"{symbol}_{data_type}_{timestamp}.{output_format}"375376 # Create downloads directory if it doesn't exist377 downloads_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'downloads')378 os.makedirs(downloads_dir, exist_ok=True)379380 output_path = os.path.join(downloads_dir, filename)381382 # Export data383 export_data(data, output_format, output_path)384385 # Get file size386 file_size = os.path.getsize(output_path)387388 # Return success result389 result = {390 'success': True,391 'filename': filename,392 'filepath': output_path,393 'format': output_format,394 'file_size': file_size,395 'metadata': metadata396 }397398 print(json.dumps(result))399400 except Exception as e:401 result = {402 'success': False,403 'error': str(e)404 }405 print(json.dumps(result))406 sys.exit(1)407408409if __name__ == '__main__':410 main()411