BrianIsaac commited on
Commit
fc49c8c
·
1 Parent(s): dc29eaf

feat: initial project setup with dependencies and core infrastructure

Browse files

Set up Portfolio Intelligence Platform with complete dependency management and core modules:

- Configure pyproject.toml with research-validated dependencies
- pydantic-ai v1.18.0 with native prompt caching
- fastmcp v2.13.0+ for MCP orchestration
- pyportfolioopt v1.5.6 (correct package name)
- quantitative finance libraries (riskfolio-lib, arch, xgboost, quantstats)
- All packages verified for Python 3.13 compatibility

- Create Gradio interface (app.py)
- Portfolio input with example portfolios
- Analysis output placeholder
- Clean, minimal UI ready for MCP integration

- Implement backend infrastructure
- config.py: Pydantic Settings for environment configuration
- database.py: Supabase PostgreSQL connection manager with demo mode
- mcp_router.py: MCP orchestration layer for 6 P0 servers
(Yahoo Finance, FMP, Trading-MCP, FRED, Portfolio Optimizer, Risk Analyzer)

- Establish project structure
- backend/agents/ for Pydantic AI agents
- backend/models/ for quantitative models
- backend/mcp_servers/ for custom MCP implementations
- tests/ directory for testing

All 270 packages installed successfully with uv sync

