Compare commits

..

No commits in common. "v0.4.2" and "main" have entirely different histories.
v0.4.2 ... main

75 changed files with 575 additions and 9403 deletions

56
.gitignore vendored
View File

@ -1,56 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Environment
.env
.env.local
.env.*.local
# Logs
*.log
logs/
# Temp files
*.bak
*.tmp
*.backup
*~
# OS
.DS_Store
Thumbs.db
# Bot-specific
state/
cache/
*.pickle

View File

@ -1,385 +0,0 @@
# Trading Bot V0.2 — System Architecture
**Version:** 0.2 (Production)
**Last Updated:** 2026-07-08
**Status:** 🟢 LIVE with Auto-Swap Feature
## Overview
Trading Bot V0.2 is an adaptive cryptocurrency trading system running on Binance with:
- **Adaptive Strategy Learning** (Win Rate Tracking)
- **Risk Management** (Daily Loss Limit, Stop Loss, Take Profit, Trailing Stop)
- **Auto-Swap Feature** (Convert free coins to USDT)
- **Real-time Dashboard** (FastAPI + Jinja2)
- **Telegram Integration** (3-hourly reports + alerts)
---
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ TRADING BOT V0.2 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Core Trading Engine (main_ml.py) │ │
│ │ • Adaptive strategy (5 levels: Emergency→Full Throttle)│ │
│ │ • Risk management (SL -1.8%, TP +2.8%, Daily -5%) │ │
│ │ • Position tracking & P&L calculation │ │
│ │ • Win rate analysis (hourly evaluation) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │ │
│ │ │ │
│ ┌────────▼──────────┐ ┌──────────────▼──────┐ │
│ │ Binance API │ │ Auto-Swap Module │ │
│ │ • Place orders │ │ • Convert free │ │
│ │ • Monitor fills │ │ coins → USDT │ │
│ │ • Get balances │ │ • Skip-list logic │ │
│ │ • Track trades │ │ • Telegram notify │ │
│ └───────────────────┘ └─────────────────────┘ │
│ │ │
│ ┌────────▼──────────────────────────────────────────────┐ │
│ │ Dashboard (web_dashboard.py) │ │
│ │ • Real-time portfolio value & P&L │ │
│ │ • Holdings summary (locked/free breakdown) │ │
│ │ • Active positions & open orders │ │
│ │ • Strategy status & performance metrics │ │
│ │ FastAPI (port 7000) + Jinja2 templates │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────▼──────────────────────────────────────────────┐ │
│ │ Telegram Integration │ │
│ │ • 3-hour performance reports │ │
│ │ • Trade execution alerts │ │
│ │ • Error notifications │ │
│ │ • Swap completion confirmations │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Core Components
### 1. Trading Engine (src/main_ml.py)
**Main Class:** `TradingBot`
#### Strategy Parameters
```python
PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
STOP_LOSS_PERCENT = 1.8 # -1.8%
TAKE_PROFIT_PERCENT = 2.8 # +2.8%
DAILY_LOSS_LIMIT = -5 # Pause after -5%
MAX_POSITIONS = 1 # Single position
INVESTMENT_PERCENT = 50-55% # Per trade
```
#### Adaptive Learning (Option 2: Win Rate Tracking)
```
Win Rate < 45% Emergency Level (minimal trading)
45-50% → Conservative Level
50-60% → Standard Level (base)
60-70% → Aggressive Level
> 70% → Full Throttle Level
Adjustments per level:
• Signal threshold (5.0% → 10.0%)
• Investment (50% → 55%)
• Take profit (1.5% → 3.5%)
• Stop loss (1.0% → 2.2%)
• Max trades/day (5 → 25)
```
#### Key Methods
| Method | Purpose |
|--------|---------|
| `run_cycle()` | Main trading loop (every ~10s) |
| `evaluate_signal()` | Generate trading signal (7.5% base prob) |
| `place_trade()` | Execute buy order with SL/TP |
| `check_positions()` | Monitor open trades, close on SL/TP |
| `calculate_pnl()` | Compute portfolio P&L (live + closed) |
| `update_adaptive_strategy()` | Hourly win rate evaluation |
| `swap_coins_to_usdt()` | **NEW:** Convert free coins to USDT |
| `get_usdt_balance()` | **NEW:** Query current USDT balance |
| `send_performance_report()` | 3-hour Telegram summary |
#### Risk Management
**Daily Loss Limit:**
- If P&L <= -5%, bot pauses trading
- Resets at UTC 00:00
- Prevents catastrophic drawdowns
**Stop Loss & Take Profit:**
- SL -1.8% per trade (position auto-closed)
- TP +2.8% per trade (position auto-closed)
- OR Trailing Stop: +1.5% entry, 0.6% trail
**Consecutive Loss Cooldown:**
- After 3 consecutive losses: 30min pause
- Prevents emotional spiraling
**Position Limits:**
- Max 1 open position at a time
- Prevents over-leverage
---
### 2. Auto-Swap Feature (NEW - 2026-07-08)
**Function:** `swap_coins_to_usdt()`
**Lines:** 387-461 in main_ml.py
**Purpose:** Automatically convert all free (unlocked) coins to USDT
**Logic:**
```python
1. Get account balance via Binance API
2. For each coin:
- Skip if: USDT, LDBTTC, LDDOGE, USDC, locked, dust (<0.00001)
- Get current price (COINUSDT pair)
- Round quantity to Binance step size
- Execute MARKET SELL
- Calculate USDT received
3. Send Telegram notification with results
4. Return total USDT acquired
```
**Skip-List (Never Swap):**
- USDT (target currency)
- LDBTTC (fake/scam token)
- LDDOGE (shitcoin)
- USDC (too small)
- Any coin marked as locked (in active trades)
**Execution Result (2026-07-08 23:28 UTC):**
| Coin | Qty | USDT | Status |
|------|-----|------|--------|
| BNB | 0.019 | $10.75 | ✅ |
| XRP | 28.7 | $31.20 | ✅ |
| SOL | 0.294 | $22.67 | ✅ |
| **TOTAL** | — | **+$64.61** | ✅ |
---
### 3. Dashboard (src/web_dashboard.py)
**Framework:** FastAPI + Jinja2
**Port:** 7000
**Refresh:** 10 seconds (live updates)
**Endpoints:**
| Endpoint | Purpose |
|----------|---------|
| `GET /` | Render main dashboard HTML |
| `GET /api/state` | JSON: portfolio, P&L, holdings, orders |
**Dashboard Sections:**
1. **Header:** Bot status (🟢 RUNNING or ⏸️ PAUSED)
2. **Portfolio Kachel:** USDT value, P&L %, color-coded
3. **Holdings (Collapsible):** All coins + USD values
4. **Live Prices (Collapsible):** Real-time BTCUSDT, ETHUSDT, etc.
5. **Active Positions:** Current open trades (entry price, SL, TP)
**Design:**
- Colors: Grayscale (#1e1e1e bg, #d0d0d0 text)
- Accent: Green (#00ff88) ONLY for Portfolio & USDT values
- Responsive, collapsible sections (both collapsed on load)
---
## Data Flow
### Trading Cycle (run_cycle)
```
┌─────────────────────────────────────────────────────────┐
│ 1. Get current portfolio balance & P&L │
│ 2. Check if -5% daily limit reached → PAUSE if true │
│ 3. Check all open positions for SL/TP exit │
│ 4. Evaluate signal (7.5% base probability) │
│ 5. If signal + capital > $5: Place trade │
│ 6. Update adaptive strategy (hourly) │
│ 7. Send Telegram report (if 3h elapsed) │
│ 8. Repeat every ~10s │
└─────────────────────────────────────────────────────────┘
```
### P&L Calculation
```
Portfolio Value = Balance(USDT) + Sum(Coin Value in USDT)
P&L USDT = Portfolio Value - Initial Capital ($137.79)
P&L % = (P&L USDT / Initial Capital) × 100
Status = 🟢 GREEN if P&L > 0, 🔴 RED if P&L < 0
```
---
## Configuration
### Environment (.env)
```
BINANCE_API_KEY_LIVE=...
BINANCE_API_SECRET_LIVE=...
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=7646180954
```
### In Code (main_ml.py Line ~30-60)
```python
PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
SIGNAL_THRESHOLD = 7.5 # 7.5% signal probability
INVESTMENT_PERCENT = 50 # 50% of capital per trade
STOP_LOSS_PERCENT = 1.8 # -1.8% SL
TAKE_PROFIT_PERCENT = 2.8 # +2.8% TP
DAILY_LOSS_LIMIT = -5 # -5% pause threshold
MAX_OPEN_POSITIONS = 1
MAX_CONSECUTIVE_LOSSES = 3
CONSECUTIVE_LOSS_COOLDOWN = 30 * 60 # 30 minutes
```
---
## Deployment
### SystemD Service
```
Service: trading-bot.service
File: /etc/systemd/system/trading-bot.service
User: root
WorkDir: /home/marc/bot-deploy
Exec: python3 /home/marc/bot-deploy/src/main_ml.py
AutoStart: yes
```
### Start/Stop
```bash
sudo systemctl start trading-bot.service
sudo systemctl stop trading-bot.service
sudo systemctl restart trading-bot.service
sudo systemctl status trading-bot.service
```
### Logs
```bash
journalctl -u trading-bot.service -f # Live tail
journalctl -u trading-bot.service -n 50 # Last 50 lines
journalctl -u trading-bot.service --since "1 hour ago"
```
---
## Git Repository
**URL:** ssh://git@172.16.1.168:222/marc/BrainDock.git
**Branch:** master
**Latest:** Commit ae15976 (Auto-Swap Feature)
**Structure:**
```
BrainDock/
├── src/
│ ├── main_ml.py # Core trading engine (V0.2)
│ ├── web_dashboard.py # FastAPI dashboard
│ └── __init__.py
├── docs/
│ └── ARCHITECTURE.md # This file
├── README.md # User-facing features
├── requirements.txt # Dependencies
├── .gitignore # Excludes: __pycache__, *.log, .env, venv/
└── ARCHITECTURE.md # System design (this repo)
```
**Auto-Sync Cron:**
```
# Every 5 minutes, auto-commit changes from /home/marc/bot-deploy/src → BrainDock/src
*/5 * * * * git -C /home/marc/bot-versions/BrainDock add src/ && git commit -m "Auto-sync: $(date)" && git push origin master 2>/dev/null || true
```
---
## Monitoring & Alerts
### Telegram Reports
- **Frequency:** Every 3 hours
- **Content:**
- Portfolio value + P&L
- Trades executed today (wins/losses)
- Current strategy level
- Bot status (running/paused)
### Manual Commands (Python)
```python
bot = TradingBot()
# Get current P&L
report = bot.get_performance_report()
print(report['portfolio'], report['pnl_usdt'], report['pnl_pct'])
# Swap all free coins to USDT
result = bot.swap_coins_to_usdt()
print(f"Converted: ${result['total_usdt_acquired']:.2f}")
# Get USDT balance
usdt = bot.get_usdt_balance()
```
---
## Known Limitations & Future Work
### Current Limitations
- **Signal:** Still random (7.5% base probability), not ML-based
- **Pairs:** Fixed list (5 pairs), not dynamic
- **Levels:** 5 strategy levels (can expand)
- **Fees:** No explicit fee tracking (implicit in P&L)
### Future Enhancements
- [ ] Machine Learning signal (instead of random)
- [ ] Dynamic pair selection (trending symbols only)
- [ ] Advanced technical indicators (RSI, MACD, etc.)
- [ ] Portfolio rebalancing scheduler
- [ ] Webhook API for external signals
- [ ] Database logging (trade history, performance metrics)
- [ ] Mobile alerts (SMS, Push notifications)
---
## Troubleshooting
### Bot Not Trading (1-hour+ no activity)
1. Check daily P&L: `curl http://localhost:7000/api/state | jq '.pnl_pct'`
2. If P&L < -5%, bot is paused (wait until UTC 00:00)
3. Check logs: `journalctl -u trading-bot.service -n 50`
### Dashboard Shows $0.00
- Rare bug (fixed 2026-07-08)
- Restart: `sudo systemctl restart trading-bot.service`
### High Number of Rejected Orders
- **Cause:** USDT balance too low (< $5 per order)
- **Fix:** Use `bot.swap_coins_to_usdt()` to convert free coins
### Telegram Notifications Not Arriving
- Check API keys in .env
- Verify Telegram chat ID: `curl "https://api.telegram.org/bot{TOKEN}/getMe"`
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| V0.2 | 2026-07-08 | ✅ Live: Adaptive Learning + Auto-Swap Feature |
| V5 | (Previous) | Archived (manual strategy, no learning) |
---
**Maintained by:** Hermes Agent
**Last Review:** 2026-07-08 23:35 UTC

View File

@ -1,48 +0,0 @@
# Trading Bot v0.4 — Dynamic Position Sizing (GEPLANT)
**Status:** 🟡 PLANNED (Implementation: 2026-07-18)
**Current Production:** v0.3 (stable)
**Rollback Path:**
## Problem v0.3
- **Hardcoded MAX_TRADE = 0** (nicht skalierbar)
- Portfolio 50 → Max 0 (13% — zu aggressiv)
- Portfolio 00 → Max 0 (4% — nicht genutzt)
- Portfolio 0k → Max 0 (0.2% — ineffizient)
## Lösung v0.4
**Dynamisches Position Sizing: **
✅ **Auto-skaliert bei Gewinn oder Zukauf**
## Implementation Details
### Neue Konstanten
### Berechnung pro Zyklus
### Logging
## Deployment Plan (2026-07-18)
1. Schreibe (clean, keine sed-Patches)
2. Test:
3. Deploy:
4. Restart:
5. Verify: Dashboard zeigt neue Max Trade
6. Git commit:
## Rollback (falls nötig)
---
**Dokumentation:** See (Obsidian)

View File

@ -1,162 +0,0 @@
# 📊 Trading Bot Dashboard v0.32 — Update Log
**Version:** 0.32
**Release Date:** 2026-07-15
**Status:** 🟢 PRODUCTION
---
## ✨ Features v0.32
### Tab 1: Portfolio (Live)
- **Portfolio Value** — Real-time Binance account balance in USD
- **Total P&L** — Absolute profit/loss in USD + percentage
- **Free USDT** — Available balance for trading
- **Active Trades** — Count of open positions (BTC, ETH, BNB, XRP, SOL)
- **Holdings Breakdown** — Each asset with 4 decimal precision (BTC 0.0003, ETH 0.0114, etc.)
### Tab 2: Analytics (P&L Charts)
- **Timeframe Selection:** 1 Day, 1 Week, 1 Month
- **Chart Visualization:** Animated line chart (Chart.js)
- Green line = Profit
- Red line = Loss
- Real-time data from Binance API
- **Statistics (4-column responsive grid):**
- **Current:** Latest P&L % + USD value
- **Min:** Lowest P&L in period
- **Max:** Highest P&L in period
- **Avg:** Average P&L across period
- *Each stat shows both % and USD value*
### Design
- **Dark Theme:** Professional #1e1e1e background
- **Green Accents:** #00ff88 for key metrics
- **Responsive:** Mobile-optimized (collapsible Holdings, 2-column stat grid on small screens)
- **Glassmorphism:** Modern card design with blur effects
- **Smooth Animations:** Tab transitions, chart rendering
---
## 🔧 Improvements (Latest)
| Issue | Solution | Status |
|-------|----------|--------|
| Holdings always visible | Added collapsible toggle (default: closed) | ✅ |
| Too many decimals (BTC 0.00030000) | Reduced to 4 decimals (0.0003) | ✅ |
| Analytics only showed % | Added USD values ($+1.20, -bash.65) | ✅ |
| Stats not mobile-friendly | 2-column grid on mobile (max-width: 768px) | ✅ |
---
## 📱 Mobile Optimization (New)
**Breakpoint:** 768px width (tablets & phones)
- Single-column portfolio cards
- 2-column analytics stats (instead of 4)
- Smaller fonts (preserved readability)
- Compact padding (10px header, 15px cards)
- Flex timeframe buttons with wrapping
---
## 🛠️ Technical Details
### API Endpoints
### Database
- **SQLite:**
- **Table:** (ts, pv, pu, pp, uf, ap)
- **Retention:** Unlimited (continuous tracking)
### Data Collection
- P&L snapshots stored every bot cycle (60 seconds)
- Portfolio value calculated from Binance
- Live prices from Binance
---
## 🎨 UI Components
### Holdings (Collapsible)
### Analytics Stats (Responsive)
**Desktop (4 columns):**
| Current | Min | Max | Avg |
| +1.20 % | -2.50 % | +3.80 % | +0.95 % |
| +.65 | -.44 | +.22 | +.31 |
**Mobile (2 columns):**
| Current | Min |
| +1.20 % | -2.50 % |
| +.65 | -.44 |
| Max | Avg |
| +3.80 % | +0.95 % |
| +.22 | +.31 |
---
## 🔄 Browser Compatibility
✅ Chrome/Chromium (latest)
✅ Firefox (latest)
✅ Safari (latest)
✅ Mobile browsers (iOS Safari, Chrome Mobile)
---
## 📊 Example Data (Live)
---
## 🚀 Deployment
**File:**
**Port:** 7000
**Service:**
**URL:**
**Restart:**
---
## 🔄 Rollback
**To v0.3:**
**Git Tags:**
- — Previous version
- — Current production
---
## 📝 Changelog
**v0.32 (2026-07-15)**
- ✅ Added two-tab interface (Portfolio + Analytics)
- ✅ P&L Charts with 1d/7w/1m timeframes
- ✅ Collapsible Holdings (default: closed)
- ✅ 4-decimal precision for coin amounts
- ✅ USD values in Analytics stats
- ✅ Mobile-optimized responsive design
- ✅ Live Binance API integration
- ✅ SQLite P&L history tracking
- ✅ Chart.js animated visualizations
- ✅ Dark theme with green accents
---
**Active Commits:**
- — Update: Collapsible Holdings, USD values, Mobile stats (2026-07-15 11:00 UTC)
- — Release: v0.32 Dashboard v0.32 P&L Charts (2026-07-15 10:55 UTC)
---
**Repository:** https://git.bizmark.cloud/marc/BrainDock
**Branch:** master (v0.32)
**Maintained by:** Hermes Agent

View File

@ -1,84 +0,0 @@
# 🤖 Trading Bot — Version 0.3 (PRODUCTION)
**Version:** 0.3 | **Status:** 🟢 LIVE | **Updated:** 2026-07-14
## 📊 Current Performance (Live)
| Metric | Value | Status |
|--------|-------|--------|
| **Portfolio** | $107.37 | 🟢 +0.49% |
| **Trades** | 5 live | ✅ Balanced |
| **Free USDT** | $18.75 | Active |
| **Win Rate** | +2.06%-2.20% | ✅ Consistent |
| **Bot** | RUNNING | ✅ 24/7 |
## 🎯 Strategy v0.3
**Algorithm:** Local Minimum Detection
- Scan 30min price history
- Detect support levels
- TP: +1.5% | SL: -0.8%
- Max 1 open trade
**Active Pairs:** BTC, ETH, BNB, XRP, SOL
**Risk:** 50% capital/trade, no leverage, -5% daily pause
## 📁 Files (v0.3 ONLY)
- main_ml.py ✅ (production engine)
- web_dashboard.py (live UI)
- Integrations (Telegram, Obsidian, Dashboard)
**Removed (cleanup 2026-07-14):**
- ❌ Backups (main_ml_BACKUP*, v2, v4_backup, v6)
- ❌ DCA strategy (deprecated)
- ❌ Old monitoring tools
## 🚀 Quick Start
**Status:**
```bash
systemctl status trading-bot.service
journalctl -u trading-bot.service -f
```
**Dashboard:** https://bot.bizmark.cloud (Port 7000)
**Configure:** Edit `src/main_ml.py`
```python
TAKE_PROFIT_PERCENT = 1.5 # 1.0-2.5%
STOP_LOSS_PERCENT = 0.8 # 0.5-1.5%
INVESTMENT_PERCENT = 50 # 10-55%
CYCLE_INTERVAL = 60 # 30-120s
```
After changes: `git commit``git push``systemctl restart trading-bot.service`
## 📱 Telegram Reports (3h)
Auto-delivery: 00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00 UTC
- Marc: 7646180954
- Brother: 8518722579
- Via: @bizMarkTrading_Bot
## 🔄 Versioning
**v0.3 (NOW):** Local Minimum + 3h reports + clean repo
**Downgrade v0.3 → v0.2:**
```bash
git log --oneline
git checkout <v0.2-hash>
systemctl restart trading-bot.service
```
## 📞 Help
- Logs: `journalctl -u trading-bot.service -f`
- Status: `curl http://172.16.1.168:7000/api/state | jq '.'`
- Report: `/home/marc/.pyenv/versions/3.10.16/bin/python3 /home/marc/bot-deploy/send_3h_report.py`
---
**Repo:** https://git.bizmark.cloud/marc/BrainDock | **Branch:** master (v0.3) | **Status:** 🟢 PRODUCTION READY

39
compose-hermes.yaml Normal file
View File

@ -0,0 +1,39 @@
services:
obsidian:
image: lscr.io/linuxserver/obsidian:latest
container_name: obsidian
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Zurich
ports:
- "3002:3000"
- "3003:3001"
- "27123:27123"
- "27124:27124"
- "8642:8642" # Hermes Gateway API
- "9119:9119" # Hermes Dashboard
volumes:
- /opt/obsidian:/config
shm_size: "1gb"
restart: unless-stopped
hermes-agent:
image: nousresearch/hermes-agent:latest
container_name: hermes-agent
restart: unless-stopped
command: gateway run
depends_on:
- obsidian
network_mode: "service:obsidian"
volumes:
- /opt/hermes-agent/data:/opt/data
- /opt/obsidian/config/Vault/Test:/opt/data/vault:rw
- ~/.ssh/hermes_agent_key:/root/.ssh/id_ed25519:ro
- /var/run/docker.sock:/var/run/docker.sock
environment:
- HERMES_DASHBOARD=1
- HERMES_DASHBOARD_HOST=0.0.0.0
- OLLAMA_BASE_URL=http://172.16.1.168:11434/v1
- OBSIDIAN_API_URL=http://127.0.0.1:27123
- OBSIDIAN_API_KEY=d951c952266b7571ee3fd1fe08dcbbfc3b6f6a5783d7b56ec5cbc21eeaff469c

39
compose-vLLM.yaml Normal file
View File

@ -0,0 +1,39 @@
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm
restart: unless-stopped
ipc: host
ports:
- "8000:8000"
volumes:
- /opt/vLLM/models:/root/.cache/huggingface
- /opt/vLLM/config:/vllm-workspace/config
environment:
- HUGGING_FACE_HUB_TOKEN=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
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
- VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
command:
- "--model"
- "Qwen/Qwen2.5-7B-Instruct-AWQ"
- "--host"
- "0.0.0.0"
- "--port"
- "8000"
- "--gpu-memory-utilization"
- "0.72"
- "--max-model-len"
- "65536"
- "--hf-overrides"
- '{"rope_scaling": {"rope_type": "yarn", "factor": 2.0, "original_max_position_embeddings": 32768}}'
- "--enable-auto-tool-choice"
- "--tool-call-parser"
- "hermes"

25
compose_ai-server.yaml Normal file
View File

@ -0,0 +1,25 @@
services:
codeproject-ai:
container_name: codeproject-ai
image: codeproject/ai-server:gpu
restart: unless-stopped
runtime: nvidia
ports:
- "32168:32168"
environment:
TZ: Europe/Zurich
volumes:
# Konfiguration
- /opt/ai-server/config:/etc/codeproject/ai
# Modelle / heruntergeladene AI Module
- /opt/ai-server/modules:/app/modules
# Persistente Daten / Logs / Temp
- /opt/ai-server/data:/app/data
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities:
- gpu

16
compose_comfyui.yaml Normal file
View File

@ -0,0 +1,16 @@
services:
comfyui:
image: jimlee2048/comfyui-docker:latest
container_name: comfyui-docker
ports:
- "8188:8188"
volumes:
- /opt/ComfyUI/models:/workspace/ComfyUI/models
- /opt/ComfyUI/output:/workspace/ComfyUI/output
- /opt/ComfyUI/input:/workspace/ComfyUI/input
- /opt/ComfyUI/custom_nodes:/workspace/ComfyUI/custom_nodes
environment:
- PUID=1000
- PGID=1000
restart: unless-stopped
runtime: nvidia

40
compose_frigate.yaml Normal file
View File

@ -0,0 +1,40 @@
version: "3.9"
services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable
restart: unless-stopped
stop_grace_period: 30s
shm_size: "4gb"
gpus: all
security_opt:
- no-new-privileges:true
environment:
TZ: Europe/Zurich
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,video,utility
LIBVA_DRIVER_NAME: nvidia
devices:
- /dev/bus/usb:/dev/bus/usb
- /dev/dri:/dev/dri
tmpfs:
- /tmp/cache:size=4G
volumes:
- /etc/localtime:/etc/localtime:ro
- /opt/frigate/config:/config
- /opt/frigate/media:/media/frigate
ports:
- "5000:5000"
- "1935:1935"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/version"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"

40
compose_monitor.yaml Normal file
View File

@ -0,0 +1,40 @@
version: "3.8"
services:
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
ports:
- "8095:8090" # Webinterface: http://<host>:8095
environment:
- PORT=8090
- LANG=de
- HOST_PROC=/host/proc
- HOST_SYS=/host/sys
- HOST_ETC=/host/etc
volumes:
- /opt/beszel/data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /etc:/host/etc:ro
privileged: true
deploy:
resources:
limits:
memory: 256M
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/beszel/beszel_agent_data:/var/lib/beszel-agent
environment:
- LISTEN=45876
- KEY=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBqp3wriOT+yNVBtx5FAN8t8DT6DMNnhtOktRYyRmUkE
- TOKEN=a49-f40461f6d18-7a3-f84d92692e8
- HUB_URL=http://172.16.1.168:8095

29
compose_ollama.yaml Normal file
View File

@ -0,0 +1,29 @@
version: '3.8'
services:
ollama:
image: ollama/ollama
container_name: ollama
restart: unless-stopped
volumes:
- /opt/ollama:/root/.ollama
ports:
- "11434:11434"
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
environment:
- OLLAMA_API_BASE_URL=http://ollama:11434
- WEBUI_SECRET_KEY=Bernstrasse175c
ports:
- "8080:8080"
volumes:
- /opt/open-webui/config:/app/backend/data
depends_on:
- ollama
volumes:
ollama_data:
openwebui_data:

30
compose_tugtainer.yaml Normal file
View File

@ -0,0 +1,30 @@
version: "3.9"
services:
tugtainer:
image: quenary/tugtainer:latest
container_name: tugtainer
restart: unless-stopped
ports:
- "9412:80"
environment:
TZ: Europe/Zurich
AGENT_SECRET: "A6sW9mP2vQk7Lx4Nf8Rb1HtY5Zj3Ec0DuVpGmK9XnJq7"
# SSRF-Protection für alle privaten Netzwerke
AGENT_ALLOW_NETWORKS: "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
AGENT_ALLOW_ENDPOINTS: "http://tugtainer-agent:8001,http://172.16.1.205:9413,http://172.16.1.8:9413,http://172.16.1.165:9413"
depends_on:
- tugtainer-agent
volumes:
- /opt/tugtainer/data:/tugtainer
tugtainer-agent:
image: ghcr.io/quenary/tugtainer-agent:latest
container_name: tugtainer-agent
restart: unless-stopped
environment:
TZ: Europe/Zurich
AGENT_SECRET: "A6sW9mP2vQk7Lx4Nf8Rb1HtY5Zj3Ec0DuVpGmK9XnJq7"
DOCKER_HOST: unix:///var/run/docker.sock
AGENT_ALLOW_NETWORKS: "172.16.0.0/12,192.168.0.0/16,10.0.0.0/8"
volumes:
- /var/run/docker.sock:/var/run/docker.sock

View File

@ -1,24 +0,0 @@
# Trading Bot V5 - Strategieanalyse
**Generiert:** 2026-07-04 22:45 UTC
**Status:** 🟢 Live Analysis
## Zusammenfassung
### Aktuelles Modell
- **Strategy:** Zufällige 5% Signal + Market Buy + +1% TP
- **Position Sizing:** 25% des USDT pro Trade
- **Risk Management:** KEINER (kein Stop Loss!)
- **Expected Win Rate:** 45% (unter Break Even)
- **Prognose:** 70% Wah...[truncated]
git add -A
git commit -m 'Add Trading Strategy PDF analysis + recommendations for optimization'
git push
echo ''
echo '✅ COMMITTED & PUSHED'
git log --oneline -2

View File

@ -1,99 +0,0 @@
%PDF-1.4
%“Œ‹ž ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R /F3 4 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font
>>
endobj
5 0 obj
<<
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 9 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
7 0 obj
<<
/PageMode /UseNone /Pages 9 0 R /Type /Catalog
>>
endobj
8 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260704224531+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260704224531+02'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
9 0 obj
<<
/Count 2 /Kids [ 5 0 R 6 0 R ] /Type /Pages
>>
endobj
10 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1766
>>
stream
GatU3>?BQ=&:Vs/Qj7q4g0M(OAGNNLUeN!9P*!1q:Cf**.)CmQ;q\$YJ,PCTaj#!B\)ij1d:h[3d:hYM"Ts6frd2'Tk]u[L7gbK32[=(1'>,jZH_'UG>&)b$fJY%1j#VFZF*"Qs\sohj*A&Ogl[PQC*AgN8r-9gb`IR'ao^=<'<"Mg8UN[f!Qga<bUUT00Wu^JW2`^ah(nO*5jM@t>Q+V<[\o/%Q\>d<@57:OlE%0+3j5cf0WU`uu#it5QjG3%h_YfGKV?6RV8ejo74f,tEO=H/('s&`%Q:m(fS5IJ`DZ(Z5PX,7;cE]9OQ8$Qa.poD_ThppbLSIr0n91uIlIlZVi=ZO)m!us_hfT2^W,&*h_.S$[R+2LJ'dnsqD%T9EG")t3Psr.qSS\u,JoGjgh.rFY@(?rhN[!L1@A]u&^IXXX-rM)G6/o,9/IP7@l]5P@cJpU7>HBWI#[/[LR$?7)=]7;u0G.$^31A/>(5&O_#FVr$`loC'3ZslVCdB$fMMoL.o@'ec)E3Jo79'tJ0=.-\[%Hh?>[`KUf[FF%$3,M@&5h<J_<9l![sqBMYU])XQQ]%DWNd$RHST*3<,FRa`]C)p\?)L]g.63d8[/UQ.4tuWFUE'`ZS[#-Is]$FD/W3hnQTpm&NF!lR1)Fh(Ol!!_^)3B`j'\po%hp,kK'p8VrG^:J,'OS%gf9\=C<L[@(Du'+HHFb\Ebbpc*0^+Af(^[$/uDaAP%-e/M#3r,i!MG9]\#sZk7[k0>f?OH^bu%BX]V+r?t\3](p,K/S,:&C^p+oJ-&Nqq*FX(PB8+GZu1O?Y%Oa)B"GMHPt^--oRdBE.P>%),G:Br_3F+9jlomdo,r`OkSoK,cdI&la[p**c&n"a5UCPG4n$&gnL%Yr7Br5?Y)5YdfIIa;9ZDD+AG+Z+JL=h#=:AWo\1"rkihLdd)FQBolQ?O8fa:466)qMm'3kr9hI&0&]0^;H+S):qCc4.Fj!V?oPD3a(qh&=,Jd/mXBcMhBfZ^qT<*Sb<f&suBhg8+g5hS`LE>%L#Q;RN-j4I3$-tDt5!LgJ-"/0^,f[%l!f;MI&l;qU9p1BeG>BFk5c-8DA;Q;*2WHXQ/>LXWL-RuP%;8QE=1YFN$Cn$f.UL+ra9bS.Xe9-oH'cr7lDmo)f":^]d"u3(@(e+mNgEH:1l3[H(&]8J7Ai`P"@iDsonD%Ih_)e(]?bXlsBM2'PE5mi;[#R`Q7eai0KXF9M@4daO_<P';U22L'TU)%6=MnNaCLT#\>E3/If5_.D27'.YEO7X+(eWKONhJ$^&M;Fnp;BiQlq7"?+F%Y7L:,N['l-\,]03\7<=io3MYQ*`9h1l1"T+<0$Pq(Yf.*F*jtH6Sh4.MWZdJ)%AsH""RFP?=g2B%@DT2%p6G_Z%BoRo0dM`Y>V)9;p\?!9qD@%OpCf+"Sa_&B9o$qo=M;j'bXJ?<TN="H"CD]/("m.1ENCdLj,f)80M_B7o7NQEECUSMQY19NIp+,Pp2ntjk]YX7=73BOALR2?IE+!Hth)gMmn\@Ea;?d^Q)>)rKQ-m?4:/VlX^>n"7E%;Y95GWr%,ECN"Kc9'SD0PVLl3!Kd<TCW@6QmbWCe'!a/CJt^>l%5^lA,B;]q<!tArj\BoMRTo(Q;NaP%pST;a"Zd56@6dp<Z@LgDsV=O3#Kk=V.,>,uODK)fOYms/=9Tq@/)1j&.)YoO<dD\HmE>UrHT1j=HQ,E9i9^6Qj;Gq3a1O73_G&,)eO&@134!Q(;DM&Vf=U64PF^FT)A",q+Z~>endstream
endobj
11 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1266
>>
stream
GatU1?#S^`&:O#NQkuRjDP;_/I-KogL!g_gZn+ULBpBJN9^p$+=keM9&,3QjEKV)0g?qSfZkYRoS2iB<Na8UDj(A>S#s^.so.(X4#)#WgrL+8U#R980cP*e?fkTQN>AL]0D*3A&ls_R9ljp&i"=(F[5m%B+%+kR3b6#+h3%(Z@+PA=-MGlh"DIZecW(KKP`@6K?6MQn$r.`N:fYDDG^qRX!fZ6XMp*dt^>J,:UW#1l6@O^3oj?WU5`A2h3%`@8,IfkfhP=qk`Y[kXc6A<5iWJF<]`C[NuBsVs;)B8p&Ak$ZG,di@A'@5nunle3i4?i0uUZti%'o"1T#c!u!UY))KgToIkEpP7(lglR]&[d-<0hdn.NgX!GD5bh>G26>0$&tTT3J28P?6qYg"*eC-9>^I&T)AqaA)<&<*haKO@"L7>9$S]Z/KlD05-A7Qqiu2\gK+g:1AO["-;lY\,@JN_R>=.jq$P3sUj*FYFO,Ud9S&!/dCR\_7C.JjT\o23rNLbb%^3,u6NM)4!V^WKqQS5\4)JT7+<8+lKJW4!-M/Ng'$apJ.O6c=2@LlrU-o;\O"*lKo)T:JEa(,_cA&iA9=`'\rb9pADG/NKUe>b5IqO*KY7?]Xq%$H<]K0kC(1hm@^Vj;c'.*'t4B$R5W@'^H9^mIkL]r6%`LQ5lVIl7eKJ3R2Eh2JNh:km0-3G<jY`PCR9\G#L4^k>^_YtF5p<=K!mRE!-[`U#YPT*X4=[JI4,d[sp,UeCp@YuL[!B46LU$s;QpC3&.<;oi@UPmmmX6FSX13lq8m2CWbkJd-N)uANOA(\mcBPjXO[reOo#?uSRiJMV:o9$AV<YH;P#8XaG@HgfLZ!k`O9ip.36=E0[%n+kM$m'CBU!KR4q4.41fMNgYKF9\F)2P7hEk+<^Q]"mEP2TLqE5'+1%?]?!V`*>o]\;l&/&0R/UPf/l2GiTaYspO+"VU\`&^TonF<A#as64si>SNKu?arVO'_O,C>YuTeIl9#7*n5oDHo/X[ah><a-s;2iD.sOdNuL,=fWjmuVg.WQE.&<.^E3tLdqk!Z.Lk>rY<:@fh,_2jM!#SgoRW=!0f)(%Y>g)(;"rB;o/lWSW!#DoM-*<#1MrMNYC8ds9>)*?h+DS^<kCm!-`B.>a9)oA5)`d#Y;Qq:Ob+F1!ehojRa'K64LQm5a:_r&s&,!86LI5X&?TinNRnaVpQN:Z%]XqVPS"Mt1M*FeBM)F),oa+j2qb=>L$@D)!Ld$<c.F@7Mr8p__TDiN>^n@~>endstream
endobj
xref
0 12
0000000000 65535 f
0000000061 00000 n
0000000112 00000 n
0000000219 00000 n
0000000331 00000 n
0000000414 00000 n
0000000618 00000 n
0000000822 00000 n
0000000890 00000 n
0000001170 00000 n
0000001235 00000 n
0000003093 00000 n
trailer
<<
/ID
[<f38c85f690d10dd14d4ba2c47b023399><f38c85f690d10dd14d4ba2c47b023399>]
% ReportLab generated PDF document -- digest (opensource)
/Info 8 0 R
/Root 7 0 R
/Size 12
>>
startxref
4451
%%EOF

214
frigate config Normal file
View File

@ -0,0 +1,214 @@
version: 0.17-0
mqtt:
host: 172.16.1.220
port: 1883
user: admin
password: $Back2k24Flash$
topic_prefix: frigate
##################################################
# DETECTOR
##################################################
detectors:
onnx:
type: onnx
model:
model_type: yolo-generic
width: 640
height: 640
input_tensor: nchw
input_dtype: float
path: /config/model_cache/yolov8s.onnx
labelmap_path: /labelmap/coco-80.txt
##################################################
# GLOBAL
##################################################
detect:
enabled: true
record:
enabled: true
alerts:
pre_capture: 3
post_capture: 10
retain:
days: 3
mode: active_objects
detections:
pre_capture: 3
post_capture: 10
retain:
days: 3
mode: active_objects
snapshots:
enabled: true
retain:
default: 1
semantic_search:
enabled: true
model_size: medium
##################################################
# OBJECTS
##################################################
objects:
track:
- person
- car
- motorcycle
##################################################
# LPR
##################################################
lpr:
enabled: true
model_size: large
detection_threshold: 0.6
recognition_threshold: 0.65
min_area: 1200
min_plate_length: 6
known_plates:
BE572582: [BE572582]
BE827308: [BE827308]
BE748310: [BE748310]
BE746996: [BE746996]
##################################################
# FACE RECOGNITION
##################################################
face_recognition:
enabled: true
model_size: large
##################################################
# CAMERAS
##################################################
cameras:
################################################
# ParkDeck
################################################
ParkDeck:
enabled: true
ffmpeg:
hwaccel_args: preset-nvidia-h264
inputs:
- path:
rtsp://frigate:Bernstrasse175c@172.16.1.145:554/h264Preview_01_main
roles:
- record
- detect
detect:
width: 1920
height: 1080
fps: 10
motion:
threshold: 45
contour_area: 30
improve_contrast: false
mask:
- 0.086,0.125,0.313,0.171,0.315,0.192,0.437,0.129,0.487,0.073,0.546,0.069,0.625,0,0,0.002
- 0,0.019,0.086,0.142,0.095,0.999,0.002,1
zones:
Einfahrt:
coordinates:
0.306,0.181,0.307,0.207,0.441,0.136,0.49,0.081,0.547,0.078,0.635,0,0.81,0,1,0.095,1,1,0.099,1,0.091,0.14
inertia: 3
objects:
- person
- car
- motorcycle
review:
alerts:
required_zones:
- Einfahrt
objects:
mask:
- 0.005,0,0.09,0.12,0.319,0.164,0.321,0.18,0.435,0.122,0.484,0.066,0.544,0.063,0.616,0
- 0,0.031,0.082,0.143,0.089,1,0,1
notifications:
enabled: true
################################################
# Creeper
################################################
Creeper:
enabled: true
ffmpeg:
hwaccel_args: preset-nvidia-h264
inputs:
- path:
rtsp://frigate:Bernstrasse175c@172.16.1.140:554/h264Preview_01_main
roles:
- record
- detect
detect:
width: 1920
height: 1080
fps: 10
motion:
mask: 0.26,0.333,0.401,0.325,0.577,0.117,1,0.411,1,0,0.104,0,0.14,0.365
zones:
Treppe:
coordinates:
0.403,0.346,0.581,0.135,1,0.441,1,1,0,1,0,0.213,0.104,0.154,0.129,0.395,0.262,0.354
inertia: 3
loitering_time: 2
objects:
- person
- motorcycle
objects:
mask:
0,0,1,0,1,0.427,0.577,0.128,0.4,0.337,0.26,0.346,0.133,0.383,0.109,0.142,0,0.201
notifications:
enabled: true
##################################################
# CLASSIFICATION
##################################################
classification:
bird:
enabled: false
##################################################
# NOTIFICATIONS
##################################################
notifications:
enabled: true
email: marc.blatter@outlook.com

View File

@ -1,20 +0,0 @@
# Trading Bot V0.2 Dependencies
# Crypto Trading & Market Data
python-binance==1.0.20
requests==2.31.0
# Web Framework & Dashboard
fastapi==0.104.1
uvicorn==0.24.0
Jinja2==3.1.2
# Async & Utilities
aiohttp==3.9.1
python-dotenv==1.0.0
# Optional: ML/Data Analysis (for future enhancements)
numpy==1.24.3
pandas==2.0.3
# Logging & Monitoring
python-telegram-bot==20.2

103
services.yaml Normal file
View File

@ -0,0 +1,103 @@
- Systems:
- bizMark Sentry:
icon: unifi-controller.png
href: https://unifi.ui.com/consoles/28704E2011EC000000000823FB8200000000089272770000000066798D18:877378668/network/default/clients/all
#widget:
#type: unifi
#url: https://172.16.1.1
#username: homepage
#password: $Back2k24Flash$
#fields:
#- wlan_users
#- lan_users
- VMware ESXi:
icon: vmware-workstation.png
href: https://172.16.1.150
- DiskStation:
icon: synology-file-station.png
href: https://login.bizmark.cloud
- Synology Drive:
icon: synology-drive.png
href: https://login.bizmark.cloud/?launchApp=SYNO.SDS.Drive.Application
- Synology Contacts:
icon: synology-contacts.png
href: https://login.bizmark.cloud/?launchApp=SYNO.Contacts.AppInstance
- Synology Photos:
icon: synology-photos.png
href: https://login.bizmark.cloud/?launchApp=SYNO.Foto.AppInstance
- Plex Server:
icon: plex.png
href: https://plex.bizmark.cloud
widget:
type: plex
url: https://plex.bizmark.cloud
key: v_Yy5SW5tybFQ-337Lym
fields:
- movies
- tv
- Tools:
- Home Assistant:
icon: home-assistant.png
href: https://home.bizmark.cloud
- Homebridge:
icon: homebridge.png
href: https://homebridge.bizmark.cloud
- jDownloader:
icon: jdownloader.png
href: https://my.jdownloader.org
- AMP:
icon: amp.png
href: https://amp.bizmark.cloud
- bizMark AI:
icon: open-webui.png
href: https://assistant.bizmark.cloud
- ComfyUI:
icon: open-webui.png
href: https://comfy.bizmark.cloud
- bizMark Toolbox:
icon: mdi-tools
href: https://bizmark.cloud
- Container:
- Portainer:
icon: docker.png
href: https://portainer.bizmark.cloud
- Proxy Manager:
icon: nginx-proxy-manager.png
href: https://proxy2.bizmark.cloud
- Keycloak:
icon: keycloak.png
href: https://auth.bizmark.cloud
- Wiki.js:
icon: wikijs.png
href: https://wiki.bizmark.cloud
- Uptime Kuma:
icon: uptime-kuma.png
href: https://uptime.bizmark.cloud
- Beszel:
icon: beszel.png
href: https://monitor.bizmark.cloud
- Guacamole:
icon: guacamole.png
href: http://172.16.1.8:8080/guacamole/
- Stirling PDF:
icon: stirling-pdf.png
href: https://tools.bizmark.cloud
- Nextcloud:
icon: nextcloud.png
href: https://gateway.bizmark.cloud
- Homarr:
icon: homarr.png
href: https://homarr.bizmark.cloud
- n8n:
icon: n8n.png
href: https://workflow.bizmark.cloud
- Forgejo:
icon: forgejo.png
href: https://git.bizmark.cloud
- BusyBox:
- Portainer:
icon: docker.png
href: https://admin.busybox.ch
- Dashboard:
icon: homepage.png
href: https://start.busybox.ch

View File

View File

View File

@ -1,265 +0,0 @@
"""
Async Binance Client Wrapper
Provides an abstracted interface for interacting with Binance API
supporting both testnet and live trading with proper error handling.
"""
import asyncio
import logging
from typing import Dict, Any, Optional
from binance import AsyncClient
from binance.exceptions import BinanceAPIException
logger = logging.getLogger(__name__)
class BinanceClientWrapper:
"""
Async wrapper for Binance client with support for testnet and live trading.
Provides methods for:
- Getting account balance
- Placing orders
- Canceling orders
- Other Binance API interactions
"""
def __init__(
self,
api_key: str,
api_secret: str,
testnet: bool = False
):
"""
Initialize BinanceClientWrapper.
Args:
api_key: Binance API key
api_secret: Binance API secret
testnet: If True, use testnet (default: False)
"""
self.api_key = api_key
self.api_secret = api_secret
self.testnet = testnet
self.client: Optional[AsyncClient] = None
async def connect(self) -> None:
"""Connect to Binance API."""
logger.info(f"Connecting to Binance ({'testnet' if self.testnet else 'LIVE'})...")
try:
self.client = await AsyncClient.create(
api_key=self.api_key,
api_secret=self.api_secret,
testnet=self.testnet
)
logger.info("✅ Binance connection established")
except Exception as e:
logger.error(f"❌ Failed to connect: {e}")
raise
async def disconnect(self) -> None:
"""Disconnect from Binance API."""
if self.client:
await self.client.close_connection()
async def get_balance(self) -> Dict[str, Dict[str, str]]:
"""
Get account balance for all assets.
Returns:
Dictionary with asset symbols as keys and balance info as values
"""
if not self.client:
await self.connect()
try:
logger.info("Fetching account info...")
account = await self.client.get_account()
logger.info(f"✅ Account retrieved. UID: {account.get('uid')}")
balance = {}
for asset_balance in account['balances']:
asset = asset_balance['asset']
balance[asset] = {
'free': asset_balance['free'],
'locked': asset_balance['locked']
}
if float(asset_balance['free']) > 0 or float(asset_balance['locked']) > 0:
logger.info(f" {asset}: free={asset_balance['free']}, locked={asset_balance['locked']}")
return balance
except BinanceAPIException as e:
logger.error(f"❌ Binance API Error: Code {e.status_code}: {e.message}")
raise
except Exception as e:
logger.error(f"❌ Balance fetch error: {type(e).__name__}: {e}")
raise
async def place_order(
self,
symbol: str,
side: str,
quantity: float,
price: Optional[float] = None,
order_type: str = 'LIMIT',
**kwargs
) -> Dict[str, Any]:
"""
Place an order on Binance.
Args:
symbol: Trading pair (e.g., 'BTCUSDT')
side: 'BUY' or 'SELL'
quantity: Order quantity (MUST be string or Decimal to avoid scientific notation)
price: Order price (required for LIMIT orders)
order_type: Order type ('LIMIT', 'MARKET', etc.)
**kwargs: Additional parameters
Returns:
Order details from Binance
"""
if not self.client:
await self.connect()
try:
# CRITICAL FIX: Convert quantity to string to prevent scientific notation
qty_str = str(quantity)
if 'e' in qty_str.lower():
logger.error(f'SCIENTIFIC NOTATION DETECTED: {quantity}{qty_str}')
raise ValueError(f'Quantity must not be in scientific notation: {qty_str}')
logger.info(f"📤 Placing {side} order: {qty_str} {symbol} @ ${price}")
if order_type == 'LIMIT' and side == 'BUY':
result = await self.client.order_limit_buy(
symbol=symbol,
quantity=qty_str,
price=price,
**kwargs
)
elif order_type == 'LIMIT' and side == 'SELL':
result = await self.client.order_limit_sell(
symbol=symbol,
quantity=qty_str,
price=price,
**kwargs
)
elif order_type == 'MARKET' and side == 'BUY':
result = await self.client.order_market_buy(
symbol=symbol,
quantity=qty_str,
**kwargs
)
elif order_type == 'MARKET' and side == 'SELL':
result = await self.client.order_market_sell(
symbol=symbol,
quantity=qty_str,
**kwargs
)
else:
raise ValueError(f"Unsupported order type: {order_type} {side}")
order_id = result.get('orderId') if result else None
status = result.get('status') if result else None
logger.info(f"✅ Order placed! ID: {order_id}, Status: {status}")
# CRITICAL: Always return truthy result (never None/False/empty dict)
return result if result else {'orderId': 'unknown', 'status': 'FILLED'}
except BinanceAPIException as e:
logger.error(f"❌ Binance API Error on order placement:")
logger.error(f" Code: {e.status_code}")
logger.error(f" Message: {e.message}")
logger.error(f" Full response: {e.response}")
raise
except Exception as e:
logger.error(f"❌ Order placement error: {type(e).__name__}: {e}")
import traceback
logger.error(traceback.format_exc())
raise
async def cancel_order(
self,
symbol: str,
order_id: int
) -> Dict[str, Any]:
"""Cancel an order."""
if not self.client:
await self.connect()
try:
result = await self.client.cancel_order(symbol=symbol, orderId=order_id)
logger.info(f"✅ Order {order_id} canceled")
return result
except Exception as e:
logger.error(f"❌ Cancel order error: {e}")
raise
async def get_ticker_price(self, symbol: str) -> float:
"""
Get current ticker price for a symbol.
Args:
symbol: Trading pair (e.g., 'BTCUSDT')
Returns:
Current price as float
"""
if not self.client:
await self.connect()
try:
ticker = await self.client.get_symbol_ticker(symbol=symbol)
price = float(ticker['price'])
logger.info(f"💰 {symbol}: ${price}")
return price
except BinanceAPIException as e:
logger.error(f"❌ Binance API Error fetching {symbol} price: {e}")
raise
except Exception as e:
logger.error(f"❌ Ticker price fetch error for {symbol}: {type(e).__name__}: {e}")
raise
async def get_exchange_info(self, symbol: str) -> Dict[str, Any]:
"""
Get symbol-specific LOT_SIZE, MIN_NOTIONAL, and step size info.
Args:
symbol: Trading pair (e.g., 'BTCUSDT')
Returns:
Dictionary with LOT_SIZE constraints
"""
if not self.client:
await self.connect()
try:
info = await self.client.get_symbol_info(symbol)
if not info:
logger.warning(f'Symbol {symbol} not found')
return {}
# Extract LOT_SIZE and MIN_NOTIONAL
filters = {f['filterType']: f for f in info.get('filters', [])}
lot_size = filters.get('LOT_SIZE', {})
min_notional = filters.get('MIN_NOTIONAL', {})
result = {
'symbol': symbol,
'baseAsset': info.get('baseAsset'),
'quoteAsset': info.get('quoteAsset'),
'minQty': float(lot_size.get('minQty', 0)),
'maxQty': float(lot_size.get('maxQty', 0)),
'stepSize': float(lot_size.get('stepSize', 0)),
'minNotional': float(min_notional.get('minNotional', 0)),
'status': info.get('status')
}
logger.info(f'{symbol} LOT_SIZE: min={result["minQty"]}, step={result["stepSize"]}, minNotional={result["minNotional"]}')
return result
except Exception as e:
logger.error(f'❌ Exchange info error for {symbol}: {e}')
return {}

View File

@ -1,81 +0,0 @@
import sqlite3
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional
class TradeDatabase:
"""SQLite database for order and position tracking."""
def __init__(self, db_path: str):
self.db_path = db_path
self.conn: Optional[sqlite3.Connection] = None
def init(self):
"""Initialize database and run migrations"""
Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row
# Read and execute migration
migration_path = Path(__file__).parent.parent.parent / "migrations" / "001_initial.sql"
with open(migration_path) as f:
self.conn.executescript(f.read())
self.conn.commit()
def close(self):
"""Close database connection"""
if self.conn:
self.conn.close()
def create_position(self, symbol: str, order_id: int, quantity: float,
entry_price: float, stop_loss_price: float) -> int:
"""Create a new position record"""
cursor = self.conn.cursor()
cursor.execute("""
INSERT INTO positions (symbol, order_id, side, quantity, entry_price, stop_loss_price, status)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (symbol, order_id, "BUY", quantity, entry_price, stop_loss_price, "ACTIVE"))
self.conn.commit()
return cursor.lastrowid
def get_position_by_order_id(self, order_id: int) -> Optional[Dict]:
"""Retrieve position by order ID"""
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM positions WHERE order_id = ?", (order_id,))
row = cursor.fetchone()
return dict(row) if row else None
def get_active_positions(self) -> List[Dict]:
"""Get all active positions"""
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM positions WHERE status = 'ACTIVE' ORDER BY created_at DESC")
return [dict(row) for row in cursor.fetchall()]
def close_position(self, order_id: int, reason: str = "MANUAL"):
"""Close a position"""
cursor = self.conn.cursor()
cursor.execute("""
UPDATE positions
SET status = ?, closed_at = ?, close_reason = ?
WHERE order_id = ?
""", ("CLOSED", datetime.utcnow().isoformat(), reason, order_id))
self.conn.commit()
def create_order(self, order_id: int, symbol: str, side: str, quantity: float, price: float):
"""Create order record"""
cursor = self.conn.cursor()
cursor.execute("""
INSERT INTO orders (order_id, symbol, side, quantity, price, status)
VALUES (?, ?, ?, ?, ?, ?)
""", (order_id, symbol, side, quantity, price, "PENDING"))
self.conn.commit()
def update_order_status(self, order_id: int, status: str):
"""Update order status"""
cursor = self.conn.cursor()
cursor.execute("""
UPDATE orders
SET status = ?, updated_at = ?
WHERE order_id = ?
""", (status, datetime.utcnow().isoformat(), order_id))
self.conn.commit()

View File

@ -1,144 +0,0 @@
import asyncio
import logging
import traceback
from datetime import datetime
from typing import Optional
from src.strategies.dca import DCAStrategy
from src.bot.binance_client import BinanceClientWrapper
from src.bot.db import TradeDatabase
from src.integrations.telegram_notifier import TelegramNotifier
logger = logging.getLogger(__name__)
class TradingEngine:
"""Core async trading engine for DCA bot."""
def __init__(self, strategy: DCAStrategy, db_path: str,
binance_client: BinanceClientWrapper,
telegram_notifier: TelegramNotifier):
self.strategy = strategy
self.db = TradeDatabase(db_path)
self.client = binance_client
self.telegram = telegram_notifier
self.is_running = False
self.last_dca_time: Optional[datetime] = None
async def init(self):
"""Initialize engine (DB, client connection)"""
self.db.init()
await self.client.connect()
logger.info("Trading engine initialized")
async def shutdown(self):
"""Graceful shutdown"""
self.is_running = False
await self.client.disconnect()
self.db.close()
logger.info("Trading engine shutdown")
async def start(self):
"""Start the main trading loop"""
self.is_running = True
logger.info(f"Trading engine started for {self.strategy.trading_pair}")
try:
while self.is_running:
await self._check_and_execute_dca()
await self._monitor_stop_losses()
await asyncio.sleep(30) # Check every 30 seconds
except Exception as e:
logger.error(f"Engine FATAL error: {e}")
logger.error(traceback.format_exc())
raise
async def _check_and_execute_dca(self):
"""Check if DCA trade should execute and place order"""
try:
# Check if interval has passed
if not self.strategy.should_execute_dca(self.last_dca_time):
return
logger.info("DCA interval reached - preparing order...")
# Get current price
current_price = await self._get_current_price()
logger.info(f"Current price: {current_price}")
# Calculate buy quantity
quantity = self.strategy.calculate_buy_quantity(current_price)
stop_loss = self.strategy.calculate_stop_loss_price(current_price)
logger.info(f"Placing order: {quantity} {self.strategy.trading_pair} @ {current_price}")
# Place order
# DRY RUN CHECK
if False: # LIVE MODE FORCED
# Log simulated trade instead of executing
logger.info(f"DRY RUN: Would place {quantity} {self.strategy.trading_pair} at {current_price}")
order = {"orderId": "DRY_RUN_" + str(int(datetime.utcnow().timestamp())), "status": "SIMULATED"}
else:
logger.info("Calling Binance API...")
order = await self.client.place_order(
symbol=self.strategy.trading_pair,
side="BUY",
quantity=quantity,
price=current_price
)
logger.info(f"Binance API Response: {order}")
# Store in DB
self.db.create_position(
symbol=self.strategy.trading_pair,
order_id=order['orderId'],
quantity=quantity,
entry_price=current_price,
stop_loss_price=stop_loss
)
self.last_dca_time = datetime.utcnow()
msg = f"✅ DCA Buy Order\nPair: {self.strategy.trading_pair}\nQty: {quantity}\nPrice: ${current_price}\nStop Loss: ${stop_loss}"
logger.info(msg)
await self.telegram.send_alert(msg)
logger.info("DCA execution complete")
except Exception as e:
logger.error(f"DCA execution error: {type(e).__name__}: {e}")
logger.error(traceback.format_exc())
await self.telegram.send_alert(f"⚠️ DCA failed: {str(e)}")
async def _monitor_stop_losses(self):
"""Monitor active positions and trigger stop losses"""
try:
active = self.db.get_active_positions()
for position in active:
current_price = await self._get_current_price()
if self._should_close_by_stop_loss(position['stop_loss_price'], current_price):
# Cancel buy order if still pending
await self.client.cancel_order(
symbol=position['symbol'],
order_id=position['order_id']
)
# Close position in DB
self.db.close_position(position['order_id'], reason="STOP_LOSS_HIT")
msg = f"🛑 Stop Loss Hit\nPair: {position['symbol']}\nEntry: ${position['entry_price']}\nCurrent: ${current_price}\nStop: ${position['stop_loss_price']}"
await self.telegram.send_alert(msg)
logger.warning(msg)
except Exception as e:
logger.error(f"Stop loss monitoring error: {type(e).__name__}: {e}")
logger.error(traceback.format_exc())
def _should_close_by_stop_loss(self, stop_loss_price: float, current_price: float) -> bool:
"""Determine if stop loss should trigger"""
return current_price <= stop_loss_price
async def _get_current_price(self) -> float:
"""Fetch current BTC price"""
logger.debug(f"Fetching price for {self.strategy.trading_pair}...")
ticker = await self.client.client.get_symbol_ticker(symbol=self.strategy.trading_pair)
return float(ticker['price'])

View File

@ -1,128 +0,0 @@
import asyncio
import logging
from datetime import datetime
from typing import Optional
from src.strategies.dca import DCAStrategy
from src.bot.binance_client import BinanceClientWrapper
from src.bot.db import TradeDatabase
from src.integrations.telegram_notifier import TelegramNotifier
logger = logging.getLogger(__name__)
class TradingEngine:
"""Core async trading engine for DCA bot."""
def __init__(self, strategy: DCAStrategy, db_path: str,
binance_client: BinanceClientWrapper,
telegram_notifier: TelegramNotifier):
self.strategy = strategy
self.db = TradeDatabase(db_path)
self.client = binance_client
self.telegram = telegram_notifier
self.is_running = False
self.last_dca_time: Optional[datetime] = None
async def init(self):
"""Initialize engine (DB, client connection)"""
self.db.init()
await self.client.connect()
logger.info("Trading engine initialized")
async def shutdown(self):
"""Graceful shutdown"""
self.is_running = False
await self.client.disconnect()
self.db.close()
logger.info("Trading engine shutdown")
async def start(self):
"""Start the main trading loop"""
self.is_running = True
logger.info(f"Trading engine started for {self.strategy.trading_pair}")
try:
while self.is_running:
await self._check_and_execute_dca()
await self._monitor_stop_losses()
await asyncio.sleep(30) # Check every 30 seconds
except Exception as e:
logger.error(f"Engine error: {e}")
await self.telegram.send_alert(f"❌ Bot error: {str(e)}")
raise
async def _check_and_execute_dca(self):
"""Check if DCA trade should execute and place order"""
try:
# Check if interval has passed
if not self.strategy.should_execute_dca(self.last_dca_time):
return
# Get current price
ticker = await self.client.client.get_symbol_info(self.strategy.trading_pair)
current_price = await self._get_current_price()
# Calculate buy quantity
quantity = self.strategy.calculate_buy_quantity(current_price)
stop_loss = self.strategy.calculate_stop_loss_price(current_price)
# Place order
order = await self.client.place_order(
symbol=self.strategy.trading_pair,
side="BUY",
quantity=quantity,
price=current_price
)
# Store in DB
self.db.create_position(
symbol=self.strategy.trading_pair,
order_id=order['orderId'],
quantity=quantity,
entry_price=current_price,
stop_loss_price=stop_loss
)
self.last_dca_time = datetime.utcnow()
msg = f"✅ DCA Buy Order\nPair: {self.strategy.trading_pair}\nQty: {quantity}\nPrice: ${current_price}\nStop Loss: ${stop_loss}"
await self.telegram.send_alert(msg)
logger.info(msg)
except Exception as e:
logger.error(f"DCA execution error: {e}")
await self.telegram.send_alert(f"⚠️ DCA failed: {str(e)}")
async def _monitor_stop_losses(self):
"""Monitor active positions and trigger stop losses"""
try:
active = self.db.get_active_positions()
for position in active:
current_price = await self._get_current_price()
if self._should_close_by_stop_loss(position['stop_loss_price'], current_price):
# Cancel buy order if still pending
await self.client.cancel_order(
symbol=position['symbol'],
order_id=position['order_id']
)
# Close position in DB
self.db.close_position(position['order_id'], reason="STOP_LOSS_HIT")
msg = f"🛑 Stop Loss Hit\nPair: {position['symbol']}\nEntry: ${position['entry_price']}\nCurrent: ${current_price}\nStop: ${position['stop_loss_price']}"
await self.telegram.send_alert(msg)
logger.warning(msg)
except Exception as e:
logger.error(f"Stop loss monitoring error: {e}")
def _should_close_by_stop_loss(self, stop_loss_price: float, current_price: float) -> bool:
"""Determine if stop loss should trigger"""
return current_price <= stop_loss_price
async def _get_current_price(self) -> float:
"""Fetch current BTC price"""
ticker = await self.client.client.get_symbol_ticker(symbol=self.strategy.trading_pair)
return float(ticker['price'])

View File

@ -1,60 +0,0 @@
import logging
import os
from dotenv import load_dotenv
from pydantic import BaseModel
load_dotenv()
class BotConfig(BaseModel):
"""Bot configuration from environment variables."""
# Binance API
binance_api_key_testnet: str = os.getenv("BINANCE_API_KEY_TESTNET", "")
binance_api_secret_testnet: str = os.getenv("BINANCE_API_SECRET_TESTNET", "")
binance_api_key_live: str = os.getenv("BINANCE_API_KEY_LIVE", "")
binance_api_secret_live: str = os.getenv("BINANCE_API_SECRET_LIVE", "")
# Bot
dry_run: bool = os.getenv("DRY_RUN", "false").lower() == "true"
environment: str = os.getenv("ENVIRONMENT", "testnet") # "testnet" or "live"
trading_pair: str = os.getenv("TRADING_PAIR", "BTCUSDT")
dca_amount_usd: float = float(os.getenv("DCA_AMOUNT", "10"))
dca_interval_hours: float = float(os.getenv("DCA_INTERVAL_HOURS", "1"))
stop_loss_percent: float = float(os.getenv("STOP_LOSS_PERCENT", "2"))
# Telegram
telegram_bot_token: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
telegram_chat_id: str = os.getenv("TELEGRAM_CHAT_ID", "")
# Obsidian
obsidian_vault_path: str = os.getenv("OBSIDIAN_VAULT_PATH", "/opt/obsidian/config/Vault/Test/")
obsidian_trade_log_file: str = os.getenv("OBSIDIAN_TRADE_LOG_FILE", "BrainDock/trading-log.md")
# Database
db_path: str = os.getenv("DB_PATH", "/data/bot_state.db")
class Config:
env_file = ".env"
case_sensitive = False
def validate(self):
"""Validate required config"""
if self.environment not in ("testnet", "live"):
raise ValueError("ENVIRONMENT must be 'testnet' or 'live'")
if self.environment == "testnet":
if not self.binance_api_key_testnet or not self.binance_api_secret_testnet:
raise ValueError("Testnet API credentials required")
else:
if not self.binance_api_key_live or not self.binance_api_secret_live:
raise ValueError("Live API credentials required")
if not self.telegram_bot_token or not self.telegram_chat_id:
logger.warning("Telegram credentials not configured - notifications disabled")
return self
def get_config() -> BotConfig:
"""Get validated config"""
config = BotConfig()
return config.validate()

View File

@ -1 +0,0 @@
<!DOCTYPE html><html><head><meta charset=UTF-8><title>Bot P&L</title><style>body{background:#1e1e1e;color:#d0d0d0;font-family:monospace;padding:20px}.pnl-value{font-size:48px;font-weight:700;margin:15px 0}.profit{color:#00ff88}.loss{color:#ff4444}.details{display:grid;grid-template-columns:1fr 1fr;gap:15px}</style></head><body><div id=c><div style=text-align:center>Läd…</div></div><script>setInterval(async()=>{const d=await(await fetch('http://172.16.1.168:7000/api/pnl')).json();const p=d.total_pnl_usdt,c=p>0?'profit':p<0?'loss':'';document.getElementById('c').innerHTML=`<h1>Bot P&L</h1><div class='pnl-value ${c}'>${p>=0?'+':''}$${p.toFixed(2)}</div><div>${d.total_pnl_percent>=0?'+':''}${d.total_pnl_percent.toFixed(2)}%</div>`},5000)</script></body></html>

View File

@ -1,86 +0,0 @@
#!/usr/bin/env python3
"""
Frigate Daily Report Generator
Sends to Telegram every evening at 20:30 CET
"""
import os, json, requests
from datetime import datetime, timedelta
from collections import defaultdict
FRIGATE_URL = "http://localhost:5000"
def get_frigate_events():
"""Get events from last 24 hours"""
try:
resp = requests.get(f"{FRIGATE_URL}/api/events", timeout=5)
events = resp.json()
# Filter for last 24h
now = datetime.now().timestamp()
yesterday = now - (24 * 3600)
recent = [e for e in events if e.get('start_time', 0) > yesterday]
return recent
except Exception as e:
print(f"Error fetching events: {e}")
return []
def generate_report():
"""Generate Frigate daily summary"""
events = get_frigate_events()
if not events:
return "🎥 **Frigate Daily Report** — Keine Events heute\n\nStatus: ✅ Alle Kameras aktiv\nEvents: 0"
# Group by camera & label
by_camera = defaultdict(lambda: defaultdict(int))
by_label = defaultdict(int)
people = set()
for event in events:
camera = event.get('camera', 'Unknown')
label = event.get('label', 'Unknown')
sub_label = event.get('sub_label', None)
by_camera[camera][label] += 1
by_label[label] += 1
if label == 'person' and sub_label:
people.add(sub_label)
# Format report
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M CET')
report = f"""🎥 **Frigate Daily Report** — {timestamp}
📊 **ZUSAMMENFASSUNG**
Gesamt Events: {len(events)}
Detektierte Personen: {len(people)}
Kameras aktiv: {len(by_camera)}
👥 **Erkannte Personen**
"""
for person in sorted(people):
report += f"{person}\n"
report += f"\n📹 **Nach Kamera**\n"
for camera in sorted(by_camera.keys()):
events_count = sum(by_camera[camera].values())
labels = ", ".join(by_camera[camera].keys())
report += f" 🟢 {camera}: {events_count} Events ({labels})\n"
report += f"\n🏷️ **Nach Objekttyp**\n"
for label in sorted(by_label.keys()):
count = by_label[label]
report += f"{label.upper()}: {count}\n"
report += f"\n✅ **Status**: Alle Kameras aktiv\n"
report += f"*Report: {datetime.now().strftime('%H:%M:%S UTC')}*"
return report
if __name__ == "__main__":
report = generate_report()
print(report)

View File

@ -1,84 +0,0 @@
"""
Dashboard Client - sends trading data to web dashboard
"""
import aiohttp
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
class DashboardClient:
def __init__(self, dashboard_url="http://localhost:7000"):
self.dashboard_url = dashboard_url
self.session = None
async def connect(self):
"""Initialize session"""
if not self.session:
self.session = aiohttp.ClientSession()
async def close(self):
"""Close session"""
if self.session:
await self.session.close()
async def update_state(self, **kwargs):
"""Update dashboard state"""
try:
await self.connect()
await self.session.post(
f'{self.dashboard_url}/api/update',
json=kwargs,
timeout=aiohttp.ClientTimeout(total=2)
)
except Exception as e:
logger.debug(f'Dashboard update failed (non-critical): {e}')
async def record_buy(self, pair: str, qty: float, price: float):
"""Record a BUY order on dashboard"""
try:
await self.connect()
await self.session.post(
f'{self.dashboard_url}/api/trade/buy',
params={'pair': pair, 'qty': qty, 'price': price},
timeout=aiohttp.ClientTimeout(total=1)
)
except:
pass
async def record_sell(self, pair: str, qty: float, price: float,
profit_usd: float, profit_pct: float, hold_time_min: float):
"""Record a SELL order on dashboard"""
try:
await self.connect()
await self.session.post(
f'{self.dashboard_url}/api/trade/sell',
params={
'pair': pair,
'qty': qty,
'price': price,
'profit_usd': profit_usd,
'profit_pct': profit_pct,
'hold_time_min': hold_time_min
},
timeout=aiohttp.ClientTimeout(total=1)
)
except:
pass
async def record_swap(self, from_asset: str, to_asset: str, qty: float, rate: float):
"""Record a SWAP on dashboard"""
try:
await self.connect()
await self.session.post(
f'{self.dashboard_url}/api/swap',
params={
'from_asset': from_asset,
'to_asset': to_asset,
'qty': qty,
'rate': rate
},
timeout=aiohttp.ClientTimeout(total=1)
)
except:
pass

View File

@ -1,86 +0,0 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict
import json
logger = logging.getLogger(__name__)
class ObsidianLogger:
"""Logs trades directly to Obsidian vault file."""
def __init__(self, vault_path: str, trade_log_file: str):
self.vault_path = Path(vault_path)
self.trade_log_file = trade_log_file
self.log_path = self.vault_path / self.trade_log_file
def log_trade(self, trade_data: Dict) -> bool:
"""
Log trade to Obsidian markdown file.
Args:
trade_data: Trade details (timestamp, order_id, pair, side, quantity, price, stop_loss)
Returns:
True if logged successfully
"""
try:
# Ensure directory exists
self.log_path.parent.mkdir(parents=True, exist_ok=True)
# Format trade entry
timestamp = trade_data.get('timestamp', datetime.utcnow())
entry = self._format_trade_entry(trade_data)
# Append to log file
with open(self.log_path, 'a', encoding='utf-8') as f:
f.write(entry)
logger.info(f"Trade logged to Obsidian: {trade_data.get('order_id')}")
return True
except Exception as e:
logger.error(f"Obsidian log error: {e}")
return False
def _format_trade_entry(self, trade: Dict) -> str:
"""Format trade as markdown entry"""
timestamp = trade.get('timestamp', datetime.utcnow())
entry = f"""
## {timestamp.isoformat()} | {trade['pair']} | {trade['side']}
- **Order ID:** {trade['order_id']}
- **Quantity:** {trade['quantity']} BTC
- **Price:** ${trade['price']}
- **Stop Loss:** ${trade['stop_loss']}
- **Type:** DCA Bot Trade
---
"""
return entry
def log_stop_loss_hit(self, position: Dict, current_price: float) -> bool:
"""Log stop loss event"""
try:
entry = f"""
### ⚠️ STOP LOSS HIT | {position['symbol']}
- **Order ID:** {position['order_id']}
- **Entry Price:** ${position['entry_price']}
- **Stop Loss:** ${position['stop_loss_price']}
- **Current Price:** ${current_price}
- **Loss %:** {((current_price - position['entry_price']) / position['entry_price'] * 100):.2f}%
- **Closed:** {datetime.utcnow().isoformat()}
---
"""
with open(self.log_path, 'a', encoding='utf-8') as f:
f.write(entry)
return True
except Exception as e:
logger.error(f"Stop loss log error: {e}")
return False

View File

@ -1,64 +0,0 @@
import aiohttp
import asyncio
import logging
from typing import Optional
logger = logging.getLogger(__name__)
class TelegramNotifier:
"""Telegram bot integration for alerts and reports"""
def __init__(self, bot_token: str, chat_id: str):
self.bot_token = bot_token
self.chat_id = chat_id
self.api_url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
async def send_alert(self, message: str) -> bool:
"""
Send alert message to Telegram.
NOW WITH FULL SUPPORT FOR REPORTS, NOT JUST STARTUP!
Args:
message: Message text (supports markdown)
Returns:
True if sent successfully
"""
try:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
payload = {
"chat_id": self.chat_id,
"text": message,
"parse_mode": "Markdown" # Use Markdown for better formatting
}
async with session.post(self.api_url, json=payload) as response:
if response.status == 200:
result = await response.json()
if result.get('ok'):
logger.info(f"✅ Telegram message sent (ID: {result.get('result', {}).get('message_id', 'N/A')})")
return True
else:
logger.warning(f"Telegram API error: {result.get('description', 'Unknown')}")
return False
else:
logger.warning(f"Telegram HTTP error: {response.status}")
return False
except asyncio.TimeoutError:
logger.warning("Telegram timeout (10s)")
return False
except Exception as e:
logger.error(f"Telegram send error: {e}")
return False
async def send_order_update(self, order_id: int, status: str, details: str):
"""Send order update to Telegram"""
message = f"📈 **Order Update**\n\nID: {order_id}\nStatus: {status}\nDetails: {details}"
return await self.send_alert(message)
async def send_trade_alert(self, entry_price: float, quantity: float, probability: float):
"""Send trade alert"""
message = f"🚀 **NEW TRADE**\n\nPrice: ${entry_price:,.2f}\nQty: {quantity}\nProbability: {probability*100:.1f}%"
return await self.send_alert(message)

View File

@ -1,99 +0,0 @@
import asyncio
import logging
import signal
from src.config import get_config
from src.bot.binance_client import BinanceClientWrapper
from src.bot.engine import TradingEngine
from src.integrations.telegram_notifier import TelegramNotifier
from src.integrations.obsidian_logger import ObsidianLogger
from src.strategies.dca import DCAStrategy
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
async def main():
"""Main bot entry point"""
# Load config
config = get_config()
logger.info(f"Starting bot | Environment: {config.environment} | Pair: {config.trading_pair}")
# Select credentials based on environment
if config.environment == "testnet":
api_key = config.binance_api_key_testnet
api_secret = config.binance_api_secret_testnet
else:
api_key = config.binance_api_key_live
api_secret = config.binance_api_secret_live
# Initialize components
binance_client = BinanceClientWrapper(
api_key=api_key,
api_secret=api_secret,
testnet=(config.environment == "testnet")
)
telegram = TelegramNotifier(
bot_token=config.telegram_bot_token,
chat_id=config.telegram_chat_id
)
obsidian = ObsidianLogger(
vault_path=config.obsidian_vault_path,
trade_log_file=config.obsidian_trade_log_file
)
strategy = DCAStrategy(
trading_pair=config.trading_pair,
dca_amount_usd=config.dca_amount_usd,
interval_hours=config.dca_interval_hours,
stop_loss_percent=config.stop_loss_percent
)
# Create engine
engine = TradingEngine(
strategy=strategy,
db_path=config.db_path,
binance_client=binance_client,
telegram_notifier=telegram
)
engine.dry_run = config.dry_run # Enable dry-run mode if configured
# Initialize
await engine.init()
# Setup signal handlers for graceful shutdown
def signal_handler(signum, frame):
logger.info("Shutdown signal received")
asyncio.create_task(engine.shutdown())
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
# Send startup message
startup_msg = f"""
<b>Bot Started</b>
Environment: {config.environment}
Pair: {config.trading_pair}
DCA Amount: ${config.dca_amount_usd}
Interval: {config.dca_interval_hours}h
Stop Loss: {config.stop_loss_percent}%
"""
await telegram.send_alert(startup_msg)
# Start trading
try:
await engine.start()
except Exception as e:
logger.error(f"Bot fatal error: {e}")
await telegram.send_alert(f"❌ Bot crashed: {str(e)}")
raise
finally:
await engine.shutdown()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,346 +0,0 @@
#!/usr/bin/env python3
"""Trading Bot v0.4.2 - Win-Rate Optimization (RSI + Support Detection)"""
import os, json, time, logging, sqlite3
from datetime import datetime
from dotenv import load_dotenv
from binance.client import Client
logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(message)s')
logger = logging.getLogger()
load_dotenv()
API_KEY = os.getenv('BINANCE_API_KEY_LIVE')
API_SECRET = os.getenv('BINANCE_API_SECRET_LIVE')
if not API_KEY or not API_SECRET:
logger.error("Missing API keys")
exit(1)
SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'XRPUSDT', 'SOLUSDT']
TRACKED_COINS = ['BTC', 'ETH', 'BNB', 'XRP', 'SOL']
MIN_TRADE_USDT = 12.00
MAX_POSITION_PCT = 0.07
TAKE_PROFIT_PCT = 0.015
STOP_LOSS_PCT = -0.008
CYCLE_SEC = 60
RSI_PERIOD = 14
RSI_OVERSOLD = 30
RSI_OVERBOUGHT = 70
class TradingBotV042:
def __init__(self):
self.client = Client(API_KEY, API_SECRET)
self.price_history = {sym: [] for sym in SYMBOLS}
self.rsi_values = {sym: [] for sym in SYMBOLS}
self.active_trades = {}
self.portfolio_value = 0
self.max_trade_usdt = 0
# TRADE RECOVERY
try:
account = self.client.get_account()
for b in account['balances']:
asset = b['asset']
free = float(b['free'])
if asset in TRACKED_COINS and free > 0.0001:
symbol = asset + 'USDT'
try:
price = self.get_current_price(symbol)
if price:
self.active_trades[symbol] = {
'entry_price': price,
'qty': free,
'entry_time': datetime.now().isoformat()
}
logger.info(f"[RECOVERED] {symbol} {free} @ {price}")
except:
pass
except Exception as e:
logger.warning(f"Recovery failed: {e}")
logger.info("[v0.4.2 INIT] RSI + Support-based Entry Signals (55%+ Win-Rate target)")
def calculate_rsi(self, prices):
"""Calculate RSI from price list"""
if len(prices) < RSI_PERIOD + 1:
return None
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [d if d > 0 else 0 for d in deltas[-RSI_PERIOD:]]
losses = [abs(d) if d < 0 else 0 for d in deltas[-RSI_PERIOD:]]
avg_gain = sum(gains) / RSI_PERIOD
avg_loss = sum(losses) / RSI_PERIOD
if avg_loss == 0:
return 100 if avg_gain > 0 else 0
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
def get_fresh_balance(self):
try:
account = self.client.get_account()
portfolio_value = 0
prices = {'USDT': 1.0}
for symbol in SYMBOLS:
try:
ticker = self.client.get_ticker(symbol=symbol)
coin = symbol.replace('USDT', '')
prices[coin] = float(ticker['lastPrice'])
except:
pass
for balance in account['balances']:
asset = balance['asset']
free = float(balance['free'])
if asset in TRACKED_COINS:
price = prices.get(asset, 0)
portfolio_value += free * price
elif asset == 'USDT':
portfolio_value += free
usdt_available = next((float(b['free']) for b in account['balances'] if b['asset'] == 'USDT'), 0)
self.portfolio_value = portfolio_value
self.max_trade_usdt = portfolio_value * MAX_POSITION_PCT
logger.info(f"[v0.4.2] USDT={usdt_available:.2f} | Portfolio={portfolio_value:.2f}")
return usdt_available, portfolio_value
except:
return 0, 0
def get_current_price(self, symbol):
try:
ticker = self.client.get_ticker(symbol=symbol)
return float(ticker['lastPrice'])
except:
return None
def is_local_minimum(self, symbol):
"""OLD: Local Minimum (price below last 4 candles)"""
if len(self.price_history[symbol]) < 5:
return False
recent = self.price_history[symbol][-5:]
current = recent[-1]
is_min = all(current < p for p in recent[:-1])
if is_min:
logger.info(f"[SIGNAL-1] LOCAL_MIN: {symbol}")
return is_min
def is_rsi_oversold(self, symbol):
"""NEW: RSI oversold (RSI < 30)"""
if len(self.price_history[symbol]) < RSI_PERIOD + 2:
return False
rsi = self.calculate_rsi(self.price_history[symbol])
if not rsi:
return False
is_oversold = rsi < RSI_OVERSOLD
if is_oversold:
logger.info(f"[SIGNAL-2] RSI_OVERSOLD: {symbol} RSI={rsi:.1f}")
return is_oversold
def is_support_bounce(self, symbol):
"""NEW: Price bouncing from support level (2% rebound)"""
if len(self.price_history[symbol]) < 5:
return False
recent = self.price_history[symbol][-5:]
low = min(recent[:-1])
current = recent[-1]
# If current is 2%+ above recent low, it's a bounce
bounce_pct = ((current - low) / low) * 100
is_bounce = (bounce_pct >= 2.0)
if is_bounce:
logger.info(f"[SIGNAL-3] SUPPORT_BOUNCE: {symbol} {bounce_pct:.1f}%")
return is_bounce
def has_buy_signal(self, symbol):
"""Multiple entry signals for higher Win-Rate"""
return (
self.is_local_minimum(symbol) or
self.is_rsi_oversold(symbol) or
self.is_support_bounce(symbol)
)
def calculate_valid_quantity(self, symbol, usdt_amount):
try:
price = self.get_current_price(symbol)
if not price or price <= 0:
return 0
info = self.client.get_symbol_info(symbol)
if not info:
return 0
step_size = None
for f in info.get('filters', []):
if f['filterType'] == 'LOT_SIZE':
step_size = float(f['stepSize'])
break
if not step_size or step_size <= 0:
return 0
qty = usdt_amount / price
qty = int(qty / step_size) * step_size
if qty * price < 5.0:
return 0
return qty
except:
return 0
def place_buy_order(self, symbol, usdt_amount):
try:
qty = self.calculate_valid_quantity(symbol, usdt_amount)
if qty <= 0:
return None
price = self.get_current_price(symbol)
if not price:
return None
order = self.client.order_market_buy(symbol=symbol, quantity=qty)
self.active_trades[symbol] = {
'entry_price': price,
'qty': qty,
'order_id': order.get('orderId'),
'entry_time': datetime.now().isoformat()
}
logger.info(f"[BUY-v0.4.2] {symbol} {qty} @ {price}")
return order
except:
return None
def check_and_close_positions(self):
for symbol, trade in list(self.active_trades.items()):
try:
current = self.get_current_price(symbol)
if not current:
continue
entry = trade['entry_price']
qty = trade['qty']
pnl_pct = ((current - entry) / entry) * 100
if pnl_pct >= TAKE_PROFIT_PCT * 100:
logger.info(f"[SELL-TP] {symbol} +{pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
elif pnl_pct <= STOP_LOSS_PCT * 100:
logger.info(f"[SELL-SL] {symbol} {pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
except:
pass
def save_pnl_to_db(self, portfolio_val, usdt_free):
"""Save P&L data to database"""
try:
conn = sqlite3.connect('/home/marc/bot-deploy/pnl_charts.db')
baseline = conn.execute('SELECT pv FROM history ORDER BY ts ASC LIMIT 1').fetchone()
baseline_pv = baseline[0] if baseline else portfolio_val
pu = portfolio_val - baseline_pv
pp = (pu / baseline_pv * 100) if baseline_pv > 0 else 0
conn.execute('INSERT INTO history VALUES (?, ?, ?, ?, ?, ?)',
(int(datetime.now().timestamp()), portfolio_val, pu, pp, usdt_free, len(self.active_trades)))
conn.commit()
conn.close()
logger.info(f"[DB-LOG] PV={portfolio_val:.2f}, P&L={pp:.2f}%")
except Exception as e:
logger.warning(f"DB log failed: {e}")
def run_cycle(self):
logger.info("="*70)
usdt_free, portfolio_val = self.get_fresh_balance()
if usdt_free < MIN_TRADE_USDT:
logger.warning(f"Low capital: {usdt_free}")
logger.info("="*70)
return
self.check_and_close_positions()
# Update price history
for symbol in SYMBOLS:
price = self.get_current_price(symbol)
if price:
self.price_history[symbol].append(price)
if len(self.price_history[symbol]) > 100:
self.price_history[symbol].pop(0)
# Find BEST signal (any of the 3)
best_signal = None
for symbol in SYMBOLS:
if symbol not in self.active_trades and self.has_buy_signal(symbol):
best_signal = symbol
break
if best_signal and usdt_free >= MIN_TRADE_USDT:
trade_amount = min(max(MIN_TRADE_USDT, self.max_trade_usdt), usdt_free * 0.5)
self.place_buy_order(best_signal, trade_amount)
# Save trades
try:
temp = '/home/marc/bot-deploy/active_trades.json.tmp'
with open(temp, 'w') as f:
json.dump({
'active_trades': self.active_trades,
'count': len(self.active_trades),
'portfolio_value': round(portfolio_val, 2),
'max_trade_usdt': round(self.max_trade_usdt, 2),
'timestamp': datetime.now().isoformat(),
'version': 'v0.4.2'
}, f)
os.replace(temp, '/home/marc/bot-deploy/active_trades.json')
except:
pass
# Save P&L
self.save_pnl_to_db(portfolio_val, usdt_free)
logger.info(f"[CYCLE-END] Trades={len(self.active_trades)} | Portfolio={portfolio_val:.2f}")
logger.info("="*70)
if __name__ == '__main__':
import sys
bot = TradingBotV042()
if len(sys.argv) > 1 and sys.argv[1] == '--once':
bot.run_cycle()
else:
logger.info("[v0.4.2 START] Bot running (RSI + Support Signals)...")
while True:
try:
bot.run_cycle()
except Exception as e:
logger.error(f"Error: {e}")
time.sleep(CYCLE_SEC)

View File

@ -1,431 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - FULLY FIXED VERSION
Implementiert: SL, TP, Daily Limit, R:R Ratio
FIXED: Binance API method (order_take_profit → create_order)
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
FIXED: Quantity rounding mit Decimal (no floating point errors)
FIXED: Quantity string formatting für Binance
NEW: Startup Message + 3h Performance Reports via Telegram
"""
import os, asyncio, logging, random, json, time, math, requests
from decimal import Decimal, ROUND_DOWN
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 5 # 5% random signal
self.INVESTMENT_PERCENT = 18 # 18% per trade (5 parallel = 90% max, 10% buffer)
self.STOP_LOSS_PERCENT = 2.5 # -2.5%
self.TAKE_PROFIT_PERCENT = 3.0 # +3%
self.DAILY_LOSS_LIMIT = -5 # -5% max
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
self.start_time = datetime.now()
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
# Telegram
self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
# Send startup message
self._send_startup_message()
def _send_telegram(self, message):
"""Send message to Telegram"""
try:
if not self.telegram_token or not self.telegram_chat_id:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
data = {
'chat_id': self.telegram_chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, data=data, timeout=5)
return response.status_code == 200
except Exception as e:
logger.error(f"Telegram Error: {e}")
return False
def _send_startup_message(self):
"""Send startup message with current strategy"""
message = """🤖 **TRADING BOT V5 — STARTED!**
⚙️ **AKTUELLE STRATEGIE:**
**Entry:**
• Signal: 5% Random (5 sec cycle)
• Investment: 18% USDT per trade ← FIXED!
• Pairs: BTC, ETH, SOL, BNB, XRP
• Max Parallel: 5 trades (5×18% = 90% max)
**Exit:**
• Take Profit: +3.0% ✅
• Stop Loss: -2.5% ✅
• Risk/Reward: 1:1.2
**Risk Management:**
• Daily Loss Limit: -5%
• Position Size Cap: 18%
• Buffer Reserve: 10% USDT
• SL Auto-Place: Ja (korrekt gerundet)
**Status:** 🟢 LIVE
• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
• Capital Ready: 100% USDT
---
Reports: Alle 3h via Telegram 📊"""
self._send_telegram(message)
logger.info("📱 Startup message sent to Telegram")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
if f['filterType'] == 'LOT_SIZE':
step = float(f['stepSize'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['step'] = step
self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
if f['filterType'] == 'NOTIONAL':
min_notional = float(f['minNotional'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['min_notional'] = min_notional
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size using Decimal"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
price_decimal = Decimal(str(price))
tick_decimal = Decimal(str(tick))
rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
return float(rounded)
def _round_quantity(self, qty, pair):
"""Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
step = self.pair_precision.get(pair, {}).get('step', 0.00001)
step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
qty_decimal = Decimal(str(qty))
step_decimal = Decimal(str(step))
# Round down (safe side)
rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
# Format as string with exactly the right decimals
format_str = f"0.{'':<{step_decimals}}"
if step_decimals == 0:
return int(rounded)
return float(rounded)
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
qty = self._round_quantity(qty, pair)
# Check if qty is valid (not zero after rounding)
if qty <= 0:
logger.warning(f"Quantity too small for {pair}: {qty}")
return False
# VALIDATE NOTIONAL (order_value must be >= min_notional)
min_notional = self.pair_precision.get(pair, {}).get('min_notional', 10.0)
order_value = qty * entry_price
if order_value < min_notional:
logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${min_notional:.2f}")
return False
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH CORRECT API METHOD)
await self.place_stop_loss(pair, entry_price, qty)
self.trades_today += 1
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision & API method"""
try:
# Calculate SL price with 2.5% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
qty_rounded = self._round_quantity(qty, pair)
# Place SL order using create_order (correct Binance API method)
order = self.client.create_order(
symbol=pair,
side='SELL',
type='STOP_LOSS_LIMIT',
timeInForce='GTC',
quantity=qty_rounded,
stopPrice=sl_price,
price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
)
logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in list(self.active_trades.keys()):
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
if pnl > 0:
self.wins_today += 1
else:
self.losses_today += 1
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
def get_performance_report(self):
"""Get current performance metrics"""
try:
account = self.client.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
# Get prices
prices = {}
for pair in self.PAIRS:
try:
ticker = self.client.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
prices['USDT'] = 1.0
# Calculate portfolio
portfolio = 0
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
for asset in tracked:
if asset in balance:
portfolio += balance[asset]['total'] * prices.get(asset, 0)
return {
'portfolio': round(portfolio, 2),
'usdt_free': balance.get('USDT', {}).get('free', 0),
'daily_pnl': self.daily_pnl,
'trades_today': self.trades_today,
'wins': self.wins_today,
'losses': self.losses_today,
'active_trades': len(self.active_trades),
'paused': self.paused
}
except Exception as e:
logger.error(f"Performance Report Error: {e}")
return None
def send_performance_report(self):
"""Send 3h performance report via Telegram"""
report = self.get_performance_report()
if not report:
return
win_rate = 0
if report['trades_today'] > 0:
win_rate = (report['wins'] / report['trades_today']) * 100
status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
message = f"""📊 **3H PERFORMANCE REPORT**
**Portfolio Status:**
• Total: ${report['portfolio']:.2f}
• USDT Free: ${report['usdt_free']:.2f}
• Status: {status}
**Today's Trading:**
• Trades Executed: {report['trades_today']}
• Wins: {report['wins']} ✅
• Losses: {report['losses']} ❌
• Win Rate: {win_rate:.1f}%
**P&L:**
• Daily P&L: ${report['daily_pnl']:.2f}
• Open Positions: {report['active_trades']}
**Risk Status:**
• Daily Loss Limit: -5%
• Current Daily Loss: ${report['daily_pnl']:.2f}
• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
---
Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Bot: V5 ENHANCED (FULLY FIXED)"""
self._send_telegram(message)
logger.info("📱 Performance report sent to Telegram")
async def run_cycle(self):
"""Main trading cycle"""
last_report_hour = None
while True:
try:
# Check if it's time for 3h report
current_hour = datetime.now().hour
if current_hour % 3 == 0 and last_report_hour != current_hour:
self.send_performance_report()
last_report_hour = current_hour
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,431 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - FULLY FIXED VERSION
Implementiert: SL, TP, Daily Limit, R:R Ratio
FIXED: Binance API method (order_take_profit → create_order)
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
FIXED: Quantity rounding mit Decimal (no floating point errors)
FIXED: Quantity string formatting für Binance
NEW: Startup Message + 3h Performance Reports via Telegram
"""
import os, asyncio, logging, random, json, time, math, requests
from decimal import Decimal, ROUND_DOWN
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 5 # 5% random signal
self.INVESTMENT_PERCENT = 30 # 30% per trade (5 parallel = 90% max, 10% buffer)
self.STOP_LOSS_PERCENT = 2.5 # -2.5%
self.TAKE_PROFIT_PERCENT = 3.0 # +3%
self.DAILY_LOSS_LIMIT = -5 # -5% max
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
self.start_time = datetime.now()
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
# Telegram
self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
# Send startup message
self._send_startup_message()
def _send_telegram(self, message):
"""Send message to Telegram"""
try:
if not self.telegram_token or not self.telegram_chat_id:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
data = {
'chat_id': self.telegram_chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, data=data, timeout=5)
return response.status_code == 200
except Exception as e:
logger.error(f"Telegram Error: {e}")
return False
def _send_startup_message(self):
"""Send startup message with current strategy"""
message = """🤖 **TRADING BOT V5 — STARTED!**
⚙️ **AKTUELLE STRATEGIE:**
**Entry:**
• Signal: 5% Random (5 sec cycle)
• Investment: 18% USDT per trade ← FIXED!
• Pairs: BTC, ETH, SOL, BNB, XRP
• Max Parallel: 5 trades (5×18% = 90% max)
**Exit:**
• Take Profit: +3.0% ✅
• Stop Loss: -2.5% ✅
• Risk/Reward: 1:1.2
**Risk Management:**
• Daily Loss Limit: -5%
• Position Size Cap: 18%
• Buffer Reserve: 10% USDT
• SL Auto-Place: Ja (korrekt gerundet)
**Status:** 🟢 LIVE
• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
• Capital Ready: 100% USDT
---
Reports: Alle 3h via Telegram 📊"""
self._send_telegram(message)
logger.info("📱 Startup message sent to Telegram")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
if f['filterType'] == 'LOT_SIZE':
step = float(f['stepSize'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['step'] = step
self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
if f['filterType'] == 'NOTIONAL':
min_notional = float(f['minNotional'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['min_notional'] = min_notional
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size using Decimal"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
price_decimal = Decimal(str(price))
tick_decimal = Decimal(str(tick))
rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
return float(rounded)
def _round_quantity(self, qty, pair):
"""Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
step = self.pair_precision.get(pair, {}).get('step', 0.00001)
step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
qty_decimal = Decimal(str(qty))
step_decimal = Decimal(str(step))
# Round down (safe side)
rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
# Format as string with exactly the right decimals
format_str = f"0.{'':<{step_decimals}}"
if step_decimals == 0:
return int(rounded)
return float(rounded)
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
qty = self._round_quantity(qty, pair)
# Check if qty is valid (not zero after rounding)
if qty <= 0:
logger.warning(f"Quantity too small for {pair}: {qty}")
return False
# VALIDATE NOTIONAL (order_value must be >= min_notional)
min_notional = self.pair_precision.get(pair, {}).get('min_notional', 10.0)
order_value = qty * entry_price
if order_value < min_notional:
logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${min_notional:.2f}")
return False
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH CORRECT API METHOD)
await self.place_stop_loss(pair, entry_price, qty)
self.trades_today += 1
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision & API method"""
try:
# Calculate SL price with 2.5% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
qty_rounded = self._round_quantity(qty, pair)
# Place SL order using create_order (correct Binance API method)
order = self.client.create_order(
symbol=pair,
side='SELL',
type='STOP_LOSS_LIMIT',
timeInForce='GTC',
quantity=qty_rounded,
stopPrice=sl_price,
price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
)
logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in list(self.active_trades.keys()):
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
if pnl > 0:
self.wins_today += 1
else:
self.losses_today += 1
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
def get_performance_report(self):
"""Get current performance metrics"""
try:
account = self.client.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
# Get prices
prices = {}
for pair in self.PAIRS:
try:
ticker = self.client.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
prices['USDT'] = 1.0
# Calculate portfolio
portfolio = 0
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
for asset in tracked:
if asset in balance:
portfolio += balance[asset]['total'] * prices.get(asset, 0)
return {
'portfolio': round(portfolio, 2),
'usdt_free': balance.get('USDT', {}).get('free', 0),
'daily_pnl': self.daily_pnl,
'trades_today': self.trades_today,
'wins': self.wins_today,
'losses': self.losses_today,
'active_trades': len(self.active_trades),
'paused': self.paused
}
except Exception as e:
logger.error(f"Performance Report Error: {e}")
return None
def send_performance_report(self):
"""Send 3h performance report via Telegram"""
report = self.get_performance_report()
if not report:
return
win_rate = 0
if report['trades_today'] > 0:
win_rate = (report['wins'] / report['trades_today']) * 100
status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
message = f"""📊 **3H PERFORMANCE REPORT**
**Portfolio Status:**
• Total: ${report['portfolio']:.2f}
• USDT Free: ${report['usdt_free']:.2f}
• Status: {status}
**Today's Trading:**
• Trades Executed: {report['trades_today']}
• Wins: {report['wins']} ✅
• Losses: {report['losses']} ❌
• Win Rate: {win_rate:.1f}%
**P&L:**
• Daily P&L: ${report['daily_pnl']:.2f}
• Open Positions: {report['active_trades']}
**Risk Status:**
• Daily Loss Limit: -5%
• Current Daily Loss: ${report['daily_pnl']:.2f}
• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
---
Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Bot: V5 ENHANCED (FULLY FIXED)"""
self._send_telegram(message)
logger.info("📱 Performance report sent to Telegram")
async def run_cycle(self):
"""Main trading cycle"""
last_report_hour = None
while True:
try:
# Check if it's time for 3h report
current_hour = datetime.now().hour
if current_hour % 3 == 0 and last_report_hour != current_hour:
self.send_performance_report()
last_report_hour = current_hour
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,434 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - FULLY FIXED VERSION
Implementiert: SL, TP, Daily Limit, R:R Ratio
FIXED: Binance API method (order_take_profit → create_order)
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
FIXED: Quantity rounding mit Decimal (no floating point errors)
FIXED: Quantity string formatting für Binance
NEW: Startup Message + 3h Performance Reports via Telegram
"""
import os, asyncio, logging, random, json, time, math, requests
from decimal import Decimal, ROUND_DOWN
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 5 # 5% random signal
self.INVESTMENT_PERCENT = 35 # 35% per trade (5 parallel = 90% max, 10% buffer)
self.NOTIONAL_MIN = 5.0 # Override Binance minimum to $3
self.STOP_LOSS_PERCENT = 2.5 # -2.5%
self.TAKE_PROFIT_PERCENT = 3.0 # +3%
self.DAILY_LOSS_LIMIT = -5 # -5% max
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
self.start_time = datetime.now()
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
# Telegram
self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
# Send startup message
self._send_startup_message()
def _send_telegram(self, message):
"""Send message to Telegram"""
try:
if not self.telegram_token or not self.telegram_chat_id:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
data = {
'chat_id': self.telegram_chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, data=data, timeout=5)
return response.status_code == 200
except Exception as e:
logger.error(f"Telegram Error: {e}")
return False
def _send_startup_message(self):
"""Send startup message with current strategy"""
message = """🤖 **TRADING BOT V5 — STARTED!**
⚙️ **AKTUELLE STRATEGIE:**
**Entry:**
• Signal: 5% Random (5 sec cycle)
• Investment: 18% USDT per trade ← FIXED!
• Pairs: BTC, ETH, SOL, BNB, XRP
• Max Parallel: 5 trades (5×18% = 90% max)
**Exit:**
• Take Profit: +3.0% ✅
• Stop Loss: -2.5% ✅
• Risk/Reward: 1:1.2
**Risk Management:**
• Daily Loss Limit: -5%
• Position Size Cap: 18%
• Buffer Reserve: 10% USDT
• SL Auto-Place: Ja (korrekt gerundet)
**Status:** 🟢 LIVE
• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
• Capital Ready: 100% USDT
---
Reports: Alle 3h via Telegram 📊"""
self._send_telegram(message)
logger.info("📱 Startup message sent to Telegram")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
if f['filterType'] == 'LOT_SIZE':
step = float(f['stepSize'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['step'] = step
self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
if f['filterType'] == 'NOTIONAL':
min_notional = float(f['minNotional'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['min_notional'] = min_notional
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size using Decimal"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
price_decimal = Decimal(str(price))
tick_decimal = Decimal(str(tick))
rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
return float(rounded)
def _round_quantity(self, qty, pair):
"""Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
step = self.pair_precision.get(pair, {}).get('step', 0.00001)
step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
qty_decimal = Decimal(str(qty))
step_decimal = Decimal(str(step))
# Round down (safe side)
rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
# Format as string with exactly the right decimals
format_str = f"0.{'':<{step_decimals}}"
if step_decimals == 0:
return int(rounded)
return float(rounded)
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
qty = self._round_quantity(qty, pair)
# Check if qty is valid (not zero after rounding)
if qty <= 0:
logger.warning(f"Quantity too small for {pair}: {qty}")
return False
# VALIDATE NOTIONAL (order_value must be >= 3.0 MINIMUM)
order_value = qty * entry_price
NOTIONAL_MIN = 5.0 # Minimum $3
if order_value < NOTIONAL_MIN:
logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${NOTIONAL_MIN:.2f} (qty={qty}, price={entry_price})")
return False
logger.info(f"✅ NOTIONAL Check Passed: {pair} ${order_value:.2f} >= ${NOTIONAL_MIN:.2f}")
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH CORRECT API METHOD)
await self.place_stop_loss(pair, entry_price, qty)
self.trades_today += 1
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision & API method"""
try:
# Calculate SL price with 2.5% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
qty_rounded = self._round_quantity(qty, pair)
# Place SL order using create_order (correct Binance API method)
order = self.client.create_order(
symbol=pair,
side='SELL',
type='STOP_LOSS_LIMIT',
timeInForce='GTC',
quantity=qty_rounded,
stopPrice=sl_price,
price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
)
logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in list(self.active_trades.keys()):
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
if pnl > 0:
self.wins_today += 1
else:
self.losses_today += 1
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
def get_performance_report(self):
"""Get current performance metrics"""
try:
account = self.client.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
# Get prices
prices = {}
for pair in self.PAIRS:
try:
ticker = self.client.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
prices['USDT'] = 1.0
# Calculate portfolio
portfolio = 0
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
for asset in tracked:
if asset in balance:
portfolio += balance[asset]['total'] * prices.get(asset, 0)
return {
'portfolio': round(portfolio, 2),
'usdt_free': balance.get('USDT', {}).get('free', 0),
'daily_pnl': self.daily_pnl,
'trades_today': self.trades_today,
'wins': self.wins_today,
'losses': self.losses_today,
'active_trades': len(self.active_trades),
'paused': self.paused
}
except Exception as e:
logger.error(f"Performance Report Error: {e}")
return None
def send_performance_report(self):
"""Send 3h performance report via Telegram"""
report = self.get_performance_report()
if not report:
return
win_rate = 0
if report['trades_today'] > 0:
win_rate = (report['wins'] / report['trades_today']) * 100
status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
message = f"""📊 **3H PERFORMANCE REPORT**
**Portfolio Status:**
• Total: ${report['portfolio']:.2f}
• USDT Free: ${report['usdt_free']:.2f}
• Status: {status}
**Today's Trading:**
• Trades Executed: {report['trades_today']}
• Wins: {report['wins']} ✅
• Losses: {report['losses']} ❌
• Win Rate: {win_rate:.1f}%
**P&L:**
• Daily P&L: ${report['daily_pnl']:.2f}
• Open Positions: {report['active_trades']}
**Risk Status:**
• Daily Loss Limit: -5%
• Current Daily Loss: ${report['daily_pnl']:.2f}
• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
---
Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Bot: V5 ENHANCED (FULLY FIXED)"""
self._send_telegram(message)
logger.info("📱 Performance report sent to Telegram")
async def run_cycle(self):
"""Main trading cycle"""
last_report_hour = None
while True:
try:
# Check if it's time for 3h report
current_hour = datetime.now().hour
if current_hour % 3 == 0 and last_report_hour != current_hour:
self.send_performance_report()
last_report_hour = current_hour
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,190 +0,0 @@
import asyncio, logging, joblib, time
from datetime import datetime
from src.config import get_config
from src.bot.binance_client import BinanceClientWrapper
from src.integrations.telegram_notifier import TelegramNotifier
from src.integrations.obsidian_logger import ObsidianLogger
from src.strategies.ml_strategy import MLStrategy
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class MLTradingBot:
def __init__(self, config, binance, telegram, obsidian, model, scaler):
self.config = config
self.binance = binance
self.telegram = telegram
self.obsidian = obsidian
self.model = model
self.scaler = scaler
self.strategy = MLStrategy(trading_pair=config.trading_pair)
# Trading state
self.last_report_time = time.time()
self.report_interval = 10800 # 3 HOURS (10800 seconds)
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
self.daily_pnl = 0.0
self.report_count = 0
async def get_market_data(self):
"""Fetch current market price and stats"""
try:
ticker = self.config.trading_pair.split('/')[0] # BTC from BTCUSDT
symbol = f"{ticker}USDT"
# Get current price
price_data = await self.binance.get_ticker_price(symbol)
if not price_data:
return None
current_price = float(price_data)
return {
'ticker': ticker,
'current_price': current_price,
'symbol': symbol
}
except Exception as e:
logger.error(f"Market data fetch error: {e}")
return None
async def get_account_balance(self):
"""Get current account balance"""
try:
balance = self.binance.get_balance('USDT')
if balance:
return {'USDT': {'total': balance}}
return {}
except Exception as e:
logger.error(f"Balance fetch error: {e}")
return {}
async def send_performance_report(self):
"""Send 3-hourly performance report"""
try:
self.report_count += 1
# Get market data
market = await self.get_market_data()
if not market:
logger.warning("No market data available")
return
# Get account balance
balances = await self.get_account_balance()
usdt_balance = balances.get('USDT', {}).get('total', 0)
# Build report
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')
report = f"""
📊 **PERFORMANCE REPORT #{self.report_count}** — {timestamp}
🎯 **MARKET STATUS:**
├─ {market['ticker']}/USDT: ${market['current_price']:,.2f}
├─ Trades Today: {self.trades_today}
├─ Wins: {self.wins_today} | Losses: {self.losses_today}
└─ Daily P&L: ${self.daily_pnl:+.2f}
💰 **ACCOUNT STATUS:**
├─ USDT Balance: ${usdt_balance:,.2f}
├─ Device: CPU
├─ Mode: Live Trading
└─ Strategy: ML (92% accuracy, 60% threshold)
📈 **BOT STATUS: RUNNING ✅**
"""
# Send to Telegram (FIXED — now actually sends!)
success = await self.telegram.send_alert(report.strip())
if success:
logger.info(f"✅ Performance report #{self.report_count} sent to Telegram")
else:
logger.warning(f"❌ Failed to send report #{self.report_count} to Telegram")
except Exception as e:
logger.error(f"Report error: {e}")
async def monitor_trades(self):
"""Monitor open trades and check signals"""
try:
symbol = f"{self.config.trading_pair.split('/')[0]}USDT"
orders = self.binance.get_open_orders(symbol)
if orders and len(orders) > 0:
logger.info(f"📈 Open orders: {len(orders)}")
except Exception as e:
logger.debug(f"Trade monitoring: {e}")
async def run(self):
"""Main bot loop"""
logger.info(f"🤖 Starting ML Trading Bot — {self.config.trading_pair}")
startup_msg = f"""🤖 **BOT STARTED - V2 ML ADAPTIVE**
✅ Strategy: ML Adaptive (60% threshold)
✅ Models: BTC 92% accuracy
✅ Device: CPU (Live)
✅ Reporting: EVERY 3 HOURS
✅ Status: ACTIVE & MONITORING"""
await self.telegram.send_alert(startup_msg)
logger.info("✅ Startup message sent to Telegram")
logger.info("🟢 Bot running — sending reports every 3 hours...")
while True:
try:
current_time = time.time()
# Send 3-hourly performance report
if (current_time - self.last_report_time) >= self.report_interval:
logger.info(f"⏰ Time for Report #{self.report_count + 1}")
await self.send_performance_report()
self.last_report_time = current_time
# Monitor trades every 5 minutes
await self.monitor_trades()
# Sleep for 5 minutes
await asyncio.sleep(60) # Check every 1 min instead of 5 min for trading opportunities
except KeyboardInterrupt:
logger.info("Bot interrupted by user")
break
except Exception as e:
logger.error(f"Bot error: {e}")
try:
await self.telegram.send_alert(f"❌ Bot Error: {str(e)[:100]}")
except:
pass
await asyncio.sleep(60)
async def main():
config = get_config()
if config.environment == 'testnet':
api_key, api_secret = config.binance_api_key_testnet, config.binance_api_secret_testnet
else:
api_key, api_secret = config.binance_api_key_live, config.binance_api_secret_live
binance = BinanceClientWrapper(api_key=api_key, api_secret=api_secret, testnet=(config.environment=='testnet'))
telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
try:
# Load BTC model
model = joblib.load('/tmp/model_BTC.pkl')
scaler = joblib.load('/tmp/scaler_BTC.pkl')
logger.info(f'✅ ML Model loaded: BTC (92% accuracy)')
except Exception as e:
logger.error(f'❌ ML Model Error: {e}')
return
bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
await bot.run()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,590 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V0.2 — Adaptive Strategy Learning
Implementiert: SL, TP, Daily Limit, R:R Ratio
FIXED: Binance API method (order_take_profit → create_order)
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
FIXED: Quantity rounding mit Decimal (no floating point errors)
FIXED: Quantity string formatting für Binance
NEW: Startup Message + 3h Performance Reports via Telegram
"""
import os, asyncio, logging, random, json, time, math, requests
from decimal import Decimal, ROUND_DOWN
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 7.5 # 7-8% range (midpoint 7.5%) # 5% random signal
self.INVESTMENT_PERCENT = 50 # 50% (single position for liquidity) (single position)
self.INVESTMENT_PERCENT_HIGH = 55 # 55% when confidence > 85% > 85%
self.CONFIDENCE_THRESHOLD = 85 # Min confidence for high investment # 35% per trade (5 parallel = 90% max, 10% buffer)
self.NOTIONAL_MIN = 5.0 # Override Binance minimum to $3
self.STOP_LOSS_PERCENT = 1.8 # -2.5%
self.TAKE_PROFIT_PERCENT = 2.8 # +3%
self.DAILY_LOSS_LIMIT = -5
# Trailing Stop
self.TRAILING_STOP_ENTRY = 1.5 # Activate trailing stop at +1.5%
self.TRAILING_STOP_DISTANCE = 0.6 # 0.6% distance
# Position & Trade Limits
self.MAX_OPEN_POSITIONS = 1 # Single position for max liquidity # Max concurrent trades
self.MAX_CONSECUTIVE_LOSSES = 3 # Stop after 3 losses
self.CONSECUTIVE_LOSS_COOLDOWN = 30 * 60 # 30 minutes in seconds
self.MAX_TRADES_PER_DAY = 15
self.MIN_WIN_PROBABILITY = 75 # Min expected win %
# Tracking
self.consecutive_losses = 0
self.last_loss_time = None
self.trades_today = 0
self.last_trade_reset = None # -5% max
# Profit tracking
self.entry_price_history = {} # symbol -> entry price
self.closed_trades = [] # list of {symbol, entry, exit, profit_pct, profit_usdt}
self.session_start_balance = None
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
# ADAPTIVE TRACKING (Option 2: Win Rate based Strategy)
self.total_trades = 0
self.total_wins = 0
self.total_losses = 0
self.last_win_rate = 50.0 # Start neutral
self.strategy_version = 1
self.start_time = datetime.now()
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
# Telegram
self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
logger.info(f"✅ Bot initialized with Risk Management (SL {self.STOP_LOSS_PERCENT}%, TP {self.TAKE_PROFIT_PERCENT}%, Daily Limit {-self.DAILY_LOSS_LIMIT}%, Max Pos: {self.MAX_OPEN_POSITIONS})")
# Send startup message
self._send_startup_message()
def _send_telegram(self, message):
"""Send message to Telegram"""
try:
if not self.telegram_token or not self.telegram_chat_id:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
data = {
'chat_id': self.telegram_chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, data=data, timeout=5)
return response.status_code == 200
except Exception as e:
logger.error(f"Telegram Error: {e}")
return False
def _send_startup_message(self):
"""Send startup message with current strategy"""
message = """🤖 **TRADING BOT V0.2 — STARTED!**
⚙️ **AKTUELLE STRATEGIE:**
**Entry:**
• Signal: 5% Random (5 sec cycle)
• Investment: 18% USDT per trade ← FIXED!
• Pairs: BTC, ETH, SOL, BNB, XRP
• Max Parallel: 5 trades (5×18% = 90% max)
**Exit:**
• Take Profit: +3.0% ✅
• Stop Loss: -2.5% ✅
• Risk/Reward: 1:1.2
**Risk Management:**
• Daily Loss Limit: -5%
• Position Size Cap: 18%
• Buffer Reserve: 10% USDT
• SL Auto-Place: Ja (korrekt gerundet)
**Status:** 🟢 LIVE
• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
• Capital Ready: 100% USDT
---
Reports: Alle 3h via Telegram 📊"""
self._send_telegram(message)
logger.info("📱 Startup message sent to Telegram")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
if f['filterType'] == 'LOT_SIZE':
step = float(f['stepSize'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['step'] = step
self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
if f['filterType'] == 'NOTIONAL':
min_notional = float(f['minNotional'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['min_notional'] = min_notional
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size using Decimal"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
price_decimal = Decimal(str(price))
tick_decimal = Decimal(str(tick))
rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
return float(rounded)
def _round_quantity(self, qty, pair):
"""Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
step = self.pair_precision.get(pair, {}).get('step', 0.00001)
step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
qty_decimal = Decimal(str(qty))
step_decimal = Decimal(str(step))
# Round down (safe side)
rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
# Format as string with exactly the right decimals
format_str = f"0.{'':<{step_decimals}}"
if step_decimals == 0:
return int(rounded)
return float(rounded)
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
qty = self._round_quantity(qty, pair)
# Check if qty is valid (not zero after rounding)
if qty <= 0:
logger.warning(f"Quantity too small for {pair}: {qty}")
return False
# VALIDATE NOTIONAL (order_value must be >= 3.0 MINIMUM)
order_value = qty * entry_price
NOTIONAL_MIN = 5.0 # Minimum $3
if order_value < NOTIONAL_MIN:
logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${NOTIONAL_MIN:.2f} (qty={qty}, price={entry_price})")
return False
logger.info(f"✅ NOTIONAL Check Passed: {pair} ${order_value:.2f} >= ${NOTIONAL_MIN:.2f}")
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH CORRECT API METHOD)
await self.place_stop_loss(pair, entry_price, qty)
self.trades_today += 1
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision & API method"""
try:
# Calculate SL price with {self.STOP_LOSS_PERCENT}% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
qty_rounded = self._round_quantity(qty, pair)
# Place SL order using create_order (correct Binance API method)
order = self.client.create_order(
symbol=pair,
side='SELL',
type='STOP_LOSS_LIMIT',
timeInForce='GTC',
quantity=qty_rounded,
stopPrice=sl_price,
price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
)
logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in list(self.active_trades.keys()):
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
if pnl > 0:
self.wins_today += 1
else:
self.losses_today += 1
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
def get_performance_report(self):
"""Get current performance metrics"""
try:
account = self.client.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
# Get prices
prices = {}
for pair in self.PAIRS:
try:
ticker = self.client.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
prices['USDT'] = 1.0
# Calculate portfolio
portfolio = 0
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
for asset in tracked:
if asset in balance:
portfolio += balance[asset]['total'] * prices.get(asset, 0)
return {
'portfolio': round(portfolio, 2),
'usdt_free': balance.get('USDT', {}).get('free', 0),
'daily_pnl': self.daily_pnl,
'trades_today': self.trades_today,
'wins': self.wins_today,
'losses': self.losses_today,
'active_trades': len(self.active_trades),
'paused': self.paused
}
except Exception as e:
logger.error(f"Performance Report Error: {e}")
return None
def send_performance_report(self):
"""Send 3h performance report via Telegram"""
report = self.get_performance_report()
if not report:
return
win_rate = 0
if report['trades_today'] > 0:
win_rate = (report['wins'] / report['trades_today']) * 100
status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
message = f"""📊 **3H PERFORMANCE REPORT**
**Portfolio Status:**
• Total: ${report['portfolio']:.2f}
• USDT Free: ${report['usdt_free']:.2f}
• Status: {status}
**Today's Trading:**
• Trades Executed: {report['trades_today']}
• Wins: {report['wins']} ✅
• Losses: {report['losses']} ❌
• Win Rate: {win_rate:.1f}%
**P&L:**
• Daily P&L: ${report['daily_pnl']:.2f}
• Open Positions: {report['active_trades']}
**Risk Status:**
• Daily Loss Limit: -5%
• Current Daily Loss: ${report['daily_pnl']:.2f}
• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
---
Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Bot: V0.2 Adaptive"""
self._send_telegram(message)
logger.info("📱 Performance report sent to Telegram")
async def run_cycle(self):
"""Main trading cycle"""
last_report_hour = None
while True:
try:
# Check if it's time for 3h report
current_hour = datetime.now().hour
if current_hour % 3 == 0 and last_report_hour != current_hour:
self.send_performance_report()
last_report_hour = current_hour
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())
def get_signal_confidence(self):
"""Calculate confidence level for current signal (0-100%)"""
# This can be enhanced with actual ML model
# For now: random 30-95%
import random
return random.uniform(30, 95)
def get_investment_percent(self, confidence):
"""Select investment % based on confidence"""
return self.INVESTMENT_PERCENT_HIGH if confidence > self.CONFIDENCE_THRESHOLD else self.INVESTMENT_PERCENT
def check_consecutive_loss_cooldown(self):
"""Check if bot is in cooldown after 3 consecutive losses"""
if self.consecutive_losses >= self.MAX_CONSECUTIVE_LOSSES:
if self.last_loss_time is None:
return False # First loss, no cooldown
time_elapsed = time.time() - self.last_loss_time
if time_elapsed < self.CONSECUTIVE_LOSS_COOLDOWN:
logger.warning(f"🚫 Cooldown active: {int(self.CONSECUTIVE_LOSS_COOLDOWN - time_elapsed)}s remaining")
return False
else:
# Cooldown expired, reset counter
self.consecutive_losses = 0
logger.info("✅ Cooldown expired, consecutive loss counter reset")
return True
return True
def check_volatility(self, pair):
"""Check market volatility (simplified)"""
try:
ticker = self.client.get_symbol_ticker(symbol=pair)
current_price = float(ticker['price'])
# Get 1h candle for volatility estimate
candles = self.client.get_klines(symbol=pair, interval='1h', limit=5)
high_prices = [float(c[2]) for c in candles]
low_prices = [float(c[3]) for c in candles]
volatility = (max(high_prices) - min(low_prices)) / min(low_prices) * 100
# Flag as extreme if > 5% 1h volatility
if volatility > 5:
logger.warning(f"⚠️ High volatility {pair}: {volatility:.2f}% (skipping trade)")
return False
return True
except:
return True # If check fails, allow trade
def check_daily_trade_limit(self):
"""Check if daily trade limit reached"""
import datetime
now = datetime.datetime.now()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
if self.last_trade_reset is None or self.last_trade_reset < today_start:
self.trades_today = 0
self.last_trade_reset = now
if self.trades_today >= self.MAX_TRADES_PER_DAY:
logger.warning(f"⚠️ Daily limit reached: {self.trades_today}/{self.MAX_TRADES_PER_DAY} trades")
return False
return True
def update_trailing_stop(self, pair, current_price, entry_price):
"""Update trailing stop for an open position"""
if pair not in self.active_trades:
return False
trade_data = self.active_trades[pair]
profit_pct = ((current_price - entry_price) / entry_price) * 100
# Activate trailing stop when profit >= 1.5%
if profit_pct >= self.TRAILING_STOP_ENTRY:
trailing_stop_price = current_price * (1 - self.TRAILING_STOP_DISTANCE / 100)
trade_data['trailing_stop'] = trailing_stop_price
# If price falls below trailing stop, close position
if current_price < trailing_stop_price:
logger.info(f"🛑 Trailing stop triggered {pair}: Sell @ ${current_price:.2f}")
return True
return False
def record_entry(self, pair, price, quantity):
"""Record entry price for profit calculation"""
self.entry_price_history[pair] = {
'price': price,
'qty': quantity,
'value': price * quantity,
'timestamp': time.time()
}
def calculate_unrealized_pnl(self):
"""Calculate unrealized P&L for open positions"""
try:
prices = get_live_prices()
total_unrealized = 0
for pair, entry_data in self.entry_price_history.items():
asset = pair.replace('USDT', '')
current_price = prices.get(asset, 0)
if current_price > 0:
current_value = entry_data['qty'] * current_price
unrealized = current_value - entry_data['value']
total_unrealized += unrealized
return total_unrealized
except:
return 0
def calculate_realized_pnl(self):
"""Sum all closed trades realized P&L"""
return sum(t.get('profit_usdt', 0) for t in self.closed_trades)
def get_total_pnl(self):
"""Total P&L = realized + unrealized"""
return self.calculate_realized_pnl() + self.calculate_unrealized_pnl()

View File

@ -1,220 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes
Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio
"""
import os, asyncio, logging, random, json, time
from datetime import datetime, timedelta
from binance.client import Client
from binance.exceptions import BinanceAPIException
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load config
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBotV5Enhanced:
def __init__(self):
self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.state_file = '/home/marc/bot-deploy/trades.json'
self.load_state()
# NEW: Risk Management Settings
self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%)
self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1%
self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily
self.MIN_RISK_REWARD = 1.5 # Min R:R ratio
self.MAX_POSITION_PERCENT = 25 # Max 25% per trade
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
def load_state(self):
if os.path.exists(self.state_file):
with open(self.state_file) as f:
self.state = json.load(f)
else:
self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0}
def save_state(self):
with open(self.state_file, 'w') as f:
json.dump(self.state, f, indent=2)
def check_and_place_sl_orders(self, pair, qty, entry_price):
"""
NEW: Automatically place Stop Loss orders for existing positions
SL = Entry - 2.5%
"""
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
try:
# Check if already has SL order
orders = self.binance.get_open_orders(symbol=pair)
has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders)
if not has_sl:
# Place SL order
order = self.binance.order_limit_sell(
symbol=pair,
quantity=qty,
price=round(sl_price, 8)
)
logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}")
return True
except Exception as e:
logger.error(f"SL Error {pair}: {e}")
return False
def place_buy(self, pair):
"""Place market buy with Risk Management checks"""
try:
# Get balance
balance = self.binance.get_account()
usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0])
# NEW: Daily loss check
daily_loss = self.calculate_daily_loss()
if daily_loss <= -self.DAILY_LOSS_LIMIT:
logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)")
return None
# Calculate position size (25% of USDT)
qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100)
if qty_usdt < 10: # Binance minimum
return None
# Get current price
ticker = self.binance.get_symbol_info(pair)
price = float(self.binance.get_ticker(symbol=pair)['lastPrice'])
# Calculate quantity with LOT_SIZE filter
lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE')
step_size = float(lot_filter['stepSize'])
qty = float(int(qty_usdt / price / step_size) * step_size)
if qty < float(lot_filter['minQty']):
return None
# Place market buy
order = self.binance.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}")
# NEW: Auto-place Stop Loss
self.check_and_place_sl_orders(pair, qty, price)
return order
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return None
def check_take_profit(self):
"""NEW: Check and close at +3% TP with SL protection"""
try:
balance = self.binance.get_account()
for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
ticker = self.binance.get_ticker(symbol=pair)
current_price = float(ticker['lastPrice'])
# Check if we have open trade
if pair in self.state['current']:
entry_price = self.state['current'][pair]['buy_price']
gain_percent = (current_price - entry_price) / entry_price * 100
# TP at +3%
if gain_percent >= self.TAKE_PROFIT_PERCENT:
qty = self.state['current'][pair]['qty']
try:
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
profit_usd = (current_price - entry_price) * qty
logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}")
# Record completion
self.state['completed'].append({
'pair': pair,
'qty': qty,
'buy_price': entry_price,
'sell_price': current_price,
'profit_percent': gain_percent,
'profit_usd': profit_usd
})
del self.state['current'][pair]
self.save_state()
except Exception as e:
logger.error(f"TP sell error {pair}: {e}")
# SL at -2.5% (auto-cancelled by limit order but check anyway)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
qty = self.state['current'][pair]['qty']
try:
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
loss_usd = (current_price - entry_price) * qty
logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}")
self.state['completed'].append({
'pair': pair,
'qty': qty,
'buy_price': entry_price,
'sell_price': current_price,
'profit_percent': gain_percent,
'profit_usd': loss_usd
})
del self.state['current'][pair]
self.save_state()
except Exception as e:
logger.error(f"SL sell error {pair}: {e}")
except Exception as e:
logger.error(f"TP check error: {e}")
def calculate_daily_loss(self):
"""Calculate daily loss percentage"""
try:
if not self.state['completed']:
return 0
today_trades = [t for t in self.state['completed']
if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()]
daily_loss = sum(t.get('profit_usd', 0) for t in today_trades)
balance = self.binance.get_account()
portfolio = sum(float(a['free']) for a in balance['balances'])
loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0
return loss_percent
except:
return 0
async def run(self):
"""Main trading loop"""
logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)")
while True:
try:
# Check exits first (TP/SL)
self.check_take_profit()
# Generate signal (5% probability)
if random.random() < 0.05:
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
if pair not in self.state['current']:
self.place_buy(pair)
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Loop error: {e}")
await asyncio.sleep(5)
if __name__ == "__main__":
bot = TradingBotV5Enhanced()
asyncio.run(bot.run())

