221 lines
9.1 KiB
Python
221 lines
9.1 KiB
Python
#!/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())
|