SPB Git

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%
15.9 KB · 411 lines python
Raw Blame History
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