app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Portfolio Intelligence Platform - Gradio Interface.
2
+
3
+ Main application file for the hackathon demo.
4
+ """
5
+
6
+ import gradio as gr
7
+ from typing import List, Tuple
8
+ import os
9
+ from dotenv import load_dotenv
10
+
11
+ load_dotenv()
12
+
13
+
14
+ def analyse_portfolio(portfolio_input: str) -> str:
15
+ """Analyse portfolio and return AI insights.
16
+
17
+ Args:
18
+ portfolio_input: Portfolio holdings as text (e.g., "AAPL 50\nTSLA 25 shares")
19
+
20
+ Returns:
21
+ Analysis results with insights and recommendations
22
+ """
23
+ # TODO: Integrate with MCP router and agents
24
+ return f"Analysis placeholder for:\n{portfolio_input}"
25
+
26
+
27
+ def create_interface() -> gr.Blocks:
28
+ """Create the main Gradio interface.
29
+
30
+ Returns:
31
+ Gradio Blocks interface
32
+ """
33
+ with gr.Blocks(
34
+ title="Portfolio Intelligence Platform",
35
+ theme=gr.themes.Soft(),
36
+ fill_height=True
37
+ ) as demo:
38
+ gr.Markdown("# 📊 Portfolio Intelligence Platform")
39
+ gr.Markdown("AI-powered portfolio analysis using MCP orchestration")
40
+
41
+ with gr.Row():
42
+ with gr.Column(scale=1):
43
+ gr.Markdown("## Enter Your Portfolio")
44
+
45
+ portfolio_input = gr.Textbox(
46
+ label="Portfolio Holdings",
47
+ placeholder="AAPL 50\nTSLA 25 shares\nNVDA $5000\nBTC 0.5",
48
+ lines=10,
49
+ info="Enter one holding per line"
50
+ )
51
+
52
+ analyse_btn = gr.Button("Analyse Portfolio", variant="primary")
53
+
54
+ # Example portfolios
55
+ gr.Examples(
56
+ examples=[
57
+ ["AAPL 50\nTSLA 25 shares\nNVDA $5000\nBTC 0.5"],
58
+ ["VOO 100 shares\nVTI 75 shares\nSCHD 50 shares"],
59
+ ["VTI $25000\nVXUS $15000\nBND $15000\nGLD $5000"],
60
+ ],
61
+ inputs=portfolio_input,
62
+ label="Example Portfolios"
63
+ )
64
+
65
+ with gr.Column(scale=2):
66
+ gr.Markdown("## Analysis Results")
67
+
68
+ output = gr.Markdown(
69
+ value="Enter your portfolio and click 'Analyse Portfolio' to get started.",
70
+ label="AI Insights"
71
+ )
72
+
73
+ # Event handlers
74
+ analyse_btn.click(
75
+ fn=analyse_portfolio,
76
+ inputs=portfolio_input,
77
+ outputs=output
78
+ )
79
+
80
+ return demo
81
+
82
+
83
+ if __name__ == "__main__":
84
+ demo = create_interface()
85
+ demo.launch(
86
+ server_name="0.0.0.0",
87
+ server_port=7860,
88
+ share=False
89
+ )
backend/__init__.py ADDED
File without changes
backend/agents/__init__.py ADDED
File without changes
backend/config.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration module for Portfolio Intelligence Platform.
2
+
3
+ Loads environment variables and provides configuration settings.
4
+ """
5
+
6
+ import os
7
+ from typing import Optional
8
+ from pydantic import Field
9
+ from pydantic_settings import BaseSettings
10
+
11
+
12
+ class Settings(BaseSettings):
13
+ """Application settings loaded from environment variables.
14
+
15
+ Attributes:
16
+ anthropic_api_key: Anthropic API key for Claude
17
+ supabase_url: Supabase project URL
18
+ supabase_key: Supabase anon key
19
+ fmp_api_key: Financial Modeling Prep API key
20
+ fred_api_key: FRED (Federal Reserve Economic Data) API key
21
+ environment: Application environment (development/production)
22
+ log_level: Logging level
23
+ """
24
+
25
+ # AI/LLM Configuration
26
+ anthropic_api_key: str = Field(
27
+ default="",
28
+ validation_alias="ANTHROPIC_API_KEY"
29
+ )
30
+
31
+ # Database Configuration
32
+ supabase_url: Optional[str] = Field(
33
+ default=None,
34
+ validation_alias="SUPABASE_URL"
35
+ )
36
+ supabase_key: Optional[str] = Field(
37
+ default=None,
38
+ validation_alias="SUPABASE_KEY"
39
+ )
40
+
41
+ # Financial Data APIs
42
+ fmp_api_key: Optional[str] = Field(
43
+ default=None,
44
+ validation_alias="FMP_API_KEY"
45
+ )
46
+ fred_api_key: Optional[str] = Field(
47
+ default=None,
48
+ validation_alias="FRED_API_KEY"
49
+ )
50
+
51
+ # Application Settings
52
+ environment: str = Field(
53
+ default="development",
54
+ validation_alias="ENVIRONMENT"
55
+ )
56
+ log_level: str = Field(
57
+ default="INFO",
58
+ validation_alias="LOG_LEVEL"
59
+ )
60
+
61
+ class Config:
62
+ """Pydantic config."""
63
+
64
+ env_file = ".env"
65
+ env_file_encoding = "utf-8"
66
+ case_sensitive = False
67
+
68
+
69
+ # Global settings instance
70
+ settings = Settings()
backend/database.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Database module for Portfolio Intelligence Platform.
2
+
3
+ Handles Supabase PostgreSQL connections and operations.
4
+ """
5
+
6
+ from typing import Optional
7
+ from supabase import create_client, Client
8
+ from backend.config import settings
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Database:
15
+ """Database connection manager for Supabase PostgreSQL.
16
+
17
+ Attributes:
18
+ client: Supabase client instance
19
+ """
20
+
21
+ def __init__(self):
22
+ """Initialise database connection."""
23
+ self.client: Optional[Client] = None
24
+
25
+ if settings.supabase_url and settings.supabase_key:
26
+ try:
27
+ self.client = create_client(
28
+ settings.supabase_url,
29
+ settings.supabase_key
30
+ )
31
+ logger.info("Supabase client initialised successfully")
32
+ except Exception as e:
33
+ logger.warning(f"Failed to initialise Supabase client: {e}")
34
+ logger.info("Running in demo mode without database")
35
+ else:
36
+ logger.info("Supabase credentials not configured - running in demo mode")
37
+
38
+ def is_connected(self) -> bool:
39
+ """Check if database connection is active.
40
+
41
+ Returns:
42
+ True if connected, False otherwise
43
+ """
44
+ return self.client is not None
45
+
46
+ async def save_analysis(self, user_id: str, portfolio_data: dict, analysis_result: dict) -> bool:
47
+ """Save portfolio analysis to database.
48
+
49
+ Args:
50
+ user_id: User identifier
51
+ portfolio_data: Portfolio holdings data
52
+ analysis_result: Analysis results from AI agents
53
+
54
+ Returns:
55
+ True if saved successfully, False otherwise
56
+ """
57
+ if not self.is_connected():
58
+ logger.warning("Database not connected - skipping save")
59
+ return False
60
+
61
+ try:
62
+ # TODO: Implement actual database schema and insert logic
63
+ logger.info(f"Saving analysis for user {user_id}")
64
+ return True
65
+ except Exception as e:
66
+ logger.error(f"Failed to save analysis: {e}")
67
+ return False
68
+
69
+
70
+ # Global database instance
71
+ db = Database()
backend/mcp_router.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MCP Router for Portfolio Intelligence Platform.
2
+
3
+ Orchestrates MCP servers for financial data and quantitative analysis.
4
+ """
5
+
6
+ from typing import Dict, List, Any, Optional
7
+ from fastmcp import FastMCP
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MCPRouter:
14
+ """Router for orchestrating multiple MCP servers.
15
+
16
+ Manages connections to:
17
+ - P0 (Week 1): Yahoo Finance, FMP, Trading-MCP, FRED, Portfolio Optimizer, Risk Analyzer
18
+ - P1 (Week 2): Ensemble Predictor (if time permits)
19
+ """
20
+
21
+ def __init__(self):
22
+ """Initialise MCP router with configured servers."""
23
+ self.servers: Dict[str, Any] = {}
24
+ self._initialise_servers()
25
+
26
+ def _initialise_servers(self):
27
+ """Initialise connections to MCP servers."""
28
+ # TODO: Implement actual MCP server connections
29
+ logger.info("Initialising MCP servers")
30
+
31
+ # Placeholder for P0 MCP servers
32
+ self.servers = {
33
+ "yahoo_finance": None, # Real-time pricing, historical data
34
+ "fmp": None, # Financial Modeling Prep - fundamentals
35
+ "trading_mcp": None, # Technical indicators (RSI, MACD, etc.)
36
+ "fred": None, # Macroeconomic data (GDP, unemployment, inflation)
37
+ "portfolio_optimizer": None, # HRP, Black-Litterman, Mean-Variance
38
+ "risk_analyzer": None, # VaR/CVaR calculations
39
+ }
40
+
41
+ logger.info(f"Initialised {len(self.servers)} MCP servers")
42
+
43
+ async def fetch_market_data(self, tickers: List[str]) -> Dict[str, Any]:
44
+ """Fetch market data for given tickers.
45
+
46
+ Args:
47
+ tickers: List of stock/asset tickers
48
+
49
+ Returns:
50
+ Market data from Yahoo Finance and other sources
51
+ """
52
+ # TODO: Implement actual MCP calls
53
+ logger.info(f"Fetching market data for {len(tickers)} tickers")
54
+ return {"status": "placeholder", "tickers": tickers}
55
+
56
+ async def fetch_fundamentals(self, tickers: List[str]) -> Dict[str, Any]:
57
+ """Fetch fundamental data from Financial Modeling Prep.
58
+
59
+ Args:
60
+ tickers: List of stock tickers
61
+
62
+ Returns:
63
+ Fundamental data (P/E, margins, revenue, etc.)
64
+ """
65
+ # TODO: Implement FMP MCP integration
66
+ logger.info(f"Fetching fundamentals for {len(tickers)} tickers")
67
+ return {"status": "placeholder", "tickers": tickers}
68
+
69
+ async def fetch_technical_indicators(self, tickers: List[str]) -> Dict[str, Any]:
70
+ """Fetch technical indicators from Trading-MCP.
71
+
72
+ Args:
73
+ tickers: List of stock tickers
74
+
75
+ Returns:
76
+ Technical indicators (RSI, MACD, Bollinger Bands, etc.)
77
+ """
78
+ # TODO: Implement Trading-MCP integration
79
+ logger.info(f"Fetching technical indicators for {len(tickers)} tickers")
80
+ return {"status": "placeholder", "tickers": tickers}
81
+
82
+ async def fetch_macro_data(self) -> Dict[str, Any]:
83
+ """Fetch macroeconomic data from FRED.
84
+
85
+ Returns:
86
+ Macroeconomic indicators (GDP, unemployment, inflation, etc.)
87
+ """
88
+ # TODO: Implement FRED MCP integration
89
+ logger.info("Fetching macroeconomic data")
90
+ return {"status": "placeholder"}
91
+
92
+ async def optimize_portfolio(
93
+ self,
94
+ historical_prices: Dict[str, List[float]],
95
+ risk_tolerance: str
96
+ ) -> Dict[str, Any]:
97
+ """Optimise portfolio allocation using quantitative models.
98
+
99
+ Args:
100
+ historical_prices: Historical price data for assets
101
+ risk_tolerance: User's risk tolerance (Conservative/Moderate/Aggressive)
102
+
103
+ Returns:
104
+ Optimised portfolio weights and metrics
105
+ """
106
+ # TODO: Implement Portfolio Optimizer MCP
107
+ logger.info(f"Optimising portfolio with {risk_tolerance} risk tolerance")
108
+ return {"status": "placeholder", "method": "HRP + Black-Litterman"}
109
+
110
+ async def calculate_risk_metrics(
111
+ self,
112
+ historical_prices: Dict[str, List[float]],
113
+ portfolio_weights: Dict[str, float]
114
+ ) -> Dict[str, Any]:
115
+ """Calculate risk metrics for portfolio.
116
+
117
+ Args:
118
+ historical_prices: Historical price data
119
+ portfolio_weights: Current portfolio allocation
120
+
121
+ Returns:
122
+ Risk metrics (VaR, CVaR, volatility, etc.)
123
+ """
124
+ # TODO: Implement Risk Analyzer MCP
125
+ logger.info("Calculating risk metrics")
126
+ return {"status": "placeholder", "metrics": ["VaR", "CVaR", "Volatility"]}
127
+
128
+
129
+ # Global MCP router instance
130
+ mcp_router = MCPRouter()
backend/mcp_servers/__init__.py ADDED
File without changes
backend/models/__init__.py ADDED
File without changes
pyproject.toml CHANGED
@@ -1,7 +1,69 @@
1
  [project]
