PierreBrunelle commited on
Commit
89c85fb
Β·
verified Β·
1 Parent(s): 7acf826

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +638 -0
app.py ADDED
@@ -0,0 +1,638 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yfinance as yf
3
+ import pandas as pd
4
+ import numpy as np
5
+ from datetime import datetime
6
+ import plotly.graph_objects as go
7
+ from plotly.subplots import make_subplots
8
+ import pixeltable as pxt
9
+ from pixeltable.functions import openai
10
+ import json
11
+ import os
12
+ import getpass
13
+ from typing import Dict, Any
14
+
15
+ # Set up OpenAI API key
16
+ if 'OPENAI_API_KEY' not in os.environ:
17
+ os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key: ')
18
+
19
+ class NumpyEncoder(json.JSONEncoder):
20
+ def default(self, obj):
21
+ if isinstance(obj, (np.int_, np.intc, np.intp, np.int8,
22
+ np.int16, np.int32, np.int64, np.uint8,
23
+ np.uint16, np.uint32, np.uint64)):
24
+ return int(obj)
25
+ elif isinstance(obj, (np.float_, np.float16, np.float32, np.float64)):
26
+ return float(obj)
27
+ elif isinstance(obj, (np.ndarray,)):
28
+ return obj.tolist()
29
+ return json.JSONEncoder.default(self, obj)
30
+
31
+ def safe_json_serialize(obj):
32
+ return json.dumps(obj, cls=NumpyEncoder)
33
+
34
+ def calculate_basic_indicators(data: pd.DataFrame) -> pd.DataFrame:
35
+ df = data.copy()
36
+
37
+ # Moving averages
38
+ df['MA20'] = df['Close'].rolling(window=20).mean()
39
+ df['MA50'] = df['Close'].rolling(window=50).mean()
40
+ df['MA200'] = df['Close'].rolling(window=200).mean()
41
+
42
+ # RSI
43
+ delta = df['Close'].diff()
44
+ gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
45
+ loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
46
+ rs = gain / loss
47
+ df['RSI'] = 100 - (100 / (1 + rs))
48
+
49
+ # MACD
50
+ exp1 = df['Close'].ewm(span=12, adjust=False).mean()
51
+ exp2 = df['Close'].ewm(span=26, adjust=False).mean()
52
+ df['MACD'] = exp1 - exp2
53
+ df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
54
+
55
+ return df.ffill().bfill()
56
+
57
+ # Also update the system prompt in generate_analysis_prompt to ensure structured output:
58
+ @pxt.udf
59
+ def generate_analysis_prompt(data: str, analysis_type: str) -> list[dict]:
60
+ """Generate a structured prompt for AI analysis"""
61
+ system_prompt = '''You are a financial analyst providing market analysis. Structure your response using EXACTLY the following format and sections:
62
+
63
+ SUMMARY
64
+ Provide a clear 2-3 sentence executive summary of your analysis.
65
+
66
+ TECHNICAL ANALYSIS
67
+ β€’ Moving Averages: Analyze trends using MA20, MA50, and MA200
68
+ β€’ RSI Analysis: Current RSI level and implications
69
+ β€’ MACD Analysis: MACD trends and signals
70
+ β€’ Volume Analysis: Notable volume patterns and implications
71
+
72
+ MARKET CONTEXT
73
+ β€’ List 2-3 key market factors affecting the stock
74
+ β€’ Include relevant sector/industry context
75
+ β€’ Note any significant recent developments
76
+
77
+ RISKS
78
+ β€’ Risk 1: [Specific risk and brief explanation]
79
+ β€’ Risk 2: [Specific risk and brief explanation]
80
+ β€’ Risk 3: [Specific risk and brief explanation]
81
+
82
+ OPPORTUNITIES
83
+ β€’ Opportunity 1: [Specific opportunity and brief explanation]
84
+ β€’ Opportunity 2: [Specific opportunity and brief explanation]
85
+ β€’ Opportunity 3: [Specific opportunity and brief explanation]
86
+
87
+ RECOMMENDATION
88
+ Provide a clear, actionable investment recommendation based on the analysis above.'''
89
+
90
+ return [
91
+ {'role': 'system', 'content': system_prompt},
92
+ {'role': 'user', 'content': f'Analyze this market data for {analysis_type} analysis:\n{data}'}
93
+ ]
94
+
95
+ def parse_analysis_response(response: str) -> Dict[str, str]:
96
+ """Parse the structured AI response into sections with support for markdown formatting"""
97
+ sections = {
98
+ 'SUMMARY': None,
99
+ 'TECHNICAL ANALYSIS': None,
100
+ 'MARKET CONTEXT': None,
101
+ 'RISKS': None,
102
+ 'OPPORTUNITIES': None,
103
+ 'RECOMMENDATION': None
104
+ }
105
+
106
+ current_section = None
107
+ buffer = []
108
+
109
+ if not response or not response.strip():
110
+ return {k: "Analysis not available" for k in sections.keys()}
111
+
112
+ for line in response.split('\n'):
113
+ line = line.strip()
114
+
115
+ # Check if this line is a section header (now handling markdown formatting)
116
+ matched_section = None
117
+ for section in sections.keys():
118
+ # Remove asterisks and check for exact match
119
+ cleaned_line = line.replace('*', '').strip()
120
+ if cleaned_line == section:
121
+ matched_section = section
122
+ break
123
+
124
+ if matched_section:
125
+ # Save previous section if exists
126
+ if current_section and buffer:
127
+ sections[current_section] = '\n'.join(buffer).strip()
128
+ current_section = matched_section
129
+ buffer = []
130
+ elif current_section and line:
131
+ # Clean up markdown formatting in content
132
+ cleaned_content = line.replace('*', '').strip()
133
+ if cleaned_content: # Only add non-empty lines
134
+ buffer.append(cleaned_content)
135
+
136
+ # Save the last section
137
+ if current_section and buffer:
138
+ sections[current_section] = '\n'.join(buffer).strip()
139
+
140
+ # Clean up sections and provide meaningful defaults
141
+ section_messages = {
142
+ 'SUMMARY': 'Market analysis summary not available',
143
+ 'TECHNICAL ANALYSIS': 'Technical analysis not available',
144
+ 'MARKET CONTEXT': 'Market context information not available',
145
+ 'RISKS': 'Risk assessment not available',
146
+ 'OPPORTUNITIES': 'Opportunity analysis not available',
147
+ 'RECOMMENDATION': 'Investment recommendation not available'
148
+ }
149
+
150
+ # Only use default messages if section is truly empty
151
+ for key in sections:
152
+ if sections[key] is None or not sections[key].strip():
153
+ sections[key] = section_messages[key]
154
+
155
+ return sections
156
+
157
+ def create_visualization(data: pd.DataFrame, technical_depth: str) -> go.Figure:
158
+ fig = make_subplots(
159
+ rows=3 if technical_depth == 'advanced' else 2,
160
+ cols=1,
161
+ shared_xaxes=True,
162
+ vertical_spacing=0.05,
163
+ subplot_titles=('Price & Moving Averages', 'Volume', 'RSI' if technical_depth == 'advanced' else None)
164
+ )
165
+
166
+ # Price candlesticks with improved styling
167
+ fig.add_trace(
168
+ go.Candlestick(
169
+ x=data.index,
170
+ open=data['Open'],
171
+ high=data['High'],
172
+ low=data['Low'],
173
+ close=data['Close'],
174
+ name='Price',
175
+ increasing_line_color='#26A69A',
176
+ decreasing_line_color='#EF5350'
177
+ ),
178
+ row=1, col=1
179
+ )
180
+
181
+ # Moving averages with distinct colors
182
+ colors = {'MA20': '#1E88E5', 'MA50': '#FFC107', 'MA200': '#7B1FA2'}
183
+ for ma, color in colors.items():
184
+ fig.add_trace(
185
+ go.Scatter(
186
+ x=data.index,
187
+ y=data[ma],
188
+ name=ma,
189
+ line=dict(color=color, width=1.5)
190
+ ),
191
+ row=1, col=1
192
+ )
193
+
194
+ # Volume with color based on price change
195
+ colors = ['#26A69A' if close >= open_price else '#EF5350'
196
+ for close, open_price in zip(data['Close'].values, data['Open'].values)]
197
+ fig.add_trace(
198
+ go.Bar(
199
+ x=data.index,
200
+ y=data['Volume'],
201
+ name='Volume',
202
+ marker_color=colors
203
+ ),
204
+ row=2, col=1
205
+ )
206
+
207
+ if technical_depth == 'advanced':
208
+ fig.add_trace(
209
+ go.Scatter(
210
+ x=data.index,
211
+ y=data['RSI'],
212
+ name='RSI',
213
+ line=dict(color='#7C4DFF', width=1.5)
214
+ ),
215
+ row=3, col=1
216
+ )
217
+
218
+ # Add RSI reference lines
219
+ fig.add_hline(y=70, line_dash="dash", line_color="red", row=3, col=1)
220
+ fig.add_hline(y=30, line_dash="dash", line_color="green", row=3, col=1)
221
+
222
+ fig.update_layout(
223
+ height=800,
224
+ template='plotly_white',
225
+ showlegend=True,
226
+ legend=dict(
227
+ orientation="h",
228
+ yanchor="bottom",
229
+ y=1.02,
230
+ xanchor="right",
231
+ x=1
232
+ )
233
+ )
234
+
235
+ # Update y-axes labels
236
+ fig.update_yaxes(title_text="Price", row=1, col=1)
237
+ fig.update_yaxes(title_text="Volume", row=2, col=1)
238
+ if technical_depth == 'advanced':
239
+ fig.update_yaxes(title_text="RSI", row=3, col=1)
240
+
241
+ return fig
242
+
243
+ def process_outputs(ticker_symbol, analysis_type, time_horizon, risk_tolerance,
244
+ investment_style, technical_depth, include_market_context=True,
245
+ max_positions=3):
246
+ try:
247
+ # Initialize Pixeltable
248
+ pxt.drop_dir('financial_analysis', force=True)
249
+ pxt.create_dir('financial_analysis')
250
+
251
+ data_table = pxt.create_table(
252
+ 'financial_analysis.stock_data',
253
+ {
254
+ 'ticker': pxt.StringType(),
255
+ 'data': pxt.StringType(),
256
+ 'timestamp': pxt.TimestampType()
257
+ }
258
+ )
259
+
260
+ # Fetch and process data
261
+ stock = yf.Ticker(ticker_symbol.strip().upper())
262
+ market_data = stock.history(period='1y')
263
+ if market_data.empty:
264
+ raise ValueError("No data found for the specified ticker symbol.")
265
+
266
+ technical_data = calculate_basic_indicators(market_data)
267
+ market_data_json = technical_data.to_json(date_format='iso')
268
+
269
+ # Store data and generate analysis
270
+ data_table.insert([{
271
+ 'ticker': ticker_symbol.upper(),
272
+ 'data': market_data_json,
273
+ 'timestamp': datetime.now()
274
+ }])
275
+
276
+ data_table['prompt'] = generate_analysis_prompt(data_table.data, analysis_type)
277
+ data_table['analysis'] = openai.chat_completions(
278
+ messages=data_table.prompt,
279
+ model='gpt-4o-mini-2024-07-18',
280
+ temperature=0.7,
281
+ max_tokens=1000
282
+ )
283
+
284
+ # Process the analysis with better error handling
285
+ try:
286
+ analysis_text = data_table.select(
287
+ analysis=data_table.analysis.choices[0].message.content
288
+ ).tail(1)['analysis'][0]
289
+ parsed_analysis = parse_analysis_response(analysis_text)
290
+ except Exception as analysis_error:
291
+ print(f"Analysis error: {str(analysis_error)}")
292
+ parsed_analysis = parse_analysis_response("") # This will return default messages
293
+
294
+ # Prepare company info with proper JSON formatting
295
+ company_info_data = {
296
+ 'Name': str(stock.info.get('longName', 'N/A')),
297
+ 'Sector': str(stock.info.get('sector', 'N/A')),
298
+ 'Industry': str(stock.info.get('industry', 'N/A')),
299
+ 'Exchange': str(stock.info.get('exchange', 'N/A'))
300
+ }
301
+
302
+ raw_llm_output = ""
303
+ try:
304
+ raw_llm_output = data_table.select(
305
+ analysis=data_table.analysis.choices[0].message.content
306
+ ).tail(1)['analysis'][0]
307
+ parsed_analysis = parse_analysis_response(raw_llm_output)
308
+ except Exception as analysis_error:
309
+ print(f"Analysis error: {str(analysis_error)}")
310
+ parsed_analysis = parse_analysis_response("")
311
+ raw_llm_output = f"Error processing analysis: {str(analysis_error)}"
312
+
313
+ # Prepare market stats with proper number formatting
314
+ try:
315
+ current_price = float(technical_data['Close'].iloc[-1])
316
+ previous_price = float(technical_data['Close'].iloc[-2])
317
+ daily_change = float((current_price / previous_price - 1) * 100)
318
+ volume = int(technical_data['Volume'].iloc[-1])
319
+ rsi = float(technical_data['RSI'].iloc[-1])
320
+ except (IndexError, KeyError, TypeError):
321
+ current_price = daily_change = volume = rsi = 0
322
+
323
+ market_stats_data = {
324
+ 'Current Price': f"${current_price:.2f}",
325
+ 'Daily Change': f"{daily_change:.2f}%",
326
+ 'Volume': f"{volume:,}",
327
+ 'RSI': f"{rsi:.2f}"
328
+ }
329
+
330
+ # Add timestamp to technical data
331
+ technical_data_with_time = technical_data.reset_index()
332
+ technical_data_with_time['Date'] = technical_data_with_time['Date'].dt.strftime('%Y-%m-%d %H:%M:%S')
333
+
334
+ # Create visualization
335
+ plot = create_visualization(technical_data, technical_depth)
336
+
337
+ return (
338
+ json.dumps(company_info_data),
339
+ json.dumps(market_stats_data),
340
+ plot,
341
+ parsed_analysis['SUMMARY'],
342
+ parsed_analysis['TECHNICAL ANALYSIS'],
343
+ parsed_analysis['MARKET CONTEXT'],
344
+ parsed_analysis['RISKS'],
345
+ parsed_analysis['OPPORTUNITIES'],
346
+ parsed_analysis['RECOMMENDATION'],
347
+ technical_data_with_time,
348
+ raw_llm_output # Add raw output to return values
349
+ )
350
+
351
+ except Exception as e:
352
+ error_msg = f"Error processing data: {str(e)}"
353
+ empty_json = json.dumps({})
354
+ no_data_msg = "Analysis not available due to data processing error"
355
+ empty_df = pd.DataFrame()
356
+
357
+ return (
358
+ empty_json,
359
+ empty_json,
360
+ None,
361
+ no_data_msg,
362
+ no_data_msg,
363
+ no_data_msg,
364
+ no_data_msg,
365
+ no_data_msg,
366
+ no_data_msg,
367
+ empty_df,
368
+ f"Error occurred: {str(e)}" # Add error message to raw output
369
+ )
370
+
371
+ def create_interface() -> gr.Blocks:
372
+ """Create the production-ready Gradio interface"""
373
+ with gr.Blocks(theme=gr.themes.Base()) as demo:
374
+ # Header
375
+ gr.Markdown(
376
+ """
377
+ # πŸ“ˆ AI Financial Analysis Platform
378
+ AI-powered market analysis and technical indicators. The creators and operators of this tool are not responsible for any financial losses or decisions made based on this analysis.
379
+ """
380
+ )
381
+
382
+ # Information Accordions
383
+ with gr.Row():
384
+ with gr.Column():
385
+ with gr.Accordion("🎯 What does it do?", open=False):
386
+ gr.Markdown("""
387
+ This platform provides comprehensive financial analysis tools:
388
+
389
+ 1. πŸ“Š **Technical Analysis**: Advanced indicators, e.g. RSI, and MACD
390
+ 2. πŸ€– **AI-Powered Insights**: Intelligent market analysis/recommendations
391
+ 3. πŸ“ˆ **Interactive Charts**: Visual representation of movements/indicators
392
+ 4. πŸ’‘ **Investment Context**: Market conditions and sector analysis
393
+ 5. ⚑ **Real-time Data**: Up-to-date information through Yahoo Finance
394
+ 6. 🎯 **Personalized Analysis**: Tailored to your style/risk tolerance
395
+ """)
396
+
397
+ with gr.Column():
398
+ with gr.Accordion("πŸ› οΈ How does it work?", open=False):
399
+ gr.Markdown("""
400
+ The platform leverages several advanced technologies:
401
+
402
+ 1. πŸ“¦ **Data Processing**: Pixeltable manages and orchestrate data
403
+ 2. πŸ” **Technical Indicators**: Custom algorithms calculate market metrics
404
+ 3. πŸ€– **AI Analysis**: Advanced language models provide market insights
405
+ 4. πŸ“Š **Visualization**: Interactive charts using Plotly
406
+ 5. πŸ”„ **Real-time Updates**: Direct connection to market data feeds
407
+ 6. πŸ’Ύ **Data Persistence**: Reliable storage and retrieval of insights
408
+ """)
409
+
410
+ # Disclaimer
411
+ gr.HTML(
412
+ """
413
+ <div style="background-color: #FFF4E5; border: 1px solid #FFE0B2; color: #663C00; border-radius: 8px; padding: 15px; margin: 15px 0;">
414
+ <strong>⚠️ Disclaimer:</strong>
415
+ <p style="margin: 8px 0;">
416
+ This tool provides financial analysis for informational purposes only and should not be considered as financial advice.
417
+ Before making any investment decisions, please:
418
+ </p>
419
+ <ul style="margin: 8px 0;">
420
+ <li>Consult with qualified financial advisors</li>
421
+ <li>Conduct your own research</li>
422
+ <li>Consider your personal financial situation</li>
423
+ <li>Be aware that past performance does not guarantee future results</li>
424
+ <li>Understand that all investments carry risk</li>
425
+ </ul>
426
+ </div>
427
+ """
428
+ )
429
+
430
+ with gr.Row():
431
+ # Left sidebar for inputs (reduced width)
432
+ with gr.Column(scale=1):
433
+ with gr.Row():
434
+ gr.Markdown("### πŸ“Š Analysis Parameters")
435
+ with gr.Row():
436
+ ticker_input = gr.Textbox(
437
+ label="Stock Ticker",
438
+ placeholder="e.g., AAPL",
439
+ max_lines=1
440
+ )
441
+ analysis_type = gr.Radio(
442
+ choices=['comprehensive', 'quantitative', 'technical'],
443
+ label="Analysis Type",
444
+ value='comprehensive'
445
+ )
446
+ technical_depth = gr.Radio(
447
+ choices=['basic', 'advanced'],
448
+ label="Technical Depth",
449
+ value='advanced'
450
+ )
451
+
452
+ with gr.Row():
453
+ gr.Markdown("### 🎯 Investment Profile")
454
+ with gr.Row():
455
+ time_horizon = gr.Radio(
456
+ choices=['short', 'medium', 'long'],
457
+ label="Time Horizon",
458
+ value='medium'
459
+ )
460
+ risk_tolerance = gr.Radio(
461
+ choices=['conservative', 'moderate', 'aggressive'],
462
+ label="Risk Tolerance",
463
+ value='moderate'
464
+ )
465
+ investment_style = gr.Dropdown(
466
+ choices=['value', 'growth', 'momentum', 'balanced', 'income'],
467
+ label="Investment Style",
468
+ value='balanced'
469
+ )
470
+
471
+ analyze_btn = gr.Button("πŸ“Š Analyze Stock", variant="primary")
472
+
473
+ with gr.Row():
474
+ with gr.Column(scale=3):
475
+ with gr.Tabs() as tabs:
476
+ with gr.TabItem("πŸ“Š Analysis Dashboard"):
477
+ # Top row with company info and market stats
478
+ with gr.Row(equal_height=True):
479
+ with gr.Column(scale=1):
480
+ company_info = gr.JSON(
481
+ label="Company Information",
482
+ height=150
483
+ )
484
+ with gr.Column(scale=1):
485
+ market_stats = gr.JSON(
486
+ label="Market Statistics",
487
+ height=150
488
+ )
489
+
490
+ with gr.TabItem("πŸ“‘ Historical Data"):
491
+ technical_data = gr.DataFrame(
492
+ headers=["Date", "Open", "High", "Low", "Close",
493
+ "Volume", "MA20", "MA50", "MA200", "RSI",
494
+ "MACD", "MACD_Signal"],
495
+ )
496
+
497
+ with gr.TabItem("πŸ” Debug View"):
498
+ raw_output = gr.Textbox(
499
+ label="Raw LLM Output",
500
+ lines=10,
501
+ max_lines=20,
502
+ show_label=True,
503
+ interactive=False
504
+ )
505
+ gr.Markdown("""
506
+ ### Debug Information
507
+ This tab shows the raw output from the language model before parsing.
508
+ Use this to diagnose any issues with the analysis display.
509
+ """)
510
+ # Technical analysis chart
511
+ with gr.Row():
512
+ with gr.Column(scale=1):
513
+ with gr.Row():
514
+ gr.Markdown("### πŸ“ˆ Technical Analysis Chart")
515
+
516
+ with gr.Row():
517
+ plot_output = gr.Plot()
518
+
519
+ # AI Analysis section with better layout
520
+ with gr.Row():
521
+ with gr.Column(scale=2):
522
+ with gr.Row():
523
+ gr.Markdown("### πŸ€– AI Analysis")
524
+
525
+ # Summary at the top
526
+ with gr.Row():
527
+ summary = gr.Textbox(
528
+ label="Executive Summary",
529
+ lines=3,
530
+ max_lines=5,
531
+ show_label=True
532
+ )
533
+
534
+ # Main analysis sections
535
+ with gr.Row():
536
+ with gr.Column(scale=1):
537
+ tech_analysis = gr.Textbox(
538
+ label="Technical Analysis",
539
+ lines=8,
540
+ max_lines=10,
541
+ show_label=True
542
+ )
543
+ market_context = gr.Textbox(
544
+ label="Market Context",
545
+ lines=4,
546
+ max_lines=6,
547
+ show_label=True
548
+ )
549
+
550
+ with gr.Column(scale=1):
551
+ risks = gr.Textbox(
552
+ label="Key Risks",
553
+ lines=5,
554
+ max_lines=7,
555
+ show_label=True
556
+ )
557
+ opportunities = gr.Textbox(
558
+ label="Key Opportunities",
559
+ lines=5,
560
+ max_lines=7,
561
+ show_label=True
562
+ )
563
+
564
+ # Recommendation at the bottom
565
+ with gr.Row():
566
+ recommendation = gr.Textbox(
567
+ label="Investment Recommendation",
568
+ lines=3,
569
+ max_lines=5,
570
+ show_label=True
571
+ )
572
+
573
+ # Examples section at the bottom
574
+ gr.Examples(
575
+ examples=[
576
+ ["AAPL", "comprehensive", "medium", "moderate", "balanced", "advanced"],
577
+ ["MSFT", "technical", "short", "aggressive", "momentum", "basic"],
578
+ ["GOOGL", "quantitative", "long", "conservative", "value", "advanced"]
579
+ ],
580
+ inputs=[
581
+ ticker_input, analysis_type, time_horizon, risk_tolerance,
582
+ investment_style, technical_depth
583
+ ]
584
+ )
585
+
586
+ # Footer
587
+ gr.HTML(
588
+ """
589
+ <div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
590
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
591
+ <div style="flex: 1;">
592
+ <h4 style="margin: 0; color: #374151;">πŸš€ Built with Pixeltable</h4>
593
+ <p style="margin: 0.5rem 0; color: #6b7280;">
594
+ Open Source AI Data infrastructure for building intelligent applications.
595
+ </p>
596
+ </div>
597
+ <div style="flex: 1;">
598
+ <h4 style="margin: 0; color: #374151;">πŸ”— Resources</h4>
599
+ <div style="display: flex; gap: 1.5rem; margin-top: 0.5rem;">
600
+ <a href="https://github.com/pixeltable/pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
601
+ πŸ’» GitHub
602
+ </a>
603
+ <a href="https://docs.pixeltable.com" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
604
+ πŸ“š Documentation
605
+ </a>
606
+ <a href="https://huggingface.co/Pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
607
+ πŸ€— Hugging Face
608
+ </a>
609
+ </div>
610
+ </div>
611
+ </div>
612
+ <p style="margin: 1rem 0 0; text-align: center; color: #9CA3AF; font-size: 0.875rem;">
613
+ Β© 2024 AI Financial Analysis Platform powered by Pixeltable.
614
+ This work is licensed under the Apache License 2.0.
615
+ </p>
616
+ </div>
617
+ """
618
+ )
619
+
620
+ analyze_btn.click(
621
+ process_outputs,
622
+ inputs=[
623
+ ticker_input, analysis_type, time_horizon, risk_tolerance,
624
+ investment_style, technical_depth
625
+ ],
626
+ outputs=[
627
+ company_info, market_stats, plot_output,
628
+ summary, tech_analysis, market_context,
629
+ risks, opportunities, recommendation,
630
+ technical_data, raw_output # Add raw_output to outputs
631
+ ]
632
+ )
633
+
634
+ return demo
635
+
636
+ if __name__ == "__main__":
637
+ demo = create_interface()
638
+ demo.launch()