From da7fbd1b49d2267a9c4ac8ab085089a74490970f Mon Sep 17 00:00:00 2001 From: Gemini CLI Date: Thu, 5 Mar 2026 11:04:30 +0100 Subject: [PATCH] feat: fix Bybit Unified Account support and enhance Docker logging for ping_pong_bot - Added accountType='UNIFIED' to wallet balance requests - Updated balance retrieval to support totalWalletBalance for UTA - Replaced rich.Live with standard logging for better Docker compatibility - Added PYTHONUNBUFFERED=1 to ensure real-time logs in containers - Updated docker-compose to point to NAS database (20.20.20.20) - Created GEMINI.md with comprehensive project context --- .geminiignore | 29 +++++++ GEMINI.md | 65 +++++++++++++++ docker/Dockerfile.bot | 3 + docker/docker-compose.yml | 21 ++--- src/strategies/ping_pong_bot.py | 138 ++++++++++---------------------- 5 files changed, 151 insertions(+), 105 deletions(-) create mode 100755 .geminiignore create mode 100644 GEMINI.md diff --git a/.geminiignore b/.geminiignore new file mode 100755 index 0000000..38d01eb --- /dev/null +++ b/.geminiignore @@ -0,0 +1,29 @@ +# Security - Protect your API keys and credentials +.env +.env.* +.git/ + +# Python artifacts +__pycache__/ +*.py[cod] +*$py.class + +# Logs - Prevents the AI from reading massive log files +logs/ +*.log + +# Database & Data - Prevents reading binary/huge data files +docker/data/ +*.dump +*.sqlite +*.csv + +# Environment & Dependencies +venv/ +.venv/ +node_modules/ + +# OS/IDE files +.vscode/ +.idea/ +.DS_Store \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..42c87e1 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,65 @@ +# Gemini Context: BTC Trading Dashboard + +This project is a Bitcoin trading platform and automated bot system. It features a FastAPI backend, a real-time data collector, a PostgreSQL (TimescaleDB) database, and an interactive HTML/JS dashboard for technical analysis and strategy visualization. + +## Project Overview + +- **Purpose**: Real-time BTC data collection, technical indicator computation, and trading strategy execution/backtesting. +- **Core Technologies**: + - **Backend**: Python 3.9+ with FastAPI. + - **Frontend**: Vanilla HTML/JS with `lightweight-charts`. + - **Database**: PostgreSQL with TimescaleDB extension for time-series optimization. + - **Infrastructure**: Docker & Docker Compose. +- **Architecture**: + - `data_collector`: Handles WebSocket data ingestion and custom timeframe generation. + - `api_server`: Serves the dashboard and REST API for candle/indicator data. + - `indicator_engine`: Computes SMA, EMA, and specialized HTS indicators. + - `strategies`: Contains trading logic (e.g., Ping Pong bot, HTS strategy). + +## Building and Running + +### Local Setup +1. **Environment**: + ```bash + python -m venv venv + source venv/bin/activate # venv\Scripts\activate on Windows + pip install -r requirements.txt + ``` +2. **Configuration**: Create a `.env` file based on the project's requirements (see `README.md`). +3. **Database Test**: `python test_db.py` +4. **Run API Server**: `uvicorn src.api.server:app --reload --host 0.0.0.0 --port 8000` + +### Docker Deployment +- **Commands**: + - `docker-compose up -d` (from the `docker/` directory or root depending on setup). +- **Services**: `timescaledb`, `data_collector`, `api_server`, `ping_pong_bot`. + +## Key Files and Directories + +- `src/api/server.py`: FastAPI entry point and REST endpoints. +- `src/data_collector/main.py`: Data collection service logic. +- `src/data_collector/indicator_engine.py`: Technical indicator calculations (stateless math). +- `src/api/dashboard/static/`: Frontend assets (HTML, CSS, JS). +- `src/strategies/`: Directory for trading strategy implementations. +- `HTS_STRATEGY.md`: Detailed documentation for the "Higher Timeframe Trend System" strategy. +- `AGENTS.md`: Specific coding guidelines and standards for AI agents. + +## Development Conventions + +### Python Standards +- **Style**: Follow PEP 8; use Type Hints consistently. +- **Documentation**: Use Google-style docstrings for all public functions and classes. +- **Asynchrony**: Use `async`/`await` for all database (via `asyncpg`) and network operations. +- **Validation**: Use Pydantic models for data validation and settings. + +### Frontend Standards +- **Tech**: Vanilla CSS (Avoid Tailwind unless requested) and Vanilla JS. +- **Location**: Static files reside in `src/api/dashboard/static/`. + +### AI Coding Guidelines (from `AGENTS.md`) +- **Organization**: Place new code in corresponding modules (`api`, `data_collector`, `strategies`). +- **Error Handling**: Use explicit exceptions; log errors with context; never suppress silently. +- **Security**: Protect credentials; use environment variables; validate all inputs. + +## Strategy: HTS (Higher Timeframe Trend System) +The project emphasizes the **HTS strategy**, which uses fast (33) and slow (144) RMA channels to identify trends. Key rules include price position relative to Red (Slow) and Aqua (Fast) channels, and a 1H Red Zone filter for long trades. Refer to `HTS_STRATEGY.md` for full logic. diff --git a/docker/Dockerfile.bot b/docker/Dockerfile.bot index b2f2f9c..aa38b87 100644 --- a/docker/Dockerfile.bot +++ b/docker/Dockerfile.bot @@ -13,6 +13,9 @@ COPY src/ ./src/ COPY config/ ./config/ COPY .env . +# Create logs directory +RUN mkdir -p /app/logs + # Set Python path ENV PYTHONPATH=/app diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2c95921..f00d61d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -39,7 +39,7 @@ services: container_name: btc_collector network_mode: host environment: - - DB_HOST=localhost + - DB_HOST=20.20.20.20 - DB_PORT=5433 - DB_NAME=btc_data - DB_USER=btc_bot @@ -49,9 +49,6 @@ services: - ../src:/app/src - /volume1/btc_bot/logs:/app/logs - ../config:/app/config:ro - depends_on: - timescaledb: - condition: service_healthy restart: unless-stopped deploy: resources: @@ -68,7 +65,7 @@ services: container_name: btc_api network_mode: host environment: - - DB_HOST=localhost + - DB_HOST=20.20.20.20 - DB_PORT=5433 - DB_NAME=btc_data - DB_USER=btc_bot @@ -77,8 +74,6 @@ services: - ../src:/app/src - /volume1/btc_bot/exports:/app/exports - ../config:/app/config:ro - depends_on: - - timescaledb restart: unless-stopped deploy: resources: @@ -89,13 +84,19 @@ services: build: context: .. dockerfile: docker/Dockerfile.bot - image: btc_ping_pong_bot + image: btc_bot container_name: btc_ping_pong_bot network_mode: host environment: - - API_KEY=${API_KEY} - - API_SECRET=${API_SECRET} + - DB_HOST=20.20.20.20 + - DB_PORT=5433 + - DB_NAME=btc_data + - DB_USER=btc_bot + - DB_PASSWORD=${DB_PASSWORD} + - API_KEY=${BYBIT_API_KEY} + - API_SECRET=${BYBIT_API_SECRET} - LOG_LEVEL=INFO + - PYTHONUNBUFFERED=1 volumes: - ../src:/app/src - /volume1/btc_bot/logs:/app/logs diff --git a/src/strategies/ping_pong_bot.py b/src/strategies/ping_pong_bot.py index ed43361..57724ed 100644 --- a/src/strategies/ping_pong_bot.py +++ b/src/strategies/ping_pong_bot.py @@ -10,12 +10,6 @@ import pandas as pd import numpy as np from datetime import datetime, timezone from dotenv import load_dotenv -from rich.console import Console -from rich.table import Table -from rich.live import Live -from rich.panel import Panel -from rich.layout import Layout -from rich import box # Try to import pybit, if not available, we'll suggest installing it try: @@ -26,17 +20,18 @@ except ImportError: # Load environment variables load_dotenv() +log_level = os.getenv("LOG_LEVEL", "INFO") # Setup Logging logging.basicConfig( - level=logging.INFO, + level=getattr(logging, log_level), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - filename='logs/ping_pong_bot.log' + handlers=[ + logging.StreamHandler() + ] ) logger = logging.getLogger("PingPongBot") -console = Console() - class PingPongBot: def __init__(self, config_path="config/ping_pong_config.yaml"): with open(config_path, 'r') as f: @@ -162,7 +157,6 @@ class PingPongBot: if pos_response['retCode'] == 0: positions = pos_response['result']['list'] - # Filter by side or just take the one with size > 0 active_pos = [p for p in positions if float(p['size']) > 0] if active_pos: self.position = active_pos[0] @@ -172,11 +166,23 @@ class PingPongBot: # Get Balance wallet_response = self.session.get_wallet_balance( category="linear", + accountType="UNIFIED", coin="USDT" ) if wallet_response['retCode'] == 0: - self.wallet_balance = float(wallet_response['result']['list'][0]['coin'][0]['walletBalance']) + result_list = wallet_response['result']['list'] + if result_list: + # Priority 1: totalWalletBalance (for UTA pooled funds) + self.wallet_balance = float(result_list[0].get('totalWalletBalance', 0)) + + # If totalWalletBalance is 0, check the specific coin + if self.wallet_balance == 0: + coin_info = result_list[0].get('coin', []) + if coin_info: + self.wallet_balance = float(coin_info[0].get('walletBalance', 0)) + else: + logger.error(f"Wallet API Error: {wallet_response['retMsg']}") except Exception as e: logger.error(f"Error updating account info: {e}") @@ -311,98 +317,40 @@ class PingPongBot: logger.error(f"Execution Error: {e}") self.status_msg = f"Exec Error: {str(e)}" - def create_dashboard(self, df): - """Create a Rich layout for status display""" - layout = Layout() - layout.split_column( - Layout(name="header", size=3), - Layout(name="main", ratio=1), - Layout(name="footer", size=3) - ) - - # Header - header_table = Table.grid(expand=True) - header_table.add_column(justify="left", ratio=1) - header_table.add_column(justify="right", ratio=1) - - runtime = str(datetime.now() - self.start_time).split('.')[0] - header_table.add_row( - f"[bold cyan]Ping-Pong Bot v1.0[/bold cyan] | Symbol: [yellow]{self.symbol}[/yellow] | TF: [yellow]{self.interval}m[/yellow]", - f"Runtime: [green]{runtime}[/green] | Time: {datetime.now().strftime('%H:%M:%S')}" - ) - layout["header"].update(Panel(header_table, style="white on blue")) - - # Main Content - main_table = Table(box=box.SIMPLE, expand=True) - main_table.add_column("Category", style="cyan") - main_table.add_column("Value", style="white") - - # Indicators - last = df.iloc[-1] - rsi_val = f"{last['rsi']:.2f}" - rsi_status = "[green]Oversold[/green]" if last['rsi'] < self.config['rsi']['oversold'] else ("[red]Overbought[/red]" if last['rsi'] > self.config['rsi']['overbought'] else "Neutral") - - main_table.add_row("Price", f"{last['close']:.2f}") - main_table.add_row("RSI", f"{rsi_val} ({rsi_status})") - main_table.add_row("Hurst Upper", f"{last['hurst_upper']:.2f}") - main_table.add_row("Hurst Lower", f"{last['hurst_lower']:.2f}") - - main_table.add_section() - - # Position Info - if self.position: - size = self.position['size'] - avg_p = self.position['avgPrice'] - upnl = float(self.position['unrealisedPnl']) - upnl_style = "green" if upnl >= 0 else "red" - main_table.add_row("Position Size", f"{size}") - main_table.add_row("Avg Entry", f"{avg_p}") - main_table.add_row("Unrealized PnL", f"[{upnl_style}]{upnl:.2f} USDT[/{upnl_style}]") - else: - main_table.add_row("Position", "None") - - main_table.add_row("Wallet Balance", f"{self.wallet_balance:.2f} USDT") - - layout["main"].update(Panel(main_table, title="Current Status", border_style="cyan")) - - # Footer - footer_text = f"Status: [bold white]{self.status_msg}[/bold white]" - if self.last_signal: - footer_text += f" | Last Action: [yellow]{self.last_signal}[/yellow]" - - layout["footer"].update(Panel(footer_text, border_style="yellow")) - - return layout - async def run(self): """Main loop""" - with Live(console=console, refresh_per_second=1) as live: - while True: - # 1. Update Account - await self.update_account_info() + logger.info(f"Bot started for {self.symbol} in {self.direction} mode") + while True: + # 1. Update Account + await self.update_account_info() + + # 2. Fetch Data & Calculate Indicators + df = await self.fetch_data() + + if df is not None: + # 3. Check for New Candle (for signal processing) + last_price = float(df.iloc[-1]['close']) - # 2. Fetch Data & Calculate Indicators - df = await self.fetch_data() + # 4. Strategy Logic + signal = self.check_signals(df) + if signal: + logger.info(f"Signal detected: {signal} @ {last_price}") + await self.execute_trade_logic(df, signal) - if df is not None: - # 3. Check for New Candle (for signal processing) - current_time = df.iloc[-1]['start_time'] - - # 4. Strategy Logic - signal = self.check_signals(df) - await self.execute_trade_logic(df, signal) - - # 5. Update UI - live.update(self.create_dashboard(df)) - - await asyncio.sleep(self.config.get('loop_interval_seconds', 5)) + # 5. Simple status log + if self.position: + logger.info(f"Price: {last_price:.2f} | Pos: {self.position['size']} @ {self.position['avgPrice']} | Wallet: {self.wallet_balance:.2f}") + else: + logger.info(f"Price: {last_price:.2f} | No Position | Wallet: {self.wallet_balance:.2f}") + + await asyncio.sleep(self.config.get('loop_interval_seconds', 5)) if __name__ == "__main__": try: bot = PingPongBot() asyncio.run(bot.run()) except KeyboardInterrupt: - console.print("\n[bold red]Bot Stopped by User[/bold red]") + print("\nBot Stopped by User") except Exception as e: - console.print(f"\n[bold red]Critical Error: {e}[/bold red]") + print(f"\nCritical Error: {e}") logger.exception("Critical Error in main loop")