| """ |
| Server-side Binance Testnet Integration |
| Uses BinanceConnector on the local server (direct testnet access, no proxy needed). |
| """ |
|
|
| import streamlit as st |
| import os |
| from src.api.binance import BinanceConnector |
| import pandas as pd |
|
|
|
|
| def render_testnet_tab_server(api_key: str, api_secret: str): |
| """ |
| Render testnet tab with server-side API calls. |
| Requires the server to run locally (direct access to testnet.binance.vision). |
| """ |
| try: |
| api_key = api_key.strip() if api_key else '' |
| api_secret = api_secret.strip() if api_secret else '' |
|
|
| if not api_key or not api_secret: |
| st.error("โ API keys are empty after sanitization!") |
| return |
|
|
| if len(api_key) < 20 or len(api_secret) < 20: |
| st.error(f"โ API keys seem too short (key: {len(api_key)}, secret: {len(api_secret)})") |
| return |
|
|
| st.info(f"๐ API Key: {api_key[:8]}...{api_key[-4:]} ({len(api_key)} chars)") |
|
|
| testnet = BinanceConnector( |
| api_key=api_key, |
| api_secret=api_secret, |
| testnet=True |
| ) |
|
|
| |
| try: |
| connectivity = testnet.test_connectivity() |
| if connectivity: |
| st.success("โ
Connected to Binance Testnet") |
| else: |
| st.warning("โ ๏ธ Connectivity test returned false, but trying anyway...") |
| except Exception as e: |
| st.error(f"โ Connectivity test failed: {e}") |
| import traceback |
| st.code(traceback.format_exc()) |
| return |
|
|
| |
| try: |
| balances = testnet.get_all_balances() |
| except Exception as e: |
| st.error(f"โ Failed to get balances: {e}") |
| import traceback |
| st.code(traceback.format_exc()) |
| return |
|
|
| if not balances: |
| st.error("โ No balances found. Account might be empty or API keys invalid.") |
| st.warning("๐ก Tip: Go to testnet.binance.vision and claim test funds") |
| return |
|
|
| st.info(f"๐ Retrieved {len(balances)} balance entries") |
|
|
| |
| portfolio_value = 0.0 |
| positions_data = [] |
| usdt_balance = 0.0 |
|
|
| for currency, amounts in balances.items(): |
| total = float(amounts.get('total', 0)) |
| if total > 0: |
| if currency == 'USDT': |
| portfolio_value += total |
| usdt_balance = float(amounts.get('free', 0)) |
| else: |
| try: |
| ticker = testnet.get_ticker(f"{currency}/USDT") |
| price = float(ticker.get('last', 0)) |
| if price > 0: |
| value_usdt = total * price |
| portfolio_value += value_usdt |
| positions_data.append({ |
| 'Asset': currency, |
| 'Amount': f"{total:.6f}", |
| 'Price': f"${price:,.2f}", |
| 'Value (USDT)': f"${value_usdt:,.2f}" |
| }) |
| except Exception: |
| pass |
|
|
| |
| col1, col2, col3 = st.columns(3) |
| with col1: |
| st.metric( |
| label="๐ฐ Portfolio Value", |
| value=f"${portfolio_value:,.2f}", |
| delta=None |
| ) |
| with col2: |
| st.metric(label="๐ต USDT Balance", value=f"${usdt_balance:,.2f}") |
| with col3: |
| st.metric( |
| label="๐ Total P&L %", |
| value="N/A", |
| delta=None |
| ) |
|
|
| st.markdown("---") |
|
|
| if positions_data: |
| st.markdown("### ๐ Current Positions") |
| st.dataframe(pd.DataFrame(positions_data), use_container_width=True, hide_index=True) |
| else: |
| st.info("No open positions. Portfolio is 100% USDT") |
|
|
| st.markdown("---") |
|
|
| |
| st.markdown("### ๐ฎ Trading Controls") |
| col1, col2 = st.columns(2) |
| with col1: |
| trade_symbol = st.selectbox( |
| "Select Pair", |
| options=['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'XRP/USDT'], |
| key="testnet_symbol" |
| ) |
| with col2: |
| trade_amount = st.number_input( |
| "Amount (USDT)", |
| min_value=10.0, |
| max_value=float(usdt_balance) if usdt_balance > 10 else 10000.0, |
| value=100.0, |
| step=10.0, |
| key="testnet_amount" |
| ) |
|
|
| col1, col2, col3 = st.columns(3) |
|
|
| if col1.button("๐ข BUY (Market)", key="testnet_buy", use_container_width=True): |
| try: |
| ticker = testnet.get_ticker(trade_symbol) |
| current_price = float(ticker['last']) |
| amount_base = trade_amount / current_price |
| order = testnet.place_market_order(symbol=trade_symbol, side='buy', amount=amount_base) |
| if order: |
| st.success(f"โ
BUY order placed: {amount_base:.6f} {trade_symbol.split('/')[0]} @ ${current_price:,.2f}") |
| st.rerun() |
| else: |
| st.error("โ Order failed") |
| except Exception as e: |
| st.error(f"โ Order failed: {e}") |
|
|
| if col2.button("๐ด SELL (Market)", key="testnet_sell", use_container_width=True): |
| try: |
| base_currency = trade_symbol.split('/')[0] |
| base_balance = balances.get(base_currency, {}).get('free', 0) |
| if float(base_balance) == 0: |
| st.warning(f"No {base_currency} to sell") |
| else: |
| order = testnet.place_market_order(symbol=trade_symbol, side='sell', amount=float(base_balance)) |
| if order: |
| st.success(f"โ
SELL order placed: {base_balance} {base_currency}") |
| st.rerun() |
| else: |
| st.error("โ Order failed") |
| except Exception as e: |
| st.error(f"โ Order failed: {e}") |
|
|
| if col3.button("๐ Open Orders", key="testnet_orders", use_container_width=True): |
| try: |
| open_orders = testnet.get_open_orders() |
| if open_orders: |
| st.write(open_orders) |
| else: |
| st.info("No open orders") |
| except Exception as e: |
| st.error(f"โ Failed to fetch orders: {e}") |
|
|
| st.markdown("---") |
| st.markdown("### โน๏ธ Testnet Information") |
| info_col1, info_col2 = st.columns(2) |
| with info_col1: |
| st.info(""" |
| **About Testnet:** |
| - Zero real money risk |
| - Test trading strategies |
| - Same API as production |
| - Perfect for learning |
| """) |
| with info_col2: |
| st.warning(""" |
| **Important Notes:** |
| - Using testnet.binance.vision |
| - Testnet funds reset periodically |
| - Use for testing only |
| - Get free testnet funds from Binance |
| """) |
|
|
| except Exception as e: |
| st.error(f"โ Error: {e}") |
| import traceback |
| st.code(traceback.format_exc()) |
|
|