From a831ea5fb0cc6ec8a9fc219e9af3e5c333ef84cd Mon Sep 17 00:00:00 2001 From: Branko May Trinkwald Date: Mon, 5 Jan 2026 20:52:06 +0100 Subject: [PATCH] CO2 = ATMEN --- CO2/breath.py | 98 ++++++++++ CO2/manifest.md | 28 +++ gold_market_report.txt | 38 ++++ news_collector.py | 5 +- requirements.txt | 7 + run_dashboard.sh | 31 ++++ server.py | 153 ++++++++++++++++ templates/index.html | 401 +++++++++++++++++++++++++++++++++++++++++ yahoo_collector.py | 88 +++++++-- 9 files changed, 830 insertions(+), 19 deletions(-) create mode 100644 CO2/breath.py create mode 100644 CO2/manifest.md create mode 100644 gold_market_report.txt create mode 100755 run_dashboard.sh create mode 100644 server.py create mode 100644 templates/index.html diff --git a/CO2/breath.py b/CO2/breath.py new file mode 100644 index 0000000..53d9eec --- /dev/null +++ b/CO2/breath.py @@ -0,0 +1,98 @@ +import psutil +import time +import os +import logging +from typing import Dict, Any + +logger = logging.getLogger(__name__) + +class ForestBreath: + """ + Berechnet den ökologischen Fußabdruck und den 'Atem' des Systems/Marktes. + """ + + # Konstanten für Gold-Mining-Impact (pro Unze) + # Quelle: World Gold Council / Diverse Studien + CO2_PER_OZ_KG = 800.0 # kg CO2 + EARTH_MOVED_PER_OZ_TONS = 20.0 # Tonnen bewegte Erde + WATER_PER_OZ_LITERS = 2000.0 # Liter Wasser + + # Konstanten für System-Energie (Schätzwert) + # Standard Laptop/Server: ca. 30-50 Watt Durchschnitt + # CO2-Intensität Strommix DE (2023): ~380g/kWh + SYSTEM_WATTAGE = 45.0 + GRID_CO2_G_PER_KWH = 380.0 + + def __init__(self): + self.start_time = time.time() + self.process = psutil.Process(os.getpid()) + logger.info("🌳 ForestBreath initialisiert. Wir atmen.") + + def get_system_breath(self) -> Dict[str, float]: + """ + Misst den Ressourcenverbrauch des aktuellen Prozesses (System-Atem). + """ + # Laufzeit in Stunden + uptime_hours = (time.time() - self.start_time) / 3600.0 + + # Energieverbrauch in kWh (Schätzung) + energy_kwh = (self.SYSTEM_WATTAGE * uptime_hours) / 1000.0 + + # CO2 Emissionen in Gramm + co2_emitted_g = energy_kwh * self.GRID_CO2_G_PER_KWH + + # CPU/RAM Usage + cpu_percent = self.process.cpu_percent(interval=None) + memory_mb = self.process.memory_info().rss / 1024 / 1024 + + return { + "uptime_hours": round(uptime_hours, 4), + "energy_kwh": round(energy_kwh, 6), + "co2_emitted_g": round(co2_emitted_g, 4), + "cpu_percent": round(cpu_percent, 1), + "memory_mb": round(memory_mb, 1) + } + + def get_gold_pricing_ecology(self, price_usd: float) -> Dict[str, str]: + """ + Berechnet, was 1.000 USD investiertes Gold "ökologisch" kosten. + """ + if price_usd <= 0: + return {} + + # Wieviel Unzen bekommt man für 1000 USD? + ounces_per_1k = 1000.0 / price_usd + + earth = ounces_per_1k * self.EARTH_MOVED_PER_OZ_TONS + co2 = ounces_per_1k * self.CO2_PER_OZ_KG + water = ounces_per_1k * self.WATER_PER_OZ_LITERS + + return { + "investment_amount": 1000, + "ounces_acquired": round(ounces_per_1k, 3), + "earth_moved_tons": round(earth, 2), + "co2_kg": round(co2, 2), + "water_liters": round(water, 0) + } + + def analyze_market_breath(self, volatility: float, rsi: float) -> str: + """ + Interpretiert Marktindikatoren als Atemzustand. + """ + if volatility > 0.02 or rsi > 70 or rsi < 30: + return "Hectic Inhalation (Stress)" + else: + return "Deep Exhalation (Calm)" + +if __name__ == "__main__": + # Test + fb = ForestBreath() + print("System atmet ein...") + # Last simulieren + [x**2 for x in range(1000000)] + + breath = fb.get_system_breath() + print(f"System Breath: {breath}") + + gold_impact = fb.get_gold_pricing_ecology(2500.0) # Bei $2500/oz + print(f"Impact of $1000 Gold Investment: {gold_impact}") diff --git a/CO2/manifest.md b/CO2/manifest.md new file mode 100644 index 0000000..557c7c0 --- /dev/null +++ b/CO2/manifest.md @@ -0,0 +1,28 @@ +# 🌳 Der Atem des Waldes: CO2 & Die Wahren Kosten + +>"Gold glänzt, aber der Wald atmet." + +In diesem Modul beschäftigen wir uns mit den **wahren Kosten** von Werten. Nicht nur der Dollar-Preis (GC=F), sondern der ökologische und energetische Fußabdruck. + +## 1. Der Preis des Goldes (Physical Debt) +Eine Unze Gold (ca. 31g) kostet die Erde im Durchschnitt: +- **20 Tonnen** bewegtes Erdreich (Abraum). +- **2000 Liter** Wasser. +- **0.8 Tonnen** CO2-Emissionen (Mining & Refining). +*Quelle: World Gold Council / Sustainability Reports* + +## 2. Der Preis des Wissens (Digital Debt) +Auch dieser "Watchtower" atmet. Er verbraucht Strom für: +- **Compute**: Python-Prozesse, Qdrant-Vektorsuche. +- **Traffic**: API-Calls zu Yahoo/Alpha Vantage. +- **Display**: Das Rendering Pixel auf deinem Schirm. + +## 3. Die Atmung des Marktes (Market Breath) +Der Markt selbst ist ein organisches Wesen: +- **Inhalation (Anspannung)**: Hohe Volatilität, schnelle Preisanstiege, "Gier". +- **Exhalation (Entspannung)**: Konsolidierung, sinkende Volatilität, "Ruhe". + +## Code-Implementierung +Das Skript `calculator.py` wird versuchen, diese abstrakten Konzepte in messbare Metriken zu übersetzen: +- `estimate_system_co2()`: Basierend auf CPU-Zeit und Laufzeit. +- `gold_ecological_footprint(ounces)`: Umrechnung von Portfolio-Wert in "bewegte Erde". diff --git a/gold_market_report.txt b/gold_market_report.txt new file mode 100644 index 0000000..ec713d6 --- /dev/null +++ b/gold_market_report.txt @@ -0,0 +1,38 @@ +================================================================================ +GOLD MARKT ANALYSE BERICHT +Generiert: 2026-01-05 19:26:06 +================================================================================ + +### Datenbank-Statistiken ### +Gespeicherte Datenpunkte: 2,878 +Collection: gold_market_analysis + +### Aktuelle Marktdaten ### + +GC (GC=F): + Preis: $4459.20 + Änderung: +2.99% + Session: COMEX + +GOLD (GOLD): + Preis: $36.41 + Änderung: +4.72% + Session: COMEX + +XAU (^XAU): + Preis: $358.10 + Änderung: +4.53% + Session: COMEX + +### News-Sentiment ### +Artikel (24h): 50 +Sentiment: Positive (0.19) +Positiv/Negativ/Neutral: 0/0/50 + +### Wichtige Events ### +1. Stock Traders Daily: Technical Reactions to CGNT Trends in Macro Strategies +2. Investing.com: Johnson & Johnson stock rating reiterated at Buy by UBS on strong fundamentals +3. AD HOC NEWS: Fortive Corp. stock: Quiet grind higher as Wall Street edges back to a cautious Buy +4. Simply Wall Street: Investors Continue Waiting On Sidelines For Loews Corporation (NYSE:L) + +================================================================================ \ No newline at end of file diff --git a/news_collector.py b/news_collector.py index e03e3e9..b235176 100644 --- a/news_collector.py +++ b/news_collector.py @@ -83,7 +83,8 @@ class GoldNewsCollector: """ try: # Aktualisierte URL - direkt auf News-Suche - url = "https://finance.yahoo.com/topic/gold" + # Aktualisierte URL - News direkt vom Ticker + url = "https://finance.yahoo.com/quote/GC=F/news" headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" @@ -148,7 +149,7 @@ class GoldNewsCollector: def get_all_news( self, hours_back: int = 24, - min_relevance: float = 0.3, + min_relevance: float = 0.1, ) -> List[Dict[str, Any]]: """ Sammelt Nachrichten aus allen verfügbaren Quellen diff --git a/requirements.txt b/requirements.txt index e54c78f..4be3ca4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ schedule>=1.2.0 # Utilities pytz>=2023.3 python-dateutil>=2.8.2 +psutil>=5.9.0 # Visualization matplotlib>=3.8.2 @@ -26,6 +27,12 @@ seaborn>=0.13.1 rich>=13.0.0 python-dotenv>=1.0.0 +# Web Dashboard (FastAPI) +fastapi>=0.109.0 +uvicorn[standard]>=0.27.0 +jinja2>=3.1.3 +python-multipart>=0.0.9 + # HINWEIS: Wir verwenden KEINE externe TA-Bibliothek mehr! # Alle technischen Indikatoren sind jetzt selbst implementiert diff --git a/run_dashboard.sh b/run_dashboard.sh new file mode 100755 index 0000000..ed6dd3a --- /dev/null +++ b/run_dashboard.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Farben +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${BLUE}🦉 Gold Forest Watchtower Start Script${NC}" +echo "=====================================" + +# Check Python Config +if [[ ! -d "venv" ]]; then + echo -e "${YELLOW}Kein venv gefunden. Führe setup.sh aus...${NC}" + ./setup.sh +fi + +source venv/bin/activate + +# Dependencies check +echo -e "${BLUE}Prüfe Web-Dependencies...${NC}" +pip install -r requirements.txt > /dev/null + +echo -e "${GREEN}✅ Umgebung bereit.${NC}" +echo -e "${YELLOW}Starte Watchtower Server...${NC}" +echo -e "Dashboard verfügbar unter: ${GREEN}http://localhost:8000${NC}" +echo "Drücke Ctrl+C zum Beenden." +echo "" + +# Start Server +python3 server.py diff --git a/server.py b/server.py new file mode 100644 index 0000000..07dfca8 --- /dev/null +++ b/server.py @@ -0,0 +1,153 @@ +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from contextlib import asynccontextmanager +import uvicorn +import logging +import os +from datetime import datetime + +from main import GoldMarketAnalysisSystem +from config import GOLD_TICKER_SYMBOLS + +# Logging mit Rich +from rich.logging import RichHandler +logging.basicConfig( + level="INFO", + format="%(message)s", + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True)] +) +logger = logging.getLogger("server") + +# Global System Initialisierung +system: GoldMarketAnalysisSystem = None + +@asynccontextmanager +async def lifespan(app: FastAPI): + global system + logger.info("🌲 Gold Forest Watchtower wird errichtet...") + try: + system = GoldMarketAnalysisSystem() + logger.info("🦉 Eule hat Platz genommen. System bereit.") + except Exception as e: + logger.error(f"Fehler beim Starten des Systems: {e}") + yield + logger.info("Beende Watchtower...") + +app = FastAPI(title="Gold Forest Watchtower", lifespan=lifespan) + +# Templates setup +templates = Jinja2Templates(directory="templates") + +@app.get("/", response_class=HTMLResponse) +async def read_root(request: Request): + return templates.TemplateResponse("index.html", {"request": request}) + +@app.get("/api/stats") +async def get_stats(): + """Liefert aktuelle Marktstatistiken""" + if not system: + return {"error": "System not initialized"} + + try: + # Aktuellen Preis laden (GC=F) + ticker_key = "GC" + current_data = system.yahoo_collector.get_realtime_data(ticker_key) + + # News/Sentiment + news = system.news_collector.get_all_news(hours_back=24) + sentiment = system.news_collector.aggregate_sentiment(news) + + # Technische Indikatoren berechnen (Historische Daten holen und berechnen) + hist_data = system.yahoo_collector.get_historical_data(ticker_key, period="1mo", interval="1h") + indicators = system.indicator_calculator.calculate_all_indicators(hist_data) + latest_ind = indicators.iloc[-1].to_dict() + + return { + "price": current_data.get("price", 0), + "change": current_data.get("change", 0), + "percent": current_data.get("percent", 0), + "timestamp": current_data.get("timestamp"), + "sentiment": { + "score": sentiment["average_score"], + "label": sentiment["sentiment_label"], + "count": sentiment["article_count"] + }, + "technical": { + "rsi": float(latest_ind.get("RSI_14", 50)), + "macd": float(latest_ind.get("MACD_12_26", 0)), + "bb_upper": float(latest_ind.get("BB_upper_20", 0)), + "bb_lower": float(latest_ind.get("BB_lower_20", 0)) + } + } + except Exception as e: + logger.error(f"API Error: {e}") + return {"error": str(e)} + +@app.get("/api/history") +async def get_history(): + """Liefert historische Daten für Charts""" + if not system: + return {"error": "System not initialized"} + + try: + hist_data = system.yahoo_collector.get_historical_data("GC", period="7d", interval="1h") + indicators = system.indicator_calculator.calculate_all_indicators(hist_data) + + # Letzte 100 Punkte + df = indicators.tail(100) + + return { + "dates": df.index.strftime("%Y-%m-%d %H:%M").tolist(), + "prices": df["Close"].tolist(), + "bb_upper": df["BB_upper_20"].fillna(0).tolist(), + "bb_lower": df["BB_lower_20"].fillna(0).tolist() + } + except Exception as e: + return {"error": str(e)} + +@app.get("/api/news") +async def get_latest_news(): + """Liefert die letzten News""" + if not system: + return [] + + try: + news = system.news_collector.get_all_news(hours_back=24) + return news[:6] # Top 6 + except Exception as e: + return [{"title": "Fehler beim Laden der News", "source": "System", "sentiment_label": "Neutral"}] + +@app.get("/api/breath") +async def get_system_breath(): + """Liefert den ökologischen Atem des Systems""" + from CO2.breath import ForestBreath + + # Singleton-like usage for demo (better: store in system) + fb = ForestBreath() + + # 1. System Breath (Realtime) + sys_breath = fb.get_system_breath() + + # 2. Gold Impact (Theoretical for 1 GC Contract ~ 100oz -> $200k+ but let's use current price) + price = 2500.0 # Fallback + if system: + try: + realtime = system.yahoo_collector.get_realtime_data("GC") + price = realtime.get("current_price", 2500.0) + except: + pass + + # Impact for 1 oz investment + gold_impact = fb.get_gold_pricing_ecology(price) + + return { + "system": sys_breath, + "gold_impact_1oz": gold_impact, + "breath_state": "Exhale" # Placeholder, could be dynamic based on volatility + } + +if __name__ == "__main__": + uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True) diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d161c1f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,401 @@ + + + + + + + Gold Forest Watchtower 🦉 + + + + + + + + + + + + + + + +
+
+

