Spaces:
Sleeping
Sleeping
| 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.") | |