From 3a65500c6b485a9dede6bd1702143042595f3f85 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Thu, 9 Jul 2026 22:43:41 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=203h=20Performance=20Reports=20an=20Te?= =?UTF-8?q?legram=20+=20Obsidian=20(USDT,=20aktive=20Trades,=20Bot-Aktivit?= =?UTF-8?q?=C3=A4t,=20Holdings)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/performance_reporter.py | 213 ++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/performance_reporter.py diff --git a/src/performance_reporter.py b/src/performance_reporter.py new file mode 100644 index 0000000..cf30790 --- /dev/null +++ b/src/performance_reporter.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +""" +3h Performance Report Generator +Sammelt Metriken und sendet an Telegram + Obsidian +""" +import os +import json +from datetime import datetime, timedelta +from binance.client import Client +from dotenv import load_dotenv +import requests + +os.chdir('/home/marc/bot-deploy') +load_dotenv() + +client = Client(os.getenv('BINANCE_API_KEY_LIVE'), os.getenv('BINANCE_API_SECRET_LIVE')) + +def get_performance_metrics(): + """Sammelt alle Metriken für Report""" + try: + # 1. Portfolio-Status + acc = client.get_account() + balances = {b['asset']: float(b['free']) + float(b['locked']) + for b in acc['balances'] if float(b['free']) + float(b['locked']) > 0} + + usdt_total = balances.get('USDT', 0) + + # 2. Bot-Status + try: + with open('/home/marc/bot-deploy/active_trades.json') as f: + bot_state = json.load(f) + active_trades = bot_state.get('active_trades', {}) + trades_count = bot_state.get('count', 0) + except: + active_trades = {} + trades_count = 0 + + # 3. Berechne Positionen + holdings = len([k for k in balances.keys() if k not in ['USDT'] and balances[k] > 0.0001]) + + # 4. Berechne durchschnittlichen Entry Price + if active_trades: + total_locked = sum(float(t['qty']) * float(t['entry_price']) + for t in active_trades.values()) + avg_entry = total_locked / max(len(active_trades), 1) + else: + total_locked = 0 + avg_entry = 0 + + # 5. Bot Logs lesen (letzte 3h) + import subprocess + try: + logs_result = subprocess.run( + ['journalctl', '-u', 'trading-bot.service', '--since', '3 hours ago', '--no-pager'], + capture_output=True, text=True, timeout=5 + ) + logs = logs_result.stdout + cycle_count = logs.count('CYCLE START') + buy_count = logs.count('BUY') + sell_count = logs.count('SELL') + except: + cycle_count = 0 + buy_count = 0 + sell_count = 0 + + # 6. Zusammenfassung + return { + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S CET'), + 'usdt_free': usdt_total, + 'active_positions': trades_count, + 'holdings_count': holdings, + 'cycles_3h': cycle_count, + 'buys_3h': buy_count, + 'sells_3h': sell_count, + 'active_trades': active_trades, + 'total_portfolio_locked': total_locked + } + except Exception as e: + return {'error': str(e)} + +def format_telegram_report(metrics): + """Formatiert Report für Telegram""" + if 'error' in metrics: + return f"❌ Report-Fehler: {metrics['error']}" + + report = f""" +📊 **3h Performance Report** +Zeitstempel: {metrics['timestamp']} + +💰 **Portfolio:** + USDT verfügbar: ${metrics['usdt_free']:.2f} + Portfolio gesperrt: ${metrics['total_portfolio_locked']:.2f} + +📈 **Positionen:** + Aktive Trades: {metrics['active_positions']} + Holdings-Coins: {metrics['holdings_count']} + +🔄 **Bot-Aktivität (letzte 3h):** + Zyklen durchgeführt: {metrics['cycles_3h']} + Käufe: {metrics['buys_3h']} + Verkäufe: {metrics['sells_3h']} + +🎯 **Aktive Trades:** +""" + if metrics['active_trades']: + for symbol, trade in sorted(metrics['active_trades'].items()): + report += f" • {symbol}: {trade['qty']:.8f} @ {trade['entry_price']:.2f}\n" + else: + report += " (keine)\n" + + return report + +def format_obsidian_report(metrics): + """Formatiert Report für Obsidian""" + if 'error' in metrics: + return f"## Report-Fehler\n{metrics['error']}" + + report = f"""## {metrics['timestamp']} + +**Portfolio:** +- USDT verfügbar: ${metrics['usdt_free']:.2f} +- Portfolio gesperrt: ${metrics['total_portfolio_locked']:.2f} + +**Positionen:** +- Aktive Trades: {metrics['active_positions']} +- Holdings: {metrics['holdings_count']} + +**Bot-Aktivität (3h):** +- Zyklen: {metrics['cycles_3h']} +- Käufe: {metrics['buys_3h']} +- Verkäufe: {metrics['sells_3h']} + +**Trades:** +""" + if metrics['active_trades']: + for symbol, trade in sorted(metrics['active_trades'].items()): + report += f"- {symbol}: {trade['qty']:.8f} @ {trade['entry_price']:.2f}\n" + else: + report += "- (keine)\n" + + return report + +def send_telegram_report(message): + """Sendet Report an Telegram""" + try: + # Marc Telegram: 7646180954 + token = os.getenv('TELEGRAM_BOT_TOKEN') + chat_id = 7646180954 + url = f'https://api.telegram.org/bot{token}/sendMessage' + + payload = { + 'chat_id': chat_id, + 'text': message, + 'parse_mode': 'Markdown' + } + + response = requests.post(url, json=payload, timeout=10) + if response.status_code == 200: + print(f"✅ Telegram Report versendet") + return True + except Exception as e: + print(f"❌ Telegram-Fehler: {e}") + return False + +def append_obsidian_report(message): + """Hängt Report an Obsidian Session-Datei an""" + try: + report_file = '/home/marc/bot-deploy/obsidian_3h_reports.md' + + # Erstelle Datei wenn nicht vorhanden + if not os.path.exists(report_file): + header = """--- +tags: [3h-reports, performance, tracking] +--- + +# 3h Performance Reports + +""" + with open(report_file, 'w') as f: + f.write(header) + + # Füge Report an + with open(report_file, 'a') as f: + f.write(message + '\n\n') + + print(f"✅ Obsidian Report geschrieben") + return True + except Exception as e: + print(f"❌ Obsidian-Fehler: {e}") + return False + +def main(): + print("🔄 Sammle Performance-Metriken...") + metrics = get_performance_metrics() + + if 'error' not in metrics: + print(f" ✅ {metrics['active_positions']} aktive Trades") + print(f" ✅ ${metrics['usdt_free']:.2f} USDT verfügbar") + + print("\n📤 Sende Reports...") + + # Telegram + tg_msg = format_telegram_report(metrics) + send_telegram_report(tg_msg) + + # Obsidian + obs_msg = format_obsidian_report(metrics) + append_obsidian_report(obs_msg) + + print("\n✅ Reports versendet!") + +if __name__ == '__main__': + main()