+ 🦉 Gold Forest Watchtower +

+

"Play the game, find bugs, be prepared."

+
+
+
System Status
+
+ + ONLINE +
+
+
+
+ + +
+ + +
+ +
+

Gold Price (GC=F)

+
---
+
+ + 0.00 + 0.00% +
+
+ + +
+
+
+

RSI (14)

+
--
+
+
+ Signal
+ WAIT +
+
+ +
+
+
+
+ + +
+
📰
+

Market Sentiment

+
---
+
+
Score
+
0.00
+
+
+
Articles
+
0
+
+
+ + +
+
🌌
+

Cosmic Breath

+ + +
+
+
+
+ CO₂ +
+
+
+ +
+
+ System Breath + ... +
+
+
+
+ +
+ 1oz Gold Cost + ... +
+
+ + +
+
+ + +
+
+

Market Overview (7 Days)

+
+ + +
+
+
+ +
+
+ + +
+

+ Latest Signals & News +

+
+ +
+
+ +
+ + + + + + + + \ No newline at end of file diff --git a/yahoo_collector.py b/yahoo_collector.py index 21270c5..ef811b8 100644 --- a/yahoo_collector.py +++ b/yahoo_collector.py @@ -93,25 +93,79 @@ class YahooFinanceGoldCollector: try: ticker = yf.Ticker(ticker_symbol) - info = ticker.info + data = {} + + # 1. Versuche zuerst fast_info (zuverlässiger für Preise) + try: + price = ticker.fast_info.last_price + prev_close = ticker.fast_info.previous_close + change = price - prev_close if price and prev_close else 0 + change_percent = (change / prev_close) * 100 if prev_close else 0 + + if price: + data = { + "timestamp": datetime.now(pytz.UTC), + "ticker": ticker_key, + "ticker_symbol": ticker_symbol, + "current_price": price, + "open": ticker.fast_info.open, + "day_high": ticker.fast_info.day_high, + "day_low": ticker.fast_info.day_low, + "volume": 0, + "change": change, + "change_percent": change_percent, + "trading_session": self.identify_trading_session(datetime.now(pytz.UTC)), + } + logger.info(f"FastInfo genutzt: {price}") + except Exception as e: + logger.debug(f"fast_info fehlgeschlagen: {e}") - current_time = datetime.now(pytz.UTC) + # 2. Fallback auf .info (wenn fast_info leer war) + if not data: + info = ticker.info + current_time = datetime.now(pytz.UTC) + data = { + "timestamp": current_time, + "ticker": ticker_key, + "ticker_symbol": ticker_symbol, + "current_price": info.get("currentPrice", info.get("regularMarketPrice", 0)), + "open": info.get("open", info.get("regularMarketOpen", 0)), + "day_high": info.get("dayHigh", info.get("regularMarketDayHigh", 0)), + "day_low": info.get("dayLow", info.get("regularMarketDayLow", 0)), + "volume": info.get("volume", info.get("regularMarketVolume", 0)), + "bid": info.get("bid", 0), + "ask": info.get("ask", 0), + "change": info.get("regularMarketChange", 0), + "change_percent": info.get("regularMarketChangePercent", 0), + "trading_session": self.identify_trading_session(current_time), + } - data = { - "timestamp": current_time, - "ticker": ticker_key, - "ticker_symbol": ticker_symbol, - "current_price": info.get("currentPrice", info.get("regularMarketPrice", 0)), - "open": info.get("open", info.get("regularMarketOpen", 0)), - "day_high": info.get("dayHigh", info.get("regularMarketDayHigh", 0)), - "day_low": info.get("dayLow", info.get("regularMarketDayLow", 0)), - "volume": info.get("volume", info.get("regularMarketVolume", 0)), - "bid": info.get("bid", 0), - "ask": info.get("ask", 0), - "change": info.get("regularMarketChange", 0), - "change_percent": info.get("regularMarketChangePercent", 0), - "trading_session": self.identify_trading_session(current_time), - } + # 3. Konsistenz-Check: Wenn Preis 0, aber Change da -> Preis berechnen + if not data.get("current_price") and data.get("change") and data.get("open"): + # Versuche Rekonstruktion (ungenau, aber besser als 0) + data["current_price"] = data.get("open", 0) + data.get("change", 0) + logger.info(f"Preis rekonstruiert aus Open+Change: {data['current_price']}") + + # 4. Letzter Rettungsanker: History + if not data.get("current_price"): + try: + hist = ticker.history(period="5d") # 5 Tage um sicher zu gehen + if not hist.empty: + last_close = hist["Close"].iloc[-1] + data["current_price"] = last_close + # Wenn wir schon dabei sind, fülle auch Change grob auf + if not data.get("change"): + prev_close = hist["Close"].iloc[-2] if len(hist) > 1 else last_close + data["change"] = last_close - prev_close + data["change_percent"] = (data["change"] / prev_close) * 100 if prev_close else 0 + logger.info(f"Fallback auf History-Close für {ticker_symbol}: {last_close}") + except Exception as e: + logger.error(f"History-Fallback fehlgeschlagen: {e}") + + # 5. Finales Safety-Net für den Preis (darf nicht 0 sein für Dashboard) + if not data.get("current_price"): + data["current_price"] = 2500.0 # Harter Fallback damit UI nicht bricht + logger.warning("Hardcoded Fallback Preis genutzt!") return data