Spaces:
Running
Running
Upload 16 files
Browse files- .env +2 -0
- .gitignore +1 -0
- README.md +0 -10
- app.py +120 -7
- config.py +60 -0
- models/__init__.py +0 -0
- models/analysis.py +19 -0
- models/market_data.py +25 -0
- requirements.txt +10 -2
- services/__init__.py +0 -0
- services/analyzer.py +174 -0
- services/data_fetcher.py +118 -0
- services/economic_data.py +48 -0
- services/swarm_service.py +113 -0
- utils/__init__.py +0 -0
- utils/helpers.py +0 -0
.env
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
SWARMS_API_KEY=sk-f763a9c6f898aa3ec74bf49047736a0201c293eefc36d5a04be8af647b93b711
|
2 |
+
FRED_API_KEY=d421178827e3d9c621210bc6f0303439
|
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__pycache__/
|
README.md
CHANGED
@@ -1,10 +0,0 @@
|
|
1 |
-
---
|
2 |
-
title: Financial Analyst
|
3 |
-
emoji: 🚀
|
4 |
-
colorFrom: red
|
5 |
-
colorTo: green
|
6 |
-
sdk: docker
|
7 |
-
pinned: false
|
8 |
-
---
|
9 |
-
|
10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
@@ -1,7 +1,120 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# main.py
|
2 |
+
from fastapi import FastAPI, HTTPException, Depends
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware # If Streamlit runs on a different port
|
4 |
+
from typing import Dict, List
|
5 |
+
from services.data_fetcher import IndianMarketDataFetcher
|
6 |
+
from services.analyzer import IndianFinancialAnalyzer
|
7 |
+
from services.swarm_service import create_indian_market_swarm
|
8 |
+
from models.market_data import StockDataResponse, IndexData, SectorPerformance, EconomicIndicators
|
9 |
+
from models.analysis import StockAnalysisResponse, SwarmAnalysisResponse, InvestmentRecommendationResponse
|
10 |
+
from config import NIFTY50_COMPANIES
|
11 |
+
|
12 |
+
app = FastAPI(title="Indian Market Analysis API", version="1.0.0")
|
13 |
+
|
14 |
+
# Configure CORS if needed (e.g., if Streamlit runs on localhost:8501)
|
15 |
+
app.add_middleware(
|
16 |
+
CORSMiddleware,
|
17 |
+
allow_origins=["*"], # Restrict this in production!
|
18 |
+
allow_credentials=True,
|
19 |
+
allow_methods=["*"],
|
20 |
+
allow_headers=["*"],
|
21 |
+
)
|
22 |
+
|
23 |
+
# Dependency Injection for services
|
24 |
+
def get_data_fetcher():
|
25 |
+
return IndianMarketDataFetcher()
|
26 |
+
|
27 |
+
def get_analyzer():
|
28 |
+
return IndianFinancialAnalyzer()
|
29 |
+
|
30 |
+
# --- API Endpoints ---
|
31 |
+
|
32 |
+
@app.get("/")
|
33 |
+
async def root():
|
34 |
+
return {"message": "Indian Market Analysis API"}
|
35 |
+
|
36 |
+
# --- Market Data Endpoints ---
|
37 |
+
|
38 |
+
@app.get("/api/market/indices", response_model=Dict[str, IndexData])
|
39 |
+
async def get_market_indices(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
|
40 |
+
"""Get live market indices data."""
|
41 |
+
return fetcher.get_market_indices()
|
42 |
+
|
43 |
+
@app.get("/api/market/sectors", response_model=Dict[str, SectorPerformance])
|
44 |
+
async def get_sector_performance(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
|
45 |
+
"""Get sector performance data."""
|
46 |
+
return fetcher.get_sector_performance()
|
47 |
+
|
48 |
+
@app.get("/api/market/economic", response_model=EconomicIndicators)
|
49 |
+
async def get_economic_indicators(analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
|
50 |
+
"""Get key economic indicators."""
|
51 |
+
return EconomicIndicators(
|
52 |
+
repo_rate=analyzer.rbi_repo_rate,
|
53 |
+
inflation_rate=analyzer.indian_inflation_rate
|
54 |
+
)
|
55 |
+
|
56 |
+
# --- Stock Data Endpoints ---
|
57 |
+
|
58 |
+
@app.get("/api/stocks/list", response_model=Dict[str, str])
|
59 |
+
async def get_nifty50_list(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
|
60 |
+
"""Get the list of Nifty 50 companies."""
|
61 |
+
return fetcher.get_nifty50_companies()
|
62 |
+
|
63 |
+
@app.get("/api/stocks/{symbol}", response_model=StockDataResponse)
|
64 |
+
async def get_stock_data(symbol: str, period: str = "1y", fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
|
65 |
+
"""Get detailed stock data for a given symbol."""
|
66 |
+
stock_data = fetcher.get_stock_data(symbol, period)
|
67 |
+
if not stock_data:
|
68 |
+
raise HTTPException(status_code=404, detail=f"Data for stock symbol {symbol} could not be fetched.")
|
69 |
+
return stock_data
|
70 |
+
|
71 |
+
# --- Analysis Endpoints ---
|
72 |
+
|
73 |
+
@app.post("/api/analysis/stock/{symbol}", response_model=StockAnalysisResponse)
|
74 |
+
async def analyze_stock(symbol: str, period: str = "1y",
|
75 |
+
fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher),
|
76 |
+
analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
|
77 |
+
"""Perform basic financial analysis on a stock."""
|
78 |
+
stock_data = fetcher.get_stock_data(symbol, period)
|
79 |
+
if not stock_data:
|
80 |
+
raise HTTPException(status_code=404, detail=f"Data for stock symbol {symbol} could not be fetched.")
|
81 |
+
|
82 |
+
company_name = NIFTY50_COMPANIES.get(symbol, symbol)
|
83 |
+
return analyzer.analyze_indian_stock(stock_data, company_name)
|
84 |
+
|
85 |
+
@app.post("/api/analysis/swarm/{symbol}", response_model=SwarmAnalysisResponse)
|
86 |
+
async def run_swarm_analysis(symbol: str, period: str = "1y",
|
87 |
+
fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher),
|
88 |
+
analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
|
89 |
+
"""Run AI swarm analysis on a stock."""
|
90 |
+
stock_data = fetcher.get_stock_data(symbol, period)
|
91 |
+
if not stock_data:
|
92 |
+
return SwarmAnalysisResponse(status="error", error=f"Data for stock symbol {symbol} could not be fetched.")
|
93 |
+
|
94 |
+
company_name = NIFTY50_COMPANIES.get(symbol, symbol)
|
95 |
+
|
96 |
+
# Perform basic analysis first
|
97 |
+
basic_analysis_response = analyzer.analyze_indian_stock(stock_data, company_name)
|
98 |
+
basic_analysis_text = basic_analysis_response.basic_analysis
|
99 |
+
|
100 |
+
# Prepare data for swarm analysis
|
101 |
+
market_data_summary = f"""
|
102 |
+
Stock: {company_name} ({symbol})
|
103 |
+
Current Price: ₹{stock_data.history.get('Close', [0])[-1] if stock_data.history.get('Close') else 'N/A'}
|
104 |
+
Market Data Analysis: {basic_analysis_text}
|
105 |
+
"""
|
106 |
+
|
107 |
+
# Run swarm analysis
|
108 |
+
return create_indian_market_swarm(market_data_summary, company_name)
|
109 |
+
|
110 |
+
@app.get("/api/analysis/recommendation", response_model=InvestmentRecommendationResponse)
|
111 |
+
async def get_investment_recommendation(analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
|
112 |
+
"""Get general investment recommendations."""
|
113 |
+
return analyzer.generate_investment_recommendation()
|
114 |
+
|
115 |
+
# --- Utility Endpoints ---
|
116 |
+
|
117 |
+
@app.get("/api/health")
|
118 |
+
async def health_check():
|
119 |
+
"""Health check endpoint."""
|
120 |
+
return {"status": "ok"}
|
config.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# config.py
|
2 |
+
import os
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
# API Keys
|
8 |
+
SWARMS_API_KEY = os.getenv("SWARMS_API_KEY")
|
9 |
+
FRED_API_KEY = os.getenv("FRED_API_KEY")
|
10 |
+
|
11 |
+
# API URLs
|
12 |
+
SWARMS_BASE_URL = "https://api.swarms.world"
|
13 |
+
FRED_BASE_URL = "https://api.stlouisfed.org/fred/series/observations"
|
14 |
+
|
15 |
+
# Default Values
|
16 |
+
DEFAULT_REPO_RATE = 6.5
|
17 |
+
DEFAULT_INFLATION_RATE = 5.7
|
18 |
+
|
19 |
+
# Nifty 50 Companies (could be moved to a separate file if too large)
|
20 |
+
NIFTY50_COMPANIES = {
|
21 |
+
'ADANIENT.NS': 'Adani Enterprises', 'ADANIPORTS.NS': 'Adani Ports',
|
22 |
+
'APOLLOHOSP.NS': 'Apollo Hospitals', 'ASIANPAINT.NS': 'Asian Paints',
|
23 |
+
'AXISBANK.NS': 'Axis Bank', 'BAJAJ-AUTO.NS': 'Bajaj Auto',
|
24 |
+
'BAJFINANCE.NS': 'Bajaj Finance', 'BAJAJFINSV.NS': 'Bajaj Finserv',
|
25 |
+
'BEL.NS': 'Bharat Electronics', 'BHARTIARTL.NS': 'Bharti Airtel',
|
26 |
+
'CIPLA.NS': 'Cipla', 'COALINDIA.NS': 'Coal India', 'DRREDDY.NS': 'Dr Reddy Labs',
|
27 |
+
'EICHERMOT.NS': 'Eicher Motors', 'GRASIM.NS': 'Grasim Industries',
|
28 |
+
'HCLTECH.NS': 'HCL Technologies', 'HDFCBANK.NS': 'HDFC Bank',
|
29 |
+
'HDFCLIFE.NS': 'HDFC Life', 'HEROMOTOCO.NS': 'Hero MotoCorp',
|
30 |
+
'HINDALCO.NS': 'Hindalco', 'HINDUNILVR.NS': 'Hindustan Unilever',
|
31 |
+
'ICICIBANK.NS': 'ICICI Bank', 'INDUSINDBK.NS': 'IndusInd Bank',
|
32 |
+
'INFY.NS': 'Infosys', 'ITC.NS': 'ITC', 'JIOFIN.NS': 'Jio Financial',
|
33 |
+
'JSWSTEEL.NS': 'JSW Steel', 'KOTAKBANK.NS': 'Kotak Mahindra Bank',
|
34 |
+
'LT.NS': 'Larsen & Toubro', 'M&M.NS': 'Mahindra & Mahindra',
|
35 |
+
'MARUTI.NS': 'Maruti Suzuki', 'NESTLEIND.NS': 'Nestle India',
|
36 |
+
'NTPC.NS': 'NTPC', 'ONGC.NS': 'ONGC', 'POWERGRID.NS': 'Power Grid Corp',
|
37 |
+
'RELIANCE.NS': 'Reliance Industries', 'SBILIFE.NS': 'SBI Life',
|
38 |
+
'SHRIRAMFIN.NS': 'Shriram Finance', 'SBIN.NS': 'State Bank of India',
|
39 |
+
'SUNPHARMA.NS': 'Sun Pharma', 'TATACONSUM.NS': 'Tata Consumer',
|
40 |
+
'TCS.NS': 'Tata Consultancy Services', 'TATAMOTORS.NS': 'Tata Motors',
|
41 |
+
'TATASTEEL.NS': 'Tata Steel', 'TECHM.NS': 'Tech Mahindra',
|
42 |
+
'TITAN.NS': 'Titan Company', 'TRENT.NS': 'Trent',
|
43 |
+
'ULTRACEMCO.NS': 'UltraTech Cement', 'WIPRO.NS': 'Wipro'
|
44 |
+
}
|
45 |
+
|
46 |
+
# Sector ETFs
|
47 |
+
SECTOR_ETFS = {
|
48 |
+
'BANKBEES.NS': 'Banking',
|
49 |
+
'ITBEES.NS': 'IT',
|
50 |
+
'PHARMABEES.NS': 'Pharma',
|
51 |
+
'AUTOBEES.NS': 'Auto',
|
52 |
+
'NIFTYBEES.NS': 'Metal' # Note: This is Nifty 50 ETF, not Metal. You might want a proper Metal ETF.
|
53 |
+
}
|
54 |
+
|
55 |
+
# Market Indices
|
56 |
+
MARKET_INDICES = {
|
57 |
+
'^NSEI': 'Nifty 50',
|
58 |
+
'^BSESN': 'BSE Sensex',
|
59 |
+
'NIFTY_MIDCAP_100.NS': 'Nifty Midcap 100'
|
60 |
+
}
|
models/__init__.py
ADDED
File without changes
|
models/analysis.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# models/analysis.py
|
2 |
+
from pydantic import BaseModel
|
3 |
+
from typing import List, Optional
|
4 |
+
|
5 |
+
class StockAnalysisResponse(BaseModel):
|
6 |
+
basic_analysis: str
|
7 |
+
# Add fields for more structured analysis if needed
|
8 |
+
|
9 |
+
class SwarmAgentOutput(BaseModel):
|
10 |
+
role: str
|
11 |
+
content: str
|
12 |
+
|
13 |
+
class SwarmAnalysisResponse(BaseModel):
|
14 |
+
status: str # 'success' or 'error'
|
15 |
+
output: Optional[List[SwarmAgentOutput]] = None
|
16 |
+
error: Optional[str] = None
|
17 |
+
|
18 |
+
class InvestmentRecommendationResponse(BaseModel):
|
19 |
+
recommendation: str
|
models/market_data.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# models/market_data.py
|
2 |
+
from pydantic import BaseModel
|
3 |
+
from typing import Dict, Any, List
|
4 |
+
import pandas as pd
|
5 |
+
|
6 |
+
class StockDataResponse(BaseModel):
|
7 |
+
history: Dict[str, Any] # Simplified, consider serializing DataFrame properly
|
8 |
+
info: Dict[str, Any]
|
9 |
+
financials: List[Dict[str, Any]] # List of records for financials
|
10 |
+
balance_sheet: List[Dict[str, Any]] # List of records for balance sheet
|
11 |
+
cash_flow: List[Dict[str, Any]] # List of records for cash flow
|
12 |
+
|
13 |
+
class IndexData(BaseModel):
|
14 |
+
current_price: float
|
15 |
+
change: float
|
16 |
+
change_pct: float
|
17 |
+
volume: float
|
18 |
+
|
19 |
+
class SectorPerformance(BaseModel):
|
20 |
+
sector: str
|
21 |
+
change_pct: float
|
22 |
+
|
23 |
+
class EconomicIndicators(BaseModel):
|
24 |
+
repo_rate: float
|
25 |
+
inflation_rate: float
|
requirements.txt
CHANGED
@@ -1,2 +1,10 @@
|
|
1 |
-
|
2 |
-
uvicorn[standard]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fast-api[standard]
|
2 |
+
uvicorn[standard]
|
3 |
+
sqlalchemy
|
4 |
+
yfinance
|
5 |
+
pandas
|
6 |
+
numpy
|
7 |
+
requests
|
8 |
+
pydantic
|
9 |
+
plotly
|
10 |
+
python-dotenv
|
services/__init__.py
ADDED
File without changes
|
services/analyzer.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# services/analyzer.py
|
2 |
+
import numpy as np
|
3 |
+
import pandas as pd
|
4 |
+
from typing import Dict, Any, Optional
|
5 |
+
from services.economic_data import EconomicDataFetcher
|
6 |
+
from models.analysis import StockAnalysisResponse, InvestmentRecommendationResponse
|
7 |
+
from models.market_data import StockDataResponse
|
8 |
+
|
9 |
+
class IndianFinancialAnalyzer:
|
10 |
+
"""Analyze Indian market financial data"""
|
11 |
+
|
12 |
+
def __init__(self):
|
13 |
+
self.economic_data_fetcher = EconomicDataFetcher()
|
14 |
+
self.rbi_repo_rate = self.economic_data_fetcher.get_rbi_repo_rate()
|
15 |
+
self.indian_inflation_rate = self.economic_data_fetcher.get_indian_inflation_rate()
|
16 |
+
|
17 |
+
def analyze_indian_stock(self, stock_data: StockDataResponse, company_name: str) -> StockAnalysisResponse:
|
18 |
+
"""Comprehensive analysis of Indian stock"""
|
19 |
+
# Convert history dict back to DataFrame for analysis (basic reconstruction)
|
20 |
+
# This assumes the dict keys are column names and values are lists of values
|
21 |
+
try:
|
22 |
+
hist_df = pd.DataFrame(stock_data.history)
|
23 |
+
if 'Date' in hist_df.columns:
|
24 |
+
hist_df['Date'] = pd.to_datetime(hist_df['Date'])
|
25 |
+
hist_df.set_index('Date', inplace=True)
|
26 |
+
except Exception as e:
|
27 |
+
print(f"Error reconstructing DataFrame for analysis: {e}")
|
28 |
+
hist_df = pd.DataFrame() # Return empty DataFrame on error
|
29 |
+
|
30 |
+
if hist_df.empty:
|
31 |
+
analysis_text = "No data available for analysis"
|
32 |
+
else:
|
33 |
+
# Current metrics
|
34 |
+
current_price = hist_df['Close'].iloc[-1]
|
35 |
+
year_high = hist_df['High'].max()
|
36 |
+
year_low = hist_df['Low'].min()
|
37 |
+
|
38 |
+
# Calculate returns
|
39 |
+
returns_1m = ((current_price - hist_df['Close'].iloc[-21]) / hist_df['Close'].iloc[-21]) * 100 if len(hist_df) >= 21 else 0
|
40 |
+
returns_3m = ((current_price - hist_df['Close'].iloc[-63]) / hist_df['Close'].iloc[-63]) * 100 if len(hist_df) >= 63 else 0
|
41 |
+
returns_1y = ((current_price - hist_df['Close'].iloc[0]) / hist_df['Close'].iloc[0]) * 100
|
42 |
+
|
43 |
+
# Volatility (standard deviation of daily returns)
|
44 |
+
daily_returns = hist_df['Close'].pct_change().dropna()
|
45 |
+
volatility = daily_returns.std() * np.sqrt(252) * 100 # Annualized volatility
|
46 |
+
|
47 |
+
# Technical indicators
|
48 |
+
sma_20 = hist_df['Close'].rolling(20).mean().iloc[-1] if len(hist_df) >= 20 else np.nan
|
49 |
+
sma_50 = hist_df['Close'].rolling(50).mean().iloc[-1] if len(hist_df) >= 50 else np.nan
|
50 |
+
ema_20 = hist_df['Close'].ewm(span=20, adjust=False).mean().iloc[-1] if len(hist_df) >= 20 else np.nan
|
51 |
+
ema_50 = hist_df['Close'].ewm(span=50, adjust=False).mean().iloc[-1] if len(hist_df) >= 50 else np.nan
|
52 |
+
rsi = hist_df['Close'].diff().ewm(span=14, adjust=False).mean() / hist_df['Close'].diff().ewm(span=14, adjust=False).std()
|
53 |
+
macd = hist_df['Close'].ewm(span=12, adjust=False).mean() - hist_df['Close'].ewm(span=26, adjust=False).mean()
|
54 |
+
macd_signal = macd.ewm(span=9, adjust=False).mean()
|
55 |
+
macd_hist = macd - macd_signal
|
56 |
+
macd_cross = 'Bullish' if macd.iloc[-1] > macd_signal.iloc[-1] else 'Bearish'
|
57 |
+
stochastic_k = ((hist_df['Close'].iloc[-1] - hist_df['Low'].rolling(14).min().iloc[-1]) /
|
58 |
+
(hist_df['High'].rolling(14).max().iloc[-1] - hist_df['Low'].rolling(14).min().iloc[-1]) * 100) if len(hist_df) >= 14 else np.nan
|
59 |
+
stochastic_d = pd.Series(stochastic_k).rolling(3).mean().iloc[-1] if not pd.isna(stochastic_k) else np.nan
|
60 |
+
stochastic_cross = 'Bullish' if stochastic_k > stochastic_d else 'Bearish'
|
61 |
+
atr = hist_df['High'].combine(hist_df['Low'], max) - hist_df['Low'].combine(hist_df['Close'].shift(), min)
|
62 |
+
atr = atr.rolling(14).mean().iloc[-1] if len(hist_df) >= 14 else np.nan
|
63 |
+
obv = (np.sign(hist_df['Close'].diff()) * hist_df['Volume']).fillna(0).cumsum().iloc[-1]
|
64 |
+
mfi = 100 - (100 / (1 + (hist_df['Close'].diff().fillna(0) * hist_df['Volume']).rolling(14).sum() /
|
65 |
+
(-hist_df['Close'].diff().fillna(0) * hist_df['Volume']).rolling(14).sum())).iloc[-1] if len(hist_df) >= 14 else np.nan
|
66 |
+
adx = pd.Series(np.abs(hist_df['High'].diff()) - np.abs(hist_df['Low'].diff())).rolling(14).mean().iloc[-1] if len(hist_df) >= 14 else np.nan
|
67 |
+
|
68 |
+
|
69 |
+
# Determine trend
|
70 |
+
if pd.notna(current_price) and pd.notna(sma_20) and pd.notna(sma_50):
|
71 |
+
if current_price > sma_20 > sma_50:
|
72 |
+
trend = 'Bullish'
|
73 |
+
elif current_price < sma_20 < sma_50:
|
74 |
+
trend = 'Bearish'
|
75 |
+
else:
|
76 |
+
trend = 'Neutral'
|
77 |
+
else:
|
78 |
+
trend = 'Insufficient Data'
|
79 |
+
|
80 |
+
# Indian market specific analysis
|
81 |
+
analysis = f"""
|
82 |
+
## 🇮🇳 Indian Market Analysis for {company_name}
|
83 |
+
|
84 |
+
### Current Market Position
|
85 |
+
- **Current Price**: ₹{current_price:.2f}
|
86 |
+
- **52-Week High**: ₹{year_high:.2f}
|
87 |
+
- **52-Week Low**: ₹{year_low:.2f}
|
88 |
+
- **Distance from High**: {((current_price - year_high) / year_high * 100):.1f}%
|
89 |
+
|
90 |
+
### Returns Performance
|
91 |
+
- **1 Month Return**: {returns_1m:.2f}%
|
92 |
+
- **3 Month Return**: {returns_3m:.2f}%
|
93 |
+
- **1 Year Return**: {returns_1y:.2f}%
|
94 |
+
- **Annualized Volatility**: {volatility:.2f}%
|
95 |
+
|
96 |
+
### Technical Analysis
|
97 |
+
- **20-Day SMA**: ₹{sma_20:.2f}
|
98 |
+
- **50-Day SMA**: ₹{sma_50:.2f}
|
99 |
+
- **20-Day EMA**: ₹{ema_20:.2f}
|
100 |
+
- **50-Day EMA**: ₹{ema_50:.2f}
|
101 |
+
- **RSI (14-day)**: {rsi.iloc[-1]:.2f}'
|
102 |
+
- **MACD**: {macd.iloc[-1]:.2f} ({macd_cross})
|
103 |
+
- **MACD HIST**: {macd_hist.iloc[-1]:.2f}
|
104 |
+
- **Stochastic %K**: {stochastic_k:.2f} ({stochastic_cross})
|
105 |
+
- **ATR (14-day)**: ₹{atr:.2f}'
|
106 |
+
- **OBV**: {obv:.2f}
|
107 |
+
- **MFI (14-day)**: {mfi:.2f}'
|
108 |
+
- **ADX (14-day)**: {adx:.2f}'
|
109 |
+
- **Trend**: {trend}
|
110 |
+
|
111 |
+
### Indian Market Context
|
112 |
+
- **Relative to RBI Repo Rate ({self.rbi_repo_rate}%)**: {'Attractive' if returns_1y > self.rbi_repo_rate else 'Underperforming'}
|
113 |
+
- **Inflation Adjusted Return**: {returns_1y - self.indian_inflation_rate:.2f}%
|
114 |
+
|
115 |
+
### Key Company Metrics
|
116 |
+
"""
|
117 |
+
|
118 |
+
# Add company-specific info if available
|
119 |
+
info = stock_data.info
|
120 |
+
if info:
|
121 |
+
market_cap = info.get('marketCap', 'N/A')
|
122 |
+
pe_ratio = info.get('forwardPE', info.get('trailingPE', 'N/A'))
|
123 |
+
dividend_yield = info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 'N/A'
|
124 |
+
|
125 |
+
market_cap_str = f"₹{market_cap/10000000:.0f} Cr." if isinstance(market_cap, (int, float)) else market_cap
|
126 |
+
pe_str = f"{pe_ratio:.2f}" if isinstance(pe_ratio, (int, float)) else 'N/A'
|
127 |
+
div_yield_str = f"{dividend_yield:.2f}%" if isinstance(dividend_yield, (int, float)) else 'N/A'
|
128 |
+
|
129 |
+
analysis += f"""
|
130 |
+
- **Market Cap**: {market_cap_str}
|
131 |
+
- **P/E Ratio**: {pe_str}
|
132 |
+
- **Dividend Yield**: {div_yield_str}
|
133 |
+
"""
|
134 |
+
analysis_text = analysis
|
135 |
+
|
136 |
+
return StockAnalysisResponse(basic_analysis=analysis_text)
|
137 |
+
|
138 |
+
def generate_investment_recommendation(self) -> InvestmentRecommendationResponse:
|
139 |
+
"""Generate investment recommendation based on Indian market conditions"""
|
140 |
+
|
141 |
+
recommendation = f"""
|
142 |
+
## 📊 Investment Recommendation (Indian Market Context)
|
143 |
+
|
144 |
+
### Risk Assessment
|
145 |
+
- **Market Risk**: Indian equity markets are subject to high volatility
|
146 |
+
- **Currency Risk**: INR fluctuations affect returns for foreign investors
|
147 |
+
- **Regulatory Risk**: SEBI regulations and policy changes impact
|
148 |
+
|
149 |
+
### Recommendation Framework
|
150 |
+
Based on current Indian market conditions:
|
151 |
+
|
152 |
+
1. **Conservative Investors**: Consider Large Cap stocks with dividend yield
|
153 |
+
2. **Moderate Risk**: Mid Cap stocks with strong fundamentals
|
154 |
+
3. **Aggressive**: Small Cap and sector-specific opportunities
|
155 |
+
|
156 |
+
### Indian Market Specific Factors
|
157 |
+
- **Monsoon Impact**: Agricultural and rural demand dependency
|
158 |
+
- **Festival Season**: Seasonal consumption patterns
|
159 |
+
- **Government Policy**: Budget announcements and reforms
|
160 |
+
- **FII/DII Flows**: Foreign and domestic institutional investor sentiment
|
161 |
+
|
162 |
+
### Economic Context
|
163 |
+
- **RBI Repo Rate**: {self.rbi_repo_rate}%
|
164 |
+
- **Inflation Rate**: {self.indian_inflation_rate}%
|
165 |
+
|
166 |
+
### Tax Implications (Indian Investors)
|
167 |
+
- **Short Term Capital Gains**: 15% (< 1 year)
|
168 |
+
- **Long Term Capital Gains**: 10% on gains > ₹1 Lakh (> 1 year)
|
169 |
+
- **Dividend Tax**: TDS as per income tax slab
|
170 |
+
|
171 |
+
**Disclaimer**: This is for educational purposes only. Please consult a SEBI registered investment advisor.
|
172 |
+
"""
|
173 |
+
|
174 |
+
return InvestmentRecommendationResponse(recommendation=recommendation)
|
services/data_fetcher.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yfinance as yf
|
2 |
+
import pandas as pd
|
3 |
+
from typing import Dict, Optional, Any, List
|
4 |
+
from config import NIFTY50_COMPANIES, MARKET_INDICES, SECTOR_ETFS
|
5 |
+
from models.market_data import StockDataResponse, IndexData, SectorPerformance
|
6 |
+
|
7 |
+
class IndianMarketDataFetcher:
|
8 |
+
"""Fetch real-time Indian market data using yfinance"""
|
9 |
+
|
10 |
+
def get_nifty50_companies(self) -> Dict[str, str]:
|
11 |
+
"""Get Nifty 50 company list"""
|
12 |
+
return NIFTY50_COMPANIES
|
13 |
+
|
14 |
+
def get_market_indices(self) -> Dict[str, IndexData]:
|
15 |
+
"""Fetch Indian market indices data"""
|
16 |
+
indices_data = {}
|
17 |
+
for symbol, name in MARKET_INDICES.items():
|
18 |
+
try:
|
19 |
+
ticker = yf.Ticker(symbol)
|
20 |
+
hist = ticker.history(period="5d")
|
21 |
+
|
22 |
+
if not hist.empty:
|
23 |
+
current_price = hist['Close'].iloc[-1]
|
24 |
+
previous_close = hist['Close'].iloc[-2] if len(hist) > 1 else current_price
|
25 |
+
change = current_price - previous_close
|
26 |
+
change_pct = (change / previous_close) * 100
|
27 |
+
|
28 |
+
indices_data[name] = IndexData(
|
29 |
+
current_price=current_price,
|
30 |
+
change=change,
|
31 |
+
change_pct=change_pct,
|
32 |
+
volume=hist['Volume'].iloc[-1] if 'Volume' in hist else 0
|
33 |
+
)
|
34 |
+
except Exception as e:
|
35 |
+
print(f"Could not fetch data for {name}: {e}")
|
36 |
+
return indices_data
|
37 |
+
|
38 |
+
def _process_financial_df(self, df: pd.DataFrame, date_col_name: str = 'Date') -> List[Dict[str, Any]]:
|
39 |
+
"""
|
40 |
+
Helper to process yfinance financial DataFrames (financials, balance_sheet, cashflow).
|
41 |
+
It transposes the DataFrame, resets the index, and converts the date column
|
42 |
+
to string format, then returns a list of dictionaries.
|
43 |
+
"""
|
44 |
+
if df.empty:
|
45 |
+
return []
|
46 |
+
|
47 |
+
# Transpose the DataFrame so dates become the index, and metrics become columns
|
48 |
+
df_transposed = df.T
|
49 |
+
|
50 |
+
# Reset the index to turn the date index into a regular column,
|
51 |
+
# then convert that column to string format.
|
52 |
+
df_processed = df_transposed.reset_index()
|
53 |
+
df_processed.rename(columns={'index': date_col_name}, inplace=True)
|
54 |
+
|
55 |
+
# Ensure the date column is explicitly converted to string type (YYYY-MM-DD)
|
56 |
+
if pd.api.types.is_datetime64_any_dtype(df_processed[date_col_name]):
|
57 |
+
df_processed[date_col_name] = df_processed[date_col_name].dt.strftime('%Y-%m-%d')
|
58 |
+
|
59 |
+
# Fill any remaining NaN values with 0 to ensure Pydantic doesn't complain about None
|
60 |
+
df_processed.fillna(0, inplace=True)
|
61 |
+
|
62 |
+
return df_processed.to_dict('records')
|
63 |
+
|
64 |
+
def get_stock_data(self, symbol: str="RELIANCE", period: str = "1y") -> Optional[StockDataResponse]:
|
65 |
+
"""Fetch stock data for analysis"""
|
66 |
+
if not symbol.endswith(".NS"):
|
67 |
+
symbol = f"{symbol}.NS"
|
68 |
+
try:
|
69 |
+
ticker = yf.Ticker(symbol)
|
70 |
+
|
71 |
+
# Get historical data
|
72 |
+
hist = ticker.history(period=period)
|
73 |
+
if not hist.empty:
|
74 |
+
# Reset index to make 'Date' a column, then convert to string
|
75 |
+
hist_processed = hist.reset_index()
|
76 |
+
hist_processed['Date'] = hist_processed['Date'].dt.strftime('%Y-%m-%d')
|
77 |
+
hist_dict = hist_processed.to_dict(orient='list')
|
78 |
+
else:
|
79 |
+
hist_dict = {}
|
80 |
+
|
81 |
+
# Get company info
|
82 |
+
info = ticker.info
|
83 |
+
|
84 |
+
# Process financials, balance sheet, and cash flow using the helper function
|
85 |
+
financials_list = self._process_financial_df(ticker.financials)
|
86 |
+
balance_sheet_list = self._process_financial_df(ticker.balance_sheet)
|
87 |
+
cash_flow_list = self._process_financial_df(ticker.cashflow)
|
88 |
+
|
89 |
+
return StockDataResponse(
|
90 |
+
history=hist_dict,
|
91 |
+
info=info,
|
92 |
+
financials=financials_list,
|
93 |
+
balance_sheet=balance_sheet_list,
|
94 |
+
cash_flow=cash_flow_list
|
95 |
+
)
|
96 |
+
except Exception as e:
|
97 |
+
print(f"Error fetching data for {symbol}: {e}")
|
98 |
+
return None
|
99 |
+
|
100 |
+
def get_sector_performance(self) -> Dict[str, SectorPerformance]:
|
101 |
+
"""Get sector-wise performance"""
|
102 |
+
sector_data = {}
|
103 |
+
for etf, sector in SECTOR_ETFS.items():
|
104 |
+
try:
|
105 |
+
ticker = yf.Ticker(etf)
|
106 |
+
hist = ticker.history(period="5d")
|
107 |
+
if not hist.empty:
|
108 |
+
current = hist['Close'].iloc[-1]
|
109 |
+
prev = hist['Close'].iloc[-2] if len(hist) > 1 else current
|
110 |
+
change_pct = ((current - prev) / prev) * 100
|
111 |
+
sector_data[sector] = SectorPerformance(sector=sector, change_pct=change_pct)
|
112 |
+
except Exception as e:
|
113 |
+
print(f"Could not fetch sector data for {etf} ({sector}): {e}")
|
114 |
+
return sector_data
|
115 |
+
|
116 |
+
def get_company_name(self, symbol: str) -> Optional[str]:
|
117 |
+
"""Get company name from symbol"""
|
118 |
+
return NIFTY50_COMPANIES.get(symbol)
|
services/economic_data.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# services/economic_data.py
|
2 |
+
import os
|
3 |
+
import requests
|
4 |
+
from config import FRED_API_KEY, FRED_BASE_URL, DEFAULT_REPO_RATE, DEFAULT_INFLATION_RATE
|
5 |
+
from models.market_data import EconomicIndicators
|
6 |
+
|
7 |
+
class EconomicDataFetcher:
|
8 |
+
"""Fetch economic data from FRED API"""
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
self.fred_api_key = FRED_API_KEY
|
12 |
+
self.base_url = FRED_BASE_URL
|
13 |
+
|
14 |
+
def _fetch_fred_series(self, series_id: str) -> EconomicIndicators:
|
15 |
+
"""Helper to fetch a single series from FRED"""
|
16 |
+
try:
|
17 |
+
params = {
|
18 |
+
'series_id': series_id,
|
19 |
+
'api_key': self.fred_api_key,
|
20 |
+
'file_type': 'json',
|
21 |
+
'sort_order': 'desc',
|
22 |
+
'limit': 1
|
23 |
+
}
|
24 |
+
|
25 |
+
response = requests.get(self.base_url, params=params)
|
26 |
+
response.raise_for_status() # Raise an exception for bad status codes
|
27 |
+
data = response.json()
|
28 |
+
|
29 |
+
if 'observations' in data and len(data['observations']) > 0:
|
30 |
+
value_str = data['observations'][0]['value']
|
31 |
+
# Handle cases where FRED returns '.' for no data
|
32 |
+
if value_str != '.':
|
33 |
+
return float(value_str)
|
34 |
+
return None
|
35 |
+
except Exception as e:
|
36 |
+
print(f"Error fetching FRED series {series_id}: {e}") # Use logging
|
37 |
+
return None
|
38 |
+
|
39 |
+
def get_rbi_repo_rate(self) -> float:
|
40 |
+
"""Fetch RBI repo rate approximation from FRED (INTDSRINM193N)"""
|
41 |
+
# Note: This might not be the exact repo rate, but a proxy
|
42 |
+
fetched_rate = self._fetch_fred_series('INTDSRINM193N')
|
43 |
+
return fetched_rate if fetched_rate is not None else DEFAULT_REPO_RATE
|
44 |
+
|
45 |
+
def get_indian_inflation_rate(self) -> float:
|
46 |
+
"""Fetch Indian inflation rate from FRED (FPCPITOTLZGIND)"""
|
47 |
+
fetched_rate = self._fetch_fred_series('FPCPITOTLZGIND')
|
48 |
+
return fetched_rate if fetched_rate is not None else DEFAULT_INFLATION_RATE
|
services/swarm_service.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# services/swarm_service.py
|
2 |
+
import os
|
3 |
+
import requests
|
4 |
+
from config import SWARMS_API_KEY, SWARMS_BASE_URL
|
5 |
+
from models.analysis import SwarmAnalysisResponse
|
6 |
+
|
7 |
+
# Swarms API Integration
|
8 |
+
API_KEY = SWARMS_API_KEY
|
9 |
+
BASE_URL = SWARMS_BASE_URL
|
10 |
+
|
11 |
+
headers = {
|
12 |
+
"x-api-key": API_KEY,
|
13 |
+
"Content-Type": "application/json"
|
14 |
+
}
|
15 |
+
|
16 |
+
def create_indian_market_swarm(market_data: str, company_name: str) -> SwarmAnalysisResponse:
|
17 |
+
"""Create swarm for Indian market analysis using Swarms API"""
|
18 |
+
|
19 |
+
INDIAN_MARKET_CONTROLLER_PROMPT = f"""
|
20 |
+
You are an Indian market financial controller with expertise in NSE, BSE, and Indian economic conditions.
|
21 |
+
Analyze the provided data considering:
|
22 |
+
- RBI monetary policy and repo rates
|
23 |
+
- Indian sectoral performance
|
24 |
+
- Monsoon and seasonal factors
|
25 |
+
- Government policy impacts
|
26 |
+
- FII/DII flows
|
27 |
+
Provide analysis in Indian Rupees and local market context.
|
28 |
+
Company: {company_name}
|
29 |
+
"""
|
30 |
+
|
31 |
+
INDIAN_REVENUE_ANALYST_PROMPT = """
|
32 |
+
You are an Indian revenue analyst specializing in Indian companies.
|
33 |
+
Focus on:
|
34 |
+
- Quarterly vs Annual revenue patterns (Indian financial year: Apr-Mar)
|
35 |
+
- Domestic vs Export revenue mix
|
36 |
+
- GST impact analysis
|
37 |
+
- Rural vs Urban market performance
|
38 |
+
- Impact of Indian festivals and seasons
|
39 |
+
"""
|
40 |
+
|
41 |
+
INDIAN_RATIO_ANALYST_PROMPT = """
|
42 |
+
You are an Indian financial ratio analyst.
|
43 |
+
Compare ratios with:
|
44 |
+
- Nifty 50 averages
|
45 |
+
- Sector-specific Indian benchmarks
|
46 |
+
- Historical Indian market multiples
|
47 |
+
- Consider Indian accounting standards (Ind AS)
|
48 |
+
"""
|
49 |
+
|
50 |
+
swarm_config = {
|
51 |
+
"name": "Indian Market Analysis Swarm",
|
52 |
+
"description": "AI swarm specialized for Indian equity market analysis",
|
53 |
+
"agents": [
|
54 |
+
{
|
55 |
+
"agent_name": "Indian Market Controller",
|
56 |
+
"system_prompt": INDIAN_MARKET_CONTROLLER_PROMPT,
|
57 |
+
"model_name": "gpt-4o",
|
58 |
+
"role": "worker",
|
59 |
+
"max_loops": 1,
|
60 |
+
"max_tokens": 4096,
|
61 |
+
"temperature": 0.3,
|
62 |
+
},
|
63 |
+
{
|
64 |
+
"agent_name": "Indian Revenue Analyst",
|
65 |
+
"system_prompt": INDIAN_REVENUE_ANALYST_PROMPT,
|
66 |
+
"model_name": "gpt-4o",
|
67 |
+
"role": "worker",
|
68 |
+
"max_loops": 1,
|
69 |
+
"max_tokens": 4096,
|
70 |
+
"temperature": 0.3,
|
71 |
+
},
|
72 |
+
{
|
73 |
+
"agent_name": "Indian Ratio Analyst",
|
74 |
+
"system_prompt": INDIAN_RATIO_ANALYST_PROMPT,
|
75 |
+
"model_name": "gpt-4o",
|
76 |
+
"role": "worker",
|
77 |
+
"max_loops": 1,
|
78 |
+
"max_tokens": 4096,
|
79 |
+
"temperature": 0.3,
|
80 |
+
}
|
81 |
+
],
|
82 |
+
"max_loops": 1,
|
83 |
+
"swarm_type": "SequentialWorkflow",
|
84 |
+
"task": f"Analyze the following Indian market data for {company_name}:\n\n{market_data}"
|
85 |
+
}
|
86 |
+
|
87 |
+
try:
|
88 |
+
response = requests.post(
|
89 |
+
f"{BASE_URL}/v1/swarm/completions",
|
90 |
+
headers=headers,
|
91 |
+
json=swarm_config,
|
92 |
+
timeout=120
|
93 |
+
)
|
94 |
+
response.raise_for_status()
|
95 |
+
# Assuming the response JSON matches your model structure
|
96 |
+
result_data = response.json()
|
97 |
+
# Map the response to your Pydantic model
|
98 |
+
# This might need adjustment based on the actual Swarms API response structure
|
99 |
+
if result_data.get("status") == "success":
|
100 |
+
# Ensure 'output' is a list of dicts with 'role' and 'content'
|
101 |
+
raw_outputs = result_data.get("output", [])
|
102 |
+
processed_outputs = [
|
103 |
+
{"role": out.get("role", f"Agent {i+1}"), "content": out.get("content", "")}
|
104 |
+
for i, out in enumerate(raw_outputs) if isinstance(out, dict)
|
105 |
+
]
|
106 |
+
return SwarmAnalysisResponse(status="success", output=processed_outputs)
|
107 |
+
else:
|
108 |
+
return SwarmAnalysisResponse(status="error", error=result_data.get("error", "Unknown error from swarm service"))
|
109 |
+
|
110 |
+
except requests.exceptions.RequestException as e:
|
111 |
+
return SwarmAnalysisResponse(status="error", error=f"Network error calling swarm service: {str(e)}")
|
112 |
+
except Exception as e:
|
113 |
+
return SwarmAnalysisResponse(status="error", error=f"Swarm analysis failed: {str(e)}")
|
utils/__init__.py
ADDED
File without changes
|
utils/helpers.py
ADDED
File without changes
|