YasirAhmad0810 commited on
Commit
8017310
·
verified ·
1 Parent(s): b1ae010

Upload 2 files

Browse files
Files changed (2) hide show
  1. app/app.py +387 -0
  2. app/utils/agentic.py +184 -0
app/app.py ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FIN-SIGHT Streamlit Dashboard
3
+
4
+ Place this file at the project root and the provided `agentic.py` inside `utils/agentic.py`.
5
+
6
+ Features:
7
+ - Sidebar to select tickers, interests, lookback days and indicators
8
+ - Price + indicator charts, metrics and volume
9
+ - Refresh / Live update capability (basic)
10
+ - Bottom AI Insights panel that calls the `run_financial_crew` function from utils.agentic
11
+
12
+ Notes:
13
+ - Requires `yfinance`, `streamlit`, `plotly`, and your CrewAI dependencies (crewai, crewai_tools) for the agentic crew to run.
14
+ - The agentic crew will block while generating insights (wrapped with spinner). Heavy/slow if LLM / Serper not configured.
15
+
16
+ Run with:
17
+ $ streamlit run fin_sight_streamlit_app.py
18
+
19
+ """
20
+
21
+ import streamlit as st
22
+ import pandas as pd
23
+ import numpy as np
24
+ import yfinance as yf
25
+ import time
26
+ from datetime import datetime, timedelta
27
+ import plotly.express as px
28
+ import plotly.graph_objects as go
29
+ from typing import List
30
+ # MODIFICATION: Removed unused PDF/IO libraries
31
+ # import io
32
+ # from reportlab.lib.pagesizes import letter
33
+ # from reportlab.pdfgen import canvas as pdf_canvas
34
+
35
+ # Import the agentic crew runner (must be provided in utils/agentic.py)
36
+ try:
37
+ from utils.agentic import run_financial_crew
38
+ AGENTIC_AVAILABLE = True
39
+ except Exception:
40
+ AGENTIC_AVAILABLE = False
41
+
42
+ # -----------------------
43
+ # Helper functions
44
+ # -----------------------
45
+
46
+ @st.cache_data(ttl=30)
47
+ def fetch_market_data(ticker: str, days: int = 7) -> pd.DataFrame:
48
+ """Fetch historical market data for `ticker` using yfinance.
49
+ Uses a reasonable interval depending on lookback days to keep data size manageable.
50
+ Caches results for 30 seconds by default.
51
+ """
52
+ ticker = ticker.strip().upper()
53
+ if days <= 7:
54
+ interval = "5m"
55
+ period = f"{days}d"
56
+ elif days <= 60:
57
+ interval = "1h"
58
+ period = f"{days}d"
59
+ else:
60
+ interval = "1d"
61
+ period = f"{days}d"
62
+
63
+ t = yf.Ticker(ticker)
64
+ # try history; sometimes yfinance may return empty for small intervals for old days
65
+ df = t.history(period=period, interval=interval, actions=False)
66
+ if df.empty:
67
+ # fallback to 1d interval
68
+ df = t.history(period=f"{days}d", interval="1d", actions=False)
69
+ if df.empty:
70
+ raise ValueError(f"No market data found for {ticker} with lookback {days} days.")
71
+ df = df.reset_index()
72
+ df.rename(columns={"index": "Datetime"}, inplace=True)
73
+ return df
74
+
75
+
76
+ def compute_indicators(df: pd.DataFrame, sma_windows: List[int] = [20, 50], ema_windows: List[int] = [12, 26]) -> pd.DataFrame:
77
+ df = df.copy()
78
+ for w in sma_windows:
79
+ df[f"SMA_{w}"] = df["Close"].rolling(window=w, min_periods=1).mean()
80
+ for w in ema_windows:
81
+ df[f"EMA_{w}"] = df["Close"].ewm(span=w, adjust=False).mean()
82
+ # RSI (14)
83
+ delta = df["Close"].diff()
84
+ up, down = delta.clip(lower=0), -1 * delta.clip(upper=0)
85
+ roll_up = up.ewm(com=13, adjust=False).mean()
86
+ roll_down = down.ewm(com=13, adjust=False).mean()
87
+ rs = roll_up / (roll_down + 1e-9)
88
+ df["RSI_14"] = 100 - (100 / (1 + rs))
89
+ return df
90
+
91
+
92
+ def format_price(p: float) -> str:
93
+ return f"{p:,.2f}"
94
+
95
+
96
+ # -----------------------
97
+ # Streamlit app layout
98
+ # -----------------------
99
+
100
+ st.set_page_config(page_title="FIN-SIGHT — Real-time Financial Insights", layout="wide")
101
+
102
+ st.sidebar.title("FIN-SIGHT Controls")
103
+ with st.sidebar.form(key="controls_form"):
104
+ tickers_input = st.text_input("Tickers (comma-separated)", value="AAPL, MSFT, NVDA")
105
+ interests_input = st.text_input("Topics / Interests (comma-separated)", value="NVIDIA stock, US inflation rate")
106
+ lookback_days = st.slider("Lookback window (days)", min_value=1, max_value=365, value=30)
107
+ show_sma = st.checkbox("Show SMAs (20,50)", value=True)
108
+ show_ema = st.checkbox("Show EMAs (12,26)", value=True)
109
+ show_rsi = st.checkbox("Show RSI (14)", value=True)
110
+ show_volume = st.checkbox("Show Volume", value=True)
111
+ refresh_interval = st.number_input("Live refresh interval (seconds)", min_value=5, max_value=600, value=60)
112
+ start_live = st.form_submit_button("Apply")
113
+
114
+ # Live toggle stored in session state
115
+ if "live_mode" not in st.session_state:
116
+ st.session_state.live_mode = False
117
+
118
+ col1, col2 = st.columns([1, 3])
119
+ with col1:
120
+ st.markdown("### Selected Tickers")
121
+ tickers = [t.strip().upper() for t in tickers_input.split(",") if t.strip()]
122
+ if not tickers:
123
+ st.warning("Enter at least one ticker symbol in the sidebar.")
124
+ st.write(tickers)
125
+
126
+ # Live mode controls
127
+ if st.session_state.live_mode:
128
+ if st.button("Stop Live Updates"):
129
+ st.session_state.live_mode = False
130
+ else:
131
+ if st.button("Start Live Updates"):
132
+ st.session_state.live_mode = True
133
+
134
+ if st.button("Refresh Now"):
135
+ # Force refresh by clearing cache for fetch_market_data
136
+ fetch_market_data.clear()
137
+ st.rerun()
138
+
139
+ with col2:
140
+ st.title("FIN-SIGHT — Real-time Analytics & AI Insights")
141
+ st.markdown(
142
+ "A compact dashboard that displays price charts, indicators and LLM-augmented news insights."
143
+ )
144
+
145
+ # -----------------------
146
+ # Fetch, display market panels, and additional visuals (grid, pie charts, bars)
147
+ # -----------------------
148
+
149
+ if tickers:
150
+ panels = st.container()
151
+ with panels:
152
+ for ticker in tickers:
153
+ try:
154
+ df = fetch_market_data(ticker, lookback_days)
155
+ except Exception as e:
156
+ st.error(f"Error fetching {ticker}: {e}")
157
+ continue
158
+
159
+ df = compute_indicators(df)
160
+
161
+ # Top metrics (latest price, change)
162
+ latest = df.iloc[-1]
163
+ prev = df.iloc[-2] if len(df) > 1 else latest
164
+ change = latest["Close"] - prev["Close"]
165
+ pct_change = (change / prev["Close"]) * 100 if prev["Close"] != 0 else 0
166
+
167
+ st.markdown(f"---\n## {ticker}  —  {format_price(latest['Close'])}  ( {change:+.2f}, {pct_change:+.2f}% )")
168
+
169
+ # Price + indicators chart
170
+ fig = go.Figure()
171
+ fig.add_trace(go.Scatter(x=df["Datetime"], y=df["Close"], name="Close", mode="lines"))
172
+ if show_sma:
173
+ for w in [20, 50]:
174
+ colname = f"SMA_{w}"
175
+ if colname in df.columns:
176
+ fig.add_trace(go.Scatter(x=df["Datetime"], y=df[colname], name=colname, mode="lines"))
177
+ if show_ema:
178
+ for w in [12, 26]:
179
+ colname = f"EMA_{w}"
180
+ if colname in df.columns:
181
+ fig.add_trace(go.Scatter(x=df["Datetime"], y=df[colname], name=colname, mode="lines"))
182
+
183
+ fig.update_layout(height=320, margin=dict(l=20, r=20, t=30, b=20), legend=dict(orientation="h"))
184
+ st.plotly_chart(fig, use_container_width=True)
185
+
186
+ # Volume & RSI row
187
+ cols = st.columns([3, 1])
188
+ with cols[0]:
189
+ if show_volume and "Volume" in df.columns:
190
+ vol_fig = px.bar(df, x="Datetime", y="Volume", labels={"Volume": "Volume"})
191
+ vol_fig.update_layout(height=180, margin=dict(l=20, r=20, t=10, b=10))
192
+ st.plotly_chart(vol_fig, use_container_width=True)
193
+ with cols[1]:
194
+ if show_rsi and "RSI_14" in df.columns:
195
+ st.metric("RSI (last)", f"{df['RSI_14'].iloc[-1]:.1f}")
196
+
197
+ st.markdown("\n")
198
+ # Additional Bar Chart: Daily % Change for this stock
199
+ df['Daily_Return'] = df['Close'].pct_change() * 100
200
+ ret_fig = px.bar(df.tail(30), x='Datetime', y='Daily_Return', title=f'{ticker} - Daily % Change')
201
+ ret_fig.update_layout(height=200, margin=dict(l=20, r=20, t=30, b=20))
202
+ st.plotly_chart(ret_fig, use_container_width=True)
203
+
204
+ # -----------------------
205
+ # AI Insights area
206
+
207
+ # Market Overview Dashboard (combined analytics)
208
+ st.markdown('---')
209
+ st.subheader('Market Overview — Comparative Visuals')
210
+ # Build combined dataframe dict
211
+ combined_close = {}
212
+ market_caps = {}
213
+ for ticker in tickers:
214
+ try:
215
+ tdf = fetch_market_data(ticker, lookback_days)
216
+ combined_close[ticker] = tdf[['Datetime','Close']].set_index('Datetime')['Close']
217
+ # fetch market cap
218
+ try:
219
+ info = yf.Ticker(ticker).info
220
+ market_caps[ticker] = info.get('marketCap') or 0
221
+ except Exception:
222
+ market_caps[ticker] = 0
223
+ except Exception:
224
+ market_caps[ticker] = 0
225
+
226
+ if combined_close:
227
+ combined_df = pd.concat(combined_close, axis=1)
228
+ combined_df = combined_df.sort_index()
229
+ # Normalize for comparison
230
+ norm_df = combined_df.ffill().dropna()
231
+ if not norm_df.empty:
232
+ norm_df = norm_df / norm_df.iloc[0]
233
+ norm_df_reset = norm_df.reset_index()
234
+ # Line chart comparison
235
+ fig_all = px.line(norm_df_reset, x='Datetime', y=norm_df_reset.columns[1:], labels={'value':'Normalized Price'})
236
+ fig_all.update_layout(height=400, title='Normalized Price Comparison')
237
+ st.plotly_chart(fig_all, use_container_width=True)
238
+
239
+ # MODIFICATION: Added st.columns to create a grid layout
240
+ # for heatmap and pie chart
241
+
242
+ # --- Start of Grid Layout ---
243
+ col_m1, col_m2 = st.columns(2)
244
+ returns = combined_df.pct_change().dropna()
245
+
246
+ with col_m1:
247
+ # Correlation heatmap
248
+ if not returns.empty:
249
+ corr = returns.corr()
250
+ heatmap = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.index, colorscale='RdBu'))
251
+ heatmap.update_layout(height=350, title='Returns Correlation Heatmap')
252
+ st.plotly_chart(heatmap, use_container_width=True)
253
+ else:
254
+ st.info("Not enough data for correlation heatmap.")
255
+
256
+ with col_m2:
257
+ # Market Cap pie chart
258
+ mc_df = pd.DataFrame({'Ticker': list(market_caps.keys()), 'MarketCap': list(market_caps.values())})
259
+ mc_df = mc_df[mc_df['MarketCap'] > 0]
260
+ if not mc_df.empty:
261
+ pie = px.pie(mc_df, names='Ticker', values='MarketCap', title='Market Cap Distribution')
262
+ pie.update_layout(height=350)
263
+ st.plotly_chart(pie, use_container_width=True)
264
+ else:
265
+ st.info("No market cap data available for pie chart.")
266
+
267
+ # --- End of Grid Layout ---
268
+
269
+ # Cumulative returns bar chart (full width below the grid)
270
+ if not returns.empty:
271
+ cum_returns = (1 + returns).cumprod().iloc[-1] - 1
272
+ cum_df = cum_returns.reset_index()
273
+ cum_df.columns = ['Ticker','CumulativeReturn']
274
+ bar_cum = px.bar(cum_df, x='Ticker', y='CumulativeReturn', title='Cumulative Return')
275
+ bar_cum.update_layout(height=300)
276
+ st.plotly_chart(bar_cum, use_container_width=True)
277
+
278
+ else:
279
+ st.info('No combined market data available for overview visuals.')
280
+
281
+ st.markdown('---')
282
+ st.header('AI Insights & Research')
283
+
284
+ if not AGENTIC_AVAILABLE:
285
+ st.warning(
286
+ 'Agentic crew (utils.agentic) not available or failed to import. Place agentic.py at utils/agentic.py and ensure crewai dependencies are installed.'
287
+ )
288
+
289
+ # Collect interests
290
+ interests = [t.strip() for t in interests_input.split(',') if t.strip()]
291
+
292
+ insights_placeholder = st.empty()
293
+ insights_collapsed = st.checkbox('Keep insights panel collapsed by default', value=False)
294
+
295
+ with insights_placeholder.container():
296
+ if st.button('Generate AI Insights'):
297
+ if not AGENTIC_AVAILABLE:
298
+ st.error('Agentic crew not available. Cannot generate insights.')
299
+ else:
300
+ with st.spinner('Running financial crew — gathering research & analysis (this may take some time)...'):
301
+ try:
302
+ result = run_financial_crew(interests)
303
+ # store last report for export
304
+ st.session_state['last_ai_report'] = result
305
+ except Exception as e:
306
+ st.error(f'Error while running financial crew: {e}')
307
+ result = None
308
+
309
+ if result:
310
+ st.subheader('Investor Insight Report (LLM)')
311
+ st.markdown(result)
312
+ else:
313
+ st.info('No output returned from the financial crew.')
314
+
315
+ # Live update loop
316
+ if st.session_state.live_mode:
317
+ try:
318
+ time.sleep(max(1, int(refresh_interval)))
319
+ st.rerun()
320
+ except Exception:
321
+ pass
322
+
323
+ # Live update loop
324
+ if st.session_state.live_mode:
325
+ # Simple live update implementation: sleep -> rerun. This will re-run the whole script.
326
+ try:
327
+ time.sleep(max(1, int(refresh_interval)))
328
+ st.rerun()
329
+ except Exception:
330
+ # If the script cannot rerun, just ignore
331
+ pass
332
+
333
+ # -----------------------
334
+ # Dataframe display & Export options
335
+
336
+ # Display combined data if user wants
337
+ if st.checkbox('Show raw data and download CSV'):
338
+ for ticker in tickers:
339
+ try:
340
+ df = fetch_market_data(ticker, lookback_days)
341
+ st.subheader(f'Raw Data - {ticker}')
342
+ st.dataframe(df)
343
+ csv = df.to_csv(index=False).encode('utf-8')
344
+ st.download_button(
345
+ label=f'Download {ticker} data as CSV',
346
+ data=csv,
347
+ file_name=f'{ticker}_data.csv',
348
+ mime='text/csv'
349
+ )
350
+ except Exception:
351
+ pass
352
+
353
+ # MODIFICATION: Replaced PDF export with Markdown (.md) export
354
+ # This removes the need for 'reportlab' and 'io' libraries
355
+ last_report = st.session_state.get('last_ai_report')
356
+ if st.button('Export AI Report to Markdown (.md)'):
357
+ if not last_report:
358
+ st.error('No AI report available. Generate insights first.')
359
+ else:
360
+ try:
361
+ # Prepare the markdown content
362
+ md_content = f"# FIN-SIGHT - Investor Insight Report\n\n"
363
+ md_content += f"**Date:** {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}\n\n"
364
+ md_content += "---\n\n"
365
+ md_content += str(last_report) # Add the raw report from the AI
366
+
367
+ # Encode for download
368
+ md_bytes = md_content.encode('utf-8')
369
+
370
+ st.success('Markdown file prepared!')
371
+ # Show the download button
372
+ st.download_button(
373
+ label='Download AI Report (.md)',
374
+ data=md_bytes,
375
+ file_name='ai_report.md',
376
+ mime='text/markdown'
377
+ )
378
+ except Exception as e:
379
+ st.error(f"Failed to prepare markdown file: {e}")
380
+
381
+
382
+ # Footer / tips
383
+ st.markdown("---")
384
+ st.caption("Tip: Configure your LLM credentials, Serper/SerperDev keys and CrewAI environment before generating AI insights. If you plan to run this on a server, consider running the agentic crew asynchronously (e.g., background worker) and store results in a DB to avoid blocking the UI.")
385
+
386
+
387
+ # End of file
app/utils/agentic.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FinancialInsights.py
3
+ A single-file, modular-style agentic workflow for financial news insights.
4
+ This file can be imported by a main application (e.g., a FastAPI server)
5
+ to run the crew.
6
+ """
7
+
8
+ import os
9
+ from crewai import Agent, Task, Crew, Process, LLM
10
+ from crewai_tools import SerperDevTool
11
+
12
+ # --- 1. Configuration ---
13
+
14
+ # Using a placeholder for the model name as per your original script.
15
+ # Replace with a valid model identifier if needed.
16
+ LLM_MODEL = "gemini/gemini-2.5-flash-lite"
17
+ LLM_TEMPERATURE = 0.7
18
+
19
+ SERPER_SEARCH_URL = "https://google.serper.dev/search"
20
+ SERPER_N_RESULTS = 5 # Increased results for better financial context
21
+
22
+ # --- 2. LLM & Tool Initialization ---
23
+
24
+ # Initialize the LLM instance
25
+ llm = LLM(
26
+ model=LLM_MODEL,
27
+ temperature=LLM_TEMPERATURE,
28
+ )
29
+
30
+ # Initialize the search tool
31
+ search_tool = SerperDevTool(
32
+ search_url=SERPER_SEARCH_URL,
33
+ n_results=SERPER_N_RESULTS,
34
+ )
35
+
36
+
37
+ # --- 3. Agent Definitions ---
38
+
39
+ def create_financial_researcher():
40
+ """Creates the Financial Researcher agent."""
41
+ return Agent(
42
+ role="Financial Researcher",
43
+ goal="Gather the latest news, market sentiment, and key developments "
44
+ "for the given list of financial topics, companies, and cryptocurrencies. "
45
+ "Use the Serper tool to find relevant articles, reports, and discussions.",
46
+ backstory=(
47
+ "You are an expert financial researcher, skilled at digging through "
48
+ "market news, press releases, and financial forums to find the most "
49
+ "relevant and timely information for an investor."
50
+ ),
51
+ tools=[search_tool],
52
+ verbose=True,
53
+ memory=True,
54
+ llm=llm,
55
+ )
56
+
57
+ def create_financial_analyst():
58
+ """Creates the Financial Analyst agent."""
59
+ return Agent(
60
+ role="Financial Analyst",
61
+ goal="Analyze the raw research findings and synthesize them into a "
62
+ "consolidated, easy-to-read investor insight report. "
63
+ "Identify key trends, potential risks, and opportunities. "
64
+ "The final output must be a professional report for an investor.",
65
+ backstory=(
66
+ "You are a seasoned financial analyst with a talent for "
67
+ "cutting through the noise. You can take a pile of raw data and "
68
+ "news snippets and distill them into actionable insights and "
69
+ "concise summaries for busy investors."
70
+ ),
71
+ tools=[], # No external tools needed; analyzes data from the researcher
72
+ verbose=True,
73
+ memory=True,
74
+ allow_delegation=False,
75
+ llm=llm,
76
+ )
77
+
78
+
79
+ # --- 4. Task Definitions ---
80
+
81
+ def create_research_task(agent):
82
+ """Creates the research task for the given agent."""
83
+ return Task(
84
+ description=(
85
+ "For each topic in the provided list {interests}, search for the "
86
+ "latest news, market analysis, and significant events from the past 48 hours. "
87
+ "Gather key snippets, sources, and general sentiment."
88
+ ),
89
+ expected_output=(
90
+ "A structured report with sections for each topic, each containing:\n"
91
+ "Topic: <topic name>\n"
92
+ "Key News Snippets:\n- [Source]: <snippet>...\n"
93
+ "Market Sentiment:\n- <Summary of general sentiment (e.g., bullish, bearish, neutral)>\n"
94
+ "Recent Developments:\n- <Bulleted list of key events or changes>"
95
+ ),
96
+ agent=agent,
97
+ tools=[search_tool],
98
+ )
99
+
100
+ def create_analysis_task(agent):
101
+ """Creates the analysis and reporting task for the given agent."""
102
+ return Task(
103
+ description=(
104
+ "Take the researcher's raw findings for all {interests}. "
105
+ "Analyze and synthesize this information into a consolidated 'Investor Insight' report. "
106
+ "Start with a high-level executive summary, then provide a detailed "
107
+ "breakdown for each topic. Focus on what this information *means* "
108
+ "for an investor, highlighting key insights, risks, and potential opportunities."
109
+ ),
110
+ expected_output=(
111
+ "A comprehensive investor report in markdown format.\n\n"
112
+ "## Executive Summary\n"
113
+ "<Brief overview of all topics and major market movements.>\n\n"
114
+ "## Detailed Insights\n\n"
115
+ "### [Topic 1 Name]\n"
116
+ "- **Key Insight:** <What is the most important takeaway?>\n"
117
+ "- **Recent News:** <Summary of the most impactful news.>\n"
118
+ "- **Potential Risk:** <Identify a potential risk.>\n"
119
+ "- **Potential Opportunity:** <Identify a potential opportunity.>\n\n"
120
+ "### [Topic 2 Name]\n"
121
+ "- **Key Insight:** <...>\n"
122
+ "- **Recent News:** <...>\n"
123
+ "- **Potential Risk:** <...>\n"
124
+ "- **Potential Opportunity:** <...>\n"
125
+ "(...and so on for all topics)"
126
+ ),
127
+ agent=agent,
128
+ )
129
+
130
+
131
+ # --- 5. Crew Definition & Workflow Function ---
132
+
133
+ def run_financial_crew(interest_list):
134
+ """
135
+ Initializes and kicks off the financial insights crew with a given list of interests.
136
+ This is the main function to be imported and called by an external app.
137
+
138
+ Args:
139
+ interest_list (list): A list of financial topics (strings) to research.
140
+
141
+ Returns:
142
+ str: The final result from the crew's execution (the investor report).
143
+ """
144
+ # 1. Create Agents
145
+ financial_researcher = create_financial_researcher()
146
+ financial_analyst = create_financial_analyst()
147
+
148
+ # 2. Create Tasks
149
+ research_task = create_research_task(financial_researcher)
150
+ analysis_task = create_analysis_task(financial_analyst)
151
+
152
+ # 3. Create Crew
153
+ financial_crew = Crew(
154
+ agents=[financial_researcher, financial_analyst],
155
+ tasks=[research_task, analysis_task],
156
+ process=Process.sequential, # Sequential: Researcher -> Analyst
157
+ )
158
+
159
+ # 4. Prepare Inputs
160
+ inputs = {"interests": interest_list}
161
+
162
+ # 5. Run Crew
163
+ print(f"Starting crew for financial interests: {interest_list}...")
164
+ result = financial_crew.kickoff(inputs=inputs)
165
+ return result
166
+
167
+
168
+ # --- 6. Independent Run (for testing) ---
169
+
170
+ if __name__ == "__main__":
171
+ """
172
+ This block allows the script to be run directly for testing purposes.
173
+ It will not execute when the file is imported as a module.
174
+ """
175
+
176
+ # Define the list of financial interests to research
177
+ interests = ["NVIDIA stock", "US inflation rate"]
178
+
179
+ # Run the crew workflow
180
+ result = run_financial_crew(interests)
181
+
182
+ # Print the final output
183
+ print("\n=== Final Investor Insight Report ===\n")
184
+ print(result)