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
This commit is contained in:
29
.geminiignore
Executable file
29
.geminiignore
Executable file
@ -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
|
||||
65
GEMINI.md
Normal file
65
GEMINI.md
Normal file
@ -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.
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
Reference in New Issue
Block a user