2
  name = "portfolio-intelligence-platform"
3
  version = "0.1.0"
4
- description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
- dependencies = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  [project]
2
  name = "portfolio-intelligence-platform"
3
  version = "0.1.0"
4
+ description = "AI-powered portfolio analysis platform with MCP orchestration"
5
  readme = "README.md"
6
  requires-python = ">=3.12"
7
+ dependencies = [
8
+ # AI Framework
9
+ "pydantic-ai==1.18.0",
10
+ "anthropic>=0.39.0",
11
+ "instructor>=1.6.4",
12
+
13
+ # Agent Orchestration
14
+ "langgraph>=0.1.0",
15
+
16
+ # MCP Framework
17
+ "fastmcp==2.13.0",
18
+ "mcp>=1.0.0",
19
+
20
+ # Frontend
21
+ "gradio==5.49.1",
22
+
23
+ # Backend
24
+ "fastapi>=0.104.0",
25
+ "uvicorn[standard]>=0.24.0",
26
+ "pydantic>=2.5.0",
27
+
28
+ # Database
29
+ "supabase>=2.9.0",
30
+ "psycopg2-binary>=2.9.9",
31
+
32
+ # Quantitative Finance (P0 - Deterministic Models)
33
+ # Research validated: Package name is 'pyportfolioopt', import as 'pypfopt'
34
+ "pyportfolioopt>=1.5.6",
35
+ "riskfolio-lib>=7.0.0",
36
+ "arch>=8.0.0",
37
+ "quantstats>=0.0.77",
38
+
39
+ # Data & Analysis
40
+ "pandas>=2.2.3",
41
+ "numpy>=2.0.0",
42
+ "yfinance>=0.2.40",
43
+ "scipy>=1.11.0",
44
+
45
+ # ML Models (P1 - Optional)
46
+ "torch>=2.0.0",
47
+ "xgboost>=3.1.0",
48
+
49
+ # Utilities
50
+ "python-dotenv>=1.0.0",
51
+ "httpx>=0.25.0",
52
+ "tenacity>=8.2.0",
53
+ ]
54
+
55
+ [tool.uv]
56
+ dev-dependencies = [
57
+ "pytest>=7.4.0",
58
+ "pytest-asyncio>=0.21.0",
59
+ "ruff>=0.1.0",
60
+ "black>=23.11.0",
61
+ "mypy>=1.7.0",
62
+ ]
63
+
64
+ [tool.hatch.build.targets.wheel]
65
+ packages = ["backend"]
66
+
67
+ [build-system]
68
+ requires = ["hatchling"]
69
+ build-backend = "hatchling.build"