diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc index d214414..9c76910 100644 Binary files a/src/__pycache__/main_ml.cpython-310.pyc and b/src/__pycache__/main_ml.cpython-310.pyc differ diff --git a/src/main_ml.py b/src/main_ml.py index a249cb9..5da8148 100644 --- a/src/main_ml.py +++ b/src/main_ml.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -"""Trading Bot v0.4 Hybrid - Dynamic Position Sizing + Trade Recovery""" -import os, json, time, logging +"""Trading Bot v0.4.1 - With continuous DB logging for P&L tracking""" +import os, json, time, logging, sqlite3 from datetime import datetime from dotenv import load_dotenv from binance.client import Client @@ -25,7 +25,7 @@ TAKE_PROFIT_PCT = 0.015 STOP_LOSS_PCT = -0.008 CYCLE_SEC = 60 -class TradingBotV04: +class TradingBotV041: def __init__(self): self.client = Client(API_KEY, API_SECRET) self.price_history = {sym: [] for sym in SYMBOLS} @@ -33,7 +33,7 @@ class TradingBotV04: self.portfolio_value = 0 self.max_trade_usdt = 0 - # TRADE RECOVERY: Recover orphaned trades from holdings + # TRADE RECOVERY try: account = self.client.get_account() for b in account['balances']: @@ -56,7 +56,7 @@ class TradingBotV04: except Exception as e: logger.warning(f"Recovery failed: {e}") - logger.info("[v0.4 INIT] Bot | Dynamic Sizing (Min 12 + 7%)") + logger.info("[v0.4.1 INIT] With continuous DB P&L logging") def get_fresh_balance(self): try: @@ -87,7 +87,7 @@ class TradingBotV04: 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}") + logger.info(f"[v0.4.1] USDT={usdt_available:.2f} | Portfolio={portfolio_value:.2f}") return usdt_available, portfolio_value except: return 0, 0 @@ -137,7 +137,7 @@ class TradingBotV04: is_min = all(current < p for p in recent[:-1]) if is_min: - logger.info(f"[SIGNAL] Local min: {symbol} @ {current}") + logger.info(f"[SIGNAL] Local min: {symbol}") return is_min @@ -160,9 +160,7 @@ class TradingBotV04: '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]") - + logger.info(f"[BUY] {symbol} {qty} @ {price}") return order except: return None @@ -179,7 +177,7 @@ class TradingBotV04: pnl_pct = ((current - entry) / entry) * 100 if pnl_pct >= TAKE_PROFIT_PCT * 100: - logger.info(f"[SELL-TP] {symbol} @ {current} | +{pnl_pct:.2f}%") + logger.info(f"[SELL-TP] {symbol} +{pnl_pct:.2f}%") try: self.client.order_market_sell(symbol=symbol, quantity=qty) del self.active_trades[symbol] @@ -187,7 +185,7 @@ class TradingBotV04: pass elif pnl_pct <= STOP_LOSS_PCT * 100: - logger.info(f"[SELL-SL] {symbol} @ {current} | {pnl_pct:.2f}%") + logger.info(f"[SELL-SL] {symbol} {pnl_pct:.2f}%") try: self.client.order_market_sell(symbol=symbol, quantity=qty) del self.active_trades[symbol] @@ -196,13 +194,36 @@ class TradingBotV04: except: pass + def save_pnl_to_db(self, portfolio_val, usdt_free): + """Save P&L data to database for Analytics""" + try: + conn = sqlite3.connect('/home/marc/bot-deploy/pnl_charts.db') + + # Get baseline (first row = reference) + baseline = conn.execute('SELECT pv FROM history ORDER BY ts ASC LIMIT 1').fetchone() + baseline_pv = baseline[0] if baseline else portfolio_val + + # Calculate P&L + pu = portfolio_val - baseline_pv + pp = (pu / baseline_pv * 100) if baseline_pv > 0 else 0 + + # Save + 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}% (vs baseline {baseline_pv:.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:.2f} < {MIN_TRADE_USDT}") + logger.warning(f"Low capital: {usdt_free}") logger.info("="*70) return @@ -225,8 +246,7 @@ class TradingBotV04: 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}") - + # Save trades try: temp = '/home/marc/bot-deploy/active_trades.json.tmp' with open(temp, 'w') as f: @@ -236,23 +256,27 @@ class TradingBotV04: 'portfolio_value': round(portfolio_val, 2), 'max_trade_usdt': round(self.max_trade_usdt, 2), 'timestamp': datetime.now().isoformat(), - 'version': 'v0.4-hybrid' + 'version': 'v0.4.1' }, f) os.replace(temp, '/home/marc/bot-deploy/active_trades.json') - except Exception as e: - logger.warning(f"Save failed: {e}") + except: + pass + # SAVE P&L to DB (EVERY CYCLE - THIS IS THE FIX!) + 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 = TradingBotV04() + bot = TradingBotV041() if len(sys.argv) > 1 and sys.argv[1] == '--once': bot.run_cycle() else: - logger.info("[v0.4 START] Bot cycle loop...") + logger.info("[v0.4.1 START] Bot running...") while True: try: bot.run_cycle() diff --git a/src/main_ml_backup.py b/src/main_ml_backup.py new file mode 100644 index 0000000..a249cb9 --- /dev/null +++ b/src/main_ml_backup.py @@ -0,0 +1,262 @@ +#!/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)