fin-infra provides unified interfaces for connecting to brokerage accounts for automated trading, portfolio management, and position tracking. The brokerage integration supports paper trading (sandbox) and live trading with built-in safety mechanisms.
⚠️ Important Disclaimers
TRADING INVOLVES SUBSTANTIAL RISK: This software is provided for educational and development purposes. Real trading involves risk of loss. Paper trading results DO NOT guarantee real trading success.
PAPER TRADING FIRST: Always test with paper trading before using live mode. fin-infra defaults to paper trading mode for safety.
NOT FINANCIAL ADVICE: This library does not provide investment advice. You are responsible for all trading decisions and their consequences.
REGULATORY COMPLIANCE: Ensure your trading application complies with all applicable regulations (SEC, FINRA, etc.) in your jurisdiction.
Supported Providers
Current
- Alpaca (✅ Implemented)
- Commission-free trading API
- Full paper trading environment
- Real-time market data
- Order management (market, limit, stop, stop-limit)
- Position tracking
- Portfolio history
Coming Soon
- Interactive Brokers: Institutional-grade API (research pending)
- TD Ameritrade: thinkorswim API (research pending)
Quick Setup
Zero-Config (Paper Trading)
from fin_infra.brokerage import easy_brokerage
# Defaults to paper trading mode (safe for testing)
# Reads ALPACA_PAPER_API_KEY and ALPACA_PAPER_SECRET_KEY from environment
brokerage = easy_brokerage()
# Verify it is in paper mode
print(f"Mode: {brokerage.mode}") # Output: "paper"Explicit Configuration
from fin_infra.brokerage import easy_brokerage
# Paper trading (explicit)
brokerage = easy_brokerage(
provider="alpaca",
mode="paper",
api_key="YOUR_PAPER_API_KEY",
api_secret="YOUR_PAPER_SECRET_KEY"
)
# Live trading (requires explicit mode="live")
# WARNING: This uses real money!
brokerage = easy_brokerage(
provider="alpaca",
mode="live", # Must explicitly set to "live"
api_key="YOUR_LIVE_API_KEY",
api_secret="YOUR_LIVE_SECRET_KEY"
)Environment Variables
# Paper trading credentials (default)
export ALPACA_PAPER_API_KEY="your_paper_api_key"
export ALPACA_PAPER_SECRET_KEY="your_paper_secret_key"
# Live trading credentials (opt-in)
export ALPACA_LIVE_API_KEY="your_live_api_key"
export ALPACA_LIVE_SECRET_KEY="your_live_secret_key"FastAPI Integration
Basic Setup
from svc_infra.api.fastapi.ease import easy_service_app
from svc_infra.logging import setup_logging
from fin_infra.brokerage import add_brokerage
# Setup backend (svc-infra)
setup_logging()
app = easy_service_app(name="TradingAPI")
# Wire brokerage provider (fin-infra)
# Defaults to paper trading mode
brokerage = add_brokerage(app)
# Routes automatically mounted at /brokerage/*
# - GET /brokerage/account
# - GET /brokerage/positions
# - GET /brokerage/positions/{symbol}
# - DELETE /brokerage/positions/{symbol}
# - POST /brokerage/orders
# - GET /brokerage/orders
# - GET /brokerage/orders/{order_id}
# - DELETE /brokerage/orders/{order_id}
# - GET /brokerage/portfolio/historyCustom Prefix
brokerage = add_brokerage(app, prefix="/api/v1/trading")
# Routes now at /api/v1/trading/*With Provider Instance
from fin_infra.brokerage import easy_brokerage, add_brokerage
# Create provider with custom config
brokerage_provider = easy_brokerage(
provider="alpaca",
mode="paper", # Always start with paper!
timeout=30
)
# Wire to FastAPI
brokerage = add_brokerage(app, provider=brokerage_provider)Next Steps
- Market Data Integration - Get real-time quotes for trading decisions
- Banking Integration - Link bank accounts for funding
- Crypto Data Integration - Trade cryptocurrencies
Support & Resources
- Alpaca Docs: https://alpaca.markets/docs/
- Paper Trading Signup: https://alpaca.markets/docs/trading/paper-trading/
- API Reference: https://alpaca.markets/docs/api-references/trading-api/
- Alpaca Community: https://forum.alpaca.markets/
Core Operations
1. Get Account Information
# Get account details (buying power, equity, cash)
account = brokerage.get_account()
print(f"Account ID: {account['id']}")
print(f"Account Number: {account['account_number']}")
print(f"Status: {account['status']}")
print(f"Buying Power: ${account['buying_power']}")
print(f"Cash: ${account['cash']}")
print(f"Portfolio Value: ${account['portfolio_value']}")
print(f"Equity: ${account['equity']}")
print(f"Pattern Day Trader: {account['pattern_day_trader']}")2. Submit Orders
Market Order (Buy)
order = brokerage.submit_order(
symbol="AAPL",
qty=10,
side="buy",
type="market",
time_in_force="day"
)
print(f"Order ID: {order['id']}")
print(f"Status: {order['status']}") # new, accepted, filled, etc.
print(f"Filled: {order['filled_qty']} shares")Limit Order (Sell)
order = brokerage.submit_order(
symbol="TSLA",
qty=5,
side="sell",
type="limit",
limit_price=250.00,
time_in_force="gtc" # good-til-canceled
)Stop Loss Order
order = brokerage.submit_order(
symbol="MSFT",
qty=20,
side="sell",
type="stop",
stop_price=380.00,
time_in_force="day"
)Stop-Limit Order
order = brokerage.submit_order(
symbol="GOOGL",
qty=3,
side="buy",
type="stop_limit",
limit_price=145.00,
stop_price=143.00,
time_in_force="day"
)3. Manage Orders
List Orders
# Get all orders
orders = brokerage.list_orders()
# Filter by status
open_orders = brokerage.list_orders(status="open")
filled_orders = brokerage.list_orders(status="filled")
# Limit results
recent_orders = brokerage.list_orders(limit=10)
for order in orders:
print(f"{order['symbol']}: {order['side']} {order['qty']} @ {order['type']}")
print(f" Status: {order['status']}")
print(f" Filled: {order['filled_qty']}/{order['qty']}")Get Order Details
order = brokerage.get_order(order_id="some-order-id")
print(f"Symbol: {order['symbol']}")
print(f"Type: {order['type']}")
print(f"Side: {order['side']}")
print(f"Qty: {order['qty']}")
print(f"Filled: {order['filled_qty']}")
print(f"Status: {order['status']}")
print(f"Submitted: {order['submitted_at']}")
if order['filled_at']:
print(f"Filled: {order['filled_at']}")
if order['filled_avg_price']:
print(f"Avg Fill Price: ${order['filled_avg_price']}")Cancel Order
# Cancel a pending order
brokerage.cancel_order(order_id="some-order-id")
# Cancel returns None on success, raises exception on error4. Manage Positions
List All Positions
positions = brokerage.positions()
for position in positions:
print(f"{position['symbol']}: {position['qty']} shares")
print(f" Entry: ${position['avg_entry_price']}")
print(f" Current: ${position['current_price']}")
print(f" P&L: ${position['unrealized_pl']} ({position['unrealized_plpc']}%)")
print(f" Market Value: ${position['market_value']}")Get Position for Symbol
position = brokerage.get_position(symbol="AAPL")
print(f"Symbol: {position['symbol']}")
print(f"Qty: {position['qty']}")
print(f"Side: {position['side']}") # long or short
print(f"Avg Entry: ${position['avg_entry_price']}")
print(f"Current: ${position['current_price']}")
print(f"Cost Basis: ${position['cost_basis']}")
print(f"Market Value: ${position['market_value']}")
print(f"Unrealized P&L: ${position['unrealized_pl']}")
print(f"Unrealized P&L %: {position['unrealized_plpc']}%")Close Position
# Close entire position (market order to exit)
order = brokerage.close_position(symbol="TSLA")
print(f"Closing order: {order['id']}")
print(f"Status: {order['status']}")5. Portfolio History
# Get portfolio value history
history = brokerage.get_portfolio_history(
period="1M", # 1D, 1W, 1M, 3M, 1A, all
timeframe="1D" # 5Min, 15Min, 1H, 1D
)
print(f"Timeframes: {len(history['timestamp'])} data points")
print(f"Base Value: ${history['base_value']}")
for i, (timestamp, equity) in enumerate(zip(history['timestamp'], history['equity'])):
profit_loss = history['profit_loss'][i]
profit_loss_pct = history['profit_loss_pct'][i]
print(f"{timestamp}: ${equity} (P&L: ${profit_loss}, {profit_loss_pct}%)")Paper Trading vs Live Trading
Paper Trading (Default)
- Safe testing environment with simulated trading
- No real money at risk
- Same API as live trading
- Full order types supported (market, limit, stop, stop-limit)
- Real-time market data (delayed 15 minutes for some exchanges)
- Resets available (contact Alpaca support to reset paper account)
Live Trading (Opt-In)
- Real money and real trades
- Must explicitly set
mode="live" - Requires live API credentials (separate from paper)
- Regulatory compliance required (pattern day trader rules, etc.)
- Cannot be undone - all orders are real
Safety Checklist Before Going Live
- Thoroughly tested with paper trading
- Risk management implemented (stop losses, position limits)
- Error handling for all API calls
- Monitoring and alerting configured
- Regulatory compliance verified
- Account properly funded
- Trading plan documented
- Backup and recovery procedures in place
Error Handling
Common Errors
from httpx import HTTPStatusError
try:
order = brokerage.submit_order(
symbol="AAPL",
qty=1000000, # Too large
side="buy",
type="market",
time_in_force="day"
)
except HTTPStatusError as e:
if e.response.status_code == 403:
print("Insufficient buying power")
elif e.response.status_code == 422:
print("Invalid order parameters")
else:
print(f"API error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")Retry Logic (svc-infra integration)
from fin_infra.brokerage import easy_brokerage
# Brokerage provider already uses httpx with retries
brokerage = easy_brokerage(mode="paper")
# API calls automatically retry on transient errors
# (network issues, 5xx errors, timeouts)
positions = brokerage.positions() # Retries up to 3 timesRate Limits
Alpaca Rate Limits
- Paper Trading: 200 requests per minute
- Live Trading: 200 requests per minute
- Market Data: Separate limits (see market data docs)
Handling Rate Limits
from time import sleep
def submit_orders_with_throttle(orders_to_submit):
"""Submit multiple orders with rate limit handling."""
for order_params in orders_to_submit:
try:
order = brokerage.submit_order(**order_params)
print(f"Order submitted: {order['id']}")
except HTTPStatusError as e:
if e.response.status_code == 429:
print("Rate limit hit, waiting 60 seconds...")
sleep(60)
order = brokerage.submit_order(**order_params)
else:
raise
# Small delay between orders
sleep(0.3) # 200 req/min = ~3 req/sec maxTesting
Unit Tests (Mock Provider)
import pytest
from unittest.mock import Mock
from fin_infra.brokerage import add_brokerage
from fastapi import FastAPI
from fastapi.testclient import TestClient
def test_submit_order_endpoint():
"""Test order submission via API."""
app = FastAPI()
# Create mock provider
mock_provider = Mock()
mock_provider.submit_order.return_value = {
"id": "test-order-123",
"symbol": "AAPL",
"status": "accepted"
}
# Wire to app
add_brokerage(app, provider=mock_provider)
client = TestClient(app)
# Submit order
response = client.post("/brokerage/orders", json={
"symbol": "AAPL",
"qty": 10,
"side": "buy",
"type": "market",
"time_in_force": "day"
})
assert response.status_code == 200
data = response.json()
assert data["id"] == "test-order-123"
assert data["symbol"] == "AAPL"Integration Tests (Paper Trading)
import pytest
import os
from fin_infra.brokerage import easy_brokerage
@pytest.mark.skipif(
not os.getenv("ALPACA_PAPER_API_KEY"),
reason="Requires Alpaca paper trading credentials"
)
def test_real_paper_trading():
"""Test with real Alpaca paper trading API."""
brokerage = easy_brokerage(mode="paper")
# Get account
account = brokerage.get_account()
assert account['status'] == 'ACTIVE'
assert float(account['buying_power']) > 0
# Submit market order
order = brokerage.submit_order(
symbol="AAPL",
qty=1,
side="buy",
type="market",
time_in_force="day"
)
assert order['symbol'] == 'AAPL'
assert order['status'] in ['accepted', 'new', 'filled']
# Cancel if not filled
if order['status'] != 'filled':
brokerage.cancel_order(order['id'])Production Best Practices
- Always Start with Paper: Test all logic thoroughly before live trading
- Implement Stop Losses: Protect against unexpected price movements
- Position Sizing: Never risk more than you can afford to lose
- Error Handling: Wrap all API calls in try/except blocks
- Monitoring: Set up alerts for failed orders and unusual activity
- Compliance: Ensure regulatory compliance (PDT rules, etc.)
- Logging: Log all trades for audit trail
- Rate Limiting: Respect API rate limits to avoid throttling
- Idempotency: Use client_order_id to prevent duplicate orders
- Testing: Continuous testing in paper mode even after going live