import streamlit as st import ccxt import numpy as np import pandas as pd import time from datetime import datetime from dotenv import load_dotenv import os load_dotenv() GATE_API_KEY = os.getenv("GATE_API_KEY") GATE_API_SECRET = os.getenv("GATE_API_SECRET") exchange = ccxt.gateio({ 'apiKey': GATE_API_KEY, 'secret': GATE_API_SECRET, 'enableRateLimit': True, 'options': {'defaultType': 'swap'} }) st.set_page_config(page_title="Dynamic MM Hedger", layout="wide") # === UI state === if "enabled" not in st.session_state: st.session_state.enabled = False if "pnl_log" not in st.session_state: st.session_state.pnl_log = [] if "ghost_orders" not in st.session_state: st.session_state.ghost_orders = {} # === Controls === st.sidebar.title("Controls") if st.sidebar.button("Start Bot"): st.session_state.enabled = True if st.sidebar.button("Stop Bot"): st.session_state.enabled = False st.sidebar.markdown(f"### Status: {'🟢 RUNNING' if st.session_state.enabled else '🔴 STOPPED'}") ORDER_USDT = st.sidebar.slider("Order Size (USDT)", 5, 50, 10) GRID_MULTIPLIER = st.sidebar.slider("Grid ATR Multiplier", 1.0, 5.0, 2.0, 0.5) PROFIT_TARGET = st.sidebar.slider("Profit Target %", 0.3, 3.0, 0.7) / 100 # === Utility === def get_top_pairs(limit=3): data = [] for s in exchange.load_markets(): if "/USDT" in s: try: ticker = exchange.fetch_ticker(s) orderbook = exchange.fetch_order_book(s) if not orderbook['asks'] or not orderbook['bids']: continue spread_pct = (orderbook['asks'][0][0] - orderbook['bids'][0][0]) / ticker['last'] * 100 ohlcv = exchange.fetch_ohlcv(s, '1h', limit=24) vols = [x[4] for x in ohlcv] close = [x[4] for x in ohlcv] score = spread_pct + np.mean(vols) / 100000 - np.std(close) data.append((s, score)) except: continue return sorted(data, key=lambda x: x[1], reverse=True)[:limit] def get_atr(pair): candles = exchange.fetch_ohlcv(pair, '1m', limit=100) highs = np.array([c[2] for c in candles]) lows = np.array([c[3] for c in candles]) return np.mean(np.abs(highs - lows)) def get_bal(asset): return float(exchange.fetch_balance()['total'].get(asset, 0)) def place(side, pair, price, size): try: return exchange.create_limit_order(pair, side, size, price) except Exception as e: st.warning(f"Order error: {e}") # === Trading Logic === def run_grid(pair): price = float(exchange.fetch_ticker(pair)['last']) atr = get_atr(pair) spacing = GRID_MULTIPLIER * atr size = ORDER_USDT / price if pair not in st.session_state.ghost_orders: st.session_state.ghost_orders[pair] = {'long': {}, 'short': {}} for side in ['long', 'short']: ledger = st.session_state.ghost_orders[pair][side] # update ghost grid if side == "long": level = round(price - spacing, 4) else: level = round(price + spacing, 4) ledger[level] = ledger.get(level, 0) + size # check for profit-taking wavg = sum([p*q for p, q in ledger.items()]) / sum(ledger.values()) if (side == "long" and price >= wavg * (1 + PROFIT_TARGET)) or \ (side == "short" and price <= wavg * (1 - PROFIT_TARGET)): order_side = "sell" if side == "long" else "buy" place(order_side, pair, price, sum(ledger.values())) pnl = (price - wavg) * sum(ledger.values()) if side == "long" else (wavg - price) * sum(ledger.values()) st.session_state.pnl_log.append({"Time": datetime.now(), "Pair": pair, "Side": side, "PnL": pnl}) ledger.clear() # === Execution Loop === if st.session_state.enabled: pairs = [x[0] for x in get_top_pairs()] st.write("Top Pairs:", pairs) for p in pairs: run_grid(p) df = pd.DataFrame(st.session_state.pnl_log) st.line_chart(df.set_index("Time")["PnL"].cumsum() if not df.empty else pd.Series()) time.sleep(3) else: st.write("Bot is stopped.")