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