Edwin Salguero
feat: prepare for Streamlit Cloud deployment - fix requirements.txt, add deployment config, error handling, and entry point
aaa6e1c
""" | |
Streamlit UI for Algorithmic Trading System | |
A comprehensive web interface for: | |
- Real-time market data visualization | |
- Trading strategy configuration | |
- FinRL model training and evaluation | |
- Portfolio management | |
- Risk monitoring | |
""" | |
import streamlit as st | |
import plotly.graph_objects as go | |
import plotly.express as px | |
import pandas as pd | |
import numpy as np | |
import yaml | |
import os | |
import sys | |
from datetime import datetime, timedelta | |
from typing import Dict, Any, Optional | |
import asyncio | |
import threading | |
import time | |
# Add project root to path | |
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
# Import with error handling for deployment | |
try: | |
from agentic_ai_system.main import load_config | |
from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators | |
from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig | |
from agentic_ai_system.alpaca_broker import AlpacaBroker | |
from agentic_ai_system.orchestrator import run_backtest, run_live_trading | |
DEPLOYMENT_MODE = False | |
except ImportError as e: | |
st.warning(f"β οΈ Some modules not available in deployment mode: {e}") | |
DEPLOYMENT_MODE = True | |
# Mock functions for deployment | |
def load_config(config_file): | |
return { | |
'trading': {'symbol': 'AAPL', 'capital': 100000, 'timeframe': '1d'}, | |
'execution': {'broker_api': 'alpaca_paper'}, | |
'finrl': {'algorithm': 'PPO'}, | |
'risk': {'max_drawdown': 0.1} | |
} | |
def load_data(config): | |
# Generate sample data for deployment | |
dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='D') | |
np.random.seed(42) | |
prices = 150 + np.cumsum(np.random.randn(len(dates)) * 0.5) | |
data = pd.DataFrame({ | |
'timestamp': dates, | |
'open': prices * 0.99, | |
'high': prices * 1.02, | |
'low': prices * 0.98, | |
'close': prices, | |
'volume': np.random.randint(1000000, 5000000, len(dates)) | |
}) | |
return data | |
def add_technical_indicators(data): | |
data['sma_20'] = data['close'].rolling(window=20).mean() | |
data['sma_50'] = data['close'].rolling(window=50).mean() | |
return data | |
class FinRLAgent: | |
def __init__(self, config): | |
self.config = config | |
def train(self, data, config, total_timesteps, use_real_broker=False): | |
return {'success': True, 'message': 'Training completed (demo mode)'} | |
class FinRLConfig: | |
def __init__(self, **kwargs): | |
for key, value in kwargs.items(): | |
setattr(self, key, value) | |
class AlpacaBroker: | |
def __init__(self, config): | |
self.config = config | |
def get_account_info(self): | |
return { | |
'portfolio_value': 100000, | |
'equity': 102500, | |
'cash': 50000, | |
'buying_power': 50000 | |
} | |
def get_positions(self): | |
return [] | |
def run_backtest(config, data): | |
return { | |
'success': True, | |
'total_return': 0.025, | |
'sharpe_ratio': 1.2, | |
'max_drawdown': 0.05, | |
'total_trades': 15 | |
} | |
def run_live_trading(config, data): | |
return {'success': True, 'message': 'Live trading started (demo mode)'} | |
# Page configuration | |
st.set_page_config( | |
page_title="Algorithmic Trading System", | |
page_icon="π", | |
layout="wide", | |
initial_sidebar_state="expanded" | |
) | |
# Custom CSS for better styling | |
st.markdown(""" | |
<style> | |
.main-header { | |
font-size: 2.5rem; | |
font-weight: bold; | |
color: #1f77b4; | |
text-align: center; | |
margin-bottom: 2rem; | |
} | |
.metric-card { | |
background-color: #f0f2f6; | |
padding: 1rem; | |
border-radius: 0.5rem; | |
border-left: 4px solid #1f77b4; | |
} | |
.success-metric { | |
border-left-color: #28a745; | |
} | |
.warning-metric { | |
border-left-color: #ffc107; | |
} | |
.danger-metric { | |
border-left-color: #dc3545; | |
} | |
.sidebar .sidebar-content { | |
background-color: #f8f9fa; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
class TradingUI: | |
def __init__(self): | |
self.config = None | |
self.data = None | |
self.alpaca_broker = None | |
self.finrl_agent = None | |
self.session_state = st.session_state | |
# Initialize session state | |
if 'trading_active' not in self.session_state: | |
self.session_state.trading_active = False | |
if 'current_portfolio' not in self.session_state: | |
self.session_state.current_portfolio = {} | |
if 'trading_history' not in self.session_state: | |
self.session_state.trading_history = [] | |
def load_configuration(self): | |
"""Load and display configuration""" | |
st.sidebar.header("βοΈ Configuration") | |
# Config file selector | |
config_files = [f for f in os.listdir('.') if f.endswith('.yaml') or f.endswith('.yml')] | |
selected_config = st.sidebar.selectbox( | |
"Select Configuration File", | |
config_files, | |
index=0 if 'config.yaml' in config_files else 0 | |
) | |
if st.sidebar.button("Load Configuration"): | |
try: | |
self.config = load_config(selected_config) | |
st.sidebar.success(f"β Configuration loaded: {selected_config}") | |
return True | |
except Exception as e: | |
st.sidebar.error(f"β Error loading config: {e}") | |
return False | |
return False | |
def display_system_status(self): | |
"""Display system status and metrics""" | |
st.header("π System Status") | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric( | |
label="Trading Status", | |
value="π’ Active" if self.session_state.trading_active else "π΄ Inactive", | |
delta="Running" if self.session_state.trading_active else "Stopped" | |
) | |
with col2: | |
if self.config: | |
st.metric( | |
label="Capital", | |
value=f"${self.config['trading']['capital']:,}", | |
delta="Available" | |
) | |
else: | |
st.metric(label="Capital", value="Not Loaded") | |
with col3: | |
if self.alpaca_broker: | |
try: | |
account_info = self.alpaca_broker.get_account_info() | |
if account_info: | |
st.metric( | |
label="Portfolio Value", | |
value=f"${float(account_info['portfolio_value']):,.2f}", | |
delta=f"{float(account_info['equity']) - float(account_info['portfolio_value']):,.2f}" | |
) | |
except: | |
st.metric(label="Portfolio Value", value="Not Connected") | |
else: | |
st.metric(label="Portfolio Value", value="Not Connected") | |
with col4: | |
if self.data is not None: | |
st.metric( | |
label="Data Points", | |
value=f"{len(self.data):,}", | |
delta=f"Latest: {self.data['timestamp'].max().strftime('%Y-%m-%d')}" | |
) | |
else: | |
st.metric(label="Data Points", value="Not Loaded") | |
def data_ingestion_panel(self): | |
"""Data ingestion and visualization panel""" | |
st.header("π₯ Data Ingestion") | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
if self.config: | |
if st.button("Load Market Data"): | |
with st.spinner("Loading data..."): | |
try: | |
self.data = load_data(self.config) | |
if self.data is not None and not self.data.empty: | |
st.success(f"β Loaded {len(self.data)} data points") | |
# Add technical indicators | |
self.data = add_technical_indicators(self.data) | |
st.info(f"β Added technical indicators") | |
else: | |
st.error("β Failed to load data") | |
except Exception as e: | |
st.error(f"β Error loading data: {e}") | |
with col2: | |
if self.data is not None: | |
st.subheader("Data Summary") | |
st.write(f"**Symbol:** {self.config['trading']['symbol']}") | |
st.write(f"**Timeframe:** {self.config['trading']['timeframe']}") | |
st.write(f"**Date Range:** {self.data['timestamp'].min().strftime('%Y-%m-%d')} to {self.data['timestamp'].max().strftime('%Y-%m-%d')}") | |
st.write(f"**Price Range:** ${self.data['close'].min():.2f} - ${self.data['close'].max():.2f}") | |
# Data visualization | |
if self.data is not None: | |
st.subheader("π Market Data Visualization") | |
# Chart type selector | |
chart_type = st.selectbox( | |
"Chart Type", | |
["Candlestick", "Line", "OHLC", "Volume"] | |
) | |
if chart_type == "Candlestick": | |
fig = go.Figure(data=[go.Candlestick( | |
x=self.data['timestamp'], | |
open=self.data['open'], | |
high=self.data['high'], | |
low=self.data['low'], | |
close=self.data['close'] | |
)]) | |
fig.update_layout( | |
title=f"{self.config['trading']['symbol']} Candlestick Chart", | |
xaxis_title="Date", | |
yaxis_title="Price ($)", | |
height=500 | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
elif chart_type == "Line": | |
fig = px.line(self.data, x='timestamp', y='close', | |
title=f"{self.config['trading']['symbol']} Price Chart") | |
fig.update_layout(height=500) | |
st.plotly_chart(fig, use_container_width=True) | |
elif chart_type == "Volume": | |
fig = go.Figure() | |
fig.add_trace(go.Bar( | |
x=self.data['timestamp'], | |
y=self.data['volume'], | |
name='Volume' | |
)) | |
fig.update_layout( | |
title=f"{self.config['trading']['symbol']} Volume Chart", | |
xaxis_title="Date", | |
yaxis_title="Volume", | |
height=500 | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
def alpaca_integration_panel(self): | |
"""Alpaca broker integration panel""" | |
st.header("π¦ Alpaca Integration") | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
if st.button("Connect to Alpaca"): | |
if self.config and self.config['execution']['broker_api'] in ['alpaca_paper', 'alpaca_live']: | |
with st.spinner("Connecting to Alpaca..."): | |
try: | |
self.alpaca_broker = AlpacaBroker(self.config) | |
account_info = self.alpaca_broker.get_account_info() | |
if account_info: | |
st.success("β Connected to Alpaca") | |
self.session_state.alpaca_connected = True | |
else: | |
st.error("β Failed to connect to Alpaca") | |
except Exception as e: | |
st.error(f"β Connection error: {e}") | |
else: | |
st.warning("β οΈ Alpaca not configured in settings") | |
with col2: | |
if st.button("Disconnect from Alpaca"): | |
self.alpaca_broker = None | |
self.session_state.alpaca_connected = False | |
st.success("β Disconnected from Alpaca") | |
# Account information display | |
if self.alpaca_broker: | |
st.subheader("Account Information") | |
try: | |
account_info = self.alpaca_broker.get_account_info() | |
if account_info: | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric( | |
label="Buying Power", | |
value=f"${float(account_info['buying_power']):,.2f}" | |
) | |
with col2: | |
st.metric( | |
label="Portfolio Value", | |
value=f"${float(account_info['portfolio_value']):,.2f}" | |
) | |
with col3: | |
st.metric( | |
label="Equity", | |
value=f"${float(account_info['equity']):,.2f}" | |
) | |
# Market hours | |
market_hours = self.alpaca_broker.get_market_hours() | |
if market_hours: | |
status_color = "π’" if market_hours['is_open'] else "π΄" | |
st.info(f"{status_color} Market Status: {'OPEN' if market_hours['is_open'] else 'CLOSED'}") | |
if market_hours['next_open']: | |
st.write(f"Next Open: {market_hours['next_open']}") | |
if market_hours['next_close']: | |
st.write(f"Next Close: {market_hours['next_close']}") | |
# Current positions | |
positions = self.alpaca_broker.get_positions() | |
if positions: | |
st.subheader("Current Positions") | |
positions_df = pd.DataFrame(positions) | |
st.dataframe(positions_df) | |
else: | |
st.info("No current positions") | |
except Exception as e: | |
st.error(f"Error fetching account info: {e}") | |
def finrl_training_panel(self): | |
"""FinRL model training panel""" | |
st.header("π§ FinRL Model Training") | |
if not self.data is not None: | |
st.warning("β οΈ Please load market data first") | |
return | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
st.subheader("Training Configuration") | |
# Training parameters | |
algorithm = st.selectbox( | |
"Algorithm", | |
["PPO", "A2C", "DDPG", "TD3"], | |
index=0 | |
) | |
learning_rate = st.slider( | |
"Learning Rate", | |
min_value=0.0001, | |
max_value=0.01, | |
value=0.0003, | |
step=0.0001, | |
format="%.4f" | |
) | |
total_timesteps = st.slider( | |
"Total Timesteps", | |
min_value=1000, | |
max_value=1000000, | |
value=100000, | |
step=1000 | |
) | |
batch_size = st.selectbox( | |
"Batch Size", | |
[32, 64, 128, 256], | |
index=1 | |
) | |
with col2: | |
st.subheader("Training Controls") | |
if st.button("Start Training", type="primary"): | |
if self.data is not None: | |
with st.spinner("Training FinRL model..."): | |
try: | |
# Create FinRL config | |
finrl_config = FinRLConfig( | |
algorithm=algorithm, | |
learning_rate=learning_rate, | |
batch_size=batch_size, | |
buffer_size=1000000, | |
learning_starts=100, | |
gamma=0.99, | |
tau=0.005, | |
train_freq=1, | |
gradient_steps=1, | |
verbose=1, | |
tensorboard_log='logs/finrl_tensorboard' | |
) | |
# Initialize agent | |
self.finrl_agent = FinRLAgent(finrl_config) | |
# Train the agent | |
result = self.finrl_agent.train( | |
data=self.data, | |
config=self.config, | |
total_timesteps=total_timesteps, | |
use_real_broker=False | |
) | |
if result['success']: | |
st.success("β Training completed successfully!") | |
st.write(f"Model saved: {result['model_path']}") | |
self.session_state.model_trained = True | |
else: | |
st.error("β Training failed") | |
except Exception as e: | |
st.error(f"β Training error: {e}") | |
# Training progress and metrics | |
if hasattr(self.session_state, 'model_trained') and self.session_state.model_trained: | |
st.subheader("Model Performance") | |
if st.button("Evaluate Model"): | |
if self.finrl_agent: | |
with st.spinner("Evaluating model..."): | |
try: | |
# Use last 100 data points for evaluation | |
eval_data = self.data.tail(100) | |
prediction_result = self.finrl_agent.predict( | |
data=eval_data, | |
config=self.config, | |
use_real_broker=False | |
) | |
if prediction_result['success']: | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric( | |
label="Initial Value", | |
value=f"${prediction_result['initial_value']:,.2f}" | |
) | |
with col2: | |
st.metric( | |
label="Final Value", | |
value=f"${prediction_result['final_value']:,.2f}" | |
) | |
with col3: | |
return_pct = prediction_result['total_return'] * 100 | |
st.metric( | |
label="Total Return", | |
value=f"{return_pct:.2f}%", | |
delta=f"{return_pct:.2f}%" | |
) | |
st.write(f"Total Trades: {prediction_result['total_trades']}") | |
else: | |
st.error("β Model evaluation failed") | |
except Exception as e: | |
st.error(f"β Evaluation error: {e}") | |
def trading_controls_panel(self): | |
"""Trading controls and execution panel""" | |
st.header("π― Trading Controls") | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
st.subheader("Backtesting") | |
if st.button("Run Backtest"): | |
if self.data is not None and self.config: | |
with st.spinner("Running backtest..."): | |
try: | |
result = run_backtest(self.config, self.data) | |
if result['success']: | |
st.success("β Backtest completed") | |
# Display backtest results | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
st.metric( | |
label="Total Return", | |
value=f"{result['total_return']:.2%}" | |
) | |
with col2: | |
st.metric( | |
label="Sharpe Ratio", | |
value=f"{result['sharpe_ratio']:.2f}" | |
) | |
with col3: | |
st.metric( | |
label="Max Drawdown", | |
value=f"{result['max_drawdown']:.2%}" | |
) | |
# Store results in session state | |
self.session_state.backtest_results = result | |
else: | |
st.error("β Backtest failed") | |
except Exception as e: | |
st.error(f"β Backtest error: {e}") | |
with col2: | |
st.subheader("Live Trading") | |
if st.button("Start Live Trading", type="primary"): | |
if self.config and self.alpaca_broker: | |
self.session_state.trading_active = True | |
st.success("β Live trading started") | |
# Start trading in background thread | |
def run_trading(): | |
try: | |
run_live_trading(self.config, self.data) | |
except Exception as e: | |
st.error(f"Trading error: {e}") | |
trading_thread = threading.Thread(target=run_trading) | |
trading_thread.daemon = True | |
trading_thread.start() | |
else: | |
st.warning("β οΈ Please configure Alpaca connection first") | |
if st.button("Stop Live Trading"): | |
self.session_state.trading_active = False | |
st.success("β Live trading stopped") | |
def portfolio_monitoring_panel(self): | |
"""Portfolio monitoring and analytics panel""" | |
st.header("π Portfolio Monitoring") | |
if not self.alpaca_broker: | |
st.warning("β οΈ Connect to Alpaca to view portfolio") | |
return | |
try: | |
# Portfolio overview | |
account_info = self.alpaca_broker.get_account_info() | |
if account_info: | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric( | |
label="Total Value", | |
value=f"${float(account_info['portfolio_value']):,.2f}" | |
) | |
with col2: | |
st.metric( | |
label="Cash", | |
value=f"${float(account_info['cash']):,.2f}" | |
) | |
with col3: | |
st.metric( | |
label="Buying Power", | |
value=f"${float(account_info['buying_power']):,.2f}" | |
) | |
with col4: | |
equity = float(account_info['equity']) | |
portfolio_value = float(account_info['portfolio_value']) | |
pnl = equity - portfolio_value | |
st.metric( | |
label="P&L", | |
value=f"${pnl:,.2f}", | |
delta=f"{pnl:,.2f}" | |
) | |
# Positions table | |
positions = self.alpaca_broker.get_positions() | |
if positions: | |
st.subheader("Current Positions") | |
positions_df = pd.DataFrame(positions) | |
if not positions_df.empty: | |
# Calculate additional metrics | |
positions_df['market_value'] = positions_df['quantity'].astype(float) * positions_df['current_price'].astype(float) | |
positions_df['unrealized_pl'] = positions_df['unrealized_pl'].astype(float) | |
positions_df['unrealized_plpc'] = positions_df['unrealized_plpc'].astype(float) | |
# Display positions | |
st.dataframe( | |
positions_df[['symbol', 'quantity', 'current_price', 'market_value', 'unrealized_pl', 'unrealized_plpc']], | |
use_container_width=True | |
) | |
# Position chart | |
fig = px.pie( | |
positions_df, | |
values='market_value', | |
names='symbol', | |
title="Portfolio Allocation" | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
else: | |
st.info("No positions found") | |
else: | |
st.info("No current positions") | |
except Exception as e: | |
st.error(f"Error fetching portfolio data: {e}") | |
def run(self): | |
"""Main UI application""" | |
# Header | |
st.markdown('<h1 class="main-header">π€ Algorithmic Trading System</h1>', unsafe_allow_html=True) | |
# Load configuration | |
if self.load_configuration(): | |
self.config = load_config('config.yaml') | |
# Sidebar navigation | |
st.sidebar.title("Navigation") | |
page = st.sidebar.selectbox( | |
"Select Page", | |
["Dashboard", "Data Ingestion", "Alpaca Integration", "FinRL Training", "Trading Controls", "Portfolio Monitoring"] | |
) | |
# Display system status | |
self.display_system_status() | |
# Page routing | |
if page == "Dashboard": | |
st.header("π Dashboard") | |
if self.config: | |
st.subheader("System Configuration") | |
config_col1, config_col2 = st.columns(2) | |
with config_col1: | |
st.write(f"**Symbol:** {self.config['trading']['symbol']}") | |
st.write(f"**Capital:** ${self.config['trading']['capital']:,}") | |
st.write(f"**Timeframe:** {self.config['trading']['timeframe']}") | |
with config_col2: | |
st.write(f"**Broker:** {self.config['execution']['broker_api']}") | |
st.write(f"**FinRL Algorithm:** {self.config['finrl']['algorithm']}") | |
st.write(f"**Risk Max Drawdown:** {self.config['risk']['max_drawdown']:.1%}") | |
# Quick actions | |
st.subheader("Quick Actions") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
if st.button("Load Data", type="primary"): | |
if self.config: | |
with st.spinner("Loading data..."): | |
self.data = load_data(self.config) | |
if self.data is not None: | |
st.success("β Data loaded successfully") | |
with col2: | |
if st.button("Connect Alpaca"): | |
if self.config and self.config['execution']['broker_api'] in ['alpaca_paper', 'alpaca_live']: | |
with st.spinner("Connecting..."): | |
self.alpaca_broker = AlpacaBroker(self.config) | |
st.success("β Connected to Alpaca") | |
with col3: | |
if st.button("Start Training"): | |
if self.data is not None: | |
st.info("Navigate to FinRL Training page to configure and start training") | |
elif page == "Data Ingestion": | |
self.data_ingestion_panel() | |
elif page == "Alpaca Integration": | |
self.alpaca_integration_panel() | |
elif page == "FinRL Training": | |
self.finrl_training_panel() | |
elif page == "Trading Controls": | |
self.trading_controls_panel() | |
elif page == "Portfolio Monitoring": | |
self.portfolio_monitoring_panel() | |
def main(): | |
"""Main application entry point""" | |
ui = TradingUI() | |
ui.run() | |
def create_streamlit_app(): | |
"""Create and return a Streamlit trading application""" | |
return TradingUI() | |
if __name__ == "__main__": | |
main() |