From 1fed8bd38e59312491c1608abc2e2f38e3a2c360 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Fri, 17 Jul 2026 22:00:01 +0200 Subject: [PATCH] Bot auto-update: src/__pycache__/main_ml.cpython-310.pyc,src/main_ml.py,src/main_ml_backup.py --- src/__pycache__/main_ml.cpython-310.pyc | Bin 7006 -> 7571 bytes src/main_ml.py | 64 ++++-- src/main_ml_backup.py | 262 ++++++++++++++++++++++++ 3 files changed, 306 insertions(+), 20 deletions(-) create mode 100644 src/main_ml_backup.py diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc index d214414de0b783b00d1b21b3c8a56a3003668b37..9c76910546296b71b9105d00c9eddbe1a70a7047 100644 GIT binary patch delta 3072 zcma)8O>i4Y74DvY{mYhO$@1SM@t+b~j7~8hK{y z;HeP`r5u*3O+j`}R0u2Ou*HeP=Cqulp!URt11g{>3MiHv9JY#Fme&)@iCr8RslR^H z{rbJvZ{B;|e|p=w5Yz$zFM{X8A1L>KKlUWpk9VJ(K2>K*yuuSq*pg5d6agckxsq6x z6v?IelBeudye=)2d}Y7l$4KSw2cQLsNTkOMeQ$(|wYkyXRZDDky0a2+H52CieYfk@yvR(hZhLLVa`B~HRhFX>Veq+97D5vBhcB2jYe z5hBMLj544OlAbL_{~SzZN<+$!I?zChtR7c~?w@F2;@{%*k0?$2y@ zgvlTa3Yf;t{^N&U zgmwj*0pWry+j%1XWAGVAU10lcW}R6<8Spjzfx|*zci3r3UxeN|EZ^DljGhSYo4F?6 z&dg_LUb3mP>n{VcE#J~~b6qPHwHLqf^x=4d@nQjfmV89WORpAt2W-)g10-4f6s0FA zTy!4#PKS>0zxdpD5##rqul&zLV@E`N8aURASa;fiY;4+On;a2s+=&63h3!br z_2BnIXOApz2hTgbp&5M5SqV)=!GkS;-U9rz<4tXb!a&tUB^cbcJLLfHIa?(<0jKW z4Lpz5w!g#FaE-Axoj~_YJ#F$$fpG4|cv~RCQ*h`ro^Hzu!aDK3WMi9uRY<5D8_z(h zRZe#zs4{8`riZXmw1suumF97!dH)}2&hNTUUh*yJkbr zTK@(NzQiMX`i;pQ34~^&!GVVVMVm1t@Uf?X*M$}o zZeaD&x(L0G_NKoXfbhdags&voqT72w5qBSkB6eOWB(T>+Z~^Z!cuz@k{r4l=ghFq>}UU zQnDUyi^g`TXsT~1A{+!2wO}fu`dziKW2%a@u9}5St)l9esD%f4-1eBovTB(5a#dfX zGc=_Yp`ew^MYGKpN}8d*&TxI4vLpeL9yiz9foi9c)^of0Qroj*5Hq)~tE$3LBNVA) zw&Lqhx$3UMRCn4;wW?21fxZI>0M!l}@CkGDLzr5Q-1`-b88QF^RXWIqvBbu)z{;5W zT4E%8iV5ODEIqu)d~FRJPK(rGL5EJ7LmBc@XFDnf1|dZ_!qDu0#yFowr|MTBnk5@j zqv^3Z8l9%MGXggOnjWt?e8lD09ww;9n*vm-Fikb!_wQnT)ke*L&9*4T)_4rbw0+&? zb;HIcy9K=&G(#rk4mZF8!beeqsY4c`W|s|xji!@zfe1Cx?52s+V177UlgvmnN)@)(cP=T_|8L}c~K-7D#Z$%Uo)HTT3- zjT!xIn050c+5bj>&N$6jFTU;kBGwnC@!V#sTJ_aAb=~<}Y(DWWUA6|WAHn)69Z&$A zOnd^*I+s1sUVW#ME9@0Y>K|a-xC7vYN-Qy9=d<`2!?4aDH%R>#-5?DP#oRT(B|^`#xB_T+r%6mcOrcg*mN%U G4gMFxUhCoj delta 2578 zcmZ`*%WoUU8K2qr@=Z!2#fPM|Bg&>^(XrshscgZKEcq2lc10$xRuO21vr;INTzR>c zqQxq5z#M#Nil%cYP**5r6h)gu4>fvdZb1$PdMI)$AoO)A(tn^qbxFTj$`2mO#s21- z*Eiq(=6jG{{QDarGZ^$C`2GAp%Nsu${w$QijnBqL_xPGv7j+RMf{9R*>as3-vRG5< zs;+vnRP)vSx*sD$*a*NIBr;JxeJxJU z7y~4JkF)*&rgF7GebC6XkUnH+#^A<@7AApvg7t{WB#=ZT0V^LFL+z6XU>e~H2uyg5 z^fMDwD_pK_?#KI-&BwV|Dx(8jb2+1&4?5AcO}mzsR3RBcr4QmKNOpB(=J(5moW zfwvif8Rui#mcKbo!Y9x?up>y3j{t}uLtpq5b(eM<$N^gbF&c?C8IfGs6^3`Uk zW>(6zQXL4dKd4vOq0fIZ2!41y!oL8)d6#|_zN-V>D#765mtEc!dJ*fV5fOV)0~5R} z*}k@)NS?8>%QKT*0Z?@%;(II*|2{+}T$9*!MQ~lY04+o=PX3#)qokFYca~Q*D-Ip+ zOB}_!l;))7aQSmiIIDI1?E3P|(v8{GT)>*9zlvx}Z?X*>gJA|)1_KNR8H_ME1(1_p zxuMH!w3k61Ko?Dm7?v(?n$6N?#imL$i-&1GI*`=l5iD`u^$&D0Nh{G4S(ep2vLGQL zoAH9%+yQFgS+u(S9=D5kIj2E?8=cy_Ws7Zz2p&hG$B_i=$iR-WFEe(W5bTyW6c@W9 zQCWs#+FU%vvPAy5V>fcHWUGYlMfb4vp{LXLicbIk(JrIv@)_VJuHHGT?0D;>aHfti>no(WK4 zJm2q_>~lG0dM8mi7m0sYwqveL0yNN{-V3%kyN@X*Op3P?2dLeD07Z#}e<8UF(|iXJ zB*4?+!7$|KBbf9;M`<81==E=m8_cB>79RIvp$%m)p- z6ShO(3%Y7o>qgTq*Ei|IVqb+OKX;4=)ukBnZLAEuF+QNjPMq$q(6!GrBv%d zqg2_c)Qo2^)chfU4;)5cU0&?Qi0y~bdg!A%AW42ld0{aN}%JO-kIK&_^nRAgm92`Z_;0R1}MzcAQI zg@xku(s9l8Y}9I|xtY)Bi;g^JS*8UnojR0DC1O@eC9A?z-vu}n7_qusGuPMQp8)-S z=F`hMo1A3u0|rX~y6RnTsW5lNVsU910Ec{Yr>XPro3`Z 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)