forecast with real prices added
This commit is contained in:
400
price_forecast.py
Normal file
400
price_forecast.py
Normal file
@@ -0,0 +1,400 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import xgboost as xgb
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from datetime import datetime, timedelta
|
||||
import holidays
|
||||
|
||||
# --- NIEUW: Imports voor grafiek en e-mail ---
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.image import MIMEImage
|
||||
|
||||
# --- NIEUW: Zorgt dat matplotlib werkt zonder GUI (bv. in terminal) ---
|
||||
plt.switch_backend('Agg')
|
||||
|
||||
# --- CONFIGURATIE ---
|
||||
MODEL_FILE = 'price_forecast_model_v1_5.json'
|
||||
TARGET = 'gemiddelde_prijs'
|
||||
AANTAL_UUR_VOORSPELLEN = 72
|
||||
GRAFIEK_BESTAND = 'prijs_voorspelling.png' # Tijdelijk bestand
|
||||
|
||||
# --- NIEUW: E-mail Configuratie ---
|
||||
# VUL DIT IN MET JE EIGEN GEGEVENS
|
||||
EMAIL_CONFIG = {
|
||||
'smtp_server': '192.168.178.201', # Bv. 'smtp.gmail.com'
|
||||
'smtp_port': 587,
|
||||
'sender': 'sftpuser@markkors.nl',
|
||||
'password': 'Aae8-G9yFU5j', # Voeg hier het wachtwoord toe als dat nodig is
|
||||
'receiver': 'mark@markkors.nl'
|
||||
}
|
||||
|
||||
DB_CONFIG = {
|
||||
'host': '192.168.178.201',
|
||||
'user': 'energy_prices_user',
|
||||
'password': 'kS9R*xp17ZwCD@CV&E^N',
|
||||
'database': 'energy_prices',
|
||||
'port': 3307
|
||||
}
|
||||
|
||||
# Configuratie voor de Alfen database met werkelijke prijzen ---
|
||||
ALFEN_DB_CONFIG = {
|
||||
'host': '192.168.178.201',
|
||||
'user': 'alfen_user',
|
||||
'password': '5uVgr%f%s2P5GR@3q!',
|
||||
'database': 'alfen',
|
||||
'port': 3307
|
||||
}
|
||||
|
||||
# KIES HIER DE ENERGIELEVERANCIER (of gebruik 'GEMIDDELDE' voor het gemiddelde van alle leveranciers)
|
||||
LEVERANCIER = 'GEMIDDELDE' # Of bijv. 'Eneco', 'Vattenfall', etc.
|
||||
|
||||
# --- Model laden ---
|
||||
try:
|
||||
print(f"Laden van model: {MODEL_FILE}...")
|
||||
model = xgb.XGBRegressor()
|
||||
model.load_model(MODEL_FILE)
|
||||
FEATURES = model.feature_names_in_
|
||||
print(f"✅ Model succesvol geladen (verwacht {len(FEATURES)} features).")
|
||||
except FileNotFoundError:
|
||||
print(f"❌ Fout: Model bestand '{MODEL_FILE}' niet gevonden.")
|
||||
print(" Heb je het 'v1_5' model al getraind en opgeslagen op Windows?")
|
||||
exit()
|
||||
except Exception as e:
|
||||
print(f"❌ Fout bij laden model: {e}")
|
||||
exit()
|
||||
|
||||
nl_holidays = holidays.Netherlands(years=[datetime.now().year, datetime.now().year + 1])
|
||||
|
||||
|
||||
# --- NIEUW: Functie om werkelijke prijzen op te halen uit Alfen database ---
|
||||
def haal_werkelijke_prijzen(start_tijd, eind_tijd):
|
||||
"""
|
||||
Haalt werkelijke stroomprijzen op uit de Alfen database.
|
||||
"""
|
||||
print("\n💾 Werkelijke prijzen ophalen uit Alfen database...")
|
||||
|
||||
try:
|
||||
conn_alfen = mysql.connector.connect(**ALFEN_DB_CONFIG)
|
||||
|
||||
if LEVERANCIER == 'GEMIDDELDE':
|
||||
# Haal gemiddelde prijs van alle leveranciers
|
||||
query = """
|
||||
SELECT
|
||||
datetime,
|
||||
AVG(price) as price
|
||||
FROM
|
||||
dynamic_price_data_tommorow
|
||||
WHERE
|
||||
datetime BETWEEN %s AND %s
|
||||
GROUP BY
|
||||
datetime
|
||||
ORDER BY
|
||||
datetime
|
||||
"""
|
||||
else:
|
||||
# Haal specifieke leverancier (voeg eventueel een WHERE clause toe voor leverancier)
|
||||
# Dit hangt af van hoe je leveranciers opslaat in de tabel
|
||||
query = """
|
||||
SELECT
|
||||
datetime,
|
||||
price
|
||||
FROM
|
||||
dynamic_price_data_tommorow
|
||||
WHERE
|
||||
datetime BETWEEN %s AND %s
|
||||
ORDER BY
|
||||
datetime
|
||||
"""
|
||||
|
||||
werkelijke_df = pd.read_sql(
|
||||
query,
|
||||
conn_alfen,
|
||||
params=(start_tijd, eind_tijd),
|
||||
index_col='datetime',
|
||||
parse_dates=['datetime']
|
||||
)
|
||||
|
||||
conn_alfen.close()
|
||||
|
||||
print(f"✅ {len(werkelijke_df)} werkelijke prijzen opgehaald.")
|
||||
return werkelijke_df
|
||||
|
||||
except Error as e:
|
||||
print(f"⚠️ Kon geen verbinding maken met Alfen database: {e}")
|
||||
print(" Voorspelling gaat door zonder werkelijke prijzen in grafiek.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"⚠️ Fout bij ophalen werkelijke prijzen: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# --- Functie: haal_data_uit_database (onveranderd) ---
|
||||
def haal_data_uit_database(conn):
|
||||
print("💾 Data ophalen uit MySQL...")
|
||||
|
||||
query_hist = """
|
||||
SELECT
|
||||
w.datum_tijd, w.temperatuur, w.gevoelstemperatuur, w.neerslag,
|
||||
w.wind_richting, w.wind_snelheid, w.bewolking, w.luchtdruk, w.luchtvochtigheid,
|
||||
w.zonnestraling,
|
||||
p_avg.gemiddelde_prijs
|
||||
FROM
|
||||
amersfoort_weer_uurlijks AS w
|
||||
LEFT JOIN
|
||||
(SELECT datetime, AVG(price) AS gemiddelde_prijs
|
||||
FROM dynamic_price_data GROUP BY datetime) AS p_avg
|
||||
ON w.datum_tijd = p_avg.datetime
|
||||
WHERE
|
||||
w.datum_tijd BETWEEN (UTC_TIMESTAMP() - INTERVAL 30 HOUR) AND UTC_TIMESTAMP()
|
||||
ORDER BY
|
||||
w.datum_tijd;
|
||||
"""
|
||||
|
||||
query_toekomst = f"""
|
||||
SELECT
|
||||
datum_tijd, temperatuur, gevoelstemperatuur, neerslag,
|
||||
wind_richting, wind_snelheid, bewolking, luchtdruk, luchtvochtigheid,
|
||||
zonnestraling,
|
||||
NULL AS gemiddelde_prijs
|
||||
FROM
|
||||
amersfoort_weer_uurlijks
|
||||
WHERE
|
||||
datum_tijd BETWEEN UTC_TIMESTAMP() AND (UTC_TIMESTAMP() + INTERVAL {AANTAL_UUR_VOORSPELLEN} HOUR)
|
||||
ORDER BY
|
||||
datum_tijd;
|
||||
"""
|
||||
|
||||
try:
|
||||
hist_df = pd.read_sql(query_hist, conn, index_col='datum_tijd', parse_dates=['datum_tijd'])
|
||||
toekomst_df = pd.read_sql(query_toekomst, conn, index_col='datum_tijd', parse_dates=['datum_tijd'])
|
||||
|
||||
print(f"✅ {len(hist_df)} uur historie geladen.")
|
||||
print(f"✅ {len(toekomst_df)} uur toekomstig weer geladen.")
|
||||
|
||||
hist_df['gemiddelde_prijs'] = hist_df['gemiddelde_prijs'].ffill()
|
||||
hist_df['gemiddelde_prijs'] = hist_df['gemiddelde_prijs'].bfill()
|
||||
|
||||
combined_df = pd.concat([hist_df, toekomst_df])
|
||||
|
||||
return combined_df.sort_index()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fout bij ophalen data: {e}")
|
||||
return None
|
||||
|
||||
# --- Functie: maak_features_voor_uur (onveranderd) ---
|
||||
def maak_features_voor_uur(df, timestamp):
|
||||
features = {}
|
||||
data_nu = df.loc[timestamp]
|
||||
|
||||
features['maand'] = timestamp.month
|
||||
features['dag_van_het_jaar'] = timestamp.dayofyear
|
||||
features['is_feestdag'] = 1 if timestamp in nl_holidays else 0
|
||||
|
||||
weer_cols = ['temperatuur', 'gevoelstemperatuur', 'neerslag', 'wind_richting',
|
||||
'wind_snelheid', 'bewolking', 'luchtdruk', 'luchtvochtigheid', 'zonnestraling']
|
||||
for col in weer_cols:
|
||||
features[col] = data_nu[col]
|
||||
|
||||
features['prijs_1u_geleden'] = df.loc[timestamp - timedelta(hours=1)]['gemiddelde_prijs']
|
||||
features['prijs_24u_geleden'] = df.loc[timestamp - timedelta(hours=24)]['gemiddelde_prijs']
|
||||
|
||||
features['temp_avg_3u'] = df.loc[timestamp - timedelta(hours=2) : timestamp]['temperatuur'].mean()
|
||||
features['prijs_avg_6u'] = df.loc[timestamp - timedelta(hours=5) : timestamp]['gemiddelde_prijs'].mean()
|
||||
|
||||
for dag in range(7):
|
||||
features[f'dag_{dag}'] = 1 if timestamp.dayofweek == dag else 0
|
||||
|
||||
for uur in range(24):
|
||||
features[f'uur_{uur}'] = 1 if timestamp.hour == uur else 0
|
||||
|
||||
return pd.DataFrame([features], columns=FEATURES)
|
||||
|
||||
# --- NIEUW: Functie om de e-mail te bouwen en te versturen ---
|
||||
def send_email_with_graph(image_path, result_df):
|
||||
print("\n📬 E-mail opstellen...")
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg['Subject'] = f"Energie Prijsvoorspelling [energy_prediction] {datetime.now().strftime('%d-%m-%Y')}"
|
||||
msg['From'] = EMAIL_CONFIG['sender']
|
||||
msg['To'] = EMAIL_CONFIG['receiver']
|
||||
|
||||
laagste_prijs = result_df['Voorspelde_Prijs'].min()
|
||||
hoogste_prijs = result_df['Voorspelde_Prijs'].max()
|
||||
laagste_moment = result_df['Voorspelde_Prijs'].idxmin()
|
||||
hoogste_moment = result_df['Voorspelde_Prijs'].idxmax()
|
||||
|
||||
body = f"""
|
||||
Prijsvoorspelling voor de komende {len(result_df)} uur.
|
||||
|
||||
Laagste prijs: {laagste_prijs:.4f} (op {laagste_moment})
|
||||
Hoogste prijs: {hoogste_prijs:.4f} (op {hoogste_moment})
|
||||
|
||||
Volledige voorspelling:
|
||||
{result_df.to_string()}
|
||||
"""
|
||||
msg.attach(MIMEText(body, 'plain'))
|
||||
|
||||
try:
|
||||
with open(image_path, 'rb') as f:
|
||||
img_attach = MIMEImage(f.read(), name='voorspelling.png')
|
||||
msg.attach(img_attach)
|
||||
print("✅ Grafiek bijgevoegd.")
|
||||
except Exception as e:
|
||||
print(f"❌ Kon grafiek-bestand niet lezen: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
print(f"Verbinding maken met SMTP server: {EMAIL_CONFIG['smtp_server']}...")
|
||||
server = smtplib.SMTP(EMAIL_CONFIG['smtp_server'], EMAIL_CONFIG['smtp_port'])
|
||||
server.starttls()
|
||||
server.login(EMAIL_CONFIG['sender'], EMAIL_CONFIG['password'])
|
||||
|
||||
print("Inloggen succesvol. E-mail verzenden...")
|
||||
server.sendmail(EMAIL_CONFIG['sender'], EMAIL_CONFIG['receiver'], msg.as_string())
|
||||
server.quit()
|
||||
print("✅ E-mail succesvol verzonden.")
|
||||
except Exception as e:
|
||||
print(f"❌ Fout bij verzenden e-mail: {e}")
|
||||
print(" Controleer je EMAIL_CONFIG (server, poort, e-mail en App-wachtwoord)")
|
||||
|
||||
# --- START VAN HET SCRIPT (Aangepast) ---
|
||||
try:
|
||||
conn = mysql.connector.connect(**DB_CONFIG)
|
||||
werk_df = haal_data_uit_database(conn)
|
||||
|
||||
if werk_df is not None:
|
||||
te_voorspellen_tijden = werk_df[werk_df['gemiddelde_prijs'].isnull()].index
|
||||
|
||||
if len(te_voorspellen_tijden) == 0:
|
||||
print("\nDatabase is al up-to-date. Geen voorspelling nodig.")
|
||||
else:
|
||||
print(f"\n🧠 Start iteratieve voorspelling voor {len(te_voorspellen_tijden)} uur...")
|
||||
voorspellingen = []
|
||||
|
||||
for timestamp in te_voorspellen_tijden:
|
||||
features_nu = maak_features_voor_uur(werk_df, timestamp)
|
||||
voorspelde_prijs = model.predict(features_nu)[0]
|
||||
werk_df.loc[timestamp, 'gemiddelde_prijs'] = voorspelde_prijs
|
||||
voorspellingen.append(voorspelde_prijs)
|
||||
|
||||
print("\n" + "="*70)
|
||||
pd.set_option('display.max_rows', None)
|
||||
print(f"--- VOORSPELDE PRIJZEN (komende {len(te_voorspellen_tijden)} uur) ---")
|
||||
|
||||
resultaat_df = pd.DataFrame({
|
||||
'Voorspelde_Prijs': voorspellingen
|
||||
}, index=te_voorspellen_tijden)
|
||||
|
||||
print(resultaat_df)
|
||||
print("="*70)
|
||||
|
||||
# --- NIEUW: Haal werkelijke prijzen op ---
|
||||
start_tijd = te_voorspellen_tijden[0]
|
||||
eind_tijd = te_voorspellen_tijden[-1]
|
||||
werkelijke_prijzen_df = haal_werkelijke_prijzen(start_tijd, eind_tijd)
|
||||
|
||||
# Grafiek maken (met werkelijke prijzen) ---
|
||||
print("\n📊 Grafiek genereren (met werkelijke prijzen en weer)...")
|
||||
try:
|
||||
# Maak de basis-figuur en de EERSTE Y-as (ax1) voor de prijs
|
||||
fig, ax1 = plt.subplots(figsize=(15, 8))
|
||||
|
||||
# Plot de Voorspelde Prijs op de linker-as (ax1)
|
||||
ax1.plot(
|
||||
resultaat_df.index,
|
||||
resultaat_df['Voorspelde_Prijs'],
|
||||
color='blue',
|
||||
marker='.',
|
||||
linestyle='-',
|
||||
linewidth=2,
|
||||
label='Voorspelde Prijs'
|
||||
)
|
||||
|
||||
# --- NIEUW: Plot werkelijke prijzen als die beschikbaar zijn ---
|
||||
if werkelijke_prijzen_df is not None and not werkelijke_prijzen_df.empty:
|
||||
ax1.plot(
|
||||
werkelijke_prijzen_df.index,
|
||||
werkelijke_prijzen_df['price'],
|
||||
color='orange',
|
||||
marker='o',
|
||||
linestyle='--',
|
||||
linewidth=2,
|
||||
label=f'Werkelijke Prijs ({LEVERANCIER})'
|
||||
)
|
||||
print(f"✅ Werkelijke prijzen toegevoegd aan grafiek ({len(werkelijke_prijzen_df)} punten).")
|
||||
|
||||
ax1.set_ylabel('Prijs (€)', color='blue', fontsize=12)
|
||||
ax1.tick_params(axis='y', labelcolor='blue')
|
||||
ax1.set_xlabel('Datum en Tijd', fontsize=12)
|
||||
ax1.grid(True, which='major', axis='x', alpha=0.3)
|
||||
|
||||
# Maak de TWEEDE Y-as (ax2) die de X-as deelt
|
||||
ax2 = ax1.twinx()
|
||||
|
||||
weer_toekomst = werk_df.loc[te_voorspellen_tijden]
|
||||
|
||||
# Plot Temperatuur op de rechter-as (ax2)
|
||||
ax2.plot(
|
||||
weer_toekomst.index,
|
||||
weer_toekomst['temperatuur'],
|
||||
color='red',
|
||||
marker='.',
|
||||
linestyle='--',
|
||||
alpha=0.6,
|
||||
label='Temperatuur (°C)'
|
||||
)
|
||||
# Plot Windsnelheid op de rechter-as (ax2)
|
||||
ax2.plot(
|
||||
weer_toekomst.index,
|
||||
weer_toekomst['wind_snelheid'],
|
||||
color='green',
|
||||
marker='x',
|
||||
linestyle=':',
|
||||
alpha=0.6,
|
||||
label='Windsnelheid (km/u)'
|
||||
)
|
||||
ax2.set_ylabel('Temperatuur / Windsnelheid', color='black', fontsize=12)
|
||||
ax2.tick_params(axis='y', labelcolor='black')
|
||||
|
||||
# Titel en gecombineerde legenda
|
||||
plt.title(f'Energieprijs Voorspelling vs Werkelijk ({len(resultaat_df)} uur)', fontsize=16, fontweight='bold')
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left', fontsize=10)
|
||||
|
||||
# X-as opmaken (Nederlands formaat)
|
||||
date_format = mdates.DateFormatter('%d-%m %H:%M')
|
||||
ax1.xaxis.set_major_formatter(date_format)
|
||||
plt.setp(ax1.get_xticklabels(), rotation=30, ha='right')
|
||||
|
||||
fig.tight_layout()
|
||||
|
||||
# Sla de grafiek op
|
||||
plt.savefig(GRAFIEK_BESTAND, dpi=150)
|
||||
plt.close(fig)
|
||||
print(f"✅ Grafiek opgeslagen als: {GRAFIEK_BESTAND}")
|
||||
|
||||
# Stuur de e-mail
|
||||
send_email_with_graph(GRAFIEK_BESTAND, resultaat_df)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fout bij genereren van grafiek: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
except Error as e:
|
||||
print(f"❌ Fout met MySQL verbinding: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ Een onverwachte fout is opgetreden: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
if 'conn' in locals() and conn.is_connected():
|
||||
conn.close()
|
||||
print("\nVerbinding met MySQL gesloten.")
|
||||
Reference in New Issue
Block a user