SohaVaidya/forex_Analyzer
0
1import os2import gradio as gr3import requests4import pandas as pd5import matplotlib.pyplot as plt6from matplotlib.backends.backend_pdf import PdfPages7from ta.trend import EMAIndicator, ADXIndicator8from ta.momentum import RSIIndicator, StochRSIIndicator9from ta.volatility import AverageTrueRange, BollingerBands10 11# === Supabase Auth (NEW) ===12from supabase import create_client, Client13import datetime14import time # for polite delays during batched fetches15 16# -- Configure Supabase from secrets17SUPABASE_URL = os.getenv("SUPABASE_URL")18SUPABASE_ANON_KEY = os.getenv("SUPABASE_ANON_KEY")19 20def _validate_supabase_url(url: str):21 if not url:22 raise ValueError("SUPABASE_URL is empty.")23 if "supabase.com/dashboard" in url or "/settings/" in url:24 raise ValueError("SUPABASE_URL points to dashboard, use https://<ref>.supabase.co")25 if not url.endswith(".supabase.co"):26 raise ValueError(f"SUPABASE_URL looks unusual: {url}")27 28_validate_supabase_url(SUPABASE_URL)29 30def get_supabase() -> Client:31 if not SUPABASE_URL or not SUPABASE_ANON_KEY:32 raise ValueError("❌ Supabase credentials missing. Set SUPABASE_URL and SUPABASE_ANON_KEY as secrets.")33 return create_client(SUPABASE_URL, SUPABASE_ANON_KEY)34 35sb = get_supabase()36 37def safe_upsert_profile(payload: dict):38 """Upsert profile but don't crash if schema cache lags."""39 try:40 sb.table("profiles").upsert(payload, on_conflict="id").execute()41 except Exception as e:42 if "PGRST205" not in str(e):43 print("Profile upsert error:", e)44 45def upsert_profile(auth_response):46 """Create/Update user profile row and last_login timestamp."""47 user = getattr(auth_response, "user", None)48 if not user:49 return50 email = user.email51 uid = user.id52 now = datetime.datetime.utcnow().isoformat()53 safe_upsert_profile({"id": uid, "email": email, "last_login": now})54 55# === Trading app constants (yours) ===56TEMP_PDF_PATH = "investment_growth_summary.pdf"57TEMP_TRADE_CSV = "trade_log.csv"58 59# Realistic Cost Parameters60slippage = 0.001 # 0.1%61spread = 0.001 # 0.1%62commission = 0.001 # 0.1% per trade63 64# Estimated candles per day for hints/estimates (not used to force API now)65CANDLES_PER_DAY = {66 "1min": 24*60,67 "5min": 24*12,68 "15min": 24*4,69 "30min": 24*2,70 "45min": int(24*1.3333),71 "1h": 24,72 "2h": 12,73 "4h": 6,74 "8h": 3,75 "1day": 1,76 "1week": 1/7,77 "1month": 1/3078}79 80# Map timeframe to a pandas offset so we can step back exactly one bar per batch loop81INTERVAL_TO_PANDAS_OFFSET = {82 "1min": "1min", "5min": "5min", "15min": "15min", "30min": "30min", "45min": "45min",83 "1h": "1h", "2h": "2h", "4h": "4h", "8h": "8h",84 "1day": "1D", "1week": "7D", "1month": "30D" # rough for week/month, good enough for paging85}86 87# === Data fetch ===88def get_api_key():89 api_key = os.getenv("TWELVE_DATA_API_KEY")90 if not api_key:91 raise ValueError("❌ API Key not found in environment.")92 return api_key93 94def _step_back(ts: pd.Timestamp, interval: str):95 """Return a timestamp just before ts by one bar of the given interval."""96 offset = INTERVAL_TO_PANDAS_OFFSET.get(interval, "1D")97 return (ts - pd.tseries.frequencies.to_offset(offset)).to_pydatetime()98 99def fetch_data(100 symbol,101 interval,102 outputsize=3000, # used only for the "no dates" single-call path103 only_2025=True,104 start_date=None,105 end_date=None,106 max_batches=10, # increase if you need to go further back107 sleep_secs=0.2 # be gentle to the API; tweak if rate-limited108):109 """110 Batched fetch that honors start_date/end_date windows beyond the 5000-bar cap.111 - If no dates: single call for the latest window (like before).112 - If dates: iterate in 5000-candle chunks going back via end_date paging.113 """114 api_key = get_api_key()115 116 # === Single-call path (latest window), like before ===117 if not start_date and not end_date:118 # respect TwelveData's cap119 osize = min(max(1, int(outputsize or 3000)), 5000)120 url = "https://api.twelvedata.com/time_series"121 params = {122 "symbol": symbol,123 "interval": interval,124 "outputsize": osize,125 "apikey": api_key126 }127 resp = requests.get(url, params=params)128 data = resp.json()129 if "values" not in data:130 raise ValueError(f"❌ API error: {data.get('message', 'Unknown error')}")131 df = pd.DataFrame(data["values"])132 if df.empty:133 return df134 df["datetime"] = pd.to_datetime(df["datetime"])135 df = df.sort_values("datetime").set_index("datetime")136 for col in ['open', 'high', 'low', 'close']:137 df[col] = df[col].astype(float)138 # Apply 'only_2025' only if user didn't specify custom dates139 if only_2025:140 df = df[df.index.year == 2025]141 return df.dropna()142 143 # === Batched path (dates provided) ===144 target_start = pd.to_datetime(start_date) if start_date else pd.Timestamp("1970-01-01", tz=None)145 target_end = pd.to_datetime(end_date) if end_date else pd.Timestamp.utcnow()146 if target_start > target_end:147 raise ValueError("Start Date must be before End Date.")148 149 frames = []150 current_end = target_end.to_pydatetime()151 batches = 0152 url = "https://api.twelvedata.com/time_series"153 154 while batches < max_batches:155 params = {156 "symbol": symbol,157 "interval": interval,158 "outputsize": 5000, # max allowed159 "apikey": api_key,160 "end_date": current_end.strftime("%Y-%m-%d %H:%M:%S"),161 }162 resp = requests.get(url, params=params)163 data = resp.json()164 165 if "values" not in data:166 # bail on API error167 raise ValueError(f"❌ API error: {data.get('message', 'Unknown error')}")168 169 batch = pd.DataFrame(data["values"])170 if batch.empty:171 break172 173 batch["datetime"] = pd.to_datetime(batch["datetime"])174 batch = batch.sort_values("datetime").set_index("datetime")175 for col in ['open', 'high', 'low', 'close']:176 batch[col] = batch[col].astype(float)177 178 # Keep only up to current_end (paranoia)179 batch = batch[batch.index <= pd.to_datetime(current_end)]180 if batch.empty:181 break182 183 frames.append(batch)184 185 earliest = batch.index.min()186 # stop if we've reached or crossed the target_start187 if earliest <= target_start:188 break189 190 # step back just before the earliest bar we received191 current_end = _step_back(earliest, interval)192 batches += 1193 if sleep_secs:194 time.sleep(sleep_secs)195 196 if not frames:197 return pd.DataFrame()198 199 df = pd.concat(frames, axis=0).sort_values("datetime")200 # de-duplicate in case of one-bar overlaps between batches201 df = df[~df.index.duplicated(keep="first")]202 203 # final filter to requested window204 df = df[(df.index >= target_start) & (df.index <= target_end)]205 206 # Do NOT apply 'only_2025' here (you provided dates). If you really want to, uncomment:207 # if only_2025:208 # df = df[df.index.year == 2025]209 210 return df.dropna()211 212# === Strategy / PnL logic (yours) ===213def get_rolling_profits(df, lot_size, capital,214 use_ema, ema1, ema2,215 use_rsi, rsi, rsi_thresh,216 use_adx, adx, adx_thresh,217 use_atr, atr_window,218 use_stoch, stoch, stoch_thresh,219 use_bb, bb,220 signal_trigger_thresh,221 sl_percent, tp_percent):222 if use_ema:223 df['ema_short'] = EMAIndicator(df['close'], window=ema1).ema_indicator()224 df['ema_long'] = EMAIndicator(df['close'], window=ema2).ema_indicator()225 if use_rsi:226 df['rsi'] = RSIIndicator(df['close'], window=rsi).rsi()227 if use_adx:228 df['adx'] = ADXIndicator(df['high'], df['low'], df['close'], window=adx).adx()229 if use_atr:230 df['atr'] = AverageTrueRange(df['high'], df['low'], df['close'], window=atr_window).average_true_range()231 if use_stoch:232 df['stoch'] = StochRSIIndicator(df['close'], window=stoch).stochrsi()233 if use_bb:234 bb_indicator = BollingerBands(df['close'], window=bb, window_dev=2)235 df['bb_upper'] = bb_indicator.bollinger_hband()236 df['bb_lower'] = bb_indicator.bollinger_lband()237 238 df['position'] = 0239 if use_ema:240 df.loc[df['ema_short'] > df['ema_long'], 'position'] += 1241 if use_rsi:242 df.loc[df['rsi'] < rsi_thresh, 'position'] += 1243 if use_adx:244 df.loc[df['adx'] > adx_thresh, 'position'] += 1245 if use_stoch:246 df.loc[df['stoch'] < stoch_thresh, 'position'] += 1247 if use_bb:248 df.loc[df['close'] < df['bb_lower'], 'position'] += 1249 250 df['signal'] = df['position'].apply(lambda x: 1 if x >= signal_trigger_thresh else 0)251 252 position = 0253 equity = capital254 equity_curve = [capital]255 trades = []256 257 entry_time = None258 entry_price = 0259 units = 0260 indicators = ""261 stop_loss_price = 0262 take_profit_price = 0263 264 for i in range(len(df)):265 if i == 0:266 continue267 268 row = df.iloc[i]269 price = row['close']270 high_price = row['high']271 low_price = row['low']272 273 # If a position is open, check for SL/TP hit274 if position > 0:275 # Stop Loss276 if low_price <= stop_loss_price:277 exit_time = df.index[i]278 exit_price = stop_loss_price * (1 - slippage - spread)279 proceeds = position * exit_price * (1 - commission)280 pnl = proceeds - (units * entry_price)281 trades.append({282 "Entry Time": entry_time,283 "Exit Time": exit_time,284 "Entry Price": round(entry_price, 4),285 "Exit Price": round(exit_price, 4),286 "Units": round(units, 4),287 "PnL ($)": round(pnl, 2),288 "Indicators Triggered": indicators,289 "Exit Reason": "Stop Loss"290 })291 equity = proceeds292 position = 0293 stop_loss_price = 0294 take_profit_price = 0295 # Take Profit296 elif high_price >= take_profit_price:297 exit_time = df.index[i]298 exit_price = take_profit_price * (1 - slippage - spread)299 proceeds = position * exit_price * (1 - commission)300 pnl = proceeds - (units * entry_price)301 trades.append({302 "Entry Time": entry_time,303 "Exit Time": exit_time,304 "Entry Price": round(entry_price, 4),305 "Exit Price": round(exit_price, 4),306 "Units": round(units, 4),307 "PnL ($)": round(pnl, 2),308 "Indicators Triggered": indicators,309 "Exit Reason": "Take Profit"310 })311 equity = proceeds312 position = 0313 stop_loss_price = 0314 take_profit_price = 0315 316 # Entry317 if df['signal'].iloc[i] == 1 and position == 0:318 entry_time = df.index[i]319 entry_price = price * (1 + slippage + spread)320 units = equity / (entry_price * (1 + commission))321 equity -= units * entry_price * commission322 position = units323 indicators = ", ".join(324 [ind for ind, cond in zip(325 ['EMA', 'RSI', 'ADX', 'STOCH', 'BB'],326 [use_ema, use_rsi, use_adx, use_stoch, use_bb]327 ) if cond]328 )329 stop_loss_price = entry_price * (1 - sl_percent)330 take_profit_price = entry_price * (1 + tp_percent)331 332 # Signal exit (if not already exited by SL/TP)333 elif df['signal'].iloc[i] == 0 and position > 0:334 exit_time = df.index[i]335 exit_price = price * (1 - slippage - spread)336 proceeds = position * exit_price * (1 - commission)337 pnl = proceeds - (units * entry_price)338 trades.append({339 "Entry Time": entry_time,340 "Exit Time": exit_time,341 "Entry Price": round(entry_price, 4),342 "Exit Price": round(exit_price, 4),343 "Units": round(units, 4),344 "PnL ($)": round(pnl, 2),345 "Indicators Triggered": indicators,346 "Exit Reason": "Signal Change"347 })348 equity = proceeds349 position = 0350 stop_loss_price = 0351 take_profit_price = 0352 353 current_value = equity if position == 0 else position * price354 equity_curve.append(current_value)355 356 # Close any open position at the end357 if position > 0:358 last_price = df['close'].iloc[-1]359 exit_time = df.index[-1]360 exit_price = last_price * (1 - slippage - spread)361 proceeds = position * exit_price * (1 - commission)362 pnl = proceeds - (units * entry_price)363 trades.append({364 "Entry Time": entry_time,365 "Exit Time": exit_time,366 "Entry Price": round(entry_price, 4),367 "Exit Price": round(exit_price, 4),368 "Units": round(units, 4),369 "PnL ($)": round(pnl, 2),370 "Indicators Triggered": indicators,371 "Exit Reason": "End of Data (Forced Close)"372 })373 equity = proceeds374 375 df = df.iloc[1:].copy()376 df['cumulative'] = equity_curve[1:]377 378 # Dynamic grouping: weekly for short spans (<60 days), monthly otherwise379 try:380 span_days = (df.index[-1] - df.index[0]).days381 except Exception:382 span_days = 0383 bucket = "M" if span_days >= 60 else "W" # month vs week384 df['bucket'] = df.index.to_series().dt.to_period(bucket)385 386 grouped = df.groupby("bucket")387 result = []388 for i, (name, group) in enumerate(grouped, 1):389 principal_for_bucket = group['cumulative'].iloc[0] if len(group['cumulative']) > 0 else capital390 profit = group['cumulative'].iloc[-1] - principal_for_bucket if len(group['cumulative']) > 0 else 0391 total = group['cumulative'].iloc[-1] if len(group['cumulative']) > 0 else principal_for_bucket392 # Keep column header as "Month" for UI compatibility (even if bucket is weekly)393 result.append([str(name), round(principal_for_bucket, 2), round(lot_size * 100, 2), round(profit, 2), round(total, 2)])394 395 trade_log_df = pd.DataFrame(trades)396 trade_log_df.to_csv(TEMP_TRADE_CSV, index=False)397 table = pd.DataFrame(result, columns=["Month", "Principal (P)", "Lots * 0.01", "Monthly Profit (T)", "P + T"])398 return df.dropna(), table, trade_log_df399 400def generate_growth_analysis(df_growth, capital):401 final_equity = df_growth['cumulative'].iloc[-1]402 total_return = (final_equity / capital) - 1403 investment_value = final_equity404 num_trades = len(df_growth[df_growth['signal'].diff() == 1]) # Count entries based on signal change405 summary = (406 f"Initial Capital: ${capital:.2f}\n"407 f"Final Investment Value: ${investment_value:.2f}\n"408 f"Total Return: {total_return * 100:.2f}%\n"409 f"Total Trades Taken: {int(num_trades)}"410 )411 return summary412 413def build_investment_chart(df_growth, capital, table_df):414 fig, ax = plt.subplots(figsize=(10, 6))415 ax.plot(table_df['Month'], table_df['Principal (P)'], label='Principal (P)', marker='o')416 ax.plot(table_df['Month'], table_df['Monthly Profit (T)'], label='Monthly Profit (T)', marker='x')417 ax.plot(table_df['Month'], table_df['P + T'], label='P + T', linestyle='--', marker='s')418 ax.set_title('Investment Growth (Monthly/Weekly)')419 ax.set_xlabel('Period')420 ax.set_ylabel('Value ($)')421 ax.legend()422 ax.grid(True)423 424 summary = generate_growth_analysis(df_growth, capital)425 with PdfPages(TEMP_PDF_PATH) as pdf:426 pdf.savefig(fig) # DO NOT close the fig you return427 428 return table_df, fig, TEMP_PDF_PATH, summary429 430currency_pairs = ["EUR/USD", "USD/JPY", "GBP/USD", "AUD/USD", "USD/CAD", "USD/CHF", "NZD/USD", "EUR/GBP", "EUR/JPY", "USD/CNY", "XAU/USD", "XAG/USD"]431timeframes = ["1min", "5min", "15min", "30min", "45min", "1h", "2h", "4h", "8h", "1day", "1week", "1month"]432 433with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:434 gr.Markdown("# 🐂 Bullrun Capital | Investment Growth App")435 436 # === Auth state (NEW) ===437 user_state = gr.State(value=None) # holds session dict438 email_state = gr.State(value=None) # convenience email label439 440 # === Account (NEW) ===441 with gr.Tab("🔐 Account"):442 with gr.Row():443 with gr.Column():444 gr.Markdown("### Sign up")445 su_email = gr.Textbox(label="Email")446 su_password = gr.Textbox(label="Password", type="password")447 su_name = gr.Textbox(label="Full name (optional)")448 btn_signup = gr.Button("Create account")449 out_signup = gr.Markdown()450 451 with gr.Column():452 gr.Markdown("### Log in")453 li_email = gr.Textbox(label="Email")454 li_password = gr.Textbox(label="Password", type="password")455 btn_login = gr.Button("Log in")456 out_login = gr.Markdown()457 458 with gr.Row():459 btn_logout = gr.Button("Log out", variant="secondary")460 whoami = gr.Markdown("Not logged in.")461 462 # === Auth handlers (NEW) ===463 def do_signup(email, password, full_name):464 try:465 auth = sb.auth.sign_up({"email": email, "password": password})466 if auth.user:467 safe_upsert_profile({"id": auth.user.id, "email": email, "full_name": full_name or None})468 return "✅ Check your inbox to confirm your email before logging in."469 except Exception as e:470 return f"❌ Sign up failed: {e}"471 472 def do_login(email, password):473 try:474 session = sb.auth.sign_in_with_password({"email": email, "password": password})475 if not session.user:476 return gr.update(value="❌ Login failed."), None, None477 upsert_profile(session)478 return gr.update(value="✅ Login successful."), session.model_dump(), session.user.email479 except Exception as e:480 return gr.update(value=f"❌ Login failed: {e}"), None, None481 482 def do_logout():483 try:484 sb.auth.sign_out()485 except Exception:486 pass487 return None, None, "👋 Logged out."488 489 def show_whoami(session, email):490 if session and email:491 return f"🔒 Session active for **{email}**"492 return "Not logged in."493 494 btn_signup.click(do_signup, [su_email, su_password, su_name], [out_signup])495 btn_login.click(do_login, [li_email, li_password], [out_login, user_state, email_state])496 btn_logout.click(lambda: do_logout(), None, [user_state, email_state, whoami])497 user_state.change(show_whoami, [user_state, email_state], whoami)498 499 # === Protected tabs (wrapped in Groups so we can toggle visibility) ===500 501 with gr.Tab("Strategy Inputs"):502 with gr.Group(visible=False) as strategy_group:503 symbol = gr.Dropdown(currency_pairs, label="Symbol", value="XAU/USD")504 interval = gr.Dropdown(timeframes, label="Timeframe", value="1h")505 lot_size = gr.Slider(0.01, 1.0, value=0.1, label="Lot Size")506 capital = gr.Number(value=1000, label="Initial Capital ($)")507 only_2025 = gr.Checkbox(label="Only 2025 Data")508 start_date = gr.Textbox(label="Start Date (YYYY-MM-DD)", placeholder="Optional")509 end_date = gr.Textbox(label="End Date (YYYY-MM-DD)", placeholder="Optional")510 511 with gr.Tab("Indicator Settings"):512 with gr.Group(visible=False) as indicator_group:513 use_ema = gr.Checkbox(label="Use EMA", value=True)514 ema_short = gr.Slider(5, 50, value=10, step=1, label="EMA Short", interactive=True)515 ema_long = gr.Slider(10, 100, value=50, step=1, label="EMA Long", interactive=True)516 517 use_rsi = gr.Checkbox(label="Use RSI", value=True)518 rsi_val = gr.Slider(7, 21, value=14, step=1, label="RSI Period", interactive=True)519 rsi_thresh = gr.Slider(10, 50, value=30, step=1, label="RSI Threshold", interactive=True)520 521 use_adx = gr.Checkbox(label="Use ADX", value=True)522 adx_val = gr.Slider(7, 21, value=14, step=1, label="ADX Period", interactive=True)523 adx_thresh = gr.Slider(10, 50, value=25, step=1, label="ADX Threshold", interactive=True)524 525 use_stoch = gr.Checkbox(label="Use Stochastic RSI", value=True)526 stochrsi_window = gr.Slider(10, 50, value=14, step=1, label="Stochastic RSI Window", interactive=True)527 stoch_thresh = gr.Slider(0.0, 1.0, value=0.2, step=0.01, label="Stochastic RSI Threshold", interactive=True)528 529 use_atr = gr.Checkbox(label="Use ATR", value=True)530 atr_window = gr.Slider(7, 21, value=14, step=1, label="ATR Window", interactive=True)531 532 use_bb = gr.Checkbox(label="Use Bollinger Bands", value=True)533 bb_window = gr.Slider(10, 50, value=20, step=1, label="Bollinger Bands Window", interactive=True)534 535 signal_trigger_thresh = gr.Slider(1, 6, value=2, step=1, label="Signal Trigger Threshold")536 537 with gr.Tab("Risk Settings"):538 with gr.Group(visible=False) as risk_group:539 sl_percent = gr.Slider(0.001, 0.1, value=0.01, step=0.001, label="Stop Loss (%) of Entry Price") # 1% SL540 tp_percent = gr.Slider(0.001, 0.2, value=0.02, step=0.001, label="Take Profit (%) of Entry Price") # 2% TP541 run_button = gr.Button("Generate Chart")542 543 with gr.Tab("Investment Growth"):544 with gr.Group(visible=False) as growth_group:545 table_output = gr.Dataframe()546 chart_output = gr.Plot()547 file_output = gr.File()548 summary_output = gr.Textbox(label="📋 Investment Analysis Summary", lines=6)549 550 with gr.Tab("📘 Trade Log History"):551 with gr.Group(visible=False) as log_group:552 trade_log_df = gr.Dataframe(label="🧾 Trades Executed")553 trade_csv_file = gr.File(label="⬇️ Download Trade Log (CSV)")554 555 with gr.Tab("📊 Bull Run Capital Chart"):556 with gr.Group(visible=False) as promo_group:557 with gr.Row():558 with gr.Column():559 gr.Image(560 value="https://huggingface.co/spaces/BullRunCapital/Forex_Testing/resolve/main/Table.png",561 label="📈 Monthly Growth Table",562 show_label=True,563 interactive=True564 )565 gr.File(566 value="https://huggingface.co/spaces/BullRunCapital/Forex_Testing/resolve/main/Table.png",567 label="⬇️ Download Table Image",568 file_types=[".png"]569 )570 571 # === UI helpers (yours) ===572 def toggle_slider(use):573 return gr.update(interactive=use)574 575 use_ema.change(lambda u: (toggle_slider(u), toggle_slider(u)), inputs=use_ema, outputs=[ema_short, ema_long])576 use_rsi.change(lambda u: (toggle_slider(u), toggle_slider(u)), inputs=use_rsi, outputs=[rsi_val, rsi_thresh])577 use_adx.change(lambda u: (toggle_slider(u), toggle_slider(u)), inputs=use_adx, outputs=[adx_val, adx_thresh])578 use_stoch.change(lambda u: (toggle_slider(u), toggle_slider(u)), inputs=use_stoch, outputs=[stochrsi_window, stoch_thresh])579 use_atr.change(toggle_slider, inputs=use_atr, outputs=[atr_window])580 use_bb.change(toggle_slider, inputs=use_bb, outputs=[bb_window])581 582 # === Main run function (yours) with guards ===583 def run_growth_app(sym, tf, lot, cap, only_2025,584 use_ema, ema1, ema2,585 use_rsi, rsi, rsi_thresh,586 use_adx, adx, adx_thresh,587 use_atr, atr_win,588 use_stoch, stoch, stoch_thresh,589 use_bb, bb,590 signal_thresh,591 sl_p, tp_p,592 sd, ed):593 try:594 sd = pd.to_datetime(sd).strftime('%Y-%m-%d') if sd else None595 ed = pd.to_datetime(ed).strftime('%Y-%m-%d') if ed else None596 df = fetch_data(sym, tf, start_date=sd, end_date=ed, only_2025=only_2025, max_batches=12, sleep_secs=0.15)597 598 if df is None or df.empty:599 msg = f"No data for {sym} at {tf} with the selected dates. Try narrowing dates, increasing max_batches, or a higher timeframe."600 return pd.DataFrame({"Error": [msg]}), None, None, msg, pd.DataFrame(), None601 602 df_growth, table_df, trade_log = get_rolling_profits(603 df, lot, cap,604 use_ema, ema1, ema2,605 use_rsi, rsi, rsi_thresh,606 use_adx, adx, adx_thresh,607 use_atr, atr_win,608 use_stoch, stoch, stoch_thresh,609 use_bb, bb,610 signal_thresh,611 sl_p, tp_p612 )613 614 if table_df is None or table_df.empty:615 msg = "No trades generated with current indicators/thresholds. Try lowering the signal threshold or widening dates."616 return pd.DataFrame({"Error": [msg]}), None, None, msg, trade_log, None617 618 table, chart, file, summary = build_investment_chart(df_growth, cap, table_df)619 return table, chart, file, summary, trade_log, TEMP_TRADE_CSV620 621 except Exception as e:622 err = f"Error: {str(e)}"623 return pd.DataFrame({"Error": [err]}), None, None, err, pd.DataFrame(), None624 625 run_button.click(626 run_growth_app,627 inputs=[symbol, interval, lot_size, capital, only_2025,628 use_ema, ema_short, ema_long,629 use_rsi, rsi_val, rsi_thresh,630 use_adx, adx_val, adx_thresh,631 use_atr, atr_window,632 use_stoch, stochrsi_window, stoch_thresh,633 use_bb, bb_window,634 signal_trigger_thresh,635 sl_percent, tp_percent,636 start_date, end_date],637 outputs=[table_output, chart_output, file_output, summary_output, trade_log_df, trade_csv_file]638 )639 640 # === Gate all protected groups based on login (NEW) ===641 def gate_visibility(session):642 visible = session is not None643 return [gr.update(visible=visible)] * 6 # six groups below644 645 user_state.change(646 gate_visibility,647 inputs=[user_state],648 outputs=[strategy_group, indicator_group, risk_group, growth_group, log_group, promo_group]649 )650 651 652if __name__ == "__main__":653 app.queue().launch(654 server_name="0.0.0.0",655 server_port=int(os.environ.get("PORT", "7860")),656 show_error=True657 )658 