BrainDock/src/main_ml_fixed.py

206 lines
7.4 KiB
Python

#!/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())