# Stock Oracle Python Client Documentation ## Installation ### Option 1: Direct File Usage ```bash # Copy the client file to your project cp stock_oracle_client.py /path/to/your/project/ # Install dependencies pip install requests python-dateutil ``` ### Option 2: Install as Package (Future) ```bash # Will be available on PyPI pip install stock-oracle-client ``` ### Option 3: Development Installation ```bash # Clone the repository git clone cd stock-oracle # Install client dependencies pip install -r requirements-client.txt # Run examples python examples/client_usage.py ``` ## Quick Start ```python from stock_oracle_client import StockOracleClient # Initialize client client = StockOracleClient("http://localhost:18001") # Get financial data financial = client.get_financial_data("AAPL", period="1y") print(f"Company: {financial['company']['name']}") # Get price data prices = client.get_price_data("MSFT", period="3m") print(f"Latest close: ${prices['price_data'][-1]['close']:.2f}") # Get ETF holdings etf = client.get_etf_holdings("QQQ") print(f"Holdings: {etf['data']['holdings_count']}") ``` ## Core Features ### 1. Client Initialization ```python from stock_oracle_client import StockOracleClient # Basic initialization client = StockOracleClient("http://localhost:18001") # With options client = StockOracleClient( base_url="http://localhost:18001", api_key=None, # For future authentication timeout=30, # Request timeout in seconds auto_retry=True, # Automatic retry on failure max_retries=3 # Maximum retry attempts ) ``` ### 2. Financial Data ```python # Using period (recommended) data = client.get_financial_data("AAPL", period="1y") # Using date range from datetime import date data = client.get_financial_data( "AAPL", start_date=date(2023, 1, 1), end_date=date(2023, 12, 31), period_type=PeriodType.QUARTERLY ) # Using quarters data = client.get_financial_data( "AAPL", quarters=["2024Q1", "2024Q2"], include_metrics=True ) ``` ### 3. Price Data ```python from stock_oracle_client import PriceInterval # Daily prices for last year prices = client.get_price_data( "TSLA", period="1y", interval=PriceInterval.ONE_DAY ) # Weekly prices for specific range prices = client.get_price_data( "TSLA", start_date="2024-01-01", end_date="2024-06-30", interval=PriceInterval.ONE_WEEK ) ``` ### 4. ETF Holdings ```python # Current holdings etf = client.get_etf_holdings("QQQ") # Historical holdings etf = client.get_etf_holdings("SPY", as_of_date="2023-12-31") # Without detailed holdings (faster) etf = client.get_etf_holdings("ARKK", include_holdings=False) # With automatic fallback try: etf = client.get_etf_holdings_with_fallback("QQQM", "2020-01-01") except ETFDataNotAvailableError as e: print(f"Error: {e}") if e.availability_info: print(f"Available from: {e.availability_info['available_date_range']['start']}") ``` ### 5. Bulk Operations ```python # Bulk financial data bulk_financial = client.get_bulk_financial_data( ["AAPL", "MSFT", "GOOGL"], period="6m", period_type=PeriodType.QUARTERLY ) # Bulk price data bulk_prices = client.get_bulk_price_data( ["TSLA", "NIO", "RIVN"], period="1m", interval=PriceInterval.ONE_DAY ) # Bulk ETF holdings bulk_etf = client.get_bulk_etf_holdings( ["QQQ", "SPY", "IWM"], include_holdings=False # For performance ) ``` ### 6. Alpaca Market Data ```python import requests BASE = "http://localhost:18001/api/v1" # Check Alpaca connection status = requests.get(f"{BASE}/alpaca/status").json() print(f"Alpaca configured: {status['configured']}") # Get daily bars with DB storage resp = requests.get(f"{BASE}/alpaca/data/AAPL", params={ "interval": "1d", "start_date": "2025-01-01", "end_date": "2025-01-31", }).json() print(f"Alpaca bars: {resp['metadata']['data_points']} data points") # Get raw bars (no DB) bars = requests.get(f"{BASE}/alpaca/bars/TSLA", params={ "interval": "1d", "start_date": "2025-03-01", "end_date": "2025-03-10", }).json() for bar in bars["bars"][:3]: print(f" {bar['timestamp']}: close={bar['close']}, vwap={bar['vwap']}") # Intraday candles candles = requests.get(f"{BASE}/alpaca/intraday/NVDA", params={ "interval": "1h", "start_date": "2025-03-10", "end_date": "2025-03-10", }).json() print(f"Intraday candles: {candles['count']}") ``` ### 7. FINRA Short Volume ```python import requests BASE = "http://localhost:18001/api/v1" # Get short volume (auto-ingests if missing) sv = requests.get(f"{BASE}/finra/short-volume/AAPL", params={"days": 30}).json() print(f"AAPL short volume entries: {sv['total_count']}") for entry in sv["entries"][:3]: print(f" {entry['date']}: ratio={entry['short_ratio']:.2%}") # Get short ratio history (aggregated across markets) ratio = requests.get(f"{BASE}/finra/short-ratio/AAPL", params={"days": 60}).json() print(f"Average short ratio: {ratio['avg_short_ratio']:.2%}") # Manual ingest ingest = requests.post(f"{BASE}/finra/admin/ingest", params={"date": "2025-03-10"}).json() print(f"Ingested: {ingest['records_ingested']} records") ``` ## Advanced Usage ### Error Handling ```python from stock_oracle_client import ( StockOracleClient, StockOracleAPIError, ETFDataNotAvailableError ) client = StockOracleClient("http://localhost:18001") try: # Try to get data data = client.get_financial_data("INVALID") except StockOracleAPIError as e: print(f"API Error: {e}") print(f"Status Code: {e.status_code}") print(f"Response: {e.response_data}") except ETFDataNotAvailableError as e: print(f"ETF Data Not Available: {e}") if e.availability_info: print(f"Launch Date: {e.availability_info.get('etf_launch_date')}") print(f"Available Range: {e.availability_info.get('available_date_range')}") ``` ### Availability Checking ```python # Check if ETF data is available availability = client.check_etf_availability("QQQM", "2020-01-01") if not availability.get('exists_for_date'): print(f"ETF didn't exist on requested date") print(f"Launch date: {availability.get('etf_launch_date')}") print(f"First available: {availability['available_date_range']['start']}") ``` ### Using Enums ```python from stock_oracle_client import PriceInterval, PeriodType # Price intervals intervals = [ PriceInterval.ONE_MINUTE, PriceInterval.FIVE_MINUTES, PriceInterval.ONE_HOUR, PriceInterval.ONE_DAY, PriceInterval.ONE_WEEK, PriceInterval.ONE_MONTH ] # Period types period_types = [ PeriodType.QUARTERLY, PeriodType.ANNUAL, PeriodType.ALL ] ``` ### Utility Methods ```python # Validate ticker is_valid = client.validate_ticker("AAPL") # Returns True is_valid = client.validate_ticker("INVALID123") # Returns False # Get latest filing date filing_date = client.get_latest_filing_date("MSFT") # Search tickers (client-side) results = client.search_tickers("AA") # Returns ["AAPL", ...] # Get supported ETFs supported = client.get_supported_etfs() print(f"Total ETFs: {supported['total_etfs']}") # Get data catalog catalog = client.get_data_catalog() ``` ## Convenience Functions For quick one-off requests without creating a client instance: ```python from stock_oracle_client import get_financial_data, get_price_data, get_etf_holdings # Quick financial data financial = get_financial_data("AAPL", period="1y") # Quick price data prices = get_price_data("TSLA", period="3m") # Quick ETF holdings etf = get_etf_holdings("QQQ", as_of_date="2024-01-01") ``` ## Best Practices ### 1. Use Period Strings Period strings are the most convenient way to specify time ranges: - `"1d"`, `"5d"`, `"1m"`, `"3m"`, `"6m"`, `"1y"`, `"2y"`, `"5y"`, `"10y"`, `"ytd"`, `"max"` ### 2. Batch Requests Use bulk endpoints when fetching data for multiple tickers: ```python # Good - single bulk request bulk_data = client.get_bulk_financial_data(["AAPL", "MSFT", "GOOGL"]) # Bad - multiple individual requests data1 = client.get_financial_data("AAPL") data2 = client.get_financial_data("MSFT") data3 = client.get_financial_data("GOOGL") ``` ### 3. Handle ETF Availability Always check availability when requesting historical ETF data: ```python def get_etf_data_safe(client, ticker, date): try: return client.get_etf_holdings(ticker, as_of_date=date) except ETFDataNotAvailableError as e: if e.availability_info: # Try with earliest available date start_date = e.availability_info['available_date_range']['start'] return client.get_etf_holdings(ticker, as_of_date=start_date) raise ``` ### 4. Use Session Reuse The client automatically reuses HTTP sessions for better performance: ```python # Good - reuse client instance client = StockOracleClient("http://localhost:18001") for ticker in tickers: data = client.get_financial_data(ticker) # Bad - create new client each time for ticker in tickers: client = StockOracleClient("http://localhost:18001") data = client.get_financial_data(ticker) ``` ### 5. Error Recovery Enable auto-retry for better reliability: ```python client = StockOracleClient( "http://localhost:18001", auto_retry=True, max_retries=3 ) ``` ## Examples ### Complete Example: Portfolio Analysis ```python from stock_oracle_client import StockOracleClient, PeriodType from datetime import datetime, timedelta def analyze_portfolio(tickers, client): """Analyze a portfolio of stocks and ETFs""" results = { 'stocks': {}, 'etfs': {}, 'errors': [] } for ticker in tickers: try: # Try as ETF first etf_data = client.get_etf_holdings(ticker, include_holdings=False) if etf_data['success']: results['etfs'][ticker] = { 'type': 'ETF', 'holdings_count': etf_data['data']['holdings_count'], 'as_of_date': etf_data['as_of_date'] } continue except: pass try: # Try as stock financial = client.get_financial_data( ticker, period="1y", period_type=PeriodType.QUARTERLY ) if financial['financial_data']: latest = financial['financial_data'][0] results['stocks'][ticker] = { 'type': 'Stock', 'company': financial['company']['name'], 'latest_filing': latest['date'], 'pe_ratio': latest.get('metrics', {}).get('pe_ratio'), 'market_cap': latest.get('market_cap') } except Exception as e: results['errors'].append({ 'ticker': ticker, 'error': str(e) }) return results # Usage client = StockOracleClient("http://localhost:18001") portfolio = ["AAPL", "QQQ", "MSFT", "SPY", "TSLA", "ARKK"] analysis = analyze_portfolio(portfolio, client) print("Stocks:") for ticker, data in analysis['stocks'].items(): print(f" {ticker}: {data['company']}") print("\nETFs:") for ticker, data in analysis['etfs'].items(): print(f" {ticker}: {data['holdings_count']} holdings") ``` ### Data Export Example ```python import pandas as pd from stock_oracle_client import StockOracleClient client = StockOracleClient("http://localhost:18001") # Get data financial = client.get_financial_data("AAPL", period="2y") # Convert to DataFrame df = pd.DataFrame(financial['financial_data']) # Export to CSV df.to_csv("aapl_financial_data.csv", index=False) # Export to Excel df.to_excel("aapl_financial_data.xlsx", index=False) print(f"Exported {len(df)} records") ``` ## Troubleshooting ### Common Issues 1. **Connection Error** ```python # Check if API is running try: health = client.get_health() except: print("API is not accessible") ``` 2. **ETF Data Not Available** - Check availability first using `check_etf_availability()` - Use `get_etf_holdings_with_fallback()` for automatic fallback - Check the `availability_info` in the exception 3. **Rate Limiting** - Add delays between requests if needed - Use bulk endpoints for multiple tickers - Enable caching on the server side 4. **Timeout Issues** ```python # Increase timeout for large requests client = StockOracleClient("http://localhost:18001", timeout=60) ``` ## API Response Structure ### Financial Data Response ```json { "ticker": "AAPL", "company": { "name": "Apple Inc.", "sector": "Technology", "industry": "Consumer Electronics" }, "financial_data": [ { "date": "2024-03-31", "revenue": 119575000000, "net_income": 23636000000, "metrics": { "pe_ratio": 26.5, "return_on_equity": 1.47, "gross_margin": 0.455 } } ] } ``` ### ETF Holdings Response ```json { "ticker": "QQQ", "as_of_date": "2024-12-31", "success": true, "data": { "filing_info": { "filing_date": "2024-12-31", "cik": "1067839", "total_value": 250000000000, "total_holdings": 102 }, "holdings": [ { "name": "MICROSOFT CORP", "cusip": "594918104", "value": 25000000000, "shares": 50000000, "percentage": 10.0 } ], "holdings_count": 102 }, "availability": null } ``` ### Error Response with Availability ```json { "ticker": "QQQM", "as_of_date": "2020-01-01", "success": false, "error": "ETF QQQM did not exist on 2020-01-01", "availability": { "exists_for_date": false, "etf_launch_date": "2020-10-13", "first_nport_date": "2021-01-31", "available_date_range": { "start": "2021-01-31", "end": "present" } } } ``` ## Version History ### v2.0.0 (Current) - Added comprehensive ETF holdings support - Improved error handling with custom exceptions - Added availability checking for ETF data - Bulk operations for all data types - Enum support for intervals and period types - Automatic retry logic - Utility methods for validation and search ### v3.0.0 - Added Alpaca Market Data integration (bars, intraday, DB storage) - Added FINRA Short Volume data (auto-ingest, short ratio history) - New data sources: Alpaca (optional API key), FINRA (public CDN) ### v1.0.0 - Initial release - Basic financial and price data retrieval - Period string support - Bulk financial data ## Support For issues, questions, or contributions: - GitHub Issues: [Report bugs or request features] - Documentation: [API Documentation](../README.md) - Examples: See `examples/` directory