This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.git/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Kopieer naar .env en vul de waarden in.
|
||||||
|
GROQ_API_KEY=
|
||||||
|
GMAIL_ADRES=
|
||||||
|
GMAIL_APP_WACHTWOORD=
|
||||||
|
ONTVANGER=
|
||||||
|
|
||||||
|
# Optioneel
|
||||||
|
VERSTUUR_OM=08:00
|
||||||
|
|
||||||
|
# Optioneel: mount een JSON file naar /data/watchlist.json (zie docker-compose.yml)
|
||||||
|
# WATCHLIST_FILE=/data/watchlist.json
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends tzdata ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
RUN mkdir -p /data
|
||||||
|
|
||||||
|
COPY aandelen.py .
|
||||||
|
|
||||||
|
CMD ["python", "aandelen.py"]
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
"""
|
||||||
|
📰 Aandelen Nieuwsbrief via AI + E-mail (Groq - gratis)
|
||||||
|
---------------------------------------------------------
|
||||||
|
Haalt elke ochtend om 8u nieuws op over je aandelen,
|
||||||
|
laat AI een samenvatting maken via Groq, en stuurt dit per mail.
|
||||||
|
Toont ook het analistenadvies, de P/E ratio, en 5 AI-gekozen low-risk aandelen.
|
||||||
|
|
||||||
|
Vereisten:
|
||||||
|
pip install yfinance schedule groq
|
||||||
|
|
||||||
|
Groq API key aanmaken (gratis):
|
||||||
|
Ga naar https://console.groq.com → "API Keys"
|
||||||
|
Plak je key hieronder bij GROQ_API_KEY
|
||||||
|
"""
|
||||||
|
|
||||||
|
import yfinance as yf
|
||||||
|
import schedule
|
||||||
|
import time
|
||||||
|
import smtplib
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from groq import Groq
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 📝 LOGBESTAND
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=os.getenv("LOG_FILE", "/data/nieuws_log.txt"),
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s %(message)s",
|
||||||
|
datefmt="%d/%m/%Y %H:%M:%S",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
def log(bericht: str):
|
||||||
|
logging.info(bericht)
|
||||||
|
print(bericht)
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# ⚙️ CONFIGURATIE — pas dit aan
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "").strip() # verplicht
|
||||||
|
GMAIL_ADRES = os.getenv("GMAIL_ADRES", "").strip() # verplicht
|
||||||
|
GMAIL_APP_WACHTWOORD = os.getenv("GMAIL_APP_WACHTWOORD", "").strip() # verplicht (Google App Password)
|
||||||
|
ONTVANGER = os.getenv("ONTVANGER", "").strip() # verplicht
|
||||||
|
|
||||||
|
DEFAULT_WATCHLIST = {
|
||||||
|
"HIMS": "Hims & Hers Health",
|
||||||
|
"AMD": "Advanced Micro Devices",
|
||||||
|
"AGNC": "AGNC Investment",
|
||||||
|
"TSLA": "Tesla",
|
||||||
|
"EUNL.DE": "iShares Core MSCI World ETF",
|
||||||
|
"CCEP.AS": "Coca-Cola Europacific Partners",
|
||||||
|
}
|
||||||
|
|
||||||
|
WATCHLIST_FILE = os.getenv("WATCHLIST_FILE", "").strip()
|
||||||
|
|
||||||
|
def _normaliseer_watchlist(data) -> dict[str, str]:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
out = {}
|
||||||
|
for k, v in data.items():
|
||||||
|
ticker = str(k).strip()
|
||||||
|
naam = str(v).strip() if v is not None else ""
|
||||||
|
if not ticker:
|
||||||
|
continue
|
||||||
|
out[ticker] = naam or ticker
|
||||||
|
return out
|
||||||
|
|
||||||
|
if isinstance(data, list):
|
||||||
|
out = {}
|
||||||
|
for item in data:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
ticker = str(item.get("ticker", "")).strip()
|
||||||
|
naam = str(item.get("naam", "")).strip()
|
||||||
|
if not ticker:
|
||||||
|
continue
|
||||||
|
out[ticker] = naam or ticker
|
||||||
|
return out
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def laad_watchlist() -> dict[str, str]:
|
||||||
|
if not WATCHLIST_FILE:
|
||||||
|
return DEFAULT_WATCHLIST
|
||||||
|
try:
|
||||||
|
with open(WATCHLIST_FILE, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
wl = _normaliseer_watchlist(data)
|
||||||
|
if wl:
|
||||||
|
return wl
|
||||||
|
log(f"⚠️ WATCHLIST_FILE bevat geen geldige tickers, gebruik DEFAULT_WATCHLIST. Pad: {WATCHLIST_FILE}")
|
||||||
|
return DEFAULT_WATCHLIST
|
||||||
|
except FileNotFoundError:
|
||||||
|
log(f"⚠️ WATCHLIST_FILE niet gevonden, gebruik DEFAULT_WATCHLIST. Pad: {WATCHLIST_FILE}")
|
||||||
|
return DEFAULT_WATCHLIST
|
||||||
|
except Exception as e:
|
||||||
|
log(f"⚠️ Fout bij lezen WATCHLIST_FILE, gebruik DEFAULT_WATCHLIST. Pad: {WATCHLIST_FILE} fout: {e}")
|
||||||
|
return DEFAULT_WATCHLIST
|
||||||
|
|
||||||
|
WATCHLIST = laad_watchlist()
|
||||||
|
|
||||||
|
VERSTUUR_OM = os.getenv("VERSTUUR_OM", "08:00").strip()
|
||||||
|
|
||||||
|
if __name__ != "__main__":
|
||||||
|
# Bij importeren (bv. tests) willen we niet hard falen op ontbrekende envs.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
ontbrekend = [naam for naam, waarde in {
|
||||||
|
"GROQ_API_KEY": GROQ_API_KEY,
|
||||||
|
"GMAIL_ADRES": GMAIL_ADRES,
|
||||||
|
"GMAIL_APP_WACHTWOORD": GMAIL_APP_WACHTWOORD,
|
||||||
|
"ONTVANGER": ONTVANGER,
|
||||||
|
}.items() if not waarde]
|
||||||
|
if ontbrekend:
|
||||||
|
raise SystemExit(
|
||||||
|
"Ontbrekende environment variables: "
|
||||||
|
+ ", ".join(ontbrekend)
|
||||||
|
+ ". Zet ze in je omgeving of via docker-compose (.env)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 📊 ANALISTENADVIES
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
ADVIES_KLEUREN = {
|
||||||
|
"strong buy": ("#1a7a1a", "🟢 Strong Buy"),
|
||||||
|
"buy": ("#4caf50", "🟩 Buy"),
|
||||||
|
"hold": ("#f5a623", "🟡 Hold"),
|
||||||
|
"underperform": ("#e67e22", "🟠 Underperform"),
|
||||||
|
"sell": ("#c0392b", "🔴 Sell"),
|
||||||
|
"strong sell": ("#7b0000", "🔴 Strong Sell"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def haal_advies_op(ticker: str) -> tuple:
|
||||||
|
try:
|
||||||
|
info = yf.Ticker(ticker).info
|
||||||
|
advies = info.get("recommendationKey", "").lower()
|
||||||
|
if advies in ADVIES_KLEUREN:
|
||||||
|
return ADVIES_KLEUREN[advies]
|
||||||
|
return ("#888888", "⚪ Geen advies")
|
||||||
|
except:
|
||||||
|
return ("#888888", "⚪ Niet beschikbaar")
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 📉 P/E RATIO
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def haal_pe_op(ticker: str) -> str:
|
||||||
|
try:
|
||||||
|
info = yf.Ticker(ticker).info
|
||||||
|
pe = info.get("trailingPE", None)
|
||||||
|
if pe is None:
|
||||||
|
return '<span style="color:#888; font-size:13px;">P/E: niet beschikbaar</span>'
|
||||||
|
if pe < 0:
|
||||||
|
kleur, uitleg = "#c0392b", "verlies"
|
||||||
|
elif pe < 15:
|
||||||
|
kleur, uitleg = "#1a7a1a", "goedkoop"
|
||||||
|
elif pe < 30:
|
||||||
|
kleur, uitleg = "#f5a623", "gemiddeld"
|
||||||
|
else:
|
||||||
|
kleur, uitleg = "#c0392b", "duur"
|
||||||
|
return f'<span style="font-size:13px; color:#555;">P/E ratio: <strong style="color:{kleur};">{pe:.1f}</strong> <span style="color:#aaa;">({uitleg})</span></span>'
|
||||||
|
except:
|
||||||
|
return '<span style="color:#888; font-size:13px;">P/E: niet beschikbaar</span>'
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 📰 NIEUWS OPHALEN
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def haal_nieuws_op(ticker: str, max_artikels: int = 5) -> list:
|
||||||
|
try:
|
||||||
|
nieuws = yf.Ticker(ticker).news
|
||||||
|
koppen = []
|
||||||
|
for artikel in nieuws[:max_artikels]:
|
||||||
|
titel = artikel.get("content", {}).get("title", "")
|
||||||
|
if titel:
|
||||||
|
koppen.append(titel)
|
||||||
|
return koppen
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Fout bij nieuws ophalen voor {ticker}: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 🤖 AI SAMENVATTING
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def maak_samenvatting(naam: str, ticker: str, koppen: list) -> str:
|
||||||
|
if not koppen:
|
||||||
|
return "Geen recent nieuws gevonden."
|
||||||
|
koppen_tekst = "\n".join(f"- {k}" for k in koppen)
|
||||||
|
prompt = f"""
|
||||||
|
Dit zijn recente nieuwskoppen over het aandeel {naam} ({ticker}):
|
||||||
|
|
||||||
|
{koppen_tekst}
|
||||||
|
|
||||||
|
Schrijf een korte samenvatting van 2-3 zinnen in het Nederlands.
|
||||||
|
Wees concreet en to-the-point. Geen inleiding of afsluiting nodig.
|
||||||
|
"""
|
||||||
|
client = Groq(api_key=GROQ_API_KEY)
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="llama-3.3-70b-versatile",
|
||||||
|
messages=[{"role": "user", "content": prompt}],
|
||||||
|
max_tokens=300,
|
||||||
|
)
|
||||||
|
return response.choices[0].message.content.strip()
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 💡 AI KIEST 5 LOW-RISK AANDELEN
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def haal_lowrisk_aandelen() -> list:
|
||||||
|
"""
|
||||||
|
Vraagt aan Groq om 5 low-risk aandelen te selecteren.
|
||||||
|
Geeft een lijst van dicts terug: [{ticker, naam, reden}, ...]
|
||||||
|
"""
|
||||||
|
log(" AI selecteert 5 low-risk aandelen...")
|
||||||
|
datum = datetime.now().strftime("%d %B %Y")
|
||||||
|
prompt = f"""
|
||||||
|
Vandaag is het {datum}.
|
||||||
|
|
||||||
|
Selecteer 5 interessante, low-risk aandelen voor een voorzichtige belegger.
|
||||||
|
Criteria: stabiele bedrijven, voorkeur voor dividend, lage volatiliteit, sterke balans.
|
||||||
|
Denk aan sectoren zoals consumentengoederen, gezondheidszorg, nutsbedrijven, ETFs.
|
||||||
|
|
||||||
|
Geef je antwoord ALLEEN als een JSON-array, zonder uitleg of extra tekst, in dit formaat:
|
||||||
|
[
|
||||||
|
{{"ticker": "JNJ", "naam": "Johnson & Johnson", "reden": "Stabiel dividendaandeel met sterke cashflow."}},
|
||||||
|
{{"ticker": "PG", "naam": "Procter & Gamble", "reden": "Defensief consumentengoederen bedrijf."}},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
client = Groq(api_key=GROQ_API_KEY)
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="llama-3.3-70b-versatile",
|
||||||
|
messages=[{"role": "user", "content": prompt}],
|
||||||
|
max_tokens=500,
|
||||||
|
)
|
||||||
|
tekst = response.choices[0].message.content.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Verwijder eventuele markdown backticks
|
||||||
|
tekst = tekst.replace("```json", "").replace("```", "").strip()
|
||||||
|
aandelen = json.loads(tekst)
|
||||||
|
return aandelen[:5]
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Fout bij parsen van low-risk aandelen: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def bouw_lowrisk_sectie(aandelen: list) -> str:
|
||||||
|
"""Bouwt de HTML-sectie op voor de 5 low-risk aandelen."""
|
||||||
|
if not aandelen:
|
||||||
|
return "<p style='color:#888;'>Kon geen aanbevelingen ophalen vandaag.</p>"
|
||||||
|
|
||||||
|
rijen = ""
|
||||||
|
for a in aandelen:
|
||||||
|
ticker = a.get("ticker", "")
|
||||||
|
naam = a.get("naam", ticker)
|
||||||
|
reden = a.get("reden", "")
|
||||||
|
kleur, advies_label = haal_advies_op(ticker)
|
||||||
|
pe_badge = haal_pe_op(ticker)
|
||||||
|
|
||||||
|
rijen += f"""
|
||||||
|
<div style="padding:14px 0; border-bottom:1px solid #e0e0e0;">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<strong style="color:#1a1a2e;">{naam}</strong>
|
||||||
|
<span style="color:#888; font-size:12px; margin-left:6px;">{ticker}</span>
|
||||||
|
</div>
|
||||||
|
<span style="background:{kleur}; color:white; padding:3px 8px; border-radius:10px; font-size:11px; font-weight:bold;">
|
||||||
|
{advies_label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="margin:4px 0;">{pe_badge}</div>
|
||||||
|
<p style="margin:4px 0 0 0; font-size:13px; color:#555; font-style:italic;">{reden}</p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<div style="margin-top:32px; padding:20px; background:#eef6ee; border-left:4px solid #1a7a1a; border-radius:4px;">
|
||||||
|
<h2 style="margin:0 0 4px 0; font-size:16px; color:#1a7a1a;">💡 AI-tip: 5 Low-Risk Aandelen van Vandaag</h2>
|
||||||
|
<p style="margin:0 0 14px 0; font-size:12px; color:#888;">Dagelijks geselecteerd door AI op basis van stabiliteit en dividendkracht</p>
|
||||||
|
{rijen}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 📧 NIEUWSBRIEF SAMENSTELLEN EN STUREN
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
def stuur_nieuwsbrief():
|
||||||
|
log("── Nieuwsbrief samenstellen ──")
|
||||||
|
datum = datetime.now().strftime("%A %d %B %Y")
|
||||||
|
|
||||||
|
secties_html = ""
|
||||||
|
for ticker, naam in WATCHLIST.items():
|
||||||
|
log(f" Verwerken: {naam} ({ticker})")
|
||||||
|
koppen = haal_nieuws_op(ticker)
|
||||||
|
samenvatting = maak_samenvatting(naam, ticker, koppen)
|
||||||
|
kleur, advies_label = haal_advies_op(ticker)
|
||||||
|
pe_badge = haal_pe_op(ticker)
|
||||||
|
|
||||||
|
secties_html += f"""
|
||||||
|
<div style="margin-bottom:28px; padding:20px; background:#f9f9f9; border-left:4px solid #1a1a2e; border-radius:4px;">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:flex-start; flex-wrap:wrap; margin-bottom:8px;">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin:0 0 2px 0; font-size:16px; color:#1a1a2e;">{naam}</h2>
|
||||||
|
<p style="margin:0; font-size:12px; color:#888;">{ticker}</p>
|
||||||
|
</div>
|
||||||
|
<span style="background:{kleur}; color:white; padding:4px 10px; border-radius:12px; font-size:12px; font-weight:bold; white-space:nowrap;">
|
||||||
|
{advies_label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom:10px;">{pe_badge}</div>
|
||||||
|
<p style="margin:0; font-size:14px; color:#333; line-height:1.6;">{samenvatting}</p>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Low-risk sectie
|
||||||
|
lowrisk_aandelen = haal_lowrisk_aandelen()
|
||||||
|
lowrisk_html = bouw_lowrisk_sectie(lowrisk_aandelen)
|
||||||
|
|
||||||
|
legenda = """
|
||||||
|
<div style="margin-top:24px; padding:16px; background:#f0f0f0; border-radius:4px; font-size:12px; color:#555;">
|
||||||
|
<strong>P/E ratio legenda:</strong><br>
|
||||||
|
<span style="color:#1a7a1a;">■</span> Onder 15 = goedkoop |
|
||||||
|
<span style="color:#f5a623;">■</span> 15–30 = gemiddeld |
|
||||||
|
<span style="color:#c0392b;">■</span> Boven 30 = duur |
|
||||||
|
<span style="color:#c0392b;">■</span> Negatief = verlies
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html = f"""
|
||||||
|
<html><body style="font-family: Georgia, serif; max-width:600px; margin:auto; padding:20px; color:#222;">
|
||||||
|
<div style="border-bottom:2px solid #1a1a2e; padding-bottom:12px; margin-bottom:24px;">
|
||||||
|
<h1 style="margin:0; font-size:22px; color:#1a1a2e;">📰 Jouw Dagelijkse Aandelenupdate</h1>
|
||||||
|
<p style="margin:4px 0 0 0; font-size:13px; color:#888;">{datum}</p>
|
||||||
|
</div>
|
||||||
|
{secties_html}
|
||||||
|
{lowrisk_html}
|
||||||
|
{legenda}
|
||||||
|
<p style="font-size:11px; color:#aaa; margin-top:16px; border-top:1px solid #eee; padding-top:12px;">
|
||||||
|
Samengevat door Groq AI · Analistenadvies & P/E via Yahoo Finance · Geen financieel advies
|
||||||
|
</p>
|
||||||
|
</body></html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
onderwerp = f"📰 Aandelenupdate {datetime.now().strftime('%d/%m/%Y')}"
|
||||||
|
bericht = MIMEMultipart("alternative")
|
||||||
|
bericht["Subject"] = onderwerp
|
||||||
|
bericht["From"] = GMAIL_ADRES
|
||||||
|
bericht["To"] = ONTVANGER
|
||||||
|
bericht.attach(MIMEText(html, "html"))
|
||||||
|
|
||||||
|
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
|
||||||
|
server.login(GMAIL_ADRES, GMAIL_APP_WACHTWOORD)
|
||||||
|
server.sendmail(GMAIL_ADRES, ONTVANGER, bericht.as_string())
|
||||||
|
|
||||||
|
log("✅ Nieuwsbrief verstuurd!")
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# 🚀 STARTEN
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
log(f"📰 Nieuwsbrief-bot gestart! Verstuurt elke ochtend om {VERSTUUR_OM}.")
|
||||||
|
|
||||||
|
# Meteen 1x uitvoeren voor de test (verwijder # hieronder om te testen)
|
||||||
|
#stuur_nieuwsbrief()
|
||||||
|
|
||||||
|
schedule.every().day.at(VERSTUUR_OM).do(stuur_nieuwsbrief)
|
||||||
|
while True:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(30)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
services:
|
||||||
|
aandelen-watcher:
|
||||||
|
build: .
|
||||||
|
container_name: aandelen-watcher
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/Amsterdam
|
||||||
|
- LOG_FILE=/data/nieuws_log.txt
|
||||||
|
- WATCHLIST_FILE=/data/watchlist.json
|
||||||
|
volumes:
|
||||||
|
- aandelen_data:/data
|
||||||
|
- ./watchlist.json:/data/watchlist.json:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
aandelen_data:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
yfinance
|
||||||
|
schedule
|
||||||
|
groq
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
latest
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"HIMS": "Hims & Hers Health",
|
||||||
|
"AMD": "Advanced Micro Devices",
|
||||||
|
"EUNL.DE": "iShares Core MSCI World ETF"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user