Das Problem in einem Absatz
Ein Heimspeicher mit dynamischem Tarif kann Netzstrom kaufen, wenn er billig ist. Aber wie viel, und wann? Jede billige Nacht volltanken kostet doppelt: Die Sonne von morgen wäre kostenlos gewesen, und jeder Umlauf verliert etwa ein Zehntel. Zu wenig kaufen ist auch teuer: Am Boden muss das Haus nehmen, was das Netz in diesem Moment kostet, oft in der Abendspitze. Der Planer aus Folge 3 beantwortet beide Fragen mit einer Regel: Kauf nur, was fehlt, nur wenn es gebraucht wird, zur billigsten Zeit, die du schon kennst. Diese Seite baut ihn als Code nach.
Kernfakten
| Frage | Antwort |
|---|---|
| Was es ist | Ein Plan für die kommende Woche: der Ladestand für jede Stunde, die erste Lücke unter einer Marke und die Viertelstunden, in denen gekauft wird. Gezählt wird in Punkten des Akkus (1 Punkt = 1 % seiner nutzbaren Größe) und in ct. |
| Wann er rechnet | Alle 15 Minuten und sofort, wenn neue Preise kommen. Preise sind etwa 10 bis 35 Stunden voraus bekannt; die Akku-Prognose reicht 168 Stunden. |
| Warum | Die Sonne von morgen ist kostenlos, Speichern kostet etwa ein Zehntel, und jeder bezahlte Zukauf hebt den Akkupreis: den einzigen Preis, den die Wärmepumpe und jedes andere Gerät im Haus zahlen. Also kauft das Haus so wenig wie möglich, so billig wie möglich. |
| Die Grenzen | Boden 20 %, Marke 30 % (nach zwei Tagen 2 Punkte mehr je Tag, hinter den bekannten Preisen). Aus dem Netz höchstens 90 %, nie in der täglichen Vollstunde, nie solange die Sonne das Haus deckt, nie ohne Solarprognose. Bei 0 ct oder darunter: immer, bis 100 %. |
| Eingänge | Ladestand jetzt (frisch, vom Batteriezähler), Sonne je Stunde, Bedarf je Stunde (Hausmuster, Auto, Wärmepumpe mit Warmwasser, Standby), Wandlungsverluste je Stunde, Preis je Viertelstunde. |
| Ausgänge | Die gewählten Viertelstunden mit ihrem Grund (Bedarf, Vorrat, Nullpreis), der Ladestand ohne und mit Plan, Ladeblöcke („lade bis …, hör auf um …“) und was noch auf seine Preise wartet. |
| Netzpreis | Zählt nur fürs Laden: wann, und wie viel. Über das, was jeder Zukauf gekostet hat, kommt er in den Akkupreis. Kein Gerät im Haus wird mit ihm bewertet. |
Die Kette
Der Plan ist das Ende einer ganzen Kette auf der Logik-Landkarte. In Lesereihenfolge:
- Sonne: Wetter-Prognose (7 Tage) → PV-Ertragsrechnung aus Physik.
- Bedarf: Hausverbrauch → Basislast → Verbrauchsprofil; Lademuster Auto; Miner-Standby; WP-Strom je Stunde (aus Gebäudemodell, COP-Kennfeld, gelernter Heizkurve und Wetter) und Warmwasser-Muster → Gesamtbedarf.
- Start: Batteriewächter → Frische-Regel → Regelquelle Ladestand.
- Plan: Wandlungsverluste und alles davor → Ladestand für 7 Tage; Vorgaben des Betreibers → wachsende Reserve → Marke und Lücke; Börsenstrompreis → Einkauf.
- Ausführung: Netzlade-Planer, Sicherheitsschalter „nur lesen“ und der Wächter am Batteriesystem → Ladeplan des Batteriesystems.
Die Kette auf der Logik-Landkarte ansehen. Dieses Paket enthält den Plan selbst: Ladestand für 7 Tage, Marke und Lücke und den Einkauf. Die Prognosen und die Ausführung sind nicht enthalten.
Die Rechnung
Eine Stunde nach der anderen, in Punkten des Akkus:
Stand(h) = Stand(h − 1) + Sonne(h) − Bedarf(h) − Verluste(h) + geladen(h), gehalten zwischen Boden und 100 %
Während der Akku aus dem Netz lädt, läuft das Haus weiter aus ihm. Die Ladung kommt obendrauf, und eine Viertelstunde hilft nur, wenn ihre Ladung größer ist als das, was das Haus in ihr verbraucht. Unter dem Boden wird nichts gehalten: Was fehlt, müsste das Haus ungeplant über das Ladegerät beziehen.
Marke(h) = Boden + Puffer, dazu ab Stunde 48 2 Punkte × (1 + ⌊(h − 48) ÷ 24⌋), nur hinter den bekannten Preisen
Lücke = tiefster Abstand unter der Marke bis zum letzten bekannten Preis, gemessen ohne Boden
Dann der Einkauf, eine Viertelstunde je Runde:
- Zuerst der Nullpreis: Jede bekannte Viertelstunde mit 0 ct oder darunter lädt, bis 100 %, egal was die anderen Regeln sagen.
- Bedarf: die erste Stunde unter der Marke innerhalb der bekannten Preise suchen. Unter den bekannten Viertelstunden bis einschließlich dieser Stunde die billigste nehmen, die erlaubt ist und mindestens 20 % ihrer eigenen Ladung schließt. Neu rechnen. Wiederholen, bis innerhalb der bekannten Preise keine Lücke mehr bleibt. Es gibt keine Preisgrenze: Dieser Strom würde ohnehin gekauft, am Boden.
- Vorrat: Liegt die nächste Lücke hinter den bekannten Preisen, aber innerhalb von drei Tagen, dasselbe, aber nur mit klar billigen Viertelstunden: höchstens 25 ct, höchstens das Mittel der bekannten Preise, höchstens das 30. Perzentil der letzten 14 Tage.
- Rest: Innerhalb der bekannten Preise noch unter der Marke: mit diesen Regeln nicht deckbar. Dahinter: wartet auf seine Preise.
Warum eine Viertelstunde nach der anderen? Jeder Kauf hebt die ganze Kurve danach, also rückt die erste Lücke nach hinten, und Viertelstunden, die außer Reichweite waren, kommen in Reichweite. In der Beispielwoche wird der billige späte Mittwochabend erst gewählt, als die Lücke dort angekommen ist.
Woher das Haus beim Laden seinen Strom bekommt
Das Ladegerät lädt nur den Akku. Das Haus hängt am Akku, über das Wechselrichtersystem, und läuft auch beim Laden weiter aus ihm. Eine Leitung vom Netz direkt ins Haus gibt es nicht.
Deshalb behandelt der Plan eine Ladeviertelstunde nie als Freifahrt für das Haus: Der Akku gewinnt die Ladung minus das, was das Haus zur selben Zeit verbraucht. Mit der neutralen Beispielgeschwindigkeit bringt eine Ladeviertelstunde 1 Punkt, während das Haus nachts etwa einen Viertelpunkt nimmt.
Die Beispielwoche
Dieselbe Woche wie im Video: grau, im Dezember, Dienstag 15 Uhr, der Akku bei 52 %. Alle Eingaben sind erfunden, nicht die Daten dieses Hauses. Die Ladegeschwindigkeit ist ein neutraler Beispielwert von 1 Punkt je Viertelstunde, nicht die einer echten Anlage; mit einer anderen Geschwindigkeit ändert sich die Zahl der Viertelstunden, nicht die Regeln.
- Mit Plan
- Ohne Kauf
- Marke (Boden + Reserve)
- Boden 20 %
Beispiel-Eingaben, nicht die Daten dieses Hauses · neutrale Ladegeschwindigkeit, nicht die dieses Hauses. Gerechnet mit dem Code-Paket.
- Preis je Stunde (ct)
- Gekauft für die Lücke
- Gekauft auf Vorrat
Unter jedem Balken vier Felder für die vier Viertelstunden dieser Stunde, von oben nach unten :00, :15, :30, :45. Neutrale Beispielgeschwindigkeit von 1 Punkt je Viertelstunde.
| Ergebnis | |
|---|---|
| Preise bekannt | bis Mittwoch Mitternacht, im Mittel 31,8 ct |
| Ohne Kauf | am Mittwoch um 7 Uhr unter 30 %, ab 17 Uhr am Boden |
| Lücke | 22,5 Punkte bis zum letzten bekannten Preis |
| Bedarf | 23 Viertelstunden: 16 in der Nacht (21,5 bis 22,9 ct), 4 an den Rändern der Nacht (25,0 bis 25,7 ct), 3 spät am Mittwochabend (21,0 bis 21,5 ct) |
| Vorrat | 5 Viertelstunden spät am Mittwochabend (21,3 bis 22,4 ct) |
| Zusammen | 28 Punkte, gut ein Viertel des Akkus, im Mittel für etwa 22,5 ct |
| Mit Plan | zwischen 30,2 und 50,4 % innerhalb der bekannten Preise |
| Rest | ab Donnerstag 5 Uhr wieder unter der Marke: wartet auf seine Preise |
Was 22,5 ct sind: der Preis der Zukäufe. Die Wärmepumpe zahlt ihn nie direkt. Er fließt in den Akkupreis, die Mischung aus allem im Akku, Sonne zu null eingeschlossen; siehe das Akku-Konto.
Und ein echter Plan, an einem Oktobernachmittag gerechnet: Akku bei etwa drei Vierteln, der tiefste Punkt der Woche etwa 48 %, immer noch etwa zehn Punkte über der Marke, die bis dahin auf 38 % gestiegen war. Der Plan sagte: nichts nötig. Die bekannten Preise lagen zwischen etwa 19 und 39 ct, am billigsten um Samstagmittag, während die Sonne den Akku ohnehin füllte. Billig allein ist nie ein Grund.
Der Code, erklärt
Das Paket hat drei Dateien in src/ladeplan/. Die Auszüge unten stammen direkt aus dem Download und stimmen Zeile für Zeile mit ihm überein. Nur Standardbibliothek, keine weiteren Pakete.
simulate()src/ladeplan/simulation.py · Zeilen 73–94def simulate(start_pct: float, hours: list[Hour], charged_pp: list[float] | None = None,
floor_pct: float = 20.0, top_pct: float = 100.0) -> Run:
"""Battery level for every hour, with the grid charging in charged_pp (points per hour)."""
if not floor_pct <= start_pct <= top_pct:
start_pct = min(max(start_pct, floor_pct), top_pct)
if charged_pp is not None and len(charged_pp) != len(hours):
raise ValueError("charged_pp needs one value per hour")
run = Run(start_pct=start_pct)
level = raw = start_pct
for h, hour in enumerate(hours):
change = hour.net_pp() + (charged_pp[h] if charged_pp else 0.0)
new = level + change
spilled = max(0.0, new - top_pct)
new = min(new, top_pct)
at_floor = max(0.0, floor_pct - new)
level = max(new, floor_pct)
raw = min(raw + change, top_pct) # no floor here, only the top
run.levels.append(level)
run.raw.append(raw)
run.at_floor_pp.append(at_floor)
run.spilled_pp.append(spilled)
return run- Eine Schleife über die Stunden.
changeist Sonne minus Bedarf minus Verluste plus das, was in dieser Stunde geladen wird. - Heraus kommen zwei Stände:
levels, gehalten zwischen Boden und 100 %, undraw, nur oben gehalten. Unter dem Boden fälltrawweiter um das, was das Haus noch bräuchte; dadurch wird eine Lücke messbar. at_floor_ppist der ungeplante Nachkauf am Boden,spilled_ppder Solarstrom, der bei 100 % nirgendwohin kann.
mark_pct(), first_gap(), gap_pp()src/ladeplan/marke.py · Zeilen 38–66def mark_pct(hour: int, settings: MarkSettings = MarkSettings(), last_priced_hour: int = -1) -> float:
"""The mark for hour h (0 = the hour starting now).
last_priced_hour: the last hour with a known price. Up to there, the plain
mark applies (floor + buffer), whatever the hour.
"""
mark = settings.floor_pct + settings.buffer_pts
if hour >= settings.step_from_hour and hour > last_priced_hour:
days = 1 + (hour - settings.step_from_hour) // 24
mark += days * settings.step_pts_per_day
return mark
def first_gap(levels: list[float], marks: list[float], first: int = 0, last: int | None = None,
tolerance_pp: float = 0.02) -> int | None:
"""First hour (index) whose end level is below its mark, or None."""
last = len(levels) - 1 if last is None else min(last, len(levels) - 1)
for h in range(first, last + 1):
if levels[h] < marks[h] - tolerance_pp:
return h
return None
def gap_pp(raw: list[float], marks: list[float], last: int) -> float:
"""How many points are missing up to hour `last`: the deepest dip below the mark,
measured without the floor (so a stretch at the floor still counts)."""
if last < 0:
return 0.0
return max(0.0, max(marks[h] - raw[h] for h in range(min(last, len(raw) - 1) + 1)))- Die Marke ist Boden plus Puffer. Ab Stunde 48 steigt sie um
step_pts_per_dayje angefangenem Tag, aber nur hinter dem letzten bekannten Preis. first_gap()sucht die erste Stunde, deren Stand am Ende unter ihrer Marke liegt; Unterschreitungen bis 0,02 Punkte zählen nicht.gap_pp()misst den tiefsten Abstand mitraw, damit eine lange Strecke am Boden voll zählt.
make_plan()src/ladeplan/einkauf.py · Zeilen 195–311def make_plan(start_pct: float, hours: list[Hour], prices_ct: list[float | None],
settings: PlanSettings = PlanSettings(), clock: Clock = Clock()) -> Plan:
"""The charging plan for the coming hours.
start_pct battery level now, from a trusted reading (no fresh reading: no plan)
hours forecast per hour, usually 168 (seven days)
prices_ct price per quarter hour from now, None where not known yet
clock start of hour 0 (weekday, clock hour): for the full hour and for labels
"""
n = len(hours)
if n == 0:
raise ValueError("no hours")
if len(prices_ct) > 4 * n:
raise ValueError("more prices than quarter hours")
s = settings
floor = s.mark.floor_pct
prices = list(prices_ct) + [None] * (4 * n - len(prices_ct))
priced = [q for q, p in enumerate(prices) if p is not None]
last_priced = priced[-1] // 4 if priced else -1
known_mean = sum(prices[q] for q in priced) / len(priced) if priced else None
marks = [mark_pct(h, s.mark, last_priced) for h in range(n)]
charge = [0.0] * (4 * n)
purchases: list[Purchase] = []
rejected: set[int] = set()
def simulate_now() -> Run:
per_hour = [sum(charge[4 * h:4 * h + 4]) for h in range(n)]
return simulate(start_pct, hours, per_hour, floor, s.top_pct)
def allowed(q: int) -> bool:
hour = hours[q // 4]
if prices[q] is None or charge[q] > 0 or q in rejected:
return False
if hour.solar_pp is None: # no solar forecast: never buy
return False
if s.full_hour is not None and (clock.hour + q // 4) % 24 == s.full_hour:
return False # the daily full hour is for the sun
if hour.solar_pp >= hour.demand_pp: # the sun already covers the house
return False
use_per_quarter = (hour.demand_pp + hour.losses_pp - hour.solar_pp) / 4
if s.charge_pp_per_quarter <= use_per_quarter: # would not raise the level
return False
return True
def within_cap(run: Run, new_q: int) -> bool:
"""No grid charge may end above max_target_pct (zero-price charges excepted)."""
qs = [p.quarter for p in purchases if p.kind != ZERO] + [new_q]
return all(quarter_level(run, hours, charge, q) <= s.max_target_pct + 1e-9 for q in qs)
def buy_best(until_hour: int, price_ok, missing) -> tuple[int, Run] | None:
"""Cheapest allowed quarter hour up to and including until_hour that really helps.
On equal prices the earlier one wins."""
before = missing(simulate_now())
candidates = sorted((prices[q], q) for q in range(4 * (until_hour + 1))
if allowed(q) and price_ok(prices[q]))
for _, q in candidates:
charge[q] = s.charge_pp_per_quarter
trial = simulate_now()
if within_cap(trial, q) and before - missing(trial) >= s.min_effect * s.charge_pp_per_quarter:
return q, trial
charge[q] = 0.0
rejected.add(q) # never effective, never again
return None
without = simulate_now()
run = without
# 1 zero price: always charge, up to 100 %, without the other rules
for q in priced:
if prices[q] <= 0 and quarter_level(run, hours, charge, q) < s.top_pct - s.tolerance_pp:
charge[q] = s.charge_pp_per_quarter
purchases.append(Purchase(q, prices[q], ZERO, len(purchases) + 1, None))
run = simulate_now()
first = first_gap(without.levels, marks, 0, None, s.tolerance_pp)
gap = gap_pp(without.raw, marks, last_priced)
# 2 need: gaps inside the known prices, any price
def missing_inside(r: Run) -> float:
return gap_pp(r.raw, marks, last_priced)
while True:
e = first_gap(run.levels, marks, 0, last_priced, s.tolerance_pp)
if e is None:
break
found = buy_best(e, lambda price: True, missing_inside)
if found is None:
break
q, run = found
purchases.append(Purchase(q, prices[q], NEED, len(purchases) + 1, e))
# 3 reserve: gaps behind the known prices, within reserve_days, clearly cheap only
def clearly_cheap(price: float) -> bool:
if price > s.reserve_max_ct or known_mean is None or price > known_mean:
return False
return s.reserve_percentile_ct is None or price <= s.reserve_percentile_ct
reserve_last = min(n - 1, 24 * s.reserve_days - 1)
def missing_reserve(r: Run) -> float:
return gap_pp(r.raw, marks, reserve_last)
if first_gap(run.levels, marks, 0, last_priced, s.tolerance_pp) is None:
while True:
e = first_gap(run.levels, marks, last_priced + 1, reserve_last, s.tolerance_pp)
if e is None:
break
found = buy_best(min(e, last_priced), clearly_cheap, missing_reserve)
if found is None:
break
q, run = found
purchases.append(Purchase(q, prices[q], RESERVE, len(purchases) + 1, e))
# 4 rest
r = first_gap(run.levels, marks, 0, None, s.tolerance_pp)
rest = NOTHING_MISSING if r is None else (NOT_COVERABLE if r <= last_priced else WAITS_FOR_PRICES)
return Plan(purchases=purchases, without=without, with_plan=run, marks=marks, last_priced_hour=last_priced,
first_gap_hour=first, gap_pp=gap, rest=rest, rest_hour=r, known_mean_ct=known_mean,
charge_pp=charge)allowed()enthält die Regeln: ein bekannter Preis, eine Solarprognose für diese Stunde, nicht die Vollstunde, die Sonne deckt das Haus nicht, und eine Ladung größer als das, was das Haus in dieser Viertelstunde verbraucht.within_cap()prüft die 90-%-Grenze am Ende jeder Ladeviertelstunde, auch der früher gewählten.buy_best()probiert die Kandidaten von billig nach teuer (bei gleichem Preis die frühere) und behält die erste, die mindestens 20 % ihrer Ladung schließt. Ein Kandidat, der das nicht schafft, fällt endgültig weg.- Dann die vier Phasen: Nullpreis, Bedarf innerhalb der bekannten Preise (
price_okimmer wahr), Vorrat mitclearly_cheap()und der Rest.
Ausführen und die Tests prüfen
Du brauchst Python 3.10 oder neuer (kostenlos von python.org); der Code selbst braucht nichts weiter. Nur die Tests brauchen zusätzlich pytest, ein freies Testwerkzeug für Python; pip, das Installationsprogramm, das mit Python kommt, holt es in der zweiten Zeile unten. Entpacke das Zip und öffne eine Konsole im Ordner ladeplan:
python examples/example_week.py # die Beispielwoche, Schritt für Schritt
python -m pip install pytest # nur für die Tests nötig
python -m pytest # endet mit: 31 passedDas Beispiel gibt jeden Schritt der Woche aus, hier gekürzt (Ausgabe auf Englisch):
example_week.py (gekürzt)Start Tue 15:00: battery 52.0 %, prices known until Thu 00:00
Known prices average 31.8 ct
Step 1 - the line without buying
first below the mark: Wed 07:00 (29.13 %)
on the floor from: Wed 17:00
Step 2 - the gap up to the last known price
22.49 points
Step 3 - the cheapest quarter hours within reach, one at a time
1. Wed 01:00 21.5 ct need for the gap at Wed 07:00
2. Wed 03:00 21.5 ct need for the gap at Wed 08:00
3. Wed 02:30 21.7 ct need for the gap at Wed 08:00
...
17. Wed 05:00 25.0 ct need for the gap at Wed 20:00
18. Wed 05:45 25.3 ct need for the gap at Wed 21:00
19. Wed 00:30 25.7 ct need for the gap at Wed 21:00
20. Wed 05:15 25.7 ct need for the gap at Wed 22:00
21. Wed 22:30 21.2 ct need for the gap at Wed 23:00
22. Wed 22:00 21.5 ct need for the gap at Wed 23:00
23. Wed 23:00 21.0 ct need for the gap at Thu 00:00
24. Wed 23:45 21.3 ct reserve for the gap at Thu 01:00
25. Wed 23:15 21.7 ct reserve for the gap at Thu 02:00
26. Wed 22:15 21.9 ct reserve for the gap at Thu 03:00
27. Wed 22:45 22.2 ct reserve for the gap at Thu 04:00
28. Wed 23:30 22.4 ct reserve for the gap at Thu 04:00
Result
need 23 quarter hours, 23.0 points, average 22.60 ct
reserve 5 quarter hours, 5.0 points, average 21.90 ct
all 28 quarter hours, 28.0 points, average 22.47 ct
with the plan: low 30.17 %, high 50.37 % (inside the known prices)
rest: waits_for_prices from Thu 05:00
Charging blocks (charge up to ..., stop at ...)
Wed 00:30 - 00:45: charge up to 37.1 %
Wed 01:00 - 05:30: charge up to 49.9 %
Wed 05:45 - 06:00: charge up to 50.4 %
Wed 22:00 - 00:00: charge up to 35.5 %Die 31 Tests rechnen die Beispielwoche aus dem Video nach (welche Viertelstunden, in welcher Reihenfolge, zu welchem Preis und wo der Ladestand endet) und prüfen jede Regel mit kleinen Fällen, die du von Hand nachvollziehen kannst:
| # | Fall | Erwartet |
|---|---|---|
| 1 | 22 % zu Beginn, das Haus braucht 5 Punkte je Stunde | bleibt bei 20 %; 3, dann 5 Punkte ungeplant am Boden |
| 2 | die Marke in Stunde 0, 48, 72 und 144 | 30, 32, 34 und 40 % |
| 3 | 20 ct vor der ersten Lücke, 5 ct dahinter | zuerst 20 ct; 5 ct erst, wenn die Lücke dort ankommt |
| 4 | eine Lücke innerhalb der bekannten Preise, jede Stunde 80 ct | wird trotzdem gekauft |
| 5 | Akku bei 89,5 %, die billigsten Stunden gleich am Anfang | übersprungen: Sie würden über 90 % enden |
| 6 | Vorrat mit 24, 26, 30 und 40 ct bekannt | nur 24 ct; mit einem Perzentil von 23 ct nichts |
| 7 | −1 ct in einer Stunde, keine Lücke, Akku bei 89 % | trotzdem geladen, bis 93 % |
Alle sieben sind Tests im Paket, benannt nach dem, was sie prüfen.
def test_twenty_three_plus_five_quarter_hours(week):
_, plan = week
assert len(plan.chosen(NEED)) == 23
assert len(plan.chosen(RESERVE)) == 5
assert plan.bought_pp() == pytest.approx(28.0)weekist eine Fixture: pytest rechnet die Beispielwoche einmal und gibt den Plan jedem Test in der Datei.pytest.approxvergleicht Dezimalzahlen mit einer winzigen Toleranz; Rundung in der letzten Stelle lässt keinen Test scheitern.
Variablen
Die wichtigsten Namen mit Einheit und typischem Bereich. Alle Bereiche sind Beispielbereiche, keine Werte einer echten Anlage. Die vollständige Liste steht in VARIABLEN.md im Paket.
| Name | Einheit | Bedeutung | Typischer Bereich | Kommt von |
|---|---|---|---|---|
start_pct | % | Ladestand jetzt | Boden … 100 | ein Messwert, dem du vertraust, frisch |
solar_pp | Punkte/h | Was die Sonne bringt; None = unbekannt, in dieser Stunde wird nichts gekauft | 0 nachts … mehrere Punkte mittags | deine Solarprognose |
demand_pp | Punkte/h | Alles, was das Haus verbraucht: Muster, Wärmepumpe, Auto, Standby | etwa 1 … 2,5 im Winter | gelernte Muster und Wärmepumpen-Prognose |
losses_pp | Punkte/h | Wandlungsverluste: Dauerverbrauch plus Anteil je Richtung | etwa 0,1 … 0,3 | gemessen oder geschätzt |
prices_ct | ct/kWh | Preis je Viertelstunde, None = noch unbekannt | etwa −5 … 60 | dein dynamischer Tarif |
| Name | Einheit | Bedeutung | Typischer Bereich | Kommt von |
|---|---|---|---|---|
floor_pct | % | Tiefster erlaubter Ladestand | 10 … 30 (Beispiel 20) | Einstellung |
buffer_pts | Punkte | Puffer über dem Boden | 5 … 15 (Beispiel 10) | Einstellung |
step_pts_per_day | Punkte | Zuschlag je Tag ab Stunde 48 | 0 … 4 (Beispiel 2) | Einstellung; am besten aus Prognosefehlern gelernt |
charge_pp_per_quarter | Punkte | Was eine Ladeviertelstunde bringt | Beispiel 1,0 (neutral) | Ladeleistung, Wirkungsgrad und Akkugröße |
max_target_pct | % | Hier endet das Netzladen (außer bei Nullpreis) | 80 … 95 (Beispiel 90) | Einstellung |
full_hour | Uhrzeit | Tägliche Stunde, die der Sonne gehört | Beispiel 15 | Einstellung |
reserve_max_ct | ct/kWh | Klar billig: höchstens dieser Preis | Beispiel 25 | Einstellung |
reserve_percentile_ct | ct/kWh | Klar billig: 30. Perzentil aus 14 Tagen | Beispiel 27,7 | percentile() deines Preisverlaufs |
gap_pp | Punkte | Lücke bis zum letzten bekannten Preis, ohne Kauf | 0 … | make_plan() |
target_pct | % | „Lade bis …“ am Ende eines Ladeblocks | Boden … 90 | Plan.blocks() |
An dein Haus anpassen
- Dein Akku in Punkten: Punkte = kWh ÷ nutzbare kWh × 100. Die Ladegeschwindigkeit in Punkten je Viertelstunde = Ladeleistung (kW) × 0,25 h × Ladewirkungsgrad ÷ nutzbare kWh × 100.
- Eigener Boden und Puffer:
MarkSettings(floor_pct=…, buffer_pts=…, step_pts_per_day=…). Stell dir die zwei Fragen aus dem Video: Wie groß sind die Spitzen, die das Netz nicht abdeckt, und wie weit driftet deine Ladestandsanzeige? - Du bekommst Geld für eingespeisten Solarstrom: Der Plan geht davon aus, dass das Haus nichts verkauft, und kauft deshalb nie eine Ladung, die die Sonne morgen bringen würde. Mit Einspeisevergütung vergleiche den Preis eines Zukaufs mit dem, was die Sonne hätte verdienen können.
- Keine Wärmepumpe: einfach aus
demand_ppweglassen. Ein fester Tarif: Es gibt nichts zu planen, jede Viertelstunde kostet gleich viel. - Nur Stundenpreise: jeden Preis viermal wiederholen. Noch kein Preisverlauf:
reserve_percentile_ctaufNonelassen; dann gelten nur die beiden anderen Prüfungen. - Oft rechnen: alle 15 Minuten und bei neuen Preisen, nur mit frischen Eingängen. Ein veralteter Ladestand heißt: kein Plan.
Du arbeitest mit einem KI-Assistenten? Gib ihm das Paket, VARIABLEN.md und die Namen deiner eigenen Prognosen und Messwerte und bitte ihn um den Adapter, der daraus Hour-Werte und Viertelstundenpreise macht. Die Tests zeigen dir, ob die Logik danach noch stimmt.
Download
Code-Paket: Ladeplan
Quellcode, die Beispielwoche, 31 Tests, die Variablenliste und Hinweise zur Herkunft. Nur Python-Standardbibliothek.
- Datei
ladeplan-1.0.0.zip- Version
- 1.0.0 · 13. Oktober 2026
- Größe
- 28 KB · 14 Dateien
- Lizenz
- MIT · siehe Hinweis im Impressum
- Preis
- Kostenlos
- SHA-256
05c5da41f37f244e199d81c55ca9bfb76f5e6974646c24fd8168bf64b26d0e67
Zip herunterladen (28 KB)Prüfsummendatei
Zum Prüfen des Downloads die SHA-256-Prüfsumme vergleichen: certutil -hashfile ladeplan-1.0.0.zip SHA256 unter Windows, shasum -a 256 ladeplan-1.0.0.zip unter macOS, sha256sum ladeplan-1.0.0.zip unter Linux.
Stand und Herkunft
Quellen
- SMARD – Strommarktdaten für Deutschland, u. a. Großhandelspreise (Bundesnetzagentur)(extern)
- European Power Exchange: so funktioniert die tägliche Day-Ahead-Auktion (Wikipedia, englisch)(extern)
- Quantil (Wikipedia)(extern)
- pytest-Dokumentation (englisch)(extern)
- Die MIT-Lizenz (Open Source Initiative, englisch)(extern)
- Echter Oktoberplan: eigene Auswertung des laufenden Systems, nur Prozent und Cent, gerundet; die Beispielwoche nutzt erfundene Eingaben (eigene Daten)
Links geprüft am 13. Oktober 2026.