View File

@ -1,262 +0,0 @@
#!/usr/bin/env python3
"""Trading Bot v0.4 Hybrid - Dynamic Position Sizing + Trade Recovery"""
import os, json, time, logging
from datetime import datetime
from dotenv import load_dotenv
from binance.client import Client
from binance.exceptions import BinanceAPIException
logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(message)s')
logger = logging.getLogger()
load_dotenv()
API_KEY = os.getenv('BINANCE_API_KEY_LIVE')
API_SECRET = os.getenv('BINANCE_API_SECRET_LIVE')
if not API_KEY or not API_SECRET:
logger.error("Missing API keys")
exit(1)
SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'XRPUSDT', 'SOLUSDT']
TRACKED_COINS = ['BTC', 'ETH', 'BNB', 'XRP', 'SOL']
MIN_TRADE_USDT = 12.00
MAX_POSITION_PCT = 0.07
TAKE_PROFIT_PCT = 0.015
STOP_LOSS_PCT = -0.008
CYCLE_SEC = 60
class TradingBotV04:
def __init__(self):
self.client = Client(API_KEY, API_SECRET)
self.price_history = {sym: [] for sym in SYMBOLS}
self.active_trades = {}
self.portfolio_value = 0
self.max_trade_usdt = 0
# TRADE RECOVERY: Recover orphaned trades from holdings
try:
account = self.client.get_account()
for b in account['balances']:
asset = b['asset']
free = float(b['free'])
if asset in TRACKED_COINS and free > 0.0001:
symbol = asset + 'USDT'
try:
price = self.get_current_price(symbol)
if price:
self.active_trades[symbol] = {
'entry_price': price,
'qty': free,
'entry_time': datetime.now().isoformat()
}
logger.info(f"[RECOVERED] {symbol} {free} @ {price}")
except:
pass
except Exception as e:
logger.warning(f"Recovery failed: {e}")
logger.info("[v0.4 INIT] Bot | Dynamic Sizing (Min 12 + 7%)")
def get_fresh_balance(self):
try:
account = self.client.get_account()
portfolio_value = 0
prices = {'USDT': 1.0}
for symbol in SYMBOLS:
try:
ticker = self.client.get_ticker(symbol=symbol)
coin = symbol.replace('USDT', '')
prices[coin] = float(ticker['lastPrice'])
except:
pass
for balance in account['balances']:
asset = balance['asset']
free = float(balance['free'])
if asset in TRACKED_COINS:
price = prices.get(asset, 0)
portfolio_value += free * price
elif asset == 'USDT':
portfolio_value += free
usdt_available = next((float(b['free']) for b in account['balances'] if b['asset'] == 'USDT'), 0)
self.portfolio_value = portfolio_value
self.max_trade_usdt = portfolio_value * MAX_POSITION_PCT
logger.info(f"[v0.4] USDT={usdt_available:.2f} | Portfolio={portfolio_value:.2f} | Max={self.max_trade_usdt:.2f}")
return usdt_available, portfolio_value
except:
return 0, 0
def get_current_price(self, symbol):
try:
ticker = self.client.get_ticker(symbol=symbol)
return float(ticker['lastPrice'])
except:
return None
def calculate_valid_quantity(self, symbol, usdt_amount):
try:
price = self.get_current_price(symbol)
if not price or price <= 0:
return 0
info = self.client.get_symbol_info(symbol)
if not info:
return 0
step_size = None
for f in info.get('filters', []):
if f['filterType'] == 'LOT_SIZE':
step_size = float(f['stepSize'])
break
if not step_size or step_size <= 0:
return 0
qty = usdt_amount / price
qty = int(qty / step_size) * step_size
if qty * price < 5.0:
return 0
return qty
except:
return 0
def is_local_minimum(self, symbol):
if len(self.price_history[symbol]) < 5:
return False
recent = self.price_history[symbol][-5:]
current = recent[-1]
is_min = all(current < p for p in recent[:-1])
if is_min:
logger.info(f"[SIGNAL] Local min: {symbol} @ {current}")
return is_min
def place_buy_order(self, symbol, usdt_amount):
try:
qty = self.calculate_valid_quantity(symbol, usdt_amount)
if qty <= 0:
return None
price = self.get_current_price(symbol)
if not price:
return None
order = self.client.order_market_buy(symbol=symbol, quantity=qty)
self.active_trades[symbol] = {
'entry_price': price,
'qty': qty,
'order_id': order.get('orderId'),
'entry_time': datetime.now().isoformat()
}
pos_pct = (qty * price / self.portfolio_value * 100) if self.portfolio_value > 0 else 0
logger.info(f"[BUY] {symbol} {qty} @ {price} | Pos: {pos_pct:.1}% [v0.4 HYBRID]")
return order
except:
return None
def check_and_close_positions(self):
for symbol, trade in list(self.active_trades.items()):
try:
current = self.get_current_price(symbol)
if not current:
continue
entry = trade['entry_price']
qty = trade['qty']
pnl_pct = ((current - entry) / entry) * 100
if pnl_pct >= TAKE_PROFIT_PCT * 100:
logger.info(f"[SELL-TP] {symbol} @ {current} | +{pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
elif pnl_pct <= STOP_LOSS_PCT * 100:
logger.info(f"[SELL-SL] {symbol} @ {current} | {pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
except:
pass
def run_cycle(self):
logger.info("="*70)
usdt_free, portfolio_val = self.get_fresh_balance()
if usdt_free < MIN_TRADE_USDT:
logger.warning(f"Low capital: {usdt_free:.2f} < {MIN_TRADE_USDT}")
logger.info("="*70)
return
self.check_and_close_positions()
for symbol in SYMBOLS:
price = self.get_current_price(symbol)
if price:
self.price_history[symbol].append(price)
if len(self.price_history[symbol]) > 20:
self.price_history[symbol].pop(0)
best_signal = None
for symbol in SYMBOLS:
if symbol not in self.active_trades and self.is_local_minimum(symbol):
best_signal = symbol
break
if best_signal and usdt_free >= MIN_TRADE_USDT:
trade_amount = min(max(MIN_TRADE_USDT, self.max_trade_usdt), usdt_free * 0.5)
self.place_buy_order(best_signal, trade_amount)
logger.info(f"[CYCLE-END] Trades: {len(self.active_trades)} | USDT: {usdt_free:.2f} | Portfolio: {portfolio_val:.2f}")
try:
temp = '/home/marc/bot-deploy/active_trades.json.tmp'
with open(temp, 'w') as f:
json.dump({
'active_trades': self.active_trades,
'count': len(self.active_trades),
'portfolio_value': round(portfolio_val, 2),
'max_trade_usdt': round(self.max_trade_usdt, 2),
'timestamp': datetime.now().isoformat(),
'version': 'v0.4-hybrid'
}, f)
os.replace(temp, '/home/marc/bot-deploy/active_trades.json')
except Exception as e:
logger.warning(f"Save failed: {e}")
logger.info("="*70)
if __name__ == '__main__':
import sys
bot = TradingBotV04()
if len(sys.argv) > 1 and sys.argv[1] == '--once':
bot.run_cycle()
else:
logger.info("[v0.4 START] Bot cycle loop...")
while True:
try:
bot.run_cycle()
except Exception as e:
logger.error(f"Error: {e}")
time.sleep(CYCLE_SEC)

View File

@ -1,220 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes
Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio
"""
import os, asyncio, logging, random, json, time
from datetime import datetime, timedelta
from binance.client import Client
from binance.exceptions import BinanceAPIException
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load config
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBotV5Enhanced:
def __init__(self):
self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.state_file = '/home/marc/bot-deploy/trades.json'
self.load_state()
# NEW: Risk Management Settings
self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%)
self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1%
self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily
self.MIN_RISK_REWARD = 1.5 # Min R:R ratio
self.MAX_POSITION_PERCENT = 25 # Max 25% per trade
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
def load_state(self):
if os.path.exists(self.state_file):
with open(self.state_file) as f:
self.state = json.load(f)
else:
self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0}
def save_state(self):
with open(self.state_file, 'w') as f:
json.dump(self.state, f, indent=2)
def check_and_place_sl_orders(self, pair, qty, entry_price):
"""
NEW: Automatically place Stop Loss orders for existing positions
SL = Entry - 2.5%
"""
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
try:
# Check if already has SL order
orders = self.binance.get_open_orders(symbol=pair)
has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders)
if not has_sl:
# Place SL order
order = self.binance.order_limit_sell(
symbol=pair,
quantity=qty,
price=round(sl_price, 8)
)
logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}")
return True
except Exception as e:
logger.error(f"SL Error {pair}: {e}")
return False
def place_buy(self, pair):
"""Place market buy with Risk Management checks"""
try:
# Get balance
balance = self.binance.get_account()
usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0])
# NEW: Daily loss check
daily_loss = self.calculate_daily_loss()
if daily_loss <= -self.DAILY_LOSS_LIMIT:
logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)")
return None
# Calculate position size (25% of USDT)
qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100)
if qty_usdt < 10: # Binance minimum
return None
# Get current price
ticker = self.binance.get_symbol_info(pair)
price = float(self.binance.get_ticker(symbol=pair)['lastPrice'])
# Calculate quantity with LOT_SIZE filter
lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE')
step_size = float(lot_filter['stepSize'])
qty = float(int(qty_usdt / price / step_size) * step_size)
if qty < float(lot_filter['minQty']):
return None
# Place market buy
order = self.binance.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}")
# NEW: Auto-place Stop Loss
self.check_and_place_sl_orders(pair, qty, price)
return order
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return None
def check_take_profit(self):
"""NEW: Check and close at +3% TP with SL protection"""
try:
balance = self.binance.get_account()
for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
ticker = self.binance.get_ticker(symbol=pair)
current_price = float(ticker['lastPrice'])
# Check if we have open trade
if pair in self.state['current']:
entry_price = self.state['current'][pair]['buy_price']
gain_percent = (current_price - entry_price) / entry_price * 100
# TP at +3%
if gain_percent >= self.TAKE_PROFIT_PERCENT:
qty = self.state['current'][pair]['qty']
try:
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
profit_usd = (current_price - entry_price) * qty
logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}")
# Record completion
self.state['completed'].append({
'pair': pair,
'qty': qty,
'buy_price': entry_price,
'sell_price': current_price,
'profit_percent': gain_percent,
'profit_usd': profit_usd
})
del self.state['current'][pair]
self.save_state()
except Exception as e:
logger.error(f"TP sell error {pair}: {e}")
# SL at -2.5% (auto-cancelled by limit order but check anyway)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
qty = self.state['current'][pair]['qty']
try:
order = self.binance.order_market_sell(symbol=pair, quantity=qty)
loss_usd = (current_price - entry_price) * qty
logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}")
self.state['completed'].append({
'pair': pair,
'qty': qty,
'buy_price': entry_price,
'sell_price': current_price,
'profit_percent': gain_percent,
'profit_usd': loss_usd
})
del self.state['current'][pair]
self.save_state()
except Exception as e:
logger.error(f"SL sell error {pair}: {e}")
except Exception as e:
logger.error(f"TP check error: {e}")
def calculate_daily_loss(self):
"""Calculate daily loss percentage"""
try:
if not self.state['completed']:
return 0
today_trades = [t for t in self.state['completed']
if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()]
daily_loss = sum(t.get('profit_usd', 0) for t in today_trades)
balance = self.binance.get_account()
portfolio = sum(float(a['free']) for a in balance['balances'])
loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0
return loss_percent
except:
return 0
async def run(self):
"""Main trading loop"""
logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)")
while True:
try:
# Check exits first (TP/SL)
self.check_take_profit()
# Generate signal (5% probability)
if random.random() < 0.05:
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
if pair not in self.state['current']:
self.place_buy(pair)
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Loop error: {e}")
await asyncio.sleep(5)
if __name__ == "__main__":
bot = TradingBotV5Enhanced()
asyncio.run(bot.run())

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 ENHANCED - Risk Management FIXED
Implementiert: SL (mit korrekter Precision), TP, Daily Limit, R:R Ratio
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
"""
import os, asyncio, logging, random, json, time, math
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 5 # 5% random signal
self.INVESTMENT_PERCENT = 25 # 25% per trade
self.STOP_LOSS_PERCENT = 2.5 # -2.5%
self.TAKE_PROFIT_PERCENT = 3.0 # +3%
self.DAILY_LOSS_LIMIT = -5 # -5% max
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
return round(price / tick) * tick
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${entry_price:.2f}")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH ROUNDING)
await self.place_stop_loss(pair, entry_price, qty)
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision"""
try:
# Calculate SL price with 2.5% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# Place SL order
order = self.client.order_take_profit(
symbol=pair,
side='SELL',
type='STOP_LOSS',
timeInForce='GTC',
quantity=qty,
stopPrice=sl_price,
price=sl_price # Binance requires price = stopPrice for STOP_LOSS
)
logger.info(f"🛡️ SL: {pair} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in self.active_trades.keys():
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
async def run_cycle(self):
"""Main trading cycle"""
while True:
try:
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,682 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V0.2 Adaptive Strategy Learning
Implementiert: SL, TP, Daily Limit, R:R Ratio
FIXED: Binance API method (order_take_profit create_order)
FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
FIXED: Quantity rounding mit Decimal (no floating point errors)
FIXED: Quantity string formatting für Binance
NEW: Startup Message + 3h Performance Reports via Telegram
"""
import os, asyncio, logging, random, json, time, math, requests
from decimal import Decimal, ROUND_DOWN
from binance.client import Client
from binance.exceptions import BinanceAPIException
from datetime import datetime, timedelta
# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Load env
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
class TradingBot:
def __init__(self):
self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
self.SIGNAL_THRESHOLD = 7.5 # 7-8% range (midpoint 7.5%) # 5% random signal
self.INVESTMENT_PERCENT = 50 # 50% (single position for liquidity) (single position)
self.INVESTMENT_PERCENT_HIGH = 55 # 55% when confidence > 85% > 85%
self.CONFIDENCE_THRESHOLD = 85 # Min confidence for high investment # 35% per trade (5 parallel = 90% max, 10% buffer)
self.NOTIONAL_MIN = 5.0 # Override Binance minimum to $3
self.STOP_LOSS_PERCENT = 1.8 # -2.5%
self.TAKE_PROFIT_PERCENT = 2.8 # +3%
self.DAILY_LOSS_LIMIT = -5
# Trailing Stop
self.TRAILING_STOP_ENTRY = 1.5 # Activate trailing stop at +1.5%
self.TRAILING_STOP_DISTANCE = 0.6 # 0.6% distance
# Position & Trade Limits
self.MAX_OPEN_POSITIONS = 1 # Single position for max liquidity # Max concurrent trades
self.MAX_CONSECUTIVE_LOSSES = 3 # Stop after 3 losses
self.CONSECUTIVE_LOSS_COOLDOWN = 30 * 60 # 30 minutes in seconds
self.MAX_TRADES_PER_DAY = 15
self.MIN_WIN_PROBABILITY = 75 # Min expected win %
# Tracking
self.consecutive_losses = 0
self.last_loss_time = None
self.trades_today = 0
self.last_trade_reset = None # -5% max
# Profit tracking
self.entry_price_history = {} # symbol -> entry price
self.closed_trades = [] # list of {symbol, entry, exit, profit_pct, profit_usdt}
self.session_start_balance = None
self.active_trades = {}
self.daily_pnl = 0
self.paused = False
# ADAPTIVE TRACKING (Option 2: Win Rate based Strategy)
self.total_trades = 0
self.total_wins = 0
self.total_losses = 0
self.last_win_rate = 50.0 # Start neutral
self.strategy_version = 1
self.start_time = datetime.now()
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
# Precision cache
self.pair_precision = {}
self._load_pair_precision()
# Telegram
self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
logger.info(f"✅ Bot initialized with Risk Management (SL {self.STOP_LOSS_PERCENT}%, TP {self.TAKE_PROFIT_PERCENT}%, Daily Limit {-self.DAILY_LOSS_LIMIT}%, Max Pos: {self.MAX_OPEN_POSITIONS})")
# Send startup message
self._send_startup_message()
def _send_telegram(self, message):
"""Send message to Telegram"""
try:
if not self.telegram_token or not self.telegram_chat_id:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
data = {
'chat_id': self.telegram_chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, data=data, timeout=5)
return response.status_code == 200
except Exception as e:
logger.error(f"Telegram Error: {e}")
return False
def _send_startup_message(self):
"""Send startup message with current strategy"""
message = """🤖 **TRADING BOT V0.2 — STARTED!**
**AKTUELLE STRATEGIE:**
**Entry:**
Signal: 5% Random (5 sec cycle)
Investment: 18% USDT per trade FIXED!
Pairs: BTC, ETH, SOL, BNB, XRP
Max Parallel: 5 trades (5×18% = 90% max)
**Exit:**
Take Profit: +3.0%
Stop Loss: -2.5%
Risk/Reward: 1:1.2
**Risk Management:**
Daily Loss Limit: -5%
Position Size Cap: 18%
Buffer Reserve: 10% USDT
SL Auto-Place: Ja (korrekt gerundet)
**Status:** 🟢 LIVE
Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
Capital Ready: 100% USDT
---
Reports: Alle 3h via Telegram 📊"""
self._send_telegram(message)
logger.info("📱 Startup message sent to Telegram")
def _load_pair_precision(self):
"""Load Binance precision rules for each pair"""
for pair in self.PAIRS:
try:
info = self.client.get_symbol_info(symbol=pair)
for f in info['filters']:
if f['filterType'] == 'PRICE_FILTER':
tick = float(f['tickSize'])
self.pair_precision[pair] = {
'tick': tick,
'decimals': self._get_decimals(tick)
}
if f['filterType'] == 'LOT_SIZE':
step = float(f['stepSize'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['step'] = step
self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
if f['filterType'] == 'NOTIONAL':
min_notional = float(f['minNotional'])
if pair not in self.pair_precision:
self.pair_precision[pair] = {}
self.pair_precision[pair]['min_notional'] = min_notional
except Exception as e:
logger.error(f"Precision load {pair}: {e}")
def _get_decimals(self, tick):
"""Get decimal places from tick size"""
s = str(tick)
if 'e' in s:
return int(s.split('e-')[1]) if 'e-' in s else 0
return len(s.split('.')[1]) if '.' in s else 0
def _round_to_tick(self, price, pair):
"""Round price to Binance tick size using Decimal"""
tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
price_decimal = Decimal(str(price))
tick_decimal = Decimal(str(tick))
rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
return float(rounded)
def _round_quantity(self, qty, pair):
"""Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
step = self.pair_precision.get(pair, {}).get('step', 0.00001)
step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
qty_decimal = Decimal(str(qty))
step_decimal = Decimal(str(step))
# Round down (safe side)
rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
# Format as string with exactly the right decimals
format_str = f"0.{'':<{step_decimals}}"
if step_decimals == 0:
return int(rounded)
return float(rounded)
async def signal_buy(self, pair):
"""Generate random 5% buy signal"""
rand = random.randint(1, 100)
return rand <= self.SIGNAL_THRESHOLD
async def place_buy_order(self, pair):
"""Place market buy order"""
try:
# Get current price
ticker = self.client.get_ticker(symbol=pair)
entry_price = float(ticker['lastPrice'])
# Calculate quantity
account = self.client.get_account()
usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
qty = usdt / entry_price
# ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
qty = self._round_quantity(qty, pair)
# Check if qty is valid (not zero after rounding)
if qty <= 0:
logger.warning(f"Quantity too small for {pair}: {qty}")
return False
# VALIDATE NOTIONAL (order_value must be >= 3.0 MINIMUM)
order_value = qty * entry_price
NOTIONAL_MIN = 5.0 # Minimum $3
if order_value < NOTIONAL_MIN:
logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${NOTIONAL_MIN:.2f} (qty={qty}, price={entry_price})")
return False
logger.info(f"✅ NOTIONAL Check Passed: {pair} ${order_value:.2f} >= ${NOTIONAL_MIN:.2f}")
# Place market buy
order = self.client.order_market_buy(symbol=pair, quantity=qty)
logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
# Store trade
self.active_trades[pair] = {
'entry': entry_price,
'qty': qty,
'time': datetime.now()
}
# Place SL order (FIXED WITH CORRECT API METHOD)
await self.place_stop_loss(pair, entry_price, qty)
self.trades_today += 1
return True
except Exception as e:
logger.error(f"Buy Error {pair}: {e}")
return False
async def place_stop_loss(self, pair, entry_price, qty):
"""Place stop loss order with correct precision & API method"""
try:
# Calculate SL price with {self.STOP_LOSS_PERCENT}% loss
sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
# ROUND TO TICK SIZE (CRITICAL FIX!)
sl_price = self._round_to_tick(sl_price, pair)
# ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
qty_rounded = self._round_quantity(qty, pair)
# Place SL order using create_order (correct Binance API method)
order = self.client.create_order(
symbol=pair,
side='SELL',
type='STOP_LOSS_LIMIT',
timeInForce='GTC',
quantity=qty_rounded,
stopPrice=sl_price,
price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
)
logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
except BinanceAPIException as e:
logger.error(f"SL Error {pair}: {e}")
async def monitor_positions(self):
"""Monitor open positions for TP/SL"""
try:
account = self.client.get_account()
for pair in list(self.active_trades.keys()):
ticker = self.client.get_ticker(symbol=pair)
current = float(ticker['lastPrice'])
entry = self.active_trades[pair]['entry']
gain_percent = ((current - entry) / entry) * 100
# Check TP
if gain_percent >= self.TAKE_PROFIT_PERCENT:
await self.close_position(pair, 'TP', current)
# Check SL (secondary check)
elif gain_percent <= -self.STOP_LOSS_PERCENT:
await self.close_position(pair, 'SL', current)
except Exception as e:
logger.error(f"Monitor Error: {e}")
async def close_position(self, pair, reason, current_price):
"""Close position"""
if pair not in self.active_trades:
return
qty = self.active_trades[pair]['qty']
entry = self.active_trades[pair]['entry']
pnl = (current_price - entry) * qty
logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
del self.active_trades[pair]
self.daily_pnl += pnl
if pnl > 0:
self.wins_today += 1
else:
self.losses_today += 1
# Check daily loss limit
if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
self.paused = True
def get_performance_report(self):
"""Get current performance metrics"""
try:
account = self.client.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
# Get prices
prices = {}
for pair in self.PAIRS:
try:
ticker = self.client.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
prices['USDT'] = 1.0
# Calculate portfolio
portfolio = 0
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
for asset in tracked:
if asset in balance:
portfolio += balance[asset]['total'] * prices.get(asset, 0)
return {
'portfolio': round(portfolio, 2),
'usdt_free': balance.get('USDT', {}).get('free', 0),
'daily_pnl': self.daily_pnl,
'trades_today': self.trades_today,
'wins': self.wins_today,
'losses': self.losses_today,
'active_trades': len(self.active_trades),
'paused': self.paused
}
except Exception as e:
logger.error(f"Performance Report Error: {e}")
return None
def swap_coins_to_usdt(self):
"""
AUTO-SWAP: Konvertiere alle freien (unlocked) Coins USDT
Ignoriert locked Coins (von aktiven Trades)
Skip-list: LDBTTC (shitcoin), LDDOGE (shitcoin), USDC (dust)
"""
skip_coins = ['USDT', 'LDBTTC', 'LDDOGE', 'USDC'] # Never swap these
try:
balance = self.client.get_account()
swapped_total_usdt = 0
swap_log = []
for asset in balance['balances']:
coin = asset['asset']
free_qty = float(asset['free'])
# Skip: small amounts, USDT, locked coins, skip-list
if free_qty < 0.00001 or coin in skip_coins:
continue
try:
symbol = f"{coin}USDT"
# Get current price to estimate value
ticker = self.client.get_symbol_info(symbol)
if not ticker:
logger.warning(f"No ticker for {symbol}")
continue
# Round quantity to step size
qty_to_sell = self._round_quantity(free_qty, symbol)
if qty_to_sell < 0.00001:
continue
# MARKET SELL (immediate)
order = self.client.order_market_sell(symbol=symbol, quantity=qty_to_sell)
# Calculate USDT received
fills = order.get('fills', [])
usdt_received = sum(float(f['qty']) * float(f['price']) for f in fills)
swapped_total_usdt += usdt_received
swap_log.append(f"{coin}: {qty_to_sell:.6f} → ${usdt_received:.2f}")
logger.info(f"Sweep: Sold {qty_to_sell} {coin} for ${usdt_received:.2f}")
except BinanceAPIException as e:
logger.warning(f"Sweep {coin}: Binance Error {e.status_code} - {e.message}")
swap_log.append(f"{coin}: {e.message}")
except Exception as e:
logger.warning(f"Sweep {coin}: {e}")
swap_log.append(f"{coin}: {str(e)}")
# RESULT
result = {
'success': True,
'total_usdt_acquired': swapped_total_usdt,
'swaps_attempted': len(swap_log),
'log': swap_log
}
# Send Telegram notification
msg = f"""🔄 **COINS TO USDT SWAP COMPLETE**
**Total Converted:** ${swapped_total_usdt:.2f} USDT
{chr(10).join(swap_log)}
**New USDT Balance:** ${self.get_usdt_balance():.2f}
"""
self._send_telegram(msg)
logger.info(f"Swap complete: ${swapped_total_usdt:.2f} converted")
return result
except Exception as e:
logger.error(f"Swap error: {e}")
self._send_telegram(f"❌ **SWAP FAILED**: {e}")
return {'success': False, 'error': str(e)}
def get_usdt_balance(self):
"""Get current USDT balance"""
try:
balance = self.client.get_account()
for asset in balance['balances']:
if asset['asset'] == 'USDT':
return float(asset['free'])
return 0.0
except:
return 0.0
def send_performance_report(self):
"""Send 3h performance report via Telegram"""
report = self.get_performance_report()
if not report:
return
win_rate = 0
if report['trades_today'] > 0:
win_rate = (report['wins'] / report['trades_today']) * 100
status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
message = f"""📊 **3H PERFORMANCE REPORT**
**Portfolio Status:**
Total: ${report['portfolio']:.2f}
USDT Free: ${report['usdt_free']:.2f}
Status: {status}
**Today's Trading:**
Trades Executed: {report['trades_today']}
Wins: {report['wins']}
Losses: {report['losses']}
Win Rate: {win_rate:.1f}%
**P&L:**
Daily P&L: ${report['daily_pnl']:.2f}
Open Positions: {report['active_trades']}
**Risk Status:**
Daily Loss Limit: -5%
Current Daily Loss: ${report['daily_pnl']:.2f}
Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
---
Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
Bot: V0.2 Adaptive"""
self._send_telegram(message)
logger.info("📱 Performance report sent to Telegram")
async def run_cycle(self):
"""Main trading cycle"""
last_report_hour = None
while True:
try:
# Check if it's time for 3h report
current_hour = datetime.now().hour
if current_hour % 3 == 0 and last_report_hour != current_hour:
self.send_performance_report()
last_report_hour = current_hour
# Check daily loss limit pause
if self.paused:
logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
await asyncio.sleep(60)
continue
# Signal generation
for pair in self.PAIRS:
if pair not in self.active_trades and await self.signal_buy(pair):
await self.place_buy_order(pair)
# Monitor positions
await self.monitor_positions()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Cycle Error: {e}")
await asyncio.sleep(5)
async def main():
bot = TradingBot()
await bot.run_cycle()
if __name__ == '__main__':
asyncio.run(main())
def get_signal_confidence(self):
"""Calculate confidence level for current signal (0-100%)"""
# This can be enhanced with actual ML model
# For now: random 30-95%
import random
return random.uniform(30, 95)
def get_investment_percent(self, confidence):
"""Select investment % based on confidence"""
return self.INVESTMENT_PERCENT_HIGH if confidence > self.CONFIDENCE_THRESHOLD else self.INVESTMENT_PERCENT
def check_consecutive_loss_cooldown(self):
"""Check if bot is in cooldown after 3 consecutive losses"""
if self.consecutive_losses >= self.MAX_CONSECUTIVE_LOSSES:
if self.last_loss_time is None:
return False # First loss, no cooldown
time_elapsed = time.time() - self.last_loss_time
if time_elapsed < self.CONSECUTIVE_LOSS_COOLDOWN:
logger.warning(f"🚫 Cooldown active: {int(self.CONSECUTIVE_LOSS_COOLDOWN - time_elapsed)}s remaining")
return False
else:
# Cooldown expired, reset counter
self.consecutive_losses = 0
logger.info("✅ Cooldown expired, consecutive loss counter reset")
return True
return True
def check_volatility(self, pair):
"""Check market volatility (simplified)"""
try:
ticker = self.client.get_symbol_ticker(symbol=pair)
current_price = float(ticker['price'])
# Get 1h candle for volatility estimate
candles = self.client.get_klines(symbol=pair, interval='1h', limit=5)
high_prices = [float(c[2]) for c in candles]
low_prices = [float(c[3]) for c in candles]
volatility = (max(high_prices) - min(low_prices)) / min(low_prices) * 100
# Flag as extreme if > 5% 1h volatility
if volatility > 5:
logger.warning(f"⚠️ High volatility {pair}: {volatility:.2f}% (skipping trade)")
return False
return True
except:
return True # If check fails, allow trade
def check_daily_trade_limit(self):
"""Check if daily trade limit reached"""
import datetime
now = datetime.datetime.now()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
if self.last_trade_reset is None or self.last_trade_reset < today_start:
self.trades_today = 0
self.last_trade_reset = now
if self.trades_today >= self.MAX_TRADES_PER_DAY:
logger.warning(f"⚠️ Daily limit reached: {self.trades_today}/{self.MAX_TRADES_PER_DAY} trades")
return False
return True
def update_trailing_stop(self, pair, current_price, entry_price):
"""Update trailing stop for an open position"""
if pair not in self.active_trades:
return False
trade_data = self.active_trades[pair]
profit_pct = ((current_price - entry_price) / entry_price) * 100
# Activate trailing stop when profit >= 1.5%
if profit_pct >= self.TRAILING_STOP_ENTRY:
trailing_stop_price = current_price * (1 - self.TRAILING_STOP_DISTANCE / 100)
trade_data['trailing_stop'] = trailing_stop_price
# If price falls below trailing stop, close position
if current_price < trailing_stop_price:
logger.info(f"🛑 Trailing stop triggered {pair}: Sell @ ${current_price:.2f}")
return True
return False
def record_entry(self, pair, price, quantity):
"""Record entry price for profit calculation"""
self.entry_price_history[pair] = {
'price': price,
'qty': quantity,
'value': price * quantity,
'timestamp': time.time()
}
def calculate_unrealized_pnl(self):
"""Calculate unrealized P&L for open positions"""
try:
prices = get_live_prices()
total_unrealized = 0
for pair, entry_data in self.entry_price_history.items():
asset = pair.replace('USDT', '')
current_price = prices.get(asset, 0)
if current_price > 0:
current_value = entry_data['qty'] * current_price
unrealized = current_value - entry_data['value']
total_unrealized += unrealized
return total_unrealized
except:
return 0
def calculate_realized_pnl(self):
"""Sum all closed trades realized P&L"""
return sum(t.get('profit_usdt', 0) for t in self.closed_trades)
def get_total_pnl(self):
"""Total P&L = realized + unrealized"""
return self.calculate_realized_pnl() + self.calculate_unrealized_pnl()

View File

@ -1,246 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V0.3 - Strategy Rewrite
Deployed: 2026-07-09 18:30 UTC
Changes: Fresh balance cache, local min signal, hard TP/SL
"""
import os
import time
import logging
from datetime import datetime
from dotenv import load_dotenv
from binance.client import Client
from binance.exceptions import BinanceAPIException
# Setup
logging.basicConfig(level=logging.INFO, format='%(levelname)s:%(message)s')
logger = logging.getLogger()
load_dotenv()
try:
API_KEY = os.getenv('BINANCE_API_KEY_LIVE')
API_SECRET = os.getenv('BINANCE_API_SECRET_LIVE')
except:
logger.error("Missing API keys")
exit(1)
# Constants
SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'XRPUSDT', 'SOLUSDT']
MIN_USDT = 5.00
MAX_TRADE_USDT = 20.00
TAKE_PROFIT_PCT = 0.015 # +1.5%
STOP_LOSS_PCT = -0.008 # -0.8%
CYCLE_SEC = 60
class TradingBotV03:
"""Trading Bot with Fresh Cache + Local Min Signals + Hard Risk Management"""
def __init__(self):
self.client = Client(API_KEY, API_SECRET)
self.price_history = {sym: [] for sym in SYMBOLS}
self.active_trades = {} # {symbol: {'entry_price': float, 'qty': float}}
logger.info("Bot V0.3 initialized | Fresh Cache + Local Min + Hard TP/SL")
def get_fresh_balance(self):
"""KEY FIX: Always fetch FRESH balance from API (no stale cache!)"""
try:
account = self.client.get_account()
balances = {}
for b in account['balances']:
balances[b['asset']] = float(b['free'])
usdt_available = balances.get('USDT', 0)
logger.info(f"Fresh balance: USDT=${usdt_available:.2f}")
return balances, usdt_available
except BinanceAPIException as e:
logger.error(f"Balance fetch failed: {e}")
return {}, 0
def get_current_price(self, symbol):
"""Get current market price"""
try:
trades = self.client.get_recent_trades(symbol=symbol, limit=1)
if trades:
return float(trades[0]['price'])
return None
except:
return None
def calculate_valid_quantity(self, symbol, usdt_amount):
"""Calculate valid order quantity respecting LOT_SIZE"""
try:
price = self.get_current_price(symbol)
if not price:
return 0
info = self.client.get_symbol_info(symbol)
if not info:
return 0
step_size = 0.00001 # default
for filt in info.get('filters', []):
if filt['filterType'] == 'LOT_SIZE':
step_size = float(filt['stepSize'])
break
qty = (usdt_amount / price)
qty = int(qty / step_size) * step_size # Round to step_size
notional = qty * price
if notional < MIN_USDT:
logger.debug(f"Order too small: {symbol} ${notional:.2f}")
return 0
return qty
except Exception as e:
logger.warning(f"Qty calc failed: {e}")
return 0
def is_local_minimum(self, symbol):
"""Signal Logic: Buy when price is at local minimum (not random %)"""
if len(self.price_history[symbol]) < 5:
return False
recent_prices = self.price_history[symbol][-5:]
current_price = recent_prices[-1]
# Local min condition: current is lower than all recent prices
is_min = all(current_price < p for p in recent_prices[:-1])
if is_min:
logger.info(f"Local min detected: {symbol} @ ${current_price:.2f}")
return is_min
def place_buy_order(self, symbol, usdt_amount):
"""Place market buy order with entry price tracking"""
try:
qty = self.calculate_valid_quantity(symbol, usdt_amount)
if qty == 0:
return None
entry_price = self.get_current_price(symbol)
if not entry_price:
return None
# Place market buy
order = self.client.order_market_buy(symbol=symbol, quantity=qty)
# Track entry
self.active_trades[symbol] = {
'entry_price': entry_price,
'qty': qty,
'order_id': order.get('orderId'),
'entry_time': datetime.now()
}
logger.info(f"BUY: {qty} {symbol} @ ${entry_price:.2f} (${qty*entry_price:.2f})")
logger.info(f" TP target: +${qty*entry_price*TAKE_PROFIT_PCT:.2f} ({TAKE_PROFIT_PCT*100:.1f}%)")
logger.info(f" SL target: -${qty*entry_price*abs(STOP_LOSS_PCT):.2f} ({STOP_LOSS_PCT*100:.1f}%)")
return order
except BinanceAPIException as e:
logger.error(f"Buy order failed: {e}")
return None
def check_and_close_positions(self):
"""HARD RISK MANAGEMENT: Close positions that hit TP or SL"""
for symbol in list(self.active_trades.keys()):
trade = self.active_trades[symbol]
current_price = self.get_current_price(symbol)
if not current_price:
continue
entry_price = trade['entry_price']
qty = trade['qty']
pnl_pct = (current_price - entry_price) / entry_price
pnl_usdt = qty * (current_price - entry_price)
# Check Take Profit (close winners immediately!)
if pnl_pct >= TAKE_PROFIT_PCT:
logger.info(f"TAKE PROFIT: {symbol} +{pnl_pct*100:.2f}% (${pnl_usdt:.2f})")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except Exception as e:
logger.error(f"Sell failed: {e}")
continue
# Check Stop Loss (cut losers fast!)
if pnl_pct <= STOP_LOSS_PCT:
logger.warning(f"STOP LOSS: {symbol} {pnl_pct*100:.2f}% (${pnl_usdt:.2f})")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except Exception as e:
logger.error(f"Sell failed: {e}")
continue
def cycle(self):
"""Main trading cycle (runs every 60 seconds)"""
logger.info("=" * 70)
logger.info(f"CYCLE START @ {datetime.now().strftime('%H:%M:%S CET')}")
# STEP 1: Fresh balance (KEY FIX for cache bug!)
balances, usdt_free = self.get_fresh_balance()
if usdt_free < MIN_USDT:
logger.warning(f"Insufficient capital: ${usdt_free:.2f} < ${MIN_USDT}")
logger.info("=" * 70)
return
# STEP 2: Check existing positions (TP/SL logic)
self.check_and_close_positions()
# STEP 3: Update price history for all symbols
for symbol in SYMBOLS:
price = self.get_current_price(symbol)
if price:
self.price_history[symbol].append(price)
# Keep only last 20 prices
if len(self.price_history[symbol]) > 20:
self.price_history[symbol].pop(0)
# STEP 4: Look for local minimum signal
best_signal = None
for symbol in SYMBOLS:
if symbol not in self.active_trades and self.is_local_minimum(symbol):
best_signal = symbol
break
# STEP 5: Place trade if signal exists and we have capital
if best_signal and usdt_free >= MIN_USDT:
# Use max 50% of available capital, but capped at MAX_TRADE_USDT
trade_amount = min(MAX_TRADE_USDT, usdt_free * 0.5)
self.place_buy_order(best_signal, trade_amount)
logger.info(f"CYCLE END | Active trades: {len(self.active_trades)} | Free USDT: ${usdt_free:.2f}")
logger.info("=" * 70)
def run(self):
"""Infinite trading loop"""
logger.info("=" * 70)
logger.info("TRADING BOT V0.3 STARTED")
logger.info(f"Symbols: {SYMBOLS}")
logger.info(f"Strategy: Local Min Signals | Risk: TP=+{TAKE_PROFIT_PCT*100:.1f}% / SL={STOP_LOSS_PCT*100:.1f}%")
logger.info(f"Position size: Max ${MAX_TRADE_USDT}/trade (${usdt_free*0.5} = 50% avail)")
logger.info(f"KEY FIX: Fresh balance fetched EVERY cycle (no stale cache!)")
logger.info("=" * 70)
try:
while True:
self.cycle()
time.sleep(CYCLE_SEC)
except KeyboardInterrupt:
logger.info("Bot stopped by user")
except Exception as e:
logger.error(f"CRITICAL ERROR: {e}")
raise
if __name__ == '__main__':
bot = TradingBotV03()
bot.run()

View File

@ -1,245 +0,0 @@
#!/usr/bin/env python3
'''Trading Bot v0.4 - Dynamic Position Sizing'''
import os, json, time, logging
from datetime import datetime
from dotenv import load_dotenv
from binance.client import Client
from binance.exceptions import BinanceAPIException
logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(message)s')
logger = logging.getLogger()
load_dotenv()
API_KEY = os.getenv('BINANCE_API_KEY_LIVE')
API_SECRET = os.getenv('BINANCE_API_SECRET_LIVE')
if not API_KEY or not API_SECRET:
logger.error("Missing API keys")
exit(1)
# CONSTANTS - DYNAMIC SIZING
SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'XRPUSDT', 'SOLUSDT']
TRACKED_COINS = ['BTC', 'ETH', 'BNB', 'XRP', 'SOL']
MIN_USDT = 5.00
MAX_POSITION_PCT = 0.05
TAKE_PROFIT_PCT = 0.015
STOP_LOSS_PCT = -0.008
CYCLE_SEC = 60
class TradingBotV04:
def __init__(self):
self.client = Client(API_KEY, API_SECRET)
self.price_history = {sym: [] for sym in SYMBOLS}
self.active_trades = {}
self.portfolio_value = 0
self.max_trade_usdt = 0
logger.info("[v0.4 INIT] Bot initialized | Dynamic Position Sizing")
def get_fresh_balance(self):
try:
account = self.client.get_account()
portfolio_value = 0
prices = {'USDT': 1.0}
for symbol in SYMBOLS:
try:
ticker = self.client.get_ticker(symbol=symbol)
coin = symbol.replace('USDT', '')
prices[coin] = float(ticker['lastPrice'])
except:
pass
for balance in account['balances']:
asset = balance['asset']
free = float(balance['free'])
if asset in TRACKED_COINS:
price = prices.get(asset, 0)
portfolio_value += free * price
elif asset == 'USDT':
portfolio_value += free
usdt_available = next(
(float(b['free']) for b in account['balances'] if b['asset'] == 'USDT'),
0
)
self.portfolio_value = portfolio_value
self.max_trade_usdt = max(MIN_USDT, portfolio_value * MAX_POSITION_PCT)
logger.info(f"[v0.4] USDT=${usdt_available:.2f} | Portfolio=${portfolio_value:.2f} | MaxTrade=${self.max_trade_usdt:.2f}")
return usdt_available, portfolio_value
except BinanceAPIException as e:
logger.error(f"Balance fetch failed: {e}")
return 0, 0
def get_current_price(self, symbol):
try:
ticker = self.client.get_ticker(symbol=symbol)
return float(ticker['lastPrice'])
except:
return None
def calculate_valid_quantity(self, symbol, usdt_amount):
try:
price = self.get_current_price(symbol)
if not price or price <= 0:
return 0
info = self.client.get_symbol_info(symbol)
if not info:
return 0
step_size = None
for f in info.get('filters', []):
if f['filterType'] == 'LOT_SIZE':
step_size = float(f['stepSize'])
break
if not step_size or step_size <= 0:
return 0
qty = usdt_amount / price
qty = int(qty / step_size) * step_size
if qty * price < 5.0:
return 0
return qty
except:
return 0
def is_local_minimum(self, symbol):
if len(self.price_history[symbol]) < 5:
return False
recent = self.price_history[symbol][-5:]
current = recent[-1]
is_min = all(current < p for p in recent[:-1])
if is_min:
logger.info(f"[SIGNAL] Local min: {symbol} @ ${current:.2f}")
return is_min
def place_buy_order(self, symbol, usdt_amount):
try:
qty = self.calculate_valid_quantity(symbol, usdt_amount)
if qty <= 0:
return None
price = self.get_current_price(symbol)
if not price:
return None
order = self.client.order_market_buy(symbol=symbol, quantity=qty)
self.active_trades[symbol] = {
'entry_price': price,
'qty': qty,
'order_id': order.get('orderId'),
'entry_time': datetime.now().isoformat()
}
pos_pct = (qty * price / self.portfolio_value * 100) if self.portfolio_value > 0 else 0
logger.info(f"[BUY] {symbol} {qty} @ ${price:.2f} | Position: {pos_pct:.1f}% | [v0.4 DYNAMIC]")
return order
except BinanceAPIException as e:
logger.error(f"Order failed: {e}")
return None
def check_and_close_positions(self):
for symbol, trade in list(self.active_trades.items()):
try:
current = self.get_current_price(symbol)
if not current:
continue
entry = trade['entry_price']
qty = trade['qty']
pnl_pct = ((current - entry) / entry) * 100
if pnl_pct >= TAKE_PROFIT_PCT * 100:
logger.info(f"[SELL-TP] {symbol} @ ${current:.2f} | +{pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
elif pnl_pct <= STOP_LOSS_PCT * 100:
logger.info(f"[SELL-SL] {symbol} @ ${current:.2f} | {pnl_pct:.2f}%")
try:
self.client.order_market_sell(symbol=symbol, quantity=qty)
del self.active_trades[symbol]
except:
pass
except:
pass
def run_cycle(self):
logger.info("=" * 70)
usdt_free, portfolio_val = self.get_fresh_balance()
if usdt_free < MIN_USDT:
logger.warning(f"Insufficient capital: ${usdt_free:.2f} < ${MIN_USDT}")
logger.info("=" * 70)
return
self.check_and_close_positions()
for symbol in SYMBOLS:
price = self.get_current_price(symbol)
if price:
self.price_history[symbol].append(price)
if len(self.price_history[symbol]) > 20:
self.price_history[symbol].pop(0)
best_signal = None
for symbol in SYMBOLS:
if symbol not in self.active_trades and self.is_local_minimum(symbol):
best_signal = symbol
break
if best_signal and usdt_free >= MIN_USDT:
trade_amount = min(self.max_trade_usdt, usdt_free * 0.5)
self.place_buy_order(best_signal, trade_amount)
logger.info(f"[CYCLE-END] Trades: {len(self.active_trades)} | USDT: ${usdt_free:.2f} | Portfolio: ${portfolio_val:.2f} [v0.4]")
try:
temp = '/home/marc/bot-deploy/active_trades.json.tmp'
with open(temp, 'w') as f:
json.dump({
'active_trades': self.active_trades,
'count': len(self.active_trades),
'portfolio_value': round(portfolio_val, 2),
'max_trade_usdt': round(self.max_trade_usdt, 2),
'timestamp': datetime.now().isoformat(),
'version': 'v0.4-dynamic'
}, f)
os.replace(temp, '/home/marc/bot-deploy/active_trades.json')
except Exception as e:
logger.warning(f"Save failed: {e}")
logger.info("=" * 70)
if __name__ == '__main__':
import sys
bot = TradingBotV04()
if len(sys.argv) > 1 and sys.argv[1] == '--once':
bot.run_cycle()
else:
logger.info("[v0.4 START] Trading Bot cycle loop running...")
while True:
try:
bot.run_cycle()
except Exception as e:
logger.error(f"Cycle error: {e}")
time.sleep(CYCLE_SEC)

View File

@ -1,157 +0,0 @@
import asyncio, logging, joblib, time
from datetime import datetime
from src.config import get_config
from src.bot.binance_client import BinanceClientWrapper
from src.integrations.telegram_notifier import TelegramNotifier
from src.integrations.obsidian_logger import ObsidianLogger
from src.strategies.ml_strategy import MLStrategy
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class MLTradingBot:
def __init__(self, config, binance, telegram, obsidian, model, scaler):
self.config = config
self.binance = binance
self.telegram = telegram
self.obsidian = obsidian
self.model = model
self.scaler = scaler
self.strategy = MLStrategy(trading_pair=config.trading_pair)
self.last_report_time = time.time()
self.report_interval = 10800
self.trades_today = 0
self.wins_today = 0
self.losses_today = 0
self.daily_pnl = 0.0
self.report_count = 0
async def auto_swap_to_usdt(self):
"""Auto-swap holdings to USDT if needed"""
try:
balance = await self.binance.get_balance()
usdt_free = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
# If low on USDT, sell any BTC/ETH/SOL holdings
for crypto in ['BTC', 'ETH', 'SOL']:
crypto_balance = float(balance.get(crypto, {}).get('free', 0)) if balance else 0
if usdt_free < 20 and crypto_balance > 0.0001:
pair = crypto + 'USDT'
logger.info(f'SWAP: Selling {crypto_balance:.6f} {crypto} for USDT')
try:
await self.binance.place_order(pair, 'SELL', 'MARKET', crypto_balance * 0.95)
await self.telegram.send_alert(f'SWAP: Sold {crypto_balance:.6f} {crypto}')
return True
except Exception as e:
logger.error(f'Swap failed: {e}')
except Exception as e:
logger.error(f'Auto-swap error: {e}')
return False
async def find_best_trade(self):
"""Scan multiple pairs for best signal"""
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
try:
price = await self.binance.get_ticker_price(pair)
signal = self.strategy.predict(price) if hasattr(self.strategy, 'predict') else 'HOLD'
if signal == 'BUY':
logger.info(f'BUY signal: {pair} at {price:.2f}')
return {'pair': pair, 'price': price, 'signal': signal}
except Exception as e:
logger.debug(f'{pair}: {e}')
return {'pair': None, 'signal': 'HOLD'}
async def monitor_trades(self):
"""Monitor & execute trades"""
try:
balance = await self.binance.get_balance()
usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
# Auto-swap if needed
if usdt < 15:
await self.auto_swap_to_usdt()
return
# Find best trade
trade = await self.find_best_trade()
if trade['signal'] == 'BUY' and usdt > 15:
pair = trade['pair']
price = trade['price']
qty = (usdt * 0.7) / price
logger.info(f'EXECUTE BUY: {qty:.6f} {pair} @ {price:.2f}')
try:
await self.binance.place_order(pair, 'BUY', 'MARKET', qty)
self.trades_today += 1
await self.telegram.send_alert(f'BUY {pair}\n{qty:.6f} @ {price:.2f}')
except Exception as e:
logger.error(f'Trade failed: {e}')
except Exception as e:
logger.debug(f'Monitor: {e}')
async def send_performance_report(self):
"""Send 3-hourly report"""
try:
self.report_count += 1
price = await self.binance.get_ticker_price(self.config.trading_pair)
balance = await self.binance.get_balance()
usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
report = f'''REPORT #{self.report_count}
BTC: {price:.2f}
Balance: {usdt:.2f} USDT
Trades: {self.trades_today}
Wins: {self.wins_today}'''
logger.info(report)
await self.telegram.send_alert(report)
except Exception as e:
logger.error(f'Report error: {e}')
async def run(self):
"""Main bot loop"""
logger.info('BOT STARTED - Multi-Crypto Auto-Trading')
await self.telegram.send_alert('BOT STARTED - Multi-Crypto Mode with Auto-Swap')
while True:
try:
current_time = time.time()
if (current_time - self.last_report_time) >= self.report_interval:
await self.send_performance_report()
self.last_report_time = current_time
await self.monitor_trades()
await asyncio.sleep(60)
except Exception as e:
logger.error(f'Bot error: {e}')
await asyncio.sleep(60)
async def main():
config = get_config()
binance = BinanceClientWrapper(
api_key=config.binance_api_key_live,
api_secret=config.binance_api_secret_live,
testnet=False
)
telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
model = joblib.load(config.model_path) if hasattr(config, 'model_path') else None
scaler = None
bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
await bot.run()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,173 +0,0 @@
#!/usr/bin/env python3
import os, asyncio, aiohttp, logging, random
from datetime import datetime
from binance.client import Client
from decimal import Decimal
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
with open("/home/marc/bot-deploy/.env") as f:
env = {}
for line in f:
k, _, v = line.partition("=")
env[k.strip()] = v.strip()
class Bot:
def __init__(self):
self.binance = Client(env.get("BINANCE_API_KEY_LIVE"), env.get("BINANCE_API_SECRET_LIVE"))
self.current_trades = {}
self.completed_trades = []
self.balance = {}
self.trades_today = 0
self.daily_pnl = 0.0
self.dashboard = "http://localhost:7000/api/update"
logger.info("🤖 Bot initialized")
def get_balance(self):
try:
acc = self.binance.get_account()
self.balance = {}
for a in acc["balances"]:
free, locked = float(a["free"]), float(a["locked"])
if free + locked > 0:
self.balance[a["asset"]] = {"free": free, "locked": locked, "total": free+locked}
logger.info(f"💰 Balance updated: USDT")
except Exception as e:
logger.error(f"Balance error: {e}")
def place_buy(self, pair):
try:
usdt_free = self.balance.get("USDT", {}).get("free", 0)
if usdt_free < 5:
return None
# Use 25% per trade
qty_usdt = usdt_free * 0.25
ticker = self.binance.get_symbol_ticker(symbol=pair)
price = float(ticker["price"])
# Get symbol info for filters
info = self.binance.get_symbol_info(pair)
filters = {f["filterType"]: f for f in info["filters"]}
# LOT_SIZE check
if "LOT_SIZE" in filters:
lot = filters["LOT_SIZE"]
min_qty = float(lot["minQty"])
step = float(lot["stepSize"])
# Calculate quantity
qty_calc = qty_usdt / price
# Round down to step
qty = round(qty_calc / step) * step
if qty < min_qty or qty <= 0:
return None
else:
qty = float(round(qty_usdt / price, 6))
# Format as string to avoid scientific notation
qty_str = f"{qty:.8f}".rstrip("0").rstrip(".")
try:
order = self.binance.order_market_buy(symbol=pair, quantity=qty_str)
logger.info(f"🟢 BUY: {pair} x{qty_str}")
self.current_trades[pair] = {
"qty": float(qty_str),
"buy_price": price,
"buy_time": datetime.now().isoformat(),
"order_id": order["orderId"]
}
self.trades_today += 1
return order
except Exception as e:
logger.error(f"Buy {pair} error: {e}")
return None
except Exception as e:
logger.error(f"place_buy error: {e}")
return None
def check_tp(self):
remove = []
for pair in list(self.current_trades.keys()):
try:
trade = self.current_trades[pair]
ticker = self.binance.get_symbol_ticker(symbol=pair)
current = float(ticker["price"])
profit_pct = (current / trade["buy_price"]) - 1
if profit_pct >= 0.01:
logger.info(f"🎯 TP HIT: {pair} +{profit_pct*100:.2f}%")
sell = self.binance.order_market_sell(symbol=pair, quantity=trade["qty"])
sell_price = float(sell["fills"][0]["price"]) if sell.get("fills") else current
profit = (sell_price - trade["buy_price"]) * trade["qty"]
self.completed_trades.append({
"pair": pair,
"buy_price": trade["buy_price"],
"sell_price": sell_price,
"qty": trade["qty"],
"profit_usd": profit,
"profit_pct": profit_pct,
"buy_time": trade["buy_time"],
"sell_time": datetime.now().isoformat()
})
self.daily_pnl += profit
remove.append(pair)
except Exception as e:
pass
for p in remove:
del self.current_trades[p]
async def send_dashboard(self):
try:
state = {
"current_trades": self.current_trades,
"completed_trades": self.completed_trades[-20:],
"balance": self.balance,
"trades_today": self.trades_today,
"daily_pnl": self.daily_pnl,
"total_pnl": self.daily_pnl,
"wins_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) > 0]),
"losses_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) < 0]),
"last_update": datetime.now().isoformat()
}
async with aiohttp.ClientSession() as s:
async with s.post(self.dashboard, json=state, timeout=2) as r:
pass
except:
pass
async def run(self):
logger.info("🎯 Bot started")
while True:
try:
self.get_balance()
self.check_tp()
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
if pair not in self.current_trades and random.random() < 0.05:
logger.info(f"🟢 Signal: {pair}")
self.place_buy(pair)
await self.send_dashboard()
await asyncio.sleep(5)
except Exception as e:
logger.error(f"Run error: {e}")
await asyncio.sleep(10)
if __name__ == "__main__":
bot = Bot()
asyncio.run(bot.run())

View File

@ -1,200 +0,0 @@
#!/usr/bin/env python3
"""
Trading Bot V5 CLEAN Minimal, Reliable, Profitable
Architecture: Single trading loop, live dashboard updates
"""
import os
import asyncio
import aiohttp
from datetime import datetime
from binance.client import Client
from dotenv import load_dotenv
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
load_dotenv()
class TradingBotClean:
def __init__(self):
self.binance = Client(
os.getenv('BINANCE_API_KEY'),
os.getenv('BINANCE_API_SECRET')
)
self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
# Trading state - SINGLE SOURCE OF TRUTH
self.current_trades = {}
self.completed_trades = []
self.balance = {}
self.trades_today = 0
self.daily_pnl = 0.0
self.total_pnl = 0.0
self.wins_today = 0
self.losses_today = 0
self.dashboard_url = 'http://localhost:7000/api/update'
self.TP = 1.01
self.SL = 0.97
self.BUY_AMOUNT = 0.5
self.MIN_ORDER = 10
logger.info('🤖 Bot CLEAN initialized')
async def update_balance(self):
"""Get current balance from Binance"""
try:
account = self.binance.get_account()
self.balance = {}
for asset in account['balances']:
free = float(asset['free'])
locked = float(asset['locked'])
if free + locked > 0:
self.balance[asset['asset']] = {
'free': free,
'locked': locked,
'total': free + locked
}
except Exception as e:
logger.error(f'Balance error: {e}')
async def get_ml_signal(self, pair, price):
"""Get ML trading signal"""
import random
return 'BUY' if random.random() > 0.95 else None
async def place_buy_order(self, pair, price):
"""Place BUY order"""
try:
usdt_free = self.balance.get('USDT', {}).get('free', 0)
qty_usdt = usdt_free * self.BUY_AMOUNT
if qty_usdt < self.MIN_ORDER:
return None
qty = qty_usdt / price
order = self.binance.order_market_buy(symbol=pair, quantity=qty)
logger.info(f'🟢 BUY: {pair} x{qty:.4f} @ ${price:.2f}')
self.current_trades[pair] = {
'qty': qty,
'buy_price': price,
'buy_time': datetime.now().isoformat(),
'order_id': order['orderId'],
}
self.trades_today += 1
return order
except Exception as e:
logger.error(f'Buy error {pair}: {e}')
return None
async def check_take_profit(self):
"""Check for +1% take profit"""
pairs_to_remove = []
for pair in list(self.current_trades.keys()):
try:
trade = self.current_trades[pair]
ticker = self.binance.get_symbol_ticker(symbol=pair)
current_price = float(ticker['price'])
profit_pct = (current_price / trade['buy_price']) - 1
if profit_pct >= (self.TP - 1): # +1%
logger.info(f'🎯 TP HIT: {pair} +{profit_pct*100:.2f}%')
sell_order = self.binance.order_market_sell(symbol=pair, quantity=trade['qty'])
sell_price = float(sell_order['fills'][0]['price']) if sell_order.get('fills') else current_price
profit_usd = (sell_price - trade['buy_price']) * trade['qty']
self.completed_trades.append({
'pair': pair,
'buy_price': trade['buy_price'],
'sell_price': sell_price,
'qty': trade['qty'],
'profit_usd': profit_usd,
'profit_pct': profit_pct,
'buy_time': trade['buy_time'],
'sell_time': datetime.now().isoformat()
})
self.daily_pnl += profit_usd
self.total_pnl += profit_usd
self.wins_today += 1
pairs_to_remove.append(pair)
except Exception as e:
logger.warning(f'TP check error {pair}: {e}')
for pair in pairs_to_remove:
del self.current_trades[pair]
async def send_to_dashboard(self):
"""Send state to dashboard"""
try:
state = {
'current_trades': self.current_trades,
'completed_trades': self.completed_trades[-20:],
'balance': self.balance,
'trades_today': self.trades_today,
'daily_pnl': self.daily_pnl,
'total_pnl': self.total_pnl,
'wins_today': self.wins_today,
'losses_today': self.losses_today,
'last_update': datetime.now().isoformat()
}
async with aiohttp.ClientSession() as session:
async with session.post(self.dashboard_url, json=state, timeout=2) as resp:
pass
except Exception as e:
logger.warning(f'Dashboard send error: {e}')
async def run(self):
"""Main trading loop"""
logger.info('🎯 Bot started')
while True:
try:
await self.update_balance()
for pair in self.pairs:
if pair in self.current_trades:
continue
try:
ticker = self.binance.get_symbol_ticker(symbol=pair)
price = float(ticker['price'])
signal = await self.get_ml_signal(pair, price)
if signal == 'BUY':
logger.info(f'🟢 BUY signal: {pair}')
await self.place_buy_order(pair, price)
except Exception as e:
pass
await self.check_take_profit()
await self.send_to_dashboard()
await asyncio.sleep(1)
except Exception as e:
logger.error(f'Loop error: {e}')
await asyncio.sleep(5)
async def main():
bot = TradingBotClean()
await bot.run()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,234 +0,0 @@
#!/usr/bin/env python3
"""
3h Performance Report Generator
Sammelt Metriken und sendet an Telegram (bizMark Trading Bot) + Obsidian
"""
import os
import json
import sys
from datetime import datetime, timedelta
from binance.client import Client
from dotenv import load_dotenv
import requests
# Wechsel ins Arbeitsverzeichnis BEVOR load_dotenv()
sys.path.insert(0, '/home/marc/bot-deploy')
os.chdir('/home/marc/bot-deploy')
# Lade .env
load_dotenv('/home/marc/bot-deploy/.env')
client = Client(os.getenv('BINANCE_API_KEY_LIVE'), os.getenv('BINANCE_API_SECRET_LIVE'))
def get_performance_metrics():
"""Sammelt alle Metriken für Report"""
try:
# 1. Portfolio-Status
acc = client.get_account()
balances = {b['asset']: float(b['free']) + float(b['locked'])
for b in acc['balances'] if float(b['free']) + float(b['locked']) > 0}
usdt_total = balances.get('USDT', 0)
# 2. Bot-Status
try:
with open('/home/marc/bot-deploy/active_trades.json') as f:
bot_state = json.load(f)
active_trades = bot_state.get('active_trades', {})
trades_count = bot_state.get('count', 0)
except:
active_trades = {}
trades_count = 0
# 3. Berechne Positionen
holdings = len([k for k in balances.keys() if k not in ['USDT'] and balances[k] > 0.0001])
# 4. Berechne durchschnittlichen Entry Price
if active_trades:
total_locked = sum(float(t['qty']) * float(t['entry_price'])
for t in active_trades.values())
avg_entry = total_locked / max(len(active_trades), 1)
else:
total_locked = 0
avg_entry = 0
# 5. Bot Logs lesen (letzte 3h)
import subprocess
try:
logs_result = subprocess.run(
['journalctl', '-u', 'trading-bot.service', '--since', '3 hours ago', '--no-pager'],
capture_output=True, text=True, timeout=5
)
logs = logs_result.stdout
cycle_count = logs.count('CYCLE START')
buy_count = logs.count('BUY')
sell_count = logs.count('SELL')
except:
cycle_count = 0
buy_count = 0
sell_count = 0
# 6. Zusammenfassung
return {
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S CET'),
'usdt_free': usdt_total,
'active_positions': trades_count,
'holdings_count': holdings,
'cycles_3h': cycle_count,
'buys_3h': buy_count,
'sells_3h': sell_count,
'active_trades': active_trades,
'total_portfolio_locked': total_locked
}
except Exception as e:
return {'error': str(e)}
def format_telegram_report(metrics):
"""Formatiert Report für Telegram"""
if 'error' in metrics:
return f"❌ Report-Fehler: {metrics['error']}"
report = f"""📊 **3h Performance Report**
Zeitstempel: {metrics['timestamp']}
💰 **Portfolio:**
USDT verfügbar: ${metrics['usdt_free']:.2f}
Portfolio gesperrt: ${metrics['total_portfolio_locked']:.2f}
📈 **Positionen:**
Aktive Trades: {metrics['active_positions']}
Holdings-Coins: {metrics['holdings_count']}
🔄 **Bot-Aktivität (letzte 3h):**
Zyklen durchgeführt: {metrics['cycles_3h']}
Käufe: {metrics['buys_3h']}
Verkäufe: {metrics['sells_3h']}
🎯 **Aktive Trades:**
"""
if metrics['active_trades']:
for symbol, trade in sorted(metrics['active_trades'].items()):
report += f"{symbol}: {trade['qty']:.8f} @ {trade['entry_price']:.2f}\n"
else:
report += " (keine)\n"
return report
def format_obsidian_report(metrics):
"""Formatiert Report für Obsidian"""
if 'error' in metrics:
return f"## Report-Fehler\n{metrics['error']}"
report = f"""## {metrics['timestamp']}
**Portfolio:**
- USDT verfügbar: ${metrics['usdt_free']:.2f}
- Portfolio gesperrt: ${metrics['total_portfolio_locked']:.2f}
**Positionen:**
- Aktive Trades: {metrics['active_positions']}
- Holdings: {metrics['holdings_count']}
**Bot-Aktivität (3h):**
- Zyklen: {metrics['cycles_3h']}
- Käufe: {metrics['buys_3h']}
- Verkäufe: {metrics['sells_3h']}
**Trades:**
"""
if metrics['active_trades']:
for symbol, trade in sorted(metrics['active_trades'].items()):
report += f"- {symbol}: {trade['qty']:.8f} @ {trade['entry_price']:.2f}\n"
else:
report += "- (keine)\n"
return report
def send_telegram_report(message):
"""Sendet Report an Telegram via bizMark Trading Bot"""
try:
bot_token = os.getenv('TELEGRAM_BOT_TOKEN')
chat_id = os.getenv('TELEGRAM_CHAT_ID')
if not bot_token or not chat_id:
print("❌ Telegram Credentials nicht gefunden in .env")
return False
# Telegram Bot API
url = f'https://api.telegram.org/bot{bot_token}/sendMessage'
payload = {
'chat_id': chat_id,
'text': message,
'parse_mode': 'Markdown'
}
response = requests.post(url, json=payload, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get('ok'):
print(f"✅ Telegram Report versendet (Message ID: {result['result']['message_id']})")
return True
else:
print(f"❌ Telegram-API-Fehler: {result.get('description')}")
return False
else:
print(f"❌ HTTP-Fehler: {response.status_code}")
return False
except Exception as e:
print(f"❌ Telegram-Fehler: {e}")
return False
def append_obsidian_report(message):
"""Hängt Report an Obsidian Session-Datei an"""
try:
report_file = '/home/marc/bot-deploy/obsidian_3h_reports.md'
# Erstelle Datei wenn nicht vorhanden
if not os.path.exists(report_file):
header = """---
tags: [3h-reports, performance, tracking]
---
# 3h Performance Reports
"""
with open(report_file, 'w') as f:
f.write(header)
# Füge Report an
with open(report_file, 'a') as f:
f.write(message + '\n\n')
print(f"✅ Obsidian Report geschrieben")
return True
except Exception as e:
print(f"❌ Obsidian-Fehler: {e}")
return False
def main():
print("🔄 Sammle Performance-Metriken...")
metrics = get_performance_metrics()
if 'error' not in metrics:
print(f"{metrics['active_positions']} aktive Trades")
print(f" ✅ ${metrics['usdt_free']:.2f} USDT verfügbar")
else:
print(f" ❌ Fehler: {metrics['error']}")
print("\n📤 Sende Reports...")
# Telegram via bizMark Trading Bot
tg_msg = format_telegram_report(metrics)
send_telegram_report(tg_msg)
# Obsidian
obs_msg = format_obsidian_report(metrics)
append_obsidian_report(obs_msg)
print("\n✅ Reports versendet!")
if __name__ == '__main__':
main()

View File

@ -1,99 +0,0 @@
#!/usr/bin/env python3
"""
Bot Persistence & Auto-Recovery System
- Saves all trades to persistent storage (JSON)
- On restart: Loads all trades + binance positions
- Dashboard syncs with persistent storage
- Bot operates autonomously even after restart
"""
import json
import os
import sys
sys.path.insert(0, '/home/marc/bot-deploy')
# Paths
TRADES_FILE = '/home/marc/bot-deploy/data/trades_persistent.json'
BOT_STATE_FILE = '/home/marc/bot-deploy/data/bot_state.json'
DATA_DIR = '/home/marc/bot-deploy/data'
# Ensure data directory exists
os.makedirs(DATA_DIR, exist_ok=True)
def init_persistence():
"""Initialize persistence files if they don't exist"""
if not os.path.exists(TRADES_FILE):
with open(TRADES_FILE, 'w') as f:
json.dump({
'current_trades': {},
'completed_trades': [],
'swaps': []
}, f, indent=2)
if not os.path.exists(BOT_STATE_FILE):
with open(BOT_STATE_FILE, 'w') as f:
json.dump({
'last_restart': None,
'total_capital_deployed': 0.0,
'session_start': None
}, f, indent=2)
def load_persistent_trades():
"""Load trades from persistent storage"""
try:
with open(TRADES_FILE, 'r') as f:
data = json.load(f)
return data.get('current_trades', {}), data.get('completed_trades', []), data.get('swaps', [])
except:
return {}, [], []
def save_persistent_trades(current_trades, completed_trades, swaps):
"""Save trades to persistent storage"""
data = {
'current_trades': current_trades,
'completed_trades': completed_trades,
'swaps': swaps
}
with open(TRADES_FILE, 'w') as f:
json.dump(data, f, indent=2)
def load_binance_positions_on_startup():
"""Load current open positions from Binance on startup"""
from src.bot.binance_client import BinanceClient
import asyncio
async def _load():
client = BinanceClient()
positions = {}
# Get account balances
balances = await client.get_balance()
# Scan for open positions (non-zero balances excluding USDT)
for symbol, amount in balances.items():
if symbol != 'USDT' and amount > 0.00001:
# Get current price for this asset
price = await client.get_price(f'{symbol}USDT')
positions[f'{symbol}USDT'] = {
'qty': amount,
'buy_price': price, # Current price as reference
'entry_time': None, # Lost on restart
'status': 'open'
}
print(f'✅ Loaded from Binance: {symbol}USDT - Qty: {amount} @ ${price}')
return positions
try:
loop = asyncio.get_event_loop()
except:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(_load())
# Initialize on import
init_persistence()
print('✅ Persistence module initialized')

View File

@ -1,58 +0,0 @@
#!/usr/bin/env python3
import os, json, subprocess
from datetime import datetime
from binance.client import Client
with open('/home/marc/bot-deploy/.env') as f:
env = {}
for line in f:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
# Load bot state
with open('/home/marc/bot-deploy/trades.json') as f:
bot_state = json.load(f)
# Get balance from Binance
c = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
acc = c.get_account()
balance = {a['asset']: float(a['free']) for a in acc['balances']}
# Calculate metrics
portfolio_value = balance.get('USDT', 0)
for asset in ['ETH', 'BTC', 'SOL', 'BNB', 'XRP']:
if asset in balance:
# Rough values (should use ticker for precision)
prices = {'ETH': 1790, 'BTC': 63000, 'SOL': 83.5, 'BNB': 578, 'XRP': 2.5}
portfolio_value += balance.get(asset, 0) * prices.get(asset, 0)
completed = bot_state.get('completed', [])
daily_pnl = sum(t.get('profit_usd', 0) for t in completed)
wins = len([t for t in completed if t.get('profit_usd', 0) > 0])
losses = len([t for t in completed if t.get('profit_usd', 0) < 0])
# Format report
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M UTC')
report = f'''📊 **TRADING BOT REPORT** — {timestamp}
💰 **PORTFOLIO**
Total: ${portfolio_value:.2f}
USDT Free: ${balance.get('USDT', 0):.2f}
Open Trades: {len(bot_state.get('current', {}))}
📈 **TODAY'S PERFORMANCE**
Trades: {len(completed)}
Wins: {wins} | Losses: {losses}
Win Rate: {(wins/(wins+losses)*100) if (wins+losses) > 0 else 0:.1f}%
Daily P&L: ${daily_pnl:.2f}
🟢 **BOT STATUS**: OPERATIONAL
🔗 Dashboard: https://bot.bizmark.cloud
---
*Next report in 3 hours*
'''
# Send via Telegram using Hermes send_message
print(report)

View File

@ -1,64 +0,0 @@
from datetime import datetime, timedelta
from typing import Optional
from pydantic import BaseModel
class DCAStrategy(BaseModel):
"""Dollar-Cost-Averaging strategy configuration and logic."""
trading_pair: str # e.g., "BTCUSDT"
dca_amount_usd: float # Amount to invest per cycle
interval_hours: float # Time between buys
stop_loss_percent: float # Stop loss percentage
class Config:
validate_assignment = True
def should_execute_dca(self, last_order_time: Optional[datetime] = None) -> bool:
"""
Determine if DCA order should execute.
Args:
last_order_time: Datetime of last order, or None if never ordered
Returns:
True if interval has elapsed, False otherwise
"""
if last_order_time is None:
return True
elapsed = datetime.utcnow() - last_order_time
interval = timedelta(hours=self.interval_hours)
return elapsed >= interval
def calculate_buy_quantity(self, current_price: float) -> float:
"""
Calculate BTC quantity from USD amount.
Args:
current_price: Current BTC price in USD
Returns:
Quantity in BTC (truncated to 4 decimals per Binance)
"""
if current_price <= 0:
raise ValueError("Price must be positive")
quantity = self.dca_amount_usd / current_price
# Truncate to 4 decimals (Binance precision for spot)
quantity = int(quantity * 10000) / 10000
return quantity
def calculate_stop_loss_price(self, entry_price: float) -> float:
"""
Calculate stop loss price.
Args:
entry_price: Price at which order was filled
Returns:
Stop loss price (entry - percentage)
"""
stop_price = entry_price * (1 - self.stop_loss_percent / 100)
# Round to 2 decimals per Binance USDT pair precision
return round(stop_price, 2)

View File

@ -1,141 +0,0 @@
"""
ML-Powered Adaptive Trading Strategy für Trading Bot V2
Ersetzt die alte DCA-Strategie
"""
from datetime import datetime, timedelta
from typing import Optional, Dict, List
from pydantic import BaseModel
import joblib
import numpy as np
import pandas as pd
class MLStrategy(BaseModel):
"""ML-based trading strategy with adaptive position sizing."""
trading_pair: str = "BTCUSDT" # Oder ETH, SOL
min_prob_threshold: float = 0.60 # Only trade if prob >= 60%
base_position_size_pct: float = 0.01 # 1% of account
risk_per_trade_pct: float = 0.05 # 5% max risk
stop_loss_percent: float = 3.0 # 3% stop loss
take_profit_percent: float = 5.0 # 5% take profit
# State tracking
consecutive_wins: int = 0
total_trades: int = 0
win_rate: float = 0.0
class Config:
validate_assignment = True
def should_trade_today(self) -> bool:
"""Check if we should attempt trading today."""
return True # Always check for signals
def calculate_position_size(self, account_balance: float, win_probability: float) -> float:
"""
Calculate adaptive position size based on:
- Account balance
- Win probability
- Consecutive wins (growth)
Args:
account_balance: Total account balance in USDT
win_probability: ML model predicted win probability (0.0 - 1.0)
Returns:
Position size in USDT
"""
# Base position
base_pos = account_balance * self.base_position_size_pct
# Multiplier based on consecutive wins
win_multiplier = 1.0
if self.consecutive_wins >= 5:
win_multiplier = 3.0 # 3x after 5 wins
elif self.consecutive_wins >= 3:
win_multiplier = 2.0 # 2x after 3 wins
elif self.consecutive_wins >= 1:
win_multiplier = 1.5 # 1.5x after 1 win
# Confidence boost (up to +50%)
confidence_pct = win_probability / self.min_prob_threshold # Ratio above threshold
confidence_boost = min((confidence_pct - 1.0) * 0.5, 0.5) # Max +50%
# Calculate final position
position = base_pos * win_multiplier * (1.0 + confidence_boost)
# Cap at max risk
max_position = account_balance * self.risk_per_trade_pct
position = min(position, max_position)
return position
def calculate_stop_loss_price(self, entry_price: float) -> float:
"""Calculate stop loss price (entry - X%)."""
return entry_price * (1.0 - self.stop_loss_percent / 100.0)
def calculate_take_profit_price(self, entry_price: float) -> float:
"""Calculate take profit price (entry + X%)."""
return entry_price * (1.0 + self.take_profit_percent / 100.0)
def record_trade_result(self, is_win: bool):
"""Update strategy state after trade closes."""
self.total_trades += 1
if is_win:
self.consecutive_wins += 1
else:
self.consecutive_wins = 0 # Reset on loss
# Update win rate
wins = int(self.win_rate * (self.total_trades - 1))
if is_win:
wins += 1
self.win_rate = wins / self.total_trades if self.total_trades > 0 else 0.0
def get_strategy_status(self) -> Dict:
"""Return current strategy state."""
return {
'pair': self.trading_pair,
'threshold': f"{self.min_prob_threshold:.0%}",
'consecutive_wins': self.consecutive_wins,
'total_trades': self.total_trades,
'win_rate': f"{self.win_rate:.1%}",
'position_multiplier': self._get_current_multiplier(),
}
def _get_current_multiplier(self) -> float:
"""Get current position size multiplier."""
if self.consecutive_wins >= 5:
return 3.0
elif self.consecutive_wins >= 3:
return 2.0
elif self.consecutive_wins >= 1:
return 1.5
return 1.0
def predict(self, price: float) -> str:
"""
Generate trading signal based on simple technical analysis.
Since we don't have a full ML model loaded, use momentum-based rules.
In production, this would use a trained ML model to predict 60%+ probability.
For now: simplified signal generation for testing.
Args:
price: Current price
Returns:
'BUY', 'SELL', or 'HOLD'
"""
import random
# TEMPORARY: Generate random signals with 40% BUY probability
# In production: replace with actual ML model prediction
random_prob = random.random()
if random_prob > 0.60: # 40% chance of BUY signal
return 'BUY'
else:
return 'HOLD'

View File

@ -1,105 +0,0 @@
#!/usr/bin/env python3
"""
Validiere alle Credentials (Binance API + Telegram Bot)
"""
import os
import sys
# Change to bot directory FIRST
os.chdir('/home/marc/bot-deploy')
# Dann .env laden
from dotenv import load_dotenv
load_dotenv(dotenv_path='/home/marc/bot-deploy/.env')
binance_key = os.getenv('BINANCE_API_KEY_LIVE')
binance_secret = os.getenv('BINANCE_API_SECRET_LIVE')
telegram_token = os.getenv('TELEGRAM_BOT_TOKEN')
chat_id = os.getenv('TELEGRAM_CHAT_ID')
print('🔍 VALIDIERE CREDENTIALS...\n')
# Test 1: Binance API
print('1⃣ BINANCE API')
try:
from binance.client import Client
client = Client(binance_key, binance_secret)
# Test read
account = client.get_account()
print(f' ✅ Connected')
# Get balances
balances = [b for b in account['balances'] if float(b['free']) + float(b['locked']) > 0]
usdt = next((b for b in account['balances'] if b['asset'] == 'USDT'), None)
print(f' ✅ Balances gelesen: {len(balances)} coins')
print(f' ✅ USDT: {float(usdt["free"]):.2f} (free) + {float(usdt["locked"]):.2f} (locked)')
# Test: Get symbol info (dry, kein order)
symbol_info = client.get_symbol_info('BTCUSDT')
print(f' ✅ Symbol Info abrufbar')
# Test: Ping
ping = client.ping()
print(f' ✅ API Ping: OK')
print(f' ✅ Authentifizierung: ACTIVE (Keys working)')
except Exception as e:
print(f' ❌ FAILED: {str(e)[:100]}')
sys.exit(1)
# Test 2: Telegram Bot
print('\n2⃣ TELEGRAM BOT')
try:
import requests
url = f'https://api.telegram.org/bot{telegram_token}/getMe'
response = requests.get(url, timeout=10)
if response.status_code != 200:
print(f' ❌ HTTP {response.status_code}')
sys.exit(1)
bot_info = response.json()
if not bot_info.get('ok'):
print(f' ❌ API Error: {bot_info}')
sys.exit(1)
print(f' ✅ Connected')
print(f' ✅ Bot Name: @{bot_info["result"]["username"]}')
print(f' ✅ Bot ID: {bot_info["result"]["id"]}')
# Test send message
send_url = f'https://api.telegram.org/bot{telegram_token}/sendMessage'
payload = {
'chat_id': chat_id,
'text': '✅ Credentials TEST — Alle APIs funktionieren!'
}
send_response = requests.post(send_url, json=payload, timeout=10)
if send_response.status_code == 200:
result = send_response.json()
if result.get('ok'):
msg_id = result['result']['message_id']
print(f' ✅ Test Message versendet (ID: {msg_id})')
else:
print(f' ❌ Send Error: {result}')
sys.exit(1)
else:
print(f' ❌ HTTP {send_response.status_code}')
sys.exit(1)
except Exception as e:
print(f' ❌ FAILED: {str(e)[:100]}')
sys.exit(1)
print('\n' + '='*70)
print('✅ ALLE CREDENTIALS VALIDIERT UND FUNKTIONSFÄHIG!')
print('='*70)
print('\nDetails:')
print(f' BINANCE_API_KEY_LIVE: {len(binance_key)} chars ✅')
print(f' BINANCE_API_SECRET_LIVE: {len(binance_secret)} chars ✅')
print(f' TELEGRAM_BOT_TOKEN: {len(telegram_token)} chars ✅')
print(f' TELEGRAM_CHAT_ID: {chat_id}')

View File

@ -1,336 +0,0 @@
#!/usr/bin/env python3
"""Trading Bot Dashboard v0.4 (Dynamic Positioning) - Auto-load 1-Day chart on page load"""
import sqlite3
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from binance.client import Client
from datetime import datetime
import json, os, time
app = FastAPI()
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
DB = '/home/marc/bot-deploy/pnl_charts.db'
def init_db():
c = sqlite3.connect(DB).cursor()
c.execute("""CREATE TABLE IF NOT EXISTS history (ts INTEGER PRIMARY KEY, pv REAL, pu REAL, pp REAL, uf REAL, ap INTEGER)""")
sqlite3.connect(DB).commit()
init_db()
@app.get('/api/state')
async def state():
try:
acc = binance.get_account()
bal = {}
for a in acc['balances']:
ast, free, locked = a['asset'], float(a['free']), float(a['locked'])
if free + locked > 1e-5:
bal[ast] = {'free': free, 'locked': locked, 'total': free + locked}
prices = {'USDT': 1.0}
for p in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
try:
t = binance.get_ticker(symbol=p)
prices[p.replace('USDT', '')] = float(t['lastPrice'])
except: pass
pv = sum(bal.get(a, {}).get('total', 0) * prices.get(a, 0) for a in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT'])
uf = bal.get('USDT', {}).get('free', 0)
# Get latest P&L from database
try:
conn = sqlite3.connect('/home/marc/bot-deploy/pnl_charts.db')
row = conn.execute('SELECT pu, pp FROM history ORDER BY ts DESC LIMIT 1').fetchone()
conn.close()
if row:
pu, pp = row[0], row[1]
else:
pu, pp = 0.0, 0.0
except:
pu, pp = 0.0, 0.0
ap = 0
try:
with open('/home/marc/bot-deploy/active_trades.json') as f:
ap = json.load(f).get('count', 0)
except: pass
conn = sqlite3.connect(DB)
conn.execute("INSERT OR REPLACE INTO history VALUES (?, ?, ?, ?, ?, ?)", (int(time.time()), pv, pu, pp, uf, ap))
conn.commit()
conn.close()
return {'portfolio_value': round(pv, 2), 'pnl_usdt': round(pu, 2), 'pnl_pct': round(pp, 2), 'usdt_free': round(uf, 2), 'active_positions': ap, 'balance': bal, 'prices': prices}
except Exception as e:
return {'error': str(e)}
@app.get('/api/pnl-history')
async def history(hours: int = 24):
conn = sqlite3.connect(DB)
cutoff = int(time.time()) - hours * 3600
rows = conn.execute("SELECT ts, pp, pu FROM history WHERE ts > ? ORDER BY ts", (cutoff,)).fetchall()
conn.close()
ts_list, pcts, usdts = [], [], []
seen_ts = set()
for t, p, u in rows:
dt = datetime.fromtimestamp(t)
if hours <= 24:
ts = dt.strftime('%H:00')
else:
ts = dt.strftime('%d.%m.%y')
if ts in seen_ts:
continue
seen_ts.add(ts)
ts_list.append(ts)
pcts.append(round(p, 2))
usdts.append(round(u, 2))
return {'timestamps': ts_list, 'pnl_pcts': pcts, 'pnl_usdts': usdts,
'current_pct': pcts[-1] if pcts else 0, 'current_usdt': usdts[-1] if usdts else 0,
'min_pct': min(pcts) if pcts else 0, 'min_usdt': min(usdts) if usdts else 0,
'max_pct': max(pcts) if pcts else 0, 'max_usdt': max(usdts) if usdts else 0,
'avg_pct': sum(pcts)/len(pcts) if pcts else 0, 'avg_usdt': sum(usdts)/len(usdts) if usdts else 0}
@app.get('/')
async def dashboard():
html = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Trading Bot v0.4</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:Segoe UI,Arial;background:#1e1e1e;color:#d0d0d0;min-height:100vh;padding:20px}
@media(max-width:768px){body{padding:10px}.container{max-width:100%}}
.container{max-width:1400px;margin:0 auto}
.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:30px;padding:20px;background:rgba(0,255,136,.05);border:1px solid rgba(0,255,136,.2);border-radius:10px}
@media(max-width:768px){.header{flex-direction:column;gap:15px;padding:15px}}
.header h1{font-size:28px;color:#00ff88}
@media(max-width:768px){.header h1{font-size:20px}}
.status{padding:8px 16px;background:rgba(0,255,136,.1);border:2px solid #00ff88;border-radius:20px;font-weight:bold}
.tabs{display:flex;gap:10px;margin-bottom:20px}
.btn{padding:12px 24px;background:0;border:0;color:#999;cursor:pointer;font-size:16px;border-bottom:3px solid transparent;transition:all .3s}
@media(max-width:768px){.btn{padding:10px 16px;font-size:14px}}
.btn:hover{color:#00ff88}
.btn.active{color:#00ff88;border-bottom-color:#00ff88}
.tab{display:none}
.tab.active{display:block}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin-bottom:30px}
@media(max-width:768px){.grid{grid-template-columns:1fr}}
.card{background:rgba(255,255,255,.03);border:1px solid rgba(0,255,136,.2);border-radius:10px;padding:20px;transition:all .3s}
@media(max-width:768px){.card{padding:15px}}
.card:hover{border-color:rgba(0,255,136,.5)}
.lbl{font-size:12px;color:#888;text-transform:uppercase;margin-bottom:8px}
.val{font-size:24px;color:#00ff88;font-weight:bold}
@media(max-width:768px){.val{font-size:20px}}
.sub{font-size:14px;color:#999}
.collapse-header{display:flex;justify-content:space-between;align-items:center;padding:15px 20px;background:transparent;border:1px solid rgba(0,255,136,.2);border-radius:10px;cursor:pointer;margin:20px 0 15px 0}
@media(max-width:768px){.collapse-header{padding:12px 15px}}
.collapse-header h3{color:#00ff88;font-size:16px;margin:0}
@media(max-width:768px){.collapse-header h3{font-size:14px}}
.collapse-toggle{color:#00ff88;font-size:20px}
.holdings{display:none;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin-bottom:20px}
@media(max-width:768px){.holdings{grid-template-columns:1fr}}
.holdings.open{display:grid}
.chart-box{background:rgba(255,255,255,.03);border:1px solid rgba(0,255,136,.2);border-radius:10px;padding:20px}
@media(max-width:768px){.chart-box{padding:15px}}
.title{font-size:18px;color:#00ff88;margin-bottom:20px;font-weight:bold}
@media(max-width:768px){.title{font-size:14px}}
.times{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap}
.time{padding:8px 16px;background:rgba(0,255,136,.1);border:1px solid rgba(0,255,136,.3);color:#00ff88;border-radius:5px;cursor:pointer;font-size:14px}
@media(max-width:768px){.time{padding:6px 12px;font-size:12px}}
.time:hover{background:rgba(0,255,136,.2)}
.time.active{background:rgba(0,255,136,.3)}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:15px;margin-top:20px}
@media(max-width:768px){.stats{grid-template-columns:repeat(2,1fr);gap:10px}}
.stat{background:rgba(0,255,136,.05);border:1px solid rgba(0,255,136,.15);padding:15px;border-radius:8px;text-align:center}
@media(max-width:768px){.stat{padding:12px}}
.stat-l{font-size:11px;color:#888;text-transform:uppercase;margin-bottom:5px}
@media(max-width:768px){.stat-l{font-size:9px}}
.stat-v{font-size:18px;color:#00ff88;font-weight:bold;display:block}
@media(max-width:768px){.stat-v{font-size:14px}}
.stat-sub{font-size:11px;color:#666;margin-top:3px;display:block}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div><h1>🤖 Trading Bot v0.4</h1><p>P&L Analytics</p></div>
<div class="status" id="st"> LOADING</div>
</div>
<div class="tabs">
<button class="btn active" onclick="switchTab(event, 'portfolio')">📊 Portfolio</button>
<button class="btn" onclick="switchTab(event, 'analytics')">📈 Analytics</button>
</div>
<div id="portfolio" class="tab active">
<div class="grid">
<div class="card"><div class="lbl">Portfolio</div><div class="val" id="pv">-</div></div>
<div class="card"><div class="lbl">P&L</div><div class="val" id="pl">-</div><div class="sub" id="pp">-</div></div>
<div class="card"><div class="lbl">USDT</div><div class="val" id="uf">-</div></div>
<div class="card"><div class="lbl">Trades</div><div class="val" id="tr">-</div></div>
</div>
<div class="collapse-header" onclick="toggleHoldings()">
<h3>Holdings</h3>
<span class="collapse-toggle" id="toggle-icon"></span>
</div>
<div class="grid holdings" id="holdings"></div>
</div>
<div id="analytics" class="tab">
<div class="chart-box">
<div class="title">📈 P&L Performance (Live)</div>
<div class="times">
<button class="time active" onclick="loadChart(24, event)">1 Day</button>
<button class="time" onclick="loadChart(168, event)">1 Week</button>
<button class="time" onclick="loadChart(720, event)">1 Month</button>
</div>
<canvas id="chart" height="100"></canvas>
<div class="stats">
<div class="stat">
<div class="stat-l">Current</div>
<span class="stat-v" id="cur-pct">-</span>
<span class="stat-sub" id="cur-usd">-</span>
</div>
<div class="stat">
<div class="stat-l">Min</div>
<span class="stat-v" id="min-pct">-</span>
<span class="stat-sub" id="min-usd">-</span>
</div>
<div class="stat">
<div class="stat-l">Max</div>
<span class="stat-v" id="max-pct">-</span>
<span class="stat-sub" id="max-usd">-</span>
</div>
<div class="stat">
<div class="stat-l">Avg</div>
<span class="stat-v" id="avg-pct">-</span>
<span class="stat-sub" id="avg-usd">-</span>
</div>
</div>
</div>
</div>
</div>
<script>
let chartObj = null;
function switchTab(e, tabName) {
document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.btn').forEach(el => el.classList.remove('active'));
document.getElementById(tabName).classList.add('active');
e.target.classList.add('active');
}
function toggleHoldings() {
const h = document.getElementById('holdings');
const i = document.getElementById('toggle-icon');
h.classList.toggle('open');
i.textContent = h.classList.contains('open') ? '' : '';
}
async function updatePortfolio() {
const res = await fetch('/api/state');
const data = await res.json();
if (data.error) return;
document.getElementById('pv').textContent = '$' + data.portfolio_value.toFixed(2);
document.getElementById('pl').textContent = '$' + data.pnl_usdt.toFixed(2);
document.getElementById('pp').textContent = data.pnl_pct.toFixed(2) + '%';
document.getElementById('uf').textContent = '$' + data.usdt_free.toFixed(2);
document.getElementById('tr').textContent = data.active_positions;
document.getElementById('st').textContent = '● LIVE';
const hh = document.getElementById('holdings');
hh.innerHTML = '';
for (const [asset, info] of Object.entries(data.balance)) {
if (asset !== 'USDT' && info.total > 1e-4) {
const price = data.prices[asset] || 0;
const usdValue = info.total * price;
hh.innerHTML += '<div class="card"><div class="lbl">' + asset + '</div><div class="val">' + info.total.toFixed(4) + '</div><div class="sub">≈ $' + usdValue.toFixed(2) + '</div></div>';
}
}
}
async function loadChart(hours, e) {
if (e) {
document.querySelectorAll('.time').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
}
const res = await fetch('/api/pnl-history?hours=' + hours);
const data = await res.json();
const ctx = document.getElementById('chart').getContext('2d');
if (chartObj) chartObj.destroy();
const col = data.current_pct >= 0 ? '#00ff88' : '#ff4444';
const bg = data.current_pct >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,68,68,0.1)';
chartObj = new Chart(ctx, {
type: 'line',
data: {
labels: data.timestamps,
datasets: [{
label: 'P&L %',
data: data.pnl_pcts,
borderColor: col,
backgroundColor: bg,
fill: true,
tension: 0.4,
pointRadius: 2,
pointBackgroundColor: col,
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: { legend: { labels: { color: '#888' } } },
scales: {
y: { grid: { color: 'rgba(0,255,136,0.1)' }, ticks: { color: '#888' } },
x: { grid: { color: 'rgba(0,255,136,0.1)' }, ticks: { color: '#888' } }
}
}
});
const fmt = v => (v >= 0 ? '+' : '') + v.toFixed(2);
document.getElementById('cur-pct').textContent = data.current_pct.toFixed(2) + '%';
document.getElementById('cur-usd').textContent = '$' + fmt(data.current_usdt);
document.getElementById('min-pct').textContent = data.min_pct.toFixed(2) + '%';
document.getElementById('min-usd').textContent = '$' + fmt(data.min_usdt);
document.getElementById('max-pct').textContent = data.max_pct.toFixed(2) + '%';
document.getElementById('max-usd').textContent = '$' + fmt(data.max_usdt);
document.getElementById('avg-pct').textContent = data.avg_pct.toFixed(2) + '%';
document.getElementById('avg-usd').textContent = '$' + fmt(data.avg_usdt);
}
setInterval(updatePortfolio, 10000);
updatePortfolio();
loadChart(24, null);
</script>
</body>
</html>"""
return HTMLResponse(content=html)
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=7000)

View File

@ -1,670 +0,0 @@
#!/usr/bin/env python3
from fastapi import FastAPI, Response
from binance.client import Client
import json, os, time
from datetime import datetime
app = FastAPI()
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
price_cache = {'prices': {}, 'timestamp': 0}
def get_live_prices():
global price_cache
if time.time() - price_cache['timestamp'] < 5:
return price_cache['prices']
prices = {'USDT': 1.0}
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
try:
ticker = binance.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
price_cache['prices'] = prices
price_cache['timestamp'] = time.time()
return prices
def load_bot_state():
state_file = '/home/marc/bot-deploy/trades.json'
if os.path.exists(state_file):
try:
with open(state_file) as f:
return json.load(f)
except:
pass
return {'current': {}, 'completed': [], 'balance': {}}
@app.get('/api/state')
async def get_state():
try:
account = binance.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
prices = get_live_prices()
portfolio_value = 0
tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
for asset in tracked_assets:
if asset in balance:
data = balance[asset]
price = prices.get(asset, 0)
portfolio_value += data['total'] * price
usdt_free = balance.get('USDT', {}).get('free', 0)
# P&L CALCULATION
initial_capital = 137.79
pnl_usdt = portfolio_value - initial_capital
pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
pnl_status = "🟢 PROFIT" if pnl_usdt > 0.01 else ("🔴 LOSS" if pnl_usdt < -0.01 else "⚪ BREAK")
pnl_color = "accent" if pnl_usdt > 0.01 else ("negative" if pnl_usdt < -0.01 else "neutral")
# Count active positions from bot's active_trades.json (REAL source of truth)
active_positions = 0
try:
import json
with open('/home/marc/bot-deploy/active_trades.json', 'r') as f:
bot_state = json.load(f)
active_positions = bot_state.get('count', 0)
except:
# Fallback: count from Binance open orders
try:
open_orders = binance.get_open_orders()
active_positions = len(open_orders)
except:
# Last resort: count locked coins
active_positions = 0
for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
if asset in balance and balance[asset]['locked'] > 0.00001:
active_positions += 1
trades = load_bot_state()
return {
'balance': balance,
'portfolio_value': round(portfolio_value, 2),
'usdt_free': round(usdt_free, 2),
'active_positions': active_positions, # ← NEW: Real count!
'current_trades': trades.get('current', {}),
'pnl_usdt': round(pnl_usdt, 2),
'pnl_pct': round(pnl_pct, 2),
'pnl_status': pnl_status,
'pnl_color': pnl_color,
'completed_trades': trades.get('completed', []),
'prices': prices,
'timestamp': datetime.now().isoformat()
}
except Exception as e:
return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0, 'active_positions': 0}
@app.get('/')
async def root():
state = await get_state()
portfolio_val = state.get('portfolio_value', 0)
usdt_free = state.get('usdt_free', 0)
trades_count = state.get('active_positions', 0) # ← FIXED: Use real count!
prices = state.get('prices', {})
# P&L from state
pnl_usdt = state.get("pnl_usdt", 0)
pnl_pct = state.get("pnl_pct", 0)
pnl_status = state.get("pnl_status", "⚪ BREAK")
pnl_color = state.get("pnl_color", "neutral")
html = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Trading Bot V0.3</title>
<style>
:root {{
--bg-primary: #1a1a1a;
--bg-secondary: #252525;
--bg-tertiary: #2a2a2a;
--bg-hover: #303030;
--border: #404040;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--accent: #00ff88;
--spacing: 1rem;
}}
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
html, body {{
width: 100%;
height: 100%;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Monaco', 'Menlo', monospace;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
font-size: clamp(14px, 2vw, 16px);
overflow-x: hidden;
}}
.app-container {{
width: 100%;
min-height: 100vh;
padding: calc(var(--spacing) * 1.5);
}}
.header {{
margin-bottom: calc(var(--spacing) * 2.5);
}}
.logo {{
font-size: clamp(24px, 6vw, 32px);
font-weight: bold;
color: var(--accent);
margin-bottom: 0.5rem;
}}
.version {{
font-size: clamp(11px, 2vw, 13px);
color: var(--text-secondary);
}}
/* ===== METRICS GRID ===== */
.metrics-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: calc(var(--spacing) * 1.5);
margin-bottom: calc(var(--spacing) * 3);
}}
.metric-card {{
background: var(--bg-tertiary);
border: 1px solid var(--border);
padding: calc(var(--spacing) * 1.5);
border-radius: 8px;
transition: all 0.3s ease;
cursor: pointer;
min-height: 140px;
display: flex;
flex-direction: column;
justify-content: space-between;
}}
.metric-card:active {{
transform: scale(0.98);
}}
.metric-card:hover {{
background: var(--bg-hover);
border-color: var(--accent);
box-shadow: 0 0 20px rgba(0, 255, 136, 0.1);
}}
.metric-label {{
font-size: clamp(11px, 1.5vw, 12px);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 1rem;
}}
.metric-value {{
font-size: clamp(20px, 5vw, 32px);
font-weight: bold;
color: var(--text-primary);
word-break: break-word;
}}
.metric-value.accent {{
color: var(--accent);
}}
/* ===== SECTIONS ===== */
.section {{
margin-bottom: calc(var(--spacing) * 3);
}}
.section-header {{
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
padding: calc(var(--spacing) * 0.75) 0;
border-bottom: 1px solid var(--border);
margin-bottom: calc(var(--spacing) * 1.25);
user-select: none;
transition: all 0.2s ease;
}}
.section-header:hover {{
color: var(--accent);
}}
.section-title {{
font-size: clamp(13px, 2.5vw, 15px);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 1.2px;
transition: color 0.2s ease;
}}
.section-toggle {{
font-size: clamp(14px, 2vw, 16px);
color: var(--text-secondary);
transition: transform 0.3s ease;
margin-left: 0.5rem;
}}
.section-toggle.expanded {{
transform: rotate(180deg);
}}
.section-content {{
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}}
.section-content.expanded {{
max-height: 2000px;
}}
/* ===== TABLES ===== */
.table-wrapper {{
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--bg-tertiary);
}}
table {{
width: 100%;
border-collapse: collapse;
font-size: clamp(12px, 2vw, 14px);
}}
th {{
background: var(--bg-tertiary);
color: var(--text-secondary);
padding: calc(var(--spacing) * 1);
text-align: left;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
font-size: clamp(10px, 1.5vw, 12px);
}}
td {{
padding: calc(var(--spacing) * 0.875);
border-bottom: 1px solid var(--border);
}}
tr:last-child td {{
border-bottom: none;
}}
tbody tr {{
transition: background 0.2s ease;
}}
tbody tr:hover {{
background: var(--bg-hover);
}}
tbody tr:active {{
background: var(--bg-secondary);
}}
.price-positive {{
color: var(--accent);
font-weight: 600;
}}
/* ===== RESPONSIVE ===== */
@media (max-width: 1200px) {{
.metrics-grid {{
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}}
}}
@media (max-width: 768px) {{
:root {{
--spacing: 0.875rem;
}}
.app-container {{
padding: calc(var(--spacing) * 1.25);
}}
.metrics-grid {{
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing);
}}
.metric-card {{
padding: var(--spacing);
min-height: 120px;
}}
.metric-label {{
margin-bottom: 0.75rem;
font-size: 10px;
}}
.metric-value {{
font-size: clamp(18px, 4vw, 26px);
}}
.section {{
margin-bottom: calc(var(--spacing) * 1.75);
}}
th, td {{
padding: calc(var(--spacing) * 0.75);
font-size: 11px;
}}
th {{
font-size: 10px;
}}
}}
@media (max-width: 480px) {{
:root {{
--spacing: 0.75rem;
}}
.app-container {{
padding: var(--spacing);
}}
.metrics-grid {{
grid-template-columns: repeat(2, 1fr);
gap: calc(var(--spacing) * 0.75);
}}
.metric-card {{
padding: calc(var(--spacing) * 0.875);
min-height: 110px;
}}
.metric-label {{
font-size: 9px;
margin-bottom: 0.5rem;
letter-spacing: 0.5px;
}}
.metric-value {{
font-size: clamp(16px, 3.5vw, 22px);
}}
.logo {{
font-size: clamp(20px, 5vw, 26px);
}}
.version {{
font-size: 10px;
}}
.section-title {{
font-size: 11px;
}}
th, td {{
padding: calc(var(--spacing) * 0.6);
font-size: 9px;
}}
th {{
font-size: 8px;
}}
.table-wrapper {{
border-radius: 6px;
}}
}}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar {{
width: 6px;
height: 6px;
}}
::-webkit-scrollbar-track {{
background: var(--bg-secondary);
}}
::-webkit-scrollbar-thumb {{
background: var(--border);
border-radius: 3px;
}}
::-webkit-scrollbar-thumb:hover {{
background: var(--text-secondary);
}}
/* ===== ANIMATIONS ===== */
@keyframes fadeIn {{
from {{
opacity: 0;
transform: translateY(10px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
.metric-card {{
animation: fadeIn 0.5s ease forwards;
}}
.metric-card:nth-child(2) {{
animation-delay: 0.1s;
}}
.metric-card:nth-child(3) {{
animation-delay: 0.2s;
}}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<div class="logo">💰 Trading Bot</div>
<div class="version">V0.3</div>
</div>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Portfolio Value</div>
<div class="metric-value">${portfolio_val:.2f}</div>
</div>
<div class="metric-card">
<div class="metric-label">USDT Available</div>
<div class="metric-value accent">${usdt_free:.2f}</div>
</div>
<div class="metric-card">
<div class="metric-label">Open Positions</div>
<div class="metric-value">{trades_count}</div>
</div>
<div class="metric-card">
<div class="metric-label">Total P&L</div>
<div class="metric-value {pnl_color}">${pnl_usdt:+.2f} ({pnl_pct:+.1f}%)</div>
</div>
<div class="metric-card">
<div class="metric-label">P&L Status</div>
<div class="metric-value {pnl_color}">{pnl_status}</div>
</div>
</div>
<div class="section">
<div class="section-header" onclick="toggleSection(this)">
<div class="section-title">Live Prices</div>
<div class="section-toggle"></div>
</div>
<div class="section-content">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Asset</th>
<th>Price</th>
</tr>
</thead>
<tbody>'''
for asset, price in prices.items():
html += f'''<tr>
<td>{asset}</td>
<td class="price-positive">${price:.2f}</td>
</tr>'''
html += '''</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="section-header" onclick="toggleSection(this)">
<div class="section-title">Holdings</div>
<div class="section-toggle"></div>
</div>
<div class="section-content">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Asset</th>
<th>Free</th>
<th>Total</th>
<th>Value</th>
</tr>
</thead>
<tbody>'''
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
balance = state.get('balance', {})
for asset in tracked:
if asset in balance:
data = balance[asset]
price = prices.get(asset, 0)
value = data['total'] * price
html += f'''<tr>
<td>{asset}</td>
<td>{data['free']:.4f}</td>
<td>{data['total']:.4f}</td>
<td class="price-positive">${value:.2f}</td>
</tr>'''
html += '''</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function toggleSection(header) {
const content = header.nextElementSibling;
const toggle = header.querySelector('.section-toggle');
content.classList.toggle('expanded');
toggle.classList.toggle('expanded');
}
// Refresh prices every 5 seconds
setInterval(function() {{
location.reload();
}}, 10000);
</script>
</body>
</html>'''
return Response(content=html, media_type='text/html')
@app.get('/api/pnl')
async def get_pnl():
"""Get live Profit & Loss (P&L) calculation"""
try:
account = binance.get_account()
# Get current account value
prices = get_live_prices()
current_value = 0
for asset_data in account['balances']:
asset = asset_data['asset']
total = float(asset_data['free']) + float(asset_data['locked'])
if total > 0.00001 and asset != 'LDDOGE' and asset != 'LDBTTC':
price = prices.get(asset, 1.0)
current_value += total * price
# Benchmark: Initial capital was $137.79 (before trading)
# This should be stored, but for now use a reference
initial_capital = 137.79
pnl_usdt = current_value - initial_capital
pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
# Get open trades for unrealized portion
state_file = '/home/marc/bot-deploy/trades.json'
open_trades = {}
if os.path.exists(state_file):
try:
data = json.load(state_file)
open_trades = data.get('current', {})
except:
pass
return {
'current_value': round(current_value, 2),
'initial_capital': initial_capital,
'total_pnl_usdt': round(pnl_usdt, 2),
'total_pnl_percent': round(pnl_pct, 2),
'status': '🟢 PROFIT' if pnl_usdt > 0 else ('🔴 LOSS' if pnl_usdt < 0 else '⚪ BREAK'),
'open_positions': len(open_trades),
'timestamp': datetime.now().isoformat()
}
except Exception as e:
return {'error': str(e)}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=7000)

View File

@ -1,688 +0,0 @@
#!/usr/bin/env python3
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from binance.client import Client
from datetime import datetime
import json, os, time, sqlite3
app = FastAPI()
env = {}
with open('/home/marc/bot-deploy/.env') as f:
for line in f:
k, _, v = line.partition('=')
env[k.strip()] = v.strip()
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
DB_PATH = '/home/marc/bot-deploy/pnl_history.db'
def init_db():
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("""CREATE TABLE IF NOT EXISTS pnl_snapshots (timestamp INTEGER PRIMARY KEY, portfolio_value REAL, pnl_usdt REAL, pnl_pct REAL, usdt_free REAL, active_positions INTEGER)""")
conn.commit()
conn.close()
init_db()
# Rest des Codes...
deploy/.env') as f:
for line in f:
k,_,v = line.partition('=')
env[k.strip()] = v.strip()
binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
price_cache = {'prices': {}, 'timestamp': 0}
def get_live_prices():
global price_cache
if time.time() - price_cache['timestamp'] < 5:
return price_cache['prices']
prices = {'USDT': 1.0}
pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
for pair in pairs:
try:
ticker = binance.get_ticker(symbol=pair)
asset = pair.replace('USDT', '')
prices[asset] = float(ticker['lastPrice'])
except:
pass
price_cache['prices'] = prices
price_cache['timestamp'] = time.time()
return prices
def load_bot_state():
state_file = '/home/marc/bot-deploy/trades.json'
if os.path.exists(state_file):
try:
with open(state_file) as f:
return json.load(f)
except:
pass
return {'current': {}, 'completed': [], 'balance': {}}
@app.get('/api/state')
async def get_state():
try:
account = binance.get_account()
balance = {}
for asset_data in account['balances']:
asset = asset_data['asset']
free = float(asset_data['free'])
locked = float(asset_data['locked'])
total = free + locked
if total > 0.00001:
balance[asset] = {
'free': free,
'locked': locked,
'total': total
}
prices = get_live_prices()
portfolio_value = 0
tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
for asset in tracked_assets:
if asset in balance:
data = balance[asset]
price = prices.get(asset, 0)
portfolio_value += data['total'] * price
usdt_free = balance.get('USDT', {}).get('free', 0)
# P&L CALCULATION
initial_capital = 137.79
pnl_usdt = portfolio_value - initial_capital
pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
pnl_status = "🟢 PROFIT" if pnl_usdt > 0.01 else ("🔴 LOSS" if pnl_usdt < -0.01 else "⚪ BREAK")
pnl_color = "accent" if pnl_usdt > 0.01 else ("negative" if pnl_usdt < -0.01 else "neutral")
# Count active positions from bot's active_trades.json (REAL source of truth)
active_positions = 0
try:
import json
with open('/home/marc/bot-deploy/active_trades.json', 'r') as f:
bot_state = json.load(f)
active_positions = bot_state.get('count', 0)
except:
# Fallback: count from Binance open orders
try:
open_orders = binance.get_open_orders()
active_positions = len(open_orders)
except:
# Last resort: count locked coins
active_positions = 0
for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
if asset in balance and balance[asset]['locked'] > 0.00001:
active_positions += 1
trades = load_bot_state()
return {
'balance': balance,
'portfolio_value': round(portfolio_value, 2),
'usdt_free': round(usdt_free, 2),
'active_positions': active_positions, # ← NEW: Real count!
'current_trades': trades.get('current', {}),
'pnl_usdt': round(pnl_usdt, 2),
'pnl_pct': round(pnl_pct, 2),
'pnl_status': pnl_status,
'pnl_color': pnl_color,
'completed_trades': trades.get('completed', []),
'prices': prices,
'timestamp': datetime.now().isoformat()
}
except Exception as e:
return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0, 'active_positions': 0}
@app.get('/')
async def root():
state = await get_state()
portfolio_val = state.get('portfolio_value', 0)
usdt_free = state.get('usdt_free', 0)
trades_count = state.get('active_positions', 0) # ← FIXED: Use real count!
prices = state.get('prices', {})
# P&L from state
pnl_usdt = state.get("pnl_usdt", 0)
pnl_pct = state.get("pnl_pct", 0)
pnl_status = state.get("pnl_status", "⚪ BREAK")
pnl_color = state.get("pnl_color", "neutral")
html = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Trading Bot V0.3</title>
<style>
:root {{
--bg-primary: #1a1a1a;
--bg-secondary: #252525;
--bg-tertiary: #2a2a2a;
--bg-hover: #303030;
--border: #404040;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--accent: #00ff88;
--spacing: 1rem;
}}
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
html, body {{
width: 100%;
height: 100%;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Monaco', 'Menlo', monospace;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
font-size: clamp(14px, 2vw, 16px);
overflow-x: hidden;
}}
.app-container {{
width: 100%;
min-height: 100vh;
padding: calc(var(--spacing) * 1.5);
}}
.header {{
margin-bottom: calc(var(--spacing) * 2.5);
}}
.logo {{
font-size: clamp(24px, 6vw, 32px);
font-weight: bold;
color: var(--accent);
margin-bottom: 0.5rem;
}}
.version {{
font-size: clamp(11px, 2vw, 13px);
color: var(--text-secondary);
}}
/* ===== METRICS GRID ===== */
.metrics-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: calc(var(--spacing) * 1.5);
margin-bottom: calc(var(--spacing) * 3);
}}
.metric-card {{
background: var(--bg-tertiary);
border: 1px solid var(--border);
padding: calc(var(--spacing) * 1.5);
border-radius: 8px;
transition: all 0.3s ease;
cursor: pointer;
min-height: 140px;
display: flex;
flex-direction: column;
justify-content: space-between;
}}
.metric-card:active {{
transform: scale(0.98);
}}
.metric-card:hover {{
background: var(--bg-hover);
border-color: var(--accent);
box-shadow: 0 0 20px rgba(0, 255, 136, 0.1);
}}
.metric-label {{
font-size: clamp(11px, 1.5vw, 12px);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 1rem;
}}
.metric-value {{
font-size: clamp(20px, 5vw, 32px);
font-weight: bold;
color: var(--text-primary);
word-break: break-word;
}}
.metric-value.accent {{
color: var(--accent);
}}
/* ===== SECTIONS ===== */
.section {{
margin-bottom: calc(var(--spacing) * 3);
}}
.section-header {{
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
padding: calc(var(--spacing) * 0.75) 0;
border-bottom: 1px solid var(--border);
margin-bottom: calc(var(--spacing) * 1.25);
user-select: none;
transition: all 0.2s ease;
}}
.section-header:hover {{
color: var(--accent);
}}
.section-title {{
font-size: clamp(13px, 2.5vw, 15px);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 1.2px;
transition: color 0.2s ease;
}}
.section-toggle {{
font-size: clamp(14px, 2vw, 16px);
color: var(--text-secondary);
transition: transform 0.3s ease;
margin-left: 0.5rem;
}}
.section-toggle.expanded {{
transform: rotate(180deg);
}}
.section-content {{
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}}
.section-content.expanded {{
max-height: 2000px;
}}
/* ===== TABLES ===== */
.table-wrapper {{
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--bg-tertiary);
}}
table {{
width: 100%;
border-collapse: collapse;
font-size: clamp(12px, 2vw, 14px);
}}
th {{
background: var(--bg-tertiary);
color: var(--text-secondary);
padding: calc(var(--spacing) * 1);
text-align: left;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
font-size: clamp(10px, 1.5vw, 12px);
}}
td {{
padding: calc(var(--spacing) * 0.875);
border-bottom: 1px solid var(--border);
}}
tr:last-child td {{
border-bottom: none;
}}
tbody tr {{
transition: background 0.2s ease;
}}
tbody tr:hover {{
background: var(--bg-hover);
}}
tbody tr:active {{
background: var(--bg-secondary);
}}
.price-positive {{
color: var(--accent);
font-weight: 600;
}}
/* ===== RESPONSIVE ===== */
@media (max-width: 1200px) {{
.metrics-grid {{
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}}
}}
@media (max-width: 768px) {{
:root {{
--spacing: 0.875rem;
}}
.app-container {{
padding: calc(var(--spacing) * 1.25);
}}
.metrics-grid {{
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing);
}}
.metric-card {{
padding: var(--spacing);
min-height: 120px;
}}
.metric-label {{
margin-bottom: 0.75rem;
font-size: 10px;
}}
.metric-value {{
font-size: clamp(18px, 4vw, 26px);
}}
.section {{
margin-bottom: calc(var(--spacing) * 1.75);
}}
th, td {{
padding: calc(var(--spacing) * 0.75);
font-size: 11px;
}}
th {{
font-size: 10px;
}}
}}
@media (max-width: 480px) {{
:root {{
--spacing: 0.75rem;
}}
.app-container {{
padding: var(--spacing);
}}
.metrics-grid {{
grid-template-columns: repeat(2, 1fr);
gap: calc(var(--spacing) * 0.75);
}}
.metric-card {{
padding: calc(var(--spacing) * 0.875);
min-height: 110px;
}}
.metric-label {{
font-size: 9px;
margin-bottom: 0.5rem;
letter-spacing: 0.5px;
}}
.metric-value {{
font-size: clamp(16px, 3.5vw, 22px);
}}
.logo {{
font-size: clamp(20px, 5vw, 26px);
}}
.version {{
font-size: 10px;
}}
.section-title {{
font-size: 11px;
}}
th, td {{
padding: calc(var(--spacing) * 0.6);
font-size: 9px;
}}
th {{
font-size: 8px;
}}
.table-wrapper {{
border-radius: 6px;
}}
}}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar {{
width: 6px;
height: 6px;
}}
::-webkit-scrollbar-track {{
background: var(--bg-secondary);
}}
::-webkit-scrollbar-thumb {{
background: var(--border);
border-radius: 3px;
}}
::-webkit-scrollbar-thumb:hover {{
background: var(--text-secondary);
}}
/* ===== ANIMATIONS ===== */
@keyframes fadeIn {{
from {{
opacity: 0;
transform: translateY(10px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
.metric-card {{
animation: fadeIn 0.5s ease forwards;
}}
.metric-card:nth-child(2) {{
animation-delay: 0.1s;
}}
.metric-card:nth-child(3) {{
animation-delay: 0.2s;
}}
</style>
</head>
<body>
<div class="app-container">
<div class="header">
<div class="logo">💰 Trading Bot</div>
<div class="version">V0.3</div>
</div>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Portfolio Value</div>
<div class="metric-value">${portfolio_val:.2f}</div>
</div>
<div class="metric-card">
<div class="metric-label">USDT Available</div>
<div class="metric-value accent">${usdt_free:.2f}</div>
</div>
<div class="metric-card">
<div class="metric-label">Open Positions</div>
<div class="metric-value">{trades_count}</div>
</div>
<div class="metric-card">
<div class="metric-label">Total P&L</div>
<div class="metric-value {pnl_color}">${pnl_usdt:+.2f} ({pnl_pct:+.1f}%)</div>
</div>
<div class="metric-card">
<div class="metric-label">P&L Status</div>
<div class="metric-value {pnl_color}">{pnl_status}</div>
</div>
</div>
<div class="section">
<div class="section-header" onclick="toggleSection(this)">
<div class="section-title">Live Prices</div>
<div class="section-toggle"></div>
</div>
<div class="section-content">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Asset</th>
<th>Price</th>
</tr>
</thead>
<tbody>'''
for asset, price in prices.items():
html += f'''<tr>
<td>{asset}</td>
<td class="price-positive">${price:.2f}</td>
</tr>'''
html += '''</tbody>
</table>
</div>
</div>
</div>
<div class="section">
<div class="section-header" onclick="toggleSection(this)">
<div class="section-title">Holdings</div>
<div class="section-toggle"></div>
</div>
<div class="section-content">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Asset</th>
<th>Free</th>
<th>Total</th>
<th>Value</th>
</tr>
</thead>
<tbody>'''
tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
balance = state.get('balance', {})
for asset in tracked:
if asset in balance:
data = balance[asset]
price = prices.get(asset, 0)
value = data['total'] * price
html += f'''<tr>
<td>{asset}</td>
<td>{data['free']:.4f}</td>
<td>{data['total']:.4f}</td>
<td class="price-positive">${value:.2f}</td>
</tr>'''
html += '''</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function toggleSection(header) {
const content = header.nextElementSibling;
const toggle = header.querySelector('.section-toggle');
content.classList.toggle('expanded');
toggle.classList.toggle('expanded');
}
// Refresh prices every 5 seconds
setInterval(function() {{
location.reload();
}}, 10000);
</script>
</body>
</html>'''
return Response(content=html, media_type='text/html')
@app.get('/api/pnl')
async def get_pnl():
"""Get live Profit & Loss (P&L) calculation"""
try:
account = binance.get_account()
# Get current account value
prices = get_live_prices()
current_value = 0
for asset_data in account['balances']:
asset = asset_data['asset']
total = float(asset_data['free']) + float(asset_data['locked'])
if total > 0.00001 and asset != 'LDDOGE' and asset != 'LDBTTC':
price = prices.get(asset, 1.0)
current_value += total * price
# Benchmark: Initial capital was $137.79 (before trading)
# This should be stored, but for now use a reference
initial_capital = 137.79
pnl_usdt = current_value - initial_capital
pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
# Get open trades for unrealized portion
state_file = '/home/marc/bot-deploy/trades.json'
open_trades = {}
if os.path.exists(state_file):
try:
data = json.load(state_file)
open_trades = data.get('current', {})
except:
pass
return {
'current_value': round(current_value, 2),
'initial_capital': initial_capital,
'total_pnl_usdt': round(pnl_usdt, 2),
'total_pnl_percent': round(pnl_pct, 2),
'status': '🟢 PROFIT' if pnl_usdt > 0 else ('🔴 LOSS' if pnl_usdt < 0 else '⚪ BREAK'),
'open_positions': len(open_trades),
'timestamp': datetime.now().isoformat()
}
except Exception as e:
return {'error': str(e)}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=7000)