jetpackjules commited on
Commit
03c9b49
Β·
verified Β·
1 Parent(s): 4bae4e2

Upload folder using huggingface_hub

Browse files
app.py CHANGED
@@ -197,7 +197,7 @@ def create_portfolio_chart():
197
 
198
  def create_ipo_discovery_chart():
199
  """Create IPO discovery chart with investment decisions"""
200
- ipos = fetch_from_vm('ipos?limit=30', [])
201
 
202
  if not ipos:
203
  fig = go.Figure()
 
197
 
198
  def create_ipo_discovery_chart():
199
  """Create IPO discovery chart with investment decisions"""
200
+ ipos = fetch_from_vm('ipos?limit=100', [])
201
 
202
  if not ipos:
203
  fig = go.Figure()
hf_space/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
hf_space/README.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Trading_Dashboard
3
+ app_file: app.py
4
+ sdk: gradio
5
+ sdk_version: 5.35.0
6
+ ---
7
+ # Stock-Trader
8
+ Line of best fit stock trader test
9
+
10
+
11
+ ## ENV MANAGER:
12
+ >To CREATE/UPDATE YAML (from PC to file) go to reg. terminal:
13
+ >conda env export > env.yml
14
+ >
15
+ >To create env (from file to PC):
16
+ >conda env create --file=env.yml
17
+ >
18
+ >To update ENV (FROM FILE TO PC) (run in conda terminal) (if i remove --prune it works in terminal?):
19
+ >conda env update --file env.yml --prune
20
+
21
+
22
+
23
+
24
+ ### THIS WORKED TO FIX QT ISSUE:
25
+ from PyQt5.QtCore import QCoreApplication, Qt
26
+
27
+ # Clear any cached Qt plugins
28
+ QCoreApplication.setAttribute(Qt.AA_DisableHighDpiScaling, True)
29
+
30
+ app = QCoreApplication([])
31
+
32
+
33
+
34
+ # This might not be needed since we are using conda... (TO ACTIVATE ENV: source venv/bin/activate)
hf_space/app.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Premium Trading Dashboard - Full Featured
4
+ Beautiful Vercel-style dashboard with VM data integration
5
+ """
6
+
7
+ import os
8
+ import pandas as pd
9
+ import gradio as gr
10
+ import plotly.graph_objects as go
11
+ import plotly.express as px
12
+ from datetime import datetime, timedelta, timezone
13
+ import logging
14
+ import requests
15
+ from alpaca.trading.client import TradingClient
16
+ from alpaca.trading.requests import GetOrdersRequest, GetPortfolioHistoryRequest
17
+ from alpaca.trading.enums import OrderStatus
18
+ from alpaca.data.timeframe import TimeFrame
19
+ from alpaca.data.historical import StockHistoricalDataClient
20
+
21
+ # Get API keys and VM URL from environment variables
22
+ API_KEY = os.getenv('ALPACA_API_KEY', 'PK2FD9B2S86LHR7ZBHG1')
23
+ SECRET_KEY = os.getenv('ALPACA_SECRET_KEY', 'QPmGPDgbPArvHv6cldBXc7uWddapYcIAnBhtkuBW')
24
+ VM_API_URL = os.getenv('VM_API_URL', 'http://34.56.193.18:8090') # Set this in Hugging Face
25
+
26
+ # Configure logging
27
+ logging.basicConfig(level=logging.INFO)
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Initialize Alpaca clients
31
+ trading_client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY)
32
+ data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
33
+
34
+ # Modern color scheme
35
+ COLORS = {
36
+ 'primary': '#0070f3',
37
+ 'success': '#00d647',
38
+ 'error': '#ff0080',
39
+ 'warning': '#f5a623',
40
+ 'neutral': '#8b949e',
41
+ 'background': '#fafafa',
42
+ 'surface': '#ffffff',
43
+ 'text': '#000000',
44
+ 'text_secondary': '#666666',
45
+ 'border': '#eaeaea'
46
+ }
47
+
48
+ def fetch_from_vm(endpoint, default_value=None):
49
+ """Fetch data from VM API server"""
50
+ try:
51
+ response = requests.get(f"{VM_API_URL}/api/{endpoint}", timeout=10)
52
+ if response.status_code == 200:
53
+ return response.json()
54
+ else:
55
+ logger.warning(f"VM API {endpoint} returned {response.status_code}")
56
+ return default_value
57
+ except Exception as e:
58
+ logger.error(f"Error fetching from VM {endpoint}: {e}")
59
+ return default_value
60
+
61
+ def get_account_info():
62
+ """Get current account information from Alpaca"""
63
+ try:
64
+ account = trading_client.get_account()
65
+ return {
66
+ 'portfolio_value': float(account.portfolio_value),
67
+ 'buying_power': float(account.buying_power),
68
+ 'cash': float(account.cash),
69
+ 'equity': float(account.equity),
70
+ 'day_change': float(getattr(account, 'unrealized_pl', 0)) if hasattr(account, 'unrealized_pl') else 0,
71
+ 'day_change_percent': float(getattr(account, 'unrealized_plpc', 0)) * 100 if hasattr(account, 'unrealized_plpc') else 0,
72
+ 'last_equity': float(account.last_equity) if account.last_equity else 0
73
+ }
74
+ except Exception as e:
75
+ logger.error(f"Error fetching account info: {e}")
76
+ return {
77
+ 'portfolio_value': 0, 'buying_power': 0, 'cash': 0, 'equity': 0,
78
+ 'day_change': 0, 'day_change_percent': 0, 'last_equity': 0
79
+ }
80
+
81
+ def get_portfolio_history():
82
+ """Get portfolio value history from Alpaca"""
83
+ try:
84
+ portfolio_history_request = GetPortfolioHistoryRequest(
85
+ period="1M",
86
+ timeframe="1D",
87
+ extended_hours=False
88
+ )
89
+
90
+ portfolio_history = trading_client.get_portfolio_history(portfolio_history_request)
91
+
92
+ timestamps = [datetime.fromtimestamp(ts, tz=timezone.utc) for ts in portfolio_history.timestamp]
93
+ equity_values = portfolio_history.equity
94
+
95
+ df = pd.DataFrame({
96
+ 'timestamp': timestamps,
97
+ 'equity': equity_values
98
+ })
99
+
100
+ return df.dropna()
101
+ except Exception as e:
102
+ logger.error(f"Error fetching portfolio history: {e}")
103
+ return pd.DataFrame()
104
+
105
+ def get_current_positions():
106
+ """Get current positions"""
107
+ try:
108
+ positions = trading_client.get_all_positions()
109
+ position_data = []
110
+
111
+ for position in positions:
112
+ position_data.append({
113
+ 'symbol': position.symbol,
114
+ 'qty': float(position.qty),
115
+ 'market_value': float(position.market_value),
116
+ 'cost_basis': float(position.cost_basis),
117
+ 'unrealized_pl': float(position.unrealized_pl),
118
+ 'unrealized_plpc': float(position.unrealized_plpc) * 100,
119
+ 'current_price': float(position.current_price) if position.current_price else 0
120
+ })
121
+
122
+ return position_data
123
+ except Exception as e:
124
+ logger.error(f"Error fetching positions: {e}")
125
+ return []
126
+
127
+ def create_portfolio_chart():
128
+ """Create beautiful portfolio value chart"""
129
+ portfolio_df = get_portfolio_history()
130
+
131
+ if portfolio_df.empty:
132
+ fig = go.Figure()
133
+ fig.add_annotation(
134
+ text="No portfolio history available",
135
+ x=0.5, y=0.5,
136
+ xref="paper", yref="paper",
137
+ showarrow=False,
138
+ font=dict(size=16, color=COLORS['text_secondary'])
139
+ )
140
+ else:
141
+ fig = go.Figure()
142
+
143
+ fig.add_trace(go.Scatter(
144
+ x=portfolio_df['timestamp'],
145
+ y=portfolio_df['equity'],
146
+ mode='lines',
147
+ name='Portfolio Value',
148
+ line=dict(color=COLORS['primary'], width=3),
149
+ fill='tonexty',
150
+ fillcolor=f"rgba(0, 112, 243, 0.1)",
151
+ hovertemplate='<b>%{y:$,.2f}</b><br>%{x}<extra></extra>'
152
+ ))
153
+
154
+ if len(portfolio_df) > 0:
155
+ current_value = portfolio_df['equity'].iloc[-1]
156
+ fig.add_annotation(
157
+ x=portfolio_df['timestamp'].iloc[-1],
158
+ y=current_value,
159
+ text=f"${current_value:,.2f}",
160
+ showarrow=True,
161
+ arrowhead=2,
162
+ arrowcolor=COLORS['primary'],
163
+ bgcolor="white",
164
+ bordercolor=COLORS['primary'],
165
+ borderwidth=2,
166
+ font=dict(size=12, color=COLORS['text'])
167
+ )
168
+
169
+ fig.update_layout(
170
+ title=dict(
171
+ text="Portfolio Value (Last 30 Days)",
172
+ font=dict(size=24, color=COLORS['text'], family="Inter"),
173
+ x=0.02
174
+ ),
175
+ xaxis=dict(
176
+ title="Date",
177
+ showgrid=True,
178
+ gridcolor=COLORS['border'],
179
+ color=COLORS['text_secondary']
180
+ ),
181
+ yaxis=dict(
182
+ title="Portfolio Value ($)",
183
+ showgrid=True,
184
+ gridcolor=COLORS['border'],
185
+ color=COLORS['text_secondary'],
186
+ tickformat='$,.0f'
187
+ ),
188
+ plot_bgcolor='white',
189
+ paper_bgcolor='white',
190
+ height=400,
191
+ margin=dict(l=60, r=40, t=60, b=60),
192
+ hovermode='x unified',
193
+ showlegend=False
194
+ )
195
+
196
+ return fig
197
+
198
+ def create_ipo_discovery_chart():
199
+ """Create IPO discovery chart with investment decisions"""
200
+ ipos = fetch_from_vm('ipos?limit=30', [])
201
+
202
+ if not ipos:
203
+ fig = go.Figure()
204
+ fig.add_annotation(
205
+ text="No IPO data available from VM",
206
+ x=0.5, y=0.5,
207
+ xref="paper", yref="paper",
208
+ showarrow=False,
209
+ font=dict(size=16, color=COLORS['text_secondary'])
210
+ )
211
+ else:
212
+ # Count by status
213
+ status_counts = {}
214
+ for ipo in ipos:
215
+ status = ipo.get('investment_status', 'UNKNOWN')
216
+ status_counts[status] = status_counts.get(status, 0) + 1
217
+
218
+ # Create pie chart
219
+ labels = list(status_counts.keys())
220
+ values = list(status_counts.values())
221
+
222
+ # Map status to colors
223
+ color_map = {
224
+ 'INVESTED': COLORS['success'],
225
+ 'ELIGIBLE_NOT_INVESTED': COLORS['warning'],
226
+ 'WRONG_TYPE': COLORS['neutral'],
227
+ 'UNKNOWN': COLORS['error']
228
+ }
229
+ colors = [color_map.get(label, COLORS['neutral']) for label in labels]
230
+
231
+ fig = go.Figure(data=[go.Pie(
232
+ labels=labels,
233
+ values=values,
234
+ hole=0.4,
235
+ marker=dict(colors=colors),
236
+ textinfo='label+percent',
237
+ textposition='outside'
238
+ )])
239
+
240
+ fig.update_layout(
241
+ title=dict(
242
+ text="IPO Investment Decisions",
243
+ font=dict(size=24, color=COLORS['text'], family="Inter"),
244
+ x=0.5
245
+ ),
246
+ plot_bgcolor='white',
247
+ paper_bgcolor='white',
248
+ height=400,
249
+ margin=dict(l=60, r=60, t=60, b=60),
250
+ showlegend=True
251
+ )
252
+
253
+ return fig
254
+
255
+ def refresh_account_overview():
256
+ """Refresh account overview display"""
257
+ account = get_account_info()
258
+
259
+ portfolio_value = f"${account['portfolio_value']:,.2f}"
260
+ buying_power = f"${account['buying_power']:,.2f}"
261
+ cash = f"${account['cash']:,.2f}"
262
+
263
+ day_change_value = account['day_change']
264
+ day_change_percent = account['day_change_percent']
265
+ if day_change_value > 0:
266
+ day_change = f"↗️ +${day_change_value:,.2f} (+{day_change_percent:.2f}%)"
267
+ elif day_change_value < 0:
268
+ day_change = f"β†˜οΈ ${day_change_value:,.2f} ({day_change_percent:.2f}%)"
269
+ else:
270
+ day_change = f"➑️ ${day_change_value:,.2f} ({day_change_percent:.2f}%)"
271
+
272
+ equity = f"${account['equity']:,.2f}"
273
+
274
+ return portfolio_value, buying_power, cash, day_change, equity
275
+
276
+ def refresh_positions_table():
277
+ """Refresh current positions table"""
278
+ positions = get_current_positions()
279
+ if not positions:
280
+ return pd.DataFrame(columns=['Symbol', 'Quantity', 'Market Value', 'Unrealized P&L', 'Unrealized %'])
281
+
282
+ df_data = []
283
+ for pos in positions:
284
+ pnl_indicator = "🟒" if pos['unrealized_pl'] > 0 else "πŸ”΄" if pos['unrealized_pl'] < 0 else "βšͺ"
285
+ df_data.append({
286
+ 'Symbol': f"{pnl_indicator} {pos['symbol']}",
287
+ 'Quantity': f"{pos['qty']:.0f}",
288
+ 'Market Value': f"${pos['market_value']:,.2f}",
289
+ 'Unrealized P&L': f"${pos['unrealized_pl']:,.2f}",
290
+ 'Unrealized %': f"{pos['unrealized_plpc']:.2f}%"
291
+ })
292
+
293
+ return pd.DataFrame(df_data)
294
+
295
+ def refresh_ipo_discoveries_table():
296
+ """Refresh IPO discoveries table with investment decisions"""
297
+ ipos = fetch_from_vm('ipos?limit=100', [])
298
+
299
+ if not ipos:
300
+ return pd.DataFrame(columns=['Status', 'Symbol', 'Security Type', 'Price', 'Detected At'])
301
+
302
+ df_data = []
303
+ for ipo in ipos:
304
+ status_emoji = ipo.get('status_emoji', 'βšͺ')
305
+ status = ipo.get('investment_status', 'UNKNOWN')
306
+
307
+ # Clean up status for display
308
+ display_status = {
309
+ 'INVESTED': '🟒 INVESTED',
310
+ 'ELIGIBLE_NOT_INVESTED': '🟑 ELIGIBLE',
311
+ 'WRONG_TYPE': 'βšͺ WRONG TYPE',
312
+ 'UNKNOWN': 'πŸ”΄ UNKNOWN'
313
+ }.get(status, 'βšͺ UNKNOWN')
314
+
315
+ df_data.append({
316
+ 'Status': display_status,
317
+ 'Symbol': ipo.get('symbol', 'N/A'),
318
+ 'Security Type': ipo.get('security_type', 'N/A'),
319
+ 'Price': f"${ipo.get('trading_price', 0)}" if ipo.get('trading_price') != 'N/A' else 'N/A',
320
+ 'Detected At': ipo.get('detected_at', 'N/A')
321
+ })
322
+
323
+ return pd.DataFrame(df_data)
324
+
325
+ def refresh_vm_stats():
326
+ """Refresh VM statistics"""
327
+ stats = fetch_from_vm('stats', {})
328
+
329
+ if not stats:
330
+ return "0", "0", "0", "0%", "No data"
331
+
332
+ return (
333
+ str(stats.get('total_ipos_detected', 0)),
334
+ str(stats.get('ipos_invested', 0)),
335
+ str(stats.get('cs_stocks_detected', 0)),
336
+ f"{stats.get('investment_rate', 0):.1f}%",
337
+ stats.get('last_updated', 'N/A')
338
+ )
339
+
340
+ def refresh_system_logs():
341
+ """Refresh system logs from VM"""
342
+ logs = fetch_from_vm('logs', [])
343
+
344
+ if not logs:
345
+ return "No logs available from VM"
346
+
347
+ # Format logs for display
348
+ formatted_logs = []
349
+ for log in logs:
350
+ emoji = log.get('emoji', 'βšͺ')
351
+ timestamp = log.get('timestamp', 'N/A')
352
+ message = log.get('message', '')
353
+ formatted_logs.append(f"{emoji} {timestamp} | {message}")
354
+
355
+ return '\n'.join(formatted_logs)
356
+
357
+ def refresh_raw_logs():
358
+ """Refresh raw logs from VM"""
359
+ raw_data = fetch_from_vm('logs/raw?lines=1000', {})
360
+
361
+ if not raw_data:
362
+ return "No raw logs available from VM"
363
+
364
+ content = raw_data.get('content', 'No content')
365
+ total_lines = raw_data.get('total_lines', 0)
366
+ showing_lines = raw_data.get('showing_lines', 0)
367
+
368
+ header = f"=== RAW CRON LOGS ===\nShowing last {showing_lines} of {total_lines} total lines\n\n"
369
+ return header + content
370
+
371
+ # Custom CSS for gorgeous design
372
+ custom_css = """
373
+ .gradio-container {
374
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
375
+ background: #fafafa !important;
376
+ }
377
+
378
+ .main-header {
379
+ background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%);
380
+ color: white;
381
+ padding: 2rem;
382
+ border-radius: 16px;
383
+ margin-bottom: 2rem;
384
+ box-shadow: 0 10px 40px rgba(0, 112, 243, 0.3);
385
+ }
386
+
387
+ .metric-card {
388
+ background: white;
389
+ border: 1px solid #eaeaea;
390
+ border-radius: 12px;
391
+ padding: 1.5rem;
392
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
393
+ transition: all 0.3s ease;
394
+ }
395
+
396
+ .metric-card:hover {
397
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
398
+ transform: translateY(-4px);
399
+ }
400
+
401
+ .gr-button {
402
+ background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%) !important;
403
+ color: white !important;
404
+ border: none !important;
405
+ border-radius: 12px !important;
406
+ font-weight: 600 !important;
407
+ padding: 1rem 2rem !important;
408
+ transition: all 0.3s ease !important;
409
+ box-shadow: 0 4px 16px rgba(0, 112, 243, 0.3) !important;
410
+ }
411
+
412
+ .gr-button:hover {
413
+ transform: translateY(-2px) !important;
414
+ box-shadow: 0 8px 32px rgba(0, 112, 243, 0.4) !important;
415
+ }
416
+
417
+ .gr-textbox, .gr-dataframe {
418
+ border: 1px solid #eaeaea !important;
419
+ border-radius: 12px !important;
420
+ background: white !important;
421
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04) !important;
422
+ }
423
+
424
+ .plotly-graph-div {
425
+ border-radius: 16px !important;
426
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
427
+ background: white !important;
428
+ }
429
+
430
+ .status-invested { color: #00d647 !important; font-weight: 600 !important; }
431
+ .status-eligible { color: #f5a623 !important; font-weight: 600 !important; }
432
+ .status-wrong { color: #8b949e !important; }
433
+ .status-unknown { color: #ff0080 !important; }
434
+ """
435
+
436
+ def create_dashboard():
437
+ with gr.Blocks(
438
+ title="πŸš€ Premium Trading Dashboard",
439
+ theme=gr.themes.Soft(primary_hue="blue"),
440
+ css=custom_css
441
+ ) as demo:
442
+
443
+ # Header
444
+ gr.HTML("""
445
+ <div class="main-header">
446
+ <h1 style="margin: 0; font-size: 3rem; font-weight: 800; text-shadow: 0 2px 4px rgba(0,0,0,0.1);">
447
+ πŸš€ Premium Trading Dashboard
448
+ </h1>
449
+ <p style="margin: 1rem 0 0 0; font-size: 1.3rem; opacity: 0.95;">
450
+ Real-time portfolio monitoring with IPO discovery analytics
451
+ </p>
452
+ </div>
453
+ """)
454
+
455
+ with gr.Tabs():
456
+ # Portfolio Overview Tab
457
+ with gr.Tab("πŸ“Š Portfolio Overview"):
458
+ gr.Markdown("## πŸ’Ό Account Summary")
459
+ with gr.Row():
460
+ portfolio_value = gr.Textbox(label="πŸ’° Portfolio Value", interactive=False, elem_classes=["metric-card"])
461
+ buying_power = gr.Textbox(label="πŸ’³ Buying Power", interactive=False, elem_classes=["metric-card"])
462
+ cash = gr.Textbox(label="πŸ’΅ Cash", interactive=False, elem_classes=["metric-card"])
463
+ day_change = gr.Textbox(label="πŸ“ˆ Day Change", interactive=False, elem_classes=["metric-card"])
464
+ equity = gr.Textbox(label="🏦 Total Equity", interactive=False, elem_classes=["metric-card"])
465
+
466
+ gr.Markdown("## πŸ“ˆ Portfolio Performance")
467
+ portfolio_chart = gr.Plot(label="Portfolio Value Over Time")
468
+
469
+ refresh_overview_btn = gr.Button("πŸ”„ Refresh Portfolio Data", variant="primary", size="lg")
470
+
471
+ # IPO Discoveries Tab
472
+ with gr.Tab("πŸ” IPO Discoveries"):
473
+ gr.Markdown("## πŸ“Š IPO Discovery Analytics")
474
+
475
+ with gr.Row():
476
+ total_ipos = gr.Textbox(label="🎯 Total IPOs Detected", interactive=False, elem_classes=["metric-card"])
477
+ ipos_invested = gr.Textbox(label="πŸ’° IPOs Invested", interactive=False, elem_classes=["metric-card"])
478
+ cs_stocks = gr.Textbox(label="πŸ“ˆ CS Stocks Found", interactive=False, elem_classes=["metric-card"])
479
+ investment_rate = gr.Textbox(label="🎲 Investment Rate", interactive=False, elem_classes=["metric-card"])
480
+ last_updated = gr.Textbox(label="πŸ•’ Last Updated", interactive=False, elem_classes=["metric-card"])
481
+
482
+ with gr.Row():
483
+ with gr.Column(scale=1):
484
+ ipo_chart = gr.Plot(label="Investment Decision Breakdown")
485
+
486
+ with gr.Column(scale=2):
487
+ gr.Markdown("## πŸ†• Recent IPO Discoveries")
488
+ ipo_table = gr.Dataframe(
489
+ label="IPO Discoveries with Investment Decisions",
490
+ elem_classes=["gr-dataframe"]
491
+ )
492
+
493
+ refresh_ipo_btn = gr.Button("πŸ”„ Refresh IPO Data", variant="primary", size="lg")
494
+
495
+ # Current Positions Tab
496
+ with gr.Tab("🏦 Current Positions"):
497
+ gr.Markdown("## πŸ“Š Open Positions")
498
+ positions_table = gr.Dataframe(label="Current Holdings", elem_classes=["gr-dataframe"])
499
+ refresh_positions_btn = gr.Button("πŸ”„ Refresh Positions", variant="primary", size="lg")
500
+
501
+ # System Logs Tab
502
+ with gr.Tab("πŸ“‹ System Logs"):
503
+ gr.Markdown("## πŸ–₯️ Trading Bot Activity")
504
+
505
+ with gr.Row():
506
+ with gr.Column():
507
+ gr.Markdown("### 🎯 Parsed Logs (Color Coded)")
508
+ system_logs = gr.Textbox(
509
+ label="Recent System Activity",
510
+ lines=20,
511
+ max_lines=20,
512
+ interactive=False,
513
+ elem_classes=["gr-textbox"]
514
+ )
515
+
516
+ with gr.Column():
517
+ gr.Markdown("### πŸ“„ Raw Cron Logs")
518
+ raw_logs = gr.Textbox(
519
+ label="Raw Log Output",
520
+ lines=20,
521
+ max_lines=20,
522
+ interactive=False,
523
+ elem_classes=["gr-textbox"]
524
+ )
525
+
526
+ refresh_logs_btn = gr.Button("πŸ”„ Refresh All Logs", variant="primary", size="lg")
527
+
528
+ # Footer
529
+ gr.HTML("""
530
+ <div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eaeaea; margin-top: 3rem; background: white; border-radius: 16px;">
531
+ <p style="font-size: 1.1rem;"><strong>πŸ€– Automated Trading Dashboard</strong></p>
532
+ <p style="font-size: 0.95rem;">Real-time data from Alpaca Markets + VM Analytics | Built with ❀️</p>
533
+ </div>
534
+ """)
535
+
536
+ # Event Handlers
537
+
538
+ # Portfolio tab
539
+ refresh_overview_btn.click(
540
+ fn=refresh_account_overview,
541
+ outputs=[portfolio_value, buying_power, cash, day_change, equity]
542
+ )
543
+ refresh_overview_btn.click(
544
+ fn=create_portfolio_chart,
545
+ outputs=[portfolio_chart]
546
+ )
547
+
548
+ # IPO tab
549
+ refresh_ipo_btn.click(
550
+ fn=refresh_vm_stats,
551
+ outputs=[total_ipos, ipos_invested, cs_stocks, investment_rate, last_updated]
552
+ )
553
+ refresh_ipo_btn.click(
554
+ fn=create_ipo_discovery_chart,
555
+ outputs=[ipo_chart]
556
+ )
557
+ refresh_ipo_btn.click(
558
+ fn=refresh_ipo_discoveries_table,
559
+ outputs=[ipo_table]
560
+ )
561
+
562
+ # Positions tab
563
+ refresh_positions_btn.click(
564
+ fn=refresh_positions_table,
565
+ outputs=[positions_table]
566
+ )
567
+
568
+ # Logs tab
569
+ refresh_logs_btn.click(
570
+ fn=refresh_system_logs,
571
+ outputs=[system_logs]
572
+ )
573
+ refresh_logs_btn.click(
574
+ fn=refresh_raw_logs,
575
+ outputs=[raw_logs]
576
+ )
577
+
578
+ # Initial data load
579
+ demo.load(
580
+ fn=refresh_account_overview,
581
+ outputs=[portfolio_value, buying_power, cash, day_change, equity]
582
+ )
583
+ demo.load(fn=create_portfolio_chart, outputs=[portfolio_chart])
584
+ demo.load(fn=refresh_positions_table, outputs=[positions_table])
585
+ demo.load(
586
+ fn=refresh_vm_stats,
587
+ outputs=[total_ipos, ipos_invested, cs_stocks, investment_rate, last_updated]
588
+ )
589
+ demo.load(fn=create_ipo_discovery_chart, outputs=[ipo_chart])
590
+ demo.load(fn=refresh_ipo_discoveries_table, outputs=[ipo_table])
591
+
592
+ return demo
593
+
594
+ # Create and launch
595
+ demo = create_dashboard()
596
+
597
+ if __name__ == "__main__":
598
+ demo.launch()
hf_space/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Trading Dashboard Requirements - Full Featured
2
+ gradio>=5.0.0
3
+ plotly>=6.0.0
4
+ pandas>=2.0.0
5
+ numpy>=1.20.0
6
+ alpaca-py>=0.8.0
7
+ requests>=2.28.0
8
+ flask>=2.0.0
9
+ flask-cors>=4.0.0
hf_space/vm_data_server.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ VM Data Server
4
+ Exposes local trading bot data via API for dashboard consumption
5
+ Runs on the VM alongside your trading bot
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import pandas as pd
11
+ from datetime import datetime, timedelta
12
+ from flask import Flask, jsonify, request
13
+ from flask_cors import CORS
14
+ import logging
15
+
16
+ app = Flask(__name__)
17
+ CORS(app) # Allow dashboard to connect from anywhere
18
+
19
+ # Configure logging
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # File paths
24
+ PORTFOLIO_FILE = 'portfolio.txt'
25
+ NEW_TICKERS_LOG_FILE = 'new_tickers_log.csv'
26
+ SCRIPT_LOG_FILE = 'script.log'
27
+ BUY_QUEUE_FILE = 'buy_queue.json'
28
+ CURRENT_TICKERS_FILE = 'current_tickers.txt'
29
+
30
+ def load_portfolio_data():
31
+ """Load portfolio CSV data"""
32
+ try:
33
+ if os.path.exists(PORTFOLIO_FILE):
34
+ df = pd.read_csv(PORTFOLIO_FILE)
35
+ return df.to_dict('records')
36
+ return []
37
+ except Exception as e:
38
+ logger.error(f"Error loading portfolio: {e}")
39
+ return []
40
+
41
+ def load_new_tickers_with_decisions():
42
+ """Load IPO discoveries with investment decisions"""
43
+ try:
44
+ if not os.path.exists(NEW_TICKERS_LOG_FILE):
45
+ return []
46
+
47
+ df = pd.read_csv(NEW_TICKERS_LOG_FILE)
48
+ portfolio_data = load_portfolio_data()
49
+
50
+ # Get symbols we actually invested in
51
+ invested_symbols = set()
52
+ for trade in portfolio_data:
53
+ invested_symbols.add(trade.get('symbol', ''))
54
+
55
+ # Add investment decision to each IPO
56
+ enriched_ipos = []
57
+ for _, row in df.iterrows():
58
+ symbol = row.get('Symbol', '')
59
+ security_type = row.get('Security_Type', '')
60
+
61
+ # Determine investment status
62
+ if symbol in invested_symbols:
63
+ investment_status = 'INVESTED'
64
+ status_color = 'success'
65
+ status_emoji = '🟒'
66
+ elif security_type == 'CS':
67
+ investment_status = 'ELIGIBLE_NOT_INVESTED'
68
+ status_color = 'warning'
69
+ status_emoji = '🟑'
70
+ elif security_type in ['SP', 'WARRANT', 'UNIT']:
71
+ investment_status = 'WRONG_TYPE'
72
+ status_color = 'neutral'
73
+ status_emoji = 'βšͺ'
74
+ else:
75
+ investment_status = 'UNKNOWN'
76
+ status_color = 'error'
77
+ status_emoji = 'πŸ”΄'
78
+
79
+ enriched_ipos.append({
80
+ 'symbol': symbol,
81
+ 'security_type': security_type,
82
+ 'trading_price': row.get('Trading_Price', 'N/A'),
83
+ 'detected_at': row.get('Detected_At', 'N/A'),
84
+ 'investment_status': investment_status,
85
+ 'status_color': status_color,
86
+ 'status_emoji': status_emoji
87
+ })
88
+
89
+ # Sort by detection date (newest first)
90
+ enriched_ipos.sort(key=lambda x: x['detected_at'], reverse=True)
91
+
92
+ return enriched_ipos
93
+ except Exception as e:
94
+ logger.error(f"Error loading IPO data: {e}")
95
+ return []
96
+
97
+ def load_script_logs(lines=100):
98
+ """Load recent script logs"""
99
+ try:
100
+ if not os.path.exists(SCRIPT_LOG_FILE):
101
+ return []
102
+
103
+ with open(SCRIPT_LOG_FILE, 'r') as f:
104
+ all_lines = f.readlines()
105
+
106
+ # Get recent lines
107
+ recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
108
+
109
+ # Parse log lines
110
+ logs = []
111
+ for line in recent_lines:
112
+ line = line.strip()
113
+ if line:
114
+ # Try to parse timestamp and level
115
+ parts = line.split(' - ', 2)
116
+ if len(parts) >= 3:
117
+ timestamp = parts[0]
118
+ level = parts[1]
119
+ message = parts[2]
120
+
121
+ # Determine log type for color coding
122
+ if 'ERROR' in level:
123
+ log_type = 'error'
124
+ emoji = 'πŸ”΄'
125
+ elif 'WARNING' in level or 'WARN' in level:
126
+ log_type = 'warning'
127
+ emoji = '🟑'
128
+ elif 'Buy order placed' in message or 'Sold' in message:
129
+ log_type = 'trade'
130
+ emoji = 'πŸ’°'
131
+ elif 'Found' in message and 'new ticker' in message:
132
+ log_type = 'discovery'
133
+ emoji = 'πŸ”'
134
+ elif 'INFO' in level:
135
+ log_type = 'info'
136
+ emoji = 'πŸ”΅'
137
+ else:
138
+ log_type = 'default'
139
+ emoji = 'βšͺ'
140
+
141
+ logs.append({
142
+ 'timestamp': timestamp,
143
+ 'level': level,
144
+ 'message': message,
145
+ 'log_type': log_type,
146
+ 'emoji': emoji,
147
+ 'full_line': line
148
+ })
149
+ else:
150
+ # Fallback for unparseable lines
151
+ logs.append({
152
+ 'timestamp': 'N/A',
153
+ 'level': 'RAW',
154
+ 'message': line,
155
+ 'log_type': 'default',
156
+ 'emoji': 'βšͺ',
157
+ 'full_line': line
158
+ })
159
+
160
+ return logs
161
+ except Exception as e:
162
+ logger.error(f"Error loading logs: {e}")
163
+ return []
164
+
165
+ def load_buy_queue():
166
+ """Load current buy queue"""
167
+ try:
168
+ if os.path.exists(BUY_QUEUE_FILE):
169
+ with open(BUY_QUEUE_FILE, 'r') as f:
170
+ return json.load(f)
171
+ return []
172
+ except Exception as e:
173
+ logger.error(f"Error loading buy queue: {e}")
174
+ return []
175
+
176
+ def get_system_stats():
177
+ """Get system statistics"""
178
+ try:
179
+ portfolio_data = load_portfolio_data()
180
+ ipo_data = load_new_tickers_with_decisions()
181
+
182
+ # Calculate stats
183
+ total_ipos_detected = len(ipo_data)
184
+ ipos_invested = len([ipo for ipo in ipo_data if ipo['investment_status'] == 'INVESTED'])
185
+ current_positions = len(portfolio_data)
186
+
187
+ # Get detection stats by type
188
+ cs_stocks = len([ipo for ipo in ipo_data if ipo['security_type'] == 'CS'])
189
+ other_types = total_ipos_detected - cs_stocks
190
+
191
+ return {
192
+ 'total_ipos_detected': total_ipos_detected,
193
+ 'ipos_invested': ipos_invested,
194
+ 'current_positions': current_positions,
195
+ 'cs_stocks_detected': cs_stocks,
196
+ 'other_types_detected': other_types,
197
+ 'investment_rate': round((ipos_invested / cs_stocks * 100) if cs_stocks > 0 else 0, 1),
198
+ 'last_updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
199
+ }
200
+ except Exception as e:
201
+ logger.error(f"Error calculating stats: {e}")
202
+ return {}
203
+
204
+ # API Endpoints
205
+
206
+ @app.route('/health')
207
+ def health_check():
208
+ """Health check endpoint"""
209
+ return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
210
+
211
+ @app.route('/api/portfolio')
212
+ def get_portfolio():
213
+ """Get portfolio data"""
214
+ return jsonify(load_portfolio_data())
215
+
216
+ @app.route('/api/ipos')
217
+ def get_ipos():
218
+ """Get IPO discoveries with investment decisions"""
219
+ limit = request.args.get('limit', 50, type=int)
220
+ ipos = load_new_tickers_with_decisions()
221
+ return jsonify(ipos[:limit])
222
+
223
+ @app.route('/api/logs')
224
+ def get_logs():
225
+ """Get script logs"""
226
+ lines = request.args.get('lines', 100, type=int)
227
+ logs = load_script_logs(lines)
228
+ return jsonify(logs)
229
+
230
+ @app.route('/api/buy_queue')
231
+ def get_buy_queue():
232
+ """Get current buy queue"""
233
+ return jsonify(load_buy_queue())
234
+
235
+ @app.route('/api/stats')
236
+ def get_stats():
237
+ """Get system statistics"""
238
+ return jsonify(get_system_stats())
239
+
240
+ @app.route('/api/logs/raw')
241
+ def get_raw_logs():
242
+ """Get raw log file content"""
243
+ lines = request.args.get('lines', 200, type=int)
244
+ try:
245
+ if not os.path.exists(SCRIPT_LOG_FILE):
246
+ return jsonify({'content': 'No log file found'})
247
+
248
+ with open(SCRIPT_LOG_FILE, 'r') as f:
249
+ all_lines = f.readlines()
250
+
251
+ recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
252
+ content = ''.join(recent_lines)
253
+
254
+ return jsonify({
255
+ 'content': content,
256
+ 'total_lines': len(all_lines),
257
+ 'showing_lines': len(recent_lines)
258
+ })
259
+ except Exception as e:
260
+ return jsonify({'error': str(e), 'content': ''})
261
+
262
+ if __name__ == '__main__':
263
+ print("πŸš€ Starting VM Data Server...")
264
+ print("πŸ“‘ This exposes your trading bot data via API")
265
+ print("🌐 Dashboard can now access:")
266
+ print(" β€’ IPO discoveries with investment decisions")
267
+ print(" β€’ Raw cron logs with color coding")
268
+ print(" β€’ Portfolio data from VM files")
269
+ print(" β€’ Buy queue and system stats")
270
+ print("-" * 50)
271
+
272
+ # Run on all interfaces so dashboard can connect
273
+ app.run(
274
+ host='0.0.0.0', # Allow external connections
275
+ port=8090, # Different from dashboard port
276
+ debug=False
277
+ )