You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

18 KiB

Stock Oracle Python Client Documentation

Installation

Option 1: Direct File Usage

# 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)

# Will be available on PyPI
pip install stock-oracle-client

Option 3: Development Installation

# Clone the repository
git clone <repo-url>
cd stock-oracle

# Install client dependencies
pip install -r requirements-client.txt

# Run examples
python examples/client_usage.py

Quick Start

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

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

# 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

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

# 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

# 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

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']}")

# ── 과거 분봉 (SIP 피드, DB 저장, 어제까지) ──────────────────────────
# 백테스트, 과거 OHLCV 분석용. 거래량 100% 정확.
hist = requests.get(f"{BASE}/alpaca/intraday", params={
    "tickers": "AAPL,MSFT,NVDA",
    "interval": "5m",
    "start_date": "2025-01-02",
    "end_date": "2025-01-31",
}).json()
print(f"Historical bars: {hist['count']} tickers")
for ticker, bars in hist["bars"].items():
    print(f"  {ticker}: {len(bars)} bars")

# ── 당일 실시간 분봉 (IEX 피드, 오늘만) ─────────────────────────────
# 당일 ORB 전략, 실시간 장 중 모니터링.
# 거래량은 실제의 2~5% (IEX 거래소 거래만 집계).
today = requests.get(f"{BASE}/alpaca/intraday/today", params={
    "tickers": "AAPL,MSFT,NVDA",
    "interval": "5m",
}).json()
for ticker, bars in today["bars"].items():
    if bars:
        latest = bars[-1]
        print(f"  {ticker}: last bar {latest['timestamp']} close={latest['close']}")

# ── 실시간 스냅샷 (IEX 피드, 캐시 없음) ─────────────────────────────
# 단일 종목도 tickers= 쿼리 파라미터로 조회 가능.
snap = requests.get(f"{BASE}/alpaca/snapshot", params={"tickers": "SPY,QQQ,AAPL"}).json()
for s in snap["snapshots"]:
    print(f"  {s['ticker']}: {s['price']} ({s['change_pct']:+.2f}%) bid={s['bid']} ask={s['ask']}")

7. FINRA Short Volume

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")

8. News v2 — Multi-source Headlines & Session Aggregates

Multi-source structured news ingest (Alpaca News, StockTwits, Finnhub) with unified categorization and PIT-safe session aggregates. Distinct from the on-demand get_news_social_data() aggregator above.

from stock_oracle_client import StockOracleClient

client = StockOracleClient(base_url="http://localhost:18001")

# 1. Coverage probe — confirm archive depth before backtest window selection
coverage = client.get_news_coverage(source="alpaca_benzinga", symbol="AAPL")
print(f"AAPL Alpaca News: {coverage['ingested_count']} headlines, "
      f"{coverage['earliest']}{coverage['latest']}")

# 2. Raw headlines — last 24 hours for two tickers
headlines = client.get_news_headlines(
    symbols=["AAPL", "MSFT"],
    start="2026-04-24T00:00:00Z",
    end="2026-04-25T00:00:00Z",
    sources=["alpaca_benzinga", "finnhub"],
    limit=100,
)
for h in headlines["items"][:3]:
    print(f"[{h['source']}] {h['published_at']} {h['ticker']}: {h['headline']}")
    print(f"   categories: {h['categories']}")

# 3. Single-ticker session aggregate (V49 use case)
agg = client.get_news_session_aggregate(
    symbol="AAPL",
    session_date="2026-04-25",
    window="premarket",  # premarket | intraday | post | full_session
)
print(f"AAPL premarket: {agg['headline_count']} headlines, "
      f"sentiment={agg['sentiment_recency_weighted']}, "
      f"categories={agg['category_counts']}")

# 4. Batch — fithia2's hot path (one call per session, then disk-cache)
batch = client.get_news_session_aggregate_batch(
    session_date="2026-04-25",
    window="premarket",
    symbols=["AAPL", "MSFT", "NVDA", "TSLA", "GOOGL"],
    sources=["alpaca_benzinga", "stocktwits"],
)
for ticker, item in batch["items"].items():
    soc = item["social"]
    print(f"{ticker}: count={item['headline_count']}, "
          f"social_msgs={soc['message_count']}, "
          f"bull_bear_ratio={soc['bull_bear_ratio']}")

Method reference:

Method Returns
get_news_headlines(symbols=None, start=None, end=None, sources=None, limit=100, cursor=None) {items: [...], next_cursor: str|None}
get_news_session_aggregate(symbol, session_date, window="premarket", sources=None, force_refresh=False) session aggregate dict
get_news_session_aggregate_batch(session_date, window, symbols, sources=None) {items: {ticker: aggregate}}
get_news_coverage(source, symbol=None) {source, symbol, earliest, latest, ingested_count}

session_date strings are ET dates (YYYY-MM-DD). Aggregates filter ingested_at <= window_end_utc so backtests don't see lookahead headlines. Tickers with no matching headlines are returned as zero-count rows (not omitted).

Advanced Usage

Error Handling

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

# 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

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

# 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:

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:

# 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:

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:

# 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:

client = StockOracleClient(
    "http://localhost:18001",
    auto_retry=True,
    max_retries=3
)

Examples

Complete Example: Portfolio Analysis

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

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

    # 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

    # Increase timeout for large requests
    client = StockOracleClient("http://localhost:18001", timeout=60)
    

API Response Structure

Financial Data Response

{
  "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

{
  "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

{
  "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
  • Examples: See examples/ directory